diff --git a/src/xenia/gpu/spirv_compatibility.h b/src/xenia/gpu/spirv_compatibility.h index fe8127509..3c35413eb 100644 --- a/src/xenia/gpu/spirv_compatibility.h +++ b/src/xenia/gpu/spirv_compatibility.h @@ -428,6 +428,8 @@ namespace spv { #define ExecutionModeVertexOrderCw ExecutionMode::VertexOrderCw #define ExecutionModeVertexOrderCcw ExecutionMode::VertexOrderCcw #define ExecutionModeDepthReplacing ExecutionMode::DepthReplacing +#define ExecutionModeDepthGreater ExecutionMode::DepthGreater +#define ExecutionModeDepthLess ExecutionMode::DepthLess #define ExecutionModeStencilRefReplacingEXT \ ExecutionMode::StencilRefReplacingEXT #define ExecutionModeLocalSize ExecutionMode::LocalSize @@ -438,6 +440,7 @@ namespace spv { #define DecorationBufferBlock Decoration::BufferBlock #define DecorationCoherent Decoration::Coherent #define DecorationCentroid Decoration::Centroid +#define DecorationSample Decoration::Sample #define DecorationSpecId Decoration::SpecId #define DecorationNonReadable Decoration::NonReadable diff --git a/src/xenia/gpu/spirv_shader_translator.cc b/src/xenia/gpu/spirv_shader_translator.cc index 54ca48b99..f36e73688 100644 --- a/src/xenia/gpu/spirv_shader_translator.cc +++ b/src/xenia/gpu/spirv_shader_translator.cc @@ -92,14 +92,18 @@ uint64_t SpirvShaderTranslator::GetDefaultPixelShaderModification( return shader_modification.value; } -std::vector SpirvShaderTranslator::CreateDepthOnlyFragmentShader() { +std::vector SpirvShaderTranslator::CreateDepthOnlyFragmentShader( + Modification::DepthStencilMode depth_stencil_mode) { is_depth_only_fragment_shader_ = true; // TODO(Triang3l): Handle in a nicer way (is_depth_only_fragment_shader_ is a // leftover from when a Shader object wasn't used during translation). Shader shader(xenos::ShaderType::kPixel, 0, nullptr, 0); StringBuffer instruction_disassembly_buffer; shader.AnalyzeUcode(instruction_disassembly_buffer); - Shader::Translation& translation = *shader.GetOrCreateTranslation(0); + Modification modification(0); + modification.pixel.depth_stencil_mode = depth_stencil_mode; + Shader::Translation& translation = + *shader.GetOrCreateTranslation(modification.value); TranslateAnalyzedShader(translation); is_depth_only_fragment_shader_ = false; return translation.translated_binary(); @@ -734,9 +738,19 @@ std::vector SpirvShaderTranslator::CompleteTranslation() { builder_->addExecutionMode(function_main_, spv::ExecutionModeEarlyFragmentTests); } - if (current_shader().writes_depth() && !edram_fragment_shader_interlock_) { + // FSI handles depth manually. + if (!edram_fragment_shader_interlock_ && + (current_shader().writes_depth() || DSV_IsWritingFloat24Depth())) { builder_->addExecutionMode(function_main_, spv::ExecutionModeDepthReplacing); + // Truncating float24 conversion of the rasterizer's own depth rounds + // towards zero, so the output is always <= the original - announce that + // to keep coarse early-Z culling possible. + if (!current_shader().writes_depth() && + GetSpirvShaderModification().pixel.depth_stencil_mode == + Modification::DepthStencilMode::kFloat24Truncating) { + builder_->addExecutionMode(function_main_, spv::ExecutionModeDepthLess); + } } if (edram_fragment_shader_interlock_) { // Accessing per-sample values, so interlocking just when there's common @@ -2246,14 +2260,15 @@ void SpirvShaderTranslator::StartFragmentShaderBeforeMain() { } // Fragment coordinates. - // TODO(Triang3l): More conditions - depth writing in the fragment shader - // (per-sample if supported). // FSI: Always needed for EDRAM offset calculation and depth derivatives. // param_gen: Needed for PsParamGen calculation. // FBO alpha-to-coverage: Needed for dithering pattern, but only when // alpha-to-coverage can actually run (no early fragment tests). + // FBO float24 in-PS conversion of the rasterizer's depth: reads + // gl_FragCoord.z + // - and must do so per-sample for MSAA antialiasing of intersections. bool need_frag_coord = - edram_fragment_shader_interlock_ || param_gen_needed || + edram_fragment_shader_interlock_ || param_gen_needed || IsSampleRate() || (!edram_fragment_shader_interlock_ && !is_depth_only_fragment_shader_ && current_shader().writes_color_target(0) && !IsExecutionModeEarlyFragmentTests()); @@ -2262,6 +2277,13 @@ void SpirvShaderTranslator::StartFragmentShaderBeforeMain() { spv::NoPrecision, spv::StorageClassInput, type_float4_, "gl_FragCoord"); builder_->addDecoration(input_fragment_coordinates_, spv::DecorationBuiltIn, static_cast(spv::BuiltIn::FragCoord)); + if (IsSampleRate()) { + // Per the Vulkan spec, a Sample-decorated fragment input forces + // per-sample shader invocation - no explicit sampleShadingEnable needed. + builder_->addCapability(spv::CapabilitySampleRateShading); + builder_->addDecoration(input_fragment_coordinates_, + spv::DecorationSample); + } main_interface_.push_back(input_fragment_coordinates_); } @@ -2328,10 +2350,12 @@ void SpirvShaderTranslator::StartFragmentShaderBeforeMain() { } // Fragment depth output (gl_FragDepth) for the FBO path. - // Created when the guest pixel shader writes oDepth. + // Created when the guest pixel shader writes oDepth, or when the host depth + // buffer is float24 and the rasterizer's own depth needs in-PS conversion + // (including the synthetic depth-only shader used for no-PS guest draws). // FSI manages its own depth and does not need an Output. - if (!edram_fragment_shader_interlock_ && !is_depth_only_fragment_shader_ && - current_shader().writes_depth()) { + if (!edram_fragment_shader_interlock_ && + (current_shader().writes_depth() || DSV_IsWritingFloat24Depth())) { output_fragment_depth_ = builder_->createVariable( spv::NoPrecision, spv::StorageClassOutput, type_float_, "gl_FragDepth"); builder_->addDecoration(output_fragment_depth_, spv::DecorationBuiltIn, diff --git a/src/xenia/gpu/spirv_shader_translator.h b/src/xenia/gpu/spirv_shader_translator.h index 13a5b25fe..15fe83a99 100644 --- a/src/xenia/gpu/spirv_shader_translator.h +++ b/src/xenia/gpu/spirv_shader_translator.h @@ -34,15 +34,24 @@ class SpirvShaderTranslator : public ShaderTranslator { // TODO(Triang3l): Change to 0xYYYYMMDD once it's out of the rapid // prototyping stage (easier to do small granular updates with an // incremental counter). - static constexpr uint32_t kVersion = 12; + static constexpr uint32_t kVersion = 13; enum class DepthStencilMode : uint32_t { kNoModifiers, // Early fragment tests - enable if alpha test and alpha to coverage are // disabled; ignored if anything in the shader blocks early Z writing. kEarlyHint, - // TODO(Triang3l): Unorm24 (rounding) and float24 (truncating and - // rounding) output modes. + // Converting the depth to the closest 32-bit float representable exactly + // as a 20e4 float, truncating towards zero, so SV_DepthLessEqual-style + // conservative depth output (ExecutionModeDepthLess) can still allow + // coarse early Z culling. MSAA depth must be per-sample, so the shader + // runs at sample frequency. + // Fixed-function viewport depth bounds must be snapped to float24 too. + kFloat24Truncating, + // Similar to kFloat24Truncating, but rounding to the nearest even, so + // plain ExecutionModeDepthReplacing is used rather than DepthLess. + kFloat24Rounding, + // TODO(Triang3l): Unorm24 (rounding) output mode. }; struct { @@ -439,8 +448,13 @@ class SpirvShaderTranslator : public ShaderTranslator { } // Creates a special fragment shader without color outputs - this resets the - // state of the translator. - std::vector CreateDepthOnlyFragmentShader(); + // state of the translator. When depth_stencil_mode is a float24 mode, the + // shader reads gl_FragCoord.z, converts to float24, and writes the result to + // gl_FragDepth - matching the substitute pixel shader the DXBC backend uses + // when a guest draw has no pixel shader. + std::vector CreateDepthOnlyFragmentShader( + Modification::DepthStencilMode depth_stencil_mode = + Modification::DepthStencilMode::kNoModifiers); // Common functions useful not only for the translator, but also for EDRAM // emulation via conventional render targets. @@ -567,6 +581,26 @@ class SpirvShaderTranslator : public ShaderTranslator { current_shader().implicit_early_z_write_allowed(); } + // Whether the current non-FSI pixel shader should convert the depth to 20e4. + bool DSV_IsWritingFloat24Depth() const { + if (edram_fragment_shader_interlock_) { + return false; + } + Modification::DepthStencilMode depth_stencil_mode = + GetSpirvShaderModification().pixel.depth_stencil_mode; + return depth_stencil_mode == + Modification::DepthStencilMode::kFloat24Truncating || + depth_stencil_mode == + Modification::DepthStencilMode::kFloat24Rounding; + } + // Whether the shader runs at sample frequency - when converting depth to + // float24 from the rasterizer's own depth (not guest oDepth), each sample + // needs its own depth value for intersections to be antialiased. + bool IsSampleRate() const { + return is_pixel_shader() && DSV_IsWritingFloat24Depth() && + !current_shader().writes_depth(); + } + uint32_t GetModificationInterpolatorMask() const { Modification modification = GetSpirvShaderModification(); return is_vertex_shader() ? modification.vertex.interpolator_mask diff --git a/src/xenia/gpu/spirv_shader_translator_rb.cc b/src/xenia/gpu/spirv_shader_translator_rb.cc index 64d1386e3..2301754b0 100644 --- a/src/xenia/gpu/spirv_shader_translator_rb.cc +++ b/src/xenia/gpu/spirv_shader_translator_rb.cc @@ -1517,28 +1517,107 @@ void SpirvShaderTranslator::CompleteFragmentShader_DSV_DepthTo24Bit() { output_fragment_depth_ == spv::NoResult) { return; } - if (!current_shader().writes_depth()) { + bool shader_writes_depth = current_shader().writes_depth(); + bool is_float24 = DSV_IsWritingFloat24Depth(); + assert_true(shader_writes_depth || is_float24); + + // Source the depth: staged guest oDepth (already [0, 1]), or the rasterizer's + // own depth from gl_FragCoord.z remapped from host 0...0.5 back to 0...1. + spv::Id depth_value; + if (shader_writes_depth) { + depth_value = + builder_->createLoad(output_or_var_fragment_depth_, spv::NoPrecision); + } else { + assert_true(input_fragment_coordinates_ != spv::NoResult); + id_vector_temp_.clear(); + id_vector_temp_.push_back(builder_->makeIntConstant(2)); + spv::Id raster_z = + builder_->createLoad(builder_->createAccessChain( + spv::StorageClassInput, + input_fragment_coordinates_, id_vector_temp_), + spv::NoPrecision); + depth_value = builder_->createBinOp(spv::OpFMul, type_float_, raster_z, + builder_->makeFloatConstant(2.0f)); + depth_value = builder_->createTriBuiltinCall( + type_float_, ext_inst_glsl_std_450_, GLSLstd450NClamp, depth_value, + const_float_0_, const_float_1_); + } + + if (!is_float24) { + // Legacy path: shader writes oDepth, but the modification is not in float24 + // mode (the host buffer may still be float24 if depth_float24_convert_in_ + // pixel_shader is off - check dynamically via the system flag). + spv::Id depth_float24_flag = builder_->createBinOp( + spv::OpINotEqual, type_bool_, + builder_->createBinOp( + spv::OpBitwiseAnd, type_uint_, main_system_constant_flags_, + builder_->makeUintConstant(kSysFlag_DepthFloat24)), + const_uint_0_); + spv::Id depth_scaled = + builder_->createBinOp(spv::OpFMul, type_float_, depth_value, + builder_->makeFloatConstant(0.5f)); + spv::Id depth_remapped = + builder_->createTriOp(spv::OpSelect, type_float_, depth_float24_flag, + depth_scaled, depth_value); + builder_->createStore(depth_remapped, output_fragment_depth_); return; } - assert_true(output_or_var_fragment_depth_ != spv::NoResult); - // The shader writes depth explicitly, for float24, need to scale it from - // guest 0...1 to host 0...0.5 to support reinterpretation round trips as - // viewport scaling doesn't apply to oDepth. - spv::Id depth_value = - builder_->createLoad(output_or_var_fragment_depth_, spv::NoPrecision); - spv::Id depth_float24_flag = builder_->createBinOp( - spv::OpINotEqual, type_bool_, - builder_->createBinOp(spv::OpBitwiseAnd, type_uint_, - main_system_constant_flags_, - builder_->makeUintConstant(kSysFlag_DepthFloat24)), - const_uint_0_); - spv::Id depth_scaled = builder_->createBinOp( - spv::OpFMul, type_float_, depth_value, builder_->makeFloatConstant(0.5f)); - spv::Id depth_remapped = - builder_->createTriOp(spv::OpSelect, type_float_, depth_float24_flag, - depth_scaled, depth_value); - // Write the depth from the temporary to the system depth output. - builder_->createStore(depth_remapped, output_fragment_depth_); + // Float24 mode: statically known float24 host buffer; perform the conversion. + Modification::DepthStencilMode mode = + GetSpirvShaderModification().pixel.depth_stencil_mode; + if (mode == Modification::DepthStencilMode::kFloat24Truncating) { + // Mantissa bit-truncation, then guest 0...1 -> host 0...0.5. + spv::Id depth_uint = + builder_->createUnaryOp(spv::OpBitcast, type_uint_, depth_value); + // Representable as float24 (exponent >= -34): bit pattern >= 0x2E800000. + spv::Id representable = + builder_->createBinOp(spv::OpUGreaterThanEqual, type_bool_, depth_uint, + builder_->makeUintConstant(0x2E800000)); + SpirvBuilder::IfBuilder representable_if( + representable, spv::SelectionControlDontFlattenMask, *builder_); + { + // Biased exponent: 113+ at exp -14+; 93 at exp -34. + spv::Id exponent = builder_->createTriOp( + spv::OpBitFieldUExtract, type_uint_, depth_uint, + builder_->makeUintConstant(23), builder_->makeUintConstant(8)); + // trunc_bits = max(116 - exponent, 3), in signed - drops 3 mantissa bits + // at exp -14+ and 23 at exp -34. Must be signed: exponent > 116 (i.e. + // values larger than ~2^-11) makes 116 - exponent negative; an unsigned + // underflow would feed OpBitFieldInsert a Count > 32 (undefined). + spv::Id trunc_bits_signed = builder_->createBinOp( + spv::OpISub, type_int_, builder_->makeIntConstant(116), + builder_->createUnaryOp(spv::OpBitcast, type_int_, exponent)); + trunc_bits_signed = builder_->createBinBuiltinCall( + type_int_, ext_inst_glsl_std_450_, GLSLstd450SMax, trunc_bits_signed, + builder_->makeIntConstant(3)); + spv::Id trunc_bits = builder_->createUnaryOp(spv::OpBitcast, type_uint_, + trunc_bits_signed); + spv::Id truncated_uint = + builder_->createQuadOp(spv::OpBitFieldInsert, type_uint_, depth_uint, + builder_->makeUintConstant(0), + builder_->makeUintConstant(0), trunc_bits); + spv::Id truncated_f32 = + builder_->createUnaryOp(spv::OpBitcast, type_float_, truncated_uint); + spv::Id remapped = + builder_->createBinOp(spv::OpFMul, type_float_, truncated_f32, + builder_->makeFloatConstant(0.5f)); + builder_->createStore(remapped, output_fragment_depth_); + } + representable_if.makeBeginElse(); + { + // Not representable - zero. + builder_->createStore(const_float_0_, output_fragment_depth_); + } + representable_if.makeEndIf(); + } else { + // kFloat24Rounding: round-trip through 20e4 (round to nearest even), with + // the 0...0.5 host remap baked in via remap_to_0_to_0_5 on Depth20e4To32. + spv::Id f24_uint = PreClampedDepthTo20e4(*builder_, depth_value, true, + false, ext_inst_glsl_std_450_); + spv::Id depth_f32 = Depth20e4To32(*builder_, f24_uint, 0, true, false, + ext_inst_glsl_std_450_); + builder_->createStore(depth_f32, output_fragment_depth_); + } } spv::Id SpirvShaderTranslator::LoadMsaaSamplesFromFlags() { diff --git a/src/xenia/gpu/vulkan/vulkan_command_processor.cc b/src/xenia/gpu/vulkan/vulkan_command_processor.cc index 33ec54279..11cc85330 100644 --- a/src/xenia/gpu/vulkan/vulkan_command_processor.cc +++ b/src/xenia/gpu/vulkan/vulkan_command_processor.cc @@ -2441,6 +2441,8 @@ bool VulkanCommandProcessor::IssueDraw(xenos::PrimitiveType prim_type, SpirvShaderTranslator::Modification pixel_shader_modification; VulkanShader::VulkanTranslation* vertex_shader_translation; VulkanShader::VulkanTranslation* pixel_shader_translation; + uint32_t normalized_color_mask; + reg::RB_DEPTHCONTROL normalized_depth_control; // Two iterations because a submission (even the current one - in which case // it needs to be ended, and a new one must be started) may need to be awaited @@ -2475,6 +2477,14 @@ bool VulkanCommandProcessor::IssueDraw(xenos::PrimitiveType prim_type, return false; } + normalized_depth_control = draw_util::GetNormalizedDepthControl(regs); + + // Compute which color render targets are used. + normalized_color_mask = + pixel_shader ? draw_util::GetNormalizedColorMask( + regs, pixel_shader->writes_color_targets()) + : 0; + // Shader modifications. vertex_shader_modification = pipeline_cache_->GetCurrentVertexShaderModification( @@ -2482,7 +2492,8 @@ bool VulkanCommandProcessor::IssueDraw(xenos::PrimitiveType prim_type, interpolator_mask, ps_param_gen_pos != UINT32_MAX); pixel_shader_modification = pixel_shader ? pipeline_cache_->GetCurrentPixelShaderModification( - *pixel_shader, interpolator_mask, ps_param_gen_pos) + *pixel_shader, interpolator_mask, ps_param_gen_pos, + normalized_depth_control, normalized_color_mask) : SpirvShaderTranslator::Modification(0); // Translate the shaders now to obtain the sampler bindings. @@ -2571,12 +2582,6 @@ bool VulkanCommandProcessor::IssueDraw(xenos::PrimitiveType prim_type, } // Set up the render targets - this may perform dispatches and draws. - reg::RB_DEPTHCONTROL normalized_depth_control = - draw_util::GetNormalizedDepthControl(regs); - uint32_t normalized_color_mask = - pixel_shader ? draw_util::GetNormalizedColorMask( - regs, pixel_shader->writes_color_targets()) - : 0; if (!render_target_cache_->Update(is_rasterization_done, normalized_depth_control, normalized_color_mask, *vertex_shader)) { @@ -2699,17 +2704,20 @@ bool VulkanCommandProcessor::IssueDraw(xenos::PrimitiveType prim_type, // into system constants. UpdateZPDScale(draw_resolution_scale_x * draw_resolution_scale_y); draw_util::GetViewportInfoArgs gviargs{}; - gviargs.Setup(draw_resolution_scale_x, draw_resolution_scale_y, - draw_resolution_scale_x > 1 - ? texture_cache_->draw_resolution_scale_x_divisor() - : divisors::MagicDiv(1), - draw_resolution_scale_y > 1 - ? texture_cache_->draw_resolution_scale_y_divisor() - : divisors::MagicDiv(1), - false, device_properties.maxViewportDimensions[0], - device_properties.maxViewportDimensions[1], true, - normalized_depth_control, false, host_render_targets_used, - pixel_shader && pixel_shader->writes_depth()); + gviargs.Setup( + draw_resolution_scale_x, draw_resolution_scale_y, + draw_resolution_scale_x > 1 + ? texture_cache_->draw_resolution_scale_x_divisor() + : divisors::MagicDiv(1), + draw_resolution_scale_y > 1 + ? texture_cache_->draw_resolution_scale_y_divisor() + : divisors::MagicDiv(1), + false, device_properties.maxViewportDimensions[0], + device_properties.maxViewportDimensions[1], true, + normalized_depth_control, + host_render_targets_used && + render_target_cache_->depth_float24_convert_in_pixel_shader(), + host_render_targets_used, pixel_shader && pixel_shader->writes_depth()); gviargs.SetupRegisterValues(regs); draw_util::GetHostViewportInfo(&gviargs, viewport_info); diff --git a/src/xenia/gpu/vulkan/vulkan_pipeline_cache.cc b/src/xenia/gpu/vulkan/vulkan_pipeline_cache.cc index 791b4a534..f3c42ca9f 100644 --- a/src/xenia/gpu/vulkan/vulkan_pipeline_cache.cc +++ b/src/xenia/gpu/vulkan/vulkan_pipeline_cache.cc @@ -108,6 +108,32 @@ bool VulkanPipelineCache::Initialize() { } } + // Substitute fragment shaders for guest depth-only draws when in-PS float24 + // conversion is active - keep the depth buffer's encoding consistent with + // PS-converted draws (matches the DXBC backend's + // float24_{truncate,round}_ps). + if (render_target_cache_.depth_float24_convert_in_pixel_shader()) { + using DepthStencilMode = + SpirvShaderTranslator::Modification::DepthStencilMode; + auto build = [&](DepthStencilMode mode, VkShaderModule& out) -> bool { + std::vector code = + shader_translator_->CreateDepthOnlyFragmentShader(mode); + out = ui::vulkan::util::CreateShaderModule( + vulkan_device, reinterpret_cast(code.data()), + code.size()); + return out != VK_NULL_HANDLE; + }; + if (!build(DepthStencilMode::kFloat24Truncating, + float24_truncate_fragment_shader_) || + !build(DepthStencilMode::kFloat24Rounding, + float24_round_fragment_shader_)) { + XELOGE( + "VulkanPipelineCache: Failed to create the float24 substitute " + "depth-only fragment shaders"); + return false; + } + } + // Create tessellation shaders if tessellation is supported. if (vulkan_device->properties().tessellationShader) { // Vertex shaders for tessellation. @@ -287,6 +313,10 @@ void VulkanPipelineCache::Shutdown() { // Destroy all internal shaders. ui::vulkan::util::DestroyAndNullHandle(dfn.vkDestroyShaderModule, device, depth_only_fragment_shader_); + ui::vulkan::util::DestroyAndNullHandle(dfn.vkDestroyShaderModule, device, + float24_truncate_fragment_shader_); + ui::vulkan::util::DestroyAndNullHandle(dfn.vkDestroyShaderModule, device, + float24_round_fragment_shader_); ui::vulkan::util::DestroyAndNullHandle(dfn.vkDestroyShaderModule, device, placeholder_pixel_shader_); // Destroy tessellation shaders. @@ -395,8 +425,9 @@ VulkanPipelineCache::GetCurrentVertexShaderModification( SpirvShaderTranslator::Modification VulkanPipelineCache::GetCurrentPixelShaderModification( - const Shader& shader, uint32_t interpolator_mask, - uint32_t param_gen_pos) const { + const Shader& shader, uint32_t interpolator_mask, uint32_t param_gen_pos, + reg::RB_DEPTHCONTROL normalized_depth_control, + uint32_t normalized_color_mask) const { assert_true(shader.type() == xenos::ShaderType::kPixel); assert_true(shader.is_ucode_analyzed()); const auto& regs = register_file_; @@ -434,10 +465,18 @@ VulkanPipelineCache::GetCurrentPixelShaderModification( using DepthStencilMode = SpirvShaderTranslator::Modification::DepthStencilMode; - if (shader.implicit_early_z_write_allowed() && - (!shader.writes_color_target(0) || - !draw_util::DoesCoverageDependOnAlpha( - regs.Get()))) { + if (render_target_cache_.depth_float24_convert_in_pixel_shader() && + normalized_depth_control.z_enable && + regs.Get().depth_format == + xenos::DepthRenderTargetFormat::kD24FS8) { + modification.pixel.depth_stencil_mode = + render_target_cache_.depth_float24_round() + ? DepthStencilMode::kFloat24Rounding + : DepthStencilMode::kFloat24Truncating; + } else if (shader.implicit_early_z_write_allowed() && + (!shader.writes_color_target(0) || + !draw_util::DoesCoverageDependOnAlpha( + regs.Get()))) { modification.pixel.depth_stencil_mode = DepthStencilMode::kEarlyHint; } else { modification.pixel.depth_stencil_mode = DepthStencilMode::kNoModifiers; @@ -2630,6 +2669,19 @@ bool VulkanPipelineCache::EnsurePipelineCreated( } else { if (edram_fragment_shader_interlock) { shader_stage_fragment.module = depth_only_fragment_shader_; + } else if (render_target_cache_.depth_float24_convert_in_pixel_shader() && + (description.depth_write_enable || + description.depth_compare_op != + xenos::CompareFunction::kAlways) && + (description.render_pass_key.depth_and_color_used & 0b1) && + description.render_pass_key.depth_format == + xenos::DepthRenderTargetFormat::kD24FS8) { + // No guest pixel shader, but depth matters and the host buffer is + // float24 - bind a substitute that converts gl_FragCoord.z so the + // depth buffer encoding stays consistent with PS-converted draws. + shader_stage_fragment.module = render_target_cache_.depth_float24_round() + ? float24_round_fragment_shader_ + : float24_truncate_fragment_shader_; } } if (shader_stage_fragment.module == VK_NULL_HANDLE) { diff --git a/src/xenia/gpu/vulkan/vulkan_pipeline_cache.h b/src/xenia/gpu/vulkan/vulkan_pipeline_cache.h index f251beb80..114cc3f6b 100644 --- a/src/xenia/gpu/vulkan/vulkan_pipeline_cache.h +++ b/src/xenia/gpu/vulkan/vulkan_pipeline_cache.h @@ -132,8 +132,9 @@ class VulkanPipelineCache { Shader::HostVertexShaderType host_vertex_shader_type, uint32_t interpolator_mask, bool ps_param_gen_used) const; SpirvShaderTranslator::Modification GetCurrentPixelShaderModification( - const Shader& shader, uint32_t interpolator_mask, - uint32_t param_gen_pos) const; + const Shader& shader, uint32_t interpolator_mask, uint32_t param_gen_pos, + reg::RB_DEPTHCONTROL normalized_depth_control, + uint32_t normalized_color_mask) const; bool EnsureShadersTranslated(VulkanShader::VulkanTranslation* vertex_shader, VulkanShader::VulkanTranslation* pixel_shader); @@ -433,6 +434,13 @@ class VulkanPipelineCache { // shader interlock when no Xenos pixel shader provided. VkShaderModule depth_only_fragment_shader_ = VK_NULL_HANDLE; + // Substitute depth-only pixel shaders that perform float24 conversion of the + // rasterizer's depth, bound for guest depth-only draws when in-PS float24 + // conversion is active and the depth buffer is D24FS8. Mirrors the DXBC + // backend's float24_{truncate,round}_ps. + VkShaderModule float24_truncate_fragment_shader_ = VK_NULL_HANDLE; + VkShaderModule float24_round_fragment_shader_ = VK_NULL_HANDLE; + // Placeholder pixel shader for pipeline hot-swap to reduce stutter. // Outputs transparent black while the real shader compiles in background. VkShaderModule placeholder_pixel_shader_ = VK_NULL_HANDLE; diff --git a/src/xenia/gpu/vulkan/vulkan_render_target_cache.cc b/src/xenia/gpu/vulkan/vulkan_render_target_cache.cc index cfa940497..24b778c8b 100644 --- a/src/xenia/gpu/vulkan/vulkan_render_target_cache.cc +++ b/src/xenia/gpu/vulkan/vulkan_render_target_cache.cc @@ -570,6 +570,12 @@ bool VulkanRenderTargetCache::Initialize(uint32_t shared_memory_binding_count) { kGammaUnorm16Features) == kGammaUnorm16Features; depth_float24_round_ = cvars::depth_float24_round; + // In-PS conversion requires per-sample shading under MSAA for intersections + // to antialias; without sampleRateShading, fall back to transfer-time + // conversion so the host/PS encoding stays consistent across all draws. + depth_float24_convert_in_pixel_shader_ = + cvars::depth_float24_convert_in_pixel_shader && + device_properties.sampleRateShading; // Host depth storing pipeline layout. VkDescriptorSetLayout host_depth_store_descriptor_set_layouts[] = { @@ -773,8 +779,11 @@ bool VulkanRenderTargetCache::Initialize(uint32_t shared_memory_binding_count) { // Piecewise linear gamma is 8-bit with programmable blending. gamma_render_target_as_unorm16_ = false; - // Always true float24 depth rounded to the nearest even. + // Always true float24 depth rounded to the nearest even, converted in the + // shader (FSI ignores depth_float24_convert_in_pixel_shader, but set it for + // parity with the host render target path). depth_float24_round_ = true; + depth_float24_convert_in_pixel_shader_ = true; // The pipeline layout and the pipelines for clearing the EDRAM buffer in // resolves. @@ -2105,12 +2114,14 @@ RenderTargetCache::RenderTarget* VulkanRenderTargetCache::CreateRenderTarget( bool VulkanRenderTargetCache::IsHostDepthEncodingDifferent( xenos::DepthRenderTargetFormat format) const { - // TODO(Triang3l): Conversion directly in shaders. switch (format) { case xenos::DepthRenderTargetFormat::kD24S8: return !depth_unorm24_vulkan_format_supported(); case xenos::DepthRenderTargetFormat::kD24FS8: - return true; + // When converting in the pixel shader, the host float32 depth already + // holds float24-grid values, so it's the canonical encoding and the + // separate host depth tracking isn't needed. + return !depth_float24_convert_in_pixel_shader(); } return false; } @@ -3605,8 +3616,10 @@ VkShaderModule VulkanRenderTargetCache::GetTransferShader( } break; case xenos::DepthRenderTargetFormat::kD24FS8: { depth24 = SpirvShaderTranslator::PreClampedDepthTo20e4( - builder, source_depth_float[i], depth_float24_round(), true, - ext_inst_glsl_std_450); + builder, source_depth_float[i], + !depth_float24_convert_in_pixel_shader() && + depth_float24_round(), + true, ext_inst_glsl_std_450); } break; } // Merge depth and stencil. @@ -3911,8 +3924,10 @@ VkShaderModule VulkanRenderTargetCache::GetTransferShader( } break; case xenos::DepthRenderTargetFormat::kD24FS8: { packed = SpirvShaderTranslator::PreClampedDepthTo20e4( - builder, source_depth_float[0], depth_float24_round(), true, - ext_inst_glsl_std_450); + builder, source_depth_float[0], + !depth_float24_convert_in_pixel_shader() && + depth_float24_round(), + true, ext_inst_glsl_std_450); } break; } if (mode.output == TransferOutput::kDepth) { @@ -4391,8 +4406,10 @@ VkShaderModule VulkanRenderTargetCache::GetTransferShader( } break; case xenos::DepthRenderTargetFormat::kD24FS8: { host_depth24 = SpirvShaderTranslator::PreClampedDepthTo20e4( - builder, host_depth32, depth_float24_round(), true, - ext_inst_glsl_std_450); + builder, host_depth32, + !depth_float24_convert_in_pixel_shader() && + depth_float24_round(), + true, ext_inst_glsl_std_450); } break; } assert_true(host_depth24 != spv::NoResult); @@ -6109,8 +6126,9 @@ VkPipeline VulkanRenderTargetCache::GetDumpPipeline(DumpPipelineKey key) { } break; case xenos::DepthRenderTargetFormat::kD24FS8: { packed[0] = SpirvShaderTranslator::PreClampedDepthTo20e4( - builder, source_depth32, depth_float24_round(), true, - ext_inst_glsl_std_450); + builder, source_depth32, + !depth_float24_convert_in_pixel_shader() && depth_float24_round(), + true, ext_inst_glsl_std_450); } break; } packed[0] = builder.createQuadOp( diff --git a/src/xenia/gpu/vulkan/vulkan_render_target_cache.h b/src/xenia/gpu/vulkan/vulkan_render_target_cache.h index fa8d0b926..e08cd3205 100644 --- a/src/xenia/gpu/vulkan/vulkan_render_target_cache.h +++ b/src/xenia/gpu/vulkan/vulkan_render_target_cache.h @@ -161,6 +161,9 @@ class VulkanRenderTargetCache final : public RenderTargetCache { return depth_unorm24_vulkan_format_supported_; } bool depth_float24_round() const { return depth_float24_round_; } + bool depth_float24_convert_in_pixel_shader() const { + return depth_float24_convert_in_pixel_shader_; + } bool msaa_2x_attachments_supported() const { return msaa_2x_attachments_supported_; @@ -891,6 +894,7 @@ class VulkanRenderTargetCache final : public RenderTargetCache { bool depth_unorm24_vulkan_format_supported_ = false; bool depth_float24_round_ = false; + bool depth_float24_convert_in_pixel_shader_ = false; bool msaa_2x_attachments_supported_ = false; bool msaa_2x_no_attachments_supported_ = false;