mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-27 16:02:37 +00:00
render: build indexed triangles for merged ES/GS shaders
Co-authored-by: shadowbeat070 <70843316+shadowbeat070@users.noreply.github.com>
This commit is contained in:
1 parent
843c0c59be
commit
3ab372ccec
6 files changed
+163
-19
No files matched your search
@@ -713,6 +713,7 @@ struct DrawIndexBufferSource {
|
||||
const void* host_data = nullptr;
|
||||
uint64_t size = 0;
|
||||
vk::IndexType type = vk::IndexType::eUint16;
|
||||
uint32_t guest_element_size = 0;
|
||||
};
|
||||
|
||||
struct PreparedIndexBuffer {
|
||||
@@ -1129,15 +1130,15 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff
|
||||
uint32_t mesh_groups = 0;
|
||||
if (mesh_active) {
|
||||
const auto& mesh = state.vs_input_info.mesh;
|
||||
if (emit.indexed || ucfg.GetPrimType() != Prospero::PrimitiveType::kTriStrip ||
|
||||
primitive_restart_enable || mesh.primitives_per_group == 0) {
|
||||
if (primitive_restart_enable || mesh.primitives_per_group == 0) {
|
||||
EXIT("unsupported mesh draw: primitive=%u indexed=%u restart=%u\n",
|
||||
static_cast<uint32_t>(ucfg.GetPrimType()), emit.indexed, primitive_restart_enable);
|
||||
}
|
||||
if (draw.index_count < 3 || draw.instance_count == 0) {
|
||||
const auto primitives = mesh.InputPrimitiveCount(draw.index_count);
|
||||
if (primitives == 0 || draw.instance_count == 0) {
|
||||
return;
|
||||
}
|
||||
mesh_groups = (draw.index_count - 3u) / mesh.primitives_per_group + 1u;
|
||||
mesh_groups = (primitives - 1u) / mesh.primitives_per_group + 1u;
|
||||
const auto& limits = m_context.GetGraphics().mesh_shader_properties;
|
||||
if (mesh_groups > limits.maxMeshWorkGroupCount[0] ||
|
||||
draw.instance_count > limits.maxMeshWorkGroupCount[1] ||
|
||||
@@ -1148,6 +1149,13 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff
|
||||
}
|
||||
}
|
||||
|
||||
if (mesh_active && emit.indexed) {
|
||||
// Register the original guest indices for shader reads; PrepareGraphicsBindings
|
||||
// synchronizes registered BDA ranges before any draw commands are committed.
|
||||
(void)m_context.GetBufferCache().FindBuffer(
|
||||
index_source.address, static_cast<uint64_t>(draw.index_count) *
|
||||
index_source.guest_element_size);
|
||||
}
|
||||
LogDrawPhase(draw.name, "PrepareBindings");
|
||||
auto bindings = PrepareGraphicsBindings(state.vs_input_info.stage, state.ps_input_info.stage,
|
||||
state.ps_active);
|
||||
@@ -1195,7 +1203,12 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff
|
||||
CommitBindings(buffer, vk::PipelineBindPoint::eGraphics, pipeline,
|
||||
std::span {descriptor_stages.data(), descriptor_stage_count});
|
||||
if (mesh_active) {
|
||||
const uint32_t draw_data[] {draw.index_count, emit.first_vertex, emit.first_instance};
|
||||
const uint32_t draw_data[] {
|
||||
draw.index_count,
|
||||
emit.indexed ? static_cast<uint32_t>(emit.vertex_offset) : emit.first_vertex,
|
||||
emit.first_instance, index_source.guest_element_size,
|
||||
static_cast<uint32_t>(index_source.address),
|
||||
static_cast<uint32_t>(index_source.address >> 32u)};
|
||||
static_assert(std::size(draw_data) == ShaderRecompiler::IR::PushData::MeshDrawDwordCount);
|
||||
vk_buffer.pushConstants(pipeline.pipeline_layout,
|
||||
vk::ShaderStageFlagBits::eMeshEXT |
|
||||
@@ -1342,6 +1355,7 @@ void RenderExecutor::DrawIndex(uint64_t submit_id, CommandBuffer& buffer,
|
||||
index_source.size =
|
||||
expanded_indices.empty() ? index_size : expanded_indices.size() * sizeof(uint16_t);
|
||||
index_source.type = index_type;
|
||||
index_source.guest_element_size = static_cast<uint32_t>(index_size / args.index_count);
|
||||
|
||||
DrawRenderState state {};
|
||||
if (!PrepareDrawRenderState(submit_id, buffer, draw, args.render_target_slice_offset, true,
|
||||
|
||||
@@ -1079,11 +1079,17 @@ IR::Program TranslateProgram(const Decoder::Program& decoded, const CFG::Graph&
|
||||
return entry_ir.ISub(lhs, minimum(lhs, rhs));
|
||||
};
|
||||
const auto local = builtin(IR::StageInputKind::LocalInvocationIndex);
|
||||
const auto chunk = entry_ir.IMul(builtin(IR::StageInputKind::WorkgroupId, 0),
|
||||
u32(mesh.primitives_per_group));
|
||||
const auto primitive_chunk = entry_ir.IMul(builtin(IR::StageInputKind::WorkgroupId, 0),
|
||||
u32(mesh.primitives_per_group));
|
||||
const auto step = u32(mesh.InputPrimitiveStep());
|
||||
const auto chunk = entry_ir.IMul(primitive_chunk, step);
|
||||
const auto vertices =
|
||||
minimum(subtract_saturate(draw(0), chunk), u32(mesh.vertices_per_group));
|
||||
const auto primitives = subtract_saturate(vertices, u32(2));
|
||||
const auto primitives = entry_ir.Select(
|
||||
entry_ir.ULessThan(vertices, u32(3)), u32(0),
|
||||
entry_ir.IAdd(IR::U32(entry_ir.Emit(IR::ValueOpcode::UDiv32,
|
||||
{subtract_saturate(vertices, u32(3)), step})),
|
||||
u32(1)));
|
||||
const auto wave = entry_ir.ShiftRightLogical(local, u32(6));
|
||||
const auto wave_base = entry_ir.BitwiseAnd(local, u32(~63u));
|
||||
const auto vertex_count = minimum(subtract_saturate(vertices, wave_base), u32(64));
|
||||
@@ -1095,18 +1101,41 @@ IR::Program TranslateProgram(const Decoder::Program& decoded, const CFG::Graph&
|
||||
entry_ir.BitwiseOr(wave_info, entry_ir.BitwiseOr(entry_ir.ShiftLeftLogical(
|
||||
primitive_count, u32(8)),
|
||||
vertex_count)));
|
||||
// Triangle-strip adjacency addresses the ES records in LDS. Alternating the
|
||||
// first two indices preserves winding across subgroup boundaries.
|
||||
const auto parity = entry_ir.BitwiseAnd(entry_ir.IAdd(chunk, local), u32(1));
|
||||
const auto first = entry_ir.IAdd(local, parity);
|
||||
const auto second = entry_ir.ISub(entry_ir.IAdd(local, u32(1)), parity);
|
||||
// GS adjacency addresses local ES records in LDS. Strip winding alternates
|
||||
// with the global primitive number, including across subgroup boundaries.
|
||||
const auto parity = mesh.InputPrimitiveStep() == 1u
|
||||
? entry_ir.BitwiseAnd(entry_ir.IAdd(primitive_chunk, local), u32(1))
|
||||
: u32(0);
|
||||
const auto vertex = entry_ir.IMul(local, step);
|
||||
const auto first = entry_ir.IAdd(vertex, parity);
|
||||
const auto second = entry_ir.ISub(entry_ir.IAdd(vertex, u32(1)), parity);
|
||||
entry_ir.SetVectorReg(static_cast<IR::VectorReg>(0),
|
||||
entry_ir.BitwiseOr(entry_ir.ShiftLeftLogical(first, u32(2)),
|
||||
entry_ir.ShiftLeftLogical(second, u32(18))));
|
||||
entry_ir.SetVectorReg(static_cast<IR::VectorReg>(1),
|
||||
entry_ir.ShiftLeftLogical(entry_ir.IAdd(local, u32(2)), u32(2)));
|
||||
entry_ir.ShiftLeftLogical(entry_ir.IAdd(vertex, u32(2)), u32(2)));
|
||||
const auto input_vertex = entry_ir.IAdd(chunk, local);
|
||||
const auto index_bytes = draw(3);
|
||||
const auto indexed = entry_ir.INotEqual(index_bytes, u32(0));
|
||||
const auto index_low = draw(4);
|
||||
const auto byte_offset = entry_ir.IAdd(entry_ir.BitwiseAnd(index_low, u32(3)),
|
||||
entry_ir.IMul(input_vertex, index_bytes));
|
||||
const auto index_resource = entry_ir.Emit(
|
||||
IR::ValueOpcode::GetAddressResource,
|
||||
{entry_ir.BitwiseAnd(index_low, u32(~3u)), draw(5)});
|
||||
const auto memory_index = static_cast<uint32_t>(result.memory_info.size());
|
||||
result.memory_info.push_back({.kind = IR::ResourceKind::Global});
|
||||
const auto packed_index = entry_ir.Emit(
|
||||
IR::ValueOpcode::LoadAddressU32,
|
||||
{index_resource, entry_ir.BitwiseAnd(byte_offset, u32(~3u)), u32(0),
|
||||
entry_ir.LogicalAnd(indexed, entry_ir.ULessThan(local, vertices))},
|
||||
IR::MemoryFlags {.index = memory_index});
|
||||
const auto index = IR::U32(entry_ir.Emit(
|
||||
IR::ValueOpcode::BitFieldUExtract,
|
||||
{packed_index, entry_ir.IMul(entry_ir.BitwiseAnd(byte_offset, u32(3)), u32(8)),
|
||||
entry_ir.IMul(index_bytes, u32(8))}));
|
||||
entry_ir.SetVectorReg(static_cast<IR::VectorReg>(5),
|
||||
entry_ir.IAdd(draw(1), entry_ir.IAdd(chunk, local)));
|
||||
entry_ir.IAdd(draw(1), entry_ir.Select(indexed, index, input_vertex)));
|
||||
entry_ir.SetVectorReg(
|
||||
static_cast<IR::VectorReg>(8),
|
||||
entry_ir.IAdd(draw(2), builtin(IR::StageInputKind::WorkgroupId, 1)));
|
||||
|
||||
@@ -273,7 +273,7 @@ static_assert(static_cast<uint32_t>(DescriptorBindingKind::Count) == 43u);
|
||||
|
||||
struct PushData {
|
||||
static constexpr uint32_t DwordCount = 32;
|
||||
static constexpr uint32_t MeshDrawDwordCount = 3;
|
||||
static constexpr uint32_t MeshDrawDwordCount = 6;
|
||||
static constexpr uint32_t NoStart = UINT32_MAX;
|
||||
std::array<uint32_t, DwordCount> dwords {};
|
||||
|
||||
|
||||
@@ -878,7 +878,8 @@ ShaderParams PrepareProgram(const HW::VertexShaderInfo& regs, const HW::Context&
|
||||
EXIT_NOT_IMPLEMENTED(regs.gs_regs.rsrc1.gs_vgpr_component_count != 3u ||
|
||||
regs.gs_regs.rsrc2.es_vgpr_component_count != 3u);
|
||||
const auto& group = user_config.GetGeControl();
|
||||
if (user_config.GetPrimType() != Prospero::PrimitiveType::kTriStrip ||
|
||||
if ((user_config.GetPrimType() != Prospero::PrimitiveType::kTriStrip &&
|
||||
user_config.GetPrimType() != Prospero::PrimitiveType::kTriList) ||
|
||||
sh.m_vgtGsOutPrimType != 2u || sh.m_vgtGsMaxVertOut < 3u || group.vertex_group_size < 3u ||
|
||||
mesh.max_vertices == 0u) {
|
||||
EXIT("unsupported GS assembly: input=%u output=%u vertices=%u GE=%u/%u max_output=%u\n",
|
||||
@@ -887,10 +888,10 @@ ShaderParams PrepareProgram(const HW::VertexShaderInfo& regs, const HW::Context&
|
||||
}
|
||||
mesh.max_primitives = group.primitive_group_size * (sh.m_vgtGsMaxVertOut - 2u);
|
||||
mesh.primitives_per_group = std::min({static_cast<uint32_t>(group.primitive_group_size),
|
||||
static_cast<uint32_t>(group.vertex_group_size) - 2u,
|
||||
mesh.InputPrimitiveCount(group.vertex_group_size),
|
||||
mesh.max_vertices / sh.m_vgtGsMaxVertOut});
|
||||
EXIT_IF(mesh.primitives_per_group == 0u);
|
||||
mesh.vertices_per_group = mesh.primitives_per_group + 2u;
|
||||
mesh.vertices_per_group = mesh.InputVertexCount(mesh.primitives_per_group);
|
||||
mesh.threads_num[0] =
|
||||
((mesh.max_vertices + mesh.wave_size - 1u) / mesh.wave_size) * mesh.wave_size;
|
||||
mesh.threads_num[1] = mesh.threads_num[2] = 1u;
|
||||
|
||||
@@ -78,6 +78,16 @@ struct ShaderMeshInputInfo: ShaderWorkgroupInputInfo {
|
||||
uint32_t max_vertices = 0;
|
||||
uint32_t max_primitives = 0;
|
||||
uint32_t provoking_vertex = 0;
|
||||
|
||||
[[nodiscard]] constexpr uint32_t InputPrimitiveStep() const {
|
||||
return input_primitive == static_cast<uint32_t>(Prospero::PrimitiveType::kTriList) ? 3u : 1u;
|
||||
}
|
||||
[[nodiscard]] constexpr uint32_t InputPrimitiveCount(uint32_t vertices) const {
|
||||
return vertices < 3u ? 0u : (vertices - 3u) / InputPrimitiveStep() + 1u;
|
||||
}
|
||||
[[nodiscard]] constexpr uint32_t InputVertexCount(uint32_t primitives) const {
|
||||
return primitives == 0u ? 0u : (primitives - 1u) * InputPrimitiveStep() + 3u;
|
||||
}
|
||||
};
|
||||
|
||||
struct ShaderVertexInputInfo {
|
||||
|
||||
@@ -9054,6 +9054,95 @@ void TestFusedShaderHandoffPreservesRegisters() {
|
||||
Check(allocations == 1u, "fused shader omitted the back shader allocation");
|
||||
}
|
||||
|
||||
void TestMeshInputAssembly() {
|
||||
using namespace ShaderRecompiler;
|
||||
using namespace ShaderRecompiler::IR;
|
||||
struct Case {
|
||||
Prospero::PrimitiveType topology;
|
||||
uint32_t capacity, count, group, lane, width, address_low, base_vertex;
|
||||
uint32_t wave_info, first, second, third, byte_offset, vertex_id;
|
||||
bool fetch;
|
||||
};
|
||||
const Case cases[] = {
|
||||
{Prospero::PrimitiveType::kTriList, 14, 177, 14, 2, 2, 0x1002, UINT32_MAX,
|
||||
0x40000309, 6, 7, 8, 340, 0xabcc, true},
|
||||
{Prospero::PrimitiveType::kTriList, 14, 177, 14, 9, 2, 0x1002, 0,
|
||||
0x40000309, 27, 28, 29, 356, 0, false},
|
||||
{Prospero::PrimitiveType::kTriList, 8, 180, 0, 64, 2, 0x1002, 0,
|
||||
0x41000000, 192, 193, 194, 128, 0, false},
|
||||
{Prospero::PrimitiveType::kTriList, 8, 180, 1, 1, 1, 0x1000, 5,
|
||||
0x40000206, 3, 4, 5, 4, 0xb0, true},
|
||||
{Prospero::PrimitiveType::kTriList, 8, 180, 1, 1, 4, 0x1000, 5,
|
||||
0x40000206, 3, 4, 5, 28, 0xabcd0128, true},
|
||||
{Prospero::PrimitiveType::kTriStrip, 5, 8, 1, 1, 0, 0, 11,
|
||||
0x40000305, 1, 2, 3, 0, 15, false},
|
||||
{Prospero::PrimitiveType::kTriStrip, 5, 8, 0, 1, 0, 0, 11,
|
||||
0x40000305, 2, 1, 3, 0, 12, false},
|
||||
};
|
||||
for (const auto &test : cases) {
|
||||
ShaderVertexInputInfo input{};
|
||||
auto &mesh = input.mesh;
|
||||
mesh.input_primitive = static_cast<uint32_t>(test.topology);
|
||||
mesh.primitives_per_group = mesh.InputPrimitiveCount(test.capacity);
|
||||
mesh.vertices_per_group = mesh.InputVertexCount(mesh.primitives_per_group);
|
||||
mesh.threads_num[0] = 256;
|
||||
mesh.threads_num[1] = mesh.threads_num[2] = 1;
|
||||
Decoder::Program decoded;
|
||||
CFG::Graph graph;
|
||||
CFG::BasicBlock block;
|
||||
block.id = 0;
|
||||
block.terminator.kind = CFG::TerminatorKind::Return;
|
||||
graph.blocks.push_back(std::move(block));
|
||||
graph.entry_block = 0;
|
||||
Frontend::TranslateOptions options{};
|
||||
options.stage = ShaderType::Mesh;
|
||||
options.wave_size = 64;
|
||||
options.user_data_count = 0;
|
||||
options.vertex = &input;
|
||||
auto program = Frontend::TranslateProgram(decoded, graph, options);
|
||||
const uint32_t draw[] = {test.count, test.base_vertex, 7, test.width,
|
||||
test.address_low, 0x12};
|
||||
Inst *load = nullptr;
|
||||
for (auto &inst : *program.blocks.front()) {
|
||||
if (inst.GetOpcode() == ValueOpcode::MeshDrawParameter) {
|
||||
inst.ReplaceUsesWith(Value(draw[inst.Arg(0).U32()]));
|
||||
} else if (inst.GetOpcode() == ValueOpcode::GetBuiltin) {
|
||||
const auto kind = static_cast<StageInputKind>(inst.Arg(0).U32());
|
||||
const uint32_t value = kind == StageInputKind::LocalInvocationIndex
|
||||
? test.lane
|
||||
: inst.Arg(1).U32() == 0 ? test.group : 2;
|
||||
inst.ReplaceUsesWith(Value(value));
|
||||
} else if (inst.GetOpcode() == ValueOpcode::LoadAddressU32) {
|
||||
Check(load == nullptr, "mesh index fetch emitted duplicate loads");
|
||||
load = &inst;
|
||||
}
|
||||
}
|
||||
ConstantPropagationPass(program.blocks);
|
||||
Check(load != nullptr && load->Arg(1).Resolve().U32() == test.byte_offset &&
|
||||
load->Arg(3).Resolve().U1() == test.fetch,
|
||||
"mesh index fetch address or active-lane predicate is wrong");
|
||||
const auto *resource = load->Arg(0).ResolveInstruction();
|
||||
Check(resource != nullptr && resource->Arg(0).Resolve().U32() == (test.address_low & ~3u) &&
|
||||
resource->Arg(1).Resolve().U32() == 0x12 &&
|
||||
program.memory_info[load->Flags<MemoryFlags>().index].kind == ResourceKind::Global,
|
||||
"mesh index fetch lost its aligned guest address resource");
|
||||
load->ReplaceUsesWith(Value(test.fetch ? 0xabcd0123u : 0u));
|
||||
ConstantPropagationPass(program.blocks);
|
||||
std::array<uint32_t, 9> vgprs{};
|
||||
uint32_t sgpr3 = 0;
|
||||
for (const auto &inst : *program.blocks.front()) {
|
||||
if (inst.GetOpcode() == ValueOpcode::SetVectorRegister) {
|
||||
vgprs[RegIndex(inst.Arg(0).VectorRegister())] = inst.Arg(1).Resolve().U32();
|
||||
} else if (inst.GetOpcode() == ValueOpcode::SetScalarRegister) {
|
||||
sgpr3 = inst.Arg(1).Resolve().U32();
|
||||
}
|
||||
}
|
||||
Check(sgpr3 == test.wave_info && vgprs[0] == ((test.first << 2) | (test.second << 18)) &&
|
||||
vgprs[1] == test.third * 4 && vgprs[5] == test.vertex_id && vgprs[8] == 9,
|
||||
"mesh prolog changed triangle assembly, wave counts, vertex ID, or instance ID");
|
||||
}
|
||||
}
|
||||
|
||||
void TestNewShaderRecompilerSetpcJumpTable() {
|
||||
const uint32_t shader[] = {
|
||||
EncodeSop2(0x07, 0, 0, 129), // s_min_u32 s0, s0, 1
|
||||
@@ -12317,6 +12406,7 @@ int main() {
|
||||
TestNewShaderRecompilerBranchConditionForms();
|
||||
TestNewShaderRecompilerSetpcBranch();
|
||||
TestFusedShaderHandoffPreservesRegisters();
|
||||
TestMeshInputAssembly();
|
||||
TestNewShaderRecompilerSetpcJumpTable();
|
||||
TestNewShaderRecompilerPrunesUnreachableSetpcMetadata();
|
||||
TestNewShaderRecompilerSetpcDwordJumpTable();
|
||||
|
||||
Reference in new issue
Block a user