Compare commits

..

No commits in common. "main" and "v2.4" have entirely different histories.
main ... v2.4

130 changed files with 1856 additions and 2787 deletions

View file

@ -39,7 +39,7 @@ jobs:
- name: "Install system dependencies"
run: |
sudo apt update -qq
sudo apt install -y clang-15 cmake freeglut3-dev libgcrypt20-dev libglm-dev libgtk-3-dev libpulse-dev libsecret-1-dev libsystemd-dev libudev-dev nasm ninja-build libbluetooth-dev
sudo apt install -y clang-15 cmake freeglut3-dev libgcrypt20-dev libglm-dev libgtk-3-dev libpulse-dev libsecret-1-dev libsystemd-dev libudev-dev nasm ninja-build
- name: "Setup cmake"
uses: jwlawson/actions-setup-cmake@v2
@ -96,7 +96,7 @@ jobs:
- name: "Install system dependencies"
run: |
sudo apt update -qq
sudo apt install -y clang-15 cmake freeglut3-dev libgcrypt20-dev libglm-dev libgtk-3-dev libpulse-dev libsecret-1-dev libsystemd-dev nasm ninja-build appstream libbluetooth-dev
sudo apt install -y clang-15 cmake freeglut3-dev libgcrypt20-dev libglm-dev libgtk-3-dev libpulse-dev libsecret-1-dev libsystemd-dev nasm ninja-build appstream
- name: "Build AppImage"
run: |

View file

@ -35,7 +35,7 @@ jobs:
-o cemu.pot
- name: Upload artifact
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v3
with:
name: POT file
path: ./cemu.pot

View file

@ -46,10 +46,10 @@ To compile Cemu, a recent enough compiler and STL with C++20 support is required
### Dependencies
#### For Arch and derivatives:
`sudo pacman -S --needed base-devel bluez-libs clang cmake freeglut git glm gtk3 libgcrypt libpulse libsecret linux-headers llvm nasm ninja systemd unzip zip`
`sudo pacman -S --needed base-devel clang cmake freeglut git glm gtk3 libgcrypt libpulse libsecret linux-headers llvm nasm ninja systemd unzip zip`
#### For Debian, Ubuntu and derivatives:
`sudo apt install -y cmake curl clang-15 freeglut3-dev git libbluetooth-dev libgcrypt20-dev libglm-dev libgtk-3-dev libpulse-dev libsecret-1-dev libsystemd-dev libtool nasm ninja-build`
`sudo apt install -y cmake curl clang-15 freeglut3-dev git libgcrypt20-dev libglm-dev libgtk-3-dev libpulse-dev libsecret-1-dev libsystemd-dev libtool nasm ninja-build`
You may also need to install `libusb-1.0-0-dev` as a workaround for an issue with the vcpkg hidapi package.
@ -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`
#### For Fedora and derivatives:
`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`
`sudo dnf install 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`
### Build Cemu
@ -120,9 +120,6 @@ This section refers to running `cmake -S...` (truncated).
* Compiling failed during rebuild after `git pull` with an error that mentions RPATH
* Add the following and try running the command again:
* `-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.
@ -192,41 +189,3 @@ 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.
## 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 |

View file

@ -2,7 +2,6 @@ cmake_minimum_required(VERSION 3.21.1)
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(ALLOW_PORTABLE "Allow Cemu to be run in portable mode" ON)
# used by CI script to set version:
set(EMULATOR_VERSION_MAJOR "0" CACHE STRING "")
@ -99,7 +98,6 @@ endif()
if (UNIX AND NOT APPLE)
option(ENABLE_WAYLAND "Build with Wayland support" ON)
option(ENABLE_FERAL_GAMEMODE "Enables Feral Interactive GameMode Support" ON)
option(ENABLE_BLUEZ "Build with Bluez support" ON)
endif()
option(ENABLE_OPENGL "Enables the OpenGL backend" ON)
@ -124,6 +122,23 @@ if (WIN32)
endif()
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)
set(THREADS_PREFER_PTHREAD_FLAG true)
@ -164,12 +179,6 @@ if (UNIX AND NOT APPLE)
endif()
find_package(GTK3 REQUIRED)
if(ENABLE_BLUEZ)
find_package(bluez REQUIRED)
set(ENABLE_WIIMOTE ON)
add_compile_definitions(HAS_BLUEZ)
endif()
endif()
if (ENABLE_VULKAN)

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -1,20 +0,0 @@
# SPDX-FileCopyrightText: 2022 Andrea Pappacoda <andrea@pappacoda.it>
# SPDX-License-Identifier: ISC
find_package(bluez CONFIG)
if (NOT bluez_FOUND)
find_package(PkgConfig)
if (PKG_CONFIG_FOUND)
pkg_search_module(bluez IMPORTED_TARGET GLOBAL bluez-1.0 bluez)
if (bluez_FOUND)
add_library(bluez::bluez ALIAS PkgConfig::bluez)
endif ()
endif ()
endif ()
find_package_handle_standard_args(bluez
REQUIRED_VARS
bluez_LINK_LIBRARIES
bluez_FOUND
VERSION_VAR bluez_VERSION
)

View file

@ -82,8 +82,8 @@ if (MACOS_BUNDLE)
set(MACOSX_BUNDLE_ICON_FILE "cemu.icns")
set(MACOSX_BUNDLE_GUI_IDENTIFIER "info.cemu.Cemu")
set(MACOSX_BUNDLE_BUNDLE_NAME "Cemu")
set(MACOSX_BUNDLE_SHORT_VERSION_STRING "${EMULATOR_VERSION_MAJOR}.${EMULATOR_VERSION_MINOR}.${EMULATOR_VERSION_PATCH}")
set(MACOSX_BUNDLE_BUNDLE_VERSION "${EMULATOR_VERSION_MAJOR}.${EMULATOR_VERSION_MINOR}.${EMULATOR_VERSION_PATCH}")
set(MACOSX_BUNDLE_SHORT_VERSION_STRING ${CMAKE_PROJECT_VERSION})
set(MACOSX_BUNDLE_BUNDLE_VERSION ${CMAKE_PROJECT_VERSION})
set(MACOSX_BUNDLE_COPYRIGHT "Copyright © 2024 Cemu Project")
set(MACOSX_BUNDLE_CATEGORY "public.app-category.games")

View file

@ -463,6 +463,8 @@ add_library(CemuCafe
OS/libs/nsyshid/BackendEmulated.h
OS/libs/nsyshid/BackendLibusb.cpp
OS/libs/nsyshid/BackendLibusb.h
OS/libs/nsyshid/BackendWindowsHID.cpp
OS/libs/nsyshid/BackendWindowsHID.h
OS/libs/nsyshid/Dimensions.cpp
OS/libs/nsyshid/Dimensions.h
OS/libs/nsyshid/Infinity.cpp
@ -530,12 +532,6 @@ set_property(TARGET CemuCafe PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CON
target_include_directories(CemuCafe PUBLIC "../")
if (glslang_VERSION VERSION_LESS "15.0.0")
set(glslang_target "glslang::SPIRV")
else()
set(glslang_target "glslang")
endif()
target_link_libraries(CemuCafe PRIVATE
CemuAsm
CemuAudio
@ -551,7 +547,7 @@ target_link_libraries(CemuCafe PRIVATE
Boost::nowide
CURL::libcurl
fmt::fmt
${glslang_target}
glslang::SPIRV
ih264d
OpenSSL::Crypto
OpenSSL::SSL
@ -567,10 +563,8 @@ if (ENABLE_WAYLAND)
target_link_libraries(CemuCafe PUBLIC Wayland::Client)
endif()
if (ENABLE_NSYSHID_LIBUSB)
if (ENABLE_VCPKG)
if(WIN32)
set(PKG_CONFIG_EXECUTABLE "${VCPKG_INSTALLED_DIR}/x64-windows/tools/pkgconf/pkgconf.exe")
endif()
find_package(PkgConfig REQUIRED)
pkg_check_modules(libusb REQUIRED IMPORTED_TARGET libusb-1.0)
target_link_libraries(CemuCafe PRIVATE PkgConfig::libusb)
@ -578,6 +572,7 @@ else ()
find_package(libusb MODULE REQUIRED)
target_link_libraries(CemuCafe PRIVATE libusb::libusb)
endif ()
endif ()
if (ENABLE_WXWIDGETS)
target_link_libraries(CemuCafe PRIVATE wx::base wx::core)

View file

@ -9,7 +9,6 @@
#include "audio/IAudioAPI.h"
#include "audio/IAudioInputAPI.h"
#include "config/ActiveSettings.h"
#include "config/LaunchSettings.h"
#include "Cafe/TitleList/GameInfo.h"
#include "Cafe/GraphicPack/GraphicPack2.h"
#include "util/helpers/SystemException.h"
@ -844,7 +843,7 @@ namespace CafeSystem
module->TitleStart();
cemu_initForGame();
// enter scheduler
if (ActiveSettings::GetCPUMode() == CPUMode::MulticoreRecompiler && !LaunchSettings::ForceInterpreter())
if (ActiveSettings::GetCPUMode() == CPUMode::MulticoreRecompiler)
coreinit::OSSchedulerBegin(3);
else
coreinit::OSSchedulerBegin(1);

View file

@ -140,7 +140,7 @@ bool gameProfile_loadEnumOption(IniParser& iniParser, const char* optionName, T&
for(const T& v : T())
{
// test integer option
if (boost::iequals(fmt::format("{}", fmt::underlying(v)), *option_value))
if (boost::iequals(fmt::format("{}", static_cast<typename std::underlying_type<T>::type>(v)), *option_value))
{
option = v;
return true;

View file

@ -345,7 +345,7 @@ GraphicPack2::GraphicPack2(fs::path rulesPath, IniParser& rules)
const auto preset_name = rules.FindOption("name");
if (!preset_name)
{
cemuLog_log(LogType::Force, "Graphic pack \"{}\": Preset in line {} skipped because it has no name option defined", GetNormalizedPathString(), rules.GetCurrentSectionLineNumber());
cemuLog_log(LogType::Force, "Graphic pack \"{}\": Preset in line {} skipped because it has no name option defined", m_name, rules.GetCurrentSectionLineNumber());
continue;
}
@ -369,7 +369,7 @@ GraphicPack2::GraphicPack2(fs::path rulesPath, IniParser& rules)
}
catch (const std::exception & ex)
{
cemuLog_log(LogType::Force, "Graphic pack \"{}\": Can't parse preset \"{}\": {}", GetNormalizedPathString(), *preset_name, ex.what());
cemuLog_log(LogType::Force, "Graphic pack \"{}\": Can't parse preset \"{}\": {}", m_name, *preset_name, ex.what());
}
}
else if (boost::iequals(currentSectionName, "RAM"))
@ -383,7 +383,7 @@ GraphicPack2::GraphicPack2(fs::path rulesPath, IniParser& rules)
{
if (m_version <= 5)
{
cemuLog_log(LogType::Force, "Graphic pack \"{}\": [RAM] options are only available for graphic pack version 6 or higher", GetNormalizedPathString(), optionNameBuf);
cemuLog_log(LogType::Force, "Graphic pack \"{}\": [RAM] options are only available for graphic pack version 6 or higher", m_name, optionNameBuf);
throw std::exception();
}
@ -393,12 +393,12 @@ GraphicPack2::GraphicPack2(fs::path rulesPath, IniParser& rules)
{
if (addrEnd <= addrStart)
{
cemuLog_log(LogType::Force, "Graphic pack \"{}\": start address (0x{:08x}) must be greater than end address (0x{:08x}) for {}", GetNormalizedPathString(), addrStart, addrEnd, optionNameBuf);
cemuLog_log(LogType::Force, "Graphic pack \"{}\": start address (0x{:08x}) must be greater than end address (0x{:08x}) for {}", m_name, addrStart, addrEnd, optionNameBuf);
throw std::exception();
}
else if ((addrStart & 0xFFF) != 0 || (addrEnd & 0xFFF) != 0)
{
cemuLog_log(LogType::Force, "Graphic pack \"{}\": addresses for %s are not aligned to 0x1000", GetNormalizedPathString(), optionNameBuf);
cemuLog_log(LogType::Force, "Graphic pack \"{}\": addresses for %s are not aligned to 0x1000", m_name, optionNameBuf);
throw std::exception();
}
else
@ -408,7 +408,7 @@ GraphicPack2::GraphicPack2(fs::path rulesPath, IniParser& rules)
}
else
{
cemuLog_log(LogType::Force, "Graphic pack \"{}\": has invalid syntax for option {}", GetNormalizedPathString(), optionNameBuf);
cemuLog_log(LogType::Force, "Graphic pack \"{}\": has invalid syntax for option {}", m_name, optionNameBuf);
throw std::exception();
}
}
@ -422,30 +422,22 @@ GraphicPack2::GraphicPack2(fs::path rulesPath, IniParser& rules)
std::unordered_map<std::string, std::vector<PresetPtr>> tmp_map;
// all vars must be defined in the default preset vars before
std::vector<std::pair<std::string, std::string>> mismatchingPresetVars;
for (const auto& presetEntry : m_presets)
for (const auto& entry : m_presets)
{
tmp_map[presetEntry->category].emplace_back(presetEntry);
tmp_map[entry->category].emplace_back(entry);
for (auto& presetVar : presetEntry->variables)
for (auto& kv : entry->variables)
{
const auto it = m_preset_vars.find(presetVar.first);
const auto it = m_preset_vars.find(kv.first);
if (it == m_preset_vars.cend())
{
mismatchingPresetVars.emplace_back(presetEntry->name, presetVar.first);
continue;
}
// overwrite var type with default var type
presetVar.second.first = it->second.first;
}
cemuLog_log(LogType::Force, "Graphic pack: \"{}\" contains preset variables which are not defined in the default section", m_name);
throw std::exception();
}
if(!mismatchingPresetVars.empty())
{
cemuLog_log(LogType::Force, "Graphic pack \"{}\" contains preset variables which are not defined in the [Default] section:", GetNormalizedPathString());
for (const auto& [presetName, varName] : mismatchingPresetVars)
cemuLog_log(LogType::Force, "Preset: {} Variable: {}", presetName, varName);
throw std::exception();
// overwrite var type with default var type
kv.second.first = it->second.first;
}
}
// have first entry be default active for every category if no default= is set
@ -477,7 +469,7 @@ GraphicPack2::GraphicPack2(fs::path rulesPath, IniParser& rules)
auto& p2 = kv.second[i + 1];
if (p1->variables.size() != p2->variables.size())
{
cemuLog_log(LogType::Force, "Graphic pack: \"{}\" contains inconsistent preset variables", GetNormalizedPathString());
cemuLog_log(LogType::Force, "Graphic pack: \"{}\" contains inconsistent preset variables", m_name);
throw std::exception();
}
@ -485,14 +477,14 @@ GraphicPack2::GraphicPack2(fs::path rulesPath, IniParser& rules)
std::set<std::string> keys2(get_keys(p2->variables).begin(), get_keys(p2->variables).end());
if (keys1 != keys2)
{
cemuLog_log(LogType::Force, "Graphic pack: \"{}\" contains inconsistent preset variables", GetNormalizedPathString());
cemuLog_log(LogType::Force, "Graphic pack: \"{}\" contains inconsistent preset variables", m_name);
throw std::exception();
}
if(p1->is_default)
{
if(has_default)
cemuLog_log(LogType::Force, "Graphic pack: \"{}\" has more than one preset with the default key set for the same category \"{}\"", GetNormalizedPathString(), p1->name);
cemuLog_log(LogType::Force, "Graphic pack: \"{}\" has more than one preset with the default key set for the same category \"{}\"", m_name, p1->name);
p1->active = true;
has_default = true;
}
@ -968,7 +960,7 @@ bool GraphicPack2::Activate()
auto option_upscale = rules.FindOption("upscaleMagFilter");
if(option_upscale && boost::iequals(*option_upscale, "NearestNeighbor"))
m_output_settings.upscale_filter = LatteTextureView::MagFilter::kNearestNeighbor;
auto option_downscale = rules.FindOption("downscaleMinFilter");
auto option_downscale = rules.FindOption("NearestNeighbor");
if (option_downscale && boost::iequals(*option_downscale, "NearestNeighbor"))
m_output_settings.downscale_filter = LatteTextureView::MagFilter::kNearestNeighbor;
}

View file

@ -8,7 +8,6 @@
#include "gui/debugger/DebuggerWindow2.h"
#include "Cafe/OS/libs/coreinit/coreinit.h"
#include "util/helpers/helpers.h"
#if BOOST_OS_WINDOWS
#include <Windows.h>
@ -137,6 +136,11 @@ void debugger_createCodeBreakpoint(uint32 address, uint8 bpType)
debugger_updateExecutionBreakpoint(address);
}
void debugger_createExecuteBreakpoint(uint32 address)
{
debugger_createCodeBreakpoint(address, DEBUGGER_BP_T_NORMAL);
}
namespace coreinit
{
std::vector<std::thread::native_handle_type>& OSGetSchedulerThreads();
@ -290,23 +294,8 @@ void debugger_toggleExecuteBreakpoint(uint32 address)
}
else
{
// create new execution breakpoint
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);
// create new breakpoint
debugger_createExecuteBreakpoint(address);
}
}
@ -458,34 +447,6 @@ bool debugger_hasPatch(uint32 address)
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)
{
bool isRecEnabled = ppcRecompilerEnabled;
@ -549,48 +510,7 @@ void debugger_enterTW(PPCInterpreter_t* hCPU)
{
if (bp->bpType == DEBUGGER_BP_T_LOGGING && bp->enabled)
{
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 logName = !bp->comment.empty() ? "Breakpoint '"+boost::nowide::narrow(bp->comment)+"'" : fmt::format("Breakpoint at 0x{:08X} (no comment)", bp->address);
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);
if (cemuLog_advancedPPCLoggingEnabled())
@ -627,7 +547,7 @@ void debugger_enterTW(PPCInterpreter_t* hCPU)
debuggerState.debugSession.stepInto = false;
debuggerState.debugSession.stepOver = false;
debuggerState.debugSession.run = false;
while (debuggerState.debugSession.isTrapped)
while (true)
{
std::this_thread::sleep_for(std::chrono::milliseconds(1));
// check for step commands

View file

@ -100,8 +100,8 @@ extern debuggerState_t debuggerState;
// new API
DebuggerBreakpoint* debugger_getFirstBP(uint32 address);
void debugger_createCodeBreakpoint(uint32 address, uint8 bpType);
void debugger_createExecuteBreakpoint(uint32 address);
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_createMemoryBreakpoint(uint32 address, bool onRead, bool onWrite);
@ -114,7 +114,6 @@ void debugger_updateExecutionBreakpoint(uint32 address, bool forceRestore = fals
void debugger_createPatch(uint32 address, std::span<uint8> patchData);
bool debugger_hasPatch(uint32 address);
void debugger_removePatch(uint32 address);
void debugger_forceBreak(); // force breakpoint at the next possible instruction
bool debugger_isTrapped();

View file

@ -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)
{
uint64 coreTime = coreinit::OSGetSystemTime();
uint64 coreTime = coreinit::coreinit_getTimerTick();
hCPU->gpr[gprIndex] = (uint32)(coreTime&0xFFFFFFFF);
}
void ATTR_MS_ABI PPCRecompiler_getTBU(PPCInterpreter_t* hCPU, uint32 gprIndex)
{
uint64 coreTime = coreinit::OSGetSystemTime();
uint64 coreTime = coreinit::coreinit_getTimerTick();
hCPU->gpr[gprIndex] = (uint32)((coreTime>>32)&0xFFFFFFFF);
}

View file

@ -141,14 +141,6 @@ private:
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
* If no data is available then wait in a busy loop
@ -474,8 +466,6 @@ LatteCMDPtr LatteCP_itWaitRegMem(LatteCMDPtr cmd, uint32 nWords)
const uint32 GPU7_WAIT_MEM_OP_GREATER = 6;
const uint32 GPU7_WAIT_MEM_OP_NEVER = 7;
LatteCP_signalEnterWait();
bool stalls = false;
if ((word0 & 0x10) != 0)
{
@ -604,7 +594,6 @@ LatteCMDPtr LatteCP_itMemSemaphore(LatteCMDPtr cmd, uint32 nWords)
else if(SEM_SIGNAL == 7)
{
// wait
LatteCP_signalEnterWait();
size_t loopCount = 0;
while (true)
{
@ -799,7 +788,7 @@ LatteCMDPtr LatteCP_itHLESampleTimer(LatteCMDPtr cmd, uint32 nWords)
{
cemu_assert_debug(nWords == 1);
MPTR timerMPTR = (MPTR)LatteReadCMD();
memory_writeU64(timerMPTR, coreinit::OSGetSystemTime());
memory_writeU64(timerMPTR, coreinit::coreinit_getTimerTick());
return cmd;
}
@ -1316,13 +1305,11 @@ void LatteCP_processCommandBuffer(DrawPassContext& drawPassCtx)
}
case IT_HLE_TRIGGER_SCANBUFFER_SWAP:
{
LatteCP_signalEnterWait();
LatteCP_itHLESwapScanBuffer(cmdData, nWords);
break;
}
case IT_HLE_WAIT_FOR_FLIP:
{
LatteCP_signalEnterWait();
LatteCP_itHLEWaitForFlip(cmdData, nWords);
break;
}
@ -1607,14 +1594,12 @@ void LatteCP_ProcessRingbuffer()
}
case IT_HLE_TRIGGER_SCANBUFFER_SWAP:
{
LatteCP_signalEnterWait();
LatteCP_itHLESwapScanBuffer(cmd, nWords);
timerRecheck += CP_TIMER_RECHECK / 64;
break;
}
case IT_HLE_WAIT_FOR_FLIP:
{
LatteCP_signalEnterWait();
LatteCP_itHLEWaitForFlip(cmd, nWords);
timerRecheck += CP_TIMER_RECHECK / 1;
break;

View file

@ -1,7 +1,6 @@
#include "Cafe/HW/Latte/Core/LatteConst.h"
#include "Cafe/HW/Latte/Renderer/Renderer.h"
#include "Cafe/HW/Latte/ISA/RegDefines.h"
#include "Cafe/HW/Latte/Core/LattePerformanceMonitor.h"
#include "Common/cpu_features.h"
#if defined(ARCH_X86_64) && defined(__GNUC__)
@ -10,53 +9,32 @@
struct
{
struct CacheEntry
{
// input data
const void* lastPtr;
uint32 lastCount;
LattePrimitiveMode lastPrimitiveMode;
LatteIndexType lastIndexType;
uint64 lastUsed;
// output
uint32 indexMin;
uint32 indexMax;
Renderer::INDEX_TYPE renderIndexType;
uint32 outputCount;
Renderer::IndexAllocation indexAllocation;
};
std::array<CacheEntry, 8> entry;
uint64 currentUsageCounter{0};
uint32 indexBufferOffset;
uint32 indexBufferIndex;
}LatteIndexCache{};
void LatteIndices_invalidate(const void* memPtr, uint32 size)
{
for(auto& entry : LatteIndexCache.entry)
if (LatteIndexCache.lastPtr >= memPtr && (LatteIndexCache.lastPtr < ((uint8*)memPtr + size)) )
{
if (entry.lastPtr >= memPtr && (entry.lastPtr < ((uint8*)memPtr + size)) )
{
if(entry.lastPtr != nullptr)
g_renderer->indexData_releaseIndexMemory(entry.indexAllocation);
entry.lastPtr = nullptr;
entry.lastCount = 0;
}
LatteIndexCache.lastPtr = nullptr;
LatteIndexCache.lastCount = 0;
}
}
void LatteIndices_invalidateAll()
{
for(auto& entry : LatteIndexCache.entry)
{
if (entry.lastPtr != nullptr)
g_renderer->indexData_releaseIndexMemory(entry.indexAllocation);
entry.lastPtr = nullptr;
entry.lastCount = 0;
}
}
uint64 LatteIndices_GetNextUsageIndex()
{
return LatteIndexCache.currentUsageCounter++;
LatteIndexCache.lastPtr = nullptr;
LatteIndexCache.lastCount = 0;
}
uint32 LatteIndices_calculateIndexOutputSize(LattePrimitiveMode primitiveMode, LatteIndexType indexType, uint32 count)
@ -554,7 +532,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, Renderer::IndexAllocation& indexAllocation)
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)
{
// what this should do:
// [x] use fast SIMD-based index decoding
@ -564,18 +542,17 @@ void LatteIndices_decode(const void* indexData, LatteIndexType indexType, uint32
// [ ] better cache implementation, allow to cache across frames
// reuse from cache if data didn't change
auto cacheEntry = std::find_if(LatteIndexCache.entry.begin(), LatteIndexCache.entry.end(), [indexData, count, primitiveMode, indexType](const auto& entry)
if (LatteIndexCache.lastPtr == indexData &&
LatteIndexCache.lastCount == count &&
LatteIndexCache.lastPrimitiveMode == primitiveMode &&
LatteIndexCache.lastIndexType == indexType)
{
return entry.lastPtr == indexData && entry.lastCount == count && entry.lastPrimitiveMode == primitiveMode && entry.lastIndexType == indexType;
});
if (cacheEntry != LatteIndexCache.entry.end())
{
indexMin = cacheEntry->indexMin;
indexMax = cacheEntry->indexMax;
renderIndexType = cacheEntry->renderIndexType;
outputCount = cacheEntry->outputCount;
indexAllocation = cacheEntry->indexAllocation;
cacheEntry->lastUsed = LatteIndices_GetNextUsageIndex();
indexMin = LatteIndexCache.indexMin;
indexMax = LatteIndexCache.indexMax;
renderIndexType = LatteIndexCache.renderIndexType;
outputCount = LatteIndexCache.outputCount;
indexBufferOffset = LatteIndexCache.indexBufferOffset;
indexBufferIndex = LatteIndexCache.indexBufferIndex;
return;
}
@ -599,12 +576,10 @@ void LatteIndices_decode(const void* indexData, LatteIndexType indexType, uint32
indexMin = 0;
indexMax = std::max(count, 1u)-1;
renderIndexType = Renderer::INDEX_TYPE::NONE;
indexAllocation = {};
return; // no indices
}
// query index buffer from renderer
indexAllocation = g_renderer->indexData_reserveIndexMemory(indexOutputSize);
void* indexOutputPtr = indexAllocation.mem;
void* indexOutputPtr = g_renderer->indexData_reserveIndexMemory(indexOutputSize, indexBufferOffset, indexBufferIndex);
// decode indices
indexMin = std::numeric_limits<uint32>::max();
@ -729,25 +704,16 @@ void LatteIndices_decode(const void* indexData, LatteIndexType indexType, uint32
// recalculate index range but filter out primitive restart index
LatteIndices_alternativeCalculateIndexMinMax(indexData, indexType, count, indexMin, indexMax);
}
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);
g_renderer->indexData_uploadIndexMemory(indexBufferOffset, indexOutputSize);
// update cache
lruEntry->lastPtr = indexData;
lruEntry->lastCount = count;
lruEntry->lastPrimitiveMode = primitiveMode;
lruEntry->lastIndexType = indexType;
lruEntry->indexMin = indexMin;
lruEntry->indexMax = indexMax;
lruEntry->renderIndexType = renderIndexType;
lruEntry->outputCount = outputCount;
lruEntry->indexAllocation = indexAllocation;
lruEntry->lastUsed = LatteIndices_GetNextUsageIndex();
LatteIndexCache.lastPtr = indexData;
LatteIndexCache.lastCount = count;
LatteIndexCache.lastPrimitiveMode = primitiveMode;
LatteIndexCache.lastIndexType = indexType;
LatteIndexCache.indexMin = indexMin;
LatteIndexCache.indexMax = indexMax;
LatteIndexCache.renderIndexType = renderIndexType;
LatteIndexCache.outputCount = outputCount;
LatteIndexCache.indexBufferOffset = indexBufferOffset;
LatteIndexCache.indexBufferIndex = indexBufferIndex;
}

View file

@ -4,4 +4,4 @@
void LatteIndices_invalidate(const void* memPtr, uint32 size);
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, Renderer::IndexAllocation& indexAllocation);
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);

View file

@ -107,13 +107,7 @@ void LatteOverlay_renderOverlay(ImVec2& position, ImVec2& pivot, sint32 directio
ImGui::Text("VRAM: %dMB / %dMB", g_state.vramUsage, g_state.vramTotal);
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();
}
position.y += (ImGui::GetWindowSize().y + 10.0f) * direction;
}

