Initial screen layout implementation

This commit is contained in:
wheremyfoodat 2025-07-04 17:53:36 +03:00
parent d06f600b3a
commit 62748eef47
11 changed files with 335 additions and 126 deletions

View file

@ -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)

View file

@ -0,0 +1,162 @@
#pragma once
#include <algorithm>
#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

View file

@ -3,6 +3,7 @@
#include <functional>
#include <memory>
#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<GL::Context> glContext = nullptr;
ResizeCallback resizeCallback;

View file

@ -4,6 +4,7 @@
#include <filesystem>
#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);
};

View file

@ -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)

View file

@ -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;

View file

@ -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<GLenum, 8> stencilFuncs = {
GL_NEVER,
GL_ALWAYS,
GL_EQUAL,
GL_NOTEQUAL,
GL_LESS,
GL_LEQUAL,
GL_GREATER,
GL_GEQUAL
};
static constexpr std::array<GLenum, 8> 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<GLenum, 8> 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<const Vertex> 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<GLenum, 8> depthModes = {GL_NEVER, GL_ALWAYS, GL_EQUAL, GL_NOTEQUAL, GL_LESS, GL_LEQUAL, GL_GREATER, GL_GEQUAL};
static constexpr std::array<GLenum, 8> 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<u32> 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<u32>(attributeMaskDiff);

View file

@ -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<float, FOG_LUT_TEXTURE_WIDTH* 2> fogLut = {0.0f};
std::array<float, FOG_LUT_TEXTURE_WIDTH * 2> 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(),

View file

@ -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<u16>(x) - 40;
u16 y_converted = static_cast<u16>(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});
}
}

View file

@ -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);
}

View file

@ -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<u16>(x) - 40;
u16 y_converted = static_cast<u16>(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<u16>(x) - 40;
u16 y_converted = static_cast<u16>(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<u16>(std::clamp(relX * ScreenLayout::BOTTOM_SCREEN_WIDTH, 0.f, float(ScreenLayout::BOTTOM_SCREEN_WIDTH - 1)));
u16 y_converted = static_cast<u16>(std::clamp(relY * ScreenLayout::BOTTOM_SCREEN_HEIGHT, 0.f, float(ScreenLayout::BOTTOM_SCREEN_HEIGHT - 1)));
hid.setTouchScreenPress(x_converted, y_converted);
} else {
hid.releaseTouchScreen();
}
}