diff --git a/src/xenia/gpu/spirv_shader_translator.cc b/src/xenia/gpu/spirv_shader_translator.cc index a6830c20f..cfbbd28e4 100644 --- a/src/xenia/gpu/spirv_shader_translator.cc +++ b/src/xenia/gpu/spirv_shader_translator.cc @@ -231,6 +231,8 @@ void SpirvShaderTranslator::StartTranslation() { offsetof(SystemConstants, texture_swizzled_signs), type_uint4_array_2}, {"texture_swizzles", offsetof(SystemConstants, texture_swizzles), type_uint4_array_4}, + {"alpha_test_reference", offsetof(SystemConstants, alpha_test_reference), + type_float_}, {"color_exp_bias", offsetof(SystemConstants, color_exp_bias), type_float4_}, }; @@ -606,6 +608,10 @@ std::vector SpirvShaderTranslator::CompleteTranslation() { execution_model = spv::ExecutionModelFragment; builder_->addExecutionMode(function_main_, spv::ExecutionModeOriginUpperLeft); + if (IsExecutionModeEarlyFragmentTests()) { + builder_->addExecutionMode(function_main_, + spv::ExecutionModeEarlyFragmentTests); + } } else { assert_true(is_vertex_shader()); execution_model = IsSpirvTessEvalShader() diff --git a/src/xenia/gpu/spirv_shader_translator.h b/src/xenia/gpu/spirv_shader_translator.h index 0a94300a0..aca23efe5 100644 --- a/src/xenia/gpu/spirv_shader_translator.h +++ b/src/xenia/gpu/spirv_shader_translator.h @@ -34,7 +34,16 @@ 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 = 3; + static constexpr uint32_t kVersion = 4; + + 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. + }; struct { // Dynamically indexable register count from SQ_PROGRAM_CNTL. @@ -52,6 +61,8 @@ class SpirvShaderTranslator : public ShaderTranslator { // must not be set for other primitive types - enables the point sprite // coordinates input, and also effects the flag bits in PsParamGen. uint32_t param_gen_point : 1; + // For host render targets - depth / stencil output mode. + DepthStencilMode depth_stencil_mode : 3; } pixel; uint64_t value = 0; @@ -64,6 +75,9 @@ class SpirvShaderTranslator : public ShaderTranslator { kSysFlag_WNotReciprocal_Shift, kSysFlag_PrimitivePolygonal_Shift, kSysFlag_PrimitiveLine_Shift, + kSysFlag_AlphaPassIfLess_Shift, + kSysFlag_AlphaPassIfEqual_Shift, + kSysFlag_AlphaPassIfGreater_Shift, kSysFlag_ConvertColor0ToGamma_Shift, kSysFlag_ConvertColor1ToGamma_Shift, kSysFlag_ConvertColor2ToGamma_Shift, @@ -76,6 +90,9 @@ class SpirvShaderTranslator : public ShaderTranslator { kSysFlag_WNotReciprocal = 1u << kSysFlag_WNotReciprocal_Shift, kSysFlag_PrimitivePolygonal = 1u << kSysFlag_PrimitivePolygonal_Shift, kSysFlag_PrimitiveLine = 1u << kSysFlag_PrimitiveLine_Shift, + kSysFlag_AlphaPassIfLess = 1u << kSysFlag_AlphaPassIfLess_Shift, + kSysFlag_AlphaPassIfEqual = 1u << kSysFlag_AlphaPassIfEqual_Shift, + kSysFlag_AlphaPassIfGreater = 1u << kSysFlag_AlphaPassIfGreater_Shift, kSysFlag_ConvertColor0ToGamma = 1u << kSysFlag_ConvertColor0ToGamma_Shift, kSysFlag_ConvertColor1ToGamma = 1u << kSysFlag_ConvertColor1ToGamma_Shift, kSysFlag_ConvertColor2ToGamma = 1u << kSysFlag_ConvertColor2ToGamma_Shift, @@ -108,6 +125,9 @@ class SpirvShaderTranslator : public ShaderTranslator { // swizzles for 2 texture fetch constants (in bits 0:11 and 12:23). uint32_t texture_swizzles[16]; + float alpha_test_reference; + float padding_alpha_test_reference[3]; + float color_exp_bias[4]; }; @@ -311,6 +331,14 @@ class SpirvShaderTranslator : public ShaderTranslator { GetSpirvShaderModification().vertex.host_vertex_shader_type); } + bool IsExecutionModeEarlyFragmentTests() const { + // TODO(Triang3l): Not applicable to fragment shader interlock. + return is_pixel_shader() && + GetSpirvShaderModification().pixel.depth_stencil_mode == + Modification::DepthStencilMode::kEarlyHint && + current_shader().implicit_early_z_write_allowed(); + } + // Returns UINT32_MAX if PsParamGen doesn't need to be written. uint32_t GetPsParamGenInterpolator() const; @@ -528,6 +556,7 @@ class SpirvShaderTranslator : public ShaderTranslator { kSystemConstantNdcOffset, kSystemConstantTextureSwizzledSigns, kSystemConstantTextureSwizzles, + kSystemConstantAlphaTestReference, kSystemConstantColorExpBias, }; spv::Id uniform_system_constants_; diff --git a/src/xenia/gpu/spirv_shader_translator_rb.cc b/src/xenia/gpu/spirv_shader_translator_rb.cc index 82a58dfec..c594a902f 100644 --- a/src/xenia/gpu/spirv_shader_translator_rb.cc +++ b/src/xenia/gpu/spirv_shader_translator_rb.cc @@ -433,6 +433,129 @@ void SpirvShaderTranslator::CompleteFragmentShaderInMain() { uniform_system_constants_, id_vector_temp_), spv::NoPrecision); + if (current_shader().writes_color_target(0) && + !IsExecutionModeEarlyFragmentTests()) { + // Alpha test. + // TODO(Triang3l): Check how alpha test works with NaN on Direct3D 9. + // Extract the comparison function (less, equal, greater bits). + spv::Id alpha_test_function = builder_->createTriOp( + spv::OpBitFieldUExtract, type_uint_, main_system_constant_flags_, + builder_->makeUintConstant(kSysFlag_AlphaPassIfLess_Shift), + builder_->makeUintConstant(3)); + // Check if the comparison function is not "always" - that should pass even + // for NaN likely, unlike "less, equal or greater". + spv::Id alpha_test_function_is_non_always = builder_->createBinOp( + spv::OpINotEqual, type_bool_, alpha_test_function, + builder_->makeUintConstant(uint32_t(xenos::CompareFunction::kAlways))); + spv::Block& block_alpha_test = builder_->makeNewBlock(); + spv::Block& block_alpha_test_merge = builder_->makeNewBlock(); + SpirvCreateSelectionMerge(block_alpha_test_merge.getId(), + spv::SelectionControlDontFlattenMask); + builder_->createConditionalBranch(alpha_test_function_is_non_always, + &block_alpha_test, + &block_alpha_test_merge); + builder_->setBuildPoint(&block_alpha_test); + { + id_vector_temp_.clear(); + id_vector_temp_.push_back(builder_->makeIntConstant(3)); + spv::Id alpha_test_alpha = + builder_->createLoad(builder_->createAccessChain( + spv::StorageClassOutput, + output_fragment_data_[0], id_vector_temp_), + spv::NoPrecision); + id_vector_temp_.clear(); + id_vector_temp_.push_back( + builder_->makeIntConstant(kSystemConstantAlphaTestReference)); + spv::Id alpha_test_reference = + builder_->createLoad(builder_->createAccessChain( + spv::StorageClassUniform, + uniform_system_constants_, id_vector_temp_), + spv::NoPrecision); + // The comparison function is not "always" - perform the alpha test. + // Handle "not equal" specially (specifically as "not equal" so it's true + // for NaN, not "less or greater" which is false for NaN). + spv::Id alpha_test_function_is_not_equal = builder_->createBinOp( + spv::OpIEqual, type_bool_, alpha_test_function, + builder_->makeUintConstant( + uint32_t(xenos::CompareFunction::kNotEqual))); + spv::Block& block_alpha_test_not_equal = builder_->makeNewBlock(); + spv::Block& block_alpha_test_non_not_equal = builder_->makeNewBlock(); + spv::Block& block_alpha_test_not_equal_merge = builder_->makeNewBlock(); + SpirvCreateSelectionMerge(block_alpha_test_not_equal_merge.getId(), + spv::SelectionControlDontFlattenMask); + builder_->createConditionalBranch(alpha_test_function_is_not_equal, + &block_alpha_test_not_equal, + &block_alpha_test_non_not_equal); + spv::Id alpha_test_result_not_equal, alpha_test_result_non_not_equal; + builder_->setBuildPoint(&block_alpha_test_not_equal); + { + // "Not equal" function. + alpha_test_result_not_equal = + builder_->createBinOp(spv::OpFUnordNotEqual, type_bool_, + alpha_test_alpha, alpha_test_reference); + builder_->createBranch(&block_alpha_test_not_equal_merge); + } + builder_->setBuildPoint(&block_alpha_test_non_not_equal); + { + // Function other than "not equal". + static const spv::Op kAlphaTestOps[] = { + spv::OpFOrdLessThan, spv::OpFOrdEqual, spv::OpFOrdGreaterThan}; + for (uint32_t i = 0; i < 3; ++i) { + spv::Id alpha_test_comparison_result = builder_->createBinOp( + spv::OpLogicalAnd, type_bool_, + builder_->createBinOp(kAlphaTestOps[i], type_bool_, + alpha_test_alpha, alpha_test_reference), + builder_->createBinOp( + spv::OpINotEqual, type_bool_, + builder_->createBinOp( + spv::OpBitwiseAnd, type_uint_, alpha_test_function, + builder_->makeUintConstant(UINT32_C(1) << i)), + const_uint_0_)); + if (i) { + alpha_test_result_non_not_equal = builder_->createBinOp( + spv::OpLogicalOr, type_bool_, alpha_test_result_non_not_equal, + alpha_test_comparison_result); + } else { + alpha_test_result_non_not_equal = alpha_test_comparison_result; + } + } + builder_->createBranch(&block_alpha_test_not_equal_merge); + } + builder_->setBuildPoint(&block_alpha_test_not_equal_merge); + spv::Id alpha_test_result; + { + std::unique_ptr alpha_test_result_phi_op = + std::make_unique(builder_->getUniqueId(), + type_bool_, spv::OpPhi); + alpha_test_result_phi_op->addIdOperand(alpha_test_result_not_equal); + alpha_test_result_phi_op->addIdOperand( + block_alpha_test_not_equal.getId()); + alpha_test_result_phi_op->addIdOperand(alpha_test_result_non_not_equal); + alpha_test_result_phi_op->addIdOperand( + block_alpha_test_non_not_equal.getId()); + alpha_test_result = alpha_test_result_phi_op->getResultId(); + builder_->getBuildPoint()->addInstruction( + std::move(alpha_test_result_phi_op)); + } + // Discard the pixel if the alpha test has failed. Creating a merge block + // even though it will contain just one OpBranch since SPIR-V requires + // structured control flow in shaders. + spv::Block& block_alpha_test_kill = builder_->makeNewBlock(); + spv::Block& block_alpha_test_kill_merge = builder_->makeNewBlock(); + SpirvCreateSelectionMerge(block_alpha_test_kill_merge.getId(), + spv::SelectionControlDontFlattenMask); + builder_->createConditionalBranch(alpha_test_result, + &block_alpha_test_kill_merge, + &block_alpha_test_kill); + builder_->setBuildPoint(&block_alpha_test_kill); + builder_->createNoResultOp(spv::OpKill); + // OpKill terminates the block. + builder_->setBuildPoint(&block_alpha_test_kill_merge); + builder_->createBranch(&block_alpha_test_merge); + } + builder_->setBuildPoint(&block_alpha_test_merge); + } + uint32_t color_targets_remaining = current_shader().writes_color_targets(); uint32_t color_target_index; while (xe::bit_scan_forward(color_targets_remaining, &color_target_index)) { diff --git a/src/xenia/gpu/vulkan/vulkan_command_processor.cc b/src/xenia/gpu/vulkan/vulkan_command_processor.cc index 6eb614088..e38dceb1b 100644 --- a/src/xenia/gpu/vulkan/vulkan_command_processor.cc +++ b/src/xenia/gpu/vulkan/vulkan_command_processor.cc @@ -3377,6 +3377,8 @@ void VulkanCommandProcessor::UpdateSystemConstantValues( const RegisterFile& regs = *register_file_; auto pa_cl_vte_cntl = regs.Get(); + float rb_alpha_ref = regs[XE_GPU_REG_RB_ALPHA_REF].f32; + auto rb_colorcontrol = regs.Get(); auto vgt_draw_initiator = regs.Get(); int32_t vgt_indx_offset = int32_t(regs[XE_GPU_REG_VGT_INDX_OFFSET].u32); @@ -3416,6 +3418,12 @@ void VulkanCommandProcessor::UpdateSystemConstantValues( if (draw_util::IsPrimitiveLine(regs)) { flags |= SpirvShaderTranslator::kSysFlag_PrimitiveLine; } + // Alpha test. + xenos::CompareFunction alpha_test_function = + rb_colorcontrol.alpha_test_enable ? rb_colorcontrol.alpha_func + : xenos::CompareFunction::kAlways; + flags |= uint32_t(alpha_test_function) + << SpirvShaderTranslator::kSysFlag_AlphaPassIfLess_Shift; // Gamma writing. // TODO(Triang3l): Gamma as sRGB check. for (uint32_t i = 0; i < xenos::kMaxColorRenderTargets; ++i) { @@ -3492,6 +3500,10 @@ void VulkanCommandProcessor::UpdateSystemConstantValues( } } + // Alpha test. + dirty |= system_constants_.alpha_test_reference != rb_alpha_ref; + system_constants_.alpha_test_reference = rb_alpha_ref; + // Color exponent bias. for (uint32_t i = 0; i < xenos::kMaxColorRenderTargets; ++i) { reg::RB_COLOR_INFO color_info = color_infos[i]; diff --git a/src/xenia/gpu/vulkan/vulkan_pipeline_cache.cc b/src/xenia/gpu/vulkan/vulkan_pipeline_cache.cc index 406a1a444..39decc091 100644 --- a/src/xenia/gpu/vulkan/vulkan_pipeline_cache.cc +++ b/src/xenia/gpu/vulkan/vulkan_pipeline_cache.cc @@ -155,6 +155,17 @@ VulkanPipelineCache::GetCurrentPixelShaderModification( } } + using DepthStencilMode = + SpirvShaderTranslator::Modification::DepthStencilMode; + 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; + } + return modification; }