mirror of
https://github.com/RetroDECK/Duckstation.git
synced 2024-11-22 22:05:38 +00:00
Merge branch 'postprocessing'
This commit is contained in:
commit
68d08ddbcb
|
@ -121,6 +121,8 @@ void Texture::Destroy()
|
|||
m_rtv.Reset();
|
||||
m_srv.Reset();
|
||||
m_texture.Reset();
|
||||
m_width = 0;
|
||||
m_height = 0;
|
||||
}
|
||||
|
||||
} // namespace D3D11
|
|
@ -10,11 +10,8 @@ namespace Common {
|
|||
template<typename T>
|
||||
struct Rectangle
|
||||
{
|
||||
enum : T
|
||||
{
|
||||
InvalidMinCoord = std::numeric_limits<T>::max(),
|
||||
InvalidMaxCoord = std::numeric_limits<T>::min()
|
||||
};
|
||||
static constexpr T InvalidMinCoord = std::numeric_limits<T>::max();
|
||||
static constexpr T InvalidMaxCoord = std::numeric_limits<T>::min();
|
||||
|
||||
/// Default constructor - initializes to an invalid coordinate range suitable for including points.
|
||||
constexpr Rectangle() : left(InvalidMinCoord), top(InvalidMinCoord), right(InvalidMaxCoord), bottom(InvalidMaxCoord)
|
||||
|
|
|
@ -166,6 +166,11 @@ String::String(String&& moveString)
|
|||
Assign(moveString);
|
||||
}
|
||||
|
||||
String::String(const std::string_view& sv)
|
||||
{
|
||||
AppendString(sv.data(), static_cast<u32>(sv.size()));
|
||||
}
|
||||
|
||||
String::~String()
|
||||
{
|
||||
StringDataRelease(m_pStringData);
|
||||
|
|
|
@ -54,6 +54,9 @@ public:
|
|||
// Construct a string from a data object, does not increment the reference count on the string data, use carefully.
|
||||
explicit String(StringData* pStringData) : m_pStringData(pStringData) {}
|
||||
|
||||
// Creates string from string_view.
|
||||
String(const std::string_view& sv);
|
||||
|
||||
// Destructor. Child classes may not have any destructors, as this is not virtual.
|
||||
~String();
|
||||
|
||||
|
@ -307,6 +310,12 @@ public:
|
|||
Assign(copyString.GetCharArray());
|
||||
}
|
||||
|
||||
StackString(const std::string_view& sv) : String(&m_sStringData)
|
||||
{
|
||||
InitStackStringData();
|
||||
AppendString(sv.data(), static_cast<u32>(sv.size()));
|
||||
}
|
||||
|
||||
// Override the fromstring method
|
||||
static StackString FromFormat(const char* FormatString, ...)
|
||||
{
|
||||
|
|
|
@ -91,7 +91,7 @@ bool Context::CheckValidationLayerAvailablility()
|
|||
return strcmp(it.extensionName, VK_EXT_DEBUG_REPORT_EXTENSION_NAME) == 0;
|
||||
}) != extension_list.end() &&
|
||||
std::find_if(layer_list.begin(), layer_list.end(), [](const auto& it) {
|
||||
return strcmp(it.layerName, "VK_LAYER_LUNARG_standard_validation") == 0;
|
||||
return strcmp(it.layerName, "VK_LAYER_KHRONOS_validation") == 0;
|
||||
}) != layer_list.end());
|
||||
}
|
||||
|
||||
|
@ -123,7 +123,7 @@ VkInstance Context::CreateVulkanInstance(bool enable_surface, bool enable_debug_
|
|||
// Enable debug layer on debug builds
|
||||
if (enable_validation_layer)
|
||||
{
|
||||
static const char* layer_names[] = {"VK_LAYER_LUNARG_standard_validation"};
|
||||
static const char* layer_names[] = {"VK_LAYER_KHRONOS_validation"};
|
||||
instance_create_info.enabledLayerCount = 1;
|
||||
instance_create_info.ppEnabledLayerNames = layer_names;
|
||||
}
|
||||
|
@ -997,6 +997,12 @@ void Context::DeferImageViewDestruction(VkImageView object)
|
|||
resources.cleanup_resources.push_back([this, object]() { vkDestroyImageView(m_device, object, nullptr); });
|
||||
}
|
||||
|
||||
void Context::DeferPipelineDestruction(VkPipeline pipeline)
|
||||
{
|
||||
FrameResources& resources = m_frame_resources[m_current_frame];
|
||||
resources.cleanup_resources.push_back([this, pipeline]() { vkDestroyPipeline(m_device, pipeline, nullptr); });
|
||||
}
|
||||
|
||||
static VKAPI_ATTR VkBool32 VKAPI_CALL DebugReportCallback(VkDebugReportFlagsEXT flags,
|
||||
VkDebugReportObjectTypeEXT objectType, uint64_t object,
|
||||
size_t location, int32_t messageCode,
|
||||
|
|
|
@ -163,6 +163,7 @@ public:
|
|||
void DeferFramebufferDestruction(VkFramebuffer object);
|
||||
void DeferImageDestruction(VkImage object);
|
||||
void DeferImageViewDestruction(VkImageView object);
|
||||
void DeferPipelineDestruction(VkPipeline pipeline);
|
||||
|
||||
// Wait for a fence to be completed.
|
||||
// Also invokes callbacks for completion.
|
||||
|
|
|
@ -31,7 +31,7 @@ public:
|
|||
|
||||
ALWAYS_INLINE VkSurfaceKHR GetSurface() const { return m_surface; }
|
||||
ALWAYS_INLINE VkSurfaceFormatKHR GetSurfaceFormat() const { return m_surface_format; }
|
||||
ALWAYS_INLINE VkFormat GetTextureFormat() const { return m_texture_format; }
|
||||
ALWAYS_INLINE VkFormat GetTextureFormat() const { return m_surface_format.format; }
|
||||
ALWAYS_INLINE bool IsVSyncEnabled() const { return m_vsync_enabled; }
|
||||
ALWAYS_INLINE VkSwapchainKHR GetSwapChain() const { return m_swap_chain; }
|
||||
ALWAYS_INLINE u32 GetWidth() const { return m_width; }
|
||||
|
@ -85,7 +85,6 @@ private:
|
|||
VkSurfaceKHR m_surface = VK_NULL_HANDLE;
|
||||
VkSurfaceFormatKHR m_surface_format = {};
|
||||
VkPresentModeKHR m_present_mode = VK_PRESENT_MODE_IMMEDIATE_KHR;
|
||||
VkFormat m_texture_format = VK_FORMAT_UNDEFINED;
|
||||
|
||||
VkRenderPass m_load_render_pass = VK_NULL_HANDLE;
|
||||
VkRenderPass m_clear_render_pass = VK_NULL_HANDLE;
|
||||
|
|
|
@ -71,6 +71,8 @@ add_library(core
|
|||
save_state_version.h
|
||||
settings.cpp
|
||||
settings.h
|
||||
shadergen.cpp
|
||||
shadergen.h
|
||||
sio.cpp
|
||||
sio.h
|
||||
spu.cpp
|
||||
|
|
|
@ -85,6 +85,7 @@
|
|||
<ClCompile Include="psf_loader.cpp" />
|
||||
<ClCompile Include="resources.cpp" />
|
||||
<ClCompile Include="settings.cpp" />
|
||||
<ClCompile Include="shadergen.cpp" />
|
||||
<ClCompile Include="sio.cpp" />
|
||||
<ClCompile Include="spu.cpp" />
|
||||
<ClCompile Include="system.cpp" />
|
||||
|
@ -134,6 +135,7 @@
|
|||
<ClInclude Include="resources.h" />
|
||||
<ClInclude Include="save_state_version.h" />
|
||||
<ClInclude Include="settings.h" />
|
||||
<ClInclude Include="shadergen.h" />
|
||||
<ClInclude Include="sio.h" />
|
||||
<ClInclude Include="spu.h" />
|
||||
<ClInclude Include="system.h" />
|
||||
|
|
|
@ -47,6 +47,7 @@
|
|||
<ClCompile Include="host_interface_progress_callback.cpp" />
|
||||
<ClCompile Include="pgxp.cpp" />
|
||||
<ClCompile Include="cheats.cpp" />
|
||||
<ClCompile Include="shadergen.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="types.h" />
|
||||
|
@ -97,5 +98,6 @@
|
|||
<ClInclude Include="pgxp.h" />
|
||||
<ClInclude Include="cpu_core_private.h" />
|
||||
<ClInclude Include="cheats.h" />
|
||||
<ClInclude Include="shadergen.h" />
|
||||
</ItemGroup>
|
||||
</Project>
|
|
@ -99,6 +99,7 @@ void GPU_HW_OpenGL::ResetGraphicsAPIState()
|
|||
if (m_resolution_scale > 1 && !m_supports_geometry_shaders)
|
||||
glLineWidth(1.0f);
|
||||
glBindVertexArray(0);
|
||||
m_uniform_stream_buffer->Unbind();
|
||||
}
|
||||
|
||||
void GPU_HW_OpenGL::RestoreGraphicsAPIState()
|
||||
|
@ -114,6 +115,7 @@ void GPU_HW_OpenGL::RestoreGraphicsAPIState()
|
|||
if (m_resolution_scale > 1 && !m_supports_geometry_shaders)
|
||||
glLineWidth(static_cast<float>(m_resolution_scale));
|
||||
glBindVertexArray(m_vao_id);
|
||||
m_uniform_stream_buffer->Bind();
|
||||
|
||||
SetScissorFromDrawingArea();
|
||||
m_batch_ubo_dirty = true;
|
||||
|
|
|
@ -5,174 +5,15 @@
|
|||
#include <glad.h>
|
||||
Log_SetChannel(GPU_HW_ShaderGen);
|
||||
|
||||
GPU_HW_ShaderGen::GPU_HW_ShaderGen(HostDisplay::RenderAPI render_api, u32 resolution_scale, bool true_color,
|
||||
bool scaled_dithering, GPUTextureFilter texture_filtering, bool uv_limits,
|
||||
bool supports_dual_source_blend)
|
||||
: m_render_api(render_api), m_resolution_scale(resolution_scale), m_true_color(true_color),
|
||||
m_scaled_dithering(scaled_dithering), m_texture_filter(texture_filtering), m_uv_limits(uv_limits),
|
||||
m_glsl(render_api != HostDisplay::RenderAPI::D3D11), m_supports_dual_source_blend(supports_dual_source_blend),
|
||||
m_use_glsl_interface_blocks(false)
|
||||
GPU_HW_ShaderGen::GPU_HW_ShaderGen(HostDisplay::RenderAPI render_api, u32 resolution_scale, bool true_color, bool scaled_dithering, GPUTextureFilter texture_filtering, bool uv_limits, bool supports_dual_source_blend) :
|
||||
ShaderGen(render_api, supports_dual_source_blend),
|
||||
m_resolution_scale(resolution_scale), m_true_color(true_color),
|
||||
m_scaled_dithering(scaled_dithering), m_texture_filter(texture_filtering), m_uv_limits(uv_limits)
|
||||
{
|
||||
if (m_glsl)
|
||||
{
|
||||
if (m_render_api == HostDisplay::RenderAPI::OpenGL || m_render_api == HostDisplay::RenderAPI::OpenGLES)
|
||||
SetGLSLVersionString();
|
||||
|
||||
m_use_glsl_interface_blocks = (IsVulkan() || GLAD_GL_ES_VERSION_3_2 || GLAD_GL_VERSION_3_2);
|
||||
m_use_glsl_binding_layout = (IsVulkan() || UseGLSLBindingLayout());
|
||||
}
|
||||
}
|
||||
|
||||
GPU_HW_ShaderGen::~GPU_HW_ShaderGen() = default;
|
||||
|
||||
bool GPU_HW_ShaderGen::UseGLSLBindingLayout()
|
||||
{
|
||||
return (GLAD_GL_ES_VERSION_3_1 || GLAD_GL_VERSION_4_2 ||
|
||||
(GLAD_GL_ARB_explicit_attrib_location && GLAD_GL_ARB_explicit_uniform_location &&
|
||||
GLAD_GL_ARB_shading_language_420pack));
|
||||
}
|
||||
|
||||
static void DefineMacro(std::stringstream& ss, const char* name, bool enabled)
|
||||
{
|
||||
ss << "#define " << name << " " << BoolToUInt32(enabled) << "\n";
|
||||
}
|
||||
|
||||
void GPU_HW_ShaderGen::SetGLSLVersionString()
|
||||
{
|
||||
const char* glsl_version = reinterpret_cast<const char*>(glGetString(GL_SHADING_LANGUAGE_VERSION));
|
||||
const bool glsl_es = (m_render_api == HostDisplay::RenderAPI::OpenGLES);
|
||||
Assert(glsl_version != nullptr);
|
||||
|
||||
// Skip any strings in front of the version code.
|
||||
const char* glsl_version_start = glsl_version;
|
||||
while (*glsl_version_start != '\0' && (*glsl_version_start < '0' || *glsl_version_start > '9'))
|
||||
glsl_version_start++;
|
||||
|
||||
int major_version = 0, minor_version = 0;
|
||||
if (std::sscanf(glsl_version_start, "%d.%d", &major_version, &minor_version) == 2)
|
||||
{
|
||||
// Cap at GLSL 4.3, we're not using anything newer for now.
|
||||
if (!glsl_es && (major_version > 4 || (major_version == 4 && minor_version > 30)))
|
||||
{
|
||||
major_version = 4;
|
||||
minor_version = 30;
|
||||
}
|
||||
else if (glsl_es && (major_version > 3 || (major_version == 3 && minor_version > 20)))
|
||||
{
|
||||
major_version = 3;
|
||||
minor_version = 20;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Log_ErrorPrintf("Invalid GLSL version string: '%s' ('%s')", glsl_version, glsl_version_start);
|
||||
if (glsl_es)
|
||||
{
|
||||
major_version = 3;
|
||||
minor_version = 0;
|
||||
}
|
||||
m_glsl_version_string = glsl_es ? "300" : "130";
|
||||
}
|
||||
|
||||
char buf[128];
|
||||
std::snprintf(buf, sizeof(buf), "#version %d%02d%s", major_version, minor_version,
|
||||
(glsl_es && major_version >= 3) ? " es" : "");
|
||||
m_glsl_version_string = buf;
|
||||
}
|
||||
|
||||
void GPU_HW_ShaderGen::WriteHeader(std::stringstream& ss)
|
||||
{
|
||||
if (m_render_api == HostDisplay::RenderAPI::OpenGL || m_render_api == HostDisplay::RenderAPI::OpenGLES)
|
||||
ss << m_glsl_version_string << "\n\n";
|
||||
else if (m_render_api == HostDisplay::RenderAPI::Vulkan)
|
||||
ss << "#version 450 core\n\n";
|
||||
|
||||
// Extension enabling for OpenGL.
|
||||
if (m_render_api == HostDisplay::RenderAPI::OpenGLES)
|
||||
{
|
||||
// Enable EXT_blend_func_extended for dual-source blend on OpenGL ES.
|
||||
if (GLAD_GL_EXT_blend_func_extended)
|
||||
ss << "#extension GL_EXT_blend_func_extended : require\n";
|
||||
}
|
||||
else if (m_render_api == HostDisplay::RenderAPI::OpenGL)
|
||||
{
|
||||
// Need extensions for binding layout if GL<4.3.
|
||||
if (m_use_glsl_binding_layout && !GLAD_GL_VERSION_4_3)
|
||||
{
|
||||
ss << "#extension GL_ARB_explicit_attrib_location : require\n";
|
||||
ss << "#extension GL_ARB_explicit_uniform_location : require\n";
|
||||
ss << "#extension GL_ARB_shading_language_420pack : require\n";
|
||||
}
|
||||
|
||||
if (!GLAD_GL_VERSION_3_2)
|
||||
ss << "#extension GL_ARB_uniform_buffer_object : require\n";
|
||||
|
||||
// Enable SSBOs if it's not required by the version.
|
||||
if (!GLAD_GL_VERSION_4_3 && !GLAD_GL_ES_VERSION_3_1 && GLAD_GL_ARB_shader_storage_buffer_object)
|
||||
ss << "#extension GL_ARB_shader_storage_buffer_object : require\n";
|
||||
}
|
||||
|
||||
DefineMacro(ss, "API_OPENGL", m_render_api == HostDisplay::RenderAPI::OpenGL);
|
||||
DefineMacro(ss, "API_OPENGL_ES", m_render_api == HostDisplay::RenderAPI::OpenGLES);
|
||||
DefineMacro(ss, "API_D3D11", m_render_api == HostDisplay::RenderAPI::D3D11);
|
||||
DefineMacro(ss, "API_VULKAN", m_render_api == HostDisplay::RenderAPI::Vulkan);
|
||||
|
||||
if (m_render_api == HostDisplay::RenderAPI::OpenGLES)
|
||||
{
|
||||
ss << "precision highp float;\n";
|
||||
ss << "precision highp int;\n";
|
||||
ss << "precision highp sampler2D;\n";
|
||||
|
||||
if (GLAD_GL_ES_VERSION_3_2)
|
||||
ss << "precision highp usamplerBuffer;\n";
|
||||
|
||||
ss << "\n";
|
||||
}
|
||||
|
||||
if (m_glsl)
|
||||
{
|
||||
ss << "#define GLSL 1\n";
|
||||
ss << "#define float2 vec2\n";
|
||||
ss << "#define float3 vec3\n";
|
||||
ss << "#define float4 vec4\n";
|
||||
ss << "#define int2 ivec2\n";
|
||||
ss << "#define int3 ivec3\n";
|
||||
ss << "#define int4 ivec4\n";
|
||||
ss << "#define uint2 uvec2\n";
|
||||
ss << "#define uint3 uvec3\n";
|
||||
ss << "#define uint4 uvec4\n";
|
||||
ss << "#define nointerpolation flat\n";
|
||||
ss << "#define frac fract\n";
|
||||
ss << "#define lerp mix\n";
|
||||
|
||||
ss << "#define CONSTANT const\n";
|
||||
ss << "#define VECTOR_EQ(a, b) ((a) == (b))\n";
|
||||
ss << "#define VECTOR_NEQ(a, b) ((a) != (b))\n";
|
||||
ss << "#define VECTOR_COMP_EQ(a, b) equal((a), (b))\n";
|
||||
ss << "#define VECTOR_COMP_NEQ(a, b) notEqual((a), (b))\n";
|
||||
ss << "#define SAMPLE_TEXTURE(name, coords) texture(name, coords)\n";
|
||||
ss << "#define LOAD_TEXTURE(name, coords, mip) texelFetch(name, coords, mip)\n";
|
||||
ss << "#define LOAD_TEXTURE_OFFSET(name, coords, mip, offset) texelFetchOffset(name, coords, mip, offset)\n";
|
||||
ss << "#define LOAD_TEXTURE_BUFFER(name, index) texelFetch(name, index)\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
ss << "#define HLSL 1\n";
|
||||
ss << "#define roundEven round\n";
|
||||
ss << "#define CONSTANT static const\n";
|
||||
ss << "#define VECTOR_EQ(a, b) (all((a) == (b)))\n";
|
||||
ss << "#define VECTOR_NEQ(a, b) (any((a) != (b)))\n";
|
||||
ss << "#define VECTOR_COMP_EQ(a, b) ((a) == (b))\n";
|
||||
ss << "#define VECTOR_COMP_NEQ(a, b) ((a) != (b))\n";
|
||||
ss << "#define SAMPLE_TEXTURE(name, coords) name.Sample(name##_ss, coords)\n";
|
||||
ss << "#define LOAD_TEXTURE(name, coords, mip) name.Load(int3(coords, mip))\n";
|
||||
ss << "#define LOAD_TEXTURE_OFFSET(name, coords, mip, offset) name.Load(int3(coords, mip), offset)\n";
|
||||
ss << "#define LOAD_TEXTURE_BUFFER(name, index) name.Load(index)\n";
|
||||
}
|
||||
|
||||
ss << "\n";
|
||||
}
|
||||
|
||||
void GPU_HW_ShaderGen::WriteCommonFunctions(std::stringstream& ss)
|
||||
{
|
||||
ss << "CONSTANT uint RESOLUTION_SCALE = " << m_resolution_scale << "u;\n";
|
||||
|
@ -224,272 +65,6 @@ float4 RGBA5551ToRGBA8(uint v)
|
|||
)";
|
||||
}
|
||||
|
||||
void GPU_HW_ShaderGen::DeclareUniformBuffer(std::stringstream& ss, const std::initializer_list<const char*>& members,
|
||||
bool push_constant_on_vulkan)
|
||||
{
|
||||
if (IsVulkan())
|
||||
{
|
||||
if (push_constant_on_vulkan)
|
||||
ss << "layout(push_constant) uniform PushConstants\n";
|
||||
else
|
||||
ss << "layout(std140, set = 0, binding = 0) uniform UBOBlock\n";
|
||||
}
|
||||
else if (m_glsl)
|
||||
{
|
||||
if (m_use_glsl_binding_layout)
|
||||
ss << "layout(std140, binding = 1) uniform UBOBlock\n";
|
||||
else
|
||||
ss << "layout(std140) uniform UBOBlock\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
ss << "cbuffer UBOBlock : register(b0)\n";
|
||||
}
|
||||
|
||||
ss << "{\n";
|
||||
for (const char* member : members)
|
||||
ss << member << ";\n";
|
||||
ss << "};\n\n";
|
||||
}
|
||||
|
||||
void GPU_HW_ShaderGen::DeclareTexture(std::stringstream& ss, const char* name, u32 index)
|
||||
{
|
||||
if (m_glsl)
|
||||
{
|
||||
if (IsVulkan())
|
||||
ss << "layout(set = 0, binding = " << (index + 1u) << ") ";
|
||||
else if (m_use_glsl_binding_layout)
|
||||
ss << "layout(binding = " << index << ") ";
|
||||
|
||||
ss << "uniform sampler2D " << name << ";\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
ss << "Texture2D " << name << " : register(t" << index << ");\n";
|
||||
ss << "SamplerState " << name << "_ss : register(s" << index << ");\n";
|
||||
}
|
||||
}
|
||||
|
||||
void GPU_HW_ShaderGen::DeclareTextureBuffer(std::stringstream& ss, const char* name, u32 index, bool is_int,
|
||||
bool is_unsigned)
|
||||
{
|
||||
if (m_glsl)
|
||||
{
|
||||
if (IsVulkan())
|
||||
ss << "layout(set = 0, binding = " << index << ") ";
|
||||
else if (m_use_glsl_binding_layout)
|
||||
ss << "layout(binding = " << index << ") ";
|
||||
|
||||
ss << "uniform " << (is_int ? (is_unsigned ? "u" : "i") : "") << "samplerBuffer " << name << ";\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
ss << "Buffer<" << (is_int ? (is_unsigned ? "uint4" : "int4") : "float4") << "> " << name << " : register(t"
|
||||
<< index << ");\n";
|
||||
}
|
||||
}
|
||||
|
||||
void GPU_HW_ShaderGen::DeclareVertexEntryPoint(
|
||||
std::stringstream& ss, const std::initializer_list<const char*>& attributes, u32 num_color_outputs,
|
||||
u32 num_texcoord_outputs, const std::initializer_list<std::pair<const char*, const char*>>& additional_outputs,
|
||||
bool declare_vertex_id, const char* output_block_suffix)
|
||||
{
|
||||
if (m_glsl)
|
||||
{
|
||||
if (m_use_glsl_binding_layout)
|
||||
{
|
||||
u32 attribute_counter = 0;
|
||||
for (const char* attribute : attributes)
|
||||
{
|
||||
ss << "layout(location = " << attribute_counter << ") in " << attribute << ";\n";
|
||||
attribute_counter++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (const char* attribute : attributes)
|
||||
ss << "in " << attribute << ";\n";
|
||||
}
|
||||
|
||||
if (m_use_glsl_interface_blocks)
|
||||
{
|
||||
if (IsVulkan())
|
||||
ss << "layout(location = 0) ";
|
||||
|
||||
ss << "out VertexData" << output_block_suffix << " {\n";
|
||||
for (u32 i = 0; i < num_color_outputs; i++)
|
||||
ss << " float4 v_col" << i << ";\n";
|
||||
|
||||
for (u32 i = 0; i < num_texcoord_outputs; i++)
|
||||
ss << " float2 v_tex" << i << ";\n";
|
||||
|
||||
for (const auto [qualifiers, name] : additional_outputs)
|
||||
ss << " " << qualifiers << " " << name << ";\n";
|
||||
ss << "};\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
for (u32 i = 0; i < num_color_outputs; i++)
|
||||
ss << "out float4 v_col" << i << ";\n";
|
||||
|
||||
for (u32 i = 0; i < num_texcoord_outputs; i++)
|
||||
ss << "out float2 v_tex" << i << ";\n";
|
||||
|
||||
for (const auto [qualifiers, name] : additional_outputs)
|
||||
ss << qualifiers << " out " << name << ";\n";
|
||||
}
|
||||
|
||||
ss << "#define v_pos gl_Position\n\n";
|
||||
if (declare_vertex_id)
|
||||
{
|
||||
if (IsVulkan())
|
||||
ss << "#define v_id uint(gl_VertexIndex)\n";
|
||||
else
|
||||
ss << "#define v_id uint(gl_VertexID)\n";
|
||||
}
|
||||
|
||||
ss << "\n";
|
||||
ss << "void main()\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
ss << "void main(\n";
|
||||
|
||||
if (declare_vertex_id)
|
||||
ss << " in uint v_id : SV_VertexID,\n";
|
||||
|
||||
u32 attribute_counter = 0;
|
||||
for (const char* attribute : attributes)
|
||||
{
|
||||
ss << " in " << attribute << " : ATTR" << attribute_counter << ",\n";
|
||||
attribute_counter++;
|
||||
}
|
||||
|
||||
for (u32 i = 0; i < num_color_outputs; i++)
|
||||
ss << " out float4 v_col" << i << " : COLOR" << i << ",\n";
|
||||
|
||||
for (u32 i = 0; i < num_texcoord_outputs; i++)
|
||||
ss << " out float2 v_tex" << i << " : TEXCOORD" << i << ",\n";
|
||||
|
||||
u32 additional_counter = num_texcoord_outputs;
|
||||
for (const auto [qualifiers, name] : additional_outputs)
|
||||
{
|
||||
ss << " " << qualifiers << " out " << name << " : TEXCOORD" << additional_counter << ",\n";
|
||||
additional_counter++;
|
||||
}
|
||||
|
||||
ss << " out float4 v_pos : SV_Position)\n";
|
||||
}
|
||||
}
|
||||
|
||||
void GPU_HW_ShaderGen::DeclareFragmentEntryPoint(
|
||||
std::stringstream& ss, u32 num_color_inputs, u32 num_texcoord_inputs,
|
||||
const std::initializer_list<std::pair<const char*, const char*>>& additional_inputs,
|
||||
bool declare_fragcoord /* = false */, u32 num_color_outputs /* = 1 */, bool depth_output /* = false */)
|
||||
{
|
||||
if (m_glsl)
|
||||
{
|
||||
if (m_use_glsl_interface_blocks)
|
||||
{
|
||||
if (IsVulkan())
|
||||
ss << "layout(location = 0) ";
|
||||
|
||||
ss << "in VertexData {\n";
|
||||
for (u32 i = 0; i < num_color_inputs; i++)
|
||||
ss << " float4 v_col" << i << ";\n";
|
||||
|
||||
for (u32 i = 0; i < num_texcoord_inputs; i++)
|
||||
ss << " float2 v_tex" << i << ";\n";
|
||||
|
||||
for (const auto [qualifiers, name] : additional_inputs)
|
||||
ss << " " << qualifiers << " " << name << ";\n";
|
||||
ss << "};\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
for (u32 i = 0; i < num_color_inputs; i++)
|
||||
ss << "in float4 v_col" << i << ";\n";
|
||||
|
||||
for (u32 i = 0; i < num_texcoord_inputs; i++)
|
||||
ss << "in float2 v_tex" << i << ";\n";
|
||||
|
||||
for (const auto [qualifiers, name] : additional_inputs)
|
||||
ss << qualifiers << " in " << name << ";\n";
|
||||
}
|
||||
|
||||
if (declare_fragcoord)
|
||||
ss << "#define v_pos gl_FragCoord\n";
|
||||
|
||||
if (depth_output)
|
||||
ss << "#define o_depth gl_FragDepth\n";
|
||||
|
||||
if (m_use_glsl_binding_layout)
|
||||
{
|
||||
if (m_supports_dual_source_blend)
|
||||
{
|
||||
for (u32 i = 0; i < num_color_outputs; i++)
|
||||
ss << "layout(location = 0, index = " << i << ") out float4 o_col" << i << ";\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert(num_color_outputs <= 1);
|
||||
for (u32 i = 0; i < num_color_outputs; i++)
|
||||
ss << "layout(location = 0" << i << ") out float4 o_col" << i << ";\n";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (u32 i = 0; i < num_color_outputs; i++)
|
||||
ss << "out float4 o_col" << i << ";\n";
|
||||
}
|
||||
|
||||
ss << "\n";
|
||||
|
||||
ss << "void main()\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
{
|
||||
ss << "void main(\n";
|
||||
|
||||
for (u32 i = 0; i < num_color_inputs; i++)
|
||||
ss << " in float4 v_col" << i << " : COLOR" << i << ",\n";
|
||||
|
||||
for (u32 i = 0; i < num_texcoord_inputs; i++)
|
||||
ss << " in float2 v_tex" << i << " : TEXCOORD" << i << ",\n";
|
||||
|
||||
u32 additional_counter = num_texcoord_inputs;
|
||||
for (const auto [qualifiers, name] : additional_inputs)
|
||||
{
|
||||
ss << " " << qualifiers << " in " << name << " : TEXCOORD" << additional_counter << ",\n";
|
||||
additional_counter++;
|
||||
}
|
||||
|
||||
if (declare_fragcoord)
|
||||
ss << " in float4 v_pos : SV_Position,\n";
|
||||
|
||||
if (depth_output)
|
||||
{
|
||||
ss << " out float o_depth : SV_Depth";
|
||||
if (num_color_outputs > 0)
|
||||
ss << ",\n";
|
||||
else
|
||||
ss << ")\n";
|
||||
}
|
||||
|
||||
for (u32 i = 0; i < num_color_outputs; i++)
|
||||
{
|
||||
ss << " out float4 o_col" << i << " : SV_Target" << i;
|
||||
|
||||
if (i == (num_color_outputs - 1))
|
||||
ss << ")\n";
|
||||
else
|
||||
ss << ",\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GPU_HW_ShaderGen::WriteBatchUniformBuffer(std::stringstream& ss)
|
||||
{
|
||||
DeclareUniformBuffer(ss,
|
||||
|
@ -1371,41 +946,6 @@ float4 SampleFromVRAM(uint4 texpage, float2 coords)
|
|||
return ss.str();
|
||||
}
|
||||
|
||||
std::string GPU_HW_ShaderGen::GenerateScreenQuadVertexShader()
|
||||
{
|
||||
std::stringstream ss;
|
||||
WriteHeader(ss);
|
||||
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
|
||||
}
|
||||
)";
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::string GPU_HW_ShaderGen::GenerateFillFragmentShader()
|
||||
{
|
||||
std::stringstream ss;
|
||||
WriteHeader(ss);
|
||||
DeclareUniformBuffer(ss, {"float4 u_fill_color"}, true);
|
||||
DeclareFragmentEntryPoint(ss, 0, 1, {}, false, 1, true);
|
||||
|
||||
ss << R"(
|
||||
{
|
||||
o_col0 = u_fill_color;
|
||||
o_depth = u_fill_color.a;
|
||||
}
|
||||
)";
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::string GPU_HW_ShaderGen::GenerateInterlacedFillFragmentShader()
|
||||
{
|
||||
std::stringstream ss;
|
||||
|
@ -1427,24 +967,6 @@ std::string GPU_HW_ShaderGen::GenerateInterlacedFillFragmentShader()
|
|||
return ss.str();
|
||||
}
|
||||
|
||||
std::string GPU_HW_ShaderGen::GenerateCopyFragmentShader()
|
||||
{
|
||||
std::stringstream ss;
|
||||
WriteHeader(ss);
|
||||
DeclareUniformBuffer(ss, {"float4 u_src_rect"}, true);
|
||||
DeclareTexture(ss, "samp0", 0);
|
||||
DeclareFragmentEntryPoint(ss, 0, 1, {}, false, 1);
|
||||
|
||||
ss << R"(
|
||||
{
|
||||
float2 coords = u_src_rect.xy + v_tex0 * u_src_rect.zw;
|
||||
o_col0 = SAMPLE_TEXTURE(samp0, coords);
|
||||
}
|
||||
)";
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::string GPU_HW_ShaderGen::GenerateDisplayFragmentShader(bool depth_24bit,
|
||||
GPU_HW::InterlacedRenderMode interlace_mode)
|
||||
{
|
||||
|
|
|
@ -1,25 +1,18 @@
|
|||
#pragma once
|
||||
#include "gpu_hw.h"
|
||||
#include "host_display.h"
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include "shadergen.h"
|
||||
|
||||
class GPU_HW_ShaderGen
|
||||
class GPU_HW_ShaderGen : public ShaderGen
|
||||
{
|
||||
public:
|
||||
GPU_HW_ShaderGen(HostDisplay::RenderAPI render_api, u32 resolution_scale, bool true_color, bool scaled_dithering,
|
||||
GPUTextureFilter texture_filtering, bool uv_limits, bool supports_dual_source_blend);
|
||||
~GPU_HW_ShaderGen();
|
||||
|
||||
static bool UseGLSLBindingLayout();
|
||||
|
||||
std::string GenerateBatchVertexShader(bool textured);
|
||||
std::string GenerateBatchFragmentShader(GPU_HW::BatchRenderMode transparency, GPU::TextureMode texture_mode,
|
||||
bool dithering, bool interlacing);
|
||||
std::string GenerateScreenQuadVertexShader();
|
||||
std::string GenerateFillFragmentShader();
|
||||
std::string GenerateInterlacedFillFragmentShader();
|
||||
std::string GenerateCopyFragmentShader();
|
||||
std::string GenerateDisplayFragmentShader(bool depth_24bit, GPU_HW::InterlacedRenderMode interlace_mode);
|
||||
std::string GenerateVRAMReadFragmentShader();
|
||||
std::string GenerateVRAMWriteFragmentShader(bool use_ssbo);
|
||||
|
@ -27,36 +20,13 @@ public:
|
|||
std::string GenerateVRAMUpdateDepthFragmentShader();
|
||||
|
||||
private:
|
||||
ALWAYS_INLINE bool IsVulkan() const { return (m_render_api == HostDisplay::RenderAPI::Vulkan); }
|
||||
|
||||
void SetGLSLVersionString();
|
||||
void WriteHeader(std::stringstream& ss);
|
||||
void DeclareUniformBuffer(std::stringstream& ss, const std::initializer_list<const char*>& members,
|
||||
bool push_constant_on_vulkan);
|
||||
void DeclareTexture(std::stringstream& ss, const char* name, u32 index);
|
||||
void DeclareTextureBuffer(std::stringstream& ss, const char* name, u32 index, bool is_int, bool is_unsigned);
|
||||
void DeclareVertexEntryPoint(std::stringstream& ss, const std::initializer_list<const char*>& attributes,
|
||||
u32 num_color_outputs, u32 num_texcoord_outputs,
|
||||
const std::initializer_list<std::pair<const char*, const char*>>& additional_outputs,
|
||||
bool declare_vertex_id = false, const char* output_block_suffix = "");
|
||||
void DeclareFragmentEntryPoint(std::stringstream& ss, u32 num_color_inputs, u32 num_texcoord_inputs,
|
||||
const std::initializer_list<std::pair<const char*, const char*>>& additional_inputs,
|
||||
bool declare_fragcoord = false, u32 num_color_outputs = 1, bool depth_output = false);
|
||||
|
||||
void WriteCommonFunctions(std::stringstream& ss);
|
||||
void WriteBatchUniformBuffer(std::stringstream& ss);
|
||||
void WriteBatchTextureFilter(std::stringstream& ss, GPUTextureFilter texture_filter);
|
||||
|
||||
HostDisplay::RenderAPI m_render_api;
|
||||
u32 m_resolution_scale;
|
||||
bool m_true_color;
|
||||
bool m_scaled_dithering;
|
||||
GPUTextureFilter m_texture_filter;
|
||||
bool m_uv_limits;
|
||||
bool m_glsl;
|
||||
bool m_supports_dual_source_blend;
|
||||
bool m_use_glsl_interface_blocks;
|
||||
bool m_use_glsl_binding_layout;
|
||||
|
||||
std::string m_glsl_version_string;
|
||||
};
|
||||
|
|
|
@ -69,6 +69,8 @@ public:
|
|||
virtual bool CreateResources() = 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.
|
||||
virtual void ResizeRenderWindow(s32 new_window_width, s32 new_window_height) = 0;
|
||||
|
||||
|
|
|
@ -396,6 +396,7 @@ void HostInterface::SetDefaultSettings(SettingsInterface& si)
|
|||
si.SetBoolValue("Display", "ShowResolution", false);
|
||||
si.SetBoolValue("Display", "Fullscreen", false);
|
||||
si.SetBoolValue("Display", "VSync", true);
|
||||
si.SetStringValue("Display", "PostProcessChain", "");
|
||||
|
||||
si.SetBoolValue("CDROM", "ReadThread", true);
|
||||
si.SetBoolValue("CDROM", "RegionCheck", true);
|
||||
|
|
|
@ -135,6 +135,7 @@ void Settings::Load(SettingsInterface& si)
|
|||
display_show_speed = si.GetBoolValue("Display", "ShowSpeed", false);
|
||||
display_show_resolution = si.GetBoolValue("Display", "ShowResolution", false);
|
||||
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_region_check = si.GetBoolValue("CDROM", "RegionCheck", true);
|
||||
|
@ -244,6 +245,10 @@ void Settings::Save(SettingsInterface& si) const
|
|||
si.SetBoolValue("Display", "ShowSpeed", display_show_speed);
|
||||
si.SetBoolValue("Display", "ShowResolution", display_show_speed);
|
||||
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", "RegionCheck", cdrom_region_check);
|
||||
|
|
|
@ -86,6 +86,7 @@ struct Settings
|
|||
|
||||
GPURenderer gpu_renderer = GPURenderer::Software;
|
||||
std::string gpu_adapter;
|
||||
std::string display_post_process_chain;
|
||||
u32 gpu_resolution_scale = 1;
|
||||
bool gpu_use_debug_device = false;
|
||||
bool gpu_true_color = true;
|
||||
|
|
493
src/core/shadergen.cpp
Normal file
493
src/core/shadergen.cpp
Normal file
|
@ -0,0 +1,493 @@
|
|||
#include "shadergen.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/log.h"
|
||||
#include <cstdio>
|
||||
#include <glad.h>
|
||||
Log_SetChannel(ShaderGen);
|
||||
|
||||
ShaderGen::ShaderGen(HostDisplay::RenderAPI render_api, bool supports_dual_source_blend)
|
||||
: m_render_api(render_api), m_glsl(render_api != HostDisplay::RenderAPI::D3D11),
|
||||
m_supports_dual_source_blend(supports_dual_source_blend), m_use_glsl_interface_blocks(false)
|
||||
{
|
||||
if (m_glsl)
|
||||
{
|
||||
if (m_render_api == HostDisplay::RenderAPI::OpenGL || m_render_api == HostDisplay::RenderAPI::OpenGLES)
|
||||
SetGLSLVersionString();
|
||||
|
||||
m_use_glsl_interface_blocks = (IsVulkan() || GLAD_GL_ES_VERSION_3_2 || GLAD_GL_VERSION_3_2);
|
||||
m_use_glsl_binding_layout = (IsVulkan() || UseGLSLBindingLayout());
|
||||
}
|
||||
}
|
||||
|
||||
ShaderGen::~ShaderGen() = default;
|
||||
|
||||
bool ShaderGen::UseGLSLBindingLayout()
|
||||
{
|
||||
return (GLAD_GL_ES_VERSION_3_1 || GLAD_GL_VERSION_4_2 ||
|
||||
(GLAD_GL_ARB_explicit_attrib_location && GLAD_GL_ARB_explicit_uniform_location &&
|
||||
GLAD_GL_ARB_shading_language_420pack));
|
||||
}
|
||||
|
||||
void ShaderGen::DefineMacro(std::stringstream& ss, const char* name, bool enabled)
|
||||
{
|
||||
ss << "#define " << name << " " << BoolToUInt32(enabled) << "\n";
|
||||
}
|
||||
|
||||
void ShaderGen::SetGLSLVersionString()
|
||||
{
|
||||
const char* glsl_version = reinterpret_cast<const char*>(glGetString(GL_SHADING_LANGUAGE_VERSION));
|
||||
const bool glsl_es = (m_render_api == HostDisplay::RenderAPI::OpenGLES);
|
||||
Assert(glsl_version != nullptr);
|
||||
|
||||
// Skip any strings in front of the version code.
|
||||
const char* glsl_version_start = glsl_version;
|
||||
while (*glsl_version_start != '\0' && (*glsl_version_start < '0' || *glsl_version_start > '9'))
|
||||
glsl_version_start++;
|
||||
|
||||
int major_version = 0, minor_version = 0;
|
||||
if (std::sscanf(glsl_version_start, "%d.%d", &major_version, &minor_version) == 2)
|
||||
{
|
||||
// Cap at GLSL 4.3, we're not using anything newer for now.
|
||||
if (!glsl_es && (major_version > 4 || (major_version == 4 && minor_version > 30)))
|
||||
{
|
||||
major_version = 4;
|
||||
minor_version = 30;
|
||||
}
|
||||
else if (glsl_es && (major_version > 3 || (major_version == 3 && minor_version > 20)))
|
||||
{
|
||||
major_version = 3;
|
||||
minor_version = 20;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Log_ErrorPrintf("Invalid GLSL version string: '%s' ('%s')", glsl_version, glsl_version_start);
|
||||
if (glsl_es)
|
||||
{
|
||||
major_version = 3;
|
||||
minor_version = 0;
|
||||
}
|
||||
m_glsl_version_string = glsl_es ? "300" : "130";
|
||||
}
|
||||
|
||||
char buf[128];
|
||||
std::snprintf(buf, sizeof(buf), "#version %d%02d%s", major_version, minor_version,
|
||||
(glsl_es && major_version >= 3) ? " es" : "");
|
||||
m_glsl_version_string = buf;
|
||||
}
|
||||
|
||||
void ShaderGen::WriteHeader(std::stringstream& ss)
|
||||
{
|
||||
if (m_render_api == HostDisplay::RenderAPI::OpenGL || m_render_api == HostDisplay::RenderAPI::OpenGLES)
|
||||
ss << m_glsl_version_string << "\n\n";
|
||||
else if (m_render_api == HostDisplay::RenderAPI::Vulkan)
|
||||
ss << "#version 450 core\n\n";
|
||||
|
||||
// Extension enabling for OpenGL.
|
||||
if (m_render_api == HostDisplay::RenderAPI::OpenGLES)
|
||||
{
|
||||
// Enable EXT_blend_func_extended for dual-source blend on OpenGL ES.
|
||||
if (GLAD_GL_EXT_blend_func_extended)
|
||||
ss << "#extension GL_EXT_blend_func_extended : require\n";
|
||||
}
|
||||
else if (m_render_api == HostDisplay::RenderAPI::OpenGL)
|
||||
{
|
||||
// Need extensions for binding layout if GL<4.3.
|
||||
if (m_use_glsl_binding_layout && !GLAD_GL_VERSION_4_3)
|
||||
{
|
||||
ss << "#extension GL_ARB_explicit_attrib_location : require\n";
|
||||
ss << "#extension GL_ARB_explicit_uniform_location : require\n";
|
||||
ss << "#extension GL_ARB_shading_language_420pack : require\n";
|
||||
}
|
||||
|
||||
if (!GLAD_GL_VERSION_3_2)
|
||||
ss << "#extension GL_ARB_uniform_buffer_object : require\n";
|
||||
|
||||
// Enable SSBOs if it's not required by the version.
|
||||
if (!GLAD_GL_VERSION_4_3 && !GLAD_GL_ES_VERSION_3_1 && GLAD_GL_ARB_shader_storage_buffer_object)
|
||||
ss << "#extension GL_ARB_shader_storage_buffer_object : require\n";
|
||||
}
|
||||
|
||||
DefineMacro(ss, "API_OPENGL", m_render_api == HostDisplay::RenderAPI::OpenGL);
|
||||
DefineMacro(ss, "API_OPENGL_ES", m_render_api == HostDisplay::RenderAPI::OpenGLES);
|
||||
DefineMacro(ss, "API_D3D11", m_render_api == HostDisplay::RenderAPI::D3D11);
|
||||
DefineMacro(ss, "API_VULKAN", m_render_api == HostDisplay::RenderAPI::Vulkan);
|
||||
|
||||
if (m_render_api == HostDisplay::RenderAPI::OpenGLES)
|
||||
{
|
||||
ss << "precision highp float;\n";
|
||||
ss << "precision highp int;\n";
|
||||
ss << "precision highp sampler2D;\n";
|
||||
|
||||
if (GLAD_GL_ES_VERSION_3_2)
|
||||
ss << "precision highp usamplerBuffer;\n";
|
||||
|
||||
ss << "\n";
|
||||
}
|
||||
|
||||
if (m_glsl)
|
||||
{
|
||||
ss << "#define GLSL 1\n";
|
||||
ss << "#define float2 vec2\n";
|
||||
ss << "#define float3 vec3\n";
|
||||
ss << "#define float4 vec4\n";
|
||||
ss << "#define int2 ivec2\n";
|
||||
ss << "#define int3 ivec3\n";
|
||||
ss << "#define int4 ivec4\n";
|
||||
ss << "#define uint2 uvec2\n";
|
||||
ss << "#define uint3 uvec3\n";
|
||||
ss << "#define uint4 uvec4\n";
|
||||
ss << "#define nointerpolation flat\n";
|
||||
ss << "#define frac fract\n";
|
||||
ss << "#define lerp mix\n";
|
||||
|
||||
ss << "#define CONSTANT const\n";
|
||||
ss << "#define VECTOR_EQ(a, b) ((a) == (b))\n";
|
||||
ss << "#define VECTOR_NEQ(a, b) ((a) != (b))\n";
|
||||
ss << "#define VECTOR_COMP_EQ(a, b) equal((a), (b))\n";
|
||||
ss << "#define VECTOR_COMP_NEQ(a, b) notEqual((a), (b))\n";
|
||||
ss << "#define SAMPLE_TEXTURE(name, coords) texture(name, coords)\n";
|
||||
ss << "#define LOAD_TEXTURE(name, coords, mip) texelFetch(name, coords, mip)\n";
|
||||
ss << "#define LOAD_TEXTURE_OFFSET(name, coords, mip, offset) texelFetchOffset(name, coords, mip, offset)\n";
|
||||
ss << "#define LOAD_TEXTURE_BUFFER(name, index) texelFetch(name, index)\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
ss << "#define HLSL 1\n";
|
||||
ss << "#define roundEven round\n";
|
||||
ss << "#define CONSTANT static const\n";
|
||||
ss << "#define VECTOR_EQ(a, b) (all((a) == (b)))\n";
|
||||
ss << "#define VECTOR_NEQ(a, b) (any((a) != (b)))\n";
|
||||
ss << "#define VECTOR_COMP_EQ(a, b) ((a) == (b))\n";
|
||||
ss << "#define VECTOR_COMP_NEQ(a, b) ((a) != (b))\n";
|
||||
ss << "#define SAMPLE_TEXTURE(name, coords) name.Sample(name##_ss, coords)\n";
|
||||
ss << "#define LOAD_TEXTURE(name, coords, mip) name.Load(int3(coords, mip))\n";
|
||||
ss << "#define LOAD_TEXTURE_OFFSET(name, coords, mip, offset) name.Load(int3(coords, mip), offset)\n";
|
||||
ss << "#define LOAD_TEXTURE_BUFFER(name, index) name.Load(index)\n";
|
||||
}
|
||||
|
||||
ss << "\n";
|
||||
}
|
||||
|
||||
void ShaderGen::WriteUniformBufferDeclaration(std::stringstream& ss, bool push_constant_on_vulkan)
|
||||
{
|
||||
if (IsVulkan())
|
||||
{
|
||||
if (push_constant_on_vulkan)
|
||||
ss << "layout(push_constant) uniform PushConstants\n";
|
||||
else
|
||||
ss << "layout(std140, set = 0, binding = 0) uniform UBOBlock\n";
|
||||
}
|
||||
else if (m_glsl)
|
||||
{
|
||||
if (m_use_glsl_binding_layout)
|
||||
ss << "layout(std140, binding = 1) uniform UBOBlock\n";
|
||||
else
|
||||
ss << "layout(std140) uniform UBOBlock\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
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";
|
||||
for (const char* member : members)
|
||||
ss << member << ";\n";
|
||||
ss << "};\n\n";
|
||||
}
|
||||
|
||||
void ShaderGen::DeclareTexture(std::stringstream& ss, const char* name, u32 index)
|
||||
{
|
||||
if (m_glsl)
|
||||
{
|
||||
if (IsVulkan())
|
||||
ss << "layout(set = 0, binding = " << (index + 1u) << ") ";
|
||||
else if (m_use_glsl_binding_layout)
|
||||
ss << "layout(binding = " << index << ") ";
|
||||
|
||||
ss << "uniform sampler2D " << name << ";\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
ss << "Texture2D " << name << " : register(t" << index << ");\n";
|
||||
ss << "SamplerState " << name << "_ss : register(s" << index << ");\n";
|
||||
}
|
||||
}
|
||||
|
||||
void ShaderGen::DeclareTextureBuffer(std::stringstream& ss, const char* name, u32 index, bool is_int, bool is_unsigned)
|
||||
{
|
||||
if (m_glsl)
|
||||
{
|
||||
if (IsVulkan())
|
||||
ss << "layout(set = 0, binding = " << index << ") ";
|
||||
else if (m_use_glsl_binding_layout)
|
||||
ss << "layout(binding = " << index << ") ";
|
||||
|
||||
ss << "uniform " << (is_int ? (is_unsigned ? "u" : "i") : "") << "samplerBuffer " << name << ";\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
ss << "Buffer<" << (is_int ? (is_unsigned ? "uint4" : "int4") : "float4") << "> " << name << " : register(t"
|
||||
<< index << ");\n";
|
||||
}
|
||||
}
|
||||
|
||||
void ShaderGen::DeclareVertexEntryPoint(
|
||||
std::stringstream& ss, const std::initializer_list<const char*>& attributes, u32 num_color_outputs,
|
||||
u32 num_texcoord_outputs, const std::initializer_list<std::pair<const char*, const char*>>& additional_outputs,
|
||||
bool declare_vertex_id, const char* output_block_suffix)
|
||||
{
|
||||
if (m_glsl)
|
||||
{
|
||||
if (m_use_glsl_binding_layout)
|
||||
{
|
||||
u32 attribute_counter = 0;
|
||||
for (const char* attribute : attributes)
|
||||
{
|
||||
ss << "layout(location = " << attribute_counter << ") in " << attribute << ";\n";
|
||||
attribute_counter++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (const char* attribute : attributes)
|
||||
ss << "in " << attribute << ";\n";
|
||||
}
|
||||
|
||||
if (m_use_glsl_interface_blocks)
|
||||
{
|
||||
if (IsVulkan())
|
||||
ss << "layout(location = 0) ";
|
||||
|
||||
ss << "out VertexData" << output_block_suffix << " {\n";
|
||||
for (u32 i = 0; i < num_color_outputs; i++)
|
||||
ss << " float4 v_col" << i << ";\n";
|
||||
|
||||
for (u32 i = 0; i < num_texcoord_outputs; i++)
|
||||
ss << " float2 v_tex" << i << ";\n";
|
||||
|
||||
for (const auto [qualifiers, name] : additional_outputs)
|
||||
ss << " " << qualifiers << " " << name << ";\n";
|
||||
ss << "};\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
for (u32 i = 0; i < num_color_outputs; i++)
|
||||
ss << "out float4 v_col" << i << ";\n";
|
||||
|
||||
for (u32 i = 0; i < num_texcoord_outputs; i++)
|
||||
ss << "out float2 v_tex" << i << ";\n";
|
||||
|
||||
for (const auto [qualifiers, name] : additional_outputs)
|
||||
ss << qualifiers << " out " << name << ";\n";
|
||||
}
|
||||
|
||||
ss << "#define v_pos gl_Position\n\n";
|
||||
if (declare_vertex_id)
|
||||
{
|
||||
if (IsVulkan())
|
||||
ss << "#define v_id uint(gl_VertexIndex)\n";
|
||||
else
|
||||
ss << "#define v_id uint(gl_VertexID)\n";
|
||||
}
|
||||
|
||||
ss << "\n";
|
||||
ss << "void main()\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
ss << "void main(\n";
|
||||
|
||||
if (declare_vertex_id)
|
||||
ss << " in uint v_id : SV_VertexID,\n";
|
||||
|
||||
u32 attribute_counter = 0;
|
||||
for (const char* attribute : attributes)
|
||||
{
|
||||
ss << " in " << attribute << " : ATTR" << attribute_counter << ",\n";
|
||||
attribute_counter++;
|
||||
}
|
||||
|
||||
for (u32 i = 0; i < num_color_outputs; i++)
|
||||
ss << " out float4 v_col" << i << " : COLOR" << i << ",\n";
|
||||
|
||||
for (u32 i = 0; i < num_texcoord_outputs; i++)
|
||||
ss << " out float2 v_tex" << i << " : TEXCOORD" << i << ",\n";
|
||||
|
||||
u32 additional_counter = num_texcoord_outputs;
|
||||
for (const auto [qualifiers, name] : additional_outputs)
|
||||
{
|
||||
ss << " " << qualifiers << " out " << name << " : TEXCOORD" << additional_counter << ",\n";
|
||||
additional_counter++;
|
||||
}
|
||||
|
||||
ss << " out float4 v_pos : SV_Position)\n";
|
||||
}
|
||||
}
|
||||
|
||||
void ShaderGen::DeclareFragmentEntryPoint(
|
||||
std::stringstream& ss, u32 num_color_inputs, u32 num_texcoord_inputs,
|
||||
const std::initializer_list<std::pair<const char*, const char*>>& additional_inputs,
|
||||
bool declare_fragcoord /* = false */, u32 num_color_outputs /* = 1 */, bool depth_output /* = false */)
|
||||
{
|
||||
if (m_glsl)
|
||||
{
|
||||
if (m_use_glsl_interface_blocks)
|
||||
{
|
||||
if (IsVulkan())
|
||||
ss << "layout(location = 0) ";
|
||||
|
||||
ss << "in VertexData {\n";
|
||||
for (u32 i = 0; i < num_color_inputs; i++)
|
||||
ss << " float4 v_col" << i << ";\n";
|
||||
|
||||
for (u32 i = 0; i < num_texcoord_inputs; i++)
|
||||
ss << " float2 v_tex" << i << ";\n";
|
||||
|
||||
for (const auto [qualifiers, name] : additional_inputs)
|
||||
ss << " " << qualifiers << " " << name << ";\n";
|
||||
ss << "};\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
for (u32 i = 0; i < num_color_inputs; i++)
|
||||
ss << "in float4 v_col" << i << ";\n";
|
||||
|
||||
for (u32 i = 0; i < num_texcoord_inputs; i++)
|
||||
ss << "in float2 v_tex" << i << ";\n";
|
||||
|
||||
for (const auto [qualifiers, name] : additional_inputs)
|
||||
ss << qualifiers << " in " << name << ";\n";
|
||||
}
|
||||
|
||||
if (declare_fragcoord)
|
||||
ss << "#define v_pos gl_FragCoord\n";
|
||||
|
||||
if (depth_output)
|
||||
ss << "#define o_depth gl_FragDepth\n";
|
||||
|
||||
if (m_use_glsl_binding_layout)
|
||||
{
|
||||
if (m_supports_dual_source_blend)
|
||||
{
|
||||
for (u32 i = 0; i < num_color_outputs; i++)
|
||||
ss << "layout(location = 0, index = " << i << ") out float4 o_col" << i << ";\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert(num_color_outputs <= 1);
|
||||
for (u32 i = 0; i < num_color_outputs; i++)
|
||||
ss << "layout(location = 0" << i << ") out float4 o_col" << i << ";\n";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (u32 i = 0; i < num_color_outputs; i++)
|
||||
ss << "out float4 o_col" << i << ";\n";
|
||||
}
|
||||
|
||||
ss << "\n";
|
||||
|
||||
ss << "void main()\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
{
|
||||
ss << "void main(\n";
|
||||
|
||||
for (u32 i = 0; i < num_color_inputs; i++)
|
||||
ss << " in float4 v_col" << i << " : COLOR" << i << ",\n";
|
||||
|
||||
for (u32 i = 0; i < num_texcoord_inputs; i++)
|
||||
ss << " in float2 v_tex" << i << " : TEXCOORD" << i << ",\n";
|
||||
|
||||
u32 additional_counter = num_texcoord_inputs;
|
||||
for (const auto [qualifiers, name] : additional_inputs)
|
||||
{
|
||||
ss << " " << qualifiers << " in " << name << " : TEXCOORD" << additional_counter << ",\n";
|
||||
additional_counter++;
|
||||
}
|
||||
|
||||
if (declare_fragcoord)
|
||||
ss << " in float4 v_pos : SV_Position,\n";
|
||||
|
||||
if (depth_output)
|
||||
{
|
||||
ss << " out float o_depth : SV_Depth";
|
||||
if (num_color_outputs > 0)
|
||||
ss << ",\n";
|
||||
else
|
||||
ss << ")\n";
|
||||
}
|
||||
|
||||
for (u32 i = 0; i < num_color_outputs; i++)
|
||||
{
|
||||
ss << " out float4 o_col" << i << " : SV_Target" << i;
|
||||
|
||||
if (i == (num_color_outputs - 1))
|
||||
ss << ")\n";
|
||||
else
|
||||
ss << ",\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string ShaderGen::GenerateScreenQuadVertexShader()
|
||||
{
|
||||
std::stringstream ss;
|
||||
WriteHeader(ss);
|
||||
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
|
||||
}
|
||||
)";
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::string ShaderGen::GenerateFillFragmentShader()
|
||||
{
|
||||
std::stringstream ss;
|
||||
WriteHeader(ss);
|
||||
DeclareUniformBuffer(ss, {"float4 u_fill_color"}, true);
|
||||
DeclareFragmentEntryPoint(ss, 0, 1, {}, false, 1, true);
|
||||
|
||||
ss << R"(
|
||||
{
|
||||
o_col0 = u_fill_color;
|
||||
o_depth = u_fill_color.a;
|
||||
}
|
||||
)";
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::string ShaderGen::GenerateCopyFragmentShader()
|
||||
{
|
||||
std::stringstream ss;
|
||||
WriteHeader(ss);
|
||||
DeclareUniformBuffer(ss, {"float4 u_src_rect"}, true);
|
||||
DeclareTexture(ss, "samp0", 0);
|
||||
DeclareFragmentEntryPoint(ss, 0, 1, {}, false, 1);
|
||||
|
||||
ss << R"(
|
||||
{
|
||||
float2 coords = u_src_rect.xy + v_tex0 * u_src_rect.zw;
|
||||
o_col0 = SAMPLE_TEXTURE(samp0, coords);
|
||||
}
|
||||
)";
|
||||
|
||||
return ss.str();
|
||||
}
|
45
src/core/shadergen.h
Normal file
45
src/core/shadergen.h
Normal file
|
@ -0,0 +1,45 @@
|
|||
#pragma once
|
||||
#include "gpu_hw.h"
|
||||
#include "host_display.h"
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
class ShaderGen
|
||||
{
|
||||
public:
|
||||
ShaderGen(HostDisplay::RenderAPI render_api, bool supports_dual_source_blend);
|
||||
~ShaderGen();
|
||||
|
||||
static bool UseGLSLBindingLayout();
|
||||
|
||||
std::string GenerateScreenQuadVertexShader();
|
||||
std::string GenerateFillFragmentShader();
|
||||
std::string GenerateCopyFragmentShader();
|
||||
|
||||
protected:
|
||||
ALWAYS_INLINE bool IsVulkan() const { return (m_render_api == HostDisplay::RenderAPI::Vulkan); }
|
||||
|
||||
void SetGLSLVersionString();
|
||||
void DefineMacro(std::stringstream& ss, const char* name, bool enabled);
|
||||
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,
|
||||
bool push_constant_on_vulkan);
|
||||
void DeclareTexture(std::stringstream& ss, const char* name, u32 index);
|
||||
void DeclareTextureBuffer(std::stringstream& ss, const char* name, u32 index, bool is_int, bool is_unsigned);
|
||||
void DeclareVertexEntryPoint(std::stringstream& ss, const std::initializer_list<const char*>& attributes,
|
||||
u32 num_color_outputs, u32 num_texcoord_outputs,
|
||||
const std::initializer_list<std::pair<const char*, const char*>>& additional_outputs,
|
||||
bool declare_vertex_id = false, const char* output_block_suffix = "");
|
||||
void DeclareFragmentEntryPoint(std::stringstream& ss, u32 num_color_inputs, u32 num_texcoord_inputs,
|
||||
const std::initializer_list<std::pair<const char*, const char*>>& additional_inputs,
|
||||
bool declare_fragcoord = false, u32 num_color_outputs = 1, bool depth_output = false);
|
||||
|
||||
HostDisplay::RenderAPI m_render_api;
|
||||
bool m_glsl;
|
||||
bool m_supports_dual_source_blend;
|
||||
bool m_use_glsl_interface_blocks;
|
||||
bool m_use_glsl_binding_layout;
|
||||
|
||||
std::string m_glsl_version_string;
|
||||
};
|
|
@ -156,6 +156,11 @@ void LibretroHostDisplay::ResizeRenderWindow(s32 new_window_width, s32 new_windo
|
|||
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,
|
||||
u32 data_stride, bool dynamic)
|
||||
{
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
#pragma once
|
||||
#include "core/host_display.h"
|
||||
#include <memory>
|
||||
|
||||
class LibretroHostDisplay final : public HostDisplay
|
||||
{
|
||||
|
@ -26,6 +25,8 @@ public:
|
|||
void ResizeRenderWindow(s32 new_window_width, s32 new_window_height) override;
|
||||
void DestroyRenderSurface() override;
|
||||
|
||||
bool SetPostProcessingChain(const std::string_view& config) override;
|
||||
|
||||
bool CreateResources() override;
|
||||
void DestroyResources() override;
|
||||
|
||||
|
|
|
@ -54,6 +54,11 @@ set(SRCS
|
|||
mainwindow.ui
|
||||
memorycardsettingswidget.cpp
|
||||
memorycardsettingswidget.h
|
||||
postprocessingchainconfigwidget.cpp
|
||||
postprocessingchainconfigwidget.h
|
||||
postprocessingchainconfigwidget.ui
|
||||
postprocessingshaderconfigwidget.cpp
|
||||
postprocessingshaderconfigwidget.h
|
||||
qtdisplaywidget.cpp
|
||||
qtdisplaywidget.h
|
||||
qthostinterface.cpp
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
#include "displaysettingswidget.h"
|
||||
#include "core/gpu.h"
|
||||
#include "core/settings.h"
|
||||
#include "postprocessingchainconfigwidget.h"
|
||||
#include "qtutils.h"
|
||||
#include "settingsdialog.h"
|
||||
#include "settingwidgetbinder.h"
|
||||
#include <QtWidgets/QMessageBox>
|
||||
|
||||
// For enumerating adapters.
|
||||
#include "frontend-common/vulkan_host_display.h"
|
||||
|
@ -45,6 +47,25 @@ DisplaySettingsWidget::DisplaySettingsWidget(QtHostInterface* host_interface, QW
|
|||
&DisplaySettingsWidget::onGPUAdapterIndexChanged);
|
||||
populateGPUAdapters();
|
||||
|
||||
{
|
||||
std::string post_chain = g_host_interface->GetStringSettingValue("Display", "PostProcessChain");
|
||||
if (!post_chain.empty() && !m_ui.postChain->setConfigString(post_chain))
|
||||
{
|
||||
QMessageBox::critical(this, tr("Error"),
|
||||
tr("The current post-processing chain is invalid, it has been reset. Any changes made will "
|
||||
"overwrite the existing config."));
|
||||
}
|
||||
}
|
||||
connect(m_ui.postChain, &PostProcessingChainConfigWidget::chainConfigStringChanged,
|
||||
[this](const std::string& new_config) {
|
||||
if (new_config.empty())
|
||||
m_host_interface->RemoveSettingValue("Display", "PostProcessChain");
|
||||
else
|
||||
m_host_interface->SetStringSettingValue("Display", "PostProcessChain", new_config.c_str());
|
||||
|
||||
m_host_interface->applySettings();
|
||||
});
|
||||
|
||||
dialog->registerWidgetHelp(
|
||||
m_ui.renderer, tr("Renderer"), Settings::GetRendererDisplayName(Settings::DEFAULT_GPU_RENDERER),
|
||||
tr("Chooses the backend to use for rendering the console/game visuals. <br>Depending on your system and hardware, "
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include "ui_displaysettingswidget.h"
|
||||
|
||||
class QtHostInterface;
|
||||
class PostProcessingChainConfigWidget;
|
||||
class SettingsDialog;
|
||||
|
||||
class DisplaySettingsWidget : public QWidget
|
||||
|
|
|
@ -26,86 +26,90 @@
|
|||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
<property name="title">
|
||||
<string>Basic</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout_3">
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout_3">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Renderer:</string>
|
||||
<string>Renderer:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="renderer"/>
|
||||
<widget class="QComboBox" name="renderer"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>Adapter:</string>
|
||||
<string>Adapter:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QComboBox" name="adapter"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_3">
|
||||
<property name="title">
|
||||
<string>Screen Display</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout_4">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>Aspect Ratio:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="displayAspectRatio"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Crop:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QComboBox" name="displayCropMode"/>
|
||||
<widget class="QComboBox" name="adapter"/>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="displayLinearFiltering">
|
||||
<widget class="QCheckBox" name="vsync">
|
||||
<property name="text">
|
||||
<string>Linear Upscaling</string>
|
||||
<string>VSync</string>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="displayIntegerScaling">
|
||||
<property name="text">
|
||||
<string>Integer Upscaling</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="vsync">
|
||||
<property name="text">
|
||||
<string>VSync</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_3">
|
||||
<property name="title">
|
||||
<string>Screen Display</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>Aspect Ratio:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="displayAspectRatio"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Crop:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QComboBox" name="displayCropMode"/>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="2">
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="0" column="0">
|
||||
<widget class="QCheckBox" name="displayLinearFiltering">
|
||||
<property name="text">
|
||||
<string>Linear Upscaling</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QCheckBox" name="displayIntegerScaling">
|
||||
<property name="text">
|
||||
<string>Integer Upscaling</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_5">
|
||||
<property name="title">
|
||||
<string>On-Screen Display</string>
|
||||
|
@ -149,21 +153,53 @@
|
|||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_2">
|
||||
<property name="title">
|
||||
<string>Post-Processing Chain</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="PostProcessingChainConfigWidget" name="postChain" native="true"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>PostProcessingChainConfigWidget</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>postprocessingchainconfigwidget.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
|
|
|
@ -56,6 +56,8 @@
|
|||
<ClCompile Include="mainwindow.cpp" />
|
||||
<ClCompile Include="controllersettingswidget.cpp" />
|
||||
<ClCompile Include="memorycardsettingswidget.cpp" />
|
||||
<ClCompile Include="postprocessingchainconfigwidget.cpp" />
|
||||
<ClCompile Include="postprocessingshaderconfigwidget.cpp" />
|
||||
<ClCompile Include="qthostinterface.cpp" />
|
||||
<ClCompile Include="qtprogresscallback.cpp" />
|
||||
<ClCompile Include="qtutils.cpp" />
|
||||
|
@ -84,6 +86,8 @@
|
|||
<QtMoc Include="gamelistsettingswidget.h" />
|
||||
<QtMoc Include="gamelistwidget.h" />
|
||||
<QtMoc Include="gamepropertiesdialog.h" />
|
||||
<QtMoc Include="postprocessingchainconfigwidget.h" />
|
||||
<QtMoc Include="postprocessingshaderconfigwidget.h" />
|
||||
<QtMoc Include="mainwindow.h" />
|
||||
<QtMoc Include="qthostinterface.h" />
|
||||
<ClInclude Include="qtutils.h" />
|
||||
|
@ -143,6 +147,9 @@
|
|||
<QtUi Include="gamepropertiesdialog.ui">
|
||||
<FileType>Document</FileType>
|
||||
</QtUi>
|
||||
<QtUi Include="postprocessingchainconfigwidget.ui">
|
||||
<FileType>Document</FileType>
|
||||
</QtUi>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<QtResource Include="resources\resources.qrc">
|
||||
|
@ -169,6 +176,8 @@
|
|||
<ClCompile Include="$(IntDir)moc_inputbindingwidgets.cpp" />
|
||||
<ClCompile Include="$(IntDir)moc_mainwindow.cpp" />
|
||||
<ClCompile Include="$(IntDir)moc_memorycardsettingswidget.cpp" />
|
||||
<ClCompile Include="$(IntDir)moc_postprocessingchainconfigwidget.cpp" />
|
||||
<ClCompile Include="$(IntDir)moc_postprocessingshaderconfigwidget.cpp" />
|
||||
<ClCompile Include="$(IntDir)moc_qtdisplaywidget.cpp" />
|
||||
<ClCompile Include="$(IntDir)moc_qthostinterface.cpp" />
|
||||
<ClCompile Include="$(IntDir)moc_qtprogresscallback.cpp" />
|
||||
|
|
174
src/duckstation-qt/postprocessingchainconfigwidget.cpp
Normal file
174
src/duckstation-qt/postprocessingchainconfigwidget.cpp
Normal file
|
@ -0,0 +1,174 @@
|
|||
#include "postprocessingchainconfigwidget.h"
|
||||
#include "frontend-common/postprocessing_chain.h"
|
||||
#include <QtGui/QCursor>
|
||||
#include <QtWidgets/QMenu>
|
||||
#include <QtWidgets/QMessageBox>
|
||||
|
||||
PostProcessingChainConfigWidget::PostProcessingChainConfigWidget(QWidget* parent) : QWidget(parent)
|
||||
{
|
||||
m_ui.setupUi(this);
|
||||
connectUi();
|
||||
updateButtonStates();
|
||||
}
|
||||
|
||||
PostProcessingChainConfigWidget::~PostProcessingChainConfigWidget() = default;
|
||||
|
||||
void PostProcessingChainConfigWidget::connectUi()
|
||||
{
|
||||
connect(m_ui.add, &QPushButton::clicked, this, &PostProcessingChainConfigWidget::onAddButtonClicked);
|
||||
connect(m_ui.remove, &QPushButton::clicked, this, &PostProcessingChainConfigWidget::onRemoveButtonClicked);
|
||||
connect(m_ui.clear, &QPushButton::clicked, this, &PostProcessingChainConfigWidget::onClearButtonClicked);
|
||||
connect(m_ui.moveUp, &QPushButton::clicked, this, &PostProcessingChainConfigWidget::onMoveUpButtonClicked);
|
||||
connect(m_ui.moveDown, &QPushButton::clicked, this, &PostProcessingChainConfigWidget::onMoveDownButtonClicked);
|
||||
connect(m_ui.shaderSettings, &QPushButton::clicked, this,
|
||||
&PostProcessingChainConfigWidget::onShaderConfigButtonClicked);
|
||||
connect(m_ui.shaders, &QListWidget::itemSelectionChanged, this, &PostProcessingChainConfigWidget::updateButtonStates);
|
||||
|
||||
m_ui.loadPreset->setEnabled(false);
|
||||
m_ui.savePreset->setEnabled(false);
|
||||
}
|
||||
|
||||
bool PostProcessingChainConfigWidget::setConfigString(const std::string_view& config_string)
|
||||
{
|
||||
if (!m_chain.CreateFromString(config_string))
|
||||
return false;
|
||||
|
||||
updateList();
|
||||
return true;
|
||||
}
|
||||
|
||||
std::optional<u32> PostProcessingChainConfigWidget::getSelectedIndex() const
|
||||
{
|
||||
QList<QListWidgetItem*> selected_items = m_ui.shaders->selectedItems();
|
||||
return selected_items.empty() ? std::nullopt :
|
||||
std::optional<u32>(selected_items.first()->data(Qt::UserRole).toUInt());
|
||||
}
|
||||
|
||||
void PostProcessingChainConfigWidget::updateList()
|
||||
{
|
||||
m_ui.shaders->clear();
|
||||
|
||||
for (u32 i = 0; i < m_chain.GetStageCount(); i++)
|
||||
{
|
||||
const FrontendCommon::PostProcessingShader& shader = m_chain.GetShaderStage(i);
|
||||
|
||||
QListWidgetItem* item = new QListWidgetItem(QString::fromStdString(shader.GetName()), m_ui.shaders);
|
||||
item->setData(Qt::UserRole, QVariant(i));
|
||||
}
|
||||
|
||||
updateButtonStates();
|
||||
}
|
||||
|
||||
void PostProcessingChainConfigWidget::configChanged()
|
||||
{
|
||||
if (m_chain.IsEmpty())
|
||||
chainConfigStringChanged(std::string());
|
||||
else
|
||||
chainConfigStringChanged(m_chain.GetConfigString());
|
||||
}
|
||||
|
||||
void PostProcessingChainConfigWidget::updateButtonStates()
|
||||
{
|
||||
std::optional<u32> index = getSelectedIndex();
|
||||
m_ui.remove->setEnabled(index.has_value());
|
||||
m_ui.clear->setEnabled(!m_chain.IsEmpty());
|
||||
m_ui.shaderSettings->setEnabled(index.has_value());
|
||||
|
||||
if (index.has_value())
|
||||
{
|
||||
m_ui.moveUp->setEnabled(index.value() > 0);
|
||||
m_ui.moveDown->setEnabled(index.value() < (m_chain.GetStageCount() - 1u));
|
||||
}
|
||||
else
|
||||
{
|
||||
m_ui.moveUp->setEnabled(false);
|
||||
m_ui.moveDown->setEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
void PostProcessingChainConfigWidget::onAddButtonClicked()
|
||||
{
|
||||
QMenu menu;
|
||||
|
||||
const std::vector<std::string> shaders(FrontendCommon::PostProcessingChain::GetAvailableShaderNames());
|
||||
if (shaders.empty())
|
||||
{
|
||||
menu.addAction(tr("No Shaders Available"))->setEnabled(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (const std::string& shader : shaders)
|
||||
{
|
||||
QAction* action = menu.addAction(QString::fromStdString(shader));
|
||||
connect(action, &QAction::triggered, [this, &shader]() {
|
||||
if (!m_chain.AddStage(shader))
|
||||
{
|
||||
QMessageBox::critical(this, tr("Error"), tr("Failed to add shader. The log may contain more information."));
|
||||
return;
|
||||
}
|
||||
|
||||
updateList();
|
||||
configChanged();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
menu.exec(QCursor::pos());
|
||||
}
|
||||
|
||||
void PostProcessingChainConfigWidget::onRemoveButtonClicked()
|
||||
{
|
||||
QList<QListWidgetItem*> selected_items = m_ui.shaders->selectedItems();
|
||||
if (selected_items.empty())
|
||||
return;
|
||||
|
||||
QListWidgetItem* item = selected_items.first();
|
||||
u32 index = item->data(Qt::UserRole).toUInt();
|
||||
if (index < m_chain.GetStageCount())
|
||||
{
|
||||
m_chain.RemoveStage(index);
|
||||
updateList();
|
||||
configChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void PostProcessingChainConfigWidget::onClearButtonClicked()
|
||||
{
|
||||
if (QMessageBox::question(this, tr("Question"), tr("Are you sure you want to clear all shader stages?"),
|
||||
QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes)
|
||||
{
|
||||
m_chain.ClearStages();
|
||||
updateList();
|
||||
configChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void PostProcessingChainConfigWidget::onMoveUpButtonClicked()
|
||||
{
|
||||
std::optional<u32> index = getSelectedIndex();
|
||||
if (index.has_value())
|
||||
{
|
||||
m_chain.MoveStageUp(index.value());
|
||||
updateList();
|
||||
configChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void PostProcessingChainConfigWidget::onMoveDownButtonClicked()
|
||||
{
|
||||
std::optional<u32> index = getSelectedIndex();
|
||||
if (index.has_value())
|
||||
{
|
||||
m_chain.MoveStageDown(index.value());
|
||||
updateList();
|
||||
configChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void PostProcessingChainConfigWidget::onShaderConfigButtonClicked()
|
||||
{
|
||||
std::optional<u32> index = getSelectedIndex();
|
||||
if (index.has_value())
|
||||
{
|
||||
}
|
||||
}
|
45
src/duckstation-qt/postprocessingchainconfigwidget.h
Normal file
45
src/duckstation-qt/postprocessingchainconfigwidget.h
Normal file
|
@ -0,0 +1,45 @@
|
|||
#pragma once
|
||||
#include "common/types.h"
|
||||
#include "ui_postprocessingchainconfigwidget.h"
|
||||
#include "frontend-common/postprocessing_chain.h"
|
||||
#include <QtWidgets/QWidget>
|
||||
#include <optional>
|
||||
#include <memory>
|
||||
#include <string_view>
|
||||
|
||||
namespace FrontendCommon {
|
||||
class PostProcessingChain;
|
||||
}
|
||||
|
||||
class PostProcessingChainConfigWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
PostProcessingChainConfigWidget(QWidget* parent);
|
||||
~PostProcessingChainConfigWidget();
|
||||
|
||||
bool setConfigString(const std::string_view& config_string);
|
||||
|
||||
Q_SIGNALS:
|
||||
void chainConfigStringChanged(const std::string& new_config_string);
|
||||
|
||||
private Q_SLOTS:
|
||||
void onAddButtonClicked();
|
||||
void onRemoveButtonClicked();
|
||||
void onClearButtonClicked();
|
||||
void onMoveUpButtonClicked();
|
||||
void onMoveDownButtonClicked();
|
||||
void onShaderConfigButtonClicked();
|
||||
void updateButtonStates();
|
||||
|
||||
private:
|
||||
void connectUi();
|
||||
std::optional<u32> getSelectedIndex() const;
|
||||
void updateList();
|
||||
void configChanged();
|
||||
|
||||
Ui::PostProcessingChainConfigWidget m_ui;
|
||||
|
||||
FrontendCommon::PostProcessingChain m_chain;
|
||||
};
|
130
src/duckstation-qt/postprocessingchainconfigwidget.ui
Normal file
130
src/duckstation-qt/postprocessingchainconfigwidget.ui
Normal file
|
@ -0,0 +1,130 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>PostProcessingChainConfigWidget</class>
|
||||
<widget class="QWidget" name="PostProcessingChainConfigWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>497</width>
|
||||
<height>151</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="1" column="0">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QToolButton" name="add">
|
||||
<property name="text">
|
||||
<string>Add</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="resources/resources.qrc">
|
||||
<normaloff>:/icons/list-add.png</normaloff>:/icons/list-add.png</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="remove">
|
||||
<property name="text">
|
||||
<string>Remove</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="resources/resources.qrc">
|
||||
<normaloff>:/icons/list-remove.png</normaloff>:/icons/list-remove.png</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="clear">
|
||||
<property name="text">
|
||||
<string>Clear</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="resources/resources.qrc">
|
||||
<normaloff>:/icons/edit-clear-16.png</normaloff>:/icons/edit-clear-16.png</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="moveUp">
|
||||
<property name="text">
|
||||
<string>Move Up</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="resources/resources.qrc">
|
||||
<normaloff>:/icons/go-up-16.png</normaloff>:/icons/go-up-16.png</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="moveDown">
|
||||
<property name="text">
|
||||
<string>Move Down</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="resources/resources.qrc">
|
||||
<normaloff>:/icons/go-down-16.png</normaloff>:/icons/go-down-16.png</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="shaderSettings">
|
||||
<property name="text">
|
||||
<string>Shader Settings...</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="resources/resources.qrc">
|
||||
<normaloff>:/icons/preferences-system@2x.png</normaloff>:/icons/preferences-system@2x.png</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="loadPreset">
|
||||
<property name="text">
|
||||
<string>Load Preset</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="savePreset">
|
||||
<property name="text">
|
||||
<string>Save Preset</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QListWidget" name="shaders">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>50</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources>
|
||||
<include location="resources/resources.qrc"/>
|
||||
</resources>
|
||||
<connections/>
|
||||
</ui>
|
BIN
src/duckstation-qt/resources/icons/edit-clear-16.png
Normal file
BIN
src/duckstation-qt/resources/icons/edit-clear-16.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 912 B |
BIN
src/duckstation-qt/resources/icons/edit-clear-16@2x.png
Normal file
BIN
src/duckstation-qt/resources/icons/edit-clear-16@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.7 KiB |
BIN
src/duckstation-qt/resources/icons/go-down-16.png
Normal file
BIN
src/duckstation-qt/resources/icons/go-down-16.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 745 B |
BIN
src/duckstation-qt/resources/icons/go-down-16@2x.png
Normal file
BIN
src/duckstation-qt/resources/icons/go-down-16@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.6 KiB |
BIN
src/duckstation-qt/resources/icons/go-up-16.png
Normal file
BIN
src/duckstation-qt/resources/icons/go-up-16.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 755 B |
BIN
src/duckstation-qt/resources/icons/go-up-16@2x.png
Normal file
BIN
src/duckstation-qt/resources/icons/go-up-16@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
|
@ -32,6 +32,8 @@
|
|||
<file>icons/duck.png</file>
|
||||
<file>icons/duck_128.png</file>
|
||||
<file>icons/duck_64.png</file>
|
||||
<file>icons/edit-clear-16.png</file>
|
||||
<file>icons/edit-clear-16@2x.png</file>
|
||||
<file>icons/edit-find.png</file>
|
||||
<file>icons/flag-eu.png</file>
|
||||
<file>icons/flag-eu@2x.png</file>
|
||||
|
@ -43,6 +45,10 @@
|
|||
<file>icons/flag-us@2x.png</file>
|
||||
<file>icons/folder-open.png</file>
|
||||
<file>icons/folder-open@2x.png</file>
|
||||
<file>icons/go-down-16.png</file>
|
||||
<file>icons/go-down-16@2x.png</file>
|
||||
<file>icons/go-up-16.png</file>
|
||||
<file>icons/go-up-16@2x.png</file>
|
||||
<file>icons/input-gaming.png</file>
|
||||
<file>icons/input-gaming@2x.png</file>
|
||||
<file>icons/list-add.png</file>
|
||||
|
|
|
@ -48,6 +48,12 @@ if(NOT BUILD_LIBRETRO_CORE)
|
|||
imgui_styles.h
|
||||
ini_settings_interface.cpp
|
||||
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.h
|
||||
)
|
||||
|
|
|
@ -123,6 +123,7 @@ void CommonHostInterface::InitializeUserDirectory()
|
|||
result &= FileSystem::CreateDirectory(GetUserDirectoryRelativePath("inputprofiles").c_str(), false);
|
||||
result &= FileSystem::CreateDirectory(GetUserDirectoryRelativePath("savestates").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);
|
||||
|
||||
if (!result)
|
||||
|
@ -172,6 +173,7 @@ void CommonHostInterface::DestroySystem()
|
|||
{
|
||||
SetTimerResolutionIncreased(false);
|
||||
m_save_state_selector_ui->Close();
|
||||
m_display->SetPostProcessingChain({});
|
||||
|
||||
HostInterface::DestroySystem();
|
||||
}
|
||||
|
@ -684,6 +686,9 @@ void CommonHostInterface::SetUserDirectory()
|
|||
void CommonHostInterface::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)
|
||||
|
@ -1955,6 +1960,12 @@ void CommonHostInterface::CheckForSettingsChanges(const Settings& old_settings)
|
|||
{
|
||||
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 ||
|
||||
|
|
|
@ -3,10 +3,12 @@
|
|||
#include "common/d3d11/shader_compiler.h"
|
||||
#include "common/log.h"
|
||||
#include "common/string_util.h"
|
||||
#include "core/settings.h"
|
||||
#include "display_ps.hlsl.h"
|
||||
#include "display_vs.hlsl.h"
|
||||
#include <array>
|
||||
#ifndef LIBRETRO
|
||||
#include "frontend-common/postprocessing_shadergen.h"
|
||||
#include <dxgi1_5.h>
|
||||
#endif
|
||||
#ifdef WITH_IMGUI
|
||||
|
@ -524,6 +526,12 @@ bool D3D11HostDisplay::CreateResources()
|
|||
|
||||
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_linear_sampler.Reset();
|
||||
m_point_sampler.Reset();
|
||||
|
@ -580,9 +588,7 @@ bool D3D11HostDisplay::Render()
|
|||
if (ImGui::GetCurrentContext())
|
||||
ImGui_ImplDX11_NewFrame();
|
||||
#endif
|
||||
#else
|
||||
RenderDisplay();
|
||||
RenderSoftwareCursor();
|
||||
|
||||
#endif
|
||||
|
||||
return true;
|
||||
|
@ -598,13 +604,24 @@ void D3D11HostDisplay::RenderImGui()
|
|||
|
||||
void D3D11HostDisplay::RenderDisplay()
|
||||
{
|
||||
#ifndef LIBRETRO
|
||||
if (!HasDisplayTexture())
|
||||
return;
|
||||
|
||||
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,
|
||||
m_display_texture_view_x, m_display_texture_view_y, m_display_texture_view_width,
|
||||
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,
|
||||
|
@ -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_width) - 0.5f) / static_cast<float>(texture_width),
|
||||
(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));
|
||||
m_display_uniform_buffer.Unmap(m_context.Get(), sizeof(uniforms));
|
||||
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());
|
||||
|
||||
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));
|
||||
m_display_uniform_buffer.Unmap(m_context.Get(), sizeof(uniforms));
|
||||
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;
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
} // namespace FrontendCommon
|
||||
|
|
|
@ -13,6 +13,10 @@
|
|||
#include <vector>
|
||||
#include <wrl/client.h>
|
||||
|
||||
#ifndef LIBRETRO
|
||||
#include "frontend-common/postprocessing_chain.h"
|
||||
#endif
|
||||
|
||||
namespace FrontendCommon {
|
||||
|
||||
class D3D11HostDisplay : public HostDisplay
|
||||
|
@ -42,6 +46,8 @@ public:
|
|||
virtual void ResizeRenderWindow(s32 new_window_width, s32 new_window_height) 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,
|
||||
u32 initial_data_stride, bool dynamic) override;
|
||||
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);
|
||||
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<ID3D11DeviceContext> m_context;
|
||||
|
||||
|
@ -109,6 +131,10 @@ protected:
|
|||
bool m_using_flip_model_swap_chain = true;
|
||||
bool m_using_allow_tearing = false;
|
||||
bool m_vsync = true;
|
||||
|
||||
PostProcessingChain m_post_processing_chain;
|
||||
D3D11::Texture m_post_processing_input_texture;
|
||||
std::vector<PostProcessingStage> m_post_processing_stages;
|
||||
#endif
|
||||
};
|
||||
|
||||
|
|
|
@ -79,6 +79,9 @@
|
|||
<ClCompile Include="imgui_styles.cpp" />
|
||||
<ClCompile Include="ini_settings_interface.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="sdl_audio_stream.cpp" />
|
||||
<ClCompile Include="sdl_controller_interface.cpp" />
|
||||
|
@ -99,6 +102,9 @@
|
|||
<ClInclude Include="imgui_styles.h" />
|
||||
<ClInclude Include="ini_settings_interface.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="sdl_audio_stream.h" />
|
||||
<ClInclude Include="sdl_controller_interface.h" />
|
||||
|
|
|
@ -19,6 +19,9 @@
|
|||
<ClCompile Include="imgui_impl_dx11.cpp" />
|
||||
<ClCompile Include="imgui_impl_opengl3.cpp" />
|
||||
<ClCompile Include="imgui_impl_vulkan.cpp" />
|
||||
<ClCompile Include="postprocessing_shader.cpp" />
|
||||
<ClCompile Include="postprocessing_shadergen.cpp" />
|
||||
<ClCompile Include="postprocessing_chain.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="icon.h" />
|
||||
|
@ -39,6 +42,9 @@
|
|||
<ClInclude Include="imgui_impl_vulkan.h" />
|
||||
<ClInclude Include="imgui_impl_dx11.h" />
|
||||
<ClInclude Include="imgui_impl_opengl3.h" />
|
||||
<ClInclude Include="postprocessing_shader.h" />
|
||||
<ClInclude Include="postprocessing_shadergen.h" />
|
||||
<ClInclude Include="postprocessing_chain.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="font_roboto_regular.inl" />
|
||||
|
|
|
@ -7,6 +7,9 @@
|
|||
#include "imgui.h"
|
||||
#include "imgui_impl_opengl3.h"
|
||||
#endif
|
||||
#ifndef LIBRETRO
|
||||
#include "postprocessing_shadergen.h"
|
||||
#endif
|
||||
Log_SetChannel(LibretroOpenGLHostDisplay);
|
||||
|
||||
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)
|
||||
{
|
||||
glGetIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, reinterpret_cast<GLint*>(&m_uniform_buffer_alignment));
|
||||
|
||||
if (debug_device && GLAD_GL_KHR_debug)
|
||||
{
|
||||
if (GetRenderAPI() == RenderAPI::OpenGLES)
|
||||
|
@ -418,6 +423,13 @@ void main()
|
|||
|
||||
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)
|
||||
glDeleteVertexArrays(1, &m_display_vao);
|
||||
if (m_display_linear_sampler != 0)
|
||||
|
@ -470,9 +482,18 @@ void OpenGLHostDisplay::RenderDisplay()
|
|||
return;
|
||||
|
||||
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,
|
||||
m_display_texture_view_width, m_display_texture_view_height, m_display_linear_filtering);
|
||||
|
||||
if (!m_post_processing_chain.IsEmpty())
|
||||
{
|
||||
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,
|
||||
|
@ -526,4 +547,181 @@ void OpenGLHostDisplay::RenderSoftwareCursor(s32 left, s32 bottom, s32 width, s3
|
|||
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
|
||||
|
|
|
@ -10,11 +10,16 @@
|
|||
|
||||
#include "common/gl/context.h"
|
||||
#include "common/gl/program.h"
|
||||
#include "common/gl/stream_buffer.h"
|
||||
#include "common/gl/texture.h"
|
||||
#include "common/window_info.h"
|
||||
#include "core/host_display.h"
|
||||
#include <memory>
|
||||
|
||||
#ifndef LIBRETRO
|
||||
#include "postprocessing_chain.h"
|
||||
#endif
|
||||
|
||||
namespace FrontendCommon {
|
||||
|
||||
class OpenGLHostDisplay : public HostDisplay
|
||||
|
@ -41,6 +46,8 @@ public:
|
|||
virtual void ResizeRenderWindow(s32 new_window_width, s32 new_window_height) 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,
|
||||
u32 initial_data_stride, bool dynamic) override;
|
||||
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);
|
||||
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;
|
||||
|
||||
GL::Program m_display_program;
|
||||
|
@ -78,6 +99,14 @@ protected:
|
|||
GLuint m_display_vao = 0;
|
||||
GLuint m_display_nearest_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
|
203
src/frontend-common/postprocessing_chain.cpp
Normal file
203
src/frontend-common/postprocessing_chain.cpp
Normal 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
|
36
src/frontend-common/postprocessing_chain.h
Normal file
36
src/frontend-common/postprocessing_chain.h
Normal 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
|
412
src/frontend-common/postprocessing_shader.cpp
Normal file
412
src/frontend-common/postprocessing_shader.cpp
Normal 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 = ¤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 == 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
|
106
src/frontend-common/postprocessing_shader.h
Normal file
106
src/frontend-common/postprocessing_shader.h
Normal 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
|
184
src/frontend-common/postprocessing_shadergen.cpp
Normal file
184
src/frontend-common/postprocessing_shadergen.cpp
Normal 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
|
21
src/frontend-common/postprocessing_shadergen.h
Normal file
21
src/frontend-common/postprocessing_shadergen.h
Normal 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
|
|
@ -14,6 +14,9 @@
|
|||
#include "imgui.h"
|
||||
#include "imgui_impl_vulkan.h"
|
||||
#endif
|
||||
#ifndef LIBRETRO
|
||||
#include "postprocessing_shadergen.h"
|
||||
#endif
|
||||
Log_SetChannel(VulkanHostDisplay);
|
||||
|
||||
namespace FrontendCommon {
|
||||
|
@ -343,6 +346,34 @@ void main()
|
|||
if (m_pipeline_layout == VK_NULL_HANDLE)
|
||||
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);
|
||||
if (vertex_shader == VK_NULL_HANDLE)
|
||||
return false;
|
||||
|
@ -395,6 +426,17 @@ void main()
|
|||
|
||||
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_upload_staging_texture.Destroy(false);
|
||||
|
||||
|
@ -500,17 +542,6 @@ bool VulkanHostDisplay::Render()
|
|||
swap_chain_texture.OverrideImageLayout(VK_IMAGE_LAYOUT_UNDEFINED);
|
||||
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();
|
||||
|
||||
#ifdef WITH_IMGUI
|
||||
|
@ -537,12 +568,38 @@ bool VulkanHostDisplay::Render()
|
|||
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()
|
||||
{
|
||||
if (!HasDisplayTexture())
|
||||
{
|
||||
BeginSwapChainRenderPass(m_swap_chain->GetCurrentFramebuffer());
|
||||
return;
|
||||
}
|
||||
|
||||
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,
|
||||
m_display_texture_view_x, m_display_texture_view_y, m_display_texture_view_width,
|
||||
m_display_texture_view_height, m_display_linear_filtering);
|
||||
|
@ -646,4 +703,273 @@ std::vector<std::string> VulkanHostDisplay::EnumerateAdapterNames()
|
|||
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
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#pragma once
|
||||
#include "common/vulkan/staging_texture.h"
|
||||
#include "common/vulkan/stream_buffer.h"
|
||||
#include "common/vulkan/swap_chain.h"
|
||||
#include "common/window_info.h"
|
||||
#include "core/host_display.h"
|
||||
|
@ -12,6 +13,10 @@ class StreamBuffer;
|
|||
class SwapChain;
|
||||
} // namespace Vulkan
|
||||
|
||||
#ifndef LIBRETRO
|
||||
#include "postprocessing_chain.h"
|
||||
#endif
|
||||
|
||||
namespace FrontendCommon {
|
||||
|
||||
class VulkanHostDisplay : public HostDisplay
|
||||
|
@ -38,6 +43,8 @@ public:
|
|||
virtual void ResizeRenderWindow(s32 new_window_width, s32 new_window_height) 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,
|
||||
u32 initial_data_stride, bool dynamic) override;
|
||||
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;
|
||||
};
|
||||
|
||||
#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.
|
||||
virtual VkRenderPass GetRenderPassForDisplay() const;
|
||||
|
||||
|
@ -69,6 +95,7 @@ protected:
|
|||
virtual bool CreateImGuiContext();
|
||||
virtual void DestroyImGuiContext();
|
||||
|
||||
void BeginSwapChainRenderPass(VkFramebuffer framebuffer);
|
||||
void RenderDisplay();
|
||||
void RenderImGui();
|
||||
void RenderSoftwareCursor();
|
||||
|
@ -89,6 +116,19 @@ protected:
|
|||
|
||||
Vulkan::StagingTexture m_upload_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
|
Loading…
Reference in a new issue