gl: Fix context race condition when preparing optimized interpreter variants

- The main thread may bind the program before the compiler context has published the binding instructions
- Once bound, late bindings are not updated so the program remains with uninitialized bindings and rendering breaks
This commit is contained in:
kd-11 2026-04-14 00:48:48 +03:00 committed by kd-11
parent c350c02592
commit 3fc41b6d66
3 changed files with 34 additions and 9 deletions

View File

@ -7,7 +7,6 @@
#include "Emu/RSX/rsx_methods.h" #include "Emu/RSX/rsx_methods.h"
#include "Emu/RSX/Overlays/Shaders/shader_loading_dialog.h" #include "Emu/RSX/Overlays/Shaders/shader_loading_dialog.h"
#include "Emu/RSX/Program/ShaderInterpreter.h"
#include "Emu/RSX/Program/GLSLCommon.h" #include "Emu/RSX/Program/GLSLCommon.h"
namespace gl namespace gl
@ -108,7 +107,8 @@ namespace gl
} }
auto data = new interpreter::cached_program(); auto data = new interpreter::cached_program();
data->flags |= CACHED_PIPE_UNOPTIMIZED; data->flags = base_pipeline->second->flags | CACHED_PIPE_UNOPTIMIZED;
data->build_compiler_options = base_pipeline->second->build_compiler_options;
data->allocator = base_pipeline->second->allocator; data->allocator = base_pipeline->second->allocator;
data->vertex_shader = base_pipeline->second->vertex_shader; data->vertex_shader = base_pipeline->second->vertex_shader;
data->fragment_shader = base_pipeline->second->fragment_shader; data->fragment_shader = base_pipeline->second->fragment_shader;
@ -203,6 +203,13 @@ namespace gl
m_current_interpreter->flags |= CACHED_PIPE_RECOMPILING; m_current_interpreter->flags |= CACHED_PIPE_RECOMPILING;
build_program_async(opt, {}); build_program_async(opt, {});
} }
if (m_current_interpreter->flags & CACHED_PIPE_UNINITIALIZED)
{
m_current_interpreter->prog->sync();
init_program(m_current_interpreter, m_current_interpreter->build_compiler_options);
}
return m_current_interpreter->prog.get(); return m_current_interpreter->prog.get();
} }
} }
@ -491,7 +498,8 @@ namespace gl
attach(*data->fragment_shader). attach(*data->fragment_shader).
link(); link();
post_init_hook(data, compiler_options); init_program(data, compiler_options);
store_program(data, compiler_options);
return data; return data;
} }
@ -510,8 +518,11 @@ namespace gl
auto storage_hook = [=, this](std::unique_ptr<glsl::program>& prog) auto storage_hook = [=, this](std::unique_ptr<glsl::program>& prog)
{ {
// NOTE: We need to do the program bindings in the consumer's context, so we skip the post-init hook and just set a flag
// The consumer will handle initializing the bindings
// The synchronization problem doesn't matter much on Windows driver, but Mesa drivers actually care about it.
data->prog = std::move(prog); data->prog = std::move(prog);
post_init_hook(data, compiler_options); store_program(data, compiler_options);
if (callback) if (callback)
{ {
@ -528,7 +539,7 @@ namespace gl
); );
} }
void shader_interpreter::post_init_hook(const std::shared_ptr<interpreter::cached_program>& data, u64 compiler_options) void shader_interpreter::init_program(const std::shared_ptr<interpreter::cached_program>& data, u64 compiler_options)
{ {
data->prog->uniforms[0] = GL_STREAM_BUFFER_START + 0; data->prog->uniforms[0] = GL_STREAM_BUFFER_START + 0;
data->prog->uniforms[1] = GL_STREAM_BUFFER_START + 1; data->prog->uniforms[1] = GL_STREAM_BUFFER_START + 1;
@ -551,6 +562,13 @@ namespace gl
} }
} }
data->flags &= ~CACHED_PIPE_UNINITIALIZED;
}
void shader_interpreter::store_program(const std::shared_ptr<interpreter::cached_program>& data, u64 compiler_options)
{
data->build_compiler_options = compiler_options;
std::lock_guard lock(m_program_cache_lock); std::lock_guard lock(m_program_cache_lock);
m_program_cache[compiler_options] = data; m_program_cache[compiler_options] = data;
} }

View File

@ -1,6 +1,7 @@
#pragma once #pragma once
#include "glutils/program.h" #include "glutils/program.h"
#include "../Program/ProgramStateCache.h" #include "../Program/ProgramStateCache.h"
#include "../Program/ShaderInterpreter.h"
#include "../Common/TextureUtils.h" #include "../Common/TextureUtils.h"
#include <unordered_map> #include <unordered_map>
@ -64,7 +65,11 @@ namespace gl
struct cached_program struct cached_program
{ {
u32 flags = 0; u32 flags = program_common::interpreter::CACHED_PIPE_UNINITIALIZED;
// Compiler options mask - May not always match the storage compiler options in case of compatible pipelines
// However the storage mask must be a subset of this options mask
u32 build_compiler_options = 0;
std::shared_ptr<glsl::shader> vertex_shader; std::shared_ptr<glsl::shader> vertex_shader;
std::shared_ptr<glsl::shader> fragment_shader; std::shared_ptr<glsl::shader> fragment_shader;
@ -92,7 +97,8 @@ namespace gl
std::shared_ptr<interpreter::cached_program> build_program(u64 compiler_options); std::shared_ptr<interpreter::cached_program> build_program(u64 compiler_options);
void build_program_async(u64 compiler_options, async_build_callback_t callback); void build_program_async(u64 compiler_options, async_build_callback_t callback);
void post_init_hook(const std::shared_ptr<interpreter::cached_program>& data, u64 compiler_options); void init_program(const std::shared_ptr<interpreter::cached_program>& data, u64 compiler_options);
void store_program(const std::shared_ptr<interpreter::cached_program>& data, u64 compiler_options);
std::shared_ptr<interpreter::cached_program> m_current_interpreter; std::shared_ptr<interpreter::cached_program> m_current_interpreter;

View File

@ -47,8 +47,9 @@ namespace program_common
enum cached_pipeline_flags : u32 enum cached_pipeline_flags : u32
{ {
CACHED_PIPE_UNOPTIMIZED = (1 << 0), CACHED_PIPE_UNOPTIMIZED = (1 << 0),
CACHED_PIPE_RECOMPILING = (1 << 1), CACHED_PIPE_RECOMPILING = (1 << 1),
CACHED_PIPE_UNINITIALIZED = (1 << 2),
}; };
[[maybe_unused]] static std::string get_vertex_interpreter() [[maybe_unused]] static std::string get_vertex_interpreter()