diff --git a/src/graphics/host_gpu/renderer/pipeline/pipelineCache.cpp b/src/graphics/host_gpu/renderer/pipeline/pipelineCache.cpp index 44d8ba745..a9d2eca06 100644 --- a/src/graphics/host_gpu/renderer/pipeline/pipelineCache.cpp +++ b/src/graphics/host_gpu/renderer/pipeline/pipelineCache.cpp @@ -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(depth.image_id); diff --git a/src/graphics/host_gpu/renderer/renderDraw.cpp b/src/graphics/host_gpu/renderer/renderDraw.cpp index 6b3467e2a..60abb212a 100644 --- a/src/graphics/host_gpu/renderer/renderDraw.cpp +++ b/src/graphics/host_gpu/renderer/renderDraw.cpp @@ -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::max(); state.height = std::numeric_limits::max(); state.num_layers = std::numeric_limits::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 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 && diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index 5f6fa7692..c758a1a45 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -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, 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(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(code.data()); + ShaderMapUserData(address, + {.type = Prospero::ShaderBinaryType::kPs, + .code_size_bytes = static_cast(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(sparse_pixel.data()); + ShaderMapUserData(sparse_address, + {.type = Prospero::ShaderBinaryType::kPs, + .code_size_bytes = static_cast(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; }