mirror of
https://git.eden-emu.dev/eden-emu/eden.git
synced 2026-09-12 14:48:49 +00:00
Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b2e80a5b5d | |||
| 03e4e9201f | |||
| a82c6a4fba | |||
| 413b6060e0 | |||
| 4c63d973e1 | |||
| ff8368c606 | |||
| 8a22f1845b | |||
| 301da63a15 | |||
| c95ad020fb | |||
| f3af5d0c25 | |||
| 00a1c392e7 | |||
| e7a96c7907 | |||
| ed566919f4 |
@@ -1,28 +0,0 @@
|
|||||||
From cc15da16e533b2a801934eab2dfeaf3c3949a1dc Mon Sep 17 00:00:00 2001
|
|
||||||
From: crueter <crueter@eden-emu.dev>
|
|
||||||
Date: Mon, 8 Sep 2025 12:28:55 -0400
|
|
||||||
Subject: [PATCH] [cmake] disable NEON runtime check on clang-cl
|
|
||||||
|
|
||||||
When enabling runtime NEON checking for clang-cl, the linker would error out with `undefined symbol: __emit`, since clang doesn't actually implement this instruction. Therefore it makes sense to disable the runtime check by default on this platform, until either this is fixed or a clang-cl compatible intrinsic check is added (I don't have enough knowledge of MSVC to do this)
|
|
||||||
---
|
|
||||||
cmake/OpusConfig.cmake | 7 ++++++-
|
|
||||||
1 file changed, 6 insertions(+), 1 deletion(-)
|
|
||||||
|
|
||||||
diff --git a/cmake/OpusConfig.cmake b/cmake/OpusConfig.cmake
|
|
||||||
index e9319fbad..d0f459e88 100644
|
|
||||||
--- a/cmake/OpusConfig.cmake
|
|
||||||
+++ b/cmake/OpusConfig.cmake
|
|
||||||
@@ -71,7 +71,12 @@ elseif(OPUS_CPU_ARM AND NOT OPUS_DISABLE_INTRINSICS)
|
|
||||||
opus_detect_neon(COMPILER_SUPPORT_NEON)
|
|
||||||
if(COMPILER_SUPPORT_NEON)
|
|
||||||
option(OPUS_USE_NEON "Option to enable NEON" ON)
|
|
||||||
- option(OPUS_MAY_HAVE_NEON "Does runtime check for neon support" ON)
|
|
||||||
+ if (MSVC AND CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
|
|
||||||
+ set(NEON_RUNTIME_CHECK_DEFAULT OFF)
|
|
||||||
+ else()
|
|
||||||
+ set(NEON_RUNTIME_CHECK_DEFAULT ON)
|
|
||||||
+ endif()
|
|
||||||
+ option(OPUS_MAY_HAVE_NEON "Does runtime check for neon support" ${NEON_RUNTIME_CHECK_DEFAULT})
|
|
||||||
option(OPUS_PRESUME_NEON "Assume target CPU has NEON support" OFF)
|
|
||||||
if(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64")
|
|
||||||
set(OPUS_PRESUME_NEON ON)
|
|
||||||
@@ -1,153 +0,0 @@
|
|||||||
From bf455b67b4eaa446ffae5d25410b141b7b1b1082 Mon Sep 17 00:00:00 2001
|
|
||||||
From: crueter <crueter@eden-emu.dev>
|
|
||||||
Date: Mon, 8 Sep 2025 12:08:20 -0400
|
|
||||||
Subject: [PATCH] [cmake] `OPUS_INSTALL` option; only default install if root
|
|
||||||
project
|
|
||||||
|
|
||||||
Signed-off-by: crueter <crueter@eden-emu.dev>
|
|
||||||
---
|
|
||||||
CMakeLists.txt | 112 ++++++++++++++++++++++++++++---------------------
|
|
||||||
1 file changed, 64 insertions(+), 48 deletions(-)
|
|
||||||
|
|
||||||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
|
||||||
index fcf034b19..08b5e16f8 100644
|
|
||||||
--- a/CMakeLists.txt
|
|
||||||
+++ b/CMakeLists.txt
|
|
||||||
@@ -4,6 +4,13 @@ list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
|
|
||||||
include(OpusPackageVersion)
|
|
||||||
get_package_version(PACKAGE_VERSION PROJECT_VERSION)
|
|
||||||
|
|
||||||
+# root project detection
|
|
||||||
+if(DEFINED PROJECT_NAME)
|
|
||||||
+ set(root_project OFF)
|
|
||||||
+else()
|
|
||||||
+ set(root_project ON)
|
|
||||||
+endif()
|
|
||||||
+
|
|
||||||
project(Opus LANGUAGES C VERSION ${PROJECT_VERSION})
|
|
||||||
|
|
||||||
include(OpusFunctions)
|
|
||||||
@@ -83,12 +90,16 @@ set(OPUS_DNN_FLOAT_DEBUG_HELP_STR "Run DNN computations as float for debugging p
|
|
||||||
option(OPUS_DNN_FLOAT_DEBUG ${OPUS_DNN_FLOAT_DEBUG_HELP_STR} OFF)
|
|
||||||
add_feature_info(OPUS_DNN_FLOAT_DEBUG OPUS_DNN_FLOAT_DEBUG ${OPUS_DNN_FLOAT_DEBUG_HELP_STR})
|
|
||||||
|
|
||||||
+set(OPUS_INSTALL_HELP_STR "Install Opus targets")
|
|
||||||
+option(OPUS_INSTALL ${OPUS_INSTALL_HELP_STR} ${root_project})
|
|
||||||
+add_feature_info(OPUS_INSTALL OPUS_INSTALL ${OPUS_INSTALL_HELP_STR})
|
|
||||||
+
|
|
||||||
set(OPUS_INSTALL_PKG_CONFIG_MODULE_HELP_STR "install pkg-config module.")
|
|
||||||
-option(OPUS_INSTALL_PKG_CONFIG_MODULE ${OPUS_INSTALL_PKG_CONFIG_MODULE_HELP_STR} ON)
|
|
||||||
+option(OPUS_INSTALL_PKG_CONFIG_MODULE ${OPUS_INSTALL_PKG_CONFIG_MODULE_HELP_STR} ${OPUS_INSTALL})
|
|
||||||
add_feature_info(OPUS_INSTALL_PKG_CONFIG_MODULE OPUS_INSTALL_PKG_CONFIG_MODULE ${OPUS_INSTALL_PKG_CONFIG_MODULE_HELP_STR})
|
|
||||||
|
|
||||||
set(OPUS_INSTALL_CMAKE_CONFIG_MODULE_HELP_STR "install CMake package config module.")
|
|
||||||
-option(OPUS_INSTALL_CMAKE_CONFIG_MODULE ${OPUS_INSTALL_CMAKE_CONFIG_MODULE_HELP_STR} ON)
|
|
||||||
+option(OPUS_INSTALL_CMAKE_CONFIG_MODULE ${OPUS_INSTALL_CMAKE_CONFIG_MODULE_HELP_STR} ${OPUS_INSTALL})
|
|
||||||
add_feature_info(OPUS_INSTALL_CMAKE_CONFIG_MODULE OPUS_INSTALL_CMAKE_CONFIG_MODULE ${OPUS_INSTALL_CMAKE_CONFIG_MODULE_HELP_STR})
|
|
||||||
|
|
||||||
set(OPUS_DRED_HELP_STR "enable DRED.")
|
|
||||||
@@ -613,53 +624,58 @@ if(OPUS_BUILD_FRAMEWORK)
|
|
||||||
OUTPUT_NAME Opus)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
-install(TARGETS opus
|
|
||||||
- EXPORT OpusTargets
|
|
||||||
- ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
|
||||||
- LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
|
||||||
- RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
|
||||||
- FRAMEWORK DESTINATION ${CMAKE_INSTALL_PREFIX}
|
|
||||||
- PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/opus)
|
|
||||||
-
|
|
||||||
-if(OPUS_INSTALL_PKG_CONFIG_MODULE)
|
|
||||||
- set(prefix ${CMAKE_INSTALL_PREFIX})
|
|
||||||
- set(exec_prefix ${CMAKE_INSTALL_PREFIX})
|
|
||||||
- set(libdir ${CMAKE_INSTALL_FULL_LIBDIR})
|
|
||||||
- set(includedir ${CMAKE_INSTALL_FULL_INCLUDEDIR})
|
|
||||||
- set(VERSION ${PACKAGE_VERSION})
|
|
||||||
- if(HAVE_LIBM)
|
|
||||||
- set(LIBM "-lm")
|
|
||||||
+if (OPUS_INSTALL)
|
|
||||||
+ install(TARGETS opus
|
|
||||||
+ EXPORT OpusTargets
|
|
||||||
+ ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
|
||||||
+ LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
|
||||||
+ RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
|
||||||
+ FRAMEWORK DESTINATION ${CMAKE_INSTALL_PREFIX}
|
|
||||||
+ PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/opus)
|
|
||||||
+
|
|
||||||
+ if(OPUS_INSTALL_PKG_CONFIG_MODULE)
|
|
||||||
+ set(prefix ${CMAKE_INSTALL_PREFIX})
|
|
||||||
+ set(exec_prefix ${CMAKE_INSTALL_PREFIX})
|
|
||||||
+ set(libdir ${CMAKE_INSTALL_FULL_LIBDIR})
|
|
||||||
+ set(includedir ${CMAKE_INSTALL_FULL_INCLUDEDIR})
|
|
||||||
+ set(VERSION ${PACKAGE_VERSION})
|
|
||||||
+ if(HAVE_LIBM)
|
|
||||||
+ set(LIBM "-lm")
|
|
||||||
+ endif()
|
|
||||||
+ configure_file(opus.pc.in opus.pc)
|
|
||||||
+ install(FILES ${CMAKE_CURRENT_BINARY_DIR}/opus.pc
|
|
||||||
+ DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)
|
|
||||||
+ endif()
|
|
||||||
+
|
|
||||||
+ if(OPUS_INSTALL_CMAKE_CONFIG_MODULE)
|
|
||||||
+ set(CPACK_GENERATOR TGZ)
|
|
||||||
+ include(CPack)
|
|
||||||
+ set(CMAKE_INSTALL_PACKAGEDIR ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME})
|
|
||||||
+ install(EXPORT OpusTargets
|
|
||||||
+ NAMESPACE Opus::
|
|
||||||
+ DESTINATION ${CMAKE_INSTALL_PACKAGEDIR})
|
|
||||||
+
|
|
||||||
+ include(CMakePackageConfigHelpers)
|
|
||||||
+
|
|
||||||
+ set(INCLUDE_INSTALL_DIR ${CMAKE_INSTALL_INCLUDEDIR})
|
|
||||||
+ configure_package_config_file(
|
|
||||||
+ ${PROJECT_SOURCE_DIR}/cmake/OpusConfig.cmake.in
|
|
||||||
+ OpusConfig.cmake
|
|
||||||
+ INSTALL_DESTINATION
|
|
||||||
+ ${CMAKE_INSTALL_PACKAGEDIR}
|
|
||||||
+ PATH_VARS
|
|
||||||
+ INCLUDE_INSTALL_DIR
|
|
||||||
+ INSTALL_PREFIX
|
|
||||||
+ ${CMAKE_INSTALL_PREFIX})
|
|
||||||
+
|
|
||||||
+ write_basic_package_version_file(OpusConfigVersion.cmake
|
|
||||||
+ VERSION ${PROJECT_VERSION}
|
|
||||||
+ COMPATIBILITY SameMajorVersion)
|
|
||||||
+
|
|
||||||
+ install(FILES ${CMAKE_CURRENT_BINARY_DIR}/OpusConfig.cmake
|
|
||||||
+ ${CMAKE_CURRENT_BINARY_DIR}/OpusConfigVersion.cmake
|
|
||||||
+ DESTINATION ${CMAKE_INSTALL_PACKAGEDIR})
|
|
||||||
endif()
|
|
||||||
- configure_file(opus.pc.in opus.pc)
|
|
||||||
- install(FILES ${CMAKE_CURRENT_BINARY_DIR}/opus.pc
|
|
||||||
- DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)
|
|
||||||
-endif()
|
|
||||||
-
|
|
||||||
-if(OPUS_INSTALL_CMAKE_CONFIG_MODULE)
|
|
||||||
- set(CPACK_GENERATOR TGZ)
|
|
||||||
- include(CPack)
|
|
||||||
- set(CMAKE_INSTALL_PACKAGEDIR ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME})
|
|
||||||
- install(EXPORT OpusTargets
|
|
||||||
- NAMESPACE Opus::
|
|
||||||
- DESTINATION ${CMAKE_INSTALL_PACKAGEDIR})
|
|
||||||
-
|
|
||||||
- include(CMakePackageConfigHelpers)
|
|
||||||
-
|
|
||||||
- set(INCLUDE_INSTALL_DIR ${CMAKE_INSTALL_INCLUDEDIR})
|
|
||||||
- configure_package_config_file(${PROJECT_SOURCE_DIR}/cmake/OpusConfig.cmake.in
|
|
||||||
- OpusConfig.cmake
|
|
||||||
- INSTALL_DESTINATION
|
|
||||||
- ${CMAKE_INSTALL_PACKAGEDIR}
|
|
||||||
- PATH_VARS
|
|
||||||
- INCLUDE_INSTALL_DIR
|
|
||||||
- INSTALL_PREFIX
|
|
||||||
- ${CMAKE_INSTALL_PREFIX})
|
|
||||||
- write_basic_package_version_file(OpusConfigVersion.cmake
|
|
||||||
- VERSION ${PROJECT_VERSION}
|
|
||||||
- COMPATIBILITY SameMajorVersion)
|
|
||||||
- install(FILES ${CMAKE_CURRENT_BINARY_DIR}/OpusConfig.cmake
|
|
||||||
- ${CMAKE_CURRENT_BINARY_DIR}/OpusConfigVersion.cmake
|
|
||||||
- DESTINATION ${CMAKE_INSTALL_PACKAGEDIR})
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(OPUS_BUILD_PROGRAMS)
|
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
--- a/source/effect_lexer.cpp
|
||||||
|
+++ b/source/effect_lexer.cpp
|
||||||
|
@@ -1181,7 +1181,7 @@
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
-#if 0
|
||||||
|
+#if !defined(__cpp_lib_to_chars)
|
||||||
|
exponent += decimal_location - mantissa_size;
|
||||||
|
|
||||||
|
const bool exponent_negative = exponent < 0;
|
||||||
+2
-15
@@ -85,6 +85,8 @@ option(ENABLE_WERROR "Enable -Werror diagnostics" ON)
|
|||||||
# Lossless Scaling frame generation. Only Android.
|
# Lossless Scaling frame generation. Only Android.
|
||||||
cmake_dependent_option(ENABLE_LSFG "Enable Lossless Scaling frame generation" ON "ANDROID" OFF)
|
cmake_dependent_option(ENABLE_LSFG "Enable Lossless Scaling frame generation" ON "ANDROID" OFF)
|
||||||
|
|
||||||
|
option(ENABLE_RESHADE "Enable ReShade FX post-processing effects" ON)
|
||||||
|
|
||||||
# non-linux bundled qt are static
|
# non-linux bundled qt are static
|
||||||
if (YUZU_USE_BUNDLED_QT AND (APPLE OR NOT UNIX))
|
if (YUZU_USE_BUNDLED_QT AND (APPLE OR NOT UNIX))
|
||||||
set(YUZU_STATIC_BUILD ON)
|
set(YUZU_STATIC_BUILD ON)
|
||||||
@@ -462,21 +464,6 @@ if (NOT YUZU_STATIC_ROOM)
|
|||||||
if (ZLIB_ADDED)
|
if (ZLIB_ADDED)
|
||||||
add_library(ZLIB::ZLIB ALIAS zlibstatic)
|
add_library(ZLIB::ZLIB ALIAS zlibstatic)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Opus
|
|
||||||
AddJsonPackage(opus)
|
|
||||||
|
|
||||||
if (Opus_ADDED)
|
|
||||||
if (MSVC AND CXX_CLANG)
|
|
||||||
target_compile_options(opus PRIVATE
|
|
||||||
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-implicit-function-declaration>
|
|
||||||
)
|
|
||||||
endif()
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if (NOT TARGET Opus::opus)
|
|
||||||
add_library(Opus::opus ALIAS opus)
|
|
||||||
endif()
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(NOT TARGET Boost::headers)
|
if(NOT TARGET Boost::headers)
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
# SPDX-FileCopyrightText: 2022 yuzu Emulator Project
|
|
||||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
find_package(PkgConfig QUIET)
|
|
||||||
pkg_search_module(OPUS QUIET IMPORTED_TARGET opus)
|
|
||||||
|
|
||||||
include(FindPackageHandleStandardArgs)
|
|
||||||
find_package_handle_standard_args(Opus
|
|
||||||
REQUIRED_VARS OPUS_LINK_LIBRARIES
|
|
||||||
VERSION_VAR OPUS_VERSION
|
|
||||||
)
|
|
||||||
|
|
||||||
if (MSYS2)
|
|
||||||
FixMsysPath(PkgConfig::OPUS)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if (Opus_FOUND AND NOT TARGET Opus::opus)
|
|
||||||
add_library(Opus::opus ALIAS PkgConfig::OPUS)
|
|
||||||
endif()
|
|
||||||
+9
-16
@@ -82,7 +82,7 @@
|
|||||||
"name": "ffmpeg",
|
"name": "ffmpeg",
|
||||||
"package": "FFmpeg",
|
"package": "FFmpeg",
|
||||||
"repo": "crueter-ci/FFmpeg",
|
"repo": "crueter-ci/FFmpeg",
|
||||||
"version": "9.0.1-1788120736-bf1b838f2a"
|
"version": "9.0.1-1788303113-bf1b838f2a"
|
||||||
},
|
},
|
||||||
"fmt": {
|
"fmt": {
|
||||||
"hash": "f0da82c545b01692e9fd30fdfb613dbb8dd9716983dcd0ff19ac2a8d36f74beb5540ef38072fdecc1e34191b3682a8542ecbf3a61ef287dbba0a2679d4e023f2",
|
"hash": "f0da82c545b01692e9fd30fdfb613dbb8dd9716983dcd0ff19ac2a8d36f74beb5540ef38072fdecc1e34191b3682a8542ecbf3a61ef287dbba0a2679d4e023f2",
|
||||||
@@ -210,21 +210,6 @@
|
|||||||
"repo": "jimmy-park/openssl-cmake",
|
"repo": "jimmy-park/openssl-cmake",
|
||||||
"version": "3.6.2"
|
"version": "3.6.2"
|
||||||
},
|
},
|
||||||
"opus": {
|
|
||||||
"find_args": "MODULE",
|
|
||||||
"hash": "9506147b0de35befda8633ff272981cc2575c860874791bd455b752f797fd7dbd1079f0ba42ccdd7bb1fe6773fa5e84b3d75667c2883dd1fb2d0e4a5fa4f8387",
|
|
||||||
"min_version": "1.3",
|
|
||||||
"options": [
|
|
||||||
"OPUS_PRESUME_NEON ON"
|
|
||||||
],
|
|
||||||
"package": "Opus",
|
|
||||||
"patches": [
|
|
||||||
"0001-disable-clang-runtime-neon.patch",
|
|
||||||
"0002-no-install.patch"
|
|
||||||
],
|
|
||||||
"repo": "xiph/opus",
|
|
||||||
"version": "a3f0ec02b3"
|
|
||||||
},
|
|
||||||
"quazip": {
|
"quazip": {
|
||||||
"hash": "609c240c7f029ac26a37d8fbab51bc16284e05e128b78b9b9c0e95d083538c36047a67d682759ac990e4adb0eeb90f04f1ea7fe2253bbda7e7e3bcce32e53dd8",
|
"hash": "609c240c7f029ac26a37d8fbab51bc16284e05e128b78b9b9c0e95d083538c36047a67d682759ac990e4adb0eeb90f04f1ea7fe2253bbda7e7e3bcce32e53dd8",
|
||||||
"min_version": "1.3",
|
"min_version": "1.3",
|
||||||
@@ -238,6 +223,14 @@
|
|||||||
"repo": "stachenov/quazip",
|
"repo": "stachenov/quazip",
|
||||||
"version": "2e95c9001b"
|
"version": "2e95c9001b"
|
||||||
},
|
},
|
||||||
|
"reshade": {
|
||||||
|
"hash": "6fc9b39821ca6e2d91160f05846b3a42159fec759f5d6fba91d1a57801a1bdaf6047740578c136f0b1a60a9c29ad160d87dbfe0dbfbcdf6ea879a8a078377b8c",
|
||||||
|
"patches": [
|
||||||
|
"0001-from-chars-fallback.patch"
|
||||||
|
],
|
||||||
|
"repo": "crosire/reshade",
|
||||||
|
"version": "v6.8.0"
|
||||||
|
},
|
||||||
"sdl3": {
|
"sdl3": {
|
||||||
"hash": "df5a323af7ac366661a3c0e887969c72584d232f3cc211419d59b0487b620b6b2859d4549c9e8df002ee489290062e466fcfddf7edc0872a37b1f2845e81c0f3",
|
"hash": "df5a323af7ac366661a3c0e887969c72584d232f3cc211419d59b0487b620b6b2859d4549c9e8df002ee489290062e466fcfddf7edc0872a37b1f2845e81c0f3",
|
||||||
"min_version": "3.2.10",
|
"min_version": "3.2.10",
|
||||||
|
|||||||
Vendored
+3
@@ -0,0 +1,3 @@
|
|||||||
|
name=Anime
|
||||||
|
description=Cel shaded look: the picture is cleaned first, then the lighting is collapsed into flat bands with inked edges, finished with a small glow.
|
||||||
|
chain=Denoise.fx|Denoise|Strength=0.18,Radius=1.2;CelShading.fx|CelShading|Bands=5,BandContrast=0.35,BandEdge=0.2,Detail=0.7,OutlineStrength=0.7,OutlineThreshold=0.2,Saturation=1.3;Bloom.fx|Bloom|Radius=1.2,Amount=0.25
|
||||||
Vendored
+3
@@ -0,0 +1,3 @@
|
|||||||
|
name=Cinematic
|
||||||
|
description=Film look: soft glow on the highlights, a filmic contrast curve, cool shadows against warm highlights, fine grain and a gentle vignette.
|
||||||
|
chain=Bloom.fx|Bloom|Radius=1.6,Amount=0.35;FilmicCurve.fx|FilmicCurve|Toe=1.35,Shoulder=1.3,Amount=0.85;SplitToning.fx|SplitToning|ShadowHue=210,ShadowStrength=0.25,HighlightHue=38,HighlightStrength=0.3;FilmGrain.fx|FilmGrain|Intensity=0.022;Vignette.fx|Vignette|Strength=0.5
|
||||||
Vendored
+3
@@ -0,0 +1,3 @@
|
|||||||
|
name=Clean
|
||||||
|
description=Cleans up a low internal resolution: removes dithering and compression noise, smooths banded skies and brings edge detail back. The safe starting point if you are not sure what you want.
|
||||||
|
chain=Denoise.fx|Denoise|Strength=0.14,Curve=1.2;Deband.fx|Deband|;Sharpen.fx|Sharpen|Amount=0.75
|
||||||
Vendored
+3
@@ -0,0 +1,3 @@
|
|||||||
|
name=Dreamy
|
||||||
|
description=Soft focus: the centre of the screen stays sharp while everything around it blurs, with a wide glow over the highlights. Good for slower games and cutscenes.
|
||||||
|
chain=Blur.fx|Blur|Radius=6,Strength=0.85,FocusSize=0.28,FocusSoftness=0.5;Bloom.fx|Bloom|Radius=2.0,Amount=0.55
|
||||||
Vendored
+3
@@ -0,0 +1,3 @@
|
|||||||
|
name=Retro
|
||||||
|
description=Old television look: colour fringing at the edges of the screen, curved scanlines with a phosphor mask, and darkened corners. The scanline roll is switched off so it does not crawl.
|
||||||
|
chain=ChromaticAberration.fx|ChromaticAberration|Strength=1.0;CRT.fx|CRT|RollSpeed=0,Bleed=1.2;Vignette.fx|Vignette|Strength=0.7
|
||||||
Vendored
+3
@@ -0,0 +1,3 @@
|
|||||||
|
name=Vivid
|
||||||
|
description=Punchier colour without stylising the image: warmer and more saturated, blacks and whites pushed to the ends of the range, and a light sharpen.
|
||||||
|
chain=NaturalColors.fx|NaturalColors|Luma=1.15,Chroma=1.35;Levels.fx|Levels|InputBlack=0.02,InputWhite=0.98;Sharpen.fx|Sharpen|Amount=0.5
|
||||||
Vendored
+70
@@ -0,0 +1,70 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2012 PPSSPP Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
// Ported from bloomnoblur.fsh in PPSSPP.
|
||||||
|
|
||||||
|
texture BackBufferTex : COLOR;
|
||||||
|
sampler BackBuffer { Texture = BackBufferTex; };
|
||||||
|
|
||||||
|
uniform float Radius <
|
||||||
|
ui_type = "slider";
|
||||||
|
ui_label = "Radius";
|
||||||
|
ui_tooltip = "How far the glow spreads from bright areas.";
|
||||||
|
ui_min = 0.0; ui_max = 4.0; ui_step = 0.05;
|
||||||
|
> = 1.0;
|
||||||
|
|
||||||
|
uniform float Amount <
|
||||||
|
ui_type = "slider";
|
||||||
|
ui_label = "Amount";
|
||||||
|
ui_tooltip = "Strength of the glow added on top of the image.";
|
||||||
|
ui_min = 0.0; ui_max = 2.0; ui_step = 0.05;
|
||||||
|
> = 0.6;
|
||||||
|
|
||||||
|
void VS_PostProcess(in uint id : SV_VertexID, out float4 pos : SV_Position, out float2 uv : TEXCOORD)
|
||||||
|
{
|
||||||
|
uv = float2(float(id & 2), float((id & 1) << 1));
|
||||||
|
pos = float4(uv * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
float Weight(float3 color)
|
||||||
|
{
|
||||||
|
float gray = (color.r + color.g + color.b) / 3.0;
|
||||||
|
float saturation = (abs(color.r - gray) + abs(color.g - gray) + abs(color.b - gray)) / 3.0;
|
||||||
|
return gray * gray / max(saturation, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
float4 PS_Bloom(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
|
||||||
|
{
|
||||||
|
float3 color = tex2D(BackBuffer, uv).rgb;
|
||||||
|
|
||||||
|
float gray = (color.r + color.g + color.b) / 3.0;
|
||||||
|
float saturation = (abs(color.r - gray) + abs(color.g - gray) + abs(color.b - gray)) / 3.0;
|
||||||
|
float spread = 0.002 * gray / max(saturation, 0.25) * Radius;
|
||||||
|
|
||||||
|
float3 sum = float3(0.0, 0.0, 0.0);
|
||||||
|
[unroll] for (int x = -3; x <= 3; x += 2)
|
||||||
|
{
|
||||||
|
[unroll] for (int y = -3; y <= 3; y += 2)
|
||||||
|
{
|
||||||
|
float3 tap = tex2D(BackBuffer, uv + float2(x, y) * spread).rgb;
|
||||||
|
sum += tap * Weight(tap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sum /= 16.0;
|
||||||
|
|
||||||
|
return float4(saturate(color + sum * Amount), 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
technique Bloom <
|
||||||
|
ui_label = "Bloom";
|
||||||
|
ui_tooltip = "Bleeds light out of the brightest areas into a soft halo, the way a camera does when pointed at something too bright.";
|
||||||
|
>
|
||||||
|
{
|
||||||
|
pass
|
||||||
|
{
|
||||||
|
VertexShader = VS_PostProcess;
|
||||||
|
PixelShader = PS_Bloom;
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+87
@@ -0,0 +1,87 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
texture BackBufferTex : COLOR;
|
||||||
|
sampler BackBuffer { Texture = BackBufferTex; };
|
||||||
|
|
||||||
|
uniform float Radius <
|
||||||
|
ui_type = "slider";
|
||||||
|
ui_label = "Radius";
|
||||||
|
ui_tooltip = "How far the blur reaches, in pixels.";
|
||||||
|
ui_min = 1.0; ui_max = 12.0; ui_step = 0.5;
|
||||||
|
> = 4.0;
|
||||||
|
|
||||||
|
uniform float Strength <
|
||||||
|
ui_type = "slider";
|
||||||
|
ui_label = "Strength";
|
||||||
|
ui_tooltip = "Blend between the original image and the blurred one.";
|
||||||
|
ui_min = 0.0; ui_max = 1.0; ui_step = 0.05;
|
||||||
|
> = 1.0;
|
||||||
|
|
||||||
|
uniform float FocusSize <
|
||||||
|
ui_type = "slider";
|
||||||
|
ui_label = "Sharp Centre";
|
||||||
|
ui_tooltip = "Size of the region left in focus at the centre of the screen. At zero the whole image is blurred evenly.";
|
||||||
|
ui_min = 0.0; ui_max = 1.0; ui_step = 0.05;
|
||||||
|
> = 0.0;
|
||||||
|
|
||||||
|
uniform float FocusSoftness <
|
||||||
|
ui_type = "slider";
|
||||||
|
ui_label = "Focus Falloff";
|
||||||
|
ui_tooltip = "How gradually the sharp centre gives way to the blur.";
|
||||||
|
ui_min = 0.05; ui_max = 1.0; ui_step = 0.05;
|
||||||
|
> = 0.4;
|
||||||
|
|
||||||
|
static const int TAPS = 17;
|
||||||
|
|
||||||
|
void VS_PostProcess(in uint id : SV_VertexID, out float4 pos : SV_Position, out float2 uv : TEXCOORD)
|
||||||
|
{
|
||||||
|
uv = float2(float(id & 2), float((id & 1) << 1));
|
||||||
|
pos = float4(uv * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
float4 PS_Blur(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
|
||||||
|
{
|
||||||
|
float2 texel = float2(BUFFER_RCP_WIDTH, BUFFER_RCP_HEIGHT);
|
||||||
|
float3 original = tex2D(BackBuffer, uv).rgb;
|
||||||
|
|
||||||
|
float2 centred = (uv - 0.5) * float2(BUFFER_WIDTH * BUFFER_RCP_HEIGHT, 1.0) * 2.0;
|
||||||
|
float distance_from_centre = length(centred);
|
||||||
|
float no_focus = 1.0 - step(0.001, FocusSize);
|
||||||
|
float focus = max(smoothstep(FocusSize, FocusSize + FocusSoftness, distance_from_centre), no_focus);
|
||||||
|
float amount = Strength * focus;
|
||||||
|
|
||||||
|
float angle = frac(sin(dot(pos.xy, float2(12.9898, 78.233))) * 43758.5453) * 6.2831853;
|
||||||
|
float2 spoke = float2(cos(angle), sin(angle));
|
||||||
|
|
||||||
|
const float2 turn = float2(cos(2.39996323), sin(2.39996323));
|
||||||
|
const float decay = exp(-2.0 / float(TAPS));
|
||||||
|
float weight = exp(-1.0 / float(TAPS));
|
||||||
|
|
||||||
|
float3 sum = float3(0.0, 0.0, 0.0);
|
||||||
|
float total = 0.0;
|
||||||
|
[unroll] for (int i = 0; i < TAPS; ++i)
|
||||||
|
{
|
||||||
|
float reach = sqrt((float(i) + 0.5) / float(TAPS));
|
||||||
|
sum += tex2D(BackBuffer, uv + spoke * reach * texel * Radius).rgb * weight;
|
||||||
|
total += weight;
|
||||||
|
weight *= decay;
|
||||||
|
spoke = float2(spoke.x * turn.x - spoke.y * turn.y,
|
||||||
|
spoke.x * turn.y + spoke.y * turn.x);
|
||||||
|
}
|
||||||
|
|
||||||
|
float3 blurred = sum / total;
|
||||||
|
return float4(lerp(original, blurred, amount), 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
technique Blur <
|
||||||
|
ui_label = "Blur";
|
||||||
|
ui_tooltip = "Gaussian blur with an optional sharp centre, for softening the picture or faking depth of field.";
|
||||||
|
>
|
||||||
|
{
|
||||||
|
pass
|
||||||
|
{
|
||||||
|
VertexShader = VS_PostProcess;
|
||||||
|
PixelShader = PS_Blur;
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+74
@@ -0,0 +1,74 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2012 PPSSPP Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
// Ported from crt.fsh in PPSSPP, by KillaMaaki.
|
||||||
|
|
||||||
|
|
||||||
|
texture BackBufferTex : COLOR;
|
||||||
|
sampler BackBuffer { Texture = BackBufferTex; };
|
||||||
|
|
||||||
|
uniform float Timer < source = "timer"; > = 0.0;
|
||||||
|
|
||||||
|
uniform float Density <
|
||||||
|
ui_type = "slider";
|
||||||
|
ui_label = "Line Density";
|
||||||
|
ui_min = 60.0; ui_max = 720.0; ui_step = 10.0;
|
||||||
|
> = 272.0;
|
||||||
|
|
||||||
|
uniform float RollSpeed <
|
||||||
|
ui_type = "slider";
|
||||||
|
ui_label = "Roll Speed";
|
||||||
|
ui_tooltip = "Speed of the rolling bar. Zero disables it.";
|
||||||
|
ui_min = 0.0; ui_max = 2.0; ui_step = 0.05;
|
||||||
|
> = 1.0;
|
||||||
|
|
||||||
|
uniform float Bleed <
|
||||||
|
ui_type = "slider";
|
||||||
|
ui_label = "Colour Bleed";
|
||||||
|
ui_tooltip = "Horizontal separation of the red and green channels.";
|
||||||
|
ui_min = 0.0; ui_max = 4.0; ui_step = 0.1;
|
||||||
|
> = 1.0;
|
||||||
|
|
||||||
|
void VS_PostProcess(in uint id : SV_VertexID, out float4 pos : SV_Position, out float2 uv : TEXCOORD)
|
||||||
|
{
|
||||||
|
uv = float2(float(id & 2), float((id & 1) << 1));
|
||||||
|
pos = float4(uv * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
float4 PS_CRT(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
|
||||||
|
{
|
||||||
|
float seconds = Timer * 0.001;
|
||||||
|
float scan = floor((uv.y + seconds * RollSpeed * 0.5) * Density);
|
||||||
|
float line_intensity = frac(scan * 0.5) * 2.0;
|
||||||
|
|
||||||
|
float2 shift = float2(line_intensity * 0.0005, 0.0);
|
||||||
|
float2 bleed = float2(BUFFER_RCP_WIDTH * Bleed, 0.0);
|
||||||
|
|
||||||
|
float r = tex2D(BackBuffer, uv + bleed + shift).r;
|
||||||
|
float g = tex2D(BackBuffer, uv - bleed + shift).g;
|
||||||
|
float b = tex2D(BackBuffer, uv).b;
|
||||||
|
|
||||||
|
float3 color = float3(r, g * 0.99, b) * clamp(line_intensity, 0.85, 1.0);
|
||||||
|
|
||||||
|
if (RollSpeed > 0.0)
|
||||||
|
{
|
||||||
|
float rollbar = sin((uv.y + seconds * RollSpeed) * 4.0);
|
||||||
|
color += rollbar * 0.02;
|
||||||
|
}
|
||||||
|
|
||||||
|
return float4(saturate(color), 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
technique CRT <
|
||||||
|
ui_label = "CRT";
|
||||||
|
ui_tooltip = "Curved scanlines and the phosphor mask of a CRT television, for games that were drawn with that kind of screen in mind.";
|
||||||
|
>
|
||||||
|
{
|
||||||
|
pass
|
||||||
|
{
|
||||||
|
VertexShader = VS_PostProcess;
|
||||||
|
PixelShader = PS_CRT;
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+79
@@ -0,0 +1,79 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2012 PPSSPP Project
|
||||||
|
// SPDX-FileCopyrightText: guest(r)
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
// Ported from cartoon.fsh in PPSSPP, Advanced Cartoon shader I by guest(r).
|
||||||
|
|
||||||
|
texture BackBufferTex : COLOR;
|
||||||
|
sampler BackBuffer { Texture = BackBufferTex; };
|
||||||
|
|
||||||
|
uniform float EdgeStrength <
|
||||||
|
ui_type = "slider";
|
||||||
|
ui_label = "Edge Strength";
|
||||||
|
ui_tooltip = "Darkness of the ink outline drawn around detected edges.";
|
||||||
|
ui_min = 0.0; ui_max = 2.0; ui_step = 0.05;
|
||||||
|
> = 0.5;
|
||||||
|
|
||||||
|
uniform float Levels <
|
||||||
|
ui_type = "slider";
|
||||||
|
ui_label = "Colour Levels";
|
||||||
|
ui_tooltip = "How many bands the colours are quantised into.";
|
||||||
|
ui_min = 2.0; ui_max = 16.0; ui_step = 1.0;
|
||||||
|
> = 4.0;
|
||||||
|
|
||||||
|
void VS_PostProcess(in uint id : SV_VertexID, out float4 pos : SV_Position, out float2 uv : TEXCOORD)
|
||||||
|
{
|
||||||
|
uv = float2(float(id & 2), float((id & 1) << 1));
|
||||||
|
pos = float4(uv * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
float4 PS_Cartoon(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
|
||||||
|
{
|
||||||
|
float2 texel = float2(BUFFER_RCP_WIDTH, BUFFER_RCP_HEIGHT);
|
||||||
|
|
||||||
|
float3 c00 = tex2D(BackBuffer, uv + texel * float2(-1.0, -1.0)).rgb;
|
||||||
|
float3 c10 = tex2D(BackBuffer, uv + texel * float2( 0.0, -1.0)).rgb;
|
||||||
|
float3 c20 = tex2D(BackBuffer, uv + texel * float2( 1.0, -1.0)).rgb;
|
||||||
|
float3 c01 = tex2D(BackBuffer, uv + texel * float2(-1.0, 0.0)).rgb;
|
||||||
|
float3 c11 = tex2D(BackBuffer, uv).rgb;
|
||||||
|
float3 c21 = tex2D(BackBuffer, uv + texel * float2( 1.0, 0.0)).rgb;
|
||||||
|
float3 c02 = tex2D(BackBuffer, uv + texel * float2(-1.0, 1.0)).rgb;
|
||||||
|
float3 c12 = tex2D(BackBuffer, uv + texel * float2( 0.0, 1.0)).rgb;
|
||||||
|
float3 c22 = tex2D(BackBuffer, uv + texel * float2( 1.0, 1.0)).rgb;
|
||||||
|
|
||||||
|
const float3 dt = float3(1.0, 1.0, 1.0);
|
||||||
|
|
||||||
|
float d1 = dot(abs(c00 - c22), dt);
|
||||||
|
float d2 = dot(abs(c20 - c02), dt);
|
||||||
|
float hl = dot(abs(c01 - c21), dt);
|
||||||
|
float vl = dot(abs(c10 - c12), dt);
|
||||||
|
float edge = EdgeStrength * (d1 + d2 + hl + vl) / (dot(c11, dt) + 0.15);
|
||||||
|
|
||||||
|
float lc = Levels * length(c11);
|
||||||
|
float f = frac(lc);
|
||||||
|
f *= f;
|
||||||
|
lc = (floor(lc) + f * f) / Levels + 0.05;
|
||||||
|
|
||||||
|
float3 unit = normalize(max(c11, 0.0001));
|
||||||
|
float3 quant = Levels * unit;
|
||||||
|
float3 frct = frac(quant);
|
||||||
|
frct *= frct;
|
||||||
|
quant = floor(quant) + 0.05 * dt + frct * frct;
|
||||||
|
|
||||||
|
float3 color = lc * (1.1 - edge * sqrt(edge)) * quant / Levels;
|
||||||
|
return float4(saturate(color), 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
technique Cartoon <
|
||||||
|
ui_label = "Cartoon";
|
||||||
|
ui_tooltip = "Ink outlines and flat colour bands. Faithful port of the PPSSPP shader; the outline gets heavy in dark scenes, so try Cartoon Soft or Cel Shading if the blacks smear.";
|
||||||
|
>
|
||||||
|
{
|
||||||
|
pass
|
||||||
|
{
|
||||||
|
VertexShader = VS_PostProcess;
|
||||||
|
PixelShader = PS_Cartoon;
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+100
@@ -0,0 +1,100 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2012 PPSSPP Project
|
||||||
|
// SPDX-FileCopyrightText: guest(r)
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
texture BackBufferTex : COLOR;
|
||||||
|
sampler BackBuffer { Texture = BackBufferTex; };
|
||||||
|
|
||||||
|
uniform float EdgeStrength <
|
||||||
|
ui_type = "slider";
|
||||||
|
ui_label = "Edge Strength";
|
||||||
|
ui_tooltip = "Darkness of the ink outline drawn around detected edges.";
|
||||||
|
ui_min = 0.0; ui_max = 2.0; ui_step = 0.05;
|
||||||
|
> = 0.45;
|
||||||
|
|
||||||
|
uniform float ShadowGuard <
|
||||||
|
ui_type = "slider";
|
||||||
|
ui_label = "Shadow Guard";
|
||||||
|
ui_tooltip = "Holds the outline back in dark areas. Raise it if shadows turn into black blobs.";
|
||||||
|
ui_min = 0.1; ui_max = 2.0; ui_step = 0.05;
|
||||||
|
> = 0.8;
|
||||||
|
|
||||||
|
uniform float Levels <
|
||||||
|
ui_type = "slider";
|
||||||
|
ui_label = "Colour Levels";
|
||||||
|
ui_tooltip = "How many bands the colours are quantised into.";
|
||||||
|
ui_min = 2.0; ui_max = 16.0; ui_step = 1.0;
|
||||||
|
> = 6.0;
|
||||||
|
|
||||||
|
uniform float Smoothing <
|
||||||
|
ui_type = "slider";
|
||||||
|
ui_label = "Banding";
|
||||||
|
ui_tooltip = "How far the picture is pushed towards flat bands.";
|
||||||
|
ui_min = 0.0; ui_max = 1.0; ui_step = 0.05;
|
||||||
|
> = 0.75;
|
||||||
|
|
||||||
|
uniform float Saturation <
|
||||||
|
ui_type = "slider";
|
||||||
|
ui_label = "Saturation";
|
||||||
|
ui_min = 0.0; ui_max = 2.0; ui_step = 0.05;
|
||||||
|
> = 1.15;
|
||||||
|
|
||||||
|
void VS_PostProcess(in uint id : SV_VertexID, out float4 pos : SV_Position, out float2 uv : TEXCOORD)
|
||||||
|
{
|
||||||
|
uv = float2(float(id & 2), float((id & 1) << 1));
|
||||||
|
pos = float4(uv * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
float4 PS_CartoonSoft(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
|
||||||
|
{
|
||||||
|
float2 texel = float2(BUFFER_RCP_WIDTH, BUFFER_RCP_HEIGHT);
|
||||||
|
|
||||||
|
float3 c00 = tex2D(BackBuffer, uv + texel * float2(-1.0, -1.0)).rgb;
|
||||||
|
float3 c10 = tex2D(BackBuffer, uv + texel * float2( 0.0, -1.0)).rgb;
|
||||||
|
float3 c20 = tex2D(BackBuffer, uv + texel * float2( 1.0, -1.0)).rgb;
|
||||||
|
float3 c01 = tex2D(BackBuffer, uv + texel * float2(-1.0, 0.0)).rgb;
|
||||||
|
float3 c11 = tex2D(BackBuffer, uv).rgb;
|
||||||
|
float3 c21 = tex2D(BackBuffer, uv + texel * float2( 1.0, 0.0)).rgb;
|
||||||
|
float3 c02 = tex2D(BackBuffer, uv + texel * float2(-1.0, 1.0)).rgb;
|
||||||
|
float3 c12 = tex2D(BackBuffer, uv + texel * float2( 0.0, 1.0)).rgb;
|
||||||
|
float3 c22 = tex2D(BackBuffer, uv + texel * float2( 1.0, 1.0)).rgb;
|
||||||
|
|
||||||
|
const float3 dt = float3(1.0, 1.0, 1.0);
|
||||||
|
const float3 luma_weights = float3(0.299, 0.587, 0.114);
|
||||||
|
|
||||||
|
float d1 = dot(abs(c00 - c22), dt);
|
||||||
|
float d2 = dot(abs(c20 - c02), dt);
|
||||||
|
float hl = dot(abs(c01 - c21), dt);
|
||||||
|
float vl = dot(abs(c10 - c12), dt);
|
||||||
|
|
||||||
|
float luma = dot(c11, luma_weights);
|
||||||
|
float response = (d1 + d2 + hl + vl) / (luma * 2.0 + ShadowGuard);
|
||||||
|
float ink = 1.0 - saturate(response * EdgeStrength);
|
||||||
|
|
||||||
|
float scaled = luma * Levels;
|
||||||
|
float step_position = frac(scaled);
|
||||||
|
float eased = step_position * step_position * (3.0 - 2.0 * step_position);
|
||||||
|
float banded = (floor(scaled) + eased) / Levels;
|
||||||
|
float target = lerp(luma, banded, Smoothing);
|
||||||
|
|
||||||
|
float3 tinted = c11 * (target / max(luma, 0.001));
|
||||||
|
float3 grey = dot(tinted, luma_weights);
|
||||||
|
float3 color = lerp(grey, tinted, Saturation) * ink;
|
||||||
|
|
||||||
|
return float4(saturate(color), 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
technique CartoonSoft <
|
||||||
|
ui_label = "Cartoon Soft";
|
||||||
|
ui_tooltip = "Ink outlines and flat colour bands, with the outline held back in the shadows so dark scenes do not turn into black blobs.";
|
||||||
|
>
|
||||||
|
{
|
||||||
|
pass
|
||||||
|
{
|
||||||
|
VertexShader = VS_PostProcess;
|
||||||
|
PixelShader = PS_CartoonSoft;
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+120
@@ -0,0 +1,120 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
texture BackBufferTex : COLOR;
|
||||||
|
sampler BackBuffer { Texture = BackBufferTex; };
|
||||||
|
|
||||||
|
uniform float Bands <
|
||||||
|
ui_type = "slider";
|
||||||
|
ui_label = "Shading Bands";
|
||||||
|
ui_tooltip = "How many flat steps the lighting is collapsed into. Three or four gives the classic look.";
|
||||||
|
ui_min = 2.0; ui_max = 8.0; ui_step = 1.0;
|
||||||
|
> = 4.0;
|
||||||
|
|
||||||
|
uniform float BandContrast <
|
||||||
|
ui_type = "slider";
|
||||||
|
ui_label = "Band Contrast";
|
||||||
|
ui_tooltip = "Spreads the bands towards pure black and white. Lower it if the shadows crush.";
|
||||||
|
ui_min = 0.0; ui_max = 1.0; ui_step = 0.05;
|
||||||
|
> = 0.5;
|
||||||
|
|
||||||
|
uniform float BandEdge <
|
||||||
|
ui_type = "slider";
|
||||||
|
ui_label = "Band Edge";
|
||||||
|
ui_tooltip = "Width of the transition between bands. Low values give hard cel steps.";
|
||||||
|
ui_min = 0.02; ui_max = 1.0; ui_step = 0.02;
|
||||||
|
> = 0.15;
|
||||||
|
|
||||||
|
uniform float Detail <
|
||||||
|
ui_type = "slider";
|
||||||
|
ui_label = "Texture Detail";
|
||||||
|
ui_tooltip = "How much surface texture survives the flattening. At zero the bands are completely flat.";
|
||||||
|
ui_min = 0.0; ui_max = 1.0; ui_step = 0.05;
|
||||||
|
> = 0.6;
|
||||||
|
|
||||||
|
uniform float OutlineStrength <
|
||||||
|
ui_type = "slider";
|
||||||
|
ui_label = "Outline Strength";
|
||||||
|
ui_tooltip = "Opacity of the ink line drawn along detected edges.";
|
||||||
|
ui_min = 0.0; ui_max = 1.0; ui_step = 0.05;
|
||||||
|
> = 0.8;
|
||||||
|
|
||||||
|
uniform float OutlineThreshold <
|
||||||
|
ui_type = "slider";
|
||||||
|
ui_label = "Outline Threshold";
|
||||||
|
ui_tooltip = "How strong an edge has to be before it is inked. Raise it to keep ink off textures and specular highlights.";
|
||||||
|
ui_min = 0.01; ui_max = 0.4; ui_step = 0.01;
|
||||||
|
> = 0.18;
|
||||||
|
|
||||||
|
uniform float Saturation <
|
||||||
|
ui_type = "slider";
|
||||||
|
ui_label = "Saturation";
|
||||||
|
ui_tooltip = "Colour intensity of the flat regions.";
|
||||||
|
ui_min = 0.0; ui_max = 2.0; ui_step = 0.05;
|
||||||
|
> = 1.25;
|
||||||
|
|
||||||
|
void VS_PostProcess(in uint id : SV_VertexID, out float4 pos : SV_Position, out float2 uv : TEXCOORD)
|
||||||
|
{
|
||||||
|
uv = float2(float(id & 2), float((id & 1) << 1));
|
||||||
|
pos = float4(uv * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
float4 PS_CelShading(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
|
||||||
|
{
|
||||||
|
const float3 luma_weights = float3(0.2126, 0.7152, 0.0722);
|
||||||
|
float2 texel = float2(BUFFER_RCP_WIDTH, BUFFER_RCP_HEIGHT);
|
||||||
|
|
||||||
|
float3 centre = tex2D(BackBuffer, uv).rgb;
|
||||||
|
float luma = dot(centre, luma_weights);
|
||||||
|
|
||||||
|
float2 near = texel * 1.5;
|
||||||
|
float2 far = texel * 3.5;
|
||||||
|
|
||||||
|
float a00 = dot(tex2D(BackBuffer, uv + near * float2(-1.0, -1.0)).rgb, luma_weights);
|
||||||
|
float a20 = dot(tex2D(BackBuffer, uv + near * float2( 1.0, -1.0)).rgb, luma_weights);
|
||||||
|
float a02 = dot(tex2D(BackBuffer, uv + near * float2(-1.0, 1.0)).rgb, luma_weights);
|
||||||
|
float a22 = dot(tex2D(BackBuffer, uv + near * float2( 1.0, 1.0)).rgb, luma_weights);
|
||||||
|
|
||||||
|
float b00 = dot(tex2D(BackBuffer, uv + far * float2(-1.0, -1.0)).rgb, luma_weights);
|
||||||
|
float b20 = dot(tex2D(BackBuffer, uv + far * float2( 1.0, -1.0)).rgb, luma_weights);
|
||||||
|
float b02 = dot(tex2D(BackBuffer, uv + far * float2(-1.0, 1.0)).rgb, luma_weights);
|
||||||
|
float b22 = dot(tex2D(BackBuffer, uv + far * float2( 1.0, 1.0)).rgb, luma_weights);
|
||||||
|
|
||||||
|
float gx = (a00 + a02) - (a20 + a22);
|
||||||
|
float gy = (a00 + a20) - (a02 + a22);
|
||||||
|
float gradient = sqrt(gx * gx + gy * gy);
|
||||||
|
float ink = smoothstep(OutlineThreshold, OutlineThreshold + 0.04, gradient);
|
||||||
|
|
||||||
|
float lighting = (a00 + a20 + a02 + a22 + b00 + b20 + b02 + b22) * 0.125;
|
||||||
|
float detail = luma - lighting;
|
||||||
|
|
||||||
|
float softness = max(BandEdge, 0.001);
|
||||||
|
float coord = lighting * Bands - softness * 0.5;
|
||||||
|
float index = floor(coord) + smoothstep(1.0 - softness, 1.0, frac(coord));
|
||||||
|
|
||||||
|
float centred = (index + 0.5) / Bands;
|
||||||
|
float stretched = index / max(Bands - 1.0, 1.0);
|
||||||
|
float banded = saturate(lerp(centred, stretched, BandContrast));
|
||||||
|
|
||||||
|
float target = saturate(banded + detail * Detail);
|
||||||
|
|
||||||
|
float gain = min(target / max(luma, 0.001), 4.0);
|
||||||
|
float3 shaded = centre * gain;
|
||||||
|
float3 grey = dot(shaded, luma_weights);
|
||||||
|
float3 color = lerp(grey, shaded, Saturation);
|
||||||
|
color *= 1.0 - ink * OutlineStrength;
|
||||||
|
|
||||||
|
return float4(saturate(color), 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
technique CelShading <
|
||||||
|
ui_label = "Cel Shading";
|
||||||
|
ui_tooltip = "Collapses the lighting into a few hard steps and inks the edges, keeping texture detail and hue intact.";
|
||||||
|
>
|
||||||
|
{
|
||||||
|
pass
|
||||||
|
{
|
||||||
|
VertexShader = VS_PostProcess;
|
||||||
|
PixelShader = PS_CelShading;
|
||||||
|
}
|
||||||
|
}
|
||||||
+53
@@ -0,0 +1,53 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
texture BackBufferTex : COLOR;
|
||||||
|
sampler BackBuffer { Texture = BackBufferTex; };
|
||||||
|
|
||||||
|
uniform float Strength <
|
||||||
|
ui_type = "slider";
|
||||||
|
ui_label = "Strength";
|
||||||
|
ui_tooltip = "Channel separation in pixels, measured at the edge of the screen.";
|
||||||
|
ui_min = 0.0; ui_max = 8.0; ui_step = 0.1;
|
||||||
|
> = 1.5;
|
||||||
|
|
||||||
|
uniform float Falloff <
|
||||||
|
ui_type = "slider";
|
||||||
|
ui_label = "Falloff";
|
||||||
|
ui_tooltip = "How fast the separation grows away from the centre. Higher keeps the middle clean.";
|
||||||
|
ui_min = 1.0; ui_max = 4.0; ui_step = 0.1;
|
||||||
|
> = 2.0;
|
||||||
|
|
||||||
|
void VS_PostProcess(in uint id : SV_VertexID, out float4 pos : SV_Position, out float2 uv : TEXCOORD)
|
||||||
|
{
|
||||||
|
uv = float2(float(id & 2), float((id & 1) << 1));
|
||||||
|
pos = float4(uv * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
float4 PS_ChromaticAberration(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
|
||||||
|
{
|
||||||
|
float2 texel = float2(BUFFER_RCP_WIDTH, BUFFER_RCP_HEIGHT);
|
||||||
|
|
||||||
|
float2 direction = uv - float2(0.5, 0.5);
|
||||||
|
float radius = length(direction);
|
||||||
|
float2 unit = direction / max(radius, 0.0001);
|
||||||
|
float2 offset = unit * Strength * pow(radius * 2.0, Falloff) * texel;
|
||||||
|
|
||||||
|
float red = tex2D(BackBuffer, uv + offset).r;
|
||||||
|
float green = tex2D(BackBuffer, uv).g;
|
||||||
|
float blue = tex2D(BackBuffer, uv - offset).b;
|
||||||
|
|
||||||
|
return float4(red, green, blue, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
technique ChromaticAberration <
|
||||||
|
ui_label = "Chromatic Aberration";
|
||||||
|
ui_tooltip = "Splits the colour channels apart towards the edges of the screen, the way a cheap lens fails to focus every colour on the same spot.";
|
||||||
|
>
|
||||||
|
{
|
||||||
|
pass
|
||||||
|
{
|
||||||
|
VertexShader = VS_PostProcess;
|
||||||
|
PixelShader = PS_ChromaticAberration;
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+64
@@ -0,0 +1,64 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2012 PPSSPP Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
// Based on colorcorrection.fsh in PPSSPP.
|
||||||
|
|
||||||
|
texture BackBufferTex : COLOR;
|
||||||
|
sampler BackBuffer { Texture = BackBufferTex; };
|
||||||
|
|
||||||
|
uniform float Saturation <
|
||||||
|
ui_type = "slider";
|
||||||
|
ui_label = "Saturation";
|
||||||
|
ui_min = 0.0; ui_max = 2.0; ui_step = 0.01;
|
||||||
|
> = 1.0;
|
||||||
|
|
||||||
|
uniform float Brightness <
|
||||||
|
ui_type = "slider";
|
||||||
|
ui_label = "Brightness";
|
||||||
|
ui_min = 0.0; ui_max = 2.0; ui_step = 0.01;
|
||||||
|
> = 1.0;
|
||||||
|
|
||||||
|
uniform float Contrast <
|
||||||
|
ui_type = "slider";
|
||||||
|
ui_label = "Contrast";
|
||||||
|
ui_min = 0.0; ui_max = 2.0; ui_step = 0.01;
|
||||||
|
> = 1.0;
|
||||||
|
|
||||||
|
uniform float Gamma <
|
||||||
|
ui_type = "slider";
|
||||||
|
ui_label = "Gamma";
|
||||||
|
ui_min = 0.5; ui_max = 2.0; ui_step = 0.01;
|
||||||
|
> = 1.0;
|
||||||
|
|
||||||
|
void VS_PostProcess(in uint id : SV_VertexID, out float4 pos : SV_Position, out float2 uv : TEXCOORD)
|
||||||
|
{
|
||||||
|
uv = float2(float(id & 2), float((id & 1) << 1));
|
||||||
|
pos = float4(uv * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
float4 PS_ColorGrade(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
|
||||||
|
{
|
||||||
|
float3 rgb = tex2D(BackBuffer, uv).rgb;
|
||||||
|
|
||||||
|
float luma = dot(rgb, float3(0.2126, 0.7152, 0.0722));
|
||||||
|
rgb = lerp(float3(luma, luma, luma), rgb, Saturation);
|
||||||
|
rgb *= Brightness;
|
||||||
|
rgb = (rgb - 0.5) * Contrast + 0.5;
|
||||||
|
rgb = pow(max(rgb, 0.0), 1.0 / max(Gamma, 0.0001));
|
||||||
|
|
||||||
|
return float4(saturate(rgb), 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
technique ColorGrade <
|
||||||
|
ui_label = "Colour Grade";
|
||||||
|
ui_tooltip = "The four basic colour controls in one pass: saturation, brightness, contrast and gamma. Reach for this before anything more specialised.";
|
||||||
|
>
|
||||||
|
{
|
||||||
|
pass
|
||||||
|
{
|
||||||
|
VertexShader = VS_PostProcess;
|
||||||
|
PixelShader = PS_ColorGrade;
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+91
@@ -0,0 +1,91 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2015 Niklas Haas
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
texture BackBufferTex : COLOR;
|
||||||
|
sampler BackBuffer { Texture = BackBufferTex; };
|
||||||
|
|
||||||
|
uniform float Threshold <
|
||||||
|
ui_type = "slider";
|
||||||
|
ui_label = "Threshold";
|
||||||
|
ui_tooltip = "How flat a neighbourhood must be before it gets smoothed. Raise it to catch wider bands, lower it to keep more detail.";
|
||||||
|
ui_min = 0.002; ui_max = 0.05; ui_step = 0.001;
|
||||||
|
> = 0.012;
|
||||||
|
|
||||||
|
uniform float Radius <
|
||||||
|
ui_type = "slider";
|
||||||
|
ui_label = "Radius";
|
||||||
|
ui_tooltip = "How far the sampling reaches, in pixels. Wider gradients need a larger radius.";
|
||||||
|
ui_min = 1.0; ui_max = 32.0; ui_step = 1.0;
|
||||||
|
> = 8.0;
|
||||||
|
|
||||||
|
uniform float Grain <
|
||||||
|
ui_type = "slider";
|
||||||
|
ui_label = "Dither";
|
||||||
|
ui_tooltip = "Noise added to break up whatever banding survives the smoothing.";
|
||||||
|
ui_min = 0.0; ui_max = 0.02; ui_step = 0.001;
|
||||||
|
> = 0.004;
|
||||||
|
|
||||||
|
void VS_PostProcess(in uint id : SV_VertexID, out float4 pos : SV_Position, out float2 uv : TEXCOORD)
|
||||||
|
{
|
||||||
|
uv = float2(float(id & 2), float((id & 1) << 1));
|
||||||
|
pos = float4(uv * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
float Hash(float2 p)
|
||||||
|
{
|
||||||
|
float3 scattered = frac(float3(p.x, p.y, p.x) * 0.1031);
|
||||||
|
scattered += dot(scattered, scattered.yzx + 33.33);
|
||||||
|
return frac((scattered.x + scattered.y) * scattered.z);
|
||||||
|
}
|
||||||
|
|
||||||
|
float4 PS_Deband(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
|
||||||
|
{
|
||||||
|
float2 texel = float2(BUFFER_RCP_WIDTH, BUFFER_RCP_HEIGHT);
|
||||||
|
|
||||||
|
float3 centre = tex2D(BackBuffer, uv).rgb;
|
||||||
|
float base = Hash(pos.xy) * 6.2831853;
|
||||||
|
|
||||||
|
float3 total = float3(0.0, 0.0, 0.0);
|
||||||
|
float3 deviation = float3(0.0, 0.0, 0.0);
|
||||||
|
|
||||||
|
[unroll] for (int ring = 1; ring <= 2; ++ring)
|
||||||
|
{
|
||||||
|
float angle = base + float(ring) * 2.3999632;
|
||||||
|
float reach = Radius * float(ring) * 0.5;
|
||||||
|
float2 along = float2(cos(angle), sin(angle)) * reach;
|
||||||
|
float2 across = float2(-along.y, along.x);
|
||||||
|
|
||||||
|
float3 s0 = tex2D(BackBuffer, uv + along * texel).rgb;
|
||||||
|
float3 s1 = tex2D(BackBuffer, uv - along * texel).rgb;
|
||||||
|
float3 s2 = tex2D(BackBuffer, uv + across * texel).rgb;
|
||||||
|
float3 s3 = tex2D(BackBuffer, uv - across * texel).rgb;
|
||||||
|
|
||||||
|
total += s0 + s1 + s2 + s3;
|
||||||
|
deviation = max(deviation, max(max(abs(s0 - centre), abs(s1 - centre)),
|
||||||
|
max(abs(s2 - centre), abs(s3 - centre))));
|
||||||
|
}
|
||||||
|
|
||||||
|
float3 average = total * 0.125;
|
||||||
|
float3 flatness = float3(1.0, 1.0, 1.0) -
|
||||||
|
smoothstep(Threshold * 0.5, Threshold, deviation);
|
||||||
|
float3 result = lerp(centre, average, flatness);
|
||||||
|
|
||||||
|
float dither = (Hash(pos.xy + float2(71.3, 41.7)) - 0.5) * Grain;
|
||||||
|
|
||||||
|
return float4(saturate(result + dither), 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
technique Deband <
|
||||||
|
ui_label = "Deband";
|
||||||
|
ui_tooltip = "Smooths the visible steps in gradients such as skies, then dithers whatever survives. Worth adding whenever large flat areas show rings.";
|
||||||
|
>
|
||||||
|
{
|
||||||
|
pass
|
||||||
|
{
|
||||||
|
VertexShader = VS_PostProcess;
|
||||||
|
PixelShader = PS_Deband;
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+83
@@ -0,0 +1,83 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2019-2021 bloc97
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
texture BackBufferTex : COLOR;
|
||||||
|
sampler BackBuffer { Texture = BackBufferTex; };
|
||||||
|
|
||||||
|
uniform float Strength <
|
||||||
|
ui_type = "slider";
|
||||||
|
ui_label = "Strength";
|
||||||
|
ui_min = 0.01; ui_max = 0.5; ui_step = 0.01;
|
||||||
|
> = 0.1;
|
||||||
|
|
||||||
|
uniform float Radius <
|
||||||
|
ui_type = "slider";
|
||||||
|
ui_label = "Radius";
|
||||||
|
ui_min = 0.3; ui_max = 2.0; ui_step = 0.05;
|
||||||
|
> = 1.0;
|
||||||
|
|
||||||
|
uniform float Curve <
|
||||||
|
ui_type = "slider";
|
||||||
|
ui_label = "Shadow Bias";
|
||||||
|
ui_min = 0.0; ui_max = 2.0; ui_step = 0.05;
|
||||||
|
> = 1.0;
|
||||||
|
|
||||||
|
void VS_PostProcess(in uint id : SV_VertexID, out float4 pos : SV_Position, out float2 uv : TEXCOORD)
|
||||||
|
{
|
||||||
|
uv = float2(float(id & 2), float((id & 1) << 1));
|
||||||
|
pos = float4(uv * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
float3 IntensityWeight(float3 value, float3 sigma, float3 centre)
|
||||||
|
{
|
||||||
|
float3 scaled = (value - centre) / sigma;
|
||||||
|
return exp(-0.5 * scaled * scaled);
|
||||||
|
}
|
||||||
|
|
||||||
|
float SpatialWeight(float distance, float sigma)
|
||||||
|
{
|
||||||
|
float scaled = distance / sigma;
|
||||||
|
return exp(-0.5 * scaled * scaled);
|
||||||
|
}
|
||||||
|
|
||||||
|
float4 PS_Denoise(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
|
||||||
|
{
|
||||||
|
float2 texel = float2(BUFFER_RCP_WIDTH, BUFFER_RCP_HEIGHT);
|
||||||
|
|
||||||
|
float3 centre = tex2D(BackBuffer, uv).rgb;
|
||||||
|
float3 intensity_sigma = max(pow(centre + 0.0001, Curve) * Strength, 0.0001);
|
||||||
|
float spatial_sigma = max(Radius, 0.05);
|
||||||
|
|
||||||
|
float3 sum = float3(0.0, 0.0, 0.0);
|
||||||
|
float3 total = float3(0.0, 0.0, 0.0);
|
||||||
|
|
||||||
|
[unroll] for (int y = -2; y <= 2; ++y)
|
||||||
|
{
|
||||||
|
[unroll] for (int x = -2; x <= 2; ++x)
|
||||||
|
{
|
||||||
|
float2 offset = float2(x, y);
|
||||||
|
float3 tap = tex2D(BackBuffer, uv + offset * texel).rgb;
|
||||||
|
float3 weight = IntensityWeight(tap, intensity_sigma, centre) *
|
||||||
|
SpatialWeight(length(offset), spatial_sigma);
|
||||||
|
sum += weight * tap;
|
||||||
|
total += weight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return float4(sum / total, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
technique Denoise <
|
||||||
|
ui_label = "Denoise";
|
||||||
|
ui_tooltip = "Edge preserving blur that clears dithering and compression noise while leaving outlines sharp. Ported from Anime4K.";
|
||||||
|
>
|
||||||
|
{
|
||||||
|
pass
|
||||||
|
{
|
||||||
|
VertexShader = VS_PostProcess;
|
||||||
|
PixelShader = PS_Denoise;
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+65
@@ -0,0 +1,65 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
texture BackBufferTex : COLOR;
|
||||||
|
sampler BackBuffer { Texture = BackBufferTex; };
|
||||||
|
|
||||||
|
uniform float Intensity <
|
||||||
|
ui_type = "slider";
|
||||||
|
ui_label = "Intensity";
|
||||||
|
ui_min = 0.0; ui_max = 0.2; ui_step = 0.005;
|
||||||
|
> = 0.03;
|
||||||
|
|
||||||
|
uniform float Size <
|
||||||
|
ui_type = "slider";
|
||||||
|
ui_label = "Size";
|
||||||
|
ui_tooltip = "Grain cell size in pixels.";
|
||||||
|
ui_min = 1.0; ui_max = 4.0; ui_step = 1.0;
|
||||||
|
> = 1.0;
|
||||||
|
|
||||||
|
uniform float Colored <
|
||||||
|
ui_type = "slider";
|
||||||
|
ui_label = "Colour";
|
||||||
|
ui_tooltip = "Zero gives monochrome grain, one gives independent noise per channel.";
|
||||||
|
ui_min = 0.0; ui_max = 1.0; ui_step = 0.01;
|
||||||
|
> = 0.0;
|
||||||
|
|
||||||
|
void VS_PostProcess(in uint id : SV_VertexID, out float4 pos : SV_Position, out float2 uv : TEXCOORD)
|
||||||
|
{
|
||||||
|
uv = float2(float(id & 2), float((id & 1) << 1));
|
||||||
|
pos = float4(uv * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
float Hash(float2 p)
|
||||||
|
{
|
||||||
|
float3 scattered = frac(float3(p.x, p.y, p.x) * 0.1031);
|
||||||
|
scattered += dot(scattered, scattered.yzx + 33.33);
|
||||||
|
return frac((scattered.x + scattered.y) * scattered.z);
|
||||||
|
}
|
||||||
|
|
||||||
|
float4 PS_FilmGrain(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
|
||||||
|
{
|
||||||
|
float3 rgb = tex2D(BackBuffer, uv).rgb;
|
||||||
|
|
||||||
|
float2 cell = floor(pos.xy / max(Size, 1.0));
|
||||||
|
float mono = Hash(cell) - 0.5;
|
||||||
|
float3 chroma = float3(Hash(cell + 11.7), Hash(cell + 23.1), Hash(cell + 37.5)) - 0.5;
|
||||||
|
float3 noise = lerp(float3(mono, mono, mono), chroma, Colored);
|
||||||
|
|
||||||
|
float luma = dot(rgb, float3(0.2126, 0.7152, 0.0722));
|
||||||
|
float response = 1.0 - abs(luma * 2.0 - 1.0);
|
||||||
|
|
||||||
|
return float4(saturate(rgb + noise * Intensity * response), 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
technique FilmGrain <
|
||||||
|
ui_label = "Film Grain";
|
||||||
|
ui_tooltip = "Adds photographic grain, strongest in the midtones and fading out in blacks and whites.";
|
||||||
|
>
|
||||||
|
{
|
||||||
|
pass
|
||||||
|
{
|
||||||
|
VertexShader = VS_PostProcess;
|
||||||
|
PixelShader = PS_FilmGrain;
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+60
@@ -0,0 +1,60 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
texture BackBufferTex : COLOR;
|
||||||
|
sampler BackBuffer { Texture = BackBufferTex; };
|
||||||
|
|
||||||
|
uniform float Exposure <
|
||||||
|
ui_type = "slider";
|
||||||
|
ui_label = "Exposure";
|
||||||
|
ui_min = 0.5; ui_max = 2.0; ui_step = 0.01;
|
||||||
|
> = 1.0;
|
||||||
|
|
||||||
|
uniform float Toe <
|
||||||
|
ui_type = "slider";
|
||||||
|
ui_label = "Toe";
|
||||||
|
ui_tooltip = "Above 1.0 deepens the shadows, below 1.0 lifts them.";
|
||||||
|
ui_min = 0.5; ui_max = 2.0; ui_step = 0.01;
|
||||||
|
> = 1.2;
|
||||||
|
|
||||||
|
uniform float Shoulder <
|
||||||
|
ui_type = "slider";
|
||||||
|
ui_label = "Shoulder";
|
||||||
|
ui_tooltip = "Above 1.0 opens up the highlights, below 1.0 compresses them.";
|
||||||
|
ui_min = 0.5; ui_max = 2.0; ui_step = 0.01;
|
||||||
|
> = 1.2;
|
||||||
|
|
||||||
|
uniform float Amount <
|
||||||
|
ui_type = "slider";
|
||||||
|
ui_label = "Amount";
|
||||||
|
ui_min = 0.0; ui_max = 1.0; ui_step = 0.01;
|
||||||
|
> = 0.7;
|
||||||
|
|
||||||
|
void VS_PostProcess(in uint id : SV_VertexID, out float4 pos : SV_Position, out float2 uv : TEXCOORD)
|
||||||
|
{
|
||||||
|
uv = float2(float(id & 2), float((id & 1) << 1));
|
||||||
|
pos = float4(uv * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
float4 PS_FilmicCurve(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
|
||||||
|
{
|
||||||
|
float3 rgb = tex2D(BackBuffer, uv).rgb;
|
||||||
|
|
||||||
|
float3 curved = saturate(rgb * Exposure);
|
||||||
|
curved = pow(max(curved, 0.0), Toe);
|
||||||
|
curved = 1.0 - pow(max(1.0 - curved, 0.0), Shoulder);
|
||||||
|
|
||||||
|
return float4(saturate(lerp(rgb, curved, Amount)), 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
technique FilmicCurve <
|
||||||
|
ui_label = "Filmic Curve";
|
||||||
|
ui_tooltip = "Filmic contrast curve. Deepens the shadows and opens the highlights without clipping either end.";
|
||||||
|
>
|
||||||
|
{
|
||||||
|
pass
|
||||||
|
{
|
||||||
|
VertexShader = VS_PostProcess;
|
||||||
|
PixelShader = PS_FilmicCurve;
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+53
@@ -0,0 +1,53 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
texture BackBufferTex : COLOR;
|
||||||
|
sampler BackBuffer { Texture = BackBufferTex; };
|
||||||
|
|
||||||
|
uniform float Distortion <
|
||||||
|
ui_type = "slider";
|
||||||
|
ui_label = "Distortion";
|
||||||
|
ui_tooltip = "Positive bulges the picture outwards, negative pinches it inwards.";
|
||||||
|
ui_min = -0.5; ui_max = 0.5; ui_step = 0.01;
|
||||||
|
> = 0.1;
|
||||||
|
|
||||||
|
uniform float Zoom <
|
||||||
|
ui_type = "slider";
|
||||||
|
ui_label = "Zoom";
|
||||||
|
ui_tooltip = "Scales the picture to hide the edges the warp pulls in.";
|
||||||
|
ui_min = 0.5; ui_max = 1.5; ui_step = 0.01;
|
||||||
|
> = 1.0;
|
||||||
|
|
||||||
|
void VS_PostProcess(in uint id : SV_VertexID, out float4 pos : SV_Position, out float2 uv : TEXCOORD)
|
||||||
|
{
|
||||||
|
uv = float2(float(id & 2), float((id & 1) << 1));
|
||||||
|
pos = float4(uv * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
float4 PS_LensDistortion(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
|
||||||
|
{
|
||||||
|
float aspect = BUFFER_WIDTH * BUFFER_RCP_HEIGHT;
|
||||||
|
float2 half_size = float2(aspect, 1.0);
|
||||||
|
float2 unit = half_size / length(half_size);
|
||||||
|
|
||||||
|
float2 centred = (uv - 0.5) * 2.0 * unit;
|
||||||
|
float r2 = dot(centred, centred);
|
||||||
|
centred *= 1.0 + Distortion * r2;
|
||||||
|
centred /= max(Zoom, 0.001);
|
||||||
|
|
||||||
|
float2 source = centred / (2.0 * unit) + 0.5;
|
||||||
|
|
||||||
|
return float4(tex2D(BackBuffer, source).rgb, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
technique LensDistortion <
|
||||||
|
ui_label = "Lens Distortion";
|
||||||
|
ui_tooltip = "Barrel or pincushion warp, like looking through a wide angle lens.";
|
||||||
|
>
|
||||||
|
{
|
||||||
|
pass
|
||||||
|
{
|
||||||
|
VertexShader = VS_PostProcess;
|
||||||
|
PixelShader = PS_LensDistortion;
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+66
@@ -0,0 +1,66 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
texture BackBufferTex : COLOR;
|
||||||
|
sampler BackBuffer { Texture = BackBufferTex; };
|
||||||
|
|
||||||
|
uniform float InputBlack <
|
||||||
|
ui_type = "slider";
|
||||||
|
ui_label = "Input Black";
|
||||||
|
ui_tooltip = "Input level mapped to black. Raise it to deepen washed out shadows.";
|
||||||
|
ui_min = 0.0; ui_max = 0.5; ui_step = 0.005;
|
||||||
|
> = 0.0;
|
||||||
|
|
||||||
|
uniform float InputWhite <
|
||||||
|
ui_type = "slider";
|
||||||
|
ui_label = "Input White";
|
||||||
|
ui_min = 0.5; ui_max = 1.0; ui_step = 0.005;
|
||||||
|
> = 1.0;
|
||||||
|
|
||||||
|
uniform float Gamma <
|
||||||
|
ui_type = "slider";
|
||||||
|
ui_label = "Gamma";
|
||||||
|
ui_min = 0.2; ui_max = 3.0; ui_step = 0.01;
|
||||||
|
> = 1.0;
|
||||||
|
|
||||||
|
uniform float OutputBlack <
|
||||||
|
ui_type = "slider";
|
||||||
|
ui_label = "Output Black";
|
||||||
|
ui_tooltip = "Lifts crushed shadows so detail stops collapsing into one flat black.";
|
||||||
|
ui_min = 0.0; ui_max = 0.5; ui_step = 0.005;
|
||||||
|
> = 0.0;
|
||||||
|
|
||||||
|
uniform float OutputWhite <
|
||||||
|
ui_type = "slider";
|
||||||
|
ui_label = "Output White";
|
||||||
|
ui_min = 0.5; ui_max = 1.0; ui_step = 0.005;
|
||||||
|
> = 1.0;
|
||||||
|
|
||||||
|
void VS_PostProcess(in uint id : SV_VertexID, out float4 pos : SV_Position, out float2 uv : TEXCOORD)
|
||||||
|
{
|
||||||
|
uv = float2(float(id & 2), float((id & 1) << 1));
|
||||||
|
pos = float4(uv * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
float4 PS_Levels(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
|
||||||
|
{
|
||||||
|
float3 rgb = tex2D(BackBuffer, uv).rgb;
|
||||||
|
|
||||||
|
rgb = saturate((rgb - InputBlack) / max(InputWhite - InputBlack, 0.001));
|
||||||
|
rgb = pow(max(rgb, 0.0), 1.0 / max(Gamma, 0.001));
|
||||||
|
rgb = lerp(OutputBlack, OutputWhite, rgb);
|
||||||
|
|
||||||
|
return float4(saturate(rgb), 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
technique Levels <
|
||||||
|
ui_label = "Levels";
|
||||||
|
ui_tooltip = "Black point, white point, gamma and output range. Use it to fix crushed or washed out shadows.";
|
||||||
|
>
|
||||||
|
{
|
||||||
|
pass
|
||||||
|
{
|
||||||
|
VertexShader = VS_PostProcess;
|
||||||
|
PixelShader = PS_Levels;
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+88
@@ -0,0 +1,88 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
texture BackBufferTex : COLOR;
|
||||||
|
sampler BackBuffer { Texture = BackBufferTex; };
|
||||||
|
|
||||||
|
uniform float Length <
|
||||||
|
ui_type = "slider";
|
||||||
|
ui_label = "Length";
|
||||||
|
ui_tooltip = "How far the streaks reach, as a percentage of screen height.";
|
||||||
|
ui_min = 0.0; ui_max = 20.0; ui_step = 0.5;
|
||||||
|
> = 5.0;
|
||||||
|
|
||||||
|
uniform float Zoom <
|
||||||
|
ui_type = "slider";
|
||||||
|
ui_label = "Zoom";
|
||||||
|
ui_tooltip = "Streaks running out from the centre of the screen, as if rushing forward. The centre stays sharp.";
|
||||||
|
ui_min = 0.0; ui_max = 1.0; ui_step = 0.05;
|
||||||
|
> = 1.0;
|
||||||
|
|
||||||
|
uniform float Pan <
|
||||||
|
ui_type = "slider";
|
||||||
|
ui_label = "Pan";
|
||||||
|
ui_tooltip = "Streaks running in one fixed direction, as if the camera were sweeping across.";
|
||||||
|
ui_min = 0.0; ui_max = 1.0; ui_step = 0.05;
|
||||||
|
> = 0.0;
|
||||||
|
|
||||||
|
uniform float Angle <
|
||||||
|
ui_type = "slider";
|
||||||
|
ui_label = "Pan Angle";
|
||||||
|
ui_tooltip = "Direction of the sweep, in degrees.";
|
||||||
|
ui_min = 0.0; ui_max = 360.0; ui_step = 5.0;
|
||||||
|
> = 0.0;
|
||||||
|
|
||||||
|
uniform float Spin <
|
||||||
|
ui_type = "slider";
|
||||||
|
ui_label = "Spin";
|
||||||
|
ui_tooltip = "Streaks curling around the centre, as if the camera were rolling. Negative turns the other way.";
|
||||||
|
ui_min = -1.0; ui_max = 1.0; ui_step = 0.05;
|
||||||
|
> = 0.0;
|
||||||
|
|
||||||
|
static const int TAPS = 24;
|
||||||
|
|
||||||
|
void VS_PostProcess(in uint id : SV_VertexID, out float4 pos : SV_Position, out float2 uv : TEXCOORD)
|
||||||
|
{
|
||||||
|
uv = float2(float(id & 2), float((id & 1) << 1));
|
||||||
|
pos = float4(uv * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
float4 PS_MotionBlur(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
|
||||||
|
{
|
||||||
|
float aspect = BUFFER_WIDTH * BUFFER_RCP_HEIGHT;
|
||||||
|
float2 to_screen = float2(aspect, 1.0);
|
||||||
|
|
||||||
|
float2 centred = (uv - 0.5) * to_screen * 2.0;
|
||||||
|
float2 outward = centred;
|
||||||
|
float2 around = float2(-centred.y, centred.x);
|
||||||
|
|
||||||
|
float radians_angle = Angle * 0.01745329;
|
||||||
|
float2 sweep = float2(cos(radians_angle), sin(radians_angle));
|
||||||
|
|
||||||
|
float2 velocity = sweep * Pan + outward * Zoom + around * Spin;
|
||||||
|
velocity *= Length * 0.01;
|
||||||
|
velocity /= to_screen;
|
||||||
|
|
||||||
|
float jitter = frac(sin(dot(pos.xy, float2(12.9898, 78.233))) * 43758.5453);
|
||||||
|
|
||||||
|
float3 sum = float3(0.0, 0.0, 0.0);
|
||||||
|
[unroll] for (int i = 0; i < TAPS; ++i)
|
||||||
|
{
|
||||||
|
float t = (float(i) + jitter) / float(TAPS) - 0.5;
|
||||||
|
sum += tex2D(BackBuffer, uv + velocity * t).rgb;
|
||||||
|
}
|
||||||
|
|
||||||
|
return float4(sum / float(TAPS), 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
technique MotionBlur <
|
||||||
|
ui_label = "Motion Blur";
|
||||||
|
ui_tooltip = "Camera style motion blur: streaks the picture outwards, sideways or around, without touching still detail at the centre.";
|
||||||
|
>
|
||||||
|
{
|
||||||
|
pass
|
||||||
|
{
|
||||||
|
VertexShader = VS_PostProcess;
|
||||||
|
PixelShader = PS_MotionBlur;
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+60
@@ -0,0 +1,60 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2012 PPSSPP Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
// Ported from naturalA.fsh in PPSSPP, by ShadX, modified by SimoneT.
|
||||||
|
|
||||||
|
texture BackBufferTex : COLOR;
|
||||||
|
sampler BackBuffer { Texture = BackBufferTex; };
|
||||||
|
|
||||||
|
uniform float Luma <
|
||||||
|
ui_type = "slider";
|
||||||
|
ui_label = "Luma Curve";
|
||||||
|
ui_tooltip = "Gamma applied to the luminance channel in YIQ space.";
|
||||||
|
ui_min = 0.5; ui_max = 2.0; ui_step = 0.01;
|
||||||
|
> = 1.2;
|
||||||
|
|
||||||
|
uniform float Chroma <
|
||||||
|
ui_type = "slider";
|
||||||
|
ui_label = "Chroma Gain";
|
||||||
|
ui_tooltip = "Boost applied to the two colour difference channels.";
|
||||||
|
ui_min = 0.0; ui_max = 2.0; ui_step = 0.01;
|
||||||
|
> = 1.2;
|
||||||
|
|
||||||
|
void VS_PostProcess(in uint id : SV_VertexID, out float4 pos : SV_Position, out float2 uv : TEXCOORD)
|
||||||
|
{
|
||||||
|
uv = float2(float(id & 2), float((id & 1) << 1));
|
||||||
|
pos = float4(uv * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
float4 PS_Natural(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
|
||||||
|
{
|
||||||
|
const float3x3 RGBtoYIQ = float3x3(0.299, 0.587, 0.114,
|
||||||
|
0.596, -0.275, -0.321,
|
||||||
|
0.212, -0.523, 0.311);
|
||||||
|
|
||||||
|
const float3x3 YIQtoRGB = float3x3(1.0, 0.95568806, 0.61985809,
|
||||||
|
1.0, -0.27158180, -0.64687382,
|
||||||
|
1.0, -1.10817733, 1.70506456);
|
||||||
|
|
||||||
|
float3 rgb = tex2D(BackBuffer, uv).rgb;
|
||||||
|
float3 yiq = mul(RGBtoYIQ, rgb);
|
||||||
|
|
||||||
|
yiq.x = pow(max(yiq.x, 0.0), Luma);
|
||||||
|
yiq.yz *= Chroma;
|
||||||
|
|
||||||
|
return float4(saturate(mul(YIQtoRGB, yiq)), 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
technique NaturalColors <
|
||||||
|
ui_label = "Natural Colours";
|
||||||
|
ui_tooltip = "Warms the picture and lifts saturation for a less washed out look. Ported from the PPSSPP shader.";
|
||||||
|
>
|
||||||
|
{
|
||||||
|
pass
|
||||||
|
{
|
||||||
|
VertexShader = VS_PostProcess;
|
||||||
|
PixelShader = PS_Natural;
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+90
@@ -0,0 +1,90 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
texture BackBufferTex : COLOR;
|
||||||
|
sampler BackBuffer { Texture = BackBufferTex; };
|
||||||
|
|
||||||
|
uniform float Timer < source = "timer"; > = 0.0;
|
||||||
|
|
||||||
|
uniform float Horizon <
|
||||||
|
ui_type = "slider";
|
||||||
|
ui_label = "Reflection Line";
|
||||||
|
ui_tooltip = "Height on screen where the reflective floor begins. Everything above it is left untouched.";
|
||||||
|
ui_min = 0.0; ui_max = 1.0; ui_step = 0.01;
|
||||||
|
> = 0.55;
|
||||||
|
|
||||||
|
uniform float Amount <
|
||||||
|
ui_type = "slider";
|
||||||
|
ui_label = "Amount";
|
||||||
|
ui_tooltip = "How strongly the reflection shows through the floor.";
|
||||||
|
ui_min = 0.0; ui_max = 1.0; ui_step = 0.05;
|
||||||
|
> = 0.35;
|
||||||
|
|
||||||
|
uniform float Falloff <
|
||||||
|
ui_type = "slider";
|
||||||
|
ui_label = "Falloff";
|
||||||
|
ui_tooltip = "How quickly the reflection fades as the floor comes towards the viewer. At zero it stays even.";
|
||||||
|
ui_min = 0.0; ui_max = 4.0; ui_step = 0.1;
|
||||||
|
> = 1.2;
|
||||||
|
|
||||||
|
uniform float Perspective <
|
||||||
|
ui_type = "slider";
|
||||||
|
ui_label = "Perspective";
|
||||||
|
ui_tooltip = "Stretches or squashes the mirrored image. One is a true mirror, higher values pull it towards the line.";
|
||||||
|
ui_min = 0.2; ui_max = 2.0; ui_step = 0.05;
|
||||||
|
> = 1.0;
|
||||||
|
|
||||||
|
uniform float Ripple <
|
||||||
|
ui_type = "slider";
|
||||||
|
ui_label = "Ripple";
|
||||||
|
ui_tooltip = "Amplitude of the waves disturbing the reflection. At zero the mirror is perfectly still.";
|
||||||
|
ui_min = 0.0; ui_max = 1.0; ui_step = 0.05;
|
||||||
|
> = 0.0;
|
||||||
|
|
||||||
|
uniform float RippleSpeed <
|
||||||
|
ui_type = "slider";
|
||||||
|
ui_label = "Ripple Speed";
|
||||||
|
ui_tooltip = "How fast the waves travel.";
|
||||||
|
ui_min = 0.0; ui_max = 4.0; ui_step = 0.1;
|
||||||
|
> = 1.0;
|
||||||
|
|
||||||
|
void VS_PostProcess(in uint id : SV_VertexID, out float4 pos : SV_Position, out float2 uv : TEXCOORD)
|
||||||
|
{
|
||||||
|
uv = float2(float(id & 2), float((id & 1) << 1));
|
||||||
|
pos = float4(uv * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
float4 PS_Reflections(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
|
||||||
|
{
|
||||||
|
float3 color = tex2D(BackBuffer, uv).rgb;
|
||||||
|
|
||||||
|
float depth = uv.y - Horizon;
|
||||||
|
float on_floor = step(0.0, depth);
|
||||||
|
float span = max(1.0 - Horizon, 0.001);
|
||||||
|
float distance_down = saturate(depth / span);
|
||||||
|
|
||||||
|
float seconds = Timer * 0.001;
|
||||||
|
float wave = sin(uv.x * 38.0 + seconds * RippleSpeed * 2.0) *
|
||||||
|
sin(uv.y * 21.0 - seconds * RippleSpeed * 1.3);
|
||||||
|
float2 disturbance = float2(wave * 0.004, wave * 0.002) * Ripple * distance_down;
|
||||||
|
|
||||||
|
float2 mirrored = float2(uv.x, Horizon - depth * Perspective) + disturbance;
|
||||||
|
float3 reflection = tex2D(BackBuffer, saturate(mirrored)).rgb;
|
||||||
|
|
||||||
|
float fade = pow(max(1.0 - distance_down, 0.0001), Falloff);
|
||||||
|
float strength = Amount * fade * on_floor;
|
||||||
|
|
||||||
|
return float4(lerp(color, reflection, strength), 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
technique Reflections <
|
||||||
|
ui_label = "Reflections";
|
||||||
|
ui_tooltip = "Mirrors the picture into a reflective floor below a line you choose, with optional water ripples.";
|
||||||
|
>
|
||||||
|
{
|
||||||
|
pass
|
||||||
|
{
|
||||||
|
VertexShader = VS_PostProcess;
|
||||||
|
PixelShader = PS_Reflections;
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+66
@@ -0,0 +1,66 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2012 PPSSPP Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
// Ported from scanlines.fsh in PPSSPP.
|
||||||
|
|
||||||
|
|
||||||
|
texture BackBufferTex : COLOR;
|
||||||
|
sampler BackBuffer { Texture = BackBufferTex; };
|
||||||
|
|
||||||
|
uniform float Density <
|
||||||
|
ui_type = "slider";
|
||||||
|
ui_label = "Line Density";
|
||||||
|
ui_tooltip = "Number of scanline pairs across the screen.";
|
||||||
|
ui_min = 60.0; ui_max = 720.0; ui_step = 10.0;
|
||||||
|
> = 340.0;
|
||||||
|
|
||||||
|
uniform float Intensity <
|
||||||
|
ui_type = "slider";
|
||||||
|
ui_label = "Intensity";
|
||||||
|
ui_tooltip = "How dark the gaps between lines become.";
|
||||||
|
ui_min = 0.0; ui_max = 1.0; ui_step = 0.05;
|
||||||
|
> = 0.5;
|
||||||
|
|
||||||
|
uniform float Tint <
|
||||||
|
ui_type = "slider";
|
||||||
|
ui_label = "Phosphor Tint";
|
||||||
|
ui_tooltip = "Strength of the green-warm phosphor cast.";
|
||||||
|
ui_min = 0.0; ui_max = 1.0; ui_step = 0.05;
|
||||||
|
> = 1.0;
|
||||||
|
|
||||||
|
void VS_PostProcess(in uint id : SV_VertexID, out float4 pos : SV_Position, out float2 uv : TEXCOORD)
|
||||||
|
{
|
||||||
|
uv = float2(float(id & 2), float((id & 1) << 1));
|
||||||
|
pos = float4(uv * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
float4 PS_Scanlines(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
|
||||||
|
{
|
||||||
|
float line_pos = uv.y * Density * 0.5;
|
||||||
|
float gate = cos((frac(line_pos) - 0.5) * 3.1415926 * Intensity) * 1.5;
|
||||||
|
|
||||||
|
float3 rgb = tex2D(BackBuffer, uv).rgb;
|
||||||
|
float3 color = rgb * 0.5 + 0.5 * rgb * rgb * 1.2;
|
||||||
|
|
||||||
|
float3 phosphor = lerp(float3(1.0, 1.0, 1.0), float3(0.9, 1.0, 0.7), Tint);
|
||||||
|
color *= phosphor;
|
||||||
|
|
||||||
|
float2 diff = uv - 0.5;
|
||||||
|
color *= 1.1 - 0.6 * (dot(diff, diff) * 2.0);
|
||||||
|
|
||||||
|
return float4(saturate(color * saturate(gate)), 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
technique Scanlines <
|
||||||
|
ui_label = "Scanlines";
|
||||||
|
ui_tooltip = "Darkens alternating rows of pixels to imitate the horizontal scanlines of a CRT. Cheaper than the full CRT effect when you only want the lines.";
|
||||||
|
>
|
||||||
|
{
|
||||||
|
pass
|
||||||
|
{
|
||||||
|
VertexShader = VS_PostProcess;
|
||||||
|
PixelShader = PS_Scanlines;
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+43
@@ -0,0 +1,43 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
texture BackBufferTex : COLOR;
|
||||||
|
sampler BackBuffer { Texture = BackBufferTex; };
|
||||||
|
|
||||||
|
uniform float Amount <
|
||||||
|
ui_type = "slider";
|
||||||
|
ui_label = "Amount";
|
||||||
|
ui_min = 0.0; ui_max = 3.0; ui_step = 0.01;
|
||||||
|
> = 0.6;
|
||||||
|
|
||||||
|
void VS_PostProcess(in uint id : SV_VertexID, out float4 pos : SV_Position, out float2 uv : TEXCOORD)
|
||||||
|
{
|
||||||
|
uv = float2(float(id & 2), float((id & 1) << 1));
|
||||||
|
pos = float4(uv * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
float4 PS_Sharpen(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
|
||||||
|
{
|
||||||
|
float2 texel = float2(BUFFER_RCP_WIDTH, BUFFER_RCP_HEIGHT);
|
||||||
|
|
||||||
|
float3 centre = tex2D(BackBuffer, uv).rgb;
|
||||||
|
float3 blur = tex2D(BackBuffer, uv + float2(-texel.x, 0.0)).rgb;
|
||||||
|
blur += tex2D(BackBuffer, uv + float2(texel.x, 0.0)).rgb;
|
||||||
|
blur += tex2D(BackBuffer, uv + float2(0.0, -texel.y)).rgb;
|
||||||
|
blur += tex2D(BackBuffer, uv + float2(0.0, texel.y)).rgb;
|
||||||
|
blur *= 0.25;
|
||||||
|
|
||||||
|
return float4(centre + (centre - blur) * Amount, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
technique Sharpen <
|
||||||
|
ui_label = "Sharpen";
|
||||||
|
ui_tooltip = "Unsharp mask that brings back edge detail lost to scaling. Most useful after upscaling a low internal resolution.";
|
||||||
|
>
|
||||||
|
{
|
||||||
|
pass
|
||||||
|
{
|
||||||
|
VertexShader = VS_PostProcess;
|
||||||
|
PixelShader = PS_Sharpen;
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+75
@@ -0,0 +1,75 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
texture BackBufferTex : COLOR;
|
||||||
|
sampler BackBuffer { Texture = BackBufferTex; };
|
||||||
|
|
||||||
|
uniform float ShadowHue <
|
||||||
|
ui_type = "slider";
|
||||||
|
ui_label = "Shadow Hue";
|
||||||
|
ui_min = 0.0; ui_max = 360.0; ui_step = 1.0;
|
||||||
|
> = 210.0;
|
||||||
|
|
||||||
|
uniform float ShadowStrength <
|
||||||
|
ui_type = "slider";
|
||||||
|
ui_label = "Shadow Strength";
|
||||||
|
ui_min = 0.0; ui_max = 1.0; ui_step = 0.01;
|
||||||
|
> = 0.0;
|
||||||
|
|
||||||
|
uniform float HighlightHue <
|
||||||
|
ui_type = "slider";
|
||||||
|
ui_label = "Highlight Hue";
|
||||||
|
ui_min = 0.0; ui_max = 360.0; ui_step = 1.0;
|
||||||
|
> = 45.0;
|
||||||
|
|
||||||
|
uniform float HighlightStrength <
|
||||||
|
ui_type = "slider";
|
||||||
|
ui_label = "Highlight Strength";
|
||||||
|
ui_min = 0.0; ui_max = 1.0; ui_step = 0.01;
|
||||||
|
> = 0.0;
|
||||||
|
|
||||||
|
uniform float Balance <
|
||||||
|
ui_type = "slider";
|
||||||
|
ui_label = "Balance";
|
||||||
|
ui_tooltip = "Moves the split between what counts as shadow and what counts as highlight.";
|
||||||
|
ui_min = -0.5; ui_max = 0.5; ui_step = 0.01;
|
||||||
|
> = 0.0;
|
||||||
|
|
||||||
|
void VS_PostProcess(in uint id : SV_VertexID, out float4 pos : SV_Position, out float2 uv : TEXCOORD)
|
||||||
|
{
|
||||||
|
uv = float2(float(id & 2), float((id & 1) << 1));
|
||||||
|
pos = float4(uv * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
float3 HueToRGB(float hue)
|
||||||
|
{
|
||||||
|
float h = frac(hue / 360.0) * 6.0;
|
||||||
|
return saturate(float3(abs(h - 3.0) - 1.0, 2.0 - abs(h - 2.0), 2.0 - abs(h - 4.0)));
|
||||||
|
}
|
||||||
|
|
||||||
|
float4 PS_SplitToning(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
|
||||||
|
{
|
||||||
|
float3 rgb = tex2D(BackBuffer, uv).rgb;
|
||||||
|
|
||||||
|
float luma = saturate(dot(rgb, float3(0.2126, 0.7152, 0.0722)) + Balance);
|
||||||
|
|
||||||
|
float3 shadow_tint = HueToRGB(ShadowHue) - 0.5;
|
||||||
|
float3 highlight_tint = HueToRGB(HighlightHue) - 0.5;
|
||||||
|
|
||||||
|
rgb += shadow_tint * ShadowStrength * 0.25 * (1.0 - luma);
|
||||||
|
rgb += highlight_tint * HighlightStrength * 0.25 * luma;
|
||||||
|
|
||||||
|
return float4(saturate(rgb), 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
technique SplitToning <
|
||||||
|
ui_label = "Split Toning";
|
||||||
|
ui_tooltip = "Tints the shadows and the highlights towards two different hues.";
|
||||||
|
>
|
||||||
|
{
|
||||||
|
pass
|
||||||
|
{
|
||||||
|
VertexShader = VS_PostProcess;
|
||||||
|
PixelShader = PS_SplitToning;
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+52
@@ -0,0 +1,52 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2012 PPSSPP Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
// Ported from vignette.fsh in PPSSPP, by Henrik Rydgard.
|
||||||
|
|
||||||
|
texture BackBufferTex : COLOR;
|
||||||
|
sampler BackBuffer { Texture = BackBufferTex; };
|
||||||
|
|
||||||
|
uniform float Strength <
|
||||||
|
ui_type = "slider";
|
||||||
|
ui_label = "Strength";
|
||||||
|
ui_tooltip = "How dark the corners become.";
|
||||||
|
ui_min = 0.0; ui_max = 2.0; ui_step = 0.01;
|
||||||
|
> = 0.6;
|
||||||
|
|
||||||
|
uniform float Aspect <
|
||||||
|
ui_type = "slider";
|
||||||
|
ui_label = "Aspect";
|
||||||
|
ui_min = 0.5; ui_max = 2.0; ui_step = 0.01;
|
||||||
|
> = 1.0;
|
||||||
|
|
||||||
|
void VS_PostProcess(in uint id : SV_VertexID, out float4 pos : SV_Position, out float2 uv : TEXCOORD)
|
||||||
|
{
|
||||||
|
uv = float2(float(id & 2), float((id & 1) << 1));
|
||||||
|
pos = float4(uv * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
float4 PS_Vignette(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
|
||||||
|
{
|
||||||
|
float2 diff = uv - 0.5;
|
||||||
|
diff.x *= Aspect;
|
||||||
|
diff.y /= max(Aspect, 0.0001);
|
||||||
|
|
||||||
|
float falloff = 1.0 - min(1.0, Strength * dot(diff, diff) * 2.0);
|
||||||
|
float3 rgb = tex2D(BackBuffer, uv).rgb;
|
||||||
|
|
||||||
|
return float4(rgb * falloff, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
technique Vignette <
|
||||||
|
ui_label = "Vignette";
|
||||||
|
ui_tooltip = "Darkens the corners of the screen to pull the eye towards the middle, the way a camera lens falls off at its edges.";
|
||||||
|
>
|
||||||
|
{
|
||||||
|
pass
|
||||||
|
{
|
||||||
|
VertexShader = VS_PostProcess;
|
||||||
|
PixelShader = PS_Vignette;
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+76
@@ -0,0 +1,76 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
texture BackBufferTex : COLOR;
|
||||||
|
sampler BackBuffer { Texture = BackBufferTex; };
|
||||||
|
|
||||||
|
uniform float Temperature <
|
||||||
|
ui_type = "slider";
|
||||||
|
ui_label = "Temperature";
|
||||||
|
ui_tooltip = "Negative cools the picture towards blue, positive warms it towards orange.";
|
||||||
|
ui_min = -100.0; ui_max = 100.0; ui_step = 1.0;
|
||||||
|
> = 0.0;
|
||||||
|
|
||||||
|
uniform float Tint <
|
||||||
|
ui_type = "slider";
|
||||||
|
ui_label = "Tint";
|
||||||
|
ui_tooltip = "Negative shifts towards green, positive towards magenta.";
|
||||||
|
ui_min = -100.0; ui_max = 100.0; ui_step = 1.0;
|
||||||
|
> = 0.0;
|
||||||
|
|
||||||
|
void VS_PostProcess(in uint id : SV_VertexID, out float4 pos : SV_Position, out float2 uv : TEXCOORD)
|
||||||
|
{
|
||||||
|
uv = float2(float(id & 2), float((id & 1) << 1));
|
||||||
|
pos = float4(uv * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
float3 WhitePointLMS(float t1, float t2)
|
||||||
|
{
|
||||||
|
float shift = 0.05;
|
||||||
|
if (t1 < 0.0)
|
||||||
|
{
|
||||||
|
shift = 0.10;
|
||||||
|
}
|
||||||
|
float x = 0.31271 - t1 * shift;
|
||||||
|
float y = 2.87 * x - 3.0 * x * x - 0.27509507 + t2 * 0.05;
|
||||||
|
|
||||||
|
float big_y = 1.0;
|
||||||
|
float big_x = big_y * x / y;
|
||||||
|
float big_z = big_y * (1.0 - x - y) / y;
|
||||||
|
|
||||||
|
return float3( 0.7328 * big_x + 0.4296 * big_y - 0.1624 * big_z,
|
||||||
|
-0.7036 * big_x + 1.6975 * big_y + 0.0061 * big_z,
|
||||||
|
0.0030 * big_x + 0.0136 * big_y + 0.9834 * big_z);
|
||||||
|
}
|
||||||
|
|
||||||
|
float4 PS_WhiteBalance(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
|
||||||
|
{
|
||||||
|
float3 rgb = pow(max(tex2D(BackBuffer, uv).rgb, 0.0), 2.2);
|
||||||
|
|
||||||
|
float3 balance = float3(0.949237, 1.03542, 1.08728) /
|
||||||
|
WhitePointLMS(Temperature / 65.0, Tint / 65.0);
|
||||||
|
|
||||||
|
const float3x3 rgb_to_lms = float3x3(0.390405, 0.549941, 0.008926,
|
||||||
|
0.070841, 0.963172, 0.001358,
|
||||||
|
0.023108, 0.128021, 0.936245);
|
||||||
|
const float3x3 lms_to_rgb = float3x3( 2.858470, -1.628790, -0.024891,
|
||||||
|
-0.210182, 1.158200, 0.000324,
|
||||||
|
-0.041812, -0.118169, 1.068670);
|
||||||
|
|
||||||
|
float3 lms = mul(rgb_to_lms, rgb) * balance;
|
||||||
|
rgb = mul(lms_to_rgb, lms);
|
||||||
|
|
||||||
|
return float4(saturate(pow(max(rgb, 0.0), 1.0 / 2.2)), 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
technique WhiteBalance <
|
||||||
|
ui_label = "White Balance";
|
||||||
|
ui_tooltip = "Corrects a picture that looks too cool, too warm or tinted.";
|
||||||
|
>
|
||||||
|
{
|
||||||
|
pass
|
||||||
|
{
|
||||||
|
VertexShader = VS_PostProcess;
|
||||||
|
PixelShader = PS_WhiteBalance;
|
||||||
|
}
|
||||||
|
}
|
||||||
+9
-10
@@ -60,7 +60,6 @@ All other dependencies will be downloaded and built by [CPM](https://github.com/
|
|||||||
* [ZLIB](https://www.zlib.net/) 1.2+
|
* [ZLIB](https://www.zlib.net/) 1.2+
|
||||||
* [zstd](https://facebook.github.io/zstd/) 1.5+
|
* [zstd](https://facebook.github.io/zstd/) 1.5+
|
||||||
* [enet](http://enet.bespin.org/) 1.3+
|
* [enet](http://enet.bespin.org/) 1.3+
|
||||||
* [Opus](https://opus-codec.org/) 1.3+
|
|
||||||
|
|
||||||
Vulkan 1.3.274+ is also needed:
|
Vulkan 1.3.274+ is also needed:
|
||||||
|
|
||||||
@@ -121,7 +120,7 @@ sudo emerge -a \
|
|||||||
dev-libs/boost dev-libs/openssl dev-libs/discord-rpc \
|
dev-libs/boost dev-libs/openssl dev-libs/discord-rpc \
|
||||||
dev-util/spirv-tools dev-util/spirv-headers dev-util/vulkan-headers \
|
dev-util/spirv-tools dev-util/spirv-headers dev-util/vulkan-headers \
|
||||||
dev-util/vulkan-utility-libraries dev-util/glslang \
|
dev-util/vulkan-utility-libraries dev-util/glslang \
|
||||||
media-gfx/renderdoc media-libs/libva media-libs/opus media-video/ffmpeg \
|
media-gfx/renderdoc media-libs/libva media-video/ffmpeg \
|
||||||
media-libs/VulkanMemoryAllocator media-libs/libsdl3 media-libs/cubeb \
|
media-libs/VulkanMemoryAllocator media-libs/libsdl3 media-libs/cubeb \
|
||||||
net-libs/enet \
|
net-libs/enet \
|
||||||
sys-libs/zlib \
|
sys-libs/zlib \
|
||||||
@@ -153,7 +152,7 @@ Required USE flags:
|
|||||||
<summary>Arch Linux</summary>
|
<summary>Arch Linux</summary>
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
sudo pacman -Syu --needed base-devel boost catch2 cmake enet ffmpeg fmt git glslang libzip lz4 ninja nlohmann-json openssl opus qt6-base qt6-multimedia qt6-charts sdl3 zlib zstd zip unzip vulkan-headers vulkan-utility-libraries libusb spirv-tools spirv-headers
|
sudo pacman -Syu --needed base-devel boost catch2 cmake enet ffmpeg fmt git glslang libzip lz4 ninja nlohmann-json openssl qt6-base qt6-multimedia qt6-charts sdl3 zlib zstd zip unzip vulkan-headers vulkan-utility-libraries libusb spirv-tools spirv-headers
|
||||||
```
|
```
|
||||||
|
|
||||||
* Building with QT Web Engine requires `qt6-webengine` as well.
|
* Building with QT Web Engine requires `qt6-webengine` as well.
|
||||||
@@ -166,7 +165,7 @@ sudo pacman -Syu --needed base-devel boost catch2 cmake enet ffmpeg fmt git glsl
|
|||||||
<summary>Ubuntu, Debian, Mint Linux</summary>
|
<summary>Ubuntu, Debian, Mint Linux</summary>
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
sudo apt-get install autoconf cmake g++ gcc git glslang-tools libglu1-mesa-dev libhidapi-dev libpulse-dev libtool libudev-dev libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-render-util0 libxcb-xinerama0 libxcb-xkb1 libxext-dev libxkbcommon-x11-0 mesa-common-dev nasm ninja-build qt6-base-private-dev catch2 libfmt-dev liblz4-dev nlohmann-json3-dev libzstd-dev libssl-dev libavfilter-dev libavcodec-dev libswscale-dev pkg-config zlib1g-dev libva-dev libvdpau-dev qt6-tools-dev qt6-charts-dev libvulkan-dev spirv-tools spirv-headers libusb-1.0-0-dev libxbyak-dev libboost-dev libboost-fiber-dev libboost-context-dev libsdl3-dev libopus-dev libasound2t64 vulkan-utility-libraries-dev
|
sudo apt-get install autoconf cmake g++ gcc git glslang-tools libglu1-mesa-dev libhidapi-dev libpulse-dev libtool libudev-dev libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-render-util0 libxcb-xinerama0 libxcb-xkb1 libxext-dev libxkbcommon-x11-0 mesa-common-dev nasm ninja-build qt6-base-private-dev catch2 libfmt-dev liblz4-dev nlohmann-json3-dev libzstd-dev libssl-dev libavfilter-dev libavcodec-dev libswscale-dev pkg-config zlib1g-dev libva-dev libvdpau-dev qt6-tools-dev qt6-charts-dev libvulkan-dev spirv-tools spirv-headers libusb-1.0-0-dev libxbyak-dev libboost-dev libboost-fiber-dev libboost-context-dev libsdl3-dev libasound2t64 vulkan-utility-libraries-dev
|
||||||
```
|
```
|
||||||
|
|
||||||
* Ubuntu 26.04, Linux Mint 22.3, or Debian 13 or later is required.
|
* Ubuntu 26.04, Linux Mint 22.3, or Debian 13 or later is required.
|
||||||
@@ -213,7 +212,7 @@ First, enable the community repository; [see here](https://wiki.alpinelinux.org/
|
|||||||
# Enable the community repository
|
# Enable the community repository
|
||||||
setup-apkrepos -c
|
setup-apkrepos -c
|
||||||
# Install
|
# Install
|
||||||
apk add g++ git cmake make mesa-dev qt6-qtbase-dev qt6-qtbase-private-dev libquazip1-qt6 ffmpeg-dev qt6-charts-dev libusb-dev libtool boost-dev sdl3-dev zstd-dev vulkan-utility-libraries spirv-tools-dev openssl-dev nlohmann-json lz4-dev opus-dev jq patch
|
apk add g++ git cmake make mesa-dev qt6-qtbase-dev qt6-qtbase-private-dev libquazip1-qt6 ffmpeg-dev qt6-charts-dev libusb-dev libtool boost-dev sdl3-dev zstd-dev vulkan-utility-libraries spirv-tools-dev openssl-dev nlohmann-json lz4-dev jq patch
|
||||||
```
|
```
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
@@ -261,7 +260,7 @@ brew install molten-vk
|
|||||||
|
|
||||||
As root run:
|
As root run:
|
||||||
```sh
|
```sh
|
||||||
pkg install devel/cmake devel/sdl3 devel/boost-libs devel/catch2 devel/libfmt devel/nlohmann-json devel/ninja devel/nasm devel/autoconf devel/pkgconf devel/qt6-base x11-toolkits/qt6-charts devel/simpleini net/enet multimedia/ffnvcodec-headers multimedia/ffmpeg audio/opus archivers/liblz4 lang/gcc12 graphics/glslang graphics/vulkan-utility-libraries graphics/spirv-tools www/cpp-httplib graphics/vulkan-utility-libraries graphics/vulkan-headers graphics/spirv-headers quazip-qt6
|
pkg install devel/cmake devel/sdl3 devel/boost-libs devel/catch2 devel/libfmt devel/nlohmann-json devel/ninja devel/nasm devel/autoconf devel/pkgconf devel/qt6-base x11-toolkits/qt6-charts devel/simpleini net/enet multimedia/ffnvcodec-headers multimedia/ffmpeg archivers/liblz4 lang/gcc12 graphics/glslang graphics/vulkan-utility-libraries graphics/spirv-tools www/cpp-httplib graphics/vulkan-utility-libraries graphics/vulkan-headers graphics/spirv-headers quazip-qt6
|
||||||
```
|
```
|
||||||
|
|
||||||
If using FreeBSD 12 or prior, use `devel/pkg-config` instead.
|
If using FreeBSD 12 or prior, use `devel/pkg-config` instead.
|
||||||
@@ -275,7 +274,7 @@ If using FreeBSD 12 or prior, use `devel/pkg-config` instead.
|
|||||||
For NetBSD +10.1:
|
For NetBSD +10.1:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
pkgin install git cmake boost fmtlib SDL3 catch2 libjwt spirv-headers spirv-tools ffmpeg7 libva nlohmann-json jq libopus qt6-qtbase qt6-qtcharts qt6-qtmultimedia qt6-qttools cpp-httplib lz4 vulkan-headers nasm autoconf enet pkg-config libusb1 libcxx frozen
|
pkgin install git cmake boost fmtlib SDL3 catch2 libjwt spirv-headers spirv-tools ffmpeg7 libva nlohmann-json jq qt6-qtbase qt6-qtcharts qt6-qtmultimedia qt6-qttools cpp-httplib lz4 vulkan-headers nasm autoconf enet pkg-config libusb1 libcxx frozen
|
||||||
```
|
```
|
||||||
|
|
||||||
[Caveats](./Caveats.md#netbsd).
|
[Caveats](./Caveats.md#netbsd).
|
||||||
@@ -306,7 +305,7 @@ pkg install gcc14 git cmake unzip nasm autoconf bash pkgconf ffmpeg glslang gmak
|
|||||||
<summary>OpenIndiana</summary>
|
<summary>OpenIndiana</summary>
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
sudo pkg install git cmake qt6 boost glslang libzip library/lz4 libusb-1 nlohmann-json openssl opus sdl3 zlib compress/zstd unzip pkg-config nasm autoconf mesa library/libdrm header-drm developer/fmt
|
sudo pkg install git cmake qt6 boost glslang libzip library/lz4 libusb-1 nlohmann-json openssl sdl3 zlib compress/zstd unzip pkg-config nasm autoconf mesa library/libdrm header-drm developer/fmt
|
||||||
```
|
```
|
||||||
|
|
||||||
[Caveats](./Caveats.md#openindiana).
|
[Caveats](./Caveats.md#openindiana).
|
||||||
@@ -330,7 +329,7 @@ sudo pkgin install git cmake autoconf build-essential libusb-1 nasm gcc13
|
|||||||
|
|
||||||
```sh
|
```sh
|
||||||
BASE="git make autoconf libtool automake-wrapper jq patch"
|
BASE="git make autoconf libtool automake-wrapper jq patch"
|
||||||
MINGW="qt6-base qt6-charts qt6-tools qt6-translations qt6-svg cmake toolchain clang python-pip openssl vulkan-memory-allocator vulkan-devel glslang boost fmt lz4 nlohmann-json zlib zstd enet opus libusb openssl SDL3"
|
MINGW="qt6-base qt6-charts qt6-tools qt6-translations qt6-svg cmake toolchain clang python-pip openssl vulkan-memory-allocator vulkan-devel glslang boost fmt lz4 nlohmann-json zlib zstd enet libusb openssl SDL3"
|
||||||
# Either x86_64 or clang-aarch64 (Windows on ARM)
|
# Either x86_64 or clang-aarch64 (Windows on ARM)
|
||||||
packages="$BASE"
|
packages="$BASE"
|
||||||
for pkg in $MINGW; do
|
for pkg in $MINGW; do
|
||||||
@@ -356,7 +355,7 @@ pacman -Syuu --needed --noconfirm $packages
|
|||||||
<summary>HaikuOS</summary>
|
<summary>HaikuOS</summary>
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
pkgman install git cmake patch libfmt_devel nlohmann_json lz4_devel opus_devel boost1.90_devel vulkan_devel qt6_base_devel qt6_declarative_devel libsdl3_devel ffmpeg7_devel libx11_devel enet_devel catch2_devel quazip1_qt5_devel qt6_5compat_devel glslang qt6_devel qt6_charts_devel cubeb_devel simpleini quazip_qt6_devel
|
pkgman install git cmake patch libfmt_devel nlohmann_json lz4_devel boost1.90_devel vulkan_devel qt6_base_devel qt6_declarative_devel libsdl3_devel ffmpeg7_devel libx11_devel enet_devel catch2_devel quazip1_qt5_devel qt6_5compat_devel glslang qt6_devel qt6_charts_devel cubeb_devel simpleini quazip_qt6_devel
|
||||||
```
|
```
|
||||||
|
|
||||||
[Caveats](./Caveats.md#haikuos).
|
[Caveats](./Caveats.md#haikuos).
|
||||||
|
|||||||
Vendored
+44
@@ -195,6 +195,50 @@ else()
|
|||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
# reshadefx
|
||||||
|
if (ENABLE_RESHADE)
|
||||||
|
AddJsonPackage(NAME reshade DOWNLOAD_ONLY)
|
||||||
|
|
||||||
|
set(RESHADEFX_SHIM_DIR ${CMAKE_CURRENT_BINARY_DIR}/reshadefx_shim)
|
||||||
|
file(WRITE ${RESHADEFX_SHIM_DIR}/spirv.hpp "#include <spirv/unified1/spirv.hpp>\n")
|
||||||
|
file(WRITE ${RESHADEFX_SHIM_DIR}/GLSL.std.450.h "#include <spirv/unified1/GLSL.std.450.h>\n")
|
||||||
|
if (APPLE)
|
||||||
|
file(WRITE ${RESHADEFX_SHIM_DIR}/malloc.h "#include <stdlib.h>\n#include <alloca.h>\n")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
add_library(reshadefx STATIC
|
||||||
|
${reshade_SOURCE_DIR}/source/effect_codegen_spirv.cpp
|
||||||
|
${reshade_SOURCE_DIR}/source/effect_expression.cpp
|
||||||
|
${reshade_SOURCE_DIR}/source/effect_lexer.cpp
|
||||||
|
${reshade_SOURCE_DIR}/source/effect_parser_exp.cpp
|
||||||
|
${reshade_SOURCE_DIR}/source/effect_parser_stmt.cpp
|
||||||
|
${reshade_SOURCE_DIR}/source/effect_preprocessor.cpp
|
||||||
|
${reshade_SOURCE_DIR}/source/effect_symbol_table.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
target_include_directories(reshadefx SYSTEM PUBLIC ${reshade_SOURCE_DIR}/source)
|
||||||
|
target_include_directories(reshadefx PRIVATE ${RESHADEFX_SHIM_DIR})
|
||||||
|
target_link_libraries(reshadefx PUBLIC SPIRV-Headers::SPIRV-Headers)
|
||||||
|
|
||||||
|
if (NOT MSVC)
|
||||||
|
target_compile_options(reshadefx PRIVATE -w -fno-char8_t)
|
||||||
|
else()
|
||||||
|
target_compile_options(reshadefx PRIVATE /w /Zc:char8_t-)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if (WIN32)
|
||||||
|
if (NOT MSVC)
|
||||||
|
target_compile_options(reshadefx PRIVATE -include share.h)
|
||||||
|
else()
|
||||||
|
target_compile_options(reshadefx PRIVATE /FIshare.h)
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if (NOT TARGET reshadefx::reshadefx)
|
||||||
|
add_library(reshadefx::reshadefx ALIAS reshadefx)
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
# Catch2
|
# Catch2
|
||||||
if (YUZU_TESTS OR DYNARMIC_TESTS)
|
if (YUZU_TESTS OR DYNARMIC_TESTS)
|
||||||
AddJsonPackage(catch2)
|
AddJsonPackage(catch2)
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ pkgs.mkShellNoCC {
|
|||||||
git cmake clang gnumake patch jq pkg-config
|
git cmake clang gnumake patch jq pkg-config
|
||||||
# libraries
|
# libraries
|
||||||
openssl boost fmt nlohmann_json lz4 zlib zstd
|
openssl boost fmt nlohmann_json lz4 zlib zstd
|
||||||
enet libopus vulkan-headers vulkan-utility-libraries
|
enet vulkan-headers vulkan-utility-libraries
|
||||||
spirv-tools spirv-headers vulkan-loader unzip
|
spirv-tools spirv-headers vulkan-loader unzip
|
||||||
glslang python3 httplib cpp-jwt ffmpeg-headless
|
glslang python3 httplib cpp-jwt ffmpeg-headless
|
||||||
libusb1 cubeb
|
libusb1 cubeb
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
|
|
||||||
package org.yuzu.yuzu_emu.dialogs
|
package org.yuzu.yuzu_emu.dialogs
|
||||||
|
|
||||||
|
import android.content.res.ColorStateList
|
||||||
|
import android.graphics.Rect
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
import android.view.View
|
import android.view.View
|
||||||
@@ -18,11 +20,28 @@ import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
|
|||||||
import org.yuzu.yuzu_emu.features.settings.model.IntSetting
|
import org.yuzu.yuzu_emu.features.settings.model.IntSetting
|
||||||
import org.yuzu.yuzu_emu.fragments.EmulationFragment
|
import org.yuzu.yuzu_emu.fragments.EmulationFragment
|
||||||
import org.yuzu.yuzu_emu.utils.NativeConfig
|
import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||||
|
import org.yuzu.yuzu_emu.utils.NativePostProcessing
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
|
import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.AbstractShortSetting
|
import org.yuzu.yuzu_emu.features.settings.model.AbstractShortSetting
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting
|
import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting
|
||||||
|
|
||||||
class QuickSettings(val emulationFragment: EmulationFragment) {
|
class QuickSettings(val emulationFragment: EmulationFragment) {
|
||||||
|
private val expandedShaders = mutableSetOf<Int>()
|
||||||
|
|
||||||
|
private fun forgetShaderSlot(index: Int) {
|
||||||
|
val shifted = mutableSetOf<Int>()
|
||||||
|
for (slot in expandedShaders) {
|
||||||
|
if (slot < index) {
|
||||||
|
shifted.add(slot)
|
||||||
|
}
|
||||||
|
if (slot > index) {
|
||||||
|
shifted.add(slot - 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
expandedShaders.clear()
|
||||||
|
expandedShaders.addAll(shifted)
|
||||||
|
}
|
||||||
|
|
||||||
private fun saveSettings() {
|
private fun saveSettings() {
|
||||||
if (emulationFragment.shouldUseCustom) {
|
if (emulationFragment.shouldUseCustom) {
|
||||||
NativeConfig.savePerGameConfig()
|
NativeConfig.savePerGameConfig()
|
||||||
@@ -232,6 +251,428 @@ class QuickSettings(val emulationFragment: EmulationFragment) {
|
|||||||
container.addView(itemView)
|
container.addView(itemView)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun addChoice(
|
||||||
|
title: String,
|
||||||
|
container: ViewGroup,
|
||||||
|
choices: List<String>,
|
||||||
|
selectedIndex: Int,
|
||||||
|
onSelected: (Int) -> Unit
|
||||||
|
) {
|
||||||
|
val inflater = LayoutInflater.from(emulationFragment.requireContext())
|
||||||
|
val itemView = inflater.inflate(R.layout.item_quick_settings_menu, container, false)
|
||||||
|
val headerView = itemView.findViewById<ViewGroup>(R.id.setting_header)
|
||||||
|
val titleView = itemView.findViewById<TextView>(R.id.setting_title)
|
||||||
|
val valueView = itemView.findViewById<TextView>(R.id.setting_value)
|
||||||
|
val expandIcon = itemView.findViewById<android.widget.ImageView>(R.id.expand_icon)
|
||||||
|
val radioGroup = itemView.findViewById<RadioGroup>(R.id.radio_group)
|
||||||
|
|
||||||
|
titleView.text = title
|
||||||
|
|
||||||
|
var current = ""
|
||||||
|
if (selectedIndex in choices.indices) {
|
||||||
|
current = choices[selectedIndex]
|
||||||
|
}
|
||||||
|
valueView.text = current
|
||||||
|
headerView.visibility = View.VISIBLE
|
||||||
|
|
||||||
|
var isExpanded = false
|
||||||
|
choices.forEachIndexed { index, name ->
|
||||||
|
val radioButton = com.google.android.material.radiobutton.MaterialRadioButton(
|
||||||
|
emulationFragment.requireContext()
|
||||||
|
)
|
||||||
|
radioButton.text = name
|
||||||
|
radioButton.id = View.generateViewId()
|
||||||
|
radioButton.isChecked = index == selectedIndex
|
||||||
|
radioButton.setPadding(16, 8, 16, 8)
|
||||||
|
|
||||||
|
radioButton.setOnCheckedChangeListener { _, isChecked ->
|
||||||
|
if (isChecked) {
|
||||||
|
valueView.text = name
|
||||||
|
onSelected(index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
radioGroup.addView(radioButton)
|
||||||
|
}
|
||||||
|
|
||||||
|
headerView.setOnClickListener {
|
||||||
|
isExpanded = !isExpanded
|
||||||
|
if (isExpanded) {
|
||||||
|
radioGroup.visibility = View.VISIBLE
|
||||||
|
expandIcon.animate().rotation(180f).setDuration(200).start()
|
||||||
|
} else {
|
||||||
|
radioGroup.visibility = View.GONE
|
||||||
|
expandIcon.animate().rotation(0f).setDuration(200).start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
container.addView(itemView)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addStepSlider(
|
||||||
|
title: String,
|
||||||
|
container: ViewGroup,
|
||||||
|
steps: Int,
|
||||||
|
selectedStep: Int,
|
||||||
|
describe: (Int) -> String,
|
||||||
|
onCommitted: (Int) -> Unit,
|
||||||
|
onChanged: (Int) -> Unit
|
||||||
|
) {
|
||||||
|
val inflater = LayoutInflater.from(emulationFragment.requireContext())
|
||||||
|
val itemView = inflater.inflate(R.layout.item_quick_settings_menu, container, false)
|
||||||
|
|
||||||
|
val sliderContainer = itemView.findViewById<ViewGroup>(R.id.slider_container)
|
||||||
|
val titleView = itemView.findViewById<TextView>(R.id.slider_title)
|
||||||
|
val valueDisplay = itemView.findViewById<TextView>(R.id.slider_value_display)
|
||||||
|
val slider = itemView.findViewById<com.google.android.material.slider.Slider>(
|
||||||
|
R.id.setting_slider
|
||||||
|
)
|
||||||
|
|
||||||
|
titleView.text = title
|
||||||
|
sliderContainer.visibility = View.VISIBLE
|
||||||
|
|
||||||
|
slider.valueFrom = 0f
|
||||||
|
slider.valueTo = steps.toFloat()
|
||||||
|
slider.stepSize = 1f
|
||||||
|
slider.value = selectedStep.toFloat().coerceIn(0f, steps.toFloat())
|
||||||
|
valueDisplay.text = describe(slider.value.toInt())
|
||||||
|
|
||||||
|
slider.addOnChangeListener { _, value, fromUser ->
|
||||||
|
if (fromUser) {
|
||||||
|
val step = value.toInt()
|
||||||
|
onChanged(step)
|
||||||
|
valueDisplay.text = describe(step)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var pressedValue = slider.value
|
||||||
|
|
||||||
|
slider.setOnTouchListener { _, event ->
|
||||||
|
val drawer = emulationFragment.view?.findViewById<DrawerLayout>(R.id.drawer_layout)
|
||||||
|
when (event.action) {
|
||||||
|
MotionEvent.ACTION_DOWN -> {
|
||||||
|
drawer?.requestDisallowInterceptTouchEvent(true)
|
||||||
|
pressedValue = slider.value
|
||||||
|
}
|
||||||
|
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
|
||||||
|
drawer?.requestDisallowInterceptTouchEvent(false)
|
||||||
|
if (slider.value != pressedValue) {
|
||||||
|
onCommitted(slider.value.toInt())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
container.addView(itemView)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addShaderCard(
|
||||||
|
index: Int,
|
||||||
|
title: String,
|
||||||
|
summary: String,
|
||||||
|
container: ViewGroup,
|
||||||
|
onRemove: () -> Unit
|
||||||
|
): ViewGroup {
|
||||||
|
val inflater = LayoutInflater.from(emulationFragment.requireContext())
|
||||||
|
val itemView = inflater.inflate(R.layout.item_quick_settings_shader, container, false)
|
||||||
|
|
||||||
|
val headerView = itemView.findViewById<ViewGroup>(R.id.shader_header)
|
||||||
|
val titleView = itemView.findViewById<TextView>(R.id.shader_title)
|
||||||
|
val summaryView = itemView.findViewById<TextView>(R.id.shader_summary)
|
||||||
|
val removeView = itemView.findViewById<android.widget.ImageView>(R.id.shader_remove)
|
||||||
|
val expandIcon = itemView.findViewById<android.widget.ImageView>(R.id.shader_expand)
|
||||||
|
val bodyView = itemView.findViewById<ViewGroup>(R.id.shader_body)
|
||||||
|
|
||||||
|
titleView.text = title
|
||||||
|
if (summary.isEmpty()) {
|
||||||
|
summaryView.visibility = View.GONE
|
||||||
|
} else {
|
||||||
|
summaryView.text = summary
|
||||||
|
}
|
||||||
|
|
||||||
|
var isExpanded = expandedShaders.contains(index)
|
||||||
|
if (isExpanded) {
|
||||||
|
bodyView.visibility = View.VISIBLE
|
||||||
|
expandIcon.rotation = 180f
|
||||||
|
}
|
||||||
|
|
||||||
|
headerView.setOnClickListener {
|
||||||
|
isExpanded = !isExpanded
|
||||||
|
if (isExpanded) {
|
||||||
|
expandedShaders.add(index)
|
||||||
|
bodyView.visibility = View.VISIBLE
|
||||||
|
expandIcon.animate().rotation(180f).setDuration(200).start()
|
||||||
|
} else {
|
||||||
|
expandedShaders.remove(index)
|
||||||
|
bodyView.visibility = View.GONE
|
||||||
|
expandIcon.animate().rotation(0f).setDuration(200).start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
removeView.setOnClickListener {
|
||||||
|
onRemove()
|
||||||
|
}
|
||||||
|
|
||||||
|
container.addView(itemView)
|
||||||
|
return bodyView
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addEffectPicker(
|
||||||
|
container: ViewGroup,
|
||||||
|
choices: List<String>,
|
||||||
|
hasEffects: Boolean,
|
||||||
|
onRemoveAll: () -> Unit,
|
||||||
|
onPicked: (Int) -> Unit
|
||||||
|
) {
|
||||||
|
val context = emulationFragment.requireContext()
|
||||||
|
val inflater = LayoutInflater.from(context)
|
||||||
|
val itemView = inflater.inflate(R.layout.item_quick_settings_add, container, false)
|
||||||
|
|
||||||
|
val button = itemView.findViewById<com.google.android.material.button.MaterialButton>(
|
||||||
|
R.id.add_button
|
||||||
|
)
|
||||||
|
val removeButton =
|
||||||
|
itemView.findViewById<com.google.android.material.button.MaterialButton>(
|
||||||
|
R.id.remove_all_button
|
||||||
|
)
|
||||||
|
val choiceGroup = itemView.findViewById<RadioGroup>(R.id.add_choices)
|
||||||
|
|
||||||
|
choices.forEachIndexed { index, name ->
|
||||||
|
val radioButton = com.google.android.material.radiobutton.MaterialRadioButton(context)
|
||||||
|
radioButton.text = name
|
||||||
|
radioButton.id = View.generateViewId()
|
||||||
|
radioButton.setPadding(16, 8, 16, 8)
|
||||||
|
radioButton.setOnCheckedChangeListener { _, isChecked ->
|
||||||
|
if (isChecked) {
|
||||||
|
onPicked(index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
choiceGroup.addView(radioButton)
|
||||||
|
}
|
||||||
|
|
||||||
|
var closedLabel = R.string.post_processing_add
|
||||||
|
if (hasEffects) {
|
||||||
|
closedLabel = R.string.post_processing_open_list
|
||||||
|
removeButton.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
button.setText(closedLabel)
|
||||||
|
|
||||||
|
val removeBackground = MaterialColors.getColor(
|
||||||
|
removeButton,
|
||||||
|
com.google.android.material.R.attr.colorErrorContainer
|
||||||
|
)
|
||||||
|
val removeForeground = MaterialColors.getColor(
|
||||||
|
removeButton,
|
||||||
|
com.google.android.material.R.attr.colorOnErrorContainer
|
||||||
|
)
|
||||||
|
removeButton.backgroundTintList = ColorStateList.valueOf(removeBackground)
|
||||||
|
removeButton.setTextColor(removeForeground)
|
||||||
|
removeButton.iconTint = ColorStateList.valueOf(removeForeground)
|
||||||
|
removeButton.setOnClickListener {
|
||||||
|
onRemoveAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
val slide = context.resources.displayMetrics.density * 24.0f
|
||||||
|
|
||||||
|
var isOpen = false
|
||||||
|
button.setOnClickListener {
|
||||||
|
isOpen = !isOpen
|
||||||
|
if (isOpen) {
|
||||||
|
choiceGroup.alpha = 0.0f
|
||||||
|
choiceGroup.translationY = slide
|
||||||
|
choiceGroup.visibility = View.VISIBLE
|
||||||
|
choiceGroup.animate()
|
||||||
|
.alpha(1.0f)
|
||||||
|
.translationY(0.0f)
|
||||||
|
.setDuration(220)
|
||||||
|
.withEndAction {
|
||||||
|
choiceGroup.requestRectangleOnScreen(
|
||||||
|
Rect(0, 0, choiceGroup.width, choiceGroup.height),
|
||||||
|
false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.start()
|
||||||
|
|
||||||
|
button.setText(R.string.post_processing_close_list)
|
||||||
|
button.setIconResource(R.drawable.ic_clear)
|
||||||
|
} else {
|
||||||
|
choiceGroup.animate()
|
||||||
|
.alpha(0.0f)
|
||||||
|
.translationY(slide)
|
||||||
|
.setDuration(160)
|
||||||
|
.withEndAction {
|
||||||
|
choiceGroup.visibility = View.GONE
|
||||||
|
}
|
||||||
|
.start()
|
||||||
|
|
||||||
|
button.setText(closedLabel)
|
||||||
|
button.setIconResource(R.drawable.ic_add)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
container.addView(itemView)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addPresetBand(container: ViewGroup, name: String, summary: String) {
|
||||||
|
val inflater = LayoutInflater.from(emulationFragment.requireContext())
|
||||||
|
val itemView = inflater.inflate(R.layout.item_quick_settings_preset, container, false)
|
||||||
|
|
||||||
|
val titleView = itemView.findViewById<TextView>(R.id.preset_title)
|
||||||
|
val summaryView = itemView.findViewById<TextView>(R.id.preset_summary)
|
||||||
|
val switchView = itemView.findViewById<MaterialSwitch>(R.id.preset_switch)
|
||||||
|
|
||||||
|
titleView.text = name
|
||||||
|
if (summary.isEmpty()) {
|
||||||
|
summaryView.visibility = View.GONE
|
||||||
|
} else {
|
||||||
|
summaryView.text = summary
|
||||||
|
}
|
||||||
|
|
||||||
|
switchView.isChecked = NativePostProcessing.isEnabled()
|
||||||
|
switchView.setOnCheckedChangeListener { _, checked ->
|
||||||
|
emulationFragment.editPostProcessing {
|
||||||
|
NativePostProcessing.setEnabled(checked)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
container.addView(itemView)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addPostProcessing(container: ViewGroup, onStructureChanged: () -> Unit) {
|
||||||
|
val usable = NativePostProcessing.catalog().filter { it.valid }
|
||||||
|
if (usable.isEmpty()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val preset = NativePostProcessing.getActivePreset()
|
||||||
|
if (preset.isNotEmpty()) {
|
||||||
|
addDivider(container)
|
||||||
|
|
||||||
|
var summary = ""
|
||||||
|
val described = NativePostProcessing.presets().firstOrNull { it.name == preset }
|
||||||
|
if (described != null) {
|
||||||
|
summary = described.description
|
||||||
|
}
|
||||||
|
if (summary.isEmpty()) {
|
||||||
|
summary =
|
||||||
|
YuzuApplication.appContext.getString(R.string.post_processing_preset_locked)
|
||||||
|
}
|
||||||
|
if (NativePostProcessing.isPresetModified()) {
|
||||||
|
summary = summary + "\n" +
|
||||||
|
YuzuApplication.appContext.getString(R.string.post_processing_preset_modified)
|
||||||
|
}
|
||||||
|
|
||||||
|
addPresetBand(container, preset, summary)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val labels = mutableListOf<String>()
|
||||||
|
val files = mutableListOf<String>()
|
||||||
|
val techniques = mutableListOf<String>()
|
||||||
|
|
||||||
|
for (effect in usable) {
|
||||||
|
for (technique in effect.techniques) {
|
||||||
|
var label = effect.label
|
||||||
|
if (effect.techniques.size > 1) {
|
||||||
|
label = effect.label + " \u00b7 " + technique
|
||||||
|
}
|
||||||
|
labels.add(label)
|
||||||
|
files.add(effect.file)
|
||||||
|
techniques.add(technique)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addDivider(container)
|
||||||
|
|
||||||
|
val chain = NativePostProcessing.chain()
|
||||||
|
chain.forEachIndexed { index, entry ->
|
||||||
|
val effect = usable.firstOrNull { it.file == entry.file }
|
||||||
|
|
||||||
|
var title = entry.file
|
||||||
|
var summary = ""
|
||||||
|
if (effect != null) {
|
||||||
|
title = effect.label
|
||||||
|
if (effect.techniques.size > 1) {
|
||||||
|
title = effect.label + " \u00b7 " + entry.technique
|
||||||
|
}
|
||||||
|
summary = effect.description
|
||||||
|
}
|
||||||
|
|
||||||
|
val body = addShaderCard(index, title, summary, container) {
|
||||||
|
emulationFragment.editPostProcessing {
|
||||||
|
NativePostProcessing.remove(index)
|
||||||
|
}
|
||||||
|
forgetShaderSlot(index)
|
||||||
|
onStructureChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (effect != null) {
|
||||||
|
for (uniform in effect.uniforms) {
|
||||||
|
addUniformSliders(body, index, uniform)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addEffectPicker(
|
||||||
|
container,
|
||||||
|
labels,
|
||||||
|
chain.isNotEmpty(),
|
||||||
|
{
|
||||||
|
emulationFragment.editPostProcessing {
|
||||||
|
NativePostProcessing.clearChain()
|
||||||
|
}
|
||||||
|
expandedShaders.clear()
|
||||||
|
onStructureChanged()
|
||||||
|
}
|
||||||
|
) { picked ->
|
||||||
|
emulationFragment.editPostProcessing {
|
||||||
|
NativePostProcessing.append(files[picked], techniques[picked])
|
||||||
|
}
|
||||||
|
onStructureChanged()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addUniformSliders(
|
||||||
|
container: ViewGroup,
|
||||||
|
index: Int,
|
||||||
|
uniform: NativePostProcessing.Uniform
|
||||||
|
) {
|
||||||
|
if (uniform.uiType == NativePostProcessing.UI_HIDDEN) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for (component in 0 until uniform.components) {
|
||||||
|
var title = uniform.label
|
||||||
|
if (uniform.components > 1) {
|
||||||
|
title = uniform.label + " [" + component + "]"
|
||||||
|
}
|
||||||
|
|
||||||
|
var value = uniform.defaultAt(component)
|
||||||
|
if (NativePostProcessing.hasValue(index, uniform.name)) {
|
||||||
|
value = NativePostProcessing.getValue(index, uniform.name, component)
|
||||||
|
}
|
||||||
|
|
||||||
|
val steps = uniform.steps
|
||||||
|
val step = Math.round((value - uniform.min) / uniform.step)
|
||||||
|
|
||||||
|
addStepSlider(
|
||||||
|
title,
|
||||||
|
container,
|
||||||
|
steps,
|
||||||
|
step,
|
||||||
|
{ position -> uniform.describe(position) },
|
||||||
|
{ emulationFragment.persistPostProcessing() }
|
||||||
|
) { position ->
|
||||||
|
NativePostProcessing.setValue(
|
||||||
|
index,
|
||||||
|
uniform.name,
|
||||||
|
component,
|
||||||
|
uniform.min + position * uniform.step
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun addDivider(container: ViewGroup) {
|
fun addDivider(container: ViewGroup) {
|
||||||
val inflater = LayoutInflater.from(emulationFragment.requireContext())
|
val inflater = LayoutInflater.from(emulationFragment.requireContext())
|
||||||
val dividerView = inflater.inflate(R.layout.item_quick_settings_divider, container, false)
|
val dividerView = inflater.inflate(R.layout.item_quick_settings_divider, container, false)
|
||||||
|
|||||||
+36
@@ -0,0 +1,36 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
package org.yuzu.yuzu_emu.features.settings.model
|
||||||
|
|
||||||
|
class FxPresetNameSetting(private val onNamed: (String) -> Unit) : AbstractStringSetting {
|
||||||
|
override val key: String
|
||||||
|
get() = "fx_preset_name"
|
||||||
|
|
||||||
|
override val defaultValue: Any
|
||||||
|
get() = ""
|
||||||
|
|
||||||
|
override val isRuntimeModifiable: Boolean
|
||||||
|
get() = true
|
||||||
|
|
||||||
|
override val pairedSettingKey: String
|
||||||
|
get() = ""
|
||||||
|
|
||||||
|
override val isSwitchable: Boolean
|
||||||
|
get() = false
|
||||||
|
|
||||||
|
override val isSaveable: Boolean
|
||||||
|
get() = true
|
||||||
|
|
||||||
|
override var global: Boolean
|
||||||
|
get() = true
|
||||||
|
set(_) {}
|
||||||
|
|
||||||
|
override fun getString(needsGlobal: Boolean): String = ""
|
||||||
|
|
||||||
|
override fun setString(value: String) = onNamed(value)
|
||||||
|
|
||||||
|
override fun getValueAsString(needsGlobal: Boolean): String = ""
|
||||||
|
|
||||||
|
override fun reset() {}
|
||||||
|
}
|
||||||
+67
@@ -0,0 +1,67 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
package org.yuzu.yuzu_emu.features.settings.model
|
||||||
|
|
||||||
|
import org.yuzu.yuzu_emu.utils.NativePostProcessing
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
|
abstract class FxUniformSetting(
|
||||||
|
protected val index: Int,
|
||||||
|
protected val uniform: NativePostProcessing.Uniform,
|
||||||
|
protected val component: Int
|
||||||
|
) : AbstractSetting {
|
||||||
|
override val key: String
|
||||||
|
get() = "fx_${index}_${uniform.name}_$component"
|
||||||
|
|
||||||
|
override val isRuntimeModifiable: Boolean
|
||||||
|
get() = true
|
||||||
|
|
||||||
|
override val pairedSettingKey: String
|
||||||
|
get() = ""
|
||||||
|
|
||||||
|
override val isSwitchable: Boolean
|
||||||
|
get() = false
|
||||||
|
|
||||||
|
override val isSaveable: Boolean
|
||||||
|
get() = true
|
||||||
|
|
||||||
|
override var global: Boolean
|
||||||
|
get() = true
|
||||||
|
set(_) {}
|
||||||
|
|
||||||
|
protected fun currentValue(): Float {
|
||||||
|
if (NativePostProcessing.hasValue(index, uniform.name)) {
|
||||||
|
return NativePostProcessing.getValue(index, uniform.name, component)
|
||||||
|
}
|
||||||
|
return uniform.defaultAt(component)
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun commit(value: Float) {
|
||||||
|
NativePostProcessing.setValue(index, uniform.name, component, value)
|
||||||
|
NativePostProcessing.store()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun reset() = commit(uniform.defaultAt(component))
|
||||||
|
}
|
||||||
|
|
||||||
|
class FxUniformSliderSetting(
|
||||||
|
index: Int,
|
||||||
|
uniform: NativePostProcessing.Uniform,
|
||||||
|
component: Int
|
||||||
|
) : FxUniformSetting(index, uniform, component), AbstractIntSetting {
|
||||||
|
override val defaultValue: Any
|
||||||
|
get() = ((uniform.defaultAt(component) - uniform.min) / uniform.step).roundToInt()
|
||||||
|
|
||||||
|
override fun getInt(needsGlobal: Boolean): Int =
|
||||||
|
((currentValue() - uniform.min) / uniform.step).roundToInt()
|
||||||
|
|
||||||
|
override fun setInt(value: Int) = commit(uniform.min + value * uniform.step)
|
||||||
|
|
||||||
|
override fun getValueAsString(needsGlobal: Boolean): String {
|
||||||
|
if (uniform.kind == NativePostProcessing.KIND_FLOAT) {
|
||||||
|
return String.format("%.3f", currentValue())
|
||||||
|
}
|
||||||
|
return currentValue().roundToInt().toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,6 +12,7 @@ object Settings {
|
|||||||
SECTION_SYSTEM(R.string.preferences_system),
|
SECTION_SYSTEM(R.string.preferences_system),
|
||||||
SECTION_RENDERER(R.string.preferences_graphics),
|
SECTION_RENDERER(R.string.preferences_graphics),
|
||||||
SECTION_FRAME_GEN(R.string.frame_gen),
|
SECTION_FRAME_GEN(R.string.frame_gen),
|
||||||
|
SECTION_POST_PROCESSING(R.string.post_processing),
|
||||||
SECTION_PERFORMANCE_STATS(R.string.stats_overlay_options),
|
SECTION_PERFORMANCE_STATS(R.string.stats_overlay_options),
|
||||||
SECTION_INPUT_OVERLAY(R.string.input_overlay_options),
|
SECTION_INPUT_OVERLAY(R.string.input_overlay_options),
|
||||||
SECTION_SOC_OVERLAY(R.string.soc_overlay_options),
|
SECTION_SOC_OVERLAY(R.string.soc_overlay_options),
|
||||||
|
|||||||
+1
@@ -13,6 +13,7 @@ enum class StringSetting(override val key: String) : AbstractStringSetting {
|
|||||||
DEVICE_NAME("device_name"),
|
DEVICE_NAME("device_name"),
|
||||||
LOG_FILTER("log_filter"),
|
LOG_FILTER("log_filter"),
|
||||||
PROGRAM_ARGS("program_args"),
|
PROGRAM_ARGS("program_args"),
|
||||||
|
POST_SHADER_CHAIN("post_shader_chain"),
|
||||||
|
|
||||||
WEB_TOKEN("eden_token"),
|
WEB_TOKEN("eden_token"),
|
||||||
WEB_USERNAME("eden_username")
|
WEB_USERNAME("eden_username")
|
||||||
|
|||||||
+13
@@ -0,0 +1,13 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
package org.yuzu.yuzu_emu.features.settings.model.view
|
||||||
|
|
||||||
|
import androidx.annotation.StringRes
|
||||||
|
|
||||||
|
class FxButtonSetting(
|
||||||
|
@StringRes titleId: Int,
|
||||||
|
val onClick: () -> Unit
|
||||||
|
) : SettingsItem(emptySetting, titleId, "", 0, "") {
|
||||||
|
override val type = TYPE_FX_BUTTON
|
||||||
|
}
|
||||||
+14
@@ -0,0 +1,14 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
package org.yuzu.yuzu_emu.features.settings.model.view
|
||||||
|
|
||||||
|
class FxPresetSetting(
|
||||||
|
titleString: String,
|
||||||
|
descriptionString: String = "",
|
||||||
|
val deletable: Boolean = false,
|
||||||
|
val onApply: () -> Unit,
|
||||||
|
val onDelete: () -> Unit = {}
|
||||||
|
) : SettingsItem(emptySetting, 0, titleString, 0, descriptionString) {
|
||||||
|
override val type = TYPE_FX_PRESET
|
||||||
|
}
|
||||||
+19
@@ -0,0 +1,19 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
package org.yuzu.yuzu_emu.features.settings.model.view
|
||||||
|
|
||||||
|
import org.yuzu.yuzu_emu.utils.NativePostProcessing
|
||||||
|
|
||||||
|
class FxShaderCardSetting(
|
||||||
|
titleString: String,
|
||||||
|
descriptionString: String,
|
||||||
|
val index: Int,
|
||||||
|
val expanded: Boolean,
|
||||||
|
val uniforms: List<NativePostProcessing.Uniform>,
|
||||||
|
val onToggle: () -> Unit,
|
||||||
|
val onRemove: () -> Unit,
|
||||||
|
val onReset: () -> Unit
|
||||||
|
) : SettingsItem(emptySetting, 0, titleString, 0, descriptionString) {
|
||||||
|
override val type = TYPE_FX_SHADER
|
||||||
|
}
|
||||||
+19
@@ -0,0 +1,19 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
package org.yuzu.yuzu_emu.features.settings.model.view
|
||||||
|
|
||||||
|
import androidx.annotation.StringRes
|
||||||
|
|
||||||
|
class FxToolbarSetting(
|
||||||
|
@StringRes val addLabelId: Int,
|
||||||
|
val listOpen: Boolean,
|
||||||
|
val presetLabel: String,
|
||||||
|
val hasEffects: Boolean,
|
||||||
|
val createPreset: StringInputSetting,
|
||||||
|
val onAdd: () -> Unit,
|
||||||
|
val onPresets: () -> Unit,
|
||||||
|
val onRemoveAll: () -> Unit
|
||||||
|
) : SettingsItem(emptySetting, 0, "", 0, "") {
|
||||||
|
override val type = TYPE_FX_TOOLBAR
|
||||||
|
}
|
||||||
+4
@@ -144,6 +144,10 @@ abstract class SettingsItem(
|
|||||||
const val TYPE_LAUNCHABLE = 13
|
const val TYPE_LAUNCHABLE = 13
|
||||||
const val TYPE_PATH = 14
|
const val TYPE_PATH = 14
|
||||||
const val TYPE_GPU_UNSWIZZLE = 15
|
const val TYPE_GPU_UNSWIZZLE = 15
|
||||||
|
const val TYPE_FX_TOOLBAR = 16
|
||||||
|
const val TYPE_FX_PRESET = 17
|
||||||
|
const val TYPE_FX_SHADER = 18
|
||||||
|
const val TYPE_FX_BUTTON = 19
|
||||||
|
|
||||||
const val FASTMEM_COMBINED = "fastmem_combined"
|
const val FASTMEM_COMBINED = "fastmem_combined"
|
||||||
const val GPU_UNSWIZZLE_COMBINED = "gpu_unswizzle_combined"
|
const val GPU_UNSWIZZLE_COMBINED = "gpu_unswizzle_combined"
|
||||||
|
|||||||
+32
@@ -23,6 +23,10 @@ import com.google.android.material.timepicker.TimeFormat
|
|||||||
import org.yuzu.yuzu_emu.R
|
import org.yuzu.yuzu_emu.R
|
||||||
import org.yuzu.yuzu_emu.SettingsNavigationDirections
|
import org.yuzu.yuzu_emu.SettingsNavigationDirections
|
||||||
import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
|
import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
|
||||||
|
import org.yuzu.yuzu_emu.databinding.ListItemSettingFxButtonBinding
|
||||||
|
import org.yuzu.yuzu_emu.databinding.ListItemSettingFxPresetBinding
|
||||||
|
import org.yuzu.yuzu_emu.databinding.ListItemSettingFxShaderBinding
|
||||||
|
import org.yuzu.yuzu_emu.databinding.ListItemSettingFxToolbarBinding
|
||||||
import org.yuzu.yuzu_emu.databinding.ListItemSettingInputBinding
|
import org.yuzu.yuzu_emu.databinding.ListItemSettingInputBinding
|
||||||
import org.yuzu.yuzu_emu.databinding.ListItemSettingSwitchBinding
|
import org.yuzu.yuzu_emu.databinding.ListItemSettingSwitchBinding
|
||||||
import org.yuzu.yuzu_emu.databinding.ListItemSettingsHeaderBinding
|
import org.yuzu.yuzu_emu.databinding.ListItemSettingsHeaderBinding
|
||||||
@@ -106,6 +110,34 @@ class SettingsAdapter(
|
|||||||
GpuUnswizzleViewHolder(ListItemSettingBinding.inflate(inflater), this)
|
GpuUnswizzleViewHolder(ListItemSettingBinding.inflate(inflater), this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SettingsItem.TYPE_FX_TOOLBAR -> {
|
||||||
|
FxToolbarViewHolder(
|
||||||
|
ListItemSettingFxToolbarBinding.inflate(inflater, parent, false),
|
||||||
|
this
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsItem.TYPE_FX_PRESET -> {
|
||||||
|
FxPresetViewHolder(
|
||||||
|
ListItemSettingFxPresetBinding.inflate(inflater, parent, false),
|
||||||
|
this
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsItem.TYPE_FX_SHADER -> {
|
||||||
|
FxShaderCardViewHolder(
|
||||||
|
ListItemSettingFxShaderBinding.inflate(inflater, parent, false),
|
||||||
|
this
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsItem.TYPE_FX_BUTTON -> {
|
||||||
|
FxButtonViewHolder(
|
||||||
|
ListItemSettingFxButtonBinding.inflate(inflater, parent, false),
|
||||||
|
this
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
HeaderViewHolder(ListItemSettingsHeaderBinding.inflate(inflater), this)
|
HeaderViewHolder(ListItemSettingsHeaderBinding.inflate(inflater), this)
|
||||||
}
|
}
|
||||||
|
|||||||
+214
@@ -18,6 +18,7 @@ import org.yuzu.yuzu_emu.features.input.model.NpadStyleIndex
|
|||||||
import org.yuzu.yuzu_emu.features.settings.model.AbstractBooleanSetting
|
import org.yuzu.yuzu_emu.features.settings.model.AbstractBooleanSetting
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting
|
import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
|
import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
|
||||||
|
import org.yuzu.yuzu_emu.features.settings.model.FxPresetNameSetting
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.ByteSetting
|
import org.yuzu.yuzu_emu.features.settings.model.ByteSetting
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.IntSetting
|
import org.yuzu.yuzu_emu.features.settings.model.IntSetting
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.LongSetting
|
import org.yuzu.yuzu_emu.features.settings.model.LongSetting
|
||||||
@@ -30,6 +31,7 @@ import org.yuzu.yuzu_emu.features.settings.model.view.*
|
|||||||
import org.yuzu.yuzu_emu.utils.InputHandler
|
import org.yuzu.yuzu_emu.utils.InputHandler
|
||||||
import org.yuzu.yuzu_emu.utils.LosslessScalingHelper
|
import org.yuzu.yuzu_emu.utils.LosslessScalingHelper
|
||||||
import org.yuzu.yuzu_emu.utils.NativeConfig
|
import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||||
|
import org.yuzu.yuzu_emu.utils.NativePostProcessing
|
||||||
import org.yuzu.yuzu_emu.utils.DirectoryInitialization
|
import org.yuzu.yuzu_emu.utils.DirectoryInitialization
|
||||||
import org.yuzu.yuzu_emu.utils.FullscreenHelper
|
import org.yuzu.yuzu_emu.utils.FullscreenHelper
|
||||||
import androidx.core.content.edit
|
import androidx.core.content.edit
|
||||||
@@ -44,6 +46,14 @@ class SettingsFragmentPresenter(
|
|||||||
) {
|
) {
|
||||||
private var settingsList = ArrayList<SettingsItem>()
|
private var settingsList = ArrayList<SettingsItem>()
|
||||||
|
|
||||||
|
private val expandedShaderSlots = mutableSetOf<Int>()
|
||||||
|
|
||||||
|
private var shaderPickerOpen = false
|
||||||
|
|
||||||
|
private var presetPickerOpen = false
|
||||||
|
|
||||||
|
private var postProcessingSynced = false
|
||||||
|
|
||||||
private val context get() = YuzuApplication.appContext
|
private val context get() = YuzuApplication.appContext
|
||||||
|
|
||||||
// Extension for altering settings list based on each setting's properties
|
// Extension for altering settings list based on each setting's properties
|
||||||
@@ -163,6 +173,7 @@ class SettingsFragmentPresenter(
|
|||||||
MenuTag.SECTION_SYSTEM -> addSystemSettings(sl)
|
MenuTag.SECTION_SYSTEM -> addSystemSettings(sl)
|
||||||
MenuTag.SECTION_RENDERER -> addGraphicsSettings(sl)
|
MenuTag.SECTION_RENDERER -> addGraphicsSettings(sl)
|
||||||
MenuTag.SECTION_FRAME_GEN -> addFrameGenSettings(sl)
|
MenuTag.SECTION_FRAME_GEN -> addFrameGenSettings(sl)
|
||||||
|
MenuTag.SECTION_POST_PROCESSING -> addPostProcessingSettings(sl)
|
||||||
MenuTag.SECTION_PERFORMANCE_STATS -> addPerformanceOverlaySettings(sl)
|
MenuTag.SECTION_PERFORMANCE_STATS -> addPerformanceOverlaySettings(sl)
|
||||||
MenuTag.SECTION_SOC_OVERLAY -> addSocOverlaySettings(sl)
|
MenuTag.SECTION_SOC_OVERLAY -> addSocOverlaySettings(sl)
|
||||||
MenuTag.SECTION_INPUT_OVERLAY -> addInputOverlaySettings(sl)
|
MenuTag.SECTION_INPUT_OVERLAY -> addInputOverlaySettings(sl)
|
||||||
@@ -190,6 +201,209 @@ class SettingsFragmentPresenter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun addPostProcessingSettings(sl: ArrayList<SettingsItem>) {
|
||||||
|
if (!postProcessingSynced) {
|
||||||
|
postProcessingSynced = true
|
||||||
|
NativePostProcessing.reload()
|
||||||
|
}
|
||||||
|
|
||||||
|
val usable = NativePostProcessing.catalog().filter { it.valid }
|
||||||
|
|
||||||
|
sl.apply {
|
||||||
|
if (usable.isEmpty()) {
|
||||||
|
add(
|
||||||
|
RunnableSetting(
|
||||||
|
titleId = R.string.post_processing_empty,
|
||||||
|
descriptionString = NativePostProcessing.getShaderDirectory(),
|
||||||
|
isRunnable = false
|
||||||
|
) {}
|
||||||
|
)
|
||||||
|
return@apply
|
||||||
|
}
|
||||||
|
|
||||||
|
val labels = mutableListOf<String>()
|
||||||
|
val summaries = mutableListOf<String>()
|
||||||
|
val files = mutableListOf<String>()
|
||||||
|
val techniques = mutableListOf<String>()
|
||||||
|
for (effect in usable) {
|
||||||
|
for (technique in effect.techniques) {
|
||||||
|
if (effect.techniques.size == 1) {
|
||||||
|
labels.add(effect.label)
|
||||||
|
} else {
|
||||||
|
labels.add(effect.label + " \u00b7 " + technique)
|
||||||
|
}
|
||||||
|
summaries.add(effect.description)
|
||||||
|
files.add(effect.file)
|
||||||
|
techniques.add(technique)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val chain = NativePostProcessing.chain()
|
||||||
|
val active = NativePostProcessing.getActivePreset()
|
||||||
|
|
||||||
|
var addLabel = R.string.post_processing_add
|
||||||
|
if (chain.isNotEmpty()) {
|
||||||
|
addLabel = R.string.post_processing_open_list
|
||||||
|
}
|
||||||
|
if (shaderPickerOpen) {
|
||||||
|
addLabel = R.string.post_processing_close_list
|
||||||
|
}
|
||||||
|
|
||||||
|
var presetLabel = context.getString(R.string.post_processing_presets)
|
||||||
|
if (active.isNotEmpty()) {
|
||||||
|
presetLabel = active
|
||||||
|
}
|
||||||
|
|
||||||
|
val createPreset = StringInputSetting(
|
||||||
|
setting = FxPresetNameSetting { name ->
|
||||||
|
NativePostProcessing.savePreset(name, "")
|
||||||
|
settingsViewModel.setReloadListAndNotifyDataset(true)
|
||||||
|
},
|
||||||
|
titleId = R.string.post_processing_preset_new,
|
||||||
|
descriptionId = R.string.post_processing_preset_new_description,
|
||||||
|
validator = { it != null && it.isNotBlank() && !it.contains('=') },
|
||||||
|
errorId = R.string.post_processing_preset_name_invalid
|
||||||
|
)
|
||||||
|
|
||||||
|
add(
|
||||||
|
FxToolbarSetting(
|
||||||
|
addLabelId = addLabel,
|
||||||
|
listOpen = shaderPickerOpen,
|
||||||
|
presetLabel = presetLabel,
|
||||||
|
hasEffects = chain.isNotEmpty(),
|
||||||
|
createPreset = createPreset,
|
||||||
|
onAdd = {
|
||||||
|
shaderPickerOpen = !shaderPickerOpen
|
||||||
|
presetPickerOpen = false
|
||||||
|
settingsViewModel.setReloadListAndNotifyDataset(true)
|
||||||
|
},
|
||||||
|
onPresets = {
|
||||||
|
presetPickerOpen = !presetPickerOpen
|
||||||
|
shaderPickerOpen = false
|
||||||
|
settingsViewModel.setReloadListAndNotifyDataset(true)
|
||||||
|
},
|
||||||
|
onRemoveAll = {
|
||||||
|
NativePostProcessing.clearChain()
|
||||||
|
NativePostProcessing.clearPreset()
|
||||||
|
NativePostProcessing.store()
|
||||||
|
expandedShaderSlots.clear()
|
||||||
|
shaderPickerOpen = false
|
||||||
|
settingsViewModel.setReloadListAndNotifyDataset(true)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if (shaderPickerOpen) {
|
||||||
|
for (choice in labels.indices) {
|
||||||
|
add(
|
||||||
|
RunnableSetting(
|
||||||
|
titleString = labels[choice],
|
||||||
|
descriptionString = summaries[choice],
|
||||||
|
isRunnable = true
|
||||||
|
) {
|
||||||
|
NativePostProcessing.append(files[choice], techniques[choice])
|
||||||
|
NativePostProcessing.store()
|
||||||
|
shaderPickerOpen = false
|
||||||
|
settingsViewModel.setReloadListAndNotifyDataset(true)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (presetPickerOpen) {
|
||||||
|
add(
|
||||||
|
FxPresetSetting(
|
||||||
|
titleString = context.getString(R.string.post_processing_preset_none),
|
||||||
|
onApply = {
|
||||||
|
NativePostProcessing.clearPreset()
|
||||||
|
presetPickerOpen = false
|
||||||
|
settingsViewModel.setReloadListAndNotifyDataset(true)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
for (preset in NativePostProcessing.presets()) {
|
||||||
|
add(
|
||||||
|
FxPresetSetting(
|
||||||
|
titleString = preset.name,
|
||||||
|
descriptionString = preset.description,
|
||||||
|
deletable = !preset.bundled,
|
||||||
|
onApply = {
|
||||||
|
NativePostProcessing.applyPreset(preset.name)
|
||||||
|
NativePostProcessing.store()
|
||||||
|
presetPickerOpen = false
|
||||||
|
expandedShaderSlots.clear()
|
||||||
|
settingsViewModel.setReloadListAndNotifyDataset(true)
|
||||||
|
},
|
||||||
|
onDelete = {
|
||||||
|
NativePostProcessing.deletePreset(preset.name)
|
||||||
|
settingsViewModel.setReloadListAndNotifyDataset(true)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (active.isNotEmpty()) {
|
||||||
|
add(
|
||||||
|
FxButtonSetting(titleId = R.string.post_processing_preset_reset) {
|
||||||
|
NativePostProcessing.applyPreset(active)
|
||||||
|
NativePostProcessing.store()
|
||||||
|
expandedShaderSlots.clear()
|
||||||
|
settingsViewModel.setReloadListAndNotifyDataset(true)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
for (index in chain.indices) {
|
||||||
|
val entry = chain[index]
|
||||||
|
val effect = usable.firstOrNull { it.file == entry.file }
|
||||||
|
|
||||||
|
var header = entry.file
|
||||||
|
var summary = ""
|
||||||
|
var uniforms = emptyList<NativePostProcessing.Uniform>()
|
||||||
|
if (effect != null) {
|
||||||
|
header = effect.label
|
||||||
|
if (effect.techniques.size > 1) {
|
||||||
|
header = effect.label + " \u00b7 " + entry.technique
|
||||||
|
}
|
||||||
|
summary = effect.description
|
||||||
|
uniforms = effect.uniforms
|
||||||
|
}
|
||||||
|
|
||||||
|
val isOpen = expandedShaderSlots.contains(index)
|
||||||
|
|
||||||
|
add(
|
||||||
|
FxShaderCardSetting(
|
||||||
|
titleString = header,
|
||||||
|
descriptionString = summary,
|
||||||
|
index = index,
|
||||||
|
expanded = isOpen,
|
||||||
|
uniforms = uniforms,
|
||||||
|
onToggle = {
|
||||||
|
if (isOpen) {
|
||||||
|
expandedShaderSlots.remove(index)
|
||||||
|
} else {
|
||||||
|
expandedShaderSlots.add(index)
|
||||||
|
}
|
||||||
|
settingsViewModel.setReloadListAndNotifyDataset(true)
|
||||||
|
},
|
||||||
|
onRemove = {
|
||||||
|
NativePostProcessing.remove(index)
|
||||||
|
NativePostProcessing.store()
|
||||||
|
expandedShaderSlots.clear()
|
||||||
|
settingsViewModel.setReloadListAndNotifyDataset(true)
|
||||||
|
},
|
||||||
|
onReset = {
|
||||||
|
NativePostProcessing.resetValues(index)
|
||||||
|
NativePostProcessing.store()
|
||||||
|
settingsViewModel.setReloadListAndNotifyDataset(true)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun addConfigSettings(sl: ArrayList<SettingsItem>) {
|
private fun addConfigSettings(sl: ArrayList<SettingsItem>) {
|
||||||
sl.apply {
|
sl.apply {
|
||||||
add(
|
add(
|
||||||
|
|||||||
+28
@@ -0,0 +1,28 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
package org.yuzu.yuzu_emu.features.settings.ui.viewholder
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
|
import org.yuzu.yuzu_emu.databinding.ListItemSettingFxButtonBinding
|
||||||
|
import org.yuzu.yuzu_emu.features.settings.model.view.FxButtonSetting
|
||||||
|
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
|
||||||
|
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
|
||||||
|
|
||||||
|
class FxButtonViewHolder(
|
||||||
|
val binding: ListItemSettingFxButtonBinding,
|
||||||
|
adapter: SettingsAdapter
|
||||||
|
) : SettingViewHolder(binding.root, adapter) {
|
||||||
|
private lateinit var setting: FxButtonSetting
|
||||||
|
|
||||||
|
override fun bind(item: SettingsItem) {
|
||||||
|
setting = item as FxButtonSetting
|
||||||
|
|
||||||
|
binding.fxButton.text = item.title
|
||||||
|
binding.fxButton.setOnClickListener { setting.onClick.invoke() }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onClick(clicked: View) {}
|
||||||
|
|
||||||
|
override fun onLongClick(clicked: View): Boolean = true
|
||||||
|
}
|
||||||
+35
@@ -0,0 +1,35 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
package org.yuzu.yuzu_emu.features.settings.ui.viewholder
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
|
import org.yuzu.yuzu_emu.databinding.ListItemSettingFxPresetBinding
|
||||||
|
import org.yuzu.yuzu_emu.features.settings.model.view.FxPresetSetting
|
||||||
|
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
|
||||||
|
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
|
||||||
|
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
||||||
|
|
||||||
|
class FxPresetViewHolder(
|
||||||
|
val binding: ListItemSettingFxPresetBinding,
|
||||||
|
adapter: SettingsAdapter
|
||||||
|
) : SettingViewHolder(binding.root, adapter) {
|
||||||
|
private lateinit var setting: FxPresetSetting
|
||||||
|
|
||||||
|
override fun bind(item: SettingsItem) {
|
||||||
|
setting = item as FxPresetSetting
|
||||||
|
|
||||||
|
binding.presetName.text = item.title
|
||||||
|
binding.presetDescription.text = item.description
|
||||||
|
binding.presetDescription.setVisible(item.description.isNotEmpty())
|
||||||
|
|
||||||
|
binding.presetRow.setOnClickListener { setting.onApply.invoke() }
|
||||||
|
|
||||||
|
binding.presetDelete.setVisible(setting.deletable)
|
||||||
|
binding.presetDelete.setOnClickListener { setting.onDelete.invoke() }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onClick(clicked: View) {}
|
||||||
|
|
||||||
|
override fun onLongClick(clicked: View): Boolean = true
|
||||||
|
}
|
||||||
+100
@@ -0,0 +1,100 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
package org.yuzu.yuzu_emu.features.settings.ui.viewholder
|
||||||
|
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import org.yuzu.yuzu_emu.databinding.ItemSettingFxActionsBinding
|
||||||
|
import org.yuzu.yuzu_emu.databinding.ItemSettingFxSliderBinding
|
||||||
|
import org.yuzu.yuzu_emu.databinding.ListItemSettingFxShaderBinding
|
||||||
|
import org.yuzu.yuzu_emu.features.settings.model.FxUniformSliderSetting
|
||||||
|
import org.yuzu.yuzu_emu.features.settings.model.view.FxShaderCardSetting
|
||||||
|
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
|
||||||
|
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
|
||||||
|
import org.yuzu.yuzu_emu.utils.NativePostProcessing
|
||||||
|
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
||||||
|
|
||||||
|
class FxShaderCardViewHolder(
|
||||||
|
val binding: ListItemSettingFxShaderBinding,
|
||||||
|
adapter: SettingsAdapter
|
||||||
|
) : SettingViewHolder(binding.root, adapter) {
|
||||||
|
private lateinit var setting: FxShaderCardSetting
|
||||||
|
|
||||||
|
override fun bind(item: SettingsItem) {
|
||||||
|
setting = item as FxShaderCardSetting
|
||||||
|
|
||||||
|
binding.shaderTitle.text = item.title
|
||||||
|
binding.shaderSummary.text = item.description
|
||||||
|
binding.shaderSummary.setVisible(item.description.isNotEmpty())
|
||||||
|
|
||||||
|
var rotation = 0f
|
||||||
|
if (setting.expanded) {
|
||||||
|
rotation = 180f
|
||||||
|
}
|
||||||
|
binding.shaderExpand.rotation = rotation
|
||||||
|
|
||||||
|
binding.shaderHeader.setOnClickListener { setting.onToggle.invoke() }
|
||||||
|
binding.shaderRemove.setOnClickListener { setting.onRemove.invoke() }
|
||||||
|
|
||||||
|
binding.shaderBody.removeAllViews()
|
||||||
|
binding.shaderBody.setVisible(setting.expanded)
|
||||||
|
if (!setting.expanded) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val inflater = LayoutInflater.from(binding.root.context)
|
||||||
|
for (uniform in setting.uniforms) {
|
||||||
|
if (uniform.uiType == NativePostProcessing.UI_HIDDEN) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for (component in 0 until uniform.components) {
|
||||||
|
addSlider(inflater, uniform, component)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addActions(inflater)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addSlider(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
uniform: NativePostProcessing.Uniform,
|
||||||
|
component: Int
|
||||||
|
) {
|
||||||
|
val row = ItemSettingFxSliderBinding.inflate(inflater, binding.shaderBody, false)
|
||||||
|
val value = FxUniformSliderSetting(setting.index, uniform, component)
|
||||||
|
|
||||||
|
var title = uniform.label
|
||||||
|
if (uniform.components > 1) {
|
||||||
|
title = uniform.label + " [" + component + "]"
|
||||||
|
}
|
||||||
|
row.fxSliderTitle.text = title
|
||||||
|
|
||||||
|
val steps = uniform.steps.toFloat()
|
||||||
|
row.fxSlider.valueFrom = 0f
|
||||||
|
row.fxSlider.valueTo = steps
|
||||||
|
row.fxSlider.stepSize = 1f
|
||||||
|
row.fxSlider.value = value.getInt(false).toFloat().coerceIn(0f, steps)
|
||||||
|
row.fxSliderValue.text = uniform.describe(row.fxSlider.value.toInt())
|
||||||
|
|
||||||
|
row.fxSlider.addOnChangeListener { _, position, fromUser ->
|
||||||
|
if (fromUser) {
|
||||||
|
value.setInt(position.toInt())
|
||||||
|
row.fxSliderValue.text = uniform.describe(position.toInt())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.shaderBody.addView(row.root)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addActions(inflater: LayoutInflater) {
|
||||||
|
val row = ItemSettingFxActionsBinding.inflate(inflater, binding.shaderBody, false)
|
||||||
|
|
||||||
|
row.fxReset.setOnClickListener { setting.onReset.invoke() }
|
||||||
|
|
||||||
|
binding.shaderBody.addView(row.root)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onClick(clicked: View) {}
|
||||||
|
|
||||||
|
override fun onLongClick(clicked: View): Boolean = true
|
||||||
|
}
|
||||||
+57
@@ -0,0 +1,57 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
package org.yuzu.yuzu_emu.features.settings.ui.viewholder
|
||||||
|
|
||||||
|
import android.content.res.ColorStateList
|
||||||
|
import android.view.View
|
||||||
|
import com.google.android.material.color.MaterialColors
|
||||||
|
import org.yuzu.yuzu_emu.R
|
||||||
|
import org.yuzu.yuzu_emu.databinding.ListItemSettingFxToolbarBinding
|
||||||
|
import org.yuzu.yuzu_emu.features.settings.model.view.FxToolbarSetting
|
||||||
|
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
|
||||||
|
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
|
||||||
|
|
||||||
|
class FxToolbarViewHolder(
|
||||||
|
val binding: ListItemSettingFxToolbarBinding,
|
||||||
|
adapter: SettingsAdapter
|
||||||
|
) : SettingViewHolder(binding.root, adapter) {
|
||||||
|
private lateinit var setting: FxToolbarSetting
|
||||||
|
|
||||||
|
override fun bind(item: SettingsItem) {
|
||||||
|
setting = item as FxToolbarSetting
|
||||||
|
|
||||||
|
binding.fxAdd.setText(setting.addLabelId)
|
||||||
|
var addIcon = R.drawable.ic_add
|
||||||
|
if (setting.listOpen) {
|
||||||
|
addIcon = R.drawable.ic_clear
|
||||||
|
}
|
||||||
|
binding.fxAdd.setIconResource(addIcon)
|
||||||
|
binding.fxAdd.setOnClickListener { setting.onAdd.invoke() }
|
||||||
|
|
||||||
|
binding.fxPresets.text = setting.presetLabel
|
||||||
|
binding.fxPresets.setOnClickListener { setting.onPresets.invoke() }
|
||||||
|
|
||||||
|
binding.fxCreatePreset.isEnabled = setting.hasEffects
|
||||||
|
binding.fxCreatePreset.setOnClickListener {
|
||||||
|
adapter.onStringInputClick(setting.createPreset, bindingAdapterPosition)
|
||||||
|
}
|
||||||
|
|
||||||
|
val removeBackground = MaterialColors.getColor(
|
||||||
|
binding.fxRemoveAll,
|
||||||
|
com.google.android.material.R.attr.colorErrorContainer
|
||||||
|
)
|
||||||
|
val removeForeground = MaterialColors.getColor(
|
||||||
|
binding.fxRemoveAll,
|
||||||
|
com.google.android.material.R.attr.colorOnErrorContainer
|
||||||
|
)
|
||||||
|
binding.fxRemoveAll.backgroundTintList = ColorStateList.valueOf(removeBackground)
|
||||||
|
binding.fxRemoveAll.iconTint = ColorStateList.valueOf(removeForeground)
|
||||||
|
binding.fxRemoveAll.isEnabled = setting.hasEffects
|
||||||
|
binding.fxRemoveAll.setOnClickListener { setting.onRemoveAll.invoke() }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onClick(clicked: View) {}
|
||||||
|
|
||||||
|
override fun onLongClick(clicked: View): Boolean = true
|
||||||
|
}
|
||||||
@@ -94,6 +94,7 @@ import org.yuzu.yuzu_emu.utils.InputHandler
|
|||||||
import org.yuzu.yuzu_emu.utils.Log
|
import org.yuzu.yuzu_emu.utils.Log
|
||||||
import org.yuzu.yuzu_emu.utils.NativeConfig
|
import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||||
import org.yuzu.yuzu_emu.utils.NativeFreedrenoConfig
|
import org.yuzu.yuzu_emu.utils.NativeFreedrenoConfig
|
||||||
|
import org.yuzu.yuzu_emu.utils.NativePostProcessing
|
||||||
import org.yuzu.yuzu_emu.utils.ViewUtils
|
import org.yuzu.yuzu_emu.utils.ViewUtils
|
||||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
||||||
import org.yuzu.yuzu_emu.utils.collect
|
import org.yuzu.yuzu_emu.utils.collect
|
||||||
@@ -883,6 +884,8 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||||||
if (shouldUseCustom) {
|
if (shouldUseCustom) {
|
||||||
SettingsFile.loadCustomConfig(game!!)
|
SettingsFile.loadCustomConfig(game!!)
|
||||||
}
|
}
|
||||||
|
refreshPostProcessing()
|
||||||
|
addQuickSettings()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1087,6 +1090,34 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun withPerGameConfig(create: Boolean, action: () -> Unit) {
|
||||||
|
val target = game
|
||||||
|
var owned = false
|
||||||
|
if (target != null && !NativeConfig.isPerGameConfigLoaded()) {
|
||||||
|
if (create || SettingsFile.getCustomSettingsFile(target).exists()) {
|
||||||
|
SettingsFile.loadCustomConfig(target)
|
||||||
|
owned = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
action()
|
||||||
|
if (owned) {
|
||||||
|
NativeConfig.unloadPerGameConfig()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun refreshPostProcessing() = withPerGameConfig(false) {
|
||||||
|
NativePostProcessing.reload()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun persistPostProcessing() = withPerGameConfig(true) {
|
||||||
|
NativePostProcessing.persist()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun editPostProcessing(action: () -> Unit) = withPerGameConfig(true) {
|
||||||
|
action()
|
||||||
|
NativePostProcessing.persist()
|
||||||
|
}
|
||||||
|
|
||||||
private fun addQuickSettings() {
|
private fun addQuickSettings() {
|
||||||
binding.quickSettingsSheet.apply {
|
binding.quickSettingsSheet.apply {
|
||||||
val container = binding.quickSettingsSheet.findViewById<ViewGroup>(R.id.quick_settings_container)
|
val container = binding.quickSettingsSheet.findViewById<ViewGroup>(R.id.quick_settings_container)
|
||||||
@@ -1194,6 +1225,10 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||||||
R.array.rendererAntiAliasingNames,
|
R.array.rendererAntiAliasingNames,
|
||||||
R.array.rendererAntiAliasingValues
|
R.array.rendererAntiAliasingValues
|
||||||
)
|
)
|
||||||
|
|
||||||
|
quickSettings.addPostProcessing(container) {
|
||||||
|
addQuickSettings()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -383,6 +383,20 @@ class GamePropertiesFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
add(
|
||||||
|
SubmenuProperty(
|
||||||
|
R.string.post_processing,
|
||||||
|
R.string.post_processing_per_game_description,
|
||||||
|
R.drawable.ic_post_processing,
|
||||||
|
action = {
|
||||||
|
val action = HomeNavigationDirections.actionGlobalSettingsActivity(
|
||||||
|
args.game,
|
||||||
|
Settings.MenuTag.SECTION_POST_PROCESSING
|
||||||
|
)
|
||||||
|
binding.root.findNavController().navigate(action)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
if (GpuDriverHelper.isAdrenoGpu()) {
|
if (GpuDriverHelper.isAdrenoGpu()) {
|
||||||
add(
|
add(
|
||||||
|
|||||||
@@ -171,6 +171,20 @@ class HomeSettingsFragment : Fragment() {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
add(
|
||||||
|
HomeSetting(
|
||||||
|
R.string.post_processing,
|
||||||
|
R.string.post_processing_description,
|
||||||
|
R.drawable.ic_post_processing,
|
||||||
|
{
|
||||||
|
val action = HomeNavigationDirections.actionGlobalSettingsActivity(
|
||||||
|
null,
|
||||||
|
Settings.MenuTag.SECTION_POST_PROCESSING
|
||||||
|
)
|
||||||
|
binding.root.findNavController().navigate(action)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
add(
|
add(
|
||||||
HomeSetting(
|
HomeSetting(
|
||||||
R.string.lossless_scaling,
|
R.string.lossless_scaling,
|
||||||
|
|||||||
@@ -79,13 +79,6 @@ class LicensesFragment : Fragment() {
|
|||||||
R.string.license_ffmpeg_copyright,
|
R.string.license_ffmpeg_copyright,
|
||||||
R.string.license_ffmpeg_text
|
R.string.license_ffmpeg_text
|
||||||
),
|
),
|
||||||
License(
|
|
||||||
R.string.license_opus,
|
|
||||||
R.string.license_opus_description,
|
|
||||||
R.string.license_opus_link,
|
|
||||||
R.string.license_opus_copyright,
|
|
||||||
R.string.license_opus_text
|
|
||||||
),
|
|
||||||
License(
|
License(
|
||||||
R.string.license_sirit,
|
R.string.license_sirit,
|
||||||
R.string.license_sirit_description,
|
R.string.license_sirit_description,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||||
@@ -37,7 +37,7 @@ class Game(
|
|||||||
|
|
||||||
val settingsName: String
|
val settingsName: String
|
||||||
get() {
|
get() {
|
||||||
val programIdLong = programId.toLong()
|
val programIdLong = programId.toLongOrNull() ?: 0L
|
||||||
return if (programIdLong == 0L) {
|
return if (programIdLong == 0L) {
|
||||||
FileUtil.getFilename(Uri.parse(path))
|
FileUtil.getFilename(Uri.parse(path))
|
||||||
} else {
|
} else {
|
||||||
@@ -47,7 +47,7 @@ class Game(
|
|||||||
|
|
||||||
val programIdHex: String
|
val programIdHex: String
|
||||||
get() {
|
get() {
|
||||||
val programIdLong = programId.toLong()
|
val programIdLong = programId.toLongOrNull() ?: 0L
|
||||||
return if (programIdLong == 0L) {
|
return if (programIdLong == 0L) {
|
||||||
"0"
|
"0"
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -0,0 +1,242 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
package org.yuzu.yuzu_emu.utils
|
||||||
|
|
||||||
|
import org.json.JSONArray
|
||||||
|
import org.json.JSONObject
|
||||||
|
|
||||||
|
object NativePostProcessing {
|
||||||
|
const val KIND_BOOL = 0
|
||||||
|
const val KIND_INT = 1
|
||||||
|
const val KIND_FLOAT = 2
|
||||||
|
|
||||||
|
const val UI_HIDDEN = 0
|
||||||
|
const val UI_SLIDER = 1
|
||||||
|
const val UI_DRAG = 2
|
||||||
|
const val UI_COMBO = 3
|
||||||
|
const val UI_RADIO = 4
|
||||||
|
const val UI_CHECKBOX = 5
|
||||||
|
const val UI_COLOR = 6
|
||||||
|
const val UI_INPUT_BOX = 7
|
||||||
|
|
||||||
|
external fun getCatalogJson(): String
|
||||||
|
|
||||||
|
external fun getChainJson(): String
|
||||||
|
|
||||||
|
external fun append(file: String, technique: String)
|
||||||
|
|
||||||
|
external fun replace(index: Int, file: String, technique: String)
|
||||||
|
|
||||||
|
external fun remove(index: Int)
|
||||||
|
|
||||||
|
external fun move(index: Int, delta: Int)
|
||||||
|
|
||||||
|
external fun resetValues(index: Int)
|
||||||
|
|
||||||
|
external fun getValue(index: Int, uniform: String, component: Int): Float
|
||||||
|
|
||||||
|
external fun hasValue(index: Int, uniform: String): Boolean
|
||||||
|
|
||||||
|
external fun setValue(index: Int, uniform: String, component: Int, value: Float)
|
||||||
|
|
||||||
|
external fun store()
|
||||||
|
|
||||||
|
external fun reload()
|
||||||
|
|
||||||
|
external fun clearChain()
|
||||||
|
|
||||||
|
external fun getPresetsJson(): String
|
||||||
|
|
||||||
|
external fun getActivePreset(): String
|
||||||
|
|
||||||
|
external fun isPresetModified(): Boolean
|
||||||
|
|
||||||
|
external fun applyPreset(name: String): Boolean
|
||||||
|
|
||||||
|
external fun savePreset(name: String, description: String): Boolean
|
||||||
|
|
||||||
|
external fun deletePreset(name: String): Boolean
|
||||||
|
|
||||||
|
external fun clearPreset()
|
||||||
|
|
||||||
|
external fun isEnabled(): Boolean
|
||||||
|
|
||||||
|
external fun setEnabled(enabled: Boolean)
|
||||||
|
|
||||||
|
external fun getPresetDirectory(): String
|
||||||
|
|
||||||
|
fun persist() {
|
||||||
|
val perGame = NativeConfig.isPerGameConfigLoaded()
|
||||||
|
store()
|
||||||
|
if (perGame) {
|
||||||
|
NativeConfig.savePerGameConfig()
|
||||||
|
} else {
|
||||||
|
NativeConfig.saveGlobalConfig()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
external fun getShaderDirectory(): String
|
||||||
|
|
||||||
|
data class Uniform(
|
||||||
|
val name: String,
|
||||||
|
val label: String,
|
||||||
|
val tooltip: String,
|
||||||
|
val category: String,
|
||||||
|
val kind: Int,
|
||||||
|
val uiType: Int,
|
||||||
|
val components: Int,
|
||||||
|
val min: Float,
|
||||||
|
val max: Float,
|
||||||
|
val step: Float,
|
||||||
|
val items: List<String>,
|
||||||
|
val defaults: List<Float>
|
||||||
|
) {
|
||||||
|
val steps: Int
|
||||||
|
get() {
|
||||||
|
val span = max - min
|
||||||
|
if (step <= 0f) {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
val count = Math.round(span / step)
|
||||||
|
if (count < 1) {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
|
||||||
|
fun describe(position: Int): String {
|
||||||
|
val value = min + position * step
|
||||||
|
if (kind == NativePostProcessing.KIND_FLOAT) {
|
||||||
|
return String.format("%.3f", value)
|
||||||
|
}
|
||||||
|
return Math.round(value).toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun defaultAt(component: Int): Float {
|
||||||
|
if (component < defaults.size) {
|
||||||
|
return defaults[component]
|
||||||
|
}
|
||||||
|
return 0f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class Effect(
|
||||||
|
val file: String,
|
||||||
|
val name: String,
|
||||||
|
val label: String,
|
||||||
|
val description: String,
|
||||||
|
val error: String,
|
||||||
|
val techniques: List<String>,
|
||||||
|
val uniforms: List<Uniform>
|
||||||
|
) {
|
||||||
|
val valid: Boolean
|
||||||
|
get() = error.isEmpty() && techniques.isNotEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
data class ChainEntry(val file: String, val technique: String)
|
||||||
|
|
||||||
|
data class Preset(
|
||||||
|
val name: String,
|
||||||
|
val description: String,
|
||||||
|
val bundled: Boolean
|
||||||
|
)
|
||||||
|
|
||||||
|
fun presets(): List<Preset> {
|
||||||
|
val out = mutableListOf<Preset>()
|
||||||
|
val array = JSONArray(getPresetsJson())
|
||||||
|
for (i in 0 until array.length()) {
|
||||||
|
val obj = array.getJSONObject(i)
|
||||||
|
out.add(
|
||||||
|
Preset(
|
||||||
|
name = obj.optString("name"),
|
||||||
|
description = obj.optString("description"),
|
||||||
|
bundled = obj.optBoolean("bundled")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
fun catalog(): List<Effect> {
|
||||||
|
val out = mutableListOf<Effect>()
|
||||||
|
val array = JSONArray(getCatalogJson())
|
||||||
|
for (i in 0 until array.length()) {
|
||||||
|
val obj = array.getJSONObject(i)
|
||||||
|
out.add(
|
||||||
|
Effect(
|
||||||
|
file = obj.optString("file"),
|
||||||
|
name = obj.optString("name"),
|
||||||
|
label = obj.optString("label"),
|
||||||
|
description = obj.optString("description"),
|
||||||
|
error = obj.optString("error"),
|
||||||
|
techniques = obj.optJSONArray("techniques").toStringList(),
|
||||||
|
uniforms = obj.optJSONArray("uniforms").toUniformList()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
fun chain(): List<ChainEntry> {
|
||||||
|
val out = mutableListOf<ChainEntry>()
|
||||||
|
val array = JSONArray(getChainJson())
|
||||||
|
for (i in 0 until array.length()) {
|
||||||
|
val obj = array.getJSONObject(i)
|
||||||
|
out.add(ChainEntry(obj.optString("file"), obj.optString("technique")))
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
fun findEffect(file: String): Effect? = catalog().firstOrNull { it.file == file }
|
||||||
|
|
||||||
|
private fun JSONArray?.toStringList(): List<String> {
|
||||||
|
if (this == null) {
|
||||||
|
return emptyList()
|
||||||
|
}
|
||||||
|
val out = mutableListOf<String>()
|
||||||
|
for (i in 0 until length()) {
|
||||||
|
out.add(optString(i))
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun JSONArray?.toFloatList(): List<Float> {
|
||||||
|
if (this == null) {
|
||||||
|
return emptyList()
|
||||||
|
}
|
||||||
|
val out = mutableListOf<Float>()
|
||||||
|
for (i in 0 until length()) {
|
||||||
|
out.add(optDouble(i, 0.0).toFloat())
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun JSONArray?.toUniformList(): List<Uniform> {
|
||||||
|
if (this == null) {
|
||||||
|
return emptyList()
|
||||||
|
}
|
||||||
|
val out = mutableListOf<Uniform>()
|
||||||
|
for (i in 0 until length()) {
|
||||||
|
val obj: JSONObject = optJSONObject(i) ?: continue
|
||||||
|
out.add(
|
||||||
|
Uniform(
|
||||||
|
name = obj.optString("name"),
|
||||||
|
label = obj.optString("label"),
|
||||||
|
tooltip = obj.optString("tooltip"),
|
||||||
|
category = obj.optString("category"),
|
||||||
|
kind = obj.optInt("kind", KIND_FLOAT),
|
||||||
|
uiType = obj.optInt("uiType", UI_HIDDEN),
|
||||||
|
components = obj.optInt("components", 1),
|
||||||
|
min = obj.optDouble("min", 0.0).toFloat(),
|
||||||
|
max = obj.optDouble("max", 1.0).toFloat(),
|
||||||
|
step = obj.optDouble("step", 0.01).toFloat(),
|
||||||
|
items = obj.optJSONArray("items").toStringList(),
|
||||||
|
defaults = obj.optJSONArray("defaults").toFloatList()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,6 +17,7 @@ add_library(yuzu-android SHARED
|
|||||||
android_config.cpp
|
android_config.cpp
|
||||||
android_config.h
|
android_config.h
|
||||||
native_input.cpp
|
native_input.cpp
|
||||||
|
native_post_processing.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
set_property(TARGET yuzu-android PROPERTY IMPORTED_LOCATION ${FFmpeg_LIBRARY_DIR})
|
set_property(TARGET yuzu-android PROPERTY IMPORTED_LOCATION ${FFmpeg_LIBRARY_DIR})
|
||||||
|
|||||||
@@ -15,10 +15,22 @@
|
|||||||
#include "frontend_common/config.h"
|
#include "frontend_common/config.h"
|
||||||
#include "frontend_common/settings_generator.h"
|
#include "frontend_common/settings_generator.h"
|
||||||
#include "native.h"
|
#include "native.h"
|
||||||
|
#ifdef HAS_RESHADE
|
||||||
|
#include "video_core/post_processing/fx_chain.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
std::unique_ptr<AndroidConfig> global_config;
|
std::unique_ptr<AndroidConfig> global_config;
|
||||||
std::unique_ptr<AndroidConfig> per_game_config;
|
std::unique_ptr<AndroidConfig> per_game_config;
|
||||||
|
|
||||||
|
#ifdef HAS_RESHADE
|
||||||
|
static void ResetFxChainToGlobal() {
|
||||||
|
VideoCore::UseGlobalFxSettings();
|
||||||
|
VideoCore::FxChain::Instance().LoadFromSettings();
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
static void ResetFxChainToGlobal() {}
|
||||||
|
#endif
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
Settings::Setting<T>* getSetting(JNIEnv* env, jstring jkey) {
|
Settings::Setting<T>* getSetting(JNIEnv* env, jstring jkey) {
|
||||||
auto key = Common::Android::GetJString(env, jkey);
|
auto key = Common::Android::GetJString(env, jkey);
|
||||||
@@ -39,6 +51,7 @@ extern "C" {
|
|||||||
void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_initializeGlobalConfig(JNIEnv* env, jobject obj) {
|
void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_initializeGlobalConfig(JNIEnv* env, jobject obj) {
|
||||||
global_config = std::make_unique<AndroidConfig>();
|
global_config = std::make_unique<AndroidConfig>();
|
||||||
FrontendCommon::GenerateSettings();
|
FrontendCommon::GenerateSettings();
|
||||||
|
ResetFxChainToGlobal();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_unloadGlobalConfig(JNIEnv* env, jobject obj) {
|
void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_unloadGlobalConfig(JNIEnv* env, jobject obj) {
|
||||||
@@ -47,6 +60,7 @@ void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_unloadGlobalConfig(JNIEnv* env,
|
|||||||
|
|
||||||
void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_reloadGlobalConfig(JNIEnv* env, jobject obj) {
|
void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_reloadGlobalConfig(JNIEnv* env, jobject obj) {
|
||||||
global_config->AndroidConfig::ReloadAllValues();
|
global_config->AndroidConfig::ReloadAllValues();
|
||||||
|
ResetFxChainToGlobal();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_saveGlobalConfig(JNIEnv* env, jobject obj) {
|
void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_saveGlobalConfig(JNIEnv* env, jobject obj) {
|
||||||
|
|||||||
@@ -0,0 +1,350 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include <jni.h>
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
|
#include "common/android/android_common.h"
|
||||||
|
#ifdef HAS_RESHADE
|
||||||
|
#include "android_config.h"
|
||||||
|
#include "video_core/post_processing/fx_chain.h"
|
||||||
|
#include "common/settings.h"
|
||||||
|
#include "video_core/post_processing/fx_effect.h"
|
||||||
|
#include "video_core/post_processing/fx_preset.h"
|
||||||
|
|
||||||
|
extern std::unique_ptr<AndroidConfig> per_game_config;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
#ifdef HAS_RESHADE
|
||||||
|
bool EditingPerGame() {
|
||||||
|
return per_game_config != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BeginFxEdit() {
|
||||||
|
if (!EditingPerGame()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
VideoCore::UsePerGameFxSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
nlohmann::json SerializeUniform(const VideoCore::FxUniformDesc& uniform) {
|
||||||
|
nlohmann::json out;
|
||||||
|
out["name"] = uniform.name;
|
||||||
|
out["label"] = uniform.label;
|
||||||
|
out["tooltip"] = uniform.tooltip;
|
||||||
|
out["category"] = uniform.category;
|
||||||
|
out["kind"] = static_cast<int>(uniform.kind);
|
||||||
|
out["uiType"] = static_cast<int>(uniform.ui_type);
|
||||||
|
out["components"] = uniform.components;
|
||||||
|
out["min"] = uniform.ui_min;
|
||||||
|
out["max"] = uniform.ui_max;
|
||||||
|
out["step"] = uniform.ui_step;
|
||||||
|
out["items"] = uniform.items;
|
||||||
|
|
||||||
|
nlohmann::json defaults = nlohmann::json::array();
|
||||||
|
for (u32 i = 0; i < uniform.components; ++i) {
|
||||||
|
defaults.push_back(uniform.default_value[i]);
|
||||||
|
}
|
||||||
|
out["defaults"] = defaults;
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::array<f32, 4> DefaultValueOf(size_t index, const std::string& uniform) {
|
||||||
|
const auto entries = VideoCore::FxChain::Instance().Entries();
|
||||||
|
if (index >= entries.size()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
const VideoCore::FxEffectDesc* effect = VideoCore::FindFxEffect(entries[index].file);
|
||||||
|
if (effect == nullptr) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
const VideoCore::FxUniformDesc* desc = VideoCore::FindFxUniform(*effect, uniform);
|
||||||
|
if (desc == nullptr) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return desc->default_value;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
} // Anonymous namespace
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
|
||||||
|
jstring Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_getCatalogJson(JNIEnv* env,
|
||||||
|
jobject obj) {
|
||||||
|
nlohmann::json out = nlohmann::json::array();
|
||||||
|
|
||||||
|
#ifdef HAS_RESHADE
|
||||||
|
VideoCore::FxChain::Instance().DropUnknownEntries();
|
||||||
|
|
||||||
|
for (const auto& effect : VideoCore::GetFxCatalog()) {
|
||||||
|
nlohmann::json entry;
|
||||||
|
entry["file"] = effect.file;
|
||||||
|
entry["name"] = effect.name;
|
||||||
|
entry["label"] = effect.label;
|
||||||
|
entry["description"] = effect.description;
|
||||||
|
entry["error"] = effect.error;
|
||||||
|
entry["techniques"] = effect.techniques;
|
||||||
|
|
||||||
|
nlohmann::json uniforms = nlohmann::json::array();
|
||||||
|
for (const auto& uniform : effect.uniforms) {
|
||||||
|
uniforms.push_back(SerializeUniform(uniform));
|
||||||
|
}
|
||||||
|
entry["uniforms"] = uniforms;
|
||||||
|
|
||||||
|
out.push_back(entry);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return Common::Android::ToJString(env, out.dump());
|
||||||
|
}
|
||||||
|
|
||||||
|
jstring Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_getChainJson(JNIEnv* env, jobject obj) {
|
||||||
|
nlohmann::json out = nlohmann::json::array();
|
||||||
|
|
||||||
|
#ifdef HAS_RESHADE
|
||||||
|
for (const auto& entry : VideoCore::FxChain::Instance().Entries()) {
|
||||||
|
nlohmann::json item;
|
||||||
|
item["file"] = entry.file;
|
||||||
|
item["technique"] = entry.technique;
|
||||||
|
|
||||||
|
nlohmann::json values = nlohmann::json::object();
|
||||||
|
for (const auto& [name, value] : entry.values) {
|
||||||
|
values[name] = {value[0], value[1], value[2], value[3]};
|
||||||
|
}
|
||||||
|
item["values"] = values;
|
||||||
|
|
||||||
|
out.push_back(item);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return Common::Android::ToJString(env, out.dump());
|
||||||
|
}
|
||||||
|
|
||||||
|
void Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_append(JNIEnv* env, jobject obj,
|
||||||
|
jstring jfile, jstring jtechnique) {
|
||||||
|
#ifdef HAS_RESHADE
|
||||||
|
VideoCore::FxChain::Instance().Append(Common::Android::GetJString(env, jfile),
|
||||||
|
Common::Android::GetJString(env, jtechnique));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_replace(JNIEnv* env, jobject obj,
|
||||||
|
jint index, jstring jfile,
|
||||||
|
jstring jtechnique) {
|
||||||
|
#ifdef HAS_RESHADE
|
||||||
|
VideoCore::FxChain::Instance().Replace(static_cast<size_t>(index),
|
||||||
|
Common::Android::GetJString(env, jfile),
|
||||||
|
Common::Android::GetJString(env, jtechnique));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_remove(JNIEnv* env, jobject obj,
|
||||||
|
jint index) {
|
||||||
|
#ifdef HAS_RESHADE
|
||||||
|
VideoCore::FxChain::Instance().Remove(static_cast<size_t>(index));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_move(JNIEnv* env, jobject obj, jint index,
|
||||||
|
jint delta) {
|
||||||
|
#ifdef HAS_RESHADE
|
||||||
|
VideoCore::FxChain::Instance().Move(static_cast<size_t>(index), delta);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_resetValues(JNIEnv* env, jobject obj,
|
||||||
|
jint index) {
|
||||||
|
#ifdef HAS_RESHADE
|
||||||
|
VideoCore::FxChain::Instance().ResetValues(static_cast<size_t>(index));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
jfloat Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_getValue(JNIEnv* env, jobject obj,
|
||||||
|
jint index, jstring juniform,
|
||||||
|
jint component) {
|
||||||
|
#ifdef HAS_RESHADE
|
||||||
|
if (component < 0 || component >= 4) {
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
const auto value = VideoCore::FxChain::Instance().GetValue(
|
||||||
|
static_cast<size_t>(index), Common::Android::GetJString(env, juniform));
|
||||||
|
return value[static_cast<size_t>(component)];
|
||||||
|
#else
|
||||||
|
return 0.0f;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
jboolean Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_hasValue(JNIEnv* env, jobject obj,
|
||||||
|
jint index,
|
||||||
|
jstring juniform) {
|
||||||
|
#ifdef HAS_RESHADE
|
||||||
|
return static_cast<jboolean>(VideoCore::FxChain::Instance().HasValue(
|
||||||
|
static_cast<size_t>(index), Common::Android::GetJString(env, juniform)));
|
||||||
|
#else
|
||||||
|
return static_cast<jboolean>(false);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_setValue(JNIEnv* env, jobject obj,
|
||||||
|
jint index, jstring juniform,
|
||||||
|
jint component, jfloat value) {
|
||||||
|
#ifdef HAS_RESHADE
|
||||||
|
if (component < 0 || component >= 4) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const std::string uniform = Common::Android::GetJString(env, juniform);
|
||||||
|
auto& chain = VideoCore::FxChain::Instance();
|
||||||
|
const auto slot = static_cast<size_t>(index);
|
||||||
|
|
||||||
|
auto current = chain.GetValue(slot, uniform);
|
||||||
|
if (!chain.HasValue(slot, uniform)) {
|
||||||
|
current = DefaultValueOf(slot, uniform);
|
||||||
|
}
|
||||||
|
|
||||||
|
current[static_cast<size_t>(component)] = value;
|
||||||
|
chain.SetValue(slot, uniform, current);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_store(JNIEnv* env, jobject obj) {
|
||||||
|
#ifdef HAS_RESHADE
|
||||||
|
BeginFxEdit();
|
||||||
|
VideoCore::FxChain::Instance().StoreToSettings();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_reload(JNIEnv* env, jobject obj) {
|
||||||
|
#ifdef HAS_RESHADE
|
||||||
|
if (!EditingPerGame()) {
|
||||||
|
VideoCore::UseGlobalFxSettings();
|
||||||
|
}
|
||||||
|
VideoCore::FxChain::Instance().LoadFromSettings();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_clearChain(JNIEnv* env, jobject obj) {
|
||||||
|
#ifdef HAS_RESHADE
|
||||||
|
VideoCore::FxChain::Instance().Clear();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
jstring Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_getShaderDirectory(JNIEnv* env,
|
||||||
|
jobject obj) {
|
||||||
|
#ifdef HAS_RESHADE
|
||||||
|
return Common::Android::ToJString(env, VideoCore::GetFxRootDirectory().string());
|
||||||
|
#else
|
||||||
|
return Common::Android::ToJString(env, "");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
jstring Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_getPresetsJson(JNIEnv* env,
|
||||||
|
jobject obj) {
|
||||||
|
nlohmann::json out = nlohmann::json::array();
|
||||||
|
#ifdef HAS_RESHADE
|
||||||
|
VideoCore::ReloadFxPresetCatalog();
|
||||||
|
|
||||||
|
for (const auto& preset : VideoCore::GetFxPresetCatalog()) {
|
||||||
|
nlohmann::json entry;
|
||||||
|
entry["name"] = preset.name;
|
||||||
|
entry["description"] = preset.description;
|
||||||
|
entry["bundled"] = preset.bundled;
|
||||||
|
out.push_back(entry);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return Common::Android::ToJString(env, out.dump());
|
||||||
|
}
|
||||||
|
|
||||||
|
jstring Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_getActivePreset(JNIEnv* env,
|
||||||
|
jobject obj) {
|
||||||
|
#ifdef HAS_RESHADE
|
||||||
|
return Common::Android::ToJString(env, VideoCore::GetActiveFxPreset());
|
||||||
|
#else
|
||||||
|
return Common::Android::ToJString(env, "");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
jboolean Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_isPresetModified(JNIEnv* env,
|
||||||
|
jobject obj) {
|
||||||
|
#ifdef HAS_RESHADE
|
||||||
|
return static_cast<jboolean>(VideoCore::IsActiveFxPresetModified());
|
||||||
|
#else
|
||||||
|
return static_cast<jboolean>(false);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
jboolean Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_applyPreset(JNIEnv* env, jobject obj,
|
||||||
|
jstring jname) {
|
||||||
|
#ifdef HAS_RESHADE
|
||||||
|
BeginFxEdit();
|
||||||
|
return static_cast<jboolean>(
|
||||||
|
VideoCore::ApplyFxPreset(Common::Android::GetJString(env, jname)));
|
||||||
|
#else
|
||||||
|
return static_cast<jboolean>(false);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
jboolean Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_savePreset(JNIEnv* env, jobject obj,
|
||||||
|
jstring jname,
|
||||||
|
jstring jdescription) {
|
||||||
|
#ifdef HAS_RESHADE
|
||||||
|
BeginFxEdit();
|
||||||
|
return static_cast<jboolean>(
|
||||||
|
VideoCore::SaveFxPreset(Common::Android::GetJString(env, jname),
|
||||||
|
Common::Android::GetJString(env, jdescription)));
|
||||||
|
#else
|
||||||
|
return static_cast<jboolean>(false);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
jboolean Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_deletePreset(JNIEnv* env, jobject obj,
|
||||||
|
jstring jname) {
|
||||||
|
#ifdef HAS_RESHADE
|
||||||
|
BeginFxEdit();
|
||||||
|
return static_cast<jboolean>(
|
||||||
|
VideoCore::DeleteFxPreset(Common::Android::GetJString(env, jname)));
|
||||||
|
#else
|
||||||
|
return static_cast<jboolean>(false);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_clearPreset(JNIEnv* env, jobject obj) {
|
||||||
|
#ifdef HAS_RESHADE
|
||||||
|
BeginFxEdit();
|
||||||
|
VideoCore::SetActiveFxPreset(std::string_view());
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
jboolean Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_isEnabled(JNIEnv* env, jobject obj) {
|
||||||
|
#ifdef HAS_RESHADE
|
||||||
|
return static_cast<jboolean>(Settings::values.post_shader_enabled.GetValue());
|
||||||
|
#else
|
||||||
|
return static_cast<jboolean>(false);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_setEnabled(JNIEnv* env, jobject obj,
|
||||||
|
jboolean enabled) {
|
||||||
|
#ifdef HAS_RESHADE
|
||||||
|
BeginFxEdit();
|
||||||
|
Settings::values.post_shader_enabled.SetValue(enabled != JNI_FALSE);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
jstring Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_getPresetDirectory(JNIEnv* env,
|
||||||
|
jobject obj) {
|
||||||
|
#ifdef HAS_RESHADE
|
||||||
|
return Common::Android::ToJString(env, VideoCore::GetFxPresetDirectory().string());
|
||||||
|
#else
|
||||||
|
return Common::Android::ToJString(env, "");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
} // extern "C"
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="?attr/colorControlNormal"
|
||||||
|
android:fillType="evenOdd"
|
||||||
|
android:pathData="M8.5,3 L19.4,3 Q21,3 21,4.6 L21,16.5 L19.4,16.5 L19.4,4.6 L8.5,4.6 Z M5,7 L15,7 Q17,7 17,9 L17,19 Q17,21 15,21 L5,21 Q3,21 3,19 L3,9 Q3,7 5,7 Z M5.2,8.6 L14.8,8.6 Q15.4,8.6 15.4,9.2 L15.4,18.8 Q15.4,19.4 14.8,19.4 L5.2,19.4 Q4.6,19.4 4.6,18.8 L4.6,9.2 Q4.6,8.6 5.2,8.6 Z M10,10.9 A3.1,3.1 0 0 1 10,17.1 Z"/>
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
|
||||||
|
<solid android:color="?attr/colorSurfaceVariant" />
|
||||||
|
|
||||||
|
<corners android:radius="16dp" />
|
||||||
|
|
||||||
|
<stroke
|
||||||
|
android:width="1dp"
|
||||||
|
android:color="?attr/colorOutline" />
|
||||||
|
|
||||||
|
</shape>
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:baselineAligned="false"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/add_button"
|
||||||
|
style="@style/Widget.Material3.Button.TonalButton"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:minHeight="48dp"
|
||||||
|
android:paddingStart="12dp"
|
||||||
|
android:paddingEnd="12dp"
|
||||||
|
android:text="@string/post_processing_add"
|
||||||
|
app:icon="@drawable/ic_add"
|
||||||
|
app:iconPadding="6dp" />
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/remove_all_button"
|
||||||
|
style="@style/Widget.Material3.Button.TonalButton"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:minHeight="48dp"
|
||||||
|
android:paddingStart="12dp"
|
||||||
|
android:paddingEnd="12dp"
|
||||||
|
android:text="@string/post_processing_remove_all"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:icon="@drawable/ic_delete"
|
||||||
|
app:iconPadding="6dp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<RadioGroup
|
||||||
|
android:id="@+id/add_choices"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingStart="24dp"
|
||||||
|
android:paddingEnd="24dp"
|
||||||
|
android:paddingTop="4dp"
|
||||||
|
android:paddingBottom="8dp"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:layout_marginTop="6dp"
|
||||||
|
android:layout_marginBottom="6dp"
|
||||||
|
android:background="@drawable/shader_card_background"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingStart="20dp"
|
||||||
|
android:paddingEnd="16dp"
|
||||||
|
android:paddingTop="16dp"
|
||||||
|
android:paddingBottom="16dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<com.google.android.material.textview.MaterialTextView
|
||||||
|
android:id="@+id/preset_title"
|
||||||
|
style="@style/TextAppearance.Material3.TitleMedium"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
|
<com.google.android.material.textview.MaterialTextView
|
||||||
|
android:id="@+id/preset_summary"
|
||||||
|
style="@style/TextAppearance.Material3.BodySmall"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:textColor="?attr/colorOnSurfaceVariant" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<com.google.android.material.materialswitch.MaterialSwitch
|
||||||
|
android:id="@+id/preset_switch"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:minHeight="48dp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:layout_marginTop="6dp"
|
||||||
|
android:layout_marginBottom="6dp"
|
||||||
|
android:background="@drawable/shader_card_background"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/shader_header"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingStart="16dp"
|
||||||
|
android:paddingEnd="8dp"
|
||||||
|
android:paddingTop="12dp"
|
||||||
|
android:paddingBottom="12dp"
|
||||||
|
android:background="?attr/selectableItemBackground"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<com.google.android.material.textview.MaterialTextView
|
||||||
|
android:id="@+id/shader_title"
|
||||||
|
style="@style/TextAppearance.Material3.TitleSmall"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
|
<com.google.android.material.textview.MaterialTextView
|
||||||
|
android:id="@+id/shader_summary"
|
||||||
|
style="@style/TextAppearance.Material3.BodySmall"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="2dp"
|
||||||
|
android:textColor="?attr/colorOnSurfaceVariant" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/shader_remove"
|
||||||
|
android:layout_width="40dp"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:src="@drawable/ic_delete"
|
||||||
|
android:contentDescription="@string/post_processing_remove"
|
||||||
|
app:tint="?attr/colorOnSurfaceVariant" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/shader_expand"
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:layout_marginStart="4dp"
|
||||||
|
android:src="@drawable/ic_dropdown_arrow"
|
||||||
|
android:contentDescription=""
|
||||||
|
app:tint="?attr/colorPrimary" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/shader_body"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingBottom="8dp"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingStart="20dp"
|
||||||
|
android:paddingEnd="20dp"
|
||||||
|
android:paddingTop="8dp"
|
||||||
|
android:paddingBottom="8dp">
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/fx_reset"
|
||||||
|
style="@style/Widget.Material3.Button.TonalButton"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:text="@string/post_processing_reset" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingStart="20dp"
|
||||||
|
android:paddingEnd="20dp"
|
||||||
|
android:paddingTop="8dp"
|
||||||
|
android:paddingBottom="4dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<com.google.android.material.textview.MaterialTextView
|
||||||
|
android:id="@+id/fx_slider_title"
|
||||||
|
style="@style/TextAppearance.Material3.TitleSmall"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1" />
|
||||||
|
|
||||||
|
<com.google.android.material.textview.MaterialTextView
|
||||||
|
android:id="@+id/fx_slider_value"
|
||||||
|
style="@style/TextAppearance.Material3.BodySmall"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:textColor="?attr/colorOnSurfaceVariant" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<com.google.android.material.slider.Slider
|
||||||
|
android:id="@+id/fx_slider"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:stepSize="1"
|
||||||
|
android:valueFrom="0"
|
||||||
|
android:valueTo="100" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:layout_marginTop="6dp"
|
||||||
|
android:layout_marginBottom="6dp"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/fx_button"
|
||||||
|
style="@style/Widget.Material3.Button.TonalButton"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:maxLines="1" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:layout_marginTop="6dp"
|
||||||
|
android:layout_marginBottom="6dp"
|
||||||
|
android:background="@drawable/shader_card_background"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/preset_row"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="?attr/selectableItemBackground"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingStart="16dp"
|
||||||
|
android:paddingEnd="8dp"
|
||||||
|
android:paddingTop="12dp"
|
||||||
|
android:paddingBottom="12dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<com.google.android.material.textview.MaterialTextView
|
||||||
|
android:id="@+id/preset_name"
|
||||||
|
style="@style/TextAppearance.Material3.TitleSmall"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
|
<com.google.android.material.textview.MaterialTextView
|
||||||
|
android:id="@+id/preset_description"
|
||||||
|
style="@style/TextAppearance.Material3.BodySmall"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="2dp"
|
||||||
|
android:textColor="?attr/colorOnSurfaceVariant" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/preset_delete"
|
||||||
|
android:layout_width="40dp"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
android:clickable="true"
|
||||||
|
android:contentDescription="@string/post_processing_preset_delete"
|
||||||
|
android:focusable="true"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:src="@drawable/ic_delete"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:tint="?attr/colorOnSurfaceVariant" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:layout_marginTop="6dp"
|
||||||
|
android:layout_marginBottom="6dp"
|
||||||
|
android:background="@drawable/shader_card_background"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/shader_header"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="?attr/selectableItemBackground"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingStart="20dp"
|
||||||
|
android:paddingEnd="8dp"
|
||||||
|
android:paddingTop="14dp"
|
||||||
|
android:paddingBottom="14dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<com.google.android.material.textview.MaterialTextView
|
||||||
|
android:id="@+id/shader_title"
|
||||||
|
style="@style/TextAppearance.Material3.TitleMedium"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
|
<com.google.android.material.textview.MaterialTextView
|
||||||
|
android:id="@+id/shader_summary"
|
||||||
|
style="@style/TextAppearance.Material3.BodySmall"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:textColor="?attr/colorOnSurfaceVariant" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/shader_remove"
|
||||||
|
android:layout_width="40dp"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
android:clickable="true"
|
||||||
|
android:contentDescription="@string/post_processing_remove"
|
||||||
|
android:focusable="true"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:src="@drawable/ic_delete"
|
||||||
|
app:tint="?attr/colorOnSurfaceVariant" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/shader_expand"
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:layout_marginStart="4dp"
|
||||||
|
android:contentDescription=""
|
||||||
|
android:src="@drawable/ic_dropdown_arrow"
|
||||||
|
app:tint="?attr/colorPrimary" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/shader_body"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingBottom="8dp"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:layout_marginTop="6dp"
|
||||||
|
android:layout_marginBottom="6dp"
|
||||||
|
android:baselineAligned="false"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/fx_add"
|
||||||
|
style="@style/Widget.Material3.Button.TonalButton"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:minHeight="48dp"
|
||||||
|
android:paddingStart="12dp"
|
||||||
|
android:paddingEnd="12dp"
|
||||||
|
android:text="@string/post_processing_add"
|
||||||
|
app:icon="@drawable/ic_add"
|
||||||
|
app:iconPadding="6dp" />
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/fx_presets"
|
||||||
|
style="@style/Widget.Material3.Button.TonalButton"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:layout_weight="1.4"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:minHeight="48dp"
|
||||||
|
android:paddingStart="12dp"
|
||||||
|
android:paddingEnd="12dp"
|
||||||
|
android:text="@string/post_processing_presets"
|
||||||
|
app:icon="@drawable/ic_dropdown_arrow"
|
||||||
|
app:iconGravity="textEnd"
|
||||||
|
app:iconPadding="6dp" />
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/fx_create_preset"
|
||||||
|
style="@style/Widget.Material3.Button.IconButton.Filled.Tonal"
|
||||||
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:contentDescription="@string/post_processing_preset_new"
|
||||||
|
app:icon="@drawable/ic_add" />
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/fx_remove_all"
|
||||||
|
style="@style/Widget.Material3.Button.IconButton.Filled.Tonal"
|
||||||
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:contentDescription="@string/post_processing_remove_all"
|
||||||
|
app:icon="@drawable/ic_delete" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
@@ -298,6 +298,25 @@
|
|||||||
<string name="gpu_driver_fetcher">GPU driver fetcher</string>
|
<string name="gpu_driver_fetcher">GPU driver fetcher</string>
|
||||||
<string name="gpu_driver_manager">GPU driver manager</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="install_gpu_driver_description">Install alternative drivers for potentially better performance or accuracy</string>
|
||||||
|
<string name="post_processing">Post-Processing Effects</string>
|
||||||
|
<string name="post_processing_description">ReShade FX effects applied after rendering</string>
|
||||||
|
<string name="post_processing_per_game_description">Configure the effect chain for this game</string>
|
||||||
|
<string name="post_processing_add">Add effect</string>
|
||||||
|
<string name="post_processing_remove">Remove</string>
|
||||||
|
<string name="post_processing_open_list">Open list</string>
|
||||||
|
<string name="post_processing_close_list">Close list</string>
|
||||||
|
<string name="post_processing_remove_all">Remove effects</string>
|
||||||
|
<string name="post_processing_preset_locked">Preset active. Edit it in Post-Processing Effects before starting a game.</string>
|
||||||
|
<string name="post_processing_preset_modified">Values changed from the original preset.</string>
|
||||||
|
<string name="post_processing_presets">Presets</string>
|
||||||
|
<string name="post_processing_preset_new">New preset</string>
|
||||||
|
<string name="post_processing_preset_new_description">Saves the effects you have loaded, with their current values, as a preset you can pick later.</string>
|
||||||
|
<string name="post_processing_preset_name_invalid">Give the preset a name without an equals sign.</string>
|
||||||
|
<string name="post_processing_preset_none">No preset</string>
|
||||||
|
<string name="post_processing_preset_delete">Delete preset</string>
|
||||||
|
<string name="post_processing_preset_reset">Reset preset values</string>
|
||||||
|
<string name="post_processing_reset">Reset to default</string>
|
||||||
|
<string name="post_processing_empty">No effects found. Place .fx files in this folder:</string>
|
||||||
<string name="frame_gen">Frame generation</string>
|
<string name="frame_gen">Frame generation</string>
|
||||||
<string name="frame_gen_per_game_description">Configure frame generation for this game</string>
|
<string name="frame_gen_per_game_description">Configure frame generation for this game</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_description">Insert interpolated frames between rendered ones using Lossless Scaling. Forces FIFO presentation while enabled.</string>
|
||||||
@@ -1764,51 +1783,6 @@ RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
|
|||||||
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
|
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
|
||||||
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
||||||
DAMAGES.
|
DAMAGES.
|
||||||
</string>
|
|
||||||
<string name="license_opus" translatable="false">Opus</string>
|
|
||||||
<string name="license_opus_description" translatable="false">Modern audio compression for the internet</string>
|
|
||||||
<string name="license_opus_link" translatable="false">https://github.com/xiph/opus</string>
|
|
||||||
<string name="license_opus_copyright" translatable="false">Copyright 2001–2011 Xiph.Org, Skype Limited, Octasic, Jean-Marc Valin, Timothy B. Terriberry, CSIRO, Gregory Maxwell, Mark Borgerding, Erik de Castro Lopo</string>
|
|
||||||
<string name="license_opus_text" translatable="false">
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions
|
|
||||||
are met:\n\n
|
|
||||||
|
|
||||||
- Redistributions of source code must retain the above copyright
|
|
||||||
notice, this list of conditions and the following disclaimer.\n\n
|
|
||||||
|
|
||||||
- Redistributions in binary form must reproduce the above copyright
|
|
||||||
notice, this list of conditions and the following disclaimer in the
|
|
||||||
documentation and/or other materials provided with the distribution.\n\n
|
|
||||||
|
|
||||||
- Neither the name of Internet Society, IETF or IETF Trust, nor the
|
|
||||||
names of specific contributors, may be used to endorse or promote
|
|
||||||
products derived from this software without specific prior written
|
|
||||||
permission.\n\n
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
||||||
``AS IS\'\' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
||||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
||||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
|
|
||||||
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
|
||||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
||||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|
||||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|
||||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
||||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
||||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n
|
|
||||||
|
|
||||||
Opus is subject to the royalty-free patent licenses which are
|
|
||||||
specified at:\n\n
|
|
||||||
|
|
||||||
Xiph.Org Foundation:
|
|
||||||
https://datatracker.ietf.org/ipr/1524/ \n\n
|
|
||||||
|
|
||||||
Microsoft Corporation:
|
|
||||||
https://datatracker.ietf.org/ipr/1914/ \n\n
|
|
||||||
|
|
||||||
Broadcom Corporation:
|
|
||||||
https://datatracker.ietf.org/ipr/1526/
|
|
||||||
</string>
|
</string>
|
||||||
<string name="license_sirit" translatable="false">Sirit</string>
|
<string name="license_sirit" translatable="false">Sirit</string>
|
||||||
<string name="license_sirit_description" translatable="false">A runtime SPIR-V assembler</string>
|
<string name="license_sirit_description" translatable="false">A runtime SPIR-V assembler</string>
|
||||||
|
|||||||
@@ -15,10 +15,6 @@ add_library(audio_core STATIC
|
|||||||
adsp/apps/audio_renderer/command_list_processor.h
|
adsp/apps/audio_renderer/command_list_processor.h
|
||||||
adsp/apps/opus/opus_decoder.cpp
|
adsp/apps/opus/opus_decoder.cpp
|
||||||
adsp/apps/opus/opus_decoder.h
|
adsp/apps/opus/opus_decoder.h
|
||||||
adsp/apps/opus/opus_decode_object.cpp
|
|
||||||
adsp/apps/opus/opus_decode_object.h
|
|
||||||
adsp/apps/opus/opus_multistream_decode_object.cpp
|
|
||||||
adsp/apps/opus/opus_multistream_decode_object.h
|
|
||||||
adsp/apps/opus/shared_memory.h
|
adsp/apps/opus/shared_memory.h
|
||||||
audio_core.cpp
|
audio_core.cpp
|
||||||
audio_core.h
|
audio_core.h
|
||||||
@@ -226,8 +222,14 @@ else()
|
|||||||
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-sign-conversion>)
|
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-sign-conversion>)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
target_include_directories(audio_core PRIVATE ${OPUS_INCLUDE_DIRS})
|
if (YUZU_USE_EXTERNAL_FFMPEG)
|
||||||
target_link_libraries(audio_core PUBLIC common core Opus::opus)
|
add_dependencies(audio_core ffmpeg-build)
|
||||||
|
endif()
|
||||||
|
target_include_directories(audio_core PUBLIC ${FFmpeg_INCLUDE_DIR})
|
||||||
|
target_link_libraries(audio_core PRIVATE ${FFmpeg_LIBRARIES})
|
||||||
|
target_link_options(audio_core PRIVATE ${FFmpeg_LDFLAGS})
|
||||||
|
|
||||||
|
target_link_libraries(audio_core PUBLIC common core)
|
||||||
|
|
||||||
if (ENABLE_CUBEB)
|
if (ENABLE_CUBEB)
|
||||||
target_sources(audio_core PRIVATE
|
target_sources(audio_core PRIVATE
|
||||||
|
|||||||
@@ -1,107 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
#include "audio_core/adsp/apps/opus/opus_decode_object.h"
|
|
||||||
#include "common/assert.h"
|
|
||||||
|
|
||||||
namespace AudioCore::ADSP::OpusDecoder {
|
|
||||||
namespace {
|
|
||||||
bool IsValidChannelCount(u32 channel_count) {
|
|
||||||
return channel_count == 1 || channel_count == 2;
|
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
u32 OpusDecodeObject::GetWorkBufferSize(u32 channel_count) {
|
|
||||||
if (!IsValidChannelCount(channel_count)) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return static_cast<u32>(sizeof(OpusDecodeObject)) + opus_decoder_get_size(channel_count);
|
|
||||||
}
|
|
||||||
|
|
||||||
OpusDecodeObject& OpusDecodeObject::Initialize(u64 buffer, u64 buffer2) {
|
|
||||||
auto* new_decoder = reinterpret_cast<OpusDecodeObject*>(buffer);
|
|
||||||
auto* comparison = reinterpret_cast<OpusDecodeObject*>(buffer2);
|
|
||||||
|
|
||||||
if (new_decoder->magic == DecodeObjectMagic) {
|
|
||||||
if (!new_decoder->initialized ||
|
|
||||||
(new_decoder->initialized && new_decoder->self == comparison)) {
|
|
||||||
new_decoder->state_valid = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
new_decoder->initialized = false;
|
|
||||||
new_decoder->state_valid = true;
|
|
||||||
}
|
|
||||||
return *new_decoder;
|
|
||||||
}
|
|
||||||
|
|
||||||
s32 OpusDecodeObject::InitializeDecoder(u32 sample_rate, u32 channel_count) {
|
|
||||||
if (!state_valid) {
|
|
||||||
return OPUS_INVALID_STATE;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (initialized) {
|
|
||||||
return OPUS_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unfortunately libopus does not expose the OpusDecoder struct publicly, so we can't include
|
|
||||||
// it in this class. Nintendo does not allocate memory, which is why we have a workbuffer
|
|
||||||
// provided.
|
|
||||||
// We could use _create and have libopus allocate it for us, but then we have to separately
|
|
||||||
// track which decoder is being used between this and multistream in order to call the correct
|
|
||||||
// destroy from the host side.
|
|
||||||
// This is a bit cringe, but is safe as these objects are only ever initialized inside the given
|
|
||||||
// workbuffer, and GetWorkBufferSize will guarantee there's enough space to follow.
|
|
||||||
decoder = (LibOpusDecoder*)(this + 1);
|
|
||||||
s32 ret = opus_decoder_init(decoder, sample_rate, channel_count);
|
|
||||||
if (ret == OPUS_OK) {
|
|
||||||
magic = DecodeObjectMagic;
|
|
||||||
initialized = true;
|
|
||||||
state_valid = true;
|
|
||||||
self = this;
|
|
||||||
final_range = 0;
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
s32 OpusDecodeObject::Shutdown() {
|
|
||||||
if (!state_valid) {
|
|
||||||
return OPUS_INVALID_STATE;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (initialized) {
|
|
||||||
magic = 0x0;
|
|
||||||
initialized = false;
|
|
||||||
state_valid = false;
|
|
||||||
self = nullptr;
|
|
||||||
final_range = 0;
|
|
||||||
decoder = nullptr;
|
|
||||||
}
|
|
||||||
return OPUS_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
s32 OpusDecodeObject::ResetDecoder() {
|
|
||||||
return opus_decoder_ctl(decoder, OPUS_RESET_STATE);
|
|
||||||
}
|
|
||||||
|
|
||||||
s32 OpusDecodeObject::Decode(u32& out_sample_count, u64 output_data, u64 output_data_size,
|
|
||||||
u64 input_data, u64 input_data_size) {
|
|
||||||
ASSERT(initialized);
|
|
||||||
out_sample_count = 0;
|
|
||||||
|
|
||||||
if (!state_valid) {
|
|
||||||
return OPUS_INVALID_STATE;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto ret_code_or_samples = opus_decode(
|
|
||||||
decoder, reinterpret_cast<const u8*>(input_data), static_cast<opus_int32>(input_data_size),
|
|
||||||
reinterpret_cast<opus_int16*>(output_data), static_cast<opus_int32>(output_data_size), 0);
|
|
||||||
|
|
||||||
if (ret_code_or_samples < OPUS_OK) {
|
|
||||||
return ret_code_or_samples;
|
|
||||||
}
|
|
||||||
|
|
||||||
out_sample_count = ret_code_or_samples;
|
|
||||||
return opus_decoder_ctl(decoder, OPUS_GET_FINAL_RANGE_REQUEST, &final_range);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace AudioCore::ADSP::OpusDecoder
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <opus.h>
|
|
||||||
|
|
||||||
#include "common/common_types.h"
|
|
||||||
|
|
||||||
namespace AudioCore::ADSP::OpusDecoder {
|
|
||||||
using LibOpusDecoder = ::OpusDecoder;
|
|
||||||
static constexpr u32 DecodeObjectMagic = 0xDEADBEEF;
|
|
||||||
|
|
||||||
class OpusDecodeObject {
|
|
||||||
public:
|
|
||||||
static u32 GetWorkBufferSize(u32 channel_count);
|
|
||||||
static OpusDecodeObject& Initialize(u64 buffer, u64 buffer2);
|
|
||||||
|
|
||||||
s32 InitializeDecoder(u32 sample_rate, u32 channel_count);
|
|
||||||
s32 Shutdown();
|
|
||||||
s32 ResetDecoder();
|
|
||||||
s32 Decode(u32& out_sample_count, u64 output_data, u64 output_data_size, u64 input_data,
|
|
||||||
u64 input_data_size);
|
|
||||||
u32 GetFinalRange() const noexcept {
|
|
||||||
return final_range;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
u32 magic;
|
|
||||||
bool initialized;
|
|
||||||
bool state_valid;
|
|
||||||
OpusDecodeObject* self;
|
|
||||||
u32 final_range;
|
|
||||||
LibOpusDecoder* decoder;
|
|
||||||
};
|
|
||||||
static_assert(std::is_trivially_constructible_v<OpusDecodeObject>);
|
|
||||||
|
|
||||||
} // namespace AudioCore::ADSP::OpusDecoder
|
|
||||||
@@ -5,55 +5,388 @@
|
|||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <chrono>
|
|
||||||
|
|
||||||
#include "audio_core/adsp/apps/opus/opus_decode_object.h"
|
extern "C" {
|
||||||
#include "audio_core/adsp/apps/opus/opus_multistream_decode_object.h"
|
#include <libswresample/swresample.h>
|
||||||
|
#include <libavcodec/avcodec.h>
|
||||||
|
#include <libavcodec/codec.h>
|
||||||
|
#include <libavcodec/packet.h>
|
||||||
|
#include <libavutil/channel_layout.h>
|
||||||
|
#include <libavutil/frame.h>
|
||||||
|
#include <libavutil/opt.h>
|
||||||
|
#include <libavutil/samplefmt.h>
|
||||||
|
}
|
||||||
|
|
||||||
#include "audio_core/adsp/apps/opus/shared_memory.h"
|
#include "audio_core/adsp/apps/opus/shared_memory.h"
|
||||||
#include "audio_core/audio_core.h"
|
#include "audio_core/audio_core.h"
|
||||||
#include "audio_core/common/common.h"
|
|
||||||
#include "common/logging.h"
|
#include "common/logging.h"
|
||||||
#include "common/thread.h"
|
#include "common/thread.h"
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
#include "core/core_timing.h"
|
#include "core/core_timing.h"
|
||||||
|
#include "core/hle/service/audio/errors.h"
|
||||||
|
|
||||||
namespace AudioCore::ADSP::OpusDecoder {
|
namespace AudioCore::ADSP::OpusDecoder {
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
constexpr size_t OpusStreamCountMax = 255;
|
constexpr u32 OPUS_STREAM_COUNT_MAX = 255;
|
||||||
|
// https://git.ffmpeg.org/gitweb/ffmpeg.git/blob_plain/HEAD:/libavcodec/libopusdec.c
|
||||||
|
constexpr u32 OPUS_HEAD_SIZE = 19;
|
||||||
|
constexpr u32 OPUS_MAX_CHANNELS = 2;
|
||||||
|
|
||||||
bool IsValidChannelCount(u32 channel_count) {
|
bool IsValidChannelCount(u32 channel_count) {
|
||||||
return channel_count == 1 || channel_count == 2;
|
return channel_count >= 1 || channel_count <= OPUS_MAX_CHANNELS;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IsValidMultiStreamChannelCount(u32 channel_count) {
|
bool IsValidStreamCounts(u32 total_stream_count, u32 stereo_stream_count) {
|
||||||
return channel_count <= OpusStreamCountMax;
|
return total_stream_count > 0 && total_stream_count <= OPUS_STREAM_COUNT_MAX
|
||||||
|
&& s32(stereo_stream_count) >= 0 && stereo_stream_count <= total_stream_count;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IsValidMultiStreamStreamCounts(s32 total_stream_count, s32 stereo_stream_count) {
|
class OpusGenericDecodeObject {
|
||||||
return IsValidMultiStreamChannelCount(total_stream_count) && total_stream_count > 0 &&
|
public:
|
||||||
stereo_stream_count >= 0 && stereo_stream_count <= total_stream_count;
|
static u32 GetWorkBufferSizeMultistream(u32 total_stream_count, u32 stereo_stream_count) {
|
||||||
}
|
if (IsValidStreamCounts(total_stream_count, stereo_stream_count))
|
||||||
|
return 48 + 2556 * (total_stream_count * stereo_stream_count);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static u32 GetWorkBufferSize(u32 channel_count) {
|
||||||
|
if (channel_count == 1 || channel_count == 2)
|
||||||
|
return 48 + 16 * channel_count;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// idempotency of initialize is guaranteed
|
||||||
|
Result InitializeDecoder(u32 sample_rate, u32 total_stream_count, u32 channel_count, u32 stereo_stream_count, u8 const* mappings) {
|
||||||
|
// prefer libopus, ffmpeg docs say to use libopus **if** available
|
||||||
|
// However, native opus can also work with swrescale:
|
||||||
|
// it uses planarfloat, we can resample to s16
|
||||||
|
AVCodec const* codec = avcodec_find_decoder_by_name("libopus");
|
||||||
|
bool is_libopus = codec != nullptr;
|
||||||
|
if (!codec) {
|
||||||
|
LOG_WARNING(Audio_DSP, "using ffmpeg native opus decoder");
|
||||||
|
codec = avcodec_find_decoder(AV_CODEC_ID_OPUS);
|
||||||
|
}
|
||||||
|
if (codec) {
|
||||||
|
if ((avc = avc ? avc : avcodec_alloc_context3(codec))) {
|
||||||
|
if (is_libopus) {
|
||||||
|
const std::array<u8, 2> mapping_arr{0, 1};
|
||||||
|
mappings = mappings ? mappings : mapping_arr.data();
|
||||||
|
|
||||||
|
// freed by avcodec_context_free()
|
||||||
|
u8 *edata = reinterpret_cast<u8*>(av_mallocz(OPUS_HEAD_SIZE + 2 * OPUS_MAX_CHANNELS + AV_INPUT_BUFFER_PADDING_SIZE));
|
||||||
|
ASSERT(edata);
|
||||||
|
edata[9] = u8(channel_count); //channels
|
||||||
|
edata[10] = u8(0); //opus->pre_skip
|
||||||
|
edata[16] = u8(0); //gain_db
|
||||||
|
edata[18] = u8(0); //channel_map
|
||||||
|
edata[OPUS_HEAD_SIZE + 0] = u8(total_stream_count);
|
||||||
|
edata[OPUS_HEAD_SIZE + 1] = u8(stereo_stream_count);
|
||||||
|
if (channel_count >= 1) edata[OPUS_HEAD_SIZE + 2] = mappings[0];
|
||||||
|
if (channel_count >= 2) edata[OPUS_HEAD_SIZE + 3] = mappings[1];
|
||||||
|
avc->extradata = edata;
|
||||||
|
avc->extradata_size = OPUS_HEAD_SIZE + 2 * channel_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// FFmpeg hardcodes sample rate
|
||||||
|
avc->sample_rate = sample_rate;
|
||||||
|
avc->request_sample_fmt = AV_SAMPLE_FMT_S16;
|
||||||
|
av_channel_layout_default(&avc->ch_layout, channel_count);
|
||||||
|
if (avcodec_open2(avc, codec, nullptr) >= 0) {
|
||||||
|
avpkt = av_packet_alloc();
|
||||||
|
frame = av_frame_alloc();
|
||||||
|
return ResultSuccess;
|
||||||
|
} else {
|
||||||
|
avcodec_free_context(&avc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Service::Audio::ResultLibOpusInternalError;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result Shutdown() {
|
||||||
|
avcodec_free_context(&avc);
|
||||||
|
av_frame_free(&frame);
|
||||||
|
av_packet_free(&avpkt);
|
||||||
|
return ResultSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result ResetDecoder() {
|
||||||
|
if (avc) {
|
||||||
|
if (avcodec_is_open(avc)) avcodec_flush_buffers(avc);
|
||||||
|
return ResultSuccess;
|
||||||
|
}
|
||||||
|
return Service::Audio::ResultLibOpusInvalidState;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result Decode(u32& out_sample_count, u64 output_data, u64 output_data_size, u64 input_data, u64 input_data_size) {
|
||||||
|
out_sample_count = 0;
|
||||||
|
if (avc) {
|
||||||
|
int rem_output_bytes = int(output_data_size);
|
||||||
|
while (rem_output_bytes > 0) {
|
||||||
|
int r = avcodec_receive_frame(avc, frame);
|
||||||
|
if (r == AVERROR(EAGAIN)) {
|
||||||
|
av_packet_unref(avpkt);
|
||||||
|
av_new_packet(avpkt, int(input_data_size));
|
||||||
|
std::memcpy(avpkt->data, reinterpret_cast<const u8*>(input_data), input_data_size);
|
||||||
|
r = avcodec_send_packet(avc, avpkt);
|
||||||
|
ASSERT(r >= 0);
|
||||||
|
} else if (r == AVERROR_EOF) {
|
||||||
|
break;
|
||||||
|
} else if (r >= 0) {
|
||||||
|
auto const bsize = av_samples_get_buffer_size(nullptr, frame->ch_layout.nb_channels, frame->nb_samples, AV_SAMPLE_FMT_S16, 1);
|
||||||
|
if (frame->format == AV_SAMPLE_FMT_S16) {
|
||||||
|
std::memcpy(reinterpret_cast<s16*>(output_data) + (int(output_data_size) - rem_output_bytes), frame->data[0], size_t(bsize));
|
||||||
|
} else {
|
||||||
|
SwrContext *swr = nullptr;
|
||||||
|
if (swr_alloc_set_opts2(
|
||||||
|
&swr,
|
||||||
|
&avc->ch_layout,
|
||||||
|
AV_SAMPLE_FMT_S16,
|
||||||
|
48000,
|
||||||
|
&avc->ch_layout,
|
||||||
|
(enum AVSampleFormat)frame->format,
|
||||||
|
48000,
|
||||||
|
0,
|
||||||
|
nullptr
|
||||||
|
) >= 0) {
|
||||||
|
if (swr_init(swr) >= 0) {
|
||||||
|
AVFrame *s16_frame = av_frame_alloc();
|
||||||
|
s16_frame->format = AV_SAMPLE_FMT_S16;
|
||||||
|
s16_frame->sample_rate = frame->sample_rate;
|
||||||
|
av_channel_layout_copy(&s16_frame->ch_layout, &frame->ch_layout);
|
||||||
|
s16_frame->nb_samples = frame->nb_samples;
|
||||||
|
av_frame_get_buffer(s16_frame, 0);
|
||||||
|
swr_convert(swr, s16_frame->data, s16_frame->nb_samples, (const uint8_t **)frame->data, frame->nb_samples);
|
||||||
|
std::memcpy(reinterpret_cast<s16*>(output_data) + (int(output_data_size) - rem_output_bytes), s16_frame->data[0], size_t(bsize));
|
||||||
|
swr_free(&swr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out_sample_count += frame->nb_samples;
|
||||||
|
rem_output_bytes -= bsize;
|
||||||
|
} else {
|
||||||
|
LOG_ERROR(Audio_DSP, "{}", r);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ASSERT(rem_output_bytes == 0 && "remaining bytes!");
|
||||||
|
return ResultSuccess;
|
||||||
|
}
|
||||||
|
return Service::Audio::ResultLibOpusInvalidState;
|
||||||
|
}
|
||||||
|
|
||||||
|
AVCodecContext* avc = nullptr;
|
||||||
|
AVPacket* avpkt = nullptr;
|
||||||
|
AVFrame* frame = nullptr;
|
||||||
|
};
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
OpusDecoder::OpusDecoder(Core::System& system_) : system{system_} {
|
OpusDecoder::OpusDecoder(Core::System& system) {
|
||||||
init_thread = std::jthread([this](std::stop_token stop_token) { Init(stop_token); });
|
dsp_thread = std::jthread([this, &system](std::stop_token stop_token) {
|
||||||
|
Common::SetCurrentThreadName("DSP_OpusDecoder");
|
||||||
|
if (Receive(Direction::DSP, stop_token) != Message::Start) {
|
||||||
|
LOG_ERROR(Service_Audio, "DSP OpusDecoder failed to receive Start message. Opus initialization failed.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Send(Direction::Host, Message::StartOK);
|
||||||
|
|
||||||
|
// Main OpusDecoder thread, responsible for processing the incoming Opus packets.
|
||||||
|
::Common::unordered_map<u64, OpusGenericDecodeObject> decode_objects;
|
||||||
|
while (!stop_token.stop_requested()) {
|
||||||
|
auto msg = Receive(Direction::DSP, stop_token);
|
||||||
|
switch (msg) {
|
||||||
|
case Shutdown:
|
||||||
|
Send(Direction::Host, Message::ShutdownOK);
|
||||||
|
return;
|
||||||
|
case GetWorkBufferSize: {
|
||||||
|
auto channel_count = s32(shared_memory->host_send_data[0]);
|
||||||
|
|
||||||
|
ASSERT(IsValidChannelCount(channel_count));
|
||||||
|
|
||||||
|
shared_memory->dsp_return_data[0] = OpusGenericDecodeObject::GetWorkBufferSize(channel_count);
|
||||||
|
Send(Direction::Host, Message::GetWorkBufferSizeOK);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case InitializeDecodeObject: {
|
||||||
|
auto buffer = shared_memory->host_send_data[0];
|
||||||
|
auto buffer_size = shared_memory->host_send_data[1];
|
||||||
|
auto sample_rate = s32(shared_memory->host_send_data[2]);
|
||||||
|
auto channel_count = s32(shared_memory->host_send_data[3]);
|
||||||
|
|
||||||
|
ASSERT(sample_rate >= 0);
|
||||||
|
ASSERT(IsValidChannelCount(channel_count));
|
||||||
|
ASSERT(buffer_size >= OpusGenericDecodeObject::GetWorkBufferSize(channel_count));
|
||||||
|
|
||||||
|
if (auto const it = decode_objects.find(buffer); it != decode_objects.end()) {
|
||||||
|
it->second.Shutdown();
|
||||||
|
shared_memory->dsp_return_data[0] = it->second.InitializeDecoder(sample_rate, 1, channel_count, channel_count == 2 ? 1 : 0, nullptr).raw;
|
||||||
|
} else {
|
||||||
|
OpusGenericDecodeObject obj{};
|
||||||
|
shared_memory->dsp_return_data[0] = obj.InitializeDecoder(sample_rate, 1, channel_count, channel_count == 2 ? 1 : 0, nullptr).raw;
|
||||||
|
decode_objects.insert_or_assign(buffer, obj);
|
||||||
|
}
|
||||||
|
Send(Direction::Host, Message::InitializeDecodeObjectOK);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ShutdownDecodeObject: {
|
||||||
|
auto buffer = shared_memory->host_send_data[0];
|
||||||
|
//[[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1];
|
||||||
|
if (auto const it = decode_objects.find(buffer); it != decode_objects.end()) {
|
||||||
|
shared_memory->dsp_return_data[0] = it->second.Shutdown().raw;
|
||||||
|
} else {
|
||||||
|
LOG_ERROR(Audio_DSP, "operating unregistered buffer {}", buffer);
|
||||||
|
shared_memory->dsp_return_data[0] = Service::Audio::ResultLibOpusInvalidState.raw;
|
||||||
|
}
|
||||||
|
Send(Direction::Host, Message::ShutdownDecodeObjectOK);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case DecodeInterleaved: {
|
||||||
|
auto start_time = system.CoreTiming().GetGlobalTimeUs();
|
||||||
|
|
||||||
|
auto buffer = shared_memory->host_send_data[0];
|
||||||
|
auto input_data = shared_memory->host_send_data[1];
|
||||||
|
auto input_data_size = shared_memory->host_send_data[2];
|
||||||
|
auto output_data = shared_memory->host_send_data[3];
|
||||||
|
auto output_data_size = shared_memory->host_send_data[4];
|
||||||
|
//auto final_range = static_cast<u32>(shared_memory->host_send_data[5]);
|
||||||
|
auto reset_requested = shared_memory->host_send_data[6];
|
||||||
|
|
||||||
|
u32 decoded_samples{0};
|
||||||
|
|
||||||
|
if (auto const it = decode_objects.find(buffer); it != decode_objects.end()) {
|
||||||
|
auto res = ResultSuccess;
|
||||||
|
if (reset_requested)
|
||||||
|
res = it->second.ResetDecoder();
|
||||||
|
if (res == ResultSuccess)
|
||||||
|
res = it->second.Decode(decoded_samples, output_data, output_data_size, input_data, input_data_size);
|
||||||
|
|
||||||
|
auto end_time = system.CoreTiming().GetGlobalTimeUs();
|
||||||
|
shared_memory->dsp_return_data[0] = res.raw;
|
||||||
|
shared_memory->dsp_return_data[1] = decoded_samples;
|
||||||
|
shared_memory->dsp_return_data[2] = (end_time - start_time).count();
|
||||||
|
} else {
|
||||||
|
LOG_ERROR(Audio_DSP, "operating unregistered buffer {}", buffer);
|
||||||
|
shared_memory->dsp_return_data[0] = Service::Audio::ResultLibOpusInvalidState.raw;
|
||||||
|
}
|
||||||
|
Send(Direction::Host, Message::DecodeInterleavedOK);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MapMemory: {
|
||||||
|
[[maybe_unused]] auto buffer = shared_memory->host_send_data[0];
|
||||||
|
[[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1];
|
||||||
|
Send(Direction::Host, Message::MapMemoryOK);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case UnmapMemory: {
|
||||||
|
[[maybe_unused]] auto buffer = shared_memory->host_send_data[0];
|
||||||
|
[[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1];
|
||||||
|
Send(Direction::Host, Message::UnmapMemoryOK);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case GetWorkBufferSizeForMultiStream: {
|
||||||
|
auto total_stream_count = s32(shared_memory->host_send_data[0]);
|
||||||
|
auto stereo_stream_count = s32(shared_memory->host_send_data[1]);
|
||||||
|
|
||||||
|
ASSERT(IsValidStreamCounts(total_stream_count, stereo_stream_count));
|
||||||
|
|
||||||
|
shared_memory->dsp_return_data[0] = OpusGenericDecodeObject::GetWorkBufferSizeMultistream(total_stream_count, stereo_stream_count);
|
||||||
|
Send(Direction::Host, Message::GetWorkBufferSizeForMultiStreamOK);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case InitializeMultiStreamDecodeObject: {
|
||||||
|
auto buffer = shared_memory->host_send_data[0];
|
||||||
|
auto buffer_size = shared_memory->host_send_data[1];
|
||||||
|
auto sample_rate = s32(shared_memory->host_send_data[2]);
|
||||||
|
auto channel_count = s32(shared_memory->host_send_data[3]);
|
||||||
|
auto total_stream_count = s32(shared_memory->host_send_data[4]);
|
||||||
|
auto stereo_stream_count = s32(shared_memory->host_send_data[5]);
|
||||||
|
// Nintendo seem to have a bug here, they try to use &host_send_data[6] for the channel
|
||||||
|
// mappings, but [6] is never set, and there is not enough room in the argument data for
|
||||||
|
// more than 40 channels, when 255 are possible.
|
||||||
|
// It also means the mapping values are undefined, though likely always 0,
|
||||||
|
// and the mappings given by the game are ignored. The mappings are copied to this
|
||||||
|
// dedicated buffer host side, so let's do as intended.
|
||||||
|
auto mappings = shared_memory->channel_mapping.data();
|
||||||
|
|
||||||
|
ASSERT(IsValidStreamCounts(total_stream_count, stereo_stream_count));
|
||||||
|
ASSERT(sample_rate >= 0);
|
||||||
|
ASSERT(buffer_size >= OpusGenericDecodeObject::GetWorkBufferSizeMultistream(total_stream_count, stereo_stream_count));
|
||||||
|
if (auto const it = decode_objects.find(buffer); it != decode_objects.end()) {
|
||||||
|
it->second.Shutdown();
|
||||||
|
shared_memory->dsp_return_data[0] = it->second.InitializeDecoder(sample_rate, total_stream_count, channel_count, stereo_stream_count, mappings).raw;
|
||||||
|
} else {
|
||||||
|
OpusGenericDecodeObject obj{};
|
||||||
|
shared_memory->dsp_return_data[0] = obj.InitializeDecoder(sample_rate, total_stream_count, channel_count, stereo_stream_count, mappings).raw;
|
||||||
|
decode_objects.insert_or_assign(buffer, obj);
|
||||||
|
}
|
||||||
|
Send(Direction::Host, Message::InitializeMultiStreamDecodeObjectOK);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ShutdownMultiStreamDecodeObject: {
|
||||||
|
auto buffer = shared_memory->host_send_data[0];
|
||||||
|
//[[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1];
|
||||||
|
if (auto const it = decode_objects.find(buffer); it != decode_objects.end()) {
|
||||||
|
shared_memory->dsp_return_data[0] = it->second.Shutdown().raw;
|
||||||
|
} else {
|
||||||
|
LOG_ERROR(Audio_DSP, "operating unregistered buffer {}", buffer);
|
||||||
|
shared_memory->dsp_return_data[0] = Service::Audio::ResultLibOpusInvalidState.raw;
|
||||||
|
}
|
||||||
|
Send(Direction::Host, Message::ShutdownMultiStreamDecodeObjectOK);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case DecodeInterleavedForMultiStream: {
|
||||||
|
auto start_time = system.CoreTiming().GetGlobalTimeUs();
|
||||||
|
|
||||||
|
auto buffer = shared_memory->host_send_data[0];
|
||||||
|
auto input_data = shared_memory->host_send_data[1];
|
||||||
|
auto input_data_size = shared_memory->host_send_data[2];
|
||||||
|
auto output_data = shared_memory->host_send_data[3];
|
||||||
|
auto output_data_size = shared_memory->host_send_data[4];
|
||||||
|
//auto final_range = static_cast<u32>(shared_memory->host_send_data[5]);
|
||||||
|
auto reset_requested = shared_memory->host_send_data[6];
|
||||||
|
|
||||||
|
u32 decoded_samples{0};
|
||||||
|
|
||||||
|
if (auto const it = decode_objects.find(buffer); it != decode_objects.end()) {
|
||||||
|
auto res = ResultSuccess;
|
||||||
|
if (reset_requested)
|
||||||
|
res = it->second.ResetDecoder();
|
||||||
|
if (res == ResultSuccess)
|
||||||
|
res = it->second.Decode(decoded_samples, output_data, output_data_size, input_data, input_data_size);
|
||||||
|
|
||||||
|
auto end_time = system.CoreTiming().GetGlobalTimeUs();
|
||||||
|
shared_memory->dsp_return_data[0] = res.raw;
|
||||||
|
shared_memory->dsp_return_data[1] = decoded_samples;
|
||||||
|
shared_memory->dsp_return_data[2] = (end_time - start_time).count();
|
||||||
|
} else {
|
||||||
|
LOG_ERROR(Audio_DSP, "operating unregistered buffer {}", buffer);
|
||||||
|
shared_memory->dsp_return_data[0] = Service::Audio::ResultLibOpusInvalidState.raw;
|
||||||
|
}
|
||||||
|
Send(Direction::Host, Message::DecodeInterleavedForMultiStreamOK);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
LOG_ERROR(Audio_DSP, "Invalid OpusDecoder command {}", msg);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (auto e : decode_objects)
|
||||||
|
e.second.Shutdown();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
OpusDecoder::~OpusDecoder() {
|
OpusDecoder::~OpusDecoder() {
|
||||||
if (!running) {
|
if (dsp_thread.joinable()) {
|
||||||
init_thread.request_stop();
|
// Shutdown the thread
|
||||||
return;
|
auto const stop_token = dsp_thread.get_stop_token();
|
||||||
|
Send(Direction::DSP, Message::Shutdown);
|
||||||
|
auto msg = Receive(Direction::Host, stop_token);
|
||||||
|
ASSERT_MSG(msg == Message::ShutdownOK, "Expected Opus shutdown code {}, got {}", Message::ShutdownOK, msg);
|
||||||
|
dsp_thread.request_stop();
|
||||||
|
dsp_thread.join();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shutdown the thread
|
|
||||||
Send(Direction::DSP, Message::Shutdown);
|
|
||||||
auto msg = Receive(Direction::Host);
|
|
||||||
ASSERT_MSG(msg == Message::ShutdownOK, "Expected Opus shutdown code {}, got {}",
|
|
||||||
Message::ShutdownOK, msg);
|
|
||||||
main_thread.request_stop();
|
|
||||||
main_thread.join();
|
|
||||||
running = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void OpusDecoder::Send(Direction dir, u32 message) {
|
void OpusDecoder::Send(Direction dir, u32 message) {
|
||||||
@@ -64,206 +397,4 @@ u32 OpusDecoder::Receive(Direction dir, std::stop_token stop_token) {
|
|||||||
return mailbox.Receive(dir, stop_token);
|
return mailbox.Receive(dir, stop_token);
|
||||||
}
|
}
|
||||||
|
|
||||||
void OpusDecoder::Init(std::stop_token stop_token) {
|
|
||||||
Common::SetCurrentThreadName("DSP_OpusDecoder_Init");
|
|
||||||
|
|
||||||
if (Receive(Direction::DSP, stop_token) != Message::Start) {
|
|
||||||
LOG_ERROR(Service_Audio,
|
|
||||||
"DSP OpusDecoder failed to receive Start message. Opus initialization failed.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
main_thread = std::jthread([this](std::stop_token st) { Main(st); });
|
|
||||||
running = true;
|
|
||||||
Send(Direction::Host, Message::StartOK);
|
|
||||||
}
|
|
||||||
|
|
||||||
void OpusDecoder::Main(std::stop_token stop_token) {
|
|
||||||
Common::SetCurrentThreadName("DSP_OpusDecoder_Main");
|
|
||||||
|
|
||||||
while (!stop_token.stop_requested()) {
|
|
||||||
auto msg = Receive(Direction::DSP, stop_token);
|
|
||||||
switch (msg) {
|
|
||||||
case Shutdown:
|
|
||||||
Send(Direction::Host, Message::ShutdownOK);
|
|
||||||
return;
|
|
||||||
|
|
||||||
case GetWorkBufferSize: {
|
|
||||||
auto channel_count = static_cast<s32>(shared_memory->host_send_data[0]);
|
|
||||||
|
|
||||||
ASSERT(IsValidChannelCount(channel_count));
|
|
||||||
|
|
||||||
shared_memory->dsp_return_data[0] = OpusDecodeObject::GetWorkBufferSize(channel_count);
|
|
||||||
Send(Direction::Host, Message::GetWorkBufferSizeOK);
|
|
||||||
} break;
|
|
||||||
|
|
||||||
case InitializeDecodeObject: {
|
|
||||||
auto buffer = shared_memory->host_send_data[0];
|
|
||||||
auto buffer_size = shared_memory->host_send_data[1];
|
|
||||||
auto sample_rate = static_cast<s32>(shared_memory->host_send_data[2]);
|
|
||||||
auto channel_count = static_cast<s32>(shared_memory->host_send_data[3]);
|
|
||||||
|
|
||||||
ASSERT(sample_rate >= 0);
|
|
||||||
ASSERT(IsValidChannelCount(channel_count));
|
|
||||||
ASSERT(buffer_size >= OpusDecodeObject::GetWorkBufferSize(channel_count));
|
|
||||||
|
|
||||||
auto& decoder_object = OpusDecodeObject::Initialize(buffer, buffer);
|
|
||||||
shared_memory->dsp_return_data[0] =
|
|
||||||
decoder_object.InitializeDecoder(sample_rate, channel_count);
|
|
||||||
|
|
||||||
Send(Direction::Host, Message::InitializeDecodeObjectOK);
|
|
||||||
} break;
|
|
||||||
|
|
||||||
case ShutdownDecodeObject: {
|
|
||||||
auto buffer = shared_memory->host_send_data[0];
|
|
||||||
[[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1];
|
|
||||||
|
|
||||||
auto& decoder_object = OpusDecodeObject::Initialize(buffer, buffer);
|
|
||||||
shared_memory->dsp_return_data[0] = decoder_object.Shutdown();
|
|
||||||
|
|
||||||
Send(Direction::Host, Message::ShutdownDecodeObjectOK);
|
|
||||||
} break;
|
|
||||||
|
|
||||||
case DecodeInterleaved: {
|
|
||||||
auto start_time = system.CoreTiming().GetGlobalTimeUs();
|
|
||||||
|
|
||||||
auto buffer = shared_memory->host_send_data[0];
|
|
||||||
auto input_data = shared_memory->host_send_data[1];
|
|
||||||
auto input_data_size = shared_memory->host_send_data[2];
|
|
||||||
auto output_data = shared_memory->host_send_data[3];
|
|
||||||
auto output_data_size = shared_memory->host_send_data[4];
|
|
||||||
auto final_range = static_cast<u32>(shared_memory->host_send_data[5]);
|
|
||||||
auto reset_requested = shared_memory->host_send_data[6];
|
|
||||||
|
|
||||||
u32 decoded_samples{0};
|
|
||||||
|
|
||||||
auto& decoder_object = OpusDecodeObject::Initialize(buffer, buffer);
|
|
||||||
s32 error_code{OPUS_OK};
|
|
||||||
if (reset_requested) {
|
|
||||||
error_code = decoder_object.ResetDecoder();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error_code == OPUS_OK) {
|
|
||||||
error_code = decoder_object.Decode(decoded_samples, output_data, output_data_size,
|
|
||||||
input_data, input_data_size);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error_code == OPUS_OK) {
|
|
||||||
if (final_range && decoder_object.GetFinalRange() != final_range) {
|
|
||||||
error_code = OPUS_INVALID_PACKET;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto end_time = system.CoreTiming().GetGlobalTimeUs();
|
|
||||||
shared_memory->dsp_return_data[0] = error_code;
|
|
||||||
shared_memory->dsp_return_data[1] = decoded_samples;
|
|
||||||
shared_memory->dsp_return_data[2] = (end_time - start_time).count();
|
|
||||||
|
|
||||||
Send(Direction::Host, Message::DecodeInterleavedOK);
|
|
||||||
} break;
|
|
||||||
|
|
||||||
case MapMemory: {
|
|
||||||
[[maybe_unused]] auto buffer = shared_memory->host_send_data[0];
|
|
||||||
[[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1];
|
|
||||||
Send(Direction::Host, Message::MapMemoryOK);
|
|
||||||
} break;
|
|
||||||
|
|
||||||
case UnmapMemory: {
|
|
||||||
[[maybe_unused]] auto buffer = shared_memory->host_send_data[0];
|
|
||||||
[[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1];
|
|
||||||
Send(Direction::Host, Message::UnmapMemoryOK);
|
|
||||||
} break;
|
|
||||||
|
|
||||||
case GetWorkBufferSizeForMultiStream: {
|
|
||||||
auto total_stream_count = static_cast<s32>(shared_memory->host_send_data[0]);
|
|
||||||
auto stereo_stream_count = static_cast<s32>(shared_memory->host_send_data[1]);
|
|
||||||
|
|
||||||
ASSERT(IsValidMultiStreamStreamCounts(total_stream_count, stereo_stream_count));
|
|
||||||
|
|
||||||
shared_memory->dsp_return_data[0] = OpusMultiStreamDecodeObject::GetWorkBufferSize(
|
|
||||||
total_stream_count, stereo_stream_count);
|
|
||||||
Send(Direction::Host, Message::GetWorkBufferSizeForMultiStreamOK);
|
|
||||||
} break;
|
|
||||||
|
|
||||||
case InitializeMultiStreamDecodeObject: {
|
|
||||||
auto buffer = shared_memory->host_send_data[0];
|
|
||||||
auto buffer_size = shared_memory->host_send_data[1];
|
|
||||||
auto sample_rate = static_cast<s32>(shared_memory->host_send_data[2]);
|
|
||||||
auto channel_count = static_cast<s32>(shared_memory->host_send_data[3]);
|
|
||||||
auto total_stream_count = static_cast<s32>(shared_memory->host_send_data[4]);
|
|
||||||
auto stereo_stream_count = static_cast<s32>(shared_memory->host_send_data[5]);
|
|
||||||
// Nintendo seem to have a bug here, they try to use &host_send_data[6] for the channel
|
|
||||||
// mappings, but [6] is never set, and there is not enough room in the argument data for
|
|
||||||
// more than 40 channels, when 255 are possible.
|
|
||||||
// It also means the mapping values are undefined, though likely always 0,
|
|
||||||
// and the mappings given by the game are ignored. The mappings are copied to this
|
|
||||||
// dedicated buffer host side, so let's do as intended.
|
|
||||||
auto mappings = shared_memory->channel_mapping.data();
|
|
||||||
|
|
||||||
ASSERT(IsValidMultiStreamStreamCounts(total_stream_count, stereo_stream_count));
|
|
||||||
ASSERT(sample_rate >= 0);
|
|
||||||
ASSERT(buffer_size >= OpusMultiStreamDecodeObject::GetWorkBufferSize(
|
|
||||||
total_stream_count, stereo_stream_count));
|
|
||||||
|
|
||||||
auto& decoder_object = OpusMultiStreamDecodeObject::Initialize(buffer, buffer);
|
|
||||||
shared_memory->dsp_return_data[0] = decoder_object.InitializeDecoder(
|
|
||||||
sample_rate, total_stream_count, channel_count, stereo_stream_count, mappings);
|
|
||||||
|
|
||||||
Send(Direction::Host, Message::InitializeMultiStreamDecodeObjectOK);
|
|
||||||
} break;
|
|
||||||
|
|
||||||
case ShutdownMultiStreamDecodeObject: {
|
|
||||||
auto buffer = shared_memory->host_send_data[0];
|
|
||||||
[[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1];
|
|
||||||
|
|
||||||
auto& decoder_object = OpusMultiStreamDecodeObject::Initialize(buffer, buffer);
|
|
||||||
shared_memory->dsp_return_data[0] = decoder_object.Shutdown();
|
|
||||||
|
|
||||||
Send(Direction::Host, Message::ShutdownMultiStreamDecodeObjectOK);
|
|
||||||
} break;
|
|
||||||
|
|
||||||
case DecodeInterleavedForMultiStream: {
|
|
||||||
auto start_time = system.CoreTiming().GetGlobalTimeUs();
|
|
||||||
|
|
||||||
auto buffer = shared_memory->host_send_data[0];
|
|
||||||
auto input_data = shared_memory->host_send_data[1];
|
|
||||||
auto input_data_size = shared_memory->host_send_data[2];
|
|
||||||
auto output_data = shared_memory->host_send_data[3];
|
|
||||||
auto output_data_size = shared_memory->host_send_data[4];
|
|
||||||
auto final_range = static_cast<u32>(shared_memory->host_send_data[5]);
|
|
||||||
auto reset_requested = shared_memory->host_send_data[6];
|
|
||||||
|
|
||||||
u32 decoded_samples{0};
|
|
||||||
|
|
||||||
auto& decoder_object = OpusMultiStreamDecodeObject::Initialize(buffer, buffer);
|
|
||||||
s32 error_code{OPUS_OK};
|
|
||||||
if (reset_requested) {
|
|
||||||
error_code = decoder_object.ResetDecoder();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error_code == OPUS_OK) {
|
|
||||||
error_code = decoder_object.Decode(decoded_samples, output_data, output_data_size,
|
|
||||||
input_data, input_data_size);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error_code == OPUS_OK) {
|
|
||||||
if (final_range && decoder_object.GetFinalRange() != final_range) {
|
|
||||||
error_code = OPUS_INVALID_PACKET;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto end_time = system.CoreTiming().GetGlobalTimeUs();
|
|
||||||
shared_memory->dsp_return_data[0] = error_code;
|
|
||||||
shared_memory->dsp_return_data[1] = decoded_samples;
|
|
||||||
shared_memory->dsp_return_data[2] = (end_time - start_time).count();
|
|
||||||
|
|
||||||
Send(Direction::Host, Message::DecodeInterleavedForMultiStreamOK);
|
|
||||||
} break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
LOG_ERROR(Service_Audio, "Invalid OpusDecoder command {}", msg);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace AudioCore::ADSP::OpusDecoder
|
} // namespace AudioCore::ADSP::OpusDecoder
|
||||||
|
|||||||
@@ -6,12 +6,13 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
|
||||||
|
#include "common/container/unordered_map.h"
|
||||||
#include "audio_core/adsp/apps/opus/shared_memory.h"
|
#include "audio_core/adsp/apps/opus/shared_memory.h"
|
||||||
#include "audio_core/adsp/mailbox.h"
|
#include "audio_core/adsp/mailbox.h"
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
|
#include "core/hle/result.h"
|
||||||
|
|
||||||
namespace Core {
|
namespace Core {
|
||||||
class System;
|
class System;
|
||||||
@@ -48,16 +49,14 @@ enum Message : u32 {
|
|||||||
DecodeInterleavedForMultiStreamOK = 50,
|
DecodeInterleavedForMultiStreamOK = 50,
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/// @brief The AudioRenderer application running on the ADSP.
|
||||||
* The AudioRenderer application running on the ADSP.
|
|
||||||
*/
|
|
||||||
class OpusDecoder {
|
class OpusDecoder {
|
||||||
public:
|
public:
|
||||||
explicit OpusDecoder(Core::System& system);
|
explicit OpusDecoder(Core::System& system);
|
||||||
~OpusDecoder();
|
~OpusDecoder();
|
||||||
|
|
||||||
bool IsRunning() const noexcept {
|
bool IsRunning() const noexcept {
|
||||||
return running;
|
return dsp_thread.joinable();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Send(Direction dir, u32 message);
|
void Send(Direction dir, u32 message);
|
||||||
@@ -68,28 +67,12 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/**
|
|
||||||
* Initializing thread, launched at audio_core boot to avoid blocking the main emu boot thread.
|
|
||||||
*/
|
|
||||||
void Init(std::stop_token stop_token);
|
|
||||||
/**
|
|
||||||
* Main OpusDecoder thread, responsible for processing the incoming Opus packets.
|
|
||||||
*/
|
|
||||||
void Main(std::stop_token stop_token);
|
|
||||||
|
|
||||||
/// Core system
|
|
||||||
Core::System& system;
|
|
||||||
/// Mailbox to communicate messages with the host, drives the main thread
|
/// Mailbox to communicate messages with the host, drives the main thread
|
||||||
Mailbox mailbox;
|
Mailbox mailbox;
|
||||||
/// Init thread
|
|
||||||
std::jthread init_thread{};
|
|
||||||
/// Main thread
|
|
||||||
std::jthread main_thread{};
|
|
||||||
/// The current state
|
|
||||||
bool running{};
|
|
||||||
/// Structure shared with the host, input data set by the host before sending a mailbox message,
|
/// Structure shared with the host, input data set by the host before sending a mailbox message,
|
||||||
/// and the responses are written back by the OpusDecoder.
|
/// and the responses are written back by the OpusDecoder.
|
||||||
SharedMemory* shared_memory{};
|
SharedMemory* shared_memory{};
|
||||||
|
std::jthread dsp_thread{};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace AudioCore::ADSP::OpusDecoder
|
} // namespace AudioCore::ADSP::OpusDecoder
|
||||||
|
|||||||
@@ -1,113 +0,0 @@
|
|||||||
// 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
|
|
||||||
|
|
||||||
#include "audio_core/adsp/apps/opus/opus_multistream_decode_object.h"
|
|
||||||
#include "common/assert.h"
|
|
||||||
|
|
||||||
namespace AudioCore::ADSP::OpusDecoder {
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
constexpr u32 OpusStreamCountMax = 255;
|
|
||||||
|
|
||||||
bool IsValidStreamCounts(u32 total_stream_count, u32 stereo_stream_count) {
|
|
||||||
return total_stream_count > 0 && total_stream_count <= OpusStreamCountMax &&
|
|
||||||
static_cast<s32>(stereo_stream_count) >= 0 &&
|
|
||||||
stereo_stream_count <= total_stream_count;
|
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
u32 OpusMultiStreamDecodeObject::GetWorkBufferSize(u32 total_stream_count,
|
|
||||||
u32 stereo_stream_count) {
|
|
||||||
if (IsValidStreamCounts(total_stream_count, stereo_stream_count)) {
|
|
||||||
return static_cast<u32>(sizeof(OpusMultiStreamDecodeObject)) +
|
|
||||||
opus_multistream_decoder_get_size(total_stream_count, stereo_stream_count);
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
OpusMultiStreamDecodeObject& OpusMultiStreamDecodeObject::Initialize(u64 buffer, u64 buffer2) {
|
|
||||||
auto* new_decoder = reinterpret_cast<OpusMultiStreamDecodeObject*>(buffer);
|
|
||||||
auto* comparison = reinterpret_cast<OpusMultiStreamDecodeObject*>(buffer2);
|
|
||||||
|
|
||||||
if (new_decoder->magic == DecodeMultiStreamObjectMagic) {
|
|
||||||
if (!new_decoder->initialized ||
|
|
||||||
(new_decoder->initialized && new_decoder->self == comparison)) {
|
|
||||||
new_decoder->state_valid = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
new_decoder->initialized = false;
|
|
||||||
new_decoder->state_valid = true;
|
|
||||||
}
|
|
||||||
return *new_decoder;
|
|
||||||
}
|
|
||||||
|
|
||||||
s32 OpusMultiStreamDecodeObject::InitializeDecoder(u32 sample_rate, u32 total_stream_count,
|
|
||||||
u32 channel_count, u32 stereo_stream_count,
|
|
||||||
u8* mappings) {
|
|
||||||
if (!state_valid) {
|
|
||||||
return OPUS_INVALID_STATE;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (initialized) {
|
|
||||||
return OPUS_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
// See OpusDecodeObject::InitializeDecoder for an explanation of this
|
|
||||||
decoder = (LibOpusMSDecoder*)(this + 1);
|
|
||||||
s32 ret = opus_multistream_decoder_init(decoder, sample_rate, channel_count, total_stream_count,
|
|
||||||
stereo_stream_count, mappings);
|
|
||||||
if (ret == OPUS_OK) {
|
|
||||||
magic = DecodeMultiStreamObjectMagic;
|
|
||||||
initialized = true;
|
|
||||||
state_valid = true;
|
|
||||||
self = this;
|
|
||||||
final_range = 0;
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
s32 OpusMultiStreamDecodeObject::Shutdown() {
|
|
||||||
if (!state_valid) {
|
|
||||||
return OPUS_INVALID_STATE;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (initialized) {
|
|
||||||
magic = 0x0;
|
|
||||||
initialized = false;
|
|
||||||
state_valid = false;
|
|
||||||
self = nullptr;
|
|
||||||
final_range = 0;
|
|
||||||
decoder = nullptr;
|
|
||||||
}
|
|
||||||
return OPUS_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
s32 OpusMultiStreamDecodeObject::ResetDecoder() {
|
|
||||||
return opus_multistream_decoder_ctl(decoder, OPUS_RESET_STATE);
|
|
||||||
}
|
|
||||||
|
|
||||||
s32 OpusMultiStreamDecodeObject::Decode(u32& out_sample_count, u64 output_data,
|
|
||||||
u64 output_data_size, u64 input_data, u64 input_data_size) {
|
|
||||||
ASSERT(initialized);
|
|
||||||
out_sample_count = 0;
|
|
||||||
|
|
||||||
if (!state_valid) {
|
|
||||||
return OPUS_INVALID_STATE;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto ret_code_or_samples = opus_multistream_decode(
|
|
||||||
decoder, reinterpret_cast<const u8*>(input_data), static_cast<opus_int32>(input_data_size),
|
|
||||||
reinterpret_cast<opus_int16*>(output_data), static_cast<opus_int32>(output_data_size), 0);
|
|
||||||
|
|
||||||
if (ret_code_or_samples < OPUS_OK) {
|
|
||||||
return ret_code_or_samples;
|
|
||||||
}
|
|
||||||
|
|
||||||
out_sample_count = ret_code_or_samples;
|
|
||||||
return opus_multistream_decoder_ctl(decoder, OPUS_GET_FINAL_RANGE_REQUEST, &final_range);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace AudioCore::ADSP::OpusDecoder
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <opus_multistream.h>
|
|
||||||
|
|
||||||
#include "common/common_types.h"
|
|
||||||
|
|
||||||
namespace AudioCore::ADSP::OpusDecoder {
|
|
||||||
using LibOpusMSDecoder = ::OpusMSDecoder;
|
|
||||||
static constexpr u32 DecodeMultiStreamObjectMagic = 0xDEADBEEF;
|
|
||||||
|
|
||||||
class OpusMultiStreamDecodeObject {
|
|
||||||
public:
|
|
||||||
static u32 GetWorkBufferSize(u32 total_stream_count, u32 stereo_stream_count);
|
|
||||||
static OpusMultiStreamDecodeObject& Initialize(u64 buffer, u64 buffer2);
|
|
||||||
|
|
||||||
s32 InitializeDecoder(u32 sample_rate, u32 total_stream_count, u32 channel_count,
|
|
||||||
u32 stereo_stream_count, u8* mappings);
|
|
||||||
s32 Shutdown();
|
|
||||||
s32 ResetDecoder();
|
|
||||||
s32 Decode(u32& out_sample_count, u64 output_data, u64 output_data_size, u64 input_data,
|
|
||||||
u64 input_data_size);
|
|
||||||
u32 GetFinalRange() const noexcept {
|
|
||||||
return final_range;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
u32 magic;
|
|
||||||
bool initialized;
|
|
||||||
bool state_valid;
|
|
||||||
OpusMultiStreamDecodeObject* self;
|
|
||||||
u32 final_range;
|
|
||||||
LibOpusMSDecoder* decoder;
|
|
||||||
};
|
|
||||||
static_assert(std::is_trivially_constructible_v<OpusMultiStreamDecodeObject>);
|
|
||||||
|
|
||||||
} // namespace AudioCore::ADSP::OpusDecoder
|
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "common/common_types.h"
|
||||||
|
|
||||||
|
namespace AudioCore::ADSP {
|
||||||
|
|
||||||
|
static constexpr u32 DECODE_OBJECT_MAGIC = 0xDEADBEEF;
|
||||||
|
struct LibOpusDecoder {
|
||||||
|
u32 magic;
|
||||||
|
bool initialized;
|
||||||
|
bool state_valid;
|
||||||
|
LibOpusDecoder* self;
|
||||||
|
u32 final_range;
|
||||||
|
void* decoder;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(LibOpusDecoder) == 32);
|
||||||
|
|
||||||
|
static constexpr u32 DECODE_MULTISTREAM_OBJECT_MAGIC = 0xDEADBEEF;
|
||||||
|
struct LibOpusMultistreamDecoder {
|
||||||
|
u32 magic;
|
||||||
|
bool initialized;
|
||||||
|
bool state_valid;
|
||||||
|
LibOpusMultistreamDecoder* self;
|
||||||
|
u32 final_range;
|
||||||
|
void* decoder;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(LibOpusMultistreamDecoder) == 32);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "common/common_funcs.h"
|
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
|
|
||||||
namespace AudioCore::ADSP::OpusDecoder {
|
namespace AudioCore::ADSP::OpusDecoder {
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
@@ -9,7 +12,9 @@
|
|||||||
namespace AudioCore::Renderer {
|
namespace AudioCore::Renderer {
|
||||||
|
|
||||||
Manager::Manager(Core::System& system_)
|
Manager::Manager(Core::System& system_)
|
||||||
: system{system_}, system_manager{std::make_unique<SystemManager>(system)} {
|
: system{system_}
|
||||||
|
, system_manager{std::make_unique<SystemManager>(system)}
|
||||||
|
{
|
||||||
std::iota(session_ids.begin(), session_ids.end(), 0);
|
std::iota(session_ids.begin(), session_ids.end(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,14 +43,12 @@ Result Manager::GetWorkBufferSize(const AudioRendererParameterInternal& params,
|
|||||||
|
|
||||||
s32 Manager::GetSessionId() {
|
s32 Manager::GetSessionId() {
|
||||||
std::scoped_lock l{session_lock};
|
std::scoped_lock l{session_lock};
|
||||||
auto session_id{session_ids[session_count]};
|
ASSERT(session_count <= session_ids.size());
|
||||||
|
auto const session_id = session_ids[session_count];
|
||||||
if (session_id == -1) {
|
if (session_id >= 0) {
|
||||||
return -1;
|
session_ids[session_count] = -1;
|
||||||
|
session_count++;
|
||||||
}
|
}
|
||||||
|
|
||||||
session_ids[session_count] = -1;
|
|
||||||
session_count++;
|
|
||||||
return session_id;
|
return session_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
#include "audio_core/opus/hardware_opus.h"
|
#include "audio_core/opus/hardware_opus.h"
|
||||||
#include "audio_core/opus/parameters.h"
|
#include "audio_core/opus/parameters.h"
|
||||||
#include "common/alignment.h"
|
#include "common/alignment.h"
|
||||||
|
#include "common/scope_exit.h"
|
||||||
#include "common/swap.h"
|
#include "common/swap.h"
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
|
|
||||||
@@ -28,10 +29,18 @@ OpusDecoder::OpusDecoder(Core::System& system_, HardwareOpus& hardware_opus_)
|
|||||||
OpusDecoder::~OpusDecoder() {
|
OpusDecoder::~OpusDecoder() {
|
||||||
if (decode_object_initialized) {
|
if (decode_object_initialized) {
|
||||||
hardware_opus.ShutdownDecodeObject(shared_buffer.data(), shared_buffer.size());
|
hardware_opus.ShutdownDecodeObject(shared_buffer.data(), shared_buffer.size());
|
||||||
|
hardware_opus.UnregisterDecoder(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Result OpusDecoder::Initialize(const OpusParametersEx& params, Kernel::KTransferMemory* transfer_memory, u64 transfer_memory_size) {
|
Result OpusDecoder::Initialize(const OpusParametersEx& params, Kernel::KTransferMemory* transfer_memory, u64 transfer_memory_size) {
|
||||||
|
R_TRY(hardware_opus.RegisterDecoder(this));
|
||||||
|
SCOPE_EXIT {
|
||||||
|
if (!decode_object_initialized) {
|
||||||
|
hardware_opus.UnregisterDecoder(this);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
auto frame_size{params.use_large_frame_size ? 5760 : 1920};
|
auto frame_size{params.use_large_frame_size ? 5760 : 1920};
|
||||||
shared_buffer.resize(transfer_memory_size);
|
shared_buffer.resize(transfer_memory_size);
|
||||||
shared_memory_mapped = true;
|
shared_memory_mapped = true;
|
||||||
@@ -61,6 +70,13 @@ Result OpusDecoder::Initialize(const OpusParametersEx& params, Kernel::KTransfer
|
|||||||
}
|
}
|
||||||
|
|
||||||
Result OpusDecoder::Initialize(const OpusMultiStreamParametersEx& params, Kernel::KTransferMemory* transfer_memory, u64 transfer_memory_size) {
|
Result OpusDecoder::Initialize(const OpusMultiStreamParametersEx& params, Kernel::KTransferMemory* transfer_memory, u64 transfer_memory_size) {
|
||||||
|
R_TRY(hardware_opus.RegisterDecoder(this));
|
||||||
|
SCOPE_EXIT {
|
||||||
|
if (!decode_object_initialized) {
|
||||||
|
hardware_opus.UnregisterDecoder(this);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
auto frame_size{params.use_large_frame_size ? 5760 : 1920};
|
auto frame_size{params.use_large_frame_size ? 5760 : 1920};
|
||||||
shared_buffer.resize(transfer_memory_size, 0);
|
shared_buffer.resize(transfer_memory_size, 0);
|
||||||
shared_memory_mapped = true;
|
shared_memory_mapped = true;
|
||||||
|
|||||||
@@ -4,47 +4,46 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <array>
|
#include <array>
|
||||||
|
|
||||||
#include "audio_core/audio_core.h"
|
#include "audio_core/audio_core.h"
|
||||||
#include "audio_core/opus/hardware_opus.h"
|
#include "audio_core/opus/hardware_opus.h"
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
|
#include "core/hle/result.h"
|
||||||
|
|
||||||
namespace AudioCore::OpusDecoder {
|
namespace AudioCore::OpusDecoder {
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
using namespace Service::Audio;
|
using namespace Service::Audio;
|
||||||
|
|
||||||
static constexpr Result ResultCodeFromLibOpusErrorCode(u64 error_code) {
|
|
||||||
s32 error{static_cast<s32>(error_code)};
|
|
||||||
ASSERT(error <= OPUS_OK);
|
|
||||||
switch (error) {
|
|
||||||
case OPUS_ALLOC_FAIL:
|
|
||||||
R_THROW(ResultLibOpusAllocFail);
|
|
||||||
case OPUS_INVALID_STATE:
|
|
||||||
R_THROW(ResultLibOpusInvalidState);
|
|
||||||
case OPUS_UNIMPLEMENTED:
|
|
||||||
R_THROW(ResultLibOpusUnimplemented);
|
|
||||||
case OPUS_INVALID_PACKET:
|
|
||||||
R_THROW(ResultLibOpusInvalidPacket);
|
|
||||||
case OPUS_INTERNAL_ERROR:
|
|
||||||
R_THROW(ResultLibOpusInternalError);
|
|
||||||
case OPUS_BUFFER_TOO_SMALL:
|
|
||||||
R_THROW(ResultBufferTooSmall);
|
|
||||||
case OPUS_BAD_ARG:
|
|
||||||
R_THROW(ResultLibOpusBadArg);
|
|
||||||
case OPUS_OK:
|
|
||||||
R_RETURN(ResultSuccess);
|
|
||||||
}
|
|
||||||
UNREACHABLE();
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
HardwareOpus::HardwareOpus(Core::System& system_)
|
HardwareOpus::HardwareOpus(Core::System& system_)
|
||||||
: system{system_}, opus_decoder{system.AudioCore().ADSP().OpusDecoder()} {
|
: system{system_}
|
||||||
|
, opus_decoder{system.AudioCore().ADSP().OpusDecoder()}
|
||||||
|
{
|
||||||
opus_decoder.SetSharedMemory(shared_memory);
|
opus_decoder.SetSharedMemory(shared_memory);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Result HardwareOpus::RegisterDecoder(OpusDecoder* decoder) {
|
||||||
|
std::scoped_lock l{mutex};
|
||||||
|
const auto slot = std::ranges::find(decoders, nullptr);
|
||||||
|
if (slot == decoders.end()) {
|
||||||
|
R_THROW(ResultOutOfOpusDecoders);
|
||||||
|
}
|
||||||
|
*slot = decoder;
|
||||||
|
R_SUCCEED();
|
||||||
|
}
|
||||||
|
|
||||||
|
void HardwareOpus::UnregisterDecoder(OpusDecoder* decoder) {
|
||||||
|
std::scoped_lock l{mutex};
|
||||||
|
const auto slot = std::ranges::find(decoders, decoder);
|
||||||
|
if (slot == decoders.end()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
*slot = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
u32 HardwareOpus::GetWorkBufferSize(u32 channel) {
|
u32 HardwareOpus::GetWorkBufferSize(u32 channel) {
|
||||||
if (!opus_decoder.IsRunning()) {
|
if (!opus_decoder.IsRunning()) {
|
||||||
return 0;
|
return 0;
|
||||||
@@ -92,7 +91,7 @@ Result HardwareOpus::InitializeDecodeObject(u32 sample_rate, u32 channel_count,
|
|||||||
R_THROW(ResultInvalidOpusDSPReturnCode);
|
R_THROW(ResultInvalidOpusDSPReturnCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
R_RETURN(ResultCodeFromLibOpusErrorCode(shared_memory.dsp_return_data[0]));
|
R_RETURN(Result(u32(shared_memory.dsp_return_data[0])));
|
||||||
}
|
}
|
||||||
|
|
||||||
Result HardwareOpus::InitializeMultiStreamDecodeObject(u32 sample_rate, u32 channel_count,
|
Result HardwareOpus::InitializeMultiStreamDecodeObject(u32 sample_rate, u32 channel_count,
|
||||||
@@ -120,7 +119,7 @@ Result HardwareOpus::InitializeMultiStreamDecodeObject(u32 sample_rate, u32 chan
|
|||||||
R_THROW(ResultInvalidOpusDSPReturnCode);
|
R_THROW(ResultInvalidOpusDSPReturnCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
R_RETURN(ResultCodeFromLibOpusErrorCode(shared_memory.dsp_return_data[0]));
|
R_RETURN(Result(u32(shared_memory.dsp_return_data[0])));
|
||||||
}
|
}
|
||||||
|
|
||||||
Result HardwareOpus::ShutdownDecodeObject(void* buffer, u64 buffer_size) {
|
Result HardwareOpus::ShutdownDecodeObject(void* buffer, u64 buffer_size) {
|
||||||
@@ -134,7 +133,7 @@ Result HardwareOpus::ShutdownDecodeObject(void* buffer, u64 buffer_size) {
|
|||||||
"Expected Opus shutdown code {}, got {}",
|
"Expected Opus shutdown code {}, got {}",
|
||||||
ADSP::OpusDecoder::Message::ShutdownDecodeObjectOK, msg);
|
ADSP::OpusDecoder::Message::ShutdownDecodeObjectOK, msg);
|
||||||
|
|
||||||
R_RETURN(ResultCodeFromLibOpusErrorCode(shared_memory.dsp_return_data[0]));
|
R_RETURN(Result(u32(shared_memory.dsp_return_data[0])));
|
||||||
}
|
}
|
||||||
|
|
||||||
Result HardwareOpus::ShutdownMultiStreamDecodeObject(void* buffer, u64 buffer_size) {
|
Result HardwareOpus::ShutdownMultiStreamDecodeObject(void* buffer, u64 buffer_size) {
|
||||||
@@ -149,7 +148,7 @@ Result HardwareOpus::ShutdownMultiStreamDecodeObject(void* buffer, u64 buffer_si
|
|||||||
"Expected Opus shutdown code {}, got {}",
|
"Expected Opus shutdown code {}, got {}",
|
||||||
ADSP::OpusDecoder::Message::ShutdownMultiStreamDecodeObjectOK, msg);
|
ADSP::OpusDecoder::Message::ShutdownMultiStreamDecodeObjectOK, msg);
|
||||||
|
|
||||||
R_RETURN(ResultCodeFromLibOpusErrorCode(shared_memory.dsp_return_data[0]));
|
R_RETURN(Result(u32(shared_memory.dsp_return_data[0])));
|
||||||
}
|
}
|
||||||
|
|
||||||
Result HardwareOpus::DecodeInterleaved(u32& out_sample_count, void* output_data,
|
Result HardwareOpus::DecodeInterleaved(u32& out_sample_count, void* output_data,
|
||||||
@@ -173,12 +172,12 @@ Result HardwareOpus::DecodeInterleaved(u32& out_sample_count, void* output_data,
|
|||||||
R_THROW(ResultInvalidOpusDSPReturnCode);
|
R_THROW(ResultInvalidOpusDSPReturnCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto error_code{static_cast<s32>(shared_memory.dsp_return_data[0])};
|
auto error_code = s32(shared_memory.dsp_return_data[0]);
|
||||||
if (error_code == OPUS_OK) {
|
if (error_code == ResultSuccess.raw) {
|
||||||
out_sample_count = static_cast<u32>(shared_memory.dsp_return_data[1]);
|
out_sample_count = u32(shared_memory.dsp_return_data[1]);
|
||||||
out_time_taken = 1000 * shared_memory.dsp_return_data[2];
|
out_time_taken = 1000 * shared_memory.dsp_return_data[2];
|
||||||
}
|
}
|
||||||
R_RETURN(ResultCodeFromLibOpusErrorCode(error_code));
|
R_RETURN(Result(u32(error_code)));
|
||||||
}
|
}
|
||||||
|
|
||||||
Result HardwareOpus::DecodeInterleavedForMultiStream(u32& out_sample_count, void* output_data,
|
Result HardwareOpus::DecodeInterleavedForMultiStream(u32& out_sample_count, void* output_data,
|
||||||
@@ -187,29 +186,27 @@ Result HardwareOpus::DecodeInterleavedForMultiStream(u32& out_sample_count, void
|
|||||||
void* buffer, u64& out_time_taken,
|
void* buffer, u64& out_time_taken,
|
||||||
bool reset) {
|
bool reset) {
|
||||||
std::scoped_lock l{mutex};
|
std::scoped_lock l{mutex};
|
||||||
shared_memory.host_send_data[0] = (u64)buffer;
|
shared_memory.host_send_data[0] = u64(buffer);
|
||||||
shared_memory.host_send_data[1] = (u64)input_data;
|
shared_memory.host_send_data[1] = u64(input_data);
|
||||||
shared_memory.host_send_data[2] = input_data_size;
|
shared_memory.host_send_data[2] = input_data_size;
|
||||||
shared_memory.host_send_data[3] = (u64)output_data;
|
shared_memory.host_send_data[3] = u64(output_data);
|
||||||
shared_memory.host_send_data[4] = output_data_size;
|
shared_memory.host_send_data[4] = output_data_size;
|
||||||
shared_memory.host_send_data[5] = 0;
|
shared_memory.host_send_data[5] = 0;
|
||||||
shared_memory.host_send_data[6] = reset;
|
shared_memory.host_send_data[6] = reset;
|
||||||
|
|
||||||
opus_decoder.Send(ADSP::Direction::DSP,
|
opus_decoder.Send(ADSP::Direction::DSP, ADSP::OpusDecoder::Message::DecodeInterleavedForMultiStream);
|
||||||
ADSP::OpusDecoder::Message::DecodeInterleavedForMultiStream);
|
|
||||||
auto msg = opus_decoder.Receive(ADSP::Direction::Host);
|
auto msg = opus_decoder.Receive(ADSP::Direction::Host);
|
||||||
if (msg != ADSP::OpusDecoder::Message::DecodeInterleavedForMultiStreamOK) {
|
if (msg != ADSP::OpusDecoder::Message::DecodeInterleavedForMultiStreamOK) {
|
||||||
LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}",
|
LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}", ADSP::OpusDecoder::Message::DecodeInterleavedForMultiStreamOK, msg);
|
||||||
ADSP::OpusDecoder::Message::DecodeInterleavedForMultiStreamOK, msg);
|
|
||||||
R_THROW(ResultInvalidOpusDSPReturnCode);
|
R_THROW(ResultInvalidOpusDSPReturnCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto error_code{static_cast<s32>(shared_memory.dsp_return_data[0])};
|
auto const error_code = shared_memory.dsp_return_data[0];
|
||||||
if (error_code == OPUS_OK) {
|
if (error_code == ResultSuccess.raw) {
|
||||||
out_sample_count = static_cast<u32>(shared_memory.dsp_return_data[1]);
|
out_sample_count = static_cast<u32>(shared_memory.dsp_return_data[1]);
|
||||||
out_time_taken = 1000 * shared_memory.dsp_return_data[2];
|
out_time_taken = 1000 * shared_memory.dsp_return_data[2];
|
||||||
}
|
}
|
||||||
R_RETURN(ResultCodeFromLibOpusErrorCode(error_code));
|
R_RETURN(Result(u32(shared_memory.dsp_return_data[0])));
|
||||||
}
|
}
|
||||||
|
|
||||||
Result HardwareOpus::MapMemory(void* buffer, u64 buffer_size) {
|
Result HardwareOpus::MapMemory(void* buffer, u64 buffer_size) {
|
||||||
@@ -220,8 +217,7 @@ Result HardwareOpus::MapMemory(void* buffer, u64 buffer_size) {
|
|||||||
opus_decoder.Send(ADSP::Direction::DSP, ADSP::OpusDecoder::Message::MapMemory);
|
opus_decoder.Send(ADSP::Direction::DSP, ADSP::OpusDecoder::Message::MapMemory);
|
||||||
auto msg = opus_decoder.Receive(ADSP::Direction::Host);
|
auto msg = opus_decoder.Receive(ADSP::Direction::Host);
|
||||||
if (msg != ADSP::OpusDecoder::Message::MapMemoryOK) {
|
if (msg != ADSP::OpusDecoder::Message::MapMemoryOK) {
|
||||||
LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}",
|
LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}", ADSP::OpusDecoder::Message::MapMemoryOK, msg);
|
||||||
ADSP::OpusDecoder::Message::MapMemoryOK, msg);
|
|
||||||
R_THROW(ResultInvalidOpusDSPReturnCode);
|
R_THROW(ResultInvalidOpusDSPReturnCode);
|
||||||
}
|
}
|
||||||
R_SUCCEED();
|
R_SUCCEED();
|
||||||
@@ -235,8 +231,7 @@ Result HardwareOpus::UnmapMemory(void* buffer, u64 buffer_size) {
|
|||||||
opus_decoder.Send(ADSP::Direction::DSP, ADSP::OpusDecoder::Message::UnmapMemory);
|
opus_decoder.Send(ADSP::Direction::DSP, ADSP::OpusDecoder::Message::UnmapMemory);
|
||||||
auto msg = opus_decoder.Receive(ADSP::Direction::Host);
|
auto msg = opus_decoder.Receive(ADSP::Direction::Host);
|
||||||
if (msg != ADSP::OpusDecoder::Message::UnmapMemoryOK) {
|
if (msg != ADSP::OpusDecoder::Message::UnmapMemoryOK) {
|
||||||
LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}",
|
LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}", ADSP::OpusDecoder::Message::UnmapMemoryOK, msg);
|
||||||
ADSP::OpusDecoder::Message::UnmapMemoryOK, msg);
|
|
||||||
R_THROW(ResultInvalidOpusDSPReturnCode);
|
R_THROW(ResultInvalidOpusDSPReturnCode);
|
||||||
}
|
}
|
||||||
R_SUCCEED();
|
R_SUCCEED();
|
||||||
|
|||||||
@@ -1,20 +1,25 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <array>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <opus.h>
|
|
||||||
|
|
||||||
#include "audio_core/adsp/apps/opus/opus_decoder.h"
|
#include "audio_core/adsp/apps/opus/opus_decoder.h"
|
||||||
#include "audio_core/adsp/apps/opus/shared_memory.h"
|
#include "audio_core/adsp/apps/opus/shared_memory.h"
|
||||||
#include "audio_core/adsp/mailbox.h"
|
#include "audio_core/adsp/mailbox.h"
|
||||||
#include "core/hle/service/audio/errors.h"
|
#include "core/hle/service/audio/errors.h"
|
||||||
|
|
||||||
namespace AudioCore::OpusDecoder {
|
namespace AudioCore::OpusDecoder {
|
||||||
|
class OpusDecoder;
|
||||||
class HardwareOpus {
|
class HardwareOpus {
|
||||||
public:
|
public:
|
||||||
HardwareOpus(Core::System& system);
|
HardwareOpus(Core::System& system);
|
||||||
|
Result RegisterDecoder(OpusDecoder* decoder);
|
||||||
|
void UnregisterDecoder(OpusDecoder* decoder);
|
||||||
|
|
||||||
u32 GetWorkBufferSize(u32 channel);
|
u32 GetWorkBufferSize(u32 channel);
|
||||||
u32 GetWorkBufferSizeForMultiStream(u32 total_stream_count, u32 stereo_stream_count);
|
u32 GetWorkBufferSizeForMultiStream(u32 total_stream_count, u32 stereo_stream_count);
|
||||||
@@ -39,6 +44,7 @@ public:
|
|||||||
private:
|
private:
|
||||||
Core::System& system;
|
Core::System& system;
|
||||||
std::mutex mutex;
|
std::mutex mutex;
|
||||||
|
std::array<OpusDecoder*, 24> decoders{};
|
||||||
ADSP::OpusDecoder::OpusDecoder& opus_decoder;
|
ADSP::OpusDecoder::OpusDecoder& opus_decoder;
|
||||||
ADSP::OpusDecoder::SharedMemory shared_memory;
|
ADSP::OpusDecoder::SharedMemory shared_memory;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -28,10 +28,18 @@ namespace {
|
|||||||
//
|
//
|
||||||
// Keep in sync with cubeb_sink.cpp name.
|
// Keep in sync with cubeb_sink.cpp name.
|
||||||
SDL_SetHint("SDL_AUDIO_DEVICE_APP_NAME", "yuzu Latency Getter");
|
SDL_SetHint("SDL_AUDIO_DEVICE_APP_NAME", "yuzu Latency Getter");
|
||||||
|
#ifdef __ANDROID__
|
||||||
|
SDL_SetHintWithPriority(SDL_HINT_AUDIO_DRIVER, "openslES", SDL_HINT_OVERRIDE);
|
||||||
if (!SDL_InitSubSystem(SDL_INIT_AUDIO)) {
|
if (!SDL_InitSubSystem(SDL_INIT_AUDIO)) {
|
||||||
|
LOG_WARNING(Audio_Sink, "OpenSL ES audio initialization failed: {}; retrying default drivers", SDL_GetError());
|
||||||
|
SDL_ResetHint(SDL_HINT_AUDIO_DRIVER);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
if (!SDL_WasInit(SDL_INIT_AUDIO) && !SDL_InitSubSystem(SDL_INIT_AUDIO)) {
|
||||||
LOG_CRITICAL(Audio_Sink, "SDL_InitSubSystem audio failed: {}", SDL_GetError());
|
LOG_CRITICAL(Audio_Sink, "SDL_InitSubSystem audio failed: {}", SDL_GetError());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
LOG_INFO(Audio_Sink, "SDL audio driver: {}", SDL_GetCurrentAudioDriver());
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,8 @@
|
|||||||
#define LOSSLESS_DIR "lossless"
|
#define LOSSLESS_DIR "lossless"
|
||||||
#define NAND_DIR "nand"
|
#define NAND_DIR "nand"
|
||||||
#define PLAY_TIME_DIR "play_time"
|
#define PLAY_TIME_DIR "play_time"
|
||||||
|
#define POST_PRESET_DIR "post_presets"
|
||||||
|
#define POST_SHADER_DIR "post_shaders"
|
||||||
#define SCREENSHOTS_DIR "screenshots"
|
#define SCREENSHOTS_DIR "screenshots"
|
||||||
#define SDMC_DIR "sdmc"
|
#define SDMC_DIR "sdmc"
|
||||||
#define SHADER_DIR "shader"
|
#define SHADER_DIR "shader"
|
||||||
|
|||||||
@@ -160,6 +160,8 @@ public:
|
|||||||
GenerateEdenPath(EdenPath::LosslessDir, eden_path / LOSSLESS_DIR);
|
GenerateEdenPath(EdenPath::LosslessDir, eden_path / LOSSLESS_DIR);
|
||||||
GenerateEdenPath(EdenPath::NANDDir, eden_path / NAND_DIR);
|
GenerateEdenPath(EdenPath::NANDDir, eden_path / NAND_DIR);
|
||||||
GenerateEdenPath(EdenPath::PlayTimeDir, eden_path / PLAY_TIME_DIR);
|
GenerateEdenPath(EdenPath::PlayTimeDir, eden_path / PLAY_TIME_DIR);
|
||||||
|
GenerateEdenPath(EdenPath::PostPresetDir, eden_path / POST_PRESET_DIR);
|
||||||
|
GenerateEdenPath(EdenPath::PostShaderDir, eden_path / POST_SHADER_DIR);
|
||||||
GenerateEdenPath(EdenPath::SaveDir, eden_path / NAND_DIR);
|
GenerateEdenPath(EdenPath::SaveDir, eden_path / NAND_DIR);
|
||||||
GenerateEdenPath(EdenPath::ScreenshotsDir, eden_path / SCREENSHOTS_DIR);
|
GenerateEdenPath(EdenPath::ScreenshotsDir, eden_path / SCREENSHOTS_DIR);
|
||||||
GenerateEdenPath(EdenPath::SDMCDir, eden_path / SDMC_DIR);
|
GenerateEdenPath(EdenPath::SDMCDir, eden_path / SDMC_DIR);
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ enum class EdenPath {
|
|||||||
LosslessDir, // Where the user-supplied Lossless Scaling library is stored.
|
LosslessDir, // Where the user-supplied Lossless Scaling library is stored.
|
||||||
NANDDir, // Where the emulated NAND is stored.
|
NANDDir, // Where the emulated NAND is stored.
|
||||||
PlayTimeDir, // Where play time data is stored.
|
PlayTimeDir, // Where play time data is stored.
|
||||||
|
PostPresetDir,
|
||||||
|
PostShaderDir, // Where user post-processing shaders are stored.
|
||||||
SaveDir, // Where save data is stored.
|
SaveDir, // Where save data is stored.
|
||||||
ScreenshotsDir, // Where yuzu screenshots are stored.
|
ScreenshotsDir, // Where yuzu screenshots are stored.
|
||||||
SDMCDir, // Where the emulated SDMC is stored.
|
SDMCDir, // Where the emulated SDMC is stored.
|
||||||
|
|||||||
@@ -388,6 +388,30 @@ struct Values {
|
|||||||
true,
|
true,
|
||||||
true};
|
true};
|
||||||
|
|
||||||
|
SwitchableSetting<std::string> post_shader_chain{linkage,
|
||||||
|
std::string(),
|
||||||
|
"post_shader_chain",
|
||||||
|
Category::Renderer,
|
||||||
|
Specialization::Default,
|
||||||
|
true,
|
||||||
|
true};
|
||||||
|
|
||||||
|
SwitchableSetting<std::string> post_shader_preset{linkage,
|
||||||
|
std::string(),
|
||||||
|
"post_shader_preset",
|
||||||
|
Category::Renderer,
|
||||||
|
Specialization::Default,
|
||||||
|
true,
|
||||||
|
true};
|
||||||
|
|
||||||
|
SwitchableSetting<bool> post_shader_enabled{linkage,
|
||||||
|
true,
|
||||||
|
"post_shader_enabled",
|
||||||
|
Category::Renderer,
|
||||||
|
Specialization::Default,
|
||||||
|
true,
|
||||||
|
true};
|
||||||
|
|
||||||
SwitchableSetting<bool> frame_gen{linkage, false, "frame_gen", Category::Renderer,
|
SwitchableSetting<bool> frame_gen{linkage, false, "frame_gen", Category::Renderer,
|
||||||
Specialization::Default, true, false};
|
Specialization::Default, true, false};
|
||||||
|
|
||||||
|
|||||||
@@ -1199,7 +1199,6 @@ else()
|
|||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
target_include_directories(core PRIVATE ${OPUS_INCLUDE_DIRS})
|
|
||||||
target_link_libraries(core PUBLIC common PRIVATE audio_core hid_core network video_core nx_tzdb tz)
|
target_link_libraries(core PUBLIC common PRIVATE audio_core hid_core network video_core nx_tzdb tz)
|
||||||
|
|
||||||
if (BOOST_NO_HEADERS)
|
if (BOOST_NO_HEADERS)
|
||||||
|
|||||||
@@ -312,6 +312,10 @@ struct System::Impl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SystemResultStatus Load(System& system, Frontend::EmuWindow& emu_window, const std::string& filepath, Service::AM::FrontendAppletParameters& params) {
|
SystemResultStatus Load(System& system, Frontend::EmuWindow& emu_window, const std::string& filepath, Service::AM::FrontendAppletParameters& params) {
|
||||||
|
if (params.launch_type == Service::AM::LaunchType::FrontendInitiated) {
|
||||||
|
fs_controller.InitTempStorage();
|
||||||
|
}
|
||||||
|
|
||||||
InitializeKernel(system);
|
InitializeKernel(system);
|
||||||
|
|
||||||
if (params.applet_type == Service::AM::AppletType::Application) {
|
if (params.applet_type == Service::AM::AppletType::Application) {
|
||||||
|
|||||||
@@ -57,11 +57,7 @@ std::string GetFutureSaveDataPath(SaveDataSpaceId space_id, SaveDataType type, u
|
|||||||
|
|
||||||
SaveDataFactory::SaveDataFactory(Core::System& system_, ProgramId program_id_,
|
SaveDataFactory::SaveDataFactory(Core::System& system_, ProgramId program_id_,
|
||||||
VirtualDir save_directory_)
|
VirtualDir save_directory_)
|
||||||
: system{system_}, program_id{program_id_}, dir{std::move(save_directory_)} {
|
: system{system_}, program_id{program_id_}, dir{std::move(save_directory_)} {}
|
||||||
// Delete all temporary storages
|
|
||||||
// On hardware, it is expected that temporary storage be empty at first use.
|
|
||||||
dir->DeleteSubdirectoryRecursive("temp");
|
|
||||||
}
|
|
||||||
|
|
||||||
SaveDataFactory::~SaveDataFactory() = default;
|
SaveDataFactory::~SaveDataFactory() = default;
|
||||||
|
|
||||||
|
|||||||
@@ -40,10 +40,14 @@ VirtualDir SystemVersion() {
|
|||||||
VirtualFile file = std::make_shared<VectorVfsFile>(file_data, "file");
|
VirtualFile file = std::make_shared<VectorVfsFile>(file_data, "file");
|
||||||
|
|
||||||
// the "/digest"
|
// the "/digest"
|
||||||
std::vector<u8> digest_data(sizeof(HLE::ApiVersion::VERSION_DIGEST));
|
if (HLE::ApiVersion::VERSION_DIGEST[0] == '\0') {
|
||||||
std::memcpy(digest_data.data(), HLE::ApiVersion::VERSION_DIGEST, sizeof(HLE::ApiVersion::VERSION_DIGEST));
|
return std::make_shared<VectorVfsDirectory>(std::vector<VirtualFile>{file}, std::vector<VirtualDir>{}, "data");
|
||||||
VirtualFile digest_file = std::make_shared<VectorVfsFile>(digest_data, "digest");
|
} else {
|
||||||
return std::make_shared<VectorVfsDirectory>(std::vector<VirtualFile>{file, digest_file}, std::vector<VirtualDir>{}, "data");
|
std::vector<u8> digest_data(sizeof(HLE::ApiVersion::VERSION_DIGEST));
|
||||||
|
std::memcpy(digest_data.data(), HLE::ApiVersion::VERSION_DIGEST, sizeof(HLE::ApiVersion::VERSION_DIGEST));
|
||||||
|
VirtualFile digest_file = std::make_shared<VectorVfsFile>(digest_data, "digest");
|
||||||
|
return std::make_shared<VectorVfsDirectory>(std::vector<VirtualFile>{file, digest_file}, std::vector<VirtualDir>{}, "data");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace FileSys::SystemArchive
|
} // namespace FileSys::SystemArchive
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ namespace HLE::ApiVersion {
|
|||||||
|
|
||||||
// Horizon OS version constants.
|
// Horizon OS version constants.
|
||||||
|
|
||||||
constexpr u8 HOS_VERSION_MAJOR = 22;
|
constexpr u8 HOS_VERSION_MAJOR = 23;
|
||||||
constexpr u8 HOS_VERSION_MINOR = 5;
|
constexpr u8 HOS_VERSION_MINOR = 0;
|
||||||
constexpr u8 HOS_VERSION_MICRO = 0;
|
constexpr u8 HOS_VERSION_MICRO = 0;
|
||||||
|
|
||||||
// NintendoSDK version constants.
|
// NintendoSDK version constants.
|
||||||
@@ -25,15 +25,16 @@ constexpr u8 SDK_REVISION_MAJOR = 1;
|
|||||||
constexpr u8 SDK_REVISION_MINOR = 0;
|
constexpr u8 SDK_REVISION_MINOR = 0;
|
||||||
|
|
||||||
constexpr char PLATFORM_STRING[0x20] = "NX";
|
constexpr char PLATFORM_STRING[0x20] = "NX";
|
||||||
constexpr char VERSION_HASH[0x40] = "ae93061abbc7791fcf8d2f7e7b7b2d62163af697";
|
constexpr char VERSION_HASH[0x40] = "c2663accb7490a6ccfe9bc4dfd120ca215490525";
|
||||||
constexpr char DISPLAY_VERSION[0x18] = "22.5.0";
|
constexpr char DISPLAY_VERSION[0x18] = "23.0.0";
|
||||||
constexpr char DISPLAY_TITLE[0x80] = "NintendoSDK Firmware for NX 22.5.0-1.0 ";
|
constexpr char DISPLAY_TITLE[0x80] = "NintendoSDK Firmware for NX 23.0.0-4.0";
|
||||||
constexpr char VERSION_DIGEST[0x40] = "CusHY#00160500#WxlZDREksAFSi6bQJEV6nm6-cHLg2jikdYRVN3bwqyE=";
|
// Leave empty when there's no digest (N/A)
|
||||||
|
constexpr char VERSION_DIGEST[0x40] = "";
|
||||||
|
|
||||||
// Atmosphere version constants.
|
// Atmosphere version constants.
|
||||||
constexpr u8 ATMOSPHERE_RELEASE_VERSION_MAJOR = 1;
|
constexpr u8 ATMOSPHERE_RELEASE_VERSION_MAJOR = 1;
|
||||||
constexpr u8 ATMOSPHERE_RELEASE_VERSION_MINOR = 11;
|
constexpr u8 ATMOSPHERE_RELEASE_VERSION_MINOR = 12;
|
||||||
constexpr u8 ATMOSPHERE_RELEASE_VERSION_MICRO = 2;
|
constexpr u8 ATMOSPHERE_RELEASE_VERSION_MICRO = 0;
|
||||||
|
|
||||||
constexpr u32 AtmosphereTargetFirmwareWithRevision(u8 major, u8 minor, u8 micro, u8 rev) {
|
constexpr u32 AtmosphereTargetFirmwareWithRevision(u8 major, u8 minor, u8 micro, u8 rev) {
|
||||||
return u32{major} << 24 | u32{minor} << 16 | u32{micro} << 8 | u32{rev};
|
return u32{major} << 24 | u32{minor} << 16 | u32{micro} << 8 | u32{rev};
|
||||||
|
|||||||
@@ -210,6 +210,13 @@ Result GetInfo(Core::System& system, u64* result, InfoType info_id_type, Handle
|
|||||||
*result = system.Kernel().CurrentScheduler()->GetIdleThread()->GetCpuTime();
|
*result = system.Kernel().CurrentScheduler()->GetIdleThread()->GetCpuTime();
|
||||||
R_SUCCEED();
|
R_SUCCEED();
|
||||||
}
|
}
|
||||||
|
case InfoType::Unknown37:
|
||||||
|
case InfoType::Unknown38: {
|
||||||
|
LOG_WARNING(Kernel_SVC, "(STUBBED) called, info_id={:#x}, info_sub_id={:#x}, handle={:#08x}",
|
||||||
|
info_id, info_sub_id, handle);
|
||||||
|
*result = 0;
|
||||||
|
R_SUCCEED();
|
||||||
|
}
|
||||||
case InfoType::MesosphereMeta: {
|
case InfoType::MesosphereMeta: {
|
||||||
enum MesosphereMetaInfo : u64 {
|
enum MesosphereMetaInfo : u64 {
|
||||||
KernelVersion = 0,
|
KernelVersion = 0,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||||
@@ -158,6 +158,11 @@ enum class InfoType : u32 {
|
|||||||
IoRegionHint = 27,
|
IoRegionHint = 27,
|
||||||
AliasRegionExtraSize = 28,
|
AliasRegionExtraSize = 28,
|
||||||
|
|
||||||
|
TransferMemoryHint = 34,
|
||||||
|
|
||||||
|
Unknown37 = 37,
|
||||||
|
Unknown38 = 38,
|
||||||
|
|
||||||
MesosphereMeta = 65000,
|
MesosphereMeta = 65000,
|
||||||
MesosphereCurrentProcess = 65001,
|
MesosphereCurrentProcess = 65001,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -73,16 +73,20 @@ Result DisplayLayerManager::CreateManagedDisplayLayer(u64* out_layer_id) {
|
|||||||
R_TRY(m_manager_display_service->CreateManagedLayer(
|
R_TRY(m_manager_display_service->CreateManagedLayer(
|
||||||
out_layer_id, 0, display_id, Service::AppletResourceUserId{m_process->GetProcessId()}));
|
out_layer_id, 0, display_id, Service::AppletResourceUserId{m_process->GetProcessId()}));
|
||||||
|
|
||||||
|
m_manager_display_service->SetLayerVisibility(m_visible, *out_layer_id);
|
||||||
|
(void)m_display_service->GetContainer()->SetLayerStackMask(*out_layer_id,
|
||||||
|
this->GetLayerStackMask());
|
||||||
|
|
||||||
if (m_applet_id != AppletId::Application) {
|
if (m_applet_id != AppletId::Application) {
|
||||||
(void)m_manager_display_service->SetLayerBlending(m_blending_enabled, *out_layer_id);
|
(void)m_manager_display_service->SetLayerBlending(m_blending_enabled, *out_layer_id);
|
||||||
if (m_applet_id == AppletId::OverlayDisplay) {
|
if (m_applet_id == AppletId::OverlayDisplay) {
|
||||||
(void)m_manager_display_service->SetLayerZIndex(-1, *out_layer_id);
|
(void)m_manager_display_service->SetLayerZIndex(Overlay, *out_layer_id);
|
||||||
(void)m_display_service->GetContainer()->SetLayerIsOverlay(*out_layer_id, true);
|
|
||||||
} else {
|
} else {
|
||||||
(void)m_manager_display_service->SetLayerZIndex(1, *out_layer_id);
|
(void)m_manager_display_service->SetLayerZIndex(Foreground, *out_layer_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(void)m_display_service->GetContainer()->SetLayerZIndex(*out_layer_id, true);
|
|
||||||
|
m_display_service->GetContainer()->SetLayerZIndex(*out_layer_id, true);
|
||||||
m_managed_display_layers.emplace(*out_layer_id);
|
m_managed_display_layers.emplace(*out_layer_id);
|
||||||
|
|
||||||
R_SUCCEED();
|
R_SUCCEED();
|
||||||
@@ -122,14 +126,16 @@ Result DisplayLayerManager::IsSystemBufferSharingEnabled() {
|
|||||||
|
|
||||||
// Ensure the overlay layer is visible
|
// Ensure the overlay layer is visible
|
||||||
m_manager_display_service->SetLayerVisibility(m_visible, m_system_shared_layer_id);
|
m_manager_display_service->SetLayerVisibility(m_visible, m_system_shared_layer_id);
|
||||||
m_manager_display_service->SetLayerBlending(m_blending_enabled, m_system_shared_layer_id);
|
(void)m_manager_display_service->SetLayerBlending(m_blending_enabled, m_system_shared_layer_id);
|
||||||
s32 initial_z = 1;
|
s32 initial_z = Foreground;
|
||||||
(void)m_display_service->GetContainer()->SetLayerZIndex(m_system_shared_layer_id, true);
|
|
||||||
if (m_applet_id == AppletId::OverlayDisplay) {
|
if (m_applet_id == AppletId::OverlayDisplay) {
|
||||||
initial_z = -1;
|
initial_z = Overlay;
|
||||||
|
(void)m_manager_display_service->SetLayerZIndex(initial_z, m_system_shared_layer_id);
|
||||||
(void)m_display_service->GetContainer()->SetLayerIsOverlay(m_system_shared_layer_id, true);
|
(void)m_display_service->GetContainer()->SetLayerIsOverlay(m_system_shared_layer_id, true);
|
||||||
}
|
}
|
||||||
m_manager_display_service->SetLayerZIndex(initial_z, m_system_shared_layer_id);
|
m_manager_display_service->SetLayerZIndex(initial_z, m_system_shared_layer_id);
|
||||||
|
m_display_service->GetContainer()->SetLayerZIndex(m_system_shared_layer_id, true);
|
||||||
|
m_managed_display_layers.emplace(m_system_shared_layer_id);
|
||||||
R_SUCCEED();
|
R_SUCCEED();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user