mirror of
https://github.com/cemu-project/Cemu.git
synced 2025-04-02 11:01:52 -04:00
Compare commits
27 commits
Author | SHA1 | Date | |
---|---|---|---|
|
57ff99ce53 | ||
|
8b5cafa98e | ||
|
186e92221a | ||
|
31d2db6f78 | ||
|
ebb5ab53e2 | ||
|
a6fb0a48eb | ||
|
ec2d7c086a | ||
|
c714e8cb6b | ||
|
e834515f43 | ||
|
4f9eea07e0 | ||
|
372c314f06 | ||
|
5bd253a1f8 | ||
|
4ac65159ef | ||
|
eab1b24320 | ||
|
07cd402531 | ||
|
0a59085021 | ||
|
8dd809d725 | ||
|
1923b7a7c4 | ||
|
f61539a262 | ||
|
92021db230 | ||
|
4b792aa4d2 | ||
|
1e30d72658 | ||
|
2b0cbf7f6b | ||
|
3738ccd2e6 | ||
|
b53b223ba9 | ||
|
6aaad1eb83 | ||
|
adab729f43 |
96 changed files with 1974 additions and 1398 deletions
2
.github/workflows/generate_pot.yml
vendored
2
.github/workflows/generate_pot.yml
vendored
|
@ -35,7 +35,7 @@ jobs:
|
||||||
-o cemu.pot
|
-o cemu.pot
|
||||||
|
|
||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: POT file
|
name: POT file
|
||||||
path: ./cemu.pot
|
path: ./cemu.pot
|
||||||
|
|
43
BUILD.md
43
BUILD.md
|
@ -57,7 +57,7 @@ At Step 3 in [Build Cemu using cmake and clang](#build-cemu-using-cmake-and-clan
|
||||||
`cmake -S . -B build -DCMAKE_BUILD_TYPE=release -DCMAKE_C_COMPILER=/usr/bin/clang-15 -DCMAKE_CXX_COMPILER=/usr/bin/clang++-15 -G Ninja -DCMAKE_MAKE_PROGRAM=/usr/bin/ninja`
|
`cmake -S . -B build -DCMAKE_BUILD_TYPE=release -DCMAKE_C_COMPILER=/usr/bin/clang-15 -DCMAKE_CXX_COMPILER=/usr/bin/clang++-15 -G Ninja -DCMAKE_MAKE_PROGRAM=/usr/bin/ninja`
|
||||||
|
|
||||||
#### For Fedora and derivatives:
|
#### For Fedora and derivatives:
|
||||||
`sudo dnf install bluez-libs clang cmake cubeb-devel freeglut-devel git glm-devel gtk3-devel kernel-headers libgcrypt-devel libsecret-devel libtool libusb1-devel llvm nasm ninja-build perl-core systemd-devel zlib-devel zlib-static`
|
`sudo dnf install bluez-libs-devel clang cmake cubeb-devel freeglut-devel git glm-devel gtk3-devel kernel-headers libgcrypt-devel libsecret-devel libtool libusb1-devel llvm nasm ninja-build perl-core systemd-devel wayland-protocols-devel zlib-devel zlib-static`
|
||||||
|
|
||||||
### Build Cemu
|
### Build Cemu
|
||||||
|
|
||||||
|
@ -120,6 +120,9 @@ This section refers to running `cmake -S...` (truncated).
|
||||||
* Compiling failed during rebuild after `git pull` with an error that mentions RPATH
|
* Compiling failed during rebuild after `git pull` with an error that mentions RPATH
|
||||||
* Add the following and try running the command again:
|
* Add the following and try running the command again:
|
||||||
* `-DCMAKE_BUILD_WITH_INSTALL_RPATH=ON`
|
* `-DCMAKE_BUILD_WITH_INSTALL_RPATH=ON`
|
||||||
|
* Environment variable `VCPKG_FORCE_SYSTEM_BINARIES` must be set.
|
||||||
|
* Execute the folowing and then try running the command again:
|
||||||
|
* `export VCPKG_FORCE_SYSTEM_BINARIES=1`
|
||||||
* If you are getting a random error, read the [package-name-and-platform]-out.log and [package-name-and-platform]-err.log for the actual reason to see if you might be lacking the headers from a dependency.
|
* If you are getting a random error, read the [package-name-and-platform]-out.log and [package-name-and-platform]-err.log for the actual reason to see if you might be lacking the headers from a dependency.
|
||||||
|
|
||||||
|
|
||||||
|
@ -189,3 +192,41 @@ Then install the dependencies:
|
||||||
|
|
||||||
If CMake complains about Cemu already being compiled or another similar error, try deleting the `CMakeCache.txt` file inside the `build` folder and retry building.
|
If CMake complains about Cemu already being compiled or another similar error, try deleting the `CMakeCache.txt` file inside the `build` folder and retry building.
|
||||||
|
|
||||||
|
## CMake configure flags
|
||||||
|
Some flags can be passed during CMake configure to customise which features are enabled on build.
|
||||||
|
|
||||||
|
Example usage: `cmake -S . -B build -DCMAKE_BUILD_TYPE=release -DENABLE_SDL=ON -DENABLE_VULKAN=OFF`
|
||||||
|
|
||||||
|
### All platforms
|
||||||
|
| Flag | | Description | Default | Note |
|
||||||
|
|--------------------|:--|-----------------------------------------------------------------------------|---------|--------------------|
|
||||||
|
| ALLOW_PORTABLE | | Allow Cemu to use the `portable` directory to store configs and data | ON | |
|
||||||
|
| CEMU_CXX_FLAGS | | Flags passed straight to the compiler, e.g. `-march=native`, `-Wall`, `/W3` | "" | |
|
||||||
|
| ENABLE_CUBEB | | Enable cubeb audio backend | ON | |
|
||||||
|
| ENABLE_DISCORD_RPC | | Enable Discord Rich presence support | ON | |
|
||||||
|
| ENABLE_OPENGL | | Enable OpenGL graphics backend | ON | Currently required |
|
||||||
|
| ENABLE_HIDAPI | | Enable HIDAPI (used for Wiimote controller API) | ON | |
|
||||||
|
| ENABLE_SDL | | Enable SDLController controller API | ON | Currently required |
|
||||||
|
| ENABLE_VCPKG | | Use VCPKG package manager to obtain dependencies | ON | |
|
||||||
|
| ENABLE_VULKAN | | Enable the Vulkan graphics backend | ON | |
|
||||||
|
| ENABLE_WXWIDGETS | | Enable wxWidgets UI | ON | Currently required |
|
||||||
|
|
||||||
|
### Windows
|
||||||
|
| Flag | Description | Default | Note |
|
||||||
|
|--------------------|-----------------------------------|---------|--------------------|
|
||||||
|
| ENABLE_DIRECTAUDIO | Enable DirectAudio audio backend | ON | Currently required |
|
||||||
|
| ENABLE_DIRECTINPUT | Enable DirectInput controller API | ON | Currently required |
|
||||||
|
| ENABLE_XAUDIO | Enable XAudio audio backend | ON | |
|
||||||
|
| ENABLE_XINPUT | Enable XInput controller API | ON | |
|
||||||
|
|
||||||
|
### Linux
|
||||||
|
| Flag | Description | Default |
|
||||||
|
|-----------------------|----------------------------------------------------|---------|
|
||||||
|
| ENABLE_BLUEZ | Build with Bluez (used for Wiimote controller API) | ON |
|
||||||
|
| ENABLE_FERAL_GAMEMODE | Enable Feral Interactive GameMode support | ON |
|
||||||
|
| ENABLE_WAYLAND | Enable Wayland support | ON |
|
||||||
|
|
||||||
|
### macOS
|
||||||
|
| Flag | Description | Default |
|
||||||
|
|--------------|------------------------------------------------|---------|
|
||||||
|
| MACOS_BUNDLE | MacOS executable will be an application bundle | OFF |
|
||||||
|
|
|
@ -2,6 +2,7 @@ cmake_minimum_required(VERSION 3.21.1)
|
||||||
|
|
||||||
option(ENABLE_VCPKG "Enable the vcpkg package manager" ON)
|
option(ENABLE_VCPKG "Enable the vcpkg package manager" ON)
|
||||||
option(MACOS_BUNDLE "The executable when built on macOS will be created as an application bundle" OFF)
|
option(MACOS_BUNDLE "The executable when built on macOS will be created as an application bundle" OFF)
|
||||||
|
option(ALLOW_PORTABLE "Allow Cemu to be run in portable mode" ON)
|
||||||
|
|
||||||
# used by CI script to set version:
|
# used by CI script to set version:
|
||||||
set(EMULATOR_VERSION_MAJOR "0" CACHE STRING "")
|
set(EMULATOR_VERSION_MAJOR "0" CACHE STRING "")
|
||||||
|
@ -123,23 +124,6 @@ if (WIN32)
|
||||||
endif()
|
endif()
|
||||||
option(ENABLE_CUBEB "Enabled cubeb backend" ON)
|
option(ENABLE_CUBEB "Enabled cubeb backend" ON)
|
||||||
|
|
||||||
# usb hid backends
|
|
||||||
if (WIN32)
|
|
||||||
option(ENABLE_NSYSHID_WINDOWS_HID "Enables the native Windows HID backend for nsyshid" ON)
|
|
||||||
endif ()
|
|
||||||
# libusb and windows hid backends shouldn't be active at the same time; otherwise we'd see all devices twice!
|
|
||||||
if (NOT ENABLE_NSYSHID_WINDOWS_HID)
|
|
||||||
option(ENABLE_NSYSHID_LIBUSB "Enables the libusb backend for nsyshid" ON)
|
|
||||||
else ()
|
|
||||||
set(ENABLE_NSYSHID_LIBUSB OFF CACHE BOOL "" FORCE)
|
|
||||||
endif ()
|
|
||||||
if (ENABLE_NSYSHID_WINDOWS_HID)
|
|
||||||
add_compile_definitions(NSYSHID_ENABLE_BACKEND_WINDOWS_HID)
|
|
||||||
endif ()
|
|
||||||
if (ENABLE_NSYSHID_LIBUSB)
|
|
||||||
add_compile_definitions(NSYSHID_ENABLE_BACKEND_LIBUSB)
|
|
||||||
endif ()
|
|
||||||
|
|
||||||
option(ENABLE_WXWIDGETS "Build with wxWidgets UI (Currently required)" ON)
|
option(ENABLE_WXWIDGETS "Build with wxWidgets UI (Currently required)" ON)
|
||||||
|
|
||||||
set(THREADS_PREFER_PTHREAD_FLAG true)
|
set(THREADS_PREFER_PTHREAD_FLAG true)
|
||||||
|
|
|
@ -463,8 +463,6 @@ add_library(CemuCafe
|
||||||
OS/libs/nsyshid/BackendEmulated.h
|
OS/libs/nsyshid/BackendEmulated.h
|
||||||
OS/libs/nsyshid/BackendLibusb.cpp
|
OS/libs/nsyshid/BackendLibusb.cpp
|
||||||
OS/libs/nsyshid/BackendLibusb.h
|
OS/libs/nsyshid/BackendLibusb.h
|
||||||
OS/libs/nsyshid/BackendWindowsHID.cpp
|
|
||||||
OS/libs/nsyshid/BackendWindowsHID.h
|
|
||||||
OS/libs/nsyshid/Dimensions.cpp
|
OS/libs/nsyshid/Dimensions.cpp
|
||||||
OS/libs/nsyshid/Dimensions.h
|
OS/libs/nsyshid/Dimensions.h
|
||||||
OS/libs/nsyshid/Infinity.cpp
|
OS/libs/nsyshid/Infinity.cpp
|
||||||
|
@ -569,15 +567,16 @@ if (ENABLE_WAYLAND)
|
||||||
target_link_libraries(CemuCafe PUBLIC Wayland::Client)
|
target_link_libraries(CemuCafe PUBLIC Wayland::Client)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (ENABLE_NSYSHID_LIBUSB)
|
if (ENABLE_VCPKG)
|
||||||
if (ENABLE_VCPKG)
|
if(WIN32)
|
||||||
find_package(PkgConfig REQUIRED)
|
set(PKG_CONFIG_EXECUTABLE "${VCPKG_INSTALLED_DIR}/x64-windows/tools/pkgconf/pkgconf.exe")
|
||||||
pkg_check_modules(libusb REQUIRED IMPORTED_TARGET libusb-1.0)
|
endif()
|
||||||
target_link_libraries(CemuCafe PRIVATE PkgConfig::libusb)
|
find_package(PkgConfig REQUIRED)
|
||||||
else ()
|
pkg_check_modules(libusb REQUIRED IMPORTED_TARGET libusb-1.0)
|
||||||
find_package(libusb MODULE REQUIRED)
|
target_link_libraries(CemuCafe PRIVATE PkgConfig::libusb)
|
||||||
target_link_libraries(CemuCafe PRIVATE libusb::libusb)
|
else ()
|
||||||
endif ()
|
find_package(libusb MODULE REQUIRED)
|
||||||
|
target_link_libraries(CemuCafe PRIVATE libusb::libusb)
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
if (ENABLE_WXWIDGETS)
|
if (ENABLE_WXWIDGETS)
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
#include "audio/IAudioAPI.h"
|
#include "audio/IAudioAPI.h"
|
||||||
#include "audio/IAudioInputAPI.h"
|
#include "audio/IAudioInputAPI.h"
|
||||||
#include "config/ActiveSettings.h"
|
#include "config/ActiveSettings.h"
|
||||||
|
#include "config/LaunchSettings.h"
|
||||||
#include "Cafe/TitleList/GameInfo.h"
|
#include "Cafe/TitleList/GameInfo.h"
|
||||||
#include "Cafe/GraphicPack/GraphicPack2.h"
|
#include "Cafe/GraphicPack/GraphicPack2.h"
|
||||||
#include "util/helpers/SystemException.h"
|
#include "util/helpers/SystemException.h"
|
||||||
|
@ -843,7 +844,7 @@ namespace CafeSystem
|
||||||
module->TitleStart();
|
module->TitleStart();
|
||||||
cemu_initForGame();
|
cemu_initForGame();
|
||||||
// enter scheduler
|
// enter scheduler
|
||||||
if (ActiveSettings::GetCPUMode() == CPUMode::MulticoreRecompiler)
|
if (ActiveSettings::GetCPUMode() == CPUMode::MulticoreRecompiler && !LaunchSettings::ForceInterpreter())
|
||||||
coreinit::OSSchedulerBegin(3);
|
coreinit::OSSchedulerBegin(3);
|
||||||
else
|
else
|
||||||
coreinit::OSSchedulerBegin(1);
|
coreinit::OSSchedulerBegin(1);
|
||||||
|
|
|
@ -140,7 +140,7 @@ bool gameProfile_loadEnumOption(IniParser& iniParser, const char* optionName, T&
|
||||||
for(const T& v : T())
|
for(const T& v : T())
|
||||||
{
|
{
|
||||||
// test integer option
|
// test integer option
|
||||||
if (boost::iequals(fmt::format("{}", static_cast<typename std::underlying_type<T>::type>(v)), *option_value))
|
if (boost::iequals(fmt::format("{}", fmt::underlying(v)), *option_value))
|
||||||
{
|
{
|
||||||
option = v;
|
option = v;
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
#include "gui/debugger/DebuggerWindow2.h"
|
#include "gui/debugger/DebuggerWindow2.h"
|
||||||
|
|
||||||
#include "Cafe/OS/libs/coreinit/coreinit.h"
|
#include "Cafe/OS/libs/coreinit/coreinit.h"
|
||||||
|
#include "util/helpers/helpers.h"
|
||||||
|
|
||||||
#if BOOST_OS_WINDOWS
|
#if BOOST_OS_WINDOWS
|
||||||
#include <Windows.h>
|
#include <Windows.h>
|
||||||
|
@ -136,11 +137,6 @@ void debugger_createCodeBreakpoint(uint32 address, uint8 bpType)
|
||||||
debugger_updateExecutionBreakpoint(address);
|
debugger_updateExecutionBreakpoint(address);
|
||||||
}
|
}
|
||||||
|
|
||||||
void debugger_createExecuteBreakpoint(uint32 address)
|
|
||||||
{
|
|
||||||
debugger_createCodeBreakpoint(address, DEBUGGER_BP_T_NORMAL);
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace coreinit
|
namespace coreinit
|
||||||
{
|
{
|
||||||
std::vector<std::thread::native_handle_type>& OSGetSchedulerThreads();
|
std::vector<std::thread::native_handle_type>& OSGetSchedulerThreads();
|
||||||
|
@ -294,8 +290,23 @@ void debugger_toggleExecuteBreakpoint(uint32 address)
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// create new breakpoint
|
// create new execution breakpoint
|
||||||
debugger_createExecuteBreakpoint(address);
|
debugger_createCodeBreakpoint(address, DEBUGGER_BP_T_NORMAL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void debugger_toggleLoggingBreakpoint(uint32 address)
|
||||||
|
{
|
||||||
|
auto existingBP = debugger_getFirstBP(address, DEBUGGER_BP_T_LOGGING);
|
||||||
|
if (existingBP)
|
||||||
|
{
|
||||||
|
// delete existing breakpoint
|
||||||
|
debugger_deleteBreakpoint(existingBP);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// create new logging breakpoint
|
||||||
|
debugger_createCodeBreakpoint(address, DEBUGGER_BP_T_LOGGING);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -447,6 +458,34 @@ bool debugger_hasPatch(uint32 address)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void debugger_removePatch(uint32 address)
|
||||||
|
{
|
||||||
|
for (sint32 i = 0; i < debuggerState.patches.size(); i++)
|
||||||
|
{
|
||||||
|
auto& patch = debuggerState.patches[i];
|
||||||
|
if (address < patch->address || address >= (patch->address + patch->length))
|
||||||
|
continue;
|
||||||
|
MPTR startAddress = patch->address;
|
||||||
|
MPTR endAddress = patch->address + patch->length;
|
||||||
|
// remove any breakpoints overlapping with the patch
|
||||||
|
for (auto& bp : debuggerState.breakpoints)
|
||||||
|
{
|
||||||
|
if (bp->address + 4 > startAddress && bp->address < endAddress)
|
||||||
|
{
|
||||||
|
bp->enabled = false;
|
||||||
|
debugger_updateExecutionBreakpoint(bp->address);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// restore original data
|
||||||
|
memcpy(MEMPTR<void>(startAddress).GetPtr(), patch->origData.data(), patch->length);
|
||||||
|
PPCRecompiler_invalidateRange(startAddress, endAddress);
|
||||||
|
// remove patch
|
||||||
|
delete patch;
|
||||||
|
debuggerState.patches.erase(debuggerState.patches.begin() + i);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void debugger_stepInto(PPCInterpreter_t* hCPU, bool updateDebuggerWindow = true)
|
void debugger_stepInto(PPCInterpreter_t* hCPU, bool updateDebuggerWindow = true)
|
||||||
{
|
{
|
||||||
bool isRecEnabled = ppcRecompilerEnabled;
|
bool isRecEnabled = ppcRecompilerEnabled;
|
||||||
|
@ -510,7 +549,48 @@ void debugger_enterTW(PPCInterpreter_t* hCPU)
|
||||||
{
|
{
|
||||||
if (bp->bpType == DEBUGGER_BP_T_LOGGING && bp->enabled)
|
if (bp->bpType == DEBUGGER_BP_T_LOGGING && bp->enabled)
|
||||||
{
|
{
|
||||||
std::string logName = !bp->comment.empty() ? "Breakpoint '"+boost::nowide::narrow(bp->comment)+"'" : fmt::format("Breakpoint at 0x{:08X} (no comment)", bp->address);
|
std::string comment = !bp->comment.empty() ? boost::nowide::narrow(bp->comment) : fmt::format("Breakpoint at 0x{:08X} (no comment)", bp->address);
|
||||||
|
|
||||||
|
auto replacePlaceholders = [&](const std::string& prefix, const auto& formatFunc)
|
||||||
|
{
|
||||||
|
size_t pos = 0;
|
||||||
|
while ((pos = comment.find(prefix, pos)) != std::string::npos)
|
||||||
|
{
|
||||||
|
size_t endPos = comment.find('}', pos);
|
||||||
|
if (endPos == std::string::npos)
|
||||||
|
break;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (int regNum = ConvertString<int>(comment.substr(pos + prefix.length(), endPos - pos - prefix.length())); regNum >= 0 && regNum < 32)
|
||||||
|
{
|
||||||
|
std::string replacement = formatFunc(regNum);
|
||||||
|
comment.replace(pos, endPos - pos + 1, replacement);
|
||||||
|
pos += replacement.length();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pos = endPos + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (...)
|
||||||
|
{
|
||||||
|
pos = endPos + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Replace integer register placeholders {rX}
|
||||||
|
replacePlaceholders("{r", [&](int regNum) {
|
||||||
|
return fmt::format("0x{:08X}", hCPU->gpr[regNum]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Replace floating point register placeholders {fX}
|
||||||
|
replacePlaceholders("{f", [&](int regNum) {
|
||||||
|
return fmt::format("{}", hCPU->fpr[regNum].fpr);
|
||||||
|
});
|
||||||
|
|
||||||
|
std::string logName = "Breakpoint '" + comment + "'";
|
||||||
std::string logContext = fmt::format("Thread: {:08x} LR: 0x{:08x}", MEMPTR<OSThread_t>(coreinit::OSGetCurrentThread()).GetMPTR(), hCPU->spr.LR, cemuLog_advancedPPCLoggingEnabled() ? " Stack Trace:" : "");
|
std::string logContext = fmt::format("Thread: {:08x} LR: 0x{:08x}", MEMPTR<OSThread_t>(coreinit::OSGetCurrentThread()).GetMPTR(), hCPU->spr.LR, cemuLog_advancedPPCLoggingEnabled() ? " Stack Trace:" : "");
|
||||||
cemuLog_log(LogType::Force, "[Debugger] {} was executed! {}", logName, logContext);
|
cemuLog_log(LogType::Force, "[Debugger] {} was executed! {}", logName, logContext);
|
||||||
if (cemuLog_advancedPPCLoggingEnabled())
|
if (cemuLog_advancedPPCLoggingEnabled())
|
||||||
|
@ -547,7 +627,7 @@ void debugger_enterTW(PPCInterpreter_t* hCPU)
|
||||||
debuggerState.debugSession.stepInto = false;
|
debuggerState.debugSession.stepInto = false;
|
||||||
debuggerState.debugSession.stepOver = false;
|
debuggerState.debugSession.stepOver = false;
|
||||||
debuggerState.debugSession.run = false;
|
debuggerState.debugSession.run = false;
|
||||||
while (true)
|
while (debuggerState.debugSession.isTrapped)
|
||||||
{
|
{
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||||
// check for step commands
|
// check for step commands
|
||||||
|
|
|
@ -100,8 +100,8 @@ extern debuggerState_t debuggerState;
|
||||||
// new API
|
// new API
|
||||||
DebuggerBreakpoint* debugger_getFirstBP(uint32 address);
|
DebuggerBreakpoint* debugger_getFirstBP(uint32 address);
|
||||||
void debugger_createCodeBreakpoint(uint32 address, uint8 bpType);
|
void debugger_createCodeBreakpoint(uint32 address, uint8 bpType);
|
||||||
void debugger_createExecuteBreakpoint(uint32 address);
|
|
||||||
void debugger_toggleExecuteBreakpoint(uint32 address); // create/remove execute breakpoint
|
void debugger_toggleExecuteBreakpoint(uint32 address); // create/remove execute breakpoint
|
||||||
|
void debugger_toggleLoggingBreakpoint(uint32 address); // create/remove logging breakpoint
|
||||||
void debugger_toggleBreakpoint(uint32 address, bool state, DebuggerBreakpoint* bp);
|
void debugger_toggleBreakpoint(uint32 address, bool state, DebuggerBreakpoint* bp);
|
||||||
|
|
||||||
void debugger_createMemoryBreakpoint(uint32 address, bool onRead, bool onWrite);
|
void debugger_createMemoryBreakpoint(uint32 address, bool onRead, bool onWrite);
|
||||||
|
@ -114,6 +114,7 @@ void debugger_updateExecutionBreakpoint(uint32 address, bool forceRestore = fals
|
||||||
|
|
||||||
void debugger_createPatch(uint32 address, std::span<uint8> patchData);
|
void debugger_createPatch(uint32 address, std::span<uint8> patchData);
|
||||||
bool debugger_hasPatch(uint32 address);
|
bool debugger_hasPatch(uint32 address);
|
||||||
|
void debugger_removePatch(uint32 address);
|
||||||
|
|
||||||
void debugger_forceBreak(); // force breakpoint at the next possible instruction
|
void debugger_forceBreak(); // force breakpoint at the next possible instruction
|
||||||
bool debugger_isTrapped();
|
bool debugger_isTrapped();
|
||||||
|
|
|
@ -114,13 +114,13 @@ void* ATTR_MS_ABI PPCRecompiler_virtualHLE(PPCInterpreter_t* hCPU, uint32 hleFun
|
||||||
|
|
||||||
void ATTR_MS_ABI PPCRecompiler_getTBL(PPCInterpreter_t* hCPU, uint32 gprIndex)
|
void ATTR_MS_ABI PPCRecompiler_getTBL(PPCInterpreter_t* hCPU, uint32 gprIndex)
|
||||||
{
|
{
|
||||||
uint64 coreTime = coreinit::coreinit_getTimerTick();
|
uint64 coreTime = coreinit::OSGetSystemTime();
|
||||||
hCPU->gpr[gprIndex] = (uint32)(coreTime&0xFFFFFFFF);
|
hCPU->gpr[gprIndex] = (uint32)(coreTime&0xFFFFFFFF);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ATTR_MS_ABI PPCRecompiler_getTBU(PPCInterpreter_t* hCPU, uint32 gprIndex)
|
void ATTR_MS_ABI PPCRecompiler_getTBU(PPCInterpreter_t* hCPU, uint32 gprIndex)
|
||||||
{
|
{
|
||||||
uint64 coreTime = coreinit::coreinit_getTimerTick();
|
uint64 coreTime = coreinit::OSGetSystemTime();
|
||||||
hCPU->gpr[gprIndex] = (uint32)((coreTime>>32)&0xFFFFFFFF);
|
hCPU->gpr[gprIndex] = (uint32)((coreTime>>32)&0xFFFFFFFF);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -141,6 +141,14 @@ private:
|
||||||
|
|
||||||
void LatteCP_processCommandBuffer(DrawPassContext& drawPassCtx);
|
void LatteCP_processCommandBuffer(DrawPassContext& drawPassCtx);
|
||||||
|
|
||||||
|
// called whenever the GPU runs out of commands or hits a wait condition (semaphores, HLE waits)
|
||||||
|
void LatteCP_signalEnterWait()
|
||||||
|
{
|
||||||
|
// based on the assumption that games won't do a rugpull and swap out buffer data in the middle of an uninterrupted sequence of drawcalls,
|
||||||
|
// we only flush caches when the GPU goes idle or has to wait for any operation
|
||||||
|
LatteIndices_invalidateAll();
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Read a U32 from the command buffer
|
* Read a U32 from the command buffer
|
||||||
* If no data is available then wait in a busy loop
|
* If no data is available then wait in a busy loop
|
||||||
|
@ -466,6 +474,8 @@ LatteCMDPtr LatteCP_itWaitRegMem(LatteCMDPtr cmd, uint32 nWords)
|
||||||
const uint32 GPU7_WAIT_MEM_OP_GREATER = 6;
|
const uint32 GPU7_WAIT_MEM_OP_GREATER = 6;
|
||||||
const uint32 GPU7_WAIT_MEM_OP_NEVER = 7;
|
const uint32 GPU7_WAIT_MEM_OP_NEVER = 7;
|
||||||
|
|
||||||
|
LatteCP_signalEnterWait();
|
||||||
|
|
||||||
bool stalls = false;
|
bool stalls = false;
|
||||||
if ((word0 & 0x10) != 0)
|
if ((word0 & 0x10) != 0)
|
||||||
{
|
{
|
||||||
|
@ -594,6 +604,7 @@ LatteCMDPtr LatteCP_itMemSemaphore(LatteCMDPtr cmd, uint32 nWords)
|
||||||
else if(SEM_SIGNAL == 7)
|
else if(SEM_SIGNAL == 7)
|
||||||
{
|
{
|
||||||
// wait
|
// wait
|
||||||
|
LatteCP_signalEnterWait();
|
||||||
size_t loopCount = 0;
|
size_t loopCount = 0;
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
|
@ -788,7 +799,7 @@ LatteCMDPtr LatteCP_itHLESampleTimer(LatteCMDPtr cmd, uint32 nWords)
|
||||||
{
|
{
|
||||||
cemu_assert_debug(nWords == 1);
|
cemu_assert_debug(nWords == 1);
|
||||||
MPTR timerMPTR = (MPTR)LatteReadCMD();
|
MPTR timerMPTR = (MPTR)LatteReadCMD();
|
||||||
memory_writeU64(timerMPTR, coreinit::coreinit_getTimerTick());
|
memory_writeU64(timerMPTR, coreinit::OSGetSystemTime());
|
||||||
return cmd;
|
return cmd;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1305,11 +1316,13 @@ void LatteCP_processCommandBuffer(DrawPassContext& drawPassCtx)
|
||||||
}
|
}
|
||||||
case IT_HLE_TRIGGER_SCANBUFFER_SWAP:
|
case IT_HLE_TRIGGER_SCANBUFFER_SWAP:
|
||||||
{
|
{
|
||||||
|
LatteCP_signalEnterWait();
|
||||||
LatteCP_itHLESwapScanBuffer(cmdData, nWords);
|
LatteCP_itHLESwapScanBuffer(cmdData, nWords);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case IT_HLE_WAIT_FOR_FLIP:
|
case IT_HLE_WAIT_FOR_FLIP:
|
||||||
{
|
{
|
||||||
|
LatteCP_signalEnterWait();
|
||||||
LatteCP_itHLEWaitForFlip(cmdData, nWords);
|
LatteCP_itHLEWaitForFlip(cmdData, nWords);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -1594,12 +1607,14 @@ void LatteCP_ProcessRingbuffer()
|
||||||
}
|
}
|
||||||
case IT_HLE_TRIGGER_SCANBUFFER_SWAP:
|
case IT_HLE_TRIGGER_SCANBUFFER_SWAP:
|
||||||
{
|
{
|
||||||
|
LatteCP_signalEnterWait();
|
||||||
LatteCP_itHLESwapScanBuffer(cmd, nWords);
|
LatteCP_itHLESwapScanBuffer(cmd, nWords);
|
||||||
timerRecheck += CP_TIMER_RECHECK / 64;
|
timerRecheck += CP_TIMER_RECHECK / 64;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case IT_HLE_WAIT_FOR_FLIP:
|
case IT_HLE_WAIT_FOR_FLIP:
|
||||||
{
|
{
|
||||||
|
LatteCP_signalEnterWait();
|
||||||
LatteCP_itHLEWaitForFlip(cmd, nWords);
|
LatteCP_itHLEWaitForFlip(cmd, nWords);
|
||||||
timerRecheck += CP_TIMER_RECHECK / 1;
|
timerRecheck += CP_TIMER_RECHECK / 1;
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#include "Cafe/HW/Latte/Core/LatteConst.h"
|
#include "Cafe/HW/Latte/Core/LatteConst.h"
|
||||||
#include "Cafe/HW/Latte/Renderer/Renderer.h"
|
#include "Cafe/HW/Latte/Renderer/Renderer.h"
|
||||||
#include "Cafe/HW/Latte/ISA/RegDefines.h"
|
#include "Cafe/HW/Latte/ISA/RegDefines.h"
|
||||||
|
#include "Cafe/HW/Latte/Core/LattePerformanceMonitor.h"
|
||||||
#include "Common/cpu_features.h"
|
#include "Common/cpu_features.h"
|
||||||
|
|
||||||
#if defined(ARCH_X86_64) && defined(__GNUC__)
|
#if defined(ARCH_X86_64) && defined(__GNUC__)
|
||||||
|
@ -9,32 +10,53 @@
|
||||||
|
|
||||||
struct
|
struct
|
||||||
{
|
{
|
||||||
const void* lastPtr;
|
struct CacheEntry
|
||||||
uint32 lastCount;
|
{
|
||||||
LattePrimitiveMode lastPrimitiveMode;
|
// input data
|
||||||
LatteIndexType lastIndexType;
|
const void* lastPtr;
|
||||||
// output
|
uint32 lastCount;
|
||||||
uint32 indexMin;
|
LattePrimitiveMode lastPrimitiveMode;
|
||||||
uint32 indexMax;
|
LatteIndexType lastIndexType;
|
||||||
Renderer::INDEX_TYPE renderIndexType;
|
uint64 lastUsed;
|
||||||
uint32 outputCount;
|
// output
|
||||||
uint32 indexBufferOffset;
|
uint32 indexMin;
|
||||||
uint32 indexBufferIndex;
|
uint32 indexMax;
|
||||||
|
Renderer::INDEX_TYPE renderIndexType;
|
||||||
|
uint32 outputCount;
|
||||||
|
Renderer::IndexAllocation indexAllocation;
|
||||||
|
};
|
||||||
|
std::array<CacheEntry, 8> entry;
|
||||||
|
uint64 currentUsageCounter{0};
|
||||||
}LatteIndexCache{};
|
}LatteIndexCache{};
|
||||||
|
|
||||||
void LatteIndices_invalidate(const void* memPtr, uint32 size)
|
void LatteIndices_invalidate(const void* memPtr, uint32 size)
|
||||||
{
|
{
|
||||||
if (LatteIndexCache.lastPtr >= memPtr && (LatteIndexCache.lastPtr < ((uint8*)memPtr + size)) )
|
for(auto& entry : LatteIndexCache.entry)
|
||||||
{
|
{
|
||||||
LatteIndexCache.lastPtr = nullptr;
|
if (entry.lastPtr >= memPtr && (entry.lastPtr < ((uint8*)memPtr + size)) )
|
||||||
LatteIndexCache.lastCount = 0;
|
{
|
||||||
|
if(entry.lastPtr != nullptr)
|
||||||
|
g_renderer->indexData_releaseIndexMemory(entry.indexAllocation);
|
||||||
|
entry.lastPtr = nullptr;
|
||||||
|
entry.lastCount = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void LatteIndices_invalidateAll()
|
void LatteIndices_invalidateAll()
|
||||||
{
|
{
|
||||||
LatteIndexCache.lastPtr = nullptr;
|
for(auto& entry : LatteIndexCache.entry)
|
||||||
LatteIndexCache.lastCount = 0;
|
{
|
||||||
|
if (entry.lastPtr != nullptr)
|
||||||
|
g_renderer->indexData_releaseIndexMemory(entry.indexAllocation);
|
||||||
|
entry.lastPtr = nullptr;
|
||||||
|
entry.lastCount = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64 LatteIndices_GetNextUsageIndex()
|
||||||
|
{
|
||||||
|
return LatteIndexCache.currentUsageCounter++;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32 LatteIndices_calculateIndexOutputSize(LattePrimitiveMode primitiveMode, LatteIndexType indexType, uint32 count)
|
uint32 LatteIndices_calculateIndexOutputSize(LattePrimitiveMode primitiveMode, LatteIndexType indexType, uint32 count)
|
||||||
|
@ -532,7 +554,7 @@ void LatteIndices_alternativeCalculateIndexMinMax(const void* indexData, LatteIn
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void LatteIndices_decode(const void* indexData, LatteIndexType indexType, uint32 count, LattePrimitiveMode primitiveMode, uint32& indexMin, uint32& indexMax, Renderer::INDEX_TYPE& renderIndexType, uint32& outputCount, uint32& indexBufferOffset, uint32& indexBufferIndex)
|
void LatteIndices_decode(const void* indexData, LatteIndexType indexType, uint32 count, LattePrimitiveMode primitiveMode, uint32& indexMin, uint32& indexMax, Renderer::INDEX_TYPE& renderIndexType, uint32& outputCount, Renderer::IndexAllocation& indexAllocation)
|
||||||
{
|
{
|
||||||
// what this should do:
|
// what this should do:
|
||||||
// [x] use fast SIMD-based index decoding
|
// [x] use fast SIMD-based index decoding
|
||||||
|
@ -542,17 +564,18 @@ void LatteIndices_decode(const void* indexData, LatteIndexType indexType, uint32
|
||||||
// [ ] better cache implementation, allow to cache across frames
|
// [ ] better cache implementation, allow to cache across frames
|
||||||
|
|
||||||
// reuse from cache if data didn't change
|
// reuse from cache if data didn't change
|
||||||
if (LatteIndexCache.lastPtr == indexData &&
|
auto cacheEntry = std::find_if(LatteIndexCache.entry.begin(), LatteIndexCache.entry.end(), [indexData, count, primitiveMode, indexType](const auto& entry)
|
||||||
LatteIndexCache.lastCount == count &&
|
|
||||||
LatteIndexCache.lastPrimitiveMode == primitiveMode &&
|
|
||||||
LatteIndexCache.lastIndexType == indexType)
|
|
||||||
{
|
{
|
||||||
indexMin = LatteIndexCache.indexMin;
|
return entry.lastPtr == indexData && entry.lastCount == count && entry.lastPrimitiveMode == primitiveMode && entry.lastIndexType == indexType;
|
||||||
indexMax = LatteIndexCache.indexMax;
|
});
|
||||||
renderIndexType = LatteIndexCache.renderIndexType;
|
if (cacheEntry != LatteIndexCache.entry.end())
|
||||||
outputCount = LatteIndexCache.outputCount;
|
{
|
||||||
indexBufferOffset = LatteIndexCache.indexBufferOffset;
|
indexMin = cacheEntry->indexMin;
|
||||||
indexBufferIndex = LatteIndexCache.indexBufferIndex;
|
indexMax = cacheEntry->indexMax;
|
||||||
|
renderIndexType = cacheEntry->renderIndexType;
|
||||||
|
outputCount = cacheEntry->outputCount;
|
||||||
|
indexAllocation = cacheEntry->indexAllocation;
|
||||||
|
cacheEntry->lastUsed = LatteIndices_GetNextUsageIndex();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -576,10 +599,12 @@ void LatteIndices_decode(const void* indexData, LatteIndexType indexType, uint32
|
||||||
indexMin = 0;
|
indexMin = 0;
|
||||||
indexMax = std::max(count, 1u)-1;
|
indexMax = std::max(count, 1u)-1;
|
||||||
renderIndexType = Renderer::INDEX_TYPE::NONE;
|
renderIndexType = Renderer::INDEX_TYPE::NONE;
|
||||||
|
indexAllocation = {};
|
||||||
return; // no indices
|
return; // no indices
|
||||||
}
|
}
|
||||||
// query index buffer from renderer
|
// query index buffer from renderer
|
||||||
void* indexOutputPtr = g_renderer->indexData_reserveIndexMemory(indexOutputSize, indexBufferOffset, indexBufferIndex);
|
indexAllocation = g_renderer->indexData_reserveIndexMemory(indexOutputSize);
|
||||||
|
void* indexOutputPtr = indexAllocation.mem;
|
||||||
|
|
||||||
// decode indices
|
// decode indices
|
||||||
indexMin = std::numeric_limits<uint32>::max();
|
indexMin = std::numeric_limits<uint32>::max();
|
||||||
|
@ -704,16 +729,25 @@ void LatteIndices_decode(const void* indexData, LatteIndexType indexType, uint32
|
||||||
// recalculate index range but filter out primitive restart index
|
// recalculate index range but filter out primitive restart index
|
||||||
LatteIndices_alternativeCalculateIndexMinMax(indexData, indexType, count, indexMin, indexMax);
|
LatteIndices_alternativeCalculateIndexMinMax(indexData, indexType, count, indexMin, indexMax);
|
||||||
}
|
}
|
||||||
g_renderer->indexData_uploadIndexMemory(indexBufferOffset, indexOutputSize);
|
g_renderer->indexData_uploadIndexMemory(indexAllocation);
|
||||||
|
performanceMonitor.cycle[performanceMonitor.cycleIndex].indexDataUploaded += indexOutputSize;
|
||||||
|
// get least recently used cache entry
|
||||||
|
auto lruEntry = std::min_element(LatteIndexCache.entry.begin(), LatteIndexCache.entry.end(), [](const auto& a, const auto& b)
|
||||||
|
{
|
||||||
|
return a.lastUsed < b.lastUsed;
|
||||||
|
});
|
||||||
|
// invalidate previous allocation
|
||||||
|
if(lruEntry->lastPtr != nullptr)
|
||||||
|
g_renderer->indexData_releaseIndexMemory(lruEntry->indexAllocation);
|
||||||
// update cache
|
// update cache
|
||||||
LatteIndexCache.lastPtr = indexData;
|
lruEntry->lastPtr = indexData;
|
||||||
LatteIndexCache.lastCount = count;
|
lruEntry->lastCount = count;
|
||||||
LatteIndexCache.lastPrimitiveMode = primitiveMode;
|
lruEntry->lastPrimitiveMode = primitiveMode;
|
||||||
LatteIndexCache.lastIndexType = indexType;
|
lruEntry->lastIndexType = indexType;
|
||||||
LatteIndexCache.indexMin = indexMin;
|
lruEntry->indexMin = indexMin;
|
||||||
LatteIndexCache.indexMax = indexMax;
|
lruEntry->indexMax = indexMax;
|
||||||
LatteIndexCache.renderIndexType = renderIndexType;
|
lruEntry->renderIndexType = renderIndexType;
|
||||||
LatteIndexCache.outputCount = outputCount;
|
lruEntry->outputCount = outputCount;
|
||||||
LatteIndexCache.indexBufferOffset = indexBufferOffset;
|
lruEntry->indexAllocation = indexAllocation;
|
||||||
LatteIndexCache.indexBufferIndex = indexBufferIndex;
|
lruEntry->lastUsed = LatteIndices_GetNextUsageIndex();
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,4 +4,4 @@
|
||||||
|
|
||||||
void LatteIndices_invalidate(const void* memPtr, uint32 size);
|
void LatteIndices_invalidate(const void* memPtr, uint32 size);
|
||||||
void LatteIndices_invalidateAll();
|
void LatteIndices_invalidateAll();
|
||||||
void LatteIndices_decode(const void* indexData, LatteIndexType indexType, uint32 count, LattePrimitiveMode primitiveMode, uint32& indexMin, uint32& indexMax, Renderer::INDEX_TYPE& renderIndexType, uint32& outputCount, uint32& indexBufferOffset, uint32& indexBufferIndex);
|
void LatteIndices_decode(const void* indexData, LatteIndexType indexType, uint32 count, LattePrimitiveMode primitiveMode, uint32& indexMin, uint32& indexMax, Renderer::INDEX_TYPE& renderIndexType, uint32& outputCount, Renderer::IndexAllocation& indexAllocation);
|
|
@ -107,7 +107,13 @@ void LatteOverlay_renderOverlay(ImVec2& position, ImVec2& pivot, sint32 directio
|
||||||
ImGui::Text("VRAM: %dMB / %dMB", g_state.vramUsage, g_state.vramTotal);
|
ImGui::Text("VRAM: %dMB / %dMB", g_state.vramUsage, g_state.vramTotal);
|
||||||
|
|
||||||
if (config.overlay.debug)
|
if (config.overlay.debug)
|
||||||
|
{
|
||||||
|
// general debug info
|
||||||
|
ImGui::Text("--- Debug info ---");
|
||||||
|
ImGui::Text("IndexUploadPerFrame: %dKB", (performanceMonitor.stats.indexDataUploadPerFrame+1023)/1024);
|
||||||
|
// backend specific info
|
||||||
g_renderer->AppendOverlayDebugInfo();
|
g_renderer->AppendOverlayDebugInfo();
|
||||||
|
}
|
||||||
|
|
||||||
position.y += (ImGui::GetWindowSize().y + 10.0f) * direction;
|
position.y += (ImGui::GetWindowSize().y + 10.0f) * direction;
|
||||||
}
|
}
|
||||||
|
|
|
@ -74,7 +74,6 @@ void LattePerformanceMonitor_frameEnd()
|
||||||
uniformBankDataUploadedPerFrame /= 1024ULL;
|
uniformBankDataUploadedPerFrame /= 1024ULL;
|
||||||
uint32 uniformBankCountUploadedPerFrame = (uint32)(uniformBankUploadedCount / (uint64)elapsedFrames);
|
uint32 uniformBankCountUploadedPerFrame = (uint32)(uniformBankUploadedCount / (uint64)elapsedFrames);
|
||||||
uint64 indexDataUploadPerFrame = (indexDataUploaded / (uint64)elapsedFrames);
|
uint64 indexDataUploadPerFrame = (indexDataUploaded / (uint64)elapsedFrames);
|
||||||
indexDataUploadPerFrame /= 1024ULL;
|
|
||||||
|
|
||||||
double fps = (double)elapsedFrames2S * 1000.0 / (double)totalElapsedTimeFPS;
|
double fps = (double)elapsedFrames2S * 1000.0 / (double)totalElapsedTimeFPS;
|
||||||
uint32 shaderBindsPerFrame = shaderBindCounter / elapsedFrames;
|
uint32 shaderBindsPerFrame = shaderBindCounter / elapsedFrames;
|
||||||
|
@ -82,7 +81,7 @@ void LattePerformanceMonitor_frameEnd()
|
||||||
uint32 rlps = (uint32)((uint64)recompilerLeaveCount * 1000ULL / (uint64)totalElapsedTime);
|
uint32 rlps = (uint32)((uint64)recompilerLeaveCount * 1000ULL / (uint64)totalElapsedTime);
|
||||||
uint32 tlps = (uint32)((uint64)threadLeaveCount * 1000ULL / (uint64)totalElapsedTime);
|
uint32 tlps = (uint32)((uint64)threadLeaveCount * 1000ULL / (uint64)totalElapsedTime);
|
||||||
// set stats
|
// set stats
|
||||||
|
performanceMonitor.stats.indexDataUploadPerFrame = indexDataUploadPerFrame;
|
||||||
// next counter cycle
|
// next counter cycle
|
||||||
sint32 nextCycleIndex = (performanceMonitor.cycleIndex + 1) % PERFORMANCE_MONITOR_TRACK_CYCLES;
|
sint32 nextCycleIndex = (performanceMonitor.cycleIndex + 1) % PERFORMANCE_MONITOR_TRACK_CYCLES;
|
||||||
performanceMonitor.cycle[nextCycleIndex].drawCallCounter = 0;
|
performanceMonitor.cycle[nextCycleIndex].drawCallCounter = 0;
|
||||||
|
|
|
@ -124,6 +124,7 @@ typedef struct
|
||||||
LattePerfStatCounter numGraphicPipelines;
|
LattePerfStatCounter numGraphicPipelines;
|
||||||
LattePerfStatCounter numImages;
|
LattePerfStatCounter numImages;
|
||||||
LattePerfStatCounter numImageViews;
|
LattePerfStatCounter numImageViews;
|
||||||
|
LattePerfStatCounter numSamplers;
|
||||||
LattePerfStatCounter numRenderPass;
|
LattePerfStatCounter numRenderPass;
|
||||||
LattePerfStatCounter numFramebuffer;
|
LattePerfStatCounter numFramebuffer;
|
||||||
|
|
||||||
|
@ -131,6 +132,12 @@ typedef struct
|
||||||
LattePerfStatCounter numDrawBarriersPerFrame;
|
LattePerfStatCounter numDrawBarriersPerFrame;
|
||||||
LattePerfStatCounter numBeginRenderpassPerFrame;
|
LattePerfStatCounter numBeginRenderpassPerFrame;
|
||||||
}vk;
|
}vk;
|
||||||
|
|
||||||
|
// calculated stats (per frame)
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
uint32 indexDataUploadPerFrame;
|
||||||
|
}stats;
|
||||||
}performanceMonitor_t;
|
}performanceMonitor_t;
|
||||||
|
|
||||||
extern performanceMonitor_t performanceMonitor;
|
extern performanceMonitor_t performanceMonitor;
|
||||||
|
|
|
@ -11,7 +11,6 @@
|
||||||
#include "Cafe/HW/Latte/Core/LattePerformanceMonitor.h"
|
#include "Cafe/HW/Latte/Core/LattePerformanceMonitor.h"
|
||||||
#include "Cafe/GraphicPack/GraphicPack2.h"
|
#include "Cafe/GraphicPack/GraphicPack2.h"
|
||||||
#include "config/ActiveSettings.h"
|
#include "config/ActiveSettings.h"
|
||||||
#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h"
|
|
||||||
#include "gui/guiWrapper.h"
|
#include "gui/guiWrapper.h"
|
||||||
#include "Cafe/OS/libs/erreula/erreula.h"
|
#include "Cafe/OS/libs/erreula/erreula.h"
|
||||||
#include "input/InputManager.h"
|
#include "input/InputManager.h"
|
||||||
|
|
|
@ -451,9 +451,8 @@ void LatteShader_DumpShader(uint64 baseHash, uint64 auxHash, LatteDecompilerShad
|
||||||
suffix = "gs";
|
suffix = "gs";
|
||||||
else if (shader->shaderType == LatteConst::ShaderType::Pixel)
|
else if (shader->shaderType == LatteConst::ShaderType::Pixel)
|
||||||
suffix = "ps";
|
suffix = "ps";
|
||||||
fs::path dumpPath = "dump/shaders";
|
|
||||||
dumpPath /= fmt::format("{:016x}_{:016x}_{}.txt", baseHash, auxHash, suffix);
|
FileStream* fs = FileStream::createFile2(ActiveSettings::GetUserDataPath("dump/shaders/{:016x}_{:016x}_{}.txt", baseHash, auxHash, suffix));
|
||||||
FileStream* fs = FileStream::createFile2(dumpPath);
|
|
||||||
if (fs)
|
if (fs)
|
||||||
{
|
{
|
||||||
if (shader->strBuf_shaderSource)
|
if (shader->strBuf_shaderSource)
|
||||||
|
@ -479,9 +478,8 @@ void LatteShader_DumpRawShader(uint64 baseHash, uint64 auxHash, uint32 type, uin
|
||||||
suffix = "copy";
|
suffix = "copy";
|
||||||
else if (type == SHADER_DUMP_TYPE_COMPUTE)
|
else if (type == SHADER_DUMP_TYPE_COMPUTE)
|
||||||
suffix = "compute";
|
suffix = "compute";
|
||||||
fs::path dumpPath = "dump/shaders";
|
|
||||||
dumpPath /= fmt::format("{:016x}_{:016x}_{}.bin", baseHash, auxHash, suffix);
|
FileStream* fs = FileStream::createFile2(ActiveSettings::GetUserDataPath("dump/shaders/{:016x}_{:016x}_{}.bin", baseHash, auxHash, suffix));
|
||||||
FileStream* fs = FileStream::createFile2(dumpPath);
|
|
||||||
if (fs)
|
if (fs)
|
||||||
{
|
{
|
||||||
fs->writeData(programCode, programLen);
|
fs->writeData(programCode, programLen);
|
||||||
|
|
|
@ -25,6 +25,9 @@
|
||||||
#include "util/helpers/Serializer.h"
|
#include "util/helpers/Serializer.h"
|
||||||
|
|
||||||
#include <wx/msgdlg.h>
|
#include <wx/msgdlg.h>
|
||||||
|
#include <audio/IAudioAPI.h>
|
||||||
|
#include <util/bootSound/BootSoundReader.h>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
#if BOOST_OS_WINDOWS
|
#if BOOST_OS_WINDOWS
|
||||||
#include <psapi.h>
|
#include <psapi.h>
|
||||||
|
@ -155,6 +158,118 @@ bool LoadTGAFile(const std::vector<uint8>& buffer, TGAFILE *tgaFile)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class BootSoundPlayer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
BootSoundPlayer() = default;
|
||||||
|
~BootSoundPlayer()
|
||||||
|
{
|
||||||
|
m_stopRequested = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void StartSound()
|
||||||
|
{
|
||||||
|
if (!m_bootSndPlayThread.joinable())
|
||||||
|
{
|
||||||
|
m_fadeOutRequested = false;
|
||||||
|
m_stopRequested = false;
|
||||||
|
m_bootSndPlayThread = std::thread{[this]() {
|
||||||
|
StreamBootSound();
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FadeOutSound()
|
||||||
|
{
|
||||||
|
m_fadeOutRequested = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ApplyFadeOutEffect(std::span<sint16> samples, uint64& fadeOutSample, uint64 fadeOutDuration)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < samples.size(); i += 2)
|
||||||
|
{
|
||||||
|
const float decibel = (float)fadeOutSample / fadeOutDuration * -60.0f;
|
||||||
|
const float volumeFactor = pow(10, decibel / 20);
|
||||||
|
samples[i] *= volumeFactor;
|
||||||
|
samples[i + 1] *= volumeFactor;
|
||||||
|
fadeOutSample++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void StreamBootSound()
|
||||||
|
{
|
||||||
|
SetThreadName("bootsnd");
|
||||||
|
constexpr sint32 sampleRate = 48'000;
|
||||||
|
constexpr sint32 bitsPerSample = 16;
|
||||||
|
constexpr sint32 samplesPerBlock = sampleRate / 10; // block is 1/10th of a second
|
||||||
|
constexpr sint32 nChannels = 2;
|
||||||
|
static_assert(bitsPerSample % 8 == 0, "bits per sample is not a multiple of 8");
|
||||||
|
|
||||||
|
AudioAPIPtr bootSndAudioDev;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
bootSndAudioDev = IAudioAPI::CreateDeviceFromConfig(true, sampleRate, nChannels, samplesPerBlock, bitsPerSample);
|
||||||
|
if(!bootSndAudioDev)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
catch (const std::runtime_error& ex)
|
||||||
|
{
|
||||||
|
cemuLog_log(LogType::Force, "Failed to initialise audio device for bootup sound");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
bootSndAudioDev->SetAudioDelayOverride(4);
|
||||||
|
bootSndAudioDev->Play();
|
||||||
|
|
||||||
|
std::string sndPath = fmt::format("{}/meta/{}", CafeSystem::GetMlcStoragePath(CafeSystem::GetForegroundTitleId()), "bootSound.btsnd");
|
||||||
|
sint32 fscStatus = FSC_STATUS_UNDEFINED;
|
||||||
|
|
||||||
|
if(!fsc_doesFileExist(sndPath.c_str()))
|
||||||
|
return;
|
||||||
|
|
||||||
|
FSCVirtualFile* bootSndFileHandle = fsc_open(sndPath.c_str(), FSC_ACCESS_FLAG::OPEN_FILE | FSC_ACCESS_FLAG::READ_PERMISSION, &fscStatus);
|
||||||
|
if(!bootSndFileHandle)
|
||||||
|
{
|
||||||
|
cemuLog_log(LogType::Force, "failed to open bootSound.btsnd");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr sint32 audioBlockSize = samplesPerBlock * (bitsPerSample/8) * nChannels;
|
||||||
|
BootSoundReader bootSndFileReader(bootSndFileHandle, audioBlockSize);
|
||||||
|
|
||||||
|
uint64 fadeOutSample = 0; // track how far into the fadeout
|
||||||
|
constexpr uint64 fadeOutDuration = sampleRate * 2; // fadeout should last 2 seconds
|
||||||
|
while(fadeOutSample < fadeOutDuration && !m_stopRequested)
|
||||||
|
{
|
||||||
|
while (bootSndAudioDev->NeedAdditionalBlocks())
|
||||||
|
{
|
||||||
|
sint16* data = bootSndFileReader.getSamples();
|
||||||
|
if(data == nullptr)
|
||||||
|
{
|
||||||
|
// break outer loop
|
||||||
|
m_stopRequested = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if(m_fadeOutRequested)
|
||||||
|
ApplyFadeOutEffect({data, samplesPerBlock * nChannels}, fadeOutSample, fadeOutDuration);
|
||||||
|
|
||||||
|
bootSndAudioDev->FeedBlock(data);
|
||||||
|
}
|
||||||
|
// sleep for the duration of a single block
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(samplesPerBlock / (sampleRate/ 1'000)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(bootSndFileHandle)
|
||||||
|
fsc_close(bootSndFileHandle);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::thread m_bootSndPlayThread;
|
||||||
|
std::atomic_bool m_fadeOutRequested = false;
|
||||||
|
std::atomic_bool m_stopRequested = false;
|
||||||
|
};
|
||||||
|
static BootSoundPlayer g_bootSndPlayer;
|
||||||
|
|
||||||
void LatteShaderCache_finish()
|
void LatteShaderCache_finish()
|
||||||
{
|
{
|
||||||
if (g_renderer->GetType() == RendererAPI::Vulkan)
|
if (g_renderer->GetType() == RendererAPI::Vulkan)
|
||||||
|
@ -299,6 +414,9 @@ void LatteShaderCache_Load()
|
||||||
loadBackgroundTexture(true, g_shaderCacheLoaderState.textureTVId);
|
loadBackgroundTexture(true, g_shaderCacheLoaderState.textureTVId);
|
||||||
loadBackgroundTexture(false, g_shaderCacheLoaderState.textureDRCId);
|
loadBackgroundTexture(false, g_shaderCacheLoaderState.textureDRCId);
|
||||||
|
|
||||||
|
if(GetConfig().play_boot_sound)
|
||||||
|
g_bootSndPlayer.StartSound();
|
||||||
|
|
||||||
sint32 numLoadedShaders = 0;
|
sint32 numLoadedShaders = 0;
|
||||||
uint32 loadIndex = 0;
|
uint32 loadIndex = 0;
|
||||||
|
|
||||||
|
@ -365,6 +483,11 @@ void LatteShaderCache_Load()
|
||||||
g_renderer->DeleteTexture(g_shaderCacheLoaderState.textureTVId);
|
g_renderer->DeleteTexture(g_shaderCacheLoaderState.textureTVId);
|
||||||
if (g_shaderCacheLoaderState.textureDRCId)
|
if (g_shaderCacheLoaderState.textureDRCId)
|
||||||
g_renderer->DeleteTexture(g_shaderCacheLoaderState.textureDRCId);
|
g_renderer->DeleteTexture(g_shaderCacheLoaderState.textureDRCId);
|
||||||
|
|
||||||
|
g_bootSndPlayer.FadeOutSound();
|
||||||
|
|
||||||
|
if(Latte_GetStopSignal())
|
||||||
|
LatteThread_Exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
void LatteShaderCache_ShowProgress(const std::function <bool(void)>& loadUpdateFunc, bool isPipelines)
|
void LatteShaderCache_ShowProgress(const std::function <bool(void)>& loadUpdateFunc, bool isPipelines)
|
||||||
|
@ -505,8 +628,6 @@ void LatteShaderCache_LoadVulkanPipelineCache(uint64 cacheTitleId)
|
||||||
g_shaderCacheLoaderState.loadedPipelines = 0;
|
g_shaderCacheLoaderState.loadedPipelines = 0;
|
||||||
LatteShaderCache_ShowProgress(LatteShaderCache_updatePipelineLoadingProgress, true);
|
LatteShaderCache_ShowProgress(LatteShaderCache_updatePipelineLoadingProgress, true);
|
||||||
pipelineCache.EndLoading();
|
pipelineCache.EndLoading();
|
||||||
if(Latte_GetStopSignal())
|
|
||||||
LatteThread_Exit();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool LatteShaderCache_updatePipelineLoadingProgress()
|
bool LatteShaderCache_updatePipelineLoadingProgress()
|
||||||
|
|
|
@ -257,6 +257,7 @@ void LatteThread_Exit()
|
||||||
LatteSHRC_UnloadAll();
|
LatteSHRC_UnloadAll();
|
||||||
// close disk cache
|
// close disk cache
|
||||||
LatteShaderCache_Close();
|
LatteShaderCache_Close();
|
||||||
|
RendererOutputShader::ShutdownStatic();
|
||||||
// destroy renderer but make sure that g_renderer remains valid until the destructor has finished
|
// destroy renderer but make sure that g_renderer remains valid until the destructor has finished
|
||||||
if (g_renderer)
|
if (g_renderer)
|
||||||
{
|
{
|
||||||
|
|
|
@ -102,16 +102,21 @@ public:
|
||||||
static void SetAttributeArrayState(uint32 index, bool isEnabled, sint32 aluDivisor);
|
static void SetAttributeArrayState(uint32 index, bool isEnabled, sint32 aluDivisor);
|
||||||
static void SetArrayElementBuffer(GLuint arrayElementBuffer);
|
static void SetArrayElementBuffer(GLuint arrayElementBuffer);
|
||||||
|
|
||||||
// index
|
// index (not used by OpenGL renderer yet)
|
||||||
void* indexData_reserveIndexMemory(uint32 size, uint32& offset, uint32& bufferIndex) override
|
IndexAllocation indexData_reserveIndexMemory(uint32 size) override
|
||||||
{
|
{
|
||||||
assert_dbg();
|
cemu_assert_unimplemented();
|
||||||
return nullptr;
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
void indexData_uploadIndexMemory(uint32 offset, uint32 size) override
|
void indexData_releaseIndexMemory(IndexAllocation& allocation) override
|
||||||
{
|
{
|
||||||
assert_dbg();
|
cemu_assert_unimplemented();
|
||||||
|
}
|
||||||
|
|
||||||
|
void indexData_uploadIndexMemory(IndexAllocation& allocation) override
|
||||||
|
{
|
||||||
|
cemu_assert_unimplemented();
|
||||||
}
|
}
|
||||||
|
|
||||||
// uniform
|
// uniform
|
||||||
|
|
|
@ -138,8 +138,15 @@ public:
|
||||||
virtual void draw_endSequence() = 0;
|
virtual void draw_endSequence() = 0;
|
||||||
|
|
||||||
// index
|
// index
|
||||||
virtual void* indexData_reserveIndexMemory(uint32 size, uint32& offset, uint32& bufferIndex) = 0;
|
struct IndexAllocation
|
||||||
virtual void indexData_uploadIndexMemory(uint32 offset, uint32 size) = 0;
|
{
|
||||||
|
void* mem; // pointer to index data inside buffer
|
||||||
|
void* rendererInternal; // for renderer use
|
||||||
|
};
|
||||||
|
|
||||||
|
virtual IndexAllocation indexData_reserveIndexMemory(uint32 size) = 0;
|
||||||
|
virtual void indexData_releaseIndexMemory(IndexAllocation& allocation) = 0;
|
||||||
|
virtual void indexData_uploadIndexMemory(IndexAllocation& allocation) = 0;
|
||||||
|
|
||||||
// occlusion queries
|
// occlusion queries
|
||||||
virtual LatteQueryObject* occlusionQuery_create() = 0;
|
virtual LatteQueryObject* occlusionQuery_create() = 0;
|
||||||
|
|
|
@ -118,8 +118,8 @@ RendererOutputShader::RendererOutputShader(const std::string& vertex_source, con
|
||||||
{
|
{
|
||||||
auto finalFragmentSrc = PrependFragmentPreamble(fragment_source);
|
auto finalFragmentSrc = PrependFragmentPreamble(fragment_source);
|
||||||
|
|
||||||
m_vertex_shader = g_renderer->shader_create(RendererShader::ShaderType::kVertex, 0, 0, vertex_source, false, false);
|
m_vertex_shader.reset(g_renderer->shader_create(RendererShader::ShaderType::kVertex, 0, 0, vertex_source, false, false));
|
||||||
m_fragment_shader = g_renderer->shader_create(RendererShader::ShaderType::kFragment, 0, 0, finalFragmentSrc, false, false);
|
m_fragment_shader.reset(g_renderer->shader_create(RendererShader::ShaderType::kFragment, 0, 0, finalFragmentSrc, false, false));
|
||||||
|
|
||||||
m_vertex_shader->PreponeCompilation(true);
|
m_vertex_shader->PreponeCompilation(true);
|
||||||
m_fragment_shader->PreponeCompilation(true);
|
m_fragment_shader->PreponeCompilation(true);
|
||||||
|
@ -169,8 +169,8 @@ void RendererOutputShader::SetUniformParameters(const LatteTextureView& texture_
|
||||||
shader->SetUniform2fv(locations.m_loc_outputResolution, res, 1);
|
shader->SetUniform2fv(locations.m_loc_outputResolution, res, 1);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
setUniforms(m_vertex_shader, m_uniformLocations[0]);
|
setUniforms(m_vertex_shader.get(), m_uniformLocations[0]);
|
||||||
setUniforms(m_fragment_shader, m_uniformLocations[1]);
|
setUniforms(m_fragment_shader.get(), m_uniformLocations[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
RendererOutputShader* RendererOutputShader::s_copy_shader;
|
RendererOutputShader* RendererOutputShader::s_copy_shader;
|
||||||
|
@ -187,8 +187,8 @@ std::string RendererOutputShader::GetOpenGlVertexSource(bool render_upside_down)
|
||||||
// vertex shader
|
// vertex shader
|
||||||
std::ostringstream vertex_source;
|
std::ostringstream vertex_source;
|
||||||
vertex_source <<
|
vertex_source <<
|
||||||
R"(#version 400
|
R"(#version 420
|
||||||
out vec2 passUV;
|
layout(location = 0) smooth out vec2 passUV;
|
||||||
|
|
||||||
out gl_PerVertex
|
out gl_PerVertex
|
||||||
{
|
{
|
||||||
|
@ -297,7 +297,7 @@ uniform vec2 nativeResolution;
|
||||||
uniform vec2 outputResolution;
|
uniform vec2 outputResolution;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
layout(location = 0) in vec2 passUV;
|
layout(location = 0) smooth in vec2 passUV;
|
||||||
layout(binding = 0) uniform sampler2D textureSrc;
|
layout(binding = 0) uniform sampler2D textureSrc;
|
||||||
layout(location = 0) out vec4 colorOut0;
|
layout(location = 0) out vec4 colorOut0;
|
||||||
)" + shaderSrc;
|
)" + shaderSrc;
|
||||||
|
@ -325,3 +325,15 @@ void RendererOutputShader::InitializeStatic()
|
||||||
s_hermit_shader = new RendererOutputShader(vertex_source, s_hermite_shader_source);
|
s_hermit_shader = new RendererOutputShader(vertex_source, s_hermite_shader_source);
|
||||||
s_hermit_shader_ud = new RendererOutputShader(vertex_source_ud, s_hermite_shader_source);
|
s_hermit_shader_ud = new RendererOutputShader(vertex_source_ud, s_hermite_shader_source);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void RendererOutputShader::ShutdownStatic()
|
||||||
|
{
|
||||||
|
delete s_copy_shader;
|
||||||
|
delete s_copy_shader_ud;
|
||||||
|
|
||||||
|
delete s_bicubic_shader;
|
||||||
|
delete s_bicubic_shader_ud;
|
||||||
|
|
||||||
|
delete s_hermit_shader;
|
||||||
|
delete s_hermit_shader_ud;
|
||||||
|
}
|
||||||
|
|
|
@ -21,15 +21,16 @@ public:
|
||||||
|
|
||||||
RendererShader* GetVertexShader() const
|
RendererShader* GetVertexShader() const
|
||||||
{
|
{
|
||||||
return m_vertex_shader;
|
return m_vertex_shader.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
RendererShader* GetFragmentShader() const
|
RendererShader* GetFragmentShader() const
|
||||||
{
|
{
|
||||||
return m_fragment_shader;
|
return m_fragment_shader.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
static void InitializeStatic();
|
static void InitializeStatic();
|
||||||
|
static void ShutdownStatic();
|
||||||
|
|
||||||
static RendererOutputShader* s_copy_shader;
|
static RendererOutputShader* s_copy_shader;
|
||||||
static RendererOutputShader* s_copy_shader_ud;
|
static RendererOutputShader* s_copy_shader_ud;
|
||||||
|
@ -46,8 +47,8 @@ public:
|
||||||
static std::string PrependFragmentPreamble(const std::string& shaderSrc);
|
static std::string PrependFragmentPreamble(const std::string& shaderSrc);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
RendererShader* m_vertex_shader;
|
std::unique_ptr<RendererShader> m_vertex_shader;
|
||||||
RendererShader* m_fragment_shader;
|
std::unique_ptr<RendererShader> m_fragment_shader;
|
||||||
|
|
||||||
struct UniformLocations
|
struct UniformLocations
|
||||||
{
|
{
|
||||||
|
|
|
@ -211,6 +211,9 @@ RendererShaderVk::~RendererShaderVk()
|
||||||
{
|
{
|
||||||
while (!list_pipelineInfo.empty())
|
while (!list_pipelineInfo.empty())
|
||||||
delete list_pipelineInfo[0];
|
delete list_pipelineInfo[0];
|
||||||
|
|
||||||
|
VkDevice vkDev = VulkanRenderer::GetInstance()->GetLogicalDevice();
|
||||||
|
vkDestroyShaderModule(vkDev, m_shader_module, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
void RendererShaderVk::Init()
|
void RendererShaderVk::Init()
|
||||||
|
|
|
@ -60,7 +60,7 @@ void SwapchainInfoVk::Create()
|
||||||
VkAttachmentDescription colorAttachment = {};
|
VkAttachmentDescription colorAttachment = {};
|
||||||
colorAttachment.format = m_surfaceFormat.format;
|
colorAttachment.format = m_surfaceFormat.format;
|
||||||
colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
|
colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
|
||||||
colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
|
colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
||||||
colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
|
colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
|
||||||
colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
||||||
colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
||||||
|
|
|
@ -70,6 +70,7 @@ struct SwapchainInfoVk
|
||||||
VkSurfaceFormatKHR m_surfaceFormat{};
|
VkSurfaceFormatKHR m_surfaceFormat{};
|
||||||
VkSwapchainKHR m_swapchain{};
|
VkSwapchainKHR m_swapchain{};
|
||||||
Vector2i m_desiredExtent{};
|
Vector2i m_desiredExtent{};
|
||||||
|
VkExtent2D m_actualExtent{};
|
||||||
uint32 swapchainImageIndex = (uint32)-1;
|
uint32 swapchainImageIndex = (uint32)-1;
|
||||||
uint64 m_presentId = 1;
|
uint64 m_presentId = 1;
|
||||||
uint64 m_queueDepth = 0; // number of frames with pending presentation requests
|
uint64 m_queueDepth = 0; // number of frames with pending presentation requests
|
||||||
|
@ -92,5 +93,4 @@ private:
|
||||||
VkSemaphore m_currentSemaphore = VK_NULL_HANDLE;
|
VkSemaphore m_currentSemaphore = VK_NULL_HANDLE;
|
||||||
|
|
||||||
std::array<uint32, 2> m_swapchainQueueFamilyIndices;
|
std::array<uint32, 2> m_swapchainQueueFamilyIndices;
|
||||||
VkExtent2D m_actualExtent{};
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -22,7 +22,7 @@ uint32 LatteTextureReadbackInfoVk::GetImageSize(LatteTextureView* textureView)
|
||||||
cemu_assert(textureFormat == VK_FORMAT_R8G8B8A8_UNORM);
|
cemu_assert(textureFormat == VK_FORMAT_R8G8B8A8_UNORM);
|
||||||
return baseTexture->width * baseTexture->height * 4;
|
return baseTexture->width * baseTexture->height * 4;
|
||||||
}
|
}
|
||||||
else if (textureView->format == Latte::E_GX2SURFFMT::R8_UNORM)
|
else if (textureView->format == Latte::E_GX2SURFFMT::R8_UNORM )
|
||||||
{
|
{
|
||||||
cemu_assert(textureFormat == VK_FORMAT_R8_UNORM);
|
cemu_assert(textureFormat == VK_FORMAT_R8_UNORM);
|
||||||
return baseTexture->width * baseTexture->height * 1;
|
return baseTexture->width * baseTexture->height * 1;
|
||||||
|
@ -79,6 +79,13 @@ uint32 LatteTextureReadbackInfoVk::GetImageSize(LatteTextureView* textureView)
|
||||||
// todo - if driver does not support VK_FORMAT_D24_UNORM_S8_UINT this is represented as VK_FORMAT_D32_SFLOAT_S8_UINT which is 8 bytes
|
// todo - if driver does not support VK_FORMAT_D24_UNORM_S8_UINT this is represented as VK_FORMAT_D32_SFLOAT_S8_UINT which is 8 bytes
|
||||||
return baseTexture->width * baseTexture->height * 4;
|
return baseTexture->width * baseTexture->height * 4;
|
||||||
}
|
}
|
||||||
|
else if (textureView->format == Latte::E_GX2SURFFMT::R5_G6_B5_UNORM )
|
||||||
|
{
|
||||||
|
if(textureFormat == VK_FORMAT_R5G6B5_UNORM_PACK16){
|
||||||
|
return baseTexture->width * baseTexture->height * 2;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
cemuLog_log(LogType::Force, "Unsupported texture readback format {:04x}", (uint32)textureView->format);
|
cemuLog_log(LogType::Force, "Unsupported texture readback format {:04x}", (uint32)textureView->format);
|
||||||
|
|
|
@ -19,7 +19,7 @@ public:
|
||||||
|
|
||||||
virtual ~VKRMoveableRefCounter()
|
virtual ~VKRMoveableRefCounter()
|
||||||
{
|
{
|
||||||
cemu_assert_debug(refCount == 0);
|
cemu_assert_debug(m_refCount == 0);
|
||||||
|
|
||||||
// remove references
|
// remove references
|
||||||
#ifdef CEMU_DEBUG_ASSERT
|
#ifdef CEMU_DEBUG_ASSERT
|
||||||
|
@ -30,7 +30,11 @@ public:
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
for (auto itr : refs)
|
for (auto itr : refs)
|
||||||
itr->ref->refCount--;
|
{
|
||||||
|
itr->ref->m_refCount--;
|
||||||
|
if (itr->ref->m_refCount == 0)
|
||||||
|
itr->ref->RefCountReachedZero();
|
||||||
|
}
|
||||||
refs.clear();
|
refs.clear();
|
||||||
delete selfRef;
|
delete selfRef;
|
||||||
selfRef = nullptr;
|
selfRef = nullptr;
|
||||||
|
@ -41,8 +45,8 @@ public:
|
||||||
VKRMoveableRefCounter(VKRMoveableRefCounter&& rhs) noexcept
|
VKRMoveableRefCounter(VKRMoveableRefCounter&& rhs) noexcept
|
||||||
{
|
{
|
||||||
this->refs = std::move(rhs.refs);
|
this->refs = std::move(rhs.refs);
|
||||||
this->refCount = rhs.refCount;
|
this->m_refCount = rhs.m_refCount;
|
||||||
rhs.refCount = 0;
|
rhs.m_refCount = 0;
|
||||||
this->selfRef = rhs.selfRef;
|
this->selfRef = rhs.selfRef;
|
||||||
rhs.selfRef = nullptr;
|
rhs.selfRef = nullptr;
|
||||||
this->selfRef->ref = this;
|
this->selfRef->ref = this;
|
||||||
|
@ -57,7 +61,7 @@ public:
|
||||||
void addRef(VKRMoveableRefCounter* refTarget)
|
void addRef(VKRMoveableRefCounter* refTarget)
|
||||||
{
|
{
|
||||||
this->refs.emplace_back(refTarget->selfRef);
|
this->refs.emplace_back(refTarget->selfRef);
|
||||||
refTarget->refCount++;
|
refTarget->m_refCount++;
|
||||||
|
|
||||||
#ifdef CEMU_DEBUG_ASSERT
|
#ifdef CEMU_DEBUG_ASSERT
|
||||||
// add reverse ref
|
// add reverse ref
|
||||||
|
@ -68,16 +72,23 @@ public:
|
||||||
// methods to directly increment/decrement ref counter (for situations where no external object is available)
|
// methods to directly increment/decrement ref counter (for situations where no external object is available)
|
||||||
void incRef()
|
void incRef()
|
||||||
{
|
{
|
||||||
this->refCount++;
|
m_refCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
void decRef()
|
void decRef()
|
||||||
{
|
{
|
||||||
this->refCount--;
|
m_refCount--;
|
||||||
|
if (m_refCount == 0)
|
||||||
|
RefCountReachedZero();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
int refCount{};
|
virtual void RefCountReachedZero()
|
||||||
|
{
|
||||||
|
// does nothing by default
|
||||||
|
}
|
||||||
|
|
||||||
|
int m_refCount{};
|
||||||
private:
|
private:
|
||||||
VKRMoveableRefCounterRef* selfRef;
|
VKRMoveableRefCounterRef* selfRef;
|
||||||
std::vector<VKRMoveableRefCounterRef*> refs;
|
std::vector<VKRMoveableRefCounterRef*> refs;
|
||||||
|
@ -88,7 +99,7 @@ private:
|
||||||
void moveObj(VKRMoveableRefCounter&& rhs)
|
void moveObj(VKRMoveableRefCounter&& rhs)
|
||||||
{
|
{
|
||||||
this->refs = std::move(rhs.refs);
|
this->refs = std::move(rhs.refs);
|
||||||
this->refCount = rhs.refCount;
|
this->m_refCount = rhs.m_refCount;
|
||||||
this->selfRef = rhs.selfRef;
|
this->selfRef = rhs.selfRef;
|
||||||
this->selfRef->ref = this;
|
this->selfRef->ref = this;
|
||||||
}
|
}
|
||||||
|
@ -131,6 +142,25 @@ public:
|
||||||
VkSampler m_textureDefaultSampler[2] = { VK_NULL_HANDLE, VK_NULL_HANDLE }; // relict from LatteTextureViewVk, get rid of it eventually
|
VkSampler m_textureDefaultSampler[2] = { VK_NULL_HANDLE, VK_NULL_HANDLE }; // relict from LatteTextureViewVk, get rid of it eventually
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class VKRObjectSampler : public VKRDestructibleObject
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
VKRObjectSampler(VkSamplerCreateInfo* samplerInfo);
|
||||||
|
~VKRObjectSampler() override;
|
||||||
|
|
||||||
|
static VKRObjectSampler* GetOrCreateSampler(VkSamplerCreateInfo* samplerInfo);
|
||||||
|
static void DestroyCache();
|
||||||
|
|
||||||
|
void RefCountReachedZero() override; // sampler objects are destroyed when not referenced anymore
|
||||||
|
|
||||||
|
VkSampler GetSampler() const { return m_sampler; }
|
||||||
|
private:
|
||||||
|
static std::unordered_map<uint64, VKRObjectSampler*> s_samplerCache;
|
||||||
|
VkSampler m_sampler{ VK_NULL_HANDLE };
|
||||||
|
uint64 m_hash;
|
||||||
|
};
|
||||||
|
|
||||||
class VKRObjectRenderPass : public VKRDestructibleObject
|
class VKRObjectRenderPass : public VKRDestructibleObject
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
|
@ -4,6 +4,14 @@
|
||||||
|
|
||||||
/* VKRSynchronizedMemoryBuffer */
|
/* VKRSynchronizedMemoryBuffer */
|
||||||
|
|
||||||
|
VKRSynchronizedRingAllocator::~VKRSynchronizedRingAllocator()
|
||||||
|
{
|
||||||
|
for(auto& buf : m_buffers)
|
||||||
|
{
|
||||||
|
m_vkrMemMgr->DeleteBuffer(buf.vk_buffer, buf.vk_mem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void VKRSynchronizedRingAllocator::addUploadBufferSyncPoint(AllocatorBuffer_t& buffer, uint32 offset)
|
void VKRSynchronizedRingAllocator::addUploadBufferSyncPoint(AllocatorBuffer_t& buffer, uint32 offset)
|
||||||
{
|
{
|
||||||
auto cmdBufferId = m_vkr->GetCurrentCommandBufferId();
|
auto cmdBufferId = m_vkr->GetCurrentCommandBufferId();
|
||||||
|
@ -23,11 +31,11 @@ void VKRSynchronizedRingAllocator::allocateAdditionalUploadBuffer(uint32 sizeReq
|
||||||
AllocatorBuffer_t newBuffer{};
|
AllocatorBuffer_t newBuffer{};
|
||||||
newBuffer.writeIndex = 0;
|
newBuffer.writeIndex = 0;
|
||||||
newBuffer.basePtr = nullptr;
|
newBuffer.basePtr = nullptr;
|
||||||
if (m_bufferType == BUFFER_TYPE::STAGING)
|
if (m_bufferType == VKR_BUFFER_TYPE::STAGING)
|
||||||
m_vkrMemMgr->CreateBuffer(bufferAllocSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, newBuffer.vk_buffer, newBuffer.vk_mem);
|
m_vkrMemMgr->CreateBuffer(bufferAllocSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, newBuffer.vk_buffer, newBuffer.vk_mem);
|
||||||
else if (m_bufferType == BUFFER_TYPE::INDEX)
|
else if (m_bufferType == VKR_BUFFER_TYPE::INDEX)
|
||||||
m_vkrMemMgr->CreateBuffer(bufferAllocSize, VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, newBuffer.vk_buffer, newBuffer.vk_mem);
|
m_vkrMemMgr->CreateBuffer(bufferAllocSize, VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, newBuffer.vk_buffer, newBuffer.vk_mem);
|
||||||
else if (m_bufferType == BUFFER_TYPE::STRIDE)
|
else if (m_bufferType == VKR_BUFFER_TYPE::STRIDE)
|
||||||
m_vkrMemMgr->CreateBuffer(bufferAllocSize, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, newBuffer.vk_buffer, newBuffer.vk_mem);
|
m_vkrMemMgr->CreateBuffer(bufferAllocSize, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, newBuffer.vk_buffer, newBuffer.vk_mem);
|
||||||
else
|
else
|
||||||
cemu_assert_debug(false);
|
cemu_assert_debug(false);
|
||||||
|
@ -53,7 +61,7 @@ VKRSynchronizedRingAllocator::AllocatorReservation_t VKRSynchronizedRingAllocato
|
||||||
uint32 distanceToSyncPoint;
|
uint32 distanceToSyncPoint;
|
||||||
if (!itr.queue_syncPoints.empty())
|
if (!itr.queue_syncPoints.empty())
|
||||||
{
|
{
|
||||||
if(itr.queue_syncPoints.front().offset < itr.writeIndex)
|
if (itr.queue_syncPoints.front().offset < itr.writeIndex)
|
||||||
distanceToSyncPoint = 0xFFFFFFFF;
|
distanceToSyncPoint = 0xFFFFFFFF;
|
||||||
else
|
else
|
||||||
distanceToSyncPoint = itr.queue_syncPoints.front().offset - itr.writeIndex;
|
distanceToSyncPoint = itr.queue_syncPoints.front().offset - itr.writeIndex;
|
||||||
|
@ -100,7 +108,7 @@ VKRSynchronizedRingAllocator::AllocatorReservation_t VKRSynchronizedRingAllocato
|
||||||
|
|
||||||
void VKRSynchronizedRingAllocator::FlushReservation(AllocatorReservation_t& uploadReservation)
|
void VKRSynchronizedRingAllocator::FlushReservation(AllocatorReservation_t& uploadReservation)
|
||||||
{
|
{
|
||||||
cemu_assert_debug(m_bufferType == BUFFER_TYPE::STAGING); // only the staging buffer isn't coherent
|
cemu_assert_debug(m_bufferType == VKR_BUFFER_TYPE::STAGING); // only the staging buffer isn't coherent
|
||||||
// todo - use nonCoherentAtomSize for flush size (instead of hardcoded constant)
|
// todo - use nonCoherentAtomSize for flush size (instead of hardcoded constant)
|
||||||
VkMappedMemoryRange flushedRange{};
|
VkMappedMemoryRange flushedRange{};
|
||||||
flushedRange.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE;
|
flushedRange.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE;
|
||||||
|
@ -167,15 +175,88 @@ void VKRSynchronizedRingAllocator::GetStats(uint32& numBuffers, size_t& totalBuf
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* VKRSynchronizedHeapAllocator */
|
||||||
|
|
||||||
|
VKRSynchronizedHeapAllocator::VKRSynchronizedHeapAllocator(class VKRMemoryManager* vkMemoryManager, VKR_BUFFER_TYPE bufferType, size_t minimumBufferAllocSize)
|
||||||
|
: m_vkrMemMgr(vkMemoryManager), m_chunkedHeap(bufferType, minimumBufferAllocSize) {};
|
||||||
|
|
||||||
|
VKRSynchronizedHeapAllocator::AllocatorReservation* VKRSynchronizedHeapAllocator::AllocateBufferMemory(uint32 size, uint32 alignment)
|
||||||
|
{
|
||||||
|
CHAddr addr = m_chunkedHeap.alloc(size, alignment);
|
||||||
|
m_activeAllocations.emplace_back(addr);
|
||||||
|
AllocatorReservation* res = m_poolAllocatorReservation.allocObj();
|
||||||
|
res->bufferIndex = addr.chunkIndex;
|
||||||
|
res->bufferOffset = addr.offset;
|
||||||
|
res->size = size;
|
||||||
|
res->memPtr = m_chunkedHeap.GetChunkPtr(addr.chunkIndex) + addr.offset;
|
||||||
|
m_chunkedHeap.GetChunkVkMemInfo(addr.chunkIndex, res->vkBuffer, res->vkMem);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VKRSynchronizedHeapAllocator::FreeReservation(AllocatorReservation* uploadReservation)
|
||||||
|
{
|
||||||
|
// put the allocation on a delayed release queue for the current command buffer
|
||||||
|
uint64 currentCommandBufferId = VulkanRenderer::GetInstance()->GetCurrentCommandBufferId();
|
||||||
|
auto it = std::find_if(m_activeAllocations.begin(), m_activeAllocations.end(), [&uploadReservation](const TrackedAllocation& allocation) { return allocation.allocation.chunkIndex == uploadReservation->bufferIndex && allocation.allocation.offset == uploadReservation->bufferOffset; });
|
||||||
|
cemu_assert_debug(it != m_activeAllocations.end());
|
||||||
|
m_releaseQueue[currentCommandBufferId].emplace_back(it->allocation);
|
||||||
|
m_activeAllocations.erase(it);
|
||||||
|
m_poolAllocatorReservation.freeObj(uploadReservation);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VKRSynchronizedHeapAllocator::FlushReservation(AllocatorReservation* uploadReservation)
|
||||||
|
{
|
||||||
|
if (m_chunkedHeap.RequiresFlush(uploadReservation->bufferIndex))
|
||||||
|
{
|
||||||
|
VkMappedMemoryRange flushedRange{};
|
||||||
|
flushedRange.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE;
|
||||||
|
flushedRange.memory = uploadReservation->vkMem;
|
||||||
|
flushedRange.offset = uploadReservation->bufferOffset;
|
||||||
|
flushedRange.size = uploadReservation->size;
|
||||||
|
vkFlushMappedMemoryRanges(VulkanRenderer::GetInstance()->GetLogicalDevice(), 1, &flushedRange);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VKRSynchronizedHeapAllocator::CleanupBuffer(uint64 latestFinishedCommandBufferId)
|
||||||
|
{
|
||||||
|
auto it = m_releaseQueue.begin();
|
||||||
|
while (it != m_releaseQueue.end())
|
||||||
|
{
|
||||||
|
if (it->first <= latestFinishedCommandBufferId)
|
||||||
|
{
|
||||||
|
// release allocations
|
||||||
|
for(auto& addr : it->second)
|
||||||
|
m_chunkedHeap.free(addr);
|
||||||
|
it = m_releaseQueue.erase(it);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
it++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VKRSynchronizedHeapAllocator::GetStats(uint32& numBuffers, size_t& totalBufferSize, size_t& freeBufferSize) const
|
||||||
|
{
|
||||||
|
m_chunkedHeap.GetStats(numBuffers, totalBufferSize, freeBufferSize);
|
||||||
|
}
|
||||||
|
|
||||||
/* VkTextureChunkedHeap */
|
/* VkTextureChunkedHeap */
|
||||||
|
|
||||||
|
VkTextureChunkedHeap::~VkTextureChunkedHeap()
|
||||||
|
{
|
||||||
|
VkDevice device = VulkanRenderer::GetInstance()->GetLogicalDevice();
|
||||||
|
for (auto& i : m_list_chunkInfo)
|
||||||
|
{
|
||||||
|
vkFreeMemory(device, i.mem, nullptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
uint32 VkTextureChunkedHeap::allocateNewChunk(uint32 chunkIndex, uint32 minimumAllocationSize)
|
uint32 VkTextureChunkedHeap::allocateNewChunk(uint32 chunkIndex, uint32 minimumAllocationSize)
|
||||||
{
|
{
|
||||||
cemu_assert_debug(m_list_chunkInfo.size() == chunkIndex);
|
cemu_assert_debug(m_list_chunkInfo.size() == chunkIndex);
|
||||||
m_list_chunkInfo.resize(m_list_chunkInfo.size() + 1);
|
m_list_chunkInfo.resize(m_list_chunkInfo.size() + 1);
|
||||||
|
|
||||||
// pad minimumAllocationSize to 32KB alignment
|
// pad minimumAllocationSize to 32KB alignment
|
||||||
minimumAllocationSize = (minimumAllocationSize + (32*1024-1)) & ~(32 * 1024 - 1);
|
minimumAllocationSize = (minimumAllocationSize + (32 * 1024 - 1)) & ~(32 * 1024 - 1);
|
||||||
|
|
||||||
uint32 allocationSize = 1024 * 1024 * 128;
|
uint32 allocationSize = 1024 * 1024 * 128;
|
||||||
if (chunkIndex == 0)
|
if (chunkIndex == 0)
|
||||||
|
@ -189,8 +270,7 @@ uint32 VkTextureChunkedHeap::allocateNewChunk(uint32 chunkIndex, uint32 minimumA
|
||||||
std::vector<uint32> deviceLocalMemoryTypeIndices = m_vkrMemoryManager->FindMemoryTypes(m_typeFilter, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
|
std::vector<uint32> deviceLocalMemoryTypeIndices = m_vkrMemoryManager->FindMemoryTypes(m_typeFilter, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
|
||||||
std::vector<uint32> hostLocalMemoryTypeIndices = m_vkrMemoryManager->FindMemoryTypes(m_typeFilter, 0);
|
std::vector<uint32> hostLocalMemoryTypeIndices = m_vkrMemoryManager->FindMemoryTypes(m_typeFilter, 0);
|
||||||
// remove device local memory types from host local vector
|
// remove device local memory types from host local vector
|
||||||
auto pred = [&deviceLocalMemoryTypeIndices](const uint32& v) ->bool
|
auto pred = [&deviceLocalMemoryTypeIndices](const uint32& v) -> bool {
|
||||||
{
|
|
||||||
return std::find(deviceLocalMemoryTypeIndices.begin(), deviceLocalMemoryTypeIndices.end(), v) != deviceLocalMemoryTypeIndices.end();
|
return std::find(deviceLocalMemoryTypeIndices.begin(), deviceLocalMemoryTypeIndices.end(), v) != deviceLocalMemoryTypeIndices.end();
|
||||||
};
|
};
|
||||||
hostLocalMemoryTypeIndices.erase(std::remove_if(hostLocalMemoryTypeIndices.begin(), hostLocalMemoryTypeIndices.end(), pred), hostLocalMemoryTypeIndices.end());
|
hostLocalMemoryTypeIndices.erase(std::remove_if(hostLocalMemoryTypeIndices.begin(), hostLocalMemoryTypeIndices.end(), pred), hostLocalMemoryTypeIndices.end());
|
||||||
|
@ -206,7 +286,7 @@ uint32 VkTextureChunkedHeap::allocateNewChunk(uint32 chunkIndex, uint32 minimumA
|
||||||
allocInfo.memoryTypeIndex = memType;
|
allocInfo.memoryTypeIndex = memType;
|
||||||
|
|
||||||
VkDeviceMemory imageMemory;
|
VkDeviceMemory imageMemory;
|
||||||
VkResult r = vkAllocateMemory(m_device, &allocInfo, nullptr, &imageMemory);
|
VkResult r = vkAllocateMemory(VulkanRenderer::GetInstance()->GetLogicalDevice(), &allocInfo, nullptr, &imageMemory);
|
||||||
if (r != VK_SUCCESS)
|
if (r != VK_SUCCESS)
|
||||||
continue;
|
continue;
|
||||||
m_list_chunkInfo[chunkIndex].mem = imageMemory;
|
m_list_chunkInfo[chunkIndex].mem = imageMemory;
|
||||||
|
@ -221,7 +301,7 @@ uint32 VkTextureChunkedHeap::allocateNewChunk(uint32 chunkIndex, uint32 minimumA
|
||||||
allocInfo.memoryTypeIndex = memType;
|
allocInfo.memoryTypeIndex = memType;
|
||||||
|
|
||||||
VkDeviceMemory imageMemory;
|
VkDeviceMemory imageMemory;
|
||||||
VkResult r = vkAllocateMemory(m_device, &allocInfo, nullptr, &imageMemory);
|
VkResult r = vkAllocateMemory(VulkanRenderer::GetInstance()->GetLogicalDevice(), &allocInfo, nullptr, &imageMemory);
|
||||||
if (r != VK_SUCCESS)
|
if (r != VK_SUCCESS)
|
||||||
continue;
|
continue;
|
||||||
m_list_chunkInfo[chunkIndex].mem = imageMemory;
|
m_list_chunkInfo[chunkIndex].mem = imageMemory;
|
||||||
|
@ -238,28 +318,76 @@ uint32 VkTextureChunkedHeap::allocateNewChunk(uint32 chunkIndex, uint32 minimumA
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t VKRMemoryManager::FindMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties) const
|
/* VkBufferChunkedHeap */
|
||||||
{
|
|
||||||
VkPhysicalDeviceMemoryProperties memProperties;
|
|
||||||
vkGetPhysicalDeviceMemoryProperties(m_vkr->GetPhysicalDevice(), &memProperties);
|
|
||||||
|
|
||||||
for (uint32 i = 0; i < memProperties.memoryTypeCount; i++)
|
VKRBuffer* VKRBuffer::Create(VKR_BUFFER_TYPE bufferType, size_t bufferSize, VkMemoryPropertyFlags properties)
|
||||||
|
{
|
||||||
|
auto* memMgr = VulkanRenderer::GetInstance()->GetMemoryManager();
|
||||||
|
VkBuffer buffer;
|
||||||
|
VkDeviceMemory bufferMemory;
|
||||||
|
bool allocSuccess;
|
||||||
|
if (bufferType == VKR_BUFFER_TYPE::STAGING)
|
||||||
|
allocSuccess = memMgr->CreateBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, properties, buffer, bufferMemory);
|
||||||
|
else if (bufferType == VKR_BUFFER_TYPE::INDEX)
|
||||||
|
allocSuccess = memMgr->CreateBuffer(bufferSize, VK_BUFFER_USAGE_INDEX_BUFFER_BIT, properties, buffer, bufferMemory);
|
||||||
|
else if (bufferType == VKR_BUFFER_TYPE::STRIDE)
|
||||||
|
allocSuccess = memMgr->CreateBuffer(bufferSize, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, properties, buffer, bufferMemory);
|
||||||
|
else
|
||||||
|
cemu_assert_debug(false);
|
||||||
|
if (!allocSuccess)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
VKRBuffer* bufferObj = new VKRBuffer(buffer, bufferMemory);
|
||||||
|
// if host visible, then map buffer
|
||||||
|
void* data = nullptr;
|
||||||
|
if (properties & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT)
|
||||||
{
|
{
|
||||||
if ((typeFilter & (1 << i)) != 0 && (memProperties.memoryTypes[i].propertyFlags & properties) == properties)
|
vkMapMemory(VulkanRenderer::GetInstance()->GetLogicalDevice(), bufferMemory, 0, bufferSize, 0, &data);
|
||||||
return i;
|
bufferObj->m_requiresFlush = !HAS_FLAG(properties, VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
|
||||||
}
|
}
|
||||||
m_vkr->UnrecoverableError(fmt::format("failed to find suitable memory type ({0:#08x} {1:#08x})", typeFilter, properties).c_str());
|
bufferObj->m_mappedMemory = (uint8*)data;
|
||||||
return 0;
|
return bufferObj;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool VKRMemoryManager::FindMemoryType2(uint32 typeFilter, VkMemoryPropertyFlags properties, uint32& memoryIndex) const
|
VKRBuffer::~VKRBuffer()
|
||||||
|
{
|
||||||
|
if (m_mappedMemory)
|
||||||
|
vkUnmapMemory(VulkanRenderer::GetInstance()->GetLogicalDevice(), m_bufferMemory);
|
||||||
|
if (m_bufferMemory != VK_NULL_HANDLE)
|
||||||
|
vkFreeMemory(VulkanRenderer::GetInstance()->GetLogicalDevice(), m_bufferMemory, nullptr);
|
||||||
|
if (m_buffer != VK_NULL_HANDLE)
|
||||||
|
vkDestroyBuffer(VulkanRenderer::GetInstance()->GetLogicalDevice(), m_buffer, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
VkBufferChunkedHeap::~VkBufferChunkedHeap()
|
||||||
|
{
|
||||||
|
for (auto& chunk : m_chunkBuffers)
|
||||||
|
delete chunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32 VkBufferChunkedHeap::allocateNewChunk(uint32 chunkIndex, uint32 minimumAllocationSize)
|
||||||
|
{
|
||||||
|
size_t allocationSize = std::max<size_t>(m_minimumBufferAllocationSize, minimumAllocationSize);
|
||||||
|
VKRBuffer* buffer = VKRBuffer::Create(m_bufferType, allocationSize, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
|
||||||
|
if(!buffer)
|
||||||
|
buffer = VKRBuffer::Create(m_bufferType, allocationSize, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT);
|
||||||
|
if(!buffer)
|
||||||
|
VulkanRenderer::GetInstance()->UnrecoverableError("Failed to allocate buffer memory for VkBufferChunkedHeap");
|
||||||
|
cemu_assert_debug(buffer);
|
||||||
|
cemu_assert_debug(m_chunkBuffers.size() == chunkIndex);
|
||||||
|
m_chunkBuffers.emplace_back(buffer);
|
||||||
|
// todo - VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT might be worth it?
|
||||||
|
return allocationSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VKRMemoryManager::FindMemoryType(uint32 typeFilter, VkMemoryPropertyFlags properties, uint32& memoryIndex) const
|
||||||
{
|
{
|
||||||
VkPhysicalDeviceMemoryProperties memProperties;
|
VkPhysicalDeviceMemoryProperties memProperties;
|
||||||
vkGetPhysicalDeviceMemoryProperties(m_vkr->GetPhysicalDevice(), &memProperties);
|
vkGetPhysicalDeviceMemoryProperties(m_vkr->GetPhysicalDevice(), &memProperties);
|
||||||
|
|
||||||
for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++)
|
for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++)
|
||||||
{
|
{
|
||||||
if (typeFilter & (1 << i) && memProperties.memoryTypes[i].propertyFlags == properties)
|
if (typeFilter & (1 << i) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties)
|
||||||
{
|
{
|
||||||
memoryIndex = i;
|
memoryIndex = i;
|
||||||
return true;
|
return true;
|
||||||
|
@ -330,31 +458,7 @@ size_t VKRMemoryManager::GetTotalMemoryForBufferType(VkBufferUsageFlags usage, V
|
||||||
return total;
|
return total;
|
||||||
}
|
}
|
||||||
|
|
||||||
void VKRMemoryManager::CreateBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer, VkDeviceMemory& bufferMemory) const
|
bool VKRMemoryManager::CreateBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer, VkDeviceMemory& bufferMemory) const
|
||||||
{
|
|
||||||
VkBufferCreateInfo bufferInfo{};
|
|
||||||
bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
|
|
||||||
bufferInfo.usage = usage;
|
|
||||||
bufferInfo.size = size;
|
|
||||||
bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
|
||||||
if (vkCreateBuffer(m_vkr->GetLogicalDevice(), &bufferInfo, nullptr, &buffer) != VK_SUCCESS)
|
|
||||||
m_vkr->UnrecoverableError("Failed to create buffer");
|
|
||||||
|
|
||||||
VkMemoryRequirements memRequirements;
|
|
||||||
vkGetBufferMemoryRequirements(m_vkr->GetLogicalDevice(), buffer, &memRequirements);
|
|
||||||
|
|
||||||
VkMemoryAllocateInfo allocInfo{};
|
|
||||||
allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
|
|
||||||
allocInfo.allocationSize = memRequirements.size;
|
|
||||||
allocInfo.memoryTypeIndex = FindMemoryType(memRequirements.memoryTypeBits, properties);
|
|
||||||
|
|
||||||
if (vkAllocateMemory(m_vkr->GetLogicalDevice(), &allocInfo, nullptr, &bufferMemory) != VK_SUCCESS)
|
|
||||||
m_vkr->UnrecoverableError("Failed to allocate buffer memory");
|
|
||||||
if (vkBindBufferMemory(m_vkr->GetLogicalDevice(), buffer, bufferMemory, 0) != VK_SUCCESS)
|
|
||||||
m_vkr->UnrecoverableError("Failed to bind buffer memory");
|
|
||||||
}
|
|
||||||
|
|
||||||
bool VKRMemoryManager::CreateBuffer2(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer, VkDeviceMemory& bufferMemory) const
|
|
||||||
{
|
{
|
||||||
VkBufferCreateInfo bufferInfo{};
|
VkBufferCreateInfo bufferInfo{};
|
||||||
bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
|
bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
|
||||||
|
@ -363,7 +467,7 @@ bool VKRMemoryManager::CreateBuffer2(VkDeviceSize size, VkBufferUsageFlags usage
|
||||||
bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||||
if (vkCreateBuffer(m_vkr->GetLogicalDevice(), &bufferInfo, nullptr, &buffer) != VK_SUCCESS)
|
if (vkCreateBuffer(m_vkr->GetLogicalDevice(), &bufferInfo, nullptr, &buffer) != VK_SUCCESS)
|
||||||
{
|
{
|
||||||
cemuLog_log(LogType::Force, "Failed to create buffer (CreateBuffer2)");
|
cemuLog_log(LogType::Force, "Failed to create buffer (CreateBuffer)");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -373,7 +477,7 @@ bool VKRMemoryManager::CreateBuffer2(VkDeviceSize size, VkBufferUsageFlags usage
|
||||||
VkMemoryAllocateInfo allocInfo{};
|
VkMemoryAllocateInfo allocInfo{};
|
||||||
allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
|
allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
|
||||||
allocInfo.allocationSize = memRequirements.size;
|
allocInfo.allocationSize = memRequirements.size;
|
||||||
if (!FindMemoryType2(memRequirements.memoryTypeBits, properties, allocInfo.memoryTypeIndex))
|
if (!FindMemoryType(memRequirements.memoryTypeBits, properties, allocInfo.memoryTypeIndex))
|
||||||
{
|
{
|
||||||
vkDestroyBuffer(m_vkr->GetLogicalDevice(), buffer, nullptr);
|
vkDestroyBuffer(m_vkr->GetLogicalDevice(), buffer, nullptr);
|
||||||
return false;
|
return false;
|
||||||
|
@ -386,7 +490,7 @@ bool VKRMemoryManager::CreateBuffer2(VkDeviceSize size, VkBufferUsageFlags usage
|
||||||
if (vkBindBufferMemory(m_vkr->GetLogicalDevice(), buffer, bufferMemory, 0) != VK_SUCCESS)
|
if (vkBindBufferMemory(m_vkr->GetLogicalDevice(), buffer, bufferMemory, 0) != VK_SUCCESS)
|
||||||
{
|
{
|
||||||
vkDestroyBuffer(m_vkr->GetLogicalDevice(), buffer, nullptr);
|
vkDestroyBuffer(m_vkr->GetLogicalDevice(), buffer, nullptr);
|
||||||
cemuLog_log(LogType::Force, "Failed to bind buffer (CreateBuffer2)");
|
cemuLog_log(LogType::Force, "Failed to bind buffer (CreateBuffer)");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
@ -408,7 +512,7 @@ bool VKRMemoryManager::CreateBufferFromHostMemory(void* hostPointer, VkDeviceSiz
|
||||||
|
|
||||||
if (vkCreateBuffer(m_vkr->GetLogicalDevice(), &bufferInfo, nullptr, &buffer) != VK_SUCCESS)
|
if (vkCreateBuffer(m_vkr->GetLogicalDevice(), &bufferInfo, nullptr, &buffer) != VK_SUCCESS)
|
||||||
{
|
{
|
||||||
cemuLog_log(LogType::Force, "Failed to create buffer (CreateBuffer2)");
|
cemuLog_log(LogType::Force, "Failed to create buffer (CreateBuffer)");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -429,7 +533,7 @@ bool VKRMemoryManager::CreateBufferFromHostMemory(void* hostPointer, VkDeviceSiz
|
||||||
|
|
||||||
allocInfo.pNext = &importHostMem;
|
allocInfo.pNext = &importHostMem;
|
||||||
|
|
||||||
if (!FindMemoryType2(memRequirements.memoryTypeBits, properties, allocInfo.memoryTypeIndex))
|
if (!FindMemoryType(memRequirements.memoryTypeBits, properties, allocInfo.memoryTypeIndex))
|
||||||
{
|
{
|
||||||
vkDestroyBuffer(m_vkr->GetLogicalDevice(), buffer, nullptr);
|
vkDestroyBuffer(m_vkr->GetLogicalDevice(), buffer, nullptr);
|
||||||
return false;
|
return false;
|
||||||
|
@ -469,11 +573,11 @@ VkImageMemAllocation* VKRMemoryManager::imageMemoryAllocate(VkImage image)
|
||||||
auto it = map_textureHeap.find(typeFilter);
|
auto it = map_textureHeap.find(typeFilter);
|
||||||
if (it == map_textureHeap.end())
|
if (it == map_textureHeap.end())
|
||||||
{
|
{
|
||||||
texHeap = new VkTextureChunkedHeap(this, typeFilter, m_vkr->GetLogicalDevice());
|
texHeap = new VkTextureChunkedHeap(this, typeFilter);
|
||||||
map_textureHeap.emplace(typeFilter, texHeap);
|
map_textureHeap.emplace(typeFilter, texHeap);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
texHeap = it->second;
|
texHeap = it->second.get();
|
||||||
|
|
||||||
// alloc mem from heap
|
// alloc mem from heap
|
||||||
uint32 allocationSize = (uint32)memRequirements.size;
|
uint32 allocationSize = (uint32)memRequirements.size;
|
||||||
|
|
|
@ -2,6 +2,36 @@
|
||||||
#include "Cafe/HW/Latte/Renderer/Renderer.h"
|
#include "Cafe/HW/Latte/Renderer/Renderer.h"
|
||||||
#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h"
|
#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h"
|
||||||
#include "util/ChunkedHeap/ChunkedHeap.h"
|
#include "util/ChunkedHeap/ChunkedHeap.h"
|
||||||
|
#include "util/helpers/MemoryPool.h"
|
||||||
|
|
||||||
|
enum class VKR_BUFFER_TYPE
|
||||||
|
{
|
||||||
|
STAGING, // staging upload buffer
|
||||||
|
INDEX, // buffer for index data
|
||||||
|
STRIDE, // buffer for stride-adjusted vertex data
|
||||||
|
};
|
||||||
|
|
||||||
|
class VKRBuffer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static VKRBuffer* Create(VKR_BUFFER_TYPE bufferType, size_t bufferSize, VkMemoryPropertyFlags properties);
|
||||||
|
~VKRBuffer();
|
||||||
|
|
||||||
|
VkBuffer GetVkBuffer() const { return m_buffer; }
|
||||||
|
VkDeviceMemory GetVkBufferMemory() const { return m_bufferMemory; }
|
||||||
|
|
||||||
|
uint8* GetPtr() const { return m_mappedMemory; }
|
||||||
|
|
||||||
|
bool RequiresFlush() const { return m_requiresFlush; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
VKRBuffer(VkBuffer buffer, VkDeviceMemory bufferMem) : m_buffer(buffer), m_bufferMemory(bufferMem) { };
|
||||||
|
|
||||||
|
VkBuffer m_buffer;
|
||||||
|
VkDeviceMemory m_bufferMemory;
|
||||||
|
uint8* m_mappedMemory;
|
||||||
|
bool m_requiresFlush{false};
|
||||||
|
};
|
||||||
|
|
||||||
struct VkImageMemAllocation
|
struct VkImageMemAllocation
|
||||||
{
|
{
|
||||||
|
@ -14,18 +44,17 @@ struct VkImageMemAllocation
|
||||||
uint32 getAllocationSize() { return allocationSize; }
|
uint32 getAllocationSize() { return allocationSize; }
|
||||||
};
|
};
|
||||||
|
|
||||||
class VkTextureChunkedHeap : private ChunkedHeap
|
class VkTextureChunkedHeap : private ChunkedHeap<>
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
VkTextureChunkedHeap(class VKRMemoryManager* memoryManager, uint32 typeFilter, VkDevice device) : m_vkrMemoryManager(memoryManager), m_typeFilter(typeFilter), m_device(device) { };
|
VkTextureChunkedHeap(class VKRMemoryManager* memoryManager, uint32 typeFilter) : m_vkrMemoryManager(memoryManager), m_typeFilter(typeFilter) { };
|
||||||
|
~VkTextureChunkedHeap();
|
||||||
|
|
||||||
struct ChunkInfo
|
struct ChunkInfo
|
||||||
{
|
{
|
||||||
VkDeviceMemory mem;
|
VkDeviceMemory mem;
|
||||||
};
|
};
|
||||||
|
|
||||||
uint32 allocateNewChunk(uint32 chunkIndex, uint32 minimumAllocationSize) override;
|
|
||||||
|
|
||||||
CHAddr allocMem(uint32 size, uint32 alignment)
|
CHAddr allocMem(uint32 size, uint32 alignment)
|
||||||
{
|
{
|
||||||
if (alignment < 4)
|
if (alignment < 4)
|
||||||
|
@ -43,11 +72,6 @@ public:
|
||||||
this->free(addr);
|
this->free(addr);
|
||||||
}
|
}
|
||||||
|
|
||||||
void setDevice(VkDevice dev)
|
|
||||||
{
|
|
||||||
m_device = dev;
|
|
||||||
}
|
|
||||||
|
|
||||||
VkDeviceMemory getChunkMem(uint32 index)
|
VkDeviceMemory getChunkMem(uint32 index)
|
||||||
{
|
{
|
||||||
if (index >= m_list_chunkInfo.size())
|
if (index >= m_list_chunkInfo.size())
|
||||||
|
@ -57,29 +81,75 @@ public:
|
||||||
|
|
||||||
void getStatistics(uint32& totalHeapSize, uint32& allocatedBytes) const
|
void getStatistics(uint32& totalHeapSize, uint32& allocatedBytes) const
|
||||||
{
|
{
|
||||||
totalHeapSize = numHeapBytes;
|
totalHeapSize = m_numHeapBytes;
|
||||||
allocatedBytes = numAllocatedBytes;
|
allocatedBytes = m_numAllocatedBytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
VkDevice m_device;
|
private:
|
||||||
|
uint32 allocateNewChunk(uint32 chunkIndex, uint32 minimumAllocationSize) override;
|
||||||
|
|
||||||
uint32 m_typeFilter{ 0xFFFFFFFF };
|
uint32 m_typeFilter{ 0xFFFFFFFF };
|
||||||
class VKRMemoryManager* m_vkrMemoryManager;
|
class VKRMemoryManager* m_vkrMemoryManager;
|
||||||
std::vector<ChunkInfo> m_list_chunkInfo;
|
std::vector<ChunkInfo> m_list_chunkInfo;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class VkBufferChunkedHeap : private ChunkedHeap<>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
VkBufferChunkedHeap(VKR_BUFFER_TYPE bufferType, size_t minimumBufferAllocationSize) : m_bufferType(bufferType), m_minimumBufferAllocationSize(minimumBufferAllocationSize) { };
|
||||||
|
~VkBufferChunkedHeap();
|
||||||
|
|
||||||
|
using ChunkedHeap::alloc;
|
||||||
|
using ChunkedHeap::free;
|
||||||
|
|
||||||
|
uint8* GetChunkPtr(uint32 index) const
|
||||||
|
{
|
||||||
|
if (index >= m_chunkBuffers.size())
|
||||||
|
return nullptr;
|
||||||
|
return m_chunkBuffers[index]->GetPtr();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GetChunkVkMemInfo(uint32 index, VkBuffer& buffer, VkDeviceMemory& mem)
|
||||||
|
{
|
||||||
|
if (index >= m_chunkBuffers.size())
|
||||||
|
{
|
||||||
|
buffer = VK_NULL_HANDLE;
|
||||||
|
mem = VK_NULL_HANDLE;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
buffer = m_chunkBuffers[index]->GetVkBuffer();
|
||||||
|
mem = m_chunkBuffers[index]->GetVkBufferMemory();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GetStats(uint32& numBuffers, size_t& totalBufferSize, size_t& freeBufferSize) const
|
||||||
|
{
|
||||||
|
numBuffers = m_chunkBuffers.size();
|
||||||
|
totalBufferSize = m_numHeapBytes;
|
||||||
|
freeBufferSize = m_numHeapBytes - m_numAllocatedBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RequiresFlush(uint32 index) const
|
||||||
|
{
|
||||||
|
if (index >= m_chunkBuffers.size())
|
||||||
|
return false;
|
||||||
|
return m_chunkBuffers[index]->RequiresFlush();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint32 allocateNewChunk(uint32 chunkIndex, uint32 minimumAllocationSize) override;
|
||||||
|
|
||||||
|
VKR_BUFFER_TYPE m_bufferType;
|
||||||
|
std::vector<VKRBuffer*> m_chunkBuffers;
|
||||||
|
size_t m_minimumBufferAllocationSize;
|
||||||
|
};
|
||||||
|
|
||||||
// a circular ring-buffer which tracks and releases memory per command-buffer
|
// a circular ring-buffer which tracks and releases memory per command-buffer
|
||||||
class VKRSynchronizedRingAllocator
|
class VKRSynchronizedRingAllocator
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
enum class BUFFER_TYPE
|
VKRSynchronizedRingAllocator(class VulkanRenderer* vkRenderer, class VKRMemoryManager* vkMemoryManager, VKR_BUFFER_TYPE bufferType, uint32 minimumBufferAllocSize) : m_vkr(vkRenderer), m_vkrMemMgr(vkMemoryManager), m_bufferType(bufferType), m_minimumBufferAllocSize(minimumBufferAllocSize) {};
|
||||||
{
|
|
||||||
STAGING, // staging upload buffer
|
|
||||||
INDEX, // buffer for index data
|
|
||||||
STRIDE, // buffer for stride-adjusted vertex data
|
|
||||||
};
|
|
||||||
|
|
||||||
VKRSynchronizedRingAllocator(class VulkanRenderer* vkRenderer, class VKRMemoryManager* vkMemoryManager, BUFFER_TYPE bufferType, uint32 minimumBufferAllocSize) : m_vkr(vkRenderer), m_vkrMemMgr(vkMemoryManager), m_bufferType(bufferType), m_minimumBufferAllocSize(minimumBufferAllocSize) {};
|
|
||||||
VKRSynchronizedRingAllocator(const VKRSynchronizedRingAllocator&) = delete; // disallow copy
|
VKRSynchronizedRingAllocator(const VKRSynchronizedRingAllocator&) = delete; // disallow copy
|
||||||
|
~VKRSynchronizedRingAllocator();
|
||||||
|
|
||||||
struct BufferSyncPoint_t
|
struct BufferSyncPoint_t
|
||||||
{
|
{
|
||||||
|
@ -126,13 +196,53 @@ private:
|
||||||
|
|
||||||
const class VulkanRenderer* m_vkr;
|
const class VulkanRenderer* m_vkr;
|
||||||
const class VKRMemoryManager* m_vkrMemMgr;
|
const class VKRMemoryManager* m_vkrMemMgr;
|
||||||
const BUFFER_TYPE m_bufferType;
|
const VKR_BUFFER_TYPE m_bufferType;
|
||||||
const uint32 m_minimumBufferAllocSize;
|
const uint32 m_minimumBufferAllocSize;
|
||||||
|
|
||||||
std::vector<AllocatorBuffer_t> m_buffers;
|
std::vector<AllocatorBuffer_t> m_buffers;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// heap style allocator with released memory being freed after the current command buffer finishes
|
||||||
|
class VKRSynchronizedHeapAllocator
|
||||||
|
{
|
||||||
|
struct TrackedAllocation
|
||||||
|
{
|
||||||
|
TrackedAllocation(CHAddr allocation) : allocation(allocation) {};
|
||||||
|
CHAddr allocation;
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
VKRSynchronizedHeapAllocator(class VKRMemoryManager* vkMemoryManager, VKR_BUFFER_TYPE bufferType, size_t minimumBufferAllocSize);
|
||||||
|
VKRSynchronizedHeapAllocator(const VKRSynchronizedHeapAllocator&) = delete; // disallow copy
|
||||||
|
|
||||||
|
struct AllocatorReservation
|
||||||
|
{
|
||||||
|
VkBuffer vkBuffer;
|
||||||
|
VkDeviceMemory vkMem;
|
||||||
|
uint8* memPtr;
|
||||||
|
uint32 bufferOffset;
|
||||||
|
uint32 size;
|
||||||
|
uint32 bufferIndex;
|
||||||
|
};
|
||||||
|
|
||||||
|
AllocatorReservation* AllocateBufferMemory(uint32 size, uint32 alignment);
|
||||||
|
void FreeReservation(AllocatorReservation* uploadReservation);
|
||||||
|
void FlushReservation(AllocatorReservation* uploadReservation);
|
||||||
|
|
||||||
|
void CleanupBuffer(uint64 latestFinishedCommandBufferId);
|
||||||
|
|
||||||
|
void GetStats(uint32& numBuffers, size_t& totalBufferSize, size_t& freeBufferSize) const;
|
||||||
|
private:
|
||||||
|
const class VKRMemoryManager* m_vkrMemMgr;
|
||||||
|
VkBufferChunkedHeap m_chunkedHeap;
|
||||||
|
// allocations
|
||||||
|
std::vector<TrackedAllocation> m_activeAllocations;
|
||||||
|
MemoryPool<AllocatorReservation> m_poolAllocatorReservation{32};
|
||||||
|
// release queue
|
||||||
|
std::unordered_map<uint64, std::vector<CHAddr>> m_releaseQueue;
|
||||||
|
};
|
||||||
|
|
||||||
void LatteIndices_invalidateAll();
|
void LatteIndices_invalidateAll();
|
||||||
|
|
||||||
class VKRMemoryManager
|
class VKRMemoryManager
|
||||||
|
@ -140,15 +250,15 @@ class VKRMemoryManager
|
||||||
friend class VKRSynchronizedRingAllocator;
|
friend class VKRSynchronizedRingAllocator;
|
||||||
public:
|
public:
|
||||||
VKRMemoryManager(class VulkanRenderer* renderer) :
|
VKRMemoryManager(class VulkanRenderer* renderer) :
|
||||||
m_stagingBuffer(renderer, this, VKRSynchronizedRingAllocator::BUFFER_TYPE::STAGING, 32u * 1024 * 1024),
|
m_stagingBuffer(renderer, this, VKR_BUFFER_TYPE::STAGING, 32u * 1024 * 1024),
|
||||||
m_indexBuffer(renderer, this, VKRSynchronizedRingAllocator::BUFFER_TYPE::INDEX, 4u * 1024 * 1024),
|
m_indexBuffer(this, VKR_BUFFER_TYPE::INDEX, 4u * 1024 * 1024),
|
||||||
m_vertexStrideMetalBuffer(renderer, this, VKRSynchronizedRingAllocator::BUFFER_TYPE::STRIDE, 4u * 1024 * 1024)
|
m_vertexStrideMetalBuffer(renderer, this, VKR_BUFFER_TYPE::STRIDE, 4u * 1024 * 1024)
|
||||||
{
|
{
|
||||||
m_vkr = renderer;
|
m_vkr = renderer;
|
||||||
}
|
}
|
||||||
|
|
||||||
// texture memory management
|
// texture memory management
|
||||||
std::unordered_map<uint32, VkTextureChunkedHeap*> map_textureHeap; // one heap per memory type
|
std::unordered_map<uint32, std::unique_ptr<VkTextureChunkedHeap>> map_textureHeap; // one heap per memory type
|
||||||
std::vector<uint8> m_textureUploadBuffer;
|
std::vector<uint8> m_textureUploadBuffer;
|
||||||
|
|
||||||
// texture upload buffer
|
// texture upload buffer
|
||||||
|
@ -167,7 +277,7 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
VKRSynchronizedRingAllocator& getStagingAllocator() { return m_stagingBuffer; }; // allocator for texture/attribute/uniform uploads
|
VKRSynchronizedRingAllocator& getStagingAllocator() { return m_stagingBuffer; }; // allocator for texture/attribute/uniform uploads
|
||||||
VKRSynchronizedRingAllocator& getIndexAllocator() { return m_indexBuffer; }; // allocator for index data
|
VKRSynchronizedHeapAllocator& GetIndexAllocator() { return m_indexBuffer; }; // allocator for index data
|
||||||
VKRSynchronizedRingAllocator& getMetalStrideWorkaroundAllocator() { return m_vertexStrideMetalBuffer; }; // allocator for stride-adjusted vertex data
|
VKRSynchronizedRingAllocator& getMetalStrideWorkaroundAllocator() { return m_vertexStrideMetalBuffer; }; // allocator for stride-adjusted vertex data
|
||||||
|
|
||||||
void cleanupBuffers(uint64 latestFinishedCommandBufferId)
|
void cleanupBuffers(uint64 latestFinishedCommandBufferId)
|
||||||
|
@ -178,9 +288,7 @@ public:
|
||||||
m_vertexStrideMetalBuffer.CleanupBuffer(latestFinishedCommandBufferId);
|
m_vertexStrideMetalBuffer.CleanupBuffer(latestFinishedCommandBufferId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// memory helpers
|
bool FindMemoryType(uint32 typeFilter, VkMemoryPropertyFlags properties, uint32& memoryIndex) const; // searches for exact properties. Can gracefully fail without throwing exception (returns false)
|
||||||
uint32_t FindMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties) const;
|
|
||||||
bool FindMemoryType2(uint32 typeFilter, VkMemoryPropertyFlags properties, uint32& memoryIndex) const; // searches for exact properties. Can gracefully fail without throwing exception (returns false)
|
|
||||||
std::vector<uint32> FindMemoryTypes(uint32_t typeFilter, VkMemoryPropertyFlags properties) const;
|
std::vector<uint32> FindMemoryTypes(uint32_t typeFilter, VkMemoryPropertyFlags properties) const;
|
||||||
|
|
||||||
// image memory allocation
|
// image memory allocation
|
||||||
|
@ -190,8 +298,7 @@ public:
|
||||||
// buffer management
|
// buffer management
|
||||||
size_t GetTotalMemoryForBufferType(VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, size_t minimumBufferSize = 16 * 1024 * 1024);
|
size_t GetTotalMemoryForBufferType(VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, size_t minimumBufferSize = 16 * 1024 * 1024);
|
||||||
|
|
||||||
void CreateBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer, VkDeviceMemory& bufferMemory) const;
|
bool CreateBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer, VkDeviceMemory& bufferMemory) const; // same as CreateBuffer but doesn't throw exception on failure
|
||||||
bool CreateBuffer2(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer, VkDeviceMemory& bufferMemory) const; // same as CreateBuffer but doesn't throw exception on failure
|
|
||||||
bool CreateBufferFromHostMemory(void* hostPointer, VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer, VkDeviceMemory& bufferMemory) const;
|
bool CreateBufferFromHostMemory(void* hostPointer, VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer, VkDeviceMemory& bufferMemory) const;
|
||||||
|
|
||||||
void DeleteBuffer(VkBuffer& buffer, VkDeviceMemory& deviceMem) const;
|
void DeleteBuffer(VkBuffer& buffer, VkDeviceMemory& deviceMem) const;
|
||||||
|
@ -202,6 +309,6 @@ public:
|
||||||
private:
|
private:
|
||||||
class VulkanRenderer* m_vkr;
|
class VulkanRenderer* m_vkr;
|
||||||
VKRSynchronizedRingAllocator m_stagingBuffer;
|
VKRSynchronizedRingAllocator m_stagingBuffer;
|
||||||
VKRSynchronizedRingAllocator m_indexBuffer;
|
VKRSynchronizedHeapAllocator m_indexBuffer;
|
||||||
VKRSynchronizedRingAllocator m_vertexStrideMetalBuffer;
|
VKRSynchronizedRingAllocator m_vertexStrideMetalBuffer;
|
||||||
};
|
};
|
||||||
|
|
|
@ -165,6 +165,7 @@ VKFUNC_DEVICE(vkCmdDraw);
|
||||||
VKFUNC_DEVICE(vkCmdCopyBufferToImage);
|
VKFUNC_DEVICE(vkCmdCopyBufferToImage);
|
||||||
VKFUNC_DEVICE(vkCmdCopyImageToBuffer);
|
VKFUNC_DEVICE(vkCmdCopyImageToBuffer);
|
||||||
VKFUNC_DEVICE(vkCmdClearColorImage);
|
VKFUNC_DEVICE(vkCmdClearColorImage);
|
||||||
|
VKFUNC_DEVICE(vkCmdClearAttachments);
|
||||||
VKFUNC_DEVICE(vkCmdBindIndexBuffer);
|
VKFUNC_DEVICE(vkCmdBindIndexBuffer);
|
||||||
VKFUNC_DEVICE(vkCmdBindVertexBuffers);
|
VKFUNC_DEVICE(vkCmdBindVertexBuffers);
|
||||||
VKFUNC_DEVICE(vkCmdDrawIndexed);
|
VKFUNC_DEVICE(vkCmdDrawIndexed);
|
||||||
|
@ -198,6 +199,7 @@ VKFUNC_DEVICE(vkCmdEndTransformFeedbackEXT);
|
||||||
|
|
||||||
// query
|
// query
|
||||||
VKFUNC_DEVICE(vkCreateQueryPool);
|
VKFUNC_DEVICE(vkCreateQueryPool);
|
||||||
|
VKFUNC_DEVICE(vkDestroyQueryPool);
|
||||||
VKFUNC_DEVICE(vkCmdResetQueryPool);
|
VKFUNC_DEVICE(vkCmdResetQueryPool);
|
||||||
VKFUNC_DEVICE(vkCmdBeginQuery);
|
VKFUNC_DEVICE(vkCmdBeginQuery);
|
||||||
VKFUNC_DEVICE(vkCmdEndQuery);
|
VKFUNC_DEVICE(vkCmdEndQuery);
|
||||||
|
@ -236,6 +238,7 @@ VKFUNC_DEVICE(vkAllocateDescriptorSets);
|
||||||
VKFUNC_DEVICE(vkFreeDescriptorSets);
|
VKFUNC_DEVICE(vkFreeDescriptorSets);
|
||||||
VKFUNC_DEVICE(vkUpdateDescriptorSets);
|
VKFUNC_DEVICE(vkUpdateDescriptorSets);
|
||||||
VKFUNC_DEVICE(vkCreateDescriptorPool);
|
VKFUNC_DEVICE(vkCreateDescriptorPool);
|
||||||
|
VKFUNC_DEVICE(vkDestroyDescriptorPool);
|
||||||
VKFUNC_DEVICE(vkDestroyDescriptorSetLayout);
|
VKFUNC_DEVICE(vkDestroyDescriptorSetLayout);
|
||||||
|
|
||||||
#undef VKFUNC_INIT
|
#undef VKFUNC_INIT
|
||||||
|
|
|
@ -439,7 +439,7 @@ VulkanRenderer::VulkanRenderer()
|
||||||
GetDeviceFeatures();
|
GetDeviceFeatures();
|
||||||
|
|
||||||
// init memory manager
|
// init memory manager
|
||||||
memoryManager = new VKRMemoryManager(this);
|
memoryManager.reset(new VKRMemoryManager(this));
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -577,15 +577,15 @@ VulkanRenderer::VulkanRenderer()
|
||||||
void* bufferPtr;
|
void* bufferPtr;
|
||||||
// init ringbuffer for uniform vars
|
// init ringbuffer for uniform vars
|
||||||
m_uniformVarBufferMemoryIsCoherent = false;
|
m_uniformVarBufferMemoryIsCoherent = false;
|
||||||
if (memoryManager->CreateBuffer2(UNIFORMVAR_RINGBUFFER_SIZE, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT, m_uniformVarBuffer, m_uniformVarBufferMemory))
|
if (memoryManager->CreateBuffer(UNIFORMVAR_RINGBUFFER_SIZE, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT, m_uniformVarBuffer, m_uniformVarBufferMemory))
|
||||||
m_uniformVarBufferMemoryIsCoherent = true;
|
m_uniformVarBufferMemoryIsCoherent = true;
|
||||||
else if (memoryManager->CreateBuffer2(UNIFORMVAR_RINGBUFFER_SIZE, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT | VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, m_uniformVarBuffer, m_uniformVarBufferMemory))
|
else if (memoryManager->CreateBuffer(UNIFORMVAR_RINGBUFFER_SIZE, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT | VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, m_uniformVarBuffer, m_uniformVarBufferMemory))
|
||||||
m_uniformVarBufferMemoryIsCoherent = true; // unified memory
|
m_uniformVarBufferMemoryIsCoherent = true; // unified memory
|
||||||
else if (memoryManager->CreateBuffer2(UNIFORMVAR_RINGBUFFER_SIZE, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, m_uniformVarBuffer, m_uniformVarBufferMemory))
|
else if (memoryManager->CreateBuffer(UNIFORMVAR_RINGBUFFER_SIZE, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, m_uniformVarBuffer, m_uniformVarBufferMemory))
|
||||||
m_uniformVarBufferMemoryIsCoherent = true;
|
m_uniformVarBufferMemoryIsCoherent = true;
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
memoryManager->CreateBuffer2(UNIFORMVAR_RINGBUFFER_SIZE, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, m_uniformVarBuffer, m_uniformVarBufferMemory);
|
memoryManager->CreateBuffer(UNIFORMVAR_RINGBUFFER_SIZE, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, m_uniformVarBuffer, m_uniformVarBufferMemory);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!m_uniformVarBufferMemoryIsCoherent)
|
if (!m_uniformVarBufferMemoryIsCoherent)
|
||||||
|
@ -628,6 +628,31 @@ VulkanRenderer::~VulkanRenderer()
|
||||||
m_pipeline_cache_semaphore.notify();
|
m_pipeline_cache_semaphore.notify();
|
||||||
m_pipeline_cache_save_thread.join();
|
m_pipeline_cache_save_thread.join();
|
||||||
|
|
||||||
|
vkDestroyPipelineCache(m_logicalDevice, m_pipeline_cache, nullptr);
|
||||||
|
|
||||||
|
if(!m_backbufferBlitDescriptorSetCache.empty())
|
||||||
|
{
|
||||||
|
std::vector<VkDescriptorSet> freeVector;
|
||||||
|
freeVector.reserve(m_backbufferBlitDescriptorSetCache.size());
|
||||||
|
std::transform(m_backbufferBlitDescriptorSetCache.begin(), m_backbufferBlitDescriptorSetCache.end(), std::back_inserter(freeVector), [](auto& i) {
|
||||||
|
return i.second;
|
||||||
|
});
|
||||||
|
vkFreeDescriptorSets(m_logicalDevice, m_descriptorPool, freeVector.size(), freeVector.data());
|
||||||
|
}
|
||||||
|
|
||||||
|
vkDestroyDescriptorPool(m_logicalDevice, m_descriptorPool, nullptr);
|
||||||
|
|
||||||
|
for(auto& i : m_backbufferBlitPipelineCache)
|
||||||
|
{
|
||||||
|
vkDestroyPipeline(m_logicalDevice, i.second, nullptr);
|
||||||
|
}
|
||||||
|
m_backbufferBlitPipelineCache = {};
|
||||||
|
|
||||||
|
if(m_occlusionQueries.queryPool != VK_NULL_HANDLE)
|
||||||
|
vkDestroyQueryPool(m_logicalDevice, m_occlusionQueries.queryPool, nullptr);
|
||||||
|
|
||||||
|
vkDestroyDescriptorSetLayout(m_logicalDevice, m_swapchainDescriptorSetLayout, nullptr);
|
||||||
|
|
||||||
// shut down imgui
|
// shut down imgui
|
||||||
ImGui_ImplVulkan_Shutdown();
|
ImGui_ImplVulkan_Shutdown();
|
||||||
|
|
||||||
|
@ -640,10 +665,6 @@ VulkanRenderer::~VulkanRenderer()
|
||||||
memoryManager->DeleteBuffer(m_xfbRingBuffer, m_xfbRingBufferMemory);
|
memoryManager->DeleteBuffer(m_xfbRingBuffer, m_xfbRingBufferMemory);
|
||||||
memoryManager->DeleteBuffer(m_occlusionQueries.bufferQueryResults, m_occlusionQueries.memoryQueryResults);
|
memoryManager->DeleteBuffer(m_occlusionQueries.bufferQueryResults, m_occlusionQueries.memoryQueryResults);
|
||||||
memoryManager->DeleteBuffer(m_bufferCache, m_bufferCacheMemory);
|
memoryManager->DeleteBuffer(m_bufferCache, m_bufferCacheMemory);
|
||||||
// texture memory
|
|
||||||
// todo
|
|
||||||
// upload buffers
|
|
||||||
// todo
|
|
||||||
|
|
||||||
m_padSwapchainInfo = nullptr;
|
m_padSwapchainInfo = nullptr;
|
||||||
m_mainSwapchainInfo = nullptr;
|
m_mainSwapchainInfo = nullptr;
|
||||||
|
@ -666,12 +687,20 @@ VulkanRenderer::~VulkanRenderer()
|
||||||
it = VK_NULL_HANDLE;
|
it = VK_NULL_HANDLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for(auto& sem : m_commandBufferSemaphores)
|
||||||
|
{
|
||||||
|
vkDestroySemaphore(m_logicalDevice, sem, nullptr);
|
||||||
|
sem = VK_NULL_HANDLE;
|
||||||
|
}
|
||||||
|
|
||||||
if (m_pipelineLayout != VK_NULL_HANDLE)
|
if (m_pipelineLayout != VK_NULL_HANDLE)
|
||||||
vkDestroyPipelineLayout(m_logicalDevice, m_pipelineLayout, nullptr);
|
vkDestroyPipelineLayout(m_logicalDevice, m_pipelineLayout, nullptr);
|
||||||
|
|
||||||
if (m_commandPool != VK_NULL_HANDLE)
|
if (m_commandPool != VK_NULL_HANDLE)
|
||||||
vkDestroyCommandPool(m_logicalDevice, m_commandPool, nullptr);
|
vkDestroyCommandPool(m_logicalDevice, m_commandPool, nullptr);
|
||||||
|
|
||||||
|
VKRObjectSampler::DestroyCache();
|
||||||
|
|
||||||
// destroy debug callback
|
// destroy debug callback
|
||||||
if (m_debugCallback)
|
if (m_debugCallback)
|
||||||
{
|
{
|
||||||
|
@ -679,6 +708,12 @@ VulkanRenderer::~VulkanRenderer()
|
||||||
vkDestroyDebugUtilsMessengerEXT(m_instance, m_debugCallback, nullptr);
|
vkDestroyDebugUtilsMessengerEXT(m_instance, m_debugCallback, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
while(!m_destructionQueue.empty())
|
||||||
|
ProcessDestructionQueue();
|
||||||
|
|
||||||
|
// destroy memory manager
|
||||||
|
memoryManager.reset();
|
||||||
|
|
||||||
// destroy instance, devices
|
// destroy instance, devices
|
||||||
if (m_instance != VK_NULL_HANDLE)
|
if (m_instance != VK_NULL_HANDLE)
|
||||||
{
|
{
|
||||||
|
@ -690,9 +725,6 @@ VulkanRenderer::~VulkanRenderer()
|
||||||
vkDestroyInstance(m_instance, nullptr);
|
vkDestroyInstance(m_instance, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
// destroy memory manager
|
|
||||||
delete memoryManager;
|
|
||||||
|
|
||||||
// crashes?
|
// crashes?
|
||||||
//glslang::FinalizeProcess();
|
//glslang::FinalizeProcess();
|
||||||
}
|
}
|
||||||
|
@ -823,7 +855,14 @@ void VulkanRenderer::HandleScreenshotRequest(LatteTextureView* texView, bool pad
|
||||||
VkMemoryAllocateInfo allocInfo{};
|
VkMemoryAllocateInfo allocInfo{};
|
||||||
allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
|
allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
|
||||||
allocInfo.allocationSize = memRequirements.size;
|
allocInfo.allocationSize = memRequirements.size;
|
||||||
allocInfo.memoryTypeIndex = memoryManager->FindMemoryType(memRequirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
|
uint32 memIndex;
|
||||||
|
bool foundMemory = memoryManager->FindMemoryType(memRequirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, memIndex);
|
||||||
|
if(!foundMemory)
|
||||||
|
{
|
||||||
|
cemuLog_log(LogType::Force, "Screenshot request failed due to incompatible vulkan memory types.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
allocInfo.memoryTypeIndex = memIndex;
|
||||||
|
|
||||||
if (vkAllocateMemory(m_logicalDevice, &allocInfo, nullptr, &imageMemory) != VK_SUCCESS)
|
if (vkAllocateMemory(m_logicalDevice, &allocInfo, nullptr, &imageMemory) != VK_SUCCESS)
|
||||||
{
|
{
|
||||||
|
@ -1606,6 +1645,7 @@ void VulkanRenderer::Initialize()
|
||||||
|
|
||||||
void VulkanRenderer::Shutdown()
|
void VulkanRenderer::Shutdown()
|
||||||
{
|
{
|
||||||
|
DeleteFontTextures();
|
||||||
Renderer::Shutdown();
|
Renderer::Shutdown();
|
||||||
SubmitCommandBuffer();
|
SubmitCommandBuffer();
|
||||||
WaitDeviceIdle();
|
WaitDeviceIdle();
|
||||||
|
@ -1806,7 +1846,6 @@ void VulkanRenderer::ImguiEnd()
|
||||||
vkCmdEndRenderPass(m_state.currentCommandBuffer);
|
vkCmdEndRenderPass(m_state.currentCommandBuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<LatteTextureVk*> g_imgui_textures; // TODO manage better
|
|
||||||
ImTextureID VulkanRenderer::GenerateTexture(const std::vector<uint8>& data, const Vector2i& size)
|
ImTextureID VulkanRenderer::GenerateTexture(const std::vector<uint8>& data, const Vector2i& size)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
@ -1836,6 +1875,7 @@ void VulkanRenderer::DeleteTexture(ImTextureID id)
|
||||||
|
|
||||||
void VulkanRenderer::DeleteFontTextures()
|
void VulkanRenderer::DeleteFontTextures()
|
||||||
{
|
{
|
||||||
|
WaitDeviceIdle();
|
||||||
ImGui_ImplVulkan_DestroyFontsTexture();
|
ImGui_ImplVulkan_DestroyFontsTexture();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1874,7 +1914,7 @@ void VulkanRenderer::InitFirstCommandBuffer()
|
||||||
vkResetFences(m_logicalDevice, 1, &m_cmd_buffer_fences[m_commandBufferIndex]);
|
vkResetFences(m_logicalDevice, 1, &m_cmd_buffer_fences[m_commandBufferIndex]);
|
||||||
VkCommandBufferBeginInfo beginInfo{};
|
VkCommandBufferBeginInfo beginInfo{};
|
||||||
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
|
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
|
||||||
beginInfo.flags = VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT;
|
beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
|
||||||
vkBeginCommandBuffer(m_state.currentCommandBuffer, &beginInfo);
|
vkBeginCommandBuffer(m_state.currentCommandBuffer, &beginInfo);
|
||||||
|
|
||||||
vkCmdSetViewport(m_state.currentCommandBuffer, 0, 1, &m_state.currentViewport);
|
vkCmdSetViewport(m_state.currentCommandBuffer, 0, 1, &m_state.currentViewport);
|
||||||
|
@ -1996,7 +2036,7 @@ void VulkanRenderer::SubmitCommandBuffer(VkSemaphore signalSemaphore, VkSemaphor
|
||||||
|
|
||||||
VkCommandBufferBeginInfo beginInfo{};
|
VkCommandBufferBeginInfo beginInfo{};
|
||||||
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
|
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
|
||||||
beginInfo.flags = VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT;
|
beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
|
||||||
vkBeginCommandBuffer(m_state.currentCommandBuffer, &beginInfo);
|
vkBeginCommandBuffer(m_state.currentCommandBuffer, &beginInfo);
|
||||||
|
|
||||||
// make sure some states are set for this command buffer
|
// make sure some states are set for this command buffer
|
||||||
|
@ -2517,9 +2557,8 @@ VkPipeline VulkanRenderer::backbufferBlit_createGraphicsPipeline(VkDescriptorSet
|
||||||
hash += (uint64)(chainInfo.m_usesSRGB);
|
hash += (uint64)(chainInfo.m_usesSRGB);
|
||||||
hash += ((uint64)padView) << 1;
|
hash += ((uint64)padView) << 1;
|
||||||
|
|
||||||
static std::unordered_map<uint64, VkPipeline> s_pipeline_cache;
|
const auto it = m_backbufferBlitPipelineCache.find(hash);
|
||||||
const auto it = s_pipeline_cache.find(hash);
|
if (it != m_backbufferBlitPipelineCache.cend())
|
||||||
if (it != s_pipeline_cache.cend())
|
|
||||||
return it->second;
|
return it->second;
|
||||||
|
|
||||||
std::vector<VkPipelineShaderStageCreateInfo> shaderStages;
|
std::vector<VkPipelineShaderStageCreateInfo> shaderStages;
|
||||||
|
@ -2623,7 +2662,7 @@ VkPipeline VulkanRenderer::backbufferBlit_createGraphicsPipeline(VkDescriptorSet
|
||||||
throw std::runtime_error(fmt::format("Failed to create graphics pipeline: {}", result));
|
throw std::runtime_error(fmt::format("Failed to create graphics pipeline: {}", result));
|
||||||
}
|
}
|
||||||
|
|
||||||
s_pipeline_cache[hash] = pipeline;
|
m_backbufferBlitPipelineCache[hash] = pipeline;
|
||||||
m_pipeline_cache_semaphore.notify();
|
m_pipeline_cache_semaphore.notify();
|
||||||
|
|
||||||
return pipeline;
|
return pipeline;
|
||||||
|
@ -2920,9 +2959,6 @@ void VulkanRenderer::DrawBackbufferQuad(LatteTextureView* texView, RendererOutpu
|
||||||
LatteTextureViewVk* texViewVk = (LatteTextureViewVk*)texView;
|
LatteTextureViewVk* texViewVk = (LatteTextureViewVk*)texView;
|
||||||
draw_endRenderPass();
|
draw_endRenderPass();
|
||||||
|
|
||||||
if (clearBackground)
|
|
||||||
ClearColorbuffer(padView);
|
|
||||||
|
|
||||||
// barrier for input texture
|
// barrier for input texture
|
||||||
VkMemoryBarrier memoryBarrier{};
|
VkMemoryBarrier memoryBarrier{};
|
||||||
memoryBarrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER;
|
memoryBarrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER;
|
||||||
|
@ -2959,6 +2995,16 @@ void VulkanRenderer::DrawBackbufferQuad(LatteTextureView* texView, RendererOutpu
|
||||||
|
|
||||||
vkCmdBeginRenderPass(m_state.currentCommandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE);
|
vkCmdBeginRenderPass(m_state.currentCommandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE);
|
||||||
|
|
||||||
|
if (clearBackground)
|
||||||
|
{
|
||||||
|
VkClearAttachment clearAttachment{};
|
||||||
|
clearAttachment.clearValue = {0,0,0,0};
|
||||||
|
clearAttachment.colorAttachment = 0;
|
||||||
|
clearAttachment.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||||
|
VkClearRect clearExtent = {{{0,0},chainInfo.m_actualExtent}, 0, 1};
|
||||||
|
vkCmdClearAttachments(m_state.currentCommandBuffer, 1, &clearAttachment, 1, &clearExtent);
|
||||||
|
}
|
||||||
|
|
||||||
vkCmdBindPipeline(m_state.currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
|
vkCmdBindPipeline(m_state.currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
|
||||||
m_state.currentPipeline = pipeline;
|
m_state.currentPipeline = pipeline;
|
||||||
|
|
||||||
|
@ -3023,9 +3069,8 @@ VkDescriptorSet VulkanRenderer::backbufferBlit_createDescriptorSet(VkDescriptorS
|
||||||
hash += (uint64)texViewVk->GetViewRGBA();
|
hash += (uint64)texViewVk->GetViewRGBA();
|
||||||
hash += (uint64)texViewVk->GetDefaultTextureSampler(useLinearTexFilter);
|
hash += (uint64)texViewVk->GetDefaultTextureSampler(useLinearTexFilter);
|
||||||
|
|
||||||
static std::unordered_map<uint64, VkDescriptorSet> s_set_cache;
|
const auto it = m_backbufferBlitDescriptorSetCache.find(hash);
|
||||||
const auto it = s_set_cache.find(hash);
|
if (it != m_backbufferBlitDescriptorSetCache.cend())
|
||||||
if (it != s_set_cache.cend())
|
|
||||||
return it->second;
|
return it->second;
|
||||||
|
|
||||||
VkDescriptorSetAllocateInfo allocInfo = {};
|
VkDescriptorSetAllocateInfo allocInfo = {};
|
||||||
|
@ -3056,7 +3101,7 @@ VkDescriptorSet VulkanRenderer::backbufferBlit_createDescriptorSet(VkDescriptorS
|
||||||
vkUpdateDescriptorSets(m_logicalDevice, 1, &descriptorWrites, 0, nullptr);
|
vkUpdateDescriptorSets(m_logicalDevice, 1, &descriptorWrites, 0, nullptr);
|
||||||
performanceMonitor.vk.numDescriptorSamplerTextures.increment();
|
performanceMonitor.vk.numDescriptorSamplerTextures.increment();
|
||||||
|
|
||||||
s_set_cache[hash] = result;
|
m_backbufferBlitDescriptorSetCache[hash] = result;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3189,7 +3234,8 @@ VkDescriptorSetInfo::~VkDescriptorSetInfo()
|
||||||
performanceMonitor.vk.numDescriptorDynUniformBuffers.decrement(statsNumDynUniformBuffers);
|
performanceMonitor.vk.numDescriptorDynUniformBuffers.decrement(statsNumDynUniformBuffers);
|
||||||
performanceMonitor.vk.numDescriptorStorageBuffers.decrement(statsNumStorageBuffers);
|
performanceMonitor.vk.numDescriptorStorageBuffers.decrement(statsNumStorageBuffers);
|
||||||
|
|
||||||
VulkanRenderer::GetInstance()->ReleaseDestructibleObject(m_vkObjDescriptorSet);
|
auto renderer = VulkanRenderer::GetInstance();
|
||||||
|
renderer->ReleaseDestructibleObject(m_vkObjDescriptorSet);
|
||||||
m_vkObjDescriptorSet = nullptr;
|
m_vkObjDescriptorSet = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3699,7 +3745,7 @@ void VulkanRenderer::bufferCache_copyStreamoutToMainBuffer(uint32 srcOffset, uin
|
||||||
|
|
||||||
void VulkanRenderer::AppendOverlayDebugInfo()
|
void VulkanRenderer::AppendOverlayDebugInfo()
|
||||||
{
|
{
|
||||||
ImGui::Text("--- Vulkan info ---");
|
ImGui::Text("--- Vulkan debug info ---");
|
||||||
ImGui::Text("GfxPipelines %u", performanceMonitor.vk.numGraphicPipelines.get());
|
ImGui::Text("GfxPipelines %u", performanceMonitor.vk.numGraphicPipelines.get());
|
||||||
ImGui::Text("DescriptorSets %u", performanceMonitor.vk.numDescriptorSets.get());
|
ImGui::Text("DescriptorSets %u", performanceMonitor.vk.numDescriptorSets.get());
|
||||||
ImGui::Text("DS ImgSamplers %u", performanceMonitor.vk.numDescriptorSamplerTextures.get());
|
ImGui::Text("DS ImgSamplers %u", performanceMonitor.vk.numDescriptorSamplerTextures.get());
|
||||||
|
@ -3707,6 +3753,7 @@ void VulkanRenderer::AppendOverlayDebugInfo()
|
||||||
ImGui::Text("DS StorageBuf %u", performanceMonitor.vk.numDescriptorStorageBuffers.get());
|
ImGui::Text("DS StorageBuf %u", performanceMonitor.vk.numDescriptorStorageBuffers.get());
|
||||||
ImGui::Text("Images %u", performanceMonitor.vk.numImages.get());
|
ImGui::Text("Images %u", performanceMonitor.vk.numImages.get());
|
||||||
ImGui::Text("ImageView %u", performanceMonitor.vk.numImageViews.get());
|
ImGui::Text("ImageView %u", performanceMonitor.vk.numImageViews.get());
|
||||||
|
ImGui::Text("ImageSampler %u", performanceMonitor.vk.numSamplers.get());
|
||||||
ImGui::Text("RenderPass %u", performanceMonitor.vk.numRenderPass.get());
|
ImGui::Text("RenderPass %u", performanceMonitor.vk.numRenderPass.get());
|
||||||
ImGui::Text("Framebuffer %u", performanceMonitor.vk.numFramebuffer.get());
|
ImGui::Text("Framebuffer %u", performanceMonitor.vk.numFramebuffer.get());
|
||||||
m_spinlockDestructionQueue.lock();
|
m_spinlockDestructionQueue.lock();
|
||||||
|
@ -3716,7 +3763,7 @@ void VulkanRenderer::AppendOverlayDebugInfo()
|
||||||
|
|
||||||
ImGui::Text("BeginRP/f %u", performanceMonitor.vk.numBeginRenderpassPerFrame.get());
|
ImGui::Text("BeginRP/f %u", performanceMonitor.vk.numBeginRenderpassPerFrame.get());
|
||||||
ImGui::Text("Barriers/f %u", performanceMonitor.vk.numDrawBarriersPerFrame.get());
|
ImGui::Text("Barriers/f %u", performanceMonitor.vk.numDrawBarriersPerFrame.get());
|
||||||
ImGui::Text("--- Cache info ---");
|
ImGui::Text("--- Cache debug info ---");
|
||||||
|
|
||||||
uint32 bufferCacheHeapSize = 0;
|
uint32 bufferCacheHeapSize = 0;
|
||||||
uint32 bufferCacheAllocationSize = 0;
|
uint32 bufferCacheAllocationSize = 0;
|
||||||
|
@ -3736,7 +3783,7 @@ void VulkanRenderer::AppendOverlayDebugInfo()
|
||||||
ImGui::SameLine(60.0f);
|
ImGui::SameLine(60.0f);
|
||||||
ImGui::Text("%06uKB / %06uKB Buffers: %u", ((uint32)(totalSize - freeSize) + 1023) / 1024, ((uint32)totalSize + 1023) / 1024, (uint32)numBuffers);
|
ImGui::Text("%06uKB / %06uKB Buffers: %u", ((uint32)(totalSize - freeSize) + 1023) / 1024, ((uint32)totalSize + 1023) / 1024, (uint32)numBuffers);
|
||||||
|
|
||||||
memoryManager->getIndexAllocator().GetStats(numBuffers, totalSize, freeSize);
|
memoryManager->GetIndexAllocator().GetStats(numBuffers, totalSize, freeSize);
|
||||||
ImGui::Text("Index");
|
ImGui::Text("Index");
|
||||||
ImGui::SameLine(60.0f);
|
ImGui::SameLine(60.0f);
|
||||||
ImGui::Text("%06uKB / %06uKB Buffers: %u", ((uint32)(totalSize - freeSize) + 1023) / 1024, ((uint32)totalSize + 1023) / 1024, (uint32)numBuffers);
|
ImGui::Text("%06uKB / %06uKB Buffers: %u", ((uint32)(totalSize - freeSize) + 1023) / 1024, ((uint32)totalSize + 1023) / 1024, (uint32)numBuffers);
|
||||||
|
@ -3752,7 +3799,7 @@ void VKRDestructibleObject::flagForCurrentCommandBuffer()
|
||||||
|
|
||||||
bool VKRDestructibleObject::canDestroy()
|
bool VKRDestructibleObject::canDestroy()
|
||||||
{
|
{
|
||||||
if (refCount > 0)
|
if (m_refCount > 0)
|
||||||
return false;
|
return false;
|
||||||
return VulkanRenderer::GetInstance()->HasCommandBufferFinished(m_lastCmdBufferId);
|
return VulkanRenderer::GetInstance()->HasCommandBufferFinished(m_lastCmdBufferId);
|
||||||
}
|
}
|
||||||
|
@ -3793,6 +3840,111 @@ VKRObjectTextureView::~VKRObjectTextureView()
|
||||||
performanceMonitor.vk.numImageViews.decrement();
|
performanceMonitor.vk.numImageViews.decrement();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static uint64 CalcHashSamplerCreateInfo(const VkSamplerCreateInfo& info)
|
||||||
|
{
|
||||||
|
uint64 h = 0xcbf29ce484222325ULL;
|
||||||
|
auto fnvHashCombine = [](uint64_t &h, auto val) {
|
||||||
|
using T = decltype(val);
|
||||||
|
static_assert(sizeof(T) <= 8);
|
||||||
|
uint64_t val64 = 0;
|
||||||
|
std::memcpy(&val64, &val, sizeof(val));
|
||||||
|
h ^= val64;
|
||||||
|
h *= 0x100000001b3ULL;
|
||||||
|
};
|
||||||
|
cemu_assert_debug(info.sType == VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO);
|
||||||
|
fnvHashCombine(h, info.flags);
|
||||||
|
fnvHashCombine(h, info.magFilter);
|
||||||
|
fnvHashCombine(h, info.minFilter);
|
||||||
|
fnvHashCombine(h, info.mipmapMode);
|
||||||
|
fnvHashCombine(h, info.addressModeU);
|
||||||
|
fnvHashCombine(h, info.addressModeV);
|
||||||
|
fnvHashCombine(h, info.addressModeW);
|
||||||
|
fnvHashCombine(h, info.mipLodBias);
|
||||||
|
fnvHashCombine(h, info.anisotropyEnable);
|
||||||
|
if(info.anisotropyEnable == VK_TRUE)
|
||||||
|
fnvHashCombine(h, info.maxAnisotropy);
|
||||||
|
fnvHashCombine(h, info.compareEnable);
|
||||||
|
if(info.compareEnable == VK_TRUE)
|
||||||
|
fnvHashCombine(h, info.compareOp);
|
||||||
|
fnvHashCombine(h, info.minLod);
|
||||||
|
fnvHashCombine(h, info.maxLod);
|
||||||
|
fnvHashCombine(h, info.borderColor);
|
||||||
|
fnvHashCombine(h, info.unnormalizedCoordinates);
|
||||||
|
// handle custom border color
|
||||||
|
VkBaseOutStructure* ext = (VkBaseOutStructure*)info.pNext;
|
||||||
|
while(ext)
|
||||||
|
{
|
||||||
|
if(ext->sType == VK_STRUCTURE_TYPE_SAMPLER_CUSTOM_BORDER_COLOR_CREATE_INFO_EXT)
|
||||||
|
{
|
||||||
|
auto* extInfo = (VkSamplerCustomBorderColorCreateInfoEXT*)ext;
|
||||||
|
fnvHashCombine(h, extInfo->customBorderColor.uint32[0]);
|
||||||
|
fnvHashCombine(h, extInfo->customBorderColor.uint32[1]);
|
||||||
|
fnvHashCombine(h, extInfo->customBorderColor.uint32[2]);
|
||||||
|
fnvHashCombine(h, extInfo->customBorderColor.uint32[3]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cemu_assert_unimplemented();
|
||||||
|
}
|
||||||
|
ext = ext->pNext;
|
||||||
|
}
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unordered_map<uint64, VKRObjectSampler*> VKRObjectSampler::s_samplerCache;
|
||||||
|
|
||||||
|
VKRObjectSampler::VKRObjectSampler(VkSamplerCreateInfo* samplerInfo)
|
||||||
|
{
|
||||||
|
auto* vulkanRenderer = VulkanRenderer::GetInstance();
|
||||||
|
if (vkCreateSampler(vulkanRenderer->GetLogicalDevice(), samplerInfo, nullptr, &m_sampler) != VK_SUCCESS)
|
||||||
|
vulkanRenderer->UnrecoverableError("Failed to create texture sampler");
|
||||||
|
performanceMonitor.vk.numSamplers.increment();
|
||||||
|
m_hash = CalcHashSamplerCreateInfo(*samplerInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
VKRObjectSampler::~VKRObjectSampler()
|
||||||
|
{
|
||||||
|
vkDestroySampler(VulkanRenderer::GetInstance()->GetLogicalDevice(), m_sampler, nullptr);
|
||||||
|
performanceMonitor.vk.numSamplers.decrement();
|
||||||
|
// remove from cache
|
||||||
|
auto it = s_samplerCache.find(m_hash);
|
||||||
|
if(it != s_samplerCache.end())
|
||||||
|
s_samplerCache.erase(it);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VKRObjectSampler::RefCountReachedZero()
|
||||||
|
{
|
||||||
|
VulkanRenderer::GetInstance()->ReleaseDestructibleObject(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
VKRObjectSampler* VKRObjectSampler::GetOrCreateSampler(VkSamplerCreateInfo* samplerInfo)
|
||||||
|
{
|
||||||
|
auto* vulkanRenderer = VulkanRenderer::GetInstance();
|
||||||
|
uint64 hash = CalcHashSamplerCreateInfo(*samplerInfo);
|
||||||
|
auto it = s_samplerCache.find(hash);
|
||||||
|
if (it != s_samplerCache.end())
|
||||||
|
{
|
||||||
|
auto* sampler = it->second;
|
||||||
|
return sampler;
|
||||||
|
}
|
||||||
|
auto* sampler = new VKRObjectSampler(samplerInfo);
|
||||||
|
s_samplerCache[hash] = sampler;
|
||||||
|
return sampler;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VKRObjectSampler::DestroyCache()
|
||||||
|
{
|
||||||
|
// assuming all other objects which depend on vkSampler are destroyed, this cache should also have been emptied already
|
||||||
|
// but just to be sure lets still clear the cache
|
||||||
|
cemu_assert_debug(s_samplerCache.empty());
|
||||||
|
for(auto& sampler : s_samplerCache)
|
||||||
|
{
|
||||||
|
cemu_assert_debug(sampler.second->m_refCount == 0);
|
||||||
|
delete sampler.second;
|
||||||
|
}
|
||||||
|
s_samplerCache.clear();
|
||||||
|
}
|
||||||
|
|
||||||
VKRObjectRenderPass::VKRObjectRenderPass(AttachmentInfo_t& attachmentInfo, sint32 colorAttachmentCount)
|
VKRObjectRenderPass::VKRObjectRenderPass(AttachmentInfo_t& attachmentInfo, sint32 colorAttachmentCount)
|
||||||
{
|
{
|
||||||
// generate helper hash for pipeline state
|
// generate helper hash for pipeline state
|
||||||
|
|
|
@ -137,8 +137,8 @@ class VulkanRenderer : public Renderer
|
||||||
public:
|
public:
|
||||||
|
|
||||||
// memory management
|
// memory management
|
||||||
VKRMemoryManager* memoryManager{};
|
std::unique_ptr<VKRMemoryManager> memoryManager;
|
||||||
VKRMemoryManager* GetMemoryManager() const { return memoryManager; };
|
VKRMemoryManager* GetMemoryManager() const { return memoryManager.get(); };
|
||||||
|
|
||||||
VkSupportedFormatInfo_t m_supportedFormatInfo;
|
VkSupportedFormatInfo_t m_supportedFormatInfo;
|
||||||
|
|
||||||
|
@ -328,8 +328,9 @@ public:
|
||||||
|
|
||||||
RendererShader* shader_create(RendererShader::ShaderType type, uint64 baseHash, uint64 auxHash, const std::string& source, bool isGameShader, bool isGfxPackShader) override;
|
RendererShader* shader_create(RendererShader::ShaderType type, uint64 baseHash, uint64 auxHash, const std::string& source, bool isGameShader, bool isGfxPackShader) override;
|
||||||
|
|
||||||
void* indexData_reserveIndexMemory(uint32 size, uint32& offset, uint32& bufferIndex) override;
|
IndexAllocation indexData_reserveIndexMemory(uint32 size) override;
|
||||||
void indexData_uploadIndexMemory(uint32 offset, uint32 size) override;
|
void indexData_releaseIndexMemory(IndexAllocation& allocation) override;
|
||||||
|
void indexData_uploadIndexMemory(IndexAllocation& allocation) override;
|
||||||
|
|
||||||
// externally callable
|
// externally callable
|
||||||
void GetTextureFormatInfoVK(Latte::E_GX2SURFFMT format, bool isDepth, Latte::E_DIM dim, sint32 width, sint32 height, FormatInfoVK* formatInfoOut);
|
void GetTextureFormatInfoVK(Latte::E_GX2SURFFMT format, bool isDepth, Latte::E_DIM dim, sint32 width, sint32 height, FormatInfoVK* formatInfoOut);
|
||||||
|
@ -582,6 +583,8 @@ private:
|
||||||
std::shared_mutex m_pipeline_cache_save_mutex;
|
std::shared_mutex m_pipeline_cache_save_mutex;
|
||||||
std::thread m_pipeline_cache_save_thread;
|
std::thread m_pipeline_cache_save_thread;
|
||||||
VkPipelineCache m_pipeline_cache{ nullptr };
|
VkPipelineCache m_pipeline_cache{ nullptr };
|
||||||
|
std::unordered_map<uint64, VkPipeline> m_backbufferBlitPipelineCache;
|
||||||
|
std::unordered_map<uint64, VkDescriptorSet> m_backbufferBlitDescriptorSetCache;
|
||||||
VkPipelineLayout m_pipelineLayout{nullptr};
|
VkPipelineLayout m_pipelineLayout{nullptr};
|
||||||
VkCommandPool m_commandPool{ nullptr };
|
VkCommandPool m_commandPool{ nullptr };
|
||||||
|
|
||||||
|
@ -859,7 +862,7 @@ private:
|
||||||
memBarrier.pNext = nullptr;
|
memBarrier.pNext = nullptr;
|
||||||
|
|
||||||
VkPipelineStageFlags srcStages = VK_PIPELINE_STAGE_TRANSFER_BIT;
|
VkPipelineStageFlags srcStages = VK_PIPELINE_STAGE_TRANSFER_BIT;
|
||||||
VkPipelineStageFlags dstStages = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
|
VkPipelineStageFlags dstStages = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT;
|
||||||
|
|
||||||
memBarrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT;
|
memBarrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||||
memBarrier.dstAccessMask = 0;
|
memBarrier.dstAccessMask = 0;
|
||||||
|
|
|
@ -357,18 +357,20 @@ PipelineInfo* VulkanRenderer::draw_getOrCreateGraphicsPipeline(uint32 indexCount
|
||||||
return draw_createGraphicsPipeline(indexCount);
|
return draw_createGraphicsPipeline(indexCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
void* VulkanRenderer::indexData_reserveIndexMemory(uint32 size, uint32& offset, uint32& bufferIndex)
|
Renderer::IndexAllocation VulkanRenderer::indexData_reserveIndexMemory(uint32 size)
|
||||||
{
|
{
|
||||||
auto& indexAllocator = this->memoryManager->getIndexAllocator();
|
VKRSynchronizedHeapAllocator::AllocatorReservation* resv = memoryManager->GetIndexAllocator().AllocateBufferMemory(size, 32);
|
||||||
auto resv = indexAllocator.AllocateBufferMemory(size, 32);
|
return { resv->memPtr, resv };
|
||||||
offset = resv.bufferOffset;
|
|
||||||
bufferIndex = resv.bufferIndex;
|
|
||||||
return resv.memPtr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void VulkanRenderer::indexData_uploadIndexMemory(uint32 offset, uint32 size)
|
void VulkanRenderer::indexData_releaseIndexMemory(IndexAllocation& allocation)
|
||||||
{
|
{
|
||||||
// does nothing since the index buffer memory is coherent
|
memoryManager->GetIndexAllocator().FreeReservation((VKRSynchronizedHeapAllocator::AllocatorReservation*)allocation.rendererInternal);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanRenderer::indexData_uploadIndexMemory(IndexAllocation& allocation)
|
||||||
|
{
|
||||||
|
memoryManager->GetIndexAllocator().FlushReservation((VKRSynchronizedHeapAllocator::AllocatorReservation*)allocation.rendererInternal);
|
||||||
}
|
}
|
||||||
|
|
||||||
float s_vkUniformData[512 * 4];
|
float s_vkUniformData[512 * 4];
|
||||||
|
@ -727,7 +729,6 @@ VkDescriptorSetInfo* VulkanRenderer::draw_getOrCreateDescriptorSet(PipelineInfo*
|
||||||
|
|
||||||
VkSamplerCustomBorderColorCreateInfoEXT samplerCustomBorderColor{};
|
VkSamplerCustomBorderColorCreateInfoEXT samplerCustomBorderColor{};
|
||||||
|
|
||||||
VkSampler sampler;
|
|
||||||
VkSamplerCreateInfo samplerInfo{};
|
VkSamplerCreateInfo samplerInfo{};
|
||||||
samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
|
samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
|
||||||
|
|
||||||
|
@ -900,9 +901,9 @@ VkDescriptorSetInfo* VulkanRenderer::draw_getOrCreateDescriptorSet(PipelineInfo*
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (vkCreateSampler(m_logicalDevice, &samplerInfo, nullptr, &sampler) != VK_SUCCESS)
|
VKRObjectSampler* samplerObj = VKRObjectSampler::GetOrCreateSampler(&samplerInfo);
|
||||||
UnrecoverableError("Failed to create texture sampler");
|
vkObjDS->addRef(samplerObj);
|
||||||
info.sampler = sampler;
|
info.sampler = samplerObj->GetSampler();
|
||||||
textureArray.emplace_back(info);
|
textureArray.emplace_back(info);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1163,28 +1164,17 @@ void VulkanRenderer::draw_prepareDescriptorSets(PipelineInfo* pipeline_info, VkD
|
||||||
const auto geometryShader = LatteSHRC_GetActiveGeometryShader();
|
const auto geometryShader = LatteSHRC_GetActiveGeometryShader();
|
||||||
const auto pixelShader = LatteSHRC_GetActivePixelShader();
|
const auto pixelShader = LatteSHRC_GetActivePixelShader();
|
||||||
|
|
||||||
|
auto prepareShaderDescriptors = [this, &pipeline_info](LatteDecompilerShader* shader) -> VkDescriptorSetInfo* {
|
||||||
if (vertexShader)
|
if (!shader)
|
||||||
{
|
return nullptr;
|
||||||
auto descriptorSetInfo = draw_getOrCreateDescriptorSet(pipeline_info, vertexShader);
|
auto descriptorSetInfo = draw_getOrCreateDescriptorSet(pipeline_info, shader);
|
||||||
descriptorSetInfo->m_vkObjDescriptorSet->flagForCurrentCommandBuffer();
|
descriptorSetInfo->m_vkObjDescriptorSet->flagForCurrentCommandBuffer();
|
||||||
vertexDS = descriptorSetInfo;
|
return descriptorSetInfo;
|
||||||
}
|
};
|
||||||
|
|
||||||
if (pixelShader)
|
vertexDS = prepareShaderDescriptors(vertexShader);
|
||||||
{
|
pixelDS = prepareShaderDescriptors(pixelShader);
|
||||||
auto descriptorSetInfo = draw_getOrCreateDescriptorSet(pipeline_info, pixelShader);
|
geometryDS = prepareShaderDescriptors(geometryShader);
|
||||||
descriptorSetInfo->m_vkObjDescriptorSet->flagForCurrentCommandBuffer();
|
|
||||||
pixelDS = descriptorSetInfo;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
if (geometryShader)
|
|
||||||
{
|
|
||||||
auto descriptorSetInfo = draw_getOrCreateDescriptorSet(pipeline_info, geometryShader);
|
|
||||||
descriptorSetInfo->m_vkObjDescriptorSet->flagForCurrentCommandBuffer();
|
|
||||||
geometryDS = descriptorSetInfo;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void VulkanRenderer::draw_updateVkBlendConstants()
|
void VulkanRenderer::draw_updateVkBlendConstants()
|
||||||
|
@ -1415,14 +1405,15 @@ void VulkanRenderer::draw_execute(uint32 baseVertex, uint32 baseInstance, uint32
|
||||||
uint32 hostIndexCount;
|
uint32 hostIndexCount;
|
||||||
uint32 indexMin = 0;
|
uint32 indexMin = 0;
|
||||||
uint32 indexMax = 0;
|
uint32 indexMax = 0;
|
||||||
uint32 indexBufferOffset = 0;
|
Renderer::IndexAllocation indexAllocation;
|
||||||
uint32 indexBufferIndex = 0;
|
LatteIndices_decode(memory_getPointerFromVirtualOffset(indexDataMPTR), indexType, count, primitiveMode, indexMin, indexMax, hostIndexType, hostIndexCount, indexAllocation);
|
||||||
LatteIndices_decode(memory_getPointerFromVirtualOffset(indexDataMPTR), indexType, count, primitiveMode, indexMin, indexMax, hostIndexType, hostIndexCount, indexBufferOffset, indexBufferIndex);
|
VKRSynchronizedHeapAllocator::AllocatorReservation* indexReservation = (VKRSynchronizedHeapAllocator::AllocatorReservation*)indexAllocation.rendererInternal;
|
||||||
|
|
||||||
// update index binding
|
// update index binding
|
||||||
bool isPrevIndexData = false;
|
bool isPrevIndexData = false;
|
||||||
if (hostIndexType != INDEX_TYPE::NONE)
|
if (hostIndexType != INDEX_TYPE::NONE)
|
||||||
{
|
{
|
||||||
|
uint32 indexBufferIndex = indexReservation->bufferIndex;
|
||||||
|
uint32 indexBufferOffset = indexReservation->bufferOffset;
|
||||||
if (m_state.activeIndexBufferOffset != indexBufferOffset || m_state.activeIndexBufferIndex != indexBufferIndex || m_state.activeIndexType != hostIndexType)
|
if (m_state.activeIndexBufferOffset != indexBufferOffset || m_state.activeIndexBufferIndex != indexBufferIndex || m_state.activeIndexType != hostIndexType)
|
||||||
{
|
{
|
||||||
m_state.activeIndexType = hostIndexType;
|
m_state.activeIndexType = hostIndexType;
|
||||||
|
@ -1435,7 +1426,7 @@ void VulkanRenderer::draw_execute(uint32 baseVertex, uint32 baseInstance, uint32
|
||||||
vkType = VK_INDEX_TYPE_UINT32;
|
vkType = VK_INDEX_TYPE_UINT32;
|
||||||
else
|
else
|
||||||
cemu_assert(false);
|
cemu_assert(false);
|
||||||
vkCmdBindIndexBuffer(m_state.currentCommandBuffer, memoryManager->getIndexAllocator().GetBufferByIndex(indexBufferIndex), indexBufferOffset, vkType);
|
vkCmdBindIndexBuffer(m_state.currentCommandBuffer, indexReservation->vkBuffer, indexBufferOffset, vkType);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
isPrevIndexData = true;
|
isPrevIndexData = true;
|
||||||
|
|
|
@ -76,6 +76,30 @@ struct CopySurfacePipelineInfo
|
||||||
CopySurfacePipelineInfo() = default;
|
CopySurfacePipelineInfo() = default;
|
||||||
CopySurfacePipelineInfo(VkDevice device) : m_device(device) {}
|
CopySurfacePipelineInfo(VkDevice device) : m_device(device) {}
|
||||||
CopySurfacePipelineInfo(const CopySurfacePipelineInfo& info) = delete;
|
CopySurfacePipelineInfo(const CopySurfacePipelineInfo& info) = delete;
|
||||||
|
~CopySurfacePipelineInfo()
|
||||||
|
{
|
||||||
|
auto renderer = VulkanRenderer::GetInstance();
|
||||||
|
renderer->ReleaseDestructibleObject(vkObjRenderPass);
|
||||||
|
renderer->ReleaseDestructibleObject(vkObjPipeline);
|
||||||
|
|
||||||
|
for(auto& i : map_framebuffers)
|
||||||
|
{
|
||||||
|
for(auto& fb : i.second.m_array)
|
||||||
|
{
|
||||||
|
renderer->ReleaseDestructibleObject(fb->vkObjFramebuffer);
|
||||||
|
renderer->ReleaseDestructibleObject(fb->vkObjImageView);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for(auto& i : map_descriptors)
|
||||||
|
{
|
||||||
|
for(auto& descriptor : i.second.m_array)
|
||||||
|
{
|
||||||
|
renderer->ReleaseDestructibleObject(descriptor->vkObjImageView);
|
||||||
|
renderer->ReleaseDestructibleObject(descriptor->vkObjDescriptorSet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
VkDevice m_device = nullptr;
|
VkDevice m_device = nullptr;
|
||||||
|
|
||||||
|
@ -842,5 +866,9 @@ void VulkanRenderer::surfaceCopy_notifyTextureRelease(LatteTextureVk* hostTextur
|
||||||
|
|
||||||
void VulkanRenderer::surfaceCopy_cleanup()
|
void VulkanRenderer::surfaceCopy_cleanup()
|
||||||
{
|
{
|
||||||
// todo - release m_copySurfacePipelineCache etc
|
for(auto& i : m_copySurfacePipelineCache)
|
||||||
|
{
|
||||||
|
delete i.second;
|
||||||
|
}
|
||||||
|
m_copySurfacePipelineCache = {};
|
||||||
}
|
}
|
||||||
|
|
|
@ -469,7 +469,7 @@ namespace iosu
|
||||||
entry->ukn0C = 0;
|
entry->ukn0C = 0;
|
||||||
entry->sizeA = _swapEndianU64(0); // ukn
|
entry->sizeA = _swapEndianU64(0); // ukn
|
||||||
entry->sizeB = _swapEndianU64(dirSize);
|
entry->sizeB = _swapEndianU64(dirSize);
|
||||||
entry->time = _swapEndianU64((coreinit::coreinit_getOSTime() / ESPRESSO_TIMER_CLOCK));
|
entry->time = _swapEndianU64((coreinit::OSGetTime() / ESPRESSO_TIMER_CLOCK));
|
||||||
sprintf(entry->path, "%susr/save/%08x/%08x/meta/", devicePath, (uint32)(titleId >> 32), (uint32)(titleId & 0xFFFFFFFF));
|
sprintf(entry->path, "%susr/save/%08x/%08x/meta/", devicePath, (uint32)(titleId >> 32), (uint32)(titleId & 0xFFFFFFFF));
|
||||||
count++;
|
count++;
|
||||||
}
|
}
|
||||||
|
@ -504,7 +504,7 @@ namespace iosu
|
||||||
entry->ukn0C = 0;
|
entry->ukn0C = 0;
|
||||||
entry->sizeA = _swapEndianU64(0);
|
entry->sizeA = _swapEndianU64(0);
|
||||||
entry->sizeB = _swapEndianU64(0);
|
entry->sizeB = _swapEndianU64(0);
|
||||||
entry->time = _swapEndianU64((coreinit::coreinit_getOSTime() / ESPRESSO_TIMER_CLOCK));
|
entry->time = _swapEndianU64((coreinit::OSGetTime() / ESPRESSO_TIMER_CLOCK));
|
||||||
sprintf(entry->path, "%susr/save/%08x/%08x/meta/", devicePath, (uint32)(titleId >> 32), (uint32)(titleId & 0xFFFFFFFF));
|
sprintf(entry->path, "%susr/save/%08x/%08x/meta/", devicePath, (uint32)(titleId >> 32), (uint32)(titleId & 0xFFFFFFFF));
|
||||||
count++;
|
count++;
|
||||||
}
|
}
|
||||||
|
@ -584,7 +584,7 @@ namespace iosu
|
||||||
|
|
||||||
uint64 _ACPGetTimestamp()
|
uint64 _ACPGetTimestamp()
|
||||||
{
|
{
|
||||||
return coreinit::coreinit_getOSTime() / ESPRESSO_TIMER_CLOCK;
|
return coreinit::OSGetTime() / ESPRESSO_TIMER_CLOCK;
|
||||||
}
|
}
|
||||||
|
|
||||||
nnResult ACPUpdateSaveTimeStamp(uint32 persistentId, uint64 titleId, ACPDeviceType deviceType)
|
nnResult ACPUpdateSaveTimeStamp(uint32 persistentId, uint64 titleId, ACPDeviceType deviceType)
|
||||||
|
|
|
@ -186,7 +186,7 @@ namespace camera
|
||||||
if (g_cameraCounter == 0)
|
if (g_cameraCounter == 0)
|
||||||
{
|
{
|
||||||
coreinit::OSCreateAlarm(g_alarm_camera.GetPtr());
|
coreinit::OSCreateAlarm(g_alarm_camera.GetPtr());
|
||||||
coreinit::OSSetPeriodicAlarm(g_alarm_camera.GetPtr(), coreinit::coreinit_getOSTime(), (uint64)ESPRESSO_TIMER_CLOCK / 60ull, RPLLoader_MakePPCCallable(ppcCAMUpdate60));
|
coreinit::OSSetPeriodicAlarm(g_alarm_camera.GetPtr(), coreinit::OSGetTime(), (uint64)ESPRESSO_TIMER_CLOCK / 60ull, RPLLoader_MakePPCCallable(ppcCAMUpdate60));
|
||||||
}
|
}
|
||||||
g_cameraCounter++;
|
g_cameraCounter++;
|
||||||
|
|
||||||
|
|
|
@ -166,7 +166,7 @@ namespace coreinit
|
||||||
void alarm_update()
|
void alarm_update()
|
||||||
{
|
{
|
||||||
cemu_assert_debug(!__OSHasSchedulerLock());
|
cemu_assert_debug(!__OSHasSchedulerLock());
|
||||||
uint64 currentTick = coreinit::coreinit_getOSTime();
|
uint64 currentTick = coreinit::OSGetTime();
|
||||||
if (!OSHostAlarm::quickCheckForAlarm(currentTick))
|
if (!OSHostAlarm::quickCheckForAlarm(currentTick))
|
||||||
return;
|
return;
|
||||||
__OSLockScheduler();
|
__OSLockScheduler();
|
||||||
|
@ -233,7 +233,7 @@ namespace coreinit
|
||||||
if (period == 0)
|
if (period == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
uint64 currentTime = coreinit_getOSTime();
|
uint64 currentTime = OSGetTime();
|
||||||
|
|
||||||
uint64 ticksSinceStart = currentTime - startTime;
|
uint64 ticksSinceStart = currentTime - startTime;
|
||||||
uint64 numPeriods = ticksSinceStart / period;
|
uint64 numPeriods = ticksSinceStart / period;
|
||||||
|
@ -267,7 +267,7 @@ namespace coreinit
|
||||||
void OSSetAlarm(OSAlarm_t* alarm, uint64 delayInTicks, MPTR handlerFunc)
|
void OSSetAlarm(OSAlarm_t* alarm, uint64 delayInTicks, MPTR handlerFunc)
|
||||||
{
|
{
|
||||||
__OSLockScheduler();
|
__OSLockScheduler();
|
||||||
__OSInitiateAlarm(alarm, coreinit_getOSTime() + delayInTicks, 0, handlerFunc, false);
|
__OSInitiateAlarm(alarm, OSGetTime() + delayInTicks, 0, handlerFunc, false);
|
||||||
__OSUnlockScheduler();
|
__OSUnlockScheduler();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -310,7 +310,7 @@ namespace coreinit
|
||||||
while( true )
|
while( true )
|
||||||
{
|
{
|
||||||
OSWaitEvent(g_alarmEvent.GetPtr());
|
OSWaitEvent(g_alarmEvent.GetPtr());
|
||||||
uint64 currentTick = coreinit_getOSTime();
|
uint64 currentTick = OSGetTime();
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
// get alarm to fire
|
// get alarm to fire
|
||||||
|
|
|
@ -86,11 +86,11 @@ namespace coreinit
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// loop until lock acquired or timeout occurred
|
// loop until lock acquired or timeout occurred
|
||||||
uint64 timeoutValue = coreinit_getTimerTick() + coreinit::EspressoTime::ConvertNsToTimerTicks(timeout);
|
uint64 timeoutValue = OSGetSystemTime() + coreinit::EspressoTime::ConvertNsToTimerTicks(timeout);
|
||||||
while (!spinlock->ownerThread.atomic_compare_exchange(nullptr, currentThread))
|
while (!spinlock->ownerThread.atomic_compare_exchange(nullptr, currentThread))
|
||||||
{
|
{
|
||||||
OSYieldThread();
|
OSYieldThread();
|
||||||
if (coreinit_getTimerTick() >= timeoutValue)
|
if (OSGetSystemTime() >= timeoutValue)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -182,11 +182,11 @@ namespace coreinit
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// loop until lock acquired or timeout occurred
|
// loop until lock acquired or timeout occurred
|
||||||
uint64 timeoutValue = coreinit_getTimerTick() + coreinit::EspressoTime::ConvertNsToTimerTicks(timeout);
|
uint64 timeoutValue = OSGetSystemTime() + coreinit::EspressoTime::ConvertNsToTimerTicks(timeout);
|
||||||
while (!spinlock->ownerThread.atomic_compare_exchange(nullptr, currentThread))
|
while (!spinlock->ownerThread.atomic_compare_exchange(nullptr, currentThread))
|
||||||
{
|
{
|
||||||
OSYieldThread();
|
OSYieldThread();
|
||||||
if (coreinit_getTimerTick() >= timeoutValue)
|
if (OSGetSystemTime() >= timeoutValue)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,8 +73,6 @@ namespace coreinit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64 coreinit_getOSTime();
|
|
||||||
|
|
||||||
bool OSWaitEventWithTimeout(OSEvent* event, uint64 timeout)
|
bool OSWaitEventWithTimeout(OSEvent* event, uint64 timeout)
|
||||||
{
|
{
|
||||||
__OSLockScheduler();
|
__OSLockScheduler();
|
||||||
|
@ -95,14 +93,14 @@ namespace coreinit
|
||||||
|
|
||||||
// workaround for a bad implementation in some Unity games (like Qube Directors Cut, see FEventWiiU::Wait)
|
// workaround for a bad implementation in some Unity games (like Qube Directors Cut, see FEventWiiU::Wait)
|
||||||
// where the the return value of OSWaitEventWithTimeout is ignored and instead the game measures the elapsed time to determine if a timeout occurred
|
// where the the return value of OSWaitEventWithTimeout is ignored and instead the game measures the elapsed time to determine if a timeout occurred
|
||||||
timeout = timeout * 98ULL / 100ULL; // 98% (we want the function to return slightly before the actual timeout)
|
if (timeout < 0x00FFFFFFFFFFFFFFULL)
|
||||||
|
timeout = timeout * 98ULL / 100ULL; // 98% (we want the function to return slightly before the actual timeout)
|
||||||
|
|
||||||
WaitEventWithTimeoutData data;
|
WaitEventWithTimeoutData data;
|
||||||
data.thread = OSGetCurrentThread();
|
data.thread = OSGetCurrentThread();
|
||||||
data.threadQueue = &event->threadQueue;
|
data.threadQueue = &event->threadQueue;
|
||||||
data.hasTimeout = false;
|
data.hasTimeout = false;
|
||||||
|
auto hostAlarm = coreinit::OSHostAlarmCreate(OSGetTime() + coreinit::EspressoTime::ConvertNsToTimerTicks(timeout), 0, _OSWaitEventWithTimeoutHandler, &data);
|
||||||
auto hostAlarm = coreinit::OSHostAlarmCreate(coreinit::coreinit_getOSTime() + coreinit::EspressoTime::ConvertNsToTimerTicks(timeout), 0, _OSWaitEventWithTimeoutHandler, &data);
|
|
||||||
event->threadQueue.queueAndWait(OSGetCurrentThread());
|
event->threadQueue.queueAndWait(OSGetCurrentThread());
|
||||||
coreinit::OSHostAlarmDestroy(hostAlarm);
|
coreinit::OSHostAlarmDestroy(hostAlarm);
|
||||||
if (data.hasTimeout)
|
if (data.hasTimeout)
|
||||||
|
|
|
@ -655,7 +655,7 @@ namespace coreinit
|
||||||
StackAllocator<OSThreadQueue> _threadQueue;
|
StackAllocator<OSThreadQueue> _threadQueue;
|
||||||
OSInitThreadQueue(_threadQueue.GetPointer());
|
OSInitThreadQueue(_threadQueue.GetPointer());
|
||||||
__OSLockScheduler();
|
__OSLockScheduler();
|
||||||
OSHostAlarm* hostAlarm = OSHostAlarmCreate(coreinit_getOSTime() + ticks, 0, _OSSleepTicks_alarmHandler, _threadQueue.GetPointer());
|
OSHostAlarm* hostAlarm = OSHostAlarmCreate(OSGetTime() + ticks, 0, _OSSleepTicks_alarmHandler, _threadQueue.GetPointer());
|
||||||
_threadQueue.GetPointer()->queueAndWait(OSGetCurrentThread());
|
_threadQueue.GetPointer()->queueAndWait(OSGetCurrentThread());
|
||||||
OSHostAlarmDestroy(hostAlarm);
|
OSHostAlarmDestroy(hostAlarm);
|
||||||
__OSUnlockScheduler();
|
__OSUnlockScheduler();
|
||||||
|
|
|
@ -3,38 +3,32 @@
|
||||||
|
|
||||||
namespace coreinit
|
namespace coreinit
|
||||||
{
|
{
|
||||||
|
uint64 coreinit_GetMFTB()
|
||||||
uint64 coreinit_getTimerTick()
|
|
||||||
{
|
{
|
||||||
// bus clock is 1/5th of core clock
|
// bus clock is 1/5th of core clock
|
||||||
// timer clock is 1/4th of bus clock
|
// timer clock is 1/4th of bus clock
|
||||||
return PPCInterpreter_getMainCoreCycleCounter() / 20ULL;
|
return PPCInterpreter_getMainCoreCycleCounter() / 20ULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64 coreinit_getOSTime()
|
uint64 OSGetSystemTime()
|
||||||
{
|
{
|
||||||
return coreinit_getTimerTick() + ppcCyclesSince2000TimerClock;
|
return coreinit_GetMFTB();
|
||||||
}
|
|
||||||
|
|
||||||
void export_OSGetTick(PPCInterpreter_t* hCPU)
|
|
||||||
{
|
|
||||||
uint64 osTime = coreinit_getOSTime();
|
|
||||||
osLib_returnFromFunction(hCPU, (uint32)osTime);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64 OSGetTime()
|
uint64 OSGetTime()
|
||||||
{
|
{
|
||||||
return coreinit_getOSTime();
|
return OSGetSystemTime() + ppcCyclesSince2000TimerClock;
|
||||||
}
|
}
|
||||||
|
|
||||||
void export_OSGetSystemTime(PPCInterpreter_t* hCPU)
|
uint32 OSGetSystemTick()
|
||||||
{
|
{
|
||||||
osLib_returnFromFunction64(hCPU, coreinit_getTimerTick());
|
return static_cast<uint32>(coreinit_GetMFTB());
|
||||||
}
|
}
|
||||||
|
|
||||||
void export_OSGetSystemTick(PPCInterpreter_t* hCPU)
|
uint32 OSGetTick()
|
||||||
{
|
{
|
||||||
osLib_returnFromFunction(hCPU, (uint32)coreinit_getTimerTick());
|
uint64 osTime = OSGetTime();
|
||||||
|
return static_cast<uint32>(osTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32 getLeapDaysUntilYear(uint32 year)
|
uint32 getLeapDaysUntilYear(uint32 year)
|
||||||
|
@ -360,14 +354,13 @@ namespace coreinit
|
||||||
void InitializeTimeAndCalendar()
|
void InitializeTimeAndCalendar()
|
||||||
{
|
{
|
||||||
cafeExportRegister("coreinit", OSGetTime, LogType::Placeholder);
|
cafeExportRegister("coreinit", OSGetTime, LogType::Placeholder);
|
||||||
osLib_addFunction("coreinit", "OSGetSystemTime", export_OSGetSystemTime);
|
cafeExportRegister("coreinit", OSGetSystemTime, LogType::Placeholder);
|
||||||
osLib_addFunction("coreinit", "OSGetTick", export_OSGetTick);
|
cafeExportRegister("coreinit", OSGetTick, LogType::Placeholder);
|
||||||
osLib_addFunction("coreinit", "OSGetSystemTick", export_OSGetSystemTick);
|
cafeExportRegister("coreinit", OSGetSystemTick, LogType::Placeholder);
|
||||||
|
|
||||||
cafeExportRegister("coreinit", OSTicksToCalendarTime, LogType::Placeholder);
|
cafeExportRegister("coreinit", OSTicksToCalendarTime, LogType::Placeholder);
|
||||||
cafeExportRegister("coreinit", OSCalendarTimeToTicks, LogType::Placeholder);
|
cafeExportRegister("coreinit", OSCalendarTimeToTicks, LogType::Placeholder);
|
||||||
|
|
||||||
|
|
||||||
//timeTest();
|
//timeTest();
|
||||||
}
|
}
|
||||||
};
|
};
|
|
@ -40,25 +40,21 @@ namespace coreinit
|
||||||
|
|
||||||
inline TimerTicks ConvertNsToTimerTicks(uint64 ns)
|
inline TimerTicks ConvertNsToTimerTicks(uint64 ns)
|
||||||
{
|
{
|
||||||
return ((GetTimerClock() / 31250LL) * ((TimerTicks)ns) / 32000LL);
|
return static_cast<TimerTicks>((static_cast<uint64>(GetTimerClock()) / 31250ULL) * (ns) / 32000ULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline TimerTicks ConvertMsToTimerTicks(uint64 ms)
|
inline TimerTicks ConvertMsToTimerTicks(uint64 ms)
|
||||||
{
|
{
|
||||||
return (TimerTicks)ms * GetTimerClock() / 1000LL;
|
return static_cast<TimerTicks>(ms * static_cast<uint64>(GetTimerClock()) / 1000ULL);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
void OSTicksToCalendarTime(uint64 ticks, OSCalendarTime_t* calenderStruct);
|
void OSTicksToCalendarTime(uint64 ticks, OSCalendarTime_t* calenderStruct);
|
||||||
|
|
||||||
|
uint64 OSGetSystemTime();
|
||||||
uint64 OSGetTime();
|
uint64 OSGetTime();
|
||||||
|
uint32 OSGetSystemTick();
|
||||||
uint64 coreinit_getOSTime();
|
uint32 OSGetTick();
|
||||||
uint64 coreinit_getTimerTick();
|
|
||||||
|
|
||||||
static uint64 OSGetSystemTime()
|
|
||||||
{
|
|
||||||
return coreinit_getTimerTick();
|
|
||||||
}
|
|
||||||
|
|
||||||
void InitializeTimeAndCalendar();
|
void InitializeTimeAndCalendar();
|
||||||
};
|
};
|
||||||
|
|
|
@ -11,7 +11,7 @@ uint64 dmaeRetiredTimestamp = 0;
|
||||||
|
|
||||||
uint64 dmae_getTimestamp()
|
uint64 dmae_getTimestamp()
|
||||||
{
|
{
|
||||||
return coreinit::coreinit_getTimerTick();
|
return coreinit::OSGetSystemTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
void dmae_setRetiredTimestamp(uint64 timestamp)
|
void dmae_setRetiredTimestamp(uint64 timestamp)
|
||||||
|
|
|
@ -322,7 +322,7 @@ uint64 _prevReturnedGPUTime = 0;
|
||||||
|
|
||||||
uint64 Latte_GetTime()
|
uint64 Latte_GetTime()
|
||||||
{
|
{
|
||||||
uint64 gpuTime = coreinit::coreinit_getTimerTick();
|
uint64 gpuTime = coreinit::OSGetSystemTime();
|
||||||
gpuTime *= 20000ULL;
|
gpuTime *= 20000ULL;
|
||||||
if (gpuTime <= _prevReturnedGPUTime)
|
if (gpuTime <= _prevReturnedGPUTime)
|
||||||
gpuTime = _prevReturnedGPUTime + 1; // avoid ever returning identical timestamps
|
gpuTime = _prevReturnedGPUTime + 1; // avoid ever returning identical timestamps
|
||||||
|
|
|
@ -54,7 +54,7 @@ void gx2Export_GX2GetGPUTimeout(PPCInterpreter_t* hCPU)
|
||||||
void gx2Export_GX2SampleTopGPUCycle(PPCInterpreter_t* hCPU)
|
void gx2Export_GX2SampleTopGPUCycle(PPCInterpreter_t* hCPU)
|
||||||
{
|
{
|
||||||
cemuLog_log(LogType::GX2, "GX2SampleTopGPUCycle(0x{:08x})", hCPU->gpr[3]);
|
cemuLog_log(LogType::GX2, "GX2SampleTopGPUCycle(0x{:08x})", hCPU->gpr[3]);
|
||||||
memory_writeU64(hCPU->gpr[3], coreinit::coreinit_getTimerTick());
|
memory_writeU64(hCPU->gpr[3], coreinit::OSGetSystemTime());
|
||||||
osLib_returnFromFunction(hCPU, 0);
|
osLib_returnFromFunction(hCPU, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -315,7 +315,7 @@ namespace acp
|
||||||
ppcDefineParamU32BEPtr(timestamp64, 0);
|
ppcDefineParamU32BEPtr(timestamp64, 0);
|
||||||
ppcDefineParamU32BEPtr(ukn, 1); // probably timezone or offset? Could also be a bool for success/failed
|
ppcDefineParamU32BEPtr(ukn, 1); // probably timezone or offset? Could also be a bool for success/failed
|
||||||
|
|
||||||
uint64 t = coreinit::coreinit_getOSTime() + (uint64)((sint64)(ppcCyclesSince2000_UTC - ppcCyclesSince2000) / 20LL);
|
uint64 t = coreinit::OSGetTime() + (uint64)((sint64)(ppcCyclesSince2000_UTC - ppcCyclesSince2000) / 20LL);
|
||||||
|
|
||||||
timestamp64[0] = (uint32)(t >> 32);
|
timestamp64[0] = (uint32)(t >> 32);
|
||||||
timestamp64[1] = (uint32)(t & 0xFFFFFFFF);
|
timestamp64[1] = (uint32)(t & 0xFFFFFFFF);
|
||||||
|
|
|
@ -1,24 +1,12 @@
|
||||||
#include "nsyshid.h"
|
#include "nsyshid.h"
|
||||||
#include "Backend.h"
|
#include "Backend.h"
|
||||||
#include "BackendEmulated.h"
|
#include "BackendEmulated.h"
|
||||||
|
|
||||||
#if NSYSHID_ENABLE_BACKEND_LIBUSB
|
|
||||||
|
|
||||||
#include "BackendLibusb.h"
|
#include "BackendLibusb.h"
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if NSYSHID_ENABLE_BACKEND_WINDOWS_HID
|
|
||||||
|
|
||||||
#include "BackendWindowsHID.h"
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace nsyshid::backend
|
namespace nsyshid::backend
|
||||||
{
|
{
|
||||||
void AttachDefaultBackends()
|
void AttachDefaultBackends()
|
||||||
{
|
{
|
||||||
#if NSYSHID_ENABLE_BACKEND_LIBUSB
|
|
||||||
// add libusb backend
|
// add libusb backend
|
||||||
{
|
{
|
||||||
auto backendLibusb = std::make_shared<backend::libusb::BackendLibusb>();
|
auto backendLibusb = std::make_shared<backend::libusb::BackendLibusb>();
|
||||||
|
@ -27,17 +15,6 @@ namespace nsyshid::backend
|
||||||
AttachBackend(backendLibusb);
|
AttachBackend(backendLibusb);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif // NSYSHID_ENABLE_BACKEND_LIBUSB
|
|
||||||
#if NSYSHID_ENABLE_BACKEND_WINDOWS_HID
|
|
||||||
// add windows hid backend
|
|
||||||
{
|
|
||||||
auto backendWindowsHID = std::make_shared<backend::windows::BackendWindowsHID>();
|
|
||||||
if (backendWindowsHID->IsInitialisedOk())
|
|
||||||
{
|
|
||||||
AttachBackend(backendWindowsHID);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif // NSYSHID_ENABLE_BACKEND_WINDOWS_HID
|
|
||||||
// add emulated backend
|
// add emulated backend
|
||||||
{
|
{
|
||||||
auto backendEmulated = std::make_shared<backend::emulated::BackendEmulated>();
|
auto backendEmulated = std::make_shared<backend::emulated::BackendEmulated>();
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
#ifndef CEMU_NSYSHID_BACKEND_H
|
#pragma once
|
||||||
#define CEMU_NSYSHID_BACKEND_H
|
|
||||||
|
|
||||||
#include <list>
|
#include <list>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
@ -26,9 +25,9 @@ namespace nsyshid
|
||||||
struct TransferCommand
|
struct TransferCommand
|
||||||
{
|
{
|
||||||
uint8* data;
|
uint8* data;
|
||||||
sint32 length;
|
uint32 length;
|
||||||
|
|
||||||
TransferCommand(uint8* data, sint32 length)
|
TransferCommand(uint8* data, uint32 length)
|
||||||
: data(data), length(length)
|
: data(data), length(length)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -39,7 +38,7 @@ namespace nsyshid
|
||||||
{
|
{
|
||||||
sint32 bytesRead;
|
sint32 bytesRead;
|
||||||
|
|
||||||
ReadMessage(uint8* data, sint32 length, sint32 bytesRead)
|
ReadMessage(uint8* data, uint32 length, sint32 bytesRead)
|
||||||
: bytesRead(bytesRead), TransferCommand(data, length)
|
: bytesRead(bytesRead), TransferCommand(data, length)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -50,7 +49,7 @@ namespace nsyshid
|
||||||
{
|
{
|
||||||
sint32 bytesWritten;
|
sint32 bytesWritten;
|
||||||
|
|
||||||
WriteMessage(uint8* data, sint32 length, sint32 bytesWritten)
|
WriteMessage(uint8* data, uint32 length, sint32 bytesWritten)
|
||||||
: bytesWritten(bytesWritten), TransferCommand(data, length)
|
: bytesWritten(bytesWritten), TransferCommand(data, length)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -59,14 +58,11 @@ namespace nsyshid
|
||||||
|
|
||||||
struct ReportMessage final : TransferCommand
|
struct ReportMessage final : TransferCommand
|
||||||
{
|
{
|
||||||
uint8* reportData;
|
uint8 reportType;
|
||||||
sint32 length;
|
uint8 reportId;
|
||||||
uint8* originalData;
|
|
||||||
sint32 originalLength;
|
|
||||||
|
|
||||||
ReportMessage(uint8* reportData, sint32 length, uint8* originalData, sint32 originalLength)
|
ReportMessage(uint8 reportType, uint8 reportId, uint8* data, uint32 length)
|
||||||
: reportData(reportData), length(length), originalData(originalData),
|
: reportType(reportType), reportId(reportId), TransferCommand(data, length)
|
||||||
originalLength(originalLength), TransferCommand(reportData, length)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
using TransferCommand::TransferCommand;
|
using TransferCommand::TransferCommand;
|
||||||
|
@ -77,7 +73,8 @@ namespace nsyshid
|
||||||
static_assert(offsetof(HID_t, ifIndex) == 0xC, "");
|
static_assert(offsetof(HID_t, ifIndex) == 0xC, "");
|
||||||
static_assert(offsetof(HID_t, protocol) == 0xE, "");
|
static_assert(offsetof(HID_t, protocol) == 0xE, "");
|
||||||
|
|
||||||
class Device {
|
class Device
|
||||||
|
{
|
||||||
public:
|
public:
|
||||||
Device() = delete;
|
Device() = delete;
|
||||||
|
|
||||||
|
@ -131,16 +128,21 @@ namespace nsyshid
|
||||||
|
|
||||||
virtual bool GetDescriptor(uint8 descType,
|
virtual bool GetDescriptor(uint8 descType,
|
||||||
uint8 descIndex,
|
uint8 descIndex,
|
||||||
uint8 lang,
|
uint16 lang,
|
||||||
uint8* output,
|
uint8* output,
|
||||||
uint32 outputMaxLength) = 0;
|
uint32 outputMaxLength) = 0;
|
||||||
|
|
||||||
|
virtual bool SetIdle(uint8 ifIndex,
|
||||||
|
uint8 reportId,
|
||||||
|
uint8 duration) = 0;
|
||||||
|
|
||||||
virtual bool SetProtocol(uint8 ifIndex, uint8 protocol) = 0;
|
virtual bool SetProtocol(uint8 ifIndex, uint8 protocol) = 0;
|
||||||
|
|
||||||
virtual bool SetReport(ReportMessage* message) = 0;
|
virtual bool SetReport(ReportMessage* message) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Backend {
|
class Backend
|
||||||
|
{
|
||||||
public:
|
public:
|
||||||
Backend();
|
Backend();
|
||||||
|
|
||||||
|
@ -188,5 +190,3 @@ namespace nsyshid
|
||||||
void AttachDefaultBackends();
|
void AttachDefaultBackends();
|
||||||
}
|
}
|
||||||
} // namespace nsyshid
|
} // namespace nsyshid
|
||||||
|
|
||||||
#endif // CEMU_NSYSHID_BACKEND_H
|
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
#include "BackendLibusb.h"
|
#include "BackendLibusb.h"
|
||||||
|
|
||||||
#if NSYSHID_ENABLE_BACKEND_LIBUSB
|
|
||||||
|
|
||||||
namespace nsyshid::backend::libusb
|
namespace nsyshid::backend::libusb
|
||||||
{
|
{
|
||||||
BackendLibusb::BackendLibusb()
|
BackendLibusb::BackendLibusb()
|
||||||
|
@ -16,7 +14,7 @@ namespace nsyshid::backend::libusb
|
||||||
{
|
{
|
||||||
m_ctx = nullptr;
|
m_ctx = nullptr;
|
||||||
cemuLog_logDebug(LogType::Force, "nsyshid::BackendLibusb: failed to initialize libusb, return code: {}",
|
cemuLog_logDebug(LogType::Force, "nsyshid::BackendLibusb: failed to initialize libusb, return code: {}",
|
||||||
m_initReturnCode);
|
m_initReturnCode);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,8 +33,8 @@ namespace nsyshid::backend::libusb
|
||||||
if (ret != LIBUSB_SUCCESS)
|
if (ret != LIBUSB_SUCCESS)
|
||||||
{
|
{
|
||||||
cemuLog_logDebug(LogType::Force,
|
cemuLog_logDebug(LogType::Force,
|
||||||
"nsyshid::BackendLibusb: failed to register hotplug callback with return code {}",
|
"nsyshid::BackendLibusb: failed to register hotplug callback with return code {}",
|
||||||
ret);
|
ret);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -53,8 +51,8 @@ namespace nsyshid::backend::libusb
|
||||||
if (ret != 0)
|
if (ret != 0)
|
||||||
{
|
{
|
||||||
cemuLog_logDebug(LogType::Force,
|
cemuLog_logDebug(LogType::Force,
|
||||||
"nsyshid::BackendLibusb: hotplug thread: error handling events: {}",
|
"nsyshid::BackendLibusb: hotplug thread: error handling events: {}",
|
||||||
ret);
|
ret);
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
|
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -139,8 +137,8 @@ namespace nsyshid::backend::libusb
|
||||||
case LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED:
|
case LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED:
|
||||||
{
|
{
|
||||||
cemuLog_logDebug(LogType::Force, "nsyshid::BackendLibusb::OnHotplug(): device arrived: {:04x}:{:04x}",
|
cemuLog_logDebug(LogType::Force, "nsyshid::BackendLibusb::OnHotplug(): device arrived: {:04x}:{:04x}",
|
||||||
desc.idVendor,
|
desc.idVendor,
|
||||||
desc.idProduct);
|
desc.idProduct);
|
||||||
auto device = CheckAndCreateDevice(dev);
|
auto device = CheckAndCreateDevice(dev);
|
||||||
if (device != nullptr)
|
if (device != nullptr)
|
||||||
{
|
{
|
||||||
|
@ -167,8 +165,8 @@ namespace nsyshid::backend::libusb
|
||||||
case LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT:
|
case LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT:
|
||||||
{
|
{
|
||||||
cemuLog_logDebug(LogType::Force, "nsyshid::BackendLibusb::OnHotplug(): device left: {:04x}:{:04x}",
|
cemuLog_logDebug(LogType::Force, "nsyshid::BackendLibusb::OnHotplug(): device left: {:04x}:{:04x}",
|
||||||
desc.idVendor,
|
desc.idVendor,
|
||||||
desc.idProduct);
|
desc.idProduct);
|
||||||
auto device = FindLibusbDevice(dev);
|
auto device = FindLibusbDevice(dev);
|
||||||
if (device != nullptr)
|
if (device != nullptr)
|
||||||
{
|
{
|
||||||
|
@ -204,7 +202,7 @@ namespace nsyshid::backend::libusb
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
{
|
{
|
||||||
cemuLog_logDebug(LogType::Force,
|
cemuLog_logDebug(LogType::Force,
|
||||||
"nsyshid::BackendLibusb::FindLibusbDevice(): failed to get device descriptor");
|
"nsyshid::BackendLibusb::FindLibusbDevice(): failed to get device descriptor");
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
uint8 busNumber = libusb_get_bus_number(dev);
|
uint8 busNumber = libusb_get_bus_number(dev);
|
||||||
|
@ -269,12 +267,12 @@ namespace nsyshid::backend::libusb
|
||||||
if (desc.idVendor == 0x0e6f && desc.idProduct == 0x0241)
|
if (desc.idVendor == 0x0e6f && desc.idProduct == 0x0241)
|
||||||
{
|
{
|
||||||
cemuLog_logDebug(LogType::Force,
|
cemuLog_logDebug(LogType::Force,
|
||||||
"nsyshid::BackendLibusb::CheckAndCreateDevice(): lego dimensions portal detected");
|
"nsyshid::BackendLibusb::CheckAndCreateDevice(): lego dimensions portal detected");
|
||||||
}
|
}
|
||||||
auto device = std::make_shared<DeviceLibusb>(m_ctx,
|
auto device = std::make_shared<DeviceLibusb>(m_ctx,
|
||||||
desc.idVendor,
|
desc.idVendor,
|
||||||
desc.idProduct,
|
desc.idProduct,
|
||||||
1,
|
0,
|
||||||
2,
|
2,
|
||||||
0,
|
0,
|
||||||
libusb_get_bus_number(dev),
|
libusb_get_bus_number(dev),
|
||||||
|
@ -446,12 +444,13 @@ namespace nsyshid::backend::libusb
|
||||||
}
|
}
|
||||||
this->m_handleInUseCounter = 0;
|
this->m_handleInUseCounter = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int ret = ClaimAllInterfaces(0);
|
||||||
|
|
||||||
|
if (ret != 0)
|
||||||
{
|
{
|
||||||
int ret = ClaimAllInterfaces(0);
|
cemuLog_log(LogType::Force, "nsyshid::DeviceLibusb::open(): cannot claim interface for config 0");
|
||||||
if (ret != 0)
|
return false;
|
||||||
{
|
|
||||||
cemuLog_logDebug(LogType::Force, "nsyshid::DeviceLibusb::open(): cannot claim interface");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -475,7 +474,7 @@ namespace nsyshid::backend::libusb
|
||||||
{
|
{
|
||||||
m_handleInUseCounterDecremented.wait(lock);
|
m_handleInUseCounterDecremented.wait(lock);
|
||||||
}
|
}
|
||||||
libusb_release_interface(handle, 0);
|
ReleaseAllInterfacesForCurrentConfig();
|
||||||
libusb_close(handle);
|
libusb_close(handle);
|
||||||
m_handleInUseCounter = -1;
|
m_handleInUseCounter = -1;
|
||||||
m_handleInUseCounterDecremented.notify_all();
|
m_handleInUseCounterDecremented.notify_all();
|
||||||
|
@ -493,21 +492,26 @@ namespace nsyshid::backend::libusb
|
||||||
if (!handleLock->IsValid())
|
if (!handleLock->IsValid())
|
||||||
{
|
{
|
||||||
cemuLog_logDebug(LogType::Force,
|
cemuLog_logDebug(LogType::Force,
|
||||||
"nsyshid::DeviceLibusb::read(): cannot read from a non-opened device\n");
|
"nsyshid::DeviceLibusb::read(): cannot read from a non-opened device\n");
|
||||||
return ReadResult::Error;
|
return ReadResult::Error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < m_config_descriptors.size(); i++)
|
||||||
|
{
|
||||||
|
ClaimAllInterfaces(i);
|
||||||
|
}
|
||||||
|
|
||||||
const unsigned int timeout = 50;
|
const unsigned int timeout = 50;
|
||||||
int actualLength = 0;
|
int actualLength = 0;
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
ret = libusb_bulk_transfer(handleLock->GetHandle(),
|
ret = libusb_interrupt_transfer(handleLock->GetHandle(),
|
||||||
this->m_libusbEndpointIn,
|
this->m_libusbEndpointIn,
|
||||||
message->data,
|
message->data,
|
||||||
message->length,
|
message->length,
|
||||||
&actualLength,
|
&actualLength,
|
||||||
timeout);
|
timeout);
|
||||||
}
|
}
|
||||||
while (ret == LIBUSB_ERROR_TIMEOUT && actualLength == 0 && IsOpened());
|
while (ret == LIBUSB_ERROR_TIMEOUT && actualLength == 0 && IsOpened());
|
||||||
|
|
||||||
|
@ -521,8 +525,8 @@ namespace nsyshid::backend::libusb
|
||||||
return ReadResult::Success;
|
return ReadResult::Success;
|
||||||
}
|
}
|
||||||
cemuLog_logDebug(LogType::Force,
|
cemuLog_logDebug(LogType::Force,
|
||||||
"nsyshid::DeviceLibusb::read(): failed with error code: {}",
|
"nsyshid::DeviceLibusb::read(): failed at endpoint 0x{:02x} with error message: {}", this->m_libusbEndpointIn,
|
||||||
ret);
|
libusb_error_name(ret));
|
||||||
return ReadResult::Error;
|
return ReadResult::Error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -532,18 +536,23 @@ namespace nsyshid::backend::libusb
|
||||||
if (!handleLock->IsValid())
|
if (!handleLock->IsValid())
|
||||||
{
|
{
|
||||||
cemuLog_logDebug(LogType::Force,
|
cemuLog_logDebug(LogType::Force,
|
||||||
"nsyshid::DeviceLibusb::write(): cannot write to a non-opened device\n");
|
"nsyshid::DeviceLibusb::write(): cannot write to a non-opened device\n");
|
||||||
return WriteResult::Error;
|
return WriteResult::Error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < m_config_descriptors.size(); i++)
|
||||||
|
{
|
||||||
|
ClaimAllInterfaces(i);
|
||||||
|
}
|
||||||
|
|
||||||
message->bytesWritten = 0;
|
message->bytesWritten = 0;
|
||||||
int actualLength = 0;
|
int actualLength = 0;
|
||||||
int ret = libusb_bulk_transfer(handleLock->GetHandle(),
|
int ret = libusb_interrupt_transfer(handleLock->GetHandle(),
|
||||||
this->m_libusbEndpointOut,
|
this->m_libusbEndpointOut,
|
||||||
message->data,
|
message->data,
|
||||||
message->length,
|
message->length,
|
||||||
&actualLength,
|
&actualLength,
|
||||||
0);
|
0);
|
||||||
|
|
||||||
if (ret == 0)
|
if (ret == 0)
|
||||||
{
|
{
|
||||||
|
@ -556,14 +565,14 @@ namespace nsyshid::backend::libusb
|
||||||
return WriteResult::Success;
|
return WriteResult::Success;
|
||||||
}
|
}
|
||||||
cemuLog_logDebug(LogType::Force,
|
cemuLog_logDebug(LogType::Force,
|
||||||
"nsyshid::DeviceLibusb::write(): failed with error code: {}",
|
"nsyshid::DeviceLibusb::write(): failed with error code: {}",
|
||||||
ret);
|
ret);
|
||||||
return WriteResult::Error;
|
return WriteResult::Error;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DeviceLibusb::GetDescriptor(uint8 descType,
|
bool DeviceLibusb::GetDescriptor(uint8 descType,
|
||||||
uint8 descIndex,
|
uint8 descIndex,
|
||||||
uint8 lang,
|
uint16 lang,
|
||||||
uint8* output,
|
uint8* output,
|
||||||
uint32 outputMaxLength)
|
uint32 outputMaxLength)
|
||||||
{
|
{
|
||||||
|
@ -579,7 +588,6 @@ namespace nsyshid::backend::libusb
|
||||||
struct libusb_config_descriptor* conf = nullptr;
|
struct libusb_config_descriptor* conf = nullptr;
|
||||||
libusb_device* dev = libusb_get_device(handleLock->GetHandle());
|
libusb_device* dev = libusb_get_device(handleLock->GetHandle());
|
||||||
int ret = libusb_get_active_config_descriptor(dev, &conf);
|
int ret = libusb_get_active_config_descriptor(dev, &conf);
|
||||||
|
|
||||||
if (ret == 0)
|
if (ret == 0)
|
||||||
{
|
{
|
||||||
std::vector<uint8> configurationDescriptor(conf->wTotalLength);
|
std::vector<uint8> configurationDescriptor(conf->wTotalLength);
|
||||||
|
@ -656,7 +664,6 @@ namespace nsyshid::backend::libusb
|
||||||
extraReadPointer += bLength;
|
extraReadPointer += bLength;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int endpointIndex = 0; endpointIndex < altsetting.bNumEndpoints; endpointIndex++)
|
for (int endpointIndex = 0; endpointIndex < altsetting.bNumEndpoints; endpointIndex++)
|
||||||
{
|
{
|
||||||
// endpoint descriptor
|
// endpoint descriptor
|
||||||
|
@ -681,24 +688,61 @@ namespace nsyshid::backend::libusb
|
||||||
uint32 bytesWritten = currentWritePtr - &configurationDescriptor[0];
|
uint32 bytesWritten = currentWritePtr - &configurationDescriptor[0];
|
||||||
libusb_free_config_descriptor(conf);
|
libusb_free_config_descriptor(conf);
|
||||||
cemu_assert_debug(bytesWritten <= conf->wTotalLength);
|
cemu_assert_debug(bytesWritten <= conf->wTotalLength);
|
||||||
|
|
||||||
memcpy(output, &configurationDescriptor[0],
|
memcpy(output, &configurationDescriptor[0],
|
||||||
std::min<uint32>(outputMaxLength, bytesWritten));
|
std::min<uint32>(outputMaxLength, bytesWritten));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
cemuLog_logDebug(LogType::Force,
|
|
||||||
"nsyshid::DeviceLibusb::getDescriptor(): failed to get config descriptor with error code: {}",
|
|
||||||
ret);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
cemu_assert_unimplemented();
|
uint16 wValue = uint16(descType) << 8 | uint16(descIndex);
|
||||||
|
// HID Get_Descriptor requests are handled via libusb_control_transfer
|
||||||
|
int ret = libusb_control_transfer(handleLock->GetHandle(),
|
||||||
|
LIBUSB_REQUEST_TYPE_STANDARD | LIBUSB_ENDPOINT_IN,
|
||||||
|
LIBUSB_REQUEST_GET_DESCRIPTOR,
|
||||||
|
wValue,
|
||||||
|
lang,
|
||||||
|
output,
|
||||||
|
outputMaxLength,
|
||||||
|
0);
|
||||||
|
if (ret != outputMaxLength)
|
||||||
|
{
|
||||||
|
cemuLog_logDebug(LogType::Force, "nsyshid::DeviceLibusb::GetDescriptor(): Control Transfer Failed: {}", libusb_error_name(ret));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DeviceLibusb::SetIdle(uint8 ifIndex,
|
||||||
|
uint8 reportId,
|
||||||
|
uint8 duration)
|
||||||
|
{
|
||||||
|
auto handleLock = AquireHandleLock();
|
||||||
|
if (!handleLock->IsValid())
|
||||||
|
{
|
||||||
|
cemuLog_log(LogType::Force, "nsyshid::DeviceLibusb::SetIdle(): device is not opened");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16 wValue = uint16(duration) << 8 | uint16(reportId);
|
||||||
|
|
||||||
|
// HID Set_Idle requests are handled via libusb_control_transfer
|
||||||
|
int ret = libusb_control_transfer(handleLock->GetHandle(),
|
||||||
|
LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE | LIBUSB_ENDPOINT_OUT,
|
||||||
|
HID_CLASS_SET_IDLE, // Defined in HID Class Specific Requests (7.2)
|
||||||
|
wValue,
|
||||||
|
ifIndex,
|
||||||
|
nullptr,
|
||||||
|
0,
|
||||||
|
0);
|
||||||
|
|
||||||
|
if (ret != 0)
|
||||||
|
{
|
||||||
|
cemuLog_logDebug(LogType::Force, "nsyshid::DeviceLibusb::SetIdle(): Control Transfer Failed: {}", libusb_error_name(ret));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename Configs, typename Function>
|
template<typename Configs, typename Function>
|
||||||
|
@ -767,18 +811,22 @@ namespace nsyshid::backend::libusb
|
||||||
cemuLog_logDebug(LogType::Force, "nsyshid::DeviceLibusb::SetProtocol(): device is not opened");
|
cemuLog_logDebug(LogType::Force, "nsyshid::DeviceLibusb::SetProtocol(): device is not opened");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (m_interfaceIndex != ifIndex)
|
|
||||||
m_interfaceIndex = ifIndex;
|
|
||||||
|
|
||||||
ReleaseAllInterfacesForCurrentConfig();
|
int ret = libusb_control_transfer(handleLock->GetHandle(),
|
||||||
int ret = libusb_set_configuration(AquireHandleLock()->GetHandle(), protocol);
|
LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE | LIBUSB_ENDPOINT_OUT,
|
||||||
if (ret == LIBUSB_SUCCESS)
|
HID_CLASS_SET_PROTOCOL, // Defined in HID Class Specific Requests (7.2)
|
||||||
ret = ClaimAllInterfaces(protocol);
|
protocol,
|
||||||
|
ifIndex,
|
||||||
|
nullptr,
|
||||||
|
0,
|
||||||
|
0);
|
||||||
|
|
||||||
if (ret == LIBUSB_SUCCESS)
|
if (ret != 0)
|
||||||
return true;
|
{
|
||||||
|
cemuLog_logDebug(LogType::Force, "nsyshid::DeviceLibusb::SetProtocol(): Control Transfer Failed: {}", libusb_error_name(ret));
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DeviceLibusb::SetReport(ReportMessage* message)
|
bool DeviceLibusb::SetReport(ReportMessage* message)
|
||||||
|
@ -790,18 +838,20 @@ namespace nsyshid::backend::libusb
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint16 wValue = uint16(message->reportType) << 8 | uint16(message->reportId);
|
||||||
|
|
||||||
int ret = libusb_control_transfer(handleLock->GetHandle(),
|
int ret = libusb_control_transfer(handleLock->GetHandle(),
|
||||||
LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE | LIBUSB_ENDPOINT_OUT,
|
LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE | LIBUSB_ENDPOINT_OUT,
|
||||||
LIBUSB_REQUEST_SET_CONFIGURATION,
|
HID_CLASS_SET_REPORT, // Defined in HID Class Specific Requests (7.2)
|
||||||
512,
|
wValue,
|
||||||
0,
|
m_interfaceIndex,
|
||||||
message->originalData,
|
message->data,
|
||||||
message->originalLength,
|
uint16(message->length & 0xFFFF),
|
||||||
0);
|
0);
|
||||||
|
|
||||||
if (ret != message->originalLength)
|
if (ret != message->length)
|
||||||
{
|
{
|
||||||
cemuLog_logDebug(LogType::Force, "nsyshid::DeviceLibusb::SetReport(): Control Transfer Failed: {}", libusb_error_name(ret));
|
cemuLog_logDebug(LogType::Force, "nsyshid::DeviceLibusb::SetReport(): Control Transfer Failed at interface {} : {}", m_interfaceIndex, libusb_error_name(ret));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
@ -854,5 +904,3 @@ namespace nsyshid::backend::libusb
|
||||||
return m_handle;
|
return m_handle;
|
||||||
}
|
}
|
||||||
} // namespace nsyshid::backend::libusb
|
} // namespace nsyshid::backend::libusb
|
||||||
|
|
||||||
#endif // NSYSHID_ENABLE_BACKEND_LIBUSB
|
|
||||||
|
|
|
@ -1,15 +1,20 @@
|
||||||
#ifndef CEMU_NSYSHID_BACKEND_LIBUSB_H
|
|
||||||
#define CEMU_NSYSHID_BACKEND_LIBUSB_H
|
|
||||||
|
|
||||||
#include "nsyshid.h"
|
#include "nsyshid.h"
|
||||||
|
|
||||||
#if NSYSHID_ENABLE_BACKEND_LIBUSB
|
|
||||||
|
|
||||||
#include <libusb-1.0/libusb.h>
|
#include <libusb-1.0/libusb.h>
|
||||||
#include "Backend.h"
|
#include "Backend.h"
|
||||||
|
|
||||||
namespace nsyshid::backend::libusb
|
namespace nsyshid::backend::libusb
|
||||||
{
|
{
|
||||||
|
enum : uint8
|
||||||
|
{
|
||||||
|
HID_CLASS_GET_REPORT = 0x01,
|
||||||
|
HID_CLASS_GET_IDLE = 0x02,
|
||||||
|
HID_CLASS_GET_PROTOCOL = 0x03,
|
||||||
|
HID_CLASS_SET_REPORT = 0x09,
|
||||||
|
HID_CLASS_SET_IDLE = 0x0A,
|
||||||
|
HID_CLASS_SET_PROTOCOL = 0x0B
|
||||||
|
};
|
||||||
|
|
||||||
class BackendLibusb : public nsyshid::Backend {
|
class BackendLibusb : public nsyshid::Backend {
|
||||||
public:
|
public:
|
||||||
BackendLibusb();
|
BackendLibusb();
|
||||||
|
@ -75,10 +80,14 @@ namespace nsyshid::backend::libusb
|
||||||
|
|
||||||
bool GetDescriptor(uint8 descType,
|
bool GetDescriptor(uint8 descType,
|
||||||
uint8 descIndex,
|
uint8 descIndex,
|
||||||
uint8 lang,
|
uint16 lang,
|
||||||
uint8* output,
|
uint8* output,
|
||||||
uint32 outputMaxLength) override;
|
uint32 outputMaxLength) override;
|
||||||
|
|
||||||
|
bool SetIdle(uint8 ifIndex,
|
||||||
|
uint8 reportId,
|
||||||
|
uint8 duration) override;
|
||||||
|
|
||||||
bool SetProtocol(uint8 ifIndex, uint8 protocol) override;
|
bool SetProtocol(uint8 ifIndex, uint8 protocol) override;
|
||||||
|
|
||||||
int ClaimAllInterfaces(uint8 config_num);
|
int ClaimAllInterfaces(uint8 config_num);
|
||||||
|
@ -134,7 +143,3 @@ namespace nsyshid::backend::libusb
|
||||||
std::unique_ptr<HandleLock> AquireHandleLock();
|
std::unique_ptr<HandleLock> AquireHandleLock();
|
||||||
};
|
};
|
||||||
} // namespace nsyshid::backend::libusb
|
} // namespace nsyshid::backend::libusb
|
||||||
|
|
||||||
#endif // NSYSHID_ENABLE_BACKEND_LIBUSB
|
|
||||||
|
|
||||||
#endif // CEMU_NSYSHID_BACKEND_LIBUSB_H
|
|
||||||
|
|
|
@ -1,444 +0,0 @@
|
||||||
#include "BackendWindowsHID.h"
|
|
||||||
|
|
||||||
#if NSYSHID_ENABLE_BACKEND_WINDOWS_HID
|
|
||||||
|
|
||||||
#include <setupapi.h>
|
|
||||||
#include <initguid.h>
|
|
||||||
#include <hidsdi.h>
|
|
||||||
|
|
||||||
#pragma comment(lib, "Setupapi.lib")
|
|
||||||
#pragma comment(lib, "hid.lib")
|
|
||||||
|
|
||||||
DEFINE_GUID(GUID_DEVINTERFACE_HID,
|
|
||||||
0x4D1E55B2L, 0xF16F, 0x11CF, 0x88, 0xCB, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30);
|
|
||||||
|
|
||||||
namespace nsyshid::backend::windows
|
|
||||||
{
|
|
||||||
BackendWindowsHID::BackendWindowsHID()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void BackendWindowsHID::AttachVisibleDevices()
|
|
||||||
{
|
|
||||||
// add all currently connected devices
|
|
||||||
HDEVINFO hDevInfo;
|
|
||||||
SP_DEVICE_INTERFACE_DATA DevIntfData;
|
|
||||||
PSP_DEVICE_INTERFACE_DETAIL_DATA DevIntfDetailData;
|
|
||||||
SP_DEVINFO_DATA DevData;
|
|
||||||
|
|
||||||
DWORD dwSize, dwMemberIdx;
|
|
||||||
|
|
||||||
hDevInfo = SetupDiGetClassDevs(&GUID_DEVINTERFACE_HID, NULL, 0, DIGCF_DEVICEINTERFACE | DIGCF_PRESENT);
|
|
||||||
|
|
||||||
if (hDevInfo != INVALID_HANDLE_VALUE)
|
|
||||||
{
|
|
||||||
DevIntfData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
|
|
||||||
dwMemberIdx = 0;
|
|
||||||
|
|
||||||
SetupDiEnumDeviceInterfaces(hDevInfo, NULL, &GUID_DEVINTERFACE_HID,
|
|
||||||
dwMemberIdx, &DevIntfData);
|
|
||||||
|
|
||||||
while (GetLastError() != ERROR_NO_MORE_ITEMS)
|
|
||||||
{
|
|
||||||
DevData.cbSize = sizeof(DevData);
|
|
||||||
SetupDiGetDeviceInterfaceDetail(
|
|
||||||
hDevInfo, &DevIntfData, NULL, 0, &dwSize, NULL);
|
|
||||||
|
|
||||||
DevIntfDetailData = (PSP_DEVICE_INTERFACE_DETAIL_DATA)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY,
|
|
||||||
dwSize);
|
|
||||||
DevIntfDetailData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
|
|
||||||
|
|
||||||
if (SetupDiGetDeviceInterfaceDetail(hDevInfo, &DevIntfData,
|
|
||||||
DevIntfDetailData, dwSize, &dwSize, &DevData))
|
|
||||||
{
|
|
||||||
HANDLE hHIDDevice = OpenDevice(DevIntfDetailData->DevicePath);
|
|
||||||
if (hHIDDevice != INVALID_HANDLE_VALUE)
|
|
||||||
{
|
|
||||||
auto device = CheckAndCreateDevice(DevIntfDetailData->DevicePath, hHIDDevice);
|
|
||||||
if (device != nullptr)
|
|
||||||
{
|
|
||||||
if (IsDeviceWhitelisted(device->m_vendorId, device->m_productId))
|
|
||||||
{
|
|
||||||
if (!AttachDevice(device))
|
|
||||||
{
|
|
||||||
cemuLog_log(LogType::Force,
|
|
||||||
"nsyshid::BackendWindowsHID: failed to attach device: {:04x}:{:04x}",
|
|
||||||
device->m_vendorId,
|
|
||||||
device->m_productId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
CloseHandle(hHIDDevice);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
HeapFree(GetProcessHeap(), 0, DevIntfDetailData);
|
|
||||||
// next
|
|
||||||
SetupDiEnumDeviceInterfaces(hDevInfo, NULL, &GUID_DEVINTERFACE_HID, ++dwMemberIdx, &DevIntfData);
|
|
||||||
}
|
|
||||||
SetupDiDestroyDeviceInfoList(hDevInfo);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
BackendWindowsHID::~BackendWindowsHID()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
bool BackendWindowsHID::IsInitialisedOk()
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::shared_ptr<Device> BackendWindowsHID::CheckAndCreateDevice(wchar_t* devicePath, HANDLE hDevice)
|
|
||||||
{
|
|
||||||
HIDD_ATTRIBUTES hidAttr;
|
|
||||||
hidAttr.Size = sizeof(HIDD_ATTRIBUTES);
|
|
||||||
if (HidD_GetAttributes(hDevice, &hidAttr) == FALSE)
|
|
||||||
return nullptr;
|
|
||||||
|
|
||||||
auto device = std::make_shared<DeviceWindowsHID>(hidAttr.VendorID,
|
|
||||||
hidAttr.ProductID,
|
|
||||||
1,
|
|
||||||
2,
|
|
||||||
0,
|
|
||||||
_wcsdup(devicePath));
|
|
||||||
// get additional device info
|
|
||||||
sint32 maxPacketInputLength = -1;
|
|
||||||
sint32 maxPacketOutputLength = -1;
|
|
||||||
PHIDP_PREPARSED_DATA ppData = nullptr;
|
|
||||||
if (HidD_GetPreparsedData(hDevice, &ppData))
|
|
||||||
{
|
|
||||||
HIDP_CAPS caps;
|
|
||||||
if (HidP_GetCaps(ppData, &caps) == HIDP_STATUS_SUCCESS)
|
|
||||||
{
|
|
||||||
// length includes the report id byte
|
|
||||||
maxPacketInputLength = caps.InputReportByteLength - 1;
|
|
||||||
maxPacketOutputLength = caps.OutputReportByteLength - 1;
|
|
||||||
}
|
|
||||||
HidD_FreePreparsedData(ppData);
|
|
||||||
}
|
|
||||||
if (maxPacketInputLength <= 0 || maxPacketInputLength >= 0xF000)
|
|
||||||
{
|
|
||||||
cemuLog_logDebug(LogType::Force, "HID: Input packet length not available or out of range (length = {})", maxPacketInputLength);
|
|
||||||
maxPacketInputLength = 0x20;
|
|
||||||
}
|
|
||||||
if (maxPacketOutputLength <= 0 || maxPacketOutputLength >= 0xF000)
|
|
||||||
{
|
|
||||||
cemuLog_logDebug(LogType::Force, "HID: Output packet length not available or out of range (length = {})", maxPacketOutputLength);
|
|
||||||
maxPacketOutputLength = 0x20;
|
|
||||||
}
|
|
||||||
|
|
||||||
device->m_maxPacketSizeRX = maxPacketInputLength;
|
|
||||||
device->m_maxPacketSizeTX = maxPacketOutputLength;
|
|
||||||
|
|
||||||
return device;
|
|
||||||
}
|
|
||||||
|
|
||||||
DeviceWindowsHID::DeviceWindowsHID(uint16 vendorId,
|
|
||||||
uint16 productId,
|
|
||||||
uint8 interfaceIndex,
|
|
||||||
uint8 interfaceSubClass,
|
|
||||||
uint8 protocol,
|
|
||||||
wchar_t* devicePath)
|
|
||||||
: Device(vendorId,
|
|
||||||
productId,
|
|
||||||
interfaceIndex,
|
|
||||||
interfaceSubClass,
|
|
||||||
protocol),
|
|
||||||
m_devicePath(devicePath),
|
|
||||||
m_hFile(INVALID_HANDLE_VALUE)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
DeviceWindowsHID::~DeviceWindowsHID()
|
|
||||||
{
|
|
||||||
if (m_hFile != INVALID_HANDLE_VALUE)
|
|
||||||
{
|
|
||||||
CloseHandle(m_hFile);
|
|
||||||
m_hFile = INVALID_HANDLE_VALUE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DeviceWindowsHID::Open()
|
|
||||||
{
|
|
||||||
if (IsOpened())
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
m_hFile = OpenDevice(m_devicePath);
|
|
||||||
if (m_hFile == INVALID_HANDLE_VALUE)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
HidD_SetNumInputBuffers(m_hFile, 2); // don't cache too many reports
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DeviceWindowsHID::Close()
|
|
||||||
{
|
|
||||||
if (m_hFile != INVALID_HANDLE_VALUE)
|
|
||||||
{
|
|
||||||
CloseHandle(m_hFile);
|
|
||||||
m_hFile = INVALID_HANDLE_VALUE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DeviceWindowsHID::IsOpened()
|
|
||||||
{
|
|
||||||
return m_hFile != INVALID_HANDLE_VALUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
Device::ReadResult DeviceWindowsHID::Read(ReadMessage* message)
|
|
||||||
{
|
|
||||||
message->bytesRead = 0;
|
|
||||||
DWORD bt;
|
|
||||||
OVERLAPPED ovlp = {0};
|
|
||||||
ovlp.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
|
|
||||||
|
|
||||||
uint8* tempBuffer = (uint8*)malloc(message->length + 1);
|
|
||||||
sint32 transferLength = 0; // minus report byte
|
|
||||||
|
|
||||||
_debugPrintHex("HID_READ_BEFORE", message->data, message->length);
|
|
||||||
|
|
||||||
cemuLog_logDebug(LogType::Force, "HidRead Begin (Length 0x{:08x})", message->length);
|
|
||||||
BOOL readResult = ReadFile(this->m_hFile, tempBuffer, message->length + 1, &bt, &ovlp);
|
|
||||||
if (readResult != FALSE)
|
|
||||||
{
|
|
||||||
// sometimes we get the result immediately
|
|
||||||
if (bt == 0)
|
|
||||||
transferLength = 0;
|
|
||||||
else
|
|
||||||
transferLength = bt - 1;
|
|
||||||
cemuLog_logDebug(LogType::Force, "HidRead Result received immediately (error 0x{:08x}) Length 0x{:08x}",
|
|
||||||
GetLastError(), transferLength);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// wait for result
|
|
||||||
cemuLog_logDebug(LogType::Force, "HidRead WaitForResult (error 0x{:08x})", GetLastError());
|
|
||||||
// async hid read is never supposed to return unless there is a response? Lego Dimensions stops HIDRead calls as soon as one of them fails with a non-zero error (which includes time out)
|
|
||||||
DWORD r = WaitForSingleObject(ovlp.hEvent, 2000 * 100);
|
|
||||||
if (r == WAIT_TIMEOUT)
|
|
||||||
{
|
|
||||||
cemuLog_logDebug(LogType::Force, "HidRead internal timeout (error 0x{:08x})", GetLastError());
|
|
||||||
// return -108 in case of timeout
|
|
||||||
free(tempBuffer);
|
|
||||||
CloseHandle(ovlp.hEvent);
|
|
||||||
return ReadResult::ErrorTimeout;
|
|
||||||
}
|
|
||||||
|
|
||||||
cemuLog_logDebug(LogType::Force, "HidRead WaitHalfComplete");
|
|
||||||
GetOverlappedResult(this->m_hFile, &ovlp, &bt, false);
|
|
||||||
if (bt == 0)
|
|
||||||
transferLength = 0;
|
|
||||||
else
|
|
||||||
transferLength = bt - 1;
|
|
||||||
cemuLog_logDebug(LogType::Force, "HidRead WaitComplete Length: 0x{:08x}", transferLength);
|
|
||||||
}
|
|
||||||
sint32 returnCode = 0;
|
|
||||||
ReadResult result = ReadResult::Success;
|
|
||||||
if (bt != 0)
|
|
||||||
{
|
|
||||||
memcpy(message->data, tempBuffer + 1, transferLength);
|
|
||||||
sint32 hidReadLength = transferLength;
|
|
||||||
|
|
||||||
char debugOutput[1024] = {0};
|
|
||||||
for (sint32 i = 0; i < transferLength; i++)
|
|
||||||
{
|
|
||||||
sprintf(debugOutput + i * 3, "%02x ", tempBuffer[1 + i]);
|
|
||||||
}
|
|
||||||
cemuLog_logDebug(LogType::Force, "HIDRead data: {}", debugOutput);
|
|
||||||
|
|
||||||
message->bytesRead = transferLength;
|
|
||||||
result = ReadResult::Success;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
cemuLog_log(LogType::Force, "Failed HID read");
|
|
||||||
result = ReadResult::Error;
|
|
||||||
}
|
|
||||||
free(tempBuffer);
|
|
||||||
CloseHandle(ovlp.hEvent);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
Device::WriteResult DeviceWindowsHID::Write(WriteMessage* message)
|
|
||||||
{
|
|
||||||
message->bytesWritten = 0;
|
|
||||||
DWORD bt;
|
|
||||||
OVERLAPPED ovlp = {0};
|
|
||||||
ovlp.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
|
|
||||||
|
|
||||||
uint8* tempBuffer = (uint8*)malloc(message->length + 1);
|
|
||||||
memcpy(tempBuffer + 1, message->data, message->length);
|
|
||||||
tempBuffer[0] = 0; // report byte?
|
|
||||||
|
|
||||||
cemuLog_logDebug(LogType::Force, "HidWrite Begin (Length 0x{:08x})", message->length);
|
|
||||||
BOOL writeResult = WriteFile(this->m_hFile, tempBuffer, message->length + 1, &bt, &ovlp);
|
|
||||||
if (writeResult != FALSE)
|
|
||||||
{
|
|
||||||
// sometimes we get the result immediately
|
|
||||||
cemuLog_logDebug(LogType::Force, "HidWrite Result received immediately (error 0x{:08x}) Length 0x{:08x}",
|
|
||||||
GetLastError());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// wait for result
|
|
||||||
cemuLog_logDebug(LogType::Force, "HidWrite WaitForResult (error 0x{:08x})", GetLastError());
|
|
||||||
// todo - check for error type
|
|
||||||
DWORD r = WaitForSingleObject(ovlp.hEvent, 2000);
|
|
||||||
if (r == WAIT_TIMEOUT)
|
|
||||||
{
|
|
||||||
cemuLog_logDebug(LogType::Force, "HidWrite internal timeout");
|
|
||||||
// return -108 in case of timeout
|
|
||||||
free(tempBuffer);
|
|
||||||
CloseHandle(ovlp.hEvent);
|
|
||||||
return WriteResult::ErrorTimeout;
|
|
||||||
}
|
|
||||||
|
|
||||||
cemuLog_logDebug(LogType::Force, "HidWrite WaitHalfComplete");
|
|
||||||
GetOverlappedResult(this->m_hFile, &ovlp, &bt, false);
|
|
||||||
cemuLog_logDebug(LogType::Force, "HidWrite WaitComplete");
|
|
||||||
}
|
|
||||||
|
|
||||||
free(tempBuffer);
|
|
||||||
CloseHandle(ovlp.hEvent);
|
|
||||||
|
|
||||||
if (bt != 0)
|
|
||||||
{
|
|
||||||
message->bytesWritten = message->length;
|
|
||||||
return WriteResult::Success;
|
|
||||||
}
|
|
||||||
return WriteResult::Error;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DeviceWindowsHID::GetDescriptor(uint8 descType,
|
|
||||||
uint8 descIndex,
|
|
||||||
uint8 lang,
|
|
||||||
uint8* output,
|
|
||||||
uint32 outputMaxLength)
|
|
||||||
{
|
|
||||||
if (!IsOpened())
|
|
||||||
{
|
|
||||||
cemuLog_logDebug(LogType::Force, "nsyshid::DeviceWindowsHID::getDescriptor(): device is not opened");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (descType == 0x02)
|
|
||||||
{
|
|
||||||
uint8 configurationDescriptor[0x29];
|
|
||||||
|
|
||||||
uint8* currentWritePtr;
|
|
||||||
|
|
||||||
// configuration descriptor
|
|
||||||
currentWritePtr = configurationDescriptor + 0;
|
|
||||||
*(uint8*)(currentWritePtr + 0) = 9; // bLength
|
|
||||||
*(uint8*)(currentWritePtr + 1) = 2; // bDescriptorType
|
|
||||||
*(uint16be*)(currentWritePtr + 2) = 0x0029; // wTotalLength
|
|
||||||
*(uint8*)(currentWritePtr + 4) = 1; // bNumInterfaces
|
|
||||||
*(uint8*)(currentWritePtr + 5) = 1; // bConfigurationValue
|
|
||||||
*(uint8*)(currentWritePtr + 6) = 0; // iConfiguration
|
|
||||||
*(uint8*)(currentWritePtr + 7) = 0x80; // bmAttributes
|
|
||||||
*(uint8*)(currentWritePtr + 8) = 0xFA; // MaxPower
|
|
||||||
currentWritePtr = currentWritePtr + 9;
|
|
||||||
// configuration descriptor
|
|
||||||
*(uint8*)(currentWritePtr + 0) = 9; // bLength
|
|
||||||
*(uint8*)(currentWritePtr + 1) = 0x04; // bDescriptorType
|
|
||||||
*(uint8*)(currentWritePtr + 2) = 0; // bInterfaceNumber
|
|
||||||
*(uint8*)(currentWritePtr + 3) = 0; // bAlternateSetting
|
|
||||||
*(uint8*)(currentWritePtr + 4) = 2; // bNumEndpoints
|
|
||||||
*(uint8*)(currentWritePtr + 5) = 3; // bInterfaceClass
|
|
||||||
*(uint8*)(currentWritePtr + 6) = 0; // bInterfaceSubClass
|
|
||||||
*(uint8*)(currentWritePtr + 7) = 0; // bInterfaceProtocol
|
|
||||||
*(uint8*)(currentWritePtr + 8) = 0; // iInterface
|
|
||||||
currentWritePtr = currentWritePtr + 9;
|
|
||||||
// configuration descriptor
|
|
||||||
*(uint8*)(currentWritePtr + 0) = 9; // bLength
|
|
||||||
*(uint8*)(currentWritePtr + 1) = 0x21; // bDescriptorType
|
|
||||||
*(uint16be*)(currentWritePtr + 2) = 0x0111; // bcdHID
|
|
||||||
*(uint8*)(currentWritePtr + 4) = 0x00; // bCountryCode
|
|
||||||
*(uint8*)(currentWritePtr + 5) = 0x01; // bNumDescriptors
|
|
||||||
*(uint8*)(currentWritePtr + 6) = 0x22; // bDescriptorType
|
|
||||||
*(uint16be*)(currentWritePtr + 7) = 0x001D; // wDescriptorLength
|
|
||||||
currentWritePtr = currentWritePtr + 9;
|
|
||||||
// endpoint descriptor 1
|
|
||||||
*(uint8*)(currentWritePtr + 0) = 7; // bLength
|
|
||||||
*(uint8*)(currentWritePtr + 1) = 0x05; // bDescriptorType
|
|
||||||
*(uint8*)(currentWritePtr + 2) = 0x81; // bEndpointAddress
|
|
||||||
*(uint8*)(currentWritePtr + 3) = 0x03; // bmAttributes
|
|
||||||
*(uint16be*)(currentWritePtr + 4) =
|
|
||||||
this->m_maxPacketSizeRX; // wMaxPacketSize
|
|
||||||
*(uint8*)(currentWritePtr + 6) = 0x01; // bInterval
|
|
||||||
currentWritePtr = currentWritePtr + 7;
|
|
||||||
// endpoint descriptor 2
|
|
||||||
*(uint8*)(currentWritePtr + 0) = 7; // bLength
|
|
||||||
*(uint8*)(currentWritePtr + 1) = 0x05; // bDescriptorType
|
|
||||||
*(uint8*)(currentWritePtr + 2) = 0x02; // bEndpointAddress
|
|
||||||
*(uint8*)(currentWritePtr + 3) = 0x03; // bmAttributes
|
|
||||||
*(uint16be*)(currentWritePtr + 4) =
|
|
||||||
this->m_maxPacketSizeTX; // wMaxPacketSize
|
|
||||||
*(uint8*)(currentWritePtr + 6) = 0x01; // bInterval
|
|
||||||
currentWritePtr = currentWritePtr + 7;
|
|
||||||
|
|
||||||
cemu_assert_debug((currentWritePtr - configurationDescriptor) == 0x29);
|
|
||||||
|
|
||||||
memcpy(output, configurationDescriptor,
|
|
||||||
std::min<uint32>(outputMaxLength, sizeof(configurationDescriptor)));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
cemu_assert_unimplemented();
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DeviceWindowsHID::SetProtocol(uint8 ifIndex, uint8 protocol)
|
|
||||||
{
|
|
||||||
// ToDo: implement this
|
|
||||||
// pretend that everything is fine
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DeviceWindowsHID::SetReport(ReportMessage* message)
|
|
||||||
{
|
|
||||||
sint32 retryCount = 0;
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
BOOL r = HidD_SetOutputReport(this->m_hFile, message->reportData, message->length);
|
|
||||||
if (r != FALSE)
|
|
||||||
break;
|
|
||||||
Sleep(20); // retry
|
|
||||||
retryCount++;
|
|
||||||
if (retryCount >= 50)
|
|
||||||
{
|
|
||||||
cemuLog_log(LogType::Force, "nsyshid::DeviceWindowsHID::SetReport(): HID SetReport failed");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
HANDLE OpenDevice(wchar_t* devicePath)
|
|
||||||
{
|
|
||||||
return CreateFile(devicePath,
|
|
||||||
GENERIC_READ | GENERIC_WRITE,
|
|
||||||
FILE_SHARE_READ |
|
|
||||||
FILE_SHARE_WRITE,
|
|
||||||
NULL,
|
|
||||||
OPEN_EXISTING,
|
|
||||||
FILE_FLAG_OVERLAPPED,
|
|
||||||
NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _debugPrintHex(std::string prefix, uint8* data, size_t len)
|
|
||||||
{
|
|
||||||
char debugOutput[1024] = {0};
|
|
||||||
len = std::min(len, (size_t)100);
|
|
||||||
for (sint32 i = 0; i < len; i++)
|
|
||||||
{
|
|
||||||
sprintf(debugOutput + i * 3, "%02x ", data[i]);
|
|
||||||
}
|
|
||||||
cemuLog_logDebug(LogType::Force, "[{}] Data: {}", prefix, debugOutput);
|
|
||||||
}
|
|
||||||
} // namespace nsyshid::backend::windows
|
|
||||||
|
|
||||||
#endif // NSYSHID_ENABLE_BACKEND_WINDOWS_HID
|
|
|
@ -1,66 +0,0 @@
|
||||||
#ifndef CEMU_NSYSHID_BACKEND_WINDOWS_HID_H
|
|
||||||
#define CEMU_NSYSHID_BACKEND_WINDOWS_HID_H
|
|
||||||
|
|
||||||
#include "nsyshid.h"
|
|
||||||
|
|
||||||
#if NSYSHID_ENABLE_BACKEND_WINDOWS_HID
|
|
||||||
|
|
||||||
#include "Backend.h"
|
|
||||||
|
|
||||||
namespace nsyshid::backend::windows
|
|
||||||
{
|
|
||||||
class BackendWindowsHID : public nsyshid::Backend {
|
|
||||||
public:
|
|
||||||
BackendWindowsHID();
|
|
||||||
|
|
||||||
~BackendWindowsHID();
|
|
||||||
|
|
||||||
bool IsInitialisedOk() override;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void AttachVisibleDevices() override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::shared_ptr<Device> CheckAndCreateDevice(wchar_t* devicePath, HANDLE hDevice);
|
|
||||||
};
|
|
||||||
|
|
||||||
class DeviceWindowsHID : public nsyshid::Device {
|
|
||||||
public:
|
|
||||||
DeviceWindowsHID(uint16 vendorId,
|
|
||||||
uint16 productId,
|
|
||||||
uint8 interfaceIndex,
|
|
||||||
uint8 interfaceSubClass,
|
|
||||||
uint8 protocol,
|
|
||||||
wchar_t* devicePath);
|
|
||||||
|
|
||||||
~DeviceWindowsHID();
|
|
||||||
|
|
||||||
bool Open() override;
|
|
||||||
|
|
||||||
void Close() override;
|
|
||||||
|
|
||||||
bool IsOpened() override;
|
|
||||||
|
|
||||||
ReadResult Read(ReadMessage* message) override;
|
|
||||||
|
|
||||||
WriteResult Write(WriteMessage* message) override;
|
|
||||||
|
|
||||||
bool GetDescriptor(uint8 descType, uint8 descIndex, uint8 lang, uint8* output, uint32 outputMaxLength) override;
|
|
||||||
|
|
||||||
bool SetProtocol(uint8 ifIndex, uint8 protocol) override;
|
|
||||||
|
|
||||||
bool SetReport(ReportMessage* message) override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
wchar_t* m_devicePath;
|
|
||||||
HANDLE m_hFile;
|
|
||||||
};
|
|
||||||
|
|
||||||
HANDLE OpenDevice(wchar_t* devicePath);
|
|
||||||
|
|
||||||
void _debugPrintHex(std::string prefix, uint8* data, size_t len);
|
|
||||||
} // namespace nsyshid::backend::windows
|
|
||||||
|
|
||||||
#endif // NSYSHID_ENABLE_BACKEND_WINDOWS_HID
|
|
||||||
|
|
||||||
#endif // CEMU_NSYSHID_BACKEND_WINDOWS_HID_H
|
|
|
@ -426,7 +426,7 @@ namespace nsyshid
|
||||||
|
|
||||||
bool DimensionsToypadDevice::GetDescriptor(uint8 descType,
|
bool DimensionsToypadDevice::GetDescriptor(uint8 descType,
|
||||||
uint8 descIndex,
|
uint8 descIndex,
|
||||||
uint8 lang,
|
uint16 lang,
|
||||||
uint8* output,
|
uint8* output,
|
||||||
uint32 outputMaxLength)
|
uint32 outputMaxLength)
|
||||||
{
|
{
|
||||||
|
@ -489,6 +489,13 @@ namespace nsyshid
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool DimensionsToypadDevice::SetIdle(uint8 ifIndex,
|
||||||
|
uint8 reportId,
|
||||||
|
uint8 duration)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool DimensionsToypadDevice::SetProtocol(uint8 ifIndex, uint8 protocol)
|
bool DimensionsToypadDevice::SetProtocol(uint8 ifIndex, uint8 protocol)
|
||||||
{
|
{
|
||||||
cemuLog_log(LogType::Force, "Toypad Protocol");
|
cemuLog_log(LogType::Force, "Toypad Protocol");
|
||||||
|
|
|
@ -25,10 +25,14 @@ namespace nsyshid
|
||||||
|
|
||||||
bool GetDescriptor(uint8 descType,
|
bool GetDescriptor(uint8 descType,
|
||||||
uint8 descIndex,
|
uint8 descIndex,
|
||||||
uint8 lang,
|
uint16 lang,
|
||||||
uint8* output,
|
uint8* output,
|
||||||
uint32 outputMaxLength) override;
|
uint32 outputMaxLength) override;
|
||||||
|
|
||||||
|
bool SetIdle(uint8 ifIndex,
|
||||||
|
uint8 reportId,
|
||||||
|
uint8 duration) override;
|
||||||
|
|
||||||
bool SetProtocol(uint8 ifIndex, uint8 protocol) override;
|
bool SetProtocol(uint8 ifIndex, uint8 protocol) override;
|
||||||
|
|
||||||
bool SetReport(ReportMessage* message) override;
|
bool SetReport(ReportMessage* message) override;
|
||||||
|
|
|
@ -387,7 +387,7 @@ namespace nsyshid
|
||||||
|
|
||||||
bool InfinityBaseDevice::GetDescriptor(uint8 descType,
|
bool InfinityBaseDevice::GetDescriptor(uint8 descType,
|
||||||
uint8 descIndex,
|
uint8 descIndex,
|
||||||
uint8 lang,
|
uint16 lang,
|
||||||
uint8* output,
|
uint8* output,
|
||||||
uint32 outputMaxLength)
|
uint32 outputMaxLength)
|
||||||
{
|
{
|
||||||
|
@ -450,6 +450,13 @@ namespace nsyshid
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool InfinityBaseDevice::SetIdle(uint8 ifIndex,
|
||||||
|
uint8 reportId,
|
||||||
|
uint8 duration)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool InfinityBaseDevice::SetProtocol(uint8 ifIndex, uint8 protocol)
|
bool InfinityBaseDevice::SetProtocol(uint8 ifIndex, uint8 protocol)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
|
@ -492,7 +499,7 @@ namespace nsyshid
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
void InfinityUSB::SendCommand(uint8* buf, sint32 originalLength)
|
void InfinityUSB::SendCommand(uint8* buf, uint32 length)
|
||||||
{
|
{
|
||||||
const uint8 command = buf[2];
|
const uint8 command = buf[2];
|
||||||
const uint8 sequence = buf[3];
|
const uint8 sequence = buf[3];
|
||||||
|
|
|
@ -26,10 +26,14 @@ namespace nsyshid
|
||||||
|
|
||||||
bool GetDescriptor(uint8 descType,
|
bool GetDescriptor(uint8 descType,
|
||||||
uint8 descIndex,
|
uint8 descIndex,
|
||||||
uint8 lang,
|
uint16 lang,
|
||||||
uint8* output,
|
uint8* output,
|
||||||
uint32 outputMaxLength) override;
|
uint32 outputMaxLength) override;
|
||||||
|
|
||||||
|
bool SetIdle(uint8 ifIndex,
|
||||||
|
uint8 reportId,
|
||||||
|
uint8 duration) override;
|
||||||
|
|
||||||
bool SetProtocol(uint8 ifIndex, uint8 protocol) override;
|
bool SetProtocol(uint8 ifIndex, uint8 protocol) override;
|
||||||
|
|
||||||
bool SetReport(ReportMessage* message) override;
|
bool SetReport(ReportMessage* message) override;
|
||||||
|
@ -53,7 +57,7 @@ namespace nsyshid
|
||||||
void Save();
|
void Save();
|
||||||
};
|
};
|
||||||
|
|
||||||
void SendCommand(uint8* buf, sint32 originalLength);
|
void SendCommand(uint8* buf, uint32 length);
|
||||||
std::array<uint8, 32> GetStatus();
|
std::array<uint8, 32> GetStatus();
|
||||||
|
|
||||||
void GetBlankResponse(uint8 sequence, std::array<uint8, 32>& replyBuf);
|
void GetBlankResponse(uint8 sequence, std::array<uint8, 32>& replyBuf);
|
||||||
|
|
|
@ -564,7 +564,7 @@ namespace nsyshid
|
||||||
|
|
||||||
bool SkylanderPortalDevice::GetDescriptor(uint8 descType,
|
bool SkylanderPortalDevice::GetDescriptor(uint8 descType,
|
||||||
uint8 descIndex,
|
uint8 descIndex,
|
||||||
uint8 lang,
|
uint16 lang,
|
||||||
uint8* output,
|
uint8* output,
|
||||||
uint32 outputMaxLength)
|
uint32 outputMaxLength)
|
||||||
{
|
{
|
||||||
|
@ -583,7 +583,7 @@ namespace nsyshid
|
||||||
*(uint8*)(currentWritePtr + 7) = 0x80; // bmAttributes
|
*(uint8*)(currentWritePtr + 7) = 0x80; // bmAttributes
|
||||||
*(uint8*)(currentWritePtr + 8) = 0xFA; // MaxPower
|
*(uint8*)(currentWritePtr + 8) = 0xFA; // MaxPower
|
||||||
currentWritePtr = currentWritePtr + 9;
|
currentWritePtr = currentWritePtr + 9;
|
||||||
// configuration descriptor
|
// interface descriptor
|
||||||
*(uint8*)(currentWritePtr + 0) = 9; // bLength
|
*(uint8*)(currentWritePtr + 0) = 9; // bLength
|
||||||
*(uint8*)(currentWritePtr + 1) = 0x04; // bDescriptorType
|
*(uint8*)(currentWritePtr + 1) = 0x04; // bDescriptorType
|
||||||
*(uint8*)(currentWritePtr + 2) = 0; // bInterfaceNumber
|
*(uint8*)(currentWritePtr + 2) = 0; // bInterfaceNumber
|
||||||
|
@ -594,7 +594,7 @@ namespace nsyshid
|
||||||
*(uint8*)(currentWritePtr + 7) = 0; // bInterfaceProtocol
|
*(uint8*)(currentWritePtr + 7) = 0; // bInterfaceProtocol
|
||||||
*(uint8*)(currentWritePtr + 8) = 0; // iInterface
|
*(uint8*)(currentWritePtr + 8) = 0; // iInterface
|
||||||
currentWritePtr = currentWritePtr + 9;
|
currentWritePtr = currentWritePtr + 9;
|
||||||
// configuration descriptor
|
// HID descriptor
|
||||||
*(uint8*)(currentWritePtr + 0) = 9; // bLength
|
*(uint8*)(currentWritePtr + 0) = 9; // bLength
|
||||||
*(uint8*)(currentWritePtr + 1) = 0x21; // bDescriptorType
|
*(uint8*)(currentWritePtr + 1) = 0x21; // bDescriptorType
|
||||||
*(uint16be*)(currentWritePtr + 2) = 0x0111; // bcdHID
|
*(uint16be*)(currentWritePtr + 2) = 0x0111; // bcdHID
|
||||||
|
@ -608,7 +608,7 @@ namespace nsyshid
|
||||||
*(uint8*)(currentWritePtr + 1) = 0x05; // bDescriptorType
|
*(uint8*)(currentWritePtr + 1) = 0x05; // bDescriptorType
|
||||||
*(uint8*)(currentWritePtr + 2) = 0x81; // bEndpointAddress
|
*(uint8*)(currentWritePtr + 2) = 0x81; // bEndpointAddress
|
||||||
*(uint8*)(currentWritePtr + 3) = 0x03; // bmAttributes
|
*(uint8*)(currentWritePtr + 3) = 0x03; // bmAttributes
|
||||||
*(uint16be*)(currentWritePtr + 4) = 0x40; // wMaxPacketSize
|
*(uint16be*)(currentWritePtr + 4) = 0x0040; // wMaxPacketSize
|
||||||
*(uint8*)(currentWritePtr + 6) = 0x01; // bInterval
|
*(uint8*)(currentWritePtr + 6) = 0x01; // bInterval
|
||||||
currentWritePtr = currentWritePtr + 7;
|
currentWritePtr = currentWritePtr + 7;
|
||||||
// endpoint descriptor 2
|
// endpoint descriptor 2
|
||||||
|
@ -616,7 +616,7 @@ namespace nsyshid
|
||||||
*(uint8*)(currentWritePtr + 1) = 0x05; // bDescriptorType
|
*(uint8*)(currentWritePtr + 1) = 0x05; // bDescriptorType
|
||||||
*(uint8*)(currentWritePtr + 2) = 0x02; // bEndpointAddress
|
*(uint8*)(currentWritePtr + 2) = 0x02; // bEndpointAddress
|
||||||
*(uint8*)(currentWritePtr + 3) = 0x03; // bmAttributes
|
*(uint8*)(currentWritePtr + 3) = 0x03; // bmAttributes
|
||||||
*(uint16be*)(currentWritePtr + 4) = 0x40; // wMaxPacketSize
|
*(uint16be*)(currentWritePtr + 4) = 0x0040; // wMaxPacketSize
|
||||||
*(uint8*)(currentWritePtr + 6) = 0x01; // bInterval
|
*(uint8*)(currentWritePtr + 6) = 0x01; // bInterval
|
||||||
currentWritePtr = currentWritePtr + 7;
|
currentWritePtr = currentWritePtr + 7;
|
||||||
|
|
||||||
|
@ -627,6 +627,13 @@ namespace nsyshid
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool SkylanderPortalDevice::SetIdle(uint8 ifIndex,
|
||||||
|
uint8 reportId,
|
||||||
|
uint8 duration)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool SkylanderPortalDevice::SetProtocol(uint8 ifIndex, uint8 protocol)
|
bool SkylanderPortalDevice::SetProtocol(uint8 ifIndex, uint8 protocol)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
|
@ -634,12 +641,12 @@ namespace nsyshid
|
||||||
|
|
||||||
bool SkylanderPortalDevice::SetReport(ReportMessage* message)
|
bool SkylanderPortalDevice::SetReport(ReportMessage* message)
|
||||||
{
|
{
|
||||||
g_skyportal.ControlTransfer(message->originalData, message->originalLength);
|
g_skyportal.ControlTransfer(message->data, message->length);
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SkylanderUSB::ControlTransfer(uint8* buf, sint32 originalLength)
|
void SkylanderUSB::ControlTransfer(uint8* buf, uint32 length)
|
||||||
{
|
{
|
||||||
std::array<uint8, 64> interruptResponse = {};
|
std::array<uint8, 64> interruptResponse = {};
|
||||||
switch (buf[0])
|
switch (buf[0])
|
||||||
|
|
|
@ -26,10 +26,14 @@ namespace nsyshid
|
||||||
|
|
||||||
bool GetDescriptor(uint8 descType,
|
bool GetDescriptor(uint8 descType,
|
||||||
uint8 descIndex,
|
uint8 descIndex,
|
||||||
uint8 lang,
|
uint16 lang,
|
||||||
uint8* output,
|
uint8* output,
|
||||||
uint32 outputMaxLength) override;
|
uint32 outputMaxLength) override;
|
||||||
|
|
||||||
|
bool SetIdle(uint8 ifIndex,
|
||||||
|
uint8 reportId,
|
||||||
|
uint8 duration) override;
|
||||||
|
|
||||||
bool SetProtocol(uint8 ifIndex, uint8 protocol) override;
|
bool SetProtocol(uint8 ifIndex, uint8 protocol) override;
|
||||||
|
|
||||||
bool SetReport(ReportMessage* message) override;
|
bool SetReport(ReportMessage* message) override;
|
||||||
|
@ -70,7 +74,7 @@ namespace nsyshid
|
||||||
uint8 blue = 0;
|
uint8 blue = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
void ControlTransfer(uint8* buf, sint32 originalLength);
|
void ControlTransfer(uint8* buf, uint32 length);
|
||||||
|
|
||||||
void Activate();
|
void Activate();
|
||||||
void Deactivate();
|
void Deactivate();
|
||||||
|
|
|
@ -305,47 +305,37 @@ namespace nsyshid
|
||||||
osLib_returnFromFunction(hCPU, 0);
|
osLib_returnFromFunction(hCPU, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void export_HIDGetDescriptor(PPCInterpreter_t* hCPU)
|
void _debugPrintHex(const std::string prefix, const uint8* data, size_t size)
|
||||||
{
|
{
|
||||||
ppcDefineParamU32(hidHandle, 0); // r3
|
constexpr size_t BYTES_PER_LINE = 16;
|
||||||
ppcDefineParamU8(descType, 1); // r4
|
|
||||||
ppcDefineParamU8(descIndex, 2); // r5
|
|
||||||
ppcDefineParamU8(lang, 3); // r6
|
|
||||||
ppcDefineParamUStr(output, 4); // r7
|
|
||||||
ppcDefineParamU32(outputMaxLength, 5); // r8
|
|
||||||
ppcDefineParamMPTR(cbFuncMPTR, 6); // r9
|
|
||||||
ppcDefineParamMPTR(cbParamMPTR, 7); // r10
|
|
||||||
|
|
||||||
int returnValue = -1;
|
std::string out;
|
||||||
std::shared_ptr<Device> device = GetDeviceByHandle(hidHandle, true);
|
for (size_t row_start = 0; row_start < size; row_start += BYTES_PER_LINE)
|
||||||
if (device)
|
|
||||||
{
|
{
|
||||||
memset(output, 0, outputMaxLength);
|
out += fmt::format("{:06x}: ", row_start);
|
||||||
if (device->GetDescriptor(descType, descIndex, lang, output, outputMaxLength))
|
for (size_t i = 0; i < BYTES_PER_LINE; ++i)
|
||||||
{
|
{
|
||||||
returnValue = 0;
|
if (row_start + i < size)
|
||||||
|
{
|
||||||
|
out += fmt::format("{:02x} ", data[row_start + i]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
out += " ";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
out += " ";
|
||||||
|
for (size_t i = 0; i < BYTES_PER_LINE; ++i)
|
||||||
{
|
{
|
||||||
returnValue = -1;
|
if (row_start + i < size)
|
||||||
|
{
|
||||||
|
char c = static_cast<char>(data[row_start + i]);
|
||||||
|
out += std::isprint(c, std::locale::classic()) ? c : '.';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
out += "\n";
|
||||||
}
|
}
|
||||||
else
|
cemuLog_logDebug(LogType::Force, "[{}] Data: \n{}", prefix, out);
|
||||||
{
|
|
||||||
cemu_assert_suspicious();
|
|
||||||
}
|
|
||||||
osLib_returnFromFunction(hCPU, returnValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _debugPrintHex(std::string prefix, uint8* data, size_t len)
|
|
||||||
{
|
|
||||||
char debugOutput[1024] = {0};
|
|
||||||
len = std::min(len, (size_t)100);
|
|
||||||
for (sint32 i = 0; i < len; i++)
|
|
||||||
{
|
|
||||||
sprintf(debugOutput + i * 3, "%02x ", data[i]);
|
|
||||||
}
|
|
||||||
cemuLog_logDebug(LogType::Force, "[{}] Data: {}", prefix, debugOutput);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DoHIDTransferCallback(MPTR callbackFuncMPTR, MPTR callbackParamMPTR, uint32 hidHandle, uint32 errorCode,
|
void DoHIDTransferCallback(MPTR callbackFuncMPTR, MPTR callbackParamMPTR, uint32 hidHandle, uint32 errorCode,
|
||||||
|
@ -354,26 +344,152 @@ namespace nsyshid
|
||||||
coreinitAsyncCallback_add(callbackFuncMPTR, 5, hidHandle, errorCode, buffer, length, callbackParamMPTR);
|
coreinitAsyncCallback_add(callbackFuncMPTR, 5, hidHandle, errorCode, buffer, length, callbackParamMPTR);
|
||||||
}
|
}
|
||||||
|
|
||||||
void export_HIDSetIdle(PPCInterpreter_t* hCPU)
|
void _hidGetDescriptorAsync(std::shared_ptr<Device> device, uint8 descType, uint8 descIndex, uint16 lang, uint8* output, uint32 outputMaxLength, MPTR callbackFuncMPTR, MPTR callbackParamMPTR)
|
||||||
{
|
{
|
||||||
ppcDefineParamU32(hidHandle, 0); // r3
|
if (device->GetDescriptor(descType, descIndex, lang, output, outputMaxLength))
|
||||||
ppcDefineParamU32(ifIndex, 1); // r4
|
|
||||||
ppcDefineParamU32(ukn, 2); // r5
|
|
||||||
ppcDefineParamU32(duration, 3); // r6
|
|
||||||
ppcDefineParamMPTR(callbackFuncMPTR, 4); // r7
|
|
||||||
ppcDefineParamMPTR(callbackParamMPTR, 5); // r8
|
|
||||||
cemuLog_logDebug(LogType::Force, "nsyshid.HIDSetIdle(...)");
|
|
||||||
|
|
||||||
// todo
|
|
||||||
if (callbackFuncMPTR)
|
|
||||||
{
|
{
|
||||||
DoHIDTransferCallback(callbackFuncMPTR, callbackParamMPTR, hidHandle, 0, MPTR_NULL, 0);
|
DoHIDTransferCallback(callbackFuncMPTR,
|
||||||
|
callbackParamMPTR,
|
||||||
|
device->m_hid->handle,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
cemu_assert_unimplemented();
|
DoHIDTransferCallback(callbackFuncMPTR,
|
||||||
|
callbackParamMPTR,
|
||||||
|
device->m_hid->handle,
|
||||||
|
-1,
|
||||||
|
0,
|
||||||
|
0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void export_HIDGetDescriptor(PPCInterpreter_t* hCPU)
|
||||||
|
{
|
||||||
|
ppcDefineParamU32(hidHandle, 0); // r3
|
||||||
|
ppcDefineParamU8(descType, 1); // r4
|
||||||
|
ppcDefineParamU8(descIndex, 2); // r5
|
||||||
|
ppcDefineParamU16(lang, 3); // r6
|
||||||
|
ppcDefineParamUStr(output, 4); // r7
|
||||||
|
ppcDefineParamU32(outputMaxLength, 5); // r8
|
||||||
|
ppcDefineParamMPTR(cbFuncMPTR, 6); // r9
|
||||||
|
ppcDefineParamMPTR(cbParamMPTR, 7); // r10
|
||||||
|
cemuLog_logDebug(LogType::Force, "nsyshid.HIDGetDescriptor(0x{:08x}, 0x{:02x}, 0x{:02x}, 0x{:04x}, 0x{:x}, 0x{:08x}, 0x{:08x}, 0x{:08x})",
|
||||||
|
hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6], hCPU->gpr[7], hCPU->gpr[8], hCPU->gpr[9], hCPU->gpr[10]);
|
||||||
|
|
||||||
|
std::shared_ptr<Device> device = GetDeviceByHandle(hidHandle, true);
|
||||||
|
if (device == nullptr)
|
||||||
|
{
|
||||||
|
cemuLog_log(LogType::Force, "nsyshid.HIDGetDescriptor(): Unable to find device with hid handle {}", hidHandle);
|
||||||
|
osLib_returnFromFunction(hCPU, -1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// issue request (synchronous or asynchronous)
|
||||||
|
sint32 returnCode = 0;
|
||||||
|
if (cbFuncMPTR == MPTR_NULL)
|
||||||
|
{
|
||||||
|
// synchronous
|
||||||
|
returnCode = -1;
|
||||||
|
if (device->GetDescriptor(descType, descIndex, lang, output, outputMaxLength))
|
||||||
|
{
|
||||||
|
returnCode = outputMaxLength;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// asynchronous
|
||||||
|
std::thread(&_hidGetDescriptorAsync, device, descType, descIndex, lang, output, outputMaxLength, cbFuncMPTR, cbParamMPTR)
|
||||||
|
.detach();
|
||||||
|
returnCode = 0;
|
||||||
|
}
|
||||||
|
osLib_returnFromFunction(hCPU, returnCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _hidSetIdleAsync(std::shared_ptr<Device> device, uint8 ifIndex, uint8 reportId, uint8 duration, MPTR callbackFuncMPTR, MPTR callbackParamMPTR)
|
||||||
|
{
|
||||||
|
if (device->SetIdle(ifIndex, reportId, duration))
|
||||||
|
{
|
||||||
|
DoHIDTransferCallback(callbackFuncMPTR,
|
||||||
|
callbackParamMPTR,
|
||||||
|
device->m_hid->handle,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
DoHIDTransferCallback(callbackFuncMPTR,
|
||||||
|
callbackParamMPTR,
|
||||||
|
device->m_hid->handle,
|
||||||
|
-1,
|
||||||
|
0,
|
||||||
|
0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void export_HIDSetIdle(PPCInterpreter_t* hCPU)
|
||||||
|
{
|
||||||
|
ppcDefineParamU32(hidHandle, 0); // r3
|
||||||
|
ppcDefineParamU8(ifIndex, 1); // r4
|
||||||
|
ppcDefineParamU8(reportId, 2); // r5
|
||||||
|
ppcDefineParamU8(duration, 3); // r6
|
||||||
|
ppcDefineParamMPTR(callbackFuncMPTR, 4); // r7
|
||||||
|
ppcDefineParamMPTR(callbackParamMPTR, 5); // r8
|
||||||
|
cemuLog_logDebug(LogType::Force, "nsyshid.HIDSetIdle(0x{:08x}, 0x{:02x}, 0x{:02x}, 0x{:02x}, 0x{:08x}, 0x{:08x})", hCPU->gpr[3],
|
||||||
|
hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6], hCPU->gpr[7], hCPU->gpr[8]);
|
||||||
|
|
||||||
|
std::shared_ptr<Device> device = GetDeviceByHandle(hidHandle, true);
|
||||||
|
if (device == nullptr)
|
||||||
|
{
|
||||||
|
cemuLog_log(LogType::Force, "nsyshid.HIDSetIdle(): Unable to find device with hid handle {}", hidHandle);
|
||||||
|
osLib_returnFromFunction(hCPU, -1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// issue request (synchronous or asynchronous)
|
||||||
|
sint32 returnCode = 0;
|
||||||
|
if (callbackFuncMPTR == MPTR_NULL)
|
||||||
|
{
|
||||||
|
// synchronous
|
||||||
|
returnCode = -1;
|
||||||
|
if (device->SetIdle(ifIndex, reportId, duration))
|
||||||
|
{
|
||||||
|
returnCode = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// asynchronous
|
||||||
|
std::thread(&_hidSetIdleAsync, device, ifIndex, reportId, duration, callbackFuncMPTR, callbackParamMPTR)
|
||||||
|
.detach();
|
||||||
|
returnCode = 0;
|
||||||
|
}
|
||||||
|
osLib_returnFromFunction(hCPU, returnCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _hidSetProtocolAsync(std::shared_ptr<Device> device, uint8 ifIndex, uint8 protocol, MPTR callbackFuncMPTR, MPTR callbackParamMPTR)
|
||||||
|
{
|
||||||
|
if (device->SetProtocol(ifIndex, protocol))
|
||||||
|
{
|
||||||
|
DoHIDTransferCallback(callbackFuncMPTR,
|
||||||
|
callbackParamMPTR,
|
||||||
|
device->m_hid->handle,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
DoHIDTransferCallback(callbackFuncMPTR,
|
||||||
|
callbackParamMPTR,
|
||||||
|
device->m_hid->handle,
|
||||||
|
-1,
|
||||||
|
0,
|
||||||
|
0);
|
||||||
}
|
}
|
||||||
osLib_returnFromFunction(hCPU, 0); // for non-async version, return number of bytes transferred
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void export_HIDSetProtocol(PPCInterpreter_t* hCPU)
|
void export_HIDSetProtocol(PPCInterpreter_t* hCPU)
|
||||||
|
@ -383,51 +499,51 @@ namespace nsyshid
|
||||||
ppcDefineParamU8(protocol, 2); // r5
|
ppcDefineParamU8(protocol, 2); // r5
|
||||||
ppcDefineParamMPTR(callbackFuncMPTR, 3); // r6
|
ppcDefineParamMPTR(callbackFuncMPTR, 3); // r6
|
||||||
ppcDefineParamMPTR(callbackParamMPTR, 4); // r7
|
ppcDefineParamMPTR(callbackParamMPTR, 4); // r7
|
||||||
cemuLog_logDebug(LogType::Force, "nsyshid.HIDSetProtocol(...)");
|
cemuLog_logDebug(LogType::Force, "nsyshid.HIDSetProtocol(0x{:08x}, 0x{:02x}, 0x{:02x}, 0x{:08x}, 0x{:08x})", hCPU->gpr[3],
|
||||||
|
hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6], hCPU->gpr[7]);
|
||||||
|
|
||||||
std::shared_ptr<Device> device = GetDeviceByHandle(hidHandle, true);
|
std::shared_ptr<Device> device = GetDeviceByHandle(hidHandle, true);
|
||||||
sint32 returnCode = -1;
|
if (device == nullptr)
|
||||||
if (device)
|
|
||||||
{
|
{
|
||||||
if (!device->IsOpened())
|
cemuLog_log(LogType::Force, "nsyshid.HIDSetProtocol(): Unable to find device with hid handle {}", hidHandle);
|
||||||
|
osLib_returnFromFunction(hCPU, -1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// issue request (synchronous or asynchronous)
|
||||||
|
sint32 returnCode = 0;
|
||||||
|
if (callbackFuncMPTR == MPTR_NULL)
|
||||||
|
{
|
||||||
|
// synchronous
|
||||||
|
returnCode = -1;
|
||||||
|
if (device->SetProtocol(ifIndex, protocol))
|
||||||
{
|
{
|
||||||
cemuLog_logDebug(LogType::Force, "nsyshid.HIDSetProtocol(): error: device is not opened");
|
returnCode = 0;
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (device->SetProtocol(ifIndex, protocol))
|
|
||||||
{
|
|
||||||
returnCode = 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
cemu_assert_suspicious();
|
// asynchronous
|
||||||
}
|
std::thread(&_hidSetProtocolAsync, device, ifIndex, protocol, callbackFuncMPTR, callbackParamMPTR)
|
||||||
|
.detach();
|
||||||
if (callbackFuncMPTR)
|
returnCode = 0;
|
||||||
{
|
|
||||||
DoHIDTransferCallback(callbackFuncMPTR, callbackParamMPTR, hidHandle, 0, MPTR_NULL, 0);
|
|
||||||
}
|
}
|
||||||
osLib_returnFromFunction(hCPU, returnCode);
|
osLib_returnFromFunction(hCPU, returnCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
// handler for async HIDSetReport transfers
|
// handler for async HIDSetReport transfers
|
||||||
void _hidSetReportAsync(std::shared_ptr<Device> device, uint8* reportData, sint32 length,
|
void _hidSetReportAsync(std::shared_ptr<Device> device, uint8 reportType, uint8 reportId, uint8* data, uint32 length,
|
||||||
uint8* originalData,
|
MPTR callbackFuncMPTR, MPTR callbackParamMPTR)
|
||||||
sint32 originalLength, MPTR callbackFuncMPTR, MPTR callbackParamMPTR)
|
|
||||||
{
|
{
|
||||||
cemuLog_logDebug(LogType::Force, "_hidSetReportAsync begin");
|
cemuLog_logDebug(LogType::Force, "_hidSetReportAsync begin");
|
||||||
ReportMessage message(reportData, length, originalData, originalLength);
|
ReportMessage message(reportType, reportId, data, length);
|
||||||
if (device->SetReport(&message))
|
if (device->SetReport(&message))
|
||||||
{
|
{
|
||||||
DoHIDTransferCallback(callbackFuncMPTR,
|
DoHIDTransferCallback(callbackFuncMPTR,
|
||||||
callbackParamMPTR,
|
callbackParamMPTR,
|
||||||
device->m_hid->handle,
|
device->m_hid->handle,
|
||||||
0,
|
0,
|
||||||
memory_getVirtualOffsetFromPointer(originalData),
|
memory_getVirtualOffsetFromPointer(data),
|
||||||
originalLength);
|
length);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -435,24 +551,22 @@ namespace nsyshid
|
||||||
callbackParamMPTR,
|
callbackParamMPTR,
|
||||||
device->m_hid->handle,
|
device->m_hid->handle,
|
||||||
-1,
|
-1,
|
||||||
memory_getVirtualOffsetFromPointer(originalData),
|
memory_getVirtualOffsetFromPointer(data),
|
||||||
0);
|
length);
|
||||||
}
|
}
|
||||||
free(reportData);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// handler for synchronous HIDSetReport transfers
|
// handler for synchronous HIDSetReport transfers
|
||||||
sint32 _hidSetReportSync(std::shared_ptr<Device> device, uint8* reportData, sint32 length,
|
sint32 _hidSetReportSync(std::shared_ptr<Device> device, uint8 reportType, uint8 reportId,
|
||||||
uint8* originalData, sint32 originalLength, coreinit::OSEvent* event)
|
uint8* data, uint32 length, coreinit::OSEvent* event)
|
||||||
{
|
{
|
||||||
_debugPrintHex("_hidSetReportSync Begin", reportData, length);
|
_debugPrintHex("_hidSetReportSync Begin", data, length);
|
||||||
sint32 returnCode = 0;
|
sint32 returnCode = 0;
|
||||||
ReportMessage message(reportData, length, originalData, originalLength);
|
ReportMessage message(reportType, reportId, data, length);
|
||||||
if (device->SetReport(&message))
|
if (device->SetReport(&message))
|
||||||
{
|
{
|
||||||
returnCode = originalLength;
|
returnCode = length;
|
||||||
}
|
}
|
||||||
free(reportData);
|
|
||||||
cemuLog_logDebug(LogType::Force, "_hidSetReportSync end. returnCode: {}", returnCode);
|
cemuLog_logDebug(LogType::Force, "_hidSetReportSync end. returnCode: {}", returnCode);
|
||||||
coreinit::OSSignalEvent(event);
|
coreinit::OSSignalEvent(event);
|
||||||
return returnCode;
|
return returnCode;
|
||||||
|
@ -461,19 +575,19 @@ namespace nsyshid
|
||||||
void export_HIDSetReport(PPCInterpreter_t* hCPU)
|
void export_HIDSetReport(PPCInterpreter_t* hCPU)
|
||||||
{
|
{
|
||||||
ppcDefineParamU32(hidHandle, 0); // r3
|
ppcDefineParamU32(hidHandle, 0); // r3
|
||||||
ppcDefineParamU32(reportRelatedUkn, 1); // r4
|
ppcDefineParamU8(reportType, 1); // r4
|
||||||
ppcDefineParamU32(reportId, 2); // r5
|
ppcDefineParamU8(reportId, 2); // r5
|
||||||
ppcDefineParamUStr(data, 3); // r6
|
ppcDefineParamUStr(data, 3); // r6
|
||||||
ppcDefineParamU32(dataLength, 4); // r7
|
ppcDefineParamU32(dataLength, 4); // r7
|
||||||
ppcDefineParamMPTR(callbackFuncMPTR, 5); // r8
|
ppcDefineParamMPTR(callbackFuncMPTR, 5); // r8
|
||||||
ppcDefineParamMPTR(callbackParamMPTR, 6); // r9
|
ppcDefineParamMPTR(callbackParamMPTR, 6); // r9
|
||||||
cemuLog_logDebug(LogType::Force, "nsyshid.HIDSetReport({},0x{:02x},0x{:02x},...)", hidHandle, reportRelatedUkn,
|
cemuLog_logDebug(LogType::Force, "nsyshid.HIDSetReport(0x{:08x}, 0x{:02x}, 0x{:02x}, 0x{:08x}, 0x{:08x}, 0x{:08x}, 0x{:08x})", hCPU->gpr[3],
|
||||||
reportId);
|
hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6], hCPU->gpr[7], hCPU->gpr[8], hCPU->gpr[9]);
|
||||||
|
|
||||||
_debugPrintHex("HIDSetReport", data, dataLength);
|
_debugPrintHex("HIDSetReport", data, dataLength);
|
||||||
|
|
||||||
#ifdef CEMU_DEBUG_ASSERT
|
#ifdef CEMU_DEBUG_ASSERT
|
||||||
if (reportRelatedUkn != 2 || reportId != 0)
|
if (reportType != 2 || reportId != 0)
|
||||||
assert_dbg();
|
assert_dbg();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -485,15 +599,6 @@ namespace nsyshid
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// prepare report data
|
|
||||||
// note: Currently we need to pad the data to 0x20 bytes for it to work (plus one extra byte for HidD_SetOutputReport)
|
|
||||||
// Does IOSU pad data to 0x20 byte? Also check if this is specific to Skylanders portal
|
|
||||||
sint32 paddedLength = (dataLength + 0x1F) & ~0x1F;
|
|
||||||
uint8* reportData = (uint8*)malloc(paddedLength + 1);
|
|
||||||
memset(reportData, 0, paddedLength + 1);
|
|
||||||
reportData[0] = 0;
|
|
||||||
memcpy(reportData + 1, data, dataLength);
|
|
||||||
|
|
||||||
// issue request (synchronous or asynchronous)
|
// issue request (synchronous or asynchronous)
|
||||||
sint32 returnCode = 0;
|
sint32 returnCode = 0;
|
||||||
if (callbackFuncMPTR == MPTR_NULL)
|
if (callbackFuncMPTR == MPTR_NULL)
|
||||||
|
@ -501,15 +606,14 @@ namespace nsyshid
|
||||||
// synchronous
|
// synchronous
|
||||||
StackAllocator<coreinit::OSEvent> event;
|
StackAllocator<coreinit::OSEvent> event;
|
||||||
coreinit::OSInitEvent(&event, coreinit::OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, coreinit::OSEvent::EVENT_MODE::MODE_AUTO);
|
coreinit::OSInitEvent(&event, coreinit::OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, coreinit::OSEvent::EVENT_MODE::MODE_AUTO);
|
||||||
std::future<sint32> res = std::async(std::launch::async, &_hidSetReportSync, device, reportData,
|
std::future<sint32> res = std::async(std::launch::async, &_hidSetReportSync, device, reportType, reportId, data, dataLength, &event);
|
||||||
paddedLength + 1, data, dataLength, &event);
|
|
||||||
coreinit::OSWaitEvent(&event);
|
coreinit::OSWaitEvent(&event);
|
||||||
returnCode = res.get();
|
returnCode = res.get();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// asynchronous
|
// asynchronous
|
||||||
std::thread(&_hidSetReportAsync, device, reportData, paddedLength + 1, data, dataLength,
|
std::thread(&_hidSetReportAsync, device, reportType, reportId, data, dataLength,
|
||||||
callbackFuncMPTR, callbackParamMPTR)
|
callbackFuncMPTR, callbackParamMPTR)
|
||||||
.detach();
|
.detach();
|
||||||
returnCode = 0;
|
returnCode = 0;
|
||||||
|
@ -586,7 +690,7 @@ namespace nsyshid
|
||||||
ppcDefineParamMPTR(callbackFuncMPTR, 3); // r6
|
ppcDefineParamMPTR(callbackFuncMPTR, 3); // r6
|
||||||
ppcDefineParamMPTR(callbackParamMPTR, 4); // r7
|
ppcDefineParamMPTR(callbackParamMPTR, 4); // r7
|
||||||
cemuLog_logDebug(LogType::Force, "nsyshid.HIDRead(0x{:x},0x{:08x},0x{:08x},0x{:08x},0x{:08x})", hCPU->gpr[3],
|
cemuLog_logDebug(LogType::Force, "nsyshid.HIDRead(0x{:x},0x{:08x},0x{:08x},0x{:08x},0x{:08x})", hCPU->gpr[3],
|
||||||
hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6], hCPU->gpr[7]);
|
hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6], hCPU->gpr[7]);
|
||||||
|
|
||||||
std::shared_ptr<Device> device = GetDeviceByHandle(hidHandle, true);
|
std::shared_ptr<Device> device = GetDeviceByHandle(hidHandle, true);
|
||||||
if (device == nullptr)
|
if (device == nullptr)
|
||||||
|
@ -683,7 +787,7 @@ namespace nsyshid
|
||||||
ppcDefineParamMPTR(callbackFuncMPTR, 3); // r6
|
ppcDefineParamMPTR(callbackFuncMPTR, 3); // r6
|
||||||
ppcDefineParamMPTR(callbackParamMPTR, 4); // r7
|
ppcDefineParamMPTR(callbackParamMPTR, 4); // r7
|
||||||
cemuLog_logDebug(LogType::Force, "nsyshid.HIDWrite(0x{:x},0x{:08x},0x{:08x},0x{:08x},0x{:08x})", hCPU->gpr[3],
|
cemuLog_logDebug(LogType::Force, "nsyshid.HIDWrite(0x{:x},0x{:08x},0x{:08x},0x{:08x},0x{:08x})", hCPU->gpr[3],
|
||||||
hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6], hCPU->gpr[7]);
|
hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6], hCPU->gpr[7]);
|
||||||
|
|
||||||
std::shared_ptr<Device> device = GetDeviceByHandle(hidHandle, true);
|
std::shared_ptr<Device> device = GetDeviceByHandle(hidHandle, true);
|
||||||
if (device == nullptr)
|
if (device == nullptr)
|
||||||
|
@ -718,7 +822,7 @@ namespace nsyshid
|
||||||
ppcDefineParamTypePtr(ukn0, uint32be, 1);
|
ppcDefineParamTypePtr(ukn0, uint32be, 1);
|
||||||
ppcDefineParamTypePtr(ukn1, uint32be, 2);
|
ppcDefineParamTypePtr(ukn1, uint32be, 2);
|
||||||
cemuLog_logDebug(LogType::Force, "nsyshid.HIDDecodeError(0x{:08x},0x{:08x},0x{:08x})", hCPU->gpr[3],
|
cemuLog_logDebug(LogType::Force, "nsyshid.HIDDecodeError(0x{:08x},0x{:08x},0x{:08x})", hCPU->gpr[3],
|
||||||
hCPU->gpr[4], hCPU->gpr[5]);
|
hCPU->gpr[4], hCPU->gpr[5]);
|
||||||
|
|
||||||
// todo
|
// todo
|
||||||
*ukn0 = 0x3FF;
|
*ukn0 = 0x3FF;
|
||||||
|
|
|
@ -760,7 +760,7 @@ namespace padscore
|
||||||
void start()
|
void start()
|
||||||
{
|
{
|
||||||
OSCreateAlarm(&g_padscore.alarm);
|
OSCreateAlarm(&g_padscore.alarm);
|
||||||
const uint64 start_tick = coreinit::coreinit_getOSTime();
|
const uint64 start_tick = coreinit::OSGetTime();
|
||||||
const uint64 period_tick = coreinit::EspressoTime::GetTimerClock() / 200; // every 5ms
|
const uint64 period_tick = coreinit::EspressoTime::GetTimerClock() / 200; // every 5ms
|
||||||
MPTR handler = PPCInterpreter_makeCallableExportDepr(TickFunction);
|
MPTR handler = PPCInterpreter_makeCallableExportDepr(TickFunction);
|
||||||
OSSetPeriodicAlarm(&g_padscore.alarm, start_tick, period_tick, handler);
|
OSSetPeriodicAlarm(&g_padscore.alarm, start_tick, period_tick, handler);
|
||||||
|
|
|
@ -396,90 +396,35 @@ namespace snd_core
|
||||||
|
|
||||||
void AXOut_init()
|
void AXOut_init()
|
||||||
{
|
{
|
||||||
auto& config = GetConfig();
|
|
||||||
const auto audio_api = (IAudioAPI::AudioAPI)config.audio_api;
|
|
||||||
|
|
||||||
numQueuedFramesSndGeneric = 0;
|
numQueuedFramesSndGeneric = 0;
|
||||||
|
|
||||||
std::unique_lock lock(g_audioMutex);
|
std::unique_lock lock(g_audioMutex);
|
||||||
if (!g_tvAudio)
|
if (!g_tvAudio)
|
||||||
{
|
{
|
||||||
sint32 channels;
|
try
|
||||||
switch (config.tv_channels)
|
|
||||||
{
|
{
|
||||||
case 0:
|
g_tvAudio = IAudioAPI::CreateDeviceFromConfig(true, 48000, snd_core::AX_SAMPLES_PER_3MS_48KHZ * AX_FRAMES_PER_GROUP, 16);
|
||||||
channels = 1; // will mix mono sound on both output channels
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
channels = 6;
|
|
||||||
break;
|
|
||||||
default: // stereo
|
|
||||||
channels = 2;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
catch (std::runtime_error& ex)
|
||||||
IAudioAPI::DeviceDescriptionPtr device_description;
|
|
||||||
if (IAudioAPI::IsAudioAPIAvailable(audio_api))
|
|
||||||
{
|
{
|
||||||
auto devices = IAudioAPI::GetDevices(audio_api);
|
cemuLog_log(LogType::Force, "can't initialize tv audio: {}", ex.what());
|
||||||
const auto it = std::find_if(devices.begin(), devices.end(), [&config](const auto& d) {return d->GetIdentifier() == config.tv_device; });
|
exit(0);
|
||||||
if (it != devices.end())
|
|
||||||
device_description = *it;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (device_description)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
g_tvAudio = IAudioAPI::CreateDevice((IAudioAPI::AudioAPI)config.audio_api, device_description, 48000, channels, snd_core::AX_SAMPLES_PER_3MS_48KHZ * AX_FRAMES_PER_GROUP, 16);
|
|
||||||
g_tvAudio->SetVolume(config.tv_volume);
|
|
||||||
}
|
|
||||||
catch (std::runtime_error& ex)
|
|
||||||
{
|
|
||||||
cemuLog_log(LogType::Force, "can't initialize tv audio: {}", ex.what());
|
|
||||||
exit(0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!g_padAudio)
|
if (!g_padAudio)
|
||||||
{
|
{
|
||||||
sint32 channels;
|
try
|
||||||
switch (config.pad_channels)
|
|
||||||
{
|
{
|
||||||
case 0:
|
g_padAudio = IAudioAPI::CreateDeviceFromConfig(false, 48000, snd_core::AX_SAMPLES_PER_3MS_48KHZ * AX_FRAMES_PER_GROUP, 16);
|
||||||
channels = 1; // will mix mono sound on both output channels
|
if(g_padAudio)
|
||||||
break;
|
g_padVolume = g_padAudio->GetVolume();
|
||||||
case 2:
|
|
||||||
channels = 6;
|
|
||||||
break;
|
|
||||||
default: // stereo
|
|
||||||
channels = 2;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
catch (std::runtime_error& ex)
|
||||||
IAudioAPI::DeviceDescriptionPtr device_description;
|
|
||||||
if (IAudioAPI::IsAudioAPIAvailable(audio_api))
|
|
||||||
{
|
{
|
||||||
auto devices = IAudioAPI::GetDevices(audio_api);
|
cemuLog_log(LogType::Force, "can't initialize pad audio: {}", ex.what());
|
||||||
const auto it = std::find_if(devices.begin(), devices.end(), [&config](const auto& d) {return d->GetIdentifier() == config.pad_device; });
|
exit(0);
|
||||||
if (it != devices.end())
|
|
||||||
device_description = *it;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (device_description)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
g_padAudio = IAudioAPI::CreateDevice((IAudioAPI::AudioAPI)config.audio_api, device_description, 48000, channels, snd_core::AX_SAMPLES_PER_3MS_48KHZ * AX_FRAMES_PER_GROUP, 16);
|
|
||||||
g_padAudio->SetVolume(config.pad_volume);
|
|
||||||
g_padVolume = config.pad_volume;
|
|
||||||
}
|
|
||||||
catch (std::runtime_error& ex)
|
|
||||||
{
|
|
||||||
cemuLog_log(LogType::Force, "can't initialize pad audio: {}", ex.what());
|
|
||||||
exit(0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -267,7 +267,7 @@ namespace vpad
|
||||||
{
|
{
|
||||||
if (channel <= 1 && vpadDelayEnabled)
|
if (channel <= 1 && vpadDelayEnabled)
|
||||||
{
|
{
|
||||||
uint64 currentTime = coreinit::coreinit_getOSTime();
|
uint64 currentTime = coreinit::OSGetTime();
|
||||||
const auto dif = currentTime - vpad::g_vpad.controller_data[channel].drcLastCallTime;
|
const auto dif = currentTime - vpad::g_vpad.controller_data[channel].drcLastCallTime;
|
||||||
if (dif <= (ESPRESSO_TIMER_CLOCK / 60ull))
|
if (dif <= (ESPRESSO_TIMER_CLOCK / 60ull))
|
||||||
{
|
{
|
||||||
|
@ -1149,7 +1149,7 @@ namespace vpad
|
||||||
void start()
|
void start()
|
||||||
{
|
{
|
||||||
coreinit::OSCreateAlarm(&g_vpad.alarm);
|
coreinit::OSCreateAlarm(&g_vpad.alarm);
|
||||||
const uint64 start_tick = coreinit::coreinit_getOSTime();
|
const uint64 start_tick = coreinit::OSGetTime();
|
||||||
const uint64 period_tick = coreinit::EspressoTime::GetTimerClock() * 5 / 1000;
|
const uint64 period_tick = coreinit::EspressoTime::GetTimerClock() * 5 / 1000;
|
||||||
const MPTR handler = PPCInterpreter_makeCallableExportDepr(TickFunction);
|
const MPTR handler = PPCInterpreter_makeCallableExportDepr(TickFunction);
|
||||||
coreinit::OSSetPeriodicAlarm(&g_vpad.alarm, start_tick, period_tick, handler);
|
coreinit::OSSetPeriodicAlarm(&g_vpad.alarm, start_tick, period_tick, handler);
|
||||||
|
|
|
@ -274,6 +274,25 @@ inline uint64 _udiv128(uint64 highDividend, uint64 lowDividend, uint64 divisor,
|
||||||
#define NOEXPORT __attribute__ ((visibility ("hidden")))
|
#define NOEXPORT __attribute__ ((visibility ("hidden")))
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if defined(_MSC_VER)
|
||||||
|
#define FORCE_INLINE __forceinline
|
||||||
|
#elif defined(__GNUC__) || defined(__clang__)
|
||||||
|
#define FORCE_INLINE inline __attribute__((always_inline))
|
||||||
|
#else
|
||||||
|
#define FORCE_INLINE inline
|
||||||
|
#endif
|
||||||
|
|
||||||
|
FORCE_INLINE int BSF(uint32 v) // returns index of first bit set, counting from LSB. If v is 0 then result is undefined
|
||||||
|
{
|
||||||
|
#if defined(_MSC_VER)
|
||||||
|
return _tzcnt_u32(v); // TZCNT requires BMI1. But if not supported it will execute as BSF
|
||||||
|
#elif defined(__GNUC__) || defined(__clang__)
|
||||||
|
return __builtin_ctz(v);
|
||||||
|
#else
|
||||||
|
return std::countr_zero(v);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
// On aarch64 we handle some of the x86 intrinsics by implementing them as wrappers
|
// On aarch64 we handle some of the x86 intrinsics by implementing them as wrappers
|
||||||
#if defined(__aarch64__)
|
#if defined(__aarch64__)
|
||||||
|
|
||||||
|
|
|
@ -114,7 +114,7 @@ CubebAPI::~CubebAPI()
|
||||||
bool CubebAPI::NeedAdditionalBlocks() const
|
bool CubebAPI::NeedAdditionalBlocks() const
|
||||||
{
|
{
|
||||||
std::shared_lock lock(m_mutex);
|
std::shared_lock lock(m_mutex);
|
||||||
return m_buffer.size() < s_audioDelay * m_bytesPerBlock;
|
return m_buffer.size() < GetAudioDelay() * m_bytesPerBlock;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CubebAPI::FeedBlock(sint16* data)
|
bool CubebAPI::FeedBlock(sint16* data)
|
||||||
|
@ -183,17 +183,17 @@ void CubebAPI::Destroy()
|
||||||
|
|
||||||
std::vector<IAudioAPI::DeviceDescriptionPtr> CubebAPI::GetDevices()
|
std::vector<IAudioAPI::DeviceDescriptionPtr> CubebAPI::GetDevices()
|
||||||
{
|
{
|
||||||
cubeb_device_collection devices;
|
|
||||||
if (cubeb_enumerate_devices(s_context, CUBEB_DEVICE_TYPE_OUTPUT, &devices) != CUBEB_OK)
|
|
||||||
return {};
|
|
||||||
|
|
||||||
std::vector<DeviceDescriptionPtr> result;
|
std::vector<DeviceDescriptionPtr> result;
|
||||||
result.reserve(devices.count + 1); // Reserve space for the default device
|
|
||||||
|
|
||||||
// Add the default device to the list
|
// Add the default device to the list
|
||||||
auto defaultDevice = std::make_shared<CubebDeviceDescription>(nullptr, "default", L"Default Device");
|
auto defaultDevice = std::make_shared<CubebDeviceDescription>(nullptr, "default", L"Default Device");
|
||||||
result.emplace_back(defaultDevice);
|
result.emplace_back(defaultDevice);
|
||||||
|
|
||||||
|
cubeb_device_collection devices;
|
||||||
|
if (cubeb_enumerate_devices(s_context, CUBEB_DEVICE_TYPE_OUTPUT, &devices) != CUBEB_OK)
|
||||||
|
return result;
|
||||||
|
|
||||||
|
result.reserve(devices.count + 1); // The default device already occupies one element
|
||||||
|
|
||||||
for (size_t i = 0; i < devices.count; ++i)
|
for (size_t i = 0; i < devices.count; ++i)
|
||||||
{
|
{
|
||||||
// const auto& device = devices.device[i];
|
// const auto& device = devices.device[i];
|
||||||
|
|
|
@ -175,17 +175,17 @@ void CubebInputAPI::Destroy()
|
||||||
|
|
||||||
std::vector<IAudioInputAPI::DeviceDescriptionPtr> CubebInputAPI::GetDevices()
|
std::vector<IAudioInputAPI::DeviceDescriptionPtr> CubebInputAPI::GetDevices()
|
||||||
{
|
{
|
||||||
cubeb_device_collection devices;
|
|
||||||
if (cubeb_enumerate_devices(s_context, CUBEB_DEVICE_TYPE_INPUT, &devices) != CUBEB_OK)
|
|
||||||
return {};
|
|
||||||
|
|
||||||
std::vector<DeviceDescriptionPtr> result;
|
std::vector<DeviceDescriptionPtr> result;
|
||||||
result.reserve(devices.count + 1); // Reserve space for the default device
|
|
||||||
|
|
||||||
// Add the default device to the list
|
// Add the default device to the list
|
||||||
auto defaultDevice = std::make_shared<CubebDeviceDescription>(nullptr, "default", L"Default Device");
|
auto defaultDevice = std::make_shared<CubebDeviceDescription>(nullptr, "default", L"Default Device");
|
||||||
result.emplace_back(defaultDevice);
|
result.emplace_back(defaultDevice);
|
||||||
|
|
||||||
|
cubeb_device_collection devices;
|
||||||
|
if (cubeb_enumerate_devices(s_context, CUBEB_DEVICE_TYPE_INPUT, &devices) != CUBEB_OK)
|
||||||
|
return result;
|
||||||
|
|
||||||
|
result.reserve(devices.count + 1); // The default device already occupies one element
|
||||||
|
|
||||||
for (size_t i = 0; i < devices.count; ++i)
|
for (size_t i = 0; i < devices.count; ++i)
|
||||||
{
|
{
|
||||||
// const auto& device = devices.device[i];
|
// const auto& device = devices.device[i];
|
||||||
|
|
|
@ -210,7 +210,7 @@ void DirectSoundAPI::SetVolume(sint32 volume)
|
||||||
bool DirectSoundAPI::NeedAdditionalBlocks() const
|
bool DirectSoundAPI::NeedAdditionalBlocks() const
|
||||||
{
|
{
|
||||||
std::shared_lock lock(m_mutex);
|
std::shared_lock lock(m_mutex);
|
||||||
return m_buffer.size() < s_audioDelay;
|
return m_buffer.size() < GetAudioDelay();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<DirectSoundAPI::DeviceDescriptionPtr> DirectSoundAPI::GetDevices()
|
std::vector<DirectSoundAPI::DeviceDescriptionPtr> DirectSoundAPI::GetDevices()
|
||||||
|
|
|
@ -97,7 +97,40 @@ bool IAudioAPI::IsAudioAPIAvailable(AudioAPI api)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AudioAPIPtr IAudioAPI::CreateDeviceFromConfig(bool TV, sint32 rate, sint32 samples_per_block, sint32 bits_per_sample)
|
||||||
|
{
|
||||||
|
auto& config = GetConfig();
|
||||||
|
sint32 channels = CemuConfig::AudioChannelsToNChannels(TV ? config.tv_channels : config.pad_channels);
|
||||||
|
return CreateDeviceFromConfig(TV, rate, channels, samples_per_block, bits_per_sample);
|
||||||
|
}
|
||||||
|
|
||||||
|
AudioAPIPtr IAudioAPI::CreateDeviceFromConfig(bool TV, sint32 rate, sint32 channels, sint32 samples_per_block, sint32 bits_per_sample)
|
||||||
|
{
|
||||||
|
AudioAPIPtr audioAPIDev;
|
||||||
|
|
||||||
|
auto& config = GetConfig();
|
||||||
|
|
||||||
|
const auto audio_api = (IAudioAPI::AudioAPI)config.audio_api;
|
||||||
|
auto& selectedDevice = TV ? config.tv_device : config.pad_device;
|
||||||
|
|
||||||
|
if(selectedDevice.empty())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
IAudioAPI::DeviceDescriptionPtr device_description;
|
||||||
|
if (IAudioAPI::IsAudioAPIAvailable(audio_api))
|
||||||
|
{
|
||||||
|
auto devices = IAudioAPI::GetDevices(audio_api);
|
||||||
|
const auto it = std::find_if(devices.begin(), devices.end(), [&selectedDevice](const auto& d) {return d->GetIdentifier() == selectedDevice; });
|
||||||
|
if (it != devices.end())
|
||||||
|
device_description = *it;
|
||||||
|
}
|
||||||
|
if (!device_description)
|
||||||
|
throw std::runtime_error("failed to find selected device while trying to create audio device");
|
||||||
|
|
||||||
|
audioAPIDev = CreateDevice(audio_api, device_description, rate, channels, samples_per_block, bits_per_sample);
|
||||||
|
audioAPIDev->SetVolume(TV ? config.tv_volume : config.pad_volume);
|
||||||
|
return audioAPIDev;
|
||||||
|
}
|
||||||
|
|
||||||
AudioAPIPtr IAudioAPI::CreateDevice(AudioAPI api, const DeviceDescriptionPtr& device, sint32 samplerate, sint32 channels, sint32 samples_per_block, sint32 bits_per_sample)
|
AudioAPIPtr IAudioAPI::CreateDevice(AudioAPI api, const DeviceDescriptionPtr& device, sint32 samplerate, sint32 channels, sint32 samples_per_block, sint32 bits_per_sample)
|
||||||
{
|
{
|
||||||
|
@ -167,3 +200,12 @@ std::vector<IAudioAPI::DeviceDescriptionPtr> IAudioAPI::GetDevices(AudioAPI api)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void IAudioAPI::SetAudioDelayOverride(uint32 delay)
|
||||||
|
{
|
||||||
|
m_audioDelayOverride = delay;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32 IAudioAPI::GetAudioDelay() const
|
||||||
|
{
|
||||||
|
return m_audioDelayOverride > 0 ? m_audioDelayOverride : s_audioDelay;
|
||||||
|
}
|
||||||
|
|
|
@ -55,11 +55,15 @@ public:
|
||||||
virtual bool FeedBlock(sint16* data) = 0;
|
virtual bool FeedBlock(sint16* data) = 0;
|
||||||
virtual bool Play() = 0;
|
virtual bool Play() = 0;
|
||||||
virtual bool Stop() = 0;
|
virtual bool Stop() = 0;
|
||||||
|
void SetAudioDelayOverride(uint32 delay);
|
||||||
|
uint32 GetAudioDelay() const;
|
||||||
|
|
||||||
static void PrintLogging();
|
static void PrintLogging();
|
||||||
static void InitializeStatic();
|
static void InitializeStatic();
|
||||||
static bool IsAudioAPIAvailable(AudioAPI api);
|
static bool IsAudioAPIAvailable(AudioAPI api);
|
||||||
|
|
||||||
|
static std::unique_ptr<IAudioAPI> CreateDeviceFromConfig(bool TV, sint32 rate, sint32 samples_per_block, sint32 bits_per_sample);
|
||||||
|
static std::unique_ptr<IAudioAPI> CreateDeviceFromConfig(bool TV, sint32 rate, sint32 channels, sint32 samples_per_block, sint32 bits_per_sample);
|
||||||
static std::unique_ptr<IAudioAPI> CreateDevice(AudioAPI api, const DeviceDescriptionPtr& device, sint32 samplerate, sint32 channels, sint32 samples_per_block, sint32 bits_per_sample);
|
static std::unique_ptr<IAudioAPI> CreateDevice(AudioAPI api, const DeviceDescriptionPtr& device, sint32 samplerate, sint32 channels, sint32 samples_per_block, sint32 bits_per_sample);
|
||||||
static std::vector<DeviceDescriptionPtr> GetDevices(AudioAPI api);
|
static std::vector<DeviceDescriptionPtr> GetDevices(AudioAPI api);
|
||||||
|
|
||||||
|
@ -75,9 +79,10 @@ protected:
|
||||||
bool m_playing = false;
|
bool m_playing = false;
|
||||||
|
|
||||||
static std::array<bool, AudioAPIEnd> s_availableApis;
|
static std::array<bool, AudioAPIEnd> s_availableApis;
|
||||||
static uint32 s_audioDelay;
|
uint32 m_audioDelayOverride = 0;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
static uint32 s_audioDelay;
|
||||||
void InitWFX(sint32 samplerate, sint32 channels, sint32 bits_per_sample);
|
void InitWFX(sint32 samplerate, sint32 channels, sint32 bits_per_sample);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -33,8 +33,8 @@ XAudio27API::XAudio27API(uint32 device_id, uint32 samplerate, uint32 channels, u
|
||||||
m_wfx.Format.nChannels = channels;
|
m_wfx.Format.nChannels = channels;
|
||||||
m_wfx.Format.nSamplesPerSec = samplerate;
|
m_wfx.Format.nSamplesPerSec = samplerate;
|
||||||
m_wfx.Format.wBitsPerSample = bits_per_sample;
|
m_wfx.Format.wBitsPerSample = bits_per_sample;
|
||||||
m_wfx.Format.nBlockAlign = (m_wfx.Format.nChannels * m_wfx.Format.wBitsPerSample) / 8; // must equal (nChannels × wBitsPerSample) / 8
|
m_wfx.Format.nBlockAlign = (m_wfx.Format.nChannels * m_wfx.Format.wBitsPerSample) / 8; // must equal (nChannels × wBitsPerSample) / 8
|
||||||
m_wfx.Format.nAvgBytesPerSec = m_wfx.Format.nSamplesPerSec * m_wfx.Format.nBlockAlign; // must equal nSamplesPerSec × nBlockAlign.
|
m_wfx.Format.nAvgBytesPerSec = m_wfx.Format.nSamplesPerSec * m_wfx.Format.nBlockAlign; // must equal nSamplesPerSec × nBlockAlign.
|
||||||
m_wfx.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
|
m_wfx.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
|
||||||
|
|
||||||
m_wfx.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
|
m_wfx.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
|
||||||
|
@ -199,9 +199,7 @@ bool XAudio27API::FeedBlock(sint16* data)
|
||||||
// check if we queued too many blocks
|
// check if we queued too many blocks
|
||||||
if(m_blocks_queued >= kBlockCount)
|
if(m_blocks_queued >= kBlockCount)
|
||||||
{
|
{
|
||||||
XAUDIO2_VOICE_STATE state{};
|
m_blocks_queued = GetQueuedBuffers();
|
||||||
m_source_voice->GetState(&state);
|
|
||||||
m_blocks_queued = state.BuffersQueued;
|
|
||||||
|
|
||||||
if (m_blocks_queued >= kBlockCount)
|
if (m_blocks_queued >= kBlockCount)
|
||||||
{
|
{
|
||||||
|
@ -222,7 +220,14 @@ bool XAudio27API::FeedBlock(sint16* data)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint32 XAudio27API::GetQueuedBuffers() const
|
||||||
|
{
|
||||||
|
XAUDIO2_VOICE_STATE state{};
|
||||||
|
m_source_voice->GetState(&state);
|
||||||
|
return state.BuffersQueued;
|
||||||
|
}
|
||||||
|
|
||||||
bool XAudio27API::NeedAdditionalBlocks() const
|
bool XAudio27API::NeedAdditionalBlocks() const
|
||||||
{
|
{
|
||||||
return m_blocks_queued < s_audioDelay;
|
return GetQueuedBuffers() < GetAudioDelay();
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,6 +47,8 @@ public:
|
||||||
static std::vector<DeviceDescriptionPtr> GetDevices();
|
static std::vector<DeviceDescriptionPtr> GetDevices();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
uint32 GetQueuedBuffers() const;
|
||||||
|
|
||||||
struct XAudioDeleter
|
struct XAudioDeleter
|
||||||
{
|
{
|
||||||
void operator()(IXAudio2* ptr) const;
|
void operator()(IXAudio2* ptr) const;
|
||||||
|
|
|
@ -270,9 +270,7 @@ bool XAudio2API::FeedBlock(sint16* data)
|
||||||
// check if we queued too many blocks
|
// check if we queued too many blocks
|
||||||
if (m_blocks_queued >= kBlockCount)
|
if (m_blocks_queued >= kBlockCount)
|
||||||
{
|
{
|
||||||
XAUDIO2_VOICE_STATE state{};
|
m_blocks_queued = GetQueuedBuffers();
|
||||||
m_source_voice->GetState(&state);
|
|
||||||
m_blocks_queued = state.BuffersQueued;
|
|
||||||
|
|
||||||
if (m_blocks_queued >= kBlockCount)
|
if (m_blocks_queued >= kBlockCount)
|
||||||
{
|
{
|
||||||
|
@ -293,7 +291,14 @@ bool XAudio2API::FeedBlock(sint16* data)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint32 XAudio2API::GetQueuedBuffers() const
|
||||||
|
{
|
||||||
|
XAUDIO2_VOICE_STATE state{};
|
||||||
|
m_source_voice->GetState(&state);
|
||||||
|
return state.BuffersQueued;
|
||||||
|
}
|
||||||
|
|
||||||
bool XAudio2API::NeedAdditionalBlocks() const
|
bool XAudio2API::NeedAdditionalBlocks() const
|
||||||
{
|
{
|
||||||
return m_blocks_queued < s_audioDelay;
|
return GetQueuedBuffers() < GetAudioDelay();
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,6 +46,8 @@ public:
|
||||||
static const std::vector<DeviceDescriptionPtr>& GetDevices() { return s_devices; }
|
static const std::vector<DeviceDescriptionPtr>& GetDevices() { return s_devices; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
uint32 GetQueuedBuffers() const;
|
||||||
|
|
||||||
static const std::vector<DeviceDescriptionPtr>& RefreshDevices();
|
static const std::vector<DeviceDescriptionPtr>& RefreshDevices();
|
||||||
|
|
||||||
struct XAudioDeleter
|
struct XAudioDeleter
|
||||||
|
|
|
@ -46,6 +46,7 @@ void CemuConfig::Load(XMLConfigParser& parser)
|
||||||
fullscreen = parser.get("fullscreen", fullscreen);
|
fullscreen = parser.get("fullscreen", fullscreen);
|
||||||
proxy_server = parser.get("proxy_server", "");
|
proxy_server = parser.get("proxy_server", "");
|
||||||
disable_screensaver = parser.get("disable_screensaver", disable_screensaver);
|
disable_screensaver = parser.get("disable_screensaver", disable_screensaver);
|
||||||
|
play_boot_sound = parser.get("play_boot_sound", play_boot_sound);
|
||||||
console_language = parser.get("console_language", console_language.GetInitValue());
|
console_language = parser.get("console_language", console_language.GetInitValue());
|
||||||
|
|
||||||
window_position.x = parser.get("window_position").get("x", -1);
|
window_position.x = parser.get("window_position").get("x", -1);
|
||||||
|
@ -370,6 +371,7 @@ void CemuConfig::Save(XMLConfigParser& parser)
|
||||||
config.set<bool>("fullscreen", fullscreen);
|
config.set<bool>("fullscreen", fullscreen);
|
||||||
config.set("proxy_server", proxy_server.GetValue().c_str());
|
config.set("proxy_server", proxy_server.GetValue().c_str());
|
||||||
config.set<bool>("disable_screensaver", disable_screensaver);
|
config.set<bool>("disable_screensaver", disable_screensaver);
|
||||||
|
config.set<bool>("play_boot_sound", play_boot_sound);
|
||||||
|
|
||||||
// config.set("cpu_mode", cpu_mode.GetValue());
|
// config.set("cpu_mode", cpu_mode.GetValue());
|
||||||
//config.set("console_region", console_region.GetValue());
|
//config.set("console_region", console_region.GetValue());
|
||||||
|
|
|
@ -192,7 +192,7 @@ ENABLE_ENUM_ITERATORS(CrashDump, CrashDump::Disabled, CrashDump::Enabled);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
struct fmt::formatter<PrecompiledShaderOption> : formatter<string_view> {
|
struct fmt::formatter<const PrecompiledShaderOption> : formatter<string_view> {
|
||||||
template <typename FormatContext>
|
template <typename FormatContext>
|
||||||
auto format(const PrecompiledShaderOption c, FormatContext &ctx) const {
|
auto format(const PrecompiledShaderOption c, FormatContext &ctx) const {
|
||||||
string_view name;
|
string_view name;
|
||||||
|
@ -207,7 +207,7 @@ struct fmt::formatter<PrecompiledShaderOption> : formatter<string_view> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
template <>
|
template <>
|
||||||
struct fmt::formatter<AccurateShaderMulOption> : formatter<string_view> {
|
struct fmt::formatter<const AccurateShaderMulOption> : formatter<string_view> {
|
||||||
template <typename FormatContext>
|
template <typename FormatContext>
|
||||||
auto format(const AccurateShaderMulOption c, FormatContext &ctx) const {
|
auto format(const AccurateShaderMulOption c, FormatContext &ctx) const {
|
||||||
string_view name;
|
string_view name;
|
||||||
|
@ -221,7 +221,7 @@ struct fmt::formatter<AccurateShaderMulOption> : formatter<string_view> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
template <>
|
template <>
|
||||||
struct fmt::formatter<CPUMode> : formatter<string_view> {
|
struct fmt::formatter<const CPUMode> : formatter<string_view> {
|
||||||
template <typename FormatContext>
|
template <typename FormatContext>
|
||||||
auto format(const CPUMode c, FormatContext &ctx) const {
|
auto format(const CPUMode c, FormatContext &ctx) const {
|
||||||
string_view name;
|
string_view name;
|
||||||
|
@ -238,7 +238,7 @@ struct fmt::formatter<CPUMode> : formatter<string_view> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
template <>
|
template <>
|
||||||
struct fmt::formatter<CPUModeLegacy> : formatter<string_view> {
|
struct fmt::formatter<const CPUModeLegacy> : formatter<string_view> {
|
||||||
template <typename FormatContext>
|
template <typename FormatContext>
|
||||||
auto format(const CPUModeLegacy c, FormatContext &ctx) const {
|
auto format(const CPUModeLegacy c, FormatContext &ctx) const {
|
||||||
string_view name;
|
string_view name;
|
||||||
|
@ -255,7 +255,7 @@ struct fmt::formatter<CPUModeLegacy> : formatter<string_view> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
template <>
|
template <>
|
||||||
struct fmt::formatter<CafeConsoleRegion> : formatter<string_view> {
|
struct fmt::formatter<const CafeConsoleRegion> : formatter<string_view> {
|
||||||
template <typename FormatContext>
|
template <typename FormatContext>
|
||||||
auto format(const CafeConsoleRegion v, FormatContext &ctx) const {
|
auto format(const CafeConsoleRegion v, FormatContext &ctx) const {
|
||||||
string_view name;
|
string_view name;
|
||||||
|
@ -276,7 +276,7 @@ struct fmt::formatter<CafeConsoleRegion> : formatter<string_view> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
template <>
|
template <>
|
||||||
struct fmt::formatter<CafeConsoleLanguage> : formatter<string_view> {
|
struct fmt::formatter<const CafeConsoleLanguage> : formatter<string_view> {
|
||||||
template <typename FormatContext>
|
template <typename FormatContext>
|
||||||
auto format(const CafeConsoleLanguage v, FormatContext &ctx) {
|
auto format(const CafeConsoleLanguage v, FormatContext &ctx) {
|
||||||
string_view name;
|
string_view name;
|
||||||
|
@ -302,7 +302,7 @@ struct fmt::formatter<CafeConsoleLanguage> : formatter<string_view> {
|
||||||
|
|
||||||
#if BOOST_OS_WINDOWS
|
#if BOOST_OS_WINDOWS
|
||||||
template <>
|
template <>
|
||||||
struct fmt::formatter<CrashDump> : formatter<string_view> {
|
struct fmt::formatter<const CrashDump> : formatter<string_view> {
|
||||||
template <typename FormatContext>
|
template <typename FormatContext>
|
||||||
auto format(const CrashDump v, FormatContext &ctx) {
|
auto format(const CrashDump v, FormatContext &ctx) {
|
||||||
string_view name;
|
string_view name;
|
||||||
|
@ -319,7 +319,7 @@ struct fmt::formatter<CrashDump> : formatter<string_view> {
|
||||||
};
|
};
|
||||||
#elif BOOST_OS_UNIX
|
#elif BOOST_OS_UNIX
|
||||||
template <>
|
template <>
|
||||||
struct fmt::formatter<CrashDump> : formatter<string_view> {
|
struct fmt::formatter<const CrashDump> : formatter<string_view> {
|
||||||
template <typename FormatContext>
|
template <typename FormatContext>
|
||||||
auto format(const CrashDump v, FormatContext &ctx) {
|
auto format(const CrashDump v, FormatContext &ctx) {
|
||||||
string_view name;
|
string_view name;
|
||||||
|
@ -380,6 +380,7 @@ struct CemuConfig
|
||||||
#endif
|
#endif
|
||||||
ConfigValue<bool> disable_screensaver{DISABLE_SCREENSAVER_DEFAULT};
|
ConfigValue<bool> disable_screensaver{DISABLE_SCREENSAVER_DEFAULT};
|
||||||
#undef DISABLE_SCREENSAVER_DEFAULT
|
#undef DISABLE_SCREENSAVER_DEFAULT
|
||||||
|
ConfigValue<bool> play_boot_sound{false};
|
||||||
|
|
||||||
std::vector<std::string> game_paths;
|
std::vector<std::string> game_paths;
|
||||||
std::mutex game_cache_entries_mutex;
|
std::mutex game_cache_entries_mutex;
|
||||||
|
@ -524,7 +525,20 @@ struct CemuConfig
|
||||||
ConfigValue<bool> emulate_dimensions_toypad{false};
|
ConfigValue<bool> emulate_dimensions_toypad{false};
|
||||||
}emulated_usb_devices{};
|
}emulated_usb_devices{};
|
||||||
|
|
||||||
private:
|
static int AudioChannelsToNChannels(AudioChannels kStereo)
|
||||||
|
{
|
||||||
|
switch (kStereo)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
return 1; // will mix mono sound on both output channels
|
||||||
|
case 2:
|
||||||
|
return 6;
|
||||||
|
default: // stereo
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
GameEntry* GetGameEntryByTitleId(uint64 titleId);
|
GameEntry* GetGameEntryByTitleId(uint64 titleId);
|
||||||
GameEntry* CreateGameEntry(uint64 titleId);
|
GameEntry* CreateGameEntry(uint64 titleId);
|
||||||
};
|
};
|
||||||
|
|
|
@ -178,3 +178,7 @@ endif()
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
target_link_libraries(CemuGui PRIVATE bthprops)
|
target_link_libraries(CemuGui PRIVATE bthprops)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if(ALLOW_PORTABLE)
|
||||||
|
target_compile_definitions(CemuGui PRIVATE CEMU_ALLOW_PORTABLE)
|
||||||
|
endif ()
|
|
@ -88,12 +88,14 @@ void CemuApp::DeterminePaths(std::set<fs::path>& failedWriteAccess) // for Windo
|
||||||
fs::path exePath(wxHelper::MakeFSPath(standardPaths.GetExecutablePath()));
|
fs::path exePath(wxHelper::MakeFSPath(standardPaths.GetExecutablePath()));
|
||||||
fs::path portablePath = exePath.parent_path() / "portable";
|
fs::path portablePath = exePath.parent_path() / "portable";
|
||||||
data_path = exePath.parent_path(); // the data path is always the same as the exe path
|
data_path = exePath.parent_path(); // the data path is always the same as the exe path
|
||||||
if (fs::exists(portablePath, ec))
|
#ifdef CEMU_ALLOW_PORTABLE
|
||||||
|
if (fs::is_directory(portablePath, ec))
|
||||||
{
|
{
|
||||||
isPortable = true;
|
isPortable = true;
|
||||||
user_data_path = config_path = cache_path = portablePath;
|
user_data_path = config_path = cache_path = portablePath;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
#endif
|
||||||
{
|
{
|
||||||
fs::path roamingPath = GetAppDataRoamingPath() / "Cemu";
|
fs::path roamingPath = GetAppDataRoamingPath() / "Cemu";
|
||||||
user_data_path = config_path = cache_path = roamingPath;
|
user_data_path = config_path = cache_path = roamingPath;
|
||||||
|
@ -124,12 +126,13 @@ void CemuApp::DeterminePaths(std::set<fs::path>& failedWriteAccess) // for Linux
|
||||||
fs::path portablePath = exePath.parent_path() / "portable";
|
fs::path portablePath = exePath.parent_path() / "portable";
|
||||||
// GetExecutablePath returns the AppImage's temporary mount location
|
// GetExecutablePath returns the AppImage's temporary mount location
|
||||||
wxString appImagePath;
|
wxString appImagePath;
|
||||||
if (wxGetEnv(("APPIMAGE"), &appImagePath))
|
if (wxGetEnv("APPIMAGE", &appImagePath))
|
||||||
{
|
{
|
||||||
exePath = wxHelper::MakeFSPath(appImagePath);
|
exePath = wxHelper::MakeFSPath(appImagePath);
|
||||||
portablePath = exePath.parent_path() / "portable";
|
portablePath = exePath.parent_path() / "portable";
|
||||||
}
|
}
|
||||||
if (fs::exists(portablePath, ec))
|
#ifdef CEMU_ALLOW_PORTABLE
|
||||||
|
if (fs::is_directory(portablePath, ec))
|
||||||
{
|
{
|
||||||
isPortable = true;
|
isPortable = true;
|
||||||
user_data_path = config_path = cache_path = portablePath;
|
user_data_path = config_path = cache_path = portablePath;
|
||||||
|
@ -137,6 +140,7 @@ void CemuApp::DeterminePaths(std::set<fs::path>& failedWriteAccess) // for Linux
|
||||||
data_path = exePath.parent_path();
|
data_path = exePath.parent_path();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
#endif
|
||||||
{
|
{
|
||||||
SetAppName("Cemu");
|
SetAppName("Cemu");
|
||||||
wxString appName = GetAppName();
|
wxString appName = GetAppName();
|
||||||
|
@ -167,16 +171,18 @@ void CemuApp::DeterminePaths(std::set<fs::path>& failedWriteAccess) // for MacOS
|
||||||
fs::path user_data_path, config_path, cache_path, data_path;
|
fs::path user_data_path, config_path, cache_path, data_path;
|
||||||
auto standardPaths = wxStandardPaths::Get();
|
auto standardPaths = wxStandardPaths::Get();
|
||||||
fs::path exePath(wxHelper::MakeFSPath(standardPaths.GetExecutablePath()));
|
fs::path exePath(wxHelper::MakeFSPath(standardPaths.GetExecutablePath()));
|
||||||
// If run from an app bundle, use its parent directory
|
// If run from an app bundle, use its parent directory
|
||||||
fs::path appPath = exePath.parent_path().parent_path().parent_path();
|
fs::path appPath = exePath.parent_path().parent_path().parent_path();
|
||||||
fs::path portablePath = appPath.extension() == ".app" ? appPath.parent_path() / "portable" : exePath.parent_path() / "portable";
|
fs::path portablePath = appPath.extension() == ".app" ? appPath.parent_path() / "portable" : exePath.parent_path() / "portable";
|
||||||
if (fs::exists(portablePath, ec))
|
#ifdef CEMU_ALLOW_PORTABLE
|
||||||
|
if (fs::is_directory(portablePath, ec))
|
||||||
{
|
{
|
||||||
isPortable = true;
|
isPortable = true;
|
||||||
user_data_path = config_path = cache_path = portablePath;
|
user_data_path = config_path = cache_path = portablePath;
|
||||||
data_path = exePath.parent_path();
|
data_path = exePath.parent_path();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
#endif
|
||||||
{
|
{
|
||||||
SetAppName("Cemu");
|
SetAppName("Cemu");
|
||||||
wxString appName = GetAppName();
|
wxString appName = GetAppName();
|
||||||
|
|
|
@ -207,8 +207,10 @@ wxPanel* GeneralSettings2::AddGeneralPage(wxNotebook* notebook)
|
||||||
#if BOOST_OS_MACOS
|
#if BOOST_OS_MACOS
|
||||||
m_disable_screensaver->Enable(false);
|
m_disable_screensaver->Enable(false);
|
||||||
#endif
|
#endif
|
||||||
|
m_play_boot_sound = new wxCheckBox(box, wxID_ANY, _("Enable intro sound"));
|
||||||
// InsertEmptyRow();
|
m_play_boot_sound->SetToolTip(_("Play bootSound file while compiling shaders/pipelines."));
|
||||||
|
second_row->Add(m_play_boot_sound, 0, botflag, 5);
|
||||||
|
CountRowElement();
|
||||||
|
|
||||||
m_auto_update = new wxCheckBox(box, wxID_ANY, _("Automatically check for updates"));
|
m_auto_update = new wxCheckBox(box, wxID_ANY, _("Automatically check for updates"));
|
||||||
m_auto_update->SetToolTip(_("Automatically checks for new cemu versions on startup"));
|
m_auto_update->SetToolTip(_("Automatically checks for new cemu versions on startup"));
|
||||||
|
@ -936,6 +938,7 @@ void GeneralSettings2::StoreConfig()
|
||||||
#if BOOST_OS_LINUX && defined(ENABLE_FERAL_GAMEMODE)
|
#if BOOST_OS_LINUX && defined(ENABLE_FERAL_GAMEMODE)
|
||||||
config.feral_gamemode = m_feral_gamemode->IsChecked();
|
config.feral_gamemode = m_feral_gamemode->IsChecked();
|
||||||
#endif
|
#endif
|
||||||
|
config.play_boot_sound = m_play_boot_sound->IsChecked();
|
||||||
config.disable_screensaver = m_disable_screensaver->IsChecked();
|
config.disable_screensaver = m_disable_screensaver->IsChecked();
|
||||||
// Toggle while a game is running
|
// Toggle while a game is running
|
||||||
if (CafeSystem::IsTitleRunning())
|
if (CafeSystem::IsTitleRunning())
|
||||||
|
@ -943,6 +946,7 @@ void GeneralSettings2::StoreConfig()
|
||||||
ScreenSaver::SetInhibit(config.disable_screensaver);
|
ScreenSaver::SetInhibit(config.disable_screensaver);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// -1 is default wx widget value -> set to dummy 0 so mainwindow and padwindow will update it
|
// -1 is default wx widget value -> set to dummy 0 so mainwindow and padwindow will update it
|
||||||
config.window_position = m_save_window_position_size->IsChecked() ? Vector2i{ 0,0 } : Vector2i{-1,-1};
|
config.window_position = m_save_window_position_size->IsChecked() ? Vector2i{ 0,0 } : Vector2i{-1,-1};
|
||||||
config.window_size = m_save_window_position_size->IsChecked() ? Vector2i{ 0,0 } : Vector2i{-1,-1};
|
config.window_size = m_save_window_position_size->IsChecked() ? Vector2i{ 0,0 } : Vector2i{-1,-1};
|
||||||
|
@ -1574,6 +1578,7 @@ void GeneralSettings2::ApplyConfig()
|
||||||
m_save_screenshot->SetValue(config.save_screenshot);
|
m_save_screenshot->SetValue(config.save_screenshot);
|
||||||
|
|
||||||
m_disable_screensaver->SetValue(config.disable_screensaver);
|
m_disable_screensaver->SetValue(config.disable_screensaver);
|
||||||
|
m_play_boot_sound->SetValue(config.play_boot_sound);
|
||||||
#if BOOST_OS_LINUX && defined(ENABLE_FERAL_GAMEMODE)
|
#if BOOST_OS_LINUX && defined(ENABLE_FERAL_GAMEMODE)
|
||||||
m_feral_gamemode->SetValue(config.feral_gamemode);
|
m_feral_gamemode->SetValue(config.feral_gamemode);
|
||||||
#endif
|
#endif
|
||||||
|
@ -1776,20 +1781,7 @@ void GeneralSettings2::UpdateAudioDevice()
|
||||||
if (m_game_launched && g_tvAudio)
|
if (m_game_launched && g_tvAudio)
|
||||||
channels = g_tvAudio->GetChannels();
|
channels = g_tvAudio->GetChannels();
|
||||||
else
|
else
|
||||||
{
|
channels = CemuConfig::AudioChannelsToNChannels(config.tv_channels);
|
||||||
switch (config.tv_channels)
|
|
||||||
{
|
|
||||||
case 0:
|
|
||||||
channels = 1;
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
channels = 6;
|
|
||||||
break;
|
|
||||||
default: // stereo
|
|
||||||
channels = 2;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -1824,20 +1816,7 @@ void GeneralSettings2::UpdateAudioDevice()
|
||||||
if (m_game_launched && g_padAudio)
|
if (m_game_launched && g_padAudio)
|
||||||
channels = g_padAudio->GetChannels();
|
channels = g_padAudio->GetChannels();
|
||||||
else
|
else
|
||||||
{
|
channels = CemuConfig::AudioChannelsToNChannels(config.pad_channels);
|
||||||
switch (config.pad_channels)
|
|
||||||
{
|
|
||||||
case 0:
|
|
||||||
channels = 1;
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
channels = 6;
|
|
||||||
break;
|
|
||||||
default: // stereo
|
|
||||||
channels = 2;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -1873,20 +1852,7 @@ void GeneralSettings2::UpdateAudioDevice()
|
||||||
if (m_game_launched && g_inputAudio)
|
if (m_game_launched && g_inputAudio)
|
||||||
channels = g_inputAudio->GetChannels();
|
channels = g_inputAudio->GetChannels();
|
||||||
else
|
else
|
||||||
{
|
channels = CemuConfig::AudioChannelsToNChannels(config.input_channels);
|
||||||
switch (config.input_channels)
|
|
||||||
{
|
|
||||||
case 0:
|
|
||||||
channels = 1;
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
channels = 6;
|
|
||||||
break;
|
|
||||||
default: // stereo
|
|
||||||
channels = 2;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
|
@ -43,6 +43,7 @@ private:
|
||||||
wxCheckBox* m_discord_presence, *m_fullscreen_menubar;
|
wxCheckBox* m_discord_presence, *m_fullscreen_menubar;
|
||||||
wxCheckBox* m_auto_update, *m_receive_untested_releases, *m_save_screenshot;
|
wxCheckBox* m_auto_update, *m_receive_untested_releases, *m_save_screenshot;
|
||||||
wxCheckBox* m_disable_screensaver;
|
wxCheckBox* m_disable_screensaver;
|
||||||
|
wxCheckBox* m_play_boot_sound;
|
||||||
#if BOOST_OS_LINUX && defined(ENABLE_FERAL_GAMEMODE)
|
#if BOOST_OS_LINUX && defined(ENABLE_FERAL_GAMEMODE)
|
||||||
wxCheckBox* m_feral_gamemode;
|
wxCheckBox* m_feral_gamemode;
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -77,6 +77,7 @@ enum
|
||||||
MAINFRAME_MENU_ID_FILE_INSTALL_UPDATE,
|
MAINFRAME_MENU_ID_FILE_INSTALL_UPDATE,
|
||||||
MAINFRAME_MENU_ID_FILE_OPEN_CEMU_FOLDER,
|
MAINFRAME_MENU_ID_FILE_OPEN_CEMU_FOLDER,
|
||||||
MAINFRAME_MENU_ID_FILE_OPEN_MLC_FOLDER,
|
MAINFRAME_MENU_ID_FILE_OPEN_MLC_FOLDER,
|
||||||
|
MAINFRAME_MENU_ID_FILE_OPEN_SHADERCACHE_FOLDER,
|
||||||
MAINFRAME_MENU_ID_FILE_EXIT,
|
MAINFRAME_MENU_ID_FILE_EXIT,
|
||||||
MAINFRAME_MENU_ID_FILE_END_EMULATION,
|
MAINFRAME_MENU_ID_FILE_END_EMULATION,
|
||||||
MAINFRAME_MENU_ID_FILE_RECENT_0,
|
MAINFRAME_MENU_ID_FILE_RECENT_0,
|
||||||
|
@ -169,6 +170,7 @@ EVT_MENU(MAINFRAME_MENU_ID_FILE_LOAD, MainWindow::OnFileMenu)
|
||||||
EVT_MENU(MAINFRAME_MENU_ID_FILE_INSTALL_UPDATE, MainWindow::OnInstallUpdate)
|
EVT_MENU(MAINFRAME_MENU_ID_FILE_INSTALL_UPDATE, MainWindow::OnInstallUpdate)
|
||||||
EVT_MENU(MAINFRAME_MENU_ID_FILE_OPEN_CEMU_FOLDER, MainWindow::OnOpenFolder)
|
EVT_MENU(MAINFRAME_MENU_ID_FILE_OPEN_CEMU_FOLDER, MainWindow::OnOpenFolder)
|
||||||
EVT_MENU(MAINFRAME_MENU_ID_FILE_OPEN_MLC_FOLDER, MainWindow::OnOpenFolder)
|
EVT_MENU(MAINFRAME_MENU_ID_FILE_OPEN_MLC_FOLDER, MainWindow::OnOpenFolder)
|
||||||
|
EVT_MENU(MAINFRAME_MENU_ID_FILE_OPEN_SHADERCACHE_FOLDER, MainWindow::OnOpenFolder)
|
||||||
EVT_MENU(MAINFRAME_MENU_ID_FILE_EXIT, MainWindow::OnFileExit)
|
EVT_MENU(MAINFRAME_MENU_ID_FILE_EXIT, MainWindow::OnFileExit)
|
||||||
EVT_MENU(MAINFRAME_MENU_ID_FILE_END_EMULATION, MainWindow::OnFileMenu)
|
EVT_MENU(MAINFRAME_MENU_ID_FILE_END_EMULATION, MainWindow::OnFileMenu)
|
||||||
EVT_MENU_RANGE(MAINFRAME_MENU_ID_FILE_RECENT_0 + 0, MAINFRAME_MENU_ID_FILE_RECENT_LAST, MainWindow::OnFileMenu)
|
EVT_MENU_RANGE(MAINFRAME_MENU_ID_FILE_RECENT_0 + 0, MAINFRAME_MENU_ID_FILE_RECENT_LAST, MainWindow::OnFileMenu)
|
||||||
|
@ -673,10 +675,15 @@ void MainWindow::OnFileMenu(wxCommandEvent& event)
|
||||||
|
|
||||||
void MainWindow::OnOpenFolder(wxCommandEvent& event)
|
void MainWindow::OnOpenFolder(wxCommandEvent& event)
|
||||||
{
|
{
|
||||||
if(event.GetId() == MAINFRAME_MENU_ID_FILE_OPEN_CEMU_FOLDER)
|
const auto id = event.GetId();
|
||||||
|
if(id == MAINFRAME_MENU_ID_FILE_OPEN_CEMU_FOLDER)
|
||||||
wxLaunchDefaultApplication(wxHelper::FromPath(ActiveSettings::GetUserDataPath()));
|
wxLaunchDefaultApplication(wxHelper::FromPath(ActiveSettings::GetUserDataPath()));
|
||||||
else if(event.GetId() == MAINFRAME_MENU_ID_FILE_OPEN_MLC_FOLDER)
|
else if(id == MAINFRAME_MENU_ID_FILE_OPEN_MLC_FOLDER)
|
||||||
wxLaunchDefaultApplication(wxHelper::FromPath(ActiveSettings::GetMlcPath()));
|
wxLaunchDefaultApplication(wxHelper::FromPath(ActiveSettings::GetMlcPath()));
|
||||||
|
else if (id == MAINFRAME_MENU_ID_FILE_OPEN_SHADERCACHE_FOLDER)
|
||||||
|
wxLaunchDefaultApplication(wxHelper::FromPath(ActiveSettings::GetCachePath("shaderCache")));
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::OnInstallUpdate(wxCommandEvent& event)
|
void MainWindow::OnInstallUpdate(wxCommandEvent& event)
|
||||||
|
@ -1113,9 +1120,7 @@ void MainWindow::OnDebugDumpUsedShaders(wxCommandEvent& event)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// create directory
|
fs::create_directories(ActiveSettings::GetUserDataPath("dump/shaders"));
|
||||||
const fs::path path(ActiveSettings::GetUserDataPath());
|
|
||||||
fs::create_directories(path / "dump" / "shaders");
|
|
||||||
}
|
}
|
||||||
catch (const std::exception & ex)
|
catch (const std::exception & ex)
|
||||||
{
|
{
|
||||||
|
@ -2101,6 +2106,7 @@ void MainWindow::RecreateMenu()
|
||||||
|
|
||||||
m_fileMenu->Append(MAINFRAME_MENU_ID_FILE_OPEN_CEMU_FOLDER, _("&Open Cemu folder"));
|
m_fileMenu->Append(MAINFRAME_MENU_ID_FILE_OPEN_CEMU_FOLDER, _("&Open Cemu folder"));
|
||||||
m_fileMenu->Append(MAINFRAME_MENU_ID_FILE_OPEN_MLC_FOLDER, _("&Open MLC folder"));
|
m_fileMenu->Append(MAINFRAME_MENU_ID_FILE_OPEN_MLC_FOLDER, _("&Open MLC folder"));
|
||||||
|
m_fileMenu->Append(MAINFRAME_MENU_ID_FILE_OPEN_SHADERCACHE_FOLDER, _("Open &shader cache folder"));
|
||||||
m_fileMenu->AppendSeparator();
|
m_fileMenu->AppendSeparator();
|
||||||
|
|
||||||
m_exitMenuItem = m_fileMenu->Append(MAINFRAME_MENU_ID_FILE_EXIT, _("&Exit"));
|
m_exitMenuItem = m_fileMenu->Append(MAINFRAME_MENU_ID_FILE_EXIT, _("&Exit"));
|
||||||
|
|
|
@ -632,7 +632,7 @@ void TitleManager::OnSaveExport(wxCommandEvent& event)
|
||||||
|
|
||||||
const auto persistent_id = (uint32)(uintptr_t)m_save_account_list->GetClientData(selection_index);
|
const auto persistent_id = (uint32)(uintptr_t)m_save_account_list->GetClientData(selection_index);
|
||||||
|
|
||||||
wxFileDialog path_dialog(this, _("Select a target file to export the save entry"), entry->path.string(), wxEmptyString,
|
wxFileDialog path_dialog(this, _("Select a target file to export the save entry"), wxHelper::FromPath(entry->path), wxEmptyString,
|
||||||
fmt::format("{}|*.zip", _("Exported save entry (*.zip)")), wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
|
fmt::format("{}|*.zip", _("Exported save entry (*.zip)")), wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
|
||||||
if (path_dialog.ShowModal() != wxID_OK || path_dialog.GetPath().IsEmpty())
|
if (path_dialog.ShowModal() != wxID_OK || path_dialog.GetPath().IsEmpty())
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -202,14 +202,14 @@ void BreakpointWindow::OnLeftDClick(wxMouseEvent& event)
|
||||||
auto it = debuggerState.breakpoints.begin();
|
auto it = debuggerState.breakpoints.begin();
|
||||||
std::advance(it, index);
|
std::advance(it, index);
|
||||||
|
|
||||||
wxTextEntryDialog set_value_dialog(this, _("Enter a new comment."), wxString::Format(_("Set comment for breakpoint at address %08x"), address), (*it)->comment);
|
const wxString dialogTitle = (*it)->bpType == DEBUGGER_BP_T_LOGGING ? _("Enter a new logging message") : _("Enter a new comment");
|
||||||
if (set_value_dialog.ShowModal() == wxID_OK)
|
const wxString dialogMessage = (*it)->bpType == DEBUGGER_BP_T_LOGGING ? _("Set logging message when code at address %08x is ran.\nUse placeholders like {r3} or {f3} to log register values") : _("Set comment for breakpoint at address %08x");
|
||||||
|
wxTextEntryDialog set_comment_dialog(this, dialogMessage, dialogTitle, (*it)->comment);
|
||||||
|
if (set_comment_dialog.ShowModal() == wxID_OK)
|
||||||
{
|
{
|
||||||
(*it)->comment = set_value_dialog.GetValue().ToStdWstring();
|
(*it)->comment = set_comment_dialog.GetValue().ToStdWstring();
|
||||||
m_breakpoints->SetItem(index, ColumnComment, set_value_dialog.GetValue());
|
m_breakpoints->SetItem(index, ColumnComment, set_comment_dialog.GetValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -64,6 +64,7 @@ wxBEGIN_EVENT_TABLE(DebuggerWindow2, wxFrame)
|
||||||
EVT_COMMAND(wxID_ANY, wxEVT_RUN, DebuggerWindow2::OnRunProgram)
|
EVT_COMMAND(wxID_ANY, wxEVT_RUN, DebuggerWindow2::OnRunProgram)
|
||||||
EVT_COMMAND(wxID_ANY, wxEVT_NOTIFY_MODULE_LOADED, DebuggerWindow2::OnNotifyModuleLoaded)
|
EVT_COMMAND(wxID_ANY, wxEVT_NOTIFY_MODULE_LOADED, DebuggerWindow2::OnNotifyModuleLoaded)
|
||||||
EVT_COMMAND(wxID_ANY, wxEVT_NOTIFY_MODULE_UNLOADED, DebuggerWindow2::OnNotifyModuleUnloaded)
|
EVT_COMMAND(wxID_ANY, wxEVT_NOTIFY_MODULE_UNLOADED, DebuggerWindow2::OnNotifyModuleUnloaded)
|
||||||
|
EVT_COMMAND(wxID_ANY, wxEVT_DISASMCTRL_NOTIFY_GOTO_ADDRESS, DebuggerWindow2::OnDisasmCtrlGotoAddress)
|
||||||
// file menu
|
// file menu
|
||||||
EVT_MENU(MENU_ID_FILE_EXIT, DebuggerWindow2::OnExit)
|
EVT_MENU(MENU_ID_FILE_EXIT, DebuggerWindow2::OnExit)
|
||||||
// window
|
// window
|
||||||
|
@ -383,6 +384,12 @@ void DebuggerWindow2::OnMoveIP(wxCommandEvent& event)
|
||||||
m_disasm_ctrl->CenterOffset(ip);
|
m_disasm_ctrl->CenterOffset(ip);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DebuggerWindow2::OnDisasmCtrlGotoAddress(wxCommandEvent& event)
|
||||||
|
{
|
||||||
|
uint32 address = static_cast<uint32>(event.GetExtraLong());
|
||||||
|
UpdateModuleLabel(address);
|
||||||
|
}
|
||||||
|
|
||||||
void DebuggerWindow2::OnParentMove(const wxPoint& main_position, const wxSize& main_size)
|
void DebuggerWindow2::OnParentMove(const wxPoint& main_position, const wxSize& main_size)
|
||||||
{
|
{
|
||||||
m_main_position = main_position;
|
m_main_position = main_position;
|
||||||
|
@ -416,7 +423,7 @@ void DebuggerWindow2::OnNotifyModuleLoaded(wxCommandEvent& event)
|
||||||
|
|
||||||
void DebuggerWindow2::OnNotifyModuleUnloaded(wxCommandEvent& event)
|
void DebuggerWindow2::OnNotifyModuleUnloaded(wxCommandEvent& event)
|
||||||
{
|
{
|
||||||
RPLModule* module = (RPLModule*)event.GetClientData();
|
RPLModule* module = (RPLModule*)event.GetClientData(); // todo - the RPL module is already unloaded at this point. Find a better way to handle this
|
||||||
SaveModuleStorage(module, true);
|
SaveModuleStorage(module, true);
|
||||||
m_module_window->OnGameLoaded();
|
m_module_window->OnGameLoaded();
|
||||||
m_symbol_window->OnGameLoaded();
|
m_symbol_window->OnGameLoaded();
|
||||||
|
@ -659,7 +666,7 @@ void DebuggerWindow2::CreateMenuBar()
|
||||||
|
|
||||||
void DebuggerWindow2::UpdateModuleLabel(uint32 address)
|
void DebuggerWindow2::UpdateModuleLabel(uint32 address)
|
||||||
{
|
{
|
||||||
if(address == 0)
|
if (address == 0)
|
||||||
address = m_disasm_ctrl->GetViewBaseAddress();
|
address = m_disasm_ctrl->GetViewBaseAddress();
|
||||||
|
|
||||||
RPLModule* module = RPLLoader_FindModuleByCodeAddr(address);
|
RPLModule* module = RPLLoader_FindModuleByCodeAddr(address);
|
||||||
|
|
|
@ -86,6 +86,8 @@ private:
|
||||||
void OnMoveIP(wxCommandEvent& event);
|
void OnMoveIP(wxCommandEvent& event);
|
||||||
void OnNotifyModuleLoaded(wxCommandEvent& event);
|
void OnNotifyModuleLoaded(wxCommandEvent& event);
|
||||||
void OnNotifyModuleUnloaded(wxCommandEvent& event);
|
void OnNotifyModuleUnloaded(wxCommandEvent& event);
|
||||||
|
// events from DisasmCtrl
|
||||||
|
void OnDisasmCtrlGotoAddress(wxCommandEvent& event);
|
||||||
|
|
||||||
void CreateMenuBar();
|
void CreateMenuBar();
|
||||||
void UpdateModuleLabel(uint32 address = 0);
|
void UpdateModuleLabel(uint32 address = 0);
|
||||||
|
|
|
@ -15,6 +15,8 @@
|
||||||
#include "Cafe/HW/Espresso/Debugger/DebugSymbolStorage.h"
|
#include "Cafe/HW/Espresso/Debugger/DebugSymbolStorage.h"
|
||||||
#include <wx/mstream.h> // for wxMemoryInputStream
|
#include <wx/mstream.h> // for wxMemoryInputStream
|
||||||
|
|
||||||
|
wxDEFINE_EVENT(wxEVT_DISASMCTRL_NOTIFY_GOTO_ADDRESS, wxCommandEvent);
|
||||||
|
|
||||||
#define MAX_SYMBOL_LEN (120)
|
#define MAX_SYMBOL_LEN (120)
|
||||||
|
|
||||||
#define COLOR_DEBUG_ACTIVE_BP 0xFFFFA0FF
|
#define COLOR_DEBUG_ACTIVE_BP 0xFFFFA0FF
|
||||||
|
@ -74,6 +76,8 @@ DisasmCtrl::DisasmCtrl(wxWindow* parent, const wxWindowID& id, const wxPoint& po
|
||||||
auto tooltip_sizer = new wxBoxSizer(wxVERTICAL);
|
auto tooltip_sizer = new wxBoxSizer(wxVERTICAL);
|
||||||
tooltip_sizer->Add(new wxStaticText(m_tooltip_window, wxID_ANY, wxEmptyString), 0, wxALL, 5);
|
tooltip_sizer->Add(new wxStaticText(m_tooltip_window, wxID_ANY, wxEmptyString), 0, wxALL, 5);
|
||||||
m_tooltip_window->SetSizer(tooltip_sizer);
|
m_tooltip_window->SetSizer(tooltip_sizer);
|
||||||
|
|
||||||
|
Bind(wxEVT_MENU, &DisasmCtrl::OnContextMenuEntryClicked, this, IDContextMenu_ToggleBreakpoint, IDContextMenu_Last);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DisasmCtrl::Init()
|
void DisasmCtrl::Init()
|
||||||
|
@ -534,7 +538,7 @@ void DisasmCtrl::OnKeyPressed(sint32 key_code, const wxPoint& position)
|
||||||
auto optVirtualAddress = LinePixelPosToAddress(position.y);
|
auto optVirtualAddress = LinePixelPosToAddress(position.y);
|
||||||
switch (key_code)
|
switch (key_code)
|
||||||
{
|
{
|
||||||
case WXK_F9:
|
case WXK_F9:
|
||||||
{
|
{
|
||||||
if (optVirtualAddress)
|
if (optVirtualAddress)
|
||||||
{
|
{
|
||||||
|
@ -545,7 +549,7 @@ void DisasmCtrl::OnKeyPressed(sint32 key_code, const wxPoint& position)
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
case 'G':
|
case 'G':
|
||||||
{
|
{
|
||||||
if(IsKeyDown(WXK_CONTROL))
|
if(IsKeyDown(WXK_CONTROL))
|
||||||
{
|
{
|
||||||
|
@ -662,29 +666,75 @@ void DisasmCtrl::CopyToClipboard(std::string text) {
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static uint32 GetUnrelocatedAddress(MPTR address)
|
||||||
|
{
|
||||||
|
RPLModule* rplModule = RPLLoader_FindModuleByCodeAddr(address);
|
||||||
|
if (!rplModule)
|
||||||
|
return 0;
|
||||||
|
if (address >= rplModule->regionMappingBase_text.GetMPTR() && address < (rplModule->regionMappingBase_text.GetMPTR() + rplModule->regionSize_text))
|
||||||
|
return 0x02000000 + (address - rplModule->regionMappingBase_text.GetMPTR());
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
void DisasmCtrl::OnContextMenu(const wxPoint& position, uint32 line)
|
void DisasmCtrl::OnContextMenu(const wxPoint& position, uint32 line)
|
||||||
{
|
{
|
||||||
wxPoint pos = position;
|
|
||||||
auto optVirtualAddress = LinePixelPosToAddress(position.y - GetViewStart().y * m_line_height);
|
auto optVirtualAddress = LinePixelPosToAddress(position.y - GetViewStart().y * m_line_height);
|
||||||
if (!optVirtualAddress)
|
if (!optVirtualAddress)
|
||||||
return;
|
return;
|
||||||
MPTR virtualAddress = *optVirtualAddress;
|
MPTR virtualAddress = *optVirtualAddress;
|
||||||
|
m_contextMenuAddress = virtualAddress;
|
||||||
|
// show dialog
|
||||||
|
wxMenu menu;
|
||||||
|
menu.Append(IDContextMenu_ToggleBreakpoint, _("Toggle breakpoint"));
|
||||||
|
menu.Append(IDContextMenu_ToggleLoggingBreakpoint, _("Toggle logging point"));
|
||||||
|
if(debugger_hasPatch(virtualAddress))
|
||||||
|
menu.Append(IDContextMenu_RestoreOriginalInstructions, _("Restore original instructions"));
|
||||||
|
menu.AppendSeparator();
|
||||||
|
menu.Append(IDContextMenu_CopyAddress, _("Copy address"));
|
||||||
|
uint32 unrelocatedAddress = GetUnrelocatedAddress(virtualAddress);
|
||||||
|
if (unrelocatedAddress && unrelocatedAddress != virtualAddress)
|
||||||
|
menu.Append(IDContextMenu_CopyUnrelocatedAddress, _("Copy virtual address (for IDA/Ghidra)"));
|
||||||
|
PopupMenu(&menu);
|
||||||
|
}
|
||||||
|
|
||||||
// address
|
void DisasmCtrl::OnContextMenuEntryClicked(wxCommandEvent& event)
|
||||||
if (pos.x <= OFFSET_ADDRESS + OFFSET_ADDRESS_RELATIVE)
|
{
|
||||||
|
switch(event.GetId())
|
||||||
{
|
{
|
||||||
CopyToClipboard(fmt::format("{:#10x}", virtualAddress));
|
case IDContextMenu_ToggleBreakpoint:
|
||||||
return;
|
{
|
||||||
}
|
debugger_toggleExecuteBreakpoint(m_contextMenuAddress);
|
||||||
else if (pos.x <= OFFSET_ADDRESS + OFFSET_ADDRESS_RELATIVE + OFFSET_DISASSEMBLY)
|
wxCommandEvent evt(wxEVT_BREAKPOINT_CHANGE);
|
||||||
{
|
wxPostEvent(this->m_parent, evt);
|
||||||
// double-clicked on disassembly (operation and operand data)
|
break;
|
||||||
return;
|
}
|
||||||
}
|
case IDContextMenu_ToggleLoggingBreakpoint:
|
||||||
else
|
{
|
||||||
{
|
debugger_toggleLoggingBreakpoint(m_contextMenuAddress);
|
||||||
// comment
|
wxCommandEvent evt(wxEVT_BREAKPOINT_CHANGE);
|
||||||
return;
|
wxPostEvent(this->m_parent, evt);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case IDContextMenu_RestoreOriginalInstructions:
|
||||||
|
{
|
||||||
|
debugger_removePatch(m_contextMenuAddress);
|
||||||
|
wxCommandEvent evt(wxEVT_BREAKPOINT_CHANGE); // This also refreshes the disassembly view
|
||||||
|
wxPostEvent(this->m_parent, evt);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case IDContextMenu_CopyAddress:
|
||||||
|
{
|
||||||
|
CopyToClipboard(fmt::format("{:#10x}", m_contextMenuAddress));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case IDContextMenu_CopyUnrelocatedAddress:
|
||||||
|
{
|
||||||
|
uint32 unrelocatedAddress = GetUnrelocatedAddress(m_contextMenuAddress);
|
||||||
|
CopyToClipboard(fmt::format("{:#10x}", unrelocatedAddress));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
UNREACHABLE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -722,7 +772,6 @@ std::optional<MPTR> DisasmCtrl::LinePixelPosToAddress(sint32 posY)
|
||||||
if (posY < 0)
|
if (posY < 0)
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
|
|
||||||
|
|
||||||
sint32 lineIndex = posY / m_line_height;
|
sint32 lineIndex = posY / m_line_height;
|
||||||
if (lineIndex >= m_lineToAddress.size())
|
if (lineIndex >= m_lineToAddress.size())
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
|
@ -751,8 +800,6 @@ void DisasmCtrl::CenterOffset(uint32 offset)
|
||||||
|
|
||||||
m_active_line = line;
|
m_active_line = line;
|
||||||
RefreshLine(m_active_line);
|
RefreshLine(m_active_line);
|
||||||
|
|
||||||
debug_printf("scroll to %x\n", debuggerState.debugSession.instructionPointer);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DisasmCtrl::GoToAddressDialog()
|
void DisasmCtrl::GoToAddressDialog()
|
||||||
|
@ -765,6 +812,10 @@ void DisasmCtrl::GoToAddressDialog()
|
||||||
auto value = goto_dialog.GetValue().ToStdString();
|
auto value = goto_dialog.GetValue().ToStdString();
|
||||||
std::transform(value.begin(), value.end(), value.begin(), tolower);
|
std::transform(value.begin(), value.end(), value.begin(), tolower);
|
||||||
|
|
||||||
|
// trim any leading spaces
|
||||||
|
while(!value.empty() && value[0] == ' ')
|
||||||
|
value.erase(value.begin());
|
||||||
|
|
||||||
debugger_addParserSymbols(parser);
|
debugger_addParserSymbols(parser);
|
||||||
|
|
||||||
// try to parse expression as hex value first (it should interpret 1234 as 0x1234, not 1234)
|
// try to parse expression as hex value first (it should interpret 1234 as 0x1234, not 1234)
|
||||||
|
@ -773,17 +824,24 @@ void DisasmCtrl::GoToAddressDialog()
|
||||||
const auto result = (uint32)parser.Evaluate("0x"+value);
|
const auto result = (uint32)parser.Evaluate("0x"+value);
|
||||||
m_lastGotoTarget = result;
|
m_lastGotoTarget = result;
|
||||||
CenterOffset(result);
|
CenterOffset(result);
|
||||||
|
wxCommandEvent evt(wxEVT_DISASMCTRL_NOTIFY_GOTO_ADDRESS);
|
||||||
|
evt.SetExtraLong(static_cast<long>(result));
|
||||||
|
wxPostEvent(GetParent(), evt);
|
||||||
}
|
}
|
||||||
else if (parser.IsConstantExpression(value))
|
else if (parser.IsConstantExpression(value))
|
||||||
{
|
{
|
||||||
const auto result = (uint32)parser.Evaluate(value);
|
const auto result = (uint32)parser.Evaluate(value);
|
||||||
m_lastGotoTarget = result;
|
m_lastGotoTarget = result;
|
||||||
CenterOffset(result);
|
CenterOffset(result);
|
||||||
|
wxCommandEvent evt(wxEVT_DISASMCTRL_NOTIFY_GOTO_ADDRESS);
|
||||||
|
evt.SetExtraLong(static_cast<long>(result));
|
||||||
|
wxPostEvent(GetParent(), evt);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
// if not a constant expression (i.e. relying on unknown variables), then evaluating will throw an exception with a detailed error message
|
||||||
const auto _ = (uint32)parser.Evaluate(value);
|
const auto _ = (uint32)parser.Evaluate(value);
|
||||||
}
|
}
|
||||||
catch (const std::exception& ex)
|
catch (const std::exception& ex)
|
||||||
|
|
|
@ -1,9 +1,21 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "gui/components/TextList.h"
|
#include "gui/components/TextList.h"
|
||||||
|
|
||||||
|
wxDECLARE_EVENT(wxEVT_DISASMCTRL_NOTIFY_GOTO_ADDRESS, wxCommandEvent); // Notify parent that goto address operation completed. Event contains the address that was jumped to.
|
||||||
|
|
||||||
class DisasmCtrl : public TextList
|
class DisasmCtrl : public TextList
|
||||||
{
|
{
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
IDContextMenu_ToggleBreakpoint = wxID_HIGHEST + 1,
|
||||||
|
IDContextMenu_ToggleLoggingBreakpoint,
|
||||||
|
IDContextMenu_RestoreOriginalInstructions,
|
||||||
|
IDContextMenu_CopyAddress,
|
||||||
|
IDContextMenu_CopyUnrelocatedAddress,
|
||||||
|
IDContextMenu_Last
|
||||||
|
};
|
||||||
public:
|
public:
|
||||||
|
|
||||||
DisasmCtrl(wxWindow* parent, const wxWindowID& id, const wxPoint& pos, const wxSize& size, long style);
|
DisasmCtrl(wxWindow* parent, const wxWindowID& id, const wxPoint& pos, const wxSize& size, long style);
|
||||||
|
|
||||||
void Init();
|
void Init();
|
||||||
|
@ -26,6 +38,7 @@ protected:
|
||||||
void OnKeyPressed(sint32 key_code, const wxPoint& position) override;
|
void OnKeyPressed(sint32 key_code, const wxPoint& position) override;
|
||||||
void OnMouseDClick(const wxPoint& position, uint32 line) override;
|
void OnMouseDClick(const wxPoint& position, uint32 line) override;
|
||||||
void OnContextMenu(const wxPoint& position, uint32 line) override;
|
void OnContextMenu(const wxPoint& position, uint32 line) override;
|
||||||
|
void OnContextMenuEntryClicked(wxCommandEvent& event);
|
||||||
bool OnShowTooltip(const wxPoint& position, uint32 line) override;
|
bool OnShowTooltip(const wxPoint& position, uint32 line) override;
|
||||||
void ScrollWindow(int dx, int dy, const wxRect* prect) override;
|
void ScrollWindow(int dx, int dy, const wxRect* prect) override;
|
||||||
|
|
||||||
|
@ -40,6 +53,7 @@ private:
|
||||||
sint32 m_mouse_line, m_mouse_line_drawn;
|
sint32 m_mouse_line, m_mouse_line_drawn;
|
||||||
sint32 m_active_line;
|
sint32 m_active_line;
|
||||||
uint32 m_lastGotoTarget{};
|
uint32 m_lastGotoTarget{};
|
||||||
|
uint32 m_contextMenuAddress{};
|
||||||
// code region info
|
// code region info
|
||||||
uint32 currentCodeRegionStart;
|
uint32 currentCodeRegionStart;
|
||||||
uint32 currentCodeRegionEnd;
|
uint32 currentCodeRegionEnd;
|
||||||
|
|
|
@ -304,7 +304,7 @@ void SaveImportWindow::OnImport(wxCommandEvent& event)
|
||||||
auto new_node = info_node.append_child("account");
|
auto new_node = info_node.append_child("account");
|
||||||
new_node.append_attribute("persistentId").set_value(new_persistend_id_string.c_str());
|
new_node.append_attribute("persistentId").set_value(new_persistend_id_string.c_str());
|
||||||
auto timestamp = new_node.append_child("timestamp");
|
auto timestamp = new_node.append_child("timestamp");
|
||||||
timestamp.text().set(fmt::format("{:016x}", coreinit::coreinit_getOSTime() / ESPRESSO_TIMER_CLOCK).c_str()); // TODO time not initialized yet?
|
timestamp.text().set(fmt::format("{:016x}", coreinit::OSGetTime() / ESPRESSO_TIMER_CLOCK).c_str()); // TODO time not initialized yet?
|
||||||
|
|
||||||
if(!doc.save_file(saveinfo.c_str()))
|
if(!doc.save_file(saveinfo.c_str()))
|
||||||
cemuLog_log(LogType::Force, "couldn't insert save entry in saveinfo.xml: {}", _pathToUtf8(saveinfo));
|
cemuLog_log(LogType::Force, "couldn't insert save entry in saveinfo.xml: {}", _pathToUtf8(saveinfo));
|
||||||
|
|
|
@ -465,6 +465,15 @@ void ImGui_ImplVulkan_DestroyFontsTexture()
|
||||||
if (g_FontView) { vkDestroyImageView(v->Device, g_FontView, v->Allocator); g_FontView = VK_NULL_HANDLE; }
|
if (g_FontView) { vkDestroyImageView(v->Device, g_FontView, v->Allocator); g_FontView = VK_NULL_HANDLE; }
|
||||||
if (g_FontImage) { vkDestroyImage(v->Device, g_FontImage, v->Allocator); g_FontImage = VK_NULL_HANDLE; }
|
if (g_FontImage) { vkDestroyImage(v->Device, g_FontImage, v->Allocator); g_FontImage = VK_NULL_HANDLE; }
|
||||||
if (g_FontMemory) { vkFreeMemory(v->Device, g_FontMemory, v->Allocator); g_FontMemory = VK_NULL_HANDLE; }
|
if (g_FontMemory) { vkFreeMemory(v->Device, g_FontMemory, v->Allocator); g_FontMemory = VK_NULL_HANDLE; }
|
||||||
|
|
||||||
|
ImGuiIO& io = ImGui::GetIO();
|
||||||
|
auto texture = io.Fonts->TexID;
|
||||||
|
if(texture != (ImTextureID)nullptr)
|
||||||
|
{
|
||||||
|
ImGui_ImplVulkan_DeleteTexture(texture);
|
||||||
|
delete (ImGuiTexture*)texture;
|
||||||
|
io.Fonts->TexID = nullptr;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ImGui_ImplVulkan_CreateFontsTexture(VkCommandBuffer command_buffer)
|
bool ImGui_ImplVulkan_CreateFontsTexture(VkCommandBuffer command_buffer)
|
||||||
|
|
|
@ -78,9 +78,7 @@ bool DSUControllerProvider::connect()
|
||||||
using namespace boost::asio;
|
using namespace boost::asio;
|
||||||
|
|
||||||
ip::udp::resolver resolver(m_io_service);
|
ip::udp::resolver resolver(m_io_service);
|
||||||
const ip::udp::resolver::query query(ip::udp::v4(), get_settings().ip, fmt::format("{}", get_settings().port),
|
m_receiver_endpoint = *resolver.resolve(get_settings().ip, fmt::format("{}", get_settings().port)).cbegin();
|
||||||
ip::udp::resolver::query::canonical_name);
|
|
||||||
m_receiver_endpoint = *resolver.resolve(query);
|
|
||||||
|
|
||||||
if (m_socket.is_open())
|
if (m_socket.is_open())
|
||||||
m_socket.close();
|
m_socket.close();
|
||||||
|
|
|
@ -102,7 +102,7 @@ private:
|
||||||
std::condition_variable m_writer_cond;
|
std::condition_variable m_writer_cond;
|
||||||
|
|
||||||
uint32 m_uid;
|
uint32 m_uid;
|
||||||
boost::asio::io_service m_io_service;
|
boost::asio::io_context m_io_service;
|
||||||
boost::asio::ip::udp::endpoint m_receiver_endpoint;
|
boost::asio::ip::udp::endpoint m_receiver_endpoint;
|
||||||
boost::asio::ip::udp::socket m_socket;
|
boost::asio::ip::udp::socket m_socket;
|
||||||
|
|
||||||
|
|
|
@ -23,15 +23,15 @@ static bool AttemptSetNonBlock(int sockFd)
|
||||||
return fcntl(sockFd, F_SETFL, fcntl(sockFd, F_GETFL) | O_NONBLOCK) == 0;
|
return fcntl(sockFd, F_SETFL, fcntl(sockFd, F_GETFL) | O_NONBLOCK) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
L2CapWiimote::L2CapWiimote(int recvFd, int sendFd, bdaddr_t addr)
|
L2CapWiimote::L2CapWiimote(int controlFd, int dataFd, bdaddr_t addr)
|
||||||
: m_recvFd(recvFd), m_sendFd(sendFd), m_addr(addr)
|
: m_controlFd(controlFd), m_dataFd(dataFd), m_addr(addr)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
L2CapWiimote::~L2CapWiimote()
|
L2CapWiimote::~L2CapWiimote()
|
||||||
{
|
{
|
||||||
close(m_recvFd);
|
close(m_dataFd);
|
||||||
close(m_sendFd);
|
close(m_controlFd);
|
||||||
const auto& b = m_addr.b;
|
const auto& b = m_addr.b;
|
||||||
cemuLog_logDebug(LogType::Force, "Wiimote at {:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x} disconnected", b[5], b[4], b[3], b[2], b[1], b[0]);
|
cemuLog_logDebug(LogType::Force, "Wiimote at {:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x} disconnected", b[5], b[4], b[3], b[2], b[1], b[0]);
|
||||||
|
|
||||||
|
@ -61,51 +61,51 @@ std::vector<WiimoteDevicePtr> L2CapWiimote::get_devices()
|
||||||
std::vector<WiimoteDevicePtr> outDevices;
|
std::vector<WiimoteDevicePtr> outDevices;
|
||||||
for (const auto& addr : unconnected)
|
for (const auto& addr : unconnected)
|
||||||
{
|
{
|
||||||
// Socket for sending data to controller, PSM 0x11
|
// Control socket, PSM 0x11, needs to be open for the data socket to be opened
|
||||||
auto sendFd = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP);
|
auto controlFd = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP);
|
||||||
if (sendFd < 0)
|
if (controlFd < 0)
|
||||||
{
|
{
|
||||||
cemuLog_logDebug(LogType::Force, "Failed to open send socket: {}", strerror(errno));
|
cemuLog_logDebug(LogType::Force, "Failed to open control socket: {}", strerror(errno));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
sockaddr_l2 sendAddr{};
|
sockaddr_l2 controlAddr{};
|
||||||
sendAddr.l2_family = AF_BLUETOOTH;
|
controlAddr.l2_family = AF_BLUETOOTH;
|
||||||
sendAddr.l2_psm = htobs(0x11);
|
controlAddr.l2_psm = htobs(0x11);
|
||||||
sendAddr.l2_bdaddr = addr;
|
controlAddr.l2_bdaddr = addr;
|
||||||
|
|
||||||
if (!AttemptConnect(sendFd, sendAddr) || !AttemptSetNonBlock(sendFd))
|
if (!AttemptConnect(controlFd, controlAddr) || !AttemptSetNonBlock(controlFd))
|
||||||
{
|
{
|
||||||
const auto& b = addr.b;
|
const auto& b = addr.b;
|
||||||
cemuLog_logDebug(LogType::Force, "Failed to connect send socket to '{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}': {}",
|
cemuLog_logDebug(LogType::Force, "Failed to connect control socket to '{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}': {}",
|
||||||
b[5], b[4], b[3], b[2], b[1], b[0], strerror(errno));
|
b[5], b[4], b[3], b[2], b[1], b[0], strerror(errno));
|
||||||
close(sendFd);
|
close(controlFd);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Socket for receiving data from controller, PSM 0x13
|
// Socket for sending and receiving data from controller, PSM 0x13
|
||||||
auto recvFd = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP);
|
auto dataFd = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP);
|
||||||
if (recvFd < 0)
|
if (dataFd < 0)
|
||||||
{
|
{
|
||||||
cemuLog_logDebug(LogType::Force, "Failed to open recv socket: {}", strerror(errno));
|
cemuLog_logDebug(LogType::Force, "Failed to open data socket: {}", strerror(errno));
|
||||||
close(sendFd);
|
close(controlFd);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
sockaddr_l2 recvAddr{};
|
sockaddr_l2 dataAddr{};
|
||||||
recvAddr.l2_family = AF_BLUETOOTH;
|
dataAddr.l2_family = AF_BLUETOOTH;
|
||||||
recvAddr.l2_psm = htobs(0x13);
|
dataAddr.l2_psm = htobs(0x13);
|
||||||
recvAddr.l2_bdaddr = addr;
|
dataAddr.l2_bdaddr = addr;
|
||||||
|
|
||||||
if (!AttemptConnect(recvFd, recvAddr) || !AttemptSetNonBlock(recvFd))
|
if (!AttemptConnect(dataFd, dataAddr) || !AttemptSetNonBlock(dataFd))
|
||||||
{
|
{
|
||||||
const auto& b = addr.b;
|
const auto& b = addr.b;
|
||||||
cemuLog_logDebug(LogType::Force, "Failed to connect recv socket to '{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}': {}",
|
cemuLog_logDebug(LogType::Force, "Failed to connect data socket to '{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}': {}",
|
||||||
b[5], b[4], b[3], b[2], b[1], b[0], strerror(errno));
|
b[5], b[4], b[3], b[2], b[1], b[0], strerror(errno));
|
||||||
close(sendFd);
|
close(dataFd);
|
||||||
close(recvFd);
|
close(controlFd);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
outDevices.emplace_back(std::make_shared<L2CapWiimote>(sendFd, recvFd, addr));
|
outDevices.emplace_back(std::make_shared<L2CapWiimote>(controlFd, dataFd, addr));
|
||||||
|
|
||||||
s_addressMutex.lock();
|
s_addressMutex.lock();
|
||||||
s_addresses[addr] = true;
|
s_addresses[addr] = true;
|
||||||
|
@ -123,13 +123,13 @@ bool L2CapWiimote::write_data(const std::vector<uint8>& data)
|
||||||
buffer[0] = 0xA2;
|
buffer[0] = 0xA2;
|
||||||
std::memcpy(buffer + 1, data.data(), size);
|
std::memcpy(buffer + 1, data.data(), size);
|
||||||
const auto outSize = size + 1;
|
const auto outSize = size + 1;
|
||||||
return send(m_sendFd, buffer, outSize, 0) == outSize;
|
return send(m_dataFd, buffer, outSize, 0) == outSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<std::vector<uint8>> L2CapWiimote::read_data()
|
std::optional<std::vector<uint8>> L2CapWiimote::read_data()
|
||||||
{
|
{
|
||||||
uint8 buffer[23];
|
uint8 buffer[23];
|
||||||
const auto nBytes = recv(m_sendFd, buffer, 23, 0);
|
const auto nBytes = recv(m_dataFd, buffer, 23, 0);
|
||||||
|
|
||||||
if (nBytes < 0 && errno == EWOULDBLOCK)
|
if (nBytes < 0 && errno == EWOULDBLOCK)
|
||||||
return std::vector<uint8>{};
|
return std::vector<uint8>{};
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
class L2CapWiimote : public WiimoteDevice
|
class L2CapWiimote : public WiimoteDevice
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
L2CapWiimote(int recvFd, int sendFd, bdaddr_t addr);
|
L2CapWiimote(int controlFd, int dataFd, bdaddr_t addr);
|
||||||
~L2CapWiimote() override;
|
~L2CapWiimote() override;
|
||||||
|
|
||||||
bool write_data(const std::vector<uint8>& data) override;
|
bool write_data(const std::vector<uint8>& data) override;
|
||||||
|
@ -15,8 +15,8 @@ class L2CapWiimote : public WiimoteDevice
|
||||||
static void AddCandidateAddress(bdaddr_t addr);
|
static void AddCandidateAddress(bdaddr_t addr);
|
||||||
static std::vector<WiimoteDevicePtr> get_devices();
|
static std::vector<WiimoteDevicePtr> get_devices();
|
||||||
private:
|
private:
|
||||||
int m_recvFd;
|
int m_controlFd;
|
||||||
int m_sendFd;
|
int m_dataFd;
|
||||||
bdaddr_t m_addr;
|
bdaddr_t m_addr;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
add_library(CemuUtil
|
add_library(CemuUtil
|
||||||
boost/bluetooth.h
|
boost/bluetooth.h
|
||||||
|
bootSound/BootSoundReader.cpp
|
||||||
|
bootSound/BootSoundReader.h
|
||||||
ChunkedHeap/ChunkedHeap.h
|
ChunkedHeap/ChunkedHeap.h
|
||||||
containers/flat_hash_map.hpp
|
containers/flat_hash_map.hpp
|
||||||
containers/IntervalBucketContainer.h
|
containers/IntervalBucketContainer.h
|
||||||
|
|
|
@ -1,35 +1,39 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <util/helpers/MemoryPool.h>
|
||||||
|
|
||||||
struct CHAddr
|
struct CHAddr
|
||||||
{
|
{
|
||||||
uint32 offset;
|
uint32 offset;
|
||||||
uint32 chunkIndex;
|
uint32 chunkIndex;
|
||||||
|
void* internal; // AllocRange
|
||||||
|
|
||||||
CHAddr(uint32 _offset, uint32 _chunkIndex) : offset(_offset), chunkIndex(_chunkIndex) {};
|
CHAddr(uint32 _offset, uint32 _chunkIndex, void* internal = nullptr) : offset(_offset), chunkIndex(_chunkIndex), internal(internal) {};
|
||||||
CHAddr() : offset(0xFFFFFFFF), chunkIndex(0xFFFFFFFF) {};
|
CHAddr() : offset(0xFFFFFFFF), chunkIndex(0xFFFFFFFF) {};
|
||||||
|
|
||||||
bool isValid() { return chunkIndex != 0xFFFFFFFF; };
|
bool isValid() { return chunkIndex != 0xFFFFFFFF; };
|
||||||
static CHAddr getInvalid() { return CHAddr(0xFFFFFFFF, 0xFFFFFFFF); };
|
static CHAddr getInvalid() { return CHAddr(0xFFFFFFFF, 0xFFFFFFFF); };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template<uint32 TMinimumAlignment = 32>
|
||||||
class ChunkedHeap
|
class ChunkedHeap
|
||||||
{
|
{
|
||||||
struct allocRange_t
|
struct AllocRange
|
||||||
{
|
{
|
||||||
allocRange_t* nextFree{};
|
AllocRange* nextFree{};
|
||||||
allocRange_t* prevFree{};
|
AllocRange* prevFree{};
|
||||||
allocRange_t* prevOrdered{};
|
AllocRange* prevOrdered{};
|
||||||
allocRange_t* nextOrdered{};
|
AllocRange* nextOrdered{};
|
||||||
uint32 offset;
|
uint32 offset;
|
||||||
uint32 chunkIndex;
|
uint32 chunkIndex;
|
||||||
uint32 size;
|
uint32 size;
|
||||||
bool isFree;
|
bool isFree;
|
||||||
allocRange_t(uint32 _offset, uint32 _chunkIndex, uint32 _size, bool _isFree) : offset(_offset), chunkIndex(_chunkIndex), size(_size), isFree(_isFree), nextFree(nullptr) {};
|
AllocRange(uint32 _offset, uint32 _chunkIndex, uint32 _size, bool _isFree) : offset(_offset), chunkIndex(_chunkIndex), size(_size), isFree(_isFree), nextFree(nullptr) {};
|
||||||
};
|
};
|
||||||
|
|
||||||
struct chunk_t
|
struct Chunk
|
||||||
{
|
{
|
||||||
std::unordered_map<uint32, allocRange_t*> map_allocatedRange;
|
uint32 size;
|
||||||
};
|
};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
@ -47,45 +51,32 @@ public:
|
||||||
_free(addr);
|
_free(addr);
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual uint32 allocateNewChunk(uint32 chunkIndex, uint32 minimumAllocationSize)
|
virtual uint32 allocateNewChunk(uint32 chunkIndex, uint32 minimumAllocationSize) = 0;
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
unsigned ulog2(uint32 v)
|
unsigned ulog2(uint32 v)
|
||||||
{
|
{
|
||||||
static const unsigned MUL_DE_BRUIJN_BIT[] =
|
cemu_assert_debug(v != 0);
|
||||||
{
|
return 31 - std::countl_zero(v);
|
||||||
0, 9, 1, 10, 13, 21, 2, 29, 11, 14, 16, 18, 22, 25, 3, 30,
|
|
||||||
8, 12, 20, 28, 15, 17, 24, 7, 19, 27, 23, 6, 26, 5, 4, 31
|
|
||||||
};
|
|
||||||
|
|
||||||
v |= v >> 1;
|
|
||||||
v |= v >> 2;
|
|
||||||
v |= v >> 4;
|
|
||||||
v |= v >> 8;
|
|
||||||
v |= v >> 16;
|
|
||||||
|
|
||||||
return MUL_DE_BRUIJN_BIT[(v * 0x07C4ACDDu) >> 27];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void trackFreeRange(allocRange_t* range)
|
void trackFreeRange(AllocRange* range)
|
||||||
{
|
{
|
||||||
// get index of msb
|
// get index of msb
|
||||||
cemu_assert_debug(range->size != 0); // size of zero is not allowed
|
cemu_assert_debug(range->size != 0); // size of zero is not allowed
|
||||||
uint32 bucketIndex = ulog2(range->size);
|
uint32 bucketIndex = ulog2(range->size);
|
||||||
range->nextFree = bucketFreeRange[bucketIndex];
|
range->nextFree = m_bucketFreeRange[bucketIndex];
|
||||||
if (bucketFreeRange[bucketIndex])
|
if (m_bucketFreeRange[bucketIndex])
|
||||||
bucketFreeRange[bucketIndex]->prevFree = range;
|
m_bucketFreeRange[bucketIndex]->prevFree = range;
|
||||||
range->prevFree = nullptr;
|
range->prevFree = nullptr;
|
||||||
bucketFreeRange[bucketIndex] = range;
|
m_bucketFreeRange[bucketIndex] = range;
|
||||||
|
m_bucketUseMask |= (1u << bucketIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
void forgetFreeRange(allocRange_t* range, uint32 bucketIndex)
|
void forgetFreeRange(AllocRange* range, uint32 bucketIndex)
|
||||||
{
|
{
|
||||||
allocRange_t* prevRange = range->prevFree;
|
AllocRange* prevRange = range->prevFree;
|
||||||
allocRange_t* nextRange = range->nextFree;
|
AllocRange* nextRange = range->nextFree;
|
||||||
if (prevRange)
|
if (prevRange)
|
||||||
{
|
{
|
||||||
prevRange->nextFree = nextRange;
|
prevRange->nextFree = nextRange;
|
||||||
|
@ -94,36 +85,42 @@ private:
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (bucketFreeRange[bucketIndex] != range)
|
cemu_assert_debug(m_bucketFreeRange[bucketIndex] == range);
|
||||||
assert_dbg();
|
m_bucketFreeRange[bucketIndex] = nextRange;
|
||||||
bucketFreeRange[bucketIndex] = nextRange;
|
|
||||||
if (nextRange)
|
if (nextRange)
|
||||||
nextRange->prevFree = nullptr;
|
nextRange->prevFree = nullptr;
|
||||||
|
else
|
||||||
|
m_bucketUseMask &= ~(1u << bucketIndex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool allocateChunk(uint32 minimumAllocationSize)
|
bool allocateChunk(uint32 minimumAllocationSize)
|
||||||
{
|
{
|
||||||
uint32 chunkIndex = (uint32)list_chunks.size();
|
uint32 chunkIndex = (uint32)m_chunks.size();
|
||||||
list_chunks.emplace_back(new chunk_t());
|
m_chunks.emplace_back();
|
||||||
uint32 chunkSize = allocateNewChunk(chunkIndex, minimumAllocationSize);
|
uint32 chunkSize = allocateNewChunk(chunkIndex, minimumAllocationSize);
|
||||||
|
cemu_assert_debug((chunkSize%TMinimumAlignment) == 0); // chunk size should be a multiple of the minimum alignment
|
||||||
if (chunkSize == 0)
|
if (chunkSize == 0)
|
||||||
return false;
|
return false;
|
||||||
allocRange_t* range = new allocRange_t(0, chunkIndex, chunkSize, true);
|
cemu_assert_debug(chunkSize < 0x80000000u); // chunk size must be below 2GB
|
||||||
|
AllocRange* range = m_allocEntriesPool.allocObj(0, chunkIndex, chunkSize, true);
|
||||||
trackFreeRange(range);
|
trackFreeRange(range);
|
||||||
numHeapBytes += chunkSize;
|
m_numHeapBytes += chunkSize;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void _allocFrom(allocRange_t* range, uint32 bucketIndex, uint32 allocOffset, uint32 allocSize)
|
void _allocFrom(AllocRange* range, uint32 bucketIndex, uint32 allocOffset, uint32 allocSize)
|
||||||
{
|
{
|
||||||
|
cemu_assert_debug(allocSize > 0);
|
||||||
// remove the range from the chain of free ranges
|
// remove the range from the chain of free ranges
|
||||||
forgetFreeRange(range, bucketIndex);
|
forgetFreeRange(range, bucketIndex);
|
||||||
// split head, allocation and tail into separate ranges
|
// split head, allocation and tail into separate ranges
|
||||||
if (allocOffset > range->offset)
|
uint32 headBytes = allocOffset - range->offset;
|
||||||
|
if (headBytes > 0)
|
||||||
{
|
{
|
||||||
// alignment padding -> create free range
|
// alignment padding -> create free range
|
||||||
allocRange_t* head = new allocRange_t(range->offset, range->chunkIndex, allocOffset - range->offset, true);
|
cemu_assert_debug(headBytes >= TMinimumAlignment);
|
||||||
|
AllocRange* head = m_allocEntriesPool.allocObj(range->offset, range->chunkIndex, headBytes, true);
|
||||||
trackFreeRange(head);
|
trackFreeRange(head);
|
||||||
if (range->prevOrdered)
|
if (range->prevOrdered)
|
||||||
range->prevOrdered->nextOrdered = head;
|
range->prevOrdered->nextOrdered = head;
|
||||||
|
@ -131,10 +128,12 @@ private:
|
||||||
head->nextOrdered = range;
|
head->nextOrdered = range;
|
||||||
range->prevOrdered = head;
|
range->prevOrdered = head;
|
||||||
}
|
}
|
||||||
if ((allocOffset + allocSize) < (range->offset + range->size)) // todo - create only if it's more than a couple of bytes?
|
uint32 tailBytes = (range->offset + range->size) - (allocOffset + allocSize);
|
||||||
|
if (tailBytes > 0)
|
||||||
{
|
{
|
||||||
// tail -> create free range
|
// tail -> create free range
|
||||||
allocRange_t* tail = new allocRange_t((allocOffset + allocSize), range->chunkIndex, (range->offset + range->size) - (allocOffset + allocSize), true);
|
cemu_assert_debug(tailBytes >= TMinimumAlignment);
|
||||||
|
AllocRange* tail = m_allocEntriesPool.allocObj((allocOffset + allocSize), range->chunkIndex, tailBytes, true);
|
||||||
trackFreeRange(tail);
|
trackFreeRange(tail);
|
||||||
if (range->nextOrdered)
|
if (range->nextOrdered)
|
||||||
range->nextOrdered->prevOrdered = tail;
|
range->nextOrdered->prevOrdered = tail;
|
||||||
|
@ -149,36 +148,51 @@ private:
|
||||||
|
|
||||||
CHAddr _alloc(uint32 size, uint32 alignment)
|
CHAddr _alloc(uint32 size, uint32 alignment)
|
||||||
{
|
{
|
||||||
|
cemu_assert_debug(size <= (0x7FFFFFFFu-TMinimumAlignment));
|
||||||
|
// make sure size is not zero and align it
|
||||||
|
if(size == 0) [[unlikely]]
|
||||||
|
size = TMinimumAlignment;
|
||||||
|
else
|
||||||
|
size = (size + (TMinimumAlignment - 1)) & ~(TMinimumAlignment - 1);
|
||||||
// find smallest bucket to scan
|
// find smallest bucket to scan
|
||||||
uint32 alignmentM1 = alignment - 1;
|
uint32 alignmentM1 = alignment - 1;
|
||||||
uint32 bucketIndex = ulog2(size);
|
uint32 bucketIndex = ulog2(size);
|
||||||
while (bucketIndex < 32)
|
// check if the bucket is available
|
||||||
|
if( !(m_bucketUseMask & (1u << bucketIndex)) )
|
||||||
{
|
{
|
||||||
allocRange_t* range = bucketFreeRange[bucketIndex];
|
// skip to next non-empty bucket
|
||||||
|
uint32 nextIndex = BSF(m_bucketUseMask>>bucketIndex);
|
||||||
|
bucketIndex += nextIndex;
|
||||||
|
}
|
||||||
|
while (bucketIndex < 31)
|
||||||
|
{
|
||||||
|
AllocRange* range = m_bucketFreeRange[bucketIndex];
|
||||||
while (range)
|
while (range)
|
||||||
{
|
{
|
||||||
if (range->size >= size)
|
if (range->size >= size)
|
||||||
{
|
{
|
||||||
// verify if aligned allocation fits
|
// verify if aligned allocation fits
|
||||||
uint32 alignedOffset = (range->offset + alignmentM1) & ~alignmentM1;
|
uint32 alignedOffset = (range->offset + alignmentM1) & ~alignmentM1;
|
||||||
uint32 alignmentLoss = alignedOffset - range->offset;
|
uint32 endOffset = alignedOffset + size;
|
||||||
if (alignmentLoss < range->size && (range->size - alignmentLoss) >= size)
|
if((range->offset+range->size) >= endOffset)
|
||||||
{
|
{
|
||||||
_allocFrom(range, bucketIndex, alignedOffset, size);
|
_allocFrom(range, bucketIndex, alignedOffset, size);
|
||||||
list_chunks[range->chunkIndex]->map_allocatedRange.emplace(alignedOffset, range);
|
m_numAllocatedBytes += size;
|
||||||
numAllocatedBytes += size;
|
return CHAddr(alignedOffset, range->chunkIndex, range);
|
||||||
return CHAddr(alignedOffset, range->chunkIndex);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
range = range->nextFree;
|
range = range->nextFree;
|
||||||
}
|
}
|
||||||
bucketIndex++; // try higher bucket
|
// check next non-empty bucket or skip to end
|
||||||
|
bucketIndex++;
|
||||||
|
uint32 emptyBuckets = BSF(m_bucketUseMask>>bucketIndex);
|
||||||
|
bucketIndex += emptyBuckets;
|
||||||
}
|
}
|
||||||
if(allocationLimitReached)
|
if(m_allocationLimitReached)
|
||||||
return CHAddr(0xFFFFFFFF, 0xFFFFFFFF);
|
return CHAddr(0xFFFFFFFF, 0xFFFFFFFF);
|
||||||
if (!allocateChunk(size))
|
if (!allocateChunk(size))
|
||||||
{
|
{
|
||||||
allocationLimitReached = true;
|
m_allocationLimitReached = true;
|
||||||
return CHAddr(0xFFFFFFFF, 0xFFFFFFFF);
|
return CHAddr(0xFFFFFFFF, 0xFFFFFFFF);
|
||||||
}
|
}
|
||||||
return _alloc(size, alignment);
|
return _alloc(size, alignment);
|
||||||
|
@ -186,24 +200,16 @@ private:
|
||||||
|
|
||||||
void _free(CHAddr addr)
|
void _free(CHAddr addr)
|
||||||
{
|
{
|
||||||
auto it = list_chunks[addr.chunkIndex]->map_allocatedRange.find(addr.offset);
|
if(!addr.internal)
|
||||||
if (it == list_chunks[addr.chunkIndex]->map_allocatedRange.end())
|
|
||||||
{
|
{
|
||||||
cemuLog_log(LogType::Force, "Internal heap error. {:08x} {:08x}", addr.chunkIndex, addr.offset);
|
cemuLog_log(LogType::Force, "Internal heap error. {:08x} {:08x}", addr.chunkIndex, addr.offset);
|
||||||
cemuLog_log(LogType::Force, "Debug info:");
|
|
||||||
for (auto& rangeItr : list_chunks[addr.chunkIndex]->map_allocatedRange)
|
|
||||||
{
|
|
||||||
cemuLog_log(LogType::Force, "{:08x} {:08x}", rangeItr.second->offset, rangeItr.second->size);
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
AllocRange* range = (AllocRange*)addr.internal;
|
||||||
allocRange_t* range = it->second;
|
m_numAllocatedBytes -= range->size;
|
||||||
numAllocatedBytes -= it->second->size;
|
|
||||||
list_chunks[range->chunkIndex]->map_allocatedRange.erase(it);
|
|
||||||
// try merge left or right
|
// try merge left or right
|
||||||
allocRange_t* prevRange = range->prevOrdered;
|
AllocRange* prevRange = range->prevOrdered;
|
||||||
allocRange_t* nextRange = range->nextOrdered;
|
AllocRange* nextRange = range->nextOrdered;
|
||||||
if (prevRange && prevRange->isFree)
|
if (prevRange && prevRange->isFree)
|
||||||
{
|
{
|
||||||
if (nextRange && nextRange->isFree)
|
if (nextRange && nextRange->isFree)
|
||||||
|
@ -216,8 +222,8 @@ private:
|
||||||
forgetFreeRange(prevRange, ulog2(prevRange->size));
|
forgetFreeRange(prevRange, ulog2(prevRange->size));
|
||||||
prevRange->size = newSize;
|
prevRange->size = newSize;
|
||||||
trackFreeRange(prevRange);
|
trackFreeRange(prevRange);
|
||||||
delete range;
|
m_allocEntriesPool.freeObj(range);
|
||||||
delete nextRange;
|
m_allocEntriesPool.freeObj(nextRange);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -228,7 +234,7 @@ private:
|
||||||
forgetFreeRange(prevRange, ulog2(prevRange->size));
|
forgetFreeRange(prevRange, ulog2(prevRange->size));
|
||||||
prevRange->size = newSize;
|
prevRange->size = newSize;
|
||||||
trackFreeRange(prevRange);
|
trackFreeRange(prevRange);
|
||||||
delete range;
|
m_allocEntriesPool.freeObj(range);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (nextRange && nextRange->isFree)
|
else if (nextRange && nextRange->isFree)
|
||||||
|
@ -242,7 +248,7 @@ private:
|
||||||
range->prevOrdered->nextOrdered = nextRange;
|
range->prevOrdered->nextOrdered = nextRange;
|
||||||
nextRange->prevOrdered = range->prevOrdered;
|
nextRange->prevOrdered = range->prevOrdered;
|
||||||
trackFreeRange(nextRange);
|
trackFreeRange(nextRange);
|
||||||
delete range;
|
m_allocEntriesPool.freeObj(range);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -265,7 +271,7 @@ private:
|
||||||
|
|
||||||
for (uint32 i = 0; i < 32; i++)
|
for (uint32 i = 0; i < 32; i++)
|
||||||
{
|
{
|
||||||
allocRange_t* ar = bucketFreeRange[i];
|
AllocRange* ar = m_bucketFreeRange[i];
|
||||||
while (ar)
|
while (ar)
|
||||||
{
|
{
|
||||||
availableRange_t dbgRange;
|
availableRange_t dbgRange;
|
||||||
|
@ -278,7 +284,7 @@ private:
|
||||||
if (itr.chunkIndex != dbgRange.chunkIndex)
|
if (itr.chunkIndex != dbgRange.chunkIndex)
|
||||||
continue;
|
continue;
|
||||||
if (itr.offset < (dbgRange.offset + dbgRange.size) && (itr.offset + itr.size) >(dbgRange.offset))
|
if (itr.offset < (dbgRange.offset + dbgRange.size) && (itr.offset + itr.size) >(dbgRange.offset))
|
||||||
assert_dbg();
|
cemu_assert_error();
|
||||||
}
|
}
|
||||||
|
|
||||||
availRanges.emplace_back(dbgRange);
|
availRanges.emplace_back(dbgRange);
|
||||||
|
@ -290,14 +296,16 @@ private:
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::vector<chunk_t*> list_chunks;
|
std::vector<Chunk> m_chunks;
|
||||||
allocRange_t* bucketFreeRange[32]{};
|
uint32 m_bucketUseMask{0x80000000}; // bitmask indicating non-empty buckets. MSB always set to provide an upper bound for BSF instruction
|
||||||
bool allocationLimitReached = false;
|
AllocRange* m_bucketFreeRange[32]{}; // we are only using 31 entries since the MSB is reserved (thus chunks equal or larger than 2^31 are not allowed)
|
||||||
|
bool m_allocationLimitReached = false;
|
||||||
|
MemoryPool<AllocRange> m_allocEntriesPool{64};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
// statistics
|
// statistics
|
||||||
uint32 numHeapBytes{}; // total size of the heap
|
uint32 m_numHeapBytes{}; // total size of the heap
|
||||||
uint32 numAllocatedBytes{};
|
uint32 m_numAllocatedBytes{};
|
||||||
};
|
};
|
||||||
|
|
||||||
class VGenericHeap
|
class VGenericHeap
|
||||||
|
|
51
src/util/bootSound/BootSoundReader.cpp
Normal file
51
src/util/bootSound/BootSoundReader.cpp
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
#include "BootSoundReader.h"
|
||||||
|
#include "Cafe/CafeSystem.h"
|
||||||
|
|
||||||
|
BootSoundReader::BootSoundReader(FSCVirtualFile* bootsndFile, sint32 blockSize) : bootsndFile(bootsndFile), blockSize(blockSize)
|
||||||
|
{
|
||||||
|
// crash if this constructor is invoked with a blockSize that has a different number of samples per channel
|
||||||
|
cemu_assert(blockSize % (sizeof(sint16be) * 2) == 0);
|
||||||
|
|
||||||
|
fsc_setFileSeek(bootsndFile, 0);
|
||||||
|
fsc_readFile(bootsndFile, &muteBits, 4);
|
||||||
|
fsc_readFile(bootsndFile, &loopPoint, 4);
|
||||||
|
|
||||||
|
buffer.resize(blockSize / sizeof(sint16));
|
||||||
|
bufferBE.resize(blockSize / sizeof(sint16be));
|
||||||
|
|
||||||
|
// workaround: SM3DW has incorrect loop point
|
||||||
|
const auto titleId = CafeSystem::GetForegroundTitleId();
|
||||||
|
if(titleId == 0x0005000010145D00 || titleId == 0x0005000010145C00 || titleId == 0x0005000010106100)
|
||||||
|
loopPoint = 113074;
|
||||||
|
}
|
||||||
|
|
||||||
|
sint16* BootSoundReader::getSamples()
|
||||||
|
{
|
||||||
|
size_t totalRead = 0;
|
||||||
|
const size_t loopPointOffset = 8 + loopPoint * 4;
|
||||||
|
while (totalRead < blockSize)
|
||||||
|
{
|
||||||
|
auto read = fsc_readFile(bootsndFile, bufferBE.data(), blockSize - totalRead);
|
||||||
|
if (read == 0)
|
||||||
|
{
|
||||||
|
cemuLog_log(LogType::Force, "failed to read PCM samples from bootSound.btsnd");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
if (read % (sizeof(sint16be) * 2) != 0)
|
||||||
|
{
|
||||||
|
cemuLog_log(LogType::Force, "failed to play bootSound.btsnd: reading PCM data stopped at an odd number of samples (is the file corrupt?)");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::copy_n(bufferBE.begin(), read / sizeof(sint16be), buffer.begin() + (totalRead / sizeof(sint16)));
|
||||||
|
totalRead += read;
|
||||||
|
if (totalRead < blockSize)
|
||||||
|
fsc_setFileSeek(bootsndFile, loopPointOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle case where the end of a block of samples lines up with the end of the file
|
||||||
|
if(fsc_getFileSeek(bootsndFile) == fsc_getFileSize(bootsndFile))
|
||||||
|
fsc_setFileSeek(bootsndFile, loopPointOffset);
|
||||||
|
|
||||||
|
return buffer.data();
|
||||||
|
}
|
20
src/util/bootSound/BootSoundReader.h
Normal file
20
src/util/bootSound/BootSoundReader.h
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
#pragma once
|
||||||
|
#include "Cafe/Filesystem/fsc.h"
|
||||||
|
|
||||||
|
class BootSoundReader
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
BootSoundReader() = delete;
|
||||||
|
BootSoundReader(FSCVirtualFile* bootsndFile, sint32 blockSize);
|
||||||
|
|
||||||
|
sint16* getSamples();
|
||||||
|
|
||||||
|
private:
|
||||||
|
FSCVirtualFile* bootsndFile{};
|
||||||
|
sint32 blockSize{};
|
||||||
|
|
||||||
|
uint32be muteBits{};
|
||||||
|
uint32be loopPoint{};
|
||||||
|
std::vector<sint16> buffer{};
|
||||||
|
std::vector<sint16be> bufferBE{};
|
||||||
|
};
|
Loading…
Add table
Reference in a new issue