mirror of
https://github.com/wheremyfoodat/Panda3DS.git
synced 2025-07-07 15:52:59 +12:00
Merge pull request #772 from wheremyfoodat/screen-layout
Some checks are pending
Android Build / x64 (release) (push) Waiting to run
Android Build / arm64 (release) (push) Waiting to run
HTTP Server Build / build (push) Waiting to run
Hydra Core Build / Windows (push) Waiting to run
Hydra Core Build / MacOS (push) Waiting to run
Hydra Core Build / Linux (push) Waiting to run
Hydra Core Build / Android-x64 (push) Waiting to run
Hydra Core Build / ARM-Libretro (push) Waiting to run
Linux AppImage Build / build (push) Waiting to run
Linux Build / build (push) Waiting to run
MacOS Build / MacOS-arm64 (push) Waiting to run
MacOS Build / MacOS-x86_64 (push) Waiting to run
MacOS Build / MacOS-Universal (push) Blocked by required conditions
Qt Build / Windows (push) Waiting to run
Qt Build / MacOS-arm64 (push) Waiting to run
Qt Build / MacOS-x86_64 (push) Waiting to run
Qt Build / MacOS-Universal (push) Blocked by required conditions
Qt Build / Linux (push) Waiting to run
Windows Build / build (push) Waiting to run
iOS Simulator Build / build (push) Waiting to run
Some checks are pending
Android Build / x64 (release) (push) Waiting to run
Android Build / arm64 (release) (push) Waiting to run
HTTP Server Build / build (push) Waiting to run
Hydra Core Build / Windows (push) Waiting to run
Hydra Core Build / MacOS (push) Waiting to run
Hydra Core Build / Linux (push) Waiting to run
Hydra Core Build / Android-x64 (push) Waiting to run
Hydra Core Build / ARM-Libretro (push) Waiting to run
Linux AppImage Build / build (push) Waiting to run
Linux Build / build (push) Waiting to run
MacOS Build / MacOS-arm64 (push) Waiting to run
MacOS Build / MacOS-x86_64 (push) Waiting to run
MacOS Build / MacOS-Universal (push) Blocked by required conditions
Qt Build / Windows (push) Waiting to run
Qt Build / MacOS-arm64 (push) Waiting to run
Qt Build / MacOS-x86_64 (push) Waiting to run
Qt Build / MacOS-Universal (push) Blocked by required conditions
Qt Build / Linux (push) Waiting to run
Windows Build / build (push) Waiting to run
iOS Simulator Build / build (push) Waiting to run
Better screen layout support
This commit is contained in:
commit
d1f4ae2911
18 changed files with 488 additions and 135 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/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/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/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(CRYPTO_SOURCE_FILES src/core/crypto/aes_engine.cpp)
|
||||||
set(KERNEL_SOURCE_FILES src/core/kernel/kernel.cpp src/core/kernel/resource_limits.cpp
|
set(KERNEL_SOURCE_FILES src/core/kernel/kernel.cpp src/core/kernel/resource_limits.cpp
|
||||||
|
@ -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/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/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/services/ir/ir_device.hpp include/services/ir/circlepad_pro.hpp include/services/service_intercept.hpp
|
||||||
|
include/screen_layout.hpp
|
||||||
)
|
)
|
||||||
|
|
||||||
if(IOS)
|
if(IOS)
|
||||||
|
|
|
@ -89,6 +89,7 @@ class GPU {
|
||||||
PICA::Vertex getImmediateModeVertex();
|
PICA::Vertex getImmediateModeVertex();
|
||||||
|
|
||||||
void getAcceleratedDrawInfo(PICA::DrawAcceleration& accel, bool indexed);
|
void getAcceleratedDrawInfo(PICA::DrawAcceleration& accel, bool indexed);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
// 256 entries per LUT with each LUT as its own row forming a 2D image 256 * LUT_COUNT
|
// 256 entries per LUT with each LUT as its own row forming a 2D image 256 * LUT_COUNT
|
||||||
// Encoded in PICA native format
|
// 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
|
// Used for setting the size of the window we'll be outputting graphics to
|
||||||
void setOutputSize(u32 width, u32 height) { renderer->setOutputSize(width, height); }
|
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
|
// TODO: Emulate the transfer engine & its registers
|
||||||
// Then this can be emulated by just writing the appropriate values there
|
// Then this can be emulated by just writing the appropriate values there
|
||||||
|
@ -181,6 +184,7 @@ class GPU {
|
||||||
}
|
}
|
||||||
|
|
||||||
Renderer* getRenderer() { return renderer.get(); }
|
Renderer* getRenderer() { return renderer.get(); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// GPU external registers
|
// 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
|
// 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) {
|
ALWAYS_INLINE void setVsOutputMask(u32 val) {
|
||||||
val &= 0xffff;
|
val &= 0xffff;
|
||||||
|
|
||||||
// Avoid recomputing this if not necessary
|
// Avoid recomputing this if not necessary
|
||||||
if (oldVsOutputMask != val) [[unlikely]] {
|
if (oldVsOutputMask != val) [[unlikely]] {
|
||||||
oldVsOutputMask = val;
|
oldVsOutputMask = val;
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
#include "screen_layout.hpp"
|
||||||
#include "audio/dsp_core.hpp"
|
#include "audio/dsp_core.hpp"
|
||||||
#include "frontend_settings.hpp"
|
#include "frontend_settings.hpp"
|
||||||
#include "renderer.hpp"
|
#include "renderer.hpp"
|
||||||
|
@ -69,6 +70,9 @@ struct EmulatorConfig {
|
||||||
bool accelerateShaders = accelerateShadersDefault;
|
bool accelerateShaders = accelerateShadersDefault;
|
||||||
bool hashTextures = hashTexturesDefault;
|
bool hashTextures = hashTexturesDefault;
|
||||||
|
|
||||||
|
ScreenLayout::Layout screenLayout = ScreenLayout::Layout::Default;
|
||||||
|
float topScreenSize = 0.5;
|
||||||
|
|
||||||
bool accurateShaderMul = false;
|
bool accurateShaderMul = false;
|
||||||
bool discordRpcEnabled = false;
|
bool discordRpcEnabled = false;
|
||||||
|
|
||||||
|
|
|
@ -115,6 +115,8 @@ class Emulator {
|
||||||
|
|
||||||
RomFS::DumpingResult dumpRomFS(const std::filesystem::path& path);
|
RomFS::DumpingResult dumpRomFS(const std::filesystem::path& path);
|
||||||
void setOutputSize(u32 width, u32 height) { gpu.setOutputSize(width, height); }
|
void setOutputSize(u32 width, u32 height) { gpu.setOutputSize(width, height); }
|
||||||
|
void reloadScreenLayout() { gpu.reloadScreenLayout(); }
|
||||||
|
|
||||||
void deinitGraphicsContext() { gpu.deinitGraphicsContext(); }
|
void deinitGraphicsContext() { gpu.deinitGraphicsContext(); }
|
||||||
|
|
||||||
// Reloads some settings that require special handling, such as audio enable
|
// Reloads some settings that require special handling, such as audio enable
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
|
#include "screen_layout.hpp"
|
||||||
#include "gl/context.h"
|
#include "gl/context.h"
|
||||||
#include "window_info.h"
|
#include "window_info.h"
|
||||||
|
|
||||||
|
@ -29,6 +30,15 @@ class ScreenWidget : public QWidget {
|
||||||
u32 previousWidth = 0;
|
u32 previousWidth = 0;
|
||||||
u32 previousHeight = 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;
|
||||||
|
// Screen layouts and sizes
|
||||||
|
ScreenLayout::Layout screenLayout = ScreenLayout::Layout::Default;
|
||||||
|
float topScreenSize = 0.5f;
|
||||||
|
|
||||||
|
void reloadScreenLayout(ScreenLayout::Layout newLayout, float newTopScreenSize);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::unique_ptr<GL::Context> glContext = nullptr;
|
std::unique_ptr<GL::Context> glContext = nullptr;
|
||||||
ResizeCallback resizeCallback;
|
ResizeCallback resizeCallback;
|
||||||
|
@ -39,4 +49,6 @@ class ScreenWidget : public QWidget {
|
||||||
int scaledWindowWidth() const;
|
int scaledWindowWidth() const;
|
||||||
int scaledWindowHeight() const;
|
int scaledWindowHeight() const;
|
||||||
std::optional<WindowInfo> getWindowInfo();
|
std::optional<WindowInfo> getWindowInfo();
|
||||||
|
|
||||||
|
void reloadScreenCoordinates();
|
||||||
};
|
};
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
|
||||||
|
#include "screen_layout.hpp"
|
||||||
#include "emulator.hpp"
|
#include "emulator.hpp"
|
||||||
#include "input_mappings.hpp"
|
#include "input_mappings.hpp"
|
||||||
|
|
||||||
|
@ -27,7 +28,11 @@ class FrontendSDL {
|
||||||
u32 windowHeight = 480;
|
u32 windowHeight = 480;
|
||||||
int gameControllerID;
|
int gameControllerID;
|
||||||
bool programRunning = true;
|
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
|
// For tracking whether to update gyroscope
|
||||||
// We bind gyro to right click + mouse movement
|
// We bind gyro to right click + mouse movement
|
||||||
bool holdingRightClick = false;
|
bool holdingRightClick = false;
|
||||||
|
@ -38,5 +43,7 @@ class FrontendSDL {
|
||||||
bool keyboardAnalogX = false;
|
bool keyboardAnalogX = false;
|
||||||
bool keyboardAnalogY = false;
|
bool keyboardAnalogY = false;
|
||||||
|
|
||||||
|
private:
|
||||||
void setupControllerSensors(SDL_GameController* controller);
|
void setupControllerSensors(SDL_GameController* controller);
|
||||||
|
void handleLeftClick(int mouseX, int mouseY);
|
||||||
};
|
};
|
|
@ -130,4 +130,5 @@ class Renderer {
|
||||||
|
|
||||||
void setConfig(EmulatorConfig* config) { emulatorConfig = config; }
|
void setConfig(EmulatorConfig* config) { emulatorConfig = config; }
|
||||||
void setHashTextures(bool setting) { hashTextures = setting; }
|
void setHashTextures(bool setting) { hashTextures = setting; }
|
||||||
|
void reloadScreenLayout() { outputSizeChanged = true; }
|
||||||
};
|
};
|
||||||
|
|
|
@ -147,12 +147,24 @@ class RendererGL final : public Renderer {
|
||||||
OpenGL::Driver driverInfo;
|
OpenGL::Driver driverInfo;
|
||||||
|
|
||||||
// Information about the final 3DS screen -> Window blit, accounting for things like scaling and shifting the output based on
|
// 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 {
|
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 destX = 0;
|
||||||
int destY = 0;
|
int destY = 0;
|
||||||
int destWidth = 400;
|
int destWidth = 400;
|
||||||
int destHeight = 480;
|
int destHeight = 480;
|
||||||
|
bool canDoSingleBlit = true;
|
||||||
} blitInfo;
|
} blitInfo;
|
||||||
|
|
||||||
MAKE_LOG_FUNCTION(log, rendererLogger)
|
MAKE_LOG_FUNCTION(log, rendererLogger)
|
||||||
|
|
|
@ -89,13 +89,17 @@ class RendererMTL final : public Renderer {
|
||||||
MTL::Texture* lastDepthTexture = nullptr;
|
MTL::Texture* lastDepthTexture = nullptr;
|
||||||
|
|
||||||
// Information about the final 3DS screen -> Window blit, accounting for things like scaling and shifting the output based on
|
// 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 {
|
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
|
||||||
|
|
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);
|
lightShadergenThreshold = toml::find_or<toml::integer>(gpu, "ShadergenLightThreshold", 1);
|
||||||
hashTextures = toml::find_or<toml::boolean>(gpu, "HashTextures", hashTexturesDefault);
|
hashTextures = toml::find_or<toml::boolean>(gpu, "HashTextures", hashTexturesDefault);
|
||||||
enableRenderdoc = toml::find_or<toml::boolean>(gpu, "EnableRenderdoc", false);
|
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"]["AccelerateShaders"] = accelerateShaders;
|
||||||
data["GPU"]["EnableRenderdoc"] = enableRenderdoc;
|
data["GPU"]["EnableRenderdoc"] = enableRenderdoc;
|
||||||
data["GPU"]["HashTextures"] = hashTextures;
|
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"]["DSPEmulation"] = std::string(Audio::DSPCore::typeToString(dspType));
|
||||||
data["Audio"]["EnableAudio"] = audioEnabled;
|
data["Audio"]["EnableAudio"] = audioEnabled;
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
#include "PICA/pica_hash.hpp"
|
#include "PICA/pica_hash.hpp"
|
||||||
#include "PICA/pica_simd.hpp"
|
#include "PICA/pica_simd.hpp"
|
||||||
#include "PICA/regs.hpp"
|
#include "PICA/regs.hpp"
|
||||||
|
#include "screen_layout.hpp"
|
||||||
#include "PICA/shader_decompiler.hpp"
|
#include "PICA/shader_decompiler.hpp"
|
||||||
#include "config.hpp"
|
#include "config.hpp"
|
||||||
#include "math_util.hpp"
|
#include "math_util.hpp"
|
||||||
|
@ -134,8 +135,8 @@ void RendererGL::initGraphicsContextInternal() {
|
||||||
|
|
||||||
auto prevTexture = OpenGL::getTex2D();
|
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.
|
// 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
|
||||||
// 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
|
// 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.create(8, 8, GL_RGBA8);
|
||||||
blankTexture.bind();
|
blankTexture.bind();
|
||||||
blankTexture.setMinFilter(OpenGL::Linear);
|
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
|
// 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;
|
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]);
|
const u32 logicOp = getBits<0, 4>(regs[PICA::InternalRegs::LogicOp]);
|
||||||
gl.setLogicOp(logicOps[logicOp]);
|
gl.setLogicOp(logicOps[logicOp]);
|
||||||
|
|
||||||
|
@ -269,20 +270,13 @@ void RendererGL::setupStencilTest(bool stencilEnable) {
|
||||||
}
|
}
|
||||||
|
|
||||||
static constexpr std::array<GLenum, 8> stencilFuncs = {
|
static constexpr std::array<GLenum, 8> stencilFuncs = {
|
||||||
GL_NEVER,
|
GL_NEVER, GL_ALWAYS, GL_EQUAL, GL_NOTEQUAL, GL_LESS, GL_LEQUAL, GL_GREATER, GL_GEQUAL,
|
||||||
GL_ALWAYS,
|
|
||||||
GL_EQUAL,
|
|
||||||
GL_NOTEQUAL,
|
|
||||||
GL_LESS,
|
|
||||||
GL_LEQUAL,
|
|
||||||
GL_GREATER,
|
|
||||||
GL_GEQUAL
|
|
||||||
};
|
};
|
||||||
gl.enableStencil();
|
gl.enableStencil();
|
||||||
|
|
||||||
const u32 stencilConfig = regs[PICA::InternalRegs::StencilTest];
|
const u32 stencilConfig = regs[PICA::InternalRegs::StencilTest];
|
||||||
const u32 stencilFunc = getBits<4, 3>(stencilConfig);
|
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 u32 stencilRefMask = getBits<24, 8>(stencilConfig);
|
||||||
|
|
||||||
const bool stencilWrite = regs[PICA::InternalRegs::DepthBufferWrite];
|
const bool stencilWrite = regs[PICA::InternalRegs::DepthBufferWrite];
|
||||||
|
@ -293,15 +287,9 @@ void RendererGL::setupStencilTest(bool stencilEnable) {
|
||||||
gl.setStencilMask(stencilBufferMask);
|
gl.setStencilMask(stencilBufferMask);
|
||||||
|
|
||||||
static constexpr std::array<GLenum, 8> stencilOps = {
|
static constexpr std::array<GLenum, 8> stencilOps = {
|
||||||
GL_KEEP,
|
GL_KEEP, GL_ZERO, GL_REPLACE, GL_INCR, GL_DECR, GL_INVERT, GL_INCR_WRAP, GL_DECR_WRAP,
|
||||||
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 stencilOpConfig = regs[PICA::InternalRegs::StencilOp];
|
||||||
const u32 stencilFailOp = getBits<0, 3>(stencilOpConfig);
|
const u32 stencilFailOp = getBits<0, 3>(stencilOpConfig);
|
||||||
const u32 depthFailOp = getBits<4, 3>(stencilOpConfig);
|
const u32 depthFailOp = getBits<4, 3>(stencilOpConfig);
|
||||||
|
@ -468,7 +456,10 @@ void RendererGL::drawVertices(PICA::PrimType primType, std::span<const Vertex> v
|
||||||
const int depthFunc = getBits<4, 3>(depthControl);
|
const int depthFunc = getBits<4, 3>(depthControl);
|
||||||
const int colourMask = getBits<8, 4>(depthControl);
|
const int colourMask = getBits<8, 4>(depthControl);
|
||||||
gl.setColourMask(colourMask & 1, colourMask & 2, colourMask & 4, colourMask & 8);
|
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();
|
bindTexturesToSlots();
|
||||||
if (gpu.fogLUTDirty) {
|
if (gpu.fogLUTDirty) {
|
||||||
|
@ -565,14 +556,14 @@ void RendererGL::display() {
|
||||||
|
|
||||||
if (topScreen) {
|
if (topScreen) {
|
||||||
topScreen->get().texture.bind();
|
topScreen->get().texture.bind();
|
||||||
OpenGL::setViewport(0, 240, 400, 240); // Top screen viewport
|
OpenGL::setViewport(0, 240, 400, 240); // Top screen viewport
|
||||||
OpenGL::draw(OpenGL::TriangleStrip, 4); // Actually draw our 3DS screen
|
OpenGL::draw(OpenGL::TriangleStrip, 4); // Actually draw our 3DS screen
|
||||||
}
|
}
|
||||||
|
|
||||||
const u32 bottomActiveFb = externalRegs[Framebuffer1Select] & 1;
|
const u32 bottomActiveFb = externalRegs[Framebuffer1Select] & 1;
|
||||||
const u32 bottomScreenAddr = externalRegs[bottomActiveFb == 0 ? Framebuffer1AFirstAddr : Framebuffer1ASecondAddr];
|
const u32 bottomScreenAddr = externalRegs[bottomActiveFb == 0 ? Framebuffer1AFirstAddr : Framebuffer1ASecondAddr];
|
||||||
auto bottomScreen = colourBufferCache.findFromAddress(bottomScreenAddr);
|
auto bottomScreen = colourBufferCache.findFromAddress(bottomScreenAddr);
|
||||||
|
|
||||||
if (bottomScreen) {
|
if (bottomScreen) {
|
||||||
bottomScreen->get().texture.bind();
|
bottomScreen->get().texture.bind();
|
||||||
OpenGL::setViewport(40, 0, 320, 240);
|
OpenGL::setViewport(40, 0, 320, 240);
|
||||||
|
@ -581,34 +572,59 @@ void RendererGL::display() {
|
||||||
|
|
||||||
if constexpr (!Helpers::isHydraCore()) {
|
if constexpr (!Helpers::isHydraCore()) {
|
||||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
||||||
|
OpenGL::clearColor();
|
||||||
|
|
||||||
screenFramebuffer.bind(OpenGL::ReadFramebuffer);
|
screenFramebuffer.bind(OpenGL::ReadFramebuffer);
|
||||||
|
|
||||||
if (outputSizeChanged) {
|
if (outputSizeChanged) {
|
||||||
outputSizeChanged = false;
|
outputSizeChanged = false;
|
||||||
|
|
||||||
const float srcAspect = 400.0f / 480.0f; // 3DS aspect ratio
|
auto layout = emulatorConfig->screenLayout;
|
||||||
const float dstAspect = float(outputWindowWidth) / float(outputWindowHeight);
|
|
||||||
|
|
||||||
blitInfo.destWidth = outputWindowWidth;
|
// Get information about our new screen layout to use for blitting the output
|
||||||
blitInfo.destHeight = outputWindowHeight;
|
ScreenLayout::WindowCoordinates windowCoords;
|
||||||
blitInfo.destX = 0;
|
ScreenLayout::calculateCoordinates(windowCoords, outputWindowWidth, outputWindowHeight, emulatorConfig->topScreenSize, layout);
|
||||||
blitInfo.destY = 0;
|
|
||||||
|
|
||||||
if (dstAspect > srcAspect) {
|
blitInfo.topScreenX = windowCoords.topScreenX;
|
||||||
// Window is wider than source
|
blitInfo.topScreenY = windowCoords.topScreenY;
|
||||||
blitInfo.destWidth = int(outputWindowHeight * srcAspect + 0.5f);
|
blitInfo.topScreenWidth = windowCoords.topScreenWidth;
|
||||||
blitInfo.destX = (outputWindowWidth - blitInfo.destWidth) / 2;
|
blitInfo.topScreenHeight = windowCoords.topScreenHeight;
|
||||||
} else {
|
|
||||||
// Window is taller than source
|
blitInfo.bottomScreenX = windowCoords.bottomScreenX;
|
||||||
blitInfo.destHeight = int(outputWindowWidth / srcAspect + 0.5f);
|
blitInfo.bottomScreenY = windowCoords.bottomScreenY;
|
||||||
blitInfo.destY = (outputWindowHeight - blitInfo.destHeight) / 2;
|
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.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;
|
||||||
}
|
}
|
||||||
|
|
||||||
glBlitFramebuffer(
|
if (blitInfo.canDoSingleBlit) {
|
||||||
0, 0, 400, 480, blitInfo.destX, blitInfo.destY, blitInfo.destX + blitInfo.destWidth, blitInfo.destY + blitInfo.destHeight,
|
glBlitFramebuffer(
|
||||||
GL_COLOR_BUFFER_BIT, GL_LINEAR
|
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 +751,10 @@ void RendererGL::displayTransfer(u32 inputAddr, u32 outputAddr, u32 inputSize, u
|
||||||
u32 outputWidth = outputSize & 0xffff;
|
u32 outputWidth = outputSize & 0xffff;
|
||||||
u32 outputHeight = outputSize >> 16;
|
u32 outputHeight = outputSize >> 16;
|
||||||
|
|
||||||
OpenGL::DebugScope scope("DisplayTransfer inputAddr 0x%08X outputAddr 0x%08X inputWidth %d outputWidth %d inputHeight %d outputHeight %d",
|
OpenGL::DebugScope scope(
|
||||||
inputAddr, outputAddr, inputWidth, outputWidth, inputHeight, outputHeight);
|
"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);
|
auto srcFramebuffer = getColourBuffer(inputAddr, inputFormat, inputWidth, outputHeight);
|
||||||
Math::Rect<u32> srcRect = srcFramebuffer->getSubRect(inputAddr, outputWidth, outputHeight);
|
Math::Rect<u32> srcRect = srcFramebuffer->getSubRect(inputAddr, outputWidth, outputHeight);
|
||||||
|
@ -786,8 +804,10 @@ void RendererGL::textureCopy(u32 inputAddr, u32 outputAddr, u32 totalBytes, u32
|
||||||
const u32 outputWidth = (outputSize & 0xffff) << 4;
|
const u32 outputWidth = (outputSize & 0xffff) << 4;
|
||||||
const u32 outputGap = (outputSize >> 16) << 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",
|
OpenGL::DebugScope scope(
|
||||||
inputAddr, outputAddr, totalBytes, inputWidth, inputGap, outputWidth, outputGap);
|
"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) {
|
if (inputGap != 0 || outputGap != 0) {
|
||||||
// Helpers::warn("Strided texture copy\n");
|
// Helpers::warn("Strided texture copy\n");
|
||||||
|
@ -825,7 +845,7 @@ void RendererGL::textureCopy(u32 inputAddr, u32 outputAddr, u32 totalBytes, u32
|
||||||
// Find the source surface.
|
// Find the source surface.
|
||||||
auto srcFramebuffer = getColourBuffer(inputAddr, PICA::ColorFmt::RGBA8, copyStride, copyHeight, false);
|
auto srcFramebuffer = getColourBuffer(inputAddr, PICA::ColorFmt::RGBA8, copyStride, copyHeight, false);
|
||||||
if (!srcFramebuffer) {
|
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) {
|
if (shutUpCounter < 5) {
|
||||||
shutUpCounter++;
|
shutUpCounter++;
|
||||||
|
@ -1041,8 +1061,8 @@ bool RendererGL::prepareForDraw(ShaderUnit& shaderUnit, PICA::DrawAcceleration*
|
||||||
driverInfo.usingGLES ? PICA::ShaderGen::API::GLES : PICA::ShaderGen::API::GL, PICA::ShaderGen::Language::GLSL
|
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
|
// Empty source means compilation error, if the source is not empty then we convert the recompiled PICA code into a valid shader and
|
||||||
// it to the GPU
|
// upload it to the GPU
|
||||||
if (!picaShaderSource.empty()) {
|
if (!picaShaderSource.empty()) {
|
||||||
std::string vertexShaderSource = fragShaderGen.getVertexShaderAccelerated(picaShaderSource, vertexConfig, usingUbershader);
|
std::string vertexShaderSource = fragShaderGen.getVertexShaderAccelerated(picaShaderSource, vertexConfig, usingUbershader);
|
||||||
shader->create({vertexShaderSource}, OpenGL::Vertex);
|
shader->create({vertexShaderSource}, OpenGL::Vertex);
|
||||||
|
@ -1073,7 +1093,7 @@ bool RendererGL::prepareForDraw(ShaderUnit& shaderUnit, PICA::DrawAcceleration*
|
||||||
if (!usingUbershader) {
|
if (!usingUbershader) {
|
||||||
OpenGL::Program& program = getSpecializedShader();
|
OpenGL::Program& program = getSpecializedShader();
|
||||||
gl.useProgram(program);
|
gl.useProgram(program);
|
||||||
} else { // Bind ubershader & load ubershader uniforms
|
} else { // Bind ubershader & load ubershader uniforms
|
||||||
gl.useProgram(triangleProgram);
|
gl.useProgram(triangleProgram);
|
||||||
|
|
||||||
const float depthScale = f24::fromRaw(regs[PICA::InternalRegs::DepthScale] & 0xffffff).toFloat32();
|
const float depthScale = f24::fromRaw(regs[PICA::InternalRegs::DepthScale] & 0xffffff).toFloat32();
|
||||||
|
@ -1243,7 +1263,7 @@ void RendererGL::accelerateVertexUpload(ShaderUnit& shaderUnit, PICA::DrawAccele
|
||||||
const u32 currentAttributeMask = accel->enabledAttributeMask;
|
const u32 currentAttributeMask = accel->enabledAttributeMask;
|
||||||
// Use bitwise xor to calculate which attributes changed
|
// Use bitwise xor to calculate which attributes changed
|
||||||
u32 attributeMaskDiff = currentAttributeMask ^ previousAttributeMask;
|
u32 attributeMaskDiff = currentAttributeMask ^ previousAttributeMask;
|
||||||
|
|
||||||
while (attributeMaskDiff != 0) {
|
while (attributeMaskDiff != 0) {
|
||||||
// Get index of next different attribute and turn it off
|
// Get index of next different attribute and turn it off
|
||||||
const u32 index = 31 - std::countl_zero<u32>(attributeMaskDiff);
|
const u32 index = 31 - std::countl_zero<u32>(attributeMaskDiff);
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
|
|
||||||
#include "PICA/gpu.hpp"
|
#include "PICA/gpu.hpp"
|
||||||
#include "PICA/pica_hash.hpp"
|
#include "PICA/pica_hash.hpp"
|
||||||
|
#include "screen_layout.hpp"
|
||||||
#include "SDL_metal.h"
|
#include "SDL_metal.h"
|
||||||
|
|
||||||
using namespace PICA;
|
using namespace PICA;
|
||||||
|
@ -105,32 +106,26 @@ void RendererMTL::display() {
|
||||||
|
|
||||||
if (outputSizeChanged) {
|
if (outputSizeChanged) {
|
||||||
outputSizeChanged = false;
|
outputSizeChanged = false;
|
||||||
|
ScreenLayout::WindowCoordinates windowCoords;
|
||||||
|
ScreenLayout::calculateCoordinates(
|
||||||
|
windowCoords, outputWindowWidth, outputWindowHeight, emulatorConfig->topScreenSize, emulatorConfig->screenLayout
|
||||||
|
);
|
||||||
|
|
||||||
const float srcAspect = 400.0 / 480.0;
|
blitInfo.topScreenX = float(windowCoords.topScreenX);
|
||||||
const float destAspect = float(outputWindowWidth) / float(outputWindowHeight);
|
blitInfo.topScreenY = float(windowCoords.topScreenY);
|
||||||
int destX = 0, destY = 0, destWidth = outputWindowWidth, destHeight = outputWindowHeight;
|
blitInfo.bottomScreenX = float(windowCoords.bottomScreenX);
|
||||||
|
blitInfo.bottomScreenY = float(windowCoords.bottomScreenY);
|
||||||
|
|
||||||
if (destAspect > srcAspect) {
|
blitInfo.topScreenWidth = float(windowCoords.topScreenWidth);
|
||||||
// Window is wider than source
|
blitInfo.topScreenHeight = float(windowCoords.topScreenHeight);
|
||||||
destWidth = int(outputWindowHeight * srcAspect + 0.5f);
|
blitInfo.bottomScreenWidth = float(windowCoords.bottomScreenWidth);
|
||||||
destX = (outputWindowWidth - destWidth) / 2;
|
blitInfo.bottomScreenHeight = float(windowCoords.bottomScreenHeight);
|
||||||
} 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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));
|
||||||
|
@ -139,7 +134,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));
|
||||||
|
@ -800,7 +795,7 @@ void RendererMTL::updateLightingLUT(MTL::RenderCommandEncoder* encoder) {
|
||||||
void RendererMTL::updateFogLUT(MTL::RenderCommandEncoder* encoder) {
|
void RendererMTL::updateFogLUT(MTL::RenderCommandEncoder* encoder) {
|
||||||
gpu.fogLUTDirty = false;
|
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) {
|
for (int i = 0; i < fogLut.size(); i += 2) {
|
||||||
const uint32_t value = gpu.fogLUT[i >> 1];
|
const uint32_t value = gpu.fogLUT[i >> 1];
|
||||||
|
@ -835,8 +830,11 @@ void RendererMTL::textureCopyImpl(
|
||||||
commandEncoder.setRenderPipelineState(blitPipeline);
|
commandEncoder.setRenderPipelineState(blitPipeline);
|
||||||
|
|
||||||
// Viewport
|
// Viewport
|
||||||
renderCommandEncoder->setViewport(MTL::Viewport{
|
renderCommandEncoder->setViewport(
|
||||||
double(destRect.left), double(destRect.bottom), double(destRect.right - destRect.left), double(destRect.top - destRect.bottom), 0.0, 1.0});
|
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] = {
|
float srcRectNDC[4] = {
|
||||||
srcRect.left / (float)srcFramebuffer.size.u(),
|
srcRect.left / (float)srcFramebuffer.size.u(),
|
||||||
|
|
152
src/core/screen_layout.cpp
Normal file
152
src/core/screen_layout.cpp
Normal file
|
@ -0,0 +1,152 @@
|
||||||
|
#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 scaleTop = std::min(float(outputWindowWidth) / float(TOP_SCREEN_WIDTH), float(availableTopHeight) / float(TOP_SCREEN_HEIGHT));
|
||||||
|
float scaleBottom =
|
||||||
|
std::min(float(outputWindowWidth) / float(BOTTOM_SCREEN_WIDTH), float(availableBottomHeight) / float(BOTTOM_SCREEN_HEIGHT));
|
||||||
|
|
||||||
|
int topScreenWidth = int(TOP_SCREEN_WIDTH * scaleTop + 0.5f);
|
||||||
|
int topScreenHeight = int(TOP_SCREEN_HEIGHT * scaleTop + 0.5f);
|
||||||
|
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 centerY = (outputWindowHeight - maxHeight) / 2;
|
||||||
|
|
||||||
|
int topScreenY = centerY + (maxHeight - topScreenHeight) / 2;
|
||||||
|
int bottomScreenY = centerY + (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);
|
connectCheckbox(hashTextures, config.hashTextures);
|
||||||
gpuLayout->addRow(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"));
|
QCheckBox* forceShadergenForLights = new QCheckBox(tr("Force shadergen when rendering lights"));
|
||||||
connectCheckbox(forceShadergenForLights, config.forceShadergenForLights);
|
connectCheckbox(forceShadergenForLights, config.forceShadergenForLights);
|
||||||
gpuLayout->addRow(forceShadergenForLights);
|
gpuLayout->addRow(forceShadergenForLights);
|
||||||
|
@ -302,7 +332,7 @@ ConfigWindow::ConfigWindow(ConfigCallback configCallback, MainWindowCallback win
|
||||||
volumeSlider->setRange(0, 200);
|
volumeSlider->setRange(0, 200);
|
||||||
volumeSlider->setValue(int(config.audioDeviceConfig.volumeRaw * 100));
|
volumeSlider->setValue(int(config.audioDeviceConfig.volumeRaw * 100));
|
||||||
connect(volumeSlider, &QSlider::valueChanged, this, [this, volumeLabel](int value) {
|
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));
|
volumeLabel->setText(QString::number(value));
|
||||||
|
|
||||||
updateConfig();
|
updateConfig();
|
||||||
|
@ -467,12 +497,14 @@ void ConfigWindow::setTheme(Theme theme) {
|
||||||
p.setColor(QPalette::Highlight, QColor(42, 130, 218));
|
p.setColor(QPalette::Highlight, QColor(42, 130, 218));
|
||||||
p.setColor(QPalette::HighlightedText, Qt::black);
|
p.setColor(QPalette::HighlightedText, Qt::black);
|
||||||
qApp->setPalette(p);
|
qApp->setPalette(p);
|
||||||
qApp->setStyleSheet("QLineEdit {"
|
qApp->setStyleSheet(
|
||||||
|
"QLineEdit {"
|
||||||
"background-color: #000000; color: #ffffff; border: 1px solid #a0a0a0; "
|
"background-color: #000000; color: #ffffff; border: 1px solid #a0a0a0; "
|
||||||
"border-radius: 4px; padding: 5px; }"
|
"border-radius: 4px; padding: 5px; }"
|
||||||
|
|
||||||
"QCheckBox::indicator:unchecked {"
|
"QCheckBox::indicator:unchecked {"
|
||||||
"border: 1px solid #808080; border-radius: 4px; }");
|
"border: 1px solid #808080; border-radius: 4px; }"
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -129,6 +129,8 @@ MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent)
|
||||||
if (config.printAppVersion) {
|
if (config.printAppVersion) {
|
||||||
printf("Welcome to Panda3DS v%s!\n", PANDA3DS_VERSION);
|
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
|
// 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;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case MessageType::UpdateConfig:
|
case MessageType::UpdateConfig: {
|
||||||
emu->getConfig() = configWindow->getConfig();
|
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();
|
emu->reloadSettings();
|
||||||
|
|
||||||
|
if (reloadScreenLayout) {
|
||||||
|
emu->reloadScreenLayout();
|
||||||
|
screen->reloadScreenLayout(newConfig.screenLayout, newConfig.topScreenSize);
|
||||||
|
}
|
||||||
|
|
||||||
// Save new settings to disk
|
// Save new settings to disk
|
||||||
emu->getConfig().save();
|
emuConfig.save();
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -525,25 +538,28 @@ void MainWindow::handleTouchscreenPress(QMouseEvent* event) {
|
||||||
const QPointF clickPos = event->globalPosition();
|
const QPointF clickPos = event->globalPosition();
|
||||||
const QPointF widgetPos = screen->mapFromGlobal(clickPos);
|
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
|
// Press is inside the screen area
|
||||||
if (widgetPos.x() >= 0 && widgetPos.x() < screen->width() && widgetPos.y() >= 0 && widgetPos.y() < screen->height()) {
|
if (widgetPos.x() >= bottomScreenX && widgetPos.x() < bottomScreenX + bottomScreenWidth && widgetPos.y() >= bottomScreenY &&
|
||||||
// Go from widget positions to [0, 400) for x and [0, 480) for y
|
widgetPos.y() < bottomScreenY + bottomScreenHeight) {
|
||||||
uint x = (uint)std::round(widgetPos.x() / screen->width() * 400.f);
|
// Map widget position to 3DS touchscreen coordinates, with (0, 0) = top left of touchscreen
|
||||||
uint y = (uint)std::round(widgetPos.y() / screen->height() * 480.f);
|
float relX = (widgetPos.x() - bottomScreenX) / bottomScreenWidth;
|
||||||
|
float relY = (widgetPos.y() - bottomScreenY) / bottomScreenHeight;
|
||||||
|
|
||||||
// Check if touch falls in the touch screen area
|
u16 x_converted = u16(std::clamp(relX * ScreenLayout::BOTTOM_SCREEN_WIDTH, 0.f, float(ScreenLayout::BOTTOM_SCREEN_WIDTH - 1)));
|
||||||
if (y >= 240 && y <= 480 && x >= 40 && x < 40 + 320) {
|
u16 y_converted = u16(std::clamp(relY * ScreenLayout::BOTTOM_SCREEN_HEIGHT, 0.f, float(ScreenLayout::BOTTOM_SCREEN_HEIGHT - 1)));
|
||||||
// Convert to 3DS coordinates
|
|
||||||
u16 x_converted = static_cast<u16>(x) - 40;
|
|
||||||
u16 y_converted = static_cast<u16>(y) - 240;
|
|
||||||
|
|
||||||
EmulatorMessage message{.type = MessageType::PressTouchscreen};
|
EmulatorMessage message{.type = MessageType::PressTouchscreen};
|
||||||
message.touchscreen.x = x_converted;
|
message.touchscreen.x = x_converted;
|
||||||
message.touchscreen.y = y_converted;
|
message.touchscreen.y = y_converted;
|
||||||
sendMessage(message);
|
sendMessage(message);
|
||||||
} else {
|
} else {
|
||||||
sendMessage(EmulatorMessage{.type = MessageType::ReleaseTouchscreen});
|
sendMessage(EmulatorMessage{.type = MessageType::ReleaseTouchscreen});
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
ScreenWidget::ScreenWidget(ResizeCallback resizeCallback, QWidget* parent) : QWidget(parent), resizeCallback(resizeCallback) {
|
ScreenWidget::ScreenWidget(ResizeCallback resizeCallback, QWidget* parent) : QWidget(parent), resizeCallback(resizeCallback) {
|
||||||
// Create a native window for use with our graphics API of choice
|
// Create a native window for use with our graphics API of choice
|
||||||
resize(800, 240 * 4);
|
resize(800, 240 * 4);
|
||||||
|
|
||||||
setAutoFillBackground(false);
|
setAutoFillBackground(false);
|
||||||
setAttribute(Qt::WA_NativeWindow, true);
|
setAttribute(Qt::WA_NativeWindow, true);
|
||||||
setAttribute(Qt::WA_NoSystemBackground, true);
|
setAttribute(Qt::WA_NoSystemBackground, true);
|
||||||
|
@ -47,6 +47,8 @@ void ScreenWidget::resizeEvent(QResizeEvent* event) {
|
||||||
this->windowInfo = *windowInfo;
|
this->windowInfo = *windowInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
reloadScreenCoordinates();
|
||||||
|
|
||||||
// This will call take care of calling resizeSurface from the emulator thread
|
// This will call take care of calling resizeSurface from the emulator thread
|
||||||
resizeCallback(surfaceWidth, surfaceHeight);
|
resizeCallback(surfaceWidth, surfaceHeight);
|
||||||
}
|
}
|
||||||
|
@ -60,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() {
|
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
|
// 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 = {
|
static constexpr std::array<GL::Context::Version, 8> versionsToTry = {
|
||||||
|
|
|
@ -55,7 +55,12 @@ FrontendSDL::FrontendSDL() : keyboardMappings(InputMappings::defaultKeyboardMapp
|
||||||
windowWidth = 400;
|
windowWidth = 400;
|
||||||
windowHeight = 480;
|
windowHeight = 480;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Initialize output size and screen layout
|
||||||
emu.setOutputSize(windowWidth, windowHeight);
|
emu.setOutputSize(windowWidth, windowHeight);
|
||||||
|
ScreenLayout::calculateCoordinates(
|
||||||
|
screenCoordinates, u32(windowWidth), u32(windowHeight), emu.getConfig().topScreenSize, emu.getConfig().screenLayout
|
||||||
|
);
|
||||||
|
|
||||||
if (needOpenGL) {
|
if (needOpenGL) {
|
||||||
// Demand 4.1 core for OpenGL renderer (max available on MacOS), 3.3 for the software & null renderers
|
// Demand 4.1 core for OpenGL renderer (max available on MacOS), 3.3 for the software & null renderers
|
||||||
|
@ -230,24 +235,7 @@ void FrontendSDL::run() {
|
||||||
if (emu.romType == ROMType::None) break;
|
if (emu.romType == ROMType::None) break;
|
||||||
|
|
||||||
if (event.button.button == SDL_BUTTON_LEFT) {
|
if (event.button.button == SDL_BUTTON_LEFT) {
|
||||||
if (windowWidth == 0 || windowHeight == 0) [[unlikely]] {
|
handleLeftClick(event.button.x, event.button.y);
|
||||||
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();
|
|
||||||
}
|
|
||||||
} else if (event.button.button == SDL_BUTTON_RIGHT) {
|
} else if (event.button.button == SDL_BUTTON_RIGHT) {
|
||||||
holdingRightClick = true;
|
holdingRightClick = true;
|
||||||
}
|
}
|
||||||
|
@ -321,18 +309,7 @@ void FrontendSDL::run() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Go from window positions to [0, 400) for x and [0, 480) for y
|
handleLeftClick(event.motion.x, event.motion.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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
// 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 +370,12 @@ void FrontendSDL::run() {
|
||||||
if (type == SDL_WINDOWEVENT_RESIZED) {
|
if (type == SDL_WINDOWEVENT_RESIZED) {
|
||||||
windowWidth = event.window.data1;
|
windowWidth = event.window.data1;
|
||||||
windowHeight = event.window.data2;
|
windowHeight = event.window.data2;
|
||||||
|
|
||||||
|
const auto& config = emu.getConfig();
|
||||||
|
ScreenLayout::calculateCoordinates(
|
||||||
|
screenCoordinates, u32(windowWidth), u32(windowHeight), emu.getConfig().topScreenSize, emu.getConfig().screenLayout
|
||||||
|
);
|
||||||
|
|
||||||
emu.setOutputSize(windowWidth, windowHeight);
|
emu.setOutputSize(windowWidth, windowHeight);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -471,3 +454,31 @@ void FrontendSDL::setupControllerSensors(SDL_GameController* controller) {
|
||||||
SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_ACCEL, SDL_TRUE);
|
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();
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue