Merge pull request #209 from MajorPainTheCactus/master

Added HDR shader chain to be used with associated pull request 'Added…
This commit is contained in:
Autechre 2021-12-22 02:17:33 +01:00 committed by GitHub
commit f8651de0f5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 506 additions and 0 deletions

View file

@ -0,0 +1,78 @@
/*
A group of shaders that tries to emulate a sony PVM type aperture grille screen but with full brightness.
The novel thing about this group of shaders is that it transforms the image output by the 'console/arcade/computer' into HDR space first i.e brightens it first and then applies
an aperture grille afterwards which is kind of what a CRT would actually do - its kind of a kin to the electron beam (but nothing like it lol).
My HDR 700 monitors does seem to get reasonably close to the brightness of my PVM's - its not quite there but its close.
To use:
Please Enable HDR in RetroArch,
You'll need a version of RetroArch post Christmas 2021 that disables the HDR chain when it detects a HDR render target in the render chain
NOTE: when the hdr10 and inverse_tonemap shaders are envoked the Peak Luminance and Paper White Luminance in the menu *do nothing* instead set those values through the 'Shader Parameters' instead
Set Peak Luminance to the 'Peak Luminance' of your monitor and set Paper White Luminance to roughly half dependent on the game and the monitor.
Also try to use a integer scaling - its just better - overscaling is fine/great.
This shader doesn't do any geometry warping or bouncing of light around inside the screen - I think these effects just add unwanted noise, I know people disagree. Please feel free to make you own and add them
Works only with the D3D11/D3D12 drivers currently
If taking fullscreen at 4K it currently emulates lower than a 600TVL screen - ie 3840(res) / 6(aperture grille pattern) = 640 TVL.
But 600TVL on a 4:3 TV actually means 800 vertical lines as the TVL figure relates to the screen height across the width.
We need 8K to really start to get round the right ballpark. We need 9600 resolution to have the full 12 pixel apperture grille (800TVL * 12 (aperture grille pattern))
TODO: Make the horizontal scanlines more blurry in the vertical direction - we're working in HDR space at this point so its trickier than normal.
*/
shaders = "4"
feedback_pass = "0"
shader0 = "../../slang-shaders/stock.slang"
filter_linear0 = false
wrap_mode0 = "clamp_to_border"
mipmap_input0 = "false"
alias0 = "StockPass"
float_framebuffer0 = "false"
srgb_framebuffer0 = "false"
scale_type_x0 = "source"
scale_x0 = "1.000000"
scale_type_y0 = "source"
scale_y0 = "1.000000"
shader1 = "../../slang-shaders/misc/inverse_tonemap.slang"
filter_linear1 = "false"
wrap_mode1 = "clamp_to_border"
mipmap_input1 = "false"
alias1 = ""
float_framebuffer1 = "false"
srgb_framebuffer1 = "false"
scale_type_x1 = "source"
scale_x1 = "1.000000"
scale_type_y1 = "source"
scale_y1 = "1.000000"
shader2 = "../../slang-shaders/misc/hdr10.slang"
filter_linear2 = "false"
wrap_mode2 = "clamp_to_border"
mipmap_input2 = "false"
alias2 = ""
float_framebuffer2 = "false"
srgb_framebuffer2 = "false"
scale_type_x2 = "source"
scale_x2 = "1.000000"
scale_type_y2 = "source"
scale_y2 = "1.000000"
shader3 = "../../slang-shaders/crt/shaders/crt-sony-pvm-4k-hdr.slang"
filter_linear3 = "false"
wrap_mode3 = "clamp_to_border"
mipmap_input3 = "false"
alias3 = ""
float_framebuffer3 = "false"
srgb_framebuffer3 = "false"
ScanlineWidth = "1.000000"

View file

@ -0,0 +1,253 @@
#version 450
/*
A shader that tries to emulate a sony PVM type aperture grille screen but with full brightness.
The novel thing about this shader is that it relies on the HDR shaders to brighten up the image so that when
we apply this shader which emulates the apperture grille the resulting screen isn't left too dark.
I think you'd need a HDR 1000 monitor to get close to CRT levels of brightness but my HDR 700 does an alright job of it.
Please Enable HDR in RetroArch NOTE: when the hdr10 and inverse_tonemap shaders are envoked the Peak Luminance and Paper White Luminance in the menu do nothing instead set those values through the shader parameters instead
Set Peak Luminance to the peak luminance of your monitor and set Paper White Luminance to roughly half dependent on the game and the monitor.
Also try to use a integer scaling - its just better - overscaling is fine.
This shader doesn't do any geometry warping or bouncing of light around inside the screen etc - I think these effects just add unwanted noise, I know people disagree. Please feel free to make you own and add them
Dont use this shader directly - use the crt\crt-sony-pvm-4k-hdr.slangp to have the proper chain of effects.
*/
#pragma format A2B10G10R10_UNORM_PACK32
layout(push_constant) uniform Push
{
vec4 SourceSize;
vec4 OriginalSize;
vec4 OutputSize;
uint FrameCount;
float ScanlineWidth;
float ScreenWidth;
float ScreenHeight;
} params;
/* #pragma parameter ScanlineWidth "Scanline Width" 4.0 0.0 20.0 1.0 */
#pragma parameter ScanlineWidth "Scanline Width" 0.5 0.0 1.0 0.01
#pragma parameter ScreenWidth "Screen Width" 3840.0 0.0 7680.0 1.0
#pragma parameter ScreenHeight "Screen Height" 2160.0 0.0 4320.0 1.0
layout(std140, set = 0, binding = 0) uniform UBO
{
mat4 MVP;
} global;
#pragma stage vertex
layout(location = 0) in vec4 Position;
layout(location = 1) in vec2 TexCoord;
layout(location = 0) out vec2 vTexCoord;
layout(location = 1) out float Scale;
void main()
{
gl_Position = global.MVP * Position;
vTexCoord = TexCoord;
vec2 ScreenSize = max(params.OutputSize.xy, vec2(params.ScreenWidth, params.ScreenHeight));
if((params.SourceSize.x > ScreenSize.x) || (params.SourceSize.y > ScreenSize.y))
{
Scale = 1.0;
}
else
{
float ScaleFactor = 2.0;
while(((params.SourceSize.x * ScaleFactor) <= ScreenSize.x) && ((params.SourceSize.y * ScaleFactor) <= ScreenSize.y))
{
ScaleFactor += 1.0;
}
Scale = ScaleFactor - 1.0;
}
}
#pragma stage fragment
layout(location = 0) in vec2 vTexCoord;
layout(location = 1) in float Scale;
layout(location = 0) out vec4 FragColor;
layout(set = 0, binding = 2) uniform sampler2D Source;
layout(set = 0, binding = 3) uniform sampler2D StockPass;
float ModInteger(float a, float b)
{
float m = a - floor((a + 0.5) / b) * b;
return floor(m + 0.5);
}
#define kPi 3.1415926536
#define kEuler 2.718281828459
#define kMax 1.0
#define kGuassianMin 1.1
#define kGuassianMax 3.0
#define Sharpness 2.5
float Ramp(const float gaussian, float colour)
{
return clamp(gaussian * colour, 0.0, 1.0);
}
void main()
{
float ScanlineSize = params.OutputSize.y / params.SourceSize.y;
/* vec2 InPixels = (vTexCoord * params.SourceSize.xy) * vec2(Scale); */
const vec2 InPixels = (vTexCoord * params.OutputSize.xy);
const float ScanlinePosition = (floor(vTexCoord.y * params.SourceSize.y) * ScanlineSize) + (ScanlineSize * 0.5);
float ScanlineDistance = ScanlinePosition - (floor(InPixels.y) + 0.5);
/*
if(ModInteger(floor(InPixels.y), Scale) < params.ScanlineWidth)
{
FragColor = vec4(texture(Source, vTexCoord).xyz, 1.0);
}
else
{
FragColor = vec4(0.0,0.0,0.0,1.0);
}
*/
ScanlineDistance /= ScanlineSize * params.ScanlineWidth;
ScanlineDistance = clamp(abs(ScanlineDistance * 2.0), 0.0, 1.0);
/* Gaussian distribution */
const float gaussian = pow(kEuler, -0.5 * pow(ScanlineDistance/0.3, 2.0));
//const float guassianf = gaussian * clamp(ScanlineSize / 2, 1.2, 5.0);
//const float guassian_clamp = clamp(guassianf, 0.0, 1.0);
float Ratio = (vTexCoord.x * params.SourceSize.x) - (floor(vTexCoord.x * params.SourceSize.x));
Ratio = clamp(((Ratio - 0.5) * Sharpness) + 0.5, 0.0f, 1.0);
const vec2 SourceTexCoord0 = vec2(vTexCoord.x, ScanlinePosition / params.OutputSize.y);
vec3 hdr_colour0 = texture(Source, SourceTexCoord0).xyz;
vec3 sdr_colour0 = texture(StockPass, SourceTexCoord0).xyz;
const vec2 SourceTexCoord1 = vec2(vTexCoord.x + (1.0 / params.SourceSize.x), ScanlinePosition / params.OutputSize.y);
vec3 hdr_colour1 = texture(Source, SourceTexCoord1).xyz;
vec3 sdr_colour1 = texture(StockPass, SourceTexCoord1).xyz;
vec3 hdr_colour = mix(hdr_colour0, hdr_colour1, vec3(Ratio));
vec3 sdr_colour = mix(sdr_colour0, sdr_colour1, vec3(Ratio));
/* TODO: Below needs optimising - just needs a mask array rather than doing if's! Ran out of time */
#if 0 /* 12 pattern - 8K screens */
float x = ModInteger(floor(InPixels.x), 12.0);
if(x < 3.0)
{
float red = Ramp(gaussian, (sdr_colour.x * kGuassianMax) + kGuassianMin);
FragColor = vec4(red * hdr_colour.x, 0.0, 0.0, 1.0);
}
else if(x < 4.0)
{
FragColor = vec4(0.0, 0.0, 0.0, 1.0); /* black */
}
else if(x < 7.0)
{
float green = Ramp(gaussian, (sdr_colour.y * kGuassianMax) + kGuassianMin);
FragColor = vec4(0.0, green * hdr_colour.y, 0.0, 1.0);
}
else if(x < 8.0)
{
FragColor = vec4(0.0, 0.0, 0.0, 1.0); /* black */
}
else if(x < 11.0)
{
float blue = Ramp(gaussian, (sdr_colour.z * kGuassianMax) + kGuassianMin);
FragColor = vec4(0.0, 0.0, blue * hdr_colour.z, 1.0);
}
else /* if(x < 12.0) */
{
FragColor = vec4(0.0, 0.0, 0.0, 1.0); /* black */
}
#endif /* 0 */
#if 0 /* 9 pattern - 8K screens */
float x = ModInteger(floor(InPixels.x), 9.0);
if(x < 2.0)
{
float red = Ramp(gaussian, (sdr_colour.x * kGuassianMax) + kGuassianMin);
FragColor = vec4(red * hdr_colour.x, 0.0, 0.0, 1.0);
}
else if(x < 3.0)
{
FragColor = vec4(0.0, 0.0, 0.0, 1.0); /* black */
}
else if(x < 5.0)
{
float green = Ramp(gaussian, (sdr_colour.y * kGuassianMax) + kGuassianMin);
FragColor = vec4(0.0, green * hdr_colour.y, 0.0, 1.0);
}
else if(x < 6.0)
{
FragColor = vec4(0.0, 0.0, 0.0, 1.0); /* black */
}
else if(x < 8.0)
{
float blue = Ramp(gaussian, (sdr_colour.z * kGuassianMax) + kGuassianMin);
FragColor = vec4(0.0, 0.0, blue * hdr_colour.z, 1.0);
}
else /* if(x < 9.0) */
{
FragColor = vec4(0.0, 0.0, 0.0, 1.0); /* black */
}
#endif /* 0 */
#if 1 /* 6 pattern - 4K screens */
float x = ModInteger(floor(InPixels.x), 6.0);
if(x < 2.0)
{
float red = Ramp(gaussian, (sdr_colour.x * kGuassianMax) + kGuassianMin);
FragColor = vec4(red * hdr_colour.x, 0.0, 0.0, 1.0);
}
else if(x < 4.0)
{
float green = Ramp(gaussian, (sdr_colour.y * kGuassianMax) + kGuassianMin);
FragColor = vec4(0.0, green * hdr_colour.y, 0.0, 1.0);
}
else /* if(x < 6.0) */
{
float blue = Ramp(gaussian, (sdr_colour.z * kGuassianMax) + kGuassianMin);
FragColor = vec4(0.0, 0.0, blue * hdr_colour.z, 1.0);
}
#endif /* 1 */
#if 0 /* 3 pattern - 1080-1440p screens */
float x = ModInteger(floor(InPixels.x), 3.0);
if(x < 1.0)
{
float red = Ramp(gaussian, (sdr_colour.x * kGuassianMax) + kGuassianMin);
FragColor = vec4(red * hdr_colour.x, 0.0, 0.0, 1.0);
}
else if(x < 2.0)
{
float green = Ramp(gaussian, (sdr_colour.y * kGuassianMax) + kGuassianMin);
FragColor = vec4(0.0, green * hdr_colour.y, 0.0, 1.0);
}
else /* if(x < 3.0) */
{
float blue = Ramp(gaussian, (sdr_colour.z * kGuassianMax) + kGuassianMin);
FragColor = vec4(0.0, 0.0, blue * hdr_colour.z, 1.0);
}
#endif /* 0 */
}

