Reuse prepared shader bindings across draws and dispatches

This commit is contained in:
nmzik
2026-09-24 04:42:23 +04:00
parent 0382d0ca58
commit 3bd5de646d
5 changed files with 115 additions and 30 deletions
@@ -750,18 +750,27 @@ void RenderExecutor::ResetBindings() {
m_bound_images.clear();
}
PreparedBindings RenderExecutor::PrepareBindings(const ShaderStageRuntime& runtime) {
void RenderExecutor::PrepareBindings(const ShaderStageRuntime& runtime,
PreparedBindings& prepared) {
KYTY_PROFILER_FUNCTION();
EXIT_IF(!runtime);
const auto& program = *runtime.program;
const auto& snapshot = *runtime.resources;
PreparedBindings prepared;
prepared.runtime = &runtime;
prepared.images.reserve(program.info.images.size());
prepared.gds = {nullptr, 0, VK_WHOLE_SIZE};
prepared.flattened_srt = {};
prepared.shader_data_buffer = {};
prepared.buffer_sources.clear();
prepared.buffers.clear();
prepared.images.resize(program.info.images.size());
prepared.samplers.clear();
prepared.shader_data.clear();
for (uint32_t i = 0; i < program.info.images.size(); i++) {
auto binding = ResolveTexture(program.info.images[i], snapshot.images[i]);
BindImage(binding.image_id, binding.desc.type == TextureCache::BindingType::Storage);
prepared.images.push_back(std::move(binding));
binding.mip_views.swap(prepared.images[i].mip_views);
binding.mip_views.clear();
prepared.images[i] = std::move(binding);
}
prepared.samplers.reserve(program.info.samplers.size());
for (uint32_t i = 0; i < program.info.samplers.size(); i++) {
@@ -776,7 +785,6 @@ PreparedBindings RenderExecutor::PrepareBindings(const ShaderStageRuntime& runti
program.bindings, ShaderRecompiler::IR::DescriptorBindingKind::Gds) != nullptr) {
prepared.gds.buffer = m_context.GetBufferCache().GetGdsBuffer()->Handle();
}
return prepared;
}
void RenderExecutor::FindBuffers(PreparedBindings& prepared) {
+3 -1
View File
@@ -160,7 +160,7 @@ public:
void DispatchIndirect(uint64_t submit_id, CommandBuffer& buffer, uint64_t args_addr,
uint32_t mode);
[[nodiscard]] PreparedBindings PrepareBindings(const ShaderStageRuntime& runtime);
void PrepareBindings(const ShaderStageRuntime& runtime, PreparedBindings& prepared);
void FindBuffers(PreparedBindings& bindings);
void RebindBuffers(PreparedBindings& bindings);
void RebindImages(PreparedBindings& bindings);
@@ -210,6 +210,8 @@ private:
uint32_t group_y, uint32_t group_z, uint32_t mode);
RenderContext& m_context;
GraphicsBindings m_graphics_bindings;
PreparedBindings m_compute_bindings;
std::vector<ImageId> m_bound_images;
std::vector<vk::DescriptorBufferInfo> m_descriptor_buffers;
std::vector<vk::DescriptorImageInfo> m_descriptor_images;
@@ -362,7 +362,8 @@ void RenderExecutor::DispatchDirect(uint64_t submit_id, CommandBuffer& buffer,
buffer.EndRendering();
auto& pipeline =
m_context.GetPipelineCache().GetComputePipeline(input_info, compute_program);
auto bindings = PrepareBindings(input_info.stage);
auto& bindings = m_compute_bindings;
PrepareBindings(input_info.stage, bindings);
FindBuffers(bindings);
if (program.info.uses_dma) {
m_context.PrepareBda();
@@ -414,7 +415,8 @@ void RenderExecutor::DispatchIndirect(uint64_t submit_id, CommandBuffer& buffer,
cs_regs, buffer.GetRegisters().GetShaderRegisters(), input_info);
buffer.EndRendering();
auto& pipeline = m_context.GetPipelineCache().GetComputePipeline(input_info, compute_program);
auto bindings = PrepareBindings(input_info.stage);
auto& bindings = m_compute_bindings;
PrepareBindings(input_info.stage, bindings);
FindBuffers(bindings);
const auto& program = *input_info.stage.program;
if (program.info.uses_dma) {
@@ -1076,15 +1076,16 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff
index_source.guest_element_size);
}
LogDrawPhase(draw.Name(), "PrepareBindings");
GraphicsBindings bindings;
auto& bindings = m_graphics_bindings;
std::array<PreparedBindings*, 4> descriptor_stages {};
uint32_t stage_count = 0;
for (uint32_t i = 0; i < vertex_stages.size(); i++) {
bindings.vertex[i] = PrepareBindings(state.vertex_info[i].stage);
PrepareBindings(state.vertex_info[i].stage, bindings.vertex[i]);
descriptor_stages[stage_count++] = &bindings.vertex[i];
}
if (state.ps_active) {
bindings.pixel.emplace(PrepareBindings(state.ps_input_info.stage));
if (!bindings.pixel) bindings.pixel.emplace();
PrepareBindings(state.ps_input_info.stage, *bindings.pixel);
descriptor_stages[stage_count++] = &*bindings.pixel;
}
const auto stages = std::span {descriptor_stages.data(), stage_count};
@@ -1116,7 +1117,7 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff
if (!mesh_active) {
CommitVertexBuffers(vk_buffer, vertex_bindings);
}
if (bindings.pixel && !draw.IsIndexed()) {
if (state.ps_active && !draw.IsIndexed()) {
SetDrawDebugPhase(buffer, submit_id, draw, 0x300u);
}
CommitBindings(buffer, vk::PipelineBindPoint::eGraphics, pipeline, stages);
+90 -18
View File
@@ -408,10 +408,11 @@ struct RenderExecutorTestAccess {
const ShaderStageRuntime &pixel,
bool pixel_active) {
RenderExecutor::GraphicsBindings result;
result.vertex[0] = executor.PrepareBindings(vertex);
executor.PrepareBindings(vertex, result.vertex[0]);
std::array<PreparedBindings *, 2> stages{&result.vertex[0], nullptr};
if (pixel_active) {
result.pixel.emplace(executor.PrepareBindings(pixel));
result.pixel.emplace();
executor.PrepareBindings(pixel, *result.pixel);
stages[1] = &*result.pixel;
}
executor.PrepareGraphicsBindings(
@@ -1983,17 +1984,65 @@ public:
ShaderRecompiler::IR::ResourceSnapshot pixel_snapshot;
pixel_snapshot.user_data = {0x33333333u, 0x44444444u};
ShaderStageRuntime pixel_runtime{&pixel_program, &pixel_snapshot};
auto vertex = context.GetRenderExecutor().PrepareBindings(vertex_runtime);
auto pixel = context.GetRenderExecutor().PrepareBindings(pixel_runtime);
PreparedBindings vertex;
PreparedBindings pixel;
auto &executor = context.GetRenderExecutor();
executor.PrepareBindings(vertex_runtime, vertex);
executor.PrepareBindings(pixel_runtime, pixel);
const auto *vertex_storage = vertex.shader_data.data();
const auto *pixel_storage = pixel.shader_data.data();
vertex.buffer_sources.push_back({});
vertex.buffers.push_back({});
vertex.gds.offset = 1;
vertex.flattened_srt.offset = 1;
vertex.shader_data_buffer.offset = 1;
vertex_snapshot.user_data = {0x55555555u, 0x66666666u};
pixel_snapshot.user_data = {0x77777777u, 0x88888888u};
executor.PrepareBindings(vertex_runtime, vertex);
executor.PrepareBindings(pixel_runtime, pixel);
Require(name, "prepared binding scratch",
vertex.shader_data.data() == vertex_storage &&
pixel.shader_data.data() == pixel_storage &&
vertex.buffer_sources.empty() && vertex.buffers.empty() &&
vertex.gds.offset == 0 && vertex.flattened_srt.offset == 0 &&
vertex.shader_data_buffer.offset == 0,
"repeated shader binding preparation allocated or retained stale state");
const auto pipeline = RenderExecutorTestAccess::CommitBindings(
context.GetRenderExecutor(), scheduler.Current(), vertex, pixel);
Require(name, "shared push data",
vertex.shader_data ==
std::vector<uint32_t>{0x11111111u, 0x22222222u} &&
std::vector<uint32_t>{0x55555555u, 0x66666666u} &&
pixel.shader_data ==
std::vector<uint32_t>{0x33333333u, 0x44444444u},
std::vector<uint32_t>{0x77777777u, 0x88888888u},
"graphics stages did not commit their shared push data");
ShaderRecompiler::IR::CompiledShaderInfo image_program{};
image_program.stage = ShaderType::Compute;
ShaderRecompiler::IR::ImageResource image{};
image.resource_class = ShaderRecompiler::IR::ImageResourceClass::Sampled;
image.numeric_class = Prospero::TextureNumericClass::Float;
image.dimension = ShaderRecompiler::Decoder::ImageDimension::Dim2D;
image.read = true;
image_program.info.images.push_back(image);
ShaderRecompiler::IR::ResourceSnapshot image_snapshot{};
image_snapshot.images.emplace_back().dword_count = 8;
ShaderStageRuntime image_runtime{&image_program, &image_snapshot};
PreparedBindings image_prepared;
executor.PrepareBindings(image_runtime, image_prepared);
auto *image_storage = image_prepared.images.data();
image_prepared.images[0].mip_views.resize(3);
const auto *mip_storage = image_prepared.images[0].mip_views.data();
executor.PrepareBindings(image_runtime, image_prepared);
Require(name, "prepared image scratch",
image_prepared.images.data() == image_storage &&
image_prepared.images[0].mip_views.empty() &&
image_prepared.images[0].mip_views.capacity() >= 3,
"repeated image preparation retained stale mip views or image storage changed");
image_prepared.images[0].mip_views.resize(3);
Require(name, "prepared mip storage",
image_prepared.images[0].mip_views.data() == mip_storage,
"repeated image preparation reallocated mip view storage");
RenderExecutorTestAccess::ResetBindings(executor);
scheduler.Finish();
RenderExecutorTestAccess::DestroyDescriptorPipelines(
context.GetRenderExecutor(), std::span {&pipeline, 1u});
@@ -10092,7 +10141,8 @@ public:
std::memcpy(value.dwords.data(), buffer_descriptor.fields,
sizeof(buffer_descriptor.fields));
value.dword_count = 4;
auto buffer_bindings = executor.PrepareBindings(buffer_runtime);
PreparedBindings buffer_bindings;
executor.PrepareBindings(buffer_runtime, buffer_bindings);
executor.FindBuffers(buffer_bindings);
const auto original_id = buffer_bindings.buffer_sources[0].id;
@@ -10159,7 +10209,8 @@ public:
null_info.info = std::move(null_program.info);
null_info.bindings = std::move(null_program.bindings);
ShaderStageRuntime null_runtime{&null_info, &null_snapshot};
auto null_bindings = executor.PrepareBindings(null_runtime);
PreparedBindings null_bindings;
executor.PrepareBindings(null_runtime, null_bindings);
executor.RebindImages(null_bindings);
Require(name, "null descriptor count",
null_bindings.images.size() == 3,
@@ -10421,6 +10472,19 @@ public:
sampled_overwide_binding.mip_views.empty() &&
sampled_overwide_binding.image_view != nullptr,
"sampled view lost addressable mips in the allocated tail");
const auto *image_storage = mipped_prepared.images.data();
const auto *mip_storage = mipped_prepared.images[1].mip_views.data();
executor.PrepareBindings(mipped_runtime, mipped_prepared);
Require(name, "prepared mip scratch",
mipped_prepared.images.data() == image_storage &&
mipped_prepared.images[1].mip_views.empty() &&
mipped_prepared.images[1].mip_views.capacity() >= 3,
"repeated image preparation retained stale mip views or reallocated scratch");
executor.RebindImages(mipped_prepared);
Require(name, "prepared mip views rebound",
mipped_prepared.images[1].mip_views.size() == 3 &&
mipped_prepared.images[1].mip_views.data() == mip_storage,
"reused image scratch did not rebuild dynamic storage mip views");
// Streaming T# clamps apply after S# max LOD, without changing the view
// base.
{
@@ -10665,7 +10729,8 @@ public:
storage_info.info = std::move(storage_program.info);
storage_info.bindings = std::move(storage_program.bindings);
ShaderStageRuntime storage_runtime{&storage_info, &storage_snapshot};
auto storage_discovery = executor.PrepareBindings(storage_runtime);
PreparedBindings storage_discovery;
executor.PrepareBindings(storage_runtime, storage_discovery);
const auto storage_id = storage_discovery.images[0].image_id;
Require(name, "storage prefetch purity",
storage_discovery.images[0].image_view == nullptr &&
@@ -11798,7 +11863,8 @@ public:
array_snapshot.images.push_back(array_descriptor);
ShaderStageRuntime array_runtime{&array_program, &array_snapshot};
auto array_binding = executor.PrepareBindings(array_runtime);
PreparedBindings array_binding;
executor.PrepareBindings(array_runtime, array_binding);
executor.RebindImages(array_binding);
const auto expanded_array_id = array_binding.images[0].image_id;
const auto &expanded_array = texture_cache.GetImage(expanded_array_id);
@@ -11886,7 +11952,8 @@ public:
auto &value = buffer_snapshot.buffers.emplace_back();
std::memcpy(value.dwords.data(), descriptor.fields, sizeof(descriptor.fields));
value.dword_count = 4;
auto buffer_bindings = executor.PrepareBindings(buffer_runtime);
PreparedBindings buffer_bindings;
executor.PrepareBindings(buffer_runtime, buffer_bindings);
std::array stages{&buffer_bindings};
RenderColorInfo target{};
target.desc = array_target;
@@ -11971,8 +12038,8 @@ public:
colliding_msaa_snapshot.images.push_back(colliding_msaa_descriptor);
ShaderStageRuntime colliding_msaa_runtime{
&colliding_msaa_program, &colliding_msaa_snapshot};
auto colliding_msaa_binding =
executor.PrepareBindings(colliding_msaa_runtime);
PreparedBindings colliding_msaa_binding;
executor.PrepareBindings(colliding_msaa_runtime, colliding_msaa_binding);
executor.RebindImages(colliding_msaa_binding);
const auto &resolved_colliding_msaa =
colliding_msaa_binding.images[0];
@@ -12030,7 +12097,8 @@ public:
ShaderRecompiler::IR::ResourceSnapshot msaa_snapshot{};
msaa_snapshot.images.push_back(msaa_descriptor);
ShaderStageRuntime msaa_runtime{&msaa_program, &msaa_snapshot};
auto msaa_binding = executor.PrepareBindings(msaa_runtime);
PreparedBindings msaa_binding;
executor.PrepareBindings(msaa_runtime, msaa_binding);
executor.RebindImages(msaa_binding);
const auto &resolved_msaa = msaa_binding.images[0];
Require(
@@ -12069,7 +12137,8 @@ public:
ShaderRecompiler::IR::ResourceSnapshot msaa_array_snapshot{};
msaa_array_snapshot.images.push_back(msaa_array_descriptor);
ShaderStageRuntime msaa_array_runtime{&msaa_array_program, &msaa_array_snapshot};
auto msaa_array_binding = executor.PrepareBindings(msaa_array_runtime);
PreparedBindings msaa_array_binding;
executor.PrepareBindings(msaa_array_runtime, msaa_array_binding);
executor.RebindImages(msaa_array_binding);
const auto &resolved_msaa_array = msaa_array_binding.images[0];
Require(name, "MSAA array backing expansion",
@@ -12333,7 +12402,8 @@ public:
snapshot.images.push_back(descriptor);
ShaderStageRuntime runtime{&program, &snapshot};
auto prepared = context.GetRenderExecutor().PrepareBindings(runtime);
PreparedBindings prepared;
context.GetRenderExecutor().PrepareBindings(runtime, prepared);
const auto sampled_stencil_id = prepared.images[0].image_id;
Require(name, "first stencil discovery",
prepared.images.size() == 1 &&
@@ -12365,7 +12435,8 @@ public:
"at the stencil guest address");
RenderExecutorTestAccess::ResetBindings(executor);
auto redirected = context.GetRenderExecutor().PrepareBindings(runtime);
PreparedBindings redirected;
context.GetRenderExecutor().PrepareBindings(runtime, redirected);
Require(name, "redirected owner discovery",
redirected.images.size() == 1 &&
redirected.images[0].image_id == depth_id &&
@@ -12555,7 +12626,8 @@ public:
sizeof(plane_descriptor.fields));
plane_value.dword_count = 4;
const ShaderStageRuntime plane_runtime{&plane_program, &plane_snapshot};
auto plane_bindings = executor.PrepareBindings(plane_runtime);
PreparedBindings plane_bindings;
executor.PrepareBindings(plane_runtime, plane_bindings);
vk::MemoryBarrier2 plane_barrier{};
plane_barrier.srcStageMask = plane_barrier.dstStageMask =
vk::PipelineStageFlagBits2::eAllCommands;