diff --git a/src/xenia/gpu/d3d12/pipeline_cache.cc b/src/xenia/gpu/d3d12/pipeline_cache.cc index 1c1425438..003575b4b 100644 --- a/src/xenia/gpu/d3d12/pipeline_cache.cc +++ b/src/xenia/gpu/d3d12/pipeline_cache.cc @@ -947,6 +947,38 @@ PipelineCache::GetCurrentPixelShaderModification( modification.pixel.depth_stencil_mode = DepthStencilMode::kNoModifiers; } } + + // Check if MIN/MAX blend is used with non-trivial source factors. + // D3D12 fixed-function blend ignores factors for MIN/MAX, but Xbox 360 + // applies them. If the destination factor is ONE (or ZERO), we can + // pre-multiply the shader output by the source factor to emulate this. + // Only RT0 is supported for now. + modification.pixel.rt0_blend_rgb_factor_for_premult = + xenos::BlendFactor::kOne; + modification.pixel.rt0_blend_a_factor_for_premult = + xenos::BlendFactor::kOne; + + if (shader.writes_color_target(0)) { + auto blend_control = regs.Get( + reg::RB_BLENDCONTROL::rt_register_indices[0]); + + // Pre-multiply by kSrcAlpha for MIN/MAX blend ops when dstFactor is ONE. + if ((blend_control.color_comb_fcn == xenos::BlendOp::kMin || + blend_control.color_comb_fcn == xenos::BlendOp::kMax) && + blend_control.color_srcblend == xenos::BlendFactor::kSrcAlpha && + blend_control.color_destblend == xenos::BlendFactor::kOne) { + modification.pixel.rt0_blend_rgb_factor_for_premult = + xenos::BlendFactor::kSrcAlpha; + } + + if ((blend_control.alpha_comb_fcn == xenos::BlendOp::kMin || + blend_control.alpha_comb_fcn == xenos::BlendOp::kMax) && + blend_control.alpha_srcblend == xenos::BlendFactor::kSrcAlpha && + blend_control.alpha_destblend == xenos::BlendFactor::kOne) { + modification.pixel.rt0_blend_a_factor_for_premult = + xenos::BlendFactor::kSrcAlpha; + } + } } return modification; diff --git a/src/xenia/gpu/dxbc_shader_translator.h b/src/xenia/gpu/dxbc_shader_translator.h index 20fbdd328..6af1ce0b0 100644 --- a/src/xenia/gpu/dxbc_shader_translator.h +++ b/src/xenia/gpu/dxbc_shader_translator.h @@ -114,7 +114,7 @@ class DxbcShaderTranslator : public ShaderTranslator { // If anything in this is structure is changed in a way not compatible with // the previous layout, invalidate the pipeline storages by increasing this // version number (0xYYYYMMDD)! - static constexpr uint32_t kVersion = 0x20220720; + static constexpr uint32_t kVersion = 0x20251216; enum class DepthStencilMode : uint32_t { kNoModifiers, @@ -179,6 +179,12 @@ class DxbcShaderTranslator : public ShaderTranslator { uint32_t dynamic_addressable_register_count : 8; // Non-ROV - depth / stencil output mode. DepthStencilMode depth_stencil_mode : 2; + // For host render targets with MIN/MAX blend op - the source blend factor + // to pre-multiply the shader output by (since D3D12 MIN/MAX ignores blend + // factors, but Xbox 360 applies them). kOne means no pre-multiply. + // Only RT0 is supported for now. + xenos::BlendFactor rt0_blend_rgb_factor_for_premult : 5; + xenos::BlendFactor rt0_blend_a_factor_for_premult : 5; } pixel; explicit Modification(uint64_t modification_value = 0) diff --git a/src/xenia/gpu/dxbc_shader_translator_om.cc b/src/xenia/gpu/dxbc_shader_translator_om.cc index e2df54609..001be04b4 100644 --- a/src/xenia/gpu/dxbc_shader_translator_om.cc +++ b/src/xenia/gpu/dxbc_shader_translator_om.cc @@ -1701,6 +1701,143 @@ void DxbcShaderTranslator::CompletePixelShader_WriteToRTVs() { } a_.OpEndIf(); } + // For RT0 with MIN/MAX blend op, pre-multiply by the source blend factor + // (since D3D12 MIN/MAX ignores blend factors, but Xbox 360 applies them). + if (i == 0 && !edram_rov_used_) { + xenos::BlendFactor rgb_factor_for_premult = + GetDxbcShaderModification().pixel.rt0_blend_rgb_factor_for_premult; + xenos::BlendFactor a_factor_for_premult = + GetDxbcShaderModification().pixel.rt0_blend_a_factor_for_premult; + bool premult_rgb = rgb_factor_for_premult != xenos::BlendFactor::kOne; + bool premult_a = a_factor_for_premult != xenos::BlendFactor::kOne; + if (premult_rgb || premult_a) { + uint32_t premult_temp = PushSystemTemp(); + // Compute and apply RGB factor. + if (premult_rgb) { + switch (rgb_factor_for_premult) { + case xenos::BlendFactor::kZero: + a_.OpMov(dxbc::Dest::R(system_temp_color, 0b0111), + dxbc::Src::LF(0.0f)); + break; + case xenos::BlendFactor::kSrcColor: + // Multiply by itself (square). + a_.OpMul(dxbc::Dest::R(system_temp_color, 0b0111), + dxbc::Src::R(system_temp_color), + dxbc::Src::R(system_temp_color)); + break; + case xenos::BlendFactor::kOneMinusSrcColor: + a_.OpAdd(dxbc::Dest::R(premult_temp, 0b0111), dxbc::Src::LF(1.0f), + -dxbc::Src::R(system_temp_color)); + a_.OpMul(dxbc::Dest::R(system_temp_color, 0b0111), + dxbc::Src::R(system_temp_color), + dxbc::Src::R(premult_temp)); + break; + case xenos::BlendFactor::kSrcAlpha: + a_.OpMul(dxbc::Dest::R(system_temp_color, 0b0111), + dxbc::Src::R(system_temp_color), + dxbc::Src::R(system_temp_color, dxbc::Src::kWWWW)); + break; + case xenos::BlendFactor::kOneMinusSrcAlpha: + a_.OpAdd(dxbc::Dest::R(premult_temp, 0b0001), dxbc::Src::LF(1.0f), + -dxbc::Src::R(system_temp_color, dxbc::Src::kWWWW)); + a_.OpMul(dxbc::Dest::R(system_temp_color, 0b0111), + dxbc::Src::R(system_temp_color), + dxbc::Src::R(premult_temp, dxbc::Src::kXXXX)); + break; + case xenos::BlendFactor::kConstantColor: + a_.OpMul(dxbc::Dest::R(system_temp_color, 0b0111), + dxbc::Src::R(system_temp_color), + LoadSystemConstant( + SystemConstants::Index::kEdramBlendConstant, + offsetof(SystemConstants, edram_blend_constant), + dxbc::Src::kXYZW)); + break; + case xenos::BlendFactor::kOneMinusConstantColor: + a_.OpAdd(dxbc::Dest::R(premult_temp, 0b0111), dxbc::Src::LF(1.0f), + -LoadSystemConstant( + SystemConstants::Index::kEdramBlendConstant, + offsetof(SystemConstants, edram_blend_constant), + dxbc::Src::kXYZW)); + a_.OpMul(dxbc::Dest::R(system_temp_color, 0b0111), + dxbc::Src::R(system_temp_color), + dxbc::Src::R(premult_temp)); + break; + case xenos::BlendFactor::kConstantAlpha: + a_.OpMul(dxbc::Dest::R(system_temp_color, 0b0111), + dxbc::Src::R(system_temp_color), + LoadSystemConstant( + SystemConstants::Index::kEdramBlendConstant, + offsetof(SystemConstants, edram_blend_constant), + dxbc::Src::kWWWW)); + break; + case xenos::BlendFactor::kOneMinusConstantAlpha: + a_.OpAdd(dxbc::Dest::R(premult_temp, 0b0001), dxbc::Src::LF(1.0f), + -LoadSystemConstant( + SystemConstants::Index::kEdramBlendConstant, + offsetof(SystemConstants, edram_blend_constant), + dxbc::Src::kWWWW)); + a_.OpMul(dxbc::Dest::R(system_temp_color, 0b0111), + dxbc::Src::R(system_temp_color), + dxbc::Src::R(premult_temp, dxbc::Src::kXXXX)); + break; + default: + // kOne or unsupported - no pre-multiply. + break; + } + } + // Compute and apply alpha factor. + if (premult_a) { + switch (a_factor_for_premult) { + case xenos::BlendFactor::kZero: + a_.OpMov(dxbc::Dest::R(system_temp_color, 0b1000), + dxbc::Src::LF(0.0f)); + break; + case xenos::BlendFactor::kSrcColor: + case xenos::BlendFactor::kSrcAlpha: + // Alpha * Alpha. + a_.OpMul(dxbc::Dest::R(system_temp_color, 0b1000), + dxbc::Src::R(system_temp_color, dxbc::Src::kWWWW), + dxbc::Src::R(system_temp_color, dxbc::Src::kWWWW)); + break; + case xenos::BlendFactor::kOneMinusSrcColor: + case xenos::BlendFactor::kOneMinusSrcAlpha: + a_.OpAdd(dxbc::Dest::R(premult_temp, 0b0001), dxbc::Src::LF(1.0f), + -dxbc::Src::R(system_temp_color, dxbc::Src::kWWWW)); + a_.OpMul(dxbc::Dest::R(system_temp_color, 0b1000), + dxbc::Src::R(system_temp_color, dxbc::Src::kWWWW), + dxbc::Src::R(premult_temp, dxbc::Src::kXXXX)); + break; + case xenos::BlendFactor::kConstantColor: + case xenos::BlendFactor::kConstantAlpha: + a_.OpMul(dxbc::Dest::R(system_temp_color, 0b1000), + dxbc::Src::R(system_temp_color, dxbc::Src::kWWWW), + LoadSystemConstant( + SystemConstants::Index::kEdramBlendConstant, + offsetof(SystemConstants, edram_blend_constant), + dxbc::Src::kWWWW)); + break; + case xenos::BlendFactor::kOneMinusConstantColor: + case xenos::BlendFactor::kOneMinusConstantAlpha: + a_.OpAdd(dxbc::Dest::R(premult_temp, 0b0001), dxbc::Src::LF(1.0f), + -LoadSystemConstant( + SystemConstants::Index::kEdramBlendConstant, + offsetof(SystemConstants, edram_blend_constant), + dxbc::Src::kWWWW)); + a_.OpMul(dxbc::Dest::R(system_temp_color, 0b1000), + dxbc::Src::R(system_temp_color, dxbc::Src::kWWWW), + dxbc::Src::R(premult_temp, dxbc::Src::kXXXX)); + break; + case xenos::BlendFactor::kSrcAlphaSaturate: + // For alpha, SrcAlphaSaturate is 1.0, so no pre-multiply needed. + break; + default: + // kOne or unsupported - no pre-multiply. + break; + } + } + PopSystemTemp(); // premult_temp + } + } // Copy the color from a readable temp register to an output register. a_.OpMov(dxbc::Dest::O(i), dxbc::Src::R(system_temp_color)); } @@ -2526,33 +2663,126 @@ void DxbcShaderTranslator::CompletePixelShader_WriteToROV() { rt_clamp_vec_src.Select(2)); } // Need to do min/max for color. + // Note: Unlike Vulkan/D3D12 fixed-function blend which ignores + // factors for MIN/MAX, the Xbox 360 applies blend factors before + // min/max. a_.OpElse(); { - // Extract the color min (0) or max (1) bit to temp.x - // temp.x = whether min or max should be used for color. + uint32_t blend_src_temp = PushSystemTemp(); + dxbc::Dest blend_src_temp_rgb_dest( + dxbc::Dest::R(blend_src_temp, 0b0111)); + dxbc::Src blend_src_temp_src(dxbc::Src::R(blend_src_temp)); + + // Apply source color factor for min/max. + // Extract the source color factor to temp.x. a_.OpAnd(temp_x_dest, rt_blend_factors_ops_src, - dxbc::Src::LU(1 << 5)); - // Check if need to do min or max for color. - // temp.x = free. + dxbc::Src::LU((1 << 5) - 1)); a_.OpIf(true, temp_x_src); { - // Choose max of the colors without applying the factors to - // color_temp.xyz. - // color_temp.xyz = blended color. - a_.OpMax(color_temp_rgb_dest, - dxbc::Src::R(system_temps_color_[i]), color_temp_src); + a_.OpSwitch(temp_x_src); + ROV_HandleColorBlendFactorCases(system_temps_color_[i], + color_temp, blend_src_temp); + a_.OpEndSwitch(); + // Check if fixed-point and needs clamping. + a_.OpAnd( + temp_x_dest, rt_format_flags_src, + dxbc::Src::LU( + RenderTargetCache::kPSIColorFormatFlag_FixedPointColor)); + a_.OpIf(true, temp_x_src); + { + a_.OpMax(blend_src_temp_rgb_dest, blend_src_temp_src, + rt_clamp_vec_src.Select(0)); + a_.OpMin(blend_src_temp_rgb_dest, blend_src_temp_src, + rt_clamp_vec_src.Select(2)); + } + a_.OpEndIf(); + // Multiply source by factor. + a_.OpMul(blend_src_temp_rgb_dest, + dxbc::Src::R(system_temps_color_[i]), + blend_src_temp_src); + // Clamp result if fixed-point. + a_.OpIf(true, temp_x_src); + { + a_.OpMax(blend_src_temp_rgb_dest, blend_src_temp_src, + rt_clamp_vec_src.Select(0)); + a_.OpMin(blend_src_temp_rgb_dest, blend_src_temp_src, + rt_clamp_vec_src.Select(2)); + } + a_.OpEndIf(); } - // Need to do min. a_.OpElse(); { - // Choose min of the colors without applying the factors to - // color_temp.xyz. - // color_temp.xyz = blended color. - a_.OpMin(color_temp_rgb_dest, - dxbc::Src::R(system_temps_color_[i]), color_temp_src); + a_.OpMov(blend_src_temp_rgb_dest, dxbc::Src::LF(0.0f)); } - // Close the min or max check. a_.OpEndIf(); + + // Apply destination color factor for min/max. + uint32_t blend_dest_temp = PushSystemTemp(); + dxbc::Dest blend_dest_temp_rgb_dest( + dxbc::Dest::R(blend_dest_temp, 0b0111)); + dxbc::Src blend_dest_temp_src(dxbc::Src::R(blend_dest_temp)); + + // Extract the destination color factor to temp.x. + a_.OpUBFE(temp_x_dest, dxbc::Src::LU(5), dxbc::Src::LU(8), + rt_blend_factors_ops_src); + a_.OpIf(true, temp_x_src); + { + a_.OpSwitch(temp_x_src); + ROV_HandleColorBlendFactorCases(system_temps_color_[i], + color_temp, blend_dest_temp); + a_.OpEndSwitch(); + // Check if fixed-point and needs clamping. + a_.OpAnd( + temp_x_dest, rt_format_flags_src, + dxbc::Src::LU( + RenderTargetCache::kPSIColorFormatFlag_FixedPointColor)); + a_.OpIf(true, temp_x_src); + { + a_.OpMax(blend_dest_temp_rgb_dest, blend_dest_temp_src, + rt_clamp_vec_src.Select(0)); + a_.OpMin(blend_dest_temp_rgb_dest, blend_dest_temp_src, + rt_clamp_vec_src.Select(2)); + } + a_.OpEndIf(); + // Multiply destination by factor. + a_.OpMul(blend_dest_temp_rgb_dest, color_temp_src, + blend_dest_temp_src); + // Clamp result if fixed-point. + a_.OpIf(true, temp_x_src); + { + a_.OpMax(blend_dest_temp_rgb_dest, blend_dest_temp_src, + rt_clamp_vec_src.Select(0)); + a_.OpMin(blend_dest_temp_rgb_dest, blend_dest_temp_src, + rt_clamp_vec_src.Select(2)); + } + a_.OpEndIf(); + } + a_.OpElse(); + { + a_.OpMov(blend_dest_temp_rgb_dest, dxbc::Src::LF(0.0f)); + } + a_.OpEndIf(); + + // Now do min or max on the factored values. + // Extract the color min (0) or max (1) bit to temp.x. + a_.OpAnd(temp_x_dest, rt_blend_factors_ops_src, + dxbc::Src::LU(1 << 5)); + a_.OpIf(true, temp_x_src); + { + // MAX: color_temp.xyz = max(src * srcFactor, dst * dstFactor) + a_.OpMax(color_temp_rgb_dest, blend_src_temp_src, + blend_dest_temp_src); + } + a_.OpElse(); + { + // MIN: color_temp.xyz = min(src * srcFactor, dst * dstFactor) + a_.OpMin(color_temp_rgb_dest, blend_src_temp_src, + blend_dest_temp_src); + } + a_.OpEndIf(); + + PopSystemTemp(); // blend_dest_temp + PopSystemTemp(); // blend_src_temp } // Close the color factor blending or min/max check. a_.OpEndIf(); @@ -2731,34 +2961,114 @@ void DxbcShaderTranslator::CompletePixelShader_WriteToROV() { rt_clamp_vec_src.Select(3)); } // Need to do min/max for alpha. + // Note: Unlike Vulkan/D3D12 fixed-function blend which ignores + // factors for MIN/MAX, the Xbox 360 applies blend factors before + // min/max. a_.OpElse(); { - // Extract the alpha min (0) or max (1) bit to temp.x. - // temp.x = whether min or max should be used for alpha. - a_.OpAnd(temp_x_dest, rt_blend_factors_ops_src, - dxbc::Src::LU(1 << 21)); - // Check if need to do min or max for alpha. - // temp.x = free. + // We'll use temp.x for source alpha (factored) and temp.y for + // destination alpha (factored). + + // Apply source alpha factor for min/max. + // Extract the source alpha factor to temp.x (bits 16-20). + a_.OpUBFE(temp_x_dest, dxbc::Src::LU(5), dxbc::Src::LU(16), + rt_blend_factors_ops_src); a_.OpIf(true, temp_x_src); { - // Choose max of the alphas without applying the factors to - // color_temp.w. - // color_temp.w = blended alpha. - a_.OpMax(color_temp_a_dest, + a_.OpSwitch(temp_x_src); + ROV_HandleAlphaBlendFactorCases(system_temps_color_[i], + color_temp, temp, 0); + a_.OpEndSwitch(); + // Check if fixed-point and needs clamping. + uint32_t alpha_is_fixed_temp = PushSystemTemp(); + a_.OpAnd( + dxbc::Dest::R(alpha_is_fixed_temp, 0b0001), + rt_format_flags_src, + dxbc::Src::LU( + RenderTargetCache::kPSIColorFormatFlag_FixedPointAlpha)); + a_.OpIf(true, + dxbc::Src::R(alpha_is_fixed_temp, dxbc::Src::kXXXX)); + { + a_.OpMax(temp_x_dest, temp_x_src, rt_clamp_vec_src.Select(1)); + a_.OpMin(temp_x_dest, temp_x_src, rt_clamp_vec_src.Select(3)); + } + a_.OpEndIf(); + // Multiply source alpha by factor. + a_.OpMul(temp_x_dest, dxbc::Src::R(system_temps_color_[i], dxbc::Src::kWWWW), - color_temp_a_src); + temp_x_src); + // Clamp result if fixed-point. + a_.OpIf(true, + dxbc::Src::R(alpha_is_fixed_temp, dxbc::Src::kXXXX)); + PopSystemTemp(); // alpha_is_fixed_temp + { + a_.OpMax(temp_x_dest, temp_x_src, rt_clamp_vec_src.Select(1)); + a_.OpMin(temp_x_dest, temp_x_src, rt_clamp_vec_src.Select(3)); + } + a_.OpEndIf(); } - // Need to do min. a_.OpElse(); { - // Choose min of the alphas without applying the factors to - // color_temp.w. - // color_temp.w = blended alpha. - a_.OpMin(color_temp_a_dest, - dxbc::Src::R(system_temps_color_[i], dxbc::Src::kWWWW), - color_temp_a_src); + a_.OpMov(temp_x_dest, dxbc::Src::LF(0.0f)); + } + a_.OpEndIf(); + + // Apply destination alpha factor for min/max. + // Extract the destination alpha factor to temp.y (bits 24-28). + a_.OpUBFE(temp_y_dest, dxbc::Src::LU(5), dxbc::Src::LU(24), + rt_blend_factors_ops_src); + a_.OpIf(true, temp_y_src); + { + a_.OpSwitch(temp_y_src); + ROV_HandleAlphaBlendFactorCases(system_temps_color_[i], + color_temp, temp, 1); + a_.OpEndSwitch(); + // Check if fixed-point and needs clamping. + uint32_t alpha_is_fixed_temp2 = PushSystemTemp(); + a_.OpAnd( + dxbc::Dest::R(alpha_is_fixed_temp2, 0b0001), + rt_format_flags_src, + dxbc::Src::LU( + RenderTargetCache::kPSIColorFormatFlag_FixedPointAlpha)); + a_.OpIf(true, + dxbc::Src::R(alpha_is_fixed_temp2, dxbc::Src::kXXXX)); + { + a_.OpMax(temp_y_dest, temp_y_src, rt_clamp_vec_src.Select(1)); + a_.OpMin(temp_y_dest, temp_y_src, rt_clamp_vec_src.Select(3)); + } + a_.OpEndIf(); + // Multiply destination alpha by factor. + a_.OpMul(temp_y_dest, color_temp_a_src, temp_y_src); + // Clamp result if fixed-point. + a_.OpIf(true, + dxbc::Src::R(alpha_is_fixed_temp2, dxbc::Src::kXXXX)); + PopSystemTemp(); // alpha_is_fixed_temp2 + { + a_.OpMax(temp_y_dest, temp_y_src, rt_clamp_vec_src.Select(1)); + a_.OpMin(temp_y_dest, temp_y_src, rt_clamp_vec_src.Select(3)); + } + a_.OpEndIf(); + } + a_.OpElse(); + { + a_.OpMov(temp_y_dest, dxbc::Src::LF(0.0f)); + } + a_.OpEndIf(); + + // Now do min or max on the factored alpha values. + // Extract the alpha min (0) or max (1) bit to color_temp.w. + a_.OpAnd(color_temp_a_dest, rt_blend_factors_ops_src, + dxbc::Src::LU(1 << 21)); + a_.OpIf(true, color_temp_a_src); + { + // MAX: color_temp.w = max(src * srcFactor, dst * dstFactor) + a_.OpMax(color_temp_a_dest, temp_x_src, temp_y_src); + } + a_.OpElse(); + { + // MIN: color_temp.w = min(src * srcFactor, dst * dstFactor) + a_.OpMin(color_temp_a_dest, temp_x_src, temp_y_src); } - // Close the min or max check. a_.OpEndIf(); } // Close the alpha factor blending or min/max check. diff --git a/src/xenia/gpu/spirv_shader_translator.h b/src/xenia/gpu/spirv_shader_translator.h index 9a52a8f79..066b3b5ee 100644 --- a/src/xenia/gpu/spirv_shader_translator.h +++ b/src/xenia/gpu/spirv_shader_translator.h @@ -34,7 +34,7 @@ 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 = 7; + static constexpr uint32_t kVersion = 8; enum class DepthStencilMode : uint32_t { kNoModifiers, @@ -83,6 +83,12 @@ class SpirvShaderTranslator : public ShaderTranslator { uint32_t param_gen_point : 1; // For host render targets - depth / stencil output mode. DepthStencilMode depth_stencil_mode : 3; + // For host render targets with MIN/MAX blend op - the source blend factor + // to pre-multiply the shader output by (since Vulkan/D3D12 MIN/MAX + // ignores blend factors, but Xbox 360 applies them). kOne means no + // pre-multiply. Only RT0 is supported for now. + xenos::BlendFactor rt0_blend_rgb_factor_for_premult : 5; + xenos::BlendFactor rt0_blend_a_factor_for_premult : 5; } pixel; uint64_t value = 0; diff --git a/src/xenia/gpu/spirv_shader_translator_rb.cc b/src/xenia/gpu/spirv_shader_translator_rb.cc index 649b7876a..cc600dc8a 100644 --- a/src/xenia/gpu/spirv_shader_translator_rb.cc +++ b/src/xenia/gpu/spirv_shader_translator_rb.cc @@ -1301,6 +1301,12 @@ void SpirvShaderTranslator::CompleteFragmentShaderInMain() { // FBO path: Copy from Function-scoped variables to Output variables. // This is done at the end after alpha test/coverage so we can read the // color values during those operations. + Modification shader_modification = GetSpirvShaderModification(); + xenos::BlendFactor rt0_rgb_premult_factor = + shader_modification.pixel.rt0_blend_rgb_factor_for_premult; + xenos::BlendFactor rt0_a_premult_factor = + shader_modification.pixel.rt0_blend_a_factor_for_premult; + uint32_t color_targets_to_copy = current_shader().writes_color_targets(); uint32_t color_target_index; while (xe::bit_scan_forward(color_targets_to_copy, &color_target_index)) { @@ -1308,8 +1314,175 @@ void SpirvShaderTranslator::CompleteFragmentShaderInMain() { spv::Id var_color = output_or_var_fragment_data_[color_target_index]; spv::Id out_color = output_fragment_data_[color_target_index]; if (var_color != spv::NoResult && out_color != spv::NoResult) { - builder_->createStore(builder_->createLoad(var_color, spv::NoPrecision), - out_color); + spv::Id color = builder_->createLoad(var_color, spv::NoPrecision); + + // For RT0, apply pre-multiply by source blend factor if needed for + // MIN/MAX blend emulation (since Vulkan/D3D12 ignores blend factors + // for MIN/MAX but Xbox 360 applies them). + if (color_target_index == 0 && + (rt0_rgb_premult_factor != xenos::BlendFactor::kOne || + rt0_a_premult_factor != xenos::BlendFactor::kOne)) { + // Helper to extract RGB (xyz) from a float4. + auto extract_rgb = [&](spv::Id vec4) -> spv::Id { + uint_vector_temp_.clear(); + uint_vector_temp_.push_back(0); + uint_vector_temp_.push_back(1); + uint_vector_temp_.push_back(2); + return builder_->createRvalueSwizzle(spv::NoPrecision, type_float3_, + vec4, uint_vector_temp_); + }; + + // Get blend factor values. + auto get_factor_value = [&](xenos::BlendFactor factor, + bool for_alpha) -> spv::Id { + spv::Id src_color = color; + switch (factor) { + case xenos::BlendFactor::kZero: + return for_alpha ? const_float_0_ : const_float3_0_; + case xenos::BlendFactor::kOne: + return for_alpha ? const_float_1_ : const_float3_1_; + case xenos::BlendFactor::kSrcColor: + return for_alpha ? builder_->createCompositeExtract( + src_color, type_float_, 3) + : extract_rgb(src_color); + case xenos::BlendFactor::kOneMinusSrcColor: { + spv::Id src = for_alpha ? builder_->createCompositeExtract( + src_color, type_float_, 3) + : extract_rgb(src_color); + spv::Id one = for_alpha ? const_float_1_ : const_float3_1_; + return builder_->createBinOp( + spv::OpFSub, for_alpha ? type_float_ : type_float3_, one, + src); + } + case xenos::BlendFactor::kSrcAlpha: { + spv::Id alpha = + builder_->createCompositeExtract(src_color, type_float_, 3); + if (for_alpha) { + return alpha; + } + return builder_->smearScalar(spv::NoPrecision, alpha, + type_float3_); + } + case xenos::BlendFactor::kOneMinusSrcAlpha: { + spv::Id alpha = + builder_->createCompositeExtract(src_color, type_float_, 3); + spv::Id one_minus_alpha = builder_->createBinOp( + spv::OpFSub, type_float_, const_float_1_, alpha); + if (for_alpha) { + return one_minus_alpha; + } + return builder_->smearScalar(spv::NoPrecision, one_minus_alpha, + type_float3_); + } + case xenos::BlendFactor::kConstantColor: + case xenos::BlendFactor::kConstantAlpha: { + // Load blend constant from system constants. + id_vector_temp_.clear(); + id_vector_temp_.push_back(builder_->makeIntConstant( + kSystemConstantEdramBlendConstant)); + spv::Id blend_constant = builder_->createLoad( + builder_->createAccessChain(spv::StorageClassUniform, + uniform_system_constants_, + id_vector_temp_), + spv::NoPrecision); + if (factor == xenos::BlendFactor::kConstantAlpha) { + spv::Id alpha = builder_->createCompositeExtract( + blend_constant, type_float_, 3); + if (for_alpha) { + return alpha; + } + return builder_->smearScalar(spv::NoPrecision, alpha, + type_float3_); + } + if (for_alpha) { + return builder_->createCompositeExtract(blend_constant, + type_float_, 3); + } + return extract_rgb(blend_constant); + } + case xenos::BlendFactor::kOneMinusConstantColor: + case xenos::BlendFactor::kOneMinusConstantAlpha: { + id_vector_temp_.clear(); + id_vector_temp_.push_back(builder_->makeIntConstant( + kSystemConstantEdramBlendConstant)); + spv::Id blend_constant = builder_->createLoad( + builder_->createAccessChain(spv::StorageClassUniform, + uniform_system_constants_, + id_vector_temp_), + spv::NoPrecision); + spv::Id constant_value; + if (factor == xenos::BlendFactor::kOneMinusConstantAlpha) { + spv::Id alpha = builder_->createCompositeExtract( + blend_constant, type_float_, 3); + if (for_alpha) { + constant_value = alpha; + } else { + constant_value = builder_->smearScalar(spv::NoPrecision, + alpha, type_float3_); + } + } else { + if (for_alpha) { + constant_value = builder_->createCompositeExtract( + blend_constant, type_float_, 3); + } else { + constant_value = extract_rgb(blend_constant); + } + } + spv::Id one = for_alpha ? const_float_1_ : const_float3_1_; + return builder_->createBinOp( + spv::OpFSub, for_alpha ? type_float_ : type_float3_, one, + constant_value); + } + default: + // Unsupported factors - return 1 (no multiply). + return for_alpha ? const_float_1_ : const_float3_1_; + } + }; + + // Apply RGB pre-multiply. + if (rt0_rgb_premult_factor != xenos::BlendFactor::kOne) { + spv::Id rgb_factor = + get_factor_value(rt0_rgb_premult_factor, false); + spv::Id rgb = extract_rgb(color); + rgb = builder_->createBinOp(spv::OpFMul, type_float3_, rgb, + rgb_factor); + // Reconstruct float4 with new RGB and original alpha. + spv::Id alpha = + builder_->createCompositeExtract(color, type_float_, 3); + id_vector_temp_.clear(); + id_vector_temp_.push_back( + builder_->createCompositeExtract(rgb, type_float_, 0)); + id_vector_temp_.push_back( + builder_->createCompositeExtract(rgb, type_float_, 1)); + id_vector_temp_.push_back( + builder_->createCompositeExtract(rgb, type_float_, 2)); + id_vector_temp_.push_back(alpha); + color = builder_->createCompositeConstruct(type_float4_, + id_vector_temp_); + } + + // Apply alpha pre-multiply. + if (rt0_a_premult_factor != xenos::BlendFactor::kOne) { + spv::Id a_factor = get_factor_value(rt0_a_premult_factor, true); + spv::Id alpha = + builder_->createCompositeExtract(color, type_float_, 3); + alpha = builder_->createBinOp(spv::OpFMul, type_float_, alpha, + a_factor); + // Replace alpha in color (extract RGB, reconstruct with new alpha). + id_vector_temp_.clear(); + id_vector_temp_.push_back( + builder_->createCompositeExtract(color, type_float_, 0)); + id_vector_temp_.push_back( + builder_->createCompositeExtract(color, type_float_, 1)); + id_vector_temp_.push_back( + builder_->createCompositeExtract(color, type_float_, 2)); + id_vector_temp_.push_back(alpha); + color = builder_->createCompositeConstruct(type_float4_, + id_vector_temp_); + } + } + + builder_->createStore(color, out_color); } } } @@ -3346,142 +3519,113 @@ spv::Id SpirvShaderTranslator::FSI_BlendColorOrAlphaWithUnclampedResult( constant_color_clamped != spv::NoResult)); spv::Id value_type = is_alpha ? type_float_ : type_float3_; - // Handle min and max blend operations, which don't involve the factors. - spv::Block& block_min_max_head = *builder_->getBuildPoint(); - spv::Block& block_min_max_min = builder_->makeNewBlock(); - spv::Block& block_min_max_max = builder_->makeNewBlock(); - spv::Block& block_min_max_default = builder_->makeNewBlock(); - spv::Block& block_min_max_merge = builder_->makeNewBlock(); - builder_->createSelectionMerge(&block_min_max_merge, + // Apply blend factors to source and destination first. + // Note: Unlike Vulkan's VK_BLEND_OP_MIN/MAX which ignore blend factors, + // the Xbox 360 applies blend factors before the min/max operation. + // So we apply factors unconditionally, then switch on the equation. + spv::Id term_source, term_dest; + if (is_alpha) { + term_source = FSI_ApplyAlphaBlendFactor( + source_alpha_clamped, is_fixed_point, clamp_min_value, clamp_max_value, + source_factor, source_alpha_clamped, dest_alpha, + constant_alpha_clamped); + term_dest = FSI_ApplyAlphaBlendFactor( + dest_alpha, is_fixed_point, clamp_min_value, clamp_max_value, + dest_factor, source_alpha_clamped, dest_alpha, constant_alpha_clamped); + } else { + term_source = FSI_ApplyColorBlendFactor( + source_color_clamped, is_fixed_point, clamp_min_value, clamp_max_value, + source_factor, source_color_clamped, source_alpha_clamped, dest_color, + dest_alpha, constant_color_clamped, constant_alpha_clamped); + term_dest = FSI_ApplyColorBlendFactor( + dest_color, is_fixed_point, clamp_min_value, clamp_max_value, + dest_factor, source_color_clamped, source_alpha_clamped, dest_color, + dest_alpha, constant_color_clamped, constant_alpha_clamped); + } + + // Now switch on the blend equation to combine the factored terms. + spv::Block& block_equation_head = *builder_->getBuildPoint(); + spv::Block& block_equation_add = builder_->makeNewBlock(); + spv::Block& block_equation_subtract = builder_->makeNewBlock(); + spv::Block& block_equation_rev_subtract = builder_->makeNewBlock(); + spv::Block& block_equation_min = builder_->makeNewBlock(); + spv::Block& block_equation_max = builder_->makeNewBlock(); + spv::Block& block_equation_merge = builder_->makeNewBlock(); + builder_->createSelectionMerge(&block_equation_merge, spv::SelectionControlDontFlattenMask); { - std::unique_ptr min_max_switch_op = + std::unique_ptr equation_switch_op = std::make_unique(spv::OpSwitch); - min_max_switch_op->addIdOperand(equation); - min_max_switch_op->addIdOperand(block_min_max_default.getId()); - min_max_switch_op->addImmediateOperand(int32_t(xenos::BlendOp::kMin)); - min_max_switch_op->addIdOperand(block_min_max_min.getId()); - min_max_switch_op->addImmediateOperand(int32_t(xenos::BlendOp::kMax)); - min_max_switch_op->addIdOperand(block_min_max_max.getId()); - builder_->getBuildPoint()->addInstruction(std::move(min_max_switch_op)); + equation_switch_op->addIdOperand(equation); + // Make addition the default. + equation_switch_op->addIdOperand(block_equation_add.getId()); + equation_switch_op->addImmediateOperand(int32_t(xenos::BlendOp::kSubtract)); + equation_switch_op->addIdOperand(block_equation_subtract.getId()); + equation_switch_op->addImmediateOperand( + int32_t(xenos::BlendOp::kRevSubtract)); + equation_switch_op->addIdOperand(block_equation_rev_subtract.getId()); + equation_switch_op->addImmediateOperand(int32_t(xenos::BlendOp::kMin)); + equation_switch_op->addIdOperand(block_equation_min.getId()); + equation_switch_op->addImmediateOperand(int32_t(xenos::BlendOp::kMax)); + equation_switch_op->addIdOperand(block_equation_max.getId()); + builder_->getBuildPoint()->addInstruction(std::move(equation_switch_op)); } - block_min_max_default.addPredecessor(&block_min_max_head); - block_min_max_min.addPredecessor(&block_min_max_head); - block_min_max_max.addPredecessor(&block_min_max_head); + block_equation_add.addPredecessor(&block_equation_head); + block_equation_subtract.addPredecessor(&block_equation_head); + block_equation_rev_subtract.addPredecessor(&block_equation_head); + block_equation_min.addPredecessor(&block_equation_head); + block_equation_max.addPredecessor(&block_equation_head); + + // Addition case (default). + builder_->setBuildPoint(&block_equation_add); + spv::Id result_add = builder_->createNoContractionBinOp( + spv::OpFAdd, value_type, term_source, term_dest); + builder_->createBranch(&block_equation_merge); + + // Subtraction case. + builder_->setBuildPoint(&block_equation_subtract); + spv::Id result_subtract = builder_->createNoContractionBinOp( + spv::OpFSub, value_type, term_source, term_dest); + builder_->createBranch(&block_equation_merge); + + // Reverse subtraction case. + builder_->setBuildPoint(&block_equation_rev_subtract); + spv::Id result_rev_subtract = builder_->createNoContractionBinOp( + spv::OpFSub, value_type, term_dest, term_source); + builder_->createBranch(&block_equation_merge); // Min case. - builder_->setBuildPoint(&block_min_max_min); - spv::Id result_min = builder_->createBinBuiltinCall( - value_type, ext_inst_glsl_std_450_, GLSLstd450FMin, - is_alpha ? source_alpha_clamped : source_color_clamped, - is_alpha ? dest_alpha : dest_color); - builder_->createBranch(&block_min_max_merge); + builder_->setBuildPoint(&block_equation_min); + spv::Id result_min = + builder_->createBinBuiltinCall(value_type, ext_inst_glsl_std_450_, + GLSLstd450FMin, term_source, term_dest); + builder_->createBranch(&block_equation_merge); // Max case. - builder_->setBuildPoint(&block_min_max_max); - spv::Id result_max = builder_->createBinBuiltinCall( - value_type, ext_inst_glsl_std_450_, GLSLstd450FMax, - is_alpha ? source_alpha_clamped : source_color_clamped, - is_alpha ? dest_alpha : dest_color); - builder_->createBranch(&block_min_max_merge); + builder_->setBuildPoint(&block_equation_max); + spv::Id result_max = + builder_->createBinBuiltinCall(value_type, ext_inst_glsl_std_450_, + GLSLstd450FMax, term_source, term_dest); + builder_->createBranch(&block_equation_merge); - // Blending with factors. - spv::Id result_factors; - { - builder_->setBuildPoint(&block_min_max_default); - - spv::Id term_source, term_dest; - if (is_alpha) { - term_source = FSI_ApplyAlphaBlendFactor( - source_alpha_clamped, is_fixed_point, clamp_min_value, - clamp_max_value, source_factor, source_alpha_clamped, dest_alpha, - constant_alpha_clamped); - term_dest = FSI_ApplyAlphaBlendFactor(dest_alpha, is_fixed_point, - clamp_min_value, clamp_max_value, - dest_factor, source_alpha_clamped, - dest_alpha, constant_alpha_clamped); - } else { - term_source = FSI_ApplyColorBlendFactor( - source_color_clamped, is_fixed_point, clamp_min_value, - clamp_max_value, source_factor, source_color_clamped, - source_alpha_clamped, dest_color, dest_alpha, constant_color_clamped, - constant_alpha_clamped); - term_dest = FSI_ApplyColorBlendFactor( - dest_color, is_fixed_point, clamp_min_value, clamp_max_value, - dest_factor, source_color_clamped, source_alpha_clamped, dest_color, - dest_alpha, constant_color_clamped, constant_alpha_clamped); - } - - spv::Block& block_signs_head = *builder_->getBuildPoint(); - spv::Block& block_signs_add = builder_->makeNewBlock(); - spv::Block& block_signs_subtract = builder_->makeNewBlock(); - spv::Block& block_signs_reverse_subtract = builder_->makeNewBlock(); - spv::Block& block_signs_merge = builder_->makeNewBlock(); - builder_->createSelectionMerge(&block_signs_merge, - spv::SelectionControlDontFlattenMask); - { - std::unique_ptr signs_switch_op = - std::make_unique(spv::OpSwitch); - signs_switch_op->addIdOperand(equation); - // Make addition the default. - signs_switch_op->addIdOperand(block_signs_add.getId()); - signs_switch_op->addImmediateOperand(int32_t(xenos::BlendOp::kSubtract)); - signs_switch_op->addIdOperand(block_signs_subtract.getId()); - signs_switch_op->addImmediateOperand( - int32_t(xenos::BlendOp::kRevSubtract)); - signs_switch_op->addIdOperand(block_signs_reverse_subtract.getId()); - builder_->getBuildPoint()->addInstruction(std::move(signs_switch_op)); - } - block_signs_add.addPredecessor(&block_signs_head); - block_signs_subtract.addPredecessor(&block_signs_head); - block_signs_reverse_subtract.addPredecessor(&block_signs_head); - - // Addition case. - builder_->setBuildPoint(&block_signs_add); - spv::Id result_add = builder_->createNoContractionBinOp( - spv::OpFAdd, value_type, term_source, term_dest); - builder_->createBranch(&block_signs_merge); - - // Subtraction case. - builder_->setBuildPoint(&block_signs_subtract); - spv::Id result_subtract = builder_->createNoContractionBinOp( - spv::OpFSub, value_type, term_source, term_dest); - builder_->createBranch(&block_signs_merge); - - // Reverse subtraction case. - builder_->setBuildPoint(&block_signs_reverse_subtract); - spv::Id result_reverse_subtract = builder_->createNoContractionBinOp( - spv::OpFSub, value_type, term_dest, term_source); - builder_->createBranch(&block_signs_merge); - - // Selection between the signs involved in the addition. - builder_->setBuildPoint(&block_signs_merge); - id_vector_temp_.clear(); - id_vector_temp_.reserve(2 * 3); - id_vector_temp_.push_back(result_add); - id_vector_temp_.push_back(block_signs_add.getId()); - id_vector_temp_.push_back(result_subtract); - id_vector_temp_.push_back(block_signs_subtract.getId()); - id_vector_temp_.push_back(result_reverse_subtract); - id_vector_temp_.push_back(block_signs_reverse_subtract.getId()); - result_factors = - builder_->createOp(spv::OpPhi, value_type, id_vector_temp_); - builder_->createBranch(&block_min_max_merge); - } - // Get the latest block for blending with factors after all the control flow. - spv::Block& block_min_max_default_end = *builder_->getBuildPoint(); - - builder_->setBuildPoint(&block_min_max_merge); - // Choose out of min, max, and blending with factors. + // Merge and create phi for the result. + builder_->setBuildPoint(&block_equation_merge); id_vector_temp_.clear(); - id_vector_temp_.reserve(2 * 3); + id_vector_temp_.push_back(result_add); + id_vector_temp_.push_back(block_equation_add.getId()); + id_vector_temp_.push_back(result_subtract); + id_vector_temp_.push_back(block_equation_subtract.getId()); + id_vector_temp_.push_back(result_rev_subtract); + id_vector_temp_.push_back(block_equation_rev_subtract.getId()); id_vector_temp_.push_back(result_min); - id_vector_temp_.push_back(block_min_max_min.getId()); + id_vector_temp_.push_back(block_equation_min.getId()); id_vector_temp_.push_back(result_max); - id_vector_temp_.push_back(block_min_max_max.getId()); - id_vector_temp_.push_back(result_factors); - id_vector_temp_.push_back(block_min_max_default_end.getId()); - return builder_->createOp(spv::OpPhi, value_type, id_vector_temp_); + id_vector_temp_.push_back(block_equation_max.getId()); + spv::Id result_unclamped = + builder_->createOp(spv::OpPhi, value_type, id_vector_temp_); + + return FSI_FlushNaNClampAndInBlending(result_unclamped, is_fixed_point, + clamp_min_value, clamp_max_value); } void SpirvShaderTranslator::FSI_AlphaToMaskSample( diff --git a/src/xenia/gpu/vulkan/vulkan_pipeline_cache.cc b/src/xenia/gpu/vulkan/vulkan_pipeline_cache.cc index 1e89e5b3d..5ffaded3c 100644 --- a/src/xenia/gpu/vulkan/vulkan_pipeline_cache.cc +++ b/src/xenia/gpu/vulkan/vulkan_pipeline_cache.cc @@ -344,6 +344,38 @@ VulkanPipelineCache::GetCurrentPixelShaderModification( } else { modification.pixel.depth_stencil_mode = DepthStencilMode::kNoModifiers; } + + // Check if MIN/MAX blend is used with non-trivial source factors. + // Vulkan/D3D12 fixed-function blend ignores factors for MIN/MAX, but + // Xbox 360 applies them. If the destination factor is ONE (or ZERO), we can + // pre-multiply the shader output by the source factor to emulate this. + // Only RT0 is supported for now. + modification.pixel.rt0_blend_rgb_factor_for_premult = + xenos::BlendFactor::kOne; + modification.pixel.rt0_blend_a_factor_for_premult = + xenos::BlendFactor::kOne; + + if (shader.writes_color_target(0)) { + auto blend_control = regs.Get( + reg::RB_BLENDCONTROL::rt_register_indices[0]); + + // Pre-multiply by kSrcAlpha for MIN/MAX blend ops when dstFactor is ONE. + if ((blend_control.color_comb_fcn == xenos::BlendOp::kMin || + blend_control.color_comb_fcn == xenos::BlendOp::kMax) && + blend_control.color_srcblend == xenos::BlendFactor::kSrcAlpha && + blend_control.color_destblend == xenos::BlendFactor::kOne) { + modification.pixel.rt0_blend_rgb_factor_for_premult = + xenos::BlendFactor::kSrcAlpha; + } + + if ((blend_control.alpha_comb_fcn == xenos::BlendOp::kMin || + blend_control.alpha_comb_fcn == xenos::BlendOp::kMax) && + blend_control.alpha_srcblend == xenos::BlendFactor::kSrcAlpha && + blend_control.alpha_destblend == xenos::BlendFactor::kOne) { + modification.pixel.rt0_blend_a_factor_for_premult = + xenos::BlendFactor::kSrcAlpha; + } + } } return modification; @@ -2383,6 +2415,16 @@ bool VulkanPipelineCache::EnsurePipelineCreated( VK_BLEND_OP_ADD, VK_BLEND_OP_ADD, VK_BLEND_OP_ADD}; + // Check if the shader pre-multiplies by blend factors for MIN/MAX. + SpirvShaderTranslator::Modification pixel_shader_modification( + description.pixel_shader_modification); + bool rt0_rgb_premult = + pixel_shader_modification.pixel.rt0_blend_rgb_factor_for_premult != + xenos::BlendFactor::kOne; + bool rt0_a_premult = + pixel_shader_modification.pixel.rt0_blend_a_factor_for_premult != + xenos::BlendFactor::kOne; + uint32_t color_rts_remaining = color_rts_used; uint32_t color_rt_index; while (xe::bit_scan_forward(color_rts_remaining, &color_rt_index)) { @@ -2410,6 +2452,18 @@ bool VulkanPipelineCache::EnsurePipelineCreated( kBlendFactorMap[uint32_t(color_rt.dst_alpha_blend_factor)]; color_blend_attachment.alphaBlendOp = kBlendOpMap[uint32_t(color_rt.alpha_blend_op)]; + + // If the shader pre-multiplies by the source blend factor for RT0 + // MIN/MAX, set the pipeline source factor to ONE since it's already + // applied in the shader. + if (color_rt_index == 0) { + if (rt0_rgb_premult) { + color_blend_attachment.srcColorBlendFactor = VK_BLEND_FACTOR_ONE; + } + if (rt0_a_premult) { + color_blend_attachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE; + } + } } color_blend_attachment.colorWriteMask = VkColorComponentFlags(color_rt.color_write_mask);