From 32db6c18779a3e69b065ecbd68be2b61cb2a9c4d Mon Sep 17 00:00:00 2001 From: xbzk Date: Thu, 13 Nov 2025 14:01:32 +0100 Subject: [PATCH] [renderer] NG ragebound workaround via LoadOverrides + vk_rasterizer UpdateBlending TouchBlendequations Rework (#2934) this pull should impact ninja gaiden ragebound only! it makes it playable past stage 4-1. it contains a workaround for missing maxwell_3d's iterated_blend functionality, which fixes several graphics all over the game. the issue causes transparency enabled blends (mostly lighting fx) to be wrongly blended into destination, turning textures into black frames. in stage 4-1 there are lighthing layers in the foreground, causing sprites layer to become overlapped by these opaque black frames, including entire screen in a mid boss fight, making it unplayable* (players maneuvered by turning immortal option on and swinging sword all around until defeating it). also only in stage 4-1 the fix has a short drawback: when you buff up next attack these problematique blends will be drawn back as black frames, but only for a split second, so no big deal. this workaround was already discovered and available in PR 302, but in an unconventional way for a game specific override, so we did forbidden it. now it uses classic game specific override solution exampled in core.cpp's System::Impl::LoadOverrides method, so now i guess it's worth to merge it and deliver this to players until we harness iterated_blend control. additionally I've slightly reworked vk_rasterizer.cpp's RasterizerVulkan::UpdateBlending, if (state_tracker.TouchBlendEquations()) {...} session. it was made in a way that for a single blend, it exhaustly calls 48 (6 x 8) MaxwellToVK redundant functions, and declared a lambda function inside a 8 laps loop. reworked it so that instead of 48 calls it makes only the necessary 6 calls, and then merely safely copy the result for the other 7 times. Co-authored-by: Allison Cunha Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/2934 Reviewed-by: MaranBr Reviewed-by: Shinmegumi Reviewed-by: Maufeat Co-authored-by: xbzk Co-committed-by: xbzk --- src/common/settings.cpp | 3 ++ src/common/settings.h | 3 ++ src/core/core.cpp | 13 +++++ .../renderer_opengl/gl_rasterizer.cpp | 8 ++++ .../renderer_vulkan/fixed_pipeline_state.cpp | 14 ++++++ .../renderer_vulkan/vk_rasterizer.cpp | 47 +++++++++++++------ 6 files changed, 74 insertions(+), 14 deletions(-) diff --git a/src/common/settings.cpp b/src/common/settings.cpp index 6800c20f69..56b65c527c 100644 --- a/src/common/settings.cpp +++ b/src/common/settings.cpp @@ -359,6 +359,9 @@ void RestoreGlobalState(bool is_powered_on) { for (const auto& reset : values.linkage.restore_functions) { reset(); } + + // Reset per-game flags + values.use_squashed_iterated_blend = false; } static bool configuring_global = true; diff --git a/src/common/settings.h b/src/common/settings.h index 360a49c6c6..2e16e4bc59 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -759,6 +759,9 @@ struct Values { // Add-Ons std::map> disabled_addons; + + // Per-game overrides + bool use_squashed_iterated_blend; }; extern Values values; diff --git a/src/core/core.cpp b/src/core/core.cpp index 7315f35e0c..6c321afdbb 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -297,6 +297,9 @@ struct System::Impl { std::string vendor = gpu_core->Renderer().GetDeviceVendor(); LOG_INFO(Core, "GPU Vendor: {}", vendor); + // Reset all per-game flags + Settings::values.use_squashed_iterated_blend = false; + // Insert PC overrides here #ifdef ANDROID @@ -322,6 +325,13 @@ struct System::Impl { #endif + // Ninja Gaiden Ragebound + constexpr u64 ngr = 0x0100781020710000ULL; + + if (programId == ngr) { + LOG_INFO(Core, "Enabling game specifc override: use_squashed_iterated_blend"); + Settings::values.use_squashed_iterated_blend = true; + } } SystemResultStatus Load(System& system, Frontend::EmuWindow& emu_window, @@ -425,6 +435,9 @@ struct System::Impl { void ShutdownMainProcess() { SetShuttingDown(true); + // Reset per-game flags + Settings::values.use_squashed_iterated_blend = false; + is_powered_on = false; exit_locked = false; exit_requested = false; diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index 718958fc11..6f25267a8f 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -1142,6 +1142,14 @@ void RasterizerOpenGL::SyncBlendState() { glDisable(GL_BLEND); return; } + // Temporary workaround for games that use iterated blending + if (regs.iterated_blend.enable && Settings::values.use_squashed_iterated_blend) { + glEnable(GL_BLEND); + glBlendFuncSeparate(GL_ONE, GL_ONE, GL_ONE_MINUS_SRC_COLOR, GL_ZERO); + glBlendEquationSeparate(GL_FUNC_ADD, GL_FUNC_ADD); + return; + } + glEnable(GL_BLEND); glBlendFuncSeparate(MaxwellToGL::BlendFunc(regs.blend.color_source), MaxwellToGL::BlendFunc(regs.blend.color_dest), diff --git a/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp b/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp index 453a3d942b..e643e98ead 100644 --- a/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp +++ b/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp @@ -11,6 +11,7 @@ #include #include "common/cityhash.h" #include "common/common_types.h" +#include "common/settings.h" #include "video_core/engines/draw_manager.h" #include "video_core/renderer_vulkan/fixed_pipeline_state.h" #include "video_core/renderer_vulkan/vk_state_tracker.h" @@ -201,6 +202,19 @@ void FixedPipelineState::BlendingAttachment::Refresh(const Maxwell& regs, size_t }; if (!regs.blend_per_target_enabled) { + // Temporary workaround for games that use iterated blending + // even when dynamic blending is off so overrides work with EDS = 0 as well + if (regs.iterated_blend.enable && Settings::values.use_squashed_iterated_blend) { + equation_rgb.Assign(PackBlendEquation(Maxwell::Blend::Equation::Add_GL)); + equation_a.Assign(PackBlendEquation(Maxwell::Blend::Equation::Add_GL)); + factor_source_rgb.Assign(PackBlendFactor(Maxwell::Blend::Factor::One_GL)); + factor_dest_rgb.Assign(PackBlendFactor(Maxwell::Blend::Factor::One_GL)); + factor_source_a.Assign( + PackBlendFactor(Maxwell::Blend::Factor::OneMinusSourceColor_GL)); + factor_dest_a.Assign(PackBlendFactor(Maxwell::Blend::Factor::Zero_GL)); + enable.Assign(1); + return; + } setup_blend(regs.blend); return; } diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index 530c7e8e41..134327fa8d 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -1538,22 +1538,41 @@ void RasterizerVulkan::UpdateBlending(Tegra::Engines::Maxwell3D::Regs& regs) { if (state_tracker.TouchBlendEquations()) { std::array setup_blends{}; - for (size_t index = 0; index < Maxwell::NumRenderTargets; index++) { - const auto blend_setup = [&](const T& guest_blend) { - auto& host_blend = setup_blends[index]; - host_blend.srcColorBlendFactor = MaxwellToVK::BlendFactor(guest_blend.color_source); - host_blend.dstColorBlendFactor = MaxwellToVK::BlendFactor(guest_blend.color_dest); - host_blend.colorBlendOp = MaxwellToVK::BlendEquation(guest_blend.color_op); - host_blend.srcAlphaBlendFactor = MaxwellToVK::BlendFactor(guest_blend.alpha_source); - host_blend.dstAlphaBlendFactor = MaxwellToVK::BlendFactor(guest_blend.alpha_dest); - host_blend.alphaBlendOp = MaxwellToVK::BlendEquation(guest_blend.alpha_op); - }; - if (!regs.blend_per_target_enabled) { - blend_setup(regs.blend); - continue; + + const auto blend_setup = [&](auto& host_blend, const auto& guest_blend) { + host_blend.srcColorBlendFactor = MaxwellToVK::BlendFactor(guest_blend.color_source); + host_blend.dstColorBlendFactor = MaxwellToVK::BlendFactor(guest_blend.color_dest); + host_blend.colorBlendOp = MaxwellToVK::BlendEquation(guest_blend.color_op); + host_blend.srcAlphaBlendFactor = MaxwellToVK::BlendFactor(guest_blend.alpha_source); + host_blend.dstAlphaBlendFactor = MaxwellToVK::BlendFactor(guest_blend.alpha_dest); + host_blend.alphaBlendOp = MaxwellToVK::BlendEquation(guest_blend.alpha_op); + }; + + // Single blend equation for all targets + if (!regs.blend_per_target_enabled) { + // Temporary workaround for games that use iterated blending + if (regs.iterated_blend.enable && Settings::values.use_squashed_iterated_blend) { + setup_blends[0].srcColorBlendFactor = VK_BLEND_FACTOR_ONE; + setup_blends[0].dstColorBlendFactor = VK_BLEND_FACTOR_ONE; + setup_blends[0].colorBlendOp = VK_BLEND_OP_ADD; + setup_blends[0].srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_COLOR; + setup_blends[0].dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO; + setup_blends[0].alphaBlendOp = VK_BLEND_OP_ADD; + } else { + blend_setup(setup_blends[0], regs.blend); + } + + // Copy first blend state to all other targets + for (size_t index = 1; index < Maxwell::NumRenderTargets; index++) { + setup_blends[index] = setup_blends[0]; + } + } else { + // Per-target blending + for (size_t index = 0; index < Maxwell::NumRenderTargets; index++) { + blend_setup(setup_blends[index], regs.blend_per_target[index]); } - blend_setup(regs.blend_per_target[index]); } + scheduler.Record([setup_blends](vk::CommandBuffer cmdbuf) { cmdbuf.SetColorBlendEquationEXT(0, setup_blends); });