Bind only exported color targets and preserve MRT slots

Related work: https://github.com/KytyPS5/KytyPS5/pull/558

Co-authored-by: Çağatay Güçlü <33038942+defektu@users.noreply.github.com>
This commit is contained in:
nmzik
2026-09-16 19:20:14 +04:00
parent bc45aaf604
commit b594f8c834
3 changed files with 198 additions and 89 deletions
@@ -666,13 +666,16 @@ PipelineCache::Pipeline& PipelineCache::GetGraphicsPipeline(
key.ps_shader_id = ps_id;
auto& static_params = key.static_params;
auto& rendering = key.rendering;
rendering.color_count = color_count;
rendering.color_count = 0;
uint32_t attachment_samples = 0;
for (uint32_t i = 0; i < color_count; i++) {
const auto slot = colors[i].target_slot;
EXIT_IF(slot >= RENDER_COLOR_ATTACHMENTS_MAX);
rendering.color_count = std::max(rendering.color_count, slot + 1);
EXIT_IF(!colors[i].image_id || colors[i].desc.view_info.format == vk::Format::eUndefined);
static_params.color_mask[i] = colors[i].export_mapping.ApplyMask(
static_params.color_mask[slot] = colors[i].export_mapping.ApplyMask(
render_target_mask_slot(ctx.GetRenderTargetMask(), colors[i].target_slot));
rendering.color_formats[i] = colors[i].desc.view_info.format;
rendering.color_formats[slot] = colors[i].desc.view_info.format;
if (attachment_samples == 0) {
attachment_samples = colors[i].desc.info.samples;
} else if (attachment_samples != colors[i].desc.info.samples) {
@@ -681,14 +684,14 @@ PipelineCache::Pipeline& PipelineCache::GetGraphicsPipeline(
}
const auto& rt = ctx.GetRenderTarget(colors[i].target_slot);
const auto& bc = ctx.GetBlendControl(colors[i].target_slot);
static_params.color_srcblend[i] = bc.color_srcblend;
static_params.color_comb_fcn[i] = bc.color_comb_fcn;
static_params.color_destblend[i] = bc.color_destblend;
static_params.alpha_srcblend[i] = bc.alpha_srcblend;
static_params.alpha_comb_fcn[i] = bc.alpha_comb_fcn;
static_params.alpha_destblend[i] = bc.alpha_destblend;
static_params.separate_alpha_blend[i] = bc.separate_alpha_blend;
static_params.blend_enable[i] = bc.enable && !rt.info.blend_bypass;
static_params.color_srcblend[slot] = bc.color_srcblend;
static_params.color_comb_fcn[slot] = bc.color_comb_fcn;
static_params.color_destblend[slot] = bc.color_destblend;
static_params.alpha_srcblend[slot] = bc.alpha_srcblend;
static_params.alpha_comb_fcn[slot] = bc.alpha_comb_fcn;
static_params.alpha_destblend[slot] = bc.alpha_destblend;
static_params.separate_alpha_blend[slot] = bc.separate_alpha_blend;
static_params.blend_enable[slot] = bc.enable && !rt.info.blend_bypass;
}
const bool with_depth =
depth.desc.view_info.format != vk::Format::eUndefined && static_cast<bool>(depth.image_id);
+68 -76
View File
@@ -15,6 +15,7 @@
#include "graphics/host_gpu/renderer/colorRenderTarget.h"
#include "graphics/host_gpu/renderer/debug.h"
#include "graphics/host_gpu/renderer/depthRenderTarget.h"
#include "graphics/host_gpu/renderer/image/textureCommon.h"
#include "graphics/host_gpu/renderer/pipeline/pipelineCache.h"
#include "graphics/host_gpu/renderer/pipeline/shaderResourceBarrier.h"
#include "graphics/host_gpu/renderer/render.h"
@@ -308,24 +309,12 @@ static void LogDrawInputState(const CommandBuffer& buffer, const RenderColorInfo
static void SetGraphicsDynamicParams(const CommandBuffer& buffer, vk::CommandBuffer vk_buffer,
const ShaderVertexInputInfo& vs_input_info,
const RenderColorInfo* colors, uint32_t color_count,
const RenderDepthInfo& depth) {
const RenderDepthInfo& depth, const RenderState& rendering) {
KYTY_PROFILER_FUNCTION();
EXIT_IF(colors == nullptr);
const auto& ctx = buffer.GetRegisters();
const auto& vp = ctx.GetScreenViewport();
vk::Extent2D framebuffer_extent {};
if (color_count > 0 && colors[0].image_id) {
framebuffer_extent = colors[0].Extent();
} else if (depth.image_id) {
framebuffer_extent = {depth.desc.info.extent.width, depth.desc.info.extent.height};
} else {
const auto& limits = buffer.GetGraphics().GetPhysicalDeviceProperties().limits;
framebuffer_extent = {limits.maxFramebufferWidth, limits.maxFramebufferHeight};
}
const auto& vp = ctx.GetScreenViewport();
const vk::Extent2D framebuffer_extent {rendering.width, rendering.height};
const auto& outputs = vs_input_info.stage.program->info.outputs;
const bool indexed_viewports =
std::any_of(outputs.begin(), outputs.end(), [](const auto& output) {
@@ -420,15 +409,11 @@ static void SetGraphicsDynamicParams(const CommandBuffer& buffer, vk::CommandBuf
// eColorWriteEnableEXT dynamic state and relies on the static colorWriteMask instead.
#else
vk::Bool32 enable[RENDER_COLOR_ATTACHMENTS_MAX] = {};
// Color-control operation selects special color-buffer paths, not the normal component write
// mask. Attachment availability therefore follows the target write mask.
for (uint32_t i = 0; i < color_count; i++) {
enable[i] = render_target_mask_slot(ctx.GetRenderTargetMask(), colors[i].target_slot) != 0
? VK_TRUE
: VK_FALSE;
for (uint32_t slot = 0; slot < rendering.num_color_attachments; slot++) {
enable[slot] = rendering.color_attachments[slot].image_view != nullptr;
}
if (color_count != 0) {
vk_buffer.setColorWriteEnableEXT(color_count, enable);
if (rendering.num_color_attachments != 0) {
vk_buffer.setColorWriteEnableEXT(rendering.num_color_attachments, enable);
}
#endif
}
@@ -474,7 +459,7 @@ RenderState RenderExecutor::AcquireRenderTargets(CommandBuffer& buffer, RenderCo
state.width = std::numeric_limits<uint32_t>::max();
state.height = std::numeric_limits<uint32_t>::max();
state.num_layers = std::numeric_limits<uint32_t>::max();
state.num_color_attachments = color_count;
state.num_color_attachments = 0;
uint32_t attachment_samples = 0;
for (uint32_t i = 0; i < color_count; i++) {
auto& target = colors[i];
@@ -516,7 +501,8 @@ RenderState RenderExecutor::AcquireRenderTargets(CommandBuffer& buffer, RenderCo
state.width = std::min(state.width, extent.width);
state.height = std::min(state.height, extent.height);
state.num_layers = std::min(state.num_layers, view.layer_count);
auto& attachment = state.color_attachments[i];
state.num_color_attachments = std::max(state.num_color_attachments, target.target_slot + 1);
auto& attachment = state.color_attachments[target.target_slot];
attachment.image_view = image_view;
attachment.image_layout = layout;
}
@@ -891,42 +877,6 @@ static bool ResolvePrimitiveRestart(const CommandBuffer& buffer,
return false;
}
bool RenderExecutor::PrepareDrawRenderState(CommandBuffer& buffer, const DrawCallInfo& draw,
uint32_t render_target_slice_offset,
DrawRenderState& state) {
auto& ctx = buffer.GetRegisters();
if (ResolveColorTargets(buffer, render_target_slice_offset)) {
return false;
}
if (draw.IsIndexed()) {
LogDrawPhase(draw.Name(), "ResolveRenderColorTarget");
}
for (uint32_t slot = 0; slot < RENDER_COLOR_ATTACHMENTS_MAX; slot++) {
if (slot == 0 || (render_target_mask_slot(ctx.GetRenderTargetMask(), slot) != 0 &&
ctx.GetRenderTarget(slot).base.addr != 0)) {
ResolveRenderColorTarget(buffer, state.color_info[state.color_count],
render_target_slice_offset, slot);
if (state.color_info[state.color_count].image_id) {
state.color_count++;
}
}
}
if (draw.IsIndexed()) {
LogDrawPhase(draw.Name(), "ResolveRenderDepthTarget");
}
ResolveRenderDepthTarget(buffer, state.depth_info);
state.ps_active = DrawHasActivePixelShader(buffer);
if (state.color_count == 0 && !state.depth_info.image_id && !state.ps_active) {
LogFramebufferSkip(draw.Name(), state.color_info[0], state.depth_info, buffer,
draw.index_count, 0);
return false;
}
return true;
}
static void RefreshShaders(CommandBuffer& buffer, const DrawCallInfo& draw,
DrawRenderState& state) {
auto& ctx = buffer.GetRegisters();
@@ -940,8 +890,14 @@ static void RefreshShaders(CommandBuffer& buffer, const DrawCallInfo& draw,
state.ps_input_info = {};
std::array<Prospero::ColorComponentMapping, RENDER_COLOR_ATTACHMENTS_MAX>
target_export_mapping {};
for (uint32_t i = 0; i < state.color_count; i++) {
target_export_mapping[state.color_info[i].target_slot] = state.color_info[i].export_mapping;
for (uint32_t slot = 0; slot < RENDER_COLOR_ATTACHMENTS_MAX; slot++) {
const auto& rt = ctx.GetRenderTarget(slot);
if (rt.base.addr != 0 && render_target_mask_slot(ctx.GetRenderTargetMask(), slot) != 0) {
target_export_mapping[slot] =
TextureGetRenderTargetFormat(rt.info.format, rt.info.channel_type,
rt.info.channel_order)
.export_mapping;
}
}
auto& pipeline_cache = buffer.GetContext().GetPipelineCache();
if (draw.IsIndexed()) {
@@ -952,6 +908,45 @@ static void RefreshShaders(CommandBuffer& buffer, const DrawCallInfo& draw,
target_export_mapping, state.ps_active, state.vertex_info, state.ps_input_info);
}
bool RenderExecutor::PrepareDrawRenderState(CommandBuffer& buffer, const DrawCallInfo& draw,
uint32_t render_target_slice_offset,
DrawRenderState& state) {
state.ps_active = DrawHasActivePixelShader(buffer);
RefreshShaders(buffer, draw, state);
uint32_t mrt_mask = 0;
if (state.ps_active) {
for (const auto& output: state.ps_input_info.stage.program->info.outputs) {
if (output.kind == ShaderRecompiler::IR::StageOutputKind::Mrt) {
mrt_mask |= 1u << output.index;
}
}
}
if (draw.IsIndexed()) {
LogDrawPhase(draw.Name(), "ResolveRenderColorTarget");
}
for (uint32_t slot = 0; slot < RENDER_COLOR_ATTACHMENTS_MAX; slot++) {
if ((mrt_mask & (1u << slot)) != 0) {
ResolveRenderColorTarget(buffer, state.color_info[state.color_count],
render_target_slice_offset, slot);
if (state.color_info[state.color_count].image_id) {
state.color_count++;
}
}
}
if (draw.IsIndexed()) {
LogDrawPhase(draw.Name(), "ResolveRenderDepthTarget");
}
ResolveRenderDepthTarget(buffer, state.depth_info);
if (state.color_count == 0 && !state.depth_info.image_id && !state.ps_active) {
LogFramebufferSkip(draw.Name(), state.color_info[0], state.depth_info, buffer,
draw.index_count, 0);
return false;
}
return true;
}
static PreparedIndexBuffer PrepareIndexBuffer(CommandBuffer& buffer,
const DrawIndexBufferSource& source) {
PreparedIndexBuffer prepared;
@@ -1160,8 +1155,7 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff
CommitIndexBuffer(vk_buffer, index_binding);
}
SetGraphicsDynamicParams(buffer, vk_buffer, vertex_stages.back(), state.color_info,
state.color_count, state.depth_info);
SetGraphicsDynamicParams(buffer, vk_buffer, vertex_stages.back(), state.depth_info, rendering);
if (m_context.GetGraphics().attachment_feedback_loop_enabled) {
vk_buffer.setAttachmentFeedbackLoopEnableEXT(
rendering.depth_stencil_attachment.image_layout ==
@@ -1226,7 +1220,8 @@ void RenderExecutor::DrawIndex(uint64_t submit_id, CommandBuffer& buffer,
return;
}
if (ConsumeMetadataColorOperation(buffer) || DepthStencilCopy(buffer)) {
if (ConsumeMetadataColorOperation(buffer) || DepthStencilCopy(buffer) ||
ResolveColorTargets(buffer, args.render_target_slice_offset)) {
ResetBindings();
return;
}
@@ -1300,8 +1295,6 @@ void RenderExecutor::DrawIndex(uint64_t submit_id, CommandBuffer& buffer,
return;
}
RefreshShaders(buffer, draw, state);
LogDrawStateIfNeeded(buffer, draw, state, args.index_type_and_size,
args.index_addr);
@@ -1338,7 +1331,8 @@ void RenderExecutor::DrawAuto(uint64_t submit_id, CommandBuffer& buffer, const D
return;
}
if (ConsumeMetadataColorOperation(buffer) || DepthStencilCopy(buffer)) {
if (ConsumeMetadataColorOperation(buffer) || DepthStencilCopy(buffer) ||
ResolveColorTargets(buffer, args.render_target_slice_offset)) {
ResetBindings();
return;
}
@@ -1367,18 +1361,16 @@ void RenderExecutor::DrawAuto(uint64_t submit_id, CommandBuffer& buffer, const D
const DrawCallInfo draw {CommandBufferDebugOp::DrawIndexAuto,
args.vertex_count, args.instance_count, args.first_instance};
DrawRenderState state {};
if (!PrepareDrawRenderState(buffer, draw, args.render_target_slice_offset, state)) {
ResetBindings();
return;
}
vk::PrimitiveTopology topology = vk::PrimitiveTopology::ePointList;
if (!GetDrawTopology(ucfg, true, topology)) {
ResetBindings();
return;
}
RefreshShaders(buffer, draw, state);
DrawRenderState state {};
if (!PrepareDrawRenderState(buffer, draw, args.render_target_slice_offset, state)) {
ResetBindings();
return;
}
const bool rect_list = ucfg.GetPrimType() == Prospero::PrimitiveType::kRectList;
if (rect_list && state.vertex_info[0].buffers_num == 0 &&
+116 -2
View File
@@ -379,6 +379,11 @@ struct TextureCacheTestAccess {
};
struct RenderExecutorTestAccess {
static void DrawAuto(RenderExecutor &executor, CommandBuffer &command,
const DrawAutoArgs &args) {
executor.DrawAuto(0, command, args);
}
static bool TryConsumeComputeImageClear(RenderExecutor &executor,
const ShaderComputeInputInfo &input, CommandBuffer &command,
uint32_t x, uint32_t y, uint32_t z, uint32_t mode) {
@@ -13095,8 +13100,6 @@ public:
check_stencil("masked compare passes", false, 0xb8, 0xb9);
check_stencil("masked compare fails", false, 0xb0, 0xb0);
DestroyBuffer(&stencil_readback);
// SPI_PS_IN_CONTROL must select the native wave width through the actual
// program cache. In wave32 a low-word compare preserves scalar VCC_HI.
static const auto native_vertex = [] {
@@ -13248,6 +13251,117 @@ public:
"zero positions drew an extra triangle, or a valid position was culled");
}
// A NULL-export stencil pass must ignore smaller stale color targets,
// materialize the whole pending HTile clear, and still execute pixel discard.
static const auto null_pixels = [] {
std::array<std::vector<u32>, 2> result;
for (uint32_t discard = 0; discard < result.size(); discard++) {
auto &code = result[discard];
if (discard != 0) {
code.push_back(EncodeSop1(0x04, 126, InlineU32(0)));
}
code.push_back(EncodeExp0(0x09, 0, true, false, true));
code.push_back(EncodeExp1(0, 0, 0, 0));
AppendEnd(&code);
}
return result;
}();
registers.Reset();
registers.SetViewportTransformControl(0x300);
registers.SetViewportScaleOffset(0, extent / 2, extent / 2,
extent / 2, extent / 2, 1, 0);
registers.SetViewportZMax(0, 1);
registers.SetScreenScissor(0, 0, extent, extent);
registers.SetWindowScissor(0, 0, extent, extent, false);
registers.SetGenericScissor(0, 0, extent, extent, false);
registers.SetViewportScissor(0, 0, 0, extent, extent, false);
registers.SetRenderTargetMask(0xf00f);
registers.SetShaderMask(0xf00f);
registers.SetPsInControl(0x8000);
registers.SetDepthShaderControl({.shader_kill_enable = true});
for (const auto slot : {0u, 3u}) {
registers.SetColorBase(slot,
{.addr = depth_address + (slot == 0 ? 0x38000 : 0x10000)});
registers.SetColorInfo(slot,
{.format = Prospero::ChannelLayout::k32_32_32_32,
.channel_type = Prospero::ChannelType::kFloat,
.channel_order = Prospero::ChannelOrder::kStandard});
const auto side = slot == 0 ? extent / 4 : extent;
registers.SetColorAttrib2(slot, {.height = side - 1, .width = side - 1});
registers.SetColorAttrib3(slot,
{.tile_mode = Prospero::TileMode::kLinear, .dimension = 1});
registers.SetTargetOutputMode(slot, 4);
}
stencil_target.z_info.htile_acceleration = true;
stencil_target.htile_data_base_addr = depth_address + 0x30000;
registers.SetDepthRenderTarget(stencil_target);
registers.SetDepthClearValue(1);
stencil_control = {};
stencil_control.stencil_enable = true;
stencil_control.stencilfunc = static_cast<uint8_t>(vk::CompareOp::eAlways);
registers.SetDepthControl(stencil_control);
registers.SetStencilControl({3, 3, 3, 3, 3, 3});
registers.SetStencilMask({0x80, 0, 0x80, 1, 0x80, 0, 0x80, 1});
user_config.SetPrimitiveType(Prospero::PrimitiveType::kTriList);
shaders.SetEsShaderBase(vertex_address);
for (uint32_t discard = 0; discard < null_pixels.size(); discard++) {
const auto &code = null_pixels[discard];
const auto address = reinterpret_cast<uint64_t>(code.data());
ShaderMapUserData(address,
{.type = Prospero::ShaderBinaryType::kPs,
.code_size_bytes = static_cast<uint32_t>(code.size() * sizeof(u32))});
shaders.SetPsShaderBase(address);
RenderExecutorTestAccess::ResolveRenderDepthTarget(executor, scheduler.Current(), depth);
(void)cache.FindDepthTarget(depth.image_id, depth.desc);
vk::ClearValue zero{};
TextureCacheTestAccess::ClearImage(cache, scheduler.Current(), depth.image_id,
{vk::ImageAspectFlagBits::eDepth | vk::ImageAspectFlagBits::eStencil,
0, 1, 0, 1}, zero);
Require(name, "pending stencil-pass HTile clear",
cache.ClearMeta(stencil_target.htile_data_base_addr),
"the stencil pass did not retain its HTile metadata");
RenderExecutorTestAccess::DrawAuto(
executor, scheduler.Current(), {.vertex_count = 3, .instance_count = 1});
const auto depth_pixels = ReadCachedTexel(name, context, depth.image_id,
{}, {extent, extent, 1});
Require(name, "NULL-export full depth clear",
std::ranges::all_of(depth_pixels, [](u32 v) { return v == 0x3f800000u; }),
"an unexported color target clipped the pending depth clear");
const auto stencil = read_stencil();
const auto expected = discard == 0 ? 0x80808080u : 0u;
Require(name, "NULL-export stencil coverage and discard",
std::ranges::all_of(stencil, [=](u32 v) { return v == expected; }),
"a stale color target clipped stencil coverage or the NULL-export shader was skipped");
}
// With MRT0 still bound, an MRT3-only export must retain location3 in
// rendering attachments, pipeline formats, blend masks and dynamic write enables.
static const auto sparse_pixel = [] {
auto code = native_pixel;
*std::ranges::find(code, EncodeExp0(0x00, 0xf)) = EncodeExp0(0x03, 0xf);
return code;
}();
const auto sparse_address = reinterpret_cast<uint64_t>(sparse_pixel.data());
ShaderMapUserData(sparse_address,
{.type = Prospero::ShaderBinaryType::kPs,
.code_size_bytes = static_cast<uint32_t>(sparse_pixel.size() * sizeof(u32))});
shaders.SetPsShaderBase(sparse_address);
registers.SetDepthControl({});
registers.SetDepthShaderControl({});
RenderExecutorTestAccess::DrawAuto(
executor, scheduler.Current(), {.vertex_count = 3, .instance_count = 1});
RenderColorInfo sparse_color{};
RenderExecutorTestAccess::ResolveRenderColorTarget(
executor, scheduler.Current(), sparse_color, 3);
const auto sparse_pixels = ReadCachedTexel(name, context, sparse_color.image_id,
{}, {extent, extent, 1});
for (size_t component = 0; component < sparse_pixels.size(); component++) {
const auto expected = component % 4 == 3 ? 0x3f800000u : 0x3e800000u;
Require(name, "sparse MRT3 output", sparse_pixels[component] == expected,
"MRT3 was compacted to a different slot or clipped by unused MRT0");
}
DestroyBuffer(&stencil_readback);
vertex_shader = owned_vertex_shader;
pixel_shader = owned_pixel_shader;
}