FrontendCommon: Add a post processing implementation

This commit is contained in:
Connor McLaughlin 2020-09-13 01:19:57 +10:00
parent 5804778339
commit 2819715260
25 changed files with 1828 additions and 22 deletions

View file

@ -99,6 +99,7 @@ void GPU_HW_OpenGL::ResetGraphicsAPIState()
if (m_resolution_scale > 1 && !m_supports_geometry_shaders) if (m_resolution_scale > 1 && !m_supports_geometry_shaders)
glLineWidth(1.0f); glLineWidth(1.0f);
glBindVertexArray(0); glBindVertexArray(0);
m_uniform_stream_buffer->Unbind();
} }
void GPU_HW_OpenGL::RestoreGraphicsAPIState() void GPU_HW_OpenGL::RestoreGraphicsAPIState()
@ -114,6 +115,7 @@ void GPU_HW_OpenGL::RestoreGraphicsAPIState()
if (m_resolution_scale > 1 && !m_supports_geometry_shaders) if (m_resolution_scale > 1 && !m_supports_geometry_shaders)
glLineWidth(static_cast<float>(m_resolution_scale)); glLineWidth(static_cast<float>(m_resolution_scale));
glBindVertexArray(m_vao_id); glBindVertexArray(m_vao_id);
m_uniform_stream_buffer->Bind();
SetScissorFromDrawingArea(); SetScissorFromDrawingArea();
m_batch_ubo_dirty = true; m_batch_ubo_dirty = true;

View file

@ -69,6 +69,8 @@ public:
virtual bool CreateResources() = 0; virtual bool CreateResources() = 0;
virtual void DestroyResources() = 0; virtual void DestroyResources() = 0;
virtual bool SetPostProcessingChain(const std::string_view& config) = 0;
/// Call when the window size changes externally to recreate any resources. /// Call when the window size changes externally to recreate any resources.
virtual void ResizeRenderWindow(s32 new_window_width, s32 new_window_height) = 0; virtual void ResizeRenderWindow(s32 new_window_width, s32 new_window_height) = 0;

View file

@ -396,6 +396,7 @@ void HostInterface::SetDefaultSettings(SettingsInterface& si)
si.SetBoolValue("Display", "ShowResolution", false); si.SetBoolValue("Display", "ShowResolution", false);
si.SetBoolValue("Display", "Fullscreen", false); si.SetBoolValue("Display", "Fullscreen", false);
si.SetBoolValue("Display", "VSync", true); si.SetBoolValue("Display", "VSync", true);
si.SetStringValue("Display", "PostProcessChain", "");
si.SetBoolValue("CDROM", "ReadThread", true); si.SetBoolValue("CDROM", "ReadThread", true);
si.SetBoolValue("CDROM", "RegionCheck", true); si.SetBoolValue("CDROM", "RegionCheck", true);

View file

@ -133,6 +133,7 @@ void Settings::Load(SettingsInterface& si)
display_show_speed = si.GetBoolValue("Display", "ShowSpeed", false); display_show_speed = si.GetBoolValue("Display", "ShowSpeed", false);
display_show_resolution = si.GetBoolValue("Display", "ShowResolution", false); display_show_resolution = si.GetBoolValue("Display", "ShowResolution", false);
video_sync_enabled = si.GetBoolValue("Display", "VSync", true); video_sync_enabled = si.GetBoolValue("Display", "VSync", true);
display_post_process_chain = si.GetStringValue("Display", "PostProcessChain", "");
cdrom_read_thread = si.GetBoolValue("CDROM", "ReadThread", true); cdrom_read_thread = si.GetBoolValue("CDROM", "ReadThread", true);
cdrom_region_check = si.GetBoolValue("CDROM", "RegionCheck", true); cdrom_region_check = si.GetBoolValue("CDROM", "RegionCheck", true);
@ -242,6 +243,10 @@ void Settings::Save(SettingsInterface& si) const
si.SetBoolValue("Display", "ShowSpeed", display_show_speed); si.SetBoolValue("Display", "ShowSpeed", display_show_speed);
si.SetBoolValue("Display", "ShowResolution", display_show_speed); si.SetBoolValue("Display", "ShowResolution", display_show_speed);
si.SetBoolValue("Display", "VSync", video_sync_enabled); si.SetBoolValue("Display", "VSync", video_sync_enabled);
if (display_post_process_chain.empty())
si.DeleteValue("Display", "PostProcessChain");
else
si.SetStringValue("Display", "PostProcessChain", display_post_process_chain.c_str());
si.SetBoolValue("CDROM", "ReadThread", cdrom_read_thread); si.SetBoolValue("CDROM", "ReadThread", cdrom_read_thread);
si.SetBoolValue("CDROM", "RegionCheck", cdrom_region_check); si.SetBoolValue("CDROM", "RegionCheck", cdrom_region_check);

View file

@ -84,6 +84,7 @@ struct Settings
GPURenderer gpu_renderer = GPURenderer::Software; GPURenderer gpu_renderer = GPURenderer::Software;
std::string gpu_adapter; std::string gpu_adapter;
std::string display_post_process_chain;
u32 gpu_resolution_scale = 1; u32 gpu_resolution_scale = 1;
bool gpu_use_debug_device = false; bool gpu_use_debug_device = false;
bool gpu_true_color = true; bool gpu_true_color = true;

View file

