video_core: fix color blend min/max mode in OpenGL (#2038)

* video_core: fix check for fragment color blend emulation

* video_core: Fix typo in gl fragment shader gen
This commit is contained in:
PabloMK7 2026-04-14 19:26:22 +02:00 committed by GitHub
parent 1edc5de18e
commit f1cd5f5ff4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 43 additions and 36 deletions

View File

@ -895,7 +895,11 @@ void FragmentModule::WriteLogicOp() {
}
void FragmentModule::WriteBlending() {
if (!config.EmulateBlend() || profile.is_vulkan) [[likely]] {
bool requires_rgb_minmax_emulation =
config.framebuffer.requested_rgb_blend.RequiresMinMaxEmulation();
bool requires_alpha_minmax_emulation =
config.framebuffer.requested_alpha_blend.RequiresMinMaxEmulation();
if (!requires_rgb_minmax_emulation && !requires_alpha_minmax_emulation) [[likely]] {
return;
}
@ -937,23 +941,25 @@ void FragmentModule::WriteBlending() {
return "vec4(1.f)";
}
};
// At this point, the blend equation can only be min or max.
const auto get_func = [](Pica::FramebufferRegs::BlendEquation eq) {
return eq == Pica::FramebufferRegs::BlendEquation::Min ? "min" : "max";
};
if (config.framebuffer.rgb_blend.eq != Pica::FramebufferRegs::BlendEquation::Add) {
if (requires_rgb_minmax_emulation) {
out += fmt::format(
"combiner_output.rgb = {}(source_color.rgb * ({}).rgb, dest_color.rgb * ({}).rgb);\n",
get_func(config.framebuffer.rgb_blend.eq),
get_factor(config.framebuffer.rgb_blend.src_factor),
get_factor(config.framebuffer.rgb_blend.dst_factor));
get_func(config.framebuffer.requested_rgb_blend.eq),
get_factor(config.framebuffer.requested_rgb_blend.src_factor),
get_factor(config.framebuffer.requested_rgb_blend.dst_factor));
}
if (config.framebuffer.alpha_blend.eq != Pica::FramebufferRegs::BlendEquation::Add) {
if (requires_alpha_minmax_emulation) {
out +=
fmt::format("combiner_output.a = {}(source_color.a * ({}).a, dest_color.a * ({}).a);\n",
get_func(config.framebuffer.alpha_blend.eq),
get_factor(config.framebuffer.alpha_blend.src_factor),
get_factor(config.framebuffer.alpha_blend.dst_factor));
get_func(config.framebuffer.requested_alpha_blend.eq),
get_factor(config.framebuffer.requested_alpha_blend.src_factor),
get_factor(config.framebuffer.requested_alpha_blend.dst_factor));
}
}
@ -1239,7 +1245,8 @@ void FragmentModule::DefineExtensions() {
use_fragment_shader_barycentric = false;
}
}
if (config.EmulateBlend() && !profile.is_vulkan) {
if (config.framebuffer.requested_rgb_blend.RequiresMinMaxEmulation() ||
config.framebuffer.requested_alpha_blend.RequiresMinMaxEmulation()) [[unlikely]] {
if (profile.has_gl_ext_framebuffer_fetch) {
out += "#extension GL_EXT_shader_framebuffer_fetch : enable\n";
out += "#define destFactor color\n";
@ -1338,7 +1345,7 @@ void FragmentModule::DefineBindingsGL() {
out += "layout(binding = 6) uniform sampler2D tex_normal;\n";
}
if (use_blend_fallback) {
out += "layout(location = 7) uniform sampler2D tex_color;\n";
out += "layout(binding = 7) uniform sampler2D tex_color;\n";
}
// Shadow textures

View File

@ -21,13 +21,13 @@ FramebufferConfig::FramebufferConfig(const Pica::RegsInternal& regs) {
logic_op.Assign(Pica::FramebufferRegs::LogicOp::Copy);
if (alphablend_enable) {
rgb_blend.eq = output_merger.alpha_blending.blend_equation_rgb.Value();
rgb_blend.src_factor = output_merger.alpha_blending.factor_source_rgb;
rgb_blend.dst_factor = output_merger.alpha_blending.factor_dest_rgb;
requested_rgb_blend.eq = output_merger.alpha_blending.blend_equation_rgb.Value();
requested_rgb_blend.src_factor = output_merger.alpha_blending.factor_source_rgb;
requested_rgb_blend.dst_factor = output_merger.alpha_blending.factor_dest_rgb;
alpha_blend.eq = output_merger.alpha_blending.blend_equation_a.Value();
alpha_blend.src_factor = output_merger.alpha_blending.factor_source_a;
alpha_blend.dst_factor = output_merger.alpha_blending.factor_dest_a;
requested_alpha_blend.eq = output_merger.alpha_blending.blend_equation_a.Value();
requested_alpha_blend.src_factor = output_merger.alpha_blending.factor_source_a;
requested_alpha_blend.dst_factor = output_merger.alpha_blending.factor_dest_a;
}
}
@ -37,17 +37,10 @@ void FramebufferConfig::ApplyProfile(const Profile& profile) {
logic_op.Assign(requested_logic_op);
}
// Min/max blend emulation
if (!profile.has_blend_minmax_factor && alphablend_enable) {
if (rgb_blend.eq != Pica::FramebufferRegs::BlendEquation::Min &&
rgb_blend.eq != Pica::FramebufferRegs::BlendEquation::Max) {
rgb_blend = {};
}
if (alpha_blend.eq != Pica::FramebufferRegs::BlendEquation::Min &&
alpha_blend.eq != Pica::FramebufferRegs::BlendEquation::Max) {
alpha_blend = {};
}
// Check if we don't need blend min/max emulation.
if ((profile.has_blend_minmax_factor || profile.is_vulkan) && alphablend_enable) {
requested_rgb_blend.SetMinMaxEmulationDisabled();
requested_alpha_blend.SetMinMaxEmulationDisabled();
}
}

View File

@ -43,6 +43,17 @@ struct BlendConfig {
// fields
FIELD_HASH(eq), FIELD_HASH(src_factor), FIELD_HASH(dst_factor));
}
void SetMinMaxEmulationDisabled() {
// If we don't need min/max emulation, set the blend equation
// to "-1" as a clear marker that this config is disabled.
eq = static_cast<Pica::FramebufferRegs::BlendEquation>(UINT32_MAX);
}
bool RequiresMinMaxEmulation() {
return eq == Pica::FramebufferRegs::BlendEquation::Min ||
eq == Pica::FramebufferRegs::BlendEquation::Max;
}
};
static_assert(std::has_unique_object_representations_v<BlendConfig>);
@ -58,8 +69,8 @@ struct FramebufferConfig {
BitField<10, 1, u32> shadow_rendering;
BitField<11, 1, u32> alphablend_enable;
};
BlendConfig rgb_blend{};
BlendConfig alpha_blend{};
BlendConfig requested_rgb_blend{};
BlendConfig requested_alpha_blend{};
Pica::FramebufferRegs::LogicOp requested_logic_op{};
@ -78,7 +89,8 @@ struct FramebufferConfig {
// fields
FIELD_HASH(alpha_test_func), FIELD_HASH(scissor_test_mode), FIELD_HASH(depthmap_enable),
FIELD_HASH(logic_op), FIELD_HASH(shadow_rendering), FIELD_HASH(alphablend_enable),
FIELD_HASH(rgb_blend), FIELD_HASH(alpha_blend), FIELD_HASH(requested_logic_op),
FIELD_HASH(requested_rgb_blend), FIELD_HASH(requested_alpha_blend),
FIELD_HASH(requested_logic_op),
// nested layout
BlendConfig::StructHash());
@ -387,11 +399,6 @@ struct FSConfig {
return (stage_index < 4) && ((texture.combiner_buffer_input >> 4) & (1 << stage_index));
}
[[nodiscard]] bool EmulateBlend() const {
return framebuffer.rgb_blend.eq != Pica::FramebufferRegs::BlendEquation::Add ||
framebuffer.alpha_blend.eq != Pica::FramebufferRegs::BlendEquation::Add;
}
[[nodiscard]] bool UsesSpirvIncompatibleConfig() const {
const auto texture0_type = texture.texture0_type.Value();
return texture0_type == Pica::TexturingRegs::TextureConfig::ShadowCube ||