mirror of
https://github.com/wheremyfoodat/Panda3DS.git
synced 2025-07-06 23:32:59 +12:00
Better screen layout support
This commit is contained in:
parent
1c0f65c740
commit
cf321b1ed8
17 changed files with 328 additions and 186 deletions
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -1,161 +0,0 @@
|
|||
#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;
|
||||
};
|
||||
|
||||
// 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
|
|
@ -2,6 +2,7 @@
|
|||
#include <filesystem>
|
||||
#include <string>
|
||||
|
||||
#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;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
#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<GL::Context> glContext = nullptr;
|
||||
|
@ -44,4 +49,6 @@ class ScreenWidget : public QWidget {
|
|||
int scaledWindowWidth() const;
|
||||
int scaledWindowHeight() const;
|
||||
std::optional<WindowInfo> getWindowInfo();
|
||||
|
||||
void reloadScreenCoordinates();
|
||||
};
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
#include <filesystem>
|
||||
|
||||
#include "PICA/screen_layout.hpp"
|
||||
#include "screen_layout.hpp"
|
||||
#include "emulator.hpp"
|
||||
#include "input_mappings.hpp"
|
||||
|
||||
|
|
|
@ -130,4 +130,5 @@ class Renderer {
|
|||
|
||||
void setConfig(EmulatorConfig* config) { emulatorConfig = config; }
|
||||
void setHashTextures(bool setting) { hashTextures = setting; }
|
||||
void reloadScreenLayout() { outputSizeChanged = true; }
|
||||
};
|
||||
|
|
58
include/screen_layout.hpp
Normal file
58
include/screen_layout.hpp
Normal file
|
@ -0,0 +1,58 @@
|
|||
#pragma once
|
||||
#include <string>
|
||||
|
||||
#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
|
|
@ -93,6 +93,10 @@ void EmulatorConfig::load() {
|
|||
lightShadergenThreshold = toml::find_or<toml::integer>(gpu, "ShadergenLightThreshold", 1);
|
||||
hashTextures = toml::find_or<toml::boolean>(gpu, "HashTextures", hashTexturesDefault);
|
||||
enableRenderdoc = toml::find_or<toml::boolean>(gpu, "EnableRenderdoc", false);
|
||||
|
||||
auto screenLayoutName = toml::find_or<std::string>(gpu, "ScreenLayout", "Default");
|
||||
screenLayout = ScreenLayout::layoutFromString(screenLayoutName);
|
||||
topScreenSize = float(std::clamp(toml::find_or<toml::floating>(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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
155
src/core/screen_layout.cpp
Normal file
155
src/core/screen_layout.cpp
Normal file
|
@ -0,0 +1,155 @@
|
|||
#include "screen_layout.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <unordered_map>
|
||||
|
||||
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<std::string, Layout> 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";
|
||||
}
|
||||
}
|
|
@ -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<int>(config.screenLayout));
|
||||
connect(screenLayout, &QComboBox::currentIndexChanged, this, [&](int index) {
|
||||
config.screenLayout = static_cast<ScreenLayout::Layout>(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<float>(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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<GL::Context::Version, 8> versionsToTry = {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue