Compare commits

..

8 Commits

Author SHA1 Message Date
CamilleLaVey 4ae14993ae Step back + keeping fixes from unified download + shared mem refactor 2026-08-11 18:50:22 -04:00
CamilleLaVey b69a998b1a another take 2026-08-11 17:37:46 -04:00
CamilleLaVey cf65326b2b Keep AHB limitations 2026-08-11 14:44:58 -04:00
CamilleLaVey fd75a6d1e4 [TEST] Another increased on windows size 2026-08-07 22:46:16 -04:00
CamilleLaVey 9c596696ae Increased windows size 2026-08-07 22:46:16 -04:00
CamilleLaVey 7067a5f298 [TEST] Increased window limiter on AHB 2026-08-07 22:46:16 -04:00
CamilleLaVey ba11ea2eb9 [TEST] Adjustment AHB to tiled-gpu-v2 state 2026-08-07 22:46:16 -04:00
CamilleLaVey 2a15ff7b69 [memory, vulkan] Initial implementation for Unified Memory 2026-08-07 22:46:16 -04:00
140 changed files with 1817 additions and 4469 deletions
+2 -2
View File
@@ -1,9 +1,9 @@
name: tx-pull
on:
# monday at 4pm
# tuesday, saturday at 2pm
schedule:
- cron: '0 16 * * 1'
- cron: '0 14 * * 2,6'
workflow_dispatch:
jobs:
+2 -2
View File
@@ -3,7 +3,7 @@ description: File a bug report
body:
- type: markdown
attributes:
value: Tech support does not belong here. You should only file an issue here if you think you have experienced an actual bug with Eden.
value: Tech support does not belong here. You should only file an issue here if you think you have experienced an actual bug with yuzu.
- type: checkboxes
attributes:
label: Is there an existing issue for this?
@@ -43,7 +43,7 @@ body:
id: log
attributes:
label: Log File
description: A log file will help our developers to better diagnose and fix the issue. Instructions can be found [here](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/HowToAccessLogs.md).
description: A log file will help our developers to better diagnose and fix the issue. Instructions can be found [here](https://yuzu-emu.org/help/reference/log-files).
validations:
required: true
- type: textarea
+2 -2
View File
@@ -4,7 +4,7 @@ labels: "request"
body:
- type: markdown
attributes:
value: Tech support does not belong here. You should only file an issue here if you are requesting a feature you believe would make Eden better.
value: Tech support does not belong here. You should only file an issue here if you are requesting a feature you believe would make yuzu better.
- type: checkboxes
attributes:
label: Is there an existing issue for this?
@@ -23,6 +23,6 @@ body:
id: why-feature
attributes:
label: Why would this feature be useful?
description: A brief description of why this feature would make Eden better.
description: A brief description of why this feature would make yuzu better.
validations:
required: true
+20 -47
View File
@@ -28,23 +28,23 @@ if (NOT DEFINED ARCHITECTURE)
endif()
# Needed for FFmpeg w/ VAAPI and DRM
if (OPENBSD)
if (PLATFORM_OPENBSD)
# OpenBSD 7.8 broke libcxx when upgrading, so we must define the PSTL backend manually
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -I${CMAKE_SYSROOT}/usr/X11R6/include -D_LIBCPP_PSTL_BACKEND_SERIAL=1")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -I${CMAKE_SYSROOT}/usr/X11R6/include -D_LIBCPP_PSTL_BACKEND_SERIAL=1")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -L${CMAKE_SYSROOT}/usr/X11R6/lib")
elseif (NETBSD)
elseif (PLATFORM_NETBSD)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -I${CMAKE_SYSROOT}/usr/X11R7/include -I${CMAKE_SYSROOT}/usr/pkg/include/c++/v1")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -I${CMAKE_SYSROOT}/usr/X11R7/include -I${CMAKE_SYSROOT}/usr/pkg/include/c++/v1")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -L${CMAKE_SYSROOT}/usr/X11R7/lib")
endif()
# NetBSD: Fun for the whole family!
if (NETBSD)
if (PLATFORM_NETBSD)
set(ENV{PKG_CONFIG_PATH} "${PKG_CONFIG_PATH}:${CMAKE_SYSROOT}/usr/pkg/lib/ffmpeg7/pkgconfig")
endif()
cmake_dependent_option(YUZU_STATIC_ROOM "Build a static room executable only (CI only)" OFF "LINUX" OFF)
cmake_dependent_option(YUZU_STATIC_ROOM "Build a static room executable only (CI only)" OFF "PLATFORM_LINUX" OFF)
if (YUZU_STATIC_ROOM)
set(YUZU_ROOM ON)
set(YUZU_ROOM_STANDALONE ON)
@@ -79,7 +79,6 @@ set(YUZU_QT_MIRROR "" CACHE STRING "What mirror to use for downloading the bundl
cmake_dependent_option(YUZU_USE_BUNDLED_QT "Download bundled Qt binaries" "${MSVC}" "ENABLE_QT" OFF)
option(ENABLE_DEBUG_TOOLS "Enable debugging tools (maxwell disassembler, SPIRV translator, etc)" OFF)
option(ENABLE_WERROR "Enable -Werror diagnostics" ON)
# non-linux bundled qt are static
if (YUZU_USE_BUNDLED_QT AND (APPLE OR NOT UNIX))
@@ -158,19 +157,6 @@ if (CXX_CLANG_CL)
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-reserved-identifier>
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-deprecated-declarations>
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-cast-function-type-mismatch>
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-c99-extensions>
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-c++17-compat>
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-c++11-compat-reserved-user-defined-literal>
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-c++11-compat-deprecated-writable-strings>
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-c++11-compat-pedantic>
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-c++11-compat>
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-c++0x-compat>
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-c++98-c++11-compat-binary-literal>
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-c++98-compat-pedantic>
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-c++98-compat>
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-c99-compat>
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-c98-compat>
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-c99-extensions>
$<$<COMPILE_LANGUAGE:C,CXX>:/EHsc>)
# REQUIRED CPU features IN Windows-amd64
if (ARCHITECTURE_x86_64)
@@ -224,7 +210,7 @@ endif()
# ffmpeg
option(YUZU_USE_BUNDLED_FFMPEG "Download bundled FFmpeg" ${EXT_DEFAULT})
cmake_dependent_option(YUZU_USE_EXTERNAL_FFMPEG "Build FFmpeg from external source" "${SOLARIS}" "NOT WIN32 AND NOT ANDROID" OFF)
cmake_dependent_option(YUZU_USE_EXTERNAL_FFMPEG "Build FFmpeg from external source" "${PLATFORM_SUN}" "NOT WIN32 AND NOT ANDROID" OFF)
# sirit
set(BUNDLED_SIRIT_DEFAULT OFF)
@@ -235,7 +221,7 @@ endif()
option(YUZU_USE_BUNDLED_SIRIT "Download bundled sirit" ${BUNDLED_SIRIT_DEFAULT})
# FreeBSD 15+ has libusb, versions below should disable it
cmake_dependent_option(ENABLE_LIBUSB "Enable the use of LibUSB" ON "WIN32 OR LINUX OR FREEBSD OR APPLE" OFF)
cmake_dependent_option(ENABLE_LIBUSB "Enable the use of LibUSB" ON "WIN32 OR PLATFORM_LINUX OR PLATFORM_FREEBSD OR APPLE" OFF)
cmake_dependent_option(ENABLE_OPENGL "Enable OpenGL" ON "NOT (WIN32 AND ARCHITECTURE_arm64) AND NOT APPLE" OFF)
mark_as_advanced(FORCE ENABLE_OPENGL)
@@ -249,7 +235,7 @@ option(YUZU_TESTS "Compile tests" "${BUILD_TESTING}")
# Install udev rules on Linux (mainly for gyros)
# Only acts on joysticks and nothing else.
cmake_dependent_option(YUZU_INSTALL_UDEV_RULES "Install udev rules for gyro access" OFF "LINUX" OFF)
cmake_dependent_option(YUZU_INSTALL_UDEV_RULES "Install udev rules for gyro access" OFF "PLATFORM_LINUX" OFF)
option(YUZU_DOWNLOAD_ANDROID_VVL "Download validation layer binary for android" ON)
@@ -262,7 +248,7 @@ cmake_dependent_option(YUZU_ROOM_STANDALONE "Enable standalone room executable"
cmake_dependent_option(YUZU_CMD "Compile the eden-cli executable" ON "NOT ANDROID" OFF)
cmake_dependent_option(YUZU_CRASH_DUMPS "Compile crash dump (Minidump) support" OFF "WIN32 OR LINUX" OFF)
cmake_dependent_option(YUZU_CRASH_DUMPS "Compile crash dump (Minidump) support" OFF "WIN32 OR PLATFORM_LINUX" OFF)
option(YUZU_DOWNLOAD_TIME_ZONE_DATA "Always download time zone binaries" ON)
set(YUZU_TZDB_PATH "" CACHE STRING "Path to a pre-downloaded timezone database")
@@ -272,7 +258,7 @@ cmake_dependent_option(YUZU_USE_BUNDLED_MOLTENVK "Download bundled MoltenVK lib"
option(YUZU_DISABLE_LLVM "Disable LLVM (useful for CI)" OFF)
set(DEFAULT_YUZU_USE_BUNDLED_OPENSSL OFF)
if (EXT_DEFAULT OR SOLARIS OR OPENBSD)
if (EXT_DEFAULT OR PLATFORM_SUN OR PLATFORM_OPENBSD)
set(DEFAULT_YUZU_USE_BUNDLED_OPENSSL ON)
endif()
@@ -295,7 +281,7 @@ if(EXISTS ${PROJECT_SOURCE_DIR}/hooks/pre-commit AND NOT EXISTS ${PROJECT_SOURCE
endif()
endif()
if (ARCHITECTURE_arm64 AND (ANDROID OR LINUX))
if (ARCHITECTURE_arm64 AND (ANDROID OR PLATFORM_LINUX))
set(HAS_NCE 1)
add_compile_definitions(HAS_NCE=1)
endif()
@@ -304,7 +290,7 @@ if (YUZU_ROOM)
add_compile_definitions(YUZU_ROOM)
endif()
if (UNIX AND NOT (LINUX OR WIN32))
if (UNIX AND NOT (PLATFORM_LINUX OR WIN32))
if(CXX_APPLE OR CXX_CLANG)
# libc++ has stop_token and jthread as experimental
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fexperimental-library")
@@ -339,26 +325,13 @@ if (CXX_GCC OR CXX_CLANG)
endif()
elseif(ARCHITECTURE_arm64)
# See https://gcc.gnu.org/onlinedocs/gcc/AArch64-Options.html
set(YUZU_BUILD_PRESET "custom" CACHE STRING "Build preset to use. One of: custom, generic, armv9, native")
set(mtune generic)
set(armv8_2_target armv8.2-a+fp16+dotprod)
if (ANDROID)
set(YUZU_BUILD_PRESET "custom" CACHE STRING "Build preset to use. One of: custom, armv8.2, armv9, native")
set(march ${armv8_2_target})
if (${YUZU_BUILD_PRESET} STREQUAL "armv9")
set(march armv9-a)
endif()
else()
set(YUZU_BUILD_PRESET "custom" CACHE STRING "Build preset to use. One of: custom, generic, armv8.2, armv9, native")
if (${YUZU_BUILD_PRESET} STREQUAL "generic")
set(march armv8-a)
elseif (${YUZU_BUILD_PRESET} STREQUAL "armv8.2")
set(march ${armv8_2_target})
elseif (${YUZU_BUILD_PRESET} STREQUAL "armv9")
set(march armv9-a)
endif()
if (${YUZU_BUILD_PRESET} STREQUAL "generic")
set(march armv8-a)
elseif (${YUZU_BUILD_PRESET} STREQUAL "armv9")
set(march armv9-a)
endif()
endif()
@@ -431,7 +404,7 @@ if (Boost_ADDED)
if (NOT MSVC OR CXX_CLANG)
# boost sucks
if (SOLARIS)
if (PLATFORM_SUN)
add_compile_options($<$<COMPILE_LANGUAGE:C,CXX>:-pthreads>)
endif()
@@ -535,9 +508,9 @@ elseif (WIN32)
# PSAPI is the Process Status API
set(PLATFORM_LIBRARIES ${PLATFORM_LIBRARIES} psapi imm32 version crypt32 rpcrt4 gdi32 wldap32 mswsock)
endif()
elseif (MANAGARM)
elseif (PLATFORM_MANAGARM)
set(PLATFORM_LIBRARIES iconv intl)
elseif (HAIKUOS)
elseif (PLATFORM_HAIKU)
# Haiku is so special :)
set(PLATFORM_LIBRARIES bsd /boot/system/lib/libnetwork.so)
elseif (CMAKE_SYSTEM_NAME MATCHES "^(Linux|kFreeBSD|GNU|SunOS)$")
@@ -631,7 +604,7 @@ if (ENABLE_QT)
# Best practice is to ask for all components at once, so they are from the same version
set(YUZU_QT_COMPONENTS Core Widgets Charts Concurrent Gui)
if (LINUX OR FREEBSD)
if (PLATFORM_LINUX OR PLATFORM_FREEBSD)
list(APPEND YUZU_QT_COMPONENTS DBus)
# yes Qt, we get it
set(QT_NO_PRIVATE_MODULE_WARNING ON)
+1 -1
View File
@@ -13,7 +13,7 @@ find_package_handle_standard_args(Opus
VERSION_VAR OPUS_VERSION
)
if (MSYS2)
if (PLATFORM_MSYS)
FixMsysPath(PkgConfig::OPUS)
endif()
+1 -1
View File
@@ -14,7 +14,7 @@ find_package_handle_standard_args(enet
VERSION_VAR ENET_VERSION
)
if (MSYS2)
if (PLATFORM_MSYS)
FixMsysPath(PkgConfig::ENET)
endif()
+1 -1
View File
@@ -14,7 +14,7 @@ find_package_handle_standard_args(libusb
VERSION_VAR LIBUSB_VERSION
)
if (MSYS2)
if (PLATFORM_MSYS)
FixMsysPath(PkgConfig::LIBUSB)
endif()
+1 -1
View File
@@ -13,7 +13,7 @@ else()
find_package(PkgConfig QUIET)
pkg_search_module(LZ4 QUIET IMPORTED_TARGET liblz4)
if (MSYS2)
if (PLATFORM_MSYS)
FixMsysPath(PkgConfig::LZ4)
endif()
+1 -5
View File
@@ -22,13 +22,9 @@ Eden is free, open-source, copyleft software, licensed under the terms of the [G
- No LLM or AI usage, *period*, for patches, pull requests, issues, comments, debugging, brainstorming, etc.
- For details on why, see the [detailed AI policy](docs/policies/AI.md).
- Usage of any form of profanity or otherwise unsavory language is generally discouraged.
- This is primarily because it rarely helps to actually understand what's going on.
- Remember that your comments should be focused and actually address what's happening. With very few exceptions, expletives are actively detrimental at best.
- New code must follow the same general style as the surrounding codebase. Exceptions may be granted in certain cases.
- Maintainers reserve the right to change your patches and pull requests at will. We will try to avoid this.
- You should generally respect all decisions made by the [code owners](docs/CODEOWNERS) in your particular subsystem.
- However, if you feel they are overstepping or are incorrect, don't be afraid to stand your ground! Maintainers are not always correct.
- You should respect all decisions made by the [code owners](docs/CODEOWNERS) in your particular subsystem. If you feel they are overstepping or are incorrect, don't be afraid to stand your ground!
- While we do *not* adhere to the terms of a formal code of conduct, you will generally be expected to respect other developers, contributors, and community members.
- You **must** have basic knowledge of [Git](https://git-scm.com/learn). Knowing how to manage your branches and follow proper fork policies is a necessity.
-6
View File
@@ -65,12 +65,6 @@
"repo": "eden-emulator/discord-rpc",
"version": "0d8b2d6a37"
},
"dxbc": {
"bundled": true,
"hash": "196d26c07747d7aa2ced6fb1a5ae6f665e9b917de223a540038690b037c70a1eeca51c21d42aa5bec9fff7df0a6656a75ee8b4e2cb4008469f18a6812280f831",
"repo": "PancakeTAS/dxbc",
"version": "78ab59a8aaeb43cd1b0a5e91ba86722433a10b78"
},
"enet": {
"find_args": "MODULE",
"hash": "a0d2fa8c957704dd49e00a726284ac5ca034b50b00d2b20a94fa1bbfbb80841467834bfdc84aa0ed0d6aab894608fd6c86c3b94eee46343f0e6d9c22e391dbf9",
-2
View File
@@ -46,8 +46,6 @@ These options control dependencies.
- `YUZU_INSTALL_UDEV_RULES` (OFF) Install udev rules to enable hidraw access
- Needed for gyroscopes
- Only available on Linux
- `ENABLE_DEBUG_TOOLS` (OFF) Enables debugging and development tools, see [tools](../tools/README.md).
- `ENABLE_WERROR` (ON) Enables warnings as errors (-Werror).
### Flavors
+1 -1
View File
@@ -34,7 +34,7 @@ Everyone has their own way of viewing good/bad C++ practices, my general outline
- The reason is because the project has `-fno-rtti` disabled by default, due to the costs of dynamic polymorphism.
- Always copy-on-value for objects with `sizeof(void *) >= sizeof(T) * 2`, i.e objects sized as 2 pointers or less, for bigger objects you can use ref/pointer as usual.
- Try using move semantics instead of references, whenever possible.
- Function parameters are cheap. Don't be afraid to use as many as needed (API usability permitting).
- Remember function parameters are extremelly cheap as fuck, don't be afraid to place upto 8 parameters on a given function.
- Don't save a reference in structures of a parent object, i.e:
```c++
+1 -5
View File
@@ -92,10 +92,6 @@ AddDependentPackages(vulkan-headers vulkan-utility-libraries)
# frozen
AddJsonPackage(frozen)
# DXVK's DXBC compiler, used to translate the frame generation shaders
# out of a user-supplied Lossless.dll into SPIR-V
AddJsonPackage(dxbc)
# DiscordRPC
if (USE_DISCORD_PRESENCE)
if (ARCHITECTURE_arm64)
@@ -168,7 +164,7 @@ if (NOT ANDROID)
AddJsonPackage(sdl3)
else()
message(STATUS "Using bundled SDL3")
if (FREEBSD)
if (PLATFORM_FREEBSD)
set(BUILD_SHARED_LIBS ON)
endif()
AddJsonPackage(sdl3-ci)
+80 -23
View File
@@ -1,46 +1,45 @@
# SPDX-FileCopyrightText: Copyright 2026 crueter
# SPDX-FileCopyrightText: Copyright 2025 crueter
# SPDX-License-Identifier: LGPL-3.0-or-later
## DetectPlatform ##
# This is a small helper that sets platform variables for various
# This is a small helper that sets PLATFORM_<platform> variables for various
# operating systems and distributions. Note that Apple, Windows, Android, etc.
# are not covered, as CMake already does that for us.
# It also sets CXX_<compiler> for the C++ compiler.
# Furthermore, some platforms have really silly requirements/quirks, so this
# also does a few of those.
# This module contains contributions from the Eden Emulator Project,
# notably from crueter and Lizzie.
if (${CMAKE_SYSTEM_NAME} STREQUAL "SunOS")
set(SOLARIS ON)
set(PLATFORM_SUN ON)
elseif (${CMAKE_SYSTEM_NAME} STREQUAL "OpenOrbis")
set(OPENORBIS ON)
set(PLATFORM_PS4 ON)
elseif (${CMAKE_SYSTEM_NAME} STREQUAL "managarm")
set(MANAGARM ON)
set(PLATFORM_MANAGARM ON)
elseif (${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD")
set(PLATFORM_FREEBSD ON)
elseif (${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD")
set(PLATFORM_OPENBSD ON)
elseif (${CMAKE_SYSTEM_NAME} STREQUAL "NetBSD")
set(PLATFORM_NETBSD ON)
elseif (${CMAKE_SYSTEM_NAME} STREQUAL "DragonFly")
set(PLATFORM_DRAGONFLYBSD ON)
elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Haiku")
set(HAIKUOS ON)
endif()
# BSD
if (DEFINED BSD)
if ("${BSD}" STREQUAL "DragonFlyBSD")
set(DRAGONFLYBSD ON)
elseif ("${BSD}" STREQUAL "FreeBSD")
set(FREEBSD ON)
elseif ("${BSD}" STREQUAL "OpenBSD")
set(OPENBSD ON)
elseif ("${BSD}" STREQUAL "NetBSD")
set(NETBSD ON)
endif()
set(PLATFORM_HAIKU ON)
elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
set(PLATFORM_LINUX ON)
endif()
# dumb heuristic to detect msys2
if (CMAKE_COMMAND MATCHES "msys64")
set(MSYS2 ON)
set(PLATFORM_MSYS ON)
endif()
# compiler checks
if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
set(CXX_CLANG ON)
if (MSVC)
@@ -48,6 +47,8 @@ if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
endif()
elseif (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
set(CXX_GCC ON)
elseif (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
set(CXX_CL ON)
elseif (CMAKE_CXX_COMPILER_ID STREQUAL "IntelLLVM")
set(CXX_ICC ON)
elseif (CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang")
@@ -60,13 +61,37 @@ if(MINGW AND CXX_CLANG)
set(CMAKE_SYSTEM_VERSION 10.0.0)
endif()
# NB: this does not account for SPARC
if (PLATFORM_SUN)
# Terrific OpenIndiana pkg shenanigans
list(APPEND CMAKE_PREFIX_PATH
"${CMAKE_SYSROOT}/usr/lib/qt/6.6/lib/amd64/cmake")
list(APPEND CMAKE_MODULE_PATH
"${CMAKE_SYSROOT}/usr/lib/qt/6.6/lib/amd64/cmake")
# Amazing - absolutely incredible
list(APPEND CMAKE_PREFIX_PATH "${CMAKE_SYSROOT}/usr/lib/amd64/cmake")
list(APPEND CMAKE_MODULE_PATH "${CMAKE_SYSROOT}/usr/lib/amd64/cmake")
# For some mighty reason, doing a normal release build sometimes
# may not trigger the proper -O3 switch to materialize
if (CMAKE_BUILD_TYPE MATCHES "Release")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O3")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3")
endif()
if (CMAKE_BUILD_TYPE MATCHES "RelWithDebInfo")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O2")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O2")
endif()
endif()
# MSYS2 utilities
# Sometimes, PkgConfig modules will incorrectly reference / when CMake
# wants you to reference it as C:/msys64/. This function corrects that.
# Example in a Find module:
#[[
if (MSYS2)
if (PLATFORM_MSYS)
FixMsysPath(PkgConfig::OPUS)
endif()
]]
@@ -91,9 +116,41 @@ function(FixMsysPath target)
INTERFACE_INCLUDE_DIRECTORIES ${include_dir})
endfunction()
# Saves linking time
# MSYSTEM handling + program_path
if (PLATFORM_MSYS)
# really, really dumb heuristic to detect what environment we are in
macro(system var)
if (CMAKE_COMMAND MATCHES ${var})
set(MSYSTEM ${var})
endif()
endmacro()
system(mingw64)
system(clang64)
system(clangarm64)
system(ucrt64)
if (NOT DEFINED MSYSTEM)
set(MSYSTEM msys2)
endif()
# We generally want to prioritize environment-specific binaries if possible
# some, like autoconf, are not present on environments besides msys2 though
set(CMAKE_PROGRAM_PATH C:/msys64/${MSYSTEM}/bin C:/msys64/usr/bin)
set(ENV{PKG_CONFIG_PATH} C:/msys64/${MSYSTEM}/lib/pkgconfig)
endif()
# This saves a truly ridiculous amount of time during linking
# In my tests, without this, Eden takes 2 mins, with this, it takes 3-5 seconds
# or on GitHub Actions, 10 minutes -> 3 seconds
if (MINGW)
set(MINGW_FLAGS "-Wl,--strip-all -Wl,--gc-sections")
set(CMAKE_EXE_LINKER_FLAGS_RELEASE
"${CMAKE_EXE_LINKER_FLAGS_RELEASE} ${MINGW_FLAGS}")
endif()
# awesome
if (PLATFORM_FREEBSD OR PLATFORM_DRAGONFLYBSD)
set(CMAKE_EXE_LINKER_FLAGS
"${CMAKE_EXE_LINKER_FLAGS} -L${CMAKE_SYSROOT}/usr/local/lib")
endif()
+5 -5
View File
@@ -56,7 +56,7 @@ if (NOT YUZU_USE_BUNDLED_FFMPEG)
endif()
endif()
if (OPENORBIS OR MANAGARM)
if (PLATFORM_PS4 OR PLATFORM_MANAGARM)
# Doesn't support VA-API, don't go thru the embarrassment of trying to enable it
list(APPEND FFmpeg_HWACCEL_FLAGS --disable-vaapi)
elseif (ANDROID)
@@ -75,7 +75,7 @@ elseif (UNIX AND NOT DEFINED FFmpeg_IS_CROSS_COMPILING AND NOT ANDROID)
if(X11_FOUND)
if (NOT APPLE)
# In Solaris needs explicit linking for ffmpeg which links to /lib/amd64/libX11.so
if(SOLARIS)
if(PLATFORM_SUN)
list(APPEND FFmpeg_HWACCEL_LIBRARIES
X11
"${CMAKE_SYSROOT}/usr/lib/xorg/amd64/libdrm.so")
@@ -158,7 +158,7 @@ elseif (UNIX AND NOT DEFINED FFmpeg_IS_CROSS_COMPILING AND NOT ANDROID)
endif()
endif()
if (OPENORBIS)
if (PLATFORM_PS4)
list(APPEND FFmpeg_CROSS_COMPILE_LIBS
-lkernel
-lSceUserService
@@ -172,7 +172,7 @@ if (OPENORBIS)
--extra-cxxflags=${CMAKE_SYSROOT}/usr/include
--extra-libs="${FFmpeg_CROSS_COMPILE_LIBS}"
)
elseif (MANAGARM)
elseif (PLATFORM_MANAGARM)
# Required for proper stuff
list(APPEND FFmpeg_CROSS_COMPILE_FLAGS
--disable-pthreads
@@ -299,7 +299,7 @@ else()
set(FFmpeg_BUILD_LIBRARIES ${FFmpeg_LIBRARIES})
# BSD make or Solaris make don't support ffmpeg make-j8
if (LINUX OR ANDROID OR APPLE OR WIN32 OR FREEBSD)
if (PLATFORM_LINUX OR ANDROID OR APPLE OR WIN32 OR PLATFORM_FREEBSD)
set(FFmpeg_MAKE_ARGS -j${SYSTEM_THREADS})
else()
set(FFmpeg_MAKE_ARGS "")
+1 -1
View File
@@ -11,7 +11,7 @@ if (NOT libusb_ADDED)
endif()
# TODO: *BSD fails to compile--may need different configs/symbols
if (MINGW OR LINUX OR APPLE)
if (MINGW OR PLATFORM_LINUX OR APPLE)
set(LIBUSB_FOUND ON CACHE BOOL "libusb is present" FORCE)
set(LIBUSB_VERSION "1.0.24" CACHE STRING "libusb version string" FORCE)
+2 -2
View File
@@ -1177,7 +1177,7 @@ static void *stbi__load_main(stbi__context *s, int *x, int *y, int *comp, int re
#endif
#ifndef STBI_NO_TGA
// test tga last because it's a bad test!
// test tga last because it's a crappy test!
if (stbi__tga_test(s))
return stbi__tga_load(s,x,y,comp,req_comp, ri);
#endif
@@ -7662,7 +7662,7 @@ static int stbi__info_main(stbi__context *s, int *x, int *y, int *comp)
if (stbi__hdr_info(s, x, y, comp)) return 1;
#endif
// test tga last because it's a bad test!
// test tga last because it's a crappy test!
#ifndef STBI_NO_TGA
if (stbi__tga_info(s, x, y, comp))
return 1;
+9 -21
View File
@@ -155,24 +155,12 @@ else()
$<$<COMPILE_LANGUAGE:CXX>:-fno-rtti>)
endif()
if (ENABLE_WERROR)
add_compile_options(
$<$<COMPILE_LANGUAGE:C,CXX>:-Werror=all>
$<$<COMPILE_LANGUAGE:C,CXX>:-Werror=extra>
$<$<COMPILE_LANGUAGE:C,CXX>:-Werror=missing-declarations>
$<$<COMPILE_LANGUAGE:C,CXX>:-Werror=shadow>
$<$<COMPILE_LANGUAGE:C,CXX>:-Werror=unused>)
else()
add_compile_options(
$<$<COMPILE_LANGUAGE:C,CXX>:-Wall>
$<$<COMPILE_LANGUAGE:C,CXX>:-Wextra>
$<$<COMPILE_LANGUAGE:C,CXX>:-Wmissing-declarations>
$<$<COMPILE_LANGUAGE:C,CXX>:-Wshadow>
$<$<COMPILE_LANGUAGE:C,CXX>:-Wunused>
# Some compilers could particularly misbehave :)
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-error-all>)
endif()
add_compile_options(
$<$<COMPILE_LANGUAGE:C,CXX>:-Werror=all>
$<$<COMPILE_LANGUAGE:C,CXX>:-Werror=extra>
$<$<COMPILE_LANGUAGE:C,CXX>:-Werror=missing-declarations>
$<$<COMPILE_LANGUAGE:C,CXX>:-Werror=shadow>
$<$<COMPILE_LANGUAGE:C,CXX>:-Werror=unused>
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-attributes>
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-invalid-offsetof>
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-unused-parameter>
@@ -181,9 +169,9 @@ else()
if (CXX_CLANG OR CXX_ICC OR CXX_APPLE) # Clang, AppleClang, or Intel C++
if (NOT MSVC)
add_compile_options(
$<$<COMPILE_LANGUAGE:C,CXX>:-Wshadow-uncaptured-local>
$<$<COMPILE_LANGUAGE:C,CXX>:-Wimplicit-fallthrough>
$<$<COMPILE_LANGUAGE:C,CXX>:-Wtype-limits>)
$<$<COMPILE_LANGUAGE:C,CXX>:-Werror=shadow-uncaptured-local>
$<$<COMPILE_LANGUAGE:C,CXX>:-Werror=implicit-fallthrough>
$<$<COMPILE_LANGUAGE:C,CXX>:-Werror=type-limits>)
endif()
add_compile_options(
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-braced-scalar-init>
@@ -193,7 +181,7 @@ else()
if (ARCHITECTURE_x86_64)
add_compile_options($<$<COMPILE_LANGUAGE:C,CXX>:-mcx16>)
if (LINUX OR FREEBSD)
if (PLATFORM_LINUX OR PLATFORM_FREEBSD)
add_compile_options($<$<COMPILE_LANGUAGE:C,CXX>:-mtls-dialect=gnu2>)
endif()
endif()
@@ -539,40 +539,6 @@ object NativeLibrary {
*/
external fun installKeys(path: String, ext: String): Int
/**
* @return Whether this GPU can run the Lossless Scaling frame generation shaders,
* which are built against the Vulkan memory model.
*/
external fun supportsFrameGeneration(): Boolean
/**
* @return Path the user-supplied Lossless Scaling library is expected at.
*/
external fun getLosslessDllPath(): String
/**
* Parses the installed Lossless Scaling library and checks that every shader the
* frame generation chain needs is present.
*
* @return The result code, matching the losslessDllResults array.
*/
external fun validateLosslessDll(): Int
/**
* Translates the frame generation shaders out of the installed Lossless Scaling library
* and writes them to the SPIR-V cache. Slow, so call it off the main thread.
*
* @return The result code, matching the losslessDllResults array.
*/
external fun prepareLosslessDll(): Int
/**
* Deletes the installed Lossless Scaling library.
*
* @return Whether the library is gone after the call.
*/
external fun removeLosslessDll(): Boolean
/**
* Checks the PatchManager for any addons that are available
*
@@ -27,6 +27,7 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
RENDERER_ASYNCHRONOUS_GPU_EMULATION("use_asynchronous_gpu_emulation"),
RENDERER_ASYNC_PRESENTATION("async_presentation"),
RENDERER_ASYNCHRONOUS_SHADERS("use_asynchronous_shaders"),
RENDERER_UNIFIED_MEMORY("use_unified_memory"),
RENDERER_REACTIVE_FLUSHING("use_reactive_flushing"),
ENABLE_BUFFER_HISTORY("enable_buffer_history"),
USE_OPTIMIZED_VERTEX_BUFFERS("use_optimized_vertex_buffers"),
@@ -37,9 +38,6 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
RENDERER_PATCH_OLD_QCOM_DRIVERS("patch_old_qcom_drivers"),
RENDERER_VERTEX_INPUT_DYNAMIC_STATE("vertex_input_dynamic_state"),
RENDERER_SAMPLE_SHADING("sample_shading"),
RENDERER_FRAME_GEN("frame_gen"),
RENDERER_FRAME_GEN_FP16("frame_gen_fp16"),
RENDERER_FRAME_GEN_DUMP_FLOW("frame_gen_dump_flow"),
GPU_UNSWIZZLE_ENABLED("gpu_unswizzle_enabled"),
PICTURE_IN_PICTURE("picture_in_picture"),
USE_CUSTOM_RTC("custom_rtc_enabled"),
@@ -19,8 +19,6 @@ enum class IntSetting(override val key: String) : AbstractIntSetting {
RENDERER_ASTC_DECODE_METHOD("accelerate_astc"),
RENDERER_ACCURACY("gpu_accuracy"),
RENDERER_RESOLUTION("resolution_setup"),
RENDERER_FRAME_GEN_MULTIPLIER("frame_gen_multiplier"),
RENDERER_FRAME_GEN_FLOW_SCALE("frame_gen_flow_scale"),
RENDERER_VSYNC("use_vsync"),
RENDERER_SCALING_FILTER("scaling_filter"),
RENDERER_ANTI_ALIASING("anti_aliasing"),
@@ -11,7 +11,6 @@ object Settings {
SECTION_ROOT(R.string.advanced_settings),
SECTION_SYSTEM(R.string.preferences_system),
SECTION_RENDERER(R.string.preferences_graphics),
SECTION_FRAME_GEN(R.string.frame_gen),
SECTION_PERFORMANCE_STATS(R.string.stats_overlay_options),
SECTION_INPUT_OVERLAY(R.string.input_overlay_options),
SECTION_SOC_OVERLAY(R.string.soc_overlay_options),
@@ -21,7 +21,6 @@ import org.yuzu.yuzu_emu.features.settings.model.LongSetting
import org.yuzu.yuzu_emu.features.settings.model.ShortSetting
import org.yuzu.yuzu_emu.features.settings.model.StringSetting
import org.yuzu.yuzu_emu.network.NetDataValidators
import org.yuzu.yuzu_emu.utils.LosslessScalingHelper
import org.yuzu.yuzu_emu.utils.NativeConfig
/**
@@ -66,12 +65,6 @@ abstract class SettingsItem(
return NativeLibrary.isFirmwareAvailable()
}
if (setting.key in frameGenKeys &&
!(LosslessScalingHelper.isInstalled() && LosslessScalingHelper.isSupportedByGpu())
) {
return false
}
// Can't edit settings that aren't saveable in per-game config even if they are switchable
if (NativeConfig.isPerGameConfigLoaded() && !setting.isSaveable) {
return false
@@ -96,14 +89,6 @@ abstract class SettingsItem(
get() = !setting.global && NativeConfig.isPerGameConfigLoaded()
companion object {
private val frameGenKeys = setOf(
BooleanSetting.RENDERER_FRAME_GEN.key,
IntSetting.RENDERER_FRAME_GEN_MULTIPLIER.key,
IntSetting.RENDERER_FRAME_GEN_FLOW_SCALE.key,
BooleanSetting.RENDERER_FRAME_GEN_FP16.key,
BooleanSetting.RENDERER_FRAME_GEN_DUMP_FLOW.key
)
const val TYPE_HEADER = 0
const val TYPE_SWITCH = 1
const val TYPE_SINGLE_CHOICE = 2
@@ -622,46 +607,6 @@ abstract class SettingsItem(
valuesId = R.array.rendererAntiAliasingValues
)
)
put(
SwitchSetting(
BooleanSetting.RENDERER_FRAME_GEN,
titleId = R.string.frame_gen,
descriptionId = R.string.frame_gen_description
)
)
put(
SingleChoiceSetting(
IntSetting.RENDERER_FRAME_GEN_MULTIPLIER,
titleId = R.string.frame_gen_multiplier,
descriptionId = R.string.frame_gen_multiplier_description,
choicesId = R.array.frameGenMultiplierNames,
valuesId = R.array.frameGenMultiplierValues
)
)
put(
SliderSetting(
IntSetting.RENDERER_FRAME_GEN_FLOW_SCALE,
titleId = R.string.frame_gen_flow_scale,
descriptionId = R.string.frame_gen_flow_scale_description,
min = 25,
max = 100,
units = "%"
)
)
put(
SwitchSetting(
BooleanSetting.RENDERER_FRAME_GEN_FP16,
titleId = R.string.frame_gen_fp16,
descriptionId = R.string.frame_gen_fp16_description
)
)
put(
SwitchSetting(
BooleanSetting.RENDERER_FRAME_GEN_DUMP_FLOW,
titleId = R.string.frame_gen_dump_flow,
descriptionId = R.string.frame_gen_dump_flow_description
)
)
put(
SingleChoiceSetting(
IntSetting.RENDERER_SCREEN_LAYOUT,
@@ -740,6 +685,13 @@ abstract class SettingsItem(
descriptionId = R.string.renderer_asynchronous_shaders_description
)
)
put(
SwitchSetting(
BooleanSetting.RENDERER_UNIFIED_MEMORY,
titleId = R.string.renderer_unified_memory,
descriptionId = R.string.renderer_unified_memory_description
)
)
put(
SingleChoiceSetting(
IntSetting.FAST_GPU_TIME,
@@ -33,7 +33,6 @@ import org.yuzu.yuzu_emu.features.input.NativeInput
import org.yuzu.yuzu_emu.features.settings.model.Settings
import org.yuzu.yuzu_emu.features.settings.model.view.PathSetting
import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
import org.yuzu.yuzu_emu.fragments.ProgressDialogFragment
import org.yuzu.yuzu_emu.utils.PathUtil
import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins
import org.yuzu.yuzu_emu.utils.*
@@ -115,29 +114,6 @@ class SettingsFragment : Fragment() {
viewLifecycleOwner,
resetState = { settingsViewModel.setShouldReloadSettingsList(false) }
) { if (it) presenter.loadSettingsList() }
settingsViewModel.shouldShowLosslessInstaller.collect(
viewLifecycleOwner,
resetState = { settingsViewModel.setShouldShowLosslessInstaller(false) }
) { if (it) losslessDllPickerLauncher.launch(arrayOf("*/*")) }
settingsViewModel.shouldShowLosslessRemoveDialog.collect(
viewLifecycleOwner,
resetState = { settingsViewModel.setShouldShowLosslessRemoveDialog(false) }
) {
if (it) {
MessageDialogFragment.newInstance(
activity = requireActivity(),
titleId = R.string.lossless_scaling_remove,
descriptionId = R.string.lossless_scaling_remove_confirmation,
positiveButtonTitleId = R.string.lossless_scaling_remove,
positiveAction = {
LosslessScalingHelper.remove()
settingsViewModel.setShouldReloadSettingsList(true)
},
showNegativeButton = true,
negativeAction = {}
).show(parentFragmentManager, MessageDialogFragment.TAG)
}
}
settingsViewModel.adapterItemChanged.collect(
viewLifecycleOwner,
resetState = { settingsViewModel.setAdapterItemChanged(-1) }
@@ -291,33 +267,6 @@ private fun getPlayerIndex(): Int =
directoryPickerLauncher.launch(null)
}
private val losslessDllPickerLauncher = registerForActivityResult(
ActivityResultContracts.OpenDocument()
) { uri ->
if (uri == null) {
return@registerForActivityResult
}
val resultStrings = resources.getStringArray(R.array.losslessDllResults)
ProgressDialogFragment.newInstance(
requireActivity(),
R.string.lossless_scaling_installing,
false
) { _, _ ->
val result = LosslessScalingHelper.install(uri)
if (result == LosslessScalingHelper.RESULT_OK) {
getString(R.string.lossless_scaling_install_success)
} else {
MessageDialogFragment.newInstance(
titleId = R.string.lossless_scaling_install_failed,
descriptionString = resultStrings[result]
)
}
}.apply {
onDialogComplete = { settingsViewModel.setShouldReloadSettingsList(true) }
}.show(parentFragmentManager, ProgressDialogFragment.TAG)
}
private val directoryPickerLauncher = registerForActivityResult(
ActivityResultContracts.OpenDocumentTree()
) { uri ->
@@ -27,7 +27,6 @@ import org.yuzu.yuzu_emu.features.settings.model.ShortSetting
import org.yuzu.yuzu_emu.features.settings.model.StringSetting
import org.yuzu.yuzu_emu.features.settings.model.view.*
import org.yuzu.yuzu_emu.utils.InputHandler
import org.yuzu.yuzu_emu.utils.LosslessScalingHelper
import org.yuzu.yuzu_emu.utils.NativeConfig
import org.yuzu.yuzu_emu.utils.DirectoryInitialization
import org.yuzu.yuzu_emu.utils.FullscreenHelper
@@ -77,60 +76,6 @@ class SettingsFragmentPresenter(
}
}
private fun addFrameGenSettings(sl: ArrayList<SettingsItem>) {
sl.apply {
val installed = LosslessScalingHelper.isInstalled()
val supported = LosslessScalingHelper.isSupportedByGpu()
add(HeaderSetting(R.string.lossless_scaling))
if (!supported) {
add(
RunnableSetting(
titleId = R.string.frame_gen_unsupported,
descriptionId = R.string.frame_gen_unsupported_description,
isRunnable = false
) {}
)
}
add(
RunnableSetting(
titleId = if (installed) {
R.string.lossless_scaling_replace
} else {
R.string.lossless_scaling_install
},
descriptionId = if (installed) {
R.string.lossless_scaling_replace_description
} else {
R.string.lossless_scaling_install_description
},
isRunnable = !NativeLibrary.isRunning(),
iconId = R.drawable.ic_install
) { settingsViewModel.setShouldShowLosslessInstaller(true) }
)
if (installed) {
add(
RunnableSetting(
titleId = R.string.lossless_scaling_remove,
descriptionId = R.string.lossless_scaling_remove_description,
isRunnable = !NativeLibrary.isRunning(),
iconId = R.drawable.ic_delete
) { settingsViewModel.setShouldShowLosslessRemoveDialog(true) }
)
}
add(HeaderSetting(R.string.frame_gen))
add(BooleanSetting.RENDERER_FRAME_GEN.key)
add(IntSetting.RENDERER_FRAME_GEN_MULTIPLIER.key)
add(IntSetting.RENDERER_FRAME_GEN_FLOW_SCALE.key)
add(BooleanSetting.RENDERER_FRAME_GEN_FP16.key)
}
}
private fun isSharpnessScalingFilterSelected(): Boolean {
val needsGlobal = getNeedsGlobalForKey(IntSetting.RENDERER_SCALING_FILTER.key)
val selectedFilter = IntSetting.RENDERER_SCALING_FILTER.getInt(needsGlobal)
@@ -175,7 +120,6 @@ class SettingsFragmentPresenter(
MenuTag.SECTION_ROOT -> addConfigSettings(sl)
MenuTag.SECTION_SYSTEM -> addSystemSettings(sl)
MenuTag.SECTION_RENDERER -> addGraphicsSettings(sl)
MenuTag.SECTION_FRAME_GEN -> addFrameGenSettings(sl)
MenuTag.SECTION_PERFORMANCE_STATS -> addPerformanceOverlaySettings(sl)
MenuTag.SECTION_SOC_OVERLAY -> addSocOverlaySettings(sl)
MenuTag.SECTION_INPUT_OVERLAY -> addInputOverlaySettings(sl)
@@ -336,15 +280,6 @@ class SettingsFragmentPresenter(
}
add(IntSetting.RENDERER_ANTI_ALIASING.key)
add(
SubmenuSetting(
titleId = R.string.frame_gen,
descriptionId = R.string.frame_gen_submenu_description,
iconId = R.drawable.ic_frames,
menuKey = MenuTag.SECTION_FRAME_GEN
)
)
add(HeaderSetting(R.string.advanced))
add(IntSetting.RENDERER_ACCURACY.key)
@@ -367,7 +302,9 @@ class SettingsFragmentPresenter(
add(BooleanSetting.SKIP_CPU_INNER_INVALIDATION.key)
add(BooleanSetting.FIX_BLOOM_EFFECTS.key)
add(BooleanSetting.EMULATE_BGR565.key)
add(BooleanSetting.RESCALE_HACK.key)
add(BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS.key)
add(BooleanSetting.RENDERER_UNIFIED_MEMORY.key)
add(IntSetting.ANDROID_PIPELINE_WORKERS.key)
add(BooleanSetting.RENDERER_ASYNCHRONOUS_GPU_EMULATION.key)
add(BooleanSetting.RENDERER_ASYNC_PRESENTATION.key)
@@ -1360,7 +1297,6 @@ class SettingsFragmentPresenter(
add(BooleanSetting.DUMP_GUEST_SHADERS.key)
add(BooleanSetting.GPU_LOG_SHADER_DUMPS.key)
add(BooleanSetting.DUMP_MACROS.key)
add(BooleanSetting.RENDERER_FRAME_GEN_DUMP_FLOW.key)
add(BooleanSetting.GPU_LOG_MEMORY_TRACKING.key)
add(BooleanSetting.GPU_LOG_DRIVER_DEBUG.key)
add(IntSetting.GPU_LOG_RING_BUFFER_SIZE.key)
@@ -36,12 +36,6 @@ class SettingsViewModel : ViewModel() {
val shouldReloadSettingsList: StateFlow<Boolean> get() = _shouldReloadSettingsList
private val _shouldReloadSettingsList = MutableStateFlow(false)
val shouldShowLosslessInstaller: StateFlow<Boolean> get() = _shouldShowLosslessInstaller
private val _shouldShowLosslessInstaller = MutableStateFlow(false)
val shouldShowLosslessRemoveDialog: StateFlow<Boolean> get() = _shouldShowLosslessRemoveDialog
private val _shouldShowLosslessRemoveDialog = MutableStateFlow(false)
val sliderProgress: StateFlow<Int> get() = _sliderProgress
private val _sliderProgress = MutableStateFlow(-1)
@@ -91,14 +85,6 @@ class SettingsViewModel : ViewModel() {
_shouldReloadSettingsList.value = value
}
fun setShouldShowLosslessInstaller(value: Boolean) {
_shouldShowLosslessInstaller.value = value
}
fun setShouldShowLosslessRemoveDialog(value: Boolean) {
_shouldShowLosslessRemoveDialog.value = value
}
fun setSliderTextValue(value: Float, units: String) {
_sliderProgress.value = value.toInt()
_sliderTextValue.value = String.format(
@@ -13,7 +13,6 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.app.NotificationCompat
@@ -45,7 +44,6 @@ import org.yuzu.yuzu_emu.ui.main.MainActivity
import org.yuzu.yuzu_emu.utils.FileUtil
import org.yuzu.yuzu_emu.utils.GpuDriverHelper
import org.yuzu.yuzu_emu.utils.Log
import org.yuzu.yuzu_emu.utils.LosslessScalingHelper
import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins
class HomeSettingsFragment : Fragment() {
@@ -172,18 +170,6 @@ class HomeSettingsFragment : Fragment() {
)
)
}
add(
HomeSetting(
R.string.lossless_scaling,
R.string.lossless_scaling_description,
R.drawable.ic_frames,
{ onLosslessScalingClicked() },
{ true },
0,
0,
LosslessScalingHelper.statusText
)
)
add(
HomeSetting(
R.string.multiplayer,
@@ -351,51 +337,8 @@ class HomeSettingsFragment : Fragment() {
override fun onResume() {
super.onResume()
driverViewModel.updateDriverNameForGame(null)
LosslessScalingHelper.refreshStatus()
}
private fun onLosslessScalingClicked() {
if (!LosslessScalingHelper.isInstalled()) {
getLosslessDllLauncher.launch(arrayOf("*/*"))
return
}
MessageDialogFragment.newInstance(
requireActivity(),
titleId = R.string.lossless_scaling,
descriptionId = R.string.lossless_scaling_installed_description,
positiveButtonTitleId = R.string.lossless_scaling_replace,
positiveAction = { getLosslessDllLauncher.launch(arrayOf("*/*")) },
showNegativeButton = true,
negativeButtonTitleId = R.string.lossless_scaling_remove,
negativeAction = { LosslessScalingHelper.remove() }
).show(parentFragmentManager, MessageDialogFragment.TAG)
}
private val getLosslessDllLauncher =
registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
if (result == null) {
return@registerForActivityResult
}
val resultStrings = resources.getStringArray(R.array.losslessDllResults)
ProgressDialogFragment.newInstance(
requireActivity(),
R.string.lossless_scaling_installing,
false
) { _, _ ->
val installResult = LosslessScalingHelper.install(result)
if (installResult == LosslessScalingHelper.RESULT_OK) {
getString(R.string.lossless_scaling_install_success)
} else {
MessageDialogFragment.newInstance(
titleId = R.string.lossless_scaling_install_failed,
descriptionString = resultStrings[installResult]
)
}
}.show(parentFragmentManager, ProgressDialogFragment.TAG)
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
@@ -42,7 +42,6 @@ import org.yuzu.yuzu_emu.model.SetupPage
import org.yuzu.yuzu_emu.model.PageState
import org.yuzu.yuzu_emu.ui.main.MainActivity
import org.yuzu.yuzu_emu.utils.DirectoryInitialization
import org.yuzu.yuzu_emu.utils.LosslessScalingHelper
import org.yuzu.yuzu_emu.utils.NativeConfig
import org.yuzu.yuzu_emu.utils.ViewUtils
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
@@ -203,24 +202,6 @@ class SetupFragment : Fragment() {
R.string.install_firmware_warning_help,
)
)
add(
PageButton(
R.drawable.ic_frames,
R.string.lossless_scaling,
R.string.lossless_scaling_setup_description,
{
pageButtonCallback = it
getLosslessDll.launch(arrayOf("*/*"))
},
{
if (LosslessScalingHelper.isInstalled()) {
ButtonState.BUTTON_ACTION_COMPLETE
} else {
ButtonState.BUTTON_ACTION_INCOMPLETE
}
}
)
)
add(
PageButton(
R.drawable.ic_controller,
@@ -465,32 +446,6 @@ class SetupFragment : Fragment() {
}
}
val getLosslessDll =
registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
if (result == null) {
return@registerForActivityResult
}
val resultStrings = resources.getStringArray(R.array.losslessDllResults)
ProgressDialogFragment.newInstance(
requireActivity(),
R.string.lossless_scaling_installing,
false
) { _, _ ->
val installResult = LosslessScalingHelper.install(result)
if (installResult == LosslessScalingHelper.RESULT_OK) {
getString(R.string.lossless_scaling_install_success)
} else {
MessageDialogFragment.newInstance(
titleId = R.string.lossless_scaling_install_failed,
descriptionString = resultStrings[installResult]
)
}
}.apply {
onDialogComplete = { checkForButtonState.invoke() }
}.show(parentFragmentManager, ProgressDialogFragment.TAG)
}
val getGamesDirectory =
registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { result ->
if (result != null) {
@@ -72,18 +72,6 @@ class SystemInfoDialogFragment : DialogFragment() {
val vulkanDriver = NativeLibrary.getVulkanDriverVersion()
appendLine("${getString(R.string.vulkan_driver_version)}: $vulkanDriver")
val frameGen = NativeLibrary.supportsFrameGeneration()
appendLine(
"${getString(R.string.frame_generation_support)}: " +
getString(
if (frameGen) {
R.string.frame_generation_supported
} else {
R.string.frame_generation_unsupported
}
)
)
} catch (e: Exception) {
appendLine("${getString(R.string.error_getting_emulator_info)}: ${e.message}")
}
@@ -1,77 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.utils
import android.net.Uri
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication
import java.io.File
object LosslessScalingHelper {
const val RESULT_OK = 0
const val RESULT_NOT_INSTALLED = 1
private val _statusText = MutableStateFlow("")
val statusText: StateFlow<String> = _statusText.asStateFlow()
private var installed: Boolean? = null
private var gpuSupported: Boolean? = null
fun isInstalled(): Boolean = installed ?: refreshStatus()
fun isSupportedByGpu(): Boolean {
val cached = gpuSupported
if (cached != null) {
return cached
}
val result = NativeLibrary.supportsFrameGeneration()
gpuSupported = result
return result
}
fun refreshStatus(): Boolean {
val result = NativeLibrary.validateLosslessDll() == RESULT_OK
installed = result
val context = YuzuApplication.appContext
_statusText.value = if (result) {
context.getString(R.string.lossless_scaling_installed)
} else {
context.getString(R.string.lossless_scaling_not_installed)
}
return result
}
fun install(source: Uri): Int {
val destination = File(NativeLibrary.getLosslessDllPath())
destination.parentFile?.mkdirs()
val copied = FileUtil.copyUriToInternalStorage(
source,
destination.parent!!,
destination.name
)
if (copied == null) {
refreshStatus()
return RESULT_NOT_INSTALLED
}
val result = NativeLibrary.prepareLosslessDll()
if (result != RESULT_OK) {
NativeLibrary.removeLosslessDll()
}
refreshStatus()
return result
}
fun remove(): Boolean {
val removed = NativeLibrary.removeLosslessDll()
refreshStatus()
return removed
}
}
@@ -33,8 +33,8 @@ void AndroidConfig::SaveAllValues() {
}
void AndroidConfig::ReadAndroidValues() {
ReadAndroidUIValues();
if (global) {
ReadAndroidUIValues();
ReadUIValues();
BeginGroup(Settings::TranslateCategory(Settings::Category::DataStorage));
Settings::values.ext_content_from_game_dirs = ReadBooleanSetting(
@@ -223,8 +223,8 @@ void AndroidConfig::ReadAndroidControlValues() {
}
void AndroidConfig::SaveAndroidValues() {
SaveAndroidUIValues();
if (global) {
SaveAndroidUIValues();
SaveUIValues();
SaveOverlayValues();
}
@@ -147,7 +147,7 @@ namespace AndroidSettings {
&show_performance_overlay};
Settings::SwitchableSetting<s32> pipeline_worker_count{linkage, 2, "pipeline_worker_count",
Settings::Setting<s32> pipeline_worker_count{linkage, 4, "pipeline_worker_count",
Settings::Category::Android,
Settings::Specialization::Default,
true,
@@ -124,18 +124,9 @@ float EmuWindow_Android::GetFrameTimeVerifiedHint() const {
return QuantizeFrameRateHint(verified_rate);
}
float EmuWindow_Android::GetPresentedFrameMultiplier() {
if (!Settings::values.frame_gen.GetValue()) {
return 1.0f;
}
return static_cast<float>(std::clamp<u32>(Settings::values.frame_gen_multiplier.GetValue(), 2, 4));
}
float EmuWindow_Android::GetFrameRateHint() const {
const float presented_multiplier = GetPresentedFrameMultiplier();
const float observed_rate =
std::clamp(m_smoothed_present_rate * presented_multiplier, 0.0f, 240.0f);
const float frame_time_verified_hint = GetFrameTimeVerifiedHint() * presented_multiplier;
const float observed_rate = std::clamp(m_smoothed_present_rate, 0.0f, 240.0f);
const float frame_time_verified_hint = GetFrameTimeVerifiedHint();
if (m_last_frame_rate_hint > 0.0f && observed_rate > 0.0f) {
const float tolerance = std::max(m_last_frame_rate_hint * 0.12f, 4.0f);
@@ -159,9 +150,9 @@ float EmuWindow_Android::GetFrameRateHint() const {
return frame_time_verified_hint;
}
const float nominal_rate = 60.0f * presented_multiplier;
constexpr float NominalFrameRate = 60.0f;
if (!Settings::values.use_speed_limit.GetValue()) {
return QuantizeFrameRateHint(nominal_rate);
return NominalFrameRate;
}
const u16 speed_limit = Settings::SpeedLimit();
@@ -170,7 +161,7 @@ float EmuWindow_Android::GetFrameRateHint() const {
}
const float speed_limited_rate =
nominal_rate * (static_cast<float>(std::min<u16>(speed_limit, 100)) / 100.0f);
NominalFrameRate * (static_cast<float>(std::min<u16>(speed_limit, 100)) / 100.0f);
return QuantizeFrameRateHint(speed_limited_rate);
}
@@ -61,7 +61,6 @@ private:
void UpdateObservedFrameRate();
[[nodiscard]] float GetFrameRateHint() const;
[[nodiscard]] float GetFrameTimeVerifiedHint() const;
[[nodiscard]] static float GetPresentedFrameMultiplier();
[[nodiscard]] static float QuantizeFrameRateHint(float frame_rate);
float m_window_width{};
-55
View File
@@ -44,7 +44,6 @@ extern "C" {
#include "common/android/android_common.h"
#include "common/android/id_cache.h"
#include "common/dynamic_library.h"
#include "common/fs/fs_util.h"
#include "common/fs/path_util.h"
#include "common/logging.h"
#include "common/scm_rev.h"
@@ -91,7 +90,6 @@ extern "C" {
#include "hid_core/hid_types.h"
#include "input_common/drivers/virtual_amiibo.h"
#include "jni/native.h"
#include "video_core/frame_gen/lossless_dll.h"
#include "video_core/renderer_base.h"
#include "video_core/renderer_vulkan/renderer_vulkan.h"
#include "video_core/capture.h"
@@ -1092,34 +1090,6 @@ VkPhysicalDeviceProperties GetVulkanDeviceProperties() {
const Vulkan::vk::PhysicalDevice physical_device(physical_devices[0], dld);
return physical_device.GetProperties();
}
bool GetVulkanMemoryModelSupport() {
Common::DynamicLibrary library;
if (!library.Open("libvulkan.so")) {
return false;
}
Vulkan::vk::InstanceDispatch dld;
const auto instance = Vulkan::CreateInstance(library, dld, VK_API_VERSION_1_1);
const auto physical_devices = instance.EnumeratePhysicalDevices();
if (physical_devices.empty()) {
return false;
}
const Vulkan::vk::PhysicalDevice physical_device(physical_devices[0], dld);
VkPhysicalDeviceVulkanMemoryModelFeatures memory_model{
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_MEMORY_MODEL_FEATURES,
.pNext = nullptr,
};
VkPhysicalDeviceFeatures2 features{
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2,
.pNext = &memory_model,
};
physical_device.GetFeatures2(features);
return memory_model.vulkanMemoryModel == VK_TRUE;
}
} // namespace
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getVulkanDriverVersion(JNIEnv* env, jobject jobj) {
@@ -1195,14 +1165,6 @@ jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getVulkanApiVersion(JNIEnv* env, j
}
}
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_supportsFrameGeneration(JNIEnv* env, jobject jobj) {
try {
return static_cast<jboolean>(GetVulkanMemoryModelSupport());
} catch (...) {
return static_cast<jboolean>(false);
}
}
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getGpuModel(JNIEnv* env, jobject jobj) {
const auto props = GetVulkanDeviceProperties();
if (props.deviceID == 0) {
@@ -1428,23 +1390,6 @@ jint Java_org_yuzu_yuzu_1emu_NativeLibrary_installKeys(JNIEnv* env, jclass clazz
return static_cast<int>(FirmwareManager::InstallKeys(path, ext));
}
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getLosslessDllPath(JNIEnv* env, jclass clazz) {
const auto path = VideoCore::FrameGen::GetLosslessDllPath();
return Common::Android::ToJString(env, Common::FS::PathToUTF8String(path));
}
jint Java_org_yuzu_yuzu_1emu_NativeLibrary_validateLosslessDll(JNIEnv* env, jclass clazz) {
return static_cast<jint>(VideoCore::FrameGen::GetInstalledLosslessStatus());
}
jint Java_org_yuzu_yuzu_1emu_NativeLibrary_prepareLosslessDll(JNIEnv* env, jclass clazz) {
return static_cast<jint>(VideoCore::FrameGen::BuildShaderCache());
}
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_removeLosslessDll(JNIEnv* env, jclass clazz) {
return static_cast<jboolean>(VideoCore::FrameGen::RemoveInstalledLosslessDll());
}
jobjectArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getPatchesForFile(JNIEnv* env, jobject jobj,
jstring jpath,
jstring jprogramId) {
@@ -162,18 +162,6 @@
<item>@string/resolution_four</item>
</string-array>
<string-array name="frameGenMultiplierNames">
<item>@string/frame_gen_multiplier_2x</item>
<item>@string/frame_gen_multiplier_3x</item>
<item>@string/frame_gen_multiplier_4x</item>
</string-array>
<integer-array name="frameGenMultiplierValues">
<item>2</item>
<item>3</item>
<item>4</item>
</integer-array>
<string-array name="rendererVSyncNames">
<item>@string/renderer_vsync_immediate</item>
<item>@string/renderer_vsync_mailbox</item>
@@ -650,16 +638,6 @@
<item>@string/error_keys_failed_init</item>
</string-array>
<string-array name="losslessDllResults">
<item>""</item>
<item>@string/error_lossless_copy_failed</item>
<item>@string/error_lossless_unreadable</item>
<item>@string/error_lossless_not_pe</item>
<item>@string/error_lossless_missing_shaders</item>
<item>@string/error_lossless_translation_failed</item>
<item>@string/error_lossless_cache_failed</item>
</string-array>
<!-- GPU Logging Arrays -->
<string-array name="gpuLogLevelEntries">
<item>Off</item>
@@ -298,47 +298,6 @@
<string name="gpu_driver_fetcher">GPU driver fetcher</string>
<string name="gpu_driver_manager">GPU driver manager</string>
<string name="install_gpu_driver_description">Install alternative drivers for potentially better performance or accuracy</string>
<string name="frame_gen">Frame generation</string>
<string name="frame_gen_submenu_description">Manage and configure frame generation</string>
<string name="frame_gen_description">Insert interpolated frames between rendered ones using Lossless Scaling. Forces FIFO presentation while enabled.</string>
<string name="frame_gen_multiplier">Frame multiplier</string>
<string name="frame_gen_multiplier_description">How many frames to display for each rendered frame. Higher values cost proportionally more GPU time.</string>
<string name="frame_gen_multiplier_2x">2x</string>
<string name="frame_gen_multiplier_3x">3x</string>
<string name="frame_gen_multiplier_4x">4x</string>
<string name="frame_gen_flow_scale">Motion estimation resolution</string>
<string name="frame_gen_flow_scale_description">Resolution of the optical flow pass, as a fraction of the output. Lowering it is the cheapest way to reclaim performance.</string>
<string name="frame_gen_fp16">Half precision shaders</string>
<string name="frame_gen_fp16_description">Use the 16-bit shader variant shipped in Lossless.dll. Falls back automatically if the driver or the file lacks it.</string>
<string name="frame_gen_dump_flow">Dump generated frame</string>
<string name="frame_gen_dump_flow_description">Write the optical flow mip levels and the interpolated frame to the lossless/debug folder once, for troubleshooting</string>
<string name="frame_gen_unsupported">Frame generation unavailable</string>
<string name="frame_gen_unsupported_description">This GPU driver does not support the Vulkan memory model, which the Lossless Scaling shaders require.</string>
<string name="lossless_scaling_setup_description">Optional. Provide your own Lossless.dll to enable frame generation later</string>
<string name="lossless_scaling_install">Install Lossless.dll</string>
<string name="lossless_scaling_install_description">Frame generation needs your own legal copy of Lossless.dll from Lossless Scaling</string>
<string name="lossless_scaling_replace_description">Select a different copy of Lossless.dll</string>
<string name="frame_generation_support">Frame generation</string>
<string name="frame_generation_supported">Supported</string>
<string name="frame_generation_unsupported">Unsupported (no Vulkan memory model)</string>
<string name="lossless_scaling">Lossless Scaling</string>
<string name="lossless_scaling_description">Provide your own copy of Lossless.dll to enable frame generation</string>
<string name="lossless_scaling_installed">Installed</string>
<string name="lossless_scaling_not_installed">Not installed</string>
<string name="lossless_scaling_installed_description">Lossless.dll is installed and contains every shader frame generation needs.</string>
<string name="lossless_scaling_replace">Replace</string>
<string name="lossless_scaling_remove">Remove</string>
<string name="lossless_scaling_remove_description">Delete the installed Lossless.dll and its prepared shaders</string>
<string name="lossless_scaling_remove_confirmation">Frame generation will stop working until you install Lossless.dll again. Your original file is not affected.</string>
<string name="lossless_scaling_installing">Preparing frame generation shaders…</string>
<string name="lossless_scaling_install_success">Lossless.dll installed successfully</string>
<string name="lossless_scaling_install_failed">Could not install Lossless.dll</string>
<string name="error_lossless_copy_failed">The selected file could not be copied.</string>
<string name="error_lossless_unreadable">The selected file could not be read.</string>
<string name="error_lossless_not_pe">The selected file is not a Windows library. Select Lossless.dll from your Lossless Scaling installation.</string>
<string name="error_lossless_missing_shaders">This copy of Lossless.dll does not contain the frame generation shaders. Update Lossless Scaling and try again.</string>
<string name="error_lossless_translation_failed">The frame generation shaders could not be translated. This version of Lossless Scaling is not supported yet.</string>
<string name="error_lossless_cache_failed">The translated shaders could not be written to storage. Check that there is free space available.</string>
<string name="advanced_settings">Advanced settings</string>
<string name="settings_description">Configure emulator settings</string>
<string name="search_recently_played">Recently played</string>
@@ -565,6 +524,8 @@
<string name="rescale_hack_description">Enables a legacy handling for the rescale configuration pass for games by using a quick rescale path</string>
<string name="renderer_asynchronous_shaders">Use asynchronous shaders</string>
<string name="renderer_asynchronous_shaders_description">Compiles shaders asynchronously. This may reduce stutters but may also introduce glitches.</string>
<string name="renderer_unified_memory">Unified memory access</string>
<string name="renderer_unified_memory_description">Allows GPU write buffer readbacks directly into guest memory, skipping the CPU staging copy.</string>
<string name="gpu_unswizzle_settings">GPU Unswizzle Settings</string>
<string name="gpu_unswizzle_settings_description">Configure GPU-based texture unswizzling parameters or disable it entirely. Adjust these settings to balance performance and texture loading quality.</string>
<string name="gpu_unswizzle_enable">Enable GPU Unswizzle</string>
+1 -1
View File
@@ -222,7 +222,7 @@ if (MSVC)
)
else()
target_compile_options(audio_core PRIVATE
$<$<COMPILE_LANGUAGE:C,CXX>:-Wconversion>
$<$<COMPILE_LANGUAGE:C,CXX>:-Werror=conversion>
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-sign-conversion>)
endif()
+1 -1
View File
@@ -220,7 +220,7 @@ endif()
if(CXX_CLANG)
target_compile_options(common PRIVATE
$<$<COMPILE_LANGUAGE:C,CXX>:-fsized-deallocation>
$<$<COMPILE_LANGUAGE:C,CXX>:-Wunreachable-code-aggressive>)
$<$<COMPILE_LANGUAGE:C,CXX>:-Werror=unreachable-code-aggressive>)
target_compile_definitions(
common
PRIVATE
-3
View File
@@ -20,7 +20,6 @@
#define KEYS_DIR "keys"
#define LOAD_DIR "load"
#define LOG_DIR "log"
#define LOSSLESS_DIR "lossless"
#define NAND_DIR "nand"
#define PLAY_TIME_DIR "play_time"
#define SCREENSHOTS_DIR "screenshots"
@@ -38,5 +37,3 @@
// yuzu-specific files
#define LOG_FILE "eden_log.txt"
#define LOSSLESS_DLL_FILE "Lossless.dll"
#define LOSSLESS_CACHE_FILE "lsfg_spirv.cache"
-1
View File
@@ -157,7 +157,6 @@ public:
GenerateEdenPath(EdenPath::KeysDir, eden_path / KEYS_DIR);
GenerateEdenPath(EdenPath::LoadDir, eden_path / LOAD_DIR);
GenerateEdenPath(EdenPath::LogDir, eden_path / LOG_DIR);
GenerateEdenPath(EdenPath::LosslessDir, eden_path / LOSSLESS_DIR);
GenerateEdenPath(EdenPath::NANDDir, eden_path / NAND_DIR);
GenerateEdenPath(EdenPath::PlayTimeDir, eden_path / PLAY_TIME_DIR);
GenerateEdenPath(EdenPath::SaveDir, eden_path / NAND_DIR);
-1
View File
@@ -23,7 +23,6 @@ enum class EdenPath {
KeysDir, // Where key files are stored.
LoadDir, // Where cheat/mod files are stored.
LogDir, // Where log files are stored.
LosslessDir, // Where the user-supplied Lossless Scaling library is stored.
NANDDir, // Where the emulated NAND is stored.
PlayTimeDir, // Where play time data is stored.
SaveDir, // Where save data is stored.
+21 -8
View File
@@ -31,7 +31,9 @@ s64 GetMaxPermissibleResidentMapCount() {
} // namespace
HeapTracker::HeapTracker(Common::HostMemory& buffer)
: m_buffer(buffer), m_max_resident_map_count(GetMaxPermissibleResidentMapCount()) {}
: m_buffer(buffer),
m_has_hardware_buffer_backing(!buffer.BackingHardwareBuffers().empty()),
m_max_resident_map_count(GetMaxPermissibleResidentMapCount()) {}
HeapTracker::~HeapTracker() = default;
void HeapTracker::Map(size_t virtual_offset, size_t host_offset, size_t length,
@@ -85,7 +87,8 @@ void HeapTracker::Unmap(size_t virtual_offset, size_t size, bool is_separate_hea
// If resident, erase from resident map.
if (item->is_resident) {
ASSERT(--m_resident_map_count >= 0);
m_resident_map_count -= this->HostMapCount(item->paddr, item->size);
ASSERT(m_resident_map_count >= 0);
m_resident_mappings.erase(m_resident_mappings.iterator_to(*item));
}
@@ -191,7 +194,7 @@ bool HeapTracker::DeferredMapSeparateHeap(size_t virtual_offset) {
// This map is now resident.
it->is_resident = true;
m_resident_map_count++;
m_resident_map_count += this->HostMapCount(it->paddr, it->size);
m_resident_mappings.insert(*it);
}
@@ -213,17 +216,17 @@ void HeapTracker::RebuildSeparateHeapAddressSpace() {
// Despite being worse in theory, this has proven to be better in practice than more
// regularly dumping a smaller amount, because it significantly reduces average case
// lock contention.
std::size_t const desired_count = (std::min)(m_resident_map_count, m_max_resident_map_count) / 2;
std::size_t const evict_count = m_resident_map_count - desired_count;
s64 const desired_count = (std::min)(m_resident_map_count, m_max_resident_map_count) / 2;
auto it = m_resident_mappings.begin();
for (size_t i = 0; i < evict_count && it != m_resident_mappings.end(); i++) {
while (m_resident_map_count > desired_count && it != m_resident_mappings.end()) {
// Unmark and unmap.
it->is_resident = false;
m_buffer.Unmap(it->vaddr, it->size, false);
// Advance.
ASSERT(--m_resident_map_count >= 0);
m_resident_map_count -= this->HostMapCount(it->paddr, it->size);
ASSERT(m_resident_map_count >= 0);
it = m_resident_mappings.erase(it);
}
}
@@ -245,6 +248,7 @@ void HeapTracker::SplitHeapMapLocked(VAddr offset) {
// Cache the original values.
auto* const left = std::addressof(*it);
const size_t orig_size = left->size;
const s64 orig_host_map_count = this->HostMapCount(left->paddr, orig_size);
// Adjust the left map.
const size_t left_size = offset - left->vaddr;
@@ -266,11 +270,20 @@ void HeapTracker::SplitHeapMapLocked(VAddr offset) {
// If resident, also insert into resident map.
if (right->is_resident) {
m_resident_map_count++;
m_resident_map_count += this->HostMapCount(left->paddr, left->size) +
this->HostMapCount(right->paddr, right->size) -
orig_host_map_count;
m_resident_mappings.insert(*right);
}
}
s64 HeapTracker::HostMapCount(PAddr paddr, size_t size) const {
if (!m_has_hardware_buffer_backing) {
return size != 0 ? 1 : 0;
}
return static_cast<s64>(m_buffer.BackingMapCount(paddr, size));
}
HeapTracker::AddrTree::iterator HeapTracker::GetNearestHeapMapLocked(VAddr offset) {
const SeparateHeapMap key{
.vaddr = offset,
+6
View File
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -82,10 +85,13 @@ private:
AddrTree::iterator GetNearestHeapMapLocked(VAddr offset);
s64 HostMapCount(PAddr paddr, size_t size) const;
void RebuildSeparateHeapAddressSpace();
private:
Common::HostMemory& m_buffer;
const bool m_has_hardware_buffer_backing;
const s64 m_max_resident_map_count;
std::shared_mutex m_rebuild_lock{};
+368 -6
View File
@@ -51,14 +51,45 @@
#endif // ^^^ POSIX ^^^
#include <atomic>
#include <mutex>
#include <random>
#include <vector>
#include "common/alignment.h"
#include "common/assert.h"
#include "common/free_region_manager.h"
#include "common/host_memory.h"
#include "common/logging.h"
#include "common/memory_detect.h"
#include "common/settings.h"
#ifdef __ANDROID__
#include <dlfcn.h>
#include <android/hardware_buffer.h>
namespace {
struct NativeHandle {
int version;
int numFds;
int numInts;
int data[1];
};
using PFN_AHardwareBuffer_getNativeHandle = const NativeHandle* (*)(const AHardwareBuffer*);
PFN_AHardwareBuffer_getNativeHandle ResolveGetNativeHandle() {
void* const lib = dlopen("libnativewindow.so", RTLD_NOW);
if (lib == nullptr) {
return nullptr;
}
return reinterpret_cast<PFN_AHardwareBuffer_getNativeHandle>(
dlsym(lib, "AHardwareBuffer_getNativeHandle"));
}
} // namespace
#endif
#if defined(__ANDROID__) && __ANDROID_API__ < 30
#include <sys/syscall.h>
@@ -75,6 +106,12 @@ namespace Common {
[[maybe_unused]] constexpr size_t PageAlignment = 0x1000;
[[maybe_unused]] constexpr size_t HugePageSize = 0x200000;
static std::atomic<u64> committed_backing_size{};
u64 GetCommittedBackingSize() noexcept {
return committed_backing_size.load(std::memory_order_relaxed);
}
#ifdef _WIN32
// Manually imported for MinGW compatibility
@@ -123,7 +160,7 @@ static void GetFuncAddress(Common::DynamicLibrary& dll, const char* name, T& pfn
class HostMemory::Impl {
public:
explicit Impl(size_t backing_size_, size_t virtual_size_)
explicit Impl(size_t backing_size_, size_t virtual_size_, size_t)
: backing_size{backing_size_}
, virtual_size{virtual_size_}
, process{GetCurrentProcess()}
@@ -229,6 +266,10 @@ public:
UNREACHABLE();
}
bool IsBackingShared() const noexcept {
return true;
}
const size_t backing_size; ///< Size of the backing memory in bytes
const size_t virtual_size; ///< Size of the virtual address placeholder in bytes
@@ -501,9 +542,10 @@ static int shm_open_anon(int flags, mode_t mode) {
class HostMemory::Impl {
public:
explicit Impl(size_t backing_size_, size_t virtual_size_)
explicit Impl(size_t backing_size_, size_t virtual_size_, size_t preferred_offset_)
: backing_size{backing_size_}
, virtual_size{virtual_size_}
, preferred_offset{preferred_offset_}
{}
bool Init() {
@@ -543,10 +585,15 @@ public:
LOG_WARNING(Common_Memory, "Using private mappings instead of shared ones");
backing_base = static_cast<u8*>(mmap(nullptr, backing_size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0));
if (fd > 0) {
fd = -1;
close(fd);
}
fd = -1;
} else {
#ifdef __ANDROID__
if (InitAhbBacking()) {
return InitVirtual();
}
#endif
backing_base = static_cast<u8*>(mmap(nullptr, backing_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0));
}
if (backing_base == MAP_FAILED) {
@@ -554,7 +601,10 @@ public:
return false;
}
// Virtual memory initialization
return InitVirtual();
}
bool InitVirtual() {
virtual_base = virtual_map_base = static_cast<u8*>(ChooseVirtualBase(virtual_size));
if (virtual_base == MAP_FAILED) {
LOG_CRITICAL(HW_Memory, "mmap failed: {}", strerror(errno));
@@ -567,6 +617,242 @@ public:
return true;
}
#ifdef __ANDROID__
static AHardwareBuffer_Desc MakeBlobDesc(size_t len) {
return AHardwareBuffer_Desc{
.width = static_cast<u32>(len),
.height = 1,
.layers = 1,
.format = AHARDWAREBUFFER_FORMAT_BLOB,
.usage = AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN |
AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN |
AHARDWAREBUFFER_USAGE_GPU_DATA_BUFFER,
.stride = 0,
.rfu0 = 0,
.rfu1 = 0,
};
}
static bool ProbeAhbBacking(PFN_AHardwareBuffer_getNativeHandle get_native_handle) {
const AHardwareBuffer_Desc desc = MakeBlobDesc(PageAlignment * 2);
AHardwareBuffer* buffer{};
if (AHardwareBuffer_allocate(&desc, &buffer) != 0 || buffer == nullptr) {
return false;
}
const NativeHandle* const handle = get_native_handle(buffer);
if (handle == nullptr || handle->numFds < 1) {
AHardwareBuffer_release(buffer);
return false;
}
const int probe_fd = handle->data[0];
bool ok = true;
const auto try_map = [&](int prot, off_t offset, const char* what) {
if (!ok) {
return;
}
void* const ptr = mmap(nullptr, PageAlignment, prot, MAP_SHARED, probe_fd, offset);
if (ptr == MAP_FAILED) {
ok = false;
return;
}
munmap(ptr, PageAlignment);
};
try_map(PROT_READ | PROT_WRITE, 0, "shared mappings");
try_map(PROT_READ | PROT_WRITE, static_cast<off_t>(PageAlignment), "mappings at an offset");
#ifdef ARCHITECTURE_arm64
try_map(PROT_READ | PROT_EXEC, 0, "executable mappings");
#endif
AHardwareBuffer_release(buffer);
return ok;
}
size_t ComputeAhbBudget(size_t window_size) const {
const u64 total_physical = Common::GetMemInfo().TotalPhysicalMemory;
constexpr u64 BaselineFootprint = 6ULL << 30;
if (total_physical <= BaselineFootprint) {
return 0;
}
const u64 max_map_count = Common::GetMaxMapCount();
constexpr u64 ReservedMaps = 24576;
if (max_map_count == 0 || max_map_count <= ReservedMaps) {
return 0;
}
u64 budget = (total_physical - BaselineFootprint) / 2;
constexpr u64 MapSlotsPerWindow = 4096;
const u64 affordable_windows = (max_map_count - ReservedMaps) / MapSlotsPerWindow;
budget = (std::min)(budget, affordable_windows * window_size);
const u64 available = Common::GetAvailablePhysicalMemory();
if (available != 0) {
budget = (std::min)(budget, available / 2);
}
budget = (std::min)(budget, static_cast<u64>(backing_size));
budget = Common::AlignDown(budget, window_size);
constexpr u64 MinimumBudget = 256ULL << 20;
if (budget < MinimumBudget) {
return 0;
}
return static_cast<size_t>(budget);
}
bool InitAhbBacking() {
if (!Settings::values.use_unified_memory.GetValue()) {
return false;
}
static const PFN_AHardwareBuffer_getNativeHandle get_native_handle =
ResolveGetNativeHandle();
if (get_native_handle == nullptr) {
return false;
}
constexpr size_t window_size = 256ULL << 20;
const size_t budget = ComputeAhbBudget(window_size);
if (budget == 0) {
return false;
}
if (!ProbeAhbBacking(get_native_handle)) {
return false;
}
const size_t aligned_backing = Common::AlignDown(backing_size, window_size);
const size_t max_windows = (std::min)(budget, aligned_backing) / window_size;
std::vector<AHardwareBuffer*> buffers;
std::vector<int> buffer_fds;
const auto cleanup = [&] {
for (AHardwareBuffer* buffer : buffers) {
AHardwareBuffer_release(buffer);
}
buffers.clear();
buffer_fds.clear();
};
for (size_t i = 0; i < max_windows; ++i) {
const AHardwareBuffer_Desc desc = MakeBlobDesc(window_size);
AHardwareBuffer* buffer{};
if (AHardwareBuffer_allocate(&desc, &buffer) != 0 || buffer == nullptr) {
break;
}
const NativeHandle* const handle = get_native_handle(buffer);
if (handle == nullptr || handle->numFds < 1) {
AHardwareBuffer_release(buffer);
break;
}
const int buffer_fd = handle->data[0];
const off_t buffer_len = lseek(buffer_fd, 0, SEEK_END);
if (buffer_len < static_cast<off_t>(window_size)) {
AHardwareBuffer_release(buffer);
break;
}
buffers.push_back(buffer);
buffer_fds.push_back(buffer_fd);
}
const size_t num_windows = buffers.size();
if (num_windows == 0) {
return false;
}
const size_t region_size = num_windows * window_size;
const size_t region_base = Common::AlignDown(
(std::min)(preferred_offset, aligned_backing - region_size), window_size);
u8* const base = static_cast<u8*>(mmap(nullptr, backing_size, PROT_NONE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0));
if (base == MAP_FAILED) {
cleanup();
return false;
}
const auto map_over_reservation = [&](size_t offset, size_t len, int map_fd,
off_t map_offset) {
if (len == 0) {
return true;
}
if (mmap(base + offset, len, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED, map_fd,
map_offset) == MAP_FAILED) {
munmap(base, backing_size);
cleanup();
return false;
}
return true;
};
if (!map_over_reservation(0, region_base, fd, 0)) {
return false;
}
for (size_t i = 0; i < num_windows; ++i) {
if (!map_over_reservation(region_base + i * window_size, window_size, buffer_fds[i],
0)) {
return false;
}
}
const size_t tail_offset = region_base + region_size;
if (!map_over_reservation(tail_offset, backing_size - tail_offset, fd,
static_cast<off_t>(tail_offset))) {
return false;
}
backing_base = base;
ahb_windows = std::move(buffers);
ahb_fds = std::move(buffer_fds);
ahb_window_size = window_size;
ahb_base = region_base;
ahb_bytes = region_size;
committed_backing_size.store(region_size, std::memory_order_relaxed);
return true;
}
void MapBackingRange(size_t virtual_offset, size_t host_offset, size_t length, int prot_flags) {
while (length > 0) {
int map_fd = fd;
off_t map_offset = static_cast<off_t>(host_offset);
size_t chunk = length;
if (host_offset < ahb_base) {
chunk = (std::min)(chunk, ahb_base - host_offset);
} else if (host_offset < ahb_base + ahb_bytes) {
const size_t relative = host_offset - ahb_base;
const size_t window = relative / ahb_window_size;
const size_t local = relative % ahb_window_size;
map_fd = ahb_fds[window];
map_offset = static_cast<off_t>(local);
chunk = (std::min)(chunk, ahb_window_size - local);
}
void* const ret = mmap(virtual_base + virtual_offset, chunk, prot_flags,
MAP_SHARED | MAP_FIXED, map_fd, map_offset);
ASSERT_MSG(ret != MAP_FAILED, "mmap: {}", strerror(errno));
virtual_offset += chunk;
host_offset += chunk;
length -= chunk;
}
}
size_t BackingMapCount(size_t host_offset, size_t length) const noexcept {
if (length == 0) {
return 0;
}
if (ahb_bytes == 0) {
return 1;
}
size_t count = 0;
while (length > 0) {
size_t chunk = length;
if (host_offset < ahb_base) {
chunk = (std::min)(chunk, ahb_base - host_offset);
} else if (host_offset < ahb_base + ahb_bytes) {
const size_t local = (host_offset - ahb_base) % ahb_window_size;
chunk = (std::min)(chunk, ahb_window_size - local);
}
host_offset += chunk;
length -= chunk;
++count;
}
return count;
}
std::span<AHardwareBuffer* const> AhbWindows() const noexcept {
return ahb_windows;
}
size_t AhbWindowSize() const noexcept {
return ahb_bytes != 0 ? ahb_window_size : 0;
}
size_t AhbBase() const noexcept {
return ahb_base;
}
#endif
~Impl() {
Release();
}
@@ -587,6 +873,12 @@ public:
#ifdef ARCHITECTURE_arm64
if (True(perms & MemoryPermission::Execute))
prot_flags |= PROT_EXEC;
#endif
#ifdef __ANDROID__
if (ahb_bytes != 0) {
MapBackingRange(virtual_offset, host_offset, length, prot_flags);
return;
}
#endif
int flags = (fd >= 0 ? MAP_SHARED : MAP_PRIVATE) | MAP_FIXED;
void* ret = mmap(virtual_base + virtual_offset, length, prot_flags, flags, fd, host_offset);
@@ -632,8 +924,18 @@ public:
virtual_base = nullptr;
}
bool IsBackingShared() const noexcept {
#ifdef __ANDROID__
if (ahb_bytes != 0) {
return true;
}
#endif
return fd >= 0;
}
const size_t backing_size; ///< Size of the backing memory in bytes
const size_t virtual_size; ///< Size of the virtual address placeholder in bytes
const size_t preferred_offset;
u8* backing_base{reinterpret_cast<u8*>(MAP_FAILED)};
u8* virtual_base{reinterpret_cast<u8*>(MAP_FAILED)};
@@ -656,6 +958,18 @@ private:
int ret = close(fd);
ASSERT_MSG(ret == 0, "close failed: {}", strerror(errno));
}
#ifdef __ANDROID__
for (AHardwareBuffer* buffer : ahb_windows) {
AHardwareBuffer_release(buffer);
}
ahb_windows.clear();
ahb_fds.clear();
if (ahb_bytes != 0) {
committed_backing_size.store(0, std::memory_order_relaxed);
ahb_bytes = 0;
}
#endif
}
void AdjustMap(size_t* virtual_offset, size_t* length) {
@@ -681,11 +995,19 @@ private:
int fd{-1}; // memfd file descriptor, -1 is the error value of memfd_create
FreeRegionManager free_manager{};
#ifdef __ANDROID__
std::vector<AHardwareBuffer*> ahb_windows;
std::vector<int> ahb_fds;
size_t ahb_window_size{};
size_t ahb_base{};
size_t ahb_bytes{};
#endif
};
#endif // ^^^ POSIX ^^^
HostMemory::HostMemory(size_t backing_size_, size_t virtual_size_)
HostMemory::HostMemory(size_t backing_size_, size_t virtual_size_, size_t preferred_offset_)
: backing_size(backing_size_)
, virtual_size(virtual_size_)
{
@@ -697,7 +1019,7 @@ HostMemory::HostMemory(size_t backing_size_, size_t virtual_size_)
#else
// Try to allocate a fastmem arena.
// The implementation will fail with std::bad_alloc on errors.
impl = std::make_unique<HostMemory::Impl>(AlignUp(backing_size, PageAlignment), AlignUp(virtual_size, PageAlignment) + HugePageSize);
impl = std::make_unique<HostMemory::Impl>(AlignUp(backing_size, PageAlignment), AlignUp(virtual_size, PageAlignment) + HugePageSize, preferred_offset_);
if (impl->Init()) {
backing_base = impl->backing_base;
virtual_base = impl->virtual_base;
@@ -767,6 +1089,46 @@ void HostMemory::ClearBackingRegion(size_t physical_offset, size_t length, u32 f
std::memset(backing_base + physical_offset, fill_value, length);
}
std::span<AHardwareBuffer* const> HostMemory::BackingHardwareBuffers() const noexcept {
#ifdef __ANDROID__
return impl ? impl->AhbWindows() : std::span<AHardwareBuffer* const>{};
#else
return {};
#endif
}
size_t HostMemory::BackingMapCount(size_t host_offset, size_t length) const noexcept {
#ifdef __ANDROID__
return impl ? impl->BackingMapCount(host_offset, length) : (length != 0 ? 1 : 0);
#else
return length != 0 ? 1 : 0;
#endif
}
size_t HostMemory::BackingHardwareBufferWindowSize() const noexcept {
#ifdef __ANDROID__
return impl ? impl->AhbWindowSize() : 0;
#else
return 0;
#endif
}
bool HostMemory::IsBackingShared() const noexcept {
#if defined(__OPENORBIS__) || defined(__managarm__)
return false;
#else
return impl && impl->IsBackingShared();
#endif
}
size_t HostMemory::BackingHardwareBufferBase() const noexcept {
#ifdef __ANDROID__
return impl ? impl->AhbBase() : 0;
#else
return 0;
#endif
}
void HostMemory::EnableDirectMappedAddress() {
#if !(defined(__OPENORBIS__) || defined(__managarm__))
if (impl) {
+20 -1
View File
@@ -8,12 +8,17 @@
#include <memory>
#include <optional>
#include <span>
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/virtual_buffer.h"
struct AHardwareBuffer;
namespace Common {
[[nodiscard]] u64 GetCommittedBackingSize() noexcept;
enum class MemoryPermission : u32 {
Read = 1 << 0,
Write = 1 << 1,
@@ -28,7 +33,7 @@ DECLARE_ENUM_FLAG_OPERATORS(MemoryPermission)
*/
class HostMemory {
public:
explicit HostMemory(size_t backing_size_, size_t virtual_size_);
explicit HostMemory(size_t backing_size_, size_t virtual_size_, size_t preferred_offset_ = 0);
~HostMemory();
/**
@@ -62,6 +67,20 @@ public:
return backing_base;
}
[[nodiscard]] size_t BackingSize() const noexcept {
return backing_size;
}
[[nodiscard]] size_t BackingMapCount(size_t host_offset, size_t length) const noexcept;
[[nodiscard]] std::span<AHardwareBuffer* const> BackingHardwareBuffers() const noexcept;
[[nodiscard]] size_t BackingHardwareBufferWindowSize() const noexcept;
[[nodiscard]] size_t BackingHardwareBufferBase() const noexcept;
[[nodiscard]] bool IsBackingShared() const noexcept;
[[nodiscard]] u8* VirtualBasePointer() noexcept {
return virtual_base;
}
+58
View File
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -17,6 +20,10 @@
#endif
#endif
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include "common/memory_detect.h"
namespace Common {
@@ -69,4 +76,55 @@ const MemoryInfo& GetMemInfo() {
return mem_info;
}
u64 GetAvailablePhysicalMemory() {
#ifdef _WIN32
MEMORYSTATUSEX memorystatus;
memorystatus.dwLength = sizeof(memorystatus);
if (GlobalMemoryStatusEx(&memorystatus)) {
return memorystatus.ullAvailPhys;
}
return 0;
#elif defined(__linux__)
if (std::FILE* const file = std::fopen("/proc/meminfo", "re")) {
char line[256];
u64 available = 0;
while (std::fgets(line, sizeof(line), file) != nullptr) {
if (std::strncmp(line, "MemAvailable:", 13) == 0) {
available = std::strtoull(line + 13, nullptr, 10) * 1024ULL;
break;
}
}
std::fclose(file);
if (available != 0) {
return available;
}
}
struct sysinfo info;
if (sysinfo(&info) == 0) {
const u64 unit = info.mem_unit != 0 ? info.mem_unit : 1ULL;
return (static_cast<u64>(info.freeram) + static_cast<u64>(info.bufferram)) * unit;
}
return 0;
#else
return 0;
#endif
}
u64 GetMaxMapCount() {
#ifdef __linux__
if (std::FILE* const file = std::fopen("/proc/sys/vm/max_map_count", "re")) {
char line[32];
u64 count = 0;
if (std::fgets(line, sizeof(line), file) != nullptr) {
count = std::strtoull(line, nullptr, 10);
}
std::fclose(file);
return count;
}
return 0;
#else
return 0;
#endif
}
} // namespace Common
+7
View File
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -18,4 +21,8 @@ struct MemoryInfo {
*/
[[nodiscard]] const MemoryInfo& GetMemInfo();
[[nodiscard]] u64 GetAvailablePhysicalMemory();
[[nodiscard]] u64 GetMaxMapCount();
} // namespace Common
+12 -44
View File
@@ -352,7 +352,7 @@ struct Values {
true};
SwitchableSetting<ScalingFilter> scaling_filter{linkage,
ScalingFilter::NearestNeighbor,
ScalingFilter::Bilinear,
"scaling_filter",
Category::Renderer,
Specialization::Default,
@@ -388,38 +388,6 @@ struct Values {
true,
true};
SwitchableSetting<bool> frame_gen{linkage, false, "frame_gen", Category::Renderer,
Specialization::Default, true, false};
SwitchableSetting<u32, true> frame_gen_multiplier{linkage,
2,
2,
4,
"frame_gen_multiplier",
Category::Renderer,
Specialization::Countable,
true,
false,
&frame_gen};
SwitchableSetting<u32, true> frame_gen_flow_scale{linkage,
75,
25,
100,
"frame_gen_flow_scale",
Category::Renderer,
Specialization::Countable |
Specialization::Percentage,
true,
true,
&frame_gen};
SwitchableSetting<bool> frame_gen_fp16{linkage, true, "frame_gen_fp16", Category::Renderer,
Specialization::Default, true, false, &frame_gen};
SwitchableSetting<bool> frame_gen_dump_flow{linkage, false, "frame_gen_dump_flow",
Category::Renderer};
SwitchableSetting<bool> use_asynchronous_gpu_emulation{linkage,
#ifdef __ANDROID__
false,
@@ -601,8 +569,13 @@ struct Values {
SwitchableSetting<bool> emulate_bgr565{linkage, false, "emulate_bgr565",
Category::RendererHacks};
SwitchableSetting<bool> rescale_hack{linkage, false, "rescale_hack",
Category::RendererHacks};
SwitchableSetting<bool> rescale_hack{linkage,
#ifdef __ANDROID__
true,
#else
false,
#endif
"rescale_hack", Category::RendererHacks};
SwitchableSetting<bool> enable_gpu_buffer_readback{linkage,
false,
"enable_gpu_buffer_readback",
@@ -614,6 +587,9 @@ struct Values {
SwitchableSetting<bool> use_asynchronous_shaders{linkage, false, "use_asynchronous_shaders",
Category::RendererHacks};
SwitchableSetting<bool> use_unified_memory{linkage, false, "use_unified_memory",
Category::RendererHacks};
SwitchableSetting<GpuUnswizzleSize> gpu_unswizzle_texture_size{linkage,
GpuUnswizzleSize::Large,
"gpu_unswizzle_texture_size",
@@ -729,15 +705,7 @@ struct Values {
// Controls
InputSetting<std::array<PlayerInput, 10>> players;
Setting<bool> disable_wgi_xinput{
linkage, false, "disable_wgi_xinput", Category::Controls, Specialization::Default,
// Only read/write disable_wgi_xinput on Windows platforms
#ifdef _WIN32
true
#else
false
#endif
};
Setting<bool> enable_raw_input{
linkage, false, "enable_raw_input", Category::Controls, Specialization::Default,
// Only read/write enable_raw_input on Windows platforms
+2 -2
View File
@@ -1173,7 +1173,7 @@ add_library(core STATIC
if (ENABLE_WIFI_SCAN)
target_sources(core PRIVATE internal_network/wifi_scanner.cpp)
if (LINUX)
if (PLATFORM_LINUX)
target_link_libraries(core PRIVATE iw)
endif()
else()
@@ -1199,7 +1199,7 @@ if (MSVC)
)
else()
target_compile_options(core PRIVATE
$<$<COMPILE_LANGUAGE:C,CXX>:-Wconversion>
$<$<COMPILE_LANGUAGE:C,CXX>:-Werror=conversion>
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-sign-conversion>
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-cast-function-type>
$<$<CXX_COMPILER_ID:Clang>:-fsized-deallocation>)
+5 -1
View File
@@ -119,6 +119,7 @@ struct System::Impl {
is_multicore = Settings::values.use_multi_core.GetValue();
extended_memory_layout = Settings::values.memory_layout_mode.GetValue() != Settings::MemoryLayout::Memory_4Gb;
unified_memory = Settings::values.use_unified_memory.GetValue();
core_timing.SetMulticore(is_multicore);
core_timing.Initialize([&system]() { system.RegisterHostThread(); });
@@ -146,7 +147,8 @@ struct System::Impl {
!device_memory.has_value() ||
is_multicore != Settings::values.use_multi_core.GetValue() ||
extended_memory_layout != (Settings::values.memory_layout_mode.GetValue() !=
Settings::MemoryLayout::Memory_4Gb);
Settings::MemoryLayout::Memory_4Gb) ||
unified_memory != Settings::values.use_unified_memory.GetValue();
if (!must_reinitialize) {
return;
@@ -157,6 +159,7 @@ struct System::Impl {
is_multicore = Settings::values.use_multi_core.GetValue();
extended_memory_layout =
Settings::values.memory_layout_mode.GetValue() != Settings::MemoryLayout::Memory_4Gb;
unified_memory = Settings::values.use_unified_memory.GetValue();
Initialize(system);
}
@@ -503,6 +506,7 @@ struct System::Impl {
std::atomic_bool is_powered_on{};
bool is_multicore : 1 = false;
bool extended_memory_layout : 1 = false;
bool unified_memory : 1 = false;
bool exit_locked : 1 = false;
bool exit_requested : 1 = false;
bool nvdec_active : 1 = false;
+13 -1
View File
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -12,9 +15,18 @@ constexpr size_t VirtualReserveSize = 1ULL << 38;
constexpr size_t VirtualReserveSize = 1ULL << 39;
#endif
namespace {
size_t ApplicationPoolOffset() {
using Init = Kernel::Board::Nintendo::Nx::KSystemControl::Init;
const size_t dram_size = Init::GetIntendedMemorySize();
const size_t application_pool_size = Init::GetApplicationPoolSize();
return dram_size > application_pool_size ? dram_size - application_pool_size : 0;
}
}
DeviceMemory::DeviceMemory()
: buffer{Kernel::Board::Nintendo::Nx::KSystemControl::Init::GetIntendedMemorySize(),
VirtualReserveSize} {}
VirtualReserveSize, ApplicationPoolOffset()} {}
DeviceMemory::~DeviceMemory() = default;
+35
View File
@@ -20,6 +20,8 @@
#include "common/scratch_buffer.h"
#include "common/virtual_buffer.h"
struct AHardwareBuffer;
namespace Core {
constexpr size_t DEVICE_PAGEBITS = 12ULL;
@@ -95,6 +97,34 @@ public:
ApplyOpOnPAddr(address, buffer, operation);
}
u8* GetPhysicalBase() noexcept {
return reinterpret_cast<u8*>(physical_base);
}
const u8* GetPhysicalBase() const noexcept {
return reinterpret_cast<const u8*>(physical_base);
}
size_t GetPhysicalSize() const noexcept {
return physical_size;
}
std::span<AHardwareBuffer* const> GetBackingHardwareBuffers() const noexcept {
return ahb_windows;
}
size_t GetBackingHardwareBufferWindowSize() const noexcept {
return ahb_window_size;
}
size_t GetBackingHardwareBufferBase() const noexcept {
return ahb_base;
}
bool IsBackingShared() const noexcept {
return backing_is_shared;
}
PAddr GetPhysicalRawAddressFromDAddr(DAddr address) const {
PAddr subbits = PAddr(address & page_mask);
auto paddr = tracked_entries[(address >> page_bits)].compressed_physical_ptr;
@@ -171,6 +201,11 @@ private:
std::unique_ptr<DeviceMemoryManagerAllocator<Traits>> impl;
const uintptr_t physical_base;
const size_t physical_size;
const std::span<AHardwareBuffer* const> ahb_windows;
const size_t ahb_window_size;
const size_t ahb_base;
const bool backing_is_shared;
DeviceInterface* device_inter;
struct TrackedEntry {
+5
View File
@@ -171,6 +171,11 @@ struct DeviceMemoryManagerAllocator {
template <typename Traits>
DeviceMemoryManager<Traits>::DeviceMemoryManager(const DeviceMemory& device_memory_)
: physical_base{uintptr_t(device_memory_.buffer.BackingBasePointer())}
, physical_size{device_memory_.buffer.BackingSize()}
, ahb_windows{device_memory_.buffer.BackingHardwareBuffers()}
, ahb_window_size{device_memory_.buffer.BackingHardwareBufferWindowSize()}
, ahb_base{device_memory_.buffer.BackingHardwareBufferBase()}
, backing_is_shared{device_memory_.buffer.IsBackingShared()}
, device_inter{nullptr}
, compressed_device_addr(1ULL << ((Settings::values.memory_layout_mode.GetValue() == Settings::MemoryLayout::Memory_4Gb ? physical_min_bits : physical_max_bits) - Memory::YUZU_PAGEBITS))
, tracked_entries(device_as_size >> Memory::YUZU_PAGEBITS)
@@ -28,7 +28,7 @@ Result CloseHandle(Core::System& system, Handle handle) {
/// Clears the signaled state of an event or process.
Result ResetSignal(Core::System& system, Handle handle) {
LOG_TRACE(Kernel_SVC, "called handle {:#08x}", handle);
LOG_DEBUG(Kernel_SVC, "called handle {:#08x}", handle);
// Get the current handle table.
const auto& handle_table = GetCurrentProcess(system.Kernel()).GetHandleTable();
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator
@@ -84,7 +84,7 @@ private:
RestrictionSettings restriction_settings{};
std::array<char, 8> pin_code{};
Capability capability{};
// TODO: this is raw
// TODO: this is RAW as fuck
PlayTimerSettings raw_play_timer_settings{};
KernelHelpers::ServiceContext service_context;
@@ -1020,7 +1020,7 @@ Result ISystemSettingsServer::GetBatteryLot(Out<BatteryLot> out_battery_lot) {
c.lot_number[1] = 'H';
c.lot_number[2] = 'A';
c.lot_number[3] = 'C';
// TODO: what do the letters mean?
// TODO: I have no fucking idea what the letters mean
c.lot_number[4] = 'H';
c.lot_number[5] = 'Z';
c.lot_number[6] = 'Z';
+1 -2
View File
@@ -531,8 +531,7 @@ int TranslateTypeToNative(Type type) {
NETWORK_PROTOCOL_TRANSLATE_ELEM(MPLS) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(PFSYNC)
#elif defined(__linux__)
// Other platforms may not support some niche protocols.
// This is usually not an issue
// Other platforms get fucked
#define NETWORK_PROTOCOL_TRANSLATE_LIST \
NETWORK_PROTOCOL_TRANSLATE_ELEM(IP) \
/*NETWORK_PROTOCOL_TRANSLATE_ELEM(HOPOPTS)*/ \
+1 -1
View File
@@ -25,7 +25,7 @@ endif()
# Dynarmic project options
option(DYNARMIC_ENABLE_CPU_FEATURE_DETECTION "Turning this off causes dynarmic to assume the host CPU doesn't support anything later than SSE3" ON)
if (OPENBSD OR DRAGONFLY OR NETBSD)
if (PLATFORM_OPENBSD OR PLATFORM_DRAGONFLY OR PLATFORM_NETBSD)
set(REQUIRE_WX ON)
else()
set(REQUIRE_WX OFF)
+1 -1
View File
@@ -363,7 +363,7 @@ elseif (APPLE)
backend/exception_handler_macos_mig.c
)
endif()
elseif (UNIX AND NOT HAIKUOS)
elseif (UNIX AND NOT PLATFORM_HAIKU)
# Haiku lacks <ucontext.h>
if (CMAKE_SYSTEM_NAME STREQUAL "Linux")
target_link_libraries(dynarmic PRIVATE rt)
@@ -234,7 +234,7 @@ private:
BlockOfCode block_of_code;
A32EmitX64 emitter;
Optimization::PolyfillOptions polyfill_options;
// Keep it here in order to not mess with initializer lists
// Keep it here, you don't wanna mess with the fuckery that's initializer lists
const A32::UserConfig conf;
Jit* jit_interface;
@@ -2160,6 +2160,8 @@ void EmitFPVectorToFixed(BlockOfCode& code, EmitContext& ctx, IR::Inst* inst) {
ROUNDING_MODE_CASE(CASE, 0x3e) \
ROUNDING_MODE_CASE(CASE, 0x3f)
// FUCK YOU MSVC, FUCKING DEPTH CANT EVEN HANDLE 8+16+32+64 DEPTH OF A ELSE STATMENT YOU FUCKING STUPID
// BURN MSVC BURN IT STUPID COMPILER CAN'T EVEN COMPILE THE MOST BASIC C++
ROUNDING_MODE_SWITCH(ToNearest_TieEven)
ROUNDING_MODE_SWITCH(TowardsPlusInfinity)
ROUNDING_MODE_SWITCH(TowardsMinusInfinity)
+1 -1
View File
@@ -787,7 +787,7 @@ static void FoldCountLeadingZeros(IR::Inst& inst, bool is_32_bit) {
/// Folds division operations based on the following:
///
/// 1. x / 0 -> 0 (NOTE: This is an ARM-specific behavior defined in the architecture reference manual)
/// 2a. 0x8000_0000 / 0xFFFF_FFFF -> 0x8000_0000 (NOTE: More ARM errata)
/// 2a. 0x8000_0000 / 0xFFFF_FFFF -> 0x8000_0000 (NOTE: More ARM bullshit)
/// 2b. 0x8000_0000_0000_0000 / 0xFFFF_FFFF_FFFF_FFFF -> 0x8000_0000_0000_0000
/// 3. imm_x / imm_y -> result
/// 4. x / 1 -> x
+1 -1
View File
@@ -151,7 +151,7 @@ if (MSVC)
)
else()
target_compile_options(hid_core PRIVATE
$<$<COMPILE_LANGUAGE:C,CXX>:-Wconversion>
$<$<COMPILE_LANGUAGE:C,CXX>:-Werror=conversion>
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-sign-conversion>
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-cast-function-type>
$<$<CXX_COMPILER_ID:Clang>:-fsized-deallocation>)
+1 -1
View File
@@ -44,7 +44,7 @@ if (MSVC)
/we4800 # Implicit conversion from 'type' to bool. Possible information loss
)
else()
target_compile_options(input_common PRIVATE $<$<COMPILE_LANGUAGE:C,CXX>:-Wconversion>)
target_compile_options(input_common PRIVATE $<$<COMPILE_LANGUAGE:C,CXX>:-Werror=conversion>)
endif()
if (ANDROID)
+2 -5
View File
@@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
@@ -160,8 +157,8 @@ Common::ParamPackage Android::BuildButtonParamPackageForButton(PadIdentifier ide
bool Android::MatchVID(Common::UUID device, const std::vector<std::string>& vids) const {
for (size_t i = 0; i < vids.size(); ++i) {
auto dev_str = device.RawString();
if (dev_str.find(vids[i]) != std::string::npos) {
auto fucker = device.RawString();
if (fucker.find(vids[i]) != std::string::npos) {
return true;
}
}
-7
View File
@@ -648,13 +648,6 @@ SDLDriver::SDLDriver(std::string input_engine_) : InputEngine(std::move(input_en
// Disable raw input. When enabled this setting causes SDL to die when a web applet opens
SDL_SetHint(SDL_HINT_JOYSTICK_RAWINPUT, Settings::values.enable_raw_input ? "1" : "0");
#ifdef _WIN32
if (Settings::values.disable_wgi_xinput) {
SDL_SetHintWithPriority(SDL_HINT_JOYSTICK_RAWINPUT_CORRELATE_XINPUT, "0", SDL_HINT_OVERRIDE);
SDL_SetHintWithPriority(SDL_HINT_JOYSTICK_WGI, "0", SDL_HINT_OVERRIDE);
}
#endif
// SDL3 defaults Steam Controller Bluetooth HIDAPI support to off, which can disable gyro.
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_STEAM, "1");
SDL_SetHint(SDL_HINT_GAMECONTROLLER_SENSOR_FUSION, "1");
+2 -2
View File
@@ -1,4 +1,4 @@
# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
# SPDX-License-Identifier: GPL-3.0-or-later
# SPDX-FileCopyrightText: 2022 yuzu Emulator Project
@@ -28,6 +28,6 @@ if (ENABLE_WEB_SERVICE)
endif()
# Solaris uses /lib/amd64/libsocket.so and /lib/amd64/libnsl.so
if (SOLARIS)
if (PLATFORM_SUN)
target_link_libraries(network PRIVATE socket nsl)
endif()
@@ -227,6 +227,8 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent) {
tr("Preserves GPU-modified data by reading it back before uploading.\nSome games require this to render certain effects properly."));
INSERT(Settings, use_asynchronous_shaders, tr("Enable asynchronous shader compilation"),
tr("May reduce shader stutter."));
INSERT(Settings, use_unified_memory, tr("Enable unified memory access"),
tr("Lets the GPU write buffer readbacks directly into guest memory."));
INSERT(Settings, gpu_clock, tr("GPU Clocks"),
tr("Makes the game believe GPU work finishes faster than it does, so it stops lowering "
"resolution and render distance to fit the Switch's clocks."));
+1 -1
View File
@@ -253,7 +253,7 @@ if (MSVC)
)
else()
target_compile_options(shader_recompiler PRIVATE
$<$<COMPILE_LANGUAGE:C,CXX>:-Wconversion>
$<$<COMPILE_LANGUAGE:C,CXX>:-Werror=conversion>
# Bracket depth determines maximum size of a fold expression in Clang since 9c9974c3ccb6.
# And this in turns limits the size of a std::array.
$<$<CXX_COMPILER_ID:Clang>:-fbracket-depth=1024>
@@ -13,6 +13,7 @@
namespace Shader::Backend::SPIRV {
namespace {
Id SharedPointer(EmitContext& ctx, Id offset, u32 index_offset = 0) {
offset = ctx.BoundSharedOffset(offset, 4 + index_offset * 4);
const Id shift_id{ctx.Const(2U)};
Id index{ctx.OpShiftRightArithmetic(ctx.U32[1], offset, shift_id)};
if (index_offset > 0) {
@@ -160,7 +161,8 @@ Id EmitSharedAtomicExchange32(EmitContext& ctx, Id offset, Id value) {
Id EmitSharedAtomicExchange64(EmitContext& ctx, Id offset, Id value) {
if (ctx.profile.support_shared_int64_atomics && ctx.uses_explicit_workgroup_layout) {
const Id shift_id{ctx.Const(3U)};
const Id index{ctx.OpShiftRightArithmetic(ctx.U32[1], offset, shift_id)};
const Id index{
ctx.OpShiftRightArithmetic(ctx.U32[1], ctx.BoundSharedOffset(offset, 8), shift_id)};
const Id pointer{
ctx.OpAccessChain(ctx.shared_u64, ctx.shared_memory_u64, ctx.u32_zero_value, index)};
const auto [scope, semantics]{AtomicArgs(ctx)};
@@ -31,6 +31,7 @@ std::pair<Id, Id> ExtractArgs(EmitContext& ctx, Id offset, u32 mask, u32 count)
} // Anonymous namespace
Id EmitLoadSharedU8(EmitContext& ctx, Id offset) {
offset = ctx.BoundSharedOffset(offset, 1);
if (ctx.uses_explicit_workgroup_layout) {
const Id pointer{
ctx.OpAccessChain(ctx.shared_u8, ctx.shared_memory_u8, ctx.u32_zero_value, offset)};
@@ -42,6 +43,7 @@ Id EmitLoadSharedU8(EmitContext& ctx, Id offset) {
}
Id EmitLoadSharedS8(EmitContext& ctx, Id offset) {
offset = ctx.BoundSharedOffset(offset, 1);
if (ctx.uses_explicit_workgroup_layout) {
const Id pointer{
ctx.OpAccessChain(ctx.shared_u8, ctx.shared_memory_u8, ctx.u32_zero_value, offset)};
@@ -53,6 +55,7 @@ Id EmitLoadSharedS8(EmitContext& ctx, Id offset) {
}
Id EmitLoadSharedU16(EmitContext& ctx, Id offset) {
offset = ctx.BoundSharedOffset(offset, 2);
if (ctx.uses_explicit_workgroup_layout) {
const Id pointer{Pointer(ctx, ctx.shared_u16, ctx.shared_memory_u16, offset, 1)};
return ctx.OpUConvert(ctx.U32[1], ctx.OpLoad(ctx.U16, pointer));
@@ -63,6 +66,7 @@ Id EmitLoadSharedU16(EmitContext& ctx, Id offset) {
}
Id EmitLoadSharedS16(EmitContext& ctx, Id offset) {
offset = ctx.BoundSharedOffset(offset, 2);
if (ctx.uses_explicit_workgroup_layout) {
const Id pointer{Pointer(ctx, ctx.shared_u16, ctx.shared_memory_u16, offset, 1)};
return ctx.OpSConvert(ctx.U32[1], ctx.OpLoad(ctx.U16, pointer));
@@ -73,6 +77,7 @@ Id EmitLoadSharedS16(EmitContext& ctx, Id offset) {
}
Id EmitLoadSharedU32(EmitContext& ctx, Id offset) {
offset = ctx.BoundSharedOffset(offset, 4);
if (ctx.uses_explicit_workgroup_layout) {
const Id pointer{Pointer(ctx, ctx.shared_u32, ctx.shared_memory_u32, offset, 2)};
return ctx.OpLoad(ctx.U32[1], pointer);
@@ -82,6 +87,7 @@ Id EmitLoadSharedU32(EmitContext& ctx, Id offset) {
}
Id EmitLoadSharedU64(EmitContext& ctx, Id offset) {
offset = ctx.BoundSharedOffset(offset, 8);
if (ctx.uses_explicit_workgroup_layout) {
const Id pointer{Pointer(ctx, ctx.shared_u32x2, ctx.shared_memory_u32x2, offset, 3)};
return ctx.OpLoad(ctx.U32[2], pointer);
@@ -97,6 +103,7 @@ Id EmitLoadSharedU64(EmitContext& ctx, Id offset) {
}
Id EmitLoadSharedU128(EmitContext& ctx, Id offset) {
offset = ctx.BoundSharedOffset(offset, 16);
if (ctx.uses_explicit_workgroup_layout) {
const Id pointer{Pointer(ctx, ctx.shared_u32x4, ctx.shared_memory_u32x4, offset, 4)};
return ctx.OpLoad(ctx.U32[4], pointer);
@@ -113,6 +120,7 @@ Id EmitLoadSharedU128(EmitContext& ctx, Id offset) {
}
void EmitWriteSharedU8(EmitContext& ctx, Id offset, Id value) {
offset = ctx.BoundSharedOffset(offset, 1);
if (ctx.uses_explicit_workgroup_layout) {
const Id pointer{
ctx.OpAccessChain(ctx.shared_u8, ctx.shared_memory_u8, ctx.u32_zero_value, offset)};
@@ -123,6 +131,7 @@ void EmitWriteSharedU8(EmitContext& ctx, Id offset, Id value) {
}
void EmitWriteSharedU16(EmitContext& ctx, Id offset, Id value) {
offset = ctx.BoundSharedOffset(offset, 2);
if (ctx.uses_explicit_workgroup_layout) {
const Id pointer{Pointer(ctx, ctx.shared_u16, ctx.shared_memory_u16, offset, 1)};
ctx.OpStore(pointer, ctx.OpUConvert(ctx.U16, value));
@@ -132,6 +141,7 @@ void EmitWriteSharedU16(EmitContext& ctx, Id offset, Id value) {
}
void EmitWriteSharedU32(EmitContext& ctx, Id offset, Id value) {
offset = ctx.BoundSharedOffset(offset, 4);
Id pointer{};
if (ctx.uses_explicit_workgroup_layout) {
pointer = Pointer(ctx, ctx.shared_u32, ctx.shared_memory_u32, offset, 2);
@@ -144,6 +154,7 @@ void EmitWriteSharedU32(EmitContext& ctx, Id offset, Id value) {
}
void EmitWriteSharedU64(EmitContext& ctx, Id offset, Id value) {
offset = ctx.BoundSharedOffset(offset, 8);
if (ctx.uses_explicit_workgroup_layout) {
const Id pointer{Pointer(ctx, ctx.shared_u32x2, ctx.shared_memory_u32x2, offset, 3)};
ctx.OpStore(pointer, value);
@@ -159,6 +170,7 @@ void EmitWriteSharedU64(EmitContext& ctx, Id offset, Id value) {
}
void EmitWriteSharedU128(EmitContext& ctx, Id offset, Id value) {
offset = ctx.BoundSharedOffset(offset, 16);
if (ctx.uses_explicit_workgroup_layout) {
const Id pointer{Pointer(ctx, ctx.shared_u32x4, ctx.shared_memory_u32x4, offset, 4)};
ctx.OpStore(pointer, value);
@@ -600,6 +600,16 @@ void EmitContext::DefineLocalMemory(const IR::Program& program) {
}
}
Id EmitContext::BoundSharedOffset(Id offset, u32 access_bytes) {
if (shared_memory_declared_bytes == 0) {
return offset;
}
const u32 last_valid{shared_memory_declared_bytes > access_bytes
? shared_memory_declared_bytes - access_bytes
: 0U};
return OpUMin(U32[1], offset, Const(last_valid));
}
void EmitContext::DefineSharedMemory(const IR::Program& program) {
uses_explicit_workgroup_layout =
profile.support_explicit_workgroup_layout &&
@@ -608,8 +618,15 @@ void EmitContext::DefineSharedMemory(const IR::Program& program) {
if (program.shared_memory_size == 0) {
return;
}
const u32 device_limit{profile.max_shared_memory_size};
const u32 shared_memory_size{device_limit != 0 && program.shared_memory_size > device_limit
? device_limit
: program.shared_memory_size};
if (shared_memory_size != program.shared_memory_size) {
shared_memory_declared_bytes = shared_memory_size;
}
const auto make{[&](Id element_type, u32 element_size) {
const u32 num_elements{Common::DivCeil(program.shared_memory_size, element_size)};
const u32 num_elements{Common::DivCeil(shared_memory_size, element_size)};
const Id array_type{TypeArray(element_type, Const(num_elements))};
Decorate(array_type, spv::Decoration::ArrayStride, element_size);
@@ -644,7 +661,7 @@ void EmitContext::DefineSharedMemory(const IR::Program& program) {
std::tie(shared_memory_u32x4, shared_u32x4, std::ignore) = make(U32[4], 16);
return;
}
const u32 num_elements{Common::DivCeil(program.shared_memory_size, 4U)};
const u32 num_elements{Common::DivCeil(shared_memory_size, 4U)};
const Id type{TypeArray(U32[1], Const(num_elements))};
shared_memory_u32_type = TypePointer(spv::StorageClass::Workgroup, type);
@@ -312,6 +312,8 @@ public:
Id local_memory{};
bool uses_explicit_workgroup_layout{};
u32 shared_memory_declared_bytes{};
[[nodiscard]] Id BoundSharedOffset(Id offset, u32 access_bytes);
Id shared_memory_u8{};
Id shared_memory_u16{};
Id shared_memory_u32{};
+3
View File
@@ -101,6 +101,9 @@ struct Profile {
u32 gl_max_compute_smem_size{};
/// Largest workgroup shared memory allocation the device accepts, 0 when unconstrained
u32 max_shared_memory_size{};
/// Maxwell and earlier nVidia architectures have broken robust support
bool has_broken_robust{};
+1 -26
View File
@@ -59,10 +59,6 @@ add_library(video_core STATIC
engines/maxwell_dma.h
engines/puller.cpp
engines/puller.h
frame_gen/lossless_dll.cpp
frame_gen/lossless_dll.h
frame_gen/lsfg_translate.cpp
frame_gen/lsfg_translate.h
framebuffer_config.cpp
framebuffer_config.h
fsr.cpp
@@ -125,26 +121,6 @@ add_library(video_core STATIC
renderer_vulkan/present/anti_alias_pass.h
renderer_vulkan/present/filters.cpp
renderer_vulkan/present/filters.h
renderer_vulkan/present/frame_gen.cpp
renderer_vulkan/present/frame_gen.h
renderer_vulkan/present/lsfg_alpha.cpp
renderer_vulkan/present/lsfg_alpha.h
renderer_vulkan/present/lsfg_beta.cpp
renderer_vulkan/present/lsfg_beta.h
renderer_vulkan/present/lsfg_chain.cpp
renderer_vulkan/present/lsfg_chain.h
renderer_vulkan/present/lsfg_common.cpp
renderer_vulkan/present/lsfg_common.h
renderer_vulkan/present/lsfg_delta.cpp
renderer_vulkan/present/lsfg_delta.h
renderer_vulkan/present/lsfg_gamma.cpp
renderer_vulkan/present/lsfg_gamma.h
renderer_vulkan/present/lsfg_generate.cpp
renderer_vulkan/present/lsfg_generate.h
renderer_vulkan/present/lsfg_mipmaps.cpp
renderer_vulkan/present/lsfg_mipmaps.h
renderer_vulkan/present/lsfg_shaders.cpp
renderer_vulkan/present/lsfg_shaders.h
renderer_vulkan/present/fsr.cpp
renderer_vulkan/present/fsr.h
renderer_vulkan/present/fxaa.cpp
@@ -369,7 +345,6 @@ add_dependencies(video_core host_shaders)
target_include_directories(video_core PRIVATE ${HOST_SHADERS_INCLUDE})
target_link_libraries(video_core PRIVATE sirit::sirit)
target_link_libraries(video_core PRIVATE dxbc)
# Header-only stuff needed by all dependent targets
target_link_libraries(video_core PUBLIC Vulkan::Headers Vulkan::UtilityHeaders GPUOpen::VulkanMemoryAllocator)
@@ -398,7 +373,7 @@ else()
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-shadow>
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-unused-local-typedef>)
else()
target_compile_options(video_core PRIVATE $<$<COMPILE_LANGUAGE:C,CXX>:-Wconversion>)
target_compile_options(video_core PRIVATE $<$<COMPILE_LANGUAGE:C,CXX>:-Werror=conversion>)
endif()
target_compile_options(video_core PRIVATE $<$<COMPILE_LANGUAGE:C,CXX>:-Wno-sign-conversion>)
+214 -36
View File
@@ -571,7 +571,11 @@ void BufferCache<P>::AccumulateFlushes() {
template <class P>
bool BufferCache<P>::ShouldWaitAsyncFlushes() const noexcept {
return (!async_buffers.empty() && async_buffers.front().has_value());
if (async_buffers.empty()) {
return false;
}
return async_buffers.front().has_value() ||
!pending_downloads.front().unified_copies.empty();
}
template <class P>
@@ -579,6 +583,7 @@ void BufferCache<P>::CommitAsyncFlushesHigh() {
AccumulateFlushes();
if (committed_gpu_modified_ranges.empty()) {
pending_downloads.emplace_back();
async_buffers.emplace_back(std::optional<Async_Buffer>{});
return;
}
@@ -638,27 +643,84 @@ void BufferCache<P>::CommitAsyncFlushesHigh() {
}
committed_gpu_modified_ranges.clear();
if (downloads.empty()) {
pending_downloads.emplace_back();
async_buffers.emplace_back(std::optional<Async_Buffer>{});
return;
}
auto download_staging = runtime.DownloadStagingBuffer(total_size_bytes, true);
boost::container::small_vector<BufferCopy, 4> normalized_copies;
runtime.PreCopyBarrier();
struct QueuedUnifiedCopy {
u64 window;
BufferId buffer_id;
boost::container::small_vector<BufferCopy, 16> copies;
};
AsyncDownloadBatch batch;
boost::container::small_vector<std::pair<BufferCopy, BufferId>, 16> staging_downloads;
boost::container::small_vector<QueuedUnifiedCopy, 4> unified_copy_queue;
boost::container::small_vector<u64, 4> window_ids;
UnifiedWindowGroups groups;
u64 staging_size_bytes = 0;
for (auto& [copy, buffer_id] : downloads) {
copy.dst_offset += download_staging.offset;
const std::array copies{copy};
BufferCopy second_copy{copy};
Buffer& buffer = slot_buffers[buffer_id];
second_copy.src_offset = static_cast<size_t>(buffer.CpuAddr()) + copy.src_offset;
const DAddr orig_device_addr = static_cast<DAddr>(second_copy.src_offset);
const DAddr orig_device_addr = buffer.CpuAddr() + copy.src_offset;
bool unified = false;
if constexpr (USE_UNIFIED_MEMORY) {
if (runtime.HasUnifiedMemory()) {
window_ids.clear();
groups.clear();
unified = ResolveUnifiedWindows(orig_device_addr, copy.src_offset, copy.size,
window_ids, groups);
}
}
BufferCopy record{copy};
record.src_offset = static_cast<size_t>(orig_device_addr);
if (unified) {
async_downloads.Add(orig_device_addr, copy.size);
buffer.MarkUsage(copy.src_offset, copy.size);
for (size_t i = 0; i < window_ids.size(); ++i) {
unified_copy_queue.push_back(
QueuedUnifiedCopy{window_ids[i], buffer_id, std::move(groups[i])});
}
batch.unified_copies.push_back(record);
continue;
}
copy.dst_offset = staging_size_bytes;
constexpr u64 align = 64ULL;
staging_size_bytes += (copy.size + align - 1) & ~(align - 1ULL);
staging_downloads.push_back({copy, buffer_id});
}
std::optional<Async_Buffer> download_staging;
if (!staging_downloads.empty()) {
download_staging = runtime.DownloadStagingBuffer(staging_size_bytes, true);
}
runtime.PreCopyBarrier();
for (auto& [copy, buffer_id] : staging_downloads) {
copy.dst_offset += download_staging->offset;
const std::array copies{copy};
Buffer& buffer = slot_buffers[buffer_id];
BufferCopy record{copy};
record.src_offset = static_cast<size_t>(buffer.CpuAddr()) + copy.src_offset;
const DAddr orig_device_addr = static_cast<DAddr>(record.src_offset);
async_downloads.Add(orig_device_addr, copy.size);
buffer.MarkUsage(copy.src_offset, copy.size);
runtime.CopyBuffer(download_staging.buffer, buffer, copies, false);
normalized_copies.push_back(second_copy);
runtime.CopyBuffer(download_staging->buffer, buffer, copies, false);
batch.staging_copies.push_back(record);
}
if constexpr (USE_UNIFIED_MEMORY) {
for (const auto& queued : unified_copy_queue) {
const std::span<const BufferCopy> group_span(queued.copies.data(),
queued.copies.size());
runtime.CopyToUnifiedMemory(queued.window, slot_buffers[queued.buffer_id], group_span);
}
if (!unified_copy_queue.empty()) {
runtime.FlushUnifiedMemoryCopies();
runtime.UnifiedMemoryHostBarrier();
}
}
runtime.PostCopyBarrier();
pending_downloads.emplace_back(std::move(normalized_copies));
async_buffers.emplace_back(download_staging);
pending_downloads.emplace_back(std::move(batch));
async_buffers.emplace_back(std::move(download_staging));
}
template <class P>
@@ -673,32 +735,49 @@ void BufferCache<P>::PopAsyncFlushes() {
template <class P>
void BufferCache<P>::PopAsyncBuffers() {
if (async_buffers.empty()) {
return;
}
if (!async_buffers.front().has_value()) {
struct Writeback {
DAddr addr;
const u8* src;
u64 size;
};
boost::container::small_vector<Writeback, 8> writebacks;
{
std::scoped_lock lock{mutex};
if (async_buffers.empty()) {
return;
}
auto& batch = pending_downloads.front();
auto& async_buffer = async_buffers.front();
if (async_buffer.has_value()) {
const u8* base = async_buffer->mapped_span.data();
const size_t base_offset = async_buffer->offset;
for (const auto& copy : batch.staging_copies) {
const DAddr device_addr = static_cast<DAddr>(copy.src_offset);
const u64 dst_offset = copy.dst_offset - base_offset;
const u8* read_mapped_memory = base + dst_offset;
async_downloads.ForEachInRange(
device_addr, copy.size, [&](DAddr start, DAddr end, s32) {
writebacks.push_back(
{start, &read_mapped_memory[start - device_addr], end - start});
});
async_downloads.Subtract(device_addr, copy.size, [&](DAddr start, DAddr end) {
gpu_modified_ranges.Subtract(start, end - start);
});
}
async_buffers_death_ring.emplace_back(*async_buffer);
}
for (const auto& copy : batch.unified_copies) {
const DAddr device_addr = static_cast<DAddr>(copy.src_offset);
async_downloads.Subtract(device_addr, copy.size, [&](DAddr start, DAddr end) {
gpu_modified_ranges.Subtract(start, end - start);
});
}
async_buffers.pop_front();
return;
pending_downloads.pop_front();
}
auto& downloads = pending_downloads.front();
auto& async_buffer = async_buffers.front();
u8* base = async_buffer->mapped_span.data();
const size_t base_offset = async_buffer->offset;
for (const auto& copy : downloads) {
const DAddr device_addr = static_cast<DAddr>(copy.src_offset);
const u64 dst_offset = copy.dst_offset - base_offset;
const u8* read_mapped_memory = base + dst_offset;
async_downloads.ForEachInRange(device_addr, copy.size, [&](DAddr start, DAddr end, s32) {
device_memory.WriteBlockUnsafe(start, &read_mapped_memory[start - device_addr],
end - start);
});
async_downloads.Subtract(device_addr, copy.size, [&](DAddr start, DAddr end) {
gpu_modified_ranges.Subtract(start, end - start);
});
for (const auto& wb : writebacks) {
device_memory.WriteBlockUnsafe(wb.addr, wb.src, wb.size);
}
async_buffers_death_ring.emplace_back(*async_buffer);
async_buffers.pop_front();
pending_downloads.pop_front();
}
template <class P>
@@ -1699,6 +1778,99 @@ void BufferCache<P>::ImmediateUploadMemory([[maybe_unused]] Buffer& buffer,
}
}
template <class P>
bool BufferCache<P>::ResolveUnifiedWindows(
[[maybe_unused]] DAddr device_addr, [[maybe_unused]] u64 buffer_offset,
[[maybe_unused]] u64 size, [[maybe_unused]] boost::container::small_vector<u64, 4>& window_ids,
[[maybe_unused]] UnifiedWindowGroups& groups) {
if constexpr (USE_UNIFIED_MEMORY) {
const u8* const physical_base = device_memory.GetPhysicalBase();
const u64 unified_base = runtime.UnifiedMemoryBase();
const u64 unified_size = runtime.UnifiedMemorySize();
const u64 window_size = runtime.UnifiedMemoryWindowSize();
if (window_size == 0) {
return false;
}
const auto group_for = [&](u64 window) -> boost::container::small_vector<BufferCopy, 16>& {
for (size_t i = 0; i < window_ids.size(); ++i) {
if (window_ids[i] == window) {
return groups[i];
}
}
window_ids.push_back(window);
groups.emplace_back();
return groups.back();
};
u64 downloaded = 0;
while (downloaded < size) {
const DAddr page_addr = device_addr + downloaded;
const u8* const ptr = device_memory.GetPointer<u8>(page_addr);
if (ptr == nullptr) {
return false;
}
const u64 page_offset = page_addr & Core::DEVICE_PAGEMASK;
u64 chunk = (std::min)(size - downloaded,
static_cast<u64>(Core::DEVICE_PAGESIZE) - page_offset);
const u64 phys_offset = static_cast<u64>(ptr - physical_base);
if (phys_offset < unified_base || phys_offset - unified_base + chunk > unified_size) {
return false;
}
const u64 relative = phys_offset - unified_base;
const u64 window = relative / window_size;
const u64 local_offset = relative % window_size;
chunk = (std::min)(chunk, window_size - local_offset);
auto& group = group_for(window);
if (!group.empty()) {
BufferCopy& last = group.back();
if (last.src_offset + last.size == buffer_offset + downloaded &&
last.dst_offset + last.size == local_offset) {
last.size += chunk;
downloaded += chunk;
continue;
}
}
group.push_back(BufferCopy{
.src_offset = buffer_offset + downloaded,
.dst_offset = local_offset,
.size = chunk,
});
downloaded += chunk;
}
return true;
} else {
return false;
}
}
template <class P>
bool BufferCache<P>::TryUnifiedDownloadMemory([[maybe_unused]] Buffer& buffer,
[[maybe_unused]] std::span<BufferCopy> copies) {
if constexpr (USE_UNIFIED_MEMORY) {
boost::container::small_vector<u64, 4> window_ids;
UnifiedWindowGroups groups;
for (const BufferCopy& copy : copies) {
if (!ResolveUnifiedWindows(buffer.CpuAddr() + copy.src_offset, copy.src_offset,
copy.size, window_ids, groups)) {
return false;
}
}
for (const BufferCopy& copy : copies) {
buffer.MarkUsage(copy.src_offset, copy.size);
}
runtime.PreCopyBarrier();
for (size_t i = 0; i < window_ids.size(); ++i) {
const std::span<const BufferCopy> group_span(groups[i].data(), groups[i].size());
runtime.CopyToUnifiedMemory(window_ids[i], buffer, group_span);
}
runtime.FlushUnifiedMemoryCopies();
runtime.UnifiedMemoryHostBarrier();
runtime.Finish();
return true;
} else {
return false;
}
}
template <class P>
void BufferCache<P>::MappedUploadMemory([[maybe_unused]] Buffer& buffer,
[[maybe_unused]] u64 total_size_bytes,
@@ -1802,6 +1974,12 @@ void BufferCache<P>::DownloadBufferMemory(Buffer& buffer, DAddr device_addr, u64
}
if constexpr (USE_MEMORY_MAPS) {
if constexpr (USE_UNIFIED_MEMORY) {
if (runtime.HasUnifiedMemory() &&
TryUnifiedDownloadMemory(buffer, std::span(copies.data(), copies.size()))) {
return;
}
}
auto download_staging = runtime.DownloadStagingBuffer(total_size_bytes);
const u8* const mapped_memory = download_staging.mapped_span.data();
const std::span<BufferCopy> copies_span(copies.data(), copies.data() + copies.size());
@@ -180,6 +180,7 @@ class BufferCache : public VideoCommon::ChannelSetupCaches<BufferCacheChannelInf
static constexpr bool USE_MEMORY_MAPS = P::USE_MEMORY_MAPS;
static constexpr bool SEPARATE_IMAGE_BUFFERS_BINDINGS = P::SEPARATE_IMAGE_BUFFER_BINDINGS;
static constexpr bool USE_MEMORY_MAPS_FOR_UPLOADS = P::USE_MEMORY_MAPS_FOR_UPLOADS;
static constexpr bool USE_UNIFIED_MEMORY = P::USE_UNIFIED_MEMORY;
#ifdef YUZU_LEGACY
static constexpr s64 TARGET_THRESHOLD = 3_GiB;
@@ -443,6 +444,15 @@ private:
void MappedUploadMemory(Buffer& buffer, u64 total_size_bytes, std::span<BufferCopy> copies);
bool TryUnifiedDownloadMemory(Buffer& buffer, std::span<BufferCopy> copies);
using UnifiedWindowGroups =
boost::container::small_vector<boost::container::small_vector<BufferCopy, 16>, 4>;
bool ResolveUnifiedWindows(DAddr device_addr, u64 buffer_offset, u64 size,
boost::container::small_vector<u64, 4>& window_ids,
UnifiedWindowGroups& groups);
void DownloadBufferMemory(Buffer& buffer_id);
void DownloadBufferMemory(Buffer& buffer_id, DAddr device_addr, u64 size);
@@ -498,9 +508,14 @@ private:
std::deque<Common::RangeSet<DAddr>> committed_gpu_modified_ranges;
// Async Buffers
struct AsyncDownloadBatch {
boost::container::small_vector<BufferCopy, 4> staging_copies;
boost::container::small_vector<BufferCopy, 4> unified_copies;
};
Common::OverlapRangeSet<DAddr> async_downloads;
std::deque<std::optional<Async_Buffer>> async_buffers;
std::deque<boost::container::small_vector<BufferCopy, 4>> pending_downloads;
std::deque<AsyncDownloadBatch> pending_downloads;
std::optional<Async_Buffer> current_buffer;
std::deque<Async_Buffer> async_buffers_death_ring;
+1 -1
View File
@@ -2257,7 +2257,7 @@ public:
/// Returns whether the vertex array specified by index is supposed to be
/// accessed per instance or not.
bool IsInstancingEnabled(std::size_t index) const {
return bool(is_instanced[index]);
return bool(is_instanced[index]); //FUCK YOU MSVC
}
};
+1
View File
@@ -40,6 +40,7 @@ public:
}
struct Regs {
// No fucking idea
INSERT_PADDING_BYTES_NOINIT(0x48);
} regs{};
private:
-575
View File
@@ -1,575 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include <algorithm>
#include <cstring>
#include <optional>
#include <span>
#include "common/cityhash.h"
#include "common/fs/file.h"
#include "common/fs/fs.h"
#include "common/fs/fs_paths.h"
#include "common/fs/path_util.h"
#include "video_core/frame_gen/lossless_dll.h"
#include "video_core/frame_gen/lsfg_translate.h"
namespace VideoCore::FrameGen {
namespace {
constexpr u16 DOS_MAGIC = 0x5A4D;
constexpr u32 PE_SIGNATURE = 0x00004550;
constexpr u16 PE32_MAGIC = 0x010B;
constexpr u16 PE32_PLUS_MAGIC = 0x020B;
constexpr size_t DOS_LFANEW_OFFSET = 0x3C;
constexpr size_t COFF_HEADER_SIZE = 20;
constexpr size_t OPTIONAL_HEADER_SIZE_OFFSET = 16;
constexpr size_t SECTION_HEADER_SIZE = 40;
constexpr size_t DATA_DIRECTORY_ENTRY_SIZE = 8;
constexpr size_t DATA_DIRECTORY_OFFSET_PE32 = 96;
constexpr size_t DATA_DIRECTORY_OFFSET_PE32_PLUS = 112;
constexpr size_t RESOURCE_DATA_DIRECTORY_INDEX = 2;
constexpr size_t RESOURCE_DIRECTORY_SIZE = 16;
constexpr size_t RESOURCE_NAMED_COUNT_OFFSET = 12;
constexpr size_t RESOURCE_ID_COUNT_OFFSET = 14;
constexpr size_t RESOURCE_ENTRY_SIZE = 8;
constexpr u32 RESOURCE_SUBDIRECTORY_FLAG = 0x80000000;
constexpr u32 RESOURCE_TYPE_RCDATA = 10;
constexpr u32 MIPMAPS_SHADER_ID = 255;
constexpr u32 GENERATE_SHADER_ID = 256;
constexpr u32 PERFORMANCE_SHADER_ID_FIRST = 280;
constexpr u32 PERFORMANCE_SHADER_ID_LAST = 302;
constexpr u32 CACHE_MAGIC = 0x4746534C;
constexpr u32 CACHE_VERSION = 2;
struct CacheHeader {
u32 magic;
u32 version;
u64 source_size;
u64 source_hash;
u32 module_count;
u32 variant;
};
struct Section {
u32 virtual_address;
u32 virtual_size;
u32 raw_address;
u32 raw_size;
};
struct ResourceEntry {
u32 id;
u32 offset;
bool is_directory;
bool is_named;
};
class ImageReader {
public:
explicit ImageReader(std::span<const u8> image_) : image{image_} {}
template <typename T>
[[nodiscard]] bool Read(size_t offset, T& out_value) const {
if (offset > image.size() || image.size() - offset < sizeof(T)) {
return false;
}
std::memcpy(&out_value, image.data() + offset, sizeof(T));
return true;
}
[[nodiscard]] bool Slice(size_t offset, size_t size, std::span<const u8>& out_slice) const {
if (offset > image.size() || image.size() - offset < size) {
return false;
}
out_slice = image.subspan(offset, size);
return true;
}
private:
std::span<const u8> image;
};
[[nodiscard]] std::optional<size_t> FindPeHeader(const ImageReader& reader) {
u16 dos_magic{};
if (!reader.Read(0, dos_magic) || dos_magic != DOS_MAGIC) {
return std::nullopt;
}
u32 pe_offset{};
if (!reader.Read(DOS_LFANEW_OFFSET, pe_offset)) {
return std::nullopt;
}
u32 pe_signature{};
if (!reader.Read(pe_offset, pe_signature) || pe_signature != PE_SIGNATURE) {
return std::nullopt;
}
return static_cast<size_t>(pe_offset);
}
[[nodiscard]] std::optional<size_t> FindDataDirectory(const ImageReader& reader,
size_t optional_header_offset) {
u16 optional_magic{};
if (!reader.Read(optional_header_offset, optional_magic)) {
return std::nullopt;
}
switch (optional_magic) {
case PE32_MAGIC:
return optional_header_offset + DATA_DIRECTORY_OFFSET_PE32;
case PE32_PLUS_MAGIC:
return optional_header_offset + DATA_DIRECTORY_OFFSET_PE32_PLUS;
default:
return std::nullopt;
}
}
[[nodiscard]] bool ReadSections(const ImageReader& reader, size_t pe_offset,
std::vector<Section>& out_sections) {
u16 section_count{};
u16 optional_header_size{};
if (!reader.Read(pe_offset + 4 + 2, section_count) ||
!reader.Read(pe_offset + 4 + OPTIONAL_HEADER_SIZE_OFFSET, optional_header_size)) {
return false;
}
const size_t table_offset = pe_offset + 4 + COFF_HEADER_SIZE + optional_header_size;
out_sections.reserve(section_count);
for (size_t i = 0; i < section_count; ++i) {
const size_t offset = table_offset + i * SECTION_HEADER_SIZE;
Section section{};
if (!reader.Read(offset + 8, section.virtual_size) ||
!reader.Read(offset + 12, section.virtual_address) ||
!reader.Read(offset + 16, section.raw_size) ||
!reader.Read(offset + 20, section.raw_address)) {
return false;
}
out_sections.push_back(section);
}
return true;
}
[[nodiscard]] std::optional<size_t> RvaToFileOffset(std::span<const Section> sections, u32 rva) {
for (const Section& section : sections) {
const u32 span = std::max(section.virtual_size, section.raw_size);
if (span == 0 || rva < section.virtual_address) {
continue;
}
const u32 relative = rva - section.virtual_address;
if (relative < span) {
return static_cast<size_t>(section.raw_address) + relative;
}
}
return std::nullopt;
}
[[nodiscard]] bool ReadResourceEntries(const ImageReader& reader, size_t directory_offset,
std::vector<ResourceEntry>& out_entries) {
u16 named_count{};
u16 id_count{};
if (!reader.Read(directory_offset + RESOURCE_NAMED_COUNT_OFFSET, named_count) ||
!reader.Read(directory_offset + RESOURCE_ID_COUNT_OFFSET, id_count)) {
return false;
}
const size_t total = size_t{named_count} + size_t{id_count};
out_entries.clear();
out_entries.reserve(total);
for (size_t i = 0; i < total; ++i) {
const size_t offset = directory_offset + RESOURCE_DIRECTORY_SIZE + i * RESOURCE_ENTRY_SIZE;
u32 name{};
u32 data{};
if (!reader.Read(offset, name) || !reader.Read(offset + 4, data)) {
return false;
}
out_entries.push_back(ResourceEntry{
.id = name & ~RESOURCE_SUBDIRECTORY_FLAG,
.offset = data & ~RESOURCE_SUBDIRECTORY_FLAG,
.is_directory = (data & RESOURCE_SUBDIRECTORY_FLAG) != 0,
.is_named = (name & RESOURCE_SUBDIRECTORY_FLAG) != 0,
});
}
return true;
}
[[nodiscard]] bool ReadResourceLeaf(const ImageReader& reader, std::span<const Section> sections,
size_t leaf_offset, std::span<const u8>& out_data) {
u32 data_rva{};
u32 data_size{};
if (!reader.Read(leaf_offset, data_rva) || !reader.Read(leaf_offset + 4, data_size) ||
data_size == 0) {
return false;
}
const std::optional<size_t> data_offset = RvaToFileOffset(sections, data_rva);
if (!data_offset) {
return false;
}
return reader.Slice(*data_offset, data_size, out_data);
}
using ResourceSpans = std::map<u32, std::span<const u8>>;
[[nodiscard]] bool CollectRcData(const ImageReader& reader, std::span<const Section> sections,
size_t resource_base, ResourceSpans& out_resources) {
std::vector<ResourceEntry> type_entries;
if (!ReadResourceEntries(reader, resource_base, type_entries)) {
return false;
}
for (const ResourceEntry& type_entry : type_entries) {
if (type_entry.is_named || type_entry.id != RESOURCE_TYPE_RCDATA ||
!type_entry.is_directory) {
continue;
}
std::vector<ResourceEntry> name_entries;
if (!ReadResourceEntries(reader, resource_base + type_entry.offset, name_entries)) {
return false;
}
for (const ResourceEntry& name_entry : name_entries) {
if (name_entry.is_named || !name_entry.is_directory) {
continue;
}
std::vector<ResourceEntry> language_entries;
if (!ReadResourceEntries(reader, resource_base + name_entry.offset, language_entries)) {
return false;
}
for (const ResourceEntry& language_entry : language_entries) {
if (language_entry.is_directory) {
continue;
}
std::span<const u8> data;
if (!ReadResourceLeaf(reader, sections, resource_base + language_entry.offset,
data)) {
continue;
}
out_resources.insert_or_assign(name_entry.id, data);
break;
}
}
}
return true;
}
[[nodiscard]] std::vector<u32> PerformanceShaderIds() {
std::vector<u32> ids{MIPMAPS_SHADER_ID, GENERATE_SHADER_ID};
for (u32 id = PERFORMANCE_SHADER_ID_FIRST; id <= PERFORMANCE_SHADER_ID_LAST; ++id) {
ids.push_back(id);
}
return ids;
}
template <typename Map>
[[nodiscard]] bool HasPerformanceShaders(const Map& resources) {
const std::vector<u32> ids = PerformanceShaderIds();
return std::ranges::all_of(ids, [&](u32 id) { return resources.contains(id); });
}
[[nodiscard]] u32 VariantOffset(ShaderVariant variant) {
switch (variant) {
case ShaderVariant::NativeFp16:
return PerformanceShader::NATIVE_FP16_OFFSET;
case ShaderVariant::NativeFp32:
return PerformanceShader::NATIVE_FP32_OFFSET;
default:
return 0;
}
}
template <typename Map>
[[nodiscard]] bool HasNativeVariant(const Map& resources, ShaderVariant variant) {
const u32 offset = VariantOffset(variant);
return std::ranges::all_of(PerformanceShaderIds(), [&](u32 id) {
const auto hit = resources.find(id + offset);
return hit != resources.end() && IsSpirvModule(hit->second);
});
}
[[nodiscard]] ShaderVariant SelectVariant(const ResourceSpans& resources, bool prefer_fp16) {
if (prefer_fp16 && HasNativeVariant(resources, ShaderVariant::NativeFp16)) {
return ShaderVariant::NativeFp16;
}
if (HasNativeVariant(resources, ShaderVariant::NativeFp32)) {
return ShaderVariant::NativeFp32;
}
return ShaderVariant::TranslatedDxbc;
}
[[nodiscard]] LosslessStatus TranslateAll(const ResourceSpans& resources,
ShaderModules& out_modules,
ShaderVariant variant) {
const u32 offset = VariantOffset(variant);
out_modules.clear();
for (const u32 id : PerformanceShaderIds()) {
const auto hit = resources.find(id + offset);
if (hit == resources.end()) {
return LosslessStatus::MissingShaders;
}
if (variant != ShaderVariant::TranslatedDxbc) {
std::vector<u32> adopted = AdoptSpirvModule(hit->second);
if (adopted.empty()) {
return LosslessStatus::TranslationFailed;
}
out_modules.emplace(id, std::move(adopted));
continue;
}
std::vector<u32> words = TranslateComputeShader(hit->second);
if (words.empty()) {
return LosslessStatus::TranslationFailed;
}
out_modules.emplace(id, std::move(words));
}
return LosslessStatus::Ok;
}
[[nodiscard]] bool WriteShaderCache(const std::filesystem::path& path, const CacheHeader& header,
const ShaderModules& modules) {
Common::FS::IOFile file{path, Common::FS::FileAccessMode::Write,
Common::FS::FileType::BinaryFile};
if (!file.IsOpen() || file.Write(header) != 1) {
return false;
}
for (const auto& [id, words] : modules) {
const u32 word_count = static_cast<u32>(words.size());
if (file.Write(id) != 1 || file.Write(word_count) != 1 ||
file.Write(words) != words.size()) {
return false;
}
}
return file.Flush();
}
[[nodiscard]] bool ReadShaderCache(const std::filesystem::path& path, u64 source_size,
u64 source_hash, u32 variant, ShaderModules& out_modules) {
if (!Common::FS::Exists(path)) {
return false;
}
Common::FS::IOFile file{path, Common::FS::FileAccessMode::Read,
Common::FS::FileType::BinaryFile};
CacheHeader header{};
if (!file.IsOpen() || file.Read(header) != 1) {
return false;
}
if (header.magic != CACHE_MAGIC || header.version != CACHE_VERSION ||
header.source_size != source_size || header.source_hash != source_hash ||
header.variant != variant) {
return false;
}
out_modules.clear();
for (u32 i = 0; i < header.module_count; ++i) {
u32 id{};
u32 word_count{};
if (file.Read(id) != 1 || file.Read(word_count) != 1 || word_count == 0) {
return false;
}
std::vector<u32> words(word_count);
if (file.Read(words) != words.size()) {
return false;
}
out_modules.emplace(id, std::move(words));
}
return HasPerformanceShaders(out_modules);
}
[[nodiscard]] LosslessStatus ReadImageFile(const std::filesystem::path& path,
std::vector<u8>& out_image) {
if (!Common::FS::Exists(path)) {
return LosslessStatus::NotInstalled;
}
Common::FS::IOFile file{path, Common::FS::FileAccessMode::Read,
Common::FS::FileType::BinaryFile};
if (!file.IsOpen()) {
return LosslessStatus::UnreadableFile;
}
out_image.resize(static_cast<size_t>(file.GetSize()));
if (out_image.empty() || file.Read(out_image) != out_image.size()) {
return LosslessStatus::UnreadableFile;
}
return LosslessStatus::Ok;
}
[[nodiscard]] LosslessStatus ParseShaderSpans(std::span<const u8> image,
ResourceSpans& out_resources) {
const ImageReader reader{image};
const std::optional<size_t> pe_offset = FindPeHeader(reader);
if (!pe_offset) {
return LosslessStatus::NotPortableExecutable;
}
const std::optional<size_t> data_directory =
FindDataDirectory(reader, *pe_offset + 4 + COFF_HEADER_SIZE);
if (!data_directory) {
return LosslessStatus::NotPortableExecutable;
}
std::vector<Section> sections;
if (!ReadSections(reader, *pe_offset, sections)) {
return LosslessStatus::NotPortableExecutable;
}
u32 resource_rva{};
if (!reader.Read(*data_directory + RESOURCE_DATA_DIRECTORY_INDEX * DATA_DIRECTORY_ENTRY_SIZE,
resource_rva) ||
resource_rva == 0) {
return LosslessStatus::MissingShaders;
}
const std::optional<size_t> resource_base = RvaToFileOffset(sections, resource_rva);
if (!resource_base) {
return LosslessStatus::NotPortableExecutable;
}
out_resources.clear();
if (!CollectRcData(reader, sections, *resource_base, out_resources)) {
return LosslessStatus::MissingShaders;
}
return HasPerformanceShaders(out_resources) ? LosslessStatus::Ok
: LosslessStatus::MissingShaders;
}
} // Anonymous namespace
std::filesystem::path GetLosslessDllPath() {
return Common::FS::GetEdenPath(Common::FS::EdenPath::LosslessDir) / LOSSLESS_DLL_FILE;
}
std::filesystem::path GetShaderCachePath() {
return Common::FS::GetEdenPath(Common::FS::EdenPath::LosslessDir) / LOSSLESS_CACHE_FILE;
}
LosslessStatus ReadShaderResources(const std::filesystem::path& path,
ShaderResources& out_resources) {
std::vector<u8> image;
const LosslessStatus read_status = ReadImageFile(path, image);
if (read_status != LosslessStatus::Ok) {
return read_status;
}
ResourceSpans spans;
const LosslessStatus parse_status = ParseShaderSpans(image, spans);
if (parse_status != LosslessStatus::Ok) {
return parse_status;
}
out_resources.clear();
for (const auto& [id, data] : spans) {
out_resources.emplace(id, std::vector<u8>{data.begin(), data.end()});
}
return LosslessStatus::Ok;
}
LosslessStatus ValidateLosslessDll(const std::filesystem::path& path) {
std::vector<u8> image;
const LosslessStatus read_status = ReadImageFile(path, image);
if (read_status != LosslessStatus::Ok) {
return read_status;
}
ResourceSpans spans;
return ParseShaderSpans(image, spans);
}
LosslessStatus GetInstalledLosslessStatus() {
return ValidateLosslessDll(GetLosslessDllPath());
}
ShaderVariant GetAvailableVariant(bool prefer_fp16) {
std::vector<u8> image;
if (ReadImageFile(GetLosslessDllPath(), image) != LosslessStatus::Ok) {
return ShaderVariant::TranslatedDxbc;
}
ResourceSpans spans;
if (ParseShaderSpans(image, spans) != LosslessStatus::Ok) {
return ShaderVariant::TranslatedDxbc;
}
return SelectVariant(spans, prefer_fp16);
}
LosslessStatus LoadShaderModules(ShaderModules& out_modules, bool prefer_fp16) {
std::vector<u8> image;
const LosslessStatus read_status = ReadImageFile(GetLosslessDllPath(), image);
if (read_status != LosslessStatus::Ok) {
return read_status;
}
const u64 source_size = image.size();
const u64 source_hash =
Common::CityHash64(reinterpret_cast<const char*>(image.data()), image.size());
const std::filesystem::path cache_path = GetShaderCachePath();
ResourceSpans spans;
const LosslessStatus parse_status = ParseShaderSpans(image, spans);
if (parse_status != LosslessStatus::Ok) {
return parse_status;
}
const ShaderVariant variant = SelectVariant(spans, prefer_fp16);
if (ReadShaderCache(cache_path, source_size, source_hash, static_cast<u32>(variant),
out_modules)) {
return LosslessStatus::Ok;
}
const LosslessStatus translate_status = TranslateAll(spans, out_modules, variant);
if (translate_status != LosslessStatus::Ok) {
return translate_status;
}
const CacheHeader header{
.magic = CACHE_MAGIC,
.version = CACHE_VERSION,
.source_size = source_size,
.source_hash = source_hash,
.module_count = static_cast<u32>(out_modules.size()),
.variant = static_cast<u32>(variant),
};
if (!WriteShaderCache(cache_path, header, out_modules)) {
void(Common::FS::RemoveFile(cache_path));
return LosslessStatus::CacheUnusable;
}
return LosslessStatus::Ok;
}
LosslessStatus BuildShaderCache() {
ShaderModules modules;
return LoadShaderModules(modules);
}
bool RemoveInstalledLosslessDll() {
const std::filesystem::path cache_path = GetShaderCachePath();
if (Common::FS::Exists(cache_path)) {
void(Common::FS::RemoveFile(cache_path));
}
const std::filesystem::path path = GetLosslessDllPath();
if (!Common::FS::Exists(path)) {
return true;
}
return Common::FS::RemoveFile(path);
}
} // namespace VideoCore::FrameGen
-69
View File
@@ -1,69 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <array>
#include <filesystem>
#include <map>
#include <vector>
#include "common/common_types.h"
namespace VideoCore::FrameGen {
enum class LosslessStatus : u32 {
Ok,
NotInstalled,
UnreadableFile,
NotPortableExecutable,
MissingShaders,
TranslationFailed,
CacheUnusable,
};
using ShaderResources = std::map<u32, std::vector<u8>>;
using ShaderModules = std::map<u32, std::vector<u32>>;
enum class ShaderVariant : u32 {
TranslatedDxbc,
NativeFp32,
NativeFp16,
};
namespace PerformanceShader {
constexpr u32 MIPMAPS = 255;
constexpr u32 GENERATE = 256;
constexpr std::array<u32, 4> ALPHA{290, 291, 292, 293};
constexpr std::array<u32, 5> BETA{298, 299, 300, 301, 302};
constexpr std::array<u32, 5> GAMMA{280, 282, 283, 284, 285};
constexpr std::array<u32, 10> DELTA{280, 286, 287, 288, 289, 281, 294, 295, 296, 297};
constexpr u32 NATIVE_FP16_OFFSET = 49;
constexpr u32 NATIVE_FP32_OFFSET = 98;
} // namespace PerformanceShader
[[nodiscard]] std::filesystem::path GetLosslessDllPath();
[[nodiscard]] std::filesystem::path GetShaderCachePath();
[[nodiscard]] LosslessStatus ReadShaderResources(const std::filesystem::path& path,
ShaderResources& out_resources);
[[nodiscard]] LosslessStatus ValidateLosslessDll(const std::filesystem::path& path);
[[nodiscard]] LosslessStatus GetInstalledLosslessStatus();
[[nodiscard]] LosslessStatus BuildShaderCache();
[[nodiscard]] ShaderVariant GetAvailableVariant(bool prefer_fp16);
[[nodiscard]] LosslessStatus LoadShaderModules(ShaderModules& out_modules,
bool prefer_fp16 = false);
bool RemoveInstalledLosslessDll();
} // namespace VideoCore::FrameGen
-129
View File
@@ -1,129 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include <algorithm>
#include <cstring>
#include <map>
#include <tuple>
#include <dxbc_modinfo.h>
#include <dxbc_module.h>
#include <dxbc_reader.h>
#include <thirdparty/spirv.hpp>
#include "video_core/frame_gen/lsfg_translate.h"
namespace VideoCore::FrameGen {
namespace {
constexpr u32 DECORATION_LITERAL_WORD = 3;
constexpr size_t SPIRV_HEADER_WORDS = 5;
void RenumberBindings(dxvk::SpirvCodeBuffer& code) {
std::vector<u32> literal_offsets;
for (const auto instruction : code) {
if (instruction.opCode() == spv::OpFunction) {
break;
}
if (instruction.opCode() == spv::OpDecorate &&
instruction.arg(2) == spv::DecorationBinding) {
literal_offsets.push_back(instruction.offset() + DECORATION_LITERAL_WORD);
}
}
for (size_t i = 0; i < literal_offsets.size(); ++i) {
code.data()[literal_offsets[i]] = static_cast<u32>(i);
}
}
void RenumberBindingsInOrder(std::vector<u32>& words) {
struct Slot {
u32 set;
u32 binding;
size_t literal_offset;
};
std::map<u32, u32> sets;
std::vector<Slot> slots;
size_t offset = SPIRV_HEADER_WORDS;
while (offset + 1 <= words.size()) {
const u32 length = words[offset] >> spv::WordCountShift;
const u32 opcode = words[offset] & spv::OpCodeMask;
if (length == 0 || offset + length > words.size()) {
return;
}
if (opcode == spv::OpFunction) {
break;
}
if (opcode == spv::OpDecorate && length >= 4) {
if (words[offset + 2] == spv::DecorationDescriptorSet) {
sets[words[offset + 1]] = words[offset + 3];
} else if (words[offset + 2] == spv::DecorationBinding) {
slots.push_back(Slot{0, words[offset + 3], offset + DECORATION_LITERAL_WORD});
}
}
offset += length;
}
for (Slot& slot : slots) {
const auto hit = sets.find(words[slot.literal_offset - 2]);
slot.set = hit == sets.end() ? 0 : hit->second;
}
std::ranges::stable_sort(slots, [](const Slot& lhs, const Slot& rhs) {
return std::tie(lhs.set, lhs.binding) < std::tie(rhs.set, rhs.binding);
});
for (size_t i = 0; i < slots.size(); ++i) {
words[slots[i].literal_offset] = static_cast<u32>(i);
}
}
} // Anonymous namespace
bool IsSpirvModule(std::span<const u8> blob) {
if (blob.size() < SPIRV_HEADER_WORDS * sizeof(u32) || blob.size() % sizeof(u32) != 0) {
return false;
}
u32 magic{};
std::memcpy(&magic, blob.data(), sizeof(magic));
return magic == spv::MagicNumber;
}
std::vector<u32> AdoptSpirvModule(std::span<const u8> blob) {
if (!IsSpirvModule(blob)) {
return {};
}
std::vector<u32> words(blob.size() / sizeof(u32));
std::memcpy(words.data(), blob.data(), blob.size());
RenumberBindingsInOrder(words);
return words;
}
std::vector<u32> TranslateComputeShader(std::span<const u8> dxbc) {
if (dxbc.empty()) {
return {};
}
try {
dxvk::DxbcReader reader{reinterpret_cast<const char*>(dxbc.data()), dxbc.size()};
dxvk::DxbcModule module{reader};
const dxvk::DxbcModuleInfo module_info{};
dxvk::SpirvCodeBuffer code = module.compile(module_info, "CS");
if (code.dwords() == 0) {
return {};
}
RenumberBindings(code);
return std::vector<u32>{code.data(), code.data() + code.dwords()};
} catch (...) {
return {};
}
}
} // namespace VideoCore::FrameGen
-19
View File
@@ -1,19 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <span>
#include <vector>
#include "common/common_types.h"
namespace VideoCore::FrameGen {
[[nodiscard]] bool IsSpirvModule(std::span<const u8> blob);
[[nodiscard]] std::vector<u32> AdoptSpirvModule(std::span<const u8> blob);
[[nodiscard]] std::vector<u32> TranslateComputeShader(std::span<const u8> dxbc);
} // namespace VideoCore::FrameGen
+1 -1
View File
@@ -963,7 +963,7 @@ void Vic::WriteABGR(const OutputSurfaceConfig& output_surface_config, VideoPixel
auto pixel1213 = _mm_load_si128((__m128i*)&inp[src + x + 12]);
auto pixel1415 = _mm_load_si128((__m128i*)&inp[src + x + 14]);
// Right-shift the channels by 16 to un-do the left shift on read and bring the range
// Right-shift the channels by 16 to un-do the left shit on read and bring the range
// back to 8-bit.
pixel01 = _mm_srli_epi16(pixel01, 2);
pixel23 = _mm_srli_epi16(pixel23, 2);
+1 -1
View File
@@ -88,7 +88,7 @@ set(SHADER_FILES
${CMAKE_CURRENT_SOURCE_DIR}/sgsr1_shader_mobile_edge_direction.frag
)
if (HAIKUOS)
if (PLATFORM_HAIKU)
# glslangValidator WILL crash, glslang will not
set(GLSLANGVALIDATOR "glslang")
else()
+1 -1
View File
@@ -441,7 +441,7 @@ void MacroInterpreterImpl::Reset() {
pc = 0;
delayed_pc = {};
method_address.raw = 0;
// Vector must hold its last indices otherwise chaos will ensue
// Vector must hold its last indices otherwise wonky shit will happen
// The next parameter index starts at 1, because $r1 already has the value of the first
// parameter.
next_parameter_index = 1;
@@ -261,6 +261,7 @@ struct BufferCacheParams {
// TODO: Investigate why OpenGL seems to perform worse with persistently mapped buffer uploads
static constexpr bool USE_MEMORY_MAPS_FOR_UPLOADS = false;
static constexpr bool USE_UNIFIED_MEMORY = false;
};
using BufferCache = VideoCommon::BufferCache<BufferCacheParams>;
+1 -1
View File
@@ -59,7 +59,7 @@ std::vector<std::string> GetExtensions() {
std::vector<std::string> extensions;
for (GLint index = 0; index < num_extensions; ++index) {
auto const* p = reinterpret_cast<const char*>(glGetStringi(GL_EXTENSIONS, GLuint(index)));
if (p != nullptr)
if (p != nullptr) // Fuck you? - sincerely, buggy mesa drivers
extensions.push_back(std::string{p});
}
return extensions;
@@ -237,6 +237,7 @@ ShaderCache::ShaderCache(Tegra::MaxwellDeviceMemoryManager& device_memory_,
.has_gl_bool_ref_bug = device.HasBoolRefBug(),
.ignore_nan_fp_comparisons = true,
.gl_max_compute_smem_size = device.GetMaxComputeSharedMemorySize(),
.max_shared_memory_size = device.GetMaxComputeSharedMemorySize(),
.min_ssbo_alignment = device.GetShaderStorageBufferAlignment(),
// Use the host limit, but never more than the guest can produce. Maxwell exposes 8 clip
// distances and the SPIR-V output array is sized for at most 8, so clamping here keeps a
@@ -1,332 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include <algorithm>
#include <string>
#include <vector>
#include "common/fs/file.h"
#include "common/fs/fs.h"
#include "common/fs/path_util.h"
#include "common/settings.h"
#include "video_core/renderer_vulkan/present/frame_gen.h"
#include "video_core/renderer_vulkan/present/util.h"
#include "video_core/renderer_vulkan/vk_present_manager.h"
#include "video_core/renderer_vulkan/vk_scheduler.h"
#include "video_core/vulkan_common/vulkan_device.h"
namespace Vulkan {
namespace {
constexpr size_t COLOR_CHANNELS = 4;
constexpr u64 LSFG_REQUIRED_FRAMES = 2;
[[nodiscard]] f32 ConfiguredFlowScale() {
return static_cast<f32>(Settings::values.frame_gen_flow_scale.GetValue()) / 100.0f;
}
[[nodiscard]] size_t ConfiguredGenerations() {
const u32 multiplier = std::clamp<u32>(Settings::values.frame_gen_multiplier.GetValue(),
LSFG_MIN_MULTIPLIER, LSFG_MAX_MULTIPLIER);
return multiplier - 1;
}
bool IsBlueFirst(VkFormat format) {
return format == VK_FORMAT_B8G8R8A8_UNORM || format == VK_FORMAT_B8G8R8A8_SRGB;
}
VkDeviceSize BytesPerTexel(VkFormat format) {
switch (format) {
case VK_FORMAT_R8_UNORM:
return 1;
case VK_FORMAT_R16G16B16A16_SFLOAT:
return 8;
default:
return COLOR_CHANNELS;
}
}
void WritePortablePixmap(const std::filesystem::path& path, const std::string& magic,
VkExtent2D extent, std::span<const u8> pixels) {
Common::FS::IOFile file{path, Common::FS::FileAccessMode::Write,
Common::FS::FileType::BinaryFile};
if (!file.IsOpen()) {
return;
}
const std::string header = magic + "\n" + std::to_string(extent.width) + " " +
std::to_string(extent.height) + "\n255\n";
if (file.Write(header) != header.size()) {
return;
}
void(file.Write(pixels));
void(file.Flush());
}
void WriteGrayscalePgm(const std::filesystem::path& path, VkExtent2D extent,
std::span<const u8> pixels) {
const size_t expected = static_cast<size_t>(extent.width) * extent.height;
WritePortablePixmap(path, "P5", extent, pixels.subspan(0, std::min(expected, pixels.size())));
}
void WriteRaw(const std::filesystem::path& path, std::span<const u8> pixels) {
Common::FS::IOFile file{path, Common::FS::FileAccessMode::Write,
Common::FS::FileType::BinaryFile};
if (!file.IsOpen()) {
return;
}
void(file.Write(pixels));
void(file.Flush());
}
void WriteColorPpm(const std::filesystem::path& path, VkExtent2D extent,
std::span<const u8> pixels, bool blue_first) {
const size_t pixel_count = static_cast<size_t>(extent.width) * extent.height;
if (pixels.size() < pixel_count * COLOR_CHANNELS) {
return;
}
std::vector<u8> rgb(pixel_count * 3);
for (size_t i = 0; i < pixel_count; ++i) {
const u8 first = pixels[i * COLOR_CHANNELS];
const u8 green = pixels[i * COLOR_CHANNELS + 1];
const u8 third = pixels[i * COLOR_CHANNELS + 2];
rgb[i * 3] = blue_first ? third : first;
rgb[i * 3 + 1] = green;
rgb[i * 3 + 2] = blue_first ? first : third;
}
WritePortablePixmap(path, "P6", extent, rgb);
}
VkImageMemoryBarrier MakeTransitionBarrier(VkImage image, VkAccessFlags src_access,
VkAccessFlags dst_access, VkImageLayout old_layout,
VkImageLayout new_layout) {
return VkImageMemoryBarrier{
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = src_access,
.dstAccessMask = dst_access,
.oldLayout = old_layout,
.newLayout = new_layout,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = image,
.subresourceRange{
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.baseMipLevel = 0,
.levelCount = 1,
.baseArrayLayer = 0,
.layerCount = 1,
},
};
}
VkImageCopy MakeCopyRegion(VkExtent2D extent) {
return VkImageCopy{
.srcSubresource{
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.mipLevel = 0,
.baseArrayLayer = 0,
.layerCount = 1,
},
.srcOffset = {},
.dstSubresource{
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.mipLevel = 0,
.baseArrayLayer = 0,
.layerCount = 1,
},
.dstOffset = {},
.extent = {.width = extent.width, .height = extent.height, .depth = 1},
};
}
void CopyPresentedFrame(vk::CommandBuffer cmdbuf, VkImage source, LsfgImage& destination,
VkExtent2D extent) {
const auto make_barrier = MakeTransitionBarrier;
const std::array before{
make_barrier(source, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT,
VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL),
make_barrier(destination.Handle(), VK_ACCESS_SHADER_READ_BIT, VK_ACCESS_TRANSFER_WRITE_BIT,
destination.Layout(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL),
};
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT |
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT, 0, {}, {}, before);
cmdbuf.CopyImage(source, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, destination.Handle(),
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, MakeCopyRegion(extent));
const std::array after{
make_barrier(source, VK_ACCESS_TRANSFER_READ_BIT, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_IMAGE_LAYOUT_GENERAL),
make_barrier(destination.Handle(), VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_GENERAL),
};
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT |
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
0, {}, {}, after);
destination.SetLayout(VK_IMAGE_LAYOUT_GENERAL);
}
} // Anonymous namespace
FrameGen::FrameGen(MemoryAllocator& memory_allocator_, Scheduler& scheduler_)
: memory_allocator{memory_allocator_}, scheduler{scheduler_} {}
FrameGen::~FrameGen() = default;
void FrameGen::Process(const Device& device, Frame* frame, VkFormat format, bool generate) {
generated = false;
if (unavailable || !Settings::values.frame_gen.GetValue()) {
return;
}
if (!frame->storage_view) {
unavailable = true;
return;
}
if (!shaders) {
shaders.emplace(device);
if (!shaders->IsValid()) {
unavailable = true;
return;
}
}
const VkExtent2D extent{.width = frame->width, .height = frame->height};
if (!chain || built_extent.width != extent.width || built_extent.height != extent.height ||
built_format != format || built_flow_scale != ConfiguredFlowScale() ||
built_generations != ConfiguredGenerations()) {
Rebuild(device, extent, format);
}
const u64 count = frame_count++;
last_count = count;
generated = generate && count + 1 >= LSFG_REQUIRED_FRAMES;
scheduler.RequestOutsideRenderPassOperationContext();
scheduler.Record([this, source = *frame->image, extent, count,
dispatch = generated](vk::CommandBuffer cmdbuf) {
CopyPresentedFrame(cmdbuf, source, chain->Input(count), extent);
if (dispatch) {
chain->DispatchShared(cmdbuf, count);
}
});
const bool dump_requested = generated && Settings::values.frame_gen_dump_flow.GetValue();
if (!dump_requested) {
dumped = false;
} else if (!dumped) {
DumpDebugImages(count);
dumped = true;
}
}
size_t FrameGen::WantedGenerations() const {
if (unavailable || !Settings::values.frame_gen.GetValue()) {
return 0;
}
return ConfiguredGenerations();
}
size_t FrameGen::GeneratedFrameCount() const {
return generated && chain ? chain->GenerationCount() : 0;
}
void FrameGen::GenerateInto(const Device& device, Frame* destination, size_t generation) {
chain->SetTarget(device, generation, destination->index, *destination->storage_view);
const VkExtent2D extent{.width = destination->width, .height = destination->height};
scheduler.RequestOutsideRenderPassOperationContext();
scheduler.Record([this, count = last_count, generation, target = destination->index,
image = *destination->image, extent](vk::CommandBuffer cmdbuf) {
chain->DispatchGeneration(cmdbuf, count, generation, target, image, extent);
});
}
void FrameGen::Rebuild(const Device& device, VkExtent2D extent, VkFormat format) {
scheduler.Finish();
chain.reset();
built_flow_scale = ConfiguredFlowScale();
built_generations = ConfiguredGenerations();
chain.emplace(device, memory_allocator, *shaders, extent, format, built_flow_scale,
built_generations);
built_extent = extent;
built_format = format;
frame_count = 0;
generated = false;
}
void FrameGen::DumpDebugImages(u64 count) {
const std::filesystem::path directory =
Common::FS::GetEdenPath(Common::FS::EdenPath::LosslessDir) / "debug";
if (!Common::FS::CreateDirs(directory)) {
return;
}
const auto dump = [&](const std::string& name, LsfgImage& image) {
const VkExtent2D extent = image.Extent();
const VkFormat format = image.Format();
const VkDeviceSize texel_size = BytesPerTexel(format);
const VkDeviceSize size =
static_cast<VkDeviceSize>(extent.width) * extent.height * texel_size;
vk::Buffer buffer = CreateWrappedBuffer(memory_allocator, size, MemoryUsage::Download);
scheduler.RequestOutsideRenderPassOperationContext();
scheduler.Record(
[handle = image.Handle(), dst = *buffer, extent](vk::CommandBuffer cmdbuf) {
DownloadColorImage(
cmdbuf, handle, dst,
VkExtent3D{.width = extent.width, .height = extent.height, .depth = 1});
});
scheduler.Finish();
buffer.Invalidate();
const std::span<u8> mapped = buffer.Mapped();
if (format == LSFG_FLOW_FORMAT) {
WriteGrayscalePgm(directory / (name + ".pgm"), extent, mapped);
} else if (texel_size == COLOR_CHANNELS) {
WriteColorPpm(directory / (name + ".ppm"), extent, mapped, IsBlueFirst(format));
} else {
WriteRaw(directory / (name + "_" + std::to_string(extent.width) + "x" +
std::to_string(extent.height) + ".f16"),
mapped.subspan(0, std::min<size_t>(size, mapped.size())));
}
};
dump("in0", chain->Input(0));
dump("in1", chain->Input(1));
for (size_t level = 0; level < LSFG_MIP_LEVELS; ++level) {
dump("flow_mip" + std::to_string(level), chain->FlowLevel(level));
}
for (size_t index = 0; index < 2; ++index) {
dump("alpha0_" + std::to_string(index), chain->AlphaOutput(0, count, index));
dump("alpha6_" + std::to_string(index), chain->AlphaOutput(LSFG_MIP_LEVELS - 1, count,
index));
}
for (size_t level = 0; level < LSFG_BETA_OUTPUTS; ++level) {
dump("beta_" + std::to_string(level), chain->BetaOutput(level));
}
dump("gamma0", chain->GammaOutput(0));
dump("gamma6", chain->GammaOutput(LSFG_MIP_LEVELS - 1));
dump("delta2_out1", chain->DeltaOutput1(LSFG_DELTA_INSTANCES - 1));
dump("delta2_out2", chain->DeltaOutput2(LSFG_DELTA_INSTANCES - 1));
}
} // namespace Vulkan
@@ -1,52 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <optional>
#include "common/common_types.h"
#include "video_core/renderer_vulkan/present/lsfg_chain.h"
#include "video_core/renderer_vulkan/present/lsfg_shaders.h"
#include "video_core/vulkan_common/vulkan_memory_allocator.h"
namespace Vulkan {
class Device;
class Scheduler;
struct Frame;
class FrameGen {
public:
explicit FrameGen(MemoryAllocator& memory_allocator, Scheduler& scheduler);
~FrameGen();
void Process(const Device& device, Frame* frame, VkFormat format, bool generate);
[[nodiscard]] size_t WantedGenerations() const;
[[nodiscard]] size_t GeneratedFrameCount() const;
void GenerateInto(const Device& device, Frame* destination, size_t generation);
private:
void Rebuild(const Device& device, VkExtent2D extent, VkFormat format);
void DumpDebugImages(u64 count);
MemoryAllocator& memory_allocator;
Scheduler& scheduler;
std::optional<LsfgShaders> shaders;
std::optional<LsfgChain> chain;
VkExtent2D built_extent{};
VkFormat built_format{VK_FORMAT_UNDEFINED};
f32 built_flow_scale{};
size_t built_generations{};
u64 frame_count{};
u64 last_count{};
bool generated{};
bool unavailable{};
bool dumped{};
};
} // namespace Vulkan
@@ -1,140 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk
// SPDX-License-Identifier: GPL-3.0-or-later
#include <vector>
#include "video_core/frame_gen/lossless_dll.h"
#include "video_core/renderer_vulkan/present/lsfg_alpha.h"
#include "video_core/renderer_vulkan/present/lsfg_shaders.h"
#include "video_core/renderer_vulkan/present/util.h"
#include "video_core/vulkan_common/vulkan_device.h"
namespace Vulkan {
namespace {
constexpr u32 DISPATCH_TILE_SHIFT = 3;
[[nodiscard]] u32 GroupCount(u32 size) {
return (size + (1u << DISPATCH_TILE_SHIFT) - 1) >> DISPATCH_TILE_SHIFT;
}
[[nodiscard]] VkExtent2D HalveExtent(VkExtent2D extent) {
return VkExtent2D{
.width = (extent.width + 1) >> 1,
.height = (extent.height + 1) >> 1,
};
}
} // Anonymous namespace
LsfgAlphaPasses::LsfgAlphaPasses(const Device& device, const LsfgShaders& shaders) {
using namespace VideoCore::FrameGen::PerformanceShader;
passes[0] = LsfgPass(device, shaders, ALPHA[0],
{{1, VK_DESCRIPTOR_TYPE_SAMPLER},
{1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE},
{1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}});
passes[1] = LsfgPass(device, shaders, ALPHA[1],
{{1, VK_DESCRIPTOR_TYPE_SAMPLER},
{1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE},
{1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}});
passes[2] = LsfgPass(device, shaders, ALPHA[2],
{{1, VK_DESCRIPTOR_TYPE_SAMPLER},
{1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE},
{2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}});
passes[3] = LsfgPass(device, shaders, ALPHA[3],
{{1, VK_DESCRIPTOR_TYPE_SAMPLER},
{2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE},
{2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}});
}
LsfgAlpha::LsfgAlpha(const Device& device, MemoryAllocator& memory_allocator,
const LsfgAlphaPasses& passes_, LsfgResources& resources,
vk::DescriptorPool& descriptor_pool, LsfgImage& input_)
: passes{&passes_}, input{&input_} {
const VkExtent2D half_extent = HalveExtent(input->Extent());
const VkExtent2D quarter_extent = HalveExtent(half_extent);
temp1 = LsfgImage(device, memory_allocator, half_extent);
temp2 = LsfgImage(device, memory_allocator, half_extent);
for (size_t i = 0; i < temp3.size(); ++i) {
temp3[i] = LsfgImage(device, memory_allocator, quarter_extent);
for (size_t j = 0; j < LSFG_HISTORY_SLOTS; ++j) {
out_images[j][i] = LsfgImage(device, memory_allocator, quarter_extent);
}
}
std::vector<VkDescriptorSetLayout> layouts;
for (size_t i = 0; i < LSFG_ALPHA_STAGES - 1; ++i) {
layouts.push_back(passes->Get(i).SetLayout());
}
for (size_t i = 0; i < LSFG_HISTORY_SLOTS; ++i) {
layouts.push_back(passes->Get(3).SetLayout());
}
owned_sets = CreateWrappedDescriptorSets(descriptor_pool, layouts);
for (size_t i = 0; i < LSFG_ALPHA_STAGES - 1; ++i) {
descriptor_sets[i] = owned_sets[i];
}
for (size_t i = 0; i < LSFG_HISTORY_SLOTS; ++i) {
last_descriptor_sets[i] = owned_sets[LSFG_ALPHA_STAGES - 1 + i];
}
const VkSampler sampler = resources.GetSampler();
LsfgDescriptorWriter(descriptor_sets[0])
.AddSampler(sampler)
.AddSampledImage(*input)
.AddStorageImage(temp1)
.Build(device);
LsfgDescriptorWriter(descriptor_sets[1])
.AddSampler(sampler)
.AddSampledImage(temp1)
.AddStorageImage(temp2)
.Build(device);
LsfgDescriptorWriter(descriptor_sets[2])
.AddSampler(sampler)
.AddSampledImage(temp2)
.AddStorageImages(temp3)
.Build(device);
for (size_t i = 0; i < LSFG_HISTORY_SLOTS; ++i) {
LsfgDescriptorWriter(last_descriptor_sets[i])
.AddSampler(sampler)
.AddSampledImages(temp3)
.AddStorageImages(out_images[i])
.Build(device);
}
}
void LsfgAlpha::PushBarriers(LsfgBarriers& barriers, u64 frame_count, size_t stage) {
switch (stage) {
case 0:
barriers.WriteToRead(*input).ReadToWrite(temp1);
break;
case 1:
barriers.WriteToRead(temp1).ReadToWrite(temp2);
break;
case 2:
barriers.WriteToRead(temp2).ReadToWriteAll(temp3);
break;
default:
barriers.WriteToReadAll(temp3).ReadToWriteAll(out_images[frame_count % LSFG_HISTORY_SLOTS]);
break;
}
}
void LsfgAlpha::DispatchStage(vk::CommandBuffer cmdbuf, u64 frame_count, size_t stage) {
const VkExtent2D extent = stage < 2 ? temp1.Extent() : temp3[0].Extent();
const VkDescriptorSet set = stage < LSFG_ALPHA_STAGES - 1
? descriptor_sets[stage]
: last_descriptor_sets[frame_count % LSFG_HISTORY_SLOTS];
passes->Get(stage).BindSet(cmdbuf, set);
cmdbuf.Dispatch(GroupCount(extent.width), GroupCount(extent.height), 1);
}
} // namespace Vulkan
@@ -1,62 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <array>
#include "common/common_types.h"
#include "video_core/renderer_vulkan/present/lsfg_common.h"
namespace Vulkan {
class Device;
class LsfgShaders;
constexpr size_t LSFG_ALPHA_STAGES = 4;
class LsfgAlphaPasses {
public:
LsfgAlphaPasses() = default;
LsfgAlphaPasses(const Device& device, const LsfgShaders& shaders);
[[nodiscard]] const LsfgPass& Get(size_t stage) const {
return passes[stage];
}
private:
std::array<LsfgPass, LSFG_ALPHA_STAGES> passes;
};
class LsfgAlpha {
public:
LsfgAlpha() = default;
LsfgAlpha(const Device& device, MemoryAllocator& memory_allocator,
const LsfgAlphaPasses& passes_, LsfgResources& resources,
vk::DescriptorPool& descriptor_pool, LsfgImage& input);
void PushBarriers(LsfgBarriers& barriers, u64 frame_count, size_t stage);
void DispatchStage(vk::CommandBuffer cmdbuf, u64 frame_count, size_t stage);
[[nodiscard]] LsfgImageHistory& Outputs() {
return out_images;
}
private:
const LsfgAlphaPasses* passes{};
LsfgImage* input{};
std::array<VkDescriptorSet, LSFG_ALPHA_STAGES - 1> descriptor_sets{};
std::array<VkDescriptorSet, LSFG_HISTORY_SLOTS> last_descriptor_sets{};
vk::DescriptorSets owned_sets;
LsfgImage temp1;
LsfgImage temp2;
LsfgImagePair temp3;
LsfgImageHistory out_images;
};
} // namespace Vulkan
@@ -1,147 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk
// SPDX-License-Identifier: GPL-3.0-or-later
#include <vector>
#include "video_core/frame_gen/lossless_dll.h"
#include "video_core/renderer_vulkan/present/lsfg_beta.h"
#include "video_core/renderer_vulkan/present/lsfg_shaders.h"
#include "video_core/renderer_vulkan/present/util.h"
#include "video_core/vulkan_common/vulkan_device.h"
namespace Vulkan {
namespace {
constexpr u32 DISPATCH_TILE_SHIFT = 3;
constexpr u32 OUTPUT_TILE_SHIFT = 5;
[[nodiscard]] u32 GroupCount(u32 size, u32 shift) {
return (size + (1u << shift) - 1) >> shift;
}
} // Anonymous namespace
LsfgBeta::LsfgBeta(const Device& device, MemoryAllocator& memory_allocator,
const LsfgShaders& shaders, LsfgResources& resources,
vk::DescriptorPool& descriptor_pool, LsfgImageHistory& inputs_)
: inputs{&inputs_} {
using namespace VideoCore::FrameGen::PerformanceShader;
passes[0] = LsfgPass(device, shaders, BETA[0],
{{1, VK_DESCRIPTOR_TYPE_SAMPLER},
{6, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE},
{2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}});
for (size_t i = 1; i < LSFG_BETA_STAGES - 1; ++i) {
passes[i] = LsfgPass(device, shaders, BETA[i],
{{1, VK_DESCRIPTOR_TYPE_SAMPLER},
{2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE},
{2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}});
}
passes[4] = LsfgPass(device, shaders, BETA[4],
{{1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER},
{1, VK_DESCRIPTOR_TYPE_SAMPLER},
{2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE},
{6, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}});
const VkExtent2D extent = (*inputs)[0][0].Extent();
for (size_t i = 0; i < temp1.size(); ++i) {
temp1[i] = LsfgImage(device, memory_allocator, extent);
temp2[i] = LsfgImage(device, memory_allocator, extent);
}
for (size_t i = 0; i < LSFG_BETA_OUTPUTS; ++i) {
const VkExtent2D level_extent{
.width = extent.width >> i,
.height = extent.height >> i,
};
out_images[i] = LsfgImage(device, memory_allocator, level_extent, LSFG_FLOW_FORMAT);
}
std::vector<VkDescriptorSetLayout> layouts;
for (size_t i = 0; i < LSFG_HISTORY_SLOTS; ++i) {
layouts.push_back(passes[0].SetLayout());
}
for (size_t i = 1; i < LSFG_BETA_STAGES; ++i) {
layouts.push_back(passes[i].SetLayout());
}
owned_sets = CreateWrappedDescriptorSets(descriptor_pool, layouts);
for (size_t i = 0; i < LSFG_HISTORY_SLOTS; ++i) {
first_descriptor_sets[i] = owned_sets[i];
}
for (size_t i = 0; i < LSFG_BETA_STAGES - 1; ++i) {
descriptor_sets[i] = owned_sets[LSFG_HISTORY_SLOTS + i];
}
const VkSampler sampler = resources.GetSampler();
const VkSampler border_sampler = resources.GetSampler(
VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER, VK_COMPARE_OP_NEVER, true);
for (size_t i = 0; i < LSFG_HISTORY_SLOTS; ++i) {
LsfgDescriptorWriter(first_descriptor_sets[i])
.AddSampler(border_sampler)
.AddSampledImages((*inputs)[(i + 1) % LSFG_HISTORY_SLOTS])
.AddSampledImages((*inputs)[(i + 2) % LSFG_HISTORY_SLOTS])
.AddSampledImages((*inputs)[i % LSFG_HISTORY_SLOTS])
.AddStorageImages(temp1)
.Build(device);
}
LsfgDescriptorWriter(descriptor_sets[0])
.AddSampler(sampler)
.AddSampledImages(temp1)
.AddStorageImages(temp2)
.Build(device);
LsfgDescriptorWriter(descriptor_sets[1])
.AddSampler(sampler)
.AddSampledImages(temp2)
.AddStorageImages(temp1)
.Build(device);
LsfgDescriptorWriter(descriptor_sets[2])
.AddSampler(sampler)
.AddSampledImages(temp1)
.AddStorageImages(temp2)
.Build(device);
LsfgDescriptorWriter(descriptor_sets[3])
.AddUniformBuffer(resources.GetBuffer(0.5f), LsfgResources::BufferSize())
.AddSampler(sampler)
.AddSampledImages(temp2)
.AddStorageImages(out_images)
.Build(device);
}
void LsfgBeta::Dispatch(vk::CommandBuffer cmdbuf, u64 frame_count) {
const VkExtent2D extent = temp1[0].Extent();
const u32 groups_x = GroupCount(extent.width, DISPATCH_TILE_SHIFT);
const u32 groups_y = GroupCount(extent.height, DISPATCH_TILE_SHIFT);
LsfgBarriers barriers(cmdbuf);
for (auto& slot : *inputs) {
barriers.WriteToReadAll(slot);
}
barriers.ReadToWriteAll(temp1).Build();
passes[0].Bind(cmdbuf, first_descriptor_sets[frame_count % LSFG_HISTORY_SLOTS]);
cmdbuf.Dispatch(groups_x, groups_y, 1);
LsfgBarriers(cmdbuf).WriteToReadAll(temp1).ReadToWriteAll(temp2).Build();
passes[1].Bind(cmdbuf, descriptor_sets[0]);
cmdbuf.Dispatch(groups_x, groups_y, 1);
LsfgBarriers(cmdbuf).WriteToReadAll(temp2).ReadToWriteAll(temp1).Build();
passes[2].Bind(cmdbuf, descriptor_sets[1]);
cmdbuf.Dispatch(groups_x, groups_y, 1);
LsfgBarriers(cmdbuf).WriteToReadAll(temp1).ReadToWriteAll(temp2).Build();
passes[3].Bind(cmdbuf, descriptor_sets[2]);
cmdbuf.Dispatch(groups_x, groups_y, 1);
LsfgBarriers(cmdbuf).WriteToReadAll(temp2).ReadToWriteAll(out_images).Build();
passes[4].Bind(cmdbuf, descriptor_sets[3]);
cmdbuf.Dispatch(GroupCount(extent.width, OUTPUT_TILE_SHIFT),
GroupCount(extent.height, OUTPUT_TILE_SHIFT), 1);
}
} // namespace Vulkan
@@ -1,48 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <array>
#include "common/common_types.h"
#include "video_core/renderer_vulkan/present/lsfg_common.h"
namespace Vulkan {
class Device;
class LsfgShaders;
constexpr size_t LSFG_BETA_STAGES = 5;
constexpr size_t LSFG_BETA_OUTPUTS = 6;
class LsfgBeta {
public:
LsfgBeta() = default;
LsfgBeta(const Device& device, MemoryAllocator& memory_allocator, const LsfgShaders& shaders,
LsfgResources& resources, vk::DescriptorPool& descriptor_pool,
LsfgImageHistory& inputs);
void Dispatch(vk::CommandBuffer cmdbuf, u64 frame_count);
[[nodiscard]] LsfgImage& Output(size_t level) {
return out_images[level];
}
private:
LsfgImageHistory* inputs{};
std::array<LsfgPass, LSFG_BETA_STAGES> passes;
std::array<VkDescriptorSet, LSFG_HISTORY_SLOTS> first_descriptor_sets{};
std::array<VkDescriptorSet, LSFG_BETA_STAGES - 1> descriptor_sets{};
vk::DescriptorSets owned_sets;
LsfgImagePair temp1;
LsfgImagePair temp2;
std::array<LsfgImage, LSFG_BETA_OUTPUTS> out_images;
};
} // namespace Vulkan
@@ -1,102 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk
// SPDX-License-Identifier: GPL-3.0-or-later
#include <algorithm>
#include "video_core/renderer_vulkan/present/lsfg_chain.h"
#include "video_core/renderer_vulkan/present/lsfg_shaders.h"
#include "video_core/vulkan_common/vulkan_device.h"
namespace Vulkan {
namespace {
constexpr u32 FIXED_DESCRIPTOR_SETS = 64;
constexpr u32 DESCRIPTOR_SETS_PER_GENERATION = 112;
constexpr size_t FIRST_DELTA_LEVEL = 4;
} // Anonymous namespace
LsfgChain::LsfgChain(const Device& device, MemoryAllocator& memory_allocator,
const LsfgShaders& shaders, VkExtent2D extent, VkFormat format,
f32 flow_scale, size_t generation_count_)
: generation_count{generation_count_},
resources{device, memory_allocator, flow_scale},
descriptor_pool{CreateLsfgDescriptorPool(
device, FIXED_DESCRIPTOR_SETS +
DESCRIPTOR_SETS_PER_GENERATION * static_cast<u32>(generation_count))} {
for (auto& image : frames) {
image = LsfgImage(device, memory_allocator, extent, format);
}
mipmaps = LsfgMipmaps(device, memory_allocator, shaders, resources, descriptor_pool, frames,
flow_scale);
alpha_passes = LsfgAlphaPasses(device, shaders);
for (size_t i = 0; i < LSFG_MIP_LEVELS; ++i) {
alpha[i] = LsfgAlpha(device, memory_allocator, alpha_passes, resources, descriptor_pool,
mipmaps.Output(i));
}
beta = LsfgBeta(device, memory_allocator, shaders, resources, descriptor_pool,
alpha[0].Outputs());
for (size_t i = 0; i < LSFG_MIP_LEVELS; ++i) {
const size_t level = LSFG_MIP_LEVELS - 1 - i;
gamma[i] = LsfgGamma(device, memory_allocator, shaders, resources, descriptor_pool,
alpha[level].Outputs(),
beta.Output(std::min(level, LSFG_BETA_OUTPUTS - 1)),
i == 0 ? nullptr : &gamma[i - 1].Output(), generation_count);
if (i < FIRST_DELTA_LEVEL) {
continue;
}
const size_t index = i - FIRST_DELTA_LEVEL;
delta[index] = LsfgDelta(
device, memory_allocator, shaders, resources, descriptor_pool, alpha[level].Outputs(),
beta.Output(level), i == FIRST_DELTA_LEVEL ? nullptr : &gamma[i - 1].Output(),
i == FIRST_DELTA_LEVEL ? nullptr : &delta[index - 1].Output1(),
i == FIRST_DELTA_LEVEL ? nullptr : &delta[index - 1].Output2(), generation_count);
}
generate = LsfgGenerate(device, shaders, resources, descriptor_pool, frames,
gamma[LSFG_MIP_LEVELS - 1].Output(),
delta[LSFG_DELTA_INSTANCES - 1].Output1(),
delta[LSFG_DELTA_INSTANCES - 1].Output2(), generation_count);
}
void LsfgChain::DispatchShared(vk::CommandBuffer cmdbuf, u64 frame_count) {
mipmaps.Dispatch(cmdbuf, frame_count);
for (size_t stage = 0; stage < LSFG_ALPHA_STAGES; ++stage) {
LsfgBarriers barriers(cmdbuf);
for (auto& level : alpha) {
level.PushBarriers(barriers, frame_count, stage);
}
barriers.Build();
alpha_passes.Get(stage).BindPipeline(cmdbuf);
for (auto& level : alpha) {
level.DispatchStage(cmdbuf, frame_count, stage);
}
}
beta.Dispatch(cmdbuf, frame_count);
}
void LsfgChain::DispatchGeneration(vk::CommandBuffer cmdbuf, u64 frame_count, size_t generation,
u32 target, VkImage image, VkExtent2D extent) {
for (size_t i = 0; i < LSFG_MIP_LEVELS; ++i) {
gamma[i].Dispatch(cmdbuf, frame_count, generation);
if (i >= FIRST_DELTA_LEVEL) {
delta[i - FIRST_DELTA_LEVEL].Dispatch(cmdbuf, frame_count, generation);
}
}
generate.Dispatch(cmdbuf, frame_count, generation, target, image, extent);
}
} // namespace Vulkan
@@ -1,91 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <array>
#include "common/common_types.h"
#include "video_core/renderer_vulkan/present/lsfg_alpha.h"
#include "video_core/renderer_vulkan/present/lsfg_beta.h"
#include "video_core/renderer_vulkan/present/lsfg_common.h"
#include "video_core/renderer_vulkan/present/lsfg_delta.h"
#include "video_core/renderer_vulkan/present/lsfg_gamma.h"
#include "video_core/renderer_vulkan/present/lsfg_generate.h"
#include "video_core/renderer_vulkan/present/lsfg_mipmaps.h"
namespace Vulkan {
class Device;
class LsfgShaders;
constexpr size_t LSFG_DELTA_INSTANCES = 3;
class LsfgChain {
public:
LsfgChain(const Device& device, MemoryAllocator& memory_allocator, const LsfgShaders& shaders,
VkExtent2D extent, VkFormat format, f32 flow_scale, size_t generation_count_);
LsfgChain(const LsfgChain&) = delete;
LsfgChain& operator=(const LsfgChain&) = delete;
void DispatchShared(vk::CommandBuffer cmdbuf, u64 frame_count);
void DispatchGeneration(vk::CommandBuffer cmdbuf, u64 frame_count, size_t generation,
u32 target, VkImage image, VkExtent2D extent);
void SetTarget(const Device& device, size_t generation, u32 target, VkImageView view) {
generate.SetTarget(device, generation, target, view);
}
[[nodiscard]] LsfgImage& Input(u64 frame_count) {
return frames[frame_count % frames.size()];
}
[[nodiscard]] size_t GenerationCount() const {
return generation_count;
}
[[nodiscard]] LsfgImage& FlowLevel(size_t level) {
return mipmaps.Output(level);
}
[[nodiscard]] LsfgImage& AlphaOutput(size_t level, u64 frame_count, size_t index) {
return alpha[level].Outputs()[frame_count % LSFG_HISTORY_SLOTS][index];
}
[[nodiscard]] LsfgImage& BetaOutput(size_t level) {
return beta.Output(level);
}
[[nodiscard]] LsfgImage& GammaOutput(size_t index) {
return gamma[index].Output();
}
[[nodiscard]] LsfgImage& DeltaOutput1(size_t index) {
return delta[index].Output1();
}
[[nodiscard]] LsfgImage& DeltaOutput2(size_t index) {
return delta[index].Output2();
}
private:
size_t generation_count{};
LsfgResources resources;
vk::DescriptorPool descriptor_pool;
LsfgImagePair frames;
LsfgMipmaps mipmaps;
LsfgAlphaPasses alpha_passes;
std::array<LsfgAlpha, LSFG_MIP_LEVELS> alpha;
LsfgBeta beta;
std::array<LsfgGamma, LSFG_MIP_LEVELS> gamma;
std::array<LsfgDelta, LSFG_DELTA_INSTANCES> delta;
LsfgGenerate generate;
};
} // namespace Vulkan
@@ -1,339 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk
// SPDX-License-Identifier: GPL-3.0-or-later
#include <algorithm>
#include <cstring>
#include "video_core/renderer_vulkan/present/lsfg_common.h"
#include "video_core/renderer_vulkan/present/lsfg_shaders.h"
#include "video_core/renderer_vulkan/present/util.h"
#include "video_core/vulkan_common/vulkan_device.h"
namespace Vulkan {
namespace {
constexpr u32 DESCRIPTORS_PER_TYPE = 4096;
struct LsfgConstants {
std::array<u32, 2> input_offset;
u32 first_iter;
u32 first_iter_s;
u32 advanced_color_kind;
u32 hdr_support;
f32 resolution_inv_scale;
f32 timestamp;
f32 ui_threshold;
std::array<u32, 3> padding;
};
static_assert(sizeof(LsfgConstants) == 48);
vk::Image CreateChainImage(MemoryAllocator& memory_allocator, VkExtent2D extent, VkFormat format) {
const VkImageCreateInfo image_ci{
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.imageType = VK_IMAGE_TYPE_2D,
.format = format,
.extent = {.width = extent.width, .height = extent.height, .depth = 1},
.mipLevels = 1,
.arrayLayers = 1,
.samples = VK_SAMPLE_COUNT_1_BIT,
.tiling = VK_IMAGE_TILING_OPTIMAL,
.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT |
VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
.queueFamilyIndexCount = 0,
.pQueueFamilyIndices = nullptr,
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
};
return memory_allocator.CreateImage(image_ci);
}
vk::Buffer CreateUniformBuffer(MemoryAllocator& memory_allocator, VkDeviceSize size) {
const VkBufferCreateInfo buffer_ci{
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.size = size,
.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
.queueFamilyIndexCount = 0,
.pQueueFamilyIndices = nullptr,
};
return memory_allocator.CreateBuffer(buffer_ci, MemoryUsage::Upload);
}
VkImageMemoryBarrier MakeBarrier(const LsfgImage& image, VkAccessFlags src_access,
VkAccessFlags dst_access) {
return VkImageMemoryBarrier{
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = src_access,
.dstAccessMask = dst_access,
.oldLayout = image.Layout(),
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = image.Handle(),
.subresourceRange{
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.baseMipLevel = 0,
.levelCount = 1,
.baseArrayLayer = 0,
.layerCount = 1,
},
};
}
} // Anonymous namespace
LsfgImage::LsfgImage(const Device& device, MemoryAllocator& memory_allocator, VkExtent2D extent_,
VkFormat format_)
: extent{std::max(1u, extent_.width), std::max(1u, extent_.height)}, format{format_} {
image = CreateChainImage(memory_allocator, extent, format);
view = CreateWrappedImageView(device, image, format);
}
LsfgBarriers& LsfgBarriers::Push(LsfgImage& image, VkAccessFlags src_access,
VkAccessFlags dst_access) {
barriers.push_back(MakeBarrier(image, src_access, dst_access));
image.SetLayout(VK_IMAGE_LAYOUT_GENERAL);
return *this;
}
LsfgBarriers& LsfgBarriers::WriteToRead(LsfgImage& image) {
return Push(image, VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT);
}
LsfgBarriers& LsfgBarriers::ReadToWrite(LsfgImage& image) {
return Push(image, VK_ACCESS_SHADER_READ_BIT, VK_ACCESS_SHADER_WRITE_BIT);
}
LsfgBarriers& LsfgBarriers::WriteToRead(LsfgImage* image) {
return image == nullptr ? *this : WriteToRead(*image);
}
LsfgBarriers& LsfgBarriers::ReadToWrite(LsfgImage* image) {
return image == nullptr ? *this : ReadToWrite(*image);
}
LsfgBarriers& LsfgBarriers::DiscardToWrite(VkImage image) {
barriers.push_back(VkImageMemoryBarrier{
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = 0,
.dstAccessMask = VK_ACCESS_SHADER_WRITE_BIT,
.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED,
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = image,
.subresourceRange{
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.baseMipLevel = 0,
.levelCount = 1,
.baseArrayLayer = 0,
.layerCount = 1,
},
});
return *this;
}
VkDeviceSize LsfgResources::BufferSize() {
return sizeof(LsfgConstants);
}
VkSampler LsfgResources::GetSampler(VkSamplerAddressMode address_mode, VkCompareOp compare_op,
bool white_border) {
const u64 key = static_cast<u64>(address_mode) | (static_cast<u64>(compare_op) << 8) |
(static_cast<u64>(white_border) << 16);
const auto it = samplers.find(key);
if (it != samplers.end()) {
return *it->second;
}
const auto [entry, inserted] =
samplers.emplace(key, CreateLsfgSampler(*device, address_mode, compare_op, white_border));
return *entry->second;
}
VkBuffer LsfgResources::GetBuffer(f32 timestamp, bool first_iter, bool first_iter_s) {
u32 timestamp_bits{};
std::memcpy(&timestamp_bits, &timestamp, sizeof(timestamp_bits));
const u64 key = static_cast<u64>(timestamp_bits) | (static_cast<u64>(first_iter) << 32) |
(static_cast<u64>(first_iter_s) << 33);
const auto it = buffers.find(key);
if (it != buffers.end()) {
return *it->second;
}
vk::Buffer buffer = CreateUniformBuffer(*memory_allocator, sizeof(LsfgConstants));
const LsfgConstants constants{
.input_offset = {0, 0},
.first_iter = first_iter ? 1u : 0u,
.first_iter_s = first_iter_s ? 1u : 0u,
.advanced_color_kind = 0,
.hdr_support = 0,
.resolution_inv_scale = 1.0f / flow_scale,
.timestamp = timestamp,
.ui_threshold = 0.5f,
.padding = {0, 0, 0},
};
const std::span<u8> mapped = buffer.Mapped();
std::memcpy(mapped.data(), &constants, sizeof(constants));
buffer.Flush();
const auto [entry, inserted] = buffers.emplace(key, std::move(buffer));
return *entry->second;
}
void LsfgBarriers::Build() {
if (barriers.empty()) {
return;
}
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, {}, {}, barriers);
barriers.clear();
}
LsfgDescriptorWriter& LsfgDescriptorWriter::PushImage(VkDescriptorType type, VkSampler sampler,
VkImageView view) {
image_infos.push_back(VkDescriptorImageInfo{
.sampler = sampler,
.imageView = view,
.imageLayout = view == VK_NULL_HANDLE ? VK_IMAGE_LAYOUT_UNDEFINED
: VK_IMAGE_LAYOUT_GENERAL,
});
writes.push_back(VkWriteDescriptorSet{
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
.pNext = nullptr,
.dstSet = set,
.dstBinding = binding++,
.dstArrayElement = 0,
.descriptorCount = 1,
.descriptorType = type,
.pImageInfo = &image_infos.back(),
.pBufferInfo = nullptr,
.pTexelBufferView = nullptr,
});
return *this;
}
LsfgDescriptorWriter& LsfgDescriptorWriter::AddSampler(VkSampler sampler) {
return PushImage(VK_DESCRIPTOR_TYPE_SAMPLER, sampler, VK_NULL_HANDLE);
}
LsfgDescriptorWriter& LsfgDescriptorWriter::AddSampledImage(const LsfgImage& image) {
return PushImage(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, VK_NULL_HANDLE, image.View());
}
LsfgDescriptorWriter& LsfgDescriptorWriter::AddSampledImage(const LsfgImage* image) {
return PushImage(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, VK_NULL_HANDLE,
image == nullptr ? VK_NULL_HANDLE : image->View());
}
LsfgDescriptorWriter& LsfgDescriptorWriter::AddStorageImage(const LsfgImage& image) {
return PushImage(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, VK_NULL_HANDLE, image.View());
}
LsfgDescriptorWriter& LsfgDescriptorWriter::AddStorageView(VkImageView view) {
return PushImage(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, VK_NULL_HANDLE, view);
}
LsfgDescriptorWriter& LsfgDescriptorWriter::AddUniformBuffer(VkBuffer buffer, VkDeviceSize size) {
buffer_infos.push_back(VkDescriptorBufferInfo{
.buffer = buffer,
.offset = 0,
.range = size,
});
writes.push_back(VkWriteDescriptorSet{
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
.pNext = nullptr,
.dstSet = set,
.dstBinding = binding++,
.dstArrayElement = 0,
.descriptorCount = 1,
.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
.pImageInfo = nullptr,
.pBufferInfo = &buffer_infos.back(),
.pTexelBufferView = nullptr,
});
return *this;
}
void LsfgDescriptorWriter::Build(const Device& device) {
if (writes.empty()) {
return;
}
device.GetLogical().UpdateDescriptorSets(writes, {});
writes.clear();
}
LsfgPass::LsfgPass(const Device& device, const LsfgShaders& shaders, u32 shader_id,
LsfgBindings bindings) {
std::vector<VkDescriptorType> types;
for (const auto& [count, type] : bindings) {
types.insert(types.end(), count, type);
}
descriptor_count = static_cast<u32>(types.size());
descriptor_set_layout = CreateWrappedDescriptorSetLayout(
device, std::span<const VkDescriptorType>{types}, VK_SHADER_STAGE_COMPUTE_BIT);
pipeline_layout = CreateWrappedPipelineLayout(device, descriptor_set_layout);
pipeline = CreateWrappedComputePipeline(device, pipeline_layout, shaders.Get(shader_id));
}
void LsfgPass::Bind(vk::CommandBuffer cmdbuf, VkDescriptorSet set) const {
BindPipeline(cmdbuf);
BindSet(cmdbuf, set);
}
void LsfgPass::BindPipeline(vk::CommandBuffer cmdbuf) const {
cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, *pipeline);
}
void LsfgPass::BindSet(vk::CommandBuffer cmdbuf, VkDescriptorSet set) const {
cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, *pipeline_layout, 0, set, {});
}
vk::DescriptorPool CreateLsfgDescriptorPool(const Device& device, u32 max_sets) {
return CreateWrappedDescriptorPool(
device, DESCRIPTORS_PER_TYPE, max_sets,
{VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_DESCRIPTOR_TYPE_SAMPLER,
VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE});
}
vk::Sampler CreateLsfgSampler(const Device& device, VkSamplerAddressMode address_mode,
VkCompareOp compare_op, bool white_border) {
return device.GetLogical().CreateSampler(VkSamplerCreateInfo{
.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.magFilter = VK_FILTER_LINEAR,
.minFilter = VK_FILTER_LINEAR,
.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR,
.addressModeU = address_mode,
.addressModeV = address_mode,
.addressModeW = address_mode,
.mipLodBias = 0.0f,
.anisotropyEnable = VK_FALSE,
.maxAnisotropy = 0.0f,
.compareEnable = VK_FALSE,
.compareOp = compare_op,
.minLod = 0.0f,
.maxLod = VK_LOD_CLAMP_NONE,
.borderColor = white_border ? VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE
: VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK,
.unnormalizedCoordinates = VK_FALSE,
});
}
} // namespace Vulkan
@@ -1,210 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <array>
#include <deque>
#include <initializer_list>
#include <map>
#include <utility>
#include <vector>
#include "common/common_types.h"
#include "video_core/vulkan_common/vulkan_memory_allocator.h"
#include "video_core/vulkan_common/vulkan_wrapper.h"
namespace Vulkan {
class Device;
class LsfgShaders;
constexpr VkFormat LSFG_DEFAULT_FORMAT = VK_FORMAT_R8G8B8A8_UNORM;
constexpr VkFormat LSFG_FLOW_FORMAT = VK_FORMAT_R8_UNORM;
constexpr VkFormat LSFG_MOTION_FORMAT = VK_FORMAT_R16G16B16A16_SFLOAT;
constexpr size_t LSFG_HISTORY_SLOTS = 3;
constexpr size_t LSFG_MIN_MULTIPLIER = 2;
constexpr size_t LSFG_MAX_MULTIPLIER = 4;
constexpr size_t LSFG_MAX_GENERATIONS = LSFG_MAX_MULTIPLIER - 1;
constexpr size_t LSFG_MAX_TARGETS = 7;
[[nodiscard]] constexpr f32 LsfgTimestamp(size_t generation, size_t generation_count) {
return static_cast<f32>(generation + 1) / static_cast<f32>(generation_count + 1);
}
class LsfgImage {
public:
LsfgImage() = default;
LsfgImage(const Device& device, MemoryAllocator& memory_allocator, VkExtent2D extent_,
VkFormat format = LSFG_DEFAULT_FORMAT);
[[nodiscard]] VkImage Handle() const {
return *image;
}
[[nodiscard]] VkImageView View() const {
return *view;
}
[[nodiscard]] VkExtent2D Extent() const {
return extent;
}
[[nodiscard]] VkFormat Format() const {
return format;
}
[[nodiscard]] VkImageLayout Layout() const {
return layout;
}
void SetLayout(VkImageLayout new_layout) {
layout = new_layout;
}
private:
vk::Image image;
vk::ImageView view;
VkExtent2D extent{};
VkFormat format{VK_FORMAT_UNDEFINED};
VkImageLayout layout{VK_IMAGE_LAYOUT_UNDEFINED};
};
using LsfgImagePair = std::array<LsfgImage, 2>;
using LsfgImageHistory = std::array<LsfgImagePair, LSFG_HISTORY_SLOTS>;
class LsfgResources {
public:
LsfgResources() = default;
LsfgResources(const Device& device_, MemoryAllocator& memory_allocator_, f32 flow_scale_)
: device{&device_}, memory_allocator{&memory_allocator_}, flow_scale{flow_scale_} {}
[[nodiscard]] VkSampler GetSampler(
VkSamplerAddressMode address_mode = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER,
VkCompareOp compare_op = VK_COMPARE_OP_NEVER, bool white_border = false);
[[nodiscard]] VkBuffer GetBuffer(f32 timestamp = 0.0f, bool first_iter = false,
bool first_iter_s = false);
[[nodiscard]] static VkDeviceSize BufferSize();
private:
const Device* device{};
MemoryAllocator* memory_allocator{};
f32 flow_scale{1.0f};
std::map<u64, vk::Sampler> samplers;
std::map<u64, vk::Buffer> buffers;
};
class LsfgBarriers {
public:
explicit LsfgBarriers(vk::CommandBuffer cmdbuf_) : cmdbuf{cmdbuf_} {}
LsfgBarriers& WriteToRead(LsfgImage& image);
LsfgBarriers& ReadToWrite(LsfgImage& image);
LsfgBarriers& WriteToRead(LsfgImage* image);
LsfgBarriers& ReadToWrite(LsfgImage* image);
LsfgBarriers& DiscardToWrite(VkImage image);
template <typename Range>
LsfgBarriers& WriteToReadAll(Range& images) {
for (auto& image : images) {
WriteToRead(image);
}
return *this;
}
template <typename Range>
LsfgBarriers& ReadToWriteAll(Range& images) {
for (auto& image : images) {
ReadToWrite(image);
}
return *this;
}
void Build();
private:
LsfgBarriers& Push(LsfgImage& image, VkAccessFlags src_access, VkAccessFlags dst_access);
vk::CommandBuffer cmdbuf;
std::vector<VkImageMemoryBarrier> barriers;
};
class LsfgDescriptorWriter {
public:
explicit LsfgDescriptorWriter(VkDescriptorSet set_) : set{set_} {}
LsfgDescriptorWriter& AddSampler(VkSampler sampler);
LsfgDescriptorWriter& AddSampledImage(const LsfgImage& image);
LsfgDescriptorWriter& AddSampledImage(const LsfgImage* image);
LsfgDescriptorWriter& AddStorageImage(const LsfgImage& image);
LsfgDescriptorWriter& AddStorageView(VkImageView view);
LsfgDescriptorWriter& AddUniformBuffer(VkBuffer buffer, VkDeviceSize size);
template <typename Range>
LsfgDescriptorWriter& AddSampledImages(const Range& images) {
for (const auto& image : images) {
AddSampledImage(image);
}
return *this;
}
template <typename Range>
LsfgDescriptorWriter& AddStorageImages(const Range& images) {
for (const auto& image : images) {
AddStorageImage(image);
}
return *this;
}
void Build(const Device& device);
private:
LsfgDescriptorWriter& PushImage(VkDescriptorType type, VkSampler sampler, VkImageView view);
VkDescriptorSet set;
u32 binding{};
std::deque<VkDescriptorImageInfo> image_infos;
std::deque<VkDescriptorBufferInfo> buffer_infos;
std::vector<VkWriteDescriptorSet> writes;
};
using LsfgBindings = std::initializer_list<std::pair<u32, VkDescriptorType>>;
class LsfgPass {
public:
LsfgPass() = default;
LsfgPass(const Device& device, const LsfgShaders& shaders, u32 shader_id,
LsfgBindings bindings);
[[nodiscard]] VkDescriptorSetLayout SetLayout() const {
return *descriptor_set_layout;
}
[[nodiscard]] u32 DescriptorCount() const {
return descriptor_count;
}
void Bind(vk::CommandBuffer cmdbuf, VkDescriptorSet set) const;
void BindPipeline(vk::CommandBuffer cmdbuf) const;
void BindSet(vk::CommandBuffer cmdbuf, VkDescriptorSet set) const;
private:
vk::DescriptorSetLayout descriptor_set_layout;
vk::PipelineLayout pipeline_layout;
vk::Pipeline pipeline;
u32 descriptor_count{};
};
[[nodiscard]] vk::DescriptorPool CreateLsfgDescriptorPool(const Device& device, u32 max_sets);
[[nodiscard]] vk::Sampler CreateLsfgSampler(const Device& device, VkSamplerAddressMode address_mode,
VkCompareOp compare_op, bool white_border);
} // namespace Vulkan

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