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
This commit is contained in:
xperia64 2019-08-09 14:00:47 -04:00 committed by Ben
parent 3e9c2e77d9
commit 8131bd32e3
22 changed files with 587 additions and 48 deletions

View file

@ -0,0 +1,198 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <sstream>
#include <string>
#include <vector>
#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<std::string> GetPostProcessingShaderList(bool anaglyph) {
std::string shader_dir = FileUtil::GetUserPath(FileUtil::UserPath::ShaderDir);
std::vector<std::string> 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

View file

@ -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 <string>
#include <vector>
namespace OpenGL {
// Returns a vector of the names of the shaders available in the
// "shaders" directory in citra's data directory
std::vector<std::string> 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

View file

@ -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<ScreenRectVertex, 4> vertices = {{
const std::array<ScreenRectVertex, 4> 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<ScreenRectVertex, 4> 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());
}
}

View file

@ -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<ScreenInfo, 3> 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;