Merge pull request #597 from w23/srgb-gamma

sRGB-γ all the things

- [x] Makes all textures SRGB by default
- [x] For traditional renderer: convert back to SRGB space like it's 1998 again
  - [x] Make different `VkImageView`s, for linear (trad) and SRGB (rt) to avoid extra conversion
    - see https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#formats-compatibility-classes
supported surface formats, and also whether the image can be used as a storage destination in compute shader.
- [x] Improve `view_unorm` handling:
  - [x] Don't create a separate view for natively `UNORM`
  - [x] Create fallback 
- [x] Use "native" hint for pbr base_color
- [x] Remove `test_val` from shaders
- [x] Do model and material colors srgb-linear conversion in C
- [x] Investigate lighting brightness
  - Nihrena neponyatno. 

Problems caused by this amazing change:
- Undesired interpolation artifacts: #599 (see discussion below).
- Dark places are too dark (caused by more correct linear-to-sRGB conversion that differs from the fast one for smaller values).
- Lighting looks a bit different (probably has the same cause as above)
This commit is contained in:
Ivan Avdeev 2023-10-10 09:47:25 -07:00 committed by GitHub
commit 82dea7a86b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 375 additions and 156 deletions

View File

@ -88,13 +88,14 @@ bool getHit(vec3 origin, vec3 direction, inout RayPayloadPrimary payload) {
//L = rayQueryGetIntersectionTEXT(rq, true);
return true;
}
const int INDIRECT_SCALE = 2;
void computeBounce(ivec2 pix, vec3 direction, out vec3 diffuse, out vec3 specular) {
diffuse = vec3(0.);
specular = vec3(0.);
const vec4 material_data = imageLoad(material_rmxx, pix);
const vec4 base_a = imageLoad(base_color_a, pix);
const vec4 base_a = SRGBtoLINEAR(imageLoad(base_color_a, pix));
MaterialProperties material;
material.baseColor = vec3(1.f);
@ -180,7 +181,6 @@ void computeBounce(ivec2 pix, vec3 direction, out vec3 diffuse, out vec3 specula
}
}
const int INDIRECT_SCALE = 2;
void main() {
const ivec2 pix = ivec2(gl_GlobalInvocationID);

View File

@ -30,6 +30,7 @@ const float dlight_attenuation_const = 5000.;
void main() {
outColor = vec4(0.);
const vec4 tex_color = texture(sTexture0, vTexture0);
// TODO make sure textures are premultiplied alpha
const vec4 baseColor = vColor * tex_color;
@ -48,6 +49,4 @@ void main() {
const float attenuation = dlight_attenuation_const / (d2 + r2 * .5);
outColor.rgb += baseColor.rgb * light_color * max(0., dot(normalize(light_dir), vNormal)) * attenuation;
}
//outColor.rgb = vNormal * .5 + .5;
}

View File

@ -1,5 +1,5 @@
#define SRGB_FAST_APPROXIMATION
#ifndef RT_COLOR_SPACES_GLSL_INCLUDED
#define RT_COLOR_SPACES_GLSL_INCLUDED
#ifdef SRGB_FAST_APPROXIMATION
#define LINEARtoSRGB OECF_sRGBFast
#define SRGBtoLINEAR sRGB_OECFFast
@ -63,3 +63,5 @@ vec3 OECF_sRGBFast(const vec3 linear) {
vec4 OECF_sRGBFast(const vec4 linear) {
return vec4(pow(linear.rgb, vec3(1.0 / 2.2)), linear.w);
}
#endif //ifndef RT_COLOR_SPACES_GLSL_INCLUDED

View File

@ -36,11 +36,13 @@ layout(set = 0, binding = 15, rgba16f) uniform image2D prev_temporal_diffuse;
layout(set = 0, binding = 16, rgba16f) uniform image2D out_temporal_specular;
layout(set = 0, binding = 17, rgba16f) uniform image2D prev_temporal_specular;
//layout(set = 0, binding = 19) uniform sampler2D textures[MAX_TEXTURES];
const int INDIRECT_SCALE = 2;
//#define DEBUG_TEXTURE normals_gs
//#define DEBUG_TEXTURE emissive
//#define DEBUG_TEXTURE base_color_a
//#define DEBUG_TEXTURE light_point_diffuse
//#define DEBUG_NORMAL
//layout(set = 0, binding = 18, rgba8) uniform readonly image2D material_rmxx;
@ -194,9 +196,31 @@ void main() {
/* } */
#if defined(DEBUG_TEXTURE)
//if (pix.x < res.x / 2) {
//imageStore(out_dest, pix, vec4(LINEARtoSRGB(texture(textures[161], vec2(pix)/vec2(res)).rgb), 0.)); return;
//}
imageStore(out_dest, pix, vec4(LINEARtoSRGB(imageLoad(DEBUG_TEXTURE, pix).rgb), 0.)); return;
#endif
/*
if (pix.x < res.x / 4) {
imageStore(out_dest, pix, vec4(imageLoad(base_color_a, pix).rgb, 0.)); return;
} else if (pix.x < res.x / 2) {
imageStore(out_dest, pix, vec4(LINEARtoSRGB(imageLoad(emissive, pix).rgb), 0.)); return;
} else if (pix.x < res.x * 3 / 4) {
imageStore(out_dest, pix, vec4(LINEARtoSRGB(fract(
imageLoad(light_poly_diffuse, pix).rgb
+ imageLoad(light_poly_specular, pix).rgb
+ imageLoad(light_point_diffuse, pix).rgb
+ imageLoad(light_point_specular, pix).rgb
)), 0.)); return;
} else {
imageStore(out_dest, pix, vec4(LINEARtoSRGB(
imageLoad(indirect_diffuse, pix / INDIRECT_SCALE).rgb
+ imageLoad(indirect_specular, pix / INDIRECT_SCALE).rgb
), 0.)); return;
}*/
//imageStore(out_dest, pix, vec4(fract(imageLoad(position_t, pix).rgb/10.), 0.)); return;
//imageStore(out_dest, pix, vec4(fract(imageLoad(geometry_prev_position, pix).rgb/50.), 0.)); return;
@ -270,7 +294,7 @@ void main() {
//imageStore(out_dest, pix, vec4(LINEARtoSRGB(diffuse), 0.)); return;
}
const vec4 base_color_a = imageLoad(base_color_a, pix);
const vec4 base_color_a = SRGBtoLINEAR(imageLoad(base_color_a, pix));
colour *= base_color_a.rgb;
colour += imageLoad(emissive, pix).rgb;
colour = LINEARtoSRGB(colour);

View File

@ -107,13 +107,13 @@ 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);
imageStore(out_position_t, pix, payload.hit_t);
imageStore(out_base_color_a, pix, payload.base_color_a);
imageStore(out_base_color_a, pix, LINEARtoSRGB(payload.base_color_a));
imageStore(out_normals_gs, pix, payload.normals_gs);
//imageStore(out_material_rmxx, pix, vec4(payload.material_rmxx.rg, 0, uintToFloat01(xxhash32(debug_geometry_index))));
imageStore(out_material_rmxx, pix, payload.material_rmxx);

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;
@ -99,7 +99,7 @@ void primaryRayHit(rayQueryEXT rq, inout RayPayloadPrimary payload) {
const int model_index = rayQueryGetIntersectionInstanceIdEXT(rq, true);
const ModelHeader model = getModelHeader(model_index);
const vec4 color = model.color * SRGBtoLINEAR(kusok.material.base_color); // FIXME why is material.base_color in gamma space?
const vec4 color = model.color * kusok.material.base_color;
payload.base_color_a *= color;
payload.emissive.rgb *= color.rgb;

View File

@ -1,6 +1,7 @@
#ifndef RT_GEOMETRY_GLSL_INCLUDED
#define RT_GEOMETRY_GLSL_INCLUDED
#include "utils.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,
@ -156,9 +157,9 @@ MiniGeometry readCandidateMiniGeometry(rayQueryEXT rq) {
const vec2 uv = baryMix(uvs[0], uvs[1], uvs[2], bary);
const vec4 colors[3] = {
unpackUnorm4x8(GET_VERTEX(vi1).color),
unpackUnorm4x8(GET_VERTEX(vi2).color),
unpackUnorm4x8(GET_VERTEX(vi3).color),
SRGBtoLINEAR(unpackUnorm4x8(GET_VERTEX(vi1).color)),
SRGBtoLINEAR(unpackUnorm4x8(GET_VERTEX(vi2).color)),
SRGBtoLINEAR(unpackUnorm4x8(GET_VERTEX(vi3).color)),
};
MiniGeometry ret;

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

@ -12,7 +12,8 @@ typedef struct descriptor_pool_s
int next_free;
//uint32_t *free_set;
VkDescriptorSet sets[MAX_TEXTURES];
// * 2 because of unorm views for trad renderer
VkDescriptorSet sets[MAX_TEXTURES * 2];
VkDescriptorSetLayout one_texture_layout;
// FIXME HOW THE F
@ -31,7 +32,7 @@ typedef union {
VkDescriptorImageInfo image;
VkDescriptorImageInfo *image_array;
VkWriteDescriptorSetAccelerationStructureKHR accel;
const struct xvk_image_s *image_object;
const struct r_vk_image_s *image_object;
} vk_descriptor_value_t;
typedef struct {

View File

@ -259,6 +259,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.}}},
{.depthStencil = {1., 0.}} // TODO reverse-z
@ -307,10 +308,12 @@ static void enqueueRendering( vk_combuf_t* combuf, qboolean draw ) {
vkCmdEndRenderPass(cmdbuf);
g_frame.current.phase = Phase_RenderingEnqueued;
APROF_SCOPE_END(enqueue);
}
// FIXME pass frame, not combuf (possible desync)
static void submit( vk_combuf_t* combuf, qboolean wait, qboolean draw ) {
APROF_SCOPE_DECLARE_BEGIN(submit, __FUNCTION__);
ASSERT(g_frame.current.phase == Phase_RenderingEnqueued);
const VkCommandBuffer cmdbuf = combuf->cmdbuf;
@ -390,6 +393,8 @@ static void submit( vk_combuf_t* combuf, qboolean wait, qboolean draw ) {
/* } */
g_frame.current.phase = Phase_Idle;
}
APROF_SCOPE_END(submit);
}
inline static VkCommandBuffer currentCommandBuffer( void ) {
@ -510,7 +515,7 @@ static qboolean canBlitFromSwapchainToFormat( VkFormat dest_format ) {
static rgbdata_t *XVK_ReadPixels( void ) {
const VkFormat dest_format = VK_FORMAT_R8G8B8A8_UNORM;
xvk_image_t dest_image;
r_vk_image_t dest_image;
const VkImage frame_image = g_frame.current.framebuffer.image;
rgbdata_t *r_shot = NULL;
qboolean blit = canBlitFromSwapchainToFormat( dest_format );
@ -525,7 +530,7 @@ static rgbdata_t *XVK_ReadPixels( void ) {
// Create destination image to blit/copy framebuffer pixels to
{
const xvk_image_create_t xic = {
const r_vk_image_create_t xic = {
.debug_name = "screenshot",
.width = vk_frame.width,
.height = vk_frame.height,
@ -534,11 +539,10 @@ static rgbdata_t *XVK_ReadPixels( void ) {
.format = dest_format,
.tiling = VK_IMAGE_TILING_LINEAR,
.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT,
.has_alpha = false,
.is_cubemap = false,
.flags = 0,
.memory_props = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT,
};
dest_image = XVK_ImageCreate(&xic);
dest_image = R_VkImageCreate(&xic);
}
// Make sure that all rendering ops are enqueued
@ -700,7 +704,7 @@ static rgbdata_t *XVK_ReadPixels( void ) {
}
}
XVK_ImageDestroy( &dest_image );
R_VkImageDestroy( &dest_image );
return r_shot;
}

View File

@ -1,4 +1,5 @@
#include "vk_image.h"
#include "vk_logs.h"
static const VkImageUsageFlags usage_bits_implying_views =
VK_IMAGE_USAGE_SAMPLED_BIT |
@ -16,11 +17,92 @@ static const VkImageUsageFlags usage_bits_implying_views =
VK_IMAGE_USAGE_VIDEO_ENCODE_DPB_BIT_KHR;
*/
xvk_image_t XVK_ImageCreate(const xvk_image_create_t *create) {
static VkFormat unormFormatFor(VkFormat fmt) {
switch (fmt) {
case VK_FORMAT_R8_SRGB: return VK_FORMAT_R8_UNORM;
case VK_FORMAT_R8_UNORM: return VK_FORMAT_R8_UNORM;
case VK_FORMAT_R8G8_SRGB: return VK_FORMAT_R8G8_UNORM;
case VK_FORMAT_R8G8_UNORM: return VK_FORMAT_R8G8_UNORM;
case VK_FORMAT_R8G8B8_SRGB: return VK_FORMAT_R8G8B8_UNORM;
case VK_FORMAT_R8G8B8_UNORM: return VK_FORMAT_R8G8B8_UNORM;
case VK_FORMAT_B8G8R8_SRGB: return VK_FORMAT_B8G8R8_UNORM;
case VK_FORMAT_B8G8R8_UNORM: return VK_FORMAT_B8G8R8_UNORM;
case VK_FORMAT_R8G8B8A8_SRGB: return VK_FORMAT_R8G8B8A8_UNORM;
case VK_FORMAT_R8G8B8A8_UNORM: return VK_FORMAT_R8G8B8A8_UNORM;
case VK_FORMAT_B8G8R8A8_SRGB: return VK_FORMAT_B8G8R8A8_UNORM;
case VK_FORMAT_B8G8R8A8_UNORM: return VK_FORMAT_B8G8R8A8_UNORM;
case VK_FORMAT_A8B8G8R8_SRGB_PACK32: return VK_FORMAT_A8B8G8R8_UNORM_PACK32;
case VK_FORMAT_A8B8G8R8_UNORM_PACK32: return VK_FORMAT_A8B8G8R8_UNORM_PACK32;
case VK_FORMAT_BC1_RGB_SRGB_BLOCK: return VK_FORMAT_BC1_RGB_UNORM_BLOCK;
case VK_FORMAT_BC1_RGB_UNORM_BLOCK: return VK_FORMAT_BC1_RGB_UNORM_BLOCK;
case VK_FORMAT_BC1_RGBA_SRGB_BLOCK: return VK_FORMAT_BC1_RGBA_UNORM_BLOCK;
case VK_FORMAT_BC1_RGBA_UNORM_BLOCK: return VK_FORMAT_BC1_RGBA_UNORM_BLOCK;
case VK_FORMAT_BC2_SRGB_BLOCK: return VK_FORMAT_BC2_UNORM_BLOCK;
case VK_FORMAT_BC2_UNORM_BLOCK: return VK_FORMAT_BC2_UNORM_BLOCK;
case VK_FORMAT_BC3_SRGB_BLOCK: return VK_FORMAT_BC3_UNORM_BLOCK;
case VK_FORMAT_BC3_UNORM_BLOCK: return VK_FORMAT_BC3_UNORM_BLOCK;
case VK_FORMAT_BC7_SRGB_BLOCK: return VK_FORMAT_BC7_UNORM_BLOCK;
case VK_FORMAT_BC7_UNORM_BLOCK: return VK_FORMAT_BC7_UNORM_BLOCK;
case VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK: return VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK;
case VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK: return VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK;
case VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK: return VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK;
case VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK: return VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK;
case VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK: return VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK;
case VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK: return VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK;
case VK_FORMAT_ASTC_4x4_SRGB_BLOCK: return VK_FORMAT_ASTC_4x4_UNORM_BLOCK;
case VK_FORMAT_ASTC_4x4_UNORM_BLOCK: return VK_FORMAT_ASTC_4x4_UNORM_BLOCK;
case VK_FORMAT_ASTC_5x4_SRGB_BLOCK: return VK_FORMAT_ASTC_5x4_UNORM_BLOCK;
case VK_FORMAT_ASTC_5x4_UNORM_BLOCK: return VK_FORMAT_ASTC_5x4_UNORM_BLOCK;
case VK_FORMAT_ASTC_5x5_SRGB_BLOCK: return VK_FORMAT_ASTC_5x5_UNORM_BLOCK;
case VK_FORMAT_ASTC_5x5_UNORM_BLOCK: return VK_FORMAT_ASTC_5x5_UNORM_BLOCK;
case VK_FORMAT_ASTC_6x5_SRGB_BLOCK: return VK_FORMAT_ASTC_6x5_UNORM_BLOCK;
case VK_FORMAT_ASTC_6x5_UNORM_BLOCK: return VK_FORMAT_ASTC_6x5_UNORM_BLOCK;
case VK_FORMAT_ASTC_6x6_SRGB_BLOCK: return VK_FORMAT_ASTC_6x6_UNORM_BLOCK;
case VK_FORMAT_ASTC_6x6_UNORM_BLOCK: return VK_FORMAT_ASTC_6x6_UNORM_BLOCK;
case VK_FORMAT_ASTC_8x5_SRGB_BLOCK: return VK_FORMAT_ASTC_8x5_UNORM_BLOCK;
case VK_FORMAT_ASTC_8x5_UNORM_BLOCK: return VK_FORMAT_ASTC_8x5_UNORM_BLOCK;
case VK_FORMAT_ASTC_8x6_SRGB_BLOCK: return VK_FORMAT_ASTC_8x6_UNORM_BLOCK;
case VK_FORMAT_ASTC_8x6_UNORM_BLOCK: return VK_FORMAT_ASTC_8x6_UNORM_BLOCK;
case VK_FORMAT_ASTC_8x8_SRGB_BLOCK: return VK_FORMAT_ASTC_8x8_UNORM_BLOCK;
case VK_FORMAT_ASTC_8x8_UNORM_BLOCK: return VK_FORMAT_ASTC_8x8_UNORM_BLOCK;
case VK_FORMAT_ASTC_10x5_SRGB_BLOCK: return VK_FORMAT_ASTC_10x5_UNORM_BLOCK;
case VK_FORMAT_ASTC_10x5_UNORM_BLOCK: return VK_FORMAT_ASTC_10x5_UNORM_BLOCK;
case VK_FORMAT_ASTC_10x6_SRGB_BLOCK: return VK_FORMAT_ASTC_10x6_UNORM_BLOCK;
case VK_FORMAT_ASTC_10x6_UNORM_BLOCK: return VK_FORMAT_ASTC_10x6_UNORM_BLOCK;
case VK_FORMAT_ASTC_10x8_SRGB_BLOCK: return VK_FORMAT_ASTC_10x8_UNORM_BLOCK;
case VK_FORMAT_ASTC_10x8_UNORM_BLOCK: return VK_FORMAT_ASTC_10x8_UNORM_BLOCK;
case VK_FORMAT_ASTC_10x10_SRGB_BLOCK: return VK_FORMAT_ASTC_10x10_UNORM_BLOCK;
case VK_FORMAT_ASTC_10x10_UNORM_BLOCK: return VK_FORMAT_ASTC_10x10_UNORM_BLOCK;
case VK_FORMAT_ASTC_12x10_SRGB_BLOCK: return VK_FORMAT_ASTC_12x10_UNORM_BLOCK;
case VK_FORMAT_ASTC_12x10_UNORM_BLOCK: return VK_FORMAT_ASTC_12x10_UNORM_BLOCK;
case VK_FORMAT_ASTC_12x12_SRGB_BLOCK: return VK_FORMAT_ASTC_12x12_UNORM_BLOCK;
case VK_FORMAT_ASTC_12x12_UNORM_BLOCK: return VK_FORMAT_ASTC_12x12_UNORM_BLOCK;
case VK_FORMAT_PVRTC1_2BPP_SRGB_BLOCK_IMG: return VK_FORMAT_PVRTC1_2BPP_UNORM_BLOCK_IMG;
case VK_FORMAT_PVRTC1_2BPP_UNORM_BLOCK_IMG: return VK_FORMAT_PVRTC1_2BPP_UNORM_BLOCK_IMG;
case VK_FORMAT_PVRTC1_4BPP_SRGB_BLOCK_IMG: return VK_FORMAT_PVRTC1_4BPP_UNORM_BLOCK_IMG;
case VK_FORMAT_PVRTC1_4BPP_UNORM_BLOCK_IMG: return VK_FORMAT_PVRTC1_4BPP_UNORM_BLOCK_IMG;
case VK_FORMAT_PVRTC2_2BPP_SRGB_BLOCK_IMG: return VK_FORMAT_PVRTC2_2BPP_UNORM_BLOCK_IMG;
case VK_FORMAT_PVRTC2_2BPP_UNORM_BLOCK_IMG: return VK_FORMAT_PVRTC2_2BPP_UNORM_BLOCK_IMG;
case VK_FORMAT_PVRTC2_4BPP_SRGB_BLOCK_IMG: return VK_FORMAT_PVRTC2_4BPP_UNORM_BLOCK_IMG;
case VK_FORMAT_PVRTC2_4BPP_UNORM_BLOCK_IMG: return VK_FORMAT_PVRTC2_4BPP_UNORM_BLOCK_IMG;
default:
return VK_FORMAT_UNDEFINED;
}
}
r_vk_image_t R_VkImageCreate(const r_vk_image_create_t *create) {
const qboolean is_depth = !!(create->usage & VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT);
xvk_image_t image = {0};
r_vk_image_t image = {0};
VkMemoryRequirements memreq;
const qboolean is_cubemap = !!(create->flags & kVkImageFlagIsCubemap);
const VkFormat unorm_format = unormFormatFor(create->format);
const qboolean create_unorm =
!!(create->flags & kVkImageFlagCreateUnormView)
&& unorm_format != VK_FORMAT_UNDEFINED
&& unorm_format != create->format;
VkImageCreateInfo ici = {
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
.imageType = VK_IMAGE_TYPE_2D,
@ -35,11 +117,15 @@ xvk_image_t XVK_ImageCreate(const xvk_image_create_t *create) {
.usage = create->usage,
.samples = VK_SAMPLE_COUNT_1_BIT,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
.flags = create->is_cubemap ? VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT : 0,
.flags = 0
| (is_cubemap ? VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT : 0)
| (create_unorm ? VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT : 0),
};
XVK_CHECK(vkCreateImage(vk_core.device, &ici, NULL, &image.image));
image.format = ici.format;
if (create->debug_name)
SET_DEBUG_NAME(image.image, VK_OBJECT_TYPE_IMAGE, create->debug_name);
@ -48,9 +134,11 @@ xvk_image_t XVK_ImageCreate(const xvk_image_create_t *create) {
XVK_CHECK(vkBindImageMemory(vk_core.device, image.image, image.devmem.device_memory, image.devmem.offset));
if (create->usage & usage_bits_implying_views) {
const VkImageViewCreateInfo ivci = {
const qboolean has_alpha = !!(create->flags & kVkImageFlagHasAlpha);
VkImageViewCreateInfo ivci = {
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
.viewType = create->is_cubemap ? VK_IMAGE_VIEW_TYPE_CUBE : VK_IMAGE_VIEW_TYPE_2D,
.viewType = is_cubemap ? VK_IMAGE_VIEW_TYPE_CUBE : VK_IMAGE_VIEW_TYPE_2D,
.format = ici.format,
.image = image.image,
.subresourceRange.aspectMask = is_depth ? VK_IMAGE_ASPECT_DEPTH_BIT : VK_IMAGE_ASPECT_COLOR_BIT,
@ -58,12 +146,21 @@ xvk_image_t XVK_ImageCreate(const xvk_image_create_t *create) {
.subresourceRange.levelCount = ici.mipLevels,
.subresourceRange.baseArrayLayer = 0,
.subresourceRange.layerCount = ici.arrayLayers,
.components = (VkComponentMapping){0, 0, 0, (is_depth || create->has_alpha) ? 0 : VK_COMPONENT_SWIZZLE_ONE},
// TODO component swizzling based on format, e.g. R8 -> RRRR
.components = (VkComponentMapping){0, 0, 0, (is_depth || has_alpha) ? 0 : VK_COMPONENT_SWIZZLE_ONE},
};
XVK_CHECK(vkCreateImageView(vk_core.device, &ivci, NULL, &image.view));
if (create->debug_name)
SET_DEBUG_NAME(image.view, VK_OBJECT_TYPE_IMAGE_VIEW, create->debug_name);
if (create_unorm) {
ivci.format = unorm_format;
XVK_CHECK(vkCreateImageView(vk_core.device, &ivci, NULL, &image.view_unorm));
if (create->debug_name)
SET_DEBUG_NAMEF(image.view_unorm, VK_OBJECT_TYPE_IMAGE_VIEW, "%s_unorm", create->debug_name);
}
}
image.width = create->width;
@ -73,11 +170,17 @@ xvk_image_t XVK_ImageCreate(const xvk_image_create_t *create) {
return image;
}
void XVK_ImageDestroy(xvk_image_t *img) {
vkDestroyImageView(vk_core.device, img->view, NULL);
void R_VkImageDestroy(r_vk_image_t *img) {
if (img->view_unorm != VK_NULL_HANDLE)
vkDestroyImageView(vk_core.device, img->view_unorm, NULL);
if (img->view != VK_NULL_HANDLE)
vkDestroyImageView(vk_core.device, img->view, NULL);
vkDestroyImage(vk_core.device, img->image, NULL);
VK_DevMemFree(&img->devmem);
*img = (xvk_image_t){0};
*img = (r_vk_image_t){0};
}
void R_VkImageClear(VkCommandBuffer cmdbuf, VkImage image) {

View File

@ -2,14 +2,25 @@
#include "vk_core.h"
#include "vk_devmem.h"
typedef struct xvk_image_s {
typedef struct r_vk_image_s {
vk_devmem_t devmem;
VkImage image;
VkImageView view;
// Optional, created by kVkImageFlagCreateUnormView
// Used for sRGB-γ-unaware traditional renderer
VkImageView view_unorm;
uint32_t width, height;
int mips;
} xvk_image_t;
VkFormat format;
} r_vk_image_t;
enum {
kVkImageFlagHasAlpha = (1<<0),
kVkImageFlagIsCubemap = (1<<1),
kVkImageFlagCreateUnormView = (1<<2),
};
typedef struct {
const char *debug_name;
@ -18,13 +29,12 @@ typedef struct {
VkFormat format;
VkImageTiling tiling;
VkImageUsageFlags usage;
qboolean has_alpha;
qboolean is_cubemap;
VkMemoryPropertyFlags memory_props;
} xvk_image_create_t;
uint32_t flags;
} r_vk_image_create_t;
xvk_image_t XVK_ImageCreate(const xvk_image_create_t *create);
void XVK_ImageDestroy(xvk_image_t *img);
r_vk_image_t R_VkImageCreate(const r_vk_image_create_t *create);
void R_VkImageDestroy(r_vk_image_t *img);
void R_VkImageClear(VkCommandBuffer cmdbuf, VkImage image);

View File

@ -1263,6 +1263,7 @@ static void uploadPointLights( struct LightsMetadata *metadata ) {
}
vk_lights_bindings_t VK_LightsUpload( void ) {
APROF_SCOPE_DECLARE_BEGIN(upload, __FUNCTION__);
const vk_staging_region_t locked = R_VkStagingLockForBuffer( (vk_staging_buffer_args_t) {
.buffer = g_lights_.buffer.buffer,
.offset = 0,
@ -1287,6 +1288,8 @@ vk_lights_bindings_t VK_LightsUpload( void ) {
g_lights_.frame_sequence++;
APROF_SCOPE_END(upload);
return (vk_lights_bindings_t){
.buffer = g_lights_.buffer.buffer,
.metadata = {

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, kColorspaceNative);
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

@ -7,6 +7,8 @@
#include "vk_common.h"
#include "vk_logs.h"
#include "profiler.h"
#define LOG_MODULE LogModule_Meatpipe
#define MIN(a,b) ((a)<(b)?(a):(b))
@ -427,6 +429,7 @@ void R_VkMeatpipeDestroy(vk_meatpipe_t *mp) {
}
void R_VkMeatpipePerform(vk_meatpipe_t *mp, struct vk_combuf_s *combuf, vk_meatpipe_perfrom_args_t args) {
APROF_SCOPE_DECLARE_BEGIN(perform, __FUNCTION__);
for (int i = 0; i < mp->passes_count; ++i) {
const vk_meatpipe_pass_t *pass = mp->passes + i;
RayPassPerform(pass->pass, combuf,
@ -439,4 +442,5 @@ void R_VkMeatpipePerform(vk_meatpipe_t *mp, struct vk_combuf_s *combuf, vk_meatp
}
);
}
APROF_SCOPE_END(perform);
}

View File

@ -266,10 +266,10 @@ static void drawOverlay( VkCommandBuffer cmdbuf ) {
{
vk_texture_t *texture = findTexture(g2d.batch[i].texture);
const VkPipeline pipeline = g2d.pipelines[g2d.batch[i].blending_mode];
if (texture->vk.descriptor)
if (texture->vk.descriptor_unorm)
{
vkCmdBindPipeline(cmdbuf, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
vkCmdBindDescriptorSets(cmdbuf, VK_PIPELINE_BIND_POINT_GRAPHICS, g2d.pipeline_layout, 0, 1, &texture->vk.descriptor, 0, NULL);
vkCmdBindDescriptorSets(cmdbuf, VK_PIPELINE_BIND_POINT_GRAPHICS, g2d.pipeline_layout, 0, 1, &texture->vk.descriptor_unorm, 0, NULL);
vkCmdDraw(cmdbuf, g2d.batch[i].vertex_count, 1, g2d.batch[i].vertex_offset, 0);
} // FIXME else what?
}

View File

@ -11,6 +11,8 @@
#include "vk_render.h"
#include "vk_logs.h"
#include "profiler.h"
#include "xash3d_mathlib.h"
#define LOG_MODULE LogModule_RT
@ -240,6 +242,7 @@ static void createTlas( vk_combuf_t *combuf, VkDeviceAddress instances_addr ) {
}
vk_resource_t RT_VkAccelPrepareTlas(vk_combuf_t *combuf) {
APROF_SCOPE_DECLARE_BEGIN(prepare, __FUNCTION__);
ASSERT(g_ray_model_state.frame.instances_count > 0);
DEBUG_BEGIN(combuf->cmdbuf, "prepare tlas");
@ -345,6 +348,7 @@ vk_resource_t RT_VkAccelPrepareTlas(vk_combuf_t *combuf) {
0, 0, NULL, COUNTOF(bmb), bmb, 0, NULL);
}
APROF_SCOPE_END(prepare);
return (vk_resource_t){
.type = VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR,
.value = (vk_descriptor_value_t){

View File

@ -298,6 +298,20 @@ rt_draw_instance_t *getDrawInstance(void) {
return g_ray_model_state.frame.instances + (g_ray_model_state.frame.instances_count++);
}
static float sRGBtoLinearScalar(const float sRGB) {
// IEC 61966-2-1:1999
const float linearLow = sRGB / 12.92f;
const float linearHigh = powf((sRGB + 0.055f) / 1.055f, 2.4f);
return sRGB <= 0.04045f ? linearLow : linearHigh;
}
static void sRGBtoLinearVec4(const vec4_t in, vec4_t out) {
out[0] = sRGBtoLinearScalar(in[0]);
out[1] = sRGBtoLinearScalar(in[1]);
out[2] = sRGBtoLinearScalar(in[2]);
out[3] = in[3];
}
void RT_FrameAddModel( struct rt_model_s *model, rt_frame_add_model_t args ) {
if (!model || !model->blas)
return;
@ -329,7 +343,7 @@ void RT_FrameAddModel( struct rt_model_s *model, rt_frame_add_model_t args ) {
draw_instance->blas_addr = model->blas_addr;
draw_instance->kusochki_offset = kusochki_offset;
draw_instance->material_mode = args.material_mode;
Vector4Copy(*args.color, draw_instance->color);
sRGBtoLinearVec4(*args.color_srgb, draw_instance->color);
Matrix3x4_Copy(draw_instance->transform_row, args.transform);
Matrix4x4_Copy(draw_instance->prev_transform_row, args.prev_transform);
}
@ -390,6 +404,7 @@ void RT_DynamicModelShutdown(void) {
}
void RT_DynamicModelProcessFrame(void) {
APROF_SCOPE_DECLARE_BEGIN(process, __FUNCTION__);
for (int i = 0; i < MATERIAL_MODE_COUNT; ++i) {
rt_dynamic_t *const dyn = g_dyn.groups + i;
if (!dyn->geometries_count)
@ -427,6 +442,7 @@ void RT_DynamicModelProcessFrame(void) {
tail:
dyn->geometries_count = 0;
}
APROF_SCOPE_END(process);
}
void RT_FrameAddOnce( rt_frame_add_once_t args ) {

View File

@ -589,14 +589,14 @@ void VK_RenderEnd( VkCommandBuffer cmdbuf, qboolean draw )
if (lightmap != draw->draw.lightmap) {
lightmap = draw->draw.lightmap;
vkCmdBindDescriptorSets(cmdbuf, VK_PIPELINE_BIND_POINT_GRAPHICS, g_render.pipeline_layout, 2, 1, &findTexture(lightmap)->vk.descriptor, 0, NULL);
vkCmdBindDescriptorSets(cmdbuf, VK_PIPELINE_BIND_POINT_GRAPHICS, g_render.pipeline_layout, 2, 1, &findTexture(lightmap)->vk.descriptor_unorm, 0, NULL);
}
if (texture != draw->draw.texture)
{
texture = draw->draw.texture;
// TODO names/enums for binding points
vkCmdBindDescriptorSets(cmdbuf, VK_PIPELINE_BIND_POINT_GRAPHICS, g_render.pipeline_layout, 1, 1, &findTexture(texture)->vk.descriptor, 0, NULL);
vkCmdBindDescriptorSets(cmdbuf, VK_PIPELINE_BIND_POINT_GRAPHICS, g_render.pipeline_layout, 1, 1, &findTexture(texture)->vk.descriptor_unorm, 0, NULL);
}
// Only indexed mode is supported
@ -784,7 +784,7 @@ void R_RenderModelDraw(const vk_render_model_t *model, r_model_draw_t args) {
.material_mode = args.material_mode,
.transform = (const matrix3x4*)args.transform,
.prev_transform = (const matrix3x4*)args.prev_transform,
.color = args.color,
.color_srgb = args.color,
.dynamic_polylights = model->dynamic_polylights,
.dynamic_polylights_count = model->dynamic_polylights_count,
.override = {

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

@ -19,7 +19,7 @@
#include "vk_logs.h"
#include "alolcator.h"
#include "profiler.h"
#include "eiface.h"
#include "xash3d_mathlib.h"
@ -67,7 +67,7 @@ enum {
typedef struct {
char name[64];
vk_resource_t resource;
xvk_image_t image;
r_vk_image_t image;
int refcount;
int source_index_plus_1;
} rt_resource_t;
@ -170,6 +170,7 @@ typedef struct {
} perform_tracing_args_t;
static void performTracing( vk_combuf_t *combuf, const perform_tracing_args_t* args) {
APROF_SCOPE_DECLARE_BEGIN(perform, __FUNCTION__);
const VkCommandBuffer cmdbuf = combuf->cmdbuf;
#define RES_SET_BUFFER(name, type_, source_, offset_, size_) \
@ -238,7 +239,7 @@ static void performTracing( vk_combuf_t *combuf, const perform_tracing_args_t* a
// Swap resources
const vk_resource_t tmp_res = res->resource;
const xvk_image_t tmp_img = res->image;
const r_vk_image_t tmp_img = res->image;
res->resource = src->resource;
res->image = src->image;
@ -338,6 +339,8 @@ static void performTracing( vk_combuf_t *combuf, const perform_tracing_args_t* a
g_rtx.mainpipe_out->resource.write.image_layout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
}
DEBUG_END(cmdbuf);
APROF_SCOPE_END(perform);
}
static void cleanupResources(void) {
@ -346,7 +349,7 @@ static void cleanupResources(void) {
if (!res->name[0] || res->refcount || !res->image.image)
continue;
XVK_ImageDestroy(&res->image);
R_VkImageDestroy(&res->image);
res->name[0] = '\0';
}
}
@ -387,10 +390,11 @@ static void reloadMainpipe(void) {
for (int i = 0; i < newpipe->resources_count; ++i) {
const vk_meatpipe_resource_t *mr = newpipe->resources + i;
DEBUG("res %d/%d: %s descriptor=%u count=%d flags=[%c%c] image_format=%u",
DEBUG("res %d/%d: %s descriptor=%u count=%d flags=[%c%c] image_format=(%s)%u",
i, newpipe->resources_count, mr->name, mr->descriptor_type, mr->count,
(mr->flags & MEATPIPE_RES_WRITE) ? 'W' : ' ',
(mr->flags & MEATPIPE_RES_CREATE) ? 'C' : ' ',
R_VkFormatName(mr->image_format),
mr->image_format);
const qboolean create = !!(mr->flags & MEATPIPE_RES_CREATE);
@ -415,8 +419,11 @@ static void reloadMainpipe(void) {
newpipe_out = res;
if (create) {
if (res->image.image == VK_NULL_HANDLE) {
const xvk_image_create_t create = {
if (res->image.image == VK_NULL_HANDLE || mr->image_format != res->image.format) {
if (res->image.image != VK_NULL_HANDLE) {
R_VkImageDestroy(&res->image);
}
const r_vk_image_create_t create = {
.debug_name = mr->name,
.width = FRAME_WIDTH,
.height = FRAME_HEIGHT,
@ -427,13 +434,10 @@ static void reloadMainpipe(void) {
// TODO figure out how to detect this need properly. prev_dest is not defined as "output"
//.usage = VK_IMAGE_USAGE_STORAGE_BIT | (output ? VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT : 0),
.usage = VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT,
.has_alpha = true,
.is_cubemap = false,
.flags = kVkImageFlagHasAlpha,
};
res->image = XVK_ImageCreate(&create);
res->image = R_VkImageCreate(&create);
Q_strncpy(res->name, mr->name, sizeof(res->name));
} else {
// TODO if (mr->image_format != res->image.format) { S_ERROR and goto fail }
}
}
@ -493,7 +497,7 @@ static void reloadMainpipe(void) {
// TODO currently changing texture format is not handled. It will try to reuse existing image with the old format
// which will probably fail. To handle it we'd need to refactor this:
// 1. xvk_image_t should have a field with its current format? (or we'd also store if with the resource here)
// 1. r_vk_image_t should have a field with its current format? (or we'd also store if with the resource here)
// 2. do another loop here to detect format mismatch and recreate.
g_rtx.mainpipe = newpipe;
@ -513,6 +517,8 @@ fail:
void VK_RayFrameEnd(const vk_ray_frame_render_args_t* args)
{
APROF_SCOPE_DECLARE_BEGIN(ray_frame_end, __FUNCTION__);
const VkCommandBuffer cmdbuf = args->combuf->cmdbuf;
// const xvk_ray_frame_images_t* current_frame = g_rtx.frames + (g_rtx.frame_number % 2);
@ -576,6 +582,8 @@ void VK_RayFrameEnd(const vk_ray_frame_render_args_t* args)
};
performTracing( args->combuf, &trace_args );
}
APROF_SCOPE_END(ray_frame_end);
}
static void reloadPipeline( void ) {

View File

@ -58,7 +58,7 @@ qboolean RT_ModelUpdateMaterials(struct rt_model_s *model, const struct vk_rende
typedef struct {
int material_mode;
const matrix3x4 *transform, *prev_transform;
const vec4_t *color;
const vec4_t *color_srgb;
struct rt_light_add_polygon_s *dynamic_polylights;
int dynamic_polylights_count;

View File

@ -230,6 +230,9 @@ int R_FIXME_GetEntityRenderMode( cl_entity_t *ent )
}
void R_SceneMapDestroy( void ) {
// Make sure no rendering is happening
XVK_CHECK(vkDeviceWaitIdle( vk_core.device ));
VK_BrushModelDestroyAll();
}

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

@ -18,7 +18,7 @@ static struct {
VkImageView *image_views;
VkFramebuffer *framebuffers;
xvk_image_t depth;
r_vk_image_t depth;
uint32_t width, height;
@ -33,11 +33,10 @@ static uint32_t clamp_u32(uint32_t v, uint32_t min, uint32_t max) {
}
static void createDepthImage(int w, int h, VkFormat depth_format) {
const xvk_image_create_t xic = {
const r_vk_image_create_t xic = {
.debug_name = "depth",
.format = depth_format,
.has_alpha = false,
.is_cubemap = false,
.flags = 0,
.mips = 1,
.layers = 1,
.width = w,
@ -45,7 +44,7 @@ static void createDepthImage(int w, int h, VkFormat depth_format) {
.tiling = VK_IMAGE_TILING_OPTIMAL,
.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT,
};
g_swapchain.depth = XVK_ImageCreate( &xic );
g_swapchain.depth = R_VkImageCreate( &xic );
}
static void destroySwapchainAndFramebuffers( VkSwapchainKHR swapchain ) {
@ -56,7 +55,7 @@ static void destroySwapchainAndFramebuffers( VkSwapchainKHR swapchain ) {
vkDestroyFramebuffer(vk_core.device, g_swapchain.framebuffers[i], NULL);
}
XVK_ImageDestroy( &g_swapchain.depth );
R_VkImageDestroy( &g_swapchain.depth );
vkDestroySwapchainKHR(vk_core.device, swapchain, NULL);
}
@ -187,7 +186,12 @@ qboolean R_VkSwapchainInit( VkRenderPass render_pass, VkFormat depth_format ) {
VkSurfaceCapabilitiesKHR surface_caps;
XVK_CHECK(vkGetPhysicalDeviceSurfaceCapabilitiesKHR(vk_core.physical_device.device, vk_core.surface.surface, &surface_caps));
g_swapchain.image_format = VK_FORMAT_B8G8R8A8_UNORM;//SRGB, // TODO get from surface_formats
/*
[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.render_pass = render_pass;
g_swapchain.depth_format = depth_format;

View File

@ -95,7 +95,7 @@ void destroyTextures( void )
unloadSkybox();
XVK_ImageDestroy(&tglob.cubemap_placeholder.vk.image);
R_VkImageDestroy(&tglob.cubemap_placeholder.vk.image);
g_textures.stats.size_total -= tglob.cubemap_placeholder.total_size;
g_textures.stats.count--;
memset(&tglob.cubemap_placeholder, 0, sizeof(tglob.cubemap_placeholder));
@ -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;
@ -591,7 +594,7 @@ static qboolean uploadTexture(vk_texture_t *tex, rgbdata_t *const *const layers,
tex->height = layers[0]->height;
mipCount = CalcMipmapCount( tex, true);
DEBUG("Uploading texture %s, mips=%d, layers=%d", tex->name, mipCount, num_layers);
DEBUG("Uploading texture[%d] %s, mips=%d, layers=%d", (int)(tex-vk_textures), tex->name, mipCount, num_layers);
// TODO this vvv
// // NOTE: only single uncompressed textures can be resamples, no mips, no layers, no sides
@ -603,7 +606,7 @@ static qboolean uploadTexture(vk_texture_t *tex, rgbdata_t *const *const layers,
// data = GL_ApplyFilter( data, tex->width, tex->height );
{
const xvk_image_create_t create = {
const r_vk_image_create_t create = {
.debug_name = tex->name,
.width = tex->width,
.height = tex->height,
@ -612,10 +615,12 @@ static qboolean uploadTexture(vk_texture_t *tex, rgbdata_t *const *const layers,
.format = format,
.tiling = VK_IMAGE_TILING_OPTIMAL,
.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT,
.has_alpha = layers[0]->flags & IMAGE_HAS_ALPHA,
.is_cubemap = cubemap,
.flags = 0
| ((layers[0]->flags & IMAGE_HAS_ALPHA) ? kVkImageFlagHasAlpha : 0)
| (cubemap ? kVkImageFlagIsCubemap : 0)
| (colorspace_hint == kColorspaceGamma ? kVkImageFlagCreateUnormView : 0),
};
tex->vk.image = XVK_ImageCreate(&create);
tex->vk.image = R_VkImageCreate(&create);
}
{
@ -721,30 +726,54 @@ static qboolean uploadTexture(vk_texture_t *tex, rgbdata_t *const *const layers,
// TODO how should we approach this:
// - per-texture desc sets can be inconvenient if texture is used in different incompatible contexts
// - update descriptor sets in batch?
if (vk_desc.next_free != MAX_TEXTURES) {
if (vk_desc.next_free < MAX_TEXTURES-2) {
const int index = tex - vk_textures;
VkDescriptorImageInfo dii_tmp;
// FIXME handle cubemaps properly w/o this garbage. they should be the same as regular textures.
VkDescriptorImageInfo *const dii_tex = (num_layers == 1) ? tglob.dii_all_textures + index : &dii_tmp;
*dii_tex = (VkDescriptorImageInfo){
const VkDescriptorSet ds = vk_desc.sets[vk_desc.next_free++];
const VkDescriptorSet ds_unorm =
(colorspace_hint == kColorspaceGamma && tex->vk.image.view_unorm != VK_NULL_HANDLE)
? vk_desc.sets[vk_desc.next_free++] : VK_NULL_HANDLE;
const VkDescriptorImageInfo dii = {
.imageView = tex->vk.image.view,
.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
.sampler = pickSamplerForFlags( tex->flags ),
};
const VkWriteDescriptorSet wds[] = { {
const VkDescriptorImageInfo dii_unorm = {
.imageView = tex->vk.image.view_unorm,
.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
.sampler = dii.sampler,
};
VkWriteDescriptorSet wds[2] = { {
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
.dstBinding = 0,
.dstArrayElement = 0,
.descriptorCount = 1,
.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
.pImageInfo = dii_tex,
.dstSet = tex->vk.descriptor = vk_desc.sets[vk_desc.next_free++],
.pImageInfo = &dii,
.dstSet = ds,
}, {
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
.dstBinding = 0,
.dstArrayElement = 0,
.descriptorCount = 1,
.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
.pImageInfo = &dii_unorm,
.dstSet = ds_unorm,
}};
vkUpdateDescriptorSets(vk_core.device, ARRAYSIZE(wds), wds, 0, NULL);
vkUpdateDescriptorSets(vk_core.device, ds_unorm != VK_NULL_HANDLE ? 2 : 1 , wds, 0, NULL);
// FIXME handle cubemaps properly w/o this garbage. they should be the same as regular textures.
if (num_layers == 1) {
tglob.dii_all_textures[index] = dii;
}
tex->vk.descriptor_unorm = ds_unorm != VK_NULL_HANDLE ? ds_unorm : ds;
}
else
{
tex->vk.descriptor = VK_NULL_HANDLE;
tex->vk.descriptor_unorm = VK_NULL_HANDLE;
}
g_textures.stats.size_total += tex->total_size;
@ -888,7 +917,7 @@ static int loadKtx2( const char *name ) {
// 1. Create image
{
const xvk_image_create_t create = {
const r_vk_image_create_t create = {
.debug_name = tex->name,
.width = header->pixelWidth,
.height = header->pixelHeight,
@ -897,10 +926,10 @@ static int loadKtx2( const char *name ) {
.format = header->vkFormat,
.tiling = VK_IMAGE_TILING_OPTIMAL,
.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT,
.has_alpha = false, // FIXME
.is_cubemap = false,
// FIXME find out if there's alpha
.flags = 0,
};
tex->vk.image = XVK_ImageCreate(&create);
tex->vk.image = R_VkImageCreate(&create);
}
// 2. Prep cmdbuf, barrier, etc
@ -997,9 +1026,13 @@ static int loadKtx2( const char *name ) {
}
}
// KTX2 textures are inaccessible from trad renderer (for now)
tex->vk.descriptor_unorm = VK_NULL_HANDLE;
// TODO how should we approach this:
// - per-texture desc sets can be inconvenient if texture is used in different incompatible contexts
// - update descriptor sets in batch?
if (vk_desc.next_free != MAX_TEXTURES) {
const int num_layers = 1; // TODO cubemap
const int index = tex - vk_textures;
@ -1018,14 +1051,10 @@ static int loadKtx2( const char *name ) {
.descriptorCount = 1,
.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
.pImageInfo = dii_tex,
.dstSet = tex->vk.descriptor = vk_desc.sets[vk_desc.next_free++],
.dstSet = vk_desc.sets[vk_desc.next_free++],
}};
vkUpdateDescriptorSets(vk_core.device, ARRAYSIZE(wds), wds, 0, NULL);
}
else
{
tex->vk.descriptor = VK_NULL_HANDLE;
}
g_textures.stats.size_total += tex->total_size;
g_textures.stats.count++;
@ -1044,29 +1073,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 +1094,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 +1110,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 )
@ -1182,7 +1215,7 @@ void VK_FreeTexture( unsigned int texnum ) {
R_VkStagingFlushSync();
XVK_CHECK(vkDeviceWaitIdle(vk_core.device));
XVK_ImageDestroy(&tex->vk.image);
R_VkImageDestroy(&tex->vk.image);
g_textures.stats.size_total -= tex->total_size;
g_textures.stats.count--;
memset(tex, 0, sizeof(*tex));
@ -1222,7 +1255,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;
@ -1250,7 +1283,7 @@ int XVK_TextureLookupF( const char *fmt, ...) {
static void unloadSkybox( void ) {
if (tglob.skybox_cube.vk.image.image) {
XVK_ImageDestroy(&tglob.skybox_cube.vk.image);
R_VkImageDestroy(&tglob.skybox_cube.vk.image);
g_textures.stats.size_total -= tglob.skybox_cube.total_size;
g_textures.stats.count--;
memset(&tglob.skybox_cube, 0, sizeof(tglob.skybox_cube));
@ -1348,7 +1381,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

@ -16,8 +16,8 @@ typedef struct vk_texture_s
uint texnum;
struct {
xvk_image_t image;
VkDescriptorSet descriptor;
r_vk_image_t image;
VkDescriptorSet descriptor_unorm;
} vk;
uint hashValue;
@ -48,6 +48,7 @@ typedef struct vk_textures_global_s
vk_texture_t skybox_cube;
vk_texture_t cubemap_placeholder;
// All textures descriptors in their native formats used for RT
VkDescriptorImageInfo dii_all_textures[MAX_TEXTURES];
// FIXME this should not exist, all textures should have their own samplers based on flags
@ -67,18 +68,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, ...);