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] 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