mirror of
				https://git.zaroz.cloud/nintendo-back-up/yuzu/yuzu.git
				synced 2025-05-12 00:45:25 +00:00 
			
		
		
		
	
						commit
						e4905143c8
					
				| @ -42,12 +42,9 @@ EmuWindow_GLFW::EmuWindow_GLFW() { | |||||||
|     } |     } | ||||||
|     glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); |     glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); | ||||||
|     glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2); |     glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2); | ||||||
| 	 |  | ||||||
| #if EMU_PLATFORM == PLATFORM_MACOSX |  | ||||||
|     // GLFW on OSX requires these window hints to be set to create a 3.2+ GL context.
 |     // GLFW on OSX requires these window hints to be set to create a 3.2+ GL context.
 | ||||||
|     glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); |     glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); | ||||||
|     glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); |     glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); | ||||||
| #endif |  | ||||||
| 	 | 	 | ||||||
|     m_render_window = glfwCreateWindow(VideoCore::kScreenTopWidth,  |     m_render_window = glfwCreateWindow(VideoCore::kScreenTopWidth,  | ||||||
|         (VideoCore::kScreenTopHeight + VideoCore::kScreenBottomHeight),  |         (VideoCore::kScreenTopHeight + VideoCore::kScreenBottomHeight),  | ||||||
|  | |||||||
| @ -113,10 +113,10 @@ GRenderWindow::GRenderWindow(QWidget* parent) : QWidget(parent), emu_thread(this | |||||||
| 
 | 
 | ||||||
|     // TODO: One of these flags might be interesting: WA_OpaquePaintEvent, WA_NoBackground, WA_DontShowOnScreen, WA_DeleteOnClose
 |     // TODO: One of these flags might be interesting: WA_OpaquePaintEvent, WA_NoBackground, WA_DontShowOnScreen, WA_DeleteOnClose
 | ||||||
|     QGLFormat fmt; |     QGLFormat fmt; | ||||||
|     fmt.setProfile(QGLFormat::CoreProfile); |  | ||||||
|     fmt.setVersion(3,2); |     fmt.setVersion(3,2); | ||||||
|     fmt.setSampleBuffers(true); |     fmt.setProfile(QGLFormat::CoreProfile); | ||||||
|     fmt.setSamples(4); |     // Requests a forward-compatible context, which is required to get a 3.2+ context on OS X
 | ||||||
|  |     fmt.setOption(QGL::NoDeprecatedFunctions); | ||||||
|      |      | ||||||
|     child = new GGLWidgetInternal(fmt, this); |     child = new GGLWidgetInternal(fmt, this); | ||||||
|     QBoxLayout* layout = new QHBoxLayout(this); |     QBoxLayout* layout = new QHBoxLayout(this); | ||||||
|  | |||||||
| @ -89,7 +89,7 @@ inline void Write(u32 addr, const T data) { | |||||||
|                     } source_color = { 0, 0, 0, 0 }; |                     } source_color = { 0, 0, 0, 0 }; | ||||||
| 
 | 
 | ||||||
|                     switch (config.input_format) { |                     switch (config.input_format) { | ||||||
|                     case Regs::FramebufferFormat::RGBA8: |                     case Regs::PixelFormat::RGBA8: | ||||||
|                     { |                     { | ||||||
|                         // TODO: Most likely got the component order messed up.
 |                         // TODO: Most likely got the component order messed up.
 | ||||||
|                         u8* srcptr = source_pointer + x * 4 + y * config.input_width * 4; |                         u8* srcptr = source_pointer + x * 4 + y * config.input_width * 4; | ||||||
| @ -106,7 +106,7 @@ inline void Write(u32 addr, const T data) { | |||||||
|                     } |                     } | ||||||
| 
 | 
 | ||||||
|                     switch (config.output_format) { |                     switch (config.output_format) { | ||||||
|                     /*case Regs::FramebufferFormat::RGBA8:
 |                     /*case Regs::PixelFormat::RGBA8:
 | ||||||
|                     { |                     { | ||||||
|                         // TODO: Untested
 |                         // TODO: Untested
 | ||||||
|                         u8* dstptr = (u32*)(dest_pointer + x * 4 + y * config.output_width * 4); |                         u8* dstptr = (u32*)(dest_pointer + x * 4 + y * config.output_width * 4); | ||||||
| @ -117,7 +117,7 @@ inline void Write(u32 addr, const T data) { | |||||||
|                         break; |                         break; | ||||||
|                     }*/ |                     }*/ | ||||||
| 
 | 
 | ||||||
|                     case Regs::FramebufferFormat::RGB8: |                     case Regs::PixelFormat::RGB8: | ||||||
|                     { |                     { | ||||||
|                         // TODO: Most likely got the component order messed up.
 |                         // TODO: Most likely got the component order messed up.
 | ||||||
|                         u8* dstptr = dest_pointer + x * 3 + y * config.output_width * 3; |                         u8* dstptr = dest_pointer + x * 3 + y * config.output_width * 3; | ||||||
| @ -236,13 +236,13 @@ void Init() { | |||||||
|     framebuffer_top.width = 240; |     framebuffer_top.width = 240; | ||||||
|     framebuffer_top.height = 400; |     framebuffer_top.height = 400; | ||||||
|     framebuffer_top.stride = 3 * 240; |     framebuffer_top.stride = 3 * 240; | ||||||
|     framebuffer_top.color_format = Regs::FramebufferFormat::RGB8; |     framebuffer_top.color_format = Regs::PixelFormat::RGB8; | ||||||
|     framebuffer_top.active_fb = 0; |     framebuffer_top.active_fb = 0; | ||||||
| 
 | 
 | ||||||
|     framebuffer_sub.width = 240; |     framebuffer_sub.width = 240; | ||||||
|     framebuffer_sub.height = 320; |     framebuffer_sub.height = 320; | ||||||
|     framebuffer_sub.stride = 3 * 240; |     framebuffer_sub.stride = 3 * 240; | ||||||
|     framebuffer_sub.color_format = Regs::FramebufferFormat::RGB8; |     framebuffer_sub.color_format = Regs::PixelFormat::RGB8; | ||||||
|     framebuffer_sub.active_fb = 0; |     framebuffer_sub.active_fb = 0; | ||||||
| 
 | 
 | ||||||
|     NOTICE_LOG(GPU, "initialized OK"); |     NOTICE_LOG(GPU, "initialized OK"); | ||||||
|  | |||||||
| @ -56,7 +56,7 @@ struct Regs { | |||||||
|                   "Structure size and register block length don't match") |                   "Structure size and register block length don't match") | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
|     enum class FramebufferFormat : u32 { |     enum class PixelFormat : u32 { | ||||||
|         RGBA8  = 0, |         RGBA8  = 0, | ||||||
|         RGB8   = 1, |         RGB8   = 1, | ||||||
|         RGB565 = 2, |         RGB565 = 2, | ||||||
| @ -84,9 +84,7 @@ struct Regs { | |||||||
| 
 | 
 | ||||||
|     INSERT_PADDING_WORDS(0x10b); |     INSERT_PADDING_WORDS(0x10b); | ||||||
| 
 | 
 | ||||||
|     struct { |     struct FramebufferConfig { | ||||||
|         using Format = Regs::FramebufferFormat; |  | ||||||
| 
 |  | ||||||
|         union { |         union { | ||||||
|             u32 size; |             u32 size; | ||||||
| 
 | 
 | ||||||
| @ -102,7 +100,7 @@ struct Regs { | |||||||
|         union { |         union { | ||||||
|             u32 format; |             u32 format; | ||||||
| 
 | 
 | ||||||
|             BitField< 0, 3, Format> color_format; |             BitField< 0, 3, PixelFormat> color_format; | ||||||
|         }; |         }; | ||||||
| 
 | 
 | ||||||
|         INSERT_PADDING_WORDS(0x1); |         INSERT_PADDING_WORDS(0x1); | ||||||
| @ -130,8 +128,6 @@ struct Regs { | |||||||
|     INSERT_PADDING_WORDS(0x169); |     INSERT_PADDING_WORDS(0x169); | ||||||
| 
 | 
 | ||||||
|     struct { |     struct { | ||||||
|         using Format = Regs::FramebufferFormat; |  | ||||||
| 
 |  | ||||||
|         u32 input_address; |         u32 input_address; | ||||||
|         u32 output_address; |         u32 output_address; | ||||||
| 
 | 
 | ||||||
| @ -161,8 +157,8 @@ struct Regs { | |||||||
|             u32 flags; |             u32 flags; | ||||||
| 
 | 
 | ||||||
|             BitField< 0, 1, u32> flip_data;        // flips input data horizontally (TODO) if true
 |             BitField< 0, 1, u32> flip_data;        // flips input data horizontally (TODO) if true
 | ||||||
|             BitField< 8, 3, Format> input_format; |             BitField< 8, 3, PixelFormat> input_format; | ||||||
|             BitField<12, 3, Format> output_format; |             BitField<12, 3, PixelFormat> output_format; | ||||||
|             BitField<16, 1, u32> output_tiled;     // stores output in a tiled format
 |             BitField<16, 1, u32> output_tiled;     // stores output in a tiled format
 | ||||||
|         }; |         }; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -19,7 +19,7 @@ public: | |||||||
|     RendererBase() : m_current_fps(0), m_current_frame(0) { |     RendererBase() : m_current_fps(0), m_current_frame(0) { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     ~RendererBase() { |     virtual ~RendererBase() { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /// Swap buffers (render frame)
 |     /// Swap buffers (render frame)
 | ||||||
|  | |||||||
| @ -6,34 +6,40 @@ | |||||||
| 
 | 
 | ||||||
| namespace GLShaders { | namespace GLShaders { | ||||||
| 
 | 
 | ||||||
| static const char g_vertex_shader[] = R"( | const char g_vertex_shader[] = R"( | ||||||
| #version 150 core | #version 150 core | ||||||
| in vec3 position; |  | ||||||
| in vec2 texCoord; |  | ||||||
| 
 | 
 | ||||||
| out vec2 UV; | in vec2 vert_position; | ||||||
|  | in vec2 vert_tex_coord; | ||||||
|  | out vec2 frag_tex_coord; | ||||||
| 
 | 
 | ||||||
| mat3 window_scale = mat3( | // This is a truncated 3x3 matrix for 2D transformations:
 | ||||||
|                          vec3(1.0, 0.0, 0.0), | // The upper-left 2x2 submatrix performs scaling/rotation/mirroring.
 | ||||||
|                          vec3(0.0, 5.0/6.0, 0.0), // TODO(princesspeachum): replace hard-coded aspect with uniform
 | // The third column performs translation.
 | ||||||
|                          vec3(0.0, 0.0, 1.0) | // 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() { | void main() { | ||||||
|     gl_Position.xyz = window_scale * position; |     // Multiply input position by the rotscale part of the matrix and then manually translate by
 | ||||||
|     gl_Position.w = 1.0; |     // 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; | ||||||
|  | } | ||||||
|  | )"; | ||||||
| 
 | 
 | ||||||
|     UV = texCoord; | const char g_fragment_shader[] = R"( | ||||||
| })"; |  | ||||||
| 
 |  | ||||||
| static const char g_fragment_shader[] = R"( |  | ||||||
| #version 150 core | #version 150 core | ||||||
| in vec2 UV; | 
 | ||||||
| out vec3 color; | in vec2 frag_tex_coord; | ||||||
| uniform sampler2D sampler; | out vec4 color; | ||||||
|  | 
 | ||||||
|  | uniform sampler2D color_texture; | ||||||
| 
 | 
 | ||||||
| void main() { | void main() { | ||||||
|     color = texture(sampler, UV).rgb; |     color = texture(color_texture, frag_tex_coord); | ||||||
| })"; | } | ||||||
|  | )"; | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
|  | |||||||
| @ -3,64 +3,51 @@ | |||||||
| // Refer to the license.txt file included.
 | // Refer to the license.txt file included.
 | ||||||
| 
 | 
 | ||||||
| #include "core/hw/gpu.h" | #include "core/hw/gpu.h" | ||||||
| 
 | #include "core/mem_map.h" | ||||||
|  | #include "common/emu_window.h" | ||||||
| #include "video_core/video_core.h" | #include "video_core/video_core.h" | ||||||
| #include "video_core/renderer_opengl/renderer_opengl.h" | #include "video_core/renderer_opengl/renderer_opengl.h" | ||||||
| #include "video_core/renderer_opengl/gl_shader_util.h" | #include "video_core/renderer_opengl/gl_shader_util.h" | ||||||
| #include "video_core/renderer_opengl/gl_shaders.h" | #include "video_core/renderer_opengl/gl_shaders.h" | ||||||
| 
 | 
 | ||||||
| #include "core/mem_map.h" |  | ||||||
| 
 |  | ||||||
| #include <algorithm> | #include <algorithm> | ||||||
| 
 | 
 | ||||||
| static const GLfloat kViewportAspectRatio = | /**
 | ||||||
|     (static_cast<float>(VideoCore::kScreenTopHeight) + VideoCore::kScreenBottomHeight) / VideoCore::kScreenTopWidth; |  * 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; | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
| // Fullscreen quad dimensions
 |     GLfloat position[2]; | ||||||
| static const GLfloat kTopScreenWidthNormalized = 2; |     GLfloat tex_coord[2]; | ||||||
| static const GLfloat kTopScreenHeightNormalized    = kTopScreenWidthNormalized    * (static_cast<float>(VideoCore::kScreenTopHeight)    / VideoCore::kScreenTopWidth); |  | ||||||
| static const GLfloat kBottomScreenWidthNormalized  = kTopScreenWidthNormalized    * (static_cast<float>(VideoCore::kScreenBottomWidth)  / VideoCore::kScreenTopWidth); |  | ||||||
| static const GLfloat kBottomScreenHeightNormalized = kBottomScreenWidthNormalized * (static_cast<float>(VideoCore::kScreenBottomHeight) / VideoCore::kScreenBottomWidth); |  | ||||||
| 
 |  | ||||||
| static const GLfloat g_vbuffer_top[] = { |  | ||||||
|     // x,   y                           z     u     v
 |  | ||||||
|     -1.0f, 0.0f,                       0.0f, 0.0f, 1.0f, |  | ||||||
|      1.0f, 0.0f,                       0.0f, 1.0f, 1.0f, |  | ||||||
|      1.0f, kTopScreenHeightNormalized, 0.0f, 1.0f, 0.0f, |  | ||||||
|      1.0f, kTopScreenHeightNormalized, 0.0f, 1.0f, 0.0f, |  | ||||||
|     -1.0f, kTopScreenHeightNormalized, 0.0f, 0.0f, 0.0f, |  | ||||||
|     -1.0f, 0.0f,                       0.0f, 0.0f, 1.0f |  | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| static const GLfloat g_vbuffer_bottom[] = { | /**
 | ||||||
|     // x                                   y                              z     u     v
 |  * Defines a 1:1 pixel ortographic projection matrix with (0,0) on the top-left | ||||||
|     -(kBottomScreenWidthNormalized / 2), -kBottomScreenHeightNormalized, 0.0f, 0.0f, 1.0f, |  * corner and (width, height) on the lower-bottom. | ||||||
|      (kBottomScreenWidthNormalized / 2), -kBottomScreenHeightNormalized, 0.0f, 1.0f, 1.0f, |  * | ||||||
|      (kBottomScreenWidthNormalized / 2),  0.0f,                          0.0f, 1.0f, 0.0f, |  * The projection part of the matrix is trivial, hence these operations are represented | ||||||
|      (kBottomScreenWidthNormalized / 2),  0.0f,                          0.0f, 1.0f, 0.0f, |  * by a 3x2 matrix. | ||||||
|     -(kBottomScreenWidthNormalized / 2),  0.0f,                          0.0f, 0.0f, 0.0f, |  */ | ||||||
|     -(kBottomScreenWidthNormalized / 2), -kBottomScreenHeightNormalized, 0.0f, 0.0f, 1.0f | static std::array<GLfloat, 3*2> MakeOrthographicMatrix(const float width, const float height) { | ||||||
| }; |     std::array<GLfloat, 3*2> matrix; | ||||||
|  | 
 | ||||||
|  |     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].
 | ||||||
|  | 
 | ||||||
|  |     return matrix; | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| /// RendererOpenGL constructor
 | /// RendererOpenGL constructor
 | ||||||
| RendererOpenGL::RendererOpenGL() { | RendererOpenGL::RendererOpenGL() { | ||||||
| 
 |  | ||||||
|     resolution_width  = std::max(VideoCore::kScreenTopWidth, VideoCore::kScreenBottomWidth); |     resolution_width  = std::max(VideoCore::kScreenTopWidth, VideoCore::kScreenBottomWidth); | ||||||
|     resolution_height = VideoCore::kScreenTopHeight + VideoCore::kScreenBottomHeight; |     resolution_height = VideoCore::kScreenTopHeight + VideoCore::kScreenBottomHeight; | ||||||
| 
 |  | ||||||
|     // Initialize screen info
 |  | ||||||
|     const auto& framebuffer_top = GPU::g_regs.framebuffer_config[0]; |  | ||||||
|     const auto& framebuffer_sub = GPU::g_regs.framebuffer_config[1]; |  | ||||||
| 
 |  | ||||||
|     screen_info.Top().width               = VideoCore::kScreenTopWidth; |  | ||||||
|     screen_info.Top().height              = VideoCore::kScreenTopHeight; |  | ||||||
|     screen_info.Top().stride              = framebuffer_top.stride; |  | ||||||
|     screen_info.Top().flipped_xfb_data    = xfb_top_flipped; |  | ||||||
| 
 |  | ||||||
|     screen_info.Bottom().width            = VideoCore::kScreenBottomWidth; |  | ||||||
|     screen_info.Bottom().height           = VideoCore::kScreenBottomHeight; |  | ||||||
|     screen_info.Bottom().stride           = framebuffer_sub.stride; |  | ||||||
|     screen_info.Bottom().flipped_xfb_data = xfb_bottom_flipped; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /// RendererOpenGL destructor
 | /// RendererOpenGL destructor
 | ||||||
| @ -71,17 +58,24 @@ RendererOpenGL::~RendererOpenGL() { | |||||||
| void RendererOpenGL::SwapBuffers() { | void RendererOpenGL::SwapBuffers() { | ||||||
|     render_window->MakeCurrent(); |     render_window->MakeCurrent(); | ||||||
| 
 | 
 | ||||||
|     // EFB->XFB copy
 |     for(int i : {0, 1}) { | ||||||
|     // TODO(bunnei): This is a hack and does not belong here. The copy should be triggered by some
 |         const auto& framebuffer = GPU::g_regs.framebuffer_config[i]; | ||||||
|     // register write.
 |  | ||||||
|     //
 |  | ||||||
|     // TODO(princesspeachum): (related to above^) this should only be called when there's new data, not every frame.
 |  | ||||||
|     // Currently this uploads data that shouldn't have changed.
 |  | ||||||
|     Common::Rect framebuffer_size(0, 0, resolution_width, resolution_height); |  | ||||||
|     RenderXFB(framebuffer_size, framebuffer_size); |  | ||||||
| 
 | 
 | ||||||
|     // XFB->Window copy
 |         if (textures[i].width != framebuffer.width || textures[i].height != framebuffer.height) { | ||||||
|     RenderFramebuffer(); |             // Reallocate texture if the framebuffer size has changed.
 | ||||||
|  |             // This is expected to not happen very often and hence should not be a
 | ||||||
|  |             // performance problem.
 | ||||||
|  |             glBindTexture(GL_TEXTURE_2D, textures[i].handle); | ||||||
|  |             glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, framebuffer.width, framebuffer.height, 0, | ||||||
|  |                 GL_BGR, GL_UNSIGNED_BYTE, nullptr); | ||||||
|  |             textures[i].width = framebuffer.width; | ||||||
|  |             textures[i].height = framebuffer.height; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         LoadFBToActiveGLTexture(GPU::g_regs.framebuffer_config[i], textures[i]); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     DrawScreens(); | ||||||
| 
 | 
 | ||||||
|     // Swap buffers
 |     // Swap buffers
 | ||||||
|     render_window->PollEvents(); |     render_window->PollEvents(); | ||||||
| @ -89,144 +83,135 @@ void RendererOpenGL::SwapBuffers() { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /**
 | /**
 | ||||||
|  * Helper function to flip framebuffer from left-to-right to top-to-bottom |  * Loads framebuffer from emulated memory into the active OpenGL texture. | ||||||
|  * @param raw_data Pointer to input raw framebuffer in V/RAM |  | ||||||
|  * @param screen_info ScreenInfo structure with screen size and output buffer pointer |  | ||||||
|  * @todo Early on hack... I'd like to find a more efficient way of doing this /bunnei |  | ||||||
|  */ |  */ | ||||||
| void RendererOpenGL::FlipFramebuffer(const u8* raw_data, ScreenInfo& screen_info) { | void RendererOpenGL::LoadFBToActiveGLTexture(const GPU::Regs::FramebufferConfig& framebuffer, | ||||||
|     for (int x = 0; x < screen_info.width; x++) { |                                              const TextureInfo& texture) { | ||||||
|         int in_coord = x * screen_info.stride; |     const VAddr framebuffer_vaddr = Memory::PhysicalToVirtualAddress( | ||||||
|         for (int y = screen_info.height-1; y >= 0; y--) { |         framebuffer.active_fb == 1 ? framebuffer.address_left2 : framebuffer.address_left1); | ||||||
|             // TODO: Properly support other framebuffer formats
 | 
 | ||||||
|             int out_coord = (x + y * screen_info.width) * 3; |     DEBUG_LOG(GPU, "0x%08x bytes from 0x%08x(%dx%d), fmt %x", | ||||||
|             screen_info.flipped_xfb_data[out_coord] = raw_data[in_coord + 2];       // Red
 |         framebuffer.stride * framebuffer.height, | ||||||
|             screen_info.flipped_xfb_data[out_coord + 1] = raw_data[in_coord + 1];   // Green
 |         framebuffer_vaddr, (int)framebuffer.width, | ||||||
|             screen_info.flipped_xfb_data[out_coord + 2] = raw_data[in_coord];       // Blue
 |         (int)framebuffer.height, (int)framebuffer.format); | ||||||
|             in_coord += 3; | 
 | ||||||
|         } |     const u8* framebuffer_data = Memory::GetPointer(framebuffer_vaddr); | ||||||
|     } | 
 | ||||||
|  |     // TODO: Handle other pixel formats
 | ||||||
|  |     _dbg_assert_msg_(RENDER, framebuffer.color_format == GPU::Regs::PixelFormat::RGB8, | ||||||
|  |                      "Unsupported 3DS pixel format."); | ||||||
|  | 
 | ||||||
|  |     size_t pixel_stride = framebuffer.stride / 3; | ||||||
|  |     // OpenGL only supports specifying a stride in units of pixels, not bytes, unfortunately
 | ||||||
|  |     _dbg_assert_(RENDER, pixel_stride * 3 == framebuffer.stride); | ||||||
|  |     // Ensure no bad interactions with GL_UNPACK_ALIGNMENT, which by default
 | ||||||
|  |     // only allows rows to have a memory alignement of 4.
 | ||||||
|  |     _dbg_assert_(RENDER, pixel_stride % 4 == 0); | ||||||
|  | 
 | ||||||
|  |     glBindTexture(GL_TEXTURE_2D, texture.handle); | ||||||
|  |     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.
 | ||||||
|  |     // 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, | ||||||
|  |         GL_BGR, GL_UNSIGNED_BYTE, framebuffer_data); | ||||||
|  | 
 | ||||||
|  |     glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); | ||||||
|  | 
 | ||||||
|  |     glBindTexture(GL_TEXTURE_2D, 0); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /**
 | /**
 | ||||||
|  * Renders external framebuffer (XFB) |  * Initializes the OpenGL state and creates persistent objects. | ||||||
|  * @param src_rect Source rectangle in XFB to copy |  | ||||||
|  * @param dst_rect Destination rectangle in output framebuffer to copy to |  | ||||||
|  */ |  */ | ||||||
| void RendererOpenGL::RenderXFB(const Common::Rect& src_rect, const Common::Rect& dst_rect) { | void RendererOpenGL::InitOpenGLObjects() { | ||||||
|     const auto& framebuffer_top = GPU::g_regs.framebuffer_config[0]; |     glClearColor(1.0f, 1.0f, 1.0f, 0.0f); | ||||||
|     const auto& framebuffer_sub = GPU::g_regs.framebuffer_config[1]; |     glDisable(GL_DEPTH_TEST); | ||||||
|     const u32 active_fb_top = (framebuffer_top.active_fb == 1) |  | ||||||
|                             ? Memory::PhysicalToVirtualAddress(framebuffer_top.address_left2) |  | ||||||
|                             : Memory::PhysicalToVirtualAddress(framebuffer_top.address_left1); |  | ||||||
|     const u32 active_fb_sub = (framebuffer_sub.active_fb == 1) |  | ||||||
|                             ? Memory::PhysicalToVirtualAddress(framebuffer_sub.address_left2) |  | ||||||
|                             : Memory::PhysicalToVirtualAddress(framebuffer_sub.address_left1); |  | ||||||
| 
 | 
 | ||||||
|     DEBUG_LOG(GPU, "RenderXFB: 0x%08x bytes from 0x%08x(%dx%d), fmt %x", |     // Link shaders and get variable locations
 | ||||||
|               framebuffer_top.stride * framebuffer_top.height, |  | ||||||
|               active_fb_top, (int)framebuffer_top.width, |  | ||||||
|               (int)framebuffer_top.height, (int)framebuffer_top.format); |  | ||||||
| 
 |  | ||||||
|     FlipFramebuffer(Memory::GetPointer(active_fb_top), screen_info.Top()); |  | ||||||
|     FlipFramebuffer(Memory::GetPointer(active_fb_sub), screen_info.Bottom()); |  | ||||||
| 
 |  | ||||||
|     for (int i = 0; i < 2; i++) { |  | ||||||
|         ScreenInfo* current_screen = &screen_info[i]; |  | ||||||
| 
 |  | ||||||
|         glBindTexture(GL_TEXTURE_2D, current_screen->texture_id); |  | ||||||
| 
 |  | ||||||
|         // TODO: This should consider the GPU registers for framebuffer width, height and stride.
 |  | ||||||
|         glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, current_screen->width, current_screen->height, |  | ||||||
|                         GL_RGB, GL_UNSIGNED_BYTE, current_screen->flipped_xfb_data); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     glBindTexture(GL_TEXTURE_2D, 0); |  | ||||||
| 
 |  | ||||||
|     // TODO(princesspeachum):
 |  | ||||||
|     // Only the subset src_rect of the GPU buffer
 |  | ||||||
|     // should be copied into the texture of the relevant screen.
 |  | ||||||
|     //
 |  | ||||||
|     // The method's parameters also only include src_rect and dest_rec for one screen,
 |  | ||||||
|     // so this may need to be changed (pair for each screen).
 |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /// Initialize the FBO
 |  | ||||||
| void RendererOpenGL::InitFramebuffer() { |  | ||||||
|     program_id = ShaderUtil::LoadShaders(GLShaders::g_vertex_shader, GLShaders::g_fragment_shader); |     program_id = ShaderUtil::LoadShaders(GLShaders::g_vertex_shader, GLShaders::g_fragment_shader); | ||||||
|     sampler_id = glGetUniformLocation(program_id, "sampler"); |     uniform_modelview_matrix = glGetUniformLocation(program_id, "modelview_matrix"); | ||||||
|     attrib_position = glGetAttribLocation(program_id, "position"); |     uniform_color_texture = glGetUniformLocation(program_id, "color_texture"); | ||||||
|     attrib_texcoord = glGetAttribLocation(program_id, "texCoord"); |     attrib_position = glGetAttribLocation(program_id, "vert_position"); | ||||||
|  |     attrib_tex_coord = glGetAttribLocation(program_id, "vert_tex_coord"); | ||||||
| 
 | 
 | ||||||
|     // Generate vertex buffers for both screens
 |     // Generate VBO handle for drawing
 | ||||||
|     glGenBuffers(1, &screen_info.Top().vertex_buffer_id); |     glGenBuffers(1, &vertex_buffer_handle); | ||||||
|     glGenBuffers(1, &screen_info.Bottom().vertex_buffer_id); |  | ||||||
| 
 | 
 | ||||||
|     // Attach vertex data for top screen
 |     // Generate VAO
 | ||||||
|     glBindBuffer(GL_ARRAY_BUFFER, screen_info.Top().vertex_buffer_id); |     glGenVertexArrays(1, &vertex_array_handle); | ||||||
|     glBufferData(GL_ARRAY_BUFFER, sizeof(g_vbuffer_top), g_vbuffer_top, GL_STATIC_DRAW); |     glBindVertexArray(vertex_array_handle); | ||||||
| 
 | 
 | ||||||
|     // Attach vertex data for bottom screen
 |     // Attach vertex data to VAO
 | ||||||
|     glBindBuffer(GL_ARRAY_BUFFER, screen_info.Bottom().vertex_buffer_id); |     glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer_handle); | ||||||
|     glBufferData(GL_ARRAY_BUFFER, sizeof(g_vbuffer_bottom), g_vbuffer_bottom, GL_STATIC_DRAW); |     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); | ||||||
| 
 | 
 | ||||||
|     // Create color buffers for both screens
 |     // Allocate textures for each screen
 | ||||||
|     glGenTextures(1, &screen_info.Top().texture_id); |     for (auto& texture : textures) { | ||||||
|     glGenTextures(1, &screen_info.Bottom().texture_id); |         glGenTextures(1, &texture.handle); | ||||||
| 
 | 
 | ||||||
|     for (int i = 0; i < 2; i++) { |         // Allocation of storage is deferred until the first frame, when we
 | ||||||
| 
 |         // know the framebuffer size.
 | ||||||
|         ScreenInfo* current_screen = &screen_info[i]; |  | ||||||
| 
 |  | ||||||
|         // Allocate texture
 |  | ||||||
|         glBindTexture(GL_TEXTURE_2D, current_screen->vertex_buffer_id); |  | ||||||
|         glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, current_screen->width, current_screen->height, |  | ||||||
|                      0, GL_RGB, GL_UNSIGNED_BYTE, NULL); |  | ||||||
| 
 | 
 | ||||||
|  |         glBindTexture(GL_TEXTURE_2D, texture.handle); | ||||||
|  |         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_MAG_FILTER, GL_LINEAR); | ||||||
|         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_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); | ||||||
|     } |     } | ||||||
| 
 |  | ||||||
|     glBindTexture(GL_TEXTURE_2D, 0); |     glBindTexture(GL_TEXTURE_2D, 0); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void RendererOpenGL::RenderFramebuffer() { | /**
 | ||||||
|  |  * Draws a single texture to the emulator window, rotating the texture to correct for the 3DS's LCD rotation. | ||||||
|  |  */ | ||||||
|  | void RendererOpenGL::DrawSingleScreenRotated(const TextureInfo& texture, float x, float y, float w, float h) { | ||||||
|  |     std::array<ScreenRectVertex, 4> vertices = { | ||||||
|  |         ScreenRectVertex(x,   y,   1.f, 0.f), | ||||||
|  |         ScreenRectVertex(x+w, y,   1.f, 1.f), | ||||||
|  |         ScreenRectVertex(x,   y+h, 0.f, 0.f), | ||||||
|  |         ScreenRectVertex(x+w, y+h, 0.f, 1.f), | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     glBindTexture(GL_TEXTURE_2D, texture.handle); | ||||||
|  |     glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer_handle); | ||||||
|  |     glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices.data()); | ||||||
|  |     glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * Draws the emulated screens to the emulator window. | ||||||
|  |  */ | ||||||
|  | void RendererOpenGL::DrawScreens() { | ||||||
|     glViewport(0, 0, resolution_width, resolution_height); |     glViewport(0, 0, resolution_width, resolution_height); | ||||||
|     glClear(GL_COLOR_BUFFER_BIT); |     glClear(GL_COLOR_BUFFER_BIT); | ||||||
| 
 | 
 | ||||||
|     glUseProgram(program_id); |     glUseProgram(program_id); | ||||||
| 
 | 
 | ||||||
|  |     // Set projection matrix
 | ||||||
|  |     std::array<GLfloat, 3*2> ortho_matrix = MakeOrthographicMatrix((float)resolution_width, (float)resolution_height); | ||||||
|  |     glUniformMatrix3x2fv(uniform_modelview_matrix, 1, GL_FALSE, ortho_matrix.data()); | ||||||
|  | 
 | ||||||
|     // Bind texture in Texture Unit 0
 |     // Bind texture in Texture Unit 0
 | ||||||
|     glActiveTexture(GL_TEXTURE0); |     glActiveTexture(GL_TEXTURE0); | ||||||
|  |     glUniform1i(uniform_color_texture, 0); | ||||||
| 
 | 
 | ||||||
|     glEnableVertexAttribArray(attrib_position); |     const float max_width = std::max((float)VideoCore::kScreenTopWidth, (float)VideoCore::kScreenBottomWidth); | ||||||
|     glEnableVertexAttribArray(attrib_texcoord); |     const float top_x = 0.5f * (max_width - VideoCore::kScreenTopWidth); | ||||||
|  |     const float bottom_x = 0.5f * (max_width - VideoCore::kScreenBottomWidth); | ||||||
| 
 | 
 | ||||||
|     for (int i = 0; i < 2; i++) { |     DrawSingleScreenRotated(textures[0], top_x, 0, | ||||||
| 
 |         (float)VideoCore::kScreenTopWidth, (float)VideoCore::kScreenTopHeight); | ||||||
|         ScreenInfo* current_screen = &screen_info[i]; |     DrawSingleScreenRotated(textures[1], bottom_x, (float)VideoCore::kScreenTopHeight, | ||||||
| 
 |         (float)VideoCore::kScreenBottomWidth, (float)VideoCore::kScreenBottomHeight); | ||||||
|         glBindTexture(GL_TEXTURE_2D, current_screen->texture_id); |  | ||||||
| 
 |  | ||||||
|         // Set sampler on Texture Unit 0
 |  | ||||||
|         glUniform1i(sampler_id, 0); |  | ||||||
| 
 |  | ||||||
|         glBindBuffer(GL_ARRAY_BUFFER, current_screen->vertex_buffer_id); |  | ||||||
| 
 |  | ||||||
|         // Vertex buffer layout
 |  | ||||||
|         const GLsizei stride = 5 * sizeof(GLfloat); |  | ||||||
|         const GLvoid* uv_offset = (const GLvoid*)(3 * sizeof(GLfloat)); |  | ||||||
| 
 |  | ||||||
|         // Configure vertex buffer
 |  | ||||||
|         glVertexAttribPointer(attrib_position, 3, GL_FLOAT, GL_FALSE, stride, NULL); |  | ||||||
|         glVertexAttribPointer(attrib_texcoord, 2, GL_FLOAT, GL_FALSE, stride, uv_offset); |  | ||||||
| 
 |  | ||||||
|         // Draw screen
 |  | ||||||
|         glDrawArrays(GL_TRIANGLES, 0, 6); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     glDisableVertexAttribArray(attrib_position); |  | ||||||
|     glDisableVertexAttribArray(attrib_texcoord); |  | ||||||
| 
 | 
 | ||||||
|     m_current_frame++; |     m_current_frame++; | ||||||
| } | } | ||||||
| @ -253,20 +238,8 @@ void RendererOpenGL::Init() { | |||||||
|         exit(-1); |         exit(-1); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // Generate VAO
 |  | ||||||
|     glGenVertexArrays(1, &vertex_array_id); |  | ||||||
|     glBindVertexArray(vertex_array_id); |  | ||||||
| 
 |  | ||||||
|     glClearColor(1.0f, 1.0f, 1.0f, 0.0f); |  | ||||||
|     glDisable(GL_DEPTH_TEST); |  | ||||||
| 
 |  | ||||||
|     glPixelStorei(GL_UNPACK_ALIGNMENT, 4); |  | ||||||
| 
 |  | ||||||
|     // Initialize everything else
 |  | ||||||
|     // --------------------------
 |  | ||||||
|     InitFramebuffer(); |  | ||||||
| 
 |  | ||||||
|     NOTICE_LOG(RENDER, "GL_VERSION: %s\n", glGetString(GL_VERSION)); |     NOTICE_LOG(RENDER, "GL_VERSION: %s\n", glGetString(GL_VERSION)); | ||||||
|  |     InitOpenGLObjects(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /// Shutdown the renderer
 | /// Shutdown the renderer
 | ||||||
|  | |||||||
| @ -7,28 +7,22 @@ | |||||||
| #include "generated/gl_3_2_core.h" | #include "generated/gl_3_2_core.h" | ||||||
| 
 | 
 | ||||||
| #include "common/common.h" | #include "common/common.h" | ||||||
| #include "common/emu_window.h" | #include "core/hw/gpu.h" | ||||||
| 
 |  | ||||||
| #include "video_core/renderer_base.h" | #include "video_core/renderer_base.h" | ||||||
| 
 | 
 | ||||||
| #include <array> | #include <array> | ||||||
| 
 | 
 | ||||||
| class RendererOpenGL : virtual public RendererBase { | class EmuWindow; | ||||||
|  | 
 | ||||||
|  | class RendererOpenGL : public RendererBase { | ||||||
| public: | public: | ||||||
| 
 | 
 | ||||||
|     RendererOpenGL(); |     RendererOpenGL(); | ||||||
|     ~RendererOpenGL(); |     ~RendererOpenGL() override; | ||||||
| 
 | 
 | ||||||
|     /// Swap buffers (render frame)
 |     /// Swap buffers (render frame)
 | ||||||
|     void SwapBuffers(); |     void SwapBuffers(); | ||||||
| 
 | 
 | ||||||
|     /**
 |  | ||||||
|      * Renders external framebuffer (XFB) |  | ||||||
|      * @param src_rect Source rectangle in XFB to copy |  | ||||||
|      * @param dst_rect Destination rectangle in output framebuffer to copy to |  | ||||||
|      */ |  | ||||||
|     void RenderXFB(const Common::Rect& src_rect, const Common::Rect& dst_rect); |  | ||||||
| 
 |  | ||||||
|     /**
 |     /**
 | ||||||
|      * Set the emulator window to use for renderer |      * Set the emulator window to use for renderer | ||||||
|      * @param window EmuWindow handle to emulator window to use for rendering |      * @param window EmuWindow handle to emulator window to use for rendering | ||||||
| @ -42,38 +36,21 @@ public: | |||||||
|     void ShutDown(); |     void ShutDown(); | ||||||
| 
 | 
 | ||||||
| private: | private: | ||||||
| 
 |     /// Structure used for storing information about the textures for each 3DS screen
 | ||||||
|     /// Initialize the FBO
 |     struct TextureInfo { | ||||||
|     void InitFramebuffer(); |         GLuint handle; | ||||||
| 
 |         GLsizei width; | ||||||
|     // Blit the FBO to the OpenGL default framebuffer
 |         GLsizei height; | ||||||
|     void RenderFramebuffer(); |  | ||||||
| 
 |  | ||||||
|     /// Updates the framerate
 |  | ||||||
|     void UpdateFramerate(); |  | ||||||
| 
 |  | ||||||
|     /// Structure used for storing information for rendering each 3DS screen
 |  | ||||||
|     struct ScreenInfo { |  | ||||||
|         // Properties
 |  | ||||||
|         int width; |  | ||||||
|         int height; |  | ||||||
|         int stride; ///< Number of bytes between the coordinates (0,0) and (1,0)
 |  | ||||||
| 
 |  | ||||||
|         // OpenGL object IDs
 |  | ||||||
|         GLuint texture_id; |  | ||||||
|         GLuint vertex_buffer_id; |  | ||||||
| 
 |  | ||||||
|         // Temporary
 |  | ||||||
|         u8* flipped_xfb_data; |  | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     /**
 |     void InitOpenGLObjects(); | ||||||
|     * Helper function to flip framebuffer from left-to-right to top-to-bottom |     void DrawScreens(); | ||||||
|     * @param raw_data Pointer to input raw framebuffer in V/RAM |     void DrawSingleScreenRotated(const TextureInfo& texture, float x, float y, float w, float h); | ||||||
|     * @param screen_info ScreenInfo structure with screen size and output buffer pointer |     void UpdateFramerate(); | ||||||
|     * @todo Early on hack... I'd like to find a more efficient way of doing this /bunnei | 
 | ||||||
|     */ |     // Loads framebuffer from emulated memory into the active OpenGL texture.
 | ||||||
|     void FlipFramebuffer(const u8* raw_data, ScreenInfo& screen_info); |     static void LoadFBToActiveGLTexture(const GPU::Regs::FramebufferConfig& framebuffer, | ||||||
|  |                                         const TextureInfo& texture); | ||||||
| 
 | 
 | ||||||
|     EmuWindow*  render_window;                    ///< Handle to render window
 |     EmuWindow*  render_window;                    ///< Handle to render window
 | ||||||
|     u32         last_mode;                        ///< Last render mode
 |     u32         last_mode;                        ///< Last render mode
 | ||||||
| @ -81,22 +58,15 @@ private: | |||||||
|     int resolution_width;                         ///< Current resolution width
 |     int resolution_width;                         ///< Current resolution width
 | ||||||
|     int resolution_height;                        ///< Current resolution height
 |     int resolution_height;                        ///< Current resolution height
 | ||||||
| 
 | 
 | ||||||
|     // OpenGL global object IDs
 |     // OpenGL object IDs
 | ||||||
|     GLuint vertex_array_id; |     GLuint vertex_array_handle; | ||||||
|  |     GLuint vertex_buffer_handle; | ||||||
|     GLuint program_id; |     GLuint program_id; | ||||||
|     GLuint sampler_id; |     std::array<TextureInfo, 2> textures; | ||||||
|  |     // Shader uniform location indices
 | ||||||
|  |     GLuint uniform_modelview_matrix; | ||||||
|  |     GLuint uniform_color_texture; | ||||||
|     // Shader attribute input indices
 |     // Shader attribute input indices
 | ||||||
|     GLuint attrib_position; |     GLuint attrib_position; | ||||||
|     GLuint attrib_texcoord; |     GLuint attrib_tex_coord; | ||||||
| 
 |  | ||||||
|     struct : std::array<ScreenInfo, 2> { |  | ||||||
|         ScreenInfo& Top() { return (*this)[0]; } |  | ||||||
|         ScreenInfo& Bottom() { return (*this)[1]; } |  | ||||||
|     } screen_info; |  | ||||||
| 
 |  | ||||||
|     // "Flipped" framebuffers translate scanlines from native 3DS left-to-right to top-to-bottom
 |  | ||||||
|     // as OpenGL expects them in a texture. There probably is a more efficient way of doing this:
 |  | ||||||
|     u8 xfb_top_flipped[VideoCore::kScreenTopWidth * VideoCore::kScreenTopHeight * 4]; |  | ||||||
|     u8 xfb_bottom_flipped[VideoCore::kScreenBottomWidth * VideoCore::kScreenBottomHeight * 4]; |  | ||||||
| 
 |  | ||||||
| }; | }; | ||||||
|  | |||||||
| @ -17,6 +17,10 @@ namespace VideoCore { | |||||||
| // 3DS Video Constants
 | // 3DS Video Constants
 | ||||||
| // -------------------
 | // -------------------
 | ||||||
| 
 | 
 | ||||||
|  | // NOTE: The LCDs actually rotate the image 90 degrees when displaying. Because of that the
 | ||||||
|  | // framebuffers in video memory are stored in column-major order and rendered sideways, causing
 | ||||||
|  | // the widths and heights of the framebuffers read by the LCD to be switched compared to the
 | ||||||
|  | // heights and widths of the screens listed here.
 | ||||||
| static const int kScreenTopWidth        = 400;  ///< 3DS top screen width
 | static const int kScreenTopWidth        = 400;  ///< 3DS top screen width
 | ||||||
| static const int kScreenTopHeight       = 240;  ///< 3DS top screen height
 | static const int kScreenTopHeight       = 240;  ///< 3DS top screen height
 | ||||||
| static const int kScreenBottomWidth     = 320;  ///< 3DS bottom screen width
 | static const int kScreenBottomWidth     = 320;  ///< 3DS bottom screen width
 | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	 Tony Wasserka
						Tony Wasserka