citra/src/video_core/renderer_opengl/renderer_opengl.cpp

938 lines
36 KiB
C++
Raw Normal View History

2014-04-09 01:04:25 +02:00
// Copyright 2014 Citra Emulator Project
2014-12-17 06:38:14 +01:00
// Licensed under GPLv2 or any later version
2014-04-09 01:04:25 +02:00
// Refer to the license.txt file included.
2014-04-06 22:55:39 +02:00
#include <algorithm>
#include <cstddef>
#include <cstdlib>
#include <memory>
#include <glad/glad.h>
#include "common/assert.h"
#include "common/bit_field.h"
#include "common/logging/log.h"
#include "core/core.h"
#include "core/core_timing.h"
#include "core/dumping/backend.h"
#include "core/frontend/emu_window.h"
#include "core/frontend/framebuffer_layout.h"
2014-05-17 22:50:33 +02:00
#include "core/hw/gpu.h"
#include "core/hw/hw.h"
#include "core/hw/lcd.h"
#include "core/memory.h"
#include "core/settings.h"
#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/post_processing_opengl.h"
#include "video_core/renderer_opengl/renderer_opengl.h"
#include "video_core/video_core.h"
namespace Frontend {
struct Frame {
GLuint index;
GLsync render_sync;
GLsync present_sync;
};
} // namespace Frontend
namespace OpenGL {
class OGLTextureMailbox : public Frontend::TextureMailbox {
public:
Frontend::Frame render_tex = {0, 0, 0}, present_tex = {1, 0, 0}, off_tex = {2, 0, 0};
bool swapped = false;
std::mutex swap_mutex{};
OGLTextureMailbox() = default;
~OGLTextureMailbox() = default;
Frontend::Frame& GetRenderFrame() {
return render_tex;
}
void RenderComplete() {
std::scoped_lock lock(swap_mutex);
swapped = true;
std::swap(render_tex, off_tex);
}
Frontend::Frame& GetPresentationFrame() {
return present_tex;
}
void PresentationComplete() {
std::scoped_lock lock(swap_mutex);
if (swapped) {
swapped = false;
std::swap(present_tex, off_tex);
}
}
};
static const char vertex_shader[] = R"(
in vec2 vert_position;
in vec2 vert_tex_coord;
out vec2 frag_tex_coord;
// This is a truncated 3x3 matrix for 2D transformations:
// The upper-left 2x2 submatrix performs scaling/rotation/mirroring.
// The third column performs translation.
// The third row could be used for projection, which we don't need in 2D. It hence is assumed to
// implicitly be [0, 0, 1]
uniform mat3x2 modelview_matrix;
void main() {
// Multiply input position by the rotscale part of the matrix and then manually translate by
// the last column. This is equivalent to using a full 3x3 matrix and expanding the vector
// to `vec3(vert_position.xy, 1.0)`
gl_Position = vec4(mat2(modelview_matrix) * vert_position + modelview_matrix[2], 0.0, 1.0);
frag_tex_coord = vert_tex_coord;
}
)";
static const char fragment_shader[] = R"(
in vec2 frag_tex_coord;
layout(location = 0) out vec4 color;
uniform vec4 i_resolution;
uniform vec4 o_resolution;
uniform int layer;
uniform sampler2D color_texture;
void main() {
color = texture(color_texture, frag_tex_coord);
}
)";
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.
*/
struct ScreenRectVertex {
ScreenRectVertex(GLfloat x, GLfloat y, GLfloat u, GLfloat v) {
position[0] = x;
position[1] = y;
tex_coord[0] = u;
tex_coord[1] = v;
}
GLfloat position[2];
GLfloat tex_coord[2];
};
/**
* Defines a 1:1 pixel ortographic projection matrix with (0,0) on the top-left
* corner and (width, height) on the lower-bottom.
*
* The projection part of the matrix is trivial, hence these operations are represented
* by a 3x2 matrix.
*/
static std::array<GLfloat, 3 * 2> MakeOrthographicMatrix(const float width, const float height) {
std::array<GLfloat, 3 * 2> matrix; // Laid out in column-major order
// clang-format off
matrix[0] = 2.f / width; matrix[2] = 0.f; matrix[4] = -1.f;
matrix[1] = 0.f; matrix[3] = -2.f / height; matrix[5] = 1.f;
// Last matrix row is implicitly assumed to be [0, 0, 1].
// clang-format on
return matrix;
}
RendererOpenGL::RendererOpenGL(Frontend::EmuWindow& window) : RendererBase{window} {
window.mailbox = std::make_unique<OGLTextureMailbox>();
}
RendererOpenGL::~RendererOpenGL() = default;
2014-04-06 22:55:39 +02:00
/// Swap buffers (render frame)
void RendererOpenGL::SwapBuffers() {
2015-05-19 06:21:33 +02:00
// Maintain the rasterizer's state as a priority
OpenGLState prev_state = OpenGLState::GetCurState();
state.Apply();
for (int i : {0, 1, 2}) {
int fb_id = i == 2 ? 1 : 0;
const auto& framebuffer = GPU::g_regs.framebuffer_config[fb_id];
// Main LCD (0): 0x1ED02204, Sub LCD (1): 0x1ED02A04
u32 lcd_color_addr =
(fb_id == 0) ? LCD_REG_INDEX(color_fill_top) : LCD_REG_INDEX(color_fill_bottom);
lcd_color_addr = HW::VADDR_LCD + 4 * lcd_color_addr;
LCD::Regs::ColorFill color_fill = {0};
LCD::Read(color_fill.raw, lcd_color_addr);
if (color_fill.is_enabled) {
LoadColorToActiveGLTexture(color_fill.color_r, color_fill.color_g, color_fill.color_b,
screen_infos[i].texture);
// Resize the texture in case the framebuffer size has changed
2016-04-17 00:57:57 +02:00
screen_infos[i].texture.width = 1;
screen_infos[i].texture.height = 1;
} else {
2016-04-17 00:57:57 +02:00
if (screen_infos[i].texture.width != (GLsizei)framebuffer.width ||
screen_infos[i].texture.height != (GLsizei)framebuffer.height ||
screen_infos[i].texture.format != framebuffer.color_format) {
// Reallocate texture if the framebuffer size has changed.
// This is expected to not happen very often and hence should not be a
// performance problem.
2016-04-17 00:57:57 +02:00
ConfigureFramebufferTexture(screen_infos[i].texture, framebuffer);
}
LoadFBToScreenInfo(framebuffer, screen_infos[i], i == 1);
// Resize the texture in case the framebuffer size has changed
2016-04-17 00:57:57 +02:00
screen_infos[i].texture.width = framebuffer.width;
screen_infos[i].texture.height = framebuffer.height;
}
}
2014-04-06 22:55:39 +02:00
if (VideoCore::g_renderer_screenshot_requested) {
// Draw this frame to the screenshot framebuffer
screenshot_framebuffer.Create();
GLuint old_read_fb = state.draw.read_framebuffer;
GLuint old_draw_fb = state.draw.draw_framebuffer;
state.draw.read_framebuffer = state.draw.draw_framebuffer = screenshot_framebuffer.handle;
state.Apply();
Layout::FramebufferLayout layout{VideoCore::g_screenshot_framebuffer_layout};
GLuint renderbuffer;
glGenRenderbuffers(1, &renderbuffer);
glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer);
glRenderbufferStorage(GL_RENDERBUFFER, GL_RGB8, layout.width, layout.height);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER,
renderbuffer);
DrawScreens(layout);
glReadPixels(0, 0, layout.width, layout.height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV,
VideoCore::g_screenshot_bits);
screenshot_framebuffer.Release();
state.draw.read_framebuffer = old_read_fb;
state.draw.draw_framebuffer = old_draw_fb;
state.Apply();
glDeleteRenderbuffers(1, &renderbuffer);
VideoCore::g_screenshot_complete_callback();
VideoCore::g_renderer_screenshot_requested = false;
}
if (cleanup_video_dumping.exchange(false)) {
ReleaseVideoDumpingGLObjects();
}
if (Core::System::GetInstance().VideoDumper().IsDumping()) {
if (prepare_video_dumping.exchange(false)) {
InitVideoDumpingGLObjects();
}
const auto& layout = Core::System::GetInstance().VideoDumper().GetLayout();
glBindFramebuffer(GL_READ_FRAMEBUFFER, frame_dumping_framebuffer.handle);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, frame_dumping_framebuffer.handle);
DrawScreens(layout);
glBindBuffer(GL_PIXEL_PACK_BUFFER, frame_dumping_pbos[current_pbo].handle);
glReadPixels(0, 0, layout.width, layout.height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, 0);
glBindBuffer(GL_PIXEL_PACK_BUFFER, frame_dumping_pbos[next_pbo].handle);
GLubyte* pixels = static_cast<GLubyte*>(glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY));
VideoDumper::VideoFrame frame_data{layout.width, layout.height, pixels};
Core::System::GetInstance().VideoDumper().AddVideoFrame(frame_data);
glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
current_pbo = (current_pbo + 1) % 2;
next_pbo = (current_pbo + 1) % 2;
}
const auto& layout = render_window.GetFramebufferLayout();
auto& frame = render_window.mailbox->GetRenderFrame();
auto& presentation = presentation_textures[frame.index];
// Clean up sync objects before drawing
// INTEL driver workaround. We can't delete the previous render sync object until we are sure
// that the presentation is done
if (frame.present_sync) {
glClientWaitSync(frame.present_sync, 0, GL_TIMEOUT_IGNORED);
}
// delete the draw fence if the frame wasn't presented
if (frame.render_sync) {
glDeleteSync(frame.render_sync);
frame.render_sync = 0;
}
// wait for the presentation to be done
if (frame.present_sync) {
glWaitSync(frame.present_sync, 0, GL_TIMEOUT_IGNORED);
glDeleteSync(frame.present_sync);
frame.present_sync = 0;
}
// Recreate the presentation texture if the size of the window has changed
if (layout.width != presentation.width || layout.height != presentation.height) {
presentation.width = layout.width;
presentation.height = layout.height;
presentation.texture.Release();
presentation.texture.Create();
state.texture_units[0].texture_2d = presentation.texture.handle;
state.Apply();
glActiveTexture(GL_TEXTURE0);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, layout.width, layout.height, 0, GL_RGBA,
GL_UNSIGNED_BYTE, 0);
state.texture_units[0].texture_2d = 0;
state.Apply();
}
GLuint render_texture = presentation.texture.handle;
state.draw.draw_framebuffer = draw_framebuffer.handle;
state.Apply();
glFramebufferTexture(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, render_texture, 0);
GLenum DrawBuffers[1] = {GL_COLOR_ATTACHMENT0};
glDrawBuffers(1, DrawBuffers); // "1" is the size of DrawBuffers
DrawScreens(layout);
// Create a fence for the frontend to wait on and swap this frame to OffTex
frame.render_sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
glFlush();
render_window.mailbox->RenderComplete();
m_current_frame++;
2014-04-06 22:55:39 +02:00
Core::System::GetInstance().perf_stats->EndSystemFrame();
render_window.PollEvents();
2018-10-27 21:53:20 +02:00
Core::System::GetInstance().frame_limiter.DoFrameLimiting(
Core::System::GetInstance().CoreTiming().GetGlobalTimeUs());
Core::System::GetInstance().perf_stats->BeginSystemFrame();
2015-05-19 06:21:33 +02:00
prev_state.Apply();
RefreshRasterizerSetting();
if (Pica::g_debug_context && Pica::g_debug_context->recorder) {
Pica::g_debug_context->recorder->FrameFinished();
}
}
2014-04-06 22:55:39 +02:00
/**
* Loads framebuffer from emulated memory into the active OpenGL texture.
2014-04-06 22:55:39 +02:00
*/
2016-04-17 00:57:57 +02:00
void RendererOpenGL::LoadFBToScreenInfo(const GPU::Regs::FramebufferConfig& framebuffer,
ScreenInfo& screen_info, bool right_eye) {
if (framebuffer.address_right1 == 0 || framebuffer.address_right2 == 0)
right_eye = false;
const PAddr framebuffer_addr =
framebuffer.active_fb == 0
? (!right_eye ? framebuffer.address_left1 : framebuffer.address_right1)
: (!right_eye ? framebuffer.address_left2 : framebuffer.address_right2);
2018-06-29 13:18:07 +02:00
LOG_TRACE(Render_OpenGL, "0x{:08x} bytes from 0x{:08x}({}x{}), fmt {:x}",
2018-06-29 15:56:12 +02:00
framebuffer.stride * framebuffer.height, framebuffer_addr, (int)framebuffer.width,
(int)framebuffer.height, (int)framebuffer.format);
int bpp = GPU::Regs::BytesPerPixel(framebuffer.color_format);
std::size_t pixel_stride = framebuffer.stride / bpp;
// OpenGL only supports specifying a stride in units of pixels, not bytes, unfortunately
ASSERT(pixel_stride * bpp == framebuffer.stride);
// Ensure no bad interactions with GL_UNPACK_ALIGNMENT, which by default
// only allows rows to have a memory alignement of 4.
ASSERT(pixel_stride % 4 == 0);
if (!Rasterizer()->AccelerateDisplay(framebuffer, framebuffer_addr,
static_cast<u32>(pixel_stride), screen_info)) {
2016-04-17 00:57:57 +02:00
// Reset the screen info's display texture to its own permanent texture
screen_info.display_texture = screen_info.texture.resource.handle;
screen_info.display_texcoords = Common::Rectangle<float>(0.f, 0.f, 1.f, 1.f);
2016-04-17 00:57:57 +02:00
Memory::RasterizerFlushRegion(framebuffer_addr, framebuffer.stride * framebuffer.height);
const u8* framebuffer_data = VideoCore::g_memory->GetPhysicalPointer(framebuffer_addr);
2016-04-17 00:57:57 +02:00
state.texture_units[0].texture_2d = screen_info.texture.resource.handle;
state.Apply();
2015-05-30 02:53:50 +02:00
2016-04-17 00:57:57 +02:00
glActiveTexture(GL_TEXTURE0);
glPixelStorei(GL_UNPACK_ROW_LENGTH, (GLint)pixel_stride);
// Update existing texture
// TODO: Test what happens on hardware when you change the framebuffer dimensions so that
// they differ from the LCD resolution.
2016-04-17 00:57:57 +02:00
// TODO: Applications could theoretically crash Citra here by specifying too large
// framebuffer sizes. We should make sure that this cannot happen.
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, framebuffer.width, framebuffer.height,
screen_info.texture.gl_format, screen_info.texture.gl_type,
framebuffer_data);
2016-04-17 00:57:57 +02:00
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
state.texture_units[0].texture_2d = 0;
state.Apply();
}
}
/**
* Fills active OpenGL texture with the given RGB color. Since the color is solid, the texture can
* be 1x1 but will stretch across whatever it's rendered on.
*/
void RendererOpenGL::LoadColorToActiveGLTexture(u8 color_r, u8 color_g, u8 color_b,
const TextureInfo& texture) {
2016-04-17 00:57:57 +02:00
state.texture_units[0].texture_2d = texture.resource.handle;
2015-05-19 06:21:33 +02:00
state.Apply();
2015-05-19 06:21:33 +02:00
glActiveTexture(GL_TEXTURE0);
u8 framebuffer_data[3] = {color_r, color_g, color_b};
// Update existing texture
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, framebuffer_data);
2016-04-17 00:57:57 +02:00
state.texture_units[0].texture_2d = 0;
state.Apply();
}
2014-04-06 22:55:39 +02:00
/**
* Initializes the OpenGL state and creates persistent objects.
*/
void RendererOpenGL::InitOpenGLObjects() {
glClearColor(Settings::values.bg_red, Settings::values.bg_green, Settings::values.bg_blue,
0.0f);
filter_sampler.Create();
ReloadSampler();
ReloadShader();
// Generate VBO handle for drawing
2016-04-17 00:57:57 +02:00
vertex_buffer.Create();
// Generate VAO
2016-04-17 00:57:57 +02:00
vertex_array.Create();
2015-05-19 06:21:33 +02:00
2016-04-17 00:57:57 +02:00
state.draw.vertex_array = vertex_array.handle;
state.draw.vertex_buffer = vertex_buffer.handle;
state.draw.uniform_buffer = 0;
2015-05-19 06:21:33 +02:00
state.Apply();
// Attach vertex data to VAO
glBufferData(GL_ARRAY_BUFFER, sizeof(ScreenRectVertex) * 4, nullptr, GL_STREAM_DRAW);
glVertexAttribPointer(attrib_position, 2, GL_FLOAT, GL_FALSE, sizeof(ScreenRectVertex),
(GLvoid*)offsetof(ScreenRectVertex, position));
glVertexAttribPointer(attrib_tex_coord, 2, GL_FLOAT, GL_FALSE, sizeof(ScreenRectVertex),
(GLvoid*)offsetof(ScreenRectVertex, tex_coord));
glEnableVertexAttribArray(attrib_position);
glEnableVertexAttribArray(attrib_tex_coord);
// Allocate textures for each screen
2016-04-17 00:57:57 +02:00
for (auto& screen_info : screen_infos) {
screen_info.texture.resource.Create();
// Allocation of storage is deferred until the first frame, when we
// know the framebuffer size.
2016-04-17 00:57:57 +02:00
state.texture_units[0].texture_2d = screen_info.texture.resource.handle;
2015-05-19 06:21:33 +02:00
state.Apply();
glActiveTexture(GL_TEXTURE0);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
2016-04-17 00:57:57 +02:00
screen_info.display_texture = screen_info.texture.resource.handle;
}
2015-05-19 06:21:33 +02:00
draw_framebuffer.Create();
presentation_framebuffer.Create();
presentation_textures[0].texture.Create();
presentation_textures[1].texture.Create();
presentation_textures[2].texture.Create();
2015-05-30 02:53:50 +02:00
state.texture_units[0].texture_2d = 0;
state.Apply();
2014-04-06 22:55:39 +02:00
}
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;
GLint internal_format;
texture.format = format;
texture.width = framebuffer.width;
texture.height = framebuffer.height;
switch (format) {
case GPU::Regs::PixelFormat::RGBA8:
internal_format = GL_RGBA;
texture.gl_format = GL_RGBA;
texture.gl_type = GLES ? GL_UNSIGNED_BYTE : GL_UNSIGNED_INT_8_8_8_8;
break;
case GPU::Regs::PixelFormat::RGB8:
// This pixel format uses BGR since GL_UNSIGNED_BYTE specifies byte-order, unlike every
// specific OpenGL type used in this function using native-endian (that is, little-endian
// mostly everywhere) for words or half-words.
// TODO: check how those behave on big-endian processors.
internal_format = GL_RGB;
// GLES Dosen't support BGR , Use RGB instead
texture.gl_format = GLES ? GL_RGB : GL_BGR;
texture.gl_type = GL_UNSIGNED_BYTE;
break;
case GPU::Regs::PixelFormat::RGB565:
internal_format = GL_RGB;
texture.gl_format = GL_RGB;
texture.gl_type = GL_UNSIGNED_SHORT_5_6_5;
break;
case GPU::Regs::PixelFormat::RGB5A1:
internal_format = GL_RGBA;
texture.gl_format = GL_RGBA;
texture.gl_type = GL_UNSIGNED_SHORT_5_5_5_1;
break;
case GPU::Regs::PixelFormat::RGBA4:
internal_format = GL_RGBA;
texture.gl_format = GL_RGBA;
texture.gl_type = GL_UNSIGNED_SHORT_4_4_4_4;
break;
default:
UNIMPLEMENTED();
}
2016-04-17 00:57:57 +02:00
state.texture_units[0].texture_2d = texture.resource.handle;
2015-05-19 06:21:33 +02:00
state.Apply();
glActiveTexture(GL_TEXTURE0);
glTexImage2D(GL_TEXTURE_2D, 0, internal_format, texture.width, texture.height, 0,
texture.gl_format, texture.gl_type, nullptr);
2016-04-17 00:57:57 +02:00
state.texture_units[0].texture_2d = 0;
state.Apply();
}
/**
* Draws a single texture to the emulator window, rotating the texture to correct for the 3DS's LCD
* rotation.
*/
void RendererOpenGL::DrawSingleScreenRotated(const ScreenInfo& screen_info, float x, float y,
float w, float h) {
const auto& texcoords = screen_info.display_texcoords;
2016-04-17 00:57:57 +02:00
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);
2016-04-17 00:57:57 +02:00
state.texture_units[0].texture_2d = screen_info.display_texture;
state.texture_units[0].sampler = filter_sampler.handle;
2015-05-19 06:21:33 +02:00
state.Apply();
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices.data());
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
2016-04-17 00:57:57 +02:00
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;
2016-04-17 00:57:57 +02:00
state.Apply();
}
/**
* Draws the emulated screens to the emulator window.
*/
void RendererOpenGL::DrawScreens(const Layout::FramebufferLayout& layout) {
if (VideoCore::g_renderer_bg_color_update_requested.exchange(false)) {
// Update background color before drawing
glClearColor(Settings::values.bg_red, Settings::values.bg_green, Settings::values.bg_blue,
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;
2015-03-07 23:21:19 +01:00
glViewport(0, 0, layout.width, layout.height);
glClear(GL_COLOR_BUFFER_BIT);
// Set projection matrix
std::array<GLfloat, 3 * 2> ortho_matrix =
MakeOrthographicMatrix((float)layout.width, (float)layout.height);
glUniformMatrix3x2fv(uniform_modelview_matrix, 1, GL_FALSE, ortho_matrix.data());
// Bind texture in Texture Unit 0
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.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 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.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 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());
}
}
2014-04-06 22:55:39 +02:00
}
void RendererOpenGL::Present() {
const auto& layout = render_window.GetFramebufferLayout();
auto& frame = render_window.mailbox->GetPresentationFrame();
const auto& presentation = presentation_textures[frame.index];
const GLuint texture_handle = presentation.texture.handle;
glWaitSync(frame.render_sync, 0, GL_TIMEOUT_IGNORED);
// INTEL workaround.
// Normally we could just delete the draw fence here, but due to driver bugs, we can just delete
// it on the emulation thread without too much penalty
// glDeleteSync(frame.render_sync);
// frame.render_sync = 0;
glClearColor(Settings::values.bg_red, Settings::values.bg_green, Settings::values.bg_blue,
0.0f);
glClear(GL_COLOR_BUFFER_BIT);
glBindFramebuffer(GL_READ_FRAMEBUFFER, presentation_framebuffer.handle);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture_handle);
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture_handle,
0);
glBlitFramebuffer(0, 0, presentation.width, presentation.height, 0, 0, layout.width,
layout.height, GL_COLOR_BUFFER_BIT, GL_LINEAR);
/* insert fence for the main thread to block on */
frame.present_sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
glFlush();
render_window.mailbox->PresentationComplete();
}
2014-04-06 22:55:39 +02:00
/// Updates the framerate
void RendererOpenGL::UpdateFramerate() {}
2014-04-06 22:55:39 +02:00
void RendererOpenGL::PrepareVideoDumping() {
prepare_video_dumping = true;
}
void RendererOpenGL::CleanupVideoDumping() {
cleanup_video_dumping = true;
}
void RendererOpenGL::InitVideoDumpingGLObjects() {
const auto& layout = Core::System::GetInstance().VideoDumper().GetLayout();
frame_dumping_framebuffer.Create();
glGenRenderbuffers(1, &frame_dumping_renderbuffer);
glBindRenderbuffer(GL_RENDERBUFFER, frame_dumping_renderbuffer);
glRenderbufferStorage(GL_RENDERBUFFER, GL_RGB8, layout.width, layout.height);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, frame_dumping_framebuffer.handle);
glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER,
frame_dumping_renderbuffer);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
for (auto& buffer : frame_dumping_pbos) {
buffer.Create();
glBindBuffer(GL_PIXEL_PACK_BUFFER, buffer.handle);
glBufferData(GL_PIXEL_PACK_BUFFER, layout.width * layout.height * 4, nullptr,
GL_STREAM_READ);
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
}
}
void RendererOpenGL::ReleaseVideoDumpingGLObjects() {
frame_dumping_framebuffer.Release();
glDeleteRenderbuffers(1, &frame_dumping_renderbuffer);
for (auto& buffer : frame_dumping_pbos) {
buffer.Release();
}
}
static const char* GetSource(GLenum source) {
#define RET(s) \
case GL_DEBUG_SOURCE_##s: \
return #s
switch (source) {
RET(API);
RET(WINDOW_SYSTEM);
RET(SHADER_COMPILER);
RET(THIRD_PARTY);
RET(APPLICATION);
RET(OTHER);
default:
UNREACHABLE();
}
#undef RET
}
static const char* GetType(GLenum type) {
#define RET(t) \
case GL_DEBUG_TYPE_##t: \
return #t
switch (type) {
RET(ERROR);
RET(DEPRECATED_BEHAVIOR);
RET(UNDEFINED_BEHAVIOR);
RET(PORTABILITY);
RET(PERFORMANCE);
RET(OTHER);
RET(MARKER);
default:
UNREACHABLE();
}
#undef RET
}
static void APIENTRY DebugHandler(GLenum source, GLenum type, GLuint id, GLenum severity,
GLsizei length, const GLchar* message, const void* user_param) {
Log::Level level;
switch (severity) {
case GL_DEBUG_SEVERITY_HIGH:
level = Log::Level::Critical;
break;
case GL_DEBUG_SEVERITY_MEDIUM:
level = Log::Level::Warning;
break;
case GL_DEBUG_SEVERITY_NOTIFICATION:
case GL_DEBUG_SEVERITY_LOW:
level = Log::Level::Debug;
break;
}
2018-06-29 15:56:12 +02:00
LOG_GENERIC(Log::Class::Render_OpenGL, level, "{} {} {}: {}", GetSource(source), GetType(type),
id, message);
}
2014-04-06 22:55:39 +02:00
/// Initialize the renderer
Core::System::ResultStatus RendererOpenGL::Init() {
if (!gladLoadGL()) {
return Core::System::ResultStatus::ErrorVideoCore_ErrorBelowGL33;
}
2014-04-06 22:55:39 +02:00
if (GLAD_GL_KHR_debug) {
glEnable(GL_DEBUG_OUTPUT);
glDebugMessageCallback(DebugHandler, nullptr);
}
const char* gl_version{reinterpret_cast<char const*>(glGetString(GL_VERSION))};
const char* gpu_vendor{reinterpret_cast<char const*>(glGetString(GL_VENDOR))};
const char* gpu_model{reinterpret_cast<char const*>(glGetString(GL_RENDERER))};
2018-06-29 13:18:07 +02:00
LOG_INFO(Render_OpenGL, "GL_VERSION: {}", gl_version);
LOG_INFO(Render_OpenGL, "GL_VENDOR: {}", gpu_vendor);
LOG_INFO(Render_OpenGL, "GL_RENDERER: {}", gpu_model);
2019-03-09 18:46:37 +01:00
auto& telemetry_session = Core::System::GetInstance().TelemetrySession();
telemetry_session.AddField(Telemetry::FieldType::UserSystem, "GPU_Vendor", gpu_vendor);
telemetry_session.AddField(Telemetry::FieldType::UserSystem, "GPU_Model", gpu_model);
telemetry_session.AddField(Telemetry::FieldType::UserSystem, "GPU_OpenGL_Version", gl_version);
if (!strcmp(gpu_vendor, "GDI Generic")) {
return Core::System::ResultStatus::ErrorVideoCore_ErrorGenericDrivers;
}
if (!(GLAD_GL_VERSION_3_3 || GLAD_GL_ES_VERSION_3_1)) {
return Core::System::ResultStatus::ErrorVideoCore_ErrorBelowGL33;
}
InitOpenGLObjects();
RefreshRasterizerSetting();
return Core::System::ResultStatus::Success;
2014-04-06 22:55:39 +02:00
}
/// Shutdown the renderer
void RendererOpenGL::ShutDown() {}
} // namespace OpenGL