|
|
|
@ -33,7 +33,32 @@
|
|
|
|
|
|
|
|
|
|
namespace VideoCommon
|
|
|
|
|
{
|
|
|
|
|
static const char s_default_shader[] = "void main() { SetOutput(Sample()); }\n";
|
|
|
|
|
static const char s_empty_pixel_shader[] = "void main() { SetOutput(Sample()); }\n";
|
|
|
|
|
static const char s_default_pixel_shader_name[] = "default_pre_post_process";
|
|
|
|
|
// Keep the highest quality possible to avoid losing quality on subtle gamma conversions.
|
|
|
|
|
// RGBA16F should have enough quality even if we store colors in gamma space on it.
|
|
|
|
|
static const AbstractTextureFormat s_intermediary_buffer_format = AbstractTextureFormat::RGBA16F;
|
|
|
|
|
|
|
|
|
|
bool LoadShaderFromFile(const std::string& shader, const std::string& sub_dir,
|
|
|
|
|
std::string& out_code)
|
|
|
|
|
{
|
|
|
|
|
std::string path = File::GetUserPath(D_SHADERS_IDX) + sub_dir + shader + ".glsl";
|
|
|
|
|
|
|
|
|
|
if (!File::Exists(path))
|
|
|
|
|
{
|
|
|
|
|
// Fallback to shared user dir
|
|
|
|
|
path = File::GetSysDirectory() + SHADERS_DIR DIR_SEP + sub_dir + shader + ".glsl";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!File::ReadFileToString(path, out_code))
|
|
|
|
|
{
|
|
|
|
|
out_code = "";
|
|
|
|
|
ERROR_LOG_FMT(VIDEO, "Post-processing shader not found: {}", path);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
PostProcessingConfiguration::PostProcessingConfiguration() = default;
|
|
|
|
|
|
|
|
|
@ -60,24 +85,16 @@ void PostProcessingConfiguration::LoadShader(const std::string& shader)
|
|
|
|
|
sub_dir = PASSIVE_DIR DIR_SEP;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// loading shader code
|
|
|
|
|
std::string code;
|
|
|
|
|
std::string path = File::GetUserPath(D_SHADERS_IDX) + sub_dir + shader + ".glsl";
|
|
|
|
|
|
|
|
|
|
if (!File::Exists(path))
|
|
|
|
|
if (!LoadShaderFromFile(shader, sub_dir, code))
|
|
|
|
|
{
|
|
|
|
|
// Fallback to shared user dir
|
|
|
|
|
path = File::GetSysDirectory() + SHADERS_DIR DIR_SEP + sub_dir + shader + ".glsl";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!File::ReadFileToString(path, code))
|
|
|
|
|
{
|
|
|
|
|
ERROR_LOG_FMT(VIDEO, "Post-processing shader not found: {}", path);
|
|
|
|
|
LoadDefaultShader();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LoadOptions(code);
|
|
|
|
|
// Note that this will build the shaders with the custom options values users
|
|
|
|
|
// might have set in the settings
|
|
|
|
|
LoadOptionsConfiguration();
|
|
|
|
|
m_current_shader_code = code;
|
|
|
|
|
}
|
|
|
|
@ -86,7 +103,8 @@ void PostProcessingConfiguration::LoadDefaultShader()
|
|
|
|
|
{
|
|
|
|
|
m_options.clear();
|
|
|
|
|
m_any_options_dirty = false;
|
|
|
|
|
m_current_shader_code = s_default_shader;
|
|
|
|
|
m_current_shader = "";
|
|
|
|
|
m_current_shader_code = s_empty_pixel_shader;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void PostProcessingConfiguration::LoadOptions(const std::string& code)
|
|
|
|
@ -242,6 +260,7 @@ void PostProcessingConfiguration::LoadOptionsConfiguration()
|
|
|
|
|
ini.Load(File::GetUserPath(F_DOLPHINCONFIG_IDX));
|
|
|
|
|
std::string section = m_current_shader + "-options";
|
|
|
|
|
|
|
|
|
|
// We already expect all the options to be marked as "dirty" when we reach here
|
|
|
|
|
for (auto& it : m_options)
|
|
|
|
|
{
|
|
|
|
|
switch (it.second.m_type)
|
|
|
|
@ -375,6 +394,8 @@ static std::vector<std::string> GetShaders(const std::string& sub_dir = "")
|
|
|
|
|
{
|
|
|
|
|
std::string name;
|
|
|
|
|
SplitPath(path, nullptr, &name, nullptr);
|
|
|
|
|
if (name == s_default_pixel_shader_name)
|
|
|
|
|
continue;
|
|
|
|
|
result.push_back(name);
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
@ -409,8 +430,15 @@ bool PostProcessing::Initialize(AbstractTextureFormat format)
|
|
|
|
|
|
|
|
|
|
void PostProcessing::RecompileShader()
|
|
|
|
|
{
|
|
|
|
|
// Note: for simplicity we already recompile all the shaders
|
|
|
|
|
// and pipelines even if there might not be need to.
|
|
|
|
|
|
|
|
|
|
m_default_pipeline.reset();
|
|
|
|
|
m_pipeline.reset();
|
|
|
|
|
m_default_pixel_shader.reset();
|
|
|
|
|
m_pixel_shader.reset();
|
|
|
|
|
m_default_vertex_shader.reset();
|
|
|
|
|
m_vertex_shader.reset();
|
|
|
|
|
if (!CompilePixelShader())
|
|
|
|
|
return;
|
|
|
|
|
if (!CompileVertexShader())
|
|
|
|
@ -421,10 +449,27 @@ void PostProcessing::RecompileShader()
|
|
|
|
|
|
|
|
|
|
void PostProcessing::RecompilePipeline()
|
|
|
|
|
{
|
|
|
|
|
m_default_pipeline.reset();
|
|
|
|
|
m_pipeline.reset();
|
|
|
|
|
CompilePipeline();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool PostProcessing::IsColorCorrectionActive() const
|
|
|
|
|
{
|
|
|
|
|
// We can skip the color correction pass if none of these settings are on
|
|
|
|
|
// (it might have still helped with gamma correct sampling, but it's not worth running it).
|
|
|
|
|
return g_ActiveConfig.color_correction.bCorrectColorSpace ||
|
|
|
|
|
g_ActiveConfig.color_correction.bCorrectGamma ||
|
|
|
|
|
m_framebuffer_format == AbstractTextureFormat::RGBA16F;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool PostProcessing::NeedsIntermediaryBuffer() const
|
|
|
|
|
{
|
|
|
|
|
// If we have no user selected post process shader,
|
|
|
|
|
// there's no point in having an intermediary buffer doing nothing.
|
|
|
|
|
return !m_config.GetShader().empty();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void PostProcessing::BlitFromTexture(const MathUtil::Rectangle<int>& dst,
|
|
|
|
|
const MathUtil::Rectangle<int>& src,
|
|
|
|
|
const AbstractTexture* src_tex, int src_layer)
|
|
|
|
@ -435,69 +480,183 @@ void PostProcessing::BlitFromTexture(const MathUtil::Rectangle<int>& dst,
|
|
|
|
|
RecompilePipeline();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!m_pipeline)
|
|
|
|
|
return;
|
|
|
|
|
// By default all source layers will be copied into the respective target layers
|
|
|
|
|
const bool copy_all_layers = src_layer < 0;
|
|
|
|
|
src_layer = std::max(src_layer, 0);
|
|
|
|
|
|
|
|
|
|
FillUniformBuffer(src, src_tex, src_layer);
|
|
|
|
|
g_vertex_manager->UploadUtilityUniforms(m_uniform_staging_buffer.data(),
|
|
|
|
|
static_cast<u32>(m_uniform_staging_buffer.size()));
|
|
|
|
|
|
|
|
|
|
g_gfx->SetViewportAndScissor(
|
|
|
|
|
g_gfx->ConvertFramebufferRectangle(dst, g_gfx->GetCurrentFramebuffer()));
|
|
|
|
|
g_gfx->SetPipeline(m_pipeline.get());
|
|
|
|
|
g_gfx->SetTexture(0, src_tex);
|
|
|
|
|
MathUtil::Rectangle<int> src_rect = src;
|
|
|
|
|
g_gfx->SetSamplerState(0, RenderState::GetLinearSamplerState());
|
|
|
|
|
g_gfx->Draw(0, 3);
|
|
|
|
|
g_gfx->SetTexture(0, src_tex);
|
|
|
|
|
|
|
|
|
|
const bool is_color_correction_active = IsColorCorrectionActive();
|
|
|
|
|
const bool needs_intermediary_buffer = NeedsIntermediaryBuffer();
|
|
|
|
|
const AbstractPipeline* final_pipeline = m_pipeline.get();
|
|
|
|
|
std::vector<u8>* uniform_staging_buffer = &m_default_uniform_staging_buffer;
|
|
|
|
|
bool default_uniform_staging_buffer = true;
|
|
|
|
|
|
|
|
|
|
// Intermediary pass.
|
|
|
|
|
// We draw to a high quality intermediary texture for two reasons:
|
|
|
|
|
// -Keep quality for gamma and gamut conversions, and HDR output
|
|
|
|
|
// (low bit depths lose too much quality with gamma conversions)
|
|
|
|
|
// -We make a texture of the exact same res as the source one,
|
|
|
|
|
// because all the post process shaders we already had assume that
|
|
|
|
|
// the source texture size (EFB) is different from the swap chain
|
|
|
|
|
// texture size (which matches the window size).
|
|
|
|
|
if (m_default_pipeline && is_color_correction_active && needs_intermediary_buffer)
|
|
|
|
|
{
|
|
|
|
|
AbstractFramebuffer* const previous_framebuffer = g_gfx->GetCurrentFramebuffer();
|
|
|
|
|
|
|
|
|
|
// We keep the min number of layers as the render target,
|
|
|
|
|
// as in case of OpenGL, the source FBX will have two layers,
|
|
|
|
|
// but we will render onto two separate frame buffers (one by one),
|
|
|
|
|
// so it would be a waste to allocate two layers (see "bUsesExplictQuadBuffering").
|
|
|
|
|
const u32 target_layers = copy_all_layers ? src_tex->GetLayers() : 1;
|
|
|
|
|
|
|
|
|
|
if (!m_intermediary_frame_buffer || !m_intermediary_color_texture ||
|
|
|
|
|
m_intermediary_color_texture.get()->GetWidth() != static_cast<u32>(src_rect.GetWidth()) ||
|
|
|
|
|
m_intermediary_color_texture.get()->GetHeight() != static_cast<u32>(src_rect.GetHeight()) ||
|
|
|
|
|
m_intermediary_color_texture.get()->GetLayers() != target_layers)
|
|
|
|
|
{
|
|
|
|
|
const TextureConfig intermediary_color_texture_config(
|
|
|
|
|
src_rect.GetWidth(), src_rect.GetHeight(), 1, target_layers, src_tex->GetSamples(),
|
|
|
|
|
s_intermediary_buffer_format, AbstractTextureFlag_RenderTarget);
|
|
|
|
|
m_intermediary_color_texture = g_gfx->CreateTexture(intermediary_color_texture_config,
|
|
|
|
|
"Intermediary post process texture");
|
|
|
|
|
|
|
|
|
|
m_intermediary_frame_buffer =
|
|
|
|
|
g_gfx->CreateFramebuffer(m_intermediary_color_texture.get(), nullptr);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
g_gfx->SetFramebuffer(m_intermediary_frame_buffer.get());
|
|
|
|
|
|
|
|
|
|
FillUniformBuffer(src_rect, src_tex, src_layer, g_gfx->GetCurrentFramebuffer()->GetRect(),
|
|
|
|
|
g_presenter->GetTargetRectangle(), uniform_staging_buffer->data(),
|
|
|
|
|
!default_uniform_staging_buffer);
|
|
|
|
|
g_vertex_manager->UploadUtilityUniforms(uniform_staging_buffer->data(),
|
|
|
|
|
static_cast<u32>(uniform_staging_buffer->size()));
|
|
|
|
|
|
|
|
|
|
g_gfx->SetViewportAndScissor(g_gfx->ConvertFramebufferRectangle(
|
|
|
|
|
m_intermediary_color_texture->GetRect(), m_intermediary_frame_buffer.get()));
|
|
|
|
|
g_gfx->SetPipeline(m_default_pipeline.get());
|
|
|
|
|
g_gfx->Draw(0, 3);
|
|
|
|
|
|
|
|
|
|
g_gfx->SetFramebuffer(previous_framebuffer);
|
|
|
|
|
src_rect = m_intermediary_color_texture->GetRect();
|
|
|
|
|
src_tex = m_intermediary_color_texture.get();
|
|
|
|
|
g_gfx->SetTexture(0, src_tex);
|
|
|
|
|
// The "m_intermediary_color_texture" has already copied
|
|
|
|
|
// from the specified source layer onto its first one.
|
|
|
|
|
// If we query for a layer that the source texture doesn't have,
|
|
|
|
|
// it will fall back on the first one anyway.
|
|
|
|
|
src_layer = 0;
|
|
|
|
|
uniform_staging_buffer = &m_uniform_staging_buffer;
|
|
|
|
|
default_uniform_staging_buffer = false;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// If we have no custom user shader selected, and color correction
|
|
|
|
|
// is active, directly run the fixed pipeline shader instead of
|
|
|
|
|
// doing two passes, with the second one doing nothing useful.
|
|
|
|
|
if (m_default_pipeline && is_color_correction_active)
|
|
|
|
|
{
|
|
|
|
|
final_pipeline = m_default_pipeline.get();
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
uniform_staging_buffer = &m_uniform_staging_buffer;
|
|
|
|
|
default_uniform_staging_buffer = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_intermediary_frame_buffer.release();
|
|
|
|
|
m_intermediary_color_texture.release();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO: ideally we'd do the user selected post process pass in the intermediary buffer in linear
|
|
|
|
|
// space (instead of gamma space), so the shaders could act more accurately (and sample in linear
|
|
|
|
|
// space), though that would break the look of some of current post processes we have, and thus is
|
|
|
|
|
// better avoided for now.
|
|
|
|
|
|
|
|
|
|
// Final pass, either a user selected shader or the default (fixed) shader.
|
|
|
|
|
if (final_pipeline)
|
|
|
|
|
{
|
|
|
|
|
FillUniformBuffer(src_rect, src_tex, src_layer, g_gfx->GetCurrentFramebuffer()->GetRect(),
|
|
|
|
|
g_presenter->GetTargetRectangle(), uniform_staging_buffer->data(),
|
|
|
|
|
!default_uniform_staging_buffer);
|
|
|
|
|
g_vertex_manager->UploadUtilityUniforms(uniform_staging_buffer->data(),
|
|
|
|
|
static_cast<u32>(uniform_staging_buffer->size()));
|
|
|
|
|
|
|
|
|
|
g_gfx->SetViewportAndScissor(
|
|
|
|
|
g_gfx->ConvertFramebufferRectangle(dst, g_gfx->GetCurrentFramebuffer()));
|
|
|
|
|
g_gfx->SetPipeline(final_pipeline);
|
|
|
|
|
g_gfx->Draw(0, 3);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string PostProcessing::GetUniformBufferHeader() const
|
|
|
|
|
std::string PostProcessing::GetUniformBufferHeader(bool user_post_process) const
|
|
|
|
|
{
|
|
|
|
|
std::ostringstream ss;
|
|
|
|
|
u32 unused_counter = 1;
|
|
|
|
|
ss << "UBO_BINDING(std140, 1) uniform PSBlock {\n";
|
|
|
|
|
|
|
|
|
|
// Builtin uniforms
|
|
|
|
|
ss << " float4 resolution;\n";
|
|
|
|
|
// Builtin uniforms:
|
|
|
|
|
|
|
|
|
|
ss << " float4 resolution;\n"; // Source resolution
|
|
|
|
|
ss << " float4 target_resolution;\n";
|
|
|
|
|
ss << " float4 window_resolution;\n";
|
|
|
|
|
// How many horizontal and vertical stereo views do we have? (set to 1 when we use layers instead)
|
|
|
|
|
ss << " int2 stereo_views;\n";
|
|
|
|
|
ss << " float4 src_rect;\n";
|
|
|
|
|
// The first (but not necessarily only) source layer we target
|
|
|
|
|
ss << " int src_layer;\n";
|
|
|
|
|
ss << " uint time;\n";
|
|
|
|
|
for (u32 i = 0; i < 2; i++)
|
|
|
|
|
ss << " uint ubo_align_" << unused_counter++ << "_;\n";
|
|
|
|
|
ss << "\n";
|
|
|
|
|
|
|
|
|
|
// Custom options/uniforms
|
|
|
|
|
for (const auto& it : m_config.GetOptions())
|
|
|
|
|
ss << " int correct_color_space;\n";
|
|
|
|
|
ss << " int game_color_space;\n";
|
|
|
|
|
ss << " int correct_gamma;\n";
|
|
|
|
|
ss << " float game_gamma;\n";
|
|
|
|
|
ss << " int sdr_display_gamma_sRGB;\n";
|
|
|
|
|
ss << " float sdr_display_custom_gamma;\n";
|
|
|
|
|
ss << " int linear_space_output;\n";
|
|
|
|
|
ss << " int hdr_output;\n";
|
|
|
|
|
ss << " float hdr_paper_white_nits;\n";
|
|
|
|
|
ss << " float hdr_sdr_white_nits;\n";
|
|
|
|
|
|
|
|
|
|
if (user_post_process)
|
|
|
|
|
{
|
|
|
|
|
if (it.second.m_type == PostProcessingConfiguration::ConfigurationOption::OptionType::Bool)
|
|
|
|
|
ss << "\n";
|
|
|
|
|
// Custom options/uniforms
|
|
|
|
|
for (const auto& it : m_config.GetOptions())
|
|
|
|
|
{
|
|
|
|
|
ss << fmt::format(" int {};\n", it.first);
|
|
|
|
|
for (u32 i = 0; i < 3; i++)
|
|
|
|
|
ss << " int ubo_align_" << unused_counter++ << "_;\n";
|
|
|
|
|
}
|
|
|
|
|
else if (it.second.m_type ==
|
|
|
|
|
PostProcessingConfiguration::ConfigurationOption::OptionType::Integer)
|
|
|
|
|
{
|
|
|
|
|
u32 count = static_cast<u32>(it.second.m_integer_values.size());
|
|
|
|
|
if (count == 1)
|
|
|
|
|
if (it.second.m_type == PostProcessingConfiguration::ConfigurationOption::OptionType::Bool)
|
|
|
|
|
{
|
|
|
|
|
ss << fmt::format(" int {};\n", it.first);
|
|
|
|
|
else
|
|
|
|
|
ss << fmt::format(" int{} {};\n", count, it.first);
|
|
|
|
|
for (u32 i = 0; i < 3; i++)
|
|
|
|
|
ss << " int ubo_align_" << unused_counter++ << "_;\n";
|
|
|
|
|
}
|
|
|
|
|
else if (it.second.m_type ==
|
|
|
|
|
PostProcessingConfiguration::ConfigurationOption::OptionType::Integer)
|
|
|
|
|
{
|
|
|
|
|
u32 count = static_cast<u32>(it.second.m_integer_values.size());
|
|
|
|
|
if (count == 1)
|
|
|
|
|
ss << fmt::format(" int {};\n", it.first);
|
|
|
|
|
else
|
|
|
|
|
ss << fmt::format(" int{} {};\n", count, it.first);
|
|
|
|
|
|
|
|
|
|
for (u32 i = count; i < 4; i++)
|
|
|
|
|
ss << " int ubo_align_" << unused_counter++ << "_;\n";
|
|
|
|
|
}
|
|
|
|
|
else if (it.second.m_type ==
|
|
|
|
|
PostProcessingConfiguration::ConfigurationOption::OptionType::Float)
|
|
|
|
|
{
|
|
|
|
|
u32 count = static_cast<u32>(it.second.m_float_values.size());
|
|
|
|
|
if (count == 1)
|
|
|
|
|
ss << fmt::format(" float {};\n", it.first);
|
|
|
|
|
else
|
|
|
|
|
ss << fmt::format(" float{} {};\n", count, it.first);
|
|
|
|
|
for (u32 i = count; i < 4; i++)
|
|
|
|
|
ss << " int ubo_align_" << unused_counter++ << "_;\n";
|
|
|
|
|
}
|
|
|
|
|
else if (it.second.m_type ==
|
|
|
|
|
PostProcessingConfiguration::ConfigurationOption::OptionType::Float)
|
|
|
|
|
{
|
|
|
|
|
u32 count = static_cast<u32>(it.second.m_float_values.size());
|
|
|
|
|
if (count == 1)
|
|
|
|
|
ss << fmt::format(" float {};\n", it.first);
|
|
|
|
|
else
|
|
|
|
|
ss << fmt::format(" float{} {};\n", count, it.first);
|
|
|
|
|
|
|
|
|
|
for (u32 i = count; i < 4; i++)
|
|
|
|
|
ss << " float ubo_align_" << unused_counter++ << "_;\n";
|
|
|
|
|
for (u32 i = count; i < 4; i++)
|
|
|
|
|
ss << " float ubo_align_" << unused_counter++ << "_;\n";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -505,11 +664,12 @@ std::string PostProcessing::GetUniformBufferHeader() const
|
|
|
|
|
return ss.str();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string PostProcessing::GetHeader() const
|
|
|
|
|
std::string PostProcessing::GetHeader(bool user_post_process) const
|
|
|
|
|
{
|
|
|
|
|
std::ostringstream ss;
|
|
|
|
|
ss << GetUniformBufferHeader();
|
|
|
|
|
ss << GetUniformBufferHeader(user_post_process);
|
|
|
|
|
ss << "SAMPLER_BINDING(0) uniform sampler2DArray samp0;\n";
|
|
|
|
|
ss << "SAMPLER_BINDING(1) uniform sampler2DArray samp1;\n";
|
|
|
|
|
|
|
|
|
|
if (g_ActiveConfig.backend_info.bSupportsGeometryShaders)
|
|
|
|
|
{
|
|
|
|
@ -530,6 +690,16 @@ float4 SampleLocation(float2 location) { return texture(samp0, float3(location,
|
|
|
|
|
float4 SampleLayer(int layer) { return texture(samp0, float3(v_tex0.xy, float(layer))); }
|
|
|
|
|
#define SampleOffset(offset) textureOffset(samp0, v_tex0, offset)
|
|
|
|
|
|
|
|
|
|
float2 GetTargetResolution()
|
|
|
|
|
{
|
|
|
|
|
return target_resolution.xy;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float2 GetInvTargetResolution()
|
|
|
|
|
{
|
|
|
|
|
return target_resolution.zw;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float2 GetWindowResolution()
|
|
|
|
|
{
|
|
|
|
|
return window_resolution.xy;
|
|
|
|
@ -585,7 +755,9 @@ std::string PostProcessing::GetFooter() const
|
|
|
|
|
bool PostProcessing::CompileVertexShader()
|
|
|
|
|
{
|
|
|
|
|
std::ostringstream ss;
|
|
|
|
|
ss << GetUniformBufferHeader();
|
|
|
|
|
// We never need the user selected post process custom uniforms in the vertex shader
|
|
|
|
|
const bool user_post_process = false;
|
|
|
|
|
ss << GetUniformBufferHeader(user_post_process);
|
|
|
|
|
|
|
|
|
|
if (g_ActiveConfig.backend_info.bSupportsGeometryShaders)
|
|
|
|
|
{
|
|
|
|
@ -605,16 +777,28 @@ bool PostProcessing::CompileVertexShader()
|
|
|
|
|
ss << " opos = float4(v_tex0.xy * float2(2.0f, -2.0f) + float2(-1.0f, 1.0f), 0.0f, 1.0f);\n";
|
|
|
|
|
ss << " v_tex0 = float3(src_rect.xy + (src_rect.zw * v_tex0.xy), float(src_layer));\n";
|
|
|
|
|
|
|
|
|
|
// Vulkan Y needs to be inverted on every pass
|
|
|
|
|
if (g_ActiveConfig.backend_info.api_type == APIType::Vulkan)
|
|
|
|
|
ss << " opos.y = -opos.y;\n";
|
|
|
|
|
|
|
|
|
|
std::string s2 = ss.str();
|
|
|
|
|
s2 += "}\n";
|
|
|
|
|
m_default_vertex_shader = g_gfx->CreateShaderFromSource(ShaderStage::Vertex, s2,
|
|
|
|
|
"Default post-processing vertex shader");
|
|
|
|
|
|
|
|
|
|
// OpenGL Y needs to be inverted once only (in the last pass)
|
|
|
|
|
if (g_ActiveConfig.backend_info.api_type == APIType::OpenGL)
|
|
|
|
|
ss << " opos.y = -opos.y;\n";
|
|
|
|
|
|
|
|
|
|
ss << "}\n";
|
|
|
|
|
|
|
|
|
|
m_vertex_shader =
|
|
|
|
|
g_gfx->CreateShaderFromSource(ShaderStage::Vertex, ss.str(), "Post-processing vertex shader");
|
|
|
|
|
if (!m_vertex_shader)
|
|
|
|
|
if (!m_default_vertex_shader || !m_vertex_shader)
|
|
|
|
|
{
|
|
|
|
|
PanicAlertFmt("Failed to compile post-processing vertex shader");
|
|
|
|
|
m_default_vertex_shader.reset();
|
|
|
|
|
m_vertex_shader.reset();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -623,44 +807,86 @@ bool PostProcessing::CompileVertexShader()
|
|
|
|
|
|
|
|
|
|
struct BuiltinUniforms
|
|
|
|
|
{
|
|
|
|
|
float resolution[4];
|
|
|
|
|
float window_resolution[4];
|
|
|
|
|
float src_rect[4];
|
|
|
|
|
// bools need to be represented as "s32"
|
|
|
|
|
|
|
|
|
|
std::array<float, 4> source_resolution;
|
|
|
|
|
std::array<float, 4> target_resolution;
|
|
|
|
|
std::array<float, 4> window_resolution;
|
|
|
|
|
std::array<float, 4> stereo_views;
|
|
|
|
|
std::array<float, 4> src_rect;
|
|
|
|
|
s32 src_layer;
|
|
|
|
|
u32 time;
|
|
|
|
|
u32 padding[2];
|
|
|
|
|
s32 correct_color_space;
|
|
|
|
|
s32 game_color_space;
|
|
|
|
|
s32 correct_gamma;
|
|
|
|
|
float game_gamma;
|
|
|
|
|
s32 sdr_display_gamma_sRGB;
|
|
|
|
|
float sdr_display_custom_gamma;
|
|
|
|
|
s32 linear_space_output;
|
|
|
|
|
s32 hdr_output;
|
|
|
|
|
float hdr_paper_white_nits;
|
|
|
|
|
float hdr_sdr_white_nits;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
size_t PostProcessing::CalculateUniformsSize() const
|
|
|
|
|
size_t PostProcessing::CalculateUniformsSize(bool user_post_process) const
|
|
|
|
|
{
|
|
|
|
|
// Allocate a vec4 for each uniform to simplify allocation.
|
|
|
|
|
return sizeof(BuiltinUniforms) + m_config.GetOptions().size() * sizeof(float) * 4;
|
|
|
|
|
return sizeof(BuiltinUniforms) +
|
|
|
|
|
(user_post_process ? m_config.GetOptions().size() : 0) * sizeof(float) * 4;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void PostProcessing::FillUniformBuffer(const MathUtil::Rectangle<int>& src,
|
|
|
|
|
const AbstractTexture* src_tex, int src_layer)
|
|
|
|
|
const AbstractTexture* src_tex, int src_layer,
|
|
|
|
|
const MathUtil::Rectangle<int>& dst,
|
|
|
|
|
const MathUtil::Rectangle<int>& wnd, u8* buffer,
|
|
|
|
|
bool user_post_process)
|
|
|
|
|
{
|
|
|
|
|
const auto& window_rect = g_presenter->GetTargetRectangle();
|
|
|
|
|
const float rcp_src_width = 1.0f / src_tex->GetWidth();
|
|
|
|
|
const float rcp_src_height = 1.0f / src_tex->GetHeight();
|
|
|
|
|
BuiltinUniforms builtin_uniforms = {
|
|
|
|
|
{static_cast<float>(src_tex->GetWidth()), static_cast<float>(src_tex->GetHeight()),
|
|
|
|
|
rcp_src_width, rcp_src_height},
|
|
|
|
|
{static_cast<float>(window_rect.GetWidth()), static_cast<float>(window_rect.GetHeight()),
|
|
|
|
|
1.0f / static_cast<float>(window_rect.GetWidth()),
|
|
|
|
|
1.0f / static_cast<float>(window_rect.GetHeight())},
|
|
|
|
|
{static_cast<float>(src.left) * rcp_src_width, static_cast<float>(src.top) * rcp_src_height,
|
|
|
|
|
static_cast<float>(src.GetWidth()) * rcp_src_width,
|
|
|
|
|
static_cast<float>(src.GetHeight()) * rcp_src_height},
|
|
|
|
|
static_cast<s32>(src_layer),
|
|
|
|
|
static_cast<u32>(m_timer.ElapsedMs()),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
u8* buf = m_uniform_staging_buffer.data();
|
|
|
|
|
std::memcpy(buf, &builtin_uniforms, sizeof(builtin_uniforms));
|
|
|
|
|
buf += sizeof(builtin_uniforms);
|
|
|
|
|
BuiltinUniforms builtin_uniforms;
|
|
|
|
|
builtin_uniforms.source_resolution = {static_cast<float>(src_tex->GetWidth()),
|
|
|
|
|
static_cast<float>(src_tex->GetHeight()), rcp_src_width,
|
|
|
|
|
rcp_src_height};
|
|
|
|
|
builtin_uniforms.target_resolution = {
|
|
|
|
|
static_cast<float>(dst.GetWidth()), static_cast<float>(dst.GetHeight()),
|
|
|
|
|
1.0f / static_cast<float>(dst.GetWidth()), 1.0f / static_cast<float>(dst.GetHeight())};
|
|
|
|
|
builtin_uniforms.window_resolution = {
|
|
|
|
|
static_cast<float>(wnd.GetWidth()), static_cast<float>(wnd.GetHeight()),
|
|
|
|
|
1.0f / static_cast<float>(wnd.GetWidth()), 1.0f / static_cast<float>(wnd.GetHeight())};
|
|
|
|
|
builtin_uniforms.src_rect = {static_cast<float>(src.left) * rcp_src_width,
|
|
|
|
|
static_cast<float>(src.top) * rcp_src_height,
|
|
|
|
|
static_cast<float>(src.GetWidth()) * rcp_src_width,
|
|
|
|
|
static_cast<float>(src.GetHeight()) * rcp_src_height};
|
|
|
|
|
builtin_uniforms.src_layer = static_cast<s32>(src_layer);
|
|
|
|
|
builtin_uniforms.time = static_cast<u32>(m_timer.ElapsedMs());
|
|
|
|
|
|
|
|
|
|
for (const auto& it : m_config.GetOptions())
|
|
|
|
|
// Color correction related uniforms.
|
|
|
|
|
// These are mainly used by the "m_default_pixel_shader",
|
|
|
|
|
// but should also be accessible to all other shaders.
|
|
|
|
|
builtin_uniforms.correct_color_space = g_ActiveConfig.color_correction.bCorrectColorSpace;
|
|
|
|
|
builtin_uniforms.game_color_space =
|
|
|
|
|
static_cast<int>(g_ActiveConfig.color_correction.game_color_space);
|
|
|
|
|
builtin_uniforms.correct_gamma = g_ActiveConfig.color_correction.bCorrectGamma;
|
|
|
|
|
builtin_uniforms.game_gamma = g_ActiveConfig.color_correction.fGameGamma;
|
|
|
|
|
builtin_uniforms.sdr_display_gamma_sRGB = g_ActiveConfig.color_correction.bSDRDisplayGammaSRGB;
|
|
|
|
|
builtin_uniforms.sdr_display_custom_gamma =
|
|
|
|
|
g_ActiveConfig.color_correction.fSDRDisplayCustomGamma;
|
|
|
|
|
// scRGB (RGBA16F) expects linear values as opposed to sRGB gamma
|
|
|
|
|
builtin_uniforms.linear_space_output = m_framebuffer_format == AbstractTextureFormat::RGBA16F;
|
|
|
|
|
// Implies ouput values can be beyond the 0-1 range
|
|
|
|
|
builtin_uniforms.hdr_output = m_framebuffer_format == AbstractTextureFormat::RGBA16F;
|
|
|
|
|
builtin_uniforms.hdr_paper_white_nits = g_ActiveConfig.color_correction.fHDRPaperWhiteNits;
|
|
|
|
|
// A value of 1 1 1 usually matches 80 nits in HDR
|
|
|
|
|
builtin_uniforms.hdr_sdr_white_nits = 80.f;
|
|
|
|
|
|
|
|
|
|
std::memcpy(buffer, &builtin_uniforms, sizeof(builtin_uniforms));
|
|
|
|
|
buffer += sizeof(builtin_uniforms);
|
|
|
|
|
|
|
|
|
|
if (!user_post_process)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
for (auto& it : m_config.GetOptions())
|
|
|
|
|
{
|
|
|
|
|
union
|
|
|
|
|
{
|
|
|
|
@ -688,53 +914,116 @@ void PostProcessing::FillUniformBuffer(const MathUtil::Rectangle<int>& src,
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::memcpy(buf, &value, sizeof(value));
|
|
|
|
|
buf += sizeof(value);
|
|
|
|
|
it.second.m_dirty = false;
|
|
|
|
|
|
|
|
|
|
std::memcpy(buffer, &value, sizeof(value));
|
|
|
|
|
buffer += sizeof(value);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_config.SetDirty(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool PostProcessing::CompilePixelShader()
|
|
|
|
|
{
|
|
|
|
|
m_pipeline.reset();
|
|
|
|
|
m_default_pixel_shader.reset();
|
|
|
|
|
m_pixel_shader.reset();
|
|
|
|
|
|
|
|
|
|
// Generate GLSL and compile the new shader.
|
|
|
|
|
// Generate GLSL and compile the new shaders:
|
|
|
|
|
|
|
|
|
|
std::string default_pixel_shader_code;
|
|
|
|
|
if (LoadShaderFromFile(s_default_pixel_shader_name, "", default_pixel_shader_code))
|
|
|
|
|
{
|
|
|
|
|
m_default_pixel_shader = g_gfx->CreateShaderFromSource(
|
|
|
|
|
ShaderStage::Pixel, GetHeader(false) + default_pixel_shader_code + GetFooter(),
|
|
|
|
|
"Default post-processing pixel shader");
|
|
|
|
|
// We continue even if all of this failed, it doesn't matter
|
|
|
|
|
m_default_uniform_staging_buffer.resize(CalculateUniformsSize(false));
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
m_default_uniform_staging_buffer.resize(0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_config.LoadShader(g_ActiveConfig.sPostProcessingShader);
|
|
|
|
|
m_pixel_shader = g_gfx->CreateShaderFromSource(
|
|
|
|
|
ShaderStage::Pixel, GetHeader() + m_config.GetShaderCode() + GetFooter(),
|
|
|
|
|
fmt::format("Post-processing pixel shader: {}", m_config.GetShader()));
|
|
|
|
|
ShaderStage::Pixel, GetHeader(true) + m_config.GetShaderCode() + GetFooter(),
|
|
|
|
|
fmt::format("User post-processing pixel shader: {}", m_config.GetShader()));
|
|
|
|
|
if (!m_pixel_shader)
|
|
|
|
|
{
|
|
|
|
|
PanicAlertFmt("Failed to compile post-processing shader {}", m_config.GetShader());
|
|
|
|
|
PanicAlertFmt("Failed to compile user post-processing shader {}", m_config.GetShader());
|
|
|
|
|
|
|
|
|
|
// Use default shader.
|
|
|
|
|
m_config.LoadDefaultShader();
|
|
|
|
|
m_pixel_shader = g_gfx->CreateShaderFromSource(
|
|
|
|
|
ShaderStage::Pixel, GetHeader() + m_config.GetShaderCode() + GetFooter(),
|
|
|
|
|
"Default post-processing pixel shader");
|
|
|
|
|
ShaderStage::Pixel, GetHeader(true) + m_config.GetShaderCode() + GetFooter(),
|
|
|
|
|
"Default user post-processing pixel shader");
|
|
|
|
|
if (!m_pixel_shader)
|
|
|
|
|
{
|
|
|
|
|
m_uniform_staging_buffer.resize(0);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_uniform_staging_buffer.resize(CalculateUniformsSize());
|
|
|
|
|
m_uniform_staging_buffer.resize(CalculateUniformsSize(true));
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool UseGeometryShaderForPostProcess(bool is_intermediary_buffer)
|
|
|
|
|
{
|
|
|
|
|
// We only return true on stereo modes that need to copy
|
|
|
|
|
// both source texture layers into the target texture layers.
|
|
|
|
|
// Any other case is handled manually with multiple copies, thus
|
|
|
|
|
// it doesn't need a geom shader.
|
|
|
|
|
switch (g_ActiveConfig.stereo_mode)
|
|
|
|
|
{
|
|
|
|
|
case StereoMode::QuadBuffer:
|
|
|
|
|
return !g_ActiveConfig.backend_info.bUsesExplictQuadBuffering;
|
|
|
|
|
case StereoMode::Anaglyph:
|
|
|
|
|
case StereoMode::Passive:
|
|
|
|
|
return is_intermediary_buffer;
|
|
|
|
|
case StereoMode::SBS:
|
|
|
|
|
case StereoMode::TAB:
|
|
|
|
|
case StereoMode::Off:
|
|
|
|
|
default:
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool PostProcessing::CompilePipeline()
|
|
|
|
|
{
|
|
|
|
|
// Not needed. Some backends don't like making pipelines with no targets,
|
|
|
|
|
// and in any case, we don't need to render anything if that happened.
|
|
|
|
|
if (m_framebuffer_format == AbstractTextureFormat::Undefined)
|
|
|
|
|
return true; // Not needed (some backends don't like making pipelines with no targets)
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
// If this is true, the "m_default_pipeline" won't be the only one that runs
|
|
|
|
|
const bool needs_intermediary_buffer = NeedsIntermediaryBuffer();
|
|
|
|
|
|
|
|
|
|
AbstractPipelineConfig config = {};
|
|
|
|
|
config.vertex_shader = m_vertex_shader.get();
|
|
|
|
|
config.geometry_shader =
|
|
|
|
|
g_gfx->UseGeometryShaderForUI() ? g_shader_cache->GetTexcoordGeometryShader() : nullptr;
|
|
|
|
|
config.pixel_shader = m_pixel_shader.get();
|
|
|
|
|
config.vertex_shader =
|
|
|
|
|
needs_intermediary_buffer ? m_vertex_shader.get() : m_default_vertex_shader.get();
|
|
|
|
|
// This geometry shader will take care of reading both layer 0 and 1 on the source texture,
|
|
|
|
|
// and writing to both layer 0 and 1 on the render target.
|
|
|
|
|
config.geometry_shader = UseGeometryShaderForPostProcess(needs_intermediary_buffer) ?
|
|
|
|
|
g_shader_cache->GetTexcoordGeometryShader() :
|
|
|
|
|
nullptr;
|
|
|
|
|
config.pixel_shader = m_default_pixel_shader.get();
|
|
|
|
|
config.rasterization_state = RenderState::GetNoCullRasterizationState(PrimitiveType::Triangles);
|
|
|
|
|
config.depth_state = RenderState::GetNoDepthTestingDepthState();
|
|
|
|
|
config.blending_state = RenderState::GetNoBlendingBlendState();
|
|
|
|
|
config.framebuffer_state = RenderState::GetColorFramebufferState(m_framebuffer_format);
|
|
|
|
|
config.framebuffer_state = RenderState::GetColorFramebufferState(
|
|
|
|
|
needs_intermediary_buffer ? s_intermediary_buffer_format : m_framebuffer_format);
|
|
|
|
|
config.usage = AbstractPipelineUsage::Utility;
|
|
|
|
|
// We continue even if it failed, it will be skipped later on
|
|
|
|
|
if (config.pixel_shader)
|
|
|
|
|
m_default_pipeline = g_gfx->CreatePipeline(config);
|
|
|
|
|
|
|
|
|
|
config.vertex_shader = m_default_vertex_shader.get();
|
|
|
|
|
config.geometry_shader = UseGeometryShaderForPostProcess(false) ?
|
|
|
|
|
g_shader_cache->GetTexcoordGeometryShader() :
|
|
|
|
|
nullptr;
|
|
|
|
|
config.pixel_shader = m_pixel_shader.get();
|
|
|
|
|
config.framebuffer_state = RenderState::GetColorFramebufferState(m_framebuffer_format);
|
|
|
|
|
m_pipeline = g_gfx->CreatePipeline(config);
|
|
|
|
|
if (!m_pipeline)
|
|
|
|
|
return false;
|
|
|
|
|