View file

@ -74,6 +74,7 @@ void LattePerformanceMonitor_frameEnd()
uniformBankDataUploadedPerFrame /= 1024ULL;
uint32 uniformBankCountUploadedPerFrame = (uint32)(uniformBankUploadedCount / (uint64)elapsedFrames);
uint64 indexDataUploadPerFrame = (indexDataUploaded / (uint64)elapsedFrames);
indexDataUploadPerFrame /= 1024ULL;
double fps = (double)elapsedFrames2S * 1000.0 / (double)totalElapsedTimeFPS;
uint32 shaderBindsPerFrame = shaderBindCounter / elapsedFrames;
@ -81,7 +82,7 @@ void LattePerformanceMonitor_frameEnd()
uint32 rlps = (uint32)((uint64)recompilerLeaveCount * 1000ULL / (uint64)totalElapsedTime);
uint32 tlps = (uint32)((uint64)threadLeaveCount * 1000ULL / (uint64)totalElapsedTime);
// set stats
performanceMonitor.stats.indexDataUploadPerFrame = indexDataUploadPerFrame;
// next counter cycle
sint32 nextCycleIndex = (performanceMonitor.cycleIndex + 1) % PERFORMANCE_MONITOR_TRACK_CYCLES;
performanceMonitor.cycle[nextCycleIndex].drawCallCounter = 0;

View file

@ -124,7 +124,6 @@ typedef struct
LattePerfStatCounter numGraphicPipelines;
LattePerfStatCounter numImages;
LattePerfStatCounter numImageViews;
LattePerfStatCounter numSamplers;
LattePerfStatCounter numRenderPass;
LattePerfStatCounter numFramebuffer;
@ -132,12 +131,6 @@ typedef struct
LattePerfStatCounter numDrawBarriersPerFrame;
LattePerfStatCounter numBeginRenderpassPerFrame;
}vk;
// calculated stats (per frame)
struct
{
uint32 indexDataUploadPerFrame;
}stats;
}performanceMonitor_t;
extern performanceMonitor_t performanceMonitor;

View file

