Merge branch 'master' into metal2

This commit is contained in:
SamoZ256 2024-09-24 09:22:17 +02:00 committed by GitHub
commit 779e30e3e5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
48 changed files with 1691 additions and 242 deletions

View file

@ -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;

View file

@ -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)) {

View file

@ -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";
}

View 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;
}
}

View file

@ -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:

View file

@ -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) {

View file

@ -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
}

View file

@ -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;

View file

@ -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, "");
}

View file

@ -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;
}
}
}
}

View file

@ -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";

View file

@ -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;

View file

@ -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(

View file

@ -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();
}
}

View file

@ -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);
}
}

View file

@ -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);

View file

@ -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;

View file

@ -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
View 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