Native HDR output: settings toggle + paper white / peak / intensity sliders
Adds optional native HDR (scRGB) presentation with an SDR->HDR highlight tone map, exposed as real graphics settings: - 'Enable HDR' checkbox, off by default. Only engages when the display/OS advertises the scRGB (extended sRGB linear) HDR colorspace, so SDR output is left completely unchanged. - Paper white, peak brightness and highlight intensity sliders, read by the HDR color-blit and FSR sharpening shaders through uniforms. - Blacks are preserved by construction (highlight-only expansion). - Defaults calibrated on a 4K OLED: 200 nits paper white, 1100 nits peak, intensity 80. Vulkan only. Co-Authored-By: The Roofer Dev <therooferdev@gmail.com>
This commit is contained in:
@@ -15,5 +15,6 @@ namespace Ryujinx.Graphics.GAL
|
|||||||
void SetScalingFilter(ScalingFilter type);
|
void SetScalingFilter(ScalingFilter type);
|
||||||
void SetScalingFilterLevel(float level);
|
void SetScalingFilterLevel(float level);
|
||||||
void SetColorSpacePassthrough(bool colorSpacePassThroughEnabled);
|
void SetColorSpacePassthrough(bool colorSpacePassThroughEnabled);
|
||||||
|
void SetHdrMode(bool enabled, float paperWhite, float peak, float curve);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,5 +41,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
|||||||
public void SetScalingFilterLevel(float level) { }
|
public void SetScalingFilterLevel(float level) { }
|
||||||
|
|
||||||
public void SetColorSpacePassthrough(bool colorSpacePassthroughEnabled) { }
|
public void SetColorSpacePassthrough(bool colorSpacePassthroughEnabled) { }
|
||||||
|
|
||||||
|
public void SetHdrMode(bool enabled, float paperWhite, float peak, float curve) { }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -423,5 +423,10 @@ namespace Ryujinx.Graphics.OpenGL
|
|||||||
_scalingFilterLevel = level;
|
_scalingFilterLevel = level;
|
||||||
_updateScalingFilter = true;
|
_updateScalingFilter = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SetHdrMode(bool enabled, float paperWhite, float peak, float curve)
|
||||||
|
{
|
||||||
|
// Native HDR output is only implemented on the Vulkan backend.
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,7 +63,11 @@ namespace Ryujinx.Graphics.Vulkan.Effects
|
|||||||
int width,
|
int width,
|
||||||
int height,
|
int height,
|
||||||
Extent2D source,
|
Extent2D source,
|
||||||
Extent2D destination)
|
Extent2D destination,
|
||||||
|
bool hdr = false,
|
||||||
|
float paperWhite = 2.5f,
|
||||||
|
float peak = 12.5f,
|
||||||
|
float curve = 4.0f)
|
||||||
{
|
{
|
||||||
_pipeline.SetCommandBuffer(cbs);
|
_pipeline.SetCommandBuffer(cbs);
|
||||||
_pipeline.SetProgram(_scalingProgram);
|
_pipeline.SetProgram(_scalingProgram);
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ namespace Ryujinx.Graphics.Vulkan.Effects
|
|||||||
private ISampler _sampler;
|
private ISampler _sampler;
|
||||||
private ShaderCollection _scalingProgram;
|
private ShaderCollection _scalingProgram;
|
||||||
private ShaderCollection _sharpeningProgram;
|
private ShaderCollection _sharpeningProgram;
|
||||||
|
private ShaderCollection _sharpeningProgramHdr;
|
||||||
private float _sharpeningLevel = 1;
|
private float _sharpeningLevel = 1;
|
||||||
private Device _device;
|
private Device _device;
|
||||||
private TextureView _intermediaryTexture;
|
private TextureView _intermediaryTexture;
|
||||||
@@ -43,6 +44,7 @@ namespace Ryujinx.Graphics.Vulkan.Effects
|
|||||||
_pipeline.Dispose();
|
_pipeline.Dispose();
|
||||||
_scalingProgram.Dispose();
|
_scalingProgram.Dispose();
|
||||||
_sharpeningProgram.Dispose();
|
_sharpeningProgram.Dispose();
|
||||||
|
_sharpeningProgramHdr.Dispose();
|
||||||
_sampler.Dispose();
|
_sampler.Dispose();
|
||||||
_intermediaryTexture?.Dispose();
|
_intermediaryTexture?.Dispose();
|
||||||
}
|
}
|
||||||
@@ -77,6 +79,12 @@ namespace Ryujinx.Graphics.Vulkan.Effects
|
|||||||
_sharpeningProgram = _renderer.CreateProgramWithMinimalLayout([
|
_sharpeningProgram = _renderer.CreateProgramWithMinimalLayout([
|
||||||
new ShaderSource(sharpeningShader, ShaderStage.Compute, TargetLanguage.Spirv)
|
new ShaderSource(sharpeningShader, ShaderStage.Compute, TargetLanguage.Spirv)
|
||||||
], sharpeningResourceLayout);
|
], sharpeningResourceLayout);
|
||||||
|
|
||||||
|
byte[] sharpeningShaderHdr = EmbeddedResources.Read("Ryujinx.Graphics.Vulkan/Effects/Shaders/FsrSharpeningHdr.spv");
|
||||||
|
|
||||||
|
_sharpeningProgramHdr = _renderer.CreateProgramWithMinimalLayout([
|
||||||
|
new ShaderSource(sharpeningShaderHdr, ShaderStage.Compute, TargetLanguage.Spirv)
|
||||||
|
], sharpeningResourceLayout);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Run(
|
public void Run(
|
||||||
@@ -87,7 +95,11 @@ namespace Ryujinx.Graphics.Vulkan.Effects
|
|||||||
int width,
|
int width,
|
||||||
int height,
|
int height,
|
||||||
Extent2D source,
|
Extent2D source,
|
||||||
Extent2D destination)
|
Extent2D destination,
|
||||||
|
bool hdr = false,
|
||||||
|
float paperWhite = 2.5f,
|
||||||
|
float peak = 12.5f,
|
||||||
|
float curve = 4.0f)
|
||||||
{
|
{
|
||||||
if (_intermediaryTexture == null
|
if (_intermediaryTexture == null
|
||||||
|| _intermediaryTexture.Info.Width != width
|
|| _intermediaryTexture.Info.Width != width
|
||||||
@@ -143,9 +155,16 @@ namespace Ryujinx.Graphics.Vulkan.Effects
|
|||||||
using ScopedTemporaryBuffer buffer = _renderer.BufferManager.ReserveOrCreate(_renderer, cbs, rangeSize);
|
using ScopedTemporaryBuffer buffer = _renderer.BufferManager.ReserveOrCreate(_renderer, cbs, rangeSize);
|
||||||
buffer.Holder.SetDataUnchecked(buffer.Offset, dimensionsBuffer);
|
buffer.Holder.SetDataUnchecked(buffer.Offset, dimensionsBuffer);
|
||||||
|
|
||||||
ReadOnlySpan<float> sharpeningBufferData = [1.5f - (Level * 0.01f * 1.5f)];
|
// Non-HDR: [sharpening]. HDR: [sharpening, paperWhite, peak, curve] (the HDR sharpening
|
||||||
using ScopedTemporaryBuffer sharpeningBuffer = _renderer.BufferManager.ReserveOrCreate(_renderer, cbs, sizeof(float));
|
// shader reads the extra floats from the same uniform block to tone-map to scRGB).
|
||||||
sharpeningBuffer.Holder.SetDataUnchecked(sharpeningBuffer.Offset, sharpeningBufferData);
|
int sharpeningCount = hdr ? 4 : 1;
|
||||||
|
Span<float> sharpeningBufferData = stackalloc float[4];
|
||||||
|
sharpeningBufferData[0] = 1.5f - (Level * 0.01f * 1.5f);
|
||||||
|
sharpeningBufferData[1] = paperWhite;
|
||||||
|
sharpeningBufferData[2] = peak;
|
||||||
|
sharpeningBufferData[3] = curve;
|
||||||
|
using ScopedTemporaryBuffer sharpeningBuffer = _renderer.BufferManager.ReserveOrCreate(_renderer, cbs, sharpeningCount * sizeof(float));
|
||||||
|
sharpeningBuffer.Holder.SetDataUnchecked(sharpeningBuffer.Offset, sharpeningBufferData[..sharpeningCount]);
|
||||||
|
|
||||||
int threadGroupWorkRegionDim = 16;
|
int threadGroupWorkRegionDim = 16;
|
||||||
int dispatchX = (width + (threadGroupWorkRegionDim - 1)) / threadGroupWorkRegionDim;
|
int dispatchX = (width + (threadGroupWorkRegionDim - 1)) / threadGroupWorkRegionDim;
|
||||||
@@ -157,7 +176,7 @@ namespace Ryujinx.Graphics.Vulkan.Effects
|
|||||||
_pipeline.ComputeBarrier();
|
_pipeline.ComputeBarrier();
|
||||||
|
|
||||||
// Sharpening pass
|
// Sharpening pass
|
||||||
_pipeline.SetProgram(_sharpeningProgram);
|
_pipeline.SetProgram(hdr ? _sharpeningProgramHdr : _sharpeningProgram);
|
||||||
_pipeline.SetTextureAndSampler(ShaderStage.Compute, 1, _intermediaryTexture, _sampler);
|
_pipeline.SetTextureAndSampler(ShaderStage.Compute, 1, _intermediaryTexture, _sampler);
|
||||||
_pipeline.SetUniformBuffers([new BufferAssignment(4, sharpeningBuffer.Range)]);
|
_pipeline.SetUniformBuffers([new BufferAssignment(4, sharpeningBuffer.Range)]);
|
||||||
_pipeline.SetImage(0, destinationTexture);
|
_pipeline.SetImage(0, destinationTexture);
|
||||||
|
|||||||
@@ -15,6 +15,10 @@ namespace Ryujinx.Graphics.Vulkan.Effects
|
|||||||
int width,
|
int width,
|
||||||
int height,
|
int height,
|
||||||
Extent2D source,
|
Extent2D source,
|
||||||
Extent2D destination);
|
Extent2D destination,
|
||||||
|
bool hdr = false,
|
||||||
|
float paperWhite = 2.5f,
|
||||||
|
float peak = 12.5f,
|
||||||
|
float curve = 4.0f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
// Sharpening
|
// Sharpening
|
||||||
#version 430 core
|
#version 430 core
|
||||||
layout (local_size_x = 64) in;
|
layout (local_size_x = 64) in;
|
||||||
|
#ifdef HDR_OUTPUT
|
||||||
|
layout( rgba16f, binding = 0, set = 3) uniform image2D imgOutput;
|
||||||
|
#else
|
||||||
layout( rgba8, binding = 0, set = 3) uniform image2D imgOutput;
|
layout( rgba8, binding = 0, set = 3) uniform image2D imgOutput;
|
||||||
|
#endif
|
||||||
layout( binding = 2 ) uniform invResolution
|
layout( binding = 2 ) uniform invResolution
|
||||||
{
|
{
|
||||||
vec2 invResolution_data;
|
vec2 invResolution_data;
|
||||||
@@ -14,6 +18,11 @@ layout( binding = 1, set = 2) uniform sampler2D source;
|
|||||||
layout( binding = 4 ) uniform sharpening
|
layout( binding = 4 ) uniform sharpening
|
||||||
{
|
{
|
||||||
float sharpening_data;
|
float sharpening_data;
|
||||||
|
#ifdef HDR_OUTPUT
|
||||||
|
float paperWhite_data;
|
||||||
|
float peak_data;
|
||||||
|
float curve_data;
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
#define A_GPU 1
|
#define A_GPU 1
|
||||||
@@ -3883,10 +3892,33 @@ AF1 sharpness){
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef HDR_OUTPUT
|
||||||
|
// sRGB encoded -> linear, for the scRGB (extended sRGB linear) HDR swapchain.
|
||||||
|
vec3 srgbToLinear(vec3 c)
|
||||||
|
{
|
||||||
|
return mix(c / 12.92, pow((c + 0.055) / 1.055, vec3(2.4)), step(vec3(0.04045), c));
|
||||||
|
}
|
||||||
|
// First-pass SDR->HDR: scale to paper white + expand ONLY the highlights (blacks stay black).
|
||||||
|
// scRGB: 1.0 == 80 nits, paperWhite 2.0 ~= 160 nits, peak 8.0 ~= 640 nits.
|
||||||
|
vec3 sdrToHdr(vec3 srgb)
|
||||||
|
{
|
||||||
|
vec3 lin = srgbToLinear(srgb);
|
||||||
|
float paperWhite = paperWhite_data;
|
||||||
|
float peak = peak_data;
|
||||||
|
float curve = curve_data;
|
||||||
|
float luma = dot(lin, vec3(0.2126, 0.7152, 0.0722));
|
||||||
|
float boost = mix(1.0, peak / paperWhite, pow(clamp(luma, 0.0, 1.0), curve));
|
||||||
|
return lin * paperWhite * boost;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
void CurrFilter(AU2 pos)
|
void CurrFilter(AU2 pos)
|
||||||
{
|
{
|
||||||
AF3 c;
|
AF3 c;
|
||||||
FsrRcasF(c.r, c.g, c.b, pos, con0);
|
FsrRcasF(c.r, c.g, c.b, pos, con0);
|
||||||
|
#ifdef HDR_OUTPUT
|
||||||
|
c = sdrToHdr(c);
|
||||||
|
#endif
|
||||||
imageStore(imgOutput, ASU2(pos), AF4(c, 1));
|
imageStore(imgOutput, ASU2(pos), AF4(c, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
@@ -33,6 +33,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
private readonly ISampler _samplerLinear;
|
private readonly ISampler _samplerLinear;
|
||||||
private readonly ISampler _samplerNearest;
|
private readonly ISampler _samplerNearest;
|
||||||
private readonly ShaderCollection _programColorBlit;
|
private readonly ShaderCollection _programColorBlit;
|
||||||
|
private readonly ShaderCollection _programColorBlitHdr;
|
||||||
private readonly ShaderCollection _programColorBlitMs;
|
private readonly ShaderCollection _programColorBlitMs;
|
||||||
private readonly ShaderCollection _programColorBlitClearAlpha;
|
private readonly ShaderCollection _programColorBlitClearAlpha;
|
||||||
private readonly ShaderCollection _programColorClearF;
|
private readonly ShaderCollection _programColorClearF;
|
||||||
@@ -73,6 +74,16 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
new ShaderSource(ReadSpirv("ColorBlitFragment.spv"), ShaderStage.Fragment, TargetLanguage.Spirv)
|
new ShaderSource(ReadSpirv("ColorBlitFragment.spv"), ShaderStage.Fragment, TargetLanguage.Spirv)
|
||||||
], blitResourceLayout);
|
], blitResourceLayout);
|
||||||
|
|
||||||
|
ResourceLayout blitHdrResourceLayout = new ResourceLayoutBuilder()
|
||||||
|
.Add(ResourceStages.Vertex, ResourceType.UniformBuffer, 1)
|
||||||
|
.Add(ResourceStages.Fragment, ResourceType.TextureAndSampler, 0)
|
||||||
|
.Add(ResourceStages.Fragment, ResourceType.UniformBuffer, 2).Build();
|
||||||
|
|
||||||
|
_programColorBlitHdr = gd.CreateProgramWithMinimalLayout([
|
||||||
|
new ShaderSource(ReadSpirv("ColorBlitVertex.spv"), ShaderStage.Vertex, TargetLanguage.Spirv),
|
||||||
|
new ShaderSource(ReadSpirv("ColorBlitHdrFragment.spv"), ShaderStage.Fragment, TargetLanguage.Spirv)
|
||||||
|
], blitHdrResourceLayout);
|
||||||
|
|
||||||
_programColorBlitMs = gd.CreateProgramWithMinimalLayout([
|
_programColorBlitMs = gd.CreateProgramWithMinimalLayout([
|
||||||
new ShaderSource(ReadSpirv("ColorBlitVertex.spv"), ShaderStage.Vertex, TargetLanguage.Spirv),
|
new ShaderSource(ReadSpirv("ColorBlitVertex.spv"), ShaderStage.Vertex, TargetLanguage.Spirv),
|
||||||
new ShaderSource(ReadSpirv("ColorBlitMsFragment.spv"), ShaderStage.Fragment, TargetLanguage.Spirv)
|
new ShaderSource(ReadSpirv("ColorBlitMsFragment.spv"), ShaderStage.Fragment, TargetLanguage.Spirv)
|
||||||
@@ -352,7 +363,11 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
Extents2D srcRegion,
|
Extents2D srcRegion,
|
||||||
Extents2D dstRegion,
|
Extents2D dstRegion,
|
||||||
bool linearFilter,
|
bool linearFilter,
|
||||||
bool clearAlpha = false)
|
bool clearAlpha = false,
|
||||||
|
bool hdr = false,
|
||||||
|
float paperWhite = 2.5f,
|
||||||
|
float peak = 12.5f,
|
||||||
|
float curve = 4.0f)
|
||||||
{
|
{
|
||||||
_pipeline.SetCommandBuffer(cbs);
|
_pipeline.SetCommandBuffer(cbs);
|
||||||
|
|
||||||
@@ -383,7 +398,24 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
|
|
||||||
buffer.Holder.SetDataUnchecked<float>(buffer.Offset, region);
|
buffer.Holder.SetDataUnchecked<float>(buffer.Offset, region);
|
||||||
|
|
||||||
_pipeline.SetUniformBuffers([new BufferAssignment(1, buffer.Range)]);
|
// Reserved at method scope (must stay alive until the draw). Only filled/bound for HDR;
|
||||||
|
// the HDR blit program's layout has a fragment uniform buffer at binding 2.
|
||||||
|
using ScopedTemporaryBuffer hdrBuffer = gd.BufferManager.ReserveOrCreate(gd, cbs, 16);
|
||||||
|
|
||||||
|
if (hdr)
|
||||||
|
{
|
||||||
|
Span<float> hdrParams = stackalloc float[4];
|
||||||
|
hdrParams[0] = paperWhite;
|
||||||
|
hdrParams[1] = peak;
|
||||||
|
hdrParams[2] = curve;
|
||||||
|
hdrBuffer.Holder.SetDataUnchecked<float>(hdrBuffer.Offset, hdrParams);
|
||||||
|
|
||||||
|
_pipeline.SetUniformBuffers([new BufferAssignment(1, buffer.Range), new BufferAssignment(2, hdrBuffer.Range)]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_pipeline.SetUniformBuffers([new BufferAssignment(1, buffer.Range)]);
|
||||||
|
}
|
||||||
|
|
||||||
Span<Viewport> viewports = stackalloc Viewport[1];
|
Span<Viewport> viewports = stackalloc Viewport[1];
|
||||||
|
|
||||||
@@ -415,7 +447,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
}
|
}
|
||||||
else if (clearAlpha)
|
else if (clearAlpha)
|
||||||
{
|
{
|
||||||
_pipeline.SetProgram(_programColorBlitClearAlpha);
|
_pipeline.SetProgram(hdr ? _programColorBlitHdr : _programColorBlitClearAlpha);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -1672,6 +1704,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
{
|
{
|
||||||
_programColorBlitClearAlpha.Dispose();
|
_programColorBlitClearAlpha.Dispose();
|
||||||
_programColorBlit.Dispose();
|
_programColorBlit.Dispose();
|
||||||
|
_programColorBlitHdr.Dispose();
|
||||||
_programColorBlitMs.Dispose();
|
_programColorBlitMs.Dispose();
|
||||||
_programColorClearF.Dispose();
|
_programColorClearF.Dispose();
|
||||||
_programColorClearSI.Dispose();
|
_programColorClearSI.Dispose();
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
<EmbeddedResource Include="Effects\Shaders\AreaScaling.spv" />
|
<EmbeddedResource Include="Effects\Shaders\AreaScaling.spv" />
|
||||||
<EmbeddedResource Include="Effects\Shaders\FsrScaling.spv" />
|
<EmbeddedResource Include="Effects\Shaders\FsrScaling.spv" />
|
||||||
<EmbeddedResource Include="Effects\Shaders\FsrSharpening.spv" />
|
<EmbeddedResource Include="Effects\Shaders\FsrSharpening.spv" />
|
||||||
|
<EmbeddedResource Include="Effects\Shaders\FsrSharpeningHdr.spv" />
|
||||||
<EmbeddedResource Include="Effects\Shaders\Fxaa.spv" />
|
<EmbeddedResource Include="Effects\Shaders\Fxaa.spv" />
|
||||||
<EmbeddedResource Include="Effects\Shaders\SmaaBlend.spv" />
|
<EmbeddedResource Include="Effects\Shaders\SmaaBlend.spv" />
|
||||||
<EmbeddedResource Include="Effects\Shaders\SmaaEdge.spv" />
|
<EmbeddedResource Include="Effects\Shaders\SmaaEdge.spv" />
|
||||||
@@ -25,6 +26,7 @@
|
|||||||
<EmbeddedResource Include="Shaders\SpirvBinaries\ChangeBufferStride.spv" />
|
<EmbeddedResource Include="Shaders\SpirvBinaries\ChangeBufferStride.spv" />
|
||||||
<EmbeddedResource Include="Shaders\SpirvBinaries\ColorBlitClearAlphaFragment.spv" />
|
<EmbeddedResource Include="Shaders\SpirvBinaries\ColorBlitClearAlphaFragment.spv" />
|
||||||
<EmbeddedResource Include="Shaders\SpirvBinaries\ColorBlitFragment.spv" />
|
<EmbeddedResource Include="Shaders\SpirvBinaries\ColorBlitFragment.spv" />
|
||||||
|
<EmbeddedResource Include="Shaders\SpirvBinaries\ColorBlitHdrFragment.spv" />
|
||||||
<EmbeddedResource Include="Shaders\SpirvBinaries\ColorBlitMsFragment.spv" />
|
<EmbeddedResource Include="Shaders\SpirvBinaries\ColorBlitMsFragment.spv" />
|
||||||
<EmbeddedResource Include="Shaders\SpirvBinaries\ColorBlitVertex.spv" />
|
<EmbeddedResource Include="Shaders\SpirvBinaries\ColorBlitVertex.spv" />
|
||||||
<EmbeddedResource Include="Shaders\SpirvBinaries\ColorClearFFragment.spv" />
|
<EmbeddedResource Include="Shaders\SpirvBinaries\ColorClearFFragment.spv" />
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
#version 450 core
|
||||||
|
|
||||||
|
layout (binding = 0, set = 2) uniform sampler2D tex;
|
||||||
|
|
||||||
|
layout (std140, binding = 2) uniform hdr_params
|
||||||
|
{
|
||||||
|
// x = paper white (scRGB multiplier), y = peak (scRGB multiplier), z = highlight curve exponent
|
||||||
|
vec4 hdr_params_data;
|
||||||
|
};
|
||||||
|
|
||||||
|
layout (location = 0) in vec2 tex_coord;
|
||||||
|
layout (location = 0) out vec4 colour;
|
||||||
|
|
||||||
|
// sRGB encoded -> linear, for the scRGB (extended sRGB linear) HDR swapchain.
|
||||||
|
vec3 srgbToLinear(vec3 c)
|
||||||
|
{
|
||||||
|
return mix(c / 12.92, pow((c + 0.055) / 1.055, vec3(2.4)), step(vec3(0.04045), c));
|
||||||
|
}
|
||||||
|
|
||||||
|
// First-pass SDR->HDR "inverse tone map": scale to paper white, then expand ONLY the
|
||||||
|
// highlights toward the peak. Blacks stay black (no constant lift) to avoid a washed look.
|
||||||
|
// scRGB: 1.0 == 80 nits, so paperWhite 2.0 ~= 160 nits, peak 8.0 ~= 640 nits.
|
||||||
|
vec3 sdrToHdr(vec3 srgb)
|
||||||
|
{
|
||||||
|
vec3 lin = srgbToLinear(srgb);
|
||||||
|
float paperWhite = hdr_params_data.x;
|
||||||
|
float peak = hdr_params_data.y;
|
||||||
|
float curve = hdr_params_data.z;
|
||||||
|
float luma = dot(lin, vec3(0.2126, 0.7152, 0.0722));
|
||||||
|
float boost = mix(1.0, peak / paperWhite, pow(clamp(luma, 0.0, 1.0), curve));
|
||||||
|
return lin * paperWhite * boost;
|
||||||
|
}
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
vec3 c = texture(tex, tex_coord).rgb;
|
||||||
|
colour = vec4(sdrToHdr(c), 1.0f);
|
||||||
|
}
|
||||||
Binary file not shown.
@@ -42,6 +42,10 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
private bool _updateScalingFilter;
|
private bool _updateScalingFilter;
|
||||||
private ScalingFilter _currentScalingFilter;
|
private ScalingFilter _currentScalingFilter;
|
||||||
private bool _colorSpacePassthroughEnabled;
|
private bool _colorSpacePassthroughEnabled;
|
||||||
|
private bool _hdrEnabled;
|
||||||
|
private float _hdrPaperWhite = 200f;
|
||||||
|
private float _hdrPeak = 1100f;
|
||||||
|
private float _hdrCurve = 2.8f;
|
||||||
|
|
||||||
public unsafe Window(VulkanRenderer gd, SurfaceKHR surface, PhysicalDevice physicalDevice, Device device)
|
public unsafe Window(VulkanRenderer gd, SurfaceKHR surface, PhysicalDevice physicalDevice, Device device)
|
||||||
{
|
{
|
||||||
@@ -117,7 +121,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
imageCount = capabilities.MaxImageCount;
|
imageCount = capabilities.MaxImageCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
SurfaceFormatKHR surfaceFormat = ChooseSwapSurfaceFormat(surfaceFormats, _colorSpacePassthroughEnabled);
|
SurfaceFormatKHR surfaceFormat = ChooseSwapSurfaceFormat(surfaceFormats, _colorSpacePassthroughEnabled, _hdrEnabled);
|
||||||
|
|
||||||
Extent2D extent = ChooseSwapExtent(capabilities);
|
Extent2D extent = ChooseSwapExtent(capabilities);
|
||||||
|
|
||||||
@@ -226,8 +230,24 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
return new TextureView(_gd, _device, new DisposableImageView(_gd.Api, _device, imageView), info, format);
|
return new TextureView(_gd, _device, new DisposableImageView(_gd.Api, _device, imageView), info, format);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static SurfaceFormatKHR ChooseSwapSurfaceFormat(SurfaceFormatKHR[] availableFormats, bool colorSpacePassthroughEnabled)
|
private static SurfaceFormatKHR ChooseSwapSurfaceFormat(SurfaceFormatKHR[] availableFormats, bool colorSpacePassthroughEnabled, bool hdrEnabled)
|
||||||
{
|
{
|
||||||
|
// Native HDR output: when HDR is enabled and the surface advertises the scRGB
|
||||||
|
// (extended sRGB linear) HDR colorspace, select the 16-bit float swapchain. That
|
||||||
|
// colorspace is only enumerated on a genuinely HDR-capable display/OS, so on an SDR
|
||||||
|
// display this branch never triggers and the normal SDR format is used unchanged.
|
||||||
|
if (hdrEnabled)
|
||||||
|
{
|
||||||
|
foreach (SurfaceFormatKHR hdrFormat in availableFormats)
|
||||||
|
{
|
||||||
|
if (hdrFormat.Format == VkFormat.R16G16B16A16Sfloat &&
|
||||||
|
hdrFormat.ColorSpace == ColorSpaceKHR.ColorSpaceExtendedSrgbLinearExt)
|
||||||
|
{
|
||||||
|
return hdrFormat;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (availableFormats.Length == 1 && availableFormats[0].Format == VkFormat.Undefined)
|
if (availableFormats.Length == 1 && availableFormats[0].Format == VkFormat.Undefined)
|
||||||
{
|
{
|
||||||
return new SurfaceFormatKHR(VkFormat.B8G8R8A8Unorm, ColorSpaceKHR.PaceSrgbNonlinearKhr);
|
return new SurfaceFormatKHR(VkFormat.B8G8R8A8Unorm, ColorSpaceKHR.PaceSrgbNonlinearKhr);
|
||||||
@@ -438,7 +458,11 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
_width,
|
_width,
|
||||||
_height,
|
_height,
|
||||||
new Extents2D(srcX0, srcY0, srcX1, srcY1),
|
new Extents2D(srcX0, srcY0, srcX1, srcY1),
|
||||||
new Extents2D(dstX0, dstY0, dstX1, dstY1)
|
new Extents2D(dstX0, dstY0, dstX1, dstY1),
|
||||||
|
_format == VkFormat.R16G16B16A16Sfloat,
|
||||||
|
_hdrPaperWhite / 80f,
|
||||||
|
_hdrPeak / 80f,
|
||||||
|
_hdrCurve
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -451,7 +475,11 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
new Extents2D(srcX0, srcY0, srcX1, srcY1),
|
new Extents2D(srcX0, srcY0, srcX1, srcY1),
|
||||||
new Extents2D(dstX0, dstY1, dstX1, dstY0),
|
new Extents2D(dstX0, dstY1, dstX1, dstY0),
|
||||||
_isLinear,
|
_isLinear,
|
||||||
true);
|
true,
|
||||||
|
_format == VkFormat.R16G16B16A16Sfloat,
|
||||||
|
_hdrPaperWhite / 80f,
|
||||||
|
_hdrPeak / 80f,
|
||||||
|
_hdrCurve);
|
||||||
}
|
}
|
||||||
|
|
||||||
Transition(
|
Transition(
|
||||||
@@ -653,6 +681,20 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
_swapchainIsDirty = true;
|
_swapchainIsDirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void SetHdrMode(bool enabled, float paperWhite, float peak, float curve)
|
||||||
|
{
|
||||||
|
_hdrPaperWhite = paperWhite;
|
||||||
|
_hdrPeak = peak;
|
||||||
|
_hdrCurve = curve;
|
||||||
|
|
||||||
|
if (_hdrEnabled != enabled)
|
||||||
|
{
|
||||||
|
_hdrEnabled = enabled;
|
||||||
|
// The swapchain surface format (SDR vs scRGB float) changes, so recreate it.
|
||||||
|
_swapchainIsDirty = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected virtual void Dispose(bool disposing)
|
protected virtual void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
if (disposing)
|
if (disposing)
|
||||||
|
|||||||
@@ -16,5 +16,6 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
public abstract void SetScalingFilter(ScalingFilter scalerType);
|
public abstract void SetScalingFilter(ScalingFilter scalerType);
|
||||||
public abstract void SetScalingFilterLevel(float scale);
|
public abstract void SetScalingFilterLevel(float scale);
|
||||||
public abstract void SetColorSpacePassthrough(bool colorSpacePassthroughEnabled);
|
public abstract void SetColorSpacePassthrough(bool colorSpacePassthroughEnabled);
|
||||||
|
public abstract void SetHdrMode(bool enabled, float paperWhite, float peak, float curve);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -206,6 +206,10 @@ namespace Ryujinx.Ava.Systems
|
|||||||
ConfigurationState.Instance.Graphics.ScalingFilter.Event += UpdateScalingFilter;
|
ConfigurationState.Instance.Graphics.ScalingFilter.Event += UpdateScalingFilter;
|
||||||
ConfigurationState.Instance.Graphics.ScalingFilterLevel.Event += UpdateScalingFilterLevel;
|
ConfigurationState.Instance.Graphics.ScalingFilterLevel.Event += UpdateScalingFilterLevel;
|
||||||
ConfigurationState.Instance.Graphics.EnableColorSpacePassthrough.Event += UpdateColorSpacePassthrough;
|
ConfigurationState.Instance.Graphics.EnableColorSpacePassthrough.Event += UpdateColorSpacePassthrough;
|
||||||
|
ConfigurationState.Instance.Graphics.EnableHdr.Event += UpdateHdrEnabled;
|
||||||
|
ConfigurationState.Instance.Graphics.HdrPaperWhite.Event += UpdateHdrLevel;
|
||||||
|
ConfigurationState.Instance.Graphics.HdrPeakBrightness.Event += UpdateHdrLevel;
|
||||||
|
ConfigurationState.Instance.Graphics.HdrIntensity.Event += UpdateHdrLevel;
|
||||||
ConfigurationState.Instance.Graphics.VSyncMode.Event += UpdateVSyncMode;
|
ConfigurationState.Instance.Graphics.VSyncMode.Event += UpdateVSyncMode;
|
||||||
ConfigurationState.Instance.Graphics.CustomVSyncInterval.Event += UpdateCustomVSyncIntervalValue;
|
ConfigurationState.Instance.Graphics.CustomVSyncInterval.Event += UpdateCustomVSyncIntervalValue;
|
||||||
ConfigurationState.Instance.Graphics.EnableCustomVSyncInterval.Event += UpdateCustomVSyncIntervalEnabled;
|
ConfigurationState.Instance.Graphics.EnableCustomVSyncInterval.Event += UpdateCustomVSyncIntervalEnabled;
|
||||||
@@ -305,6 +309,26 @@ namespace Ryujinx.Ava.Systems
|
|||||||
_renderer.Window?.SetColorSpacePassthrough(ConfigurationState.Instance.Graphics.EnableColorSpacePassthrough);
|
_renderer.Window?.SetColorSpacePassthrough(ConfigurationState.Instance.Graphics.EnableColorSpacePassthrough);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void UpdateHdrEnabled(object sender, ReactiveEventArgs<bool> e) => ApplyHdrMode();
|
||||||
|
|
||||||
|
private void UpdateHdrLevel(object sender, ReactiveEventArgs<int> e) => ApplyHdrMode();
|
||||||
|
|
||||||
|
private void ApplyHdrMode()
|
||||||
|
{
|
||||||
|
_renderer.Window?.SetHdrMode(
|
||||||
|
ConfigurationState.Instance.Graphics.EnableHdr,
|
||||||
|
ConfigurationState.Instance.Graphics.HdrPaperWhite,
|
||||||
|
ConfigurationState.Instance.Graphics.HdrPeakBrightness,
|
||||||
|
HdrIntensityToCurve(ConfigurationState.Instance.Graphics.HdrIntensity));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Maps the user-facing HDR intensity (0-100, higher = more pop) to the highlight curve
|
||||||
|
// exponent: 0 -> 6.0 (only near-white pops), 50 -> 4.0 (default), 100 -> 2.0 (whole scene pops).
|
||||||
|
private static float HdrIntensityToCurve(int intensity)
|
||||||
|
{
|
||||||
|
return 6.0f - (Math.Clamp(intensity, 0, 100) * 0.04f);
|
||||||
|
}
|
||||||
|
|
||||||
public void UpdateVSyncMode(object sender, ReactiveEventArgs<VSyncMode> e)
|
public void UpdateVSyncMode(object sender, ReactiveEventArgs<VSyncMode> e)
|
||||||
{
|
{
|
||||||
if (Device != null)
|
if (Device != null)
|
||||||
@@ -661,6 +685,10 @@ namespace Ryujinx.Ava.Systems
|
|||||||
ConfigurationState.Instance.Graphics.ScalingFilterLevel.Event -= UpdateScalingFilterLevel;
|
ConfigurationState.Instance.Graphics.ScalingFilterLevel.Event -= UpdateScalingFilterLevel;
|
||||||
ConfigurationState.Instance.Graphics.AntiAliasing.Event -= UpdateAntiAliasing;
|
ConfigurationState.Instance.Graphics.AntiAliasing.Event -= UpdateAntiAliasing;
|
||||||
ConfigurationState.Instance.Graphics.EnableColorSpacePassthrough.Event -= UpdateColorSpacePassthrough;
|
ConfigurationState.Instance.Graphics.EnableColorSpacePassthrough.Event -= UpdateColorSpacePassthrough;
|
||||||
|
ConfigurationState.Instance.Graphics.EnableHdr.Event -= UpdateHdrEnabled;
|
||||||
|
ConfigurationState.Instance.Graphics.HdrPaperWhite.Event -= UpdateHdrLevel;
|
||||||
|
ConfigurationState.Instance.Graphics.HdrPeakBrightness.Event -= UpdateHdrLevel;
|
||||||
|
ConfigurationState.Instance.Graphics.HdrIntensity.Event -= UpdateHdrLevel;
|
||||||
|
|
||||||
_topLevel.PointerMoved -= TopLevel_PointerEnteredOrMoved;
|
_topLevel.PointerMoved -= TopLevel_PointerEnteredOrMoved;
|
||||||
_topLevel.PointerEntered -= TopLevel_PointerEnteredOrMoved;
|
_topLevel.PointerEntered -= TopLevel_PointerEnteredOrMoved;
|
||||||
@@ -1116,6 +1144,11 @@ namespace Ryujinx.Ava.Systems
|
|||||||
_renderer?.Window?.SetScalingFilter(ConfigurationState.Instance.Graphics.ScalingFilter);
|
_renderer?.Window?.SetScalingFilter(ConfigurationState.Instance.Graphics.ScalingFilter);
|
||||||
_renderer?.Window?.SetScalingFilterLevel(ConfigurationState.Instance.Graphics.ScalingFilterLevel);
|
_renderer?.Window?.SetScalingFilterLevel(ConfigurationState.Instance.Graphics.ScalingFilterLevel);
|
||||||
_renderer?.Window?.SetColorSpacePassthrough(ConfigurationState.Instance.Graphics.EnableColorSpacePassthrough);
|
_renderer?.Window?.SetColorSpacePassthrough(ConfigurationState.Instance.Graphics.EnableColorSpacePassthrough);
|
||||||
|
_renderer?.Window?.SetHdrMode(
|
||||||
|
ConfigurationState.Instance.Graphics.EnableHdr,
|
||||||
|
ConfigurationState.Instance.Graphics.HdrPaperWhite,
|
||||||
|
ConfigurationState.Instance.Graphics.HdrPeakBrightness,
|
||||||
|
HdrIntensityToCurve(ConfigurationState.Instance.Graphics.HdrIntensity));
|
||||||
|
|
||||||
Width = (int)RendererHost.Bounds.Width;
|
Width = (int)RendererHost.Bounds.Width;
|
||||||
Height = (int)RendererHost.Bounds.Height;
|
Height = (int)RendererHost.Bounds.Height;
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ namespace Ryujinx.Ava.Systems.Configuration
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The current version of the file format
|
/// The current version of the file format
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const int CurrentVersion = 73;
|
public const int CurrentVersion = 75;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Version of the configuration file format
|
/// Version of the configuration file format
|
||||||
@@ -69,6 +69,26 @@ namespace Ryujinx.Ava.Systems.Configuration
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public int ScalingFilterLevel { get; set; }
|
public int ScalingFilterLevel { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enables or disables native HDR output (scRGB) with SDR->HDR tone mapping.
|
||||||
|
/// </summary>
|
||||||
|
public bool EnableHdr { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// HDR paper white (reference white) brightness, in nits.
|
||||||
|
/// </summary>
|
||||||
|
public int HdrPaperWhite { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// HDR peak (highlight) brightness target, in nits.
|
||||||
|
/// </summary>
|
||||||
|
public int HdrPeakBrightness { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// HDR highlight intensity (0-100): how far down the brightness range the highlight expansion reaches.
|
||||||
|
/// </summary>
|
||||||
|
public int HdrIntensity { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Dumps shaders in this local directory
|
/// Dumps shaders in this local directory
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -85,6 +85,10 @@ namespace Ryujinx.Ava.Systems.Configuration
|
|||||||
Graphics.AntiAliasing.Value = cff.AntiAliasing;
|
Graphics.AntiAliasing.Value = cff.AntiAliasing;
|
||||||
Graphics.ScalingFilter.Value = cff.ScalingFilter;
|
Graphics.ScalingFilter.Value = cff.ScalingFilter;
|
||||||
Graphics.ScalingFilterLevel.Value = cff.ScalingFilterLevel;
|
Graphics.ScalingFilterLevel.Value = cff.ScalingFilterLevel;
|
||||||
|
Graphics.EnableHdr.Value = cff.EnableHdr;
|
||||||
|
Graphics.HdrPaperWhite.Value = cff.HdrPaperWhite;
|
||||||
|
Graphics.HdrPeakBrightness.Value = cff.HdrPeakBrightness;
|
||||||
|
Graphics.HdrIntensity.Value = cff.HdrIntensity;
|
||||||
Graphics.VSyncMode.Value = cff.VSyncMode;
|
Graphics.VSyncMode.Value = cff.VSyncMode;
|
||||||
Graphics.EnableCustomVSyncInterval.Value = cff.EnableCustomVSyncInterval;
|
Graphics.EnableCustomVSyncInterval.Value = cff.EnableCustomVSyncInterval;
|
||||||
Graphics.CustomVSyncInterval.Value = cff.CustomVSyncInterval;
|
Graphics.CustomVSyncInterval.Value = cff.CustomVSyncInterval;
|
||||||
@@ -539,7 +543,14 @@ namespace Ryujinx.Ava.Systems.Configuration
|
|||||||
if (cff.AudioBackend is AudioBackend.SDL2)
|
if (cff.AudioBackend is AudioBackend.SDL2)
|
||||||
cff.AudioBackend = AudioBackend.SDL3;
|
cff.AudioBackend = AudioBackend.SDL3;
|
||||||
}),
|
}),
|
||||||
(72, static cff => cff.GCLowLatency = false)
|
(72, static cff => cff.GCLowLatency = false),
|
||||||
|
(74, static cff =>
|
||||||
|
{
|
||||||
|
cff.EnableHdr = false;
|
||||||
|
cff.HdrPaperWhite = 200;
|
||||||
|
cff.HdrPeakBrightness = 1100;
|
||||||
|
}),
|
||||||
|
(75, static cff => cff.HdrIntensity = 80)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -620,6 +620,27 @@ namespace Ryujinx.Ava.Systems.Configuration
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public ReactiveObject<int> ScalingFilterLevel { get; private set; }
|
public ReactiveObject<int> ScalingFilterLevel { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enables or disables native HDR output (scRGB) with SDR->HDR tone mapping.
|
||||||
|
/// </summary>
|
||||||
|
public ReactiveObject<bool> EnableHdr { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// HDR paper white (reference white) brightness, in nits.
|
||||||
|
/// </summary>
|
||||||
|
public ReactiveObject<int> HdrPaperWhite { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// HDR peak (highlight) brightness target, in nits.
|
||||||
|
/// </summary>
|
||||||
|
public ReactiveObject<int> HdrPeakBrightness { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// HDR highlight intensity (0-100): how far down the brightness range the highlight
|
||||||
|
/// expansion reaches. Higher = more of the scene pops, lower = only near-white pops.
|
||||||
|
/// </summary>
|
||||||
|
public ReactiveObject<int> HdrIntensity { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Preferred GPU
|
/// Preferred GPU
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -662,6 +683,14 @@ namespace Ryujinx.Ava.Systems.Configuration
|
|||||||
ScalingFilter.LogChangesToValue(nameof(ScalingFilter));
|
ScalingFilter.LogChangesToValue(nameof(ScalingFilter));
|
||||||
ScalingFilterLevel = new ReactiveObject<int>();
|
ScalingFilterLevel = new ReactiveObject<int>();
|
||||||
ScalingFilterLevel.LogChangesToValue(nameof(ScalingFilterLevel));
|
ScalingFilterLevel.LogChangesToValue(nameof(ScalingFilterLevel));
|
||||||
|
EnableHdr = new ReactiveObject<bool>();
|
||||||
|
EnableHdr.LogChangesToValue(nameof(EnableHdr));
|
||||||
|
HdrPaperWhite = new ReactiveObject<int>();
|
||||||
|
HdrPaperWhite.LogChangesToValue(nameof(HdrPaperWhite));
|
||||||
|
HdrPeakBrightness = new ReactiveObject<int>();
|
||||||
|
HdrPeakBrightness.LogChangesToValue(nameof(HdrPeakBrightness));
|
||||||
|
HdrIntensity = new ReactiveObject<int>();
|
||||||
|
HdrIntensity.LogChangesToValue(nameof(HdrIntensity));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -40,6 +40,10 @@ namespace Ryujinx.Ava.Systems.Configuration
|
|||||||
AntiAliasing = Graphics.AntiAliasing,
|
AntiAliasing = Graphics.AntiAliasing,
|
||||||
ScalingFilter = Graphics.ScalingFilter,
|
ScalingFilter = Graphics.ScalingFilter,
|
||||||
ScalingFilterLevel = Graphics.ScalingFilterLevel,
|
ScalingFilterLevel = Graphics.ScalingFilterLevel,
|
||||||
|
EnableHdr = Graphics.EnableHdr,
|
||||||
|
HdrPaperWhite = Graphics.HdrPaperWhite,
|
||||||
|
HdrPeakBrightness = Graphics.HdrPeakBrightness,
|
||||||
|
HdrIntensity = Graphics.HdrIntensity,
|
||||||
GraphicsShadersDumpPath = Graphics.ShadersDumpPath,
|
GraphicsShadersDumpPath = Graphics.ShadersDumpPath,
|
||||||
LoggingEnableDebug = Logger.EnableDebug,
|
LoggingEnableDebug = Logger.EnableDebug,
|
||||||
LoggingEnableStub = Logger.EnableStub,
|
LoggingEnableStub = Logger.EnableStub,
|
||||||
@@ -208,6 +212,10 @@ namespace Ryujinx.Ava.Systems.Configuration
|
|||||||
Graphics.AntiAliasing.Value = AntiAliasing.None;
|
Graphics.AntiAliasing.Value = AntiAliasing.None;
|
||||||
Graphics.ScalingFilter.Value = ScalingFilter.Bilinear;
|
Graphics.ScalingFilter.Value = ScalingFilter.Bilinear;
|
||||||
Graphics.ScalingFilterLevel.Value = 80;
|
Graphics.ScalingFilterLevel.Value = 80;
|
||||||
|
Graphics.EnableHdr.Value = false;
|
||||||
|
Graphics.HdrPaperWhite.Value = 200;
|
||||||
|
Graphics.HdrPeakBrightness.Value = 1100;
|
||||||
|
Graphics.HdrIntensity.Value = 80;
|
||||||
System.EnablePtc.Value = true;
|
System.EnablePtc.Value = true;
|
||||||
System.EnableInternetAccess.Value = false;
|
System.EnableInternetAccess.Value = false;
|
||||||
System.EnableFsIntegrityChecks.Value = true;
|
System.EnableFsIntegrityChecks.Value = true;
|
||||||
|
|||||||
@@ -287,6 +287,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
public MemoryConfiguration DramSize { get; set; }
|
public MemoryConfiguration DramSize { get; set; }
|
||||||
public bool EnableShaderCache { get; set; }
|
public bool EnableShaderCache { get; set; }
|
||||||
public bool EnableTextureRecompression { get; set; }
|
public bool EnableTextureRecompression { get; set; }
|
||||||
|
public bool EnableHdr { get; set; }
|
||||||
public bool EnableMacroHLE { get; set; }
|
public bool EnableMacroHLE { get; set; }
|
||||||
public bool EnableColorSpacePassthrough { get; set; }
|
public bool EnableColorSpacePassthrough { get; set; }
|
||||||
public bool ColorSpacePassthroughAvailable => RunningPlatform.IsMacOS;
|
public bool ColorSpacePassthroughAvailable => RunningPlatform.IsMacOS;
|
||||||
@@ -352,6 +353,45 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string HdrPaperWhiteText => $"{HdrPaperWhite} nits";
|
||||||
|
|
||||||
|
public int HdrPaperWhite
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
field = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
OnPropertyChanged(nameof(HdrPaperWhiteText));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string HdrPeakBrightnessText => $"{HdrPeakBrightness} nits";
|
||||||
|
|
||||||
|
public int HdrPeakBrightness
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
field = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
OnPropertyChanged(nameof(HdrPeakBrightnessText));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string HdrIntensityText => $"{HdrIntensity}";
|
||||||
|
|
||||||
|
public int HdrIntensity
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
field = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
OnPropertyChanged(nameof(HdrIntensityText));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public int OpenglDebugLevel { get; set; }
|
public int OpenglDebugLevel { get; set; }
|
||||||
public int MemoryMode { get; set; }
|
public int MemoryMode { get; set; }
|
||||||
public int BaseStyleIndex { get; set; }
|
public int BaseStyleIndex { get; set; }
|
||||||
@@ -725,6 +765,10 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
// Physical devices are queried asynchronously hence the preferred index config value is loaded in LoadAvailableGpus().
|
// Physical devices are queried asynchronously hence the preferred index config value is loaded in LoadAvailableGpus().
|
||||||
EnableShaderCache = config.Graphics.EnableShaderCache;
|
EnableShaderCache = config.Graphics.EnableShaderCache;
|
||||||
EnableTextureRecompression = config.Graphics.EnableTextureRecompression;
|
EnableTextureRecompression = config.Graphics.EnableTextureRecompression;
|
||||||
|
EnableHdr = config.Graphics.EnableHdr;
|
||||||
|
HdrPaperWhite = config.Graphics.HdrPaperWhite.Value;
|
||||||
|
HdrPeakBrightness = config.Graphics.HdrPeakBrightness.Value;
|
||||||
|
HdrIntensity = config.Graphics.HdrIntensity.Value;
|
||||||
EnableMacroHLE = config.Graphics.EnableMacroHLE;
|
EnableMacroHLE = config.Graphics.EnableMacroHLE;
|
||||||
EnableColorSpacePassthrough = config.Graphics.EnableColorSpacePassthrough;
|
EnableColorSpacePassthrough = config.Graphics.EnableColorSpacePassthrough;
|
||||||
ResolutionScale = config.Graphics.ResScale == -1 ? 4 : config.Graphics.ResScale - 1;
|
ResolutionScale = config.Graphics.ResScale == -1 ? 4 : config.Graphics.ResScale - 1;
|
||||||
@@ -842,6 +886,10 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
config.Graphics.PreferredGpu.Value = _gpuIds.ElementAtOrDefault(PreferredGpuIndex);
|
config.Graphics.PreferredGpu.Value = _gpuIds.ElementAtOrDefault(PreferredGpuIndex);
|
||||||
config.Graphics.EnableShaderCache.Value = EnableShaderCache;
|
config.Graphics.EnableShaderCache.Value = EnableShaderCache;
|
||||||
config.Graphics.EnableTextureRecompression.Value = EnableTextureRecompression;
|
config.Graphics.EnableTextureRecompression.Value = EnableTextureRecompression;
|
||||||
|
config.Graphics.EnableHdr.Value = EnableHdr;
|
||||||
|
config.Graphics.HdrPaperWhite.Value = HdrPaperWhite;
|
||||||
|
config.Graphics.HdrPeakBrightness.Value = HdrPeakBrightness;
|
||||||
|
config.Graphics.HdrIntensity.Value = HdrIntensity;
|
||||||
config.Graphics.EnableMacroHLE.Value = EnableMacroHLE;
|
config.Graphics.EnableMacroHLE.Value = EnableMacroHLE;
|
||||||
config.Graphics.EnableColorSpacePassthrough.Value = EnableColorSpacePassthrough;
|
config.Graphics.EnableColorSpacePassthrough.Value = EnableColorSpacePassthrough;
|
||||||
config.Graphics.ResScale.Value = ResolutionScale == 4 ? -1 : ResolutionScale + 1;
|
config.Graphics.ResScale.Value = ResolutionScale == 4 ? -1 : ResolutionScale + 1;
|
||||||
|
|||||||
@@ -95,6 +95,76 @@
|
|||||||
ToolTip.Tip="{ext:Locale SettingsEnableColorSpacePassthroughTooltip}">
|
ToolTip.Tip="{ext:Locale SettingsEnableColorSpacePassthroughTooltip}">
|
||||||
<TextBlock Text="{ext:Locale SettingsEnableColorSpacePassthrough}" />
|
<TextBlock Text="{ext:Locale SettingsEnableColorSpacePassthrough}" />
|
||||||
</CheckBox>
|
</CheckBox>
|
||||||
|
<CheckBox IsChecked="{Binding EnableHdr}"
|
||||||
|
ToolTip.Tip="Outputs native HDR (scRGB) with SDR->HDR tone mapping. Only engages on an HDR-capable display with HDR enabled in the OS; SDR displays are unaffected. Vulkan only.">
|
||||||
|
<TextBlock Text="Enable HDR (experimental)" />
|
||||||
|
</CheckBox>
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="24,4,0,0" IsVisible="{Binding EnableHdr}">
|
||||||
|
<TextBlock VerticalAlignment="Center"
|
||||||
|
ToolTip.Tip="Brightness of reference white (UI, paper, diffuse surfaces), in nits. Higher = brighter overall image."
|
||||||
|
Text="HDR paper white"
|
||||||
|
Width="226" />
|
||||||
|
<controls:SliderScroll Value="{Binding HdrPaperWhite}"
|
||||||
|
MinWidth="150"
|
||||||
|
Margin="10,-3,0,0"
|
||||||
|
Height="32"
|
||||||
|
Padding="0,-5"
|
||||||
|
TickFrequency="5"
|
||||||
|
IsSnapToTickEnabled="True"
|
||||||
|
LargeChange="20"
|
||||||
|
SmallChange="5"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Minimum="80"
|
||||||
|
Maximum="400" />
|
||||||
|
<TextBlock Margin="5,0"
|
||||||
|
Width="70"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Text="{Binding HdrPaperWhiteText}"/>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="24,4,0,0" IsVisible="{Binding EnableHdr}">
|
||||||
|
<TextBlock VerticalAlignment="Center"
|
||||||
|
ToolTip.Tip="Peak highlight brightness target, in nits. Only the brightest parts of the image are pushed toward this; blacks stay black. Set near your display's peak."
|
||||||
|
Text="HDR peak brightness"
|
||||||
|
Width="226" />
|
||||||
|
<controls:SliderScroll Value="{Binding HdrPeakBrightness}"
|
||||||
|
MinWidth="150"
|
||||||
|
Margin="10,-3,0,0"
|
||||||
|
Height="32"
|
||||||
|
Padding="0,-5"
|
||||||
|
TickFrequency="10"
|
||||||
|
IsSnapToTickEnabled="True"
|
||||||
|
LargeChange="100"
|
||||||
|
SmallChange="10"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Minimum="200"
|
||||||
|
Maximum="2000" />
|
||||||
|
<TextBlock Margin="5,0"
|
||||||
|
Width="70"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Text="{Binding HdrPeakBrightnessText}"/>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="24,4,0,0" IsVisible="{Binding EnableHdr}">
|
||||||
|
<TextBlock VerticalAlignment="Center"
|
||||||
|
ToolTip.Tip="How far down the brightness range the highlight 'pop' reaches. Low = only near-white (UI, sun) pops; high = the whole bright scene (sky, sunlit surfaces) pops in gameplay. Blacks always stay black."
|
||||||
|
Text="HDR intensity"
|
||||||
|
Width="226" />
|
||||||
|
<controls:SliderScroll Value="{Binding HdrIntensity}"
|
||||||
|
MinWidth="150"
|
||||||
|
Margin="10,-3,0,0"
|
||||||
|
Height="32"
|
||||||
|
Padding="0,-5"
|
||||||
|
TickFrequency="1"
|
||||||
|
IsSnapToTickEnabled="True"
|
||||||
|
LargeChange="10"
|
||||||
|
SmallChange="1"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Minimum="0"
|
||||||
|
Maximum="100" />
|
||||||
|
<TextBlock Margin="5,0"
|
||||||
|
Width="70"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Text="{Binding HdrIntensityText}"/>
|
||||||
|
</StackPanel>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<StackPanel Orientation="Horizontal">
|
<StackPanel Orientation="Horizontal">
|
||||||
<TextBlock VerticalAlignment="Center"
|
<TextBlock VerticalAlignment="Center"
|
||||||
|
|||||||
Reference in New Issue
Block a user