renderer_opengl: Support rendering Switch framebuffer.
This commit is contained in:
parent
236d463c52
commit
ee4691297f
|
@ -15,7 +15,10 @@ public:
|
||||||
/// Used to reference a framebuffer
|
/// Used to reference a framebuffer
|
||||||
enum kFramebuffer { kFramebuffer_VirtualXFB = 0, kFramebuffer_EFB, kFramebuffer_Texture };
|
enum kFramebuffer { kFramebuffer_VirtualXFB = 0, kFramebuffer_EFB, kFramebuffer_Texture };
|
||||||
|
|
||||||
/// Struct describing framebuffer metadata
|
/**
|
||||||
|
* Struct describing framebuffer metadata
|
||||||
|
* TODO(bunnei): This struct belongs in the GPU code, but we don't have a good place for it yet.
|
||||||
|
*/
|
||||||
struct FramebufferInfo {
|
struct FramebufferInfo {
|
||||||
enum class PixelFormat : u32 {
|
enum class PixelFormat : u32 {
|
||||||
ABGR8 = 1,
|
ABGR8 = 1,
|
||||||
|
@ -44,7 +47,7 @@ public:
|
||||||
virtual ~RendererBase() {}
|
virtual ~RendererBase() {}
|
||||||
|
|
||||||
/// Swap buffers (render frame)
|
/// Swap buffers (render frame)
|
||||||
virtual void SwapBuffers() = 0;
|
virtual void SwapBuffers(const FramebufferInfo& framebuffer_info) = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the emulator window to use for renderer
|
* Set the emulator window to use for renderer
|
||||||
|
|
|
@ -56,7 +56,9 @@ out vec4 color;
|
||||||
uniform sampler2D color_texture;
|
uniform sampler2D color_texture;
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
color = texture(color_texture, frag_tex_coord);
|
// Swap RGBA -> ABGR so we don't have to do this on the CPU. This needs to change if we have to
|
||||||
|
// support more framebuffer pixel formats.
|
||||||
|
color = texture(color_texture, frag_tex_coord).abgr;
|
||||||
}
|
}
|
||||||
)";
|
)";
|
||||||
|
|
||||||
|
@ -98,44 +100,20 @@ RendererOpenGL::RendererOpenGL() = default;
|
||||||
RendererOpenGL::~RendererOpenGL() = default;
|
RendererOpenGL::~RendererOpenGL() = default;
|
||||||
|
|
||||||
/// Swap buffers (render frame)
|
/// Swap buffers (render frame)
|
||||||
void RendererOpenGL::SwapBuffers() {
|
void RendererOpenGL::SwapBuffers(const FramebufferInfo& framebuffer_info) {
|
||||||
// Maintain the rasterizer's state as a priority
|
// Maintain the rasterizer's state as a priority
|
||||||
OpenGLState prev_state = OpenGLState::GetCurState();
|
OpenGLState prev_state = OpenGLState::GetCurState();
|
||||||
state.Apply();
|
state.Apply();
|
||||||
|
|
||||||
for (int i : {0, 1}) {
|
if (screen_info.texture.width != (GLsizei)framebuffer_info.width ||
|
||||||
const auto& framebuffer = GPU::g_regs.framebuffer_config[i];
|
screen_info.texture.height != (GLsizei)framebuffer_info.height ||
|
||||||
|
screen_info.texture.pixel_format != framebuffer_info.pixel_format) {
|
||||||
// Main LCD (0): 0x1ED02204, Sub LCD (1): 0x1ED02A04
|
|
||||||
u32 lcd_color_addr =
|
|
||||||
(i == 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
|
|
||||||
screen_infos[i].texture.width = 1;
|
|
||||||
screen_infos[i].texture.height = 1;
|
|
||||||
} else {
|
|
||||||
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.
|
// Reallocate texture if the framebuffer size has changed.
|
||||||
// This is expected to not happen very often and hence should not be a
|
// This is expected to not happen very often and hence should not be a
|
||||||
// performance problem.
|
// performance problem.
|
||||||
ConfigureFramebufferTexture(screen_infos[i].texture, framebuffer);
|
ConfigureFramebufferTexture(screen_info.texture, framebuffer_info);
|
||||||
}
|
|
||||||
LoadFBToScreenInfo(framebuffer, screen_infos[i]);
|
|
||||||
|
|
||||||
// Resize the texture in case the framebuffer size has changed
|
|
||||||
screen_infos[i].texture.width = framebuffer.width;
|
|
||||||
screen_infos[i].texture.height = framebuffer.height;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
LoadFBToScreenInfo(framebuffer_info, screen_info);
|
||||||
|
|
||||||
DrawScreens();
|
DrawScreens();
|
||||||
|
|
||||||
|
@ -270,57 +248,49 @@ static void MortonCopyPixels128(u32 width, u32 height, u32 bytes_per_pixel, u32
|
||||||
/**
|
/**
|
||||||
* Loads framebuffer from emulated memory into the active OpenGL texture.
|
* Loads framebuffer from emulated memory into the active OpenGL texture.
|
||||||
*/
|
*/
|
||||||
void RendererOpenGL::LoadFBToScreenInfo(const GPU::Regs::FramebufferConfig& framebuffer,
|
void RendererOpenGL::LoadFBToScreenInfo(const FramebufferInfo& framebuffer_info,
|
||||||
ScreenInfo& screen_info) {
|
ScreenInfo& screen_info) {
|
||||||
|
const u32 bpp{FramebufferInfo::BytesPerPixel(framebuffer_info.pixel_format)};
|
||||||
|
const u32 size_in_bytes{framebuffer_info.stride * framebuffer_info.height * bpp};
|
||||||
|
|
||||||
const PAddr framebuffer_addr =
|
MortonCopyPixels128(framebuffer_info.width, framebuffer_info.height, bpp, 4,
|
||||||
framebuffer.active_fb == 0 ? framebuffer.address_left1 : framebuffer.address_left2;
|
Memory::GetPointer(framebuffer_info.address), gl_framebuffer_data.data(),
|
||||||
|
true);
|
||||||
|
|
||||||
LOG_TRACE(Render_OpenGL, "0x%08x bytes from 0x%08x(%dx%d), fmt %x",
|
LOG_TRACE(Render_OpenGL, "0x%08x bytes from 0x%llx(%dx%d), fmt %x", size_in_bytes,
|
||||||
framebuffer.stride * framebuffer.height, framebuffer_addr, (int)framebuffer.width,
|
framebuffer_info.address, framebuffer_info.width, framebuffer_info.height,
|
||||||
(int)framebuffer.height, (int)framebuffer.format);
|
(int)framebuffer_info.format);
|
||||||
|
|
||||||
int bpp = GPU::Regs::BytesPerPixel(framebuffer.color_format);
|
|
||||||
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
|
// Ensure no bad interactions with GL_UNPACK_ALIGNMENT, which by default
|
||||||
// only allows rows to have a memory alignement of 4.
|
// only allows rows to have a memory alignement of 4.
|
||||||
ASSERT(pixel_stride % 4 == 0);
|
ASSERT(framebuffer_info.stride % 4 == 0);
|
||||||
|
|
||||||
if (!Rasterizer()->AccelerateDisplay(framebuffer, framebuffer_addr,
|
|
||||||
static_cast<u32>(pixel_stride), screen_info)) {
|
|
||||||
// Reset the screen info's display texture to its own permanent texture
|
// Reset the screen info's display texture to its own permanent texture
|
||||||
screen_info.display_texture = screen_info.texture.resource.handle;
|
screen_info.display_texture = screen_info.texture.resource.handle;
|
||||||
screen_info.display_texcoords = MathUtil::Rectangle<float>(0.f, 0.f, 1.f, 1.f);
|
screen_info.display_texcoords = MathUtil::Rectangle<float>(0.f, 0.f, 1.f, 1.f);
|
||||||
|
|
||||||
Memory::RasterizerFlushRegion(framebuffer_addr, framebuffer.stride * framebuffer.height);
|
Memory::RasterizerFlushRegion(framebuffer_info.address, size_in_bytes);
|
||||||
|
|
||||||
const u8* framebuffer_data = Memory::GetPhysicalPointer(framebuffer_addr);
|
|
||||||
|
|
||||||
state.texture_units[0].texture_2d = screen_info.texture.resource.handle;
|
state.texture_units[0].texture_2d = screen_info.texture.resource.handle;
|
||||||
state.Apply();
|
state.Apply();
|
||||||
|
|
||||||
glActiveTexture(GL_TEXTURE0);
|
glActiveTexture(GL_TEXTURE0);
|
||||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, (GLint)pixel_stride);
|
glPixelStorei(GL_UNPACK_ROW_LENGTH, (GLint)framebuffer_info.stride);
|
||||||
|
|
||||||
// Update existing texture
|
// Update existing texture
|
||||||
// TODO: Test what happens on hardware when you change the framebuffer dimensions so that
|
// TODO: Test what happens on hardware when you change the framebuffer dimensions so that
|
||||||
// they differ from the LCD resolution.
|
// they differ from the LCD resolution.
|
||||||
// TODO: Applications could theoretically crash Citra here by specifying too large
|
// TODO: Applications could theoretically crash Citra here by specifying too large
|
||||||
// framebuffer sizes. We should make sure that this cannot happen.
|
// framebuffer sizes. We should make sure that this cannot happen.
|
||||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, framebuffer.width, framebuffer.height,
|
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, framebuffer_info.width, framebuffer_info.height,
|
||||||
screen_info.texture.gl_format, screen_info.texture.gl_type,
|
screen_info.texture.gl_format, screen_info.texture.gl_type,
|
||||||
framebuffer_data);
|
gl_framebuffer_data.data());
|
||||||
|
|
||||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
|
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
|
||||||
|
|
||||||
state.texture_units[0].texture_2d = 0;
|
state.texture_units[0].texture_2d = 0;
|
||||||
state.Apply();
|
state.Apply();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fills active OpenGL texture with the given RGB color. Since the color is solid, the texture can
|
* Fills active OpenGL texture with the given RGB color. Since the color is solid, the texture can
|
||||||
|
@ -377,8 +347,7 @@ void RendererOpenGL::InitOpenGLObjects() {
|
||||||
glEnableVertexAttribArray(attrib_position);
|
glEnableVertexAttribArray(attrib_position);
|
||||||
glEnableVertexAttribArray(attrib_tex_coord);
|
glEnableVertexAttribArray(attrib_tex_coord);
|
||||||
|
|
||||||
// Allocate textures for each screen
|
// Allocate textures for the screen
|
||||||
for (auto& screen_info : screen_infos) {
|
|
||||||
screen_info.texture.resource.Create();
|
screen_info.texture.resource.Create();
|
||||||
|
|
||||||
// Allocation of storage is deferred until the first frame, when we
|
// Allocation of storage is deferred until the first frame, when we
|
||||||
|
@ -395,56 +364,26 @@ void RendererOpenGL::InitOpenGLObjects() {
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||||
|
|
||||||
screen_info.display_texture = screen_info.texture.resource.handle;
|
screen_info.display_texture = screen_info.texture.resource.handle;
|
||||||
}
|
|
||||||
|
|
||||||
state.texture_units[0].texture_2d = 0;
|
state.texture_units[0].texture_2d = 0;
|
||||||
state.Apply();
|
state.Apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
void RendererOpenGL::ConfigureFramebufferTexture(TextureInfo& texture,
|
void RendererOpenGL::ConfigureFramebufferTexture(TextureInfo& texture,
|
||||||
const GPU::Regs::FramebufferConfig& framebuffer) {
|
const FramebufferInfo& framebuffer_info) {
|
||||||
GPU::Regs::PixelFormat format = framebuffer.color_format;
|
|
||||||
|
texture.width = framebuffer_info.width;
|
||||||
|
texture.height = framebuffer_info.height;
|
||||||
|
|
||||||
GLint internal_format;
|
GLint internal_format;
|
||||||
|
switch (framebuffer_info.pixel_format) {
|
||||||
texture.format = format;
|
case FramebufferInfo::PixelFormat::ABGR8:
|
||||||
texture.width = framebuffer.width;
|
// Use RGBA8 and swap in the fragment shader
|
||||||
texture.height = framebuffer.height;
|
|
||||||
|
|
||||||
switch (format) {
|
|
||||||
case GPU::Regs::PixelFormat::RGBA8:
|
|
||||||
internal_format = GL_RGBA;
|
internal_format = GL_RGBA;
|
||||||
texture.gl_format = GL_RGBA;
|
texture.gl_format = GL_RGBA;
|
||||||
texture.gl_type = GL_UNSIGNED_INT_8_8_8_8;
|
texture.gl_type = GL_UNSIGNED_INT_8_8_8_8;
|
||||||
|
gl_framebuffer_data.resize(texture.width * texture.height * 4);
|
||||||
break;
|
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;
|
|
||||||
texture.gl_format = 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:
|
default:
|
||||||
UNIMPLEMENTED();
|
UNIMPLEMENTED();
|
||||||
}
|
}
|
||||||
|
@ -465,10 +404,10 @@ void RendererOpenGL::DrawSingleScreen(const ScreenInfo& screen_info, float x, fl
|
||||||
auto& texcoords = screen_info.display_texcoords;
|
auto& texcoords = screen_info.display_texcoords;
|
||||||
|
|
||||||
std::array<ScreenRectVertex, 4> vertices = {{
|
std::array<ScreenRectVertex, 4> vertices = {{
|
||||||
ScreenRectVertex(x, y, texcoords.top, texcoords.left),
|
ScreenRectVertex(x, y, texcoords.top, texcoords.right),
|
||||||
ScreenRectVertex(x + w, y, texcoords.bottom, texcoords.left),
|
ScreenRectVertex(x + w, y, texcoords.bottom, texcoords.right),
|
||||||
ScreenRectVertex(x, y + h, texcoords.top, texcoords.right),
|
ScreenRectVertex(x, y + h, texcoords.top, texcoords.left),
|
||||||
ScreenRectVertex(x + w, y + h, texcoords.bottom, texcoords.right),
|
ScreenRectVertex(x + w, y + h, texcoords.bottom, texcoords.left),
|
||||||
}};
|
}};
|
||||||
|
|
||||||
state.texture_units[0].texture_2d = screen_info.display_texture;
|
state.texture_units[0].texture_2d = screen_info.display_texture;
|
||||||
|
@ -500,8 +439,8 @@ void RendererOpenGL::DrawScreens() {
|
||||||
glActiveTexture(GL_TEXTURE0);
|
glActiveTexture(GL_TEXTURE0);
|
||||||
glUniform1i(uniform_color_texture, 0);
|
glUniform1i(uniform_color_texture, 0);
|
||||||
|
|
||||||
DrawSingleScreen(screen_infos[0], (float)screen.left, (float)screen.top,
|
DrawSingleScreen(screen_info, (float)screen.left, (float)screen.top, (float)screen.GetWidth(),
|
||||||
(float)screen.GetWidth(), (float)screen.GetHeight());
|
(float)screen.GetHeight());
|
||||||
|
|
||||||
m_current_frame++;
|
m_current_frame++;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <array>
|
#include <vector>
|
||||||
#include <glad/glad.h>
|
#include <glad/glad.h>
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
#include "common/math_util.h"
|
#include "common/math_util.h"
|
||||||
|
@ -20,9 +20,9 @@ struct TextureInfo {
|
||||||
OGLTexture resource;
|
OGLTexture resource;
|
||||||
GLsizei width;
|
GLsizei width;
|
||||||
GLsizei height;
|
GLsizei height;
|
||||||
GPU::Regs::PixelFormat format;
|
|
||||||
GLenum gl_format;
|
GLenum gl_format;
|
||||||
GLenum gl_type;
|
GLenum gl_type;
|
||||||
|
RendererBase::FramebufferInfo::PixelFormat pixel_format;
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Structure used for storing information about the display target for each 3DS screen
|
/// Structure used for storing information about the display target for each 3DS screen
|
||||||
|
@ -38,7 +38,7 @@ public:
|
||||||
~RendererOpenGL() override;
|
~RendererOpenGL() override;
|
||||||
|
|
||||||
/// Swap buffers (render frame)
|
/// Swap buffers (render frame)
|
||||||
void SwapBuffers() override;
|
void SwapBuffers(const FramebufferInfo& framebuffer_info) override;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the emulator window to use for renderer
|
* Set the emulator window to use for renderer
|
||||||
|
@ -55,13 +55,13 @@ public:
|
||||||
private:
|
private:
|
||||||
void InitOpenGLObjects();
|
void InitOpenGLObjects();
|
||||||
void ConfigureFramebufferTexture(TextureInfo& texture,
|
void ConfigureFramebufferTexture(TextureInfo& texture,
|
||||||
const GPU::Regs::FramebufferConfig& framebuffer);
|
const FramebufferInfo& framebuffer_info);
|
||||||
void DrawScreens();
|
void DrawScreens();
|
||||||
void DrawSingleScreen(const ScreenInfo& screen_info, float x, float y, float w, float h);
|
void DrawSingleScreen(const ScreenInfo& screen_info, float x, float y, float w, float h);
|
||||||
void UpdateFramerate();
|
void UpdateFramerate();
|
||||||
|
|
||||||
// Loads framebuffer from emulated memory into the display information structure
|
// Loads framebuffer from emulated memory into the display information structure
|
||||||
void LoadFBToScreenInfo(const GPU::Regs::FramebufferConfig& framebuffer,
|
void LoadFBToScreenInfo(const FramebufferInfo& framebuffer_info,
|
||||||
ScreenInfo& screen_info);
|
ScreenInfo& screen_info);
|
||||||
// Fills active OpenGL texture with the given RGB color.
|
// Fills active OpenGL texture with the given RGB color.
|
||||||
void LoadColorToActiveGLTexture(u8 color_r, u8 color_g, u8 color_b, const TextureInfo& texture);
|
void LoadColorToActiveGLTexture(u8 color_r, u8 color_g, u8 color_b, const TextureInfo& texture);
|
||||||
|
@ -75,8 +75,11 @@ private:
|
||||||
OGLBuffer vertex_buffer;
|
OGLBuffer vertex_buffer;
|
||||||
OGLShader shader;
|
OGLShader shader;
|
||||||
|
|
||||||
/// Display information for top and bottom screens respectively
|
/// Display information for Switch screen
|
||||||
std::array<ScreenInfo, 2> screen_infos;
|
ScreenInfo screen_info;
|
||||||
|
|
||||||
|
/// OpenGL framebuffer data
|
||||||
|
std::vector<u8> gl_framebuffer_data;
|
||||||
|
|
||||||
// Shader uniform location indices
|
// Shader uniform location indices
|
||||||
GLuint uniform_modelview_matrix;
|
GLuint uniform_modelview_matrix;
|
||||||
|
|
Loading…
Reference in New Issue