summaryrefslogtreecommitdiff
path: root/Source/Core
diff options
context:
space:
mode:
authorStenzek <stenzek@users.noreply.github.com>2017-04-28 22:50:14 +1000
committerGitHub <noreply@github.com>2017-04-28 22:50:14 +1000
commit68ee4fc932ddff320f535b6054ceff0c58348b64 (patch)
tree937bda8967539f3dfbc15133b9e78fbfba4a555c /Source/Core
parentf2035384e5e03ec86abefeb7ab24fa1fc3dd8f39 (diff)
parent27ae5b8d34675244af689b260f0dbb391bb6209e (diff)
Merge pull request #5296 from stenzek/vulkan-postprocessing
Vulkan: Implement support for post-processing
Diffstat (limited to 'Source/Core')
-rw-r--r--Source/Core/DolphinWX/VideoConfigDiag.cpp7
-rw-r--r--Source/Core/VideoBackends/D3D/main.cpp4
-rw-r--r--Source/Core/VideoBackends/D3D12/main.cpp4
-rw-r--r--Source/Core/VideoBackends/Null/NullBackend.cpp2
-rw-r--r--Source/Core/VideoBackends/OGL/PostProcessing.h4
-rw-r--r--Source/Core/VideoBackends/OGL/Render.cpp7
-rw-r--r--Source/Core/VideoBackends/OGL/main.cpp21
-rw-r--r--Source/Core/VideoBackends/Vulkan/CMakeLists.txt1
-rw-r--r--Source/Core/VideoBackends/Vulkan/ObjectCache.cpp32
-rw-r--r--Source/Core/VideoBackends/Vulkan/ObjectCache.h7
-rw-r--r--Source/Core/VideoBackends/Vulkan/PostProcessing.cpp319
-rw-r--r--Source/Core/VideoBackends/Vulkan/PostProcessing.h45
-rw-r--r--Source/Core/VideoBackends/Vulkan/RasterFont.cpp5
-rw-r--r--Source/Core/VideoBackends/Vulkan/RasterFont.h2
-rw-r--r--Source/Core/VideoBackends/Vulkan/Renderer.cpp76
-rw-r--r--Source/Core/VideoBackends/Vulkan/Renderer.h7
-rw-r--r--Source/Core/VideoBackends/Vulkan/Vulkan.vcxproj2
-rw-r--r--Source/Core/VideoBackends/Vulkan/VulkanContext.cpp2
-rw-r--r--Source/Core/VideoCommon/PostProcessing.cpp35
-rw-r--r--Source/Core/VideoCommon/PostProcessing.h9
-rw-r--r--Source/Core/VideoCommon/VideoConfig.h2
21 files changed, 473 insertions, 120 deletions
diff --git a/Source/Core/DolphinWX/VideoConfigDiag.cpp b/Source/Core/DolphinWX/VideoConfigDiag.cpp
index 6f4ae390d2..2efc80c7e0 100644
--- a/Source/Core/DolphinWX/VideoConfigDiag.cpp
+++ b/Source/Core/DolphinWX/VideoConfigDiag.cpp
@@ -1253,9 +1253,10 @@ void VideoConfigDiag::CreateDescriptionArea(wxPanel* const page, wxBoxSizer* con
void VideoConfigDiag::PopulatePostProcessingShaders()
{
- std::vector<std::string>& shaders = (vconfig.iStereoMode == STEREO_ANAGLYPH) ?
- vconfig.backend_info.AnaglyphShaders :
- vconfig.backend_info.PPShaders;
+ std::vector<std::string> shaders =
+ vconfig.iStereoMode == STEREO_ANAGLYPH ?
+ PostProcessingShaderImplementation::GetAnaglyphShaderList(vconfig.backend_info.api_type) :
+ PostProcessingShaderImplementation::GetShaderList(vconfig.backend_info.api_type);
if (shaders.empty())
return;
diff --git a/Source/Core/VideoBackends/D3D/main.cpp b/Source/Core/VideoBackends/D3D/main.cpp
index bc0af68180..48c7dd8391 100644
--- a/Source/Core/VideoBackends/D3D/main.cpp
+++ b/Source/Core/VideoBackends/D3D/main.cpp
@@ -128,10 +128,6 @@ void VideoBackend::InitBackendInfo()
}
factory->Release();
- // Clear ppshaders string vector
- g_Config.backend_info.PPShaders.clear();
- g_Config.backend_info.AnaglyphShaders.clear();
-
DX11::D3D::UnloadDXGI();
DX11::D3D::UnloadD3D();
}
diff --git a/Source/Core/VideoBackends/D3D12/main.cpp b/Source/Core/VideoBackends/D3D12/main.cpp
index 2c95bff055..cb075fa071 100644
--- a/Source/Core/VideoBackends/D3D12/main.cpp
+++ b/Source/Core/VideoBackends/D3D12/main.cpp
@@ -140,10 +140,6 @@ void VideoBackend::InitBackendInfo()
}
factory->Release();
- // Clear ppshaders string vector
- g_Config.backend_info.PPShaders.clear();
- g_Config.backend_info.AnaglyphShaders.clear();
-
D3D::UnloadD3D();
D3D::UnloadDXGI();
}
diff --git a/Source/Core/VideoBackends/Null/NullBackend.cpp b/Source/Core/VideoBackends/Null/NullBackend.cpp
index 60a6f637ac..7892b86927 100644
--- a/Source/Core/VideoBackends/Null/NullBackend.cpp
+++ b/Source/Core/VideoBackends/Null/NullBackend.cpp
@@ -49,8 +49,6 @@ void VideoBackend::InitBackendInfo()
// aamodes: We only support 1 sample, so no MSAA
g_Config.backend_info.Adapters.clear();
g_Config.backend_info.AAModes = {1};
- g_Config.backend_info.PPShaders.clear();
- g_Config.backend_info.AnaglyphShaders.clear();
}
bool VideoBackend::Initialize(void* window_handle)
diff --git a/Source/Core/VideoBackends/OGL/PostProcessing.h b/Source/Core/VideoBackends/OGL/PostProcessing.h
index 706b113c22..1c8c8ecf52 100644
--- a/Source/Core/VideoBackends/OGL/PostProcessing.h
+++ b/Source/Core/VideoBackends/OGL/PostProcessing.h
@@ -23,8 +23,8 @@ public:
~OpenGLPostProcessing();
void BlitFromTexture(TargetRectangle src, TargetRectangle dst, int src_texture, int src_width,
- int src_height, int layer) override;
- void ApplyShader() override;
+ int src_height, int layer);
+ void ApplyShader();
private:
bool m_initialized;
diff --git a/Source/Core/VideoBackends/OGL/Render.cpp b/Source/Core/VideoBackends/OGL/Render.cpp
index ec68c14f23..ce58be68cc 100644
--- a/Source/Core/VideoBackends/OGL/Render.cpp
+++ b/Source/Core/VideoBackends/OGL/Render.cpp
@@ -1176,6 +1176,7 @@ void Renderer::ClearScreen(const EFBRectangle& rc, bool colorEnable, bool alphaE
void Renderer::BlitScreen(TargetRectangle src, TargetRectangle dst, GLuint src_texture,
int src_width, int src_height)
{
+ OpenGLPostProcessing* post_processor = static_cast<OpenGLPostProcessing*>(m_post_processor.get());
if (g_ActiveConfig.iStereoMode == STEREO_SBS || g_ActiveConfig.iStereoMode == STEREO_TAB)
{
TargetRectangle leftRc, rightRc;
@@ -1186,12 +1187,12 @@ void Renderer::BlitScreen(TargetRectangle src, TargetRectangle dst, GLuint src_t
else
std::tie(leftRc, rightRc) = ConvertStereoRectangle(dst);
- m_post_processor->BlitFromTexture(src, leftRc, src_texture, src_width, src_height, 0);
- m_post_processor->BlitFromTexture(src, rightRc, src_texture, src_width, src_height, 1);
+ post_processor->BlitFromTexture(src, leftRc, src_texture, src_width, src_height, 0);
+ post_processor->BlitFromTexture(src, rightRc, src_texture, src_width, src_height, 1);
}
else
{
- m_post_processor->BlitFromTexture(src, dst, src_texture, src_width, src_height);
+ post_processor->BlitFromTexture(src, dst, src_texture, src_width, src_height, 0);
}
}
diff --git a/Source/Core/VideoBackends/OGL/main.cpp b/Source/Core/VideoBackends/OGL/main.cpp
index 868d789785..430859932e 100644
--- a/Source/Core/VideoBackends/OGL/main.cpp
+++ b/Source/Core/VideoBackends/OGL/main.cpp
@@ -38,8 +38,6 @@ Make AA apply instantly during gameplay if possible
#include <string>
#include <vector>
-#include "Common/CommonPaths.h"
-#include "Common/FileSearch.h"
#include "Common/GL/GLInterfaceBase.h"
#include "Common/GL/GLUtil.h"
#include "Common/MsgHandler.h"
@@ -79,21 +77,6 @@ std::string VideoBackend::GetDisplayName() const
return "OpenGL";
}
-static std::vector<std::string> GetShaders(const std::string& sub_dir = "")
-{
- std::vector<std::string> paths =
- Common::DoFileSearch({".glsl"}, {File::GetUserPath(D_SHADERS_IDX) + sub_dir,
- File::GetSysDirectory() + SHADERS_DIR DIR_SEP + sub_dir});
- std::vector<std::string> result;
- for (std::string path : paths)
- {
- std::string name;
- SplitPath(path, nullptr, &name, nullptr);
- result.push_back(name);
- }
- return result;
-}
-
void VideoBackend::InitBackendInfo()
{
g_Config.backend_info.api_type = APIType::OpenGL;
@@ -125,10 +108,6 @@ void VideoBackend::InitBackendInfo()
// aamodes - 1 is to stay consistent with D3D (means no AA)
g_Config.backend_info.AAModes = {1, 2, 4, 8};
-
- // pp shaders
- g_Config.backend_info.PPShaders = GetShaders("");
- g_Config.backend_info.AnaglyphShaders = GetShaders(ANAGLYPH_DIR DIR_SEP);
}
bool VideoBackend::InitializeGLExtensions()
diff --git a/Source/Core/VideoBackends/Vulkan/CMakeLists.txt b/Source/Core/VideoBackends/Vulkan/CMakeLists.txt
index 8d9f980dd9..1b5ed96f9e 100644
--- a/Source/Core/VideoBackends/Vulkan/CMakeLists.txt
+++ b/Source/Core/VideoBackends/Vulkan/CMakeLists.txt
@@ -4,6 +4,7 @@ set(SRCS
FramebufferManager.cpp
ObjectCache.cpp
PerfQuery.cpp
+ PostProcessing.cpp
RasterFont.cpp
Renderer.cpp
ShaderCompiler.cpp
diff --git a/Source/Core/VideoBackends/Vulkan/ObjectCache.cpp b/Source/Core/VideoBackends/Vulkan/ObjectCache.cpp
index 431728f39e..4fadbfcfc4 100644
--- a/Source/Core/VideoBackends/Vulkan/ObjectCache.cpp
+++ b/Source/Core/VideoBackends/Vulkan/ObjectCache.cpp
@@ -421,6 +421,23 @@ VkPipeline ObjectCache::GetComputePipeline(const ComputePipelineInfo& info)
return pipeline;
}
+void ObjectCache::ClearPipelineCache()
+{
+ for (const auto& it : m_pipeline_objects)
+ {
+ if (it.second != VK_NULL_HANDLE)
+ vkDestroyPipeline(g_vulkan_context->GetDevice(), it.second, nullptr);
+ }
+ m_pipeline_objects.clear();
+
+ for (const auto& it : m_compute_pipeline_objects)
+ {
+ if (it.second != VK_NULL_HANDLE)
+ vkDestroyPipeline(g_vulkan_context->GetDevice(), it.second, nullptr);
+ }
+ m_compute_pipeline_objects.clear();
+}
+
std::string ObjectCache::GetDiskCacheFileName(const char* type)
{
return StringFromFormat("%svulkan-%s-%s.cache", File::GetUserPath(D_SHADERCACHE_IDX).c_str(),
@@ -567,20 +584,7 @@ bool ObjectCache::ValidatePipelineCache(const u8* data, size_t data_length)
void ObjectCache::DestroyPipelineCache()
{
- for (const auto& it : m_pipeline_objects)
- {
- if (it.second != VK_NULL_HANDLE)
- vkDestroyPipeline(g_vulkan_context->GetDevice(), it.second, nullptr);
- }
- m_pipeline_objects.clear();
-
- for (const auto& it : m_compute_pipeline_objects)
- {
- if (it.second != VK_NULL_HANDLE)
- vkDestroyPipeline(g_vulkan_context->GetDevice(), it.second, nullptr);
- }
- m_compute_pipeline_objects.clear();
-
+ ClearPipelineCache();
vkDestroyPipelineCache(g_vulkan_context->GetDevice(), m_pipeline_cache, nullptr);
m_pipeline_cache = VK_NULL_HANDLE;
}
diff --git a/Source/Core/VideoBackends/Vulkan/ObjectCache.h b/Source/Core/VideoBackends/Vulkan/ObjectCache.h
index ac2fe7e962..97c457a7f8 100644
--- a/Source/Core/VideoBackends/Vulkan/ObjectCache.h
+++ b/Source/Core/VideoBackends/Vulkan/ObjectCache.h
@@ -137,6 +137,13 @@ public:
// Find a pipeline by the specified description, if not found, attempts to create it
VkPipeline GetComputePipeline(const ComputePipelineInfo& info);
+ // Clears our pipeline cache of all objects. This is necessary when recompiling shaders,
+ // as drivers are free to return the same pointer again, which means that we may end up using
+ // and old pipeline object if they are not cleared first. Some stutter may be experienced
+ // while our cache is rebuilt on use, but the pipeline cache object should mitigate this.
+ // NOTE: Ensure that none of these objects are in use before calling.
+ void ClearPipelineCache();
+
// Saves the pipeline cache to disk. Call when shutting down.
void SavePipelineCache();
diff --git a/Source/Core/VideoBackends/Vulkan/PostProcessing.cpp b/Source/Core/VideoBackends/Vulkan/PostProcessing.cpp
new file mode 100644
index 0000000000..db8da49499
--- /dev/null
+++ b/Source/Core/VideoBackends/Vulkan/PostProcessing.cpp
@@ -0,0 +1,319 @@
+// Copyright 2017 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#include "VideoBackends/Vulkan/PostProcessing.h"
+#include <sstream>
+
+#include "Common/Assert.h"
+#include "Common/StringUtil.h"
+
+#include "VideoBackends/Vulkan/CommandBufferManager.h"
+#include "VideoBackends/Vulkan/ObjectCache.h"
+#include "VideoBackends/Vulkan/Texture2D.h"
+#include "VideoBackends/Vulkan/Util.h"
+#include "VideoBackends/Vulkan/VulkanContext.h"
+
+#include "VideoCommon/VideoCommon.h"
+#include "VideoCommon/VideoConfig.h"
+
+namespace Vulkan
+{
+VulkanPostProcessing::~VulkanPostProcessing()
+{
+ if (m_default_fragment_shader != VK_NULL_HANDLE)
+ vkDestroyShaderModule(g_vulkan_context->GetDevice(), m_default_fragment_shader, nullptr);
+ if (m_fragment_shader != VK_NULL_HANDLE)
+ vkDestroyShaderModule(g_vulkan_context->GetDevice(), m_fragment_shader, nullptr);
+}
+
+bool VulkanPostProcessing::Initialize(const Texture2D* font_texture)
+{
+ m_font_texture = font_texture;
+ if (!CompileDefaultShader())
+ return false;
+
+ RecompileShader();
+ return true;
+}
+
+void VulkanPostProcessing::BlitFromTexture(const TargetRectangle& dst, const TargetRectangle& src,
+ const Texture2D* src_tex, int src_layer,
+ VkRenderPass render_pass)
+{
+ VkShaderModule fragment_shader =
+ m_fragment_shader != VK_NULL_HANDLE ? m_fragment_shader : m_default_fragment_shader;
+ UtilityShaderDraw draw(g_command_buffer_mgr->GetCurrentCommandBuffer(),
+ g_object_cache->GetPipelineLayout(PIPELINE_LAYOUT_STANDARD), render_pass,
+ g_object_cache->GetPassthroughVertexShader(), VK_NULL_HANDLE,
+ fragment_shader);
+
+ // Source is always bound.
+ draw.SetPSSampler(0, src_tex->GetView(), g_object_cache->GetLinearSampler());
+
+ // No need to allocate uniforms for the default shader.
+ // The config will also still contain the invalid shader at this point.
+ if (fragment_shader != m_default_fragment_shader)
+ {
+ size_t uniforms_size = CalculateUniformsSize();
+ u8* uniforms = draw.AllocatePSUniforms(uniforms_size);
+ FillUniformBuffer(uniforms, src, src_tex, src_layer);
+ draw.CommitPSUniforms(uniforms_size);
+ draw.SetPSSampler(1, m_font_texture->GetView(), g_object_cache->GetLinearSampler());
+ }
+
+ draw.DrawQuad(dst.left, dst.top, dst.GetWidth(), dst.GetHeight(), src.left, src.top, src_layer,
+ src.GetWidth(), src.GetHeight(), static_cast<int>(src_tex->GetWidth()),
+ static_cast<int>(src_tex->GetHeight()));
+}
+
+struct BuiltinUniforms
+{
+ float resolution[4];
+ float src_rect[4];
+ u32 time;
+ u32 unused[3];
+};
+
+size_t VulkanPostProcessing::CalculateUniformsSize() const
+{
+ // Allocate a vec4 for each uniform to simplify allocation.
+ return sizeof(BuiltinUniforms) + m_config.GetOptions().size() * sizeof(float) * 4;
+}
+
+void VulkanPostProcessing::FillUniformBuffer(u8* buf, const TargetRectangle& src,
+ const Texture2D* src_tex, int src_layer)
+{
+ float src_width_float = static_cast<float>(src_tex->GetWidth());
+ float src_height_float = static_cast<float>(src_tex->GetHeight());
+ BuiltinUniforms builtin_uniforms = {
+ {src_width_float, src_height_float, 1.0f / src_width_float, 1.0f / src_height_float},
+ {static_cast<float>(src.left) / src_width_float,
+ static_cast<float>(src.top) / src_height_float,
+ static_cast<float>(src.GetWidth()) / src_width_float,
+ static_cast<float>(src.GetHeight()) / src_height_float},
+ static_cast<u32>(m_timer.GetTimeElapsed())};
+
+ std::memcpy(buf, &builtin_uniforms, sizeof(builtin_uniforms));
+ buf += sizeof(builtin_uniforms);
+
+ for (const auto& it : m_config.GetOptions())
+ {
+ union
+ {
+ u32 as_bool[4];
+ s32 as_int[4];
+ float as_float[4];
+ } value = {};
+
+ switch (it.second.m_type)
+ {
+ case PostProcessingShaderConfiguration::ConfigurationOption::OptionType::OPTION_BOOL:
+ value.as_bool[0] = it.second.m_bool_value ? 1 : 0;
+ break;
+
+ case PostProcessingShaderConfiguration::ConfigurationOption::OptionType::OPTION_INTEGER:
+ _assert_(it.second.m_integer_values.size() < 4);
+ std::copy_n(it.second.m_integer_values.begin(), it.second.m_integer_values.size(),
+ value.as_int);
+ break;
+
+ case PostProcessingShaderConfiguration::ConfigurationOption::OptionType::OPTION_FLOAT:
+ _assert_(it.second.m_float_values.size() < 4);
+ std::copy_n(it.second.m_float_values.begin(), it.second.m_float_values.size(),
+ value.as_float);
+ break;
+ }
+
+ std::memcpy(buf, &value, sizeof(value));
+ buf += sizeof(value);
+ }
+}
+
+static const std::string DEFAULT_FRAGMENT_SHADER_SOURCE = R"(
+ layout(set = 1, binding = 0) uniform sampler2DArray samp0;
+
+ layout(location = 0) in float3 uv0;
+ layout(location = 1) in float4 col0;
+ layout(location = 0) out float4 ocol0;
+
+ void main()
+ {
+ ocol0 = float4(texture(samp0, uv0).xyz, 1.0);
+ }
+)";
+
+static const std::string POSTPROCESSING_SHADER_HEADER = R"(
+ SAMPLER_BINDING(0) uniform sampler2DArray samp0;
+ SAMPLER_BINDING(1) uniform sampler2D samp1;
+
+ layout(location = 0) in float3 uv0;
+ layout(location = 1) in float4 col0;
+ layout(location = 0) out float4 ocol0;
+
+ // Interfacing functions
+ // The EFB may have a zero alpha value, which we don't want to write to the frame dump, so set it to one here.
+ float4 Sample()
+ {
+ return float4(texture(samp0, uv0).xyz, 1.0);
+ }
+
+ float4 SampleLocation(float2 location)
+ {
+ return float4(texture(samp0, float3(location, uv0.z)).xyz, 1.0);
+ }
+
+ float4 SampleLayer(int layer)
+ {
+ return float4(texture(samp0, float3(uv0.xy, float(layer))).xyz, 1.0);
+ }
+
+ #define SampleOffset(offset) float4(textureOffset(samp0, uv0, offset).xyz, 1.0)
+
+ float4 SampleFontLocation(float2 location)
+ {
+ return texture(samp1, location);
+ }
+
+ float2 GetResolution()
+ {
+ return options.resolution.xy;
+ }
+
+ float2 GetInvResolution()
+ {
+ return options.resolution.zw;
+ }
+
+ float2 GetCoordinates()
+ {
+ return uv0.xy;
+ }
+
+ uint GetTime()
+ {
+ return options.time;
+ }
+
+ void SetOutput(float4 color)
+ {
+ ocol0 = color;
+ }
+
+ #define GetOption(x) (options.x)
+ #define OptionEnabled(x) (options.x != 0)
+
+ // Workaround because there is no getter function for src rect/layer.
+ float4 src_rect = options.src_rect;
+ int layer = int(uv0.z);
+)";
+
+void VulkanPostProcessing::UpdateConfig()
+{
+ if (m_config.GetShader() == g_ActiveConfig.sPostProcessingShader)
+ return;
+
+ RecompileShader();
+}
+
+bool VulkanPostProcessing::CompileDefaultShader()
+{
+ m_default_fragment_shader = Util::CompileAndCreateFragmentShader(DEFAULT_FRAGMENT_SHADER_SOURCE);
+ if (m_default_fragment_shader == VK_NULL_HANDLE)
+ {
+ PanicAlert("Failed to compile default post-processing shader.");
+ return false;
+ }
+
+ return true;
+}
+
+bool VulkanPostProcessing::RecompileShader()
+{
+ // As a driver can return the same new module pointer when destroying a shader and re-compiling,
+ // we need to wipe out the pipeline cache, otherwise we risk using old pipelines with old shaders.
+ // We can't just clear a single pipeline, because we don't know which render pass is going to be
+ // used here either.
+ if (m_fragment_shader != VK_NULL_HANDLE)
+ {
+ g_command_buffer_mgr->WaitForGPUIdle();
+ g_object_cache->ClearPipelineCache();
+ vkDestroyShaderModule(g_vulkan_context->GetDevice(), m_fragment_shader, nullptr);
+ m_fragment_shader = VK_NULL_HANDLE;
+ }
+
+ // If post-processing is disabled, just use the default shader.
+ // This way we don't need to allocate uniforms.
+ if (g_ActiveConfig.sPostProcessingShader.empty())
+ return true;
+
+ // Generate GLSL and compile the new shader.
+ std::string main_code = m_config.LoadShader();
+ std::string options_code = GetGLSLUniformBlock();
+ std::string code = options_code + POSTPROCESSING_SHADER_HEADER + main_code;
+ m_fragment_shader = Util::CompileAndCreateFragmentShader(code);
+ if (m_fragment_shader == VK_NULL_HANDLE)
+ {
+ // BlitFromTexture will use the default shader as a fallback.
+ PanicAlert("Failed to compile post-processing shader %s", m_config.GetShader().c_str());
+ return false;
+ }
+
+ return true;
+}
+
+std::string VulkanPostProcessing::GetGLSLUniformBlock() const
+{
+ std::stringstream ss;
+ u32 unused_counter = 1;
+ ss << "UBO_BINDING(std140, 1) uniform PSBlock {\n";
+
+ // Builtin uniforms
+ ss << " float4 resolution;\n";
+ ss << " float4 src_rect;\n";
+ ss << " uint time;\n";
+ for (u32 i = 0; i < 3; i++)
+ ss << " uint unused" << unused_counter++ << ";\n\n";
+
+ // Custom options/uniforms
+ for (const auto& it : m_config.GetOptions())
+ {
+ if (it.second.m_type ==
+ PostProcessingShaderConfiguration::ConfigurationOption::OptionType::OPTION_BOOL)
+ {
+ ss << StringFromFormat(" int %s;\n", it.first.c_str());
+ for (u32 i = 0; i < 3; i++)
+ ss << " int unused" << unused_counter++ << ";\n";
+ }
+ else if (it.second.m_type ==
+ PostProcessingShaderConfiguration::ConfigurationOption::OptionType::OPTION_INTEGER)
+ {
+ u32 count = static_cast<u32>(it.second.m_integer_values.size());
+ if (count == 1)
+ ss << StringFromFormat(" int %s;\n", it.first.c_str());
+ else
+ ss << StringFromFormat(" int%u %s;\n", count, it.first.c_str());
+
+ for (u32 i = count; i < 4; i++)
+ ss << " int unused" << unused_counter++ << ";\n";
+ }
+ else if (it.second.m_type ==
+ PostProcessingShaderConfiguration::ConfigurationOption::OptionType::OPTION_FLOAT)
+ {
+ u32 count = static_cast<u32>(it.second.m_float_values.size());
+ if (count == 1)
+ ss << StringFromFormat(" float %s;\n", it.first.c_str());
+ else
+ ss << StringFromFormat(" float%u %s;\n", count, it.first.c_str());
+
+ for (u32 i = count; i < 4; i++)
+ ss << " float unused" << unused_counter++ << ";\n";
+ }
+ }
+
+ ss << "} options;\n\n";
+
+ return ss.str();
+}
+
+} // namespace Vulkan
diff --git a/Source/Core/VideoBackends/Vulkan/PostProcessing.h b/Source/Core/VideoBackends/Vulkan/PostProcessing.h
new file mode 100644
index 0000000000..9c07c47772
--- /dev/null
+++ b/Source/Core/VideoBackends/Vulkan/PostProcessing.h
@@ -0,0 +1,45 @@
+// Copyright 2017 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <string>
+
+#include "VideoBackends/Vulkan/VulkanContext.h"
+
+#include "VideoCommon/PostProcessing.h"
+#include "VideoCommon/VideoCommon.h"
+
+namespace Vulkan
+{
+class Texture2D;
+
+class VulkanPostProcessing : public PostProcessingShaderImplementation
+{
+public:
+ VulkanPostProcessing() = default;
+ ~VulkanPostProcessing();
+
+ bool Initialize(const Texture2D* font_texture);
+
+ void BlitFromTexture(const TargetRectangle& dst, const TargetRectangle& src,
+ const Texture2D* src_tex, int src_layer, VkRenderPass render_pass);
+
+ void UpdateConfig();
+
+private:
+ size_t CalculateUniformsSize() const;
+ void FillUniformBuffer(u8* buf, const TargetRectangle& src, const Texture2D* src_tex,
+ int src_layer);
+
+ bool CompileDefaultShader();
+ bool RecompileShader();
+ std::string GetGLSLUniformBlock() const;
+
+ const Texture2D* m_font_texture = nullptr;
+ VkShaderModule m_fragment_shader = VK_NULL_HANDLE;
+ VkShaderModule m_default_fragment_shader = VK_NULL_HANDLE;
+};
+
+} // namespace
diff --git a/Source/Core/VideoBackends/Vulkan/RasterFont.cpp b/Source/Core/VideoBackends/Vulkan/RasterFont.cpp
index 689c0a8195..def32e5050 100644
--- a/Source/Core/VideoBackends/Vulkan/RasterFont.cpp
+++ b/Source/Core/VideoBackends/Vulkan/RasterFont.cpp
@@ -175,6 +175,11 @@ RasterFont::~RasterFont()
vkDestroyShaderModule(g_vulkan_context->GetDevice(), m_fragment_shader, nullptr);
}
+const Texture2D* RasterFont::GetTexture() const
+{
+ return m_texture.get();
+}
+
bool RasterFont::Initialize()
{
// Create shaders and texture
diff --git a/Source/Core/VideoBackends/Vulkan/RasterFont.h b/Source/Core/VideoBackends/Vulkan/RasterFont.h
index 5836da2f73..f12cf0f16c 100644
--- a/Source/Core/VideoBackends/Vulkan/RasterFont.h
+++ b/Source/Core/VideoBackends/Vulkan/RasterFont.h
@@ -21,6 +21,8 @@ public:
RasterFont();
~RasterFont();
+ const Texture2D* GetTexture() const;
+
bool Initialize();
void PrintMultiLineText(VkRenderPass render_pass, const std::string& text, float start_x,
diff --git a/Source/Core/VideoBackends/Vulkan/Renderer.cpp b/Source/Core/VideoBackends/Vulkan/Renderer.cpp
index 13acc6ac2d..94087bdc1f 100644
--- a/Source/Core/VideoBackends/Vulkan/Renderer.cpp
+++ b/Source/Core/VideoBackends/Vulkan/Renderer.cpp
@@ -21,6 +21,7 @@
#include "VideoBackends/Vulkan/CommandBufferManager.h"
#include "VideoBackends/Vulkan/FramebufferManager.h"
#include "VideoBackends/Vulkan/ObjectCache.h"
+#include "VideoBackends/Vulkan/PostProcessing.h"
#include "VideoBackends/Vulkan/RasterFont.h"
#include "VideoBackends/Vulkan/StagingTexture2D.h"
#include "VideoBackends/Vulkan/StateTracker.h"
@@ -117,6 +118,15 @@ bool Renderer::Initialize()
// Ensure all pipelines previously used by the game have been created.
StateTracker::GetInstance()->LoadPipelineUIDCache();
+ // Initialize post processing.
+ m_post_processor = std::make_unique<VulkanPostProcessing>();
+ if (!static_cast<VulkanPostProcessing*>(m_post_processor.get())
+ ->Initialize(m_raster_font->GetTexture()))
+ {
+ PanicAlert("failed to initialize post processor.");
+ return false;
+ }
+
// Various initialization routines will have executed commands on the command buffer.
// Execute what we have done before beginning the first frame.
g_command_buffer_mgr->PrepareToSubmitCommandBuffer();
@@ -618,7 +628,7 @@ void Renderer::DrawEFB(VkRenderPass render_pass, const TargetRectangle& target_r
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
// Copy EFB -> backbuffer
- BlitScreen(render_pass, target_rect, scaled_efb_rect, efb_color_texture, true);
+ BlitScreen(render_pass, target_rect, scaled_efb_rect, efb_color_texture);
// Restore the EFB color texture to color attachment ready for rendering the next frame.
if (efb_color_texture == FramebufferManager::GetInstance()->GetEFBColorTexture())
@@ -660,7 +670,7 @@ void Renderer::DrawVirtualXFB(VkRenderPass render_pass, const TargetRectangle& t
2;
source_rect.right -= Renderer::EFBToScaledX(fb_stride - fb_width);
- BlitScreen(render_pass, draw_rect, source_rect, xfb_source->GetTexture()->GetTexture(), true);
+ BlitScreen(render_pass, draw_rect, source_rect, xfb_source->GetTexture()->GetTexture());
}
}
@@ -677,7 +687,7 @@ void Renderer::DrawRealXFB(VkRenderPass render_pass, const TargetRectangle& targ
TargetRectangle source_rect = xfb_source->sourceRc;
TargetRectangle draw_rect = target_rect;
source_rect.right -= fb_stride - fb_width;
- BlitScreen(render_pass, draw_rect, source_rect, xfb_source->GetTexture()->GetTexture(), true);
+ BlitScreen(render_pass, draw_rect, source_rect, xfb_source->GetTexture()->GetTexture());
}
}
@@ -911,40 +921,21 @@ void Renderer::FlushFrameDump()
}
void Renderer::BlitScreen(VkRenderPass render_pass, const TargetRectangle& dst_rect,
- const TargetRectangle& src_rect, const Texture2D* src_tex,
- bool linear_filter)
+ const TargetRectangle& src_rect, const Texture2D* src_tex)
{
- // We could potentially use vkCmdBlitImage here.
- VkSampler sampler =
- linear_filter ? g_object_cache->GetLinearSampler() : g_object_cache->GetPointSampler();
-
- // Set up common data
- UtilityShaderDraw draw(g_command_buffer_mgr->GetCurrentCommandBuffer(),
- g_object_cache->GetPipelineLayout(PIPELINE_LAYOUT_STANDARD), render_pass,
- g_object_cache->GetPassthroughVertexShader(), VK_NULL_HANDLE,
- m_blit_fragment_shader);
-
- draw.SetPSSampler(0, src_tex->GetView(), sampler);
-
+ VulkanPostProcessing* post_processor = static_cast<VulkanPostProcessing*>(m_post_processor.get());
if (g_ActiveConfig.iStereoMode == STEREO_SBS || g_ActiveConfig.iStereoMode == STEREO_TAB)
{
TargetRectangle left_rect;
TargetRectangle right_rect;
std::tie(left_rect, right_rect) = ConvertStereoRectangle(dst_rect);
- draw.DrawQuad(left_rect.left, left_rect.top, left_rect.GetWidth(), left_rect.GetHeight(),
- src_rect.left, src_rect.top, 0, src_rect.GetWidth(), src_rect.GetHeight(),
- src_tex->GetWidth(), src_tex->GetHeight());
-
- draw.DrawQuad(right_rect.left, right_rect.top, right_rect.GetWidth(), right_rect.GetHeight(),
- src_rect.left, src_rect.top, 1, src_rect.GetWidth(), src_rect.GetHeight(),
- src_tex->GetWidth(), src_tex->GetHeight());
+ post_processor->BlitFromTexture(left_rect, src_rect, src_tex, 0, render_pass);
+ post_processor->BlitFromTexture(right_rect, src_rect, src_tex, 1, render_pass);
}
else
{
- draw.DrawQuad(dst_rect.left, dst_rect.top, dst_rect.GetWidth(), dst_rect.GetHeight(),
- src_rect.left, src_rect.top, 0, src_rect.GetWidth(), src_rect.GetHeight(),
- src_tex->GetWidth(), src_tex->GetHeight());
+ post_processor->BlitFromTexture(dst_rect, src_rect, src_tex, 0, render_pass);
}
}
@@ -1182,6 +1173,9 @@ void Renderer::CheckForConfigChanges()
// Wipe sampler cache if force texture filtering or anisotropy changes.
if (anisotropy_changed || force_texture_filtering_changed)
ResetSamplerStates();
+
+ // Check for a changed post-processing shader and recompile if needed.
+ static_cast<VulkanPostProcessing*>(m_post_processor.get())->UpdateConfig();
}
void Renderer::OnSwapChainResized()
@@ -1490,33 +1484,10 @@ bool Renderer::CompileShaders()
)";
- static const char BLIT_FRAGMENT_SHADER_SOURCE[] = R"(
- layout(set = 1, binding = 0) uniform sampler2DArray samp0;
-
- layout(location = 0) in float3 uv0;
- layout(location = 1) in float4 col0;
- layout(location = 0) out float4 ocol0;
-
- void main()
- {
- ocol0 = float4(texture(samp0, uv0).xyz, 1.0);
- }
- )";
-
- std::string header = g_object_cache->GetUtilityShaderHeader();
- std::string source;
-
- source = header + CLEAR_FRAGMENT_SHADER_SOURCE;
+ std::string source = g_object_cache->GetUtilityShaderHeader() + CLEAR_FRAGMENT_SHADER_SOURCE;
m_clear_fragment_shader = Util::CompileAndCreateFragmentShader(source);
- source = header + BLIT_FRAGMENT_SHADER_SOURCE;
- m_blit_fragment_shader = Util::CompileAndCreateFragmentShader(source);
-
- if (m_clear_fragment_shader == VK_NULL_HANDLE || m_blit_fragment_shader == VK_NULL_HANDLE)
- {
- return false;
- }
- return true;
+ return m_clear_fragment_shader != VK_NULL_HANDLE;
}
void Renderer::DestroyShaders()
@@ -1530,7 +1501,6 @@ void Renderer::DestroyShaders()
};
DestroyShader(m_clear_fragment_shader);
- DestroyShader(m_blit_fragment_shader);
}
} // namespace Vulkan
diff --git a/Source/Core/VideoBackends/Vulkan/Renderer.h b/Source/Core/VideoBackends/Vulkan/Renderer.h
index 5f86489c48..a6f9400560 100644
--- a/Source/Core/VideoBackends/Vulkan/Renderer.h
+++ b/Source/Core/VideoBackends/Vulkan/Renderer.h
@@ -136,7 +136,7 @@ private:
// Copies/scales an image to the currently-bound framebuffer.
void BlitScreen(VkRenderPass render_pass, const TargetRectangle& dst_rect,
- const TargetRectangle& src_rect, const Texture2D* src_tex, bool linear_filter);
+ const TargetRectangle& src_rect, const Texture2D* src_tex);
bool ResizeFrameDumpBuffer(u32 new_width, u32 new_height);
void DestroyFrameDumpResources();
@@ -154,11 +154,6 @@ private:
// Shaders used for clear/blit.
VkShaderModule m_clear_fragment_shader = VK_NULL_HANDLE;
- // NOTE: The blit shader here is used for the final copy from the source buffer(s) to the swap
- // chain buffer for presentation. It ignores the alpha channel of the input image and sets the
- // alpha channel to 1.0 to avoid issues with frame dumping and screenshots.
- VkShaderModule m_blit_fragment_shader = VK_NULL_HANDLE;
-
// Texture used for screenshot/frame dumping
std::unique_ptr<Texture2D> m_frame_dump_render_texture;
VkFramebuffer m_frame_dump_framebuffer = VK_NULL_HANDLE;
diff --git a/Source/Core/VideoBackends/Vulkan/Vulkan.vcxproj b/Source/Core/VideoBackends/Vulkan/Vulkan.vcxproj
index c7bdada31b..ed5d15d5ae 100644
--- a/Source/Core/VideoBackends/Vulkan/Vulkan.vcxproj
+++ b/Source/Core/VideoBackends/Vulkan/Vulkan.vcxproj
@@ -53,6 +53,7 @@
<ClCompile Include="CommandBufferManager.cpp" />
<ClCompile Include="FramebufferManager.cpp" />
<ClCompile Include="main.cpp" />
+ <ClCompile Include="PostProcessing.cpp" />
<ClCompile Include="TextureConverter.cpp" />
<ClCompile Include="PerfQuery.cpp" />
<ClCompile Include="RasterFont.cpp" />
@@ -77,6 +78,7 @@
<ClInclude Include="CommandBufferManager.h" />
<ClInclude Include="FramebufferManager.h" />
<ClInclude Include="Constants.h" />
+ <ClInclude Include="PostProcessing.h" />
<ClInclude Include="TextureConverter.h" />
<ClInclude Include="RasterFont.h" />
<ClInclude Include="StagingBuffer.h" />
diff --git a/Source/Core/VideoBackends/Vulkan/VulkanContext.cpp b/Source/Core/VideoBackends/Vulkan/VulkanContext.cpp
index b7ef583786..58b10dd92e 100644
--- a/Source/Core/VideoBackends/Vulkan/VulkanContext.cpp
+++ b/Source/Core/VideoBackends/Vulkan/VulkanContext.cpp
@@ -237,7 +237,7 @@ void VulkanContext::PopulateBackendInfo(VideoConfig* config)
config->backend_info.bSupportsComputeShaders = true; // Assumed support.
config->backend_info.bSupportsGPUTextureDecoding = true; // Assumed support.
config->backend_info.bSupportsInternalResolutionFrameDumps = true; // Assumed support.
- config->backend_info.bSupportsPostProcessing = false; // No support yet.
+ config->backend_info.bSupportsPostProcessing = true; // Assumed support.
config->backend_info.bSupportsDualSourceBlend = false; // Dependent on features.
config->backend_info.bSupportsGeometryShaders = false; // Dependent on features.
config->backend_info.bSupportsGSInstancing = false; // Dependent on features.
diff --git a/Source/Core/VideoCommon/PostProcessing.cpp b/Source/Core/VideoCommon/PostProcessing.cpp
index 023b6ef8bc..71f38732e7 100644
--- a/Source/Core/VideoCommon/PostProcessing.cpp
+++ b/Source/Core/VideoCommon/PostProcessing.cpp
@@ -7,6 +7,7 @@
#include "Common/CommonPaths.h"
#include "Common/CommonTypes.h"
+#include "Common/FileSearch.h"
#include "Common/FileUtil.h"
#include "Common/IniFile.h"
#include "Common/Logging/Log.h"
@@ -27,6 +28,40 @@ PostProcessingShaderImplementation::~PostProcessingShaderImplementation()
m_timer.Stop();
}
+static std::vector<std::string> GetShaders(const std::string& sub_dir = "")
+{
+ std::vector<std::string> paths =
+ Common::DoFileSearch({".glsl"}, {File::GetUserPath(D_SHADERS_IDX) + sub_dir,
+ File::GetSysDirectory() + SHADERS_DIR DIR_SEP + sub_dir});
+ std::vector<std::string> result;
+ for (std::string path : paths)
+ {
+ std::string name;
+ SplitPath(path, nullptr, &name, nullptr);
+ result.push_back(name);
+ }
+ return result;
+}
+
+std::vector<std::string> PostProcessingShaderImplementation::GetShaderList(APIType api_type)
+{
+ // Currently there is no differentiation between API types and shader languages.
+ // This could change in the future, hence the api_type parameter, but ideally,
+ // shaders should be compatible across backends.
+ if (api_type == APIType::OpenGL || api_type == APIType::Vulkan)
+ return GetShaders();
+
+ return {};
+}
+
+std::vector<std::string> PostProcessingShaderImplementation::GetAnaglyphShaderList(APIType api_type)
+{
+ if (api_type == APIType::OpenGL || api_type == APIType::Vulkan)
+ return GetShaders(ANAGLYPH_DIR DIR_SEP);
+
+ return {};
+}
+
std::string PostProcessingShaderConfiguration::LoadShader(std::string shader)
{
// Load the shader from the configuration if there isn't one sent to us.
diff --git a/Source/Core/VideoCommon/PostProcessing.h b/Source/Core/VideoCommon/PostProcessing.h
index 74628c3e54..4a51e66842 100644
--- a/Source/Core/VideoCommon/PostProcessing.h
+++ b/Source/Core/VideoCommon/PostProcessing.h
@@ -60,6 +60,7 @@ public:
bool IsDirty() { return m_any_options_dirty; }
void SetDirty(bool dirty) { m_any_options_dirty = dirty; }
bool HasOptions() { return m_options.size() > 0; }
+ const ConfigMap& GetOptions() const { return m_options; }
ConfigMap& GetOptions() { return m_options; }
const ConfigurationOption& GetOption(const std::string& option) { return m_options[option]; }
// For updating option's values
@@ -82,12 +83,10 @@ public:
PostProcessingShaderImplementation();
virtual ~PostProcessingShaderImplementation();
- PostProcessingShaderConfiguration* GetConfig() { return &m_config; }
- // Should be implemented by the backends for backend specific code
- virtual void BlitFromTexture(TargetRectangle src, TargetRectangle dst, int src_texture,
- int src_width, int src_height, int layer = 0) = 0;
- virtual void ApplyShader() = 0;
+ static std::vector<std::string> GetShaderList(APIType api_type);
+ static std::vector<std::string> GetAnaglyphShaderList(APIType api_type);
+ PostProcessingShaderConfiguration* GetConfig() { return &m_config; }
protected:
// Timer for determining our time value
Common::Timer m_timer;
diff --git a/Source/Core/VideoCommon/VideoConfig.h b/Source/Core/VideoCommon/VideoConfig.h
index c628722a3a..eacb30d8b1 100644
--- a/Source/Core/VideoCommon/VideoConfig.h
+++ b/Source/Core/VideoCommon/VideoConfig.h
@@ -170,8 +170,6 @@ struct VideoConfig final
std::vector<std::string> Adapters; // for D3D
std::vector<int> AAModes;
- std::vector<std::string> PPShaders; // post-processing shaders
- std::vector<std::string> AnaglyphShaders; // anaglyph shaders
// TODO: merge AdapterName and Adapters array
std::string AdapterName; // for OpenGL