Compare commits

..

3 Commits

Author SHA1 Message Date
lizzie be67a31c93 2026-09-06 04:05:29
Signed-off-by: lizzie <lizzie@eden-emu.dev>
2026-09-06 04:05:29 +00:00
lizzie 11de264541 [cmake] Fix some macOS compilation errors (#4350)
Don't compile GLSL/GLASM backends without OpenGL enabled. These codepaths are unused on non-OpenGL platforms.

Fixes other miscellaneous errors that pop up on Clang 23 as well.

Signed-off-by: Lizzie lizzie@eden-emu.dev
Signed-off-by: crueter crueter@eden-emu.dev

- [x] I have read and followed the [Contribution Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CONTRIBUTING.md#code-contributions).
- [x] I have read and followed the [AI Policy](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/AI.md)
- [x] I have read and followed the [Coding Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/Coding.md) to the best of my ability.

-------------------

Co-authored-by: crueter <crueter@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4350
Reviewed-by: crueter <crueter@eden-emu.dev>
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
2026-09-05 23:43:46 +02:00
lizzie f6e7686038 [ci] Fix macOS "no type named 'free' in namespace 'std' (#4347)
Signed-off-by: lizzie <lizzie@eden-emu.dev>

- [x] I have read and followed the [Contribution Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CONTRIBUTING.md#code-contributions).
- [x] I have read and followed the [AI Policy](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/AI.md)
- [x] I have read and followed the [Coding Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/Coding.md) to the best of my ability.

-------------------

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4347
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
2026-09-04 13:32:36 +02:00
86 changed files with 550 additions and 4550 deletions
@@ -0,0 +1,112 @@
diff --git a/include/vk_mem_alloc.h b/include/vk_mem_alloc.h
index 8df0364..4856064 100644
--- a/include/vk_mem_alloc.h
+++ b/include/vk_mem_alloc.h
@@ -3017,7 +3017,7 @@ remove them if not needed.
#if defined(__ANDROID_API__) && (__ANDROID_API__ < 16)
#include <cstdlib>
-static void* vma_aligned_alloc(size_t alignment, size_t size)
+static inline void* vma_aligned_alloc(size_t alignment, size_t size)
{
// alignment must be >= sizeof(void*)
if(alignment < sizeof(void*))
@@ -3860,7 +3860,7 @@ Returned value is the found element, if present in the collection or place where
new element with value (key) should be inserted.
*/
template <typename CmpLess, typename IterT, typename KeyT>
-static IterT VmaBinaryFindFirstNotLess(IterT beg, IterT end, const KeyT& key, const CmpLess& cmp)
+static inline IterT VmaBinaryFindFirstNotLess(IterT beg, IterT end, const KeyT& key, const CmpLess& cmp)
{
size_t down = 0;
size_t up = size_t(end - beg);
@@ -3898,7 +3898,7 @@ Warning! O(n^2) complexity. Use only inside VMA_HEAVY_ASSERT.
T must be pointer type, e.g. VmaAllocation, VmaPool.
*/
template<typename T>
-static bool VmaValidatePointerArray(uint32_t count, const T* arr)
+static inline bool VmaValidatePointerArray(uint32_t count, const T* arr)
{
for (uint32_t i = 0; i < count; ++i)
{
@@ -4188,13 +4188,13 @@ static void VmaFree(const VkAllocationCallbacks* pAllocationCallbacks, void* ptr
}
template<typename T>
-static T* VmaAllocate(const VkAllocationCallbacks* pAllocationCallbacks)
+static inline T* VmaAllocate(const VkAllocationCallbacks* pAllocationCallbacks)
{
return (T*)VmaMalloc(pAllocationCallbacks, sizeof(T), VMA_ALIGN_OF(T));
}
template<typename T>
-static T* VmaAllocateArray(const VkAllocationCallbacks* pAllocationCallbacks, size_t count)
+static inline T* VmaAllocateArray(const VkAllocationCallbacks* pAllocationCallbacks, size_t count)
{
return (T*)VmaMalloc(pAllocationCallbacks, sizeof(T) * count, VMA_ALIGN_OF(T));
}
@@ -4204,14 +4204,14 @@ static T* VmaAllocateArray(const VkAllocationCallbacks* pAllocationCallbacks, si
#define vma_new_array(allocator, type, count) new(VmaAllocateArray<type>((allocator), (count)))(type)
template<typename T>
-static void vma_delete(const VkAllocationCallbacks* pAllocationCallbacks, T* ptr)
+static inline void vma_delete(const VkAllocationCallbacks* pAllocationCallbacks, T* ptr)
{
ptr->~T();
VmaFree(pAllocationCallbacks, ptr);
}
template<typename T>
-static void vma_delete_array(const VkAllocationCallbacks* pAllocationCallbacks, T* ptr, size_t count)
+static inline void vma_delete_array(const VkAllocationCallbacks* pAllocationCallbacks, T* ptr, size_t count)
{
if (ptr != VMA_NULL)
{
@@ -4658,13 +4658,13 @@ void VmaVector<T, AllocatorT>::remove(size_t index)
#endif // _VMA_VECTOR_FUNCTIONS
template<typename T, typename allocatorT>
-static void VmaVectorInsert(VmaVector<T, allocatorT>& vec, size_t index, const T& item)
+static inline void VmaVectorInsert(VmaVector<T, allocatorT>& vec, size_t index, const T& item)
{
vec.insert(index, item);
}
template<typename T, typename allocatorT>
-static void VmaVectorRemove(VmaVector<T, allocatorT>& vec, size_t index)
+static inline void VmaVectorRemove(VmaVector<T, allocatorT>& vec, size_t index)
{
vec.remove(index);
}
@@ -10620,19 +10620,19 @@ static void VmaFree(VmaAllocator hAllocator, void* ptr)
}
template<typename T>
-static T* VmaAllocate(VmaAllocator hAllocator)
+static inline T* VmaAllocate(VmaAllocator hAllocator)
{
return (T*)VmaMalloc(hAllocator, sizeof(T), VMA_ALIGN_OF(T));
}
template<typename T>
-static T* VmaAllocateArray(VmaAllocator hAllocator, size_t count)
+static inline T* VmaAllocateArray(VmaAllocator hAllocator, size_t count)
{
return (T*)VmaMalloc(hAllocator, sizeof(T) * count, VMA_ALIGN_OF(T));
}
template<typename T>
-static void vma_delete(VmaAllocator hAllocator, T* ptr)
+static inline void vma_delete(VmaAllocator hAllocator, T* ptr)
{
if(ptr != VMA_NULL)
{
@@ -10642,7 +10642,7 @@ static void vma_delete(VmaAllocator hAllocator, T* ptr)
}
template<typename T>
-static void vma_delete_array(VmaAllocator hAllocator, T* ptr, size_t count)
+static inline void vma_delete_array(VmaAllocator hAllocator, T* ptr, size_t count)
{
if(ptr != VMA_NULL)
{
+2 -3
View File
@@ -3,6 +3,7 @@
cmake_minimum_required(VERSION 3.31)
set(CMAKE_OSX_DEPLOYMENT_TARGET "15.0" CACHE STRING "macOS deployment target")
project(yuzu)
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMakeModules")
@@ -84,8 +85,6 @@ option(ENABLE_WERROR "Enable -Werror diagnostics" ON)
# Lossless Scaling frame generation. Only Android.
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
if (YUZU_USE_BUNDLED_QT AND (APPLE OR NOT UNIX))
set(YUZU_STATIC_BUILD ON)
@@ -514,7 +513,7 @@ endfunction()
# =============================================
if (APPLE)
foreach(fw Carbon Metal Cocoa IOKit CoreVideo CoreMedia Security UniformTypeIdentifiers)
foreach(fw Carbon Metal Cocoa IOKit CoreVideo CoreMedia Security UniformTypeIdentifiers Foundation)
find_library(${fw}_LIBRARY ${fw} REQUIRED)
list(APPEND PLATFORM_LIBRARIES ${${fw}_LIBRARY})
endforeach()
+13
View File
@@ -83,3 +83,16 @@ Once your request is processed, you will receive a confirmation email with your
Alongside the other contribution methods listed up top, you can also choose to contribute through documentation, organization, or community guides. These can be done either through the code contribution methods described above, or created externally and shared via our Discord community.
If you have an external tool/page that you believe would be handy to integrate/link into Eden, please additionally email our developers at [`developers@eden-emu.dev`](mailto:developers@eden-emu.dev). **Do not submit vibe-coded or AI generated tools or applications**.
### Non-English Contributions
If you can't speak English or find more comfortable speaking in your native tongue, you may submit PR, commits, comments, and reviews in other languages. There is no restriction what languages are allowed, but maintainers will use translators if they're unable to understand.
Triage can speak:
- Chinese
- Portuguese
- Russian
- Spanish
Documentation must be in American English, we don't have the capacity to support multilingual manuals. Code comments must remain in English for future referral.
+5 -15
View File
@@ -1,12 +1,4 @@
{
"": {
"ci": true,
"hash": "9f50d993c39529e022ad456163de91ac5934e16faf8fc348f305355fc0a643c534f87ded707e11bd03bcbc0a1760bd20852b6533a67ac0558a078385181cb184",
"name": "SDL3",
"package": "SDL3",
"repo": "crueter-ci/SDL3",
"version": "3.4.14-1788231389-147a8ee32d"
},
"biscuit": {
"hash": "1229f345b014f7ca544dedb4edb3311e41ba736f9aa9a67f88b5f26f3c983288c6bb6cdedcfb0b8a02c63088a37e6a0d7ba97d9c2a4d721b213916327cffe28a",
"min_version": "0.9.1",
@@ -68,10 +60,10 @@
},
"discord-rpc": {
"find_args": "MODULE",
"hash": "8213c43dcb0f7d479f5861091d111ed12fbdec1e62e6d729d65a4bc181d82f48a35d5fd3cd5c291f2393ac7c9681eabc6b76609755f55376284c8a8d67e148f3",
"hash": "8d680b3a16d6f6bf292ad823cf8635595ff986f2a49f758e3009f505b540a9d75194a8cf44cddea75736a8c3e3b5160166e716a0939ce3f18cc463cf648753fe",
"package": "DiscordRPC",
"repo": "eden-emulator/discord-rpc",
"version": "0d8b2d6a37"
"version": "76616d8675"
},
"enet": {
"find_args": "MODULE",
@@ -246,11 +238,6 @@
"repo": "stachenov/quazip",
"version": "2e95c9001b"
},
"reshade": {
"hash": "a1bd3fcf135fb6d018c1831ae45a8942d9777d0418b55e1189921e2ab775d1b53b0a9808924e09ef1c3e68ea11d69b3bcbebc7f4b59de14f6deec2dc24531d5e",
"repo": "crosire/reshade",
"version": "v6.7.3"
},
"sdl3": {
"hash": "df5a323af7ac366661a3c0e887969c72584d232f3cc211419d59b0487b620b6b2859d4549c9e8df002ee489290062e466fcfddf7edc0872a37b1f2845e81c0f3",
"min_version": "3.2.10",
@@ -316,6 +303,9 @@
"find_args": "CONFIG",
"hash": "deb5902ef8db0e329fbd5f3f4385eb0e26bdd9f14f3a2334823fb3fe18f36bc5d235d620d6e5f6fe3551ec3ea7038638899db8778c09f6d5c278f5ff95c3344b",
"package": "VulkanMemoryAllocator",
"patches": [
"0001-macos-clang.patch"
],
"repo": "GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator",
"version": "v3.3.0"
},
-75
View File
@@ -1,75 +0,0 @@
// 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(0.0, 0.0);
if (id == 2)
{
uv.x = 2.0;
}
if (id == 1)
{
uv.y = 2.0;
}
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);
for (int x = -3; x <= 3; x += 2)
{
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
{
pass
{
VertexShader = VS_PostProcess;
PixelShader = PS_Bloom;
}
}
-79
View File
@@ -1,79 +0,0 @@
// 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(0.0, 0.0);
if (id == 2)
{
uv.x = 2.0;
}
if (id == 1)
{
uv.y = 2.0;
}
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
{
pass
{
VertexShader = VS_PostProcess;
PixelShader = PS_CRT;
}
}
-84
View File
@@ -1,84 +0,0 @@
// 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(0.0, 0.0);
if (id == 2)
{
uv.x = 2.0;
}
if (id == 1)
{
uv.y = 2.0;
}
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
{
pass
{
VertexShader = VS_PostProcess;
PixelShader = PS_Cartoon;
}
}
-69
View File
@@ -1,69 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 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(0.0, 0.0);
if (id == 2)
{
uv.x = 2.0;
}
if (id == 1)
{
uv.y = 2.0;
}
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
{
pass
{
VertexShader = VS_PostProcess;
PixelShader = PS_ColorGrade;
}
}
-150
View File
@@ -1,150 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
texture BackBufferTex : COLOR;
sampler BackBuffer { Texture = BackBufferTex; };
texture BrightTex { Width = BUFFER_WIDTH / 2; Height = BUFFER_HEIGHT / 2; Format = RGBA16F; };
sampler Bright { Texture = BrightTex; };
texture BounceTex { Width = BUFFER_WIDTH / 2; Height = BUFFER_HEIGHT / 2; Format = RGBA16F; };
sampler BounceMap { Texture = BounceTex; };
uniform float Threshold <
ui_type = "slider";
ui_label = "Light Threshold";
ui_tooltip = "Luminance above which a pixel is treated as a light source.";
ui_min = 0.0; ui_max = 1.0; ui_step = 0.01;
> = 0.45;
uniform float Radius <
ui_type = "slider";
ui_label = "Bounce Radius";
ui_tooltip = "How far light spreads onto neighbouring surfaces.";
ui_min = 0.5; ui_max = 8.0; ui_step = 0.1;
> = 3.0;
uniform float Bounce <
ui_type = "slider";
ui_label = "Bounce Strength";
ui_tooltip = "Amount of coloured light bleeding onto the image.";
ui_min = 0.0; ui_max = 2.0; ui_step = 0.05;
> = 0.7;
uniform float Occlusion <
ui_type = "slider";
ui_label = "Contact Shadows";
ui_tooltip = "Darkening applied where a pixel is dimmer than its surroundings.";
ui_min = 0.0; ui_max = 2.0; ui_step = 0.05;
> = 0.6;
uniform float Saturation <
ui_type = "slider";
ui_label = "Bounce Saturation";
ui_tooltip = "How strongly the bounced light keeps the colour of its source.";
ui_min = 0.0; ui_max = 2.0; ui_step = 0.05;
> = 1.3;
void VS_PostProcess(in uint id : SV_VertexID, out float4 pos : SV_Position, out float2 uv : TEXCOORD)
{
uv = float2(0.0, 0.0);
if (id == 2)
{
uv.x = 2.0;
}
if (id == 1)
{
uv.y = 2.0;
}
pos = float4(uv * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0);
}
float Luma(float3 color)
{
return dot(color, float3(0.2126, 0.7152, 0.0722));
}
float4 PS_Extract(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
{
float2 texel = float2(BUFFER_RCP_WIDTH, BUFFER_RCP_HEIGHT);
float3 sum = tex2D(BackBuffer, uv).rgb;
sum += tex2D(BackBuffer, uv + texel * float2(-1.0, -1.0)).rgb;
sum += tex2D(BackBuffer, uv + texel * float2( 1.0, -1.0)).rgb;
sum += tex2D(BackBuffer, uv + texel * float2(-1.0, 1.0)).rgb;
sum += tex2D(BackBuffer, uv + texel * float2( 1.0, 1.0)).rgb;
sum *= 0.2;
float luma = Luma(sum);
float mask = saturate((luma - Threshold) / max(1.0 - Threshold, 0.0001));
float3 tint = lerp(float3(luma, luma, luma), sum, Saturation);
return float4(tint * mask * mask, 1.0);
}
float4 PS_Spread(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
{
float2 unit = float2(BUFFER_RCP_WIDTH, BUFFER_RCP_HEIGHT) * Radius * 8.0;
const float2 dirs[8] = {
float2( 1.0, 0.0), float2(-1.0, 0.0),
float2( 0.0, 1.0), float2( 0.0, -1.0),
float2( 0.7071, 0.7071), float2(-0.7071, 0.7071),
float2( 0.7071, -0.7071), float2(-0.7071, -0.7071)
};
float3 sum = tex2D(Bright, uv).rgb;
float total = 1.0;
for (int i = 0; i < 8; ++i)
{
sum += tex2D(Bright, uv + dirs[i] * unit * 0.45).rgb * 0.8;
sum += tex2D(Bright, uv + dirs[i] * unit).rgb * 0.5;
total += 1.3;
}
return float4(sum / total, 1.0);
}
float4 PS_Composite(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
{
float2 texel = float2(BUFFER_RCP_WIDTH, BUFFER_RCP_HEIGHT) * 2.0;
float3 color = tex2D(BackBuffer, uv).rgb;
float centre = Luma(color);
float around = Luma(tex2D(BackBuffer, uv + texel * float2(-1.0, 0.0)).rgb);
around += Luma(tex2D(BackBuffer, uv + texel * float2( 1.0, 0.0)).rgb);
around += Luma(tex2D(BackBuffer, uv + texel * float2( 0.0, -1.0)).rgb);
around += Luma(tex2D(BackBuffer, uv + texel * float2( 0.0, 1.0)).rgb);
around *= 0.25;
float cavity = saturate(around - centre);
float shadow = 1.0 - cavity * Occlusion;
float3 bounce = tex2D(BounceMap, uv).rgb;
float3 lit = color * shadow + bounce * Bounce * (1.0 - centre);
return float4(saturate(lit), 1.0);
}
technique FakeGI
{
pass
{
VertexShader = VS_PostProcess;
PixelShader = PS_Extract;
RenderTarget = BrightTex;
}
pass
{
VertexShader = VS_PostProcess;
PixelShader = PS_Spread;
RenderTarget = BounceTex;
}
pass
{
VertexShader = VS_PostProcess;
PixelShader = PS_Composite;
}
}
-71
View File
@@ -1,71 +0,0 @@
// 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 fakereflections.fsh in PPSSPP.
texture BackBufferTex : COLOR;
sampler BackBuffer { Texture = BackBufferTex; };
uniform float Amount <
ui_type = "slider";
ui_label = "Amount";
ui_tooltip = "Strength of the reflection added to the lower half of the screen.";
ui_min = 0.0; ui_max = 1.0; ui_step = 0.05;
> = 0.6;
uniform float Power <
ui_type = "slider";
ui_label = "Power";
ui_tooltip = "How sharply the reflection falls off away from bright areas.";
ui_min = 0.0; ui_max = 1.0; ui_step = 0.05;
> = 0.5;
void VS_PostProcess(in uint id : SV_VertexID, out float4 pos : SV_Position, out float2 uv : TEXCOORD)
{
uv = float2(0.0, 0.0);
if (id == 2)
{
uv.x = 2.0;
}
if (id == 1)
{
uv.y = 2.0;
}
pos = float4(uv * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0);
}
float Wrap(float value, float period)
{
return period * frac(value / period);
}
float4 PS_FakeReflections(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 rndx = Wrap(uv.x + gray, 0.03) + Wrap(uv.y + saturation, 0.05);
float rndy = Wrap(uv.y + saturation, 0.03) + Wrap(uv.x + gray, 0.05);
float falloff = (max(gray, saturation) + 0.1) * uv.y;
float2 offset = float2(rndx, rndy - min(uv.y, 0.25)) * falloff;
float3 reflection = tex2D(BackBuffer, uv + offset).rgb;
reflection *= 4.0 * (1.0 - gray) * Amount;
reflection *= reflection * falloff * Power;
return float4(saturate(color + reflection), 1.0);
}
technique FakeReflections
{
pass
{
VertexShader = VS_PostProcess;
PixelShader = PS_FakeReflections;
}
}
-65
View File
@@ -1,65 +0,0 @@
// 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(0.0, 0.0);
if (id == 2)
{
uv.x = 2.0;
}
if (id == 1)
{
uv.y = 2.0;
}
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
{
pass
{
VertexShader = VS_PostProcess;
PixelShader = PS_Natural;
}
}
-71
View File
@@ -1,71 +0,0 @@
// 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(0.0, 0.0);
if (id == 2)
{
uv.x = 2.0;
}
if (id == 1)
{
uv.y = 2.0;
}
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
{
pass
{
VertexShader = VS_PostProcess;
PixelShader = PS_Scanlines;
}
}
-48
View File
@@ -1,48 +0,0 @@
// 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(0.0, 0.0);
if (id == 2)
{
uv.x = 2.0;
}
if (id == 1)
{
uv.y = 2.0;
}
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
{
pass
{
VertexShader = VS_PostProcess;
PixelShader = PS_Sharpen;
}
}
-57
View File
@@ -1,57 +0,0 @@
// 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(0.0, 0.0);
if (id == 2)
{
uv.x = 2.0;
}
if (id == 1)
{
uv.y = 2.0;
}
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
{
pass
{
VertexShader = VS_PostProcess;
PixelShader = PS_Vignette;
}
}
-44
View File
@@ -195,50 +195,6 @@ else()
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
if (YUZU_TESTS OR DYNARMIC_TESTS)
AddJsonPackage(catch2)
+1
View File
@@ -11,6 +11,7 @@
#include <limits>
#include <span>
#include <array>
#include <algorithm>
#include <time.h>
namespace Tz {
@@ -1,103 +0,0 @@
// 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.persist()
}
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()
}
}
class FxUniformChoiceSetting(
index: Int,
uniform: NativePostProcessing.Uniform,
component: Int
) : FxUniformSetting(index, uniform, component), AbstractIntSetting {
override val defaultValue: Any
get() = uniform.defaultAt(component).roundToInt()
override fun getInt(needsGlobal: Boolean): Int = currentValue().roundToInt()
override fun setInt(value: Int) = commit(value.toFloat())
override fun getValueAsString(needsGlobal: Boolean): String = getInt().toString()
}
class FxUniformBooleanSetting(
index: Int,
uniform: NativePostProcessing.Uniform,
component: Int
) : FxUniformSetting(index, uniform, component), AbstractBooleanSetting {
override val defaultValue: Any
get() = uniform.defaultAt(component) != 0f
override fun getBoolean(needsGlobal: Boolean): Boolean = currentValue() != 0f
override fun setBoolean(value: Boolean) {
if (value) {
commit(1f)
return
}
commit(0f)
}
override fun getValueAsString(needsGlobal: Boolean): String = getBoolean().toString()
}
@@ -12,7 +12,6 @@ object Settings {
SECTION_SYSTEM(R.string.preferences_system),
SECTION_RENDERER(R.string.preferences_graphics),
SECTION_FRAME_GEN(R.string.frame_gen),
SECTION_POST_PROCESSING(R.string.post_processing),
SECTION_PERFORMANCE_STATS(R.string.stats_overlay_options),
SECTION_INPUT_OVERLAY(R.string.input_overlay_options),
SECTION_SOC_OVERLAY(R.string.soc_overlay_options),
@@ -13,7 +13,6 @@ enum class StringSetting(override val key: String) : AbstractStringSetting {
DEVICE_NAME("device_name"),
LOG_FILTER("log_filter"),
PROGRAM_ARGS("program_args"),
POST_SHADER_CHAIN("post_shader_chain"),
WEB_TOKEN("eden_token"),
WEB_USERNAME("eden_username")
@@ -18,9 +18,6 @@ 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.AbstractIntSetting
import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
import org.yuzu.yuzu_emu.features.settings.model.FxUniformBooleanSetting
import org.yuzu.yuzu_emu.features.settings.model.FxUniformChoiceSetting
import org.yuzu.yuzu_emu.features.settings.model.FxUniformSliderSetting
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.LongSetting
@@ -33,7 +30,6 @@ import org.yuzu.yuzu_emu.features.settings.model.view.*
import org.yuzu.yuzu_emu.utils.InputHandler
import org.yuzu.yuzu_emu.utils.LosslessScalingHelper
import org.yuzu.yuzu_emu.utils.NativeConfig
import org.yuzu.yuzu_emu.utils.NativePostProcessing
import org.yuzu.yuzu_emu.utils.DirectoryInitialization
import org.yuzu.yuzu_emu.utils.FullscreenHelper
import androidx.core.content.edit
@@ -167,7 +163,6 @@ class SettingsFragmentPresenter(
MenuTag.SECTION_SYSTEM -> addSystemSettings(sl)
MenuTag.SECTION_RENDERER -> addGraphicsSettings(sl)
MenuTag.SECTION_FRAME_GEN -> addFrameGenSettings(sl)
MenuTag.SECTION_POST_PROCESSING -> addPostProcessingSettings(sl)
MenuTag.SECTION_PERFORMANCE_STATS -> addPerformanceOverlaySettings(sl)
MenuTag.SECTION_SOC_OVERLAY -> addSocOverlaySettings(sl)
MenuTag.SECTION_INPUT_OVERLAY -> addInputOverlaySettings(sl)
@@ -195,219 +190,6 @@ class SettingsFragmentPresenter(
}
}
private fun addPostProcessingSettings(sl: ArrayList<SettingsItem>) {
val usable = NativePostProcessing.catalog().filter { it.valid }
sl.apply {
add(
RunnableSetting(
titleId = R.string.post_processing_reload,
descriptionId = R.string.post_processing_reload_description,
isRunnable = true
) {
NativePostProcessing.reload()
settingsViewModel.setReloadListAndNotifyDataset(true)
}
)
if (usable.isEmpty()) {
add(
RunnableSetting(
titleId = R.string.post_processing_empty,
descriptionString = NativePostProcessing.getShaderDirectory(),
isRunnable = false
) {}
)
return@apply
}
val labels = 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.name)
} else {
labels.add(effect.name + " \u00b7 " + technique)
}
files.add(effect.file)
techniques.add(technique)
}
}
val chain = NativePostProcessing.chain()
for (index in chain.indices) {
val entry = chain[index]
val effect = usable.firstOrNull { it.file == entry.file }
var header = entry.file
if (effect != null) {
header = effect.name
}
add(HeaderSetting(titleString = header))
add(
IntSingleChoiceSetting(
buildSlotSelector(index, entry, files, techniques),
titleId = R.string.post_processing_effect,
choices = labels.toTypedArray(),
values = labels.indices.toList().toTypedArray()
)
)
if (effect != null) {
for (uniform in effect.uniforms) {
addUniform(this, index, uniform)
}
}
if (index > 0) {
add(
RunnableSetting(
titleId = R.string.post_processing_move_up,
isRunnable = true
) {
NativePostProcessing.move(index, -1)
NativePostProcessing.persist()
settingsViewModel.setReloadListAndNotifyDataset(true)
}
)
}
if (index < chain.size - 1) {
add(
RunnableSetting(
titleId = R.string.post_processing_move_down,
isRunnable = true
) {
NativePostProcessing.move(index, 1)
NativePostProcessing.persist()
settingsViewModel.setReloadListAndNotifyDataset(true)
}
)
}
add(
RunnableSetting(
titleId = R.string.post_processing_reset,
isRunnable = true
) {
NativePostProcessing.resetValues(index)
NativePostProcessing.persist()
settingsViewModel.setReloadListAndNotifyDataset(true)
}
)
add(
RunnableSetting(
titleId = R.string.post_processing_remove,
isRunnable = true
) {
NativePostProcessing.remove(index)
NativePostProcessing.persist()
settingsViewModel.setReloadListAndNotifyDataset(true)
}
)
}
add(
RunnableSetting(
titleId = R.string.post_processing_add,
isRunnable = true
) {
NativePostProcessing.append(files[0], techniques[0])
NativePostProcessing.persist()
settingsViewModel.setReloadListAndNotifyDataset(true)
}
)
}
}
private fun buildSlotSelector(
index: Int,
entry: NativePostProcessing.ChainEntry,
files: List<String>,
techniques: List<String>
): AbstractIntSetting = object : AbstractIntSetting {
override val key = "fx_slot_$index"
override fun getInt(needsGlobal: Boolean): Int {
for (i in files.indices) {
if (files[i] == entry.file && techniques[i] == entry.technique) {
return i
}
}
return -1
}
override fun setInt(value: Int) {
NativePostProcessing.replace(index, files[value], techniques[value])
NativePostProcessing.persist()
settingsViewModel.setReloadListAndNotifyDataset(true)
}
override val defaultValue = 0
override fun getValueAsString(needsGlobal: Boolean): String = getInt().toString()
override fun reset() {}
override val isRuntimeModifiable = true
override val pairedSettingKey = ""
override val isSwitchable = false
override val isSaveable = true
override var global: Boolean
get() = true
set(_) {}
}
private fun addUniform(
sl: ArrayList<SettingsItem>,
index: Int,
uniform: NativePostProcessing.Uniform
) {
if (uniform.uiType == NativePostProcessing.UI_CHECKBOX ||
uniform.kind == NativePostProcessing.KIND_BOOL
) {
sl.add(
SwitchSetting(
FxUniformBooleanSetting(index, uniform, 0),
titleString = uniform.label,
descriptionString = uniform.tooltip
)
)
return
}
if (uniform.items.isNotEmpty() &&
(uniform.uiType == NativePostProcessing.UI_COMBO ||
uniform.uiType == NativePostProcessing.UI_RADIO)
) {
sl.add(
IntSingleChoiceSetting(
FxUniformChoiceSetting(index, uniform, 0),
titleString = uniform.label,
descriptionString = uniform.tooltip,
choices = uniform.items.toTypedArray(),
values = uniform.items.indices.toList().toTypedArray()
)
)
return
}
for (component in 0 until uniform.components) {
var title = uniform.label
if (uniform.components > 1) {
title = uniform.label + " [" + component + "]"
}
sl.add(
SliderSetting(
FxUniformSliderSetting(index, uniform, component),
titleString = title,
descriptionString = uniform.tooltip,
min = 0,
max = uniform.steps
)
)
}
}
private fun addConfigSettings(sl: ArrayList<SettingsItem>) {
sl.apply {
add(
@@ -383,20 +383,6 @@ 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()) {
add(
@@ -171,20 +171,6 @@ 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(
HomeSetting(
R.string.lossless_scaling,
@@ -1,180 +0,0 @@
// 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()
fun persist() {
store()
NativeConfig.saveGlobalConfig()
}
external fun reload()
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 defaultAt(component: Int): Float {
if (component < defaults.size) {
return defaults[component]
}
return 0f
}
}
data class Effect(
val file: String,
val name: 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)
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"),
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,7 +17,6 @@ add_library(yuzu-android SHARED
android_config.cpp
android_config.h
native_input.cpp
native_post_processing.cpp
)
set_property(TARGET yuzu-android PROPERTY IMPORTED_LOCATION ${FFmpeg_LIBRARY_DIR})
@@ -1,218 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include <array>
#include <string>
#include <jni.h>
#include <nlohmann/json.hpp>
#include "common/android/android_common.h"
#ifdef HAS_RESHADE
#include "video_core/post_processing/fx_chain.h"
#include "video_core/post_processing/fx_effect.h"
#endif
namespace {
#ifdef HAS_RESHADE
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
for (const auto& effect : VideoCore::GetFxCatalog()) {
nlohmann::json entry;
entry["file"] = effect.file;
entry["name"] = effect.name;
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
VideoCore::FxChain::Instance().StoreToSettings();
#endif
}
void Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_reload(JNIEnv* env, jobject obj) {
#ifdef HAS_RESHADE
VideoCore::ReloadFxCatalog();
VideoCore::FxChain::Instance().DropUnknownEntries();
#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
}
} // extern "C"
@@ -1,10 +0,0 @@
<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>
@@ -298,18 +298,6 @@
<string name="gpu_driver_fetcher">GPU driver fetcher</string>
<string name="gpu_driver_manager">GPU driver manager</string>
<string name="install_gpu_driver_description">Install alternative drivers for potentially better performance or accuracy</string>
<string name="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_effect">Effect</string>
<string name="post_processing_add">Add effect</string>
<string name="post_processing_remove">Remove</string>
<string name="post_processing_move_up">Move up</string>
<string name="post_processing_move_down">Move down</string>
<string name="post_processing_reset">Reset to defaults</string>
<string name="post_processing_reload">Reload from disk</string>
<string name="post_processing_reload_description">Rescan the shader folder for .fx files</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_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>
+1
View File
@@ -5,6 +5,7 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <string>
#include <cstdlib>
#include <string_view>
#ifdef _WIN32
#include <llvm/Demangle/Demangle.h>
-1
View File
@@ -23,7 +23,6 @@
#define LOSSLESS_DIR "lossless"
#define NAND_DIR "nand"
#define PLAY_TIME_DIR "play_time"
#define POST_SHADER_DIR "post_shaders"
#define SCREENSHOTS_DIR "screenshots"
#define SDMC_DIR "sdmc"
#define SHADER_DIR "shader"
-1
View File
@@ -160,7 +160,6 @@ public:
GenerateEdenPath(EdenPath::LosslessDir, eden_path / LOSSLESS_DIR);
GenerateEdenPath(EdenPath::NANDDir, eden_path / NAND_DIR);
GenerateEdenPath(EdenPath::PlayTimeDir, eden_path / PLAY_TIME_DIR);
GenerateEdenPath(EdenPath::PostShaderDir, eden_path / POST_SHADER_DIR);
GenerateEdenPath(EdenPath::SaveDir, eden_path / NAND_DIR);
GenerateEdenPath(EdenPath::ScreenshotsDir, eden_path / SCREENSHOTS_DIR);
GenerateEdenPath(EdenPath::SDMCDir, eden_path / SDMC_DIR);
-1
View File
@@ -26,7 +26,6 @@ enum class EdenPath {
LosslessDir, // Where the user-supplied Lossless Scaling library is stored.
NANDDir, // Where the emulated NAND is stored.
PlayTimeDir, // Where play time data is stored.
PostShaderDir, // Where user post-processing shaders are stored.
SaveDir, // Where save data is stored.
ScreenshotsDir, // Where yuzu screenshots are stored.
SDMCDir, // Where the emulated SDMC is stored.
+5 -1
View File
@@ -1,9 +1,13 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <iterator>
#include <cstring>
#include "common/make_unique_for_overwrite.h"
@@ -61,7 +65,7 @@ public:
void resize(size_type size) {
if (size > buffer_capacity) {
auto new_buffer = Common::make_unique_for_overwrite<T[]>(size);
std::move(buffer.get(), buffer.get() + buffer_capacity, new_buffer.get());
std::memcpy(new_buffer.get(), buffer.get(), buffer_capacity * sizeof(T));
buffer = std::move(new_buffer);
buffer_capacity = size;
}
-8
View File
@@ -388,14 +388,6 @@ struct Values {
true,
true};
Setting<std::string> post_shader_chain{linkage,
std::string(),
"post_shader_chain",
Category::Renderer,
Specialization::Default,
true,
true};
SwitchableSetting<bool> frame_gen{linkage, false, "frame_gen", Category::Renderer,
Specialization::Default, true, false};
+1
View File
@@ -10,6 +10,7 @@
#include <functional>
#include <span>
#include <string>
#include <type_traits>
#include "common/common_types.h"
+2 -2
View File
@@ -6,13 +6,13 @@
#include <mutex>
#include <utility>
#include <type_traits>
#include <boost/asio.hpp>
#include <boost/version.hpp>
#if BOOST_VERSION > 108400 && (!defined(_WINDOWS) && !defined(__ANDROID__)) || defined(YUZU_BOOST_v1)
#define USE_BOOST_v1
#endif
#ifdef USE_BOOST_v1
#include <boost/process/v1/async_pipe.hpp>
#else
+2 -1
View File
@@ -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-FileCopyrightText: Copyright 2023 yuzu Emulator Project
@@ -6,6 +6,7 @@
#pragma once
#include <type_traits>
#include "common/common_funcs.h"
namespace FileSys {
@@ -7,6 +7,7 @@
#pragma once
#include <optional>
#include <type_traits>
#include "common/literals.h"
#include "core/file_sys/fssystem/fs_i_storage.h"
@@ -4,6 +4,7 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <type_traits>
#include "core/file_sys/errors.h"
#include "core/file_sys/fssystem/fssystem_bucket_tree.h"
#include "core/file_sys/fssystem/fssystem_bucket_tree_utils.h"
@@ -1,9 +1,13 @@
// 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
#pragma once
#include <mutex>
#include <type_traits>
#include "common/alignment.h"
#include "common/common_funcs.h"
@@ -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-FileCopyrightText: Copyright 2023 yuzu Emulator Project
@@ -6,6 +6,7 @@
#pragma once
#include <type_traits>
#include "core/file_sys/errors.h"
#include "core/file_sys/fssystem/fssystem_bucket_tree.h"
#include "core/file_sys/fssystem/fssystem_bucket_tree_utils.h"
@@ -6,6 +6,8 @@
#pragma once
#include <type_traits>
#include <cstddef>
#include "common/literals.h"
#include "core/file_sys/errors.h"
@@ -1,8 +1,12 @@
// 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
#pragma once
#include <type_traits>
#include "common/alignment.h"
#include "core/file_sys/fssystem/fs_i_storage.h"
#include "core/file_sys/fssystem/fs_types.h"
@@ -6,6 +6,9 @@
#pragma once
#include <type_traits>
#include <array>
#include <cstddef>
#include "core/file_sys/errors.h"
#include "core/file_sys/fssystem/fs_i_storage.h"
#include "core/file_sys/fssystem/fssystem_bucket_tree.h"
@@ -1,9 +1,15 @@
// 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
#pragma once
#include <optional>
#include <array>
#include <cstddef>
#include <type_traits>
#include "core/file_sys/fssystem/fs_i_storage.h"
#include "core/file_sys/fssystem/fs_types.h"
@@ -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-FileCopyrightText: Copyright 2023 yuzu Emulator Project
@@ -6,6 +6,8 @@
#pragma once
#include <type_traits>
#include <cstddef>
#include "core/file_sys/fssystem/fssystem_compression_common.h"
#include "core/file_sys/fssystem/fssystem_nca_header.h"
#include "core/file_sys/vfs/vfs.h"
@@ -1,8 +1,14 @@
// 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
#pragma once
#include <type_traits>
#include <array>
#include <cstddef>
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/literals.h"
+1
View File
@@ -7,6 +7,7 @@
#pragma once
#include <memory>
#include <type_traits>
#include "common/common_funcs.h"
#include "common/page_table.h"
+1
View File
@@ -6,6 +6,7 @@
#pragma once
#include <type_traits>
#include "common/assert.h"
#include "common/bit_field.h"
#include "common/common_funcs.h"
+4
View File
@@ -1,9 +1,13 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <array>
#include <type_traits>
#include <functional>
#include "common/common_funcs.h"
+8 -26
View File
@@ -175,19 +175,10 @@ Result AlbumManager::LoadAlbumScreenShotImage(LoadAlbumScreenShotImageOutput& ou
return ResultIsNotMounted;
}
out_image_output = {
.width = 1280,
.height = 720,
.attribute =
{
.unknown_0{},
.orientation = AlbumImageOrientation::None,
.unknown_1{},
.unknown_2{},
.pad163{},
},
.pad179{},
};
out_image_output = {};
out_image_output.width = 1280;
out_image_output.height = 720;
out_image_output.attribute.orientation = AlbumImageOrientation::None;
std::filesystem::path path;
const auto result = GetFile(path, file_id);
@@ -211,19 +202,10 @@ Result AlbumManager::LoadAlbumScreenShotThumbnail(
return ResultIsNotMounted;
}
out_image_output = {
.width = 320,
.height = 180,
.attribute =
{
.unknown_0{},
.orientation = AlbumImageOrientation::None,
.unknown_1{},
.unknown_2{},
.pad163{},
},
.pad179{},
};
out_image_output = {};
out_image_output.width = 320;
out_image_output.height = 180;
out_image_output.attribute.orientation = AlbumImageOrientation::None;
std::filesystem::path path;
const auto result = GetFile(path, file_id);
+2 -7
View File
@@ -73,13 +73,8 @@ void IScreenShotApplicationService::CaptureAndSaveScreenshot(AlbumReportOption r
Layout::FramebufferLayout layout =
Layout::DefaultFrameLayout(screenshot_width, screenshot_height);
const Capture::ScreenShotAttribute attribute{
.unknown_0{},
.orientation = Capture::AlbumImageOrientation::None,
.unknown_1{},
.unknown_2{},
.pad163{},
};
Capture::ScreenShotAttribute attribute{};
attribute.orientation = Capture::AlbumImageOrientation::None;
renderer.RequestScreenshot(
image_data.data(),
+4
View File
@@ -1,8 +1,12 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <type_traits>
#include "common/common_funcs.h"
#include "common/common_types.h"
+2 -1
View File
@@ -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-FileCopyrightText: Copyright 2022 yuzu Emulator Project
@@ -6,6 +6,7 @@
#pragma once
#include <type_traits>
#include <fmt/ranges.h>
#include "common/common_funcs.h"
+1
View File
@@ -8,6 +8,7 @@
#include <array>
#include <chrono>
#include <type_traits>
#include <fmt/ranges.h>
#include "common/common_types.h"
@@ -1,10 +1,13 @@
// 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
#pragma once
#include <array>
#include <type_traits>
#include "common/common_types.h"
#include "core/hle/service/psc/time/common.h"
@@ -7,6 +7,7 @@
#pragma once
#include <array>
#include <type_traits>
#include "common/bit_field.h"
#include "common/common_funcs.h"
+7
View File
@@ -1,8 +1,15 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <cstddef>
#include <array>
#include <type_traits>
#include "common/bit_field.h"
#include "common/common_funcs.h"
#include "common/common_types.h"
@@ -150,7 +150,6 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent) {
INSERT(Settings, anti_aliasing, tr("Anti-Aliasing Method:"),
tr("The anti-aliasing method to use.\nSMAA offers the best quality.\nFXAA "
"can produce a more stable picture in lower resolutions."));
INSERT(Settings, post_shader_chain, QString(), QString());
INSERT(Settings, fullscreen_mode, tr("Fullscreen Mode:"),
tr("The method used to render the window in fullscreen.\nBorderless offers the best "
"compatibility with the on-screen keyboard that some games request for "
+53 -51
View File
@@ -6,55 +6,6 @@
add_library(shader_recompiler STATIC
backend/bindings.h
backend/glasm/emit_glasm.cpp
backend/glasm/emit_glasm.h
backend/glasm/emit_glasm_barriers.cpp
backend/glasm/emit_glasm_bitwise_conversion.cpp
backend/glasm/emit_glasm_composite.cpp
backend/glasm/emit_glasm_context_get_set.cpp
backend/glasm/emit_glasm_control_flow.cpp
backend/glasm/emit_glasm_convert.cpp
backend/glasm/emit_glasm_floating_point.cpp
backend/glasm/emit_glasm_image.cpp
backend/glasm/emit_glasm_instructions.h
backend/glasm/emit_glasm_integer.cpp
backend/glasm/emit_glasm_logical.cpp
backend/glasm/emit_glasm_memory.cpp
backend/glasm/emit_glasm_not_implemented.cpp
backend/glasm/emit_glasm_select.cpp
backend/glasm/emit_glasm_shared_memory.cpp
backend/glasm/emit_glasm_special.cpp
backend/glasm/emit_glasm_undefined.cpp
backend/glasm/emit_glasm_warp.cpp
backend/glasm/glasm_emit_context.cpp
backend/glasm/glasm_emit_context.h
backend/glasm/reg_alloc.cpp
backend/glasm/reg_alloc.h
backend/glsl/emit_glsl.cpp
backend/glsl/emit_glsl.h
backend/glsl/emit_glsl_atomic.cpp
backend/glsl/emit_glsl_barriers.cpp
backend/glsl/emit_glsl_bitwise_conversion.cpp
backend/glsl/emit_glsl_composite.cpp
backend/glsl/emit_glsl_context_get_set.cpp
backend/glsl/emit_glsl_control_flow.cpp
backend/glsl/emit_glsl_convert.cpp
backend/glsl/emit_glsl_floating_point.cpp
backend/glsl/emit_glsl_image.cpp
backend/glsl/emit_glsl_instructions.h
backend/glsl/emit_glsl_integer.cpp
backend/glsl/emit_glsl_logical.cpp
backend/glsl/emit_glsl_memory.cpp
backend/glsl/emit_glsl_not_implemented.cpp
backend/glsl/emit_glsl_select.cpp
backend/glsl/emit_glsl_shared_memory.cpp
backend/glsl/emit_glsl_special.cpp
backend/glsl/emit_glsl_undefined.cpp
backend/glsl/emit_glsl_warp.cpp
backend/glsl/glsl_emit_context.cpp
backend/glsl/glsl_emit_context.h
backend/glsl/var_alloc.cpp
backend/glsl/var_alloc.h
backend/spirv/emit_spirv.cpp
backend/spirv/emit_spirv.h
backend/spirv/emit_spirv_atomic.cpp
@@ -239,9 +190,60 @@ add_library(shader_recompiler STATIC
program_header.h
runtime_info.h
shader_info.h
varying_state.h
varying_state.h)
)
if (ENABLE_OPENGL)
target_sources(shader_recompiler PRIVATE
backend/glasm/emit_glasm.cpp
backend/glasm/emit_glasm.h
backend/glasm/emit_glasm_barriers.cpp
backend/glasm/emit_glasm_bitwise_conversion.cpp
backend/glasm/emit_glasm_composite.cpp
backend/glasm/emit_glasm_context_get_set.cpp
backend/glasm/emit_glasm_control_flow.cpp
backend/glasm/emit_glasm_convert.cpp
backend/glasm/emit_glasm_floating_point.cpp
backend/glasm/emit_glasm_image.cpp
backend/glasm/emit_glasm_instructions.h
backend/glasm/emit_glasm_integer.cpp
backend/glasm/emit_glasm_logical.cpp
backend/glasm/emit_glasm_memory.cpp
backend/glasm/emit_glasm_not_implemented.cpp
backend/glasm/emit_glasm_select.cpp
backend/glasm/emit_glasm_shared_memory.cpp
backend/glasm/emit_glasm_special.cpp
backend/glasm/emit_glasm_undefined.cpp
backend/glasm/emit_glasm_warp.cpp
backend/glasm/glasm_emit_context.cpp
backend/glasm/glasm_emit_context.h
backend/glasm/reg_alloc.cpp
backend/glasm/reg_alloc.h
backend/glsl/emit_glsl.cpp
backend/glsl/emit_glsl.h
backend/glsl/emit_glsl_atomic.cpp
backend/glsl/emit_glsl_barriers.cpp
backend/glsl/emit_glsl_bitwise_conversion.cpp
backend/glsl/emit_glsl_composite.cpp
backend/glsl/emit_glsl_context_get_set.cpp
backend/glsl/emit_glsl_control_flow.cpp
backend/glsl/emit_glsl_convert.cpp
backend/glsl/emit_glsl_floating_point.cpp
backend/glsl/emit_glsl_image.cpp
backend/glsl/emit_glsl_instructions.h
backend/glsl/emit_glsl_integer.cpp
backend/glsl/emit_glsl_logical.cpp
backend/glsl/emit_glsl_memory.cpp
backend/glsl/emit_glsl_not_implemented.cpp
backend/glsl/emit_glsl_select.cpp
backend/glsl/emit_glsl_shared_memory.cpp
backend/glsl/emit_glsl_special.cpp
backend/glsl/emit_glsl_undefined.cpp
backend/glsl/emit_glsl_warp.cpp
backend/glsl/glsl_emit_context.cpp
backend/glsl/glsl_emit_context.h
backend/glsl/var_alloc.cpp
backend/glsl/var_alloc.h)
endif()
target_link_libraries(shader_recompiler PUBLIC common fmt::fmt sirit::sirit)
@@ -553,17 +553,6 @@ void GlobalMemoryToStorageBufferPass(IR::Program& program, const HostTranslateIn
}
}
template <typename Descriptors, typename Descriptor, typename Func>
static u32 Add(Descriptors& descriptors, const Descriptor& desc, Func&& pred) {
// TODO: Handle arrays
const auto it{std::ranges::find_if(descriptors, pred)};
if (it != descriptors.end()) {
return static_cast<u32>(std::distance(descriptors.begin(), it));
}
descriptors.push_back(desc);
return static_cast<u32>(descriptors.size()) - 1;
}
void JoinStorageInfo(Info& base, Info& source) {
auto& descriptors = base.storage_buffers_descriptors;
for (auto& desc : source.storage_buffers_descriptors) {
-29
View File
@@ -299,35 +299,6 @@ if (ENABLE_LSFG)
target_compile_definitions(video_core PUBLIC HAS_LSFG)
endif()
if (ENABLE_RESHADE)
set(BUNDLED_FX_DIR ${CMAKE_SOURCE_DIR}/dist/post_shaders)
set(BUNDLED_FX_HEADER ${CMAKE_CURRENT_BINARY_DIR}/bundled_fx_effects.h)
file(GLOB BUNDLED_FX_FILES ${BUNDLED_FX_DIR}/*.fx)
add_custom_command(
OUTPUT ${BUNDLED_FX_HEADER}
COMMAND ${CMAKE_COMMAND} -P
${CMAKE_CURRENT_SOURCE_DIR}/post_processing/GenerateBundledEffects.cmake
${BUNDLED_FX_DIR} ${BUNDLED_FX_HEADER}
DEPENDS ${BUNDLED_FX_FILES}
)
target_sources(video_core PRIVATE
post_processing/fx_chain.cpp
post_processing/fx_chain.h
post_processing/fx_compile.cpp
post_processing/fx_compile.h
post_processing/fx_effect.cpp
post_processing/fx_effect.h
renderer_vulkan/present/post_process.cpp
renderer_vulkan/present/post_process.h
${BUNDLED_FX_HEADER}
)
target_include_directories(video_core PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
target_link_libraries(video_core PRIVATE reshadefx::reshadefx)
target_compile_definitions(video_core PUBLIC HAS_RESHADE)
endif()
if (ENABLE_OPENGL)
target_sources(video_core PRIVATE
renderer_opengl/present/filters.cpp
+1
View File
@@ -8,6 +8,7 @@
#include <array>
#include <vector>
#include <type_traits>
#include "common/bit_field.h"
#include "common/common_funcs.h"
+7
View File
@@ -1,8 +1,15 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <cstddef>
#include <type_traits>
#include "common/bit_field.h"
#include "common/common_funcs.h"
#include "common/common_types.h"
+1
View File
@@ -7,6 +7,7 @@
#pragma once
#include <memory>
#include <type_traits>
#include "common/common_types.h"
#include "common/scratch_buffer.h"
@@ -1,50 +0,0 @@
# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
# SPDX-License-Identifier: GPL-3.0-or-later
set(EFFECT_DIR ${CMAKE_ARGV3})
set(HEADER_FILE ${CMAKE_ARGV4})
file(GLOB EFFECT_FILES ${EFFECT_DIR}/*.fx)
list(SORT EFFECT_FILES)
set(ENTRIES "")
foreach(EFFECT_FILE IN LISTS EFFECT_FILES)
get_filename_component(EFFECT_NAME ${EFFECT_FILE} NAME)
file(READ ${EFFECT_FILE} EFFECT_BODY)
string(REGEX REPLACE ";" "{{SEMICOLON}}" EFFECT_BODY "${EFFECT_BODY}")
string(REGEX REPLACE "\n" ";" EFFECT_BODY "${EFFECT_BODY}")
set(EFFECT_TEXT "")
foreach(LINE IN LISTS EFFECT_BODY)
string(CONCAT EFFECT_TEXT "${EFFECT_TEXT}" " R\"(${LINE}\n)\"\n")
endforeach()
string(REGEX REPLACE "{{SEMICOLON}}" ";" EFFECT_TEXT "${EFFECT_TEXT}")
string(CONCAT ENTRIES "${ENTRIES}"
" {\n \"${EFFECT_NAME}\",\n${EFFECT_TEXT} },\n")
endforeach()
get_filename_component(OUTPUT_DIR ${HEADER_FILE} DIRECTORY)
make_directory(${OUTPUT_DIR})
file(WRITE ${HEADER_FILE}
"// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <string_view>
namespace VideoCore {
struct BundledFxEffect {
std::string_view name;
std::string_view source;
};
constexpr BundledFxEffect BUNDLED_FX_EFFECTS[]{
${ENTRIES}};
} // namespace VideoCore
")
-304
View File
@@ -1,304 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include <algorithm>
#include <cstdlib>
#include <fmt/format.h>
#include "common/settings.h"
#include "video_core/post_processing/fx_chain.h"
#include "video_core/post_processing/fx_effect.h"
namespace VideoCore {
namespace {
std::vector<std::string_view> Split(std::string_view value, char separator) {
std::vector<std::string_view> out;
size_t start = 0;
while (start <= value.size()) {
size_t end = value.find(separator, start);
if (end == std::string_view::npos) {
end = value.size();
}
out.push_back(value.substr(start, end - start));
start = end + 1;
}
return out;
}
bool IsSerializableName(std::string_view value) {
return value.find_first_of(";|,=") == std::string_view::npos;
}
} // Anonymous namespace
std::vector<FxChainEntry> ParseFxChain(std::string_view value) {
std::vector<FxChainEntry> parsed;
for (const std::string_view record : Split(value, ';')) {
if (record.empty()) {
continue;
}
const auto fields = Split(record, '|');
if (fields.size() < 2 || fields[0].empty() || fields[1].empty()) {
continue;
}
FxChainEntry entry;
entry.file = std::string(fields[0]);
entry.technique = std::string(fields[1]);
if (fields.size() >= 3) {
for (const std::string_view assignment : Split(fields[2], ',')) {
const size_t equals = assignment.find('=');
if (equals == std::string_view::npos) {
continue;
}
const std::string name(assignment.substr(0, equals));
if (name.empty()) {
continue;
}
std::array<f32, 4> components{};
size_t index = 0;
for (const std::string_view piece : Split(assignment.substr(equals + 1), '/')) {
if (index >= components.size()) {
break;
}
const std::string text(piece);
if (!text.empty()) {
components[index] = std::strtof(text.c_str(), nullptr);
}
++index;
}
entry.values.emplace(name, components);
}
}
parsed.push_back(std::move(entry));
}
return parsed;
}
std::string SerializeFxChain(std::span<const FxChainEntry> entries) {
std::string out;
for (const auto& entry : entries) {
if (!IsSerializableName(entry.file) || !IsSerializableName(entry.technique)) {
continue;
}
if (!out.empty()) {
out += ';';
}
out += entry.file;
out += '|';
out += entry.technique;
out += '|';
bool first = true;
for (const auto& [name, value] : entry.values) {
if (!IsSerializableName(name)) {
continue;
}
if (!first) {
out += ',';
}
first = false;
out += name;
out += '=';
for (size_t i = 0; i < value.size(); ++i) {
if (i > 0) {
out += '/';
}
out += fmt::format("{}", value[i]);
}
}
}
return out;
}
FxChain& FxChain::Instance() {
static FxChain instance;
return instance;
}
FxChainSnapshot FxChain::Snapshot() const {
std::scoped_lock lock{mutex};
return FxChainSnapshot{
.entries = entries,
.generation = generation.load(std::memory_order_relaxed),
};
}
std::vector<FxChainEntry> FxChain::Entries() const {
std::scoped_lock lock{mutex};
return entries;
}
size_t FxChain::Size() const {
std::scoped_lock lock{mutex};
return entries.size();
}
void FxChain::Append(std::string_view file, std::string_view technique) {
{
std::scoped_lock lock{mutex};
FxChainEntry entry;
entry.file = std::string(file);
entry.technique = std::string(technique);
entries.push_back(std::move(entry));
}
generation.fetch_add(1, std::memory_order_relaxed);
}
void FxChain::Replace(size_t index, std::string_view file, std::string_view technique) {
{
std::scoped_lock lock{mutex};
if (index >= entries.size()) {
return;
}
if (entries[index].file == file && entries[index].technique == technique) {
return;
}
FxChainEntry entry;
entry.file = std::string(file);
entry.technique = std::string(technique);
entries[index] = std::move(entry);
}
generation.fetch_add(1, std::memory_order_relaxed);
}
void FxChain::Remove(size_t index) {
{
std::scoped_lock lock{mutex};
if (index >= entries.size()) {
return;
}
entries.erase(entries.begin() + static_cast<std::ptrdiff_t>(index));
}
generation.fetch_add(1, std::memory_order_relaxed);
}
void FxChain::Move(size_t index, int delta) {
{
std::scoped_lock lock{mutex};
if (index >= entries.size()) {
return;
}
const std::ptrdiff_t target = static_cast<std::ptrdiff_t>(index) + delta;
if (target < 0 || target >= static_cast<std::ptrdiff_t>(entries.size())) {
return;
}
std::swap(entries[index], entries[static_cast<size_t>(target)]);
}
generation.fetch_add(1, std::memory_order_relaxed);
}
void FxChain::Clear() {
{
std::scoped_lock lock{mutex};
entries.clear();
}
generation.fetch_add(1, std::memory_order_relaxed);
}
void FxChain::SetValue(size_t index, std::string_view uniform, const std::array<f32, 4>& value) {
std::scoped_lock lock{mutex};
if (index >= entries.size()) {
return;
}
entries[index].values[std::string(uniform)] = value;
}
std::array<f32, 4> FxChain::GetValue(size_t index, std::string_view uniform) const {
std::scoped_lock lock{mutex};
if (index >= entries.size()) {
return {};
}
const auto it = entries[index].values.find(std::string(uniform));
if (it == entries[index].values.end()) {
return {};
}
return it->second;
}
std::map<std::string, std::array<f32, 4>> FxChain::EntryValues(size_t index) const {
std::scoped_lock lock{mutex};
if (index >= entries.size()) {
return {};
}
return entries[index].values;
}
bool FxChain::HasValue(size_t index, std::string_view uniform) const {
std::scoped_lock lock{mutex};
if (index >= entries.size()) {
return false;
}
return entries[index].values.contains(std::string(uniform));
}
void FxChain::ResetValues(size_t index) {
std::scoped_lock lock{mutex};
if (index >= entries.size()) {
return;
}
entries[index].values.clear();
}
void FxChain::LoadFromSettings() {
auto parsed = ParseFxChain(Settings::values.post_shader_chain.GetValue());
std::scoped_lock lock{mutex};
entries = std::move(parsed);
loaded = true;
generation.fetch_add(1, std::memory_order_relaxed);
}
void FxChain::EnsureLoadedFromSettings() {
{
std::scoped_lock lock{mutex};
if (loaded) {
return;
}
}
LoadFromSettings();
}
void FxChain::StoreToSettings() const {
std::string serialized;
{
std::scoped_lock lock{mutex};
serialized = SerializeFxChain(entries);
}
Settings::values.post_shader_chain.SetValue(serialized);
}
void FxChain::DropUnknownEntries() {
bool changed = false;
{
std::scoped_lock lock{mutex};
const auto removed = std::remove_if(entries.begin(), entries.end(), [](const FxChainEntry& entry) {
const FxEffectDesc* effect = FindFxEffect(entry.file);
if (effect == nullptr || !effect->Valid()) {
return true;
}
return std::find(effect->techniques.begin(), effect->techniques.end(),
entry.technique) == effect->techniques.end();
});
if (removed != entries.end()) {
entries.erase(removed, entries.end());
changed = true;
}
}
if (changed) {
generation.fetch_add(1, std::memory_order_relaxed);
}
}
} // namespace VideoCore
-81
View File
@@ -1,81 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <array>
#include <atomic>
#include <map>
#include <mutex>
#include <span>
#include <string>
#include <string_view>
#include <vector>
#include "common/common_types.h"
namespace VideoCore {
struct FxChainEntry {
std::string file;
std::string technique;
std::map<std::string, std::array<f32, 4>> values;
};
struct FxChainSnapshot {
std::vector<FxChainEntry> entries;
u64 generation{};
};
std::vector<FxChainEntry> ParseFxChain(std::string_view value);
std::string SerializeFxChain(std::span<const FxChainEntry> entries);
class FxChain {
public:
static FxChain& Instance();
FxChainSnapshot Snapshot() const;
std::vector<FxChainEntry> Entries() const;
size_t Size() const;
void Append(std::string_view file, std::string_view technique);
void Replace(size_t index, std::string_view file, std::string_view technique);
void Remove(size_t index);
void Move(size_t index, int delta);
void Clear();
void SetValue(size_t index, std::string_view uniform, const std::array<f32, 4>& value);
std::array<f32, 4> GetValue(size_t index, std::string_view uniform) const;
std::map<std::string, std::array<f32, 4>> EntryValues(size_t index) const;
bool HasValue(size_t index, std::string_view uniform) const;
void ResetValues(size_t index);
void LoadFromSettings();
void EnsureLoadedFromSettings();
void StoreToSettings() const;
void DropUnknownEntries();
private:
FxChain() = default;
mutable std::mutex mutex;
std::vector<FxChainEntry> entries;
bool loaded{};
std::atomic<u64> generation{1};
};
} // namespace VideoCore
@@ -1,105 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include <cstring>
#include <memory>
#include <set>
#include "effect_codegen.hpp"
#include "effect_parser.hpp"
#include "effect_preprocessor.hpp"
#include "common/fs/fs.h"
#include "common/fs/fs_util.h"
#include "video_core/post_processing/fx_compile.h"
#include "video_core/post_processing/fx_effect.h"
namespace VideoCore {
FxCompileResult CompileFxEffect(const std::filesystem::path& path, u32 width, u32 height,
u32 color_bit_depth) {
FxCompileResult result;
if (!Common::FS::Exists(path)) {
result.error = "Effect file not found: " + Common::FS::PathToUTF8String(path);
return result;
}
reshadefx::preprocessor preprocessor;
preprocessor.add_macro_definition("__RESHADE__", "50000");
preprocessor.add_macro_definition("__RESHADE_PERFORMANCE_MODE__", "1");
preprocessor.add_macro_definition("__RENDERER__", "0x20000");
preprocessor.add_macro_definition("__VENDOR__", "0");
preprocessor.add_macro_definition("__DEVICE__", "0");
preprocessor.add_macro_definition("__APPLICATION__", "0");
preprocessor.add_macro_definition("BUFFER_WIDTH", std::to_string(width));
preprocessor.add_macro_definition("BUFFER_HEIGHT", std::to_string(height));
preprocessor.add_macro_definition("BUFFER_RCP_WIDTH", "(1.0 / BUFFER_WIDTH)");
preprocessor.add_macro_definition("BUFFER_RCP_HEIGHT", "(1.0 / BUFFER_HEIGHT)");
preprocessor.add_macro_definition("BUFFER_COLOR_DEPTH", std::to_string(color_bit_depth));
preprocessor.add_macro_definition("BUFFER_COLOR_BIT_DEPTH", std::to_string(color_bit_depth));
for (const auto& include : GetFxIncludePaths(path)) {
preprocessor.add_include_path(include);
}
if (!preprocessor.append_file(path)) {
result.error = preprocessor.errors();
if (result.error.empty()) {
result.error = "Failed to preprocess " + Common::FS::PathToUTF8String(path);
}
return result;
}
std::unique_ptr<reshadefx::codegen> backend(
reshadefx::create_codegen_spirv(true, false, false, false, true));
reshadefx::parser parser;
if (!parser.parse(preprocessor.output(), backend.get())) {
result.error = parser.errors();
if (result.error.empty()) {
result.error = "Failed to parse " + Common::FS::PathToUTF8String(path);
}
return result;
}
result.module = backend->module();
std::set<std::string> wanted;
for (const auto& technique : result.module.techniques) {
for (const auto& pass : technique.passes) {
if (!pass.vs_entry_point.empty()) {
wanted.insert(pass.vs_entry_point);
}
if (!pass.ps_entry_point.empty()) {
wanted.insert(pass.ps_entry_point);
}
}
}
for (const auto& name : wanted) {
std::string binary;
std::string assembly;
std::string errors;
if (!backend->assemble_code_for_entry_point(name, binary, assembly, errors)) {
result.error = "Failed to assemble entry point '" + name + "': " + errors;
return result;
}
if (binary.size() % sizeof(u32) != 0) {
result.error = "Entry point '" + name + "' produced a malformed SPIR-V module";
return result;
}
std::vector<u32> words(binary.size() / sizeof(u32));
std::memcpy(words.data(), binary.data(), binary.size());
result.entry_points.emplace(name, std::move(words));
}
if (result.entry_points.empty()) {
result.error = "Effect declares no usable entry points";
}
return result;
}
} // namespace VideoCore
@@ -1,29 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <filesystem>
#include <map>
#include <string>
#include <vector>
#include "common/common_types.h"
#include "effect_module.hpp"
namespace VideoCore {
struct FxCompileResult {
reshadefx::effect_module module;
std::map<std::string, std::vector<u32>> entry_points;
std::string error;
bool Succeeded() const {
return error.empty() && !entry_points.empty();
}
};
FxCompileResult CompileFxEffect(const std::filesystem::path& path, u32 width, u32 height,
u32 color_bit_depth);
} // namespace VideoCore
@@ -1,298 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include <algorithm>
#include "bundled_fx_effects.h"
#include "common/fs/file.h"
#include "common/fs/fs.h"
#include "common/fs/fs_util.h"
#include "common/fs/path_util.h"
#include "common/logging.h"
#include "video_core/post_processing/fx_compile.h"
#include "video_core/post_processing/fx_effect.h"
namespace VideoCore {
namespace {
constexpr u32 CATALOG_PROBE_WIDTH = 1280;
constexpr u32 CATALOG_PROBE_HEIGHT = 720;
constexpr u32 CATALOG_PROBE_DEPTH = 8;
std::vector<FxEffectDesc> catalog;
bool catalog_scanned = false;
const reshadefx::annotation* FindAnnotation(const std::vector<reshadefx::annotation>& annotations,
std::string_view name) {
const auto it = std::find_if(annotations.begin(), annotations.end(),
[&](const reshadefx::annotation& a) { return a.name == name; });
if (it == annotations.end()) {
return nullptr;
}
return &*it;
}
std::string AnnotationString(const std::vector<reshadefx::annotation>& annotations,
std::string_view name) {
const reshadefx::annotation* a = FindAnnotation(annotations, name);
if (a == nullptr) {
return std::string();
}
return a->value.string_data;
}
bool AnnotationFloat(const std::vector<reshadefx::annotation>& annotations, std::string_view name,
f32& out) {
const reshadefx::annotation* a = FindAnnotation(annotations, name);
if (a == nullptr) {
return false;
}
if (a->type.is_floating_point()) {
out = a->value.as_float[0];
return true;
}
if (a->type.is_integral()) {
out = static_cast<f32>(a->value.as_int[0]);
return true;
}
return false;
}
FxUiType ParseUiType(std::string_view value) {
if (value == "slider") {
return FxUiType::Slider;
}
if (value == "drag") {
return FxUiType::Drag;
}
if (value == "combo") {
return FxUiType::Combo;
}
if (value == "radio") {
return FxUiType::Radio;
}
if (value == "check" || value == "checkbox") {
return FxUiType::CheckBox;
}
if (value == "color") {
return FxUiType::Color;
}
if (value == "input") {
return FxUiType::InputBox;
}
return FxUiType::Hidden;
}
std::vector<std::string> SplitItems(const std::string& items) {
std::vector<std::string> out;
std::string current;
for (const char c : items) {
if (c == '\0') {
out.push_back(current);
current.clear();
continue;
}
current += c;
}
if (!current.empty()) {
out.push_back(current);
}
return out;
}
FxUniformDesc DescribeUniform(const reshadefx::uniform& info) {
FxUniformDesc desc;
desc.name = info.name;
desc.components = std::min<u32>(info.type.components(), 4);
if (info.type.is_boolean()) {
desc.kind = FxUniformKind::Boolean;
} else if (info.type.is_integral()) {
desc.kind = FxUniformKind::Integer;
} else {
desc.kind = FxUniformKind::Floating;
}
desc.label = AnnotationString(info.annotations, "ui_label");
if (desc.label.empty()) {
desc.label = info.name;
}
desc.tooltip = AnnotationString(info.annotations, "ui_tooltip");
desc.category = AnnotationString(info.annotations, "ui_category");
desc.ui_type = ParseUiType(AnnotationString(info.annotations, "ui_type"));
desc.items = SplitItems(AnnotationString(info.annotations, "ui_items"));
if (desc.kind == FxUniformKind::Boolean) {
desc.ui_min = 0.0f;
desc.ui_max = 1.0f;
desc.ui_step = 1.0f;
} else if (desc.kind == FxUniformKind::Integer) {
desc.ui_min = 0.0f;
desc.ui_max = 100.0f;
desc.ui_step = 1.0f;
}
void(AnnotationFloat(info.annotations, "ui_min", desc.ui_min));
void(AnnotationFloat(info.annotations, "ui_max", desc.ui_max));
void(AnnotationFloat(info.annotations, "ui_step", desc.ui_step));
if (desc.ui_step <= 0.0f) {
desc.ui_step = 0.01f;
if (desc.kind != FxUniformKind::Floating) {
desc.ui_step = 1.0f;
}
}
if (desc.ui_max < desc.ui_min) {
std::swap(desc.ui_min, desc.ui_max);
}
if (info.has_initializer_value) {
for (u32 i = 0; i < desc.components; ++i) {
if (desc.kind == FxUniformKind::Floating) {
desc.default_value[i] = info.initializer_value.as_float[i];
} else {
desc.default_value[i] = static_cast<f32>(info.initializer_value.as_int[i]);
}
}
}
return desc;
}
FxEffectDesc DescribeEffect(const std::filesystem::path& path, const std::filesystem::path& root) {
FxEffectDesc desc;
desc.file = Common::FS::PathToUTF8String(std::filesystem::relative(path, root));
desc.name = Common::FS::PathToUTF8String(path.stem());
const auto compiled =
CompileFxEffect(path, CATALOG_PROBE_WIDTH, CATALOG_PROBE_HEIGHT, CATALOG_PROBE_DEPTH);
if (!compiled.Succeeded()) {
desc.error = compiled.error;
return desc;
}
for (const auto& technique : compiled.module.techniques) {
desc.techniques.push_back(technique.name);
}
for (const auto& uniform : compiled.module.uniforms) {
FxUniformDesc uniform_desc = DescribeUniform(uniform);
if (uniform_desc.ui_type == FxUiType::Hidden) {
continue;
}
desc.uniforms.push_back(std::move(uniform_desc));
}
return desc;
}
} // Anonymous namespace
std::filesystem::path GetFxRootDirectory() {
return Common::FS::GetEdenPath(Common::FS::EdenPath::PostShaderDir);
}
std::vector<std::filesystem::path> GetFxIncludePaths(const std::filesystem::path& effect_path) {
const auto root = GetFxRootDirectory();
std::vector<std::filesystem::path> paths;
paths.push_back(effect_path.parent_path());
paths.push_back(root);
paths.push_back(root / "Shaders");
const auto last = std::unique(paths.begin(), paths.end());
paths.erase(last, paths.end());
return paths;
}
std::filesystem::path ResolveFxTexturePath(const std::filesystem::path& effect_path,
std::string_view source) {
const auto root = GetFxRootDirectory();
const std::filesystem::path name{source};
const std::array candidates{
effect_path.parent_path() / name,
root / "Textures" / name,
root / name,
};
for (const auto& candidate : candidates) {
if (Common::FS::Exists(candidate)) {
return candidate;
}
}
return std::filesystem::path();
}
void ReloadFxCatalog() {
catalog.clear();
catalog_scanned = true;
const auto root = GetFxRootDirectory();
if (!Common::FS::Exists(root) && !Common::FS::CreateDirs(root)) {
return;
}
for (const auto& bundled : BUNDLED_FX_EFFECTS) {
const auto path = root / bundled.name;
if (Common::FS::Exists(path)) {
continue;
}
void(Common::FS::WriteStringToFile(path, Common::FS::FileType::TextFile, bundled.source));
}
std::vector<std::filesystem::path> effect_files;
Common::FS::IterateDirEntriesRecursively(
root,
[&](const std::filesystem::directory_entry& entry) {
if (entry.path().extension() == ".fx") {
effect_files.push_back(entry.path());
}
return true;
},
Common::FS::DirEntryFilter::File);
std::sort(effect_files.begin(), effect_files.end());
for (const auto& file : effect_files) {
FxEffectDesc desc = DescribeEffect(file, root);
if (!desc.error.empty()) {
LOG_WARNING(Render, "Post-processing effect '{}' failed to compile:\n{}", desc.file,
desc.error);
}
catalog.push_back(std::move(desc));
}
const size_t usable = std::count_if(catalog.begin(), catalog.end(),
[](const FxEffectDesc& d) { return d.Valid(); });
LOG_INFO(Render, "Loaded {} post-processing effects ({} usable)", catalog.size(), usable);
}
const std::vector<FxEffectDesc>& GetFxCatalog() {
if (!catalog_scanned) {
ReloadFxCatalog();
}
return catalog;
}
const FxEffectDesc* FindFxEffect(std::string_view file) {
const auto& effects = GetFxCatalog();
const auto it = std::find_if(effects.begin(), effects.end(),
[&](const FxEffectDesc& d) { return d.file == file; });
if (it == effects.end()) {
return nullptr;
}
return &*it;
}
const FxUniformDesc* FindFxUniform(const FxEffectDesc& effect, std::string_view name) {
const auto it = std::find_if(effect.uniforms.begin(), effect.uniforms.end(),
[&](const FxUniformDesc& u) { return u.name == name; });
if (it == effect.uniforms.end()) {
return nullptr;
}
return &*it;
}
} // namespace VideoCore
@@ -1,75 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <array>
#include <filesystem>
#include <string>
#include <string_view>
#include <vector>
#include "common/common_types.h"
namespace VideoCore {
enum class FxUniformKind {
Boolean,
Integer,
Floating,
};
enum class FxUiType {
Hidden,
Slider,
Drag,
Combo,
Radio,
CheckBox,
Color,
InputBox,
};
struct FxUniformDesc {
std::string name;
std::string label;
std::string tooltip;
std::string category;
FxUniformKind kind{FxUniformKind::Floating};
u32 components{1};
FxUiType ui_type{FxUiType::Hidden};
f32 ui_min{0.0f};
f32 ui_max{1.0f};
f32 ui_step{0.01f};
std::vector<std::string> items;
std::array<f32, 4> default_value{};
};
struct FxEffectDesc {
std::string file;
std::string name;
std::vector<std::string> techniques;
std::vector<FxUniformDesc> uniforms;
std::string error;
bool Valid() const {
return error.empty() && !techniques.empty();
}
};
std::filesystem::path GetFxRootDirectory();
std::vector<std::filesystem::path> GetFxIncludePaths(const std::filesystem::path& effect_path);
std::filesystem::path ResolveFxTexturePath(const std::filesystem::path& effect_path,
std::string_view source);
void ReloadFxCatalog();
const std::vector<FxEffectDesc>& GetFxCatalog();
const FxEffectDesc* FindFxEffect(std::string_view file);
const FxUniformDesc* FindFxUniform(const FxEffectDesc& effect, std::string_view name);
} // namespace VideoCore
@@ -18,10 +18,6 @@
#include "video_core/renderer_vulkan/present/sgsr.h"
#include "video_core/renderer_vulkan/present/fxaa.h"
#include "video_core/renderer_vulkan/present/layer.h"
#ifdef HAS_RESHADE
#include "video_core/post_processing/fx_chain.h"
#include "video_core/renderer_vulkan/present/post_process.h"
#endif
#include "video_core/renderer_vulkan/present/present_push_constants.h"
#include "video_core/renderer_vulkan/present/smaa.h"
#include "video_core/renderer_vulkan/present/util.h"
@@ -97,9 +93,6 @@ void Layer::ConfigureDraw(const Device& device, PresentPushConstants* out_push_c
RefreshResources(device, framebuffer);
SetAntiAliasPass(device);
#ifdef HAS_RESHADE
SetPostProcessPass(device);
#endif
// Finish any pending renderpass
scheduler.RequestOutsideRenderPassOperationContext();
@@ -122,12 +115,6 @@ void Layer::ConfigureDraw(const Device& device, PresentPushConstants* out_push_c
smaa->Draw(device, scheduler, image_index, &source_image, &source_image_view);
}
#ifdef HAS_RESHADE
if (post_process.has_value()) {
post_process->Draw(device, scheduler, image_index, &source_image, &source_image_view);
}
#endif
auto crop_rect = Tegra::NormalizeCrop(framebuffer, texture_width, texture_height);
const VkExtent2D render_extent{
.width = scaled_width,
@@ -227,40 +214,6 @@ void Layer::SetAntiAliasPass(const Device& device) {
}
}
#ifdef HAS_RESHADE
void Layer::SetPostProcessPass(const Device& device) {
const VkExtent2D render_area{
.width = Settings::values.resolution_info.ScaleUp(raw_width),
.height = Settings::values.resolution_info.ScaleUp(raw_height),
};
const u64 generation = VideoCore::FxChain::Instance().Snapshot().generation;
if (post_process_generation == generation && post_process_extent.width == render_area.width &&
post_process_extent.height == render_area.height) {
return;
}
for (const u64 tick : resource_ticks) {
scheduler.Wait(tick);
}
post_process_generation = generation;
post_process_extent = render_area;
post_process.reset();
if (VideoCore::FxChain::Instance().Size() == 0) {
return;
}
post_process.emplace(device, memory_allocator, scheduler, image_count, render_area);
if (post_process->Empty()) {
post_process.reset();
}
}
#endif
void Layer::ReleaseRawImages() {
for (const u64 tick : resource_ticks) {
scheduler.Wait(tick);
@@ -15,9 +15,6 @@
#include "video_core/renderer_vulkan/present/fsr.h"
#include "video_core/renderer_vulkan/present/sgsr.h"
#include "video_core/renderer_vulkan/present/fxaa.h"
#ifdef HAS_RESHADE
#include "video_core/renderer_vulkan/present/post_process.h"
#endif
#include "video_core/renderer_vulkan/present/smaa.h"
namespace Layout {
@@ -69,9 +66,6 @@ private:
void RefreshResources(const Device& device, const Tegra::FramebufferConfig& framebuffer);
void SetAntiAliasPass(const Device& device);
#ifdef HAS_RESHADE
void SetPostProcessPass(const Device& device);
#endif
void ReleaseRawImages();
u64 CalculateBufferSize(const Tegra::FramebufferConfig& framebuffer) const;
@@ -101,11 +95,6 @@ private:
Settings::AntiAliasing anti_alias_setting{};
std::variant<std::monostate, FXAA, SMAA> anti_alias{};
std::variant<std::monostate, SGSR, FSR> sr_filter{};
#ifdef HAS_RESHADE
std::optional<PostProcessChain> post_process{};
u64 post_process_generation{};
VkExtent2D post_process_extent{};
#endif
std::vector<u64> resource_ticks{};
};
@@ -1,899 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include <algorithm>
#include <cstring>
#include <random>
#include "common/fs/fs.h"
#include "common/fs/fs_util.h"
#include "common/logging.h"
#include "video_core/post_processing/fx_chain.h"
#include "video_core/post_processing/fx_compile.h"
#include "video_core/post_processing/fx_effect.h"
#include "video_core/renderer_vulkan/present/post_process.h"
#include "video_core/renderer_vulkan/present/util.h"
#include "video_core/renderer_vulkan/vk_scheduler.h"
#include "video_core/vulkan_common/vulkan_device.h"
namespace Vulkan {
namespace {
constexpr VkFormat BACKBUFFER_FORMAT = VK_FORMAT_R16G16B16A16_SFLOAT;
constexpr size_t NO_TEXTURE = ~size_t{0};
VkFormat ToVkFormat(reshadefx::texture_format format) {
switch (format) {
case reshadefx::texture_format::r8:
return VK_FORMAT_R8_UNORM;
case reshadefx::texture_format::r16f:
return VK_FORMAT_R16_SFLOAT;
case reshadefx::texture_format::r32f:
return VK_FORMAT_R32_SFLOAT;
case reshadefx::texture_format::rg8:
return VK_FORMAT_R8G8_UNORM;
case reshadefx::texture_format::rg16:
return VK_FORMAT_R16G16_UNORM;
case reshadefx::texture_format::rg16f:
return VK_FORMAT_R16G16_SFLOAT;
case reshadefx::texture_format::rg32f:
return VK_FORMAT_R32G32_SFLOAT;
case reshadefx::texture_format::rgba8:
return VK_FORMAT_R8G8B8A8_UNORM;
case reshadefx::texture_format::rgba16:
return VK_FORMAT_R16G16B16A16_UNORM;
case reshadefx::texture_format::rgba16f:
return VK_FORMAT_R16G16B16A16_SFLOAT;
case reshadefx::texture_format::rgba32f:
return VK_FORMAT_R32G32B32A32_SFLOAT;
case reshadefx::texture_format::rgb10a2:
return VK_FORMAT_A2B10G10R10_UNORM_PACK32;
default:
return VK_FORMAT_R8G8B8A8_UNORM;
}
}
VkSamplerAddressMode ToAddressMode(reshadefx::texture_address_mode mode) {
switch (mode) {
case reshadefx::texture_address_mode::wrap:
return VK_SAMPLER_ADDRESS_MODE_REPEAT;
case reshadefx::texture_address_mode::mirror:
return VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT;
case reshadefx::texture_address_mode::border:
return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER;
case reshadefx::texture_address_mode::clamp:
default:
return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
}
}
VkBlendFactor ToBlendFactor(reshadefx::blend_factor func) {
switch (func) {
case reshadefx::blend_factor::zero:
return VK_BLEND_FACTOR_ZERO;
case reshadefx::blend_factor::source_color:
return VK_BLEND_FACTOR_SRC_COLOR;
case reshadefx::blend_factor::source_alpha:
return VK_BLEND_FACTOR_SRC_ALPHA;
case reshadefx::blend_factor::one_minus_source_color:
return VK_BLEND_FACTOR_ONE_MINUS_SRC_COLOR;
case reshadefx::blend_factor::one_minus_source_alpha:
return VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
case reshadefx::blend_factor::dest_color:
return VK_BLEND_FACTOR_DST_COLOR;
case reshadefx::blend_factor::dest_alpha:
return VK_BLEND_FACTOR_DST_ALPHA;
case reshadefx::blend_factor::one_minus_dest_color:
return VK_BLEND_FACTOR_ONE_MINUS_DST_COLOR;
case reshadefx::blend_factor::one_minus_dest_alpha:
return VK_BLEND_FACTOR_ONE_MINUS_DST_ALPHA;
case reshadefx::blend_factor::one:
default:
return VK_BLEND_FACTOR_ONE;
}
}
VkBlendOp ToBlendOp(reshadefx::blend_op op) {
switch (op) {
case reshadefx::blend_op::subtract:
return VK_BLEND_OP_SUBTRACT;
case reshadefx::blend_op::reverse_subtract:
return VK_BLEND_OP_REVERSE_SUBTRACT;
case reshadefx::blend_op::min:
return VK_BLEND_OP_MIN;
case reshadefx::blend_op::max:
return VK_BLEND_OP_MAX;
case reshadefx::blend_op::add:
default:
return VK_BLEND_OP_ADD;
}
}
VkPrimitiveTopology ToTopology(reshadefx::primitive_topology topology) {
switch (topology) {
case reshadefx::primitive_topology::point_list:
return VK_PRIMITIVE_TOPOLOGY_POINT_LIST;
case reshadefx::primitive_topology::line_list:
return VK_PRIMITIVE_TOPOLOGY_LINE_LIST;
case reshadefx::primitive_topology::line_strip:
return VK_PRIMITIVE_TOPOLOGY_LINE_STRIP;
case reshadefx::primitive_topology::triangle_strip:
return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP;
case reshadefx::primitive_topology::triangle_list:
default:
return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
}
}
vk::RenderPass CreateFxRenderPass(const Device& device, VkFormat format, bool clear) {
VkAttachmentLoadOp load_op = VK_ATTACHMENT_LOAD_OP_LOAD;
VkImageLayout initial_layout = VK_IMAGE_LAYOUT_GENERAL;
if (clear) {
load_op = VK_ATTACHMENT_LOAD_OP_CLEAR;
initial_layout = VK_IMAGE_LAYOUT_UNDEFINED;
}
const VkAttachmentDescription attachment{
.flags = 0,
.format = format,
.samples = VK_SAMPLE_COUNT_1_BIT,
.loadOp = load_op,
.storeOp = VK_ATTACHMENT_STORE_OP_STORE,
.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE,
.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,
.initialLayout = initial_layout,
.finalLayout = VK_IMAGE_LAYOUT_GENERAL,
};
const VkAttachmentReference reference{
.attachment = 0,
.layout = VK_IMAGE_LAYOUT_GENERAL,
};
const VkSubpassDescription subpass{
.flags = 0,
.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS,
.inputAttachmentCount = 0,
.pInputAttachments = nullptr,
.colorAttachmentCount = 1,
.pColorAttachments = &reference,
.pResolveAttachments = nullptr,
.pDepthStencilAttachment = nullptr,
.preserveAttachmentCount = 0,
.pPreserveAttachments = nullptr,
};
return device.GetLogical().CreateRenderPass(VkRenderPassCreateInfo{
.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.attachmentCount = 1,
.pAttachments = &attachment,
.subpassCount = 1,
.pSubpasses = &subpass,
.dependencyCount = 0,
.pDependencies = nullptr,
});
}
vk::Pipeline CreateFxPipeline(const Device& device, vk::RenderPass& renderpass,
vk::PipelineLayout& layout, VkShaderModule vertex_shader,
VkShaderModule fragment_shader,
const reshadefx::pass& pass) {
const std::array<VkPipelineShaderStageCreateInfo, 2> stages{{
{
.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.stage = VK_SHADER_STAGE_VERTEX_BIT,
.module = vertex_shader,
.pName = pass.vs_entry_point.c_str(),
.pSpecializationInfo = nullptr,
},
{
.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.stage = VK_SHADER_STAGE_FRAGMENT_BIT,
.module = fragment_shader,
.pName = pass.ps_entry_point.c_str(),
.pSpecializationInfo = nullptr,
},
}};
constexpr VkPipelineVertexInputStateCreateInfo vertex_input{
.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.vertexBindingDescriptionCount = 0,
.pVertexBindingDescriptions = nullptr,
.vertexAttributeDescriptionCount = 0,
.pVertexAttributeDescriptions = nullptr,
};
const VkPipelineInputAssemblyStateCreateInfo input_assembly{
.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.topology = ToTopology(pass.topology),
.primitiveRestartEnable = VK_FALSE,
};
constexpr VkPipelineViewportStateCreateInfo viewport_state{
.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.viewportCount = 1,
.pViewports = nullptr,
.scissorCount = 1,
.pScissors = nullptr,
};
constexpr VkPipelineRasterizationStateCreateInfo rasterization{
.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.depthClampEnable = VK_FALSE,
.rasterizerDiscardEnable = VK_FALSE,
.polygonMode = VK_POLYGON_MODE_FILL,
.cullMode = VK_CULL_MODE_NONE,
.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE,
.depthBiasEnable = VK_FALSE,
.depthBiasConstantFactor = 0.0f,
.depthBiasClamp = 0.0f,
.depthBiasSlopeFactor = 0.0f,
.lineWidth = 1.0f,
};
constexpr VkPipelineMultisampleStateCreateInfo multisampling{
.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT,
.sampleShadingEnable = VK_FALSE,
.minSampleShading = 0.0f,
.pSampleMask = nullptr,
.alphaToCoverageEnable = VK_FALSE,
.alphaToOneEnable = VK_FALSE,
};
VkBool32 blend_enable = VK_FALSE;
if (pass.blend_enable[0]) {
blend_enable = VK_TRUE;
}
const VkPipelineColorBlendAttachmentState blending{
.blendEnable = blend_enable,
.srcColorBlendFactor = ToBlendFactor(pass.source_color_blend_factor[0]),
.dstColorBlendFactor = ToBlendFactor(pass.dest_color_blend_factor[0]),
.colorBlendOp = ToBlendOp(pass.color_blend_op[0]),
.srcAlphaBlendFactor = ToBlendFactor(pass.source_alpha_blend_factor[0]),
.dstAlphaBlendFactor = ToBlendFactor(pass.dest_alpha_blend_factor[0]),
.alphaBlendOp = ToBlendOp(pass.alpha_blend_op[0]),
.colorWriteMask = static_cast<VkColorComponentFlags>(pass.render_target_write_mask[0] & 0xF),
};
const VkPipelineColorBlendStateCreateInfo color_blend{
.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.logicOpEnable = VK_FALSE,
.logicOp = VK_LOGIC_OP_COPY,
.attachmentCount = 1,
.pAttachments = &blending,
.blendConstants = {0.0f, 0.0f, 0.0f, 0.0f},
};
constexpr std::array dynamic_states{
VK_DYNAMIC_STATE_VIEWPORT,
VK_DYNAMIC_STATE_SCISSOR,
};
const VkPipelineDynamicStateCreateInfo dynamic_state{
.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.dynamicStateCount = static_cast<u32>(dynamic_states.size()),
.pDynamicStates = dynamic_states.data(),
};
return device.GetLogical().CreateGraphicsPipeline(VkGraphicsPipelineCreateInfo{
.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.stageCount = static_cast<u32>(stages.size()),
.pStages = stages.data(),
.pVertexInputState = &vertex_input,
.pInputAssemblyState = &input_assembly,
.pTessellationState = nullptr,
.pViewportState = &viewport_state,
.pRasterizationState = &rasterization,
.pMultisampleState = &multisampling,
.pDepthStencilState = nullptr,
.pColorBlendState = &color_blend,
.pDynamicState = &dynamic_state,
.layout = *layout,
.renderPass = *renderpass,
.subpass = 0,
.basePipelineHandle = nullptr,
.basePipelineIndex = 0,
});
}
} // Anonymous namespace
PostProcessChain::PostProcessChain(const Device& device, MemoryAllocator& allocator,
Scheduler& scheduler, size_t image_count, VkExtent2D extent)
: m_extent(extent)
, m_image_count(u32(image_count))
{
m_start = std::chrono::steady_clock::now();
m_previous = m_start;
CreatePingPongImages(device, allocator);
m_fallback_sampler = CreateWrappedSampler(device);
m_fallback_image = CreateWrappedImage(allocator, VkExtent2D{1, 1}, VK_FORMAT_R8G8B8A8_UNORM);
m_fallback_view = CreateWrappedImageView(device, m_fallback_image, VK_FORMAT_R8G8B8A8_UNORM);
if (!BuildEffects(device, allocator, scheduler)) {
m_effects.clear();
}
}
PostProcessChain::~PostProcessChain() = default;
bool PostProcessChain::Empty() const {
return m_effects.empty();
}
void PostProcessChain::CreatePingPongImages(const Device& device, MemoryAllocator& allocator) {
m_frames.resize(m_image_count);
for (auto& frame : m_frames) {
for (size_t i = 0; i < frame.images.size(); ++i) {
frame.images[i] = CreateWrappedImage(allocator, m_extent, BACKBUFFER_FORMAT);
frame.views[i] = CreateWrappedImageView(device, frame.images[i], BACKBUFFER_FORMAT);
}
}
}
bool PostProcessChain::BuildEffects(const Device& device, MemoryAllocator& allocator,
Scheduler& scheduler) {
const auto snapshot = VideoCore::FxChain::Instance().Snapshot();
if (snapshot.entries.empty()) {
return true;
}
const auto root = VideoCore::GetFxRootDirectory();
for (size_t entry_index = 0; entry_index < snapshot.entries.size(); ++entry_index) {
const auto& entry = snapshot.entries[entry_index];
const auto path = root / entry.file;
const auto compiled = VideoCore::CompileFxEffect(path, m_extent.width, m_extent.height, 8);
if (!compiled.Succeeded()) {
LOG_ERROR(Render_Vulkan, "Post-processing effect '{}' failed to compile:\n{}",
entry.file, compiled.error);
continue;
}
const auto& module = compiled.module;
const auto technique = std::find_if(
module.techniques.begin(), module.techniques.end(),
[&](const reshadefx::technique& t) { return t.name == entry.technique; });
if (technique == module.techniques.end()) {
LOG_ERROR(Render_Vulkan, "Effect '{}' has no technique '{}'", entry.file,
entry.technique);
continue;
}
Effect effect;
effect.entry_index = entry_index;
effect.file = entry.file;
effect.uniform_size = module.total_uniform_size;
for (const auto& [name, words] : compiled.entry_points) {
effect.shaders.emplace(name, CreateWrappedShaderModule(device, words));
}
for (const auto& texture : module.textures) {
Texture out;
out.name = texture.unique_name;
out.extent = VkExtent2D{texture.width, texture.height};
out.format = ToVkFormat(texture.format);
if (texture.semantic == "COLOR") {
out.is_backbuffer = true;
effect.textures.push_back(std::move(out));
continue;
}
if (texture.semantic == "DEPTH") {
effect.textures.push_back(std::move(out));
continue;
}
out.image = CreateWrappedImage(allocator, out.extent, out.format);
out.view = CreateWrappedImageView(device, out.image, out.format);
effect.textures.push_back(std::move(out));
}
for (const auto& sampler : module.samplers) {
Sampler out;
out.texture_index = NO_TEXTURE;
for (size_t i = 0; i < effect.textures.size(); ++i) {
if (effect.textures[i].name == sampler.texture_name) {
out.texture_index = i;
break;
}
}
VkFilter mag_filter = VK_FILTER_LINEAR;
VkFilter min_filter = VK_FILTER_LINEAR;
VkSamplerMipmapMode mip_mode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
const u32 filter = static_cast<u32>(sampler.filter);
if ((filter & 0x10) == 0) {
min_filter = VK_FILTER_NEAREST;
}
if ((filter & 0x04) == 0) {
mag_filter = VK_FILTER_NEAREST;
}
if ((filter & 0x01) == 0) {
mip_mode = VK_SAMPLER_MIPMAP_MODE_NEAREST;
}
out.sampler = device.GetLogical().CreateSampler(VkSamplerCreateInfo{
.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.magFilter = mag_filter,
.minFilter = min_filter,
.mipmapMode = mip_mode,
.addressModeU = ToAddressMode(sampler.address_u),
.addressModeV = ToAddressMode(sampler.address_v),
.addressModeW = ToAddressMode(sampler.address_w),
.mipLodBias = sampler.lod_bias,
.anisotropyEnable = VK_FALSE,
.maxAnisotropy = 1.0f,
.compareEnable = VK_FALSE,
.compareOp = VK_COMPARE_OP_NEVER,
.minLod = sampler.min_lod,
.maxLod = sampler.max_lod,
.borderColor = VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK,
.unnormalizedCoordinates = VK_FALSE,
});
effect.samplers.push_back(std::move(out));
}
for (const auto& uniform : module.uniforms) {
UniformWrite write;
write.name = uniform.name;
write.offset = uniform.offset;
write.components = std::min<u32>(uniform.type.components(), 4);
write.kind = UniformKind::Floating;
if (uniform.type.is_boolean()) {
write.kind = UniformKind::Boolean;
} else if (uniform.type.is_integral()) {
write.kind = UniformKind::Integer;
}
for (const auto& annotation : uniform.annotations) {
if (annotation.name != "source") {
continue;
}
const std::string& source = annotation.value.string_data;
if (source == "frametime") {
write.source = UniformSource::FrameTime;
} else if (source == "framecount") {
write.source = UniformSource::FrameCount;
} else if (source == "timer") {
write.source = UniformSource::Timer;
} else if (source == "random") {
write.source = UniformSource::Random;
} else if (source == "pingpong") {
write.source = UniformSource::PingPong;
}
}
if (uniform.has_initializer_value) {
for (u32 i = 0; i < write.components; ++i) {
if (write.kind == UniformKind::Floating) {
write.fallback[i] = uniform.initializer_value.as_float[i];
} else {
write.fallback[i] = static_cast<f32>(uniform.initializer_value.as_int[i]);
}
}
}
write.args = {0.0f, 1.0f, 1.0f, 0.0f};
for (const auto& annotation : uniform.annotations) {
if (annotation.name == "min" && annotation.type.is_floating_point()) {
write.args[0] = annotation.value.as_float[0];
}
if (annotation.name == "max" && annotation.type.is_floating_point()) {
write.args[1] = annotation.value.as_float[0];
}
if (annotation.name == "step" && annotation.type.is_floating_point()) {
write.args[2] = annotation.value.as_float[0];
}
}
write.state = write.args[0];
effect.uniforms.push_back(std::move(write));
}
effect.uniform_layout = CreateWrappedDescriptorSetLayout(
device, std::array{VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER},
VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT);
for (const auto& pass : technique->passes) {
Pass out;
out.num_vertices = pass.num_vertices;
out.clear = pass.clear_render_targets != 0;
out.target_texture = NO_TEXTURE;
out.extent = m_extent;
const std::string& target = pass.render_target_names[0];
if (target.empty()) {
out.writes_backbuffer = true;
} else {
for (size_t i = 0; i < effect.textures.size(); ++i) {
if (effect.textures[i].name == target) {
out.target_texture = i;
out.extent = effect.textures[i].extent;
break;
}
}
if (out.target_texture == NO_TEXTURE) {
LOG_WARNING(Render_Vulkan, "Effect '{}' pass targets unknown texture '{}'",
entry.file, target);
out.writes_backbuffer = true;
}
}
if (pass.viewport_width != 0 && pass.viewport_height != 0) {
out.extent = VkExtent2D{pass.viewport_width, pass.viewport_height};
}
VkFormat target_format = BACKBUFFER_FORMAT;
if (!out.writes_backbuffer) {
target_format = effect.textures[out.target_texture].format;
}
u32 binding_count = 0;
for (const auto& binding : pass.sampler_bindings) {
SamplerBinding entry_binding;
entry_binding.binding = binding.entry_point_binding;
entry_binding.sampler_index = binding.index;
out.sampler_bindings.push_back(entry_binding);
binding_count = std::max(binding_count, binding.entry_point_binding + 1);
}
const std::vector<VkDescriptorType> sampler_types(
std::max<size_t>(binding_count, 1), VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER);
out.sampler_layout = CreateWrappedDescriptorSetLayout(
device, sampler_types, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT);
const std::array set_layouts{*effect.uniform_layout, *out.sampler_layout};
out.pipeline_layout =
device.GetLogical().CreatePipelineLayout(VkPipelineLayoutCreateInfo{
.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.setLayoutCount = static_cast<u32>(set_layouts.size()),
.pSetLayouts = set_layouts.data(),
.pushConstantRangeCount = 0,
.pPushConstantRanges = nullptr,
});
const auto vertex_shader = effect.shaders.find(pass.vs_entry_point);
const auto fragment_shader = effect.shaders.find(pass.ps_entry_point);
if (vertex_shader == effect.shaders.end() ||
fragment_shader == effect.shaders.end()) {
LOG_WARNING(Render_Vulkan, "Effect '{}' pass references a missing entry point",
entry.file);
continue;
}
out.renderpass = CreateFxRenderPass(device, target_format, out.clear);
out.pipeline = CreateFxPipeline(device, out.renderpass, out.pipeline_layout,
*vertex_shader->second, *fragment_shader->second, pass);
if (out.writes_backbuffer) {
out.backbuffer_slot = static_cast<u32>(effect.backbuffer_pass_count % 2);
++effect.backbuffer_pass_count;
for (u32 image = 0; image < m_image_count; ++image) {
for (size_t slot = 0; slot < 2; ++slot) {
out.framebuffers.push_back(CreateWrappedFramebuffer(
device, out.renderpass, m_frames[image].views[slot], out.extent));
}
}
} else {
out.framebuffers.push_back(
CreateWrappedFramebuffer(device, out.renderpass,
effect.textures[out.target_texture].view, out.extent));
}
effect.passes.push_back(std::move(out));
}
if (effect.passes.empty()) {
LOG_WARNING(Render_Vulkan, "Effect '{}' technique '{}' has no passes", entry.file,
entry.technique);
continue;
}
const u32 buffer_size = std::max<u32>(effect.uniform_size, 4);
for (u32 i = 0; i < m_image_count; ++i) {
effect.uniform_buffers.push_back(
CreateWrappedBuffer(allocator, buffer_size, MemoryUsage::Upload));
}
size_t sampler_descriptor_count = 0;
size_t sampler_set_count = 0;
for (const auto& pass : effect.passes) {
sampler_descriptor_count +=
m_image_count * std::max<size_t>(pass.sampler_bindings.size(), 1);
sampler_set_count += m_image_count;
}
effect.descriptor_pool = CreateWrappedDescriptorPool(
device, m_image_count + sampler_descriptor_count, m_image_count + sampler_set_count,
{VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER});
const std::vector<VkDescriptorSetLayout> uniform_layouts(m_image_count,
*effect.uniform_layout);
effect.uniform_sets = CreateWrappedDescriptorSets(effect.descriptor_pool, uniform_layouts);
for (auto& pass : effect.passes) {
const std::vector<VkDescriptorSetLayout> layouts(m_image_count, *pass.sampler_layout);
pass.sampler_sets = CreateWrappedDescriptorSets(effect.descriptor_pool, layouts);
}
m_effects.push_back(std::move(effect));
}
return true;
}
void PostProcessChain::PrepareImages(const Device& device, Scheduler& scheduler) {
if (m_images_ready) {
return;
}
scheduler.Record([this](vk::CommandBuffer cmdbuf) {
ClearColorImage(cmdbuf, *m_fallback_image);
for (auto& frame : m_frames) {
for (auto& image : frame.images) {
ClearColorImage(cmdbuf, *image);
}
}
for (auto& effect : m_effects) {
for (auto& texture : effect.textures) {
if (texture.image) {
ClearColorImage(cmdbuf, *texture.image);
}
}
}
});
scheduler.Finish();
m_images_ready = true;
}
void PostProcessChain::UpdateUniforms(Effect& effect, size_t image_index, f32 delta_seconds) {
if (effect.uniform_size == 0) {
return;
}
static thread_local std::mt19937 rng{std::random_device{}()};
std::vector<u8> staging(effect.uniform_size, 0);
const f32 elapsed =
std::chrono::duration<f32>(std::chrono::steady_clock::now() - m_start).count();
const auto overrides = VideoCore::FxChain::Instance().EntryValues(effect.entry_index);
for (auto& uniform : effect.uniforms) {
std::array<f32, 4> value = uniform.fallback;
const auto override = overrides.find(uniform.name);
if (override != overrides.end()) {
value = override->second;
}
switch (uniform.source) {
case UniformSource::FrameTime:
value[0] = delta_seconds * 1000.0f;
break;
case UniformSource::FrameCount:
value[0] = static_cast<f32>(m_frame_count);
break;
case UniformSource::Timer:
value[0] = elapsed * 1000.0f;
break;
case UniformSource::Random: {
const int low = static_cast<int>(uniform.args[0]);
int high = static_cast<int>(uniform.args[1]);
if (high <= low) {
high = low + 1;
}
std::uniform_int_distribution<int> dist(low, high);
value[0] = static_cast<f32>(dist(rng));
break;
}
case UniformSource::PingPong: {
const f32 min_value = uniform.args[0];
f32 max_value = uniform.args[1];
if (max_value <= min_value) {
max_value = min_value + 1.0f;
}
f32 step = uniform.args[2];
if (step == 0.0f) {
step = 1.0f;
}
uniform.state += uniform.direction * step * delta_seconds;
if (uniform.state >= max_value) {
uniform.state = max_value;
uniform.direction = -1.0f;
}
if (uniform.state <= min_value) {
uniform.state = min_value;
uniform.direction = 1.0f;
}
value[0] = uniform.state;
value[1] = uniform.direction;
break;
}
case UniformSource::Value:
default:
break;
}
for (u32 i = 0; i < uniform.components; ++i) {
const size_t offset = uniform.offset + i * sizeof(u32);
if (offset + sizeof(u32) > staging.size()) {
break;
}
if (uniform.kind == UniformKind::Floating) {
const f32 element = value[i];
std::memcpy(staging.data() + offset, &element, sizeof(f32));
} else {
const s32 element = static_cast<s32>(value[i]);
std::memcpy(staging.data() + offset, &element, sizeof(s32));
}
}
}
const std::span<u8> mapped = effect.uniform_buffers[image_index].Mapped();
if (mapped.size() >= staging.size()) {
std::memcpy(mapped.data(), staging.data(), staging.size());
effect.uniform_buffers[image_index].Flush();
}
}
void PostProcessChain::UpdateDescriptors(const Device& device, Effect& effect, Pass& pass,
size_t image_index, VkImageView backbuffer_view) {
std::vector<VkDescriptorImageInfo> image_infos;
std::vector<VkWriteDescriptorSet> writes;
image_infos.reserve(pass.sampler_bindings.size() + 1);
const VkDescriptorSet sampler_set = pass.sampler_sets[image_index];
for (const auto& binding : pass.sampler_bindings) {
VkImageView view = *m_fallback_view;
VkSampler handle = *m_fallback_sampler;
if (binding.sampler_index < effect.samplers.size()) {
const Sampler& sampler = effect.samplers[binding.sampler_index];
if (sampler.sampler) {
handle = *sampler.sampler;
}
if (sampler.texture_index != NO_TEXTURE) {
const Texture& texture = effect.textures[sampler.texture_index];
if (texture.is_backbuffer) {
view = backbuffer_view;
} else if (texture.view) {
view = *texture.view;
}
}
}
writes.push_back(
CreateWriteDescriptorSet(image_infos, handle, view, sampler_set, binding.binding));
}
const VkDescriptorBufferInfo buffer_info{
.buffer = *effect.uniform_buffers[image_index],
.offset = 0,
.range = VK_WHOLE_SIZE,
};
writes.push_back(VkWriteDescriptorSet{
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
.pNext = nullptr,
.dstSet = effect.uniform_sets[image_index],
.dstBinding = 0,
.dstArrayElement = 0,
.descriptorCount = 1,
.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
.pImageInfo = nullptr,
.pBufferInfo = &buffer_info,
.pTexelBufferView = nullptr,
});
device.GetLogical().UpdateDescriptorSets(writes, {});
}
void PostProcessChain::Draw(const Device& device, Scheduler& scheduler, size_t image_index,
VkImage* inout_image, VkImageView* inout_image_view) {
if (m_effects.empty()) {
return;
}
PrepareImages(device, scheduler);
const auto now = std::chrono::steady_clock::now();
const f32 delta_seconds = std::chrono::duration<f32>(now - m_previous).count();
m_previous = now;
++m_frame_count;
FrameImages& frame = m_frames[image_index];
VkImage current_image = *inout_image;
VkImageView current_view = *inout_image_view;
u32 slot = 0;
for (auto& effect : m_effects) {
UpdateUniforms(effect, image_index, delta_seconds);
for (size_t pass_index = 0; pass_index < effect.passes.size(); ++pass_index) {
Pass& pass = effect.passes[pass_index];
UpdateDescriptors(device, effect, pass, image_index, current_view);
VkFramebuffer framebuffer{};
VkImage target_image{};
if (pass.writes_backbuffer) {
const u32 target_slot = (slot + 1) % 2;
framebuffer = *pass.framebuffers[image_index * 2 + target_slot];
target_image = *frame.images[target_slot];
} else {
framebuffer = *pass.framebuffers[0];
target_image = *effect.textures[pass.target_texture].image;
}
const VkImage source_image = current_image;
const VkRenderPass renderpass = *pass.renderpass;
const VkPipeline pipeline = *pass.pipeline;
const VkPipelineLayout layout = *pass.pipeline_layout;
const VkDescriptorSet uniform_set = effect.uniform_sets[image_index];
const VkDescriptorSet sampler_set = pass.sampler_sets[image_index];
const VkExtent2D extent = pass.extent;
const u32 vertices = pass.num_vertices;
scheduler.RequestOutsideRenderPassOperationContext();
scheduler.Record([=](vk::CommandBuffer cmdbuf) {
TransitionImageLayout(cmdbuf, source_image, VK_IMAGE_LAYOUT_GENERAL);
TransitionImageLayout(cmdbuf, target_image, VK_IMAGE_LAYOUT_GENERAL);
BeginRenderPass(cmdbuf, renderpass, framebuffer, extent);
cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, layout, 0,
std::array{uniform_set, sampler_set}, {});
cmdbuf.Draw(vertices, 1, 0, 0);
cmdbuf.EndRenderPass();
TransitionImageLayout(cmdbuf, target_image, VK_IMAGE_LAYOUT_GENERAL);
});
if (pass.writes_backbuffer) {
slot = (slot + 1) % 2;
current_image = *frame.images[slot];
current_view = *frame.views[slot];
}
}
}
*inout_image = current_image;
*inout_image_view = current_view;
}
} // namespace Vulkan
@@ -1,140 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <array>
#include <chrono>
#include <map>
#include <string>
#include <vector>
#include "common/common_types.h"
#include "video_core/vulkan_common/vulkan_memory_allocator.h"
#include "video_core/vulkan_common/vulkan_wrapper.h"
namespace Vulkan {
class Device;
class Scheduler;
class PostProcessChain {
public:
explicit PostProcessChain(const Device& device, MemoryAllocator& allocator, Scheduler& scheduler,
size_t image_count, VkExtent2D extent);
~PostProcessChain();
void Draw(const Device& device, Scheduler& scheduler, size_t image_index, VkImage* inout_image,
VkImageView* inout_image_view);
bool Empty() const;
private:
enum class UniformKind : u32 {
Boolean,
Integer,
Floating,
};
enum class UniformSource : u32 {
Value,
FrameTime,
FrameCount,
Timer,
Random,
PingPong,
};
struct UniformWrite {
std::string name;
u32 offset{};
u32 components{};
UniformKind kind{UniformKind::Floating};
UniformSource source{UniformSource::Value};
std::array<f32, 4> fallback{};
std::array<f32, 4> args{};
f32 state{};
f32 direction{1.0f};
};
struct Texture {
std::string name;
vk::Image image{};
vk::ImageView view{};
VkExtent2D extent{};
VkFormat format{};
bool is_backbuffer{};
};
struct Sampler {
vk::Sampler sampler{};
size_t texture_index{};
};
struct SamplerBinding {
u32 binding{};
size_t sampler_index{};
};
struct Pass {
vk::RenderPass renderpass{};
vk::Pipeline pipeline{};
vk::DescriptorSetLayout sampler_layout{};
vk::PipelineLayout pipeline_layout{};
vk::DescriptorSets sampler_sets{};
std::vector<SamplerBinding> sampler_bindings{};
std::vector<vk::Framebuffer> framebuffers{};
size_t target_texture{};
VkExtent2D extent{};
u32 num_vertices{3};
bool clear{};
bool writes_backbuffer{};
u32 backbuffer_slot{};
};
struct Effect {
size_t entry_index{};
std::string file{};
std::map<std::string, vk::ShaderModule> shaders{};
std::vector<Texture> textures{};
std::vector<Sampler> samplers{};
std::vector<Pass> passes{};
std::vector<UniformWrite> uniforms{};
u32 uniform_size{};
std::vector<vk::Buffer> uniform_buffers{};
vk::DescriptorSetLayout uniform_layout{};
vk::DescriptorPool descriptor_pool{};
vk::DescriptorSets uniform_sets{};
size_t backbuffer_pass_count{};
u32 backbuffer_slots{1};
};
struct FrameImages {
std::array<vk::Image, 2> images{};
std::array<vk::ImageView, 2> views{};
};
bool BuildEffects(const Device& device, MemoryAllocator& allocator, Scheduler& scheduler);
void CreatePingPongImages(const Device& device, MemoryAllocator& allocator);
void PrepareImages(const Device& device, Scheduler& scheduler);
void UpdateUniforms(Effect& effect, size_t image_index, f32 delta_seconds);
void UpdateDescriptors(const Device& device, Effect& effect, Pass& pass, size_t image_index,
VkImageView backbuffer_view);
const VkExtent2D m_extent;
const u32 m_image_count;
std::vector<Effect> m_effects{};
std::vector<FrameImages> m_frames{};
vk::Sampler m_fallback_sampler{};
vk::Image m_fallback_image{};
vk::ImageView m_fallback_view{};
std::chrono::steady_clock::time_point m_start{};
std::chrono::steady_clock::time_point m_previous{};
u64 m_frame_count{};
bool m_images_ready{};
};
} // namespace Vulkan
@@ -24,9 +24,6 @@
#include "video_core/gpu.h"
#include "video_core/present.h"
#include "video_core/renderer_vulkan/present/util.h"
#ifdef HAS_RESHADE
#include "video_core/post_processing/fx_chain.h"
#endif
#include "video_core/renderer_vulkan/renderer_vulkan.h"
#include "video_core/renderer_vulkan/vk_blit_screen.h"
#include "video_core/renderer_vulkan/vk_rasterizer.h"
@@ -186,10 +183,6 @@ try
scheduler.RegisterOnSubmit([this] { turbo_mode->QueueSubmitted(); });
}
#ifdef HAS_RESHADE
VideoCore::FxChain::Instance().EnsureLoadedFromSettings();
#endif
Report();
} catch (const vk::Exception& exception) {
LOG_ERROR(Render_Vulkan, "Vulkan initialization failed with error: {}", exception.what());
@@ -338,7 +338,7 @@ void PresentManager::PresentThread(std::stop_token token) {
// By exchanging the lock ownership we take the swapchain lock
// before the queue lock goes out of scope. This way the swapchain
// lock in WaitPresent is guaranteed to occur after here.
std::exchange(lock, std::unique_lock{swapchain_mutex});
void(std::exchange(lock, std::unique_lock{swapchain_mutex}));
CopyToSwapchain(frame);
// Free the frame for reuse
@@ -300,7 +300,7 @@ void Scheduler::WorkerThread(std::stop_token stop_token) {
// Exchange lock ownership so that we take the execution lock before
// the queue lock goes out of scope. This allows us to force execution
// to complete in the next step.
std::exchange(lk, std::unique_lock{execution_mutex});
void(std::exchange(lk, std::unique_lock{execution_mutex}));
// Perform the work, tracking whether the chunk was a submission
// before executing.
@@ -26,350 +26,329 @@
#include "common/settings.h"
namespace Vulkan {
namespace {
namespace {
// Helpers translating MemoryUsage to flags/usage
[[maybe_unused]] VkMemoryPropertyFlags MemoryUsagePropertyFlags(MemoryUsage usage) {
switch (usage) {
case MemoryUsage::DeviceLocal:
return VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
case MemoryUsage::Upload:
return VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
case MemoryUsage::Download:
return VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT |
VK_MEMORY_PROPERTY_HOST_CACHED_BIT;
case MemoryUsage::Stream:
return VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT |
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
}
ASSERT_MSG(false, "Invalid memory usage={}", usage);
return VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
[[maybe_unused]] VkMemoryPropertyFlags MemoryUsagePropertyFlags(MemoryUsage usage) {
switch (usage) {
case MemoryUsage::DeviceLocal:
return VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
case MemoryUsage::Upload:
return VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
case MemoryUsage::Download:
return VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT |
VK_MEMORY_PROPERTY_HOST_CACHED_BIT;
case MemoryUsage::Stream:
return VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT |
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
}
ASSERT_MSG(false, "Invalid memory usage={}", usage);
return VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
}
[[nodiscard]] VkMemoryPropertyFlags MemoryUsagePreferredVmaFlags(MemoryUsage usage) {
if (usage == MemoryUsage::Download) {
return VK_MEMORY_PROPERTY_HOST_CACHED_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
}
return usage != MemoryUsage::DeviceLocal ? VK_MEMORY_PROPERTY_HOST_COHERENT_BIT
: VkMemoryPropertyFlagBits{};
[[nodiscard]] VkMemoryPropertyFlags MemoryUsagePreferredVmaFlags(MemoryUsage usage) {
if (usage == MemoryUsage::Download) {
return VK_MEMORY_PROPERTY_HOST_CACHED_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
}
return usage != MemoryUsage::DeviceLocal ? VK_MEMORY_PROPERTY_HOST_COHERENT_BIT
: VkMemoryPropertyFlagBits{};
}
[[nodiscard]] VmaAllocationCreateFlags MemoryUsageVmaFlags(MemoryUsage usage) {
switch (usage) {
case MemoryUsage::Upload:
case MemoryUsage::Stream:
return VMA_ALLOCATION_CREATE_MAPPED_BIT |
VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT;
case MemoryUsage::Download:
return VMA_ALLOCATION_CREATE_MAPPED_BIT |
VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT;
case MemoryUsage::DeviceLocal:
return {};
}
return {};
[[nodiscard]] VmaAllocationCreateFlags MemoryUsageVmaFlags(MemoryUsage usage) {
switch (usage) {
case MemoryUsage::Upload:
case MemoryUsage::Stream:
return VMA_ALLOCATION_CREATE_MAPPED_BIT |
VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT;
case MemoryUsage::Download:
return VMA_ALLOCATION_CREATE_MAPPED_BIT |
VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT;
case MemoryUsage::DeviceLocal:
return {};
}
return {};
}
[[nodiscard]] VmaMemoryUsage MemoryUsageVma(MemoryUsage usage) {
switch (usage) {
case MemoryUsage::DeviceLocal:
case MemoryUsage::Stream:
return VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
case MemoryUsage::Upload:
case MemoryUsage::Download:
return VMA_MEMORY_USAGE_AUTO_PREFER_HOST;
}
return VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
[[nodiscard]] VmaMemoryUsage MemoryUsageVma(MemoryUsage usage) {
switch (usage) {
case MemoryUsage::DeviceLocal:
case MemoryUsage::Stream:
return VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
case MemoryUsage::Upload:
case MemoryUsage::Download:
return VMA_MEMORY_USAGE_AUTO_PREFER_HOST;
}
// This avoids calling vkGetBufferMemoryRequirements* directly.
template<typename T>
static VkBuffer GetVkHandleFromBuffer(const T &buf) {
if constexpr (requires { static_cast<VkBuffer>(buf); }) {
return static_cast<VkBuffer>(buf);
} else if constexpr (requires {{ buf.GetHandle() } -> std::convertible_to<VkBuffer>; }) {
return buf.GetHandle();
} else if constexpr (requires {{ buf.Handle() } -> std::convertible_to<VkBuffer>; }) {
return buf.Handle();
} else if constexpr (requires {{ buf.vk_handle() } -> std::convertible_to<VkBuffer>; }) {
return buf.vk_handle();
} else {
static_assert(sizeof(T) == 0, "Cannot extract VkBuffer handle from vk::Buffer");
return VK_NULL_HANDLE;
}
}
} // namespace
return VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
}
} // namespace
//MemoryCommit is now VMA-backed
MemoryCommit::MemoryCommit(VmaAllocator alloc, VmaAllocation a,
const VmaAllocationInfo &info) noexcept
: allocator{alloc}, allocation{a}, memory{info.deviceMemory},
offset{info.offset}, size{info.size}, mapped_ptr{info.pMappedData} {
// Log GPU memory allocation
MemoryCommit::MemoryCommit(VmaAllocator alloc, VmaAllocation a,
const VmaAllocationInfo &info) noexcept
: allocator{alloc}, allocation{a}, memory{info.deviceMemory},
offset{info.offset}, size{info.size}, mapped_ptr{info.pMappedData} {
// Log GPU memory allocation
if (GPU::Logging::IsActive() &&
Settings::values.gpu_log_memory_tracking.GetValue()) {
GPU::Logging::GPULogger::GetInstance().LogMemoryAllocation(
reinterpret_cast<uintptr_t>(memory),
static_cast<u64>(size),
0 // Memory property flags (not easily available from VMA)
);
}
}
MemoryCommit::~MemoryCommit() { Release(); }
MemoryCommit::MemoryCommit(MemoryCommit &&rhs) noexcept
: allocator{std::exchange(rhs.allocator, nullptr)},
allocation{std::exchange(rhs.allocation, nullptr)},
memory{std::exchange(rhs.memory, VK_NULL_HANDLE)},
offset{std::exchange(rhs.offset, 0)},
size{std::exchange(rhs.size, 0)},
mapped_ptr{std::exchange(rhs.mapped_ptr, nullptr)} {}
MemoryCommit &MemoryCommit::operator=(MemoryCommit &&rhs) noexcept {
if (this != &rhs) {
Release();
allocator = std::exchange(rhs.allocator, nullptr);
allocation = std::exchange(rhs.allocation, nullptr);
memory = std::exchange(rhs.memory, VK_NULL_HANDLE);
offset = std::exchange(rhs.offset, 0);
size = std::exchange(rhs.size, 0);
mapped_ptr = std::exchange(rhs.mapped_ptr, nullptr);
}
return *this;
}
std::span<u8> MemoryCommit::Map()
{
if (!allocation) return {};
if (!mapped_ptr) {
if (vmaMapMemory(allocator, allocation, &mapped_ptr) != VK_SUCCESS) return {};
}
const size_t n = static_cast<size_t>(std::min<VkDeviceSize>(size,
(std::numeric_limits<size_t>::max)()));
return std::span<u8>{static_cast<u8 *>(mapped_ptr), n};
}
std::span<const u8> MemoryCommit::Map() const
{
if (!allocation) return {};
if (!mapped_ptr) {
void *p = nullptr;
if (vmaMapMemory(allocator, allocation, &p) != VK_SUCCESS) return {};
const_cast<MemoryCommit *>(this)->mapped_ptr = p;
}
const size_t n = static_cast<size_t>(std::min<VkDeviceSize>(size,
(std::numeric_limits<size_t>::max)()));
return std::span<const u8>{static_cast<const u8 *>(mapped_ptr), n};
}
void MemoryCommit::Unmap()
{
if (allocation && mapped_ptr) {
vmaUnmapMemory(allocator, allocation);
mapped_ptr = nullptr;
}
}
void MemoryCommit::Release() {
if (allocation && allocator) {
// Log GPU memory deallocation
if (GPU::Logging::IsActive() &&
Settings::values.gpu_log_memory_tracking.GetValue()) {
GPU::Logging::GPULogger::GetInstance().LogMemoryAllocation(
reinterpret_cast<uintptr_t>(memory),
static_cast<u64>(size),
0 // Memory property flags (not easily available from VMA)
Settings::values.gpu_log_memory_tracking.GetValue() &&
memory != VK_NULL_HANDLE) {
GPU::Logging::GPULogger::GetInstance().LogMemoryDeallocation(
reinterpret_cast<uintptr_t>(memory)
);
}
}
MemoryCommit::~MemoryCommit() { Release(); }
MemoryCommit::MemoryCommit(MemoryCommit &&rhs) noexcept
: allocator{std::exchange(rhs.allocator, nullptr)},
allocation{std::exchange(rhs.allocation, nullptr)},
memory{std::exchange(rhs.memory, VK_NULL_HANDLE)},
offset{std::exchange(rhs.offset, 0)},
size{std::exchange(rhs.size, 0)},
mapped_ptr{std::exchange(rhs.mapped_ptr, nullptr)} {}
MemoryCommit &MemoryCommit::operator=(MemoryCommit &&rhs) noexcept {
if (this != &rhs) {
Release();
allocator = std::exchange(rhs.allocator, nullptr);
allocation = std::exchange(rhs.allocation, nullptr);
memory = std::exchange(rhs.memory, VK_NULL_HANDLE);
offset = std::exchange(rhs.offset, 0);
size = std::exchange(rhs.size, 0);
mapped_ptr = std::exchange(rhs.mapped_ptr, nullptr);
}
return *this;
}
std::span<u8> MemoryCommit::Map()
{
if (!allocation) return {};
if (!mapped_ptr) {
if (vmaMapMemory(allocator, allocation, &mapped_ptr) != VK_SUCCESS) return {};
}
const size_t n = static_cast<size_t>(std::min<VkDeviceSize>(size,
(std::numeric_limits<size_t>::max)()));
return std::span<u8>{static_cast<u8 *>(mapped_ptr), n};
}
std::span<const u8> MemoryCommit::Map() const
{
if (!allocation) return {};
if (!mapped_ptr) {
void *p = nullptr;
if (vmaMapMemory(allocator, allocation, &p) != VK_SUCCESS) return {};
const_cast<MemoryCommit *>(this)->mapped_ptr = p;
}
const size_t n = static_cast<size_t>(std::min<VkDeviceSize>(size,
(std::numeric_limits<size_t>::max)()));
return std::span<const u8>{static_cast<const u8 *>(mapped_ptr), n};
}
void MemoryCommit::Unmap()
{
if (allocation && mapped_ptr) {
if (mapped_ptr) {
vmaUnmapMemory(allocator, allocation);
mapped_ptr = nullptr;
}
vmaFreeMemory(allocator, allocation);
}
allocation = nullptr;
allocator = nullptr;
memory = VK_NULL_HANDLE;
offset = 0;
size = 0;
}
void MemoryCommit::Release() {
if (allocation && allocator) {
// Log GPU memory deallocation
if (GPU::Logging::IsActive() &&
Settings::values.gpu_log_memory_tracking.GetValue() &&
memory != VK_NULL_HANDLE) {
GPU::Logging::GPULogger::GetInstance().LogMemoryDeallocation(
reinterpret_cast<uintptr_t>(memory)
);
}
MemoryAllocator::MemoryAllocator(const Device &device_)
: device{device_}, allocator{device.GetAllocator()},
properties{device_.GetPhysical().GetMemoryProperties().memoryProperties},
buffer_image_granularity{
device_.GetPhysical().GetProperties().limits.bufferImageGranularity} {
if (mapped_ptr) {
vmaUnmapMemory(allocator, allocation);
mapped_ptr = nullptr;
}
vmaFreeMemory(allocator, allocation);
}
allocation = nullptr;
allocator = nullptr;
memory = VK_NULL_HANDLE;
offset = 0;
size = 0;
}
MemoryAllocator::MemoryAllocator(const Device &device_)
: device{device_}, allocator{device.GetAllocator()},
properties{device_.GetPhysical().GetMemoryProperties().memoryProperties},
buffer_image_granularity{
device_.GetPhysical().GetProperties().limits.bufferImageGranularity} {
// Preserve the previous "RenderDoc small heap" trimming behavior that we had in original vma minus the heap bug
if (device.HasDebuggingToolAttached())
{
using namespace Common::Literals;
ForEachDeviceLocalHostVisibleHeap(device, [this](size_t heap_idx, VkMemoryHeap &heap) {
if (heap.size <= 256_MiB) {
for (u32 t = 0; t < properties.memoryTypeCount; ++t) {
if (properties.memoryTypes[t].heapIndex == heap_idx) {
valid_memory_types &= ~(1u << t);
}
// Preserve the previous "RenderDoc small heap" trimming behavior that we had in original vma minus the heap bug
if (device.HasDebuggingToolAttached())
{
using namespace Common::Literals;
ForEachDeviceLocalHostVisibleHeap(device, [this](size_t heap_idx, VkMemoryHeap &heap) {
if (heap.size <= 256_MiB) {
for (u32 t = 0; t < properties.memoryTypeCount; ++t) {
if (properties.memoryTypes[t].heapIndex == heap_idx) {
valid_memory_types &= ~(1u << t);
}
}
});
}
}
});
}
}
MemoryAllocator::~MemoryAllocator() = default;
MemoryAllocator::~MemoryAllocator() = default;
vk::Image MemoryAllocator::CreateImage(const VkImageCreateInfo &ci) const
{
const VmaAllocationCreateInfo alloc_ci = {
.flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT,
.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE,
.requiredFlags = 0,
.preferredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
.memoryTypeBits = 0,
.pool = VK_NULL_HANDLE,
.pUserData = nullptr,
.priority = 0.f,
};
VkImage handle{};
VmaAllocation allocation{};
VmaAllocationInfo alloc_info{};
vk::Check(vmaCreateImage(allocator, &ci, &alloc_ci, &handle, &allocation, &alloc_info));
// Log GPU memory allocation for images
if (GPU::Logging::IsActive() &&
Settings::values.gpu_log_memory_tracking.GetValue()) {
GPU::Logging::GPULogger::GetInstance().LogMemoryAllocation(
reinterpret_cast<uintptr_t>(alloc_info.deviceMemory),
static_cast<u64>(alloc_info.size),
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT
);
}
return vk::Image(handle, ci.usage, *device.GetLogical(), allocator, allocation,
device.GetDispatchLoader());
}
vk::Buffer MemoryAllocator::CreateBuffer(const VkBufferCreateInfo &ci, MemoryUsage usage) const {
// MESA will do memcpy() if not marked as host cached, so just force mark it for most buffers
auto const anv_flags = (usage == MemoryUsage::Stream
&& device.GetDriverID() == VK_DRIVER_ID_INTEL_OPEN_SOURCE_MESA)
? VK_MEMORY_PROPERTY_HOST_CACHED_BIT : 0;
const VmaAllocationCreateInfo alloc_ci = {
.flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT | MemoryUsageVmaFlags(usage),
.usage = MemoryUsageVma(usage),
vk::Image MemoryAllocator::CreateImage(const VkImageCreateInfo &ci) const
{
const VmaAllocationCreateInfo alloc_ci = {
.flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT,
.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE,
.requiredFlags = 0,
.preferredFlags = MemoryUsagePreferredVmaFlags(usage) | anv_flags,
.memoryTypeBits = usage == MemoryUsage::Stream ? 0u : valid_memory_types,
.preferredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
.memoryTypeBits = 0,
.pool = VK_NULL_HANDLE,
.pUserData = nullptr,
.priority = 0.f,
};
};
VkBuffer handle{};
VmaAllocationInfo alloc_info{};
VmaAllocation allocation{};
VkMemoryPropertyFlags property_flags{};
VkImage handle{};
VmaAllocation allocation{};
VmaAllocationInfo alloc_info{};
vk::Check(vmaCreateImage(allocator, &ci, &alloc_ci, &handle, &allocation, &alloc_info));
vk::Check(vmaCreateBuffer(allocator, &ci, &alloc_ci, &handle, &allocation, &alloc_info));
vmaGetAllocationMemoryProperties(allocator, allocation, &property_flags);
// Log GPU memory allocation for buffers
if (GPU::Logging::IsActive() &&
Settings::values.gpu_log_memory_tracking.GetValue()) {
GPU::Logging::GPULogger::GetInstance().LogMemoryAllocation(
reinterpret_cast<uintptr_t>(alloc_info.deviceMemory),
static_cast<u64>(alloc_info.size),
property_flags
);
}
u8 *data = reinterpret_cast<u8 *>(alloc_info.pMappedData);
const std::span<u8> mapped_data = data ? std::span<u8>{data, ci.size} : std::span<u8>{};
const bool is_coherent = (property_flags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) != 0;
return vk::Buffer(handle, *device.GetLogical(), allocator, allocation, mapped_data,
is_coherent,
device.GetDispatchLoader());
// Log GPU memory allocation for images
if (GPU::Logging::IsActive() &&
Settings::values.gpu_log_memory_tracking.GetValue()) {
GPU::Logging::GPULogger::GetInstance().LogMemoryAllocation(
reinterpret_cast<uintptr_t>(alloc_info.deviceMemory),
static_cast<u64>(alloc_info.size),
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT
);
}
MemoryCommit MemoryAllocator::Commit(const VkMemoryRequirements &reqs, MemoryUsage usage)
{
const auto vma_usage = MemoryUsageVma(usage);
VmaAllocationCreateInfo ci{};
ci.flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT | MemoryUsageVmaFlags(usage);
ci.usage = vma_usage;
ci.memoryTypeBits = reqs.memoryTypeBits & valid_memory_types;
ci.requiredFlags = 0;
ci.preferredFlags = MemoryUsagePreferredVmaFlags(usage);
return vk::Image(handle, ci.usage, *device.GetLogical(), allocator, allocation,
device.GetDispatchLoader());
}
VmaAllocation a{};
VmaAllocationInfo info{};
vk::Buffer MemoryAllocator::CreateBuffer(const VkBufferCreateInfo &ci, MemoryUsage usage) const {
// MESA will do memcpy() if not marked as host cached, so just force mark it for most buffers
auto const anv_flags = (usage == MemoryUsage::Stream
&& device.GetDriverID() == VK_DRIVER_ID_INTEL_OPEN_SOURCE_MESA)
? VK_MEMORY_PROPERTY_HOST_CACHED_BIT : 0;
const VmaAllocationCreateInfo alloc_ci = {
.flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT | MemoryUsageVmaFlags(usage),
.usage = MemoryUsageVma(usage),
.requiredFlags = 0,
.preferredFlags = MemoryUsagePreferredVmaFlags(usage) | anv_flags,
.memoryTypeBits = usage == MemoryUsage::Stream ? 0u : valid_memory_types,
.pool = VK_NULL_HANDLE,
.pUserData = nullptr,
.priority = 0.f,
};
VkResult res = vmaAllocateMemory(allocator, &reqs, &ci, &a, &info);
VkBuffer handle{};
VmaAllocationInfo alloc_info{};
VmaAllocation allocation{};
VkMemoryPropertyFlags property_flags{};
if (res != VK_SUCCESS) {
// Relax 1: drop budget constraint
auto ci2 = ci;
ci2.flags &= ~VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT;
res = vmaAllocateMemory(allocator, &reqs, &ci2, &a, &info);
vk::Check(vmaCreateBuffer(allocator, &ci, &alloc_ci, &handle, &allocation, &alloc_info));
vmaGetAllocationMemoryProperties(allocator, allocation, &property_flags);
// Relax 2: if we preferred DEVICE_LOCAL, drop that preference
if (res != VK_SUCCESS && (ci.preferredFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)) {
auto ci3 = ci2;
ci3.preferredFlags &= ~VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
res = vmaAllocateMemory(allocator, &reqs, &ci3, &a, &info);
}
}
vk::Check(res);
return MemoryCommit(allocator, a, info);
// Log GPU memory allocation for buffers
if (GPU::Logging::IsActive() &&
Settings::values.gpu_log_memory_tracking.GetValue()) {
GPU::Logging::GPULogger::GetInstance().LogMemoryAllocation(
reinterpret_cast<uintptr_t>(alloc_info.deviceMemory),
static_cast<u64>(alloc_info.size),
property_flags
);
}
MemoryCommit MemoryAllocator::Commit(const vk::Buffer &buffer, MemoryUsage usage) {
// Allocate memory appropriate for this buffer automatically
const auto vma_usage = MemoryUsageVma(usage);
u8 *data = reinterpret_cast<u8 *>(alloc_info.pMappedData);
const std::span<u8> mapped_data = data ? std::span<u8>{data, ci.size} : std::span<u8>{};
const bool is_coherent = (property_flags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) != 0;
VmaAllocationCreateInfo ci{};
ci.flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT | MemoryUsageVmaFlags(usage);
ci.usage = vma_usage;
ci.requiredFlags = 0;
ci.preferredFlags = MemoryUsagePreferredVmaFlags(usage);
ci.pool = VK_NULL_HANDLE;
ci.pUserData = nullptr;
ci.priority = 0.0f;
return vk::Buffer(handle, *device.GetLogical(), allocator, allocation, mapped_data,
is_coherent,
device.GetDispatchLoader());
}
const VkBuffer raw = *buffer;
MemoryCommit MemoryAllocator::Commit(const VkMemoryRequirements &reqs, MemoryUsage usage)
{
const auto vma_usage = MemoryUsageVma(usage);
VmaAllocationCreateInfo ci{};
ci.flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT | MemoryUsageVmaFlags(usage);
ci.usage = vma_usage;
ci.memoryTypeBits = reqs.memoryTypeBits & valid_memory_types;
ci.requiredFlags = 0;
ci.preferredFlags = MemoryUsagePreferredVmaFlags(usage);
VmaAllocation a{};
VmaAllocationInfo info{};
VmaAllocation a{};
VmaAllocationInfo info{};
// Let VMA infer memory requirements from the buffer
VkResult res = vmaAllocateMemoryForBuffer(allocator, raw, &ci, &a, &info);
VkResult res = vmaAllocateMemory(allocator, &reqs, &ci, &a, &info);
if (res != VK_SUCCESS) {
auto ci2 = ci;
ci2.flags &= ~VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT;
res = vmaAllocateMemoryForBuffer(allocator, raw, &ci2, &a, &info);
if (res != VK_SUCCESS) {
// Relax 1: drop budget constraint
auto ci2 = ci;
ci2.flags &= ~VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT;
res = vmaAllocateMemory(allocator, &reqs, &ci2, &a, &info);
if (res != VK_SUCCESS && (ci.preferredFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)) {
auto ci3 = ci2;
ci3.preferredFlags &= ~VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
res = vmaAllocateMemoryForBuffer(allocator, raw, &ci3, &a, &info);
}
// Relax 2: if we preferred DEVICE_LOCAL, drop that preference
if (res != VK_SUCCESS && (ci.preferredFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)) {
auto ci3 = ci2;
ci3.preferredFlags &= ~VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
res = vmaAllocateMemory(allocator, &reqs, &ci3, &a, &info);
}
vk::Check(res);
vk::Check(vmaBindBufferMemory2(allocator, a, 0, raw, nullptr));
return MemoryCommit(allocator, a, info);
}
vk::Check(res);
return MemoryCommit(allocator, a, info);
}
MemoryCommit MemoryAllocator::Commit(const vk::Buffer &buffer, MemoryUsage usage) {
// Allocate memory appropriate for this buffer automatically
const auto vma_usage = MemoryUsageVma(usage);
VmaAllocationCreateInfo ci{};
ci.flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT | MemoryUsageVmaFlags(usage);
ci.usage = vma_usage;
ci.requiredFlags = 0;
ci.preferredFlags = MemoryUsagePreferredVmaFlags(usage);
ci.pool = VK_NULL_HANDLE;
ci.pUserData = nullptr;
ci.priority = 0.0f;
const VkBuffer raw = *buffer;
VmaAllocation a{};
VmaAllocationInfo info{};
// Let VMA infer memory requirements from the buffer
VkResult res = vmaAllocateMemoryForBuffer(allocator, raw, &ci, &a, &info);
if (res != VK_SUCCESS) {
auto ci2 = ci;
ci2.flags &= ~VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT;
res = vmaAllocateMemoryForBuffer(allocator, raw, &ci2, &a, &info);
if (res != VK_SUCCESS && (ci.preferredFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)) {
auto ci3 = ci2;
ci3.preferredFlags &= ~VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
res = vmaAllocateMemoryForBuffer(allocator, raw, &ci3, &a, &info);
}
}
vk::Check(res);
vk::Check(vmaBindBufferMemory2(allocator, a, 0, raw, nullptr));
return MemoryCommit(allocator, a, info);
}
} // namespace Vulkan
-6
View File
@@ -250,12 +250,6 @@ if (YUZU_CRASH_DUMPS)
target_compile_definitions(yuzu PRIVATE YUZU_CRASH_DUMPS)
endif()
if (ENABLE_RESHADE)
target_sources(yuzu PRIVATE
configuration/configure_post_processing.cpp
configuration/configure_post_processing.h)
endif()
if (CXX_CLANG)
target_compile_definitions(yuzu PRIVATE
$<$<VERSION_LESS:$<CXX_COMPILER_VERSION>,15>:CANNOT_EXPLICITLY_INSTANTIATE>)
@@ -1,379 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include <array>
#include <cmath>
#include <string>
#include <QCheckBox>
#include <QComboBox>
#include <QDesktopServices>
#include <QDialogButtonBox>
#include <QGridLayout>
#include <QGroupBox>
#include <QHBoxLayout>
#include <QLabel>
#include <QPushButton>
#include <QScrollArea>
#include <QSlider>
#include <QToolButton>
#include <QUrl>
#include <QVBoxLayout>
#include "common/fs/fs.h"
#include "common/fs/fs_util.h"
#include "video_core/post_processing/fx_chain.h"
#include "video_core/post_processing/fx_effect.h"
#include "yuzu/configuration/configure_post_processing.h"
namespace {
std::array<float, 4> CurrentValue(int index, const VideoCore::FxUniformDesc& uniform) {
auto& chain = VideoCore::FxChain::Instance();
if (chain.HasValue(static_cast<size_t>(index), uniform.name)) {
return chain.GetValue(static_cast<size_t>(index), uniform.name);
}
return uniform.default_value;
}
int SliderSteps(const VideoCore::FxUniformDesc& uniform) {
const float span = uniform.ui_max - uniform.ui_min;
const int steps = static_cast<int>(std::lround(span / uniform.ui_step));
if (steps < 1) {
return 1;
}
return steps;
}
QString FormatValue(const VideoCore::FxUniformDesc& uniform, float value) {
if (uniform.kind == VideoCore::FxUniformKind::Floating) {
return QString::number(value, 'f', 3);
}
return QString::number(static_cast<int>(std::lround(value)));
}
QString SlotLabel(const VideoCore::FxEffectDesc& effect, const std::string& technique) {
const QString name = QString::fromStdString(effect.name);
if (effect.techniques.size() == 1) {
return name;
}
return name + QStringLiteral(" · ") + QString::fromStdString(technique);
}
} // Anonymous namespace
ConfigurePostProcessing::ConfigurePostProcessing(QWidget* parent) : QDialog(parent) {
setWindowTitle(tr("Post-Processing Effects"));
setMinimumWidth(560);
setMinimumHeight(460);
auto* root = new QVBoxLayout(this);
auto* description = new QLabel(
tr("ReShade FX effects are loaded from the post_shaders folder in the Eden data "
"directory. Changes apply immediately while a game is running."),
this);
description->setWordWrap(true);
root->addWidget(description);
auto* scroll = new QScrollArea(this);
scroll->setWidgetResizable(true);
slots_container = new QWidget(scroll);
slots_layout = new QVBoxLayout(slots_container);
slots_layout->setAlignment(Qt::AlignTop);
scroll->setWidget(slots_container);
root->addWidget(scroll, 1);
auto* actions = new QHBoxLayout();
auto* add_button = new QPushButton(tr("Add Effect"), this);
connect(add_button, &QPushButton::clicked, this, [this]() {
for (const auto& effect : VideoCore::GetFxCatalog()) {
if (!effect.Valid()) {
continue;
}
VideoCore::FxChain::Instance().Append(effect.file, effect.techniques.front());
ApplyStructuralChange();
return;
}
});
actions->addWidget(add_button);
auto* reload_button = new QPushButton(tr("Reload From Disk"), this);
connect(reload_button, &QPushButton::clicked, this, [this]() {
VideoCore::ReloadFxCatalog();
VideoCore::FxChain::Instance().DropUnknownEntries();
ApplyStructuralChange();
});
actions->addWidget(reload_button);
auto* open_button = new QPushButton(tr("Open Folder"), this);
connect(open_button, &QPushButton::clicked, this, []() {
const auto path = VideoCore::GetFxRootDirectory();
void(Common::FS::CreateDirs(path));
QDesktopServices::openUrl(
QUrl::fromLocalFile(QString::fromStdString(Common::FS::PathToUTF8String(path))));
});
actions->addWidget(open_button);
actions->addStretch();
root->addLayout(actions);
auto* buttons = new QDialogButtonBox(QDialogButtonBox::Close, this);
connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::close);
root->addWidget(buttons);
VideoCore::ReloadFxCatalog();
VideoCore::FxChain::Instance().DropUnknownEntries();
RebuildRows();
}
ConfigurePostProcessing::~ConfigurePostProcessing() = default;
void ConfigurePostProcessing::ApplyStructuralChange() {
VideoCore::FxChain::Instance().StoreToSettings();
RebuildRows();
}
void ConfigurePostProcessing::PopulateEffectCombo(QComboBox* combo,
const VideoCore::FxChainEntry& entry) const {
combo->clear();
int selected = -1;
for (const auto& effect : VideoCore::GetFxCatalog()) {
if (!effect.Valid()) {
continue;
}
for (const auto& technique : effect.techniques) {
const QString key = QString::fromStdString(effect.file + "|" + technique);
combo->addItem(SlotLabel(effect, technique), key);
if (effect.file == entry.file && technique == entry.technique) {
selected = combo->count() - 1;
}
}
}
if (selected >= 0) {
combo->setCurrentIndex(selected);
}
}
void ConfigurePostProcessing::BuildUniformWidget(QWidget* parent, QVBoxLayout* layout, int index,
const VideoCore::FxUniformDesc& uniform) {
const auto value = CurrentValue(index, uniform);
const QString label = QString::fromStdString(uniform.label);
if (uniform.ui_type == VideoCore::FxUiType::CheckBox) {
auto* box = new QCheckBox(label, parent);
box->setChecked(value[0] != 0.0f);
if (!uniform.tooltip.empty()) {
box->setToolTip(QString::fromStdString(uniform.tooltip));
}
const std::string name = uniform.name;
connect(box, &QCheckBox::toggled, this, [index, name](bool checked) {
std::array<float, 4> next{};
if (checked) {
next[0] = 1.0f;
}
VideoCore::FxChain::Instance().SetValue(static_cast<size_t>(index), name, next);
VideoCore::FxChain::Instance().StoreToSettings();
});
layout->addWidget(box);
return;
}
if (uniform.ui_type == VideoCore::FxUiType::Combo ||
uniform.ui_type == VideoCore::FxUiType::Radio) {
auto* row = new QHBoxLayout();
row->addWidget(new QLabel(label, parent));
auto* combo = new QComboBox(parent);
for (size_t i = 0; i < uniform.items.size(); ++i) {
combo->addItem(QString::fromStdString(uniform.items[i]), static_cast<int>(i));
}
if (combo->count() == 0) {
combo->addItem(tr("Enabled"), 1);
combo->addItem(tr("Disabled"), 0);
}
const int current = static_cast<int>(std::lround(value[0]));
if (current >= 0 && current < combo->count()) {
combo->setCurrentIndex(current);
}
if (!uniform.tooltip.empty()) {
combo->setToolTip(QString::fromStdString(uniform.tooltip));
}
const std::string name = uniform.name;
connect(combo, &QComboBox::currentIndexChanged, this, [index, name](int selected) {
std::array<float, 4> next{};
next[0] = static_cast<float>(selected);
VideoCore::FxChain::Instance().SetValue(static_cast<size_t>(index), name, next);
VideoCore::FxChain::Instance().StoreToSettings();
});
row->addWidget(combo, 1);
layout->addLayout(row);
return;
}
auto* grid = new QGridLayout();
for (unsigned component = 0; component < uniform.components; ++component) {
QString component_label = label;
if (uniform.components > 1) {
component_label = label + QStringLiteral(" [%1]").arg(component);
}
auto* name_label = new QLabel(component_label, parent);
auto* value_label = new QLabel(parent);
value_label->setMinimumWidth(64);
value_label->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
value_label->setText(FormatValue(uniform, value[component]));
auto* slider = new QSlider(Qt::Horizontal, parent);
slider->setMinimum(0);
slider->setMaximum(SliderSteps(uniform));
slider->setValue(
static_cast<int>(std::lround((value[component] - uniform.ui_min) / uniform.ui_step)));
if (!uniform.tooltip.empty()) {
slider->setToolTip(QString::fromStdString(uniform.tooltip));
}
const std::string name = uniform.name;
const auto desc = uniform;
connect(slider, &QSlider::valueChanged, this,
[index, name, desc, component, value_label](int steps) {
auto next = CurrentValue(index, desc);
next[component] = desc.ui_min + static_cast<float>(steps) * desc.ui_step;
VideoCore::FxChain::Instance().SetValue(static_cast<size_t>(index), name, next);
VideoCore::FxChain::Instance().StoreToSettings();
value_label->setText(FormatValue(desc, next[component]));
});
grid->addWidget(name_label, static_cast<int>(component), 0);
grid->addWidget(slider, static_cast<int>(component), 1);
grid->addWidget(value_label, static_cast<int>(component), 2);
}
layout->addLayout(grid);
}
QWidget* ConfigurePostProcessing::BuildSlot(int index, const VideoCore::FxChainEntry& entry) {
auto* group = new QGroupBox(slots_container);
auto* layout = new QVBoxLayout(group);
auto* header = new QHBoxLayout();
auto* combo = new QComboBox(group);
PopulateEffectCombo(combo, entry);
connect(combo, &QComboBox::currentIndexChanged, this, [this, index, combo](int) {
const QString key = combo->currentData().toString();
const qsizetype separator = key.indexOf(QLatin1Char('|'));
if (separator < 0) {
return;
}
VideoCore::FxChain::Instance().Replace(static_cast<size_t>(index),
key.left(separator).toStdString(),
key.mid(separator + 1).toStdString());
ApplyStructuralChange();
});
header->addWidget(combo, 1);
auto* up_button = new QToolButton(group);
up_button->setText(QStringLiteral(""));
up_button->setEnabled(index > 0);
connect(up_button, &QToolButton::clicked, this, [this, index]() {
VideoCore::FxChain::Instance().Move(static_cast<size_t>(index), -1);
ApplyStructuralChange();
});
header->addWidget(up_button);
auto* down_button = new QToolButton(group);
down_button->setText(QStringLiteral(""));
down_button->setEnabled(static_cast<size_t>(index) + 1 < VideoCore::FxChain::Instance().Size());
connect(down_button, &QToolButton::clicked, this, [this, index]() {
VideoCore::FxChain::Instance().Move(static_cast<size_t>(index), 1);
ApplyStructuralChange();
});
header->addWidget(down_button);
auto* reset_button = new QToolButton(group);
reset_button->setText(QStringLiteral(""));
reset_button->setToolTip(tr("Reset to defaults"));
connect(reset_button, &QToolButton::clicked, this, [this, index]() {
VideoCore::FxChain::Instance().ResetValues(static_cast<size_t>(index));
ApplyStructuralChange();
});
header->addWidget(reset_button);
auto* remove_button = new QToolButton(group);
remove_button->setText(QStringLiteral(""));
connect(remove_button, &QToolButton::clicked, this, [this, index]() {
VideoCore::FxChain::Instance().Remove(static_cast<size_t>(index));
ApplyStructuralChange();
});
header->addWidget(remove_button);
layout->addLayout(header);
const VideoCore::FxEffectDesc* effect = VideoCore::FindFxEffect(entry.file);
if (effect == nullptr) {
auto* missing =
new QLabel(tr("Effect '%1' was not found.").arg(QString::fromStdString(entry.file)),
group);
missing->setWordWrap(true);
layout->addWidget(missing);
return group;
}
if (!effect->error.empty()) {
auto* failed = new QLabel(
tr("Effect failed to compile:\n%1").arg(QString::fromStdString(effect->error)), group);
failed->setWordWrap(true);
layout->addWidget(failed);
return group;
}
std::string current_category;
for (const auto& uniform : effect->uniforms) {
if (uniform.category != current_category) {
current_category = uniform.category;
if (!current_category.empty()) {
auto* category = new QLabel(QString::fromStdString(current_category), group);
category->setStyleSheet(QStringLiteral("font-weight: bold;"));
layout->addWidget(category);
}
}
BuildUniformWidget(group, layout, index, uniform);
}
return group;
}
void ConfigurePostProcessing::RebuildRows() {
QLayoutItem* item = nullptr;
while ((item = slots_layout->takeAt(0)) != nullptr) {
if (item->widget() != nullptr) {
item->widget()->deleteLater();
}
delete item;
}
bool has_usable = false;
for (const auto& effect : VideoCore::GetFxCatalog()) {
if (effect.Valid()) {
has_usable = true;
break;
}
}
if (!has_usable) {
auto* empty = new QLabel(
tr("No usable ReShade FX effects were found. Place .fx files in the post_shaders "
"folder."),
slots_container);
empty->setWordWrap(true);
slots_layout->addWidget(empty);
return;
}
const auto entries = VideoCore::FxChain::Instance().Entries();
for (size_t i = 0; i < entries.size(); ++i) {
slots_layout->addWidget(BuildSlot(static_cast<int>(i), entries[i]));
}
}
@@ -1,35 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <QDialog>
class QComboBox;
class QVBoxLayout;
class QWidget;
namespace VideoCore {
struct FxChainEntry;
struct FxEffectDesc;
struct FxUniformDesc;
}
class ConfigurePostProcessing : public QDialog {
Q_OBJECT
public:
explicit ConfigurePostProcessing(QWidget* parent = nullptr);
~ConfigurePostProcessing() override;
private:
void RebuildRows();
QWidget* BuildSlot(int index, const VideoCore::FxChainEntry& entry);
void BuildUniformWidget(QWidget* parent, QVBoxLayout* layout, int index,
const VideoCore::FxUniformDesc& uniform);
void PopulateEffectCombo(QComboBox* combo, const VideoCore::FxChainEntry& entry) const;
void ApplyStructuralChange();
QVBoxLayout* slots_layout{};
QWidget* slots_container{};
};
-6
View File
@@ -148,7 +148,6 @@
<addaction name="action_Show_Filter_Bar"/>
<addaction name="action_Show_Status_Bar"/>
<addaction name="action_Show_Performance_Overlay"/>
<addaction name="action_Post_Processing_Shaders"/>
<addaction name="separator"/>
<addaction name="menu_Reset_Window_Size"/>
<addaction name="menu_View_Debugging"/>
@@ -611,11 +610,6 @@
<string>Show &amp;Performance Overlay</string>
</property>
</action>
<action name="action_Post_Processing_Shaders">
<property name="text">
<string>Post-Processing &amp;Shaders...</string>
</property>
</action>
<action name="action_Carousel_View">
<property name="checkable">
<bool>true</bool>
+1 -25
View File
@@ -13,9 +13,6 @@
#include "common/settings_enums.h"
#include "frontend_common/settings_generator.h"
#include "render/performance_overlay.h"
#ifdef HAS_RESHADE
#include "configuration/configure_post_processing.h"
#endif
#include "updater/update_dialog.h"
#include "common/fs/ryujinx_compat.h"
@@ -1521,11 +1518,6 @@ void MainWindow::ConnectMenuEvents() {
connect_menu(ui->action_Show_Filter_Bar, &MainWindow::OnToggleFilterBar);
connect_menu(ui->action_Show_Status_Bar, &MainWindow::OnToggleStatusBar);
connect_menu(ui->action_Show_Performance_Overlay, &MainWindow::OnTogglePerfOverlay);
#ifdef HAS_RESHADE
connect_menu(ui->action_Post_Processing_Shaders, &MainWindow::OnPostProcessingShaders);
#else
ui->action_Post_Processing_Shaders->setVisible(false);
#endif
connect_menu(ui->action_Reset_Window_Size_720, &MainWindow::ResetWindowSize720);
connect_menu(ui->action_Reset_Window_Size_900, &MainWindow::ResetWindowSize900);
@@ -3908,22 +3900,6 @@ void MainWindow::OnTogglePerfOverlay() {
perf_overlay->setVisible(ui->action_Show_Performance_Overlay->isChecked());
}
#ifdef HAS_RESHADE
void MainWindow::OnPostProcessingShaders() {
if (post_processing_dialog == nullptr) {
post_processing_dialog = new ConfigurePostProcessing(this);
connect(post_processing_dialog, &QDialog::finished, post_processing_dialog, [this]() {
post_processing_dialog->deleteLater();
post_processing_dialog = nullptr;
});
}
post_processing_dialog->show();
post_processing_dialog->raise();
post_processing_dialog->activateWindow();
}
#endif
void MainWindow::OnGameListRefresh() {
// Resets metadata cache and reloads
QtCommon::Game::ResetMetadata(false);
@@ -4820,6 +4796,6 @@ void VolumeButton::ResetMultiplier() {
#endif
#if !defined(QT_STATICPLUGIN) || defined(__APPLE__)
#define VMA_IMPLEMENTATION
#define VMA_IMPLEMENTATION 1
#include "video_core/vulkan_common/vma.h"
#endif
-9
View File
@@ -56,9 +56,6 @@ class QSlider;
class QHBoxLayout;
class WaitTreeWidget;
class PerformanceOverlay;
#ifdef HAS_RESHADE
class ConfigurePostProcessing;
#endif
enum class GameListOpenTarget;
enum class DumpRomFSTarget;
class GameListPlaceholder;
@@ -395,9 +392,6 @@ private slots:
void OnToggleFilterBar();
void OnToggleStatusBar();
void OnTogglePerfOverlay();
#ifdef HAS_RESHADE
void OnPostProcessingShaders();
#endif
void OnGameListRefresh();
void InitializeHotkeys();
void ToggleFullscreen();
@@ -502,9 +496,6 @@ private:
QTimer shutdown_timer;
OverlayDialog* shutdown_dialog{};
PerformanceOverlay* perf_overlay = nullptr;
#ifdef HAS_RESHADE
ConfigurePostProcessing* post_processing_dialog = nullptr;
#endif
GameListPlaceholder* game_list_placeholder = nullptr;
+1 -1
View File
@@ -65,7 +65,7 @@ else()
endif()
# update cached cpmfile content
string(JSON cpmfile SET "${cpmfile}" "${key}" "${new_object}")
string(JSON cpmfile SET "${cpmfile}" "${KEY}" "${new_object}")
# write cached cpmfile
get_cpmfile_path(file)