This commit is contained in:
NightFox 2024-03-30 20:21:06 +08:00 committed by GitHub
commit 4ae9b0c423
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 680 additions and 47 deletions

View File

@ -1,5 +1,9 @@
#version 450
//#define SRGB_FAST_APPROXIMATION
#include "color_spaces.glsl"
#include "tonemapping.glsl"
layout(set=0,binding=0) uniform sampler2D tex;
layout(location=0) in vec2 vUv;
@ -7,6 +11,30 @@ layout(location=1) in vec4 vColor;
layout(location = 0) out vec4 outColor;
layout (constant_id = 0) const int vk_display_dr_mode = 0; // TODO: ubo
void main() {
outColor = texture(tex, vUv) * vColor;
switch (vk_display_dr_mode) {
// LDR: VK_FORMAT_B8G8R8A8_UNORM(44) VK_COLOR_SPACE_SRGB_NONLINEAR_KHR(0)
//case LDR_B8G8R8A8_UNORM_SRGB_NONLINEAR:
//break;
// LDR: VK_FORMAT_R8G8B8A8_SRGB(43) VK_COLOR_SPACE_SRGB_NONLINEAR_KHR(0)
// LDR: VK_FORMAT_R8G8B8A8_UNORM(37) VK_COLOR_SPACE_SRGB_NONLINEAR_KHR(0)
// LDR: VK_FORMAT_B8G8R8A8_SRGB(50) VK_COLOR_SPACE_SRGB_NONLINEAR_KHR(0)
// LDR: VK_FORMAT_A2B10G10R10_UNORM_PACK32(64) VK_COLOR_SPACE_SRGB_NONLINEAR_KHR(0)
// HDR: VK_FORMAT_R16G16B16A16_SFLOAT(97) VK_COLOR_SPACE_EXTENDED_SRGB_LINEAR_EXT(1000104002)
case HDR_R16G16B16A16_SFLOAT_EXTENDED_SRGB_LINEAR:
outColor.rgb = SRGBtoLINEAR(outColor.rgb); // standard menu
break;
// HDR: VK_FORMAT_A2B10G10R10_UNORM_PACK32(64) VK_COLOR_SPACE_HDR10_ST2084_EXT(1000104008)
case HDR_A2B10G10R10_UNORM_PACK32_HDR10_ST2084: // TODO: need special way for this format.
outColor.rgb = linearToPQ(Tonemap_Lottes(SRGBtoLINEAR(outColor.rgb))); // hot beautiful menu, brighter than necessary HUD
break;
}
}

View File

