mirror of
https://github.com/wheremyfoodat/Panda3DS.git
synced 2025-07-18 04:51:43 +12:00
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:
parent
d5506f311f
commit
082b6216b3
302 changed files with 55525 additions and 747 deletions
|
@ -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)];
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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) {};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
62
src/core/renderer_mtl/pica_to_mtl.cpp
Normal file
62
src/core/renderer_mtl/pica_to_mtl.cpp
Normal 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
|
|
@ -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);
|
||||
|
|
334
src/core/renderer_mtl/texture_decoder.cpp
Normal file
334
src/core/renderer_mtl/texture_decoder.cpp
Normal 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);
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
#include "services/gsp_lcd.hpp"
|
||||
|
||||
#include "ipc.hpp"
|
||||
|
||||
namespace LCDCommands {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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
|
||||
}
|
10
src/host_shaders/opengl_es_display.frag
Normal file
10
src/host_shaders/opengl_es_display.frag
Normal 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);
|
||||
}
|
25
src/host_shaders/opengl_es_display.vert
Normal file
25
src/host_shaders/opengl_es_display.vert
Normal 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];
|
||||
}
|
|
@ -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
38
src/ios_driver.mm
Normal 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);
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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) {}
|
||||
|
|
|
@ -4,4 +4,6 @@
|
|||
#define MA_NO_ENCODING
|
||||
#define MINIAUDIO_IMPLEMENTATION
|
||||
|
||||
#include "miniaudio.h"
|
||||
#ifndef PANDA3DS_IOS
|
||||
#include "miniaudio.h"
|
||||
#endif
|
8
src/miniaudio/miniaudio.m
Normal file
8
src/miniaudio/miniaudio.m
Normal 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"
|
|
@ -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;
|
||||
|
|
|
@ -7,6 +7,5 @@ int main(int argc, char *argv[]) {
|
|||
QApplication app(argc, argv);
|
||||
MainWindow window(&app);
|
||||
|
||||
window.show();
|
||||
return app.exec();
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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
2
src/pandios/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
libAlber.dylib
|
||||
Alber/Headers/ios_driver.h
|
7
src/pandios/Alber/Headers/ios_driver.h
Normal file
7
src/pandios/Alber/Headers/ios_driver.h
Normal 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);
|
5
src/pandios/Alber/module.map
Normal file
5
src/pandios/Alber/module.map
Normal file
|
@ -0,0 +1,5 @@
|
|||
module AlberDriver {
|
||||
umbrella "Headers" // for multiple files
|
||||
link "libAlber"
|
||||
export *
|
||||
}
|
587
src/pandios/Pandios.xcodeproj/project.pbxproj
Normal file
587
src/pandios/Pandios.xcodeproj/project.pbxproj
Normal 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 */;
|
||||
}
|
7
src/pandios/Pandios.xcodeproj/project.xcworkspace/contents.xcworkspacedata
generated
Normal file
7
src/pandios/Pandios.xcodeproj/project.xcworkspace/contents.xcworkspacedata
generated
Normal file
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:">
|
||||
</FileRef>
|
||||
</Workspace>
|
Binary file not shown.
|
@ -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>
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"colors" : [
|
||||
{
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
6
src/pandios/Pandios/Assets.xcassets/Contents.json
Normal file
6
src/pandios/Pandios/Assets.xcassets/Contents.json
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
95
src/pandios/Pandios/ContentView.swift
Normal file
95
src/pandios/Pandios/ContentView.swift
Normal 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();
|
||||
}
|
||||
}
|
75
src/pandios/Pandios/DocumentPicker.swift
Normal file
75
src/pandios/Pandios/DocumentPicker.swift
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
11
src/pandios/Pandios/PandiosApp.swift
Normal file
11
src/pandios/Pandios/PandiosApp.swift
Normal file
|
@ -0,0 +1,11 @@
|
|||
import SwiftUI
|
||||
|
||||
@main
|
||||
struct PandiosApp: App {
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
ContentView()
|
||||
DocumentView()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
37
src/pandios/build.sh
Executable file
37
src/pandios/build.sh
Executable 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
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue