From 07bdb3454a6b866aff97cb05fc9bb3a5c4b77586 Mon Sep 17 00:00:00 2001 From: Crementif <26669564+Crementif@users.noreply.github.com> Date: Sun, 1 Feb 2026 01:53:02 +0100 Subject: [PATCH 1/8] debugger: Various QoL improvements (#1777) * Ignore keyboard inputs when debugger is focused * Add workaround for slightly improved multi-thread debugging * debugger: Add interpreter mode switch and log-only memory breakpoints --- src/Cafe/HW/Espresso/Debugger/Debugger.cpp | 25 ++++++++++++++- src/Cafe/HW/Espresso/Debugger/Debugger.h | 1 + src/gui/interface/WindowSystem.h | 1 + src/gui/wxgui/CemuApp.cpp | 25 +++++++++++++++ src/gui/wxgui/debugger/DebuggerWindow2.cpp | 32 ++++++++++++++++++- src/gui/wxgui/debugger/DebuggerWindow2.h | 8 +++-- src/input/api/Keyboard/KeyboardController.cpp | 4 +++ 7 files changed, 92 insertions(+), 4 deletions(-) diff --git a/src/Cafe/HW/Espresso/Debugger/Debugger.cpp b/src/Cafe/HW/Espresso/Debugger/Debugger.cpp index fbfc94fe..02f23497 100644 --- a/src/Cafe/HW/Espresso/Debugger/Debugger.cpp +++ b/src/Cafe/HW/Espresso/Debugger/Debugger.cpp @@ -208,7 +208,24 @@ void debugger_handleSingleStepException(uint64 dr6) if (catchBP) { PPCInterpreter_t* hCPU = PPCInterpreter_getCurrentInstance(); - debugger_createCodeBreakpoint(hCPU->instructionPointer + 4, DEBUGGER_BP_T_ONE_SHOT); + if (debuggerState.logOnlyMemoryBreakpoints) + { + float memValueF = memory_readFloat(debuggerState.activeMemoryBreakpoint->address); + uint32 memValue = memory_readU32(debuggerState.activeMemoryBreakpoint->address); + cemuLog_log(LogType::Force, "[Debugger] 0x{:08X} was read/written! New Value: 0x{:08X} (float {}) IP: {:08X} LR: {:08X}", + debuggerState.activeMemoryBreakpoint->address, + memValue, + memValueF, + hCPU->instructionPointer, + hCPU->spr.LR + ); + if (cemuLog_advancedPPCLoggingEnabled()) + DebugLogStackTrace(coreinit::OSGetCurrentThread(), hCPU->gpr[1]); + } + else + { + debugger_createCodeBreakpoint(hCPU->instructionPointer + 4, DEBUGGER_BP_T_ONE_SHOT); + } } } @@ -543,6 +560,12 @@ void debugger_createPPCStateSnapshot(PPCInterpreter_t* hCPU) void debugger_enterTW(PPCInterpreter_t* hCPU) { + // Currently, we don't support multiple threads inside the debugger. Spin loop a thread if we already paused for another breakpoint hit. + while (debuggerState.debugSession.isTrapped) + { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + // handle logging points DebuggerBreakpoint* bp = debugger_getFirstBP(hCPU->instructionPointer); bool shouldBreak = debuggerBPChain_hasType(bp, DEBUGGER_BP_T_NORMAL) || debuggerBPChain_hasType(bp, DEBUGGER_BP_T_ONE_SHOT); diff --git a/src/Cafe/HW/Espresso/Debugger/Debugger.h b/src/Cafe/HW/Espresso/Debugger/Debugger.h index 8b09477b..03602ff0 100644 --- a/src/Cafe/HW/Espresso/Debugger/Debugger.h +++ b/src/Cafe/HW/Espresso/Debugger/Debugger.h @@ -142,6 +142,7 @@ struct PPCSnapshot typedef struct { bool breakOnEntry; + bool logOnlyMemoryBreakpoints; // breakpoints std::vector breakpoints; std::vector patches; diff --git a/src/gui/interface/WindowSystem.h b/src/gui/interface/WindowSystem.h index dc614691..89e35ee6 100644 --- a/src/gui/interface/WindowSystem.h +++ b/src/gui/interface/WindowSystem.h @@ -44,6 +44,7 @@ namespace WindowSystem std::atomic_int32_t restored_pad_width = -1, restored_pad_height = -1; std::atomic_bool is_fullscreen; + std::atomic_bool debugger_focused; void set_keystate(uint32 keycode, bool state) { diff --git a/src/gui/wxgui/CemuApp.cpp b/src/gui/wxgui/CemuApp.cpp index 1c803bf6..a900f10b 100644 --- a/src/gui/wxgui/CemuApp.cpp +++ b/src/gui/wxgui/CemuApp.cpp @@ -12,6 +12,7 @@ #include "wxgui/helpers/wxHelpers.h" #include "Cemu/ncrypto/ncrypto.h" #include "wxgui/input/HotkeySettings.h" +#include "wxgui/debugger/DebuggerWindow2.h" #include #if ( BOOST_OS_LINUX || BOOST_OS_BSD ) && HAS_WAYLAND @@ -434,6 +435,30 @@ int CemuApp::FilterEvent(wxEvent& event) g_window_info.set_keystatesup(); } + // track if debugger window or its child windows are focused + if (g_debugger_window && (event.GetEventType() == wxEVT_SET_FOCUS || event.GetEventType() == wxEVT_ACTIVATE)) + { + wxWindow* target_window = wxDynamicCast(event.GetEventObject(), wxWindow); + + if (target_window && event.GetEventType() == wxEVT_ACTIVATE && !((wxActivateEvent&)event).GetActive()) + target_window = nullptr; + + if (target_window) + { + g_window_info.debugger_focused = false; + wxWindow* window_it = target_window; + while (window_it) + { + if (window_it == g_debugger_window) g_window_info.debugger_focused = true; + window_it = window_it->GetParent(); + } + } + } + else if (!g_debugger_window) + { + g_window_info.debugger_focused = false; + } + return wxApp::FilterEvent(event); } diff --git a/src/gui/wxgui/debugger/DebuggerWindow2.cpp b/src/gui/wxgui/debugger/DebuggerWindow2.cpp index 0c3cdb47..4422b61a 100644 --- a/src/gui/wxgui/debugger/DebuggerWindow2.cpp +++ b/src/gui/wxgui/debugger/DebuggerWindow2.cpp @@ -20,6 +20,8 @@ #include "wxgui/debugger/BreakpointWindow.h" #include "wxgui/debugger/ModuleWindow.h" #include "util/helpers/helpers.h" +#include "Cafe/HW/Espresso/Recompiler/PPCRecompiler.h" +#include "Cemu/Logging/CemuLogging.h" #include "resource/embedded/resources.h" @@ -30,6 +32,8 @@ enum // settings MENU_ID_OPTIONS_PIN_TO_MAINWINDOW, MENU_ID_OPTIONS_BREAK_ON_START, + MENU_ID_OPTIONS_LOG_MEMORY_BREAKPOINTS, + MENU_ID_OPTIONS_SWITCH_CPU_MODE, // window MENU_ID_WINDOW_REGISTERS, MENU_ID_WINDOW_DUMP, @@ -75,12 +79,14 @@ wxBEGIN_EVENT_TABLE(DebuggerWindow2, wxFrame) EVT_MENU_RANGE(MENU_ID_WINDOW_REGISTERS, MENU_ID_WINDOW_MODULE, DebuggerWindow2::OnWindowMenu) wxEND_EVENT_TABLE() + DebuggerWindow2* g_debugger_window; void DebuggerConfig::Load(XMLConfigParser& parser) { pin_to_main = parser.get("PinToMainWindow", true); break_on_start = parser.get("break_on_start", true); + log_memory_breakpoints = parser.get("log_memory_breakpoints", false); auto window_parser = parser.get("Windows"); show_register = window_parser.get("Registers", true); @@ -95,7 +101,8 @@ void DebuggerConfig::Save(XMLConfigParser& parser) { parser.set("PinToMainWindow", pin_to_main); parser.set("break_on_start", break_on_start); - + parser.set("log_memory_breakpoints", log_memory_breakpoints); + auto window_parser = parser.set("Windows"); window_parser.set("Registers", show_register); window_parser.set("MemoryDump", show_dump); @@ -285,6 +292,7 @@ DebuggerWindow2::DebuggerWindow2(wxFrame& parent, const wxRect& display_size) m_config.Load(); debuggerState.breakOnEntry = m_config.data().break_on_start; + debuggerState.logOnlyMemoryBreakpoints = m_config.data().log_memory_breakpoints; m_main_position = parent.GetPosition(); m_main_size = parent.GetSize(); @@ -571,6 +579,26 @@ void DebuggerWindow2::OnOptionsInput(wxCommandEvent& event) debuggerState.breakOnEntry = value; break; } + case MENU_ID_OPTIONS_LOG_MEMORY_BREAKPOINTS: + { + const bool value = !m_config.data().log_memory_breakpoints; + m_config.data().log_memory_breakpoints = value; + debuggerState.logOnlyMemoryBreakpoints = value; + break; + } + case MENU_ID_OPTIONS_SWITCH_CPU_MODE: + { + if (ppcRecompilerEnabled) + { + ppcRecompilerEnabled = false; + cemuLog_log(LogType::Force, "Debugger: Switched CPU mode to interpreter"); + } + else { + ppcRecompilerEnabled = true; + cemuLog_log(LogType::Force, "Debugger: Switched CPU mode to recompiler"); + } + break; + } default: return; } @@ -662,6 +690,8 @@ void DebuggerWindow2::CreateMenuBar() wxMenu* options_menu = new wxMenu; options_menu->Append(MENU_ID_OPTIONS_PIN_TO_MAINWINDOW, _("&Pin to main window"), wxEmptyString, wxITEM_CHECK)->Check(m_config.data().pin_to_main); options_menu->Append(MENU_ID_OPTIONS_BREAK_ON_START, _("Break on &entry point"), wxEmptyString, wxITEM_CHECK)->Check(m_config.data().break_on_start); + options_menu->Append(MENU_ID_OPTIONS_LOG_MEMORY_BREAKPOINTS, _("Log only memory breakpoints"), wxEmptyString, wxITEM_CHECK)->Check(m_config.data().log_memory_breakpoints); + options_menu->Append(MENU_ID_OPTIONS_SWITCH_CPU_MODE, _("Switch to &interpreter CPU mode"), wxEmptyString, wxITEM_CHECK); menu_bar->Append(options_menu, _("&Options")); // window diff --git a/src/gui/wxgui/debugger/DebuggerWindow2.h b/src/gui/wxgui/debugger/DebuggerWindow2.h index b06601ba..b6395e06 100644 --- a/src/gui/wxgui/debugger/DebuggerWindow2.h +++ b/src/gui/wxgui/debugger/DebuggerWindow2.h @@ -26,13 +26,16 @@ wxDECLARE_EVENT(wxEVT_NOTIFY_MODULE_LOADED, wxCommandEvent); wxDECLARE_EVENT(wxEVT_NOTIFY_MODULE_UNLOADED, wxCommandEvent); wxDECLARE_EVENT(wxEVT_NOTIFY_GRAPHIC_PACKS_MODIFIED, wxCommandEvent); +extern class DebuggerWindow2* g_debugger_window; + struct DebuggerConfig { DebuggerConfig() - : pin_to_main(true), break_on_start(true), show_register(true), show_dump(true), show_stack(true), show_breakpoints(true), show_modules(true), show_symbols(true) {} - + : pin_to_main(true), break_on_start(true), log_memory_breakpoints(false), show_register(true), show_dump(true), show_stack(true), show_breakpoints(true), show_modules(true), show_symbols(true) {} + bool pin_to_main; bool break_on_start; + bool log_memory_breakpoints; bool show_register; bool show_dump; @@ -82,6 +85,7 @@ public: bool Show(bool show = true) override; std::wstring GetModuleStoragePath(std::string module_name, uint32_t crc_hash) const; + private: void OnBreakpointHit(wxCommandEvent& event); void OnRunProgram(wxCommandEvent& event); diff --git a/src/input/api/Keyboard/KeyboardController.cpp b/src/input/api/Keyboard/KeyboardController.cpp index fedabb9d..0514cd5d 100644 --- a/src/input/api/Keyboard/KeyboardController.cpp +++ b/src/input/api/Keyboard/KeyboardController.cpp @@ -17,6 +17,10 @@ std::string KeyboardController::get_button_name(uint64 button) const ControllerState KeyboardController::raw_state() { ControllerState result{}; + + if (WindowSystem::GetWindowInfo().debugger_focused) + return result; + boost::container::small_vector pressedKeys; WindowSystem::GetWindowInfo().iter_keystates([&pressedKeys](const std::pair& keyState) { if (keyState.second) pressedKeys.emplace_back(keyState.first); }); result.buttons.SetPressedButtons(pressedKeys); From 4ad3f2104712e635cac3da6978e4fd0ba01494a5 Mon Sep 17 00:00:00 2001 From: shinra-electric <50119606+shinra-electric@users.noreply.github.com> Date: Sun, 1 Feb 2026 00:54:04 +0000 Subject: [PATCH 2/8] macOS: Bump macOS min version to 13.4 (#1798) --- src/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4fe4b07e..a4f0d19e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -86,7 +86,7 @@ if (MACOS_BUNDLE) set(MACOSX_BUNDLE_COPYRIGHT "Copyright © 2024 Cemu Project") set(MACOSX_BUNDLE_CATEGORY "public.app-category.games") - set(MACOSX_MINIMUM_SYSTEM_VERSION "12.0") + set(MACOSX_MINIMUM_SYSTEM_VERSION "13.4") set(MACOSX_BUNDLE_TYPE_EXTENSION "wua") set_target_properties(CemuBin PROPERTIES From ba82dc57d64fe8fc1b268e24fa583b350404b9a6 Mon Sep 17 00:00:00 2001 From: goeiecool9999 <7033575+goeiecool9999@users.noreply.github.com> Date: Sun, 1 Feb 2026 02:06:05 +0100 Subject: [PATCH 3/8] OpenGL+Vulkan: Use unified uniform block layout for output shaders (#1785) --- .../LegacyShaderDecompiler/LatteDecompiler.h | 2 +- .../LatteDecompilerEmitGLSLHeader.hpp | 2 +- .../LatteDecompilerEmitMSLHeader.hpp | 2 +- .../Renderer/Metal/RendererShaderMtl.cpp | 26 ---- .../Latte/Renderer/Metal/RendererShaderMtl.h | 6 - .../Latte/Renderer/OpenGL/OpenGLRenderer.cpp | 13 +- .../HW/Latte/Renderer/OpenGL/OpenGLRenderer.h | 3 + .../OpenGL/OpenGLRendererUniformData.cpp | 3 +- .../Latte/Renderer/OpenGL/RendererShaderGL.h | 10 +- .../HW/Latte/Renderer/RendererOuputShader.cpp | 87 +++---------- .../HW/Latte/Renderer/RendererOuputShader.h | 20 +-- src/Cafe/HW/Latte/Renderer/RendererShader.h | 6 - .../Renderer/Vulkan/RendererShaderVk.cpp | 26 ---- .../Latte/Renderer/Vulkan/RendererShaderVk.h | 5 - .../Latte/Renderer/Vulkan/VulkanRenderer.cpp | 87 ++++++------- .../HW/Latte/Renderer/Vulkan/VulkanRenderer.h | 1 + .../Renderer/Vulkan/VulkanRendererCore.cpp | 123 +++++++++--------- 17 files changed, 155 insertions(+), 267 deletions(-) diff --git a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.h b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.h index 475bacb0..b370123e 100644 --- a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.h +++ b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.h @@ -219,7 +219,7 @@ struct LatteDecompilerShader float ufCurrentValueFragCoordScale[2]; sint32 loc_verticesPerInstance; sint32 loc_streamoutBufferBase[LATTE_NUM_STREAMOUT_BUFFER]; - sint32 uniformRangeSize; // entire size of uniform variable block + uint32 uniformRangeSize; // entire size of uniform variable block }uniform{ 0 }; // fast access struct _RemappedUniformBufferGroup diff --git a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitGLSLHeader.hpp b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitGLSLHeader.hpp index 07b1fd34..add25195 100644 --- a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitGLSLHeader.hpp +++ b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitGLSLHeader.hpp @@ -16,7 +16,7 @@ namespace LatteDecompiler } } - sint32 uniformCurrentOffset = 0; + uint32 uniformCurrentOffset = 0; auto shader = decompilerContext->shader; auto shaderType = decompilerContext->shader->shaderType; auto shaderSrc = decompilerContext->shaderSource; diff --git a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitMSLHeader.hpp b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitMSLHeader.hpp index f61abcb1..ac48f1f7 100644 --- a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitMSLHeader.hpp +++ b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitMSLHeader.hpp @@ -14,7 +14,7 @@ namespace LatteDecompiler src->add("struct SupportBuffer {" _CRLF); - sint32 uniformCurrentOffset = 0; + uint32 uniformCurrentOffset = 0; auto shader = decompilerContext->shader; auto shaderType = decompilerContext->shader->shaderType; if (decompilerContext->shader->uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_REMAPPED) diff --git a/src/Cafe/HW/Latte/Renderer/Metal/RendererShaderMtl.cpp b/src/Cafe/HW/Latte/Renderer/Metal/RendererShaderMtl.cpp index a05bcf03..f00af85a 100644 --- a/src/Cafe/HW/Latte/Renderer/Metal/RendererShaderMtl.cpp +++ b/src/Cafe/HW/Latte/Renderer/Metal/RendererShaderMtl.cpp @@ -231,32 +231,6 @@ RendererShaderMtl::~RendererShaderMtl() m_function->release(); } -sint32 RendererShaderMtl::GetUniformLocation(const char* name) -{ - cemu_assert_suspicious(); - return 0; -} - -void RendererShaderMtl::SetUniform1i(sint32 location, sint32 value) -{ - cemu_assert_suspicious(); -} - -void RendererShaderMtl::SetUniform1f(sint32 location, float value) -{ - cemu_assert_suspicious(); -} - -void RendererShaderMtl::SetUniform2fv(sint32 location, void* data, sint32 count) -{ - cemu_assert_suspicious(); -} - -void RendererShaderMtl::SetUniform4iv(sint32 location, void* data, sint32 count) -{ - cemu_assert_suspicious(); -} - void RendererShaderMtl::PreponeCompilation(bool isRenderThread) { shaderMtlThreadPool.s_compilationQueueMutex.lock(); diff --git a/src/Cafe/HW/Latte/Renderer/Metal/RendererShaderMtl.h b/src/Cafe/HW/Latte/Renderer/Metal/RendererShaderMtl.h index 9527dc8a..1a0c33a9 100644 --- a/src/Cafe/HW/Latte/Renderer/Metal/RendererShaderMtl.h +++ b/src/Cafe/HW/Latte/Renderer/Metal/RendererShaderMtl.h @@ -36,12 +36,6 @@ public: return m_function; } - sint32 GetUniformLocation(const char* name) override; - void SetUniform1i(sint32 location, sint32 value) override; - void SetUniform1f(sint32 location, float value) override; - void SetUniform2fv(sint32 location, void* data, sint32 count) override; - void SetUniform4iv(sint32 location, void* data, sint32 count) override; - void PreponeCompilation(bool isRenderThread) override; bool IsCompiled() override; bool WaitForCompiled() override; diff --git a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.cpp b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.cpp index 76f67692..a4d1966d 100644 --- a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.cpp @@ -145,6 +145,8 @@ OpenGLRenderer::~OpenGLRenderer() { if(m_pipeline != 0) glDeleteProgramPipelines(1, &m_pipeline); + + glDeleteBuffers(1, &m_backbufferBlit_uniformBuffer); } OpenGLRenderer* OpenGLRenderer::GetInstance() @@ -371,6 +373,10 @@ void OpenGLRenderer::Initialize() glBindFramebuffer(GL_FRAMEBUFFER_EXT, 0); } + // create uniform buffers for backbufferblit + glCreateBuffers(1, &m_backbufferBlit_uniformBuffer); + glNamedBufferStorage(m_backbufferBlit_uniformBuffer, sizeof(RendererOutputShader::OutputUniformVariables), nullptr, GL_DYNAMIC_STORAGE_BIT); + draw_init(); catchOpenGLError(); @@ -603,7 +609,12 @@ void OpenGLRenderer::DrawBackbufferQuad(LatteTextureView* texView, RendererOutpu shader_unbind(RendererShader::ShaderType::kGeometry); shader_bind(shader->GetVertexShader()); shader_bind(shader->GetFragmentShader()); - shader->SetUniformParameters(*texView, {imageWidth, imageHeight}, padView); + + // update and bind uniform buffer + auto uniformBuffer = shader->FillUniformBlockBuffer(*texView, {imageWidth, imageHeight}, padView); + glNamedBufferSubData(m_backbufferBlit_uniformBuffer, 0, sizeof(uniformBuffer), &uniformBuffer); + + glBindBufferBase(GL_UNIFORM_BUFFER, 0, m_backbufferBlit_uniformBuffer); // set viewport glViewportIndexedf(0, imageX, imageY, imageWidth, imageHeight); diff --git a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h index 3ed4c27b..5b9ccd41 100644 --- a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h +++ b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h @@ -209,6 +209,9 @@ private: sint32 activeTextureUnit = 0; void* m_latteBoundTextures[Latte::GPU_LIMITS::NUM_TEXTURES_PER_STAGE * 3]{}; + // backbuffer blit + GLuint m_backbufferBlit_uniformBuffer; + // attribute stream GLuint glAttributeCacheAB{}; GLuint _boundArrayBuffer{}; diff --git a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRendererUniformData.cpp b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRendererUniformData.cpp index 73da3a3b..656536df 100644 --- a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRendererUniformData.cpp +++ b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRendererUniformData.cpp @@ -1,3 +1,4 @@ +#include "RendererShaderGL.h" #include "Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h" #include "Cafe/HW/Latte/Core/LatteShader.h" @@ -28,7 +29,7 @@ void OpenGLRenderer::uniformData_update() if (!shader) continue; - auto hostShader = shader->shader; + auto hostShader = (RendererShaderGL*)shader->shader; if (shader->uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_REMAPPED) { diff --git a/src/Cafe/HW/Latte/Renderer/OpenGL/RendererShaderGL.h b/src/Cafe/HW/Latte/Renderer/OpenGL/RendererShaderGL.h index db69c14f..1e8784ff 100644 --- a/src/Cafe/HW/Latte/Renderer/OpenGL/RendererShaderGL.h +++ b/src/Cafe/HW/Latte/Renderer/OpenGL/RendererShaderGL.h @@ -17,12 +17,12 @@ public: GLuint GetProgram() const { cemu_assert_debug(m_isCompiled); return m_program; } GLuint GetShaderObject() const { cemu_assert_debug(m_isCompiled); return m_shader_object; } - sint32 GetUniformLocation(const char* name) override; + sint32 GetUniformLocation(const char* name); - void SetUniform1i(sint32 location, sint32 value) override; - void SetUniform1f(sint32 location, float value) override; - void SetUniform2fv(sint32 location, void* data, sint32 count) override; - void SetUniform4iv(sint32 location, void* data, sint32 count) override; + void SetUniform1i(sint32 location, sint32 value); + void SetUniform1f(sint32 location, float value); + void SetUniform2fv(sint32 location, void* data, sint32 count); + void SetUniform4iv(sint32 location, void* data, sint32 count); static void ShaderCacheLoading_begin(uint64 cacheTitleId); static void ShaderCacheLoading_end(); diff --git a/src/Cafe/HW/Latte/Renderer/RendererOuputShader.cpp b/src/Cafe/HW/Latte/Renderer/RendererOuputShader.cpp index 8c4dd5d7..a4f538a8 100644 --- a/src/Cafe/HW/Latte/Renderer/RendererOuputShader.cpp +++ b/src/Cafe/HW/Latte/Renderer/RendererOuputShader.cpp @@ -263,69 +263,24 @@ RendererOutputShader::RendererOutputShader(const std::string& vertex_source, con if(!m_fragment_shader->WaitForCompiled()) throw std::exception(); - if (g_renderer->GetType() == RendererAPI::OpenGL) - { - m_uniformLocations[0].m_loc_textureSrcResolution = m_vertex_shader->GetUniformLocation("textureSrcResolution"); - m_uniformLocations[0].m_loc_nativeResolution = m_vertex_shader->GetUniformLocation("nativeResolution"); - m_uniformLocations[0].m_loc_outputResolution = m_vertex_shader->GetUniformLocation("outputResolution"); - m_uniformLocations[0].m_loc_applySRGBEncoding = m_vertex_shader->GetUniformLocation("applySRGBEncoding"); - m_uniformLocations[0].m_loc_targetGamma = m_fragment_shader->GetUniformLocation("targetGamma"); - m_uniformLocations[0].m_loc_displayGamma = m_fragment_shader->GetUniformLocation("displayGamma"); - - m_uniformLocations[1].m_loc_textureSrcResolution = m_fragment_shader->GetUniformLocation("textureSrcResolution"); - m_uniformLocations[1].m_loc_nativeResolution = m_fragment_shader->GetUniformLocation("nativeResolution"); - m_uniformLocations[1].m_loc_outputResolution = m_fragment_shader->GetUniformLocation("outputResolution"); - m_uniformLocations[1].m_loc_applySRGBEncoding = m_fragment_shader->GetUniformLocation("applySRGBEncoding"); - m_uniformLocations[1].m_loc_targetGamma = m_fragment_shader->GetUniformLocation("targetGamma"); - m_uniformLocations[1].m_loc_displayGamma = m_fragment_shader->GetUniformLocation("displayGamma"); - } } -void RendererOutputShader::SetUniformParameters(const LatteTextureView& texture_view, const Vector2i& output_res, const bool padView) const +RendererOutputShader::OutputUniformVariables RendererOutputShader::FillUniformBlockBuffer(const LatteTextureView& texture_view, const Vector2i& output_res, const bool padView) const { + OutputUniformVariables vars; + sint32 effectiveWidth, effectiveHeight; texture_view.baseTexture->GetEffectiveSize(effectiveWidth, effectiveHeight, 0); - auto setUniforms = [&](RendererShader* shader, const UniformLocations& locations){ - float res[2]; - if (locations.m_loc_textureSrcResolution != -1) - { - res[0] = (float)effectiveWidth; - res[1] = (float)effectiveHeight; - shader->SetUniform2fv(locations.m_loc_textureSrcResolution, res, 1); - } + vars.textureSrcResolution = {(float)effectiveWidth, (float)effectiveHeight}; - if (locations.m_loc_nativeResolution != -1) - { - res[0] = (float)texture_view.baseTexture->width; - res[1] = (float)texture_view.baseTexture->height; - shader->SetUniform2fv(locations.m_loc_nativeResolution, res, 1); - } + vars.nativeResolution = {(float)texture_view.baseTexture->width, (float)texture_view.baseTexture->height}; + vars.outputResolution = output_res; - if (locations.m_loc_outputResolution != -1) - { - res[0] = (float)output_res.x; - res[1] = (float)output_res.y; - shader->SetUniform2fv(locations.m_loc_outputResolution, res, 1); - } + vars.applySRGBEncoding = padView ? LatteGPUState.drcBufferUsesSRGB : LatteGPUState.tvBufferUsesSRGB; + vars.targetGamma = padView ? ActiveSettings::GetDRCGamma() : ActiveSettings::GetTVGamma(); + vars.displayGamma = GetConfig().userDisplayGamma; - if (locations.m_loc_applySRGBEncoding != -1) - { - shader->SetUniform1i(locations.m_loc_applySRGBEncoding, padView ? LatteGPUState.drcBufferUsesSRGB : LatteGPUState.tvBufferUsesSRGB); - } - - if (locations.m_loc_targetGamma != -1) - { - shader->SetUniform1f(locations.m_loc_targetGamma, padView ? ActiveSettings::GetDRCGamma() : ActiveSettings::GetTVGamma()); - } - - if (locations.m_loc_displayGamma != -1) - { - shader->SetUniform1f(locations.m_loc_displayGamma, GetConfig().userDisplayGamma); - } - - }; - setUniforms(m_vertex_shader.get(), m_uniformLocations[0]); - setUniforms(m_fragment_shader.get(), m_uniformLocations[1]); + return vars; } RendererOutputShader* RendererOutputShader::s_copy_shader; @@ -478,27 +433,23 @@ vertex VertexOut main0(ushort vid [[vertex_id]]) { std::string RendererOutputShader::PrependFragmentPreamble(const std::string& shaderSrc) { return R"(#version 430 +layout(location = 0) smooth in vec2 passUV; +layout(binding = 0) uniform sampler2D textureSrc; +layout(location = 0) out vec4 colorOut0; + #ifdef VULKAN -layout(push_constant) uniform pc { - vec2 textureSrcResolution; - vec2 nativeResolution; - vec2 outputResolution; - bool applySRGBEncoding; // true = app requested sRGB encoding - float targetGamma; - float displayGamma; -}; +layout (binding = 1, std140) #else +layout (binding = 0, std140) +#endif +uniform parameters { uniform vec2 textureSrcResolution; uniform vec2 nativeResolution; uniform vec2 outputResolution; uniform bool applySRGBEncoding; uniform float targetGamma; uniform float displayGamma; -#endif - -layout(location = 0) smooth in vec2 passUV; -layout(binding = 0) uniform sampler2D textureSrc; -layout(location = 0) out vec4 colorOut0; +}; float sRGBEncode(float linear) { diff --git a/src/Cafe/HW/Latte/Renderer/RendererOuputShader.h b/src/Cafe/HW/Latte/Renderer/RendererOuputShader.h index 2322fc6a..433a3308 100644 --- a/src/Cafe/HW/Latte/Renderer/RendererOuputShader.h +++ b/src/Cafe/HW/Latte/Renderer/RendererOuputShader.h @@ -8,6 +8,15 @@ class RendererOutputShader { public: + struct OutputUniformVariables + { + Vector2f textureSrcResolution; + Vector2f nativeResolution; + Vector2f outputResolution; + uint32 applySRGBEncoding; + float targetGamma; + float displayGamma; + }; enum Shader { kCopy, @@ -17,7 +26,7 @@ public: RendererOutputShader(const std::string& vertex_source, const std::string& fragment_source); virtual ~RendererOutputShader() = default; - void SetUniformParameters(const LatteTextureView& texture_view, const Vector2i& output_res, const bool padView) const; + OutputUniformVariables FillUniformBlockBuffer(const LatteTextureView& texture_view, const Vector2i& output_res, const bool padView) const; RendererShader* GetVertexShader() const { @@ -51,15 +60,6 @@ protected: std::unique_ptr m_vertex_shader; std::unique_ptr m_fragment_shader; - struct UniformLocations - { - sint32 m_loc_textureSrcResolution = -1; - sint32 m_loc_nativeResolution = -1; - sint32 m_loc_outputResolution = -1; - sint32 m_loc_applySRGBEncoding = -1; - sint32 m_loc_targetGamma = -1; - sint32 m_loc_displayGamma = -1; - } m_uniformLocations[2]{}; private: static const std::string s_copy_shader_source; diff --git a/src/Cafe/HW/Latte/Renderer/RendererShader.h b/src/Cafe/HW/Latte/Renderer/RendererShader.h index b3d6d62b..2bc66769 100644 --- a/src/Cafe/HW/Latte/Renderer/RendererShader.h +++ b/src/Cafe/HW/Latte/Renderer/RendererShader.h @@ -18,12 +18,6 @@ public: virtual bool IsCompiled() = 0; virtual bool WaitForCompiled() = 0; - virtual sint32 GetUniformLocation(const char* name) = 0; - - virtual void SetUniform1i(sint32 location, sint32 value) = 0; - virtual void SetUniform1f(sint32 location, float value) = 0; - virtual void SetUniform2fv(sint32 location, void* data, sint32 count) = 0; - virtual void SetUniform4iv(sint32 location, void* data, sint32 count) = 0; protected: // if isGameShader is true, then baseHash and auxHash are valid diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.cpp index c58470e0..15fd66e0 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.cpp @@ -226,32 +226,6 @@ void RendererShaderVk::Shutdown() ShaderVkThreadPool.StopThreads(); } -sint32 RendererShaderVk::GetUniformLocation(const char* name) -{ - cemu_assert_suspicious(); - return 0; -} - -void RendererShaderVk::SetUniform1i(sint32 location, sint32 value) -{ - cemu_assert_suspicious(); -} - -void RendererShaderVk::SetUniform1f(sint32 location, float value) -{ - cemu_assert_suspicious(); -} - -void RendererShaderVk::SetUniform2fv(sint32 location, void* data, sint32 count) -{ - cemu_assert_suspicious(); -} - -void RendererShaderVk::SetUniform4iv(sint32 location, void* data, sint32 count) -{ - cemu_assert_suspicious(); -} - void RendererShaderVk::CreateVkShaderModule(std::span spirvBuffer) { VkShaderModuleCreateInfo createInfo{}; diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.h b/src/Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.h index 7b9fc34b..e2e1169a 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.h +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.h @@ -31,11 +31,6 @@ public: static void Init(); static void Shutdown(); - sint32 GetUniformLocation(const char* name) override; - void SetUniform1i(sint32 location, sint32 value) override; - void SetUniform1f(sint32 location, float value) override; - void SetUniform2fv(sint32 location, void* data, sint32 count) override; - void SetUniform4iv(sint32 location, void* data, sint32 count) override; VkShaderModule& GetShaderModule() { return m_shader_module; } static inline FSpinlock s_dependencyLock; diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp index a7a94800..251fd69c 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp @@ -2225,14 +2225,20 @@ void VulkanRenderer::CreatePipelineCache() void VulkanRenderer::swapchain_createDescriptorSetLayout() { - VkDescriptorSetLayoutBinding samplerLayoutBinding = {}; + VkDescriptorSetLayoutBinding bindings[2]{}; + VkDescriptorSetLayoutBinding& samplerLayoutBinding = bindings[0]; samplerLayoutBinding.binding = 0; samplerLayoutBinding.descriptorCount = 1; samplerLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; samplerLayoutBinding.pImmutableSamplers = nullptr; samplerLayoutBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; - VkDescriptorSetLayoutBinding bindings[] = { samplerLayoutBinding }; + VkDescriptorSetLayoutBinding& uniformBufferBinding = bindings[1]; + uniformBufferBinding.binding = 1; + uniformBufferBinding.descriptorCount = 1; + uniformBufferBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC; + uniformBufferBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; + VkDescriptorSetLayoutCreateInfo layoutInfo = {}; layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; layoutInfo.bindingCount = std::size(bindings); @@ -2638,20 +2644,10 @@ VkPipeline VulkanRenderer::backbufferBlit_createGraphicsPipeline(VkDescriptorSet colorBlending.blendConstants[2] = 0.0f; colorBlending.blendConstants[3] = 0.0f; - VkPushConstantRange pushConstantRange{ - .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT, - .offset = 0, - .size = 3 * sizeof(float) * 2 // 3 vec2's - + 4 // + 1 VkBool32 - + 4 * 2 // + 2 float - }; - VkPipelineLayoutCreateInfo pipelineLayoutInfo{}; pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; pipelineLayoutInfo.setLayoutCount = 1; pipelineLayoutInfo.pSetLayouts = &descriptorLayout; - pipelineLayoutInfo.pushConstantRangeCount = 1; - pipelineLayoutInfo.pPushConstantRanges = &pushConstantRange; VkResult result; if (m_pipelineLayout == VK_NULL_HANDLE) @@ -3027,37 +3023,12 @@ void VulkanRenderer::DrawBackbufferQuad(LatteTextureView* texView, RendererOutpu vkCmdBindPipeline(m_state.currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); m_state.currentPipeline = pipeline; - vkCmdBindDescriptorSets(m_state.currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipelineLayout, 0, 1, &descriptSet, 0, nullptr); + auto outputUniforms = shader->FillUniformBlockBuffer(*texView, {imageWidth, imageHeight}, padView); + auto outputUniformOffset = uniformData_uploadUniformDataBufferGetOffset({(uint8*)&outputUniforms, sizeof(decltype(outputUniforms))}); - // update push constants - struct - { - Vector2f vecs[3]; - VkBool32 applySRGBEncoding; - float targetGamma; - float displayGamma; - } pushData; - - // textureSrcResolution - sint32 effectiveWidth, effectiveHeight; - texView->baseTexture->GetEffectiveSize(effectiveWidth, effectiveHeight, 0); - pushData.vecs[0] = {(float)effectiveWidth, (float)effectiveHeight}; - - // nativeResolution - pushData.vecs[1] = { - (float)texViewVk->baseTexture->width, - (float)texViewVk->baseTexture->height, - }; - - // outputResolution - pushData.vecs[2] = {(float)imageWidth,(float)imageHeight}; - - pushData.applySRGBEncoding = padView ? LatteGPUState.drcBufferUsesSRGB : LatteGPUState.tvBufferUsesSRGB; - pushData.targetGamma = padView ? ActiveSettings::GetDRCGamma() : ActiveSettings::GetTVGamma(); - pushData.displayGamma = GetConfig().userDisplayGamma; - - vkCmdPushConstants(m_state.currentCommandBuffer, m_pipelineLayout, VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(pushData), &pushData); + vkCmdBindDescriptorSets(m_state.currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipelineLayout, 0, 1, &descriptSet, + 1, &outputUniformOffset); vkCmdDraw(m_state.currentCommandBuffer, 6, 1, 0, 0); @@ -3119,16 +3090,32 @@ VkDescriptorSet VulkanRenderer::backbufferBlit_createDescriptorSet(VkDescriptorS imageInfo.imageView = texViewVk->GetViewRGBA()->m_textureImageView; imageInfo.sampler = texViewVk->GetDefaultTextureSampler(useLinearTexFilter); - VkWriteDescriptorSet descriptorWrites = {}; - descriptorWrites.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; - descriptorWrites.dstSet = result; - descriptorWrites.dstBinding = 0; - descriptorWrites.dstArrayElement = 0; - descriptorWrites.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; - descriptorWrites.descriptorCount = 1; - descriptorWrites.pImageInfo = &imageInfo; + VkWriteDescriptorSet descriptorWrites[2]{}; - vkUpdateDescriptorSets(m_logicalDevice, 1, &descriptorWrites, 0, nullptr); + VkWriteDescriptorSet& samplerWrite = descriptorWrites[0]; + samplerWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + samplerWrite.dstSet = result; + samplerWrite.dstBinding = 0; + samplerWrite.dstArrayElement = 0; + samplerWrite.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + samplerWrite.descriptorCount = 1; + samplerWrite.pImageInfo = &imageInfo; + + VkWriteDescriptorSet& uniformBufferWrite = descriptorWrites[1]; + uniformBufferWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + uniformBufferWrite.dstSet = result; + uniformBufferWrite.dstBinding = 1; + uniformBufferWrite.descriptorCount = 1; + uniformBufferWrite.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC; + + VkDescriptorBufferInfo uniformBufferInfo{}; + uniformBufferInfo.buffer = m_uniformVarBuffer; + uniformBufferInfo.offset = 0; + uniformBufferInfo.range = sizeof(RendererOutputShader::OutputUniformVariables); + uniformBufferWrite.pBufferInfo = &uniformBufferInfo; + + + vkUpdateDescriptorSets(m_logicalDevice, std::size(descriptorWrites), descriptorWrites, 0, nullptr); performanceMonitor.vk.numDescriptorSamplerTextures.increment(); m_backbufferBlitDescriptorSetCache[hash] = result; diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h index cae4bd10..2c7af53b 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h @@ -554,6 +554,7 @@ private: VkCommandBuffer getCurrentCommandBuffer() const { return m_state.currentCommandBuffer; } // uniform + uint32 uniformData_uploadUniformDataBufferGetOffset(std::span data); void uniformData_updateUniformVars(uint32 shaderStageIndex, LatteDecompilerShader* shader); // misc diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp index b41b7c37..23fb910c 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp @@ -376,6 +376,68 @@ void VulkanRenderer::indexData_uploadIndexMemory(IndexAllocation& allocation) float s_vkUniformData[512 * 4]; +uint32 VulkanRenderer::uniformData_uploadUniformDataBufferGetOffset(std::span data) +{ + const uint32 bufferAlignmentM1 = std::max(m_featureControl.limits.minUniformBufferOffsetAlignment, m_featureControl.limits.nonCoherentAtomSize) - 1; + const uint32 uniformSize = (data.size() + bufferAlignmentM1) & ~bufferAlignmentM1; + + auto waitWhileCondition = [&](std::function condition) { + while (condition()) + { + if (m_commandBufferSyncIndex == m_commandBufferIndex) + { + if (m_cmdBufferUniformRingbufIndices[m_commandBufferIndex] != m_uniformVarBufferReadIndex) + { + draw_endRenderPass(); + SubmitCommandBuffer(); + } + else + { + // submitting work would not change readIndex, so there's no way for conditions based on it to change + cemuLog_log(LogType::Force, "draw call overflowed and corrupted uniform ringbuffer. expect visual corruption"); + cemu_assert_suspicious(); + break; + } + } + WaitForNextFinishedCommandBuffer(); + } + }; + + // wrap around if it doesnt fit consecutively + if (m_uniformVarBufferWriteIndex + uniformSize > UNIFORMVAR_RINGBUFFER_SIZE) + { + waitWhileCondition([&]() { + return m_uniformVarBufferReadIndex > m_uniformVarBufferWriteIndex || m_uniformVarBufferReadIndex == 0; + }); + m_uniformVarBufferWriteIndex = 0; + } + + auto ringBufRemaining = [&]() { + ssize_t ringBufferUsedBytes = (ssize_t)m_uniformVarBufferWriteIndex - m_uniformVarBufferReadIndex; + if (ringBufferUsedBytes < 0) + ringBufferUsedBytes += UNIFORMVAR_RINGBUFFER_SIZE; + return UNIFORMVAR_RINGBUFFER_SIZE - 1 - ringBufferUsedBytes; + }; + waitWhileCondition([&]() { + return ringBufRemaining() < uniformSize; + }); + + const uint32 uniformOffset = m_uniformVarBufferWriteIndex; + memcpy(m_uniformVarBufferPtr + uniformOffset, data.data(), data.size()); + m_uniformVarBufferWriteIndex += uniformSize; + // flush if not coherent + if (!m_uniformVarBufferMemoryIsCoherent) + { + VkMappedMemoryRange flushedRange{}; + flushedRange.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE; + flushedRange.memory = m_uniformVarBufferMemory; + flushedRange.offset = uniformOffset; + flushedRange.size = uniformSize; + vkFlushMappedMemoryRanges(m_logicalDevice, 1, &flushedRange); + } + return uniformOffset; +} + void VulkanRenderer::uniformData_updateUniformVars(uint32 shaderStageIndex, LatteDecompilerShader* shader) { auto GET_UNIFORM_DATA_PTR = [](size_t index) { return s_vkUniformData + (index / 4); }; @@ -453,66 +515,7 @@ void VulkanRenderer::uniformData_updateUniformVars(uint32 shaderStageIndex, Latt } } } - // upload - const uint32 bufferAlignmentM1 = std::max(m_featureControl.limits.minUniformBufferOffsetAlignment, m_featureControl.limits.nonCoherentAtomSize) - 1; - const uint32 uniformSize = (shader->uniform.uniformRangeSize + bufferAlignmentM1) & ~bufferAlignmentM1; - - auto waitWhileCondition = [&](std::function condition) { - while (condition()) - { - if (m_commandBufferSyncIndex == m_commandBufferIndex) - { - if (m_cmdBufferUniformRingbufIndices[m_commandBufferIndex] != m_uniformVarBufferReadIndex) - { - draw_endRenderPass(); - SubmitCommandBuffer(); - } - else - { - // submitting work would not change readIndex, so there's no way for conditions based on it to change - cemuLog_log(LogType::Force, "draw call overflowed and corrupted uniform ringbuffer. expect visual corruption"); - cemu_assert_suspicious(); - break; - } - } - WaitForNextFinishedCommandBuffer(); - } - }; - - // wrap around if it doesnt fit consecutively - if (m_uniformVarBufferWriteIndex + uniformSize > UNIFORMVAR_RINGBUFFER_SIZE) - { - waitWhileCondition([&]() { - return m_uniformVarBufferReadIndex > m_uniformVarBufferWriteIndex || m_uniformVarBufferReadIndex == 0; - }); - m_uniformVarBufferWriteIndex = 0; - } - - auto ringBufRemaining = [&]() { - ssize_t ringBufferUsedBytes = (ssize_t)m_uniformVarBufferWriteIndex - m_uniformVarBufferReadIndex; - if (ringBufferUsedBytes < 0) - ringBufferUsedBytes += UNIFORMVAR_RINGBUFFER_SIZE; - return UNIFORMVAR_RINGBUFFER_SIZE - 1 - ringBufferUsedBytes; - }; - waitWhileCondition([&]() { - return ringBufRemaining() < uniformSize; - }); - - const uint32 uniformOffset = m_uniformVarBufferWriteIndex; - memcpy(m_uniformVarBufferPtr + uniformOffset, s_vkUniformData, shader->uniform.uniformRangeSize); - m_uniformVarBufferWriteIndex += uniformSize; - // update dynamic offset - dynamicOffsetInfo.uniformVarBufferOffset[shaderStageIndex] = uniformOffset; - // flush if not coherent - if (!m_uniformVarBufferMemoryIsCoherent) - { - VkMappedMemoryRange flushedRange{}; - flushedRange.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE; - flushedRange.memory = m_uniformVarBufferMemory; - flushedRange.offset = uniformOffset; - flushedRange.size = uniformSize; - vkFlushMappedMemoryRanges(m_logicalDevice, 1, &flushedRange); - } + dynamicOffsetInfo.uniformVarBufferOffset[shaderStageIndex] = uniformData_uploadUniformDataBufferGetOffset({(uint8*)s_vkUniformData, shader->uniform.uniformRangeSize}); } } From 5bc60b9452295eb368dae85a054e1444e4b422e3 Mon Sep 17 00:00:00 2001 From: rcaridade145 Date: Mon, 2 Feb 2026 16:16:46 +0000 Subject: [PATCH 4/8] AX: Fix linear filter (#1802) The else code would be unreachable. --- src/Cafe/OS/libs/snd_core/ax_mix.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Cafe/OS/libs/snd_core/ax_mix.cpp b/src/Cafe/OS/libs/snd_core/ax_mix.cpp index 3fdca1ff..31a24a44 100644 --- a/src/Cafe/OS/libs/snd_core/ax_mix.cpp +++ b/src/Cafe/OS/libs/snd_core/ax_mix.cpp @@ -596,7 +596,7 @@ namespace snd_core uint32 srcFilterMode = _swapEndianU16(internalShadowCopy->srcFilterMode); uint16 format = _swapEndianU16(internalShadowCopy->internalOffsets.format); - if (srcFilterMode == AX_FILTER_MODE_LINEAR || srcFilterMode == AX_FILTER_MODE_TAP) + if (srcFilterMode == AX_FILTER_MODE_TAP) { if (format == AX_FORMAT_ADPCM) AX_DecodeSamplesADPCM_Tap(internalShadowCopy, output, sampleCount); From 2c03ac32170445412b6236e6af143f0b2e531bf1 Mon Sep 17 00:00:00 2001 From: Luminyx <27lumi@protonmail.com> Date: Sun, 8 Feb 2026 20:20:34 -0500 Subject: [PATCH 5/8] GraphicPack: Support wildcard titleId and RPX hash + entrypoint callbacks (#1805) --- src/Cafe/GraphicPack/GraphicPack2.cpp | 13 +++- src/Cafe/GraphicPack/GraphicPack2.h | 8 ++- src/Cafe/GraphicPack/GraphicPack2Patches.cpp | 4 +- src/Cafe/GraphicPack/GraphicPack2Patches.h | 6 ++ .../GraphicPack/GraphicPack2PatchesApply.cpp | 15 +++++ .../GraphicPack/GraphicPack2PatchesParser.cpp | 35 ++++++++++- src/Cafe/OS/RPL/rpl.cpp | 1 + src/Cafe/OS/RPL/rpl_structs.h | 9 ++- src/Cafe/OS/libs/coreinit/coreinit_Init.cpp | 13 ++++ src/gui/wxgui/GraphicPacksWindow2.cpp | 59 ++++++++++--------- 10 files changed, 127 insertions(+), 36 deletions(-) diff --git a/src/Cafe/GraphicPack/GraphicPack2.cpp b/src/Cafe/GraphicPack/GraphicPack2.cpp index aded9188..295d5dce 100644 --- a/src/Cafe/GraphicPack/GraphicPack2.cpp +++ b/src/Cafe/GraphicPack/GraphicPack2.cpp @@ -328,7 +328,7 @@ GraphicPack2::GraphicPack2(fs::path rulesPath, IniParser& rules) } m_title_ids = ParseTitleIds(rules, "titleIds"); - if(m_title_ids.empty()) + if(m_title_ids.empty() && !m_universal) throw std::exception(); auto option_fsPriority = rules.FindOption("fsPriority"); @@ -532,6 +532,9 @@ std::string GraphicPack2::GetNormalizedPathString() const bool GraphicPack2::ContainsTitleId(uint64_t title_id) const { + if (m_universal) + return true; + const auto it = std::find_if(m_title_ids.begin(), m_title_ids.end(), [title_id](uint64 id) { return id == title_id; }); return it != m_title_ids.end(); } @@ -1188,7 +1191,7 @@ std::vector GraphicPack2::GetActivePresets() const return result; } -std::vector GraphicPack2::ParseTitleIds(IniParser& rules, const char* option_name) const +std::vector GraphicPack2::ParseTitleIds(IniParser& rules, const char* option_name) { std::vector result; @@ -1196,6 +1199,12 @@ std::vector GraphicPack2::ParseTitleIds(IniParser& rules, const char* op if (!option_text) return result; + if (*option_text == "*") + { + m_universal = true; + return result; + } + for (auto& token : TokenizeView(*option_text, ',')) { try diff --git a/src/Cafe/GraphicPack/GraphicPack2.h b/src/Cafe/GraphicPack/GraphicPack2.h index b83ac66c..e33f0946 100644 --- a/src/Cafe/GraphicPack/GraphicPack2.h +++ b/src/Cafe/GraphicPack/GraphicPack2.h @@ -109,6 +109,7 @@ public: bool Reload(); bool HasName() const { return !m_name.empty(); } + bool IsUniversal() const { return m_universal; } const std::string& GetName() const { return m_name.empty() ? m_virtualPath : m_name; } const std::string& GetVirtualPath() const { return m_virtualPath; } // returns the path in the gfx tree hierarchy @@ -122,6 +123,8 @@ public: const std::vector& GetTitleIds() const { return m_title_ids; } bool HasCustomVSyncFrequency() const { return m_vsync_frequency >= 1; } sint32 GetCustomVSyncFrequency() const { return m_vsync_frequency; } + + const std::vector>& GetCallbacks() const { return m_callbacks; } // texture rules const std::vector& GetTextureRules() const { return m_texture_rules; } @@ -229,6 +232,7 @@ private: bool m_activated = false; // set if the graphic pack is currently used by the running game std::vector m_title_ids; bool m_patchedFilesLoaded = false; // set to true once patched files are loaded + bool m_universal = false; // set if this pack applies to every title id sint32 m_vsync_frequency = -1; sint32 m_fs_priority = 100; @@ -256,7 +260,7 @@ private: std::unordered_map ParsePresetVars(IniParser& rules) const; - std::vector ParseTitleIds(IniParser& rules, const char* option_name) const; + std::vector ParseTitleIds(IniParser& rules, const char* option_name); CustomShader LoadShader(const fs::path& path, uint64 shader_base_hash, uint64 shader_aux_hash, GP_SHADER_TYPE shader_type, bool isMetalShader) const; void ApplyShaderPresets(std::string& shader_source) const; @@ -282,6 +286,8 @@ private: void LogPatchesSyntaxError(sint32 lineNumber, std::string_view errorMsg); std::vector list_patchGroups; + + std::vector> m_callbacks; static std::recursive_mutex mtx_patches; static std::vector list_modules; diff --git a/src/Cafe/GraphicPack/GraphicPack2Patches.cpp b/src/Cafe/GraphicPack/GraphicPack2Patches.cpp index d0d00bf2..7ceaf9d4 100644 --- a/src/Cafe/GraphicPack/GraphicPack2Patches.cpp +++ b/src/Cafe/GraphicPack/GraphicPack2Patches.cpp @@ -172,7 +172,7 @@ void GraphicPack2::ApplyPatchesForModule(const RPLModule* rpl) std::vector list_groups; for (auto itr : list_patchGroups) { - if (itr->matchesCRC(rpl->patchCRC)) + if (itr->matchesCRC(rpl->patchCRC) || (itr->m_isRpxOnlyTarget && rpl->IsRPX())) list_groups.emplace_back(itr); } // apply all groups at once @@ -188,7 +188,7 @@ void GraphicPack2::RevertPatchesForModule(const RPLModule* rpl) std::vector list_groups; for (auto itr : list_patchGroups) { - if (itr->matchesCRC(rpl->patchCRC)) + if (itr->matchesCRC(rpl->patchCRC) || (itr->m_isRpxOnlyTarget && rpl->IsRPX())) list_groups.emplace_back(itr); } // undo all groups at once diff --git a/src/Cafe/GraphicPack/GraphicPack2Patches.h b/src/Cafe/GraphicPack/GraphicPack2Patches.h index b33cabf9..34750533 100644 --- a/src/Cafe/GraphicPack/GraphicPack2Patches.h +++ b/src/Cafe/GraphicPack/GraphicPack2Patches.h @@ -212,6 +212,10 @@ private: bool m_addrRelocated{}; }; +enum class GPCallbackType { + Entry +}; + class PatchGroup { friend class GraphicPack2; @@ -256,7 +260,9 @@ private: std::string name; std::vector list_moduleMatches; std::vector list_patches; + std::vector> list_callbacks; uint32 codeCaveSize; MEMPTR codeCaveMem; bool m_isApplied{}; + bool m_isRpxOnlyTarget{}; }; \ No newline at end of file diff --git a/src/Cafe/GraphicPack/GraphicPack2PatchesApply.cpp b/src/Cafe/GraphicPack/GraphicPack2PatchesApply.cpp index b6af542d..10e9743b 100644 --- a/src/Cafe/GraphicPack/GraphicPack2PatchesApply.cpp +++ b/src/Cafe/GraphicPack/GraphicPack2PatchesApply.cpp @@ -710,6 +710,21 @@ void GraphicPack2::ApplyPatchGroups(std::vector& groups, const RPLM continue; patchInstruction->applyPatch(); } + + for (const auto& [name, type] : patchGroup->list_callbacks) + { + auto it = patchContext.map_values.find(name); + if (it != patchContext.map_values.end()) + { + m_callbacks.push_back(std::make_pair(it->second, type)); + } + else + { + patchContext.errorHandler.printError(patchGroup, -1, fmt::format("Failed to resolve .callback symbol: {}", name)); + patchContext.errorHandler.showStageErrorMessageBox(); + return; + } + } } // mark groups as applied for (auto patchGroup : groups) diff --git a/src/Cafe/GraphicPack/GraphicPack2PatchesParser.cpp b/src/Cafe/GraphicPack/GraphicPack2PatchesParser.cpp index 05f8c696..d93cc151 100644 --- a/src/Cafe/GraphicPack/GraphicPack2PatchesParser.cpp +++ b/src/Cafe/GraphicPack/GraphicPack2PatchesParser.cpp @@ -41,7 +41,7 @@ void GraphicPack2::CancelParsingPatches() void GraphicPack2::AddPatchGroup(PatchGroup* group) { - if (group->list_moduleMatches.empty()) + if (group->list_moduleMatches.empty() && !group->m_isRpxOnlyTarget) { LogPatchesSyntaxError(-1, fmt::format("Group \"{}\" has no moduleMatches definition", group->name)); CancelParsingPatches(); @@ -347,6 +347,12 @@ bool GraphicPack2::ParseCemuPatchesTxtInternal(MemStreamReader& patchesStream) // read the checksums while (true) { + if (parser.matchWordI("rpx")) + { + currentGroup->m_isRpxOnlyTarget = true; + break; + } + uint32 checksum = 0; if (parser.parseU32(checksum) == false) { @@ -425,7 +431,32 @@ bool GraphicPack2::ParseCemuPatchesTxtInternal(MemStreamReader& patchesStream) } continue; } - + else if (parser.matchWordI(".callback")) + { + if (parser.matchWordI("entry")) + { + const char* symbolStr; + sint32 symbolLen; + if (parser.parseSymbolName(symbolStr, symbolLen)) + { + currentGroup->list_callbacks.push_back(std::make_pair(std::string(symbolStr, static_cast(symbolLen)), GPCallbackType::Entry)); + continue; + } + else + { + LogPatchesSyntaxError(lineNumber, "'.callback' must reference a symbol after the type"); + CancelParsingPatches(); + return false; + } + } + else + { + LogPatchesSyntaxError(lineNumber, "Unrecognized type for '.callback'"); + CancelParsingPatches(); + return false; + } + } + // next we attempt to parse symbol assignment // symbols can be labels or variables. The type is determined by what comes after the symbol name // = defines a variable diff --git a/src/Cafe/OS/RPL/rpl.cpp b/src/Cafe/OS/RPL/rpl.cpp index f39d82be..d39ea95e 100644 --- a/src/Cafe/OS/RPL/rpl.cpp +++ b/src/Cafe/OS/RPL/rpl.cpp @@ -267,6 +267,7 @@ bool RPLLoader_ProcessHeaders(std::string_view moduleName, uint8* rplData, uint3 rplLoaderContext->fileInfo.tlsModuleIndex = fileInfoPtr->tlsModuleIndex; rplLoaderContext->fileInfo.sdataBase1 = fileInfoPtr->sdataBase1; rplLoaderContext->fileInfo.sdataBase2 = fileInfoPtr->sdataBase2; + rplLoaderContext->fileInfo.flags = fileInfoPtr->flags; // init section address table rplLoaderContext->sectionAddressTable2.resize(sectionCount); diff --git a/src/Cafe/OS/RPL/rpl_structs.h b/src/Cafe/OS/RPL/rpl_structs.h index 4b283f07..aa734dae 100644 --- a/src/Cafe/OS/RPL/rpl_structs.h +++ b/src/Cafe/OS/RPL/rpl_structs.h @@ -113,7 +113,7 @@ typedef struct /* +0x28 */ uint32be sdataBase2; /* +0x2C */ uint32be ukn2C; /* +0x30 */ uint32be ukn30; - /* +0x34 */ uint32be ukn34; + /* +0x34 */ uint32be flags; /* +0x38 */ uint32be ukn38; /* +0x3C */ uint32be ukn3C; /* +0x40 */ uint32be minimumToolkitVersion; @@ -198,6 +198,8 @@ struct RPLModule uint32 sdataBase1; uint32 sdataBase2; + + uint32 flags; }fileInfo; // parsed CRC std::vector crcTable; @@ -208,6 +210,11 @@ struct RPLModule return 0; return crcTable[sectionIndex]; } + + bool IsRPX() const + { + return fileInfo.flags & 2; + } // state bool isLinked; // set to true if _linkModule was called on this module diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Init.cpp b/src/Cafe/OS/libs/coreinit/coreinit_Init.cpp index 72f6ac11..04e7d07d 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_Init.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_Init.cpp @@ -11,6 +11,7 @@ #include "Cafe/OS/libs/coreinit/coreinit_MEM.h" #include "Cafe/OS/libs/coreinit/coreinit_FG.h" #include "Cafe/CafeSystem.h" +#include "Cafe/GraphicPack/GraphicPack2.h" extern MPTR _entryPoint; extern RPLModule* applicationRPX; @@ -211,6 +212,18 @@ void coreinit_start(PPCInterpreter_t* hCPU) padscore::start(); vpad::start(); + // call entry-type callbacks in graphic packs + for (const auto gp : GraphicPack2::GetActiveGraphicPacks()) + { + for (const auto [callback, type] : gp->GetCallbacks()) + { + if (type == GPCallbackType::Entry) + { + PPCCoreCallback(callback); + } + } + } + // continue at main executable entrypoint hCPU->gpr[4] = memory_getVirtualOffsetFromPointer(_coreinitInfo->argv); hCPU->gpr[3] = _coreinitInfo->argc; diff --git a/src/gui/wxgui/GraphicPacksWindow2.cpp b/src/gui/wxgui/GraphicPacksWindow2.cpp index b6f016ff..90f5e17c 100644 --- a/src/gui/wxgui/GraphicPacksWindow2.cpp +++ b/src/gui/wxgui/GraphicPacksWindow2.cpp @@ -43,44 +43,47 @@ void GraphicPacksWindow2::FillGraphicPackList() const for(auto& p : graphic_packs) { - // filter graphic packs by given title id - if (m_filter_installed_games && !m_installed_games.empty()) + if (!p->IsUniversal()) { - bool found = false; - for (uint64 titleId : p->GetTitleIds()) - { - if (std::find(m_installed_games.cbegin(), m_installed_games.cend(), titleId) != m_installed_games.cend()) - { - found = true; - break; - } - } - - if (!found) - continue; - } - - // filter graphic packs by given title id - if(has_filter) - { - bool found = false; - - if (boost::icontains(p->GetVirtualPath(), m_filter)) - found = true; - else + // filter graphic packs by given title id + if (m_filter_installed_games && !m_installed_games.empty()) { + bool found = false; for (uint64 titleId : p->GetTitleIds()) { - if (boost::icontains(fmt::format("{:x}", titleId), m_filter)) + if (std::find(m_installed_games.cbegin(), m_installed_games.cend(), titleId) != m_installed_games.cend()) { found = true; break; } } + + if (!found) + continue; + } + + // filter graphic packs by given title id + if(has_filter) + { + bool found = false; + + if (boost::icontains(p->GetVirtualPath(), m_filter)) + found = true; + else + { + for (uint64 titleId : p->GetTitleIds()) + { + if (boost::icontains(fmt::format("{:x}", titleId), m_filter)) + { + found = true; + break; + } + } + } + + if (!found) + continue; } - - if (!found) - continue; } const auto& path = p->GetVirtualPath(); From 8cd5ce102fceb244cb5c3e9ed46d375e542ab167 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Mon, 9 Feb 2026 04:11:10 +0100 Subject: [PATCH 6/8] Vulkan: Properly shutdown pipeline compile threads + code cleanup This fixes an issue where the Vulkan renderer would hang up on shutdown due to resources still being in use --- src/Cafe/HW/Latte/Core/LatteBufferCache.cpp | 3 +- .../LatteDecompilerInternal.h | 4 - .../Vulkan/VulkanPipelineCompiler.cpp | 76 ++++++++++++++++++- .../Renderer/Vulkan/VulkanPipelineCompiler.h | 7 ++ .../Latte/Renderer/Vulkan/VulkanRenderer.cpp | 10 ++- .../Renderer/Vulkan/VulkanRendererCore.cpp | 67 +--------------- 6 files changed, 90 insertions(+), 77 deletions(-) diff --git a/src/Cafe/HW/Latte/Core/LatteBufferCache.cpp b/src/Cafe/HW/Latte/Core/LatteBufferCache.cpp index 6c36ddd3..e466bf3a 100644 --- a/src/Cafe/HW/Latte/Core/LatteBufferCache.cpp +++ b/src/Cafe/HW/Latte/Core/LatteBufferCache.cpp @@ -290,7 +290,6 @@ public: { if (m_hasCacheAlloc) { - cemu_assert_debug(isInUse() == false); g_gpuBufferHeap->freeOffset(m_cacheOffset); m_hasCacheAlloc = false; } @@ -836,6 +835,8 @@ public: continue; } // delete range + if (node->m_hasCacheAlloc) + cemu_assert_debug(!node->isInUse()); node->ReleaseCacheMemoryImmediately(); LatteBufferCache_removeSingleNodeFromTree(node); delete node; diff --git a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerInternal.h b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerInternal.h index e756ce17..4c6b158a 100644 --- a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerInternal.h +++ b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerInternal.h @@ -115,11 +115,7 @@ struct LatteDecompilerCFInstruction cemu_assert_debug(!(instructionsALU.size() != 0 && instructionsTEX.size() != 0)); // make sure we haven't accidentally added the wrong instruction type } -#if BOOST_OS_WINDOWS - LatteDecompilerCFInstruction(LatteDecompilerCFInstruction& mE) = default; -#else LatteDecompilerCFInstruction(const LatteDecompilerCFInstruction& mE) = default; -#endif LatteDecompilerCFInstruction(LatteDecompilerCFInstruction&& mE) = default; LatteDecompilerCFInstruction& operator=(LatteDecompilerCFInstruction&& mE) = default; diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineCompiler.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineCompiler.cpp index eb455887..795d11c3 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineCompiler.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineCompiler.cpp @@ -6,11 +6,10 @@ #include "Cafe/HW/Latte/Core/LattePerformanceMonitor.h" #include "Cafe/OS/libs/gx2/GX2.h" #include "config/ActiveSettings.h" +#include "util/helpers/helpers.h" #include "util/helpers/Serializer.h" #include "Cafe/HW/Latte/Common/RegisterSerializer.h" -std::mutex s_nvidiaWorkaround; - /* rects emulation */ void rectsEmulationGS_outputSingleVertex(std::string& gsSrc, LatteDecompilerShader* vertexShader, LatteShaderPSInputTable* psInputTable, sint32 vIdx, const LatteContextRegister& latteRegister) @@ -923,7 +922,6 @@ bool PipelineCompiler::InitFromCurrentGPUState(PipelineInfo* pipelineInfo, const if (result != VK_SUCCESS) { cemuLog_log(LogType::Force, "Failed to create pipeline layout: {}", result); - s_nvidiaWorkaround.unlock(); return false; } @@ -941,7 +939,7 @@ bool PipelineCompiler::InitFromCurrentGPUState(PipelineInfo* pipelineInfo, const // increment ref counter for vkrObjPipeline and renderpass object to make sure they dont get released while we are using them m_vkrObjPipeline->incRef(); - renderPassObj->incRef(); + m_renderPassObj->incRef(); return true; } @@ -1121,3 +1119,73 @@ bool PipelineCompiler::CalcRobustBufferAccessRequirement(LatteDecompilerShader* } return requiresRobustBufferAcces; } + +static std::vector s_compileThreads; +static std::atomic_bool s_compileThreadsShutdownSignal{}; +static ConcurrentQueue s_pipelineCompileRequests; + +static void compilePipeline_thread(sint32 threadIndex) +{ + SetThreadName("compilePl"); +#ifdef _WIN32 + // to avoid starving the main cpu and render threads the pipeline compile threads run at lower priority + // except for one thread which we always run at normal priority to prevent the opposite scenario where all compile threads are starved + if(threadIndex != 0) + SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_BELOW_NORMAL); +#endif + while (!s_compileThreadsShutdownSignal) + { + PipelineCompiler* request = s_pipelineCompileRequests.pop(); + if (!request) + continue; + request->Compile(true, false, true); + delete request; + } +} + +void PipelineCompiler::CompileThreadPool_Start() +{ + cemu_assert_debug(s_compileThreads.empty()); + s_compileThreadsShutdownSignal = false; + uint32 numCompileThreads; + + uint32 cpuCoreCount = GetPhysicalCoreCount(); + if (cpuCoreCount <= 2) + numCompileThreads = 1; + else + numCompileThreads = 2 + (cpuCoreCount - 3); // 2 plus one additionally for every extra core above 3 + + numCompileThreads = std::min(numCompileThreads, 8u); // cap at 8 + + for (uint32_t i = 0; i < numCompileThreads; i++) + { + s_compileThreads.emplace_back(compilePipeline_thread, i); + } +} + +void PipelineCompiler::CompileThreadPool_Stop() +{ + s_compileThreadsShutdownSignal = true; + { + // push one empty workload for each thread + // this way we can make sure that each waiting thread is woken up to see the shutdown signal + for (auto& thread : s_compileThreads) + s_pipelineCompileRequests.push(nullptr); + } + for (auto& thread : s_compileThreads) + thread.join(); + while (!s_pipelineCompileRequests.empty()) + { + PipelineCompiler* pipelineCompiler = s_pipelineCompileRequests.pop(); + if (!pipelineCompiler) + break; + if (pipelineCompiler) + delete pipelineCompiler; + } + s_compileThreads.clear(); +} + +void PipelineCompiler::CompileThreadPool_QueueCompilation(PipelineCompiler* v) +{ + s_pipelineCompileRequests.push(v); +} \ No newline at end of file diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineCompiler.h b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineCompiler.h index 7297049e..f4240a53 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineCompiler.h +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineCompiler.h @@ -1,4 +1,6 @@ #pragma once +#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h" +#include "VKRBase.h" class PipelineCompiler : public VKRMoveableRefCounter { @@ -43,6 +45,11 @@ public: static bool CalcRobustBufferAccessRequirement(LatteDecompilerShader* vertexShader, LatteDecompilerShader* pixelShader, LatteDecompilerShader* geometryShader); + // API for thread pool + static void CompileThreadPool_Start(); + static void CompileThreadPool_Stop(); + static void CompileThreadPool_QueueCompilation(PipelineCompiler* v); + VkPipelineLayout m_pipelineLayout; VKRObjectRenderPass* m_renderPassObj{}; bool m_requestRobustBufferAccess{false}; diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp index 251fd69c..18fd1000 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp @@ -4,6 +4,7 @@ #include "Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.h" #include "Cafe/HW/Latte/Renderer/Vulkan/VulkanTextureReadback.h" #include "Cafe/HW/Latte/Renderer/Vulkan/CocoaSurface.h" +#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineCompiler.h" #include "Cafe/HW/Latte/Core/LatteBufferCache.h" #include "Cafe/HW/Latte/Core/LattePerformanceMonitor.h" @@ -653,7 +654,8 @@ VulkanRenderer::VulkanRenderer() m_occlusionQueries.list_availableQueryIndices.emplace_back(i); // start compilation threads - RendererShaderVk::Init(); + RendererShaderVk::Init(); // shaders + PipelineCompiler::CompileThreadPool_Start(); // pipelines } VulkanRenderer::~VulkanRenderer() @@ -661,8 +663,6 @@ VulkanRenderer::~VulkanRenderer() SubmitCommandBuffer(); WaitDeviceIdle(); WaitCommandBufferFinished(GetCurrentCommandBufferId()); - // make sure compilation threads have been shut down - RendererShaderVk::Shutdown(); // shut down pipeline save thread m_destructionRequested = true; m_pipeline_cache_semaphore.notify(); @@ -1666,6 +1666,10 @@ void VulkanRenderer::Shutdown() { SubmitCommandBuffer(); WaitDeviceIdle(); + // stop compilation threads + RendererShaderVk::Shutdown(); + PipelineCompiler::CompileThreadPool_Stop(); + DeleteFontTextures(); Renderer::Shutdown(); if (m_imguiRenderPass != VK_NULL_HANDLE) diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp index 23fb910c..a6814186 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp @@ -183,63 +183,6 @@ void VulkanRenderer::unregisterGraphicsPipeline(PipelineInfo* pipelineInfo) } } -bool g_compilePipelineThreadInit{false}; -std::mutex g_compilePipelineMutex; -std::condition_variable g_compilePipelineCondVar; -std::queue g_compilePipelineRequests; - -void compilePipeline_thread(sint32 threadIndex) -{ - SetThreadName("compilePl"); -#ifdef _WIN32 - // one thread runs at normal priority while the others run at lower priority - if(threadIndex != 0) - SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_BELOW_NORMAL); -#endif - while (true) - { - std::unique_lock lock(g_compilePipelineMutex); - while (g_compilePipelineRequests.empty()) - g_compilePipelineCondVar.wait(lock); - - PipelineCompiler* request = g_compilePipelineRequests.front(); - - g_compilePipelineRequests.pop(); - - lock.unlock(); - - request->Compile(true, false, true); - delete request; - } -} - -void compilePipelineThread_init() -{ - uint32 numCompileThreads; - - uint32 cpuCoreCount = GetPhysicalCoreCount(); - if (cpuCoreCount <= 2) - numCompileThreads = 1; - else - numCompileThreads = 2 + (cpuCoreCount - 3); // 2 plus one additionally for every extra core above 3 - - numCompileThreads = std::min(numCompileThreads, 8u); // cap at 8 - - for (uint32_t i = 0; i < numCompileThreads; i++) - { - std::thread compileThread(compilePipeline_thread, i); - compileThread.detach(); - } -} - -void compilePipelineThread_queue(PipelineCompiler* v) -{ - std::unique_lock lock(g_compilePipelineMutex); - g_compilePipelineRequests.push(std::move(v)); - lock.unlock(); - g_compilePipelineCondVar.notify_one(); -} - // make a guess if a pipeline is not essential // non-essential means that skipping these drawcalls shouldn't lead to permanently corrupted graphics bool VulkanRenderer::IsAsyncPipelineAllowed(uint32 numIndices) @@ -270,12 +213,6 @@ bool VulkanRenderer::IsAsyncPipelineAllowed(uint32 numIndices) // create graphics pipeline for current state PipelineInfo* VulkanRenderer::draw_createGraphicsPipeline(uint32 indexCount) { - if (!g_compilePipelineThreadInit) - { - compilePipelineThread_init(); - g_compilePipelineThreadInit = true; - } - const auto fetchShader = LatteSHRC_GetActiveFetchShader(); const auto vertexShader = LatteSHRC_GetActiveVertexShader(); const auto geometryShader = LatteSHRC_GetActiveGeometryShader(); @@ -313,7 +250,7 @@ PipelineInfo* VulkanRenderer::draw_createGraphicsPipeline(uint32 indexCount) if (pipelineCompiler->Compile(false, true, true) == false) { // shaders or pipeline not cached -> asynchronous compilation - compilePipelineThread_queue(pipelineCompiler); + PipelineCompiler::CompileThreadPool_QueueCompilation(pipelineCompiler); } else { @@ -379,7 +316,7 @@ float s_vkUniformData[512 * 4]; uint32 VulkanRenderer::uniformData_uploadUniformDataBufferGetOffset(std::span data) { const uint32 bufferAlignmentM1 = std::max(m_featureControl.limits.minUniformBufferOffsetAlignment, m_featureControl.limits.nonCoherentAtomSize) - 1; - const uint32 uniformSize = (data.size() + bufferAlignmentM1) & ~bufferAlignmentM1; + const uint32 uniformSize = ((uint32)data.size() + bufferAlignmentM1) & ~bufferAlignmentM1; auto waitWhileCondition = [&](std::function condition) { while (condition()) From 9f58f3a1188586125031979ea3eee1b757f9eb34 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Mon, 9 Feb 2026 07:08:01 +0100 Subject: [PATCH 7/8] coreinit: Fix initialization order Threads were initialized before SDA values were available from RPL loader. Fixes crash in Skylanders Swap Force and some other games --- src/Cafe/CafeSystem.cpp | 6 ++--- src/Cafe/OS/RPL/rpl.cpp | 21 ++++++++++++++++ src/Cafe/OS/RPL/rpl.h | 1 + src/Cafe/OS/libs/coreinit/coreinit.cpp | 18 ++++++++------ src/Cafe/OS/libs/coreinit/coreinit_Alarm.cpp | 5 +++- src/Cafe/OS/libs/coreinit/coreinit_Alarm.h | 1 + src/Cafe/OS/libs/coreinit/coreinit_IPC.cpp | 18 +++++++------- src/Cafe/OS/libs/coreinit/coreinit_IPC.h | 1 + src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp | 24 +++++++++---------- src/Cafe/OS/libs/coreinit/coreinit_Thread.h | 2 ++ 10 files changed, 65 insertions(+), 32 deletions(-) diff --git a/src/Cafe/CafeSystem.cpp b/src/Cafe/CafeSystem.cpp index d31a4a88..879085b9 100644 --- a/src/Cafe/CafeSystem.cpp +++ b/src/Cafe/CafeSystem.cpp @@ -427,10 +427,8 @@ void cemu_initForGame() cemuLog_log(LogType::Force, "------- Run title -------"); // wait till GPU thread is initialized while (g_isGPUInitFinished == false) std::this_thread::sleep_for(std::chrono::milliseconds(50)); - // init initial thread - OSThread_t* initialThread = coreinit::OSGetDefaultThread(1); - coreinit::OSSetThreadPriority(initialThread, 16); - coreinit::OSRunThread(initialThread, PPCInterpreter_makeCallableExportDepr(coreinit_start), 0, nullptr); + // run coreinit rpl_entry + RPLLoader_CallCoreinitEntrypoint(); // init AX and start AX I/O thread snd_core::AXOut_init(); } diff --git a/src/Cafe/OS/RPL/rpl.cpp b/src/Cafe/OS/RPL/rpl.cpp index d39ea95e..b906b6b1 100644 --- a/src/Cafe/OS/RPL/rpl.cpp +++ b/src/Cafe/OS/RPL/rpl.cpp @@ -2305,6 +2305,25 @@ void RPLLoader_CallEntrypoints() } } +// calls the entrypoint of coreinit and marks it as called so that RPLLoader_CallEntrypoints() wont call it again later +void RPLLoader_CallCoreinitEntrypoint() +{ + // for HLE modules we need to check the dependency list + for (auto& dependency : rplDependencyList) + { + if (strcmp(dependency->modulename, "coreinit") != 0) + continue; + if (!dependency->rplHLEModule) + continue; + if (dependency->hleEntrypointCalled) + continue; + dependency->rplHLEModule->rpl_entry(dependency->coreinitHandle, coreinit::RplEntryReason::Loaded); + dependency->hleEntrypointCalled = true; + return; + } + cemu_assert_unimplemented(); // coreinit.rpl present in cafelibs? We currently do not support native coreinit and no thread context exists yet to do a PPC call +} + void RPLLoader_NotifyControlPassedToApplication() { rplLoader_applicationHasMemoryControl = true; @@ -2350,11 +2369,13 @@ uint32 RPLLoader_FindModuleOrHLEExport(uint32 moduleHandle, bool isData, const c uint32 RPLLoader_GetSDA1Base() { + cemu_assert_debug(rplModuleCount > 0); // this should not be called before the main executable was loaded return rplLoader_sdataAddr; } uint32 RPLLoader_GetSDA2Base() { + cemu_assert_debug(rplModuleCount > 0); return rplLoader_sdata2Addr; } diff --git a/src/Cafe/OS/RPL/rpl.h b/src/Cafe/OS/RPL/rpl.h index b56411c4..6d161d2b 100644 --- a/src/Cafe/OS/RPL/rpl.h +++ b/src/Cafe/OS/RPL/rpl.h @@ -25,6 +25,7 @@ void RPLLoader_SetMainModule(RPLModule* rplLoaderContext); uint32 RPLLoader_GetMainModuleHandle(); void RPLLoader_CallEntrypoints(); +void RPLLoader_CallCoreinitEntrypoint(); void RPLLoader_NotifyControlPassedToApplication(); void RPLLoader_AddDependency(std::string_view name); diff --git a/src/Cafe/OS/libs/coreinit/coreinit.cpp b/src/Cafe/OS/libs/coreinit/coreinit.cpp index 8f783164..9ec370c3 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit.cpp @@ -331,7 +331,7 @@ namespace coreinit // init GHS and threads coreinit::PrepareGHSRuntime(); - coreinit::InitializeThread(); + coreinit::MapThreadExports(); // reset threads activeThreadCount = 0; @@ -353,13 +353,13 @@ namespace coreinit coreinit::InitializeLC(); coreinit::InitializeMP(); coreinit::InitializeTimeAndCalendar(); - coreinit::InitializeAlarm(); + coreinit::MapAlarmExports(); coreinit::InitializeFS(); coreinit::InitializeSystemInfo(); coreinit::InitializeConcurrency(); coreinit::InitializeSpinlock(); coreinit::InitializeMessageQueue(); - coreinit::InitializeIPC(); + coreinit::MapIPCExports(); coreinit::InitializeIPCBuf(); coreinit::InitializeMemoryMapping(); coreinit::InitializeCodeGen(); @@ -373,16 +373,20 @@ namespace coreinit coreinit::miscInit(); osLib_addFunction("coreinit", "OSGetSharedData", coreinitExport_OSGetSharedData); osLib_addFunction("coreinit", "UCReadSysConfig", coreinitExport_UCReadSysConfig); - - // async callbacks - InitializeAsyncCallback(); }; void rpl_entry(uint32 moduleHandle, coreinit::RplEntryReason reason) override { if (reason == coreinit::RplEntryReason::Loaded) { - // todo + coreinit::InitializeThread(); + coreinit::InitializeAlarm(); + coreinit::InitializeIPC(); + InitializeAsyncCallback(); + // remaining coreinit initialization happens in coreinit_start and requires a valid PPC context + OSThread_t* initialThread = coreinit::OSGetDefaultThread(1); + coreinit::OSSetThreadPriority(initialThread, 16); + coreinit::OSRunThread(initialThread, PPCInterpreter_makeCallableExportDepr(coreinit_start), 0, nullptr); } else if (reason == coreinit::RplEntryReason::Unloaded) { diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Alarm.cpp b/src/Cafe/OS/libs/coreinit/coreinit_Alarm.cpp index ae2d1e63..be5e9a7f 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_Alarm.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_Alarm.cpp @@ -349,7 +349,7 @@ namespace coreinit } } - void InitializeAlarm() + void MapAlarmExports() { cafeExportRegister("coreinit", OSCreateAlarm, LogType::CoreinitAlarm); cafeExportRegister("coreinit", OSCreateAlarmEx, LogType::CoreinitAlarm); @@ -358,7 +358,10 @@ namespace coreinit cafeExportRegister("coreinit", OSSetPeriodicAlarm, LogType::CoreinitAlarm); cafeExportRegister("coreinit", OSSetAlarmUserData, LogType::CoreinitAlarm); cafeExportRegister("coreinit", OSGetAlarmUserData, LogType::CoreinitAlarm); + } + void InitializeAlarm() + { // init event OSInitEvent(g_alarmEvent.GetPtr(), OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, OSEvent::EVENT_MODE::MODE_AUTO); diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Alarm.h b/src/Cafe/OS/libs/coreinit/coreinit_Alarm.h index 472d4f21..6f4964f1 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_Alarm.h +++ b/src/Cafe/OS/libs/coreinit/coreinit_Alarm.h @@ -49,5 +49,6 @@ namespace coreinit void alarm_update(); + void MapAlarmExports(); void InitializeAlarm(); } \ No newline at end of file diff --git a/src/Cafe/OS/libs/coreinit/coreinit_IPC.cpp b/src/Cafe/OS/libs/coreinit/coreinit_IPC.cpp index 12d83afc..19a3b69c 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_IPC.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_IPC.cpp @@ -445,15 +445,8 @@ namespace coreinit return r; } - void InitializeIPC() + void MapIPCExports() { - for (uint32 i = 0; i < Espresso::CORE_COUNT; i++) - { - IPCDriver_InitForCore(i); - IPCDriver_InitIPCThread(i); - } - - // register API cafeExportRegister("coreinit", IOS_Open, LogType::PPC_IPC); cafeExportRegister("coreinit", IOS_Close, LogType::PPC_IPC); cafeExportRegister("coreinit", IOS_Ioctl, LogType::PPC_IPC); @@ -462,4 +455,13 @@ namespace coreinit cafeExportRegister("coreinit", IOS_IoctlvAsync, LogType::PPC_IPC); } + void InitializeIPC() + { + for (uint32 i = 0; i < Espresso::CORE_COUNT; i++) + { + IPCDriver_InitForCore(i); + IPCDriver_InitIPCThread(i); + } + } + }; diff --git a/src/Cafe/OS/libs/coreinit/coreinit_IPC.h b/src/Cafe/OS/libs/coreinit/coreinit_IPC.h index 362c4725..2915ddc5 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_IPC.h +++ b/src/Cafe/OS/libs/coreinit/coreinit_IPC.h @@ -12,5 +12,6 @@ namespace coreinit IOS_ERROR IOS_Ioctlv(IOSDevHandle devHandle, uint32 requestId, uint32 numIn, uint32 numOut, IPCIoctlVector* vec); IOS_ERROR IOS_IoctlvAsync(IOSDevHandle devHandle, uint32 requestId, uint32 numIn, uint32 numOut, IPCIoctlVector* vec, MEMPTR asyncResultFunc, MEMPTR asyncResultUserParam); + void MapIPCExports(); void InitializeIPC(); }; \ No newline at end of file diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp b/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp index 19391bbd..8f3b9e0a 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp @@ -1588,7 +1588,7 @@ namespace coreinit } } - void InitializeThread() + void MapThreadExports() { cafeExportRegister("coreinit", OSCreateThreadType, LogType::CoreinitThread); cafeExportRegister("coreinit", OSCreateThread, LogType::CoreinitThread); @@ -1632,16 +1632,16 @@ namespace coreinit // OSThreadQueue cafeExportRegister("coreinit", OSInitThreadQueue, LogType::CoreinitThread); cafeExportRegister("coreinit", OSInitThreadQueueEx, LogType::CoreinitThread); - - OSInitThreadQueue(g_activeThreadQueue.GetPtr()); - for (sint32 i = 0; i < PPC_CORE_COUNT; i++) - OSInitThreadQueue(g_coreRunQueue.GetPtr() + i); - - for (sint32 i = 0; i < PPC_CORE_COUNT; i++) - __currentCoreThread[i] = nullptr; - - __OSInitDefaultThreads(); - __OSInitTerminatorThreads(); - } + + void InitializeThread() + { + OSInitThreadQueue(g_activeThreadQueue.GetPtr()); + for (sint32 i = 0; i < Espresso::CORE_COUNT; i++) + OSInitThreadQueue(g_coreRunQueue.GetPtr() + i); + for (sint32 i = 0; i < Espresso::CORE_COUNT; i++) + __currentCoreThread[i] = nullptr; + __OSInitDefaultThreads(); + __OSInitTerminatorThreads(); + } } diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Thread.h b/src/Cafe/OS/libs/coreinit/coreinit_Thread.h index 1a93022b..a0517d9a 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_Thread.h +++ b/src/Cafe/OS/libs/coreinit/coreinit_Thread.h @@ -503,7 +503,9 @@ static_assert(sizeof(OSThread_t) == 0x6A0); namespace coreinit { + void MapThreadExports(); void InitializeThread(); + void InitializeConcurrency(); bool __CemuIsMulticoreMode(); From 3b5c6f5246e1e94bda623f618f71a37de22578b3 Mon Sep 17 00:00:00 2001 From: Crementif <26669564+Crementif@users.noreply.github.com> Date: Mon, 9 Feb 2026 14:10:19 +0100 Subject: [PATCH 8/8] Fix dark mode in download manager --- .../components/wxDownloadManagerList.cpp | 36 +++++++++++++------ 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/src/gui/wxgui/components/wxDownloadManagerList.cpp b/src/gui/wxgui/components/wxDownloadManagerList.cpp index 132435ae..e3919cff 100644 --- a/src/gui/wxgui/components/wxDownloadManagerList.cpp +++ b/src/gui/wxgui/components/wxDownloadManagerList.cpp @@ -142,6 +142,10 @@ wxString wxDownloadManagerList::OnGetItemText(long item, long column) const wxItemAttr* wxDownloadManagerList::OnGetItemAttr(long item) const { const auto entry = GetTitleEntry(item); + + const wxColour bgColour = GetBackgroundColour(); + const bool isDarkTheme = wxSystemSettings::GetAppearance().IsDark(); + if (entry.has_value()) { auto& entryData = entry.value(); @@ -149,26 +153,38 @@ wxItemAttr* wxDownloadManagerList::OnGetItemAttr(long item) const entryData.status == TitleDownloadStatus::Verifying || entryData.status == TitleDownloadStatus::Installing) { - const wxColour kActiveColor{ 0xFFE0E0 }; - static wxListItemAttr s_error_attr(GetTextColour(), kActiveColor, GetFont()); - return &s_error_attr; + const wxColour kActiveColor = isDarkTheme ? wxColour(80, 40, 40) : wxColour(0xFFE0E0); + static wxListItemAttr s_active_attr; + s_active_attr.SetBackgroundColour(kActiveColor); + s_active_attr.SetTextColour(GetTextColour()); + s_active_attr.SetFont(GetFont()); + return &s_active_attr; } else if (entryData.status == TitleDownloadStatus::Installed && entryData.isPackage) { - const wxColour kActiveColor{ 0xE0FFE0 }; - static wxListItemAttr s_error_attr(GetTextColour(), kActiveColor, GetFont()); - return &s_error_attr; + const wxColour kActiveColor = isDarkTheme ? wxColour(40, 80, 40) : wxColour(0xE0FFE0); + static wxListItemAttr s_installed_attr; + s_installed_attr.SetBackgroundColour(kActiveColor); + s_installed_attr.SetTextColour(GetTextColour()); + s_installed_attr.SetFont(GetFont()); + return &s_installed_attr; } else if (entryData.status == TitleDownloadStatus::Error) { - const wxColour kActiveColor{ 0xCCCCF2 }; - static wxListItemAttr s_error_attr(GetTextColour(), kActiveColor, GetFont()); + const wxColour kActiveColor = isDarkTheme ? wxColour(40, 40, 80) : wxColour(0xCCCCF2); + static wxListItemAttr s_error_attr; + s_error_attr.SetBackgroundColour(kActiveColor); + s_error_attr.SetTextColour(GetTextColour()); + s_error_attr.SetFont(GetFont()); return &s_error_attr; } } - wxColour bgColourSecondary = wxHelper::CalculateAccentColour(GetBackgroundColour()); - static wxListItemAttr s_coloured_attr(GetTextColour(), bgColourSecondary, GetFont()); + wxColour bgColourSecondary = wxHelper::CalculateAccentColour(bgColour); + static wxListItemAttr s_coloured_attr; + s_coloured_attr.SetBackgroundColour(bgColourSecondary); + s_coloured_attr.SetTextColour(GetTextColour()); + s_coloured_attr.SetFont(GetFont()); return item % 2 == 0 ? nullptr : &s_coloured_attr; }