Update home menu branch (#759)

* Fix typo (#680)

Co-authored-by: Noumi <139501014+noumidev@users.noreply.github.com>

* More PTM stuff

Co-Authored-By: Noumi <139501014+noumidev@users.noreply.github.com>

* Make system language configurable

* Fix building crypto++ for x64 target on Apple silicon MacOS

* Attempt to switch to M1 runners again

* Prevent selecting Vulkan renderer in Qt frontend and present a message

* Libretro: Add system language option

* Only enable audio by default on libretro for now

* CMake: Bump version

* Store configuration file in AppData root if not in working directory (#693)

* Store configuration file in AppData root if not in working directory

This fixes MacOS app bundles, as the emulator cannot write the config
file into the app bundle.

* Remove duplicate fs calls

* I'm an idiot sandwich

---------

Co-authored-by: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com>

* GL: Add usingGLES to driverInfo struct (#694)

* Wayland fixes part 1

* Support GLES on desktop

* Qt: Fix Wayland support

Qt will only create a Wayland surface when show() is called on the main
window and on the ScreenWidget. Thus, call the function before creating
the GL context.

Doesn't cause regressions on XWayland, untested in other platforms.

Fixes #586

* No need to call screen->show() twice

* Fix disabling Wayland & building on some distros (#700)

* GLES: Properly stub out logic ops

* Fix git versioning

* Android_Build: Implement ccache (#703)

* Android_Build: Implement ccache

* Update Android_Build.yml

* Update Android_Build.yml

---------

Co-authored-by: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com>

* Removed dead Citra link in readme (#706)

* CRO: Lighter icache flushes

* Implement Luma icache SVCs

* Add missing SVC logs

* GPU: Add sw texture copies

* Use vk::detail::DynamicLoader instead of vk::DynamicLoader (#710)

* Use vk::detail::DynamicLoader instead of vk::DynamicLoader

* Update renderer_vk.cpp

* Vk: Fix typo

* Vk: Lock CI runners to SDK version 1.3.301 temporarily

* Vk: Fixing CI pt 2

* Vulkan: Fixing CI pt 3

* Vk: Fix typo

* Temporarily give 80MB to all processes (#715)

* Try to cross-compile Libretro core for arm64 (#717)

* Try to cross-compile Libretro core for arm64

* Bonk

* Update Hydra_Build.yml

* [WIP] Libretro: Add audio support (#714)

* Libretro: Add audio support

* Adding audio interface part 1

* Audio device pt 2

* More audio device

* More audio device

* Morea uudi odevice

* More audio device

* More audio device

* More audio device

---------

Co-authored-by: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com>

* Libretro audio device: Fix frame count

* Mark audio devices as final

* Add toggle for libretro audio device (#719)

* Very important work (#720)

* Very important work

* Most important fix

* Add more HLE service calls for eshop (#721)

* CI: Fix Vulkan SDK action (#723)

* GPU registers: Fix writes to some registers ignoring the mask (#725)

Co-authored-by: henry <23128103+atem2069@users.noreply.github.com>

* OLED theme

* OLED theme config fix (#736)

Co-authored-by: smiRaphi <neogt404@gmail.com>

* Adding Swedish translation

* Fix Metal renderer compilation on iOS

* [Core] Improve iOS compilation workflow

* [Qt] Hook Swedish to UI

* AppDataDocumentProvider: Typo (#740)

* More iOS work

* More iOS progress

* More iOS work

* AppDataDocumentProvider: Add missing ``COLUMN_FLAGS`` in the default document projectation (#741)

Fixes unable to copy files from device to app's internal storage problem

* More iOS work

* ios: Simplify MTKView interface (still doesn't work though)

* ios: Pass CAMetalLayer instead of void* to Obj-C++ bridging header

* Fix bridging cast

* FINALLY IOS GRAPHICS

* ios: Remove printf spam

* Metal: Reimplement some texture formats on iOS

* metal: implement texture decoder

* metal: check for format support

* metal: implement texture swizzling

* metal: remove unused texture functions

* Shadergen types: Add Metal & MSL

* Format

* Undo submodule changes

* Readme: Add Chonkystation 3

* Metal: Use std::unique_ptr for texture decode

* AppDataDocumentProvider: Allow to remove documents (#744)

* AppDataDocumentProvider: Allow to remove documents

* Typo

* Metal renderer fixes for iOS

* iOS driver: Add doc comments

* iOS: Add frontend & frontend build files (#746)

* iOS: Add SwiftUI part to repo

* Add iOS build script

* Update SDL2 submodule

* Fix iOS build script

* CI: Update xcode tools for iOS

* Update iOS_Build.yml

* Update iOS build

* Lower XCode version

* A

* Update project.pbxproj

* Update iOS_Build.yml

* Update iOS_Build.yml

* Update build.sh

* iOS: Fail on build error

* iOS: Add file picker (#747)

* iOS: Add file picker

* Fix lock placement

* Qt: Add runpog icon (#752)

* Update discord-rpc submodule (#753)

* Remove cryptoppwin submodule (#754)

* Add optional texture hashing

* Fix build on new Vk SDK (#757)

Co-authored-by: Nadia Holmquist Pedersen <893884+nadiaholmquist@users.noreply.github.com>

* CI: Use new Vulkan SDK

---------

Co-authored-by: Noumi <139501014+noumidev@users.noreply.github.com>
Co-authored-by: Thomas <thomas@thomasw.dev>
Co-authored-by: Thomas <twvd@users.noreply.github.com>
Co-authored-by: Daniel López Guimaraes <danielectra@outlook.com>
Co-authored-by: Jonian Guveli <jonian@hardpixel.eu>
Co-authored-by: Ishan09811 <156402647+Ishan09811@users.noreply.github.com>
Co-authored-by: Auxy6858 <71662994+Auxy6858@users.noreply.github.com>
Co-authored-by: Paris Oplopoios <parisoplop@gmail.com>
Co-authored-by: henry <23128103+atem2069@users.noreply.github.com>
Co-authored-by: smiRaphi <neogt404@gmail.com>
Co-authored-by: smiRaphi <87574679+smiRaphi@users.noreply.github.com>
Co-authored-by: Daniel Nylander <po@danielnylander.se>
Co-authored-by: Samuliak <samuliak77@gmail.com>
Co-authored-by: Albert <45282415+ggrtk@users.noreply.github.com>
Co-authored-by: Nadia Holmquist Pedersen <893884+nadiaholmquist@users.noreply.github.com>
This commit is contained in:
wheremyfoodat 2025-06-23 04:59:22 +03:00 committed by GitHub
parent d5506f311f
commit 082b6216b3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
302 changed files with 55525 additions and 747 deletions

View file

@ -6,6 +6,7 @@
#include <fstream>
#include <map>
#include <string>
#include <unordered_map>
#include "helpers.hpp"
#include "toml.hpp"
@ -26,6 +27,7 @@ void EmulatorConfig::load() {
return;
}
printf("Loading existing configuration file %s\n", path.string().c_str());
toml::value data;
try {
@ -45,6 +47,7 @@ void EmulatorConfig::load() {
defaultRomPath = toml::find_or<std::string>(general, "DefaultRomPath", "");
printAppVersion = toml::find_or<toml::boolean>(general, "PrintAppVersion", true);
systemLanguage = languageCodeFromString(toml::find_or<std::string>(general, "SystemLanguage", "en"));
}
}
@ -69,14 +72,14 @@ void EmulatorConfig::load() {
auto gpu = gpuResult.unwrap();
// Get renderer
auto rendererName = toml::find_or<std::string>(gpu, "Renderer", "OpenGL");
auto rendererName = toml::find_or<std::string>(gpu, "Renderer", Renderer::typeToString(rendererDefault));
auto configRendererType = Renderer::typeFromString(rendererName);
if (configRendererType.has_value()) {
rendererType = configRendererType.value();
} else {
Helpers::warn("Invalid renderer specified: %s\n", rendererName.c_str());
rendererType = RendererType::OpenGL;
rendererType = rendererDefault;
}
shaderJitEnabled = toml::find_or<toml::boolean>(gpu, "EnableShaderJIT", shaderJitDefault);
@ -87,6 +90,7 @@ void EmulatorConfig::load() {
forceShadergenForLights = toml::find_or<toml::boolean>(gpu, "ForceShadergenForLighting", true);
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);
}
}
@ -98,8 +102,8 @@ void EmulatorConfig::load() {
auto dspCoreName = toml::find_or<std::string>(audio, "DSPEmulation", "HLE");
dspType = Audio::DSPCore::typeFromString(dspCoreName);
audioEnabled = toml::find_or<toml::boolean>(audio, "EnableAudio", false);
audioEnabled = toml::find_or<toml::boolean>(audio, "EnableAudio", audioEnabledDefault);
aacEnabled = toml::find_or<toml::boolean>(audio, "EnableAACAudio", true);
printDSPFirmware = toml::find_or<toml::boolean>(audio, "PrintDSPFirmware", false);
@ -169,6 +173,7 @@ void EmulatorConfig::save() {
data["General"]["UsePortableBuild"] = usePortableBuild;
data["General"]["DefaultRomPath"] = defaultRomPath.string();
data["General"]["PrintAppVersion"] = printAppVersion;
data["General"]["SystemLanguage"] = languageCodeToString(systemLanguage);
data["Window"]["AppVersionOnWindow"] = windowSettings.showAppVersion;
data["Window"]["RememberWindowPosition"] = windowSettings.rememberPosition;
@ -176,7 +181,7 @@ void EmulatorConfig::save() {
data["Window"]["WindowPosY"] = windowSettings.y;
data["Window"]["WindowWidth"] = windowSettings.width;
data["Window"]["WindowHeight"] = windowSettings.height;
data["GPU"]["EnableShaderJIT"] = shaderJitEnabled;
data["GPU"]["Renderer"] = std::string(Renderer::typeToString(rendererType));
data["GPU"]["EnableVSync"] = vsyncEnabled;
@ -186,6 +191,7 @@ void EmulatorConfig::save() {
data["GPU"]["ShadergenLightThreshold"] = lightShadergenThreshold;
data["GPU"]["AccelerateShaders"] = accelerateShaders;
data["GPU"]["EnableRenderdoc"] = enableRenderdoc;
data["GPU"]["HashTextures"] = hashTextures;
data["Audio"]["DSPEmulation"] = std::string(Audio::DSPCore::typeToString(dspType));
data["Audio"]["EnableAudio"] = audioEnabled;
@ -231,4 +237,34 @@ const char* AudioDeviceConfig::volumeCurveToString(AudioDeviceConfig::VolumeCurv
case VolumeCurve::Cubic:
default: return "cubic";
}
}
LanguageCodes EmulatorConfig::languageCodeFromString(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, LanguageCodes> map = {
{"ja", LanguageCodes::Japanese}, {"en", LanguageCodes::English}, {"fr", LanguageCodes::French}, {"de", LanguageCodes::German},
{"it", LanguageCodes::Italian}, {"es", LanguageCodes::Spanish}, {"zh", LanguageCodes::Chinese}, {"ko", LanguageCodes::Korean},
{"nl", LanguageCodes::Dutch}, {"pt", LanguageCodes::Portuguese}, {"ru", LanguageCodes::Russian}, {"tw", LanguageCodes::Taiwanese},
};
if (auto search = map.find(inString); search != map.end()) {
return search->second;
}
// Default to English if no language code in our map matches
return LanguageCodes::English;
}
const char* EmulatorConfig::languageCodeToString(LanguageCodes code) {
static constexpr std::array<const char*, 12> codes = {
"ja", "en", "fr", "de", "it", "es", "zh", "ko", "nl", "pt", "ru", "tw",
};
// Invalid country code, return english
if (static_cast<u32>(code) > static_cast<u32>(LanguageCodes::Taiwanese)) {
return "en";
} else {
return codes[static_cast<u32>(code)];
}
}

View file

@ -284,7 +284,7 @@ void GPU::writeInternalReg(u32 index, u32 value, u32 mask) {
break;
case VertexShaderOpDescriptorIndex: {
shaderUnit.vs.setOpDescriptorIndex(value);
shaderUnit.vs.setOpDescriptorIndex(newValue);
break;
}
@ -301,7 +301,7 @@ void GPU::writeInternalReg(u32 index, u32 value, u32 mask) {
}
case VertexBoolUniform: {
shaderUnit.vs.uploadBoolUniform(value & 0xffff);
shaderUnit.vs.uploadBoolUniform(newValue & 0xffff);
break;
}
@ -309,7 +309,7 @@ void GPU::writeInternalReg(u32 index, u32 value, u32 mask) {
case VertexIntUniform1:
case VertexIntUniform2:
case VertexIntUniform3: {
shaderUnit.vs.uploadIntUniform(index - VertexIntUniform0, value);
shaderUnit.vs.uploadIntUniform(index - VertexIntUniform0, newValue);
break;
}
@ -326,7 +326,7 @@ void GPU::writeInternalReg(u32 index, u32 value, u32 mask) {
}
case VertexShaderEntrypoint: {
shaderUnit.vs.entrypoint = value & 0xffff;
shaderUnit.vs.entrypoint = newValue & 0xffff;
break;
}
@ -336,13 +336,13 @@ void GPU::writeInternalReg(u32 index, u32 value, u32 mask) {
break;
*/
case VertexShaderTransferIndex: shaderUnit.vs.setBufferIndex(value); break;
case VertexShaderTransferIndex: shaderUnit.vs.setBufferIndex(newValue); break;
// Command lists can write to the command processor registers and change the command list stream
// Several games are known to do this, including New Super Mario Bros 2 and Super Mario 3D Land
case CmdBufTrigger0:
case CmdBufTrigger1: {
if (value != 0) { // A non-zero value triggers command list processing
if (newValue != 0) { // A non-zero value triggers command list processing
int bufferIndex = index - CmdBufTrigger0; // Index of the command buffer to execute (0 or 1)
u32 addr = (regs[CmdBufAddr0 + bufferIndex] & 0xfffffff) << 3;
u32 size = (regs[CmdBufSize0 + bufferIndex] & 0xfffff) << 3;

View file

@ -7,8 +7,9 @@
#include "helpers.hpp"
MiniAudioDevice::MiniAudioDevice(const AudioDeviceConfig& audioSettings)
: initialized(false), running(false), samples(nullptr), audioSettings(audioSettings) {}
MiniAudioDevice::MiniAudioDevice(const AudioDeviceConfig& audioSettings) : AudioDeviceInterface(nullptr, audioSettings), initialized(false) {
running = false;
}
void MiniAudioDevice::init(Samples& samples, bool safe) {
this->samples = &samples;
@ -212,4 +213,4 @@ void MiniAudioDevice::close() {
ma_device_uninit(&device);
ma_context_uninit(&context);
}
}
}

View file

@ -69,6 +69,10 @@ void Kernel::serviceSVC(u32 svc) {
case 0x3A: getResourceLimitCurrentValues(); break;
case 0x3B: getThreadContext(); break;
case 0x3D: outputDebugString(); break;
// Luma SVCs
case 0x93: svcInvalidateInstructionCacheRange(); break;
case 0x94: svcInvalidateEntireInstructionCache(); break;
default: Helpers::panic("Unimplemented svc: %X @ %08X", svc, regs[15]); break;
}
@ -298,6 +302,23 @@ void Kernel::duplicateHandle() {
}
void Kernel::clearInstructionCache() { cpu.clearCache(); }
void Kernel::clearInstructionCacheRange(u32 start, u32 size) { cpu.clearCacheRange(start, size); }
void Kernel::svcInvalidateInstructionCacheRange() {
const u32 start = regs[0];
const u32 size = regs[1];
logSVC("svcInvalidateInstructionCacheRange(start = %08X, size = %08X)\n", start, size);
clearInstructionCacheRange(start, size);
regs[0] = Result::Success;
}
void Kernel::svcInvalidateEntireInstructionCache() {
logSVC("svcInvalidateEntireInstructionCache()\n");
clearInstructionCache();
regs[0] = Result::Success;
}
namespace SystemInfoType {
enum : u32 {

View file

@ -122,7 +122,10 @@ void Kernel::mapMemoryBlock() {
}
if (KernelHandles::isSharedMemHandle(block)) {
if (block == KernelHandles::FontSharedMemHandle && addr == 0) addr = 0x18000000;
if (block == KernelHandles::FontSharedMemHandle && addr == 0) {
addr = getSharedFontVaddr();
}
u8* ptr = mem.mapSharedMemory(block, addr, myPerms, otherPerms); // Map shared memory block
// Pass pointer to shared memory to the appropriate service
@ -216,3 +219,8 @@ void Kernel::unmapMemoryBlock() {
Helpers::warn("Stubbed svcUnmapMemoryBlock!");
regs[0] = Result::Success;
}
u32 Kernel::getSharedFontVaddr() {
// Place shared font at the very beginning of system FCRAM
return mem.getLinearHeapVaddr() + Memory::FCRAM_APPLICATION_SIZE;
}

View file

@ -8,6 +8,7 @@
#include "PICA/float_types.hpp"
#include "PICA/gpu.hpp"
#include "PICA/pica_frag_uniforms.hpp"
#include "PICA/pica_hash.hpp"
#include "PICA/pica_simd.hpp"
#include "PICA/regs.hpp"
#include "PICA/shader_decompiler.hpp"
@ -51,17 +52,12 @@ void RendererGL::reset() {
gl.useProgram(oldProgram); // Switch to old GL program
}
#ifdef USING_GLES
fragShaderGen.setTarget(PICA::ShaderGen::API::GLES, PICA::ShaderGen::Language::GLSL);
#endif
}
void RendererGL::initGraphicsContextInternal() {
gl.reset();
auto gl_resources = cmrc::RendererGL::get_filesystem();
auto vertexShaderSource = gl_resources.open("opengl_vertex_shader.vert");
auto fragmentShaderSource = gl_resources.open("opengl_fragment_shader.frag");
@ -70,16 +66,7 @@ void RendererGL::initGraphicsContextInternal() {
triangleProgram.create({vert, frag});
initUbershader(triangleProgram);
auto displayVertexShaderSource = gl_resources.open("opengl_display.vert");
auto displayFragmentShaderSource = gl_resources.open("opengl_display.frag");
OpenGL::Shader vertDisplay({displayVertexShaderSource.begin(), displayVertexShaderSource.size()}, OpenGL::Vertex);
OpenGL::Shader fragDisplay({displayFragmentShaderSource.begin(), displayFragmentShaderSource.size()}, OpenGL::Fragment);
displayProgram.create({vertDisplay, fragDisplay});
gl.useProgram(displayProgram);
glUniform1i(OpenGL::uniformLocation(displayProgram, "u_texture"), 0); // Init sampler object
compileDisplayShader();
// Create stream buffers for vertex, index and uniform buffers
static constexpr usize hwIndexBufferSize = 2_MB;
static constexpr usize hwVertexBufferSize = 16_MB;
@ -191,6 +178,7 @@ void RendererGL::initGraphicsContextInternal() {
}
reset();
fragShaderGen.setTarget(driverInfo.usingGLES ? PICA::ShaderGen::API::GLES : PICA::ShaderGen::API::GL, PICA::ShaderGen::Language::GLSL);
// Populate our driver info structure
driverInfo.supportsExtFbFetch = (GLAD_GL_EXT_shader_framebuffer_fetch != 0);
@ -372,17 +360,29 @@ void RendererGL::bindTexturesToSlots() {
const u32 width = getBits<16, 11>(dim);
const u32 addr = (regs[ioBase + 4] & 0x0FFFFFFF) << 3;
u32 format = regs[ioBase + (i == 0 ? 13 : 5)] & 0xF;
glActiveTexture(GL_TEXTURE0 + i);
if (addr != 0) [[likely]] {
Texture targetTex(addr, static_cast<PICA::TextureFmt>(format), width, height, config);
if (hashTextures) {
const u8* startPointer = gpu.getPointerPhys<u8>(targetTex.location);
const usize sizeInBytes = targetTex.sizeInBytes();
if (startPointer == nullptr || (sizeInBytes > 0 && gpu.getPointerPhys<u8>(targetTex.location + sizeInBytes - 1) == nullptr))
[[unlikely]] {
Helpers::warn("Out-of-bounds texture fetch");
} else {
targetTex.hash = PICAHash::computeHash((const char*)startPointer, sizeInBytes);
}
}
OpenGL::Texture tex = getTexture(targetTex);
tex.bind();
} else {
// Mapping a texture from NULL. PICA seems to read the last sampled colour, but for now we will display a black texture instead since it is far easier.
// Games that do this don't really care what it does, they just expect the PICA to not crash, since it doesn't have a PU/MMU and can do all sorts of
// Weird invalid memory accesses without crashing
// Mapping a texture from NULL. PICA seems to read the last sampled colour, but for now we will display a black texture instead since it
// is far easier. Games that do this don't really care what it does, they just expect the PICA to not crash, since it doesn't have a
// PU/MMU and can do all sorts of Weird invalid memory accesses without crashing
blankTexture.bind();
}
}
@ -805,6 +805,8 @@ void RendererGL::textureCopy(u32 inputAddr, u32 outputAddr, u32 totalBytes, u32
shutUpCounter++;
printf("RendererGL::TextureCopy failed to locate src framebuffer!\n");
}
doSoftwareTextureCopy(inputAddr, outputAddr, copySize, inputWidth, inputGap, outputWidth, outputGap);
return;
}
@ -850,9 +852,9 @@ OpenGL::Program& RendererGL::getSpecializedShader() {
PICA::FragmentConfig fsConfig(regs);
// If we're not on GLES, ignore the logic op configuration and don't generate redundant shaders for it, since we use hw logic ops
#ifndef USING_GLES
fsConfig.outConfig.logicOpMode = PICA::LogicOpMode(0);
#endif
if (!driverInfo.usingGLES) {
fsConfig.outConfig.logicOpMode = PICA::LogicOpMode(0);
}
OpenGL::Shader& fragShader = shaderCache.fragmentShaderCache[fsConfig];
if (!fragShader.exists()) {
@ -1010,7 +1012,7 @@ bool RendererGL::prepareForDraw(ShaderUnit& shaderUnit, PICA::DrawAcceleration*
std::string picaShaderSource = PICA::ShaderGen::decompileShader(
shaderUnit.vs, *emulatorConfig, shaderUnit.vs.entrypoint,
Helpers::isAndroid() ? 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
@ -1156,6 +1158,19 @@ void RendererGL::initUbershader(OpenGL::Program& program) {
glUniform1i(OpenGL::uniformLocation(program, "u_tex_luts"), 3);
}
void RendererGL::compileDisplayShader() {
auto gl_resources = cmrc::RendererGL::get_filesystem();
auto displayVertexShaderSource = driverInfo.usingGLES ? gl_resources.open("opengl_es_display.vert") : gl_resources.open("opengl_display.vert");
auto displayFragmentShaderSource = driverInfo.usingGLES ? gl_resources.open("opengl_es_display.frag") : gl_resources.open("opengl_display.frag");
OpenGL::Shader vertDisplay({displayVertexShaderSource.begin(), displayVertexShaderSource.size()}, OpenGL::Vertex);
OpenGL::Shader fragDisplay({displayFragmentShaderSource.begin(), displayFragmentShaderSource.size()}, OpenGL::Fragment);
displayProgram.create({vertDisplay, fragDisplay});
gl.useProgram(displayProgram);
glUniform1i(OpenGL::uniformLocation(displayProgram, "u_texture"), 0); // Init sampler object
}
void RendererGL::accelerateVertexUpload(ShaderUnit& shaderUnit, PICA::DrawAcceleration* accel) {
u32 buffer = 0; // Vertex buffer index for non-fixed attributes
u32 attrCount = 0;
@ -1250,4 +1265,20 @@ void RendererGL::accelerateVertexUpload(ShaderUnit& shaderUnit, PICA::DrawAccele
);
}
}
}
}
void RendererGL::setupGLES() {
driverInfo.usingGLES = true;
// OpenGL ES hardware is typically way too slow to use the ubershader (eg RPi, mobile phones, handhelds) or has other issues with it.
// So, display a warning and turn them off on OpenGL ES.
if (emulatorConfig->useUbershaders) {
emulatorConfig->useUbershaders = false;
Helpers::warn("Ubershaders enabled on OpenGL ES. This usually results in a worse experience, turning it off...");
}
// Stub out logic operations so that calling them doesn't crash the emulator
if (!glLogicOp) {
glLogicOp = [](GLenum) {};
}
}

View file

@ -1,116 +0,0 @@
#include <algorithm>
#include "colour.hpp"
#include "renderer_mtl/mtl_texture.hpp"
#include "renderer_mtl/renderer_mtl.hpp"
using namespace Helpers;
namespace Metal {
static constexpr u32 signExtend3To32(u32 val) {
return (u32)(s32(val) << 29 >> 29);
}
u32 Texture::getTexelETC(bool hasAlpha, u32 u, u32 v, u32 width, std::span<const u8> data) {
// Pixel offset of the 8x8 tile based on u, v and the width of the texture
u32 offs = ((u & ~7) * 8) + ((v & ~7) * width);
if (!hasAlpha) {
offs >>= 1;
}
// In-tile offsets for u/v
u &= 7;
v &= 7;
// ETC1(A4) also subdivide the 8x8 tile to 4 4x4 tiles
// Each tile is 8 bytes for ETC1, but since ETC1A4 has 4 alpha bits per pixel, that becomes 16 bytes
const u32 subTileSize = hasAlpha ? 16 : 8;
const u32 subTileIndex = (u / 4) + 2 * (v / 4); // Which of the 4 subtiles is this texel in?
// In-subtile offsets for u/v
u &= 3;
v &= 3;
offs += subTileSize * subTileIndex;
u32 alpha;
const u64* ptr = reinterpret_cast<const u64*>(data.data() + offs); // Cast to u64*
if (hasAlpha) {
// First 64 bits of the 4x4 subtile are alpha data
const u64 alphaData = *ptr++;
alpha = Colour::convert4To8Bit((alphaData >> (4 * (u * 4 + v))) & 0xf);
} else {
alpha = 0xff; // ETC1 without alpha uses ff for every pixel
}
// Next 64 bits of the subtile are colour data
u64 colourData = *ptr;
return decodeETC(alpha, u, v, colourData);
}
u32 Texture::decodeETC(u32 alpha, u32 u, u32 v, u64 colourData) {
static constexpr u32 modifiers[8][2] = {
{2, 8}, {5, 17}, {9, 29}, {13, 42}, {18, 60}, {24, 80}, {33, 106}, {47, 183},
};
// Parse colour data for 4x4 block
const u32 subindices = getBits<0, 16, u32>(colourData);
const u32 negationFlags = getBits<16, 16, u32>(colourData);
const bool flip = getBit<32>(colourData);
const bool diffMode = getBit<33>(colourData);
// Note: index1 is indeed stored on the higher bits, with index2 in the lower bits
const u32 tableIndex1 = getBits<37, 3, u32>(colourData);
const u32 tableIndex2 = getBits<34, 3, u32>(colourData);
const u32 texelIndex = u * 4 + v; // Index of the texel in the block
if (flip) std::swap(u, v);
s32 r, g, b;
if (diffMode) {
r = getBits<59, 5, s32>(colourData);
g = getBits<51, 5, s32>(colourData);
b = getBits<43, 5, s32>(colourData);
if (u >= 2) {
r += signExtend3To32(getBits<56, 3, u32>(colourData));
g += signExtend3To32(getBits<48, 3, u32>(colourData));
b += signExtend3To32(getBits<40, 3, u32>(colourData));
}
// Expand from 5 to 8 bits per channel
r = Colour::convert5To8Bit(r);
g = Colour::convert5To8Bit(g);
b = Colour::convert5To8Bit(b);
} else {
if (u < 2) {
r = getBits<60, 4, s32>(colourData);
g = getBits<52, 4, s32>(colourData);
b = getBits<44, 4, s32>(colourData);
} else {
r = getBits<56, 4, s32>(colourData);
g = getBits<48, 4, s32>(colourData);
b = getBits<40, 4, s32>(colourData);
}
// Expand from 4 to 8 bits per channel
r = Colour::convert4To8Bit(r);
g = Colour::convert4To8Bit(g);
b = Colour::convert4To8Bit(b);
}
const u32 index = (u < 2) ? tableIndex1 : tableIndex2;
s32 modifier = modifiers[index][(subindices >> texelIndex) & 1];
if (((negationFlags >> texelIndex) & 1) != 0) {
modifier = -modifier;
}
r = std::clamp(r + modifier, 0, 255);
g = std::clamp(g + modifier, 0, 255);
b = std::clamp(b + modifier, 0, 255);
return (alpha << 24) | (u32(b) << 16) | (u32(g) << 8) | u32(r);
}
} // namespace Metal

View file

@ -1,16 +1,18 @@
#include "renderer_mtl/mtl_texture.hpp"
#include <fmt/format.h>
#include <array>
#include <memory>
#include "colour.hpp"
#include "renderer_mtl/objc_helper.hpp"
using namespace Helpers;
namespace Metal {
void Texture::allocate() {
formatInfo = PICA::getPixelFormatInfo(format);
formatInfo = PICA::getMTLPixelFormatInfo(format);
MTL::TextureDescriptor* descriptor = MTL::TextureDescriptor::alloc()->init();
descriptor->setTextureType(MTL::TextureType2D);
@ -20,11 +22,14 @@ namespace Metal {
descriptor->setUsage(MTL::TextureUsageShaderRead);
descriptor->setStorageMode(MTL::StorageModeShared); // TODO: use private + staging buffers?
texture = device->newTexture(descriptor);
texture->setLabel(toNSString(
"Texture " + std::string(PICA::textureFormatToString(format)) + " " + std::to_string(size.u()) + "x" + std::to_string(size.v())
));
texture->setLabel(toNSString(fmt::format("Base texture {} {}x{}", std::string(PICA::textureFormatToString(format)), size.u(), size.v())));
descriptor->release();
if (formatInfo.needsSwizzle) {
base = texture;
texture = base->newTextureView(formatInfo.pixelFormat, MTL::TextureType2D, NS::Range(0, 1), NS::Range(0, 1), formatInfo.swizzle);
}
setNewConfig(config);
}
@ -58,6 +63,11 @@ namespace Metal {
if (texture) {
texture->release();
}
if (base) {
base->release();
}
if (sampler) {
sampler->release();
}
@ -99,210 +109,19 @@ namespace Metal {
}
}
// u and v are the UVs of the relevant texel
// Texture data is stored interleaved in Morton order, ie in a Z - order curve as shown here
// https://en.wikipedia.org/wiki/Z-order_curve
// Textures are split into 8x8 tiles.This function returns the in - tile offset depending on the u & v of the texel
// The in - tile offset is the sum of 2 offsets, one depending on the value of u % 8 and the other on the value of y % 8
// As documented in this picture https ://en.wikipedia.org/wiki/File:Moser%E2%80%93de_Bruijn_addition.svg
u32 Texture::mortonInterleave(u32 u, u32 v) {
static constexpr u32 xOffsets[] = {0, 1, 4, 5, 16, 17, 20, 21};
static constexpr u32 yOffsets[] = {0, 2, 8, 10, 32, 34, 40, 42};
return xOffsets[u & 7] + yOffsets[v & 7];
}
// Get the byte offset of texel (u, v) in the texture
u32 Texture::getSwizzledOffset(u32 u, u32 v, u32 width, u32 bytesPerPixel) {
u32 offset = ((u & ~7) * 8) + ((v & ~7) * width); // Offset of the 8x8 tile the texel belongs to
offset += mortonInterleave(u, v); // Add the in-tile offset of the texel
return offset * bytesPerPixel;
}
// Same as the above code except we need to divide by 2 because 4 bits is smaller than a byte
u32 Texture::getSwizzledOffset_4bpp(u32 u, u32 v, u32 width) {
u32 offset = ((u & ~7) * 8) + ((v & ~7) * width); // Offset of the 8x8 tile the texel belongs to
offset += mortonInterleave(u, v); // Add the in-tile offset of the texel
return offset / 2;
}
u8 Texture::decodeTexelU8(u32 u, u32 v, PICA::TextureFmt fmt, std::span<const u8> data) {
switch (fmt) {
case PICA::TextureFmt::A4: {
const u32 offset = getSwizzledOffset_4bpp(u, v, size.u());
// For odd U coordinates, grab the top 4 bits, and the low 4 bits for even coordinates
u8 alpha = data[offset] >> ((u % 2) ? 4 : 0);
alpha = Colour::convert4To8Bit(getBits<0, 4>(alpha));
// A8
return alpha;
}
case PICA::TextureFmt::A8: {
u32 offset = getSwizzledOffset(u, v, size.u(), 1);
const u8 alpha = data[offset];
// A8
return alpha;
}
default: Helpers::panic("[Texture::DecodeTexel] Unimplemented format = %d", static_cast<int>(fmt));
}
}
u16 Texture::decodeTexelU16(u32 u, u32 v, PICA::TextureFmt fmt, std::span<const u8> data) {
switch (fmt) {
case PICA::TextureFmt::RG8: {
u32 offset = getSwizzledOffset(u, v, size.u(), 2);
constexpr u8 b = 0;
const u8 g = data[offset];
const u8 r = data[offset + 1];
// RG8
return (g << 8) | r;
}
case PICA::TextureFmt::RGBA4: {
u32 offset = getSwizzledOffset(u, v, size.u(), 2);
u16 texel = u16(data[offset]) | (u16(data[offset + 1]) << 8);
u8 alpha = getBits<0, 4, u8>(texel);
u8 b = getBits<4, 4, u8>(texel);
u8 g = getBits<8, 4, u8>(texel);
u8 r = getBits<12, 4, u8>(texel);
// ABGR4
return (r << 12) | (g << 8) | (b << 4) | alpha;
}
case PICA::TextureFmt::RGBA5551: {
const u32 offset = getSwizzledOffset(u, v, size.u(), 2);
const u16 texel = u16(data[offset]) | (u16(data[offset + 1]) << 8);
u8 alpha = getBit<0>(texel) ? 0xff : 0;
u8 b = getBits<1, 5, u8>(texel);
u8 g = getBits<6, 5, u8>(texel);
u8 r = getBits<11, 5, u8>(texel);
// BGR5A1
return (alpha << 15) | (r << 10) | (g << 5) | b;
}
case PICA::TextureFmt::RGB565: {
const u32 offset = getSwizzledOffset(u, v, size.u(), 2);
const u16 texel = u16(data[offset]) | (u16(data[offset + 1]) << 8);
const u8 b = getBits<0, 5, u8>(texel);
const u8 g = getBits<5, 6, u8>(texel);
const u8 r = getBits<11, 5, u8>(texel);
// B5G6R5
return (r << 11) | (g << 5) | b;
}
case PICA::TextureFmt::IA4: {
const u32 offset = getSwizzledOffset(u, v, size.u(), 1);
const u8 texel = data[offset];
const u8 alpha = texel & 0xf;
const u8 intensity = texel >> 4;
// ABGR4
return (intensity << 12) | (intensity << 8) | (intensity << 4) | alpha;
}
case PICA::TextureFmt::I4: {
u32 offset = getSwizzledOffset_4bpp(u, v, size.u());
// For odd U coordinates, grab the top 4 bits, and the low 4 bits for even coordinates
u8 intensity = data[offset] >> ((u % 2) ? 4 : 0);
intensity = getBits<0, 4>(intensity);
// ABGR4
return (intensity << 12) | (intensity << 8) | (intensity << 4) | 0xff;
}
default: Helpers::panic("[Texture::DecodeTexel] Unimplemented format = %d", static_cast<int>(fmt));
}
}
u32 Texture::decodeTexelU32(u32 u, u32 v, PICA::TextureFmt fmt, std::span<const u8> data) {
switch (fmt) {
case PICA::TextureFmt::RGB8: {
const u32 offset = getSwizzledOffset(u, v, size.u(), 3);
const u8 b = data[offset];
const u8 g = data[offset + 1];
const u8 r = data[offset + 2];
// RGBA8
return (0xff << 24) | (b << 16) | (g << 8) | r;
}
case PICA::TextureFmt::RGBA8: {
const u32 offset = getSwizzledOffset(u, v, size.u(), 4);
const u8 alpha = data[offset];
const u8 b = data[offset + 1];
const u8 g = data[offset + 2];
const u8 r = data[offset + 3];
// RGBA8
return (alpha << 24) | (b << 16) | (g << 8) | r;
}
case PICA::TextureFmt::I8: {
u32 offset = getSwizzledOffset(u, v, size.u(), 1);
const u8 intensity = data[offset];
// RGBA8
return (0xff << 24) | (intensity << 16) | (intensity << 8) | intensity;
}
case PICA::TextureFmt::IA8: {
u32 offset = getSwizzledOffset(u, v, size.u(), 2);
// Same as I8 except each pixel gets its own alpha value too
const u8 alpha = data[offset];
const u8 intensity = data[offset + 1];
// RGBA8
return (alpha << 24) | (intensity << 16) | (intensity << 8) | intensity;
}
case PICA::TextureFmt::ETC1: return getTexelETC(false, u, v, size.u(), data);
case PICA::TextureFmt::ETC1A4: return getTexelETC(true, u, v, size.u(), data);
default: Helpers::panic("[Texture::DecodeTexel] Unimplemented format = %d", static_cast<int>(fmt));
}
}
void Texture::decodeTexture(std::span<const u8> data) {
std::vector<u8> decoded;
decoded.reserve(u64(size.u()) * u64(size.v()) * formatInfo.bytesPerTexel);
std::unique_ptr<u8[]> decodedData(new u8[u64(size.u()) * u64(size.v()) * formatInfo.bytesPerTexel]);
// This pointer will be incremented by our texture decoders
u8* decodePtr = decodedData.get();
// Decode texels line by line
for (u32 v = 0; v < size.v(); v++) {
for (u32 u = 0; u < size.u(); u++) {
if (formatInfo.bytesPerTexel == 1) {
u8 texel = decodeTexelU8(u, v, format, data);
decoded.push_back(texel);
} else if (formatInfo.bytesPerTexel == 2) {
u16 texel = decodeTexelU16(u, v, format, data);
decoded.push_back((texel & 0x00ff) >> 0);
decoded.push_back((texel & 0xff00) >> 8);
} else if (formatInfo.bytesPerTexel == 4) {
u32 texel = decodeTexelU32(u, v, format, data);
decoded.push_back((texel & 0x000000ff) >> 0);
decoded.push_back((texel & 0x0000ff00) >> 8);
decoded.push_back((texel & 0x00ff0000) >> 16);
decoded.push_back((texel & 0xff000000) >> 24);
} else {
Helpers::panic("[Texture::decodeTexture] Unimplemented bytesPerTexel (%u)", formatInfo.bytesPerTexel);
}
formatInfo.decoder(size, u, v, data, decodePtr);
decodePtr += formatInfo.bytesPerTexel;
}
}
texture->replaceRegion(MTL::Region(0, 0, size.u(), size.v()), 0, 0, decoded.data(), formatInfo.bytesPerTexel * size.u(), 0);
texture->replaceRegion(MTL::Region(0, 0, size.u(), size.v()), 0, 0, decodedData.get(), formatInfo.bytesPerTexel * size.u(), 0);
}
} // namespace Metal

View file

@ -0,0 +1,62 @@
#include "renderer_mtl/pica_to_mtl.hpp"
#include "renderer_mtl/texture_decoder.hpp"
using namespace Helpers;
namespace PICA {
MTLPixelFormatInfo mtlPixelFormatInfos[14] = {
{MTL::PixelFormatRGBA8Unorm, 4, decodeTexelABGR8ToRGBA8}, // RGBA8
{MTL::PixelFormatRGBA8Unorm, 4, decodeTexelBGR8ToRGBA8}, // RGB8
{MTL::PixelFormatBGR5A1Unorm, 2, decodeTexelA1BGR5ToBGR5A1}, // RGBA5551
{MTL::PixelFormatB5G6R5Unorm, 2, decodeTexelB5G6R5ToB5G6R5}, // RGB565
{MTL::PixelFormatABGR4Unorm, 2, decodeTexelABGR4ToABGR4}, // RGBA4
{MTL::PixelFormatRG8Unorm,
2,
decodeTexelAI8ToRG8,
true,
{
.red = MTL::TextureSwizzleRed,
.green = MTL::TextureSwizzleRed,
.blue = MTL::TextureSwizzleRed,
.alpha = MTL::TextureSwizzleGreen,
}}, // IA8
{MTL::PixelFormatRG8Unorm, 2, decodeTexelGR8ToRG8}, // RG8
{MTL::PixelFormatR8Unorm,
1,
decodeTexelI8ToR8,
true,
{.red = MTL::TextureSwizzleRed, .green = MTL::TextureSwizzleRed, .blue = MTL::TextureSwizzleRed, .alpha = MTL::TextureSwizzleOne}}, // I8
{MTL::PixelFormatA8Unorm, 1, decodeTexelA8ToA8}, // A8
{MTL::PixelFormatABGR4Unorm, 2, decodeTexelAI4ToABGR4}, // IA4
{MTL::PixelFormatR8Unorm,
1,
decodeTexelI4ToR8,
true,
{.red = MTL::TextureSwizzleRed, .green = MTL::TextureSwizzleRed, .blue = MTL::TextureSwizzleRed, .alpha = MTL::TextureSwizzleOne}}, // I4
{MTL::PixelFormatA8Unorm, 1, decodeTexelA4ToA8}, // A4
{MTL::PixelFormatRGBA8Unorm, 4, decodeTexelETC1ToRGBA8}, // ETC1
{MTL::PixelFormatRGBA8Unorm, 4, decodeTexelETC1A4ToRGBA8}, // ETC1A4
};
void checkForMTLPixelFormatSupport(MTL::Device* device) {
if (!device->supportsFamily(MTL::GPUFamilyApple1)) {
mtlPixelFormatInfos[2] = {MTL::PixelFormatRGBA8Unorm, 4, decodeTexelA1BGR5ToRGBA8};
mtlPixelFormatInfos[3] = {MTL::PixelFormatRGBA8Unorm, 4, decodeTexelB5G6R5ToRGBA8};
mtlPixelFormatInfos[4] = {MTL::PixelFormatRGBA8Unorm, 4, decodeTexelABGR4ToRGBA8};
mtlPixelFormatInfos[9] = {
MTL::PixelFormatRG8Unorm,
2,
decodeTexelAI4ToRG8,
true,
{
.red = MTL::TextureSwizzleRed,
.green = MTL::TextureSwizzleRed,
.blue = MTL::TextureSwizzleRed,
.alpha = MTL::TextureSwizzleGreen,
}
};
}
}
} // namespace PICA

View file

@ -9,6 +9,7 @@
#undef NO
#include "PICA/gpu.hpp"
#include "PICA/pica_hash.hpp"
#include "SDL_metal.h"
using namespace PICA;
@ -30,7 +31,6 @@ PICA::ColorFmt ToColorFormat(u32 format) {
}
MTL::Library* loadLibrary(MTL::Device* device, const cmrc::file& shaderSource) {
// MTL::CompileOptions* compileOptions = MTL::CompileOptions::alloc()->init();
NS::Error* error = nullptr;
MTL::Library* library = device->newLibrary(Metal::createDispatchData(shaderSource.begin(), shaderSource.size()), &error);
// MTL::Library* library = device->newLibrary(NS::String::string(source.c_str(), NS::ASCIIStringEncoding), compileOptions, &error);
@ -56,12 +56,18 @@ void RendererMTL::reset() {
colorRenderTargetCache.reset();
}
void RendererMTL::setMTKLayer(void* layer) {
metalLayer = (CA::MetalLayer*)layer;
}
void RendererMTL::display() {
CA::MetalDrawable* drawable = metalLayer->nextDrawable();
if (!drawable) {
return;
}
MTL::Texture* texture = drawable->texture();
using namespace PICA::ExternalRegs;
// Top screen
@ -87,13 +93,13 @@ void RendererMTL::display() {
MTL::RenderPassDescriptor* renderPassDescriptor = MTL::RenderPassDescriptor::alloc()->init();
MTL::RenderPassColorAttachmentDescriptor* colorAttachment = renderPassDescriptor->colorAttachments()->object(0);
colorAttachment->setTexture(drawable->texture());
colorAttachment->setTexture(texture);
colorAttachment->setLoadAction(MTL::LoadActionClear);
colorAttachment->setClearColor(MTL::ClearColor{0.0f, 0.0f, 0.0f, 1.0f});
colorAttachment->setStoreAction(MTL::StoreActionStore);
nextRenderPassName = "Display";
beginRenderPassIfNeeded(renderPassDescriptor, false, drawable->texture());
beginRenderPassIfNeeded(renderPassDescriptor, false, texture);
renderCommandEncoder->setRenderPipelineState(displayPipeline);
renderCommandEncoder->setFragmentSamplerState(nearestSampler, 0);
@ -119,17 +125,22 @@ void RendererMTL::display() {
// Inform the vertex buffer cache that the frame ended
vertexBufferCache.endFrame();
// Release
drawable->release();
}
void RendererMTL::initGraphicsContext(SDL_Window* window) {
// On iOS, the SwiftUI side handles the MetalLayer
#ifdef PANDA3DS_IOS
device = MTL::CreateSystemDefaultDevice();
#else
// TODO: what should be the type of the view?
void* view = SDL_Metal_CreateView(window);
metalLayer = (CA::MetalLayer*)SDL_Metal_GetLayer(view);
device = MTL::CreateSystemDefaultDevice();
metalLayer->setDevice(device);
#endif
checkForMTLPixelFormatSupport(device);
commandQueue = device->newCommandQueue();
// Textures
@ -426,7 +437,7 @@ void RendererMTL::textureCopy(u32 inputAddr, u32 outputAddr, u32 totalBytes, u32
// Find the source surface.
auto srcFramebuffer = getColorRenderTarget(inputAddr, PICA::ColorFmt::RGBA8, copyStride, copyHeight, false);
if (!srcFramebuffer) {
Helpers::warn("RendererMTL::TextureCopy failed to locate src framebuffer!\n");
doSoftwareTextureCopy(inputAddr, outputAddr, copySize, inputWidth, inputGap, outputWidth, outputGap);
return;
}
nextRenderPassName = "Clear before texture copy";
@ -719,6 +730,19 @@ void RendererMTL::bindTexturesToSlots() {
if (addr != 0) [[likely]] {
Metal::Texture targetTex(device, addr, static_cast<PICA::TextureFmt>(format), width, height, config);
if (hashTextures) {
const u8* startPointer = gpu.getPointerPhys<u8>(targetTex.location);
const usize sizeInBytes = targetTex.sizeInBytes();
if (startPointer == nullptr || (sizeInBytes > 0 && gpu.getPointerPhys<u8>(targetTex.location + sizeInBytes - 1) == nullptr))
[[unlikely]] {
Helpers::warn("Out-of-bounds texture fetch");
} else {
targetTex.hash = PICAHash::computeHash((const char*)startPointer, sizeInBytes);
}
}
auto tex = getTexture(targetTex);
commandEncoder.setFragmentTexture(tex.texture, i);
commandEncoder.setFragmentSamplerState(tex.sampler ? tex.sampler : nearestSampler, i);

View file

@ -0,0 +1,334 @@
#include "renderer_mtl/texture_decoder.hpp"
#include <array>
#include <string>
#include "colour.hpp"
#include "math_util.hpp"
using namespace Helpers;
// u and v are the UVs of the relevant texel
// Texture data is stored interleaved in Morton order, ie in a Z - order curve as shown here
// https://en.wikipedia.org/wiki/Z-order_curve
// Textures are split into 8x8 tiles.This function returns the in - tile offset depending on the u & v of the texel
// The in - tile offset is the sum of 2 offsets, one depending on the value of u % 8 and the other on the value of y % 8
// As documented in this picture https ://en.wikipedia.org/wiki/File:Moser%E2%80%93de_Bruijn_addition.svg
u32 mortonInterleave(u32 u, u32 v) {
static constexpr u32 xOffsets[] = {0, 1, 4, 5, 16, 17, 20, 21};
static constexpr u32 yOffsets[] = {0, 2, 8, 10, 32, 34, 40, 42};
return xOffsets[u & 7] + yOffsets[v & 7];
}
// Get the byte offset of texel (u, v) in the texture
u32 getSwizzledOffset(u32 u, u32 v, u32 width, u32 bytesPerPixel) {
u32 offset = ((u & ~7) * 8) + ((v & ~7) * width); // Offset of the 8x8 tile the texel belongs to
offset += mortonInterleave(u, v); // Add the in-tile offset of the texel
return offset * bytesPerPixel;
}
// Same as the above code except we need to divide by 2 because 4 bits is smaller than a byte
u32 getSwizzledOffset_4bpp(u32 u, u32 v, u32 width) {
u32 offset = ((u & ~7) * 8) + ((v & ~7) * width); // Offset of the 8x8 tile the texel belongs to
offset += mortonInterleave(u, v); // Add the in-tile offset of the texel
return offset / 2;
}
void decodeTexelABGR8ToRGBA8(OpenGL::uvec2 size, u32 u, u32 v, std::span<const u8> inData, u8* outData) {
const u32 offset = getSwizzledOffset(u, v, size.u(), 4);
const u8 alpha = inData[offset];
const u8 b = inData[offset + 1];
const u8 g = inData[offset + 2];
const u8 r = inData[offset + 3];
*outData++ = r;
*outData++ = g;
*outData++ = b;
*outData++ = alpha;
}
void decodeTexelBGR8ToRGBA8(OpenGL::uvec2 size, u32 u, u32 v, std::span<const u8> inData, u8* outData) {
const u32 offset = getSwizzledOffset(u, v, size.u(), 3);
const u8 b = inData[offset];
const u8 g = inData[offset + 1];
const u8 r = inData[offset + 2];
*outData++ = r;
*outData++ = g;
*outData++ = b;
*outData++ = 0xff;
}
void decodeTexelA1BGR5ToBGR5A1(OpenGL::uvec2 size, u32 u, u32 v, std::span<const u8> inData, u8* outData) {
const u32 offset = getSwizzledOffset(u, v, size.u(), 2);
const u16 texel = u16(inData[offset]) | (u16(inData[offset + 1]) << 8);
u8 alpha = getBit<0>(texel);
u8 b = getBits<1, 5, u8>(texel);
u8 g = getBits<6, 5, u8>(texel);
u8 r = getBits<11, 5, u8>(texel);
u16 outTexel = (alpha << 15) | (r << 10) | (g << 5) | b;
*outData++ = outTexel & 0xff;
*outData++ = (outTexel >> 8) & 0xff;
}
void decodeTexelA1BGR5ToRGBA8(OpenGL::uvec2 size, u32 u, u32 v, std::span<const u8> inData, u8* outData) {
const u32 offset = getSwizzledOffset(u, v, size.u(), 2);
const u16 texel = u16(inData[offset]) | (u16(inData[offset + 1]) << 8);
u8 alpha = getBit<0>(texel) ? 0xff : 0;
u8 b = Colour::convert5To8Bit(getBits<1, 5, u8>(texel));
u8 g = Colour::convert5To8Bit(getBits<6, 5, u8>(texel));
u8 r = Colour::convert5To8Bit(getBits<11, 5, u8>(texel));
*outData++ = r;
*outData++ = g;
*outData++ = b;
*outData++ = alpha;
}
void decodeTexelB5G6R5ToB5G6R5(OpenGL::uvec2 size, u32 u, u32 v, std::span<const u8> inData, u8* outData) {
const u32 offset = getSwizzledOffset(u, v, size.u(), 2);
const u16 texel = u16(inData[offset]) | (u16(inData[offset + 1]) << 8);
*outData++ = texel & 0xff;
*outData++ = (texel >> 8) & 0xff;
}
void decodeTexelB5G6R5ToRGBA8(OpenGL::uvec2 size, u32 u, u32 v, std::span<const u8> inData, u8* outData) {
const u32 offset = getSwizzledOffset(u, v, size.u(), 2);
const u16 texel = u16(inData[offset]) | (u16(inData[offset + 1]) << 8);
const u8 b = Colour::convert5To8Bit(getBits<0, 5, u8>(texel));
const u8 g = Colour::convert6To8Bit(getBits<5, 6, u8>(texel));
const u8 r = Colour::convert5To8Bit(getBits<11, 5, u8>(texel));
*outData++ = r;
*outData++ = g;
*outData++ = b;
*outData++ = 0xff;
}
void decodeTexelABGR4ToABGR4(OpenGL::uvec2 size, u32 u, u32 v, std::span<const u8> inData, u8* outData) {
u32 offset = getSwizzledOffset(u, v, size.u(), 2);
u16 texel = u16(inData[offset]) | (u16(inData[offset + 1]) << 8);
u8 alpha = getBits<0, 4, u8>(texel);
u8 b = getBits<4, 4, u8>(texel);
u8 g = getBits<8, 4, u8>(texel);
u8 r = getBits<12, 4, u8>(texel);
*outData++ = (b << 4) | alpha;
*outData++ = (r << 4) | g;
}
void decodeTexelABGR4ToRGBA8(OpenGL::uvec2 size, u32 u, u32 v, std::span<const u8> inData, u8* outData) {
u32 offset = getSwizzledOffset(u, v, size.u(), 2);
u16 texel = u16(inData[offset]) | (u16(inData[offset + 1]) << 8);
u8 alpha = Colour::convert4To8Bit(getBits<0, 4, u8>(texel));
u8 b = Colour::convert4To8Bit(getBits<4, 4, u8>(texel));
u8 g = Colour::convert4To8Bit(getBits<8, 4, u8>(texel));
u8 r = Colour::convert4To8Bit(getBits<12, 4, u8>(texel));
*outData++ = r;
*outData++ = g;
*outData++ = b;
*outData++ = alpha;
}
void decodeTexelAI8ToRG8(OpenGL::uvec2 size, u32 u, u32 v, std::span<const u8> inData, u8* outData) {
u32 offset = getSwizzledOffset(u, v, size.u(), 2);
// Same as I8 except each pixel gets its own alpha value too
const u8 alpha = inData[offset];
const u8 intensity = inData[offset + 1];
*outData++ = intensity;
*outData++ = alpha;
}
void decodeTexelGR8ToRG8(OpenGL::uvec2 size, u32 u, u32 v, std::span<const u8> inData, u8* outData) {
u32 offset = getSwizzledOffset(u, v, size.u(), 2);
constexpr u8 b = 0;
const u8 g = inData[offset];
const u8 r = inData[offset + 1];
*outData++ = r;
*outData++ = g;
}
void decodeTexelI8ToR8(OpenGL::uvec2 size, u32 u, u32 v, std::span<const u8> inData, u8* outData) {
u32 offset = getSwizzledOffset(u, v, size.u(), 1);
const u8 intensity = inData[offset];
*outData++ = intensity;
}
void decodeTexelA8ToA8(OpenGL::uvec2 size, u32 u, u32 v, std::span<const u8> inData, u8* outData) {
u32 offset = getSwizzledOffset(u, v, size.u(), 1);
const u8 alpha = inData[offset];
*outData++ = alpha;
}
void decodeTexelAI4ToABGR4(OpenGL::uvec2 size, u32 u, u32 v, std::span<const u8> inData, u8* outData) {
const u32 offset = getSwizzledOffset(u, v, size.u(), 1);
const u8 texel = inData[offset];
const u8 alpha = texel & 0xf;
const u8 intensity = texel >> 4;
*outData++ = (intensity << 4) | intensity;
*outData++ = (alpha << 4) | intensity;
}
void decodeTexelAI4ToRG8(OpenGL::uvec2 size, u32 u, u32 v, std::span<const u8> inData, u8* outData) {
const u32 offset = getSwizzledOffset(u, v, size.u(), 1);
const u8 texel = inData[offset];
const u8 alpha = Colour::convert4To8Bit(texel & 0xf);
const u8 intensity = Colour::convert4To8Bit(texel >> 4);
*outData++ = intensity;
*outData++ = alpha;
}
void decodeTexelI4ToR8(OpenGL::uvec2 size, u32 u, u32 v, std::span<const u8> inData, u8* outData) {
u32 offset = getSwizzledOffset_4bpp(u, v, size.u());
// For odd U coordinates, grab the top 4 bits, and the low 4 bits for even coordinates
u8 intensity = inData[offset] >> ((u % 2) ? 4 : 0);
intensity = Colour::convert4To8Bit(getBits<0, 4>(intensity));
*outData++ = intensity;
}
void decodeTexelA4ToA8(OpenGL::uvec2 size, u32 u, u32 v, std::span<const u8> inData, u8* outData) {
const u32 offset = getSwizzledOffset_4bpp(u, v, size.u());
// For odd U coordinates, grab the top 4 bits, and the low 4 bits for even coordinates
u8 alpha = inData[offset] >> ((u % 2) ? 4 : 0);
alpha = Colour::convert4To8Bit(getBits<0, 4>(alpha));
*outData++ = alpha;
}
static constexpr u32 signExtend3To32(u32 val) { return (u32)(s32(val) << 29 >> 29); }
void decodeETC(u32 u, u32 v, u64 colourData, u32 alpha, u8* outData) {
static constexpr u32 modifiers[8][2] = {
{2, 8}, {5, 17}, {9, 29}, {13, 42}, {18, 60}, {24, 80}, {33, 106}, {47, 183},
};
// Parse colour data for 4x4 block
const u32 subindices = getBits<0, 16, u32>(colourData);
const u32 negationFlags = getBits<16, 16, u32>(colourData);
const bool flip = getBit<32>(colourData);
const bool diffMode = getBit<33>(colourData);
// Note: index1 is indeed stored on the higher bits, with index2 in the lower bits
const u32 tableIndex1 = getBits<37, 3, u32>(colourData);
const u32 tableIndex2 = getBits<34, 3, u32>(colourData);
const u32 texelIndex = u * 4 + v; // Index of the texel in the block
if (flip) std::swap(u, v);
s32 r, g, b;
if (diffMode) {
r = getBits<59, 5, s32>(colourData);
g = getBits<51, 5, s32>(colourData);
b = getBits<43, 5, s32>(colourData);
if (u >= 2) {
r += signExtend3To32(getBits<56, 3, u32>(colourData));
g += signExtend3To32(getBits<48, 3, u32>(colourData));
b += signExtend3To32(getBits<40, 3, u32>(colourData));
}
// Expand from 5 to 8 bits per channel
r = Colour::convert5To8Bit(r);
g = Colour::convert5To8Bit(g);
b = Colour::convert5To8Bit(b);
} else {
if (u < 2) {
r = getBits<60, 4, s32>(colourData);
g = getBits<52, 4, s32>(colourData);
b = getBits<44, 4, s32>(colourData);
} else {
r = getBits<56, 4, s32>(colourData);
g = getBits<48, 4, s32>(colourData);
b = getBits<40, 4, s32>(colourData);
}
// Expand from 4 to 8 bits per channel
r = Colour::convert4To8Bit(r);
g = Colour::convert4To8Bit(g);
b = Colour::convert4To8Bit(b);
}
const u32 index = (u < 2) ? tableIndex1 : tableIndex2;
s32 modifier = modifiers[index][(subindices >> texelIndex) & 1];
if (((negationFlags >> texelIndex) & 1) != 0) {
modifier = -modifier;
}
r = std::clamp(r + modifier, 0, 255);
g = std::clamp(g + modifier, 0, 255);
b = std::clamp(b + modifier, 0, 255);
*outData++ = r;
*outData++ = g;
*outData++ = b;
*outData++ = alpha;
}
template <bool hasAlpha>
void getTexelETC(OpenGL::uvec2 size, u32 u, u32 v, std::span<const u8> inData, u8* outData) {
// Pixel offset of the 8x8 tile based on u, v and the width of the texture
u32 offs = ((u & ~7) * 8) + ((v & ~7) * size.u());
if (!hasAlpha) {
offs >>= 1;
}
// In-tile offsets for u/v
u &= 7;
v &= 7;
// ETC1(A4) also subdivide the 8x8 tile to 4 4x4 tiles
// Each tile is 8 bytes for ETC1, but since ETC1A4 has 4 alpha bits per pixel, that becomes 16 bytes
const u32 subTileSize = hasAlpha ? 16 : 8;
const u32 subTileIndex = (u / 4) + 2 * (v / 4); // Which of the 4 subtiles is this texel in?
// In-subtile offsets for u/v
u &= 3;
v &= 3;
offs += subTileSize * subTileIndex;
u32 alpha;
const u64* ptr = reinterpret_cast<const u64*>(inData.data() + offs); // Cast to u64*
if (hasAlpha) {
// First 64 bits of the 4x4 subtile are alpha data
const u64 alphaData = *ptr++;
alpha = Colour::convert4To8Bit((alphaData >> (4 * (u * 4 + v))) & 0xf);
} else {
alpha = 0xff; // ETC1 without alpha uses ff for every pixel
}
// Next 64 bits of the subtile are colour data
u64 colourData = *ptr;
decodeETC(u, v, colourData, alpha, outData);
}
void decodeTexelETC1ToRGBA8(OpenGL::uvec2 size, u32 u, u32 v, std::span<const u8> inData, u8* outData) {
getTexelETC<false>(size, u, v, inData, outData);
}
void decodeTexelETC1A4ToRGBA8(OpenGL::uvec2 size, u32 u, u32 v, std::span<const u8> inData, u8* outData) {
getTexelETC<true>(size, u, v, inData, outData);
}

View file

@ -885,10 +885,17 @@ void RendererVK::display() {
}
}
// DynamicLoader is in a different namespace in different versions of Vulkan-Hpp
#if VK_HEADER_VERSION >= 301
using VulkanDynamicLoader = vk::detail::DynamicLoader;
#else
using VulkanDynamicLoader = vk::DynamicLoader;
#endif
void RendererVK::initGraphicsContext(SDL_Window* window) {
targetWindow = window;
// Resolve all instance function pointers
static vk::DynamicLoader dl;
static VulkanDynamicLoader dl;
VULKAN_HPP_DEFAULT_DISPATCHER.init(dl.getProcAddress<PFN_vkGetInstanceProcAddr>("vkGetInstanceProcAddr"));
// Create Instance
@ -1588,4 +1595,4 @@ void RendererVK::deinitGraphicsContext() {
// TODO: Make it so that depth and colour buffers get written back to 3DS memory
printf("RendererVK::DeinitGraphicsContext called\n");
}
}

View file

@ -61,8 +61,8 @@ namespace Vulkan {
}
VKAPI_ATTR VkBool32 VKAPI_CALL debugMessageCallback(
VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType,
const VkDebugUtilsMessengerCallbackDataEXT* callbackData, void* userData
vk::DebugUtilsMessageSeverityFlagBitsEXT messageSeverity, vk::DebugUtilsMessageTypeFlagsEXT messageType,
const vk::DebugUtilsMessengerCallbackDataEXT* callbackData, void* userData
) {
debugMessageCallback(
vk::DebugUtilsMessageSeverityFlagBitsEXT(messageSeverity), vk::DebugUtilsMessageTypeFlagsEXT(messageType), *callbackData
@ -70,7 +70,7 @@ namespace Vulkan {
return VK_FALSE;
}
#ifdef GPU_DEBUG_INFO
#ifdef GPU_DEBUG_INFO
void setObjectName(vk::Device device, vk::ObjectType objectType, const void* objectHandle, const char* format, ...) {
va_list args;
va_start(args, format);

View file

@ -1,4 +1,5 @@
#include "services/ac.hpp"
#include "ipc.hpp"
namespace ACCommands {
@ -36,7 +37,8 @@ void ACService::handleSyncRequest(u32 messagePointer) {
case ACCommands::IsConnected: isConnected(messagePointer); break;
case ACCommands::RegisterDisconnectEvent: registerDisconnectEvent(messagePointer); break;
case ACCommands::SetClientVersion: setClientVersion(messagePointer); break;
default:
default:
mem.write32(messagePointer + 4, Result::Success);
Helpers::warn("AC service requested. Command: %08X\n", command);
break;
@ -77,7 +79,7 @@ void ACService::getLastErrorCode(u32 messagePointer) {
mem.write32(messagePointer, IPC::responseHeader(0x0A, 2, 0));
mem.write32(messagePointer + 4, Result::Success);
mem.write32(messagePointer + 8, 0); // Hopefully this means no error?
mem.write32(messagePointer + 8, 0); // Hopefully this means no error?
}
void ACService::getConnectingInfraPriority(u32 messagePointer) {

View file

@ -397,7 +397,7 @@ void APTService::setScreencapPostPermission(u32 messagePointer) {
void APTService::getSharedFont(u32 messagePointer) {
log("APT::GetSharedFont\n");
constexpr u32 fontVaddr = 0x18000000;
const u32 fontVaddr = kernel.getSharedFontVaddr();
mem.write32(messagePointer, IPC::responseHeader(0x44, 2, 2));
mem.write32(messagePointer + 4, Result::Success);
mem.write32(messagePointer + 8, fontVaddr);

View file

@ -1,4 +1,5 @@
#include "services/boss.hpp"
#include "ipc.hpp"
namespace BOSSCommands {
@ -39,9 +40,7 @@ namespace BOSSCommands {
};
}
void BOSSService::reset() {
optoutFlag = 0;
}
void BOSSService::reset() { optoutFlag = 0; }
void BOSSService::handleSyncRequest(u32 messagePointer) {
const u32 command = mem.read32(messagePointer);
@ -84,7 +83,7 @@ void BOSSService::handleSyncRequest(u32 messagePointer) {
case 0x04500102: // Home Menu uses this command, what is this?
Helpers::warn("BOSS command 0x04500102");
mem.write32(messagePointer, IPC::responseHeader(0x450, 1, 0));
mem.write32(messagePointer, IPC::responseHeader(0x450, 1, 0));
mem.write32(messagePointer + 4, Result::Success);
break;
@ -126,7 +125,7 @@ void BOSSService::getTaskState(u32 messagePointer) {
mem.write32(messagePointer + 4, Result::Success);
mem.write8(messagePointer + 8, 0); // TaskStatus: Report the task finished successfully
mem.write32(messagePointer + 12, 0); // Current state value for task PropertyID 0x4
mem.write8(messagePointer + 16, 0); // TODO: Figure out what this should be
mem.write8(messagePointer + 16, 0); // TODO: Figure out what this should be
}
void BOSSService::getTaskStatus(u32 messagePointer) {
@ -177,15 +176,15 @@ void BOSSService::getErrorCode(u32 messagePointer) {
log("BOSS::GetErrorCode (stubbed)\n");
mem.write32(messagePointer, IPC::responseHeader(0x2E, 2, 0));
mem.write32(messagePointer + 4, Result::Success);
mem.write32(messagePointer + 8, Result::Success); // No error code
mem.write32(messagePointer + 8, Result::Success); // No error code
}
void BOSSService::getStorageEntryInfo(u32 messagePointer) {
log("BOSS::GetStorageEntryInfo (undocumented)\n");
mem.write32(messagePointer, IPC::responseHeader(0x30, 3, 0));
mem.write32(messagePointer + 4, Result::Success);
mem.write32(messagePointer + 8, 0); // u32, unknown meaning
mem.write16(messagePointer + 12, 0); // s16, unknown meaning
mem.write32(messagePointer + 8, 0); // u32, unknown meaning
mem.write16(messagePointer + 12, 0); // s16, unknown meaning
}
void BOSSService::sendProperty(u32 messagePointer) {
@ -200,7 +199,6 @@ void BOSSService::sendProperty(u32 messagePointer) {
// TODO: Should this do anything else?
}
void BOSSService::receiveProperty(u32 messagePointer) {
const u32 id = mem.read32(messagePointer + 4);
const u32 size = mem.read32(messagePointer + 8);
@ -209,13 +207,13 @@ void BOSSService::receiveProperty(u32 messagePointer) {
log("BOSS::ReceiveProperty (id = %d, size = %08X, ptr = %08X) (stubbed)\n", id, size, ptr);
mem.write32(messagePointer, IPC::responseHeader(0x16, 2, 2));
mem.write32(messagePointer + 4, Result::Success);
mem.write32(messagePointer + 8, 0); // Read size
mem.write32(messagePointer + 8, 0); // Read size
}
// This seems to accept a KEvent as a parameter and register it for something Spotpass related
// I need to update the 3DBrew page when it's known what it does properly
void BOSSService::registerNewArrivalEvent(u32 messagePointer) {
const Handle eventHandle = mem.read32(messagePointer + 4); // Kernel event handle to register
const Handle eventHandle = mem.read32(messagePointer + 4); // Kernel event handle to register
log("BOSS::RegisterNewArrivalEvent (handle = %X)\n", eventHandle);
mem.write32(messagePointer, IPC::responseHeader(0x8, 1, 0));
@ -279,7 +277,7 @@ void BOSSService::getNewArrivalFlag(u32 messagePointer) {
log("BOSS::GetNewArrivalFlag (stubbed)\n");
mem.write32(messagePointer, IPC::responseHeader(0x7, 2, 0));
mem.write32(messagePointer + 4, Result::Success);
mem.write8(messagePointer + 8, 0); // Flag
mem.write8(messagePointer + 8, 0); // Flag
}
void BOSSService::startBgImmediate(u32 messagePointer) {

View file

@ -21,41 +21,80 @@ namespace CFGCommands {
SetConfigInfoBlk4 = 0x04020082,
UpdateConfigNANDSavegame = 0x04030000,
GetCountryCodeString = 0x00090040,
GetCountryCodeID = 0x000A0040,
IsFangateSupported = 0x000B0000,
SetConfigInfoBlk4 = 0x04020082,
UpdateConfigNANDSavegame = 0x04030000,
GetLocalFriendCodeSeed = 0x04050000,
SecureInfoGetByte101 = 0x04070000,
};
}
// cfg:i commands
namespace CFGICommands {
enum : u32 {
GetConfigInfoBlk8 = 0x08010082,
};
}
// cfg:nor commands
namespace NORCommands {
enum : u32 {
Initialize = 0x00010040,
ReadData = 0x00050082,
};
}
void CFGService::reset() {}
void CFGService::handleSyncRequest(u32 messagePointer, CFGService::Type type) {
const u32 command = mem.read32(messagePointer);
switch (command) {
case CFGCommands::GetConfigInfoBlk2: [[likely]] getConfigInfoBlk2(messagePointer); break;
case CFGCommands::GetCountryCodeID: getCountryCodeID(messagePointer); break;
case CFGCommands::GetRegionCanadaUSA: getRegionCanadaUSA(messagePointer); break;
case CFGCommands::GetSystemModel: getSystemModel(messagePointer); break;
case CFGCommands::GenHashConsoleUnique: genUniqueConsoleHash(messagePointer); break;
case CFGCommands::SecureInfoGetRegion: secureInfoGetRegion(messagePointer); break;
case CFGCommands::TranslateCountryInfo: translateCountryInfo(messagePointer); break;
if (type != Type::NOR) {
switch (command) {
case CFGCommands::GetConfigInfoBlk2: [[likely]] getConfigInfoBlk2(messagePointer); break;
case CFGCommands::GetCountryCodeString: getCountryCodeString(messagePointer); break;
case CFGCommands::GetCountryCodeID: getCountryCodeID(messagePointer); break;
case CFGCommands::GetRegionCanadaUSA: getRegionCanadaUSA(messagePointer); break;
case CFGCommands::GetSystemModel: getSystemModel(messagePointer); break;
case CFGCommands::GenHashConsoleUnique: genUniqueConsoleHash(messagePointer); break;
case CFGCommands::IsFangateSupported: isFangateSupported(messagePointer); break;
case CFGCommands::SecureInfoGetRegion: secureInfoGetRegion(messagePointer); break;
case CFGCommands::TranslateCountryInfo: translateCountryInfo(messagePointer); break;
default:
if (type == Type::S) {
// cfg:s-only functions
switch (command) {
case CFGCommands::GetConfigInfoBlk8: getConfigInfoBlk8(messagePointer); break;
case CFGCommands::GetLocalFriendCodeSeed: getLocalFriendCodeSeed(messagePointer); break;
case CFGCommands::SecureInfoGetByte101: secureInfoGetByte101(messagePointer); break;
case CFGCommands::SetConfigInfoBlk4: setConfigInfoBlk4(messagePointer); break;
case CFGCommands::UpdateConfigNANDSavegame: updateConfigNANDSavegame(messagePointer); break;
default:
if (type == Type::S) {
// cfg:s (and cfg:i) functions only functions
switch (command) {
case CFGCommands::GetConfigInfoBlk8: getConfigInfoBlk8(messagePointer, command); break;
case CFGCommands::GetLocalFriendCodeSeed: getLocalFriendCodeSeed(messagePointer); break;
case CFGCommands::SecureInfoGetByte101: secureInfoGetByte101(messagePointer); break;
case CFGCommands::SetConfigInfoBlk4: setConfigInfoBlk4(messagePointer); break;
case CFGCommands::UpdateConfigNANDSavegame: updateConfigNANDSavegame(messagePointer); break;
default: Helpers::panic("CFG:S service requested. Command: %08X\n", command);
default: Helpers::panic("CFG:S service requested. Command: %08X\n", command);
}
} else if (type == Type::I) {
switch (command) {
case CFGCommands::GetConfigInfoBlk8:
case CFGICommands::GetConfigInfoBlk8: getConfigInfoBlk8(messagePointer, command); break;
default: Helpers::panic("CFG:I service requested. Command: %08X\n", command);
}
} else {
Helpers::panic("CFG service requested. Command: %08X\n", command);
}
} else {
Helpers::panic("CFG service requested. Command: %08X\n", command);
}
break;
break;
}
} else {
// cfg:nor functions
switch (command) {
case NORCommands::Initialize: norInitialize(messagePointer); break;
case NORCommands::ReadData: norReadData(messagePointer); break;
default: Helpers::panic("CFG:NOR service requested. Command: %08X\n", command);
}
}
}
@ -88,14 +127,14 @@ void CFGService::getConfigInfoBlk2(u32 messagePointer) {
mem.write32(messagePointer + 4, Result::Success);
}
void CFGService::getConfigInfoBlk8(u32 messagePointer) {
void CFGService::getConfigInfoBlk8(u32 messagePointer, u32 commandWord) {
u32 size = mem.read32(messagePointer + 4);
u32 blockID = mem.read32(messagePointer + 8);
u32 output = mem.read32(messagePointer + 16); // Pointer to write the output data to
log("CFG::GetConfigInfoBlk8 (size = %X, block ID = %X, output pointer = %08X)\n", size, blockID, output);
getConfigInfo(output, blockID, size, 0x8);
mem.write32(messagePointer, IPC::responseHeader(0x401, 1, 2));
mem.write32(messagePointer, IPC::responseHeader(commandWord >> 16, 1, 2));
mem.write32(messagePointer + 4, Result::Success);
}
@ -104,7 +143,7 @@ void CFGService::getConfigInfo(u32 output, u32 blockID, u32 size, u32 permission
if (size == 1 && blockID == 0x70001) { // Sound output mode
mem.write8(output, static_cast<u8>(DSPService::SoundOutputMode::Stereo));
} else if (size == 1 && blockID == 0xA0002) { // System language
mem.write8(output, static_cast<u8>(LanguageCodes::English));
mem.write8(output, static_cast<u8>(settings.systemLanguage));
} else if (size == 4 && blockID == 0xB0000) { // Country info
mem.write8(output, 0); // Unknown
mem.write8(output + 1, 0); // Unknown
@ -178,6 +217,23 @@ void CFGService::getConfigInfo(u32 output, u32 blockID, u32 size, u32 permission
mem.write32(output, 0);
} else if (size == 1 && blockID == 0xE0000) {
mem.write8(output, 0);
} else if ((size == 512 && blockID == 0xC0002) || (size == 148 && blockID == 0x100001)) {
// CTR parental controls block (0xC0002) and TWL parental controls block (0x100001)
for (u32 i = 0; i < size; i++) {
mem.write8(output + i, 0);
}
} else if (size == 2 && blockID == 0x100000) {
// EULA agreed
mem.write8(output, 1); // We have agreed to the EULA
mem.write8(output + 1, 1); // EULA version = 1
} else if (size == 1 && blockID == 0x100002) {
Helpers::warn("Unimplemented TWL country code access");
mem.write8(output, 0);
} else if (size == 24 && blockID == 0x180001) {
// QTM calibration data
for (u32 i = 0; i < size; i++) {
mem.write8(output + i, 0);
}
} else {
Helpers::panic("Unhandled GetConfigInfoBlk2 configuration. Size = %d, block = %X", size, blockID);
}
@ -262,6 +318,24 @@ void CFGService::getCountryCodeID(u32 messagePointer) {
}
}
void CFGService::getCountryCodeString(u32 messagePointer) {
const u16 id = mem.read16(messagePointer + 4);
log("CFG::getCountryCodeString (id = %04X)\n", id);
mem.write32(messagePointer, IPC::responseHeader(0x09, 2, 0));
for (auto [string, code] : countryCodeToTableIDMap) {
if (code == id) {
mem.write32(messagePointer + 4, Result::Success);
mem.write32(messagePointer + 8, u32(string));
return;
}
}
// Code is not a valid country code, return an appropriate error
mem.write32(messagePointer + 4, 0xD90103FA);
}
void CFGService::secureInfoGetByte101(u32 messagePointer) {
log("CFG::SecureInfoGetByte101\n");
@ -329,4 +403,28 @@ void CFGService::translateCountryInfo(u32 messagePointer) {
mem.write32(messagePointer, IPC::responseHeader(0x8, 2, 0));
mem.write32(messagePointer + 4, Result::Success);
mem.write32(messagePointer + 8, result);
}
void CFGService::isFangateSupported(u32 messagePointer) {
log("CFG::IsFangateSupported\n");
// TODO: What even is fangate?
mem.write32(messagePointer, IPC::responseHeader(0xB, 2, 0));
mem.write32(messagePointer + 4, Result::Success);
mem.write32(messagePointer + 8, 1);
}
void CFGService::norInitialize(u32 messagePointer) {
log("CFG::NOR::Initialize\n");
mem.write32(messagePointer, IPC::responseHeader(0x1, 1, 0));
mem.write32(messagePointer + 4, Result::Success);
}
void CFGService::norReadData(u32 messagePointer) {
log("CFG::NOR::ReadData\n");
Helpers::warn("Unimplemented CFG::NOR::ReadData");
mem.write32(messagePointer, IPC::responseHeader(0x5, 1, 0));
mem.write32(messagePointer + 4, Result::Success);
}

View file

@ -1,4 +1,5 @@
#include "services/gsp_gpu.hpp"
#include "PICA/regs.hpp"
#include "ipc.hpp"
#include "kernel.hpp"
@ -39,7 +40,7 @@ namespace GXCommands {
}
void GPUService::reset() {
privilegedProcess = 0xFFFFFFFF; // Set the privileged process to an invalid handle
privilegedProcess = 0xFFFFFFFF; // Set the privileged process to an invalid handle
interruptEvent = std::nullopt;
gspThreadCount = 0;
sharedMem = nullptr;
@ -113,38 +114,38 @@ void GPUService::registerInterruptRelayQueue(u32 messagePointer) {
log("GSP::GPU::RegisterInterruptRelayQueue (flags = %X, event handle = %X)\n", flags, eventHandle);
const auto event = kernel.getObject(eventHandle, KernelObjectType::Event);
if (event == nullptr) { // Check if interrupt event is invalid
if (event == nullptr) { // Check if interrupt event is invalid
Helpers::panic("Invalid event passed to GSP::GPU::RegisterInterruptRelayQueue");
} else {
interruptEvent = eventHandle;
}
mem.write32(messagePointer, IPC::responseHeader(0x13, 2, 2));
mem.write32(messagePointer + 4, Result::GSP::SuccessRegisterIRQ); // First init returns a unique result
mem.write32(messagePointer + 8, 0); // TODO: GSP module thread index
mem.write32(messagePointer + 12, 0); // Translation descriptor
mem.write32(messagePointer + 4, Result::GSP::SuccessRegisterIRQ); // First init returns a unique result
mem.write32(messagePointer + 8, 0); // TODO: GSP module thread index
mem.write32(messagePointer + 12, 0); // Translation descriptor
mem.write32(messagePointer + 16, KernelHandles::GSPSharedMemHandle);
}
void GPUService::requestInterrupt(GPUInterrupt type) {
if (sharedMem == nullptr) [[unlikely]] { // Shared memory hasn't been set up yet
if (sharedMem == nullptr) [[unlikely]] { // Shared memory hasn't been set up yet
return;
}
// TODO: Add support for multiple GSP threads
u8 index = sharedMem[0]; // The interrupt block is normally located at sharedMem + processGSPIndex*0x40
u8 index = sharedMem[0]; // The interrupt block is normally located at sharedMem + processGSPIndex*0x40
u8& interruptCount = sharedMem[1];
u8 flagIndex = (index + interruptCount) % 0x34;
interruptCount++;
sharedMem[2] = 0; // Set error code to 0
sharedMem[0xC + flagIndex] = static_cast<u8>(type); // Write interrupt type to queue
sharedMem[2] = 0; // Set error code to 0
sharedMem[0xC + flagIndex] = static_cast<u8>(type); // Write interrupt type to queue
// Update framebuffer info in shared memory
// Most new games check to make sure that the "flag" byte of the framebuffer info header is set to 0
// Not emulating this causes Yoshi's Wooly World, Captain Toad, Metroid 2 et al to hang
if (type == GPUInterrupt::VBlank0 || type == GPUInterrupt::VBlank1) {
int screen = static_cast<u32>(type) - static_cast<u32>(GPUInterrupt::VBlank0); // 0 for top screen, 1 for bottom
int screen = static_cast<u32>(type) - static_cast<u32>(GPUInterrupt::VBlank0); // 0 for top screen, 1 for bottom
FramebufferUpdate* update = getFramebufferInfo(screen);
if (update->dirtyFlag & 1) {
@ -165,7 +166,6 @@ void GPUService::readHwRegs(u32 messagePointer) {
const u32 initialDataPointer = mem.read32(messagePointer + 0x104);
u32 dataPointer = initialDataPointer;
log("GSP::GPU::ReadHwRegs (GPU address = %08X, size = %X, data address = %08X)\n", ioAddr, size, dataPointer);
// Check for alignment
if ((size & 3) || (ioAddr & 3) || (dataPointer & 3)) {
@ -197,8 +197,8 @@ void GPUService::readHwRegs(u32 messagePointer) {
}
void GPUService::writeHwRegs(u32 messagePointer) {
u32 ioAddr = mem.read32(messagePointer + 4); // GPU address based at 0x1EB00000, word aligned
const u32 size = mem.read32(messagePointer + 8); // Size in bytes
u32 ioAddr = mem.read32(messagePointer + 4); // GPU address based at 0x1EB00000, word aligned
const u32 size = mem.read32(messagePointer + 8); // Size in bytes
u32 dataPointer = mem.read32(messagePointer + 16);
log("GSP::GPU::writeHwRegs (GPU address = %08X, size = %X, data address = %08X)\n", ioAddr, size, dataPointer);
@ -230,14 +230,14 @@ void GPUService::writeHwRegs(u32 messagePointer) {
// Update sequential GPU registers using an array of data and mask values using this formula
// GPU register = (register & ~mask) | (data & mask).
void GPUService::writeHwRegsWithMask(u32 messagePointer) {
u32 ioAddr = mem.read32(messagePointer + 4); // GPU address based at 0x1EB00000, word aligned
const u32 size = mem.read32(messagePointer + 8); // Size in bytes
u32 ioAddr = mem.read32(messagePointer + 4); // GPU address based at 0x1EB00000, word aligned
const u32 size = mem.read32(messagePointer + 8); // Size in bytes
u32 dataPointer = mem.read32(messagePointer + 16); // Data pointer
u32 maskPointer = mem.read32(messagePointer + 24); // Mask pointer
u32 dataPointer = mem.read32(messagePointer + 16); // Data pointer
u32 maskPointer = mem.read32(messagePointer + 24); // Mask pointer
log("GSP::GPU::writeHwRegsWithMask (GPU address = %08X, size = %X, data address = %08X, mask address = %08X)\n",
ioAddr, size, dataPointer, maskPointer);
log("GSP::GPU::writeHwRegsWithMask (GPU address = %08X, size = %X, data address = %08X, mask address = %08X)\n", ioAddr, size, dataPointer,
maskPointer);
// Check for alignment
if ((size & 3) || (ioAddr & 3) || (dataPointer & 3) || (maskPointer & 3)) {
@ -351,11 +351,11 @@ void GPUService::setInternalPriorities(u32 messagePointer) {
}
void GPUService::processCommandBuffer() {
if (sharedMem == nullptr) [[unlikely]] { // Shared memory hasn't been set up yet
if (sharedMem == nullptr) [[unlikely]] { // Shared memory hasn't been set up yet
return;
}
constexpr int threadCount = 1; // TODO: More than 1 thread can have GSP commands at a time
constexpr int threadCount = 1; // TODO: More than 1 thread can have GSP commands at a time
for (int t = 0; t < threadCount; t++) {
u8* cmdBuffer = &sharedMem[0x800 + t * 0x200];
u8& commandsLeft = cmdBuffer[1];
@ -408,9 +408,9 @@ void GPUService::memoryFill(u32* cmd) {
u32 control = cmd[7];
// buf0 parameters
u32 start0 = cmd[1]; // Start address for the fill. If 0, don't fill anything
u32 value0 = cmd[2]; // Value to fill the framebuffer with
u32 end0 = cmd[3]; // End address for the fill
u32 start0 = cmd[1]; // Start address for the fill. If 0, don't fill anything
u32 value0 = cmd[2]; // Value to fill the framebuffer with
u32 end0 = cmd[3]; // End address for the fill
u32 control0 = control & 0xffff;
// buf1 parameters
@ -439,7 +439,7 @@ void GPUService::triggerDisplayTransfer(u32* cmd) {
log("GSP::GPU::TriggerDisplayTransfer (Stubbed)\n");
gpu.displayTransfer(inputAddr, outputAddr, inputSize, outputSize, flags);
requestInterrupt(GPUInterrupt::PPF); // Send "Display transfer finished" interrupt
requestInterrupt(GPUInterrupt::PPF); // Send "Display transfer finished" interrupt
}
void GPUService::triggerDMARequest(u32* cmd) {
@ -453,22 +453,14 @@ void GPUService::triggerDMARequest(u32* cmd) {
requestInterrupt(GPUInterrupt::DMA);
}
void GPUService::flushCacheRegions(u32* cmd) {
log("GSP::GPU::FlushCacheRegions (Stubbed)\n");
}
void GPUService::flushCacheRegions(u32* cmd) { log("GSP::GPU::FlushCacheRegions (Stubbed)\n"); }
void GPUService::setBufferSwapImpl(u32 screenId, const FramebufferInfo& info) {
using namespace PICA::ExternalRegs;
static constexpr std::array<u32, 8> fbAddresses = {
Framebuffer0AFirstAddr,
Framebuffer0BFirstAddr,
Framebuffer1AFirstAddr,
Framebuffer1BFirstAddr,
Framebuffer0ASecondAddr,
Framebuffer0BSecondAddr,
Framebuffer1ASecondAddr,
Framebuffer1BSecondAddr,
Framebuffer0AFirstAddr, Framebuffer0BFirstAddr, Framebuffer1AFirstAddr, Framebuffer1BFirstAddr,
Framebuffer0ASecondAddr, Framebuffer0BSecondAddr, Framebuffer1ASecondAddr, Framebuffer1BSecondAddr,
};
auto& regs = gpu.getExtRegisters();
@ -478,12 +470,7 @@ void GPUService::setBufferSwapImpl(u32 screenId, const FramebufferInfo& info) {
regs[fbAddresses[fbIndex + 1]] = VaddrToPaddr(info.rightFramebufferVaddr);
static constexpr std::array<u32, 6> configAddresses = {
Framebuffer0Config,
Framebuffer0Select,
Framebuffer0Stride,
Framebuffer1Config,
Framebuffer1Select,
Framebuffer1Stride,
Framebuffer0Config, Framebuffer0Select, Framebuffer0Stride, Framebuffer1Config, Framebuffer1Select, Framebuffer1Stride,
};
const u32 configIndex = screenId * 3;
@ -494,14 +481,14 @@ void GPUService::setBufferSwapImpl(u32 screenId, const FramebufferInfo& info) {
// Actually send command list (aka display list) to GPU
void GPUService::processCommandList(u32* cmd) {
const u32 address = cmd[1] & ~7; // Buffer address
const u32 size = cmd[2] & ~3; // Buffer size in bytes
[[maybe_unused]] const bool updateGas = cmd[3] == 1; // Update gas additive blend results (0 = don't update, 1 = update)
[[maybe_unused]] const bool flushBuffer = cmd[7] == 1; // Flush buffer (0 = don't flush, 1 = flush)
const u32 address = cmd[1] & ~7; // Buffer address
const u32 size = cmd[2] & ~3; // Buffer size in bytes
[[maybe_unused]] const bool updateGas = cmd[3] == 1; // Update gas additive blend results (0 = don't update, 1 = update)
[[maybe_unused]] const bool flushBuffer = cmd[7] == 1; // Flush buffer (0 = don't flush, 1 = flush)
log("GPU::GSP::processCommandList. Address: %08X, size in bytes: %08X\n", address, size);
gpu.startCommandList(address, size);
requestInterrupt(GPUInterrupt::P3D); // Send an IRQ when command list processing is over
requestInterrupt(GPUInterrupt::P3D); // Send an IRQ when command list processing is over
}
// TODO: Emulate the transfer engine & its registers
@ -576,4 +563,4 @@ void GPUService::importDisplayCaptureInfo(u32 messagePointer) {
mem.write32(messagePointer + 28, bottomScreenCapture.rightFramebuffer);
mem.write32(messagePointer + 32, bottomScreenCapture.format);
mem.write32(messagePointer + 36, bottomScreenCapture.stride);
}
}

View file

@ -1,4 +1,5 @@
#include "services/gsp_lcd.hpp"
#include "ipc.hpp"
namespace LCDCommands {

View file

@ -19,7 +19,11 @@ void HTTPService::handleSyncRequest(u32 messagePointer) {
case HTTPCommands::CreateRootCertChain: createRootCertChain(messagePointer); break;
case HTTPCommands::Initialize: initialize(messagePointer); break;
case HTTPCommands::RootCertChainAddDefaultCert: rootCertChainAddDefaultCert(messagePointer); break;
default: Helpers::panic("HTTP service requested. Command: %08X\n", command);
default:
Helpers::warn("HTTP service requested. Command: %08X\n", command);
mem.write32(messagePointer + 4, Result::FailurePlaceholder);
break;
}
}

View file

@ -22,6 +22,7 @@ namespace CROHeader {
NameOffset = 0x084,
NextCRO = 0x088,
PrevCRO = 0x08C,
FixedSize = 0x98,
OnUnresolved = 0x0AC,
CodeOffset = 0x0B0,
DataOffset = 0x0B8,
@ -167,6 +168,10 @@ public:
return mem.read32(croPointer + CROHeader::PrevCRO);
}
u32 getFixedSize() {
return mem.read32(croPointer + CROHeader::FixedSize);
}
void setNextCRO(u32 nextCRO) {
mem.write32(croPointer + CROHeader::NextCRO, nextCRO);
}
@ -1248,8 +1253,7 @@ void LDRService::initialize(u32 messagePointer) {
Helpers::panic("Failed to rebase CRS");
}
kernel.clearInstructionCache();
kernel.clearInstructionCacheRange(mapVaddr, size);
loadedCRS = mapVaddr;
mem.write32(messagePointer, IPC::responseHeader(0x1, 1, 0));
@ -1278,8 +1282,6 @@ void LDRService::linkCRO(u32 messagePointer) {
Helpers::panic("Failed to link CRO");
}
kernel.clearInstructionCache();
mem.write32(messagePointer, IPC::responseHeader(0x6, 1, 0));
mem.write32(messagePointer + 4, Result::Success);
}
@ -1346,8 +1348,7 @@ void LDRService::loadCRO(u32 messagePointer, bool isNew) {
// TODO: add fixing
cro.fix(fixLevel);
kernel.clearInstructionCache();
kernel.clearInstructionCacheRange(mapVaddr, size);
if (isNew) {
mem.write32(messagePointer, IPC::responseHeader(0x9, 2, 0));
@ -1377,7 +1378,6 @@ void LDRService::unloadCRO(u32 messagePointer) {
}
CRO cro(mem, mapVaddr, true);
cro.unregisterCRO(loadedCRS);
if (!cro.unlink(loadedCRS)) {
@ -1388,8 +1388,7 @@ void LDRService::unloadCRO(u32 messagePointer) {
Helpers::panic("Failed to unrebase CRO");
}
kernel.clearInstructionCache();
kernel.clearInstructionCacheRange(mapVaddr, cro.getFixedSize());
mem.write32(messagePointer, IPC::responseHeader(0x5, 1, 0));
mem.write32(messagePointer + 4, Result::Success);
}

View file

@ -1,4 +1,5 @@
#include "services/ptm.hpp"
#include "ipc.hpp"
namespace PTMCommands {
@ -128,7 +129,7 @@ void PTMService::getTotalStepCount(u32 messagePointer) {
log("PTM::GetTotalStepCount\n");
mem.write32(messagePointer, IPC::responseHeader(0xC, 2, 0));
mem.write32(messagePointer + 4, Result::Success);
mem.write32(messagePointer + 8, 3); // We walk a lot
mem.write32(messagePointer + 8, 3); // We walk a lot
}
void PTMService::configureNew3DSCPU(u32 messagePointer) {

View file

@ -114,6 +114,7 @@ static std::map<std::string, HorizonHandle> serviceMap = {
{ "cfg:u", KernelHandles::CFG_U },
{ "cfg:i", KernelHandles::CFG_I },
{ "cfg:s", KernelHandles::CFG_S },
{ "cfg:nor", KernelHandles::CFG_NOR },
{ "csnd:SND", KernelHandles::CSND },
{ "dlp:SRVR", KernelHandles::DLP_SRVR },
{ "dsp::DSP", KernelHandles::DSP },
@ -134,6 +135,7 @@ static std::map<std::string, HorizonHandle> serviceMap = {
{ "news:u", KernelHandles::NEWS_U },
{ "nfc:u", KernelHandles::NFC },
{ "ns:s", KernelHandles::NS_S },
{ "nwm::EXT", KernelHandles::NWM_EXT },
{ "nwm::UDS", KernelHandles::NWM_UDS },
{ "nim:aoc", KernelHandles::NIM_AOC },
{ "nim:u", KernelHandles::NIM_U },
@ -223,6 +225,7 @@ void ServiceManager::sendCommandToService(u32 messagePointer, Handle handle) {
case KernelHandles::CFG_U: cfg.handleSyncRequest(messagePointer, CFGService::Type::U); break;
case KernelHandles::CFG_I: cfg.handleSyncRequest(messagePointer, CFGService::Type::I); break;
case KernelHandles::CFG_S: cfg.handleSyncRequest(messagePointer, CFGService::Type::S); break;
case KernelHandles::CFG_NOR: cfg.handleSyncRequest(messagePointer, CFGService::Type::NOR); break;
case KernelHandles::CSND: csnd.handleSyncRequest(messagePointer); break;
case KernelHandles::DLP_SRVR: dlp_srvr.handleSyncRequest(messagePointer); break;
case KernelHandles::HID: hid.handleSyncRequest(messagePointer); break;
@ -246,6 +249,7 @@ void ServiceManager::sendCommandToService(u32 messagePointer, Handle handle) {
case KernelHandles::PTM_PLAY: ptm.handleSyncRequest(messagePointer, PTMService::Type::PLAY); break;
case KernelHandles::PTM_SYSM: ptm.handleSyncRequest(messagePointer, PTMService::Type::SYSM); break;
case KernelHandles::PTM_U: ptm.handleSyncRequest(messagePointer, PTMService::Type::U); break;
case KernelHandles::PTM_GETS: ptm.handleSyncRequest(messagePointer, PTMService::Type::GETS); break;
case KernelHandles::SOC: soc.handleSyncRequest(messagePointer); break;
case KernelHandles::SSL: ssl.handleSyncRequest(messagePointer); break;
case KernelHandles::Y2R: y2r.handleSyncRequest(messagePointer); break;

View file

@ -32,18 +32,14 @@ Emulator::Emulator()
dspService.setDSPCore(dsp.get());
audioDevice.init(dsp->getSamples());
setAudioEnabled(config.audioEnabled);
if (Renderdoc::isSupported() && config.enableRenderdoc) {
loadRenderdoc();
}
#ifdef PANDA3DS_ENABLE_DISCORD_RPC
if (config.discordRpcEnabled) {
discordRpc.init();
updateDiscord();
}
#endif
reloadSettings();
reset(ReloadOption::NoReload);
}
@ -105,13 +101,18 @@ std::filesystem::path Emulator::getConfigPath() {
if constexpr (Helpers::isAndroid()) {
return getAndroidAppPath() / "config.toml";
} else {
return std::filesystem::current_path() / "config.toml";
std::filesystem::path localPath = std::filesystem::current_path() / "config.toml";
if (std::filesystem::exists(localPath)) {
return localPath;
} else {
return getAppDataRoot() / "config.toml";
}
}
}
#endif
void Emulator::step() {}
void Emulator::render() {}
// Only resume if a ROM is properly loaded
void Emulator::resume() {
@ -456,6 +457,8 @@ void Emulator::reloadSettings() {
loadRenderdoc();
}
gpu.getRenderer()->setHashTextures(config.hashTextures);
#ifdef PANDA3DS_ENABLE_DISCORD_RPC
// Reload RPC setting if we're compiling with RPC support
@ -468,4 +471,4 @@ void Emulator::reloadSettings() {
}
}
#endif
}
}

View file

@ -11,7 +11,7 @@ FrontendSettings::Theme FrontendSettings::themeFromString(std::string inString)
std::transform(inString.begin(), inString.end(), inString.begin(), [](unsigned char c) { return std::tolower(c); });
static const std::unordered_map<std::string, Theme> map = {
{"system", Theme::System}, {"light", Theme::Light}, {"dark", Theme::Dark}, {"greetingscat", Theme::GreetingsCat}, {"cream", Theme::Cream},
{"system", Theme::System}, {"light", Theme::Light}, {"dark", Theme::Dark}, {"greetingscat", Theme::GreetingsCat}, {"cream", Theme::Cream}, {"oled", Theme::Oled}
};
if (auto search = map.find(inString); search != map.end()) {
@ -28,6 +28,7 @@ const char* FrontendSettings::themeToString(Theme theme) {
case Theme::Light: return "light";
case Theme::GreetingsCat: return "greetingscat";
case Theme::Cream: return "cream";
case Theme::Oled: return "oled";
case Theme::Dark:
default: return "dark";
@ -39,7 +40,7 @@ FrontendSettings::WindowIcon FrontendSettings::iconFromString(std::string inStri
static const std::unordered_map<std::string, WindowIcon> map = {
{"rpog", WindowIcon::Rpog}, {"rsyn", WindowIcon::Rsyn}, {"rcow", WindowIcon::Rcow},
{"rnap", WindowIcon::Rnap}, {"skyemu", WindowIcon::SkyEmu},
{"rnap", WindowIcon::Rnap}, {"skyemu", WindowIcon::SkyEmu}, {"runpog", WindowIcon::Runpog},
};
if (auto search = map.find(inString); search != map.end()) {
@ -56,6 +57,7 @@ const char* FrontendSettings::iconToString(WindowIcon icon) {
case WindowIcon::Rcow: return "rcow";
case WindowIcon::Rnap: return "rnap";
case WindowIcon::SkyEmu: return "skyemu";
case WindowIcon::Runpog: return "runpog";
case WindowIcon::Rpog:
default: return "rpog";

View file

@ -1,4 +1,6 @@
#include <metal_stdlib>
#include <TargetConditionals.h>
using namespace metal;
struct BasicVertexOut {
@ -219,12 +221,6 @@ struct Globals {
uint GPUREG_LIGHTING_LUTINPUT_SELECT;
uint GPUREG_LIGHTi_CONFIG;
// HACK
//bool lightingEnabled;
//uint8_t lightingNumLights;
//uint32_t lightingConfig1;
//uint16_t alphaControl;
float3 normal;
};
@ -655,14 +651,15 @@ float4 performLogicOp(LogicOp logicOp, float4 s, float4 d) {
return as_type<float4>(performLogicOpU(logicOp, as_type<uint4>(s), as_type<uint4>(d)));
}
fragment float4 fragmentDraw(DrawVertexOut in [[stage_in]], float4 prevColor [[color(0)]], constant PicaRegs& picaRegs [[buffer(0)]], constant FragTEV& tev [[buffer(1)]], constant LogicOp& logicOp [[buffer(2)]], constant uint2& lutSlices [[buffer(3)]], texture2d<float> tex0 [[texture(0)]], texture2d<float> tex1 [[texture(1)]], texture2d<float> tex2 [[texture(2)]], texture2d_array<float> texLightingLut [[texture(3)]], texture1d_array<float> texFogLut [[texture(4)]], sampler samplr0 [[sampler(0)]], sampler samplr1 [[sampler(1)]], sampler samplr2 [[sampler(2)]], sampler linearSampler [[sampler(3)]]) {
Globals globals;
// iOS simulator doesn't support fb fetch, so don't enable it
#ifndef TARGET_OS_SIMULATOR
#define PREVIOUS_COLOR_DECL float4 prevColor [[color(0)]],
#else
#define PREVIOUS_COLOR_DECL
#endif
// HACK
//globals.lightingEnabled = picaRegs.read(0x008Fu) != 0u;
//globals.lightingNumLights = picaRegs.read(0x01C2u);
//globals.lightingConfig1 = picaRegs.read(0x01C4u);
//globals.alphaControl = picaRegs.read(0x104);
fragment float4 fragmentDraw(DrawVertexOut in [[stage_in]], PREVIOUS_COLOR_DECL constant PicaRegs& picaRegs [[buffer(0)]], constant FragTEV& tev [[buffer(1)]], constant LogicOp& logicOp [[buffer(2)]], constant uint2& lutSlices [[buffer(3)]], texture2d<float> tex0 [[texture(0)]], texture2d<float> tex1 [[texture(1)]], texture2d<float> tex2 [[texture(2)]], texture2d_array<float> texLightingLut [[texture(3)]], texture1d_array<float> texFogLut [[texture(4)]], sampler samplr0 [[sampler(0)]], sampler samplr1 [[sampler(1)]], sampler samplr2 [[sampler(2)]], sampler linearSampler [[sampler(3)]]) {
Globals globals;
globals.tevSources[0] = in.color;
if (lightingEnabled) {
@ -755,5 +752,9 @@ fragment float4 fragmentDraw(DrawVertexOut in [[stage_in]], float4 prevColor [[c
}
}
#ifndef TARGET_OS_SIMULATOR
return performLogicOp(logicOp, color, prevColor);
}
#else
return performLogicOp(logicOp, color, float4(0.0));
#endif
}

View file

@ -0,0 +1,10 @@
#version 310 es
precision mediump float;
in vec2 UV;
out vec4 FragColor;
uniform sampler2D u_texture;
void main() {
FragColor = texture(u_texture, UV);
}

View file

@ -0,0 +1,25 @@
#version 310 es
precision mediump float;
out vec2 UV;
void main() {
const vec4 positions[4] = vec4[](
vec4(-1.0, 1.0, 1.0, 1.0), // Top-left
vec4(1.0, 1.0, 1.0, 1.0), // Top-right
vec4(-1.0, -1.0, 1.0, 1.0), // Bottom-left
vec4(1.0, -1.0, 1.0, 1.0) // Bottom-right
);
// The 3DS displays both screens' framebuffer rotated 90 deg counter clockwise
// So we adjust our texcoords accordingly
const vec2 texcoords[4] = vec2[](
vec2(1.0, 1.0), // Top-right
vec2(1.0, 0.0), // Bottom-right
vec2(0.0, 1.0), // Top-left
vec2(0.0, 0.0) // Bottom-left
);
gl_Position = positions[gl_VertexID];
UV = texcoords[gl_VertexID];
}

View file

@ -118,6 +118,7 @@ void HydraCore::resetContext() {
if (!gladLoadGLES2Loader(reinterpret_cast<GLADloadproc>(getProcAddress))) {
Helpers::panic("OpenGL ES init failed");
}
emulator->getRenderer()->setupGLES();
#else
if (!gladLoadGLLoader(reinterpret_cast<GLADloadproc>(getProcAddress))) {
Helpers::panic("OpenGL init failed");

38
src/ios_driver.mm Normal file
View file

@ -0,0 +1,38 @@
#import <Foundation/Foundation.h>
extern "C" {
#include "ios_driver.h"
}
// Apple's Foundation headers define some macros globablly that create issues with our own code, so remove the definitions
#undef ABS
#undef NO
#include <memory>
#include "emulator.hpp"
// The Objective-C++ bridge functions must be exported without name mangling in order for the SwiftUI frontend to be able to call them
#define IOS_EXPORT extern "C" __attribute__((visibility("default")))
std::unique_ptr<Emulator> emulator = nullptr;
HIDService* hidService = nullptr;
IOS_EXPORT void iosCreateEmulator() {
printf("Creating emulator\n");
emulator = std::make_unique<Emulator>();
hidService = &emulator->getServiceManager().getHID();
emulator->initGraphicsContext(nullptr);
}
IOS_EXPORT void iosRunFrame(CAMetalLayer* layer) {
void* layerBridged = (__bridge void*)layer;
emulator->getRenderer()->setMTKLayer(layerBridged);
emulator->runFrame();
}
IOS_EXPORT void iosLoadROM(NSString* pathNS) {
auto path = std::filesystem::path([pathNS UTF8String]);
emulator->loadROM(path);
}

View file

@ -78,6 +78,7 @@ AlberFunction(void, Initialize)(JNIEnv* env, jobject obj) {
}
__android_log_print(ANDROID_LOG_INFO, "AlberDriver", "OpenGL ES %d.%d", GLVersion.major, GLVersion.minor);
emulator->getRenderer()->setupGLES();
emulator->initGraphicsContext(nullptr);
}
@ -153,7 +154,6 @@ int AndroidUtils::openDocument(const char* path, const char* perms) {
jstring uri = env->NewStringUTF(path);
jstring jmode = env->NewStringUTF(perms);
jint result = env->CallStaticIntMethod(alberClass, alberClassOpenDocument, uri, jmode);
env->DeleteLocalRef(uri);

View file

@ -17,7 +17,8 @@ static retro_input_state_t inputStateCallback;
static retro_hw_render_callback hwRender;
static std::filesystem::path savePath;
static bool screenTouched;
static bool screenTouched = false;
static bool usingGLES = false;
std::unique_ptr<Emulator> emulator;
RendererGL* renderer;
@ -35,15 +36,19 @@ static void* getGLProcAddress(const char* name) {
}
static void videoResetContext() {
#ifdef USING_GLES
if (!gladLoadGLES2Loader(reinterpret_cast<GLADloadproc>(getGLProcAddress))) {
Helpers::panic("OpenGL ES init failed");
if (usingGLES) {
if (!gladLoadGLES2Loader(reinterpret_cast<GLADloadproc>(getGLProcAddress))) {
Helpers::panic("OpenGL ES init failed");
}
emulator->getRenderer()->setupGLES();
}
#else
if (!gladLoadGLLoader(reinterpret_cast<GLADloadproc>(getGLProcAddress))) {
Helpers::panic("OpenGL init failed");
else {
if (!gladLoadGLLoader(reinterpret_cast<GLADloadproc>(getGLProcAddress))) {
Helpers::panic("OpenGL init failed");
}
}
#endif
emulator->initGraphicsContext(nullptr);
}
@ -73,6 +78,7 @@ static bool setHWRender(retro_hw_context_type type) {
hwRender.version_minor = 1;
if (envCallback(RETRO_ENVIRONMENT_SET_HW_RENDER, &hwRender)) {
usingGLES = true;
return true;
}
break;
@ -170,8 +176,12 @@ static void configInit() {
{"panda3ds_use_ubershader", EmulatorConfig::ubershaderDefault ? "Use ubershaders (No stutter, maybe slower); enabled|disabled"
: "Use ubershaders (No stutter, maybe slower); disabled|enabled"},
{"panda3ds_use_vsync", "Enable VSync; enabled|disabled"},
{"panda3ds_hash_textures", EmulatorConfig::hashTexturesDefault ? "Hash textures (Better graphics, maybe slower); enabled|disabled"
: "Hash textures (Better graphics, maybe slower); disabled|enabled"},
{"panda3ds_system_language", "System language; En|Fr|Es|De|It|Pt|Nl|Ru|Ja|Zh|Ko|Tw"},
{"panda3ds_dsp_emulation", "DSP emulation; HLE|LLE|Null"},
{"panda3ds_use_audio", "Enable audio; disabled|enabled"},
{"panda3ds_use_audio", EmulatorConfig::audioEnabledDefault ? "Enable audio; enabled|disabled" : "Enable audio; disabled|enabled"},
{"panda3ds_audio_volume", "Audio volume; 100|0|10|20|40|60|80|90|100|120|140|150|180|200"},
{"panda3ds_mute_audio", "Mute audio; disabled|enabled"},
{"panda3ds_enable_aac", "Enable AAC audio; enabled|disabled"},
@ -196,6 +206,8 @@ static void configUpdate() {
config.shaderJitEnabled = fetchVariableBool("panda3ds_use_shader_jit", EmulatorConfig::shaderJitDefault);
config.chargerPlugged = fetchVariableBool("panda3ds_use_charger", true);
config.batteryPercentage = fetchVariableRange("panda3ds_battery_level", 5, 100);
config.systemLanguage = EmulatorConfig::languageCodeFromString(fetchVariable("panda3ds_system_language", "en"));
config.dspType = Audio::DSPCore::typeFromString(fetchVariable("panda3ds_dsp_emulation", "null"));
config.audioEnabled = fetchVariableBool("panda3ds_use_audio", false);
config.aacEnabled = fetchVariableBool("panda3ds_enable_aac", true);
@ -207,6 +219,7 @@ static void configUpdate() {
config.accurateShaderMul = fetchVariableBool("panda3ds_accurate_shader_mul", false);
config.useUbershaders = fetchVariableBool("panda3ds_use_ubershader", EmulatorConfig::ubershaderDefault);
config.accelerateShaders = fetchVariableBool("panda3ds_accelerate_shaders", EmulatorConfig::accelerateShadersDefault);
config.hashTextures = fetchVariableBool("panda3ds_hash_textures", EmulatorConfig::hashTexturesDefault);
config.forceShadergenForLights = fetchVariableBool("panda3ds_ubershader_lighting_override", true);
config.lightShadergenThreshold = fetchVariableRange("panda3ds_ubershader_lighting_override_threshold", 1, 8);
@ -380,6 +393,8 @@ void retro_run() {
emulator->runFrame();
videoCallback(RETRO_HW_FRAME_BUFFER_VALID, emulator->width, emulator->height, 0);
// Call audio batch callback
emulator->getAudioDevice().renderBatch(audioBatchCallback);
}
void retro_set_controller_port_device(uint port, uint device) {}

View file

@ -4,4 +4,6 @@
#define MA_NO_ENCODING
#define MINIAUDIO_IMPLEMENTATION
#include "miniaudio.h"
#ifndef PANDA3DS_IOS
#include "miniaudio.h"
#endif

View file

@ -0,0 +1,8 @@
// We do not need the ability to be able to encode or decode audio files for the time being
// So we disable said functionality to make the executable smaller.
#define MA_NO_DECODING
#define MA_NO_ENCODING
#define MINIAUDIO_IMPLEMENTATION
// On iOS we have to compile miniaudio as Obj-C
#include "miniaudio.h"

View file

@ -71,6 +71,7 @@ ConfigWindow::ConfigWindow(ConfigCallback configCallback, MainWindowCallback win
themeSelect->addItem(tr("Dark"));
themeSelect->addItem(tr("Greetings Cat"));
themeSelect->addItem(tr("Cream"));
themeSelect->addItem(tr("OLED"));
themeSelect->setCurrentIndex(static_cast<int>(config.frontendSettings.theme));
connect(themeSelect, &QComboBox::currentIndexChanged, this, [&](int index) {
config.frontendSettings.theme = static_cast<Theme>(index);
@ -86,6 +87,7 @@ ConfigWindow::ConfigWindow(ConfigCallback configCallback, MainWindowCallback win
iconSelect->addItem(tr("Sleepy panda"));
iconSelect->addItem(tr("Cow panda"));
iconSelect->addItem(tr("The penguin from SkyEmu"));
iconSelect->addItem(tr("Unpog"));
iconSelect->setCurrentIndex(static_cast<int>(config.frontendSettings.icon));
connect(iconSelect, &QComboBox::currentIndexChanged, this, [&](int index) {
@ -145,6 +147,27 @@ ConfigWindow::ConfigWindow(ConfigCallback configCallback, MainWindowCallback win
romLayout->addWidget(browseRomPath);
genLayout->addRow(tr("Default ROMs path"), romLayout);
QComboBox* systemLanguage = new QComboBox();
systemLanguage->addItem(tr("Japanese"));
systemLanguage->addItem(tr("English"));
systemLanguage->addItem(tr("French"));
systemLanguage->addItem(tr("German"));
systemLanguage->addItem(tr("Italian"));
systemLanguage->addItem(tr("Spanish"));
systemLanguage->addItem(tr("Chinese"));
systemLanguage->addItem(tr("Korean"));
systemLanguage->addItem(tr("Dutch"));
systemLanguage->addItem(tr("Portuguese"));
systemLanguage->addItem(tr("Russian"));
systemLanguage->addItem(tr("Taiwanese"));
systemLanguage->setCurrentIndex(static_cast<int>(config.systemLanguage));
connect(systemLanguage, &QComboBox::currentIndexChanged, this, [&](int index) {
config.systemLanguage = static_cast<LanguageCodes>(index);
updateConfig();
});
genLayout->addRow(tr("System language"), systemLanguage);
QCheckBox* discordRpcEnabled = new QCheckBox(tr("Enable Discord RPC"));
connectCheckbox(discordRpcEnabled, config.discordRpcEnabled);
genLayout->addRow(discordRpcEnabled);
@ -163,14 +186,24 @@ ConfigWindow::ConfigWindow(ConfigCallback configCallback, MainWindowCallback win
gpuLayout->setHorizontalSpacing(20);
gpuLayout->setVerticalSpacing(10);
QComboBox* rendererType = new QComboBox;
QComboBox* rendererType = new QComboBox();
rendererType->addItem(tr("Null"));
rendererType->addItem(tr("OpenGL"));
rendererType->addItem(tr("Vulkan"));
rendererType->setCurrentIndex(static_cast<int>(config.rendererType));
connect(rendererType, &QComboBox::currentIndexChanged, this, [&](int index) {
config.rendererType = static_cast<RendererType>(index);
updateConfig();
auto type = static_cast<RendererType>(index);
if (type == RendererType::Vulkan) {
QMessageBox messageBox(
QMessageBox::Icon::Critical, tr("Vulkan renderer unavailable"),
tr("Qt UI doesn't currently support Vulkan, try again at a later time")
);
messageBox.exec();
} else {
config.rendererType = type;
updateConfig();
}
});
gpuLayout->addRow(tr("GPU renderer"), rendererType);
@ -198,6 +231,11 @@ ConfigWindow::ConfigWindow(ConfigCallback configCallback, MainWindowCallback win
connectCheckbox(accelerateShaders, config.accelerateShaders);
gpuLayout->addRow(accelerateShaders);
QCheckBox* hashTextures = new QCheckBox(tr("Hash textures"));
hashTextures->setToolTip(tr("Enable this to reduce texture mismatches at the cost of slightly lower performance"));
connectCheckbox(hashTextures, config.hashTextures);
gpuLayout->addRow(hashTextures);
QCheckBox* forceShadergenForLights = new QCheckBox(tr("Force shadergen when rendering lights"));
connectCheckbox(forceShadergenForLights, config.forceShadergenForLights);
gpuLayout->addRow(forceShadergenForLights);
@ -217,7 +255,7 @@ ConfigWindow::ConfigWindow(ConfigCallback configCallback, MainWindowCallback win
audioLayout->setHorizontalSpacing(20);
audioLayout->setVerticalSpacing(10);
QComboBox* dspType = new QComboBox;
QComboBox* dspType = new QComboBox();
dspType->addItem(tr("Null"));
dspType->addItem(tr("LLE"));
dspType->addItem(tr("HLE"));
@ -244,7 +282,7 @@ ConfigWindow::ConfigWindow(ConfigCallback configCallback, MainWindowCallback win
connectCheckbox(muteAudio, config.audioDeviceConfig.muteAudio);
audioLayout->addRow(muteAudio);
QComboBox* volumeCurveType = new QComboBox;
QComboBox* volumeCurveType = new QComboBox();
volumeCurveType->addItem(tr("Cubic"));
volumeCurveType->addItem(tr("Linear"));
volumeCurveType->setCurrentIndex(static_cast<int>(config.audioDeviceConfig.volumeCurve));
@ -406,6 +444,34 @@ void ConfigWindow::setTheme(Theme theme) {
break;
}
case Theme::Oled: {
QApplication::setStyle(QStyleFactory::create("Fusion"));
QPalette p;
p.setColor(QPalette::Window, Qt::black);
p.setColor(QPalette::WindowText, Qt::white);
p.setColor(QPalette::Base, Qt::black);
p.setColor(QPalette::AlternateBase, Qt::black);
p.setColor(QPalette::ToolTipBase, Qt::black);
p.setColor(QPalette::ToolTipText, Qt::white);
p.setColor(QPalette::Text, Qt::white);
p.setColor(QPalette::Button, QColor(5, 5, 5));
p.setColor(QPalette::ButtonText, Qt::white);
p.setColor(QPalette::BrightText, Qt::red);
p.setColor(QPalette::Link, QColor(42, 130, 218));
p.setColor(QPalette::Highlight, QColor(42, 130, 218));
p.setColor(QPalette::HighlightedText, Qt::black);
qApp->setPalette(p);
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; }");
break;
}
case Theme::System: {
qApp->setPalette(this->style()->standardPalette());
qApp->setStyle(QStyleFactory::create("WindowsVista"));
@ -423,6 +489,7 @@ void ConfigWindow::setIcon(WindowIcon icon) {
case WindowIcon::Rnap: updateIcon(":/docs/img/rnap_icon.png"); break;
case WindowIcon::Rcow: updateIcon(":/docs/img/rcow_icon.png"); break;
case WindowIcon::SkyEmu: updateIcon(":/docs/img/skyemu_icon.png"); break;
case WindowIcon::Runpog: updateIcon(":/docs/img/runpog_icon.png"); break;
case WindowIcon::Rpog:
default: updateIcon(":/docs/img/rpog_icon.png"); break;

View file

@ -7,6 +7,5 @@ int main(int argc, char *argv[]) {
QApplication app(argc, argv);
MainWindow window(&app);
window.show();
return app.exec();
}

View file

@ -22,14 +22,13 @@ MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent)
// Enable drop events for loading ROMs
setAcceptDrops(true);
resize(800, 240 * 4);
show();
// We pass a callback to the screen widget that will be triggered every time we resize the screen
screen = new ScreenWidget([this](u32 width, u32 height) { handleScreenResize(width, height); }, this);
setCentralWidget(screen);
screen->show();
appRunning = true;
// Set our menu bar up
menuBar = new QMenuBar(nullptr);
@ -140,6 +139,10 @@ MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent)
glContext->MakeCurrent();
glContext->SetSwapInterval(emu->getConfig().vsyncEnabled ? 1 : 0);
if (glContext->IsGLES()) {
emu->getRenderer()->setupGLES();
}
emu->initGraphicsContext(glContext);
} else if (usingVk) {
Helpers::panic("Vulkan on Qt is currently WIP, try the SDL frontend instead!");
@ -695,4 +698,4 @@ void MainWindow::setupControllerSensors(SDL_GameController* controller) {
if (haveAccelerometer) {
SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_ACCEL, SDL_TRUE);
}
}
}

View file

@ -29,6 +29,7 @@ ScreenWidget::ScreenWidget(ResizeCallback resizeCallback, QWidget* parent) : QWi
setAttribute(Qt::WA_KeyCompression, false);
setFocusPolicy(Qt::StrongFocus);
setMouseTracking(true);
show();
if (!createGLContext()) {
Helpers::panic("Failed to create GL context for display");
@ -60,11 +61,12 @@ void ScreenWidget::resizeSurface(u32 width, u32 height) {
}
bool ScreenWidget::createGLContext() {
// List of GL context versions we will try. Anything 4.1+ is good
static constexpr std::array<GL::Context::Version, 6> versionsToTry = {
// 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 = {
GL::Context::Version{GL::Context::Profile::Core, 4, 6}, GL::Context::Version{GL::Context::Profile::Core, 4, 5},
GL::Context::Version{GL::Context::Profile::Core, 4, 4}, GL::Context::Version{GL::Context::Profile::Core, 4, 3},
GL::Context::Version{GL::Context::Profile::Core, 4, 2}, GL::Context::Version{GL::Context::Profile::Core, 4, 1},
GL::Context::Version{GL::Context::Profile::ES, 3, 2}, GL::Context::Version{GL::Context::Profile::ES, 3, 1},
};
std::optional<WindowInfo> windowInfo = getWindowInfo();
@ -72,6 +74,10 @@ bool ScreenWidget::createGLContext() {
this->windowInfo = *windowInfo;
glContext = GL::Context::Create(*getWindowInfo(), versionsToTry);
if (glContext == nullptr) {
return false;
}
glContext->DoneCurrent();
}
@ -79,7 +85,7 @@ bool ScreenWidget::createGLContext() {
}
qreal ScreenWidget::devicePixelRatioFromScreen() const {
const QScreen* screenForRatio = window()->windowHandle()->screen();
const QScreen* screenForRatio = windowHandle()->screen();
if (!screenForRatio) {
screenForRatio = QGuiApplication::primaryScreen();
}

View file

@ -46,12 +46,13 @@ struct LanguageInfo {
// Please keep this list mostly in alphabetical order.
// Also, for Unicode characters in language names, use Unicode keycodes instead of writing out the name,
// as some compilers/toolchains may not enjoy Unicode in source files.
static std::array<LanguageInfo, 5> languages = {
static std::array<LanguageInfo, 6> languages = {
LanguageInfo(QStringLiteral(u"English"), "en"), // English
LanguageInfo(QStringLiteral(u"\u0395\u03BB\u03BB\u03B7\u03BD\u03B9\u03BA\u03AC"), "el"), // Greek
LanguageInfo(QStringLiteral(u"Espa\u00F1ol"), "es"), // Spanish
LanguageInfo(QStringLiteral(u"Nederlands"), "nl"), // Dutch
LanguageInfo(QStringLiteral(u"Portugu\u00EAs (Brasil)"), "pt_br") // Portuguese (Brazilian)
LanguageInfo(QStringLiteral(u"Portugu\u00EAs (Brasil)"), "pt_br"), // Portuguese (Brazilian)
LanguageInfo(QStringLiteral(u"Svenska"), "sv"), // Swedish
};
QComboBox* ConfigWindow::createLanguageSelect() {

View file

@ -71,11 +71,27 @@ FrontendSDL::FrontendSDL() : keyboardMappings(InputMappings::defaultKeyboardMapp
glContext = SDL_GL_CreateContext(window);
if (glContext == nullptr) {
Helpers::panic("OpenGL context creation failed: %s", SDL_GetError());
}
Helpers::warn("OpenGL context creation failed: %s\nTrying again with OpenGL ES.", SDL_GetError());
if (!gladLoadGLLoader(reinterpret_cast<GLADloadproc>(SDL_GL_GetProcAddress))) {
Helpers::panic("OpenGL init failed");
// Some low end devices (eg RPi, emulation handhelds) don't support desktop GL, but only OpenGL ES, so fall back to that if GL context
// creation failed
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1);
glContext = SDL_GL_CreateContext(window);
if (glContext == nullptr) {
Helpers::panic("OpenGL context creation failed: %s", SDL_GetError());
}
if (!gladLoadGLES2Loader(reinterpret_cast<GLADloadproc>(SDL_GL_GetProcAddress))) {
Helpers::panic("OpenGL init failed");
}
emu.getRenderer()->setupGLES();
} else {
if (!gladLoadGLLoader(reinterpret_cast<GLADloadproc>(SDL_GL_GetProcAddress))) {
Helpers::panic("OpenGL init failed");
}
}
SDL_GL_SetSwapInterval(config.vsyncEnabled ? 1 : 0);

2
src/pandios/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
libAlber.dylib
Alber/Headers/ios_driver.h

View file

@ -0,0 +1,7 @@
#pragma once
#include <Foundation/Foundation.h>
#include <QuartzCore/QuartzCore.h>
void iosCreateEmulator();
void iosLoadROM(NSString* pathNS);
void iosRunFrame(CAMetalLayer* layer);

View file

@ -0,0 +1,5 @@
module AlberDriver {
umbrella "Headers" // for multiple files
link "libAlber"
export *
}

View file

@ -0,0 +1,587 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 70;
objects = {
/* Begin PBXBuildFile section */
4F798C782D8747B000F5D23F /* libAlber.dylib in Copy Files */ = {isa = PBXBuildFile; fileRef = 4F798C772D8747B000F5D23F /* libAlber.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
4F798C7A2D8747F400F5D23F /* libAlber.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 4F798C792D8747B800F5D23F /* libAlber.dylib */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
4F6E8FD32D77C0140025DD0D /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 4F6E8FBA2D77C0120025DD0D /* Project object */;
proxyType = 1;
remoteGlobalIDString = 4F6E8FC12D77C0120025DD0D;
remoteInfo = Pandios;
};
4F6E8FDD2D77C0140025DD0D /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 4F6E8FBA2D77C0120025DD0D /* Project object */;
proxyType = 1;
remoteGlobalIDString = 4F6E8FC12D77C0120025DD0D;
remoteInfo = Pandios;
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
4F9EEBE82D78898B00E0B72D /* Copy Files */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
4F798C782D8747B000F5D23F /* libAlber.dylib in Copy Files */,
);
name = "Copy Files";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
4F6E8FC22D77C0120025DD0D /* Pandios.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Pandios.app; sourceTree = BUILT_PRODUCTS_DIR; };
4F6E8FD22D77C0140025DD0D /* PandiosTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PandiosTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
4F6E8FDC2D77C0140025DD0D /* PandiosUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PandiosUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
4F798C772D8747B000F5D23F /* libAlber.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libAlber.dylib; path = ../../build/libAlber.dylib; sourceTree = "<group>"; };
4F798C792D8747B800F5D23F /* libAlber.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; path = libAlber.dylib; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFileSystemSynchronizedRootGroup section */
4F6E8FC42D77C0120025DD0D /* Pandios */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = Pandios; sourceTree = "<group>"; };
/* End PBXFileSystemSynchronizedRootGroup section */
/* Begin PBXFrameworksBuildPhase section */
4F6E8FCF2D77C0140025DD0D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
4F6E8FD92D77C0140025DD0D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
4F9EEBF82D78963D00E0B72D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
4F798C7A2D8747F400F5D23F /* libAlber.dylib in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
4F6E8FB92D77C0120025DD0D = {
isa = PBXGroup;
children = (
4F798C772D8747B000F5D23F /* libAlber.dylib */,
4F6E8FC42D77C0120025DD0D /* Pandios */,
4F9EEBF62D7895D700E0B72D /* Frameworks */,
4F6E8FC32D77C0120025DD0D /* Products */,
);
sourceTree = "<group>";
};
4F6E8FC32D77C0120025DD0D /* Products */ = {
isa = PBXGroup;
children = (
4F6E8FC22D77C0120025DD0D /* Pandios.app */,
4F6E8FD22D77C0140025DD0D /* PandiosTests.xctest */,
4F6E8FDC2D77C0140025DD0D /* PandiosUITests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
4F9EEBF62D7895D700E0B72D /* Frameworks */ = {
isa = PBXGroup;
children = (
4F798C792D8747B800F5D23F /* libAlber.dylib */,
);
name = Frameworks;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
4F6E8FC12D77C0120025DD0D /* Pandios */ = {
isa = PBXNativeTarget;
buildConfigurationList = 4F6E8FE62D77C0140025DD0D /* Build configuration list for PBXNativeTarget "Pandios" */;
buildPhases = (
4F6E8FBE2D77C0120025DD0D /* Sources */,
4F6E8FC02D77C0120025DD0D /* Resources */,
4F9EEBE82D78898B00E0B72D /* Copy Files */,
4F9EEBF82D78963D00E0B72D /* Frameworks */,
);
buildRules = (
);
dependencies = (
);
fileSystemSynchronizedGroups = (
4F6E8FC42D77C0120025DD0D /* Pandios */,
);
name = Pandios;
packageProductDependencies = (
);
productName = Pandios;
productReference = 4F6E8FC22D77C0120025DD0D /* Pandios.app */;
productType = "com.apple.product-type.application";
};
4F6E8FD12D77C0140025DD0D /* PandiosTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 4F6E8FE92D77C0140025DD0D /* Build configuration list for PBXNativeTarget "PandiosTests" */;
buildPhases = (
4F6E8FCE2D77C0140025DD0D /* Sources */,
4F6E8FCF2D77C0140025DD0D /* Frameworks */,
4F6E8FD02D77C0140025DD0D /* Resources */,
);
buildRules = (
);
dependencies = (
4F6E8FD42D77C0140025DD0D /* PBXTargetDependency */,
);
name = PandiosTests;
packageProductDependencies = (
);
productName = PandiosTests;
productReference = 4F6E8FD22D77C0140025DD0D /* PandiosTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
4F6E8FDB2D77C0140025DD0D /* PandiosUITests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 4F6E8FEC2D77C0140025DD0D /* Build configuration list for PBXNativeTarget "PandiosUITests" */;
buildPhases = (
4F6E8FD82D77C0140025DD0D /* Sources */,
4F6E8FD92D77C0140025DD0D /* Frameworks */,
4F6E8FDA2D77C0140025DD0D /* Resources */,
);
buildRules = (
);
dependencies = (
4F6E8FDE2D77C0140025DD0D /* PBXTargetDependency */,
);
name = PandiosUITests;
packageProductDependencies = (
);
productName = PandiosUITests;
productReference = 4F6E8FDC2D77C0140025DD0D /* PandiosUITests.xctest */;
productType = "com.apple.product-type.bundle.ui-testing";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
4F6E8FBA2D77C0120025DD0D /* Project object */ = {
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = 1;
LastSwiftUpdateCheck = 1620;
LastUpgradeCheck = 1620;
TargetAttributes = {
4F6E8FC12D77C0120025DD0D = {
CreatedOnToolsVersion = 16.2;
};
4F6E8FD12D77C0140025DD0D = {
CreatedOnToolsVersion = 16.2;
TestTargetID = 4F6E8FC12D77C0120025DD0D;
};
4F6E8FDB2D77C0140025DD0D = {
CreatedOnToolsVersion = 16.2;
TestTargetID = 4F6E8FC12D77C0120025DD0D;
};
};
};
buildConfigurationList = 4F6E8FBD2D77C0120025DD0D /* Build configuration list for PBXProject "Pandios" */;
compatibilityVersion = "Xcode 16.0.Superseded.1";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 4F6E8FB92D77C0120025DD0D;
minimizedProjectReferenceProxies = 1;
productRefGroup = 4F6E8FC32D77C0120025DD0D /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
4F6E8FC12D77C0120025DD0D /* Pandios */,
4F6E8FD12D77C0140025DD0D /* PandiosTests */,
4F6E8FDB2D77C0140025DD0D /* PandiosUITests */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
4F6E8FC02D77C0120025DD0D /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
4F6E8FD02D77C0140025DD0D /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
4F6E8FDA2D77C0140025DD0D /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
4F6E8FBE2D77C0120025DD0D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
4F6E8FCE2D77C0140025DD0D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
4F6E8FD82D77C0140025DD0D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
4F6E8FD42D77C0140025DD0D /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 4F6E8FC12D77C0120025DD0D /* Pandios */;
targetProxy = 4F6E8FD32D77C0140025DD0D /* PBXContainerItemProxy */;
};
4F6E8FDE2D77C0140025DD0D /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 4F6E8FC12D77C0120025DD0D /* Pandios */;
targetProxy = 4F6E8FDD2D77C0140025DD0D /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin XCBuildConfiguration section */
4F6E8FE42D77C0140025DD0D /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 18.2;
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
};
name = Debug;
};
4F6E8FE52D77C0140025DD0D /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 18.2;
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
VALIDATE_PRODUCT = YES;
};
name = Release;
};
4F6E8FE72D77C0140025DD0D /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CLANG_ENABLE_OBJC_ARC = YES;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_ASSET_PATHS = "\"Pandios/Preview Content\"";
DEVELOPMENT_TEAM = 877A43U8RR;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)",
);
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = Alber.Pandios;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_INCLUDE_PATHS = "$(PROJECT_DIR)/Alber";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
4F6E8FE82D77C0140025DD0D /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CLANG_ENABLE_OBJC_ARC = YES;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_ASSET_PATHS = "\"Pandios/Preview Content\"";
DEVELOPMENT_TEAM = 877A43U8RR;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)",
);
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = Alber.Pandios;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_INCLUDE_PATHS = "$(PROJECT_DIR)/Alber";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
4F6E8FEA2D77C0140025DD0D /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 18.2;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = Alber.PandiosTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = NO;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Pandios.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Pandios";
};
name = Debug;
};
4F6E8FEB2D77C0140025DD0D /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 18.2;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = Alber.PandiosTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = NO;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Pandios.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Pandios";
};
name = Release;
};
4F6E8FED2D77C0140025DD0D /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = Alber.PandiosUITests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = NO;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
TEST_TARGET_NAME = Pandios;
};
name = Debug;
};
4F6E8FEE2D77C0140025DD0D /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = Alber.PandiosUITests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = NO;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
TEST_TARGET_NAME = Pandios;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
4F6E8FBD2D77C0120025DD0D /* Build configuration list for PBXProject "Pandios" */ = {
isa = XCConfigurationList;
buildConfigurations = (
4F6E8FE42D77C0140025DD0D /* Debug */,
4F6E8FE52D77C0140025DD0D /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
4F6E8FE62D77C0140025DD0D /* Build configuration list for PBXNativeTarget "Pandios" */ = {
isa = XCConfigurationList;
buildConfigurations = (
4F6E8FE72D77C0140025DD0D /* Debug */,
4F6E8FE82D77C0140025DD0D /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
4F6E8FE92D77C0140025DD0D /* Build configuration list for PBXNativeTarget "PandiosTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
4F6E8FEA2D77C0140025DD0D /* Debug */,
4F6E8FEB2D77C0140025DD0D /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
4F6E8FEC2D77C0140025DD0D /* Build configuration list for PBXNativeTarget "PandiosUITests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
4F6E8FED2D77C0140025DD0D /* Debug */,
4F6E8FEE2D77C0140025DD0D /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 4F6E8FBA2D77C0120025DD0D /* Project object */;
}

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>

View file

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>SchemeUserState</key>
<dict>
<key>Pandios.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>0</integer>
</dict>
</dict>
</dict>
</plist>

View file

@ -0,0 +1,11 @@
{
"colors" : [
{
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View file

@ -0,0 +1,35 @@
{
"images" : [
{
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "tinted"
}
],
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View file

@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View file

@ -0,0 +1,95 @@
import AlberDriver
import SwiftUI
import MetalKit
import Darwin
var emulatorLock = NSLock()
class DocumentViewController: UIViewController, DocumentDelegate {
var documentPicker: DocumentPicker!
override func viewDidLoad() {
super.viewDidLoad()
/// set up the document picker
documentPicker = DocumentPicker(presentationController: self, delegate: self)
/// When the view loads (ie user opens the app) show the file picker
show()
}
/// callback from the document picker
func didPickDocument(document: Document?) {
if let pickedDoc = document {
let fileURL = pickedDoc.fileURL
print("Loading ROM", fileURL)
emulatorLock.lock()
iosLoadROM(fileURL.path(percentEncoded: false))
emulatorLock.unlock()
}
}
func show() {
documentPicker.displayPicker()
}
}
struct DocumentView: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> DocumentViewController {
return DocumentViewController()
}
func updateUIViewController(_ uiViewController: DocumentViewController, context: Context) {
// No update needed
}
}
struct ContentView: UIViewRepresentable {
@State var showFileImporter = true
/*
func makeCoordinator() -> Renderer {
Renderer(self)
}
*/
func makeUIView(context: UIViewRepresentableContext<ContentView>) -> MTKView {
let mtkView = MTKView()
mtkView.preferredFramesPerSecond = 60
mtkView.enableSetNeedsDisplay = true
mtkView.isPaused = true
if let metalDevice = MTLCreateSystemDefaultDevice() {
mtkView.device = metalDevice
}
mtkView.framebufferOnly = false
mtkView.drawableSize = mtkView.frame.size
let dispatchQueue = DispatchQueue(label: "QueueIdentification", qos: .background)
let metalLayer = mtkView.layer as! CAMetalLayer;
dispatchQueue.async{
iosCreateEmulator()
while (true) {
emulatorLock.lock()
iosRunFrame(metalLayer);
emulatorLock.unlock()
}
}
return mtkView
}
func updateUIView(_ uiView: MTKView, context: UIViewRepresentableContext<ContentView>) {
print("Updating MTKView");
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
DocumentView();
ContentView();
}
}

View file

@ -0,0 +1,75 @@
// From https://gist.github.com/aheze/dbc7f9b452e4f86f2d8fe278b3c5001f
// DocumentPicker.swift
import UIKit
import MobileCoreServices
import UniformTypeIdentifiers
protocol DocumentDelegate: AnyObject {
func didPickDocument(document: Document?)
}
class Document: UIDocument {
var data: Data?
override func contents(forType typeName: String) throws -> Any {
guard let data = data else { return Data() }
return try NSKeyedArchiver.archivedData(withRootObject:data,
requiringSecureCoding: true)
}
override func load(fromContents contents: Any, ofType typeName:
String?) throws {
guard let data = contents as? Data else { return }
self.data = data
}
}
open class DocumentPicker: NSObject {
private var pickerController: UIDocumentPickerViewController?
private weak var presentationController: UIViewController?
private weak var delegate: DocumentDelegate?
private var pickedDocument: Document?
init(presentationController: UIViewController, delegate: DocumentDelegate) {
super.init()
self.presentationController = presentationController
self.delegate = delegate
}
public func displayPicker() {
self.pickerController = UIDocumentPickerViewController(forOpeningContentTypes: [UTType.data])
self.pickerController!.delegate = self
self.presentationController?.present(self.pickerController!, animated: true)
}
}
extension DocumentPicker: UIDocumentPickerDelegate {
/// delegate method, when the user selects a file
public func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
guard let url = urls.first else {
return
}
documentFromURL(pickedURL: url)
delegate?.didPickDocument(document: pickedDocument)
}
/// delegate method, when the user cancels
public func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) {
delegate?.didPickDocument(document: nil)
}
private func documentFromURL(pickedURL: URL) {
/// start accessing the resource
let shouldStopAccessing = pickedURL.startAccessingSecurityScopedResource()
defer {
if shouldStopAccessing {
pickedURL.stopAccessingSecurityScopedResource()
}
}
NSFileCoordinator().coordinate(readingItemAt: pickedURL, error: NSErrorPointer.none) { (readURL) in
let document = Document(fileURL: readURL)
pickedDocument = document
}
}
}

View file

@ -0,0 +1,11 @@
import SwiftUI
@main
struct PandiosApp: App {
var body: some Scene {
WindowGroup {
ContentView()
DocumentView()
}
}
}

View file

@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

37
src/pandios/build.sh Executable file
View file

@ -0,0 +1,37 @@
#!/bin/bash
# Settings for the SwiftUI frontend
ARCH=arm64
CONFIGURATION=Release
SDK=iphonesimulator
# Settings for the emulator core
EMULATOR_BUILD_TYPE=Release
# Simulator settings
RUN_SIMULATOR=false
# Fail on error
set -e
# Go to the parent directory and build the emulator core
cd ../..
cmake -B build -DENABLE_VULKAN=OFF -DBUILD_HYDRA_CORE=ON -DENABLE_USER_BUILD=ON -DCMAKE_TOOLCHAIN_FILE=third_party/ios_toolchain/ios.toolchain.cmake -DPLATFORM=SIMULATORARM64 -DDEPLOYMENT_TARGET="13.0"
cmake --build build --config ${EMULATOR_BUILD_TYPE}
# Copy the bridging header and emulator dylib to the iOS folder
cp ./include/ios_driver.h ./src/pandios/Alber/Headers/ios_driver.h
cp ./build/libAlber.dylib ./src/pandios/
# Come back to the iOS directory, build the SwiftUI xcode project and link them together
cd src/pandios
xcodebuild build -configuration ${CONFIGURATION} -sdk ${SDK} -arch ${ARCH}
# To run the app in the simulator:
# Boot the iPhone, install the app on the iphone, then open the sim & launch the app
if [ "$RUN_SIMULATOR" = true ] ; then
xcrun simctl boot "iPhone 16 Pro"
xcrun simctl install "iPhone 16 Pro" "build/Release-iphonesimulator/Pandios.app"
open /Applications/Xcode.app/Contents/Developer/Applications/Simulator.app
xcrun simctl launch --console booted "Alber.Pandios"
fi

View file

@ -38,6 +38,7 @@ public class AppDataDocumentProvider extends DocumentsProvider {
Document.COLUMN_DISPLAY_NAME,
Document.COLUMN_MIME_TYPE,
Document.COLUMN_LAST_MODIFIED,
Document.COLUMN_FLAGS,
Document.COLUMN_SIZE
};
@ -101,7 +102,7 @@ public class AppDataDocumentProvider extends DocumentsProvider {
}
cursor.newRow()
.add(Document.COLUMN_DOCUMENT_ID, obtainDocumentId(file))
.add(Document.COLUMN_MIME_TYPE, file.isDirectory() ? Document.MIME_TYPE_DIR : "application/octect-stream")
.add(Document.COLUMN_MIME_TYPE, file.isDirectory() ? Document.MIME_TYPE_DIR : "application/octet-stream")
.add(Document.COLUMN_FLAGS, flags)
.add(Document.COLUMN_LAST_MODIFIED, file.lastModified())
.add(Document.COLUMN_DISPLAY_NAME, file.getName())
@ -157,8 +158,13 @@ public class AppDataDocumentProvider extends DocumentsProvider {
}
}
@Override
public void removeDocument(String documentId, String parentDocumentId) throws FileNotFoundException {
deleteDocument(documentId);
}
@Override
public ParcelFileDescriptor openDocument(String documentId, String mode, @Nullable CancellationSignal signal) throws FileNotFoundException {
return ParcelFileDescriptor.open(obtainFile(documentId), ParcelFileDescriptor.parseMode(mode));
}
}
}

View file

@ -3,6 +3,8 @@
#include <algorithm>
#include <unordered_map>
#include "PICA/gpu.hpp"
Renderer::Renderer(GPU& gpu, const std::array<u32, regNum>& internalRegs, const std::array<u32, extRegNum>& externalRegs)
: gpu(gpu), regs(internalRegs), externalRegs(externalRegs) {}
Renderer::~Renderer() {}
@ -39,3 +41,39 @@ const char* Renderer::typeToString(RendererType rendererType) {
default: return "Invalid";
}
}
void Renderer::doSoftwareTextureCopy(u32 inputAddr, u32 outputAddr, u32 copySize, u32 inputWidth, u32 inputGap, u32 outputWidth, u32 outputGap) {
u8* inputPointer = gpu.getPointerPhys<u8>(inputAddr);
u8* outputPointer = gpu.getPointerPhys<u8>(outputAddr);
if (inputPointer == nullptr || outputPointer == nullptr) {
return;
}
u32 inputBytesLeft = inputWidth;
u32 outputBytesLeft = outputWidth;
u32 copyBytesLeft = copySize;
while (copyBytesLeft > 0) {
const u32 bytes = std::min<u32>({inputBytesLeft, outputBytesLeft, copyBytesLeft});
std::memcpy(outputPointer, inputPointer, bytes);
inputPointer += bytes;
outputPointer += bytes;
inputBytesLeft -= bytes;
outputBytesLeft -= bytes;
copyBytesLeft -= bytes;
// Apply input and output gap when an input or output line ends
if (inputBytesLeft == 0) {
inputBytesLeft = inputWidth;
inputPointer += inputGap;
}
if (outputBytesLeft == 0) {
outputBytesLeft = outputWidth;
outputPointer += outputGap;
}
}
}