88
misc/hdr10.slang Normal file
View file

@ -0,0 +1,88 @@
#version 450
/*
Part of the crt-sony-pvm-4k-hdr shader group. Does the exact same thing as RetroArch does internally to map into HDR10 space.
This is used to do this mapping BEFORE screen effects are applied.
Originally part of the crt\crt-sony-pvm-4k-hdr.slangp but can be used for any shader
*/
#pragma format A2B10G10R10_UNORM_PACK32
layout(push_constant) uniform Push
{
vec4 SourceSize;
vec4 OriginalSize;
vec4 OutputSize;
uint FrameCount;
float PaperWhiteNits;
float ExpandGamut;
} params;
#pragma parameter PaperWhiteNits "Paper White Luminance" 400.0 0.0 10000.0 10.0
#pragma parameter ExpandGamut "ExpandGamut" 1.0 0.0 1.0 1.0
layout(std140, set = 0, binding = 0) uniform UBO
{
mat4 MVP;
} global;
#pragma stage vertex
layout(location = 0) in vec4 Position;
layout(location = 1) in vec2 TexCoord;
layout(location = 0) out vec2 vTexCoord;
void main()
{
gl_Position = global.MVP * Position;
vTexCoord = TexCoord;
}
#pragma stage fragment
layout(location = 0) in vec2 vTexCoord;
layout(location = 0) out vec4 FragColor;
layout(set = 0, binding = 2) uniform sampler2D Source;
#define kMaxNitsFor2084 10000.0f
const mat3 k709to2020 = mat3 (
0.6274040f, 0.3292820f, 0.0433136f,
0.0690970f, 0.9195400f, 0.0113612f,
0.0163916f, 0.0880132f, 0.8955950f);
/* START Converted from (Copyright (c) Microsoft Corporation - Licensed under the MIT License.) https://github.com/microsoft/Xbox-ATG-Samples/tree/master/Kits/ATGTK/HDR */
const mat3 kExpanded709to2020 = mat3 (
0.6274040f, 0.3292820f, 0.0433136f,
0.0457456, 0.941777, 0.0124772,
-0.00121055, 0.0176041, 0.983607);
vec3 LinearToST2084(vec3 normalizedLinearValue)
{
vec3 ST2084 = pow((0.8359375f + 18.8515625f * pow(abs(normalizedLinearValue), vec3(0.1593017578f))) / (1.0f + 18.6875f * pow(abs(normalizedLinearValue), vec3(0.1593017578f))), vec3(78.84375f));
return ST2084; /* Don't clamp between [0..1], so we can still perform operations on scene values higher than 10,000 nits */
}
/* END Converted from (Copyright (c) Microsoft Corporation - Licensed under the MIT License.) https://github.com/microsoft/Xbox-ATG-Samples/tree/master/Kits/ATGTK/HDR */
vec3 Hdr10(vec3 hdr)
{
/* Now convert into HDR10 */
vec3 rec2020 = hdr * k709to2020;
if(params.ExpandGamut > 0.0f)
{
rec2020 = hdr * kExpanded709to2020;
}
vec3 linearColour = rec2020 * (params.PaperWhiteNits / kMaxNitsFor2084);
vec3 hdr10 = LinearToST2084(linearColour);
return hdr10;
}
void main()
{
vec4 hdr = texture(Source, vTexCoord);
FragColor = vec4(Hdr10(hdr.rgb), hdr.a);
}

