From 62748eef47e667168b2bc55190fa7525c13200e4 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Fri, 4 Jul 2025 17:53:36 +0300 Subject: [PATCH 1/5] Initial screen layout implementation --- CMakeLists.txt | 1 + include/PICA/screen_layout.hpp | 162 +++++++++++++++++++++++++ include/panda_qt/screen.hpp | 5 + include/panda_sdl/frontend_sdl.hpp | 9 +- include/renderer_gl/renderer_gl.hpp | 14 ++- include/renderer_mtl/renderer_mtl.hpp | 2 +- src/core/renderer_gl/renderer_gl.cpp | 131 +++++++++++--------- src/core/renderer_mtl/renderer_mtl.cpp | 36 +++--- src/panda_qt/main_window.cpp | 35 +++--- src/panda_qt/screen.cpp | 4 +- src/panda_sdl/frontend_sdl.cpp | 62 +++++----- 11 files changed, 335 insertions(+), 126 deletions(-) create mode 100644 include/PICA/screen_layout.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index d8fc54a9..bba242e4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -416,6 +416,7 @@ set(HEADER_FILES include/emulator.hpp include/helpers.hpp include/termcolor.hpp include/fs/archive_twl_sound.hpp include/fs/archive_card_spi.hpp include/services/ns.hpp include/audio/audio_device.hpp include/audio/audio_device_interface.hpp include/audio/libretro_audio_device.hpp include/services/ir/ir_types.hpp include/services/ir/ir_device.hpp include/services/ir/circlepad_pro.hpp include/services/service_intercept.hpp + include/PICA/screen_layout.hpp ) if(IOS) diff --git a/include/PICA/screen_layout.hpp b/include/PICA/screen_layout.hpp new file mode 100644 index 00000000..e7ff89b5 --- /dev/null +++ b/include/PICA/screen_layout.hpp @@ -0,0 +1,162 @@ +#pragma once +#include + +#include "helpers.hpp" + +namespace ScreenLayout { + static constexpr u32 TOP_SCREEN_WIDTH = 400; + static constexpr u32 BOTTOM_SCREEN_WIDTH = 320; + static constexpr u32 TOP_SCREEN_HEIGHT = 240; + static constexpr u32 BOTTOM_SCREEN_HEIGHT = 240; + + // The bottom screen is less wide by 80 pixels, so we center it by offsetting it 40 pixels right + static constexpr u32 BOTTOM_SCREEN_X_OFFSET = 40; + static constexpr u32 CONSOLE_HEIGHT = TOP_SCREEN_HEIGHT + BOTTOM_SCREEN_HEIGHT; + + enum class Layout { + Default, // Top screen up, bottom screen down + DefaultFlipped, // Top screen down, bottom screen up + SideBySide, // Top screen left, bottom screen right, + SideBySideFlipped, // Top screen right, bottom screen left, + }; + + // For properly handling touchscreen, we have to remember what window coordinates our screens map to + // We also remember some more information that is useful to our renderers, particularly for the final screen blit. + struct WindowCoordinates { + u32 topScreenX = 0; + u32 topScreenY = 0; + u32 topScreenWidth = TOP_SCREEN_WIDTH; + u32 topScreenHeight = TOP_SCREEN_HEIGHT; + + u32 bottomScreenX = BOTTOM_SCREEN_X_OFFSET; + u32 bottomScreenY = TOP_SCREEN_HEIGHT; + u32 bottomScreenWidth = BOTTOM_SCREEN_WIDTH; + u32 bottomScreenHeight = BOTTOM_SCREEN_HEIGHT; + + u32 windowWidth = topScreenWidth; + u32 windowHeight = topScreenHeight + bottomScreenHeight; + + // Information used when optimizing the final screen blit into a single blit + struct { + int destX = 0, destY = 0; + int destWidth = TOP_SCREEN_WIDTH; + int destHeight = CONSOLE_HEIGHT; + } singleBlitInfo; + + float scale = 1.0f; + }; + + // Calculate screen coordinates on the screen for a given layout & a given size for the output window + // Used in both the renderers and in the frontends (To eg calculate touch screen boundaries) + static void calculateCoordinates(WindowCoordinates& coordinates, u32 outputWindowWidth, u32 outputWindowHeight, Layout layout) { + layout = Layout::SideBySideFlipped; + const float destAspect = float(outputWindowWidth) / float(outputWindowHeight); + + if (layout == Layout::Default || layout == Layout::DefaultFlipped) { + const float srcAspect = 400.0 / 480.0; + + int destX = 0, destY = 0, destWidth = outputWindowWidth, destHeight = outputWindowHeight; + + if (destAspect > srcAspect) { + // Window is wider than source + destWidth = int(outputWindowHeight * srcAspect + 0.5f); + destX = (outputWindowWidth - destWidth) / 2; + } else { + // Window is taller than source + destHeight = int(outputWindowWidth / srcAspect + 0.5f); + destY = (outputWindowHeight - destHeight) / 2; + } + + // How much we'll scale the output by + const float scale = float(destWidth) / float(TOP_SCREEN_WIDTH); + + // Calculate coordinates and return them + // TODO: This will break when we allow screens to be scaled separately + coordinates.topScreenX = u32(destX); + coordinates.topScreenWidth = u32(float(TOP_SCREEN_WIDTH) * scale); + coordinates.bottomScreenX = u32(destX) + BOTTOM_SCREEN_X_OFFSET * scale; + coordinates.bottomScreenWidth = u32(float(BOTTOM_SCREEN_WIDTH) * scale); + + if (layout == Layout::Default) { + coordinates.topScreenY = u32(destY + (destHeight - int(CONSOLE_HEIGHT * scale)) / 2); + coordinates.topScreenHeight = u32(float(TOP_SCREEN_HEIGHT) * scale); + + coordinates.bottomScreenY = coordinates.topScreenY + TOP_SCREEN_HEIGHT * scale; + coordinates.bottomScreenHeight = coordinates.topScreenHeight; + } else { + // Flip the screens vertically + coordinates.bottomScreenY = u32(destY + (destHeight - int(CONSOLE_HEIGHT * scale)) / 2); + coordinates.bottomScreenHeight = u32(float(BOTTOM_SCREEN_HEIGHT) * scale); + + coordinates.topScreenY = coordinates.bottomScreenY + TOP_SCREEN_HEIGHT * scale; + coordinates.topScreenHeight = coordinates.bottomScreenHeight; + } + + coordinates.windowWidth = outputWindowWidth; + coordinates.windowHeight = outputWindowHeight; + coordinates.scale = scale; + + coordinates.singleBlitInfo.destX = destX; + coordinates.singleBlitInfo.destY = destY; + coordinates.singleBlitInfo.destWidth = destWidth; + coordinates.singleBlitInfo.destHeight = destHeight; + } else if (layout == Layout::SideBySide || layout == Layout::SideBySideFlipped) { + // For side-by-side layouts, the 3DS aspect ratio is calculated as (top width + bottom width) / height + const float srcAspect = float(TOP_SCREEN_WIDTH + BOTTOM_SCREEN_WIDTH) / float(CONSOLE_HEIGHT); + + int destX = 0, destY = 0, destWidth = outputWindowWidth, destHeight = outputWindowHeight; + + if (destAspect > srcAspect) { + // Window is wider than the side-by-side layout — center horizontally + destWidth = int(outputWindowHeight * srcAspect + 0.5f); + destX = (outputWindowWidth - destWidth) / 2; + } else { + // Window is taller — center vertically + destHeight = int(outputWindowWidth / srcAspect + 0.5f); + destY = (outputWindowHeight - destHeight) / 2; + } + + // How much we'll scale the output by. Again, we want to take both screens into account + const float scale = float(destWidth) / float(TOP_SCREEN_WIDTH + BOTTOM_SCREEN_WIDTH); + + // Annoyingly, the top screen is wider than the bottom screen, so to display them side-by-side and centered + // vertically, we have to account for that + + // The top screen is currently always larger, but it's still best to check which screen is taller anyways + // Since eventually we'll want to let users scale each screen separately. + const int topHeightScaled = int(TOP_SCREEN_HEIGHT * scale + 0.5f); + const int bottomHeightScaled = int(BOTTOM_SCREEN_HEIGHT * scale + 0.5f); + const int maxHeight = std::max(topHeightScaled, bottomHeightScaled); + const int centerY = destY + (destHeight - maxHeight) / 2; + + coordinates.topScreenY = u32(centerY + (maxHeight - topHeightScaled) / 2); + coordinates.topScreenWidth = u32(TOP_SCREEN_WIDTH * scale); + coordinates.topScreenHeight = u32(TOP_SCREEN_HEIGHT * scale); + + coordinates.bottomScreenY = u32(centerY + (maxHeight - bottomHeightScaled) / 2); + coordinates.bottomScreenWidth = u32(BOTTOM_SCREEN_WIDTH * scale); + coordinates.bottomScreenHeight = u32(BOTTOM_SCREEN_HEIGHT * scale); + + if (layout == Layout::SideBySide) { + coordinates.topScreenX = destX; + coordinates.bottomScreenX = destX + coordinates.topScreenWidth; + } else { + // Flip the screens horizontally + coordinates.bottomScreenX = destX; + coordinates.topScreenX = destX + coordinates.bottomScreenWidth; + } + + coordinates.windowWidth = outputWindowWidth; + coordinates.windowHeight = outputWindowHeight; + coordinates.scale = scale; + + // Set singleBlitInfo values to dummy values. Side-by-side screens can't be rendere in only 1 blit. + coordinates.singleBlitInfo.destX = 0; + coordinates.singleBlitInfo.destY = 0; + coordinates.singleBlitInfo.destWidth = 0; + coordinates.singleBlitInfo.destHeight = 0; + } else { + Helpers::panic("Unimplemented screen layout"); + } + } +} // namespace ScreenLayout diff --git a/include/panda_qt/screen.hpp b/include/panda_qt/screen.hpp index 1ed4966b..f785ee57 100644 --- a/include/panda_qt/screen.hpp +++ b/include/panda_qt/screen.hpp @@ -3,6 +3,7 @@ #include #include +#include "PICA/screen_layout.hpp" #include "gl/context.h" #include "window_info.h" @@ -29,6 +30,10 @@ class ScreenWidget : public QWidget { u32 previousWidth = 0; u32 previousHeight = 0; + // Coordinates (x/y/width/height) for the two screens in window space, used for properly handling touchscreen regardless + // of layout or resizing + ScreenLayout::WindowCoordinates screenCoordinates; + private: std::unique_ptr glContext = nullptr; ResizeCallback resizeCallback; diff --git a/include/panda_sdl/frontend_sdl.hpp b/include/panda_sdl/frontend_sdl.hpp index cbd0b88e..af5b88ab 100644 --- a/include/panda_sdl/frontend_sdl.hpp +++ b/include/panda_sdl/frontend_sdl.hpp @@ -4,6 +4,7 @@ #include +#include "PICA/screen_layout.hpp" #include "emulator.hpp" #include "input_mappings.hpp" @@ -27,7 +28,11 @@ class FrontendSDL { u32 windowHeight = 480; int gameControllerID; bool programRunning = true; - + + // Coordinates (x/y/width/height) for the two screens in window space, used for properly handling touchscreen regardless + // of layout or resizing + ScreenLayout::WindowCoordinates screenCoordinates; + // For tracking whether to update gyroscope // We bind gyro to right click + mouse movement bool holdingRightClick = false; @@ -38,5 +43,7 @@ class FrontendSDL { bool keyboardAnalogX = false; bool keyboardAnalogY = false; + private: void setupControllerSensors(SDL_GameController* controller); + void handleLeftClick(int mouseX, int mouseY); }; \ No newline at end of file diff --git a/include/renderer_gl/renderer_gl.hpp b/include/renderer_gl/renderer_gl.hpp index 5476843f..b105f3e9 100644 --- a/include/renderer_gl/renderer_gl.hpp +++ b/include/renderer_gl/renderer_gl.hpp @@ -147,12 +147,24 @@ class RendererGL final : public Renderer { OpenGL::Driver driverInfo; // Information about the final 3DS screen -> Window blit, accounting for things like scaling and shifting the output based on - // the window's dimensions. + // the window's dimensions. Updated whenever the screen size or layout changes. struct { + int topScreenX = 0; + int topScreenY = 0; + int topScreenWidth = 400; + int topScreenHeight = 240; + + int bottomScreenX = 40; + int bottomScreenY = 240; + int bottomScreenWidth = 320; + int bottomScreenHeight = 240; + + // For optimizing the final screen blit into a single blit instead of 2 when possible: int destX = 0; int destY = 0; int destWidth = 400; int destHeight = 480; + bool canDoSingleBlit = true; } blitInfo; MAKE_LOG_FUNCTION(log, rendererLogger) diff --git a/include/renderer_mtl/renderer_mtl.hpp b/include/renderer_mtl/renderer_mtl.hpp index add02d52..f9c256f0 100644 --- a/include/renderer_mtl/renderer_mtl.hpp +++ b/include/renderer_mtl/renderer_mtl.hpp @@ -89,7 +89,7 @@ class RendererMTL final : public Renderer { MTL::Texture* lastDepthTexture = nullptr; // Information about the final 3DS screen -> Window blit, accounting for things like scaling and shifting the output based on - // the window's dimensions. + // the window's dimensions. Updated whenever the screen size or layout changes. struct { float topScreenX = 0; float topScreenY = 0; diff --git a/src/core/renderer_gl/renderer_gl.cpp b/src/core/renderer_gl/renderer_gl.cpp index 9dbc0434..39995f03 100644 --- a/src/core/renderer_gl/renderer_gl.cpp +++ b/src/core/renderer_gl/renderer_gl.cpp @@ -11,6 +11,7 @@ #include "PICA/pica_hash.hpp" #include "PICA/pica_simd.hpp" #include "PICA/regs.hpp" +#include "PICA/screen_layout.hpp" #include "PICA/shader_decompiler.hpp" #include "config.hpp" #include "math_util.hpp" @@ -134,8 +135,8 @@ void RendererGL::initGraphicsContextInternal() { auto prevTexture = OpenGL::getTex2D(); - // Create a plain black texture for when a game reads an invalid texture. It is common for games to configure the PICA to read texture info from NULL. - // Some games that do this are Pokemon X, Cars 2, Tomodachi Life, and more. We bind the texture to an FBO, clear it, and free the FBO + // Create a plain black texture for when a game reads an invalid texture. It is common for games to configure the PICA to read texture info from + // NULL. Some games that do this are Pokemon X, Cars 2, Tomodachi Life, and more. We bind the texture to an FBO, clear it, and free the FBO blankTexture.create(8, 8, GL_RGBA8); blankTexture.bind(); blankTexture.setMinFilter(OpenGL::Linear); @@ -228,7 +229,7 @@ void RendererGL::setupBlending() { // Shows if blending is enabled. If it is not enabled, then logic ops are enabled instead const bool blendingEnabled = (regs[PICA::InternalRegs::ColourOperation] & (1 << 8)) != 0; - if (!blendingEnabled) { // Logic ops are enabled + if (!blendingEnabled) { // Logic ops are enabled const u32 logicOp = getBits<0, 4>(regs[PICA::InternalRegs::LogicOp]); gl.setLogicOp(logicOps[logicOp]); @@ -268,21 +269,12 @@ void RendererGL::setupStencilTest(bool stencilEnable) { return; } - static constexpr std::array stencilFuncs = { - GL_NEVER, - GL_ALWAYS, - GL_EQUAL, - GL_NOTEQUAL, - GL_LESS, - GL_LEQUAL, - GL_GREATER, - GL_GEQUAL - }; + static constexpr std::array stencilFuncs = {GL_NEVER, GL_ALWAYS, GL_EQUAL, GL_NOTEQUAL, GL_LESS, GL_LEQUAL, GL_GREATER, GL_GEQUAL}; gl.enableStencil(); const u32 stencilConfig = regs[PICA::InternalRegs::StencilTest]; const u32 stencilFunc = getBits<4, 3>(stencilConfig); - const s32 reference = s8(getBits<16, 8>(stencilConfig)); // Signed reference value + const s32 reference = s8(getBits<16, 8>(stencilConfig)); // Signed reference value const u32 stencilRefMask = getBits<24, 8>(stencilConfig); const bool stencilWrite = regs[PICA::InternalRegs::DepthBufferWrite]; @@ -293,15 +285,9 @@ void RendererGL::setupStencilTest(bool stencilEnable) { gl.setStencilMask(stencilBufferMask); static constexpr std::array stencilOps = { - GL_KEEP, - GL_ZERO, - GL_REPLACE, - GL_INCR, - GL_DECR, - GL_INVERT, - GL_INCR_WRAP, - GL_DECR_WRAP + GL_KEEP, GL_ZERO, GL_REPLACE, GL_INCR, GL_DECR, GL_INVERT, GL_INCR_WRAP, GL_DECR_WRAP, }; + const u32 stencilOpConfig = regs[PICA::InternalRegs::StencilOp]; const u32 stencilFailOp = getBits<0, 3>(stencilOpConfig); const u32 depthFailOp = getBits<4, 3>(stencilOpConfig); @@ -468,7 +454,10 @@ void RendererGL::drawVertices(PICA::PrimType primType, std::span v const int depthFunc = getBits<4, 3>(depthControl); const int colourMask = getBits<8, 4>(depthControl); gl.setColourMask(colourMask & 1, colourMask & 2, colourMask & 4, colourMask & 8); - static constexpr std::array depthModes = {GL_NEVER, GL_ALWAYS, GL_EQUAL, GL_NOTEQUAL, GL_LESS, GL_LEQUAL, GL_GREATER, GL_GEQUAL}; + + static constexpr std::array depthModes = { + GL_NEVER, GL_ALWAYS, GL_EQUAL, GL_NOTEQUAL, GL_LESS, GL_LEQUAL, GL_GREATER, GL_GEQUAL, + }; bindTexturesToSlots(); if (gpu.fogLUTDirty) { @@ -565,14 +554,14 @@ void RendererGL::display() { if (topScreen) { topScreen->get().texture.bind(); - OpenGL::setViewport(0, 240, 400, 240); // Top screen viewport - OpenGL::draw(OpenGL::TriangleStrip, 4); // Actually draw our 3DS screen + OpenGL::setViewport(0, 240, 400, 240); // Top screen viewport + OpenGL::draw(OpenGL::TriangleStrip, 4); // Actually draw our 3DS screen } const u32 bottomActiveFb = externalRegs[Framebuffer1Select] & 1; const u32 bottomScreenAddr = externalRegs[bottomActiveFb == 0 ? Framebuffer1AFirstAddr : Framebuffer1ASecondAddr]; auto bottomScreen = colourBufferCache.findFromAddress(bottomScreenAddr); - + if (bottomScreen) { bottomScreen->get().texture.bind(); OpenGL::setViewport(40, 0, 320, 240); @@ -583,32 +572,62 @@ void RendererGL::display() { glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); screenFramebuffer.bind(OpenGL::ReadFramebuffer); + constexpr auto layout = ScreenLayout::Layout::Default; if (outputSizeChanged) { outputSizeChanged = false; - const float srcAspect = 400.0f / 480.0f; // 3DS aspect ratio - const float dstAspect = float(outputWindowWidth) / float(outputWindowHeight); + // Get information about our new screen layout to use for blitting the output + ScreenLayout::WindowCoordinates windowCoords; + ScreenLayout::calculateCoordinates(windowCoords, outputWindowWidth, outputWindowHeight, layout); - blitInfo.destWidth = outputWindowWidth; - blitInfo.destHeight = outputWindowHeight; - blitInfo.destX = 0; - blitInfo.destY = 0; + blitInfo.topScreenX = windowCoords.topScreenX; + blitInfo.topScreenY = windowCoords.topScreenY; + blitInfo.topScreenWidth = windowCoords.topScreenWidth; + blitInfo.topScreenHeight = windowCoords.topScreenHeight; - if (dstAspect > srcAspect) { - // Window is wider than source - blitInfo.destWidth = int(outputWindowHeight * srcAspect + 0.5f); - blitInfo.destX = (outputWindowWidth - blitInfo.destWidth) / 2; - } else { - // Window is taller than source - blitInfo.destHeight = int(outputWindowWidth / srcAspect + 0.5f); - blitInfo.destY = (outputWindowHeight - blitInfo.destHeight) / 2; - } + blitInfo.bottomScreenX = windowCoords.bottomScreenX; + blitInfo.bottomScreenY = windowCoords.bottomScreenY; + blitInfo.bottomScreenWidth = windowCoords.bottomScreenWidth; + blitInfo.bottomScreenHeight = windowCoords.bottomScreenHeight; + + // Flip topScreenY and bottomScreenY because glBlitFramebuffer uses bottom-left origin + blitInfo.topScreenY = outputWindowHeight - (blitInfo.topScreenY + blitInfo.topScreenHeight); + blitInfo.topScreenY = outputWindowHeight - (blitInfo.bottomScreenY + blitInfo.bottomScreenHeight); + + // Used for optimizing the screen blit into a single blit + blitInfo.destX = windowCoords.singleBlitInfo.destX; + blitInfo.destY = windowCoords.singleBlitInfo.destY; + blitInfo.destWidth = windowCoords.singleBlitInfo.destWidth; + blitInfo.destHeight = windowCoords.singleBlitInfo.destHeight; + + // Check if we can blit the screens in 1 blit. If not, we'll break it into two. + blitInfo.canDoSingleBlit = + windowCoords.topScreenY + windowCoords.topScreenHeight == windowCoords.bottomScreenY && + windowCoords.bottomScreenX == windowCoords.topScreenX + int(ScreenLayout::BOTTOM_SCREEN_X_OFFSET * windowCoords.scale) && + windowCoords.topScreenWidth == u32(ScreenLayout::TOP_SCREEN_WIDTH * windowCoords.scale) && + windowCoords.bottomScreenWidth == u32(ScreenLayout::BOTTOM_SCREEN_WIDTH * windowCoords.scale) && + windowCoords.topScreenHeight == u32(ScreenLayout::TOP_SCREEN_HEIGHT * windowCoords.scale) && + windowCoords.bottomScreenHeight == u32(ScreenLayout::BOTTOM_SCREEN_HEIGHT * windowCoords.scale); } - glBlitFramebuffer( - 0, 0, 400, 480, blitInfo.destX, blitInfo.destY, blitInfo.destX + blitInfo.destWidth, blitInfo.destY + blitInfo.destHeight, - GL_COLOR_BUFFER_BIT, GL_LINEAR - ); + if (blitInfo.canDoSingleBlit) { + glBlitFramebuffer( + 0, 0, 400, 480, blitInfo.destX, blitInfo.destY, blitInfo.destX + blitInfo.destWidth, blitInfo.destY + blitInfo.destHeight, + GL_COLOR_BUFFER_BIT, GL_LINEAR + ); + } else { + // Blit top screen + glBlitFramebuffer( + 0, 240, 400, 480, blitInfo.topScreenX, blitInfo.topScreenY, blitInfo.topScreenX + blitInfo.topScreenWidth, + blitInfo.topScreenY + blitInfo.topScreenHeight, GL_COLOR_BUFFER_BIT, GL_LINEAR + ); + + // Blit bottom screen + glBlitFramebuffer( + 40, 0, 360, 240, blitInfo.bottomScreenX, blitInfo.bottomScreenY, blitInfo.bottomScreenX + blitInfo.bottomScreenWidth, + blitInfo.bottomScreenY + blitInfo.bottomScreenHeight, GL_COLOR_BUFFER_BIT, GL_LINEAR + ); + } } } @@ -735,8 +754,10 @@ void RendererGL::displayTransfer(u32 inputAddr, u32 outputAddr, u32 inputSize, u u32 outputWidth = outputSize & 0xffff; u32 outputHeight = outputSize >> 16; - OpenGL::DebugScope scope("DisplayTransfer inputAddr 0x%08X outputAddr 0x%08X inputWidth %d outputWidth %d inputHeight %d outputHeight %d", - inputAddr, outputAddr, inputWidth, outputWidth, inputHeight, outputHeight); + OpenGL::DebugScope scope( + "DisplayTransfer inputAddr 0x%08X outputAddr 0x%08X inputWidth %d outputWidth %d inputHeight %d outputHeight %d", inputAddr, outputAddr, + inputWidth, outputWidth, inputHeight, outputHeight + ); auto srcFramebuffer = getColourBuffer(inputAddr, inputFormat, inputWidth, outputHeight); Math::Rect srcRect = srcFramebuffer->getSubRect(inputAddr, outputWidth, outputHeight); @@ -786,8 +807,10 @@ void RendererGL::textureCopy(u32 inputAddr, u32 outputAddr, u32 totalBytes, u32 const u32 outputWidth = (outputSize & 0xffff) << 4; const u32 outputGap = (outputSize >> 16) << 4; - OpenGL::DebugScope scope("TextureCopy inputAddr 0x%08X outputAddr 0x%08X totalBytes %d inputWidth %d inputGap %d outputWidth %d outputGap %d", - inputAddr, outputAddr, totalBytes, inputWidth, inputGap, outputWidth, outputGap); + OpenGL::DebugScope scope( + "TextureCopy inputAddr 0x%08X outputAddr 0x%08X totalBytes %d inputWidth %d inputGap %d outputWidth %d outputGap %d", inputAddr, outputAddr, + totalBytes, inputWidth, inputGap, outputWidth, outputGap + ); if (inputGap != 0 || outputGap != 0) { // Helpers::warn("Strided texture copy\n"); @@ -825,7 +848,7 @@ void RendererGL::textureCopy(u32 inputAddr, u32 outputAddr, u32 totalBytes, u32 // Find the source surface. auto srcFramebuffer = getColourBuffer(inputAddr, PICA::ColorFmt::RGBA8, copyStride, copyHeight, false); if (!srcFramebuffer) { - static int shutUpCounter = 0; // Don't want to spam the console too much, so shut up after 5 times + static int shutUpCounter = 0; // Don't want to spam the console too much, so shut up after 5 times if (shutUpCounter < 5) { shutUpCounter++; @@ -1041,8 +1064,8 @@ bool RendererGL::prepareForDraw(ShaderUnit& shaderUnit, PICA::DrawAcceleration* driverInfo.usingGLES ? PICA::ShaderGen::API::GLES : PICA::ShaderGen::API::GL, PICA::ShaderGen::Language::GLSL ); - // Empty source means compilation error, if the source is not empty then we convert the recompiled PICA code into a valid shader and upload - // it to the GPU + // Empty source means compilation error, if the source is not empty then we convert the recompiled PICA code into a valid shader and + // upload it to the GPU if (!picaShaderSource.empty()) { std::string vertexShaderSource = fragShaderGen.getVertexShaderAccelerated(picaShaderSource, vertexConfig, usingUbershader); shader->create({vertexShaderSource}, OpenGL::Vertex); @@ -1073,7 +1096,7 @@ bool RendererGL::prepareForDraw(ShaderUnit& shaderUnit, PICA::DrawAcceleration* if (!usingUbershader) { OpenGL::Program& program = getSpecializedShader(); gl.useProgram(program); - } else { // Bind ubershader & load ubershader uniforms + } else { // Bind ubershader & load ubershader uniforms gl.useProgram(triangleProgram); const float depthScale = f24::fromRaw(regs[PICA::InternalRegs::DepthScale] & 0xffffff).toFloat32(); @@ -1243,7 +1266,7 @@ void RendererGL::accelerateVertexUpload(ShaderUnit& shaderUnit, PICA::DrawAccele const u32 currentAttributeMask = accel->enabledAttributeMask; // Use bitwise xor to calculate which attributes changed u32 attributeMaskDiff = currentAttributeMask ^ previousAttributeMask; - + while (attributeMaskDiff != 0) { // Get index of next different attribute and turn it off const u32 index = 31 - std::countl_zero(attributeMaskDiff); diff --git a/src/core/renderer_mtl/renderer_mtl.cpp b/src/core/renderer_mtl/renderer_mtl.cpp index bbc8212a..6914363e 100644 --- a/src/core/renderer_mtl/renderer_mtl.cpp +++ b/src/core/renderer_mtl/renderer_mtl.cpp @@ -10,6 +10,7 @@ #include "PICA/gpu.hpp" #include "PICA/pica_hash.hpp" +#include "PICA/screen_layout.hpp" #include "SDL_metal.h" using namespace PICA; @@ -105,26 +106,14 @@ void RendererMTL::display() { if (outputSizeChanged) { outputSizeChanged = false; + ScreenLayout::WindowCoordinates windowCoords; + ScreenLayout::calculateCoordinates(windowCoords, outputWindowWidth, outputWindowHeight, ScreenLayout::Layout::Default); - const float srcAspect = 400.0 / 480.0; - const float destAspect = float(outputWindowWidth) / float(outputWindowHeight); - int destX = 0, destY = 0, destWidth = outputWindowWidth, destHeight = outputWindowHeight; - - if (destAspect > srcAspect) { - // Window is wider than source - destWidth = int(outputWindowHeight * srcAspect + 0.5f); - destX = (outputWindowWidth - destWidth) / 2; - } else { - // Window is taller than source - destHeight = int(outputWindowWidth / srcAspect + 0.5f); - destY = (outputWindowHeight - destHeight) / 2; - } - - blitInfo.scale = float(destWidth) / 400.0f; - blitInfo.topScreenX = float(destX); - blitInfo.topScreenY = float(destY + (destHeight - int(480 * blitInfo.scale)) / 2); - blitInfo.bottomScreenX = float(destX) + 40 * blitInfo.scale; - blitInfo.bottomScreenY = blitInfo.topScreenY + 240 * blitInfo.scale; + blitInfo.scale = windowCoords.scale; + blitInfo.topScreenX = windowCoords.topScreenX; + blitInfo.topScreenY = windowCoords.topScreenY; + blitInfo.bottomScreenX = windowCoords.bottomScreenX; + blitInfo.bottomScreenY = windowCoords.bottomScreenY; } // Top screen @@ -800,7 +789,7 @@ void RendererMTL::updateLightingLUT(MTL::RenderCommandEncoder* encoder) { void RendererMTL::updateFogLUT(MTL::RenderCommandEncoder* encoder) { gpu.fogLUTDirty = false; - std::array fogLut = {0.0f}; + std::array fogLut = {0.0f}; for (int i = 0; i < fogLut.size(); i += 2) { const uint32_t value = gpu.fogLUT[i >> 1]; @@ -835,8 +824,11 @@ void RendererMTL::textureCopyImpl( commandEncoder.setRenderPipelineState(blitPipeline); // Viewport - renderCommandEncoder->setViewport(MTL::Viewport{ - double(destRect.left), double(destRect.bottom), double(destRect.right - destRect.left), double(destRect.top - destRect.bottom), 0.0, 1.0}); + renderCommandEncoder->setViewport( + MTL::Viewport{ + double(destRect.left), double(destRect.bottom), double(destRect.right - destRect.left), double(destRect.top - destRect.bottom), 0.0, 1.0 + } + ); float srcRectNDC[4] = { srcRect.left / (float)srcFramebuffer.size.u(), diff --git a/src/panda_qt/main_window.cpp b/src/panda_qt/main_window.cpp index a55684e0..aa162e4f 100644 --- a/src/panda_qt/main_window.cpp +++ b/src/panda_qt/main_window.cpp @@ -525,25 +525,28 @@ void MainWindow::handleTouchscreenPress(QMouseEvent* event) { const QPointF clickPos = event->globalPosition(); const QPointF widgetPos = screen->mapFromGlobal(clickPos); + const auto& coords = screen->screenCoordinates; + const float bottomScreenX = float(coords.bottomScreenX); + const float bottomScreenY = float(coords.bottomScreenY); + const float bottomScreenWidth = float(coords.bottomScreenWidth); + const float bottomScreenHeight = float(coords.bottomScreenHeight); + // Press is inside the screen area - if (widgetPos.x() >= 0 && widgetPos.x() < screen->width() && widgetPos.y() >= 0 && widgetPos.y() < screen->height()) { - // Go from widget positions to [0, 400) for x and [0, 480) for y - uint x = (uint)std::round(widgetPos.x() / screen->width() * 400.f); - uint y = (uint)std::round(widgetPos.y() / screen->height() * 480.f); + if (widgetPos.x() >= bottomScreenX && widgetPos.x() < bottomScreenX + bottomScreenWidth && widgetPos.y() >= bottomScreenY && + widgetPos.y() < bottomScreenY + bottomScreenHeight) { + // Map widget position to 3DS touchscreen coordinates, with (0, 0) = top left of touchscreen + float relX = (widgetPos.x() - bottomScreenX) / bottomScreenWidth; + float relY = (widgetPos.y() - bottomScreenY) / bottomScreenHeight; - // Check if touch falls in the touch screen area - if (y >= 240 && y <= 480 && x >= 40 && x < 40 + 320) { - // Convert to 3DS coordinates - u16 x_converted = static_cast(x) - 40; - u16 y_converted = static_cast(y) - 240; + u16 x_converted = u16(std::clamp(relX * ScreenLayout::BOTTOM_SCREEN_WIDTH, 0.f, float(ScreenLayout::BOTTOM_SCREEN_WIDTH - 1))); + u16 y_converted = u16(std::clamp(relY * ScreenLayout::BOTTOM_SCREEN_HEIGHT, 0.f, float(ScreenLayout::BOTTOM_SCREEN_HEIGHT - 1))); - EmulatorMessage message{.type = MessageType::PressTouchscreen}; - message.touchscreen.x = x_converted; - message.touchscreen.y = y_converted; - sendMessage(message); - } else { - sendMessage(EmulatorMessage{.type = MessageType::ReleaseTouchscreen}); - } + EmulatorMessage message{.type = MessageType::PressTouchscreen}; + message.touchscreen.x = x_converted; + message.touchscreen.y = y_converted; + sendMessage(message); + } else { + sendMessage(EmulatorMessage{.type = MessageType::ReleaseTouchscreen}); } } diff --git a/src/panda_qt/screen.cpp b/src/panda_qt/screen.cpp index fc783683..14869ef2 100644 --- a/src/panda_qt/screen.cpp +++ b/src/panda_qt/screen.cpp @@ -21,7 +21,7 @@ ScreenWidget::ScreenWidget(ResizeCallback resizeCallback, QWidget* parent) : QWidget(parent), resizeCallback(resizeCallback) { // Create a native window for use with our graphics API of choice resize(800, 240 * 4); - + setAutoFillBackground(false); setAttribute(Qt::WA_NativeWindow, true); setAttribute(Qt::WA_NoSystemBackground, true); @@ -47,6 +47,8 @@ void ScreenWidget::resizeEvent(QResizeEvent* event) { this->windowInfo = *windowInfo; } + ScreenLayout::calculateCoordinates(screenCoordinates, u32(width()), u32(height()), ScreenLayout::Layout::Default); + // This will call take care of calling resizeSurface from the emulator thread resizeCallback(surfaceWidth, surfaceHeight); } diff --git a/src/panda_sdl/frontend_sdl.cpp b/src/panda_sdl/frontend_sdl.cpp index 665b2512..de4d1dc6 100644 --- a/src/panda_sdl/frontend_sdl.cpp +++ b/src/panda_sdl/frontend_sdl.cpp @@ -230,24 +230,7 @@ void FrontendSDL::run() { if (emu.romType == ROMType::None) break; if (event.button.button == SDL_BUTTON_LEFT) { - if (windowWidth == 0 || windowHeight == 0) [[unlikely]] { - break; - } - - // Go from window positions to [0, 400) for x and [0, 480) for y - const s32 x = (s32)std::round(event.button.x * 400.f / windowWidth); - const s32 y = (s32)std::round(event.button.y * 480.f / windowHeight); - - // Check if touch falls in the touch screen area - if (y >= 240 && y <= 480 && x >= 40 && x < 40 + 320) { - // Convert to 3DS coordinates - u16 x_converted = static_cast(x) - 40; - u16 y_converted = static_cast(y) - 240; - - hid.setTouchScreenPress(x_converted, y_converted); - } else { - hid.releaseTouchScreen(); - } + handleLeftClick(event.button.x, event.button.y); } else if (event.button.button == SDL_BUTTON_RIGHT) { holdingRightClick = true; } @@ -321,18 +304,7 @@ void FrontendSDL::run() { break; } - // Go from window positions to [0, 400) for x and [0, 480) for y - const s32 x = (s32)std::round(event.motion.x * 400.f / windowWidth); - const s32 y = (s32)std::round(event.motion.y * 480.f / windowHeight); - - // Check if touch falls in the touch screen area and register the new touch screen position - if (y >= 240 && y <= 480 && x >= 40 && x < 40 + 320) { - // Convert to 3DS coordinates - u16 x_converted = static_cast(x) - 40; - u16 y_converted = static_cast(y) - 240; - - hid.setTouchScreenPress(x_converted, y_converted); - } + handleLeftClick(event.motion.x, event.motion.y); } // We use right click to indicate we want to rotate the console. If right click is not held, then this is not a gyroscope rotation @@ -393,6 +365,8 @@ void FrontendSDL::run() { if (type == SDL_WINDOWEVENT_RESIZED) { windowWidth = event.window.data1; windowHeight = event.window.data2; + ScreenLayout::calculateCoordinates(screenCoordinates, u32(windowWidth), u32(windowHeight), ScreenLayout::Layout::Default); + emu.setOutputSize(windowWidth, windowHeight); } } @@ -471,3 +445,31 @@ void FrontendSDL::setupControllerSensors(SDL_GameController* controller) { SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_ACCEL, SDL_TRUE); } } + +void FrontendSDL::handleLeftClick(int mouseX, int mouseY) { + if (windowWidth == 0 || windowHeight == 0) [[unlikely]] { + return; + } + + const auto& coords = screenCoordinates; + const int bottomScreenX = int(coords.bottomScreenX); + const int bottomScreenY = int(coords.bottomScreenY); + const int bottomScreenWidth = int(coords.bottomScreenWidth); + const int bottomScreenHeight = int(coords.bottomScreenHeight); + auto& hid = emu.getServiceManager().getHID(); + + // Check if the mouse is inside the bottom screen area + if (mouseX >= int(bottomScreenX) && mouseX < int(bottomScreenX + bottomScreenWidth) && mouseY >= int(bottomScreenY) && + mouseY < int(bottomScreenY + bottomScreenHeight)) { + // Map to 3DS touchscreen coordinates + float relX = float(mouseX - bottomScreenX) / float(bottomScreenWidth); + float relY = float(mouseY - bottomScreenY) / float(bottomScreenHeight); + + u16 x_converted = static_cast(std::clamp(relX * ScreenLayout::BOTTOM_SCREEN_WIDTH, 0.f, float(ScreenLayout::BOTTOM_SCREEN_WIDTH - 1))); + u16 y_converted = static_cast(std::clamp(relY * ScreenLayout::BOTTOM_SCREEN_HEIGHT, 0.f, float(ScreenLayout::BOTTOM_SCREEN_HEIGHT - 1))); + + hid.setTouchScreenPress(x_converted, y_converted); + } else { + hid.releaseTouchScreen(); + } +} \ No newline at end of file From 1c0f65c74051136845f3bb453b7300001e25528e Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Sat, 5 Jul 2025 02:36:03 +0300 Subject: [PATCH 2/5] Screen layouts: Add configurable screen sizes --- include/PICA/screen_layout.hpp | 147 ++++++++++++------------- include/renderer_mtl/renderer_mtl.hpp | 6 +- src/core/renderer_gl/renderer_gl.cpp | 10 +- src/core/renderer_mtl/renderer_mtl.cpp | 18 +-- 4 files changed, 92 insertions(+), 89 deletions(-) diff --git a/include/PICA/screen_layout.hpp b/include/PICA/screen_layout.hpp index e7ff89b5..8a42aba9 100644 --- a/include/PICA/screen_layout.hpp +++ b/include/PICA/screen_layout.hpp @@ -42,115 +42,114 @@ namespace ScreenLayout { int destWidth = TOP_SCREEN_WIDTH; int destHeight = CONSOLE_HEIGHT; } singleBlitInfo; - - float scale = 1.0f; }; // Calculate screen coordinates on the screen for a given layout & a given size for the output window // Used in both the renderers and in the frontends (To eg calculate touch screen boundaries) static void calculateCoordinates(WindowCoordinates& coordinates, u32 outputWindowWidth, u32 outputWindowHeight, Layout layout) { layout = Layout::SideBySideFlipped; + const float topScreenPercentage = 0.5f; + const float destAspect = float(outputWindowWidth) / float(outputWindowHeight); if (layout == Layout::Default || layout == Layout::DefaultFlipped) { - const float srcAspect = 400.0 / 480.0; + // Calculate available height for each screen based on split + int availableTopHeight = int(outputWindowHeight * topScreenPercentage + 0.5f); + int availableBottomHeight = outputWindowHeight - availableTopHeight; - int destX = 0, destY = 0, destWidth = outputWindowWidth, destHeight = outputWindowHeight; + // Calculate scales for top and bottom screens, and then the actual sizes + float scaleTopX = float(outputWindowWidth) / float(TOP_SCREEN_WIDTH); + float scaleTopY = float(availableTopHeight) / float(TOP_SCREEN_HEIGHT); + float scaleTop = std::min(scaleTopX, scaleTopY); - if (destAspect > srcAspect) { - // Window is wider than source - destWidth = int(outputWindowHeight * srcAspect + 0.5f); - destX = (outputWindowWidth - destWidth) / 2; - } else { - // Window is taller than source - destHeight = int(outputWindowWidth / srcAspect + 0.5f); - destY = (outputWindowHeight - destHeight) / 2; - } + float scaleBottomX = float(outputWindowWidth) / float(BOTTOM_SCREEN_WIDTH); + float scaleBottomY = float(availableBottomHeight) / float(BOTTOM_SCREEN_HEIGHT); + float scaleBottom = std::min(scaleBottomX, scaleBottomY); - // How much we'll scale the output by - const float scale = float(destWidth) / float(TOP_SCREEN_WIDTH); + int topScreenWidth = int(TOP_SCREEN_WIDTH * scaleTop + 0.5f); + int topScreenHeight = int(TOP_SCREEN_HEIGHT * scaleTop + 0.5f); - // Calculate coordinates and return them - // TODO: This will break when we allow screens to be scaled separately - coordinates.topScreenX = u32(destX); - coordinates.topScreenWidth = u32(float(TOP_SCREEN_WIDTH) * scale); - coordinates.bottomScreenX = u32(destX) + BOTTOM_SCREEN_X_OFFSET * scale; - coordinates.bottomScreenWidth = u32(float(BOTTOM_SCREEN_WIDTH) * scale); + int bottomScreenWidth = int(BOTTOM_SCREEN_WIDTH * scaleBottom + 0.5f); + int bottomScreenHeight = int(BOTTOM_SCREEN_HEIGHT * scaleBottom + 0.5f); + + // Center screens horizontally + int topScreenX = (outputWindowWidth - topScreenWidth) / 2; + int bottomScreenX = (outputWindowWidth - bottomScreenWidth) / 2; + + coordinates.topScreenWidth = topScreenWidth; + coordinates.topScreenHeight = topScreenHeight; + coordinates.bottomScreenWidth = bottomScreenWidth; + coordinates.bottomScreenHeight = bottomScreenHeight; + + coordinates.topScreenX = topScreenX; + coordinates.bottomScreenX = bottomScreenX; if (layout == Layout::Default) { - coordinates.topScreenY = u32(destY + (destHeight - int(CONSOLE_HEIGHT * scale)) / 2); - coordinates.topScreenHeight = u32(float(TOP_SCREEN_HEIGHT) * scale); - - coordinates.bottomScreenY = coordinates.topScreenY + TOP_SCREEN_HEIGHT * scale; - coordinates.bottomScreenHeight = coordinates.topScreenHeight; + coordinates.topScreenY = 0; + coordinates.bottomScreenY = topScreenHeight; } else { - // Flip the screens vertically - coordinates.bottomScreenY = u32(destY + (destHeight - int(CONSOLE_HEIGHT * scale)) / 2); - coordinates.bottomScreenHeight = u32(float(BOTTOM_SCREEN_HEIGHT) * scale); - - coordinates.topScreenY = coordinates.bottomScreenY + TOP_SCREEN_HEIGHT * scale; - coordinates.topScreenHeight = coordinates.bottomScreenHeight; + coordinates.bottomScreenY = 0; + coordinates.topScreenY = bottomScreenHeight; } coordinates.windowWidth = outputWindowWidth; coordinates.windowHeight = outputWindowHeight; - coordinates.scale = scale; - coordinates.singleBlitInfo.destX = destX; - coordinates.singleBlitInfo.destY = destY; - coordinates.singleBlitInfo.destWidth = destWidth; - coordinates.singleBlitInfo.destHeight = destHeight; - } else if (layout == Layout::SideBySide || layout == Layout::SideBySideFlipped) { - // For side-by-side layouts, the 3DS aspect ratio is calculated as (top width + bottom width) / height - const float srcAspect = float(TOP_SCREEN_WIDTH + BOTTOM_SCREEN_WIDTH) / float(CONSOLE_HEIGHT); - - int destX = 0, destY = 0, destWidth = outputWindowWidth, destHeight = outputWindowHeight; - - if (destAspect > srcAspect) { - // Window is wider than the side-by-side layout — center horizontally - destWidth = int(outputWindowHeight * srcAspect + 0.5f); - destX = (outputWindowWidth - destWidth) / 2; + // Default layout can be rendered using a single blit, flipped layout can't + if (layout == Layout::Default) { + coordinates.singleBlitInfo.destX = coordinates.topScreenX; + coordinates.singleBlitInfo.destY = coordinates.topScreenY; + coordinates.singleBlitInfo.destWidth = coordinates.topScreenWidth; + coordinates.singleBlitInfo.destHeight = coordinates.topScreenHeight + coordinates.bottomScreenHeight; } else { - // Window is taller — center vertically - destHeight = int(outputWindowWidth / srcAspect + 0.5f); - destY = (outputWindowHeight - destHeight) / 2; + // Dummy data for the single blit info, as we can't render the screen in 1 blit when the screens are side-by-sid + coordinates.singleBlitInfo.destX = 0; + coordinates.singleBlitInfo.destY = 0; + coordinates.singleBlitInfo.destWidth = 0; + coordinates.singleBlitInfo.destHeight = 0; } + } else if (layout == Layout::SideBySide || layout == Layout::SideBySideFlipped) { + // Calculate available width for each screen based on split + int availableTopWidth = int(outputWindowWidth * topScreenPercentage + 0.5f); + int availableBottomWidth = outputWindowWidth - availableTopWidth; - // How much we'll scale the output by. Again, we want to take both screens into account - const float scale = float(destWidth) / float(TOP_SCREEN_WIDTH + BOTTOM_SCREEN_WIDTH); + // Calculate scales for top and bottom screens, and then the actual sizes + float scaleTop = std::min(float(availableTopWidth) / float(TOP_SCREEN_WIDTH), float(outputWindowHeight) / float(TOP_SCREEN_HEIGHT)); + float scaleBottom = + std::min(float(availableBottomWidth) / float(BOTTOM_SCREEN_WIDTH), float(outputWindowHeight) / float(BOTTOM_SCREEN_HEIGHT)); - // Annoyingly, the top screen is wider than the bottom screen, so to display them side-by-side and centered - // vertically, we have to account for that + int topScreenWidth = int(TOP_SCREEN_WIDTH * scaleTop + 0.5f); + int topScreenHeight = int(TOP_SCREEN_HEIGHT * scaleTop + 0.5f); + int bottomScreenWidth = int(BOTTOM_SCREEN_WIDTH * scaleBottom + 0.5f); + int bottomScreenHeight = int(BOTTOM_SCREEN_HEIGHT * scaleBottom + 0.5f); - // The top screen is currently always larger, but it's still best to check which screen is taller anyways - // Since eventually we'll want to let users scale each screen separately. - const int topHeightScaled = int(TOP_SCREEN_HEIGHT * scale + 0.5f); - const int bottomHeightScaled = int(BOTTOM_SCREEN_HEIGHT * scale + 0.5f); - const int maxHeight = std::max(topHeightScaled, bottomHeightScaled); - const int centerY = destY + (destHeight - maxHeight) / 2; + // Vertically center the tallest screen + int maxHeight = std::max(topScreenHeight, bottomScreenHeight); + int baseY = (outputWindowHeight - maxHeight) / 2; - coordinates.topScreenY = u32(centerY + (maxHeight - topHeightScaled) / 2); - coordinates.topScreenWidth = u32(TOP_SCREEN_WIDTH * scale); - coordinates.topScreenHeight = u32(TOP_SCREEN_HEIGHT * scale); - - coordinates.bottomScreenY = u32(centerY + (maxHeight - bottomHeightScaled) / 2); - coordinates.bottomScreenWidth = u32(BOTTOM_SCREEN_WIDTH * scale); - coordinates.bottomScreenHeight = u32(BOTTOM_SCREEN_HEIGHT * scale); + int topScreenY = baseY + (maxHeight - topScreenHeight) / 2; + int bottomScreenY = baseY + (maxHeight - bottomScreenHeight) / 2; if (layout == Layout::SideBySide) { - coordinates.topScreenX = destX; - coordinates.bottomScreenX = destX + coordinates.topScreenWidth; + coordinates.topScreenX = (outputWindowWidth - (topScreenWidth + bottomScreenWidth)) / 2; + coordinates.bottomScreenX = coordinates.topScreenX + topScreenWidth; } else { - // Flip the screens horizontally - coordinates.bottomScreenX = destX; - coordinates.topScreenX = destX + coordinates.bottomScreenWidth; + coordinates.bottomScreenX = (outputWindowWidth - (topScreenWidth + bottomScreenWidth)) / 2; + coordinates.topScreenX = coordinates.bottomScreenX + bottomScreenWidth; } + coordinates.topScreenY = topScreenY; + coordinates.topScreenWidth = topScreenWidth; + coordinates.topScreenHeight = topScreenHeight; + + coordinates.bottomScreenY = bottomScreenY; + coordinates.bottomScreenWidth = bottomScreenWidth; + coordinates.bottomScreenHeight = bottomScreenHeight; + coordinates.windowWidth = outputWindowWidth; coordinates.windowHeight = outputWindowHeight; - coordinates.scale = scale; - // Set singleBlitInfo values to dummy values. Side-by-side screens can't be rendere in only 1 blit. + // Dummy data for the single blit info, as we can't render the screen in 1 blit when the screens are side-by-side coordinates.singleBlitInfo.destX = 0; coordinates.singleBlitInfo.destY = 0; coordinates.singleBlitInfo.destWidth = 0; diff --git a/include/renderer_mtl/renderer_mtl.hpp b/include/renderer_mtl/renderer_mtl.hpp index f9c256f0..c9b4b9b6 100644 --- a/include/renderer_mtl/renderer_mtl.hpp +++ b/include/renderer_mtl/renderer_mtl.hpp @@ -93,9 +93,13 @@ class RendererMTL final : public Renderer { struct { float topScreenX = 0; float topScreenY = 0; + float topScreenWidth = 400; + float topScreenHeight = 240; + float bottomScreenX = 40; float bottomScreenY = 240; - float scale = 1.0; + float bottomScreenWidth = 320; + float bottomScreenHeight = 240; } blitInfo; // Debug diff --git a/src/core/renderer_gl/renderer_gl.cpp b/src/core/renderer_gl/renderer_gl.cpp index 39995f03..8a189aed 100644 --- a/src/core/renderer_gl/renderer_gl.cpp +++ b/src/core/renderer_gl/renderer_gl.cpp @@ -592,7 +592,7 @@ void RendererGL::display() { // Flip topScreenY and bottomScreenY because glBlitFramebuffer uses bottom-left origin blitInfo.topScreenY = outputWindowHeight - (blitInfo.topScreenY + blitInfo.topScreenHeight); - blitInfo.topScreenY = outputWindowHeight - (blitInfo.bottomScreenY + blitInfo.bottomScreenHeight); + blitInfo.bottomScreenY = outputWindowHeight - (blitInfo.bottomScreenY + blitInfo.bottomScreenHeight); // Used for optimizing the screen blit into a single blit blitInfo.destX = windowCoords.singleBlitInfo.destX; @@ -601,13 +601,9 @@ void RendererGL::display() { blitInfo.destHeight = windowCoords.singleBlitInfo.destHeight; // Check if we can blit the screens in 1 blit. If not, we'll break it into two. + // TODO: Maybe add some size-related checks too. blitInfo.canDoSingleBlit = - windowCoords.topScreenY + windowCoords.topScreenHeight == windowCoords.bottomScreenY && - windowCoords.bottomScreenX == windowCoords.topScreenX + int(ScreenLayout::BOTTOM_SCREEN_X_OFFSET * windowCoords.scale) && - windowCoords.topScreenWidth == u32(ScreenLayout::TOP_SCREEN_WIDTH * windowCoords.scale) && - windowCoords.bottomScreenWidth == u32(ScreenLayout::BOTTOM_SCREEN_WIDTH * windowCoords.scale) && - windowCoords.topScreenHeight == u32(ScreenLayout::TOP_SCREEN_HEIGHT * windowCoords.scale) && - windowCoords.bottomScreenHeight == u32(ScreenLayout::BOTTOM_SCREEN_HEIGHT * windowCoords.scale); + windowCoords.topScreenY + windowCoords.topScreenHeight == windowCoords.bottomScreenY && layout == ScreenLayout::Layout::Default; } if (blitInfo.canDoSingleBlit) { diff --git a/src/core/renderer_mtl/renderer_mtl.cpp b/src/core/renderer_mtl/renderer_mtl.cpp index 6914363e..7014b943 100644 --- a/src/core/renderer_mtl/renderer_mtl.cpp +++ b/src/core/renderer_mtl/renderer_mtl.cpp @@ -109,17 +109,21 @@ void RendererMTL::display() { ScreenLayout::WindowCoordinates windowCoords; ScreenLayout::calculateCoordinates(windowCoords, outputWindowWidth, outputWindowHeight, ScreenLayout::Layout::Default); - blitInfo.scale = windowCoords.scale; - blitInfo.topScreenX = windowCoords.topScreenX; - blitInfo.topScreenY = windowCoords.topScreenY; - blitInfo.bottomScreenX = windowCoords.bottomScreenX; - blitInfo.bottomScreenY = windowCoords.bottomScreenY; + blitInfo.topScreenX = float(windowCoords.topScreenX); + blitInfo.topScreenY = float(windowCoords.topScreenY); + blitInfo.bottomScreenX = float(windowCoords.bottomScreenX); + blitInfo.bottomScreenY = float(windowCoords.bottomScreenY); + + blitInfo.topScreenWidth = float(windowCoords.topScreenWidth); + blitInfo.topScreenHeight = float(windowCoords.topScreenHeight); + blitInfo.bottomScreenWidth = float(windowCoords.bottomScreenWidth); + blitInfo.bottomScreenHeight = float(windowCoords.bottomScreenHeight); } // Top screen if (topScreen) { renderCommandEncoder->setViewport( - MTL::Viewport{blitInfo.topScreenX, blitInfo.topScreenY, 400 * blitInfo.scale, 240 * blitInfo.scale, 0.0f, 1.0f} + MTL::Viewport{blitInfo.topScreenX, blitInfo.topScreenY, blitInfo.topScreenWidth, blitInfo.topScreenHeight, 0.0f, 1.0f} ); renderCommandEncoder->setFragmentTexture(topScreen->get().texture, 0); renderCommandEncoder->drawPrimitives(MTL::PrimitiveTypeTriangleStrip, NS::UInteger(0), NS::UInteger(4)); @@ -128,7 +132,7 @@ void RendererMTL::display() { // Bottom screen if (bottomScreen) { renderCommandEncoder->setViewport( - MTL::Viewport{blitInfo.bottomScreenX, blitInfo.bottomScreenY, 320 * blitInfo.scale, 240 * blitInfo.scale, 0.0f, 1.0f} + MTL::Viewport{blitInfo.bottomScreenX, blitInfo.bottomScreenY, blitInfo.bottomScreenWidth, blitInfo.bottomScreenHeight, 0.0f, 1.0f} ); renderCommandEncoder->setFragmentTexture(bottomScreen->get().texture, 0); renderCommandEncoder->drawPrimitives(MTL::PrimitiveTypeTriangleStrip, NS::UInteger(0), NS::UInteger(4)); From cf321b1ed8ae7bf4dd11f19a8db24194e7ad1f3e Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Sun, 6 Jul 2025 02:41:44 +0300 Subject: [PATCH 3/5] Better screen layout support --- CMakeLists.txt | 4 +- include/PICA/gpu.hpp | 8 +- include/PICA/screen_layout.hpp | 161 ------------------------- include/config.hpp | 4 + include/emulator.hpp | 2 + include/panda_qt/screen.hpp | 9 +- include/panda_sdl/frontend_sdl.hpp | 2 +- include/renderer.hpp | 1 + include/screen_layout.hpp | 58 +++++++++ src/config.cpp | 6 + src/core/renderer_gl/renderer_gl.cpp | 17 ++- src/core/renderer_mtl/renderer_mtl.cpp | 6 +- src/core/screen_layout.cpp | 155 ++++++++++++++++++++++++ src/panda_qt/config_window.cpp | 38 +++++- src/panda_qt/main_window.cpp | 19 ++- src/panda_qt/screen.cpp | 13 +- src/panda_sdl/frontend_sdl.cpp | 11 +- 17 files changed, 328 insertions(+), 186 deletions(-) delete mode 100644 include/PICA/screen_layout.hpp create mode 100644 include/screen_layout.hpp create mode 100644 src/core/screen_layout.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index bba242e4..e50af1b8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -328,7 +328,7 @@ set(SOURCE_FILES src/emulator.cpp src/io_file.cpp src/config.cpp src/core/memory.cpp src/renderer.cpp src/core/renderer_null/renderer_null.cpp src/http_server.cpp src/stb_image_write.c src/core/cheats.cpp src/core/action_replay.cpp src/discord_rpc.cpp src/lua.cpp src/memory_mapped_file.cpp src/renderdoc.cpp - src/frontend_settings.cpp src/miniaudio/miniaudio.cpp + src/frontend_settings.cpp src/miniaudio/miniaudio.cpp src/core/screen_layout.cpp ) set(CRYPTO_SOURCE_FILES src/core/crypto/aes_engine.cpp) set(KERNEL_SOURCE_FILES src/core/kernel/kernel.cpp src/core/kernel/resource_limits.cpp @@ -416,7 +416,7 @@ set(HEADER_FILES include/emulator.hpp include/helpers.hpp include/termcolor.hpp include/fs/archive_twl_sound.hpp include/fs/archive_card_spi.hpp include/services/ns.hpp include/audio/audio_device.hpp include/audio/audio_device_interface.hpp include/audio/libretro_audio_device.hpp include/services/ir/ir_types.hpp include/services/ir/ir_device.hpp include/services/ir/circlepad_pro.hpp include/services/service_intercept.hpp - include/PICA/screen_layout.hpp + include/screen_layout.hpp ) if(IOS) diff --git a/include/PICA/gpu.hpp b/include/PICA/gpu.hpp index c168a9bf..77bfb8ed 100644 --- a/include/PICA/gpu.hpp +++ b/include/PICA/gpu.hpp @@ -89,6 +89,7 @@ class GPU { PICA::Vertex getImmediateModeVertex(); void getAcceleratedDrawInfo(PICA::DrawAcceleration& accel, bool indexed); + public: // 256 entries per LUT with each LUT as its own row forming a 2D image 256 * LUT_COUNT // Encoded in PICA native format @@ -134,6 +135,8 @@ class GPU { // Used for setting the size of the window we'll be outputting graphics to void setOutputSize(u32 width, u32 height) { renderer->setOutputSize(width, height); } + // Used for notifying the renderer the screen layout has changed + void reloadScreenLayout() { renderer->reloadScreenLayout(); } // TODO: Emulate the transfer engine & its registers // Then this can be emulated by just writing the appropriate values there @@ -181,6 +184,7 @@ class GPU { } Renderer* getRenderer() { return renderer.get(); } + private: // GPU external registers // We have them in the end of the struct for cache locality reasons. Tl;dr we want the more commonly used things to be packed in the start @@ -189,8 +193,8 @@ class GPU { ALWAYS_INLINE void setVsOutputMask(u32 val) { val &= 0xffff; - - // Avoid recomputing this if not necessary + + // Avoid recomputing this if not necessary if (oldVsOutputMask != val) [[unlikely]] { oldVsOutputMask = val; diff --git a/include/PICA/screen_layout.hpp b/include/PICA/screen_layout.hpp deleted file mode 100644 index 8a42aba9..00000000 --- a/include/PICA/screen_layout.hpp +++ /dev/null @@ -1,161 +0,0 @@ -#pragma once -#include - -#include "helpers.hpp" - -namespace ScreenLayout { - static constexpr u32 TOP_SCREEN_WIDTH = 400; - static constexpr u32 BOTTOM_SCREEN_WIDTH = 320; - static constexpr u32 TOP_SCREEN_HEIGHT = 240; - static constexpr u32 BOTTOM_SCREEN_HEIGHT = 240; - - // The bottom screen is less wide by 80 pixels, so we center it by offsetting it 40 pixels right - static constexpr u32 BOTTOM_SCREEN_X_OFFSET = 40; - static constexpr u32 CONSOLE_HEIGHT = TOP_SCREEN_HEIGHT + BOTTOM_SCREEN_HEIGHT; - - enum class Layout { - Default, // Top screen up, bottom screen down - DefaultFlipped, // Top screen down, bottom screen up - SideBySide, // Top screen left, bottom screen right, - SideBySideFlipped, // Top screen right, bottom screen left, - }; - - // For properly handling touchscreen, we have to remember what window coordinates our screens map to - // We also remember some more information that is useful to our renderers, particularly for the final screen blit. - struct WindowCoordinates { - u32 topScreenX = 0; - u32 topScreenY = 0; - u32 topScreenWidth = TOP_SCREEN_WIDTH; - u32 topScreenHeight = TOP_SCREEN_HEIGHT; - - u32 bottomScreenX = BOTTOM_SCREEN_X_OFFSET; - u32 bottomScreenY = TOP_SCREEN_HEIGHT; - u32 bottomScreenWidth = BOTTOM_SCREEN_WIDTH; - u32 bottomScreenHeight = BOTTOM_SCREEN_HEIGHT; - - u32 windowWidth = topScreenWidth; - u32 windowHeight = topScreenHeight + bottomScreenHeight; - - // Information used when optimizing the final screen blit into a single blit - struct { - int destX = 0, destY = 0; - int destWidth = TOP_SCREEN_WIDTH; - int destHeight = CONSOLE_HEIGHT; - } singleBlitInfo; - }; - - // Calculate screen coordinates on the screen for a given layout & a given size for the output window - // Used in both the renderers and in the frontends (To eg calculate touch screen boundaries) - static void calculateCoordinates(WindowCoordinates& coordinates, u32 outputWindowWidth, u32 outputWindowHeight, Layout layout) { - layout = Layout::SideBySideFlipped; - const float topScreenPercentage = 0.5f; - - const float destAspect = float(outputWindowWidth) / float(outputWindowHeight); - - if (layout == Layout::Default || layout == Layout::DefaultFlipped) { - // Calculate available height for each screen based on split - int availableTopHeight = int(outputWindowHeight * topScreenPercentage + 0.5f); - int availableBottomHeight = outputWindowHeight - availableTopHeight; - - // Calculate scales for top and bottom screens, and then the actual sizes - float scaleTopX = float(outputWindowWidth) / float(TOP_SCREEN_WIDTH); - float scaleTopY = float(availableTopHeight) / float(TOP_SCREEN_HEIGHT); - float scaleTop = std::min(scaleTopX, scaleTopY); - - float scaleBottomX = float(outputWindowWidth) / float(BOTTOM_SCREEN_WIDTH); - float scaleBottomY = float(availableBottomHeight) / float(BOTTOM_SCREEN_HEIGHT); - float scaleBottom = std::min(scaleBottomX, scaleBottomY); - - int topScreenWidth = int(TOP_SCREEN_WIDTH * scaleTop + 0.5f); - int topScreenHeight = int(TOP_SCREEN_HEIGHT * scaleTop + 0.5f); - - int bottomScreenWidth = int(BOTTOM_SCREEN_WIDTH * scaleBottom + 0.5f); - int bottomScreenHeight = int(BOTTOM_SCREEN_HEIGHT * scaleBottom + 0.5f); - - // Center screens horizontally - int topScreenX = (outputWindowWidth - topScreenWidth) / 2; - int bottomScreenX = (outputWindowWidth - bottomScreenWidth) / 2; - - coordinates.topScreenWidth = topScreenWidth; - coordinates.topScreenHeight = topScreenHeight; - coordinates.bottomScreenWidth = bottomScreenWidth; - coordinates.bottomScreenHeight = bottomScreenHeight; - - coordinates.topScreenX = topScreenX; - coordinates.bottomScreenX = bottomScreenX; - - if (layout == Layout::Default) { - coordinates.topScreenY = 0; - coordinates.bottomScreenY = topScreenHeight; - } else { - coordinates.bottomScreenY = 0; - coordinates.topScreenY = bottomScreenHeight; - } - - coordinates.windowWidth = outputWindowWidth; - coordinates.windowHeight = outputWindowHeight; - - // Default layout can be rendered using a single blit, flipped layout can't - if (layout == Layout::Default) { - coordinates.singleBlitInfo.destX = coordinates.topScreenX; - coordinates.singleBlitInfo.destY = coordinates.topScreenY; - coordinates.singleBlitInfo.destWidth = coordinates.topScreenWidth; - coordinates.singleBlitInfo.destHeight = coordinates.topScreenHeight + coordinates.bottomScreenHeight; - } else { - // Dummy data for the single blit info, as we can't render the screen in 1 blit when the screens are side-by-sid - coordinates.singleBlitInfo.destX = 0; - coordinates.singleBlitInfo.destY = 0; - coordinates.singleBlitInfo.destWidth = 0; - coordinates.singleBlitInfo.destHeight = 0; - } - } else if (layout == Layout::SideBySide || layout == Layout::SideBySideFlipped) { - // Calculate available width for each screen based on split - int availableTopWidth = int(outputWindowWidth * topScreenPercentage + 0.5f); - int availableBottomWidth = outputWindowWidth - availableTopWidth; - - // Calculate scales for top and bottom screens, and then the actual sizes - float scaleTop = std::min(float(availableTopWidth) / float(TOP_SCREEN_WIDTH), float(outputWindowHeight) / float(TOP_SCREEN_HEIGHT)); - float scaleBottom = - std::min(float(availableBottomWidth) / float(BOTTOM_SCREEN_WIDTH), float(outputWindowHeight) / float(BOTTOM_SCREEN_HEIGHT)); - - int topScreenWidth = int(TOP_SCREEN_WIDTH * scaleTop + 0.5f); - int topScreenHeight = int(TOP_SCREEN_HEIGHT * scaleTop + 0.5f); - int bottomScreenWidth = int(BOTTOM_SCREEN_WIDTH * scaleBottom + 0.5f); - int bottomScreenHeight = int(BOTTOM_SCREEN_HEIGHT * scaleBottom + 0.5f); - - // Vertically center the tallest screen - int maxHeight = std::max(topScreenHeight, bottomScreenHeight); - int baseY = (outputWindowHeight - maxHeight) / 2; - - int topScreenY = baseY + (maxHeight - topScreenHeight) / 2; - int bottomScreenY = baseY + (maxHeight - bottomScreenHeight) / 2; - - if (layout == Layout::SideBySide) { - coordinates.topScreenX = (outputWindowWidth - (topScreenWidth + bottomScreenWidth)) / 2; - coordinates.bottomScreenX = coordinates.topScreenX + topScreenWidth; - } else { - coordinates.bottomScreenX = (outputWindowWidth - (topScreenWidth + bottomScreenWidth)) / 2; - coordinates.topScreenX = coordinates.bottomScreenX + bottomScreenWidth; - } - - coordinates.topScreenY = topScreenY; - coordinates.topScreenWidth = topScreenWidth; - coordinates.topScreenHeight = topScreenHeight; - - coordinates.bottomScreenY = bottomScreenY; - coordinates.bottomScreenWidth = bottomScreenWidth; - coordinates.bottomScreenHeight = bottomScreenHeight; - - coordinates.windowWidth = outputWindowWidth; - coordinates.windowHeight = outputWindowHeight; - - // Dummy data for the single blit info, as we can't render the screen in 1 blit when the screens are side-by-side - coordinates.singleBlitInfo.destX = 0; - coordinates.singleBlitInfo.destY = 0; - coordinates.singleBlitInfo.destWidth = 0; - coordinates.singleBlitInfo.destHeight = 0; - } else { - Helpers::panic("Unimplemented screen layout"); - } - } -} // namespace ScreenLayout diff --git a/include/config.hpp b/include/config.hpp index bc18f287..b890e251 100644 --- a/include/config.hpp +++ b/include/config.hpp @@ -2,6 +2,7 @@ #include #include +#include "screen_layout.hpp" #include "audio/dsp_core.hpp" #include "frontend_settings.hpp" #include "renderer.hpp" @@ -69,6 +70,9 @@ struct EmulatorConfig { bool accelerateShaders = accelerateShadersDefault; bool hashTextures = hashTexturesDefault; + ScreenLayout::Layout screenLayout = ScreenLayout::Layout::Default; + float topScreenSize = 0.5; + bool accurateShaderMul = false; bool discordRpcEnabled = false; diff --git a/include/emulator.hpp b/include/emulator.hpp index 6b7f97fc..6b63a211 100644 --- a/include/emulator.hpp +++ b/include/emulator.hpp @@ -115,6 +115,8 @@ class Emulator { RomFS::DumpingResult dumpRomFS(const std::filesystem::path& path); void setOutputSize(u32 width, u32 height) { gpu.setOutputSize(width, height); } + void reloadScreenLayout() { gpu.reloadScreenLayout(); } + void deinitGraphicsContext() { gpu.deinitGraphicsContext(); } // Reloads some settings that require special handling, such as audio enable diff --git a/include/panda_qt/screen.hpp b/include/panda_qt/screen.hpp index f785ee57..4c1fc1a5 100644 --- a/include/panda_qt/screen.hpp +++ b/include/panda_qt/screen.hpp @@ -3,7 +3,7 @@ #include #include -#include "PICA/screen_layout.hpp" +#include "screen_layout.hpp" #include "gl/context.h" #include "window_info.h" @@ -33,6 +33,11 @@ class ScreenWidget : public QWidget { // Coordinates (x/y/width/height) for the two screens in window space, used for properly handling touchscreen regardless // of layout or resizing ScreenLayout::WindowCoordinates screenCoordinates; + // Screen layouts and sizes + ScreenLayout::Layout screenLayout = ScreenLayout::Layout::Default; + float topScreenSize = 0.5f; + + void reloadScreenLayout(ScreenLayout::Layout newLayout, float newTopScreenSize); private: std::unique_ptr glContext = nullptr; @@ -44,4 +49,6 @@ class ScreenWidget : public QWidget { int scaledWindowWidth() const; int scaledWindowHeight() const; std::optional getWindowInfo(); + + void reloadScreenCoordinates(); }; diff --git a/include/panda_sdl/frontend_sdl.hpp b/include/panda_sdl/frontend_sdl.hpp index af5b88ab..7bd9892f 100644 --- a/include/panda_sdl/frontend_sdl.hpp +++ b/include/panda_sdl/frontend_sdl.hpp @@ -4,7 +4,7 @@ #include -#include "PICA/screen_layout.hpp" +#include "screen_layout.hpp" #include "emulator.hpp" #include "input_mappings.hpp" diff --git a/include/renderer.hpp b/include/renderer.hpp index 753bc319..40f244db 100644 --- a/include/renderer.hpp +++ b/include/renderer.hpp @@ -130,4 +130,5 @@ class Renderer { void setConfig(EmulatorConfig* config) { emulatorConfig = config; } void setHashTextures(bool setting) { hashTextures = setting; } + void reloadScreenLayout() { outputSizeChanged = true; } }; diff --git a/include/screen_layout.hpp b/include/screen_layout.hpp new file mode 100644 index 00000000..fc3f1c40 --- /dev/null +++ b/include/screen_layout.hpp @@ -0,0 +1,58 @@ +#pragma once +#include + +#include "helpers.hpp" + +namespace ScreenLayout { + static constexpr u32 TOP_SCREEN_WIDTH = 400; + static constexpr u32 BOTTOM_SCREEN_WIDTH = 320; + static constexpr u32 TOP_SCREEN_HEIGHT = 240; + static constexpr u32 BOTTOM_SCREEN_HEIGHT = 240; + + // The bottom screen is less wide by 80 pixels, so we center it by offsetting it 40 pixels right + static constexpr u32 BOTTOM_SCREEN_X_OFFSET = 40; + static constexpr u32 CONSOLE_HEIGHT = TOP_SCREEN_HEIGHT + BOTTOM_SCREEN_HEIGHT; + + enum class Layout { + Default = 0, // Top screen up, bottom screen down + DefaultFlipped, // Top screen down, bottom screen up + SideBySide, // Top screen left, bottom screen right, + SideBySideFlipped, // Top screen right, bottom screen left, + }; + + // For properly handling touchscreen, we have to remember what window coordinates our screens map to + // We also remember some more information that is useful to our renderers, particularly for the final screen blit. + struct WindowCoordinates { + u32 topScreenX = 0; + u32 topScreenY = 0; + u32 topScreenWidth = TOP_SCREEN_WIDTH; + u32 topScreenHeight = TOP_SCREEN_HEIGHT; + + u32 bottomScreenX = BOTTOM_SCREEN_X_OFFSET; + u32 bottomScreenY = TOP_SCREEN_HEIGHT; + u32 bottomScreenWidth = BOTTOM_SCREEN_WIDTH; + u32 bottomScreenHeight = BOTTOM_SCREEN_HEIGHT; + + u32 windowWidth = topScreenWidth; + u32 windowHeight = topScreenHeight + bottomScreenHeight; + + // Information used when optimizing the final screen blit into a single blit + struct { + // Can we actually render both of the screens in a single blit? + bool canDoSingleBlit = false; + // Blit information used if we can + int destX = 0, destY = 0; + int destWidth = TOP_SCREEN_WIDTH; + int destHeight = CONSOLE_HEIGHT; + } singleBlitInfo; + }; + + // Calculate screen coordinates on the screen for a given layout & a given size for the output window + // Used in both the renderers and in the frontends (To eg calculate touch screen boundaries) + void calculateCoordinates( + WindowCoordinates& coordinates, u32 outputWindowWidth, u32 outputWindowHeight, float topScreenPercentage, Layout layout + ); + + Layout layoutFromString(std::string inString); + const char* layoutToString(Layout layout); +} // namespace ScreenLayout diff --git a/src/config.cpp b/src/config.cpp index 28fd7df0..f2cddaba 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -93,6 +93,10 @@ void EmulatorConfig::load() { lightShadergenThreshold = toml::find_or(gpu, "ShadergenLightThreshold", 1); hashTextures = toml::find_or(gpu, "HashTextures", hashTexturesDefault); enableRenderdoc = toml::find_or(gpu, "EnableRenderdoc", false); + + auto screenLayoutName = toml::find_or(gpu, "ScreenLayout", "Default"); + screenLayout = ScreenLayout::layoutFromString(screenLayoutName); + topScreenSize = float(std::clamp(toml::find_or(gpu, "TopScreenSize", 0.5), 0.0, 1.0)); } } @@ -194,6 +198,8 @@ void EmulatorConfig::save() { data["GPU"]["AccelerateShaders"] = accelerateShaders; data["GPU"]["EnableRenderdoc"] = enableRenderdoc; data["GPU"]["HashTextures"] = hashTextures; + data["GPU"]["ScreenLayout"] = std::string(ScreenLayout::layoutToString(screenLayout)); + data["GPU"]["TopScreenSize"] = double(topScreenSize); data["Audio"]["DSPEmulation"] = std::string(Audio::DSPCore::typeToString(dspType)); data["Audio"]["EnableAudio"] = audioEnabled; diff --git a/src/core/renderer_gl/renderer_gl.cpp b/src/core/renderer_gl/renderer_gl.cpp index 8a189aed..79417976 100644 --- a/src/core/renderer_gl/renderer_gl.cpp +++ b/src/core/renderer_gl/renderer_gl.cpp @@ -11,7 +11,7 @@ #include "PICA/pica_hash.hpp" #include "PICA/pica_simd.hpp" #include "PICA/regs.hpp" -#include "PICA/screen_layout.hpp" +#include "screen_layout.hpp" #include "PICA/shader_decompiler.hpp" #include "config.hpp" #include "math_util.hpp" @@ -570,15 +570,18 @@ void RendererGL::display() { if constexpr (!Helpers::isHydraCore()) { glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); + OpenGL::clearColor(); + screenFramebuffer.bind(OpenGL::ReadFramebuffer); - constexpr auto layout = ScreenLayout::Layout::Default; if (outputSizeChanged) { outputSizeChanged = false; + auto layout = emulatorConfig->screenLayout; + // Get information about our new screen layout to use for blitting the output ScreenLayout::WindowCoordinates windowCoords; - ScreenLayout::calculateCoordinates(windowCoords, outputWindowWidth, outputWindowHeight, layout); + ScreenLayout::calculateCoordinates(windowCoords, outputWindowWidth, outputWindowHeight, emulatorConfig->topScreenSize, layout); blitInfo.topScreenX = windowCoords.topScreenX; blitInfo.topScreenY = windowCoords.topScreenY; @@ -590,20 +593,16 @@ void RendererGL::display() { blitInfo.bottomScreenWidth = windowCoords.bottomScreenWidth; blitInfo.bottomScreenHeight = windowCoords.bottomScreenHeight; - // Flip topScreenY and bottomScreenY because glBlitFramebuffer uses bottom-left origin + // Flip topScreenY and bottomScreenY because glBlitFramebuffer uses bottom-left origin blitInfo.topScreenY = outputWindowHeight - (blitInfo.topScreenY + blitInfo.topScreenHeight); blitInfo.bottomScreenY = outputWindowHeight - (blitInfo.bottomScreenY + blitInfo.bottomScreenHeight); // Used for optimizing the screen blit into a single blit + blitInfo.canDoSingleBlit = windowCoords.singleBlitInfo.canDoSingleBlit; blitInfo.destX = windowCoords.singleBlitInfo.destX; blitInfo.destY = windowCoords.singleBlitInfo.destY; blitInfo.destWidth = windowCoords.singleBlitInfo.destWidth; blitInfo.destHeight = windowCoords.singleBlitInfo.destHeight; - - // Check if we can blit the screens in 1 blit. If not, we'll break it into two. - // TODO: Maybe add some size-related checks too. - blitInfo.canDoSingleBlit = - windowCoords.topScreenY + windowCoords.topScreenHeight == windowCoords.bottomScreenY && layout == ScreenLayout::Layout::Default; } if (blitInfo.canDoSingleBlit) { diff --git a/src/core/renderer_mtl/renderer_mtl.cpp b/src/core/renderer_mtl/renderer_mtl.cpp index 7014b943..71cdd616 100644 --- a/src/core/renderer_mtl/renderer_mtl.cpp +++ b/src/core/renderer_mtl/renderer_mtl.cpp @@ -10,7 +10,7 @@ #include "PICA/gpu.hpp" #include "PICA/pica_hash.hpp" -#include "PICA/screen_layout.hpp" +#include "screen_layout.hpp" #include "SDL_metal.h" using namespace PICA; @@ -107,7 +107,9 @@ void RendererMTL::display() { if (outputSizeChanged) { outputSizeChanged = false; ScreenLayout::WindowCoordinates windowCoords; - ScreenLayout::calculateCoordinates(windowCoords, outputWindowWidth, outputWindowHeight, ScreenLayout::Layout::Default); + ScreenLayout::calculateCoordinates( + windowCoords, outputWindowWidth, outputWindowHeight, emulatorConfig->topScreenSize, emulatorConfig->screenLayout + ); blitInfo.topScreenX = float(windowCoords.topScreenX); blitInfo.topScreenY = float(windowCoords.topScreenY); diff --git a/src/core/screen_layout.cpp b/src/core/screen_layout.cpp new file mode 100644 index 00000000..ba1a5318 --- /dev/null +++ b/src/core/screen_layout.cpp @@ -0,0 +1,155 @@ +#include "screen_layout.hpp" + +#include +#include +#include + +using namespace ScreenLayout; + +// Calculate screen coordinates on the screen for a given layout & a given size for the output window +// Used in both the renderers and in the frontends (To eg calculate touch screen boundaries) +void ScreenLayout::calculateCoordinates( + WindowCoordinates& coordinates, u32 outputWindowWidth, u32 outputWindowHeight, float topScreenPercentage, Layout layout +) { + const float destAspect = float(outputWindowWidth) / float(outputWindowHeight); + + if (layout == Layout::Default || layout == Layout::DefaultFlipped) { + // Calculate available height for each screen based on split + int availableTopHeight = int(outputWindowHeight * topScreenPercentage + 0.5f); + int availableBottomHeight = outputWindowHeight - availableTopHeight; + + // Calculate scales for top and bottom screens, and then the actual sizes + float scaleTopX = float(outputWindowWidth) / float(TOP_SCREEN_WIDTH); + float scaleTopY = float(availableTopHeight) / float(TOP_SCREEN_HEIGHT); + float scaleTop = std::min(scaleTopX, scaleTopY); + + float scaleBottomX = float(outputWindowWidth) / float(BOTTOM_SCREEN_WIDTH); + float scaleBottomY = float(availableBottomHeight) / float(BOTTOM_SCREEN_HEIGHT); + float scaleBottom = std::min(scaleBottomX, scaleBottomY); + + int topScreenWidth = int(TOP_SCREEN_WIDTH * scaleTop + 0.5f); + int topScreenHeight = int(TOP_SCREEN_HEIGHT * scaleTop + 0.5f); + int bottomScreenWidth = int(BOTTOM_SCREEN_WIDTH * scaleBottom + 0.5f); + int bottomScreenHeight = int(BOTTOM_SCREEN_HEIGHT * scaleBottom + 0.5f); + + // Center screens horizontally + int topScreenX = (outputWindowWidth - topScreenWidth) / 2; + int bottomScreenX = (outputWindowWidth - bottomScreenWidth) / 2; + + coordinates.topScreenWidth = topScreenWidth; + coordinates.topScreenHeight = topScreenHeight; + coordinates.bottomScreenWidth = bottomScreenWidth; + coordinates.bottomScreenHeight = bottomScreenHeight; + + coordinates.topScreenX = topScreenX; + coordinates.bottomScreenX = bottomScreenX; + + if (layout == Layout::Default) { + coordinates.topScreenY = 0; + coordinates.bottomScreenY = topScreenHeight; + } else { + coordinates.bottomScreenY = 0; + coordinates.topScreenY = bottomScreenHeight; + } + + coordinates.windowWidth = outputWindowWidth; + coordinates.windowHeight = outputWindowHeight; + + // Default layout can be rendered using a single blit, flipped layout can't + if (layout == Layout::Default) { + coordinates.singleBlitInfo.destX = coordinates.topScreenX; + coordinates.singleBlitInfo.destY = coordinates.topScreenY; + coordinates.singleBlitInfo.destWidth = coordinates.topScreenWidth; + coordinates.singleBlitInfo.destHeight = coordinates.topScreenHeight + coordinates.bottomScreenHeight; + } else { + // Dummy data for the single blit info, as we can't render the screen in 1 blit when the screens are side-by-sid + coordinates.singleBlitInfo.destX = 0; + coordinates.singleBlitInfo.destY = 0; + coordinates.singleBlitInfo.destWidth = 0; + coordinates.singleBlitInfo.destHeight = 0; + } + + // Check if we can blit the screens in 1 blit. If not, we'll break it into two. + // TODO: Maybe add some more size-related checks too. + coordinates.singleBlitInfo.canDoSingleBlit = layout == Layout::Default && topScreenPercentage == 0.5 && + coordinates.topScreenY + coordinates.topScreenHeight == coordinates.bottomScreenY; + } else if (layout == Layout::SideBySide || layout == Layout::SideBySideFlipped) { + // Calculate available width for each screen based on split + int availableTopWidth = int(outputWindowWidth * topScreenPercentage + 0.5f); + int availableBottomWidth = outputWindowWidth - availableTopWidth; + + // Calculate scales for top and bottom screens, and then the actual sizes + float scaleTop = std::min(float(availableTopWidth) / float(TOP_SCREEN_WIDTH), float(outputWindowHeight) / float(TOP_SCREEN_HEIGHT)); + float scaleBottom = + std::min(float(availableBottomWidth) / float(BOTTOM_SCREEN_WIDTH), float(outputWindowHeight) / float(BOTTOM_SCREEN_HEIGHT)); + + int topScreenWidth = int(TOP_SCREEN_WIDTH * scaleTop + 0.5f); + int topScreenHeight = int(TOP_SCREEN_HEIGHT * scaleTop + 0.5f); + int bottomScreenWidth = int(BOTTOM_SCREEN_WIDTH * scaleBottom + 0.5f); + int bottomScreenHeight = int(BOTTOM_SCREEN_HEIGHT * scaleBottom + 0.5f); + + // Vertically center the tallest screen + int maxHeight = std::max(topScreenHeight, bottomScreenHeight); + int baseY = (outputWindowHeight - maxHeight) / 2; + + int topScreenY = baseY + (maxHeight - topScreenHeight) / 2; + int bottomScreenY = baseY + (maxHeight - bottomScreenHeight) / 2; + + if (layout == Layout::SideBySide) { + coordinates.topScreenX = (outputWindowWidth - (topScreenWidth + bottomScreenWidth)) / 2; + coordinates.bottomScreenX = coordinates.topScreenX + topScreenWidth; + } else { + coordinates.bottomScreenX = (outputWindowWidth - (topScreenWidth + bottomScreenWidth)) / 2; + coordinates.topScreenX = coordinates.bottomScreenX + bottomScreenWidth; + } + + coordinates.topScreenY = topScreenY; + coordinates.topScreenWidth = topScreenWidth; + coordinates.topScreenHeight = topScreenHeight; + + coordinates.bottomScreenY = bottomScreenY; + coordinates.bottomScreenWidth = bottomScreenWidth; + coordinates.bottomScreenHeight = bottomScreenHeight; + + coordinates.windowWidth = outputWindowWidth; + coordinates.windowHeight = outputWindowHeight; + + // Dummy data for the single blit info, as we can't render the screen in 1 blit when the screens are side-by-side + coordinates.singleBlitInfo.canDoSingleBlit = false; + coordinates.singleBlitInfo.destX = 0; + coordinates.singleBlitInfo.destY = 0; + coordinates.singleBlitInfo.destWidth = 0; + coordinates.singleBlitInfo.destHeight = 0; + } else { + Helpers::panic("Unimplemented screen layout"); + } +} + +Layout ScreenLayout::layoutFromString(std::string inString) { + // Transform to lower-case to make the setting case-insensitive + std::transform(inString.begin(), inString.end(), inString.begin(), [](unsigned char c) { return std::tolower(c); }); + + static const std::unordered_map map = { + {"default", Layout::Default}, + {"defaultflipped", Layout::DefaultFlipped}, + {"sidebyside", Layout::SideBySide}, + {"sidebysideflipped", Layout::SideBySideFlipped}, + }; + + if (auto search = map.find(inString); search != map.end()) { + return search->second; + } + + printf("Invalid screen layout. Defaulting to Default\n"); + return Layout::Default; +} + +const char* ScreenLayout::layoutToString(Layout layout) { + switch (layout) { + case Layout::Default: return "default"; + case Layout::DefaultFlipped: return "defaultFlipped"; + case Layout::SideBySide: return "sideBySide"; + case Layout::SideBySideFlipped: return "sideBySideFlipped"; + default: return "invalid"; + } +} diff --git a/src/panda_qt/config_window.cpp b/src/panda_qt/config_window.cpp index 5360b12c..aecefbc5 100644 --- a/src/panda_qt/config_window.cpp +++ b/src/panda_qt/config_window.cpp @@ -240,6 +240,36 @@ ConfigWindow::ConfigWindow(ConfigCallback configCallback, MainWindowCallback win connectCheckbox(hashTextures, config.hashTextures); gpuLayout->addRow(hashTextures); + QComboBox* screenLayout = new QComboBox(); + screenLayout->addItem(tr("Default")); + screenLayout->addItem(tr("Default (Flipped)")); + screenLayout->addItem(tr("Side-by-Side")); + screenLayout->addItem(tr("Side-by-Side (Flipped)")); + screenLayout->setCurrentIndex(static_cast(config.screenLayout)); + connect(screenLayout, &QComboBox::currentIndexChanged, this, [&](int index) { + config.screenLayout = static_cast(index); + updateConfig(); + }); + gpuLayout->addRow(tr("Screen Layout"), screenLayout); + + // Screen size slider widgets + QLabel* topScreenSizeLabel = new QLabel(QString::number(int(config.topScreenSize * 100))); + QSlider* topScreenSizeSlider = new QSlider(Qt::Horizontal); + + topScreenSizeSlider->setRange(0, 100); + topScreenSizeSlider->setValue(int(config.topScreenSize * 100)); + connect(topScreenSizeSlider, &QSlider::valueChanged, this, [this, topScreenSizeLabel](int value) { + config.topScreenSize = float(value) / 100.0f; + topScreenSizeLabel->setText(QString::number(value)); + updateConfig(); + }); + + QHBoxLayout* screenSizeLayout = new QHBoxLayout(); + screenSizeLayout->setSpacing(4); + screenSizeLayout->addWidget(topScreenSizeSlider); + screenSizeLayout->addWidget(topScreenSizeLabel); + gpuLayout->addRow(tr("Top screen size (%)"), screenSizeLayout); + QCheckBox* forceShadergenForLights = new QCheckBox(tr("Force shadergen when rendering lights")); connectCheckbox(forceShadergenForLights, config.forceShadergenForLights); gpuLayout->addRow(forceShadergenForLights); @@ -302,7 +332,7 @@ ConfigWindow::ConfigWindow(ConfigCallback configCallback, MainWindowCallback win volumeSlider->setRange(0, 200); volumeSlider->setValue(int(config.audioDeviceConfig.volumeRaw * 100)); connect(volumeSlider, &QSlider::valueChanged, this, [this, volumeLabel](int value) { - config.audioDeviceConfig.volumeRaw = static_cast(value) / 100.0f; + config.audioDeviceConfig.volumeRaw = float(value) / 100.0f; volumeLabel->setText(QString::number(value)); updateConfig(); @@ -467,12 +497,14 @@ void ConfigWindow::setTheme(Theme theme) { p.setColor(QPalette::Highlight, QColor(42, 130, 218)); p.setColor(QPalette::HighlightedText, Qt::black); qApp->setPalette(p); - qApp->setStyleSheet("QLineEdit {" + qApp->setStyleSheet( + "QLineEdit {" "background-color: #000000; color: #ffffff; border: 1px solid #a0a0a0; " "border-radius: 4px; padding: 5px; }" "QCheckBox::indicator:unchecked {" - "border: 1px solid #808080; border-radius: 4px; }"); + "border: 1px solid #808080; border-radius: 4px; }" + ); break; } diff --git a/src/panda_qt/main_window.cpp b/src/panda_qt/main_window.cpp index aa162e4f..d29b9875 100644 --- a/src/panda_qt/main_window.cpp +++ b/src/panda_qt/main_window.cpp @@ -129,6 +129,8 @@ MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent) if (config.printAppVersion) { printf("Welcome to Panda3DS v%s!\n", PANDA3DS_VERSION); } + + screen->reloadScreenLayout(config.screenLayout, config.topScreenSize); } // The emulator graphics context for the thread should be initialized in the emulator thread due to how GL contexts work @@ -435,13 +437,24 @@ void MainWindow::dispatchMessage(const EmulatorMessage& message) { break; } - case MessageType::UpdateConfig: - emu->getConfig() = configWindow->getConfig(); + case MessageType::UpdateConfig: { + auto& emuConfig = emu->getConfig(); + auto& newConfig = configWindow->getConfig(); + // If the screen layout changed, we have to notify the emulator & the screen widget + bool reloadScreenLayout = (emuConfig.screenLayout != newConfig.screenLayout || emuConfig.topScreenSize != newConfig.topScreenSize); + + emuConfig = newConfig; emu->reloadSettings(); + if (reloadScreenLayout) { + emu->reloadScreenLayout(); + screen->reloadScreenLayout(newConfig.screenLayout, newConfig.topScreenSize); + } + // Save new settings to disk - emu->getConfig().save(); + emuConfig.save(); break; + } } } diff --git a/src/panda_qt/screen.cpp b/src/panda_qt/screen.cpp index 14869ef2..0876bb71 100644 --- a/src/panda_qt/screen.cpp +++ b/src/panda_qt/screen.cpp @@ -47,7 +47,7 @@ void ScreenWidget::resizeEvent(QResizeEvent* event) { this->windowInfo = *windowInfo; } - ScreenLayout::calculateCoordinates(screenCoordinates, u32(width()), u32(height()), ScreenLayout::Layout::Default); + reloadScreenCoordinates(); // This will call take care of calling resizeSurface from the emulator thread resizeCallback(surfaceWidth, surfaceHeight); @@ -62,6 +62,17 @@ void ScreenWidget::resizeSurface(u32 width, u32 height) { } } +void ScreenWidget::reloadScreenCoordinates() { + ScreenLayout::calculateCoordinates(screenCoordinates, u32(width()), u32(height()), topScreenSize, screenLayout); +} + +void ScreenWidget::reloadScreenLayout(ScreenLayout::Layout newLayout, float newTopScreenSize) { + screenLayout = newLayout; + topScreenSize = newTopScreenSize; + + reloadScreenCoordinates(); +} + bool ScreenWidget::createGLContext() { // List of GL context versions we will try. Anything 4.1+ is good for desktop OpenGL, and 3.1+ for OpenGL ES static constexpr std::array versionsToTry = { diff --git a/src/panda_sdl/frontend_sdl.cpp b/src/panda_sdl/frontend_sdl.cpp index de4d1dc6..8033165a 100644 --- a/src/panda_sdl/frontend_sdl.cpp +++ b/src/panda_sdl/frontend_sdl.cpp @@ -55,7 +55,12 @@ FrontendSDL::FrontendSDL() : keyboardMappings(InputMappings::defaultKeyboardMapp windowWidth = 400; windowHeight = 480; } + + // Initialize output size and screen layout emu.setOutputSize(windowWidth, windowHeight); + ScreenLayout::calculateCoordinates( + screenCoordinates, u32(windowWidth), u32(windowHeight), emu.getConfig().topScreenSize, emu.getConfig().screenLayout + ); if (needOpenGL) { // Demand 4.1 core for OpenGL renderer (max available on MacOS), 3.3 for the software & null renderers @@ -365,7 +370,11 @@ void FrontendSDL::run() { if (type == SDL_WINDOWEVENT_RESIZED) { windowWidth = event.window.data1; windowHeight = event.window.data2; - ScreenLayout::calculateCoordinates(screenCoordinates, u32(windowWidth), u32(windowHeight), ScreenLayout::Layout::Default); + + const auto& config = emu.getConfig(); + ScreenLayout::calculateCoordinates( + screenCoordinates, u32(windowWidth), u32(windowHeight), emu.getConfig().topScreenSize, emu.getConfig().screenLayout + ); emu.setOutputSize(windowWidth, windowHeight); } From 4b90239ae7233cc8647d058d0f7725e05b7c646b Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Sun, 6 Jul 2025 03:04:40 +0300 Subject: [PATCH 4/5] Formatting fix --- src/core/renderer_gl/renderer_gl.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/core/renderer_gl/renderer_gl.cpp b/src/core/renderer_gl/renderer_gl.cpp index 79417976..57ccca18 100644 --- a/src/core/renderer_gl/renderer_gl.cpp +++ b/src/core/renderer_gl/renderer_gl.cpp @@ -269,7 +269,9 @@ void RendererGL::setupStencilTest(bool stencilEnable) { return; } - static constexpr std::array stencilFuncs = {GL_NEVER, GL_ALWAYS, GL_EQUAL, GL_NOTEQUAL, GL_LESS, GL_LEQUAL, GL_GREATER, GL_GEQUAL}; + static constexpr std::array stencilFuncs = { + GL_NEVER, GL_ALWAYS, GL_EQUAL, GL_NOTEQUAL, GL_LESS, GL_LEQUAL, GL_GREATER, GL_GEQUAL, + }; gl.enableStencil(); const u32 stencilConfig = regs[PICA::InternalRegs::StencilTest]; From 9527acebe6fa4b81b276aaa6f41e967a90895928 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Sun, 6 Jul 2025 03:49:02 +0300 Subject: [PATCH 5/5] More formatting fixes --- src/core/screen_layout.cpp | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/core/screen_layout.cpp b/src/core/screen_layout.cpp index ba1a5318..77318f17 100644 --- a/src/core/screen_layout.cpp +++ b/src/core/screen_layout.cpp @@ -19,13 +19,9 @@ void ScreenLayout::calculateCoordinates( int availableBottomHeight = outputWindowHeight - availableTopHeight; // Calculate scales for top and bottom screens, and then the actual sizes - float scaleTopX = float(outputWindowWidth) / float(TOP_SCREEN_WIDTH); - float scaleTopY = float(availableTopHeight) / float(TOP_SCREEN_HEIGHT); - float scaleTop = std::min(scaleTopX, scaleTopY); - - float scaleBottomX = float(outputWindowWidth) / float(BOTTOM_SCREEN_WIDTH); - float scaleBottomY = float(availableBottomHeight) / float(BOTTOM_SCREEN_HEIGHT); - float scaleBottom = std::min(scaleBottomX, scaleBottomY); + float scaleTop = std::min(float(outputWindowWidth) / float(TOP_SCREEN_WIDTH), float(availableTopHeight) / float(TOP_SCREEN_HEIGHT)); + float scaleBottom = + std::min(float(outputWindowWidth) / float(BOTTOM_SCREEN_WIDTH), float(availableBottomHeight) / float(BOTTOM_SCREEN_HEIGHT)); int topScreenWidth = int(TOP_SCREEN_WIDTH * scaleTop + 0.5f); int topScreenHeight = int(TOP_SCREEN_HEIGHT * scaleTop + 0.5f); @@ -90,10 +86,10 @@ void ScreenLayout::calculateCoordinates( // Vertically center the tallest screen int maxHeight = std::max(topScreenHeight, bottomScreenHeight); - int baseY = (outputWindowHeight - maxHeight) / 2; + int centerY = (outputWindowHeight - maxHeight) / 2; - int topScreenY = baseY + (maxHeight - topScreenHeight) / 2; - int bottomScreenY = baseY + (maxHeight - bottomScreenHeight) / 2; + int topScreenY = centerY + (maxHeight - topScreenHeight) / 2; + int bottomScreenY = centerY + (maxHeight - bottomScreenHeight) / 2; if (layout == Layout::SideBySide) { coordinates.topScreenX = (outputWindowWidth - (topScreenWidth + bottomScreenWidth)) / 2; @@ -153,3 +149,4 @@ const char* ScreenLayout::layoutToString(Layout layout) { default: return "invalid"; } } + \ No newline at end of file