vk: make all texture samplers return values in linear space

Make sure that all SRGB-gamma textures are marked with SRGB format (this
includes all the original textues, 8bit PNG textues). All other
textures (roughness, metallic, normal maps) are linear (UNORM).

Trust KTX2 texture that they store in the correct colorspace.

Then we can just sample textures w/o SRGBtoLINEAR'izing them. Supposedly
this is better because of hw interpolation in the correct colorspace.

This is a bit experimental because Compressonator can't really into
BC7_SRGB, which is sad. And also we need to re-gamma all the textures
for the gamma/srgb-unaware traditional renderer.
This commit is contained in:
Ivan Avdeev 2023-10-06 15:06:44 -04:00
parent 60f83245ee
commit 9b9e89adec
12 changed files with 75 additions and 67 deletions

View File

@ -1,4 +1,5 @@
#version 450
#include "color_spaces.glsl"
layout(set=0,binding=0) uniform sampler2D tex;
@ -8,5 +9,5 @@ layout(location=1) in vec4 vColor;
layout(location = 0) out vec4 outColor;
void main() {
outColor = texture(tex, vUv) * vColor;
outColor = LINEARtoSRGB(texture(tex, vUv)) * vColor;
}

View File

@ -1,4 +1,5 @@
#version 450
#include "color_spaces.glsl"
layout (constant_id = 0) const float alpha_test_threshold = 0.;
layout (constant_id = 1) const uint max_dlights = 1;
@ -29,7 +30,7 @@ const float dlight_attenuation_const = 5000.;
void main() {
outColor = vec4(0.);
const vec4 tex_color = texture(sTexture0, vTexture0);
const vec4 tex_color = LINEARtoSRGB(texture(sTexture0, vTexture0));
// TODO make sure textures are premultiplied alpha
const vec4 baseColor = vColor * tex_color;
@ -37,7 +38,7 @@ void main() {
discard;
outColor.a = baseColor.a;
outColor.rgb += baseColor.rgb * texture(sLightmap, vLightmapUV).rgb;
outColor.rgb += baseColor.rgb * LINEARtoSRGB(texture(sLightmap, vLightmapUV).rgb);
for (uint i = 0; i < ubo.num_lights; ++i) {
const vec4 light_pos_r = ubo.lights[i].pos_r;

View File

@ -107,7 +107,7 @@ void main() {
L = rayQueryGetIntersectionTEXT(rq, true);
} else {
// Draw skybox when nothing is hit
payload.emissive.rgb = SRGBtoLINEAR(texture(skybox, ray.direction).rgb);
payload.emissive.rgb = texture(skybox, ray.direction).rgb;
}
traceSimpleBlending(ray.origin, ray.direction, L, payload.emissive.rgb, payload.base_color_a.rgb);

View File

@ -30,7 +30,7 @@ void main() {
const Kusok kusok = getKusok(geom.kusok_index);
if (kusok.material.tex_base_color == TEX_BASE_SKYBOX) {
payload.emissive.rgb = SRGBtoLINEAR(texture(skybox, gl_WorldRayDirectionEXT).rgb);
payload.emissive.rgb = texture(skybox, gl_WorldRayDirectionEXT).rgb;
return;
} else {
const vec4 color = getModelHeader(gl_InstanceID).color * kusok.material.base_color;
@ -55,7 +55,7 @@ void main() {
#if 1
// Real correct emissive color
//payload.emissive.rgb = kusok.emissive;
payload.emissive.rgb = clamp(kusok.emissive / (1.0/3.0) / 25, 0, 1.5) * SRGBtoLINEAR(payload.base_color_a.rgb);
payload.emissive.rgb = clamp(kusok.emissive / (1.0/3.0) / 25, 0, 1.5) * payload.base_color_a.rgb;
#else
// Fake texture color
if (any(greaterThan(kusok.emissive, vec3(0.))))

View File

@ -31,10 +31,10 @@ void primaryRayHit(rayQueryEXT rq, inout RayPayloadPrimary payload) {
const Material material = kusok.material;
if (kusok.material.tex_base_color == TEX_BASE_SKYBOX) {
payload.emissive.rgb = SRGBtoLINEAR(texture(skybox, rayDirection).rgb);
payload.emissive.rgb = texture(skybox, rayDirection).rgb;
return;
} else {
payload.base_color_a = SRGBtoLINEAR(sampleTexture(material.tex_base_color, geom.uv, geom.uv_lods));
payload.base_color_a = sampleTexture(material.tex_base_color, geom.uv, geom.uv_lods);
payload.material_rmxx.r = sampleTexture(material.tex_roughness, geom.uv, geom.uv_lods).r * material.roughness;
payload.material_rmxx.g = sampleTexture(material.tex_metalness, geom.uv, geom.uv_lods).r * material.metalness;

View File

@ -53,14 +53,7 @@ void traceSimpleBlending(vec3 pos, vec3 dir, float L, inout vec3 emissive, inout
const vec4 texture_color = texture(textures[nonuniformEXT(kusok.material.tex_base_color)], geom.uv);
const vec4 mm_color = model.color * kusok.material.base_color;
float alpha = mm_color.a * texture_color.a * geom.vertex_color.a;
vec3 color = mm_color.rgb * SRGBtoLINEAR(texture_color.rgb) * geom.vertex_color.rgb * alpha;
/* TODO: I think it should be like this instead:
const vec4 texture_color = SRGBtoLINEAR(texture(textures[nonuniformEXT(kusok.material.tex_base_color)], geom.uv));
const vec4 mm_color = SRGBtoLINEAR(model.color) * SRGBtoLINEAR(kusok.material.base_color);
float alpha = mm_color.a * texture_color.a * geom.vertex_color.a;
vec3 color = mm_color.rgb * texture_color.rgb * geom.vertex_color.rgb * alpha;
*/
if (model.mode == MATERIAL_MODE_BLEND_GLOW) {
// Glow is additive + small overshoot

View File

@ -75,9 +75,9 @@ static struct {
uint64_t texture_load_duration_ns;
} g_stats;
static int loadTexture( const char *filename, qboolean force_reload ) {
static int loadTexture( const char *filename, qboolean force_reload, colorspace_hint_e colorspace ) {
const uint64_t load_begin_ns = aprof_time_now_ns();
const int tex_id = force_reload ? XVK_LoadTextureReplace( filename, NULL, 0, 0 ) : VK_LoadTexture( filename, NULL, 0, 0 );
const int tex_id = R_VkLoadTexture( filename, colorspace, force_reload);
DEBUG("Loaded texture %s => %d", filename, tex_id);
g_stats.texture_loads++;
g_stats.texture_load_duration_ns += aprof_time_now_ns() - load_begin_ns;
@ -206,11 +206,11 @@ static void loadMaterialsFromFile( const char *filename, int depth ) {
continue;
}
#define LOAD_TEXTURE_FOR(name, field) do { \
#define LOAD_TEXTURE_FOR(name, field, colorspace) do { \
if (name[0] != '\0') { \
char texture_path[256]; \
MAKE_PATH(texture_path, name); \
const int tex_id = loadTexture(texture_path, force_reload); \
const int tex_id = loadTexture(texture_path, force_reload, colorspace); \
if (tex_id < 0) { \
ERR("Failed to load texture \"%s\" for "#name"", name); \
} else { \
@ -218,10 +218,10 @@ static void loadMaterialsFromFile( const char *filename, int depth ) {
} \
}} while(0)
LOAD_TEXTURE_FOR(basecolor_map, tex_base_color);
LOAD_TEXTURE_FOR(normal_map, tex_normalmap);
LOAD_TEXTURE_FOR(metal_map, tex_metalness);
LOAD_TEXTURE_FOR(roughness_map, tex_roughness);
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);
// If there's no explicit basecolor_map value, use the "for" target texture
if (current_material.tex_base_color == -1)

View File

@ -543,7 +543,7 @@ static const ref_interface_t gReffuncs =
VK_FindTexture,
VK_TextureName,
VK_TextureData,
VK_LoadTexture,
VK_LoadTextureExternal,
VK_CreateTexture,
VK_LoadTextureArray,
VK_CreateTextureArray,

View File

@ -242,12 +242,12 @@ static const dframetype_t *VK_SpriteLoadFrame( model_t *mod, const void *pin, ms
if( FBitSet( mod->flags, MODEL_CLIENT )) // it's a HUD sprite
{
Q_snprintf( texname, sizeof( texname ), "#HUD/%s(%s:%i%i).spr", ctx->sprite_name, ctx->group_suffix, num / 10, num % 10 );
gl_texturenum = VK_LoadTexture( texname, pin, pinframe.width * pinframe.height * bytes, ctx->r_texFlags );
gl_texturenum = VK_LoadTextureExternal( texname, pin, pinframe.width * pinframe.height * bytes, ctx->r_texFlags );
}
else
{
Q_snprintf( texname, sizeof( texname ), "#%s(%s:%i%i).spr", ctx->sprite_name, ctx->group_suffix, num / 10, num % 10 );
gl_texturenum = VK_LoadTexture( texname, pin, pinframe.width * pinframe.height * bytes, ctx->r_texFlags );
gl_texturenum = VK_LoadTextureExternal( texname, pin, pinframe.width * pinframe.height * bytes, ctx->r_texFlags );
}
// setup frame description

View File

@ -3337,7 +3337,7 @@ static void R_StudioLoadTexture( model_t *mod, studiohdr_t *phdr, mstudiotexture
// build the texname
Q_snprintf( texname, sizeof( texname ), "#%s/%s.mdl", mdlname, name );
ptexture->index = VK_LoadTexture( texname, (byte *)ptexture, size, flags );
ptexture->index = VK_LoadTextureExternal( texname, (byte *)ptexture, size, flags );
if( !ptexture->index )
{

View File

@ -274,7 +274,7 @@ static void VK_ProcessImage( vk_texture_t *tex, rgbdata_t *pic )
}
}
static qboolean uploadTexture(vk_texture_t *tex, rgbdata_t *const *const layers, int num_layers, qboolean cubemap);
static qboolean uploadTexture(vk_texture_t *tex, rgbdata_t *const *const layers, int num_layers, qboolean cubemap, colorspace_hint_e colorspace_hint);
static void VK_CreateInternalTextures( void )
{
@ -350,14 +350,17 @@ static void VK_CreateInternalTextures( void )
sides[4] = pic;
sides[5] = pic;
uploadTexture( &tglob.cubemap_placeholder, sides, 6, true );
uploadTexture( &tglob.cubemap_placeholder, sides, 6, true, kColorspaceGamma );
}
}
static VkFormat VK_GetFormat(pixformat_t format) {
static VkFormat VK_GetFormat(pixformat_t format, colorspace_hint_e colorspace_hint ) {
switch(format)
{
case PF_RGBA_32: return VK_FORMAT_R8G8B8A8_UNORM;
case PF_RGBA_32:
return (colorspace_hint == kColorspaceLinear)
? VK_FORMAT_R8G8B8A8_UNORM
: VK_FORMAT_R8G8B8A8_SRGB;
default:
WARN("FIXME unsupported pixformat_t %d", format);
return VK_FORMAT_UNDEFINED;
@ -550,8 +553,8 @@ static VkSampler pickSamplerForFlags( texFlags_t flags ) {
return tglob.default_sampler_fixme;
}
static qboolean uploadTexture(vk_texture_t *tex, rgbdata_t *const *const layers, int num_layers, qboolean cubemap) {
const VkFormat format = VK_GetFormat(layers[0]->type);
static qboolean uploadTexture(vk_texture_t *tex, rgbdata_t *const *const layers, int num_layers, qboolean cubemap, colorspace_hint_e colorspace_hint) {
const VkFormat format = VK_GetFormat(layers[0]->type, colorspace_hint);
int mipCount = 0;
tex->total_size = 0;
@ -1044,29 +1047,7 @@ finalize:
return (tex - vk_textures);
}
static int loadTextureUsingEngine( const char *name, const byte *buf, size_t size, int flags );
int VK_LoadTexture( const char *name, const byte *buf, size_t size, int flags )
{
if( !Common_CheckTexName( name ))
return 0;
// see if already loaded
vk_texture_t *tex = Common_TextureForName( name );
if( tex )
return (tex - vk_textures);
{
const char *ext = Q_strrchr(name, '.');
if (Q_strcmp(ext, ".ktx2") == 0) {
return loadKtx2(name);
}
}
return loadTextureUsingEngine(name, buf, size, flags);
}
static int loadTextureUsingEngine( const char *name, const byte *buf, size_t size, int flags ) {
static int loadTextureUsingEngine( const char *name, const byte *buf, size_t size, int flags, colorspace_hint_e colorspace_hint ) {
uint picFlags = 0;
if( FBitSet( flags, TF_NOFLIP_TGA ))
@ -1087,7 +1068,7 @@ static int loadTextureUsingEngine( const char *name, const byte *buf, size_t siz
// upload texture
VK_ProcessImage( tex, pic );
if( !uploadTexture( tex, &pic, 1, false ))
if( !uploadTexture( tex, &pic, 1, false, colorspace_hint ))
{
memset( tex, 0, sizeof( vk_texture_t ));
gEngine.FS_FreeImage( pic ); // release source texture
@ -1103,17 +1084,43 @@ static int loadTextureUsingEngine( const char *name, const byte *buf, size_t siz
return tex - vk_textures;
}
int XVK_LoadTextureReplace( const char *name, const byte *buf, size_t size, int flags ) {
vk_texture_t *tex;
static int loadTextureInternal( const char *name, const byte *buf, size_t size, int flags, colorspace_hint_e colorspace_hint ) {
if( !Common_CheckTexName( name ))
return 0;
// free if already loaded
if(( tex = Common_TextureForName( name ))) {
VK_FreeTexture( tex - vk_textures );
// see if already loaded
vk_texture_t *tex = Common_TextureForName( name );
if( tex )
return (tex - vk_textures);
{
const char *ext = Q_strrchr(name, '.');
if (Q_strcmp(ext, ".ktx2") == 0) {
return loadKtx2(name);
}
}
return VK_LoadTexture( name, buf, size, flags );
return loadTextureUsingEngine(name, buf, size, flags, colorspace_hint);
}
int VK_LoadTextureExternal( const char *name, const byte *buf, size_t size, int flags ) {
return loadTextureInternal(name, buf, size, flags, kColorspaceGamma);
}
int R_VkLoadTexture( const char *filename, colorspace_hint_e colorspace, qboolean force_reload) {
vk_texture_t *tex;
if( !Common_CheckTexName( filename ))
return 0;
if (force_reload) {
// free if already loaded
// TODO consider leaving intact if loading failed
if(( tex = Common_TextureForName( filename ))) {
VK_FreeTexture( tex - vk_textures );
}
}
return loadTextureInternal( filename, NULL, 0, 0, colorspace );
}
int VK_CreateTexture( const char *name, int width, int height, const void *buffer, texFlags_t flags )
@ -1222,7 +1229,7 @@ static int loadTextureFromBuffers( const char *name, rgbdata_t *const *const pic
for (int i = 0; i < pic_count; ++i)
VK_ProcessImage( tex, pic[i] );
if( !uploadTexture( tex, pic, pic_count, false ))
if( !uploadTexture( tex, pic, pic_count, false, kColorspaceGamma ))
{
memset( tex, 0, sizeof( vk_texture_t ));
return 0;
@ -1348,7 +1355,7 @@ static qboolean loadSkybox( const char *prefix, int style ) {
goto cleanup;
Q_strncpy( tglob.skybox_cube.name, prefix, sizeof( tglob.skybox_cube.name ));
success = uploadTexture(&tglob.skybox_cube, sides, 6, true);
success = uploadTexture(&tglob.skybox_cube, sides, 6, true, kColorspaceGamma);
cleanup:
for (int j = 0; j < i; ++j)

View File

@ -67,18 +67,24 @@ void initTextures( void );
void destroyTextures( void );
vk_texture_t *findTexture(int index);
typedef enum {
//kColorspaceNative,
kColorspaceLinear,
kColorspaceGamma,
} colorspace_hint_e;
// Public API functions
int VK_FindTexture( const char *name );
const char* VK_TextureName( unsigned int texnum );
const byte* VK_TextureData( unsigned int texnum );
int VK_LoadTexture( const char *name, const byte *buf, size_t size, int flags );
int VK_LoadTextureExternal( const char *name, const byte *buf, size_t size, int flags );
int VK_CreateTexture( const char *name, int width, int height, const void *buffer, texFlags_t flags );
int VK_LoadTextureArray( const char **names, int flags );
int VK_CreateTextureArray( const char *name, int width, int height, int depth, const void *buffer, texFlags_t flags );
void VK_FreeTexture( unsigned int texnum );
int VK_LoadTextureFromBuffer( const char *name, rgbdata_t *pic, texFlags_t flags, qboolean update );
int XVK_LoadTextureReplace( const char *name, const byte *buf, size_t size, int flags );
int R_VkLoadTexture( const char *filename, colorspace_hint_e colorspace, qboolean force_reload);
int XVK_TextureLookupF( const char *fmt, ...);