From 7f158c656e8812d644891ef2bac33a96baaf8a9d Mon Sep 17 00:00:00 2001 From: nmzik Date: Sat, 3 Oct 2026 15:09:20 +0400 Subject: [PATCH] Share sampler descriptor snapshots across native variants --- .../renderer/pipeline/descriptors.cpp | 3 +- .../host_gpu/renderer/renderCompute.cpp | 3 +- src/graphics/shader/recompiler/ir/ShaderIR.h | 2 + .../ir/passes/ResourceMaterialization.cpp | 24 ++------ tests/ResourceMaterializationTests.cpp | 59 +++++++++++++------ tests/ShaderRecompilerComputeTests.cpp | 6 +- 6 files changed, 57 insertions(+), 40 deletions(-) diff --git a/src/graphics/host_gpu/renderer/pipeline/descriptors.cpp b/src/graphics/host_gpu/renderer/pipeline/descriptors.cpp index 87f8c18f..e592cc6c 100644 --- a/src/graphics/host_gpu/renderer/pipeline/descriptors.cpp +++ b/src/graphics/host_gpu/renderer/pipeline/descriptors.cpp @@ -791,7 +791,8 @@ void RenderExecutor::PrepareBindings(const ShaderStageRuntime& runtime, } prepared.samplers.reserve(program.info.samplers.size()); for (uint32_t i = 0; i < program.info.samplers.size(); i++) { - prepared.samplers.push_back(NativeSampler(m_context, program, i, snapshot.samplers[i])); + prepared.samplers.push_back(NativeSampler( + m_context, program, i, snapshot.samplers[program.info.samplers[i].snapshot_index])); } prepared.shader_data.reserve(program.bindings.ShaderDataDwords()); for (const auto reg: program.bindings.user_data_registers) { diff --git a/src/graphics/host_gpu/renderer/renderCompute.cpp b/src/graphics/host_gpu/renderer/renderCompute.cpp index b56fdf2d..8efe8162 100644 --- a/src/graphics/host_gpu/renderer/renderCompute.cpp +++ b/src/graphics/host_gpu/renderer/renderCompute.cpp @@ -321,7 +321,8 @@ void RenderExecutor::DispatchDirect(uint64_t submit_id, CommandBuffer& buffer, static_cast(r.TileMode())); } for (uint32_t i = 0; i < program.info.samplers.size(); i++) { - const auto r = DecodeNativeDescriptor(resources.samplers[i]); + const auto r = DecodeNativeDescriptor( + resources.samplers[program.info.samplers[i].snapshot_index]); LOGF(" CS sampler[%u]: source=%u clamp=%u/%u/%u filter=%u/%u/%u mip=%u " "lod=%u-%u bias=%d\n", i, program.info.samplers[i].source, static_cast(r.ClampX()), diff --git a/src/graphics/shader/recompiler/ir/ShaderIR.h b/src/graphics/shader/recompiler/ir/ShaderIR.h index 211b1ebc..07cb7f6e 100644 --- a/src/graphics/shader/recompiler/ir/ShaderIR.h +++ b/src/graphics/shader/recompiler/ir/ShaderIR.h @@ -148,6 +148,8 @@ struct ImageResource { struct SamplerResource { uint32_t source = 0; uint32_t first_use_pc = 0; + // Native filtering/border variants share the original sampler's runtime descriptor. + uint32_t snapshot_index = 0; bool force_point_filtering = false; bool depth_compare = false; bool integer_border = false; diff --git a/src/graphics/shader/recompiler/ir/passes/ResourceMaterialization.cpp b/src/graphics/shader/recompiler/ir/passes/ResourceMaterialization.cpp index 9449bcec..6cb56bec 100644 --- a/src/graphics/shader/recompiler/ir/passes/ResourceMaterialization.cpp +++ b/src/graphics/shader/recompiler/ir/passes/ResourceMaterialization.cpp @@ -109,8 +109,7 @@ Prospero::BufferFormat ImageConversionFormat(Prospero::BufferFormat format) { enum class SamplerClass : uint8_t { Float, Integer, PointInteger }; -template -SamplerClass ClassifySampler(const Image& image) { +SamplerClass ClassifySampler(const ImageResource& image) { if (image.numeric_class == Prospero::TextureNumericClass::Sint || image.conversion_format != Prospero::BufferFormat::kInvalid) { return SamplerClass::PointInteger; @@ -418,9 +417,6 @@ private: uint32_t count = 0; }; -template -bool BuildSamplerPlan(const ShaderInfo& base, const Images& images, SamplerPlan& plan); - static bool BuildResourceSpecialization(const ResourcePlan& program, ResourceSnapshot& snapshot, ResourceSpecialization& specialization) { for (uint32_t i = 0; i < specialization.images.size(); i++) { @@ -563,30 +559,21 @@ static bool BuildResourceSpecialization(const ResourcePlan& program, ResourceSna } } } - SamplerPlan sampler_plan; - if (!BuildSamplerPlan(program.info, specialization.images, sampler_plan)) { - return SpecializationFail("specialized sampler layout exceeds its resource limit"); - } - for (uint32_t index = static_cast(program.info.samplers.size()); - index < sampler_plan.sampler_count; index++) { - snapshot.samplers.push_back(snapshot.samplers[sampler_plan.bindings[index].source]); - } ImageRemap(specialization).Apply(snapshot.images); return true; } -template -bool BuildSamplerPlan(const ShaderInfo& base, const Images& images, SamplerPlan& plan) { +bool BuildSamplerPlan(const ShaderInfo& base, SamplerPlan& plan) { if (base.samplers.size() > plan.mapping.size()) { return false; } std::array usage {}; plan.sampler_count = static_cast(base.samplers.size()); for (const auto& pair: base.sampled_pairs) { - if (pair.image >= images.size() || pair.sampler >= base.samplers.size()) { + if (pair.image >= base.images.size() || pair.sampler >= base.samplers.size()) { return false; } - usage[pair.sampler] |= 1u << static_cast(ClassifySampler(images[pair.image])); + usage[pair.sampler] |= 1u << static_cast(ClassifySampler(base.images[pair.image])); } for (uint32_t index = 0; index < base.samplers.size(); index++) { auto& mapping = plan.mapping[index]; @@ -1134,7 +1121,7 @@ void ApplyResourceSpecialization(Program& program, const ResourceSpecialization& } SamplerPlan sampler_plan; - EXIT_IF(!BuildSamplerPlan(program.info, images, sampler_plan)); + EXIT_IF(!BuildSamplerPlan(program.info, sampler_plan)); auto& samplers = program.info.samplers; auto& sampled_pairs = program.info.sampled_pairs; const auto original_sampler_count = samplers.size(); @@ -1144,6 +1131,7 @@ void ApplyResourceSpecialization(Program& program, const ResourceSpecialization& if (index >= original_sampler_count) { samplers.push_back(samplers[binding.source]); } + samplers[index].snapshot_index = binding.source; samplers[index].force_point_filtering = binding.type == SamplerClass::PointInteger; samplers[index].integer_border = binding.type != SamplerClass::Float; } diff --git a/tests/ResourceMaterializationTests.cpp b/tests/ResourceMaterializationTests.cpp index d2c04740..5e0caa1c 100644 --- a/tests/ResourceMaterializationTests.cpp +++ b/tests/ResourceMaterializationTests.cpp @@ -95,29 +95,33 @@ Libs::Graphics::ShaderRecompiler::IR::ResourcePlan UserDataBufferPlan() { return ExtractResourcePlan(program); } -Libs::Graphics::ShaderRecompiler::IR::ResourcePlan MixedSamplerPlan() { +Libs::Graphics::ShaderRecompiler::IR::Program MixedSamplerProgram() { using namespace Libs::Graphics::ShaderRecompiler::IR; Program program; program.stage = Libs::Graphics::ShaderType::Compute; program.srt_plan_complete = true; program.resource_tracking_complete = true; - AddValueBlock(program); + auto &block = AddValueBlock(program); - const auto AddSource = [&program](uint32_t dword_count, uint32_t first) { + const auto AddSource = [&program](uint32_t dword_count) { DescriptorSource source; source.dword_count = dword_count; - source.dwords[0] = Value(first); - for (uint32_t i = 1; i < dword_count; i++) { + for (uint32_t i = 0; i < dword_count; i++) { source.dwords[i] = Value(0u); } program.descriptor_sources.push_back(source); return static_cast(program.descriptor_sources.size() - 1u); }; - const auto image0 = AddSource(8, 0); - const auto image1 = AddSource(8, 0); - const auto sampler0 = AddSource(4, 0x11111111u); - const auto sampler1 = AddSource(4, 0x22222222u); + const auto image0 = AddSource(8); + const auto image1 = AddSource(8); + const auto sampler0 = AddSource(4); + const auto sampler1 = AddSource(4); + for (uint32_t index = 0; index < 2; ++index) { + auto &value = block.AppendNewInst( + ValueOpcode::GetUserData, {Value(static_cast(index))}); + program.descriptor_sources[sampler0 + index].dwords[0] = Value(&value); + } program.info.images.push_back( {.source = image0, .resource_class = ImageResourceClass::Sampled, @@ -137,7 +141,7 @@ Libs::Graphics::ShaderRecompiler::IR::ResourcePlan MixedSamplerPlan() { program.info.sampled_pairs.push_back({.image = 0, .sampler = 0}); program.info.sampled_pairs.push_back({.image = 0, .sampler = 1}); program.info.sampled_pairs.push_back({.image = 1, .sampler = 1}); - return ExtractResourcePlan(program); + return program; } void TestMappedSrtUsesDirectReaderByDefault() { @@ -373,18 +377,35 @@ void TestFiniteImageRefreshReusesScalarReads() { "finite image refresh grew reusable resource storage after warmup"); } -void TestMixedSamplerDuplicatesTheCorrectSnapshot() { +void TestMixedSamplerVariantsShareRuntimeDescriptor() { using namespace Libs::Graphics::ShaderRecompiler::IR; - auto plan = MixedSamplerPlan(); + auto program = MixedSamplerProgram(); + auto plan = ExtractResourcePlan(program); + std::array user_data{0x11111111u, 0x22222222u}; + const SrtRuntime runtime{.user_data = user_data}; ResourceSnapshot snapshot; ResourceSpecialization specialization; - Check(MaterializeResources(plan, {}, snapshot, specialization), + Check(MaterializeResources(plan, runtime, snapshot, specialization), "mixed sampler materialization failed"); - Check(snapshot.samplers.size() == 3, - "mixed sampler materialization appended unrelated samplers"); - Check(snapshot.samplers[2] == snapshot.samplers[1] && - snapshot.samplers[2] != snapshot.samplers[0], - "point sampler variant duplicated the wrong runtime descriptor"); + ApplyResourceSpecialization(program, specialization); + const auto &samplers = program.info.samplers; + Check(snapshot.samplers.size() == 2 && samplers.size() == 3 && + samplers[0].snapshot_index == 0 && samplers[1].snapshot_index == 1 && + samplers[2].snapshot_index == 1 && + samplers[0].source == plan.info.samplers[0].source && + samplers[1].source == plan.info.samplers[1].source && + samplers[2].source == samplers[1].source && + !samplers[1].force_point_filtering && samplers[2].force_point_filtering && + program.info.sampled_pairs[2].sampler == 2, + "native sampler variants lost their source identity or binding order"); + const auto capacity = snapshot.samplers.capacity(); + user_data[1] = 0x33333333u; + Check(MaterializeResources(plan, runtime, snapshot, specialization) && + snapshot.samplers.size() == 2 && snapshot.samplers.capacity() == capacity && + snapshot.samplers[samplers[0].snapshot_index].dwords[0] == user_data[0] && + snapshot.samplers[samplers[1].snapshot_index].dwords[0] == user_data[1] && + snapshot.samplers[samplers[2].snapshot_index].dwords[0] == user_data[1], + "sampler variants retained stale or duplicated descriptors after refresh"); } } // namespace @@ -410,7 +431,7 @@ int main() { TestWrittenDescriptorUsesStrictReaderOnce(); TestFailedMaterializationRejectsStage(); TestFiniteImageRefreshReusesScalarReads(); - TestMixedSamplerDuplicatesTheCorrectSnapshot(); + TestMixedSamplerVariantsShareRuntimeDescriptor(); std::puts("ResourceMaterializationTests: all cases passed"); return 0; } diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index 86e1ad20..5de1d24c 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -34690,7 +34690,11 @@ void CheckImageSamplerSpecialization() { mixed_sampler_program.info.sampled_pairs[2].sampler == 2u && mixed_sampler_program.info.sampled_pairs[3].sampler == 1u && mixed_sampler_program.memory_info[0].sampler == 2u && - mixed_sampler_snapshot.samplers.size() == 3u, + mixed_sampler_snapshot.samplers.size() == 1u && + std::ranges::all_of(mixed_sampler_program.info.samplers, + [](const auto& sampler) { + return sampler.source == 4u && sampler.snapshot_index == 0u; + }), "a shared float/unsigned/signed sampler lost its border or filtering " "class in the compiled bindings, instruction or descriptor snapshot");