diff --git a/.github/gles.patch b/.github/gles.patch index 5a922fcf..548b243d 100644 --- a/.github/gles.patch +++ b/.github/gles.patch @@ -21,7 +21,7 @@ index 990e2f80..2e7842ac 100644 void main() { diff --git a/src/host_shaders/opengl_fragment_shader.frag b/src/host_shaders/opengl_fragment_shader.frag -index b9f9fe4c..f1cf286f 100644 +index 9f07df0b..96a35afa 100644 --- a/src/host_shaders/opengl_fragment_shader.frag +++ b/src/host_shaders/opengl_fragment_shader.frag @@ -1,4 +1,5 @@ @@ -31,6 +31,17 @@ index b9f9fe4c..f1cf286f 100644 in vec4 v_quaternion; in vec4 v_colour; +@@ -41,8 +42,8 @@ vec3 normal; + const uint samplerEnabledBitfields[2] = uint[2](0x7170e645u, 0x7f013fefu); + + bool isSamplerEnabled(uint environment_id, uint lut_id) { +- uint index = 7 * environment_id + lut_id; +- uint arrayIndex = (index >> 5); ++ uint index = 7u * environment_id + lut_id; ++ uint arrayIndex = (index >> 5u); + return (samplerEnabledBitfields[arrayIndex] & (1u << (index & 31u))) != 0u; + } + @@ -166,11 +167,17 @@ float lutLookup(uint lut, int index) { return texelFetch(u_tex_luts, ivec2(index, int(lut)), 0).r; } @@ -50,6 +61,15 @@ index b9f9fe4c..f1cf286f 100644 } // Convert an arbitrary-width floating point literal to an f32 +@@ -201,7 +208,7 @@ float lightLutLookup(uint environment_id, uint lut_id, uint light_id, vec3 light + // These are the spotlight attenuation LUTs + bit_in_config1 = 8 + int(light_id & 7u); + lut_index = 8u + light_id; +- } else if (lut_id <= 6) { ++ } else if (lut_id <= 6u) { + bit_in_config1 = 16 + int(lut_id); + lut_index = lut_id; + } else { @@ -210,16 +217,16 @@ float lightLutLookup(uint environment_id, uint lut_id, uint light_id, vec3 light bool current_sampler_enabled = isSamplerEnabled(environment_id, lut_id); // 7 luts per environment @@ -70,19 +90,16 @@ index b9f9fe4c..f1cf286f 100644 switch (input_id) { case 0u: { delta = dot(normal, normalize(half_vector)); -@@ -241,11 +248,11 @@ float lightLutLookup(uint environment_id, uint lut_id, uint light_id, vec3 light - int GPUREG_LIGHTi_SPOTDIR_LOW = int(readPicaReg(0x0146u + (light_id << 4u))); - int GPUREG_LIGHTi_SPOTDIR_HIGH = int(readPicaReg(0x0147u + (light_id << 4u))); +@@ -243,9 +250,9 @@ float lightLutLookup(uint environment_id, uint lut_id, uint light_id, vec3 light -- // Sign extend them. Normally bitfieldExtract would do that but it's missing on some versions -+ // Sign extend them. Normally bitfieldExtractCompat would do that but it's missing on some versions + // Sign extend them. Normally bitfieldExtract would do that but it's missing on some versions // of GLSL so we do it manually - int se_x = bitfieldExtract(GPUREG_LIGHTi_SPOTDIR_LOW, 0, 13); - int se_y = bitfieldExtract(GPUREG_LIGHTi_SPOTDIR_LOW, 16, 13); - int se_z = bitfieldExtract(GPUREG_LIGHTi_SPOTDIR_HIGH, 0, 13); -+ int se_x = bitfieldExtractCompat(GPUREG_LIGHTi_SPOTDIR_LOW, 0, 13); -+ int se_y = bitfieldExtractCompat(GPUREG_LIGHTi_SPOTDIR_LOW, 16, 13); -+ int se_z = bitfieldExtractCompat(GPUREG_LIGHTi_SPOTDIR_HIGH, 0, 13); ++ int se_x = int(bitfieldExtractCompat(uint(GPUREG_LIGHTi_SPOTDIR_LOW), 0, 13)); ++ int se_y = int(bitfieldExtractCompat(uint(GPUREG_LIGHTi_SPOTDIR_LOW), 16, 13)); ++ int se_z = int(bitfieldExtractCompat(uint(GPUREG_LIGHTi_SPOTDIR_HIGH), 0, 13)); if ((se_x & 0x1000) == 0x1000) se_x |= 0xffffe000; if ((se_y & 0x1000) == 0x1000) se_y |= 0xffffe000; @@ -225,10 +242,10 @@ index 057f9a88..dc735ced 100644 v_quaternion = a_quaternion; } diff --git a/third_party/opengl/opengl.hpp b/third_party/opengl/opengl.hpp -index 4a08650a..21af37e3 100644 +index 607815fa..cbfcc096 100644 --- a/third_party/opengl/opengl.hpp +++ b/third_party/opengl/opengl.hpp -@@ -583,22 +583,22 @@ namespace OpenGL { +@@ -602,22 +602,22 @@ namespace OpenGL { static void disableScissor() { glDisable(GL_SCISSOR_TEST); } static void enableBlend() { glEnable(GL_BLEND); } static void disableBlend() { glDisable(GL_BLEND); } diff --git a/.github/workflows/HTTP_Build.yml b/.github/workflows/HTTP_Build.yml index 7bfe9c7f..0bdaa4f7 100644 --- a/.github/workflows/HTTP_Build.yml +++ b/.github/workflows/HTTP_Build.yml @@ -19,7 +19,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Fetch submodules run: git submodule update --init --recursive diff --git a/.github/workflows/Hydra_Build.yml b/.github/workflows/Hydra_Build.yml index a269e839..e2c2004b 100644 --- a/.github/workflows/Hydra_Build.yml +++ b/.github/workflows/Hydra_Build.yml @@ -15,7 +15,7 @@ jobs: runs-on: windows-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Fetch submodules run: git submodule update --init --recursive @@ -58,7 +58,7 @@ jobs: runs-on: macos-13 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Fetch submodules run: git submodule update --init --recursive @@ -101,7 +101,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Fetch submodules run: git submodule update --init --recursive @@ -154,7 +154,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Fetch submodules run: git submodule update --init --recursive diff --git a/.github/workflows/Linux_AppImage_Build.yml b/.github/workflows/Linux_AppImage_Build.yml index 7d198b9c..f32a7d38 100644 --- a/.github/workflows/Linux_AppImage_Build.yml +++ b/.github/workflows/Linux_AppImage_Build.yml @@ -19,7 +19,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Fetch submodules run: git submodule update --init --recursive @@ -52,7 +52,7 @@ jobs: run: ./.github/linux-appimage.sh - name: Upload executable - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: Linux executable path: './Alber-x86_64.AppImage' diff --git a/.github/workflows/Linux_Build.yml b/.github/workflows/Linux_Build.yml index 78e5cc5a..9cb05303 100644 --- a/.github/workflows/Linux_Build.yml +++ b/.github/workflows/Linux_Build.yml @@ -19,7 +19,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Fetch submodules run: git submodule update --init --recursive @@ -49,7 +49,7 @@ jobs: run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} - name: Upload executable - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: Linux executable path: './build/Alber' diff --git a/.github/workflows/MacOS_Build.yml b/.github/workflows/MacOS_Build.yml index 912c8568..76b75bd4 100644 --- a/.github/workflows/MacOS_Build.yml +++ b/.github/workflows/MacOS_Build.yml @@ -19,7 +19,7 @@ jobs: runs-on: macos-13 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Fetch submodules run: git submodule update --init --recursive @@ -40,7 +40,7 @@ jobs: run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} - name: Install bundle dependencies - run: brew install --overwrite python@3.12 && brew install dylibbundler imagemagick + run: brew install dylibbundler imagemagick - name: Run bundle script run: ./.github/mac-bundle.sh @@ -52,7 +52,7 @@ jobs: run: zip -r Alber Alber.app - name: Upload MacOS App - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: MacOS Alber App Bundle path: 'Alber.zip' diff --git a/.github/workflows/Qt_Build.yml b/.github/workflows/Qt_Build.yml index 40141fb1..d3a09866 100644 --- a/.github/workflows/Qt_Build.yml +++ b/.github/workflows/Qt_Build.yml @@ -15,7 +15,7 @@ jobs: runs-on: windows-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Fetch submodules run: git submodule update --init --recursive @@ -45,7 +45,7 @@ jobs: windeployqt --dir upload upload/Alber.exe - name: Upload executable - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: Windows executable path: upload @@ -54,7 +54,7 @@ jobs: runs-on: macos-13 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Fetch submodules run: git submodule update --init --recursive @@ -67,7 +67,7 @@ jobs: - name: Install bundle dependencies run: | - brew install --overwrite python@3.12 && brew install dylibbundler imagemagick + brew install dylibbundler imagemagick - name: Install qt run: brew install qt && which macdeployqt @@ -90,7 +90,7 @@ jobs: run: zip -r Alber Alber.app - name: Upload MacOS App - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: MacOS Alber App Bundle path: 'Alber.zip' @@ -99,7 +99,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Fetch submodules run: git submodule update --init --recursive @@ -135,7 +135,7 @@ jobs: ./.github/linux-appimage-qt.sh - name: Upload executable - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: Linux executable path: './Alber-x86_64.AppImage' diff --git a/.github/workflows/Windows_Build.yml b/.github/workflows/Windows_Build.yml index a06889eb..5497c3ef 100644 --- a/.github/workflows/Windows_Build.yml +++ b/.github/workflows/Windows_Build.yml @@ -19,7 +19,7 @@ jobs: runs-on: windows-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Fetch submodules run: git submodule update --init --recursive @@ -40,7 +40,7 @@ jobs: run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} - name: Upload executable - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: Windows executable path: './build/${{ env.BUILD_TYPE }}/Alber.exe' diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 00000000..740ccf0c --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,130 @@ +# DESCRIPTION: GitLab CI/CD for libRetro (NOT FOR GitLab-proper) + +############################################################################## +################################# BOILERPLATE ################################ +############################################################################## + +# Core definitions +.core-defs: + variables: + GIT_SUBMODULE_STRATEGY: recursive + CORENAME: panda3ds + CORE_ARGS: -DBUILD_LIBRETRO_CORE=ON -DENABLE_USER_BUILD=ON -DENABLE_VULKAN=OFF -DENABLE_LUAJIT=OFF -DENABLE_DISCORD_RPC=OFF + +# Inclusion templates, required for the build to work + +include: + ################################## DESKTOPS ################################ + # Linux + - project: 'libretro-infrastructure/ci-templates' + file: '/linux-cmake.yml' + + # Windows + - project: 'libretro-infrastructure/ci-templates' + file: '/windows-cmake-mingw.yml' + + # MacOS + - project: 'libretro-infrastructure/ci-templates' + file: 'osx-cmake-x86.yml' + + # MacOS + - project: 'libretro-infrastructure/ci-templates' + file: 'osx-cmake-arm64.yml' + + ################################## CELLULAR ################################ + # Android + - project: 'libretro-infrastructure/ci-templates' + file: '/android-cmake.yml' + + # iOS + - project: 'libretro-infrastructure/ci-templates' + file: '/ios-cmake.yml' + +# Stages for building +stages: + - build-prepare + - build-static + - build-shared + +############################################################################## +#################################### STAGES ################################## +############################################################################## +# +################################### DESKTOPS ################################# +# Linux 64-bit +libretro-build-linux-x64: + image: $CI_SERVER_HOST:5050/libretro-infrastructure/libretro-build-amd64-ubuntu:latest + before_script: + - export NUMPROC=$(($(nproc)/5)) + - sudo apt-get update -qy + - sudo apt-get install -qy software-properties-common + - sudo add-apt-repository -y ppa:savoury1/build-tools + - sudo add-apt-repository -y ppa:savoury1/gcc-defaults-12 + - sudo apt-get update -qy + - sudo apt-get install -qy cmake gcc-12 g++-12 + variables: + CC: /usr/bin/gcc-12 + CXX: /usr/bin/g++-12 + extends: + - .libretro-linux-cmake-x86_64 + - .core-defs + +# Windows 64-bit +libretro-build-windows-x64: + extends: + - .libretro-windows-cmake-x86_64 + - .core-defs + +# MacOS 64-bit +libretro-build-osx-x64: + tags: + - mac-apple-silicon + variables: + CORE_ARGS: -DBUILD_LIBRETRO_CORE=ON -DENABLE_USER_BUILD=ON -DENABLE_VULKAN=OFF -DENABLE_LUAJIT=OFF -DENABLE_DISCORD_RPC=OFF -DCMAKE_OSX_ARCHITECTURES=x86_64 -DCRYPTOPP_AMD64=1 + extends: + - .libretro-osx-cmake-x86 + - .core-defs + +# MacOS arm 64-bit +libretro-build-osx-arm64: + tags: + - mac-apple-silicon + extends: + - .libretro-osx-cmake-arm64 + - .core-defs + +################################### CELLULAR ################################# +# Android ARMv7a +#android-armeabi-v7a: +# extends: +# - .libretro-android-cmake-armeabi-v7a +# - .core-defs + +# Android ARMv8a +# android-arm64-v8a: +# extends: +# - .libretro-android-cmake-arm64-v8a +# - .core-defs + +# Android 64-bit x86 +# android-x86_64: +# extends: +# - .libretro-android-cmake-x86_64 +# - .core-defs + +# Android 32-bit x86 +# android-x86: +# extends: +# - .libretro-android-cmake-x86 +# - .core-defs + +# iOS +# libretro-build-ios-arm64: +# extends: +# - .libretro-ios-cmake-arm64 +# - .core-defs +# variables: +# CORE_ARGS: -DBUILD_LIBRETRO_CORE=ON -DBUILD_PLAY=OFF -DENABLE_AMAZON_S3=off -DBUILD_TESTS=OFF -DCMAKE_TOOLCHAIN_FILE=deps/Dependencies/cmake-ios/ios.cmake -DTARGET_IOS=ON +# LIBNAME: ${CORENAME}_libretro_ios.dylib + +################################### CONSOLES ################################# diff --git a/.gitmodules b/.gitmodules index 656e1f41..97bc129c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -76,3 +76,6 @@ [submodule "third_party/metal-cpp"] path = third_party/metal-cpp url = https://github.com/Panda3DS-emu/metal-cpp +[submodule "third_party/fdk-aac"] + path = third_party/fdk-aac + url = https://github.com/Panda3DS-emu/fdk-aac/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 4fd12174..fac11cbc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,6 +19,7 @@ endif() project(Alber) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) +list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake") if(APPLE) enable_language(OBJC) @@ -32,6 +33,12 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-interference-size") endif() +if(ANDROID) + set(DEFAULT_OPENGL_PROFILE OpenGLES) +else() + set(DEFAULT_OPENGL_PROFILE OpenGL) +endif() + option(DISABLE_PANIC_DEV "Make a build with fewer and less intrusive asserts" ON) option(GPU_DEBUG_INFO "Enable additional GPU debugging info" OFF) option(ENABLE_OPENGL "Enable OpenGL rendering backend" ON) @@ -44,8 +51,18 @@ option(ENABLE_HTTP_SERVER "Enable HTTP server. Used for Discord bot support" OFF option(ENABLE_DISCORD_RPC "Compile with Discord RPC support (disabled by default)" ON) option(ENABLE_LUAJIT "Enable scripting with the Lua programming language" ON) option(ENABLE_QT_GUI "Enable the Qt GUI. If not selected then the emulator uses a minimal SDL-based UI instead" OFF) +option(ENABLE_GIT_VERSIONING "Enables querying git for the emulator version" ON) option(BUILD_HYDRA_CORE "Build a Hydra core" OFF) option(BUILD_LIBRETRO_CORE "Build a Libretro core" OFF) +option(ENABLE_RENDERDOC_API "Build with support for Renderdoc's capture API for graphics debugging" ON) + +set(OPENGL_PROFILE ${DEFAULT_OPENGL_PROFILE} CACHE STRING "OpenGL profile to use if OpenGL is enabled. Valid values are 'OpenGL' and 'OpenGLES'.") +set_property(CACHE OPENGL_PROFILE PROPERTY STRINGS OpenGL OpenGLES) + +if(ENABLE_OPENGL AND (OPENGL_PROFILE STREQUAL "OpenGLES")) + message(STATUS "Building with OpenGLES support") + add_compile_definitions(USING_GLES) +endif() if(BUILD_HYDRA_CORE) set(CMAKE_POSITION_INDEPENDENT_CODE ON) @@ -61,6 +78,37 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC" AND ENABLE_USER_BUILD) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /GS-") endif() +# Generate versioning files +find_package(Git) +set(PANDA3DS_VERSION "0.8") + +if(NOT EXISTS ${CMAKE_BINARY_DIR}/include/version.hpp.in) + file(WRITE ${CMAKE_BINARY_DIR}/include/version.hpp.in "#define PANDA3DS_VERSION \"\${PANDA3DS_VERSION}\"") +endif() + +if(GIT_FOUND AND ENABLE_GIT_VERSIONING) + execute_process( + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} COMMAND ${GIT_EXECUTABLE} describe --tags --abbrev=0 + OUTPUT_VARIABLE PANDA3DS_VERSION OUTPUT_STRIP_TRAILING_WHITESPACE + ) + execute_process( + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} COMMAND ${GIT_EXECUTABLE} describe --tags + OUTPUT_VARIABLE git_version_tag OUTPUT_STRIP_TRAILING_WHITESPACE + ) + if(NOT PANDA3DS_VERSION STREQUAL git_version_tag) + execute_process( + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} COMMAND ${GIT_EXECUTABLE} describe --always --abbrev=7 + OUTPUT_VARIABLE git_version_rev OUTPUT_STRIP_TRAILING_WHITESPACE + ) + set(PANDA3DS_VERSION "${PANDA3DS_VERSION}.${git_version_rev}") + unset(git_version_rev) + endif() + string(REGEX REPLACE "^v" "" PANDA3DS_VERSION "${PANDA3DS_VERSION}") + unset(git_version_tag) +endif() +configure_file(${CMAKE_BINARY_DIR}/include/version.hpp.in ${CMAKE_BINARY_DIR}/include/version.hpp) +include_directories(${CMAKE_BINARY_DIR}/include/) + add_library(AlberCore STATIC) include_directories(${PROJECT_SOURCE_DIR}/include/) @@ -103,6 +151,7 @@ add_subdirectory(third_party/toml11) include_directories(${SDL2_INCLUDE_DIR}) include_directories(third_party/toml11) include_directories(third_party/glm) +include_directories(third_party/renderdoc) add_subdirectory(third_party/cmrc) @@ -161,6 +210,11 @@ else() set(HOST_ARM64 FALSE) endif() +if(ENABLE_RENDERDOC_API) + find_package(RenderDoc 1.6.0 MODULE REQUIRED) + add_compile_definitions(PANDA3DS_ENABLE_RENDERDOC) +endif() + if(HOST_X64 OR HOST_ARM64) set(DYNARMIC_TESTS OFF) #set(DYNARMIC_NO_BUNDLED_FMT ON) @@ -172,6 +226,7 @@ else() endif() add_subdirectory(third_party/teakra EXCLUDE_FROM_ALL) +add_subdirectory(third_party/fdk-aac) set(CAPSTONE_ARCHITECTURE_DEFAULT OFF) set(CAPSTONE_ARM_SUPPORT ON) @@ -183,7 +238,7 @@ set(SOURCE_FILES src/emulator.cpp src/io_file.cpp src/config.cpp src/core/CPU/cpu_dynarmic.cpp src/core/CPU/dynarmic_cycles.cpp src/core/memory.cpp src/renderer.cpp src/core/renderer_null/renderer_null.cpp src/http_server.cpp src/stb_image_write.c src/core/cheats.cpp src/core/action_replay.cpp - src/discord_rpc.cpp src/lua.cpp src/memory_mapped_file.cpp src/miniaudio.cpp + src/discord_rpc.cpp src/lua.cpp src/memory_mapped_file.cpp src/miniaudio.cpp src/renderdoc.cpp ) set(CRYPTO_SOURCE_FILES src/core/crypto/aes_engine.cpp) set(KERNEL_SOURCE_FILES src/core/kernel/kernel.cpp src/core/kernel/resource_limits.cpp @@ -222,7 +277,7 @@ set(APPLET_SOURCE_FILES src/core/applets/applet.cpp src/core/applets/mii_selecto src/core/applets/error_applet.cpp ) set(AUDIO_SOURCE_FILES src/core/audio/dsp_core.cpp src/core/audio/null_core.cpp src/core/audio/teakra_core.cpp - src/core/audio/miniaudio_device.cpp src/core/audio/hle_core.cpp + src/core/audio/miniaudio_device.cpp src/core/audio/hle_core.cpp src/core/audio/aac_decoder.cpp ) set(RENDERER_SW_SOURCE_FILES src/core/renderer_sw/renderer_sw.cpp) @@ -261,7 +316,7 @@ set(HEADER_FILES include/emulator.hpp include/helpers.hpp include/termcolor.hpp include/audio/miniaudio_device.hpp include/ring_buffer.hpp include/bitfield.hpp include/audio/dsp_shared_mem.hpp include/audio/hle_core.hpp include/capstone.hpp include/audio/aac.hpp include/PICA/pica_frag_config.hpp include/PICA/pica_frag_uniforms.hpp include/PICA/shader_gen_types.hpp include/PICA/shader_decompiler.hpp - include/sdl_gyro.hpp + include/sdl_sensors.hpp include/renderdoc.hpp include/audio/aac_decoder.hpp ) cmrc_add_resource_library( @@ -492,7 +547,7 @@ set(ALL_SOURCES ${SOURCE_FILES} ${FS_SOURCE_FILES} ${CRYPTO_SOURCE_FILES} ${KERN ${AUDIO_SOURCE_FILES} ${HEADER_FILES} ${FRONTEND_HEADER_FILES}) target_sources(AlberCore PRIVATE ${ALL_SOURCES}) -target_link_libraries(AlberCore PRIVATE dynarmic cryptopp glad resources_console_fonts teakra) +target_link_libraries(AlberCore PRIVATE dynarmic cryptopp glad resources_console_fonts teakra fdk-aac) target_link_libraries(AlberCore PUBLIC glad capstone) if(ENABLE_DISCORD_RPC AND NOT ANDROID) @@ -532,6 +587,9 @@ if(NOT BUILD_HYDRA_CORE AND NOT BUILD_LIBRETRO_CORE) if(NOT ENABLE_OPENGL) message(FATAL_ERROR "Qt frontend requires OpenGL") endif() + + option(GENERATE_QT_TRANSLATION "Generate Qt translation file" OFF) + set(QT_LANGUAGES docs/translations) set(FRONTEND_SOURCE_FILES src/panda_qt/main.cpp src/panda_qt/screen.cpp src/panda_qt/main_window.cpp src/panda_qt/about_window.cpp src/panda_qt/config_window.cpp src/panda_qt/zep.cpp src/panda_qt/text_editor.cpp src/panda_qt/cheats_window.cpp src/panda_qt/mappings.cpp @@ -569,6 +627,17 @@ if(NOT BUILD_HYDRA_CORE AND NOT BUILD_LIBRETRO_CORE) endif() endif() + # Generates an en.ts file for translations + # To update the file, use cmake --build --target Alber_lupdate + if(GENERATE_QT_TRANSLATION) + find_package(Qt6 REQUIRED COMPONENTS LinguistTools) + qt_add_lupdate(Alber TS_FILES ${QT_LANGUAGES}/en.ts + SOURCES ${FRONTEND_SOURCE_FILES} + INCLUDE_DIRECTORIES ${FRONTEND_HEADER_FILES} + NO_GLOBAL_TARGET + ) + endif() + qt_add_resources(AlberCore "app_images" PREFIX "/" FILES @@ -588,17 +657,17 @@ elseif(BUILD_HYDRA_CORE) target_link_libraries(Alber PUBLIC AlberCore) elseif(BUILD_LIBRETRO_CORE) include_directories(third_party/libretro/include) - add_library(Alber SHARED src/libretro_core.cpp) - target_link_libraries(Alber PUBLIC AlberCore) - - set_target_properties(Alber PROPERTIES - OUTPUT_NAME "panda3ds_libretro" - PREFIX "" - ) + add_library(panda3ds_libretro SHARED src/libretro_core.cpp) + target_link_libraries(panda3ds_libretro PUBLIC AlberCore) + set_target_properties(panda3ds_libretro PROPERTIES PREFIX "") endif() if(ENABLE_LTO OR ENABLE_USER_BUILD) - set_target_properties(Alber PROPERTIES INTERPROCEDURAL_OPTIMIZATION TRUE) + if (NOT BUILD_LIBRETRO_CORE) + set_target_properties(Alber PROPERTIES INTERPROCEDURAL_OPTIMIZATION TRUE) + else() + set_target_properties(panda3ds_libretro PROPERTIES INTERPROCEDURAL_OPTIMIZATION TRUE) + endif() endif() if(ENABLE_TESTS) diff --git a/cmake/FindRenderDoc.cmake b/cmake/FindRenderDoc.cmake new file mode 100644 index 00000000..c00a0888 --- /dev/null +++ b/cmake/FindRenderDoc.cmake @@ -0,0 +1,25 @@ +# SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +# SPDX-License-Identifier: GPL-2.0-or-later + +set(RENDERDOC_INCLUDE_DIR third_party/renderdoc) + +if (RENDERDOC_INCLUDE_DIR AND EXISTS "${RENDERDOC_INCLUDE_DIR}/renderdoc_app.h") + file(STRINGS "${RENDERDOC_INCLUDE_DIR}/renderdoc_app.h" RENDERDOC_VERSION_LINE REGEX "typedef struct RENDERDOC_API") + string(REGEX REPLACE ".*typedef struct RENDERDOC_API_([0-9]+)_([0-9]+)_([0-9]+).*" "\\1.\\2.\\3" RENDERDOC_VERSION "${RENDERDOC_VERSION_LINE}") + unset(RENDERDOC_VERSION_LINE) +endif() + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(RenderDoc + REQUIRED_VARS RENDERDOC_INCLUDE_DIR + VERSION_VAR RENDERDOC_VERSION +) + +if (RenderDoc_FOUND AND NOT TARGET RenderDoc::API) + add_library(RenderDoc::API INTERFACE IMPORTED) + set_target_properties(RenderDoc::API PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${RENDERDOC_INCLUDE_DIR}" + ) +endif() + +mark_as_advanced(RENDERDOC_INCLUDE_DIR) \ No newline at end of file diff --git a/docs/3ds/accelerometer_readings/readings_flat_1.png b/docs/3ds/accelerometer_readings/readings_flat_1.png new file mode 100644 index 00000000..b7a425fc Binary files /dev/null and b/docs/3ds/accelerometer_readings/readings_flat_1.png differ diff --git a/docs/3ds/accelerometer_readings/readings_flat_2.png b/docs/3ds/accelerometer_readings/readings_flat_2.png new file mode 100644 index 00000000..b23c1102 Binary files /dev/null and b/docs/3ds/accelerometer_readings/readings_flat_2.png differ diff --git a/docs/3ds/accelerometer_readings/readings_shaking_1.png b/docs/3ds/accelerometer_readings/readings_shaking_1.png new file mode 100644 index 00000000..91279149 Binary files /dev/null and b/docs/3ds/accelerometer_readings/readings_shaking_1.png differ diff --git a/docs/3ds/accelerometer_readings/readings_shaking_2.png b/docs/3ds/accelerometer_readings/readings_shaking_2.png new file mode 100644 index 00000000..551e7d2e Binary files /dev/null and b/docs/3ds/accelerometer_readings/readings_shaking_2.png differ diff --git a/include/audio/aac.hpp b/include/audio/aac.hpp index afd2dbba..389ecc04 100644 --- a/include/audio/aac.hpp +++ b/include/audio/aac.hpp @@ -54,6 +54,15 @@ namespace Audio::AAC { u32_le sampleCount; }; + struct DecodeRequest { + u32_le address; // Address of input AAC stream + u32_le size; // Size of input AAC stream + u32_le destAddrLeft; // Output address for left channel samples + u32_le destAddrRight; // Output address for right channel samples + u32_le unknown1; + u32_le unknown2; + }; + struct Message { u16_le mode = Mode::None; // Encode or decode AAC? u16_le command = Command::Init; @@ -62,7 +71,9 @@ namespace Audio::AAC { // Info on the AAC request union { std::array commandData{}; + DecodeResponse decodeResponse; + DecodeRequest decodeRequest; }; }; diff --git a/include/audio/aac_decoder.hpp b/include/audio/aac_decoder.hpp new file mode 100644 index 00000000..6538bce9 --- /dev/null +++ b/include/audio/aac_decoder.hpp @@ -0,0 +1,24 @@ +#pragma once +#include + +#include "audio/aac.hpp" +#include "helpers.hpp" + +struct AAC_DECODER_INSTANCE; + +namespace Audio::AAC { + class Decoder { + using DecoderHandle = AAC_DECODER_INSTANCE*; + using PaddrCallback = std::function; + + DecoderHandle decoderHandle = nullptr; + + bool isInitialized() { return decoderHandle != nullptr; } + void initialize(); + + public: + // Decode function. Takes in a reference to the AAC response & request, and a callback for paddr -> pointer conversions + void decode(AAC::Message& response, const AAC::Message& request, PaddrCallback paddrCallback); + ~Decoder(); + }; +} // namespace Audio::AAC \ No newline at end of file diff --git a/include/audio/hle_core.hpp b/include/audio/hle_core.hpp index c0e0896f..c36f0500 100644 --- a/include/audio/hle_core.hpp +++ b/include/audio/hle_core.hpp @@ -2,10 +2,12 @@ #include #include #include +#include #include #include #include "audio/aac.hpp" +#include "audio/aac_decoder.hpp" #include "audio/dsp_core.hpp" #include "audio/dsp_shared_mem.hpp" #include "memory.hpp" @@ -33,8 +35,8 @@ namespace Audio { SampleFormat format; SourceType sourceType; - bool fromQueue = false; // Is this buffer from the buffer queue or an embedded buffer? - bool hasPlayedOnce = false; // Has the buffer been played at least once before? + bool fromQueue = false; // Is this buffer from the buffer queue or an embedded buffer? + bool hasPlayedOnce = false; // Has the buffer been played at least once before? bool operator<(const Buffer& other) const { // Lower ID = Higher priority @@ -129,6 +131,8 @@ namespace Audio { std::array sources; // DSP voices Audio::HLE::DspMemory dspRam; + std::unique_ptr aacDecoder; + void resetAudioPipe(); bool loaded = false; // Have we loaded a component? diff --git a/include/config.hpp b/include/config.hpp index 52be1af7..459f0907 100644 --- a/include/config.hpp +++ b/include/config.hpp @@ -39,6 +39,10 @@ struct EmulatorConfig { bool audioEnabled = false; bool vsyncEnabled = true; + + bool enableRenderdoc = false; + bool printAppVersion = true; + bool appVersionOnWindow = false; bool chargerPlugged = true; // Default to 3% battery to make users suffer diff --git a/include/emulator.hpp b/include/emulator.hpp index de04648e..a668d6c1 100644 --- a/include/emulator.hpp +++ b/include/emulator.hpp @@ -135,4 +135,7 @@ class Emulator { std::filesystem::path getAppDataRoot(); std::span getSMDH(); + + private: + void loadRenderdoc(); }; diff --git a/include/panda_qt/cheats_window.hpp b/include/panda_qt/cheats_window.hpp index c82b2bd8..93228d5e 100644 --- a/include/panda_qt/cheats_window.hpp +++ b/include/panda_qt/cheats_window.hpp @@ -1,6 +1,13 @@ #pragma once #include +#include +#include +#include +#include +#include +#include +#include #include #include #include @@ -24,3 +31,60 @@ class CheatsWindow final : public QWidget { std::filesystem::path cheatPath; Emulator* emu; }; + +struct CheatMetadata { + u32 handle = Cheats::badCheatHandle; + std::string name = "New cheat"; + std::string code; + bool enabled = true; +}; + +class CheatEntryWidget : public QWidget { + Q_OBJECT + + 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 { + Q_OBJECT + + public: + CheatEditDialog(Emulator* emu, CheatEntryWidget& cheatEntry); + + void accepted(); + void rejected(); + + private: + Emulator* emu; + CheatEntryWidget& cheatEntry; + QTextEdit* codeEdit; + QLineEdit* nameEdit; +}; \ No newline at end of file diff --git a/include/panda_qt/main_window.hpp b/include/panda_qt/main_window.hpp index fc756b9f..eb6b30e0 100644 --- a/include/panda_qt/main_window.hpp +++ b/include/panda_qt/main_window.hpp @@ -141,7 +141,7 @@ class MainWindow : public QMainWindow { MainWindow(QApplication* app, QWidget* parent = nullptr); ~MainWindow(); - void closeEvent(QCloseEvent *event) override; + void closeEvent(QCloseEvent* event) override; void keyPressEvent(QKeyEvent* event) override; void keyReleaseEvent(QKeyEvent* event) override; void mousePressEvent(QMouseEvent* event) override; diff --git a/include/renderdoc.hpp b/include/renderdoc.hpp new file mode 100644 index 00000000..94a0f494 --- /dev/null +++ b/include/renderdoc.hpp @@ -0,0 +1,38 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once +#include + +#include "helpers.hpp" + +#ifdef PANDA3DS_ENABLE_RENDERDOC +namespace Renderdoc { + // Loads renderdoc dynamic library module. + void loadRenderdoc(); + + // Begins a capture if a renderdoc instance is attached. + void startCapture(); + + // Ends current renderdoc capture. + void endCapture(); + + // Triggers capturing process. + void triggerCapture(); + + // Sets output directory for captures + void setOutputDir(const std::string& path, const std::string& prefix); + + // Returns whether we've compiled with Renderdoc support + static constexpr bool isSupported() { return true; } +} // namespace Renderdoc +#else +namespace Renderdoc { + static void loadRenderdoc() {} + static void startCapture() { Helpers::panic("Tried to start a Renderdoc capture while support for renderdoc is disabled") } + static void endCapture() { Helpers::panic("Tried to end a Renderdoc capture while support for renderdoc is disabled") } + static void triggerCapture() { Helpers::panic("Tried to trigger a Renderdoc capture while support for renderdoc is disabled") } + static void setOutputDir(const std::string& path, const std::string& prefix) {} + static constexpr bool isSupported() { return false; } +} // namespace Renderdoc +#endif \ No newline at end of file diff --git a/include/sdl_gyro.hpp b/include/sdl_gyro.hpp deleted file mode 100644 index e2df18df..00000000 --- a/include/sdl_gyro.hpp +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once - -#include -#include - -#include "services/hid.hpp" - -namespace Gyro::SDL { - // Convert the rotation data we get from SDL sensor events to rotation data we can feed right to HID - // Returns [pitch, roll, yaw] - static glm::vec3 convertRotation(glm::vec3 rotation) { - // Convert the rotation from rad/s to deg/s and scale by the gyroscope coefficient in HID - constexpr float scale = 180.f / std::numbers::pi * HIDService::gyroscopeCoeff; - // The axes are also inverted, so invert scale before the multiplication. - return rotation * -scale; - } -} // namespace Gyro::SDL \ No newline at end of file diff --git a/include/sdl_sensors.hpp b/include/sdl_sensors.hpp new file mode 100644 index 00000000..6de040ec --- /dev/null +++ b/include/sdl_sensors.hpp @@ -0,0 +1,32 @@ +#pragma once + +#include +#include +#include + +#include "helpers.hpp" +#include "services/hid.hpp" + +namespace Sensors::SDL { + // Convert the rotation data we get from SDL sensor events to rotation data we can feed right to HID + // Returns [pitch, roll, yaw] + static glm::vec3 convertRotation(glm::vec3 rotation) { + // Convert the rotation from rad/s to deg/s and scale by the gyroscope coefficient in HID + constexpr float scale = 180.f / std::numbers::pi * HIDService::gyroscopeCoeff; + // The axes are also inverted, so invert scale before the multiplication. + return rotation * -scale; + } + + static glm::vec3 convertAcceleration(float* data) { + // Set our cap to ~9 m/s^2. The 3DS sensors cap at -930 and +930, so values above this value will get clamped to 930 + // At rest (3DS laid flat on table), hardware reads around ~0 for x and z axis, and around ~480 for y axis due to gravity. + // This code tries to mimic this approximately, with offsets based on measurements from my DualShock 4. + static constexpr float accelMax = 9.f; + + s16 x = std::clamp(s16(data[0] / accelMax * 930.f), -930, +930); + s16 y = std::clamp(s16(data[1] / (SDL_STANDARD_GRAVITY * accelMax) * 930.f - 350.f), -930, +930); + s16 z = std::clamp(s16((data[2] - 2.1f) / accelMax * 930.f), -930, +930); + + return glm::vec3(x, y, z); + } +} // namespace Sensors::SDL diff --git a/include/services/hid.hpp b/include/services/hid.hpp index bce2cc1b..a0eefb1c 100644 --- a/include/services/hid.hpp +++ b/include/services/hid.hpp @@ -56,6 +56,7 @@ class HIDService { s16 circlePadX, circlePadY; // Circlepad state s16 touchScreenX, touchScreenY; // Touchscreen state s16 roll, pitch, yaw; // Gyroscope state + s16 accelX, accelY, accelZ; // Accelerometer state bool accelerometerEnabled; bool eventsInitialized; @@ -87,6 +88,11 @@ class HIDService { *(T*)&sharedMem[offset] = value; } + template + T* getSharedMemPointer(size_t offset) { + return (T*)&sharedMem[offset]; + } + public: static constexpr float gyroscopeCoeff = 14.375f; // Same as retail 3DS @@ -130,6 +136,12 @@ class HIDService { void setPitch(s16 value) { pitch = value; } void setYaw(s16 value) { yaw = value; } + void setAccel(s16 x, s16 y, s16 z) { + accelX = x; + accelY = y; + accelZ = z; + } + void updateInputs(u64 currentTimestamp); void setSharedMem(u8* ptr) { diff --git a/src/config.cpp b/src/config.cpp index dae5a0ab..70f2189c 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -41,6 +41,9 @@ void EmulatorConfig::load() { discordRpcEnabled = toml::find_or(general, "EnableDiscordRPC", false); usePortableBuild = toml::find_or(general, "UsePortableBuild", false); defaultRomPath = toml::find_or(general, "DefaultRomPath", ""); + + printAppVersion = toml::find_or(general, "PrintAppVersion", true); + appVersionOnWindow = toml::find_or(general, "AppVersionOnWindow", false); } } @@ -67,6 +70,7 @@ void EmulatorConfig::load() { forceShadergenForLights = toml::find_or(gpu, "ForceShadergenForLighting", true); lightShadergenThreshold = toml::find_or(gpu, "ShadergenLightThreshold", 1); + enableRenderdoc = toml::find_or(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; diff --git a/src/core/PICA/gpu.cpp b/src/core/PICA/gpu.cpp index 95001b33..71d5e2e7 100644 --- a/src/core/PICA/gpu.cpp +++ b/src/core/PICA/gpu.cpp @@ -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(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)) { diff --git a/src/core/PICA/shader_gen_glsl.cpp b/src/core/PICA/shader_gen_glsl.cpp index 60887d56..69f74930 100644 --- a/src/core/PICA/shader_gen_glsl.cpp +++ b/src/core/PICA/shader_gen_glsl.cpp @@ -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"; } diff --git a/src/core/audio/aac_decoder.cpp b/src/core/audio/aac_decoder.cpp new file mode 100644 index 00000000..281539d8 --- /dev/null +++ b/src/core/audio/aac_decoder.cpp @@ -0,0 +1,139 @@ +#include "audio/aac_decoder.hpp" + +#include + +#include +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 frame; + std::array, 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; + } +} \ No newline at end of file diff --git a/src/core/audio/hle_core.cpp b/src/core/audio/hle_core.cpp index 83271a43..b4f9ab02 100644 --- a/src/core/audio/hle_core.cpp +++ b/src/core/audio/hle_core.cpp @@ -6,6 +6,7 @@ #include #include +#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(paddr); }); break; case AAC::Command::Init: diff --git a/src/core/audio/miniaudio_device.cpp b/src/core/audio/miniaudio_device.cpp index fa36cb84..dd5cfa85 100644 --- a/src/core/audio/miniaudio_device.cpp +++ b/src/core/audio/miniaudio_device.cpp @@ -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(device->pUserData); s16* output = reinterpret_cast(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) { diff --git a/src/core/renderer_gl/renderer_gl.cpp b/src/core/renderer_gl/renderer_gl.cpp index f8fc31e7..5146370a 100644 --- a/src/core/renderer_gl/renderer_gl.cpp +++ b/src/core/renderer_gl/renderer_gl.cpp @@ -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 } diff --git a/src/core/services/hid.cpp b/src/core/services/hid.cpp index aa13096c..a7b9b13b 100644 --- a/src/core/services/hid.cpp +++ b/src/core/services/hid.cpp @@ -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(0x108, currentTick); // Write new tick count } writeSharedMem(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(0x120); + accelerometerDataRaw[0] = accelX; + accelerometerDataRaw[1] = accelY; + accelerometerDataRaw[2] = accelZ; + + // Accelerometer entry in entry table + s16* accelerometerData = getSharedMemPointer(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(0x158, currentTick); // Write new tick count } const size_t gyroEntryOffset = 0x178 + (nextGyroIndex * 6); // Offset in the array of 8 touchscreen entries - writeSharedMem(gyroEntryOffset, pitch); - writeSharedMem(gyroEntryOffset + 2, yaw); - writeSharedMem(gyroEntryOffset + 4, roll); + s16* gyroData = getSharedMemPointer(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; diff --git a/src/emulator.cpp b/src/emulator.cpp index fdf56a00..9b856425 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -1,11 +1,13 @@ #include "emulator.hpp" -#ifndef __ANDROID__ +#if !defined(__ANDROID__) && !defined(__LIBRETRO__) #include #endif #include +#include "renderdoc.hpp" + #ifdef _WIN32 #include @@ -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, ""); +} \ No newline at end of file diff --git a/src/host_shaders/opengl_fragment_shader.frag b/src/host_shaders/opengl_fragment_shader.frag index b9f9fe4c..9f07df0b 100644 --- a/src/host_shaders/opengl_fragment_shader.frag +++ b/src/host_shaders/opengl_fragment_shader.frag @@ -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; } } -} \ No newline at end of file +} diff --git a/src/hydra_core.cpp b/src/hydra_core.cpp index acbf30a8..078b8a6c 100644 --- a/src/hydra_core.cpp +++ b/src/hydra_core.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -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(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"; diff --git a/src/libretro_core.cpp b/src/libretro_core.cpp index b099067f..3f92cddd 100644 --- a/src/libretro_core.cpp +++ b/src/libretro_core.cpp @@ -4,16 +4,17 @@ #include +#include #include #include -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(GetGLProcAddress))) { + if (!gladLoadGLES2Loader(reinterpret_cast(getGLProcAddress))) { Helpers::panic("OpenGL ES init failed"); } #else - if (!gladLoadGLLoader(reinterpret_cast(GetGLProcAddress))) { + if (!gladLoadGLLoader(reinterpret_cast(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(); @@ -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; diff --git a/src/panda_qt/about_window.cpp b/src/panda_qt/about_window.cpp index 67767198..a388dad3 100644 --- a/src/panda_qt/about_window.cpp +++ b/src/panda_qt/about_window.cpp @@ -1,10 +1,13 @@ #include "panda_qt/about_window.hpp" +#include #include #include #include #include +#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"(

