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{};
@@ -123,6 +123,8 @@ void BufferCache<P>::TickFrame() {
if (total_used_memory >= minimum_memory || heap_pressure) {
RunGarbageCollector();
}
std::erase_if(pointer_ranges,
[this](const PointerRange& range) { return range.frame + 2 < frame_tick; });
++frame_tick;
delayed_destruction_ring.Tick();
@@ -1144,6 +1146,7 @@ void BufferCache<P>::BindHostGraphicsStorageBuffers(size_t stage) {
if (is_written) {
MarkWrittenBuffer(binding.buffer_id, binding.device_addr, size);
RecordPointerRange(binding);
}
if constexpr (NEEDS_BIND_STORAGE_INDEX) {
@@ -1284,6 +1287,7 @@ void BufferCache<P>::BindHostComputeStorageBuffers() {
if (is_written) {
MarkWrittenBuffer(binding.buffer_id, binding.device_addr, size);
RecordPointerRange(binding);
}
if constexpr (NEEDS_BIND_STORAGE_INDEX) {
@@ -1341,6 +1345,7 @@ void BufferCache<P>::DoUpdateGraphicsBuffers(bool is_indexed) {
if (current_draw_indirect) {
UpdateDrawIndirect();
}
UpdatePointerRanges();
});
}
@@ -1351,6 +1356,7 @@ void BufferCache<P>::DoUpdateComputeBuffers() {
UpdateComputeUniformBuffers();
UpdateComputeStorageBuffers();
UpdateComputeTextureBuffers();
UpdatePointerRanges();
});
}
@@ -1496,6 +1502,72 @@ void BufferCache<P>::UpdateStorageBuffers(size_t stage) {
});
}
template <class P>
void BufferCache<P>::UpdatePointerRanges() {
if (!pointer_table_requested) {
return;
}
for (PointerRange& range : pointer_ranges) {
range.buffer_id = NULL_BUFFER_ID;
if (gpu_memory->GpuToCpuAddress(range.gpu_addr) == range.device_addr) {
range.buffer_id = FindBuffer(range.device_addr, range.size, false);
}
}
}
template <class P>
void BufferCache<P>::RecordPointerRange(const Binding& binding) {
if (!record_pointer_ranges) {
return;
}
const auto it = std::ranges::find(pointer_ranges, binding.gpu_addr, &PointerRange::gpu_addr);
if (it != pointer_ranges.end()) {
it->device_addr = binding.device_addr;
it->size = (std::max)(it->size, binding.size);
it->frame = frame_tick;
return;
}
if (pointer_ranges.size() < MAX_POINTER_RANGES) {
pointer_ranges.push_back({binding.gpu_addr, binding.device_addr, binding.size, frame_tick,
NULL_BUFFER_ID});
}
}
template <class P>
void BufferCache<P>::RequestPointerTable(bool enable) noexcept {
pointer_table_requested = enable;
record_pointer_ranges |= enable;
}
template <class P>
std::array<u32, 3> BufferCache<P>::BindHostPointerTable(bool is_written) {
std::array<u32, 3> table{};
if constexpr (!IS_OPENGL) {
const size_t capacity = (std::max)(pointer_ranges.size(), size_t{1});
const auto upload = runtime.UploadStagingBuffer(capacity * sizeof(std::array<u64, 4>));
u32 count = 0;
for (const PointerRange& range : pointer_ranges) {
if (range.buffer_id == NULL_BUFFER_ID) {
continue;
}
Buffer& buffer = slot_buffers[range.buffer_id];
TouchBuffer(buffer, range.buffer_id);
SynchronizeBuffer(buffer, range.device_addr, range.size);
if (is_written) {
MarkWrittenBuffer(range.buffer_id, range.device_addr, range.size);
}
const u64 host_address = buffer.DeviceAddress() + buffer.Offset(range.device_addr);
const std::array<u64, 4> entry{range.gpu_addr, range.size, host_address, 0};
std::memcpy(upload.mapped_span.data() + count * sizeof(entry), entry.data(),
sizeof(entry));
++count;
}
const u64 address = upload.device_address + upload.offset;
table = {static_cast<u32>(address), static_cast<u32>(address >> 32), count};
}
return table;
}
template <class P>
void BufferCache<P>::UpdateTextureBuffers(size_t stage) {
ForEachEnabledBit(channel_state->enabled_texture_buffers[stage], [&](u32 index) {
@@ -270,6 +270,10 @@ public:
void BindHostComputeBuffers();
void RequestPointerTable(bool enable) noexcept;
[[nodiscard]] std::array<u32, 3> BindHostPointerTable(bool is_written);
void SetUniformBuffersState(const std::array<u32, NUM_STAGES>& mask,
const UniformBufferSizes* sizes);
@@ -431,6 +435,10 @@ private:
void UpdateStorageBuffers(size_t stage);
void UpdatePointerRanges();
void RecordPointerRange(const Binding& binding);
void UpdateTextureBuffers(size_t stage);
void UpdateTransformFeedbackBuffers();
@@ -563,6 +571,17 @@ private:
std::vector<MultiRangeSegment> graphics_segments;
std::vector<MultiRangeSegment> compute_segments;
u64 frame_tick = 0;
struct PointerRange {
GPUVAddr gpu_addr;
DAddr device_addr;
u32 size;
u64 frame;
BufferId buffer_id;
};
static constexpr size_t MAX_POINTER_RANGES = 256;
std::vector<PointerRange> pointer_ranges;
bool record_pointer_ranges = false;
bool pointer_table_requested = false;
u64 total_used_memory = 0;
u64 device_local_memory = 0;
u64 minimum_memory = 0;
@@ -243,15 +243,13 @@ public:
}
vk::PipelineLayout CreatePipelineLayout(VkDescriptorSetLayout descriptor_set_layout) const {
using Shader::Backend::SPIRV::RenderAreaLayout;
using Shader::Backend::SPIRV::RescalingLayout;
const u32 size_offset = is_compute ? sizeof(RescalingLayout::down_factor) : 0u;
using Shader::Backend::SPIRV::GLOBAL_POINTER_LAYOUT_OFFSET;
using Shader::Backend::SPIRV::GlobalPointerLayout;
const VkPushConstantRange range{
.stageFlags = static_cast<VkShaderStageFlags>(
is_compute ? VK_SHADER_STAGE_COMPUTE_BIT : VK_SHADER_STAGE_ALL_GRAPHICS),
.offset = 0,
.size = static_cast<u32>(sizeof(RescalingLayout)) - size_offset +
static_cast<u32>(sizeof(RenderAreaLayout)),
.size = GLOBAL_POINTER_LAYOUT_OFFSET + static_cast<u32>(sizeof(GlobalPointerLayout)),
};
return device->GetLogical().CreatePipelineLayout({
.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
@@ -28,6 +28,7 @@
namespace Vulkan {
using Shader::ImageBufferDescriptor;
using Shader::Backend::SPIRV::GLOBAL_POINTER_LAYOUT_OFFSET;
using Shader::Backend::SPIRV::RESCALING_LAYOUT_WORDS_OFFSET;
using Tegra::Texture::TexturePair;
@@ -245,8 +246,14 @@ bool ComputePipeline::Configure(Tegra::Engines::KeplerCompute& kepler_compute,
std::ranges::for_each(info.texture_buffer_descriptors, add_buffer);
std::ranges::for_each(info.image_buffer_descriptors, add_buffer);
const bool uses_pointers{info.uses_global_pointers && device.IsBufferDeviceAddressSupported()};
buffer_cache.RequestPointerTable(uses_pointers);
buffer_cache.UpdateComputeBuffers();
buffer_cache.BindHostComputeBuffers();
std::array<u32, 3> pointer_table{};
if (uses_pointers) {
pointer_table = buffer_cache.BindHostPointerTable(info.stores_global_memory);
}
RescalingPushConstant rescaling;
const VideoCommon::SamplerId* samplers_it{samplers.data()};
@@ -288,7 +295,7 @@ bool ComputePipeline::Configure(Tegra::Engines::KeplerCompute& kepler_compute,
const bool is_rescaling = !info.texture_descriptors.empty() || !info.image_descriptors.empty();
scheduler.Record([this, descriptor_data, is_rescaling, descriptor_buffer_offset,
descriptor_buffer_chunk, bind_descriptor_buffer,
descriptor_buffer_chunk, bind_descriptor_buffer, uses_pointers, pointer_table,
rescaling_data = rescaling.Data()](vk::CommandBuffer cmdbuf) {
if (bind_descriptor_buffer) {
const VkDescriptorBufferBindingInfoEXT binding_info{
@@ -299,6 +306,11 @@ bool ComputePipeline::Configure(Tegra::Engines::KeplerCompute& kepler_compute,
return;
}
cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, *pipeline);
if (uses_pointers) {
cmdbuf.PushConstants(*pipeline_layout, VK_SHADER_STAGE_COMPUTE_BIT,
GLOBAL_POINTER_LAYOUT_OFFSET, sizeof(pointer_table),
pointer_table.data());
}
if (!descriptor_set_layout) {
return;
}
@@ -41,6 +41,7 @@ namespace {
using boost::container::small_vector;
using boost::container::static_vector;
using Shader::ImageBufferDescriptor;
using Shader::Backend::SPIRV::GLOBAL_POINTER_LAYOUT_OFFSET;
using Shader::Backend::SPIRV::RENDERAREA_LAYOUT_OFFSET;
using Shader::Backend::SPIRV::RESCALING_LAYOUT_DOWN_FACTOR_OFFSET;
using Shader::Backend::SPIRV::RESCALING_LAYOUT_WORDS_OFFSET;
@@ -301,6 +302,9 @@ GraphicsPipeline::GraphicsPipeline(
num_image_elements += Shader::NumDescriptors(info->texture_descriptors);
num_image_elements += Shader::NumDescriptors(info->image_descriptors);
num_descriptor_entries += NumDescriptorEntries(*info);
uses_global_pointers |=
info->uses_global_pointers && device.IsBufferDeviceAddressSupported();
stores_global_pointers |= info->stores_global_memory;
}
fragment_has_color0_output = stage_infos[NUM_STAGES - 1].stores_frag_color[0];
@@ -536,6 +540,7 @@ bool GraphicsPipeline::ConfigureImpl(bool is_indexed) {
scheduler.RequestOutsideRenderPassOperationContext();
}
buffer_cache.RequestPointerTable(uses_global_pointers);
buffer_cache.UpdateGraphicsBuffers(is_indexed);
buffer_cache.BindHostGeometryBuffers(is_indexed);
@@ -571,6 +576,9 @@ bool GraphicsPipeline::ConfigureImpl(bool is_indexed) {
if constexpr (Spec::enabled_stages[4]) {
prepare_stage(4);
}
if (uses_global_pointers) {
pointer_table = buffer_cache.BindHostPointerTable(stores_global_pointers);
}
texture_cache.UpdateRenderTargets(false);
texture_cache.CheckFeedbackLoop(std::span<const VideoCommon::ImageViewInOut>{views.data(),
views.size()});
@@ -656,7 +664,8 @@ bool GraphicsPipeline::ConfigureDraw(const RescalingPushConstant& rescaling,
descriptor_buffer_offset, descriptor_buffer_chunk, bind_descriptor_buffer,
rescaling_data = rescaling.Data(), is_rescaling, update_rescaling,
uses_render_area = render_area.uses_render_area,
render_area_data = render_area.words](vk::CommandBuffer cmdbuf) {
render_area_data = render_area.words,
table = pointer_table](vk::CommandBuffer cmdbuf) {
if (bind_descriptor_buffer) {
const VkDescriptorBufferBindingInfoEXT binding_info{
descriptor_buffer_ring.BindingInfo(descriptor_buffer_chunk)};
@@ -683,6 +692,10 @@ bool GraphicsPipeline::ConfigureDraw(const RescalingPushConstant& rescaling,
RENDERAREA_LAYOUT_OFFSET, sizeof(render_area_data),
&render_area_data);
}
if (uses_global_pointers) {
cmdbuf.PushConstants(*pipeline_layout, VK_SHADER_STAGE_ALL_GRAPHICS,
GLOBAL_POINTER_LAYOUT_OFFSET, sizeof(table), table.data());
}
if (!descriptor_set_layout) {
return;
}
@@ -168,6 +168,9 @@ private:
size_t num_image_elements{};
u32 num_textures{};
bool fragment_has_color0_output{};
bool uses_global_pointers{};
bool stores_global_pointers{};
std::array<u32, 3> pointer_table{};
vk::DescriptorSetLayout descriptor_set_layout;
DescriptorAllocator descriptor_allocator;
@@ -421,6 +421,7 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_,
.support_demote_to_helper_invocation =
device.IsExtShaderDemoteToHelperInvocationSupported(),
.support_int64_atomics = device.IsExtShaderAtomicInt64Supported(),
.support_buffer_device_address = device.IsBufferDeviceAddressSupported(),
.support_shared_int64_atomics = device.IsSharedInt64AtomicsSupported(),
.support_derivative_control = true,
.support_geometry_shader_passthrough = device.IsNvGeometryShaderPassthroughSupported(),