diff --git a/src/citra/config.cpp b/src/citra/config.cpp index 1ed5be0047..20e0bddbfa 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 37c12a3744..bd8681c8b4 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 168fab028e..f4d2f5a72b 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 0a1a0163db..d6ef158b00 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 3c86041366..f2fb3b0b08 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 64f5df3cf8..c31c43d813 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 dd58e90e73..c5d6ac67bc 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 247104b6d2..0e9a9637c8 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 9aba60db65..28e6a42d40 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 d1e9ed212f..4fc285fee7 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 c0d9387db5..7067aace8b 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 ab88c27a31..5092b48693 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 d34aad32aa..dcdf762636 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 ef5e484109..d18953694e 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 6067303a52..1049dd8c8b 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 77f9144fdc..3129982ed3 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 0000000000..6c4fec59da --- /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 0000000000..0016638e63 --- /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 73d396d71f..874e3d8ccc 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 5bd6018346..75a1a83d68 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 525bc963c4..7b382dbac0 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 c94009c92a..8e7810b1a2 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;