@ -11,6 +11,7 @@
#include "Cafe/HW/Latte/Core/LattePerformanceMonitor.h"
#include "Cafe/GraphicPack/GraphicPack2.h"
#include "config/ActiveSettings.h"
#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h"
#include "gui/guiWrapper.h"
#include "Cafe/OS/libs/erreula/erreula.h"
#include "input/InputManager.h"
@ -933,6 +934,13 @@ void LatteRenderTarget_copyToBackbuffer(LatteTextureView* textureView, bool isPa
{
sint32 scaling_filter = downscaling ? GetConfig().downscale_filter : GetConfig().upscale_filter;
if (g_renderer->GetType() == RendererAPI::Vulkan)
{
// force linear or nearest neighbor filter
if(scaling_filter != kLinearFilter && scaling_filter != kNearestNeighborFilter)
scaling_filter = kLinearFilter;
}
if (scaling_filter == kLinearFilter)
{
if(renderUpsideDown)
@ -949,7 +957,7 @@ void LatteRenderTarget_copyToBackbuffer(LatteTextureView* textureView, bool isPa
else
shader = RendererOutputShader::s_bicubic_shader;
filter = LatteTextureView::MagFilter::kLinear;
filter = LatteTextureView::MagFilter::kNearestNeighbor;
}
else if (scaling_filter == kBicubicHermiteFilter)
{

View file

@ -451,8 +451,9 @@ void LatteShader_DumpShader(uint64 baseHash, uint64 auxHash, LatteDecompilerShad
suffix = "gs";
else if (shader->shaderType == LatteConst::ShaderType::Pixel)
suffix = "ps";
FileStream* fs = FileStream::createFile2(ActiveSettings::GetUserDataPath("dump/shaders/{:016x}_{:016x}_{}.txt", baseHash, auxHash, suffix));
fs::path dumpPath = "dump/shaders";
dumpPath /= fmt::format("{:016x}_{:016x}_{}.txt", baseHash, auxHash, suffix);
FileStream* fs = FileStream::createFile2(dumpPath);
if (fs)
{
if (shader->strBuf_shaderSource)
@ -478,8 +479,9 @@ void LatteShader_DumpRawShader(uint64 baseHash, uint64 auxHash, uint32 type, uin
suffix = "copy";
else if (type == SHADER_DUMP_TYPE_COMPUTE)
suffix = "compute";
FileStream* fs = FileStream::createFile2(ActiveSettings::GetUserDataPath("dump/shaders/{:016x}_{:016x}_{}.bin", baseHash, auxHash, suffix));
fs::path dumpPath = "dump/shaders";
dumpPath /= fmt::format("{:016x}_{:016x}_{}.bin", baseHash, auxHash, suffix);
FileStream* fs = FileStream::createFile2(dumpPath);
if (fs)
{
fs->writeData(programCode, programLen);

View file

@ -25,9 +25,6 @@
#include "util/helpers/Serializer.h"
#include <wx/msgdlg.h>
#include <audio/IAudioAPI.h>
#include <util/bootSound/BootSoundReader.h>
#include <thread>
#if BOOST_OS_WINDOWS
#include <psapi.h>
@ -158,118 +155,6 @@ bool LoadTGAFile(const std::vector<uint8>& buffer, TGAFILE *tgaFile)
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()
{
if (g_renderer->GetType() == RendererAPI::Vulkan)
@ -414,9 +299,6 @@ void LatteShaderCache_Load()
loadBackgroundTexture(true, g_shaderCacheLoaderState.textureTVId);
loadBackgroundTexture(false, g_shaderCacheLoaderState.textureDRCId);
if(GetConfig().play_boot_sound)
g_bootSndPlayer.StartSound();
sint32 numLoadedShaders = 0;
uint32 loadIndex = 0;
@ -483,11 +365,6 @@ void LatteShaderCache_Load()
g_renderer->DeleteTexture(g_shaderCacheLoaderState.textureTVId);
if (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)
@ -628,6 +505,8 @@ void LatteShaderCache_LoadVulkanPipelineCache(uint64 cacheTitleId)
g_shaderCacheLoaderState.loadedPipelines = 0;
LatteShaderCache_ShowProgress(LatteShaderCache_updatePipelineLoadingProgress, true);
pipelineCache.EndLoading();
if(Latte_GetStopSignal())
LatteThread_Exit();
}
bool LatteShaderCache_updatePipelineLoadingProgress()

View file

@ -257,7 +257,6 @@ void LatteThread_Exit()
LatteSHRC_UnloadAll();
// close disk cache
LatteShaderCache_Close();
RendererOutputShader::ShutdownStatic();
// destroy renderer but make sure that g_renderer remains valid until the destructor has finished
if (g_renderer)
{

View file

@ -370,8 +370,6 @@ bool LatteDecompiler_IsALUTransInstruction(bool isOP3, uint32 opcode)
opcode == ALU_OP2_INST_LSHR_INT ||
opcode == ALU_OP2_INST_MAX_INT ||
opcode == ALU_OP2_INST_MIN_INT ||
opcode == ALU_OP2_INST_MAX_UINT ||
opcode == ALU_OP2_INST_MIN_UINT ||
opcode == ALU_OP2_INST_MOVA_FLOOR ||
opcode == ALU_OP2_INST_MOVA_INT ||
opcode == ALU_OP2_INST_SETE_DX10 ||

View file

@ -140,8 +140,6 @@ bool _isIntegerInstruction(const LatteDecompilerALUInstruction& aluInstruction)
case ALU_OP2_INST_SUB_INT:
case ALU_OP2_INST_MAX_INT:
case ALU_OP2_INST_MIN_INT:
case ALU_OP2_INST_MAX_UINT:
case ALU_OP2_INST_MIN_UINT:
case ALU_OP2_INST_SETE_INT:
case ALU_OP2_INST_SETGT_INT:
case ALU_OP2_INST_SETGE_INT:

View file

@ -1415,23 +1415,19 @@ void _emitALUOP2InstructionCode(LatteDecompilerShaderContext* shaderContext, Lat
}
else if( aluInstruction->opcode == ALU_OP2_INST_ADD_INT )
_emitALUOperationBinary<LATTE_DECOMPILER_DTYPE_SIGNED_INT>(shaderContext, aluInstruction, " + ");
else if( aluInstruction->opcode == ALU_OP2_INST_MAX_INT || aluInstruction->opcode == ALU_OP2_INST_MIN_INT ||
aluInstruction->opcode == ALU_OP2_INST_MAX_UINT || aluInstruction->opcode == ALU_OP2_INST_MIN_UINT)
else if( aluInstruction->opcode == ALU_OP2_INST_MAX_INT || aluInstruction->opcode == ALU_OP2_INST_MIN_INT )
{
// not verified
bool isUnsigned = aluInstruction->opcode == ALU_OP2_INST_MAX_UINT || aluInstruction->opcode == ALU_OP2_INST_MIN_UINT;
auto opType = isUnsigned ? LATTE_DECOMPILER_DTYPE_UNSIGNED_INT : LATTE_DECOMPILER_DTYPE_SIGNED_INT;
_emitInstructionOutputVariableName(shaderContext, aluInstruction);
src->add(" = ");
_emitTypeConversionPrefix(shaderContext, opType, outputType);
if( aluInstruction->opcode == ALU_OP2_INST_MAX_INT || aluInstruction->opcode == ALU_OP2_INST_MAX_UINT )
src->add("max(");
if( aluInstruction->opcode == ALU_OP2_INST_MAX_INT )
src->add(" = max(");
else
src->add("min(");
_emitOperandInputCode(shaderContext, aluInstruction, 0, opType);
src->add(" = min(");
_emitTypeConversionPrefix(shaderContext, LATTE_DECOMPILER_DTYPE_SIGNED_INT, outputType);
_emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_SIGNED_INT);
src->add(", ");
_emitOperandInputCode(shaderContext, aluInstruction, 1, opType);
_emitTypeConversionSuffix(shaderContext, opType, outputType);
_emitOperandInputCode(shaderContext, aluInstruction, 1, LATTE_DECOMPILER_DTYPE_SIGNED_INT);
_emitTypeConversionSuffix(shaderContext, LATTE_DECOMPILER_DTYPE_SIGNED_INT, outputType);
src->add(");" _CRLF);
}
else if( aluInstruction->opcode == ALU_OP2_INST_SUB_INT )

View file

@ -60,8 +60,6 @@
#define ALU_OP2_INST_SUB_INT (0x035) // integer instruction
#define ALU_OP2_INST_MAX_INT (0x036) // integer instruction
#define ALU_OP2_INST_MIN_INT (0x037) // integer instruction
#define ALU_OP2_INST_MAX_UINT (0x038) // integer instruction
#define ALU_OP2_INST_MIN_UINT (0x039) // integer instruction
#define ALU_OP2_INST_SETE_INT (0x03A) // integer instruction
#define ALU_OP2_INST_SETGT_INT (0x03B) // integer instruction
#define ALU_OP2_INST_SETGE_INT (0x03C) // integer instruction

View file

@ -570,10 +570,13 @@ void OpenGLRenderer::DrawBackbufferQuad(LatteTextureView* texView, RendererOutpu
g_renderer->ClearColorbuffer(padView);
}
sint32 effectiveWidth, effectiveHeight;
texView->baseTexture->GetEffectiveSize(effectiveWidth, effectiveHeight, 0);
shader_unbind(RendererShader::ShaderType::kGeometry);
shader_bind(shader->GetVertexShader());
shader_bind(shader->GetFragmentShader());
shader->SetUniformParameters(*texView, {imageWidth, imageHeight});
shader->SetUniformParameters(*texView, { effectiveWidth, effectiveHeight }, { imageWidth, imageHeight });
// set viewport
glViewportIndexedf(0, imageX, imageY, imageWidth, imageHeight);
@ -581,12 +584,6 @@ void OpenGLRenderer::DrawBackbufferQuad(LatteTextureView* texView, RendererOutpu
LatteTextureViewGL* texViewGL = (LatteTextureViewGL*)texView;
texture_bindAndActivate(texView, 0);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
texViewGL->samplerState.clampS = texViewGL->samplerState.clampT = 0xFF;
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, useLinearTexFilter ? GL_LINEAR : GL_NEAREST);
texViewGL->samplerState.filterMin = 0xFFFFFFFF;
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, useLinearTexFilter ? GL_LINEAR : GL_NEAREST);
texViewGL->samplerState.filterMag = 0xFFFFFFFF;

View file

@ -102,21 +102,16 @@ public:
static void SetAttributeArrayState(uint32 index, bool isEnabled, sint32 aluDivisor);
static void SetArrayElementBuffer(GLuint arrayElementBuffer);
// index (not used by OpenGL renderer yet)
IndexAllocation indexData_reserveIndexMemory(uint32 size) override
// index
void* indexData_reserveIndexMemory(uint32 size, uint32& offset, uint32& bufferIndex) override
{
cemu_assert_unimplemented();
return {};
assert_dbg();
return nullptr;
}
void indexData_releaseIndexMemory(IndexAllocation& allocation) override
void indexData_uploadIndexMemory(uint32 offset, uint32 size) override
{
cemu_assert_unimplemented();
}
void indexData_uploadIndexMemory(IndexAllocation& allocation) override
{
cemu_assert_unimplemented();
assert_dbg();
}
// uniform

View file

@ -138,15 +138,8 @@ public:
virtual void draw_endSequence() = 0;
// index
struct IndexAllocation
{
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;
virtual void* indexData_reserveIndexMemory(uint32 size, uint32& offset, uint32& bufferIndex) = 0;
virtual void indexData_uploadIndexMemory(uint32 offset, uint32 size) = 0;
// occlusion queries
virtual LatteQueryObject* occlusionQuery_create() = 0;

View file

@ -2,7 +2,18 @@
#include "Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h"
const std::string RendererOutputShader::s_copy_shader_source =
R"(
R"(#version 420
#ifdef VULKAN
layout(location = 0) in vec2 passUV;
layout(binding = 0) uniform sampler2D textureSrc;
layout(location = 0) out vec4 colorOut0;
#else
in vec2 passUV;
layout(binding=0) uniform sampler2D textureSrc;
layout(location = 0) out vec4 colorOut0;
#endif
void main()
{
colorOut0 = vec4(texture(textureSrc, passUV).rgb,1.0);
@ -11,6 +22,20 @@ void main()
const std::string RendererOutputShader::s_bicubic_shader_source =
R"(
#version 420
#ifdef VULKAN
layout(location = 0) in vec2 passUV;
layout(binding = 0) uniform sampler2D textureSrc;
layout(binding = 1) uniform vec2 textureSrcResolution;
layout(location = 0) out vec4 colorOut0;
#else
in vec2 passUV;
layout(binding=0) uniform sampler2D textureSrc;
uniform vec2 textureSrcResolution;
layout(location = 0) out vec4 colorOut0;
#endif
vec4 cubic(float x)
{
float x2 = x * x;
@ -23,23 +48,24 @@ vec4 cubic(float x)
return w / 6.0;
}
vec4 bcFilter(vec2 uv, vec4 texelSize)
vec4 bcFilter(vec2 texcoord, vec2 texscale)
{
vec2 pixel = uv*texelSize.zw - 0.5;
vec2 pixelFrac = fract(pixel);
vec2 pixelInt = pixel - pixelFrac;
float fx = fract(texcoord.x);
float fy = fract(texcoord.y);
texcoord.x -= fx;
texcoord.y -= fy;
vec4 xcubic = cubic(pixelFrac.x);
vec4 ycubic = cubic(pixelFrac.y);
vec4 xcubic = cubic(fx);
vec4 ycubic = cubic(fy);
vec4 c = vec4(pixelInt.x - 0.5, pixelInt.x + 1.5, pixelInt.y - 0.5, pixelInt.y + 1.5);
vec4 c = vec4(texcoord.x - 0.5, texcoord.x + 1.5, texcoord.y - 0.5, texcoord.y + 1.5);
vec4 s = vec4(xcubic.x + xcubic.y, xcubic.z + xcubic.w, ycubic.x + ycubic.y, ycubic.z + ycubic.w);
vec4 offset = c + vec4(xcubic.y, xcubic.w, ycubic.y, ycubic.w) / s;
vec4 sample0 = texture(textureSrc, vec2(offset.x, offset.z) * texelSize.xy);
vec4 sample1 = texture(textureSrc, vec2(offset.y, offset.z) * texelSize.xy);
vec4 sample2 = texture(textureSrc, vec2(offset.x, offset.w) * texelSize.xy);
vec4 sample3 = texture(textureSrc, vec2(offset.y, offset.w) * texelSize.xy);
vec4 sample0 = texture(textureSrc, vec2(offset.x, offset.z) * texscale);
vec4 sample1 = texture(textureSrc, vec2(offset.y, offset.z) * texscale);
vec4 sample2 = texture(textureSrc, vec2(offset.x, offset.w) * texscale);
vec4 sample3 = texture(textureSrc, vec2(offset.y, offset.w) * texscale);
float sx = s.x / (s.x + s.y);
float sy = s.z / (s.z + s.w);
@ -50,13 +76,20 @@ vec4 bcFilter(vec2 uv, vec4 texelSize)
}
void main(){
vec4 texelSize = vec4( 1.0 / textureSrcResolution.xy, textureSrcResolution.xy);
colorOut0 = vec4(bcFilter(passUV, texelSize).rgb,1.0);
colorOut0 = vec4(bcFilter(passUV*textureSrcResolution, vec2(1.0,1.0)/textureSrcResolution).rgb,1.0);
}
)";
const std::string RendererOutputShader::s_hermite_shader_source =
R"(
R"(#version 420
in vec4 gl_FragCoord;
in vec2 passUV;
layout(binding=0) uniform sampler2D textureSrc;
uniform vec2 textureSrcResolution;
uniform vec2 outputResolution;
layout(location = 0) out vec4 colorOut0;
// https://www.shadertoy.com/view/MllSzX
vec3 CubicHermite (vec3 A, vec3 B, vec3 C, vec3 D, float t)
@ -78,7 +111,7 @@ vec3 BicubicHermiteTexture(vec2 uv, vec4 texelSize)
vec2 frac = fract(pixel);
pixel = floor(pixel) / texelSize.zw - vec2(texelSize.xy/2.0);
vec4 doubleSize = texelSize*2.0;
vec4 doubleSize = texelSize*texelSize;
vec3 C00 = texture(textureSrc, pixel + vec2(-texelSize.x ,-texelSize.y)).rgb;
vec3 C10 = texture(textureSrc, pixel + vec2( 0.0 ,-texelSize.y)).rgb;
@ -109,17 +142,15 @@ vec3 BicubicHermiteTexture(vec2 uv, vec4 texelSize)
}
void main(){
vec4 texelSize = vec4( 1.0 / textureSrcResolution.xy, textureSrcResolution.xy);
vec4 texelSize = vec4( 1.0 / outputResolution.xy, outputResolution.xy);
colorOut0 = vec4(BicubicHermiteTexture(passUV, texelSize), 1.0);
}
)";
RendererOutputShader::RendererOutputShader(const std::string& vertex_source, const std::string& fragment_source)
{
auto finalFragmentSrc = PrependFragmentPreamble(fragment_source);
m_vertex_shader.reset(g_renderer->shader_create(RendererShader::ShaderType::kVertex, 0, 0, vertex_source, false, false));
m_fragment_shader.reset(g_renderer->shader_create(RendererShader::ShaderType::kFragment, 0, 0, finalFragmentSrc, false, false));
m_vertex_shader = 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, fragment_source, false, false);
m_vertex_shader->PreponeCompilation(true);
m_fragment_shader->PreponeCompilation(true);
@ -132,45 +163,74 @@ RendererOutputShader::RendererOutputShader(const std::string& vertex_source, con
if (g_renderer->GetType() == RendererAPI::OpenGL)
{
m_uniformLocations[0].m_loc_textureSrcResolution = m_vertex_shader->GetUniformLocation("textureSrcResolution");
m_uniformLocations[0].m_loc_nativeResolution = m_vertex_shader->GetUniformLocation("nativeResolution");
m_uniformLocations[0].m_loc_outputResolution = m_vertex_shader->GetUniformLocation("outputResolution");
m_attributes[0].m_loc_texture_src_resolution = m_vertex_shader->GetUniformLocation("textureSrcResolution");
m_attributes[0].m_loc_input_resolution = m_vertex_shader->GetUniformLocation("inputResolution");
m_attributes[0].m_loc_output_resolution = m_vertex_shader->GetUniformLocation("outputResolution");
m_uniformLocations[1].m_loc_textureSrcResolution = m_fragment_shader->GetUniformLocation("textureSrcResolution");
m_uniformLocations[1].m_loc_nativeResolution = m_fragment_shader->GetUniformLocation("nativeResolution");
m_uniformLocations[1].m_loc_outputResolution = m_fragment_shader->GetUniformLocation("outputResolution");
m_attributes[1].m_loc_texture_src_resolution = m_fragment_shader->GetUniformLocation("textureSrcResolution");
m_attributes[1].m_loc_input_resolution = m_fragment_shader->GetUniformLocation("inputResolution");
m_attributes[1].m_loc_output_resolution = m_fragment_shader->GetUniformLocation("outputResolution");
}
}
void RendererOutputShader::SetUniformParameters(const LatteTextureView& texture_view, const Vector2i& output_res) const
else
{
cemuLog_logDebug(LogType::Force, "RendererOutputShader() - todo for Vulkan");
m_attributes[0].m_loc_texture_src_resolution = -1;
m_attributes[0].m_loc_input_resolution = -1;
m_attributes[0].m_loc_output_resolution = -1;
m_attributes[1].m_loc_texture_src_resolution = -1;
m_attributes[1].m_loc_input_resolution = -1;
m_attributes[1].m_loc_output_resolution = -1;
}
}
void RendererOutputShader::SetUniformParameters(const LatteTextureView& texture_view, const Vector2i& input_res, const Vector2i& output_res) const
{
sint32 effectiveWidth, effectiveHeight;
texture_view.baseTexture->GetEffectiveSize(effectiveWidth, effectiveHeight, 0);
auto setUniforms = [&](RendererShader* shader, const UniformLocations& locations){
float res[2];
if (locations.m_loc_textureSrcResolution != -1)
{
res[0] = (float)effectiveWidth;
res[1] = (float)effectiveHeight;
shader->SetUniform2fv(locations.m_loc_textureSrcResolution, res, 1);
}
if (locations.m_loc_nativeResolution != -1)
// vertex shader
if (m_attributes[0].m_loc_texture_src_resolution != -1)
{
res[0] = (float)texture_view.baseTexture->width;
res[1] = (float)texture_view.baseTexture->height;
shader->SetUniform2fv(locations.m_loc_nativeResolution, res, 1);
m_vertex_shader->SetUniform2fv(m_attributes[0].m_loc_texture_src_resolution, res, 1);
}
if (locations.m_loc_outputResolution != -1)
if (m_attributes[0].m_loc_input_resolution != -1)
{
res[0] = (float)input_res.x;
res[1] = (float)input_res.y;
m_vertex_shader->SetUniform2fv(m_attributes[0].m_loc_input_resolution, res, 1);
}
if (m_attributes[0].m_loc_output_resolution != -1)
{
res[0] = (float)output_res.x;
res[1] = (float)output_res.y;
shader->SetUniform2fv(locations.m_loc_outputResolution, res, 1);
m_vertex_shader->SetUniform2fv(m_attributes[0].m_loc_output_resolution, res, 1);
}
// fragment shader
if (m_attributes[1].m_loc_texture_src_resolution != -1)
{
res[0] = (float)texture_view.baseTexture->width;
res[1] = (float)texture_view.baseTexture->height;
m_fragment_shader->SetUniform2fv(m_attributes[1].m_loc_texture_src_resolution, res, 1);
}
if (m_attributes[1].m_loc_input_resolution != -1)
{
res[0] = (float)input_res.x;
res[1] = (float)input_res.y;
m_fragment_shader->SetUniform2fv(m_attributes[1].m_loc_input_resolution, res, 1);
}
if (m_attributes[1].m_loc_output_resolution != -1)
{
res[0] = (float)output_res.x;
res[1] = (float)output_res.y;
m_fragment_shader->SetUniform2fv(m_attributes[1].m_loc_output_resolution, res, 1);
}
};
setUniforms(m_vertex_shader.get(), m_uniformLocations[0]);
setUniforms(m_fragment_shader.get(), m_uniformLocations[1]);
}
RendererOutputShader* RendererOutputShader::s_copy_shader;
@ -187,8 +247,8 @@ std::string RendererOutputShader::GetOpenGlVertexSource(bool render_upside_down)
// vertex shader
std::ostringstream vertex_source;
vertex_source <<
R"(#version 420
layout(location = 0) smooth out vec2 passUV;
R"(#version 400
out vec2 passUV;
out gl_PerVertex
{
@ -281,27 +341,6 @@ void main(){
)";
return vertex_source.str();
}
std::string RendererOutputShader::PrependFragmentPreamble(const std::string& shaderSrc)
{
return R"(#version 430
#ifdef VULKAN
layout(push_constant) uniform pc {
vec2 textureSrcResolution;
vec2 nativeResolution;
vec2 outputResolution;
};
#else
uniform vec2 textureSrcResolution;
uniform vec2 nativeResolution;
uniform vec2 outputResolution;
#endif
layout(location = 0) smooth in vec2 passUV;
layout(binding = 0) uniform sampler2D textureSrc;
layout(location = 0) out vec4 colorOut0;
)" + shaderSrc;
}
void RendererOutputShader::InitializeStatic()
{
std::string vertex_source, vertex_source_ud;
@ -310,12 +349,7 @@ void RendererOutputShader::InitializeStatic()
{
vertex_source = GetOpenGlVertexSource(false);
vertex_source_ud = GetOpenGlVertexSource(true);
}
else
{
vertex_source = GetVulkanVertexSource(false);
vertex_source_ud = GetVulkanVertexSource(true);
}
s_copy_shader = new RendererOutputShader(vertex_source, s_copy_shader_source);
s_copy_shader_ud = new RendererOutputShader(vertex_source_ud, s_copy_shader_source);
@ -325,15 +359,18 @@ void RendererOutputShader::InitializeStatic()
s_hermit_shader = new RendererOutputShader(vertex_source, s_hermite_shader_source);
s_hermit_shader_ud = new RendererOutputShader(vertex_source_ud, s_hermite_shader_source);
}
void RendererOutputShader::ShutdownStatic()
else
{
delete s_copy_shader;
delete s_copy_shader_ud;
vertex_source = GetVulkanVertexSource(false);
vertex_source_ud = GetVulkanVertexSource(true);
delete s_bicubic_shader;
delete s_bicubic_shader_ud;
s_copy_shader = new RendererOutputShader(vertex_source, s_copy_shader_source);
s_copy_shader_ud = new RendererOutputShader(vertex_source_ud, s_copy_shader_source);
delete s_hermit_shader;
delete s_hermit_shader_ud;
/* s_bicubic_shader = new RendererOutputShader(vertex_source, s_bicubic_shader_source); TODO
s_bicubic_shader_ud = new RendererOutputShader(vertex_source_ud, s_bicubic_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);*/
}
}

View file

@ -17,20 +17,19 @@ public:
RendererOutputShader(const std::string& vertex_source, const std::string& fragment_source);
virtual ~RendererOutputShader() = default;
void SetUniformParameters(const LatteTextureView& texture_view, const Vector2i& output_res) const;
void SetUniformParameters(const LatteTextureView& texture_view, const Vector2i& input_res, const Vector2i& output_res) const;
RendererShader* GetVertexShader() const
{
return m_vertex_shader.get();
return m_vertex_shader;
}
RendererShader* GetFragmentShader() const
{
return m_fragment_shader.get();
return m_fragment_shader;
}
static void InitializeStatic();
static void ShutdownStatic();
static RendererOutputShader* s_copy_shader;
static RendererOutputShader* s_copy_shader_ud;
@ -44,18 +43,16 @@ public:
static std::string GetVulkanVertexSource(bool render_upside_down);
static std::string GetOpenGlVertexSource(bool render_upside_down);
static std::string PrependFragmentPreamble(const std::string& shaderSrc);
protected:
std::unique_ptr<RendererShader> m_vertex_shader;
std::unique_ptr<RendererShader> m_fragment_shader;
RendererShader* m_vertex_shader;
RendererShader* m_fragment_shader;
struct UniformLocations
struct
{
sint32 m_loc_textureSrcResolution = -1;
sint32 m_loc_nativeResolution = -1;
sint32 m_loc_outputResolution = -1;
} m_uniformLocations[2]{};
sint32 m_loc_texture_src_resolution = -1;
sint32 m_loc_input_resolution = -1;
sint32 m_loc_output_resolution = -1;
} m_attributes[2]{};
private:
static const std::string s_copy_shader_source;

View file

@ -202,13 +202,6 @@ VkSampler LatteTextureViewVk::GetDefaultTextureSampler(bool useLinearTexFilter)
VkSamplerCreateInfo samplerInfo{};
samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
// emulate OpenGL minFilters
// see note under: https://docs.vulkan.org/spec/latest/chapters/samplers.html#VkSamplerCreateInfo
// if maxLod = 0 then magnification is always performed
samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST;
samplerInfo.minLod = 0.0f;
samplerInfo.maxLod = 0.25f;
if (useLinearTexFilter)
{
samplerInfo.magFilter = VK_FILTER_LINEAR;
@ -219,9 +212,6 @@ VkSampler LatteTextureViewVk::GetDefaultTextureSampler(bool useLinearTexFilter)
samplerInfo.magFilter = VK_FILTER_NEAREST;
samplerInfo.minFilter = VK_FILTER_NEAREST;
}
samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
if (vkCreateSampler(m_device, &samplerInfo, nullptr, &sampler) != VK_SUCCESS)
{

View file

@ -211,9 +211,6 @@ RendererShaderVk::~RendererShaderVk()
{
while (!list_pipelineInfo.empty())
delete list_pipelineInfo[0];
VkDevice vkDev = VulkanRenderer::GetInstance()->GetLogicalDevice();
vkDestroyShaderModule(vkDev, m_shader_module, nullptr);
}
void RendererShaderVk::Init()

View file

@ -60,7 +60,7 @@ void SwapchainInfoVk::Create()
VkAttachmentDescription colorAttachment = {};
colorAttachment.format = m_surfaceFormat.format;
colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;

View file

@ -70,7 +70,6 @@ struct SwapchainInfoVk
VkSurfaceFormatKHR m_surfaceFormat{};
VkSwapchainKHR m_swapchain{};
Vector2i m_desiredExtent{};
VkExtent2D m_actualExtent{};
uint32 swapchainImageIndex = (uint32)-1;
uint64 m_presentId = 1;
uint64 m_queueDepth = 0; // number of frames with pending presentation requests
@ -93,4 +92,5 @@ private:
VkSemaphore m_currentSemaphore = VK_NULL_HANDLE;
std::array<uint32, 2> m_swapchainQueueFamilyIndices;
VkExtent2D m_actualExtent{};
};

View file

@ -79,13 +79,6 @@ 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
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
{
cemuLog_log(LogType::Force, "Unsupported texture readback format {:04x}", (uint32)textureView->format);

View file

@ -19,7 +19,7 @@ public:
virtual ~VKRMoveableRefCounter()
{
cemu_assert_debug(m_refCount == 0);
cemu_assert_debug(refCount == 0);
// remove references
#ifdef CEMU_DEBUG_ASSERT
@ -30,11 +30,7 @@ public:
}
#endif
for (auto itr : refs)
{
itr->ref->m_refCount--;
if (itr->ref->m_refCount == 0)
itr->ref->RefCountReachedZero();
}
itr->ref->refCount--;
refs.clear();
delete selfRef;
selfRef = nullptr;
@ -45,8 +41,8 @@ public:
VKRMoveableRefCounter(VKRMoveableRefCounter&& rhs) noexcept
{
this->refs = std::move(rhs.refs);
this->m_refCount = rhs.m_refCount;
rhs.m_refCount = 0;
this->refCount = rhs.refCount;
rhs.refCount = 0;
this->selfRef = rhs.selfRef;
rhs.selfRef = nullptr;
this->selfRef->ref = this;
@ -61,7 +57,7 @@ public:
void addRef(VKRMoveableRefCounter* refTarget)
{
this->refs.emplace_back(refTarget->selfRef);
refTarget->m_refCount++;
refTarget->refCount++;
#ifdef CEMU_DEBUG_ASSERT
// add reverse ref
@ -72,23 +68,16 @@ public:
// methods to directly increment/decrement ref counter (for situations where no external object is available)
void incRef()
{
m_refCount++;
this->refCount++;
}
void decRef()
{
m_refCount--;
if (m_refCount == 0)
RefCountReachedZero();
this->refCount--;
}
protected:
virtual void RefCountReachedZero()
{
// does nothing by default
}
int m_refCount{};
int refCount{};
private:
VKRMoveableRefCounterRef* selfRef;
std::vector<VKRMoveableRefCounterRef*> refs;
@ -99,7 +88,7 @@ private:
void moveObj(VKRMoveableRefCounter&& rhs)
{
this->refs = std::move(rhs.refs);
this->m_refCount = rhs.m_refCount;
this->refCount = rhs.refCount;
this->selfRef = rhs.selfRef;
this->selfRef->ref = this;
}
@ -142,25 +131,6 @@ public:
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
{
public:

View file

@ -4,14 +4,6 @@
/* 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)
{
auto cmdBufferId = m_vkr->GetCurrentCommandBufferId();
@ -31,11 +23,11 @@ void VKRSynchronizedRingAllocator::allocateAdditionalUploadBuffer(uint32 sizeReq
AllocatorBuffer_t newBuffer{};
newBuffer.writeIndex = 0;
newBuffer.basePtr = nullptr;
if (m_bufferType == VKR_BUFFER_TYPE::STAGING)
if (m_bufferType == 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);
else if (m_bufferType == VKR_BUFFER_TYPE::INDEX)
else if (m_bufferType == 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);
else if (m_bufferType == VKR_BUFFER_TYPE::STRIDE)
else if (m_bufferType == 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);
else
cemu_assert_debug(false);
@ -108,7 +100,7 @@ VKRSynchronizedRingAllocator::AllocatorReservation_t VKRSynchronizedRingAllocato
void VKRSynchronizedRingAllocator::FlushReservation(AllocatorReservation_t& uploadReservation)
{
cemu_assert_debug(m_bufferType == VKR_BUFFER_TYPE::STAGING); // only the staging buffer isn't coherent
cemu_assert_debug(m_bufferType == BUFFER_TYPE::STAGING); // only the staging buffer isn't coherent
// todo - use nonCoherentAtomSize for flush size (instead of hardcoded constant)
VkMappedMemoryRange flushedRange{};
flushedRange.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE;
@ -175,81 +167,8 @@ 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()
{
VkDevice device = VulkanRenderer::GetInstance()->GetLogicalDevice();
for (auto& i : m_list_chunkInfo)
{
vkFreeMemory(device, i.mem, nullptr);
}
}
uint32 VkTextureChunkedHeap::allocateNewChunk(uint32 chunkIndex, uint32 minimumAllocationSize)
{
cemu_assert_debug(m_list_chunkInfo.size() == chunkIndex);
@ -270,7 +189,8 @@ 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> hostLocalMemoryTypeIndices = m_vkrMemoryManager->FindMemoryTypes(m_typeFilter, 0);
// 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();
};
hostLocalMemoryTypeIndices.erase(std::remove_if(hostLocalMemoryTypeIndices.begin(), hostLocalMemoryTypeIndices.end(), pred), hostLocalMemoryTypeIndices.end());
@ -286,7 +206,7 @@ uint32 VkTextureChunkedHeap::allocateNewChunk(uint32 chunkIndex, uint32 minimumA
allocInfo.memoryTypeIndex = memType;
VkDeviceMemory imageMemory;
VkResult r = vkAllocateMemory(VulkanRenderer::GetInstance()->GetLogicalDevice(), &allocInfo, nullptr, &imageMemory);
VkResult r = vkAllocateMemory(m_device, &allocInfo, nullptr, &imageMemory);
if (r != VK_SUCCESS)
continue;
m_list_chunkInfo[chunkIndex].mem = imageMemory;
@ -301,7 +221,7 @@ uint32 VkTextureChunkedHeap::allocateNewChunk(uint32 chunkIndex, uint32 minimumA
allocInfo.memoryTypeIndex = memType;
VkDeviceMemory imageMemory;
VkResult r = vkAllocateMemory(VulkanRenderer::GetInstance()->GetLogicalDevice(), &allocInfo, nullptr, &imageMemory);
VkResult r = vkAllocateMemory(m_device, &allocInfo, nullptr, &imageMemory);
if (r != VK_SUCCESS)
continue;
m_list_chunkInfo[chunkIndex].mem = imageMemory;
@ -318,76 +238,28 @@ uint32 VkTextureChunkedHeap::allocateNewChunk(uint32 chunkIndex, uint32 minimumA
return 0;
}
/* VkBufferChunkedHeap */
VKRBuffer* VKRBuffer::Create(VKR_BUFFER_TYPE bufferType, size_t bufferSize, VkMemoryPropertyFlags properties)
uint32_t VKRMemoryManager::FindMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties) const
{
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;
VkPhysicalDeviceMemoryProperties memProperties;
vkGetPhysicalDeviceMemoryProperties(m_vkr->GetPhysicalDevice(), &memProperties);
VKRBuffer* bufferObj = new VKRBuffer(buffer, bufferMemory);
// if host visible, then map buffer
void* data = nullptr;
if (properties & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT)
for (uint32 i = 0; i < memProperties.memoryTypeCount; i++)
{
vkMapMemory(VulkanRenderer::GetInstance()->GetLogicalDevice(), bufferMemory, 0, bufferSize, 0, &data);
bufferObj->m_requiresFlush = !HAS_FLAG(properties, VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
if ((typeFilter & (1 << i)) != 0 && (memProperties.memoryTypes[i].propertyFlags & properties) == properties)
return i;
}
bufferObj->m_mappedMemory = (uint8*)data;
return bufferObj;
m_vkr->UnrecoverableError(fmt::format("failed to find suitable memory type ({0:#08x} {1:#08x})", typeFilter, properties).c_str());
return 0;
}
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
bool VKRMemoryManager::FindMemoryType2(uint32 typeFilter, VkMemoryPropertyFlags properties, uint32& memoryIndex) const
{
VkPhysicalDeviceMemoryProperties memProperties;
vkGetPhysicalDeviceMemoryProperties(m_vkr->GetPhysicalDevice(), &memProperties);
for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++)
{
if (typeFilter & (1 << i) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties)
if (typeFilter & (1 << i) && memProperties.memoryTypes[i].propertyFlags == properties)
{
memoryIndex = i;
return true;
@ -458,7 +330,31 @@ size_t VKRMemoryManager::GetTotalMemoryForBufferType(VkBufferUsageFlags usage, V
return total;
}
bool VKRMemoryManager::CreateBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer, VkDeviceMemory& bufferMemory) const
void 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{};
bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
@ -467,7 +363,7 @@ bool VKRMemoryManager::CreateBuffer(VkDeviceSize size, VkBufferUsageFlags usage,
bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
if (vkCreateBuffer(m_vkr->GetLogicalDevice(), &bufferInfo, nullptr, &buffer) != VK_SUCCESS)
{
cemuLog_log(LogType::Force, "Failed to create buffer (CreateBuffer)");
cemuLog_log(LogType::Force, "Failed to create buffer (CreateBuffer2)");
return false;
}
@ -477,7 +373,7 @@ bool VKRMemoryManager::CreateBuffer(VkDeviceSize size, VkBufferUsageFlags usage,
VkMemoryAllocateInfo allocInfo{};
allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
allocInfo.allocationSize = memRequirements.size;
if (!FindMemoryType(memRequirements.memoryTypeBits, properties, allocInfo.memoryTypeIndex))
if (!FindMemoryType2(memRequirements.memoryTypeBits, properties, allocInfo.memoryTypeIndex))
{
vkDestroyBuffer(m_vkr->GetLogicalDevice(), buffer, nullptr);
return false;
@ -490,7 +386,7 @@ bool VKRMemoryManager::CreateBuffer(VkDeviceSize size, VkBufferUsageFlags usage,
if (vkBindBufferMemory(m_vkr->GetLogicalDevice(), buffer, bufferMemory, 0) != VK_SUCCESS)
{
vkDestroyBuffer(m_vkr->GetLogicalDevice(), buffer, nullptr);
cemuLog_log(LogType::Force, "Failed to bind buffer (CreateBuffer)");
cemuLog_log(LogType::Force, "Failed to bind buffer (CreateBuffer2)");
return false;
}
return true;
@ -512,7 +408,7 @@ bool VKRMemoryManager::CreateBufferFromHostMemory(void* hostPointer, VkDeviceSiz
if (vkCreateBuffer(m_vkr->GetLogicalDevice(), &bufferInfo, nullptr, &buffer) != VK_SUCCESS)
{
cemuLog_log(LogType::Force, "Failed to create buffer (CreateBuffer)");
cemuLog_log(LogType::Force, "Failed to create buffer (CreateBuffer2)");
return false;
}
@ -533,7 +429,7 @@ bool VKRMemoryManager::CreateBufferFromHostMemory(void* hostPointer, VkDeviceSiz
allocInfo.pNext = &importHostMem;
if (!FindMemoryType(memRequirements.memoryTypeBits, properties, allocInfo.memoryTypeIndex))
if (!FindMemoryType2(memRequirements.memoryTypeBits, properties, allocInfo.memoryTypeIndex))
{
vkDestroyBuffer(m_vkr->GetLogicalDevice(), buffer, nullptr);
return false;
@ -573,11 +469,11 @@ VkImageMemAllocation* VKRMemoryManager::imageMemoryAllocate(VkImage image)
auto it = map_textureHeap.find(typeFilter);
if (it == map_textureHeap.end())
{
texHeap = new VkTextureChunkedHeap(this, typeFilter);
texHeap = new VkTextureChunkedHeap(this, typeFilter, m_vkr->GetLogicalDevice());
map_textureHeap.emplace(typeFilter, texHeap);
}
else
texHeap = it->second.get();
texHeap = it->second;
// alloc mem from heap
uint32 allocationSize = (uint32)memRequirements.size;

View file

@ -2,36 +2,6 @@
#include "Cafe/HW/Latte/Renderer/Renderer.h"
#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.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
{
@ -44,17 +14,18 @@ struct VkImageMemAllocation
uint32 getAllocationSize() { return allocationSize; }
};
class VkTextureChunkedHeap : private ChunkedHeap<>
class VkTextureChunkedHeap : private ChunkedHeap
{
public:
VkTextureChunkedHeap(class VKRMemoryManager* memoryManager, uint32 typeFilter) : m_vkrMemoryManager(memoryManager), m_typeFilter(typeFilter) { };
~VkTextureChunkedHeap();
VkTextureChunkedHeap(class VKRMemoryManager* memoryManager, uint32 typeFilter, VkDevice device) : m_vkrMemoryManager(memoryManager), m_typeFilter(typeFilter), m_device(device) { };
struct ChunkInfo
{
VkDeviceMemory mem;
};
uint32 allocateNewChunk(uint32 chunkIndex, uint32 minimumAllocationSize) override;
CHAddr allocMem(uint32 size, uint32 alignment)
{
if (alignment < 4)
@ -72,6 +43,11 @@ public:
this->free(addr);
}
void setDevice(VkDevice dev)
{
m_device = dev;
}
VkDeviceMemory getChunkMem(uint32 index)
{
if (index >= m_list_chunkInfo.size())
@ -81,75 +57,29 @@ public:
void getStatistics(uint32& totalHeapSize, uint32& allocatedBytes) const
{
totalHeapSize = m_numHeapBytes;
allocatedBytes = m_numAllocatedBytes;
totalHeapSize = numHeapBytes;
allocatedBytes = numAllocatedBytes;
}
private:
uint32 allocateNewChunk(uint32 chunkIndex, uint32 minimumAllocationSize) override;
VkDevice m_device;
uint32 m_typeFilter{ 0xFFFFFFFF };
class VKRMemoryManager* m_vkrMemoryManager;
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
class VKRSynchronizedRingAllocator
{
public:
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) {};
enum class BUFFER_TYPE
{
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();
struct BufferSyncPoint_t
{
@ -196,53 +126,13 @@ private:
const class VulkanRenderer* m_vkr;
const class VKRMemoryManager* m_vkrMemMgr;
const VKR_BUFFER_TYPE m_bufferType;
const BUFFER_TYPE m_bufferType;
const uint32 m_minimumBufferAllocSize;
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();
class VKRMemoryManager
@ -250,15 +140,15 @@ class VKRMemoryManager
friend class VKRSynchronizedRingAllocator;
public:
VKRMemoryManager(class VulkanRenderer* renderer) :
m_stagingBuffer(renderer, this, VKR_BUFFER_TYPE::STAGING, 32u * 1024 * 1024),
m_indexBuffer(this, VKR_BUFFER_TYPE::INDEX, 4u * 1024 * 1024),
m_vertexStrideMetalBuffer(renderer, this, VKR_BUFFER_TYPE::STRIDE, 4u * 1024 * 1024)
m_stagingBuffer(renderer, this, VKRSynchronizedRingAllocator::BUFFER_TYPE::STAGING, 32u * 1024 * 1024),
m_indexBuffer(renderer, this, VKRSynchronizedRingAllocator::BUFFER_TYPE::INDEX, 4u * 1024 * 1024),
m_vertexStrideMetalBuffer(renderer, this, VKRSynchronizedRingAllocator::BUFFER_TYPE::STRIDE, 4u * 1024 * 1024)
{
m_vkr = renderer;
}
// texture memory management
std::unordered_map<uint32, std::unique_ptr<VkTextureChunkedHeap>> map_textureHeap; // one heap per memory type
std::unordered_map<uint32, VkTextureChunkedHeap*> map_textureHeap; // one heap per memory type
std::vector<uint8> m_textureUploadBuffer;
// texture upload buffer
@ -277,7 +167,7 @@ public:
}
VKRSynchronizedRingAllocator& getStagingAllocator() { return m_stagingBuffer; }; // allocator for texture/attribute/uniform uploads
VKRSynchronizedHeapAllocator& GetIndexAllocator() { return m_indexBuffer; }; // allocator for index data
VKRSynchronizedRingAllocator& getIndexAllocator() { return m_indexBuffer; }; // allocator for index data
VKRSynchronizedRingAllocator& getMetalStrideWorkaroundAllocator() { return m_vertexStrideMetalBuffer; }; // allocator for stride-adjusted vertex data
void cleanupBuffers(uint64 latestFinishedCommandBufferId)
@ -288,7 +178,9 @@ public:
m_vertexStrideMetalBuffer.CleanupBuffer(latestFinishedCommandBufferId);
}
bool FindMemoryType(uint32 typeFilter, VkMemoryPropertyFlags properties, uint32& memoryIndex) const; // searches for exact properties. Can gracefully fail without throwing exception (returns false)
// memory helpers
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;
// image memory allocation
@ -298,7 +190,8 @@ public:
// buffer management
size_t GetTotalMemoryForBufferType(VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, size_t minimumBufferSize = 16 * 1024 * 1024);
bool CreateBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer, VkDeviceMemory& bufferMemory) const; // same as CreateBuffer but doesn't throw exception on failure
void CreateBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer, VkDeviceMemory& bufferMemory) const;
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;
void DeleteBuffer(VkBuffer& buffer, VkDeviceMemory& deviceMem) const;
@ -309,6 +202,6 @@ public:
private:
class VulkanRenderer* m_vkr;
VKRSynchronizedRingAllocator m_stagingBuffer;
VKRSynchronizedHeapAllocator m_indexBuffer;
VKRSynchronizedRingAllocator m_indexBuffer;
VKRSynchronizedRingAllocator m_vertexStrideMetalBuffer;
};

View file

@ -165,7 +165,6 @@ VKFUNC_DEVICE(vkCmdDraw);
VKFUNC_DEVICE(vkCmdCopyBufferToImage);
VKFUNC_DEVICE(vkCmdCopyImageToBuffer);
VKFUNC_DEVICE(vkCmdClearColorImage);
VKFUNC_DEVICE(vkCmdClearAttachments);
VKFUNC_DEVICE(vkCmdBindIndexBuffer);
VKFUNC_DEVICE(vkCmdBindVertexBuffers);
VKFUNC_DEVICE(vkCmdDrawIndexed);
@ -199,7 +198,6 @@ VKFUNC_DEVICE(vkCmdEndTransformFeedbackEXT);
// query
VKFUNC_DEVICE(vkCreateQueryPool);
VKFUNC_DEVICE(vkDestroyQueryPool);
VKFUNC_DEVICE(vkCmdResetQueryPool);
VKFUNC_DEVICE(vkCmdBeginQuery);
VKFUNC_DEVICE(vkCmdEndQuery);
@ -238,7 +236,6 @@ VKFUNC_DEVICE(vkAllocateDescriptorSets);
VKFUNC_DEVICE(vkFreeDescriptorSets);
VKFUNC_DEVICE(vkUpdateDescriptorSets);
VKFUNC_DEVICE(vkCreateDescriptorPool);
VKFUNC_DEVICE(vkDestroyDescriptorPool);
VKFUNC_DEVICE(vkDestroyDescriptorSetLayout);
#undef VKFUNC_INIT

View file

@ -439,7 +439,7 @@ VulkanRenderer::VulkanRenderer()
GetDeviceFeatures();
// init memory manager
memoryManager.reset(new VKRMemoryManager(this));
memoryManager = new VKRMemoryManager(this);
try
{
@ -577,15 +577,15 @@ VulkanRenderer::VulkanRenderer()
void* bufferPtr;
// init ringbuffer for uniform vars
m_uniformVarBufferMemoryIsCoherent = false;
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))
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))
m_uniformVarBufferMemoryIsCoherent = true;
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))
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))
m_uniformVarBufferMemoryIsCoherent = true; // unified memory
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))
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))
m_uniformVarBufferMemoryIsCoherent = true;
else
{
memoryManager->CreateBuffer(UNIFORMVAR_RINGBUFFER_SIZE, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, m_uniformVarBuffer, m_uniformVarBufferMemory);
memoryManager->CreateBuffer2(UNIFORMVAR_RINGBUFFER_SIZE, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, m_uniformVarBuffer, m_uniformVarBufferMemory);
}
if (!m_uniformVarBufferMemoryIsCoherent)
@ -628,31 +628,6 @@ VulkanRenderer::~VulkanRenderer()
m_pipeline_cache_semaphore.notify();
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
ImGui_ImplVulkan_Shutdown();
@ -665,6 +640,10 @@ VulkanRenderer::~VulkanRenderer()
memoryManager->DeleteBuffer(m_xfbRingBuffer, m_xfbRingBufferMemory);
memoryManager->DeleteBuffer(m_occlusionQueries.bufferQueryResults, m_occlusionQueries.memoryQueryResults);
memoryManager->DeleteBuffer(m_bufferCache, m_bufferCacheMemory);
// texture memory
// todo
// upload buffers
// todo
m_padSwapchainInfo = nullptr;
m_mainSwapchainInfo = nullptr;
@ -687,20 +666,12 @@ VulkanRenderer::~VulkanRenderer()
it = VK_NULL_HANDLE;
}
for(auto& sem : m_commandBufferSemaphores)
{
vkDestroySemaphore(m_logicalDevice, sem, nullptr);
sem = VK_NULL_HANDLE;
}
if (m_pipelineLayout != VK_NULL_HANDLE)
vkDestroyPipelineLayout(m_logicalDevice, m_pipelineLayout, nullptr);
if (m_commandPool != VK_NULL_HANDLE)
vkDestroyCommandPool(m_logicalDevice, m_commandPool, nullptr);
VKRObjectSampler::DestroyCache();
// destroy debug callback
if (m_debugCallback)
{
@ -708,12 +679,6 @@ VulkanRenderer::~VulkanRenderer()
vkDestroyDebugUtilsMessengerEXT(m_instance, m_debugCallback, nullptr);
}
while(!m_destructionQueue.empty())
ProcessDestructionQueue();
// destroy memory manager
memoryManager.reset();
// destroy instance, devices
if (m_instance != VK_NULL_HANDLE)
{
@ -725,6 +690,9 @@ VulkanRenderer::~VulkanRenderer()
vkDestroyInstance(m_instance, nullptr);
}
// destroy memory manager
delete memoryManager;
// crashes?
//glslang::FinalizeProcess();
}
@ -855,14 +823,7 @@ void VulkanRenderer::HandleScreenshotRequest(LatteTextureView* texView, bool pad
VkMemoryAllocateInfo allocInfo{};
allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
allocInfo.allocationSize = memRequirements.size;
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;
allocInfo.memoryTypeIndex = memoryManager->FindMemoryType(memRequirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
if (vkAllocateMemory(m_logicalDevice, &allocInfo, nullptr, &imageMemory) != VK_SUCCESS)
{
@ -1645,7 +1606,6 @@ void VulkanRenderer::Initialize()
void VulkanRenderer::Shutdown()
{
DeleteFontTextures();
Renderer::Shutdown();
SubmitCommandBuffer();
WaitDeviceIdle();
@ -1846,6 +1806,7 @@ void VulkanRenderer::ImguiEnd()
vkCmdEndRenderPass(m_state.currentCommandBuffer);
}
std::vector<LatteTextureVk*> g_imgui_textures; // TODO manage better
ImTextureID VulkanRenderer::GenerateTexture(const std::vector<uint8>& data, const Vector2i& size)
{
try
@ -1875,7 +1836,6 @@ void VulkanRenderer::DeleteTexture(ImTextureID id)
void VulkanRenderer::DeleteFontTextures()
{
WaitDeviceIdle();
ImGui_ImplVulkan_DestroyFontsTexture();
}
@ -1914,7 +1874,7 @@ void VulkanRenderer::InitFirstCommandBuffer()
vkResetFences(m_logicalDevice, 1, &m_cmd_buffer_fences[m_commandBufferIndex]);
VkCommandBufferBeginInfo beginInfo{};
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
beginInfo.flags = VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT;
vkBeginCommandBuffer(m_state.currentCommandBuffer, &beginInfo);
vkCmdSetViewport(m_state.currentCommandBuffer, 0, 1, &m_state.currentViewport);
@ -2036,7 +1996,7 @@ void VulkanRenderer::SubmitCommandBuffer(VkSemaphore signalSemaphore, VkSemaphor
VkCommandBufferBeginInfo beginInfo{};
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
beginInfo.flags = VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT;
vkBeginCommandBuffer(m_state.currentCommandBuffer, &beginInfo);
// make sure some states are set for this command buffer
@ -2557,8 +2517,9 @@ VkPipeline VulkanRenderer::backbufferBlit_createGraphicsPipeline(VkDescriptorSet
hash += (uint64)(chainInfo.m_usesSRGB);
hash += ((uint64)padView) << 1;
const auto it = m_backbufferBlitPipelineCache.find(hash);
if (it != m_backbufferBlitPipelineCache.cend())
static std::unordered_map<uint64, VkPipeline> s_pipeline_cache;
const auto it = s_pipeline_cache.find(hash);
if (it != s_pipeline_cache.cend())
return it->second;
std::vector<VkPipelineShaderStageCreateInfo> shaderStages;
@ -2620,18 +2581,10 @@ VkPipeline VulkanRenderer::backbufferBlit_createGraphicsPipeline(VkDescriptorSet
colorBlending.blendConstants[2] = 0.0f;
colorBlending.blendConstants[3] = 0.0f;
VkPushConstantRange pushConstantRange{
.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT,
.offset = 0,
.size = 3 * sizeof(float) * 2 // 3 vec2's
};
VkPipelineLayoutCreateInfo pipelineLayoutInfo{};
pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
pipelineLayoutInfo.setLayoutCount = 1;
pipelineLayoutInfo.pSetLayouts = &descriptorLayout;
pipelineLayoutInfo.pushConstantRangeCount = 1;
pipelineLayoutInfo.pPushConstantRanges = &pushConstantRange;
VkResult result = vkCreatePipelineLayout(m_logicalDevice, &pipelineLayoutInfo, nullptr, &m_pipelineLayout);
if (result != VK_SUCCESS)
@ -2662,7 +2615,7 @@ VkPipeline VulkanRenderer::backbufferBlit_createGraphicsPipeline(VkDescriptorSet
throw std::runtime_error(fmt::format("Failed to create graphics pipeline: {}", result));
}
m_backbufferBlitPipelineCache[hash] = pipeline;
s_pipeline_cache[hash] = pipeline;
m_pipeline_cache_semaphore.notify();
return pipeline;
@ -2959,6 +2912,9 @@ void VulkanRenderer::DrawBackbufferQuad(LatteTextureView* texView, RendererOutpu
LatteTextureViewVk* texViewVk = (LatteTextureViewVk*)texView;
draw_endRenderPass();
if (clearBackground)
ClearColorbuffer(padView);
// barrier for input texture
VkMemoryBarrier memoryBarrier{};
memoryBarrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER;
@ -2995,40 +2951,11 @@ void VulkanRenderer::DrawBackbufferQuad(LatteTextureView* texView, RendererOutpu
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);
m_state.currentPipeline = pipeline;
vkCmdBindDescriptorSets(m_state.currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipelineLayout, 0, 1, &descriptSet, 0, nullptr);
// update push constants
Vector2f pushData[3];
// textureSrcResolution
sint32 effectiveWidth, effectiveHeight;
texView->baseTexture->GetEffectiveSize(effectiveWidth, effectiveHeight, 0);
pushData[0] = {(float)effectiveWidth, (float)effectiveHeight};
// nativeResolution
pushData[1] = {
(float)texViewVk->baseTexture->width,
(float)texViewVk->baseTexture->height,
};
// outputResolution
pushData[2] = {(float)imageWidth,(float)imageHeight};
vkCmdPushConstants(m_state.currentCommandBuffer, m_pipelineLayout, VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(float) * 2 * 3, &pushData);
vkCmdDraw(m_state.currentCommandBuffer, 6, 1, 0, 0);
vkCmdEndRenderPass(m_state.currentCommandBuffer);
@ -3069,8 +2996,9 @@ VkDescriptorSet VulkanRenderer::backbufferBlit_createDescriptorSet(VkDescriptorS
hash += (uint64)texViewVk->GetViewRGBA();
hash += (uint64)texViewVk->GetDefaultTextureSampler(useLinearTexFilter);
const auto it = m_backbufferBlitDescriptorSetCache.find(hash);
if (it != m_backbufferBlitDescriptorSetCache.cend())
static std::unordered_map<uint64, VkDescriptorSet> s_set_cache;
const auto it = s_set_cache.find(hash);
if (it != s_set_cache.cend())
return it->second;
VkDescriptorSetAllocateInfo allocInfo = {};
@ -3101,7 +3029,7 @@ VkDescriptorSet VulkanRenderer::backbufferBlit_createDescriptorSet(VkDescriptorS
vkUpdateDescriptorSets(m_logicalDevice, 1, &descriptorWrites, 0, nullptr);
performanceMonitor.vk.numDescriptorSamplerTextures.increment();
m_backbufferBlitDescriptorSetCache[hash] = result;
s_set_cache[hash] = result;
return result;
}
@ -3234,8 +3162,7 @@ VkDescriptorSetInfo::~VkDescriptorSetInfo()
performanceMonitor.vk.numDescriptorDynUniformBuffers.decrement(statsNumDynUniformBuffers);
performanceMonitor.vk.numDescriptorStorageBuffers.decrement(statsNumStorageBuffers);
auto renderer = VulkanRenderer::GetInstance();
renderer->ReleaseDestructibleObject(m_vkObjDescriptorSet);
VulkanRenderer::GetInstance()->ReleaseDestructibleObject(m_vkObjDescriptorSet);
m_vkObjDescriptorSet = nullptr;
}
@ -3745,7 +3672,7 @@ void VulkanRenderer::bufferCache_copyStreamoutToMainBuffer(uint32 srcOffset, uin
void VulkanRenderer::AppendOverlayDebugInfo()
{
ImGui::Text("--- Vulkan debug info ---");
ImGui::Text("--- Vulkan info ---");
ImGui::Text("GfxPipelines %u", performanceMonitor.vk.numGraphicPipelines.get());
ImGui::Text("DescriptorSets %u", performanceMonitor.vk.numDescriptorSets.get());
ImGui::Text("DS ImgSamplers %u", performanceMonitor.vk.numDescriptorSamplerTextures.get());
@ -3753,7 +3680,6 @@ void VulkanRenderer::AppendOverlayDebugInfo()
ImGui::Text("DS StorageBuf %u", performanceMonitor.vk.numDescriptorStorageBuffers.get());
ImGui::Text("Images %u", performanceMonitor.vk.numImages.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("Framebuffer %u", performanceMonitor.vk.numFramebuffer.get());
m_spinlockDestructionQueue.lock();
@ -3763,7 +3689,7 @@ void VulkanRenderer::AppendOverlayDebugInfo()
ImGui::Text("BeginRP/f %u", performanceMonitor.vk.numBeginRenderpassPerFrame.get());
ImGui::Text("Barriers/f %u", performanceMonitor.vk.numDrawBarriersPerFrame.get());
ImGui::Text("--- Cache debug info ---");
ImGui::Text("--- Cache info ---");
uint32 bufferCacheHeapSize = 0;
uint32 bufferCacheAllocationSize = 0;
@ -3783,7 +3709,7 @@ void VulkanRenderer::AppendOverlayDebugInfo()
ImGui::SameLine(60.0f);
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::SameLine(60.0f);
ImGui::Text("%06uKB / %06uKB Buffers: %u", ((uint32)(totalSize - freeSize) + 1023) / 1024, ((uint32)totalSize + 1023) / 1024, (uint32)numBuffers);
@ -3799,7 +3725,7 @@ void VKRDestructibleObject::flagForCurrentCommandBuffer()
bool VKRDestructibleObject::canDestroy()
{
if (m_refCount > 0)
if (refCount > 0)
return false;
return VulkanRenderer::GetInstance()->HasCommandBufferFinished(m_lastCmdBufferId);
}
@ -3840,111 +3766,6 @@ VKRObjectTextureView::~VKRObjectTextureView()
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)
{
// generate helper hash for pipeline state

View file

@ -137,8 +137,8 @@ class VulkanRenderer : public Renderer
public:
// memory management
std::unique_ptr<VKRMemoryManager> memoryManager;
VKRMemoryManager* GetMemoryManager() const { return memoryManager.get(); };
VKRMemoryManager* memoryManager{};
VKRMemoryManager* GetMemoryManager() const { return memoryManager; };
VkSupportedFormatInfo_t m_supportedFormatInfo;
@ -328,9 +328,8 @@ public:
RendererShader* shader_create(RendererShader::ShaderType type, uint64 baseHash, uint64 auxHash, const std::string& source, bool isGameShader, bool isGfxPackShader) override;
IndexAllocation indexData_reserveIndexMemory(uint32 size) override;
void indexData_releaseIndexMemory(IndexAllocation& allocation) override;
void indexData_uploadIndexMemory(IndexAllocation& allocation) override;
void* indexData_reserveIndexMemory(uint32 size, uint32& offset, uint32& bufferIndex) override;
void indexData_uploadIndexMemory(uint32 offset, uint32 size) override;
// externally callable
void GetTextureFormatInfoVK(Latte::E_GX2SURFFMT format, bool isDepth, Latte::E_DIM dim, sint32 width, sint32 height, FormatInfoVK* formatInfoOut);
@ -583,8 +582,6 @@ private:
std::shared_mutex m_pipeline_cache_save_mutex;
std::thread m_pipeline_cache_save_thread;
VkPipelineCache m_pipeline_cache{ nullptr };
std::unordered_map<uint64, VkPipeline> m_backbufferBlitPipelineCache;
std::unordered_map<uint64, VkDescriptorSet> m_backbufferBlitDescriptorSetCache;
VkPipelineLayout m_pipelineLayout{nullptr};
VkCommandPool m_commandPool{ nullptr };
@ -862,7 +859,7 @@ private:
memBarrier.pNext = nullptr;
VkPipelineStageFlags srcStages = VK_PIPELINE_STAGE_TRANSFER_BIT;
VkPipelineStageFlags dstStages = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT;
VkPipelineStageFlags dstStages = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
memBarrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT;
memBarrier.dstAccessMask = 0;

View file

@ -357,20 +357,18 @@ PipelineInfo* VulkanRenderer::draw_getOrCreateGraphicsPipeline(uint32 indexCount
return draw_createGraphicsPipeline(indexCount);
}
Renderer::IndexAllocation VulkanRenderer::indexData_reserveIndexMemory(uint32 size)
void* VulkanRenderer::indexData_reserveIndexMemory(uint32 size, uint32& offset, uint32& bufferIndex)
{
VKRSynchronizedHeapAllocator::AllocatorReservation* resv = memoryManager->GetIndexAllocator().AllocateBufferMemory(size, 32);
return { resv->memPtr, resv };
auto& indexAllocator = this->memoryManager->getIndexAllocator();
auto resv = indexAllocator.AllocateBufferMemory(size, 32);
offset = resv.bufferOffset;
bufferIndex = resv.bufferIndex;
return resv.memPtr;
}
void VulkanRenderer::indexData_releaseIndexMemory(IndexAllocation& allocation)
void VulkanRenderer::indexData_uploadIndexMemory(uint32 offset, uint32 size)
{
memoryManager->GetIndexAllocator().FreeReservation((VKRSynchronizedHeapAllocator::AllocatorReservation*)allocation.rendererInternal);
}
void VulkanRenderer::indexData_uploadIndexMemory(IndexAllocation& allocation)
{
memoryManager->GetIndexAllocator().FlushReservation((VKRSynchronizedHeapAllocator::AllocatorReservation*)allocation.rendererInternal);
// does nothing since the index buffer memory is coherent
}
float s_vkUniformData[512 * 4];
@ -729,6 +727,7 @@ VkDescriptorSetInfo* VulkanRenderer::draw_getOrCreateDescriptorSet(PipelineInfo*
VkSamplerCustomBorderColorCreateInfoEXT samplerCustomBorderColor{};
VkSampler sampler;
VkSamplerCreateInfo samplerInfo{};
samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
@ -901,9 +900,9 @@ VkDescriptorSetInfo* VulkanRenderer::draw_getOrCreateDescriptorSet(PipelineInfo*
}
}
VKRObjectSampler* samplerObj = VKRObjectSampler::GetOrCreateSampler(&samplerInfo);
vkObjDS->addRef(samplerObj);
info.sampler = samplerObj->GetSampler();
if (vkCreateSampler(m_logicalDevice, &samplerInfo, nullptr, &sampler) != VK_SUCCESS)
UnrecoverableError("Failed to create texture sampler");
info.sampler = sampler;
textureArray.emplace_back(info);
}
@ -1164,17 +1163,28 @@ void VulkanRenderer::draw_prepareDescriptorSets(PipelineInfo* pipeline_info, VkD
const auto geometryShader = LatteSHRC_GetActiveGeometryShader();
const auto pixelShader = LatteSHRC_GetActivePixelShader();
auto prepareShaderDescriptors = [this, &pipeline_info](LatteDecompilerShader* shader) -> VkDescriptorSetInfo* {
if (!shader)
return nullptr;
auto descriptorSetInfo = draw_getOrCreateDescriptorSet(pipeline_info, shader);
descriptorSetInfo->m_vkObjDescriptorSet->flagForCurrentCommandBuffer();
return descriptorSetInfo;
};
vertexDS = prepareShaderDescriptors(vertexShader);
pixelDS = prepareShaderDescriptors(pixelShader);
geometryDS = prepareShaderDescriptors(geometryShader);
if (vertexShader)
{
auto descriptorSetInfo = draw_getOrCreateDescriptorSet(pipeline_info, vertexShader);
descriptorSetInfo->m_vkObjDescriptorSet->flagForCurrentCommandBuffer();
vertexDS = descriptorSetInfo;
}
if (pixelShader)
{
auto descriptorSetInfo = draw_getOrCreateDescriptorSet(pipeline_info, pixelShader);
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()
@ -1405,15 +1415,14 @@ void VulkanRenderer::draw_execute(uint32 baseVertex, uint32 baseInstance, uint32
uint32 hostIndexCount;
uint32 indexMin = 0;
uint32 indexMax = 0;
Renderer::IndexAllocation indexAllocation;
LatteIndices_decode(memory_getPointerFromVirtualOffset(indexDataMPTR), indexType, count, primitiveMode, indexMin, indexMax, hostIndexType, hostIndexCount, indexAllocation);
VKRSynchronizedHeapAllocator::AllocatorReservation* indexReservation = (VKRSynchronizedHeapAllocator::AllocatorReservation*)indexAllocation.rendererInternal;
uint32 indexBufferOffset = 0;
uint32 indexBufferIndex = 0;
LatteIndices_decode(memory_getPointerFromVirtualOffset(indexDataMPTR), indexType, count, primitiveMode, indexMin, indexMax, hostIndexType, hostIndexCount, indexBufferOffset, indexBufferIndex);
// update index binding
bool isPrevIndexData = false;
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)
{
m_state.activeIndexType = hostIndexType;
@ -1426,7 +1435,7 @@ void VulkanRenderer::draw_execute(uint32 baseVertex, uint32 baseInstance, uint32
vkType = VK_INDEX_TYPE_UINT32;
else
cemu_assert(false);
vkCmdBindIndexBuffer(m_state.currentCommandBuffer, indexReservation->vkBuffer, indexBufferOffset, vkType);
vkCmdBindIndexBuffer(m_state.currentCommandBuffer, memoryManager->getIndexAllocator().GetBufferByIndex(indexBufferIndex), indexBufferOffset, vkType);
}
else
isPrevIndexData = true;

View file

@ -76,30 +76,6 @@ struct CopySurfacePipelineInfo
CopySurfacePipelineInfo() = default;
CopySurfacePipelineInfo(VkDevice device) : m_device(device) {}
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;
@ -866,9 +842,5 @@ void VulkanRenderer::surfaceCopy_notifyTextureRelease(LatteTextureVk* hostTextur
void VulkanRenderer::surfaceCopy_cleanup()
{
for(auto& i : m_copySurfacePipelineCache)
{
delete i.second;
}
m_copySurfacePipelineCache = {};
// todo - release m_copySurfacePipelineCache etc
}

View file

@ -469,7 +469,7 @@ namespace iosu
entry->ukn0C = 0;
entry->sizeA = _swapEndianU64(0); // ukn
entry->sizeB = _swapEndianU64(dirSize);
entry->time = _swapEndianU64((coreinit::OSGetTime() / ESPRESSO_TIMER_CLOCK));
entry->time = _swapEndianU64((coreinit::coreinit_getOSTime() / ESPRESSO_TIMER_CLOCK));
sprintf(entry->path, "%susr/save/%08x/%08x/meta/", devicePath, (uint32)(titleId >> 32), (uint32)(titleId & 0xFFFFFFFF));
count++;
}
@ -504,7 +504,7 @@ namespace iosu
entry->ukn0C = 0;
entry->sizeA = _swapEndianU64(0);
entry->sizeB = _swapEndianU64(0);
entry->time = _swapEndianU64((coreinit::OSGetTime() / ESPRESSO_TIMER_CLOCK));
entry->time = _swapEndianU64((coreinit::coreinit_getOSTime() / ESPRESSO_TIMER_CLOCK));
sprintf(entry->path, "%susr/save/%08x/%08x/meta/", devicePath, (uint32)(titleId >> 32), (uint32)(titleId & 0xFFFFFFFF));
count++;
}
@ -584,7 +584,7 @@ namespace iosu
uint64 _ACPGetTimestamp()
{
return coreinit::OSGetTime() / ESPRESSO_TIMER_CLOCK;
return coreinit::coreinit_getOSTime() / ESPRESSO_TIMER_CLOCK;
}
nnResult ACPUpdateSaveTimeStamp(uint32 persistentId, uint64 titleId, ACPDeviceType deviceType)

View file

@ -116,7 +116,7 @@ typedef struct
/* +0x34 */ uint32be ukn34;
/* +0x38 */ uint32be ukn38;
/* +0x3C */ uint32be ukn3C;
/* +0x40 */ uint32be minimumToolkitVersion;
/* +0x40 */ uint32be toolkitVersion;
/* +0x44 */ uint32be ukn44;
/* +0x48 */ uint32be ukn48;
/* +0x4C */ uint32be ukn4C;

View file

@ -181,12 +181,12 @@ namespace camera
sint32 CAMInit(uint32 cameraId, CAMInitInfo_t* camInitInfo, uint32be* error)
{
CameraInstance* camInstance = new CameraInstance(camInitInfo->width, camInitInfo->height, camInitInfo->handlerFuncPtr);
*error = 0; // Hunter's Trophy 2 will fail to boot if we don't set this
std::unique_lock<std::recursive_mutex> _lock(g_mutex_camera);
if (g_cameraCounter == 0)
{
coreinit::OSCreateAlarm(g_alarm_camera.GetPtr());
coreinit::OSSetPeriodicAlarm(g_alarm_camera.GetPtr(), coreinit::OSGetTime(), (uint64)ESPRESSO_TIMER_CLOCK / 60ull, RPLLoader_MakePPCCallable(ppcCAMUpdate60));
coreinit::OSSetPeriodicAlarm(g_alarm_camera.GetPtr(), coreinit::coreinit_getOSTime(), (uint64)ESPRESSO_TIMER_CLOCK / 60ull, RPLLoader_MakePPCCallable(ppcCAMUpdate60));
}
g_cameraCounter++;

View file

@ -166,7 +166,7 @@ namespace coreinit
void alarm_update()
{
cemu_assert_debug(!__OSHasSchedulerLock());
uint64 currentTick = coreinit::OSGetTime();
uint64 currentTick = coreinit::coreinit_getOSTime();
if (!OSHostAlarm::quickCheckForAlarm(currentTick))
return;
__OSLockScheduler();
@ -233,7 +233,7 @@ namespace coreinit
if (period == 0)
return;
uint64 currentTime = OSGetTime();
uint64 currentTime = coreinit_getOSTime();
uint64 ticksSinceStart = currentTime - startTime;
uint64 numPeriods = ticksSinceStart / period;
@ -267,7 +267,7 @@ namespace coreinit
void OSSetAlarm(OSAlarm_t* alarm, uint64 delayInTicks, MPTR handlerFunc)
{
__OSLockScheduler();
__OSInitiateAlarm(alarm, OSGetTime() + delayInTicks, 0, handlerFunc, false);
__OSInitiateAlarm(alarm, coreinit_getOSTime() + delayInTicks, 0, handlerFunc, false);
__OSUnlockScheduler();
}
@ -310,7 +310,7 @@ namespace coreinit
while( true )
{
OSWaitEvent(g_alarmEvent.GetPtr());
uint64 currentTick = OSGetTime();
uint64 currentTick = coreinit_getOSTime();
while (true)
{
// get alarm to fire

View file

@ -156,22 +156,12 @@ namespace coreinit
return &currentThread->crt.eh_mem_manage;
}
sint32be* __gh_errno_ptr()
void* __gh_errno_ptr()
{
OSThread_t* currentThread = coreinit::OSGetCurrentThread();
return &currentThread->context.ghs_errno;
}
void __gh_set_errno(sint32 errNo)
{
*__gh_errno_ptr() = errNo;
}
sint32 __gh_get_errno()
{
return *__gh_errno_ptr();
}
void* __get_eh_store_globals()
{
OSThread_t* currentThread = coreinit::OSGetCurrentThread();
@ -282,8 +272,6 @@ namespace coreinit
cafeExportRegister("coreinit", __get_eh_globals, LogType::Placeholder);
cafeExportRegister("coreinit", __get_eh_mem_manage, LogType::Placeholder);
cafeExportRegister("coreinit", __gh_errno_ptr, LogType::Placeholder);
cafeExportRegister("coreinit", __gh_set_errno, LogType::Placeholder);
cafeExportRegister("coreinit", __gh_get_errno, LogType::Placeholder);
cafeExportRegister("coreinit", __get_eh_store_globals, LogType::Placeholder);
cafeExportRegister("coreinit", __get_eh_store_globals_tdeh, LogType::Placeholder);

View file

@ -4,9 +4,5 @@ namespace coreinit
{
void PrepareGHSRuntime();
sint32be* __gh_errno_ptr();
void __gh_set_errno(sint32 errNo);
sint32 __gh_get_errno();
void InitializeGHS();
};

View file

@ -86,11 +86,11 @@ namespace coreinit
else
{
// loop until lock acquired or timeout occurred
uint64 timeoutValue = OSGetSystemTime() + coreinit::EspressoTime::ConvertNsToTimerTicks(timeout);
uint64 timeoutValue = coreinit_getTimerTick() + coreinit::EspressoTime::ConvertNsToTimerTicks(timeout);
while (!spinlock->ownerThread.atomic_compare_exchange(nullptr, currentThread))
{
OSYieldThread();
if (OSGetSystemTime() >= timeoutValue)
if (coreinit_getTimerTick() >= timeoutValue)
{
return false;
}
@ -182,11 +182,11 @@ namespace coreinit
else
{
// loop until lock acquired or timeout occurred
uint64 timeoutValue = OSGetSystemTime() + coreinit::EspressoTime::ConvertNsToTimerTicks(timeout);
uint64 timeoutValue = coreinit_getTimerTick() + coreinit::EspressoTime::ConvertNsToTimerTicks(timeout);
while (!spinlock->ownerThread.atomic_compare_exchange(nullptr, currentThread))
{
OSYieldThread();
if (OSGetSystemTime() >= timeoutValue)
if (coreinit_getTimerTick() >= timeoutValue)
{
return false;
}

View file

@ -73,6 +73,8 @@ namespace coreinit
}
}
uint64 coreinit_getOSTime();
bool OSWaitEventWithTimeout(OSEvent* event, uint64 timeout)
{
__OSLockScheduler();
@ -93,14 +95,14 @@ namespace coreinit
// 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
if (timeout < 0x00FFFFFFFFFFFFFFULL)
timeout = timeout * 98ULL / 100ULL; // 98% (we want the function to return slightly before the actual timeout)
WaitEventWithTimeoutData data;
data.thread = OSGetCurrentThread();
data.threadQueue = &event->threadQueue;
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());
coreinit::OSHostAlarmDestroy(hostAlarm);
if (data.hasTimeout)

View file

@ -655,7 +655,7 @@ namespace coreinit
StackAllocator<OSThreadQueue> _threadQueue;
OSInitThreadQueue(_threadQueue.GetPointer());
__OSLockScheduler();
OSHostAlarm* hostAlarm = OSHostAlarmCreate(OSGetTime() + ticks, 0, _OSSleepTicks_alarmHandler, _threadQueue.GetPointer());
OSHostAlarm* hostAlarm = OSHostAlarmCreate(coreinit_getOSTime() + ticks, 0, _OSSleepTicks_alarmHandler, _threadQueue.GetPointer());
_threadQueue.GetPointer()->queueAndWait(OSGetCurrentThread());
OSHostAlarmDestroy(hostAlarm);
__OSUnlockScheduler();
@ -1114,13 +1114,13 @@ namespace coreinit
thread->requestFlags = (OSThread_t::REQUEST_FLAG_BIT)(thread->requestFlags & OSThread_t::REQUEST_FLAG_CANCEL); // remove all flags except cancel flag
// update total cycles
sint64 executedCycles = (sint64)thread->quantumTicks - (sint64)hCPU->remainingCycles;
executedCycles = std::max<sint64>(executedCycles, 0);
if (executedCycles < (sint64)hCPU->skippedCycles)
uint64 remainingCycles = std::min((uint64)hCPU->remainingCycles, (uint64)thread->quantumTicks);
uint64 executedCycles = thread->quantumTicks - remainingCycles;
if (executedCycles < hCPU->skippedCycles)
executedCycles = 0;
else
executedCycles -= hCPU->skippedCycles;
thread->totalCycles += (uint64)executedCycles;
thread->totalCycles += executedCycles;
// store context and set current thread to null
__OSThreadStoreContext(hCPU, thread);
OSSetCurrentThread(OSGetCoreId(), nullptr);

View file

@ -38,7 +38,7 @@ struct OSContext_t
/* +0x1E0 */ uint64be fp_ps1[32];
/* +0x2E0 */ uint64be coretime[3];
/* +0x2F8 */ uint64be starttime;
/* +0x300 */ sint32be ghs_errno; // returned by __gh_errno_ptr() (used by socketlasterr)
/* +0x300 */ uint32be ghs_errno; // returned by __gh_errno_ptr() (used by socketlasterr)
/* +0x304 */ uint32be affinity;
/* +0x308 */ uint32be upmc1;
/* +0x30C */ uint32be upmc2;

View file

@ -3,32 +3,38 @@
namespace coreinit
{
uint64 coreinit_GetMFTB()
uint64 coreinit_getTimerTick()
{
// bus clock is 1/5th of core clock
// timer clock is 1/4th of bus clock
return PPCInterpreter_getMainCoreCycleCounter() / 20ULL;
}
uint64 OSGetSystemTime()
uint64 coreinit_getOSTime()
{
return coreinit_GetMFTB();
return coreinit_getTimerTick() + ppcCyclesSince2000TimerClock;
}
void export_OSGetTick(PPCInterpreter_t* hCPU)
{
uint64 osTime = coreinit_getOSTime();
osLib_returnFromFunction(hCPU, (uint32)osTime);
}
uint64 OSGetTime()
{
return OSGetSystemTime() + ppcCyclesSince2000TimerClock;
return coreinit_getOSTime();
}
uint32 OSGetSystemTick()
void export_OSGetSystemTime(PPCInterpreter_t* hCPU)
{
return static_cast<uint32>(coreinit_GetMFTB());
osLib_returnFromFunction64(hCPU, coreinit_getTimerTick());
}
uint32 OSGetTick()
void export_OSGetSystemTick(PPCInterpreter_t* hCPU)
{
uint64 osTime = OSGetTime();
return static_cast<uint32>(osTime);
osLib_returnFromFunction(hCPU, (uint32)coreinit_getTimerTick());
}
uint32 getLeapDaysUntilYear(uint32 year)
@ -354,13 +360,14 @@ namespace coreinit
void InitializeTimeAndCalendar()
{
cafeExportRegister("coreinit", OSGetTime, LogType::Placeholder);
cafeExportRegister("coreinit", OSGetSystemTime, LogType::Placeholder);
cafeExportRegister("coreinit", OSGetTick, LogType::Placeholder);
cafeExportRegister("coreinit", OSGetSystemTick, LogType::Placeholder);
osLib_addFunction("coreinit", "OSGetSystemTime", export_OSGetSystemTime);
osLib_addFunction("coreinit", "OSGetTick", export_OSGetTick);
osLib_addFunction("coreinit", "OSGetSystemTick", export_OSGetSystemTick);
cafeExportRegister("coreinit", OSTicksToCalendarTime, LogType::Placeholder);
cafeExportRegister("coreinit", OSCalendarTimeToTicks, LogType::Placeholder);
//timeTest();
}
};

View file

@ -40,21 +40,25 @@ namespace coreinit
inline TimerTicks ConvertNsToTimerTicks(uint64 ns)
{
return static_cast<TimerTicks>((static_cast<uint64>(GetTimerClock()) / 31250ULL) * (ns) / 32000ULL);
return ((GetTimerClock() / 31250LL) * ((TimerTicks)ns) / 32000LL);
}
inline TimerTicks ConvertMsToTimerTicks(uint64 ms)
{
return static_cast<TimerTicks>(ms * static_cast<uint64>(GetTimerClock()) / 1000ULL);
return (TimerTicks)ms * GetTimerClock() / 1000LL;
}
};
void OSTicksToCalendarTime(uint64 ticks, OSCalendarTime_t* calenderStruct);
uint64 OSGetSystemTime();
uint64 OSGetTime();
uint32 OSGetSystemTick();
uint32 OSGetTick();
uint64 coreinit_getOSTime();
uint64 coreinit_getTimerTick();
static uint64 OSGetSystemTime()
{
return coreinit_getTimerTick();
}
void InitializeTimeAndCalendar();
};

View file

@ -11,7 +11,7 @@ uint64 dmaeRetiredTimestamp = 0;
uint64 dmae_getTimestamp()
{
return coreinit::OSGetSystemTime();
return coreinit::coreinit_getTimerTick();
}
void dmae_setRetiredTimestamp(uint64 timestamp)

View file

@ -322,7 +322,7 @@ uint64 _prevReturnedGPUTime = 0;
uint64 Latte_GetTime()
{
uint64 gpuTime = coreinit::OSGetSystemTime();
uint64 gpuTime = coreinit::coreinit_getTimerTick();
gpuTime *= 20000ULL;
if (gpuTime <= _prevReturnedGPUTime)
gpuTime = _prevReturnedGPUTime + 1; // avoid ever returning identical timestamps

View file

@ -54,7 +54,7 @@ void gx2Export_GX2GetGPUTimeout(PPCInterpreter_t* hCPU)
void gx2Export_GX2SampleTopGPUCycle(PPCInterpreter_t* hCPU)
{
cemuLog_log(LogType::GX2, "GX2SampleTopGPUCycle(0x{:08x})", hCPU->gpr[3]);
memory_writeU64(hCPU->gpr[3], coreinit::OSGetSystemTime());
memory_writeU64(hCPU->gpr[3], coreinit::coreinit_getTimerTick());
osLib_returnFromFunction(hCPU, 0);
}

View file

@ -87,11 +87,6 @@ namespace GX2
return true;
}
void GX2RSetBufferName(GX2RBuffer* buffer, const char* name)
{
// no-op in production builds
}
void* GX2RLockBufferEx(GX2RBuffer* buffer, uint32 resFlags)
{
return buffer->GetPtr();
@ -231,7 +226,6 @@ namespace GX2
cafeExportRegister("gx2", GX2RCreateBufferUserMemory, LogType::GX2);
cafeExportRegister("gx2", GX2RDestroyBufferEx, LogType::GX2);
cafeExportRegister("gx2", GX2RBufferExists, LogType::GX2);
cafeExportRegister("gx2", GX2RSetBufferName, LogType::GX2);
cafeExportRegister("gx2", GX2RLockBufferEx, LogType::GX2);
cafeExportRegister("gx2", GX2RUnlockBufferEx, LogType::GX2);
cafeExportRegister("gx2", GX2RInvalidateBuffer, LogType::GX2);

View file

@ -421,7 +421,7 @@ namespace GX2
{
if(aluRegisterOffset&0x8000)
{
cemuLog_logDebugOnce(LogType::Force, "_GX2SubmitUniformReg(): Unhandled loop const special case or invalid offset");
cemuLog_logDebug(LogType::Force, "_GX2SubmitUniformReg(): Unhandled loop const special case or invalid offset");
return;
}
if((aluRegisterOffset+sizeInU32s) > 0x400)

View file

@ -315,7 +315,7 @@ namespace acp
ppcDefineParamU32BEPtr(timestamp64, 0);
ppcDefineParamU32BEPtr(ukn, 1); // probably timezone or offset? Could also be a bool for success/failed
uint64 t = coreinit::OSGetTime() + (uint64)((sint64)(ppcCyclesSince2000_UTC - ppcCyclesSince2000) / 20LL);
uint64 t = coreinit::coreinit_getOSTime() + (uint64)((sint64)(ppcCyclesSince2000_UTC - ppcCyclesSince2000) / 20LL);
timestamp64[0] = (uint32)(t >> 32);
timestamp64[1] = (uint32)(t & 0xFFFFFFFF);

View file

@ -1,12 +1,24 @@
#include "nsyshid.h"
#include "Backend.h"
#include "BackendEmulated.h"
#if NSYSHID_ENABLE_BACKEND_LIBUSB
#include "BackendLibusb.h"
#endif
#if NSYSHID_ENABLE_BACKEND_WINDOWS_HID
#include "BackendWindowsHID.h"
#endif
namespace nsyshid::backend
{
void AttachDefaultBackends()
{
#if NSYSHID_ENABLE_BACKEND_LIBUSB
// add libusb backend
{
auto backendLibusb = std::make_shared<backend::libusb::BackendLibusb>();
@ -15,6 +27,17 @@ namespace nsyshid::backend
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
{
auto backendEmulated = std::make_shared<backend::emulated::BackendEmulated>();

View file

@ -1,4 +1,5 @@
#pragma once
#ifndef CEMU_NSYSHID_BACKEND_H
#define CEMU_NSYSHID_BACKEND_H
#include <list>
#include <memory>
@ -25,9 +26,9 @@ namespace nsyshid
struct TransferCommand
{
uint8* data;
uint32 length;
sint32 length;
TransferCommand(uint8* data, uint32 length)
TransferCommand(uint8* data, sint32 length)
: data(data), length(length)
{
}
@ -38,7 +39,7 @@ namespace nsyshid
{
sint32 bytesRead;
ReadMessage(uint8* data, uint32 length, sint32 bytesRead)
ReadMessage(uint8* data, sint32 length, sint32 bytesRead)
: bytesRead(bytesRead), TransferCommand(data, length)
{
}
@ -49,7 +50,7 @@ namespace nsyshid
{
sint32 bytesWritten;
WriteMessage(uint8* data, uint32 length, sint32 bytesWritten)
WriteMessage(uint8* data, sint32 length, sint32 bytesWritten)
: bytesWritten(bytesWritten), TransferCommand(data, length)
{
}
@ -58,11 +59,14 @@ namespace nsyshid
struct ReportMessage final : TransferCommand
{
uint8 reportType;
uint8 reportId;
uint8* reportData;
sint32 length;
uint8* originalData;
sint32 originalLength;
ReportMessage(uint8 reportType, uint8 reportId, uint8* data, uint32 length)
: reportType(reportType), reportId(reportId), TransferCommand(data, length)
ReportMessage(uint8* reportData, sint32 length, uint8* originalData, sint32 originalLength)
: reportData(reportData), length(length), originalData(originalData),
originalLength(originalLength), TransferCommand(reportData, length)
{
}
using TransferCommand::TransferCommand;
@ -73,8 +77,7 @@ namespace nsyshid
static_assert(offsetof(HID_t, ifIndex) == 0xC, "");
static_assert(offsetof(HID_t, protocol) == 0xE, "");
class Device
{
class Device {
public:
Device() = delete;
@ -128,21 +131,16 @@ namespace nsyshid
virtual bool GetDescriptor(uint8 descType,
uint8 descIndex,
uint16 lang,
uint8 lang,
uint8* output,
uint32 outputMaxLength) = 0;
virtual bool SetIdle(uint8 ifIndex,
uint8 reportId,
uint8 duration) = 0;
virtual bool SetProtocol(uint8 ifIndex, uint8 protocol) = 0;
virtual bool SetReport(ReportMessage* message) = 0;
};
class Backend
{
class Backend {
public:
Backend();
@ -190,3 +188,5 @@ namespace nsyshid
void AttachDefaultBackends();
}
} // namespace nsyshid
#endif // CEMU_NSYSHID_BACKEND_H

View file

@ -1,5 +1,7 @@
#include "BackendLibusb.h"
#if NSYSHID_ENABLE_BACKEND_LIBUSB
namespace nsyshid::backend::libusb
{
BackendLibusb::BackendLibusb()
@ -272,7 +274,7 @@ namespace nsyshid::backend::libusb
auto device = std::make_shared<DeviceLibusb>(m_ctx,
desc.idVendor,
desc.idProduct,
0,
1,
2,
0,
libusb_get_bus_number(dev),
@ -444,13 +446,12 @@ namespace nsyshid::backend::libusb
}
this->m_handleInUseCounter = 0;
}
{
int ret = ClaimAllInterfaces(0);
if (ret != 0)
{
cemuLog_log(LogType::Force, "nsyshid::DeviceLibusb::open(): cannot claim interface for config 0");
return false;
cemuLog_logDebug(LogType::Force, "nsyshid::DeviceLibusb::open(): cannot claim interface");
}
}
}
@ -474,7 +475,7 @@ namespace nsyshid::backend::libusb
{
m_handleInUseCounterDecremented.wait(lock);
}
ReleaseAllInterfacesForCurrentConfig();
libusb_release_interface(handle, 0);
libusb_close(handle);
m_handleInUseCounter = -1;
m_handleInUseCounterDecremented.notify_all();
@ -496,17 +497,12 @@ namespace nsyshid::backend::libusb
return ReadResult::Error;
}
for (int i = 0; i < m_config_descriptors.size(); i++)
{
ClaimAllInterfaces(i);
}
const unsigned int timeout = 50;
int actualLength = 0;
int ret = 0;
do
{
ret = libusb_interrupt_transfer(handleLock->GetHandle(),
ret = libusb_bulk_transfer(handleLock->GetHandle(),
this->m_libusbEndpointIn,
message->data,
message->length,
@ -525,8 +521,8 @@ namespace nsyshid::backend::libusb
return ReadResult::Success;
}
cemuLog_logDebug(LogType::Force,
"nsyshid::DeviceLibusb::read(): failed at endpoint 0x{:02x} with error message: {}", this->m_libusbEndpointIn,
libusb_error_name(ret));
"nsyshid::DeviceLibusb::read(): failed with error code: {}",
ret);
return ReadResult::Error;
}
@ -540,14 +536,9 @@ namespace nsyshid::backend::libusb
return WriteResult::Error;
}
for (int i = 0; i < m_config_descriptors.size(); i++)
{
ClaimAllInterfaces(i);
}
message->bytesWritten = 0;
int actualLength = 0;
int ret = libusb_interrupt_transfer(handleLock->GetHandle(),
int ret = libusb_bulk_transfer(handleLock->GetHandle(),
this->m_libusbEndpointOut,
message->data,
message->length,
@ -572,7 +563,7 @@ namespace nsyshid::backend::libusb
bool DeviceLibusb::GetDescriptor(uint8 descType,
uint8 descIndex,
uint16 lang,
uint8 lang,
uint8* output,
uint32 outputMaxLength)
{
@ -588,6 +579,7 @@ namespace nsyshid::backend::libusb
struct libusb_config_descriptor* conf = nullptr;
libusb_device* dev = libusb_get_device(handleLock->GetHandle());
int ret = libusb_get_active_config_descriptor(dev, &conf);
if (ret == 0)
{
std::vector<uint8> configurationDescriptor(conf->wTotalLength);
@ -664,6 +656,7 @@ namespace nsyshid::backend::libusb
extraReadPointer += bLength;
}
}
for (int endpointIndex = 0; endpointIndex < altsetting.bNumEndpoints; endpointIndex++)
{
// endpoint descriptor
@ -688,62 +681,25 @@ namespace nsyshid::backend::libusb
uint32 bytesWritten = currentWritePtr - &configurationDescriptor[0];
libusb_free_config_descriptor(conf);
cemu_assert_debug(bytesWritten <= conf->wTotalLength);
memcpy(output, &configurationDescriptor[0],
std::min<uint32>(outputMaxLength, bytesWritten));
return true;
}
else
{
cemuLog_logDebug(LogType::Force,
"nsyshid::DeviceLibusb::getDescriptor(): failed to get config descriptor with error code: {}",
ret);
return false;
}
}
else
{
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));
cemu_assert_unimplemented();
}
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>
static int DoForEachInterface(const Configs& configs, uint8 config_num, Function action)
@ -811,22 +767,18 @@ namespace nsyshid::backend::libusb
cemuLog_logDebug(LogType::Force, "nsyshid::DeviceLibusb::SetProtocol(): device is not opened");
return false;
}
if (m_interfaceIndex != ifIndex)
m_interfaceIndex = ifIndex;
int ret = libusb_control_transfer(handleLock->GetHandle(),
LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE | LIBUSB_ENDPOINT_OUT,
HID_CLASS_SET_PROTOCOL, // Defined in HID Class Specific Requests (7.2)
protocol,
ifIndex,
nullptr,
0,
0);
ReleaseAllInterfacesForCurrentConfig();
int ret = libusb_set_configuration(AquireHandleLock()->GetHandle(), protocol);
if (ret == LIBUSB_SUCCESS)
ret = ClaimAllInterfaces(protocol);
if (ret != 0)
{
cemuLog_logDebug(LogType::Force, "nsyshid::DeviceLibusb::SetProtocol(): Control Transfer Failed: {}", libusb_error_name(ret));
return false;
}
if (ret == LIBUSB_SUCCESS)
return true;
return false;
}
bool DeviceLibusb::SetReport(ReportMessage* message)
@ -838,20 +790,18 @@ namespace nsyshid::backend::libusb
return false;
}
uint16 wValue = uint16(message->reportType) << 8 | uint16(message->reportId);
int ret = libusb_control_transfer(handleLock->GetHandle(),
LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE | LIBUSB_ENDPOINT_OUT,
HID_CLASS_SET_REPORT, // Defined in HID Class Specific Requests (7.2)
wValue,
m_interfaceIndex,
message->data,
uint16(message->length & 0xFFFF),
LIBUSB_REQUEST_SET_CONFIGURATION,
512,
0,
message->originalData,
message->originalLength,
0);
if (ret != message->length)
if (ret != message->originalLength)
{
cemuLog_logDebug(LogType::Force, "nsyshid::DeviceLibusb::SetReport(): Control Transfer Failed at interface {} : {}", m_interfaceIndex, libusb_error_name(ret));
cemuLog_logDebug(LogType::Force, "nsyshid::DeviceLibusb::SetReport(): Control Transfer Failed: {}", libusb_error_name(ret));
return false;
}
return true;
@ -904,3 +854,5 @@ namespace nsyshid::backend::libusb
return m_handle;
}
} // namespace nsyshid::backend::libusb
#endif // NSYSHID_ENABLE_BACKEND_LIBUSB

View file

@ -1,20 +1,15 @@
#ifndef CEMU_NSYSHID_BACKEND_LIBUSB_H
#define CEMU_NSYSHID_BACKEND_LIBUSB_H
#include "nsyshid.h"
#if NSYSHID_ENABLE_BACKEND_LIBUSB
#include <libusb-1.0/libusb.h>
#include "Backend.h"
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 {
public:
BackendLibusb();
@ -80,14 +75,10 @@ namespace nsyshid::backend::libusb
bool GetDescriptor(uint8 descType,
uint8 descIndex,
uint16 lang,
uint8 lang,
uint8* output,
uint32 outputMaxLength) override;
bool SetIdle(uint8 ifIndex,
uint8 reportId,
uint8 duration) override;
bool SetProtocol(uint8 ifIndex, uint8 protocol) override;
int ClaimAllInterfaces(uint8 config_num);
@ -143,3 +134,7 @@ namespace nsyshid::backend::libusb
std::unique_ptr<HandleLock> AquireHandleLock();
};
} // namespace nsyshid::backend::libusb
#endif // NSYSHID_ENABLE_BACKEND_LIBUSB
#endif // CEMU_NSYSHID_BACKEND_LIBUSB_H

View file

@ -0,0 +1,444 @@
#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

View file

@ -0,0 +1,66 @@
#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

View file

@ -426,7 +426,7 @@ namespace nsyshid
bool DimensionsToypadDevice::GetDescriptor(uint8 descType,
uint8 descIndex,
uint16 lang,
uint8 lang,
uint8* output,
uint32 outputMaxLength)
{
@ -489,13 +489,6 @@ namespace nsyshid
return true;
}
bool DimensionsToypadDevice::SetIdle(uint8 ifIndex,
uint8 reportId,
uint8 duration)
{
return true;
}
bool DimensionsToypadDevice::SetProtocol(uint8 ifIndex, uint8 protocol)
{
cemuLog_log(LogType::Force, "Toypad Protocol");
@ -682,7 +675,6 @@ namespace nsyshid
figureChangeResponse[13] = GenerateChecksum(figureChangeResponse, 13);
m_figureAddedRemovedResponses.push(figureChangeResponse);
return true;
}
bool DimensionsUSB::CancelRemove(uint8 index)

View file

@ -25,14 +25,10 @@ namespace nsyshid
bool GetDescriptor(uint8 descType,
uint8 descIndex,
uint16 lang,
uint8 lang,
uint8* output,
uint32 outputMaxLength) override;
bool SetIdle(uint8 ifIndex,
uint8 reportId,
uint8 duration) override;
bool SetProtocol(uint8 ifIndex, uint8 protocol) override;
bool SetReport(ReportMessage* message) override;

View file

@ -387,7 +387,7 @@ namespace nsyshid
bool InfinityBaseDevice::GetDescriptor(uint8 descType,
uint8 descIndex,
uint16 lang,
uint8 lang,
uint8* output,
uint32 outputMaxLength)
{
@ -450,13 +450,6 @@ namespace nsyshid
return true;
}
bool InfinityBaseDevice::SetIdle(uint8 ifIndex,
uint8 reportId,
uint8 duration)
{
return true;
}
bool InfinityBaseDevice::SetProtocol(uint8 ifIndex, uint8 protocol)
{
return true;
@ -499,7 +492,7 @@ namespace nsyshid
return response;
}
void InfinityUSB::SendCommand(uint8* buf, uint32 length)
void InfinityUSB::SendCommand(uint8* buf, sint32 originalLength)
{
const uint8 command = buf[2];
const uint8 sequence = buf[3];

View file

@ -26,14 +26,10 @@ namespace nsyshid
bool GetDescriptor(uint8 descType,
uint8 descIndex,
uint16 lang,
uint8 lang,
uint8* output,
uint32 outputMaxLength) override;
bool SetIdle(uint8 ifIndex,
uint8 reportId,
uint8 duration) override;
bool SetProtocol(uint8 ifIndex, uint8 protocol) override;
bool SetReport(ReportMessage* message) override;
@ -57,7 +53,7 @@ namespace nsyshid
void Save();
};
void SendCommand(uint8* buf, uint32 length);
void SendCommand(uint8* buf, sint32 originalLength);
std::array<uint8, 32> GetStatus();
void GetBlankResponse(uint8 sequence, std::array<uint8, 32>& replyBuf);

View file

@ -564,7 +564,7 @@ namespace nsyshid
bool SkylanderPortalDevice::GetDescriptor(uint8 descType,
uint8 descIndex,
uint16 lang,
uint8 lang,
uint8* output,
uint32 outputMaxLength)
{
@ -583,7 +583,7 @@ namespace nsyshid
*(uint8*)(currentWritePtr + 7) = 0x80; // bmAttributes
*(uint8*)(currentWritePtr + 8) = 0xFA; // MaxPower
currentWritePtr = currentWritePtr + 9;
// interface descriptor
// configuration descriptor
*(uint8*)(currentWritePtr + 0) = 9; // bLength
*(uint8*)(currentWritePtr + 1) = 0x04; // bDescriptorType
*(uint8*)(currentWritePtr + 2) = 0; // bInterfaceNumber
@ -594,7 +594,7 @@ namespace nsyshid
*(uint8*)(currentWritePtr + 7) = 0; // bInterfaceProtocol
*(uint8*)(currentWritePtr + 8) = 0; // iInterface
currentWritePtr = currentWritePtr + 9;
// HID descriptor
// configuration descriptor
*(uint8*)(currentWritePtr + 0) = 9; // bLength
*(uint8*)(currentWritePtr + 1) = 0x21; // bDescriptorType
*(uint16be*)(currentWritePtr + 2) = 0x0111; // bcdHID
@ -608,7 +608,7 @@ namespace nsyshid
*(uint8*)(currentWritePtr + 1) = 0x05; // bDescriptorType
*(uint8*)(currentWritePtr + 2) = 0x81; // bEndpointAddress
*(uint8*)(currentWritePtr + 3) = 0x03; // bmAttributes
*(uint16be*)(currentWritePtr + 4) = 0x0040; // wMaxPacketSize
*(uint16be*)(currentWritePtr + 4) = 0x40; // wMaxPacketSize
*(uint8*)(currentWritePtr + 6) = 0x01; // bInterval
currentWritePtr = currentWritePtr + 7;
// endpoint descriptor 2
@ -616,7 +616,7 @@ namespace nsyshid
*(uint8*)(currentWritePtr + 1) = 0x05; // bDescriptorType
*(uint8*)(currentWritePtr + 2) = 0x02; // bEndpointAddress
*(uint8*)(currentWritePtr + 3) = 0x03; // bmAttributes
*(uint16be*)(currentWritePtr + 4) = 0x0040; // wMaxPacketSize
*(uint16be*)(currentWritePtr + 4) = 0x40; // wMaxPacketSize
*(uint8*)(currentWritePtr + 6) = 0x01; // bInterval
currentWritePtr = currentWritePtr + 7;
@ -627,13 +627,6 @@ namespace nsyshid
return true;
}
bool SkylanderPortalDevice::SetIdle(uint8 ifIndex,
uint8 reportId,
uint8 duration)
{
return true;
}
bool SkylanderPortalDevice::SetProtocol(uint8 ifIndex, uint8 protocol)
{
return true;
@ -641,12 +634,12 @@ namespace nsyshid
bool SkylanderPortalDevice::SetReport(ReportMessage* message)
{
g_skyportal.ControlTransfer(message->data, message->length);
g_skyportal.ControlTransfer(message->originalData, message->originalLength);
std::this_thread::sleep_for(std::chrono::milliseconds(1));
return true;
}
void SkylanderUSB::ControlTransfer(uint8* buf, uint32 length)
void SkylanderUSB::ControlTransfer(uint8* buf, sint32 originalLength)
{
std::array<uint8, 64> interruptResponse = {};
switch (buf[0])

View file

@ -26,14 +26,10 @@ namespace nsyshid
bool GetDescriptor(uint8 descType,
uint8 descIndex,
uint16 lang,
uint8 lang,
uint8* output,
uint32 outputMaxLength) override;
bool SetIdle(uint8 ifIndex,
uint8 reportId,
uint8 duration) override;
bool SetProtocol(uint8 ifIndex, uint8 protocol) override;
bool SetReport(ReportMessage* message) override;
@ -74,7 +70,7 @@ namespace nsyshid
uint8 blue = 0;
};
void ControlTransfer(uint8* buf, uint32 length);
void ControlTransfer(uint8* buf, sint32 originalLength);
void Activate();
void Deactivate();

View file

@ -305,37 +305,47 @@ namespace nsyshid
osLib_returnFromFunction(hCPU, 0);
}
void _debugPrintHex(const std::string prefix, const uint8* data, size_t size)
void export_HIDGetDescriptor(PPCInterpreter_t* hCPU)
{
constexpr size_t BYTES_PER_LINE = 16;
ppcDefineParamU32(hidHandle, 0); // r3
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
std::string out;
for (size_t row_start = 0; row_start < size; row_start += BYTES_PER_LINE)
int returnValue = -1;
std::shared_ptr<Device> device = GetDeviceByHandle(hidHandle, true);
if (device)
{
out += fmt::format("{:06x}: ", row_start);
for (size_t i = 0; i < BYTES_PER_LINE; ++i)
memset(output, 0, outputMaxLength);
if (device->GetDescriptor(descType, descIndex, lang, output, outputMaxLength))
{
if (row_start + i < size)
{
out += fmt::format("{:02x} ", data[row_start + i]);
returnValue = 0;
}
else
{
out += " ";
returnValue = -1;
}
}
out += " ";
for (size_t i = 0; i < BYTES_PER_LINE; ++i)
else
{
if (row_start + i < size)
cemu_assert_suspicious();
}
osLib_returnFromFunction(hCPU, returnValue);
}
void _debugPrintHex(std::string prefix, uint8* data, size_t len)
{
char c = static_cast<char>(data[row_start + i]);
out += std::isprint(c, std::locale::classic()) ? c : '.';
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]);
}
}
out += "\n";
}
cemuLog_logDebug(LogType::Force, "[{}] Data: \n{}", prefix, out);
cemuLog_logDebug(LogType::Force, "[{}] Data: {}", prefix, debugOutput);
}
void DoHIDTransferCallback(MPTR callbackFuncMPTR, MPTR callbackParamMPTR, uint32 hidHandle, uint32 errorCode,
@ -344,152 +354,26 @@ namespace nsyshid
coreinitAsyncCallback_add(callbackFuncMPTR, 5, hidHandle, errorCode, buffer, length, callbackParamMPTR);
}
void _hidGetDescriptorAsync(std::shared_ptr<Device> device, uint8 descType, uint8 descIndex, uint16 lang, uint8* output, uint32 outputMaxLength, MPTR callbackFuncMPTR, MPTR callbackParamMPTR)
{
if (device->GetDescriptor(descType, descIndex, lang, output, outputMaxLength))
{
DoHIDTransferCallback(callbackFuncMPTR,
callbackParamMPTR,
device->m_hid->handle,
0,
0,
0);
}
else
{
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
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(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]);
cemuLog_logDebug(LogType::Force, "nsyshid.HIDSetIdle(...)");
std::shared_ptr<Device> device = GetDeviceByHandle(hidHandle, true);
if (device == nullptr)
// todo
if (callbackFuncMPTR)
{
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;
}
DoHIDTransferCallback(callbackFuncMPTR, callbackParamMPTR, hidHandle, 0, MPTR_NULL, 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);
cemu_assert_unimplemented();
}
osLib_returnFromFunction(hCPU, 0); // for non-async version, return number of bytes transferred
}
void export_HIDSetProtocol(PPCInterpreter_t* hCPU)
@ -499,51 +383,51 @@ namespace nsyshid
ppcDefineParamU8(protocol, 2); // r5
ppcDefineParamMPTR(callbackFuncMPTR, 3); // r6
ppcDefineParamMPTR(callbackParamMPTR, 4); // r7
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]);
cemuLog_logDebug(LogType::Force, "nsyshid.HIDSetProtocol(...)");
std::shared_ptr<Device> device = GetDeviceByHandle(hidHandle, true);
if (device == nullptr)
sint32 returnCode = -1;
if (device)
{
cemuLog_log(LogType::Force, "nsyshid.HIDSetProtocol(): Unable to find device with hid handle {}", hidHandle);
osLib_returnFromFunction(hCPU, -1);
return;
if (!device->IsOpened())
{
cemuLog_logDebug(LogType::Force, "nsyshid.HIDSetProtocol(): error: device is not opened");
}
// issue request (synchronous or asynchronous)
sint32 returnCode = 0;
if (callbackFuncMPTR == MPTR_NULL)
else
{
// synchronous
returnCode = -1;
if (device->SetProtocol(ifIndex, protocol))
{
returnCode = 0;
}
}
}
else
{
// asynchronous
std::thread(&_hidSetProtocolAsync, device, ifIndex, protocol, callbackFuncMPTR, callbackParamMPTR)
.detach();
returnCode = 0;
cemu_assert_suspicious();
}
if (callbackFuncMPTR)
{
DoHIDTransferCallback(callbackFuncMPTR, callbackParamMPTR, hidHandle, 0, MPTR_NULL, 0);
}
osLib_returnFromFunction(hCPU, returnCode);
}
// handler for async HIDSetReport transfers
void _hidSetReportAsync(std::shared_ptr<Device> device, uint8 reportType, uint8 reportId, uint8* data, uint32 length,
MPTR callbackFuncMPTR, MPTR callbackParamMPTR)
void _hidSetReportAsync(std::shared_ptr<Device> device, uint8* reportData, sint32 length,
uint8* originalData,
sint32 originalLength, MPTR callbackFuncMPTR, MPTR callbackParamMPTR)
{
cemuLog_logDebug(LogType::Force, "_hidSetReportAsync begin");
ReportMessage message(reportType, reportId, data, length);
ReportMessage message(reportData, length, originalData, originalLength);
if (device->SetReport(&message))
{
DoHIDTransferCallback(callbackFuncMPTR,
callbackParamMPTR,
device->m_hid->handle,
0,
memory_getVirtualOffsetFromPointer(data),
length);
memory_getVirtualOffsetFromPointer(originalData),
originalLength);
}
else
{
@ -551,22 +435,24 @@ namespace nsyshid
callbackParamMPTR,
device->m_hid->handle,
-1,
memory_getVirtualOffsetFromPointer(data),
length);
memory_getVirtualOffsetFromPointer(originalData),
0);
}
free(reportData);
}
// handler for synchronous HIDSetReport transfers
sint32 _hidSetReportSync(std::shared_ptr<Device> device, uint8 reportType, uint8 reportId,
uint8* data, uint32 length, coreinit::OSEvent* event)
sint32 _hidSetReportSync(std::shared_ptr<Device> device, uint8* reportData, sint32 length,
uint8* originalData, sint32 originalLength, coreinit::OSEvent* event)
{
_debugPrintHex("_hidSetReportSync Begin", data, length);
_debugPrintHex("_hidSetReportSync Begin", reportData, length);
sint32 returnCode = 0;
ReportMessage message(reportType, reportId, data, length);
ReportMessage message(reportData, length, originalData, originalLength);
if (device->SetReport(&message))
{
returnCode = length;
returnCode = originalLength;
}
free(reportData);
cemuLog_logDebug(LogType::Force, "_hidSetReportSync end. returnCode: {}", returnCode);
coreinit::OSSignalEvent(event);
return returnCode;
@ -575,19 +461,19 @@ namespace nsyshid
void export_HIDSetReport(PPCInterpreter_t* hCPU)
{
ppcDefineParamU32(hidHandle, 0); // r3
ppcDefineParamU8(reportType, 1); // r4
ppcDefineParamU8(reportId, 2); // r5
ppcDefineParamU32(reportRelatedUkn, 1); // r4
ppcDefineParamU32(reportId, 2); // r5
ppcDefineParamUStr(data, 3); // r6
ppcDefineParamU32(dataLength, 4); // r7
ppcDefineParamMPTR(callbackFuncMPTR, 5); // r8
ppcDefineParamMPTR(callbackParamMPTR, 6); // r9
cemuLog_logDebug(LogType::Force, "nsyshid.HIDSetReport(0x{:08x}, 0x{:02x}, 0x{:02x}, 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[8], hCPU->gpr[9]);
cemuLog_logDebug(LogType::Force, "nsyshid.HIDSetReport({},0x{:02x},0x{:02x},...)", hidHandle, reportRelatedUkn,
reportId);
_debugPrintHex("HIDSetReport", data, dataLength);
#ifdef CEMU_DEBUG_ASSERT
if (reportType != 2 || reportId != 0)
if (reportRelatedUkn != 2 || reportId != 0)
assert_dbg();
#endif
@ -599,6 +485,15 @@ namespace nsyshid
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)
sint32 returnCode = 0;
if (callbackFuncMPTR == MPTR_NULL)
@ -606,14 +501,15 @@ namespace nsyshid
// synchronous
StackAllocator<coreinit::OSEvent> event;
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, reportType, reportId, data, dataLength, &event);
std::future<sint32> res = std::async(std::launch::async, &_hidSetReportSync, device, reportData,
paddedLength + 1, data, dataLength, &event);
coreinit::OSWaitEvent(&event);
returnCode = res.get();
}
else
{
// asynchronous
std::thread(&_hidSetReportAsync, device, reportType, reportId, data, dataLength,
std::thread(&_hidSetReportAsync, device, reportData, paddedLength + 1, data, dataLength,
callbackFuncMPTR, callbackParamMPTR)
.detach();
returnCode = 0;

View file

@ -3,7 +3,6 @@
#include "Cafe/OS/libs/coreinit/coreinit_Thread.h"
#include "Cafe/IOSU/legacy/iosu_crypto.h"
#include "Cafe/OS/libs/coreinit/coreinit_Time.h"
#include "Cafe/OS/libs/coreinit/coreinit_GHS.h"
#include "Common/socket.h"
@ -118,14 +117,20 @@ void nsysnetExport_socket_lib_finish(PPCInterpreter_t* hCPU)
osLib_returnFromFunction(hCPU, 0); // 0 -> Success
}
static uint32be* __gh_errno_ptr()
{
OSThread_t* osThread = coreinit::OSGetCurrentThread();
return &osThread->context.ghs_errno;
}
void _setSockError(sint32 errCode)
{
coreinit::__gh_set_errno(errCode);
*(uint32be*)__gh_errno_ptr() = (uint32)errCode;
}
sint32 _getSockError()
{
return coreinit::__gh_get_errno();
return (sint32)*(uint32be*)__gh_errno_ptr();
}
// error translation modes for _translateError

View file

@ -760,7 +760,7 @@ namespace padscore
void start()
{
OSCreateAlarm(&g_padscore.alarm);
const uint64 start_tick = coreinit::OSGetTime();
const uint64 start_tick = coreinit::coreinit_getOSTime();
const uint64 period_tick = coreinit::EspressoTime::GetTimerClock() / 200; // every 5ms
MPTR handler = PPCInterpreter_makeCallableExportDepr(TickFunction);
OSSetPeriodicAlarm(&g_padscore.alarm, start_tick, period_tick, handler);

View file

@ -396,15 +396,43 @@ namespace snd_core
void AXOut_init()
{
auto& config = GetConfig();
const auto audio_api = (IAudioAPI::AudioAPI)config.audio_api;
numQueuedFramesSndGeneric = 0;
std::unique_lock lock(g_audioMutex);
if (!g_tvAudio)
{
sint32 channels;
switch (config.tv_channels)
{
case 0:
channels = 1; // will mix mono sound on both output channels
break;
case 2:
channels = 6;
break;
default: // stereo
channels = 2;
break;
}
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(), [&config](const auto& d) {return d->GetIdentifier() == config.tv_device; });
if (it != devices.end())
device_description = *it;
}
if (device_description)
{
try
{
g_tvAudio = IAudioAPI::CreateDeviceFromConfig(true, 48000, snd_core::AX_SAMPLES_PER_3MS_48KHZ * AX_FRAMES_PER_GROUP, 16);
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)
{
@ -412,14 +440,40 @@ namespace snd_core
exit(0);
}
}
}
if (!g_padAudio)
{
sint32 channels;
switch (config.pad_channels)
{
case 0:
channels = 1; // will mix mono sound on both output channels
break;
case 2:
channels = 6;
break;
default: // stereo
channels = 2;
break;
}
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(), [&config](const auto& d) {return d->GetIdentifier() == config.pad_device; });
if (it != devices.end())
device_description = *it;
}
if (device_description)
{
try
{
g_padAudio = IAudioAPI::CreateDeviceFromConfig(false, 48000, snd_core::AX_SAMPLES_PER_3MS_48KHZ * AX_FRAMES_PER_GROUP, 16);
if(g_padAudio)
g_padVolume = g_padAudio->GetVolume();
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)
{
@ -428,6 +482,7 @@ namespace snd_core
}
}
}
}
void AXOut_reset()
{

View file

@ -267,7 +267,7 @@ namespace vpad
{
if (channel <= 1 && vpadDelayEnabled)
{
uint64 currentTime = coreinit::OSGetTime();
uint64 currentTime = coreinit::coreinit_getOSTime();
const auto dif = currentTime - vpad::g_vpad.controller_data[channel].drcLastCallTime;
if (dif <= (ESPRESSO_TIMER_CLOCK / 60ull))
{
@ -1149,7 +1149,7 @@ namespace vpad
void start()
{
coreinit::OSCreateAlarm(&g_vpad.alarm);
const uint64 start_tick = coreinit::OSGetTime();
const uint64 start_tick = coreinit::coreinit_getOSTime();
const uint64 period_tick = coreinit::EspressoTime::GetTimerClock() * 5 / 1000;
const MPTR handler = PPCInterpreter_makeCallableExportDepr(TickFunction);
coreinit::OSSetPeriodicAlarm(&g_vpad.alarm, start_tick, period_tick, handler);

View file

@ -274,25 +274,6 @@ inline uint64 _udiv128(uint64 highDividend, uint64 lowDividend, uint64 divisor,
#define NOEXPORT __attribute__ ((visibility ("hidden")))
#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
#if defined(__aarch64__)

View file

@ -114,7 +114,7 @@ CubebAPI::~CubebAPI()
bool CubebAPI::NeedAdditionalBlocks() const
{
std::shared_lock lock(m_mutex);
return m_buffer.size() < GetAudioDelay() * m_bytesPerBlock;
return m_buffer.size() < s_audioDelay * m_bytesPerBlock;
}
bool CubebAPI::FeedBlock(sint16* data)
@ -183,17 +183,17 @@ void CubebAPI::Destroy()
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;
result.reserve(devices.count + 1); // Reserve space for the default device
// Add the default device to the list
auto defaultDevice = std::make_shared<CubebDeviceDescription>(nullptr, "default", L"Default Device");
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)
{
// const auto& device = devices.device[i];

View file

@ -175,17 +175,17 @@ void CubebInputAPI::Destroy()
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;
result.reserve(devices.count + 1); // Reserve space for the default device
// Add the default device to the list
auto defaultDevice = std::make_shared<CubebDeviceDescription>(nullptr, "default", L"Default Device");
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)
{
// const auto& device = devices.device[i];

View file

@ -210,7 +210,7 @@ void DirectSoundAPI::SetVolume(sint32 volume)
bool DirectSoundAPI::NeedAdditionalBlocks() const
{
std::shared_lock lock(m_mutex);
return m_buffer.size() < GetAudioDelay();
return m_buffer.size() < s_audioDelay;
}
std::vector<DirectSoundAPI::DeviceDescriptionPtr> DirectSoundAPI::GetDevices()

View file

@ -97,40 +97,7 @@ bool IAudioAPI::IsAudioAPIAvailable(AudioAPI api)
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)
{
@ -200,12 +167,3 @@ 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;
}

View file

@ -55,15 +55,11 @@ public:
virtual bool FeedBlock(sint16* data) = 0;
virtual bool Play() = 0;
virtual bool Stop() = 0;
void SetAudioDelayOverride(uint32 delay);
uint32 GetAudioDelay() const;
static void PrintLogging();
static void InitializeStatic();
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::vector<DeviceDescriptionPtr> GetDevices(AudioAPI api);
@ -79,10 +75,9 @@ protected:
bool m_playing = false;
static std::array<bool, AudioAPIEnd> s_availableApis;
uint32 m_audioDelayOverride = 0;
static uint32 s_audioDelay;
private:
static uint32 s_audioDelay;
void InitWFX(sint32 samplerate, sint32 channels, sint32 bits_per_sample);
};

View file

@ -33,8 +33,8 @@ XAudio27API::XAudio27API(uint32 device_id, uint32 samplerate, uint32 channels, u
m_wfx.Format.nChannels = channels;
m_wfx.Format.nSamplesPerSec = samplerate;
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.nAvgBytesPerSec = m_wfx.Format.nSamplesPerSec * m_wfx.Format.nBlockAlign; // must equal nSamplesPerSec × nBlockAlign.
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.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
m_wfx.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
@ -199,7 +199,9 @@ bool XAudio27API::FeedBlock(sint16* data)
// check if we queued too many blocks
if(m_blocks_queued >= kBlockCount)
{
m_blocks_queued = GetQueuedBuffers();
XAUDIO2_VOICE_STATE state{};
m_source_voice->GetState(&state);
m_blocks_queued = state.BuffersQueued;
if (m_blocks_queued >= kBlockCount)
{
@ -220,14 +222,7 @@ bool XAudio27API::FeedBlock(sint16* data)
return true;
}
uint32 XAudio27API::GetQueuedBuffers() const
{
XAUDIO2_VOICE_STATE state{};
m_source_voice->GetState(&state);
return state.BuffersQueued;
}
bool XAudio27API::NeedAdditionalBlocks() const
{
return GetQueuedBuffers() < GetAudioDelay();
return m_blocks_queued < s_audioDelay;
}

View file

@ -47,8 +47,6 @@ public:
static std::vector<DeviceDescriptionPtr> GetDevices();
private:
uint32 GetQueuedBuffers() const;
struct XAudioDeleter
{
void operator()(IXAudio2* ptr) const;

View file

@ -270,7 +270,9 @@ bool XAudio2API::FeedBlock(sint16* data)
// check if we queued too many blocks
if (m_blocks_queued >= kBlockCount)
{
m_blocks_queued = GetQueuedBuffers();
XAUDIO2_VOICE_STATE state{};
m_source_voice->GetState(&state);
m_blocks_queued = state.BuffersQueued;
if (m_blocks_queued >= kBlockCount)
{
@ -291,14 +293,7 @@ bool XAudio2API::FeedBlock(sint16* data)
return true;
}
uint32 XAudio2API::GetQueuedBuffers() const
{
XAUDIO2_VOICE_STATE state{};
m_source_voice->GetState(&state);
return state.BuffersQueued;
}
bool XAudio2API::NeedAdditionalBlocks() const
{
return GetQueuedBuffers() < GetAudioDelay();
return m_blocks_queued < s_audioDelay;
}

View file

@ -46,8 +46,6 @@ public:
static const std::vector<DeviceDescriptionPtr>& GetDevices() { return s_devices; }
private:
uint32 GetQueuedBuffers() const;
static const std::vector<DeviceDescriptionPtr>& RefreshDevices();
struct XAudioDeleter

View file

@ -198,20 +198,14 @@ bool ActiveSettings::ShaderPreventInfiniteLoopsEnabled()
{
const uint64 titleId = CafeSystem::GetForegroundTitleId();
// workaround for NSMBU (and variants) having a bug where shaders can get stuck in infinite loops
// Fatal Frame has an actual infinite loop in shader 0xb6a67c19f6472e00 encountered during a cutscene for the second drop (eShop version only?)
// update: As of Cemu 1.20.0 this should no longer be required for NSMBU/NSLU due to fixes with uniform handling. But we leave it here for good measure
// todo - Once we add support for loop config registers this workaround should become unnecessary
// update: As of Cemu 1.20.0 this should no longer be required
return /* NSMBU JP */ titleId == 0x0005000010101C00 ||
/* NSMBU US */ titleId == 0x0005000010101D00 ||
/* NSMBU EU */ titleId == 0x0005000010101E00 ||
/* NSMBU+L US */ titleId == 0x000500001014B700 ||
/* NSMBU+L EU */ titleId == 0x000500001014B800 ||
/* NSLU US */ titleId == 0x0005000010142300 ||
/* NSLU EU */ titleId == 0x0005000010142400 ||
/* Project Zero: Maiden of Black Water (EU) */ titleId == 0x00050000101D0300 ||
/* Fatal Frame: Maiden of Black Water (US) */ titleId == 0x00050000101D0600 ||
/* Project Zero: Maiden of Black Water (JP) */ titleId == 0x000500001014D200 ||
/* Project Zero: Maiden of Black Water (Trial, EU) */ titleId == 0x00050000101D3F00;
/* NSLU EU */ titleId == 0x0005000010142400;
}
bool ActiveSettings::FlushGPUCacheOnSwap()

View file

@ -46,7 +46,6 @@ void CemuConfig::Load(XMLConfigParser& parser)
fullscreen = parser.get("fullscreen", fullscreen);
proxy_server = parser.get("proxy_server", "");
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());
window_position.x = parser.get("window_position").get("x", -1);
@ -371,7 +370,6 @@ void CemuConfig::Save(XMLConfigParser& parser)
config.set<bool>("fullscreen", fullscreen);
config.set("proxy_server", proxy_server.GetValue().c_str());
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("console_region", console_region.GetValue());

View file

@ -192,7 +192,7 @@ ENABLE_ENUM_ITERATORS(CrashDump, CrashDump::Disabled, CrashDump::Enabled);
#endif
template <>
struct fmt::formatter<const PrecompiledShaderOption> : formatter<string_view> {
struct fmt::formatter<PrecompiledShaderOption> : formatter<string_view> {
template <typename FormatContext>
auto format(const PrecompiledShaderOption c, FormatContext &ctx) const {
string_view name;
@ -207,7 +207,7 @@ struct fmt::formatter<const PrecompiledShaderOption> : formatter<string_view> {
}
};
template <>
struct fmt::formatter<const AccurateShaderMulOption> : formatter<string_view> {
struct fmt::formatter<AccurateShaderMulOption> : formatter<string_view> {
template <typename FormatContext>
auto format(const AccurateShaderMulOption c, FormatContext &ctx) const {
string_view name;
@ -221,7 +221,7 @@ struct fmt::formatter<const AccurateShaderMulOption> : formatter<string_view> {
}
};
template <>
struct fmt::formatter<const CPUMode> : formatter<string_view> {
struct fmt::formatter<CPUMode> : formatter<string_view> {
template <typename FormatContext>
auto format(const CPUMode c, FormatContext &ctx) const {
string_view name;
@ -238,7 +238,7 @@ struct fmt::formatter<const CPUMode> : formatter<string_view> {
}
};
template <>
struct fmt::formatter<const CPUModeLegacy> : formatter<string_view> {
struct fmt::formatter<CPUModeLegacy> : formatter<string_view> {
template <typename FormatContext>
auto format(const CPUModeLegacy c, FormatContext &ctx) const {
string_view name;
@ -255,7 +255,7 @@ struct fmt::formatter<const CPUModeLegacy> : formatter<string_view> {
}
};
template <>
struct fmt::formatter<const CafeConsoleRegion> : formatter<string_view> {
struct fmt::formatter<CafeConsoleRegion> : formatter<string_view> {
template <typename FormatContext>
auto format(const CafeConsoleRegion v, FormatContext &ctx) const {
string_view name;
@ -276,7 +276,7 @@ struct fmt::formatter<const CafeConsoleRegion> : formatter<string_view> {
}
};
template <>
struct fmt::formatter<const CafeConsoleLanguage> : formatter<string_view> {
struct fmt::formatter<CafeConsoleLanguage> : formatter<string_view> {
template <typename FormatContext>
auto format(const CafeConsoleLanguage v, FormatContext &ctx) {
string_view name;
@ -302,7 +302,7 @@ struct fmt::formatter<const CafeConsoleLanguage> : formatter<string_view> {
#if BOOST_OS_WINDOWS
template <>
struct fmt::formatter<const CrashDump> : formatter<string_view> {
struct fmt::formatter<CrashDump> : formatter<string_view> {
template <typename FormatContext>
auto format(const CrashDump v, FormatContext &ctx) {
string_view name;
@ -319,7 +319,7 @@ struct fmt::formatter<const CrashDump> : formatter<string_view> {
};
#elif BOOST_OS_UNIX
template <>
struct fmt::formatter<const CrashDump> : formatter<string_view> {
struct fmt::formatter<CrashDump> : formatter<string_view> {
template <typename FormatContext>
auto format(const CrashDump v, FormatContext &ctx) {
string_view name;
@ -380,7 +380,6 @@ struct CemuConfig
#endif
ConfigValue<bool> disable_screensaver{DISABLE_SCREENSAVER_DEFAULT};
#undef DISABLE_SCREENSAVER_DEFAULT
ConfigValue<bool> play_boot_sound{false};
std::vector<std::string> game_paths;
std::mutex game_cache_entries_mutex;
@ -525,19 +524,6 @@ struct CemuConfig
ConfigValue<bool> emulate_dimensions_toypad{false};
}emulated_usb_devices{};
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* CreateGameEntry(uint64 titleId);

View file

@ -75,8 +75,6 @@ add_library(CemuGui
input/InputAPIAddWindow.h
input/InputSettings2.cpp
input/InputSettings2.h
input/PairingDialog.cpp
input/PairingDialog.h
input/panels/ClassicControllerInputPanel.cpp
input/panels/ClassicControllerInputPanel.h
input/panels/InputPanel.cpp
@ -99,6 +97,8 @@ add_library(CemuGui
MemorySearcherTool.h
PadViewFrame.cpp
PadViewFrame.h
PairingDialog.cpp
PairingDialog.h
TitleManager.cpp
TitleManager.h
EmulatedUSBDevices/EmulatedUSBDeviceFrame.cpp
@ -178,7 +178,3 @@ endif()
if(WIN32)
target_link_libraries(CemuGui PRIVATE bthprops)
endif()
if(ALLOW_PORTABLE)
target_compile_definitions(CemuGui PRIVATE CEMU_ALLOW_PORTABLE)
endif ()

View file

@ -88,14 +88,12 @@ void CemuApp::DeterminePaths(std::set<fs::path>& failedWriteAccess) // for Windo
fs::path exePath(wxHelper::MakeFSPath(standardPaths.GetExecutablePath()));
fs::path portablePath = exePath.parent_path() / "portable";
data_path = exePath.parent_path(); // the data path is always the same as the exe path
#ifdef CEMU_ALLOW_PORTABLE
if (fs::is_directory(portablePath, ec))
if (fs::exists(portablePath, ec))
{
isPortable = true;
user_data_path = config_path = cache_path = portablePath;
}
else
#endif
{
fs::path roamingPath = GetAppDataRoamingPath() / "Cemu";
user_data_path = config_path = cache_path = roamingPath;
@ -126,13 +124,12 @@ void CemuApp::DeterminePaths(std::set<fs::path>& failedWriteAccess) // for Linux
fs::path portablePath = exePath.parent_path() / "portable";
// GetExecutablePath returns the AppImage's temporary mount location
wxString appImagePath;
if (wxGetEnv("APPIMAGE", &appImagePath))
if (wxGetEnv(("APPIMAGE"), &appImagePath))
{
exePath = wxHelper::MakeFSPath(appImagePath);
portablePath = exePath.parent_path() / "portable";
}
#ifdef CEMU_ALLOW_PORTABLE
if (fs::is_directory(portablePath, ec))
if (fs::exists(portablePath, ec))
{
isPortable = true;
user_data_path = config_path = cache_path = portablePath;
@ -140,7 +137,6 @@ void CemuApp::DeterminePaths(std::set<fs::path>& failedWriteAccess) // for Linux
data_path = exePath.parent_path();
}
else
#endif
{
SetAppName("Cemu");
wxString appName = GetAppName();
@ -174,15 +170,13 @@ void CemuApp::DeterminePaths(std::set<fs::path>& failedWriteAccess) // for MacOS
// If run from an app bundle, use its parent directory
fs::path appPath = exePath.parent_path().parent_path().parent_path();
fs::path portablePath = appPath.extension() == ".app" ? appPath.parent_path() / "portable" : exePath.parent_path() / "portable";
#ifdef CEMU_ALLOW_PORTABLE
if (fs::is_directory(portablePath, ec))
if (fs::exists(portablePath, ec))
{
isPortable = true;
user_data_path = config_path = cache_path = portablePath;
data_path = exePath.parent_path();
}
else
#endif
{
SetAppName("Cemu");
wxString appName = GetAppName();
@ -240,12 +234,6 @@ void CemuApp::InitializeExistingMLCOrFail(fs::path mlc)
g_config.Save();
}
}
else
{
// default path is not writeable. Just let the user know and quit. Unsure if it would be a good idea to ask the user to choose an alternative path instead
wxMessageBox(formatWxString(_("Cemu failed to write to the default mlc directory.\nThe path is:\n{}"), wxHelper::FromPath(mlc)), _("Error"), wxOK | wxCENTRE | wxICON_ERROR);
exit(0);
}
}
bool CemuApp::OnInit()
@ -519,13 +507,6 @@ bool CemuApp::CreateDefaultMLCFiles(const fs::path& mlc)
file.flush();
file.close();
}
// create a dummy file in the mlc folder to check if it's writable
const auto dummyFile = fs::path(mlc).append("writetestdummy");
std::ofstream file(dummyFile);
if (!file.is_open())
return false;
file.close();
fs::remove(dummyFile);
}
catch (const std::exception& ex)
{

View file

@ -207,10 +207,8 @@ wxPanel* GeneralSettings2::AddGeneralPage(wxNotebook* notebook)
#if BOOST_OS_MACOS
m_disable_screensaver->Enable(false);
#endif
m_play_boot_sound = new wxCheckBox(box, wxID_ANY, _("Enable intro sound"));
m_play_boot_sound->SetToolTip(_("Play bootSound file while compiling shaders/pipelines."));
second_row->Add(m_play_boot_sound, 0, botflag, 5);
CountRowElement();
// InsertEmptyRow();
m_auto_update = new wxCheckBox(box, wxID_ANY, _("Automatically check for updates"));
m_auto_update->SetToolTip(_("Automatically checks for new cemu versions on startup"));
@ -938,7 +936,6 @@ void GeneralSettings2::StoreConfig()
#if BOOST_OS_LINUX && defined(ENABLE_FERAL_GAMEMODE)
config.feral_gamemode = m_feral_gamemode->IsChecked();
#endif
config.play_boot_sound = m_play_boot_sound->IsChecked();
config.disable_screensaver = m_disable_screensaver->IsChecked();
// Toggle while a game is running
if (CafeSystem::IsTitleRunning())
@ -946,7 +943,6 @@ void GeneralSettings2::StoreConfig()
ScreenSaver::SetInhibit(config.disable_screensaver);
}
// -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_size = m_save_window_position_size->IsChecked() ? Vector2i{ 0,0 } : Vector2i{-1,-1};
@ -1578,7 +1574,6 @@ void GeneralSettings2::ApplyConfig()
m_save_screenshot->SetValue(config.save_screenshot);
m_disable_screensaver->SetValue(config.disable_screensaver);
m_play_boot_sound->SetValue(config.play_boot_sound);
#if BOOST_OS_LINUX && defined(ENABLE_FERAL_GAMEMODE)
m_feral_gamemode->SetValue(config.feral_gamemode);
#endif
@ -1781,7 +1776,20 @@ void GeneralSettings2::UpdateAudioDevice()
if (m_game_launched && g_tvAudio)
channels = g_tvAudio->GetChannels();
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
{
@ -1816,7 +1824,20 @@ void GeneralSettings2::UpdateAudioDevice()
if (m_game_launched && g_padAudio)
channels = g_padAudio->GetChannels();
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
{
@ -1852,7 +1873,20 @@ void GeneralSettings2::UpdateAudioDevice()
if (m_game_launched && g_inputAudio)
channels = g_inputAudio->GetChannels();
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
{

Some files were not shown because too many files have changed in this diff Show more