@ -169,8 +169,7 @@ void ShaderGen::WriteHeader(std::stringstream& ss)
ss << "\n"; ss << "\n";
} }
void ShaderGen::DeclareUniformBuffer(std::stringstream& ss, const std::initializer_list<const char*>& members, void ShaderGen::WriteUniformBufferDeclaration(std::stringstream& ss, bool push_constant_on_vulkan)
bool push_constant_on_vulkan)
{ {
if (IsVulkan()) if (IsVulkan())
{ {
@ -190,6 +189,12 @@ void ShaderGen::DeclareUniformBuffer(std::stringstream& ss, const std::initializ
{ {
ss << "cbuffer UBOBlock : register(b0)\n"; ss << "cbuffer UBOBlock : register(b0)\n";
} }
}
void ShaderGen::DeclareUniformBuffer(std::stringstream& ss, const std::initializer_list<const char*>& members,
bool push_constant_on_vulkan)
{
WriteUniformBufferDeclaration(ss, push_constant_on_vulkan);
ss << "{\n"; ss << "{\n";
for (const char* member : members) for (const char* member : members)

View file

@ -22,6 +22,7 @@ protected:
void SetGLSLVersionString(); void SetGLSLVersionString();
void DefineMacro(std::stringstream& ss, const char* name, bool enabled); void DefineMacro(std::stringstream& ss, const char* name, bool enabled);
void WriteHeader(std::stringstream& ss); void WriteHeader(std::stringstream& ss);
void WriteUniformBufferDeclaration(std::stringstream& ss, bool push_constant_on_vulkan);
void DeclareUniformBuffer(std::stringstream& ss, const std::initializer_list<const char*>& members, void DeclareUniformBuffer(std::stringstream& ss, const std::initializer_list<const char*>& members,
bool push_constant_on_vulkan); bool push_constant_on_vulkan);
void DeclareTexture(std::stringstream& ss, const char* name, u32 index); void DeclareTexture(std::stringstream& ss, const char* name, u32 index);

View file

@ -156,6 +156,11 @@ void LibretroHostDisplay::ResizeRenderWindow(s32 new_window_width, s32 new_windo
m_window_info.surface_height = new_window_height; m_window_info.surface_height = new_window_height;
} }
bool LibretroHostDisplay::SetPostProcessingChain(const std::string_view& config)
{
return false;
}
std::unique_ptr<HostDisplayTexture> LibretroHostDisplay::CreateTexture(u32 width, u32 height, const void* data, std::unique_ptr<HostDisplayTexture> LibretroHostDisplay::CreateTexture(u32 width, u32 height, const void* data,
u32 data_stride, bool dynamic) u32 data_stride, bool dynamic)
{ {

View file

@ -1,6 +1,5 @@
#pragma once #pragma once
#include "core/host_display.h" #include "core/host_display.h"
#include <memory>
class LibretroHostDisplay final : public HostDisplay class LibretroHostDisplay final : public HostDisplay
{ {
@ -26,6 +25,8 @@ public:
void ResizeRenderWindow(s32 new_window_width, s32 new_window_height) override; void ResizeRenderWindow(s32 new_window_width, s32 new_window_height) override;
void DestroyRenderSurface() override; void DestroyRenderSurface() override;
bool SetPostProcessingChain(const std::string_view& config) override;
bool CreateResources() override; bool CreateResources() override;
void DestroyResources() override; void DestroyResources() override;

View file

@ -48,6 +48,12 @@ if(NOT BUILD_LIBRETRO_CORE)
imgui_styles.h imgui_styles.h
ini_settings_interface.cpp ini_settings_interface.cpp
ini_settings_interface.h ini_settings_interface.h
postprocessing_chain.cpp
postprocessing_chain.h
postprocessing_shader.cpp
postprocessing_shader.h
postprocessing_shadergen.cpp
postprocessing_shadergen.h
save_state_selector_ui.cpp save_state_selector_ui.cpp
save_state_selector_ui.h save_state_selector_ui.h
) )

View file

@ -123,6 +123,7 @@ void CommonHostInterface::InitializeUserDirectory()
result &= FileSystem::CreateDirectory(GetUserDirectoryRelativePath("inputprofiles").c_str(), false); result &= FileSystem::CreateDirectory(GetUserDirectoryRelativePath("inputprofiles").c_str(), false);
result &= FileSystem::CreateDirectory(GetUserDirectoryRelativePath("savestates").c_str(), false); result &= FileSystem::CreateDirectory(GetUserDirectoryRelativePath("savestates").c_str(), false);
result &= FileSystem::CreateDirectory(GetUserDirectoryRelativePath("screenshots").c_str(), false); result &= FileSystem::CreateDirectory(GetUserDirectoryRelativePath("screenshots").c_str(), false);
result &= FileSystem::CreateDirectory(GetUserDirectoryRelativePath("shaders").c_str(), false);
result &= FileSystem::CreateDirectory(GetUserDirectoryRelativePath("memcards").c_str(), false); result &= FileSystem::CreateDirectory(GetUserDirectoryRelativePath("memcards").c_str(), false);
if (!result) if (!result)
@ -172,6 +173,7 @@ void CommonHostInterface::DestroySystem()
{ {
SetTimerResolutionIncreased(false); SetTimerResolutionIncreased(false);
m_save_state_selector_ui->Close(); m_save_state_selector_ui->Close();
m_display->SetPostProcessingChain({});
HostInterface::DestroySystem(); HostInterface::DestroySystem();
} }
@ -684,6 +686,9 @@ void CommonHostInterface::SetUserDirectory()
void CommonHostInterface::OnSystemCreated() void CommonHostInterface::OnSystemCreated()
{ {
HostInterface::OnSystemCreated(); HostInterface::OnSystemCreated();
if (!m_display->SetPostProcessingChain(g_settings.display_post_process_chain))
AddOSDMessage(TranslateStdString("OSDMessage", "Failed to load post processing shader chain."), 20.0f);
} }
void CommonHostInterface::OnSystemPaused(bool paused) void CommonHostInterface::OnSystemPaused(bool paused)
@ -1955,6 +1960,12 @@ void CommonHostInterface::CheckForSettingsChanges(const Settings& old_settings)
{ {
UpdateSpeedLimiterState(); UpdateSpeedLimiterState();
} }
if (g_settings.display_post_process_chain != old_settings.display_post_process_chain)
{
if (!m_display->SetPostProcessingChain(g_settings.display_post_process_chain))
AddOSDMessage(TranslateStdString("OSDMessage", "Failed to load post processing shader chain."), 20.0f);
}
} }
if (g_settings.log_level != old_settings.log_level || g_settings.log_filter != old_settings.log_filter || if (g_settings.log_level != old_settings.log_level || g_settings.log_filter != old_settings.log_filter ||

View file

@ -3,10 +3,12 @@
#include "common/d3d11/shader_compiler.h" #include "common/d3d11/shader_compiler.h"
#include "common/log.h" #include "common/log.h"
#include "common/string_util.h" #include "common/string_util.h"
#include "core/settings.h"
#include "display_ps.hlsl.h" #include "display_ps.hlsl.h"
#include "display_vs.hlsl.h" #include "display_vs.hlsl.h"
#include <array> #include <array>
#ifndef LIBRETRO #ifndef LIBRETRO
#include "frontend-common/postprocessing_shadergen.h"
#include <dxgi1_5.h> #include <dxgi1_5.h>
#endif #endif
#ifdef WITH_IMGUI #ifdef WITH_IMGUI
@ -524,6 +526,12 @@ bool D3D11HostDisplay::CreateResources()
void D3D11HostDisplay::DestroyResources() void D3D11HostDisplay::DestroyResources()
{ {
#ifndef LIBRETRO
m_post_processing_chain.ClearStages();
m_post_processing_input_texture.Destroy();
m_post_processing_stages.clear();
#endif
m_display_uniform_buffer.Release(); m_display_uniform_buffer.Release();
m_linear_sampler.Reset(); m_linear_sampler.Reset();
m_point_sampler.Reset(); m_point_sampler.Reset();
@ -580,9 +588,7 @@ bool D3D11HostDisplay::Render()
if (ImGui::GetCurrentContext()) if (ImGui::GetCurrentContext())
ImGui_ImplDX11_NewFrame(); ImGui_ImplDX11_NewFrame();
#endif #endif
#else
RenderDisplay();
RenderSoftwareCursor();
#endif #endif
return true; return true;
@ -598,13 +604,24 @@ void D3D11HostDisplay::RenderImGui()
void D3D11HostDisplay::RenderDisplay() void D3D11HostDisplay::RenderDisplay()
{ {
#ifndef LIBRETRO
if (!HasDisplayTexture()) if (!HasDisplayTexture())
return; return;
const auto [left, top, width, height] = CalculateDrawRect(GetWindowWidth(), GetWindowHeight(), m_display_top_margin); const auto [left, top, width, height] = CalculateDrawRect(GetWindowWidth(), GetWindowHeight(), m_display_top_margin);
if (!m_post_processing_chain.IsEmpty())
{
ApplyPostProcessingChain(m_swap_chain_rtv.Get(), left, top, width, height, m_display_texture_handle,
m_display_texture_width, m_display_texture_height, m_display_texture_view_x,
m_display_texture_view_y, m_display_texture_view_width, m_display_texture_view_height);
return;
}
RenderDisplay(left, top, width, height, m_display_texture_handle, m_display_texture_width, m_display_texture_height, RenderDisplay(left, top, width, height, m_display_texture_handle, m_display_texture_width, m_display_texture_height,
m_display_texture_view_x, m_display_texture_view_y, m_display_texture_view_width, m_display_texture_view_x, m_display_texture_view_y, m_display_texture_view_width,
m_display_texture_view_height, m_display_linear_filtering); m_display_texture_view_height, m_display_linear_filtering);
#endif
} }
void D3D11HostDisplay::RenderDisplay(s32 left, s32 top, s32 width, s32 height, void* texture_handle, u32 texture_width, void D3D11HostDisplay::RenderDisplay(s32 left, s32 top, s32 width, s32 height, void* texture_handle, u32 texture_width,
@ -621,7 +638,7 @@ void D3D11HostDisplay::RenderDisplay(s32 left, s32 top, s32 width, s32 height, v
static_cast<float>(texture_view_y) / static_cast<float>(texture_height), static_cast<float>(texture_view_y) / static_cast<float>(texture_height),
(static_cast<float>(texture_view_width) - 0.5f) / static_cast<float>(texture_width), (static_cast<float>(texture_view_width) - 0.5f) / static_cast<float>(texture_width),
(static_cast<float>(texture_view_height) - 0.5f) / static_cast<float>(texture_height)}; (static_cast<float>(texture_view_height) - 0.5f) / static_cast<float>(texture_height)};
const auto map = m_display_uniform_buffer.Map(m_context.Get(), sizeof(uniforms), sizeof(uniforms)); const auto map = m_display_uniform_buffer.Map(m_context.Get(), m_display_uniform_buffer.GetSize(), sizeof(uniforms));
std::memcpy(map.pointer, uniforms, sizeof(uniforms)); std::memcpy(map.pointer, uniforms, sizeof(uniforms));
m_display_uniform_buffer.Unmap(m_context.Get(), sizeof(uniforms)); m_display_uniform_buffer.Unmap(m_context.Get(), sizeof(uniforms));
m_context->VSSetConstantBuffers(0, 1, m_display_uniform_buffer.GetD3DBufferArray()); m_context->VSSetConstantBuffers(0, 1, m_display_uniform_buffer.GetD3DBufferArray());
@ -655,7 +672,7 @@ void D3D11HostDisplay::RenderSoftwareCursor(s32 left, s32 top, s32 width, s32 he
m_context->PSSetSamplers(0, 1, m_linear_sampler.GetAddressOf()); m_context->PSSetSamplers(0, 1, m_linear_sampler.GetAddressOf());
const float uniforms[4] = {0.0f, 0.0f, 1.0f, 1.0f}; const float uniforms[4] = {0.0f, 0.0f, 1.0f, 1.0f};
const auto map = m_display_uniform_buffer.Map(m_context.Get(), sizeof(uniforms), sizeof(uniforms)); const auto map = m_display_uniform_buffer.Map(m_context.Get(), m_display_uniform_buffer.GetSize(), sizeof(uniforms));
std::memcpy(map.pointer, uniforms, sizeof(uniforms)); std::memcpy(map.pointer, uniforms, sizeof(uniforms));
m_display_uniform_buffer.Unmap(m_context.Get(), sizeof(uniforms)); m_display_uniform_buffer.Unmap(m_context.Get(), sizeof(uniforms));
m_context->VSSetConstantBuffers(0, 1, m_display_uniform_buffer.GetD3DBufferArray()); m_context->VSSetConstantBuffers(0, 1, m_display_uniform_buffer.GetD3DBufferArray());
@ -728,6 +745,162 @@ std::vector<std::string> D3D11HostDisplay::EnumerateAdapterNames(IDXGIFactory* d
return adapter_names; return adapter_names;
} }
bool D3D11HostDisplay::SetPostProcessingChain(const std::string_view& config)
{
if (config.empty())
{
m_post_processing_input_texture.Destroy();
m_post_processing_stages.clear();
m_post_processing_chain.ClearStages();
return true;
}
if (!m_post_processing_chain.CreateFromString(config))
return false;
m_post_processing_stages.clear();
FrontendCommon::PostProcessingShaderGen shadergen(HostDisplay::RenderAPI::D3D11, true);
u32 max_ubo_size = 0;
for (u32 i = 0; i < m_post_processing_chain.GetStageCount(); i++)
{
const PostProcessingShader& shader = m_post_processing_chain.GetShaderStage(i);
const std::string vs = shadergen.GeneratePostProcessingVertexShader(shader);
const std::string ps = shadergen.GeneratePostProcessingFragmentShader(shader);
PostProcessingStage stage;
stage.uniforms_size = shader.GetUniformsSize();
stage.vertex_shader =
D3D11::ShaderCompiler::CompileAndCreateVertexShader(m_device.Get(), vs, g_settings.gpu_use_debug_device);
stage.pixel_shader =
D3D11::ShaderCompiler::CompileAndCreatePixelShader(m_device.Get(), ps, g_settings.gpu_use_debug_device);
if (!stage.vertex_shader || !stage.pixel_shader)
{
Log_ErrorPrintf("Failed to compile one or more post-processing shaders, disabling.");
m_post_processing_stages.clear();
m_post_processing_chain.ClearStages();
return false;
}
max_ubo_size = std::max(max_ubo_size, stage.uniforms_size);
m_post_processing_stages.push_back(std::move(stage));
}
if (m_display_uniform_buffer.GetSize() < max_ubo_size &&
!m_display_uniform_buffer.Create(m_device.Get(), D3D11_BIND_CONSTANT_BUFFER, max_ubo_size))
{
Log_ErrorPrintf("Failed to allocate %u byte constant buffer for postprocessing", max_ubo_size);
m_post_processing_stages.clear();
m_post_processing_chain.ClearStages();
return false;
}
return true;
}
bool D3D11HostDisplay::CheckPostProcessingRenderTargets(u32 target_width, u32 target_height)
{
DebugAssert(!m_post_processing_stages.empty());
const DXGI_FORMAT format = DXGI_FORMAT_R8G8B8A8_UNORM;
const u32 bind_flags = D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE;
if (m_post_processing_input_texture.GetWidth() != target_width ||
m_post_processing_input_texture.GetHeight() != target_height)
{
if (!m_post_processing_input_texture.Create(m_device.Get(), target_width, target_height, format, bind_flags))
return false;
}
const u32 target_count = (static_cast<u32>(m_post_processing_stages.size()) - 1);
for (u32 i = 0; i < target_count; i++)
{
PostProcessingStage& pps = m_post_processing_stages[i];
if (pps.output_texture.GetWidth() != target_width || pps.output_texture.GetHeight() != target_height)
{
if (!pps.output_texture.Create(m_device.Get(), target_width, target_height, format, bind_flags))
return false;
}
}
return true;
}
void D3D11HostDisplay::ApplyPostProcessingChain(ID3D11RenderTargetView* final_target, s32 final_left, s32 final_top,
s32 final_width, s32 final_height, void* texture_handle,
u32 texture_width, s32 texture_height, s32 texture_view_x,
s32 texture_view_y, s32 texture_view_width, s32 texture_view_height)
{
static constexpr std::array<float, 4> clear_color = {0.0f, 0.0f, 0.0f, 1.0f};
if (!CheckPostProcessingRenderTargets(GetWindowWidth(), GetWindowHeight()))
{
RenderDisplay(final_left, final_top, final_width, final_height, texture_handle, texture_width, texture_height,
texture_view_x, texture_view_y, texture_view_width, texture_view_height, m_display_linear_filtering);
return;
}
// downsample/upsample - use same viewport for remainder
m_context->ClearRenderTargetView(m_post_processing_input_texture.GetD3DRTV(), clear_color.data());
m_context->OMSetRenderTargets(1, m_post_processing_input_texture.GetD3DRTVArray(), nullptr);
RenderDisplay(final_left, final_top, final_width, final_height, texture_handle, texture_width, texture_height,
texture_view_x, texture_view_y, texture_view_width, texture_view_height, m_display_linear_filtering);
texture_handle = m_post_processing_input_texture.GetD3DSRV();
texture_width = m_post_processing_input_texture.GetWidth();
texture_height = m_post_processing_input_texture.GetHeight();
texture_view_x = final_left;
texture_view_y = final_top;
texture_view_width = final_width;
texture_view_height = final_height;
const u32 final_stage = static_cast<u32>(m_post_processing_stages.size()) - 1u;
for (u32 i = 0; i < static_cast<u32>(m_post_processing_stages.size()); i++)
{
PostProcessingStage& pps = m_post_processing_stages[i];
if (i == final_stage)
{
m_context->OMSetRenderTargets(1, &final_target, nullptr);
}
else
{
m_context->ClearRenderTargetView(pps.output_texture.GetD3DRTV(), clear_color.data());
m_context->OMSetRenderTargets(1, pps.output_texture.GetD3DRTVArray(), nullptr);
}
m_context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
m_context->VSSetShader(pps.vertex_shader.Get(), nullptr, 0);
m_context->PSSetShader(pps.pixel_shader.Get(), nullptr, 0);
m_context->PSSetShaderResources(0, 1, reinterpret_cast<ID3D11ShaderResourceView**>(&texture_handle));
m_context->PSSetSamplers(0, 1, m_point_sampler.GetAddressOf());
const auto map =
m_display_uniform_buffer.Map(m_context.Get(), m_display_uniform_buffer.GetSize(), pps.uniforms_size);
m_post_processing_chain.GetShaderStage(i).FillUniformBuffer(
map.pointer, texture_width, texture_height, texture_view_x, texture_view_y, texture_view_width,
texture_view_height, GetWindowWidth(), GetWindowHeight(), 0.0f);
m_display_uniform_buffer.Unmap(m_context.Get(), pps.uniforms_size);
m_context->VSSetConstantBuffers(0, 1, m_display_uniform_buffer.GetD3DBufferArray());
m_context->PSSetConstantBuffers(0, 1, m_display_uniform_buffer.GetD3DBufferArray());
m_context->Draw(3, 0);
if (i != final_stage)
texture_handle = pps.output_texture.GetD3DSRV();
}
ID3D11ShaderResourceView* null_srv = nullptr;
m_context->PSSetShaderResources(0, 1, &null_srv);
}
#else // LIBRETRO
bool D3D11HostDisplay::SetPostProcessingChain(const std::string_view& config)
{
return false;
}
#endif #endif
} // namespace FrontendCommon } // namespace FrontendCommon

View file

@ -13,6 +13,10 @@
#include <vector> #include <vector>
#include <wrl/client.h> #include <wrl/client.h>
#ifndef LIBRETRO
#include "frontend-common/postprocessing_chain.h"
#endif
namespace FrontendCommon { namespace FrontendCommon {
class D3D11HostDisplay : public HostDisplay class D3D11HostDisplay : public HostDisplay
@ -42,6 +46,8 @@ public:
virtual void ResizeRenderWindow(s32 new_window_width, s32 new_window_height) override; virtual void ResizeRenderWindow(s32 new_window_width, s32 new_window_height) override;
virtual void DestroyRenderSurface() override; virtual void DestroyRenderSurface() override;
virtual bool SetPostProcessingChain(const std::string_view& config) override;
std::unique_ptr<HostDisplayTexture> CreateTexture(u32 width, u32 height, const void* initial_data, std::unique_ptr<HostDisplayTexture> CreateTexture(u32 width, u32 height, const void* initial_data,
u32 initial_data_stride, bool dynamic) override; u32 initial_data_stride, bool dynamic) override;
void UpdateTexture(HostDisplayTexture* texture, u32 x, u32 y, u32 width, u32 height, const void* texture_data, void UpdateTexture(HostDisplayTexture* texture, u32 x, u32 y, u32 width, u32 height, const void* texture_data,
@ -82,6 +88,22 @@ protected:
s32 texture_view_height, bool linear_filter); s32 texture_view_height, bool linear_filter);
void RenderSoftwareCursor(s32 left, s32 top, s32 width, s32 height, HostDisplayTexture* texture_handle); void RenderSoftwareCursor(s32 left, s32 top, s32 width, s32 height, HostDisplayTexture* texture_handle);
#ifndef LIBRETRO
struct PostProcessingStage
{
ComPtr<ID3D11VertexShader> vertex_shader;
ComPtr<ID3D11PixelShader> pixel_shader;
D3D11::Texture output_texture;
u32 uniforms_size;
};
bool CheckPostProcessingRenderTargets(u32 target_width, u32 target_height);
void ApplyPostProcessingChain(ID3D11RenderTargetView* final_target, s32 final_left, s32 final_top, s32 final_width,
s32 final_height, void* texture_handle, u32 texture_width, s32 texture_height,
s32 texture_view_x, s32 texture_view_y, s32 texture_view_width,
s32 texture_view_height);
#endif
ComPtr<ID3D11Device> m_device; ComPtr<ID3D11Device> m_device;
ComPtr<ID3D11DeviceContext> m_context; ComPtr<ID3D11DeviceContext> m_context;
@ -109,6 +131,10 @@ protected:
bool m_using_flip_model_swap_chain = true; bool m_using_flip_model_swap_chain = true;
bool m_using_allow_tearing = false; bool m_using_allow_tearing = false;
bool m_vsync = true; bool m_vsync = true;
PostProcessingChain m_post_processing_chain;
D3D11::Texture m_post_processing_input_texture;
std::vector<PostProcessingStage> m_post_processing_stages;
#endif #endif
}; };

View file

@ -79,6 +79,9 @@
<ClCompile Include="imgui_styles.cpp" /> <ClCompile Include="imgui_styles.cpp" />
<ClCompile Include="ini_settings_interface.cpp" /> <ClCompile Include="ini_settings_interface.cpp" />
<ClCompile Include="opengl_host_display.cpp" /> <ClCompile Include="opengl_host_display.cpp" />
<ClCompile Include="postprocessing_chain.cpp" />
<ClCompile Include="postprocessing_shader.cpp" />
<ClCompile Include="postprocessing_shadergen.cpp" />
<ClCompile Include="save_state_selector_ui.cpp" /> <ClCompile Include="save_state_selector_ui.cpp" />
<ClCompile Include="sdl_audio_stream.cpp" /> <ClCompile Include="sdl_audio_stream.cpp" />
<ClCompile Include="sdl_controller_interface.cpp" /> <ClCompile Include="sdl_controller_interface.cpp" />
@ -99,6 +102,9 @@
<ClInclude Include="imgui_styles.h" /> <ClInclude Include="imgui_styles.h" />
<ClInclude Include="ini_settings_interface.h" /> <ClInclude Include="ini_settings_interface.h" />
<ClInclude Include="opengl_host_display.h" /> <ClInclude Include="opengl_host_display.h" />
<ClInclude Include="postprocessing_chain.h" />
<ClInclude Include="postprocessing_shader.h" />
<ClInclude Include="postprocessing_shadergen.h" />
<ClInclude Include="save_state_selector_ui.h" /> <ClInclude Include="save_state_selector_ui.h" />
<ClInclude Include="sdl_audio_stream.h" /> <ClInclude Include="sdl_audio_stream.h" />
<ClInclude Include="sdl_controller_interface.h" /> <ClInclude Include="sdl_controller_interface.h" />

View file

@ -19,6 +19,9 @@
<ClCompile Include="imgui_impl_dx11.cpp" /> <ClCompile Include="imgui_impl_dx11.cpp" />
<ClCompile Include="imgui_impl_opengl3.cpp" /> <ClCompile Include="imgui_impl_opengl3.cpp" />
<ClCompile Include="imgui_impl_vulkan.cpp" /> <ClCompile Include="imgui_impl_vulkan.cpp" />
<ClCompile Include="postprocessing_shader.cpp" />
<ClCompile Include="postprocessing_shadergen.cpp" />
<ClCompile Include="postprocessing_chain.cpp" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClInclude Include="icon.h" /> <ClInclude Include="icon.h" />
@ -39,6 +42,9 @@
<ClInclude Include="imgui_impl_vulkan.h" /> <ClInclude Include="imgui_impl_vulkan.h" />
<ClInclude Include="imgui_impl_dx11.h" /> <ClInclude Include="imgui_impl_dx11.h" />
<ClInclude Include="imgui_impl_opengl3.h" /> <ClInclude Include="imgui_impl_opengl3.h" />
<ClInclude Include="postprocessing_shader.h" />
<ClInclude Include="postprocessing_shadergen.h" />
<ClInclude Include="postprocessing_chain.h" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="font_roboto_regular.inl" /> <None Include="font_roboto_regular.inl" />

View file

@ -7,6 +7,9 @@
#include "imgui.h" #include "imgui.h"
#include "imgui_impl_opengl3.h" #include "imgui_impl_opengl3.h"
#endif #endif
#ifndef LIBRETRO
#include "postprocessing_shadergen.h"
#endif
Log_SetChannel(LibretroOpenGLHostDisplay); Log_SetChannel(LibretroOpenGLHostDisplay);
namespace FrontendCommon { namespace FrontendCommon {
@ -207,6 +210,8 @@ bool OpenGLHostDisplay::CreateRenderDevice(const WindowInfo& wi, std::string_vie
bool OpenGLHostDisplay::InitializeRenderDevice(std::string_view shader_cache_directory, bool debug_device) bool OpenGLHostDisplay::InitializeRenderDevice(std::string_view shader_cache_directory, bool debug_device)
{ {
glGetIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, reinterpret_cast<GLint*>(&m_uniform_buffer_alignment));
if (debug_device && GLAD_GL_KHR_debug) if (debug_device && GLAD_GL_KHR_debug)
{ {
if (GetRenderAPI() == RenderAPI::OpenGLES) if (GetRenderAPI() == RenderAPI::OpenGLES)
@ -418,6 +423,13 @@ void main()
void OpenGLHostDisplay::DestroyResources() void OpenGLHostDisplay::DestroyResources()
{ {
#ifndef LIBRETRO
m_post_processing_chain.ClearStages();
m_post_processing_input_texture.Destroy();
m_post_processing_ubo.reset();
m_post_processing_stages.clear();
#endif
if (m_display_vao != 0) if (m_display_vao != 0)
glDeleteVertexArrays(1, &m_display_vao); glDeleteVertexArrays(1, &m_display_vao);
if (m_display_linear_sampler != 0) if (m_display_linear_sampler != 0)
@ -470,9 +482,18 @@ void OpenGLHostDisplay::RenderDisplay()
return; return;
const auto [left, top, width, height] = CalculateDrawRect(GetWindowWidth(), GetWindowHeight(), m_display_top_margin); const auto [left, top, width, height] = CalculateDrawRect(GetWindowWidth(), GetWindowHeight(), m_display_top_margin);
RenderDisplay(left, GetWindowHeight() - top - height, width, height, m_display_texture_handle,
m_display_texture_width, m_display_texture_height, m_display_texture_view_x, m_display_texture_view_y, if (!m_post_processing_chain.IsEmpty())
m_display_texture_view_width, m_display_texture_view_height, m_display_linear_filtering); {
ApplyPostProcessingChain(0, left, top, width, height, m_display_texture_handle, m_display_texture_width,
m_display_texture_height, m_display_texture_view_x, m_display_texture_view_y,
m_display_texture_view_width, m_display_texture_view_height);
return;
}
RenderDisplay(left, top, width, height, m_display_texture_handle, m_display_texture_width, m_display_texture_height,
m_display_texture_view_x, m_display_texture_view_y, m_display_texture_view_width,
m_display_texture_view_height, m_display_linear_filtering);
} }
void OpenGLHostDisplay::RenderDisplay(s32 left, s32 bottom, s32 width, s32 height, void* texture_handle, void OpenGLHostDisplay::RenderDisplay(s32 left, s32 bottom, s32 width, s32 height, void* texture_handle,
@ -526,4 +547,181 @@ void OpenGLHostDisplay::RenderSoftwareCursor(s32 left, s32 bottom, s32 width, s3
glBindSampler(0, 0); glBindSampler(0, 0);
} }
#ifndef LIBRETRO
bool OpenGLHostDisplay::SetPostProcessingChain(const std::string_view& config)
{
if (config.empty())
{
m_post_processing_input_texture.Destroy();
m_post_processing_stages.clear();
m_post_processing_chain.ClearStages();
return true;
}
if (!m_post_processing_chain.CreateFromString(config))
return false;
m_post_processing_stages.clear();
FrontendCommon::PostProcessingShaderGen shadergen(HostDisplay::RenderAPI::OpenGL, false);
for (u32 i = 0; i < m_post_processing_chain.GetStageCount(); i++)
{
const PostProcessingShader& shader = m_post_processing_chain.GetShaderStage(i);
const std::string vs = shadergen.GeneratePostProcessingVertexShader(shader);
const std::string ps = shadergen.GeneratePostProcessingFragmentShader(shader);
PostProcessingStage stage;
stage.uniforms_size = shader.GetUniformsSize();
if (!stage.program.Compile(vs, {}, ps))
{
Log_InfoPrintf("Failed to compile post-processing program, disabling.");
m_post_processing_stages.clear();
m_post_processing_chain.ClearStages();
return false;
}
if (!shadergen.UseGLSLBindingLayout())
{
stage.program.BindUniformBlock("UBOBlock", 1);
stage.program.Bind();
stage.program.Uniform1i("samp0", 0);
}
if (!stage.program.Link())
{
Log_InfoPrintf("Failed to link post-processing program, disabling.");
m_post_processing_stages.clear();
m_post_processing_chain.ClearStages();
return false;
}
m_post_processing_stages.push_back(std::move(stage));
}
if (!m_post_processing_ubo)
{
m_post_processing_ubo = GL::StreamBuffer::Create(GL_UNIFORM_BUFFER, 1 * 1024 * 1024);
if (!m_post_processing_ubo)
{
Log_InfoPrintf("Failed to allocate uniform buffer for postprocessing");
m_post_processing_stages.clear();
m_post_processing_chain.ClearStages();
return false;
}
m_post_processing_ubo->Unbind();
}
return true;
}
bool OpenGLHostDisplay::CheckPostProcessingRenderTargets(u32 target_width, u32 target_height)
{
DebugAssert(!m_post_processing_stages.empty());
if (m_post_processing_input_texture.GetWidth() != target_width ||
m_post_processing_input_texture.GetHeight() != target_height)
{
if (!m_post_processing_input_texture.Create(target_width, target_height, GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE) ||
!m_post_processing_input_texture.CreateFramebuffer())
{
return false;
}
}
const u32 target_count = (static_cast<u32>(m_post_processing_stages.size()) - 1);
for (u32 i = 0; i < target_count; i++)
{
PostProcessingStage& pps = m_post_processing_stages[i];
if (pps.output_texture.GetWidth() != target_width || pps.output_texture.GetHeight() != target_height)
{
if (!pps.output_texture.Create(target_width, target_height, GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE) ||
!pps.output_texture.CreateFramebuffer())
{
return false;
}
}
}
return true;
}
void OpenGLHostDisplay::ApplyPostProcessingChain(GLuint final_target, s32 final_left, s32 final_top, s32 final_width,
s32 final_height, void* texture_handle, u32 texture_width,
s32 texture_height, s32 texture_view_x, s32 texture_view_y,
s32 texture_view_width, s32 texture_view_height)
{
static constexpr std::array<float, 4> clear_color = {0.0f, 0.0f, 0.0f, 1.0f};
if (!CheckPostProcessingRenderTargets(GetWindowWidth(), GetWindowHeight()))
{
RenderDisplay(final_left, final_top, final_width, final_height, texture_handle, texture_width, texture_height,
texture_view_x, texture_view_y, texture_view_width, texture_view_height, m_display_linear_filtering);
return;
}
// downsample/upsample - use same viewport for remainder
m_post_processing_input_texture.BindFramebuffer(GL_DRAW_FRAMEBUFFER);
glClear(GL_COLOR_BUFFER_BIT);
RenderDisplay(final_left, final_top, final_width, final_height, texture_handle, texture_width, texture_height,
texture_view_x, texture_view_y, texture_view_width, texture_view_height, m_display_linear_filtering);
texture_handle = reinterpret_cast<void*>(static_cast<uintptr_t>(m_post_processing_input_texture.GetGLId()));
texture_width = m_post_processing_input_texture.GetWidth();
texture_height = m_post_processing_input_texture.GetHeight();
texture_view_x = final_left;
texture_view_y = final_top;
texture_view_width = final_width;
texture_view_height = final_height;
m_post_processing_ubo->Bind();
const u32 final_stage = static_cast<u32>(m_post_processing_stages.size()) - 1u;
for (u32 i = 0; i < static_cast<u32>(m_post_processing_stages.size()); i++)
{
PostProcessingStage& pps = m_post_processing_stages[i];
if (i == final_stage)
{
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, final_target);
}
else
{
pps.output_texture.BindFramebuffer(GL_DRAW_FRAMEBUFFER);
glClear(GL_COLOR_BUFFER_BIT);
}
pps.program.Bind();
glBindSampler(0, m_display_linear_sampler);
glBindTexture(GL_TEXTURE_2D, static_cast<GLuint>(reinterpret_cast<uintptr_t>(texture_handle)));
glBindSampler(0, m_display_nearest_sampler);
const auto map_result = m_post_processing_ubo->Map(m_uniform_buffer_alignment, pps.uniforms_size);
m_post_processing_chain.GetShaderStage(i).FillUniformBuffer(
map_result.pointer, texture_width, texture_height, texture_view_x, texture_view_y, texture_view_width,
texture_view_height, GetWindowWidth(), GetWindowHeight(), 0.0f);
m_post_processing_ubo->Unmap(pps.uniforms_size);
glBindBufferRange(GL_UNIFORM_BUFFER, 1, m_post_processing_ubo->GetGLBufferId(), map_result.buffer_offset,
pps.uniforms_size);
glDrawArrays(GL_TRIANGLES, 0, 3);
if (i != final_stage)
texture_handle = reinterpret_cast<void*>(static_cast<uintptr_t>(pps.output_texture.GetGLId()));
}
glBindSampler(0, 0);
m_post_processing_ubo->Unbind();
}
#else
bool OpenGLHostDisplay::SetPostProcessingChain(const std::string_view& config)
{
return false;
}
#endif
} // namespace FrontendCommon } // namespace FrontendCommon

View file

@ -10,11 +10,16 @@
#include "common/gl/context.h" #include "common/gl/context.h"
#include "common/gl/program.h" #include "common/gl/program.h"
#include "common/gl/stream_buffer.h"
#include "common/gl/texture.h" #include "common/gl/texture.h"
#include "common/window_info.h" #include "common/window_info.h"
#include "core/host_display.h" #include "core/host_display.h"
#include <memory> #include <memory>
#ifndef LIBRETRO
#include "postprocessing_chain.h"
#endif
namespace FrontendCommon { namespace FrontendCommon {
class OpenGLHostDisplay : public HostDisplay class OpenGLHostDisplay : public HostDisplay
@ -41,6 +46,8 @@ public:
virtual void ResizeRenderWindow(s32 new_window_width, s32 new_window_height) override; virtual void ResizeRenderWindow(s32 new_window_width, s32 new_window_height) override;
virtual void DestroyRenderSurface() override; virtual void DestroyRenderSurface() override;
virtual bool SetPostProcessingChain(const std::string_view& config) override;
std::unique_ptr<HostDisplayTexture> CreateTexture(u32 width, u32 height, const void* initial_data, std::unique_ptr<HostDisplayTexture> CreateTexture(u32 width, u32 height, const void* initial_data,
u32 initial_data_stride, bool dynamic) override; u32 initial_data_stride, bool dynamic) override;
void UpdateTexture(HostDisplayTexture* texture, u32 x, u32 y, u32 width, u32 height, const void* texture_data, void UpdateTexture(HostDisplayTexture* texture, u32 x, u32 y, u32 width, u32 height, const void* texture_data,
@ -71,6 +78,20 @@ protected:
s32 texture_view_height, bool linear_filter); s32 texture_view_height, bool linear_filter);
void RenderSoftwareCursor(s32 left, s32 bottom, s32 width, s32 height, HostDisplayTexture* texture_handle); void RenderSoftwareCursor(s32 left, s32 bottom, s32 width, s32 height, HostDisplayTexture* texture_handle);
#ifndef LIBRETRO
struct PostProcessingStage
{
GL::Program program;
GL::Texture output_texture;
u32 uniforms_size;
};
bool CheckPostProcessingRenderTargets(u32 target_width, u32 target_height);
void ApplyPostProcessingChain(GLuint final_target, s32 final_left, s32 final_top, s32 final_width, s32 final_height,
void* texture_handle, u32 texture_width, s32 texture_height, s32 texture_view_x,
s32 texture_view_y, s32 texture_view_width, s32 texture_view_height);
#endif
std::unique_ptr<GL::Context> m_gl_context; std::unique_ptr<GL::Context> m_gl_context;
GL::Program m_display_program; GL::Program m_display_program;
@ -78,6 +99,14 @@ protected:
GLuint m_display_vao = 0; GLuint m_display_vao = 0;
GLuint m_display_nearest_sampler = 0; GLuint m_display_nearest_sampler = 0;
GLuint m_display_linear_sampler = 0; GLuint m_display_linear_sampler = 0;
GLuint m_uniform_buffer_alignment = 1;
#ifndef LIBRETRO
PostProcessingChain m_post_processing_chain;
GL::Texture m_post_processing_input_texture;
std::unique_ptr<GL::StreamBuffer> m_post_processing_ubo;
std::vector<PostProcessingStage> m_post_processing_stages;
#endif
}; };
} // namespace FrontendCommon } // namespace FrontendCommon

View file

@ -0,0 +1,203 @@
#include "postprocessing_chain.h"
#include "common/assert.h"
#include "common/file_system.h"
#include "common/log.h"
#include "common/string.h"
#include "core/host_interface.h"
#include <sstream>
Log_SetChannel(PostProcessingChain);
namespace FrontendCommon {
static bool TryLoadingShader(PostProcessingShader* shader, const std::string_view& shader_name)
{
std::string shader_name_str(shader_name);
std::string filename = g_host_interface->GetUserDirectoryRelativePath(
"shaders%c%s.glsl", FS_OSPATH_SEPERATOR_CHARACTER, shader_name_str.c_str());
if (FileSystem::FileExists(filename.c_str()))
{
if (!shader->LoadFromFile(std::move(shader_name_str), filename.c_str()))
{
Log_ErrorPrintf("Failed to load shader from '%s'", filename.c_str());
return false;
}
}
else
{
filename = g_host_interface->GetProgramDirectoryRelativePath("shaders%c%s.glsl", FS_OSPATH_SEPERATOR_CHARACTER,
shader_name_str.c_str());
if (FileSystem::FileExists(filename.c_str()))
{
if (!shader->LoadFromFile(std::move(shader_name_str), filename.c_str()))
{
Log_ErrorPrintf("Failed to load shader from '%s'", filename.c_str());
return false;
}
}
else
{
Log_ErrorPrintf("Could not find shader '%s'", std::string(shader_name).c_str());
return false;
}
}
return true;
}
PostProcessingChain::PostProcessingChain() = default;
PostProcessingChain::~PostProcessingChain() = default;
void PostProcessingChain::AddShader(PostProcessingShader shader)
{
m_shaders.push_back(std::move(shader));
}
bool PostProcessingChain::AddStage(const std::string_view& name)
{
PostProcessingShader shader;
if (!TryLoadingShader(&shader, name))
return false;
m_shaders.push_back(std::move(shader));
return true;
}
std::string PostProcessingChain::GetConfigString() const
{
std::stringstream ss;
bool first = true;
for (const PostProcessingShader& shader : m_shaders)
{
if (!first)
ss << ':';
else
first = false;
ss << shader.GetName();
std::string config_string = shader.GetConfigString();
if (!config_string.empty())
ss << ';' << config_string;
}
return ss.str();
}
bool PostProcessingChain::CreateFromString(const std::string_view& chain_config)
{
std::vector<PostProcessingShader> shaders;
size_t last_sep = 0;
while (last_sep < chain_config.size())
{
size_t next_sep = chain_config.find(':', last_sep);
if (next_sep == std::string::npos)
next_sep = chain_config.size();
const std::string_view shader_config = chain_config.substr(last_sep, next_sep - last_sep);
size_t first_shader_sep = shader_config.find(';');
if (first_shader_sep == std::string::npos)
first_shader_sep = shader_config.size();
const std::string_view shader_name = shader_config.substr(0, first_shader_sep);
if (!shader_name.empty())
{
PostProcessingShader shader;
if (!TryLoadingShader(&shader, shader_name))
return false;
if (first_shader_sep < shader_config.size())
shader.SetConfigString(shader_config.substr(first_shader_sep + 1));
shaders.push_back(std::move(shader));
}
last_sep = next_sep + 1;
}
if (shaders.empty())
{
Log_ErrorPrintf("Postprocessing chain is empty!");
return false;
}
m_shaders = std::move(shaders);
Log_InfoPrintf("Loaded postprocessing chain of %zu shaders", m_shaders.size());
return true;
}
std::vector<std::string> PostProcessingChain::GetAvailableShaderNames()
{
std::vector<std::string> names;
std::string program_dir = g_host_interface->GetProgramDirectoryRelativePath("shaders");
std::string user_dir = g_host_interface->GetUserDirectoryRelativePath("shaders");
FileSystem::FindResultsArray results;
FileSystem::FindFiles(user_dir.c_str(), "*.glsl",
FILESYSTEM_FIND_FILES | FILESYSTEM_FIND_RECURSIVE | FILESYSTEM_FIND_RELATIVE_PATHS, &results);
if (program_dir != user_dir)
{
FileSystem::FindFiles(program_dir.c_str(), "*.glsl",
FILESYSTEM_FIND_FILES | FILESYSTEM_FIND_RECURSIVE | FILESYSTEM_FIND_RELATIVE_PATHS |
FILESYSTEM_FIND_KEEP_ARRAY,
&results);
}
for (FILESYSTEM_FIND_DATA& fd : results)
{
size_t pos = fd.FileName.rfind('.');
if (pos != std::string::npos && pos > 0)
fd.FileName.erase(pos);
// swap any backslashes for forward slashes so the config is cross-platform
for (size_t i = 0; i < fd.FileName.size(); i++)
{
if (fd.FileName[i] == '\\')
fd.FileName[i] = '/';
}
if (std::none_of(names.begin(), names.end(), [&fd](const std::string& other) { return fd.FileName == other; }))
{
names.push_back(std::move(fd.FileName));
}
}
return names;
}
void PostProcessingChain::RemoveStage(u32 index)
{
Assert(index < m_shaders.size());
m_shaders.erase(m_shaders.begin() + index);
}
void PostProcessingChain::MoveStageUp(u32 index)
{
Assert(index < m_shaders.size());
if (index == 0)
return;
PostProcessingShader shader = std::move(m_shaders[index]);
m_shaders.erase(m_shaders.begin() + index);
m_shaders.insert(m_shaders.begin() + (index - 1u), std::move(shader));
}
void PostProcessingChain::MoveStageDown(u32 index)
{
Assert(index < m_shaders.size());
if (index == (m_shaders.size() - 1u))
return;
PostProcessingShader shader = std::move(m_shaders[index]);
m_shaders.erase(m_shaders.begin() + index);
m_shaders.insert(m_shaders.begin() + (index + 1u), std::move(shader));
}
void PostProcessingChain::ClearStages()
{
m_shaders.clear();
}
} // namespace FrontendCommon

View file

@ -0,0 +1,36 @@
#pragma once
#include "postprocessing_shader.h"
#include <string_view>
#include <vector>
namespace FrontendCommon {
class PostProcessingChain
{
public:
PostProcessingChain();
~PostProcessingChain();
ALWAYS_INLINE bool IsEmpty() const { return m_shaders.empty(); }
ALWAYS_INLINE const u32 GetStageCount() const { return static_cast<u32>(m_shaders.size()); }
ALWAYS_INLINE const PostProcessingShader& GetShaderStage(u32 i) const { return m_shaders[i]; }
ALWAYS_INLINE PostProcessingShader& GetShaderStage(u32 i) { return m_shaders[i]; }
void AddShader(PostProcessingShader shader);
bool AddStage(const std::string_view& name);
void RemoveStage(u32 index);
void MoveStageUp(u32 index);
void MoveStageDown(u32 index);
void ClearStages();
std::string GetConfigString() const;
bool CreateFromString(const std::string_view& chain_config);
static std::vector<std::string> GetAvailableShaderNames();
private:
std::vector<PostProcessingShader> m_shaders;
};
} // namespace FrontendCommon

View file

@ -0,0 +1,412 @@
#include "postprocessing_shader.h"
#include "common/file_system.h"
#include "common/log.h"
#include "common/string_util.h"
#include "core/shadergen.h"
#include <cctype>
#include <cstring>
#include <sstream>
Log_SetChannel(PostProcessingShader);
namespace FrontendCommon {
void ParseKeyValue(const std::string_view& line, std::string_view* key, std::string_view* value)
{
size_t key_start = 0;
while (key_start < line.size() && std::isspace(line[key_start]))
key_start++;
size_t key_end = key_start;
while (key_end < line.size() && (!std::isspace(line[key_end]) && line[key_end] != '='))
key_end++;
if (key_start == key_end || key_end == line.size())
return;
size_t value_start = key_end;
while (value_start < line.size() && std::isspace(line[value_start]))
value_start++;
if (value_start == line.size() || line[value_start] != '=')
return;
value_start++;
while (value_start < line.size() && std::isspace(line[value_start]))
value_start++;
size_t value_end = value_start;
while (value_end < line.size() && !std::isspace(line[value_end]))
value_end++;
if (value_start == value_end)
return;
*key = line.substr(key_start, key_end - key_start);
*value = line.substr(value_start, value_end - value_start);
}
template<typename T>
u32 ParseVector(const std::string_view& line, PostProcessingShader::Option::ValueVector* values)
{
u32 index = 0;
size_t start = 0;
while (index < PostProcessingShader::Option::MAX_VECTOR_COMPONENTS)
{
size_t end = line.find(',');
if (end == std::string_view::npos)
end = line.size();
T value = StringUtil::FromChars<T>(line.substr(start, end - start)).value_or(static_cast<T>(0));
if constexpr (std::is_same_v<T, float>)
(*values)[index++].float_value = value;
else if constexpr (std::is_same_v<T, s32>)
(*values)[index++].int_value = value;
}
const u32 size = index;
for (; index < PostProcessingShader::Option::MAX_VECTOR_COMPONENTS; index++)
{
if constexpr (std::is_same_v<T, float>)
(*values)[index++].float_value = 0.0f;
else if constexpr (std::is_same_v<T, s32>)
(*values)[index++].int_value = 0;
}
return size;
}
PostProcessingShader::PostProcessingShader() = default;
PostProcessingShader::PostProcessingShader(std::string name, std::string code) : m_name(name), m_code(code)
{
LoadOptions();
}
PostProcessingShader::PostProcessingShader(const PostProcessingShader& copy)
: m_name(copy.m_name), m_code(copy.m_code), m_options(copy.m_options)
{
}
PostProcessingShader::PostProcessingShader(PostProcessingShader& move)
: m_name(std::move(move.m_name)), m_code(std::move(move.m_code)), m_options(std::move(move.m_options))
{
}
PostProcessingShader::~PostProcessingShader() = default;
bool PostProcessingShader::LoadFromFile(std::string name, const char* filename)
{
std::optional<std::string> code = FileSystem::ReadFileToString(filename);
if (!code.has_value() || code->empty())
return false;
m_name = std::move(name);
m_code = std::move(code.value());
m_options.clear();
LoadOptions();
return true;
}
bool PostProcessingShader::IsValid() const
{
return !m_name.empty() && !m_code.empty();
}
const PostProcessingShader::Option* PostProcessingShader::GetOptionByName(const std::string_view& name) const
{
for (const Option& option : m_options)
{
if (option.name == name)
return &option;
}
return nullptr;
}
FrontendCommon::PostProcessingShader::Option* PostProcessingShader::GetOptionByName(const std::string_view& name)
{
for (Option& option : m_options)
{
if (option.name == name)
return &option;
}
return nullptr;
}
std::string PostProcessingShader::GetConfigString() const
{
std::stringstream ss;
bool first = true;
for (const Option& option : m_options)
{
if (!first)
ss << ';';
else
first = false;
ss << option.name;
ss << '=';
for (u32 i = 0; i < option.vector_size; i++)
{
if (i > 0)
ss << ",";
switch (option.type)
{
case Option::Type::Bool:
ss << option.value[i].bool_value ? "true" : "false";
break;
case Option::Type::Int:
ss << option.value[i].int_value;
break;
case Option::Type::Float:
ss << option.value[i].float_value;
break;
default:
break;
}
}
}
return ss.str();
}
void PostProcessingShader::SetConfigString(const std::string_view& str)
{
for (Option& option : m_options)
option.value = option.default_value;
size_t last_sep = 0;
while (last_sep < str.size())
{
size_t next_sep = str.find(';', last_sep);
if (next_sep == std::string_view::npos)
next_sep = str.size();
const std::string_view kv = str.substr(last_sep, next_sep - last_sep);
std::string_view key, value;
ParseKeyValue(kv, &key, &value);
if (!key.empty() && !value.empty())
{
Option* option = GetOptionByName(key);
if (option)
{
switch (option->type)
{
case Option::Type::Bool:
option->value[0].bool_value = StringUtil::FromChars<bool>(value).value_or(false);
break;
case Option::Type::Int:
ParseVector<s32>(value, &option->value);
break;
case Option::Type::Float:
ParseVector<float>(value, &option->value);
break;
default:
break;
}
}
}
last_sep = next_sep + 1;
}
}
bool PostProcessingShader::UsePushConstants() const
{
return GetUniformsSize() <= PUSH_CONSTANT_SIZE_THRESHOLD;
}
u32 PostProcessingShader::GetUniformsSize() const
{
// lazy packing. todo improve.
return sizeof(CommonUniforms) + (sizeof(Option::ValueVector) * static_cast<u32>(m_options.size()));
}
void PostProcessingShader::FillUniformBuffer(void* buffer, u32 texture_width, s32 texture_height, s32 texture_view_x,
s32 texture_view_y, s32 texture_view_width, s32 texture_view_height,
u32 window_width, u32 window_height, float time) const
{
CommonUniforms* common = static_cast<CommonUniforms*>(buffer);
// TODO: OpenGL?
const float rcp_texture_width = 1.0f / static_cast<float>(texture_width);
const float rcp_texture_height = 1.0f / static_cast<float>(texture_height);
common->src_rect[0] = static_cast<float>(texture_view_x) * rcp_texture_width;
common->src_rect[1] = static_cast<float>(texture_view_y) * rcp_texture_height;
common->src_rect[2] = (static_cast<float>(texture_view_x + texture_view_width - 1)) * rcp_texture_width;
common->src_rect[3] = (static_cast<float>(texture_view_y + texture_view_height - 1)) * rcp_texture_height;
common->src_size[0] = (static_cast<float>(texture_view_width)) * rcp_texture_width;
common->src_size[1] = (static_cast<float>(texture_view_height)) * rcp_texture_height;
common->resolution[0] = static_cast<float>(texture_width);
common->resolution[1] = static_cast<float>(texture_height);
common->rcp_resolution[0] = rcp_texture_width;
common->rcp_resolution[1] = rcp_texture_height;
common->window_resolution[0] = static_cast<float>(window_width);
common->window_resolution[1] = static_cast<float>(window_height);
common->rcp_window_resolution[0] = 1.0f / static_cast<float>(window_width);
common->rcp_window_resolution[1] = 1.0f / static_cast<float>(window_height);
common->time = time;
u8* option_values = reinterpret_cast<u8*>(common + 1);
for (const Option& option : m_options)
{
std::memcpy(option_values, option.value.data(), sizeof(Option::ValueVector));
option_values += sizeof(Option::ValueVector);
}
}
FrontendCommon::PostProcessingShader& PostProcessingShader::operator=(const PostProcessingShader& copy)
{
m_name = copy.m_name;
m_code = copy.m_code;
m_options = copy.m_options;
return *this;
}
FrontendCommon::PostProcessingShader& PostProcessingShader::operator=(PostProcessingShader& move)
{
m_name = std::move(move.m_name);
m_code = std::move(move.m_code);
m_options = std::move(move.m_options);
return *this;
}
void PostProcessingShader::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);
Option current_option = {};
while (!in.eof())
{
std::string line_str;
if (std::getline(in, line_str))
{
std::string_view line_view = line_str;
#ifndef _WIN32
// 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);
#endif
if (line_view.empty())
continue;
if (line_view[0] == '[')
{
size_t endpos = line_view.find("]");
if (endpos != std::string::npos)
{
if (current_option.type != Option::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 = Option::Type::Bool;
else if (sub == "OptionRangeFloat")
current_option.type = Option::Type::Float;
else if (sub == "OptionRangeInteger")
current_option.type = Option::Type::Int;
else
Log_ErrorPrintf("Invalid option type: '%s'", line_str.c_str());
}
else
{
if (current_option.type == Option::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")
{
Option::ValueVector* dst_array;
if (key == "MinValue")
dst_array = &current_option.min_value;
else if (key == "MaxValue")
dst_array = &current_option.max_value;
else if (key == "DefaultValue")
dst_array = &current_option.default_value;
else // if (key == "StepAmount")
dst_array = &current_option.step_value;
u32 size = 0;
if (current_option.type == Option::Type::Bool)
(*dst_array)[size++].bool_value = StringUtil::FromChars<bool>(value).value_or(false);
else if (current_option.type == Option::Type::Float)
size = ParseVector<float>(value, dst_array);
else if (current_option.type == Option::Type::Int)
size = ParseVector<s32>(value, dst_array);
current_option.vector_size =
(current_option.vector_size != 0) ? size : std::min(current_option.vector_size, size);
}
else
{
Log_ErrorPrintf("Invalid option key: '%s'", line_str.c_str());
}
}
}
}
}
}
if (current_option.type != Option::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));
}
}
} // namespace FrontendCommon

View file

@ -0,0 +1,106 @@
#pragma once
#include "common/rectangle.h"
#include "core/types.h"
#include <array>
#include <string>
#include <string_view>
#include <vector>
namespace FrontendCommon {
class PostProcessingShader
{
public:
enum : u32
{
PUSH_CONSTANT_SIZE_THRESHOLD = 128
};
struct Option
{
enum : u32
{
MAX_VECTOR_COMPONENTS = 4
};
enum class Type
{
Invalid,
Bool,
Int,
Float
};
union Value
{
bool bool_value;
s32 int_value;
float float_value;
};
static_assert(sizeof(Value) == sizeof(u32));
using ValueVector = std::array<Value, MAX_VECTOR_COMPONENTS>;
static_assert(sizeof(ValueVector) == sizeof(u32) * MAX_VECTOR_COMPONENTS);
std::string name;
std::string ui_name;
std::string dependent_option;
Type type;
u32 vector_size;
ValueVector default_value;
ValueVector min_value;
ValueVector max_value;
ValueVector step_value;
ValueVector value;
};
PostProcessingShader();
PostProcessingShader(std::string name, std::string code);
PostProcessingShader(const PostProcessingShader& copy);
PostProcessingShader(PostProcessingShader& move);
~PostProcessingShader();
PostProcessingShader& operator=(const PostProcessingShader& copy);
PostProcessingShader& operator=(PostProcessingShader& move);
ALWAYS_INLINE const std::string& GetName() const { return m_name; }
ALWAYS_INLINE const std::string& GetCode() const { return m_code; }
ALWAYS_INLINE const std::vector<Option>& GetOptions() const { return m_options; }
bool IsValid() const;
const Option* GetOptionByName(const std::string_view& name) const;
Option* GetOptionByName(const std::string_view& name);
std::string GetConfigString() const;
void SetConfigString(const std::string_view& str);
bool LoadFromFile(std::string name, const char* filename);
bool UsePushConstants() const;
u32 GetUniformsSize() const;
void FillUniformBuffer(void* buffer, u32 texture_width, s32 texture_height, s32 texture_view_x, s32 texture_view_y,
s32 texture_view_width, s32 texture_view_height, u32 window_width, u32 window_height,
float time) const;
private:
struct CommonUniforms
{
float src_rect[4];
float src_size[2];
float resolution[2];
float rcp_resolution[2];
float window_resolution[2];
float rcp_window_resolution[2];
float time;
float padding[1];
};
void LoadOptions();
std::string m_name;
std::string m_code;
std::vector<Option> m_options;
};
} // namespace FrontendCommon

View file

@ -0,0 +1,184 @@
#include "postprocessing_shadergen.h"
namespace FrontendCommon {
PostProcessingShaderGen::PostProcessingShaderGen(HostDisplay::RenderAPI render_api, bool supports_dual_source_blend)
: ShaderGen(render_api, supports_dual_source_blend)
{
}
PostProcessingShaderGen::~PostProcessingShaderGen() = default;
std::string PostProcessingShaderGen::GeneratePostProcessingVertexShader(const PostProcessingShader& shader)
{
std::stringstream ss;
WriteHeader(ss);
DeclareTexture(ss, "samp0", 0);
WriteUniformBuffer(ss, shader, shader.UsePushConstants());
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 = src_rect.xy + (src_size * v_tex0);
}
)";
return ss.str();
}
std::string PostProcessingShaderGen::GeneratePostProcessingFragmentShader(const PostProcessingShader& shader)
{
std::stringstream ss;
WriteHeader(ss);
DeclareTexture(ss, "samp0", 0);
WriteUniformBuffer(ss, shader, shader.UsePushConstants());
// Rename main, since we need to set up globals
if (!m_glsl)
{
// TODO: vecn -> floatn
ss << R"(
#define main real_main
static float2 v_tex0;
static float4 o_col0;
// Wrappers for sampling functions.
#define texture(sampler, coords) sampler.Sample(sampler##_ss, coords)
#define textureOffset(sampler, coords, offset) sampler.Sample(sampler##_ss, coords, offset)
)";
}
else
{
if (m_use_glsl_interface_blocks)
{
if (IsVulkan())
ss << "layout(location = 0) ";
ss << "in VertexData {\n";
ss << " float2 v_tex0;\n";
ss << "};\n";
}
else
{
ss << "in float2 v_tex0;\n";
}
if (m_use_glsl_binding_layout)
{
ss << "layout(location = 0) out float4 o_col0;\n";
}
else
{
ss << "out float4 o_col0;\n";
}
}
ss << R"(
float4 Sample() { return texture(samp0, v_tex0); }
float4 SampleLocation(float2 location) { return texture(samp0, location); }
#define SampleOffset(offset) textureOffset(samp0, v_tex0, offset)
float2 GetWindowResolution()
{
return window_resolution;
}
float2 GetResolution()
{
return resolution;
}
float2 GetInvResolution()
{
return rcp_resolution;
}
float2 GetCoordinates()
{
return v_tex0;
}
float GetTime()
{
return time;
}
void SetOutput(float4 color)
{
o_col0 = color;
}
#define GetOption(x) (x)
#define OptionEnabled(x) ((x) != 0)
)";
ss << shader.GetCode();
if (!m_glsl)
{
ss << R"(
#undef main
void main(in float2 v_tex0_ : TEXCOORD0, out float4 o_col0_ : SV_Target)
{
v_tex0 = v_tex0_;
real_main();
o_col0_ = o_col0;
}
)";
}
return ss.str();
}
void PostProcessingShaderGen::WriteUniformBuffer(std::stringstream& ss, const PostProcessingShader& shader,
bool use_push_constants)
{
u32 pad_counter = 0;
WriteUniformBufferDeclaration(ss, use_push_constants);
ss << "{\n";
ss << " float4 src_rect;\n";
ss << " float2 src_size;\n";
ss << " float2 resolution;\n";
ss << " float2 rcp_resolution;\n";
ss << " float2 window_resolution;\n";
ss << " float2 rcp_window_resolution;\n";
ss << " float time;\n";
ss << " float ubo_pad" << (pad_counter++) << ";\n";
ss << "\n";
static constexpr std::array<const char*, PostProcessingShader::Option::MAX_VECTOR_COMPONENTS> vector_size_suffix = {
{"", "2", "3", "4"}};
for (const PostProcessingShader::Option& option : shader.GetOptions())
{
switch (option.type)
{
case PostProcessingShader::Option::Type::Bool:
ss << " bool u_option_" << option.name << ";\n";
for (u32 i = option.vector_size; i < PostProcessingShader::Option::MAX_VECTOR_COMPONENTS; i++)
ss << " bool ubo_pad" << (pad_counter++) << ";\n";
break;
case PostProcessingShader::Option::Type::Int:
{
ss << " int" << vector_size_suffix[option.vector_size] << " " << option.name << ";\n";
for (u32 i = option.vector_size; i < PostProcessingShader::Option::MAX_VECTOR_COMPONENTS; i++)
ss << " int ubo_pad" << (pad_counter++) << ";\n";
}
break;
case PostProcessingShader::Option::Type::Float:
default:
{
ss << " float" << vector_size_suffix[option.vector_size] << " " << option.name << ";\n";
for (u32 i = option.vector_size; i < PostProcessingShader::Option::MAX_VECTOR_COMPONENTS; i++)
ss << " float ubo_pad" << (pad_counter++) << ";\n";
}
break;
}
}
ss << "};\n\n";
}
} // namespace FrontendCommon

View file

@ -0,0 +1,21 @@
#pragma once
#include "core/shadergen.h"
#include "postprocessing_shader.h"
#include <sstream>
namespace FrontendCommon {
class PostProcessingShaderGen : public ShaderGen
{
public:
PostProcessingShaderGen(HostDisplay::RenderAPI render_api, bool supports_dual_source_blend);
~PostProcessingShaderGen();
std::string GeneratePostProcessingVertexShader(const PostProcessingShader& shader);
std::string GeneratePostProcessingFragmentShader(const PostProcessingShader& shader);
private:
void WriteUniformBuffer(std::stringstream& ss, const PostProcessingShader& shader, bool use_push_constants);
};
} // namespace FrontendCommon

View file

@ -14,6 +14,9 @@
#include "imgui.h" #include "imgui.h"
#include "imgui_impl_vulkan.h" #include "imgui_impl_vulkan.h"
#endif #endif
#ifndef LIBRETRO
#include "postprocessing_shadergen.h"
#endif
Log_SetChannel(VulkanHostDisplay); Log_SetChannel(VulkanHostDisplay);
namespace FrontendCommon { namespace FrontendCommon {
@ -343,6 +346,34 @@ void main()
if (m_pipeline_layout == VK_NULL_HANDLE) if (m_pipeline_layout == VK_NULL_HANDLE)
return false; return false;
#ifndef LIBRETRO
dslbuilder.AddBinding(1, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_FRAGMENT_BIT);
m_post_process_descriptor_set_layout = dslbuilder.Create(device);
if (m_post_process_descriptor_set_layout == VK_NULL_HANDLE)
return false;
plbuilder.AddDescriptorSet(m_post_process_descriptor_set_layout);
plbuilder.AddPushConstants(VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0,
PostProcessingShader::PUSH_CONSTANT_SIZE_THRESHOLD);
m_post_process_pipeline_layout = plbuilder.Create(device);
if (m_post_process_pipeline_layout == VK_NULL_HANDLE)
return false;
dslbuilder.AddBinding(0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1,
VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT);
dslbuilder.AddBinding(1, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_FRAGMENT_BIT);
m_post_process_ubo_descriptor_set_layout = dslbuilder.Create(device);
if (m_post_process_ubo_descriptor_set_layout == VK_NULL_HANDLE)
return false;
plbuilder.AddDescriptorSet(m_post_process_ubo_descriptor_set_layout);
m_post_process_ubo_pipeline_layout = plbuilder.Create(device);
if (m_post_process_ubo_pipeline_layout == VK_NULL_HANDLE)
return false;
#endif
VkShaderModule vertex_shader = g_vulkan_shader_cache->GetVertexShader(fullscreen_quad_vertex_shader); VkShaderModule vertex_shader = g_vulkan_shader_cache->GetVertexShader(fullscreen_quad_vertex_shader);
if (vertex_shader == VK_NULL_HANDLE) if (vertex_shader == VK_NULL_HANDLE)
return false; return false;
@ -395,6 +426,17 @@ void main()
void VulkanHostDisplay::DestroyResources() void VulkanHostDisplay::DestroyResources()
{ {
#ifndef LIBRETRO
Vulkan::Util::SafeDestroyPipelineLayout(m_post_process_pipeline_layout);
Vulkan::Util::SafeDestroyPipelineLayout(m_post_process_ubo_pipeline_layout);
Vulkan::Util::SafeDestroyDescriptorSetLayout(m_post_process_descriptor_set_layout);
Vulkan::Util::SafeDestroyDescriptorSetLayout(m_post_process_ubo_descriptor_set_layout);
m_post_processing_input_texture.Destroy(false);
Vulkan::Util::SafeDestroyFramebuffer(m_post_processing_input_framebuffer);
m_post_processing_stages.clear();
m_post_processing_chain.ClearStages();
#endif
m_readback_staging_texture.Destroy(false); m_readback_staging_texture.Destroy(false);
m_upload_staging_texture.Destroy(false); m_upload_staging_texture.Destroy(false);
@ -500,17 +542,6 @@ bool VulkanHostDisplay::Render()
swap_chain_texture.OverrideImageLayout(VK_IMAGE_LAYOUT_UNDEFINED); swap_chain_texture.OverrideImageLayout(VK_IMAGE_LAYOUT_UNDEFINED);
swap_chain_texture.TransitionToLayout(cmdbuffer, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); swap_chain_texture.TransitionToLayout(cmdbuffer, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
const VkClearValue clear_value = {};
const VkRenderPassBeginInfo rp = {VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
nullptr,
m_swap_chain->GetClearRenderPass(),
m_swap_chain->GetCurrentFramebuffer(),
{{0, 0}, {m_swap_chain->GetWidth(), m_swap_chain->GetHeight()}},
1u,
&clear_value};
vkCmdBeginRenderPass(cmdbuffer, &rp, VK_SUBPASS_CONTENTS_INLINE);
RenderDisplay(); RenderDisplay();
#ifdef WITH_IMGUI #ifdef WITH_IMGUI
@ -537,12 +568,38 @@ bool VulkanHostDisplay::Render()
return true; return true;
} }
void VulkanHostDisplay::BeginSwapChainRenderPass(VkFramebuffer framebuffer)
{
const VkClearValue clear_value = {};
const VkRenderPassBeginInfo rp = {VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
nullptr,
m_swap_chain->GetClearRenderPass(),
framebuffer,
{{0, 0}, {m_swap_chain->GetWidth(), m_swap_chain->GetHeight()}},
1u,
&clear_value};
vkCmdBeginRenderPass(g_vulkan_context->GetCurrentCommandBuffer(), &rp, VK_SUBPASS_CONTENTS_INLINE);
}
void VulkanHostDisplay::RenderDisplay() void VulkanHostDisplay::RenderDisplay()
{ {
if (!HasDisplayTexture()) if (!HasDisplayTexture())
{
BeginSwapChainRenderPass(m_swap_chain->GetCurrentFramebuffer());
return; return;
}
const auto [left, top, width, height] = CalculateDrawRect(GetWindowWidth(), GetWindowHeight(), m_display_top_margin); const auto [left, top, width, height] = CalculateDrawRect(GetWindowWidth(), GetWindowHeight(), m_display_top_margin);
if (!m_post_processing_chain.IsEmpty())
{
ApplyPostProcessingChain(left, top, width, height, m_display_texture_handle, m_display_texture_width,
m_display_texture_height, m_display_texture_view_x, m_display_texture_view_y,
m_display_texture_view_width, m_display_texture_view_height);
return;
}
BeginSwapChainRenderPass(m_swap_chain->GetCurrentFramebuffer());
RenderDisplay(left, top, width, height, m_display_texture_handle, m_display_texture_width, m_display_texture_height, RenderDisplay(left, top, width, height, m_display_texture_handle, m_display_texture_width, m_display_texture_height,
m_display_texture_view_x, m_display_texture_view_y, m_display_texture_view_width, m_display_texture_view_x, m_display_texture_view_y, m_display_texture_view_width,
m_display_texture_view_height, m_display_linear_filtering); m_display_texture_view_height, m_display_linear_filtering);
@ -646,4 +703,273 @@ std::vector<std::string> VulkanHostDisplay::EnumerateAdapterNames()
return {}; return {};
} }
#ifndef LIBRETRO
VulkanHostDisplay::PostProcessingStage::PostProcessingStage(PostProcessingStage&& move)
: output_texture(std::move(move.output_texture)), output_framebuffer(move.output_framebuffer),
pipeline(move.pipeline), uniforms_size(move.uniforms_size)
{
move.output_framebuffer = VK_NULL_HANDLE;
move.pipeline = VK_NULL_HANDLE;
move.uniforms_size = 0;
}
VulkanHostDisplay::PostProcessingStage::~PostProcessingStage()
{
if (output_framebuffer != VK_NULL_HANDLE)
g_vulkan_context->DeferFramebufferDestruction(output_framebuffer);
output_texture.Destroy(true);
if (pipeline != VK_NULL_HANDLE)
g_vulkan_context->DeferPipelineDestruction(pipeline);
}
bool VulkanHostDisplay::SetPostProcessingChain(const std::string_view& config)
{
if (config.empty())
{
m_post_processing_stages.clear();
m_post_processing_chain.ClearStages();
return true;
}
if (!m_post_processing_chain.CreateFromString(config))
return false;
m_post_processing_stages.clear();
FrontendCommon::PostProcessingShaderGen shadergen(HostDisplay::RenderAPI::Vulkan, false);
bool only_use_push_constants = true;
for (u32 i = 0; i < m_post_processing_chain.GetStageCount(); i++)
{
const PostProcessingShader& shader = m_post_processing_chain.GetShaderStage(i);
const std::string vs = shadergen.GeneratePostProcessingVertexShader(shader);
const std::string ps = shadergen.GeneratePostProcessingFragmentShader(shader);
const bool use_push_constants = shader.UsePushConstants();
only_use_push_constants &= use_push_constants;
PostProcessingStage stage;
stage.uniforms_size = shader.GetUniformsSize();
VkShaderModule vs_mod = g_vulkan_shader_cache->GetVertexShader(vs);
VkShaderModule fs_mod = g_vulkan_shader_cache->GetFragmentShader(ps);
if (vs_mod == VK_NULL_HANDLE || fs_mod == VK_NULL_HANDLE)
{
Log_ErrorPrintf("Failed to compile one or more post-processing shaders, disabling.");
if (vs_mod != VK_NULL_HANDLE)
vkDestroyShaderModule(g_vulkan_context->GetDevice(), vs_mod, nullptr);
if (fs_mod != VK_NULL_HANDLE)
vkDestroyShaderModule(g_vulkan_context->GetDevice(), vs_mod, nullptr);
m_post_processing_stages.clear();
m_post_processing_chain.ClearStages();
return false;
}
Vulkan::GraphicsPipelineBuilder gpbuilder;
gpbuilder.SetVertexShader(vs_mod);
gpbuilder.SetFragmentShader(fs_mod);
gpbuilder.SetPrimitiveTopology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST);
gpbuilder.SetNoCullRasterizationState();
gpbuilder.SetNoDepthTestState();
gpbuilder.SetNoBlendingState();
gpbuilder.SetDynamicViewportAndScissorState();
gpbuilder.SetPipelineLayout(use_push_constants ? m_post_process_pipeline_layout :
m_post_process_ubo_pipeline_layout);
gpbuilder.SetRenderPass(GetRenderPassForDisplay(), 0);
stage.pipeline = gpbuilder.Create(g_vulkan_context->GetDevice(), g_vulkan_shader_cache->GetPipelineCache());
vkDestroyShaderModule(g_vulkan_context->GetDevice(), vs_mod, nullptr);
vkDestroyShaderModule(g_vulkan_context->GetDevice(), fs_mod, nullptr);
if (!stage.pipeline)
{
Log_ErrorPrintf("Failed to compile one or more post-processing pipelines, disabling.");
m_post_processing_stages.clear();
m_post_processing_chain.ClearStages();
return false;
}
m_post_processing_stages.push_back(std::move(stage));
}
constexpr u32 UBO_SIZE = 1 * 1024 * 1024;
if (!only_use_push_constants && m_post_processing_ubo.GetCurrentSize() < UBO_SIZE &&
!m_post_processing_ubo.Create(VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, UBO_SIZE))
{
Log_ErrorPrintf("Failed to allocate %u byte uniform buffer for postprocessing", UBO_SIZE);
m_post_processing_stages.clear();
m_post_processing_chain.ClearStages();
return false;
}
return true;
}
bool VulkanHostDisplay::CheckPostProcessingRenderTargets(u32 target_width, u32 target_height)
{
DebugAssert(!m_post_processing_stages.empty());
if (m_post_processing_input_texture.GetWidth() != target_width ||
m_post_processing_input_texture.GetHeight() != target_height)
{
if (m_post_processing_input_framebuffer != VK_NULL_HANDLE)
{
g_vulkan_context->DeferFramebufferDestruction(m_post_processing_input_framebuffer);
m_post_processing_input_framebuffer = VK_NULL_HANDLE;
}
if (!m_post_processing_input_texture.Create(target_width, target_height, 1, 1, m_swap_chain->GetTextureFormat(),
VK_SAMPLE_COUNT_1_BIT, VK_IMAGE_VIEW_TYPE_2D, VK_IMAGE_TILING_OPTIMAL,
VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT) ||
(m_post_processing_input_framebuffer =
m_post_processing_input_texture.CreateFramebuffer(GetRenderPassForDisplay())) == VK_NULL_HANDLE)
{
return false;
}
}
const u32 target_count = (static_cast<u32>(m_post_processing_stages.size()) - 1);
for (u32 i = 0; i < target_count; i++)
{
PostProcessingStage& pps = m_post_processing_stages[i];
if (pps.output_texture.GetWidth() != target_width || pps.output_texture.GetHeight() != target_height)
{
if (pps.output_framebuffer != VK_NULL_HANDLE)
{
g_vulkan_context->DeferFramebufferDestruction(pps.output_framebuffer);
pps.output_framebuffer = VK_NULL_HANDLE;
}
if (!pps.output_texture.Create(target_width, target_height, 1, 1, m_swap_chain->GetTextureFormat(),
VK_SAMPLE_COUNT_1_BIT, VK_IMAGE_VIEW_TYPE_2D, VK_IMAGE_TILING_OPTIMAL,
VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT) ||
(pps.output_framebuffer = pps.output_texture.CreateFramebuffer(GetRenderPassForDisplay())) == VK_NULL_HANDLE)
{
return false;
}
}
}
return true;
}
void VulkanHostDisplay::ApplyPostProcessingChain(s32 final_left, s32 final_top, s32 final_width, s32 final_height,
void* texture_handle, u32 texture_width, s32 texture_height,
s32 texture_view_x, s32 texture_view_y, s32 texture_view_width,
s32 texture_view_height)
{
static constexpr std::array<float, 4> clear_color = {0.0f, 0.0f, 0.0f, 1.0f};
if (!CheckPostProcessingRenderTargets(m_swap_chain->GetWidth(), m_swap_chain->GetHeight()))
{
BeginSwapChainRenderPass(m_swap_chain->GetCurrentFramebuffer());
RenderDisplay(final_left, final_top, final_width, final_height, texture_handle, texture_width, texture_height,
texture_view_x, texture_view_y, texture_view_width, texture_view_height, m_display_linear_filtering);
return;
}
// downsample/upsample - use same viewport for remainder
VkCommandBuffer cmdbuffer = g_vulkan_context->GetCurrentCommandBuffer();
m_post_processing_input_texture.TransitionToLayout(cmdbuffer, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
BeginSwapChainRenderPass(m_post_processing_input_framebuffer);
RenderDisplay(final_left, final_top, final_width, final_height, texture_handle, texture_width, texture_height,
texture_view_x, texture_view_y, texture_view_width, texture_view_height, m_display_linear_filtering);
vkCmdEndRenderPass(cmdbuffer);
m_post_processing_input_texture.TransitionToLayout(cmdbuffer, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
texture_handle = &m_post_processing_input_texture;
texture_width = m_post_processing_input_texture.GetWidth();
texture_height = m_post_processing_input_texture.GetHeight();
texture_view_x = final_left;
texture_view_y = final_top;
texture_view_width = final_width;
texture_view_height = final_height;
const u32 final_stage = static_cast<u32>(m_post_processing_stages.size()) - 1u;
for (u32 i = 0; i < static_cast<u32>(m_post_processing_stages.size()); i++)
{
PostProcessingStage& pps = m_post_processing_stages[i];
if (i != final_stage)
{
pps.output_texture.TransitionToLayout(cmdbuffer, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
BeginSwapChainRenderPass(pps.output_framebuffer);
}
else
{
BeginSwapChainRenderPass(m_swap_chain->GetCurrentFramebuffer());
}
const bool use_push_constants = m_post_processing_chain.GetShaderStage(i).UsePushConstants();
VkDescriptorSet ds = g_vulkan_context->AllocateDescriptorSet(
use_push_constants ? m_post_process_descriptor_set_layout : m_post_process_ubo_descriptor_set_layout);
if (ds == VK_NULL_HANDLE)
{
Log_ErrorPrintf("Skipping rendering display because of no descriptor set");
return;
}
const Vulkan::Texture* vktex = static_cast<Vulkan::Texture*>(texture_handle);
Vulkan::DescriptorSetUpdateBuilder dsupdate;
dsupdate.AddCombinedImageSamplerDescriptorWrite(ds, 1, vktex->GetView(), m_point_sampler, vktex->GetLayout());
if (use_push_constants)
{
u8 buffer[PostProcessingShader::PUSH_CONSTANT_SIZE_THRESHOLD];
Assert(pps.uniforms_size < sizeof(buffer));
m_post_processing_chain.GetShaderStage(i).FillUniformBuffer(
buffer, texture_width, texture_height, texture_view_x, texture_view_y, texture_view_width, texture_view_height,
texture_width, texture_width, 0.0f);
vkCmdPushConstants(cmdbuffer, m_post_process_pipeline_layout,
VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, pps.uniforms_size, buffer);
dsupdate.Update(g_vulkan_context->GetDevice());
vkCmdBindDescriptorSets(cmdbuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, m_post_process_pipeline_layout, 0, 1, &ds, 0,
nullptr);
}
else
{
if (!m_post_processing_ubo.ReserveMemory(pps.uniforms_size,
static_cast<u32>(g_vulkan_context->GetUniformBufferAlignment())))
{
Panic("Failed to reserve space in post-processing UBO");
}
const u32 offset = m_post_processing_ubo.GetCurrentOffset();
m_post_processing_chain.GetShaderStage(i).FillUniformBuffer(
m_post_processing_ubo.GetCurrentHostPointer(), texture_width, texture_height, texture_view_x, texture_view_y,
texture_view_width, texture_view_height, GetWindowWidth(), GetWindowHeight(), 0.0f);
m_post_processing_ubo.CommitMemory(pps.uniforms_size);
dsupdate.AddBufferDescriptorWrite(ds, 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, m_post_processing_ubo.GetBuffer(),
offset, pps.uniforms_size);
dsupdate.Update(g_vulkan_context->GetDevice());
vkCmdBindDescriptorSets(cmdbuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, m_post_process_ubo_pipeline_layout, 0, 1, &ds,
0, nullptr);
}
vkCmdBindPipeline(cmdbuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pps.pipeline);
vkCmdDraw(cmdbuffer, 3, 1, 0, 0);
if (i != final_stage)
{
vkCmdEndRenderPass(cmdbuffer);
pps.output_texture.TransitionToLayout(cmdbuffer, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
texture_handle = &pps.output_texture;
}
}
}
#else // LIBRETRO
bool VulkanHostDisplay::SetPostProcessingChain(const std::string_view& config)
{
return false;
}
#endif
} // namespace FrontendCommon } // namespace FrontendCommon

View file

@ -1,5 +1,6 @@
#pragma once #pragma once
#include "common/vulkan/staging_texture.h" #include "common/vulkan/staging_texture.h"
#include "common/vulkan/stream_buffer.h"
#include "common/vulkan/swap_chain.h" #include "common/vulkan/swap_chain.h"
#include "common/window_info.h" #include "common/window_info.h"
#include "core/host_display.h" #include "core/host_display.h"
@ -12,6 +13,10 @@ class StreamBuffer;
class SwapChain; class SwapChain;
} // namespace Vulkan } // namespace Vulkan
#ifndef LIBRETRO
#include "postprocessing_chain.h"
#endif
namespace FrontendCommon { namespace FrontendCommon {
class VulkanHostDisplay : public HostDisplay class VulkanHostDisplay : public HostDisplay
@ -38,6 +43,8 @@ public:
virtual void ResizeRenderWindow(s32 new_window_width, s32 new_window_height) override; virtual void ResizeRenderWindow(s32 new_window_width, s32 new_window_height) override;
virtual void DestroyRenderSurface() override; virtual void DestroyRenderSurface() override;
virtual bool SetPostProcessingChain(const std::string_view& config) override;
std::unique_ptr<HostDisplayTexture> CreateTexture(u32 width, u32 height, const void* initial_data, std::unique_ptr<HostDisplayTexture> CreateTexture(u32 width, u32 height, const void* initial_data,
u32 initial_data_stride, bool dynamic) override; u32 initial_data_stride, bool dynamic) override;
void UpdateTexture(HostDisplayTexture* texture, u32 x, u32 y, u32 width, u32 height, const void* texture_data, void UpdateTexture(HostDisplayTexture* texture, u32 x, u32 y, u32 width, u32 height, const void* texture_data,
@ -60,6 +67,25 @@ protected:
float src_rect_height; float src_rect_height;
}; };
#ifndef LIBRETRO
struct PostProcessingStage
{
PostProcessingStage() = default;
PostProcessingStage(PostProcessingStage&& move);
~PostProcessingStage();
VkPipeline pipeline = VK_NULL_HANDLE;
VkFramebuffer output_framebuffer = VK_NULL_HANDLE;
Vulkan::Texture output_texture;
u32 uniforms_size = 0;
};
bool CheckPostProcessingRenderTargets(u32 target_width, u32 target_height);
void ApplyPostProcessingChain(s32 final_left, s32 final_top, s32 final_width, s32 final_height, void* texture_handle,
u32 texture_width, s32 texture_height, s32 texture_view_x, s32 texture_view_y,
s32 texture_view_width, s32 texture_view_height);
#endif
// Can be overridden by frontends. // Can be overridden by frontends.
virtual VkRenderPass GetRenderPassForDisplay() const; virtual VkRenderPass GetRenderPassForDisplay() const;
@ -69,6 +95,7 @@ protected:
virtual bool CreateImGuiContext(); virtual bool CreateImGuiContext();
virtual void DestroyImGuiContext(); virtual void DestroyImGuiContext();
void BeginSwapChainRenderPass(VkFramebuffer framebuffer);
void RenderDisplay(); void RenderDisplay();
void RenderImGui(); void RenderImGui();
void RenderSoftwareCursor(); void RenderSoftwareCursor();
@ -89,6 +116,19 @@ protected:
Vulkan::StagingTexture m_upload_staging_texture; Vulkan::StagingTexture m_upload_staging_texture;
Vulkan::StagingTexture m_readback_staging_texture; Vulkan::StagingTexture m_readback_staging_texture;
#ifndef LIBRETRO
VkDescriptorSetLayout m_post_process_descriptor_set_layout = VK_NULL_HANDLE;
VkDescriptorSetLayout m_post_process_ubo_descriptor_set_layout = VK_NULL_HANDLE;
VkPipelineLayout m_post_process_pipeline_layout = VK_NULL_HANDLE;
VkPipelineLayout m_post_process_ubo_pipeline_layout = VK_NULL_HANDLE;
PostProcessingChain m_post_processing_chain;
Vulkan::Texture m_post_processing_input_texture;
VkFramebuffer m_post_processing_input_framebuffer = VK_NULL_HANDLE;
Vulkan::StreamBuffer m_post_processing_ubo;
std::vector<PostProcessingStage> m_post_processing_stages;
#endif
}; };
} // namespace FrontendCommon } // namespace FrontendCommon