@ -1,7 +1,12 @@
#version 450
//#define SRGB_FAST_APPROXIMATION
#include "color_spaces.glsl"
//#include "tonemapping.glsl"
layout (constant_id = 0) const float alpha_test_threshold = 0.;
layout (constant_id = 1) const uint max_dlights = 1;
layout (constant_id = 2) const int vk_display_dr_mode = 0; // TODO: ubo
layout(set=1,binding=0) uniform sampler2D sTexture0;
layout(set=2,binding=0) uniform sampler2D sLightmap;
@ -41,6 +46,7 @@ void main() {
outColor.a = baseColor.a;
outColor.rgb = texture(sLightmap, vLightmapUV).rgb;
for (uint i = 0; i < ubo.num_lights; ++i) {
const vec4 light_pos_r = ubo.lights[i].pos_r;
const vec3 light_dir = light_pos_r.xyz - vPos;
@ -53,4 +59,25 @@ void main() {
if (ubo.debug_r_lightmap == 0)
outColor.rgb *= baseColor.rgb;
switch (vk_display_dr_mode) {
// LDR: VK_FORMAT_B8G8R8A8_UNORM(44) VK_COLOR_SPACE_SRGB_NONLINEAR_KHR(0)
//case LDR_B8G8R8A8_UNORM_SRGB_NONLINEAR:
//break;
// LDR: VK_FORMAT_R8G8B8A8_SRGB(43) VK_COLOR_SPACE_SRGB_NONLINEAR_KHR(0)
// LDR: VK_FORMAT_R8G8B8A8_UNORM(37) VK_COLOR_SPACE_SRGB_NONLINEAR_KHR(0)
// LDR: VK_FORMAT_B8G8R8A8_SRGB(50) VK_COLOR_SPACE_SRGB_NONLINEAR_KHR(0)
// LDR: VK_FORMAT_A2B10G10R10_UNORM_PACK32(64) VK_COLOR_SPACE_SRGB_NONLINEAR_KHR(0)
// HDR: VK_FORMAT_R16G16B16A16_SFLOAT(97) VK_COLOR_SPACE_EXTENDED_SRGB_LINEAR_EXT(1000104002)
case HDR_R16G16B16A16_SFLOAT_EXTENDED_SRGB_LINEAR:
outColor.rgb = SRGBtoLINEAR(outColor.rgb); // standard
break;
// HDR: VK_FORMAT_A2B10G10R10_UNORM_PACK32(64) VK_COLOR_SPACE_HDR10_ST2084_EXT(1000104008)
case HDR_A2B10G10R10_UNORM_PACK32_HDR10_ST2084:
outColor.rgb = linearToPQ(SRGBtoLINEAR(outColor.rgb)); // standard
break;
}
}

View File

@ -1,5 +1,9 @@
#version 450
#include "color_spaces.glsl"
layout(set=0,binding=0) uniform UBO {
mat4 mvp;
vec4 color;

View File

@ -1,3 +1,19 @@
// LDR: VK_FORMAT_B8G8R8A8_UNORM(44) VK_COLOR_SPACE_SRGB_NONLINEAR_KHR(0)
#define LDR_B8G8R8A8_UNORM_SRGB_NONLINEAR 0
// LDR: VK_FORMAT_B8G8R8A8_SRGB(50) VK_COLOR_SPACE_SRGB_NONLINEAR_KHR(0)
// LDR: VK_FORMAT_R8G8B8A8_UNORM(37) VK_COLOR_SPACE_SRGB_NONLINEAR_KHR(0)
// LDR: VK_FORMAT_R8G8B8A8_SRGB(43) VK_COLOR_SPACE_SRGB_NONLINEAR_KHR(0)
// LDR: VK_FORMAT_A2B10G10R10_UNORM_PACK32(64) VK_COLOR_SPACE_SRGB_NONLINEAR_KHR(0)
// HDR: VK_FORMAT_A2B10G10R10_UNORM_PACK32(64) VK_COLOR_SPACE_HDR10_ST2084_EXT(1000104008)
#define HDR_A2B10G10R10_UNORM_PACK32_HDR10_ST2084 1
// HDR: VK_FORMAT_R16G16B16A16_SFLOAT(97) VK_COLOR_SPACE_EXTENDED_SRGB_LINEAR_EXT(1000104002)
#define HDR_R16G16B16A16_SFLOAT_EXTENDED_SRGB_LINEAR 2
#ifndef RT_COLOR_SPACES_GLSL_INCLUDED
#define RT_COLOR_SPACES_GLSL_INCLUDED
#ifdef SRGB_FAST_APPROXIMATION
@ -21,12 +37,10 @@ float sRGB_OECF(const float sRGB) {
* color in sRGB space. This function will thus provide LDR RGB non-linear
* color in sRGB space -> LDR RGB linear color conversion.
*/
vec3 sRGB_OECF(const vec3 sRGB)
{
vec3 sRGB_OECF(const vec3 sRGB) {
return vec3(sRGB_OECF(sRGB.r), sRGB_OECF(sRGB.g), sRGB_OECF(sRGB.b));
}
vec4 sRGB_OECF(const vec4 sRGB)
{
vec4 sRGB_OECF(const vec4 sRGB) {
return vec4(sRGB_OECF(sRGB.r), sRGB_OECF(sRGB.g), sRGB_OECF(sRGB.b), sRGB.w);
}
vec3 sRGB_OECFFast(const vec3 sRGB) {
@ -65,3 +79,151 @@ vec4 OECF_sRGBFast(const vec4 linear) {
}
#endif //ifndef RT_COLOR_SPACES_GLSL_INCLUDED
// HDR stuff (High Dynamic Range Color Grading and Display in Frostbite)
// ported to GLSL, something maybe incorrect
// RGB with sRGB/Rec.709 primaries to CIE XYZ
vec3 RGBToXYZ(vec3 c) {
mat3 mat = mat3(
0.4124564, 0.2126729, 0.0193339,
0.3575761, 0.7151522, 0.1191920,
0.1804375, 0.0721750, 0.9503041
);
return mat * c;
}
vec3 XYZToRGB(vec3 c) {
mat3 mat = mat3(
3.24045483602140870, -0.96926638987565370, 0.05564341960421366,
-1.53713885010257510, 1.87601092884249100, -0.20402585426769815,
-0.49853154686848090, 0.04155608234667354, 1.05722516245792870
);
return mat * c;
}
// Converts XYZ tristimulus values into cone responses for the three types of cones in the human visual system, matching long, medium, and short wavelengths.
// Note that there are many LMS color spaces; this one follows the ICtCp color space specification.
vec3 XYZToLMS(vec3 c) {
mat3 mat = mat3(
0.3592, -0.1922, 0.0070,
0.6976, 1.1004, 0.0749,
-0.0358, 0.0755, 0.8434
);
return mat * c;
}
vec3 LMSToXYZ(vec3 c) {
mat3 mat = mat3(
2.07018005669561320, 0.36498825003265756, -0.04959554223893212,
-1.32645687610302100, 0.68046736285223520, -0.04942116118675749,
0.206616006847855170, -0.045421753075853236, 1.187995941732803400
);
return mat * c;
}
const float PQ_constant_N = (2610.0 / 4096.0 / 4.0);
const float PQ_constant_M = (2523.0 / 4096.0 * 128.0);
const float PQ_constant_C1 = (3424.0 / 4096.0);
const float PQ_constant_C2 = (2413.0 / 4096.0 * 32.0);
const float PQ_constant_C3 = (2392.0 / 4096.0 * 32.0);
// PQ (Perceptual Quantiser; ST.2084) encode/decode used for HDR TV and grading
vec3 linearToPQ(vec3 linearCol, const float maxPqValue) {
linearCol /= maxPqValue;
vec3 colToPow = pow(linearCol, vec3(PQ_constant_N));
vec3 numerator = PQ_constant_C1 + PQ_constant_C2*colToPow;
vec3 denominator = 1.0 + PQ_constant_C3*colToPow;
vec3 pq = pow(numerator / denominator, vec3(PQ_constant_M));
return pq;
}
vec3 PQtoLinear(vec3 linearCol, const float maxPqValue) {
vec3 colToPow = pow(linearCol, 1.0 / vec3(PQ_constant_M));
vec3 numerator = max(colToPow - PQ_constant_C1, 0.0);
vec3 denominator = PQ_constant_C2 - (PQ_constant_C3 * colToPow);
vec3 linearColor = pow(numerator / denominator, vec3(1.0 / PQ_constant_N));
linearColor *= maxPqValue;
return linearColor;
}
vec3 linearToPQ(const vec3 x) {
return linearToPQ(x, 100);
}
// Aplies exponential ("Photographic") luma compression
float rangeCompress(float x) {
return 1.0 - exp(-x);
}
float rangeCompress(float val, float threshold) {
float v1 = val;
float v2 = threshold + (1 - threshold) * rangeCompress((val - threshold) / (1 - threshold));
return val < threshold ? v1 : v2;
}
vec3 rangeCompress(vec3 val, float threshold) {
return vec3(
rangeCompress(val.x, threshold),
rangeCompress(val.y, threshold),
rangeCompress(val.z, threshold)
);
}
// RGB with sRGB/Rec.709 primaries to ICtCp
vec3 RGBToICtCp(vec3 col) {
col = RGBToXYZ(col);
col = XYZToLMS(col);
// 1.0f = 100 nits, 100.0f = 10k nits
col = linearToPQ(max(0.0.xxx, col), 100.0);
// Convert PQ-LMS into ICtCp. Note that the "S" channel is not used,
// but overlap between the cone responses for long, medium, and short wavelengths
// ensures that the corresponding part of the spectrum contributes to luminance.
mat3 mat = mat3(
0.5000, 1.6137, 4.3780,
0.5000, -3.3234, -4.2455,
0.0000, 1.7097, -0.1325
);
return mat * col;
}
vec3 ICtCpToRGB(vec3 col) {
mat3 mat = mat3(
1.0, 1.0, 1.0,
0.00860514569398152, -0.00860514569398152, 0.56004885956263900,
0.11103560447547328, -0.11103560447547328, -0.32063747023212210
);
col = mat * col;
// 1.0f = 100 nits, 100.0f = 10k nits
col = PQtoLinear(col, 100.0);
col = LMSToXYZ(col);
return XYZToRGB(col);
}
vec3 applyHuePreservingShoulder(vec3 col) {
vec3 ictcp = RGBToICtCp(col);
// Hue-preserving range compression requires desaturation in order to achieve a natural look. We adaptively saturate the input based on its luminance.
float saturationAmount = pow(smoothstep(1.0, 0.3, ictcp.x), 1.3);
col = ICtCpToRGB(ictcp * vec3(1, saturationAmount.xx));
// TODO: how to do it right for HDR?
/*
// Only compress luminance starting at a certain point. Dimmer inputs are passed through without modification.
float linearSegmentEnd = 0.25;
// Hue-preserving mapping
float maxCol = max(col.x, max(col.y, col.z));
float mappedMax = rangeCompress(maxCol, linearSegmentEnd);
vec3 compressedHuePreserving = col * mappedMax / maxCol;
// Non-hue preserving mapping
vec3 perChannelCompressed = rangeCompress(col, linearSegmentEnd);
// Combine hue-preserving and non-hue-preserving colors. Absolute hue preservation looks unnatural, as bright colors *appear* to have been hue shifted.
// Actually doing some amount of hue shifting looks more pleasing
col = mix(perChannelCompressed, compressedHuePreserving, 0.6);
*/
vec3 ictcpMapped = RGBToICtCp(col);
// Smoothly ramp off saturation as brightness increases, but keep some even for very bright input
float postCompressionSaturationBoost = 0.3 * smoothstep(1.0, 0.5, ictcp.x);
// Re-introduce some hue from the pre-compression color. Something similar could be accomplished by delaying the luma-dependent desaturation before range compression.
// Doing it here however does a better job of preserving perceptual luminance of highly saturated colors. Because in the hue-preserving path we only range-compress the max channel,
// saturated colors lose luminance. By desaturating them more aggressively first, compressing, and then re-adding some saturation, we can preserve their brightness to a greater extent.
ictcpMapped.yz = mix(ictcpMapped.yz, ictcp.yz * ictcpMapped.x / max(1e-3, ictcp.x), postCompressionSaturationBoost);
col = ICtCpToRGB(ictcpMapped);
return col;
}
// TODO: maybe LogC stuff

View File

@ -2,6 +2,7 @@
#include "debug.glsl"
#include "utils.glsl"
#include "color_spaces.glsl"
#include "tonemapping.glsl"
#define GLSL
#include "ray_interop.h"
@ -51,32 +52,6 @@ layout(set = 0, binding = 20, rgba16f) uniform readonly image2D legacy_blend;
const int INDIRECT_SCALE = 2;
// Blatantly copypasted from https://www.shadertoy.com/view/XsGfWV
vec3 aces_tonemap(vec3 color){
mat3 m1 = mat3(
0.59719, 0.07600, 0.02840,
0.35458, 0.90834, 0.13383,
0.04823, 0.01566, 0.83777
);
mat3 m2 = mat3(
1.60475, -0.10208, -0.00327,
-0.53108, 1.10813, -0.07276,
-0.07367, -0.00605, 1.07602
);
vec3 v = m1 * color;
vec3 a = v * (v + 0.0245786) - 0.000090537;
vec3 b = v * (0.983729 * v + 0.4329510) + 0.238081;
//return pow(clamp(m2 * (a / b), 0.0, 1.0), vec3(1.0 / 2.2));
return clamp(m2 * (a / b), 0.0, 1.0);
}
vec3 reinhard(vec3 color){
return color / (color + 1.0);
}
vec3 reinhard02(vec3 c, vec3 Cwhite2) {
return c * (1. + c / Cwhite2) / (1. + c);
}
float normpdf2(in float x2, in float sigma) { return 0.39894*exp(-0.5*x2/(sigma*sigma))/sigma; }
float normpdf(in float x, in float sigma) { return normpdf2(x*x, sigma); }
@ -549,14 +524,35 @@ void main() {
colour += imageLoad(emissive, pix).rgb;
// Revealage. TODO: which colorspace?
colour *= legacy_blend.a;
colour = LINEARtoSRGB(colour);
// See issue https://github.com/w23/xash3d-fwgs/issues/668, map test_blendmode_additive_alpha.
// Adding emissive_blend to the final color in the *incorrect* sRGB-γ space. It makes
// it look much more like the original. Adding emissive in the *correct* linear space differs
// from the original a lot, and looks perceptively worse.
colour += legacy_blend.rgb;
switch (ubo.ubo.vk_display_dr_mode) {
// LDR: VK_FORMAT_B8G8R8A8_UNORM(44) VK_COLOR_SPACE_SRGB_NONLINEAR_KHR(0)
case LDR_B8G8R8A8_UNORM_SRGB_NONLINEAR:
colour = LINEARtoSRGB(colour);
colour += legacy_blend.rgb;
break;
// LDR: VK_FORMAT_R8G8B8A8_SRGB(43) VK_COLOR_SPACE_SRGB_NONLINEAR_KHR(0)
// LDR: VK_FORMAT_R8G8B8A8_UNORM(37) VK_COLOR_SPACE_SRGB_NONLINEAR_KHR(0)
// LDR: VK_FORMAT_B8G8R8A8_SRGB(50) VK_COLOR_SPACE_SRGB_NONLINEAR_KHR(0)
// LDR: VK_FORMAT_A2B10G10R10_UNORM_PACK32(64) VK_COLOR_SPACE_SRGB_NONLINEAR_KHR(0)
// HDR: VK_FORMAT_R16G16B16A16_SFLOAT(97) VK_COLOR_SPACE_EXTENDED_SRGB_LINEAR_EXT(1000104002)
case HDR_R16G16B16A16_SFLOAT_EXTENDED_SRGB_LINEAR:
colour += SRGBtoLINEAR(legacy_blend.rgb*5); // hack
break;
// HDR: VK_FORMAT_A2B10G10R10_UNORM_PACK32(64) VK_COLOR_SPACE_HDR10_ST2084_EXT(1000104008)
case HDR_A2B10G10R10_UNORM_PACK32_HDR10_ST2084:
colour = linearToPQ(applyHuePreservingShoulder(colour), 100);
colour += agx(SRGBtoLINEAR(legacy_blend.rgb)*0.5); // hack
break;
}
DEBUG_VALIDATE_RANGE_VEC3("denoiser.colour", colour, 0., 1e6);

View File

@ -204,6 +204,7 @@ struct UniformBuffer {
uint debug_display_only;
uint debug_flags;
uint vk_display_dr_mode;
};
#undef PAD

View File

@ -113,9 +113,13 @@ void primaryRayHit(rayQueryEXT rq, inout RayPayloadPrimary payload) {
// Real correct emissive color
//payload.emissive.rgb = kusok.emissive;
//payload.emissive.rgb = kusok.emissive * SRGBtoLINEAR(payload.base_color_a.rgb);
//payload.emissive.rgb = LINEARtoSRGB(kusok.emissive) * LINEARtoSRGB(payload.base_color_a.rgb);
//payload.emissive.rgb = clamp((kusok.emissive * (1.0/3.0) / 20), 0, 1.0) * SRGBtoLINEAR(payload.base_color_a.rgb);
//payload.emissive.rgb = (sqrt(sqrt(kusok.emissive)) * (1.0/3.0)) * SRGBtoLINEAR(payload.base_color_a.rgb);
payload.emissive.rgb = (sqrt(kusok.emissive) / 8) * payload.base_color_a.rgb;
//payload.emissive.rgb = (sqrt(kusok.emissive) / 8) * payload.base_color_a.rgb;
payload.emissive.rgb = (sqrt(kusok.emissive) / 6) * payload.base_color_a.rgb; // better for HDR (maybe 4 better)
//payload.emissive.rgb = clamp(sqrt(kusok.emissive) / 4, 0.0, 1.5) * payload.base_color_a.rgb;
//payload.emissive.rgb = (pow(kusok.emissive, vec3(1/2.2)) / 8) * payload.base_color_a.rgb;
//payload.emissive.rgb = kusok.emissive * payload.base_color_a.rgb;
#else
// Fake texture color

View File

@ -3,7 +3,7 @@
#include "debug.glsl"
#include "utils.glsl"
#include "color_spaces.glsl"
//#include "color_spaces.glsl"
// Taken from Journal of Computer Graphics Techniques, Vol. 10, No. 1, 2021.
// Improved Shader and Texture Level of Detail Using Ray Cones,

View File

@ -0,0 +1,188 @@
// Blatantly copypasted from https://www.shadertoy.com/view/XsGfWV
vec3 aces_tonemap(vec3 color){
mat3 m1 = mat3(
0.59719, 0.07600, 0.02840,
0.35458, 0.90834, 0.13383,
0.04823, 0.01566, 0.83777
);
mat3 m2 = mat3(
1.60475, -0.10208, -0.00327,
-0.53108, 1.10813, -0.07276,
-0.07367, -0.00605, 1.07602
);
vec3 v = m1 * color;
vec3 a = v * (v + 0.0245786) - 0.000090537;
vec3 b = v * (0.983729 * v + 0.4329510) + 0.238081;
return clamp(m2 * (a / b), 0.0, 1.0);
}
vec3 reinhard_tonemap(vec3 color){
return color / (color + 1.0);
}
vec3 reinhard02_tonemap(vec3 c, vec3 Cwhite2) {
return c * (1. + c / Cwhite2) / (1. + c);
}
// https://www.shadertoy.com/view/WdjSW3
float Tonemap_Uchimura(float x, float P, float a, float m, float l, float c, float b) {
// Uchimura 2017, "HDR theory and practice"
// Math: https://www.desmos.com/calculator/gslcdxvipg
// Source: https://www.slideshare.net/nikuque/hdr-theory-and-practicce-jp
float l0 = ((P - m) * l) / a;
float L0 = m - m / a;
float L1 = m + (1.0 - m) / a;
float S0 = m + l0;
float S1 = m + a * l0;
float C2 = (a * P) / (P - S1);
float CP = -C2 / P;
float w0 = 1.0 - smoothstep(0.0, m, x);
float w2 = step(m + l0, x);
float w1 = 1.0 - w0 - w2;
float T = m * pow(x / m, c) + b;
float S = P - (P - S1) * exp(CP * (x - S0));
float L = m + a * (x - m);
return T * w0 + L * w1 + S * w2;
}
float Tonemap_Uchimura(float x) {
const float P = 1.0; // max display brightness
const float a = 1.0; // contrast
const float m = 0.22; // linear section start
const float l = 0.4; // linear section length
const float c = 1.33; // black
const float b = 0.0; // pedestal
return Tonemap_Uchimura(x, P, a, m, l, c, b);
}
vec3 Tonemap_Uchimura(vec3 x) {
return vec3(Tonemap_Uchimura(x.r), Tonemap_Uchimura(x.g), Tonemap_Uchimura(x.b));
}
float Tonemap_Lottes(float x) {
// Lottes 2016, "Advanced Techniques and Optimization of HDR Color Pipelines"
const float a = 1.6;
const float d = 0.977;
const float hdrMax = 8.0;
const float midIn = 0.18;
const float midOut = 0.267;
// Can be precomputed
const float b =
(-pow(midIn, a) + pow(hdrMax, a) * midOut) /
((pow(hdrMax, a * d) - pow(midIn, a * d)) * midOut);
const float c =
(pow(hdrMax, a * d) * pow(midIn, a) - pow(hdrMax, a) * pow(midIn, a * d) * midOut) /
((pow(hdrMax, a * d) - pow(midIn, a * d)) * midOut);
return pow(x, a) / (pow(x, a * d) * b + c);
}
vec3 Tonemap_Lottes(vec3 x) {
return vec3(Tonemap_Lottes(x.r), Tonemap_Lottes(x.g), Tonemap_Lottes(x.b));
}
// https://iolite-engine.com/blog_posts/minimal_agx_implementation
// 0: Default, 1: Golden, 2: Punchy
#define AGX_LOOK 0
// Mean error^2: 3.6705141e-06
vec3 agxDefaultContrastApprox(vec3 x) {
vec3 x2 = x * x;
vec3 x4 = x2 * x2;
return + 15.5 * x4 * x2
- 40.14 * x4 * x
+ 31.96 * x4
- 6.868 * x2 * x
+ 0.4298 * x2
+ 0.1191 * x
- 0.00232;
}
/*
// Mean error^2: 1.85907662e-06
vec3 agxDefaultContrastApprox(vec3 x) {
vec3 x2 = x * x;
vec3 x4 = x2 * x2;
vec3 x6 = x4 * x2;
return - 17.86 * x6 * x
+ 78.01 * x6
- 126.7 * x4 * x
+ 92.06 * x4
- 28.72 * x2 * x
+ 4.361 * x2
- 0.1718 * x
+ 0.002857;
}
*/
vec3 agx(vec3 val) {
const mat3 agx_mat = mat3(
0.842479062253094, 0.0423282422610123, 0.0423756549057051,
0.0784335999999992, 0.878468636469772, 0.0784336,
0.0792237451477643, 0.0791661274605434, 0.879142973793104
);
const float min_ev = -12.47393f;
const float max_ev = 4.026069f;
// Input transform (inset)
val = agx_mat * val;
// Log2 space encoding
val = clamp(log2(val), min_ev, max_ev);
val = (val - min_ev) / (max_ev - min_ev);
// Apply sigmoid function approximation
val = agxDefaultContrastApprox(val);
return val;
}
vec3 agxEotf(vec3 val) {
const mat3 agx_mat_inv = mat3(
1.19687900512017, -0.0528968517574562, -0.0529716355144438,
-0.0980208811401368, 1.15190312990417, -0.0980434501171241,
-0.0990297440797205, -0.0989611768448433, 1.15107367264116
);
// Inverse input transform (outset)
val = agx_mat_inv * val;
// sRGB IEC 61966-2-1 2.2 Exponent Reference EOTF Display
// NOTE: We're linearizing the output here. Comment/adjust when
// *not* using a sRGB render target
val = pow(val, vec3(2.2));
return val;
}
vec3 agxLook(vec3 val) {
const vec3 lw = vec3(0.2126, 0.7152, 0.0722);
float luma = dot(val, lw);
// Default
vec3 offset = vec3(0.0);
vec3 slope = vec3(1.0);
vec3 power = vec3(1.0);
float sat = 1.0;
#if AGX_LOOK == 1
// Golden
slope = vec3(1.0, 0.9, 0.5);
power = vec3(0.8);
sat = 0.8;
#elif AGX_LOOK == 2
// Punchy
slope = vec3(1.0);
power = vec3(1.35, 1.35, 1.35);
sat = 1.4;
#endif
// ASC CDL
val = pow(val * slope + offset, power);
return luma + sat * (val - luma);
}

View File

@ -112,6 +112,19 @@ static const char* device_extensions_extra[] = {
VK_EXT_CALIBRATED_TIMESTAMPS_EXTENSION_NAME,
};
static const VkSurfaceFormatKHR supported_HDR_formats[] = {
{ VK_FORMAT_A2B10G10R10_UNORM_PACK32, VK_COLOR_SPACE_HDR10_ST2084_EXT },
{ VK_FORMAT_R16G16B16A16_SFLOAT, VK_COLOR_SPACE_EXTENDED_SRGB_LINEAR_EXT },
};
static const VkSurfaceFormatKHR supported_LDR_formats[] = {
{ VK_FORMAT_B8G8R8A8_UNORM, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR },
//{ VK_FORMAT_B8G8R8A8_SRGB, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR },
//{ VK_FORMAT_R8G8B8A8_UNORM, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR },
//{ VK_FORMAT_R8G8B8A8_SRGB, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR },
//{ VK_FORMAT_A2B10G10R10_UNORM_PACK32, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR },
};
VkBool32 VKAPI_PTR debugCallback(
VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,
VkDebugUtilsMessageTypeFlagsEXT messageTypes,
@ -665,6 +678,69 @@ static qboolean createDevice( void ) {
gEngine.Con_Printf( S_ERROR "No compatibe Vulkan devices found. Vulkan render will not be available\n" );
return false;
}
void setSurfaceFormat( qboolean hdr_output_enabled )
{
// TODO: add LDR formats too
vk_core.output_surface.format = supported_LDR_formats[0].format;
vk_core.output_surface.colorSpace = supported_LDR_formats[0].colorSpace;
uint32_t vk_display_dr_mode = (uint32_t)vk_hdr_output->value-1;
if (hdr_output_enabled && vk_display_dr_mode <= ARRAYSIZE(supported_HDR_formats)) {
for (int i = 0; i < vk_core.surface.num_surface_formats; ++i) {
uint32_t format = vk_core.surface.surface_formats[i].format;
uint32_t colorSpace = vk_core.surface.surface_formats[i].colorSpace;
if (format == supported_HDR_formats[vk_display_dr_mode].format
&& colorSpace == supported_HDR_formats[vk_display_dr_mode].colorSpace) {
vk_core.output_surface.format = format;
vk_core.output_surface.colorSpace = colorSpace;
break;
}
}
}
if (vk_core.hdr_output) { // update vk_hdr_output description cvar
char vk_hdr_output_description[4096];
char vk_hdr_output_description_format_list[3840];
vk_hdr_output_description[0] = 0;
vk_hdr_output_description_format_list[0] = 0;
for (int32_t i = 0; i < vk_core.surface.num_surface_formats; ++i) {
for (int32_t ii = 0; ii < ARRAYSIZE(supported_HDR_formats); ++ii) {
if (vk_core.surface.surface_formats[i].format == supported_HDR_formats[ii].format
&& vk_core.surface.surface_formats[i].colorSpace == supported_HDR_formats[ii].colorSpace) {
char vk_hdr_output_format[196];
vk_hdr_output_format[0] = 0;
Q_snprintf(vk_hdr_output_format, sizeof(vk_hdr_output_format), "\t ^2%u^7, ^5%s+%s^7\n",
ii+1,
R_VkFormatName(vk_core.surface.surface_formats[i].format),
R_VkColorSpaceName(vk_core.surface.surface_formats[i].colorSpace)
);
Q_strncat(vk_hdr_output_description_format_list, vk_hdr_output_format, sizeof(vk_hdr_output_description_format_list));
break;
}
}
}
Q_snprintf(vk_hdr_output_description, sizeof(vk_hdr_output_description),
"EXPERIMENTAL: High Dynamic Range output mode (Warning: ^1You must enable HDR in the OS settings beforehand^3)\n\tSelected surface format (^2mode^7, ^5format^7):\n\t^2%s^7, ^5%s+%s^7\n\tSupported HDR formats (^2mode^7, ^5format^7):\n",
vk_hdr_output->string,
R_VkFormatName(vk_core.output_surface.format),
R_VkColorSpaceName(vk_core.output_surface.colorSpace)
);
Q_strncat(vk_hdr_output_description, vk_hdr_output_description_format_list, sizeof(vk_hdr_output_description));
vk_hdr_output = gEngine.Cvar_Get( "vk_hdr_output", vk_hdr_output->string, FCVAR_GLCONFIG, vk_hdr_output_description);
ClearBits(vk_hdr_output->flags, FCVAR_CHANGED);
}
gEngine.Con_Reportf("Selected surface format:\n");
gEngine.Con_Reportf("\t%s(%u) %s(%u)\n",
R_VkFormatName(vk_core.output_surface.format), vk_core.output_surface.format,
R_VkColorSpaceName(vk_core.output_surface.colorSpace), vk_core.output_surface.colorSpace
);
}
static qboolean initSurface( void )
{
XVK_CHECK(vkGetPhysicalDeviceSurfacePresentModesKHR(vk_core.physical_device.device, vk_core.surface.surface, &vk_core.surface.num_present_modes, vk_core.surface.present_modes));
@ -689,9 +765,23 @@ static qboolean initSurface( void )
R_VkColorSpaceName(vk_core.surface.surface_formats[i].colorSpace), vk_core.surface.surface_formats[i].colorSpace);
}
// check support HDR
for (int32_t i = 0; i < vk_core.surface.num_surface_formats; ++i) {
for (int32_t ii = 0; ii < ARRAYSIZE(supported_HDR_formats); ++ii) {
if (vk_core.surface.surface_formats[i].format == supported_HDR_formats[ii].format
&& vk_core.surface.surface_formats[i].colorSpace == supported_HDR_formats[ii].colorSpace) {
vk_core.hdr_output = true;
break;
}
}
}
setSurfaceFormat(CVAR_TO_BOOL(vk_hdr_output));
return true;
}
// TODO modules
/*
typedef struct r_vk_module_s {
@ -728,6 +818,7 @@ qboolean R_VkInit( void )
vk_core.validate = !!gEngine.Sys_CheckParm("-vkvalidate");
vk_core.debug = vk_core.validate || !!(gEngine.Sys_CheckParm("-vkdebug") || gEngine.Sys_CheckParm("-gldebug"));
vk_core.rtx = false;
vk_core.hdr_output = false;
VK_LoadCvars();
@ -786,14 +877,14 @@ qboolean R_VkInit( void )
if (!createDevice())
return false;
VK_LoadCvarsAfterInit();
if (!R_VkCombuf_Init())
return false;
if (!initSurface())
return false;
VK_LoadCvarsAfterInit();
if (!VK_DevMemInit())
return false;

View File

@ -23,6 +23,8 @@ void R_VkSemaphoreDestroy(VkSemaphore sema);
VkFence R_VkFenceCreate( qboolean signaled );
void R_VkFenceDestroy(VkFence fence);
void setSurfaceFormat( qboolean hdr_output_enabled );
// TODO move all these to vk_device.{h,c} or something
typedef struct physical_device_s {
VkPhysicalDevice device;
@ -45,7 +47,7 @@ typedef struct vulkan_core_s {
// TODO store important capabilities that affect render code paths
// (as rtx, dedicated gpu memory, bindless, etc) separately in a struct
qboolean debug, validate, rtx, nv_checkpoint;
qboolean debug, validate, rtx, nv_checkpoint, hdr_output;
struct {
VkSurfaceKHR surface;
uint32_t num_surface_formats;
@ -55,6 +57,8 @@ typedef struct vulkan_core_s {
VkPresentModeKHR *present_modes;
} surface;
VkSurfaceFormatKHR output_surface;
physical_device_t physical_device;
VkDevice device;
VkQueue queue;

View File

@ -2,6 +2,7 @@
#include "vk_common.h"
#include "vk_core.h"
#include "vk_logs.h"
#include "vk_swapchain.h"
#define NONEXTERN_CVAR(cvar) cvar_t *cvar;
DECLARE_CVAR(NONEXTERN_CVAR)
@ -28,6 +29,9 @@ void VK_LoadCvars( void )
rt_force_disable = gEngine.Cvar_Get( "rt_force_disable", "0", FCVAR_GLCONFIG, "Force disable Ray Tracing" );
vk_device_target_id = gEngine.Cvar_Get( "vk_device_target_id", "", FCVAR_GLCONFIG, "Selected video device id" );
vk_hdr_output = gEngine.Cvar_Get( "vk_hdr_output", "0", FCVAR_GLCONFIG, "" ); // TODO: refactoring
vk_debug_log = gEngine.Cvar_Get("vk_debug_log_", "", FCVAR_GLCONFIG | FCVAR_READ_ONLY, "");
gEngine.Cmd_AddCommand("vk_debug_log", setDebugLog, "Set modules to enable debug logs for");
@ -36,6 +40,16 @@ void VK_LoadCvars( void )
void VK_LoadCvarsAfterInit( void )
{
rt_capable = gEngine.Cvar_Get( "rt_capable", vk_core.rtx ? "1" : "0", FCVAR_READ_ONLY, "" );
vk_hdr_output_capable = gEngine.Cvar_Get( "vk_hdr_output_capable", vk_core.hdr_output ? "1" : "0", FCVAR_READ_ONLY, "" );
if (!vk_core.hdr_output) {
vk_hdr_output = gEngine.Cvar_Get( "vk_hdr_output", "0", FCVAR_READ_ONLY, "DISABLED: not supported by your hardware/software/drivers" );
}
ClearBits( vk_hdr_output->flags, FCVAR_CHANGED );
//vk_hdr_output_max_brightness_ui = gEngine.Cvar_Get( "vk_hdr_output_max_brightness_ui", "100", FCVAR_GLCONFIG, "Brightness output HDR for UI (RESTART REQUIRED)" );
//vk_hdr_output_max_brightness = gEngine.Cvar_Get( "vk_hdr_output_max_brightness", "400", FCVAR_GLCONFIG, "Brightness output HDR ingame" );
// FIXME: remove this
gEngine.Cmd_AddCommand( "vk_debug_switch_swapchain_format", switchSwapchain, "EXPERIMENTAL: Apply changed swapchain format ^1(UNSTABLE)^3" );
if (vk_core.rtx) {
rt_enable = gEngine.Cvar_Get( "rt_enable", "1", FCVAR_GLCONFIG, "Enable or disable Ray Tracing mode" );

View File

@ -26,6 +26,9 @@ void VK_LoadCvarsAfterInit( void );
X(rt_force_disable) \
X(rt_enable) \
X(rt_bounces) \
X(vk_hdr_output_capable) \
X(vk_hdr_output) \
X(vk_debug_switch_swapchain_format) \
#define EXTERN_CVAR(cvar) extern cvar_t *cvar;
DECLARE_CVAR(EXTERN_CVAR)

View File

@ -14,6 +14,7 @@
#include "profiler.h"
#include "r_speeds.h"
#include "xash3d_mathlib.h"
#include "eiface.h" // ARRAYSIZE
@ -243,6 +244,14 @@ void R_BeginFrame( qboolean clearScene ) {
vk_frame.width = g_frame.current.framebuffer.width;
vk_frame.height = g_frame.current.framebuffer.height;
if (vk_core.hdr_output && FBitSet( vk_hdr_output->flags, FCVAR_CHANGED )) {
setSurfaceFormat( true );
gEngine.Con_Printf("^1RESTART the game to display correctly!!!\n");
//switchSwapchain(); // FIXME CRASH
}
ClearBits( vk_hdr_output->flags, FCVAR_CHANGED );
VK_RenderBegin( vk_frame.rtx_enabled );
R_VkCombufBegin( frame->combuf );
@ -261,7 +270,7 @@ void VK_RenderFrame( const struct ref_viewpass_s *rvp )
static void enqueueRendering( vk_combuf_t* combuf, qboolean draw ) {
APROF_SCOPE_DECLARE_BEGIN(enqueue, __FUNCTION__);
const VkClearValue clear_value[] = {
{.color = {{1., 0., 0., 0.}}},
{.color = {{0., 0., 0., 0.}}}, // rasterization (clear) background
{.depthStencil = {1., 0.}} // TODO reverse-z
};
@ -492,6 +501,37 @@ static qboolean canBlitFromSwapchainToFormat( VkFormat dest_format ) {
return true;
}
// https://github.com/res2k/Q2RTX/blob/d393d74a1ddfb3f2db98b942389f240af30c0fa6/src/refresh/vkpt/conversion.h#L36
/*
Float -> Half converter functions, adapted from
https://stackoverflow.com/questions/1659440/32-bit-to-16-bit-floating-point-conversion
*/
typedef union {
float f;
int32_t si;
uint32_t ui;
} f2hBits;
static inline float halfToFloat(uint16_t value) {
static int const shift = 13;
static int const shiftSign = 16;
static uint32_t const inf = 0x7c00;
static uint32_t const signC = 0x8000; // flt16 sign bit
static int32_t const infN = 0x7F800000; // flt32 infinity
f2hBits v, s;
v.ui = value;
s.ui = v.ui & signC;
v.ui ^= s.ui;
int32_t is_norm = v.ui < inf;
v.ui = (s.ui << shiftSign) | (v.ui << shift);
s.ui = 0x77800000; // bias_mul
v.f *= s.f;
v.ui |= -!is_norm & infN;
return v.f;
}
static rgbdata_t *R_VkReadPixels( void ) {
const VkFormat dest_format = VK_FORMAT_R8G8B8A8_UNORM;
r_vk_image_t dest_image;
@ -665,7 +705,45 @@ static rgbdata_t *R_VkReadPixels( void ) {
if (dest_format != VK_FORMAT_R8G8B8A8_UNORM || SWAPCHAIN_FORMAT != VK_FORMAT_B8G8R8A8_UNORM) {
gEngine.Con_Printf(S_WARN "Don't have a blit function for this format pair, will save as-is without conversion; expect image to look wrong\n");
blit = true;
} else {
} else if (vk_core.output_surface.format == VK_FORMAT_A2B10G10R10_UNORM_PACK32) {
byte *dst = r_shot->buffer;
const float RGB_max = 1.0f / 1023.0f; // Max color value for 10 bit
const float Alpha_max = 1.0f / 3.0f; // Max alpha value for 2 bit
for (int y = 0; y < vk_frame.height; ++y, mapped += layout.rowPitch) {
const uint32_t *src = (uint32_t*)(byte*)mapped;
for (int x = 0; x < vk_frame.width; ++x, dst += 4, src += 1) {
const float R = ((src[0] >> 0) & 1023) * RGB_max;
const float G = ((src[0] >> 10) & 1023) * RGB_max;
const float B = ((src[0] >> 20) & 1023) * RGB_max;
const float A = ((src[0] >> 30) & 3) * Alpha_max;
// FIXME: better color mapping
dst[0] = R * 255;
dst[1] = G * 255;
dst[2] = B * 255;
dst[3] = A * 255;
}
}
} else if (vk_core.output_surface.format == VK_FORMAT_R16G16B16A16_SFLOAT) {
byte *dst = r_shot->buffer;
for (int y = 0; y < vk_frame.height; ++y, mapped += layout.rowPitch) {
const uint16_t *src = (uint16_t*)(byte*)mapped;
for (int x = 0; x < vk_frame.width; ++x, dst += 4, src += 2) {
// TODO: separate pipeline for screenshot (preprocessing tonemapping and gamma-correction)
// FIXME: BROKEN BLUE CHANNEL, RED = MAGENTA, WTF!?
const float R = pow(halfToFloat(src[0]), 1.0/2.2);
const float G = pow(halfToFloat(src[1]), 1.0/2.2);
const float B = pow(halfToFloat(src[2]), 1.0/2.2);
const float A = pow(halfToFloat(src[0]), 1.0/2.2);
// clamp
float maxVal = Q_max(Q_max(R, G), B);
maxVal = (maxVal > 1.0f) ? maxVal : 1.0f;
dst[0] = (R / maxVal) * 255;
dst[1] = (G / maxVal) * 255;
dst[2] = (B / maxVal) * 255;
dst[3] = A * 255;
}
}
} else { // TODO: other LDR formats
byte *dst = r_shot->buffer;
for (int y = 0; y < vk_frame.height; ++y, mapped += layout.rowPitch) {
const byte *src = (const byte*)mapped;

View File

@ -278,6 +278,7 @@ static void loadMaterialsFromFile( const char *filename, int depth ) {
} while(0)
LOAD_TEXTURE_FOR(basecolor_map, tex_base_color, kColorspaceNative);
//LOAD_TEXTURE_FOR(basecolor_map, tex_base_color, kColorspaceGamma);
LOAD_TEXTURE_FOR(normal_map, tex_normalmap, kColorspaceLinear);
LOAD_TEXTURE_FOR(metal_map, tex_metalness, kColorspaceLinear);
LOAD_TEXTURE_FOR(roughness_map, tex_roughness, kColorspaceLinear);

View File

@ -2,6 +2,7 @@
#include "vk_buffer.h"
#include "vk_core.h"
#include "vk_cvar.h"
#include "vk_common.h"
#include "vk_textures.h"
#include "vk_framectl.h"
@ -166,6 +167,23 @@ static qboolean createPipelines( void )
{.binding = 0, .location = 2, .format = VK_FORMAT_R8G8B8A8_UNORM, .offset = offsetof(vertex_2d_t, color)},
};
// TODO: ubo
struct ShaderSpec {
int vk_display_dr_mode;
} spec_data = {
.vk_display_dr_mode = (vk_core.hdr_output) ? vk_hdr_output->value : 0,
};
const VkSpecializationMapEntry spec_map[] = {
{.constantID = 0, .offset = offsetof(struct ShaderSpec, vk_display_dr_mode), .size = sizeof(int) },
};
VkSpecializationInfo shader_spec = {
.mapEntryCount = ARRAYSIZE(spec_map),
.pMapEntries = spec_map,
.dataSize = sizeof(struct ShaderSpec),
.pData = &spec_data
};
const vk_shader_stage_t shader_stages[] = {
{
.stage = VK_SHADER_STAGE_VERTEX_BIT,
@ -173,6 +191,7 @@ static qboolean createPipelines( void )
}, {
.stage = VK_SHADER_STAGE_FRAGMENT_BIT,
.filename = "2d.frag.spv",
.specialization_info = &shader_spec,
}};
vk_pipeline_graphics_create_info_t pci = {

View File

@ -209,10 +209,16 @@ static qboolean createPipelines( void )
struct ShaderSpec {
float alpha_test_threshold;
uint32_t max_dlights;
} spec_data = { .25f, MAX_DLIGHTS };
int vk_display_dr_mode; // TODO: ubo
} spec_data = {
.alpha_test_threshold = .25f,
.max_dlights = MAX_DLIGHTS,
.vk_display_dr_mode = (vk_core.hdr_output) ? vk_hdr_output->value : 0,
};
const VkSpecializationMapEntry spec_map[] = {
{.constantID = 0, .offset = offsetof(struct ShaderSpec, alpha_test_threshold), .size = sizeof(float) },
{.constantID = 1, .offset = offsetof(struct ShaderSpec, max_dlights), .size = sizeof(uint32_t) },
{.constantID = 2, .offset = offsetof(struct ShaderSpec, vk_display_dr_mode), .size = sizeof(int) },
};
VkSpecializationInfo shader_spec = {

View File

@ -266,6 +266,8 @@ static void prepareUniformBuffer( const vk_ray_frame_render_args_t *args, int fr
ubo->debug_flags = g_rtx.debug.rt_debug_flags_value;
ubo->random_seed = getRandomSeed();
ubo->vk_display_dr_mode = (vk_core.hdr_output) ? vk_hdr_output->value : 0;
}
typedef struct {

View File

@ -95,8 +95,8 @@ static qboolean recreateSwapchainIfNeeded( qboolean force ) {
.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
.pNext = NULL,
.surface = vk_core.surface.surface,
.imageFormat = g_swapchain.image_format,
.imageColorSpace = VK_COLORSPACE_SRGB_NONLINEAR_KHR, // TODO get from surface_formats
.imageFormat = vk_core.output_surface.format,
.imageColorSpace = vk_core.output_surface.colorSpace,
.imageExtent.width = g_swapchain.width,
.imageExtent.height = g_swapchain.height,
.imageArrayLayers = 1,
@ -190,9 +190,7 @@ qboolean R_VkSwapchainInit( VkRenderPass render_pass, VkFormat depth_format ) {
/*
[2023:10:09|13:03:52] Error: Validation: Validation Error: [ VUID-VkSwapchainCreateInfoKHR-imageFormat-01778 ] Object 0: handle = 0x555556af6a00, type = VK_OBJECT_TYPE_DEVICE; | MessageID = 0xc036022f | vkCreateSwapchainKHR(): pCreateInfo->imageFormat VK_FORMAT_B8G8R8A8_SRGB with tiling VK_IMAGE_TILING_OPTIMAL does not support usage that includes VK_IMAGE_USAGE_STORAGE_BIT. The Vulkan spec states: The implied image creation parameters of the swapchain must be supported as reported by vkGetPhysicalDeviceImageFormatProperties (https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/vkspec.html#VUID-VkSwapchainCreateInfoKHR-imageFormat-01778)
*/
//g_swapchain.image_format = VK_FORMAT_B8G8R8A8_SRGB;
g_swapchain.image_format = VK_FORMAT_B8G8R8A8_UNORM; // TODO get from surface_formats
g_swapchain.image_format = vk_core.output_surface.format;
g_swapchain.render_pass = render_pass;
g_swapchain.depth_format = depth_format;
@ -296,3 +294,8 @@ void R_VkSwapchainPresent( uint32_t index, VkSemaphore done ) {
XVK_CHECK(present_result);
}
}
// FIXME: remove this
void switchSwapchain( void ) {
recreateSwapchainIfNeeded( true );
}

View File

@ -20,3 +20,5 @@ typedef struct {
r_vk_swapchain_framebuffer_t R_VkSwapchainAcquire( VkSemaphore sem_image_available );
void R_VkSwapchainPresent( uint32_t index, VkSemaphore done );
void switchSwapchain( void );