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/Overlays/Shaders/shader_loading_dialog.h"
#include "Emu/RSX/Program/ShaderInterpreter.h"
#include "Emu/RSX/Program/GLSLCommon.h"
namespace gl
@ -108,7 +107,8 @@ namespace gl
}
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->vertex_shader = base_pipeline->second->vertex_shader;
data->fragment_shader = base_pipeline->second->fragment_shader;
@ -203,6 +203,13 @@ namespace gl
m_current_interpreter->flags |= CACHED_PIPE_RECOMPILING;
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();
}
}
@ -491,7 +498,8 @@ namespace gl
attach(*data->fragment_shader).
link();
post_init_hook(data, compiler_options);
init_program(data, compiler_options);
store_program(data, compiler_options);
return data;
}
@ -510,8 +518,11 @@ namespace gl
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);
post_init_hook(data, compiler_options);
store_program(data, compiler_options);
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[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);
m_program_cache[compiler_options] = data;
}

View file

@ -1,6 +1,7 @@
#pragma once
#include "glutils/program.h"
#include "../Program/ProgramStateCache.h"
#include "../Program/ShaderInterpreter.h"
#include "../Common/TextureUtils.h"
#include <unordered_map>
@ -64,7 +65,11 @@ namespace gl
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> fragment_shader;
@ -92,7 +97,8 @@ namespace gl
std::shared_ptr<interpreter::cached_program> build_program(u64 compiler_options);
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;

View file

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