mirror of
https://github.com/wheremyfoodat/Panda3DS.git
synced 2025-06-08 12:01:39 +12:00
Merge branch 'master' into metal2
This commit is contained in:
commit
779e30e3e5
48 changed files with 1691 additions and 242 deletions
|
@ -41,6 +41,9 @@ void EmulatorConfig::load() {
|
|||
discordRpcEnabled = toml::find_or<toml::boolean>(general, "EnableDiscordRPC", false);
|
||||
usePortableBuild = toml::find_or<toml::boolean>(general, "UsePortableBuild", false);
|
||||
defaultRomPath = toml::find_or<std::string>(general, "DefaultRomPath", "");
|
||||
|
||||
printAppVersion = toml::find_or<toml::boolean>(general, "PrintAppVersion", true);
|
||||
appVersionOnWindow = toml::find_or<toml::boolean>(general, "AppVersionOnWindow", false);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -67,6 +70,7 @@ void EmulatorConfig::load() {
|
|||
|
||||
forceShadergenForLights = toml::find_or<toml::boolean>(gpu, "ForceShadergenForLighting", true);
|
||||
lightShadergenThreshold = toml::find_or<toml::integer>(gpu, "ShadergenLightThreshold", 1);
|
||||
enableRenderdoc = toml::find_or<toml::boolean>(gpu, "EnableRenderdoc", false);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -127,6 +131,8 @@ void EmulatorConfig::save() {
|
|||
data["General"]["EnableDiscordRPC"] = discordRpcEnabled;
|
||||
data["General"]["UsePortableBuild"] = usePortableBuild;
|
||||
data["General"]["DefaultRomPath"] = defaultRomPath.string();
|
||||
data["General"]["PrintAppVersion"] = printAppVersion;
|
||||
data["General"]["AppVersionOnWindow"] = appVersionOnWindow;
|
||||
|
||||
data["GPU"]["EnableShaderJIT"] = shaderJitEnabled;
|
||||
data["GPU"]["Renderer"] = std::string(Renderer::typeToString(rendererType));
|
||||
|
@ -135,6 +141,7 @@ void EmulatorConfig::save() {
|
|||
data["GPU"]["UseUbershaders"] = useUbershaders;
|
||||
data["GPU"]["ForceShadergenForLighting"] = forceShadergenForLights;
|
||||
data["GPU"]["ShadergenLightThreshold"] = lightShadergenThreshold;
|
||||
data["GPU"]["EnableRenderdoc"] = enableRenderdoc;
|
||||
|
||||
data["Audio"]["DSPEmulation"] = std::string(Audio::DSPCore::typeToString(dspType));
|
||||
data["Audio"]["EnableAudio"] = audioEnabled;
|
||||
|
|
|
@ -166,7 +166,10 @@ void GPU::drawArrays() {
|
|||
// Configures the type of primitive and the number of vertex shader outputs
|
||||
const u32 primConfig = regs[PICA::InternalRegs::PrimitiveConfig];
|
||||
const PICA::PrimType primType = static_cast<PICA::PrimType>(Helpers::getBits<8, 2>(primConfig));
|
||||
if (vertexCount > Renderer::vertexBufferSize) Helpers::panic("[PICA] vertexCount > vertexBufferSize");
|
||||
if (vertexCount > Renderer::vertexBufferSize) [[unlikely]] {
|
||||
Helpers::warn("[PICA] vertexCount > vertexBufferSize");
|
||||
return;
|
||||
}
|
||||
|
||||
if ((primType == PICA::PrimType::TriangleList && vertexCount % 3) || (primType == PICA::PrimType::TriangleStrip && vertexCount < 3) ||
|
||||
(primType == PICA::PrimType::TriangleFan && vertexCount < 3)) {
|
||||
|
|
|
@ -107,6 +107,7 @@ std::string FragmentGenerator::generate(const FragmentConfig& config) {
|
|||
if (api == API::GLES) {
|
||||
ret += R"(
|
||||
#define USING_GLES 1
|
||||
#define fma(a, b, c) ((a) * (b) + (c))
|
||||
|
||||
precision mediump int;
|
||||
precision mediump float;
|
||||
|
@ -502,7 +503,7 @@ void FragmentGenerator::compileLights(std::string& shader, const PICA::FragmentC
|
|||
"].distanceAttenuationScale + lightSources[" + std::to_string(lightID) + "].distanceAttenuationBias, 0.0, 1.0);\n";
|
||||
|
||||
shader += "distance_attenuation = lutLookup(" + std::to_string(16 + lightID) +
|
||||
", int(clamp(floor(distance_att_delta * 256.0), 0.0, 255.0)));\n";
|
||||
"u, int(clamp(floor(distance_att_delta * 256.0), 0.0, 255.0)));\n";
|
||||
}
|
||||
|
||||
compileLUTLookup(shader, config, i, spotlightLutIndex);
|
||||
|
@ -637,7 +638,7 @@ void FragmentGenerator::compileLUTLookup(std::string& shader, const PICA::Fragme
|
|||
if (absEnabled) {
|
||||
bool twoSidedDiffuse = config.lighting.lights[lightIndex].twoSidedDiffuse;
|
||||
shader += twoSidedDiffuse ? "lut_lookup_delta = abs(lut_lookup_delta);\n" : "lut_lookup_delta = max(lut_lookup_delta, 0.0);\n";
|
||||
shader += "lut_lookup_result = lutLookup(" + std::to_string(lutIndex) + ", int(clamp(floor(lut_lookup_delta * 256.0), 0.0, 255.0)));\n";
|
||||
shader += "lut_lookup_result = lutLookup(" + std::to_string(lutIndex) + "u, int(clamp(floor(lut_lookup_delta * 256.0), 0.0, 255.0)));\n";
|
||||
if (scale != 0) {
|
||||
shader += "lut_lookup_result *= " + std::to_string(scales[scale]) + ";\n";
|
||||
}
|
||||
|
@ -645,7 +646,7 @@ void FragmentGenerator::compileLUTLookup(std::string& shader, const PICA::Fragme
|
|||
// Range is [-1, 1] so we need to map it to [0, 1]
|
||||
shader += "lut_lookup_index = int(clamp(floor(lut_lookup_delta * 128.0), -128.f, 127.f));\n";
|
||||
shader += "if (lut_lookup_index < 0) lut_lookup_index += 256;\n";
|
||||
shader += "lut_lookup_result = lutLookup(" + std::to_string(lutIndex) + ", lut_lookup_index);\n";
|
||||
shader += "lut_lookup_result = lutLookup(" + std::to_string(lutIndex) + "u, lut_lookup_index);\n";
|
||||
if (scale != 0) {
|
||||
shader += "lut_lookup_result *= " + std::to_string(scales[scale]) + ";\n";
|
||||
}
|
||||
|
|
139
src/core/audio/aac_decoder.cpp
Normal file
139
src/core/audio/aac_decoder.cpp
Normal file
|
@ -0,0 +1,139 @@
|
|||
#include "audio/aac_decoder.hpp"
|
||||
|
||||
#include <aacdecoder_lib.h>
|
||||
|
||||
#include <vector>
|
||||
using namespace Audio;
|
||||
|
||||
void AAC::Decoder::decode(AAC::Message& response, const AAC::Message& request, AAC::Decoder::PaddrCallback paddrCallback) {
|
||||
// Copy the command and mode fields of the request to the response
|
||||
response.command = request.command;
|
||||
response.mode = request.mode;
|
||||
response.decodeResponse.size = request.decodeRequest.size;
|
||||
|
||||
// Write a dummy response at first. We'll be overwriting it later if decoding goes well
|
||||
response.resultCode = AAC::ResultCode::Success;
|
||||
response.decodeResponse.channelCount = 2;
|
||||
response.decodeResponse.sampleCount = 1024;
|
||||
response.decodeResponse.sampleRate = AAC::SampleRate::Rate48000;
|
||||
|
||||
if (!isInitialized()) {
|
||||
initialize();
|
||||
|
||||
// AAC decoder failed to initialize, return dummy data and return without decoding
|
||||
if (!isInitialized()) {
|
||||
Helpers::warn("Failed to initialize AAC decoder");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
u8* input = paddrCallback(request.decodeRequest.address);
|
||||
const u8* inputEnd = paddrCallback(request.decodeRequest.address + request.decodeRequest.size);
|
||||
u8* outputLeft = paddrCallback(request.decodeRequest.destAddrLeft);
|
||||
u8* outputRight = nullptr;
|
||||
|
||||
if (input == nullptr || inputEnd == nullptr || outputLeft == nullptr) {
|
||||
Helpers::warn("Invalid pointers passed to AAC decoder");
|
||||
return;
|
||||
}
|
||||
|
||||
u32 bytesValid = request.decodeRequest.size;
|
||||
u32 bufferSize = request.decodeRequest.size;
|
||||
|
||||
// Each frame is 2048 samples with 2 channels
|
||||
static constexpr usize frameSize = 2048 * 2;
|
||||
std::array<s16, frameSize> frame;
|
||||
std::array<std::vector<s16>, 2> audioStreams;
|
||||
|
||||
bool queriedStreamInfo = false;
|
||||
|
||||
while (bytesValid != 0) {
|
||||
if (aacDecoder_Fill(decoderHandle, &input, &bufferSize, &bytesValid) != AAC_DEC_OK) {
|
||||
Helpers::warn("Failed to fill AAC decoder with samples");
|
||||
return;
|
||||
}
|
||||
|
||||
auto decodeResult = aacDecoder_DecodeFrame(decoderHandle, frame.data(), frameSize, 0);
|
||||
|
||||
if (decodeResult == AAC_DEC_TRANSPORT_SYNC_ERROR) {
|
||||
// https://android.googlesource.com/platform/external/aac/+/2ddc922/libAACdec/include/aacdecoder_lib.h#362
|
||||
// According to the above, if we get a sync error, we're not meant to stop decoding, but rather just continue feeding data
|
||||
} else if (decodeResult == AAC_DEC_OK) {
|
||||
auto getSampleRate = [](u32 rate) {
|
||||
switch (rate) {
|
||||
case 8000: return AAC::SampleRate::Rate8000;
|
||||
case 11025: return AAC::SampleRate::Rate11025;
|
||||
case 12000: return AAC::SampleRate::Rate12000;
|
||||
case 16000: return AAC::SampleRate::Rate16000;
|
||||
case 22050: return AAC::SampleRate::Rate22050;
|
||||
case 24000: return AAC::SampleRate::Rate24000;
|
||||
case 32000: return AAC::SampleRate::Rate32000;
|
||||
case 44100: return AAC::SampleRate::Rate44100;
|
||||
case 48000:
|
||||
default: return AAC::SampleRate::Rate48000;
|
||||
}
|
||||
};
|
||||
|
||||
auto info = aacDecoder_GetStreamInfo(decoderHandle);
|
||||
response.decodeResponse.sampleCount = info->frameSize;
|
||||
response.decodeResponse.channelCount = info->numChannels;
|
||||
response.decodeResponse.sampleRate = getSampleRate(info->sampleRate);
|
||||
|
||||
int channels = info->numChannels;
|
||||
// Reserve space in our output stream vectors so push_back doesn't do allocations
|
||||
for (int i = 0; i < channels; i++) {
|
||||
audioStreams[i].reserve(audioStreams[i].size() + info->frameSize);
|
||||
}
|
||||
|
||||
// Fetch output pointer for right output channel if we've got > 1 channel
|
||||
if (channels > 1 && outputRight == nullptr) {
|
||||
outputRight = paddrCallback(request.decodeRequest.destAddrRight);
|
||||
// If the right output channel doesn't point to a proper padddr, return
|
||||
if (outputRight == nullptr) {
|
||||
Helpers::warn("Right AAC output channel doesn't point to valid physical address");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
for (int sample = 0; sample < info->frameSize; sample++) {
|
||||
for (int stream = 0; stream < channels; stream++) {
|
||||
audioStreams[stream].push_back(frame[(sample * channels) + stream]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Helpers::warn("Failed to decode AAC frame");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < 2; i++) {
|
||||
auto& stream = audioStreams[i];
|
||||
u8* pointer = (i == 0) ? outputLeft : outputRight;
|
||||
|
||||
if (!stream.empty() && pointer != nullptr) {
|
||||
std::memcpy(pointer, stream.data(), stream.size() * sizeof(s16));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AAC::Decoder::initialize() {
|
||||
decoderHandle = aacDecoder_Open(TRANSPORT_TYPE::TT_MP4_ADTS, 1);
|
||||
|
||||
if (decoderHandle == nullptr) [[unlikely]] {
|
||||
return;
|
||||
}
|
||||
|
||||
// Cap output channel count to 2
|
||||
if (aacDecoder_SetParam(decoderHandle, AAC_PCM_MAX_OUTPUT_CHANNELS, 2) != AAC_DEC_OK) [[unlikely]] {
|
||||
aacDecoder_Close(decoderHandle);
|
||||
decoderHandle = nullptr;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
AAC::Decoder::~Decoder() {
|
||||
if (isInitialized()) {
|
||||
aacDecoder_Close(decoderHandle);
|
||||
decoderHandle = nullptr;
|
||||
}
|
||||
}
|
|
@ -6,6 +6,7 @@
|
|||
#include <thread>
|
||||
#include <utility>
|
||||
|
||||
#include "audio/aac_decoder.hpp"
|
||||
#include "services/dsp.hpp"
|
||||
|
||||
namespace Audio {
|
||||
|
@ -23,6 +24,8 @@ namespace Audio {
|
|||
for (int i = 0; i < sources.size(); i++) {
|
||||
sources[i].index = i;
|
||||
}
|
||||
|
||||
aacDecoder.reset(new Audio::AAC::Decoder());
|
||||
}
|
||||
|
||||
void HLE_DSP::resetAudioPipe() {
|
||||
|
@ -584,7 +587,6 @@ namespace Audio {
|
|||
switch (request.command) {
|
||||
case AAC::Command::EncodeDecode:
|
||||
// Dummy response to stop games from hanging
|
||||
// TODO: Fix this when implementing AAC
|
||||
response.resultCode = AAC::ResultCode::Success;
|
||||
response.decodeResponse.channelCount = 2;
|
||||
response.decodeResponse.sampleCount = 1024;
|
||||
|
@ -593,6 +595,10 @@ namespace Audio {
|
|||
|
||||
response.command = request.command;
|
||||
response.mode = request.mode;
|
||||
|
||||
// We've already got an AAC decoder but it's currently disabled until mixing & output is properly implemented
|
||||
// TODO: Uncomment this when the time comes
|
||||
// aacDecoder->decode(response, request, [this](u32 paddr) { return getPointerPhys<u8>(paddr); });
|
||||
break;
|
||||
|
||||
case AAC::Command::Init:
|
||||
|
|
|
@ -90,16 +90,17 @@ void MiniAudioDevice::init(Samples& samples, bool safe) {
|
|||
deviceConfig.dataCallback = [](ma_device* device, void* out, const void* input, ma_uint32 frameCount) {
|
||||
auto self = reinterpret_cast<MiniAudioDevice*>(device->pUserData);
|
||||
s16* output = reinterpret_cast<ma_int16*>(out);
|
||||
const usize maxSamples = std::min(self->samples->Capacity(), usize(frameCount * channelCount));
|
||||
|
||||
// Wait until there's enough samples to pop
|
||||
while (self->samples->size() < frameCount * channelCount) {
|
||||
while (self->samples->size() < maxSamples) {
|
||||
// If audio output is disabled from the emulator thread, make sure that this callback will return and not hang
|
||||
if (!self->running) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
self->samples->pop(output, frameCount * channelCount);
|
||||
self->samples->pop(output, maxSamples);
|
||||
};
|
||||
|
||||
if (ma_device_init(&context, &deviceConfig, &device) != MA_SUCCESS) {
|
||||
|
|
|
@ -49,7 +49,7 @@ void RendererGL::reset() {
|
|||
gl.useProgram(oldProgram); // Switch to old GL program
|
||||
}
|
||||
|
||||
#ifdef __ANDROID__
|
||||
#ifdef USING_GLES
|
||||
fragShaderGen.setTarget(PICA::ShaderGen::API::GLES, PICA::ShaderGen::Language::GLSL);
|
||||
#endif
|
||||
}
|
||||
|
|
|
@ -35,6 +35,7 @@ void HIDService::reset() {
|
|||
circlePadX = circlePadY = 0;
|
||||
touchScreenX = touchScreenY = 0;
|
||||
roll = pitch = yaw = 0;
|
||||
accelX = accelY = accelZ = 0;
|
||||
}
|
||||
|
||||
void HIDService::handleSyncRequest(u32 messagePointer) {
|
||||
|
@ -189,6 +190,20 @@ void HIDService::updateInputs(u64 currentTick) {
|
|||
writeSharedMem<u64>(0x108, currentTick); // Write new tick count
|
||||
}
|
||||
writeSharedMem<u32>(0x118, nextAccelerometerIndex); // Index last updated by the HID module
|
||||
const size_t accelEntryOffset = 0x128 + (nextAccelerometerIndex * 6); // Offset in the array of 8 accelerometer entries
|
||||
|
||||
// Raw data of current accelerometer entry
|
||||
// TODO: How is the "raw" data actually calculated?
|
||||
s16* accelerometerDataRaw = getSharedMemPointer<s16>(0x120);
|
||||
accelerometerDataRaw[0] = accelX;
|
||||
accelerometerDataRaw[1] = accelY;
|
||||
accelerometerDataRaw[2] = accelZ;
|
||||
|
||||
// Accelerometer entry in entry table
|
||||
s16* accelerometerData = getSharedMemPointer<s16>(accelEntryOffset);
|
||||
accelerometerData[0] = accelX;
|
||||
accelerometerData[1] = accelY;
|
||||
accelerometerData[2] = accelZ;
|
||||
nextAccelerometerIndex = (nextAccelerometerIndex + 1) % 8; // Move to next entry
|
||||
|
||||
// Next, update gyro state
|
||||
|
@ -197,9 +212,10 @@ void HIDService::updateInputs(u64 currentTick) {
|
|||
writeSharedMem<u64>(0x158, currentTick); // Write new tick count
|
||||
}
|
||||
const size_t gyroEntryOffset = 0x178 + (nextGyroIndex * 6); // Offset in the array of 8 touchscreen entries
|
||||
writeSharedMem<u16>(gyroEntryOffset, pitch);
|
||||
writeSharedMem<u16>(gyroEntryOffset + 2, yaw);
|
||||
writeSharedMem<u16>(gyroEntryOffset + 4, roll);
|
||||
s16* gyroData = getSharedMemPointer<s16>(gyroEntryOffset);
|
||||
gyroData[0] = pitch;
|
||||
gyroData[1] = yaw;
|
||||
gyroData[2] = roll;
|
||||
|
||||
// Since gyroscope euler angles are relative, we zero them out here and the frontend will update them again when we receive a new rotation
|
||||
roll = pitch = yaw = 0;
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
#include "emulator.hpp"
|
||||
|
||||
#ifndef __ANDROID__
|
||||
#if !defined(__ANDROID__) && !defined(__LIBRETRO__)
|
||||
#include <SDL_filesystem.h>
|
||||
#endif
|
||||
|
||||
#include <fstream>
|
||||
|
||||
#include "renderdoc.hpp"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
|
||||
|
@ -32,6 +34,10 @@ Emulator::Emulator()
|
|||
audioDevice.init(dsp->getSamples());
|
||||
setAudioEnabled(config.audioEnabled);
|
||||
|
||||
if (Renderdoc::isSupported() && config.enableRenderdoc) {
|
||||
loadRenderdoc();
|
||||
}
|
||||
|
||||
#ifdef PANDA3DS_ENABLE_DISCORD_RPC
|
||||
if (config.discordRpcEnabled) {
|
||||
discordRpc.init();
|
||||
|
@ -431,3 +437,9 @@ void Emulator::setAudioEnabled(bool enable) {
|
|||
|
||||
dsp->setAudioEnabled(enable);
|
||||
}
|
||||
|
||||
void Emulator::loadRenderdoc() {
|
||||
std::string capturePath = (std::filesystem::current_path() / "RenderdocCaptures").generic_string();
|
||||
Renderdoc::loadRenderdoc();
|
||||
Renderdoc::setOutputDir(capturePath, "");
|
||||
}
|
|
@ -523,10 +523,10 @@ void main() {
|
|||
uint GPUREG_FOG_COLOR = readPicaReg(0x00E1u);
|
||||
|
||||
// Annoyingly color is not encoded in the same way as light color
|
||||
float r = (GPUREG_FOG_COLOR & 0xFFu) / 255.0;
|
||||
float g = ((GPUREG_FOG_COLOR >> 8) & 0xFFu) / 255.0;
|
||||
float b = ((GPUREG_FOG_COLOR >> 16) & 0xFFu) / 255.0;
|
||||
vec3 fog_color = vec3(r, g, b);
|
||||
float r = float(GPUREG_FOG_COLOR & 0xFFu);
|
||||
float g = float((GPUREG_FOG_COLOR >> 8u) & 0xFFu);
|
||||
float b = float((GPUREG_FOG_COLOR >> 16u) & 0xFFu);
|
||||
vec3 fog_color = (1.0 / 255.0) * vec3(r, g, b);
|
||||
|
||||
fragColour.rgb = mix(fog_color, fragColour.rgb, fog_factor);
|
||||
}
|
||||
|
@ -561,4 +561,4 @@ void main() {
|
|||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
#include <version.hpp>
|
||||
#include <emulator.hpp>
|
||||
#include <hydra/core.hxx>
|
||||
#include <renderer_gl/renderer_gl.hpp>
|
||||
|
@ -113,7 +114,7 @@ hydra::Size HydraCore::getNativeSize() { return {400, 480}; }
|
|||
void HydraCore::setOutputSize(hydra::Size size) {}
|
||||
|
||||
void HydraCore::resetContext() {
|
||||
#ifdef __ANDROID__
|
||||
#ifdef USING_GLES
|
||||
if (!gladLoadGLES2Loader(reinterpret_cast<GLADloadproc>(getProcAddress))) {
|
||||
Helpers::panic("OpenGL ES init failed");
|
||||
}
|
||||
|
@ -150,7 +151,7 @@ HC_API const char* getInfo(hydra::InfoType type) {
|
|||
case hydra::InfoType::SystemName: return "Nintendo 3DS";
|
||||
case hydra::InfoType::Description: return "HLE 3DS emulator. There's a little Alber in your computer and he runs Nintendo 3DS games.";
|
||||
case hydra::InfoType::Author: return "wheremyfoodat (Peach)";
|
||||
case hydra::InfoType::Version: return "0.7";
|
||||
case hydra::InfoType::Version: return PANDA3DS_VERSION;
|
||||
case hydra::InfoType::License: return "GPLv3";
|
||||
case hydra::InfoType::Website: return "https://panda3ds.com/";
|
||||
case hydra::InfoType::Extensions: return "3ds,cci,cxi,app,3dsx,elf,axf";
|
||||
|
|
|
@ -4,16 +4,17 @@
|
|||
|
||||
#include <libretro.h>
|
||||
|
||||
#include <version.hpp>
|
||||
#include <emulator.hpp>
|
||||
#include <renderer_gl/renderer_gl.hpp>
|
||||
|
||||
static retro_environment_t envCallbacks;
|
||||
static retro_video_refresh_t videoCallbacks;
|
||||
static retro_environment_t envCallback;
|
||||
static retro_video_refresh_t videoCallback;
|
||||
static retro_audio_sample_batch_t audioBatchCallback;
|
||||
static retro_input_poll_t inputPollCallback;
|
||||
static retro_input_state_t inputStateCallback;
|
||||
|
||||
static retro_hw_render_callback hw_render;
|
||||
static retro_hw_render_callback hwRender;
|
||||
static std::filesystem::path savePath;
|
||||
|
||||
static bool screenTouched;
|
||||
|
@ -29,17 +30,17 @@ std::filesystem::path Emulator::getAppDataRoot() {
|
|||
return std::filesystem::path(savePath / "Emulator Files");
|
||||
}
|
||||
|
||||
static void* GetGLProcAddress(const char* name) {
|
||||
return (void*)hw_render.get_proc_address(name);
|
||||
static void* getGLProcAddress(const char* name) {
|
||||
return (void*)hwRender.get_proc_address(name);
|
||||
}
|
||||
|
||||
static void VideoResetContext() {
|
||||
static void videoResetContext() {
|
||||
#ifdef USING_GLES
|
||||
if (!gladLoadGLES2Loader(reinterpret_cast<GLADloadproc>(GetGLProcAddress))) {
|
||||
if (!gladLoadGLES2Loader(reinterpret_cast<GLADloadproc>(getGLProcAddress))) {
|
||||
Helpers::panic("OpenGL ES init failed");
|
||||
}
|
||||
#else
|
||||
if (!gladLoadGLLoader(reinterpret_cast<GLADloadproc>(GetGLProcAddress))) {
|
||||
if (!gladLoadGLLoader(reinterpret_cast<GLADloadproc>(getGLProcAddress))) {
|
||||
Helpers::panic("OpenGL init failed");
|
||||
}
|
||||
#endif
|
||||
|
@ -47,31 +48,31 @@ static void VideoResetContext() {
|
|||
emulator->initGraphicsContext(nullptr);
|
||||
}
|
||||
|
||||
static void VideoDestroyContext() {
|
||||
emulator->deinitGraphicsContext();
|
||||
static void videoDestroyContext() {
|
||||
emulator->deinitGraphicsContext();
|
||||
}
|
||||
|
||||
static bool SetHWRender(retro_hw_context_type type) {
|
||||
hw_render.context_type = type;
|
||||
hw_render.context_reset = VideoResetContext;
|
||||
hw_render.context_destroy = VideoDestroyContext;
|
||||
hw_render.bottom_left_origin = true;
|
||||
static bool setHWRender(retro_hw_context_type type) {
|
||||
hwRender.context_type = type;
|
||||
hwRender.context_reset = videoResetContext;
|
||||
hwRender.context_destroy = videoDestroyContext;
|
||||
hwRender.bottom_left_origin = true;
|
||||
|
||||
switch (type) {
|
||||
case RETRO_HW_CONTEXT_OPENGL_CORE:
|
||||
hw_render.version_major = 4;
|
||||
hw_render.version_minor = 1;
|
||||
hwRender.version_major = 4;
|
||||
hwRender.version_minor = 1;
|
||||
|
||||
if (envCallbacks(RETRO_ENVIRONMENT_SET_HW_RENDER, &hw_render)) {
|
||||
if (envCallback(RETRO_ENVIRONMENT_SET_HW_RENDER, &hwRender)) {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case RETRO_HW_CONTEXT_OPENGLES3:
|
||||
case RETRO_HW_CONTEXT_OPENGL:
|
||||
hw_render.version_major = 3;
|
||||
hw_render.version_minor = 1;
|
||||
hwRender.version_major = 3;
|
||||
hwRender.version_minor = 1;
|
||||
|
||||
if (envCallbacks(RETRO_ENVIRONMENT_SET_HW_RENDER, &hw_render)) {
|
||||
if (envCallback(RETRO_ENVIRONMENT_SET_HW_RENDER, &hwRender)) {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
@ -83,18 +84,18 @@ static bool SetHWRender(retro_hw_context_type type) {
|
|||
|
||||
static void videoInit() {
|
||||
retro_hw_context_type preferred = RETRO_HW_CONTEXT_NONE;
|
||||
envCallbacks(RETRO_ENVIRONMENT_GET_PREFERRED_HW_RENDER, &preferred);
|
||||
envCallback(RETRO_ENVIRONMENT_GET_PREFERRED_HW_RENDER, &preferred);
|
||||
|
||||
if (preferred && SetHWRender(preferred)) return;
|
||||
if (SetHWRender(RETRO_HW_CONTEXT_OPENGL_CORE)) return;
|
||||
if (SetHWRender(RETRO_HW_CONTEXT_OPENGL)) return;
|
||||
if (SetHWRender(RETRO_HW_CONTEXT_OPENGLES3)) return;
|
||||
if (preferred && setHWRender(preferred)) return;
|
||||
if (setHWRender(RETRO_HW_CONTEXT_OPENGL_CORE)) return;
|
||||
if (setHWRender(RETRO_HW_CONTEXT_OPENGL)) return;
|
||||
if (setHWRender(RETRO_HW_CONTEXT_OPENGLES3)) return;
|
||||
|
||||
hw_render.context_type = RETRO_HW_CONTEXT_NONE;
|
||||
hwRender.context_type = RETRO_HW_CONTEXT_NONE;
|
||||
}
|
||||
|
||||
static bool GetButtonState(uint id) { return inputStateCallback(0, RETRO_DEVICE_JOYPAD, 0, id); }
|
||||
static float GetAxisState(uint index, uint id) { return inputStateCallback(0, RETRO_DEVICE_ANALOG, index, id); }
|
||||
static bool getButtonState(uint id) { return inputStateCallback(0, RETRO_DEVICE_JOYPAD, 0, id); }
|
||||
static float getAxisState(uint index, uint id) { return inputStateCallback(0, RETRO_DEVICE_ANALOG, index, id); }
|
||||
|
||||
static void inputInit() {
|
||||
static const retro_controller_description controllers[] = {
|
||||
|
@ -107,7 +108,7 @@ static void inputInit() {
|
|||
{NULL, 0},
|
||||
};
|
||||
|
||||
envCallbacks(RETRO_ENVIRONMENT_SET_CONTROLLER_INFO, (void*)ports);
|
||||
envCallback(RETRO_ENVIRONMENT_SET_CONTROLLER_INFO, (void*)ports);
|
||||
|
||||
retro_input_descriptor desc[] = {
|
||||
{0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT, "Left"},
|
||||
|
@ -127,14 +128,14 @@ static void inputInit() {
|
|||
{0},
|
||||
};
|
||||
|
||||
envCallbacks(RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS, &desc);
|
||||
envCallback(RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS, &desc);
|
||||
}
|
||||
|
||||
static std::string FetchVariable(std::string key, std::string def) {
|
||||
static std::string fetchVariable(std::string key, std::string def) {
|
||||
retro_variable var = {nullptr};
|
||||
var.key = key.c_str();
|
||||
|
||||
if (!envCallbacks(RETRO_ENVIRONMENT_GET_VARIABLE, &var) || var.value == nullptr) {
|
||||
if (!envCallback(RETRO_ENVIRONMENT_GET_VARIABLE, &var) || var.value == nullptr) {
|
||||
Helpers::warn("Fetching variable %s failed.", key.c_str());
|
||||
return def;
|
||||
}
|
||||
|
@ -142,13 +143,28 @@ static std::string FetchVariable(std::string key, std::string def) {
|
|||
return std::string(var.value);
|
||||
}
|
||||
|
||||
static bool FetchVariableBool(std::string key, bool def) {
|
||||
return FetchVariable(key, def ? "enabled" : "disabled") == "enabled";
|
||||
static int fetchVariableInt(std::string key, int def) {
|
||||
std::string value = fetchVariable(key, std::to_string(def));
|
||||
|
||||
if (!value.empty() && std::isdigit(value[0])) {
|
||||
return std::stoi(value);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool fetchVariableBool(std::string key, bool def) {
|
||||
return fetchVariable(key, def ? "enabled" : "disabled") == "enabled";
|
||||
}
|
||||
|
||||
static int fetchVariableRange(std::string key, int min, int max) {
|
||||
return std::clamp(fetchVariableInt(key, min), min, max);
|
||||
}
|
||||
|
||||
static void configInit() {
|
||||
static const retro_variable values[] = {
|
||||
{"panda3ds_use_shader_jit", "Enable shader JIT; enabled|disabled"},
|
||||
{"panda3ds_use_shader_jit", EmulatorConfig::shaderJitDefault ? "Enable shader JIT; enabled|disabled"
|
||||
: "Enable shader JIT; disabled|enabled"},
|
||||
{"panda3ds_accurate_shader_mul", "Enable accurate shader multiplication; disabled|enabled"},
|
||||
{"panda3ds_use_ubershader", EmulatorConfig::ubershaderDefault ? "Use ubershaders (No stutter, maybe slower); enabled|disabled"
|
||||
: "Use ubershaders (No stutter, maybe slower); disabled|enabled"},
|
||||
|
@ -164,33 +180,33 @@ static void configInit() {
|
|||
{nullptr, nullptr},
|
||||
};
|
||||
|
||||
envCallbacks(RETRO_ENVIRONMENT_SET_VARIABLES, (void*)values);
|
||||
envCallback(RETRO_ENVIRONMENT_SET_VARIABLES, (void*)values);
|
||||
}
|
||||
|
||||
static void configUpdate() {
|
||||
EmulatorConfig& config = emulator->getConfig();
|
||||
|
||||
config.rendererType = RendererType::OpenGL;
|
||||
config.vsyncEnabled = FetchVariableBool("panda3ds_use_vsync", true);
|
||||
config.shaderJitEnabled = FetchVariableBool("panda3ds_use_shader_jit", true);
|
||||
config.chargerPlugged = FetchVariableBool("panda3ds_use_charger", true);
|
||||
config.batteryPercentage = std::clamp(std::stoi(FetchVariable("panda3ds_battery_level", "5")), 0, 100);
|
||||
config.dspType = Audio::DSPCore::typeFromString(FetchVariable("panda3ds_dsp_emulation", "null"));
|
||||
config.audioEnabled = FetchVariableBool("panda3ds_use_audio", false);
|
||||
config.sdCardInserted = FetchVariableBool("panda3ds_use_virtual_sd", true);
|
||||
config.sdWriteProtected = FetchVariableBool("panda3ds_write_protect_virtual_sd", false);
|
||||
config.accurateShaderMul = FetchVariableBool("panda3ds_accurate_shader_mul", false);
|
||||
config.useUbershaders = FetchVariableBool("panda3ds_use_ubershader", true);
|
||||
config.forceShadergenForLights = FetchVariableBool("panda3ds_ubershader_lighting_override", true);
|
||||
config.lightShadergenThreshold = std::clamp(std::stoi(FetchVariable("panda3ds_ubershader_lighting_override_threshold", "1")), 1, 8);
|
||||
config.vsyncEnabled = fetchVariableBool("panda3ds_use_vsync", true);
|
||||
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.dspType = Audio::DSPCore::typeFromString(fetchVariable("panda3ds_dsp_emulation", "null"));
|
||||
config.audioEnabled = fetchVariableBool("panda3ds_use_audio", false);
|
||||
config.sdCardInserted = fetchVariableBool("panda3ds_use_virtual_sd", true);
|
||||
config.sdWriteProtected = fetchVariableBool("panda3ds_write_protect_virtual_sd", false);
|
||||
config.accurateShaderMul = fetchVariableBool("panda3ds_accurate_shader_mul", false);
|
||||
config.useUbershaders = fetchVariableBool("panda3ds_use_ubershader", EmulatorConfig::ubershaderDefault);
|
||||
config.forceShadergenForLights = fetchVariableBool("panda3ds_ubershader_lighting_override", true);
|
||||
config.lightShadergenThreshold = fetchVariableRange("panda3ds_ubershader_lighting_override_threshold", 1, 8);
|
||||
config.discordRpcEnabled = false;
|
||||
|
||||
config.save();
|
||||
}
|
||||
|
||||
static void ConfigCheckVariables() {
|
||||
static void configCheckVariables() {
|
||||
bool updated = false;
|
||||
envCallbacks(RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE, &updated);
|
||||
envCallback(RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE, &updated);
|
||||
|
||||
if (updated) {
|
||||
configUpdate();
|
||||
|
@ -200,9 +216,9 @@ static void ConfigCheckVariables() {
|
|||
void retro_get_system_info(retro_system_info* info) {
|
||||
info->need_fullpath = true;
|
||||
info->valid_extensions = "3ds|3dsx|elf|axf|cci|cxi|app";
|
||||
info->library_version = "0.8";
|
||||
info->library_version = PANDA3DS_VERSION;
|
||||
info->library_name = "Panda3DS";
|
||||
info->block_extract = true;
|
||||
info->block_extract = false;
|
||||
}
|
||||
|
||||
void retro_get_system_av_info(retro_system_av_info* info) {
|
||||
|
@ -218,11 +234,11 @@ void retro_get_system_av_info(retro_system_av_info* info) {
|
|||
}
|
||||
|
||||
void retro_set_environment(retro_environment_t cb) {
|
||||
envCallbacks = cb;
|
||||
envCallback = cb;
|
||||
}
|
||||
|
||||
void retro_set_video_refresh(retro_video_refresh_t cb) {
|
||||
videoCallbacks = cb;
|
||||
videoCallback = cb;
|
||||
}
|
||||
|
||||
void retro_set_audio_sample_batch(retro_audio_sample_batch_t cb) {
|
||||
|
@ -241,15 +257,15 @@ void retro_set_input_state(retro_input_state_t cb) {
|
|||
|
||||
void retro_init() {
|
||||
enum retro_pixel_format xrgb888 = RETRO_PIXEL_FORMAT_XRGB8888;
|
||||
envCallbacks(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &xrgb888);
|
||||
envCallback(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &xrgb888);
|
||||
|
||||
char* save_dir = nullptr;
|
||||
char* saveDir = nullptr;
|
||||
|
||||
if (!envCallbacks(RETRO_ENVIRONMENT_GET_SAVE_DIRECTORY, &save_dir) || save_dir == nullptr) {
|
||||
if (!envCallback(RETRO_ENVIRONMENT_GET_SAVE_DIRECTORY, &saveDir) || saveDir == nullptr) {
|
||||
Helpers::warn("No save directory provided by LibRetro.");
|
||||
savePath = std::filesystem::current_path();
|
||||
} else {
|
||||
savePath = std::filesystem::path(save_dir);
|
||||
savePath = std::filesystem::path(saveDir);
|
||||
}
|
||||
|
||||
emulator = std::make_unique<Emulator>();
|
||||
|
@ -288,31 +304,31 @@ void retro_reset() {
|
|||
}
|
||||
|
||||
void retro_run() {
|
||||
ConfigCheckVariables();
|
||||
configCheckVariables();
|
||||
|
||||
renderer->setFBO(hw_render.get_current_framebuffer());
|
||||
renderer->setFBO(hwRender.get_current_framebuffer());
|
||||
renderer->resetStateManager();
|
||||
|
||||
inputPollCallback();
|
||||
|
||||
HIDService& hid = emulator->getServiceManager().getHID();
|
||||
|
||||
hid.setKey(HID::Keys::A, GetButtonState(RETRO_DEVICE_ID_JOYPAD_A));
|
||||
hid.setKey(HID::Keys::B, GetButtonState(RETRO_DEVICE_ID_JOYPAD_B));
|
||||
hid.setKey(HID::Keys::X, GetButtonState(RETRO_DEVICE_ID_JOYPAD_X));
|
||||
hid.setKey(HID::Keys::Y, GetButtonState(RETRO_DEVICE_ID_JOYPAD_Y));
|
||||
hid.setKey(HID::Keys::L, GetButtonState(RETRO_DEVICE_ID_JOYPAD_L));
|
||||
hid.setKey(HID::Keys::R, GetButtonState(RETRO_DEVICE_ID_JOYPAD_R));
|
||||
hid.setKey(HID::Keys::Start, GetButtonState(RETRO_DEVICE_ID_JOYPAD_START));
|
||||
hid.setKey(HID::Keys::Select, GetButtonState(RETRO_DEVICE_ID_JOYPAD_SELECT));
|
||||
hid.setKey(HID::Keys::Up, GetButtonState(RETRO_DEVICE_ID_JOYPAD_UP));
|
||||
hid.setKey(HID::Keys::Down, GetButtonState(RETRO_DEVICE_ID_JOYPAD_DOWN));
|
||||
hid.setKey(HID::Keys::Left, GetButtonState(RETRO_DEVICE_ID_JOYPAD_LEFT));
|
||||
hid.setKey(HID::Keys::Right, GetButtonState(RETRO_DEVICE_ID_JOYPAD_RIGHT));
|
||||
hid.setKey(HID::Keys::A, getButtonState(RETRO_DEVICE_ID_JOYPAD_A));
|
||||
hid.setKey(HID::Keys::B, getButtonState(RETRO_DEVICE_ID_JOYPAD_B));
|
||||
hid.setKey(HID::Keys::X, getButtonState(RETRO_DEVICE_ID_JOYPAD_X));
|
||||
hid.setKey(HID::Keys::Y, getButtonState(RETRO_DEVICE_ID_JOYPAD_Y));
|
||||
hid.setKey(HID::Keys::L, getButtonState(RETRO_DEVICE_ID_JOYPAD_L));
|
||||
hid.setKey(HID::Keys::R, getButtonState(RETRO_DEVICE_ID_JOYPAD_R));
|
||||
hid.setKey(HID::Keys::Start, getButtonState(RETRO_DEVICE_ID_JOYPAD_START));
|
||||
hid.setKey(HID::Keys::Select, getButtonState(RETRO_DEVICE_ID_JOYPAD_SELECT));
|
||||
hid.setKey(HID::Keys::Up, getButtonState(RETRO_DEVICE_ID_JOYPAD_UP));
|
||||
hid.setKey(HID::Keys::Down, getButtonState(RETRO_DEVICE_ID_JOYPAD_DOWN));
|
||||
hid.setKey(HID::Keys::Left, getButtonState(RETRO_DEVICE_ID_JOYPAD_LEFT));
|
||||
hid.setKey(HID::Keys::Right, getButtonState(RETRO_DEVICE_ID_JOYPAD_RIGHT));
|
||||
|
||||
// Get analog values for the left analog stick (Right analog stick is N3DS-only and unimplemented)
|
||||
float xLeft = GetAxisState(RETRO_DEVICE_INDEX_ANALOG_LEFT, RETRO_DEVICE_ID_ANALOG_X);
|
||||
float yLeft = GetAxisState(RETRO_DEVICE_INDEX_ANALOG_LEFT, RETRO_DEVICE_ID_ANALOG_Y);
|
||||
float xLeft = getAxisState(RETRO_DEVICE_INDEX_ANALOG_LEFT, RETRO_DEVICE_ID_ANALOG_X);
|
||||
float yLeft = getAxisState(RETRO_DEVICE_INDEX_ANALOG_LEFT, RETRO_DEVICE_ID_ANALOG_Y);
|
||||
|
||||
hid.setCirclepadX((xLeft / +32767) * 0x9C);
|
||||
hid.setCirclepadY((yLeft / -32767) * 0x9C);
|
||||
|
@ -350,7 +366,7 @@ void retro_run() {
|
|||
hid.updateInputs(emulator->getTicks());
|
||||
emulator->runFrame();
|
||||
|
||||
videoCallbacks(RETRO_HW_FRAME_BUFFER_VALID, emulator->width, emulator->height, 0);
|
||||
videoCallback(RETRO_HW_FRAME_BUFFER_VALID, emulator->width, emulator->height, 0);
|
||||
}
|
||||
|
||||
void retro_set_controller_port_device(uint port, uint device) {}
|
||||
|
@ -368,7 +384,7 @@ uint retro_api_version() { return RETRO_API_VERSION; }
|
|||
|
||||
usize retro_get_memory_size(uint id) {
|
||||
if (id == RETRO_MEMORY_SYSTEM_RAM) {
|
||||
return 0;
|
||||
return Memory::FCRAM_SIZE;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
@ -376,7 +392,7 @@ usize retro_get_memory_size(uint id) {
|
|||
|
||||
void* retro_get_memory_data(uint id) {
|
||||
if (id == RETRO_MEMORY_SYSTEM_RAM) {
|
||||
return 0;
|
||||
return emulator->getMemory().getFCRAM();
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
#include "panda_qt/about_window.hpp"
|
||||
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QTextEdit>
|
||||
#include <QVBoxLayout>
|
||||
#include <QtGlobal>
|
||||
|
||||
#include "version.hpp"
|
||||
|
||||
// Based on https://github.com/dolphin-emu/dolphin/blob/master/Source/Core/DolphinQt/AboutDialog.cpp
|
||||
|
||||
AboutWindow::AboutWindow(QWidget* parent) : QDialog(parent) {
|
||||
|
@ -17,6 +20,8 @@ AboutWindow::AboutWindow(QWidget* parent) : QDialog(parent) {
|
|||
QStringLiteral(R"(
|
||||
<p style='font-size:38pt; font-weight:400;'>Panda3DS</p>
|
||||
|
||||
<p style='font-size:18pt;'>v%VERSION_STRING%</p>
|
||||
|
||||
<p>
|
||||
%ABOUT_PANDA3DS%<br>
|
||||
<a href='https://panda3ds.com/'>%SUPPORT%</a><br>
|
||||
|
@ -26,6 +31,7 @@ AboutWindow::AboutWindow(QWidget* parent) : QDialog(parent) {
|
|||
<a>%AUTHORS%</a>
|
||||
</p>
|
||||
)")
|
||||
.replace(QStringLiteral("%VERSION_STRING%"), PANDA3DS_VERSION)
|
||||
.replace(QStringLiteral("%ABOUT_PANDA3DS%"), tr("Panda3DS is a free and open source Nintendo 3DS emulator, for Windows, MacOS and Linux"))
|
||||
.replace(QStringLiteral("%SUPPORT%"), tr("Visit panda3ds.com for help with Panda3DS and links to our official support sites."))
|
||||
.replace(
|
||||
|
|
|
@ -1,15 +1,9 @@
|
|||
#include "panda_qt/cheats_window.hpp"
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QDialog>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QListWidget>
|
||||
#include <QPushButton>
|
||||
#include <QTextEdit>
|
||||
#include <QVBoxLayout>
|
||||
#include <QHBoxLayout>
|
||||
#include <QTimer>
|
||||
#include <QVBoxLayout>
|
||||
#include <functional>
|
||||
|
||||
#include "cheats.hpp"
|
||||
|
@ -18,71 +12,17 @@
|
|||
|
||||
MainWindow* mainWindow = nullptr;
|
||||
|
||||
struct CheatMetadata {
|
||||
u32 handle = Cheats::badCheatHandle;
|
||||
std::string name = "New cheat";
|
||||
std::string code;
|
||||
bool enabled = true;
|
||||
};
|
||||
|
||||
void dispatchToMainThread(std::function<void()> callback) {
|
||||
QTimer* timer = new QTimer();
|
||||
timer->moveToThread(qApp->thread());
|
||||
timer->setSingleShot(true);
|
||||
QObject::connect(timer, &QTimer::timeout, [=]()
|
||||
{
|
||||
callback();
|
||||
timer->deleteLater();
|
||||
});
|
||||
QMetaObject::invokeMethod(timer, "start", Qt::QueuedConnection, Q_ARG(int, 0));
|
||||
QTimer* timer = new QTimer();
|
||||
timer->moveToThread(qApp->thread());
|
||||
timer->setSingleShot(true);
|
||||
QObject::connect(timer, &QTimer::timeout, [=]() {
|
||||
callback();
|
||||
timer->deleteLater();
|
||||
});
|
||||
QMetaObject::invokeMethod(timer, "start", Qt::QueuedConnection, Q_ARG(int, 0));
|
||||
}
|
||||
|
||||
class CheatEntryWidget : public QWidget {
|
||||
public:
|
||||
CheatEntryWidget(Emulator* emu, CheatMetadata metadata, QListWidget* parent);
|
||||
|
||||
void Update() {
|
||||
name->setText(metadata.name.c_str());
|
||||
enabled->setChecked(metadata.enabled);
|
||||
update();
|
||||
}
|
||||
|
||||
void Remove() {
|
||||
emu->getCheats().removeCheat(metadata.handle);
|
||||
cheatList->takeItem(cheatList->row(listItem));
|
||||
deleteLater();
|
||||
}
|
||||
|
||||
const CheatMetadata& getMetadata() { return metadata; }
|
||||
void setMetadata(const CheatMetadata& metadata) { this->metadata = metadata; }
|
||||
|
||||
private:
|
||||
void checkboxChanged(int state);
|
||||
void editClicked();
|
||||
|
||||
Emulator* emu;
|
||||
CheatMetadata metadata;
|
||||
u32 handle;
|
||||
QLabel* name;
|
||||
QCheckBox* enabled;
|
||||
QListWidget* cheatList;
|
||||
QListWidgetItem* listItem;
|
||||
};
|
||||
|
||||
class CheatEditDialog : public QDialog {
|
||||
public:
|
||||
CheatEditDialog(Emulator* emu, CheatEntryWidget& cheatEntry);
|
||||
|
||||
void accepted();
|
||||
void rejected();
|
||||
|
||||
private:
|
||||
Emulator* emu;
|
||||
CheatEntryWidget& cheatEntry;
|
||||
QTextEdit* codeEdit;
|
||||
QLineEdit* nameEdit;
|
||||
};
|
||||
|
||||
CheatEntryWidget::CheatEntryWidget(Emulator* emu, CheatMetadata metadata, QListWidget* parent)
|
||||
: QWidget(), emu(emu), metadata(metadata), cheatList(parent) {
|
||||
QHBoxLayout* layout = new QHBoxLayout;
|
||||
|
@ -219,7 +159,7 @@ void CheatEditDialog::rejected() {
|
|||
|
||||
CheatsWindow::CheatsWindow(Emulator* emu, const std::filesystem::path& cheatPath, QWidget* parent)
|
||||
: QWidget(parent, Qt::Window), emu(emu), cheatPath(cheatPath) {
|
||||
mainWindow = static_cast<MainWindow*>(parent);
|
||||
mainWindow = static_cast<MainWindow*>(parent);
|
||||
|
||||
QVBoxLayout* layout = new QVBoxLayout;
|
||||
layout->setContentsMargins(6, 6, 6, 6);
|
||||
|
@ -265,4 +205,4 @@ void CheatsWindow::removeClicked() {
|
|||
|
||||
CheatEntryWidget* entry = static_cast<CheatEntryWidget*>(cheatList->itemWidget(item));
|
||||
entry->Remove();
|
||||
}
|
||||
}
|
|
@ -7,9 +7,10 @@
|
|||
#include <cstdio>
|
||||
#include <fstream>
|
||||
|
||||
#include "version.hpp"
|
||||
#include "cheats.hpp"
|
||||
#include "input_mappings.hpp"
|
||||
#include "sdl_gyro.hpp"
|
||||
#include "sdl_sensors.hpp"
|
||||
#include "services/dsp.hpp"
|
||||
|
||||
MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent), keyboardMappings(InputMappings::defaultKeyboardMappings()) {
|
||||
|
@ -98,6 +99,14 @@ MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent)
|
|||
}
|
||||
}
|
||||
|
||||
if (emu->getConfig().appVersionOnWindow) {
|
||||
setWindowTitle("Alber v" PANDA3DS_VERSION);
|
||||
}
|
||||
|
||||
if (emu->getConfig().printAppVersion) {
|
||||
printf("Welcome to Panda3DS v%s!\n", PANDA3DS_VERSION);
|
||||
}
|
||||
|
||||
// The emulator graphics context for the thread should be initialized in the emulator thread due to how GL contexts work
|
||||
emuThread = std::thread([this]() {
|
||||
const RendererType rendererType = emu->getConfig().rendererType;
|
||||
|
@ -609,7 +618,7 @@ void MainWindow::pollControllers() {
|
|||
|
||||
case SDL_CONTROLLERSENSORUPDATE: {
|
||||
if (event.csensor.sensor == SDL_SENSOR_GYRO) {
|
||||
auto rotation = Gyro::SDL::convertRotation({
|
||||
auto rotation = Sensors::SDL::convertRotation({
|
||||
event.csensor.data[0],
|
||||
event.csensor.data[1],
|
||||
event.csensor.data[2],
|
||||
|
@ -618,6 +627,9 @@ void MainWindow::pollControllers() {
|
|||
hid.setPitch(s16(rotation.x));
|
||||
hid.setRoll(s16(rotation.y));
|
||||
hid.setYaw(s16(rotation.z));
|
||||
} else if (event.csensor.sensor == SDL_SENSOR_ACCEL) {
|
||||
auto accel = Sensors::SDL::convertAcceleration(event.csensor.data);
|
||||
hid.setAccel(accel.x, accel.y, accel.z);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -627,8 +639,13 @@ void MainWindow::pollControllers() {
|
|||
|
||||
void MainWindow::setupControllerSensors(SDL_GameController* controller) {
|
||||
bool haveGyro = SDL_GameControllerHasSensor(controller, SDL_SENSOR_GYRO) == SDL_TRUE;
|
||||
bool haveAccelerometer = SDL_GameControllerHasSensor(controller, SDL_SENSOR_ACCEL) == SDL_TRUE;
|
||||
|
||||
if (haveGyro) {
|
||||
SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_GYRO, SDL_TRUE);
|
||||
}
|
||||
|
||||
if (haveAccelerometer) {
|
||||
SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_ACCEL, SDL_TRUE);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#include "input_mappings.hpp"
|
||||
|
||||
#include <QKeyEvent>
|
||||
|
||||
#include "input_mappings.hpp"
|
||||
|
||||
InputMappings InputMappings::defaultKeyboardMappings() {
|
||||
InputMappings mappings;
|
||||
mappings.setMapping(Qt::Key_L, HID::Keys::A);
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
#include "panda_qt/shader_editor.hpp"
|
||||
|
||||
#include <QPushButton>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "panda_qt/main_window.hpp"
|
||||
#include "panda_qt/shader_editor.hpp"
|
||||
|
||||
using namespace Zep;
|
||||
|
||||
|
|
|
@ -2,7 +2,9 @@
|
|||
|
||||
#include <glad/gl.h>
|
||||
|
||||
#include "sdl_gyro.hpp"
|
||||
#include "renderdoc.hpp"
|
||||
#include "sdl_sensors.hpp"
|
||||
#include "version.hpp"
|
||||
|
||||
FrontendSDL::FrontendSDL() : keyboardMappings(InputMappings::defaultKeyboardMappings()) {
|
||||
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS) < 0) {
|
||||
|
@ -33,13 +35,18 @@ FrontendSDL::FrontendSDL() : keyboardMappings(InputMappings::defaultKeyboardMapp
|
|||
needOpenGL = needOpenGL || (config.rendererType == RendererType::OpenGL);
|
||||
#endif
|
||||
|
||||
const char* windowTitle = config.appVersionOnWindow ? ("Alber v" PANDA3DS_VERSION) : "Alber";
|
||||
if (config.printAppVersion) {
|
||||
printf("Welcome to Panda3DS v%s!\n", PANDA3DS_VERSION);
|
||||
}
|
||||
|
||||
if (needOpenGL) {
|
||||
// Demand 3.3 core for software renderer, or 4.1 core for OpenGL renderer (max available on MacOS)
|
||||
// MacOS gets mad if we don't explicitly demand a core profile
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, config.rendererType == RendererType::Software ? 3 : 4);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, config.rendererType == RendererType::Software ? 3 : 1);
|
||||
window = SDL_CreateWindow("Alber", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 400, 480, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);
|
||||
window = SDL_CreateWindow(windowTitle, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 400, 480, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);
|
||||
|
||||
if (window == nullptr) {
|
||||
Helpers::panic("Window creation failed: %s", SDL_GetError());
|
||||
|
@ -59,7 +66,7 @@ FrontendSDL::FrontendSDL() : keyboardMappings(InputMappings::defaultKeyboardMapp
|
|||
|
||||
#ifdef PANDA3DS_ENABLE_VULKAN
|
||||
if (config.rendererType == RendererType::Vulkan) {
|
||||
window = SDL_CreateWindow("Alber", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 400, 480, SDL_WINDOW_VULKAN | SDL_WINDOW_RESIZABLE);
|
||||
window = SDL_CreateWindow(windowTitle, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 400, 480, SDL_WINDOW_VULKAN | SDL_WINDOW_RESIZABLE);
|
||||
|
||||
if (window == nullptr) {
|
||||
Helpers::warn("Window creation failed: %s", SDL_GetError());
|
||||
|
@ -144,6 +151,14 @@ void FrontendSDL::run() {
|
|||
emu.reset(Emulator::ReloadOption::Reload);
|
||||
break;
|
||||
}
|
||||
|
||||
case SDLK_F11: {
|
||||
if constexpr (Renderdoc::isSupported()) {
|
||||
Renderdoc::triggerCapture();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
@ -299,7 +314,7 @@ void FrontendSDL::run() {
|
|||
|
||||
case SDL_CONTROLLERSENSORUPDATE: {
|
||||
if (event.csensor.sensor == SDL_SENSOR_GYRO) {
|
||||
auto rotation = Gyro::SDL::convertRotation({
|
||||
auto rotation = Sensors::SDL::convertRotation({
|
||||
event.csensor.data[0],
|
||||
event.csensor.data[1],
|
||||
event.csensor.data[2],
|
||||
|
@ -308,6 +323,9 @@ void FrontendSDL::run() {
|
|||
hid.setPitch(s16(rotation.x));
|
||||
hid.setRoll(s16(rotation.y));
|
||||
hid.setYaw(s16(rotation.z));
|
||||
} else if (event.csensor.sensor == SDL_SENSOR_ACCEL) {
|
||||
auto accel = Sensors::SDL::convertAcceleration(event.csensor.data);
|
||||
hid.setAccel(accel.x, accel.y, accel.z);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -376,8 +394,13 @@ void FrontendSDL::run() {
|
|||
|
||||
void FrontendSDL::setupControllerSensors(SDL_GameController* controller) {
|
||||
bool haveGyro = SDL_GameControllerHasSensor(controller, SDL_SENSOR_GYRO) == SDL_TRUE;
|
||||
bool haveAccelerometer = SDL_GameControllerHasSensor(controller, SDL_SENSOR_ACCEL) == SDL_TRUE;
|
||||
|
||||
if (haveGyro) {
|
||||
SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_GYRO, SDL_TRUE);
|
||||
}
|
||||
|
||||
if (haveAccelerometer) {
|
||||
SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_ACCEL, SDL_TRUE);
|
||||
}
|
||||
}
|
||||
|
|
119
src/renderdoc.cpp
Normal file
119
src/renderdoc.cpp
Normal file
|
@ -0,0 +1,119 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#ifdef PANDA3DS_ENABLE_RENDERDOC
|
||||
#include "renderdoc.hpp"
|
||||
|
||||
#include <renderdoc_app.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <dlfcn.h>
|
||||
#endif
|
||||
|
||||
#include <array>
|
||||
#include <filesystem>
|
||||
|
||||
namespace Renderdoc {
|
||||
enum class CaptureState {
|
||||
Idle,
|
||||
Triggered,
|
||||
InProgress,
|
||||
};
|
||||
|
||||
static CaptureState captureState{CaptureState::Idle};
|
||||
RENDERDOC_API_1_6_0* rdocAPI{};
|
||||
|
||||
void loadRenderdoc() {
|
||||
#ifdef WIN32
|
||||
// Check if we are running in Renderdoc GUI
|
||||
HMODULE mod = GetModuleHandleA("renderdoc.dll");
|
||||
if (!mod) {
|
||||
// If enabled in config, try to load RDoc runtime in offline mode
|
||||
HKEY h_reg_key;
|
||||
LONG result = RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Classes\\RenderDoc.RDCCapture.1\\DefaultIcon\\", 0, KEY_READ, &h_reg_key);
|
||||
if (result != ERROR_SUCCESS) {
|
||||
return;
|
||||
}
|
||||
std::array<wchar_t, MAX_PATH> keyString{};
|
||||
DWORD stringSize{keyString.size()};
|
||||
result = RegQueryValueExW(h_reg_key, L"", 0, NULL, (LPBYTE)keyString.data(), &stringSize);
|
||||
if (result != ERROR_SUCCESS) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::filesystem::path path{keyString.cbegin(), keyString.cend()};
|
||||
path = path.parent_path().append("renderdoc.dll");
|
||||
const auto path_to_lib = path.generic_string();
|
||||
mod = LoadLibraryA(path_to_lib.c_str());
|
||||
}
|
||||
|
||||
if (mod) {
|
||||
const auto RENDERDOC_GetAPI = reinterpret_cast<pRENDERDOC_GetAPI>(GetProcAddress(mod, "RENDERDOC_GetAPI"));
|
||||
const s32 ret = RENDERDOC_GetAPI(eRENDERDOC_API_Version_1_6_0, (void**)&rdocAPI);
|
||||
|
||||
if (ret != 1) {
|
||||
Helpers::panic("Invalid return value from RENDERDOC_GetAPI");
|
||||
}
|
||||
}
|
||||
#else
|
||||
#ifdef ANDROID
|
||||
static constexpr const char RENDERDOC_LIB[] = "libVkLayer_GLES_RenderDoc.so";
|
||||
#else
|
||||
static constexpr const char RENDERDOC_LIB[] = "librenderdoc.so";
|
||||
#endif
|
||||
if (void* mod = dlopen(RENDERDOC_LIB, RTLD_NOW | RTLD_NOLOAD)) {
|
||||
const auto RENDERDOC_GetAPI = reinterpret_cast<pRENDERDOC_GetAPI>(dlsym(mod, "RENDERDOC_GetAPI"));
|
||||
const s32 ret = RENDERDOC_GetAPI(eRENDERDOC_API_Version_1_6_0, (void**)&rdocAPI);
|
||||
|
||||
if (ret != 1) {
|
||||
Helpers::panic("Invalid return value from RENDERDOC_GetAPI");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if (rdocAPI) {
|
||||
// Disable default capture keys as they suppose to trigger present-to-present capturing
|
||||
// and it is not what we want
|
||||
rdocAPI->SetCaptureKeys(nullptr, 0);
|
||||
|
||||
// Also remove rdoc crash handler
|
||||
rdocAPI->UnloadCrashHandler();
|
||||
}
|
||||
}
|
||||
|
||||
void startCapture() {
|
||||
if (!rdocAPI) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (captureState == CaptureState::Triggered) {
|
||||
rdocAPI->StartFrameCapture(nullptr, nullptr);
|
||||
captureState = CaptureState::InProgress;
|
||||
}
|
||||
}
|
||||
|
||||
void endCapture() {
|
||||
if (!rdocAPI) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (captureState == CaptureState::InProgress) {
|
||||
rdocAPI->EndFrameCapture(nullptr, nullptr);
|
||||
captureState = CaptureState::Idle;
|
||||
}
|
||||
}
|
||||
|
||||
void triggerCapture() {
|
||||
if (captureState == CaptureState::Idle) {
|
||||
captureState = CaptureState::Triggered;
|
||||
}
|
||||
}
|
||||
|
||||
void setOutputDir(const std::string& path, const std::string& prefix) {
|
||||
if (rdocAPI) {
|
||||
rdocAPI->SetCaptureFilePathTemplate((path + '\\' + prefix).c_str());
|
||||
}
|
||||
}
|
||||
} // namespace Renderdoc
|
||||
#endif
|
Loading…
Add table
Add a link
Reference in a new issue