Panda3DS

+

v%VERSION_STRING%

+

%ABOUT_PANDA3DS%
%SUPPORT%
@@ -26,6 +31,7 @@ AboutWindow::AboutWindow(QWidget* parent) : QDialog(parent) { %AUTHORS%

)") + .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( diff --git a/src/panda_qt/cheats_window.cpp b/src/panda_qt/cheats_window.cpp index dbd251cc..cc2c94f6 100644 --- a/src/panda_qt/cheats_window.cpp +++ b/src/panda_qt/cheats_window.cpp @@ -1,15 +1,9 @@ #include "panda_qt/cheats_window.hpp" -#include -#include #include -#include -#include -#include -#include -#include -#include +#include #include +#include #include #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 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(parent); + mainWindow = static_cast(parent); QVBoxLayout* layout = new QVBoxLayout; layout->setContentsMargins(6, 6, 6, 6); @@ -265,4 +205,4 @@ void CheatsWindow::removeClicked() { CheatEntryWidget* entry = static_cast(cheatList->itemWidget(item)); entry->Remove(); -} +} \ No newline at end of file diff --git a/src/panda_qt/main_window.cpp b/src/panda_qt/main_window.cpp index 4c187bc2..e4352c20 100644 --- a/src/panda_qt/main_window.cpp +++ b/src/panda_qt/main_window.cpp @@ -7,9 +7,10 @@ #include #include +#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); + } } diff --git a/src/panda_qt/mappings.cpp b/src/panda_qt/mappings.cpp index 22741a73..d41b0a31 100644 --- a/src/panda_qt/mappings.cpp +++ b/src/panda_qt/mappings.cpp @@ -1,7 +1,7 @@ -#include "input_mappings.hpp" - #include +#include "input_mappings.hpp" + InputMappings InputMappings::defaultKeyboardMappings() { InputMappings mappings; mappings.setMapping(Qt::Key_L, HID::Keys::A); diff --git a/src/panda_qt/shader_editor.cpp b/src/panda_qt/shader_editor.cpp index 122d841f..4ca41e22 100644 --- a/src/panda_qt/shader_editor.cpp +++ b/src/panda_qt/shader_editor.cpp @@ -1,8 +1,9 @@ +#include "panda_qt/shader_editor.hpp" + #include #include #include "panda_qt/main_window.hpp" -#include "panda_qt/shader_editor.hpp" using namespace Zep; diff --git a/src/panda_sdl/frontend_sdl.cpp b/src/panda_sdl/frontend_sdl.cpp index 057a4858..39dcab00 100644 --- a/src/panda_sdl/frontend_sdl.cpp +++ b/src/panda_sdl/frontend_sdl.cpp @@ -2,7 +2,9 @@ #include -#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); + } } diff --git a/src/renderdoc.cpp b/src/renderdoc.cpp new file mode 100644 index 00000000..1de9c451 --- /dev/null +++ b/src/renderdoc.cpp @@ -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 + +#ifdef _WIN32 +#include +#else +#include +#endif + +#include +#include + +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 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(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(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 \ No newline at end of file diff --git a/third_party/fdk-aac b/third_party/fdk-aac new file mode 160000 index 00000000..5559136b --- /dev/null +++ b/third_party/fdk-aac @@ -0,0 +1 @@ +Subproject commit 5559136bb53ce38f6f07dac4f47674dd4f032d03 diff --git a/third_party/renderdoc/renderdoc_app.h b/third_party/renderdoc/renderdoc_app.h new file mode 100644 index 00000000..e73f1c90 --- /dev/null +++ b/third_party/renderdoc/renderdoc_app.h @@ -0,0 +1,721 @@ +/****************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2019-2024 Baldur Karlsson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + ******************************************************************************/ + +#pragma once + +////////////////////////////////////////////////////////////////////////////////////////////////// +// +// Documentation for the API is available at https://renderdoc.org/docs/in_application_api.html +// + +#if !defined(RENDERDOC_NO_STDINT) +#include +#endif + +#if defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) +#define RENDERDOC_CC __cdecl +#elif defined(__linux__) || defined(__FreeBSD__) +#define RENDERDOC_CC +#elif defined(__APPLE__) +#define RENDERDOC_CC +#else +#error "Unknown platform" +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +////////////////////////////////////////////////////////////////////////////////////////////////// +// Constants not used directly in below API + +// This is a GUID/magic value used for when applications pass a path where shader debug +// information can be found to match up with a stripped shader. +// the define can be used like so: const GUID RENDERDOC_ShaderDebugMagicValue = +// RENDERDOC_ShaderDebugMagicValue_value +#define RENDERDOC_ShaderDebugMagicValue_struct \ + { 0xeab25520, 0x6670, 0x4865, 0x84, 0x29, 0x6c, 0x8, 0x51, 0x54, 0x00, 0xff } + +// as an alternative when you want a byte array (assuming x86 endianness): +#define RENDERDOC_ShaderDebugMagicValue_bytearray \ + { 0x20, 0x55, 0xb2, 0xea, 0x70, 0x66, 0x65, 0x48, 0x84, 0x29, 0x6c, 0x8, 0x51, 0x54, 0x00, 0xff } + +// truncated version when only a uint64_t is available (e.g. Vulkan tags): +#define RENDERDOC_ShaderDebugMagicValue_truncated 0x48656670eab25520ULL + +////////////////////////////////////////////////////////////////////////////////////////////////// +// RenderDoc capture options +// + +typedef enum RENDERDOC_CaptureOption { + // Allow the application to enable vsync + // + // Default - enabled + // + // 1 - The application can enable or disable vsync at will + // 0 - vsync is force disabled + eRENDERDOC_Option_AllowVSync = 0, + + // Allow the application to enable fullscreen + // + // Default - enabled + // + // 1 - The application can enable or disable fullscreen at will + // 0 - fullscreen is force disabled + eRENDERDOC_Option_AllowFullscreen = 1, + + // Record API debugging events and messages + // + // Default - disabled + // + // 1 - Enable built-in API debugging features and records the results into + // the capture, which is matched up with events on replay + // 0 - no API debugging is forcibly enabled + eRENDERDOC_Option_APIValidation = 2, + eRENDERDOC_Option_DebugDeviceMode = 2, // deprecated name of this enum + + // Capture CPU callstacks for API events + // + // Default - disabled + // + // 1 - Enables capturing of callstacks + // 0 - no callstacks are captured + eRENDERDOC_Option_CaptureCallstacks = 3, + + // When capturing CPU callstacks, only capture them from actions. + // This option does nothing without the above option being enabled + // + // Default - disabled + // + // 1 - Only captures callstacks for actions. + // Ignored if CaptureCallstacks is disabled + // 0 - Callstacks, if enabled, are captured for every event. + eRENDERDOC_Option_CaptureCallstacksOnlyDraws = 4, + eRENDERDOC_Option_CaptureCallstacksOnlyActions = 4, + + // Specify a delay in seconds to wait for a debugger to attach, after + // creating or injecting into a process, before continuing to allow it to run. + // + // 0 indicates no delay, and the process will run immediately after injection + // + // Default - 0 seconds + // + eRENDERDOC_Option_DelayForDebugger = 5, + + // Verify buffer access. This includes checking the memory returned by a Map() call to + // detect any out-of-bounds modification, as well as initialising buffers with undefined contents + // to a marker value to catch use of uninitialised memory. + // + // NOTE: This option is only valid for OpenGL and D3D11. Explicit APIs such as D3D12 and Vulkan do + // not do the same kind of interception & checking and undefined contents are really undefined. + // + // Default - disabled + // + // 1 - Verify buffer access + // 0 - No verification is performed, and overwriting bounds may cause crashes or corruption in + // RenderDoc. + eRENDERDOC_Option_VerifyBufferAccess = 6, + + // The old name for eRENDERDOC_Option_VerifyBufferAccess was eRENDERDOC_Option_VerifyMapWrites. + // This option now controls the filling of uninitialised buffers with 0xdddddddd which was + // previously always enabled + eRENDERDOC_Option_VerifyMapWrites = eRENDERDOC_Option_VerifyBufferAccess, + + // Hooks any system API calls that create child processes, and injects + // RenderDoc into them recursively with the same options. + // + // Default - disabled + // + // 1 - Hooks into spawned child processes + // 0 - Child processes are not hooked by RenderDoc + eRENDERDOC_Option_HookIntoChildren = 7, + + // By default RenderDoc only includes resources in the final capture necessary + // for that frame, this allows you to override that behaviour. + // + // Default - disabled + // + // 1 - all live resources at the time of capture are included in the capture + // and available for inspection + // 0 - only the resources referenced by the captured frame are included + eRENDERDOC_Option_RefAllResources = 8, + + // **NOTE**: As of RenderDoc v1.1 this option has been deprecated. Setting or + // getting it will be ignored, to allow compatibility with older versions. + // In v1.1 the option acts as if it's always enabled. + // + // By default RenderDoc skips saving initial states for resources where the + // previous contents don't appear to be used, assuming that writes before + // reads indicate previous contents aren't used. + // + // Default - disabled + // + // 1 - initial contents at the start of each captured frame are saved, even if + // they are later overwritten or cleared before being used. + // 0 - unless a read is detected, initial contents will not be saved and will + // appear as black or empty data. + eRENDERDOC_Option_SaveAllInitials = 9, + + // In APIs that allow for the recording of command lists to be replayed later, + // RenderDoc may choose to not capture command lists before a frame capture is + // triggered, to reduce overheads. This means any command lists recorded once + // and replayed many times will not be available and may cause a failure to + // capture. + // + // NOTE: This is only true for APIs where multithreading is difficult or + // discouraged. Newer APIs like Vulkan and D3D12 will ignore this option + // and always capture all command lists since the API is heavily oriented + // around it and the overheads have been reduced by API design. + // + // 1 - All command lists are captured from the start of the application + // 0 - Command lists are only captured if their recording begins during + // the period when a frame capture is in progress. + eRENDERDOC_Option_CaptureAllCmdLists = 10, + + // Mute API debugging output when the API validation mode option is enabled + // + // Default - enabled + // + // 1 - Mute any API debug messages from being displayed or passed through + // 0 - API debugging is displayed as normal + eRENDERDOC_Option_DebugOutputMute = 11, + + // Option to allow vendor extensions to be used even when they may be + // incompatible with RenderDoc and cause corrupted replays or crashes. + // + // Default - inactive + // + // No values are documented, this option should only be used when absolutely + // necessary as directed by a RenderDoc developer. + eRENDERDOC_Option_AllowUnsupportedVendorExtensions = 12, + + // Define a soft memory limit which some APIs may aim to keep overhead under where + // possible. Anything above this limit will where possible be saved directly to disk during + // capture. + // This will cause increased disk space use (which may cause a capture to fail if disk space is + // exhausted) as well as slower capture times. + // + // Not all memory allocations may be deferred like this so it is not a guarantee of a memory + // limit. + // + // Units are in MBs, suggested values would range from 200MB to 1000MB. + // + // Default - 0 Megabytes + eRENDERDOC_Option_SoftMemoryLimit = 13, +} RENDERDOC_CaptureOption; + +// Sets an option that controls how RenderDoc behaves on capture. +// +// Returns 1 if the option and value are valid +// Returns 0 if either is invalid and the option is unchanged +typedef int(RENDERDOC_CC *pRENDERDOC_SetCaptureOptionU32)(RENDERDOC_CaptureOption opt, uint32_t val); +typedef int(RENDERDOC_CC *pRENDERDOC_SetCaptureOptionF32)(RENDERDOC_CaptureOption opt, float val); + +// Gets the current value of an option as a uint32_t +// +// If the option is invalid, 0xffffffff is returned +typedef uint32_t(RENDERDOC_CC *pRENDERDOC_GetCaptureOptionU32)(RENDERDOC_CaptureOption opt); + +// Gets the current value of an option as a float +// +// If the option is invalid, -FLT_MAX is returned +typedef float(RENDERDOC_CC *pRENDERDOC_GetCaptureOptionF32)(RENDERDOC_CaptureOption opt); + +typedef enum RENDERDOC_InputButton { + // '0' - '9' matches ASCII values + eRENDERDOC_Key_0 = 0x30, + eRENDERDOC_Key_1 = 0x31, + eRENDERDOC_Key_2 = 0x32, + eRENDERDOC_Key_3 = 0x33, + eRENDERDOC_Key_4 = 0x34, + eRENDERDOC_Key_5 = 0x35, + eRENDERDOC_Key_6 = 0x36, + eRENDERDOC_Key_7 = 0x37, + eRENDERDOC_Key_8 = 0x38, + eRENDERDOC_Key_9 = 0x39, + + // 'A' - 'Z' matches ASCII values + eRENDERDOC_Key_A = 0x41, + eRENDERDOC_Key_B = 0x42, + eRENDERDOC_Key_C = 0x43, + eRENDERDOC_Key_D = 0x44, + eRENDERDOC_Key_E = 0x45, + eRENDERDOC_Key_F = 0x46, + eRENDERDOC_Key_G = 0x47, + eRENDERDOC_Key_H = 0x48, + eRENDERDOC_Key_I = 0x49, + eRENDERDOC_Key_J = 0x4A, + eRENDERDOC_Key_K = 0x4B, + eRENDERDOC_Key_L = 0x4C, + eRENDERDOC_Key_M = 0x4D, + eRENDERDOC_Key_N = 0x4E, + eRENDERDOC_Key_O = 0x4F, + eRENDERDOC_Key_P = 0x50, + eRENDERDOC_Key_Q = 0x51, + eRENDERDOC_Key_R = 0x52, + eRENDERDOC_Key_S = 0x53, + eRENDERDOC_Key_T = 0x54, + eRENDERDOC_Key_U = 0x55, + eRENDERDOC_Key_V = 0x56, + eRENDERDOC_Key_W = 0x57, + eRENDERDOC_Key_X = 0x58, + eRENDERDOC_Key_Y = 0x59, + eRENDERDOC_Key_Z = 0x5A, + + // leave the rest of the ASCII range free + // in case we want to use it later + eRENDERDOC_Key_NonPrintable = 0x100, + + eRENDERDOC_Key_Divide, + eRENDERDOC_Key_Multiply, + eRENDERDOC_Key_Subtract, + eRENDERDOC_Key_Plus, + + eRENDERDOC_Key_F1, + eRENDERDOC_Key_F2, + eRENDERDOC_Key_F3, + eRENDERDOC_Key_F4, + eRENDERDOC_Key_F5, + eRENDERDOC_Key_F6, + eRENDERDOC_Key_F7, + eRENDERDOC_Key_F8, + eRENDERDOC_Key_F9, + eRENDERDOC_Key_F10, + eRENDERDOC_Key_F11, + eRENDERDOC_Key_F12, + + eRENDERDOC_Key_Home, + eRENDERDOC_Key_End, + eRENDERDOC_Key_Insert, + eRENDERDOC_Key_Delete, + eRENDERDOC_Key_PageUp, + eRENDERDOC_Key_PageDn, + + eRENDERDOC_Key_Backspace, + eRENDERDOC_Key_Tab, + eRENDERDOC_Key_PrtScrn, + eRENDERDOC_Key_Pause, + + eRENDERDOC_Key_Max, +} RENDERDOC_InputButton; + +// Sets which key or keys can be used to toggle focus between multiple windows +// +// If keys is NULL or num is 0, toggle keys will be disabled +typedef void(RENDERDOC_CC *pRENDERDOC_SetFocusToggleKeys)(RENDERDOC_InputButton *keys, int num); + +// Sets which key or keys can be used to capture the next frame +// +// If keys is NULL or num is 0, captures keys will be disabled +typedef void(RENDERDOC_CC *pRENDERDOC_SetCaptureKeys)(RENDERDOC_InputButton *keys, int num); + +typedef enum RENDERDOC_OverlayBits { + // This single bit controls whether the overlay is enabled or disabled globally + eRENDERDOC_Overlay_Enabled = 0x1, + + // Show the average framerate over several seconds as well as min/max + eRENDERDOC_Overlay_FrameRate = 0x2, + + // Show the current frame number + eRENDERDOC_Overlay_FrameNumber = 0x4, + + // Show a list of recent captures, and how many captures have been made + eRENDERDOC_Overlay_CaptureList = 0x8, + + // Default values for the overlay mask + eRENDERDOC_Overlay_Default = + (eRENDERDOC_Overlay_Enabled | eRENDERDOC_Overlay_FrameRate | eRENDERDOC_Overlay_FrameNumber | eRENDERDOC_Overlay_CaptureList), + + // Enable all bits + eRENDERDOC_Overlay_All = ~0U, + + // Disable all bits + eRENDERDOC_Overlay_None = 0, +} RENDERDOC_OverlayBits; + +// returns the overlay bits that have been set +typedef uint32_t(RENDERDOC_CC *pRENDERDOC_GetOverlayBits)(); +// sets the overlay bits with an and & or mask +typedef void(RENDERDOC_CC *pRENDERDOC_MaskOverlayBits)(uint32_t And, uint32_t Or); + +// this function will attempt to remove RenderDoc's hooks in the application. +// +// Note: that this can only work correctly if done immediately after +// the module is loaded, before any API work happens. RenderDoc will remove its +// injected hooks and shut down. Behaviour is undefined if this is called +// after any API functions have been called, and there is still no guarantee of +// success. +typedef void(RENDERDOC_CC *pRENDERDOC_RemoveHooks)(); + +// DEPRECATED: compatibility for code compiled against pre-1.4.1 headers. +typedef pRENDERDOC_RemoveHooks pRENDERDOC_Shutdown; + +// This function will unload RenderDoc's crash handler. +// +// If you use your own crash handler and don't want RenderDoc's handler to +// intercede, you can call this function to unload it and any unhandled +// exceptions will pass to the next handler. +typedef void(RENDERDOC_CC *pRENDERDOC_UnloadCrashHandler)(); + +// Sets the capture file path template +// +// pathtemplate is a UTF-8 string that gives a template for how captures will be named +// and where they will be saved. +// +// Any extension is stripped off the path, and captures are saved in the directory +// specified, and named with the filename and the frame number appended. If the +// directory does not exist it will be created, including any parent directories. +// +// If pathtemplate is NULL, the template will remain unchanged +// +// Example: +// +// SetCaptureFilePathTemplate("my_captures/example"); +// +// Capture #1 -> my_captures/example_frame123.rdc +// Capture #2 -> my_captures/example_frame456.rdc +typedef void(RENDERDOC_CC *pRENDERDOC_SetCaptureFilePathTemplate)(const char *pathtemplate); + +// returns the current capture path template, see SetCaptureFileTemplate above, as a UTF-8 string +typedef const char *(RENDERDOC_CC *pRENDERDOC_GetCaptureFilePathTemplate)(); + +// DEPRECATED: compatibility for code compiled against pre-1.1.2 headers. +typedef pRENDERDOC_SetCaptureFilePathTemplate pRENDERDOC_SetLogFilePathTemplate; +typedef pRENDERDOC_GetCaptureFilePathTemplate pRENDERDOC_GetLogFilePathTemplate; + +// returns the number of captures that have been made +typedef uint32_t(RENDERDOC_CC *pRENDERDOC_GetNumCaptures)(); + +// This function returns the details of a capture, by index. New captures are added +// to the end of the list. +// +// filename will be filled with the absolute path to the capture file, as a UTF-8 string +// pathlength will be written with the length in bytes of the filename string +// timestamp will be written with the time of the capture, in seconds since the Unix epoch +// +// Any of the parameters can be NULL and they'll be skipped. +// +// The function will return 1 if the capture index is valid, or 0 if the index is invalid +// If the index is invalid, the values will be unchanged +// +// Note: when captures are deleted in the UI they will remain in this list, so the +// capture path may not exist anymore. +typedef uint32_t(RENDERDOC_CC *pRENDERDOC_GetCapture)(uint32_t idx, char *filename, uint32_t *pathlength, uint64_t *timestamp); + +// Sets the comments associated with a capture file. These comments are displayed in the +// UI program when opening. +// +// filePath should be a path to the capture file to add comments to. If set to NULL or "" +// the most recent capture file created made will be used instead. +// comments should be a NULL-terminated UTF-8 string to add as comments. +// +// Any existing comments will be overwritten. +typedef void(RENDERDOC_CC *pRENDERDOC_SetCaptureFileComments)(const char *filePath, const char *comments); + +// returns 1 if the RenderDoc UI is connected to this application, 0 otherwise +typedef uint32_t(RENDERDOC_CC *pRENDERDOC_IsTargetControlConnected)(); + +// DEPRECATED: compatibility for code compiled against pre-1.1.1 headers. +// This was renamed to IsTargetControlConnected in API 1.1.1, the old typedef is kept here for +// backwards compatibility with old code, it is castable either way since it's ABI compatible +// as the same function pointer type. +typedef pRENDERDOC_IsTargetControlConnected pRENDERDOC_IsRemoteAccessConnected; + +// This function will launch the Replay UI associated with the RenderDoc library injected +// into the running application. +// +// if connectTargetControl is 1, the Replay UI will be launched with a command line parameter +// to connect to this application +// cmdline is the rest of the command line, as a UTF-8 string. E.g. a captures to open +// if cmdline is NULL, the command line will be empty. +// +// returns the PID of the replay UI if successful, 0 if not successful. +typedef uint32_t(RENDERDOC_CC *pRENDERDOC_LaunchReplayUI)(uint32_t connectTargetControl, const char *cmdline); + +// RenderDoc can return a higher version than requested if it's backwards compatible, +// this function returns the actual version returned. If a parameter is NULL, it will be +// ignored and the others will be filled out. +typedef void(RENDERDOC_CC *pRENDERDOC_GetAPIVersion)(int *major, int *minor, int *patch); + +// Requests that the replay UI show itself (if hidden or not the current top window). This can be +// used in conjunction with IsTargetControlConnected and LaunchReplayUI to intelligently handle +// showing the UI after making a capture. +// +// This will return 1 if the request was successfully passed on, though it's not guaranteed that +// the UI will be on top in all cases depending on OS rules. It will return 0 if there is no current +// target control connection to make such a request, or if there was another error +typedef uint32_t(RENDERDOC_CC *pRENDERDOC_ShowReplayUI)(); + +////////////////////////////////////////////////////////////////////////// +// Capturing functions +// + +// A device pointer is a pointer to the API's root handle. +// +// This would be an ID3D11Device, HGLRC/GLXContext, ID3D12Device, etc +typedef void *RENDERDOC_DevicePointer; + +// A window handle is the OS's native window handle +// +// This would be an HWND, GLXDrawable, etc +typedef void *RENDERDOC_WindowHandle; + +// A helper macro for Vulkan, where the device handle cannot be used directly. +// +// Passing the VkInstance to this macro will return the RENDERDOC_DevicePointer to use. +// +// Specifically, the value needed is the dispatch table pointer, which sits as the first +// pointer-sized object in the memory pointed to by the VkInstance. Thus we cast to a void** and +// indirect once. +#define RENDERDOC_DEVICEPOINTER_FROM_VKINSTANCE(inst) (*((void **)(inst))) + +// This sets the RenderDoc in-app overlay in the API/window pair as 'active' and it will +// respond to keypresses. Neither parameter can be NULL +typedef void(RENDERDOC_CC *pRENDERDOC_SetActiveWindow)(RENDERDOC_DevicePointer device, RENDERDOC_WindowHandle wndHandle); + +// capture the next frame on whichever window and API is currently considered active +typedef void(RENDERDOC_CC *pRENDERDOC_TriggerCapture)(); + +// capture the next N frames on whichever window and API is currently considered active +typedef void(RENDERDOC_CC *pRENDERDOC_TriggerMultiFrameCapture)(uint32_t numFrames); + +// When choosing either a device pointer or a window handle to capture, you can pass NULL. +// Passing NULL specifies a 'wildcard' match against anything. This allows you to specify +// any API rendering to a specific window, or a specific API instance rendering to any window, +// or in the simplest case of one window and one API, you can just pass NULL for both. +// +// In either case, if there are two or more possible matching (device,window) pairs it +// is undefined which one will be captured. +// +// Note: for headless rendering you can pass NULL for the window handle and either specify +// a device pointer or leave it NULL as above. + +// Immediately starts capturing API calls on the specified device pointer and window handle. +// +// If there is no matching thing to capture (e.g. no supported API has been initialised), +// this will do nothing. +// +// The results are undefined (including crashes) if two captures are started overlapping, +// even on separate devices and/oror windows. +typedef void(RENDERDOC_CC *pRENDERDOC_StartFrameCapture)(RENDERDOC_DevicePointer device, RENDERDOC_WindowHandle wndHandle); + +// Returns whether or not a frame capture is currently ongoing anywhere. +// +// This will return 1 if a capture is ongoing, and 0 if there is no capture running +typedef uint32_t(RENDERDOC_CC *pRENDERDOC_IsFrameCapturing)(); + +// Ends capturing immediately. +// +// This will return 1 if the capture succeeded, and 0 if there was an error capturing. +typedef uint32_t(RENDERDOC_CC *pRENDERDOC_EndFrameCapture)(RENDERDOC_DevicePointer device, RENDERDOC_WindowHandle wndHandle); + +// Ends capturing immediately and discard any data stored without saving to disk. +// +// This will return 1 if the capture was discarded, and 0 if there was an error or no capture +// was in progress +typedef uint32_t(RENDERDOC_CC *pRENDERDOC_DiscardFrameCapture)(RENDERDOC_DevicePointer device, RENDERDOC_WindowHandle wndHandle); + +// Only valid to be called between a call to StartFrameCapture and EndFrameCapture. Gives a custom +// title to the capture produced which will be displayed in the UI. +// +// If multiple captures are ongoing, this title will be applied to the first capture to end after +// this call. The second capture to end will have no title, unless this function is called again. +// +// Calling this function has no effect if no capture is currently running, and if it is called +// multiple times only the last title will be used. +typedef void(RENDERDOC_CC *pRENDERDOC_SetCaptureTitle)(const char *title); + +////////////////////////////////////////////////////////////////////////////////////////////////// +// RenderDoc API versions +// + +// RenderDoc uses semantic versioning (http://semver.org/). +// +// MAJOR version is incremented when incompatible API changes happen. +// MINOR version is incremented when functionality is added in a backwards-compatible manner. +// PATCH version is incremented when backwards-compatible bug fixes happen. +// +// Note that this means the API returned can be higher than the one you might have requested. +// e.g. if you are running against a newer RenderDoc that supports 1.0.1, it will be returned +// instead of 1.0.0. You can check this with the GetAPIVersion entry point +typedef enum RENDERDOC_Version { + eRENDERDOC_API_Version_1_0_0 = 10000, // RENDERDOC_API_1_0_0 = 1 00 00 + eRENDERDOC_API_Version_1_0_1 = 10001, // RENDERDOC_API_1_0_1 = 1 00 01 + eRENDERDOC_API_Version_1_0_2 = 10002, // RENDERDOC_API_1_0_2 = 1 00 02 + eRENDERDOC_API_Version_1_1_0 = 10100, // RENDERDOC_API_1_1_0 = 1 01 00 + eRENDERDOC_API_Version_1_1_1 = 10101, // RENDERDOC_API_1_1_1 = 1 01 01 + eRENDERDOC_API_Version_1_1_2 = 10102, // RENDERDOC_API_1_1_2 = 1 01 02 + eRENDERDOC_API_Version_1_2_0 = 10200, // RENDERDOC_API_1_2_0 = 1 02 00 + eRENDERDOC_API_Version_1_3_0 = 10300, // RENDERDOC_API_1_3_0 = 1 03 00 + eRENDERDOC_API_Version_1_4_0 = 10400, // RENDERDOC_API_1_4_0 = 1 04 00 + eRENDERDOC_API_Version_1_4_1 = 10401, // RENDERDOC_API_1_4_1 = 1 04 01 + eRENDERDOC_API_Version_1_4_2 = 10402, // RENDERDOC_API_1_4_2 = 1 04 02 + eRENDERDOC_API_Version_1_5_0 = 10500, // RENDERDOC_API_1_5_0 = 1 05 00 + eRENDERDOC_API_Version_1_6_0 = 10600, // RENDERDOC_API_1_6_0 = 1 06 00 +} RENDERDOC_Version; + +// API version changelog: +// +// 1.0.0 - initial release +// 1.0.1 - Bugfix: IsFrameCapturing() was returning false for captures that were triggered +// by keypress or TriggerCapture, instead of Start/EndFrameCapture. +// 1.0.2 - Refactor: Renamed eRENDERDOC_Option_DebugDeviceMode to eRENDERDOC_Option_APIValidation +// 1.1.0 - Add feature: TriggerMultiFrameCapture(). Backwards compatible with 1.0.x since the new +// function pointer is added to the end of the struct, the original layout is identical +// 1.1.1 - Refactor: Renamed remote access to target control (to better disambiguate from remote +// replay/remote server concept in replay UI) +// 1.1.2 - Refactor: Renamed "log file" in function names to just capture, to clarify that these +// are captures and not debug logging files. This is the first API version in the v1.0 +// branch. +// 1.2.0 - Added feature: SetCaptureFileComments() to add comments to a capture file that will be +// displayed in the UI program on load. +// 1.3.0 - Added feature: New capture option eRENDERDOC_Option_AllowUnsupportedVendorExtensions +// which allows users to opt-in to allowing unsupported vendor extensions to function. +// Should be used at the user's own risk. +// Refactor: Renamed eRENDERDOC_Option_VerifyMapWrites to +// eRENDERDOC_Option_VerifyBufferAccess, which now also controls initialisation to +// 0xdddddddd of uninitialised buffer contents. +// 1.4.0 - Added feature: DiscardFrameCapture() to discard a frame capture in progress and stop +// capturing without saving anything to disk. +// 1.4.1 - Refactor: Renamed Shutdown to RemoveHooks to better clarify what is happening +// 1.4.2 - Refactor: Renamed 'draws' to 'actions' in callstack capture option. +// 1.5.0 - Added feature: ShowReplayUI() to request that the replay UI show itself if connected +// 1.6.0 - Added feature: SetCaptureTitle() which can be used to set a title for a +// capture made with StartFrameCapture() or EndFrameCapture() + +typedef struct RENDERDOC_API_1_6_0 { + pRENDERDOC_GetAPIVersion GetAPIVersion; + + pRENDERDOC_SetCaptureOptionU32 SetCaptureOptionU32; + pRENDERDOC_SetCaptureOptionF32 SetCaptureOptionF32; + + pRENDERDOC_GetCaptureOptionU32 GetCaptureOptionU32; + pRENDERDOC_GetCaptureOptionF32 GetCaptureOptionF32; + + pRENDERDOC_SetFocusToggleKeys SetFocusToggleKeys; + pRENDERDOC_SetCaptureKeys SetCaptureKeys; + + pRENDERDOC_GetOverlayBits GetOverlayBits; + pRENDERDOC_MaskOverlayBits MaskOverlayBits; + + // Shutdown was renamed to RemoveHooks in 1.4.1. + // These unions allow old code to continue compiling without changes + union { + pRENDERDOC_Shutdown Shutdown; + pRENDERDOC_RemoveHooks RemoveHooks; + }; + pRENDERDOC_UnloadCrashHandler UnloadCrashHandler; + + // Get/SetLogFilePathTemplate was renamed to Get/SetCaptureFilePathTemplate in 1.1.2. + // These unions allow old code to continue compiling without changes + union { + // deprecated name + pRENDERDOC_SetLogFilePathTemplate SetLogFilePathTemplate; + // current name + pRENDERDOC_SetCaptureFilePathTemplate SetCaptureFilePathTemplate; + }; + union { + // deprecated name + pRENDERDOC_GetLogFilePathTemplate GetLogFilePathTemplate; + // current name + pRENDERDOC_GetCaptureFilePathTemplate GetCaptureFilePathTemplate; + }; + + pRENDERDOC_GetNumCaptures GetNumCaptures; + pRENDERDOC_GetCapture GetCapture; + + pRENDERDOC_TriggerCapture TriggerCapture; + + // IsRemoteAccessConnected was renamed to IsTargetControlConnected in 1.1.1. + // This union allows old code to continue compiling without changes + union { + // deprecated name + pRENDERDOC_IsRemoteAccessConnected IsRemoteAccessConnected; + // current name + pRENDERDOC_IsTargetControlConnected IsTargetControlConnected; + }; + pRENDERDOC_LaunchReplayUI LaunchReplayUI; + + pRENDERDOC_SetActiveWindow SetActiveWindow; + + pRENDERDOC_StartFrameCapture StartFrameCapture; + pRENDERDOC_IsFrameCapturing IsFrameCapturing; + pRENDERDOC_EndFrameCapture EndFrameCapture; + + // new function in 1.1.0 + pRENDERDOC_TriggerMultiFrameCapture TriggerMultiFrameCapture; + + // new function in 1.2.0 + pRENDERDOC_SetCaptureFileComments SetCaptureFileComments; + + // new function in 1.4.0 + pRENDERDOC_DiscardFrameCapture DiscardFrameCapture; + + // new function in 1.5.0 + pRENDERDOC_ShowReplayUI ShowReplayUI; + + // new function in 1.6.0 + pRENDERDOC_SetCaptureTitle SetCaptureTitle; +} RENDERDOC_API_1_6_0; + +typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_0_0; +typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_0_1; +typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_0_2; +typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_1_0; +typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_1_1; +typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_1_2; +typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_2_0; +typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_3_0; +typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_4_0; +typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_4_1; +typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_4_2; +typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_5_0; + +////////////////////////////////////////////////////////////////////////////////////////////////// +// RenderDoc API entry point +// +// This entry point can be obtained via GetProcAddress/dlsym if RenderDoc is available. +// +// The name is the same as the typedef - "RENDERDOC_GetAPI" +// +// This function is not thread safe, and should not be called on multiple threads at once. +// Ideally, call this once as early as possible in your application's startup, before doing +// any API work, since some configuration functionality etc has to be done also before +// initialising any APIs. +// +// Parameters: +// version is a single value from the RENDERDOC_Version above. +// +// outAPIPointers will be filled out with a pointer to the corresponding struct of function +// pointers. +// +// Returns: +// 1 - if the outAPIPointers has been filled with a pointer to the API struct requested +// 0 - if the requested version is not supported or the arguments are invalid. +// +typedef int(RENDERDOC_CC *pRENDERDOC_GetAPI)(RENDERDOC_Version version, void **outAPIPointers); + +#ifdef __cplusplus +} // extern "C" +#endif \ No newline at end of file