Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion gpu/vk/eden/shader_recompiler/backend/spirv/emit_spirv.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -486,7 +486,7 @@ void SetupCapabilities(const Profile& profile, const Info& info, EmitContext& ct
if ((!profile.support_vertex_instance_id &&
(info.loads[IR::Attribute::InstanceId] || info.loads[IR::Attribute::VertexId])) ||
info.loads[IR::Attribute::BaseInstance] || info.loads[IR::Attribute::BaseVertex] ||
info.loads[IR::Attribute::DrawID] || ctx.wants_mesh_first) { // (hookshot: a task program's DrawIndex, its draw's first)
info.loads[IR::Attribute::DrawID] || (ctx.wants_mesh_first && !ctx.mesh_fallback)) {
ctx.AddExtension("SPV_KHR_shader_draw_parameters");
ctx.AddCapability(spv::Capability::DrawParameters);
}
Expand Down
9 changes: 6 additions & 3 deletions gpu/vk/eden/shader_recompiler/backend/spirv/emit_spirv.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,12 @@ constexpr u32 MESH_FIRST_PUSH_OFFSET = GUEST_PAGES_PUSH_OFFSET + 8;
constexpr u32 MESH_FALLBACK_PUSH_OFFSET = MESH_FIRST_PUSH_OFFSET + 8;
constexpr u32 MESH_FALLBACK_PAYLOAD_PUSH_OFFSET = MESH_FALLBACK_PUSH_OFFSET;
constexpr u32 MESH_FALLBACK_RECORDS_PUSH_OFFSET = MESH_FALLBACK_PUSH_OFFSET + 8;
constexpr u32 MESH_FALLBACK_PUSH_END = MESH_FALLBACK_PUSH_OFFSET + 16;
// (hookshot) the fallback's mesh workgroups a draw can run: each gets a record slot and a payload. The vertex stage draws
// this many slots and holds the ones no mesh workgroup wrote to outside the clip volume.
constexpr u32 MESH_FALLBACK_CAPACITY_PUSH_OFFSET = MESH_FALLBACK_PUSH_OFFSET + 16;
constexpr u32 MESH_FALLBACK_VERTICES_PUSH_OFFSET = MESH_FALLBACK_PUSH_OFFSET + 20;
constexpr u32 MESH_FALLBACK_INDIRECT_INPUT_PUSH_OFFSET = MESH_FALLBACK_PUSH_OFFSET + 24;
constexpr u32 MESH_FALLBACK_INDIRECT_OUTPUT_PUSH_OFFSET = MESH_FALLBACK_PUSH_OFFSET + 32;
constexpr u32 MESH_FALLBACK_PUSH_END = MESH_FALLBACK_PUSH_OFFSET + 40;
// The fallback's small arena for direct draws. Indirect task draws share a larger arena.
constexpr u32 MESH_FALLBACK_CAPACITY = 1024;

[[nodiscard]] std::vector<u32> EmitSPIRV(const Profile& profile, const RuntimeInfo& runtime_info, IR::Program& program, Bindings& bindings);
Expand Down
77 changes: 54 additions & 23 deletions gpu/vk/eden/shader_recompiler/backend/spirv/emit_spirv_mesh.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,17 +35,25 @@ Id Clamped(EmitContext& ctx, Id index, u32 count) {
}

Id PushU64(EmitContext& ctx, u32 member);
Id PushU32(EmitContext& ctx, u32 member) {
const Id pointer{ctx.TypePointer(spv::StorageClass::PushConstant, ctx.U32[1])};
return ctx.OpLoad(ctx.U32[1], ctx.OpAccessChain(pointer, ctx.rescaling_push_constants, ctx.Const(member)));
}
Id FallbackCapacity(EmitContext& ctx) {
return PushU32(ctx, ctx.mesh_fallback_capacity_member_index);
}
Id WorkgroupId(EmitContext& ctx);
Id BufferWordAt(EmitContext& ctx, Id base, Id word);
Id BufferWord(EmitContext& ctx, u32 member, Id word);

Id PayloadWord(EmitContext& ctx, Id index) {
if (ctx.mesh_fallback && ctx.stage == Stage::Mesh && ctx.runtime_info.mesh_fallback_task) {
// (the task wrote it out: the mesh workgroups are a later dispatch, one payload each)
const Id slot{WorkgroupId(ctx)};
// All mesh workgroups emitted by one task workgroup read the same payload.
const Id task_group{ctx.OpUDiv(ctx.U32[1], WorkgroupId(ctx),
ctx.Const(ctx.runtime_info.mesh_fallback_task_threads))};
const Id first{ctx.OpIAdd(
ctx.U32[1], ctx.Const(MESH_FALLBACK_CAPACITY),
ctx.OpIMul(ctx.U32[1], slot, ctx.Const(ctx.mesh_info.payload_words)))};
ctx.U32[1], FallbackCapacity(ctx),
ctx.OpIMul(ctx.U32[1], task_group, ctx.Const(ctx.mesh_info.payload_words)))};
return BufferWordAt(ctx, PushU64(ctx, ctx.mesh_fallback_payload_member_index),
ctx.OpIAdd(ctx.U32[1], first,
Clamped(ctx, index, ctx.mesh_info.payload_words)));
Expand Down Expand Up @@ -195,7 +203,8 @@ void MeshFallbackEpilogue(EmitContext& ctx) {
const u32 per_slot{mesh.max_primitives * corners};
Barrier(ctx);
const Id slot{WorkgroupId(ctx)};
If(ctx, ctx.OpULessThan(ctx.U1, slot, ctx.Const(MESH_FALLBACK_CAPACITY)), [&] {
const Id capacity{FallbackCapacity(ctx)};
If(ctx, ctx.OpULessThan(ctx.U1, slot, capacity), [&] {
Id count{ctx.OpUMin(ctx.U32[1], ctx.OpLoad(ctx.U32[1], ctx.mesh_primitive_count),
ctx.Const(mesh.max_primitives))};
if (ctx.runtime_info.mesh_fallback_task) {
Expand Down Expand Up @@ -224,7 +233,7 @@ void MeshFallbackEpilogue(EmitContext& ctx) {
ctx.OpIAdd(ctx.U32[1],
ctx.OpIMul(ctx.U32[1], primitive, ctx.Const(corners)),
ctx.Const(corner)))};
const Id word{ctx.OpIAdd(ctx.U32[1], ctx.Const(MESH_FALLBACK_CAPACITY),
const Id word{ctx.OpIAdd(ctx.U32[1], capacity,
ctx.OpIMul(ctx.U32[1], record, ctx.Const(words)))};
StoreWord(ctx, BufferWord(ctx, ctx.mesh_fallback_records_member_index, word),
ctx.Const(1U));
Expand Down Expand Up @@ -292,30 +301,44 @@ void TaskEpilogue(EmitContext& ctx) {
const Id count{ctx.OpUMin(ctx.U32[1], ctx.OpLoad(ctx.U32[1], PayloadWord(ctx, ctx.Const(1U))),
ctx.Const(task_cap(ctx.mesh_threads)))};
if (ctx.mesh_fallback) {
// (no OpEmitMeshTasksEXT: the mesh workgroups are a fixed dispatch; each thread that started one writes its
// payload to that workgroup's slot, so the mesh dispatch reads it back)
const Id group{WorkgroupId(ctx)};
const Id invocation{LocalInvocation(ctx)};
const Id capacity{FallbackCapacity(ctx)};
const Id task_capacity{ctx.OpUDiv(ctx.U32[1], capacity, ctx.Const(ctx.mesh_threads))};
const Id input{PushU64(ctx, ctx.mesh_fallback_indirect_input_member_index)};
const Id output{PushU64(ctx, ctx.mesh_fallback_indirect_output_member_index)};
const Id first_lane{ctx.OpIEqual(ctx.U1, ctx.OpBitwiseOr(ctx.U32[1], group, invocation), ctx.u32_zero_value)};
If(ctx, ctx.OpLogicalAnd(ctx.U1, first_lane, ctx.OpINotEqual(ctx.U1, input, ctx.Constant(ctx.U64, u64{0}))), [&] {
const Id groups{ctx.OpUMin(ctx.U32[1], task_capacity, LoadWord(ctx, BufferWordAt(ctx, input, ctx.Const(0U))))};
const Id slots{ctx.OpIMul(ctx.U32[1], groups, ctx.Const(ctx.mesh_threads))};
const Id vertices{PushU32(ctx, ctx.mesh_fallback_vertices_member_index)};
// VkDispatchIndirectCommand at byte 0, VkDrawIndirectCommand at byte 16.
StoreWord(ctx, BufferWordAt(ctx, output, ctx.Const(0U)), slots);
StoreWord(ctx, BufferWordAt(ctx, output, ctx.Const(1U)), ctx.Const(1U));
StoreWord(ctx, BufferWordAt(ctx, output, ctx.Const(2U)), ctx.Const(1U));
StoreWord(ctx, BufferWordAt(ctx, output, ctx.Const(4U)), ctx.OpIMul(ctx.U32[1], slots, vertices));
StoreWord(ctx, BufferWordAt(ctx, output, ctx.Const(5U)), ctx.Const(1U));
});
const Id slot{ctx.OpIAdd(ctx.U32[1],
ctx.OpIMul(ctx.U32[1], WorkgroupId(ctx), ctx.Const(ctx.mesh_threads)),
LocalInvocation(ctx))};
ctx.OpIMul(ctx.U32[1], group, ctx.Const(ctx.mesh_threads)), invocation)};
// (the count is how many meshlets this task workgroup started: the first that many threads, not all of them.
// Native launches one mesh workgroup a started meshlet; the fallback dispatches a fixed task_threads a workgroup
// and the rest stay culled.)
const Id started{ctx.OpULessThan(ctx.U1, LocalInvocation(ctx), count)};
If(ctx, ctx.OpULessThan(ctx.U1, slot, ctx.Const(MESH_FALLBACK_CAPACITY)), [&] {
const Id started{ctx.OpULessThan(ctx.U1, invocation, count)};
If(ctx, ctx.OpULessThan(ctx.U1, slot, capacity), [&] {
// (a u32 word: 1 when the meshlet started, 0 when culled. A bool stored there is invalid SPIR-V, which RADV's
// parser rejects and its release build crashed on)
StoreWord(ctx, BufferWordAt(ctx, PushU64(ctx, ctx.mesh_fallback_payload_member_index), slot),
ctx.OpSelect(ctx.U32[1], started, ctx.Const(1U), ctx.u32_zero_value));
If(ctx, started, [&] {
const Id first{ctx.OpIAdd(
ctx.U32[1], ctx.Const(MESH_FALLBACK_CAPACITY),
ctx.OpIMul(ctx.U32[1], slot, ctx.Const(ctx.mesh_info.payload_words)))};
const Id invocation{LocalInvocation(ctx)};
Strided(ctx, ctx.mesh_info.payload_words, ctx.mesh_threads, invocation, [&](Id index) {
StoreWord(ctx, BufferWordAt(ctx, PushU64(ctx, ctx.mesh_fallback_payload_member_index),
ctx.OpIAdd(ctx.U32[1], first, index)),
ctx.OpLoad(ctx.U32[1], PayloadWord(ctx, index)));
});
});
// Copy the whole shared payload cooperatively, including words written by invocations that emitted no meshlet.
If(ctx, ctx.OpULessThan(ctx.U1, group, task_capacity), [&] {
const Id first{ctx.OpIAdd(ctx.U32[1], capacity,
ctx.OpIMul(ctx.U32[1], group, ctx.Const(ctx.mesh_info.payload_words)))};
Strided(ctx, ctx.mesh_info.payload_words, ctx.mesh_threads, invocation, [&](Id index) {
StoreWord(ctx, BufferWordAt(ctx, PushU64(ctx, ctx.mesh_fallback_payload_member_index),
ctx.OpIAdd(ctx.U32[1], first, index)),
ctx.OpLoad(ctx.U32[1], PayloadWord(ctx, index)));
});
});
return;
Expand Down Expand Up @@ -429,7 +452,7 @@ void EmitMeshFallbackVertexBody(EmitContext& ctx) {
const Id primitive{ctx.OpUDiv(ctx.U32[1], within, ctx.Const(corners))};
const Id corner{ctx.OpUMod(ctx.U32[1], within, ctx.Const(corners))};
const Id count{LoadWord(ctx, BufferWord(ctx, ctx.mesh_fallback_records_member_index, slot))};
const Id record{ctx.OpIAdd(ctx.U32[1], ctx.Const(MESH_FALLBACK_CAPACITY),
const Id record{ctx.OpIAdd(ctx.U32[1], FallbackCapacity(ctx),
ctx.OpIMul(ctx.U32[1],
ctx.OpIAdd(ctx.U32[1],
ctx.OpIMul(ctx.U32[1], slot, ctx.Const(per_slot)),
Expand Down Expand Up @@ -489,6 +512,14 @@ Id EmitIsbeRead(EmitContext& ctx, Id address) {
ctx.OpCompositeExtract(ctx.U32[1], ctx.OpLoad(ctx.U32[3], ctx.workgroup_id), 0U)};
if (ctx.stage == Stage::Task) {
const Id workgroup{native_workgroup};
if (ctx.mesh_fallback) {
const Id input{PushU64(ctx, ctx.mesh_fallback_indirect_input_member_index)};
const Id indirect{ctx.OpINotEqual(ctx.U1, input, ctx.Constant(ctx.U64, u64{0}))};
const Id base{ctx.OpSelect(ctx.U64, indirect, input, PushU64(ctx, ctx.mesh_fallback_payload_member_index))};
const Id gpu_first{LoadWord(ctx, BufferWordAt(ctx, base, ctx.Const(1U)))};
const Id first{ctx.OpSelect(ctx.U32[1], indirect, gpu_first, PushU32(ctx, ctx.mesh_first_member_index))};
return ctx.OpIAdd(ctx.U32[1], workgroup, first);
}
if (ctx.wants_mesh_first && Sirit::ValidId(ctx.rescaling_push_constants)) {
// (hookshot) + the draw's first workgroup: NVN draws [first, first + count). Without it every draw began at 0: the
// foliage's draws (first up to ~1900) drew the first records again and again, and the forest had no leaves
Expand Down
27 changes: 24 additions & 3 deletions gpu/vk/eden/shader_recompiler/backend/spirv/spirv_emit_context.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -528,7 +528,8 @@ EmitContext::EmitContext(const Profile& profile_, const RuntimeInfo& runtime_inf
DefineCommonConstants();
DefineInterfaces(program);
// (RX_VK_MESH_FIRST=1, with RX_VK_MESH_ARGS=1: off, one task program with it hung or ended the game at its first draw)
if (stage == Stage::Task && getenv("RX_VK_MESH_FIRST") && profile.unified_descriptor_binding && profile.support_int64) {
if (stage == Stage::Task && !runtime_info.mesh_fallback && getenv("RX_VK_MESH_FIRST") &&
profile.unified_descriptor_binding && profile.support_int64) {
// (hookshot) the draw's first task workgroup, read through a pushed address (EmitIsbeRead)
if (!Sirit::ValidId(U64)) { AddCapability(spv::Capability::Int64); U64 = Name(TypeInt(64, false), "u64"); }
wants_mesh_first = true;
Expand All @@ -551,6 +552,7 @@ EmitContext::EmitContext(const Profile& profile_, const RuntimeInfo& runtime_inf
if (runtime_info.mesh_fallback && (stage == Stage::Task || stage == Stage::Mesh)) {
mesh_fallback = true;
wants_mesh_fallback = true;
wants_mesh_first = stage == Stage::Task;
// (the push block is made below, before DefineGlobalMemoryFunctions can ask for the page table: a fallback stage
// that follows pointers needs its member now, or the block would be made without it)
wants_guest_pages = program.info.uses_global_memory && profile.support_int64;
Expand Down Expand Up @@ -1587,14 +1589,24 @@ void EmitContext::DefineRescalingInputPushConstant() {
guest_pages_member_index = member_index++;
}
if (wants_mesh_first) {
members.push_back(U64);
members.push_back(mesh_fallback ? U32[1] : U64);
mesh_first_member_index = member_index++;
}
if (wants_mesh_fallback) {
members.push_back(U64);
mesh_fallback_payload_member_index = member_index++;
members.push_back(U64);
mesh_fallback_records_member_index = member_index++;
members.push_back(U32[1]);
mesh_fallback_capacity_member_index = member_index++;
if (stage == Stage::Task) {
members.push_back(U32[1]);
mesh_fallback_vertices_member_index = member_index++;
members.push_back(U64);
mesh_fallback_indirect_input_member_index = member_index++;
members.push_back(U64);
mesh_fallback_indirect_output_member_index = member_index++;
}
}
const Id push_constant_struct{TypeStruct(std::span(members.data(), members.size()))};
Decorate(push_constant_struct, spv::Decoration::Block);
Expand Down Expand Up @@ -1623,15 +1635,24 @@ void EmitContext::DefineRescalingInputPushConstant() {
MemberName(push_constant_struct, mesh_first_member_index, "mesh_first");
}
if (wants_mesh_fallback) {
const std::array<std::pair<u32, u32>, 2> fallback_members{{
const std::array<std::pair<u32, u32>, 3> fallback_members{{
{mesh_fallback_payload_member_index, MESH_FALLBACK_PAYLOAD_PUSH_OFFSET},
{mesh_fallback_records_member_index, MESH_FALLBACK_RECORDS_PUSH_OFFSET},
{mesh_fallback_capacity_member_index, MESH_FALLBACK_CAPACITY_PUSH_OFFSET},
}};
for (const auto& [member, offset] : fallback_members) {
MemberDecorate(push_constant_struct, member, spv::Decoration::Offset, offset);
}
MemberName(push_constant_struct, mesh_fallback_payload_member_index, "mesh_fallback_payload");
MemberName(push_constant_struct, mesh_fallback_records_member_index, "mesh_fallback_records");
if (stage == Stage::Task) {
MemberDecorate(push_constant_struct, mesh_fallback_vertices_member_index, spv::Decoration::Offset,
MESH_FALLBACK_VERTICES_PUSH_OFFSET);
MemberDecorate(push_constant_struct, mesh_fallback_indirect_input_member_index, spv::Decoration::Offset,
MESH_FALLBACK_INDIRECT_INPUT_PUSH_OFFSET);
MemberDecorate(push_constant_struct, mesh_fallback_indirect_output_member_index, spv::Decoration::Offset,
MESH_FALLBACK_INDIRECT_OUTPUT_PUSH_OFFSET);
}
}
const Id pointer_type{TypePointer(spv::StorageClass::PushConstant, push_constant_struct)};
rescaling_push_constants = AddGlobalVariable(pointer_type, spv::StorageClass::PushConstant);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,10 @@ class EmitContext final : public Sirit::Module {
Id mesh_fallback_word_ptr{}; ///< PhysicalStorageBuffer pointer to a block of one u32
u32 mesh_fallback_payload_member_index{};
u32 mesh_fallback_records_member_index{};
u32 mesh_fallback_capacity_member_index{};
u32 mesh_fallback_vertices_member_index{};
u32 mesh_fallback_indirect_input_member_index{};
u32 mesh_fallback_indirect_output_member_index{};
/// A pointer to the u32 at a PhysicalStorageBuffer device address
Id MeshFallbackWord(Id address);

Expand Down
Loading