[GPU] Fix min/max blend operations to properly apply factors

Applies factors before the MIN/MAX operation - in FSI/ROV via runtime
blending, in FBO/RTV by pre-multiplying shader output when dstFactor is
ONE only, as we don't have access to dest to provide full support as we
do in the FSI/ROV paths.
This commit is contained in:
Herman S.
2025-12-16 23:54:09 +09:00
parent 966d8f0925
commit 067641668f
6 changed files with 716 additions and 164 deletions

View File

@@ -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.