pipeline: look up draw programs and pipelines from the command buffer

This commit is contained in:
nmzik
2026-10-24 05:18:46 +02:00
parent 269e411ef2
commit dbc7b8b2ff
11 changed files with 301 additions and 382 deletions
+83 -65
View File
@@ -10983,7 +10983,7 @@ public:
shaders.SetCsUserSgpr(12 + i, output.fields[i]);
ShaderComputeInputInfo input{};
(void)context.GetPipelineCache().GetComputeProgram(
shaders.GetCs(), processor.GetCtx().GetShaderRegisters(), input);
shaders.GetCs(), input);
PreparedBindings bindings;
executor.PrepareBindings(input.stage, bindings);
executor.RebindImages(bindings);
@@ -11098,7 +11098,7 @@ public:
}
ShaderComputeInputInfo input{};
const auto program = context.GetPipelineCache().GetComputeProgram(
shaders.GetCs(), processor.GetCtx().GetShaderRegisters(), input);
shaders.GetCs(), input);
if (original_id == 0) original_id = program.id;
Require(name, "exact function identity",
(program.id == original_id) == (test.value == 41),
@@ -11190,7 +11190,7 @@ public:
LibKernel::Memory::WriteBacking(output, actual.data(), wave_size * 4);
ShaderComputeInputInfo input{};
const auto program = context.GetPipelineCache().GetComputeProgram(
shaders.GetCs(), processor.GetCtx().GetShaderRegisters(), input);
shaders.GetCs(), input);
if (phase == 1) original_vector_id = program.id;
if (phase >= 2)
Require(name, "ordered helper identity", (program.id == original_vector_id) == (phase == 3),
@@ -11733,7 +11733,7 @@ public:
const auto clean_tick = scheduler.CurrentTick();
ShaderComputeInputInfo input{};
const auto program = context.GetPipelineCache().GetComputeProgram(
shaders.GetCs(), processor.GetCtx().GetShaderRegisters(), input);
shaders.GetCs(), input);
if (previous_program.id != 0) {
Require(name, "count permutation reuse", program.id == previous_program.id,
"a changing GPU count compiled a new shader permutation");
@@ -11815,7 +11815,7 @@ public:
input.workgroup_counts[0] = input.workgroup_counts[1] = 1;
input.workgroup_counts[2] = test.groups;
const auto program = context.GetPipelineCache().GetComputeProgram(
shaders.GetCs(), processor.GetCtx().GetShaderRegisters(), input);
shaders.GetCs(), input);
Require(name, "workgroup table refresh and coalescing",
input.workgroup_counts[2] == test.groups &&
input.stage.program->info.images.size() == 1 &&
@@ -11833,7 +11833,7 @@ public:
const auto *image_storage = snapshot->images.data();
const auto flattened_capacity = snapshot->flattened_srt.capacity();
const auto repeated = context.GetPipelineCache().GetComputeProgram(
shaders.GetCs(), processor.GetCtx().GetShaderRegisters(), input);
shaders.GetCs(), input);
Require(name, "workgroup table cache-hit storage reuse",
repeated.id == program.id && input.stage.resources == snapshot &&
snapshot->images.data() == image_storage &&
@@ -12072,7 +12072,7 @@ public:
input.workgroup_counts[0] = material_groups;
input.workgroup_counts[1] = input.workgroup_counts[2] = 1u;
(void)context.GetPipelineCache().GetComputeProgram(
shaders.GetCs(), processor.GetCtx().GetShaderRegisters(), input);
shaders.GetCs(), input);
const auto &info = input.stage.program->info;
const auto root = std::ranges::find_if(info.buffers, [](const auto &buffer) {
return buffer.indirect_resources.size() == material_rows;
@@ -18365,10 +18365,12 @@ public:
mode.polymode_back_ptype = back;
mode.provoking_vtx_last = provoking_last;
registers.SetModeControl(mode);
return context.GetPipelineCache().GetGraphicsPipeline(
std::span{&color, 1u}, depth, std::span{&vertex, 1u}, scheduler.Current(), &pixel,
topology, false,
PipelineCache::GraphicsPrograms{{vertex_shader}, pixel_shader});
DrawRenderState state{.depth_info = depth, .color_info = {color}, .color_count = 1,
.topology = topology, .ps_active = true,
.vertex_info = {vertex}, .ps_input_info = pixel};
state.vertex_info[0].stage.handle = &vertex_shader;
state.ps_input_info.stage.handle = &pixel_shader;
return context.GetPipelineCache().GetGraphicsPipeline(scheduler.Current(), state);
};
auto &filled = pipeline(true, 2, 2);
const auto draw = [&](const PipelineCache::Pipeline &selected, uint32_t vertex_count = 3,
@@ -18719,17 +18721,50 @@ public:
ShaderMapUserData(pixel_address,
{.type = Prospero::ShaderBinaryType::kPs,
.code_size_bytes = static_cast<uint32_t>(native_pixel.size() * sizeof(u32))});
HW::VertexShaderInfo native_vertex_regs{};
native_vertex_regs.es_regs.data_addr = vertex_address;
HW::PixelShaderInfo native_pixel_regs{};
native_pixel_regs.ps_regs.data_addr = pixel_address;
shaders.SetEsShaderBase(vertex_address);
shaders.SetPsShaderBase(pixel_address);
const auto owned_vertex_shader = vertex_shader;
const auto owned_pixel_shader = pixel_shader;
depth.stencil_test_enable = false;
registers.SetViewportTransformControl(0x300);
user_config.SetPrimitiveType(Prospero::PrimitiveType::kTriList);
std::array<Prospero::ColorComponentMapping, 8> export_mapping{};
std::array<ShaderVertexInputInfo, 3> native_vertex_info{};
// The cache reads the bound shaders, the color outputs and the export mapping itself.
const auto refresh = [&] {
DrawRenderState state{};
context.GetPipelineCache().GetGraphicsPrograms(scheduler.Current(), state);
return state;
};
const auto native_programs = [&] {
const auto native = refresh();
vertex = native.vertex_info[0];
pixel = native.ps_input_info;
vertex_shader = *vertex.stage.handle;
pixel_shader = *pixel.stage.handle;
};
const auto silent = refresh();
registers.SetDepthShaderControl({.shader_kill_enable = true});
const auto killing = refresh();
registers.SetDepthShaderControl({.shader_z_export_enable = true});
const auto depth_writing = refresh();
registers.SetDepthShaderControl({});
registers.SetTargetOutputMode(0, 4);
const auto writing = refresh();
// Slot 1 takes color output, but the pixel program exports only MRT0.
registers.SetRenderTargetMask(0xff);
registers.SetShaderMask(0xff);
registers.SetTargetOutputMode(1, 4);
const auto partial = refresh();
registers.SetTargetOutputMode(1, 0);
registers.SetShaderMask(0xf);
registers.SetRenderTargetMask(0xf);
Require(name, "pixel stage activity",
!silent.ps_active && !silent.ps_input_info.stage && silent.mrt_mask == 0 &&
killing.ps_active && killing.ps_input_info.stage && killing.mrt_mask == 0 &&
depth_writing.ps_active && depth_writing.mrt_mask == 0 &&
writing.ps_active && writing.mrt_mask == 1u && partial.mrt_mask == 1u &&
writing.VertexStages().size() == 1 &&
writing.vertex_info[0].stage.handle != nullptr,
"pixel activity or its MRT mask ignored the color outputs or depth side effects");
std::array<uint64_t, 2> wave_ids{};
std::array<std::vector<u32>, 2> wave_keys;
struct PixelWaveCase { uint32_t control, width, color; };
@@ -18737,9 +18772,7 @@ public:
PixelWaveCase{0x8008, 32, 0x3e800000},
PixelWaveCase{0x0008, 64, 0}}) {
registers.SetPsInControl(wave.control);
const auto programs = context.GetPipelineCache().GetGraphicsPrograms(
native_vertex_regs, native_pixel_regs, registers.GetShaderRegisters(),
registers, user_config, export_mapping, true, native_vertex_info, pixel);
native_programs();
Require(name, "pixel wave metadata propagation",
pixel.input_num == 8 && pixel.stage.program->wave_size == wave.width,
"SPI_PS_IN_CONTROL width did not reach the compiled pixel program");
@@ -18748,14 +18781,11 @@ public:
BuildStageStaticKey(pixel, key);
if (wave_ids[index] != 0) {
Require(name, "pixel wave cache reuse",
programs.pixel.id == wave_ids[index] && key == wave_keys[index],
pixel_shader.id == wave_ids[index] && key == wave_keys[index],
"restoring wave64 did not reuse its original program");
}
wave_ids[index] = programs.pixel.id;
wave_ids[index] = pixel_shader.id;
wave_keys[index] = std::move(key);
vertex_shader = programs.vertex[0];
pixel_shader = programs.pixel;
vertex = native_vertex_info[0];
draw(pipeline(true, 2, 2));
const auto wave_pixels = read_color();
for (size_t component = 0; component < wave_pixels.size(); component++) {
@@ -18791,8 +18821,8 @@ public:
*(position + 1) = EncodeExp1(3, 4, 0, 9);
return code;
}();
native_vertex_regs.es_regs.data_addr = reinterpret_cast<uint64_t>(waterfall_vertex.data());
ShaderMapUserData(native_vertex_regs.es_regs.data_addr,
shaders.SetEsShaderBase(reinterpret_cast<uint64_t>(waterfall_vertex.data()));
ShaderMapUserData(shaders.GetVs().es_regs.data_addr,
{.type = Prospero::ShaderBinaryType::kGs, .user_data = &native_user_data,
.code_size_bytes = static_cast<u32>(waterfall_vertex.size() * sizeof(u32))});
registers.SetPsInControl(0x8008);
@@ -18800,12 +18830,7 @@ public:
for (const u32 wave : {32u, 64u}) {
registers.SetShaderStages((saved_stages & ~0x00400000u) |
(wave == 32u ? 0x00400000u : 0u));
const auto programs = context.GetPipelineCache().GetGraphicsPrograms(
native_vertex_regs, native_pixel_regs, registers.GetShaderRegisters(),
registers, user_config, export_mapping, true, native_vertex_info, pixel);
vertex_shader = programs.vertex[0];
pixel_shader = programs.pixel;
vertex = native_vertex_info[0];
native_programs();
Require(name, "native NGG waterfall wave",
vertex.stage.program->stage == ShaderType::Vertex &&
vertex.stage.program->wave_size == wave,
@@ -18820,7 +18845,7 @@ public:
}
}
registers.SetShaderStages(saved_stages);
native_vertex_regs.es_regs.data_addr = vertex_address;
shaders.SetEsShaderBase(vertex_address);
// Reversed RGBA targets must use logical Sa for RGB, with a separate
// zero or unit source factor for separate alpha attenuation or accumulation.
@@ -18835,8 +18860,8 @@ public:
AppendEnd(&code);
return code;
}();
native_pixel_regs.ps_regs.data_addr = reinterpret_cast<uint64_t>(blend_pixel.data());
ShaderMapUserData(native_pixel_regs.ps_regs.data_addr,
shaders.SetPsShaderBase(reinterpret_cast<uint64_t>(blend_pixel.data()));
ShaderMapUserData(shaders.GetPs().ps_regs.data_addr,
{.type = Prospero::ShaderBinaryType::kPs,
.code_size_bytes = static_cast<uint32_t>(blend_pixel.size() * sizeof(u32))});
const auto saved_blend = registers.GetBlendControl(0);
@@ -18845,8 +18870,13 @@ public:
registers.SetTargetOutputMode(0, 4);
auto blend_target_info = saved_target_info;
blend_target_info.blend_bypass = false;
// A reversed RGBA32F target exports through the ABGR mapping.
blend_target_info.format = Prospero::ChannelLayout::k32_32_32_32;
blend_target_info.channel_type = Prospero::ChannelType::kFloat;
blend_target_info.channel_order = Prospero::ChannelOrder::kReversed;
registers.SetColorInfo(0, blend_target_info);
export_mapping[0] = color.export_mapping = Prospero::ColorMappingAbgr;
registers.SetColorBase(0, {.addr = depth_address + 0x10000});
color.export_mapping = Prospero::ColorMappingAbgr;
HW::BlendControl alpha_blend{};
alpha_blend.enable = true;
alpha_blend.color_srcblend = static_cast<uint8_t>(Prospero::BlendFactor::kSrcAlpha);
@@ -18869,22 +18899,18 @@ public:
alpha_blend.separate_alpha_blend = test.separate;
alpha_blend.alpha_srcblend = static_cast<uint8_t>(test.alpha_source);
registers.SetBlendControl(0, alpha_blend);
const auto programs = context.GetPipelineCache().GetGraphicsPrograms(
native_vertex_regs, native_pixel_regs, registers.GetShaderRegisters(),
registers, user_config, export_mapping, true, native_vertex_info, pixel);
native_programs();
Require(name, "logical alpha blend program", pixel.alpha_blend_source == test.mode &&
pixel.dual_source_blending,
pixel.dual_source_blending &&
pixel.target_export_mapping[0] == Prospero::ColorMappingAbgr,
"the separate alpha equation did not reach the compiled pixel program");
vertex_shader = programs.vertex[0];
pixel_shader = programs.pixel;
vertex = native_vertex_info[0];
auto &selected = pipeline(true, 2, 2);
if (test.mode == ShaderAlphaBlendSource::SourceAlpha) {
Require(name, "equal blend equation cache reuse",
broadcast_program == 0 || (programs.pixel.id == broadcast_program &&
broadcast_program == 0 || (pixel_shader.id == broadcast_program &&
selected.pipeline == broadcast_pipeline),
"equivalent separate alpha state created another shader or pipeline");
broadcast_program = programs.pixel.id;
broadcast_program = pixel_shader.id;
broadcast_pipeline = selected.pipeline;
}
constexpr std::array<float, 4> destination{0.3f, 0.3f, 0.1f, 0.2f};
@@ -18898,9 +18924,10 @@ public:
"reversed blending changed logical RGB or the separate alpha equation");
}
}
native_pixel_regs.ps_regs.data_addr = pixel_address;
export_mapping[0] = color.export_mapping = {};
shaders.SetPsShaderBase(pixel_address);
color.export_mapping = {};
registers.SetBlendControl(0, saved_blend);
registers.SetColorBase(0, {});
registers.SetColorInfo(0, saved_target_info);
registers.SetTargetOutputMode(0, saved_output_mode);
@@ -18939,17 +18966,12 @@ public:
user_config.SetPrimitiveType(Prospero::PrimitiveType::kTriStrip);
for (size_t i = 0; i < position_shaders.size(); i++) {
const auto &code = position_shaders[i];
native_vertex_regs.es_regs.data_addr = reinterpret_cast<uint64_t>(code.data());
ShaderMapUserData(native_vertex_regs.es_regs.data_addr,
shaders.SetEsShaderBase(reinterpret_cast<uint64_t>(code.data()));
ShaderMapUserData(shaders.GetVs().es_regs.data_addr,
{.type = Prospero::ShaderBinaryType::kGs,
.user_data = &native_user_data,
.code_size_bytes = static_cast<uint32_t>(code.size() * sizeof(u32))});
const auto programs = context.GetPipelineCache().GetGraphicsPrograms(
native_vertex_regs, native_pixel_regs, registers.GetShaderRegisters(),
registers, user_config, export_mapping, true, native_vertex_info, pixel);
vertex_shader = programs.vertex[0];
pixel_shader = programs.pixel;
vertex = native_vertex_info[0];
native_programs();
draw(pipeline(true, 2, 2, false, false, vk::PrimitiveTopology::eTriangleStrip), 4);
const auto pixels = read_color();
bool second_triangle = false;
@@ -19486,11 +19508,7 @@ public:
shaders.SetGsShaderResource2({});
shaders.SetPsShaderBase(ps_address);
user_config.SetPrimitiveType(Prospero::PrimitiveType::kTriList);
native_vertex_regs.es_regs.data_addr = vertex_address;
native_pixel_regs.ps_regs.data_addr = ps_address;
const auto programs = context.GetPipelineCache().GetGraphicsPrograms(
native_vertex_regs, native_pixel_regs, registers.GetShaderRegisters(),
registers, user_config, export_mapping, true, native_vertex_info, pixel);
native_programs();
Require(name, "packed format follows export ordinal",
pixel.target_output_mode[2] == 9 && pixel.wave_size == test.wave &&
pixel.target_packed_unorm_mask == 4u &&
@@ -19498,9 +19516,9 @@ public:
"packed conversion or rounding contaminated another MRT or followed a physical slot");
if (test.slot == 2) {
auto &id = test.truncate ? truncate_program : half_up_program;
Require(name, "packed rounding program reuse", id == 0 || id == programs.pixel.id,
Require(name, "packed rounding program reuse", id == 0 || id == pixel_shader.id,
"restoring half-up rounding failed to reuse its program");
id = programs.pixel.id;
id = pixel_shader.id;
}
RenderColorInfo packed_target{}, float_target{};
RenderExecutorTestAccess::ResolveRenderColorTarget(
@@ -44518,13 +44536,13 @@ void CheckComputeLdsLimit(VulkanHarness &vulkan) {
uint16_t{112}, uint16_t{128}, uint16_t{128}}) {
regs.cs_regs.lds_size = units;
ShaderComputeInputInfo input{};
const auto program = cache.GetComputeProgram(regs, {}, input);
const auto program = cache.GetComputeProgram(regs, input);
Require(name, "guest allocation and device backing",
input.lds_size_dwords == units * 128u &&
input.lds_storage == (units > limit_units),
"compute LDS was truncated or chose backing that exceeds the device limit");
Require(name, "pipeline creation",
cache.GetComputePipeline(input, program).pipeline != nullptr,
cache.GetComputePipeline(input).pipeline != nullptr,
"full-size LDS shader did not create a Vulkan compute pipeline");
if (previous.id != 0) {
Require(name, "guest-size cache identity",
+4 -6
View File
@@ -10810,11 +10810,10 @@ void TestGraphicsComputeGetreg() {
ShaderMappedData mapped{};
mapped.code_size_bytes = sizeof(shader);
ShaderMapUserData(regs.cs_regs.data_addr, mapped);
HW::ShaderRegisters sh{};
ShaderComputeInputInfo graphics{}, async{};
async.async_compute = true;
(void)PrepareProgram(regs, sh, graphics);
(void)PrepareProgram(regs, sh, async);
(void)PrepareProgram(regs, graphics);
(void)PrepareProgram(regs, async);
Check(!graphics.async_compute && async.async_compute &&
MakeStageStaticKey(graphics) != MakeStageStaticKey(async) &&
MakeStageStaticKey(graphics).size() == MakeStageStaticKey(async).size(),
@@ -15782,13 +15781,12 @@ void TestComputeLdsAllocationIdentity() {
ShaderMappedData mapped{};
mapped.code_size_bytes = sizeof(shader);
ShaderMapUserData(regs.cs_regs.data_addr, mapped);
HW::ShaderRegisters sh{};
const auto compile = [&](uint16_t encoded_lds_size,
uint32_t expected_dwords) {
regs.cs_regs.lds_size = encoded_lds_size;
ShaderComputeInputInfo input_info{};
const auto params = PrepareProgram(regs, sh, input_info);
const auto params = PrepareProgram(regs, input_info);
Check(input_info.lds_size_dwords == expected_dwords,
"COMPUTE_PGM_RSRC2 LDS allocation units were not decoded");
auto options = MakeCompileOptions(ShaderType::Compute);
@@ -15854,7 +15852,7 @@ void TestComputeLdsAllocationIdentity() {
ShaderMapUserData(regs.cs_regs.data_addr, mapped);
regs.cs_regs.lds_size = decode_lds_field(lds_896_rsrc2);
ShaderComputeInputInfo scratch_info{};
const auto scratch_params = PrepareProgram(regs, sh, scratch_info);
const auto scratch_params = PrepareProgram(regs, scratch_info);
Check(scratch_info.scratch_size_dwords == 7,
"AGC per-thread scratch size was not propagated");
auto scratch_options = MakeCompileOptions(ShaderType::Compute);