diff --git a/engine/client/ref_common.h b/engine/client/ref_common.h index 687ae566..d16cd1fd 100644 --- a/engine/client/ref_common.h +++ b/engine/client/ref_common.h @@ -41,7 +41,7 @@ void R_GetTextureParms( int *w, int *h, int texnum ); #define REF_GET_PARM( parm, arg ) ref.dllFuncs.RefGetParm( (parm), (arg) ) #define GL_LoadTextureInternal( name, pic, flags ) ref.dllFuncs.GL_LoadTextureFromBuffer( (name), (pic), (flags), false ) #define GL_UpdateTextureInternal( name, pic, flags ) ref.dllFuncs.GL_LoadTextureFromBuffer( (name), (pic), (flags), true ) -#define R_GetBuiltinTexture( name ) ref.dllFuncs.GL_LoadTexture( (name), 0, 0, 0 ) +#define R_GetBuiltinTexture( name ) ref.dllFuncs.GL_FindTexture( (name) ) void GL_RenderFrame( const struct ref_viewpass_s *rvp ); diff --git a/ref/vk/NOTES.md b/ref/vk/NOTES.md index 3c318cb5..d5c0c562 100644 --- a/ref/vk/NOTES.md +++ b/ref/vk/NOTES.md @@ -635,3 +635,48 @@ For multiple replacements: - mat_id - name_to_material[] -- string "name" to mat_id - hash table of some sorts + +# 2023-10-16 E313 +## Pre-next: +- validation crash +## Next: +- KTX2 PR against upstream +- texture leaks + - better texture storage + - hash map + - texture lifetimes/refcounts + - texture deletion + - mass (for single device wait idle) + +# 2023-10-17 E314 +- [x] imagelib/ktx2 PR to upstream + 1. [x] Make a vulkan branch with the latest upstream merged in + 2. [x] Make another branch `upstream-ktx2` from upstream/master with imagelib changes hand-picked + 3. [x] Make a PR against upstream with ktx2 + 4. [x] Make a PR against vulkan with recentmost upstream + +- [x] Contemplate texture storage + +# 2023-10-19 E315 +Tried refcounts. They only break things, many textures get released prematurely. +Hunch: need to split external/refapi refcount-unaware functionality and hash map into two things: +- old name->gl_texturenum table that is refcount-unaware +- new name->vk.image refcount-aware table and api + +# 2023-10-20 E316 +## Texture mgmt refcount impedance mismatch: losing textures on changelevel +1. ref_interface_t api is refcount-oblivious: it creates and destroys textures directly. Can't really update refcounts in these calls because they are not balanced at all. Can potentially call Load on the same texture twice, and then delete it only once. (Can it delete the same texture multiple times? Probably not -- need index, which *SHOULD* be inaccessible after the first delte by the API logic -- this is broken now with refcounts) +2. Sequence of events: + 1. Changlevel initiated + 2. Textures for the old map are deleted using ref_interface api + - mostly us in ref_vk (so we can adjust), but there are a few possible calls from the engine and game.dll + - brings refcount down to "1" (many textures are still referenced from materials) + 3. Texture for the new map are created using ref_interface api + - There are common textures that weren't deleted due to being referenced by materials, so they are considered being uploaded already. + - ref_interface_t is refcount-oblivious, so refcounts are not updated, i.e. remaining =1. + 4. ref_vk finally notices the changelevel, and starts reloading materials. + - goes through the list of all old materials and releases all the textures + - old textures (but which should've been loaded for the new map too) with refcount=1 are deleted + - ;_; + + diff --git a/ref/vk/TODO.md b/ref/vk/TODO.md index a3f9a484..5b6a8110 100644 --- a/ref/vk/TODO.md +++ b/ref/vk/TODO.md @@ -1,3 +1,71 @@ +# 2023-10-31 E322 +- [x] load png blue noise files +- [-] translucent animated thing -> needs shader rework +- [x] massage texture code + - [x] single return/goto cleanup + - [-] pass args via structs? -> not necessary + - [-] collapse texture uploading into a single function -> not necessary, they are different enough +- [ ] merge materials PR + +# 2023-10-30 E321 +- [x] missing skybox +- [x] explicitly free default textures; and complain about any leftovers +- [x] use the new hash table in materials too, remove dummy textures +- [x] why are there references to \*unused +- [ ] restore blue noise + - [x] vk_texture_t blue_noise; 3d texture + - [x] separate binding similar to skybox in vk_rtx.c and shaders + - [x] patch shader function + - [ ] load 64xpngs into a single big pic + +# 2023-10-27 E320 +- [x] fix windows build +- [x] track texture visibility for ref_api via flag and refcounts +- [ ] devmem assert, not all textures are destroyed in wagonchik + - [ ] new material names+fixme => move to material hash table + - [x] preallocated default textures +- [x] check urmom stats after a few different changelevels + - [x] COUNT(IS_DELETED) + - [x] clusters size histogram +- [x] silence logs + - [x] "accessing empty texture" + - [x] "found existing texture" +- [x] check mips + +# 2023-10-26 E319 +- [x] fix pbr materials disappearing +- [x] fix surface lights +- [ ] pbr/material refcount leaks + - [ ] track texture visibility for ref_api +- [x] handle existing image on texture upload + - [x] sanely recreate + - [x] reuse if possible +- [x] case insensitive hash table + +# 2023-10-24 E318 +- [ ] use new hashmap for textures + - [x] use vk_texure array directly as open addressing hash table + - [x] Completely hide `struct vk_texture` + - [x] just try + - [x] texture indexes are no longer consecutive + - [ ] blue noise texture breaks => make it a separate (3d) thing + - [ ] index=0 is now valid + - [x] I. mark 0 as occupied to avoid allocating it + - [ ] II. Increase all returned indexes by 1. Then dec it back wherever it is passed back + - (SAD): cannot make builtin textures have stable indexes anymore + +# E313 +## Pre-next: +- validation crash +## Next: +- KTX2 PR against upstream +- texture leaks + - better texture storage + - hash map + - texture lifetimes/refcounts + - texture deletion + - mass (for single device wait idle) + # Programmable render - [ ] what if new meatpipe has different image format for a creatable image? - [ ] implicit dependency tracking. pass defines: diff --git a/ref/vk/alolcator.c b/ref/vk/alolcator.c index b607c922..6a9c7ca3 100644 --- a/ref/vk/alolcator.c +++ b/ref/vk/alolcator.c @@ -160,7 +160,10 @@ static int splitBlockAt(pool_t *blocks, int index, alo_size_t at) { alo_block_t aloPoolAllocate(struct alo_pool_s* pool, alo_size_t size, alo_size_t alignment) { alo_block_t ret = {.offset = ALO_ALLOC_FAILED}; block_t *b; + ASSERT(size > 0); + alignment = alignment > pool->min_alignment ? alignment : pool->min_alignment; + for (int i = pool->first_block; i >= 0; i = b->next) { b = poolGet(&pool->blocks, i); if (b->flags != BlockFlag_Empty) diff --git a/ref/vk/data/valve/luchiki/maps/c0a0c.patch b/ref/vk/data/valve/luchiki/maps/c0a0c.patch index 2195abb9..eaf6f15f 100644 --- a/ref/vk/data/valve/luchiki/maps/c0a0c.patch +++ b/ref/vk/data/valve/luchiki/maps/c0a0c.patch @@ -83,11 +83,11 @@ } { "_xvk_surface_id" "3728 3740 3771 3783" // ~SPOTRED -"_xvk_texture" "+A~GENERIC86B" // red->green +"_xvk_material" "+A~GENERIC86B" // red->green } { "_xvk_surface_id" "3722 3734 3765 3777" // +A~GENERIC86B -"_xvk_texture" "~SPOTRED" // green->red +"_xvk_material" "~SPOTRED" // green->red } diff --git a/ref/vk/data/valve/luchiki/maps/c0a0d.patch b/ref/vk/data/valve/luchiki/maps/c0a0d.patch index 1ded2fee..43ebd265 100644 --- a/ref/vk/data/valve/luchiki/maps/c0a0d.patch +++ b/ref/vk/data/valve/luchiki/maps/c0a0d.patch @@ -22,7 +22,7 @@ //{ // testing //"_xvk_surface_id" "980 975 872 881 999 885 886 1883 1852 1763 1709 1723 1724 1750 1728" // !TOXICGRN2 20 255 0 350 -//"_xvk_texture" "" +//"_xvk_material" "" //} diff --git a/ref/vk/data/valve/luchiki/maps/c1a0d.patch b/ref/vk/data/valve/luchiki/maps/c1a0d.patch index 4207317a..06c46c86 100644 --- a/ref/vk/data/valve/luchiki/maps/c1a0d.patch +++ b/ref/vk/data/valve/luchiki/maps/c1a0d.patch @@ -25,7 +25,7 @@ // mirror in toilet { "_xvk_surface_id" "2057" -"_xvk_texture" "mirror" +"_xvk_material" "mirror" } diff --git a/ref/vk/data/valve/luchiki/maps/c1a1.patch b/ref/vk/data/valve/luchiki/maps/c1a1.patch index 815004ae..bf2dbe73 100644 --- a/ref/vk/data/valve/luchiki/maps/c1a1.patch +++ b/ref/vk/data/valve/luchiki/maps/c1a1.patch @@ -32,7 +32,7 @@ { // TODO: animate texture "_xvk_surface_id" "1183" // +0~DRKMTLS1 205 0 0 6000 "_light" "205 0 0 10000" -//"_xvk_texture" "RED1_ANIMATE" +//"_xvk_material" "RED1_ANIMATE" } { "_xvk_ent_id" "55 54 53 52 51" // remove hack lights entity diff --git a/ref/vk/data/valve/luchiki/maps/c1a1a.patch b/ref/vk/data/valve/luchiki/maps/c1a1a.patch index c5a3be7d..abfe6e31 100644 --- a/ref/vk/data/valve/luchiki/maps/c1a1a.patch +++ b/ref/vk/data/valve/luchiki/maps/c1a1a.patch @@ -13,5 +13,5 @@ // mirror in toilet { "_xvk_surface_id" "1936 1934 1933" -"_xvk_texture" "mirror_broken" +"_xvk_material" "mirror_broken" } diff --git a/ref/vk/data/valve/luchiki/maps/c1a1b.patch b/ref/vk/data/valve/luchiki/maps/c1a1b.patch index 800e01e5..db5cfd8d 100644 --- a/ref/vk/data/valve/luchiki/maps/c1a1b.patch +++ b/ref/vk/data/valve/luchiki/maps/c1a1b.patch @@ -5,7 +5,7 @@ { // TODO: animate texture "_xvk_surface_id" "3391 3407" // +0~FIFTS_LGHT4 160 170 220 4000 "_light" "160 170 220 5000" -//"_xvk_texture" "WHITE1_ANIMATE" +//"_xvk_material" "WHITE1_ANIMATE" } // section 1 diff --git a/ref/vk/data/valve/luchiki/maps/c1a1f.patch b/ref/vk/data/valve/luchiki/maps/c1a1f.patch index 6169c734..d22ad2d6 100644 --- a/ref/vk/data/valve/luchiki/maps/c1a1f.patch +++ b/ref/vk/data/valve/luchiki/maps/c1a1f.patch @@ -48,7 +48,7 @@ { "_xvk_surface_id" "4592 4593 4594 4591 4589" // remove additive "god rays" fade2 -"_xvk_texture" "" +"_xvk_material" "" } // section 4 @@ -103,47 +103,47 @@ // fix wrong textures with texture coordinates { "_xvk_surface_id" "2363 2352 2351 2386 2389 2562 1002 1003 1013 1014" -"_xvk_texture" "crete4_flr03" +"_xvk_material" "crete4_flr03" "_xvk_tex_offset" "20 -15.5" "_xvk_tex_scale" "0.5 1" } { // i tak soydet "_xvk_surface_id" "2362" -"_xvk_texture" "crete4_flr03" +"_xvk_material" "crete4_flr03" "_xvk_tex_offset" "10 16" "_xvk_tex_scale" "0.5 1" } { // i tak soydet "_xvk_surface_id" "2563" -"_xvk_texture" "crete4_flr03" +"_xvk_material" "crete4_flr03" "_xvk_tex_offset" "10 -15.5" "_xvk_tex_scale" "0.5 1" } { "_xvk_surface_id" "999" // LITEPANEL1 -"_xvk_texture" "+ALAB1_W6" +"_xvk_material" "+ALAB1_W6" "_light" "0 0 0 0" "_xvk_tex_offset" "24 1" "_xvk_tex_scale" "2.5 0.3" // TODO: rotate at 90 } { "_xvk_surface_id" "1010" // LITEPANEL1 -"_xvk_texture" "+0LAB1_W6" +"_xvk_material" "+0LAB1_W6" "_light" "255 255 255 4000" "_xvk_tex_offset" "24 7" "_xvk_tex_scale" "2.5 0.3" // TODO: rotate at 90 } { "_xvk_surface_id" "975 2354" // +0~FIFTS_LGHT01 -"_xvk_texture" "+0~FIFTIES_LGT2" +"_xvk_material" "+0~FIFTIES_LGT2" "_light" "255 255 255 3000" "_xvk_tex_offset" "42 20" "_xvk_tex_scale" "2 1.2" } { "_xvk_surface_id" "974 2357" // +0~FIFTS_LGHT01 -"_xvk_texture" "+0~FIFTIES_LGT2" +"_xvk_material" "+0~FIFTIES_LGT2" "_light" "255 255 255 3000" "_xvk_tex_offset" "42 -18" "_xvk_tex_scale" "2 1.2" @@ -157,14 +157,14 @@ } { "_xvk_surface_id" "796 795 807 800 809 808 805 801 796 780" -"_xvk_texture" "generic_metal1" // TODO: texture -> material +"_xvk_material" "generic_metal1" // TODO: texture -> material } { "_xvk_smoothing_group" "766 765 772 773 777 776 768 769" } { "_xvk_surface_id" "766 765 772 773 777 776 768 769" -"_xvk_texture" "generic_metal1" // TODO: texture -> material +"_xvk_material" "generic_metal1" // TODO: texture -> material } { diff --git a/ref/vk/data/valve/luchiki/maps/c1a2a.patch b/ref/vk/data/valve/luchiki/maps/c1a2a.patch index 6ee9ac2f..d7401e16 100644 --- a/ref/vk/data/valve/luchiki/maps/c1a2a.patch +++ b/ref/vk/data/valve/luchiki/maps/c1a2a.patch @@ -23,7 +23,7 @@ { "_xvk_surface_id" "2412" // generic88a -//"_xvk_texture" "black" // fix texture +//"_xvk_material" "black" // fix texture "_light" "0 0 0 0" } diff --git a/ref/vk/data/valve/luchiki/maps/c1a2b.patch b/ref/vk/data/valve/luchiki/maps/c1a2b.patch index 18f1ab0c..23cf917e 100644 --- a/ref/vk/data/valve/luchiki/maps/c1a2b.patch +++ b/ref/vk/data/valve/luchiki/maps/c1a2b.patch @@ -61,7 +61,7 @@ //{ // TODO: fix 2 side texture coordinates //"_xvk_surface_id" "7011 7010" // {gratestep2 -//"_xvk_texture" "" // remove broken texture +//"_xvk_material" "" // remove broken texture //} { // fix 2 side texture coordinates diff --git a/ref/vk/data/valve/luchiki/maps/c1a3.patch b/ref/vk/data/valve/luchiki/maps/c1a3.patch index 294bfdfa..5944936b 100644 --- a/ref/vk/data/valve/luchiki/maps/c1a3.patch +++ b/ref/vk/data/valve/luchiki/maps/c1a3.patch @@ -4,7 +4,7 @@ } //{ //"_xvk_surface_id" "3875 3874" // BLACK -//"_xvk_texture" "c1a3yellow" +//"_xvk_material" "c1a3yellow" //} // alarm lights diff --git a/ref/vk/infotool.c b/ref/vk/infotool.c index 68847a7e..d1b12f3d 100644 --- a/ref/vk/infotool.c +++ b/ref/vk/infotool.c @@ -1,7 +1,7 @@ #include "camera.h" #include "vk_math.h" #include "vk_common.h" -#include "vk_textures.h" +#include "r_textures.h" #include "vk_brush.h" #include "vk_light.h" @@ -65,14 +65,14 @@ void XVK_CameraDebugPrintCenterEntity( void ) { if (surf && ent && ent->model && ent->model->surfaces) { const int surface_index = surf - ent->model->surfaces; - const texture_t *current_tex = R_TextureAnimation(ent, surf, NULL); + const texture_t *current_tex = R_TextureAnimation(ent, surf); const int tex_id = current_tex->gl_texturenum; - const vk_texture_t* const texture = findTexture( tex_id ); + const char *const tex_name = R_TextureGetNameByIndex( tex_id ); const texture_t *tex = surf->texinfo->texture; p += Q_snprintf(p, end - p, "surface index: %d; texture: %s(%d)\n", - surface_index, texture ? texture->name : "NONE", tex_id + surface_index, tex_name ? tex_name : "NONE", tex_id ); if (tex->anim_total > 0 && tex->anim_next) { @@ -80,9 +80,9 @@ void XVK_CameraDebugPrintCenterEntity( void ) { p += Q_snprintf(p, end - p, "anim textures chain (%d):\n", tex->anim_total); for (int i = 0; i < tex->anim_total && tex; ++i) { - const vk_texture_t *vkt = findTexture(tex->gl_texturenum); + const char* const texname = R_TextureGetNameByIndex(tex->gl_texturenum); p += Q_snprintf(p, end - p, - "%d: %s(%d)%s\n", i, vkt ? vkt->name : "NONE", tex->gl_texturenum, tex == current_tex ? " <-" : " "); + "%d: %s(%d)%s\n", i, texname ? texname : "NONE", tex->gl_texturenum, tex == current_tex ? " <-" : " "); tex = tex->anim_next; } } diff --git a/ref/vk/r_textures.c b/ref/vk/r_textures.c new file mode 100644 index 00000000..60dae9ea --- /dev/null +++ b/ref/vk/r_textures.c @@ -0,0 +1,1044 @@ +#include "r_textures.h" +#include "vk_textures.h" + +#include "vk_common.h" +#include "vk_const.h" +#include "vk_mapents.h" // wadlist +#include "vk_logs.h" +#include "r_speeds.h" +#include "profiler.h" +#include "unordered_roadmap.h" + +#include "xash3d_mathlib.h" +#include "crtlib.h" +#include "crclib.h" // COM_HashKey +#include "com_strings.h" +#include "eiface.h" // ARRAYSIZE + +#include +#include + +#define LOG_MODULE LogModule_Textures +#define MODULE_NAME "textures" + +vk_textures_global_t tglob = {0}; + +static struct { + poolhandle_t mempool; + + vk_texture_t all[MAX_TEXTURES]; + urmom_desc_t all_desc; +} g_textures; + +// FIXME imported from vk_textures.h +void unloadSkybox( void ); + +static void createDefaultTextures( void ); +static void destroyDefaultTextures( void ); +static void destroyTexture( uint texnum ); + +#define R_TextureUploadFromBufferNew(name, pic, flags) R_TextureUploadFromBuffer(name, pic, flags, /*update_only=*/false) + +qboolean R_TexturesInit( void ) { + g_textures.mempool = Mem_AllocPool( "vktextures" ); + + g_textures.all_desc = (urmom_desc_t){ + .array = g_textures.all, + .count = COUNTOF(g_textures.all), + .item_size = sizeof(g_textures.all[0]), + .type = kUrmomStringInsensitive, + }; + + urmomInit(&g_textures.all_desc); + + // Mark index 0 as occupied to have a special "no texture" value + g_textures.all[0].hdr_.hash = 0x7fffffff; + g_textures.all[0].hdr_.state = 1; + Q_strncpy( g_textures.all[0].hdr_.key, "*unused*", sizeof(g_textures.all[0].hdr_.key)); + + createDefaultTextures(); + + if (!R_VkTexturesInit()) + return false; + + return true; +} + +void R_TexturesShutdown( void ) +{ + destroyDefaultTextures(); + + // By this point ideally all texture should have been destroyed. + // However, there are two possible ways some texture could have been left over: + // 1. Our coding mistakes, not releasing textures when done + // 2. Engine and other external things not cleaning up (e.g. mainui is known to leave textures) + for( int i = 1; i < COUNTOF(g_textures.all); i++ ) { + const vk_texture_t *const tex = g_textures.all + i; + if (!URMOM_IS_OCCUPIED(tex->hdr_)) + continue; + + // Try to free external textures + R_TextureFree( i ); + + // If it is still not deleted, complain loudly + if (URMOM_IS_OCCUPIED(tex->hdr_)) { + // TODO consider ASSERT, as this is a coding mistake + ERR("stale texture[%d] '%s' refcount=%d", i, TEX_NAME(tex), tex->refcount); + destroyTexture( i ); + } + } + + int is_deleted_count = 0; + int clusters[16] = {0}; + int current_cluster_begin = -1; + for( int i = 1; i < COUNTOF(g_textures.all); i++ ) { + const vk_texture_t *const tex = g_textures.all + i; + + if (URMOM_IS_EMPTY(tex->hdr_)) { + if (current_cluster_begin >= 0) { + const int cluster_length = i - current_cluster_begin; + clusters[cluster_length >= COUNTOF(clusters) ? 0 : cluster_length]++; + } + current_cluster_begin = -1; + } else { + if (current_cluster_begin < 0) + current_cluster_begin = i; + } + + if (URMOM_IS_DELETED(tex->hdr_)) + ++is_deleted_count; + + ASSERT(!URMOM_IS_OCCUPIED(tex->hdr_)); + } + + // TODO handle wraparound clusters + if (current_cluster_begin >= 0) { + const int cluster_length = COUNTOF(g_textures.all) - current_cluster_begin; + clusters[cluster_length >= COUNTOF(clusters) ? 0 : cluster_length]++; + } + + DEBUG("Deleted slots in texture hash table: %d", is_deleted_count); + for (int i = 1; i < COUNTOF(clusters); ++i) + DEBUG("Texture hash table cluster[%d] = %d", i, clusters[i]); + + DEBUG("Clusters longer than %d: %d", (int)COUNTOF(clusters)-1, clusters[0]); + + R_VkTexturesShutdown(); +} + +/* OBSOLETE +static vk_texture_t *Common_AllocTexture( const char *name, texFlags_t flags ) +{ + vk_texture_t *tex; + uint i; + + // find a free texture_t slot + for( i = 0, tex = vk_textures; i < vk_numTextures; i++, tex++ ) + if( !tex->name[0] ) break; + + if( i == vk_numTextures ) + { + if( vk_numTextures == MAX_TEXTURES ) + gEngine.Host_Error( "VK_AllocTexture: MAX_TEXTURES limit exceeds\n" ); + vk_numTextures++; + } + + tex = &vk_textures[i]; + + // copy initial params + Q_strncpy( tex->name, name, sizeof( tex->name )); + tex->texnum = i; // texnum is used for fast acess into vk_textures array too + tex->flags = flags; + + // add to hash table + tex->hashValue = COM_HashKey( name, TEXTURES_HASH_SIZE ); + tex->nextHash = vk_texturesHashTable[tex->hashValue]; + vk_texturesHashTable[tex->hashValue] = tex; + + // FIXME this is not strictly correct. Refcount management should be done differently wrt public ref_interface_t + tex->refcount = 1; + return tex; +} + +static vk_texture_t *Common_TextureForName( const char *name ) +{ + vk_texture_t *tex; + uint hash; + + // find the texture in array + hash = COM_HashKey( name, TEXTURES_HASH_SIZE ); + + for( tex = vk_texturesHashTable[hash]; tex != NULL; tex = tex->nextHash ) + { + if( !Q_stricmp( tex->name, name )) + return tex; + } + + return NULL; +} +*/ + +static qboolean checkTextureName( const char *name ) +{ + int len; + + if( !COM_CheckString( name )) + return false; + + len = Q_strlen( name ); + + // because multi-layered textures can exceed name string + if( len >= sizeof( g_textures.all[0].hdr_.key )) + { + ERR("LoadTexture: too long name %s (%d)", name, len ); + return false; + } + + return true; +} + +static rgbdata_t *Common_FakeImage( int width, int height, int depth, int flags ) +{ + // TODO: Fix texture and it's buffer leaking. + rgbdata_t *r_image = Mem_Malloc( g_textures.mempool, sizeof( rgbdata_t ) ); + + // also use this for bad textures, but without alpha + r_image->width = Q_max( 1, width ); + r_image->height = Q_max( 1, height ); + r_image->depth = Q_max( 1, depth ); + r_image->flags = flags; + r_image->type = PF_RGBA_32; + + r_image->size = r_image->width * r_image->height * r_image->depth * 4; + if( FBitSet( r_image->flags, IMAGE_CUBEMAP )) r_image->size *= 6; + + r_image->buffer = Mem_Malloc( g_textures.mempool, r_image->size); + r_image->palette = NULL; + r_image->numMips = 1; + r_image->encode = 0; + + memset( r_image->buffer, 0xFF, r_image->size ); + + return r_image; +} + +static void createDefaultTextures( void ) +{ + int dx2, dy, d; + int x, y; + rgbdata_t *pic; + + // emo-texture from quake1 + pic = Common_FakeImage( 16, 16, 1, IMAGE_HAS_COLOR ); + + for( y = 0; y < 16; y++ ) + { + for( x = 0; x < 16; x++ ) + { + if(( y < 8 ) ^ ( x < 8 )) + ((uint *)pic->buffer)[y*16+x] = 0xFFFF00FF; + else ((uint *)pic->buffer)[y*16+x] = 0xFF000000; + } + } + + tglob.defaultTexture = R_TextureUploadFromBufferNew( REF_DEFAULT_TEXTURE, pic, TF_COLORMAP ); + + // particle texture from quake1 + pic = Common_FakeImage( 16, 16, 1, IMAGE_HAS_COLOR|IMAGE_HAS_ALPHA ); + + for( x = 0; x < 16; x++ ) + { + dx2 = x - 8; + dx2 = dx2 * dx2; + + for( y = 0; y < 16; y++ ) + { + dy = y - 8; + d = 255 - 35 * sqrt( dx2 + dy * dy ); + pic->buffer[( y * 16 + x ) * 4 + 3] = bound( 0, d, 255 ); + } + } + + tglob.particleTexture = R_TextureUploadFromBufferNew( REF_PARTICLE_TEXTURE, pic, TF_CLAMP ); + + // white texture + pic = Common_FakeImage( 4, 4, 1, IMAGE_HAS_COLOR ); + for( x = 0; x < 16; x++ ) + ((uint *)pic->buffer)[x] = 0xFFFFFFFF; + tglob.whiteTexture = R_TextureUploadFromBufferNew( REF_WHITE_TEXTURE, pic, TF_COLORMAP ); + + // gray texture + pic = Common_FakeImage( 4, 4, 1, IMAGE_HAS_COLOR ); + for( x = 0; x < 16; x++ ) + ((uint *)pic->buffer)[x] = 0xFF7F7F7F; + tglob.grayTexture = R_TextureUploadFromBufferNew( REF_GRAY_TEXTURE, pic, TF_COLORMAP ); + + // black texture + pic = Common_FakeImage( 4, 4, 1, IMAGE_HAS_COLOR ); + for( x = 0; x < 16; x++ ) + ((uint *)pic->buffer)[x] = 0xFF000000; + tglob.blackTexture = R_TextureUploadFromBufferNew( REF_BLACK_TEXTURE, pic, TF_COLORMAP ); + + // cinematic dummy + pic = Common_FakeImage( 640, 100, 1, IMAGE_HAS_COLOR ); + tglob.cinTexture = R_TextureUploadFromBufferNew( "*cintexture", pic, TF_NOMIPMAP|TF_CLAMP ); + + { + rgbdata_t *sides[6]; + pic = Common_FakeImage( 4, 4, 1, IMAGE_HAS_COLOR ); + for( x = 0; x < 16; x++ ) + ((uint *)pic->buffer)[x] = 0xFFFFFFFF; + + sides[0] = pic; + sides[1] = pic; + sides[2] = pic; + sides[3] = pic; + sides[4] = pic; + sides[5] = pic; + + R_VkTexturesSkyboxUpload( "skybox_placeholder", sides, kColorspaceGamma, true ); + } +} + +static void destroyDefaultTextures( void ) { + if (tglob.cinTexture > 0) + R_TextureFree( tglob.cinTexture ); + + if (tglob.blackTexture > 0) + R_TextureFree( tglob.blackTexture ); + + if (tglob.grayTexture > 0) + R_TextureFree( tglob.grayTexture ); + + if (tglob.whiteTexture > 0) + R_TextureFree( tglob.whiteTexture ); + + if (tglob.particleTexture > 0) + R_TextureFree( tglob.particleTexture ); + + if (tglob.defaultTexture > 0) + R_TextureFree( tglob.defaultTexture ); +} + +static void ProcessImage( vk_texture_t *tex, rgbdata_t *pic ) +{ + float emboss_scale = 0.0f; + uint img_flags = 0; + + // force upload texture as RGB or RGBA (detail textures requires this) + if( tex->flags & TF_FORCE_COLOR ) pic->flags |= IMAGE_HAS_COLOR; + if( pic->flags & IMAGE_HAS_ALPHA ) tex->flags |= TF_HAS_ALPHA; + + //FIXME provod: ??? tex->encode = pic->encode; // share encode method + + if( ImageCompressed( pic->type )) + { + if( !pic->numMips ) + tex->flags |= TF_NOMIPMAP; // disable mipmapping by user request + + // clear all the unsupported flags + tex->flags &= ~TF_KEEP_SOURCE; + } + else + { + // copy flag about luma pixels + if( pic->flags & IMAGE_HAS_LUMA ) + tex->flags |= TF_HAS_LUMA; + + if( pic->flags & IMAGE_QUAKEPAL ) + tex->flags |= TF_QUAKEPAL; + + // create luma texture from quake texture + if( tex->flags & TF_MAKELUMA ) + { + img_flags |= IMAGE_MAKE_LUMA; + tex->flags &= ~TF_MAKELUMA; + } + + /* FIXME provod: ??? + if( !FBitSet( tex->flags, TF_IMG_UPLOADED ) && FBitSet( tex->flags, TF_KEEP_SOURCE )) + tex->original = gEngine.FS_CopyImage( pic ); // because current pic will be expanded to rgba + */ + + // we need to expand image into RGBA buffer + if( pic->type == PF_INDEXED_24 || pic->type == PF_INDEXED_32 ) + img_flags |= IMAGE_FORCE_RGBA; + + /* FIXME provod: ??? + // dedicated server doesn't register this variable + if( gl_emboss_scale != NULL ) + emboss_scale = gl_emboss_scale->value; + */ + + // processing image before uploading (force to rgba, make luma etc) + if( pic->buffer ) gEngine.Image_Process( &pic, 0, 0, img_flags, emboss_scale ); + + if( FBitSet( tex->flags, TF_LUMINANCE )) + ClearBits( pic->flags, IMAGE_HAS_COLOR ); + } +} + +size_t CalcImageSize( pixformat_t format, int width, int height, int depth ) { + size_t size = 0; + + // check the depth error + depth = Q_max( 1, depth ); + + switch( format ) + { + case PF_LUMINANCE: + size = width * height * depth; + break; + case PF_RGB_24: + case PF_BGR_24: + size = width * height * depth * 3; + break; + case PF_BGRA_32: + case PF_RGBA_32: + size = width * height * depth * 4; + break; + case PF_DXT1: + case PF_BC4_UNSIGNED: + case PF_BC4_SIGNED: + size = (((width + 3) >> 2) * ((height + 3) >> 2) * 8) * depth; + break; + case PF_DXT3: + case PF_DXT5: + case PF_BC6H_UNSIGNED: + case PF_BC6H_SIGNED: + case PF_BC7_UNORM: + case PF_BC7_SRGB: + case PF_ATI2: + case PF_BC5_UNSIGNED: + case PF_BC5_SIGNED: + size = (((width + 3) >> 2) * ((height + 3) >> 2) * 16) * depth; + break; + default: + ERR("%s: unsupported pixformat_t %d", __FUNCTION__, format); + ASSERT(!"Unsupported format encountered"); + } + + return size; +} + +int CalcMipmapCount( int width, int height, int depth, uint32_t flags, qboolean haveBuffer ) +{ + int mipcount; + + if( !haveBuffer )// || tex->target == GL_TEXTURE_3D ) + return 1; + + // generate mip-levels by user request + if( FBitSet( flags, TF_NOMIPMAP )) + return 1; + + // mip-maps can't exceeds 16 + for( mipcount = 0; mipcount < 16; mipcount++ ) + { + const int mip_width = Q_max( 1, ( width >> mipcount )); + const int mip_height = Q_max( 1, ( height >> mipcount )); + const int mip_depth = Q_max( 1, ( depth >> mipcount )); + if( mip_width == 1 && mip_height == 1 && mip_depth == 1 ) + break; + } + + return mipcount + 1; +} + +void BuildMipMap( byte *in, int srcWidth, int srcHeight, int srcDepth, int flags ) +{ + byte *out = in; + int instride = ALIGN( srcWidth * 4, 1 ); + int mipWidth, mipHeight, outpadding; + int row, x, y, z; + vec3_t normal; + + if( !in ) return; + + mipWidth = Q_max( 1, ( srcWidth >> 1 )); + mipHeight = Q_max( 1, ( srcHeight >> 1 )); + outpadding = ALIGN( mipWidth * 4, 1 ) - mipWidth * 4; + + if( FBitSet( flags, TF_ALPHACONTRAST )) + { + memset( in, mipWidth, mipWidth * mipHeight * 4 ); + return; + } + + // move through all layers + for( z = 0; z < srcDepth; z++ ) + { + if( FBitSet( flags, TF_NORMALMAP )) + { + for( y = 0; y < mipHeight; y++, in += instride * 2, out += outpadding ) + { + byte *next = ((( y << 1 ) + 1 ) < srcHeight ) ? ( in + instride ) : in; + for( x = 0, row = 0; x < mipWidth; x++, row += 8, out += 4 ) + { + if((( x << 1 ) + 1 ) < srcWidth ) + { + normal[0] = MAKE_SIGNED( in[row+0] ) + MAKE_SIGNED( in[row+4] ) + + MAKE_SIGNED( next[row+0] ) + MAKE_SIGNED( next[row+4] ); + normal[1] = MAKE_SIGNED( in[row+1] ) + MAKE_SIGNED( in[row+5] ) + + MAKE_SIGNED( next[row+1] ) + MAKE_SIGNED( next[row+5] ); + normal[2] = MAKE_SIGNED( in[row+2] ) + MAKE_SIGNED( in[row+6] ) + + MAKE_SIGNED( next[row+2] ) + MAKE_SIGNED( next[row+6] ); + } + else + { + normal[0] = MAKE_SIGNED( in[row+0] ) + MAKE_SIGNED( next[row+0] ); + normal[1] = MAKE_SIGNED( in[row+1] ) + MAKE_SIGNED( next[row+1] ); + normal[2] = MAKE_SIGNED( in[row+2] ) + MAKE_SIGNED( next[row+2] ); + } + + if( !VectorNormalizeLength( normal )) + VectorSet( normal, 0.5f, 0.5f, 1.0f ); + + out[0] = 128 + (byte)(127.0f * normal[0]); + out[1] = 128 + (byte)(127.0f * normal[1]); + out[2] = 128 + (byte)(127.0f * normal[2]); + out[3] = 255; + } + } + } + else + { + for( y = 0; y < mipHeight; y++, in += instride * 2, out += outpadding ) + { + byte *next = ((( y << 1 ) + 1 ) < srcHeight ) ? ( in + instride ) : in; + for( x = 0, row = 0; x < mipWidth; x++, row += 8, out += 4 ) + { + if((( x << 1 ) + 1 ) < srcWidth ) + { + out[0] = (in[row+0] + in[row+4] + next[row+0] + next[row+4]) >> 2; + out[1] = (in[row+1] + in[row+5] + next[row+1] + next[row+5]) >> 2; + out[2] = (in[row+2] + in[row+6] + next[row+2] + next[row+6]) >> 2; + out[3] = (in[row+3] + in[row+7] + next[row+3] + next[row+7]) >> 2; + } + else + { + out[0] = (in[row+0] + next[row+0]) >> 1; + out[1] = (in[row+1] + next[row+1]) >> 1; + out[2] = (in[row+2] + next[row+2]) >> 1; + out[3] = (in[row+3] + next[row+3]) >> 1; + } + } + } + } + } +} + +qboolean validatePicLayers(const char* const name, rgbdata_t *const *const layers, int num_layers) { + for (int i = 0; i < num_layers; ++i) { + // FIXME create empty black texture if there's no buffer + if (!layers[i]->buffer) { + ERR("Texture %s layer %d missing buffer", name, i); + return false; + } + + if (i == 0) + continue; + + if (layers[0]->type != layers[i]->type) { + ERR("Texture %s layer %d has type %d inconsistent with layer 0 type %d", name, i, layers[i]->type, layers[0]->type); + return false; + } + + if (layers[0]->width != layers[i]->width + || layers[0]->height != layers[i]->height + || layers[0]->depth != layers[i]->depth) { + ERR("Texture %s layer %d has resolution %dx%d%d inconsistent with layer 0 resolution %dx%dx%d", + name, i, + layers[i]->width, layers[i]->height, layers[i]->depth, + layers[0]->width, layers[0]->height, layers[0]->depth); + return false; + } + + if ((layers[0]->flags ^ layers[i]->flags) & IMAGE_HAS_ALPHA) { + ERR("Texture %s layer %d has_alpha=%d inconsistent with layer 0 has_alpha=%d", + name, i, + !!(layers[i]->flags & IMAGE_HAS_ALPHA), + !!(layers[0]->flags & IMAGE_HAS_ALPHA)); + return false; + } + + if (layers[0]->numMips != layers[i]->numMips) { + ERR("Texture %s layer %d has numMips %d inconsistent with layer 0 numMips %d", + name, i, layers[i]->numMips, layers[0]->numMips); + return false; + } + } + + return true; +} + +///////////// Render API funcs ///////////// +int R_TextureFindByName( const char *name ) +{ + vk_texture_t *tex; + + if( !checkTextureName( name )) + return 0; + + const int index = urmomFind(&g_textures.all_desc, name); + return index > 0 ? index : 0; +} + +const char* R_TextureGetNameByIndex( unsigned int texnum ) +{ + ASSERT( texnum >= 0 && texnum < MAX_TEXTURES ); + return g_textures.all[texnum].hdr_.key; +} + +static int loadTextureInternalFromFile( const char *name, const byte *buf, size_t size, int flags, colorspace_hint_e colorspace_hint, qboolean force_update, qboolean ref_interface ) { + qboolean success = false; + if( !checkTextureName( name )) + return 0; + + const urmom_insert_t insert = urmomInsert(&g_textures.all_desc, name); + if (insert.index < 0) { + ERR("Cannot allocate texture slot for \"%s\"", name); + return 0; + } + + ASSERT(insert.index < COUNTOF(g_textures.all)); + + vk_texture_t *const tex = g_textures.all + insert.index; + + // return existing if already loaded and was not forced to reload + if (!insert.created && !force_update) { + DEBUG("Found existing texture %s(%d) refcount=%d", TEX_NAME(tex), insert.index, tex->refcount); + + // Increment refcount for refcount-aware calls (e.g. materials) + if (!ref_interface) { + tex->refcount++; + } else if (!tex->ref_interface_visible) { + tex->ref_interface_visible = true; + tex->refcount++; + } + + return insert.index; + } + + uint picFlags = 0; + + if( FBitSet( flags, TF_NOFLIP_TGA )) + SetBits( picFlags, IL_DONTFLIP_TGA ); + + if( FBitSet( flags, TF_KEEP_SOURCE ) && !FBitSet( flags, TF_EXPAND_SOURCE )) + SetBits( picFlags, IL_KEEP_8BIT ); + + // set some image flags + gEngine.Image_SetForceFlags( picFlags ); + + rgbdata_t *const pic = gEngine.FS_LoadImage( name, buf, size ); + if( !pic ) + goto cleanup; + + // Process flags, convert to rgba, etc + tex->flags = flags; + ProcessImage( tex, pic ); + + if( !R_VkTextureUpload( insert.index, tex, &pic, 1, colorspace_hint )) + goto cleanup; + + // New textures should have refcount = 1 regardless of refcount-aware calls + if (insert.created) { + tex->refcount = 1; + + // Mark it as visible from refount-unaware calls if it came from one + if (ref_interface) + tex->ref_interface_visible = true; + } + + success = true; + +cleanup: + if ( !success && insert.created ) + urmomRemoveByIndex(&g_textures.all_desc, insert.index); + if ( pic ) + gEngine.FS_FreeImage( pic ); + + return success ? insert.index : 0; +} + +int R_TextureUploadFromFile( const char *name, const byte *buf, size_t size, int flags ) { + const qboolean force_update = false; + const qboolean ref_interface = true; + return loadTextureInternalFromFile(name, buf, size, flags, kColorspaceGamma, force_update, ref_interface); +} + +int R_TextureUploadFromFileExAcquire( const char *filename, colorspace_hint_e colorspace, qboolean force_reload) { + const qboolean ref_interface = false; + return loadTextureInternalFromFile( filename, NULL, 0, 0, colorspace, force_reload, ref_interface ); +} + +// Unconditionally destroy the texture +static void destroyTexture( uint texnum ) { + ASSERT(texnum > 0); // 0 is *unused, cannot be destroyed + ASSERT(texnum < COUNTOF(g_textures.all)); + vk_texture_t *const tex = g_textures.all + texnum; + + DEBUG("Destroying texture=%d(%s)", texnum, TEX_NAME(tex)); + + if (tex->refcount > 0) + WARN("Texture '%s'(%d) has refcount=%d", TEX_NAME(tex), texnum, tex->refcount); + + ASSERT(URMOM_IS_OCCUPIED(tex->hdr_)); + + // remove from hash table + urmomRemoveByIndex(&g_textures.all_desc, texnum); + + /* + // release source + if( tex->original ) + gEngine.FS_FreeImage( tex->original ); + */ + + R_VkTextureDestroy( texnum, tex ); + tex->refcount = 0; + tex->ref_interface_visible = false; + tex->flags = 0; +} + +// Decrement refcount and destroy the texture if refcount has reached zero +static void releaseTexture( unsigned int texnum, qboolean ref_interface ) { + vk_texture_t *tex; + vk_texture_t **prev; + vk_texture_t *cur; + + APROF_SCOPE_DECLARE_BEGIN(free, __FUNCTION__); + + if( texnum <= 0 ) + goto end; + + ASSERT(texnum < COUNTOF(g_textures.all)); + + tex = g_textures.all + texnum; + + // already freed? + if( !tex->vk.image.image ) + goto end; + + // debug + if( !TEX_NAME(tex)[0] ) + { + ERR("%s: trying to free unnamed texture with index %u", __FUNCTION__, texnum ); + goto end; + } + + // Textures coming from legacy ref_interface_t api are not refcount-friendly + // Track them separately with a flags (and a single refcount ++/--) + if (ref_interface) { + if (!tex->ref_interface_visible) + return; + tex->ref_interface_visible = false; + } + + DEBUG("Releasing texture=%d(%s) refcount=%d", texnum, TEX_NAME(tex), tex->refcount); + ASSERT(tex->refcount > 0); + --tex->refcount; + + if (tex->refcount > 0) + goto end; + + destroyTexture(texnum); + +end: + APROF_SCOPE_END(free); +} + +void R_TextureFree( unsigned int texnum ) { + const qboolean ref_interface = true; + releaseTexture( texnum, ref_interface ); +} + + +int R_TextureUploadFromBuffer( const char *name, rgbdata_t *pic, texFlags_t flags, qboolean update_only ) { + // couldn't loading image + if( !pic ) + return 0; + + if( !checkTextureName( name )) + return 0; + + urmom_insert_t insert = {0}; + if (update_only) + insert.index = urmomFind(&g_textures.all_desc, name); + else + insert = urmomInsert(&g_textures.all_desc, name); + + if (insert.index < 0) { + if (update_only) { + gEngine.Host_Error( "%s: couldn't find texture %s for update\n", __FUNCTION__, name ); + } else { + ERR("Cannot allocate texture slot for \"%s\"", name); + } + return 0; + } + + ASSERT(insert.index < COUNTOF(g_textures.all)); + + vk_texture_t *const tex = g_textures.all + insert.index; + // see if already loaded + if (!insert.created && !update_only) + return insert.index; + + if( update_only ) + SetBits( tex->flags, flags ); + else + tex->flags = flags; + + ProcessImage( tex, pic ); + + if( !R_VkTextureUpload( insert.index, tex, &pic, 1, kColorspaceGamma )) + { + if ( !update_only && insert.created ) + urmomRemoveByIndex(&g_textures.all_desc, insert.index); + return 0; + } + + if (insert.created) { + tex->refcount = 1; + + // Loading from buffer is ref_interface only + tex->ref_interface_visible = true; + } + + return insert.index; +} + +static struct { + const char *suffix; + uint flags; +} g_skybox_info[6] = { + {"rt", IMAGE_ROT_90}, + {"lf", IMAGE_FLIP_Y | IMAGE_ROT_90 | IMAGE_FLIP_X}, + {"bk", IMAGE_FLIP_Y}, + {"ft", IMAGE_FLIP_X}, + {"up", IMAGE_ROT_90}, + {"dn", IMAGE_ROT_90}, +}; + +#define SKYBOX_MISSED 0 +#define SKYBOX_HLSTYLE 1 +#define SKYBOX_Q1STYLE 2 + +static int CheckSkybox( const char *name ) +{ + const char *skybox_ext[] = { "png", "dds", "tga", "bmp" }; + int i, j, num_checked_sides; + char sidename[MAX_VA_STRING]; + + // search for skybox images + for( i = 0; i < ARRAYSIZE(skybox_ext); i++ ) + { + num_checked_sides = 0; + for( j = 0; j < 6; j++ ) + { + // build side name + Q_snprintf( sidename, sizeof( sidename ), "%s%s.%s", name, g_skybox_info[j].suffix, skybox_ext[i] ); + if( gEngine.fsapi->FileExists( sidename, false )) + num_checked_sides++; + + } + + if( num_checked_sides == 6 ) + return SKYBOX_HLSTYLE; // image exists + + for( j = 0; j < 6; j++ ) + { + // build side name + Q_snprintf( sidename, sizeof( sidename ), "%s_%s.%s", name, g_skybox_info[j].suffix, skybox_ext[i] ); + if( gEngine.fsapi->FileExists( sidename, false )) + num_checked_sides++; + } + + if( num_checked_sides == 6 ) + return SKYBOX_Q1STYLE; // images exists + } + + return SKYBOX_MISSED; +} + +static qboolean loadSkybox( const char *prefix, int style ) { + rgbdata_t *sides[6]; + qboolean success = false; + int i; + + // release old skybox + unloadSkybox(); + DEBUG( "SKY: " ); + + for( i = 0; i < 6; i++ ) { + char sidename[MAX_STRING]; + if( style == SKYBOX_HLSTYLE ) + Q_snprintf( sidename, sizeof( sidename ), "%s%s", prefix, g_skybox_info[i].suffix ); + else Q_snprintf( sidename, sizeof( sidename ), "%s_%s", prefix, g_skybox_info[i].suffix ); + + sides[i] = gEngine.FS_LoadImage( sidename, NULL, 0); + if (!sides[i] || !sides[i]->buffer) + break; + + { + uint img_flags = g_skybox_info[i].flags; + // we need to expand image into RGBA buffer + if( sides[i]->type == PF_INDEXED_24 || sides[i]->type == PF_INDEXED_32 ) + img_flags |= IMAGE_FORCE_RGBA; + gEngine.Image_Process( &sides[i], 0, 0, img_flags, 0.f ); + } + DEBUG( "%s%s%s", prefix, g_skybox_info[i].suffix, i != 5 ? ", " : ". " ); + } + + if( i != 6 ) + goto cleanup; + + if( !checkTextureName( prefix )) + goto cleanup; + + success = R_VkTexturesSkyboxUpload( prefix, sides, kColorspaceGamma, false ); + +cleanup: + for (int j = 0; j < i; ++j) + gEngine.FS_FreeImage( sides[j] ); // release source texture + + if (success) { + tglob.fCustomSkybox = true; + DEBUG( "Skybox done" ); + } else { + ERR( "Skybox failed" ); + unloadSkybox(); + } + + return success; +} + +static const char *skybox_default = "desert"; +static const char *skybox_prefixes[] = { "pbr/env/%s", "gfx/env/%s" }; + +void R_TextureSetupSky( const char *skyboxname ) { + if( !COM_CheckString( skyboxname )) + { + unloadSkybox(); + return; // clear old skybox + } + + for (int i = 0; i < ARRAYSIZE(skybox_prefixes); ++i) { + char loadname[MAX_STRING]; + int style, len; + + Q_snprintf( loadname, sizeof( loadname ), skybox_prefixes[i], skyboxname ); + COM_StripExtension( loadname ); + + // kill the underline suffix to find them manually later + len = Q_strlen( loadname ); + + if( loadname[len - 1] == '_' ) + loadname[len - 1] = '\0'; + style = CheckSkybox( loadname ); + + if (loadSkybox(loadname, style)) + return; + } + + // Try default skybox if failed + if (Q_stricmp(skyboxname, skybox_default) != 0) { + WARN("missed or incomplete skybox '%s', trying default '%s'", skyboxname, skybox_default); + R_TextureSetupSky( skybox_default ); + } +} + +// FIXME move to r_textures_extra.h + +int R_TextureFindByNameF( const char *fmt, ...) { + int tex_id = 0; + char buffer[1024]; + va_list argptr; + va_start( argptr, fmt ); + vsnprintf( buffer, sizeof buffer, fmt, argptr ); + va_end( argptr ); + + tex_id = R_TextureFindByName(buffer); + DEBUG("Looked up texture %s -> %d", buffer, tex_id); + return tex_id; +} + +int R_TextureFindByNameLike( const char *texture_name ) { + const model_t *map = gEngine.pfnGetModelByIndex( 1 ); + string texname; + + // Try texture name as-is first + int tex_id = R_TextureFindByNameF("%s", texture_name); + + // Try bsp name + if (!tex_id) + tex_id = R_TextureFindByNameF("#%s:%s.mip", map->name, texture_name); + + if (!tex_id) { + const char *wad = g_map_entities.wadlist; + for (; *wad;) { + const char *const wad_end = Q_strchr(wad, ';'); + tex_id = R_TextureFindByNameF("%.*s/%s.mip", wad_end - wad, wad, texture_name); + if (tex_id) + break; + wad = wad_end + 1; + } + } + + return tex_id ? tex_id : -1; +} + +struct vk_texture_s *R_TextureGetByIndex( uint index ) +{ + ASSERT(index >= 0); + ASSERT(index < MAX_TEXTURES); + return g_textures.all + index; +} + +int R_TexturesGetParm( int parm, int arg ) { + const vk_texture_t *const tex = R_TextureGetByIndex( arg ); + if (!URMOM_IS_OCCUPIED(tex->hdr_)) + WARN("%s: accessing empty texture %d", __FUNCTION__, arg); + + if (!tex->ref_interface_visible) + return 0; + + switch(parm){ + case PARM_TEX_WIDTH: + case PARM_TEX_SRC_WIDTH: // TODO why is this separate? + return tex->width; + case PARM_TEX_HEIGHT: + case PARM_TEX_SRC_HEIGHT: + return tex->height; + case PARM_TEX_FLAGS: + return tex->flags; + // TODO + case PARM_TEX_SKYBOX: + case PARM_TEX_SKYTEXNUM: + case PARM_TEX_LIGHTMAP: + case PARM_TEX_TARGET: + case PARM_TEX_TEXNUM: + case PARM_TEX_DEPTH: + case PARM_TEX_GLFORMAT: + case PARM_TEX_ENCODE: + case PARM_TEX_MIPCOUNT: + case PARM_TEX_MEMORY: + return 0; + default: + return 0; + } +} + +void R_TextureAcquire( unsigned int texnum ) { + ASSERT(texnum > 0); + vk_texture_t *const tex = R_TextureGetByIndex(texnum); + ASSERT(URMOM_IS_OCCUPIED(tex->hdr_)); + ++tex->refcount; + + DEBUG("Acquiring existing texture %s(%d) refcount=%d", TEX_NAME(tex), texnum, tex->refcount); +} + +void R_TextureRelease( unsigned int texnum ) { + const qboolean ref_interface = false; + releaseTexture( texnum, ref_interface ); +} diff --git a/ref/vk/r_textures.h b/ref/vk/r_textures.h new file mode 100644 index 00000000..51a4b9b7 --- /dev/null +++ b/ref/vk/r_textures.h @@ -0,0 +1,69 @@ +#pragma once + +#include "const.h" // required for com_model.h, ref_api.h +#include "cvardef.h" // required for ref_api.h +#include "com_model.h" // required for ref_api.h +#include "ref_api.h" // texFlags_t + +#define MAX_LIGHTMAPS 256 + +typedef struct vk_textures_global_s +{ + // TODO Fix these at compile time statically, akin to BLUE_NOISE_TEXTURE_ID + int defaultTexture; // use for bad textures + int particleTexture; + int whiteTexture; + int grayTexture; + int blackTexture; + int solidskyTexture; // quake1 solid-sky layer + int alphaskyTexture; // quake1 alpha-sky layer + int lightmapTextures[MAX_LIGHTMAPS]; + int dlightTexture; // custom dlight texture + int cinTexture; // cinematic texture + + // TODO wire it up for ref_interface_t return + qboolean fCustomSkybox; +} vk_textures_global_t; + +// TODO rename this consistently +extern vk_textures_global_t tglob; + +qboolean R_TexturesInit( void ); +void R_TexturesShutdown( void ); + +//////////////////////////////////////////////////////////// +// Ref interface functions, exported +// TODO mark names somehow, ie. R_TextureApi... ? +int R_TextureFindByName( const char *name ); +const char* R_TextureGetNameByIndex( unsigned int texnum ); + +void R_TextureSetupSky( const char *skyboxname ); + +int R_TextureUploadFromFile( const char *name, const byte *buf, size_t size, int flags ); +int R_TextureUploadFromBuffer( const char *name, rgbdata_t *pic, texFlags_t flags, qboolean update_only ); +void R_TextureFree( unsigned int texnum ); + +int R_TexturesGetParm( int parm, int arg ); + +//////////////////////////////////////////////////////////// +// Extra functions used in ref_vk +void R_TextureAcquire( unsigned int texnum ); +void R_TextureRelease( unsigned int texnum ); + +typedef enum { + kColorspaceNative, + kColorspaceLinear, + kColorspaceGamma, +} colorspace_hint_e; + +int R_TextureUploadFromFileExAcquire( const char *filename, colorspace_hint_e colorspace, qboolean force_reload ); + +int R_TextureFindByNameF( const char *fmt, ...); + +// Tries to find a texture by its short name +// Full names depend on map name, wad name, etc. This function tries them all. +// Returns -1 if not found +int R_TextureFindByNameLike( const char *texture_name ); + +struct vk_texture_s; +struct vk_texture_s *R_TextureGetByIndex( uint index ); diff --git a/ref/vk/shaders/bluenoise.glsl b/ref/vk/shaders/bluenoise.glsl index be468345..bca5c685 100644 --- a/ref/vk/shaders/bluenoise.glsl +++ b/ref/vk/shaders/bluenoise.glsl @@ -1,20 +1,14 @@ #ifndef BLUENOISE_H_INCLUDED #define BLUENOISE_H_INCLUDED -// Depends on uniform sampler2D textures[MAX_TEXTURES] binding being defined - -// This is the same hardcoded value as in vk_textures.h -// Please keep them in sync onegai uwu -// TODO: -// - make bluenoise texture a separate binding, not part of textures[] array -// - make it a 3D texture -#define BLUE_NOISE_TEXTURE_BEGIN 7 +// Depends on uniform sampler3D blue_noise_texture binding being defined // Also see vk_textures.h, keep in sync, etc etc #define BLUE_NOISE_SIZE 64 + vec4 blueNoise(ivec3 v) { - v %= BLUE_NOISE_SIZE; - return texelFetch(textures[BLUE_NOISE_TEXTURE_BEGIN+v.z], v.xy, 0); + ivec3 size = textureSize(blue_noise_texture, 0); + return texelFetch(blue_noise_texture, v % size, 0); } #endif // ifndef BLUENOISE_H_INCLUDED diff --git a/ref/vk/shaders/denoiser.comp b/ref/vk/shaders/denoiser.comp index 0a16933a..52918bcb 100644 --- a/ref/vk/shaders/denoiser.comp +++ b/ref/vk/shaders/denoiser.comp @@ -36,9 +36,10 @@ layout(set = 0, binding = 15, rgba16f) uniform image2D prev_temporal_diffuse; layout(set = 0, binding = 16, rgba16f) uniform image2D out_temporal_specular; layout(set = 0, binding = 17, rgba16f) uniform image2D prev_temporal_specular; +//#define DEBUG_NOISE #ifdef DEBUG_NOISE -layout(set = 0, binding = 18) uniform sampler2D textures[MAX_TEXTURES]; -include "bluenoise.glsl" +layout(set = 0, binding = 18) uniform sampler3D blue_noise_texture; +#include "bluenoise.glsl" #endif //layout(set = 0, binding = 19) uniform sampler2D textures[MAX_TEXTURES]; diff --git a/ref/vk/tests/unordered_roadmap.c b/ref/vk/tests/unordered_roadmap.c new file mode 100644 index 00000000..310d9a34 --- /dev/null +++ b/ref/vk/tests/unordered_roadmap.c @@ -0,0 +1,240 @@ +#include "../unordered_roadmap.h" + +#define URMOM_TEST +#include "../unordered_roadmap.c" + +#define LOG(msg, ...) \ + fprintf(stderr, "%s:%d: " msg "\n", __FILE__, __LINE__, ##__VA_ARGS__) + +#define CHECK_EQUAL_I(a, b) \ + do { \ + const int ar = (a), br = (b); \ + if (ar != br) { \ + LOG("CHECK_EQUAL_I("#a", "#b") failed: %d != %d", ar, br); \ + return 0; \ + } \ + } while (0) + +#define CHECK_EQUAL_S(a, b) \ + do { \ + const char *ar = (a), *br = (b); \ + if (strcmp(ar, br) != 0) { \ + LOG("CHECK_EQUAL_S("#a", "#b") failed: \"%s\" != \"%s\"", ar, br); \ + return 0; \ + } \ + } while (0) + +#define CHECK_NOT_EQUAL_I(a, b) \ + do { \ + const int ar = (a), br = (b); \ + if (ar == br) { \ + LOG("CHECK_NOT_EQUAL_I("#a", "#b") failed: %d == %d", ar, br); \ + return 0; \ + } \ + } while (0) + +typedef struct { + urmom_header_t hdr_; + int i; + float f; +} item_t; + +#define PREAMBLE(N, type_) \ + item_t items[N]; \ + const urmom_desc_t desc = { \ + .array = items, \ + .count = COUNTOF(items), \ + .item_size = sizeof(item_t), \ + .type = type_, \ + }; \ + urmomInit(&desc) + +static int test_insert_find_remove( void ) { + PREAMBLE(4, kUrmomString); + + const urmom_insert_t i = urmomInsert(&desc, "bidonchik"); + CHECK_NOT_EQUAL_I(i.index, -1); + CHECK_EQUAL_I(i.created, 1); + CHECK_EQUAL_S(items[i.index].hdr_.key, "bidonchik"); + + const int found = urmomFind(&desc, "bidonchik"); + CHECK_EQUAL_I(found, i.index); + + const urmom_insert_t i2 = urmomInsert(&desc, "bidonchik"); + CHECK_EQUAL_I(i2.index, i.index); + CHECK_EQUAL_I(i2.created, 0); + CHECK_EQUAL_S(items[i.index].hdr_.key, "bidonchik"); + + const int removed = urmomRemove(&desc, "bidonchik"); + CHECK_EQUAL_I(removed, i.index); + CHECK_EQUAL_I(items[i.index].hdr_.key[0], '\0'); + + const int not_found = urmomFind(&desc, "bidonchik"); + CHECK_EQUAL_I(not_found, -1); + + return 1; +} + +static int test_find_nonexistent( void ) { + PREAMBLE(4, kUrmomString); + + const int found = urmomFind(&desc, "kishochki"); + CHECK_EQUAL_I(found, -1); + return 1; +} + +static int test_insert_find_many( void ) { + PREAMBLE(4, kUrmomString); + + const urmom_insert_t a = urmomInsert(&desc, "smetanka"); + CHECK_NOT_EQUAL_I(a.index, -1); + CHECK_EQUAL_I(a.created, 1); + CHECK_EQUAL_S(items[a.index].hdr_.key, "smetanka"); + + const urmom_insert_t b = urmomInsert(&desc, "tworog"); + CHECK_NOT_EQUAL_I(b.index, -1); + CHECK_EQUAL_I(b.created, 1); + CHECK_NOT_EQUAL_I(a.index, b.index); + CHECK_EQUAL_S(items[b.index].hdr_.key, "tworog"); + + const int a_found = urmomFind(&desc, "smetanka"); + const int b_found = urmomFind(&desc, "tworog"); + + CHECK_EQUAL_I(a_found, a.index); + CHECK_EQUAL_I(b_found, b.index); + + return 1; +} + +static int test_overflow( void ) { + PREAMBLE(4, kUrmomString); + + const urmom_insert_t a = urmomInsert(&desc, "smetanka"); + CHECK_NOT_EQUAL_I(a.index, -1); + CHECK_EQUAL_I(a.created, 1); + CHECK_EQUAL_S(items[a.index].hdr_.key, "smetanka"); + + const urmom_insert_t b = urmomInsert(&desc, "tworog"); + CHECK_NOT_EQUAL_I(b.index, -1); + CHECK_EQUAL_I(b.created, 1); + CHECK_NOT_EQUAL_I(a.index, b.index); + CHECK_EQUAL_S(items[b.index].hdr_.key, "tworog"); + + + const urmom_insert_t c = urmomInsert(&desc, "kefirushka"); + CHECK_NOT_EQUAL_I(c.index, -1); + CHECK_EQUAL_I(c.created, 1); + CHECK_NOT_EQUAL_I(a.index, c.index); + CHECK_NOT_EQUAL_I(b.index, c.index); + CHECK_EQUAL_S(items[c.index].hdr_.key, "kefirushka"); + + const urmom_insert_t d = urmomInsert(&desc, "ryazhenka"); + CHECK_NOT_EQUAL_I(d.index, -1); + CHECK_EQUAL_I(d.created, 1); + CHECK_NOT_EQUAL_I(a.index, d.index); + CHECK_NOT_EQUAL_I(b.index, d.index); + CHECK_NOT_EQUAL_I(c.index, d.index); + CHECK_EQUAL_S(items[d.index].hdr_.key, "ryazhenka"); + + { + const urmom_insert_t e = urmomInsert(&desc, "riajenka"); + CHECK_EQUAL_I(e.index, -1); + CHECK_EQUAL_I(e.created, 0); + } + + const int d_remove = urmomRemove(&desc, "ryazhenka"); + CHECK_EQUAL_I(d_remove, d.index); + CHECK_EQUAL_I(items[d_remove].hdr_.state, 0); + CHECK_NOT_EQUAL_I(items[d_remove].hdr_.hash, 0); + CHECK_EQUAL_I(items[d_remove].hdr_.key[0], '\0'); + + const urmom_insert_t e = urmomInsert(&desc, "riajenka"); + CHECK_NOT_EQUAL_I(e.index, -1); + CHECK_EQUAL_I(e.created, 1); + CHECK_NOT_EQUAL_I(a.index, e.index); + CHECK_NOT_EQUAL_I(b.index, e.index); + CHECK_NOT_EQUAL_I(c.index, e.index); + CHECK_EQUAL_S(items[e.index].hdr_.key, "riajenka"); + + return 1; +} + +// Assumes FNV-1a +static int test_hash_collision( void ) { + PREAMBLE(4, kUrmomString); + + const urmom_insert_t a = urmomInsert(&desc, "costarring"); + CHECK_NOT_EQUAL_I(a.index, -1); + CHECK_EQUAL_I(a.created, 1); + + const urmom_insert_t b = urmomInsert(&desc, "liquid"); + CHECK_NOT_EQUAL_I(b.index, -1); + CHECK_EQUAL_I(b.created, 1); + CHECK_NOT_EQUAL_I(b.index, a.index); + + CHECK_EQUAL_I(items[a.index].hdr_.hash, items[b.index].hdr_.hash); + + const int a_found = urmomFind(&desc, "costarring"); + CHECK_EQUAL_I(a_found, a.index); + + const int b_found = urmomFind(&desc, "liquid"); + CHECK_EQUAL_I(b_found, b.index); + + return 1; +} + +static int test_insert_find_remove_insensitive( void ) { + PREAMBLE(4, kUrmomStringInsensitive); + + const urmom_insert_t i = urmomInsert(&desc, "bidonchik"); + CHECK_NOT_EQUAL_I(i.index, -1); + CHECK_EQUAL_I(i.created, 1); + CHECK_EQUAL_S(items[i.index].hdr_.key, "bidonchik"); + + const int found = urmomFind(&desc, "BIDONCHIk"); + CHECK_EQUAL_I(found, i.index); + + const urmom_insert_t i2 = urmomInsert(&desc, "biDONChik"); + CHECK_EQUAL_I(i2.index, i.index); + CHECK_EQUAL_I(i2.created, 0); + CHECK_EQUAL_S(items[i.index].hdr_.key, "bidonchik"); + + const int removed = urmomRemove(&desc, "bidonCHIK"); + CHECK_EQUAL_I(removed, i.index); + CHECK_EQUAL_I(items[i.index].hdr_.key[0], '\0'); + + const int not_found = urmomFind(&desc, "bidonchik"); + CHECK_EQUAL_I(not_found, -1); + + return 1; +} + +static int test_fail( void ) { + //CHECK_EQUAL_S("sapogi", "tapki"); + return 1; +} + +#define LIST_TESTS(X) \ + X(test_insert_find_remove) \ + X(test_find_nonexistent) \ + X(test_insert_find_many) \ + X(test_hash_collision) \ + X(test_insert_find_remove_insensitive) \ + X(test_fail) \ + +int main( void ) { + int retval = 0; +#define X(f) \ + do { \ + fprintf(stderr, "Running " #f "...\n"); \ + const int result = f(); \ + fprintf(stderr, #f " => %s\n", result == 0 ? "FAIL" : "OK" ); \ + if (!result) \ + ++retval;\ + } while (0); + +LIST_TESTS(X) +#undef X + + return retval; +} diff --git a/ref/vk/unordered_roadmap.c b/ref/vk/unordered_roadmap.c new file mode 100644 index 00000000..e98935a7 --- /dev/null +++ b/ref/vk/unordered_roadmap.c @@ -0,0 +1,184 @@ +#include "unordered_roadmap.h" + +#ifndef URMOM_TEST +#include "vk_common.h" +#include "vk_logs.h" +#else +#include +#include +#include +#define ERR(msg, ...) fprintf(stderr, msg, ##__VA_ARGS__) +#define ASSERT(...) assert(__VA_ARGS__) +#define COUNTOF(a) (sizeof(a)/sizeof(a[0])) +#endif + +#if defined(_WIN32) && !defined(strcasecmp) +#define strcasecmp _stricmp +#endif + +static uint32_t hash32FNV1aStr(const char *str) { + static const uint32_t fnv_offset_basis = 0x811c9dc5u; + static const uint32_t fnv_prime = 0x01000193u; + + uint32_t hash = fnv_offset_basis; + while (*str) { + hash ^= *str; + hash *= fnv_prime; + ++str; + } + return hash; +} +static uint32_t hash32FNV1aStrI(const char *str) { + static const uint32_t fnv_offset_basis = 0x811c9dc5u; + static const uint32_t fnv_prime = 0x01000193u; + + uint32_t hash = fnv_offset_basis; + while (*str) { + hash ^= (*str & 0xdf); + hash *= fnv_prime; + ++str; + } + return hash; +} + +// Sets all items to empty +void urmomInit(const urmom_desc_t* desc) { + char *ptr = desc->array; + + // Make sure that count is 2^N + ASSERT((desc->count & (desc->count - 1)) == 0); + + for (int i = 0; i < desc->count; ++i) { + urmom_header_t *hdr = (urmom_header_t*)(ptr + desc->item_size * i); + hdr->state = 0; + hdr->hash = 0; + } +} + +static uint32_t hashKey(urmom_type_t type, const char *key) { + uint32_t hash; + switch (type) { + case kUrmomString: + hash = hash32FNV1aStr(key); + break; + case kUrmomStringInsensitive: + hash = hash32FNV1aStrI(key); + break; + default: + ASSERT(!"Invalid hash table key type"); + } + + return hash & 0x7fffffffu; +} + +static int sameKey(urmom_type_t type, const char *key1, const char *key2) { + switch (type) { + case kUrmomString: + return strcmp(key1, key2) == 0; + case kUrmomStringInsensitive: + return strcasecmp(key1, key2) == 0; + default: + ASSERT(!"Invalid hash table key type"); + } + + return 0; +} + +// Returns index of the element with the key if found, -1 otherwise +int urmomFind(const urmom_desc_t* desc, const char* key) { + const char *ptr = desc->array; + const uint32_t hash = hashKey(desc->type, key); + const uint32_t mask = (desc->count - 1); + const int start_index = hash & mask; + + for (int index = start_index;;) { + const urmom_header_t *hdr = (urmom_header_t*)(ptr + desc->item_size * index); + + if (URMOM_IS_OCCUPIED(*hdr)) { + if (hdr->hash == hash && sameKey(desc->type, key, hdr->key)) + return index; + } else if (URMOM_IS_EMPTY(*hdr)) + // Reached the end of non-empty chain, not found + break; + + // No match ;_;, check the next one + index = (index + 1) & mask; + + // Searched through the entire thing + if (index == start_index) + break; + } + + return -1; +} + +// Returns index of the element either found or empty slot where this could be inserted. If full, -1. +urmom_insert_t urmomInsert(const urmom_desc_t* desc, const char *key) { + char *ptr = desc->array; + const uint32_t hash = hashKey(desc->type, key); + const uint32_t mask = (desc->count - 1); + const int start_index = hash & mask; + + int index = start_index; + int first_available = -1; + for (;;) { + const urmom_header_t *hdr = (urmom_header_t*)(ptr + desc->item_size * index); + + if (URMOM_IS_OCCUPIED(*hdr)) { + if (hdr->hash == hash && sameKey(desc->type, key, hdr->key)) + // Return existing item + return (urmom_insert_t){.index = index, .created = 0}; + } else { + // Remember the very first item that wasn't occupied + if (first_available < 0) + first_available = index; + + // Reached the end of occupied chain, return the available slot + if (URMOM_IS_EMPTY(*hdr)) + break; + } + + index = (index + 1) & mask; + + // Searched through the entire thing + if (index == start_index) + break; + } + + // If no slots were encountered, exit with error + if (first_available < 0) + return (urmom_insert_t){.index = -1, .created = 0}; + + urmom_header_t *hdr = (urmom_header_t*)(ptr + desc->item_size * first_available); + hdr->hash = hash; + hdr->state = 1; + + // TODO check for key length + strncpy(hdr->key, key, sizeof(hdr->key)); + + return (urmom_insert_t){.index = first_available, .created = 1}; +} + +// Return the index of item deleted (if found), -1 otherwise +int urmomRemove(const urmom_desc_t* desc, const char *key) { + const int index = urmomFind(desc, key); + if (index >= 0) + urmomRemoveByIndex(desc, index); + return index; +} + +void urmomRemoveByIndex(const urmom_desc_t* desc, int index) { + char *ptr = desc->array; + urmom_header_t *hdr = (urmom_header_t*)(ptr + desc->item_size * index); + + if (!URMOM_IS_OCCUPIED(*hdr)) { + ERR("Hashmap=%p(is=%d, n=%d): lot %d is not occupied", desc->array, desc->item_size, desc->count, index); + return; + } + + // Mark it as deleted + // TODO when can we mark it as empty? For linear search we can do so if the next one is empty + hdr->state = 0; // not occupied + hdr->hash = 1; // deleted, not empty + hdr->key[0] = '\0'; +} diff --git a/ref/vk/unordered_roadmap.h b/ref/vk/unordered_roadmap.h new file mode 100644 index 00000000..e4c70e33 --- /dev/null +++ b/ref/vk/unordered_roadmap.h @@ -0,0 +1,68 @@ +#pragma once + +#include + +#define MAX_KEY_STRING_LENGTH 256 + +// URMOM = Unordered RoadMap Open addressiMg + +// Open-addressed hash table item header +typedef struct urmom_header_s { + // state == 1, hash == 0 -- item with hash==0 + // state == 0, hash != 0 -- deleted + // state == 0, hash == 0 -- empty + uint32_t state:1; + uint32_t hash:31; + + char key[MAX_KEY_STRING_LENGTH]; +} urmom_header_t; + +#define URMOM_IS_OCCUPIED(hdr) ((hdr).state != 0) +#define URMOM_IS_EMPTY(hdr) ((hdr).state == 0 && (hdr).hash == 0) +#define URMOM_IS_DELETED(hdr) ((hdr).state == 0 && (hdr).hash != 0) + +// TODO: +// - rename this to key type +// - allow passing key not as const char*, but as string_view +// - (or even just void*+size, which would almost make it universal) +typedef enum { + kUrmomString, + kUrmomStringInsensitive, +} urmom_type_t; + +typedef struct urmom_desc_s { + // Pointer to the beginning of the array of items. + // Each item is a struct that has urmom_header_t as its first field + void *array; + + // Array item size, including the urmom_header_t + uint32_t item_size; + + // Maximum number of items in the array + uint32_t count; + + urmom_type_t type; +} urmom_desc_t; + +// Sets all items to empty +void urmomInit(const urmom_desc_t* desc); + +// Returns index of the element with the key if found, -1 otherwise +int urmomFind(const urmom_desc_t* desc, const char* key); + +// Returns index of the element either found or empty slot where this could be inserted. If full, -1. +typedef struct urmom_insert_s { + int index; + int created; +} urmom_insert_t; +urmom_insert_t urmomInsert(const urmom_desc_t* desc, const char *key); + +// Return the index of item deleted (if found), -1 otherwise +int urmomRemove(const urmom_desc_t* desc, const char *key); + +void urmomRemoveByIndex(const urmom_desc_t* desc, int index); + +// TODO erase IS_DELETED tails +// void urmomCleanup() + +// TODO optimize storage: collapse non-tail IS_DELETED sequences. Items will have new placement, so all indexes will be stale diff --git a/ref/vk/vk_beams.c b/ref/vk/vk_beams.c index 07fafc72..735fd4b2 100644 --- a/ref/vk/vk_beams.c +++ b/ref/vk/vk_beams.c @@ -3,7 +3,7 @@ #include "camera.h" #include "vk_render.h" #include "vk_geometry.h" -#include "vk_textures.h" +#include "r_textures.h" #include "vk_sprite.h" #include "vk_scene.h" #include "vk_math.h" diff --git a/ref/vk/vk_brush.c b/ref/vk/vk_brush.c index b36e99f6..0ef58935 100644 --- a/ref/vk/vk_brush.c +++ b/ref/vk/vk_brush.c @@ -6,7 +6,7 @@ #include "vk_pipeline.h" #include "vk_framectl.h" #include "vk_math.h" -#include "vk_textures.h" +#include "r_textures.h" #include "vk_lightmap.h" #include "vk_scene.h" #include "vk_render.h" @@ -404,8 +404,8 @@ static void fillWaterSurfaces( const cl_entity_t *ent, vk_brush_model_t *bmodel, static rt_light_add_polygon_t loadPolyLight(const model_t *mod, const int surface_index, const msurface_t *surf, const vec3_t emissive); -static qboolean isSurfaceAnimated( const msurface_t *s, const struct texture_s *base_override ) { - const texture_t *base = base_override ? base_override : s->texinfo->texture; +static qboolean isSurfaceAnimated( const msurface_t *s ) { + const texture_t *base = s->texinfo->texture; /* FIXME don't have ent here, need to check both explicitly if( ent && ent->curstate.frame ) { @@ -483,8 +483,8 @@ static brush_surface_type_e getSurfaceType( const msurface_t *surf, int i ) { return BrushSurface_Hidden; } - const struct texture_s *texture_override = patch_surface ? patch_surface->tex : NULL; - if (isSurfaceAnimated(surf, texture_override)) { + const qboolean patched_material = patch_surface && !!(patch_surface->flags & Patch_Surface_Material); + if (!patched_material && isSurfaceAnimated(surf)) { return BrushSurface_Animated; } @@ -534,7 +534,7 @@ static qboolean brushCreateWaterModel(const model_t *mod, vk_brush_model_t *bmod return true; } -material_mode_e brushMaterialModeForRenderType(vk_render_type_e render_type) { +static material_mode_e brushMaterialModeForRenderType(vk_render_type_e render_type) { switch (render_type) { case kVkRenderTypeSolid: return kMaterialMode_Opaque; @@ -582,12 +582,13 @@ static void brushDrawWater(vk_brush_model_t *bmodel, const cl_entity_t *ent, int APROF_SCOPE_END(brush_draw_water); } -// FIXME use this +#if 0 +// TODO use this static void computeConveyorSpeed(const color24 rendercolor, int tex_index, vec2_t speed) { float sy, cy; float flConveyorSpeed = 0.0f; float flRate, flAngle; - vk_texture_t *texture = findTexture( tex_index ); + vk_texture_t *texture = R_TextureGetByIndex( tex_index ); //gl_texture_t *texture; // FIXME @@ -610,6 +611,7 @@ static void computeConveyorSpeed(const color24 rendercolor, int tex_index, vec2_ speed[0] = cy * flRate; speed[1] = sy * flRate; } +#endif /* =============== @@ -618,9 +620,9 @@ R_TextureAnimation Returns the proper texture for a given time and surface =============== */ -const texture_t *R_TextureAnimation( const cl_entity_t *ent, const msurface_t *s, const struct texture_s *base_override ) +const texture_t *R_TextureAnimation( const cl_entity_t *ent, const msurface_t *s ) { - const texture_t *base = base_override ? base_override : s->texinfo->texture; + const texture_t *base = s->texinfo->texture; int count, reletive; if( ent && ent->curstate.frame ) @@ -644,9 +646,10 @@ const texture_t *R_TextureAnimation( const cl_entity_t *ent, const msurface_t *s int speed; // Quake1 textures uses 10 frames per second - if( FBitSet( findTexture( base->gl_texturenum )->flags, TF_QUAKEPAL )) + /* TODO + if( FBitSet( R_TextureGetByIndex( base->gl_texturenum )->flags, TF_QUAKEPAL )) speed = 10; - else speed = 20; + else */ speed = 20; reletive = (int)(gpGlobals->time * speed) % base->anim_total; } @@ -742,8 +745,7 @@ void VK_BrushModelDraw( const cl_entity_t *ent, int render_mode, float blend, co const xvk_patch_surface_t *const patch_surface = R_VkPatchGetSurface(surface_index); // Optionally patch by texture_s pointer and run animations - const struct texture_s *texture_override = patch_surface ? patch_surface->tex : NULL; - const texture_t *t = R_TextureAnimation(ent, geom->surf_deprecate, texture_override); + const texture_t *t = R_TextureAnimation(ent, geom->surf_deprecate); const int new_tex_id = t->gl_texturenum; if (new_tex_id >= 0 && new_tex_id != geom->ye_olde_texture) { @@ -840,16 +842,11 @@ static void getSurfaceNormal( const msurface_t *surf, vec3_t out_normal) { VectorCopy( surf->plane->normal, out_normal ); // TODO scale normal by area -- bigger surfaces should have bigger impact + // NOTE scaling normal by area might be totally incorrect in many circumstances + // The more corect logic there is way more difficult //VectorScale(normal, surf->plane. } -static int getSurfaceTexture(const msurface_t *surf, int surface_index) { - const xvk_patch_surface_t *const psurf = R_VkPatchGetSurface(surface_index); - if (psurf && psurf->tex_id >= 0) - return psurf->tex_id; - return surf->texinfo->texture->gl_texturenum; -} - static qboolean shouldSmoothLinkSurfaces(const model_t* mod, qboolean smooth_entire_model, int surf1, int surf2) { //return Q_min(surf1, surf2) == 741 && Q_max(surf1, surf2) == 743; @@ -884,10 +881,13 @@ static qboolean shouldSmoothLinkSurfaces(const model_t* mod, qboolean smooth_ent } // Do not join surfaces with different textures. Assume they belong to different objects. - const int t1 = getSurfaceTexture(mod->surfaces + surf1, surf1); - const int t2 = getSurfaceTexture(mod->surfaces + surf2, surf2); - if (t1 != t2) - return false; + { + // Should we also check texture/material patches too to filter out pairs which originally had + // same textures, but with patches do not? + if (mod->surfaces[surf1].texinfo->texture->gl_texturenum + != mod->surfaces[surf2].texinfo->texture->gl_texturenum) + return false; + } vec3_t n1, n2; getSurfaceNormal(mod->surfaces + surf1, n1); @@ -1091,8 +1091,6 @@ static qboolean fillBrushSurfaces(fill_geometries_args_t args) { // 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; const brush_surface_type_e type = getSurfaceType(surf, surface_index); switch (type) { @@ -1119,9 +1117,14 @@ static qboolean fillBrushSurfaces(fill_geometries_args_t args) { } model_geometry->ye_olde_texture = orig_tex_id; - qboolean material_assigned = false; - if (entity_patch) { + + if (psurf && (psurf->flags & Patch_Surface_Material)) { + model_geometry->material = R_VkMaterialGetForRef(psurf->material_ref); + material_assigned = true; + } + + if (!material_assigned && entity_patch) { for (int i = 0; i < entity_patch->matmap_count; ++i) { if (entity_patch->matmap[i].from_tex == orig_tex_id) { model_geometry->material = R_VkMaterialGetForTexture(entity_patch->matmap[i].to_mat.index); @@ -1555,3 +1558,18 @@ void R_VkBrushModelCollectEmissiveSurfaces( const struct model_s *mod, qboolean INFO("Loaded %d polylights for %s model %s", emissive_surfaces_count, is_static ? "static" : "movable", mod->name); } } + +void VK_BrushUnloadTextures( model_t *mod ) +{ + int i; + + for( i = 0; i < mod->numtextures; i++ ) + { + texture_t *tx = mod->textures[i]; + if( !tx || tx->gl_texturenum == tglob.defaultTexture ) + continue; // free slot + + R_TextureFree( tx->gl_texturenum ); // main texture + R_TextureFree( tx->fb_texturenum ); // luma texture + } +} diff --git a/ref/vk/vk_brush.h b/ref/vk/vk_brush.h index f128c61b..91ca742e 100644 --- a/ref/vk/vk_brush.h +++ b/ref/vk/vk_brush.h @@ -16,6 +16,8 @@ void VK_BrushModelDestroyAll( void ); void VK_BrushModelDraw( const cl_entity_t *ent, int render_mode, float blend, const matrix4x4 model ); -const texture_t *R_TextureAnimation( const cl_entity_t *ent, const msurface_t *s, const struct texture_s *base_override ); +const texture_t *R_TextureAnimation( const cl_entity_t *ent, const msurface_t *s ); void R_VkBrushModelCollectEmissiveSurfaces( const struct model_s *mod, qboolean is_worldmodel ); + +void VK_BrushUnloadTextures( model_t *mod ); diff --git a/ref/vk/vk_core.c b/ref/vk/vk_core.c index 1c62527b..b86ad515 100644 --- a/ref/vk/vk_core.c +++ b/ref/vk/vk_core.c @@ -1,7 +1,7 @@ #include "vk_core.h" #include "vk_common.h" -#include "vk_textures.h" +#include "r_textures.h" #include "vk_overlay.h" #include "vk_renderstate.h" #include "vk_staging.h" @@ -786,7 +786,7 @@ qboolean R_VkInit( void ) VK_SceneInit(); - initTextures(); + R_TexturesInit(); // All below need render_pass @@ -833,7 +833,9 @@ void R_VkShutdown( void ) { VK_FrameCtlShutdown(); - destroyTextures(); + R_VkMaterialsShutdown(); + + R_TexturesShutdown(); VK_PipelineShutdown(); diff --git a/ref/vk/vk_descriptor.c b/ref/vk/vk_descriptor.c index 0d0b2b95..4618916b 100644 --- a/ref/vk/vk_descriptor.c +++ b/ref/vk/vk_descriptor.c @@ -2,7 +2,7 @@ #include "eiface.h" // ARRAYSIZE -descriptor_pool_t vk_desc; +descriptor_pool_t vk_desc_fixme; qboolean VK_DescriptorInit( void ) { @@ -14,7 +14,7 @@ qboolean VK_DescriptorInit( void ) .descriptorCount = MAX_TEXTURES, }, { .type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, - .descriptorCount = ARRAYSIZE(vk_desc.ubo_sets), + .descriptorCount = ARRAYSIZE(vk_desc_fixme.ubo_sets), /* }, { .type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, @@ -33,7 +33,7 @@ qboolean VK_DescriptorInit( void ) dpci.maxSets = max_desc_sets; - XVK_CHECK(vkCreateDescriptorPool(vk_core.device, &dpci, NULL, &vk_desc.pool)); + XVK_CHECK(vkCreateDescriptorPool(vk_core.device, &dpci, NULL, &vk_desc_fixme.pool)); { const int num_sets = MAX_TEXTURES; @@ -53,21 +53,21 @@ qboolean VK_DescriptorInit( void ) VkDescriptorSetLayout* tmp_layouts = Mem_Malloc(vk_core.pool, sizeof(VkDescriptorSetLayout) * num_sets); const VkDescriptorSetAllocateInfo dsai = { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, - .descriptorPool = vk_desc.pool, + .descriptorPool = vk_desc_fixme.pool, .descriptorSetCount = num_sets, .pSetLayouts = tmp_layouts, }; - XVK_CHECK(vkCreateDescriptorSetLayout(vk_core.device, &dslci, NULL, &vk_desc.one_texture_layout)); + XVK_CHECK(vkCreateDescriptorSetLayout(vk_core.device, &dslci, NULL, &vk_desc_fixme.one_texture_layout)); for (int i = 0; i < num_sets; ++i) - tmp_layouts[i] = vk_desc.one_texture_layout; + tmp_layouts[i] = vk_desc_fixme.one_texture_layout; - XVK_CHECK(vkAllocateDescriptorSets(vk_core.device, &dsai, vk_desc.sets)); + XVK_CHECK(vkAllocateDescriptorSets(vk_core.device, &dsai, vk_desc_fixme.texture_sets)); Mem_Free(tmp_layouts); } { - const int num_sets = ARRAYSIZE(vk_desc.ubo_sets); + const int num_sets = ARRAYSIZE(vk_desc_fixme.ubo_sets); // ... TODO find better place for this; this should be per-pipeline/shader VkDescriptorSetLayoutBinding bindings[] = { { .binding = 0, @@ -84,15 +84,15 @@ qboolean VK_DescriptorInit( void ) VkDescriptorSetLayout* tmp_layouts = Mem_Malloc(vk_core.pool, sizeof(VkDescriptorSetLayout) * num_sets); VkDescriptorSetAllocateInfo dsai = { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, - .descriptorPool = vk_desc.pool, + .descriptorPool = vk_desc_fixme.pool, .descriptorSetCount = num_sets, .pSetLayouts = tmp_layouts, }; - XVK_CHECK(vkCreateDescriptorSetLayout(vk_core.device, &dslci, NULL, &vk_desc.one_uniform_buffer_layout)); + XVK_CHECK(vkCreateDescriptorSetLayout(vk_core.device, &dslci, NULL, &vk_desc_fixme.one_uniform_buffer_layout)); for (int i = 0; i < num_sets; ++i) - tmp_layouts[i] = vk_desc.one_uniform_buffer_layout; + tmp_layouts[i] = vk_desc_fixme.one_uniform_buffer_layout; - XVK_CHECK(vkAllocateDescriptorSets(vk_core.device, &dsai, vk_desc.ubo_sets)); + XVK_CHECK(vkAllocateDescriptorSets(vk_core.device, &dsai, vk_desc_fixme.ubo_sets)); Mem_Free(tmp_layouts); } @@ -102,9 +102,9 @@ qboolean VK_DescriptorInit( void ) void VK_DescriptorShutdown( void ) { - vkDestroyDescriptorPool(vk_core.device, vk_desc.pool, NULL); - vkDestroyDescriptorSetLayout(vk_core.device, vk_desc.one_texture_layout, NULL); - vkDestroyDescriptorSetLayout(vk_core.device, vk_desc.one_uniform_buffer_layout, NULL); + vkDestroyDescriptorPool(vk_core.device, vk_desc_fixme.pool, NULL); + vkDestroyDescriptorSetLayout(vk_core.device, vk_desc_fixme.one_texture_layout, NULL); + vkDestroyDescriptorSetLayout(vk_core.device, vk_desc_fixme.one_uniform_buffer_layout, NULL); } void VK_DescriptorsCreate(vk_descriptors_t *desc) diff --git a/ref/vk/vk_descriptor.h b/ref/vk/vk_descriptor.h index ef6828bf..f9a39e07 100644 --- a/ref/vk/vk_descriptor.h +++ b/ref/vk/vk_descriptor.h @@ -4,16 +4,12 @@ #include "vk_const.h" +// Only used for traditional renderer typedef struct descriptor_pool_s { VkDescriptorPool pool; - // TODO don't expose this, make a function to alloc desc set with given layout instead - int next_free; - //uint32_t *free_set; - - // * 2 because of unorm views for trad renderer - VkDescriptorSet sets[MAX_TEXTURES * 2]; + VkDescriptorSet texture_sets[MAX_TEXTURES]; VkDescriptorSetLayout one_texture_layout; // FIXME HOW THE F @@ -21,7 +17,8 @@ typedef struct descriptor_pool_s VkDescriptorSetLayout one_uniform_buffer_layout; } descriptor_pool_t; -extern descriptor_pool_t vk_desc; +// FIXME: move to traditional renderer +extern descriptor_pool_t vk_desc_fixme; qboolean VK_DescriptorInit( void ); void VK_DescriptorShutdown( void ); @@ -30,7 +27,7 @@ struct xvk_image_s; typedef union { VkDescriptorBufferInfo buffer; VkDescriptorImageInfo image; - VkDescriptorImageInfo *image_array; + const VkDescriptorImageInfo *image_array; VkWriteDescriptorSetAccelerationStructureKHR accel; const struct r_vk_image_s *image_object; } vk_descriptor_value_t; diff --git a/ref/vk/vk_framectl.c b/ref/vk/vk_framectl.c index f0115d4c..956e1a39 100644 --- a/ref/vk/vk_framectl.c +++ b/ref/vk/vk_framectl.c @@ -534,6 +534,7 @@ static rgbdata_t *XVK_ReadPixels( void ) { .debug_name = "screenshot", .width = vk_frame.width, .height = vk_frame.height, + .depth = 1, .mips = 1, .layers = 1, .format = dest_format, diff --git a/ref/vk/vk_image.c b/ref/vk/vk_image.c index e24af1b9..11297ee2 100644 --- a/ref/vk/vk_image.c +++ b/ref/vk/vk_image.c @@ -30,6 +30,11 @@ r_vk_image_t R_VkImageCreate(const r_vk_image_create_t *create) { VkMemoryRequirements memreq; const qboolean is_cubemap = !!(create->flags & kVkImageFlagIsCubemap); + const qboolean is_3d = create->depth > 1; + + ASSERT(create->depth > 0); + + ASSERT(is_cubemap + is_3d != 2); const VkFormat unorm_format = unormFormatFor(create->format); const qboolean create_unorm = @@ -39,10 +44,10 @@ r_vk_image_t R_VkImageCreate(const r_vk_image_create_t *create) { VkImageCreateInfo ici = { .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, - .imageType = VK_IMAGE_TYPE_2D, + .imageType = is_3d ? VK_IMAGE_TYPE_3D : VK_IMAGE_TYPE_2D, .extent.width = create->width, .extent.height = create->height, - .extent.depth = 1, + .extent.depth = create->depth, .mipLevels = create->mips, .arrayLayers = create->layers, .format = create->format, @@ -72,7 +77,7 @@ r_vk_image_t R_VkImageCreate(const r_vk_image_create_t *create) { VkImageViewCreateInfo ivci = { .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, - .viewType = is_cubemap ? VK_IMAGE_VIEW_TYPE_CUBE : VK_IMAGE_VIEW_TYPE_2D, + .viewType = is_cubemap ? VK_IMAGE_VIEW_TYPE_CUBE : (is_3d ? VK_IMAGE_VIEW_TYPE_3D : 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, @@ -98,8 +103,10 @@ r_vk_image_t R_VkImageCreate(const r_vk_image_create_t *create) { image.width = create->width; image.height = create->height; + image.depth = create->depth; image.mips = create->mips; image.layers = create->layers; + image.flags = create->flags; return image; } @@ -251,6 +258,7 @@ void R_VkImageUploadBegin( r_vk_image_t *img ) { void R_VkImageUploadSlice( r_vk_image_t *img, int layer, int mip, int size, const void *data ) { const uint32_t width = Q_max(1, img->width >> mip); const uint32_t height = Q_max(1, img->height >> mip); + const uint32_t depth = Q_max(1, img->depth >> mip); const uint32_t texel_block_size = R_VkImageFormatTexelBlockSize(img->format); const vk_staging_image_args_t staging_args = { @@ -268,7 +276,7 @@ void R_VkImageUploadSlice( r_vk_image_t *img, int layer, int mip, int size, cons .imageExtent = (VkExtent3D){ .width = width, .height = height, - .depth = 1, + .depth = depth, }, }, .layout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, diff --git a/ref/vk/vk_image.h b/ref/vk/vk_image.h index 0252562b..55da9f92 100644 --- a/ref/vk/vk_image.h +++ b/ref/vk/vk_image.h @@ -11,9 +11,10 @@ typedef struct r_vk_image_s { // Used for sRGB-γ-unaware traditional renderer VkImageView view_unorm; - uint32_t width, height; + uint32_t width, height, depth; int mips, layers; VkFormat format; + uint32_t flags; } r_vk_image_t; enum { @@ -24,7 +25,7 @@ enum { typedef struct { const char *debug_name; - uint32_t width, height; + uint32_t width, height, depth; int mips, layers; VkFormat format; VkImageTiling tiling; diff --git a/ref/vk/vk_light.c b/ref/vk/vk_light.c index 61a01d9e..a87f560d 100644 --- a/ref/vk/vk_light.c +++ b/ref/vk/vk_light.c @@ -1,7 +1,7 @@ #include "vk_light.h" #include "vk_buffer.h" #include "vk_mapents.h" -#include "vk_textures.h" +#include "r_textures.h" #include "vk_lightmap.h" #include "vk_cvar.h" #include "vk_common.h" @@ -233,20 +233,21 @@ static qboolean loadRadData( const model_t *map, const char *fmt, ... ) { } // FIXME replace this with findTexturesNamedLike from vk_materials.c + // It has slightly different logic, though, and is a bit scary to change // Try bsp texture first - tex_id = XVK_TextureLookupF("#%s:%s.mip", map->name, texture_name); + tex_id = R_TextureFindByNameF("#%s:%s.mip", map->name, texture_name); // Try wad texture if bsp is not there if (!tex_id && wad_name) { - tex_id = XVK_TextureLookupF("%s.wad/%s.mip", wad_name, texture_name); + tex_id = R_TextureFindByNameF("%s.wad/%s.mip", wad_name, texture_name); } if (!tex_id) { const char *wad = g_map_entities.wadlist; for (; *wad;) { const char *const wad_end = Q_strchr(wad, ';'); - tex_id = XVK_TextureLookupF("%.*s/%s.mip", wad_end - wad, wad, texture_name); + tex_id = R_TextureFindByNameF("%.*s/%s.mip", wad_end - wad, wad, texture_name); if (tex_id) break; wad = wad_end + 1; diff --git a/ref/vk/vk_lightmap.c b/ref/vk/vk_lightmap.c index 54286029..e1a755bf 100644 --- a/ref/vk/vk_lightmap.c +++ b/ref/vk/vk_lightmap.c @@ -1,6 +1,6 @@ #include "vk_lightmap.h" #include "vk_common.h" -#include "vk_textures.h" +#include "r_textures.h" #include "vk_cvar.h" #include "com_strings.h" @@ -115,7 +115,7 @@ static void LM_UploadBlock( qboolean dynamic ) r_lightmap.size = r_lightmap.width * r_lightmap.height * 4; r_lightmap.flags = IMAGE_HAS_COLOR; r_lightmap.buffer = gl_lms.lightmap_buffer; - tglob.lightmapTextures[i] = VK_LoadTextureInternal( lmName, &r_lightmap, TF_FONT|TF_ATLAS_PAGE|TF_NOMIPMAP ); + tglob.lightmapTextures[i] = R_TextureUploadFromBuffer( lmName, &r_lightmap, TF_ATLAS_PAGE|TF_NOMIPMAP|TF_CLAMP, false ); if( ++gl_lms.current_lightmap_texture == MAX_LIGHTMAPS ) gEngine.Host_Error( "AllocBlock: full\n" ); @@ -227,7 +227,7 @@ void VK_UploadLightmap( void ) void VK_ClearLightmap( void ) { for (int i = 0; i < gl_lms.current_lightmap_texture; ++i) - VK_FreeTexture(tglob.lightmapTextures[i]); + R_TextureFree(tglob.lightmapTextures[i]); gl_lms.current_lightmap_texture = 0; LM_InitBlock(); diff --git a/ref/vk/vk_mapents.c b/ref/vk/vk_mapents.c index 8726f43c..40d47e20 100644 --- a/ref/vk/vk_mapents.c +++ b/ref/vk/vk_mapents.c @@ -1,7 +1,7 @@ #include "vk_common.h" #include "vk_mapents.h" #include "vk_core.h" // TODO we need only pool from there, not the entire vulkan garbage -#include "vk_textures.h" +#include "r_textures.h" #include "vk_logs.h" #include "eiface.h" // ARRAYSIZE @@ -397,7 +397,7 @@ static void readFuncAny( const entity_props_t *const props, uint32_t have_fields static void addPatchSurface( const entity_props_t *props, uint32_t have_fields ) { const model_t* const map = gEngine.pfnGetModelByIndex( 1 ); const int num_surfaces = map->numsurfaces; - const qboolean should_remove = (have_fields == Field__xvk_surface_id) || (have_fields & Field__xvk_texture && props->_xvk_texture[0] == '\0'); + const qboolean should_remove = (have_fields == Field__xvk_surface_id) || (have_fields & Field__xvk_material && props->_xvk_material[0] == '\0'); for (int i = 0; i < props->_xvk_surface_id.num; ++i) { const int index = props->_xvk_surface_id.values[i]; @@ -412,8 +412,7 @@ static void addPatchSurface( const entity_props_t *props, uint32_t have_fields ) g_patch.surfaces_count = num_surfaces; for (int i = 0; i < num_surfaces; ++i) { g_patch.surfaces[i].flags = Patch_Surface_NoPatch; - g_patch.surfaces[i].tex_id = -1; - g_patch.surfaces[i].tex = NULL; + g_patch.surfaces[i].material_ref.index = -1; } } @@ -425,22 +424,15 @@ static void addPatchSurface( const entity_props_t *props, uint32_t have_fields ) continue; } - if (have_fields & Field__xvk_texture) { - const int tex_id = XVK_FindTextureNamedLike( props->_xvk_texture ); - DEBUG("Patch for surface %d with texture \"%s\" -> %d", index, props->_xvk_texture, tex_id); - psurf->tex_id = tex_id; - - // Find texture_t for this index - for (int i = 0; i < map->numtextures; ++i) { - const texture_t* const tex = map->textures[i]; - if (tex->gl_texturenum == tex_id) { - psurf->tex = tex; - psurf->tex_id = -1; - break; - } + if (have_fields & Field__xvk_material) { + const r_vk_material_ref_t mat = R_VkMaterialGetForName( props->_xvk_material ); + if (mat.index >= 0) { + DEBUG("Patch for surface %d with material \"%s\" -> %d", index, props->_xvk_material, mat.index); + psurf->material_ref = mat; + psurf->flags |= Patch_Surface_Material; + } else { + ERR("Cannot patch surface %d with material \"%s\": material not found", index, props->_xvk_material); } - - psurf->flags |= Patch_Surface_Texture; } if (have_fields & Field__light) { @@ -457,13 +449,13 @@ static void addPatchSurface( const entity_props_t *props, uint32_t have_fields ) Vector4Copy(props->_xvk_svec, psurf->s_vec); Vector4Copy(props->_xvk_tvec, psurf->t_vec); psurf->flags |= Patch_Surface_STvecs; - DEBUG("Patch for surface %d: assign stvec", index); + DEBUG("Patch for surface %d: assign st_vec", index); } if (have_fields & Field__xvk_tex_scale) { Vector2Copy(props->_xvk_tex_scale, psurf->tex_scale); psurf->flags |= Patch_Surface_TexScale; - DEBUG("Patch for surface %d: assign tex scale %f %f", + DEBUG("Patch for surface %d: assign tex_scale %f %f", index, psurf->tex_scale[0], psurf->tex_scale[1] ); } @@ -471,7 +463,7 @@ static void addPatchSurface( const entity_props_t *props, uint32_t have_fields ) if (have_fields & Field__xvk_tex_offset) { Vector2Copy(props->_xvk_tex_offset, psurf->tex_offset); psurf->flags |= Patch_Surface_TexOffset; - DEBUG("Patch for surface %d: assign tex offset %f %f", + DEBUG("Patch for surface %d: assign tex_offset %f %f", index, psurf->tex_offset[0], psurf->tex_offset[1] ); } @@ -533,7 +525,7 @@ static void patchFuncAnyEntity( const entity_props_t *props, uint32_t have_field Q_strncpy(from_tex, from_begin, Q_min(sizeof from_tex, from_len + 1)); Q_strncpy(to_mat, to_begin, Q_min(sizeof to_mat, to_len + 1)); - const int from_tex_index = XVK_FindTextureNamedLike(from_tex); + const int from_tex_index = R_TextureFindByNameLike(from_tex); const r_vk_material_ref_t to_mat_ref = R_VkMaterialGetForName(to_mat); DEBUG("Adding mapping from tex \"%s\"(%d) to mat \"%s\"(%d) for entity=%d", diff --git a/ref/vk/vk_mapents.h b/ref/vk/vk_mapents.h index 4e3c1dc9..ed2a751d 100644 --- a/ref/vk/vk_mapents.h +++ b/ref/vk/vk_mapents.h @@ -23,7 +23,7 @@ X(11, string, target, String) \ X(12, int, style, Int) \ X(13, int_array_t, _xvk_surface_id, IntArray) \ - X(14, string, _xvk_texture, String) \ + X(14, string, _xvk_material, String) \ X(15, int_array_t, _xvk_ent_id, IntArray) \ X(16, float, _xvk_radius, Float) \ X(17, vec4_t, _xvk_svec, Vec4) \ @@ -181,7 +181,7 @@ void XVK_ParseMapPatches( void ); enum { Patch_Surface_NoPatch = 0, Patch_Surface_Delete = (1<<0), - Patch_Surface_Texture = (1<<1), + Patch_Surface_Material = (1<<1), Patch_Surface_Emissive = (1<<2), Patch_Surface_STvecs = (1<<3), Patch_Surface_TexOffset = (1<<4), @@ -193,11 +193,7 @@ struct texture_s; typedef struct { uint32_t flags; - // Static texture index in case there's no texture_s pointer - int tex_id; - - // Pointer to texture_s data (which also may include animation) - const struct texture_s *tex; + r_vk_material_ref_t material_ref; vec3_t emissive; diff --git a/ref/vk/vk_materials.c b/ref/vk/vk_materials.c index 583a429b..8fb9318b 100644 --- a/ref/vk/vk_materials.c +++ b/ref/vk/vk_materials.c @@ -4,6 +4,7 @@ #include "vk_const.h" #include "profiler.h" #include "vk_logs.h" +#include "unordered_roadmap.h" #include @@ -12,12 +13,13 @@ #define MAX_INCLUDE_DEPTH 4 #define MAX_MATERIALS 2048 +#define MAX_NEW_MATERIALS 128 static r_vk_material_t k_default_material = { .tex_base_color = -1, .tex_metalness = 0, .tex_roughness = 0, - .tex_normalmap = 0, + .tex_normalmap = 0, // 0 means no normal map, checked in shaders .metalness = 0.f, .roughness = 1.f, @@ -60,6 +62,11 @@ typedef struct { r_vk_material_t material; } material_entry_t; +typedef struct { + urmom_header_t hdr_; + int mat_id; // into g_materials.table +} material_name_map_t; + static struct { int count; material_entry_t table[MAX_MATERIALS]; @@ -69,7 +76,8 @@ static struct { // TODO embed into tex_to_mat r_vk_material_per_mode_t for_rendermode[kRenderTransAdd+1]; - // TODO for name + urmom_desc_t map_desc; + material_name_map_t map[MAX_NEW_MATERIALS]; } g_materials; static struct { @@ -83,7 +91,7 @@ static struct { 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 = R_VkLoadTexture( filename, colorspace, force_reload); + const int tex_id = R_TextureUploadFromFileExAcquire( 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; @@ -115,6 +123,30 @@ static void printMaterial(int index) { ); } +static void acquireTexturesForMaterial( int index ) { + const r_vk_material_t *mat = &g_materials.table[index].material; + DEBUG("%s(%d: %s)", __FUNCTION__, index, g_materials.table[index].name); + R_TextureAcquire(mat->tex_base_color); + R_TextureAcquire(mat->tex_metalness); + R_TextureAcquire(mat->tex_roughness); + if (mat->tex_normalmap > 0) + R_TextureAcquire(mat->tex_normalmap); +} + +static void releaseTexturesForMaterialPtr( const r_vk_material_t *mat ) { + R_TextureRelease(mat->tex_base_color); + R_TextureRelease(mat->tex_metalness); + R_TextureRelease(mat->tex_roughness); + if (mat->tex_normalmap > 0) + R_TextureRelease(mat->tex_normalmap); +} + +static void releaseTexturesForMaterial( int index ) { + const r_vk_material_t *mat = &g_materials.table[index].material; + DEBUG("%s(%d: %s)", __FUNCTION__, index, g_materials.table[index].name); + releaseTexturesForMaterialPtr( mat ); +} + 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); @@ -123,6 +155,7 @@ static int addMaterial(const char *name, const r_vk_material_t* mat) { 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; + acquireTexturesForMaterial(g_materials.count); printMaterial(g_materials.count); @@ -130,7 +163,7 @@ static int addMaterial(const char *name, const r_vk_material_t* mat) { } static void assignMaterialForTexture(const char *name, int for_tex_id, int mat_id) { - const char* const tex_name = findTexture(for_tex_id)->name; + const char* const tex_name = R_TextureGetNameByIndex(for_tex_id); DEBUG("Assigning material \"%s\" for_tex_id=\"%s\"(%d)", name, tex_name, for_tex_id); ASSERT(mat_id >= 0); @@ -155,7 +188,6 @@ static void loadMaterialsFromFile( const char *filename, int depth ) { r_vk_material_t current_material = k_default_material; int for_tex_id = -1; - int dummy_named_texture_fixme = -1; qboolean force_reload = false; qboolean create = false; qboolean metalness_set = false; @@ -192,7 +224,6 @@ static void loadMaterialsFromFile( const char *filename, int depth ) { if (key[0] == '{') { current_material = k_default_material; for_tex_id = -1; - dummy_named_texture_fixme = -1; force_reload = false; create = false; metalness_set = false; @@ -203,38 +234,44 @@ static void loadMaterialsFromFile( const char *filename, int depth ) { } if (key[0] == '}') { - if (for_tex_id < 0 && !create) { + 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; } - if (!name[0]) { + if (name[0] == '\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, colorspace); \ - if (tex_id < 0) { \ - ERR("Failed to load texture \"%s\" for "#name"", name); \ + // Start with *default texture for base color, it will be acquired if no replacement is specified or could be loaded. + current_material.tex_base_color = for_tex_id >= 0 ? for_tex_id : 0; + +#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, colorspace); \ + if (tex_id < 0) { \ + ERR("Failed to load texture \"%s\" for "#name"", name); \ + if (current_material.field > 0) \ + R_TextureAcquire(current_material.field); \ + } else { \ + current_material.field = tex_id; \ + } \ } else { \ - current_material.field = tex_id; \ + if (current_material.field > 0) \ + R_TextureAcquire(current_material.field); \ } \ - }} while(0) + } while(0) 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 = for_tex_id >= 0 ? for_tex_id : 0; - 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; @@ -242,21 +279,36 @@ static void loadMaterialsFromFile( const char *filename, int depth ) { const int mat_id = addMaterial(name, ¤t_material); + releaseTexturesForMaterialPtr(¤t_material); + 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); + ERR("Cannot add material \"%s\" for_tex_id=\"%s\"(%d)", name, for_tex_id >= 0 ? R_TextureGetNameByIndex(for_tex_id) : "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); + if (create) + { + const urmom_insert_t insert = urmomInsert(&g_materials.map_desc, name); + if (insert.index < 0) { + ERR("Cannot add new material '%s', ran out of space (max=%d)", name, MAX_NEW_MATERIALS); + continue; + } + + material_name_map_t *const item = g_materials.map + insert.index; + + if (!insert.created) + WARN("Replacing material '%s'@%d %d=>%d", name, insert.index, item->mat_id, mat_id); + else + DEBUG("Mapping new material '%s'@%d => %d", name, insert.index, mat_id); + + item->mat_id = 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; + const char* const tex_name = R_TextureGetNameByIndex(for_tex_id); 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; @@ -280,21 +332,23 @@ static void loadMaterialsFromFile( const char *filename, int depth ) { if (!pos) break; + //DEBUG("key=\"%s\", value=\"%s\"", key, value); + 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(); - for_tex_id = XVK_FindTextureNamedLike(value); + for_tex_id = R_TextureFindByNameLike(value); + DEBUG("R_TextureFindByNameLike(%s)=%d", value, for_tex_id); + if (for_tex_id >= 0) + ASSERT(Q_stristr(R_TextureGetNameByIndex(for_tex_id), value) != NULL); g_stats.texture_lookup_duration_ns += aprof_time_now_ns() - lookup_begin_ns; g_stats.texture_lookups++; Q_strncpy(name, value, sizeof name); } else if (Q_stricmp(key, "new") == 0) { 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) { @@ -382,14 +436,29 @@ static int findFilenameExtension(const char *s, int len) { return len; } +static void materialsReleaseTextures( void ) { + for (int i = 1; i < g_materials.count; ++i) + releaseTexturesForMaterial(i); +} + void R_VkMaterialsReload( void ) { const uint64_t begin_time_ns = aprof_time_now_ns(); memset(&g_stats, 0, sizeof(g_stats)); + materialsReleaseTextures(); + g_materials.count = 1; memset(g_materials.tex_to_mat, 0, sizeof g_materials.tex_to_mat); + g_materials.map_desc = (urmom_desc_t){ + .type = kUrmomStringInsensitive, + .array = g_materials.map, + .count = COUNTOF(g_materials.map), + .item_size = sizeof(g_materials.map[0]), + }; + urmomInit(&g_materials.map_desc); + for (int i = 0; i < COUNTOF(g_materials.for_rendermode); ++i) g_materials.for_rendermode[i].count = 0; @@ -481,7 +550,7 @@ r_vk_material_t R_VkMaterialGetForTextureWithFlags( int tex_index, uint32_t flag // 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; + const char* texname = R_TextureGetNameByIndex(tex_index); DEBUG("Would try to load texture files by default names of \"%s\"", texname); // If no PBR textures found, continue using legacy+default ones @@ -499,17 +568,24 @@ r_vk_material_t R_VkMaterialGetForTextureWithFlags( int tex_index, uint32_t flag } r_vk_material_ref_t R_VkMaterialGetForName( const char *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); + // Find in internal map first + // New materials have preference over texture names + const int index = urmomFind(&g_materials.map_desc, name); + if (index >= 0) + return (r_vk_material_ref_t){.index = g_materials.map[index].mat_id}; + DEBUG("Couldn't find material '%s', fallback to texture lookup", name); + + // Find by texture name + const int tex_id = R_TextureFindByNameLike(name); + if (tex_id <= 0) { + ERR("Neither material nor texture with name \"%s\" was found", name); return (r_vk_material_ref_t){.index = -1,}; } - ASSERT(dummy_tex_id_fixme >= 0); - ASSERT(dummy_tex_id_fixme < MAX_TEXTURES); + ASSERT(tex_id > 0); + ASSERT(tex_id < MAX_TEXTURES); - return (r_vk_material_ref_t){.index = g_materials.tex_to_mat[dummy_tex_id_fixme].mat_id}; + return (r_vk_material_ref_t){.index = g_materials.tex_to_mat[tex_id].mat_id}; } r_vk_material_t R_VkMaterialGetForRef( r_vk_material_ref_t ref ) { @@ -547,3 +623,8 @@ qboolean R_VkMaterialGetEx( int tex_id, int rendermode, r_vk_material_t *out_mat return false; } + +void R_VkMaterialsShutdown( void ) { + materialsReleaseTextures(); +} + diff --git a/ref/vk/vk_materials.h b/ref/vk/vk_materials.h index aa803dee..49eef84f 100644 --- a/ref/vk/vk_materials.h +++ b/ref/vk/vk_materials.h @@ -34,6 +34,8 @@ typedef struct { int index; } r_vk_material_ref_t; // TODO: track "version" in high bits? void R_VkMaterialsReload( void ); +void R_VkMaterialsShutdown( void ); + struct model_s; void R_VkMaterialsLoadForModel( const struct model_s* mod ); diff --git a/ref/vk/vk_overlay.c b/ref/vk/vk_overlay.c index 43dd9ad3..33ef183c 100644 --- a/ref/vk/vk_overlay.c +++ b/ref/vk/vk_overlay.c @@ -91,7 +91,7 @@ void R_DrawStretchPic( float x, float y, float w, float h, float s1, float t1, f if (!p) { /* gEngine.Con_Printf(S_ERROR "VK FIXME %s(%f, %f, %f, %f, %f, %f, %f, %f, %d(%s))\n", __FUNCTION__, */ - /* x, y, w, h, s1, t1, s2, t2, texnum, findTexture(texnum)->name); */ + /* x, y, w, h, s1, t1, s2, t2, texnum, R_TextureGetByIndex(texnum)->name); */ return; } @@ -120,7 +120,7 @@ static void drawFill( float x, float y, float w, float h, int r, int g, int b, i const int prev_blending = vk_renderstate.blending_mode; vk_renderstate.blending_mode = blending_mode; vk_renderstate.tri_color = (color_rgba8_t){r, g, b, a}; - R_DrawStretchPic(x, y, w, h, 0, 0, 1, 1, VK_FindTexture(REF_WHITE_TEXTURE)); + R_DrawStretchPic(x, y, w, h, 0, 0, 1, 1, /* TODO what is this garbage, get it by number */ R_TextureFindByName(REF_WHITE_TEXTURE)); vk_renderstate.tri_color = prev_color; vk_renderstate.blending_mode = prev_blending; } @@ -145,7 +145,7 @@ static qboolean createPipelines( void ) /* }; */ VkDescriptorSetLayout descriptor_layouts[] = { - vk_desc.one_texture_layout, + vk_desc_fixme.one_texture_layout, }; VkPipelineLayoutCreateInfo plci = { @@ -264,12 +264,12 @@ static void drawOverlay( VkCommandBuffer cmdbuf ) { for (int i = 0; i < g2d.batch_count && g2d.batch[i].vertex_count > 0; ++i) { - vk_texture_t *texture = findTexture(g2d.batch[i].texture); + const VkDescriptorSet tex_unorm = R_VkTextureGetDescriptorUnorm( g2d.batch[i].texture ); const VkPipeline pipeline = g2d.pipelines[g2d.batch[i].blending_mode]; - if (texture->vk.descriptor_unorm) + if (tex_unorm) { vkCmdBindPipeline(cmdbuf, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); - vkCmdBindDescriptorSets(cmdbuf, VK_PIPELINE_BIND_POINT_GRAPHICS, g2d.pipeline_layout, 0, 1, &texture->vk.descriptor_unorm, 0, NULL); + vkCmdBindDescriptorSets(cmdbuf, VK_PIPELINE_BIND_POINT_GRAPHICS, g2d.pipeline_layout, 0, 1, &tex_unorm, 0, NULL); vkCmdDraw(cmdbuf, g2d.batch[i].vertex_count, 1, g2d.batch[i].vertex_offset, 0); } // FIXME else what? } @@ -291,7 +291,7 @@ void R_DrawStretchRaw( float x, float y, float w, float h, int cols, int rows, c void R_DrawTileClear( int texnum, int x, int y, int w, int h ) { - PRINT_NOT_IMPLEMENTED_ARGS("%s", findTexture(texnum)->name ); + PRINT_NOT_IMPLEMENTED_ARGS("%s", R_TextureGetNameByIndex(texnum)); } void CL_FillRGBA( float x, float y, float w, float h, int r, int g, int b, int a ) diff --git a/ref/vk/vk_ray_model.c b/ref/vk/vk_ray_model.c index 918ac552..478f3fa8 100644 --- a/ref/vk/vk_ray_model.c +++ b/ref/vk/vk_ray_model.c @@ -1,7 +1,7 @@ #include "vk_ray_internal.h" #include "vk_rtx.h" -#include "vk_textures.h" +#include "r_textures.h" #include "vk_materials.h" #include "vk_geometry.h" #include "vk_render.h" diff --git a/ref/vk/vk_render.c b/ref/vk/vk_render.c index 47742e4f..0ba0b80a 100644 --- a/ref/vk/vk_render.c +++ b/ref/vk/vk_render.c @@ -62,10 +62,10 @@ static qboolean createPipelines( void ) /* }; */ VkDescriptorSetLayout descriptor_layouts[] = { - vk_desc.one_uniform_buffer_layout, - vk_desc.one_texture_layout, - vk_desc.one_texture_layout, - vk_desc.one_uniform_buffer_layout, + vk_desc_fixme.one_uniform_buffer_layout, + vk_desc_fixme.one_texture_layout, + vk_desc_fixme.one_texture_layout, + vk_desc_fixme.one_uniform_buffer_layout, }; VkPipelineLayoutCreateInfo plci = { @@ -321,7 +321,7 @@ qboolean VK_RenderInit( void ) { .descriptorCount = 1, .descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, .pBufferInfo = &dbi_uniform_data, - .dstSet = vk_desc.ubo_sets[0], // FIXME + .dstSet = vk_desc_fixme.ubo_sets[0], // FIXME }, { .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, .dstBinding = 0, @@ -329,7 +329,7 @@ qboolean VK_RenderInit( void ) { .descriptorCount = 1, .descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, .pBufferInfo = &dbi_uniform_lights, - .dstSet = vk_desc.ubo_sets[1], // FIXME + .dstSet = vk_desc_fixme.ubo_sets[1], // FIXME }}; vkUpdateDescriptorSets(vk_core.device, ARRAYSIZE(wds), wds, 0, NULL); } @@ -552,7 +552,7 @@ void VK_RenderEnd( VkCommandBuffer cmdbuf, qboolean draw ) vkCmdBindIndexBuffer(cmdbuf, geom_buffer, 0, VK_INDEX_TYPE_UINT16); } - vkCmdBindDescriptorSets(cmdbuf, VK_PIPELINE_BIND_POINT_GRAPHICS, g_render.pipeline_layout, 3, 1, vk_desc.ubo_sets + 1, 1, &dlights_ubo_offset); + vkCmdBindDescriptorSets(cmdbuf, VK_PIPELINE_BIND_POINT_GRAPHICS, g_render.pipeline_layout, 3, 1, vk_desc_fixme.ubo_sets + 1, 1, &dlights_ubo_offset); for (int i = 0; i < g_render_state.num_draw_commands; ++i) { const draw_command_t *const draw = g_render_state.draw_commands + i; @@ -579,7 +579,7 @@ void VK_RenderEnd( VkCommandBuffer cmdbuf, qboolean draw ) if (ubo_offset != draw->draw.ubo_offset) { ubo_offset = draw->draw.ubo_offset; - vkCmdBindDescriptorSets(cmdbuf, VK_PIPELINE_BIND_POINT_GRAPHICS, g_render.pipeline_layout, 0, 1, vk_desc.ubo_sets, 1, &ubo_offset); + vkCmdBindDescriptorSets(cmdbuf, VK_PIPELINE_BIND_POINT_GRAPHICS, g_render.pipeline_layout, 0, 1, vk_desc_fixme.ubo_sets, 1, &ubo_offset); } if (pipeline != draw->draw.pipeline_index) { @@ -589,14 +589,16 @@ 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_unorm, 0, NULL); + const VkDescriptorSet lm_unorm = R_VkTextureGetDescriptorUnorm(lightmap); + vkCmdBindDescriptorSets(cmdbuf, VK_PIPELINE_BIND_POINT_GRAPHICS, g_render.pipeline_layout, 2, 1, &lm_unorm, 0, NULL); } if (texture != draw->draw.texture) { texture = draw->draw.texture; + const VkDescriptorSet tex_unorm = R_VkTextureGetDescriptorUnorm(texture); // TODO names/enums for binding points - vkCmdBindDescriptorSets(cmdbuf, VK_PIPELINE_BIND_POINT_GRAPHICS, g_render.pipeline_layout, 1, 1, &findTexture(texture)->vk.descriptor_unorm, 0, NULL); + vkCmdBindDescriptorSets(cmdbuf, VK_PIPELINE_BIND_POINT_GRAPHICS, g_render.pipeline_layout, 1, 1, &tex_unorm, 0, NULL); } // Only indexed mode is supported diff --git a/ref/vk/vk_rmain.c b/ref/vk/vk_rmain.c index 0f3995da..9e295387 100644 --- a/ref/vk/vk_rmain.c +++ b/ref/vk/vk_rmain.c @@ -1,7 +1,7 @@ #include "vk_core.h" #include "vk_cvar.h" #include "vk_common.h" -#include "vk_textures.h" +#include "r_textures.h" #include "vk_renderstate.h" #include "vk_overlay.h" #include "vk_scene.h" @@ -38,14 +38,13 @@ static qboolean R_SetDisplayTransform( ref_screen_rotation_t rotate, int x, int return true; } -// only called for GL contexts static void GL_SetupAttributes( int safegl ) { - PRINT_NOT_IMPLEMENTED(); + // Nothing to do for Vulkan } static void GL_ClearExtensions( void ) { - PRINT_NOT_IMPLEMENTED(); + // Nothing to do for Vulkan } static void GL_BackendStartFrame_UNUSED( void ) { @@ -63,13 +62,13 @@ static void R_ShowTextures_UNUSED( void ) } // texture management -static const byte *R_GetTextureOriginalBuffer( unsigned int idx ) +static const byte *R_GetTextureOriginalBuffer_UNUSED( unsigned int idx ) { PRINT_NOT_IMPLEMENTED(); return NULL; } -static void GL_ProcessTexture( int texnum, float gamma, int topColor, int bottomColor ) +static void GL_ProcessTexture_UNUSED( int texnum, float gamma, int topColor, int bottomColor ) { PRINT_NOT_IMPLEMENTED(); } @@ -115,6 +114,30 @@ static void R_InitSkyClouds( struct mip_s *mt, struct texture_s *tx, qboolean cu extern void GL_SubdivideSurface( msurface_t *fa ); +static void Mod_UnloadTextures( model_t *mod ) +{ + ASSERT( mod != NULL ); + + switch( mod->type ) + { + case mod_studio: + Mod_StudioUnloadTextures( mod->cache.data ); + break; + case mod_alias: + // FIXME Mod_AliasUnloadTextures( mod->cache.data ); + break; + case mod_brush: + VK_BrushUnloadTextures( mod ); + break; + case mod_sprite: + Mod_SpriteUnloadTextures( mod->cache.data ); + break; + default: + ASSERT( 0 ); + break; + } +} + static qboolean Mod_ProcessRenderData( model_t *mod, qboolean create, const byte *buffer ) { qboolean loaded = true; @@ -152,6 +175,7 @@ static qboolean Mod_ProcessRenderData( model_t *mod, qboolean create, const byte gEngine.drawFuncs->Mod_ProcessUserData( mod, create, buffer ); if( !create ) { + Mod_UnloadTextures( mod ); switch( mod->type ) { case mod_brush: // Empirically, this function only attempts to destroy the worldmodel before loading the next map. @@ -221,20 +245,26 @@ static const char *getParmName(int parm) static int VK_RefGetParm( int parm, int arg ) { - vk_texture_t *tex = NULL; - + // TODO all PARM_TEX handle in r_texture internally switch(parm){ case PARM_TEX_WIDTH: - case PARM_TEX_SRC_WIDTH: // TODO why is this separate? - tex = findTexture(arg); - return tex->width; case PARM_TEX_HEIGHT: + case PARM_TEX_SRC_WIDTH: // TODO why is this separate? case PARM_TEX_SRC_HEIGHT: - tex = findTexture(arg); - return tex->height; case PARM_TEX_FLAGS: - tex = findTexture(arg); - return tex->flags; + /* TODO + case PARM_TEX_SKYBOX: + case PARM_TEX_SKYTEXNUM: + case PARM_TEX_LIGHTMAP: + case PARM_TEX_TARGET: + case PARM_TEX_TEXNUM: + case PARM_TEX_DEPTH: + case PARM_TEX_GLFORMAT: + case PARM_TEX_ENCODE: + case PARM_TEX_MIPCOUNT: + case PARM_TEX_MEMORY: + */ + return R_TexturesGetParm( parm, arg ); case PARM_MODERNFLASHLIGHT: if (CVAR_TO_BOOL( vk_rtx )) { return true; @@ -452,6 +482,32 @@ static int VGUI_GenerateTexture( void ) return 0; } +static const byte* R_TextureData_UNUSED( unsigned int texnum ) +{ + PRINT_NOT_IMPLEMENTED_ARGS("texnum=%d", texnum); + // We don't store original texture data + // TODO do we need to? + return NULL; +} + +int R_CreateTexture_UNUSED( const char *name, int width, int height, const void *buffer, texFlags_t flags ) +{ + PRINT_NOT_IMPLEMENTED_ARGS("name=%s width=%d height=%d buffer=%p flags=%08x", name, width, height, buffer, flags); + return 0; +} + +static int R_LoadTextureArray_UNUSED( const char **names, int flags ) +{ + PRINT_NOT_IMPLEMENTED(); + return 0; +} + +int R_CreateTextureArray_UNUSED( const char *name, int width, int height, int depth, const void *buffer, texFlags_t flags ) +{ + PRINT_NOT_IMPLEMENTED_ARGS("name=%s width=%d height=%d buffer=%p flags=%08x", name, width, height, buffer, flags); + return 0; +} + static const ref_device_t *pfnGetRenderDevice( unsigned int idx ) { if( idx >= vk_core.num_devices ) @@ -467,6 +523,7 @@ static const ref_interface_t gReffuncs = R_GetConfigName, R_SetDisplayTransform, + // only called for GL contexts GL_SetupAttributes, .GL_InitExtensions = NULL, // Unused in Vulkan renderer GL_ClearExtensions, @@ -487,13 +544,16 @@ static const ref_interface_t gReffuncs = CL_AddCustomBeam, R_ProcessEntData, + // debug .R_ShowTextures = R_ShowTextures_UNUSED, - R_GetTextureOriginalBuffer, - VK_LoadTextureFromBuffer, - GL_ProcessTexture, - XVK_SetupSky, + // texture management + .R_GetTextureOriginalBuffer = R_GetTextureOriginalBuffer_UNUSED, + .GL_LoadTextureFromBuffer = R_TextureUploadFromBuffer, + .GL_ProcessTexture = GL_ProcessTexture_UNUSED, + .R_SetupSky = R_TextureSetupSky, + // 2D R_Set2DMode, R_DrawStretchRaw, R_DrawStretchPic, @@ -502,11 +562,14 @@ static const ref_interface_t gReffuncs = CL_FillRGBABlend, R_WorldToScreen, + // screenshot, cubemapshot VID_ScreenShot, VID_CubemapShot, + // light R_LightPoint, + // decals R_DecalShoot, R_DecalRemoveAll, R_CreateDecalList, @@ -540,15 +603,17 @@ static const ref_interface_t gReffuncs = R_SetCurrentEntity, R_SetCurrentModel, - VK_FindTexture, - VK_TextureName, - VK_TextureData, - VK_LoadTextureExternal, - VK_CreateTexture, - VK_LoadTextureArray, - VK_CreateTextureArray, - VK_FreeTexture, + // Texture tools + .GL_FindTexture = R_TextureFindByName, + .GL_TextureName = R_TextureGetNameByIndex, + .GL_TextureData = R_TextureData_UNUSED, + .GL_LoadTexture = R_TextureUploadFromFile, + .GL_CreateTexture = R_CreateTexture_UNUSED, + .GL_LoadTextureArray = R_LoadTextureArray_UNUSED, + .GL_CreateTextureArray = R_CreateTextureArray_UNUSED, + .GL_FreeTexture = R_TextureFree, + // Decals manipulating (draw & remove) DrawSingleDecal, R_DecalSetupVerts, R_EntityRemoveDecals, diff --git a/ref/vk/vk_rpart.c b/ref/vk/vk_rpart.c index aa892fd5..817474a2 100644 --- a/ref/vk/vk_rpart.c +++ b/ref/vk/vk_rpart.c @@ -16,7 +16,7 @@ GNU General Public License for more details. #include "vk_rpart.h" #include "vk_triapi.h" #include "camera.h" -#include "vk_textures.h" // tglob.particleTexture +#include "r_textures.h" // tglob.particleTexture #include "vk_common.h" #include "vk_sprite.h" // R_GetSpriteTexture diff --git a/ref/vk/vk_rtx.c b/ref/vk/vk_rtx.c index f92e9513..2fd7a31a 100644 --- a/ref/vk/vk_rtx.c +++ b/ref/vk/vk_rtx.c @@ -53,7 +53,8 @@ X(Buffer, lights) \ X(Buffer, light_grid) \ X(Texture, textures) \ - X(Texture, skybox) + X(Texture, skybox) \ + X(Texture, blue_noise_texture) enum { #define RES_ENUM(type, name) ExternalResource_##name, @@ -123,11 +124,15 @@ void VK_RayNewMap( void ) { .type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, // FIXME we should pick tglob.dii_all_textures here directly .value = (vk_descriptor_value_t){ - .image = { - .sampler = tglob.default_sampler_fixme, - .imageView = tglob.skybox_cube.vk.image.view ? tglob.skybox_cube.vk.image.view : tglob.cubemap_placeholder.vk.image.view, - .imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, - }, + .image = R_VkTexturesGetSkyboxDescriptorImageInfo(), + }, + }; + + g_rtx.res[ExternalResource_blue_noise_texture].resource = (vk_resource_t){ + .type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + // FIXME we should pick tglob.dii_all_textures here directly + .value = (vk_descriptor_value_t){ + .image = R_VkTexturesGetBlueNoiseImageInfo(), }, }; } @@ -429,6 +434,7 @@ static void reloadMainpipe(void) { .debug_name = mr->name, .width = FRAME_WIDTH, .height = FRAME_HEIGHT, + .depth = 1, .mips = 1, .layers = 1, .format = mr->image_format, @@ -614,7 +620,7 @@ qboolean VK_RayInit( void ) g_rtx.res[ExternalResource_textures].resource = (vk_resource_t){ .type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, .value = (vk_descriptor_value_t){ - .image_array = tglob.dii_all_textures, + .image_array = R_VkTexturesGetAllDescriptorsArray(), } }; g_rtx.res[ExternalResource_textures].refcount = 1; diff --git a/ref/vk/vk_scene.c b/ref/vk/vk_scene.c index aba308f4..c6c8e132 100644 --- a/ref/vk/vk_scene.c +++ b/ref/vk/vk_scene.c @@ -13,7 +13,7 @@ #include "vk_beams.h" #include "vk_light.h" #include "vk_rtx.h" -#include "vk_textures.h" +#include "r_textures.h" #include "vk_cvar.h" #include "vk_materials.h" #include "camera.h" @@ -257,8 +257,7 @@ void R_NewMap( void ) { // Make sure that we're not rendering anything before starting to mess with GPU objects XVK_CHECK(vkDeviceWaitIdle(vk_core.device)); - - XVK_SetupSky( gEngine.pfnGetMoveVars()->skyName ); + R_TextureSetupSky( gEngine.pfnGetMoveVars()->skyName ); loadMap(map); diff --git a/ref/vk/vk_sprite.c b/ref/vk/vk_sprite.c index 2bc8b670..c538b7fd 100644 --- a/ref/vk/vk_sprite.c +++ b/ref/vk/vk_sprite.c @@ -1,5 +1,5 @@ #include "vk_sprite.h" -#include "vk_textures.h" +#include "r_textures.h" #include "camera.h" #include "vk_render.h" #include "vk_geometry.h" @@ -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_LoadTextureExternal( texname, pin, pinframe.width * pinframe.height * bytes, ctx->r_texFlags ); + gl_texturenum = R_TextureUploadFromFile( 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_LoadTextureExternal( texname, pin, pinframe.width * pinframe.height * bytes, ctx->r_texFlags ); + gl_texturenum = R_TextureUploadFromFile( texname, pin, pinframe.width * pinframe.height * bytes, ctx->r_texFlags ); } // setup frame description @@ -406,7 +406,6 @@ Loading a bitmap image as sprite with multiple frames as pieces of input image ==================== */ -// IS NOT CALLED BY ANYTHING?! void Mod_LoadMapSprite( model_t *mod, const void *buffer, size_t size, qboolean *loaded ) { byte *src, *dst; @@ -509,7 +508,7 @@ void Mod_LoadMapSprite( model_t *mod, const void *buffer, size_t size, qboolean pspriteframe->left = -( w >> 1 ); pspriteframe->down = ( h >> 1 ) - h; pspriteframe->right = w + -( w >> 1 ); - pspriteframe->gl_texturenum = VK_LoadTextureInternal( texname, &temp, TF_IMAGE ); + pspriteframe->gl_texturenum = R_TextureUploadFromBuffer( texname, &temp, TF_IMAGE, false ); xl += w; if( xl >= pix->width ) @@ -1074,3 +1073,36 @@ void R_VkSpriteDrawModel( cl_entity_t *e, float blend ) } */ } + +void Mod_SpriteUnloadTextures( void *data ) +{ + msprite_t *psprite; + mspritegroup_t *pspritegroup; + mspriteframe_t *pspriteframe; + int i, j; + + psprite = data; + + if( psprite ) + { + // release all textures + for( i = 0; i < psprite->numframes; i++ ) + { + if( psprite->frames[i].type == SPR_SINGLE ) + { + pspriteframe = psprite->frames[i].frameptr; + R_TextureFree( pspriteframe->gl_texturenum ); + } + else + { + pspritegroup = (mspritegroup_t *)psprite->frames[i].frameptr; + + for( j = 0; j < pspritegroup->numframes; j++ ) + { + pspriteframe = pspritegroup->frames[i]; + R_TextureFree( pspriteframe->gl_texturenum ); + } + } + } + } +} diff --git a/ref/vk/vk_sprite.h b/ref/vk/vk_sprite.h index 10664536..d6393442 100644 --- a/ref/vk/vk_sprite.h +++ b/ref/vk/vk_sprite.h @@ -14,3 +14,5 @@ void R_SpriteShutdown(void); // FIXME needed to recreate the sprite quad model, otherwise its memory will be freed, reused and corrupted void R_SpriteNewMapFIXME(void); + +void Mod_SpriteUnloadTextures( void *data ); diff --git a/ref/vk/vk_studio.c b/ref/vk/vk_studio.c index 056d4923..44adccd6 100644 --- a/ref/vk/vk_studio.c +++ b/ref/vk/vk_studio.c @@ -1,7 +1,7 @@ #include "vk_studio.h" #include "com_model.h" #include "vk_common.h" -#include "vk_textures.h" +#include "r_textures.h" #include "vk_render.h" #include "vk_geometry.h" #include "vk_renderstate.h" @@ -2229,6 +2229,33 @@ static r_studio_submodel_info_t *studioModelFindSubmodelInfo(void) { return NULL; } +static material_mode_e studioMaterialModeForRenderType(vk_render_type_e render_type) { + switch (render_type) { + case kVkRenderTypeSolid: + return kMaterialMode_Opaque; + break; + case kVkRenderType_A_1mA_RW: // blend: scr*a + dst*(1-a), depth: RW + case kVkRenderType_A_1mA_R: // blend: scr*a + dst*(1-a), depth test + return kMaterialMode_Translucent; + break; + case kVkRenderType_A_1: // blend: scr*a + dst, no depth test or write; sprite:kRenderGlow only + return kMaterialMode_BlendGlow; + break; + case kVkRenderType_A_1_R: // blend: scr*a + dst, depth test + case kVkRenderType_1_1_R: // blend: scr + dst, depth test + return kMaterialMode_BlendAdd; + break; + case kVkRenderType_AT: // no blend, depth RW, alpha test + return kMaterialMode_AlphaTest; + break; + + default: + gEngine.Host_Error("Unexpected render type %d\n", render_type); + } + + return kMaterialMode_Opaque; +} + // Draws current studio model submodel // Can be called externally, i.e. from game dll. // Expects m_pStudioHeader, m_pSubModel, RI.currententity, etc to be already set up @@ -2300,7 +2327,7 @@ static void R_StudioDrawPoints( void ) { // TODO r_model_draw_t.transform should be matrix3x4 const vk_render_type_e render_type = studioRenderModeToRenderType(RI.currententity->curstate.rendermode); - const material_mode_e material_mode = R_VkMaterialModeFromRenderType(render_type); + const material_mode_e material_mode = studioMaterialModeForRenderType(render_type); R_RenderModelDraw(&render_submodel->model, (r_model_draw_t){ .render_type = render_type, .material_mode = material_mode, @@ -3332,7 +3359,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_LoadTextureExternal( texname, (byte *)ptexture, size, flags ); + ptexture->index = R_TextureUploadFromFile( texname, (byte *)ptexture, size, flags ); if( !ptexture->index ) { @@ -3378,7 +3405,7 @@ void Mod_StudioUnloadTextures( void *data ) { if( ptexture[i].index == tglob.defaultTexture ) continue; - VK_FreeTexture( ptexture[i].index ); + R_TextureFree( ptexture[i].index ); } } diff --git a/ref/vk/vk_studio.h b/ref/vk/vk_studio.h index 728b39ef..db16c3e5 100644 --- a/ref/vk/vk_studio.h +++ b/ref/vk/vk_studio.h @@ -10,6 +10,7 @@ void VK_StudioInit( void ); void VK_StudioShutdown( void ); void Mod_StudioLoadTextures( model_t *mod, void *data ); +void Mod_StudioUnloadTextures( void *data ); void VK_StudioDrawModel( cl_entity_t *ent, int render_mode, float blend ); diff --git a/ref/vk/vk_swapchain.c b/ref/vk/vk_swapchain.c index d3994ce5..ca8a68bc 100644 --- a/ref/vk/vk_swapchain.c +++ b/ref/vk/vk_swapchain.c @@ -41,6 +41,7 @@ static void createDepthImage(int w, int h, VkFormat depth_format) { .layers = 1, .width = w, .height = h, + .depth = 1, .tiling = VK_IMAGE_TILING_OPTIMAL, .usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, }; diff --git a/ref/vk/vk_textures.c b/ref/vk/vk_textures.c index b46ff379..3113ea27 100644 --- a/ref/vk/vk_textures.c +++ b/ref/vk/vk_textures.c @@ -1,449 +1,202 @@ #include "vk_textures.h" -#include "vk_common.h" #include "vk_core.h" -#include "vk_staging.h" -#include "vk_const.h" #include "vk_descriptor.h" -#include "vk_mapents.h" // wadlist -#include "vk_combuf.h" +#include "vk_staging.h" #include "vk_logs.h" +#include "vk_combuf.h" +#include "r_textures.h" #include "r_speeds.h" +#include "alolcator.h" +#include "profiler.h" + +#include "xash3d_mathlib.h" // bound -#include "xash3d_mathlib.h" -#include "crtlib.h" -#include "crclib.h" -#include "com_strings.h" -#include "eiface.h" #include "ktx2.h" #define PCG_IMPLEMENT #include "pcg.h" -#include -#include +#include // sqrt #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; -vk_textures_global_t tglob = {0}; +#define MAX_SAMPLERS 8 // TF_NEAREST x 2 * TF_BORDER x 2 * TF_CLAMP x 2 static struct { struct { int count; int size_total; } stats; -} g_textures; -static void VK_CreateInternalTextures(void); + struct { + uint32_t flags; + VkSampler sampler; + } samplers[MAX_SAMPLERS]; + + VkSampler default_sampler; + + //vk_texture_t textures[MAX_TEXTURES]; + //alo_int_pool_t textures_free; + + // All textures descriptors in their native formats used for RT + VkDescriptorImageInfo dii_all_textures[MAX_TEXTURES]; + + vk_texture_t skybox_cube; + vk_texture_t cubemap_placeholder; + + vk_texture_t blue_noise; +} g_vktextures; + +// Exported from r_textures.h +size_t CalcImageSize( pixformat_t format, int width, int height, int depth ); +int CalcMipmapCount( int width, int height, int depth, uint32_t flags, qboolean haveBuffer ); +qboolean validatePicLayers(const char* const name, rgbdata_t *const *const layers, int num_layers); +void BuildMipMap( byte *in, int srcWidth, int srcHeight, int srcDepth, int flags ); + static VkSampler pickSamplerForFlags( texFlags_t flags ); +static qboolean uploadTexture(int index, vk_texture_t *tex, rgbdata_t *const *const layers, int num_layers, qboolean cubemap, colorspace_hint_e colorspace_hint); -void initTextures( void ) { - R_SPEEDS_METRIC(g_textures.stats.count, "count", kSpeedsMetricCount); - R_SPEEDS_METRIC(g_textures.stats.size_total, "size_total", kSpeedsMetricBytes); - - tglob.mempool = Mem_AllocPool( "vktextures" ); +// FIXME should be static +void unloadSkybox( void ); - memset( vk_textures, 0, sizeof( vk_textures )); - memset( vk_texturesHashTable, 0, sizeof( vk_texturesHashTable )); - vk_numTextures = 0; +// Hardcode blue noise texture size to 64x64x64 +#define BLUE_NOISE_SIZE 64 +#define BLUE_NOISE_NAME_F "bluenoise/LDR_RGBA_%d.png" + +static void generateFallbackNoiseTextures( rgbdata_t *pic ) { + ERR("Generating bad quality regular noise textures as a fallback for blue noise textures"); + + const int blue_noise_count = pic->size / sizeof(uint32_t); + uint32_t *const scratch = (uint32_t*)pic->buffer; + + // Fill with random data + { + pcg32_random_t pcg_state = { blue_noise_count - 1, 17 }; + for (int j = 0; j < blue_noise_count; ++j) + scratch[j] = pcg32_random_r(&pcg_state); + } +} + +static void loadBlueNoiseTextures(void) { + const size_t blue_noise_count = BLUE_NOISE_SIZE * BLUE_NOISE_SIZE * BLUE_NOISE_SIZE; + const size_t blue_noise_size = blue_noise_count * sizeof(uint32_t); + uint32_t *const scratch = Mem_Malloc(vk_core.pool /* TODO textures pool */, blue_noise_size); + rgbdata_t pic = { + .width = BLUE_NOISE_SIZE, + .height = BLUE_NOISE_SIZE, + .depth = BLUE_NOISE_SIZE, + .flags = TF_NOMIPMAP, + .type = PF_RGBA_32, + .size = blue_noise_size, + .buffer = (byte*)scratch, + .palette = NULL, + .numMips = 1, + .encode = 0, + }; + + int loaded = 0; + for (int i = 0, cursor = 0; i < BLUE_NOISE_SIZE; ++i, ++loaded) { + char filename[1024]; + snprintf( filename, sizeof filename, BLUE_NOISE_NAME_F, i ); + rgbdata_t *const filepic = gEngine.FS_LoadImage( filename, NULL, 0 ); + + if ( !filepic ) { + ERR("Couldn't load precomputed blue noise texture '%s'", filename); + break; + } + + if ( filepic->type != PF_RGBA_32 ) { + ERR("Precomputed blue noise texture '%s' has unexpected format %d", filename, filepic->type); + gEngine.FS_FreeImage( filepic ); + break; + } + + if ( filepic->width != BLUE_NOISE_SIZE ) { + ERR("Precomputed blue noise texture '%s' has unexpected width %d, expected %d", filename, filepic->width, BLUE_NOISE_SIZE); + gEngine.FS_FreeImage( filepic ); + break; + } + + if ( filepic->height != BLUE_NOISE_SIZE ) { + ERR("Precomputed blue noise texture '%s' has unexpected height %d, expected %d", filename, filepic->height, BLUE_NOISE_SIZE); + gEngine.FS_FreeImage( filepic ); + break; + } + + ASSERT( filepic->size == BLUE_NOISE_SIZE * BLUE_NOISE_SIZE * sizeof(uint32_t) ); + + memcpy(pic.buffer + cursor, filepic->buffer, filepic->size); + cursor += filepic->size; + + gEngine.FS_FreeImage( filepic ); + } + + const qboolean fail = loaded != BLUE_NOISE_SIZE; + if (fail) + generateFallbackNoiseTextures( &pic ); + + const char *const name = fail ? "*bluenoise/pcg_fallback" : "*bluenoise"; + Q_strncpy(g_vktextures.blue_noise.hdr_.key, name, sizeof(g_vktextures.blue_noise.hdr_.key)); + rgbdata_t *pica[1] = {&pic}; + const qboolean is_cubemap = false; + ASSERT(uploadTexture(-1, &g_vktextures.blue_noise, pica, 1, is_cubemap, kColorspaceLinear)); + Mem_Free(scratch); +} + +qboolean R_VkTexturesInit( void ) { + R_SPEEDS_METRIC(g_vktextures.stats.count, "count", kSpeedsMetricCount); + R_SPEEDS_METRIC(g_vktextures.stats.size_total, "size_total", kSpeedsMetricBytes); // TODO really check device caps for this gEngine.Image_AddCmdFlags( IL_DDS_HARDWARE | IL_KTX2_RAW ); - tglob.default_sampler_fixme = pickSamplerForFlags(0); - ASSERT(tglob.default_sampler_fixme != VK_NULL_HANDLE); - - // create unused 0-entry - Q_strncpy( vk_textures->name, "*unused*", sizeof( vk_textures->name )); - vk_textures->hashValue = COM_HashKey( vk_textures->name, TEXTURES_HASH_SIZE ); - vk_textures->nextHash = vk_texturesHashTable[vk_textures->hashValue]; - vk_texturesHashTable[vk_textures->hashValue] = vk_textures; - vk_numTextures = 1; + g_vktextures.default_sampler = pickSamplerForFlags(0); + ASSERT(g_vktextures.default_sampler != VK_NULL_HANDLE); /* FIXME // validate cvars R_SetTextureParameters(); */ - VK_CreateInternalTextures(); - /* FIXME gEngine.Cmd_AddCommand( "texturelist", R_TextureList_f, "display loaded textures list" ); */ // Fill empty texture with references to the default texture { - const VkImageView default_view = vk_textures[tglob.defaultTexture].vk.image.view; + const VkImageView default_view = R_TextureGetByIndex(tglob.defaultTexture)->vk.image.view; ASSERT(default_view != VK_NULL_HANDLE); for (int i = 0; i < MAX_TEXTURES; ++i) { - const vk_texture_t *const tex = vk_textures + i; + const vk_texture_t *const tex = R_TextureGetByIndex(i); if (tex->vk.image.view) continue; - tglob.dii_all_textures[i] = (VkDescriptorImageInfo){ + g_vktextures.dii_all_textures[i] = (VkDescriptorImageInfo){ .imageView = default_view, .imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, - .sampler = tglob.default_sampler_fixme, + .sampler = g_vktextures.default_sampler, }; } } -} - -static void unloadSkybox( void ); - -void destroyTextures( void ) -{ - for( unsigned int i = 0; i < vk_numTextures; i++ ) - VK_FreeTexture( i ); - - unloadSkybox(); - - 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) { - if (tglob.samplers[i].sampler != VK_NULL_HANDLE) - vkDestroySampler(vk_core.device, tglob.samplers[i].sampler, NULL); - } - - //memset( tglob.lightmapTextures, 0, sizeof( tglob.lightmapTextures )); - memset( vk_texturesHashTable, 0, sizeof( vk_texturesHashTable )); - memset( vk_textures, 0, sizeof( vk_textures )); - vk_numTextures = 0; -} - -vk_texture_t *findTexture(int index) -{ - ASSERT(index >= 0); - ASSERT(index < MAX_TEXTURES); - return vk_textures + index; -} - -static vk_texture_t *Common_AllocTexture( const char *name, texFlags_t flags ) -{ - vk_texture_t *tex; - uint i; - - // find a free texture_t slot - for( i = 0, tex = vk_textures; i < vk_numTextures; i++, tex++ ) - if( !tex->name[0] ) break; - - if( i == vk_numTextures ) - { - if( vk_numTextures == MAX_TEXTURES ) - gEngine.Host_Error( "VK_AllocTexture: MAX_TEXTURES limit exceeds\n" ); - vk_numTextures++; - } - - tex = &vk_textures[i]; - - // copy initial params - Q_strncpy( tex->name, name, sizeof( tex->name )); - tex->texnum = i; // texnum is used for fast acess into vk_textures array too - tex->flags = flags; - - // add to hash table - tex->hashValue = COM_HashKey( name, TEXTURES_HASH_SIZE ); - tex->nextHash = vk_texturesHashTable[tex->hashValue]; - vk_texturesHashTable[tex->hashValue] = tex; - - return tex; -} - -static qboolean Common_CheckTexName( const char *name ) -{ - int len; - - if( !COM_CheckString( name )) - return false; - - len = Q_strlen( name ); - - // because multi-layered textures can exceed name string - if( len >= sizeof( vk_textures->name )) - { - ERR("LoadTexture: too long name %s (%d)", name, len ); - return false; - } - - return true; -} - -static vk_texture_t *Common_TextureForName( const char *name ) -{ - vk_texture_t *tex; - uint hash; - - // find the texture in array - hash = COM_HashKey( name, TEXTURES_HASH_SIZE ); - - for( tex = vk_texturesHashTable[hash]; tex != NULL; tex = tex->nextHash ) - { - if( !Q_stricmp( tex->name, name )) - return tex; - } - - return NULL; -} - -static rgbdata_t *Common_FakeImage( int width, int height, int depth, int flags ) -{ - // TODO: Fix texture and it's buffer leaking. - rgbdata_t *r_image = Mem_Malloc( tglob.mempool, sizeof( rgbdata_t ) ); - - // also use this for bad textures, but without alpha - r_image->width = Q_max( 1, width ); - r_image->height = Q_max( 1, height ); - r_image->depth = Q_max( 1, depth ); - r_image->flags = flags; - r_image->type = PF_RGBA_32; - - r_image->size = r_image->width * r_image->height * r_image->depth * 4; - if( FBitSet( r_image->flags, IMAGE_CUBEMAP )) r_image->size *= 6; - - r_image->buffer = Mem_Malloc( tglob.mempool, r_image->size); - r_image->palette = NULL; - r_image->numMips = 1; - r_image->encode = 0; - - memset( r_image->buffer, 0xFF, r_image->size ); - - return r_image; -} - -/* -=============== -GL_ProcessImage - -do specified actions on pixels -=============== -*/ -static void VK_ProcessImage( vk_texture_t *tex, rgbdata_t *pic ) -{ - float emboss_scale = 0.0f; - uint img_flags = 0; - - // force upload texture as RGB or RGBA (detail textures requires this) - if( tex->flags & TF_FORCE_COLOR ) pic->flags |= IMAGE_HAS_COLOR; - if( pic->flags & IMAGE_HAS_ALPHA ) tex->flags |= TF_HAS_ALPHA; - - //FIXME provod: ??? tex->encode = pic->encode; // share encode method - - if( ImageCompressed( pic->type )) - { - if( !pic->numMips ) - tex->flags |= TF_NOMIPMAP; // disable mipmapping by user request - - // clear all the unsupported flags - tex->flags &= ~TF_KEEP_SOURCE; - } - else - { - // copy flag about luma pixels - if( pic->flags & IMAGE_HAS_LUMA ) - tex->flags |= TF_HAS_LUMA; - - if( pic->flags & IMAGE_QUAKEPAL ) - tex->flags |= TF_QUAKEPAL; - - // create luma texture from quake texture - if( tex->flags & TF_MAKELUMA ) - { - img_flags |= IMAGE_MAKE_LUMA; - tex->flags &= ~TF_MAKELUMA; - } - - /* FIXME provod: ??? - if( !FBitSet( tex->flags, TF_IMG_UPLOADED ) && FBitSet( tex->flags, TF_KEEP_SOURCE )) - tex->original = gEngine.FS_CopyImage( pic ); // because current pic will be expanded to rgba - */ - - // we need to expand image into RGBA buffer - if( pic->type == PF_INDEXED_24 || pic->type == PF_INDEXED_32 ) - img_flags |= IMAGE_FORCE_RGBA; - - /* FIXME provod: ??? - // dedicated server doesn't register this variable - if( gl_emboss_scale != NULL ) - emboss_scale = gl_emboss_scale->value; - */ - - // processing image before uploading (force to rgba, make luma etc) - if( pic->buffer ) gEngine.Image_Process( &pic, 0, 0, img_flags, emboss_scale ); - - if( FBitSet( tex->flags, TF_LUMINANCE )) - ClearBits( pic->flags, IMAGE_HAS_COLOR ); - } -} - -static qboolean uploadTexture(vk_texture_t *tex, rgbdata_t *const *const layers, int num_layers, qboolean cubemap, colorspace_hint_e colorspace_hint); - -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; - va_start( argptr, fmt ); - vsnprintf( buffer, sizeof buffer, fmt, argptr ); - va_end( argptr ); - - return loadTextureInternal(buffer, NULL, 0, flags, colorspace); -} - -#define BLUE_NOISE_NAME_F "bluenoise/LDR_RGBA_%d.png" - -static qboolean generateFallbackNoiseTextures(void) { - pcg32_random_t pcg_state = { - BLUE_NOISE_SIZE * BLUE_NOISE_SIZE - 1, - 17, - }; - uint32_t scratch[BLUE_NOISE_SIZE * BLUE_NOISE_SIZE]; - rgbdata_t pic = { - .width = BLUE_NOISE_SIZE, - .height = BLUE_NOISE_SIZE, - .depth = 1, - .flags = 0, - .type = PF_RGBA_32, - .size = BLUE_NOISE_SIZE * BLUE_NOISE_SIZE * 4, - .buffer = (byte*)&scratch, - .palette = NULL, - .numMips = 1, - .encode = 0, - }; - - int blueNoiseTexturesBegin = -1; - for (int i = 0; i < BLUE_NOISE_SIZE; ++i) { - for (int j = 0; j < COUNTOF(scratch); ++j) { - scratch[j] = pcg32_random_r(&pcg_state); - } - - char name[256]; - snprintf(name, sizeof(name), BLUE_NOISE_NAME_F, i); - const int texid = VK_LoadTextureInternal(name, &pic, TF_NOMIPMAP); - ASSERT(texid > 0); - - if (blueNoiseTexturesBegin == -1) { - ASSERT(texid == BLUE_NOISE_TEXTURE_ID); - blueNoiseTexturesBegin = texid; - } else { - ASSERT(blueNoiseTexturesBegin + i == texid); - } - } - - return true; -} - -static qboolean loadBlueNoiseTextures(void) { - int blueNoiseTexturesBegin = -1; - for (int i = 0; i < 64; ++i) { - const int texid = VK_LoadTextureF(TF_NOMIPMAP, kColorspaceLinear, BLUE_NOISE_NAME_F, i); - - if (blueNoiseTexturesBegin == -1) { - if (texid <= 0) { - ERR("Couldn't find precomputed blue noise textures. Generating bad quality regular noise textures as a fallback"); - return generateFallbackNoiseTextures(); - } - - blueNoiseTexturesBegin = texid; - } else { - ASSERT(texid > 0); - ASSERT(blueNoiseTexturesBegin + i == texid); - } - } - - INFO("Base blue noise texture is %d", blueNoiseTexturesBegin); - ASSERT(blueNoiseTexturesBegin == BLUE_NOISE_TEXTURE_ID); - - return true; -} - -static void VK_CreateInternalTextures( void ) -{ - int dx2, dy, d; - int x, y; - rgbdata_t *pic; - - // emo-texture from quake1 - pic = Common_FakeImage( 16, 16, 1, IMAGE_HAS_COLOR ); - - for( y = 0; y < 16; y++ ) - { - for( x = 0; x < 16; x++ ) - { - if(( y < 8 ) ^ ( x < 8 )) - ((uint *)pic->buffer)[y*16+x] = 0xFFFF00FF; - else ((uint *)pic->buffer)[y*16+x] = 0xFF000000; - } - } - - tglob.defaultTexture = VK_LoadTextureInternal( REF_DEFAULT_TEXTURE, pic, TF_COLORMAP ); - - // particle texture from quake1 - pic = Common_FakeImage( 16, 16, 1, IMAGE_HAS_COLOR|IMAGE_HAS_ALPHA ); - - for( x = 0; x < 16; x++ ) - { - dx2 = x - 8; - dx2 = dx2 * dx2; - - for( y = 0; y < 16; y++ ) - { - dy = y - 8; - d = 255 - 35 * sqrt( dx2 + dy * dy ); - pic->buffer[( y * 16 + x ) * 4 + 3] = bound( 0, d, 255 ); - } - } - - tglob.particleTexture = VK_LoadTextureInternal( REF_PARTICLE_TEXTURE, pic, TF_CLAMP ); - - // white texture - pic = Common_FakeImage( 4, 4, 1, IMAGE_HAS_COLOR ); - for( x = 0; x < 16; x++ ) - ((uint *)pic->buffer)[x] = 0xFFFFFFFF; - tglob.whiteTexture = VK_LoadTextureInternal( REF_WHITE_TEXTURE, pic, TF_COLORMAP ); - - // gray texture - pic = Common_FakeImage( 4, 4, 1, IMAGE_HAS_COLOR ); - for( x = 0; x < 16; x++ ) - ((uint *)pic->buffer)[x] = 0xFF7F7F7F; - tglob.grayTexture = VK_LoadTextureInternal( REF_GRAY_TEXTURE, pic, TF_COLORMAP ); - - // black texture - pic = Common_FakeImage( 4, 4, 1, IMAGE_HAS_COLOR ); - for( x = 0; x < 16; x++ ) - ((uint *)pic->buffer)[x] = 0xFF000000; - tglob.blackTexture = VK_LoadTextureInternal( REF_BLACK_TEXTURE, pic, TF_COLORMAP ); - - // cinematic dummy - pic = Common_FakeImage( 640, 100, 1, IMAGE_HAS_COLOR ); - tglob.cinTexture = VK_LoadTextureInternal( "*cintexture", pic, TF_NOMIPMAP|TF_CLAMP ); - - { - rgbdata_t *sides[6]; - pic = Common_FakeImage( 4, 4, 1, IMAGE_HAS_COLOR ); - for( x = 0; x < 16; x++ ) - ((uint *)pic->buffer)[x] = 0xFFFFFFFF; - - sides[0] = pic; - sides[1] = pic; - sides[2] = pic; - sides[3] = pic; - sides[4] = pic; - sides[5] = pic; - - uploadTexture( &tglob.cubemap_placeholder, sides, 6, true, kColorspaceGamma ); - } loadBlueNoiseTextures(); + + return true; +} + +static void textureDestroy( unsigned int index ); + +void R_VkTexturesShutdown( void ) { + unloadSkybox(); + R_VkTextureDestroy(-1, &g_vktextures.cubemap_placeholder); + R_VkTextureDestroy(-1, &g_vktextures.blue_noise); + + for (int i = 0; i < COUNTOF(g_vktextures.samplers); ++i) { + if (g_vktextures.samplers[i].sampler != VK_NULL_HANDLE) + vkDestroySampler(vk_core.device, g_vktextures.samplers[i].sampler, NULL); + } } static VkFormat VK_GetFormat(pixformat_t format, colorspace_hint_e colorspace_hint ) { @@ -502,158 +255,6 @@ static VkFormat VK_GetFormat(pixformat_t format, colorspace_hint_e colorspace_hi } } -static size_t CalcImageSize( pixformat_t format, int width, int height, int depth ) { - size_t size = 0; - - // check the depth error - depth = Q_max( 1, depth ); - - switch( format ) - { - case PF_LUMINANCE: - size = width * height * depth; - break; - case PF_RGB_24: - case PF_BGR_24: - size = width * height * depth * 3; - break; - case PF_BGRA_32: - case PF_RGBA_32: - size = width * height * depth * 4; - break; - case PF_DXT1: - case PF_BC4_UNSIGNED: - case PF_BC4_SIGNED: - size = (((width + 3) >> 2) * ((height + 3) >> 2) * 8) * depth; - break; - case PF_DXT3: - case PF_DXT5: - case PF_BC6H_UNSIGNED: - case PF_BC6H_SIGNED: - case PF_BC7_UNORM: - case PF_BC7_SRGB: - case PF_ATI2: - case PF_BC5_UNSIGNED: - case PF_BC5_SIGNED: - size = (((width + 3) >> 2) * ((height + 3) >> 2) * 16) * depth; - break; - default: - ERR("%s: unsupported pixformat_t %d", __FUNCTION__, format); - ASSERT(!"Unsupported format encountered"); - } - - return size; -} - -static int CalcMipmapCount( vk_texture_t *tex, qboolean haveBuffer ) -{ - int width, height; - int mipcount; - - ASSERT( tex != NULL ); - - if( !haveBuffer )// || tex->target == GL_TEXTURE_3D ) - return 1; - - // generate mip-levels by user request - if( FBitSet( tex->flags, TF_NOMIPMAP )) - return 1; - - // mip-maps can't exceeds 16 - for( mipcount = 0; mipcount < 16; mipcount++ ) - { - width = Q_max( 1, ( tex->width >> mipcount )); - height = Q_max( 1, ( tex->height >> mipcount )); - if( width == 1 && height == 1 ) - break; - } - - return mipcount + 1; -} - -static void BuildMipMap( byte *in, int srcWidth, int srcHeight, int srcDepth, int flags ) -{ - byte *out = in; - int instride = ALIGN( srcWidth * 4, 1 ); - int mipWidth, mipHeight, outpadding; - int row, x, y, z; - vec3_t normal; - - if( !in ) return; - - mipWidth = Q_max( 1, ( srcWidth >> 1 )); - mipHeight = Q_max( 1, ( srcHeight >> 1 )); - outpadding = ALIGN( mipWidth * 4, 1 ) - mipWidth * 4; - - if( FBitSet( flags, TF_ALPHACONTRAST )) - { - memset( in, mipWidth, mipWidth * mipHeight * 4 ); - return; - } - - // move through all layers - for( z = 0; z < srcDepth; z++ ) - { - if( FBitSet( flags, TF_NORMALMAP )) - { - for( y = 0; y < mipHeight; y++, in += instride * 2, out += outpadding ) - { - byte *next = ((( y << 1 ) + 1 ) < srcHeight ) ? ( in + instride ) : in; - for( x = 0, row = 0; x < mipWidth; x++, row += 8, out += 4 ) - { - if((( x << 1 ) + 1 ) < srcWidth ) - { - normal[0] = MAKE_SIGNED( in[row+0] ) + MAKE_SIGNED( in[row+4] ) - + MAKE_SIGNED( next[row+0] ) + MAKE_SIGNED( next[row+4] ); - normal[1] = MAKE_SIGNED( in[row+1] ) + MAKE_SIGNED( in[row+5] ) - + MAKE_SIGNED( next[row+1] ) + MAKE_SIGNED( next[row+5] ); - normal[2] = MAKE_SIGNED( in[row+2] ) + MAKE_SIGNED( in[row+6] ) - + MAKE_SIGNED( next[row+2] ) + MAKE_SIGNED( next[row+6] ); - } - else - { - normal[0] = MAKE_SIGNED( in[row+0] ) + MAKE_SIGNED( next[row+0] ); - normal[1] = MAKE_SIGNED( in[row+1] ) + MAKE_SIGNED( next[row+1] ); - normal[2] = MAKE_SIGNED( in[row+2] ) + MAKE_SIGNED( next[row+2] ); - } - - if( !VectorNormalizeLength( normal )) - VectorSet( normal, 0.5f, 0.5f, 1.0f ); - - out[0] = 128 + (byte)(127.0f * normal[0]); - out[1] = 128 + (byte)(127.0f * normal[1]); - out[2] = 128 + (byte)(127.0f * normal[2]); - out[3] = 255; - } - } - } - else - { - for( y = 0; y < mipHeight; y++, in += instride * 2, out += outpadding ) - { - byte *next = ((( y << 1 ) + 1 ) < srcHeight ) ? ( in + instride ) : in; - for( x = 0, row = 0; x < mipWidth; x++, row += 8, out += 4 ) - { - if((( x << 1 ) + 1 ) < srcWidth ) - { - out[0] = (in[row+0] + in[row+4] + next[row+0] + next[row+4]) >> 2; - out[1] = (in[row+1] + in[row+5] + next[row+1] + next[row+5]) >> 2; - out[2] = (in[row+2] + in[row+6] + next[row+2] + next[row+6]) >> 2; - out[3] = (in[row+3] + in[row+7] + next[row+3] + next[row+7]) >> 2; - } - else - { - out[0] = (in[row+0] + next[row+0]) >> 1; - out[1] = (in[row+1] + next[row+1]) >> 1; - out[2] = (in[row+2] + next[row+2]) >> 1; - out[3] = (in[row+3] + next[row+3]) >> 1; - } - } - } - } - } -} - static VkSampler createSamplerForFlags( texFlags_t flags ) { VkSampler sampler; const VkFilter filter_mode = (flags & TF_NEAREST) ? VK_FILTER_NEAREST : VK_FILTER_LINEAR; @@ -682,77 +283,69 @@ static VkSampler createSamplerForFlags( texFlags_t flags ) { static VkSampler pickSamplerForFlags( texFlags_t flags ) { flags &= (TF_BORDER | TF_CLAMP | TF_NEAREST); - for (int i = 0; i < ARRAYSIZE(tglob.samplers); ++i) { - if (tglob.samplers[i].sampler == VK_NULL_HANDLE) { - tglob.samplers[i].flags = flags; - return tglob.samplers[i].sampler = createSamplerForFlags(flags); + for (int i = 0; i < COUNTOF(g_vktextures.samplers); ++i) { + if (g_vktextures.samplers[i].sampler == VK_NULL_HANDLE) { + g_vktextures.samplers[i].flags = flags; + return g_vktextures.samplers[i].sampler = createSamplerForFlags(flags); } - if (tglob.samplers[i].flags == flags) - return tglob.samplers[i].sampler; + if (g_vktextures.samplers[i].flags == flags) + return g_vktextures.samplers[i].sampler; } ERR("Couldn't find/allocate sampler for flags %x", flags); - return tglob.default_sampler_fixme; + return g_vktextures.default_sampler; } -static void prepDestriptorSets(vk_texture_t* const tex, colorspace_hint_e colorspace_hint) { +static void setDescriptorSet(int index, vk_texture_t* const tex, colorspace_hint_e colorspace_hint) { + if (index < 0) + return; + + ASSERT(index < MAX_TEXTURES); + + const VkImageView view = tex->vk.image.view != VK_NULL_HANDLE + ? tex->vk.image.view + : R_TextureGetByIndex(tglob.defaultTexture)->vk.image.view; + + if (view == VK_NULL_HANDLE) + return; + + VkDescriptorImageInfo dii = { + .imageView = view, + .imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + .sampler = pickSamplerForFlags( tex->flags ), + }; + + // Set descriptor for bindless/ray tracing + g_vktextures.dii_all_textures[index] = dii; + + // Continue with setting unorm descriptor for traditional renderer + // 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-2) { - const int index = tex - vk_textures; - 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 ), - }; + if (colorspace_hint == kColorspaceGamma && tex->vk.image.view_unorm != VK_NULL_HANDLE) + dii.imageView = tex->vk.image.view_unorm; - const VkDescriptorImageInfo dii_unorm = { - .imageView = tex->vk.image.view_unorm, - .imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, - .sampler = dii.sampler, - }; + const VkDescriptorSet ds = vk_desc_fixme.texture_sets[index]; + VkWriteDescriptorSet wds[1] = { { + .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, + .dstBinding = 0, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + .pImageInfo = &dii, + .dstSet = ds, + }}; - 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, - .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, ds_unorm != VK_NULL_HANDLE ? 2 : 1 , wds, 0, NULL); + vkUpdateDescriptorSets(vk_core.device, COUNTOF(wds), wds, 0, NULL); - // FIXME detect skybox some other way - if (tex->vk.image.layers == 1) { - tglob.dii_all_textures[index] = dii; - } - - tex->vk.descriptor_unorm = ds_unorm != VK_NULL_HANDLE ? ds_unorm : ds; - } - else - { - tex->vk.descriptor_unorm = VK_NULL_HANDLE; - } + tex->vk.descriptor_unorm = ds; } -static qboolean uploadRawKtx2( vk_texture_t *tex, const rgbdata_t* pic ) { - DEBUG("Uploading raw KTX2 texture[%d] %s", (int)(tex-vk_textures), tex->name); +static qboolean uploadRawKtx2( int tex_index, vk_texture_t *tex, const rgbdata_t* pic ) { + DEBUG("Uploading raw KTX2 texture[%d] %s", tex_index, TEX_NAME(tex)); const byte *const data = pic->buffer; const int size = pic->size; @@ -802,9 +395,10 @@ static qboolean uploadRawKtx2( vk_texture_t *tex, const rgbdata_t* pic ) { { const r_vk_image_create_t create = { - .debug_name = tex->name, + .debug_name = TEX_NAME(tex), .width = header->pixelWidth, .height = header->pixelHeight, + .depth = Q_max(1, header->pixelDepth), .mips = header->levelCount, .layers = 1, // TODO or 6 for cubemap; header->faceCount .format = header->vkFormat, @@ -834,38 +428,20 @@ static qboolean uploadRawKtx2( vk_texture_t *tex, const rgbdata_t* pic ) { R_VkImageUploadEnd(&tex->vk.image); } - // KTX2 textures are inaccessible from trad renderer (for now) - tex->vk.descriptor_unorm = VK_NULL_HANDLE; + { + // 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){ + const VkDescriptorImageInfo dii = { .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_vktextures.dii_all_textures[tex_index] = dii; } - g_textures.stats.size_total += tex->total_size; - g_textures.stats.count++; + g_vktextures.stats.size_total += tex->total_size; + g_vktextures.stats.count++; tex->width = header->pixelWidth; tex->height = header->pixelHeight; @@ -873,69 +449,46 @@ static qboolean uploadRawKtx2( vk_texture_t *tex, const rgbdata_t* pic ) { return true; } -static qboolean validatePicLayers(const vk_texture_t* const tex, rgbdata_t *const *const layers, int num_layers) { - for (int i = 0; i < num_layers; ++i) { - // FIXME create empty black texture if there's no buffer - if (!layers[i]->buffer) { - ERR("Texture %s layer %d missing buffer", tex->name, i); - return false; - } +static qboolean needToCreateImage( int index, vk_texture_t *tex, const r_vk_image_create_t *create ) { + if (tex->vk.image.image == VK_NULL_HANDLE) + return true; - if (i == 0) - continue; - - if (layers[0]->type != layers[i]->type) { - ERR("Texture %s layer %d has type %d inconsistent with layer 0 type %d", tex->name, i, layers[i]->type, layers[0]->type); - return false; - } - - if (layers[0]->width != layers[i]->width || layers[0]->height != layers[i]->height) { - ERR("Texture %s layer %d has resolution %dx%d inconsistent with layer 0 resolution %dx%d", - tex->name, i, layers[i]->width, layers[i]->height, layers[0]->width, layers[0]->height); - return false; - } - - if ((layers[0]->flags ^ layers[i]->flags) & IMAGE_HAS_ALPHA) { - ERR("Texture %s layer %d has_alpha=%d inconsistent with layer 0 has_alpha=%d", - tex->name, i, - !!(layers[i]->flags & IMAGE_HAS_ALPHA), - !!(layers[0]->flags & IMAGE_HAS_ALPHA)); - return false; - } - - if (layers[0]->numMips != layers[i]->numMips) { - ERR("Texture %s layer %d has numMips %d inconsistent with layer 0 numMips %d", - tex->name, i, layers[i]->numMips, layers[0]->numMips); - return false; - } - } + if (tex->vk.image.width == create->width + && tex->vk.image.height == create->height + && tex->vk.image.format == create->format + && tex->vk.image.mips == create->mips + && tex->vk.image.layers == create->layers + && tex->vk.image.flags == create->flags) + return false; + WARN("Re-creating texture '%s' image", create->debug_name); + R_VkTextureDestroy( index, tex ); return true; } -static qboolean uploadTexture(vk_texture_t *tex, rgbdata_t *const *const layers, int num_layers, qboolean cubemap, colorspace_hint_e colorspace_hint) { +static qboolean uploadTexture(int index, vk_texture_t *tex, rgbdata_t *const *const layers, int num_layers, qboolean cubemap, colorspace_hint_e colorspace_hint) { tex->total_size = 0; if (num_layers == 1 && layers[0]->type == PF_KTX2_RAW) { - if (!uploadRawKtx2(tex, layers[0])) + if (!uploadRawKtx2(index, tex, layers[0])) return false; } else { + const int width = layers[0]->width; + const int height = layers[0]->height; + const int depth = Q_max(1, layers[0]->depth); const qboolean compute_mips = layers[0]->type == PF_RGBA_32 && layers[0]->numMips < 2; const VkFormat format = VK_GetFormat(layers[0]->type, colorspace_hint); - const int mipCount = compute_mips ? CalcMipmapCount( tex, true ) : layers[0]->numMips; + const int mipCount = compute_mips ? CalcMipmapCount( width, height, depth, tex->flags, true ) : layers[0]->numMips; if (format == VK_FORMAT_UNDEFINED) { ERR("Unsupported PF format %d", layers[0]->type); return false; } - if (!validatePicLayers(tex, layers, num_layers)) + if (!validatePicLayers(TEX_NAME(tex), layers, num_layers)) return false; - tex->width = layers[0]->width; - tex->height = layers[0]->height; - - DEBUG("Uploading texture[%d] %s, mips=%d(build=%d), layers=%d", (int)(tex-vk_textures), tex->name, mipCount, compute_mips, num_layers); + DEBUG("Uploading texture[%d] %s, mips=%d(build=%d), layers=%d", index, TEX_NAME(tex), mipCount, compute_mips, num_layers); // TODO (not sure why, but GL does this) // if( !ImageCompressed( layers->type ) && !FBitSet( tex->flags, TF_NOMIPMAP ) && FBitSet( layers->flags, IMAGE_ONEBIT_ALPHA )) @@ -943,9 +496,10 @@ static qboolean uploadTexture(vk_texture_t *tex, rgbdata_t *const *const layers, { const r_vk_image_create_t create = { - .debug_name = tex->name, - .width = tex->width, - .height = tex->height, + .debug_name = TEX_NAME(tex), + .width = width, + .height = height, + .depth = depth, .mips = mipCount, .layers = num_layers, .format = format, @@ -956,9 +510,15 @@ static qboolean uploadTexture(vk_texture_t *tex, rgbdata_t *const *const layers, | (cubemap ? kVkImageFlagIsCubemap : 0) | (colorspace_hint == kColorspaceGamma ? kVkImageFlagCreateUnormView : 0), }; - tex->vk.image = R_VkImageCreate(&create); + + if (needToCreateImage(index, tex, &create)) + tex->vk.image = R_VkImageCreate(&create); } + tex->width = width; + tex->height = height; + tex->depth = depth; + { R_VkImageUploadBegin(&tex->vk.image); @@ -969,7 +529,8 @@ static qboolean uploadTexture(vk_texture_t *tex, rgbdata_t *const *const layers, for (int mip = 0; mip < mipCount; ++mip) { 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 int depth = Q_max( 1, ( pic->depth >> mip )); + const size_t mip_size = CalcImageSize( pic->type, width, height, depth ); R_VkImageUploadSlice(&tex->vk.image, layer, mip, mip_size, buf); tex->total_size += mip_size; @@ -977,7 +538,7 @@ static qboolean uploadTexture(vk_texture_t *tex, rgbdata_t *const *const layers, // Build mip in place for the next mip level if (compute_mips) { if ( mip < mipCount - 1 ) - BuildMipMap( buf, width, height, 1, tex->flags ); + BuildMipMap( buf, width, height, depth, tex->flags ); } else { buf += mip_size; } @@ -988,170 +549,18 @@ static qboolean uploadTexture(vk_texture_t *tex, rgbdata_t *const *const layers, } } - prepDestriptorSets(tex, colorspace_hint); + setDescriptorSet(index, tex, colorspace_hint); - g_textures.stats.size_total += tex->total_size; - g_textures.stats.count++; + g_vktextures.stats.size_total += tex->total_size; + g_vktextures.stats.count++; return true; } -///////////// Render API funcs ///////////// - -// Texture tools -int VK_FindTexture( const char *name ) -{ - vk_texture_t *tex; - - if( !Common_CheckTexName( name )) - return 0; - - // see if already loaded - if(( tex = Common_TextureForName( name ))) - return (tex - vk_textures); - - return 0; -} -const char* VK_TextureName( unsigned int texnum ) -{ - ASSERT( texnum >= 0 && texnum < MAX_TEXTURES ); - return vk_textures[texnum].name; +qboolean R_VkTextureUpload(int index, vk_texture_t *tex, rgbdata_t *const *const layers, int num_layers, colorspace_hint_e colorspace_hint) { + return uploadTexture( index, tex, layers, num_layers, false, colorspace_hint ); } -const byte* VK_TextureData( unsigned int texnum ) -{ - PRINT_NOT_IMPLEMENTED_ARGS("texnum=%d", texnum); - // We don't store original texture data - // TODO do we need to? - return NULL; -} - -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; - - // see if already loaded - { - const vk_texture_t *const tex = Common_TextureForName( name ); - if( tex ) - return (tex - vk_textures); - } - - uint picFlags = 0; - - if( FBitSet( flags, TF_NOFLIP_TGA )) - SetBits( picFlags, IL_DONTFLIP_TGA ); - - if( FBitSet( flags, TF_KEEP_SOURCE ) && !FBitSet( flags, TF_EXPAND_SOURCE )) - SetBits( picFlags, IL_KEEP_8BIT ); - - // set some image flags - gEngine.Image_SetForceFlags( picFlags ); - - rgbdata_t *const pic = gEngine.FS_LoadImage( name, buf, size ); - if( !pic ) return 0; // couldn't loading image - - // allocate the new one - vk_texture_t* const tex = Common_AllocTexture( name, flags ); - - // upload texture - VK_ProcessImage( tex, pic ); - - if( !uploadTexture( tex, &pic, 1, false, colorspace_hint )) - { - memset( tex, 0, sizeof( vk_texture_t )); - gEngine.FS_FreeImage( pic ); // release source texture - return 0; - } - - tex->width = pic->width; - tex->height = pic->height; - - gEngine.FS_FreeImage( pic ); // release source texture - - // NOTE: always return texnum as index in array or engine will stop work !!! - return tex - vk_textures; -} - -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 ) -{ - PRINT_NOT_IMPLEMENTED_ARGS("name=%s width=%d height=%d buffer=%p flags=%08x", name, width, height, buffer, flags); - return 0; -} - -int VK_LoadTextureArray( const char **names, int flags ) -{ - PRINT_NOT_IMPLEMENTED(); - return 0; -} - -int VK_CreateTextureArray( const char *name, int width, int height, int depth, const void *buffer, texFlags_t flags ) -{ - PRINT_NOT_IMPLEMENTED_ARGS("name=%s width=%d height=%d buffer=%p flags=%08x", name, width, height, buffer, flags); - return 0; -} - -void VK_FreeTexture( unsigned int texnum ) { - vk_texture_t *tex; - vk_texture_t **prev; - vk_texture_t *cur; - if( texnum <= 0 ) return; - - tex = vk_textures + texnum; - - ASSERT( tex != NULL ); - - // already freed? - if( !tex->vk.image.image ) return; - - // debug - if( !tex->name[0] ) - { - ERR("VK_FreeTexture: trying to free unnamed texture with index %u", texnum ); - return; - } - - // remove from hash table - prev = &vk_texturesHashTable[tex->hashValue]; - - while( 1 ) - { - cur = *prev; - if( !cur ) break; - - if( cur == tex ) - { - *prev = cur->nextHash; - break; - } - prev = &cur->nextHash; - } - - /* - // release source - if( tex->original ) - gEngine.FS_FreeImage( tex->original ); - */ - +void R_VkTextureDestroy( int index, vk_texture_t *tex ) { // Need to make sure that there are no references to this texture anywhere. // It might have been added to staging and then immediately deleted, leaving references to its vkimage // in the staging command buffer. See https://github.com/w23/xash3d-fwgs/issues/464 @@ -1159,261 +568,60 @@ void VK_FreeTexture( unsigned int texnum ) { XVK_CHECK(vkDeviceWaitIdle(vk_core.device)); R_VkImageDestroy(&tex->vk.image); - g_textures.stats.size_total -= tex->total_size; - g_textures.stats.count--; - memset(tex, 0, sizeof(*tex)); + g_vktextures.stats.size_total -= tex->total_size; + g_vktextures.stats.count--; - tglob.dii_all_textures[texnum] = (VkDescriptorImageInfo){ - .imageView = vk_textures[tglob.defaultTexture].vk.image.view, - .imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, - .sampler = tglob.default_sampler_fixme, - }; + // Reset descriptor sets to default texture + setDescriptorSet(index, tex, kColorspaceNative); + + tex->total_size = 0; + tex->width = tex->height = 0; + + // TODO: currently cannot do this because vk_render depends on all textures having some descriptor regardless of their alive-ness + // TODO tex->vk.descriptor_unorm = VK_NULL_HANDLE; } -static int loadTextureFromBuffers( const char *name, rgbdata_t *const *const pic, int pic_count, texFlags_t flags, qboolean update ) { - vk_texture_t *tex; - - if( !Common_CheckTexName( name )) - return 0; - - // see if already loaded - if(( tex = Common_TextureForName( name )) && !update ) - return (tex - vk_textures); - - // couldn't loading image - if( !pic ) return 0; - - if( update ) - { - if( tex == NULL ) - gEngine.Host_Error( "VK_LoadTextureFromBuffer: couldn't find texture %s for update\n", name ); - SetBits( tex->flags, flags ); - } - else - { - // allocate the new one - tex = Common_AllocTexture( name, flags ); - } - - for (int i = 0; i < pic_count; ++i) - VK_ProcessImage( tex, pic[i] ); - - if( !uploadTexture( tex, pic, pic_count, false, kColorspaceGamma )) - { - memset( tex, 0, sizeof( vk_texture_t )); - return 0; - } - - return (tex - vk_textures); -} - -int VK_LoadTextureFromBuffer( const char *name, rgbdata_t *pic, texFlags_t flags, qboolean update ) { - return loadTextureFromBuffers(name, &pic, 1, flags, update); -} - -int XVK_TextureLookupF( const char *fmt, ...) { - int tex_id = 0; - char buffer[1024]; - va_list argptr; - va_start( argptr, fmt ); - vsnprintf( buffer, sizeof buffer, fmt, argptr ); - va_end( argptr ); - - tex_id = VK_FindTexture(buffer); - //DEBUG("Looked up texture %s -> %d", buffer, tex_id); - return tex_id; -} - -static void unloadSkybox( void ) { - if (tglob.skybox_cube.vk.image.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)); +void unloadSkybox( void ) { + if (g_vktextures.skybox_cube.vk.image.image) { + R_VkTextureDestroy( -1, &g_vktextures.skybox_cube ); + memset(&g_vktextures.skybox_cube, 0, sizeof(g_vktextures.skybox_cube)); } tglob.fCustomSkybox = false; } -static struct { - const char *suffix; - uint flags; -} g_skybox_info[6] = { - {"rt", IMAGE_ROT_90}, - {"lf", IMAGE_FLIP_Y | IMAGE_ROT_90 | IMAGE_FLIP_X}, - {"bk", IMAGE_FLIP_Y}, - {"ft", IMAGE_FLIP_X}, - {"up", IMAGE_ROT_90}, - {"dn", IMAGE_ROT_90}, -}; - -#define SKYBOX_MISSED 0 -#define SKYBOX_HLSTYLE 1 -#define SKYBOX_Q1STYLE 2 - -static int CheckSkybox( const char *name ) -{ - const char *skybox_ext[] = { "png", "dds", "tga", "bmp" }; - int i, j, num_checked_sides; - char sidename[MAX_VA_STRING]; - - // search for skybox images - for( i = 0; i < ARRAYSIZE(skybox_ext); i++ ) - { - num_checked_sides = 0; - for( j = 0; j < 6; j++ ) - { - // build side name - Q_snprintf( sidename, sizeof( sidename ), "%s%s.%s", name, g_skybox_info[j].suffix, skybox_ext[i] ); - if( gEngine.fsapi->FileExists( sidename, false )) - num_checked_sides++; - - } - - if( num_checked_sides == 6 ) - return SKYBOX_HLSTYLE; // image exists - - for( j = 0; j < 6; j++ ) - { - // build side name - Q_snprintf( sidename, sizeof( sidename ), "%s_%s.%s", name, g_skybox_info[j].suffix, skybox_ext[i] ); - if( gEngine.fsapi->FileExists( sidename, false )) - num_checked_sides++; - } - - if( num_checked_sides == 6 ) - return SKYBOX_Q1STYLE; // images exists - } - - return SKYBOX_MISSED; +VkDescriptorImageInfo R_VkTexturesGetSkyboxDescriptorImageInfo( void ) { + return (VkDescriptorImageInfo){ + .sampler = g_vktextures.default_sampler, + .imageView = g_vktextures.skybox_cube.vk.image.view + ? g_vktextures.skybox_cube.vk.image.view + : g_vktextures.cubemap_placeholder.vk.image.view, + .imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + }; } -static qboolean loadSkybox( const char *prefix, int style ) { - rgbdata_t *sides[6]; - qboolean success = false; - int i; - - // release old skybox - unloadSkybox(); - DEBUG( "SKY: " ); - - for( i = 0; i < 6; i++ ) { - char sidename[MAX_STRING]; - if( style == SKYBOX_HLSTYLE ) - Q_snprintf( sidename, sizeof( sidename ), "%s%s", prefix, g_skybox_info[i].suffix ); - else Q_snprintf( sidename, sizeof( sidename ), "%s_%s", prefix, g_skybox_info[i].suffix ); - - sides[i] = gEngine.FS_LoadImage( sidename, NULL, 0); - if (!sides[i] || !sides[i]->buffer) - break; - - { - uint img_flags = g_skybox_info[i].flags; - // we need to expand image into RGBA buffer - if( sides[i]->type == PF_INDEXED_24 || sides[i]->type == PF_INDEXED_32 ) - img_flags |= IMAGE_FORCE_RGBA; - gEngine.Image_Process( &sides[i], 0, 0, img_flags, 0.f ); - } - DEBUG( "%s%s%s", prefix, g_skybox_info[i].suffix, i != 5 ? ", " : ". " ); - } - - if( i != 6 ) - goto cleanup; - - if( !Common_CheckTexName( prefix )) - goto cleanup; - - Q_strncpy( tglob.skybox_cube.name, prefix, sizeof( tglob.skybox_cube.name )); - success = uploadTexture(&tglob.skybox_cube, sides, 6, true, kColorspaceGamma); - -cleanup: - for (int j = 0; j < i; ++j) - gEngine.FS_FreeImage( sides[j] ); // release source texture - - if (success) { - tglob.fCustomSkybox = true; - DEBUG( "Skybox done" ); - } else { - tglob.skybox_cube.name[0] = '\0'; - ERR( "Skybox failed" ); - unloadSkybox(); - } - - return success; +qboolean R_VkTexturesSkyboxUpload( const char *name, rgbdata_t *const sides[6], colorspace_hint_e colorspace_hint, qboolean placeholder) { + vk_texture_t *const dest = placeholder ? &g_vktextures.cubemap_placeholder : &g_vktextures.skybox_cube; + Q_strncpy( TEX_NAME(dest), name, sizeof( TEX_NAME(dest) )); + return uploadTexture(-1, dest, sides, 6, true, colorspace_hint); } -static const char *skybox_default = "desert"; -static const char *skybox_prefixes[] = { "pbr/env/%s", "gfx/env/%s" }; - -void XVK_SetupSky( const char *skyboxname ) { - if( !COM_CheckString( skyboxname )) - { - unloadSkybox(); - return; // clear old skybox - } - - for (int i = 0; i < ARRAYSIZE(skybox_prefixes); ++i) { - char loadname[MAX_STRING]; - int style, len; - - Q_snprintf( loadname, sizeof( loadname ), skybox_prefixes[i], skyboxname ); - COM_StripExtension( loadname ); - - // kill the underline suffix to find them manually later - len = Q_strlen( loadname ); - - if( loadname[len - 1] == '_' ) - loadname[len - 1] = '\0'; - style = CheckSkybox( loadname ); - - if (loadSkybox(loadname, style)) - return; - } - - if (Q_stricmp(skyboxname, skybox_default) != 0) { - WARN("missed or incomplete skybox '%s'", skyboxname); - XVK_SetupSky( "desert" ); // force to default - } +VkDescriptorSet R_VkTextureGetDescriptorUnorm( uint index ) { + ASSERT( index < MAX_TEXTURES ); + // TODO make an array of unorm descriptors + const vk_texture_t *const tex = R_TextureGetByIndex(index); + ASSERT(tex->vk.descriptor_unorm != VK_NULL_HANDLE); + return tex->vk.descriptor_unorm; } -int XVK_FindTextureNamedLike( const char *texture_name ) { - const model_t *map = gEngine.pfnGetModelByIndex( 1 ); - string texname; - - // Try texture name as-is first - int tex_id = XVK_TextureLookupF("%s", texture_name); - - // Try bsp name - if (!tex_id) - tex_id = XVK_TextureLookupF("#%s:%s.mip", map->name, texture_name); - - if (!tex_id) { - const char *wad = g_map_entities.wadlist; - for (; *wad;) { - const char *const wad_end = Q_strchr(wad, ';'); - tex_id = XVK_TextureLookupF("%.*s/%s.mip", wad_end - wad, wad, texture_name); - if (tex_id) - break; - wad = wad_end + 1; - } - } - - return tex_id ? tex_id : -1; +const VkDescriptorImageInfo* R_VkTexturesGetAllDescriptorsArray( void ) { + return g_vktextures.dii_all_textures; } -int XVK_CreateDummyTexture( const char *name ) { - // emo-texture from quake1 - rgbdata_t *pic = Common_FakeImage( 16, 16, 1, IMAGE_HAS_COLOR ); - - for( int y = 0; y < 16; y++ ) - { - for( int x = 0; x < 16; x++ ) - { - if(( y < 8 ) ^ ( x < 8 )) - ((uint *)pic->buffer)[y*16+x] = 0xFFFF00FF; - else ((uint *)pic->buffer)[y*16+x] = 0xFF000000; - } - } - - return VK_LoadTextureInternal(name, pic, TF_NOMIPMAP); +VkDescriptorImageInfo R_VkTexturesGetBlueNoiseImageInfo( void ) { + return (VkDescriptorImageInfo) { + .sampler = g_vktextures.default_sampler, + .imageView = g_vktextures.blue_noise.vk.image.view, + .imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + }; } diff --git a/ref/vk/vk_textures.h b/ref/vk/vk_textures.h index 077eb423..419f4302 100644 --- a/ref/vk/vk_textures.h +++ b/ref/vk/vk_textures.h @@ -1,111 +1,46 @@ #pragma once + +#include "r_textures.h" #include "vk_core.h" #include "vk_image.h" #include "vk_const.h" -#include "xash3d_types.h" -#include "const.h" -#include "render_api.h" -#include "com_image.h" +#include "unordered_roadmap.h" typedef struct vk_texture_s { - char name[256]; - int width, height; - texFlags_t flags; - uint texnum; + urmom_header_t hdr_; + + int width, height, depth; + uint32_t flags; + int total_size; struct { r_vk_image_t image; + + // TODO external table VkDescriptorSet descriptor_unorm; } vk; - uint hashValue; - struct vk_texture_s *nextHash; + int refcount; + qboolean ref_interface_visible; - int total_size; + // TODO "cache" eviction + // int used_maps_ago; } vk_texture_t; -#define MAX_LIGHTMAPS 256 +#define TEX_NAME(tex) ((tex)->hdr_.key) -#define MAX_SAMPLERS 8 // TF_NEAREST x 2 * TF_BORDER x 2 * TF_CLAMP x 2 +qboolean R_VkTexturesInit( void ); +void R_VkTexturesShutdown( void ); -typedef struct vk_textures_global_s -{ - poolhandle_t mempool; - - int defaultTexture; // use for bad textures - int particleTexture; - int whiteTexture; - int grayTexture; - int blackTexture; - int solidskyTexture; // quake1 solid-sky layer - int alphaskyTexture; // quake1 alpha-sky layer - int lightmapTextures[MAX_LIGHTMAPS]; - int dlightTexture; // custom dlight texture - int cinTexture; // cinematic texture +qboolean R_VkTexturesSkyboxUpload( const char *name, rgbdata_t *const sides[6], colorspace_hint_e colorspace_hint, qboolean placeholder); -// Hardcoded expected blue noise texture slot -// TODO consider moving it into a separate resource bindable by request -// TODO make it a 3D texture. Currently it's just a sequence of BLUE_NOISE_SIZE textures, loaded into consecutive slots. -#define BLUE_NOISE_TEXTURE_ID 7 +qboolean R_VkTextureUpload(int index, vk_texture_t *tex, rgbdata_t *const *const layers, int num_layers, colorspace_hint_e colorspace_hint); +void R_VkTextureDestroy(int index, vk_texture_t *tex); -// Hardcode blue noise texture size to 64x64x64 -#define BLUE_NOISE_SIZE 64 +VkDescriptorImageInfo R_VkTexturesGetSkyboxDescriptorImageInfo( void ); +const VkDescriptorImageInfo* R_VkTexturesGetAllDescriptorsArray( void ); +VkDescriptorSet R_VkTextureGetDescriptorUnorm( uint index ); - qboolean fCustomSkybox; // TODO do we need this for anything? - - 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 - VkSampler default_sampler_fixme; - - struct { - texFlags_t flags; - VkSampler sampler; - } samplers[MAX_SAMPLERS]; -} vk_textures_global_t; - -// TODO rename this consistently -extern vk_textures_global_t tglob; - -// Helper functions -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_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 R_VkLoadTexture( const char *filename, colorspace_hint_e colorspace, qboolean force_reload); - -int XVK_TextureLookupF( const char *fmt, ...); - -#define VK_LoadTextureInternal( name, pic, flags ) VK_LoadTextureFromBuffer( name, pic, flags, false ) - -void XVK_SetupSky( const char *skyboxname ); - -// Tries to find a texture by its short name -// Full names depend on map name, wad name, etc. This function tries them all. -// Returns -1 if not found -int XVK_FindTextureNamedLike( const char *texture_name ); - -int XVK_CreateDummyTexture( const char *name ); +VkDescriptorImageInfo R_VkTexturesGetBlueNoiseImageInfo( void ); diff --git a/ref/vk/vk_triapi.c b/ref/vk/vk_triapi.c index 9d6b9fed..1252fa08 100644 --- a/ref/vk/vk_triapi.c +++ b/ref/vk/vk_triapi.c @@ -4,8 +4,6 @@ #include "vk_sprite.h" // R_GetSpriteTexture #include "vk_logs.h" -#include "vk_textures.h" // FIXME temp - #include "xash3d_mathlib.h" #define MAX_TRIAPI_VERTICES 1024 diff --git a/ref/vk/wscript b/ref/vk/wscript index 4ad0c1cb..57066be6 100644 --- a/ref/vk/wscript +++ b/ref/vk/wscript @@ -153,12 +153,28 @@ def build(bld): #bld.install_files(bld.env.LIBDIR + '/valve', things) bld.install_files(bld.env.LIBDIR, - bld.path.ant_glob('data/**'), - cwd=bld.path.find_dir('data/'), - relative_trick=True) + bld.path.ant_glob('data/**'), + cwd=bld.path.find_dir('data/'), + relative_trick=True) - #bld.program(features='test', defines=['ALOLCATOR_TEST'],source='alolcator.c', target='alolcator') - bld.add_post_fun(printTestSummary) + if bld.env.TESTS: + bld.program( + features='test', + defines=['ALOLCATOR_TEST'], + source='alolcator.c', + target='test_alolcator', + subsystem = bld.env.CONSOLE_SUBSYSTEM, + install_path = None) - #from waflib.Tools import waf_unit_test - #bld.add_post_fun(waf_unit_test.summary) + tests = { + 'unordered_roadmap': 'tests/unordered_roadmap.c', + } + + for i in tests: + bld.program(features = 'test', + source = tests[i], + target = 'test_%s' % i, + subsystem = bld.env.CONSOLE_SUBSYSTEM, + install_path = None) + + #bld.add_post_fun(printTestSummary)