Merge branch 'cemu-project:main' into main

This commit is contained in:
rcaridade145 2026-05-11 17:35:09 +01:00 committed by GitHub
commit a040b12791
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
77 changed files with 1505 additions and 978 deletions

View File

@ -207,6 +207,7 @@ jobs:
build-macos:
runs-on: macos-14
strategy:
fail-fast: false
matrix:
arch: [x86_64, arm64]
steps:

View File

@ -244,9 +244,9 @@ Example usage: `cmake -S . -B build -DCMAKE_BUILD_TYPE=release -DENABLE_SDL=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_OPENGL | | Enable OpenGL graphics backend | ON | |
| ENABLE_HIDAPI | | Enable HIDAPI (used for Wiimote controller API) | ON | |
| ENABLE_SDL | | Enable SDLController controller API | ON | Currently required |
| ENABLE_SDL | | Enable SDLController controller API | ON | |
| 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 |

View File

@ -110,11 +110,13 @@ endif()
if (APPLE)
set(ENABLE_METAL_DEFAULT ON)
set(ENABLE_OPENGL_DEFAULT OFF)
else()
set(ENABLE_METAL_DEFAULT OFF)
set(ENABLE_OPENGL_DEFAULT ON)
endif()
option(ENABLE_OPENGL "Enables the OpenGL backend" ON)
option(ENABLE_OPENGL "Enables the OpenGL backend" ${ENABLE_OPENGL_DEFAULT})
option(ENABLE_VULKAN "Enables the Vulkan backend" ON)
option(ENABLE_METAL "Enables the Metal backend" ${ENABLE_METAL_DEFAULT})
option(ENABLE_DISCORD_RPC "Enables the Discord Rich Presence feature" ON)
@ -148,7 +150,6 @@ option(ENABLE_CUBEB "Enabled cubeb backend" ON)
option(ENABLE_WXWIDGETS "Build with wxWidgets UI (Currently required)" ON)
find_package(Threads REQUIRED)
find_package(SDL2 REQUIRED)
find_package(CURL REQUIRED)
find_package(pugixml REQUIRED)
find_package(RapidJSON REQUIRED)
@ -162,6 +163,11 @@ find_package(glm REQUIRED)
find_package(fmt 9 REQUIRED)
find_package(PNG REQUIRED)
if(ENABLE_SDL)
find_package(SDL3 REQUIRED CONFIG REQUIRED COMPONENTS SDL3)
add_compile_definitions(HAS_SDL)
endif()
# glslang versions older than 11.11.0 define targets without a namespace
if (NOT TARGET glslang::SPIRV AND TARGET SPIRV)
add_library(glslang::SPIRV ALIAS SPIRV)
@ -194,16 +200,19 @@ endif()
if (ENABLE_VULKAN)
include_directories("dependencies/Vulkan-Headers/include")
add_compile_definitions(ENABLE_VULKAN)
add_compile_definitions(VK_NO_PROTOTYPES)
endif()
if (ENABLE_OPENGL)
find_package(OpenGL REQUIRED)
add_compile_definitions(ENABLE_OPENGL)
endif()
if (ENABLE_METAL)
include_directories(${CMAKE_SOURCE_DIR}/dependencies/metal-cpp)
add_definitions(-DENABLE_METAL=1)
add_compile_definitions(ENABLE_METAL)
endif()
if (ENABLE_DISCORD_RPC)
@ -211,7 +220,6 @@ if (ENABLE_DISCORD_RPC)
endif()
if (ENABLE_HIDAPI)
find_package(hidapi REQUIRED)
set(SUPPORTS_WIIMOTE ON)
add_compile_definitions(HAS_HIDAPI)
endif ()

2
dependencies/vcpkg vendored

@ -1 +1 @@
Subproject commit af752f21c9d79ba3df9cb0250ce2233933f58486
Subproject commit c3867e714dd3a51c272826eea77267876517ed99

View File

@ -13,18 +13,24 @@ if(MSVC)
add_compile_definitions(WIN32_LEAN_AND_MEAN CURL_STATICLIB)
elseif(UNIX)
if(APPLE)
add_compile_definitions(
_XOPEN_SOURCE
VK_USE_PLATFORM_MACOS_MVK
VK_USE_PLATFORM_METAL_EXT
)
if (ENABLE_VULKAN)
add_compile_definitions(
_XOPEN_SOURCE
VK_USE_PLATFORM_MACOS_MVK
VK_USE_PLATFORM_METAL_EXT
)
else()
add_compile_definitions(_XOPEN_SOURCE)
endif()
else()
add_compile_definitions(
VK_USE_PLATFORM_XLIB_KHR # legacy. Do we need to support XLIB surfaces?
VK_USE_PLATFORM_XCB_KHR
)
if (ENABLE_WAYLAND)
add_compile_definitions(VK_USE_PLATFORM_WAYLAND_KHR)
if (ENABLE_VULKAN)
add_compile_definitions(
VK_USE_PLATFORM_XLIB_KHR # legacy. Do we need to support XLIB surfaces?
VK_USE_PLATFORM_XCB_KHR
)
if (ENABLE_WAYLAND)
add_compile_definitions(VK_USE_PLATFORM_WAYLAND_KHR)
endif()
endif()
endif()
# warnings
@ -35,8 +41,6 @@ elseif(UNIX)
add_compile_options(-Wno-multichar -Wno-invalid-offsetof -Wno-switch -Wno-ignored-attributes -Wno-deprecated-enum-enum-conversion)
endif()
add_compile_definitions(VK_NO_PROTOTYPES)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
# on msvc we can reuse the PCH from CemuCommon, on other platforms it's not straight forward to reuse the PCH so we opt out of it
@ -119,6 +123,16 @@ if (MACOS_BUNDLE)
COMMAND ${CMAKE_COMMAND} ARGS -E copy_directory "${CMAKE_SOURCE_DIR}/bin/${folder}" "$<TARGET_BUNDLE_DIR:CemuBin>/Contents/SharedSupport/${folder}")
endforeach(folder)
if (ENABLE_VULKAN)
if (EXISTS "/usr/local/lib/libMoltenVK.dylib")
set(MOLTENVK_PATH "/usr/local/lib/libMoltenVK.dylib")
elseif (EXISTS "/opt/homebrew/lib/libMoltenVK.dylib")
set(MOLTENVK_PATH "/opt/homebrew/lib/libMoltenVK.dylib")
else()
message(FATAL_ERROR "failed to find libMoltenVK.dylib")
endif ()
endif()
if (ENABLE_LIBUSB)
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
set(LIBUSB_PATH "${CMAKE_BINARY_DIR}/vcpkg_installed/${VCPKG_TARGET_TRIPLET}/debug/lib/libusb-1.0.0.dylib")
@ -127,23 +141,20 @@ if (MACOS_BUNDLE)
endif()
endif()
if (EXISTS "/usr/local/lib/libMoltenVK.dylib")
set(MOLTENVK_PATH "/usr/local/lib/libMoltenVK.dylib")
elseif (EXISTS "/opt/homebrew/lib/libMoltenVK.dylib")
set(MOLTENVK_PATH "/opt/homebrew/lib/libMoltenVK.dylib")
else()
message(FATAL_ERROR "failed to find libMoltenVK.dylib")
endif ()
set(UPDATE_SH_PATH "${CMAKE_SOURCE_DIR}/src/resource/update.sh")
set(APP_BUNDLE_DIR "$<TARGET_BUNDLE_DIR:CemuBin>")
set(FRAMEWORKS_DIR "${APP_BUNDLE_DIR}/Contents/Frameworks")
set(RESOURCES_DIR "${APP_BUNDLE_DIR}/Contents/Resources")
if (ENABLE_VULKAN)
add_custom_command(TARGET CemuBin POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
"${MOLTENVK_PATH}"
"${FRAMEWORKS_DIR}/libMoltenVK.dylib"
)
endif()
add_custom_command(TARGET CemuBin POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
"${MOLTENVK_PATH}"
"${FRAMEWORKS_DIR}/libMoltenVK.dylib"
COMMAND ${CMAKE_COMMAND} -E copy
"${UPDATE_SH_PATH}"
"${RESOURCES_DIR}/update.sh"
@ -174,20 +185,22 @@ if (MACOS_BUNDLE)
endif()
else()
if(APPLE)
find_library(MOLTENVK_LIBRARY
NAMES MoltenVK moltenvk libMoltenVK.dylib
PATHS /usr/local/lib /opt/homebrew/lib
)
if(MOLTENVK_LIBRARY)
message(STATUS "Found MoltenVK: ${MOLTENVK_LIBRARY}")
target_link_libraries(CemuBin PRIVATE ${MOLTENVK_LIBRARY})
else()
message(WARNING "libMoltenVK.dylib not found")
endif()
set_target_properties(CemuBin PROPERTIES
BUILD_WITH_INSTALL_RPATH TRUE
INSTALL_RPATH "/usr/local/lib;/opt/homebrew/lib"
)
if (ENABLE_VULKAN)
find_library(MOLTENVK_LIBRARY
NAMES MoltenVK moltenvk libMoltenVK.dylib
PATHS /usr/local/lib /opt/homebrew/lib
)
if(MOLTENVK_LIBRARY)
message(STATUS "Found MoltenVK: ${MOLTENVK_LIBRARY}")
target_link_libraries(CemuBin PRIVATE ${MOLTENVK_LIBRARY})
else()
message(WARNING "libMoltenVK.dylib not found")
endif()
set_target_properties(CemuBin PROPERTIES
BUILD_WITH_INSTALL_RPATH TRUE
INSTALL_RPATH "/usr/local/lib;/opt/homebrew/lib"
)
endif()
endif()
endif()
@ -207,9 +220,12 @@ target_link_libraries(CemuBin PRIVATE
CemuGui
CemuInput
CemuUtil
SDL2::SDL2
)
if(ENABLE_SDL)
target_link_libraries(CemuBin PRIVATE SDL3::SDL3)
endif()
if(UNIX AND NOT APPLE)
# due to nasm output some linkers will make stack executable
# cemu does not require this so we explicity disable it

View File

@ -106,8 +106,6 @@ add_library(CemuCafe
HW/Latte/Core/LatteCachedFBO.h
HW/Latte/Core/LatteCommandProcessor.cpp
HW/Latte/Core/LatteConst.h
HW/Latte/Core/LatteDefaultShaders.cpp
HW/Latte/Core/LatteDefaultShaders.h
HW/Latte/Core/LatteDraw.h
HW/Latte/Core/LatteGSCopyShaderParser.cpp
HW/Latte/Core/Latte.h
@ -127,7 +125,6 @@ add_library(CemuCafe
HW/Latte/Core/LatteShaderCache.cpp
HW/Latte/Core/LatteShaderCache.h
HW/Latte/Core/LatteShader.cpp
HW/Latte/Core/LatteShaderGL.cpp
HW/Latte/Core/LatteShader.h
HW/Latte/Core/LatteSoftware.cpp
HW/Latte/Core/LatteSoftware.h
@ -163,58 +160,14 @@ add_library(CemuCafe
HW/Latte/LegacyShaderDecompiler/LatteDecompilerInstructions.h
HW/Latte/LegacyShaderDecompiler/LatteDecompilerInternal.h
HW/Latte/LegacyShaderDecompiler/LatteDecompilerRegisterDataTypeTracker.cpp
HW/Latte/Renderer/OpenGL/CachedFBOGL.h
HW/Latte/Renderer/OpenGL/LatteTextureGL.cpp
HW/Latte/Renderer/OpenGL/LatteTextureGL.h
HW/Latte/Renderer/OpenGL/LatteTextureViewGL.cpp
HW/Latte/Renderer/OpenGL/LatteTextureViewGL.h
HW/Latte/Renderer/OpenGL/OpenGLQuery.cpp
HW/Latte/Renderer/OpenGL/OpenGLRendererCore.cpp
HW/Latte/Renderer/OpenGL/OpenGLRenderer.cpp
HW/Latte/Renderer/OpenGL/OpenGLRenderer.h
HW/Latte/Renderer/OpenGL/OpenGLRendererStreamout.cpp
HW/Latte/Renderer/OpenGL/OpenGLRendererUniformData.cpp
HW/Latte/Renderer/OpenGL/OpenGLSurfaceCopy.cpp
HW/Latte/Renderer/OpenGL/OpenGLTextureReadback.h
HW/Latte/Renderer/OpenGL/RendererShaderGL.cpp
HW/Latte/Renderer/OpenGL/RendererShaderGL.h
HW/Latte/Renderer/OpenGL/TextureReadbackGL.cpp
HW/Latte/Renderer/Renderer.cpp
HW/Latte/Renderer/Renderer.h
HW/Latte/Renderer/RendererCore.cpp
HW/Latte/Renderer/RendererCore.h
HW/Latte/Renderer/RendererOuputShader.cpp
HW/Latte/Renderer/RendererOuputShader.h
HW/Latte/Renderer/RendererShader.cpp
HW/Latte/Renderer/RendererShader.h
HW/Latte/Renderer/Vulkan/CachedFBOVk.cpp
HW/Latte/Renderer/Vulkan/CachedFBOVk.h
HW/Latte/Renderer/Vulkan/CocoaSurface.h
HW/Latte/Renderer/Vulkan/LatteTextureViewVk.cpp
HW/Latte/Renderer/Vulkan/LatteTextureViewVk.h
HW/Latte/Renderer/Vulkan/LatteTextureVk.cpp
HW/Latte/Renderer/Vulkan/LatteTextureVk.h
HW/Latte/Renderer/Vulkan/RendererShaderVk.cpp
HW/Latte/Renderer/Vulkan/RendererShaderVk.h
HW/Latte/Renderer/Vulkan/SwapchainInfoVk.cpp
HW/Latte/Renderer/Vulkan/SwapchainInfoVk.h
HW/Latte/Renderer/Vulkan/TextureReadbackVk.cpp
HW/Latte/Renderer/Vulkan/VKRBase.h
HW/Latte/Renderer/Vulkan/VKRMemoryManager.cpp
HW/Latte/Renderer/Vulkan/VKRMemoryManager.h
HW/Latte/Renderer/Vulkan/VKRPipelineInfo.cpp
HW/Latte/Renderer/Vulkan/VsyncDriver.cpp
HW/Latte/Renderer/Vulkan/VsyncDriver.h
HW/Latte/Renderer/Vulkan/VulkanAPI.cpp
HW/Latte/Renderer/Vulkan/VulkanAPI.h
HW/Latte/Renderer/Vulkan/VulkanPipelineCompiler.cpp
HW/Latte/Renderer/Vulkan/VulkanPipelineCompiler.h
HW/Latte/Renderer/Vulkan/VulkanPipelineStableCache.cpp
HW/Latte/Renderer/Vulkan/VulkanPipelineStableCache.h
HW/Latte/Renderer/Vulkan/VulkanQuery.cpp
HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp
HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp
HW/Latte/Renderer/Vulkan/VulkanRenderer.h
HW/Latte/Renderer/Vulkan/VulkanSurfaceCopy.cpp
HW/Latte/Renderer/Vulkan/VulkanTextureReadback.h
HW/Latte/ShaderInfo/ShaderDescription.cpp
HW/Latte/ShaderInfo/ShaderInfo.h
HW/Latte/ShaderInfo/ShaderInstanceInfo.cpp
@ -538,12 +491,73 @@ add_library(CemuCafe
TitleList/TitleList.h
)
if (ENABLE_OPENGL)
target_sources(CemuCafe PRIVATE
HW/Latte/Renderer/OpenGL/CachedFBOGL.h
HW/Latte/Renderer/OpenGL/LatteShaderGL.cpp
HW/Latte/Renderer/OpenGL/LatteTextureGL.cpp
HW/Latte/Renderer/OpenGL/LatteTextureGL.h
HW/Latte/Renderer/OpenGL/LatteTextureViewGL.cpp
HW/Latte/Renderer/OpenGL/LatteTextureViewGL.h
HW/Latte/Renderer/OpenGL/OpenGLQuery.cpp
HW/Latte/Renderer/OpenGL/OpenGLRendererCore.cpp
HW/Latte/Renderer/OpenGL/OpenGLRenderer.cpp
HW/Latte/Renderer/OpenGL/OpenGLRenderer.h
HW/Latte/Renderer/OpenGL/OpenGLRendererStreamout.cpp
HW/Latte/Renderer/OpenGL/OpenGLRendererUniformData.cpp
HW/Latte/Renderer/OpenGL/OpenGLSurfaceCopy.cpp
HW/Latte/Renderer/OpenGL/OpenGLTextureReadback.h
HW/Latte/Renderer/OpenGL/RendererShaderGL.cpp
HW/Latte/Renderer/OpenGL/RendererShaderGL.h
HW/Latte/Renderer/OpenGL/TextureReadbackGL.cpp
)
endif()
if (ENABLE_VULKAN)
target_sources(CemuCafe PRIVATE
HW/Latte/Renderer/Vulkan/CachedFBOVk.cpp
HW/Latte/Renderer/Vulkan/CachedFBOVk.h
HW/Latte/Renderer/Vulkan/CocoaSurface.h
HW/Latte/Renderer/Vulkan/LatteTextureViewVk.cpp
HW/Latte/Renderer/Vulkan/LatteTextureViewVk.h
HW/Latte/Renderer/Vulkan/LatteTextureVk.cpp
HW/Latte/Renderer/Vulkan/LatteTextureVk.h
HW/Latte/Renderer/Vulkan/RendererShaderVk.cpp
HW/Latte/Renderer/Vulkan/RendererShaderVk.h
HW/Latte/Renderer/Vulkan/SwapchainInfoVk.cpp
HW/Latte/Renderer/Vulkan/SwapchainInfoVk.h
HW/Latte/Renderer/Vulkan/TextureReadbackVk.cpp
HW/Latte/Renderer/Vulkan/VKRBase.h
HW/Latte/Renderer/Vulkan/VKRMemoryManager.cpp
HW/Latte/Renderer/Vulkan/VKRMemoryManager.h
HW/Latte/Renderer/Vulkan/VKRPipelineInfo.cpp
HW/Latte/Renderer/Vulkan/VsyncDriver.cpp
HW/Latte/Renderer/Vulkan/VsyncDriver.h
HW/Latte/Renderer/Vulkan/VulkanAPI.cpp
HW/Latte/Renderer/Vulkan/VulkanAPI.h
HW/Latte/Renderer/Vulkan/VulkanPipelineCompiler.cpp
HW/Latte/Renderer/Vulkan/VulkanPipelineCompiler.h
HW/Latte/Renderer/Vulkan/VulkanPipelineStableCache.cpp
HW/Latte/Renderer/Vulkan/VulkanPipelineStableCache.h
HW/Latte/Renderer/Vulkan/VulkanQuery.cpp
HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp
HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp
HW/Latte/Renderer/Vulkan/VulkanRenderer.h
HW/Latte/Renderer/Vulkan/VulkanSurfaceCopy.cpp
HW/Latte/Renderer/Vulkan/VulkanTextureReadback.h
)
endif()
if(APPLE)
target_sources(CemuCafe PRIVATE
HW/Latte/Renderer/Vulkan/CocoaSurface.mm
HW/Latte/Renderer/MetalView.mm
HW/Latte/Renderer/MetalView.h
)
if (ENABLE_VULKAN)
target_sources(CemuCafe PRIVATE
HW/Latte/Renderer/Vulkan/CocoaSurface.mm
)
endif()
endif()
if(ENABLE_METAL)

View File

@ -251,7 +251,7 @@ void InfoLog_PrintActiveSettings()
if (!GetConfig().vk_accurate_barriers.GetValue())
cemuLog_log(LogType::Force, "Accurate barriers are disabled!");
}
#if ENABLE_METAL
#ifdef ENABLE_METAL
else if (ActiveSettings::GetGraphicsAPI() == GraphicAPI::kMetal)
{
cemuLog_log(LogType::Force, "Async compile: {}", GetConfig().async_compile.GetValue() ? "true" : "false");

View File

@ -226,7 +226,7 @@ bool GameProfile::Load(uint64_t title_id)
m_graphics_api = (GraphicAPI)graphicsApi.value;
gameProfile_loadEnumOption(iniParser, "accurateShaderMul", m_accurateShaderMul);
#if ENABLE_METAL
#ifdef ENABLE_METAL
gameProfile_loadBooleanOption2(iniParser, "shaderFastMath", m_shaderFastMath);
gameProfile_loadEnumOption(iniParser, "metalBufferCacheMode2", m_metalBufferCacheMode);
gameProfile_loadEnumOption(iniParser, "positionInvariance2", m_positionInvariance);
@ -311,7 +311,7 @@ void GameProfile::Save(uint64_t title_id)
fs->writeLine("[Graphics]");
WRITE_ENTRY(accurateShaderMul);
#if ENABLE_METAL
#ifdef ENABLE_METAL
WRITE_ENTRY(shaderFastMath);
WRITE_ENTRY_NUMBERED(metalBufferCacheMode, 2);
WRITE_ENTRY_NUMBERED(positionInvariance, 2);
@ -346,7 +346,7 @@ void GameProfile::ResetOptional()
// graphic settings
m_accurateShaderMul = AccurateShaderMulOption::True;
#if ENABLE_METAL
#ifdef ENABLE_METAL
m_shaderFastMath = true;
m_metalBufferCacheMode = MetalBufferCacheMode::Auto;
m_positionInvariance = PositionInvariance::Auto;
@ -371,7 +371,7 @@ void GameProfile::Reset()
// graphic settings
m_accurateShaderMul = AccurateShaderMulOption::True;
#if ENABLE_METAL
#ifdef ENABLE_METAL
m_shaderFastMath = true;
m_metalBufferCacheMode = MetalBufferCacheMode::Auto;
m_positionInvariance = PositionInvariance::Auto;

View File

@ -29,7 +29,7 @@ public:
[[nodiscard]] const std::optional<GraphicAPI>& GetGraphicsAPI() const { return m_graphics_api; }
[[nodiscard]] const AccurateShaderMulOption& GetAccurateShaderMul() const { return m_accurateShaderMul; }
#if ENABLE_METAL
#ifdef ENABLE_METAL
[[nodiscard]] bool GetShaderFastMath() const { return m_shaderFastMath; }
[[nodiscard]] MetalBufferCacheMode GetBufferCacheMode() const { return m_metalBufferCacheMode; }
[[nodiscard]] PositionInvariance GetPositionInvariance() const { return m_positionInvariance; }
@ -57,7 +57,7 @@ private:
// graphic settings
std::optional<GraphicAPI> m_graphics_api{};
AccurateShaderMulOption m_accurateShaderMul = AccurateShaderMulOption::True;
#if ENABLE_METAL
#ifdef ENABLE_METAL
bool m_shaderFastMath = true;
MetalBufferCacheMode m_metalBufferCacheMode = MetalBufferCacheMode::Auto;
PositionInvariance m_positionInvariance = PositionInvariance::Auto;

View File

@ -11,7 +11,7 @@
#include "HW/Latte/Renderer/Renderer.h"
#include "util/containers/LookupTableL3.h"
#include "util/helpers/fspinlock.h"
#if ENABLE_METAL
#ifdef ENABLE_METAL
#include "Cafe/HW/Latte/Renderer/Metal/LatteToMtl.h"
#endif
#include <openssl/sha.h> /* SHA1_DIGEST_LENGTH */
@ -108,21 +108,28 @@ void LatteShader_calculateFSKey(LatteFetchShader* fetchShader)
key = std::rotl<uint64>(key, 8);
key += (uint64)attrib->semanticId;
key = std::rotl<uint64>(key, 8);
if (g_renderer->GetType() == RendererAPI::Metal)
switch(g_renderer->GetType())
{
#ifdef ENABLE_METAL
case RendererAPI::Metal:
{
key += (uint64)attrib->offset;
key = std::rotl<uint64>(key, 7);
break;
}
else
#endif
default:
{
key += (uint64)(attrib->offset & 3);
key = std::rotl<uint64>(key, 2);
break;
}
}
}
}
// todo - also hash invalid buffer groups?
#if ENABLE_METAL
#ifdef ENABLE_METAL
if (g_renderer->GetType() == RendererAPI::Metal)
{
for (sint32 g = 0; g < fetchShader->bufferGroups.size(); g++)
@ -171,7 +178,7 @@ void LatteFetchShader::CalculateFetchShaderVkHash()
this->vkPipelineHashFragment = h;
}
#if ENABLE_METAL
#ifdef ENABLE_METAL
void LatteFetchShader::CheckIfVerticesNeedManualFetchMtl(uint32* contextRegister)
{
for (sint32 g = 0; g < bufferGroups.size(); g++)
@ -376,7 +383,7 @@ LatteFetchShader* LatteShaderRecompiler_createFetchShader(LatteFetchShader::Cach
// these only make sense when vertex shader does not call FS?
LatteShader_calculateFSKey(newFetchShader);
newFetchShader->CalculateFetchShaderVkHash();
#if ENABLE_METAL
#ifdef ENABLE_METAL
newFetchShader->CheckIfVerticesNeedManualFetchMtl(contextRegister);
#endif
return newFetchShader;
@ -438,7 +445,7 @@ LatteFetchShader* LatteShaderRecompiler_createFetchShader(LatteFetchShader::Cach
}
LatteShader_calculateFSKey(newFetchShader);
newFetchShader->CalculateFetchShaderVkHash();
#if ENABLE_METAL
#ifdef ENABLE_METAL
newFetchShader->CheckIfVerticesNeedManualFetchMtl(contextRegister);
#endif

View File

@ -55,7 +55,7 @@ struct LatteFetchShader
void CalculateFetchShaderVkHash();
#if ENABLE_METAL
#ifdef ENABLE_METAL
void CheckIfVerticesNeedManualFetchMtl(uint32* contextRegister);
#endif

View File

@ -776,9 +776,8 @@ private:
{
TreeNode* treeNodeParent = (treeNode->parentNodeIndex != INVALID_NODE_INDEX) ? &m_treeNodes[treeNode->parentNodeIndex] : nullptr;
ReleaseTreeNode(treeNode, true);
if (treeNodeParent && treeNodeParent->usedCount > 0)
PropagateMinValue(treeNodeParent);
// note - this will never shrink the tree, since there is at least one left or right neighbor
if (treeNodeParent)
CollapseNode(treeNodeParent, depth - 1);
return;
}
// todo - redistribute values

View File

@ -9,7 +9,9 @@
#include "Cafe/GameProfile/GameProfile.h"
#include "Cafe/HW/Latte/Core/LatteBufferCache.h"
#ifdef ENABLE_VULKAN
#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h"
#endif
template<int vectorLen>
void rectGenerate4thVertex(uint32be* output, uint32be* input0, uint32be* input1, uint32be* input2)
@ -196,7 +198,7 @@ bool LatteBufferCache_Sync(uint32 minIndex, uint32 maxIndex, uint32 baseInstance
fixedBufferSize += 128;
#if BOOST_OS_MACOS
#if BOOST_OS_MACOS && defined(ENABLE_VULKAN)
if(bufferStride % 4 != 0)
{
if (g_renderer->GetType() == RendererAPI::Vulkan)

View File

@ -1,87 +0,0 @@
#include "Cafe/HW/Latte/Core/Latte.h"
#include "Cafe/HW/Latte/Core/LatteDraw.h"
#include "Cafe/HW/Latte/Core/LatteShader.h"
#include "Cafe/HW/Latte/Core/LatteDefaultShaders.h"
#include "util/helpers/StringBuf.h"
LatteDefaultShader_t* _copyShader_depthToColor;
LatteDefaultShader_t* _copyShader_colorToDepth;
void LatteDefaultShader_pixelCopyShader_generateVSBody(StringBuf* vs)
{
vs->add("#version 420\r\n");
vs->add("out vec2 passUV;\r\n");
vs->add("uniform vec4 uf_vertexOffsets[4];\r\n");
vs->add("\r\n");
vs->add("void main(){\r\n");
vs->add("int vID = gl_VertexID;\r\n");
vs->add("passUV = uf_vertexOffsets[vID].zw;\r\n");
vs->add("gl_Position = vec4(uf_vertexOffsets[vID].xy, 0.0, 1.0);\r\n");
vs->add("}\r\n");
}
GLuint gxShaderDepr_compileRaw(StringBuf* strSourceVS, StringBuf* strSourceFS);
GLuint gxShaderDepr_compileRaw(const std::string& vertex_source, const std::string& fragment_source);
LatteDefaultShader_t* LatteDefaultShader_getPixelCopyShader_depthToColor()
{
if (_copyShader_depthToColor != 0)
return _copyShader_depthToColor;
catchOpenGLError();
LatteDefaultShader_t* defaultShader = (LatteDefaultShader_t*)malloc(sizeof(LatteDefaultShader_t));
memset(defaultShader, 0, sizeof(LatteDefaultShader_t));
StringBuf fCStr_vertexShader(1024 * 16);
LatteDefaultShader_pixelCopyShader_generateVSBody(&fCStr_vertexShader);
StringBuf fCStr_defaultFragShader(1024 * 16);
fCStr_defaultFragShader.add("#version 420\r\n");
fCStr_defaultFragShader.add("in vec2 passUV;\r\n");
fCStr_defaultFragShader.add("uniform sampler2D textureSrc;\r\n");
fCStr_defaultFragShader.add("layout(location = 0) out vec4 colorOut0;\r\n");
fCStr_defaultFragShader.add("\r\n");
fCStr_defaultFragShader.add("void main(){\r\n");
fCStr_defaultFragShader.add("colorOut0 = vec4(texture(textureSrc, passUV).r,0.0,0.0,1.0);\r\n");
fCStr_defaultFragShader.add("}\r\n");
defaultShader->glProgamId = gxShaderDepr_compileRaw(&fCStr_vertexShader, &fCStr_defaultFragShader);
catchOpenGLError();
defaultShader->copyShaderUniforms.uniformLoc_textureSrc = glGetUniformLocation(defaultShader->glProgamId, "textureSrc");
defaultShader->copyShaderUniforms.uniformLoc_vertexOffsets = glGetUniformLocation(defaultShader->glProgamId, "uf_vertexOffsets");
_copyShader_depthToColor = defaultShader;
catchOpenGLError();
return defaultShader;
}
LatteDefaultShader_t* LatteDefaultShader_getPixelCopyShader_colorToDepth()
{
if (_copyShader_colorToDepth != 0)
return _copyShader_colorToDepth;
catchOpenGLError();
LatteDefaultShader_t* defaultShader = (LatteDefaultShader_t*)malloc(sizeof(LatteDefaultShader_t));
memset(defaultShader, 0, sizeof(LatteDefaultShader_t));
StringBuf fCStr_vertexShader(1024 * 16);
LatteDefaultShader_pixelCopyShader_generateVSBody(&fCStr_vertexShader);
StringBuf fCStr_defaultFragShader(1024 * 16);
fCStr_defaultFragShader.add("#version 420\r\n");
fCStr_defaultFragShader.add("in vec2 passUV;\r\n");
fCStr_defaultFragShader.add("uniform sampler2D textureSrc;\r\n");
fCStr_defaultFragShader.add("layout(location = 0) out vec4 colorOut0;\r\n");
fCStr_defaultFragShader.add("\r\n");
fCStr_defaultFragShader.add("void main(){\r\n");
fCStr_defaultFragShader.add("gl_FragDepth = texture(textureSrc, passUV).r;\r\n");
fCStr_defaultFragShader.add("}\r\n");
defaultShader->glProgamId = gxShaderDepr_compileRaw(&fCStr_vertexShader, &fCStr_defaultFragShader);
defaultShader->copyShaderUniforms.uniformLoc_textureSrc = glGetUniformLocation(defaultShader->glProgamId, "textureSrc");
defaultShader->copyShaderUniforms.uniformLoc_vertexOffsets = glGetUniformLocation(defaultShader->glProgamId, "uf_vertexOffsets");
_copyShader_colorToDepth = defaultShader;
catchOpenGLError();
return defaultShader;
}

View File

@ -1,13 +0,0 @@
typedef struct
{
GLuint glProgamId;
struct
{
GLuint uniformLoc_textureSrc;
GLuint uniformLoc_vertexOffsets;
}copyShaderUniforms;
}LatteDefaultShader_t;
LatteDefaultShader_t* LatteDefaultShader_getPixelCopyShader_depthToColor();
LatteDefaultShader_t* LatteDefaultShader_getPixelCopyShader_colorToDepth();

View File

@ -10,6 +10,7 @@
#include "Cafe/HW/Latte/Renderer/Renderer.h"
#include "Cafe/HW/Latte/Core/LattePerformanceMonitor.h"
#include "Cafe/GraphicPack/GraphicPack2.h"
#include "HW/Latte/Renderer/RendererCore.h"
#include "config/ActiveSettings.h"
#include "WindowSystem.h"
#include "Cafe/OS/libs/erreula/erreula.h"
@ -694,7 +695,9 @@ void LatteRenderTarget_itHLESwapScanBuffer()
performanceMonitor.gpuTime_frameTime.beginMeasuring();
LatteTC_CleanupUnusedTextures();
#ifdef ENABLE_OPENGL
LatteDraw_cleanupAfterFrame();
#endif
LatteQuery_CancelActiveGPU7Queries();
LatteBufferCache_notifySwapTVScanBuffer();
LattePerformanceMonitor_frameBegin();

View File

@ -1,3 +1,4 @@
#pragma once
typedef struct
{

View File

@ -6,16 +6,16 @@
#include "Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.h"
#include "Cafe/HW/Latte/Core/FetchShader.h"
#include "Cafe/HW/Latte/Core/LattePerformanceMonitor.h"
#ifdef ENABLE_VULKAN
#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h"
#include "Cafe/OS/libs/gx2/GX2.h" // todo - remove dependency
#endif
#include "Cafe/GraphicPack/GraphicPack2.h"
#include "HW/Latte/Core/Latte.h"
#include "HW/Latte/Renderer/Renderer.h"
#include "util/helpers/StringParser.h"
#include "config/ActiveSettings.h"
#include "Cafe/GameProfile/GameProfile.h"
#include "util/containers/flat_hash_map.hpp"
#if ENABLE_METAL
#ifdef ENABLE_METAL
#include "Cafe/HW/Latte/Renderer/Metal/LatteToMtl.h"
#endif
#include <cinttypes>
@ -376,7 +376,9 @@ void LatteShader_FinishCompilation(LatteDecompilerShader* shader)
}
shader->shader->WaitForCompiled();
#ifdef ENABLE_OPENGL
LatteShader_prepareSeparableUniforms(shader);
#endif
LatteShader_CleanupAfterCompile(shader);
}
@ -525,7 +527,7 @@ void LatteSHRC_UpdateVSBaseHash(uint8* vertexShaderPtr, uint32 vertexShaderSize,
if (LatteGPUState.contextNew.PA_CL_CLIP_CNTL.get_DX_CLIP_SPACE_DEF())
vsHash += 0x1537;
#if ENABLE_METAL
#ifdef ENABLE_METAL
if (g_renderer->GetType() == RendererAPI::Metal)
{
bool isRectVertexShader = (primitiveType == Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE::RECTS);
@ -550,7 +552,7 @@ void LatteSHRC_UpdateVSBaseHash(uint8* vertexShaderPtr, uint32 vertexShaderSize,
vsHash += 51ULL;
// Vertex fetch
if (_activeFetchShader->mtlFetchVertexManually)
if (_activeFetchShader->mtlFetchVertexManually)
vsHash += 349ULL;
}
}
@ -650,7 +652,7 @@ uint64 LatteSHRC_CalcPSAuxHash(LatteDecompilerShader* pixelShader, uint32* conte
auxHash += (uint64)dim;
}
#if ENABLE_METAL
#ifdef ENABLE_METAL
if (g_renderer->GetType() == RendererAPI::Metal)
{
// Textures as render targets
@ -683,56 +685,75 @@ uint64 LatteSHRC_CalcPSAuxHash(LatteDecompilerShader* pixelShader, uint32* conte
return auxHash;
}
static void InitUniformLayoutFromDecompiler(
LatteDecompilerShader* shader,
const LatteDecompilerOutput_t& decompilerOutput
)
{
if (g_renderer->GetType() == RendererAPI::OpenGL)
{
// hack - for OpenGL these are retrieved in _prepareSeparableUniforms()
shader->uniform.count_uniformRegister = decompilerOutput.uniformOffsetsGL.count_uniformRegister;
return;
}
const auto& offsets = decompilerOutput.uniformOffsetsVK;
shader->uniform.loc_remapped = offsets.offset_remapped;
shader->uniform.loc_uniformRegister = offsets.offset_uniformRegister;
shader->uniform.count_uniformRegister = offsets.count_uniformRegister;
shader->uniform.loc_windowSpaceToClipSpaceTransform = offsets.offset_windowSpaceToClipSpaceTransform;
shader->uniform.loc_alphaTestRef = offsets.offset_alphaTestRef;
shader->uniform.loc_pointSize = offsets.offset_pointSize;
shader->uniform.loc_fragCoordScale = offsets.offset_fragCoordScale;
// Texture scale uniforms
shader->uniform.list_ufTexRescale.clear();
for (sint32 t = 0; t < LATTE_NUM_MAX_TEX_UNITS; t++)
{
if (offsets.offset_texScale[t] >= 0)
{
LatteUniformTextureScaleEntry_t entry{};
entry.texUnit = t;
entry.uniformLocation = offsets.offset_texScale[t];
shader->uniform.list_ufTexRescale.push_back(entry);
}
}
shader->uniform.loc_verticesPerInstance = offsets.offset_verticesPerInstance;
// Streamout buffers
for (sint32 t = 0; t < LATTE_NUM_STREAMOUT_BUFFER; t++)
{
shader->uniform.loc_streamoutBufferBase[t] = offsets.offset_streamoutBufferBase[t];
}
shader->uniform.uniformRangeSize = offsets.offset_endOfBlock;
}
LatteDecompilerShader* LatteShader_CreateShaderFromDecompilerOutput(LatteDecompilerOutput_t& decompilerOutput, uint64 baseHash, bool calculateAuxHash, uint64 optionalAuxHash, uint32* contextRegister)
{
LatteDecompilerShader* shader = decompilerOutput.shader;
shader->baseHash = baseHash;
// copy resource mapping
// HACK
if (g_renderer->GetType() == RendererAPI::OpenGL)
switch (g_renderer->GetType())
{
case RendererAPI::OpenGL:
shader->resourceMapping = decompilerOutput.resourceMappingGL;
else if (g_renderer->GetType() == RendererAPI::Vulkan)
break;
case RendererAPI::Vulkan:
shader->resourceMapping = decompilerOutput.resourceMappingVK;
#if ENABLE_METAL
else
break;
case RendererAPI::Metal:
shader->resourceMapping = decompilerOutput.resourceMappingMTL;
#endif
break;
}
// copy texture info
shader->textureUnitMask2 = decompilerOutput.textureUnitMask;
// copy streamout info
shader->streamoutBufferWriteMask = decompilerOutput.streamoutBufferWriteMask;
shader->hasStreamoutBufferWrite = decompilerOutput.streamoutBufferWriteMask.any();
// copy uniform offsets
// for OpenGL these are retrieved in _prepareSeparableUniforms()
// HACK
if (g_renderer->GetType() == RendererAPI::Vulkan || g_renderer->GetType() == RendererAPI::Metal)
{
shader->uniform.loc_remapped = decompilerOutput.uniformOffsetsVK.offset_remapped;
shader->uniform.loc_uniformRegister = decompilerOutput.uniformOffsetsVK.offset_uniformRegister;
shader->uniform.count_uniformRegister = decompilerOutput.uniformOffsetsVK.count_uniformRegister;
shader->uniform.loc_windowSpaceToClipSpaceTransform = decompilerOutput.uniformOffsetsVK.offset_windowSpaceToClipSpaceTransform;
shader->uniform.loc_alphaTestRef = decompilerOutput.uniformOffsetsVK.offset_alphaTestRef;
shader->uniform.loc_pointSize = decompilerOutput.uniformOffsetsVK.offset_pointSize;
shader->uniform.loc_fragCoordScale = decompilerOutput.uniformOffsetsVK.offset_fragCoordScale;
for (sint32 t = 0; t < LATTE_NUM_MAX_TEX_UNITS; t++)
{
if (decompilerOutput.uniformOffsetsVK.offset_texScale[t] >= 0)
{
LatteUniformTextureScaleEntry_t entry = { 0 };
entry.texUnit = t;
entry.uniformLocation = decompilerOutput.uniformOffsetsVK.offset_texScale[t];
shader->uniform.list_ufTexRescale.push_back(entry);
}
}
shader->uniform.loc_verticesPerInstance = decompilerOutput.uniformOffsetsVK.offset_verticesPerInstance;
for (sint32 t = 0; t < LATTE_NUM_STREAMOUT_BUFFER; t++)
shader->uniform.loc_streamoutBufferBase[t] = decompilerOutput.uniformOffsetsVK.offset_streamoutBufferBase[t];
shader->uniform.uniformRangeSize = decompilerOutput.uniformOffsetsVK.offset_endOfBlock;
}
else
{
shader->uniform.count_uniformRegister = decompilerOutput.uniformOffsetsGL.count_uniformRegister;
}
InitUniformLayoutFromDecompiler(shader, decompilerOutput);
// calculate aux hash
if (calculateAuxHash)
{
@ -766,10 +787,12 @@ void LatteShader_GetDecompilerOptions(LatteDecompilerOptions& options, LatteCons
options.usesGeometryShader = geometryShaderEnabled;
options.spirvInstrinsics.hasRoundingModeRTEFloat32 = false;
options.useTFViaSSBO = g_renderer->UseTFViaSSBO();
#ifdef ENABLE_VULKAN
if (g_renderer->GetType() == RendererAPI::Vulkan)
{
options.spirvInstrinsics.hasRoundingModeRTEFloat32 = VulkanRenderer::GetInstance()->HasSPRIVRoundingModeRTE32();
}
#endif
options.strictMul = g_current_game_profile->GetAccurateShaderMul() != AccurateShaderMulOption::False;
}
@ -845,12 +868,14 @@ LatteDecompilerShader* LatteShader_CompileSeparableVertexShader(uint64 baseHash,
LatteShader_CreateRendererShader(vertexShader, false);
performanceMonitor.numCompiledVS++;
#ifdef ENABLE_OPENGL
if (g_renderer->GetType() == RendererAPI::OpenGL)
{
if (vertexShader->shader)
vertexShader->shader->PreponeCompilation(true);
LatteShader_FinishCompilation(vertexShader);
}
#endif
LatteSHRC_RegisterShader(vertexShader, vertexShader->baseHash, vertexShader->auxHash);
return vertexShader;
@ -874,12 +899,14 @@ LatteDecompilerShader* LatteShader_CompileSeparableGeometryShader(uint64 baseHas
LatteShader_CreateRendererShader(geometryShader, false);
performanceMonitor.numCompiledGS++;
#ifdef ENABLE_OPENGL
if (g_renderer->GetType() == RendererAPI::OpenGL)
{
if (geometryShader->shader)
geometryShader->shader->PreponeCompilation(true);
LatteShader_FinishCompilation(geometryShader);
}
#endif
LatteSHRC_RegisterShader(geometryShader, geometryShader->baseHash, geometryShader->auxHash);
return geometryShader;
@ -903,12 +930,14 @@ LatteDecompilerShader* LatteShader_CompileSeparablePixelShader(uint64 baseHash,
LatteShaderCache_writeSeparablePixelShader(_shaderBaseHash_ps, psAuxHash, pixelShaderPtr, pixelShaderSize, LatteGPUState.contextRegister, usesGeometryShader);
}
#ifdef ENABLE_OPENGL
if (g_renderer->GetType() == RendererAPI::OpenGL)
{
if (pixelShader->shader)
pixelShader->shader->PreponeCompilation(true);
LatteShader_FinishCompilation(pixelShader);
}
#endif
LatteSHRC_RegisterShader(pixelShader, _shaderBaseHash_ps, psAuxHash);
return pixelShader;

View File

@ -9,13 +9,17 @@
#include "WindowSystem.h"
#include "Cafe/HW/Latte/Renderer/Renderer.h"
#ifdef ENABLE_OPENGL
#include "Cafe/HW/Latte/Renderer/OpenGL/RendererShaderGL.h"
#endif
#ifdef ENABLE_VULKAN
#include "Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.h"
#if ENABLE_METAL
#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineStableCache.h"
#endif
#ifdef ENABLE_METAL
#include "Cafe/HW/Latte/Renderer/Metal/RendererShaderMtl.h"
#include "Cafe/HW/Latte/Renderer/Metal/MetalPipelineCache.h"
#endif
#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineStableCache.h"
#include <imgui.h>
#include "imgui/imgui_extension.h"
@ -273,14 +277,24 @@ static BootSoundPlayer g_bootSndPlayer;
void LatteShaderCache_finish()
{
if (g_renderer->GetType() == RendererAPI::Vulkan)
switch (g_renderer->GetType())
{
#ifdef ENABLE_VULKAN
case RendererAPI::Vulkan:
RendererShaderVk::ShaderCacheLoading_end();
else if (g_renderer->GetType() == RendererAPI::OpenGL)
RendererShaderGL::ShaderCacheLoading_end();
#if ENABLE_METAL
else if (g_renderer->GetType() == RendererAPI::Metal)
RendererShaderMtl::ShaderCacheLoading_end();
return;
#endif
#ifdef ENABLE_OPENGL
case RendererAPI::OpenGL:
RendererShaderGL::ShaderCacheLoading_end();
return;
#endif
#ifdef ENABLE_METAL
case RendererAPI::Metal:
RendererShaderMtl::ShaderCacheLoading_end();
return;
#endif
}
}
uint32 LatteShaderCache_getShaderCacheExtraVersion(uint64 titleId)
@ -359,21 +373,36 @@ void LatteShaderCache_Load()
fs::create_directories(ActiveSettings::GetCachePath("shaderCache/transferable"), ec);
fs::create_directories(ActiveSettings::GetCachePath("shaderCache/precompiled"), ec);
// initialize renderer specific caches
if (g_renderer->GetType() == RendererAPI::Vulkan)
switch(g_renderer->GetType())
{
#ifdef ENABLE_VULKAN
case RendererAPI::Vulkan:
RendererShaderVk::ShaderCacheLoading_begin(cacheTitleId);
else if (g_renderer->GetType() == RendererAPI::OpenGL)
RendererShaderGL::ShaderCacheLoading_begin(cacheTitleId);
#if ENABLE_METAL
else if (g_renderer->GetType() == RendererAPI::Metal)
RendererShaderMtl::ShaderCacheLoading_begin(cacheTitleId);
break;
#endif
#ifdef ENABLE_OPENGL
case RendererAPI::OpenGL:
RendererShaderGL::ShaderCacheLoading_begin(cacheTitleId);
break;
#endif
#ifdef ENABLE_METAL
case RendererAPI::Metal:
RendererShaderMtl::ShaderCacheLoading_begin(cacheTitleId);
break;
#endif
}
// get cache file name
fs::path pathGeneric;
if (g_renderer->GetType() == RendererAPI::Metal)
switch(g_renderer->GetType())
{
case RendererAPI::Metal:
pathGeneric = ActiveSettings::GetCachePath("shaderCache/transferable/{:016x}_mtlshaders.bin", cacheTitleId);
else
break;
default:
pathGeneric = ActiveSettings::GetCachePath("shaderCache/transferable/{:016x}_shaders.bin", cacheTitleId);
break;
}
// calculate extraVersion for transferable and precompiled shader cache
uint32 transferableExtraVersion = SHADER_CACHE_GENERIC_EXTRA_VERSION;
@ -471,8 +500,10 @@ void LatteShaderCache_Load()
#endif
LatteShaderCache_finish();
// if Vulkan or Metal then also load pipeline cache
#if defined(ENABLE_VULKAN) || defined(ENABLE_METAL)
if (g_renderer->GetType() == RendererAPI::Vulkan || g_renderer->GetType() == RendererAPI::Metal)
LatteShaderCache_LoadPipelineCache(cacheTitleId);
#endif
g_renderer->BeginFrame(true);
@ -634,31 +665,52 @@ void LatteShaderCache_ShowProgress(const std::function <bool(void)>& loadUpdateF
void LatteShaderCache_LoadPipelineCache(uint64 cacheTitleId)
{
if (g_renderer->GetType() == RendererAPI::Vulkan)
switch(g_renderer->GetType())
{
#ifdef ENABLE_VULKAN
case RendererAPI::Vulkan:
g_shaderCacheLoaderState.pipelineFileCount = VulkanPipelineStableCache::GetInstance().BeginLoading(cacheTitleId);
#if ENABLE_METAL
else if (g_renderer->GetType() == RendererAPI::Metal)
break;
#endif
#ifdef ENABLE_METAL
case RendererAPI::Metal:
g_shaderCacheLoaderState.pipelineFileCount = MetalPipelineCache::GetInstance().BeginLoading(cacheTitleId);
break;
#endif
}
g_shaderCacheLoaderState.loadedPipelines = 0;
LatteShaderCache_ShowProgress(LatteShaderCache_updatePipelineLoadingProgress, true);
if (g_renderer->GetType() == RendererAPI::Vulkan)
switch(g_renderer->GetType())
{
#ifdef ENABLE_VULKAN
case RendererAPI::Vulkan:
VulkanPipelineStableCache::GetInstance().EndLoading();
#if ENABLE_METAL
else if (g_renderer->GetType() == RendererAPI::Metal)
MetalPipelineCache::GetInstance().EndLoading();
break;
#endif
#ifdef ENABLE_METAL
case RendererAPI::Metal:
MetalPipelineCache::GetInstance().EndLoading();
break;
#endif
}
}
bool LatteShaderCache_updatePipelineLoadingProgress()
{
uint32 pipelinesMissingShaders = 0;
if (g_renderer->GetType() == RendererAPI::Vulkan)
switch(g_renderer->GetType())
{
#ifdef ENABLE_VULKAN
case RendererAPI::Vulkan:
return VulkanPipelineStableCache::GetInstance().UpdateLoading(g_shaderCacheLoaderState.loadedPipelines, pipelinesMissingShaders);
#if ENABLE_METAL
else if (g_renderer->GetType() == RendererAPI::Metal)
#endif
#ifdef ENABLE_METAL
case RendererAPI::Metal:
return MetalPipelineCache::GetInstance().UpdateLoading(g_shaderCacheLoaderState.loadedPipelines, pipelinesMissingShaders);
#endif
}
return false;
}
@ -918,20 +970,37 @@ void LatteShaderCache_Close()
delete s_shaderCacheGeneric;
s_shaderCacheGeneric = nullptr;
}
if (g_renderer->GetType() == RendererAPI::Vulkan)
switch(g_renderer->GetType())
{
#ifdef ENABLE_VULKAN
case RendererAPI::Vulkan:
RendererShaderVk::ShaderCacheLoading_Close();
else if (g_renderer->GetType() == RendererAPI::OpenGL)
RendererShaderGL::ShaderCacheLoading_Close();
#if ENABLE_METAL
else if (g_renderer->GetType() == RendererAPI::Metal)
RendererShaderMtl::ShaderCacheLoading_Close();
break;
#endif
#ifdef ENABLE_OPENGL
case RendererAPI::OpenGL:
RendererShaderGL::ShaderCacheLoading_Close();
break;
#endif
#ifdef ENABLE_METAL
case RendererAPI::Metal:
RendererShaderMtl::ShaderCacheLoading_Close();
break;
#endif
}
// if Vulkan or Metal then also close pipeline cache
if (g_renderer->GetType() == RendererAPI::Vulkan)
VulkanPipelineStableCache::GetInstance().Close();
#if ENABLE_METAL
else if (g_renderer->GetType() == RendererAPI::Metal)
MetalPipelineCache::GetInstance().Close();
switch(g_renderer->GetType())
{
#ifdef ENABLE_VULKAN
case RendererAPI::Vulkan:
VulkanPipelineStableCache::GetInstance().Close();
break;
#endif
#ifdef ENABLE_METAL
case RendererAPI::Metal:
MetalPipelineCache::GetInstance().Close();
break;
#endif
}
}

View File

@ -1,7 +1,6 @@
#include "Cafe/HW/Latte/Core/Latte.h"
#include "Cafe/HW/Latte/Core/LatteDraw.h"
#include "Cafe/HW/Latte/Core/LatteShader.h"
#include "Cafe/HW/Latte/Core/LatteDefaultShaders.h"
#include "Cafe/HW/Latte/Core/LatteTexture.h"
#include "Cafe/HW/Latte/Core/LatteSurfaceCopy.h"

View File

@ -567,6 +567,8 @@ bool __LatteTexture_IsBlockedFormatRelation(LatteTexture* texture1, LatteTexture
if (texture1->format == Latte::E_GX2SURFFMT::D32_FLOAT && Latte::GetHWFormat(texture2->format) == Latte::E_HWSURFFMT::HWFMT_8_8_8_8)
return true;
}
#ifdef ENABLE_VULKAN
// Vulkan has stricter rules
if (g_renderer->GetType() == RendererAPI::Vulkan)
{
@ -574,6 +576,7 @@ bool __LatteTexture_IsBlockedFormatRelation(LatteTexture* texture1, LatteTexture
if (texture1->format == Latte::E_GX2SURFFMT::D32_FLOAT && Latte::GetHWFormat(texture2->format) == Latte::E_HWSURFFMT::HWFMT_8_24)
return true;
}
#endif
return false;
}

View File

@ -4,9 +4,11 @@
#include "Cafe/HW/Latte/Renderer/Renderer.h"
#ifdef ENABLE_OPENGL
#include "Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h"
#include "Cafe/HW/Latte/Renderer/OpenGL/LatteTextureGL.h"
#include "Cafe/HW/Latte/Renderer/OpenGL/LatteTextureViewGL.h"
#endif
struct TexScaleXY
{
@ -192,6 +194,7 @@ void LatteTexture_updateTexturesForStage(LatteDecompilerShader* shaderContext, u
LatteGPUState.repeatTextureInitialization = true;
}
#ifdef ENABLE_OPENGL
if (g_renderer->GetType() == RendererAPI::OpenGL)
{
// on OpenGL, texture views and sampler parameters are tied together (we are avoiding sampler objects due to driver bugs)
@ -214,6 +217,8 @@ void LatteTexture_updateTexturesForStage(LatteDecompilerShader* shaderContext, u
textureView->lastTextureBindIndex = LatteGPUState.textureBindCounter;
rendererGL->renderstate_updateTextureSettingsGL(shaderContext, textureView, textureIndex + glBackendBaseTexUnit, word4, textureIndex, isDepthSampler);
}
#endif
g_renderer->texture_setLatteTexture(textureView, textureIndex + glBackendBaseTexUnit);
// update if data changed
bool swizzleChanged = false;

View File

@ -1,12 +1,7 @@
#include "Cafe/HW/Latte/Core/Latte.h"
#include "Cafe/HW/Latte/Core/LatteDraw.h"
#include "Cafe/HW/Latte/Core/LattePerformanceMonitor.h"
#include "Common/GLInclude/GLInclude.h"
#include "Cafe/HW/Latte/Renderer/Renderer.h"
#include "Cafe/HW/Latte/Core/LatteTexture.h"
#include "Cafe/HW/Latte/Renderer/OpenGL/LatteTextureViewGL.h"
#define LOG_READBACK_TIME

View File

@ -1,6 +1,8 @@
#include "Cafe/HW/Latte/Core/Latte.h"
#include "Cafe/OS/libs/gx2/GX2_Event.h"
#ifdef ENABLE_VULKAN
#include "Cafe/HW/Latte/Renderer/Vulkan/VsyncDriver.h"
#endif
#include "util/highresolutiontimer/HighResolutionTimer.h"
#include "config/CemuConfig.h"
#include "Cafe/CafeSystem.h"
@ -55,8 +57,10 @@ void LatteTiming_EnableHostDrivenVSync()
{
if (s_usingHostDrivenVSync)
return;
#ifdef ENABLE_VULKAN
VsyncDriver_startThread(LatteTiming_NotifyHostVSync);
s_usingHostDrivenVSync = true;
#endif
}
bool LatteTiming_IsUsingHostDrivenVSync()

View File

@ -10,7 +10,9 @@
#include "Cafe/HW/Latte/Core/FetchShader.h"
#include "Cafe/HW/Latte/Core/LattePerformanceMonitor.h"
#include "Cafe/HW/Latte/Renderer/Renderer.h"
#ifdef ENABLE_VULKAN
#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h"
#endif
#include "util/helpers/helpers.h"
// parse instruction and if valid append it to instructionList
@ -1069,12 +1071,18 @@ void _LatteDecompiler_Process(LatteDecompilerShaderContext* shaderContext, uint8
// emit code
if (shaderContext->shader->hasError == false)
{
if (g_renderer->GetType() == RendererAPI::OpenGL || g_renderer->GetType() == RendererAPI::Vulkan)
LatteDecompiler_emitGLSLShader(shaderContext, shaderContext->shader);
#if ENABLE_METAL
else
LatteDecompiler_emitMSLShader(shaderContext, shaderContext->shader);
if (g_renderer->GetType() == RendererAPI::OpenGL || g_renderer->GetType() == RendererAPI::Vulkan)
{
#if defined(ENABLE_OPENGL) || defined(ENABLE_VULKAN)
LatteDecompiler_emitGLSLShader(shaderContext, shaderContext->shader);
#endif
}
if (g_renderer->GetType() == RendererAPI::Metal)
{
#ifdef ENABLE_METAL
LatteDecompiler_emitMSLShader(shaderContext, shaderContext->shader);
#endif
}
}
LatteDecompiler_cleanup(shaderContext);
// fast access

View File

@ -10,7 +10,7 @@
#include "Cafe/HW/Latte/Renderer/Renderer.h"
#include "Common/MemPtr.h"
#include "HW/Latte/ISA/LatteReg.h"
#if ENABLE_METAL
#ifdef ENABLE_METAL
#include "HW/Latte/Renderer/Metal/MetalCommon.h"
#endif
@ -403,11 +403,9 @@ void LatteDecompiler_analyzeExport(LatteDecompilerShaderContext* shaderContext,
}
else if (cfInstruction->exportType == 0 && cfInstruction->exportArrayBase == 61)
{
#if ENABLE_METAL
// Only check for depth buffer mask on Metal, as its not in the PS hash on other backends
if (g_renderer->GetType() != RendererAPI::Metal || LatteMRT::GetActiveDepthBufferMask(*shaderContext->contextRegistersNew))
shader->depthMask = true;
#endif
}
else
debugBreakpoint();
@ -512,7 +510,7 @@ namespace LatteDecompiler
}
}
#if ENABLE_METAL
#ifdef ENABLE_METAL
void _initTextureBindingPointsMTL(LatteDecompilerShaderContext* decompilerContext)
{
// for Vulkan we use consecutive indices
@ -563,7 +561,7 @@ namespace LatteDecompiler
{
decompilerContext->hasUniformVarBlock = true; // uf_verticesPerInstance and uf_streamoutBufferBase*
}
#if ENABLE_METAL
#ifdef ENABLE_METAL
if (g_renderer->GetType() == RendererAPI::Metal)
{
bool usesGeometryShader = UseGeometryShader(*decompilerContext->contextRegistersNew, decompilerContext->options->usesGeometryShader);
@ -1113,7 +1111,7 @@ void LatteDecompiler_analyze(LatteDecompilerShaderContext* shaderContext, LatteD
shaderContext->output->resourceMappingVK.setIndex = 2;
LatteDecompiler::_initTextureBindingPointsGL(shaderContext);
LatteDecompiler::_initTextureBindingPointsVK(shaderContext);
#if ENABLE_METAL
#ifdef ENABLE_METAL
LatteDecompiler::_initTextureBindingPointsMTL(shaderContext);
#endif
LatteDecompiler::_initUniformBindingPoints(shaderContext);

View File

@ -266,7 +266,7 @@ struct LatteDecompilerShaderContext
void LatteDecompiler_analyze(LatteDecompilerShaderContext* shaderContext, LatteDecompilerShader* shader);
void LatteDecompiler_analyzeDataTypes(LatteDecompilerShaderContext* shaderContext);
void LatteDecompiler_emitGLSLShader(LatteDecompilerShaderContext* shaderContext, LatteDecompilerShader* shader);
#if ENABLE_METAL
#ifdef ENABLE_METAL
void LatteDecompiler_emitMSLShader(LatteDecompilerShaderContext* shaderContext, LatteDecompilerShader* shader);
#endif

View File

@ -33,7 +33,7 @@ extern bool hasValidFramebufferAttached;
float supportBufferData[512 * 4];
// Defined in the OpenGL renderer
// Defined in the Common renderer
void LatteDraw_handleSpecialState8_clearAsDepth();
std::vector<MetalRenderer::DeviceInfo> MetalRenderer::GetDevices()

View File

@ -17,6 +17,7 @@
#include "Cafe/OS/libs/gx2/GX2.h"
#include "Cafe/GameProfile/GameProfile.h"
#include "HW/Latte/Renderer/RendererCore.h"
#include "config/ActiveSettings.h"
@ -52,7 +53,7 @@ struct
uint32 maxIndex;
uint32 minIndex;
uint8* indexData;
// buffer
// buffer
GLuint glIndexCacheBuffer;
VirtualBufferHeap_t* indexBufferVirtualHeap;
uint8* mappedIndexBuffer;
@ -371,6 +372,8 @@ void _decodeAndUploadIndexData(indexDataCacheEntry2_t* cacheEntry)
void LatteDraw_cleanupAfterFrame()
{
if (g_renderer->GetType() != RendererAPI::OpenGL)
return;
// drop everything from cache that is older than 30 frames
uint32 frameCounter = LatteGPUState.frameCounter;
while (indexDataCacheFirst)
@ -524,70 +527,6 @@ void LatteDrawGL_prepareIndicesWithGPUCache(MPTR indexDataMPTR, _INDEX_TYPE inde
indexState.indexData = (uint8*)(size_t)cacheEntry->heapEntry->startOffset;
}
void LatteDraw_handleSpecialState8_clearAsDepth()
{
if (LatteGPUState.contextNew.GetSpecialStateValues()[0] == 0)
cemuLog_logDebug(LogType::Force, "Special state 8 requires special state 0 but it is not set?");
// get depth buffer information
uint32 regDepthBuffer = LatteGPUState.contextRegister[mmDB_HTILE_DATA_BASE];
uint32 regDepthSize = LatteGPUState.contextRegister[mmDB_DEPTH_SIZE];
uint32 regDepthBufferInfo = LatteGPUState.contextRegister[mmDB_DEPTH_INFO];
// get format and tileMode from info reg
uint32 depthBufferTileMode = (regDepthBufferInfo >> 15) & 0xF;
MPTR depthBufferPhysMem = regDepthBuffer << 8;
uint32 depthBufferPitch = (((regDepthSize >> 0) & 0x3FF) + 1);
uint32 depthBufferHeight = ((((regDepthSize >> 10) & 0xFFFFF) + 1) / depthBufferPitch);
depthBufferPitch <<= 3;
depthBufferHeight <<= 3;
uint32 depthBufferWidth = depthBufferPitch;
sint32 sliceIndex = 0; // todo
sint32 mipIndex = 0;
// clear all color buffers that match the format of the depth buffer
sint32 searchIndex = 0;
bool targetFound = false;
while (true)
{
LatteTextureView* view = LatteTC_LookupTextureByData(depthBufferPhysMem, depthBufferWidth, depthBufferHeight, depthBufferPitch, 0, 1, sliceIndex, 1, &searchIndex);
if (!view)
{
// should we clear in RAM instead?
break;
}
sint32 effectiveClearWidth = view->baseTexture->width;
sint32 effectiveClearHeight = view->baseTexture->height;
LatteTexture_scaleToEffectiveSize(view->baseTexture, &effectiveClearWidth, &effectiveClearHeight, 0);
// hacky way to get clear color
float* regClearColor = (float*)(LatteGPUState.contextRegister + 0xC000 + 0); // REG_BASE_ALU_CONST
uint8 clearColor[4] = { 0 };
clearColor[0] = (uint8)(regClearColor[0] * 255.0f);
clearColor[1] = (uint8)(regClearColor[1] * 255.0f);
clearColor[2] = (uint8)(regClearColor[2] * 255.0f);
clearColor[3] = (uint8)(regClearColor[3] * 255.0f);
// todo - use fragment shader software emulation (evoke for one pixel) to determine clear color
// todo - dont clear entire slice, use effectiveClearWidth, effectiveClearHeight
if (g_renderer->GetType() == RendererAPI::OpenGL)
{
//cemu_assert_debug(false); // implement g_renderer->texture_clearColorSlice properly for OpenGL renderer
if (glClearTexSubImage)
glClearTexSubImage(((LatteTextureViewGL*)view)->glTexId, mipIndex, 0, 0, 0, effectiveClearWidth, effectiveClearHeight, 1, GL_RGBA, GL_UNSIGNED_BYTE, clearColor);
}
else
{
if (view->baseTexture->isDepth)
g_renderer->texture_clearDepthSlice(view->baseTexture, sliceIndex + view->firstSlice, mipIndex + view->firstMip, true, view->baseTexture->hasStencil, 0.0f, 0);
else
g_renderer->texture_clearColorSlice(view->baseTexture, sliceIndex + view->firstSlice, mipIndex + view->firstMip, clearColor[0], clearColor[1], clearColor[2], clearColor[3]);
}
}
}
void LatteDrawGL_doDraw(_INDEX_TYPE indexType, uint32 baseVertex, uint32 baseInstance, uint32 instanceCount, uint32 count)
{
if (indexType == _INDEX_TYPE::U16_BE)
@ -755,10 +694,6 @@ void OpenGLRenderer::_setupVertexAttributes()
}
}
void rectsEmulationGS_outputSingleVertex(std::string& gsSrc, LatteDecompilerShader* vertexShader, LatteShaderPSInputTable* psInputTable, sint32 vIdx);
void rectsEmulationGS_outputGeneratedVertex(std::string& gsSrc, LatteDecompilerShader* vertexShader, LatteShaderPSInputTable* psInputTable, const char* variant);
void rectsEmulationGS_outputVerticesCode(std::string& gsSrc, LatteDecompilerShader* vertexShader, LatteShaderPSInputTable* psInputTable, sint32 p0, sint32 p1, sint32 p2, sint32 p3, const char* variant, const LatteContextRegister& latteRegister);
std::map<uint64, RendererShaderGL*> g_mapGLRectEmulationGS;
RendererShaderGL* rectsEmulationGS_generateShaderGL(LatteDecompilerShader* vertexShader)

View File

@ -1,6 +1,7 @@
#include "Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h"
#include "Cafe/HW/Latte/Renderer/OpenGL/RendererShaderGL.h"
#include "Cafe/HW/Latte/Renderer/OpenGL/CachedFBOGL.h"
#include "Cafe/HW/Latte/Renderer/OpenGL/OpenGLSurfaceCopy.h"
#include "Cafe/HW/Latte/Renderer/OpenGL/LatteTextureGL.h"
#include "Cafe/HW/Latte/Renderer/OpenGL/LatteTextureViewGL.h"
@ -8,7 +9,7 @@
#include "Cafe/HW/Latte/Core/LatteShader.h"
#include "Cafe/HW/Latte/Core/LatteDraw.h"
#include "Cafe/HW/Latte/Core/LatteDefaultShaders.h"
#include "util/helpers/StringBuf.h"
void LatteDraw_resetAttributePointerCache();
@ -66,9 +67,9 @@ void OpenGLRenderer::surfaceCopy_copySurfaceWithFormatConversion(LatteTexture* s
if (destinationTexture->isDepth)
renderstate_setAlwaysWriteDepth();
// bind format specific copy shader
LatteDefaultShader_t* copyShader = LatteDefaultShader_getPixelCopyShader_depthToColor();
LatteGLDefaultShader_t* copyShader = LatteGLDefaultShader_getPixelCopyShader_depthToColor();
if (destinationTexture->isDepth)
copyShader = LatteDefaultShader_getPixelCopyShader_colorToDepth();
copyShader = LatteGLDefaultShader_getPixelCopyShader_colorToDepth();
glUseProgram(copyShader->glProgamId);
catchOpenGLError();
// setup uniforms
@ -113,4 +114,86 @@ void OpenGLRenderer::surfaceCopy_copySurfaceWithFormatConversion(LatteTexture* s
LatteGPUState.repeatTextureInitialization = true;
glUseProgram(0);
}
LatteGLDefaultShader_t* _copyShader_depthToColor;
LatteGLDefaultShader_t* _copyShader_colorToDepth;
void LatteGLDefaultShader_pixelCopyShader_generateVSBody(StringBuf* vs)
{
vs->add("#version 420\r\n");
vs->add("out vec2 passUV;\r\n");
vs->add("uniform vec4 uf_vertexOffsets[4];\r\n");
vs->add("\r\n");
vs->add("void main(){\r\n");
vs->add("int vID = gl_VertexID;\r\n");
vs->add("passUV = uf_vertexOffsets[vID].zw;\r\n");
vs->add("gl_Position = vec4(uf_vertexOffsets[vID].xy, 0.0, 1.0);\r\n");
vs->add("}\r\n");
}
GLuint gxShaderDepr_compileRaw(StringBuf* strSourceVS, StringBuf* strSourceFS);
GLuint gxShaderDepr_compileRaw(const std::string& vertex_source, const std::string& fragment_source);
LatteGLDefaultShader_t* LatteGLDefaultShader_getPixelCopyShader_depthToColor()
{
if (_copyShader_depthToColor != 0)
return _copyShader_depthToColor;
catchOpenGLError();
LatteGLDefaultShader_t* defaultShader = (LatteGLDefaultShader_t*)malloc(sizeof(LatteGLDefaultShader_t));
memset(defaultShader, 0, sizeof(LatteGLDefaultShader_t));
StringBuf fCStr_vertexShader(1024 * 16);
LatteGLDefaultShader_pixelCopyShader_generateVSBody(&fCStr_vertexShader);
StringBuf fCStr_defaultFragShader(1024 * 16);
fCStr_defaultFragShader.add("#version 420\r\n");
fCStr_defaultFragShader.add("in vec2 passUV;\r\n");
fCStr_defaultFragShader.add("uniform sampler2D textureSrc;\r\n");
fCStr_defaultFragShader.add("layout(location = 0) out vec4 colorOut0;\r\n");
fCStr_defaultFragShader.add("\r\n");
fCStr_defaultFragShader.add("void main(){\r\n");
fCStr_defaultFragShader.add("colorOut0 = vec4(texture(textureSrc, passUV).r,0.0,0.0,1.0);\r\n");
fCStr_defaultFragShader.add("}\r\n");
defaultShader->glProgamId = gxShaderDepr_compileRaw(&fCStr_vertexShader, &fCStr_defaultFragShader);
catchOpenGLError();
defaultShader->copyShaderUniforms.uniformLoc_textureSrc = glGetUniformLocation(defaultShader->glProgamId, "textureSrc");
defaultShader->copyShaderUniforms.uniformLoc_vertexOffsets = glGetUniformLocation(defaultShader->glProgamId, "uf_vertexOffsets");
_copyShader_depthToColor = defaultShader;
catchOpenGLError();
return defaultShader;
}
LatteGLDefaultShader_t* LatteGLDefaultShader_getPixelCopyShader_colorToDepth()
{
if (_copyShader_colorToDepth != 0)
return _copyShader_colorToDepth;
catchOpenGLError();
LatteGLDefaultShader_t* defaultShader = (LatteGLDefaultShader_t*)malloc(sizeof(LatteGLDefaultShader_t));
memset(defaultShader, 0, sizeof(LatteGLDefaultShader_t));
StringBuf fCStr_vertexShader(1024 * 16);
LatteGLDefaultShader_pixelCopyShader_generateVSBody(&fCStr_vertexShader);
StringBuf fCStr_defaultFragShader(1024 * 16);
fCStr_defaultFragShader.add("#version 420\r\n");
fCStr_defaultFragShader.add("in vec2 passUV;\r\n");
fCStr_defaultFragShader.add("uniform sampler2D textureSrc;\r\n");
fCStr_defaultFragShader.add("layout(location = 0) out vec4 colorOut0;\r\n");
fCStr_defaultFragShader.add("\r\n");
fCStr_defaultFragShader.add("void main(){\r\n");
fCStr_defaultFragShader.add("gl_FragDepth = texture(textureSrc, passUV).r;\r\n");
fCStr_defaultFragShader.add("}\r\n");
defaultShader->glProgamId = gxShaderDepr_compileRaw(&fCStr_vertexShader, &fCStr_defaultFragShader);
defaultShader->copyShaderUniforms.uniformLoc_textureSrc = glGetUniformLocation(defaultShader->glProgamId, "textureSrc");
defaultShader->copyShaderUniforms.uniformLoc_vertexOffsets = glGetUniformLocation(defaultShader->glProgamId, "uf_vertexOffsets");
_copyShader_colorToDepth = defaultShader;
catchOpenGLError();
return defaultShader;
}

View File

@ -0,0 +1,14 @@
#pragma once
typedef struct
{
GLuint glProgamId;
struct
{
GLuint uniformLoc_textureSrc;
GLuint uniformLoc_vertexOffsets;
}copyShaderUniforms;
}LatteGLDefaultShader_t;
LatteGLDefaultShader_t* LatteGLDefaultShader_getPixelCopyShader_depthToColor();
LatteGLDefaultShader_t* LatteGLDefaultShader_getPixelCopyShader_colorToDepth();

View File

@ -0,0 +1,132 @@
#include "RendererCore.h"
#include "Cafe/HW/Latte/Renderer/Renderer.h"
#include "Cafe/HW/Latte/ISA/RegDefines.h"
#include "HW/Latte/Core/LatteShader.h"
#include "config/CemuConfig.h"
#ifdef ENABLE_OPENGL
#include "Common/GLInclude/GLInclude.h"
#include "Cafe/HW/Latte/Renderer/OpenGL/LatteTextureViewGL.h"
#endif
void LatteDraw_handleSpecialState8_clearAsDepth()
{
if (LatteGPUState.contextNew.GetSpecialStateValues()[0] == 0)
cemuLog_logDebug(LogType::Force, "Special state 8 requires special state 0 but it is not set?");
// get depth buffer information
uint32 regDepthBuffer = LatteGPUState.contextRegister[mmDB_HTILE_DATA_BASE];
uint32 regDepthSize = LatteGPUState.contextRegister[mmDB_DEPTH_SIZE];
uint32 regDepthBufferInfo = LatteGPUState.contextRegister[mmDB_DEPTH_INFO];
// get format and tileMode from info reg
uint32 depthBufferTileMode = (regDepthBufferInfo >> 15) & 0xF;
MPTR depthBufferPhysMem = regDepthBuffer << 8;
uint32 depthBufferPitch = (((regDepthSize >> 0) & 0x3FF) + 1);
uint32 depthBufferHeight = ((((regDepthSize >> 10) & 0xFFFFF) + 1) / depthBufferPitch);
depthBufferPitch <<= 3;
depthBufferHeight <<= 3;
uint32 depthBufferWidth = depthBufferPitch;
sint32 sliceIndex = 0; // todo
sint32 mipIndex = 0;
// clear all color buffers that match the format of the depth buffer
sint32 searchIndex = 0;
bool targetFound = false;
while (true)
{
LatteTextureView* view = LatteTC_LookupTextureByData(depthBufferPhysMem, depthBufferWidth, depthBufferHeight, depthBufferPitch, 0, 1, sliceIndex, 1, &searchIndex);
if (!view)
{
// should we clear in RAM instead?
break;
}
sint32 effectiveClearWidth = view->baseTexture->width;
sint32 effectiveClearHeight = view->baseTexture->height;
LatteTexture_scaleToEffectiveSize(view->baseTexture, &effectiveClearWidth, &effectiveClearHeight, 0);
// hacky way to get clear color
float* regClearColor = (float*)(LatteGPUState.contextRegister + 0xC000 + 0); // REG_BASE_ALU_CONST
uint8 clearColor[4] = { 0 };
clearColor[0] = (uint8)(regClearColor[0] * 255.0f);
clearColor[1] = (uint8)(regClearColor[1] * 255.0f);
clearColor[2] = (uint8)(regClearColor[2] * 255.0f);
clearColor[3] = (uint8)(regClearColor[3] * 255.0f);
// todo - use fragment shader software emulation (evoke for one pixel) to determine clear color
// todo - dont clear entire slice, use effectiveClearWidth, effectiveClearHeight
switch (g_renderer->GetType())
{
#ifdef ENABLE_OPENGL
case RendererAPI::OpenGL:
{
//cemu_assert_debug(false); // implement g_renderer->texture_clearColorSlice properly for OpenGL renderer
if (glClearTexSubImage)
glClearTexSubImage(((LatteTextureViewGL*)view)->glTexId, mipIndex, 0, 0, 0, effectiveClearWidth, effectiveClearHeight, 1, GL_RGBA, GL_UNSIGNED_BYTE, clearColor);
break;
}
#endif
default:
{
if (view->baseTexture->isDepth)
g_renderer->texture_clearDepthSlice(view->baseTexture, sliceIndex + view->firstSlice, mipIndex + view->firstMip, true, view->baseTexture->hasStencil, 0.0f, 0);
else
g_renderer->texture_clearColorSlice(view->baseTexture, sliceIndex + view->firstSlice, mipIndex + view->firstMip, clearColor[0], clearColor[1], clearColor[2], clearColor[3]);
}
}
}
}
/* rects emulation */
void rectsEmulationGS_outputSingleVertex(std::string& gsSrc, LatteDecompilerShader* vertexShader, LatteShaderPSInputTable* psInputTable, sint32 vIdx, const LatteContextRegister& latteRegister)
{
auto parameterMask = vertexShader->outputParameterMask;
for (uint32 i = 0; i < 32; i++)
{
if ((parameterMask & (1 << i)) == 0)
continue;
sint32 vsSemanticId = psInputTable->getVertexShaderOutParamSemanticId(latteRegister.GetRawView(), i);
if (vsSemanticId < 0)
continue;
// make sure PS has matching input
if (!psInputTable->hasPSImportForSemanticId(vsSemanticId))
continue;
gsSrc.append(fmt::format("passParameterSem{}Out = passParameterSem{}In[{}];\r\n", vsSemanticId, vsSemanticId, vIdx));
}
gsSrc.append(fmt::format("gl_Position = gl_in[{}].gl_Position;\r\n", vIdx));
gsSrc.append("EmitVertex();\r\n");
}
void rectsEmulationGS_outputGeneratedVertex(std::string& gsSrc, LatteDecompilerShader* vertexShader, LatteShaderPSInputTable* psInputTable, const char* variant, const LatteContextRegister& latteRegister)
{
auto parameterMask = vertexShader->outputParameterMask;
for (uint32 i = 0; i < 32; i++)
{
if ((parameterMask & (1 << i)) == 0)
continue;
sint32 vsSemanticId = psInputTable->getVertexShaderOutParamSemanticId(latteRegister.GetRawView(), i);
if (vsSemanticId < 0)
continue;
// make sure PS has matching input
if (!psInputTable->hasPSImportForSemanticId(vsSemanticId))
continue;
gsSrc.append(fmt::format("passParameterSem{}Out = gen4thVertex{}(passParameterSem{}In[0], passParameterSem{}In[1], passParameterSem{}In[2]);\r\n", vsSemanticId, variant, vsSemanticId, vsSemanticId, vsSemanticId));
}
gsSrc.append(fmt::format("gl_Position = gen4thVertex{}(gl_in[0].gl_Position, gl_in[1].gl_Position, gl_in[2].gl_Position);\r\n", variant));
gsSrc.append("EmitVertex();\r\n");
}
void rectsEmulationGS_outputVerticesCode(std::string& gsSrc, LatteDecompilerShader* vertexShader, LatteShaderPSInputTable* psInputTable, sint32 p0, sint32 p1, sint32 p2, sint32 p3, const char* variant, const LatteContextRegister& latteRegister)
{
sint32 pList[4] = { p0, p1, p2, p3 };
for (sint32 i = 0; i < 4; i++)
{
if (pList[i] == 3)
rectsEmulationGS_outputGeneratedVertex(gsSrc, vertexShader, psInputTable, variant, latteRegister);
else
rectsEmulationGS_outputSingleVertex(gsSrc, vertexShader, psInputTable, pList[i], latteRegister);
}
}

View File

@ -0,0 +1,11 @@
#pragma once
#include "Cafe/HW/Latte/Core/LatteRingBuffer.h"
#include "Cafe/HW/Latte/Core/Latte.h"
void LatteDraw_handleSpecialState8_clearAsDepth();
class LatteShaderPSInputTable;
void rectsEmulationGS_outputSingleVertex(std::string& gsSrc, LatteDecompilerShader* vertexShader, LatteShaderPSInputTable* psInputTable, sint32 vIdx);
void rectsEmulationGS_outputGeneratedVertex(std::string& gsSrc, LatteDecompilerShader* vertexShader, LatteShaderPSInputTable* psInputTable, const char* variant);
void rectsEmulationGS_outputVerticesCode(std::string& gsSrc, LatteDecompilerShader* vertexShader, LatteShaderPSInputTable* psInputTable, sint32 p0, sint32 p1, sint32 p2, sint32 p3, const char* variant, const LatteContextRegister& latteRegister);

View File

@ -1,5 +1,6 @@
#include "Cafe/HW/Latte/Renderer/RendererOuputShader.h"
#include "Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h"
#include "Cafe/HW/Latte/Renderer/Renderer.h"
#include "Cafe/HW/Latte/Core/Latte.h"
#include "config/ActiveSettings.h"
const std::string RendererOutputShader::s_copy_shader_source =
@ -246,10 +247,17 @@ fragment float4 main0(VertexOut in [[stage_in]], texture2d<float> textureSrc [[t
RendererOutputShader::RendererOutputShader(const std::string& vertex_source, const std::string& fragment_source)
{
std::string finalFragmentSrc;
if (g_renderer->GetType() == RendererAPI::Metal)
finalFragmentSrc = fragment_source;
else
finalFragmentSrc = PrependFragmentPreamble(fragment_source);
switch(g_renderer->GetType())
{
#ifdef ENABLE_METAL
case RendererAPI::Metal:
finalFragmentSrc = fragment_source;
break;
#endif
default:
finalFragmentSrc = PrependFragmentPreamble(fragment_source);
break;
}
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));
@ -484,7 +492,10 @@ void main()
}
void RendererOutputShader::InitializeStatic()
{
if (g_renderer->GetType() == RendererAPI::Metal)
switch(g_renderer->GetType())
{
#ifdef ENABLE_METAL
case RendererAPI::Metal:
{
std::string vertex_source = GetMetalVertexSource(false);
std::string vertex_source_ud = GetMetalVertexSource(true);
@ -497,21 +508,17 @@ void RendererOutputShader::InitializeStatic()
s_hermit_shader = new RendererOutputShader(vertex_source, s_hermite_shader_source_mtl);
s_hermit_shader_ud = new RendererOutputShader(vertex_source_ud, s_hermite_shader_source_mtl);
break;
}
else
#endif
#ifdef ENABLE_OPENGL
case RendererAPI::OpenGL:
{
std::string vertex_source, vertex_source_ud;
// vertex shader
if (g_renderer->GetType() == RendererAPI::OpenGL)
{
vertex_source = GetOpenGlVertexSource(false);
vertex_source_ud = GetOpenGlVertexSource(true);
}
else if (g_renderer->GetType() == RendererAPI::Vulkan)
{
vertex_source = GetVulkanVertexSource(false);
vertex_source_ud = GetVulkanVertexSource(true);
}
vertex_source = GetOpenGlVertexSource(false);
vertex_source_ud = GetOpenGlVertexSource(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);
@ -520,7 +527,29 @@ 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);
break;
}
#endif
#ifdef ENABLE_VULKAN
case RendererAPI::Vulkan:
{
std::string vertex_source, vertex_source_ud;
// vertex shader
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);
s_bicubic_shader = new RendererOutputShader(vertex_source, s_bicubic_shader_source);
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);
break;
}
#endif
}
}
void RendererOutputShader::ShutdownStatic()

View File

@ -9,58 +9,7 @@
#include "util/helpers/helpers.h"
#include "util/helpers/Serializer.h"
#include "Cafe/HW/Latte/Common/RegisterSerializer.h"
/* rects emulation */
void rectsEmulationGS_outputSingleVertex(std::string& gsSrc, LatteDecompilerShader* vertexShader, LatteShaderPSInputTable* psInputTable, sint32 vIdx, const LatteContextRegister& latteRegister)
{
auto parameterMask = vertexShader->outputParameterMask;
for (uint32 i = 0; i < 32; i++)
{
if ((parameterMask & (1 << i)) == 0)
continue;
sint32 vsSemanticId = psInputTable->getVertexShaderOutParamSemanticId(latteRegister.GetRawView(), i);
if (vsSemanticId < 0)
continue;
// make sure PS has matching input
if (!psInputTable->hasPSImportForSemanticId(vsSemanticId))
continue;
gsSrc.append(fmt::format("passParameterSem{}Out = passParameterSem{}In[{}];\r\n", vsSemanticId, vsSemanticId, vIdx));
}
gsSrc.append(fmt::format("gl_Position = gl_in[{}].gl_Position;\r\n", vIdx));
gsSrc.append("EmitVertex();\r\n");
}
void rectsEmulationGS_outputGeneratedVertex(std::string& gsSrc, LatteDecompilerShader* vertexShader, LatteShaderPSInputTable* psInputTable, const char* variant, const LatteContextRegister& latteRegister)
{
auto parameterMask = vertexShader->outputParameterMask;
for (uint32 i = 0; i < 32; i++)
{
if ((parameterMask & (1 << i)) == 0)
continue;
sint32 vsSemanticId = psInputTable->getVertexShaderOutParamSemanticId(latteRegister.GetRawView(), i);
if (vsSemanticId < 0)
continue;
// make sure PS has matching input
if (!psInputTable->hasPSImportForSemanticId(vsSemanticId))
continue;
gsSrc.append(fmt::format("passParameterSem{}Out = gen4thVertex{}(passParameterSem{}In[0], passParameterSem{}In[1], passParameterSem{}In[2]);\r\n", vsSemanticId, variant, vsSemanticId, vsSemanticId, vsSemanticId));
}
gsSrc.append(fmt::format("gl_Position = gen4thVertex{}(gl_in[0].gl_Position, gl_in[1].gl_Position, gl_in[2].gl_Position);\r\n", variant));
gsSrc.append("EmitVertex();\r\n");
}
void rectsEmulationGS_outputVerticesCode(std::string& gsSrc, LatteDecompilerShader* vertexShader, LatteShaderPSInputTable* psInputTable, sint32 p0, sint32 p1, sint32 p2, sint32 p3, const char* variant, const LatteContextRegister& latteRegister)
{
sint32 pList[4] = { p0, p1, p2, p3 };
for (sint32 i = 0; i < 4; i++)
{
if (pList[i] == 3)
rectsEmulationGS_outputGeneratedVertex(gsSrc, vertexShader, psInputTable, variant, latteRegister);
else
rectsEmulationGS_outputSingleVertex(gsSrc, vertexShader, psInputTable, pList[i], latteRegister);
}
}
#include "HW/Latte/Renderer/RendererCore.h"
RendererShaderVk* rectsEmulationGS_generate(LatteDecompilerShader* vertexShader, const LatteContextRegister& latteRegister)
{

View File

@ -1221,6 +1221,7 @@ void VulkanRenderer::draw_endRenderPass()
m_state.activeRenderpassFBO = nullptr;
}
// Defined in the Common renderer
void LatteDraw_handleSpecialState8_clearAsDepth();
// transfer depth buffer data to color buffer

View File

@ -421,6 +421,15 @@ namespace coreinit
return 0;
}
uint32 MCP_DemoLaunchGetRemainder(uint32 mcpHandle, uint64 titleId, uint32be* outRemainder)
{
// stub to always return 99 uses remaining for game demos.
// used by drmapp.rpl to determine remaining uses for game demos
if (outRemainder)
*outRemainder = 99;
return 0;
}
void InitializeMCP()
{
osLib_addFunction("coreinit", "MCP_Open", coreinitExport_MCP_Open);
@ -450,6 +459,7 @@ namespace coreinit
cafeExportRegister("coreinit", MCP_GetEcoSettings, LogType::Placeholder);
cafeExportRegister("coreinit", MCP_GetTitleId, LogType::Placeholder);
cafeExportRegister("coreinit", MCP_DemoLaunchGetRemainder, LogType::Placeholder);
}
}

View File

@ -2360,6 +2360,7 @@ bool _ppcAssembler_emitDataDirective(PPCAssemblerContext& internalInfo, ASM_DATA
// write string bytes + null-termination character
internalInfo.ctx->outputData.insert(internalInfo.ctx->outputData.end(), stringData.data(), stringData.data() + stringData.size());
internalInfo.ctx->outputData.emplace_back(0);
writeIndex = internalInfo.ctx->outputData.size();
continue;
}
// numeric constants
@ -2492,6 +2493,16 @@ bool ppcAssembler_assembleSingleInstruction(char const* text, PPCAssemblerInOut*
internalInfo.listOperandStr.clear();
bool isInString = false;
auto isEscaped = [](const char* operandStartPtr, const char* ptr)
{
size_t backslashCount = 0;
while (ptr > operandStartPtr && ptr[-1] == '\\')
{
backslashCount++;
ptr--;
}
return (backslashCount & 1) != 0;
};
while (currentPtr < endPtr)
{
@ -2500,7 +2511,7 @@ bool ppcAssembler_assembleSingleInstruction(char const* text, PPCAssemblerInOut*
// find end of operand
while (currentPtr < endPtr)
{
if (*currentPtr == '"')
if (*currentPtr == '"' && !isEscaped(startPtr, currentPtr))
isInString=!isInString;
if (*currentPtr == ',' && !isInString)
@ -3587,6 +3598,9 @@ void ppcAsmTestDisassembler()
_testAsmArray({ 0x7f }, ".byte 0x7f");
_testAsmArray({ 0x74, 0x65, 0x73, 0x74, 0x00 }, ".byte \"test\"");
_testAsmArray({ 0x41, 0x42, 0x43, 0x00, 0x74, 0x65, 0x73, 0x74, 0x00 }, ".byte \"ABC\", \"test\"");
_testAsmArray({ 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x22, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x00, 0x00 }, R"(.byte "hello\"world", 0)");
_testAsmArray({ 0x61, 0x5c, 0x00, 0x00 }, R"(.byte "a\\", 0)");
_testAsmArray({ 0x61, 0x5c, 0x22, 0x62, 0x00, 0x00 }, R"(.byte "a\\\"b", 0)");
}

View File

@ -1,7 +1,9 @@
#include "Cafe/GameProfile/GameProfile.h"
#include "Cafe/IOSU/legacy/iosu_crypto.h"
#include "Cafe/HW/Latte/Core/Latte.h"
#ifdef ENABLE_VULKAN
#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h"
#endif
#include "Cafe/CafeSystem.h"
#include "Cemu/Logging/CemuLogging.h"
#include "config/ActiveSettings.h"
@ -106,12 +108,28 @@ bool ActiveSettings::WaitForGX2DrawDoneEnabled()
GraphicAPI ActiveSettings::GetGraphicsAPI()
{
GraphicAPI api = g_current_game_profile->GetGraphicsAPI().value_or(GetConfig().graphic_api);
// check if vulkan even available
if (api == kVulkan && !g_vulkan_available)
api = kOpenGL;
return api;
const GraphicAPI api = g_current_game_profile->GetGraphicsAPI().value_or(GetConfig().graphic_api);
std::optional<GraphicAPI> fallbackAPI;
#ifdef ENABLE_VULKAN
if (g_vulkan_available)
{
if (api == kVulkan)
return api;
fallbackAPI = kVulkan;
}
#endif
#ifdef ENABLE_METAL
if (api == kMetal)
return api;
fallbackAPI = fallbackAPI.value_or(kMetal);
#endif
#ifdef ENABLE_OPENGL
if (api == kOpenGL)
return api;
fallbackAPI = fallbackAPI.value_or(kOpenGL);
#endif
cemu_assert(fallbackAPI.has_value());
return *fallbackAPI;
}
float ActiveSettings::GetTVGamma()

View File

@ -123,7 +123,7 @@ XMLConfigParser CemuConfig::Load(XMLConfigParser& parser)
// graphics
auto graphic = parser.get("Graphic");
graphic_api = graphic.get("api", kOpenGL);
graphic_api = graphic.get("api", kDefaultGraphicsAPI);
graphic.get("device", legacy_graphic_device_uuid);
if (graphic.get("vkDevice").valid())
graphic.get("vkDevice", vk_graphic_device_uuid);
@ -144,7 +144,7 @@ XMLConfigParser CemuConfig::Load(XMLConfigParser& parser)
fullscreen_scaling = graphic.get("FullscreenScaling", kKeepAspectRatio);
async_compile = graphic.get("AsyncCompile", async_compile);
vk_accurate_barriers = graphic.get("vkAccurateBarriers", true); // this used to be "VulkanAccurateBarriers" but because we changed the default to true in 1.27.1 the option name had to be changed
#if ENABLE_METAL
#ifdef ENABLE_METAL
force_mesh_shaders = graphic.get("ForceMeshShaders", false);
#endif
@ -273,7 +273,7 @@ XMLConfigParser CemuConfig::Load(XMLConfigParser& parser)
crash_dump = debug.get("CrashDumpUnix", crash_dump);
#endif
gdb_port = debug.get("GDBPort", 1337);
#if ENABLE_METAL
#ifdef ENABLE_METAL
gpu_capture_dir = debug.get("GPUCaptureDir", "");
framebuffer_fetch = debug.get("FramebufferFetch", true);
#endif
@ -368,7 +368,7 @@ XMLConfigParser CemuConfig::Save(XMLConfigParser& parser)
graphic.set("OverrideGammaValue", overrideGammaValue);
graphic.set("UserDisplayGamma", userDisplayGamma);
graphic.set("GX2DrawdoneSync", gx2drawdone_sync);
#if ENABLE_METAL
#ifdef ENABLE_METAL
graphic.set("ForceMeshShaders", force_mesh_shaders);
#endif
//graphic.set("PrecompiledShaders", precompiled_shaders.GetValue());
@ -437,7 +437,7 @@ XMLConfigParser CemuConfig::Save(XMLConfigParser& parser)
debug.set("CrashDumpUnix", crash_dump.GetValue());
#endif
debug.set("GDBPort", gdb_port);
#if ENABLE_METAL
#ifdef ENABLE_METAL
debug.set("GPUCaptureDir", gpu_capture_dir);
debug.set("FramebufferFetch", framebuffer_fetch);
#endif

View File

@ -70,8 +70,17 @@ enum GraphicAPI
kOpenGL = 0,
kVulkan,
kMetal,
COUNT
};
#if defined(ENABLE_VULKAN)
constexpr GraphicAPI kDefaultGraphicsAPI = kVulkan;
#elif defined(ENABLE_METAL)
constexpr GraphicAPI kDefaultGraphicsAPI = kMetal;
#elif defined(ENABLE_OPENGL)
constexpr GraphicAPI kDefaultGraphicsAPI = kOpenGL;
#endif
enum AudioChannels
{
kMono = 0,
@ -429,7 +438,7 @@ struct CemuConfig
ConfigValueBounds<CafeConsoleLanguage> console_language{ CafeConsoleLanguage::EN };
// graphics
ConfigValue<GraphicAPI> graphic_api{ kVulkan };
ConfigValue<GraphicAPI> graphic_api{ kDefaultGraphicsAPI };
std::array<uint8, 16> legacy_graphic_device_uuid{}; // placeholder option for backwards compatibility with settings from 2.6 and before (renamed to "vkDevice")
std::array<uint8, 16> vk_graphic_device_uuid;
uint64 mtl_graphic_device_uuid{ 0 };
@ -437,7 +446,7 @@ struct CemuConfig
ConfigValue<bool> gx2drawdone_sync { true };
ConfigValue<bool> render_upside_down{ false };
ConfigValue<bool> async_compile{ true };
#if ENABLE_METAL
#ifdef ENABLE_METAL
ConfigValue<bool> force_mesh_shaders{ false };
#endif
@ -503,7 +512,7 @@ struct CemuConfig
// debug
ConfigValueBounds<CrashDump> crash_dump{ CrashDump::Disabled };
ConfigValue<uint16> gdb_port{ 1337 };
#if ENABLE_METAL
#ifdef ENABLE_METAL
ConfigValue<std::string> gpu_capture_dir{ "" };
ConfigValue<bool> framebuffer_fetch{ true };
#endif

View File

@ -1,9 +1,5 @@
add_library(CemuWxGui STATIC
canvas/IRenderCanvas.h
canvas/OpenGLCanvas.cpp
canvas/OpenGLCanvas.h
canvas/VulkanCanvas.cpp
canvas/VulkanCanvas.h
CemuApp.cpp
CemuApp.h
CemuUpdateWindow.cpp
@ -118,6 +114,20 @@ add_library(CemuWxGui STATIC
wxHelper.h
)
if (ENABLE_OPENGL)
target_sources(CemuWxGui PRIVATE
canvas/OpenGLCanvas.cpp
canvas/OpenGLCanvas.h
)
endif()
if (ENABLE_VULKAN)
target_sources(CemuWxGui PRIVATE
canvas/VulkanCanvas.cpp
canvas/VulkanCanvas.h
)
endif()
if (ENABLE_METAL)
target_sources(CemuWxGui PRIVATE
canvas/MetalCanvas.cpp
@ -147,7 +157,6 @@ target_link_libraries(CemuWxGui PRIVATE
libzip::zip
ZArchive::zarchive
CemuComponents
SDL2::SDL2
pugixml::pugixml
CemuCafe
PUBLIC
@ -166,6 +175,10 @@ if(ENABLE_CUBEB)
target_link_libraries(CemuWxGui PRIVATE cubeb::cubeb)
endif()
if(ENABLE_SDL)
target_link_libraries(CemuWxGui PRIVATE SDL3::SDL3)
endif()
if(UNIX AND NOT APPLE)
if(ENABLE_FERAL_GAMEMODE)
target_link_libraries(CemuWxGui PRIVATE gamemode)

View File

@ -3,12 +3,15 @@
#include "wxgui/MainWindow.h"
#include "wxgui/wxgui.h"
#include "config/CemuConfig.h"
#ifdef ENABLE_VULKAN
#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h"
#endif
#include "Cafe/HW/Latte/Core/LatteOverlay.h"
#include "config/ActiveSettings.h"
#include "config/LaunchSettings.h"
#include "wxgui/GettingStartedDialog.h"
#include "input/InputManager.h"
#include "input/api/SDL/SDLControllerProvider.h"
#include "wxgui/helpers/wxHelpers.h"
#include "Cemu/ncrypto/ncrypto.h"
#include "wxgui/input/HotkeySettings.h"
@ -26,6 +29,7 @@
#include <wx/filename.h>
#include <wx/stdpaths.h>
#include <wx/clipbrd.h>
#include <wx/timer.h>
#include "wxHelper.h"
#include "Cafe/TitleList/TitleList.h"
@ -332,8 +336,18 @@ bool CemuApp::OnInit()
#ifdef CEMU_DEBUG_ASSERT
UnitTests();
#endif
#if BOOST_OS_MACOS
SDLControllerProvider::InitSDL();
#endif
CemuCommonInit();
#if BOOST_OS_MACOS
m_sdlEventPumpTimer = new wxTimer(this);
Bind(wxEVT_TIMER, &CemuApp::OnSDLEventPumpTimer, this);
m_sdlEventPumpTimer->Start(5, wxTIMER_CONTINUOUS);
#endif
wxInitAllImageHandlers();
// fill colour db
@ -349,7 +363,9 @@ bool CemuApp::OnInit()
__fastfail(0);
}
#endif
#ifdef ENABLE_VULKAN
InitializeGlobalVulkan();
#endif
Bind(wxEVT_ACTIVATE_APP, &CemuApp::ActivateApp, this);
@ -389,8 +405,22 @@ bool CemuApp::OnInit()
int CemuApp::OnExit()
{
#if BOOST_OS_MACOS
if (m_sdlEventPumpTimer)
{
m_sdlEventPumpTimer->Stop();
Unbind(wxEVT_TIMER, &CemuApp::OnSDLEventPumpTimer, this);
delete m_sdlEventPumpTimer;
m_sdlEventPumpTimer = nullptr;
}
#endif
wxApp::OnExit();
wxTheClipboard->Flush();
InputManager::instance().Shutdown();
#if BOOST_OS_MACOS
SDLControllerProvider::ShutdownSDL();
#endif
#if BOOST_OS_WINDOWS
ExitProcess(0);
#else
@ -398,6 +428,15 @@ int CemuApp::OnExit()
#endif
}
#if BOOST_OS_MACOS
void CemuApp::OnSDLEventPumpTimer(wxTimerEvent& event)
{
// this callback is only used on macOS where SDL event functions need to be called on the main thread
// on other platforms SDLControllerProvider creates a separate thread for SDL event polling
SDLControllerProvider::PumpSDLEvents();
}
#endif
#if BOOST_OS_WINDOWS
void DumpThreadStackTrace();
#endif

View File

@ -3,6 +3,8 @@
#include <wx/app.h>
class MainWindow;
class wxTimer;
class wxTimerEvent;
class CemuApp : public wxApp
{
@ -30,6 +32,10 @@ private:
static std::vector<const wxLanguageInfo*> GetAvailableTranslationLanguages(wxTranslations* translationsMgr);
MainWindow* m_mainFrame = nullptr;
#if BOOST_OS_MACOS
void OnSDLEventPumpTimer(wxTimerEvent& event);
wxTimer* m_sdlEventPumpTimer = nullptr;
#endif
wxLocale m_locale;
std::vector<const wxLanguageInfo*> m_availableTranslations;

View File

@ -113,7 +113,7 @@ GameProfileWindow::GameProfileWindow(wxWindow* parent, uint64_t title_id)
first_row->Add(new wxStaticText(panel, wxID_ANY, _("Graphics API")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
wxString gapi_values[] = { "", "OpenGL", "Vulkan",
#if ENABLE_METAL
#ifdef ENABLE_METAL
"Metal"
#endif
};
@ -127,7 +127,7 @@ GameProfileWindow::GameProfileWindow(wxWindow* parent, uint64_t title_id)
m_shader_mul_accuracy->SetToolTip(_("EXPERT OPTION\nControls the accuracy of floating point multiplication in shaders.\n\nRecommended: true"));
first_row->Add(m_shader_mul_accuracy, 0, wxALL, 5);
#if ENABLE_METAL
#ifdef ENABLE_METAL
first_row->Add(new wxStaticText(panel, wxID_ANY, _("Shader fast math")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
wxString math_values[] = { _("false"), _("true") };
@ -296,7 +296,7 @@ void GameProfileWindow::ApplyProfile()
else
m_graphic_api->SetSelection(1 + m_game_profile.m_graphics_api.value()); // "", OpenGL, Vulkan, Metal
m_shader_mul_accuracy->SetSelection((int)m_game_profile.m_accurateShaderMul);
#if ENABLE_METAL
#ifdef ENABLE_METAL
m_shader_fast_math->SetSelection((int)m_game_profile.m_shaderFastMath);
m_metal_buffer_cache_mode->SetSelection((int)m_game_profile.m_metalBufferCacheMode);
m_position_invariance->SetSelection((int)m_game_profile.m_positionInvariance);
@ -362,7 +362,7 @@ void GameProfileWindow::SaveProfile()
m_game_profile.m_accurateShaderMul = (AccurateShaderMulOption)m_shader_mul_accuracy->GetSelection();
if (m_game_profile.m_accurateShaderMul != AccurateShaderMulOption::False && m_game_profile.m_accurateShaderMul != AccurateShaderMulOption::True)
m_game_profile.m_accurateShaderMul = AccurateShaderMulOption::True; // force a legal value
#if ENABLE_METAL
#ifdef ENABLE_METAL
m_game_profile.m_shaderFastMath = (bool)m_shader_fast_math->GetSelection();
m_game_profile.m_metalBufferCacheMode = (MetalBufferCacheMode)m_metal_buffer_cache_mode->GetSelection();
m_game_profile.m_positionInvariance = (PositionInvariance)m_position_invariance->GetSelection();

View File

@ -40,7 +40,7 @@ private:
wxChoice* m_graphic_api;
wxChoice* m_shader_mul_accuracy;
#if ENABLE_METAL
#ifdef ENABLE_METAL
wxChoice* m_shader_fast_math;
wxChoice* m_metal_buffer_cache_mode;
wxChoice* m_position_invariance;

View File

@ -27,9 +27,11 @@
#include "audio/IAudioInputAPI.h"
#ifdef ENABLE_VULKAN
#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h"
#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h"
#if ENABLE_METAL
#endif
#ifdef ENABLE_METAL
#include "Cafe/HW/Latte/Renderer/Metal/MetalRenderer.h"
#endif
#include "Cafe/Account/Account.h"
@ -87,6 +89,7 @@ private:
IAudioInputAPI::DeviceDescriptionPtr m_description;
};
#ifdef ENABLE_VULKAN
class wxVulkanUUID : public wxClientData
{
public:
@ -97,8 +100,9 @@ public:
private:
VulkanRenderer::DeviceInfo m_device_info;
};
#endif
#if ENABLE_METAL
#ifdef ENABLE_METAL
class wxMetalUUID : public wxClientData
{
public:
@ -352,15 +356,25 @@ wxPanel* GeneralSettings2::AddGraphicsPage(wxNotebook* notebook)
row->Add(new wxStaticText(box, wxID_ANY, _("Graphics API")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
sint32 api_size = 1;
wxString choices[3] = { "OpenGL" };
sint32 api_size = 0;
wxString choices[size_t(GraphicAPI::COUNT)];
#ifdef ENABLE_OPENGL
choices[api_size++] = "OpenGL";
m_api_map.push_back(GraphicAPI::kOpenGL);
#endif
#ifdef ENABLE_VULKAN
if (g_vulkan_available)
{
choices[api_size++] = "Vulkan";
m_api_map.push_back(GraphicAPI::kVulkan);
}
#if ENABLE_METAL
choices[api_size++] = "Metal";
#endif
#ifdef ENABLE_METAL
choices[api_size++] = "Metal";
m_api_map.push_back(GraphicAPI::kMetal);
#endif
wxASSERT(api_size > 0);
m_graphic_api = new wxChoice(box, wxID_ANY, wxDefaultPosition, wxDefaultSize, api_size, choices);
m_graphic_api->SetSelection(0);
@ -400,7 +414,7 @@ wxPanel* GeneralSettings2::AddGraphicsPage(wxNotebook* notebook)
m_gx2drawdone_sync->SetToolTip(_("If synchronization is requested by the game, the emulated CPU will wait for the GPU to finish all operations.\nThis is more accurate behavior, but may cause lower performance"));
graphic_misc_row->Add(m_gx2drawdone_sync, 0, wxALL, 5);
#if ENABLE_METAL
#ifdef ENABLE_METAL
m_force_mesh_shaders = new wxCheckBox(box, wxID_ANY, _("Force mesh shaders"));
m_force_mesh_shaders->SetToolTip(_("Force mesh shaders on all GPUs that support them. Mesh shaders are disabled by default on Intel GPUs due to potential stability issues.\nMetal only"));
graphic_misc_row->Add(m_force_mesh_shaders, 0, wxALL, 5);
@ -1028,7 +1042,7 @@ wxPanel* GeneralSettings2::AddDebugPage(wxNotebook* notebook)
debug_panel_sizer->Add(debug_row, 0, wxALL | wxEXPAND, 5);
}
#if ENABLE_METAL
#ifdef ENABLE_METAL
{
auto* debug_row = new wxFlexGridSizer(0, 2, 0, 0);
debug_row->SetFlexibleDirection(wxBOTH);
@ -1216,9 +1230,10 @@ void GeneralSettings2::StoreConfig()
}
// graphics
config.graphic_api = (GraphicAPI)m_graphic_api->GetSelection();
config.graphic_api = m_api_map[m_graphic_api->GetSelection()];
selection = m_graphic_device->GetSelection();
#ifdef ENABLE_VULKAN
if (config.graphic_api == GraphicAPI::kVulkan)
{
if (selection != wxNOT_FOUND)
@ -1232,25 +1247,26 @@ void GeneralSettings2::StoreConfig()
else
config.vk_graphic_device_uuid = {};
}
#if ENABLE_METAL
else if (config.graphic_api == GraphicAPI::kMetal)
#endif
#ifdef ENABLE_METAL
if (config.graphic_api == GraphicAPI::kMetal)
{
if (selection != wxNOT_FOUND)
{
const auto* info = (wxMetalUUID*)m_graphic_device->GetClientObject(selection);
if (info)
config.mtl_graphic_device_uuid = info->GetDeviceInfo().uuid;
else
config.mtl_graphic_device_uuid = {};
}
else
config.mtl_graphic_device_uuid = {};
if (selection != wxNOT_FOUND)
{
const auto* info = (wxMetalUUID*)m_graphic_device->GetClientObject(selection);
if (info)
config.mtl_graphic_device_uuid = info->GetDeviceInfo().uuid;
else
config.mtl_graphic_device_uuid = {};
}
else
config.mtl_graphic_device_uuid = {};
}
#endif
config.gx2drawdone_sync = m_gx2drawdone_sync->IsChecked();
#if ENABLE_METAL
#ifdef ENABLE_METAL
config.force_mesh_shaders = m_force_mesh_shaders->IsChecked();
#endif
config.async_compile = m_async_compile->IsChecked();
@ -1281,7 +1297,7 @@ void GeneralSettings2::StoreConfig()
// debug
config.crash_dump = (CrashDump)m_crash_dump->GetSelection();
config.gdb_port = m_gdb_port->GetValue();
#if ENABLE_METAL
#ifdef ENABLE_METAL
config.gpu_capture_dir = m_gpu_capture_dir->GetValue().utf8_string();
config.framebuffer_fetch = m_framebuffer_fetch->IsChecked();
#endif
@ -1721,7 +1737,12 @@ void GeneralSettings2::HandleGraphicsApiSelection()
selection = GetConfig().vsync;
m_vsync->Clear();
if (m_graphic_api->GetSelection() == 0)
auto api = m_api_map[m_graphic_api->GetSelection()];
switch (api)
{
#ifdef ENABLE_OPENGL
case GraphicAPI::kOpenGL:
{
// OpenGL
m_vsync->AppendString(_("Off"));
@ -1736,16 +1757,19 @@ void GeneralSettings2::HandleGraphicsApiSelection()
m_gx2drawdone_sync->Enable();
m_async_compile->Disable();
#if ENABLE_METAL
#ifdef ENABLE_METAL
m_force_mesh_shaders->Disable();
#endif
break;
}
else if (m_graphic_api->GetSelection() == 1)
#endif
#ifdef ENABLE_VULKAN
case GraphicAPI::kVulkan:
{
// Vulkan
m_gx2drawdone_sync->Disable();
m_async_compile->Enable();
#if ENABLE_METAL
#ifdef ENABLE_METAL
m_force_mesh_shaders->Disable();
#endif
@ -1779,9 +1803,11 @@ void GeneralSettings2::HandleGraphicsApiSelection()
}
}
}
break;
}
#if ENABLE_METAL
else
#endif
#ifdef ENABLE_METAL
case GraphicAPI::kMetal:
{
// Metal
m_gx2drawdone_sync->Disable();
@ -1815,8 +1841,10 @@ void GeneralSettings2::HandleGraphicsApiSelection()
}
}
}
break;
}
#endif
}
}
void GeneralSettings2::ApplyConfig()
@ -1870,7 +1898,14 @@ void GeneralSettings2::ApplyConfig()
}
// graphics
m_graphic_api->SetSelection(config.graphic_api);
for (int i = 0; i < (int)m_api_map.size(); ++i)
{
if (m_api_map[i] == config.graphic_api)
{
m_graphic_api->SetSelection(i);
break;
}
}
m_vsync->SetSelection(config.vsync);
m_overrideGamma->SetValue(config.overrideAppGammaPreference);
m_overrideGammaValue->SetValue(config.overrideGammaValue);
@ -1883,7 +1918,7 @@ void GeneralSettings2::ApplyConfig()
}
m_async_compile->SetValue(config.async_compile);
m_gx2drawdone_sync->SetValue(config.gx2drawdone_sync);
#if ENABLE_METAL
#ifdef ENABLE_METAL
m_force_mesh_shaders->SetValue(config.force_mesh_shaders);
#endif
m_upscale_filter->SetSelection(config.upscale_filter);
@ -2022,7 +2057,7 @@ void GeneralSettings2::ApplyConfig()
// debug
m_crash_dump->SetSelection((int)config.crash_dump.GetValue());
m_gdb_port->SetValue(config.gdb_port.GetValue());
#if ENABLE_METAL
#ifdef ENABLE_METAL
m_gpu_capture_dir->SetValue(wxString::FromUTF8(config.gpu_capture_dir.GetValue()));
m_framebuffer_fetch->SetValue(config.framebuffer_fetch);
#endif

View File

@ -1,4 +1,5 @@
#pragma once
#include "config/CemuConfig.h"
#include <wx/collpane.h>
#include <wx/propgrid/propgrid.h>
#include <Cafe/Account/Account.h>
@ -36,6 +37,7 @@ private:
bool m_game_launched;
bool m_has_account_change = false; // keep track of dirty state of accounts
std::vector<GraphicAPI> m_api_map; // map from dropdown index to GraphicsAPISetting, used in HandleGraphicsApiSelection
wxPanel* AddGeneralPage(wxNotebook* notebook);
@ -71,7 +73,7 @@ private:
wxCheckBox* m_userDisplayisSRGB;
wxCheckBox *m_async_compile, *m_gx2drawdone_sync;
#if ENABLE_METAL
#ifdef ENABLE_METAL
wxCheckBox *m_force_mesh_shaders;
#endif
wxRadioBox* m_upscale_filter, *m_downscale_filter, *m_fullscreen_scaling;
@ -99,7 +101,7 @@ private:
// Debug
wxChoice* m_crash_dump;
wxSpinCtrl* m_gdb_port;
#if ENABLE_METAL
#ifdef ENABLE_METAL
wxTextCtrl* m_gpu_capture_dir;
wxCheckBox* m_framebuffer_fetch;
#endif

View File

@ -3,18 +3,21 @@
#include "wxCemuConfig.h"
#include "wxgui/wxgui.h"
#include "wxgui/MainWindow.h"
#include <wx/mstream.h>
#include "wxgui/GameUpdateWindow.h"
#include "wxgui/PadViewFrame.h"
#include "wxgui/windows/TextureRelationViewer/TextureRelationWindow.h"
#include "wxgui/windows/PPCThreadsViewer/DebugPPCThreadsWindow.h"
#include "AudioDebuggerWindow.h"
#ifdef ENABLE_OPENGL
#include "wxgui/canvas/OpenGLCanvas.h"
#endif
#ifdef ENABLE_VULKAN
#include "wxgui/canvas/VulkanCanvas.h"
#if ENABLE_METAL
#include "Cafe/HW/Latte/Renderer/Vulkan/VsyncDriver.h"
#endif
#ifdef ENABLE_METAL
#include "wxgui/canvas/MetalCanvas.h"
#include "Cafe/HW/Latte/Renderer/Metal/MetalRenderer.h"
#endif
#include "Cafe/OS/libs/nfc/nfc.h"
#include "Cafe/OS/libs/swkbd/swkbd.h"
@ -42,7 +45,6 @@
#include "wxgui/DownloadGraphicPacksWindow.h"
#include "wxgui/GettingStartedDialog.h"
#include "wxgui/helpers/wxHelpers.h"
#include "Cafe/HW/Latte/Renderer/Vulkan/VsyncDriver.h"
#include "wxgui/input/InputSettings2.h"
#include "wxgui/input/HotkeySettings.h"
#include "input/InputManager.h"
@ -66,10 +68,6 @@
#include "gamemode_client.h"
#endif
#if ENABLE_METAL
#include "Cafe/HW/Latte/Renderer/Metal/MetalRenderer.h"
#endif
#include "Cafe/TitleList/TitleInfo.h"
#include "Cafe/TitleList/TitleList.h"
#include "wxHelper.h"
@ -1043,7 +1041,7 @@ void MainWindow::OnDebugSetting(wxCommandEvent& event)
if(!GetConfig().vk_accurate_barriers)
wxMessageBox(_("Warning: Disabling the accurate barriers option will lead to flickering graphics but may improve performance. It is highly recommended to leave it turned on."), _("Accurate barriers are off"), wxOK);
}
#if ENABLE_METAL
#ifdef ENABLE_METAL
else if (event.GetId() == MAINFRAME_MENU_ID_DEBUG_GPU_CAPTURE)
{
cemu_assert_debug(g_renderer->GetType() == RendererAPI::Metal);
@ -1607,14 +1605,21 @@ void MainWindow::CreateCanvas()
this->GetSizer()->Add(m_game_panel, 1, wxEXPAND);
// create canvas
if (ActiveSettings::GetGraphicsAPI() == kVulkan)
m_render_canvas = new VulkanCanvas(m_game_panel, wxSize(1280, 720), true);
else if (ActiveSettings::GetGraphicsAPI() == kOpenGL)
#ifdef ENABLE_OPENGL
if (ActiveSettings::GetGraphicsAPI() == kOpenGL)
m_render_canvas = GLCanvas_Create(m_game_panel, wxSize(1280, 720), true);
#if ENABLE_METAL
else
m_render_canvas = new MetalCanvas(m_game_panel, wxSize(1280, 720), true);
#endif
#endif
#ifdef ENABLE_VULKAN
if (ActiveSettings::GetGraphicsAPI() == kVulkan)
m_render_canvas = new VulkanCanvas(m_game_panel, wxSize(1280, 720), true);
#endif
#ifdef ENABLE_METAL
if (ActiveSettings::GetGraphicsAPI() == kMetal)
m_render_canvas = new MetalCanvas(m_game_panel, wxSize(1280, 720), true);
#endif
if (!m_render_canvas)
cemu_assert(false && "Failed to create canvas or invalid graphics API selected");
cemu_assert(m_render_canvas != nullptr);
// mouse events
m_render_canvas->Bind(wxEVT_MOTION, &MainWindow::OnMouseMove, this);
@ -1676,7 +1681,9 @@ void MainWindow::OnSizeEvent(wxSizeEvent& event)
event.Skip();
#ifdef ENABLE_VULKAN
VsyncDriver_notifyWindowPosChanged();
#endif
}
void MainWindow::OnDPIChangedEvent(wxDPIChangedEvent& event)
@ -1697,7 +1704,9 @@ void MainWindow::OnMove(wxMoveEvent& event)
if (m_debugger_window && m_debugger_window->IsShown())
m_debugger_window->OnParentMove(GetPosition(), GetSize());
#ifdef ENABLE_VULKAN
VsyncDriver_notifyWindowPosChanged();
#endif
}
void MainWindow::OnDebuggerClose(wxCloseEvent& event)
@ -2351,7 +2360,7 @@ void MainWindow::RecreateMenu()
auto accurateBarriers = debugMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_VK_ACCURATE_BARRIERS, _("&Accurate barriers (Vulkan)"));
accurateBarriers->Check(GetConfig().vk_accurate_barriers);
#if ENABLE_METAL
#ifdef ENABLE_METAL
auto gpuCapture = debugMenu->Append(MAINFRAME_MENU_ID_DEBUG_GPU_CAPTURE, _("&GPU capture (Metal)"));
gpuCapture->Enable(m_game_launched && g_renderer->GetType() == RendererAPI::Metal);
#endif

View File

@ -6,9 +6,13 @@
#include "config/ActiveSettings.h"
#include "Cafe/OS/libs/swkbd/swkbd.h"
#ifdef ENABLE_OPENGL
#include "wxgui/canvas/OpenGLCanvas.h"
#endif
#ifdef ENABLE_VULKAN
#include "wxgui/canvas/VulkanCanvas.h"
#if ENABLE_METAL
#endif
#ifdef ENABLE_METAL
#include "wxgui/canvas/MetalCanvas.h"
#endif
#include "config/CemuConfig.h"
@ -75,16 +79,21 @@ void PadViewFrame::InitializeRenderCanvas()
{
auto sizer = new wxBoxSizer(wxVERTICAL);
{
#ifdef ENABLE_VULKAN
if (ActiveSettings::GetGraphicsAPI() == kVulkan)
m_render_canvas = new VulkanCanvas(this, wxSize(854, 480), false);
else if (ActiveSettings::GetGraphicsAPI() == kOpenGL)
#endif
#ifdef ENABLE_OPENGL
if (ActiveSettings::GetGraphicsAPI() == kOpenGL)
m_render_canvas = GLCanvas_Create(this, wxSize(854, 480), false);
#if ENABLE_METAL
else
m_render_canvas = new MetalCanvas(this, wxSize(854, 480), false);
#endif
#endif
#ifdef ENABLE_METAL
if (ActiveSettings::GetGraphicsAPI() == kMetal)
m_render_canvas = new MetalCanvas(this, wxSize(854, 480), false);
#endif
sizer->Add(m_render_canvas, 1, wxEXPAND, 0, nullptr);
}
cemu_assert(m_render_canvas != nullptr);
SetSizer(sizer);
Layout();

View File

@ -101,12 +101,10 @@ void WindowSystem::UpdateWindowTitles(bool isIdle, bool isLoading, double fps)
case RendererAPI::Vulkan:
renderer = "[Vulkan]";
break;
#if ENABLE_METAL
case RendererAPI::Metal:
renderer = "[Metal]";
break;
#endif
default:;
default: break;
}
}

View File

@ -1,12 +1,22 @@
add_library(imguiImpl
imgui_impl_opengl3.cpp
imgui_impl_opengl3.h
imgui_impl_vulkan.cpp
imgui_impl_vulkan.h
imgui_extension.cpp
imgui_extension.h
)
if (ENABLE_OPENGL)
target_sources(imguiImpl PRIVATE
imgui_impl_opengl3.cpp
imgui_impl_opengl3.h
)
endif()
if (ENABLE_VULKAN)
target_sources(imguiImpl PRIVATE
imgui_impl_vulkan.cpp
imgui_impl_vulkan.h
)
endif()
if (ENABLE_METAL)
target_sources(imguiImpl PRIVATE
imgui_impl_metal.mm

View File

@ -2,9 +2,13 @@
#include "WindowSystem.h"
#include "Cafe/HW/Latte/Renderer/Renderer.h"
#include "resource/IconsFontAwesome5.h"
#include "imgui_impl_opengl3.h"
#include "resource/resource.h"
#ifdef ENABLE_OPENGL
#include "imgui_impl_opengl3.h"
#endif
#ifdef ENABLE_VULKAN
#include "imgui_impl_vulkan.h"
#endif
#include "input/InputManager.h"
// <imgui_internal.h>

View File

@ -15,10 +15,6 @@ add_library(CemuInput
api/DSU/DSUControllerProvider.h
api/DSU/DSUMessages.h
api/DSU/DSUMessages.cpp
api/SDL/SDLController.cpp
api/SDL/SDLControllerProvider.cpp
api/SDL/SDLController.h
api/SDL/SDLControllerProvider.h
api/Keyboard/KeyboardControllerProvider.h
api/Keyboard/KeyboardControllerProvider.cpp
api/Keyboard/KeyboardController.cpp
@ -92,6 +88,16 @@ if (SUPPORTS_WIIMOTE)
endif ()
if(ENABLE_SDL)
target_sources(CemuInput PRIVATE
api/SDL/SDLController.cpp
api/SDL/SDLControllerProvider.cpp
api/SDL/SDLController.h
api/SDL/SDLControllerProvider.h
)
target_link_libraries(CemuInput PRIVATE SDL3::SDL3)
endif()
target_include_directories(CemuInput PUBLIC "../")
target_link_libraries(CemuInput PRIVATE
@ -99,10 +105,6 @@ target_link_libraries(CemuInput PRIVATE
CemuGui
)
if (ENABLE_HIDAPI)
target_link_libraries(CemuInput PRIVATE hidapi::hidapi)
endif()
if (ENABLE_BLUEZ)
target_link_libraries(CemuInput PRIVATE bluez::bluez)
endif ()

View File

@ -5,7 +5,6 @@
#include "input/emulated/ClassicController.h"
#include "input/emulated/WiimoteController.h"
#include "input/api/SDL/SDLController.h"
#include "input/api/Keyboard/KeyboardController.h"
#include "input/api/DSU/DSUController.h"
#include "input/api/GameCube/GameCubeController.h"
@ -19,6 +18,10 @@
#include "input/api/Wiimote/NativeWiimoteController.h"
#endif
#ifdef HAS_SDL
#include "input/api/SDL/SDLController.h"
#endif
ControllerPtr ControllerFactory::CreateController(InputAPI::Type api, std::string_view uuid,
std::string_view display_name)
{
@ -59,7 +62,7 @@ ControllerPtr ControllerFactory::CreateController(InputAPI::Type api, std::strin
return std::make_shared<XInputController>(index);
}
#endif
#if HAS_SDL
#ifdef HAS_SDL
case InputAPI::SDLController:
{
// diid_guid
@ -68,7 +71,7 @@ ControllerPtr ControllerFactory::CreateController(InputAPI::Type api, std::strin
throw std::invalid_argument(fmt::format("invalid sdl uuid format: {}", uuid));
const auto guid_index = ConvertString<size_t>(uuid.substr(0, index));
const auto guid = SDL_JoystickGetGUIDFromString(std::string{uuid.substr(index + 1)}.c_str());
const auto guid = SDL_StringToGUID(std::string{uuid.substr(index + 1)}.c_str());
if (display_name.empty())
return std::make_shared<SDLController>(guid, guid_index);
@ -137,7 +140,7 @@ ControllerProviderPtr ControllerFactory::CreateControllerProvider(InputAPI::Type
case InputAPI::Keyboard:
return std::make_shared<KeyboardControllerProvider>();
#endif
#if HAS_SDL
#ifdef HAS_SDL
case InputAPI::SDLController:
return std::make_shared<SDLControllerProvider>();
#endif

View File

@ -28,7 +28,7 @@ InputManager::InputManager()
#if HAS_KEYBOARD
create_provider<KeyboardControllerProvider>();
#endif
#if HAS_SDL
#ifdef HAS_SDL
create_provider<SDLControllerProvider>();
#endif
#if HAS_XINPUT
@ -53,8 +53,7 @@ InputManager::InputManager()
InputManager::~InputManager()
{
m_update_thread_shutdown.store(true);
m_update_thread.join();
// destructors will not invoked forever, so we manually release resources in Shutdown().
}
void InputManager::load() noexcept
@ -952,3 +951,21 @@ void InputManager::update_thread()
std::this_thread::yield();
}
}
void InputManager::Shutdown()
{
m_update_thread_shutdown = true;
if (m_update_thread.joinable())
{
m_update_thread.join();
}
for (auto& pad : m_vpad) pad.reset();
for (auto& pad : m_wpad) pad.reset();
for (auto& providers : m_api_available)
{
providers.clear();
}
}

View File

@ -9,9 +9,10 @@
#include "input/api/Wiimote/WiimoteControllerProvider.h"
#endif
#include "util/helpers/Singleton.h"
#ifdef HAS_SDL
#include "input/api/SDL/SDLControllerProvider.h"
#endif
#include "input/api/Keyboard/KeyboardControllerProvider.h"
#include "input/api/DSU/DSUControllerProvider.h"
#include "input/api/GameCube/GameCubeControllerProvider.h"
@ -19,8 +20,7 @@
#include "input/emulated/VPADController.h"
#include "input/emulated/WPADController.h"
#include <atomic>
#include <optional>
#include "util/helpers/Singleton.h"
class InputManager : public Singleton<InputManager>
{
@ -46,6 +46,8 @@ public:
bool is_gameprofile_set(size_t player_index) const;
void Shutdown();
EmulatedControllerPtr set_controller(EmulatedControllerPtr controller);
EmulatedControllerPtr set_controller(size_t player_index, EmulatedController::Type type);
EmulatedControllerPtr set_controller(size_t player_index, EmulatedController::Type type, const std::shared_ptr<ControllerBase>& controller);
@ -106,16 +108,14 @@ private:
std::array<bool, kMaxController> m_is_gameprofile_set{};
template <typename TProvider>
void create_provider() // lambda templates only work in c++20 -> define locally in ctor
template<std::derived_from<ControllerProviderBase> TProvider>
void create_provider()
{
static_assert(std::is_base_of_v<ControllerProviderBase, TProvider>);
try
{
auto controller = std::make_shared<TProvider>();
m_api_available[controller->api()] = std::vector<ControllerProviderPtr>{ controller };
}
catch (const std::exception& ex)
m_api_available[controller->api()] = std::vector<ControllerProviderPtr>{controller};
} catch (const std::exception& ex)
{
cemuLog_log(LogType::Force, ex.what());
}

View File

@ -7,6 +7,19 @@ ControllerBase::ControllerBase(std::string_view uuid, std::string_view display_n
{
}
inline void apply_axis_button(ControllerButtonState& buttons, const glm::vec2& axis, int flag)
{
if (axis.x < -ControllerState::kAxisThreshold)
buttons.SetButtonState(flag + (kAxisXN - kAxisXP), true);
else if (axis.x > ControllerState::kAxisThreshold)
buttons.SetButtonState(flag, true);
if (axis.y < -ControllerState::kAxisThreshold)
buttons.SetButtonState(flag + 1 + (kAxisXN - kAxisXP), true);
else if (axis.y > ControllerState::kAxisThreshold)
buttons.SetButtonState(flag + 1, true);
}
const ControllerState& ControllerBase::update_state()
{
if (!m_is_calibrated)
@ -21,26 +34,9 @@ const ControllerState& ControllerBase::update_state()
apply_axis_setting(result.rotation, m_default_state.rotation, m_settings.rotation);
apply_axis_setting(result.trigger, m_default_state.trigger, m_settings.trigger);
#define APPLY_AXIS_BUTTON(_axis_, _flag_) \
if (result._axis_.x < -ControllerState::kAxisThreshold) \
result.buttons.SetButtonState((_flag_) + (kAxisXN - kAxisXP), true); \
else if (result._axis_.x > ControllerState::kAxisThreshold) \
result.buttons.SetButtonState((_flag_), true); \
if (result._axis_.y < -ControllerState::kAxisThreshold) \
result.buttons.SetButtonState((_flag_) + 1 + (kAxisXN - kAxisXP), true); \
else if (result._axis_.y > ControllerState::kAxisThreshold) \
result.buttons.SetButtonState((_flag_) + 1, true);
if (result.axis.x < -ControllerState::kAxisThreshold)
result.buttons.SetButtonState((kAxisXP) + (kAxisXN - kAxisXP), true);
else if (result.axis.x > ControllerState::kAxisThreshold)
result.buttons.SetButtonState((kAxisXP), true);
if (result.axis.y < -ControllerState::kAxisThreshold)
result.buttons.SetButtonState((kAxisXP) + 1 + (kAxisXN - kAxisXP), true);
else if (result.axis.y > ControllerState::kAxisThreshold)
result.buttons.SetButtonState((kAxisXP) + 1, true);
APPLY_AXIS_BUTTON(rotation, kRotationXP);
APPLY_AXIS_BUTTON(trigger, kTriggerXP);
apply_axis_button(result.buttons, result.axis, kAxisXP);
apply_axis_button(result.buttons, result.rotation, kRotationXP);
apply_axis_button(result.buttons, result.trigger, kTriggerXP);
/*
// positive values
@ -64,9 +60,6 @@ const ControllerState& ControllerBase::update_state()
kTriggerYN,
*/
#undef APPLY_AXIS_BUTTON
WindowSystem::CaptureInput(result, m_last_state);
m_last_state = std::move(result);
@ -200,7 +193,6 @@ std::string ControllerBase::get_button_name(uint64 button) const
case kTriggerYN: return "y-Trigger-";
}
return fmt::format("Button {}", (uint64)button);
}

View File

@ -168,14 +168,13 @@ protected:
Settings m_settings{};
};
template<class TProvider>
template<std::derived_from<ControllerProviderBase> TProvider>
class Controller : public ControllerBase
{
public:
Controller(std::string_view uuid, std::string_view display_name)
: ControllerBase(uuid, display_name)
{
static_assert(std::is_base_of_v<ControllerProviderBase, TProvider>);
m_provider = std::dynamic_pointer_cast<TProvider>(InputManager::instance().get_api_provider(TProvider::kAPIType));
cemu_assert_debug(m_provider != nullptr);
}
@ -183,7 +182,6 @@ public:
Controller(std::string_view uuid, std::string_view display_name, const ControllerProviderSettings& settings)
: ControllerBase(uuid, display_name)
{
static_assert(std::is_base_of_v<ControllerProviderBase, TProvider>);
m_provider = std::dynamic_pointer_cast<TProvider>(InputManager::instance().get_api_provider(TProvider::kAPIType, settings));
cemu_assert_debug(m_provider != nullptr);
}

View File

@ -42,8 +42,24 @@ DSUControllerProvider::~DSUControllerProvider()
if (m_running)
{
m_running = false;
m_writer_thread.join();
// wake up the reader thread by sending a packet to self
if (m_socketWakeupEndpoint.port() != 0)
{
boost::asio::io_context io_context;
boost::asio::ip::udp::socket socket(io_context);
boost::system::error_code ec;
socket.open(m_socketWakeupEndpoint.protocol(), ec);
if (!ec)
{
std::array<char, 1> data{};
socket.send_to(boost::asio::buffer(data), m_socketWakeupEndpoint, 0, ec);
}
else
cemuLog_log(LogType::Force, "DSUControllerProvider wakeup failed");
}
m_reader_thread.join();
m_writerJobs.push(nullptr); // wake up writer thread by pushing an empty message
m_writer_thread.join();
}
}
@ -84,14 +100,8 @@ bool DSUControllerProvider::connect()
m_socket.close();
m_socket.open(ip::udp::v4());
// set timeout for our threads to give a chance to exit
#if BOOST_OS_WINDOWS
m_socket.set_option(boost::asio::detail::socket_option::integer<SOL_SOCKET, SO_RCVTIMEO>{200});
#elif BOOST_OS_LINUX || BOOST_OS_MACOS
timeval timeout{.tv_usec = 200 * 1000};
setsockopt(m_socket.native_handle(), SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeval));
#endif
m_socket.bind(ip::udp::endpoint(ip::udp::v4(), 0));
m_socketWakeupEndpoint = ip::udp::endpoint(boost::asio::ip::address_v4::loopback(), m_socket.local_endpoint().port());
// reset data
m_state = {};
m_prev_state = {};
@ -189,19 +199,13 @@ uint32_t DSUControllerProvider::get_packet_index(uint8_t index) const
void DSUControllerProvider::request_version()
{
auto msg = std::make_unique<VersionRequest>(m_uid);
std::scoped_lock lock(m_writer_mutex);
m_writer_jobs.push(std::move(msg));
m_writer_cond.notify_one();
m_writerJobs.push(std::move(msg));
}
void DSUControllerProvider::request_pad_info()
{
auto msg = std::make_unique<ListPorts>(m_uid, 4, std::array<uint8_t, 4>{0, 1, 2, 3});
std::scoped_lock lock(m_writer_mutex);
m_writer_jobs.push(std::move(msg));
m_writer_cond.notify_one();
m_writerJobs.push(std::move(msg));
}
void DSUControllerProvider::request_pad_info(uint8_t index)
@ -210,19 +214,13 @@ void DSUControllerProvider::request_pad_info(uint8_t index)
return;
auto msg = std::make_unique<ListPorts>(m_uid, 1, std::array<uint8_t, 4>{index});
std::scoped_lock lock(m_writer_mutex);
m_writer_jobs.push(std::move(msg));
m_writer_cond.notify_one();
m_writerJobs.push(std::move(msg));
}
void DSUControllerProvider::request_pad_data()
{
auto msg = std::make_unique<DataRequest>(m_uid);
std::scoped_lock lock(m_writer_mutex);
m_writer_jobs.push(std::move(msg));
m_writer_cond.notify_one();
m_writerJobs.push(std::move(msg));
}
void DSUControllerProvider::request_pad_data(uint8_t index)
@ -231,10 +229,7 @@ void DSUControllerProvider::request_pad_data(uint8_t index)
return;
auto msg = std::make_unique<DataRequest>(m_uid, index);
std::scoped_lock lock(m_writer_mutex);
m_writer_jobs.push(std::move(msg));
m_writer_cond.notify_one();
m_writerJobs.push(std::move(msg));
}
MotionSample DSUControllerProvider::get_motion_sample(uint8_t index) const
@ -259,8 +254,11 @@ void DSUControllerProvider::reader_thread()
boost::asio::ip::udp::endpoint sender_endpoint;
boost::system::error_code ec{};
const size_t len = m_socket.receive_from(boost::asio::buffer(recv_buf), sender_endpoint, 0, ec);
if (!m_running.load(std::memory_order_relaxed))
break;
if (ec)
{
#ifdef DEBUG_DSU_CLIENT
printf(" DSUControllerProvider::ReaderThread: exception %s\n", ec.what());
#endif
@ -336,6 +334,7 @@ void DSUControllerProvider::reader_thread()
}
index = info->GetIndex();
cemu_assert(index < kMaxClients);
#ifdef DEBUG_DSU_CLIENT
printf(" DSUControllerProvider::ReaderThread: received PortInfo for index %d\n", index);
#endif
@ -359,6 +358,7 @@ void DSUControllerProvider::reader_thread()
}
index = rsp->GetIndex();
cemu_assert(index < kMaxClients);
#ifdef DEBUG_DSU_CLIENT
printf(" DSUControllerProvider::ReaderThread: received DataResponse for index %d\n", index);
#endif
@ -384,20 +384,10 @@ void DSUControllerProvider::writer_thread()
SetThreadName("DSU-writer");
while (m_running.load(std::memory_order_relaxed))
{
std::unique_lock lock(m_writer_mutex);
while (m_writer_jobs.empty())
{
if (m_writer_cond.wait_for(lock, std::chrono::milliseconds(250)) == std::cv_status::timeout)
{
if (!m_running.load(std::memory_order_relaxed))
return;
}
}
const auto msg = std::move(m_writer_jobs.front());
m_writer_jobs.pop();
lock.unlock();
std::unique_ptr<ClientMessage> msg = m_writerJobs.pop();
if (!m_running.load(std::memory_order_relaxed))
return;
cemu_assert_debug(msg.get());
#ifdef DEBUG_DSU_CLIENT
printf(" DSUControllerProvider::WriterThread: sending message: 0x%x (len: 0x%x)\n", (int)msg->GetMessageType(), msg->GetSize());
#endif
@ -457,4 +447,4 @@ DSUControllerProvider::ControllerState& DSUControllerProvider::ControllerState::
this->operator=(static_cast<const PortInfo&>(data_response));
data = data_response.GetData();
return *this;
}
}

View File

@ -4,6 +4,7 @@
#include "input/api/DSU/DSUMessages.h"
#include "input/api/ControllerProvider.h"
#include "util/helpers/ConcurrentQueue.h"
#include <boost/asio.hpp>
@ -87,7 +88,6 @@ public:
void request_pad_data();
void request_pad_data(uint8_t index);
private:
uint16 m_server_version = 0;
@ -98,20 +98,18 @@ private:
void writer_thread();
void integrate_motion(uint8_t index, const DataResponse& data_response);
std::mutex m_writer_mutex;
std::condition_variable m_writer_cond;
uint32 m_uid;
boost::asio::io_context m_io_service;
boost::asio::ip::udp::endpoint m_receiver_endpoint;
boost::asio::ip::udp::socket m_socket;
boost::asio::ip::udp::endpoint m_socketWakeupEndpoint;
std::array<ControllerState, kMaxClients> m_state{};
std::array<ControllerState, kMaxClients> m_prev_state{};
mutable std::array<std::mutex, kMaxClients> m_mutex;
mutable std::array<std::condition_variable, kMaxClients> m_wait_cond;
std::queue<std::unique_ptr<ClientMessage>> m_writer_jobs;
ConcurrentQueue<std::unique_ptr<ClientMessage>> m_writerJobs;
std::array<WiiUMotionHandler, kMaxClients> m_motion_handler;
std::array<uint64, kMaxClients> m_last_motion_timestamp{};

View File

@ -2,27 +2,30 @@
#include "input/api/SDL/SDLControllerProvider.h"
SDLController::SDLController(const SDL_JoystickGUID& guid, size_t guid_index)
SDLController::SDLController(const SDL_GUID& guid, size_t guid_index)
: base_type(fmt::format("{}_", guid_index), fmt::format("Controller {}", guid_index + 1)), m_guid_index(guid_index),
m_guid(guid)
{
char tmp[64];
SDL_JoystickGetGUIDString(m_guid, tmp, std::size(tmp));
SDL_GUIDToString(m_guid, tmp, std::size(tmp));
m_uuid += tmp;
}
SDLController::SDLController(const SDL_JoystickGUID& guid, size_t guid_index, std::string_view display_name)
SDLController::SDLController(const SDL_GUID& guid, size_t guid_index, std::string_view display_name)
: base_type(fmt::format("{}_", guid_index), display_name), m_guid_index(guid_index), m_guid(guid)
{
char tmp[64];
SDL_JoystickGetGUIDString(m_guid, tmp, std::size(tmp));
SDL_GUIDToString(m_guid, tmp, std::size(tmp));
m_uuid += tmp;
}
SDLController::~SDLController()
{
if (m_controller)
SDL_GameControllerClose(m_controller);
{
SDL_CloseGamepad(m_controller);
m_controller = nullptr;
}
}
bool SDLController::is_connected()
@ -33,8 +36,9 @@ bool SDLController::is_connected()
return false;
}
if (!SDL_GameControllerGetAttached(m_controller))
if (!SDL_GamepadConnected(m_controller))
{
SDL_CloseGamepad(m_controller);
m_controller = nullptr;
return false;
}
@ -45,49 +49,39 @@ bool SDLController::is_connected()
bool SDLController::connect()
{
if (is_connected())
{
return true;
}
m_has_rumble = false;
const auto index = m_provider->get_index(m_guid_index, m_guid);
std::scoped_lock lock(m_controller_mutex);
m_diid = SDL_JoystickGetDeviceInstanceID(index);
if (m_diid == -1)
int gamepad_count = 0;
SDL_JoystickID *gamepad_ids = SDL_GetGamepads(&gamepad_count);
if (!gamepad_ids || index < 0 || index >= gamepad_count)
return false;
m_controller = SDL_GameControllerOpen(index);
m_diid = gamepad_ids[index];
SDL_free(gamepad_ids);
m_controller = SDL_OpenGamepad(m_diid);
if (!m_controller)
return false;
if (const char* name = SDL_GameControllerName(m_controller))
if (const char* name = SDL_GetGamepadName(m_controller))
m_display_name = name;
for (int i = 0; i < SDL_CONTROLLER_BUTTON_MAX; ++i)
{
m_buttons[i] = SDL_GameControllerHasButton(m_controller, (SDL_GameControllerButton)i);
}
for (int i = 0; i < SDL_CONTROLLER_AXIS_MAX; ++i)
{
m_axis[i] = SDL_GameControllerHasAxis(m_controller, (SDL_GameControllerAxis)i);
}
if (SDL_GameControllerHasSensor(m_controller, SDL_SENSOR_ACCEL))
{
m_has_accel = true;
SDL_GameControllerSetSensorEnabled(m_controller, SDL_SENSOR_ACCEL, SDL_TRUE);
}
if (SDL_GameControllerHasSensor(m_controller, SDL_SENSOR_GYRO))
{
m_has_gyro = true;
SDL_GameControllerSetSensorEnabled(m_controller, SDL_SENSOR_GYRO, SDL_TRUE);
}
m_has_rumble = SDL_GameControllerRumble(m_controller, 0, 0, 0) == 0;
for (size_t i = 0; i < SDL_GAMEPAD_BUTTON_COUNT; ++i)
m_buttons[i] = SDL_GamepadHasButton(m_controller, (SDL_GamepadButton)i);
for (size_t i = 0; i < SDL_GAMEPAD_AXIS_COUNT; ++i)
m_axis[i] = SDL_GamepadHasAxis(m_controller, (SDL_GamepadAxis)i);
if (SDL_GamepadHasSensor(m_controller, SDL_SENSOR_ACCEL))
m_has_accel = SDL_SetGamepadSensorEnabled(m_controller, SDL_SENSOR_ACCEL, true);
if (SDL_GamepadHasSensor(m_controller, SDL_SENSOR_GYRO))
m_has_gyro = SDL_SetGamepadSensorEnabled(m_controller, SDL_SENSOR_GYRO, true);
m_has_rumble = SDL_RumbleGamepad(m_controller, 0, 0, 0);
return true;
}
@ -96,12 +90,9 @@ void SDLController::start_rumble()
std::scoped_lock lock(m_controller_mutex);
if (is_connected() && !m_has_rumble)
return;
if (m_settings.rumble <= 0)
return;
SDL_GameControllerRumble(m_controller, (Uint16)(m_settings.rumble * 0xFFFF), (Uint16)(m_settings.rumble * 0xFFFF),
5 * 1000);
SDL_RumbleGamepad(m_controller, (Uint16)(m_settings.rumble * 0xFFFF), (Uint16)(m_settings.rumble * 0xFFFF), 5 * 1000);
}
void SDLController::stop_rumble()
@ -109,59 +100,47 @@ void SDLController::stop_rumble()
std::scoped_lock lock(m_controller_mutex);
if (is_connected() && !m_has_rumble)
return;
SDL_GameControllerRumble(m_controller, 0, 0, 0);
SDL_RumbleGamepad(m_controller, 0, 0, 0);
}
MotionSample SDLController::get_motion_sample()
{
if (is_connected() && has_motion())
{
return m_provider->motion_sample(m_diid);
}
return {};
}
std::string SDLController::get_button_name(uint64 button) const
{
if (const char* name = SDL_GameControllerGetStringForButton((SDL_GameControllerButton)button))
if (const char* name = SDL_GetGamepadStringForButton((SDL_GamepadButton)button))
return name;
return base_type::get_button_name(button);
}
ControllerState SDLController::raw_state()
{
ControllerState result{};
std::scoped_lock lock(m_controller_mutex);
if (!is_connected())
return result;
for (int i = 0; i < SDL_CONTROLLER_BUTTON_MAX; ++i)
for (size_t i = 0; i < SDL_GAMEPAD_BUTTON_COUNT; ++i)
{
if (m_buttons[i] && SDL_GameControllerGetButton(m_controller, (SDL_GameControllerButton)i))
if (m_buttons[i] && SDL_GetGamepadButton(m_controller, (SDL_GamepadButton)i))
result.buttons.SetButtonState(i, true);
}
if (m_axis[SDL_CONTROLLER_AXIS_LEFTX])
result.axis.x = (float)SDL_GameControllerGetAxis(m_controller, SDL_CONTROLLER_AXIS_LEFTX) / 32767.0f;
if (m_axis[SDL_CONTROLLER_AXIS_LEFTY])
result.axis.y = (float)SDL_GameControllerGetAxis(m_controller, SDL_CONTROLLER_AXIS_LEFTY) / 32767.0f;
if (m_axis[SDL_CONTROLLER_AXIS_RIGHTX])
result.rotation.x = (float)SDL_GameControllerGetAxis(m_controller, SDL_CONTROLLER_AXIS_RIGHTX) / 32767.0f;
if (m_axis[SDL_CONTROLLER_AXIS_RIGHTY])
result.rotation.y = (float)SDL_GameControllerGetAxis(m_controller, SDL_CONTROLLER_AXIS_RIGHTY) / 32767.0f;
if (m_axis[SDL_CONTROLLER_AXIS_TRIGGERLEFT])
result.trigger.x = (float)SDL_GameControllerGetAxis(m_controller, SDL_CONTROLLER_AXIS_TRIGGERLEFT) / 32767.0f;
if (m_axis[SDL_CONTROLLER_AXIS_TRIGGERRIGHT])
result.trigger.y = (float)SDL_GameControllerGetAxis(m_controller, SDL_CONTROLLER_AXIS_TRIGGERRIGHT) / 32767.0f;
if (m_axis[SDL_GAMEPAD_AXIS_LEFTX])
result.axis.x = (float)SDL_GetGamepadAxis(m_controller, SDL_GAMEPAD_AXIS_LEFTX) / 32767.0f;
if (m_axis[SDL_GAMEPAD_AXIS_LEFTY])
result.axis.y = (float)SDL_GetGamepadAxis(m_controller, SDL_GAMEPAD_AXIS_LEFTY) / 32767.0f;
if (m_axis[SDL_GAMEPAD_AXIS_RIGHTX])
result.rotation.x = (float)SDL_GetGamepadAxis(m_controller, SDL_GAMEPAD_AXIS_RIGHTX) / 32767.0f;
if (m_axis[SDL_GAMEPAD_AXIS_RIGHTY])
result.rotation.y = (float)SDL_GetGamepadAxis(m_controller, SDL_GAMEPAD_AXIS_RIGHTY) / 32767.0f;
if (m_axis[SDL_GAMEPAD_AXIS_LEFT_TRIGGER])
result.trigger.x = (float)SDL_GetGamepadAxis(m_controller, SDL_GAMEPAD_AXIS_LEFT_TRIGGER) / 32767.0f;
if (m_axis[SDL_GAMEPAD_AXIS_RIGHT_TRIGGER])
result.trigger.y = (float)SDL_GetGamepadAxis(m_controller, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER) / 32767.0f;
return result;
}

View File

@ -3,13 +3,13 @@
#include "input/api/Controller.h"
#include "input/api/SDL/SDLControllerProvider.h"
#include <SDL2/SDL_gamecontroller.h>
#include <SDL3/SDL_gamepad.h>
class SDLController : public Controller<SDLControllerProvider>
{
public:
SDLController(const SDL_JoystickGUID& guid, size_t guid_index);
SDLController(const SDL_JoystickGUID& guid, size_t guid_index, std::string_view display_name);
SDLController(const SDL_GUID& guid, size_t guid_index);
SDLController(const SDL_GUID& guid, size_t guid_index, std::string_view display_name);
~SDLController() override;
@ -32,29 +32,29 @@ public:
MotionSample get_motion_sample() override;
std::string get_button_name(uint64 button) const override;
const SDL_JoystickGUID& get_guid() const { return m_guid; }
const SDL_GUID& get_guid() const { return m_guid; }
constexpr static SDL_JoystickGUID kLeftJoyCon{ 0x03, 0x00, 0x00, 0x00, 0x7e, 0x05, 0x00, 0x00, 0x06, 0x20, 0x00, 0x00, 0x00, 0x00,0x68 ,0x00 };
constexpr static SDL_JoystickGUID kRightJoyCon{ 0x03, 0x00, 0x00, 0x00, 0x7e, 0x05, 0x00, 0x00, 0x07, 0x20, 0x00, 0x00, 0x00, 0x00, 0x68, 0x00 };
constexpr static SDL_JoystickGUID kSwitchProController{ 0x03, 0x00, 0x00, 0x00, 0x7e, 0x05, 0x00, 0x00, 0x09, 0x20, 0x00, 0x00, 0x00, 0x00, 0x68, 0x00 };
constexpr static SDL_GUID kLeftJoyCon{ 0x03, 0x00, 0x00, 0x00, 0x7e, 0x05, 0x00, 0x00, 0x06, 0x20, 0x00, 0x00, 0x00, 0x00,0x68 ,0x00 };
constexpr static SDL_GUID kRightJoyCon{ 0x03, 0x00, 0x00, 0x00, 0x7e, 0x05, 0x00, 0x00, 0x07, 0x20, 0x00, 0x00, 0x00, 0x00, 0x68, 0x00 };
constexpr static SDL_GUID kSwitchProController{ 0x03, 0x00, 0x00, 0x00, 0x7e, 0x05, 0x00, 0x00, 0x09, 0x20, 0x00, 0x00, 0x00, 0x00, 0x68, 0x00 };
protected:
ControllerState raw_state() override;
private:
inline static SDL_JoystickGUID kEmptyGUID{};
inline static SDL_GUID kEmptyGUID{};
size_t m_guid_index;
SDL_JoystickGUID m_guid;
SDL_GUID m_guid;
std::recursive_mutex m_controller_mutex;
SDL_GameController* m_controller = nullptr;
SDL_Gamepad* m_controller = nullptr;
SDL_JoystickID m_diid = -1;
bool m_has_gyro = false;
bool m_has_accel = false;
bool m_has_rumble = false;
std::array<bool, SDL_CONTROLLER_BUTTON_MAX> m_buttons{};
std::array<bool, SDL_CONTROLLER_AXIS_MAX> m_axis{};
std::array<bool, SDL_GAMEPAD_BUTTON_COUNT> m_buttons{};
std::array<bool, SDL_GAMEPAD_AXIS_COUNT> m_axis{};
};

View File

@ -3,12 +3,12 @@
#include "input/api/SDL/SDLController.h"
#include "util/helpers/TempState.h"
#include <SDL2/SDL.h>
#include <SDL3/SDL.h>
#include <boost/functional/hash.hpp>
struct SDL_JoystickGUIDHash
{
std::size_t operator()(const SDL_JoystickGUID& guid) const
std::size_t operator()(const SDL_GUID& guid) const
{
return boost::hash_value(guid.data);
}
@ -16,229 +16,285 @@ struct SDL_JoystickGUIDHash
SDLControllerProvider::SDLControllerProvider()
{
SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1");
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4, "1");
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5, "1");
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, "1");
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1");
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_GAMECUBE, "1");
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_SWITCH, "1");
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, "1");
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_STADIA, "1");
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_STEAM, "1");
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_LUNA, "1");
if (SDL_Init(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER | SDL_INIT_HAPTIC | SDL_INIT_EVENTS) < 0)
throw std::runtime_error(fmt::format("couldn't initialize SDL: {}", SDL_GetError()));
if (SDL_GameControllerEventState(SDL_ENABLE) < 0) {
cemuLog_log(LogType::Force, "Couldn't enable SDL gamecontroller event polling: {}", SDL_GetError());
#if !BOOST_OS_MACOS
std::scoped_lock _l(s_mutex);
if (s_initCount.fetch_add(1) == 0)
{
s_running = true;
s_thread = std::thread(&SDLControllerProvider::event_thread, this);
}
m_running = true;
m_thread = std::thread(&SDLControllerProvider::event_thread, this);
#endif
}
SDLControllerProvider::~SDLControllerProvider()
{
if (m_running)
#if !BOOST_OS_MACOS
bool shutdownSDL = false;
{
m_running = false;
// wake the thread with a quit event if it's currently waiting for events
SDL_Event evt;
evt.type = SDL_QUIT;
SDL_PushEvent(&evt);
// wait until thread exited
m_thread.join();
std::scoped_lock _l(s_mutex);
if (s_initCount.fetch_sub(1) == 1)
{
cemu_assert_debug(s_running);
s_running = false;
shutdownSDL = true;
}
}
SDL_QuitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER | SDL_INIT_HAPTIC | SDL_INIT_EVENTS);
if (shutdownSDL)
{
// wake the thread with a quit event if it's currently waiting for events
SDL_Event evt;
SDL_zero(evt);
evt.type = SDL_EVENT_QUIT;
SDL_PushEvent(&evt);
if (s_thread.joinable())
{
s_thread.join();
}
}
#endif
}
std::vector<std::shared_ptr<ControllerBase>> SDLControllerProvider::get_controllers()
{
std::vector<std::shared_ptr<ControllerBase>> result;
std::unordered_map<SDL_JoystickGUID, size_t, SDL_JoystickGUIDHash> guid_counter;
std::unordered_map<SDL_GUID, size_t, SDL_JoystickGUIDHash> guid_counter;
TempState lock(SDL_LockJoysticks, SDL_UnlockJoysticks);
for (int i = 0; i < SDL_NumJoysticks(); ++i)
int gamepad_count = 0;
SDL_JoystickID *gamepad_ids = SDL_GetGamepads(&gamepad_count);
if (gamepad_ids)
{
if (SDL_JoystickGetDeviceType(i) == SDL_JOYSTICK_TYPE_GAMECONTROLLER)
for (size_t i = 0; i < gamepad_count; ++i)
{
const auto guid = SDL_JoystickGetDeviceGUID(i);
const auto guid = SDL_GetGamepadGUIDForID(gamepad_ids[i]);
const auto it = guid_counter.try_emplace(guid, 0);
if (auto* controller = SDL_GameControllerOpen(i))
{
const char* name = SDL_GameControllerName(controller);
if (const char* name = SDL_GetGamepadNameForID(gamepad_ids[i]))
result.emplace_back(std::make_shared<SDLController>(guid, it.first->second, name));
SDL_GameControllerClose(controller);
}
else
result.emplace_back(std::make_shared<SDLController>(guid, it.first->second));
++it.first->second;
}
SDL_free(gamepad_ids);
}
return result;
}
int SDLControllerProvider::get_index(size_t guid_index, const SDL_JoystickGUID& guid) const
int SDLControllerProvider::get_index(size_t guid_index, const SDL_GUID& guid) const
{
size_t index = 0;
int gamepad_count = 0;
TempState lock(SDL_LockJoysticks, SDL_UnlockJoysticks);
for (int i = 0; i < SDL_NumJoysticks(); ++i)
SDL_JoystickID *gamepad_ids = SDL_GetGamepads(&gamepad_count);
if (gamepad_ids)
{
if (SDL_JoystickGetDeviceType(i) == SDL_JOYSTICK_TYPE_GAMECONTROLLER)
for (size_t i = 0; i < gamepad_count; ++i)
{
if(guid == SDL_JoystickGetDeviceGUID(i))
if (guid == SDL_GetGamepadGUIDForID(gamepad_ids[i]))
{
if (index == guid_index)
{
SDL_free(gamepad_ids);
return i;
}
++index;
}
}
SDL_free(gamepad_ids);
}
return -1;
}
MotionSample SDLControllerProvider::motion_sample(int diid)
MotionSample SDLControllerProvider::motion_sample(SDL_JoystickID diid)
{
std::scoped_lock lock(m_motion_data_mtx[diid]);
return m_motion_data[diid];
std::shared_lock lock(s_mutex);
auto it = s_motion_states.find(diid);
return (it != s_motion_states.end()) ? it->second.data : MotionSample{};
}
void SDLControllerProvider::InitSDL()
{
SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1");
SDL_SetHint(SDL_HINT_JOYSTICK_ENHANCED_REPORTS, "1");
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4, "1");
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5, "1");
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_GAMECUBE, "1");
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_SWITCH, "1");
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_SWITCH2, "1");
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, "1");
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_STADIA, "1");
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_STEAM, "1");
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_LUNA, "1");
if (!SDL_InitSubSystem(SDL_INIT_GAMEPAD | SDL_INIT_HAPTIC))
{
throw std::runtime_error(fmt::format("couldn't initialize SDL: {}", SDL_GetError()));
}
SDL_SetGamepadEventsEnabled(true);
if (!SDL_GamepadEventsEnabled())
{
cemuLog_log(LogType::Force, "Couldn't enable SDL gamecontroller event polling: {}", SDL_GetError());
}
}
void SDLControllerProvider::ShutdownSDL()
{
SDL_QuitSubSystem(SDL_INIT_GAMEPAD | SDL_INIT_HAPTIC);
}
#if BOOST_OS_MACOS
void SDLControllerProvider::PumpSDLEvents()
{
SDL_Event event;
while (SDL_PollEvent(&event))
HandleSDLEvent(event);
}
#endif
void SDLControllerProvider::HandleSDLEvent(SDL_Event& event)
{
switch (event.type)
{
case SDL_EVENT_QUIT:
{
std::scoped_lock _l(s_mutex);
s_running = false;
break;
}
case SDL_EVENT_GAMEPAD_AXIS_MOTION: /**< Game controller axis motion */
{
break;
}
case SDL_EVENT_GAMEPAD_BUTTON_DOWN: /**< Game controller button pressed */
{
break;
}
case SDL_EVENT_GAMEPAD_BUTTON_UP: /**< Game controller button released */
{
break;
}
case SDL_EVENT_GAMEPAD_ADDED: /**< A new Game controller has been inserted into the system */
{
std::scoped_lock _l(s_mutex);
InputManager::instance().on_device_changed();
break;
}
case SDL_EVENT_GAMEPAD_REMOVED: /**< An opened Game controller has been removed */
{
std::scoped_lock _l(s_mutex);
InputManager::instance().on_device_changed();
s_motion_states.erase(event.gdevice.which);
break;
}
case SDL_EVENT_GAMEPAD_REMAPPED: /**< The controller mapping was updated */
{
break;
}
case SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN: /**< Game controller touchpad was touched */
{
break;
}
case SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION: /**< Game controller touchpad finger was moved */
{
break;
}
case SDL_EVENT_GAMEPAD_TOUCHPAD_UP: /**< Game controller touchpad finger was lifted */
{
break;
}
case SDL_EVENT_GAMEPAD_SENSOR_UPDATE: /**< Game controller sensor was updated */
{
SDL_JoystickID id = event.gsensor.which;
uint64_t ts = event.gsensor.timestamp;
std::scoped_lock _l(s_mutex);
auto& state = s_motion_states[id];
auto& tracking = state.tracking;
if (event.gsensor.sensor == SDL_SENSOR_ACCEL)
{
const auto dif = ts - tracking.lastTimestampAccel;
if (dif <= 0)
{
break;
}
if (dif >= 10000000000)
{
tracking.hasAcc = false;
tracking.hasGyro = false;
tracking.lastTimestampAccel = ts;
break;
}
tracking.lastTimestampAccel = ts;
tracking.acc[0] = -event.gsensor.data[0] / 9.81f;
tracking.acc[1] = -event.gsensor.data[1] / 9.81f;
tracking.acc[2] = -event.gsensor.data[2] / 9.81f;
tracking.hasAcc = true;
}
if (event.gsensor.sensor == SDL_SENSOR_GYRO)
{
const auto dif = ts - tracking.lastTimestampGyro;
if (dif <= 0)
{
break;
}
if (dif >= 10000000000)
{
tracking.hasAcc = false;
tracking.hasGyro = false;
tracking.lastTimestampGyro = ts;
break;
}
tracking.lastTimestampGyro = ts;
tracking.gyro[0] = event.gsensor.data[0];
tracking.gyro[1] = -event.gsensor.data[1];
tracking.gyro[2] = -event.gsensor.data[2];
tracking.hasGyro = true;
}
if (tracking.hasAcc && tracking.hasGyro)
{
auto ts = std::max(tracking.lastTimestampGyro, tracking.lastTimestampAccel);
if (ts > tracking.lastTimestampIntegrate)
{
const auto tsDif = ts - tracking.lastTimestampIntegrate;
tracking.lastTimestampIntegrate = ts;
float tsDifD = (float)tsDif / 1000000000.0f;
if (tsDifD >= 1.0f)
{
tsDifD = 1.0f;
}
state.handler.processMotionSample(tsDifD, tracking.gyro.x, tracking.gyro.y, tracking.gyro.z, tracking.acc.x, -tracking.acc.y, -tracking.acc.z);
state.data = state.handler.getMotionSample();
}
tracking.hasAcc = false;
tracking.hasGyro = false;
}
break;
}
}
}
void SDLControllerProvider::event_thread()
{
#if BOOST_OS_MACOS
cemu_assert(false);
#endif
SetThreadName("SDL_events");
while (m_running.load(std::memory_order_relaxed))
InitSDL();
while (s_running.load(std::memory_order_relaxed))
{
SDL_Event event{};
SDL_WaitEvent(&event);
switch (event.type)
{
case SDL_QUIT:
m_running = false;
return;
case SDL_CONTROLLERAXISMOTION: /**< Game controller axis motion */
{
break;
}
case SDL_CONTROLLERBUTTONDOWN: /**< Game controller button pressed */
{
break;
}
case SDL_CONTROLLERBUTTONUP: /**< Game controller button released */
{
break;
}
case SDL_CONTROLLERDEVICEADDED: /**< A new Game controller has been inserted into the system */
{
InputManager::instance().on_device_changed();
break;
}
case SDL_CONTROLLERDEVICEREMOVED: /**< An opened Game controller has been removed */
{
InputManager::instance().on_device_changed();
break;
}
case SDL_CONTROLLERDEVICEREMAPPED: /**< The controller mapping was updated */
{
break;
}
case SDL_CONTROLLERTOUCHPADDOWN: /**< Game controller touchpad was touched */
{
break;
}
case SDL_CONTROLLERTOUCHPADMOTION: /**< Game controller touchpad finger was moved */
{
break;
}
case SDL_CONTROLLERTOUCHPADUP: /**< Game controller touchpad finger was lifted */
{
break;
}
case SDL_CONTROLLERSENSORUPDATE: /**< Game controller sensor was updated */
{
const auto index = event.csensor.which;
const auto ts = event.csensor.timestamp;
auto& motionTracking = m_motion_tracking[index];
if (event.csensor.sensor == SDL_SENSOR_ACCEL)
{
const auto dif = ts - motionTracking.lastTimestampAccel;
if (dif <= 0)
break;
if (dif >= 10000)
{
motionTracking.hasAcc = false;
motionTracking.hasGyro = false;
motionTracking.lastTimestampAccel = ts;
break;
}
motionTracking.lastTimestampAccel = ts;
motionTracking.acc[0] = -event.csensor.data[0] / 9.81f;
motionTracking.acc[1] = -event.csensor.data[1] / 9.81f;
motionTracking.acc[2] = -event.csensor.data[2] / 9.81f;
motionTracking.hasAcc = true;
}
if (event.csensor.sensor == SDL_SENSOR_GYRO)
{
const auto dif = ts - motionTracking.lastTimestampGyro;
if (dif <= 0)
break;
if (dif >= 10000)
{
motionTracking.hasAcc = false;
motionTracking.hasGyro = false;
motionTracking.lastTimestampGyro = ts;
break;
}
motionTracking.lastTimestampGyro = ts;
motionTracking.gyro[0] = event.csensor.data[0];
motionTracking.gyro[1] = -event.csensor.data[1];
motionTracking.gyro[2] = -event.csensor.data[2];
motionTracking.hasGyro = true;
}
if (motionTracking.hasAcc && motionTracking.hasGyro)
{
auto ts = std::max(motionTracking.lastTimestampGyro, motionTracking.lastTimestampAccel);
if (ts > motionTracking.lastTimestampIntegrate)
{
const auto tsDif = ts - motionTracking.lastTimestampIntegrate;
motionTracking.lastTimestampIntegrate = ts;
float tsDifD = (float)tsDif / 1000.0f;
if (tsDifD >= 1.0f)
tsDifD = 1.0f;
m_motion_handler[index].processMotionSample(tsDifD, motionTracking.gyro.x, motionTracking.gyro.y, motionTracking.gyro.z, motionTracking.acc.x, -motionTracking.acc.y, -motionTracking.acc.z);
std::scoped_lock lock(m_motion_data_mtx[index]);
m_motion_data[index] = m_motion_handler[index].getMotionSample();
}
motionTracking.hasAcc = false;
motionTracking.hasGyro = false;
}
break;
}
}
HandleSDLEvent(event);
}
ShutdownSDL();
}

View File

@ -1,15 +1,11 @@
#pragma once
#include <SDL2/SDL_joystick.h>
#include <SDL3/SDL_joystick.h>
#include "input/motion/MotionHandler.h"
#include "input/api/ControllerProvider.h"
#ifndef HAS_SDL
#define HAS_SDL 1
#endif
static bool operator==(const SDL_JoystickGUID& g1, const SDL_JoystickGUID& g2)
static bool operator==(const SDL_GUID& g1, const SDL_GUID& g2)
{
return memcmp(&g1, &g2, sizeof(SDL_JoystickGUID)) == 0;
return memcmp(&g1, &g2, sizeof(SDL_GUID)) == 0;
}
class SDLControllerProvider : public ControllerProviderBase
@ -24,19 +20,30 @@ public:
std::vector<std::shared_ptr<ControllerBase>> get_controllers() override;
int get_index(size_t guid_index, const SDL_JoystickGUID& guid) const;
int get_index(size_t guid_index, const SDL_GUID& guid) const;
MotionSample motion_sample(int diid);
MotionSample motion_sample(SDL_JoystickID diid);
// exposed for manual event handling on macOS
#if BOOST_OS_MACOS
static void InitSDL();
static void ShutdownSDL();
static void PumpSDLEvents();
#endif
private:
void event_thread();
std::atomic_bool m_running = false;
std::thread m_thread;
static void HandleSDLEvent(union SDL_Event& event);
#if !BOOST_OS_MACOS
static void InitSDL();
static void ShutdownSDL();
#endif
std::array<WiiUMotionHandler, 8> m_motion_handler{};
std::array<MotionSample, 8> m_motion_data{};
std::array<std::mutex, 8> m_motion_data_mtx{};
// there is only one SDL instance, for this reason all of our state can be static
inline static std::atomic_int s_initCount{0};
inline static std::shared_mutex s_mutex;
inline static std::atomic_bool s_running = false;
inline static std::thread s_thread;
struct MotionInfoTracking
{
@ -49,6 +56,14 @@ private:
glm::vec3 acc{};
};
std::array<MotionInfoTracking, 8> m_motion_tracking{};
struct MotionState
{
WiiUMotionHandler handler;
MotionSample data;
MotionInfoTracking tracking;
MotionState() = default;
};
inline static std::unordered_map<SDL_JoystickID, MotionState> s_motion_states{};
};

View File

@ -25,9 +25,31 @@ WiimoteControllerProvider::~WiimoteControllerProvider()
if (m_running)
{
m_running = false;
m_writer_thread.join();
m_reader_thread.join();
m_connectionThread.join();
{
std::scoped_lock lock(m_writer_mutex);
m_writer_cond.notify_all();
}
{
std::scoped_lock lock(m_connectionMutex);
m_connectionCond.notify_all();
}
if (m_writer_thread.joinable())
{
m_writer_thread.join();
}
if (m_reader_thread.joinable())
{
m_reader_thread.join();
}
if (m_connectionThread.joinable())
{
m_connectionThread.join();
}
}
}
@ -169,7 +191,8 @@ void WiimoteControllerProvider::connectionThread()
m_connectedDevices.clear();
std::ranges::move(devices, std::back_inserter(m_connectedDevices));
}
std::this_thread::sleep_for(std::chrono::seconds(2));
std::unique_lock<std::mutex> lock(m_connectionMutex);
m_connectionCond.wait_for(lock, std::chrono::seconds(2));
}
}

View File

@ -80,6 +80,8 @@ private:
std::shared_mutex m_device_mutex;
std::thread m_connectionThread;
std::mutex m_connectionMutex;
std::condition_variable m_connectionCond;
std::vector<WiimoteDevicePtr> m_connectedDevices;
std::mutex m_connectedDeviceMutex;
struct Wiimote

View File

@ -1,4 +1,5 @@
#include "HidapiWiimote.h"
#include <SDL3/SDL.h>
#include <cwchar>
static constexpr uint16 WIIMOTE_VENDOR_ID = 0x057e;
@ -7,18 +8,18 @@ static constexpr uint16 WIIMOTE_MP_PRODUCT_ID = 0x0330;
static constexpr uint16 WIIMOTE_MAX_INPUT_REPORT_LENGTH = 22;
static constexpr auto PRO_CONTROLLER_NAME = L"Nintendo RVL-CNT-01-UC";
HidapiWiimote::HidapiWiimote(hid_device* dev, std::string_view path)
HidapiWiimote::HidapiWiimote(SDL_hid_device* dev, std::string_view path)
: m_handle(dev), m_path(path) {
}
bool HidapiWiimote::write_data(const std::vector<uint8> &data) {
return hid_write(m_handle, data.data(), data.size()) >= 0;
return SDL_hid_write(m_handle, data.data(), data.size()) >= 0;
}
std::optional<std::vector<uint8>> HidapiWiimote::read_data() {
std::array<uint8, WIIMOTE_MAX_INPUT_REPORT_LENGTH> read_data{};
const auto result = hid_read(m_handle, read_data.data(), WIIMOTE_MAX_INPUT_REPORT_LENGTH);
const auto result = SDL_hid_read(m_handle, read_data.data(), WIIMOTE_MAX_INPUT_REPORT_LENGTH);
if (result < 0)
return {};
return {{read_data.cbegin(), read_data.cbegin() + result}};
@ -26,24 +27,24 @@ std::optional<std::vector<uint8>> HidapiWiimote::read_data() {
std::vector<WiimoteDevicePtr> HidapiWiimote::get_devices() {
std::vector<WiimoteDevicePtr> wiimote_devices;
hid_init();
const auto device_enumeration = hid_enumerate(WIIMOTE_VENDOR_ID, 0x0);
SDL_hid_init();
const auto device_enumeration = SDL_hid_enumerate(WIIMOTE_VENDOR_ID, 0x0);
for (auto it = device_enumeration; it != nullptr; it = it->next){
if (it->product_id != WIIMOTE_PRODUCT_ID && it->product_id != WIIMOTE_MP_PRODUCT_ID)
continue;
if (std::wcscmp(it->product_string, PRO_CONTROLLER_NAME) == 0)
continue;
auto dev = hid_open_path(it->path);
auto dev = SDL_hid_open_path(it->path);
if (!dev){
cemuLog_logDebug(LogType::Force, "Unable to open Wiimote device at {}: {}", it->path, boost::nowide::narrow(hid_error(nullptr)));
cemuLog_logDebug(LogType::Force, "Unable to open Wiimote device at {}: {}", it->path, SDL_GetError());
}
else {
hid_set_nonblocking(dev, true);
SDL_hid_set_nonblocking(dev, true);
wiimote_devices.push_back(std::make_shared<HidapiWiimote>(dev, it->path));
}
}
hid_free_enumeration(device_enumeration);
SDL_hid_free_enumeration(device_enumeration);
return wiimote_devices;
}
@ -55,5 +56,5 @@ bool HidapiWiimote::operator==(const WiimoteDevice& rhs) const {
}
HidapiWiimote::~HidapiWiimote() {
hid_close(m_handle);
SDL_hid_close(m_handle);
}

View File

@ -1,11 +1,10 @@
#pragma once
#include <api/Wiimote/WiimoteDevice.h>
#include <hidapi.h>
class HidapiWiimote : public WiimoteDevice {
public:
HidapiWiimote(hid_device* dev, std::string_view path);
HidapiWiimote(struct SDL_hid_device* dev, std::string_view path);
~HidapiWiimote() override;
bool write_data(const std::vector<uint8> &data) override;
@ -15,7 +14,7 @@ public:
static std::vector<WiimoteDevicePtr> get_devices();
private:
hid_device* m_handle;
struct SDL_hid_device* m_handle;
const std::string m_path;
};

View File

@ -1,7 +1,9 @@
#include "input/emulated/ClassicController.h"
#include "input/api/Controller.h"
#ifdef HAS_SDL
#include "input/api/SDL/SDLController.h"
#endif
ClassicController::ClassicController(size_t player_index)
: WPADController(player_index, kDataFormat_CLASSIC)
@ -130,6 +132,7 @@ bool ClassicController::set_default_mapping(const std::shared_ptr<ControllerBase
std::vector<std::pair<uint64, uint64>> mapping;
switch (controller->api())
{
#ifdef HAS_SDL
case InputAPI::SDLController: {
const auto sdl_controller = std::static_pointer_cast<SDLController>(controller);
if (sdl_controller->get_guid() == SDLController::kLeftJoyCon)
@ -206,6 +209,7 @@ bool ClassicController::set_default_mapping(const std::shared_ptr<ControllerBase
};
}
}
#endif
case InputAPI::XInput:
{
mapping =

View File

@ -1,7 +1,9 @@
#include "input/emulated/ProController.h"
#include "input/api/Controller.h"
#ifdef HAS_SDL
#include "input/api/SDL/SDLController.h"
#endif
ProController::ProController(size_t player_index)
: WPADController(player_index, kDataFormat_URCC)
@ -135,6 +137,7 @@ bool ProController::set_default_mapping(const std::shared_ptr<ControllerBase>& c
std::vector<std::pair<uint64, uint64>> mapping;
switch (controller->api())
{
#ifdef HAS_SDL
case InputAPI::SDLController: {
const auto sdl_controller = std::static_pointer_cast<SDLController>(controller);
if (sdl_controller->get_guid() == SDLController::kLeftJoyCon)
@ -219,6 +222,7 @@ bool ProController::set_default_mapping(const std::shared_ptr<ControllerBase>& c
}
break;
}
#endif
case InputAPI::XInput:
{
mapping =

View File

@ -1,6 +1,8 @@
#include "input/emulated/VPADController.h"
#include "input/api/Controller.h"
#ifdef HAS_SDL
#include "input/api/SDL/SDLController.h"
#endif
#include "WindowSystem.h"
#include "input/InputManager.h"
#include "Cafe/HW/Latte/Core/Latte.h"
@ -510,6 +512,7 @@ bool VPADController::set_default_mapping(const std::shared_ptr<ControllerBase>&
std::vector<std::pair<uint64, uint64>> mapping;
switch (controller->api())
{
#ifdef HAS_SDL
case InputAPI::SDLController: {
const auto sdl_controller = std::static_pointer_cast<SDLController>(controller);
if (sdl_controller->get_guid() == SDLController::kLeftJoyCon)
@ -633,6 +636,7 @@ bool VPADController::set_default_mapping(const std::shared_ptr<ControllerBase>&
}
break;
}
#endif
case InputAPI::XInput:
{
mapping =

View File

@ -19,7 +19,6 @@
#include "util/helpers/helpers.h"
#include "config/ActiveSettings.h"
#include "Cafe/HW/Latte/Renderer/Vulkan/VsyncDriver.h"
#include "Cafe/IOSU/legacy/iosu_crypto.h"
#include "Cafe/OS/libs/vpad/vpad.h"
@ -30,8 +29,11 @@
#pragma comment(lib,"Dbghelp.lib")
#endif
#ifdef HAS_SDL
#define SDL_MAIN_HANDLED
#include <SDL.h>
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#endif
#if BOOST_OS_LINUX
#define _putenv(__s) putenv((char*)(__s))
@ -67,7 +69,8 @@ void _putenvSafe(const char* c)
void reconfigureGLDrivers()
{
// reconfigure GL drivers to store
#ifdef ENABLE_OPENGL
// reconfigure GL drivers to store
const fs::path nvCacheDir = ActiveSettings::GetCachePath("shaderCache/driver/nvidia/");
std::error_code err;
@ -83,13 +86,15 @@ void reconfigureGLDrivers()
_putenvSafe(nvCacheDirEnvOption.c_str());
#endif
_putenvSafe("__GL_SHADER_DISK_CACHE_SKIP_CLEANUP=1");
#endif
}
void reconfigureVkDrivers()
{
#ifdef ENABLE_VULKAN
_putenvSafe("DISABLE_LAYER_AMD_SWITCHABLE_GRAPHICS_1=1");
_putenvSafe("DISABLE_VK_LAYER_VALVE_steam_fossilize_1=1");
#endif
}
void WindowsInitCwd()
@ -234,7 +239,9 @@ int wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int
{
if (FAILED(CoInitializeEx(nullptr, COINIT_MULTITHREADED | COINIT_DISABLE_OLE1DDE)))
cemuLog_log(LogType::Force, "CoInitializeEx() failed");
#ifdef HAS_SDL
SDL_SetMainReady();
#endif
if (!LaunchSettings::HandleCommandline(lpCmdLine))
return 0;
WindowSystem::Create();
@ -246,7 +253,9 @@ int main(int argc, char* argv[])
{
if (FAILED(CoInitializeEx(nullptr, COINIT_MULTITHREADED | COINIT_DISABLE_OLE1DDE)))
cemuLog_log(LogType::Force, "CoInitializeEx() failed");
#ifdef HAS_SDL
SDL_SetMainReady();
#endif
if (!LaunchSettings::HandleCommandline(argc, argv))
return 0;
WindowSystem::Create();
@ -258,7 +267,7 @@ int main(int argc, char* argv[])
int BreathOfTheWildChildProcessMain();
int main(int argc, char *argv[])
{
#if BOOST_OS_LINUX
#if BOOST_OS_LINUX && defined(ENABLE_VULKAN)
if (getenv("CEMU_DETECT_RADV") != nullptr)
return BreathOfTheWildChildProcessMain();
#endif

View File

@ -1,40 +1,72 @@
#include "Cemu/Logging/CemuLogging.h"
#include <SDL2/SDL.h>
#ifdef HAS_SDL
#include <SDL3/SDL.h>
#endif
class ScreenSaver
{
public:
static void SetInhibit(bool inhibit)
{
// temporary workaround because feature crashes on macOS
#if BOOST_OS_MACOS
return;
#ifdef HAS_SDL
public:
static void SetInhibit(bool inhibit)
{
bool* inhibitArg = new bool(inhibit);
if (!SDL_RunOnMainThread(SetInhibitCallback, inhibitArg, false))
{
delete inhibitArg;
cemuLog_log(LogType::Force, "Failed to schedule screen saver logic on main thread: {}", SDL_GetError());
}
}
private:
static void SDLCALL SetInhibitCallback(void* userdata)
{
if (!userdata)
{
return;
}
auto* inhibitArg = static_cast<bool*>(userdata);
bool inhibit = *inhibitArg;
delete inhibitArg;
if (SDL_WasInit(SDL_INIT_VIDEO) == 0)
{
if (!SDL_InitSubSystem(SDL_INIT_VIDEO))
{
cemuLog_log(LogType::Force, "Could not disable screen saver (SDL video subsystem initialization error: {})", SDL_GetError());
return;
}
}
if (inhibit)
{
if (!SDL_DisableScreenSaver())
{
cemuLog_log(LogType::Force, "Could not disable screen saver: {}", SDL_GetError());
}
else if (SDL_ScreenSaverEnabled())
{
cemuLog_log(LogType::Force, "Could not verify if screen saver was disabled (`SDL_IsScreenSaverEnabled()` returned true)");
}
}
else
{
if (!SDL_EnableScreenSaver())
{
cemuLog_log(LogType::Force, "Could not enable screen saver: {}", SDL_GetError());
}
else if (!SDL_ScreenSaverEnabled())
{
cemuLog_log(LogType::Force, "Could not verify if screen saver was re-enabled (`SDL_IsScreenSaverEnabled()` returned false)");
}
}
}
#else
public:
static void SetInhibit(bool /*inhibit*/)
{
}
#endif
// Initialize video subsystem if necessary
if (SDL_WasInit(SDL_INIT_VIDEO) == 0)
{
int initErr = SDL_InitSubSystem(SDL_INIT_VIDEO);
if (initErr)
{
cemuLog_log(LogType::Force, "Could not disable screen saver (SDL video subsystem initialization error)");
}
}
// Toggle SDL's screen saver inhibition
if (inhibit)
{
SDL_DisableScreenSaver();
if (SDL_IsScreenSaverEnabled() == SDL_TRUE)
{
cemuLog_log(LogType::Force, "Could not verify if screen saver was disabled (`SDL_IsScreenSaverEnabled()` returned SDL_TRUE)");
}
}
else
{
SDL_EnableScreenSaver();
if (SDL_IsScreenSaverEnabled() == SDL_FALSE)
{
cemuLog_log(LogType::Force, "Could not verify if screen saver was re-enabled (`SDL_IsScreenSaverEnabled()` returned SDL_FALSE)");
}
}
};
};

View File

@ -62,7 +62,7 @@ public:
m_condVar.wait(mlock);
}
auto val = m_queue.front();
auto val = std::move(m_queue.front());
m_queue.pop();
return val;
}
@ -75,7 +75,7 @@ public:
m_condVar.wait(mlock);
}
item = m_queue.front();
item = std::move(m_queue.front());
m_queue.pop();
}

View File

@ -11,7 +11,10 @@
"default-features": false
},
"rapidjson",
"sdl2",
{
"name": "sdl3",
"default-features": false
},
"boost-tokenizer",
"boost-container",
"boost-program-options",
@ -29,7 +32,6 @@
"name": "fmt",
"version>=": "12.1.0"
},
"hidapi",
"libpng",
"glm",
{
@ -37,7 +39,13 @@
"default-features": false
},
"zstd",
"wxwidgets",
{
"name": "wxwidgets",
"default-features": false,
"features": [
"debug-support"
]
},
"openssl",
{
"name": "curl",
@ -67,8 +75,8 @@
"version": "15.1.0"
},
{
"name": "sdl2",
"version": "2.32.10"
"name": "sdl3",
"version": "3.4.2"
},
{
"name": "wxwidgets",