Vulkan: Implement post-processing backend

No new features, just parity with OpenGL.
This commit is contained in:
Stenzek 2017-04-21 23:33:58 +10:00
parent a10e8b1ef5
commit 417a4ca206
11 changed files with 445 additions and 74 deletions

View File

@ -4,6 +4,7 @@ set(SRCS
FramebufferManager.cpp
ObjectCache.cpp
PerfQuery.cpp
PostProcessing.cpp
RasterFont.cpp
Renderer.cpp
ShaderCompiler.cpp

View File

@ -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;
}

View File

@ -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();

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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,

View File

@ -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

View File

@ -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;

View File

@ -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" />

View File

@ -6,6 +6,9 @@
#include "Common/Assert.h"
#include "Common/CommonFuncs.h"
#include "Common/CommonPaths.h"
#include "Common/FileSearch.h"
#include "Common/FileUtil.h"
#include "Common/Logging/Log.h"
#include "Common/MsgHandler.h"
#include "Common/StringUtil.h"
@ -222,6 +225,21 @@ VulkanContext::GPUList VulkanContext::EnumerateGPUs(VkInstance instance)
return gpus;
}
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 VulkanContext::PopulateBackendInfo(VideoConfig* config)
{
config->backend_info.api_type = APIType::Vulkan;
@ -237,7 +255,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.
@ -246,6 +264,9 @@ void VulkanContext::PopulateBackendInfo(VideoConfig* config)
config->backend_info.bSupportsSSAA = false; // Dependent on features.
config->backend_info.bSupportsDepthClamp = false; // Dependent on features.
config->backend_info.bSupportsReversedDepthRange = false; // No support yet due to driver bugs.
g_Config.backend_info.PPShaders = GetShaders("");
g_Config.backend_info.AnaglyphShaders = GetShaders(ANAGLYPH_DIR DIR_SEP);
}
void VulkanContext::PopulateBackendInfoAdapters(VideoConfig* config, const GPUList& gpu_list)