// Copyright 2018 Citra Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. #include #include #include #include "video_core/renderer_opengl/gl_shader_manager.h" static void SetShaderUniformBlockBinding(GLuint shader, const char* name, UniformBindings binding, size_t expected_size) { GLuint ub_index = glGetUniformBlockIndex(shader, name); if (ub_index == GL_INVALID_INDEX) { return; } GLint ub_size = 0; glGetActiveUniformBlockiv(shader, ub_index, GL_UNIFORM_BLOCK_DATA_SIZE, &ub_size); ASSERT_MSG(ub_size == expected_size, "Uniform block size did not match! Got %d, expected %zu", static_cast(ub_size), expected_size); glUniformBlockBinding(shader, ub_index, static_cast(binding)); } static void SetShaderUniformBlockBindings(GLuint shader) { SetShaderUniformBlockBinding(shader, "shader_data", UniformBindings::Common, sizeof(UniformData)); } static void SetShaderSamplerBinding(GLuint shader, const char* name, TextureUnits::TextureUnit binding) { GLint uniform_tex = glGetUniformLocation(shader, name); if (uniform_tex != -1) { glUniform1i(uniform_tex, binding.id); } } static void SetShaderSamplerBindings(GLuint shader) { OpenGLState cur_state = OpenGLState::GetCurState(); GLuint old_program = std::exchange(cur_state.draw.shader_program, shader); cur_state.Apply(); // Set the texture samplers to correspond to different texture units SetShaderSamplerBinding(shader, "tex0", TextureUnits::PicaTexture(0)); SetShaderSamplerBinding(shader, "tex1", TextureUnits::PicaTexture(1)); SetShaderSamplerBinding(shader, "tex2", TextureUnits::PicaTexture(2)); SetShaderSamplerBinding(shader, "tex_cube", TextureUnits::TextureCube); // Set the texture samplers to correspond to different lookup table texture units SetShaderSamplerBinding(shader, "lighting_lut", TextureUnits::LightingLUT); SetShaderSamplerBinding(shader, "fog_lut", TextureUnits::FogLUT); SetShaderSamplerBinding(shader, "proctex_noise_lut", TextureUnits::ProcTexNoiseLUT); SetShaderSamplerBinding(shader, "proctex_color_map", TextureUnits::ProcTexColorMap); SetShaderSamplerBinding(shader, "proctex_alpha_map", TextureUnits::ProcTexAlphaMap); SetShaderSamplerBinding(shader, "proctex_lut", TextureUnits::ProcTexLUT); SetShaderSamplerBinding(shader, "proctex_diff_lut", TextureUnits::ProcTexDiffLUT); cur_state.draw.shader_program = old_program; cur_state.Apply(); } /** * An object representing a shader program staging. It can be either a shader object or a program * object, depending on whether separable program is used. */ class OGLShaderStage { public: explicit OGLShaderStage(bool separable) { if (separable) { shader_or_program = OGLProgram(); } else { shader_or_program = OGLShader(); } } void Create(const char* source, GLenum type) { if (shader_or_program.which() == 0) { boost::get(shader_or_program).Create(source, type); } else { OGLShader shader; shader.Create(source, type); OGLProgram& program = boost::get(shader_or_program); program.Create(true, {shader.handle}); SetShaderUniformBlockBindings(program.handle); SetShaderSamplerBindings(program.handle); } } GLuint GetHandle() const { if (shader_or_program.which() == 0) { return boost::get(shader_or_program).handle; } else { return boost::get(shader_or_program).handle; } } private: boost::variant shader_or_program; }; class TrivialVertexShader { public: explicit TrivialVertexShader(bool separable) : program(separable) { program.Create(GLShader::GenerateTrivialVertexShader(separable).c_str(), GL_VERTEX_SHADER); } GLuint Get() const { return program.GetHandle(); } private: OGLShaderStage program; }; template class ShaderCache { public: explicit ShaderCache(bool separable) : separable(separable) {} GLuint Get(const KeyConfigType& config) { auto [iter, new_shader] = shaders.emplace(config, OGLShaderStage{separable}); OGLShaderStage& cached_shader = iter->second; if (new_shader) { cached_shader.Create(CodeGenerator(config, separable).c_str(), ShaderType); } return cached_shader.GetHandle(); } private: bool separable; std::unordered_map shaders; }; using FragmentShaders = ShaderCache; class ShaderProgramManager::Impl { public: explicit Impl(bool separable) : separable(separable), trivial_vertex_shader(separable), fragment_shaders(separable) { if (separable) pipeline.Create(); } struct ShaderTuple { GLuint vs = 0; GLuint gs = 0; GLuint fs = 0; bool operator==(const ShaderTuple& rhs) const { return std::tie(vs, gs, fs) == std::tie(rhs.vs, rhs.gs, rhs.fs); } bool operator!=(const ShaderTuple& rhs) const { return std::tie(vs, gs, fs) != std::tie(rhs.vs, rhs.gs, rhs.fs); } struct Hash { std::size_t operator()(const ShaderTuple& tuple) const { std::size_t hash = 0; boost::hash_combine(hash, tuple.vs); boost::hash_combine(hash, tuple.gs); boost::hash_combine(hash, tuple.fs); return hash; } }; }; ShaderTuple current; TrivialVertexShader trivial_vertex_shader; FragmentShaders fragment_shaders; bool separable; std::unordered_map program_cache; OGLPipeline pipeline; }; ShaderProgramManager::ShaderProgramManager(bool separable) : impl(std::make_unique(separable)) {} ShaderProgramManager::~ShaderProgramManager() = default; void ShaderProgramManager::UseTrivialVertexShader() { impl->current.vs = impl->trivial_vertex_shader.Get(); } void ShaderProgramManager::UseTrivialGeometryShader() { impl->current.gs = 0; } void ShaderProgramManager::UseFragmentShader(const GLShader::PicaShaderConfig& config) { impl->current.fs = impl->fragment_shaders.Get(config); } void ShaderProgramManager::ApplyTo(OpenGLState& state) { if (impl->separable) { // Without this reseting, AMD sometimes freezes when one stage is changed but not for the // others glUseProgramStages(impl->pipeline.handle, GL_VERTEX_SHADER_BIT | GL_GEOMETRY_SHADER_BIT | GL_FRAGMENT_SHADER_BIT, 0); glUseProgramStages(impl->pipeline.handle, GL_VERTEX_SHADER_BIT, impl->current.vs); glUseProgramStages(impl->pipeline.handle, GL_GEOMETRY_SHADER_BIT, impl->current.gs); glUseProgramStages(impl->pipeline.handle, GL_FRAGMENT_SHADER_BIT, impl->current.fs); state.draw.shader_program = 0; state.draw.program_pipeline = impl->pipeline.handle; } else { OGLProgram& cached_program = impl->program_cache[impl->current]; if (cached_program.handle == 0) { cached_program.Create(false, {impl->current.vs, impl->current.gs, impl->current.fs}); SetShaderUniformBlockBindings(cached_program.handle); SetShaderSamplerBindings(cached_program.handle); } state.draw.shader_program = cached_program.handle; } }