Merge pull request #593 from w23/materials-table

Materials table and friends

- [ ] KTX2 #154
  - [x] proof of concept
  - [x] proper alignment for blocks (validation complains)
- [ ] texture leaks: #594 
  - [x] investigate
- [x] `inherit`/`use`
- [x] Fix #211 
  - [x] make materials table
  - [ ] ~~make materials by-name lookup independent of texture table, stop doing dummy textures~~ moved to #601 
- [ ] normalmap glitches, #595
  - [x] investigated, seems to be offline normalization vs bit depth/precision issue; needs offline changes
This commit is contained in:
Ivan Avdeev 2023-10-12 08:43:49 -07:00 committed by GitHub
commit 726fcee3f7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 1367 additions and 229 deletions

View File

@ -600,3 +600,38 @@ For multiple replacements:
"_xvk_texture_material" "generic028 generic_metal1 generic029 generic_metal2 ... ..."
}
```
# 2023-10-02 E305
## Materials table
### Operations
- Clean
- load materials from file
- current format (mixes materials and selection rules a bit)
- other formats (can only support named materials w/o any selection rules)
- inherit/use from previously defined materials
- needs index/value by name below
- Get materials by:
- value by tex_id
- value by tex_id + rendermode
- value by tex_id + chrome
- ~~(do we need to specialize "for_chrome"? were there any cases where it would be useful?)~~ It seems not.
- index by name (currently works by having a dummy texture with this name; reuses vk_textures hash search)
- Lazy: Getting by value performs loading, getting by index does not.
### Data structures overview
- materials[mat_id] -- indexed by indexes independent of tex_id, referenced externally, requires stable index.
- (typical material fields)
- possibly lazily loaded
- arg `-vknolazymaterials` for development. To immediately recognize missing textues, not until they are requested for the first time.
- fallback onto default/error texture on lazy loading errors
- tex_to_material[tex_id] table (hash table, array, whatever)
- state -- {NotChecked, NoReplacement, ReplacementExists}
- NotChecked -- means there was no explicit replacement, but we still can check for auto replacement (TEXNAME_roughness.png, etc)
- NoReplacement -- there's nothing to replace with, use original texture with default material parameters.
- mat_id -- index into materials[] table if there's a replacement
- rendermodes-specific overrides (fixed array? too expensive; linked list?)
- rendermode
- mat_id
- name_to_material[] -- string "name" to mat_id
- hash table of some sorts

View File

@ -21,9 +21,10 @@
enum {
SPEEDS_BIT_OFF = 0, // `r_speeds 0` turns off all performance stats display
SPEEDS_BIT_SIMPLE = 1, // `r_speeds 1` displays only basic info about frame time
SPEEDS_BIT_STATS = 2, // `r_speeds 2` displays additional metrics, i.e. lights counts, dynamic geometry upload sizes, etc (TODO)
SPEEDS_BIT_GPU_USAGE = 4, // `r_speeds 4` displays overall GPU usage stats (TODO)
SPEEDS_BIT_FRAME = 8, // `r_speeds 8` diplays details instrumental profiler frame data, e.g. specific functions times graphs, etc
SPEEDS_BIT_STATS = 2, // `r_speeds 2` displays additional metrics, i.e. lights counts, dynamic geometry upload sizes, etc
SPEEDS_BIT_GRAPHS = 4, // `r_speeds 4` display instrumental metrics graphs, controlled by r_speeds_graphs var
SPEEDS_BIT_FRAME = 8, // `r_speeds 8` diplays details instrumental profiler flame graph
// TODO SPEEDS_BIT_GPU_USAGE = 16, // `r_speeds 16` displays overall GPU usage stats
// These bits can be combined, e.g. `r_speeds 9`, 8+1, will display 1: basic timing info and 8: frame graphs
};
@ -972,10 +973,11 @@ void R_SpeedsDisplayMore(uint32_t prev_frame_index, const struct vk_combuf_scope
{
int y = 100;
const int draw = speeds_bits & SPEEDS_BIT_FRAME;
y = drawFrames( draw, prev_frame_index, y, gpurofl, gpurofl_count );
const int draw_frame = speeds_bits & SPEEDS_BIT_FRAME;
y = drawFrames( draw_frame, prev_frame_index, y, gpurofl, gpurofl_count );
if (draw)
const int draw_graphs = speeds_bits & SPEEDS_BIT_GRAPHS;
if (draw_graphs)
y = drawGraphs(y + 10);
}

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

@ -41,10 +41,14 @@ layout(set = 0, binding = 18) uniform sampler2D textures[MAX_TEXTURES];
include "bluenoise.glsl"
#endif
//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;
@ -202,9 +206,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;
@ -278,8 +304,8 @@ void main() {
//imageStore(out_dest, pix, vec4(LINEARtoSRGB(diffuse), 0.)); return;
}
const vec4 base_color_a = imageLoad(base_color_a, pix);
colour *= SRGBtoLINEAR(base_color_a.rgb);
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,7 +31,7 @@ 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 = sampleTexture(material.tex_base_color, geom.uv, geom.uv_lods);
@ -45,9 +45,36 @@ void primaryRayHit(rayQueryEXT rq, inout RayPayloadPrimary payload) {
T = normalize(T - dot(T, geom.normal_shading) * geom.normal_shading);
const vec3 B = normalize(cross(geom.normal_shading, T));
const mat3 TBN = mat3(T, B, geom.normal_shading);
vec3 tnorm = sampleTexture(tex_normal, geom.uv, geom.uv_lods).xyz * 2. - 1.; // TODO is this sampling correct for normal data?
// Get to KTX2 normal maps eventually
//#define KTX2
#ifdef KTX2
// We expect KTX2 normalmaps to have only 2 SNORM components.
// TODO: BC6H only can do signed or unsigned 16-bit floats. It can't normalize them on its own. So we either deal with
// sub-par 10bit precision for <1 values. Or do normalization manually in shader. Manual normalization implies prepa-
// ring normalmaps in a special way, i.e. scaling vector components to full f16 scale.
#define NORMALMAP_SNORM
#define NORMALMAP_2COMP
#endif
#ifdef NORMALMAP_SNORM // [-1..1]
// TODO is this sampling correct for normal data?
vec3 tnorm = sampleTexture(tex_normal, geom.uv, geom.uv_lods).xyz;
#else // Older UNORM [0..1]
vec3 tnorm = sampleTexture(tex_normal, geom.uv, geom.uv_lods).xyz * 2. - 1.;
#endif
#ifndef NORMALMAP_2COMP
// Older 8-bit PNG suffers from quantization.
// Smoothen quantization by normalizing it
tnorm = normalize(tnorm);
#endif
tnorm.xy *= material.normal_scale;
// Restore z based on scaled xy
tnorm.z = sqrt(max(0., 1. - dot(tnorm.xy, tnorm.xy)));
geom.normal_shading = normalize(TBN * tnorm);
}
#endif
@ -62,7 +89,8 @@ void primaryRayHit(rayQueryEXT rq, inout RayPayloadPrimary payload) {
//payload.emissive.rgb = kusok.emissive * SRGBtoLINEAR(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) * SRGBtoLINEAR(payload.base_color_a.rgb);
payload.emissive.rgb = (sqrt(kusok.emissive) / 8) * payload.base_color_a.rgb;
//payload.emissive.rgb = kusok.emissive * payload.base_color_a.rgb;
#else
// Fake texture color
if (any(greaterThan(kusok.emissive, vec3(0.))))

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,7 +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;
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

@ -1088,6 +1088,8 @@ static qboolean fillBrushSurfaces(fill_geometries_args_t args) {
continue;
int tex_id = orig_tex_id;
// TODO this patching should probably override entity patching below
const xvk_patch_surface_t *const psurf = R_VkPatchGetSurface(surface_index);
if (psurf && psurf->tex_id >= 0)
tex_id = psurf->tex_id;
@ -1121,7 +1123,7 @@ static qboolean fillBrushSurfaces(fill_geometries_args_t args) {
qboolean material_assigned = false;
if (entity_patch) {
for (int i = 0; i < entity_patch->matmap_count; ++i) {
if (entity_patch->matmap[i].from_tex == tex_id) {
if (entity_patch->matmap[i].from_tex == orig_tex_id) {
model_geometry->material = R_VkMaterialGetForTexture(entity_patch->matmap[i].to_mat.index);
material_assigned = true;
break;

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) {
@ -185,3 +288,368 @@ void R_VkImageBlit(VkCommandBuffer cmdbuf, const r_vkimage_blit_args *blit_args)
0, 0, NULL, 0, NULL, COUNTOF(image_barriers), image_barriers);
}
}
uint32_t R_VkImageFormatTexelBlockSize( VkFormat format ) {
switch (format) {
case VK_FORMAT_R4G4_UNORM_PACK8:
case VK_FORMAT_R8_UNORM:
case VK_FORMAT_R8_SNORM:
case VK_FORMAT_R8_USCALED:
case VK_FORMAT_R8_SSCALED:
case VK_FORMAT_R8_UINT:
case VK_FORMAT_R8_SINT:
case VK_FORMAT_R8_SRGB:
return 1;
case VK_FORMAT_R10X6_UNORM_PACK16:
case VK_FORMAT_R12X4_UNORM_PACK16:
case VK_FORMAT_A4R4G4B4_UNORM_PACK16:
case VK_FORMAT_A4B4G4R4_UNORM_PACK16:
case VK_FORMAT_R4G4B4A4_UNORM_PACK16:
case VK_FORMAT_B4G4R4A4_UNORM_PACK16:
case VK_FORMAT_R5G6B5_UNORM_PACK16:
case VK_FORMAT_B5G6R5_UNORM_PACK16:
case VK_FORMAT_R5G5B5A1_UNORM_PACK16:
case VK_FORMAT_B5G5R5A1_UNORM_PACK16:
case VK_FORMAT_A1R5G5B5_UNORM_PACK16:
case VK_FORMAT_R8G8_UNORM:
case VK_FORMAT_R8G8_SNORM:
case VK_FORMAT_R8G8_USCALED:
case VK_FORMAT_R8G8_SSCALED:
case VK_FORMAT_R8G8_UINT:
case VK_FORMAT_R8G8_SINT:
case VK_FORMAT_R8G8_SRGB:
case VK_FORMAT_R16_UNORM:
case VK_FORMAT_R16_SNORM:
case VK_FORMAT_R16_USCALED:
case VK_FORMAT_R16_SSCALED:
case VK_FORMAT_R16_UINT:
case VK_FORMAT_R16_SINT:
case VK_FORMAT_R16_SFLOAT:
return 2;
case VK_FORMAT_R8G8B8_UNORM:
case VK_FORMAT_R8G8B8_SNORM:
case VK_FORMAT_R8G8B8_USCALED:
case VK_FORMAT_R8G8B8_SSCALED:
case VK_FORMAT_R8G8B8_UINT:
case VK_FORMAT_R8G8B8_SINT:
case VK_FORMAT_R8G8B8_SRGB:
case VK_FORMAT_B8G8R8_UNORM:
case VK_FORMAT_B8G8R8_SNORM:
case VK_FORMAT_B8G8R8_USCALED:
case VK_FORMAT_B8G8R8_SSCALED:
case VK_FORMAT_B8G8R8_UINT:
case VK_FORMAT_B8G8R8_SINT:
case VK_FORMAT_B8G8R8_SRGB:
return 3;
case VK_FORMAT_R10X6G10X6_UNORM_2PACK16:
case VK_FORMAT_R12X4G12X4_UNORM_2PACK16:
case VK_FORMAT_R16G16_S10_5_NV:
case VK_FORMAT_R8G8B8A8_UNORM:
case VK_FORMAT_R8G8B8A8_SNORM:
case VK_FORMAT_R8G8B8A8_USCALED:
case VK_FORMAT_R8G8B8A8_SSCALED:
case VK_FORMAT_R8G8B8A8_UINT:
case VK_FORMAT_R8G8B8A8_SINT:
case VK_FORMAT_R8G8B8A8_SRGB:
case VK_FORMAT_B8G8R8A8_UNORM:
case VK_FORMAT_B8G8R8A8_SNORM:
case VK_FORMAT_B8G8R8A8_USCALED:
case VK_FORMAT_B8G8R8A8_SSCALED:
case VK_FORMAT_B8G8R8A8_UINT:
case VK_FORMAT_B8G8R8A8_SINT:
case VK_FORMAT_B8G8R8A8_SRGB:
case VK_FORMAT_A8B8G8R8_UNORM_PACK32:
case VK_FORMAT_A8B8G8R8_SNORM_PACK32:
case VK_FORMAT_A8B8G8R8_USCALED_PACK32:
case VK_FORMAT_A8B8G8R8_SSCALED_PACK32:
case VK_FORMAT_A8B8G8R8_UINT_PACK32:
case VK_FORMAT_A8B8G8R8_SINT_PACK32:
case VK_FORMAT_A8B8G8R8_SRGB_PACK32:
case VK_FORMAT_A2R10G10B10_UNORM_PACK32:
case VK_FORMAT_A2R10G10B10_SNORM_PACK32:
case VK_FORMAT_A2R10G10B10_USCALED_PACK32:
case VK_FORMAT_A2R10G10B10_SSCALED_PACK32:
case VK_FORMAT_A2R10G10B10_UINT_PACK32:
case VK_FORMAT_A2R10G10B10_SINT_PACK32:
case VK_FORMAT_A2B10G10R10_UNORM_PACK32:
case VK_FORMAT_A2B10G10R10_SNORM_PACK32:
case VK_FORMAT_A2B10G10R10_USCALED_PACK32:
case VK_FORMAT_A2B10G10R10_SSCALED_PACK32:
case VK_FORMAT_A2B10G10R10_UINT_PACK32:
case VK_FORMAT_A2B10G10R10_SINT_PACK32:
case VK_FORMAT_R16G16_UNORM:
case VK_FORMAT_R16G16_SNORM:
case VK_FORMAT_R16G16_USCALED:
case VK_FORMAT_R16G16_SSCALED:
case VK_FORMAT_R16G16_UINT:
case VK_FORMAT_R16G16_SINT:
case VK_FORMAT_R16G16_SFLOAT:
case VK_FORMAT_R32_UINT:
case VK_FORMAT_R32_SINT:
case VK_FORMAT_R32_SFLOAT:
case VK_FORMAT_B10G11R11_UFLOAT_PACK32:
case VK_FORMAT_E5B9G9R9_UFLOAT_PACK32:
return 4;
case VK_FORMAT_R16G16B16_UNORM:
case VK_FORMAT_R16G16B16_SNORM:
case VK_FORMAT_R16G16B16_USCALED:
case VK_FORMAT_R16G16B16_SSCALED:
case VK_FORMAT_R16G16B16_UINT:
case VK_FORMAT_R16G16B16_SINT:
case VK_FORMAT_R16G16B16_SFLOAT:
return 6;
case VK_FORMAT_R16G16B16A16_UNORM:
case VK_FORMAT_R16G16B16A16_SNORM:
case VK_FORMAT_R16G16B16A16_USCALED:
case VK_FORMAT_R16G16B16A16_SSCALED:
case VK_FORMAT_R16G16B16A16_UINT:
case VK_FORMAT_R16G16B16A16_SINT:
case VK_FORMAT_R16G16B16A16_SFLOAT:
case VK_FORMAT_R32G32_UINT:
case VK_FORMAT_R32G32_SINT:
case VK_FORMAT_R32G32_SFLOAT:
case VK_FORMAT_R64_UINT:
case VK_FORMAT_R64_SINT:
case VK_FORMAT_R64_SFLOAT:
return 8;
case VK_FORMAT_R32G32B32_UINT:
case VK_FORMAT_R32G32B32_SINT:
case VK_FORMAT_R32G32B32_SFLOAT:
return 12;
case VK_FORMAT_R32G32B32A32_UINT:
case VK_FORMAT_R32G32B32A32_SINT:
case VK_FORMAT_R32G32B32A32_SFLOAT:
case VK_FORMAT_R64G64_UINT:
case VK_FORMAT_R64G64_SINT:
case VK_FORMAT_R64G64_SFLOAT:
return 16;
case VK_FORMAT_R64G64B64_UINT:
case VK_FORMAT_R64G64B64_SINT:
case VK_FORMAT_R64G64B64_SFLOAT:
return 24;
case VK_FORMAT_R64G64B64A64_UINT:
case VK_FORMAT_R64G64B64A64_SINT:
case VK_FORMAT_R64G64B64A64_SFLOAT:
return 32;
case VK_FORMAT_D16_UNORM:
return 2;
case VK_FORMAT_X8_D24_UNORM_PACK32:
return 4;
case VK_FORMAT_D32_SFLOAT:
return 4;
case VK_FORMAT_S8_UINT:
return 2;
case VK_FORMAT_D16_UNORM_S8_UINT:
return 3;
case VK_FORMAT_D24_UNORM_S8_UINT:
return 4;
case VK_FORMAT_D32_SFLOAT_S8_UINT:
return 5;
case VK_FORMAT_BC1_RGB_UNORM_BLOCK:
case VK_FORMAT_BC1_RGB_SRGB_BLOCK:
return 8;
case VK_FORMAT_BC1_RGBA_UNORM_BLOCK:
case VK_FORMAT_BC1_RGBA_SRGB_BLOCK:
return 8;
case VK_FORMAT_BC2_UNORM_BLOCK:
case VK_FORMAT_BC2_SRGB_BLOCK:
return 16;
case VK_FORMAT_BC3_UNORM_BLOCK:
case VK_FORMAT_BC3_SRGB_BLOCK:
return 16;
case VK_FORMAT_BC4_UNORM_BLOCK:
case VK_FORMAT_BC4_SNORM_BLOCK:
return 8;
case VK_FORMAT_BC5_UNORM_BLOCK:
case VK_FORMAT_BC5_SNORM_BLOCK:
return 16;
case VK_FORMAT_BC6H_UFLOAT_BLOCK:
case VK_FORMAT_BC6H_SFLOAT_BLOCK:
return 16;
case VK_FORMAT_BC7_UNORM_BLOCK:
case VK_FORMAT_BC7_SRGB_BLOCK:
return 16;
case VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK:
case VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK:
return 8;
case VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK:
case VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK:
return 8;
case VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK:
case VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK:
return 16;
case VK_FORMAT_EAC_R11_UNORM_BLOCK:
case VK_FORMAT_EAC_R11_SNORM_BLOCK:
return 8;
case VK_FORMAT_EAC_R11G11_UNORM_BLOCK:
case VK_FORMAT_EAC_R11G11_SNORM_BLOCK:
return 16;
case VK_FORMAT_ASTC_4x4_SFLOAT_BLOCK:
case VK_FORMAT_ASTC_4x4_UNORM_BLOCK:
case VK_FORMAT_ASTC_4x4_SRGB_BLOCK:
return 16;
case VK_FORMAT_ASTC_5x4_SFLOAT_BLOCK:
case VK_FORMAT_ASTC_5x4_UNORM_BLOCK:
case VK_FORMAT_ASTC_5x4_SRGB_BLOCK:
return 16;
case VK_FORMAT_ASTC_5x5_SFLOAT_BLOCK:
case VK_FORMAT_ASTC_5x5_UNORM_BLOCK:
case VK_FORMAT_ASTC_5x5_SRGB_BLOCK:
return 16;
case VK_FORMAT_ASTC_6x5_SFLOAT_BLOCK:
case VK_FORMAT_ASTC_6x5_UNORM_BLOCK:
case VK_FORMAT_ASTC_6x5_SRGB_BLOCK:
return 16;
case VK_FORMAT_ASTC_6x6_SFLOAT_BLOCK:
case VK_FORMAT_ASTC_6x6_UNORM_BLOCK:
case VK_FORMAT_ASTC_6x6_SRGB_BLOCK:
return 16;
case VK_FORMAT_ASTC_8x5_SFLOAT_BLOCK:
case VK_FORMAT_ASTC_8x5_UNORM_BLOCK:
case VK_FORMAT_ASTC_8x5_SRGB_BLOCK:
return 16;
case VK_FORMAT_ASTC_8x6_SFLOAT_BLOCK:
case VK_FORMAT_ASTC_8x6_UNORM_BLOCK:
case VK_FORMAT_ASTC_8x6_SRGB_BLOCK:
return 16;
case VK_FORMAT_ASTC_8x8_SFLOAT_BLOCK:
case VK_FORMAT_ASTC_8x8_UNORM_BLOCK:
case VK_FORMAT_ASTC_8x8_SRGB_BLOCK:
return 16;
case VK_FORMAT_ASTC_10x5_SFLOAT_BLOCK:
case VK_FORMAT_ASTC_10x5_UNORM_BLOCK:
case VK_FORMAT_ASTC_10x5_SRGB_BLOCK:
return 16;
case VK_FORMAT_ASTC_10x6_SFLOAT_BLOCK:
case VK_FORMAT_ASTC_10x6_UNORM_BLOCK:
case VK_FORMAT_ASTC_10x6_SRGB_BLOCK:
return 16;
case VK_FORMAT_ASTC_10x8_SFLOAT_BLOCK:
case VK_FORMAT_ASTC_10x8_UNORM_BLOCK:
case VK_FORMAT_ASTC_10x8_SRGB_BLOCK:
return 16;
case VK_FORMAT_ASTC_10x10_SFLOAT_BLOCK:
case VK_FORMAT_ASTC_10x10_UNORM_BLOCK:
case VK_FORMAT_ASTC_10x10_SRGB_BLOCK:
return 16;
case VK_FORMAT_ASTC_12x10_SFLOAT_BLOCK:
case VK_FORMAT_ASTC_12x10_UNORM_BLOCK:
case VK_FORMAT_ASTC_12x10_SRGB_BLOCK:
return 16;
case VK_FORMAT_ASTC_12x12_SFLOAT_BLOCK:
case VK_FORMAT_ASTC_12x12_UNORM_BLOCK:
case VK_FORMAT_ASTC_12x12_SRGB_BLOCK:
return 16;
case VK_FORMAT_G8B8G8R8_422_UNORM:
return 4;
case VK_FORMAT_B8G8R8G8_422_UNORM:
return 4;
case VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM:
return 3;
case VK_FORMAT_G8_B8R8_2PLANE_420_UNORM:
return 3;
case VK_FORMAT_G8_B8_R8_3PLANE_422_UNORM:
return 3;
case VK_FORMAT_G8_B8R8_2PLANE_422_UNORM:
return 3;
case VK_FORMAT_G8_B8_R8_3PLANE_444_UNORM:
return 3;
case VK_FORMAT_R10X6G10X6B10X6A10X6_UNORM_4PACK16:
return 8;
case VK_FORMAT_G10X6B10X6G10X6R10X6_422_UNORM_4PACK16:
return 8;
case VK_FORMAT_B10X6G10X6R10X6G10X6_422_UNORM_4PACK16:
return 8;
case VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_420_UNORM_3PACK16:
return 6;
case VK_FORMAT_G10X6_B10X6R10X6_2PLANE_420_UNORM_3PACK16:
return 6;
case VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_422_UNORM_3PACK16:
return 6;
case VK_FORMAT_G10X6_B10X6R10X6_2PLANE_422_UNORM_3PACK16:
return 6;
case VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_444_UNORM_3PACK16:
return 6;
case VK_FORMAT_R12X4G12X4B12X4A12X4_UNORM_4PACK16:
return 8;
case VK_FORMAT_G12X4B12X4G12X4R12X4_422_UNORM_4PACK16:
return 8;
case VK_FORMAT_B12X4G12X4R12X4G12X4_422_UNORM_4PACK16:
return 8;
case VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_420_UNORM_3PACK16:
return 6;
case VK_FORMAT_G12X4_B12X4R12X4_2PLANE_420_UNORM_3PACK16:
return 6;
case VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_422_UNORM_3PACK16:
return 6;
case VK_FORMAT_G12X4_B12X4R12X4_2PLANE_422_UNORM_3PACK16:
return 6;
case VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_444_UNORM_3PACK16:
return 6;
case VK_FORMAT_G16B16G16R16_422_UNORM:
return 8;
case VK_FORMAT_B16G16R16G16_422_UNORM:
return 8;
case VK_FORMAT_G16_B16_R16_3PLANE_420_UNORM:
return 6;
case VK_FORMAT_G16_B16R16_2PLANE_420_UNORM:
return 6;
case VK_FORMAT_G16_B16_R16_3PLANE_422_UNORM:
return 6;
case VK_FORMAT_G16_B16R16_2PLANE_422_UNORM:
return 6;
case VK_FORMAT_G16_B16_R16_3PLANE_444_UNORM:
return 6;
case VK_FORMAT_PVRTC1_2BPP_UNORM_BLOCK_IMG:
case VK_FORMAT_PVRTC1_2BPP_SRGB_BLOCK_IMG:
return 8;
case VK_FORMAT_PVRTC1_4BPP_UNORM_BLOCK_IMG:
case VK_FORMAT_PVRTC1_4BPP_SRGB_BLOCK_IMG:
return 8;
case VK_FORMAT_PVRTC2_2BPP_UNORM_BLOCK_IMG:
case VK_FORMAT_PVRTC2_2BPP_SRGB_BLOCK_IMG:
return 8;
case VK_FORMAT_PVRTC2_4BPP_UNORM_BLOCK_IMG:
case VK_FORMAT_PVRTC2_4BPP_SRGB_BLOCK_IMG:
return 8;
case VK_FORMAT_G8_B8R8_2PLANE_444_UNORM:
return 3;
case VK_FORMAT_G10X6_B10X6R10X6_2PLANE_444_UNORM_3PACK16:
return 6;
case VK_FORMAT_G12X4_B12X4R12X4_2PLANE_444_UNORM_3PACK16:
return 6;
case VK_FORMAT_G16_B16R16_2PLANE_444_UNORM:
return 6;
case VK_FORMAT_UNDEFINED:
case VK_FORMAT_MAX_ENUM:
return 4;
}
return 4;
}

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);
@ -40,3 +50,4 @@ typedef struct {
void R_VkImageBlit( VkCommandBuffer cmdbuf, const r_vkimage_blit_args *blit_args );
uint32_t R_VkImageFormatTexelBlockSize( VkFormat format );

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

@ -11,33 +11,67 @@
#define MAX_INCLUDE_DEPTH 4
#define MAX_MATERIALS 2048
static r_vk_material_t k_default_material = {
.tex_base_color = -1,
.tex_metalness = 0,
.tex_roughness = 0,
.tex_normalmap = 0,
.tex_base_color = -1,
.tex_metalness = 0,
.tex_roughness = 0,
.tex_normalmap = 0,
.metalness = 0.f,
.roughness = 1.f,
.normal_scale = 1.f,
.base_color = { 1.f, 1.f, 1.f, 1.f },
.metalness = 0.f,
.roughness = 1.f,
.normal_scale = 1.f,
.base_color = { 1.f, 1.f, 1.f, 1.f },
.set = false,
.set = false,
};
/* TODO
enum {
#define X(bit, type, name, key, func) kMatField_##key = (1 << (bit)),
MATERIAL_FIELDS_LIST(X)
#undef X
};
*/
#define MAX_RENDERMODE_MATERIALS 32
typedef struct {
struct {
int tex_id;
r_vk_material_t mat;
} materials[MAX_RENDERMODE_MATERIALS];
r_vk_material_ref_t mat;
} map[MAX_RENDERMODE_MATERIALS];
int count;
} r_vk_material_per_mode_t;
static struct {
r_vk_material_t materials[MAX_TEXTURES];
enum {
kMaterialNotChecked = 0,
kMaterialNoReplacement = -1,
};
r_vk_material_per_mode_t rendermode[kRenderTransAdd+1];
typedef struct {
int mat_id;
// TODO rendermode chain
} texture_to_material_t;
typedef struct {
//int for_tex_id;
string name;
r_vk_material_t material;
} material_entry_t;
static struct {
int count;
material_entry_t table[MAX_MATERIALS];
texture_to_material_t tex_to_mat[MAX_TEXTURES];
// TODO embed into tex_to_mat
r_vk_material_per_mode_t for_rendermode[kRenderTransAdd+1];
// TODO for name
} g_materials;
static struct {
@ -49,9 +83,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;
@ -71,23 +105,66 @@ static void makePath(char *out, size_t out_size, const char *value, const char *
#define MAKE_PATH(out, value) \
makePath(out, sizeof(out), value, path_begin, path_end)
static void loadMaterialsFromFile( const char *filename, int depth ) {
fs_offset_t size;
const char *const path_begin = filename;
const char *path_end = Q_strrchr(filename, '/');
static void printMaterial(int index) {
const char* const name = g_materials.table[index].name;
const r_vk_material_t* const mat = &g_materials.table[index].material;
DEBUG("material[%d] \"%s\" (tbc=%d, tr=%d, tm=%d, tn=%d bc=(%.03f,%.03f,%.03f,%.03f) r=%.03f m=%.03f ns=%.03f",
index, name,
mat->tex_base_color, mat->tex_roughness, mat->tex_metalness, mat->tex_normalmap,
mat->base_color[0], mat->base_color[1], mat->base_color[2], mat->base_color[3],
mat->roughness, mat->metalness, mat->normal_scale
);
}
static int addMaterial(const char *name, const r_vk_material_t* mat) {
if (g_materials.count == MAX_MATERIALS) {
ERR("Max count of materials %d reached", MAX_MATERIALS);
return -1;
}
Q_strncpy(g_materials.table[g_materials.count].name, name, sizeof g_materials.table[g_materials.count].name);
g_materials.table[g_materials.count].material = *mat;
printMaterial(g_materials.count);
return g_materials.count++;
}
static void assignMaterialForTexture(const char *name, int for_tex_id, int mat_id) {
const char* const tex_name = findTexture(for_tex_id)->name;
DEBUG("Assigning material \"%s\" for_tex_id=\"%s\"(%d)", name, tex_name, for_tex_id);
ASSERT(mat_id >= 0);
ASSERT(mat_id < g_materials.count);
ASSERT(for_tex_id < COUNTOF(g_materials.tex_to_mat));
texture_to_material_t* const t2m = g_materials.tex_to_mat + for_tex_id;
if (t2m->mat_id == kMaterialNoReplacement) {
ERR("Texture \"%s\"(%d) has been already queried by something. Only future queries will get the new material", tex_name, for_tex_id);
} else if (t2m->mat_id != kMaterialNotChecked) {
ERR("Texture \"%s\"(%d) already has material assigned, will replace", tex_name, for_tex_id);
}
t2m->mat_id = mat_id;
}
static void loadMaterialsFromFile( const char *filename, int depth ) {
const uint64_t load_file_begin_ns = aprof_time_now_ns();
byte *data = gEngine.fsapi->LoadFile( filename, 0, false );
g_stats.material_file_read_duration_ns += aprof_time_now_ns() - load_file_begin_ns;
char *pos = (char*)data;
r_vk_material_t current_material = k_default_material;
int current_material_index = -1;
int for_tex_id = -1;
int dummy_named_texture_fixme = -1;
qboolean force_reload = false;
qboolean create = false;
qboolean metalness_set = false;
string name;
string basecolor_map, normal_map, metal_map, roughness_map;
//uint32_t fields;
int rendermode = 0;
@ -96,11 +173,14 @@ static void loadMaterialsFromFile( const char *filename, int depth ) {
if ( !data )
return;
const char *const path_begin = filename;
const char *path_end = Q_strrchr(filename, '/');
if ( !path_end )
path_end = path_begin;
else
path_end++;
char *pos = (char*)data;
for (;;) {
char key[1024];
char value[1024];
@ -113,24 +193,34 @@ static void loadMaterialsFromFile( const char *filename, int depth ) {
if (key[0] == '{') {
current_material = k_default_material;
current_material_index = -1;
for_tex_id = -1;
dummy_named_texture_fixme = -1;
force_reload = false;
create = false;
metalness_set = false;
basecolor_map[0] = normal_map[0] = metal_map[0] = roughness_map[0] = '\0';
name[0] = basecolor_map[0] = normal_map[0] = metal_map[0] = roughness_map[0] = '\0';
rendermode = 0;
//fields = 0;
continue;
}
if (key[0] == '}') {
if (current_material_index < 0)
if (for_tex_id < 0 && !create) {
// Skip this material, as its texture hasn't been loaded
// NOTE: might want to check whether it makes sense wrt late-loading stuff
continue;
}
#define LOAD_TEXTURE_FOR(name, field) do { \
if (!name[0]) {
WARN("Unreferenceable (no \"for_texture\", no \"new\") material found in %s", filename);
continue;
}
#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 { \
@ -138,65 +228,76 @@ 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)
current_material.tex_base_color = current_material_index;
current_material.tex_base_color = for_tex_id >= 0 ? for_tex_id : 0;
if (metalness_set && current_material.tex_metalness == tglob.blackTexture) {
// Set metalness texture to white to accommodate explicitly set metalness value
current_material.tex_metalness = tglob.whiteTexture;
}
if (!metalness_set && current_material.tex_metalness != tglob.blackTexture) {
if (!metalness_set && current_material.tex_metalness != tglob.whiteTexture) {
// If metalness factor wasn't set explicitly, but texture was specified, set it to match the texture value.
current_material.metalness = 1.f;
}
DEBUG("Creating%s material for texture %s(%d)", create?" new":"",
findTexture(current_material_index)->name, current_material_index);
const int mat_id = addMaterial(name, &current_material);
// Assign rendermode-specific materials
if (rendermode > 0) {
r_vk_material_per_mode_t* const rm = g_materials.rendermode + rendermode;
if (rm->count == COUNTOF(rm->materials)) {
ERR("Too many rendermode/tex_id mappings");
continue;
}
DEBUG("Adding material %d for rendermode %d", current_material_index, rendermode);
// TODO proper texid-vs-material-index
rm->materials[rm->count].tex_id = current_material_index;
rm->materials[rm->count].mat = current_material;
rm->materials[rm->count].mat.set = true;
rm->count++;
} else {
DEBUG("Creating%s material for texture %s(%d)", create?" new":"",
findTexture(current_material_index)->name, current_material_index);
g_materials.materials[current_material_index] = current_material;
g_materials.materials[current_material_index].set = true;
if (mat_id < 0) {
ERR("Cannot add material \"%s\" for_tex_id=\"%s\"(%d)", name, for_tex_id >= 0 ? findTexture(for_tex_id)->name : "N/A", for_tex_id);
continue;
}
// FIXME have a personal hash map, don't use texture
if (dummy_named_texture_fixme > 0) {
assignMaterialForTexture(name, dummy_named_texture_fixme, mat_id);
}
// Assign from-texture mapping if there's a texture
if (for_tex_id >= 0) {
// Assign rendermode-specific materials
if (rendermode > 0) {
const char* const tex_name = findTexture(for_tex_id)->name;
DEBUG("Adding material \"%s\" for_tex_id=\"%s\"(%d) for rendermode %d", name, tex_name, for_tex_id, rendermode);
r_vk_material_per_mode_t* const rm = g_materials.for_rendermode + rendermode;
if (rm->count == COUNTOF(rm->map)) {
ERR("Too many rendermode/tex_id mappings");
continue;
}
rm->map[rm->count].tex_id = for_tex_id;
rm->map[rm->count].mat.index = mat_id;
rm->count++;
} else {
assignMaterialForTexture(name, for_tex_id, mat_id);
}
}
continue;
}
} // if (key[0] == '}') -- closing material block
pos = COM_ParseFile(pos, value, sizeof(value));
if (!pos)
break;
if (Q_stricmp(key, "for") == 0) {
if (name[0] != '\0')
WARN("Material already has \"new\" or \"for_texture\" old=\"%s\" new=\"%s\"", name, value);
const uint64_t lookup_begin_ns = aprof_time_now_ns();
current_material_index = XVK_FindTextureNamedLike(value);
for_tex_id = XVK_FindTextureNamedLike(value);
g_stats.texture_lookup_duration_ns += aprof_time_now_ns() - lookup_begin_ns;
g_stats.texture_lookups++;
create = false;
Q_strncpy(name, value, sizeof name);
} else if (Q_stricmp(key, "new") == 0) {
current_material_index = XVK_CreateDummyTexture(value);
if (name[0] != '\0')
WARN("Material already has \"new\" or \"for_texture\" old=\"%s\" new=\"%s\"", name, value);
// TODO hash map here, don't depend on textures
dummy_named_texture_fixme = XVK_CreateDummyTexture(value);
Q_strncpy(name, value, sizeof name);
create = true;
} else if (Q_stricmp(key, "force_reload") == 0) {
force_reload = Q_atoi(value) != 0;
@ -212,26 +313,43 @@ static void loadMaterialsFromFile( const char *filename, int depth ) {
int *tex_id_dest = NULL;
if (Q_stricmp(key, "basecolor_map") == 0) {
Q_strncpy(basecolor_map, value, sizeof(basecolor_map));
//fields |= kMatField_basecolor_map;
} else if (Q_stricmp(key, "normal_map") == 0) {
Q_strncpy(normal_map, value, sizeof(normal_map));
//fields |= kMatField_normal_map;
} else if (Q_stricmp(key, "metal_map") == 0) {
Q_strncpy(metal_map, value, sizeof(metal_map));
//fields |= kMatField_metal_map;
} else if (Q_stricmp(key, "roughness_map") == 0) {
Q_strncpy(roughness_map, value, sizeof(roughness_map));
//fields |= kMatField_roughness_map;
} else if (Q_stricmp(key, "inherit") == 0 || Q_stricmp(key, "use") == 0) {
const r_vk_material_ref_t ref = R_VkMaterialGetForName(value);
if (ref.index < 0) {
ERR("In material \"%s\" cannot find material \"%s\" to inherit", name, value);
continue;
}
const r_vk_material_t inherited = R_VkMaterialGetForRef(ref);
current_material = inherited;
} else if (Q_stricmp(key, "roughness") == 0) {
sscanf(value, "%f", &current_material.roughness);
//fields |= kMatField_roughness;
} else if (Q_stricmp(key, "metalness") == 0) {
sscanf(value, "%f", &current_material.metalness);
//fields |= kMatField_metalness;
metalness_set = true;
} else if (Q_stricmp(key, "normal_scale") == 0) {
sscanf(value, "%f", &current_material.normal_scale);
//fields |= kMatField_normal_scale;
} else if (Q_stricmp(key, "base_color") == 0) {
sscanf(value, "%f %f %f %f", &current_material.base_color[0], &current_material.base_color[1], &current_material.base_color[2], &current_material.base_color[3]);
//fields |= kMatField_base_color;
} else if (Q_stricmp(key, "for_rendermode") == 0) {
rendermode = R_VkRenderModeFromString(value);
if (rendermode < 0)
ERR("Invalid rendermode \"%s\"", value);
ASSERT(rendermode < COUNTOF(g_materials.rendermode[0].materials));
ASSERT(rendermode < COUNTOF(g_materials.for_rendermode[0].map));
//fields |= kMatField_rendermode;
} else {
ERR("Unknown material key \"%s\" on line `%.*s`", key, (int)(pos - line_begin), line_begin);
continue;
@ -267,26 +385,27 @@ static int findFilenameExtension(const char *s, int len) {
}
void R_VkMaterialsReload( void ) {
memset(&g_stats, 0, sizeof(g_stats));
const uint64_t begin_time_ns = aprof_time_now_ns();
memset(&g_stats, 0, sizeof(g_stats));
for (int i = 0; i < COUNTOF(g_materials.rendermode); ++i)
g_materials.rendermode[i].count = 0;
g_materials.count = 1;
k_default_material.tex_metalness = tglob.blackTexture;
memset(g_materials.tex_to_mat, 0, sizeof g_materials.tex_to_mat);
for (int i = 0; i < COUNTOF(g_materials.for_rendermode); ++i)
g_materials.for_rendermode[i].count = 0;
// TODO make these texture constants static constants
k_default_material.tex_metalness = tglob.whiteTexture;
k_default_material.tex_roughness = tglob.whiteTexture;
for (int i = 0; i < MAX_TEXTURES; ++i) {
r_vk_material_t *const mat = g_materials.materials + i;
const vk_texture_t *const tex = findTexture( i );
*mat = k_default_material;
if (tex)
mat->tex_base_color = i;
}
// TODO name?
g_materials.table[0].material = k_default_material;
g_materials.table[0].material.tex_base_color = 0;
loadMaterialsFromFile( "pbr/materials.mat", MAX_INCLUDE_DEPTH );
// Load materials by WAD files
{
for(const char *wad = g_map_entities.wadlist; *wad;) {
const char *wad_end = wad;
@ -307,6 +426,7 @@ void R_VkMaterialsReload( void ) {
}
}
// Load materials by map/BSP file
{
const model_t *map = gEngine.pfnGetModelByIndex( 1 );
const char *filename = COM_FileWithoutPath(map->name);
@ -334,31 +454,67 @@ void R_VkMaterialsLoadForModel( const struct model_s* mod ) {
if (mod->type == mod_brush)
return;
// TODO add stats
const char *filename = COM_FileWithoutPath(mod->name);
const int no_ext_len = findFilenameExtension(filename, -1);
loadMaterialsFromFileF("pbr/%s/%.*s.mat", mod->name, no_ext_len, filename);
}
r_vk_material_t R_VkMaterialGetForTexture( int tex_index ) {
//DEBUG("Getting material for tex_id=%d", tex_index);
ASSERT(tex_index >= 0);
ASSERT(tex_index < MAX_TEXTURES);
return g_materials.materials[tex_index];
texture_to_material_t* const t2m = g_materials.tex_to_mat + tex_index;
if (t2m->mat_id > 0) {
ASSERT(t2m->mat_id < g_materials.count);
//DEBUG("Getting material for tex_id=%d", tex_index);
//printMaterial(t2m->mat_id);
return g_materials.table[t2m->mat_id].material;
}
if (t2m->mat_id == kMaterialNotChecked) {
// TODO check for replacement textures named in a predictable way
// If there are, create a new material and assign it here
const char* texname = findTexture(tex_index)->name;
DEBUG("Would try to load texture files by default names of \"%s\"", texname);
// If no PBR textures found, continue using legacy+default ones
t2m->mat_id = kMaterialNoReplacement;
}
r_vk_material_t ret = k_default_material;
ret.tex_base_color = tex_index;
//DEBUG("Returning default material with tex_base_color=%d", tex_index);
return ret;
}
r_vk_material_ref_t R_VkMaterialGetForName( const char *name ) {
// TODO separate material table
// For now it depends on 1-to-1 mapping between materials and textures
return (r_vk_material_ref_t){.index = VK_FindTexture(name)};
// FIXME proper hash table here, don't depend on textures
const int dummy_tex_id_fixme = VK_FindTexture(name);
if (dummy_tex_id_fixme == 0) {
ERR("Material with name \"%s\" not found", name);
return (r_vk_material_ref_t){.index = -1,};
}
ASSERT(dummy_tex_id_fixme >= 0);
ASSERT(dummy_tex_id_fixme < MAX_TEXTURES);
return (r_vk_material_ref_t){.index = g_materials.tex_to_mat[dummy_tex_id_fixme].mat_id};
}
r_vk_material_t R_VkMaterialGetForRef( r_vk_material_ref_t ref ) {
// TODO separate material table
// For now it depends on 1-to-1 mapping between materials and textures
ASSERT(ref.index >= 0);
ASSERT(ref.index < MAX_TEXTURES);
if (ref.index < 0) {
r_vk_material_t ret = k_default_material;
ret.tex_base_color = 0; // Default/error texture
return ret;
}
return g_materials.materials[ref.index];
ASSERT(ref.index < g_materials.count);
return g_materials.table[ref.index].material;
}
qboolean R_VkMaterialGetEx( int tex_id, int rendermode, r_vk_material_t *out_material ) {
@ -370,11 +526,15 @@ qboolean R_VkMaterialGetEx( int tex_id, int rendermode, r_vk_material_t *out_mat
return true;
}
ASSERT(rendermode < COUNTOF(g_materials.rendermode));
const r_vk_material_per_mode_t* const mode = &g_materials.rendermode[rendermode];
// TODO move rendermode-specifit things to by-texid-chains
ASSERT(rendermode < COUNTOF(g_materials.for_rendermode));
const r_vk_material_per_mode_t* const mode = &g_materials.for_rendermode[rendermode];
for (int i = 0; i < mode->count; ++i) {
if (mode->materials[i].tex_id == tex_id) {
*out_material = mode->materials[i].mat;
if (mode->map[i].tex_id == tex_id) {
const int index = mode->map[i].mat.index;
ASSERT(index >= 0);
ASSERT(index < g_materials.count);
*out_material = g_materials.table[index].material;
return true;
}
}

View File

@ -2,6 +2,20 @@
#include "xash3d_types.h"
/* TODO
#define MATERIAL_FIELDS_LIST(X) \
X(0, int, tex_base_color, basecolor_map, readTexture) \
X(1, int, tex_roughness, normal_map, readTexture) \
X(2, int, tex_metalness, metal_map, readTexture) \
X(3, int, tex_normalmap, roughness_map, readTexture) \
X(4, vec4_t, base_color, base_color, readVec4) \
X(5, float, roughness, roughness, readFloat) \
X(6, float, metalness, metalness, readFloat) \
X(7, float, normal_scale, normal_scale, readFloat) \
X(7, int, rendermode, rendermode, readRendermode) \
X(8, int, _inherit, inherit, readInerit) \
*/
typedef struct r_vk_material_s {
int tex_base_color;
int tex_roughness;
@ -26,9 +40,10 @@ void R_VkMaterialsReload( void );
struct model_s;
void R_VkMaterialsLoadForModel( const struct model_s* mod );
r_vk_material_t R_VkMaterialGetForTexture( int tex_id );
qboolean R_VkMaterialGetEx( int tex_id, int rendermode, r_vk_material_t *out_material );
r_vk_material_ref_t R_VkMaterialGetForName( const char *name );
r_vk_material_t R_VkMaterialGetForRef( r_vk_material_ref_t ref );
r_vk_material_t R_VkMaterialGetForTexture( int tex_id );
r_vk_material_t R_VkMaterialGetForTextureChrome( int tex_id );
qboolean R_VkMaterialGetEx( int tex_id, int rendermode, r_vk_material_t *out_material );

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;
@ -172,6 +172,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_) \
@ -240,7 +241,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;
@ -340,6 +341,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) {
@ -348,7 +351,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';
}
}
@ -389,10 +392,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);
@ -417,8 +421,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,
@ -429,13 +436,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 }
}
}
@ -495,7 +499,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;
@ -515,6 +519,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);
@ -579,6 +585,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

@ -8,6 +8,7 @@
#include "vk_mapents.h" // wadlist
#include "vk_combuf.h"
#include "vk_logs.h"
#include "r_speeds.h"
#include "xash3d_mathlib.h"
#include "crtlib.h"
@ -22,18 +23,29 @@
#include <math.h>
#define LOG_MODULE LogModule_Textures
#define MODULE_NAME "textures"
#define TEXTURES_HASH_SIZE (MAX_TEXTURES >> 2)
static vk_texture_t vk_textures[MAX_TEXTURES];
static vk_texture_t* vk_texturesHashTable[TEXTURES_HASH_SIZE];
static uint vk_numTextures;
static uint vk_numTextures;
vk_textures_global_t tglob = {0};
static struct {
struct {
int count;
int size_total;
} stats;
} g_textures;
static void VK_CreateInternalTextures(void);
static VkSampler pickSamplerForFlags( texFlags_t flags );
void initTextures( void ) {
R_SPEEDS_METRIC(g_textures.stats.count, "count", kSpeedsMetricCount);
R_SPEEDS_METRIC(g_textures.stats.size_total, "size_total", kSpeedsMetricBytes);
memset( vk_textures, 0, sizeof( vk_textures ));
memset( vk_texturesHashTable, 0, sizeof( vk_texturesHashTable ));
vk_numTextures = 0;
@ -86,7 +98,9 @@ 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));
for (int i = 0; i < ARRAYSIZE(tglob.samplers); ++i) {
@ -263,9 +277,11 @@ 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 int VK_LoadTextureF(int flags, const char *fmt, ...) {
static int loadTextureInternal( const char *name, const byte *buf, size_t size, int flags, colorspace_hint_e colorspace_hint );
static int VK_LoadTextureF(int flags, colorspace_hint_e colorspace, const char *fmt, ...) {
int tex_id = 0;
char buffer[1024];
va_list argptr;
@ -273,7 +289,7 @@ static int VK_LoadTextureF(int flags, const char *fmt, ...) {
vsnprintf( buffer, sizeof buffer, fmt, argptr );
va_end( argptr );
return VK_LoadTexture(buffer, NULL, 0, flags);
return loadTextureInternal(buffer, NULL, 0, flags, colorspace);
}
#define BLUE_NOISE_NAME_F "bluenoise/LDR_RGBA_%d.png"
@ -322,7 +338,7 @@ static qboolean generateFallbackNoiseTextures(void) {
static qboolean loadBlueNoiseTextures(void) {
int blueNoiseTexturesBegin = -1;
for (int i = 0; i < 64; ++i) {
const int texid = VK_LoadTextureF(TF_NOMIPMAP, BLUE_NOISE_NAME_F, i);
const int texid = VK_LoadTextureF(TF_NOMIPMAP, kColorspaceLinear, BLUE_NOISE_NAME_F, i);
if (blueNoiseTexturesBegin == -1) {
if (texid <= 0) {
@ -417,16 +433,19 @@ 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 );
}
loadBlueNoiseTextures();
}
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;
@ -619,10 +638,12 @@ 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;
// TODO non-rbga textures
for (int i = 0; i < num_layers; ++i) {
@ -658,7 +679,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
@ -670,7 +691,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,
@ -679,10 +700,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);
}
{
@ -720,7 +743,7 @@ static qboolean uploadTexture(vk_texture_t *tex, rgbdata_t *const *const layers,
const int width = Q_max( 1, ( pic->width >> mip ));
const int height = Q_max( 1, ( pic->height >> mip ));
const size_t mip_size = CalcImageSize( pic->type, width, height, 1 );
const uint32_t texel_block_size = 4; // TODO compressed might be different
const uint32_t texel_block_size = R_VkImageFormatTexelBlockSize(format);
const vk_staging_image_args_t staging_args = {
.image = tex->vk.image.image,
.region = (VkBufferImageCopy) {
@ -748,6 +771,8 @@ static qboolean uploadTexture(vk_texture_t *tex, rgbdata_t *const *const layers,
ASSERT(staging.ptr);
memcpy(staging.ptr, buf, mip_size);
tex->total_size += mip_size;
// Build mip in place for the next mip level
if ( mip < mipCount - 1 )
{
@ -786,32 +811,58 @@ 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;
g_textures.stats.count++;
return true;
}
@ -845,18 +896,270 @@ const byte* VK_TextureData( unsigned int texnum )
return NULL;
}
int VK_LoadTexture( const char *name, const byte *buf, size_t size, int flags )
{
vk_texture_t *tex;
rgbdata_t *pic;
uint picFlags = 0;
if( !Common_CheckTexName( name ))
#define KTX_IDENTIFIER_SIZE 12
static const char k_ktx2_identifier[KTX_IDENTIFIER_SIZE] = {
'\xAB', 'K', 'T', 'X', ' ', '2', '0', '\xBB', '\r', '\n', '\x1A', '\n'
};
typedef struct {
uint32_t vkFormat;
uint32_t typeSize;
uint32_t pixelWidth;
uint32_t pixelHeight;
uint32_t pixelDepth;
uint32_t layerCount;
uint32_t faceCount;
uint32_t levelCount;
uint32_t supercompressionScheme;
} ktx_header_t;
typedef struct {
uint32_t dfdByteOffset;
uint32_t dfdByteLength;
uint32_t kvdByteOffset;
uint32_t kvdByteLength;
uint64_t sgdByteOffset;
uint64_t sgdByteLength;
} ktx_index_t;
typedef struct {
uint64_t byteOffset;
uint64_t byteLength;
uint64_t uncompressedByteLength;
} ktx_level_t;
static int loadKtx2( const char *name ) {
fs_offset_t size = 0;
byte *data = gEngine.fsapi->LoadFile( name, &size, false );
DEBUG("Loading KTX2 file \"%s\", exists=%d", name, data != 0);
if ( !data )
return 0;
// see if already loaded
if(( tex = Common_TextureForName( name )))
return (tex - vk_textures);
const ktx_header_t* header;
const ktx_index_t* index;
const ktx_level_t* levels;
vk_texture_t* tex = NULL;
if (size < (sizeof k_ktx2_identifier + sizeof(ktx_header_t) + sizeof(ktx_index_t) + sizeof(ktx_level_t))) {
ERR("KTX2 file \"%s\" seems truncated", name);
goto fail;
}
if (memcmp(data, k_ktx2_identifier, sizeof k_ktx2_identifier) != 0) {
ERR("KTX2 file \"%s\" identifier is invalid", name);
goto fail;
}
header = (const ktx_header_t*)(data + sizeof k_ktx2_identifier);
index = (const ktx_index_t*)(data + sizeof k_ktx2_identifier + sizeof(ktx_header_t));
levels = (const ktx_level_t*)(data + sizeof k_ktx2_identifier + sizeof(ktx_header_t) + sizeof(ktx_index_t));
DEBUG("KTX2 file \"%s\"", name);
DEBUG(" header:");
#define X(field) DEBUG(" " # field "=%d", header->field);
DEBUG(" vkFormat = %s(%d)", R_VkFormatName(header->vkFormat), header->vkFormat);
X(typeSize)
X(pixelWidth)
X(pixelHeight)
X(pixelDepth)
X(layerCount)
X(faceCount)
X(levelCount)
X(supercompressionScheme)
#undef X
DEBUG(" index:");
#define X(field) DEBUG(" " # field "=%llu", (unsigned long long)index->field);
X(dfdByteOffset)
X(dfdByteLength)
X(kvdByteOffset)
X(kvdByteLength)
X(sgdByteOffset)
X(sgdByteLength)
#undef X
for (int mip = 0; mip < header->levelCount; ++mip) {
const ktx_level_t* const level = levels + mip;
DEBUG(" level[%d]:", mip);
DEBUG(" byteOffset=%llu", (unsigned long long)level->byteOffset);
DEBUG(" byteLength=%llu", (unsigned long long)level->byteLength);
DEBUG(" uncompressedByteLength=%llu", (unsigned long long)level->uncompressedByteLength);
}
{
const uint32_t flags = 0;
tex = Common_AllocTexture( name, flags );
if (!tex)
goto fail;
}
// FIXME check that format is supported
// FIXME layers == 0
// FIXME has_alpha
// FIXME no supercompressionScheme
// 1. Create image
{
const r_vk_image_create_t create = {
.debug_name = tex->name,
.width = header->pixelWidth,
.height = header->pixelHeight,
.mips = header->levelCount,
.layers = 1, // TODO or 6 for cubemap; header->faceCount
.format = header->vkFormat,
.tiling = VK_IMAGE_TILING_OPTIMAL,
.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT,
// FIXME find out if there's alpha
.flags = 0,
};
tex->vk.image = R_VkImageCreate(&create);
}
// 2. Prep cmdbuf, barrier, etc
{
VkImageMemoryBarrier image_barrier = {
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.image = tex->vk.image.image,
.srcAccessMask = 0,
.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED,
.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
.subresourceRange = (VkImageSubresourceRange) {
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.baseMipLevel = 0,
.levelCount = header->levelCount,
.baseArrayLayer = 0,
.layerCount = 1, // TODO cubemap
}
};
{
// cmdbuf may become invalidated in locks in the loops below
const VkCommandBuffer cmdbuf = R_VkStagingGetCommandBuffer();
vkCmdPipelineBarrier(cmdbuf,
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT,
0, 0, NULL, 0, NULL, 1, &image_barrier);
}
// 3. For levels
// 3.1 upload
for (int mip = 0; mip < header->levelCount; ++mip) {
const ktx_level_t* const level = levels + mip;
const uint32_t width = Q_max(1, header->pixelWidth >> mip);
const uint32_t height = Q_max(1, header->pixelHeight >> mip);
const size_t mip_size = level->byteLength;
const uint32_t texel_block_size = R_VkImageFormatTexelBlockSize(header->vkFormat);
const void* const image_data = data + level->byteOffset;
const vk_staging_image_args_t staging_args = {
.image = tex->vk.image.image,
.region = (VkBufferImageCopy) {
.bufferOffset = 0,
.bufferRowLength = 0,
.bufferImageHeight = 0,
.imageSubresource = (VkImageSubresourceLayers){
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.mipLevel = mip,
.baseArrayLayer = 0, // TODO cubemap
.layerCount = 1,
},
.imageExtent = (VkExtent3D){
.width = width,
.height = height,
.depth = 1,
},
},
.layout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
.size = mip_size,
.alignment = texel_block_size,
};
{
const vk_staging_region_t staging = R_VkStagingLockForImage(staging_args);
ASSERT(staging.ptr);
memcpy(staging.ptr, image_data, mip_size);
tex->total_size += mip_size;
R_VkStagingUnlock(staging.handle);
}
} // for levels
{
// TODO Don't change layout here. Alternatively:
// I. Attach layout metadata to the image, and request its change next time it is used.
// II. Build-in layout transfer to staging commit and do it there on commit.
const VkCommandBuffer cmdbuf = R_VkStagingCommit()->cmdbuf;
// 5.2 image:layout:DST -> image:layout:SAMPLED
// 5.2.1 transitionToLayout(DST -> SHADER_READ_ONLY)
image_barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
image_barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
image_barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
image_barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
image_barrier.subresourceRange = (VkImageSubresourceRange){
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.baseMipLevel = 0,
.levelCount = header->levelCount,
.baseArrayLayer = 0,
.layerCount = 1, // TODO cubemap
};
vkCmdPipelineBarrier(cmdbuf,
VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, // FIXME incorrect, we also use them in compute and potentially ray tracing shaders
0, 0, NULL, 0, NULL, 1, &image_barrier);
}
}
// 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;
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){
.imageView = tex->vk.image.view,
.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
.sampler = pickSamplerForFlags( tex->flags ),
};
const VkWriteDescriptorSet wds[] = { {
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
.dstBinding = 0,
.dstArrayElement = 0,
.descriptorCount = 1,
.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
.pImageInfo = dii_tex,
.dstSet = vk_desc.sets[vk_desc.next_free++],
}};
vkUpdateDescriptorSets(vk_core.device, ARRAYSIZE(wds), wds, 0, NULL);
}
g_textures.stats.size_total += tex->total_size;
g_textures.stats.count++;
tex->width = header->pixelWidth;
tex->height = header->pixelHeight;
goto finalize;
fail:
if (tex)
memset( tex, 0, sizeof( vk_texture_t ));
finalize:
Mem_Free( data );
return (tex - vk_textures);
}
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 ))
SetBits( picFlags, IL_DONTFLIP_TGA );
@ -867,16 +1170,16 @@ int VK_LoadTexture( const char *name, const byte *buf, size_t size, int flags )
// set some image flags
gEngine.Image_SetForceFlags( picFlags );
pic = gEngine.FS_LoadImage( name, buf, size );
rgbdata_t *const pic = gEngine.FS_LoadImage( name, buf, size );
if( !pic ) return 0; // couldn't loading image
// allocate the new one
tex = Common_AllocTexture( name, flags );
vk_texture_t* const tex = Common_AllocTexture( name, flags );
// 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
@ -892,17 +1195,43 @@ int VK_LoadTexture( const char *name, const byte *buf, size_t size, int flags )
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 )
@ -971,7 +1300,9 @@ 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));
tglob.dii_all_textures[texnum] = (VkDescriptorImageInfo){
@ -1009,7 +1340,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;
@ -1037,7 +1368,9 @@ 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));
}
@ -1133,7 +1466,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,12 +16,14 @@ 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;
struct vk_texture_s *nextHash;
int total_size;
} vk_texture_t;
#define MAX_LIGHTMAPS 256
@ -54,6 +56,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
@ -73,18 +76,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, ...);