Screen layouts: Add configurable screen sizes

This commit is contained in:
wheremyfoodat 2025-07-05 02:36:03 +03:00
parent 62748eef47
commit 1c0f65c740
4 changed files with 92 additions and 89 deletions

View file

@ -42,115 +42,114 @@ namespace ScreenLayout {
int destWidth = TOP_SCREEN_WIDTH; int destWidth = TOP_SCREEN_WIDTH;
int destHeight = CONSOLE_HEIGHT; int destHeight = CONSOLE_HEIGHT;
} singleBlitInfo; } singleBlitInfo;
float scale = 1.0f;
}; };
// Calculate screen coordinates on the screen for a given layout & a given size for the output window // 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) // 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) { static void calculateCoordinates(WindowCoordinates& coordinates, u32 outputWindowWidth, u32 outputWindowHeight, Layout layout) {
layout = Layout::SideBySideFlipped; layout = Layout::SideBySideFlipped;
const float topScreenPercentage = 0.5f;
const float destAspect = float(outputWindowWidth) / float(outputWindowHeight); const float destAspect = float(outputWindowWidth) / float(outputWindowHeight);
if (layout == Layout::Default || layout == Layout::DefaultFlipped) { 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) { float scaleBottomX = float(outputWindowWidth) / float(BOTTOM_SCREEN_WIDTH);
// Window is wider than source float scaleBottomY = float(availableBottomHeight) / float(BOTTOM_SCREEN_HEIGHT);
destWidth = int(outputWindowHeight * srcAspect + 0.5f); float scaleBottom = std::min(scaleBottomX, scaleBottomY);
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 int topScreenWidth = int(TOP_SCREEN_WIDTH * scaleTop + 0.5f);
const float scale = float(destWidth) / float(TOP_SCREEN_WIDTH); int topScreenHeight = int(TOP_SCREEN_HEIGHT * scaleTop + 0.5f);
// Calculate coordinates and return them int bottomScreenWidth = int(BOTTOM_SCREEN_WIDTH * scaleBottom + 0.5f);
// TODO: This will break when we allow screens to be scaled separately int bottomScreenHeight = int(BOTTOM_SCREEN_HEIGHT * scaleBottom + 0.5f);
coordinates.topScreenX = u32(destX);
coordinates.topScreenWidth = u32(float(TOP_SCREEN_WIDTH) * scale); // Center screens horizontally
coordinates.bottomScreenX = u32(destX) + BOTTOM_SCREEN_X_OFFSET * scale; int topScreenX = (outputWindowWidth - topScreenWidth) / 2;
coordinates.bottomScreenWidth = u32(float(BOTTOM_SCREEN_WIDTH) * scale); 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) { if (layout == Layout::Default) {
coordinates.topScreenY = u32(destY + (destHeight - int(CONSOLE_HEIGHT * scale)) / 2); coordinates.topScreenY = 0;
coordinates.topScreenHeight = u32(float(TOP_SCREEN_HEIGHT) * scale); coordinates.bottomScreenY = topScreenHeight;
coordinates.bottomScreenY = coordinates.topScreenY + TOP_SCREEN_HEIGHT * scale;
coordinates.bottomScreenHeight = coordinates.topScreenHeight;
} else { } else {
// Flip the screens vertically coordinates.bottomScreenY = 0;
coordinates.bottomScreenY = u32(destY + (destHeight - int(CONSOLE_HEIGHT * scale)) / 2); coordinates.topScreenY = bottomScreenHeight;
coordinates.bottomScreenHeight = u32(float(BOTTOM_SCREEN_HEIGHT) * scale);
coordinates.topScreenY = coordinates.bottomScreenY + TOP_SCREEN_HEIGHT * scale;
coordinates.topScreenHeight = coordinates.bottomScreenHeight;
} }
coordinates.windowWidth = outputWindowWidth; coordinates.windowWidth = outputWindowWidth;
coordinates.windowHeight = outputWindowHeight; coordinates.windowHeight = outputWindowHeight;
coordinates.scale = scale;
coordinates.singleBlitInfo.destX = destX; // Default layout can be rendered using a single blit, flipped layout can't
coordinates.singleBlitInfo.destY = destY; if (layout == Layout::Default) {
coordinates.singleBlitInfo.destWidth = destWidth; coordinates.singleBlitInfo.destX = coordinates.topScreenX;
coordinates.singleBlitInfo.destHeight = destHeight; coordinates.singleBlitInfo.destY = coordinates.topScreenY;
} else if (layout == Layout::SideBySide || layout == Layout::SideBySideFlipped) { coordinates.singleBlitInfo.destWidth = coordinates.topScreenWidth;
// For side-by-side layouts, the 3DS aspect ratio is calculated as (top width + bottom width) / height coordinates.singleBlitInfo.destHeight = coordinates.topScreenHeight + coordinates.bottomScreenHeight;
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 { } else {
// Window is taller — center vertically // Dummy data for the single blit info, as we can't render the screen in 1 blit when the screens are side-by-sid
destHeight = int(outputWindowWidth / srcAspect + 0.5f); coordinates.singleBlitInfo.destX = 0;
destY = (outputWindowHeight - destHeight) / 2; 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 // Calculate scales for top and bottom screens, and then the actual sizes
const float scale = float(destWidth) / float(TOP_SCREEN_WIDTH + BOTTOM_SCREEN_WIDTH); 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 int topScreenWidth = int(TOP_SCREEN_WIDTH * scaleTop + 0.5f);
// vertically, we have to account for that 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 // Vertically center the tallest screen
// Since eventually we'll want to let users scale each screen separately. int maxHeight = std::max(topScreenHeight, bottomScreenHeight);
const int topHeightScaled = int(TOP_SCREEN_HEIGHT * scale + 0.5f); int baseY = (outputWindowHeight - maxHeight) / 2;
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); int topScreenY = baseY + (maxHeight - topScreenHeight) / 2;
coordinates.topScreenWidth = u32(TOP_SCREEN_WIDTH * scale); int bottomScreenY = baseY + (maxHeight - bottomScreenHeight) / 2;
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) { if (layout == Layout::SideBySide) {
coordinates.topScreenX = destX; coordinates.topScreenX = (outputWindowWidth - (topScreenWidth + bottomScreenWidth)) / 2;
coordinates.bottomScreenX = destX + coordinates.topScreenWidth; coordinates.bottomScreenX = coordinates.topScreenX + topScreenWidth;
} else { } else {
// Flip the screens horizontally coordinates.bottomScreenX = (outputWindowWidth - (topScreenWidth + bottomScreenWidth)) / 2;
coordinates.bottomScreenX = destX; coordinates.topScreenX = coordinates.bottomScreenX + bottomScreenWidth;
coordinates.topScreenX = destX + coordinates.bottomScreenWidth;
} }
coordinates.topScreenY = topScreenY;
coordinates.topScreenWidth = topScreenWidth;
coordinates.topScreenHeight = topScreenHeight;
coordinates.bottomScreenY = bottomScreenY;
coordinates.bottomScreenWidth = bottomScreenWidth;
coordinates.bottomScreenHeight = bottomScreenHeight;
coordinates.windowWidth = outputWindowWidth; coordinates.windowWidth = outputWindowWidth;
coordinates.windowHeight = outputWindowHeight; 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.destX = 0;
coordinates.singleBlitInfo.destY = 0; coordinates.singleBlitInfo.destY = 0;
coordinates.singleBlitInfo.destWidth = 0; coordinates.singleBlitInfo.destWidth = 0;

