From f39e89ece79436f5058bb58d50a1a4dcd6823f4e Mon Sep 17 00:00:00 2001
From: ZenoArrows <129334871+ZenoArrows@users.noreply.github.com>
Date: Tue, 17 Sep 2024 20:30:50 +0200
Subject: [PATCH 01/15] Add area sampling scaler to allow for super-sampled
anti-aliasing. (#7304)
* Add area sampling scaler to allow for super-sampled anti-aliasing.
* Area scaling filter doesn't have a scaling level.
* Add further clarification to the tooltip on how to achieve supersampling.
* ShaderHelper: Merge the two CompileProgram functions.
* Convert tabs to spaces in area scaling shaders
* Fixup Vulkan and OpenGL project files.
* AreaScaling: Replace texture() by texelFetch() and use integer vectors.
No functional difference, but it cleans up the code a bit.
* AreaScaling: Delete unused sharpening level member.
Also rename _scale to _sharpeningLevel for clarity and consistency.
* AreaScaling: Delete unused scaleX/scaleY uniforms.
* AreaScaling: Force the alpha to 1 when storing the pixel.
* AreaScaling: Remove left-over sharpening buffer.
---
src/Ryujinx.Graphics.GAL/UpscaleType.cs | 1 +
.../Effects/AreaScalingFilter.cs | 106 +++++++++++++++
.../Effects/FsrScalingFilter.cs | 6 +-
.../Effects/ShaderHelper.cs | 23 ++--
.../Effects/Shaders/area_scaling.glsl | 119 +++++++++++++++++
.../Effects/Shaders/fsr_scaling.glsl | 2 +-
.../Ryujinx.Graphics.OpenGL.csproj | 1 +
src/Ryujinx.Graphics.OpenGL/Window.cs | 10 ++
.../Effects/AreaScalingFilter.cs | 101 +++++++++++++++
.../Effects/Shaders/AreaScaling.glsl | 122 ++++++++++++++++++
.../Effects/Shaders/AreaScaling.spv | Bin 0 -> 12428 bytes
.../Ryujinx.Graphics.Vulkan.csproj | 1 +
src/Ryujinx.Graphics.Vulkan/Window.cs | 7 +
src/Ryujinx/Assets/Locales/en_US.json | 3 +-
.../Views/Settings/SettingsGraphicsView.axaml | 5 +-
15 files changed, 489 insertions(+), 18 deletions(-)
create mode 100644 src/Ryujinx.Graphics.OpenGL/Effects/AreaScalingFilter.cs
create mode 100644 src/Ryujinx.Graphics.OpenGL/Effects/Shaders/area_scaling.glsl
create mode 100644 src/Ryujinx.Graphics.Vulkan/Effects/AreaScalingFilter.cs
create mode 100644 src/Ryujinx.Graphics.Vulkan/Effects/Shaders/AreaScaling.glsl
create mode 100644 src/Ryujinx.Graphics.Vulkan/Effects/Shaders/AreaScaling.spv
diff --git a/src/Ryujinx.Graphics.GAL/UpscaleType.cs b/src/Ryujinx.Graphics.GAL/UpscaleType.cs
index ca24199c43..e2482faef3 100644
--- a/src/Ryujinx.Graphics.GAL/UpscaleType.cs
+++ b/src/Ryujinx.Graphics.GAL/UpscaleType.cs
@@ -5,5 +5,6 @@ namespace Ryujinx.Graphics.GAL
Bilinear,
Nearest,
Fsr,
+ Area,
}
}
diff --git a/src/Ryujinx.Graphics.OpenGL/Effects/AreaScalingFilter.cs b/src/Ryujinx.Graphics.OpenGL/Effects/AreaScalingFilter.cs
new file mode 100644
index 0000000000..9b19f2f26d
--- /dev/null
+++ b/src/Ryujinx.Graphics.OpenGL/Effects/AreaScalingFilter.cs
@@ -0,0 +1,106 @@
+using OpenTK.Graphics.OpenGL;
+using Ryujinx.Common;
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.OpenGL.Image;
+using System;
+using static Ryujinx.Graphics.OpenGL.Effects.ShaderHelper;
+
+namespace Ryujinx.Graphics.OpenGL.Effects
+{
+ internal class AreaScalingFilter : IScalingFilter
+ {
+ private readonly OpenGLRenderer _renderer;
+ private int _inputUniform;
+ private int _outputUniform;
+ private int _srcX0Uniform;
+ private int _srcX1Uniform;
+ private int _srcY0Uniform;
+ private int _scalingShaderProgram;
+ private int _srcY1Uniform;
+ private int _dstX0Uniform;
+ private int _dstX1Uniform;
+ private int _dstY0Uniform;
+ private int _dstY1Uniform;
+
+ public float Level { get; set; }
+
+ public AreaScalingFilter(OpenGLRenderer renderer)
+ {
+ Initialize();
+
+ _renderer = renderer;
+ }
+
+ public void Dispose()
+ {
+ if (_scalingShaderProgram != 0)
+ {
+ GL.DeleteProgram(_scalingShaderProgram);
+ }
+ }
+
+ private void Initialize()
+ {
+ var scalingShader = EmbeddedResources.ReadAllText("Ryujinx.Graphics.OpenGL/Effects/Shaders/area_scaling.glsl");
+
+ _scalingShaderProgram = CompileProgram(scalingShader, ShaderType.ComputeShader);
+
+ _inputUniform = GL.GetUniformLocation(_scalingShaderProgram, "Source");
+ _outputUniform = GL.GetUniformLocation(_scalingShaderProgram, "imgOutput");
+
+ _srcX0Uniform = GL.GetUniformLocation(_scalingShaderProgram, "srcX0");
+ _srcX1Uniform = GL.GetUniformLocation(_scalingShaderProgram, "srcX1");
+ _srcY0Uniform = GL.GetUniformLocation(_scalingShaderProgram, "srcY0");
+ _srcY1Uniform = GL.GetUniformLocation(_scalingShaderProgram, "srcY1");
+ _dstX0Uniform = GL.GetUniformLocation(_scalingShaderProgram, "dstX0");
+ _dstX1Uniform = GL.GetUniformLocation(_scalingShaderProgram, "dstX1");
+ _dstY0Uniform = GL.GetUniformLocation(_scalingShaderProgram, "dstY0");
+ _dstY1Uniform = GL.GetUniformLocation(_scalingShaderProgram, "dstY1");
+ }
+
+ public void Run(
+ TextureView view,
+ TextureView destinationTexture,
+ int width,
+ int height,
+ Extents2D source,
+ Extents2D destination)
+ {
+ int previousProgram = GL.GetInteger(GetPName.CurrentProgram);
+ int previousUnit = GL.GetInteger(GetPName.ActiveTexture);
+ GL.ActiveTexture(TextureUnit.Texture0);
+ int previousTextureBinding = GL.GetInteger(GetPName.TextureBinding2D);
+
+ GL.BindImageTexture(0, destinationTexture.Handle, 0, false, 0, TextureAccess.ReadWrite, SizedInternalFormat.Rgba8);
+
+ int threadGroupWorkRegionDim = 16;
+ int dispatchX = (width + (threadGroupWorkRegionDim - 1)) / threadGroupWorkRegionDim;
+ int dispatchY = (height + (threadGroupWorkRegionDim - 1)) / threadGroupWorkRegionDim;
+
+ // Scaling pass
+ GL.UseProgram(_scalingShaderProgram);
+ view.Bind(0);
+ GL.Uniform1(_inputUniform, 0);
+ GL.Uniform1(_outputUniform, 0);
+ GL.Uniform1(_srcX0Uniform, (float)source.X1);
+ GL.Uniform1(_srcX1Uniform, (float)source.X2);
+ GL.Uniform1(_srcY0Uniform, (float)source.Y1);
+ GL.Uniform1(_srcY1Uniform, (float)source.Y2);
+ GL.Uniform1(_dstX0Uniform, (float)destination.X1);
+ GL.Uniform1(_dstX1Uniform, (float)destination.X2);
+ GL.Uniform1(_dstY0Uniform, (float)destination.Y1);
+ GL.Uniform1(_dstY1Uniform, (float)destination.Y2);
+ GL.DispatchCompute(dispatchX, dispatchY, 1);
+
+ GL.UseProgram(previousProgram);
+ GL.MemoryBarrier(MemoryBarrierFlags.ShaderImageAccessBarrierBit);
+
+ (_renderer.Pipeline as Pipeline).RestoreImages1And2();
+
+ GL.ActiveTexture(TextureUnit.Texture0);
+ GL.BindTexture(TextureTarget.Texture2D, previousTextureBinding);
+
+ GL.ActiveTexture((TextureUnit)previousUnit);
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.OpenGL/Effects/FsrScalingFilter.cs b/src/Ryujinx.Graphics.OpenGL/Effects/FsrScalingFilter.cs
index 1a130bebb3..0522e28e0e 100644
--- a/src/Ryujinx.Graphics.OpenGL/Effects/FsrScalingFilter.cs
+++ b/src/Ryujinx.Graphics.OpenGL/Effects/FsrScalingFilter.cs
@@ -18,7 +18,7 @@ namespace Ryujinx.Graphics.OpenGL.Effects
private int _srcY0Uniform;
private int _scalingShaderProgram;
private int _sharpeningShaderProgram;
- private float _scale = 1;
+ private float _sharpeningLevel = 1;
private int _srcY1Uniform;
private int _dstX0Uniform;
private int _dstX1Uniform;
@@ -30,10 +30,10 @@ namespace Ryujinx.Graphics.OpenGL.Effects
public float Level
{
- get => _scale;
+ get => _sharpeningLevel;
set
{
- _scale = MathF.Max(0.01f, value);
+ _sharpeningLevel = MathF.Max(0.01f, value);
}
}
diff --git a/src/Ryujinx.Graphics.OpenGL/Effects/ShaderHelper.cs b/src/Ryujinx.Graphics.OpenGL/Effects/ShaderHelper.cs
index c25fe5b258..637b2fba82 100644
--- a/src/Ryujinx.Graphics.OpenGL/Effects/ShaderHelper.cs
+++ b/src/Ryujinx.Graphics.OpenGL/Effects/ShaderHelper.cs
@@ -1,4 +1,5 @@
using OpenTK.Graphics.OpenGL;
+using Ryujinx.Common.Logging;
namespace Ryujinx.Graphics.OpenGL.Effects
{
@@ -6,18 +7,7 @@ namespace Ryujinx.Graphics.OpenGL.Effects
{
public static int CompileProgram(string shaderCode, ShaderType shaderType)
{
- var shader = GL.CreateShader(shaderType);
- GL.ShaderSource(shader, shaderCode);
- GL.CompileShader(shader);
-
- var program = GL.CreateProgram();
- GL.AttachShader(program, shader);
- GL.LinkProgram(program);
-
- GL.DetachShader(program, shader);
- GL.DeleteShader(shader);
-
- return program;
+ return CompileProgram(new string[] { shaderCode }, shaderType);
}
public static int CompileProgram(string[] shaders, ShaderType shaderType)
@@ -26,6 +16,15 @@ namespace Ryujinx.Graphics.OpenGL.Effects
GL.ShaderSource(shader, shaders.Length, shaders, (int[])null);
GL.CompileShader(shader);
+ GL.GetShader(shader, ShaderParameter.CompileStatus, out int isCompiled);
+ if (isCompiled == 0)
+ {
+ string log = GL.GetShaderInfoLog(shader);
+ Logger.Error?.Print(LogClass.Gpu, $"Failed to compile effect shader:\n\n{log}\n");
+ GL.DeleteShader(shader);
+ return 0;
+ }
+
var program = GL.CreateProgram();
GL.AttachShader(program, shader);
GL.LinkProgram(program);
diff --git a/src/Ryujinx.Graphics.OpenGL/Effects/Shaders/area_scaling.glsl b/src/Ryujinx.Graphics.OpenGL/Effects/Shaders/area_scaling.glsl
new file mode 100644
index 0000000000..0fe20d3f94
--- /dev/null
+++ b/src/Ryujinx.Graphics.OpenGL/Effects/Shaders/area_scaling.glsl
@@ -0,0 +1,119 @@
+#version 430 core
+precision mediump float;
+layout (local_size_x = 16, local_size_y = 16) in;
+layout(rgba8, binding = 0, location=0) uniform image2D imgOutput;
+layout( location=1 ) uniform sampler2D Source;
+layout( location=2 ) uniform float srcX0;
+layout( location=3 ) uniform float srcX1;
+layout( location=4 ) uniform float srcY0;
+layout( location=5 ) uniform float srcY1;
+layout( location=6 ) uniform float dstX0;
+layout( location=7 ) uniform float dstX1;
+layout( location=8 ) uniform float dstY0;
+layout( location=9 ) uniform float dstY1;
+
+/***** Area Sampling *****/
+
+// By Sam Belliveau and Filippo Tarpini. Public Domain license.
+// Effectively a more accurate sharp bilinear filter when upscaling,
+// that also works as a mathematically perfect downscale filter.
+// https://entropymine.com/imageworsener/pixelmixing/
+// https://github.com/obsproject/obs-studio/pull/1715
+// https://legacy.imagemagick.org/Usage/filter/
+vec4 AreaSampling(vec2 xy)
+{
+ // Determine the sizes of the source and target images.
+ vec2 source_size = vec2(abs(srcX1 - srcX0), abs(srcY1 - srcY0));
+ vec2 target_size = vec2(abs(dstX1 - dstX0), abs(dstY1 - dstY0));
+ vec2 inverted_target_size = vec2(1.0) / target_size;
+
+ // Compute the top-left and bottom-right corners of the target pixel box.
+ vec2 t_beg = floor(xy - vec2(dstX0 < dstX1 ? dstX0 : dstX1, dstY0 < dstY1 ? dstY0 : dstY1));
+ vec2 t_end = t_beg + vec2(1.0, 1.0);
+
+ // Convert the target pixel box to source pixel box.
+ vec2 beg = t_beg * inverted_target_size * source_size;
+ vec2 end = t_end * inverted_target_size * source_size;
+
+ // Compute the top-left and bottom-right corners of the pixel box.
+ ivec2 f_beg = ivec2(beg);
+ ivec2 f_end = ivec2(end);
+
+ // Compute how much of the start and end pixels are covered horizontally & vertically.
+ float area_w = 1.0 - fract(beg.x);
+ float area_n = 1.0 - fract(beg.y);
+ float area_e = fract(end.x);
+ float area_s = fract(end.y);
+
+ // Compute the areas of the corner pixels in the pixel box.
+ float area_nw = area_n * area_w;
+ float area_ne = area_n * area_e;
+ float area_sw = area_s * area_w;
+ float area_se = area_s * area_e;
+
+ // Initialize the color accumulator.
+ vec4 avg_color = vec4(0.0, 0.0, 0.0, 0.0);
+
+ // Accumulate corner pixels.
+ avg_color += area_nw * texelFetch(Source, ivec2(f_beg.x, f_beg.y), 0);
+ avg_color += area_ne * texelFetch(Source, ivec2(f_end.x, f_beg.y), 0);
+ avg_color += area_sw * texelFetch(Source, ivec2(f_beg.x, f_end.y), 0);
+ avg_color += area_se * texelFetch(Source, ivec2(f_end.x, f_end.y), 0);
+
+ // Determine the size of the pixel box.
+ int x_range = int(f_end.x - f_beg.x - 0.5);
+ int y_range = int(f_end.y - f_beg.y - 0.5);
+
+ // Accumulate top and bottom edge pixels.
+ for (int x = f_beg.x + 1; x <= f_beg.x + x_range; ++x)
+ {
+ avg_color += area_n * texelFetch(Source, ivec2(x, f_beg.y), 0);
+ avg_color += area_s * texelFetch(Source, ivec2(x, f_end.y), 0);
+ }
+
+ // Accumulate left and right edge pixels and all the pixels in between.
+ for (int y = f_beg.y + 1; y <= f_beg.y + y_range; ++y)
+ {
+ avg_color += area_w * texelFetch(Source, ivec2(f_beg.x, y), 0);
+ avg_color += area_e * texelFetch(Source, ivec2(f_end.x, y), 0);
+
+ for (int x = f_beg.x + 1; x <= f_beg.x + x_range; ++x)
+ {
+ avg_color += texelFetch(Source, ivec2(x, y), 0);
+ }
+ }
+
+ // Compute the area of the pixel box that was sampled.
+ float area_corners = area_nw + area_ne + area_sw + area_se;
+ float area_edges = float(x_range) * (area_n + area_s) + float(y_range) * (area_w + area_e);
+ float area_center = float(x_range) * float(y_range);
+
+ // Return the normalized average color.
+ return avg_color / (area_corners + area_edges + area_center);
+}
+
+float insideBox(vec2 v, vec2 bLeft, vec2 tRight) {
+ vec2 s = step(bLeft, v) - step(tRight, v);
+ return s.x * s.y;
+}
+
+vec2 translateDest(vec2 pos) {
+ vec2 translatedPos = vec2(pos.x, pos.y);
+ translatedPos.x = dstX1 < dstX0 ? dstX1 - translatedPos.x : translatedPos.x;
+ translatedPos.y = dstY0 > dstY1 ? dstY0 + dstY1 - translatedPos.y - 1 : translatedPos.y;
+ return translatedPos;
+}
+
+void main()
+{
+ vec2 bLeft = vec2(dstX0 < dstX1 ? dstX0 : dstX1, dstY0 < dstY1 ? dstY0 : dstY1);
+ vec2 tRight = vec2(dstX1 > dstX0 ? dstX1 : dstX0, dstY1 > dstY0 ? dstY1 : dstY0);
+ ivec2 loc = ivec2(gl_GlobalInvocationID.x, gl_GlobalInvocationID.y);
+ if (insideBox(loc, bLeft, tRight) == 0) {
+ imageStore(imgOutput, loc, vec4(0, 0, 0, 1));
+ return;
+ }
+
+ vec4 outColor = AreaSampling(loc);
+ imageStore(imgOutput, ivec2(translateDest(loc)), vec4(outColor.rgb, 1));
+}
diff --git a/src/Ryujinx.Graphics.OpenGL/Effects/Shaders/fsr_scaling.glsl b/src/Ryujinx.Graphics.OpenGL/Effects/Shaders/fsr_scaling.glsl
index 8e8755db20..3c7d485b10 100644
--- a/src/Ryujinx.Graphics.OpenGL/Effects/Shaders/fsr_scaling.glsl
+++ b/src/Ryujinx.Graphics.OpenGL/Effects/Shaders/fsr_scaling.glsl
@@ -85,4 +85,4 @@ void main() {
CurrFilter(gxy);
gxy.x -= 8u;
CurrFilter(gxy);
-}
\ No newline at end of file
+}
diff --git a/src/Ryujinx.Graphics.OpenGL/Ryujinx.Graphics.OpenGL.csproj b/src/Ryujinx.Graphics.OpenGL/Ryujinx.Graphics.OpenGL.csproj
index 3d64da99bc..f3071f486a 100644
--- a/src/Ryujinx.Graphics.OpenGL/Ryujinx.Graphics.OpenGL.csproj
+++ b/src/Ryujinx.Graphics.OpenGL/Ryujinx.Graphics.OpenGL.csproj
@@ -21,6 +21,7 @@
+
diff --git a/src/Ryujinx.Graphics.OpenGL/Window.cs b/src/Ryujinx.Graphics.OpenGL/Window.cs
index 6bcfefa4ed..285ab725e2 100644
--- a/src/Ryujinx.Graphics.OpenGL/Window.cs
+++ b/src/Ryujinx.Graphics.OpenGL/Window.cs
@@ -373,6 +373,16 @@ namespace Ryujinx.Graphics.OpenGL
_isLinear = false;
_scalingFilter.Level = _scalingFilterLevel;
+ RecreateUpscalingTexture();
+ break;
+ case ScalingFilter.Area:
+ if (_scalingFilter is not AreaScalingFilter)
+ {
+ _scalingFilter?.Dispose();
+ _scalingFilter = new AreaScalingFilter(_renderer);
+ }
+ _isLinear = false;
+
RecreateUpscalingTexture();
break;
}
diff --git a/src/Ryujinx.Graphics.Vulkan/Effects/AreaScalingFilter.cs b/src/Ryujinx.Graphics.Vulkan/Effects/AreaScalingFilter.cs
new file mode 100644
index 0000000000..87b46df802
--- /dev/null
+++ b/src/Ryujinx.Graphics.Vulkan/Effects/AreaScalingFilter.cs
@@ -0,0 +1,101 @@
+using Ryujinx.Common;
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.Shader;
+using Ryujinx.Graphics.Shader.Translation;
+using Silk.NET.Vulkan;
+using System;
+using Extent2D = Ryujinx.Graphics.GAL.Extents2D;
+using Format = Silk.NET.Vulkan.Format;
+using SamplerCreateInfo = Ryujinx.Graphics.GAL.SamplerCreateInfo;
+
+namespace Ryujinx.Graphics.Vulkan.Effects
+{
+ internal class AreaScalingFilter : IScalingFilter
+ {
+ private readonly VulkanRenderer _renderer;
+ private PipelineHelperShader _pipeline;
+ private ISampler _sampler;
+ private ShaderCollection _scalingProgram;
+ private Device _device;
+
+ public float Level { get; set; }
+
+ public AreaScalingFilter(VulkanRenderer renderer, Device device)
+ {
+ _device = device;
+ _renderer = renderer;
+
+ Initialize();
+ }
+
+ public void Dispose()
+ {
+ _pipeline.Dispose();
+ _scalingProgram.Dispose();
+ _sampler.Dispose();
+ }
+
+ public void Initialize()
+ {
+ _pipeline = new PipelineHelperShader(_renderer, _device);
+
+ _pipeline.Initialize();
+
+ var scalingShader = EmbeddedResources.Read("Ryujinx.Graphics.Vulkan/Effects/Shaders/AreaScaling.spv");
+
+ var scalingResourceLayout = new ResourceLayoutBuilder()
+ .Add(ResourceStages.Compute, ResourceType.UniformBuffer, 2)
+ .Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 1)
+ .Add(ResourceStages.Compute, ResourceType.Image, 0, true).Build();
+
+ _sampler = _renderer.CreateSampler(SamplerCreateInfo.Create(MinFilter.Linear, MagFilter.Linear));
+
+ _scalingProgram = _renderer.CreateProgramWithMinimalLayout(new[]
+ {
+ new ShaderSource(scalingShader, ShaderStage.Compute, TargetLanguage.Spirv),
+ }, scalingResourceLayout);
+ }
+
+ public void Run(
+ TextureView view,
+ CommandBufferScoped cbs,
+ Auto destinationTexture,
+ Format format,
+ int width,
+ int height,
+ Extent2D source,
+ Extent2D destination)
+ {
+ _pipeline.SetCommandBuffer(cbs);
+ _pipeline.SetProgram(_scalingProgram);
+ _pipeline.SetTextureAndSampler(ShaderStage.Compute, 1, view, _sampler);
+
+ ReadOnlySpan dimensionsBuffer = stackalloc float[]
+ {
+ source.X1,
+ source.X2,
+ source.Y1,
+ source.Y2,
+ destination.X1,
+ destination.X2,
+ destination.Y1,
+ destination.Y2,
+ };
+
+ int rangeSize = dimensionsBuffer.Length * sizeof(float);
+ using var buffer = _renderer.BufferManager.ReserveOrCreate(_renderer, cbs, rangeSize);
+ buffer.Holder.SetDataUnchecked(buffer.Offset, dimensionsBuffer);
+
+ int threadGroupWorkRegionDim = 16;
+ int dispatchX = (width + (threadGroupWorkRegionDim - 1)) / threadGroupWorkRegionDim;
+ int dispatchY = (height + (threadGroupWorkRegionDim - 1)) / threadGroupWorkRegionDim;
+
+ _pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(2, buffer.Range) });
+ _pipeline.SetImage(0, destinationTexture);
+ _pipeline.DispatchCompute(dispatchX, dispatchY, 1);
+ _pipeline.ComputeBarrier();
+
+ _pipeline.Finish();
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.Vulkan/Effects/Shaders/AreaScaling.glsl b/src/Ryujinx.Graphics.Vulkan/Effects/Shaders/AreaScaling.glsl
new file mode 100644
index 0000000000..e34dd77dd5
--- /dev/null
+++ b/src/Ryujinx.Graphics.Vulkan/Effects/Shaders/AreaScaling.glsl
@@ -0,0 +1,122 @@
+// Scaling
+
+#version 430 core
+layout (local_size_x = 16, local_size_y = 16) in;
+layout( rgba8, binding = 0, set = 3) uniform image2D imgOutput;
+layout( binding = 1, set = 2) uniform sampler2D Source;
+layout( binding = 2 ) uniform dimensions{
+ float srcX0;
+ float srcX1;
+ float srcY0;
+ float srcY1;
+ float dstX0;
+ float dstX1;
+ float dstY0;
+ float dstY1;
+};
+
+/***** Area Sampling *****/
+
+// By Sam Belliveau and Filippo Tarpini. Public Domain license.
+// Effectively a more accurate sharp bilinear filter when upscaling,
+// that also works as a mathematically perfect downscale filter.
+// https://entropymine.com/imageworsener/pixelmixing/
+// https://github.com/obsproject/obs-studio/pull/1715
+// https://legacy.imagemagick.org/Usage/filter/
+vec4 AreaSampling(vec2 xy)
+{
+ // Determine the sizes of the source and target images.
+ vec2 source_size = vec2(abs(srcX1 - srcX0), abs(srcY1 - srcY0));
+ vec2 target_size = vec2(abs(dstX1 - dstX0), abs(dstY1 - dstY0));
+ vec2 inverted_target_size = vec2(1.0) / target_size;
+
+ // Compute the top-left and bottom-right corners of the target pixel box.
+ vec2 t_beg = floor(xy - vec2(dstX0 < dstX1 ? dstX0 : dstX1, dstY0 < dstY1 ? dstY0 : dstY1));
+ vec2 t_end = t_beg + vec2(1.0, 1.0);
+
+ // Convert the target pixel box to source pixel box.
+ vec2 beg = t_beg * inverted_target_size * source_size;
+ vec2 end = t_end * inverted_target_size * source_size;
+
+ // Compute the top-left and bottom-right corners of the pixel box.
+ ivec2 f_beg = ivec2(beg);
+ ivec2 f_end = ivec2(end);
+
+ // Compute how much of the start and end pixels are covered horizontally & vertically.
+ float area_w = 1.0 - fract(beg.x);
+ float area_n = 1.0 - fract(beg.y);
+ float area_e = fract(end.x);
+ float area_s = fract(end.y);
+
+ // Compute the areas of the corner pixels in the pixel box.
+ float area_nw = area_n * area_w;
+ float area_ne = area_n * area_e;
+ float area_sw = area_s * area_w;
+ float area_se = area_s * area_e;
+
+ // Initialize the color accumulator.
+ vec4 avg_color = vec4(0.0, 0.0, 0.0, 0.0);
+
+ // Accumulate corner pixels.
+ avg_color += area_nw * texelFetch(Source, ivec2(f_beg.x, f_beg.y), 0);
+ avg_color += area_ne * texelFetch(Source, ivec2(f_end.x, f_beg.y), 0);
+ avg_color += area_sw * texelFetch(Source, ivec2(f_beg.x, f_end.y), 0);
+ avg_color += area_se * texelFetch(Source, ivec2(f_end.x, f_end.y), 0);
+
+ // Determine the size of the pixel box.
+ int x_range = int(f_end.x - f_beg.x - 0.5);
+ int y_range = int(f_end.y - f_beg.y - 0.5);
+
+ // Accumulate top and bottom edge pixels.
+ for (int x = f_beg.x + 1; x <= f_beg.x + x_range; ++x)
+ {
+ avg_color += area_n * texelFetch(Source, ivec2(x, f_beg.y), 0);
+ avg_color += area_s * texelFetch(Source, ivec2(x, f_end.y), 0);
+ }
+
+ // Accumulate left and right edge pixels and all the pixels in between.
+ for (int y = f_beg.y + 1; y <= f_beg.y + y_range; ++y)
+ {
+ avg_color += area_w * texelFetch(Source, ivec2(f_beg.x, y), 0);
+ avg_color += area_e * texelFetch(Source, ivec2(f_end.x, y), 0);
+
+ for (int x = f_beg.x + 1; x <= f_beg.x + x_range; ++x)
+ {
+ avg_color += texelFetch(Source, ivec2(x, y), 0);
+ }
+ }
+
+ // Compute the area of the pixel box that was sampled.
+ float area_corners = area_nw + area_ne + area_sw + area_se;
+ float area_edges = float(x_range) * (area_n + area_s) + float(y_range) * (area_w + area_e);
+ float area_center = float(x_range) * float(y_range);
+
+ // Return the normalized average color.
+ return avg_color / (area_corners + area_edges + area_center);
+}
+
+float insideBox(vec2 v, vec2 bLeft, vec2 tRight) {
+ vec2 s = step(bLeft, v) - step(tRight, v);
+ return s.x * s.y;
+}
+
+vec2 translateDest(vec2 pos) {
+ vec2 translatedPos = vec2(pos.x, pos.y);
+ translatedPos.x = dstX1 < dstX0 ? dstX1 - translatedPos.x : translatedPos.x;
+ translatedPos.y = dstY0 < dstY1 ? dstY1 + dstY0 - translatedPos.y - 1 : translatedPos.y;
+ return translatedPos;
+}
+
+void main()
+{
+ vec2 bLeft = vec2(dstX0 < dstX1 ? dstX0 : dstX1, dstY0 < dstY1 ? dstY0 : dstY1);
+ vec2 tRight = vec2(dstX1 > dstX0 ? dstX1 : dstX0, dstY1 > dstY0 ? dstY1 : dstY0);
+ ivec2 loc = ivec2(gl_GlobalInvocationID.x, gl_GlobalInvocationID.y);
+ if (insideBox(loc, bLeft, tRight) == 0) {
+ imageStore(imgOutput, loc, vec4(0, 0, 0, 1));
+ return;
+ }
+
+ vec4 outColor = AreaSampling(loc);
+ imageStore(imgOutput, ivec2(translateDest(loc)), vec4(outColor.rgb, 1));
+}
diff --git a/src/Ryujinx.Graphics.Vulkan/Effects/Shaders/AreaScaling.spv b/src/Ryujinx.Graphics.Vulkan/Effects/Shaders/AreaScaling.spv
new file mode 100644
index 0000000000000000000000000000000000000000..7d097280f0c781e90318832e0a3cc8463a8beb08
GIT binary patch
literal 12428
zcmZQ(Qf6mhU}WHC;APNZVgLg{1||kZ1_lOh1~xG5?i1{zS6q^!XJTpqQozT+45Gkt
z{0wXitPBhcEDQ_`xrv#1Ac?<>3=9GcY+x}#1`r#9L1r>DFf)LxS;q*{!^Pn46YuWt
z@9yIopIlH7UtCg|lNz6snU@-$l3A3RT#{Lq3R1^`q%JcrIj1xQSph2p3)n80{j3b^
z3_J`B430&qiNT4v1v!~{=^ABeM%D}r46F>y4BQM13>B3iHV1U+u3i68?SQ%IuBp4VNit|g0l2hZ0Gpka;Vv-CD3@Mqpsd>ej
z`FX_*;7|pLfx@%6C^-TY@Su`U#XNSX7)Tu`9Hl{y1BV4GgFeXbiACwDB``m6FgSw4wk)-%BsC?z
z1W6LChlRlnq$fTpH66;&1oKn#Qowv>h8$3Mr>2ABtcZbu0VD&8>na8YhBPF7wO~F-
z78KSj3=Iqn42ealiSgwSemjhx2jTa^_^A;7L>Rvq!k-G`=an;n?3)SagW`&XVJ?hc
z43S?5=Y!-~8J2yc7FGdE2ocv@4
zRt63RP`*#kiFeP*PfE=3%qz=JPAtjH&+~KvyO)K5gNcEmAh9Sh7s}^E<8!0%Ss8?x
z7#K2h)BQ_J3Q8e)j+H@ziGd-%w8R-2wBY!WM$yN@pn$~pU|?kcF#g_=*e+;4%}$2c;vBJWM@^50VG@1tbrO15o(}
zF`ES(h9LDIKY-*xVG2?YvI4}{W&)M-3}811GcbYeQDgv>(hxhv7?>GAeo$awU=U|u
zVF0-u#0S~0z`())3MY^l2!rG~!DSMt%w}LvWME<7VqjnZiGkE9GO#i5LFGYw7zU{W
zg&D{UkeD0;7uY=@d+=e9{Zi1d0mZR30}BI)4+=w&`$QR77(n3wayuw|w4r=i22hR1
zzyQ)O2h|U<2PO{-9~fVafdO3O!T6d`Jup6~%mu|gjBgCB4`F-@1_p4r!T6wh2xcFM
zb^=8PLxa5z1E}U=U~pz&U;v4Mum}SagAW75t$t8*Kzcx74Jrpf{sZ|HI7E7V#gW?OM9%RpQsCpd+c5pndU|?VXi6LW222O@m
z(0B!fGgJ$hg7Kvpm>E_xFo4y8+`0xV%K#Pw#T$qZO3NT&koxX857Gm2uOJhs?u6)p7z-gmVF}9H
zAU#4L4g&)NNG-@r5FeC(Kq_J3A;yFn9-ue_xlb8tKFEC__k--!VuG}TbQsteK<)(P
zJ&+g(gW|!AffbzpK=y;|0O|d~zyxkzf$Rt64N!RuienHTlpc;SLedtDe+h{XQV%Mx
zk<~v!k_YMkfW!x-A5eJ?QxDP)DzlOKpfVeo4=S@^d{FuUmCrCfNI$4-M&^UcW)RU;v53!s8+%0|Q7ONE{R%SCGO3BzKvSf#EtdJV0!i*;k=v
zBinzCk%0ju4)PBueS!EO|A5Q{g$KwSnEo43{m5>+$;iL}5{Kyr@sag|%z^pi79#@#
z$c_MLS$La~fdLdoAaRg>Q2Gan!R)&WwGTP`?=dnkfW%>H?nBie$MFM31_qG$E@=9B
z$jHC|(gzX;h1V0L@B+y_W@KP^4h=658)o)XsM*N&KVxKI0EvU#3(9XGKFGfyGeO}6
zG6$yr1ynz>+g>s;4I={sNF1i-EmRG1
z9KT~^U;v53!s9(70|Q7ONE{R%pOC@>B=?b#f#EAOJV0!i*`J|iBisLlk%0ju4)PDE
zyaDk+{sEZ@3J;JuF#X@4`jOrCosoe7Bo5OL;v?$^nF9-tAB+qPAUj~`=O-fr11O9@
z;voH?@(?5jvJX@z{AXkUw_8E=BFGF-ISnd=LE<&g_5vfQ9$;c%fazgkg6IL2*)ToK
zObiSlahM(!CI$wO7|0!<`k0l8fdOO=NF3x2P&o?{1L+6JvoSG%+tT^aHWoXm{$OHY
z0I3C+!AuMcAhn>flZ%M~+%5;nfy!eLA124c#J~XZBS;QZHiP(<4D8@?m6wSD+}{P6
z2hs;;(%Q!5Eo3-hxS69WTC4CHoDy#eA|GO&W%9WqP|;66P_
z93&5_XFy^gvq0{UMRSK7n%VM93=AMKkX}$71mc6t28E|069c&K4ie8}U;+29l$aP8
zK;j_tLG2+GCI)cd!=HhJ0aUlEGBGfK#6TESSA((#D98dCK)opj1~n!I29O$%9863d
zDh5&u6Vrf-f%FDI+gO@R5d9$aAbC)m0u-JgKZ4u^k_UyS1p^}ksLcR!AE<8vvJ2GS
zHfLZ4x648O08swoWnf?cwGTk`F{mE`@-vJN>bE2FL49^)KB$ildV9UAoD?ed1OAQFOSRz_2rTIpuRjZ
zAJmse=7ak3$b3*=9+?m7%ftAf_6(>m595Q{q@aE~G9T1$N9Ke2?Z|vk-yN9`>c7MI
zApe2-@W^~n-yN9`>bHaVpt1+lUXo{kw3k4A7*N^-wb4QS8<03m4XD2d>H~rLF)%ft
z{w7Gg8Ja#-pnU_7I1d9GxO`BB_60!gPmny!Jawp@pned{JPig029P*RjTTf5sBZ*Q
zqs_p;01}7Usl&j)0MZ8%huNu%WG6@-R9=GotH;2=0CEpV9+YQ6=@ujgvkx=|1@a?u
zy=%n4zyK15sWE}N3At`HWnf?ciNoAv#=yV;(gzZUxyc;KO(1!gd6rP~kn1)p1_lO@
zI82QVR1I?dWy`?801}7UX~)380MZ8%huLY5WG6@-77h*!3=AOmfaF2p0Lrf*F;EzT
z!U;6C0ut+hwwGKPApJ*>I7mGxKZC?z>fOQPx{$gIl%71G?nMqGPw@B!LX8(x4RTm}
zGcYiK#9{4CUj_ySkUo$&sQw596-Lnd10)x~z`zi~0I5GfY*=^&LCr?CKbV1m0VED`
zKd8I`@j>-H$V^aq1~LbxKNPAT*==DA3=AM~n0^o+SwF}ePj#+w3y)L=
z1_qEFu=JP4z`y_sBak>K{esdQXuKAbjzDst^a~QpfTmwinGcc&*#)XQK;j^?L29xX
z7{Ft`jnK53&A`9_5(ABQfYgKP4v-j3J!qT|WCn~6np^;-=T@lxe9)W&0|Ns{9Hyp#
zfdM?e4pURez`y`f2NDO_0jiHcVleffaZZrEFg|FU5!sGX&{#caT$%yV{sPShfXXA7
z9pwxR3?OwNagZILIu0ZTGqak30X&usYg^PXFff3`Kx#qtABYcATgSk_0MZXrThGA2
z01^YK1=WopJ}9k#>W*fpxv;h?C_T3@Fff3`LGc3Ow?g9u)GmO?LE0y6&@m>EJjiTN
zJqr?pncWFB8`j1JnbpO>zyK15nca*ht>^`U;k=@Ht>lhdqK;v?tFbB1NHZU+SfYK~1?m**n
z8yOfFK;kepo1ki7@d8q_nSp@;Bn~Q9K>dU*3=9k)eIW4^1{UzR;8q3(29SO6(01@P
z1_lO@7)ULseGF>P^g;Ur+Zh-bK=L5>g4_yf1Mgs90MEUE$`=rSCj$cmNDQPF=2wsy
zNIfWCcQG(9fZ_tg2l;h3nqT)YFff4f9I{{cGB7ZJ#9@BjhsCe^85kHq;xNA+U|?VX
z=>v(w{CbdqfdOP6%&&(S7#KieAhqazJ>JMbUo@8KP0Exr=dI}mI$nkobfq?-e4)g071_lO@
zK9D%fuV)z;7(n*H{CbXofdM22Qj6}_^9&3OAbF5`L2d>4^#Yn-FETJNfW$y*VSWXP
zfz*TidWnI70hGo;e2`x+qxtm;)E~%xy~@DA01}7!^%_(SvR|(=Fff3`VSc^Az`y{~
z2NH+*^(F%Y1IRv@UvDumFo48BYSI0An}LA=BoA^g$gLp1-a+&0T?PgQkQhiU%CZ
zm^<%7-HGg%2Mi1hAaR(QhgjnF5d#AQNW6)G6+B1sn1O)-qz@zx>W_oM@d*P114tZJ
zu0La7U;v4M#^FKr6DVIlXJB9eiG$n*>KlRhFneD@-G&^7uNW8@K;kepuc2;3R`Z5|
zfdM2AbK6@61_qEmkT}e3?-&>uK;kgBy=P!x0Er>H?E?b?14ta?Hc&qe#E04Y32HBL
zxP4||U;v3XK*Qt<0|Ns{4Aj>GnE~qCfy6*fR&Mf0i+Kk4$B8@p!pU?1_oF@U}t1t0EvOsGJxg=LG?XkY=;9h2LT!sVT81?
zK>2`^5w$GmVq{v(w{3Xc9zyLBE<}V>e1_qEANG*E)7G`8%0Lg>g2689JUm|G!5@lpy
z0EvOr!u$mi1ErxK&@=?1LHa@QBL?*cEIo)bGBAL|kkcqg45S~FHbMOZP}+p0IY}(>
zDaFXZ01}6pDb2{h01|_l2^zxyiGj?NfsXltXi&PBV`N~E2gMx&0|Q820V)roVQyAr
zWMBZ90ds>A7B?s}GBAL|VR}?RW8mO+5TtxkWn^FgsR4<@(!ClZ0|UrxkQ^x8gZQ$H
z3=AMQf&2|(gUkb^4|PUJn-0VWr4J2g`hdAf6Y3`9@<5A`fdM2A(gO;6ZKywB=|hK+
zfdQljBo0d-x{M4AAhTiVLywVx0VD=ei=IC885tNr@*uZ?>;R1GMyE$jHC|5(BA)
zr4Nu8NIl43MvRcQCWsI6mob{ZOrYr;*VDVvxB|hvK
z85ls~uypFc$iM&+gQZi@*cwO-lnx!C=@3MN(y22e1A_}vI(3E0gJ_T&LE+#ADt{Ou
z;~t>4t~(YtcrY?BfW%>XJVEnDNa@szk%0lE1|$wkr{0VV3?Q>Xa-ehy;yW=iFo4_y
zN~a(;$UIQ|`9R|j#0R;_7wRS$-w!RW{23V-Kx#nZu(%3fWMBZ91BM
zptu0(2l*Ss2bCKjK8S{y7Y6ko%)D?$1_qEA%sfyz0ulr12bDn}{h<5~(htfHk&KA`
z3y2@Z2x;Sk`UD_;G$R88NDW9FrYD9G(l&;viDhJ90I36s!_>qvLfVutHSvrL3?OwN
zaZosc#&JPnAoZYdNra{gSbHT2wEhYdR?s=_WJc6JX9~3bf|;Mn$iM(n0}_YzBhx@@
zu^1T`Kyom9(xK%f%$^KJ1_qEANFLoyCj{3?Mxq
zagh0-ac__qNIxi@mO#@fh!65dDI)`T{Tqy5#>l_`audwG<%|prAT=Oyn3@Vk2JkvK
zn3_sP1_qEikT`7ar3$px4h@6GRzYs42CaPql?jZXwiXiuh!2~C0l5jZmK4+i1g&v`
z&B1`ykHXlXwPG-~Ahd25Mw*)et;Yk6lY!g|qU*uyXd!JP5Wj(u0lXfShk+fuo~RMj
z#$sS#0F8Zu)Pm$;dYeG)5~MM!W;DGmNP0nPLGqxsG{~M-(ApYC1_qECP+WlA1rh_5
z&7g6oHbw>pkQl5^0r|b1k%0ju4pY+sRRe2Zfz)&|GBAL|VR7CC$^(oH3?T7;3``6l
zJ}f*z?hpe7CIbTlXl)oQ+(7GSL3v&rDcnG77eVS_b5b)v?MbLS$i1tee3;w%7#YCp
zagp8M&&a?45{Ido09AwR{)vnX3?Ok({SUHl5+efxNFPWX6iy&_fW$y%fX3D*L+7$V
z@dJt%P#8>MWMBY^!_-WLszDzAo(3wz7(rtfkhLw-85tNr`at5KvE*6MdJI&TfaGQ}
zGB7NF)@L9#%{$^li0O<$uk@bVjfrZgxMh37Nn0ZT(
z%mdjE;)B$H}zyQ(*5(nu6
z#m^?F-;vYdW<~}EkT@v6g3{C$Mg|6uUXVCUY%3!J14tY+cMJ+MP#yq@fx;Z*FHoF;
z`~tIQI~IF(FfuTJ#9{hDd}RF~b6|0{laYY|WCzT=T}bAE^n>^yH6Zn%yt5lB56br-
IHYnc%00q#@g#Z8m
literal 0
HcmV?d00001
diff --git a/src/Ryujinx.Graphics.Vulkan/Ryujinx.Graphics.Vulkan.csproj b/src/Ryujinx.Graphics.Vulkan/Ryujinx.Graphics.Vulkan.csproj
index f6a7be91e4..aae28733f9 100644
--- a/src/Ryujinx.Graphics.Vulkan/Ryujinx.Graphics.Vulkan.csproj
+++ b/src/Ryujinx.Graphics.Vulkan/Ryujinx.Graphics.Vulkan.csproj
@@ -15,6 +15,7 @@
+
diff --git a/src/Ryujinx.Graphics.Vulkan/Window.cs b/src/Ryujinx.Graphics.Vulkan/Window.cs
index d67362be30..3dc6d4e191 100644
--- a/src/Ryujinx.Graphics.Vulkan/Window.cs
+++ b/src/Ryujinx.Graphics.Vulkan/Window.cs
@@ -568,6 +568,13 @@ namespace Ryujinx.Graphics.Vulkan
_scalingFilter.Level = _scalingFilterLevel;
break;
+ case ScalingFilter.Area:
+ if (_scalingFilter is not AreaScalingFilter)
+ {
+ _scalingFilter?.Dispose();
+ _scalingFilter = new AreaScalingFilter(_gd, _device);
+ }
+ break;
}
}
}
diff --git a/src/Ryujinx/Assets/Locales/en_US.json b/src/Ryujinx/Assets/Locales/en_US.json
index 3031dea0d2..b3cab7f5f6 100644
--- a/src/Ryujinx/Assets/Locales/en_US.json
+++ b/src/Ryujinx/Assets/Locales/en_US.json
@@ -758,10 +758,11 @@
"GraphicsAATooltip": "Applies anti-aliasing to the game render.\n\nFXAA will blur most of the image, while SMAA will attempt to find jagged edges and smooth them out.\n\nNot recommended to use in conjunction with the FSR scaling filter.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on NONE if unsure.",
"GraphicsAALabel": "Anti-Aliasing:",
"GraphicsScalingFilterLabel": "Scaling Filter:",
- "GraphicsScalingFilterTooltip": "Choose the scaling filter that will be applied when using resolution scale.\n\nBilinear works well for 3D games and is a safe default option.\n\nNearest is recommended for pixel art games.\n\nFSR 1.0 is merely a sharpening filter, not recommended for use with FXAA or SMAA.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on BILINEAR if unsure.",
+ "GraphicsScalingFilterTooltip": "Choose the scaling filter that will be applied when using resolution scale.\n\nBilinear works well for 3D games and is a safe default option.\n\nNearest is recommended for pixel art games.\n\nFSR 1.0 is merely a sharpening filter, not recommended for use with FXAA or SMAA.\n\nArea scaling is recommended when downscaling resolutions that are larger than the output window. It can be used to achieve a supersampled anti-aliasing effect when downscaling by more than 2x.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on BILINEAR if unsure.",
"GraphicsScalingFilterBilinear": "Bilinear",
"GraphicsScalingFilterNearest": "Nearest",
"GraphicsScalingFilterFsr": "FSR",
+ "GraphicsScalingFilterArea": "Area",
"GraphicsScalingFilterLevelLabel": "Level",
"GraphicsScalingFilterLevelTooltip": "Set FSR 1.0 sharpening level. Higher is sharper.",
"SmaaLow": "SMAA Low",
diff --git a/src/Ryujinx/UI/Views/Settings/SettingsGraphicsView.axaml b/src/Ryujinx/UI/Views/Settings/SettingsGraphicsView.axaml
index 5cffc6848a..0a12575adc 100644
--- a/src/Ryujinx/UI/Views/Settings/SettingsGraphicsView.axaml
+++ b/src/Ryujinx/UI/Views/Settings/SettingsGraphicsView.axaml
@@ -1,4 +1,4 @@
-
+
+
+
Date: Tue, 17 Sep 2024 20:42:00 +0200
Subject: [PATCH 02/15] Wait for async task to complete (#7122)
This way exceptions thrown during the execution of CheckLaunchState()
will correctly invoke the unhandled exception handler
and cause Ryujinx to crash.
---
src/Ryujinx/UI/Windows/MainWindow.axaml.cs | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/src/Ryujinx/UI/Windows/MainWindow.axaml.cs b/src/Ryujinx/UI/Windows/MainWindow.axaml.cs
index 348412e78c..1f57ada388 100644
--- a/src/Ryujinx/UI/Windows/MainWindow.axaml.cs
+++ b/src/Ryujinx/UI/Windows/MainWindow.axaml.cs
@@ -482,9 +482,7 @@ namespace Ryujinx.Ava.UI.Windows
LoadApplications();
}
-#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
- CheckLaunchState();
-#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
+ CheckLaunchState().Wait();
}
private void SetMainContent(Control content = null)
From eb8132b627d3c0285dd199f4e40c6f3800bdb22d Mon Sep 17 00:00:00 2001
From: gdkchan
Date: Tue, 17 Sep 2024 15:52:30 -0300
Subject: [PATCH 03/15] Change image format view handling to allow view
incompatible formats (#7311)
* Allow creating texture aliases on texture pool
* Delete old image format override code
* New format incompatible alias
* Missing bounds check
* GetForBinding now takes FormatInfo
* Make FormatInfo struct more compact
---
src/Ryujinx.Graphics.GAL/IImageArray.cs | 1 -
src/Ryujinx.Graphics.GAL/IPipeline.cs | 2 +-
.../Multithreading/CommandHelper.cs | 1 -
.../Multithreading/CommandType.cs | 1 -
.../ImageArray/ImageArraySetFormatsCommand.cs | 26 ---
.../Commands/SetImageCommand.cs | 6 +-
.../Resources/ThreadedImageArray.cs | 6 -
.../Multithreading/ThreadedPipeline.cs | 4 +-
.../Engine/ShaderTexture.cs | 83 ++++----
src/Ryujinx.Graphics.Gpu/Image/FormatInfo.cs | 21 +-
.../Image/TextureBindingInfo.cs | 10 +-
.../Image/TextureBindingsArrayCache.cs | 42 +---
.../Image/TextureBindingsManager.cs | 35 +---
.../Image/TextureCompatibility.cs | 3 +-
src/Ryujinx.Graphics.Gpu/Image/TexturePool.cs | 180 +++++++++++++++++-
.../Memory/BufferManager.cs | 15 +-
.../Memory/BufferTextureArrayBinding.cs | 11 +-
.../Memory/BufferTextureBinding.cs | 8 -
.../Shader/CachedShaderBindings.cs | 4 +-
src/Ryujinx.Graphics.Gpu/Window.cs | 2 +-
.../Image/ImageArray.cs | 10 +-
src/Ryujinx.Graphics.OpenGL/Pipeline.cs | 14 +-
.../DescriptorSetUpdater.cs | 15 +-
.../Effects/FsrScalingFilter.cs | 2 +-
.../Effects/FxaaPostProcessingEffect.cs | 2 +-
.../Effects/SmaaPostProcessingEffect.cs | 6 +-
src/Ryujinx.Graphics.Vulkan/HelperShader.cs | 4 +-
src/Ryujinx.Graphics.Vulkan/ImageArray.cs | 17 +-
src/Ryujinx.Graphics.Vulkan/PipelineBase.cs | 4 +-
src/Ryujinx.Graphics.Vulkan/TextureBuffer.cs | 34 ----
.../Services/SurfaceFlinger/SurfaceFlinger.cs | 4 +-
31 files changed, 294 insertions(+), 279 deletions(-)
delete mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/ImageArray/ImageArraySetFormatsCommand.cs
diff --git a/src/Ryujinx.Graphics.GAL/IImageArray.cs b/src/Ryujinx.Graphics.GAL/IImageArray.cs
index d119aa9fbd..d87314eb85 100644
--- a/src/Ryujinx.Graphics.GAL/IImageArray.cs
+++ b/src/Ryujinx.Graphics.GAL/IImageArray.cs
@@ -4,7 +4,6 @@ namespace Ryujinx.Graphics.GAL
{
public interface IImageArray : IDisposable
{
- void SetFormats(int index, Format[] imageFormats);
void SetImages(int index, ITexture[] images);
}
}
diff --git a/src/Ryujinx.Graphics.GAL/IPipeline.cs b/src/Ryujinx.Graphics.GAL/IPipeline.cs
index cbf1bc3a22..b8409a5736 100644
--- a/src/Ryujinx.Graphics.GAL/IPipeline.cs
+++ b/src/Ryujinx.Graphics.GAL/IPipeline.cs
@@ -58,7 +58,7 @@ namespace Ryujinx.Graphics.GAL
void SetIndexBuffer(BufferRange buffer, IndexType type);
- void SetImage(ShaderStage stage, int binding, ITexture texture, Format imageFormat);
+ void SetImage(ShaderStage stage, int binding, ITexture texture);
void SetImageArray(ShaderStage stage, int binding, IImageArray array);
void SetImageArraySeparate(ShaderStage stage, int setIndex, IImageArray array);
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/CommandHelper.cs b/src/Ryujinx.Graphics.GAL/Multithreading/CommandHelper.cs
index ef227d4a54..a1e6db9719 100644
--- a/src/Ryujinx.Graphics.GAL/Multithreading/CommandHelper.cs
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/CommandHelper.cs
@@ -67,7 +67,6 @@ namespace Ryujinx.Graphics.GAL.Multithreading
Register(CommandType.CounterEventFlush);
Register(CommandType.ImageArrayDispose);
- Register(CommandType.ImageArraySetFormats);
Register(CommandType.ImageArraySetImages);
Register(CommandType.ProgramDispose);
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/CommandType.cs b/src/Ryujinx.Graphics.GAL/Multithreading/CommandType.cs
index cf3f5d6c14..348c8e4620 100644
--- a/src/Ryujinx.Graphics.GAL/Multithreading/CommandType.cs
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/CommandType.cs
@@ -27,7 +27,6 @@ namespace Ryujinx.Graphics.GAL.Multithreading
CounterEventFlush,
ImageArrayDispose,
- ImageArraySetFormats,
ImageArraySetImages,
ProgramDispose,
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/ImageArray/ImageArraySetFormatsCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/ImageArray/ImageArraySetFormatsCommand.cs
deleted file mode 100644
index 8e3ba88a88..0000000000
--- a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/ImageArray/ImageArraySetFormatsCommand.cs
+++ /dev/null
@@ -1,26 +0,0 @@
-using Ryujinx.Graphics.GAL.Multithreading.Model;
-using Ryujinx.Graphics.GAL.Multithreading.Resources;
-
-namespace Ryujinx.Graphics.GAL.Multithreading.Commands.ImageArray
-{
- struct ImageArraySetFormatsCommand : IGALCommand, IGALCommand
- {
- public readonly CommandType CommandType => CommandType.ImageArraySetFormats;
- private TableRef _imageArray;
- private int _index;
- private TableRef _imageFormats;
-
- public void Set(TableRef imageArray, int index, TableRef imageFormats)
- {
- _imageArray = imageArray;
- _index = index;
- _imageFormats = imageFormats;
- }
-
- public static void Run(ref ImageArraySetFormatsCommand command, ThreadedRenderer threaded, IRenderer renderer)
- {
- ThreadedImageArray imageArray = command._imageArray.Get(threaded);
- imageArray.Base.SetFormats(command._index, command._imageFormats.Get(threaded));
- }
- }
-}
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetImageCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetImageCommand.cs
index 243480a817..2ba9db527e 100644
--- a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetImageCommand.cs
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetImageCommand.cs
@@ -10,19 +10,17 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands
private ShaderStage _stage;
private int _binding;
private TableRef _texture;
- private Format _imageFormat;
- public void Set(ShaderStage stage, int binding, TableRef texture, Format imageFormat)
+ public void Set(ShaderStage stage, int binding, TableRef texture)
{
_stage = stage;
_binding = binding;
_texture = texture;
- _imageFormat = imageFormat;
}
public static void Run(ref SetImageCommand command, ThreadedRenderer threaded, IRenderer renderer)
{
- renderer.Pipeline.SetImage(command._stage, command._binding, command._texture.GetAs(threaded)?.Base, command._imageFormat);
+ renderer.Pipeline.SetImage(command._stage, command._binding, command._texture.GetAs(threaded)?.Base);
}
}
}
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedImageArray.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedImageArray.cs
index 19bc6f233a..82587c189a 100644
--- a/src/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedImageArray.cs
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedImageArray.cs
@@ -27,12 +27,6 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Resources
_renderer.QueueCommand();
}
- public void SetFormats(int index, Format[] imageFormats)
- {
- _renderer.New().Set(Ref(this), index, Ref(imageFormats));
- _renderer.QueueCommand();
- }
-
public void SetImages(int index, ITexture[] images)
{
_renderer.New().Set(Ref(this), index, Ref(images));
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedPipeline.cs b/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedPipeline.cs
index edd79d8a07..deec36648e 100644
--- a/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedPipeline.cs
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedPipeline.cs
@@ -177,9 +177,9 @@ namespace Ryujinx.Graphics.GAL.Multithreading
_renderer.QueueCommand();
}
- public void SetImage(ShaderStage stage, int binding, ITexture texture, Format imageFormat)
+ public void SetImage(ShaderStage stage, int binding, ITexture texture)
{
- _renderer.New().Set(stage, binding, Ref(texture), imageFormat);
+ _renderer.New().Set(stage, binding, Ref(texture));
_renderer.QueueCommand();
}
diff --git a/src/Ryujinx.Graphics.Gpu/Engine/ShaderTexture.cs b/src/Ryujinx.Graphics.Gpu/Engine/ShaderTexture.cs
index 7bff1c4b82..bdb34180e6 100644
--- a/src/Ryujinx.Graphics.Gpu/Engine/ShaderTexture.cs
+++ b/src/Ryujinx.Graphics.Gpu/Engine/ShaderTexture.cs
@@ -1,5 +1,6 @@
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.Gpu.Image;
using Ryujinx.Graphics.Shader;
namespace Ryujinx.Graphics.Gpu.Engine
@@ -61,51 +62,51 @@ namespace Ryujinx.Graphics.Gpu.Engine
///
/// Shader image format
/// Texture format
- public static Format GetFormat(TextureFormat format)
+ public static FormatInfo GetFormatInfo(TextureFormat format)
{
return format switch
{
#pragma warning disable IDE0055 // Disable formatting
- TextureFormat.R8Unorm => Format.R8Unorm,
- TextureFormat.R8Snorm => Format.R8Snorm,
- TextureFormat.R8Uint => Format.R8Uint,
- TextureFormat.R8Sint => Format.R8Sint,
- TextureFormat.R16Float => Format.R16Float,
- TextureFormat.R16Unorm => Format.R16Unorm,
- TextureFormat.R16Snorm => Format.R16Snorm,
- TextureFormat.R16Uint => Format.R16Uint,
- TextureFormat.R16Sint => Format.R16Sint,
- TextureFormat.R32Float => Format.R32Float,
- TextureFormat.R32Uint => Format.R32Uint,
- TextureFormat.R32Sint => Format.R32Sint,
- TextureFormat.R8G8Unorm => Format.R8G8Unorm,
- TextureFormat.R8G8Snorm => Format.R8G8Snorm,
- TextureFormat.R8G8Uint => Format.R8G8Uint,
- TextureFormat.R8G8Sint => Format.R8G8Sint,
- TextureFormat.R16G16Float => Format.R16G16Float,
- TextureFormat.R16G16Unorm => Format.R16G16Unorm,
- TextureFormat.R16G16Snorm => Format.R16G16Snorm,
- TextureFormat.R16G16Uint => Format.R16G16Uint,
- TextureFormat.R16G16Sint => Format.R16G16Sint,
- TextureFormat.R32G32Float => Format.R32G32Float,
- TextureFormat.R32G32Uint => Format.R32G32Uint,
- TextureFormat.R32G32Sint => Format.R32G32Sint,
- TextureFormat.R8G8B8A8Unorm => Format.R8G8B8A8Unorm,
- TextureFormat.R8G8B8A8Snorm => Format.R8G8B8A8Snorm,
- TextureFormat.R8G8B8A8Uint => Format.R8G8B8A8Uint,
- TextureFormat.R8G8B8A8Sint => Format.R8G8B8A8Sint,
- TextureFormat.R16G16B16A16Float => Format.R16G16B16A16Float,
- TextureFormat.R16G16B16A16Unorm => Format.R16G16B16A16Unorm,
- TextureFormat.R16G16B16A16Snorm => Format.R16G16B16A16Snorm,
- TextureFormat.R16G16B16A16Uint => Format.R16G16B16A16Uint,
- TextureFormat.R16G16B16A16Sint => Format.R16G16B16A16Sint,
- TextureFormat.R32G32B32A32Float => Format.R32G32B32A32Float,
- TextureFormat.R32G32B32A32Uint => Format.R32G32B32A32Uint,
- TextureFormat.R32G32B32A32Sint => Format.R32G32B32A32Sint,
- TextureFormat.R10G10B10A2Unorm => Format.R10G10B10A2Unorm,
- TextureFormat.R10G10B10A2Uint => Format.R10G10B10A2Uint,
- TextureFormat.R11G11B10Float => Format.R11G11B10Float,
- _ => 0,
+ TextureFormat.R8Unorm => new(Format.R8Unorm, 1, 1, 1, 1),
+ TextureFormat.R8Snorm => new(Format.R8Snorm, 1, 1, 1, 1),
+ TextureFormat.R8Uint => new(Format.R8Uint, 1, 1, 1, 1),
+ TextureFormat.R8Sint => new(Format.R8Sint, 1, 1, 1, 1),
+ TextureFormat.R16Float => new(Format.R16Float, 1, 1, 2, 1),
+ TextureFormat.R16Unorm => new(Format.R16Unorm, 1, 1, 2, 1),
+ TextureFormat.R16Snorm => new(Format.R16Snorm, 1, 1, 2, 1),
+ TextureFormat.R16Uint => new(Format.R16Uint, 1, 1, 2, 1),
+ TextureFormat.R16Sint => new(Format.R16Sint, 1, 1, 2, 1),
+ TextureFormat.R32Float => new(Format.R32Float, 1, 1, 4, 1),
+ TextureFormat.R32Uint => new(Format.R32Uint, 1, 1, 4, 1),
+ TextureFormat.R32Sint => new(Format.R32Sint, 1, 1, 4, 1),
+ TextureFormat.R8G8Unorm => new(Format.R8G8Unorm, 1, 1, 2, 2),
+ TextureFormat.R8G8Snorm => new(Format.R8G8Snorm, 1, 1, 2, 2),
+ TextureFormat.R8G8Uint => new(Format.R8G8Uint, 1, 1, 2, 2),
+ TextureFormat.R8G8Sint => new(Format.R8G8Sint, 1, 1, 2, 2),
+ TextureFormat.R16G16Float => new(Format.R16G16Float, 1, 1, 4, 2),
+ TextureFormat.R16G16Unorm => new(Format.R16G16Unorm, 1, 1, 4, 2),
+ TextureFormat.R16G16Snorm => new(Format.R16G16Snorm, 1, 1, 4, 2),
+ TextureFormat.R16G16Uint => new(Format.R16G16Uint, 1, 1, 4, 2),
+ TextureFormat.R16G16Sint => new(Format.R16G16Sint, 1, 1, 4, 2),
+ TextureFormat.R32G32Float => new(Format.R32G32Float, 1, 1, 8, 2),
+ TextureFormat.R32G32Uint => new(Format.R32G32Uint, 1, 1, 8, 2),
+ TextureFormat.R32G32Sint => new(Format.R32G32Sint, 1, 1, 8, 2),
+ TextureFormat.R8G8B8A8Unorm => new(Format.R8G8B8A8Unorm, 1, 1, 4, 4),
+ TextureFormat.R8G8B8A8Snorm => new(Format.R8G8B8A8Snorm, 1, 1, 4, 4),
+ TextureFormat.R8G8B8A8Uint => new(Format.R8G8B8A8Uint, 1, 1, 4, 4),
+ TextureFormat.R8G8B8A8Sint => new(Format.R8G8B8A8Sint, 1, 1, 4, 4),
+ TextureFormat.R16G16B16A16Float => new(Format.R16G16B16A16Float, 1, 1, 8, 4),
+ TextureFormat.R16G16B16A16Unorm => new(Format.R16G16B16A16Unorm, 1, 1, 8, 4),
+ TextureFormat.R16G16B16A16Snorm => new(Format.R16G16B16A16Snorm, 1, 1, 8, 4),
+ TextureFormat.R16G16B16A16Uint => new(Format.R16G16B16A16Uint, 1, 1, 8, 4),
+ TextureFormat.R16G16B16A16Sint => new(Format.R16G16B16A16Sint, 1, 1, 8, 4),
+ TextureFormat.R32G32B32A32Float => new(Format.R32G32B32A32Float, 1, 1, 16, 4),
+ TextureFormat.R32G32B32A32Uint => new(Format.R32G32B32A32Uint, 1, 1, 16, 4),
+ TextureFormat.R32G32B32A32Sint => new(Format.R32G32B32A32Sint, 1, 1, 16, 4),
+ TextureFormat.R10G10B10A2Unorm => new(Format.R10G10B10A2Unorm, 1, 1, 4, 4),
+ TextureFormat.R10G10B10A2Uint => new(Format.R10G10B10A2Uint, 1, 1, 4, 4),
+ TextureFormat.R11G11B10Float => new(Format.R11G11B10Float, 1, 1, 4, 3),
+ _ => FormatInfo.Invalid,
#pragma warning restore IDE0055
};
}
diff --git a/src/Ryujinx.Graphics.Gpu/Image/FormatInfo.cs b/src/Ryujinx.Graphics.Gpu/Image/FormatInfo.cs
index 8a9f37bb0b..b95c684e4c 100644
--- a/src/Ryujinx.Graphics.Gpu/Image/FormatInfo.cs
+++ b/src/Ryujinx.Graphics.Gpu/Image/FormatInfo.cs
@@ -7,6 +7,11 @@ namespace Ryujinx.Graphics.Gpu.Image
///
readonly struct FormatInfo
{
+ ///
+ /// An invalid texture format.
+ ///
+ public static FormatInfo Invalid { get; } = new(0, 0, 0, 0, 0);
+
///
/// A default, generic RGBA8 texture format.
///
@@ -23,7 +28,7 @@ namespace Ryujinx.Graphics.Gpu.Image
///
/// Must be 1 for non-compressed formats.
///
- public int BlockWidth { get; }
+ public byte BlockWidth { get; }
///
/// The block height for compressed formats.
@@ -31,17 +36,17 @@ namespace Ryujinx.Graphics.Gpu.Image
///
/// Must be 1 for non-compressed formats.
///
- public int BlockHeight { get; }
+ public byte BlockHeight { get; }
///
/// The number of bytes occupied by a single pixel in memory of the texture data.
///
- public int BytesPerPixel { get; }
+ public byte BytesPerPixel { get; }
///
/// The maximum number of components this format has defined (in RGBA order).
///
- public int Components { get; }
+ public byte Components { get; }
///
/// Whenever or not the texture format is a compressed format. Determined from block size.
@@ -57,10 +62,10 @@ namespace Ryujinx.Graphics.Gpu.Image
/// The number of bytes occupied by a single pixel in memory of the texture data
public FormatInfo(
Format format,
- int blockWidth,
- int blockHeight,
- int bytesPerPixel,
- int components)
+ byte blockWidth,
+ byte blockHeight,
+ byte bytesPerPixel,
+ byte components)
{
Format = format;
BlockWidth = blockWidth;
diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureBindingInfo.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureBindingInfo.cs
index 31abc21e8a..e9930405b7 100644
--- a/src/Ryujinx.Graphics.Gpu/Image/TextureBindingInfo.cs
+++ b/src/Ryujinx.Graphics.Gpu/Image/TextureBindingInfo.cs
@@ -17,7 +17,7 @@ namespace Ryujinx.Graphics.Gpu.Image
///
/// For images, indicates the format specified on the shader.
///
- public Format Format { get; }
+ public FormatInfo FormatInfo { get; }
///
/// Shader texture host set index.
@@ -58,17 +58,17 @@ namespace Ryujinx.Graphics.Gpu.Image
/// Constructs the texture binding information structure.
///
/// The shader sampler target type
- /// Format of the image as declared on the shader
+ /// Format of the image as declared on the shader
/// Shader texture host set index
/// The shader texture binding point
/// For array of textures, this indicates the length of the array. A value of one indicates it is not an array
/// Constant buffer slot where the texture handle is located
/// The shader texture handle (read index into the texture constant buffer)
/// The texture's usage flags, indicating how it is used in the shader
- public TextureBindingInfo(Target target, Format format, int set, int binding, int arrayLength, int cbufSlot, int handle, TextureUsageFlags flags)
+ public TextureBindingInfo(Target target, FormatInfo formatInfo, int set, int binding, int arrayLength, int cbufSlot, int handle, TextureUsageFlags flags)
{
Target = target;
- Format = format;
+ FormatInfo = formatInfo;
Set = set;
Binding = binding;
ArrayLength = arrayLength;
@@ -96,7 +96,7 @@ namespace Ryujinx.Graphics.Gpu.Image
int cbufSlot,
int handle,
TextureUsageFlags flags,
- bool isSamplerOnly) : this(target, 0, set, binding, arrayLength, cbufSlot, handle, flags)
+ bool isSamplerOnly) : this(target, FormatInfo.Invalid, set, binding, arrayLength, cbufSlot, handle, flags)
{
IsSamplerOnly = isSamplerOnly;
}
diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsArrayCache.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsArrayCache.cs
index 8b9243b1ec..72bac75e5a 100644
--- a/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsArrayCache.cs
+++ b/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsArrayCache.cs
@@ -659,7 +659,6 @@ namespace Ryujinx.Graphics.Gpu.Image
int length = (isSampler ? samplerPool.MaximumId : texturePool.MaximumId) + 1;
length = Math.Min(length, bindingInfo.ArrayLength);
- Format[] formats = isImage ? new Format[bindingInfo.ArrayLength] : null;
ISampler[] samplers = isImage ? null : new ISampler[bindingInfo.ArrayLength];
ITexture[] textures = new ITexture[bindingInfo.ArrayLength];
@@ -674,7 +673,7 @@ namespace Ryujinx.Graphics.Gpu.Image
}
else
{
- ref readonly TextureDescriptor descriptor = ref texturePool.GetForBinding(index, out texture);
+ ref readonly TextureDescriptor descriptor = ref texturePool.GetForBinding(index, bindingInfo.FormatInfo, out texture);
if (texture != null)
{
@@ -697,8 +696,6 @@ namespace Ryujinx.Graphics.Gpu.Image
ITexture hostTexture = texture?.GetTargetTexture(bindingInfo.Target);
ISampler hostSampler = sampler?.GetHostSampler(texture);
- Format format = bindingInfo.Format;
-
if (hostTexture != null && texture.Target == Target.TextureBuffer)
{
// Ensure that the buffer texture is using the correct buffer as storage.
@@ -706,26 +703,15 @@ namespace Ryujinx.Graphics.Gpu.Image
// to ensure we're not using a old buffer that was already deleted.
if (isImage)
{
- if (format == 0 && texture != null)
- {
- format = texture.Format;
- }
-
- _channel.BufferManager.SetBufferTextureStorage(stage, entry.ImageArray, hostTexture, texture.Range, bindingInfo, index, format);
+ _channel.BufferManager.SetBufferTextureStorage(stage, entry.ImageArray, hostTexture, texture.Range, bindingInfo, index);
}
else
{
- _channel.BufferManager.SetBufferTextureStorage(stage, entry.TextureArray, hostTexture, texture.Range, bindingInfo, index, format);
+ _channel.BufferManager.SetBufferTextureStorage(stage, entry.TextureArray, hostTexture, texture.Range, bindingInfo, index);
}
}
else if (isImage)
{
- if (format == 0 && texture != null)
- {
- format = texture.Format;
- }
-
- formats[index] = format;
textures[index] = hostTexture;
}
else
@@ -737,7 +723,6 @@ namespace Ryujinx.Graphics.Gpu.Image
if (isImage)
{
- entry.ImageArray.SetFormats(0, formats);
entry.ImageArray.SetImages(0, textures);
SetImageArray(stage, bindingInfo, entry.ImageArray);
@@ -863,7 +848,6 @@ namespace Ryujinx.Graphics.Gpu.Image
entry.UpdateData(cachedTextureBuffer, cachedSamplerBuffer, separateSamplerBuffer);
- Format[] formats = isImage ? new Format[bindingInfo.ArrayLength] : null;
ISampler[] samplers = isImage ? null : new ISampler[bindingInfo.ArrayLength];
ITexture[] textures = new ITexture[bindingInfo.ArrayLength];
@@ -883,7 +867,7 @@ namespace Ryujinx.Graphics.Gpu.Image
samplerId = TextureHandle.UnpackSamplerId(packedId);
}
- ref readonly TextureDescriptor descriptor = ref texturePool.GetForBinding(textureId, out Texture texture);
+ ref readonly TextureDescriptor descriptor = ref texturePool.GetForBinding(textureId, bindingInfo.FormatInfo, out Texture texture);
if (texture != null)
{
@@ -916,8 +900,6 @@ namespace Ryujinx.Graphics.Gpu.Image
hostSampler = sampler?.GetHostSampler(texture);
}
- Format format = bindingInfo.Format;
-
if (hostTexture != null && texture.Target == Target.TextureBuffer)
{
// Ensure that the buffer texture is using the correct buffer as storage.
@@ -925,26 +907,15 @@ namespace Ryujinx.Graphics.Gpu.Image
// to ensure we're not using a old buffer that was already deleted.
if (isImage)
{
- if (format == 0 && texture != null)
- {
- format = texture.Format;
- }
-
- _channel.BufferManager.SetBufferTextureStorage(stage, entry.ImageArray, hostTexture, texture.Range, bindingInfo, index, format);
+ _channel.BufferManager.SetBufferTextureStorage(stage, entry.ImageArray, hostTexture, texture.Range, bindingInfo, index);
}
else
{
- _channel.BufferManager.SetBufferTextureStorage(stage, entry.TextureArray, hostTexture, texture.Range, bindingInfo, index, format);
+ _channel.BufferManager.SetBufferTextureStorage(stage, entry.TextureArray, hostTexture, texture.Range, bindingInfo, index);
}
}
else if (isImage)
{
- if (format == 0 && texture != null)
- {
- format = texture.Format;
- }
-
- formats[index] = format;
textures[index] = hostTexture;
}
else
@@ -956,7 +927,6 @@ namespace Ryujinx.Graphics.Gpu.Image
if (isImage)
{
- entry.ImageArray.SetFormats(0, formats);
entry.ImageArray.SetImages(0, textures);
SetImageArray(stage, bindingInfo, entry.ImageArray);
diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs
index 9f1f60d956..ad018f1597 100644
--- a/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs
+++ b/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs
@@ -522,7 +522,7 @@ namespace Ryujinx.Graphics.Gpu.Image
// Ensure that the buffer texture is using the correct buffer as storage.
// Buffers are frequently re-created to accommodate larger data, so we need to re-bind
// to ensure we're not using a old buffer that was already deleted.
- _channel.BufferManager.SetBufferTextureStorage(stage, hostTexture, texture.Range, bindingInfo, bindingInfo.Format, false);
+ _channel.BufferManager.SetBufferTextureStorage(stage, hostTexture, texture.Range, bindingInfo, false);
// Cache is not used for buffer texture, it must always rebind.
state.CachedTexture = null;
@@ -616,6 +616,7 @@ namespace Ryujinx.Graphics.Gpu.Image
if (!poolModified &&
state.TextureHandle == textureId &&
+ state.ImageFormat == bindingInfo.FormatInfo.Format &&
state.CachedTexture != null &&
state.CachedTexture.InvalidatedSequence == state.InvalidatedSequence)
{
@@ -629,26 +630,22 @@ namespace Ryujinx.Graphics.Gpu.Image
cachedTexture.SignalModified();
}
- Format format = bindingInfo.Format == 0 ? cachedTexture.Format : bindingInfo.Format;
-
- if (state.ImageFormat != format ||
- ((usageFlags & TextureUsageFlags.NeedsScaleValue) != 0 &&
- UpdateScale(state.CachedTexture, usageFlags, scaleIndex, stage)))
+ if ((usageFlags & TextureUsageFlags.NeedsScaleValue) != 0 && UpdateScale(state.CachedTexture, usageFlags, scaleIndex, stage))
{
ITexture hostTextureRebind = state.CachedTexture.GetTargetTexture(bindingInfo.Target);
state.Texture = hostTextureRebind;
- state.ImageFormat = format;
- _context.Renderer.Pipeline.SetImage(stage, bindingInfo.Binding, hostTextureRebind, format);
+ _context.Renderer.Pipeline.SetImage(stage, bindingInfo.Binding, hostTextureRebind);
}
continue;
}
state.TextureHandle = textureId;
+ state.ImageFormat = bindingInfo.FormatInfo.Format;
- ref readonly TextureDescriptor descriptor = ref pool.GetForBinding(textureId, out Texture texture);
+ ref readonly TextureDescriptor descriptor = ref pool.GetForBinding(textureId, bindingInfo.FormatInfo, out Texture texture);
specStateMatches &= specState.MatchesImage(stage, index, descriptor);
@@ -660,14 +657,7 @@ namespace Ryujinx.Graphics.Gpu.Image
// Buffers are frequently re-created to accommodate larger data, so we need to re-bind
// to ensure we're not using a old buffer that was already deleted.
- Format format = bindingInfo.Format;
-
- if (format == 0 && texture != null)
- {
- format = texture.Format;
- }
-
- _channel.BufferManager.SetBufferTextureStorage(stage, hostTexture, texture.Range, bindingInfo, format, true);
+ _channel.BufferManager.SetBufferTextureStorage(stage, hostTexture, texture.Range, bindingInfo, true);
// Cache is not used for buffer texture, it must always rebind.
state.CachedTexture = null;
@@ -689,16 +679,7 @@ namespace Ryujinx.Graphics.Gpu.Image
{
state.Texture = hostTexture;
- Format format = bindingInfo.Format;
-
- if (format == 0 && texture != null)
- {
- format = texture.Format;
- }
-
- state.ImageFormat = format;
-
- _context.Renderer.Pipeline.SetImage(stage, bindingInfo.Binding, hostTexture, format);
+ _context.Renderer.Pipeline.SetImage(stage, bindingInfo.Binding, hostTexture);
}
state.CachedTexture = texture;
diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs
index 3cdeac9c5c..8bed6363b6 100644
--- a/src/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs
+++ b/src/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs
@@ -739,7 +739,8 @@ namespace Ryujinx.Graphics.Gpu.Image
}
return (lhsFormat.Format == Format.R8G8B8A8Unorm && rhsFormat.Format == Format.R32G32B32A32Float) ||
- (lhsFormat.Format == Format.R8Unorm && rhsFormat.Format == Format.R8G8B8A8Unorm);
+ (lhsFormat.Format == Format.R8Unorm && rhsFormat.Format == Format.R8G8B8A8Unorm) ||
+ (lhsFormat.Format == Format.R8Unorm && rhsFormat.Format == Format.R32Uint);
}
///
diff --git a/src/Ryujinx.Graphics.Gpu/Image/TexturePool.cs b/src/Ryujinx.Graphics.Gpu/Image/TexturePool.cs
index 4ed0a93c17..5f43c1824e 100644
--- a/src/Ryujinx.Graphics.Gpu/Image/TexturePool.cs
+++ b/src/Ryujinx.Graphics.Gpu/Image/TexturePool.cs
@@ -75,6 +75,76 @@ namespace Ryujinx.Graphics.Gpu.Image
private readonly ConcurrentQueue _dereferenceQueue = new();
private TextureDescriptor _defaultDescriptor;
+ ///
+ /// List of textures that shares the same memory region, but have different formats.
+ ///
+ private class TextureAliasList
+ {
+ ///
+ /// Alias texture.
+ ///
+ /// Texture format
+ /// Texture
+ private readonly record struct Alias(Format Format, Texture Texture);
+
+ ///
+ /// List of texture aliases.
+ ///
+ private readonly List _aliases;
+
+ ///
+ /// Creates a new instance of the texture alias list.
+ ///
+ public TextureAliasList()
+ {
+ _aliases = new List();
+ }
+
+ ///
+ /// Adds a new texture alias.
+ ///
+ /// Alias format
+ /// Alias texture
+ public void Add(Format format, Texture texture)
+ {
+ _aliases.Add(new Alias(format, texture));
+ texture.IncrementReferenceCount();
+ }
+
+ ///
+ /// Finds a texture with the requested format, or returns null if not found.
+ ///
+ /// Format to find
+ /// Texture with the requested format, or null if not found
+ public Texture Find(Format format)
+ {
+ foreach (var alias in _aliases)
+ {
+ if (alias.Format == format)
+ {
+ return alias.Texture;
+ }
+ }
+
+ return null;
+ }
+
+ ///
+ /// Removes all alias textures.
+ ///
+ public void Destroy()
+ {
+ foreach (var entry in _aliases)
+ {
+ entry.Texture.DecrementReferenceCount();
+ }
+
+ _aliases.Clear();
+ }
+ }
+
+ private readonly Dictionary _aliasLists;
+
///
/// Linked list node used on the texture pool cache.
///
@@ -95,6 +165,7 @@ namespace Ryujinx.Graphics.Gpu.Image
public TexturePool(GpuContext context, GpuChannel channel, ulong address, int maximumId) : base(context, channel.MemoryManager.Physical, address, maximumId)
{
_channel = channel;
+ _aliasLists = new Dictionary();
}
///
@@ -115,14 +186,13 @@ namespace Ryujinx.Graphics.Gpu.Image
if (texture == null)
{
- TextureInfo info = GetInfo(descriptor, out int layerSize);
-
// The dereference queue can put our texture back on the cache.
if ((texture = ProcessDereferenceQueue(id)) != null)
{
return ref descriptor;
}
+ TextureInfo info = GetInfo(descriptor, out int layerSize);
texture = PhysicalMemory.TextureCache.FindOrCreateTexture(_channel.MemoryManager, TextureSearchFlags.ForSampler, info, layerSize);
// If this happens, then the texture address is invalid, we can't add it to the cache.
@@ -197,6 +267,51 @@ namespace Ryujinx.Graphics.Gpu.Image
return ref GetInternal(id, out texture);
}
+ ///
+ /// Gets the texture descriptor and texture with the given ID.
+ ///
+ ///
+ /// This method assumes that the pool has been manually synchronized before doing binding.
+ ///
+ /// ID of the texture. This is effectively a zero-based index
+ /// Texture format information
+ /// The texture with the given ID
+ /// The texture descriptor with the given ID
+ public ref readonly TextureDescriptor GetForBinding(int id, FormatInfo formatInfo, out Texture texture)
+ {
+ if ((uint)id >= Items.Length)
+ {
+ texture = null;
+ return ref _defaultDescriptor;
+ }
+
+ ref readonly TextureDescriptor descriptor = ref GetInternal(id, out texture);
+
+ if (texture != null && formatInfo.Format != 0 && texture.Format != formatInfo.Format)
+ {
+ if (!_aliasLists.TryGetValue(texture, out TextureAliasList aliasList))
+ {
+ _aliasLists.Add(texture, aliasList = new TextureAliasList());
+ }
+
+ texture = aliasList.Find(formatInfo.Format);
+
+ if (texture == null)
+ {
+ TextureInfo info = GetInfo(descriptor, out int layerSize);
+ info = ChangeFormat(info, formatInfo);
+ texture = PhysicalMemory.TextureCache.FindOrCreateTexture(_channel.MemoryManager, TextureSearchFlags.ForSampler, info, layerSize);
+
+ if (texture != null)
+ {
+ aliasList.Add(formatInfo.Format, texture);
+ }
+ }
+ }
+
+ return ref descriptor;
+ }
+
///
/// Checks if the pool was modified, and returns the last sequence number where a modification was detected.
///
@@ -234,6 +349,7 @@ namespace Ryujinx.Graphics.Gpu.Image
else
{
texture.DecrementReferenceCount();
+ RemoveAliasList(texture);
}
}
@@ -327,6 +443,8 @@ namespace Ryujinx.Graphics.Gpu.Image
{
texture.DecrementReferenceCount();
}
+
+ RemoveAliasList(texture);
}
return null;
@@ -369,6 +487,7 @@ namespace Ryujinx.Graphics.Gpu.Image
if (Interlocked.Exchange(ref Items[id], null) != null)
{
texture.DecrementReferenceCount(this, id);
+ RemoveAliasList(texture);
}
}
}
@@ -622,6 +741,57 @@ namespace Ryujinx.Graphics.Gpu.Image
component == SwizzleComponent.Green;
}
+ ///
+ /// Changes the format on the texture information structure, and also adjusts the width for the new format if needed.
+ ///
+ /// Texture information
+ /// New format
+ /// Texture information with the new format
+ private static TextureInfo ChangeFormat(in TextureInfo info, FormatInfo dstFormat)
+ {
+ int width = info.Width;
+
+ if (info.FormatInfo.BytesPerPixel != dstFormat.BytesPerPixel)
+ {
+ int stride = width * info.FormatInfo.BytesPerPixel;
+ width = stride / dstFormat.BytesPerPixel;
+ }
+
+ return new TextureInfo(
+ info.GpuAddress,
+ width,
+ info.Height,
+ info.DepthOrLayers,
+ info.Levels,
+ info.SamplesInX,
+ info.SamplesInY,
+ info.Stride,
+ info.IsLinear,
+ info.GobBlocksInY,
+ info.GobBlocksInZ,
+ info.GobBlocksInTileX,
+ info.Target,
+ dstFormat,
+ info.DepthStencilMode,
+ info.SwizzleR,
+ info.SwizzleG,
+ info.SwizzleB,
+ info.SwizzleA);
+ }
+
+ ///
+ /// Removes all aliases for a texture.
+ ///
+ /// Texture to have the aliases removed
+ private void RemoveAliasList(Texture texture)
+ {
+ if (_aliasLists.TryGetValue(texture, out TextureAliasList aliasList))
+ {
+ _aliasLists.Remove(texture);
+ aliasList.Destroy();
+ }
+ }
+
///
/// Decrements the reference count of the texture.
/// This indicates that the texture pool is not using it anymore.
@@ -629,7 +799,11 @@ namespace Ryujinx.Graphics.Gpu.Image
/// The texture to be deleted
protected override void Delete(Texture item)
{
- item?.DecrementReferenceCount(this);
+ if (item != null)
+ {
+ item.DecrementReferenceCount(this);
+ RemoveAliasList(item);
+ }
}
public override void Dispose()
diff --git a/src/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs b/src/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs
index 26d9501c68..409867e09d 100644
--- a/src/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs
+++ b/src/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs
@@ -509,7 +509,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
if (binding.IsImage)
{
- _context.Renderer.Pipeline.SetImage(binding.Stage, binding.BindingInfo.Binding, binding.Texture, binding.Format);
+ _context.Renderer.Pipeline.SetImage(binding.Stage, binding.BindingInfo.Binding, binding.Texture);
}
else
{
@@ -873,12 +873,11 @@ namespace Ryujinx.Graphics.Gpu.Memory
ITexture texture,
MultiRange range,
TextureBindingInfo bindingInfo,
- Format format,
bool isImage)
{
_channel.MemoryManager.Physical.BufferCache.CreateBuffer(range, BufferStageUtils.TextureBuffer(stage, bindingInfo.Flags));
- _bufferTextures.Add(new BufferTextureBinding(stage, texture, range, bindingInfo, format, isImage));
+ _bufferTextures.Add(new BufferTextureBinding(stage, texture, range, bindingInfo, isImage));
}
///
@@ -897,12 +896,11 @@ namespace Ryujinx.Graphics.Gpu.Memory
ITexture texture,
MultiRange range,
TextureBindingInfo bindingInfo,
- int index,
- Format format)
+ int index)
{
_channel.MemoryManager.Physical.BufferCache.CreateBuffer(range, BufferStageUtils.TextureBuffer(stage, bindingInfo.Flags));
- _bufferTextureArrays.Add(new BufferTextureArrayBinding(array, texture, range, bindingInfo, index, format));
+ _bufferTextureArrays.Add(new BufferTextureArrayBinding(array, texture, range, bindingInfo, index));
}
///
@@ -921,12 +919,11 @@ namespace Ryujinx.Graphics.Gpu.Memory
ITexture texture,
MultiRange range,
TextureBindingInfo bindingInfo,
- int index,
- Format format)
+ int index)
{
_channel.MemoryManager.Physical.BufferCache.CreateBuffer(range, BufferStageUtils.TextureBuffer(stage, bindingInfo.Flags));
- _bufferImageArrays.Add(new BufferTextureArrayBinding(array, texture, range, bindingInfo, index, format));
+ _bufferImageArrays.Add(new BufferTextureArrayBinding(array, texture, range, bindingInfo, index));
}
///
diff --git a/src/Ryujinx.Graphics.Gpu/Memory/BufferTextureArrayBinding.cs b/src/Ryujinx.Graphics.Gpu/Memory/BufferTextureArrayBinding.cs
index fa79e4f92d..a5338fa559 100644
--- a/src/Ryujinx.Graphics.Gpu/Memory/BufferTextureArrayBinding.cs
+++ b/src/Ryujinx.Graphics.Gpu/Memory/BufferTextureArrayBinding.cs
@@ -34,33 +34,26 @@ namespace Ryujinx.Graphics.Gpu.Memory
///
public int Index { get; }
- ///
- /// The image format for the binding.
- ///
- public Format Format { get; }
-
///
/// Create a new buffer texture binding.
///
+ /// Array
/// Buffer texture
/// Physical ranges of memory where the buffer texture data is located
/// Binding info
/// Index of the binding on the array
- /// Binding format
public BufferTextureArrayBinding(
T array,
ITexture texture,
MultiRange range,
TextureBindingInfo bindingInfo,
- int index,
- Format format)
+ int index)
{
Array = array;
Texture = texture;
Range = range;
BindingInfo = bindingInfo;
Index = index;
- Format = format;
}
}
}
diff --git a/src/Ryujinx.Graphics.Gpu/Memory/BufferTextureBinding.cs b/src/Ryujinx.Graphics.Gpu/Memory/BufferTextureBinding.cs
index bf0beffa23..1a3fde5b6b 100644
--- a/src/Ryujinx.Graphics.Gpu/Memory/BufferTextureBinding.cs
+++ b/src/Ryujinx.Graphics.Gpu/Memory/BufferTextureBinding.cs
@@ -30,11 +30,6 @@ namespace Ryujinx.Graphics.Gpu.Memory
///
public TextureBindingInfo BindingInfo { get; }
- ///
- /// The image format for the binding.
- ///
- public Format Format { get; }
-
///
/// Whether the binding is for an image or a sampler.
///
@@ -47,21 +42,18 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// Buffer texture
/// Physical ranges of memory where the buffer texture data is located
/// Binding info
- /// Binding format
/// Whether the binding is for an image or a sampler
public BufferTextureBinding(
ShaderStage stage,
ITexture texture,
MultiRange range,
TextureBindingInfo bindingInfo,
- Format format,
bool isImage)
{
Stage = stage;
Texture = texture;
Range = range;
BindingInfo = bindingInfo;
- Format = format;
IsImage = isImage;
}
}
diff --git a/src/Ryujinx.Graphics.Gpu/Shader/CachedShaderBindings.cs b/src/Ryujinx.Graphics.Gpu/Shader/CachedShaderBindings.cs
index 51be00b6e7..018c5fdc07 100644
--- a/src/Ryujinx.Graphics.Gpu/Shader/CachedShaderBindings.cs
+++ b/src/Ryujinx.Graphics.Gpu/Shader/CachedShaderBindings.cs
@@ -86,11 +86,11 @@ namespace Ryujinx.Graphics.Gpu.Shader
ImageBindings[i] = stage.Info.Images.Select(descriptor =>
{
Target target = ShaderTexture.GetTarget(descriptor.Type);
- Format format = ShaderTexture.GetFormat(descriptor.Format);
+ FormatInfo formatInfo = ShaderTexture.GetFormatInfo(descriptor.Format);
var result = new TextureBindingInfo(
target,
- format,
+ formatInfo,
descriptor.Set,
descriptor.Binding,
descriptor.ArrayLength,
diff --git a/src/Ryujinx.Graphics.Gpu/Window.cs b/src/Ryujinx.Graphics.Gpu/Window.cs
index 3b23685370..59cd4c8a65 100644
--- a/src/Ryujinx.Graphics.Gpu/Window.cs
+++ b/src/Ryujinx.Graphics.Gpu/Window.cs
@@ -131,7 +131,7 @@ namespace Ryujinx.Graphics.Gpu
bool isLinear,
int gobBlocksInY,
Format format,
- int bytesPerPixel,
+ byte bytesPerPixel,
ImageCrop crop,
Action acquireCallback,
Action
/// New data
- public void SetData(IMemoryOwner data)
+ public void SetData(MemoryOwner data)
{
BlacklistScale();
@@ -704,7 +703,7 @@ namespace Ryujinx.Graphics.Gpu.Image
/// New data
/// Target layer
/// Target level
- public void SetData(IMemoryOwner data, int layer, int level)
+ public void SetData(MemoryOwner data, int layer, int level)
{
BlacklistScale();
@@ -722,7 +721,7 @@ namespace Ryujinx.Graphics.Gpu.Image
/// Target layer
/// Target level
/// Target sub-region of the texture to update
- public void SetData(IMemoryOwner data, int layer, int level, Rectangle region)
+ public void SetData(MemoryOwner data, int layer, int level, Rectangle region)
{
BlacklistScale();
@@ -740,7 +739,7 @@ namespace Ryujinx.Graphics.Gpu.Image
/// Mip level to convert
/// True to convert a single slice
/// Converted data
- public IMemoryOwner ConvertToHostCompatibleFormat(ReadOnlySpan data, int level = 0, bool single = false)
+ public MemoryOwner ConvertToHostCompatibleFormat(ReadOnlySpan data, int level = 0, bool single = false)
{
int width = Info.Width;
int height = Info.Height;
@@ -755,7 +754,7 @@ namespace Ryujinx.Graphics.Gpu.Image
int sliceDepth = single ? 1 : depth;
- IMemoryOwner linear;
+ MemoryOwner linear;
if (Info.IsLinear)
{
@@ -788,7 +787,7 @@ namespace Ryujinx.Graphics.Gpu.Image
data);
}
- IMemoryOwner result = linear;
+ MemoryOwner result = linear;
// Handle compressed cases not supported by the host:
// - ASTC is usually not supported on desktop cards.
@@ -832,19 +831,19 @@ namespace Ryujinx.Graphics.Gpu.Image
case Format.Etc2RgbaUnorm:
using (result)
{
- return ETC2Decoder.DecodeRgba(result.Memory.Span, width, height, sliceDepth, levels, layers);
+ return ETC2Decoder.DecodeRgba(result.Span, width, height, sliceDepth, levels, layers);
}
case Format.Etc2RgbPtaSrgb:
case Format.Etc2RgbPtaUnorm:
using (result)
{
- return ETC2Decoder.DecodePta(result.Memory.Span, width, height, sliceDepth, levels, layers);
+ return ETC2Decoder.DecodePta(result.Span, width, height, sliceDepth, levels, layers);
}
case Format.Etc2RgbSrgb:
case Format.Etc2RgbUnorm:
using (result)
{
- return ETC2Decoder.DecodeRgb(result.Memory.Span, width, height, sliceDepth, levels, layers);
+ return ETC2Decoder.DecodeRgb(result.Span, width, height, sliceDepth, levels, layers);
}
}
}
@@ -856,43 +855,43 @@ namespace Ryujinx.Graphics.Gpu.Image
case Format.Bc1RgbaUnorm:
using (result)
{
- return BCnDecoder.DecodeBC1(result.Memory.Span, width, height, sliceDepth, levels, layers);
+ return BCnDecoder.DecodeBC1(result.Span, width, height, sliceDepth, levels, layers);
}
case Format.Bc2Srgb:
case Format.Bc2Unorm:
using (result)
{
- return BCnDecoder.DecodeBC2(result.Memory.Span, width, height, sliceDepth, levels, layers);
+ return BCnDecoder.DecodeBC2(result.Span, width, height, sliceDepth, levels, layers);
}
case Format.Bc3Srgb:
case Format.Bc3Unorm:
using (result)
{
- return BCnDecoder.DecodeBC3(result.Memory.Span, width, height, sliceDepth, levels, layers);
+ return BCnDecoder.DecodeBC3(result.Span, width, height, sliceDepth, levels, layers);
}
case Format.Bc4Snorm:
case Format.Bc4Unorm:
using (result)
{
- return BCnDecoder.DecodeBC4(result.Memory.Span, width, height, sliceDepth, levels, layers, Format == Format.Bc4Snorm);
+ return BCnDecoder.DecodeBC4(result.Span, width, height, sliceDepth, levels, layers, Format == Format.Bc4Snorm);
}
case Format.Bc5Snorm:
case Format.Bc5Unorm:
using (result)
{
- return BCnDecoder.DecodeBC5(result.Memory.Span, width, height, sliceDepth, levels, layers, Format == Format.Bc5Snorm);
+ return BCnDecoder.DecodeBC5(result.Span, width, height, sliceDepth, levels, layers, Format == Format.Bc5Snorm);
}
case Format.Bc6HSfloat:
case Format.Bc6HUfloat:
using (result)
{
- return BCnDecoder.DecodeBC6(result.Memory.Span, width, height, sliceDepth, levels, layers, Format == Format.Bc6HSfloat);
+ return BCnDecoder.DecodeBC6(result.Span, width, height, sliceDepth, levels, layers, Format == Format.Bc6HSfloat);
}
case Format.Bc7Srgb:
case Format.Bc7Unorm:
using (result)
{
- return BCnDecoder.DecodeBC7(result.Memory.Span, width, height, sliceDepth, levels, layers);
+ return BCnDecoder.DecodeBC7(result.Span, width, height, sliceDepth, levels, layers);
}
}
}
@@ -900,7 +899,7 @@ namespace Ryujinx.Graphics.Gpu.Image
{
using (result)
{
- var converted = PixelConverter.ConvertR4G4ToR4G4B4A4(result.Memory.Span, width);
+ var converted = PixelConverter.ConvertR4G4ToR4G4B4A4(result.Span, width);
if (_context.Capabilities.SupportsR4G4B4A4Format)
{
@@ -910,7 +909,7 @@ namespace Ryujinx.Graphics.Gpu.Image
{
using (converted)
{
- return PixelConverter.ConvertR4G4B4A4ToR8G8B8A8(converted.Memory.Span, width);
+ return PixelConverter.ConvertR4G4B4A4ToR8G8B8A8(converted.Span, width);
}
}
}
@@ -921,7 +920,7 @@ namespace Ryujinx.Graphics.Gpu.Image
{
using (result)
{
- return PixelConverter.ConvertR4G4B4A4ToR8G8B8A8(result.Memory.Span, width);
+ return PixelConverter.ConvertR4G4B4A4ToR8G8B8A8(result.Span, width);
}
}
}
@@ -933,24 +932,24 @@ namespace Ryujinx.Graphics.Gpu.Image
case Format.R5G6B5Unorm:
using (result)
{
- return PixelConverter.ConvertR5G6B5ToR8G8B8A8(result.Memory.Span, width);
+ return PixelConverter.ConvertR5G6B5ToR8G8B8A8(result.Span, width);
}
case Format.B5G5R5A1Unorm:
case Format.R5G5B5X1Unorm:
case Format.R5G5B5A1Unorm:
using (result)
{
- return PixelConverter.ConvertR5G5B5ToR8G8B8A8(result.Memory.Span, width, Format == Format.R5G5B5X1Unorm);
+ return PixelConverter.ConvertR5G5B5ToR8G8B8A8(result.Span, width, Format == Format.R5G5B5X1Unorm);
}
case Format.A1B5G5R5Unorm:
using (result)
{
- return PixelConverter.ConvertA1B5G5R5ToR8G8B8A8(result.Memory.Span, width);
+ return PixelConverter.ConvertA1B5G5R5ToR8G8B8A8(result.Span, width);
}
case Format.R4G4B4A4Unorm:
using (result)
{
- return PixelConverter.ConvertR4G4B4A4ToR8G8B8A8(result.Memory.Span, width);
+ return PixelConverter.ConvertR4G4B4A4ToR8G8B8A8(result.Span, width);
}
}
}
diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs
index 06ca2c5997..526fc0c246 100644
--- a/src/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs
+++ b/src/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs
@@ -1,3 +1,4 @@
+using Ryujinx.Common.Memory;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Gpu.Memory;
using Ryujinx.Graphics.Texture;
@@ -5,7 +6,6 @@ using Ryujinx.Memory;
using Ryujinx.Memory.Range;
using Ryujinx.Memory.Tracking;
using System;
-using System.Buffers;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
@@ -445,7 +445,7 @@ namespace Ryujinx.Graphics.Gpu.Image
ReadOnlySpan data = dataSpan[(offset - spanBase)..];
- IMemoryOwner result = Storage.ConvertToHostCompatibleFormat(data, info.BaseLevel + level, true);
+ MemoryOwner result = Storage.ConvertToHostCompatibleFormat(data, info.BaseLevel + level, true);
Storage.SetData(result, info.BaseLayer + layer, info.BaseLevel + level);
}
diff --git a/src/Ryujinx.Graphics.OpenGL/Image/TextureBuffer.cs b/src/Ryujinx.Graphics.OpenGL/Image/TextureBuffer.cs
index a8196541a1..22f4c04cde 100644
--- a/src/Ryujinx.Graphics.OpenGL/Image/TextureBuffer.cs
+++ b/src/Ryujinx.Graphics.OpenGL/Image/TextureBuffer.cs
@@ -1,7 +1,7 @@
using OpenTK.Graphics.OpenGL;
+using Ryujinx.Common.Memory;
using Ryujinx.Graphics.GAL;
using System;
-using System.Buffers;
namespace Ryujinx.Graphics.OpenGL.Image
{
@@ -55,9 +55,9 @@ namespace Ryujinx.Graphics.OpenGL.Image
}
///
- public void SetData(IMemoryOwner data)
+ public void SetData(MemoryOwner data)
{
- var dataSpan = data.Memory.Span;
+ var dataSpan = data.Span;
Buffer.SetData(_buffer, _bufferOffset, dataSpan[..Math.Min(dataSpan.Length, _bufferSize)]);
@@ -65,13 +65,13 @@ namespace Ryujinx.Graphics.OpenGL.Image
}
///
- public void SetData(IMemoryOwner data, int layer, int level)
+ public void SetData(MemoryOwner data, int layer, int level)
{
throw new NotSupportedException();
}
///
- public void SetData(IMemoryOwner data, int layer, int level, Rectangle region)
+ public void SetData(MemoryOwner data, int layer, int level, Rectangle region)
{
throw new NotSupportedException();
}
diff --git a/src/Ryujinx.Graphics.OpenGL/Image/TextureView.cs b/src/Ryujinx.Graphics.OpenGL/Image/TextureView.cs
index 946eb755cc..b0859c49e2 100644
--- a/src/Ryujinx.Graphics.OpenGL/Image/TextureView.cs
+++ b/src/Ryujinx.Graphics.OpenGL/Image/TextureView.cs
@@ -1,8 +1,8 @@
using OpenTK.Graphics.OpenGL;
using Ryujinx.Common;
+using Ryujinx.Common.Memory;
using Ryujinx.Graphics.GAL;
using System;
-using System.Buffers;
using System.Diagnostics;
namespace Ryujinx.Graphics.OpenGL.Image
@@ -448,13 +448,13 @@ namespace Ryujinx.Graphics.OpenGL.Image
}
}
- public void SetData(IMemoryOwner data)
+ public void SetData(MemoryOwner data)
{
using (data = EnsureDataFormat(data))
{
unsafe
{
- var dataSpan = data.Memory.Span;
+ var dataSpan = data.Span;
fixed (byte* ptr = dataSpan)
{
ReadFrom((IntPtr)ptr, dataSpan.Length);
@@ -463,13 +463,13 @@ namespace Ryujinx.Graphics.OpenGL.Image
}
}
- public void SetData(IMemoryOwner data, int layer, int level)
+ public void SetData(MemoryOwner data, int layer, int level)
{
using (data = EnsureDataFormat(data))
{
unsafe
{
- fixed (byte* ptr = data.Memory.Span)
+ fixed (byte* ptr = data.Span)
{
int width = Math.Max(Info.Width >> level, 1);
int height = Math.Max(Info.Height >> level, 1);
@@ -480,7 +480,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
}
}
- public void SetData(IMemoryOwner data, int layer, int level, Rectangle region)
+ public void SetData(MemoryOwner data, int layer, int level, Rectangle region)
{
using (data = EnsureDataFormat(data))
{
@@ -489,7 +489,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
unsafe
{
- fixed (byte* ptr = data.Memory.Span)
+ fixed (byte* ptr = data.Span)
{
ReadFrom2D(
(IntPtr)ptr,
@@ -522,13 +522,13 @@ namespace Ryujinx.Graphics.OpenGL.Image
ReadFrom2D(data, layer, level, x, y, width, height, mipSize);
}
- private IMemoryOwner EnsureDataFormat(IMemoryOwner data)
+ private MemoryOwner EnsureDataFormat(MemoryOwner data)
{
if (Format == Format.S8UintD24Unorm)
{
using (data)
{
- return FormatConverter.ConvertS8D24ToD24S8(data.Memory.Span);
+ return FormatConverter.ConvertS8D24ToD24S8(data.Span);
}
}
diff --git a/src/Ryujinx.Graphics.Vulkan/TextureBuffer.cs b/src/Ryujinx.Graphics.Vulkan/TextureBuffer.cs
index bc1a509616..073eee2ca9 100644
--- a/src/Ryujinx.Graphics.Vulkan/TextureBuffer.cs
+++ b/src/Ryujinx.Graphics.Vulkan/TextureBuffer.cs
@@ -1,7 +1,7 @@
+using Ryujinx.Common.Memory;
using Ryujinx.Graphics.GAL;
using Silk.NET.Vulkan;
using System;
-using System.Buffers;
using System.Collections.Generic;
using Format = Ryujinx.Graphics.GAL.Format;
using VkFormat = Silk.NET.Vulkan.Format;
@@ -84,20 +84,20 @@ namespace Ryujinx.Graphics.Vulkan
}
///
- public void SetData(IMemoryOwner data)
+ public void SetData(MemoryOwner data)
{
- _gd.SetBufferData(_bufferHandle, _offset, data.Memory.Span);
+ _gd.SetBufferData(_bufferHandle, _offset, data.Span);
data.Dispose();
}
///
- public void SetData(IMemoryOwner data, int layer, int level)
+ public void SetData(MemoryOwner data, int layer, int level)
{
throw new NotSupportedException();
}
///
- public void SetData(IMemoryOwner data, int layer, int level, Rectangle region)
+ public void SetData(MemoryOwner data, int layer, int level, Rectangle region)
{
throw new NotSupportedException();
}
diff --git a/src/Ryujinx.Graphics.Vulkan/TextureView.cs b/src/Ryujinx.Graphics.Vulkan/TextureView.cs
index 9b3f466627..b7b936809d 100644
--- a/src/Ryujinx.Graphics.Vulkan/TextureView.cs
+++ b/src/Ryujinx.Graphics.Vulkan/TextureView.cs
@@ -1,7 +1,7 @@
+using Ryujinx.Common.Memory;
using Ryujinx.Graphics.GAL;
using Silk.NET.Vulkan;
using System;
-using System.Buffers;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
@@ -746,23 +746,23 @@ namespace Ryujinx.Graphics.Vulkan
}
///
- public void SetData(IMemoryOwner data)
+ public void SetData(MemoryOwner data)
{
- SetData(data.Memory.Span, 0, 0, Info.GetLayers(), Info.Levels, singleSlice: false);
+ SetData(data.Span, 0, 0, Info.GetLayers(), Info.Levels, singleSlice: false);
data.Dispose();
}
///
- public void SetData(IMemoryOwner data, int layer, int level)
+ public void SetData(MemoryOwner data, int layer, int level)
{
- SetData(data.Memory.Span, layer, level, 1, 1, singleSlice: true);
+ SetData(data.Span, layer, level, 1, 1, singleSlice: true);
data.Dispose();
}
///
- public void SetData(IMemoryOwner data, int layer, int level, Rectangle region)
+ public void SetData(MemoryOwner data, int layer, int level, Rectangle region)
{
- SetData(data.Memory.Span, layer, level, 1, 1, singleSlice: true, region);
+ SetData(data.Span, layer, level, 1, 1, singleSlice: true, region);
data.Dispose();
}
diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Parcel.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Parcel.cs
index 2ca0e1aac2..1df280dce4 100644
--- a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Parcel.cs
+++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Parcel.cs
@@ -14,7 +14,7 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
{
private readonly MemoryOwner _rawDataOwner;
- private Span Raw => _rawDataOwner.Memory.Span;
+ private Span Raw => _rawDataOwner.Span;
private ref ParcelHeader Header => ref MemoryMarshal.Cast(Raw)[0];
diff --git a/src/Ryujinx.Memory/WritableRegion.cs b/src/Ryujinx.Memory/WritableRegion.cs
index 2c21ef4e80..54facb5085 100644
--- a/src/Ryujinx.Memory/WritableRegion.cs
+++ b/src/Ryujinx.Memory/WritableRegion.cs
@@ -1,5 +1,5 @@
+using Ryujinx.Common.Memory;
using System;
-using System.Buffers;
namespace Ryujinx.Memory
{
@@ -7,7 +7,7 @@ namespace Ryujinx.Memory
{
private readonly IWritableBlock _block;
private readonly ulong _va;
- private readonly IMemoryOwner _memoryOwner;
+ private readonly MemoryOwner _memoryOwner;
private readonly bool _tracked;
private bool NeedsWriteback => _block != null;
@@ -22,7 +22,7 @@ namespace Ryujinx.Memory
Memory = memory;
}
- public WritableRegion(IWritableBlock block, ulong va, IMemoryOwner memoryOwner, bool tracked = false)
+ public WritableRegion(IWritableBlock block, ulong va, MemoryOwner memoryOwner, bool tracked = false)
: this(block, va, memoryOwner.Memory, tracked)
{
_memoryOwner = memoryOwner;
From 24ee8c39f1fd8ae2dc2d92cda1cdb41e8af45f0a Mon Sep 17 00:00:00 2001
From: gdkchan
Date: Thu, 19 Sep 2024 14:38:30 -0300
Subject: [PATCH 07/15] Add support for sampler sRGB disable (#7312)
---
src/Ryujinx.Graphics.Gpu/Image/Sampler.cs | 7 +++++
.../Image/SamplerDescriptor.cs | 9 ++++++
.../Image/TextureBindingsManager.cs | 10 ++++---
src/Ryujinx.Graphics.Gpu/Image/TexturePool.cs | 28 +++++++++++++++++--
4 files changed, 48 insertions(+), 6 deletions(-)
diff --git a/src/Ryujinx.Graphics.Gpu/Image/Sampler.cs b/src/Ryujinx.Graphics.Gpu/Image/Sampler.cs
index d6a3d975b8..b007c15910 100644
--- a/src/Ryujinx.Graphics.Gpu/Image/Sampler.cs
+++ b/src/Ryujinx.Graphics.Gpu/Image/Sampler.cs
@@ -13,6 +13,11 @@ namespace Ryujinx.Graphics.Gpu.Image
///
public bool IsDisposed { get; private set; }
+ ///
+ /// True if the sampler has sRGB conversion enabled, false otherwise.
+ ///
+ public bool IsSrgb { get; }
+
///
/// Host sampler object.
///
@@ -30,6 +35,8 @@ namespace Ryujinx.Graphics.Gpu.Image
/// The Maxwell sampler descriptor
public Sampler(GpuContext context, SamplerDescriptor descriptor)
{
+ IsSrgb = descriptor.UnpackSrgb();
+
MinFilter minFilter = descriptor.UnpackMinFilter();
MagFilter magFilter = descriptor.UnpackMagFilter();
diff --git a/src/Ryujinx.Graphics.Gpu/Image/SamplerDescriptor.cs b/src/Ryujinx.Graphics.Gpu/Image/SamplerDescriptor.cs
index e04c31dfaf..836a3260c6 100644
--- a/src/Ryujinx.Graphics.Gpu/Image/SamplerDescriptor.cs
+++ b/src/Ryujinx.Graphics.Gpu/Image/SamplerDescriptor.cs
@@ -113,6 +113,15 @@ namespace Ryujinx.Graphics.Gpu.Image
return (CompareOp)(((Word0 >> 10) & 7) + 1);
}
+ ///
+ /// Unpacks the sampler sRGB format flag.
+ ///
+ /// True if the has sampler is sRGB conversion enabled, false otherwise
+ public readonly bool UnpackSrgb()
+ {
+ return (Word0 & (1 << 13)) != 0;
+ }
+
///
/// Unpacks and converts the maximum anisotropy value used for texture anisotropic filtering.
///
diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs
index ad018f1597..f96ddfb1bf 100644
--- a/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs
+++ b/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs
@@ -187,7 +187,9 @@ namespace Ryujinx.Graphics.Gpu.Image
{
(TexturePool texturePool, SamplerPool samplerPool) = GetPools();
- return (texturePool.Get(textureId), samplerPool.Get(samplerId));
+ Sampler sampler = samplerPool?.Get(samplerId);
+
+ return (texturePool.Get(textureId, sampler?.IsSrgb ?? true), sampler);
}
///
@@ -508,12 +510,12 @@ namespace Ryujinx.Graphics.Gpu.Image
state.TextureHandle = textureId;
state.SamplerHandle = samplerId;
- ref readonly TextureDescriptor descriptor = ref texturePool.GetForBinding(textureId, out Texture texture);
+ Sampler sampler = samplerPool?.Get(samplerId);
+
+ ref readonly TextureDescriptor descriptor = ref texturePool.GetForBinding(textureId, sampler?.IsSrgb ?? true, out Texture texture);
specStateMatches &= specState.MatchesTexture(stage, index, descriptor);
- Sampler sampler = samplerPool?.Get(samplerId);
-
ITexture hostTexture = texture?.GetTargetTexture(bindingInfo.Target);
ISampler hostSampler = sampler?.GetHostSampler(texture);
diff --git a/src/Ryujinx.Graphics.Gpu/Image/TexturePool.cs b/src/Ryujinx.Graphics.Gpu/Image/TexturePool.cs
index 5f43c1824e..be7cb0b893 100644
--- a/src/Ryujinx.Graphics.Gpu/Image/TexturePool.cs
+++ b/src/Ryujinx.Graphics.Gpu/Image/TexturePool.cs
@@ -227,6 +227,17 @@ namespace Ryujinx.Graphics.Gpu.Image
/// ID of the texture. This is effectively a zero-based index
/// The texture with the given ID
public override Texture Get(int id)
+ {
+ return Get(id, srgbSampler: true);
+ }
+
+ ///
+ /// Gets the texture with the given ID.
+ ///
+ /// ID of the texture. This is effectively a zero-based index
+ /// Whether the texture is being accessed with a sampler that has sRGB conversion enabled
+ /// The texture with the given ID
+ public Texture Get(int id, bool srgbSampler)
{
if ((uint)id >= Items.Length)
{
@@ -240,7 +251,7 @@ namespace Ryujinx.Graphics.Gpu.Image
SynchronizeMemory();
}
- GetInternal(id, out Texture texture);
+ GetForBinding(id, srgbSampler, out Texture texture);
return texture;
}
@@ -252,9 +263,10 @@ namespace Ryujinx.Graphics.Gpu.Image
/// This method assumes that the pool has been manually synchronized before doing binding.
///
/// ID of the texture. This is effectively a zero-based index
+ /// Whether the texture is being accessed with a sampler that has sRGB conversion enabled
/// The texture with the given ID
/// The texture descriptor with the given ID
- public ref readonly TextureDescriptor GetForBinding(int id, out Texture texture)
+ public ref readonly TextureDescriptor GetForBinding(int id, bool srgbSampler, out Texture texture)
{
if ((uint)id >= Items.Length)
{
@@ -264,6 +276,18 @@ namespace Ryujinx.Graphics.Gpu.Image
// When getting for binding, assume the pool has already been synchronized.
+ if (!srgbSampler)
+ {
+ // If the sampler does not have the sRGB bit enabled, then the texture can't use a sRGB format.
+ ref readonly TextureDescriptor tempDescriptor = ref GetDescriptorRef(id);
+
+ if (tempDescriptor.UnpackSrgb() && FormatTable.TryGetTextureFormat(tempDescriptor.UnpackFormat(), isSrgb: false, out FormatInfo formatInfo))
+ {
+ // Get a view of the texture with the right format.
+ return ref GetForBinding(id, formatInfo, out texture);
+ }
+ }
+
return ref GetInternal(id, out texture);
}
From d717aef2be6043ef5a5f064e13db444ef873f6bb Mon Sep 17 00:00:00 2001
From: gdkchan
Date: Thu, 19 Sep 2024 21:23:09 -0300
Subject: [PATCH 08/15] Shader: Assume the only remaining source is the right
one when all others are undefined (#7331)
* Shader: Assume the only remaining source is the right one when all other are undefined
* Shader cache version bump
* Improve comment
---
.../Shader/DiskCache/DiskCacheHostStorage.cs | 2 +-
.../Translation/Optimizations/Utils.cs | 22 +++++++++++++++++++
2 files changed, 23 insertions(+), 1 deletion(-)
diff --git a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs
index e1e696ca86..a5c5abd4bb 100644
--- a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs
+++ b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs
@@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
private const ushort FileFormatVersionMajor = 1;
private const ushort FileFormatVersionMinor = 2;
private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor;
- private const uint CodeGenVersion = 7320;
+ private const uint CodeGenVersion = 7331;
private const string SharedTocFileName = "shared.toc";
private const string SharedDataFileName = "shared.data";
diff --git a/src/Ryujinx.Graphics.Shader/Translation/Optimizations/Utils.cs b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/Utils.cs
index 23180ff825..6ec90fa3c5 100644
--- a/src/Ryujinx.Graphics.Shader/Translation/Optimizations/Utils.cs
+++ b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/Utils.cs
@@ -138,6 +138,8 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
// Ensure that conditions met for that branch are also met for the current one.
// Prefer the latest sources for the phi node.
+ int undefCount = 0;
+
for (int i = phiNode.SourcesCount - 1; i >= 0; i--)
{
BasicBlock phiBlock = phiNode.GetBlock(i);
@@ -159,6 +161,26 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
return match;
}
}
+ else if (phiSource.Type == OperandType.Undefined)
+ {
+ undefCount++;
+ }
+ }
+
+ // If all sources but one are undefined, we can assume that the one
+ // that is not undefined is the right one.
+
+ if (undefCount == phiNode.SourcesCount - 1)
+ {
+ for (int i = phiNode.SourcesCount - 1; i >= 0; i--)
+ {
+ Operand phiSource = phiNode.GetSource(i);
+
+ if (phiSource.Type != OperandType.Undefined)
+ {
+ return phiSource;
+ }
+ }
}
}
From 319507f2a12a6751f3ab833e498a3efd3119f806 Mon Sep 17 00:00:00 2001
From: gdkchan
Date: Sun, 22 Sep 2024 19:36:53 -0300
Subject: [PATCH 09/15] Fix quads draws after DrawTexture on Vulkan (#7336)
---
src/Ryujinx.Graphics.Vulkan/PipelineBase.cs | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs b/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs
index 86fab760f1..addad83fd5 100644
--- a/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs
+++ b/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs
@@ -636,9 +636,9 @@ namespace Ryujinx.Graphics.Vulkan
var oldStencilTestEnable = _newState.StencilTestEnable;
var oldDepthTestEnable = _newState.DepthTestEnable;
var oldDepthWriteEnable = _newState.DepthWriteEnable;
- var oldTopology = _newState.Topology;
var oldViewports = DynamicState.Viewports;
var oldViewportsCount = _newState.ViewportsCount;
+ var oldTopology = _topology;
_newState.CullMode = CullModeFlags.None;
_newState.StencilTestEnable = false;
@@ -658,7 +658,7 @@ namespace Ryujinx.Graphics.Vulkan
_newState.StencilTestEnable = oldStencilTestEnable;
_newState.DepthTestEnable = oldDepthTestEnable;
_newState.DepthWriteEnable = oldDepthWriteEnable;
- _newState.Topology = oldTopology;
+ SetPrimitiveTopology(oldTopology);
DynamicState.SetViewports(ref oldViewports, oldViewportsCount);
From 050f22977f7dc0ec193bf67c7ee66625abf36e5c Mon Sep 17 00:00:00 2001
From: Jason Youngberg <71162172+jasondyoungberg@users.noreply.github.com>
Date: Tue, 24 Sep 2024 03:10:36 -0600
Subject: [PATCH 10/15] Update bug_report.yml to provide better instructions
for finding log file (#7333)
---
.github/ISSUE_TEMPLATE/bug_report.yml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml
index 68be1f5e0d..ffb5d5f8bd 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.yml
+++ b/.github/ISSUE_TEMPLATE/bug_report.yml
@@ -23,7 +23,7 @@ body:
attributes:
label: Log file
description: A log file will help our developers to better diagnose and fix the issue.
- placeholder: Logs files can be found under "Logs" folder in Ryujinx program folder. You can drag and drop the log on to the text area
+ placeholder: Logs files can be found under "Logs" folder in Ryujinx program folder. They can also be accessed by opening Ryujinx, then going to File > Open Logs Folder. You can drag and drop the log on to the text area (do not copy paste).
validations:
required: true
- type: input
@@ -83,4 +83,4 @@ body:
- Additional info about your environment:
- Any other information relevant to your issue.
validations:
- required: false
\ No newline at end of file
+ required: false
From 04d68ca6168326f798f9acfa6a273f31ac53f3c6 Mon Sep 17 00:00:00 2001
From: riperiperi
Date: Thu, 26 Sep 2024 18:19:12 +0100
Subject: [PATCH 11/15] GPU: Ensure all clip distances are initialized when
used (#7363)
* GPU: Ensure all clip distances are initialized when used
* Shader cache version
---
.../Shader/DiskCache/DiskCacheHostStorage.cs | 2 +-
.../Translation/Translator.cs | 12 ++++++++++--
2 files changed, 11 insertions(+), 3 deletions(-)
diff --git a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs
index a5c5abd4bb..c36fc0ada2 100644
--- a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs
+++ b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs
@@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
private const ushort FileFormatVersionMajor = 1;
private const ushort FileFormatVersionMinor = 2;
private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor;
- private const uint CodeGenVersion = 7331;
+ private const uint CodeGenVersion = 7353;
private const string SharedTocFileName = "shared.toc";
private const string SharedDataFileName = "shared.data";
diff --git a/src/Ryujinx.Graphics.Shader/Translation/Translator.cs b/src/Ryujinx.Graphics.Shader/Translation/Translator.cs
index 6a31ea2e79..d1fbca0eb0 100644
--- a/src/Ryujinx.Graphics.Shader/Translation/Translator.cs
+++ b/src/Ryujinx.Graphics.Shader/Translation/Translator.cs
@@ -190,7 +190,7 @@ namespace Ryujinx.Graphics.Shader.Translation
if (stage == ShaderStage.Vertex)
{
- InitializePositionOutput(context);
+ InitializeVertexOutputs(context);
}
UInt128 usedAttributes = context.TranslatorContext.AttributeUsage.NextInputAttributesComponents;
@@ -236,12 +236,20 @@ namespace Ryujinx.Graphics.Shader.Translation
}
}
- private static void InitializePositionOutput(EmitterContext context)
+ private static void InitializeVertexOutputs(EmitterContext context)
{
for (int c = 0; c < 4; c++)
{
context.Store(StorageKind.Output, IoVariable.Position, null, Const(c), ConstF(c == 3 ? 1f : 0f));
}
+
+ if (context.Program.ClipDistancesWritten != 0)
+ {
+ for (int i = 0; i < 8; i++)
+ {
+ context.Store(StorageKind.Output, IoVariable.ClipDistance, null, Const(i), ConstF(0f));
+ }
+ }
}
private static void InitializeOutput(EmitterContext context, int location, bool perPatch)
From d86249cb0add1504c0d3a5c41527cbef43346742 Mon Sep 17 00:00:00 2001
From: MaxLastBreath <136052075+MaxLastBreath@users.noreply.github.com>
Date: Thu, 26 Sep 2024 20:33:38 +0300
Subject: [PATCH 12/15] Convert MaxTextureCacheCapacity to Dynamic
MaxTextureCacheCapacity for High Resolution Mod support. (#7307)
* Add Texture Size Capacity and 8GB Dram Build
* Update AutoDeleteCache.cs
* Dynamic Texture Cache (WIP)
* Change to float Multiplier, in-case it needs fine-tuning.
* Delete src/src.sln
* Update AutoDeleteCache.cs
* Format
* Fix Formatting
* Add DefaultTextureSizeCapacity and MemoryScaleFactor
- Also remove redundant New Lines
* Fix 4GB dram crashing
* Format newline
* Refractor
- Added Initialize() function to TextureCache and AutoDeleteCache
- Removed GetMaxTextureCapacity() function and instead added _maxCacheMemoryUsage
- Added private const MaxTextureSizeCapacity to AutoDelete Cache
- Added TextureCache.Initialize() to MemoryManager in order to fetch MaxGpuMemory at the right time.
- Moved and Changed Logger.Info for Gpu Memory to Logger.Notice and Moved it to PrintGpuInformation function.
- Opted to use a ternary operator for the Initialize function, I think it looks cleaner than bunch of if statements.
* Update src/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs
Co-authored-by: gdkchan
* maxMemory to CacheMemory, use Clamp instead of Ternary. Changed MinTextureCapacity 1GiB to 512 MiB
* Update src/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs
Co-authored-by: gdkchan
* Format comment
* comment context
* Increase TextureSize capacity for OpenGL back to 1024
- Added a new const ulong for OpenGLTextureSizeCapacity
* Fix changes from last commit.
* Adjust last OpenGL changes.
* Remove garbage VSC file
* Update src/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs
Co-authored-by: gdkchan
* Update src/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs
Co-authored-by: gdkchan
* Update src/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs
Co-authored-by: gdkchan
---------
Co-authored-by: gdkchan
---
src/Ryujinx.Graphics.GAL/Capabilities.cs | 6 +++-
.../Image/AutoDeleteCache.cs | 30 +++++++++++++++++--
.../Image/TextureCache.cs | 8 +++++
.../Memory/MemoryManager.cs | 2 ++
src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs | 3 +-
src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs | 22 +++++++++++++-
6 files changed, 65 insertions(+), 6 deletions(-)
diff --git a/src/Ryujinx.Graphics.GAL/Capabilities.cs b/src/Ryujinx.Graphics.GAL/Capabilities.cs
index a5c6eb5c8e..1eec80e51b 100644
--- a/src/Ryujinx.Graphics.GAL/Capabilities.cs
+++ b/src/Ryujinx.Graphics.GAL/Capabilities.cs
@@ -71,6 +71,8 @@ namespace Ryujinx.Graphics.GAL
public readonly int GatherBiasPrecision;
+ public readonly ulong MaximumGpuMemory;
+
public Capabilities(
TargetApi api,
string vendorName,
@@ -131,7 +133,8 @@ namespace Ryujinx.Graphics.GAL
int shaderSubgroupSize,
int storageBufferOffsetAlignment,
int textureBufferOffsetAlignment,
- int gatherBiasPrecision)
+ int gatherBiasPrecision,
+ ulong maximumGpuMemory)
{
Api = api;
VendorName = vendorName;
@@ -193,6 +196,7 @@ namespace Ryujinx.Graphics.GAL
StorageBufferOffsetAlignment = storageBufferOffsetAlignment;
TextureBufferOffsetAlignment = textureBufferOffsetAlignment;
GatherBiasPrecision = gatherBiasPrecision;
+ MaximumGpuMemory = maximumGpuMemory;
}
}
}
diff --git a/src/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs b/src/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs
index 5e66a3b543..ad6c1fecb2 100644
--- a/src/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs
+++ b/src/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs
@@ -1,3 +1,4 @@
+using System;
using System.Collections;
using System.Collections.Generic;
@@ -46,7 +47,11 @@ namespace Ryujinx.Graphics.Gpu.Image
{
private const int MinCountForDeletion = 32;
private const int MaxCapacity = 2048;
- private const ulong MaxTextureSizeCapacity = 1024 * 1024 * 1024; // MB;
+ private const ulong MinTextureSizeCapacity = 512 * 1024 * 1024;
+ private const ulong MaxTextureSizeCapacity = 4UL * 1024 * 1024 * 1024;
+ private const ulong DefaultTextureSizeCapacity = 1UL * 1024 * 1024 * 1024;
+ private const float MemoryScaleFactor = 0.50f;
+ private ulong _maxCacheMemoryUsage = 0;
private readonly LinkedList _textures;
private ulong _totalSize;
@@ -56,6 +61,25 @@ namespace Ryujinx.Graphics.Gpu.Image
private readonly Dictionary _shortCacheLookup;
+ ///
+ /// Initializes the cache, setting the maximum texture capacity for the specified GPU context.
+ ///
+ ///
+ /// If the backend GPU has 0 memory capacity, the cache size defaults to `DefaultTextureSizeCapacity`.
+ ///
+ /// The GPU context that the cache belongs to
+ public void Initialize(GpuContext context)
+ {
+ var cacheMemory = (ulong)(context.Capabilities.MaximumGpuMemory * MemoryScaleFactor);
+
+ _maxCacheMemoryUsage = Math.Clamp(cacheMemory, MinTextureSizeCapacity, MaxTextureSizeCapacity);
+
+ if (context.Capabilities.MaximumGpuMemory == 0)
+ {
+ _maxCacheMemoryUsage = DefaultTextureSizeCapacity;
+ }
+ }
+
///
/// Creates a new instance of the automatic deletion cache.
///
@@ -85,7 +109,7 @@ namespace Ryujinx.Graphics.Gpu.Image
texture.CacheNode = _textures.AddLast(texture);
if (_textures.Count > MaxCapacity ||
- (_totalSize > MaxTextureSizeCapacity && _textures.Count >= MinCountForDeletion))
+ (_totalSize > _maxCacheMemoryUsage && _textures.Count >= MinCountForDeletion))
{
RemoveLeastUsedTexture();
}
@@ -110,7 +134,7 @@ namespace Ryujinx.Graphics.Gpu.Image
_textures.AddLast(texture.CacheNode);
}
- if (_totalSize > MaxTextureSizeCapacity && _textures.Count >= MinCountForDeletion)
+ if (_totalSize > _maxCacheMemoryUsage && _textures.Count >= MinCountForDeletion)
{
RemoveLeastUsedTexture();
}
diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureCache.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureCache.cs
index 5a3319b06a..1587e20189 100644
--- a/src/Ryujinx.Graphics.Gpu/Image/TextureCache.cs
+++ b/src/Ryujinx.Graphics.Gpu/Image/TextureCache.cs
@@ -68,6 +68,14 @@ namespace Ryujinx.Graphics.Gpu.Image
_cache = new AutoDeleteCache();
}
+ ///
+ /// Initializes the cache, setting the maximum texture capacity for the specified GPU context.
+ ///
+ public void Initialize()
+ {
+ _cache.Initialize(_context);
+ }
+
///
/// Handles marking of textures written to a memory region being (partially) remapped.
///
diff --git a/src/Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs b/src/Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs
index 59a940a4f9..d1065431d1 100644
--- a/src/Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs
+++ b/src/Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs
@@ -1,4 +1,5 @@
using Ryujinx.Common.Memory;
+using Ryujinx.Graphics.Gpu.Image;
using Ryujinx.Memory;
using Ryujinx.Memory.Range;
using System;
@@ -64,6 +65,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
MemoryUnmapped += Physical.BufferCache.MemoryUnmappedHandler;
MemoryUnmapped += VirtualRangeCache.MemoryUnmappedHandler;
MemoryUnmapped += CounterCache.MemoryUnmappedHandler;
+ Physical.TextureCache.Initialize();
}
///
diff --git a/src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs b/src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs
index ba9cd45c67..9fcdf1ad79 100644
--- a/src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs
+++ b/src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs
@@ -202,7 +202,8 @@ namespace Ryujinx.Graphics.OpenGL
shaderSubgroupSize: Constants.MaxSubgroupSize,
storageBufferOffsetAlignment: HwCapabilities.StorageBufferOffsetAlignment,
textureBufferOffsetAlignment: HwCapabilities.TextureBufferOffsetAlignment,
- gatherBiasPrecision: intelWindows || amdWindows ? 8 : 0); // Precision is 8 for these vendors on Vulkan.
+ gatherBiasPrecision: intelWindows || amdWindows ? 8 : 0, // Precision is 8 for these vendors on Vulkan.
+ maximumGpuMemory: 0);
}
public void SetBufferData(BufferHandle buffer, int offset, ReadOnlySpan data)
diff --git a/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs b/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs
index 33e41ab489..0faaec82a4 100644
--- a/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs
+++ b/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs
@@ -781,7 +781,26 @@ namespace Ryujinx.Graphics.Vulkan
shaderSubgroupSize: (int)Capabilities.SubgroupSize,
storageBufferOffsetAlignment: (int)limits.MinStorageBufferOffsetAlignment,
textureBufferOffsetAlignment: (int)limits.MinTexelBufferOffsetAlignment,
- gatherBiasPrecision: IsIntelWindows || IsAmdWindows ? (int)Capabilities.SubTexelPrecisionBits : 0);
+ gatherBiasPrecision: IsIntelWindows || IsAmdWindows ? (int)Capabilities.SubTexelPrecisionBits : 0,
+ maximumGpuMemory: GetTotalGPUMemory());
+ }
+
+ private ulong GetTotalGPUMemory()
+ {
+ ulong totalMemory = 0;
+
+ Api.GetPhysicalDeviceMemoryProperties(_physicalDevice.PhysicalDevice, out PhysicalDeviceMemoryProperties memoryProperties);
+
+ for (int i = 0; i < memoryProperties.MemoryHeapCount; i++)
+ {
+ var heap = memoryProperties.MemoryHeaps[i];
+ if ((heap.Flags & MemoryHeapFlags.DeviceLocalBit) == MemoryHeapFlags.DeviceLocalBit)
+ {
+ totalMemory += heap.Size;
+ }
+ }
+
+ return totalMemory;
}
public HardwareInfo GetHardwareInfo()
@@ -865,6 +884,7 @@ namespace Ryujinx.Graphics.Vulkan
private void PrintGpuInformation()
{
Logger.Notice.Print(LogClass.Gpu, $"{GpuVendor} {GpuRenderer} ({GpuVersion})");
+ Logger.Notice.Print(LogClass.Gpu, $"GPU Memory: {GetTotalGPUMemory() / (1024 * 1024)} MiB");
}
public void Initialize(GraphicsDebugLevel logLevel)
From 5dbba07e33e83c9047dcbb701c9655edbbe89086 Mon Sep 17 00:00:00 2001
From: e2dk4r <43293320+e2dk4r@users.noreply.github.com>
Date: Sat, 28 Sep 2024 11:44:23 +0300
Subject: [PATCH 13/15] sdl: set app name (#7370)
Ryujinx was not hinting application name, so on some platforms (e.g.
Linux) volume control shows Ryujinx as 'SDL Application'. This can cause
confusion.
This commit fixes name in volume control applets on some platforms.
see: https://wiki.libsdl.org/SDL2/SDL_HINT_APP_NAME
---
src/Ryujinx.SDL2.Common/SDL2Driver.cs | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/Ryujinx.SDL2.Common/SDL2Driver.cs b/src/Ryujinx.SDL2.Common/SDL2Driver.cs
index ed6d941908..9827156d03 100644
--- a/src/Ryujinx.SDL2.Common/SDL2Driver.cs
+++ b/src/Ryujinx.SDL2.Common/SDL2Driver.cs
@@ -53,6 +53,7 @@ namespace Ryujinx.SDL2.Common
return;
}
+ SDL_SetHint(SDL_HINT_APP_NAME, "Ryujinx");
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, "1");
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1");
SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1");
From 7d158acc3b5826a08941d6e8d50d3a3897021bcd Mon Sep 17 00:00:00 2001
From: gdkchan
Date: Mon, 30 Sep 2024 11:41:07 -0300
Subject: [PATCH 14/15] Do not try to create a texture pool if shader does not
use textures (#7379)
---
src/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs b/src/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs
index 98acb6f27d..1230c05805 100644
--- a/src/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs
+++ b/src/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs
@@ -743,7 +743,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
constantBufferUsePerStageMask &= ~(1 << index);
}
- if (checkTextures)
+ if (checkTextures && _allTextures.Length > 0)
{
TexturePool pool = channel.TextureManager.GetTexturePool(poolState.TexturePoolGpuVa, poolState.TexturePoolMaximumId);
From a2c003501371463fd1f98d2e5a7602ae19c21d7c Mon Sep 17 00:00:00 2001
From: gdkchan
Date: Tue, 1 Oct 2024 07:30:57 -0300
Subject: [PATCH 15/15] Update audio renderer to REV13: Add support for
compressor statistics and volume reset (#7372)
* Update audio renderer to REV13: Add support for compressor statistics and volume reset
* XML docs
* Disable stats reset
* Wrong comment
* Fix more XML docs
* PR feedback
---
.../Renderer/Dsp/Command/CompressorCommand.cs | 45 +++++++++++----
.../Dsp/Command/LimiterCommandVersion1.cs | 46 +++++++--------
.../Dsp/Command/LimiterCommandVersion2.cs | 48 ++++++++--------
.../Parameter/Effect/CompressorParameter.cs | 11 +++-
.../Parameter/Effect/CompressorStatistics.cs | 38 +++++++++++++
.../ISplitterDestinationInParameter.cs | 5 ++
.../SplitterDestinationInParameterVersion1.cs | 9 ++-
.../SplitterDestinationInParameterVersion2.cs | 9 ++-
.../Renderer/Server/BehaviourContext.cs | 19 ++++++-
.../Renderer/Server/CommandBuffer.cs | 13 ++++-
.../Renderer/Server/CommandGenerator.cs | 16 +++++-
.../CommandProcessingTimeEstimatorVersion5.cs | 56 ++++++++++++++-----
.../Server/Effect/CompressorEffect.cs | 13 +++++
.../Server/Splitter/SplitterContext.cs | 9 ++-
.../Server/Splitter/SplitterDestination.cs | 7 ++-
.../Splitter/SplitterDestinationVersion1.cs | 6 +-
.../Splitter/SplitterDestinationVersion2.cs | 6 +-
src/Ryujinx.Horizon/Sdk/Audio/AudioResult.cs | 1 +
.../Sdk/Audio/Detail/AudioDevice.cs | 42 ++++++++++++++
.../Renderer/Server/BehaviourContextTests.cs | 41 ++++++++++++++
20 files changed, 352 insertions(+), 88 deletions(-)
create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/Effect/CompressorStatistics.cs
diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/CompressorCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/CompressorCommand.cs
index 09f415d20c..33f61e6a51 100644
--- a/src/Ryujinx.Audio/Renderer/Dsp/Command/CompressorCommand.cs
+++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/CompressorCommand.cs
@@ -1,9 +1,11 @@
using Ryujinx.Audio.Renderer.Dsp.Effect;
using Ryujinx.Audio.Renderer.Dsp.State;
+using Ryujinx.Audio.Renderer.Parameter;
using Ryujinx.Audio.Renderer.Parameter.Effect;
using Ryujinx.Audio.Renderer.Server.Effect;
using System;
using System.Diagnostics;
+using System.Runtime.InteropServices;
namespace Ryujinx.Audio.Renderer.Dsp.Command
{
@@ -21,18 +23,20 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
public CompressorParameter Parameter => _parameter;
public Memory State { get; }
+ public Memory ResultState { get; }
public ushort[] OutputBufferIndices { get; }
public ushort[] InputBufferIndices { get; }
public bool IsEffectEnabled { get; }
private CompressorParameter _parameter;
- public CompressorCommand(uint bufferOffset, CompressorParameter parameter, Memory state, bool isEnabled, int nodeId)
+ public CompressorCommand(uint bufferOffset, CompressorParameter parameter, Memory state, Memory resultState, bool isEnabled, int nodeId)
{
Enabled = true;
NodeId = nodeId;
_parameter = parameter;
State = state;
+ ResultState = resultState;
IsEffectEnabled = isEnabled;
@@ -71,9 +75,16 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
if (IsEffectEnabled && _parameter.IsChannelCountValid())
{
- Span inputBuffers = stackalloc IntPtr[Parameter.ChannelCount];
- Span outputBuffers = stackalloc IntPtr[Parameter.ChannelCount];
- Span channelInput = stackalloc float[Parameter.ChannelCount];
+ if (!ResultState.IsEmpty && _parameter.StatisticsReset)
+ {
+ ref CompressorStatistics statistics = ref MemoryMarshal.Cast(ResultState.Span[0].SpecificData)[0];
+
+ statistics.Reset(_parameter.ChannelCount);
+ }
+
+ Span inputBuffers = stackalloc IntPtr[_parameter.ChannelCount];
+ Span outputBuffers = stackalloc IntPtr[_parameter.ChannelCount];
+ Span channelInput = stackalloc float[_parameter.ChannelCount];
ExponentialMovingAverage inputMovingAverage = state.InputMovingAverage;
float unknown4 = state.Unknown4;
ExponentialMovingAverage compressionGainAverage = state.CompressionGainAverage;
@@ -92,7 +103,8 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
channelInput[channelIndex] = *((float*)inputBuffers[channelIndex] + sampleIndex);
}
- float newMean = inputMovingAverage.Update(FloatingPointHelper.MeanSquare(channelInput), _parameter.InputGain);
+ float mean = FloatingPointHelper.MeanSquare(channelInput);
+ float newMean = inputMovingAverage.Update(mean, _parameter.InputGain);
float y = FloatingPointHelper.Log10(newMean) * 10.0f;
float z = 1.0f;
@@ -111,7 +123,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
if (y >= state.Unknown14)
{
- tmpGain = ((1.0f / Parameter.Ratio) - 1.0f) * (y - Parameter.Threshold);
+ tmpGain = ((1.0f / _parameter.Ratio) - 1.0f) * (y - _parameter.Threshold);
}
else
{
@@ -126,7 +138,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
if ((unknown4 - z) <= 0.08f)
{
- compressionEmaAlpha = Parameter.ReleaseCoefficient;
+ compressionEmaAlpha = _parameter.ReleaseCoefficient;
if ((unknown4 - z) >= -0.08f)
{
@@ -140,18 +152,31 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
}
else
{
- compressionEmaAlpha = Parameter.AttackCoefficient;
+ compressionEmaAlpha = _parameter.AttackCoefficient;
}
float compressionGain = compressionGainAverage.Update(z, compressionEmaAlpha);
- for (int channelIndex = 0; channelIndex < Parameter.ChannelCount; channelIndex++)
+ for (int channelIndex = 0; channelIndex < _parameter.ChannelCount; channelIndex++)
{
*((float*)outputBuffers[channelIndex] + sampleIndex) = channelInput[channelIndex] * compressionGain * state.OutputGain;
}
unknown4 = unknown4New;
previousCompressionEmaAlpha = compressionEmaAlpha;
+
+ if (!ResultState.IsEmpty)
+ {
+ ref CompressorStatistics statistics = ref MemoryMarshal.Cast(ResultState.Span[0].SpecificData)[0];
+
+ statistics.MinimumGain = MathF.Min(statistics.MinimumGain, compressionGain * state.OutputGain);
+ statistics.MaximumMean = MathF.Max(statistics.MaximumMean, mean);
+
+ for (int channelIndex = 0; channelIndex < _parameter.ChannelCount; channelIndex++)
+ {
+ statistics.LastSamples[channelIndex] = MathF.Abs(channelInput[channelIndex] * (1f / 32768f));
+ }
+ }
}
state.InputMovingAverage = inputMovingAverage;
@@ -161,7 +186,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
}
else
{
- for (int i = 0; i < Parameter.ChannelCount; i++)
+ for (int i = 0; i < _parameter.ChannelCount; i++)
{
if (InputBufferIndices[i] != OutputBufferIndices[i])
{
diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion1.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion1.cs
index 3ba0b5884d..06e9321997 100644
--- a/src/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion1.cs
+++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion1.cs
@@ -38,10 +38,10 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
InputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
OutputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
- for (int i = 0; i < Parameter.ChannelCount; i++)
+ for (int i = 0; i < _parameter.ChannelCount; i++)
{
- InputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Input[i]);
- OutputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Output[i]);
+ InputBufferIndices[i] = (ushort)(bufferOffset + _parameter.Input[i]);
+ OutputBufferIndices[i] = (ushort)(bufferOffset + _parameter.Output[i]);
}
}
@@ -51,11 +51,11 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
if (IsEffectEnabled)
{
- if (Parameter.Status == UsageState.Invalid)
+ if (_parameter.Status == UsageState.Invalid)
{
state = new LimiterState(ref _parameter, WorkBuffer);
}
- else if (Parameter.Status == UsageState.New)
+ else if (_parameter.Status == UsageState.New)
{
LimiterState.UpdateParameter(ref _parameter);
}
@@ -66,56 +66,56 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
private unsafe void ProcessLimiter(CommandList context, ref LimiterState state)
{
- Debug.Assert(Parameter.IsChannelCountValid());
+ Debug.Assert(_parameter.IsChannelCountValid());
- if (IsEffectEnabled && Parameter.IsChannelCountValid())
+ if (IsEffectEnabled && _parameter.IsChannelCountValid())
{
- Span inputBuffers = stackalloc IntPtr[Parameter.ChannelCount];
- Span outputBuffers = stackalloc IntPtr[Parameter.ChannelCount];
+ Span inputBuffers = stackalloc IntPtr[_parameter.ChannelCount];
+ Span outputBuffers = stackalloc IntPtr[_parameter.ChannelCount];
- for (int i = 0; i < Parameter.ChannelCount; i++)
+ for (int i = 0; i < _parameter.ChannelCount; i++)
{
inputBuffers[i] = context.GetBufferPointer(InputBufferIndices[i]);
outputBuffers[i] = context.GetBufferPointer(OutputBufferIndices[i]);
}
- for (int channelIndex = 0; channelIndex < Parameter.ChannelCount; channelIndex++)
+ for (int channelIndex = 0; channelIndex < _parameter.ChannelCount; channelIndex++)
{
for (int sampleIndex = 0; sampleIndex < context.SampleCount; sampleIndex++)
{
float rawInputSample = *((float*)inputBuffers[channelIndex] + sampleIndex);
- float inputSample = (rawInputSample / short.MaxValue) * Parameter.InputGain;
+ float inputSample = (rawInputSample / short.MaxValue) * _parameter.InputGain;
float sampleInputMax = Math.Abs(inputSample);
- float inputCoefficient = Parameter.ReleaseCoefficient;
+ float inputCoefficient = _parameter.ReleaseCoefficient;
if (sampleInputMax > state.DetectorAverage[channelIndex].Read())
{
- inputCoefficient = Parameter.AttackCoefficient;
+ inputCoefficient = _parameter.AttackCoefficient;
}
float detectorValue = state.DetectorAverage[channelIndex].Update(sampleInputMax, inputCoefficient);
float attenuation = 1.0f;
- if (detectorValue > Parameter.Threshold)
+ if (detectorValue > _parameter.Threshold)
{
- attenuation = Parameter.Threshold / detectorValue;
+ attenuation = _parameter.Threshold / detectorValue;
}
- float outputCoefficient = Parameter.ReleaseCoefficient;
+ float outputCoefficient = _parameter.ReleaseCoefficient;
if (state.CompressionGainAverage[channelIndex].Read() > attenuation)
{
- outputCoefficient = Parameter.AttackCoefficient;
+ outputCoefficient = _parameter.AttackCoefficient;
}
float compressionGain = state.CompressionGainAverage[channelIndex].Update(attenuation, outputCoefficient);
- ref float delayedSample = ref state.DelayedSampleBuffer[channelIndex * Parameter.DelayBufferSampleCountMax + state.DelayedSampleBufferPosition[channelIndex]];
+ ref float delayedSample = ref state.DelayedSampleBuffer[channelIndex * _parameter.DelayBufferSampleCountMax + state.DelayedSampleBufferPosition[channelIndex]];
- float outputSample = delayedSample * compressionGain * Parameter.OutputGain;
+ float outputSample = delayedSample * compressionGain * _parameter.OutputGain;
*((float*)outputBuffers[channelIndex] + sampleIndex) = outputSample * short.MaxValue;
@@ -123,16 +123,16 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
state.DelayedSampleBufferPosition[channelIndex]++;
- while (state.DelayedSampleBufferPosition[channelIndex] >= Parameter.DelayBufferSampleCountMin)
+ while (state.DelayedSampleBufferPosition[channelIndex] >= _parameter.DelayBufferSampleCountMin)
{
- state.DelayedSampleBufferPosition[channelIndex] -= Parameter.DelayBufferSampleCountMin;
+ state.DelayedSampleBufferPosition[channelIndex] -= _parameter.DelayBufferSampleCountMin;
}
}
}
}
else
{
- for (int i = 0; i < Parameter.ChannelCount; i++)
+ for (int i = 0; i < _parameter.ChannelCount; i++)
{
if (InputBufferIndices[i] != OutputBufferIndices[i])
{
diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion2.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion2.cs
index f6e1654dd3..ed0538c061 100644
--- a/src/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion2.cs
+++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion2.cs
@@ -49,10 +49,10 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
InputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
OutputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
- for (int i = 0; i < Parameter.ChannelCount; i++)
+ for (int i = 0; i < _parameter.ChannelCount; i++)
{
- InputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Input[i]);
- OutputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Output[i]);
+ InputBufferIndices[i] = (ushort)(bufferOffset + _parameter.Input[i]);
+ OutputBufferIndices[i] = (ushort)(bufferOffset + _parameter.Output[i]);
}
}
@@ -62,11 +62,11 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
if (IsEffectEnabled)
{
- if (Parameter.Status == UsageState.Invalid)
+ if (_parameter.Status == UsageState.Invalid)
{
state = new LimiterState(ref _parameter, WorkBuffer);
}
- else if (Parameter.Status == UsageState.New)
+ else if (_parameter.Status == UsageState.New)
{
LimiterState.UpdateParameter(ref _parameter);
}
@@ -77,63 +77,63 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
private unsafe void ProcessLimiter(CommandList context, ref LimiterState state)
{
- Debug.Assert(Parameter.IsChannelCountValid());
+ Debug.Assert(_parameter.IsChannelCountValid());
- if (IsEffectEnabled && Parameter.IsChannelCountValid())
+ if (IsEffectEnabled && _parameter.IsChannelCountValid())
{
- if (!ResultState.IsEmpty && Parameter.StatisticsReset)
+ if (!ResultState.IsEmpty && _parameter.StatisticsReset)
{
ref LimiterStatistics statistics = ref MemoryMarshal.Cast(ResultState.Span[0].SpecificData)[0];
statistics.Reset();
}
- Span inputBuffers = stackalloc IntPtr[Parameter.ChannelCount];
- Span outputBuffers = stackalloc IntPtr[Parameter.ChannelCount];
+ Span inputBuffers = stackalloc IntPtr[_parameter.ChannelCount];
+ Span outputBuffers = stackalloc IntPtr[_parameter.ChannelCount];
- for (int i = 0; i < Parameter.ChannelCount; i++)
+ for (int i = 0; i < _parameter.ChannelCount; i++)
{
inputBuffers[i] = context.GetBufferPointer(InputBufferIndices[i]);
outputBuffers[i] = context.GetBufferPointer(OutputBufferIndices[i]);
}
- for (int channelIndex = 0; channelIndex < Parameter.ChannelCount; channelIndex++)
+ for (int channelIndex = 0; channelIndex < _parameter.ChannelCount; channelIndex++)
{
for (int sampleIndex = 0; sampleIndex < context.SampleCount; sampleIndex++)
{
float rawInputSample = *((float*)inputBuffers[channelIndex] + sampleIndex);
- float inputSample = (rawInputSample / short.MaxValue) * Parameter.InputGain;
+ float inputSample = (rawInputSample / short.MaxValue) * _parameter.InputGain;
float sampleInputMax = Math.Abs(inputSample);
- float inputCoefficient = Parameter.ReleaseCoefficient;
+ float inputCoefficient = _parameter.ReleaseCoefficient;
if (sampleInputMax > state.DetectorAverage[channelIndex].Read())
{
- inputCoefficient = Parameter.AttackCoefficient;
+ inputCoefficient = _parameter.AttackCoefficient;
}
float detectorValue = state.DetectorAverage[channelIndex].Update(sampleInputMax, inputCoefficient);
float attenuation = 1.0f;
- if (detectorValue > Parameter.Threshold)
+ if (detectorValue > _parameter.Threshold)
{
- attenuation = Parameter.Threshold / detectorValue;
+ attenuation = _parameter.Threshold / detectorValue;
}
- float outputCoefficient = Parameter.ReleaseCoefficient;
+ float outputCoefficient = _parameter.ReleaseCoefficient;
if (state.CompressionGainAverage[channelIndex].Read() > attenuation)
{
- outputCoefficient = Parameter.AttackCoefficient;
+ outputCoefficient = _parameter.AttackCoefficient;
}
float compressionGain = state.CompressionGainAverage[channelIndex].Update(attenuation, outputCoefficient);
- ref float delayedSample = ref state.DelayedSampleBuffer[channelIndex * Parameter.DelayBufferSampleCountMax + state.DelayedSampleBufferPosition[channelIndex]];
+ ref float delayedSample = ref state.DelayedSampleBuffer[channelIndex * _parameter.DelayBufferSampleCountMax + state.DelayedSampleBufferPosition[channelIndex]];
- float outputSample = delayedSample * compressionGain * Parameter.OutputGain;
+ float outputSample = delayedSample * compressionGain * _parameter.OutputGain;
*((float*)outputBuffers[channelIndex] + sampleIndex) = outputSample * short.MaxValue;
@@ -141,9 +141,9 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
state.DelayedSampleBufferPosition[channelIndex]++;
- while (state.DelayedSampleBufferPosition[channelIndex] >= Parameter.DelayBufferSampleCountMin)
+ while (state.DelayedSampleBufferPosition[channelIndex] >= _parameter.DelayBufferSampleCountMin)
{
- state.DelayedSampleBufferPosition[channelIndex] -= Parameter.DelayBufferSampleCountMin;
+ state.DelayedSampleBufferPosition[channelIndex] -= _parameter.DelayBufferSampleCountMin;
}
if (!ResultState.IsEmpty)
@@ -158,7 +158,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
}
else
{
- for (int i = 0; i < Parameter.ChannelCount; i++)
+ for (int i = 0; i < _parameter.ChannelCount; i++)
{
if (InputBufferIndices[i] != OutputBufferIndices[i])
{
diff --git a/src/Ryujinx.Audio/Renderer/Parameter/Effect/CompressorParameter.cs b/src/Ryujinx.Audio/Renderer/Parameter/Effect/CompressorParameter.cs
index b403f13703..c00118e49a 100644
--- a/src/Ryujinx.Audio/Renderer/Parameter/Effect/CompressorParameter.cs
+++ b/src/Ryujinx.Audio/Renderer/Parameter/Effect/CompressorParameter.cs
@@ -90,9 +90,16 @@ namespace Ryujinx.Audio.Renderer.Parameter.Effect
public bool MakeupGainEnabled;
///
- /// Reserved/padding.
+ /// Indicate if the compressor effect should output statistics.
///
- private Array2 _reserved;
+ [MarshalAs(UnmanagedType.I1)]
+ public bool StatisticsEnabled;
+
+ ///
+ /// Indicate to the DSP that the user did a statistics reset.
+ ///
+ [MarshalAs(UnmanagedType.I1)]
+ public bool StatisticsReset;
///
/// Check if the is valid.
diff --git a/src/Ryujinx.Audio/Renderer/Parameter/Effect/CompressorStatistics.cs b/src/Ryujinx.Audio/Renderer/Parameter/Effect/CompressorStatistics.cs
new file mode 100644
index 0000000000..65335e2d99
--- /dev/null
+++ b/src/Ryujinx.Audio/Renderer/Parameter/Effect/CompressorStatistics.cs
@@ -0,0 +1,38 @@
+using Ryujinx.Common.Memory;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Audio.Renderer.Parameter.Effect
+{
+ ///
+ /// Effect result state for .
+ ///
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ public struct CompressorStatistics
+ {
+ ///
+ /// Maximum input mean value since last reset.
+ ///
+ public float MaximumMean;
+
+ ///
+ /// Minimum output gain since last reset.
+ ///
+ public float MinimumGain;
+
+ ///
+ /// Last processed input sample, per channel.
+ ///
+ public Array6 LastSamples;
+
+ ///
+ /// Reset the statistics.
+ ///
+ /// Number of channels to reset.
+ public void Reset(ushort channelCount)
+ {
+ MaximumMean = 0.0f;
+ MinimumGain = 1.0f;
+ LastSamples.AsSpan()[..channelCount].Clear();
+ }
+ }
+}
diff --git a/src/Ryujinx.Audio/Renderer/Parameter/ISplitterDestinationInParameter.cs b/src/Ryujinx.Audio/Renderer/Parameter/ISplitterDestinationInParameter.cs
index 807232f208..7ee49f11a6 100644
--- a/src/Ryujinx.Audio/Renderer/Parameter/ISplitterDestinationInParameter.cs
+++ b/src/Ryujinx.Audio/Renderer/Parameter/ISplitterDestinationInParameter.cs
@@ -28,6 +28,11 @@ namespace Ryujinx.Audio.Renderer.Parameter
///
bool IsUsed { get; }
+ ///
+ /// Set to true to force resetting the previous mix volumes.
+ ///
+ bool ResetPrevVolume { get; }
+
///
/// Mix buffer volumes.
///
diff --git a/src/Ryujinx.Audio/Renderer/Parameter/SplitterDestinationInParameterVersion1.cs b/src/Ryujinx.Audio/Renderer/Parameter/SplitterDestinationInParameterVersion1.cs
index 029c001ea9..f346efcb05 100644
--- a/src/Ryujinx.Audio/Renderer/Parameter/SplitterDestinationInParameterVersion1.cs
+++ b/src/Ryujinx.Audio/Renderer/Parameter/SplitterDestinationInParameterVersion1.cs
@@ -37,10 +37,16 @@ namespace Ryujinx.Audio.Renderer.Parameter
[MarshalAs(UnmanagedType.I1)]
public bool IsUsed;
+ ///
+ /// Set to true to force resetting the previous mix volumes.
+ ///
+ [MarshalAs(UnmanagedType.I1)]
+ public bool ResetPrevVolume;
+
///
/// Reserved/padding.
///
- private unsafe fixed byte _reserved[3];
+ private unsafe fixed byte _reserved[2];
[StructLayout(LayoutKind.Sequential, Size = sizeof(float) * Constants.MixBufferCountMax, Pack = 1)]
private struct MixArray { }
@@ -58,6 +64,7 @@ namespace Ryujinx.Audio.Renderer.Parameter
readonly Array2 ISplitterDestinationInParameter.BiquadFilters => default;
readonly bool ISplitterDestinationInParameter.IsUsed => IsUsed;
+ readonly bool ISplitterDestinationInParameter.ResetPrevVolume => ResetPrevVolume;
///
/// The expected constant of any input header.
diff --git a/src/Ryujinx.Audio/Renderer/Parameter/SplitterDestinationInParameterVersion2.cs b/src/Ryujinx.Audio/Renderer/Parameter/SplitterDestinationInParameterVersion2.cs
index 312be8b707..1d867919d4 100644
--- a/src/Ryujinx.Audio/Renderer/Parameter/SplitterDestinationInParameterVersion2.cs
+++ b/src/Ryujinx.Audio/Renderer/Parameter/SplitterDestinationInParameterVersion2.cs
@@ -42,10 +42,16 @@ namespace Ryujinx.Audio.Renderer.Parameter
[MarshalAs(UnmanagedType.I1)]
public bool IsUsed;
+ ///
+ /// Set to true to force resetting the previous mix volumes.
+ ///
+ [MarshalAs(UnmanagedType.I1)]
+ public bool ResetPrevVolume;
+
///
/// Reserved/padding.
///
- private unsafe fixed byte _reserved[11];
+ private unsafe fixed byte _reserved[10];
[StructLayout(LayoutKind.Sequential, Size = sizeof(float) * Constants.MixBufferCountMax, Pack = 1)]
private struct MixArray { }
@@ -63,6 +69,7 @@ namespace Ryujinx.Audio.Renderer.Parameter
readonly Array2 ISplitterDestinationInParameter.BiquadFilters => BiquadFilters;
readonly bool ISplitterDestinationInParameter.IsUsed => IsUsed;
+ readonly bool ISplitterDestinationInParameter.ResetPrevVolume => ResetPrevVolume;
///
/// The expected constant of any input header.
diff --git a/src/Ryujinx.Audio/Renderer/Server/BehaviourContext.cs b/src/Ryujinx.Audio/Renderer/Server/BehaviourContext.cs
index 32c7de6cfb..f725eb9f3e 100644
--- a/src/Ryujinx.Audio/Renderer/Server/BehaviourContext.cs
+++ b/src/Ryujinx.Audio/Renderer/Server/BehaviourContext.cs
@@ -108,10 +108,18 @@ namespace Ryujinx.Audio.Renderer.Server
/// This was added in system update 17.0.0
public const int Revision12 = 12 << 24;
+ ///
+ /// REV13:
+ /// The compressor effect can now output statistics.
+ /// Splitter destinations now explicitly reset the previous mix volume, instead of doing so on first use.
+ ///
+ /// This was added in system update 18.0.0
+ public const int Revision13 = 13 << 24;
+
///
/// Last revision supported by the implementation.
///
- public const int LastRevision = Revision12;
+ public const int LastRevision = Revision13;
///
/// Target revision magic supported by the implementation.
@@ -384,6 +392,15 @@ namespace Ryujinx.Audio.Renderer.Server
return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision12);
}
+ ///
+ /// Check if the audio renderer should support explicit previous mix volume reset on splitter.
+ ///
+ /// True if the audio renderer support explicit previous mix volume reset on splitter
+ public bool IsSplitterPrevVolumeResetSupported()
+ {
+ return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision13);
+ }
+
///
/// Get the version of the .
///
diff --git a/src/Ryujinx.Audio/Renderer/Server/CommandBuffer.cs b/src/Ryujinx.Audio/Renderer/Server/CommandBuffer.cs
index 702f05462f..4c353b37e1 100644
--- a/src/Ryujinx.Audio/Renderer/Server/CommandBuffer.cs
+++ b/src/Ryujinx.Audio/Renderer/Server/CommandBuffer.cs
@@ -583,11 +583,20 @@ namespace Ryujinx.Audio.Renderer.Server
}
}
- public void GenerateCompressorEffect(uint bufferOffset, CompressorParameter parameter, Memory state, bool isEnabled, int nodeId)
+ ///
+ /// Generate a new .
+ ///
+ /// The target buffer offset.
+ /// The compressor parameter.
+ /// The compressor state.
+ /// The DSP effect result state.
+ /// Set to true if the effect should be active.
+ /// The node id associated to this command.
+ public void GenerateCompressorEffect(uint bufferOffset, CompressorParameter parameter, Memory state, Memory effectResultState, bool isEnabled, int nodeId)
{
if (parameter.IsChannelCountValid())
{
- CompressorCommand command = new(bufferOffset, parameter, state, isEnabled, nodeId);
+ CompressorCommand command = new(bufferOffset, parameter, state, effectResultState, isEnabled, nodeId);
command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);
diff --git a/src/Ryujinx.Audio/Renderer/Server/CommandGenerator.cs b/src/Ryujinx.Audio/Renderer/Server/CommandGenerator.cs
index d798230c1d..0b789537a7 100644
--- a/src/Ryujinx.Audio/Renderer/Server/CommandGenerator.cs
+++ b/src/Ryujinx.Audio/Renderer/Server/CommandGenerator.cs
@@ -735,14 +735,26 @@ namespace Ryujinx.Audio.Renderer.Server
}
}
- private void GenerateCompressorEffect(uint bufferOffset, CompressorEffect effect, int nodeId)
+ private void GenerateCompressorEffect(uint bufferOffset, CompressorEffect effect, int nodeId, int effectId)
{
Debug.Assert(effect.Type == EffectType.Compressor);
+ Memory dspResultState;
+
+ if (effect.Parameter.StatisticsEnabled)
+ {
+ dspResultState = _effectContext.GetDspStateMemory(effectId);
+ }
+ else
+ {
+ dspResultState = Memory.Empty;
+ }
+
_commandBuffer.GenerateCompressorEffect(
bufferOffset,
effect.Parameter,
effect.State,
+ dspResultState,
effect.IsEnabled,
nodeId);
}
@@ -795,7 +807,7 @@ namespace Ryujinx.Audio.Renderer.Server
GenerateCaptureEffect(mix.BufferOffset, (CaptureBufferEffect)effect, nodeId);
break;
case EffectType.Compressor:
- GenerateCompressorEffect(mix.BufferOffset, (CompressorEffect)effect, nodeId);
+ GenerateCompressorEffect(mix.BufferOffset, (CompressorEffect)effect, nodeId, effectId);
break;
default:
throw new NotImplementedException($"Unsupported effect type {effect.Type}");
diff --git a/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion5.cs b/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion5.cs
index 06f135a886..bc9ba073d6 100644
--- a/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion5.cs
+++ b/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion5.cs
@@ -169,14 +169,28 @@ namespace Ryujinx.Audio.Renderer.Server
{
if (command.Enabled)
{
- return command.Parameter.ChannelCount switch
+ if (command.Parameter.StatisticsEnabled)
{
- 1 => 34431,
- 2 => 44253,
- 4 => 63827,
- 6 => 83361,
- _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"),
- };
+ return command.Parameter.ChannelCount switch
+ {
+ 1 => 22100,
+ 2 => 33211,
+ 4 => 41587,
+ 6 => 58819,
+ _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"),
+ };
+ }
+ else
+ {
+ return command.Parameter.ChannelCount switch
+ {
+ 1 => 19052,
+ 2 => 29852,
+ 4 => 37904,
+ 6 => 55020,
+ _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"),
+ };
+ }
}
return command.Parameter.ChannelCount switch
@@ -191,14 +205,28 @@ namespace Ryujinx.Audio.Renderer.Server
if (command.Enabled)
{
- return command.Parameter.ChannelCount switch
+ if (command.Parameter.StatisticsEnabled)
{
- 1 => 51095,
- 2 => 65693,
- 4 => 95383,
- 6 => 124510,
- _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"),
- };
+ return command.Parameter.ChannelCount switch
+ {
+ 1 => 32518,
+ 2 => 49102,
+ 4 => 61685,
+ 6 => 87250,
+ _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"),
+ };
+ }
+ else
+ {
+ return command.Parameter.ChannelCount switch
+ {
+ 1 => 27963,
+ 2 => 44016,
+ 4 => 56183,
+ 6 => 81862,
+ _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"),
+ };
+ }
}
return command.Parameter.ChannelCount switch
diff --git a/src/Ryujinx.Audio/Renderer/Server/Effect/CompressorEffect.cs b/src/Ryujinx.Audio/Renderer/Server/Effect/CompressorEffect.cs
index eff60e7da8..de0f44e475 100644
--- a/src/Ryujinx.Audio/Renderer/Server/Effect/CompressorEffect.cs
+++ b/src/Ryujinx.Audio/Renderer/Server/Effect/CompressorEffect.cs
@@ -62,6 +62,19 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
UpdateUsageStateForCommandGeneration();
Parameter.Status = UsageState.Enabled;
+ Parameter.StatisticsReset = false;
+ }
+
+ public override void InitializeResultState(ref EffectResultState state)
+ {
+ ref CompressorStatistics statistics = ref MemoryMarshal.Cast(state.SpecificData)[0];
+
+ statistics.Reset(Parameter.ChannelCount);
+ }
+
+ public override void UpdateResultState(ref EffectResultState destState, ref EffectResultState srcState)
+ {
+ destState = srcState;
}
}
}
diff --git a/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterContext.cs b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterContext.cs
index a7b82a6bdc..6dddb43158 100644
--- a/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterContext.cs
+++ b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterContext.cs
@@ -51,6 +51,11 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
///
public bool IsBugFixed { get; private set; }
+ ///
+ /// If set to true, the previous mix volume is explicitly resetted using the input parameter, instead of implicitly on first use.
+ ///
+ public bool IsSplitterPrevVolumeResetSupported { get; private set; }
+
///
/// Initialize .
///
@@ -139,6 +144,8 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
}
}
+ IsSplitterPrevVolumeResetSupported = behaviourContext.IsSplitterPrevVolumeResetSupported();
+
SplitterState.InitializeSplitters(splitters.Span);
Setup(splitters, splitterDestinationsV1, splitterDestinationsV2, behaviourContext.IsSplitterBugFixed());
@@ -277,7 +284,7 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
{
SplitterDestination destination = GetDestination(parameter.Id);
- destination.Update(parameter);
+ destination.Update(parameter, IsSplitterPrevVolumeResetSupported);
}
return true;
diff --git a/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestination.cs b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestination.cs
index 36dfa5e413..1a46d41fde 100644
--- a/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestination.cs
+++ b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestination.cs
@@ -184,15 +184,16 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
/// Update the splitter destination data from user parameter.
///
/// The user parameter.
- public void Update(in T parameter) where T : ISplitterDestinationInParameter
+ /// Indicates that the audio renderer revision in use supports explicitly resetting the volume.
+ public void Update(in T parameter, bool isPrevVolumeResetSupported) where T : ISplitterDestinationInParameter
{
if (Unsafe.IsNullRef(ref _v2))
{
- _v1.Update(parameter);
+ _v1.Update(parameter, isPrevVolumeResetSupported);
}
else
{
- _v2.Update(parameter);
+ _v2.Update(parameter, isPrevVolumeResetSupported);
}
}
diff --git a/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestinationVersion1.cs b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestinationVersion1.cs
index 5d2b8fb0fe..ce8f33685f 100644
--- a/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestinationVersion1.cs
+++ b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestinationVersion1.cs
@@ -93,7 +93,8 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
/// Update the from user parameter.
///
/// The user parameter.
- public void Update(in T parameter) where T : ISplitterDestinationInParameter
+ /// Indicates that the audio renderer revision in use supports explicitly resetting the volume.
+ public void Update(in T parameter, bool isPrevVolumeResetSupported) where T : ISplitterDestinationInParameter
{
Debug.Assert(Id == parameter.Id);
@@ -103,7 +104,8 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
parameter.MixBufferVolume.CopyTo(MixBufferVolume);
- if (!IsUsed && parameter.IsUsed)
+ bool resetPrevVolume = isPrevVolumeResetSupported ? parameter.ResetPrevVolume : !IsUsed && parameter.IsUsed;
+ if (resetPrevVolume)
{
MixBufferVolume.CopyTo(PreviousMixBufferVolume);
diff --git a/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestinationVersion2.cs b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestinationVersion2.cs
index f9487909d4..5f96ef3aa5 100644
--- a/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestinationVersion2.cs
+++ b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestinationVersion2.cs
@@ -98,7 +98,8 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
/// Update the from user parameter.
///
/// The user parameter.
- public void Update(in T parameter) where T : ISplitterDestinationInParameter
+ /// Indicates that the audio renderer revision in use supports explicitly resetting the volume.
+ public void Update(in T parameter, bool isPrevVolumeResetSupported) where T : ISplitterDestinationInParameter
{
Debug.Assert(Id == parameter.Id);
@@ -110,7 +111,8 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
_biquadFilters = parameter.BiquadFilters;
- if (!IsUsed && parameter.IsUsed)
+ bool resetPrevVolume = isPrevVolumeResetSupported ? parameter.ResetPrevVolume : !IsUsed && parameter.IsUsed;
+ if (resetPrevVolume)
{
MixBufferVolume.CopyTo(PreviousMixBufferVolume);
diff --git a/src/Ryujinx.Horizon/Sdk/Audio/AudioResult.cs b/src/Ryujinx.Horizon/Sdk/Audio/AudioResult.cs
index c18bfee9fc..5914a747cb 100644
--- a/src/Ryujinx.Horizon/Sdk/Audio/AudioResult.cs
+++ b/src/Ryujinx.Horizon/Sdk/Audio/AudioResult.cs
@@ -8,5 +8,6 @@ namespace Ryujinx.Horizon.Sdk.Audio
public static Result DeviceNotFound => new(ModuleId, 1);
public static Result UnsupportedRevision => new(ModuleId, 2);
+ public static Result NotImplemented => new(ModuleId, 513);
}
}
diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioDevice.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioDevice.cs
index f67ea72988..2d3aa7ba92 100644
--- a/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioDevice.cs
+++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioDevice.cs
@@ -233,6 +233,48 @@ namespace Ryujinx.Horizon.Sdk.Audio.Detail
return Result.Success;
}
+ [CmifCommand(15)] // 17.0.0+
+ public Result AcquireAudioOutputDeviceNotification([CopyHandle] out int eventHandle, ulong deviceId)
+ {
+ eventHandle = 0;
+
+ return AudioResult.NotImplemented;
+ }
+
+ [CmifCommand(16)] // 17.0.0+
+ public Result ReleaseAudioOutputDeviceNotification(ulong deviceId)
+ {
+ return AudioResult.NotImplemented;
+ }
+
+ [CmifCommand(17)] // 17.0.0+
+ public Result AcquireAudioInputDeviceNotification([CopyHandle] out int eventHandle, ulong deviceId)
+ {
+ eventHandle = 0;
+
+ return AudioResult.NotImplemented;
+ }
+
+ [CmifCommand(18)] // 17.0.0+
+ public Result ReleaseAudioInputDeviceNotification(ulong deviceId)
+ {
+ return AudioResult.NotImplemented;
+ }
+
+ [CmifCommand(19)] // 18.0.0+
+ public Result SetAudioDeviceOutputVolumeAutoTuneEnabled(bool enabled)
+ {
+ return AudioResult.NotImplemented;
+ }
+
+ [CmifCommand(20)] // 18.0.0+
+ public Result IsAudioDeviceOutputVolumeAutoTuneEnabled(out bool enabled)
+ {
+ enabled = false;
+
+ return AudioResult.NotImplemented;
+ }
+
protected virtual void Dispose(bool disposing)
{
if (disposing)
diff --git a/src/Ryujinx.Tests/Audio/Renderer/Server/BehaviourContextTests.cs b/src/Ryujinx.Tests/Audio/Renderer/Server/BehaviourContextTests.cs
index 3e48a5b4ec..0b0ed7a542 100644
--- a/src/Ryujinx.Tests/Audio/Renderer/Server/BehaviourContextTests.cs
+++ b/src/Ryujinx.Tests/Audio/Renderer/Server/BehaviourContextTests.cs
@@ -55,6 +55,7 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing());
Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported());
Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
+ Assert.IsFalse(behaviourContext.IsSplitterPrevVolumeResetSupported());
Assert.AreEqual(0.70f, behaviourContext.GetAudioRendererProcessingTimeLimit());
Assert.AreEqual(1, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
@@ -83,6 +84,7 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing());
Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported());
Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
+ Assert.IsFalse(behaviourContext.IsSplitterPrevVolumeResetSupported());
Assert.AreEqual(0.70f, behaviourContext.GetAudioRendererProcessingTimeLimit());
Assert.AreEqual(1, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
@@ -111,6 +113,7 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing());
Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported());
Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
+ Assert.IsFalse(behaviourContext.IsSplitterPrevVolumeResetSupported());
Assert.AreEqual(0.70f, behaviourContext.GetAudioRendererProcessingTimeLimit());
Assert.AreEqual(1, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
@@ -139,6 +142,7 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing());
Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported());
Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
+ Assert.IsFalse(behaviourContext.IsSplitterPrevVolumeResetSupported());
Assert.AreEqual(0.75f, behaviourContext.GetAudioRendererProcessingTimeLimit());
Assert.AreEqual(1, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
@@ -167,6 +171,7 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing());
Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported());
Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
+ Assert.IsFalse(behaviourContext.IsSplitterPrevVolumeResetSupported());
Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit());
Assert.AreEqual(2, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
@@ -195,6 +200,7 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing());
Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported());
Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
+ Assert.IsFalse(behaviourContext.IsSplitterPrevVolumeResetSupported());
Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit());
Assert.AreEqual(2, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
@@ -223,6 +229,7 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing());
Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported());
Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
+ Assert.IsFalse(behaviourContext.IsSplitterPrevVolumeResetSupported());
Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit());
Assert.AreEqual(2, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
@@ -251,6 +258,7 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing());
Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported());
Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
+ Assert.IsFalse(behaviourContext.IsSplitterPrevVolumeResetSupported());
Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit());
Assert.AreEqual(3, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
@@ -279,6 +287,7 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing());
Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported());
Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
+ Assert.IsFalse(behaviourContext.IsSplitterPrevVolumeResetSupported());
Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit());
Assert.AreEqual(3, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
@@ -307,6 +316,7 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
Assert.IsTrue(behaviourContext.UseMultiTapBiquadFilterProcessing());
Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported());
Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
+ Assert.IsFalse(behaviourContext.IsSplitterPrevVolumeResetSupported());
Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit());
Assert.AreEqual(4, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
@@ -335,6 +345,7 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
Assert.IsTrue(behaviourContext.UseMultiTapBiquadFilterProcessing());
Assert.IsTrue(behaviourContext.IsNewEffectChannelMappingSupported());
Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
+ Assert.IsFalse(behaviourContext.IsSplitterPrevVolumeResetSupported());
Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit());
Assert.AreEqual(5, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
@@ -363,6 +374,36 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
Assert.IsTrue(behaviourContext.UseMultiTapBiquadFilterProcessing());
Assert.IsTrue(behaviourContext.IsNewEffectChannelMappingSupported());
Assert.IsTrue(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
+ Assert.IsFalse(behaviourContext.IsSplitterPrevVolumeResetSupported());
+
+ Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit());
+ Assert.AreEqual(5, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
+ Assert.AreEqual(2, behaviourContext.GetPerformanceMetricsDataFormat());
+ }
+
+ [Test]
+ public void TestRevision13()
+ {
+ BehaviourContext behaviourContext = new();
+
+ behaviourContext.SetUserRevision(BehaviourContext.BaseRevisionMagic + BehaviourContext.Revision13);
+
+ Assert.IsTrue(behaviourContext.IsAdpcmLoopContextBugFixed());
+ Assert.IsTrue(behaviourContext.IsSplitterSupported());
+ Assert.IsTrue(behaviourContext.IsLongSizePreDelaySupported());
+ Assert.IsTrue(behaviourContext.IsAudioUsbDeviceOutputSupported());
+ Assert.IsTrue(behaviourContext.IsFlushVoiceWaveBuffersSupported());
+ Assert.IsTrue(behaviourContext.IsSplitterBugFixed());
+ Assert.IsTrue(behaviourContext.IsElapsedFrameCountSupported());
+ Assert.IsTrue(behaviourContext.IsDecodingBehaviourFlagSupported());
+ Assert.IsTrue(behaviourContext.IsBiquadFilterEffectStateClearBugFixed());
+ Assert.IsTrue(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported());
+ Assert.IsTrue(behaviourContext.IsWaveBufferVersion2Supported());
+ Assert.IsTrue(behaviourContext.IsEffectInfoVersion2Supported());
+ Assert.IsTrue(behaviourContext.UseMultiTapBiquadFilterProcessing());
+ Assert.IsTrue(behaviourContext.IsNewEffectChannelMappingSupported());
+ Assert.IsTrue(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
+ Assert.IsTrue(behaviourContext.IsSplitterPrevVolumeResetSupported());
Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit());
Assert.AreEqual(5, behaviourContext.GetCommandProcessingTimeEstimatorVersion());