View file

@ -0,0 +1,87 @@
#version 450
/*
Part of the crt-sony-pvm-4k-hdr shader group. Does the exact same thing as RetroArch does internally to inverse tonemap from a SDR image to HDR.
This is used to do this mapping BEFORE screen effects are applied.
Originally part of the crt\crt-sony-pvm-4k-hdr.slangp but can be used for any shader
*/
#pragma format R16G16B16A16_SFLOAT
layout(push_constant) uniform Push
{
vec4 SourceSize;
vec4 OriginalSize;
vec4 OutputSize;
uint FrameCount;
float Contrast;
float PaperWhiteNits;
float MaxNits;
} params;
#pragma parameter Contrast "Contrast" 5.0 0.0 10.0 0.01
#pragma parameter PaperWhiteNits "Paper White Luminance" 400.0 0.0 10000.0 10.0
#pragma parameter MaxNits "Peak Luminance" 700.0 0.0 10000.0 10.0
layout(std140, set = 0, binding = 0) uniform UBO
{
mat4 MVP;
} global;
#pragma stage vertex
layout(location = 0) in vec4 Position;
layout(location = 1) in vec2 TexCoord;
layout(location = 0) out vec2 vTexCoord;
void main()
{
gl_Position = global.MVP * Position;
vTexCoord = TexCoord;
}
#pragma stage fragment
layout(location = 0) in vec2 vTexCoord;
layout(location = 0) out vec4 FragColor;
layout(set = 0, binding = 2) uniform sampler2D Source;
#define kMaxNitsFor2084 10000.0f
#define kEpsilon 0.0001f
#define kLumaChannelRatio 0.25f
vec3 InverseTonemap(vec3 sdr)
{
sdr = pow(abs(sdr), vec3(params.Contrast / 2.2f)); /* Display Gamma - needs to be determined by calibration screen */
float luma = dot(sdr, vec3(0.2126, 0.7152, 0.0722)); /* Rec BT.709 luma coefficients - https://en.wikipedia.org/wiki/Luma_(video) */
/* Inverse reinhard tonemap */
float maxValue = (params.MaxNits / params.PaperWhiteNits) + kEpsilon;
float elbow = maxValue / (maxValue - 1.0f);
float offset = 1.0f - ((0.5f * elbow) / (elbow - 0.5f));
float hdrLumaInvTonemap = offset + ((luma * elbow) / (elbow - luma));
float sdrLumaInvTonemap = luma / ((1.0f + kEpsilon) - luma); /* Convert the srd < 0.5 to 0.0 -> 1.0 range */
float lumaInvTonemap = (luma > 0.5f) ? hdrLumaInvTonemap : sdrLumaInvTonemap;
vec3 perLuma = sdr / (luma + kEpsilon) * lumaInvTonemap;
vec3 hdrInvTonemap = offset + ((sdr * elbow) / (elbow - sdr));
vec3 sdrInvTonemap = sdr / ((1.0f + kEpsilon) - sdr); /* Convert the srd < 0.5 to 0.0 -> 1.0 range */
vec3 perChannel = vec3(sdr.x > 0.5f ? hdrInvTonemap.x : sdrInvTonemap.x,
sdr.y > 0.5f ? hdrInvTonemap.y : sdrInvTonemap.y,
sdr.z > 0.5f ? hdrInvTonemap.z : sdrInvTonemap.z);
vec3 hdr = mix(perLuma, perChannel, vec3(kLumaChannelRatio));
return hdr;
}
void main()
{
vec4 sdr = texture(Source, vTexCoord);
FragColor = vec4(InverseTonemap(sdr.rgb), sdr.a);
}