From 8131bd32e31b747b2a518504e7ea424511874692 Mon Sep 17 00:00:00 2001 From: xperia64 Date: Fri, 9 Aug 2019 14:00:47 -0400 Subject: [PATCH] renderer_opengl: Add support for custom shaders (#4578) * Add Anaglyph 3D Change 3D slider in-game Change shaders while game is running Move shader loading into function Disable 3D slider setting when stereoscopy is off The rest of the shaders Address review issues Documentation and minor fixups Forgot clang-format Fix shader release on SDL2-software rendering Remove unnecessary state changes Respect 3D factor setting regardless of stereoscopic rendering Improve shader resolution passing Minor setting-related improvements Add option to toggle texture filtering Rebase fixes * One final clang-format * Fix OpenGL problems --- src/citra/config.cpp | 8 +- src/citra/default_ini.h | 16 +- src/citra_qt/configuration/config.cpp | 18 +- .../configuration/configure_graphics.cpp | 36 +++- .../configuration/configure_graphics.h | 3 + .../configuration/configure_graphics.ui | 72 ++++++- src/common/common_paths.h | 1 + src/common/file_util.cpp | 1 + src/common/file_util.h | 1 + src/core/frontend/emu_window.cpp | 9 +- src/core/hle/kernel/shared_page.cpp | 5 +- src/core/hle/kernel/shared_page.h | 4 +- src/core/settings.cpp | 9 +- src/core/settings.h | 7 +- src/core/telemetry_session.cpp | 4 +- src/video_core/CMakeLists.txt | 2 + .../post_processing_opengl.cpp | 198 +++++++++++++++++ .../renderer_opengl/post_processing_opengl.h | 23 ++ .../renderer_opengl/renderer_opengl.cpp | 202 ++++++++++++++++-- .../renderer_opengl/renderer_opengl.h | 12 ++ src/video_core/video_core.cpp | 2 + src/video_core/video_core.h | 2 + 22 files changed, 587 insertions(+), 48 deletions(-) create mode 100644 src/video_core/renderer_opengl/post_processing_opengl.cpp create mode 100644 src/video_core/renderer_opengl/post_processing_opengl.h diff --git a/src/citra/config.cpp b/src/citra/config.cpp index 1ed5be004..20e0bddbf 100644 --- a/src/citra/config.cpp +++ b/src/citra/config.cpp @@ -127,9 +127,15 @@ void Config::ReadValues() { Settings::values.frame_limit = static_cast(sdl2_config->GetInteger("Renderer", "frame_limit", 100)); - Settings::values.toggle_3d = sdl2_config->GetBoolean("Renderer", "toggle_3d", false); + Settings::values.render_3d = static_cast( + sdl2_config->GetInteger("Renderer", "render_3d", 0)); Settings::values.factor_3d = static_cast(sdl2_config->GetInteger("Renderer", "factor_3d", 0)); + Settings::values.pp_shader_name = sdl2_config->GetString( + "Renderer", "pp_shader_name", + (Settings::values.render_3d == Settings::StereoRenderOption::Anaglyph) ? "dubois (builtin)" + : "none (builtin)"); + Settings::values.filter_mode = sdl2_config->GetBoolean("Renderer", "filter_mode", true); Settings::values.bg_red = static_cast(sdl2_config->GetReal("Renderer", "bg_red", 0.0)); Settings::values.bg_green = diff --git a/src/citra/default_ini.h b/src/citra/default_ini.h index 37c12a374..bd8681c8b 100644 --- a/src/citra/default_ini.h +++ b/src/citra/default_ini.h @@ -139,14 +139,24 @@ bg_red = bg_blue = bg_green = -# Toggles Stereoscopic 3D -# 0 (default): Off, 1: On -toggle_3d = +# Whether and how Stereoscopic 3D should be rendered +# 0 (default): Off, 1: Side by Side, 2: Anaglyph +render_3d = # Change 3D Intensity # 0 - 100: Intensity. 0 (default) factor_3d = +# The name of the post processing shader to apply. +# Loaded from shaders if render_3d is off or side by side. +# Loaded from shaders/anaglyph if render_3d is anaglyph +pp_shader_name = + +# Whether to enable linear filtering or not +# This is required for some shaders to work correctly +# 0: Nearest, 1 (default): Linear +filter_mode = + [Layout] # Layout for the screen inside the render window. # 0 (default): Default Top Bottom Screen, 1: Single Screen Only, 2: Large Screen Small Screen, 3: Side by Side diff --git a/src/citra_qt/configuration/config.cpp b/src/citra_qt/configuration/config.cpp index 168fab028..f4d2f5a72 100644 --- a/src/citra_qt/configuration/config.cpp +++ b/src/citra_qt/configuration/config.cpp @@ -175,8 +175,17 @@ void Config::ReadValues() { qt_config->endGroup(); qt_config->beginGroup("Layout"); - Settings::values.toggle_3d = ReadSetting("toggle_3d", false).toBool(); + Settings::values.render_3d = + static_cast(ReadSetting("render_3d", 0).toInt()); Settings::values.factor_3d = ReadSetting("factor_3d", 0).toInt(); + Settings::values.pp_shader_name = + ReadSetting("pp_shader_name", + (Settings::values.render_3d == Settings::StereoRenderOption::Anaglyph) + ? "dubois (builtin)" + : "none (builtin)") + .toString() + .toStdString(); + Settings::values.filter_mode = ReadSetting("filter_mode", true).toBool(); Settings::values.layout_option = static_cast(ReadSetting("layout_option").toInt()); Settings::values.swap_screen = ReadSetting("swap_screen", false).toBool(); @@ -465,8 +474,13 @@ void Config::SaveValues() { qt_config->endGroup(); qt_config->beginGroup("Layout"); - WriteSetting("toggle_3d", Settings::values.toggle_3d, false); + WriteSetting("render_3d", static_cast(Settings::values.render_3d), 0); WriteSetting("factor_3d", Settings::values.factor_3d.load(), 0); + WriteSetting("pp_shader_name", QString::fromStdString(Settings::values.pp_shader_name), + (Settings::values.render_3d == Settings::StereoRenderOption::Anaglyph) + ? "dubois (builtin)" + : "none (builtin)"); + WriteSetting("filter_mode", Settings::values.filter_mode, true); WriteSetting("layout_option", static_cast(Settings::values.layout_option)); WriteSetting("swap_screen", Settings::values.swap_screen, false); WriteSetting("custom_layout", Settings::values.custom_layout, false); diff --git a/src/citra_qt/configuration/configure_graphics.cpp b/src/citra_qt/configuration/configure_graphics.cpp index 0a1a0163d..d6ef158b0 100644 --- a/src/citra_qt/configuration/configure_graphics.cpp +++ b/src/citra_qt/configuration/configure_graphics.cpp @@ -10,6 +10,7 @@ #include "core/core.h" #include "core/settings.h" #include "ui_configure_graphics.h" +#include "video_core/renderer_opengl/post_processing_opengl.h" ConfigureGraphics::ConfigureGraphics(QWidget* parent) : QWidget(parent), ui(new Ui::ConfigureGraphics) { @@ -37,6 +38,14 @@ ConfigureGraphics::ConfigureGraphics(QWidget* parent) } }); #endif + + connect(ui->render_3d_combobox, + static_cast(&QComboBox::currentIndexChanged), this, + [this](int currentIndex) { + updateShaders(static_cast(currentIndex) == + Settings::StereoRenderOption::Anaglyph); + }); + connect(ui->bg_button, &QPushButton::clicked, this, [this] { const QColor new_bg_color = QColorDialog::getColor(bg_color); if (!new_bg_color.isValid()) { @@ -62,8 +71,10 @@ void ConfigureGraphics::SetConfiguration() { ui->toggle_frame_limit->setChecked(Settings::values.use_frame_limit); ui->frame_limit->setEnabled(ui->toggle_frame_limit->isChecked()); ui->frame_limit->setValue(Settings::values.frame_limit); + ui->render_3d_combobox->setCurrentIndex(static_cast(Settings::values.render_3d)); ui->factor_3d->setValue(Settings::values.factor_3d); - ui->toggle_3d->setChecked(Settings::values.toggle_3d); + updateShaders(Settings::values.render_3d == Settings::StereoRenderOption::Anaglyph); + ui->toggle_linear_filter->setChecked(Settings::values.filter_mode); ui->layout_combobox->setCurrentIndex(static_cast(Settings::values.layout_option)); ui->swap_screen->setChecked(Settings::values.swap_screen); bg_color = QColor::fromRgbF(Settings::values.bg_red, Settings::values.bg_green, @@ -84,8 +95,12 @@ void ConfigureGraphics::ApplyConfiguration() { static_cast(ui->resolution_factor_combobox->currentIndex()); Settings::values.use_frame_limit = ui->toggle_frame_limit->isChecked(); Settings::values.frame_limit = ui->frame_limit->value(); + Settings::values.render_3d = + static_cast(ui->render_3d_combobox->currentIndex()); Settings::values.factor_3d = ui->factor_3d->value(); - Settings::values.toggle_3d = ui->toggle_3d->isChecked(); + Settings::values.pp_shader_name = + ui->shader_combobox->itemText(ui->shader_combobox->currentIndex()).toStdString(); + Settings::values.filter_mode = ui->toggle_linear_filter->isChecked(); Settings::values.layout_option = static_cast(ui->layout_combobox->currentIndex()); Settings::values.swap_screen = ui->swap_screen->isChecked(); @@ -94,6 +109,23 @@ void ConfigureGraphics::ApplyConfiguration() { Settings::values.bg_blue = static_cast(bg_color.blueF()); } +void ConfigureGraphics::updateShaders(bool anaglyph) { + ui->shader_combobox->clear(); + + if (anaglyph) + ui->shader_combobox->addItem("dubois (builtin)"); + else + ui->shader_combobox->addItem("none (builtin)"); + + ui->shader_combobox->setCurrentIndex(0); + + for (const auto& shader : OpenGL::GetPostProcessingShaderList(anaglyph)) { + ui->shader_combobox->addItem(QString::fromStdString(shader)); + if (Settings::values.pp_shader_name == shader) + ui->shader_combobox->setCurrentIndex(ui->shader_combobox->count() - 1); + } +} + void ConfigureGraphics::RetranslateUI() { ui->retranslateUi(this); } diff --git a/src/citra_qt/configuration/configure_graphics.h b/src/citra_qt/configuration/configure_graphics.h index 3c8604136..f2fb3b0b0 100644 --- a/src/citra_qt/configuration/configure_graphics.h +++ b/src/citra_qt/configuration/configure_graphics.h @@ -24,4 +24,7 @@ public: std::unique_ptr ui; QColor bg_color; + +private: + void updateShaders(bool anaglyph); }; diff --git a/src/citra_qt/configuration/configure_graphics.ui b/src/citra_qt/configuration/configure_graphics.ui index 64f5df3cf..c31c43d81 100644 --- a/src/citra_qt/configuration/configure_graphics.ui +++ b/src/citra_qt/configuration/configure_graphics.ui @@ -7,7 +7,7 @@ 0 0 400 - 427 + 603 @@ -212,21 +212,72 @@ + + + + Enable Linear Filtering + + + + + + + + + Post-Processing Shader + + + + + + + + - + - Layout + Stereoscopy - + - + - Enable Stereoscopic 3D + Stereoscopic 3D Mode + + + + + + + + Off + + + + + Side by Side + + + + + Anaglyph + + + + + + + + + + + + Depth @@ -248,6 +299,15 @@ + + + + + + + Layout + + diff --git a/src/common/common_paths.h b/src/common/common_paths.h index dd58e90e7..c5d6ac67b 100644 --- a/src/common/common_paths.h +++ b/src/common/common_paths.h @@ -43,6 +43,7 @@ #define LOG_DIR "log" #define CHEATS_DIR "cheats" #define DLL_DIR "external_dlls" +#define SHADER_DIR "shaders" // Filenames // Files in the directory returned by GetUserPath(UserPath::LogDir) diff --git a/src/common/file_util.cpp b/src/common/file_util.cpp index 247104b6d..0e9a9637c 100644 --- a/src/common/file_util.cpp +++ b/src/common/file_util.cpp @@ -711,6 +711,7 @@ void SetUserPath(const std::string& path) { g_paths.emplace(UserPath::LogDir, user_path + LOG_DIR DIR_SEP); g_paths.emplace(UserPath::CheatsDir, user_path + CHEATS_DIR DIR_SEP); g_paths.emplace(UserPath::DLLDir, user_path + DLL_DIR DIR_SEP); + g_paths.emplace(UserPath::ShaderDir, user_path + SHADER_DIR DIR_SEP); } const std::string& GetUserPath(UserPath path) { diff --git a/src/common/file_util.h b/src/common/file_util.h index 9aba60db6..28e6a42d4 100644 --- a/src/common/file_util.h +++ b/src/common/file_util.h @@ -30,6 +30,7 @@ enum class UserPath { NANDDir, RootDir, SDMCDir, + ShaderDir, SysDataDir, UserDir, }; diff --git a/src/core/frontend/emu_window.cpp b/src/core/frontend/emu_window.cpp index d1e9ed212..4fc285fee 100644 --- a/src/core/frontend/emu_window.cpp +++ b/src/core/frontend/emu_window.cpp @@ -62,7 +62,7 @@ EmuWindow::~EmuWindow() { */ static bool IsWithinTouchscreen(const Layout::FramebufferLayout& layout, unsigned framebuffer_x, unsigned framebuffer_y) { - if (Settings::values.toggle_3d) { + if (Settings::values.render_3d == Settings::StereoRenderOption::SideBySide) { return (framebuffer_y >= layout.bottom_screen.top && framebuffer_y < layout.bottom_screen.bottom && ((framebuffer_x >= layout.bottom_screen.left / 2 && @@ -78,7 +78,7 @@ static bool IsWithinTouchscreen(const Layout::FramebufferLayout& layout, unsigne } std::tuple EmuWindow::ClipToTouchScreen(unsigned new_x, unsigned new_y) const { - if (Settings::values.toggle_3d) { + if (Settings::values.render_3d == Settings::StereoRenderOption::SideBySide) { if (new_x >= framebuffer_layout.width / 2) new_x -= framebuffer_layout.width / 2; new_x = std::max(new_x, framebuffer_layout.bottom_screen.left / 2); @@ -98,10 +98,11 @@ void EmuWindow::TouchPressed(unsigned framebuffer_x, unsigned framebuffer_y) { if (!IsWithinTouchscreen(framebuffer_layout, framebuffer_x, framebuffer_y)) return; - if (Settings::values.toggle_3d && framebuffer_x >= framebuffer_layout.width / 2) + if (Settings::values.render_3d == Settings::StereoRenderOption::SideBySide && + framebuffer_x >= framebuffer_layout.width / 2) framebuffer_x -= framebuffer_layout.width / 2; std::lock_guard guard(touch_state->mutex); - if (Settings::values.toggle_3d) { + if (Settings::values.render_3d == Settings::StereoRenderOption::SideBySide) { touch_state->touch_x = static_cast(framebuffer_x - framebuffer_layout.bottom_screen.left / 2) / (framebuffer_layout.bottom_screen.right / 2 - diff --git a/src/core/hle/kernel/shared_page.cpp b/src/core/hle/kernel/shared_page.cpp index c0d9387db..7067aace8 100644 --- a/src/core/hle/kernel/shared_page.cpp +++ b/src/core/hle/kernel/shared_page.cpp @@ -58,9 +58,8 @@ Handler::Handler(Core::Timing& timing) : timing(timing) { std::bind(&Handler::UpdateTimeCallback, this, _1, _2)); timing.ScheduleEvent(0, update_time_event); - float slidestate = - Settings::values.toggle_3d ? (float_le)Settings::values.factor_3d / 100 : 0.0f; - shared_page.sliderstate_3d = slidestate; + float slidestate = Settings::values.factor_3d / 100.0f; + shared_page.sliderstate_3d = static_cast(slidestate); } /// Gets system time in 3DS format. The epoch is Jan 1900, and the unit is millisecond. diff --git a/src/core/hle/kernel/shared_page.h b/src/core/hle/kernel/shared_page.h index ab88c27a3..5092b4869 100644 --- a/src/core/hle/kernel/shared_page.h +++ b/src/core/hle/kernel/shared_page.h @@ -90,10 +90,10 @@ public: void SetWifiLinkLevel(WifiLinkLevel); - void Set3DSlider(float); - void Set3DLed(u8); + void Set3DSlider(float); + SharedPageDef& GetSharedPage(); private: diff --git a/src/core/settings.cpp b/src/core/settings.cpp index d34aad32a..dcdf76263 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -6,6 +6,7 @@ #include "audio_core/dsp_interface.h" #include "core/core.h" #include "core/gdbstub/gdbstub.h" +#include "core/hle/kernel/shared_page.h" #include "core/hle/service/hid/hid.h" #include "core/hle/service/ir/ir_rst.h" #include "core/hle/service/ir/ir_user.h" @@ -34,6 +35,8 @@ void Apply() { } VideoCore::g_renderer_bg_color_update_requested = true; + VideoCore::g_renderer_sampler_update_requested = true; + VideoCore::g_renderer_shader_update_requested = true; auto& system = Core::System::GetInstance(); if (system.IsPoweredOn()) { @@ -80,8 +83,10 @@ void LogSettings() { LogSetting("Renderer_VsyncEnabled", Settings::values.vsync_enabled); LogSetting("Renderer_UseFrameLimit", Settings::values.use_frame_limit); LogSetting("Renderer_FrameLimit", Settings::values.frame_limit); - LogSetting("Layout_Toggle3d", Settings::values.toggle_3d); - LogSetting("Layout_Factor3d", Settings::values.factor_3d); + LogSetting("Renderer_PostProcessingShader", Settings::values.pp_shader_name); + LogSetting("Renderer_FilterMode", Settings::values.filter_mode); + LogSetting("Stereoscopy_Render3d", static_cast(Settings::values.render_3d)); + LogSetting("Stereoscopy_Factor3d", Settings::values.factor_3d); LogSetting("Layout_LayoutOption", static_cast(Settings::values.layout_option)); LogSetting("Layout_SwapScreen", Settings::values.swap_screen); LogSetting("Audio_EnableDspLle", Settings::values.enable_dsp_lle); diff --git a/src/core/settings.h b/src/core/settings.h index ef5e48410..d18953694 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -32,6 +32,8 @@ enum class MicInputType { Static, }; +enum class StereoRenderOption { Off, SideBySide, Anaglyph }; + namespace NativeButton { enum Values { A, @@ -163,9 +165,12 @@ struct Values { float bg_green; float bg_blue; - bool toggle_3d; + StereoRenderOption render_3d; std::atomic factor_3d; + bool filter_mode; + std::string pp_shader_name; + // Audio bool enable_dsp_lle; bool enable_dsp_lle_multithread; diff --git a/src/core/telemetry_session.cpp b/src/core/telemetry_session.cpp index 6067303a5..1049dd8c8 100644 --- a/src/core/telemetry_session.cpp +++ b/src/core/telemetry_session.cpp @@ -195,7 +195,9 @@ void TelemetrySession::AddInitialInfo(Loader::AppLoader& app_loader) { AddField(Telemetry::FieldType::UserConfig, "Renderer_UseShaderJit", Settings::values.use_shader_jit); AddField(Telemetry::FieldType::UserConfig, "Renderer_UseVsync", Settings::values.vsync_enabled); - AddField(Telemetry::FieldType::UserConfig, "Renderer_Toggle3d", Settings::values.toggle_3d); + AddField(Telemetry::FieldType::UserConfig, "Renderer_FilterMode", Settings::values.filter_mode); + AddField(Telemetry::FieldType::UserConfig, "Renderer_Render3d", + static_cast(Settings::values.render_3d)); AddField(Telemetry::FieldType::UserConfig, "Renderer_Factor3d", Settings::values.factor_3d.load()); AddField(Telemetry::FieldType::UserConfig, "System_IsNew3ds", Settings::values.is_new_3ds); diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt index 77f9144fd..3129982ed 100644 --- a/src/video_core/CMakeLists.txt +++ b/src/video_core/CMakeLists.txt @@ -44,6 +44,8 @@ add_library(video_core STATIC renderer_opengl/gl_vars.cpp renderer_opengl/gl_vars.h renderer_opengl/pica_to_gl.h + renderer_opengl/post_processing_opengl.cpp + renderer_opengl/post_processing_opengl.h renderer_opengl/renderer_opengl.cpp renderer_opengl/renderer_opengl.h shader/debug_data.h diff --git a/src/video_core/renderer_opengl/post_processing_opengl.cpp b/src/video_core/renderer_opengl/post_processing_opengl.cpp new file mode 100644 index 000000000..6c4fec59d --- /dev/null +++ b/src/video_core/renderer_opengl/post_processing_opengl.cpp @@ -0,0 +1,198 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include +#include "common/common_paths.h" +#include "common/file_util.h" +#include "common/string_util.h" +#include "video_core/renderer_opengl/post_processing_opengl.h" + +namespace OpenGL { + +// The Dolphin shader header is added here for drop-in compatibility with most +// of Dolphin's "glsl" shaders, which use hlsl types, hence the #define's below +// It's fairly complete, but the features it's missing are: +// The font texture for the ascii shader (Citra doesn't have an overlay font) +// GetTime (not used in any shader provided by Dolphin) +// GetOption* (used in only one shader provided by Dolphin; would require more +// configuration/frontend work) +constexpr char dolphin_shader_header[] = R"( + +// hlsl to glsl types +#define float2 vec2 +#define float3 vec3 +#define float4 vec4 +#define uint2 uvec2 +#define uint3 uvec3 +#define uint4 uvec4 +#define int2 ivec2 +#define int3 ivec3 +#define int4 ivec4 + +// hlsl to glsl function translation +#define frac fract +#define lerp mix + +// Output variable +out float4 color; +// Input coordinates +in float2 frag_tex_coord; +// Resolution +uniform float4 i_resolution; +uniform float4 o_resolution; +// Layer +uniform int layer; + +uniform sampler2D color_texture; +uniform sampler2D color_texture_r; + +// Interfacing functions +float4 Sample() +{ + return texture(color_texture, frag_tex_coord); +} + +float4 SampleLocation(float2 location) +{ + return texture(color_texture, location); +} + +float4 SampleLayer(int layer) +{ + if(layer == 0) + return texture(color_texture, frag_tex_coord); + else + return texture(color_texture_r, frag_tex_coord); +} + +#define SampleOffset(offset) textureOffset(color_texture, frag_tex_coord, offset) + +float2 GetResolution() +{ + return i_resolution.xy; +} + +float2 GetInvResolution() +{ + return i_resolution.zw; +} + +float2 GetIResolution() +{ + return i_resolution.xy; +} + +float2 GetIInvResolution() +{ + return i_resolution.zw; +} + +float2 GetOResolution() +{ + return o_resolution.xy; +} + +float2 GetOInvResolution() +{ + return o_resolution.zw; +} + +float2 GetCoordinates() +{ + return frag_tex_coord; +} + +void SetOutput(float4 color_in) +{ + color = color_in; +} + +)"; + +std::vector GetPostProcessingShaderList(bool anaglyph) { + std::string shader_dir = FileUtil::GetUserPath(FileUtil::UserPath::ShaderDir); + std::vector shader_names; + + if (!FileUtil::IsDirectory(shader_dir)) { + FileUtil::CreateDir(shader_dir); + } + + if (anaglyph) { + shader_dir = shader_dir + "anaglyph"; + if (!FileUtil::IsDirectory(shader_dir)) { + FileUtil::CreateDir(shader_dir); + } + } + + // Would it make more sense to just add a directory list function to FileUtil? + const auto callback = [&shader_names](u64* num_entries_out, const std::string& directory, + const std::string& virtual_name) -> bool { + const std::string physical_name = directory + DIR_SEP + virtual_name; + if (!FileUtil::IsDirectory(physical_name)) { + // The following is done to avoid coupling this to Qt + std::size_t dot_pos = virtual_name.rfind("."); + if (dot_pos != std::string::npos) { + if (Common::ToLower(virtual_name.substr(dot_pos + 1)) == "glsl") { + shader_names.push_back(virtual_name.substr(0, dot_pos)); + } + } + } + return true; + }; + + FileUtil::ForeachDirectoryEntry(nullptr, shader_dir, callback); + + std::sort(shader_names.begin(), shader_names.end()); + + return shader_names; +} + +std::string GetPostProcessingShaderCode(bool anaglyph, std::string shader) { + std::string shader_dir = FileUtil::GetUserPath(FileUtil::UserPath::ShaderDir); + std::string shader_path; + + if (anaglyph) { + shader_dir = shader_dir + "anaglyph"; + } + + // Examining the directory is done because the shader extension might have an odd case + // This can be eliminated if it is specified that the shader extension must be lowercase + const auto callback = [&shader, &shader_path](u64* num_entries_out, + const std::string& directory, + const std::string& virtual_name) -> bool { + const std::string physical_name = directory + DIR_SEP + virtual_name; + if (!FileUtil::IsDirectory(physical_name)) { + // The following is done to avoid coupling this to Qt + std::size_t dot_pos = virtual_name.rfind("."); + if (dot_pos != std::string::npos) { + if (Common::ToLower(virtual_name.substr(dot_pos + 1)) == "glsl" && + virtual_name.substr(0, dot_pos) == shader) { + shader_path = physical_name; + return false; + } + } + } + return true; + }; + + FileUtil::ForeachDirectoryEntry(nullptr, shader_dir, callback); + if (shader_path.empty()) { + return ""; + } + + std::ifstream file; + OpenFStream(file, shader_path, std::ios_base::in); + if (!file) { + return ""; + } + + std::stringstream shader_text; + shader_text << file.rdbuf(); + + return dolphin_shader_header + shader_text.str(); +} + +} // namespace OpenGL diff --git a/src/video_core/renderer_opengl/post_processing_opengl.h b/src/video_core/renderer_opengl/post_processing_opengl.h new file mode 100644 index 000000000..0016638e6 --- /dev/null +++ b/src/video_core/renderer_opengl/post_processing_opengl.h @@ -0,0 +1,23 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include + +namespace OpenGL { + +// Returns a vector of the names of the shaders available in the +// "shaders" directory in citra's data directory +std::vector GetPostProcessingShaderList(bool anaglyph); + +// Returns the shader code for the shader named "shader_name" +// with the appropriate header prepended to it +// If anaglyph is true, it searches the shaders/anaglyph directory rather than +// the shaders directory +// If the shader cannot be loaded, an empty string is returned +std::string GetPostProcessingShaderCode(bool anaglyph, std::string shader_name); + +} // namespace OpenGL diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp index 73d396d71..874e3d8cc 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.cpp +++ b/src/video_core/renderer_opengl/renderer_opengl.cpp @@ -22,6 +22,7 @@ #include "video_core/debug_utils/debug_utils.h" #include "video_core/rasterizer_interface.h" #include "video_core/renderer_opengl/gl_vars.h" +#include "video_core/renderer_opengl/post_processing_opengl.h" #include "video_core/renderer_opengl/renderer_opengl.h" #include "video_core/video_core.h" @@ -52,6 +53,10 @@ static const char fragment_shader[] = R"( in vec2 frag_tex_coord; out vec4 color; +uniform vec4 i_resolution; +uniform vec4 o_resolution; +uniform int layer; + uniform sampler2D color_texture; void main() { @@ -59,6 +64,36 @@ void main() { } )"; +static const char fragment_shader_anaglyph[] = R"( + +// Anaglyph Red-Cyan shader based on Dubois algorithm +// Constants taken from the paper: +// "Conversion of a Stereo Pair to Anaglyph with +// the Least-Squares Projection Method" +// Eric Dubois, March 2009 +const mat3 l = mat3( 0.437, 0.449, 0.164, + -0.062,-0.062,-0.024, + -0.048,-0.050,-0.017); +const mat3 r = mat3(-0.011,-0.032,-0.007, + 0.377, 0.761, 0.009, + -0.026,-0.093, 1.234); + +in vec2 frag_tex_coord; +out vec4 color; + +uniform vec4 resolution; +uniform int layer; + +uniform sampler2D color_texture; +uniform sampler2D color_texture_r; + +void main() { + vec4 color_tex_l = texture(color_texture, frag_tex_coord); + vec4 color_tex_r = texture(color_texture_r, frag_tex_coord); + color = vec4(color_tex_l.rgb*l+color_tex_r.rgb*r, color_tex_l.a); +} +)"; + /** * Vertex structure that the drawn screen rectangles are composed of. */ @@ -275,20 +310,10 @@ void RendererOpenGL::InitOpenGLObjects() { glClearColor(Settings::values.bg_red, Settings::values.bg_green, Settings::values.bg_blue, 0.0f); - // Link shaders and get variable locations - if (GLES) { - std::string frag_source(fragment_shader_precision_OES); - frag_source += fragment_shader; - shader.Create(vertex_shader, frag_source.data()); - } else { - shader.Create(vertex_shader, fragment_shader); - } - state.draw.shader_program = shader.handle; - state.Apply(); - uniform_modelview_matrix = glGetUniformLocation(shader.handle, "modelview_matrix"); - uniform_color_texture = glGetUniformLocation(shader.handle, "color_texture"); - attrib_position = glGetAttribLocation(shader.handle, "vert_position"); - attrib_tex_coord = glGetAttribLocation(shader.handle, "vert_tex_coord"); + filter_sampler.Create(); + ReloadSampler(); + + ReloadShader(); // Generate VBO handle for drawing vertex_buffer.Create(); @@ -334,6 +359,63 @@ void RendererOpenGL::InitOpenGLObjects() { state.Apply(); } +void RendererOpenGL::ReloadSampler() { + glSamplerParameteri(filter_sampler.handle, GL_TEXTURE_MIN_FILTER, + Settings::values.filter_mode ? GL_LINEAR : GL_NEAREST); + glSamplerParameteri(filter_sampler.handle, GL_TEXTURE_MAG_FILTER, + Settings::values.filter_mode ? GL_LINEAR : GL_NEAREST); + glSamplerParameteri(filter_sampler.handle, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glSamplerParameteri(filter_sampler.handle, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); +} + +void RendererOpenGL::ReloadShader() { + // Link shaders and get variable locations + std::string shader_data; + if (GLES) { + shader_data += fragment_shader_precision_OES; + } + if (Settings::values.render_3d == Settings::StereoRenderOption::Anaglyph) { + if (Settings::values.pp_shader_name == "dubois (builtin)") { + shader_data += fragment_shader_anaglyph; + } else { + std::string shader_text = + OpenGL::GetPostProcessingShaderCode(true, Settings::values.pp_shader_name); + if (shader_text.empty()) { + // Should probably provide some information that the shader couldn't load + shader_data += fragment_shader_anaglyph; + } else { + shader_data += shader_text; + } + } + } else { + if (Settings::values.pp_shader_name == "none (builtin)") { + shader_data += fragment_shader; + } else { + std::string shader_text = + OpenGL::GetPostProcessingShaderCode(false, Settings::values.pp_shader_name); + if (shader_text.empty()) { + // Should probably provide some information that the shader couldn't load + shader_data += fragment_shader; + } else { + shader_data += shader_text; + } + } + } + shader.Create(vertex_shader, shader_data.c_str()); + state.draw.shader_program = shader.handle; + state.Apply(); + uniform_modelview_matrix = glGetUniformLocation(shader.handle, "modelview_matrix"); + uniform_color_texture = glGetUniformLocation(shader.handle, "color_texture"); + if (Settings::values.render_3d == Settings::StereoRenderOption::Anaglyph) { + uniform_color_texture_r = glGetUniformLocation(shader.handle, "color_texture_r"); + } + uniform_i_resolution = glGetUniformLocation(shader.handle, "i_resolution"); + uniform_o_resolution = glGetUniformLocation(shader.handle, "o_resolution"); + uniform_layer = glGetUniformLocation(shader.handle, "layer"); + attrib_position = glGetAttribLocation(shader.handle, "vert_position"); + attrib_tex_coord = glGetAttribLocation(shader.handle, "vert_tex_coord"); +} + void RendererOpenGL::ConfigureFramebufferTexture(TextureInfo& texture, const GPU::Regs::FramebufferConfig& framebuffer) { GPU::Regs::PixelFormat format = framebuffer.color_format; @@ -401,22 +483,71 @@ void RendererOpenGL::ConfigureFramebufferTexture(TextureInfo& texture, */ void RendererOpenGL::DrawSingleScreenRotated(const ScreenInfo& screen_info, float x, float y, float w, float h) { - auto& texcoords = screen_info.display_texcoords; + const auto& texcoords = screen_info.display_texcoords; - std::array vertices = {{ + const std::array vertices = {{ ScreenRectVertex(x, y, texcoords.bottom, texcoords.left), ScreenRectVertex(x + w, y, texcoords.bottom, texcoords.right), ScreenRectVertex(x, y + h, texcoords.top, texcoords.left), ScreenRectVertex(x + w, y + h, texcoords.top, texcoords.right), }}; + // As this is the "DrawSingleScreenRotated" function, the output resolution dimensions have been + // swapped. If a non-rotated draw-screen function were to be added for book-mode games, those + // should probably be set to the standard (w, h, 1.0 / w, 1.0 / h) ordering. + u16 scale_factor = VideoCore::GetResolutionScaleFactor(); + glUniform4f(uniform_i_resolution, screen_info.texture.width * scale_factor, + screen_info.texture.height * scale_factor, + 1.0 / (screen_info.texture.width * scale_factor), + 1.0 / (screen_info.texture.height * scale_factor)); + glUniform4f(uniform_o_resolution, h, w, 1.0f / h, 1.0f / w); state.texture_units[0].texture_2d = screen_info.display_texture; + state.texture_units[0].sampler = filter_sampler.handle; state.Apply(); glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices.data()); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); state.texture_units[0].texture_2d = 0; + state.texture_units[0].sampler = 0; + state.Apply(); +} + +/** + * Draws a single texture to the emulator window, rotating the texture to correct for the 3DS's LCD + * rotation. + */ +void RendererOpenGL::DrawSingleScreenAnaglyphRotated(const ScreenInfo& screen_info_l, + const ScreenInfo& screen_info_r, float x, + float y, float w, float h) { + const auto& texcoords = screen_info_l.display_texcoords; + + const std::array vertices = {{ + ScreenRectVertex(x, y, texcoords.bottom, texcoords.left), + ScreenRectVertex(x + w, y, texcoords.bottom, texcoords.right), + ScreenRectVertex(x, y + h, texcoords.top, texcoords.left), + ScreenRectVertex(x + w, y + h, texcoords.top, texcoords.right), + }}; + + u16 scale_factor = VideoCore::GetResolutionScaleFactor(); + glUniform4f(uniform_i_resolution, screen_info_l.texture.width * scale_factor, + screen_info_l.texture.height * scale_factor, + 1.0 / (screen_info_l.texture.width * scale_factor), + 1.0 / (screen_info_l.texture.height * scale_factor)); + glUniform4f(uniform_o_resolution, h, w, 1.0f / h, 1.0f / w); + state.texture_units[0].texture_2d = screen_info_l.display_texture; + state.texture_units[1].texture_2d = screen_info_r.display_texture; + state.texture_units[0].sampler = filter_sampler.handle; + state.texture_units[1].sampler = filter_sampler.handle; + state.Apply(); + + glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices.data()); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + + state.texture_units[0].texture_2d = 0; + state.texture_units[1].texture_2d = 0; + state.texture_units[0].sampler = 0; + state.texture_units[1].sampler = 0; state.Apply(); } @@ -430,6 +561,18 @@ void RendererOpenGL::DrawScreens(const Layout::FramebufferLayout& layout) { 0.0f); } + if (VideoCore::g_renderer_sampler_update_requested.exchange(false)) { + // Set the new filtering mode for the sampler + ReloadSampler(); + } + + if (VideoCore::g_renderer_shader_update_requested.exchange(false)) { + // Update fragment shader before drawing + shader.Release(); + // Link shaders and get variable locations + ReloadShader(); + } + const auto& top_screen = layout.top_screen; const auto& bottom_screen = layout.bottom_screen; @@ -442,36 +585,53 @@ void RendererOpenGL::DrawScreens(const Layout::FramebufferLayout& layout) { glUniformMatrix3x2fv(uniform_modelview_matrix, 1, GL_FALSE, ortho_matrix.data()); // Bind texture in Texture Unit 0 - glActiveTexture(GL_TEXTURE0); glUniform1i(uniform_color_texture, 0); + // Bind a second texture for the right eye if in Anaglyph mode + if (Settings::values.render_3d == Settings::StereoRenderOption::Anaglyph) { + glUniform1i(uniform_color_texture_r, 1); + } + + glUniform1i(uniform_layer, 0); if (layout.top_screen_enabled) { - if (!Settings::values.toggle_3d) { + if (Settings::values.render_3d == Settings::StereoRenderOption::Off) { DrawSingleScreenRotated(screen_infos[0], (float)top_screen.left, (float)top_screen.top, (float)top_screen.GetWidth(), (float)top_screen.GetHeight()); - } else { + } else if (Settings::values.render_3d == Settings::StereoRenderOption::SideBySide) { DrawSingleScreenRotated(screen_infos[0], (float)top_screen.left / 2, (float)top_screen.top, (float)top_screen.GetWidth() / 2, (float)top_screen.GetHeight()); + glUniform1i(uniform_layer, 1); DrawSingleScreenRotated(screen_infos[1], ((float)top_screen.left / 2) + ((float)layout.width / 2), (float)top_screen.top, (float)top_screen.GetWidth() / 2, (float)top_screen.GetHeight()); + } else if (Settings::values.render_3d == Settings::StereoRenderOption::Anaglyph) { + DrawSingleScreenAnaglyphRotated( + screen_infos[0], screen_infos[1], (float)top_screen.left, (float)top_screen.top, + (float)top_screen.GetWidth(), (float)top_screen.GetHeight()); } } + glUniform1i(uniform_layer, 0); if (layout.bottom_screen_enabled) { - if (!Settings::values.toggle_3d) { + if (Settings::values.render_3d == Settings::StereoRenderOption::Off) { DrawSingleScreenRotated(screen_infos[2], (float)bottom_screen.left, (float)bottom_screen.top, (float)bottom_screen.GetWidth(), (float)bottom_screen.GetHeight()); - } else { + } else if (Settings::values.render_3d == Settings::StereoRenderOption::SideBySide) { DrawSingleScreenRotated(screen_infos[2], (float)bottom_screen.left / 2, (float)bottom_screen.top, (float)bottom_screen.GetWidth() / 2, (float)bottom_screen.GetHeight()); + glUniform1i(uniform_layer, 1); DrawSingleScreenRotated(screen_infos[2], ((float)bottom_screen.left / 2) + ((float)layout.width / 2), (float)bottom_screen.top, (float)bottom_screen.GetWidth() / 2, (float)bottom_screen.GetHeight()); + } else if (Settings::values.render_3d == Settings::StereoRenderOption::Anaglyph) { + DrawSingleScreenAnaglyphRotated(screen_infos[2], screen_infos[2], + (float)bottom_screen.left, (float)bottom_screen.top, + (float)bottom_screen.GetWidth(), + (float)bottom_screen.GetHeight()); } } diff --git a/src/video_core/renderer_opengl/renderer_opengl.h b/src/video_core/renderer_opengl/renderer_opengl.h index 5bd601834..75a1a83d6 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.h +++ b/src/video_core/renderer_opengl/renderer_opengl.h @@ -52,10 +52,15 @@ public: private: void InitOpenGLObjects(); + void ReloadSampler(); + void ReloadShader(); void ConfigureFramebufferTexture(TextureInfo& texture, const GPU::Regs::FramebufferConfig& framebuffer); void DrawScreens(const Layout::FramebufferLayout& layout); void DrawSingleScreenRotated(const ScreenInfo& screen_info, float x, float y, float w, float h); + void DrawSingleScreenAnaglyphRotated(const ScreenInfo& screen_info_l, + const ScreenInfo& screen_info_r, float x, float y, float w, + float h); void UpdateFramerate(); // Loads framebuffer from emulated memory into the display information structure @@ -71,6 +76,7 @@ private: OGLBuffer vertex_buffer; OGLProgram shader; OGLFramebuffer screenshot_framebuffer; + OGLSampler filter_sampler; /// Display information for top and bottom screens respectively std::array screen_infos; @@ -78,6 +84,12 @@ private: // Shader uniform location indices GLuint uniform_modelview_matrix; GLuint uniform_color_texture; + GLuint uniform_color_texture_r; + + // Shader uniform for Dolphin compatibility + GLuint uniform_i_resolution; + GLuint uniform_o_resolution; + GLuint uniform_layer; // Shader attribute input indices GLuint attrib_position; diff --git a/src/video_core/video_core.cpp b/src/video_core/video_core.cpp index 525bc963c..7b382dbac 100644 --- a/src/video_core/video_core.cpp +++ b/src/video_core/video_core.cpp @@ -24,6 +24,8 @@ std::atomic g_hw_shader_enabled; std::atomic g_hw_shader_accurate_gs; std::atomic g_hw_shader_accurate_mul; std::atomic g_renderer_bg_color_update_requested; +std::atomic g_renderer_sampler_update_requested; +std::atomic g_renderer_shader_update_requested; // Screenshot std::atomic g_renderer_screenshot_requested; void* g_screenshot_bits; diff --git a/src/video_core/video_core.h b/src/video_core/video_core.h index c94009c92..8e7810b1a 100644 --- a/src/video_core/video_core.h +++ b/src/video_core/video_core.h @@ -34,6 +34,8 @@ extern std::atomic g_hw_shader_enabled; extern std::atomic g_hw_shader_accurate_gs; extern std::atomic g_hw_shader_accurate_mul; extern std::atomic g_renderer_bg_color_update_requested; +extern std::atomic g_renderer_sampler_update_requested; +extern std::atomic g_renderer_shader_update_requested; // Screenshot extern std::atomic g_renderer_screenshot_requested; extern void* g_screenshot_bits;