God has forsaken me to BDA

This commit is contained in:
CamilleLaVey
2026-09-29 21:57:46 -04:00
parent 213d9fb4a7
commit 08917afe0e
15 changed files with 301 additions and 52 deletions
@@ -41,17 +41,17 @@ void GlobalStorageOp(EmitContext& ctx, Register address, bool pointer_based, std
}
const u64 ssbo_align_mask{~(ctx.profile.min_ssbo_alignment - 1U)};
ctx.Add("LDC.U64 DC.x,c{}[{}];" // unaligned_ssbo_addr
"AND.U64 DC.x,DC.x,{};" // ssbo_addr = unaligned_ssbo_addr & ssbo_align_mask
"LDC.U32 RC.x,c{}[{}];" // ssbo_size_u32
"CVT.U64.U32 DC.y,RC.x;" // ssbo_size = ssbo_size_u32
"ADD.U64 DC.y,DC.y,DC.x;" // ssbo_end = ssbo_addr + ssbo_size
"ADD.U64 DC.y,DC.y,DC.x;" // ssbo_end = unaligned_ssbo_addr + ssbo_size
"AND.U64 DC.x,DC.x,{};" // ssbo_addr = unaligned_ssbo_addr & ssbo_align_mask
"SGE.U64 RC.x,{}.x,DC.x;" // a = input_addr >= ssbo_addr ? -1 : 0
"SLT.U64 RC.y,{}.x,DC.y;" // b = input_addr < ssbo_end ? -1 : 0
"AND.U.CC RC.x,RC.x,RC.y;" // cond = a && b
"IF NE.x;" // if cond
"SUB.U64 DC.x,{}.x,DC.x;", // offset = input_addr - ssbo_addr
ssbo.cbuf_index, ssbo.cbuf_offset, ssbo_align_mask, ssbo.cbuf_index,
ssbo.cbuf_offset + 8, address, address, address);
ssbo.cbuf_index, ssbo.cbuf_offset, ssbo.cbuf_index, ssbo.cbuf_offset + 8,
ssbo_align_mask, address, address, address);
if (pointer_based) {
ctx.Add("PK64.U DC.y,c[{}];" // host_ssbo = cbuf
"ADD.U64 DC.x,DC.x,DC.y;" // host_addr = host_ssbo + offset
@@ -618,7 +618,8 @@ std::string EmitContext::DefineGlobalMemoryFunctions() {
const auto size_vec{fmt::format("uvec2({},{})", size_xy[0], size_xy[1])};
const auto comp_lhs{fmt::format("(addr>={})", ssbo_addr)};
const auto comp_rhs{fmt::format("(addr<({}+uint64_t({})))", ssbo_addr, size_vec)};
const auto comp_rhs{fmt::format("(addr<(packUint2x32(uvec2({},{}))+uint64_t({})))",
addr_xy[0], addr_xy[1], size_vec)};
const auto comparison{fmt::format("if({}&&{}){{", comp_lhs, comp_rhs)};
func += comparison;
@@ -29,6 +29,11 @@ struct RescalingLayout {
struct RenderAreaLayout {
std::array<f32, 4> render_area;
};
struct GlobalPointerLayout {
std::array<u32, 2> table;
u32 count;
};
constexpr u32 GLOBAL_POINTER_LAYOUT_OFFSET = 64;
constexpr u32 RESCALING_LAYOUT_WORDS_OFFSET = offsetof(RescalingLayout, rescaling_textures);
constexpr u32 RESCALING_LAYOUT_DOWN_FACTOR_OFFSET = offsetof(RescalingLayout, down_factor);
constexpr u32 RENDERAREA_LAYOUT_OFFSET = offsetof(RenderAreaLayout, render_area);
@@ -486,9 +486,9 @@ EmitContext::EmitContext(const Profile& profile_, const RuntimeInfo& runtime_inf
DefineTextures(program.info, texture_binding, bindings.texture_scaling_index);
DefineImages(program.info, image_binding, bindings.image_scaling_index);
DefineAttributeMemAccess(program.info);
DefineGlobalMemoryFunctions(program);
DefineRescalingInput(program.info);
DefineRenderArea(program.info);
DefineGlobalMemoryFunctions(program);
}
EmitContext::~EmitContext() = default;
@@ -911,12 +911,48 @@ void EmitContext::DefineGlobalMemoryFunctions(const IR::Program& program) {
if (!info.uses_global_memory || !profile.support_int64) {
return;
}
struct Access {
const StorageDefinitions* ssbo;
Id word;
Id address;
};
const Id zero{u32_zero_value};
const Id scope{Const(static_cast<u32>(spv::Scope::Device))};
const Id align_mask{Const(~(static_cast<u32>(profile.min_ssbo_alignment) - 1U))};
const auto word_pointer{[&](Id ssbo, Id word, u32 element) {
return OpAccessChain(storage_types.U32.element, ssbo, zero,
OpIAdd(U32[1], word, Const(element)));
Id physical_u32{};
Id physical_u32x4{};
if (uses_global_pointers) {
AddCapability(spv::Capability::PhysicalStorageBufferAddresses);
AddExtension("SPV_KHR_physical_storage_buffer");
SetMemoryModel(spv::AddressingModel::PhysicalStorageBuffer64, spv::MemoryModel::GLSL450);
physical_u32 = TypePointer(spv::StorageClass::PhysicalStorageBuffer, U32[1]);
physical_u32x4 = TypePointer(spv::StorageClass::PhysicalStorageBuffer, U32[4]);
}
const auto word_pointer{[&](const Access& access, u32 element) {
if (access.ssbo) {
return OpAccessChain(storage_types.U32.element, access.ssbo->U32, zero,
OpIAdd(U32[1], access.word, Const(element)));
}
return OpConvertUToPtr(physical_u32,
OpIAdd(U64, access.address, Constant(U64, u64{element} * 4)));
}};
const auto load_word{[&](const Access& access, u32 element) {
if (access.ssbo) {
return OpLoad(U32[1], word_pointer(access, element));
}
return OpLoad(U32[1], word_pointer(access, element), spv::MemoryAccessMask::Aligned, 4U);
}};
const auto store_word{[&](const Access& access, u32 element, Id value) {
if (access.ssbo) {
OpStore(word_pointer(access, element), value);
return;
}
OpStore(word_pointer(access, element), value, spv::MemoryAccessMask::Aligned, 4U);
}};
const auto load_entry{[&](Id address) {
const Id entry{OpLoad(U32[4], OpConvertUToPtr(physical_u32x4, address),
spv::MemoryAccessMask::Aligned, 16U)};
return std::pair{entry, OpBitcast(U64, OpVectorShuffle(U32[2], entry, entry, 0U, 1U))};
}};
const auto cbuf_word{[&](u32 index, u32 offset) {
if (profile.support_descriptor_aliasing) {
@@ -938,7 +974,19 @@ void EmitContext::DefineGlobalMemoryFunctions(const IR::Program& program) {
const Id addr{OpFunctionParameter(U64)};
const Id value{OpFunctionParameter(value_type)};
const bool returns_value{result_type.value != void_id.value};
const auto finish{[&](Id result) {
if (returns_value) {
OpReturnValue(result);
} else {
OpReturn();
}
}};
AddLabel();
Id entry_index{};
if (uses_global_pointers) {
entry_index = AddLocalVariable(TypePointer(spv::StorageClass::Function, U32[1]),
spv::StorageClass::Function, zero);
}
const Id addr_words{OpBitcast(U32[2], addr)};
const Id addr_low{OpCompositeExtract(U32[1], addr_words, 0U)};
const Id addr_high{OpCompositeExtract(U32[1], addr_words, 1U)};
@@ -947,8 +995,7 @@ void EmitContext::DefineGlobalMemoryFunctions(const IR::Program& program) {
if (!desc.is_global_fallback) {
continue;
}
const Id ssbo_low{
OpBitwiseAnd(U32[1], cbuf_word(desc.cbuf_index, desc.cbuf_offset), align_mask)};
const Id ssbo_low{cbuf_word(desc.cbuf_index, desc.cbuf_offset)};
const Id ssbo_high{cbuf_word(desc.cbuf_index, desc.cbuf_offset + 4)};
const Id ssbo_size{cbuf_word(desc.cbuf_index, desc.cbuf_offset + 8)};
const Id offset{OpISub(U32[1], addr_low, ssbo_low)};
@@ -961,15 +1008,55 @@ void EmitContext::DefineGlobalMemoryFunctions(const IR::Program& program) {
OpSelectionMerge(else_label, spv::SelectionControlMask::MaskNone);
OpBranchConditional(cond, then_label, else_label);
AddLabel(then_label);
const Id word{OpShiftRightLogical(U32[1], offset, Const(2U))};
const Id result{callback(ssbos[index], word, offset, value)};
if (returns_value) {
OpReturnValue(result);
} else {
OpReturn();
}
const Id aligned_offset{
OpISub(U32[1], addr_low, OpBitwiseAnd(U32[1], ssbo_low, align_mask))};
const Id word{OpShiftRightLogical(U32[1], aligned_offset, Const(2U))};
finish(callback(Access{&ssbos[index], word, Id{}}, aligned_offset, value));
AddLabel(else_label);
}
if (uses_global_pointers) {
const Id table_pointer{
OpAccessChain(TypePointer(spv::StorageClass::PushConstant, U32[2]),
rescaling_push_constants, Const(global_pointer_member_index))};
const Id count_pointer{
OpAccessChain(TypePointer(spv::StorageClass::PushConstant, U32[1]),
rescaling_push_constants, Const(global_pointer_member_index + 1))};
const Id table{OpBitcast(U64, OpLoad(U32[2], table_pointer))};
const Id count{OpLoad(U32[1], count_pointer)};
const Id header_label{OpLabel()};
const Id body_label{OpLabel()};
const Id hit_label{OpLabel()};
const Id skip_label{OpLabel()};
const Id continue_label{OpLabel()};
const Id merge_label{OpLabel()};
OpBranch(header_label);
AddLabel(header_label);
const Id entry{OpLoad(U32[1], entry_index)};
const Id in_table{OpULessThan(U1, entry, count)};
OpLoopMerge(merge_label, continue_label, spv::LoopControlMask::MaskNone);
OpBranchConditional(in_table, body_label, merge_label);
AddLabel(body_label);
const Id entry_address{OpIAdd(
U64, table, OpUConvert(U64, OpShiftLeftLogical(U32[1], entry, Const(5U))))};
const auto [guest, guest_base]{load_entry(entry_address)};
const Id guest_offset{OpISub(U64, addr, guest_base)};
const Id guest_size{OpUConvert(U64, OpCompositeExtract(U32[1], guest, 2U))};
const Id hit{OpULessThan(U1, guest_offset, guest_size)};
OpSelectionMerge(skip_label, spv::SelectionControlMask::MaskNone);
OpBranchConditional(hit, hit_label, skip_label);
AddLabel(hit_label);
const Id host_base{
load_entry(OpIAdd(U64, entry_address, Constant(U64, u64{16}))).second};
const Id target{OpBitwiseAnd(U64, OpIAdd(U64, host_base, guest_offset),
Constant(U64, ~u64{3}))};
finish(callback(Access{nullptr, Id{}, target}, addr_low, value));
AddLabel(skip_label);
OpBranch(continue_label);
AddLabel(continue_label);
OpStore(entry_index, OpIAdd(U32[1], entry, Const(1U)));
OpBranch(header_label);
AddLabel(merge_label);
}
if (returns_value) {
OpReturnValue(ConstantNull(result_type));
} else {
@@ -987,13 +1074,13 @@ void EmitContext::DefineGlobalMemoryFunctions(const IR::Program& program) {
OpShiftRightLogical(U32[1], word, Const(2U)));
}};
const auto load{[&](Id type, u32 count) {
return [&, type, count](const StorageDefinitions& ssbo, Id word, Id, Id) {
if (count > 1 && profile.support_descriptor_aliasing) {
return OpLoad(type, vector_pointer(ssbo, word, count));
return [&, type, count](const Access& access, Id, Id) {
if (count > 1 && access.ssbo && profile.support_descriptor_aliasing) {
return OpLoad(type, vector_pointer(*access.ssbo, access.word, count));
}
std::array<Id, 4> words{};
for (u32 element = 0; element < count; ++element) {
words[element] = OpLoad(U32[1], word_pointer(ssbo.U32, word, element));
words[element] = load_word(access, element);
}
if (count == 1) {
return words[0];
@@ -1002,25 +1089,24 @@ void EmitContext::DefineGlobalMemoryFunctions(const IR::Program& program) {
};
}};
const auto store{[&](u32 count) {
return [&, count](const StorageDefinitions& ssbo, Id word, Id, Id value) {
if (count > 1 && profile.support_descriptor_aliasing) {
OpStore(vector_pointer(ssbo, word, count), value);
return [&, count](const Access& access, Id, Id value) {
if (count > 1 && access.ssbo && profile.support_descriptor_aliasing) {
OpStore(vector_pointer(*access.ssbo, access.word, count), value);
return Id{};
}
if (count == 1) {
OpStore(word_pointer(ssbo.U32, word, 0), value);
store_word(access, 0, value);
return Id{};
}
for (u32 element = 0; element < count; ++element) {
OpStore(word_pointer(ssbo.U32, word, element),
OpCompositeExtract(U32[1], value, element));
store_word(access, element, OpCompositeExtract(U32[1], value, element));
}
return Id{};
};
}};
const auto extract{[&](bool is_signed, u32 count) {
return [&, is_signed, count](const StorageDefinitions& ssbo, Id word, Id offset, Id) {
const Id loaded{OpLoad(U32[1], word_pointer(ssbo.U32, word, 0))};
return [&, is_signed, count](const Access& access, Id offset, Id) {
const Id loaded{load_word(access, 0)};
if (is_signed) {
return OpBitFieldSExtract(U32[1], loaded, bits(offset, count), Const(count));
}
@@ -1028,28 +1114,36 @@ void EmitContext::DefineGlobalMemoryFunctions(const IR::Program& program) {
};
}};
const auto insert{[&](u32 count) {
return [&, count](const StorageDefinitions& ssbo, Id word, Id offset, Id value) {
AtomicBitFieldInsert(word_pointer(ssbo.U32, word, 0), value, bits(offset, count),
return [&, count](const Access& access, Id offset, Id value) {
AtomicBitFieldInsert(word_pointer(access, 0), value, bits(offset, count),
Const(count));
return Id{};
};
}};
const auto atomic{[&](Id (Sirit::Module::*func)(Id, Id, Id, Id, Id)) {
return [&, func](const StorageDefinitions& ssbo, Id word, Id, Id value) {
return (this->*func)(U32[1], word_pointer(ssbo.U32, word, 0), scope, zero, value);
return [&, func](const Access& access, Id, Id value) {
return (this->*func)(U32[1], word_pointer(access, 0), scope, zero, value);
};
}};
const auto cas{[&](Id type, Id helper) {
return [&, type, helper](const StorageDefinitions& ssbo, Id word, Id, Id value) {
return OpFunctionCall(type, helper, word, value, ssbo.U32);
return [&, type, helper](const Access& access, Id, Id value) {
if (!access.ssbo) {
return ConstantNull(type);
}
return OpFunctionCall(type, helper, access.word, value, access.ssbo->U32);
};
}};
const auto packed{[&](bool is_half, Id helper) {
return [&, is_half, helper](const StorageDefinitions& ssbo, Id word, Id, Id value) {
if (is_half) {
return OpBitcast(U32[1], OpFunctionCall(F16[2], helper, word, value, ssbo.U32));
return [&, is_half, helper](const Access& access, Id, Id value) {
if (!access.ssbo) {
return ConstantNull(U32[1]);
}
return OpPackHalf2x16(U32[1], OpFunctionCall(F32[2], helper, word, value, ssbo.U32));
const Id ssbo{access.ssbo->U32};
if (is_half) {
return OpBitcast(U32[1], OpFunctionCall(F16[2], helper, access.word, value, ssbo));
}
return OpPackHalf2x16(U32[1],
OpFunctionCall(F32[2], helper, access.word, value, ssbo));
};
}};
for (const IR::Block* const block : program.post_order_blocks) {
@@ -1159,7 +1253,9 @@ void EmitContext::DefineGlobalMemoryFunctions(const IR::Program& program) {
}
void EmitContext::DefineRescalingInput(const Info& info) {
if (!info.uses_rescaling_uniform) {
uses_global_pointers = info.uses_global_pointers && profile.support_buffer_device_address &&
profile.support_int64 && profile.unified_descriptor_binding;
if (!info.uses_rescaling_uniform && !uses_global_pointers) {
return;
}
if (profile.unified_descriptor_binding) {
@@ -1170,7 +1266,7 @@ void EmitContext::DefineRescalingInput(const Info& info) {
}
void EmitContext::DefineRescalingInputPushConstant() {
boost::container::static_vector<Id, 3> members{};
boost::container::static_vector<Id, 5> members{};
u32 member_index{0};
rescaling_textures_type = TypeArray(U32[1], Const(4u));
@@ -1187,6 +1283,11 @@ void EmitContext::DefineRescalingInputPushConstant() {
members.push_back(F32[1]);
rescaling_downfactor_member_index = member_index++;
}
if (uses_global_pointers) {
members.push_back(U32[2]);
members.push_back(U32[1]);
global_pointer_member_index = member_index;
}
const Id push_constant_struct{TypeStruct(std::span(members.data(), members.size()))};
Decorate(push_constant_struct, spv::Decoration::Block);
Name(push_constant_struct, "ResolutionInfo");
@@ -1205,6 +1306,14 @@ void EmitContext::DefineRescalingInputPushConstant() {
static_cast<u32>(offsetof(RescalingLayout, down_factor)));
MemberName(push_constant_struct, rescaling_downfactor_member_index, "down_factor");
}
if (uses_global_pointers) {
MemberDecorate(push_constant_struct, global_pointer_member_index, spv::Decoration::Offset,
GLOBAL_POINTER_LAYOUT_OFFSET);
MemberDecorate(push_constant_struct, global_pointer_member_index + 1,
spv::Decoration::Offset,
GLOBAL_POINTER_LAYOUT_OFFSET +
static_cast<u32>(offsetof(GlobalPointerLayout, count)));
}
const Id pointer_type{TypePointer(spv::StorageClass::PushConstant, push_constant_struct)};
rescaling_push_constants = AddGlobalVariable(pointer_type, spv::StorageClass::PushConstant);
Name(rescaling_push_constants, "rescaling_push_constants");
@@ -307,6 +307,8 @@ public:
u32 rescaling_textures_member_index{};
u32 rescaling_images_member_index{};
u32 rescaling_downfactor_member_index{};
u32 global_pointer_member_index{};
bool uses_global_pointers{};
u32 texture_rescaling_index{};
u32 image_rescaling_index{};
@@ -56,6 +56,7 @@ struct StorageInfo {
StorageBufferSet set;
StorageInstVector to_replace;
StorageWritesSet writes;
bool pointers{};
};
/// Returns true when the instruction is a global memory instruction
@@ -335,7 +336,8 @@ std::optional<LowAddrInfo> TrackLowAddress(IR::Inst* inst) {
}
/// Tries to track the storage buffer address used by a global memory instruction
StorageBufferSet Track(const IR::Value& value, const Bias* bias, const LocalStores& local_stores) {
StorageBufferSet Track(const IR::Value& value, const Bias* bias, const LocalStores& local_stores,
bool& from_memory) {
const auto pred{[bias](const IR::Inst* inst) -> std::optional<StorageBufferAddr> {
if (inst->GetOpcode() != IR::Opcode::GetCbufU32 &&
inst->GetOpcode() != IR::Opcode::GetCbufU32x2) {
@@ -386,6 +388,8 @@ StorageBufferSet Track(const IR::Value& value, const Bias* bias, const LocalStor
}
switch (inst->GetOpcode()) {
case IR::Opcode::LoadLocal:
from_memory |=
!inst->Arg(0).IsImmediate() || !local_stores.contains(inst->Arg(0).U32());
if (inst->Arg(0).IsImmediate()) {
const auto [begin, end]{local_stores.equal_range(inst->Arg(0).U32())};
for (auto it = begin; it != end; ++it) {
@@ -405,6 +409,7 @@ StorageBufferSet Track(const IR::Value& value, const Bias* bias, const LocalStor
case IR::Opcode::GetCbufU32:
case IR::Opcode::GetCbufF32:
case IR::Opcode::GetCbufU32x2:
continue;
case IR::Opcode::LoadSharedU8:
case IR::Opcode::LoadSharedS8:
case IR::Opcode::LoadSharedU16:
@@ -412,11 +417,13 @@ StorageBufferSet Track(const IR::Value& value, const Bias* bias, const LocalStor
case IR::Opcode::LoadSharedU32:
case IR::Opcode::LoadSharedU64:
case IR::Opcode::LoadSharedU128:
from_memory = true;
continue;
default:
break;
}
if (IsGlobalMemory(*inst) || inst->MayHaveSideEffects()) {
from_memory = true;
continue;
}
for (size_t arg = 0; arg < inst->NumArgs(); ++arg) {
@@ -457,18 +464,21 @@ void CollectStorageBuffers(IR::Block& block, IR::Inst& inst, StorageInfo& info,
const std::optional<LowAddrInfo> low_addr_info{TrackLowAddress(&inst)};
if (!low_addr_info) {
// Failed to track the low address, use NVN fallbacks
info.pointers = true;
return;
}
// First try to find storage buffers in the NVN address
const IR::U32 low_addr{low_addr_info->value};
StorageBufferSet candidates{Track(low_addr, &nvn_bias, local_stores)};
bool from_memory{};
StorageBufferSet candidates{Track(low_addr, &nvn_bias, local_stores, from_memory)};
if (candidates.empty()) {
// If it fails, track without a bias
candidates = Track(low_addr, nullptr, local_stores);
candidates = Track(low_addr, nullptr, local_stores, from_memory);
}
if (candidates.size() != 1) {
// If that also fails, use NVN fallbacks
LOG_WARNING(Shader, "Storage buffer failed to track, using global memory fallbacks");
info.pointers |= from_memory;
return;
}
const StorageBufferAddr storage_buffer{*candidates.begin()};
@@ -615,6 +625,7 @@ void GlobalMemoryToStorageBufferPass(IR::Program& program, const HostTranslateIn
.is_written = info.writes.contains(storage_buffer),
});
}
program.info.uses_global_pointers = info.pointers;
for (const StorageInst& storage_inst : info.to_replace) {
const StorageBufferAddr storage_buffer{storage_inst.storage_buffer};
const auto it{info.set.find(storage_inst.storage_buffer)};
@@ -628,6 +639,7 @@ void GlobalMemoryToStorageBufferPass(IR::Program& program, const HostTranslateIn
}
void JoinStorageInfo(Info& base, Info& source) {
base.uses_global_pointers |= source.uses_global_pointers;
auto& descriptors = base.storage_buffers_descriptors;
for (auto& desc : source.storage_buffers_descriptors) {
auto it{std::ranges::find_if(descriptors, [&desc](const auto& existing) {
+1
View File
@@ -47,6 +47,7 @@ struct Profile {
bool support_typeless_image_loads{};
bool support_demote_to_helper_invocation{};
bool support_int64_atomics{};
bool support_buffer_device_address{};
bool support_shared_int64_atomics{};
bool support_derivative_control{};
bool support_geometry_shader_passthrough{};
+1
View File
@@ -312,6 +312,7 @@ struct Info {
bool uses_atomic_s32_max{};
bool uses_int64_bit_atomics{};
bool uses_global_memory{};
bool uses_global_pointers{};
bool uses_atomic_image_u32{};
bool uses_shadow_lod{};
bool uses_rescaling_uniform{};