View file

@ -93,9 +93,13 @@ class RendererMTL final : public Renderer {
struct { struct {
float topScreenX = 0; float topScreenX = 0;
float topScreenY = 0; float topScreenY = 0;
float topScreenWidth = 400;
float topScreenHeight = 240;
float bottomScreenX = 40; float bottomScreenX = 40;
float bottomScreenY = 240; float bottomScreenY = 240;
float scale = 1.0; float bottomScreenWidth = 320;
float bottomScreenHeight = 240;
} blitInfo; } blitInfo;
// Debug // Debug

View file

@ -592,7 +592,7 @@ void RendererGL::display() {
// 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.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 // Used for optimizing the screen blit into a single blit
blitInfo.destX = windowCoords.singleBlitInfo.destX; blitInfo.destX = windowCoords.singleBlitInfo.destX;
@ -601,13 +601,9 @@ void RendererGL::display() {
blitInfo.destHeight = windowCoords.singleBlitInfo.destHeight; blitInfo.destHeight = windowCoords.singleBlitInfo.destHeight;
// Check if we can blit the screens in 1 blit. If not, we'll break it into two. // 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 = blitInfo.canDoSingleBlit =
windowCoords.topScreenY + windowCoords.topScreenHeight == windowCoords.bottomScreenY && windowCoords.topScreenY + windowCoords.topScreenHeight == windowCoords.bottomScreenY && layout == ScreenLayout::Layout::Default;
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);
} }
if (blitInfo.canDoSingleBlit) { if (blitInfo.canDoSingleBlit) {

View file

@ -109,17 +109,21 @@ void RendererMTL::display() {
ScreenLayout::WindowCoordinates windowCoords; ScreenLayout::WindowCoordinates windowCoords;
ScreenLayout::calculateCoordinates(windowCoords, outputWindowWidth, outputWindowHeight, ScreenLayout::Layout::Default); ScreenLayout::calculateCoordinates(windowCoords, outputWindowWidth, outputWindowHeight, ScreenLayout::Layout::Default);
blitInfo.scale = windowCoords.scale; blitInfo.topScreenX = float(windowCoords.topScreenX);
blitInfo.topScreenX = windowCoords.topScreenX; blitInfo.topScreenY = float(windowCoords.topScreenY);
blitInfo.topScreenY = windowCoords.topScreenY; blitInfo.bottomScreenX = float(windowCoords.bottomScreenX);
blitInfo.bottomScreenX = windowCoords.bottomScreenX; blitInfo.bottomScreenY = float(windowCoords.bottomScreenY);
blitInfo.bottomScreenY = windowCoords.bottomScreenY;
blitInfo.topScreenWidth = float(windowCoords.topScreenWidth);
blitInfo.topScreenHeight = float(windowCoords.topScreenHeight);
blitInfo.bottomScreenWidth = float(windowCoords.bottomScreenWidth);
blitInfo.bottomScreenHeight = float(windowCoords.bottomScreenHeight);
} }
// Top screen // Top screen
if (topScreen) { if (topScreen) {
renderCommandEncoder->setViewport( 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->setFragmentTexture(topScreen->get().texture, 0);
renderCommandEncoder->drawPrimitives(MTL::PrimitiveTypeTriangleStrip, NS::UInteger(0), NS::UInteger(4)); renderCommandEncoder->drawPrimitives(MTL::PrimitiveTypeTriangleStrip, NS::UInteger(0), NS::UInteger(4));
@ -128,7 +132,7 @@ void RendererMTL::display() {
// Bottom screen // Bottom screen
if (bottomScreen) { if (bottomScreen) {
renderCommandEncoder->setViewport( 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->setFragmentTexture(bottomScreen->get().texture, 0);
renderCommandEncoder->drawPrimitives(MTL::PrimitiveTypeTriangleStrip, NS::UInteger(0), NS::UInteger(4)); renderCommandEncoder->drawPrimitives(MTL::PrimitiveTypeTriangleStrip, NS::UInteger(0), NS::UInteger(4));