// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) #include "postprocessing_shader_glsl.h" #include "shadergen.h" #include "common/file_system.h" #include "common/log.h" #include "common/string_util.h" #include #include #include Log_SetChannel(PostProcessing); namespace { class PostProcessingGLSLShaderGen : public ShaderGen { public: PostProcessingGLSLShaderGen(RenderAPI render_api, bool supports_dual_source_blend, bool supports_framebuffer_fetch); ~PostProcessingGLSLShaderGen(); std::string GeneratePostProcessingVertexShader(const PostProcessing::GLSLShader& shader); std::string GeneratePostProcessingFragmentShader(const PostProcessing::GLSLShader& shader); private: void WriteUniformBuffer(std::stringstream& ss, const PostProcessing::GLSLShader& shader, bool use_push_constants); }; } // namespace PostProcessing::GLSLShader::GLSLShader() = default; PostProcessing::GLSLShader::GLSLShader(std::string name, std::string code) : m_code(code) { m_name = std::move(name); LoadOptions(); } PostProcessing::GLSLShader::~GLSLShader() = default; bool PostProcessing::GLSLShader::LoadFromFile(std::string name, const char* filename, Error* error) { std::optional code = FileSystem::ReadFileToString(filename, error); if (!code.has_value() || code->empty()) return false; return LoadFromString(std::move(name), code.value(), error); } bool PostProcessing::GLSLShader::LoadFromString(std::string name, std::string code, Error* error) { m_name = std::move(name); m_code = std::move(code); m_options.clear(); LoadOptions(); return true; } bool PostProcessing::GLSLShader::IsValid() const { return !m_name.empty() && !m_code.empty(); } bool PostProcessing::GLSLShader::WantsDepthBuffer() const { return false; } u32 PostProcessing::GLSLShader::GetUniformsSize() const { // lazy packing. todo improve. return sizeof(CommonUniforms) + (sizeof(ShaderOption::ValueVector) * static_cast(m_options.size())); } void PostProcessing::GLSLShader::FillUniformBuffer(void* buffer, s32 viewport_x, s32 viewport_y, s32 viewport_width, s32 viewport_height, u32 window_width, u32 window_height, s32 original_width, s32 original_height, s32 native_width, s32 native_height, float time) const { CommonUniforms* common = static_cast(buffer); const float internal_pixel_width = static_cast(viewport_width) / static_cast(original_width); const float internal_pixel_height = static_cast(viewport_height) / static_cast(original_height); const float native_pixel_width = (static_cast(viewport_width) / static_cast(native_width)); const float native_pixel_height = (static_cast(viewport_height) / static_cast(native_height)); common->src_rect[0] = static_cast(viewport_x) / static_cast(window_width); common->src_rect[1] = static_cast(viewport_y) / static_cast(window_height); common->src_rect[2] = (static_cast(viewport_x + viewport_width - 1)) / static_cast(window_width); common->src_rect[3] = (static_cast(viewport_y + viewport_height - 1)) / static_cast(window_height); common->src_size[0] = static_cast(viewport_width) / static_cast(window_width); common->src_size[1] = static_cast(viewport_height) / static_cast(window_height); common->window_size[0] = static_cast(window_width); common->window_size[1] = static_cast(window_height); common->rcp_window_size[0] = 1.0f / static_cast(window_width); common->rcp_window_size[1] = 1.0f / static_cast(window_height); common->viewport_size[0] = static_cast(viewport_width); common->viewport_size[1] = static_cast(viewport_height); common->window_to_viewport_ratio[0] = static_cast(window_width) / static_cast(viewport_width); common->window_to_viewport_ratio[1] = static_cast(window_height) / static_cast(viewport_height); common->internal_size[0] = static_cast(original_width); common->internal_size[1] = static_cast(original_height); common->internal_pixel_size[0] = internal_pixel_width; common->internal_pixel_size[1] = internal_pixel_height; common->norm_internal_pixel_size[0] = internal_pixel_width / static_cast(window_width); common->norm_internal_pixel_size[1] = internal_pixel_height / static_cast(window_height); common->native_size[0] = static_cast(native_width); common->native_size[1] = static_cast(native_height); common->native_pixel_size[0] = native_pixel_width; common->native_pixel_size[1] = native_pixel_height; common->norm_native_pixel_size[0] = native_pixel_width / static_cast(window_width); common->norm_native_pixel_size[1] = native_pixel_height / static_cast(window_height); common->upscale_multiplier = static_cast(original_width) / static_cast(native_width); common->time = time; u8* option_values = reinterpret_cast(common + 1); for (const ShaderOption& option : m_options) { std::memcpy(option_values, option.value.data(), sizeof(ShaderOption::ValueVector)); option_values += sizeof(ShaderOption::ValueVector); } } bool PostProcessing::GLSLShader::CompilePipeline(GPUTexture::Format format, u32 width, u32 height, ProgressCallback* progress) { if (m_pipeline) m_pipeline.reset(); PostProcessingGLSLShaderGen shadergen(g_gpu_device->GetRenderAPI(), g_gpu_device->GetFeatures().dual_source_blend, g_gpu_device->GetFeatures().framebuffer_fetch); std::unique_ptr vs = g_gpu_device->CreateShader(GPUShaderStage::Vertex, shadergen.GetLanguage(), shadergen.GeneratePostProcessingVertexShader(*this)); std::unique_ptr fs = g_gpu_device->CreateShader(GPUShaderStage::Fragment, shadergen.GetLanguage(), shadergen.GeneratePostProcessingFragmentShader(*this)); if (!vs || !fs) return false; GPUPipeline::GraphicsConfig plconfig; plconfig.layout = GPUPipeline::Layout::SingleTextureAndUBO; plconfig.primitive = GPUPipeline::Primitive::Triangles; plconfig.SetTargetFormats(format); plconfig.rasterization = GPUPipeline::RasterizationState::GetNoCullState(); plconfig.depth = GPUPipeline::DepthState::GetNoTestsState(); plconfig.blend = GPUPipeline::BlendState::GetNoBlendingState(); plconfig.samples = 1; plconfig.per_sample_shading = false; plconfig.render_pass_flags = GPUPipeline::NoRenderPassFlags; plconfig.vertex_shader = vs.get(); plconfig.fragment_shader = fs.get(); plconfig.geometry_shader = nullptr; if (!(m_pipeline = g_gpu_device->CreatePipeline(plconfig))) return false; if (!m_sampler) { GPUSampler::Config config = GPUSampler::GetNearestConfig(); config.address_u = GPUSampler::AddressMode::ClampToBorder; config.address_v = GPUSampler::AddressMode::ClampToBorder; config.border_color = 0xFF000000u; if (!(m_sampler = g_gpu_device->CreateSampler(config))) return false; } return true; } bool PostProcessing::GLSLShader::Apply(GPUTexture* input_color, GPUTexture* input_depth, GPUTexture* final_target, GSVector4i final_rect, s32 orig_width, s32 orig_height, s32 native_width, s32 native_height, u32 target_width, u32 target_height) { GL_SCOPE_FMT("GLSL Shader {}", m_name); // Assumes final stage has been cleared already. if (!final_target) { if (!g_gpu_device->BeginPresent(false)) return false; } else { g_gpu_device->SetRenderTargets(&final_target, 1, nullptr); g_gpu_device->ClearRenderTarget(final_target, 0); // TODO: Could use an invalidate here too. } g_gpu_device->SetPipeline(m_pipeline.get()); g_gpu_device->SetTextureSampler(0, input_color, m_sampler.get()); g_gpu_device->SetViewportAndScissor(final_rect); const u32 uniforms_size = GetUniformsSize(); void* uniforms = g_gpu_device->MapUniformBuffer(uniforms_size); FillUniformBuffer(uniforms, final_rect.left, final_rect.top, final_rect.width(), final_rect.height(), target_width, target_height, orig_width, orig_height, native_width, native_height, static_cast(PostProcessing::GetTimer().GetTimeSeconds())); g_gpu_device->UnmapUniformBuffer(uniforms_size); g_gpu_device->Draw(3, 0); return true; } bool PostProcessing::GLSLShader::ResizeOutput(GPUTexture::Format format, u32 width, u32 height) { return true; } void PostProcessing::GLSLShader::LoadOptions() { // Adapted from Dolphin's PostProcessingConfiguration::LoadOptions(). constexpr char config_start_delimiter[] = "[configuration]"; constexpr char config_end_delimiter[] = "[/configuration]"; size_t configuration_start = m_code.find(config_start_delimiter); size_t configuration_end = m_code.find(config_end_delimiter); if (configuration_start == std::string::npos || configuration_end == std::string::npos) { // Issue loading configuration or there isn't one. return; } std::string configuration_string = m_code.substr(configuration_start + std::strlen(config_start_delimiter), configuration_end - configuration_start - std::strlen(config_start_delimiter)); std::istringstream in(configuration_string); ShaderOption current_option = {}; while (!in.eof()) { std::string line_str; if (std::getline(in, line_str)) { std::string_view line_view = line_str; // Check for CRLF eol and convert it to LF if (!line_view.empty() && line_view.at(line_view.size() - 1) == '\r') line_view.remove_suffix(1); if (line_view.empty()) continue; if (line_view[0] == '[') { size_t endpos = line_view.find("]"); if (endpos != std::string::npos) { if (current_option.type != ShaderOption::Type::Invalid) { current_option.value = current_option.default_value; if (current_option.ui_name.empty()) current_option.ui_name = current_option.name; if (!current_option.name.empty() && current_option.vector_size > 0) m_options.push_back(std::move(current_option)); current_option = {}; } // New section! std::string_view sub = line_view.substr(1, endpos - 1); if (sub == "OptionBool") current_option.type = ShaderOption::Type::Bool; else if (sub == "OptionRangeFloat") current_option.type = ShaderOption::Type::Float; else if (sub == "OptionRangeInteger") current_option.type = ShaderOption::Type::Int; else ERROR_LOG("Invalid option type: '{}'", line_str); continue; } } if (current_option.type == ShaderOption::Type::Invalid) continue; std::string_view key, value; ParseKeyValue(line_view, &key, &value); if (!key.empty() && !value.empty()) { if (key == "GUIName") { current_option.ui_name = value; } else if (key == "OptionName") { current_option.name = value; } else if (key == "DependentOption") { current_option.dependent_option = value; } else if (key == "MinValue" || key == "MaxValue" || key == "DefaultValue" || key == "StepAmount") { ShaderOption::ValueVector* dst_array; if (key == "MinValue") dst_array = ¤t_option.min_value; else if (key == "MaxValue") dst_array = ¤t_option.max_value; else if (key == "DefaultValue") dst_array = ¤t_option.default_value; else // if (key == "StepAmount") dst_array = ¤t_option.step_value; u32 size = 0; if (current_option.type == ShaderOption::Type::Bool) (*dst_array)[size++].int_value = StringUtil::FromChars(value).value_or(false) ? 1 : 0; else if (current_option.type == ShaderOption::Type::Float) size = PostProcessing::ShaderOption::ParseFloatVector(value, dst_array); else if (current_option.type == ShaderOption::Type::Int) size = PostProcessing::ShaderOption::ParseIntVector(value, dst_array); current_option.vector_size = (current_option.vector_size == 0) ? size : std::min(current_option.vector_size, size); } else { ERROR_LOG("Invalid option key: '{}'", line_str); } } } } if (current_option.type != ShaderOption::Type::Invalid && !current_option.name.empty() && current_option.vector_size > 0) { current_option.value = current_option.default_value; if (current_option.ui_name.empty()) current_option.ui_name = current_option.name; m_options.push_back(std::move(current_option)); } } PostProcessingGLSLShaderGen::PostProcessingGLSLShaderGen(RenderAPI render_api, bool supports_dual_source_blend, bool supports_framebuffer_fetch) : ShaderGen(render_api, GPUShaderLanguage::GLSLVK, supports_dual_source_blend, supports_framebuffer_fetch) { } PostProcessingGLSLShaderGen::~PostProcessingGLSLShaderGen() = default; std::string PostProcessingGLSLShaderGen::GeneratePostProcessingVertexShader(const PostProcessing::GLSLShader& shader) { std::stringstream ss; WriteHeader(ss); WriteUniformBuffer(ss, shader, false); DeclareTexture(ss, "samp0", 0); DeclareVertexEntryPoint(ss, {}, 0, 1, {}, true); ss << R"( { v_tex0 = float2(float((v_id << 1) & 2u), float(v_id & 2u)); v_pos = float4(v_tex0 * float2(2.0f, -2.0f) + float2(-1.0f, 1.0f), 0.0f, 1.0f); #if API_OPENGL || API_OPENGL_ES || API_VULKAN v_pos.y = -v_pos.y; #endif v_tex0 = u_src_rect.xy + (u_src_size * v_tex0); } )"; return ss.str(); } std::string PostProcessingGLSLShaderGen::GeneratePostProcessingFragmentShader(const PostProcessing::GLSLShader& shader) { std::stringstream ss; WriteHeader(ss); WriteUniformBuffer(ss, shader, false); DeclareTexture(ss, "samp0", 0); ss << R"( layout(location = 0) in VertexData { vec2 v_tex0; }; layout(location = 0) out float4 o_col0; float4 Sample() { return texture(samp0, v_tex0); } float4 SampleLocation(float2 location) { return texture(samp0, location); } #define SampleOffset(offset) textureOffset(samp0, v_tex0, offset) float2 GetFragCoord() { return gl_FragCoord.xy; } float2 GetCoordinates() { return v_tex0; } float2 GetWindowSize() { return u_window_size; } float2 GetInvWindowSize() { return u_rcp_window_size; } float2 GetViewportSize() { return u_viewport_size; } float2 GetWindowToViewportRatio() { return u_window_to_viewport_ratio; } float2 GetInternalSize() { return u_internal_size; } float2 GetInternalPixelSize() { return u_internal_pixel_size; } float2 GetInvInternalPixelSize() { return u_norm_internal_pixel_size; } float2 GetNativeSize() { return u_native_size; } float2 GetNativePixelSize() { return u_native_pixel_size; } float2 GetInvNativePixelSize() { return u_norm_native_pixel_size; } float GetUpscaleMultiplier() { return u_upscale_multiplier; } float GetTime() { return u_time; } void SetOutput(float4 color) { o_col0 = color; } // Deprecated, only present for backwards compatibility. float2 GetResolution() { return u_window_size; } float2 GetInvResolution() { return u_rcp_window_size; } float2 GetOriginalSize() { return u_internal_size; } float2 GetPaddedOriginalSize() { return u_internal_size * u_window_to_viewport_ratio; } float2 GetWindowResolution() { return u_window_size; } #define GetOption(x) (x) #define OptionEnabled(x) ((x) != 0) )"; ss << shader.GetCode(); return ss.str(); } void PostProcessingGLSLShaderGen::WriteUniformBuffer(std::stringstream& ss, const PostProcessing::GLSLShader& shader, bool use_push_constants) { u32 pad_counter = 0; WriteUniformBufferDeclaration(ss, use_push_constants); ss << "{\n"; ss << " float4 u_src_rect;\n"; ss << " float2 u_src_size;\n"; ss << " float2 u_window_size;\n"; ss << " float2 u_rcp_window_size;\n"; ss << " float2 u_viewport_size;\n"; ss << " float2 u_window_to_viewport_ratio;\n"; ss << " float2 u_internal_size;\n"; ss << " float2 u_internal_pixel_size;\n"; ss << " float2 u_norm_internal_pixel_size;\n"; ss << " float2 u_native_size;\n"; ss << " float2 u_native_pixel_size;\n"; ss << " float2 u_norm_native_pixel_size;\n"; ss << " float u_upscale_multiplier;\n"; ss << " float u_time;\n"; ss << "\n"; static constexpr std::array vector_size_suffix = {{"", "", "2", "3", "4"}}; for (const PostProcessing::ShaderOption& option : shader.GetOptions()) { switch (option.type) { case PostProcessing::ShaderOption::Type::Bool: ss << " int " << option.name << ";\n"; for (u32 i = option.vector_size; i < PostProcessing::ShaderOption::MAX_VECTOR_COMPONENTS; i++) ss << " int u_ubo_pad" << (pad_counter++) << ";\n"; break; case PostProcessing::ShaderOption::Type::Int: { ss << " int" << vector_size_suffix[option.vector_size] << " " << option.name << ";\n"; for (u32 i = option.vector_size; i < PostProcessing::ShaderOption::MAX_VECTOR_COMPONENTS; i++) ss << " int u_ubo_pad" << (pad_counter++) << ";\n"; } break; case PostProcessing::ShaderOption::Type::Float: default: { ss << " float" << vector_size_suffix[option.vector_size] << " " << option.name << ";\n"; for (u32 i = option.vector_size; i < PostProcessing::ShaderOption::MAX_VECTOR_COMPONENTS; i++) ss << " float u_ubo_pad" << (pad_counter++) << ";\n"; } break; } } ss << "};\n\n"; }