video_core: add workarounds to enable GLES support

video_core: shorten GetGLSLVersionString

video_core: make GLES version and extensions consistent

video_core: move some logic to LoadShader

video_core: deduplicate fragment shader precision specifier
This commit is contained in:
SachinVin 2019-01-30 14:10:33 -06:00 committed by BreadFish64
parent cd21abe50f
commit d63acfc1e9
10 changed files with 204 additions and 37 deletions

View File

@ -41,6 +41,8 @@ add_library(video_core STATIC
renderer_opengl/gl_state.h
renderer_opengl/gl_stream_buffer.cpp
renderer_opengl/gl_stream_buffer.h
renderer_opengl/gl_vars.cpp
renderer_opengl/gl_vars.h
renderer_opengl/pica_to_gl.h
renderer_opengl/renderer_opengl.cpp
renderer_opengl/renderer_opengl.h

View File

@ -28,6 +28,7 @@
#include "video_core/renderer_base.h"
#include "video_core/renderer_opengl/gl_rasterizer_cache.h"
#include "video_core/renderer_opengl/gl_state.h"
#include "video_core/renderer_opengl/gl_vars.h"
#include "video_core/utils.h"
#include "video_core/video_core.h"
@ -50,6 +51,17 @@ static constexpr std::array<FormatTuple, 5> fb_format_tuples = {{
{GL_RGBA4, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4}, // RGBA4
}};
// Same as above, with minor changes for OpenGL ES. Replaced
// GL_UNSIGNED_INT_8_8_8_8 with GL_UNSIGNED_BYTE and
// GL_BGR with GL_RGB
static constexpr std::array<FormatTuple, 5> fb_format_tuples_oes = {{
{GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE}, // RGBA8
{GL_RGB8, GL_RGB, GL_UNSIGNED_BYTE}, // RGB8
{GL_RGB5_A1, GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1}, // RGB5A1
{GL_RGB565, GL_RGB, GL_UNSIGNED_SHORT_5_6_5}, // RGB565
{GL_RGBA4, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4}, // RGBA4
}};
static constexpr std::array<FormatTuple, 4> depth_format_tuples = {{
{GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT}, // D16
{},
@ -63,6 +75,9 @@ static const FormatTuple& GetFormatTuple(PixelFormat pixel_format) {
const SurfaceType type = SurfaceParams::GetFormatType(pixel_format);
if (type == SurfaceType::Color) {
ASSERT(static_cast<std::size_t>(pixel_format) < fb_format_tuples.size());
if (GLES) {
return fb_format_tuples_oes[static_cast<unsigned int>(pixel_format)];
}
return fb_format_tuples[static_cast<unsigned int>(pixel_format)];
} else if (type == SurfaceType::Depth || type == SurfaceType::DepthStencil) {
std::size_t tuple_idx = static_cast<std::size_t>(pixel_format) - 14;
@ -72,6 +87,77 @@ static const FormatTuple& GetFormatTuple(PixelFormat pixel_format) {
return tex_tuple;
}
/**
* OpenGL ES does not support glGetTexImage. Obtain the pixels by attaching the
* texture to a framebuffer.
* Originally from https://github.com/apitrace/apitrace/blob/master/retrace/glstate_images.cpp
*/
static inline void GetTexImageOES(GLenum target, GLint level, GLenum format, GLenum type,
GLint height, GLint width, GLint depth, GLubyte* pixels) {
memset(pixels, 0x80, height * width * 4);
GLenum texture_binding = GL_NONE;
switch (target) {
case GL_TEXTURE_2D:
texture_binding = GL_TEXTURE_BINDING_2D;
break;
case GL_TEXTURE_CUBE_MAP_POSITIVE_X:
case GL_TEXTURE_CUBE_MAP_NEGATIVE_X:
case GL_TEXTURE_CUBE_MAP_POSITIVE_Y:
case GL_TEXTURE_CUBE_MAP_NEGATIVE_Y:
case GL_TEXTURE_CUBE_MAP_POSITIVE_Z:
case GL_TEXTURE_CUBE_MAP_NEGATIVE_Z:
texture_binding = GL_TEXTURE_BINDING_CUBE_MAP;
break;
case GL_TEXTURE_3D_OES:
texture_binding = GL_TEXTURE_BINDING_3D_OES;
default:
return;
}
GLint texture = 0;
glGetIntegerv(texture_binding, &texture);
if (!texture) {
return;
}
GLint prev_fbo = 0;
GLuint fbo = 0;
glGetIntegerv(GL_FRAMEBUFFER_BINDING, &prev_fbo);
glGenFramebuffers(1, &fbo);
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
switch (target) {
case GL_TEXTURE_2D:
case GL_TEXTURE_CUBE_MAP_POSITIVE_X:
case GL_TEXTURE_CUBE_MAP_NEGATIVE_X:
case GL_TEXTURE_CUBE_MAP_POSITIVE_Y:
case GL_TEXTURE_CUBE_MAP_NEGATIVE_Y:
case GL_TEXTURE_CUBE_MAP_POSITIVE_Z:
case GL_TEXTURE_CUBE_MAP_NEGATIVE_Z: {
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, level);
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
if (status != GL_FRAMEBUFFER_COMPLETE) {
LOG_DEBUG(Render_OpenGL, "Framebuffer is incomplete, status: {:X}", status);
}
glReadPixels(0, 0, width, height, format, type, pixels);
break;
}
case GL_TEXTURE_3D_OES:
for (int i = 0; i < depth; i++) {
glFramebufferTexture3D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_3D, texture,
level, i);
glReadPixels(0, 0, width, height, format, type, pixels + 4 * i * width * height);
}
break;
}
glBindFramebuffer(GL_FRAMEBUFFER, prev_fbo);
glDeleteFramebuffers(1, &fbo);
}
template <typename Map, typename Interval>
constexpr auto RangeFromInterval(Map& map, const Interval& interval) {
return boost::make_iterator_range(map.equal_range(interval));
@ -841,7 +927,12 @@ void CachedSurface::DownloadGLTexture(const MathUtil::Rectangle<u32>& rect, GLui
state.Apply();
glActiveTexture(GL_TEXTURE0);
glGetTexImage(GL_TEXTURE_2D, 0, tuple.format, tuple.type, &gl_buffer[buffer_offset]);
if (GLES) {
GetTexImageOES(GL_TEXTURE_2D, 0, tuple.format, tuple.type, height, width, 0,
&gl_buffer[buffer_offset]);
} else {
glGetTexImage(GL_TEXTURE_2D, 0, tuple.format, tuple.type, &gl_buffer[buffer_offset]);
}
} else {
state.ResetTexture(texture.handle);
state.draw.read_framebuffer = read_fb_handle;
@ -982,16 +1073,15 @@ RasterizerCacheOpenGL::RasterizerCacheOpenGL() {
d24s8_abgr_buffer.Create();
d24s8_abgr_buffer_size = 0;
const char* vs_source = R"(
#version 330 core
std::string vs_source = R"(
const vec2 vertices[4] = vec2[4](vec2(-1.0, -1.0), vec2(1.0, -1.0), vec2(-1.0, 1.0), vec2(1.0, 1.0));
void main() {
gl_Position = vec4(vertices[gl_VertexID], 0.0, 1.0);
}
)";
const char* fs_source = R"(
#version 330 core
std::string fs_source = GLES ? fragment_shader_precision_OES : "";
fs_source += R"(
uniform samplerBuffer tbo;
uniform vec2 tbo_size;
uniform vec4 viewport;
@ -1004,7 +1094,7 @@ void main() {
color = texelFetch(tbo, tbo_offset).rabg;
}
)";
d24s8_abgr_shader.Create(vs_source, fs_source);
d24s8_abgr_shader.Create(vs_source.c_str(), fs_source.c_str());
OpenGLState state = OpenGLState::GetCurState();
GLuint old_program = state.draw.shader_program;

View File

@ -18,6 +18,7 @@
#include "video_core/renderer_opengl/gl_shader_decompiler.h"
#include "video_core/renderer_opengl/gl_shader_gen.h"
#include "video_core/renderer_opengl/gl_shader_util.h"
#include "video_core/renderer_opengl/gl_vars.h"
#include "video_core/video_core.h"
using Pica::FramebufferRegs;
@ -1250,7 +1251,6 @@ std::string GenerateFragmentShader(const PicaFSConfig& config, bool separable_sh
const auto& state = config.state;
std::string out = R"(
#version 330 core
#extension GL_ARB_shader_image_load_store : enable
#extension GL_ARB_shader_image_size : enable
#define ALLOW_SHADOW (defined(GL_ARB_shader_image_load_store) && defined(GL_ARB_shader_image_size))
@ -1260,10 +1260,16 @@ std::string GenerateFragmentShader(const PicaFSConfig& config, bool separable_sh
out += "#extension GL_ARB_separate_shader_objects : enable\n";
}
if (GLES) {
out += fragment_shader_precision_OES;
}
out += GetVertexInterfaceDeclaration(false, separable_shader);
out += R"(
#ifndef CITRA_GLES
in vec4 gl_FragCoord;
#endif // CITRA_GLES
out vec4 color;
@ -1300,13 +1306,13 @@ float LookupLightingLUT(int lut_index, int index, float delta) {
float LookupLightingLUTUnsigned(int lut_index, float pos) {
int index = clamp(int(pos * 256.0), 0, 255);
float delta = pos * 256.0 - index;
float delta = pos * 256.0 - float(index);
return LookupLightingLUT(lut_index, index, delta);
}
float LookupLightingLUTSigned(int lut_index, float pos) {
int index = clamp(int(pos * 128.0), -128, 127);
float delta = pos * 128.0 - index;
float delta = pos * 128.0 - float(index);
if (index < 0) index += 256;
return LookupLightingLUT(lut_index, index, delta);
}
@ -1494,10 +1500,10 @@ vec4 secondary_fragment_color = vec4(0.0);
// Negate the condition if we have to keep only the pixels outside the scissor box
if (state.scissor_test_mode == RasterizerRegs::ScissorMode::Include)
out += "!";
out += "(gl_FragCoord.x >= scissor_x1 && "
"gl_FragCoord.y >= scissor_y1 && "
"gl_FragCoord.x < scissor_x2 && "
"gl_FragCoord.y < scissor_y2)) discard;\n";
out += "(gl_FragCoord.x >= float(scissor_x1) && "
"gl_FragCoord.y >= float(scissor_y1) && "
"gl_FragCoord.x < float(scissor_x2) && "
"gl_FragCoord.y < float(scissor_y2))) discard;\n";
}
// After perspective divide, OpenGL transform z_over_w from [-1, 1] to [near, far]. Here we use
@ -1529,7 +1535,7 @@ vec4 secondary_fragment_color = vec4(0.0);
if (state.fog_mode == TexturingRegs::FogMode::Fog) {
// Get index into fog LUT
if (state.fog_flip) {
out += "float fog_index = (1.0 - depth) * 128.0;\n";
out += "float fog_index = (1.0 - float(depth)) * 128.0;\n";
} else {
out += "float fog_index = depth * 128.0;\n";
}
@ -1591,7 +1597,7 @@ do {
}
std::string GenerateTrivialVertexShader(bool separable_shader) {
std::string out = "#version 330 core\n";
std::string out = "";
if (separable_shader) {
out += "#extension GL_ARB_separate_shader_objects : enable\n";
}
@ -1626,8 +1632,10 @@ void main() {
normquat = vert_normquat;
view = vert_view;
gl_Position = vert_position;
#if !defined(CITRA_GLES) || defined(GL_EXT_clip_cull_distance)
gl_ClipDistance[0] = -vert_position.z; // fixed PICA clipping plane z <= 0
gl_ClipDistance[1] = dot(clip_coef, vert_position);
#endif // !defined(CITRA_GLES) || defined(GL_EXT_clip_cull_distance)
}
)";
@ -1636,7 +1644,7 @@ void main() {
std::optional<std::string> GenerateVertexShader(const Pica::Shader::ShaderSetup& setup,
const PicaVSConfig& config, bool separable_shader) {
std::string out = "#version 330 core\n";
std::string out = "";
if (separable_shader) {
out += "#extension GL_ARB_separate_shader_objects : enable\n";
}
@ -1744,9 +1752,12 @@ struct Vertex {
semantic(VSOutputAttributes::POSITION_Y) + ", " +
semantic(VSOutputAttributes::POSITION_Z) + ", " +
semantic(VSOutputAttributes::POSITION_W) + ");\n";
semantic(VSOutputAttributes::POSITION_W) + ");\n";
out += " gl_Position = vtx_pos;\n";
out += "#if !defined(CITRA_GLES) || defined(GL_EXT_clip_cull_distance)\n";
out += " gl_ClipDistance[0] = -vtx_pos.z;\n"; // fixed PICA clipping plane z <= 0
out += " gl_ClipDistance[1] = dot(clip_coef, vtx_pos);\n\n";
out += " gl_ClipDistance[1] = dot(clip_coef, vtx_pos);\n";
out += "#endif // !defined(CITRA_GLES) || defined(GL_EXT_clip_cull_distance)\n\n";
out += " vec4 vtx_quat = GetVertexQuaternion(vtx);\n";
out += " normquat = mix(vtx_quat, -vtx_quat, bvec4(quats_opposite));\n\n";
@ -1789,7 +1800,7 @@ void EmitPrim(Vertex vtx0, Vertex vtx1, Vertex vtx2) {
};
std::string GenerateFixedGeometryShader(const PicaFixedGSConfig& config, bool separable_shader) {
std::string out = "#version 330 core\n";
std::string out = "";
if (separable_shader) {
out += "#extension GL_ARB_separate_shader_objects : enable\n\n";
}
@ -1824,7 +1835,7 @@ void main() {
std::optional<std::string> GenerateGeometryShader(const Pica::Shader::ShaderSetup& setup,
const PicaGSConfig& config,
bool separable_shader) {
std::string out = "#version 330 core\n";
std::string out = "";
if (separable_shader) {
out += "#extension GL_ARB_separate_shader_objects : enable\n";
}

View File

@ -2,15 +2,33 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <array>
#include <string>
#include <vector>
#include <glad/glad.h>
#include "common/assert.h"
#include "common/logging/log.h"
#include "video_core/renderer_opengl/gl_shader_util.h"
#include "video_core/renderer_opengl/gl_vars.h"
namespace OpenGL {
GLuint LoadShader(const char* source, GLenum type) {
const std::string version = GLES ? R"(
#version 310 es
#define CITRA_GLES
#if defined(GL_ANDROID_extension_pack_es31a)
#extension GL_ANDROID_extension_pack_es31a : enable
#endif // defined(GL_ANDROID_extension_pack_es31a)
#if defined(GL_EXT_clip_cull_distance)
#extension GL_EXT_clip_cull_distance : enable
#endif // defined(GL_EXT_clip_cull_distance)
)"
: "#version 330\n";
const char* debug_type;
switch (type) {
case GL_VERTEX_SHADER:
@ -26,8 +44,9 @@ GLuint LoadShader(const char* source, GLenum type) {
UNREACHABLE();
}
std::array<const char*, 2> src_arr{version.data(), source};
GLuint shader_id = glCreateShader(type);
glShaderSource(shader_id, 1, &source, nullptr);
glShaderSource(shader_id, static_cast<GLsizei>(src_arr.size()), src_arr.data(), nullptr);
LOG_DEBUG(Render_OpenGL, "Compiling {} shader...", debug_type);
glCompileShader(shader_id);
@ -44,7 +63,7 @@ GLuint LoadShader(const char* source, GLenum type) {
} else {
LOG_ERROR(Render_OpenGL, "Error compiling {} shader:\n{}", debug_type,
&shader_error[0]);
LOG_ERROR(Render_OpenGL, "Shader source code:\n{}", source);
LOG_ERROR(Render_OpenGL, "Shader source code:\n{}{}", src_arr[0], src_arr[1]);
}
}
return shader_id;

View File

@ -9,6 +9,17 @@
namespace OpenGL {
// High precision may or may not supported in GLES3. If it isn't, use medium precision instead.
static constexpr char fragment_shader_precision_OES[] = R"(
#ifdef GL_FRAGMENT_PRECISION_HIGH
precision highp float;
precision highp samplerBuffer;
#else
precision mediump float;
precision mediump samplerBuffer;
#endif // GL_FRAGMENT_PRECISION_HIGH
)";
/**
* Utility function to create and compile an OpenGL GLSL shader
* @param source String of the GLSL shader program

View File

@ -6,6 +6,7 @@
#include "common/common_funcs.h"
#include "common/logging/log.h"
#include "video_core/renderer_opengl/gl_state.h"
#include "video_core/renderer_opengl/gl_vars.h"
namespace OpenGL {
@ -193,8 +194,13 @@ void OpenGLState::Apply() const {
glBlendEquationSeparate(blend.rgb_equation, blend.a_equation);
}
if (logic_op != cur_state.logic_op) {
glLogicOp(logic_op);
// GLES3 does not support glLogicOp
if (!GLES) {
if (logic_op != cur_state.logic_op) {
glLogicOp(logic_op);
}
} else {
LOG_TRACE(Render_OpenGL, "glLogicOps are unimplemented...");
}
// Textures
@ -319,12 +325,14 @@ void OpenGLState::Apply() const {
}
// Clip distance
for (std::size_t i = 0; i < clip_distance.size(); ++i) {
if (clip_distance[i] != cur_state.clip_distance[i]) {
if (clip_distance[i]) {
glEnable(GL_CLIP_DISTANCE0 + static_cast<GLenum>(i));
} else {
glDisable(GL_CLIP_DISTANCE0 + static_cast<GLenum>(i));
if (!GLES || GLAD_GL_EXT_clip_cull_distance) {
for (size_t i = 0; i < clip_distance.size(); ++i) {
if (clip_distance[i] != cur_state.clip_distance[i]) {
if (clip_distance[i]) {
glEnable(GL_CLIP_DISTANCE0 + static_cast<GLenum>(i));
} else {
glDisable(GL_CLIP_DISTANCE0 + static_cast<GLenum>(i));
}
}
}
}

View File

@ -0,0 +1,9 @@
// Copyright 2019 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "video_core/renderer_opengl/gl_vars.h"
namespace OpenGL {
bool GLES;
}

View File

@ -0,0 +1,9 @@
// Copyright 2019 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
namespace OpenGL {
extern bool GLES;
}

View File

@ -21,14 +21,13 @@
#include "core/tracer/recorder.h"
#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/renderer_opengl.h"
#include "video_core/video_core.h"
namespace OpenGL {
static const char vertex_shader[] = R"(
#version 150 core
in vec2 vert_position;
in vec2 vert_tex_coord;
out vec2 frag_tex_coord;
@ -50,8 +49,6 @@ void main() {
)";
static const char fragment_shader[] = R"(
#version 150 core
in vec2 frag_tex_coord;
out vec4 color;
@ -279,7 +276,13 @@ void RendererOpenGL::InitOpenGLObjects() {
0.0f);
// Link shaders and get variable locations
shader.Create(vertex_shader, fragment_shader);
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");
@ -344,7 +347,7 @@ void RendererOpenGL::ConfigureFramebufferTexture(TextureInfo& texture,
case GPU::Regs::PixelFormat::RGBA8:
internal_format = GL_RGBA;
texture.gl_format = GL_RGBA;
texture.gl_type = GL_UNSIGNED_INT_8_8_8_8;
texture.gl_type = GLES ? GL_UNSIGNED_BYTE : GL_UNSIGNED_INT_8_8_8_8;
break;
case GPU::Regs::PixelFormat::RGB8:
@ -353,7 +356,9 @@ void RendererOpenGL::ConfigureFramebufferTexture(TextureInfo& texture,
// mostly everywhere) for words or half-words.
// TODO: check how those behave on big-endian processors.
internal_format = GL_RGB;
texture.gl_format = GL_BGR;
// GLES Dosen't support BGR , Use RGB instead
texture.gl_format = GLES ? GL_RGB : GL_BGR;
texture.gl_type = GL_UNSIGNED_BYTE;
break;
@ -555,7 +560,7 @@ Core::System::ResultStatus RendererOpenGL::Init() {
return Core::System::ResultStatus::ErrorVideoCore_ErrorGenericDrivers;
}
if (!GLAD_GL_VERSION_3_3) {
if (!(GLAD_GL_VERSION_3_3 || GLAD_GL_ES_VERSION_3_1)) {
return Core::System::ResultStatus::ErrorVideoCore_ErrorBelowGL33;
}

View File

@ -7,6 +7,7 @@
#include "core/settings.h"
#include "video_core/pica.h"
#include "video_core/renderer_base.h"
#include "video_core/renderer_opengl/gl_vars.h"
#include "video_core/renderer_opengl/renderer_opengl.h"
#include "video_core/video_core.h"
@ -36,6 +37,8 @@ Core::System::ResultStatus Init(EmuWindow& emu_window, Memory::MemorySystem& mem
g_memory = &memory;
Pica::Init();
OpenGL::GLES = Settings::values.use_gles;
g_renderer = std::make_unique<OpenGL::RendererOpenGL>(emu_window);
Core::System::ResultStatus result = g_renderer->Init();