diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 9027616d..ba057215 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -210,3 +210,75 @@ jobs: with: name: UnleashedRecomp-Flatpak path: ./${{ env.FLATPAK_ID }}.flatpak + build-macos: + name: Build macOS + runs-on: macos-15 + strategy: + matrix: + arch: [ "arm64" ] + preset: ["macos-debug", "macos-release", "macos-relwithdebinfo"] + env: + CMAKE_PRESET: ${{ matrix.preset }} + + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Checkout Private Repository + uses: actions/checkout@v4 + with: + repository: ${{ secrets.ASSET_REPO }} + token: ${{ secrets.ASSET_REPO_TOKEN }} + path: ./private + + - name: Setup ccache + uses: hendrikmuhs/ccache-action@v1.2 + with: + key: ccache-${{ runner.os }}-${{ matrix.arch }}-${{ matrix.preset }} + + - name: Cache vcpkg + uses: actions/cache@v4 + with: + path: | + ./thirdparty/vcpkg/downloads + ./thirdparty/vcpkg/packages + key: vcpkg-${{ runner.os }}-${{ matrix.arch }}-${{ hashFiles('vcpkg.json') }} + restore-keys: | + vcpkg-${{ runner.os }}-${{ matrix.arch }}- + + - name: Install Dependencies (macOS) + run: | + brew install ninja + + - name: Cache ccache Directory + uses: actions/cache@v4 + with: + path: /tmp/ccache + key: ccache-${{ runner.os }}-${{ matrix.arch }}-${{ matrix.preset }} + + - name: Prepare Project + run: | + cp ./private/* ./UnleashedRecompLib/private + + - name: Configure Project + env: + CCACHE_DIR: /tmp/ccache + run: cmake . --preset ${{ env.CMAKE_PRESET }} -DCMAKE_OSX_ARCHITECTURES=${{ matrix.arch }} -DSDL2MIXER_VORBIS=VORBISFILE -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DCMAKE_C_COMPILER_LAUNCHER=ccache + + - name: Build Project + env: + CCACHE_DIR: /tmp/ccache + run: cmake --build ./out/build/${{ env.CMAKE_PRESET }} --target UnleashedRecomp + + - name: Pack Release + run: | + codesign --deep -fs - "./out/build/${{ env.CMAKE_PRESET }}/UnleashedRecomp/Unleashed Recompiled.app" + tar -czf UnleashedRecomp-macOS-${{ matrix.arch }}-${{ env.CMAKE_PRESET }}.tar.gz -C ./out/build/${{ env.CMAKE_PRESET }}/UnleashedRecomp "Unleashed Recompiled.app" + + - name: Upload Artifact + uses: actions/upload-artifact@v4 + with: + name: UnleashedRecomp-macOS-${{ matrix.arch }}-${{ env.CMAKE_PRESET }} + path: UnleashedRecomp-macOS-${{ matrix.arch }}-${{ env.CMAKE_PRESET }}.tar.gz diff --git a/.gitmodules b/.gitmodules index 367a42b0..d530b241 100644 --- a/.gitmodules +++ b/.gitmodules @@ -6,7 +6,7 @@ url = https://github.com/redorav/ddspp.git [submodule "tools/XenosRecomp"] path = tools/XenosRecomp - url = https://github.com/hedge-dev/XenosRecomp.git + url = https://github.com/IsaacMarovitz/XenosRecomp.git [submodule "UnleashedRecompResources"] path = UnleashedRecompResources url = https://github.com/hedge-dev/UnleashedRecompResources.git @@ -58,6 +58,12 @@ [submodule "thirdparty/json"] path = thirdparty/json url = https://github.com/nlohmann/json +[submodule "thirdparty/MoltenVK/MoltenVK"] + path = thirdparty/MoltenVK/MoltenVK + url = https://github.com/KhronosGroup/MoltenVK.git +[submodule "thirdparty/MoltenVK/SPIRV-Cross"] + path = thirdparty/MoltenVK/SPIRV-Cross + url = https://github.com/KhronosGroup/SPIRV-Cross.git [submodule "UnleashedRecomp/api"] path = UnleashedRecomp/api url = https://github.com/hedge-dev/SWA.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 9032d762..95cf36b7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,6 @@ if(NOT DEFINED ENV{VCPKG_ROOT}) message(FATAL_ERROR "VCPKG_ROOT is not defined!") endif() -include($ENV{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake) set(UNLEASHED_RECOMP_THIRDPARTY_ROOT ${CMAKE_SOURCE_DIR}/thirdparty) set(UNLEASHED_RECOMP_TOOLS_ROOT ${CMAKE_SOURCE_DIR}/tools) set(CMAKE_CXX_STANDARD 20) @@ -18,16 +17,32 @@ endif() set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") -# Target Sandy Bridge for all projects -add_compile_options( - -march=sandybridge -) +if (APPLE) + enable_language(OBJC OBJCXX) +endif() + +project("UnleashedRecomp-ALL") + +if (CMAKE_OSX_ARCHITECTURES) + set(UNLEASHED_RECOMP_ARCHITECTURE ${CMAKE_OSX_ARCHITECTURES}) +elseif(CMAKE_SYSTEM_PROCESSOR) + set(UNLEASHED_RECOMP_ARCHITECTURE ${CMAKE_SYSTEM_PROCESSOR}) +else() + set(UNLEASHED_RECOMP_ARCHITECTURE ${CMAKE_HOST_SYSTEM_PROCESSOR}) +endif() +string(TOLOWER "${UNLEASHED_RECOMP_ARCHITECTURE}" UNLEASHED_RECOMP_ARCHITECTURE) +message(STATUS "Detected architecture: ${UNLEASHED_RECOMP_ARCHITECTURE}") + +if (UNLEASHED_RECOMP_ARCHITECTURE STREQUAL "x86_64" OR UNLEASHED_RECOMP_ARCHITECTURE STREQUAL "amd64") + # Target Sandy Bridge for all projects + add_compile_options( + -march=sandybridge + ) +endif() add_subdirectory(${UNLEASHED_RECOMP_THIRDPARTY_ROOT}) add_subdirectory(${UNLEASHED_RECOMP_TOOLS_ROOT}) -project("UnleashedRecomp-ALL") - # Include sub-projects. add_subdirectory("UnleashedRecompLib") add_subdirectory("UnleashedRecomp") diff --git a/CMakePresets.json b/CMakePresets.json index 72710ec3..b64ca36a 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -113,6 +113,58 @@ "CMAKE_BUILD_TYPE": "Release", "CMAKE_INTERPROCEDURAL_OPTIMIZATION": true } + }, + { + "name": "macos-base", + "hidden": true, + "generator": "Ninja", + "binaryDir": "${sourceDir}/out/build/${presetName}", + "installDir": "${sourceDir}/out/install/${presetName}", + "cacheVariables": { + "CMAKE_TOOLCHAIN_FILE": { + "value": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake", + "type": "FILEPATH" + }, + "CMAKE_OSX_DEPLOYMENT_TARGET": "13.0" + }, + "environment": { + "VCPKG_ROOT": "${sourceDir}/thirdparty/vcpkg" + }, + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Darwin" + }, + "vendor": { + "microsoft.com/VisualStudioRemoteSettings/CMake/2.0": { + "remoteSourceRootDir": "$env{HOME}/.vs/$ms{projectDirName}" + } + } + }, + { + "name": "macos-debug", + "displayName": "macOS-Debug", + "inherits": "macos-base", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug" + } + }, + { + "name": "macos-relwithdebinfo", + "displayName": "macOS-RelWithDebInfo", + "inherits": "macos-base", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "RelWithDebInfo" + } + }, + { + "name": "macos-release", + "displayName": "macOS-Release", + "inherits": "macos-base", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release", + "CMAKE_INTERPROCEDURAL_OPTIMIZATION": true + } } ] } diff --git a/UnleashedRecomp/CMakeLists.txt b/UnleashedRecomp/CMakeLists.txt index 10863c02..17a06eae 100644 --- a/UnleashedRecomp/CMakeLists.txt +++ b/UnleashedRecomp/CMakeLists.txt @@ -4,6 +4,10 @@ if (WIN32) option(UNLEASHED_RECOMP_D3D12 "Add D3D12 support for rendering" ON) endif() +if (APPLE) + option(UNLEASHED_RECOMP_METAL "Add Metal support for rendering" ON) +endif() + if (CMAKE_SYSTEM_NAME MATCHES "Linux") option(UNLEASHED_RECOMP_FLATPAK "Configure the build for Flatpak compatibility." OFF) endif() @@ -97,6 +101,14 @@ elseif (CMAKE_SYSTEM_NAME MATCHES "Linux") "os/linux/user_linux.cpp" "os/linux/version_linux.cpp" ) +elseif (APPLE) + set(UNLEASHED_RECOMP_OS_CXX_SOURCES + "os/macos/logger_macos.cpp" + "os/macos/media_macos.cpp" + "os/macos/process_macos.cpp" + "os/macos/user_macos.cpp" + "os/macos/version_macos.cpp" + ) endif() set(UNLEASHED_RECOMP_CPU_CXX_SOURCES @@ -119,7 +131,7 @@ endif() set(UNLEASHED_RECOMP_APU_CXX_SOURCES "apu/audio.cpp" - "apu/embedded_player.cpp" + "apu/embedded_player.cpp" "apu/driver/sdl2_driver.cpp" ) @@ -149,16 +161,16 @@ set(UNLEASHED_RECOMP_PATCHES_CXX_SOURCES set(UNLEASHED_RECOMP_UI_CXX_SOURCES "ui/achievement_menu.cpp" - "ui/achievement_overlay.cpp" + "ui/achievement_overlay.cpp" "ui/black_bar.cpp" "ui/button_guide.cpp" "ui/fader.cpp" - "ui/game_window.cpp" + "ui/game_window.cpp" "ui/imgui_utils.cpp" "ui/installer_wizard.cpp" "ui/message_window.cpp" "ui/options_menu.cpp" - "ui/options_menu_thumbnails.cpp" + "ui/options_menu_thumbnails.cpp" "ui/tv_static.cpp" ) @@ -180,7 +192,7 @@ set(UNLEASHED_RECOMP_INSTALL_CXX_SOURCES set(UNLEASHED_RECOMP_USER_CXX_SOURCES "user/achievement_data.cpp" "user/achievement_manager.cpp" - "user/config.cpp" + "user/config.cpp" "user/registry.cpp" "user/paths.cpp" "user/persistent_data.cpp" @@ -250,11 +262,11 @@ set(UNLEASHED_RECOMP_CXX_SOURCES ${UNLEASHED_RECOMP_USER_CXX_SOURCES} ${UNLEASHED_RECOMP_MOD_CXX_SOURCES} ${UNLEASHED_RECOMP_THIRDPARTY_SOURCES} -) +) -include("version.cmake") - -set(VERSION_TXT "${PROJECT_SOURCE_DIR}/res/version.txt") +include("version.cmake") + +set(VERSION_TXT "${PROJECT_SOURCE_DIR}/res/version.txt") # Only show Git info and build type if not Release. set(SHOW_GIT_INFO_AND_BUILD_TYPE 0) @@ -262,6 +274,10 @@ if (NOT ${CMAKE_BUILD_TYPE} MATCHES "Release") set(SHOW_GIT_INFO_AND_BUILD_TYPE 1) endif() +if (UNLEASHED_RECOMP_METAL) + set(XCRUN_TOOL "/usr/bin/xcrun") +endif() + GenerateVersionSources( OUTPUT_DIR ${PROJECT_SOURCE_DIR} VERSION_TXT ${VERSION_TXT} @@ -270,43 +286,85 @@ GenerateVersionSources( BUILD_TYPE ${CMAKE_BUILD_TYPE} SHOW_GIT_INFO ${SHOW_GIT_INFO_AND_BUILD_TYPE} SHOW_BUILD_TYPE ${SHOW_GIT_INFO_AND_BUILD_TYPE} -) +) if (WIN32) - # Create binary version number for Win32 integer attributes. - CreateVersionString( - VERSION_TXT ${VERSION_TXT} - OUTPUT_CSV 1 - OUTPUT_VAR WIN32_VERSION_BINARY - ) - - # Create string version number for Win32 detailed attributes. - CreateVersionString( - VERSION_TXT ${VERSION_TXT} - BUILD_TYPE ${CMAKE_BUILD_TYPE} - SHOW_GIT_INFO ${SHOW_GIT_INFO_AND_BUILD_TYPE} - SHOW_BUILD_TYPE ${SHOW_GIT_INFO_AND_BUILD_TYPE} - OUTPUT_VAR WIN32_VERSION_STRING - ) - + # Create binary version number for Win32 integer attributes. + CreateVersionString( + VERSION_TXT ${VERSION_TXT} + OUTPUT_CSV 1 + OUTPUT_VAR WIN32_VERSION_BINARY + ) + + # Create string version number for Win32 detailed attributes. + CreateVersionString( + VERSION_TXT ${VERSION_TXT} + BUILD_TYPE ${CMAKE_BUILD_TYPE} + SHOW_GIT_INFO ${SHOW_GIT_INFO_AND_BUILD_TYPE} + SHOW_BUILD_TYPE ${SHOW_GIT_INFO_AND_BUILD_TYPE} + OUTPUT_VAR WIN32_VERSION_STRING + ) + # Set Win32 icon path. - set(WIN32_ICON_PATH "${PROJECT_SOURCE_DIR}/../UnleashedRecompResources/images/game_icon.ico") + set(WIN32_ICON_PATH "${PROJECT_SOURCE_DIR}/../UnleashedRecompResources/images/game_icon.ico") configure_file("res/win32/res.rc.template" "${CMAKE_BINARY_DIR}/res.rc" @ONLY) - add_executable(UnleashedRecomp ${UNLEASHED_RECOMP_CXX_SOURCES} "${CMAKE_BINARY_DIR}/res.rc") - - # Hide console for release configurations. - if (${CMAKE_BUILD_TYPE} MATCHES "Release") - target_link_options(UnleashedRecomp PRIVATE "/SUBSYSTEM:WINDOWS" "/ENTRY:mainCRTStartup") + add_executable(UnleashedRecomp ${UNLEASHED_RECOMP_CXX_SOURCES} "${CMAKE_BINARY_DIR}/res.rc") + + # Hide console for release configurations. + if (${CMAKE_BUILD_TYPE} MATCHES "Release") + target_link_options(UnleashedRecomp PRIVATE "/SUBSYSTEM:WINDOWS" "/ENTRY:mainCRTStartup") endif() +elseif (APPLE) + # Create version number for app bundle. + CreateVersionString( + VERSION_TXT ${VERSION_TXT} + OUTPUT_VAR MACOS_BUNDLE_VERSION + ) + + add_executable(UnleashedRecomp MACOSX_BUNDLE + ${UNLEASHED_RECOMP_CXX_SOURCES} + res/macos/game_icon.icns + ) + set_source_files_properties(res/macos/game_icon.icns PROPERTIES + MACOSX_PACKAGE_LOCATION Resources) + set_target_properties(UnleashedRecomp PROPERTIES + OUTPUT_NAME "Unleashed Recompiled" + MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/res/macos/MacOSXBundleInfo.plist.in + MACOSX_BUNDLE_GUI_IDENTIFIER hedge-dev.UnleashedRecomp + MACOSX_BUNDLE_BUNDLE_NAME "Unleashed Recompiled" + MACOSX_BUNDLE_BUNDLE_VERSION ${MACOS_BUNDLE_VERSION} + MACOSX_BUNDLE_SHORT_VERSION_STRING ${MACOS_BUNDLE_VERSION} + MACOSX_BUNDLE_ICON_FILE "game_icon.icns" + ) + + # Linking with MoltenVK directly would prevent using the system Vulkan loader to load with debug layers. + # Instead, copy the MoltenVK dylib to the app bundle along with an ICD file for the loader to find it. + # In the event the loader is not installed, the MoltenVK dylib can still be picked up directly in the app bundle. + set(MVK_ICD "${UNLEASHED_RECOMP_THIRDPARTY_ROOT}/MoltenVK/MoltenVK_icd.json") + target_sources(UnleashedRecomp PRIVATE ${MVK_ICD}) + set_source_files_properties(${MVK_ICD} PROPERTIES MACOSX_PACKAGE_LOCATION Resources/vulkan/icd.d) + + # Unfortunately using the MoltenVK target output as a resource file does not quite create the correct + # dependency chain, so we need to resolve the paths manually and create a copy target dependency chain. + set(MVK_DYLIB_SRC "${CMAKE_BINARY_DIR}/thirdparty/MoltenVK/libMoltenVK.dylib") + set(MVK_DYLIB_DST "${CMAKE_CURRENT_BINARY_DIR}/Unleashed Recompiled.app/Contents/Frameworks/libMoltenVK.dylib") + add_custom_command( + OUTPUT ${MVK_DYLIB_DST} + DEPENDS ${MVK_DYLIB_SRC} + COMMAND cmake -E copy ${MVK_DYLIB_SRC} ${MVK_DYLIB_DST}) + add_custom_target(CopyMoltenVK DEPENDS ${MVK_DYLIB_DST}) + add_dependencies(CopyMoltenVK MoltenVK) + add_dependencies(UnleashedRecomp CopyMoltenVK) + set_property(TARGET UnleashedRecomp APPEND PROPERTY BUILD_RPATH "@executable_path/../Frameworks") else() add_executable(UnleashedRecomp ${UNLEASHED_RECOMP_CXX_SOURCES}) endif() if (UNLEASHED_RECOMP_FLATPAK) - target_compile_definitions(UnleashedRecomp PRIVATE - "UNLEASHED_RECOMP_FLATPAK" - "GAME_INSTALL_DIRECTORY=\"/var/data\"" + target_compile_definitions(UnleashedRecomp PRIVATE + "UNLEASHED_RECOMP_FLATPAK" + "GAME_INSTALL_DIRECTORY=\"/var/data\"" ) endif() @@ -320,23 +378,25 @@ if (UNLEASHED_RECOMP_D3D12) ) endif() +if (UNLEASHED_RECOMP_METAL) + target_compile_definitions(UnleashedRecomp PRIVATE UNLEASHED_RECOMP_METAL) +endif() + if (CMAKE_SYSTEM_NAME MATCHES "Linux") target_compile_definitions(UnleashedRecomp PRIVATE SDL_VULKAN_ENABLED) endif() -find_package(directx-dxc REQUIRED) find_package(CURL REQUIRED) if (UNLEASHED_RECOMP_D3D12) file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/D3D12) add_custom_command(TARGET UnleashedRecomp POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy $ ${CMAKE_CURRENT_BINARY_DIR}/D3D12 - COMMAND ${CMAKE_COMMAND} -E copy $ ${CMAKE_CURRENT_BINARY_DIR}/D3D12 - COMMAND_EXPAND_LISTS + COMMAND ${CMAKE_COMMAND} -E copy $ $/D3D12 + COMMAND ${CMAKE_COMMAND} -E copy $ $/D3D12 + COMMAND ${CMAKE_COMMAND} -E copy $ $ + COMMAND ${CMAKE_COMMAND} -E copy $ $ + COMMAND_EXPAND_LISTS ) - - find_file(DIRECTX_DXIL_LIBRARY "dxil.dll") - file(COPY ${DIRECTX_DXIL_LIBRARY} DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) target_link_libraries(UnleashedRecomp PRIVATE Microsoft::DirectX-Headers @@ -348,18 +408,16 @@ if (UNLEASHED_RECOMP_D3D12) ) endif() -file(CHMOD ${DIRECTX_DXC_TOOL} PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE) - if (WIN32) target_link_libraries(UnleashedRecomp PRIVATE - comctl32 + comctl32 dwmapi - ntdll + ntdll Shcore Synchronization winmm ) -endif() +endif() target_link_libraries(UnleashedRecomp PRIVATE fmt::fmt @@ -391,35 +449,50 @@ endif() target_precompile_headers(UnleashedRecomp PUBLIC ${UNLEASHED_RECOMP_PRECOMPILED_HEADERS}) function(compile_shader FILE_PATH TARGET_NAME) - set(FILE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/gpu/shader/${FILE_PATH}.hlsl) - cmake_path(GET FILE_PATH STEM VARIABLE_NAME) + set(HLSL_FILE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/gpu/shader/hlsl/${FILE_PATH}.hlsl) + set(MSL_FILE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/gpu/shader/msl/${FILE_PATH}.metal) + cmake_path(GET HLSL_FILE_PATH STEM HLSL_NAME) + cmake_path(GET MSL_FILE_PATH STEM MSL_NAME) + if (UNLEASHED_RECOMP_METAL) + add_custom_command( + OUTPUT ${MSL_FILE_PATH}.ir + COMMAND ${XCRUN_TOOL} -sdk macosx metal -o ${MSL_FILE_PATH}.ir -c ${MSL_FILE_PATH} -D__air__ -frecord-sources -gline-tables-only + DEPENDS ${MSL_FILE_PATH} + ) + add_custom_command( + OUTPUT ${MSL_FILE_PATH}.metallib + COMMAND ${XCRUN_TOOL} -sdk macosx metallib -o ${MSL_FILE_PATH}.metallib ${MSL_FILE_PATH}.ir + DEPENDS ${MSL_FILE_PATH}.ir + ) + BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${MSL_FILE_PATH}.metallib" DEST_FILE "${MSL_FILE_PATH}.metallib" ARRAY_NAME "g_${MSL_NAME}_air") + endif() if (UNLEASHED_RECOMP_D3D12) add_custom_command( - OUTPUT ${FILE_PATH}.dxil.h - COMMAND ${DIRECTX_DXC_TOOL} -T ${TARGET_NAME} -HV 2021 -all-resources-bound -Wno-ignored-attributes -Fh ${FILE_PATH}.dxil.h ${FILE_PATH} -Vn g_${VARIABLE_NAME}_dxil - DEPENDS ${FILE_PATH} + OUTPUT ${HLSL_FILE_PATH}.dxil.h + COMMAND ${DIRECTX_DXC_TOOL} -T ${TARGET_NAME} -HV 2021 -all-resources-bound -Wno-ignored-attributes -Fh ${HLSL_FILE_PATH}.dxil.h ${HLSL_FILE_PATH} -Vn g_${HLSL_NAME}_dxil + DEPENDS ${HLSL_FILE_PATH} ) - target_sources(UnleashedRecomp PRIVATE ${FILE_PATH}.dxil.h) + target_sources(UnleashedRecomp PRIVATE ${HLSL_FILE_PATH}.dxil.h) endif() add_custom_command( - OUTPUT ${FILE_PATH}.spirv.h - COMMAND ${DIRECTX_DXC_TOOL} -T ${TARGET_NAME} -HV 2021 -all-resources-bound -spirv -fvk-use-dx-layout ${ARGN} -Fh ${FILE_PATH}.spirv.h ${FILE_PATH} -Vn g_${VARIABLE_NAME}_spirv - DEPENDS ${FILE_PATH} + OUTPUT ${HLSL_FILE_PATH}.spirv.h + COMMAND ${DIRECTX_DXC_TOOL} -T ${TARGET_NAME} -HV 2021 -all-resources-bound -spirv -fvk-use-dx-layout ${ARGN} -Fh ${HLSL_FILE_PATH}.spirv.h ${HLSL_FILE_PATH} -Vn g_${HLSL_NAME}_spirv + DEPENDS ${HLSL_FILE_PATH} ) - target_sources(UnleashedRecomp PRIVATE ${FILE_PATH}.spirv.h) + target_sources(UnleashedRecomp PRIVATE ${HLSL_FILE_PATH}.spirv.h) endfunction() function(compile_vertex_shader FILE_PATH) - compile_shader(${FILE_PATH} vs_6_0 -fvk-invert-y) + compile_shader(${FILE_PATH} vs_6_0 -fvk-invert-y -DUNLEASHED_RECOMP) endfunction() function(compile_pixel_shader FILE_PATH) - compile_shader(${FILE_PATH} ps_6_0) + compile_shader(${FILE_PATH} ps_6_0 -DUNLEASHED_RECOMP) endfunction() -compile_pixel_shader(blend_color_alpha_ps) -compile_vertex_shader(copy_vs) -compile_pixel_shader(copy_color_ps) +compile_pixel_shader(blend_color_alpha_ps) +compile_vertex_shader(copy_vs) +compile_pixel_shader(copy_color_ps) compile_pixel_shader(copy_depth_ps) compile_pixel_shader(csd_filter_ps) compile_vertex_shader(csd_no_tex_vs) @@ -433,7 +506,7 @@ compile_pixel_shader(gamma_correction_ps) compile_pixel_shader(imgui_ps) compile_vertex_shader(imgui_vs) compile_pixel_shader(movie_ps) -compile_vertex_shader(movie_vs) +compile_vertex_shader(movie_vs) compile_pixel_shader(resolve_msaa_color_2x) compile_pixel_shader(resolve_msaa_color_4x) compile_pixel_shader(resolve_msaa_color_8x) @@ -443,7 +516,7 @@ compile_pixel_shader(resolve_msaa_depth_8x) set(RESOURCES_SOURCE_PATH "${PROJECT_SOURCE_DIR}/../UnleashedRecompResources") set(RESOURCES_OUTPUT_PATH "${PROJECT_SOURCE_DIR}/res") - + ## Miscellaneous ## BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/bc_diff/button_bc_diff.bin" DEST_FILE "${RESOURCES_OUTPUT_PATH}/bc_diff/button_bc_diff.bin" ARRAY_NAME "g_button_bc_diff" COMPRESSION_TYPE "zstd") BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/font/im_font_atlas.bin" DEST_FILE "${RESOURCES_OUTPUT_PATH}/font/im_font_atlas.bin" ARRAY_NAME "g_im_font_atlas" COMPRESSION_TYPE "zstd") @@ -455,7 +528,7 @@ BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/co BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/common/kbm.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/common/kbm.dds" ARRAY_NAME "g_kbm" COMPRESSION_TYPE "zstd") BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/common/select.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/common/select.dds" ARRAY_NAME "g_select" COMPRESSION_TYPE "zstd") BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/common/light.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/common/light.dds" ARRAY_NAME "g_light" COMPRESSION_TYPE "zstd") - + ## Installer ## BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/installer/arrow_circle.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/installer/arrow_circle.dds" ARRAY_NAME "g_arrow_circle" COMPRESSION_TYPE "zstd") BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/installer/install_001.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/installer/install_001.dds" ARRAY_NAME "g_install_001" COMPRESSION_TYPE "zstd") @@ -467,23 +540,23 @@ BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/in BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/installer/install_007.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/installer/install_007.dds" ARRAY_NAME "g_install_007" COMPRESSION_TYPE "zstd") BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/installer/install_008.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/installer/install_008.dds" ARRAY_NAME "g_install_008" COMPRESSION_TYPE "zstd") BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/installer/miles_electric_icon.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/installer/miles_electric_icon.dds" ARRAY_NAME "g_miles_electric_icon" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/installer/pulse_install.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/installer/pulse_install.dds" ARRAY_NAME "g_pulse_install" COMPRESSION_TYPE "zstd") - +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/installer/pulse_install.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/installer/pulse_install.dds" ARRAY_NAME "g_pulse_install" COMPRESSION_TYPE "zstd") + ## Options Menu ## BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/achievement_notifications.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/achievement_notifications.dds" ARRAY_NAME "g_achievement_notifications" COMPRESSION_TYPE "zstd") BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/allow_background_input_ps.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/allow_background_input_ps.dds" ARRAY_NAME "g_allow_background_input_ps" COMPRESSION_TYPE "zstd") BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/allow_background_input_xb.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/allow_background_input_xb.dds" ARRAY_NAME "g_allow_background_input_xb" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/antialiasing_none.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/antialiasing_none.dds" ARRAY_NAME "g_antialiasing_none" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/antialiasing_2x.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/antialiasing_2x.dds" ARRAY_NAME "g_antialiasing_2x" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/antialiasing_4x.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/antialiasing_4x.dds" ARRAY_NAME "g_antialiasing_4x" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/antialiasing_none.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/antialiasing_none.dds" ARRAY_NAME "g_antialiasing_none" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/antialiasing_2x.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/antialiasing_2x.dds" ARRAY_NAME "g_antialiasing_2x" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/antialiasing_4x.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/antialiasing_4x.dds" ARRAY_NAME "g_antialiasing_4x" COMPRESSION_TYPE "zstd") BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/antialiasing_8x.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/antialiasing_8x.dds" ARRAY_NAME "g_antialiasing_8x" COMPRESSION_TYPE "zstd") BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/aspect_ratio.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/aspect_ratio.dds" ARRAY_NAME "g_aspect_ratio" COMPRESSION_TYPE "zstd") BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/battle_theme.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/battle_theme.dds" ARRAY_NAME "g_battle_theme" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/brightness.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/brightness.dds" ARRAY_NAME "g_brightness" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/channel_stereo.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/channel_stereo.dds" ARRAY_NAME "g_channel_stereo" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/channel_surround.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/channel_surround.dds" ARRAY_NAME "g_channel_surround" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/brightness.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/brightness.dds" ARRAY_NAME "g_brightness" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/channel_stereo.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/channel_stereo.dds" ARRAY_NAME "g_channel_stereo" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/channel_surround.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/channel_surround.dds" ARRAY_NAME "g_channel_surround" COMPRESSION_TYPE "zstd") BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/control_tutorial_ps.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/control_tutorial_ps.dds" ARRAY_NAME "g_control_tutorial_ps" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/control_tutorial_xb.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/control_tutorial_xb.dds" ARRAY_NAME "g_control_tutorial_xb" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/control_tutorial_xb.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/control_tutorial_xb.dds" ARRAY_NAME "g_control_tutorial_xb" COMPRESSION_TYPE "zstd") BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/controller_icons.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/controller_icons.dds" ARRAY_NAME "g_controller_icons" COMPRESSION_TYPE "zstd") BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/default.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/default.dds" ARRAY_NAME "g_default" COMPRESSION_TYPE "zstd") BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/effects_volume.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/effects_volume.dds" ARRAY_NAME "g_effects_volume" COMPRESSION_TYPE "zstd") @@ -499,7 +572,7 @@ BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/op BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/motion_blur_off.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/motion_blur_off.dds" ARRAY_NAME "g_motion_blur_off" COMPRESSION_TYPE "zstd") BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/motion_blur_original.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/motion_blur_original.dds" ARRAY_NAME "g_motion_blur_original" COMPRESSION_TYPE "zstd") BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/motion_blur_enhanced.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/motion_blur_enhanced.dds" ARRAY_NAME "g_motion_blur_enhanced" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/movie_scale_fit.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/movie_scale_fit.dds" ARRAY_NAME "g_movie_scale_fit" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/movie_scale_fit.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/movie_scale_fit.dds" ARRAY_NAME "g_movie_scale_fit" COMPRESSION_TYPE "zstd") BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/movie_scale_fill.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/movie_scale_fill.dds" ARRAY_NAME "g_movie_scale_fill" COMPRESSION_TYPE "zstd") BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/music_attenuation.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/music_attenuation.dds" ARRAY_NAME "g_music_attenuation" COMPRESSION_TYPE "zstd") BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/music_volume.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/music_volume.dds" ARRAY_NAME "g_music_volume" COMPRESSION_TYPE "zstd") @@ -508,34 +581,34 @@ BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/op BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/shadow_resolution_x2048.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/shadow_resolution_x2048.dds" ARRAY_NAME "g_shadow_resolution_x2048" COMPRESSION_TYPE "zstd") BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/shadow_resolution_x4096.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/shadow_resolution_x4096.dds" ARRAY_NAME "g_shadow_resolution_x4096" COMPRESSION_TYPE "zstd") BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/shadow_resolution_x8192.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/shadow_resolution_x8192.dds" ARRAY_NAME "g_shadow_resolution_x8192" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/time_transition_ps.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/time_transition_ps.dds" ARRAY_NAME "g_time_of_day_transition_playstation" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/time_transition_ps.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/time_transition_ps.dds" ARRAY_NAME "g_time_of_day_transition_playstation" COMPRESSION_TYPE "zstd") BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/time_transition_xb.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/time_transition_xb.dds" ARRAY_NAME "g_time_of_day_transition_xbox" COMPRESSION_TYPE "zstd") BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/transparency_antialiasing_false.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/transparency_antialiasing_false.dds" ARRAY_NAME "g_transparency_antialiasing_false" COMPRESSION_TYPE "zstd") BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/transparency_antialiasing_true.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/transparency_antialiasing_true.dds" ARRAY_NAME "g_transparency_antialiasing_true" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/ui_alignment_centre.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/ui_alignment_centre.dds" ARRAY_NAME "g_ui_alignment_centre" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/ui_alignment_centre.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/ui_alignment_centre.dds" ARRAY_NAME "g_ui_alignment_centre" COMPRESSION_TYPE "zstd") BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/ui_alignment_edge.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/ui_alignment_edge.dds" ARRAY_NAME "g_ui_alignment_edge" COMPRESSION_TYPE "zstd") BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/vertical_camera.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/vertical_camera.dds" ARRAY_NAME "g_vertical_camera" COMPRESSION_TYPE "zstd") BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/voice_language.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/voice_language.dds" ARRAY_NAME "g_voice_language" COMPRESSION_TYPE "zstd") BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/vibration_ps.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/vibration_ps.dds" ARRAY_NAME "g_vibration_ps" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/vibration_xb.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/vibration_xb.dds" ARRAY_NAME "g_vibration_xb" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/vsync_on.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/vsync_on.dds" ARRAY_NAME "g_vsync_on" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/vibration_xb.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/vibration_xb.dds" ARRAY_NAME "g_vibration_xb" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/vsync_on.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/vsync_on.dds" ARRAY_NAME "g_vsync_on" COMPRESSION_TYPE "zstd") BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/vsync_off.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/vsync_off.dds" ARRAY_NAME "g_vsync_off" COMPRESSION_TYPE "zstd") BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/window_size.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/window_size.dds" ARRAY_NAME "g_window_size" COMPRESSION_TYPE "zstd") BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/xbox_color_correction.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/xbox_color_correction.dds" ARRAY_NAME "g_xbox_color_correction" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/miles_electric.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/miles_electric.dds" ARRAY_NAME "g_miles_electric" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/options_static.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/options_static.dds" ARRAY_NAME "g_options_static" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/miles_electric.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/miles_electric.dds" ARRAY_NAME "g_miles_electric" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/options_static.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/options_static.dds" ARRAY_NAME "g_options_static" COMPRESSION_TYPE "zstd") BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/options_static_flash.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/options_static_flash.dds" ARRAY_NAME "g_options_static_flash" COMPRESSION_TYPE "zstd") - + ## Game Icon ## BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/game_icon.bmp" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/game_icon.bmp" ARRAY_NAME "g_game_icon") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/game_icon_night.bmp" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/game_icon_night.bmp" ARRAY_NAME "g_game_icon_night") - +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/game_icon_night.bmp" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/game_icon_night.bmp" ARRAY_NAME "g_game_icon_night") + ## Audio ## -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/music/installer.ogg" DEST_FILE "${RESOURCES_OUTPUT_PATH}/music/installer.ogg" ARRAY_NAME "g_installer_music") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/music/installer.ogg" DEST_FILE "${RESOURCES_OUTPUT_PATH}/music/installer.ogg" ARRAY_NAME "g_installer_music") BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/sounds/sys_worldmap_cursor.ogg" DEST_FILE "${RESOURCES_OUTPUT_PATH}/sounds/sys_worldmap_cursor.ogg" ARRAY_NAME "g_sys_worldmap_cursor") BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/sounds/sys_worldmap_finaldecide.ogg" DEST_FILE "${RESOURCES_OUTPUT_PATH}/sounds/sys_worldmap_finaldecide.ogg" ARRAY_NAME "g_sys_worldmap_finaldecide") BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/sounds/sys_actstg_pausecansel.ogg" DEST_FILE "${RESOURCES_OUTPUT_PATH}/sounds/sys_actstg_pausecansel.ogg" ARRAY_NAME "g_sys_actstg_pausecansel") BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/sounds/sys_actstg_pausecursor.ogg" DEST_FILE "${RESOURCES_OUTPUT_PATH}/sounds/sys_actstg_pausecursor.ogg" ARRAY_NAME "g_sys_actstg_pausecursor") BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/sounds/sys_actstg_pausedecide.ogg" DEST_FILE "${RESOURCES_OUTPUT_PATH}/sounds/sys_actstg_pausedecide.ogg" ARRAY_NAME "g_sys_actstg_pausedecide") BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/sounds/sys_actstg_pausewinclose.ogg" DEST_FILE "${RESOURCES_OUTPUT_PATH}/sounds/sys_actstg_pausewinclose.ogg" ARRAY_NAME "g_sys_actstg_pausewinclose") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/sounds/sys_actstg_pausewinopen.ogg" DEST_FILE "${RESOURCES_OUTPUT_PATH}/sounds/sys_actstg_pausewinopen.ogg" ARRAY_NAME "g_sys_actstg_pausewinopen") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/sounds/sys_actstg_pausewinopen.ogg" DEST_FILE "${RESOURCES_OUTPUT_PATH}/sounds/sys_actstg_pausewinopen.ogg" ARRAY_NAME "g_sys_actstg_pausewinopen") diff --git a/UnleashedRecomp/cpu/guest_thread.cpp b/UnleashedRecomp/cpu/guest_thread.cpp index 4249c58a..61095e5e 100644 --- a/UnleashedRecomp/cpu/guest_thread.cpp +++ b/UnleashedRecomp/cpu/guest_thread.cpp @@ -40,29 +40,101 @@ GuestThreadContext::~GuestThreadContext() g_userHeap.Free(thread); } -static void GuestThreadFunc(GuestThreadHandle* hThread) +#ifdef USE_PTHREAD +static size_t GetStackSize() { + // Cache as this should not change. + static size_t stackSize = 0; + if (stackSize == 0) + { + // 8 MiB is a typical default. + constexpr auto defaultSize = 8 * 1024 * 1024; + struct rlimit lim; + const auto ret = getrlimit(RLIMIT_STACK, &lim); + if (ret == 0 && lim.rlim_cur < defaultSize) + { + // Use what the system allows. + stackSize = lim.rlim_cur; + } + else + { + stackSize = defaultSize; + } + } + return stackSize; +} + +static void* GuestThreadFunc(void* arg) +{ + GuestThreadHandle* hThread = (GuestThreadHandle*)arg; +#else +static void* GuestThreadFunc(GuestThreadHandle* hThread) +{ +#endif hThread->suspended.wait(true); GuestThread::Start(hThread->params); +#ifdef USE_PTHREAD + return nullptr; +#endif } GuestThreadHandle::GuestThreadHandle(const GuestThreadParams& params) - : params(params), suspended((params.flags & 0x1) != 0), thread(GuestThreadFunc, this) + : params(params), suspended((params.flags & 0x1) != 0) +#ifdef USE_PTHREAD +{ + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setstacksize(&attr, GetStackSize()); + const auto ret = pthread_create(&thread, &attr, GuestThreadFunc, this); + if (ret != 0) { + fprintf(stderr, "pthread_create failed with error code 0x%X.\n", ret); + return; + } +} +#else + , thread(GuestThreadFunc, this) { } +#endif GuestThreadHandle::~GuestThreadHandle() { +#ifdef USE_PTHREAD + pthread_join(thread, nullptr); +#else if (thread.joinable()) thread.join(); +#endif +} + +template +static uint32_t CalcThreadId(const ThreadType& id) +{ + if constexpr (sizeof(id) == 4) + return *reinterpret_cast(&id); + else + return XXH32(&id, sizeof(id), 0); +} + +uint32_t GuestThreadHandle::GetThreadId() const +{ +#ifdef USE_PTHREAD + return CalcThreadId(thread); +#else + return CalcThreadId(thread.get_id()); +#endif } uint32_t GuestThreadHandle::Wait(uint32_t timeout) { assert(timeout == INFINITE); +#ifdef USE_PTHREAD + pthread_join(thread, nullptr); +#else if (thread.joinable()) thread.join(); +#endif return STATUS_WAIT_0; } @@ -80,27 +152,25 @@ uint32_t GuestThread::Start(const GuestThreadParams& params) return ctx.ppcContext.r3.u32; } -static uint32_t GetThreadId(const std::thread::id& id) -{ - if constexpr (sizeof(id) == 4) - return *reinterpret_cast(&id); - else - return XXH32(&id, sizeof(id), 0); -} - GuestThreadHandle* GuestThread::Start(const GuestThreadParams& params, uint32_t* threadId) { auto hThread = CreateKernelObject(params); if (threadId != nullptr) - *threadId = GetThreadId(hThread->thread.get_id()); + { + *threadId = hThread->GetThreadId(); + } return hThread; } uint32_t GuestThread::GetCurrentThreadId() { - return GetThreadId(std::this_thread::get_id()); +#ifdef USE_PTHREAD + return CalcThreadId(pthread_self()); +#else + return CalcThreadId(std::this_thread::get_id()); +#endif } void GuestThread::SetLastError(uint32_t error) diff --git a/UnleashedRecomp/cpu/guest_thread.h b/UnleashedRecomp/cpu/guest_thread.h index 15d16bf6..e12e91a4 100644 --- a/UnleashedRecomp/cpu/guest_thread.h +++ b/UnleashedRecomp/cpu/guest_thread.h @@ -2,6 +2,15 @@ #include +// Use pthreads directly on macOS to be able to increase default stack size. +#ifdef __APPLE__ +#define USE_PTHREAD 1 +#endif + +#ifdef USE_PTHREAD +#include +#endif + #define CURRENT_THREAD_HANDLE uint32_t(-2) struct GuestThreadContext @@ -24,11 +33,17 @@ struct GuestThreadHandle : KernelObject { GuestThreadParams params; std::atomic suspended; +#ifdef USE_PTHREAD + pthread_t thread; +#else std::thread thread; +#endif GuestThreadHandle(const GuestThreadParams& params); ~GuestThreadHandle() override; + uint32_t GetThreadId() const; + uint32_t Wait(uint32_t timeout) override; }; diff --git a/UnleashedRecomp/framework.h b/UnleashedRecomp/framework.h index db88d36f..cb89adb7 100644 --- a/UnleashedRecomp/framework.h +++ b/UnleashedRecomp/framework.h @@ -63,3 +63,47 @@ inline std::unique_ptr ReadAllBytes(const char* filePath, size_t& fil return data; } + +#ifndef __cpp_lib_atomic_ref +// Polyfill for std::atomic_ref +namespace std { +template +class atomic_ref +{ +public: + atomic_ref(value_type& ref) + { + ptr = reinterpret_cast*>(&ref); + } + + void store(value_type desired) + { + ptr->store(desired); + } + + bool compare_exchange_weak(value_type& expected, value_type desired) + { + return ptr->compare_exchange_weak(expected, desired); + } + + void wait(value_type old) + { + ptr->wait(old); + } + + void notify_one() + { + ptr->notify_one(); + } + + bool operator=(const value_type& rhs) + { + store(rhs); + } + +private: + std::atomic* ptr; +}; +} +#endif + diff --git a/UnleashedRecomp/gpu/imgui/imgui_common.h b/UnleashedRecomp/gpu/imgui/imgui_common.h index 10c5ebbb..c9d0f06e 100644 --- a/UnleashedRecomp/gpu/imgui/imgui_common.h +++ b/UnleashedRecomp/gpu/imgui/imgui_common.h @@ -13,7 +13,7 @@ #define IMGUI_SHADER_MODIFIER_RECTANGLE_BEVEL 10 #define IMGUI_SHADER_MODIFIER_LOW_QUALITY_TEXT 11 -#ifdef __cplusplus +#if defined(__cplusplus) && !defined(__air__) enum class ImGuiCallback : int32_t { diff --git a/UnleashedRecomp/gpu/rhi/plume_d3d12.cpp b/UnleashedRecomp/gpu/rhi/plume_d3d12.cpp index 073ea68a..04102c02 100644 --- a/UnleashedRecomp/gpu/rhi/plume_d3d12.cpp +++ b/UnleashedRecomp/gpu/rhi/plume_d3d12.cpp @@ -3437,6 +3437,7 @@ namespace plume { adapter = adapterOption; d3d = deviceOption; shaderModel = dataShaderModel.HighestShaderModel; + capabilities.geometryShader = true; capabilities.raytracing = rtSupportOption; capabilities.raytracingStateUpdate = rtStateUpdateSupportOption; capabilities.sampleLocations = samplePositionsOption; diff --git a/UnleashedRecomp/gpu/rhi/plume_render_interface_types.h b/UnleashedRecomp/gpu/rhi/plume_render_interface_types.h index 568160a8..315274b5 100644 --- a/UnleashedRecomp/gpu/rhi/plume_render_interface_types.h +++ b/UnleashedRecomp/gpu/rhi/plume_render_interface_types.h @@ -26,7 +26,7 @@ #undef ControlMask #undef Success #elif defined(__APPLE__) -typedef struct _NSWindow NSWindow; +#include #endif #ifdef SDL_VULKAN_ENABLED @@ -52,7 +52,9 @@ namespace plume { }; #elif defined(__APPLE__) struct RenderWindow { - NSWindow* window; + SDL_Window* window; + void* view; + bool operator==(const struct RenderWindow& rhs) const { return window == rhs.window; } @@ -1784,6 +1786,9 @@ namespace plume { }; struct RenderDeviceCapabilities { + // Geometry shaders. + bool geometryShader = false; + // Raytracing. bool raytracing = false; bool raytracingStateUpdate = false; diff --git a/UnleashedRecomp/gpu/rhi/plume_vulkan.cpp b/UnleashedRecomp/gpu/rhi/plume_vulkan.cpp index 94f91faf..86134818 100644 --- a/UnleashedRecomp/gpu/rhi/plume_vulkan.cpp +++ b/UnleashedRecomp/gpu/rhi/plume_vulkan.cpp @@ -51,13 +51,18 @@ namespace plume { VK_KHR_ANDROID_SURFACE_EXTENSION_NAME, # elif defined(__linux__) VK_KHR_XLIB_SURFACE_EXTENSION_NAME, +# elif defined(__APPLE__) + VK_EXT_METAL_SURFACE_EXTENSION_NAME, # endif }; static const std::unordered_set OptionalInstanceExtensions = { - // No optional instance extensions yet. +# if defined(__APPLE__) + // Tells the system Vulkan loader to enumerate portability drivers, if supported. + VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME, +# endif }; - + static const std::unordered_set RequiredDeviceExtensions = { VK_KHR_SWAPCHAIN_EXTENSION_NAME, VK_EXT_SCALAR_BLOCK_LAYOUT_EXTENSION_NAME, @@ -79,6 +84,8 @@ namespace plume { VK_KHR_PRESENT_ID_EXTENSION_NAME, VK_KHR_PRESENT_WAIT_EXTENSION_NAME, VK_GOOGLE_DISPLAY_TIMING_EXTENSION_NAME, + // Vulkan spec requires this to be enabled if supported by the driver. + VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME, }; // Common functions. @@ -567,14 +574,16 @@ namespace plume { } } - static VkPipelineStageFlags toStageFlags(RenderBarrierStages stages, bool rtSupported) { + static VkPipelineStageFlags toStageFlags(RenderBarrierStages stages, bool geometrySupported, bool rtSupported) { VkPipelineStageFlags flags = 0; if (stages & RenderBarrierStage::GRAPHICS) { flags |= VK_PIPELINE_STAGE_DRAW_INDIRECT_BIT; flags |= VK_PIPELINE_STAGE_VERTEX_INPUT_BIT; flags |= VK_PIPELINE_STAGE_VERTEX_SHADER_BIT; - flags |= VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT; + if (geometrySupported) { + flags |= VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT; + } flags |= VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; flags |= VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; flags |= VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; @@ -2051,6 +2060,19 @@ namespace plume { fprintf(stderr, "vkCreateXlibSurfaceKHR failed with error code 0x%X.\n", res); return; } +# elif defined(__APPLE__) + assert(renderWindow.window != 0); + assert(renderWindow.view != 0); + VkMetalSurfaceCreateInfoEXT surfaceCreateInfo = {}; + surfaceCreateInfo.sType = VK_STRUCTURE_TYPE_METAL_SURFACE_CREATE_INFO_EXT; + surfaceCreateInfo.pLayer = renderWindow.view; + + VulkanInterface *renderInterface = commandQueue->device->renderInterface; + res = vkCreateMetalSurfaceEXT(renderInterface->instance, &surfaceCreateInfo, nullptr, &surface); + if (res != VK_SUCCESS) { + fprintf(stderr, "vkCreateMetalSurfaceEXT failed with error code 0x%X.\n", res); + return; + } # endif VkBool32 presentSupported = false; @@ -2191,7 +2213,14 @@ namespace plume { } // Handle the error silently. +#if defined(__APPLE__) + // Under MoltenVK, VK_SUBOPTIMAL_KHR does not result in a valid state for rendering. We intentionally + // only check for this error during present to avoid having to synchronize manually against the semaphore + // signalled by vkAcquireNextImageKHR. + if (res != VK_SUCCESS) { +#else if ((res != VK_SUCCESS) && (res != VK_SUBOPTIMAL_KHR)) { +#endif return false; } @@ -2360,6 +2389,8 @@ namespace plume { // The attributes width and height members do not include the border. dstWidth = attributes.width; dstHeight = attributes.height; +# elif defined(__APPLE__) + SDL_GetWindowSizeInPixels(renderWindow.window, (int *)(&dstWidth), (int *)(&dstHeight)); # endif } @@ -2683,9 +2714,10 @@ namespace plume { endActiveRenderPass(); + const bool geometryEnabled = device->capabilities.geometryShader; const bool rtEnabled = device->capabilities.raytracing; VkPipelineStageFlags srcStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; - VkPipelineStageFlags dstStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT | toStageFlags(stages, rtEnabled); + VkPipelineStageFlags dstStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT | toStageFlags(stages, geometryEnabled, rtEnabled); thread_local std::vector bufferMemoryBarriers; thread_local std::vector imageMemoryBarriers; bufferMemoryBarriers.clear(); @@ -2704,7 +2736,7 @@ namespace plume { bufferMemoryBarrier.offset = 0; bufferMemoryBarrier.size = interfaceBuffer->desc.size; bufferMemoryBarriers.emplace_back(bufferMemoryBarrier); - srcStageMask |= toStageFlags(interfaceBuffer->barrierStages, rtEnabled); + srcStageMask |= toStageFlags(interfaceBuffer->barrierStages, geometryEnabled, rtEnabled); interfaceBuffer->barrierStages = stages; } @@ -2724,7 +2756,7 @@ namespace plume { imageMemoryBarrier.subresourceRange.layerCount = interfaceTexture->desc.arraySize; imageMemoryBarrier.subresourceRange.aspectMask = (interfaceTexture->desc.flags & RenderTextureFlag::DEPTH_TARGET) ? VK_IMAGE_ASPECT_DEPTH_BIT : VK_IMAGE_ASPECT_COLOR_BIT; imageMemoryBarriers.emplace_back(imageMemoryBarrier); - srcStageMask |= toStageFlags(interfaceTexture->barrierStages, rtEnabled); + srcStageMask |= toStageFlags(interfaceTexture->barrierStages, geometryEnabled, rtEnabled); interfaceTexture->textureLayout = textureBarrier.layout; interfaceTexture->barrierStages = stages; } @@ -2890,6 +2922,9 @@ namespace plume { offsetVector.clear(); for (uint32_t i = 0; i < viewCount; i++) { const VulkanBuffer *interfaceBuffer = static_cast(views[i].buffer.ref); + if (interfaceBuffer == nullptr && !device->nullDescriptorSupported) { + interfaceBuffer = static_cast(device->nullBuffer.get()); + } bufferVector.emplace_back((interfaceBuffer != nullptr) ? interfaceBuffer->vk : VK_NULL_HANDLE); offsetVector.emplace_back(views[i].buffer.offset); } @@ -3696,6 +3731,11 @@ namespace plume { bufferDeviceAddressFeatures.pNext = featuresChain; featuresChain = &bufferDeviceAddressFeatures; + VkPhysicalDevicePortabilitySubsetFeaturesKHR portabilityFeatures = {}; + portabilityFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PORTABILITY_SUBSET_FEATURES_KHR; + portabilityFeatures.pNext = featuresChain; + featuresChain = &portabilityFeatures; + VkPhysicalDeviceFeatures2 deviceFeatures = {}; deviceFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; deviceFeatures.pNext = featuresChain; @@ -3766,6 +3806,12 @@ namespace plume { createDeviceChain = &bufferDeviceAddressFeatures; } + const bool portabilitySubset = supportedOptionalExtensions.find(VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME) != supportedOptionalExtensions.end(); + if (portabilitySubset) { + portabilityFeatures.pNext = createDeviceChain; + createDeviceChain = &portabilityFeatures; + } + // Retrieve the information for the queue families. uint32_t queueFamilyCount = 0; vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueFamilyCount, nullptr); @@ -3778,6 +3824,7 @@ namespace plume { uint32_t familyIndex = 0; uint32_t familySetBits = sizeof(uint32_t) * 8; uint32_t familyQueueCount = 0; + bool familyUsed = false; for (uint32_t i = 0; i < queueFamilyCount; i++) { const VkQueueFamilyProperties &props = queueFamilyProperties[i]; @@ -3787,11 +3834,14 @@ namespace plume { } // Prefer picking the queues with the least amount of bits set that match the mask we're looking for. + // If the queue families have matching capabilities but one is already used, prefer the unused one. uint32_t setBits = numberOfSetBits(props.queueFlags); - if ((setBits < familySetBits) || ((setBits == familySetBits) && (props.queueCount > familyQueueCount))) { + bool used = queueFamilyUsed[i]; + if ((setBits < familySetBits) || ((setBits == familySetBits) && ((props.queueCount > familyQueueCount) || (familyUsed && !used)))) { familyIndex = i; familySetBits = setBits; familyQueueCount = props.queueCount; + familyUsed = used; } } @@ -3912,6 +3962,7 @@ namespace plume { description.dedicatedVideoMemory = memoryHeapSize; // Fill capabilities. + capabilities.geometryShader = deviceFeatures.features.geometryShader; capabilities.raytracing = rtSupported; capabilities.raytracingStateUpdate = false; capabilities.sampleLocations = sampleLocationsSupported; @@ -3920,13 +3971,25 @@ namespace plume { capabilities.presentWait = presentWait; capabilities.displayTiming = supportedOptionalExtensions.find(VK_GOOGLE_DISPLAY_TIMING_EXTENSION_NAME) != supportedOptionalExtensions.end(); capabilities.preferHDR = memoryHeapSize > (512 * 1024 * 1024); +#if defined(__APPLE__) + // MoltenVK supports triangle fans but does so via compute shaders to translate to lists, since it has to + // support all cases including indirect draw. This results in renderpass restarts that can harm performance, + // so force disable native triangle fan support and rely on the game to emulate fans if needed. + capabilities.triangleFan = false; +#else capabilities.triangleFan = true; +#endif capabilities.dynamicDepthBias = true; capabilities.uma = (description.type == RenderDeviceType::INTEGRATED) && hasHostVisibleDeviceLocalMemory; capabilities.gpuUploadHeap = capabilities.uma; // Fill Vulkan-only capabilities. loadStoreOpNoneSupported = supportedOptionalExtensions.find(VK_EXT_LOAD_STORE_OP_NONE_EXTENSION_NAME) != supportedOptionalExtensions.end(); + nullDescriptorSupported = nullDescriptor; + + if (!nullDescriptorSupported) { + nullBuffer = createBuffer(RenderBufferDesc::DefaultBuffer(16, RenderBufferFlag::VERTEX)); + } } VulkanDevice::~VulkanDevice() { @@ -4253,6 +4316,10 @@ namespace plume { createInfo.ppEnabledLayerNames = nullptr; createInfo.enabledLayerCount = 0; +# ifdef __APPLE__ + createInfo.flags |= VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR; +# endif + // Check for extensions. uint32_t extensionCount; vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr); diff --git a/UnleashedRecomp/gpu/rhi/plume_vulkan.h b/UnleashedRecomp/gpu/rhi/plume_vulkan.h index e25e1869..bbdd62c7 100644 --- a/UnleashedRecomp/gpu/rhi/plume_vulkan.h +++ b/UnleashedRecomp/gpu/rhi/plume_vulkan.h @@ -20,8 +20,13 @@ #define VK_USE_PLATFORM_ANDROID_KHR #elif defined(__linux__) #define VK_USE_PLATFORM_XLIB_KHR +#elif defined(__APPLE__) +#define VK_USE_PLATFORM_METAL_EXT #endif +// For VK_KHR_portability_subset +#define VK_ENABLE_BETA_EXTENSIONS + #include #ifdef __clang__ @@ -403,7 +408,9 @@ namespace plume { RenderDeviceDescription description; VkPhysicalDeviceRayTracingPipelinePropertiesKHR rtPipelineProperties = {}; VkPhysicalDeviceSampleLocationsPropertiesEXT sampleLocationProperties = {}; + std::unique_ptr nullBuffer = nullptr; bool loadStoreOpNoneSupported = false; + bool nullDescriptorSupported = false; VulkanDevice(VulkanInterface *renderInterface, const std::string &preferredDeviceName); ~VulkanDevice() override; diff --git a/UnleashedRecomp/gpu/shader/.gitignore b/UnleashedRecomp/gpu/shader/hlsl/.gitignore similarity index 100% rename from UnleashedRecomp/gpu/shader/.gitignore rename to UnleashedRecomp/gpu/shader/hlsl/.gitignore diff --git a/UnleashedRecomp/gpu/shader/blend_color_alpha_ps.hlsl b/UnleashedRecomp/gpu/shader/hlsl/blend_color_alpha_ps.hlsl similarity index 94% rename from UnleashedRecomp/gpu/shader/blend_color_alpha_ps.hlsl rename to UnleashedRecomp/gpu/shader/hlsl/blend_color_alpha_ps.hlsl index 6c242903..edcdc479 100644 --- a/UnleashedRecomp/gpu/shader/blend_color_alpha_ps.hlsl +++ b/UnleashedRecomp/gpu/shader/hlsl/blend_color_alpha_ps.hlsl @@ -1,4 +1,4 @@ -#include "../../../tools/XenosRecomp/XenosRecomp/shader_common.h" +#include "../../../../tools/XenosRecomp/XenosRecomp/shader_common.h" #ifdef __spirv__ diff --git a/UnleashedRecomp/gpu/shader/copy_color_ps.hlsl b/UnleashedRecomp/gpu/shader/hlsl/copy_color_ps.hlsl similarity index 100% rename from UnleashedRecomp/gpu/shader/copy_color_ps.hlsl rename to UnleashedRecomp/gpu/shader/hlsl/copy_color_ps.hlsl diff --git a/UnleashedRecomp/gpu/shader/copy_common.hlsli b/UnleashedRecomp/gpu/shader/hlsl/copy_common.hlsli similarity index 100% rename from UnleashedRecomp/gpu/shader/copy_common.hlsli rename to UnleashedRecomp/gpu/shader/hlsl/copy_common.hlsli diff --git a/UnleashedRecomp/gpu/shader/copy_depth_ps.hlsl b/UnleashedRecomp/gpu/shader/hlsl/copy_depth_ps.hlsl similarity index 100% rename from UnleashedRecomp/gpu/shader/copy_depth_ps.hlsl rename to UnleashedRecomp/gpu/shader/hlsl/copy_depth_ps.hlsl diff --git a/UnleashedRecomp/gpu/shader/copy_vs.hlsl b/UnleashedRecomp/gpu/shader/hlsl/copy_vs.hlsl similarity index 100% rename from UnleashedRecomp/gpu/shader/copy_vs.hlsl rename to UnleashedRecomp/gpu/shader/hlsl/copy_vs.hlsl diff --git a/UnleashedRecomp/gpu/shader/csd_filter_ps.hlsl b/UnleashedRecomp/gpu/shader/hlsl/csd_filter_ps.hlsl similarity index 95% rename from UnleashedRecomp/gpu/shader/csd_filter_ps.hlsl rename to UnleashedRecomp/gpu/shader/hlsl/csd_filter_ps.hlsl index e1ecde39..99424ce4 100644 --- a/UnleashedRecomp/gpu/shader/csd_filter_ps.hlsl +++ b/UnleashedRecomp/gpu/shader/hlsl/csd_filter_ps.hlsl @@ -1,4 +1,4 @@ -#include "../../../tools/XenosRecomp/XenosRecomp/shader_common.h" +#include "../../../../tools/XenosRecomp/XenosRecomp/shader_common.h" #ifdef __spirv__ diff --git a/UnleashedRecomp/gpu/shader/csd_no_tex_vs.hlsl b/UnleashedRecomp/gpu/shader/hlsl/csd_no_tex_vs.hlsl similarity index 96% rename from UnleashedRecomp/gpu/shader/csd_no_tex_vs.hlsl rename to UnleashedRecomp/gpu/shader/hlsl/csd_no_tex_vs.hlsl index 1654e734..16eca752 100644 --- a/UnleashedRecomp/gpu/shader/csd_no_tex_vs.hlsl +++ b/UnleashedRecomp/gpu/shader/hlsl/csd_no_tex_vs.hlsl @@ -1,4 +1,4 @@ -#include "../../../tools/XenosRecomp/XenosRecomp/shader_common.h" +#include "../../../../tools/XenosRecomp/XenosRecomp/shader_common.h" #ifdef __spirv__ diff --git a/UnleashedRecomp/gpu/shader/csd_vs.hlsl b/UnleashedRecomp/gpu/shader/hlsl/csd_vs.hlsl similarity index 96% rename from UnleashedRecomp/gpu/shader/csd_vs.hlsl rename to UnleashedRecomp/gpu/shader/hlsl/csd_vs.hlsl index 90b561f5..3ec425bf 100644 --- a/UnleashedRecomp/gpu/shader/csd_vs.hlsl +++ b/UnleashedRecomp/gpu/shader/hlsl/csd_vs.hlsl @@ -1,4 +1,4 @@ -#include "../../../tools/XenosRecomp/XenosRecomp/shader_common.h" +#include "../../../../tools/XenosRecomp/XenosRecomp/shader_common.h" #ifdef __spirv__ diff --git a/UnleashedRecomp/gpu/shader/enhanced_motion_blur_ps.hlsl b/UnleashedRecomp/gpu/shader/hlsl/enhanced_motion_blur_ps.hlsl similarity index 97% rename from UnleashedRecomp/gpu/shader/enhanced_motion_blur_ps.hlsl rename to UnleashedRecomp/gpu/shader/hlsl/enhanced_motion_blur_ps.hlsl index fd522da7..c96900dc 100644 --- a/UnleashedRecomp/gpu/shader/enhanced_motion_blur_ps.hlsl +++ b/UnleashedRecomp/gpu/shader/hlsl/enhanced_motion_blur_ps.hlsl @@ -1,4 +1,4 @@ -#include "../../../tools/XenosRecomp/XenosRecomp/shader_common.h" +#include "../../../../tools/XenosRecomp/XenosRecomp/shader_common.h" #ifdef __spirv__ diff --git a/UnleashedRecomp/gpu/shader/gamma_correction_ps.hlsl b/UnleashedRecomp/gpu/shader/hlsl/gamma_correction_ps.hlsl similarity index 93% rename from UnleashedRecomp/gpu/shader/gamma_correction_ps.hlsl rename to UnleashedRecomp/gpu/shader/hlsl/gamma_correction_ps.hlsl index 2f98b6e6..17577a08 100644 --- a/UnleashedRecomp/gpu/shader/gamma_correction_ps.hlsl +++ b/UnleashedRecomp/gpu/shader/hlsl/gamma_correction_ps.hlsl @@ -1,4 +1,4 @@ -#include "../../../tools/XenosRecomp/XenosRecomp/shader_common.h" +#include "../../../../tools/XenosRecomp/XenosRecomp/shader_common.h" #ifdef __spirv__ diff --git a/UnleashedRecomp/gpu/shader/gaussian_blur.hlsli b/UnleashedRecomp/gpu/shader/hlsl/gaussian_blur.hlsli similarity index 97% rename from UnleashedRecomp/gpu/shader/gaussian_blur.hlsli rename to UnleashedRecomp/gpu/shader/hlsl/gaussian_blur.hlsli index 10d76cf3..6f1bc58b 100644 --- a/UnleashedRecomp/gpu/shader/gaussian_blur.hlsli +++ b/UnleashedRecomp/gpu/shader/hlsl/gaussian_blur.hlsli @@ -1,4 +1,4 @@ -#include "../../../tools/XenosRecomp/XenosRecomp/shader_common.h" +#include "../../../../tools/XenosRecomp/XenosRecomp/shader_common.h" #ifdef __spirv__ diff --git a/UnleashedRecomp/gpu/shader/gaussian_blur_3x3.hlsl b/UnleashedRecomp/gpu/shader/hlsl/gaussian_blur_3x3.hlsl similarity index 100% rename from UnleashedRecomp/gpu/shader/gaussian_blur_3x3.hlsl rename to UnleashedRecomp/gpu/shader/hlsl/gaussian_blur_3x3.hlsl diff --git a/UnleashedRecomp/gpu/shader/gaussian_blur_5x5.hlsl b/UnleashedRecomp/gpu/shader/hlsl/gaussian_blur_5x5.hlsl similarity index 100% rename from UnleashedRecomp/gpu/shader/gaussian_blur_5x5.hlsl rename to UnleashedRecomp/gpu/shader/hlsl/gaussian_blur_5x5.hlsl diff --git a/UnleashedRecomp/gpu/shader/gaussian_blur_7x7.hlsl b/UnleashedRecomp/gpu/shader/hlsl/gaussian_blur_7x7.hlsl similarity index 100% rename from UnleashedRecomp/gpu/shader/gaussian_blur_7x7.hlsl rename to UnleashedRecomp/gpu/shader/hlsl/gaussian_blur_7x7.hlsl diff --git a/UnleashedRecomp/gpu/shader/gaussian_blur_9x9.hlsl b/UnleashedRecomp/gpu/shader/hlsl/gaussian_blur_9x9.hlsl similarity index 100% rename from UnleashedRecomp/gpu/shader/gaussian_blur_9x9.hlsl rename to UnleashedRecomp/gpu/shader/hlsl/gaussian_blur_9x9.hlsl diff --git a/UnleashedRecomp/gpu/shader/imgui_common.hlsli b/UnleashedRecomp/gpu/shader/hlsl/imgui_common.hlsli similarity index 95% rename from UnleashedRecomp/gpu/shader/imgui_common.hlsli rename to UnleashedRecomp/gpu/shader/hlsl/imgui_common.hlsli index f4cbe3cf..42bd94ee 100644 --- a/UnleashedRecomp/gpu/shader/imgui_common.hlsli +++ b/UnleashedRecomp/gpu/shader/hlsl/imgui_common.hlsli @@ -1,6 +1,6 @@ #pragma once -#include "../imgui/imgui_common.h" +#include "../../imgui/imgui_common.h" struct PushConstants { diff --git a/UnleashedRecomp/gpu/shader/imgui_ps.hlsl b/UnleashedRecomp/gpu/shader/hlsl/imgui_ps.hlsl similarity index 100% rename from UnleashedRecomp/gpu/shader/imgui_ps.hlsl rename to UnleashedRecomp/gpu/shader/hlsl/imgui_ps.hlsl diff --git a/UnleashedRecomp/gpu/shader/imgui_vs.hlsl b/UnleashedRecomp/gpu/shader/hlsl/imgui_vs.hlsl similarity index 100% rename from UnleashedRecomp/gpu/shader/imgui_vs.hlsl rename to UnleashedRecomp/gpu/shader/hlsl/imgui_vs.hlsl diff --git a/UnleashedRecomp/gpu/shader/movie_common.hlsli b/UnleashedRecomp/gpu/shader/hlsl/movie_common.hlsli similarity index 97% rename from UnleashedRecomp/gpu/shader/movie_common.hlsli rename to UnleashedRecomp/gpu/shader/hlsl/movie_common.hlsli index 12942353..61cc3b38 100644 --- a/UnleashedRecomp/gpu/shader/movie_common.hlsli +++ b/UnleashedRecomp/gpu/shader/hlsl/movie_common.hlsli @@ -1,6 +1,6 @@ #pragma once -#include "../../../tools/XenosRecomp/XenosRecomp/shader_common.h" +#include "../../../../tools/XenosRecomp/XenosRecomp/shader_common.h" #ifdef __spirv__ diff --git a/UnleashedRecomp/gpu/shader/movie_ps.hlsl b/UnleashedRecomp/gpu/shader/hlsl/movie_ps.hlsl similarity index 100% rename from UnleashedRecomp/gpu/shader/movie_ps.hlsl rename to UnleashedRecomp/gpu/shader/hlsl/movie_ps.hlsl diff --git a/UnleashedRecomp/gpu/shader/movie_vs.hlsl b/UnleashedRecomp/gpu/shader/hlsl/movie_vs.hlsl similarity index 100% rename from UnleashedRecomp/gpu/shader/movie_vs.hlsl rename to UnleashedRecomp/gpu/shader/hlsl/movie_vs.hlsl diff --git a/UnleashedRecomp/gpu/shader/resolve_msaa_color.hlsli b/UnleashedRecomp/gpu/shader/hlsl/resolve_msaa_color.hlsli similarity index 100% rename from UnleashedRecomp/gpu/shader/resolve_msaa_color.hlsli rename to UnleashedRecomp/gpu/shader/hlsl/resolve_msaa_color.hlsli diff --git a/UnleashedRecomp/gpu/shader/resolve_msaa_color_2x.hlsl b/UnleashedRecomp/gpu/shader/hlsl/resolve_msaa_color_2x.hlsl similarity index 100% rename from UnleashedRecomp/gpu/shader/resolve_msaa_color_2x.hlsl rename to UnleashedRecomp/gpu/shader/hlsl/resolve_msaa_color_2x.hlsl diff --git a/UnleashedRecomp/gpu/shader/resolve_msaa_color_4x.hlsl b/UnleashedRecomp/gpu/shader/hlsl/resolve_msaa_color_4x.hlsl similarity index 100% rename from UnleashedRecomp/gpu/shader/resolve_msaa_color_4x.hlsl rename to UnleashedRecomp/gpu/shader/hlsl/resolve_msaa_color_4x.hlsl diff --git a/UnleashedRecomp/gpu/shader/resolve_msaa_color_8x.hlsl b/UnleashedRecomp/gpu/shader/hlsl/resolve_msaa_color_8x.hlsl similarity index 100% rename from UnleashedRecomp/gpu/shader/resolve_msaa_color_8x.hlsl rename to UnleashedRecomp/gpu/shader/hlsl/resolve_msaa_color_8x.hlsl diff --git a/UnleashedRecomp/gpu/shader/resolve_msaa_depth.hlsli b/UnleashedRecomp/gpu/shader/hlsl/resolve_msaa_depth.hlsli similarity index 100% rename from UnleashedRecomp/gpu/shader/resolve_msaa_depth.hlsli rename to UnleashedRecomp/gpu/shader/hlsl/resolve_msaa_depth.hlsli diff --git a/UnleashedRecomp/gpu/shader/resolve_msaa_depth_2x.hlsl b/UnleashedRecomp/gpu/shader/hlsl/resolve_msaa_depth_2x.hlsl similarity index 100% rename from UnleashedRecomp/gpu/shader/resolve_msaa_depth_2x.hlsl rename to UnleashedRecomp/gpu/shader/hlsl/resolve_msaa_depth_2x.hlsl diff --git a/UnleashedRecomp/gpu/shader/resolve_msaa_depth_4x.hlsl b/UnleashedRecomp/gpu/shader/hlsl/resolve_msaa_depth_4x.hlsl similarity index 100% rename from UnleashedRecomp/gpu/shader/resolve_msaa_depth_4x.hlsl rename to UnleashedRecomp/gpu/shader/hlsl/resolve_msaa_depth_4x.hlsl diff --git a/UnleashedRecomp/gpu/shader/resolve_msaa_depth_8x.hlsl b/UnleashedRecomp/gpu/shader/hlsl/resolve_msaa_depth_8x.hlsl similarity index 100% rename from UnleashedRecomp/gpu/shader/resolve_msaa_depth_8x.hlsl rename to UnleashedRecomp/gpu/shader/hlsl/resolve_msaa_depth_8x.hlsl diff --git a/UnleashedRecomp/gpu/shader/msl/.gitignore b/UnleashedRecomp/gpu/shader/msl/.gitignore new file mode 100644 index 00000000..40d087f0 --- /dev/null +++ b/UnleashedRecomp/gpu/shader/msl/.gitignore @@ -0,0 +1,4 @@ +*.ir +*.metallib +*.metal.*.c +*.metal.*.h \ No newline at end of file diff --git a/UnleashedRecomp/gpu/shader/msl/blend_color_alpha_ps.metal b/UnleashedRecomp/gpu/shader/msl/blend_color_alpha_ps.metal new file mode 100644 index 00000000..693fc10a --- /dev/null +++ b/UnleashedRecomp/gpu/shader/msl/blend_color_alpha_ps.metal @@ -0,0 +1,31 @@ +#include "../../../../tools/XenosRecomp/XenosRecomp/shader_common.h" + +#define g_SrcAlpha_DestAlpha (*(reinterpret_cast(g_PushConstants.PixelShaderConstants + 2400))) +#define s0_Texture2DDescriptorIndex (*(reinterpret_cast(g_PushConstants.SharedConstants + 0))) +#define s0_SamplerDescriptorIndex (*(reinterpret_cast(g_PushConstants.SharedConstants + 192))) + +struct Interpolators +{ + float4 iTexCoord0 [[user(TEXCOORD0)]]; +}; + +[[fragment]] +float4 shaderMain(float4 iPos [[position]], + Interpolators input [[stage_in]], + constant Texture2DDescriptorHeap* g_Texture2DDescriptorHeap [[buffer(0)]], + constant SamplerDescriptorHeap* g_SamplerDescriptorHeap [[buffer(3)]], + constant PushConstants& g_PushConstants [[buffer(4)]]) +{ + texture2d texture = g_Texture2DDescriptorHeap[s0_Texture2DDescriptorIndex].tex; + sampler samplerState = g_SamplerDescriptorHeap[s0_SamplerDescriptorIndex].samp; + + float4 color = texture.sample(samplerState, input.iTexCoord0.xy); + + if (any(input.iTexCoord0.xy < 0.0 || input.iTexCoord0.xy > 1.0)) + color = float4(0.0, 0.0, 0.0, 1.0); + + color.rgb *= color.a * g_SrcAlpha_DestAlpha.x; + color.a = g_SrcAlpha_DestAlpha.y + (1.0 - color.a) * g_SrcAlpha_DestAlpha.x; + + return color; +} diff --git a/UnleashedRecomp/gpu/shader/msl/copy_color_ps.metal b/UnleashedRecomp/gpu/shader/msl/copy_color_ps.metal new file mode 100644 index 00000000..7532550e --- /dev/null +++ b/UnleashedRecomp/gpu/shader/msl/copy_color_ps.metal @@ -0,0 +1,14 @@ +#include "copy_common.metali" + +struct Texture2DDescriptorHeap +{ + texture2d tex; +}; + +[[fragment]] +float4 shaderMain(float4 position [[position]], + constant Texture2DDescriptorHeap* g_Texture2DDescriptorHeap [[buffer(0)]], + constant PushConstants& g_PushConstants [[buffer(4)]]) +{ + return g_Texture2DDescriptorHeap[g_PushConstants.ResourceDescriptorIndex].tex.read(uint2(position.xy), 0); +} diff --git a/UnleashedRecomp/gpu/shader/msl/copy_common.metali b/UnleashedRecomp/gpu/shader/msl/copy_common.metali new file mode 100644 index 00000000..2040a3a8 --- /dev/null +++ b/UnleashedRecomp/gpu/shader/msl/copy_common.metali @@ -0,0 +1,9 @@ +#pragma once +#include + +using namespace metal; + +struct PushConstants +{ + uint ResourceDescriptorIndex; +}; diff --git a/UnleashedRecomp/gpu/shader/msl/copy_depth_ps.metal b/UnleashedRecomp/gpu/shader/msl/copy_depth_ps.metal new file mode 100644 index 00000000..c44364d2 --- /dev/null +++ b/UnleashedRecomp/gpu/shader/msl/copy_depth_ps.metal @@ -0,0 +1,23 @@ +#include "copy_common.metali" + +struct Texture2DDescriptorHeap +{ + texture2d tex; +}; + +struct PixelShaderOutput +{ + float oDepth [[depth(any)]]; +}; + +[[fragment]] +PixelShaderOutput shaderMain(float4 position [[position]], + constant Texture2DDescriptorHeap* g_Texture2DDescriptorHeap [[buffer(0)]], + constant PushConstants& g_PushConstants [[buffer(4)]]) +{ + PixelShaderOutput output = PixelShaderOutput{}; + + output.oDepth = g_Texture2DDescriptorHeap[g_PushConstants.ResourceDescriptorIndex].tex.read(uint2(position.xy), 0).x; + + return output; +} diff --git a/UnleashedRecomp/gpu/shader/msl/copy_vs.metal b/UnleashedRecomp/gpu/shader/msl/copy_vs.metal new file mode 100644 index 00000000..5703be50 --- /dev/null +++ b/UnleashedRecomp/gpu/shader/msl/copy_vs.metal @@ -0,0 +1,16 @@ +struct Interpolators +{ + float4 position [[position]]; + float2 texCoord [[user(TEXCOORD)]]; +}; + +[[vertex]] +Interpolators shaderMain(uint vertexId [[vertex_id]]) +{ + Interpolators interpolators; + + interpolators.texCoord = float2((vertexId << 1) & 2, vertexId & 2); + interpolators.position = float4(interpolators.texCoord * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0); + + return interpolators; +} diff --git a/UnleashedRecomp/gpu/shader/msl/csd_filter_ps.metal b/UnleashedRecomp/gpu/shader/msl/csd_filter_ps.metal new file mode 100644 index 00000000..3f2ab2e2 --- /dev/null +++ b/UnleashedRecomp/gpu/shader/msl/csd_filter_ps.metal @@ -0,0 +1,38 @@ +#include "../../../../tools/XenosRecomp/XenosRecomp/shader_common.h" + +#define s0_Texture2DDescriptorIndex (*(reinterpret_cast(g_PushConstants.SharedConstants + 0))) +#define s0_SamplerDescriptorIndex (*(reinterpret_cast(g_PushConstants.SharedConstants + 192))) + +struct Interpolators +{ + float4 iPosition [[position]]; + float4 iTexCoord0 [[user(TEXCOORD0)]]; + float4 iTexCoord1 [[user(TEXCOORD1)]]; +}; + +[[fragment]] +float4 shaderMain(Interpolators input [[stage_in]], + constant Texture2DDescriptorHeap* g_Texture2DDescriptorHeap [[buffer(0)]], + constant SamplerDescriptorHeap* g_SamplerDescriptorHeap [[buffer(3)]], + constant PushConstants& g_PushConstants [[buffer(4)]]) +{ + texture2d texture = g_Texture2DDescriptorHeap[s0_Texture2DDescriptorIndex].tex; + sampler samplerState = g_SamplerDescriptorHeap[s0_SamplerDescriptorIndex].samp; + + uint2 dimensions = getTexture2DDimensions(texture); + + // https://www.shadertoy.com/view/csX3RH + float2 uvTexspace = input.iTexCoord1.xy * float2(dimensions); + float2 seam = floor(uvTexspace + 0.5); + uvTexspace = (uvTexspace - seam) / fwidth(uvTexspace) + seam; + uvTexspace = clamp(uvTexspace, seam - 0.5, seam + 0.5); + float2 texCoord = uvTexspace / float2(dimensions); + + float4 color = texture.sample(samplerState, texCoord); + color *= input.iTexCoord0; + + // The game enables alpha test for CSD, but the alpha threshold doesn't seem to be assigned anywhere? Weird. + clip(color.a - g_AlphaThreshold); + + return color; +} diff --git a/UnleashedRecomp/gpu/shader/msl/csd_no_tex_vs.metal b/UnleashedRecomp/gpu/shader/msl/csd_no_tex_vs.metal new file mode 100644 index 00000000..4ec07485 --- /dev/null +++ b/UnleashedRecomp/gpu/shader/msl/csd_no_tex_vs.metal @@ -0,0 +1,64 @@ +#include "../../../../tools/XenosRecomp/XenosRecomp/shader_common.h" + +#define g_ViewportSize (*(reinterpret_cast(g_PushConstants.VertexShaderConstants + 2880))) +#define g_Z (*(reinterpret_cast(g_PushConstants.VertexShaderConstants + 3936))) + +struct VertexShaderInput +{ + float4 iPosition0 [[attribute(0)]]; + float4 iColor0 [[attribute(8)]]; +}; + +struct Interpolators +{ + float4 oPos [[position]]; + float4 oTexCoord0 [[user(TEXCOORD0)]]; + float4 oTexCoord1 [[user(TEXCOORD1)]]; + float4 oTexCoord2 [[user(TEXCOORD2)]]; + float4 oTexCoord3 [[user(TEXCOORD3)]]; + float4 oTexCoord4 [[user(TEXCOORD4)]]; + float4 oTexCoord5 [[user(TEXCOORD5)]]; + float4 oTexCoord6 [[user(TEXCOORD6)]]; + float4 oTexCoord7 [[user(TEXCOORD7)]]; + float4 oTexCoord8 [[user(TEXCOORD8)]]; + float4 oTexCoord9 [[user(TEXCOORD9)]]; + float4 oTexCoord10 [[user(TEXCOORD10)]]; + float4 oTexCoord11 [[user(TEXCOORD11)]]; + float4 oTexCoord12 [[user(TEXCOORD12)]]; + float4 oTexCoord13 [[user(TEXCOORD13)]]; + float4 oTexCoord14 [[user(TEXCOORD14)]]; + float4 oTexCoord15 [[user(TEXCOORD15)]]; + float4 oColor0 [[user(COLOR0)]]; + float4 oColor1 [[user(COLOR1)]]; +}; + +[[vertex]] +Interpolators shaderMain(VertexShaderInput input [[stage_in]], + constant PushConstants& g_PushConstants [[buffer(4)]]) +{ + Interpolators output; + + output.oPos.xy = (input.iPosition0.xy - 0.5) * g_ViewportSize.zw * float2(2.0, -2.0) + float2(-1.0, 1.0); + output.oPos.z = g_Z.x; + output.oPos.w = 1.0; + output.oTexCoord0 = input.iColor0.wxyz; + output.oTexCoord1 = 0.0; + output.oTexCoord2 = 0.0; + output.oTexCoord3 = 0.0; + output.oTexCoord4 = 0.0; + output.oTexCoord5 = 0.0; + output.oTexCoord6 = 0.0; + output.oTexCoord7 = 0.0; + output.oTexCoord8 = 0.0; + output.oTexCoord9 = 0.0; + output.oTexCoord10 = 0.0; + output.oTexCoord11 = 0.0; + output.oTexCoord12 = 0.0; + output.oTexCoord13 = 0.0; + output.oTexCoord14 = 0.0; + output.oTexCoord15 = 0.0; + output.oColor0 = 0.0; + output.oColor1 = 0.0; + + return output; +} diff --git a/UnleashedRecomp/gpu/shader/msl/csd_vs.metal b/UnleashedRecomp/gpu/shader/msl/csd_vs.metal new file mode 100644 index 00000000..b8f8f6a1 --- /dev/null +++ b/UnleashedRecomp/gpu/shader/msl/csd_vs.metal @@ -0,0 +1,66 @@ +#include "../../../../tools/XenosRecomp/XenosRecomp/shader_common.h" + +#define g_ViewportSize (*(reinterpret_cast(g_PushConstants.VertexShaderConstants + 2880))) +#define g_Z (*(reinterpret_cast(g_PushConstants.VertexShaderConstants + 3936))) + +struct VertexShaderInput +{ + float4 iPosition0 [[attribute(0)]]; + float4 iColor0 [[attribute(8)]]; + float4 iTexCoord0 [[attribute(4)]]; +}; + +struct Interpolators +{ + float4 oPos [[position]]; + float4 oTexCoord0 [[user(TEXCOORD0)]]; + float4 oTexCoord1 [[user(TEXCOORD1)]]; + float4 oTexCoord2 [[user(TEXCOORD2)]]; + float4 oTexCoord3 [[user(TEXCOORD3)]]; + float4 oTexCoord4 [[user(TEXCOORD4)]]; + float4 oTexCoord5 [[user(TEXCOORD5)]]; + float4 oTexCoord6 [[user(TEXCOORD6)]]; + float4 oTexCoord7 [[user(TEXCOORD7)]]; + float4 oTexCoord8 [[user(TEXCOORD8)]]; + float4 oTexCoord9 [[user(TEXCOORD9)]]; + float4 oTexCoord10 [[user(TEXCOORD10)]]; + float4 oTexCoord11 [[user(TEXCOORD11)]]; + float4 oTexCoord12 [[user(TEXCOORD12)]]; + float4 oTexCoord13 [[user(TEXCOORD13)]]; + float4 oTexCoord14 [[user(TEXCOORD14)]]; + float4 oTexCoord15 [[user(TEXCOORD15)]]; + float4 oColor0 [[user(COLOR0)]]; + float4 oColor1 [[user(COLOR1)]]; +}; + +[[vertex]] +Interpolators shaderMain(VertexShaderInput input [[stage_in]], + constant PushConstants& g_PushConstants [[buffer(4)]]) +{ + Interpolators output; + + output.oPos.xy = (input.iPosition0.xy - 0.5) * g_ViewportSize.zw * float2(2.0, -2.0) + float2(-1.0, 1.0); + output.oPos.z = g_Z.x; + output.oPos.w = 1.0; + output.oTexCoord0 = input.iColor0.wxyz; + output.oTexCoord1.xy = input.iTexCoord0.xy; + output.oTexCoord1.zw = 0.0; + output.oTexCoord2 = 0.0; + output.oTexCoord3 = 0.0; + output.oTexCoord4 = 0.0; + output.oTexCoord5 = 0.0; + output.oTexCoord6 = 0.0; + output.oTexCoord7 = 0.0; + output.oTexCoord8 = 0.0; + output.oTexCoord9 = 0.0; + output.oTexCoord10 = 0.0; + output.oTexCoord11 = 0.0; + output.oTexCoord12 = 0.0; + output.oTexCoord13 = 0.0; + output.oTexCoord14 = 0.0; + output.oTexCoord15 = 0.0; + output.oColor0 = 0.0; + output.oColor1 = 0.0; + + return output; +} diff --git a/UnleashedRecomp/gpu/shader/msl/enhanced_motion_blur_ps.metal b/UnleashedRecomp/gpu/shader/msl/enhanced_motion_blur_ps.metal new file mode 100644 index 00000000..84d30c31 --- /dev/null +++ b/UnleashedRecomp/gpu/shader/msl/enhanced_motion_blur_ps.metal @@ -0,0 +1,59 @@ +#include "../../../../tools/XenosRecomp/XenosRecomp/shader_common.h" + +#define g_BlurRate (*(reinterpret_cast(g_PushConstants.PixelShaderConstants + 2400))) +#define g_ViewportSize (*(reinterpret_cast(g_PushConstants.PixelShaderConstants + 384))) + +#define sampColor_Texture2DDescriptorIndex (*(reinterpret_cast(g_PushConstants.SharedConstants + 0))) +#define sampColor_SamplerDescriptorIndex (*(reinterpret_cast(g_PushConstants.SharedConstants + 192))) + +#define sampVelocityMap_Texture2DDescriptorIndex (*(reinterpret_cast(g_PushConstants.SharedConstants + 4))) +#define sampVelocityMap_SamplerDescriptorIndex (*(reinterpret_cast(g_PushConstants.SharedConstants + 196))) + +#define sampZBuffer_Texture2DDescriptorIndex (*(reinterpret_cast(g_PushConstants.SharedConstants + 8))) +#define sampZBuffer_SamplerDescriptorIndex (*(reinterpret_cast(g_PushConstants.SharedConstants + 200))) + +struct Interpolators +{ + float4 texCoord [[user(TEXCOORD0)]]; +}; + +[[fragment]] +float4 shaderMain(float4 position [[position]], + Interpolators input [[stage_in]], + constant Texture2DDescriptorHeap* g_Texture2DDescriptorHeap [[buffer(0)]], + constant SamplerDescriptorHeap* g_SamplerDescriptorHeap [[buffer(3)]], + constant PushConstants& g_PushConstants [[buffer(4)]]) +{ + texture2d sampColor = g_Texture2DDescriptorHeap[sampColor_Texture2DDescriptorIndex].tex; + texture2d sampVelocityMap = g_Texture2DDescriptorHeap[sampVelocityMap_Texture2DDescriptorIndex].tex; + texture2d sampZBuffer = g_Texture2DDescriptorHeap[sampZBuffer_Texture2DDescriptorIndex].tex; + + sampler sampColor_s = g_SamplerDescriptorHeap[sampColor_SamplerDescriptorIndex].samp; + sampler sampVelocityMap_s = g_SamplerDescriptorHeap[sampVelocityMap_SamplerDescriptorIndex].samp; + sampler sampZBuffer_s = g_SamplerDescriptorHeap[sampZBuffer_SamplerDescriptorIndex].samp; + + float depth = sampZBuffer.sample(sampZBuffer_s, input.texCoord.xy, level(0)).x; + float4 velocityMap = sampVelocityMap.sample(sampVelocityMap_s, input.texCoord.xy, level(0)); + float2 velocity = (velocityMap.xz + velocityMap.yw / 255.0) * 2.0 - 1.0; + + int sampleCount = min(64, int(round(length(velocity * g_ViewportSize.xy)))); + float2 sampleOffset = velocity / (float) sampleCount; + + float3 color = sampColor.sample(sampColor_s, input.texCoord.xy, level(0)).rgb; + int count = 1; + + for (int i = 1; i <= sampleCount; i++) + { + float2 sampleCoord = input.texCoord.xy + sampleOffset * i; + float3 sampleColor = sampColor.sample(sampColor_s, sampleCoord, level(0)).rgb; + float sampleDepth = sampZBuffer.sample(sampZBuffer_s, sampleCoord, 0).x; + + if (sampleDepth - depth < 0.01) + { + color += sampleColor; + count += 1; + } + } + + return float4(color / count, g_BlurRate.x * saturate(dot(abs(velocity), g_ViewportSize.xy) / 8.0) * saturate(float(count - 1))); +} diff --git a/UnleashedRecomp/gpu/shader/msl/gamma_correction_ps.metal b/UnleashedRecomp/gpu/shader/msl/gamma_correction_ps.metal new file mode 100644 index 00000000..6cfbf037 --- /dev/null +++ b/UnleashedRecomp/gpu/shader/msl/gamma_correction_ps.metal @@ -0,0 +1,23 @@ +#include "../../../../tools/XenosRecomp/XenosRecomp/shader_common.h" + +#define g_Gamma (*(reinterpret_cast(g_PushConstants.SharedConstants + 0))) +#define g_TextureDescriptorIndex (*(reinterpret_cast(g_PushConstants.SharedConstants + 12))) + +#define g_ViewportOffset (*(reinterpret_cast(g_PushConstants.SharedConstants + 16))) +#define g_ViewportSize (*(reinterpret_cast(g_PushConstants.SharedConstants + 24))) + +[[fragment]] +float4 shaderMain(float4 position [[position]], + constant Texture2DDescriptorHeap* g_Texture2DDescriptorHeap [[buffer(0)]], + constant PushConstants& g_PushConstants [[buffer(4)]]) +{ + texture2d texture = g_Texture2DDescriptorHeap[g_TextureDescriptorIndex].tex; + + int2 movedPosition = int2(position.xy) - g_ViewportOffset; + bool boxed = any(movedPosition < 0) || any(movedPosition >= g_ViewportSize); + if (boxed) movedPosition = 0; + + float4 color = boxed ? 0.0 : texture.read(uint2(movedPosition), 0); + color.rgb = pow(color.rgb, g_Gamma); + return color; +} diff --git a/UnleashedRecomp/gpu/shader/msl/gaussian_blur.metali b/UnleashedRecomp/gpu/shader/msl/gaussian_blur.metali new file mode 100644 index 00000000..3331e2ba --- /dev/null +++ b/UnleashedRecomp/gpu/shader/msl/gaussian_blur.metali @@ -0,0 +1,63 @@ +#include "../../../../tools/XenosRecomp/XenosRecomp/shader_common.h" + +#define g_ViewportSize (*(reinterpret_cast(g_PushConstants.PixelShaderConstants + 384))) +#define g_offsets(INDEX) selectWrapper((INDEX) < 74,(*(reinterpret_cast(g_PushConstants.PixelShaderConstants + (150 + min(INDEX, 73)) * 16))), 0.0) +#define g_weights (*(reinterpret_cast(g_PushConstants.PixelShaderConstants + 2656))) + +#define s0_Texture2DDescriptorIndex (*(reinterpret_cast(g_PushConstants.SharedConstants + 0))) +#define s0_SamplerDescriptorIndex (*(reinterpret_cast(g_PushConstants.SharedConstants + 192))) + +#ifdef __INTELLISENSE__ +#define KERNEL_SIZE 5 +#endif + +#define PI 3.14159265358979323846 + +float ComputeWeight(float x) +{ + float std = 0.952; + return exp(-(x * x) / (2.0 * std * std)) / (std * sqrt(2.0 * PI)); +} + +struct Interpolators +{ + float4 iTexCoord0 [[user(TEXCOORD0)]]; +}; + +[[fragment]] +float4 shaderMain(float4 iPosition [[position]], + Interpolators input [[stage_in]], + constant Texture2DDescriptorHeap* g_Texture2DDescriptorHeap [[buffer(0)]], + constant SamplerDescriptorHeap* g_SamplerDescriptorHeap [[buffer(3)]], + constant PushConstants& g_PushConstants [[buffer(4)]]) +{ + texture2d texture = g_Texture2DDescriptorHeap[s0_Texture2DDescriptorIndex].tex; + sampler samplerState = g_SamplerDescriptorHeap[s0_SamplerDescriptorIndex].samp; + + float scale; + if ((g_ViewportSize.x * g_ViewportSize.w) >= (16.0 / 9.0)) + scale = g_ViewportSize.y / 360.0; + else + scale = g_ViewportSize.x / 640.0; + + float2 offsets[3]; + offsets[0] = g_offsets(0).xy * scale; + offsets[1] = g_offsets(0).zw * scale; + offsets[2] = g_offsets(1).xy * scale; + + float4 color = 0.0; + float weightSum = 0.0; + + for (int i = 0; i < KERNEL_SIZE; i++) + { + float step = i / float(KERNEL_SIZE - 1); + float scaled = step * 2; + float2 offset = mix(offsets[int(scaled)], offsets[min(int(scaled) + 1, 2)], frac(scaled)); + float offsetScale = 1.0 / 0.75; + float weight = ComputeWeight(mix(-offsetScale, offsetScale, step)); + color += texture.sample(samplerState, input.iTexCoord0.xy + offset, level(0)) * weight; + weightSum += weight; + } + + return color / weightSum; +} diff --git a/UnleashedRecomp/gpu/shader/msl/gaussian_blur_3x3.metal b/UnleashedRecomp/gpu/shader/msl/gaussian_blur_3x3.metal new file mode 100644 index 00000000..1f8dbf31 --- /dev/null +++ b/UnleashedRecomp/gpu/shader/msl/gaussian_blur_3x3.metal @@ -0,0 +1,2 @@ +#define KERNEL_SIZE 3 +#include "gaussian_blur.metali" diff --git a/UnleashedRecomp/gpu/shader/msl/gaussian_blur_5x5.metal b/UnleashedRecomp/gpu/shader/msl/gaussian_blur_5x5.metal new file mode 100644 index 00000000..5b1a2c2d --- /dev/null +++ b/UnleashedRecomp/gpu/shader/msl/gaussian_blur_5x5.metal @@ -0,0 +1,2 @@ +#define KERNEL_SIZE 5 +#include "gaussian_blur.metali" diff --git a/UnleashedRecomp/gpu/shader/msl/gaussian_blur_7x7.metal b/UnleashedRecomp/gpu/shader/msl/gaussian_blur_7x7.metal new file mode 100644 index 00000000..ba1bd736 --- /dev/null +++ b/UnleashedRecomp/gpu/shader/msl/gaussian_blur_7x7.metal @@ -0,0 +1,2 @@ +#define KERNEL_SIZE 7 +#include "gaussian_blur.metali" diff --git a/UnleashedRecomp/gpu/shader/msl/gaussian_blur_9x9.metal b/UnleashedRecomp/gpu/shader/msl/gaussian_blur_9x9.metal new file mode 100644 index 00000000..50198a76 --- /dev/null +++ b/UnleashedRecomp/gpu/shader/msl/gaussian_blur_9x9.metal @@ -0,0 +1,2 @@ +#define KERNEL_SIZE 9 +#include "gaussian_blur.metali" diff --git a/UnleashedRecomp/gpu/shader/msl/imgui_common.metali b/UnleashedRecomp/gpu/shader/msl/imgui_common.metali new file mode 100644 index 00000000..35cc10ef --- /dev/null +++ b/UnleashedRecomp/gpu/shader/msl/imgui_common.metali @@ -0,0 +1,41 @@ +#pragma once +#include + +using namespace metal; + +#include "../../imgui/imgui_common.h" + +struct PushConstants +{ + float2 BoundsMin; + float2 BoundsMax; + uint GradientTopLeft; + uint GradientTopRight; + uint GradientBottomRight; + uint GradientBottomLeft; + uint ShaderModifier; + uint Texture2DDescriptorIndex; + float2 DisplaySize; + float2 InverseDisplaySize; + float2 Origin; + float2 Scale; + float2 ProceduralOrigin; + float Outline; +}; + +struct Interpolators +{ + float4 Position [[position]]; + float2 UV; + float4 Color; +}; + +struct Texture2DDescriptorHeap +{ + texture2d tex; +}; + +struct SamplerDescriptorHeap +{ + sampler samp; +}; \ No newline at end of file diff --git a/UnleashedRecomp/gpu/shader/msl/imgui_ps.metal b/UnleashedRecomp/gpu/shader/msl/imgui_ps.metal new file mode 100644 index 00000000..a42cd63d --- /dev/null +++ b/UnleashedRecomp/gpu/shader/msl/imgui_ps.metal @@ -0,0 +1,235 @@ +#include "imgui_common.metali" + +float4 DecodeColor(uint color) +{ + return float4(color & 0xFF, (color >> 8) & 0xFF, (color >> 16) & 0xFF, (color >> 24) & 0xFF) / 255.0; +} + +float4 SamplePoint(int2 position, constant PushConstants& g_PushConstants) +{ + switch (g_PushConstants.ShaderModifier) + { + case IMGUI_SHADER_MODIFIER_SCANLINE: + { + if (int(position.y) % 2 == 0) + return float4(1.0, 1.0, 1.0, 0.0); + + break; + } + case IMGUI_SHADER_MODIFIER_CHECKERBOARD: + { + int remnantX = int(position.x) % 9; + int remnantY = int(position.y) % 9; + + float4 color = 1.0; + + if (remnantX == 0 || remnantY == 0) + color.a = 0.0; + + if ((remnantY % 2) == 0) + color.rgb = 0.5; + + return color; + } + case IMGUI_SHADER_MODIFIER_SCANLINE_BUTTON: + { + if (int(position.y) % 2 == 0) + return float4(1.0, 1.0, 1.0, 0.5); + + break; + } + } + + return 1.0; +} + +float4 SampleLinear(float2 uvTexspace, constant PushConstants& g_PushConstants) +{ + int2 integerPart = int2(floor(uvTexspace)); + float2 fracPart = fract(uvTexspace); + + float4 topLeft = SamplePoint(integerPart + int2(0, 0), g_PushConstants); + float4 topRight = SamplePoint(integerPart + int2(1, 0), g_PushConstants); + float4 bottomLeft = SamplePoint(integerPart + int2(0, 1), g_PushConstants); + float4 bottomRight = SamplePoint(integerPart + int2(1, 1), g_PushConstants); + + float4 top = mix(topLeft, topRight, fracPart.x); + float4 bottom = mix(bottomLeft, bottomRight, fracPart.x); + + return mix(top, bottom, fracPart.y); +} + +float4 PixelAntialiasing(float2 uvTexspace, constant PushConstants& g_PushConstants) +{ + if ((g_PushConstants.DisplaySize.x * g_PushConstants.InverseDisplaySize.y) >= (4.0 / 3.0)) + uvTexspace *= g_PushConstants.InverseDisplaySize.y * 720.0; + else + uvTexspace *= g_PushConstants.InverseDisplaySize.x * 960.0; + + float2 seam = floor(uvTexspace + 0.5); + uvTexspace = (uvTexspace - seam) / fwidth(uvTexspace) + seam; + uvTexspace = clamp(uvTexspace, seam - 0.5, seam + 0.5); + + return SampleLinear(uvTexspace - 0.5, g_PushConstants); +} + +float median(float r, float g, float b) +{ + return max(min(r, g), min(max(r, g), b)); +} + +float4 SampleSdfFont(float4 color, texture2d texture, float2 uv, float2 screenTexSize, + constant SamplerDescriptorHeap* g_SamplerDescriptorHeap, + constant PushConstants& g_PushConstants) +{ + float4 textureColor = texture.sample(g_SamplerDescriptorHeap[0].samp, uv); + + uint width = texture.get_width(); + uint height = texture.get_height(); + + float pxRange = 8.0; + float2 unitRange = pxRange / float2(width, height); + float screenPxRange = max(0.5 * dot(unitRange, screenTexSize), 1.0); + + float sd = median(textureColor.r, textureColor.g, textureColor.b) - 0.5; + float screenPxDistance = screenPxRange * (sd + g_PushConstants.Outline / (pxRange * 1.5)); + + if (g_PushConstants.ShaderModifier == IMGUI_SHADER_MODIFIER_TITLE_BEVEL) + { + float2 normal = normalize(float3(dfdx(sd), dfdy(sd), 0.01)).xy; + float3 rimColor = float3(1, 0.8, 0.29); + float3 shadowColor = float3(0.84, 0.57, 0); + + float cosTheta = dot(normal, normalize(float2(1, 1))); + float3 gradient = mix(color.rgb, cosTheta >= 0.0 ? rimColor : shadowColor, abs(cosTheta)); + color.rgb = mix(gradient, color.rgb, pow(saturate(sd + 0.77), 32.0)); + } + else if (g_PushConstants.ShaderModifier == IMGUI_SHADER_MODIFIER_CATEGORY_BEVEL) + { + float2 normal = normalize(float3(dfdx(sd), dfdy(sd), 0.25)).xy; + float cosTheta = dot(normal, normalize(float2(1, 1))); + float gradient = 1.0 + cosTheta * 0.5; + color.rgb = saturate(color.rgb * gradient); + } + else if (g_PushConstants.ShaderModifier == IMGUI_SHADER_MODIFIER_TEXT_SKEW) + { + float2 normal = normalize(float3(dfdx(sd), dfdy(sd), 0.5)).xy; + float cosTheta = dot(normal, normalize(float2(1, 1))); + float gradient = saturate(1.0 + cosTheta); + color.rgb = mix(color.rgb * gradient, color.rgb, pow(saturate(sd + 0.77), 32.0)); + } + + color.a *= saturate(screenPxDistance + 0.5); + color.a *= textureColor.a; + + return color; +} + +[[fragment]] +float4 shaderMain(Interpolators interpolators [[stage_in]], + constant Texture2DDescriptorHeap* g_Texture2DDescriptorHeap [[buffer(0)]], + constant SamplerDescriptorHeap* g_SamplerDescriptorHeap [[buffer(1)]], + constant PushConstants& g_PushConstants [[buffer(4)]]) +{ + float4 color = interpolators.Color; + color *= PixelAntialiasing(interpolators.Position.xy - g_PushConstants.ProceduralOrigin, g_PushConstants); + + if (g_PushConstants.Texture2DDescriptorIndex != 0) + { + texture2d texture = g_Texture2DDescriptorHeap[g_PushConstants.Texture2DDescriptorIndex & 0x7FFFFFFF].tex; + + if ((g_PushConstants.Texture2DDescriptorIndex & 0x80000000) != 0) + { + if (g_PushConstants.ShaderModifier == IMGUI_SHADER_MODIFIER_LOW_QUALITY_TEXT) + { + float scale; + float invScale; + + if ((g_PushConstants.DisplaySize.x * g_PushConstants.InverseDisplaySize.y) >= (4.0 / 3.0)) + { + scale = g_PushConstants.InverseDisplaySize.y * 720.0; + invScale = g_PushConstants.DisplaySize.y / 720.0; + } + else + { + scale = g_PushConstants.InverseDisplaySize.x * 960.0; + invScale = g_PushConstants.DisplaySize.x / 960.0; + } + + float2 lowQualityPosition = (interpolators.Position.xy - 0.5) * scale; + float2 fracPart = fract(lowQualityPosition); + + float2 uvStep = fwidth(interpolators.UV) * invScale; + float2 lowQualityUV = interpolators.UV - fracPart * uvStep; + float2 screenTexSize = 1.0 / uvStep; + + float4 topLeft = SampleSdfFont(color, texture, lowQualityUV + float2(0, 0), screenTexSize, g_SamplerDescriptorHeap, g_PushConstants); + float4 topRight = SampleSdfFont(color, texture, lowQualityUV + float2(uvStep.x, 0), screenTexSize, g_SamplerDescriptorHeap, g_PushConstants); + float4 bottomLeft = SampleSdfFont(color, texture, lowQualityUV + float2(0, uvStep.y), screenTexSize, g_SamplerDescriptorHeap, g_PushConstants); + float4 bottomRight = SampleSdfFont(color, texture, lowQualityUV + uvStep.xy, screenTexSize, g_SamplerDescriptorHeap, g_PushConstants); + + float4 top = mix(topLeft, topRight, fracPart.x); + float4 bottom = mix(bottomLeft, bottomRight, fracPart.x); + + color = mix(top, bottom, fracPart.y); + } + else + { + color = SampleSdfFont(color, texture, interpolators.UV, 1.0 / fwidth(interpolators.UV), g_SamplerDescriptorHeap, g_PushConstants); + } + } + else + { + color *= texture.sample(g_SamplerDescriptorHeap[0].samp, interpolators.UV); + } + } + + if (g_PushConstants.ShaderModifier == IMGUI_SHADER_MODIFIER_HORIZONTAL_MARQUEE_FADE) + { + float minAlpha = saturate((interpolators.Position.x - g_PushConstants.BoundsMin.x) / g_PushConstants.Scale.x); + float maxAlpha = saturate((g_PushConstants.BoundsMax.x - interpolators.Position.x) / g_PushConstants.Scale.y); + + color.a *= minAlpha; + color.a *= maxAlpha; + } + else if (g_PushConstants.ShaderModifier == IMGUI_SHADER_MODIFIER_VERTICAL_MARQUEE_FADE) + { + float minAlpha = saturate((interpolators.Position.y - g_PushConstants.BoundsMin.y) / g_PushConstants.Scale.x); + float maxAlpha = saturate((g_PushConstants.BoundsMax.y - interpolators.Position.y) / g_PushConstants.Scale.y); + + color.a *= minAlpha; + color.a *= maxAlpha; + } + else if (any(g_PushConstants.BoundsMin != g_PushConstants.BoundsMax)) + { + float2 factor = saturate((interpolators.Position.xy - g_PushConstants.BoundsMin) / (g_PushConstants.BoundsMax - g_PushConstants.BoundsMin)); + + if (g_PushConstants.ShaderModifier == IMGUI_SHADER_MODIFIER_RECTANGLE_BEVEL) + { + float bevelSize = 0.9; + + float shadow = saturate((factor.x - bevelSize) / (1.0 - bevelSize)); + shadow = max(shadow, saturate((factor.y - bevelSize) / (1.0 - bevelSize))); + + float rim = saturate((1.0 - factor.x - bevelSize) / (1.0 - bevelSize)); + rim = max(rim, saturate((1.0 - factor.y - bevelSize) / (1.0 - bevelSize))); + + float3 rimColor = float3(1, 0.8, 0.29); + float3 shadowColor = float3(0.84, 0.57, 0); + + color.rgb = mix(color.rgb, rimColor, smoothstep(0.0, 1.0, rim)); + color.rgb = mix(color.rgb, shadowColor, smoothstep(0.0, 1.0, shadow)); + } + else + { + float4 top = mix(DecodeColor(g_PushConstants.GradientTopLeft), DecodeColor(g_PushConstants.GradientTopRight), smoothstep(0.0, 1.0, factor.x)); + float4 bottom = mix(DecodeColor(g_PushConstants.GradientBottomLeft), DecodeColor(g_PushConstants.GradientBottomRight), smoothstep(0.0, 1.0, factor.x)); + color *= mix(top, bottom, smoothstep(0.0, 1.0, factor.y)); + } + } + + if (g_PushConstants.ShaderModifier == IMGUI_SHADER_MODIFIER_GRAYSCALE) + color.rgb = dot(color.rgb, float3(0.2126, 0.7152, 0.0722)); + + return color; +} diff --git a/UnleashedRecomp/gpu/shader/msl/imgui_vs.metal b/UnleashedRecomp/gpu/shader/msl/imgui_vs.metal new file mode 100644 index 00000000..67a32887 --- /dev/null +++ b/UnleashedRecomp/gpu/shader/msl/imgui_vs.metal @@ -0,0 +1,32 @@ +#include "imgui_common.metali" + +struct VertexStageIn +{ + float2 position [[attribute(0)]]; + float2 uv [[attribute(1)]]; + float4 color [[attribute(2)]]; +}; + +[[vertex]] +Interpolators shaderMain(VertexStageIn input [[stage_in]], + constant PushConstants& g_PushConstants [[buffer(4)]]) +{ + Interpolators interpolators = Interpolators{}; + + if (g_PushConstants.ShaderModifier == IMGUI_SHADER_MODIFIER_TEXT_SKEW) + { + if (input.position.y < g_PushConstants.Origin.y) + input.position.x += g_PushConstants.Scale.x; + } + else if (g_PushConstants.ShaderModifier != IMGUI_SHADER_MODIFIER_HORIZONTAL_MARQUEE_FADE && + g_PushConstants.ShaderModifier != IMGUI_SHADER_MODIFIER_VERTICAL_MARQUEE_FADE) + { + input.position.xy = g_PushConstants.Origin + (input.position.xy - g_PushConstants.Origin) * g_PushConstants.Scale; + } + + interpolators.Position = float4(input.position.xy * g_PushConstants.InverseDisplaySize * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0); + interpolators.UV = input.uv; + interpolators.Color = input.color; + + return interpolators; +} diff --git a/UnleashedRecomp/gpu/shader/msl/movie_common.metali b/UnleashedRecomp/gpu/shader/msl/movie_common.metali new file mode 100644 index 00000000..9193155a --- /dev/null +++ b/UnleashedRecomp/gpu/shader/msl/movie_common.metali @@ -0,0 +1,39 @@ +#pragma once + +#include "../../../../tools/XenosRecomp/XenosRecomp/shader_common.h" + +#define fZmin (*(reinterpret_cast(g_PushConstants.PixelShaderConstants + 0))) +#define fZmax (*(reinterpret_cast(g_PushConstants.PixelShaderConstants + 16))) + +#define Tex0_ResourceDescriptorIndex (*(reinterpret_cast(g_PushConstants.SharedConstants + 0))) +#define Tex1_ResourceDescriptorIndex (*(reinterpret_cast(g_PushConstants.SharedConstants + 4))) +#define Tex2_ResourceDescriptorIndex (*(reinterpret_cast(g_PushConstants.SharedConstants + 8))) +#define Tex3_ResourceDescriptorIndex (*(reinterpret_cast(g_PushConstants.SharedConstants + 12))) +#define Tex4_ResourceDescriptorIndex (*(reinterpret_cast(g_PushConstants.SharedConstants + 16))) + +#define Tex0_SamplerDescriptorIndex (*(reinterpret_cast(g_PushConstants.SharedConstants + 64))) +#define Tex1_SamplerDescriptorIndex (*(reinterpret_cast(g_PushConstants.SharedConstants + 68))) +#define Tex2_SamplerDescriptorIndex (*(reinterpret_cast(g_PushConstants.SharedConstants + 72))) +#define Tex3_SamplerDescriptorIndex (*(reinterpret_cast(g_PushConstants.SharedConstants + 76))) +#define Tex4_SamplerDescriptorIndex (*(reinterpret_cast(g_PushConstants.SharedConstants + 80))) + +#define bCsc (g_Booleans & (1 << (16 + 0))) +#define bAmv (g_Booleans & (1 << (16 + 1))) +#define bZmv (g_Booleans & (1 << (16 + 2))) + +struct VertexShaderInput +{ + float4 ObjPos [[attribute(0)]]; + float2 UV [[attribute(4)]]; +}; + +struct Interpolators +{ + float4 ProjPos [[position]]; + float2 UV; +}; + +struct PixelShaderOutput +{ + float4 Color [[color(0)]]; +}; diff --git a/UnleashedRecomp/gpu/shader/msl/movie_ps.metal b/UnleashedRecomp/gpu/shader/msl/movie_ps.metal new file mode 100644 index 00000000..36432bb5 --- /dev/null +++ b/UnleashedRecomp/gpu/shader/msl/movie_ps.metal @@ -0,0 +1,104 @@ +#include "movie_common.metali" + +[[fragment]] +PixelShaderOutput shaderMain(Interpolators In [[stage_in]], + constant Texture2DDescriptorHeap* g_Texture2DDescriptorHeap [[buffer(0)]], + constant SamplerDescriptorHeap* g_SamplerDescriptorHeap [[buffer(3)]], + constant PushConstants& g_PushConstants [[buffer(4)]]) +{ + texture2d Tex0 = g_Texture2DDescriptorHeap[Tex0_ResourceDescriptorIndex].tex; + texture2d Tex1 = g_Texture2DDescriptorHeap[Tex1_ResourceDescriptorIndex].tex; + texture2d Tex2 = g_Texture2DDescriptorHeap[Tex2_ResourceDescriptorIndex].tex; + texture2d Tex3 = g_Texture2DDescriptorHeap[Tex3_ResourceDescriptorIndex].tex; + texture2d Tex4 = g_Texture2DDescriptorHeap[Tex4_ResourceDescriptorIndex].tex; + + sampler Tex0_s = g_SamplerDescriptorHeap[Tex0_SamplerDescriptorIndex].samp; + sampler Tex1_s = g_SamplerDescriptorHeap[Tex1_SamplerDescriptorIndex].samp; + sampler Tex2_s = g_SamplerDescriptorHeap[Tex2_SamplerDescriptorIndex].samp; + sampler Tex3_s = g_SamplerDescriptorHeap[Tex3_SamplerDescriptorIndex].samp; + sampler Tex4_s = g_SamplerDescriptorHeap[Tex4_SamplerDescriptorIndex].samp; + + PixelShaderOutput Out; + float ValY = Tex0.sample(Tex0_s, In.UV).r; + float ValU = Tex1.sample(Tex1_s, In.UV).r - 0.5; + float ValV = Tex2.sample(Tex2_s, In.UV).r - 0.5; + float ValA = 1.0; + float ValD = 0.0; + if (bAmv) + ValA = (Tex3.sample(Tex3_s, In.UV).r - 0.0625) * 1.164; + if (bZmv) + { + ValD = (Tex4.sample(Tex4_s, In.UV).r - 0.0625) * 1.164; + if (ValD < 9.0 / 255.0) + { + ValD = 0.0; + } + else if (ValD < 17.0 / 255.0) + { + ValD = fZmin; + } + else if (ValD < 224.0 / 255.0) + { + ValD = (ValD - 17.0 / 255.0) / (223.0 / 255.0 - 17.0 / 255.0) * (fZmax - fZmin) + fZmin; + } + else if (ValD < 240.0 / 255.0) + { + ValD = fZmax; + } + else + { + ValD = 1.0; + } + } + if (bCsc) + { + if (ValY < 16.0 / 255.0) + { + ValY = ValY * 3.0 / 2.0; + } + else if (ValY < 176.0 / 255.0) + { + ValY = 24.0 / 255.0 + (ValY - 16.0 / 255.0) / 2.0; + } + else if (ValY < 192.0 / 255.0) + { + ValY = 104.0 / 255.0 + (ValY - 176.0 / 255.0) / 1.0; + } + else + { + ValY = 120.0 / 255.0 + (ValY - 192.0 / 255.0) * 2.0; + } + if (abs(ValU) < 24.0 / 255.0) + { + ValU /= 3.0; + } + else + { + ValU = (8.0 / 255.0 + (abs(ValU) - 24.0 / 255.0) * (120.0 / 104.0)) * sign(ValU); + } + if (abs(ValV) < 24.0 / 255.0) + { + ValV /= 3.0; + } + else + { + ValV = (8.0 / 255.0 + (abs(ValV) - 24.0 / 255.0) * (120.0 / 104.0)) * sign(ValV); + } + Out.Color.r = ValY + ValV * 1.402; + Out.Color.g = ValY - ValU * 0.344 - ValV * 0.714; + Out.Color.b = ValY + ValU * 1.772; + } + else + { + ValY = (ValY - 0.0625) * 1.164; + Out.Color.r = ValY + ValV * 1.596; + Out.Color.g = ValY - ValU * 0.392 - ValV * 0.813; + Out.Color.b = ValY + ValU * 2.017; + } + Out.Color.a = ValA; + + if (any(In.UV < 0.0) || any(In.UV > 1.0)) + Out.Color.rgb = 0.0; + + return Out; +} diff --git a/UnleashedRecomp/gpu/shader/msl/movie_vs.metal b/UnleashedRecomp/gpu/shader/msl/movie_vs.metal new file mode 100644 index 00000000..1583d235 --- /dev/null +++ b/UnleashedRecomp/gpu/shader/msl/movie_vs.metal @@ -0,0 +1,10 @@ +#include "movie_common.metali" + +[[vertex]] +Interpolators shaderMain(VertexShaderInput In [[stage_in]]) +{ + Interpolators Out; + Out.ProjPos = In.ObjPos; + Out.UV = In.UV; + return Out; +} diff --git a/UnleashedRecomp/gpu/shader/msl/resolve_msaa_color.metali b/UnleashedRecomp/gpu/shader/msl/resolve_msaa_color.metali new file mode 100644 index 00000000..518089ec --- /dev/null +++ b/UnleashedRecomp/gpu/shader/msl/resolve_msaa_color.metali @@ -0,0 +1,21 @@ +#pragma once + +#include "copy_common.metali" + +struct Texture2DMSDescriptorHeap +{ + texture2d_ms tex; +}; + +[[fragment]] +float4 shaderMain(float4 position [[position]], + constant Texture2DMSDescriptorHeap* g_Texture2DMSDescriptorHeap [[buffer(0)]], + constant PushConstants& g_PushConstants [[buffer(4)]]) +{ + float4 result = g_Texture2DMSDescriptorHeap[g_PushConstants.ResourceDescriptorIndex].tex.read(uint2(position.xy), 0); + + for (int i = 1; i < SAMPLE_COUNT; i++) + result += g_Texture2DMSDescriptorHeap[g_PushConstants.ResourceDescriptorIndex].tex.read(uint2(position.xy), i); + + return result / SAMPLE_COUNT; +} diff --git a/UnleashedRecomp/gpu/shader/msl/resolve_msaa_color_2x.metal b/UnleashedRecomp/gpu/shader/msl/resolve_msaa_color_2x.metal new file mode 100644 index 00000000..6aa43bb0 --- /dev/null +++ b/UnleashedRecomp/gpu/shader/msl/resolve_msaa_color_2x.metal @@ -0,0 +1,2 @@ +#define SAMPLE_COUNT 2 +#include "resolve_msaa_color.metali" diff --git a/UnleashedRecomp/gpu/shader/msl/resolve_msaa_color_4x.metal b/UnleashedRecomp/gpu/shader/msl/resolve_msaa_color_4x.metal new file mode 100644 index 00000000..17878881 --- /dev/null +++ b/UnleashedRecomp/gpu/shader/msl/resolve_msaa_color_4x.metal @@ -0,0 +1,2 @@ +#define SAMPLE_COUNT 4 +#include "resolve_msaa_color.metali" diff --git a/UnleashedRecomp/gpu/shader/msl/resolve_msaa_color_8x.metal b/UnleashedRecomp/gpu/shader/msl/resolve_msaa_color_8x.metal new file mode 100644 index 00000000..497f2ed2 --- /dev/null +++ b/UnleashedRecomp/gpu/shader/msl/resolve_msaa_color_8x.metal @@ -0,0 +1,2 @@ +#define SAMPLE_COUNT 8 +#include "resolve_msaa_color.metali" diff --git a/UnleashedRecomp/gpu/shader/msl/resolve_msaa_depth.metali b/UnleashedRecomp/gpu/shader/msl/resolve_msaa_depth.metali new file mode 100644 index 00000000..8f9a14cd --- /dev/null +++ b/UnleashedRecomp/gpu/shader/msl/resolve_msaa_depth.metali @@ -0,0 +1,30 @@ +#pragma once + +#include "copy_common.metali" + +struct Texture2DMSDescriptorHeap +{ + texture2d_ms tex; +}; + +struct PixelShaderOutput +{ + float oDepth [[depth(any)]]; +}; + +[[fragment]] +PixelShaderOutput shaderMain(float4 position [[position]], + constant Texture2DMSDescriptorHeap* g_Texture2DMSDescriptorHeap [[buffer(0)]], + constant PushConstants& g_PushConstants [[buffer(4)]]) +{ + PixelShaderOutput output = PixelShaderOutput{}; + + float result = g_Texture2DMSDescriptorHeap[g_PushConstants.ResourceDescriptorIndex].tex.read(uint2(position.xy), 0).x; + + for (int i = 1; i < SAMPLE_COUNT; i++) + result = min(result, g_Texture2DMSDescriptorHeap[g_PushConstants.ResourceDescriptorIndex].tex.read(uint2(position.xy), i).x); + + output.oDepth = result; + + return output; +} diff --git a/UnleashedRecomp/gpu/shader/msl/resolve_msaa_depth_2x.metal b/UnleashedRecomp/gpu/shader/msl/resolve_msaa_depth_2x.metal new file mode 100644 index 00000000..cd3be171 --- /dev/null +++ b/UnleashedRecomp/gpu/shader/msl/resolve_msaa_depth_2x.metal @@ -0,0 +1,2 @@ +#define SAMPLE_COUNT 2 +#include "resolve_msaa_depth.metali" diff --git a/UnleashedRecomp/gpu/shader/msl/resolve_msaa_depth_4x.metal b/UnleashedRecomp/gpu/shader/msl/resolve_msaa_depth_4x.metal new file mode 100644 index 00000000..e69b2c00 --- /dev/null +++ b/UnleashedRecomp/gpu/shader/msl/resolve_msaa_depth_4x.metal @@ -0,0 +1,2 @@ +#define SAMPLE_COUNT 4 +#include "resolve_msaa_depth.metali" diff --git a/UnleashedRecomp/gpu/shader/msl/resolve_msaa_depth_8x.metal b/UnleashedRecomp/gpu/shader/msl/resolve_msaa_depth_8x.metal new file mode 100644 index 00000000..eaa42e5c --- /dev/null +++ b/UnleashedRecomp/gpu/shader/msl/resolve_msaa_depth_8x.metal @@ -0,0 +1,2 @@ +#define SAMPLE_COUNT 8 +#include "resolve_msaa_depth.metali" diff --git a/UnleashedRecomp/gpu/video.cpp b/UnleashedRecomp/gpu/video.cpp index 1f062911..7237cce4 100644 --- a/UnleashedRecomp/gpu/video.cpp +++ b/UnleashedRecomp/gpu/video.cpp @@ -37,57 +37,84 @@ #include #endif +#define UNLEASHED_RECOMP #include "../../tools/XenosRecomp/XenosRecomp/shader_common.h" #ifdef UNLEASHED_RECOMP_D3D12 -#include "shader/blend_color_alpha_ps.hlsl.dxil.h" -#include "shader/copy_vs.hlsl.dxil.h" -#include "shader/copy_color_ps.hlsl.dxil.h" -#include "shader/copy_depth_ps.hlsl.dxil.h" -#include "shader/csd_filter_ps.hlsl.dxil.h" -#include "shader/csd_no_tex_vs.hlsl.dxil.h" -#include "shader/csd_vs.hlsl.dxil.h" -#include "shader/enhanced_motion_blur_ps.hlsl.dxil.h" -#include "shader/gamma_correction_ps.hlsl.dxil.h" -#include "shader/gaussian_blur_3x3.hlsl.dxil.h" -#include "shader/gaussian_blur_5x5.hlsl.dxil.h" -#include "shader/gaussian_blur_7x7.hlsl.dxil.h" -#include "shader/gaussian_blur_9x9.hlsl.dxil.h" -#include "shader/imgui_ps.hlsl.dxil.h" -#include "shader/imgui_vs.hlsl.dxil.h" -#include "shader/movie_ps.hlsl.dxil.h" -#include "shader/movie_vs.hlsl.dxil.h" -#include "shader/resolve_msaa_color_2x.hlsl.dxil.h" -#include "shader/resolve_msaa_color_4x.hlsl.dxil.h" -#include "shader/resolve_msaa_color_8x.hlsl.dxil.h" -#include "shader/resolve_msaa_depth_2x.hlsl.dxil.h" -#include "shader/resolve_msaa_depth_4x.hlsl.dxil.h" -#include "shader/resolve_msaa_depth_8x.hlsl.dxil.h" +#include "shader/hlsl/blend_color_alpha_ps.hlsl.dxil.h" +#include "shader/hlsl/copy_vs.hlsl.dxil.h" +#include "shader/hlsl/copy_color_ps.hlsl.dxil.h" +#include "shader/hlsl/copy_depth_ps.hlsl.dxil.h" +#include "shader/hlsl/csd_filter_ps.hlsl.dxil.h" +#include "shader/hlsl/csd_no_tex_vs.hlsl.dxil.h" +#include "shader/hlsl/csd_vs.hlsl.dxil.h" +#include "shader/hlsl/enhanced_motion_blur_ps.hlsl.dxil.h" +#include "shader/hlsl/gamma_correction_ps.hlsl.dxil.h" +#include "shader/hlsl/gaussian_blur_3x3.hlsl.dxil.h" +#include "shader/hlsl/gaussian_blur_5x5.hlsl.dxil.h" +#include "shader/hlsl/gaussian_blur_7x7.hlsl.dxil.h" +#include "shader/hlsl/gaussian_blur_9x9.hlsl.dxil.h" +#include "shader/hlsl/imgui_ps.hlsl.dxil.h" +#include "shader/hlsl/imgui_vs.hlsl.dxil.h" +#include "shader/hlsl/movie_ps.hlsl.dxil.h" +#include "shader/hlsl/movie_vs.hlsl.dxil.h" +#include "shader/hlsl/resolve_msaa_color_2x.hlsl.dxil.h" +#include "shader/hlsl/resolve_msaa_color_4x.hlsl.dxil.h" +#include "shader/hlsl/resolve_msaa_color_8x.hlsl.dxil.h" +#include "shader/hlsl/resolve_msaa_depth_2x.hlsl.dxil.h" +#include "shader/hlsl/resolve_msaa_depth_4x.hlsl.dxil.h" +#include "shader/hlsl/resolve_msaa_depth_8x.hlsl.dxil.h" #endif -#include "shader/blend_color_alpha_ps.hlsl.spirv.h" -#include "shader/copy_vs.hlsl.spirv.h" -#include "shader/copy_color_ps.hlsl.spirv.h" -#include "shader/copy_depth_ps.hlsl.spirv.h" -#include "shader/csd_filter_ps.hlsl.spirv.h" -#include "shader/csd_no_tex_vs.hlsl.spirv.h" -#include "shader/csd_vs.hlsl.spirv.h" -#include "shader/enhanced_motion_blur_ps.hlsl.spirv.h" -#include "shader/gamma_correction_ps.hlsl.spirv.h" -#include "shader/gaussian_blur_3x3.hlsl.spirv.h" -#include "shader/gaussian_blur_5x5.hlsl.spirv.h" -#include "shader/gaussian_blur_7x7.hlsl.spirv.h" -#include "shader/gaussian_blur_9x9.hlsl.spirv.h" -#include "shader/imgui_ps.hlsl.spirv.h" -#include "shader/imgui_vs.hlsl.spirv.h" -#include "shader/movie_ps.hlsl.spirv.h" -#include "shader/movie_vs.hlsl.spirv.h" -#include "shader/resolve_msaa_color_2x.hlsl.spirv.h" -#include "shader/resolve_msaa_color_4x.hlsl.spirv.h" -#include "shader/resolve_msaa_color_8x.hlsl.spirv.h" -#include "shader/resolve_msaa_depth_2x.hlsl.spirv.h" -#include "shader/resolve_msaa_depth_4x.hlsl.spirv.h" -#include "shader/resolve_msaa_depth_8x.hlsl.spirv.h" +#ifdef UNLEASHED_RECOMP_METAL +#include "shader/msl/blend_color_alpha_ps.metal.metallib.h" +#include "shader/msl/copy_vs.metal.metallib.h" +#include "shader/msl/copy_color_ps.metal.metallib.h" +#include "shader/msl/copy_depth_ps.metal.metallib.h" +#include "shader/msl/csd_filter_ps.metal.metallib.h" +#include "shader/msl/csd_no_tex_vs.metal.metallib.h" +#include "shader/msl/csd_vs.metal.metallib.h" +#include "shader/msl/enhanced_motion_blur_ps.metal.metallib.h" +#include "shader/msl/gamma_correction_ps.metal.metallib.h" +#include "shader/msl/gaussian_blur_3x3.metal.metallib.h" +#include "shader/msl/gaussian_blur_5x5.metal.metallib.h" +#include "shader/msl/gaussian_blur_7x7.metal.metallib.h" +#include "shader/msl/gaussian_blur_9x9.metal.metallib.h" +#include "shader/msl/imgui_ps.metal.metallib.h" +#include "shader/msl/imgui_vs.metal.metallib.h" +#include "shader/msl/movie_ps.metal.metallib.h" +#include "shader/msl/movie_vs.metal.metallib.h" +#include "shader/msl/resolve_msaa_color_2x.metal.metallib.h" +#include "shader/msl/resolve_msaa_color_4x.metal.metallib.h" +#include "shader/msl/resolve_msaa_color_8x.metal.metallib.h" +#include "shader/msl/resolve_msaa_depth_2x.metal.metallib.h" +#include "shader/msl/resolve_msaa_depth_4x.metal.metallib.h" +#include "shader/msl/resolve_msaa_depth_8x.metal.metallib.h" +#endif + +#include "shader/hlsl/blend_color_alpha_ps.hlsl.spirv.h" +#include "shader/hlsl/copy_vs.hlsl.spirv.h" +#include "shader/hlsl/copy_color_ps.hlsl.spirv.h" +#include "shader/hlsl/copy_depth_ps.hlsl.spirv.h" +#include "shader/hlsl/csd_filter_ps.hlsl.spirv.h" +#include "shader/hlsl/csd_no_tex_vs.hlsl.spirv.h" +#include "shader/hlsl/csd_vs.hlsl.spirv.h" +#include "shader/hlsl/enhanced_motion_blur_ps.hlsl.spirv.h" +#include "shader/hlsl/gamma_correction_ps.hlsl.spirv.h" +#include "shader/hlsl/gaussian_blur_3x3.hlsl.spirv.h" +#include "shader/hlsl/gaussian_blur_5x5.hlsl.spirv.h" +#include "shader/hlsl/gaussian_blur_7x7.hlsl.spirv.h" +#include "shader/hlsl/gaussian_blur_9x9.hlsl.spirv.h" +#include "shader/hlsl/imgui_ps.hlsl.spirv.h" +#include "shader/hlsl/imgui_vs.hlsl.spirv.h" +#include "shader/hlsl/movie_ps.hlsl.spirv.h" +#include "shader/hlsl/movie_vs.hlsl.spirv.h" +#include "shader/hlsl/resolve_msaa_color_2x.hlsl.spirv.h" +#include "shader/hlsl/resolve_msaa_color_4x.hlsl.spirv.h" +#include "shader/hlsl/resolve_msaa_color_8x.hlsl.spirv.h" +#include "shader/hlsl/resolve_msaa_depth_2x.hlsl.spirv.h" +#include "shader/hlsl/resolve_msaa_depth_4x.hlsl.spirv.h" +#include "shader/hlsl/resolve_msaa_depth_8x.hlsl.spirv.h" #ifdef _WIN32 extern "C" @@ -102,6 +129,9 @@ namespace plume #ifdef UNLEASHED_RECOMP_D3D12 extern std::unique_ptr CreateD3D12Interface(); #endif +#ifdef UNLEASHED_RECOMP_METAL + extern std::unique_ptr CreateMetalInterface(); +#endif #ifdef SDL_VULKAN_ENABLED extern std::unique_ptr CreateVulkanInterface(RenderWindow sdlWindow); #else @@ -282,17 +312,14 @@ static Profiler g_swapChainAcquireProfiler; static bool g_profilerVisible; static bool g_profilerWasToggled; -#ifdef UNLEASHED_RECOMP_D3D12 -static bool g_vulkan = false; +#if !defined(UNLEASHED_RECOMP_D3D12) && !defined(UNLEASHED_RECOMP_METAL) +static constexpr Backend g_backend = Backend::VULKAN; #else -static constexpr bool g_vulkan = true; +static Backend g_backend; #endif static bool g_triangleStripWorkaround = false; -static bool g_hardwareResolve = true; -static bool g_hardwareDepthResolve = true; - static std::unique_ptr g_interface; static std::unique_ptr g_device; @@ -779,18 +806,27 @@ static std::unique_ptr g_buttonBcDiff; static void LoadEmbeddedResources() { - if (g_vulkan) + switch (g_backend) { + case Backend::VULKAN: g_shaderCache = std::make_unique(g_spirvCacheDecompressedSize); ZSTD_decompress(g_shaderCache.get(), g_spirvCacheDecompressedSize, g_compressedSpirvCache, g_spirvCacheCompressedSize); - } -#ifdef UNLEASHED_RECOMP_D3D12 - else - { - g_shaderCache = std::make_unique(g_dxilCacheDecompressedSize); - ZSTD_decompress(g_shaderCache.get(), g_dxilCacheDecompressedSize, g_compressedDxilCache, g_dxilCacheCompressedSize); - } + break; +#ifdef UNLEASHED_RECOM_D3D12 + case Backend::D3D12: + g_shaderCache = std::make_unique(g_dxilCacheDecompressedSize); + ZSTD_decompress(g_shaderCache.get(), g_dxilCacheDecompressedSize, g_compressedDxilCache, g_dxilCacheCompressedSize); + break; #endif +#ifdef UNLEASHED_RECOMP_METAL + case Backend::METAL: + g_shaderCache = std::make_unique(g_airCacheDecompressedSize); + ZSTD_decompress(g_shaderCache.get(), g_airCacheDecompressedSize, g_compressedAirCache, g_airCacheCompressedSize); + break; + default: + assert(false); +#endif + } g_buttonBcDiff = decompressZstd(g_button_bc_diff, g_button_bc_diff_uncompressed_size); } @@ -1297,10 +1333,19 @@ static std::unique_ptr g_enhancedMotionBlurShader; #define CREATE_SHADER(NAME) \ g_device->createShader( \ - g_vulkan ? g_##NAME##_spirv : g_##NAME##_dxil, \ - g_vulkan ? sizeof(g_##NAME##_spirv) : sizeof(g_##NAME##_dxil), \ + (g_backend == Backend::VULKAN) ? g_##NAME##_spirv : g_##NAME##_dxil, \ + (g_backend == Backend::VULKAN) ? sizeof(g_##NAME##_spirv) : sizeof(g_##NAME##_dxil), \ "main", \ - g_vulkan ? RenderShaderFormat::SPIRV : RenderShaderFormat::DXIL) + (g_backend == Backend::VULKAN) ? RenderShaderFormat::SPIRV : RenderShaderFormat::DXIL) + +#elif UNLEASHED_RECOMP_METAL + +#define CREATE_SHADER(NAME) \ + g_device->createShader( \ + (g_backend == Backend::VULKAN) ? g_##NAME##_spirv : g_##NAME##_air, \ + (g_backend == Backend::VULKAN) ? sizeof(g_##NAME##_spirv) : sizeof(g_##NAME##_air), \ + (g_backend == Backend::VULKAN) ? "main" : "shaderMain", \ + (g_backend == Backend::VULKAN) ? RenderShaderFormat::SPIRV : RenderShaderFormat::METAL) #else @@ -1671,7 +1716,12 @@ bool Video::CreateHostDevice(const char *sdlVideoDriver, bool graphicsApiRetry) GameWindow::Init(sdlVideoDriver); #ifdef UNLEASHED_RECOMP_D3D12 - g_vulkan = DetectWine() || Config::GraphicsAPI == EGraphicsAPI::Vulkan; + g_backend = (DetectWine() || Config::GraphicsAPI == EGraphicsAPI::Vulkan) ? Backend::VULKAN : Backend::D3D12; +#endif + +#ifdef UNLEASHED_RECOMP_METAL + // TODO: Set this based on a config value and change default to Metal + g_backend = Backend::METAL; #endif // Attempt to create the possible backends using a vector of function pointers. Whichever succeeds first will be the chosen API. @@ -1682,11 +1732,21 @@ bool Video::CreateHostDevice(const char *sdlVideoDriver, bool graphicsApiRetry) if (graphicsApiRetry) { // If we are attempting to create again after a reboot due to a crash, swap the order. - g_vulkan = !g_vulkan; + if (g_backend == Backend::VULKAN) + { + g_backend = Backend::D3D12; + } + else + { + g_backend = Backend::VULKAN; + } } - - interfaceFunctions.push_back(g_vulkan ? CreateVulkanInterfaceWrapper : CreateD3D12Interface); - interfaceFunctions.push_back(g_vulkan ? CreateD3D12Interface : CreateVulkanInterfaceWrapper); + + interfaceFunctions.push_back((g_backend == Backend::VULKAN) ? CreateVulkanInterfaceWrapper : CreateD3D12Interface); + interfaceFunctions.push_back((g_backend == Backend::VULKAN) ? CreateD3D12Interface : CreateVulkanInterfaceWrapper); +#elif UNLEASHED_RECOMP_METAL + interfaceFunctions.push_back((g_backend == Backend::VULKAN) ? CreateVulkanInterfaceWrapper : CreateMetalInterface); + interfaceFunctions.push_back((g_backend == Backend::VULKAN) ? CreateMetalInterface : CreateVulkanInterfaceWrapper); #else interfaceFunctions.push_back(CreateVulkanInterfaceWrapper); #endif @@ -1698,9 +1758,9 @@ bool Video::CreateHostDevice(const char *sdlVideoDriver, bool graphicsApiRetry) __try #endif { - g_interface = interfaceFunction(); + g_interface = interfaceFunction(); if (g_interface == nullptr) - { + { continue; } @@ -1734,11 +1794,11 @@ bool Video::CreateHostDevice(const char *sdlVideoDriver, bool graphicsApiRetry) // Allow redirection to Vulkan only if we are not retrying after a crash, // so the user can at least boot the game with D3D12 if Vulkan fails to work. if (!graphicsApiRetry && redirectToVulkan) - { - g_device.reset(); - g_interface.reset(); - continue; - } + { + g_device.reset(); + g_interface.reset(); + continue; + } // Hardware resolve seems to be completely bugged on Intel D3D12 drivers. g_hardwareResolve = (deviceDescription.vendor != RenderDeviceVendor::INTEL); @@ -1781,7 +1841,7 @@ bool Video::CreateHostDevice(const char *sdlVideoDriver, bool graphicsApiRetry) if (graphicsApiRetry) { // If we managed to create a device after retrying it in a reboot, remember the one we picked. - Config::GraphicsAPI = g_vulkan ? EGraphicsAPI::Vulkan : EGraphicsAPI::D3D12; + Config::GraphicsAPI = (g_backend == Backend::VULKAN) ? EGraphicsAPI::Vulkan : EGraphicsAPI::D3D12; } #endif @@ -1819,7 +1879,7 @@ bool Video::CreateHostDevice(const char *sdlVideoDriver, bool graphicsApiRetry) g_queue = g_device->createCommandQueue(RenderCommandListType::DIRECT); for (auto& commandList : g_commandLists) - commandList = g_device->createCommandList(RenderCommandListType::DIRECT); + commandList = g_queue->createCommandList(); for (auto& commandFence : g_commandFences) commandFence = g_device->createCommandFence(); @@ -1828,7 +1888,7 @@ bool Video::CreateHostDevice(const char *sdlVideoDriver, bool graphicsApiRetry) queryPool = g_device->createQueryPool(NUM_QUERIES); g_copyQueue = g_device->createCommandQueue(RenderCommandListType::COPY); - g_copyCommandList = g_device->createCommandList(RenderCommandListType::COPY); + g_copyCommandList = g_copyQueue->createCommandList(); g_copyCommandFence = g_device->createCommandFence(); uint32_t bufferCount = 2; @@ -1836,17 +1896,19 @@ bool Video::CreateHostDevice(const char *sdlVideoDriver, bool graphicsApiRetry) switch (Config::TripleBuffering) { case ETripleBuffering::Auto: - if (g_vulkan) - { + switch (g_backend) { + case Backend::VULKAN: // Defaulting to 3 is fine if presentWait as supported, as the maximum frame latency allowed is only 1. bufferCount = g_device->getCapabilities().presentWait ? 3 : 2; - } - else - { + break; + case Backend::D3D12: // Defaulting to 3 is fine on D3D12 thanks to flip discard model. bufferCount = 3; + break; + case Backend::METAL: + bufferCount = 2; + break; } - break; case ETripleBuffering::On: bufferCount = 3; @@ -1941,7 +2003,7 @@ bool Video::CreateHostDevice(const char *sdlVideoDriver, bool graphicsApiRetry) pipelineLayoutBuilder.addDescriptorSet(descriptorSetBuilder); - if (g_vulkan) + if (g_backend != Backend::D3D12) { pipelineLayoutBuilder.addPushConstant(0, 4, 24, RenderShaderStageFlag::VERTEX | RenderShaderStageFlag::PIXEL); } @@ -2056,12 +2118,7 @@ void Video::WaitForGPU() { g_waitForGPUCount++; - if (g_vulkan) - { - g_device->waitIdle(); - } - else - { + // Wait for all queued frames to finish. for (size_t i = 0; i < NUM_FRAMES; i++) { if (g_commandListStates[i]) @@ -2070,9 +2127,11 @@ void Video::WaitForGPU() g_commandListStates[i] = false; } } - g_queue->executeCommandLists(nullptr, g_commandFences[0].get()); + // Execute an empty command list and wait for it to end to guarantee that any remaining presentation has finished. + g_commandLists[0]->begin(); + g_commandLists[0]->end(); + g_queue->executeCommandLists(g_commandLists[0].get(), g_commandFences[0].get()); g_queue->waitForCommandFence(g_commandFences[0].get()); - } } static uint32_t CreateDevice(uint32_t a1, uint32_t a2, uint32_t a3, uint32_t a4, uint32_t a5, be* a6) @@ -2210,14 +2269,14 @@ static void UnlockBuffer(GuestBuffer* buffer, bool useCopyQueue) { auto copyBuffer = [&](T* dest) { - auto src = reinterpret_cast(buffer->mappedMemory); + auto src = reinterpret_cast(buffer->mappedMemory); - for (size_t i = 0; i < buffer->dataSize; i += sizeof(T)) - { - *dest = ByteSwap(*src); - ++dest; - ++src; - } + for (size_t i = 0; i < buffer->dataSize; i += sizeof(T)) + { + *dest = ByteSwap(*src); + ++dest; + ++src; + } }; if (useCopyQueue && g_capabilities.gpuUploadHeap) @@ -2229,25 +2288,25 @@ static void UnlockBuffer(GuestBuffer* buffer, bool useCopyQueue) { auto uploadBuffer = g_device->createBuffer(RenderBufferDesc::UploadBuffer(buffer->dataSize)); copyBuffer(reinterpret_cast(uploadBuffer->map())); - uploadBuffer->unmap(); + uploadBuffer->unmap(); - if (useCopyQueue) - { - ExecuteCopyCommandList([&] - { - g_copyCommandList->copyBufferRegion(buffer->buffer->at(0), uploadBuffer->at(0), buffer->dataSize); - }); - } - else - { - auto& commandList = g_commandLists[g_frame]; + if (useCopyQueue) + { + ExecuteCopyCommandList([&] + { + g_copyCommandList->copyBufferRegion(buffer->buffer->at(0), uploadBuffer->at(0), buffer->dataSize); + }); + } + else + { + auto& commandList = g_commandLists[g_frame]; - commandList->barriers(RenderBarrierStage::COPY, RenderBufferBarrier(buffer->buffer.get(), RenderBufferAccess::WRITE)); - commandList->copyBufferRegion(buffer->buffer->at(0), uploadBuffer->at(0), buffer->dataSize); - commandList->barriers(RenderBarrierStage::GRAPHICS, RenderBufferBarrier(buffer->buffer.get(), RenderBufferAccess::READ)); + commandList->barriers(RenderBarrierStage::COPY, RenderBufferBarrier(buffer->buffer.get(), RenderBufferAccess::WRITE)); + commandList->copyBufferRegion(buffer->buffer->at(0), uploadBuffer->at(0), buffer->dataSize); + commandList->barriers(RenderBarrierStage::GRAPHICS, RenderBufferBarrier(buffer->buffer.get(), RenderBufferAccess::READ)); - g_tempBuffers[g_frame].emplace_back(std::move(uploadBuffer)); - } + g_tempBuffers[g_frame].emplace_back(std::move(uploadBuffer)); + } } g_bufferUploadCount++; @@ -2438,12 +2497,25 @@ static void DrawProfiler() ImGui::Text("Present Wait: %s", g_capabilities.presentWait ? "Supported" : "Unsupported"); ImGui::Text("Triangle Fan: %s", g_capabilities.triangleFan ? "Supported" : "Unsupported"); ImGui::Text("Dynamic Depth Bias: %s", g_capabilities.dynamicDepthBias ? "Supported" : "Unsupported"); + ImGui::Text("Hardware Resolve Modes: %s", g_capabilities.resolveModes ? "Supported" : "Unsupported"); ImGui::Text("Triangle Strip Workaround: %s", g_triangleStripWorkaround ? "Enabled" : "Disabled"); - ImGui::Text("Hardware Resolve: %s", g_hardwareResolve ? "Enabled" : "Disabled"); - ImGui::Text("Hardware Depth Resolve: %s", g_hardwareDepthResolve ? "Enabled" : "Disabled"); ImGui::NewLine(); - ImGui::Text("API: %s", g_vulkan ? "Vulkan" : "D3D12"); + std::string backend; + + switch (g_backend) { + case Backend::VULKAN: + backend = "Vulkan"; + break; + case Backend::D3D12: + backend = "D3D12"; + break; + case Backend::METAL: + backend = "Metal"; + break; + } + + ImGui::Text("API: %s", backend.c_str()); ImGui::Text("Device: %s", g_device->getDescription().name.c_str()); ImGui::Text("Device Type: %s", DeviceTypeName(g_device->getDescription().type)); ImGui::Text("VRAM: %.2f MiB", (double)(g_device->getDescription().dedicatedVideoMemory) / (1024.0 * 1024.0)); @@ -2867,7 +2939,7 @@ static void SetRootDescriptor(const UploadAllocation& allocation, size_t index) { auto& commandList = g_commandLists[g_frame]; - if (g_vulkan) + if (g_backend != Backend::D3D12) commandList->setGraphicsPushConstants(0, &allocation.deviceAddress, 8 * index, 8); else commandList->setGraphicsRootDescriptor(allocation.buffer->at(allocation.offset), index); @@ -3354,12 +3426,9 @@ static bool PopulateBarriersForStretchRect(GuestSurface* renderTarget, GuestSurf RenderTextureLayout dstLayout; bool shaderResolve = true; - if (multiSampling && g_hardwareResolve) + if (multiSampling) { - // Hardware depth resolve is only supported on D3D12 when programmable sample positions are available. - bool hardwareDepthResolveAvailable = g_hardwareDepthResolve && !g_vulkan && g_capabilities.sampleLocations; - - if (surface->format != RenderFormat::D32_FLOAT || hardwareDepthResolveAvailable) + if (surface->format != RenderFormat::D32_FLOAT || g_capabilities.resolveModes) { srcLayout = RenderTextureLayout::RESOLVE_SOURCE; dstLayout = RenderTextureLayout::RESOLVE_DEST; @@ -3399,11 +3468,9 @@ static void ExecutePendingStretchRectCommands(GuestSurface* renderTarget, GuestS { bool shaderResolve = true; - if (multiSampling && g_hardwareResolve) + if (multiSampling) { - bool hardwareDepthResolveAvailable = g_hardwareDepthResolve && !g_vulkan && g_capabilities.sampleLocations; - - if (surface->format != RenderFormat::D32_FLOAT || hardwareDepthResolveAvailable) + if (surface->format != RenderFormat::D32_FLOAT || g_capabilities.resolveModes) { if (surface->format == RenderFormat::D32_FLOAT) commandList->resolveTextureRegion(texture->texture, 0, 0, surface->texture, nullptr, RenderResolveMode::MIN); @@ -3519,7 +3586,7 @@ static void ExecutePendingStretchRectCommands(GuestSurface* renderTarget, GuestS g_dirtyStates.pipelineState = true; g_dirtyStates.scissorRect = true; - if (g_vulkan) + if (g_backend != Backend::D3D12) { g_dirtyStates.vertexShaderConstants = true; // The push constant call invalidates vertex shader constants. g_dirtyStates.depthBias = true; // Static depth bias in copy pipeline invalidates dynamic depth bias. @@ -3832,7 +3899,7 @@ static void ProcSetScissorRect(const RenderCommand& cmd) static RenderShader* GetOrLinkShader(GuestShader* guestShader, uint32_t specConstants) { - if (g_vulkan || + if (g_backend != Backend::D3D12 || guestShader->shaderCacheEntry == nullptr || guestShader->shaderCacheEntry->specConstantsMask == 0) { @@ -3842,7 +3909,9 @@ static RenderShader* GetOrLinkShader(GuestShader* guestShader, uint32_t specCons { assert(guestShader->shaderCacheEntry != nullptr); - if (g_vulkan) + switch (g_backend) + { + case Backend::VULKAN: { auto compressedSpirvData = g_shaderCache.get() + guestShader->shaderCacheEntry->spirvOffset; @@ -3850,12 +3919,21 @@ static RenderShader* GetOrLinkShader(GuestShader* guestShader, uint32_t specCons bool result = smolv::Decode(compressedSpirvData, guestShader->shaderCacheEntry->spirvSize, decoded.data(), decoded.size()); assert(result); - guestShader->shader = g_device->createShader(decoded.data(), decoded.size(), "main", RenderShaderFormat::SPIRV); + guestShader->shader = g_device->createShader(decoded.data(), decoded.size(), "shaderMain", RenderShaderFormat::SPIRV); + break; + } + case Backend::METAL: + { + guestShader->shader = g_device->createShader(g_shaderCache.get() + guestShader->shaderCacheEntry->airOffset, + guestShader->shaderCacheEntry->airSize, "shaderMain", RenderShaderFormat::METAL); + break; } - else + case Backend::D3D12: { - guestShader->shader = g_device->createShader(g_shaderCache.get() + guestShader->shaderCacheEntry->dxilOffset, - guestShader->shaderCacheEntry->dxilSize, "main", RenderShaderFormat::DXIL); + guestShader->shader = g_device->createShader(g_shaderCache.get() + guestShader->shaderCacheEntry->dxilOffset, + guestShader->shaderCacheEntry->dxilSize, "shaderMain", RenderShaderFormat::DXIL); + break; + } } } @@ -3960,7 +4038,7 @@ static RenderShader* GetOrLinkShader(GuestShader* guestShader, uint32_t specCons const wchar_t* libraryNames[] = { specConstantsLibName, shaderLibName }; ComPtr result; - HRESULT hr = s_dxcLinker->Link(L"main", guestShader->type == ResourceType::VertexShader ? L"vs_6_0" : L"ps_6_0", + HRESULT hr = s_dxcLinker->Link(L"shaderMain", guestShader->type == ResourceType::VertexShader ? L"vs_6_0" : L"ps_6_0", libraryNames, std::size(libraryNames), nullptr, 0, result.GetAddressOf()); assert(SUCCEEDED(hr) && result != nullptr); @@ -3975,7 +4053,7 @@ static RenderShader* GetOrLinkShader(GuestShader* guestShader, uint32_t specCons auto& linkedShader = guestShader->linkedShaders[specConstants]; if (linkedShader == nullptr) { - linkedShader = g_device->createShader(blob->GetBufferPointer(), blob->GetBufferSize(), "main", RenderShaderFormat::DXIL); + linkedShader = g_device->createShader(blob->GetBufferPointer(), blob->GetBufferSize(), "shaderMain", RenderShaderFormat::DXIL); guestShader->shaderBlobs.push_back(std::move(blob)); } @@ -4473,7 +4551,7 @@ static void FlushRenderStateForRenderThread() // D3D12 resets depth bias values to the pipeline values, even if they are dynamic. // We can reduce unnecessary calls by making common depth bias values part of the pipeline. - if (g_capabilities.dynamicDepthBias && !g_vulkan) + if (g_capabilities.dynamicDepthBias && g_backend == Backend::D3D12) { bool useDepthBias = (g_depthBias != 0) || (g_slopeScaledDepthBias != 0.0f); @@ -4489,7 +4567,7 @@ static void FlushRenderStateForRenderThread() commandList->setPipeline(CreateGraphicsPipelineInRenderThread(g_pipelineState)); // D3D12 resets the depth bias values. Check if they need to be set again. - if (g_capabilities.dynamicDepthBias && !g_vulkan) + if (g_capabilities.dynamicDepthBias && g_backend == Backend::D3D12) g_dirtyStates.depthBias = (g_depthBias != g_pipelineState.depthBias) || (g_slopeScaledDepthBias != g_pipelineState.slopeScaledDepthBias); } @@ -4523,7 +4601,7 @@ static void FlushRenderStateForRenderThread() g_inputSlots + g_dirtyStates.vertexStreamFirst); } - if (g_dirtyStates.indices && (!g_vulkan || g_indexBufferView.buffer.ref != nullptr)) + if (g_dirtyStates.indices && (g_backend == Backend::D3D12 || g_indexBufferView.buffer.ref != nullptr)) commandList->setIndexBuffer(&g_indexBufferView); g_dirtyStates = DirtyStates(false); @@ -5712,10 +5790,10 @@ static bool LoadTexture(GuestTexture& texture, const uint8_t* data, size_t dataS g_copyCommandList->barriers(RenderBarrierStage::COPY, RenderTextureBarrier(texture.texture, RenderTextureLayout::COPY_DEST)); auto copyTextureRegion = [&](Slice& slice, uint32_t subresourceIndex) - { - g_copyCommandList->copyTextureRegion( - RenderTextureCopyLocation::Subresource(texture.texture, subresourceIndex), - RenderTextureCopyLocation::PlacedFootprint(uploadBuffer.get(), desc.format, slice.width, slice.height, slice.depth, (slice.dstRowPitch * 8) / ddsDesc.bitsPerPixelOrBlock * ddsDesc.blockWidth, slice.dstOffset)); + { + g_copyCommandList->copyTextureRegion( + RenderTextureCopyLocation::Subresource(texture.texture, subresourceIndex % desc.mipLevels, subresourceIndex / desc.mipLevels), + RenderTextureCopyLocation::PlacedFootprint(uploadBuffer.get(), desc.format, slice.width, slice.height, slice.depth, (slice.dstRowPitch * 8) / ddsDesc.bitsPerPixelOrBlock * ddsDesc.blockWidth, slice.dstOffset)); }; for (size_t i = 0; i < slices.size(); i++) @@ -6458,7 +6536,7 @@ static void CompileMeshPipeline(const Mesh& mesh, CompilationArgs& args) if (g_capabilities.dynamicDepthBias) { // Put common depth bias values for reducing unnecessary calls. - if (!g_vulkan) + if (g_backend == Backend::D3D12) { pipelineState.depthBias = COMMON_DEPTH_BIAS_VALUE; pipelineState.slopeScaledDepthBias = COMMON_SLOPE_SCALED_DEPTH_BIAS_VALUE; @@ -7220,7 +7298,7 @@ static void PipelineTaskConsumerThread() pipelineState.primitiveTopology = RenderPrimitiveTopology::TRIANGLE_LIST; // Zero out depth bias for Vulkan, we only store common values for D3D12. - if (g_capabilities.dynamicDepthBias && g_vulkan) + if (g_capabilities.dynamicDepthBias && g_backend != Backend::D3D12) { pipelineState.depthBias = 0; pipelineState.slopeScaledDepthBias = 0.0f; diff --git a/UnleashedRecomp/gpu/video.h b/UnleashedRecomp/gpu/video.h index b07169b9..4acf456e 100644 --- a/UnleashedRecomp/gpu/video.h +++ b/UnleashedRecomp/gpu/video.h @@ -26,6 +26,12 @@ struct Video static void ComputeViewportDimensions(); }; +enum class Backend { + VULKAN, + D3D12, + METAL +}; + struct GuestSamplerState { be data[6]; diff --git a/UnleashedRecomp/install/update_checker.cpp b/UnleashedRecomp/install/update_checker.cpp index 706a0e05..2a6f5b1f 100644 --- a/UnleashedRecomp/install/update_checker.cpp +++ b/UnleashedRecomp/install/update_checker.cpp @@ -166,6 +166,9 @@ void UpdateChecker::visitWebsite() #elif defined(__linux__) std::string command = "xdg-open " + std::string(VISIT_URL) + " &"; std::system(command.c_str()); +#elif defined(__APPLE__) + std::string command = "open " + std::string(VISIT_URL) + " &"; + std::system(command.c_str()); #else static_assert(false, "Visit website not implemented for this platform."); #endif diff --git a/UnleashedRecomp/kernel/imports.cpp b/UnleashedRecomp/kernel/imports.cpp index 2cc2aae7..58b33fab 100644 --- a/UnleashedRecomp/kernel/imports.cpp +++ b/UnleashedRecomp/kernel/imports.cpp @@ -660,7 +660,7 @@ void KeQueryBasePriorityThread() uint32_t NtSuspendThread(GuestThreadHandle* hThread, uint32_t* suspendCount) { - assert(hThread != GetKernelObject(CURRENT_THREAD_HANDLE) && hThread->thread.get_id() == std::this_thread::get_id()); + assert(hThread != GetKernelObject(CURRENT_THREAD_HANDLE) && hThread->GetThreadId() == GuestThread::GetCurrentThreadId()); hThread->suspended = true; hThread->suspended.wait(true); diff --git a/UnleashedRecomp/kernel/xam.cpp b/UnleashedRecomp/kernel/xam.cpp index 3d7ca773..357b7911 100644 --- a/UnleashedRecomp/kernel/xam.cpp +++ b/UnleashedRecomp/kernel/xam.cpp @@ -306,26 +306,26 @@ uint32_t XamContentCreateEx(uint32_t dwUserIndex, const char* szRootName, const if (!exists) { - std::string root = ""; + std::filesystem::path rootPath; if (pContentData->dwContentType == XCONTENTTYPE_SAVEDATA) { - std::u8string savePathU8 = GetSavePath(true).u8string(); - root = (const char *)(savePathU8.c_str()); + rootPath = GetSavePath(true); } else if (pContentData->dwContentType == XCONTENTTYPE_DLC) { - root = GAME_INSTALL_DIRECTORY "/dlc"; + rootPath = GetGamePath() / "dlc"; } else { - root = GAME_INSTALL_DIRECTORY; + rootPath = GetGamePath(); } + const std::string root = (const char*)rootPath.u8string().c_str(); XamRegisterContent(*pContentData, root); std::error_code ec; - std::filesystem::create_directory(std::u8string_view((const char8_t*)(root.c_str())), ec); + std::filesystem::create_directory(rootPath, ec); XamRootCreate(szRootName, root); } diff --git a/UnleashedRecomp/main.cpp b/UnleashedRecomp/main.cpp index 04e3fb10..4d8c58e4 100644 --- a/UnleashedRecomp/main.cpp +++ b/UnleashedRecomp/main.cpp @@ -1,5 +1,7 @@ #include +#ifdef __x86_64__ #include +#endif #include #include #include @@ -69,8 +71,10 @@ void KiSystemStartup() const auto gameContent = XamMakeContent(XCONTENTTYPE_RESERVED, "Game"); const auto updateContent = XamMakeContent(XCONTENTTYPE_RESERVED, "Update"); - XamRegisterContent(gameContent, GAME_INSTALL_DIRECTORY "/game"); - XamRegisterContent(updateContent, GAME_INSTALL_DIRECTORY "/update"); + const std::string gamePath = (const char*)(GetGamePath() / "game").u8string().c_str(); + const std::string updatePath = (const char*)(GetGamePath() / "update").u8string().c_str(); + XamRegisterContent(gameContent, gamePath); + XamRegisterContent(updateContent, updatePath); const auto saveFilePath = GetSaveFilePath(true); bool saveFileExists = std::filesystem::exists(saveFilePath); @@ -102,7 +106,7 @@ void KiSystemStartup() XamContentCreateEx(0, "D", &gameContent, OPEN_EXISTING, nullptr, nullptr, 0, 0, nullptr); std::error_code ec; - for (auto& file : std::filesystem::directory_iterator(GAME_INSTALL_DIRECTORY "/dlc", ec)) + for (auto& file : std::filesystem::directory_iterator(GetGamePath() / "dlc", ec)) { if (file.is_directory()) { @@ -165,10 +169,10 @@ uint32_t LdrLoadModule(const std::filesystem::path &path) return entry; } +#ifdef __x86_64__ __attribute__((constructor(101), target("no-avx,no-avx2"), noinline)) void init() { -#ifdef __x86_64__ uint32_t eax, ebx, ecx, edx; // Execute CPUID for processor info and feature bits. @@ -185,8 +189,8 @@ void init() std::_Exit(1); } -#endif } +#endif int main(int argc, char *argv[]) { @@ -324,7 +328,7 @@ int main(int argc, char *argv[]) HostStartup(); std::filesystem::path modulePath; - bool isGameInstalled = Installer::checkGameInstall(GAME_INSTALL_DIRECTORY, modulePath); + bool isGameInstalled = Installer::checkGameInstall(GetGamePath(), modulePath); bool runInstallerWizard = forceInstaller || forceDLCInstaller || !isGameInstalled; if (runInstallerWizard) { @@ -334,7 +338,7 @@ int main(int argc, char *argv[]) std::_Exit(1); } - if (!InstallerWizard::Run(GAME_INSTALL_DIRECTORY, isGameInstalled && forceDLCInstaller)) + if (!InstallerWizard::Run(GetGamePath(), isGameInstalled && forceDLCInstaller)) { std::_Exit(0); } diff --git a/UnleashedRecomp/mod/mod_loader.cpp b/UnleashedRecomp/mod/mod_loader.cpp index 6fc3aec5..804cc2f0 100644 --- a/UnleashedRecomp/mod/mod_loader.cpp +++ b/UnleashedRecomp/mod/mod_loader.cpp @@ -100,7 +100,7 @@ void ModLoader::Init() { configIni = {}; - if (!configIni.read(GAME_INSTALL_DIRECTORY "/cpkredir.ini")) + if (!configIni.read(GetGamePath() / "cpkredir.ini")) return; } diff --git a/UnleashedRecomp/os/linux/process_linux.cpp b/UnleashedRecomp/os/linux/process_linux.cpp index bb93b1ec..0af7ab64 100644 --- a/UnleashedRecomp/os/linux/process_linux.cpp +++ b/UnleashedRecomp/os/linux/process_linux.cpp @@ -15,6 +15,11 @@ std::filesystem::path os::process::GetExecutablePath() } } +std::filesystem::path os::process::GetExecutableRoot() +{ + return GetExecutablePath().remove_filename(); +} + std::filesystem::path os::process::GetWorkingDirectory() { char cwd[PATH_MAX] = {}; diff --git a/UnleashedRecomp/os/macos/logger_macos.cpp b/UnleashedRecomp/os/macos/logger_macos.cpp new file mode 100644 index 00000000..df5708dd --- /dev/null +++ b/UnleashedRecomp/os/macos/logger_macos.cpp @@ -0,0 +1,17 @@ +#include + +void os::logger::Init() +{ +} + +void os::logger::Log(const std::string_view str, ELogType type, const char* func) +{ + if (func) + { + fmt::println("[{}] {}", func, str); + } + else + { + fmt::println("{}", str); + } +} diff --git a/UnleashedRecomp/os/macos/media_macos.cpp b/UnleashedRecomp/os/macos/media_macos.cpp new file mode 100644 index 00000000..81d1e833 --- /dev/null +++ b/UnleashedRecomp/os/macos/media_macos.cpp @@ -0,0 +1,7 @@ +#include + +bool os::media::IsExternalMediaPlaying() +{ + // This functionality is not supported in macOS. + return false; +} diff --git a/UnleashedRecomp/os/macos/process_macos.cpp b/UnleashedRecomp/os/macos/process_macos.cpp new file mode 100644 index 00000000..a459dd90 --- /dev/null +++ b/UnleashedRecomp/os/macos/process_macos.cpp @@ -0,0 +1,137 @@ +#include + +#include +#include +#include +#include +#include +#include + +std::filesystem::path os::process::GetExecutablePath() +{ + uint32_t exePathSize = PATH_MAX; + char exePath[PATH_MAX] = {}; + if (_NSGetExecutablePath(exePath, &exePathSize) == 0) + { + return std::filesystem::path(std::u8string_view((const char8_t*)(exePath))); + } + else + { + return std::filesystem::path(); + } +} + +using IsTranslocatedURLFunc = Boolean (*)(CFURLRef path, bool* isTranslocated, + CFErrorRef* __nullable error); +using CreateOriginalPathForURLFunc = CFURLRef __nullable (*)(CFURLRef translocatedPath, + CFErrorRef* __nullable error); + +static CFURLRef UntranslocateBundlePath(const CFURLRef bundlePath) +{ + CFURLRef resultPath = nullptr; + if (void* securityHandle = dlopen("/System/Library/Frameworks/Security.framework/Security", RTLD_LAZY)) + { + const auto IsTranslocatedURL = reinterpret_cast( + dlsym(securityHandle, "SecTranslocateIsTranslocatedURL")); + const auto CreateOriginalPathForURL = reinterpret_cast( + dlsym(securityHandle, "SecTranslocateCreateOriginalPathForURL")); + + bool translocated = false; + if (IsTranslocatedURL && CreateOriginalPathForURL && + IsTranslocatedURL(bundlePath, &translocated, nullptr) && translocated) + { + resultPath = CreateOriginalPathForURL(bundlePath, nullptr); + } + + dlclose(securityHandle); + } + return resultPath; +} + +std::filesystem::path os::process::GetExecutableRoot() +{ + std::filesystem::path resultPath = GetExecutablePath().remove_filename(); + if (CFBundleRef bundleRef = CFBundleGetMainBundle()) + { + if (CFURLRef bundleUrlRef = CFBundleCopyBundleURL(bundleRef)) + { + // The OS may relocate the app elsewhere for security reasons if, + // for example, it was downloaded and opened from the Downloads + // folder. In this case, we need to untranslocate the path to + // find out where the actual app bundle is. + CFURLRef untranslocatedUrlRef = UntranslocateBundlePath(bundleUrlRef); + + char appBundlePath[MAXPATHLEN]; + if (CFURLGetFileSystemRepresentation( + untranslocatedUrlRef ? untranslocatedUrlRef : bundleUrlRef, true, + reinterpret_cast(appBundlePath), sizeof(appBundlePath))) + { + resultPath = std::filesystem::path(appBundlePath).parent_path(); + } + + if (untranslocatedUrlRef) + { + CFRelease(untranslocatedUrlRef); + } + CFRelease(bundleUrlRef); + } + } + return resultPath; +} + +std::filesystem::path os::process::GetWorkingDirectory() +{ + char cwd[PATH_MAX] = {}; + char *res = getcwd(cwd, sizeof(cwd)); + if (res != nullptr) + { + return std::filesystem::path(std::u8string_view((const char8_t*)(cwd))); + } + else + { + return std::filesystem::path(); + } +} + +bool os::process::SetWorkingDirectory(const std::filesystem::path& path) +{ + return chdir(path.c_str()) == 0; +} + +bool os::process::StartProcess(const std::filesystem::path& path, const std::vector& args, std::filesystem::path work) +{ + pid_t pid = fork(); + if (pid < 0) + return false; + + if (pid == 0) + { + setsid(); + + std::u8string workU8 = work.u8string(); + chdir((const char*)(workU8.c_str())); + + std::u8string pathU8 = path.u8string(); + std::vector argStrs; + argStrs.push_back((char*)(pathU8.c_str())); + for (const std::string& arg : args) + argStrs.push_back((char *)(arg.c_str())); + + argStrs.push_back(nullptr); + execvp((const char*)(pathU8.c_str()), argStrs.data()); + raise(SIGKILL); + } + + return true; +} + +void os::process::CheckConsole() +{ + // Always visible on macOS. + g_consoleVisible = true; +} + +void os::process::ShowConsole() +{ + // Unnecessary on macOS. +} diff --git a/UnleashedRecomp/os/macos/registry_macos.inl b/UnleashedRecomp/os/macos/registry_macos.inl new file mode 100644 index 00000000..d3717776 --- /dev/null +++ b/UnleashedRecomp/os/macos/registry_macos.inl @@ -0,0 +1,21 @@ +#include + +// TODO: Implement +inline bool os::registry::Init() +{ + return false; +} + +// TODO: read from file? +template +bool os::registry::ReadValue(const std::string_view& name, T& data) +{ + return false; +} + +// TODO: write to file? +template +bool os::registry::WriteValue(const std::string_view& name, const T& data) +{ + return false; +} diff --git a/UnleashedRecomp/os/macos/user_macos.cpp b/UnleashedRecomp/os/macos/user_macos.cpp new file mode 100644 index 00000000..a9679df9 --- /dev/null +++ b/UnleashedRecomp/os/macos/user_macos.cpp @@ -0,0 +1,6 @@ +#include + +bool os::user::IsDarkTheme() +{ + return false; +} diff --git a/UnleashedRecomp/os/macos/version_macos.cpp b/UnleashedRecomp/os/macos/version_macos.cpp new file mode 100644 index 00000000..381b5c44 --- /dev/null +++ b/UnleashedRecomp/os/macos/version_macos.cpp @@ -0,0 +1,7 @@ +#include + +os::version::OSVersion os::version::GetOSVersion() +{ + assert(false && "Unimplemented."); + return os::version::OSVersion(); +} diff --git a/UnleashedRecomp/os/process.h b/UnleashedRecomp/os/process.h index d85fc277..1fa8317d 100644 --- a/UnleashedRecomp/os/process.h +++ b/UnleashedRecomp/os/process.h @@ -5,6 +5,7 @@ namespace os::process inline bool g_consoleVisible; std::filesystem::path GetExecutablePath(); + std::filesystem::path GetExecutableRoot(); std::filesystem::path GetWorkingDirectory(); bool SetWorkingDirectory(const std::filesystem::path& path); bool StartProcess(const std::filesystem::path& path, const std::vector& args, std::filesystem::path work = {}); diff --git a/UnleashedRecomp/os/registry.h b/UnleashedRecomp/os/registry.h index 760512eb..d95b36ac 100644 --- a/UnleashedRecomp/os/registry.h +++ b/UnleashedRecomp/os/registry.h @@ -15,4 +15,6 @@ namespace os::registry #include #elif defined(__linux__) #include +#elif defined(__APPLE__) +#include #endif diff --git a/UnleashedRecomp/os/win32/process_win32.cpp b/UnleashedRecomp/os/win32/process_win32.cpp index d6ce9a93..c6f8e4bc 100644 --- a/UnleashedRecomp/os/win32/process_win32.cpp +++ b/UnleashedRecomp/os/win32/process_win32.cpp @@ -10,6 +10,11 @@ std::filesystem::path os::process::GetExecutablePath() return std::filesystem::path(exePath); } +std::filesystem::path os::process::GetExecutableRoot() +{ + return GetExecutablePath().remove_filename(); +} + std::filesystem::path os::process::GetWorkingDirectory() { WCHAR workPath[MAX_PATH]; diff --git a/UnleashedRecomp/res/macos/MacOSXBundleInfo.plist.in b/UnleashedRecomp/res/macos/MacOSXBundleInfo.plist.in new file mode 100644 index 00000000..fe0ff5b3 --- /dev/null +++ b/UnleashedRecomp/res/macos/MacOSXBundleInfo.plist.in @@ -0,0 +1,42 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + ${MACOSX_BUNDLE_EXECUTABLE_NAME} + CFBundleGetInfoString + ${MACOSX_BUNDLE_INFO_STRING} + CFBundleIconFile + ${MACOSX_BUNDLE_ICON_FILE} + CFBundleIdentifier + ${MACOSX_BUNDLE_GUI_IDENTIFIER} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleLongVersionString + ${MACOSX_BUNDLE_LONG_VERSION_STRING} + CFBundleName + ${MACOSX_BUNDLE_BUNDLE_NAME} + CFBundlePackageType + APPL + CFBundleShortVersionString + ${MACOSX_BUNDLE_SHORT_VERSION_STRING} + CFBundleSignature + ???? + CFBundleVersion + ${MACOSX_BUNDLE_BUNDLE_VERSION} + CSResourcesFileMapped + + NSHumanReadableCopyright + ${MACOSX_BUNDLE_COPYRIGHT} + LSMinimumSystemVersion + 13.0 + LSApplicationCategoryType + public.app-category.games + GCSupportsGameMode + + NSHighResolutionCapable + + + \ No newline at end of file diff --git a/UnleashedRecomp/res/macos/game_icon.icns b/UnleashedRecomp/res/macos/game_icon.icns new file mode 100644 index 00000000..807bb3bd Binary files /dev/null and b/UnleashedRecomp/res/macos/game_icon.icns differ diff --git a/UnleashedRecomp/ui/button_guide.cpp b/UnleashedRecomp/ui/button_guide.cpp index d926d14d..6db93d58 100644 --- a/UnleashedRecomp/ui/button_guide.cpp +++ b/UnleashedRecomp/ui/button_guide.cpp @@ -20,7 +20,7 @@ std::unique_ptr g_upKBMIcons; float g_sideMargins = DEFAULT_SIDE_MARGINS; -std::vector