Merge pull request #708 from w23/stream-E346

Skybox improvements (E346-E349)

- [x] #706 
  - [x] Do not load skybox if map doesn't have any `SURF_DRAWSKY` surfaces.
  - [x] Cache: do not reload the same skybox.
  - [x] Enable loading packed KTX2 cubemaps as skyboxes in engine/imagelib
- [x] Reuse existing imagelib cubemap loading routines, remove custom skybox loading code
- [x] Allow hiding `SURF_DRAWSKY` surfaces via `"_xvk_remove_all_sky_surfaces" "1"`, #579 
- [x] #677 
  - [x] Add skybox exposure control for HDR skyboxes
  - [x] Allow reloading skyboxes when reloading patches
This commit is contained in:
Ivan Avdeev 2023-12-18 09:56:04 -08:00 committed by GitHub
commit 7e0553d408
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 554 additions and 360 deletions

View File

@ -65,6 +65,22 @@ static void Image_KTX2Format( uint32_t ktx2_format )
}
}
static const int g_remap_cube_layer[6] = {
/* Face order
0 1 2 3 4 5 -- index
ft, bk, up, dn, rt, lf -- xash
+x, -x, +y, -y, +z, -z -- KTX2
rt, lf, bk, ft, up, dn -- ref_vk
texture[face] = ktx2[map[face]], e.g.:
texture[rt] = ktx2[+z = 4]
texture[lf] = ktx2[-z = 5]
texture[bk] = ktx2[-x = 1]
...
*/
4, 5, 1, 0, 2, 3
};
static qboolean Image_KTX2Parse( const ktx2_header_t *header, const byte *buffer, fs_offset_t filesize )
{
ktx2_index_t index;
@ -93,7 +109,7 @@ static qboolean Image_KTX2Parse( const ktx2_header_t *header, const byte *buffer
return false;
}
if( header->faceCount > 1 )
if( header->faceCount != 1 && header->faceCount != 6 )
{
Con_DPrintf( S_ERROR "%s: unsupported KTX2 faceCount %d\n", __FUNCTION__, header->faceCount );
return false;
@ -128,10 +144,10 @@ static qboolean Image_KTX2Parse( const ktx2_header_t *header, const byte *buffer
ktx2_level_t level;
memcpy( &level, levels_begin + mip * sizeof( level ), sizeof( level ));
if( mip_size != level.byteLength )
if( mip_size * header->faceCount != level.byteLength )
{
Con_DPrintf( S_ERROR "%s: mip=%d size mismatch read=%d, but computed=%d\n",
__FUNCTION__, mip, (int)level.byteLength, mip_size );
Con_DPrintf( S_ERROR "%s: mip=%d size mismatch read=%d, but computed=%d(mip=%d * faces=%d)\n",
__FUNCTION__, mip, (int)level.byteLength, mip_size * header->faceCount, mip_size, header->faceCount );
return false;
}
@ -139,8 +155,11 @@ static qboolean Image_KTX2Parse( const ktx2_header_t *header, const byte *buffer
max_offset = Q_max( max_offset, level.byteLength + level.byteOffset );
}
if( max_offset > filesize )
if( max_offset > filesize ) {
Con_DPrintf( S_ERROR "%s: size to read %d exceeds file size %d\n",
__FUNCTION__, (int)max_offset, (int)filesize );
return false;
}
image.size = total_size;
image.num_mips = header->levelCount;
@ -148,12 +167,29 @@ static qboolean Image_KTX2Parse( const ktx2_header_t *header, const byte *buffer
image.rgba = Mem_Malloc( host.imagepool, image.size );
memcpy( image.rgba, buffer, image.size );
for( int mip = 0, cursor = 0; mip < header->levelCount; ++mip )
{
ktx2_level_t level;
memcpy( &level, levels_begin + mip * sizeof( level ), sizeof( level ));
memcpy( image.rgba + cursor, buffer + level.byteOffset, level.byteLength );
cursor += level.byteLength;
int cursors[6] = {0};
if ( header->faceCount == 6 ) {
image.flags |= IMAGE_CUBEMAP;
for ( int face = 0; face < header->faceCount; ++face )
cursors[face] = g_remap_cube_layer[face] * total_size / header->faceCount;
}
for( int mip = 0; mip < header->levelCount; ++mip )
{
ktx2_level_t level;
int face_size = 0;
memcpy( &level, levels_begin + mip * sizeof( level ), sizeof( level ));
face_size = level.byteLength / header->faceCount;
for ( int face = 0; face < header->faceCount; ++face )
{
memcpy( image.rgba + cursors[face], buffer + level.byteOffset + face * face_size, face_size );
cursors[face] += face_size;
}
}
}
return true;
@ -179,7 +215,7 @@ qboolean Image_LoadKTX2( const char *name, const byte *buffer, fs_offset_t files
image.depth = Q_max( 1, header.pixelDepth );
image.num_mips = 1;
ClearBits( image.flags, IMAGE_HAS_COLOR | IMAGE_HAS_ALPHA | IMAGE_HAS_LUMA );
ClearBits( image.flags, IMAGE_HAS_COLOR | IMAGE_HAS_ALPHA | IMAGE_HAS_LUMA | IMAGE_CUBEMAP );
if( !Image_KTX2Parse( &header, buffer, filesize ))
{

View File

@ -178,7 +178,9 @@ static qboolean FS_AddSideToPack( int adjust_flags )
}
// keep constant size, render.dll expecting it
image.size = image.source_width * image.source_height * 4;
// NOTE: This is super incorrect for compressed images.
// No idea why it was needed
// image.size = image.source_width * image.source_height * 4;
// mixing dds format with any existing ?
if( image.type != image.source_type )

View File

@ -95,6 +95,7 @@ static const loadpixformat_t load_null[] =
static const loadpixformat_t load_game[] =
{
{ "%s%s.%s", "ktx2", Image_LoadKTX2, IL_HINT_NO }, // ktx2 for world and studio models
{ "%s%s.%s", "dds", Image_LoadDDS, IL_HINT_NO }, // dds for world and studio models
{ "%s%s.%s", "bmp", Image_LoadBMP, IL_HINT_NO }, // WON menu images
{ "%s%s.%s", "tga", Image_LoadTGA, IL_HINT_NO }, // hl vgui menus
@ -105,7 +106,6 @@ static const loadpixformat_t load_game[] =
{ "%s%s.%s", "lmp", Image_LoadLMP, IL_HINT_NO }, // hl menu images (cached.wad etc)
{ "%s%s.%s", "fnt", Image_LoadFNT, IL_HINT_HL }, // hl console font (fonts.wad etc)
{ "%s%s.%s", "pal", Image_LoadPAL, IL_HINT_NO }, // install studio\sprite palette
{ "%s%s.%s", "ktx2", Image_LoadKTX2, IL_HINT_NO }, // ktx2 for world and studio models
{ NULL, NULL, NULL, IL_HINT_NO }
};

View File

@ -1011,3 +1011,71 @@ This would need the same as above, plus:
- A: probably should still do it on GPU lol
This would also allow passing arbitrary per-pixel data from shaders, which would make shader debugging much much easier.
# 2023-12-12 E346
## Skyboxes
### Current state
- `R_TextureSetupSky()`
- called from:
`vk_scene.c`/`R_NewMap()`
← engine ?? -- seems optional, r_soft doesn't implement it. Set on:
- `skybox` console command
- certain movevars change, whatever that is
- `unloadSkybox()`
- for [pbr/, old/] do
- `CheckSkybox()`
- make sidenames and check whether files exist
- `loadSkybox()`
- `unloadSkybox()`
- make sidenames
- `FS_LoadImage()` and `ImageProcess()`
- `R_VkTextureSkyboxUpload(sides)`
- if failed and not default already: `R_TextureSetupSky(default)` (recurse)
# 2023-12-14 E347
## TIL engine imagelib `FS_LoadImage()`:
1. Pick format based on extension
2. If no extension is specified, try all supported extensions in sequence.
3. If loading single file failed, try to load it as skybox cubemap:
Go through all sides suffixes and try to load them in the similar fashion:
if no extension, then try all supported extensions
- `Image_Process()` can only rotate uncompressed formats. (Technically it might be possible to also
rotate some compressed format, which will amount to just reordering blocks, and then reordering block
contents. Mendokusai). Therefore, we can't just replace png sides with compressed ktx2 sides directly.
KTX2 sides should be pre-rotated.
# 2023-12-15 E348
## Textures layout
imagelib image buffer layout:
- sides[1|6]
- mips[biggest -> smallest]
- (pixel data)
KTX2 file layout:
- mips[smallest -> biggest]
- sides[1|6]
- (pixel data)
# 2023-12-18 E349
## Xash vs KTX2/vk cubemap face order
Vulkan order:
+X, -X, +Y, -Y, +Z, -Z
rt, lf, bk, ft, up, dn
0, 1, 2, 3, 4, 5
Xash order:
ft, bk, up, dn, rt, lf
Remap (KTX2 -> xash || xash[face] = KTX2[map[face]]):
3, 2, 4, 5, 0, 1
Remap (xash -> vk, vk[map[face]] = xash[face]):
4, 5, 1, 0, 2, 3
??? this shoudln't work
default cubemap order:
xash vk (remapped)
+Y = -X
+X = +Z
+Z = +Y

View File

@ -1,8 +1,33 @@
# 2023-12-18 E349
- [x] KTX2 cubemaps
- [ ] improve logs "vk/tex: Loaded skybox pbr/env/%.*s"
- [ ] variable cubemap exposure
- [ ] add skybox test
# 2023-12-15 E348
- [x] fix ktx2 sides corruption
# 2023-12-14 E346-E347
- [x] Optimize skybox loading, #706
- [x] Do not load skybox when there are no SURF_DRAWSKY, #579
- [x] Do not reload the same skybox
- [-] Load skyboxes from KTX2 sides
→ doesn't work as easily, as there's no way to rotate compressed images.
KTX2 sides should be pre-rotated
- [x] do not generate mips for skybox
- [x] support imagelib cubemaps
- [x] use imagelib skybox loader
- [x] Hide all SURF_DRAWSKY while retaining skybox, #579
- [x] possible issues with TF_NOMIPMAP
- [x] used incorrectly when loading blue noise textures
- [x] what about regular usage?
# 2023-12-11 E345
- [x] fix incorrect basecolor brdf multiplication, #666
- [x] fix black dielectrics, #666
- [x] fix incorrect basecolor brdf multiplication, #666
- [x] fixup skybox glitches caused by #666 fix
- [ ] Patch overlay textures (#696) → turned out to be much more difficult than expected.
- [x] Do not patch sprite textures for traditional raster, #695
- [x] fixup skybox glitches caused by #666 fix
# 2023-12-05 E342
- [x] tone down the specular indirect blur
@ -17,7 +42,6 @@ Longer-term agenda for current season:
- [ ] Tools:
- [ ] Shader profiling. Measure impact of changes. Regressions.
- [ ] Better PBR math, e.g.:
- [ ] fix black dielectrics, #666
- [ ] Transparency:
- [ ] Figure out why additive transparency differs visibly from raster
- [ ] Extract and specialize effects, e.g.

View File

@ -1,7 +1,8 @@
{
"_xvk_ent_id" "246" // DANGER sign
"origin" "1 0 0"
}
{
"_xvk_smoothing_threshold" "54" // FIXME
}
{
"_xvk_ent_id" "246" // DANGER sign
"origin" "1 0 0"
}
{
"_xvk_smoothing_threshold" "54" // FIXME
"_xvk_remove_all_sky_surfaces" "1"
}

View File

@ -3,6 +3,7 @@
#include "vk_framectl.h"
#include "vk_cvar.h"
#include "vk_combuf.h"
#include "stringview.h"
#include "profiler.h"
@ -302,23 +303,9 @@ static void handlePause( uint32_t prev_frame_index ) {
}
}
// TODO move this to vk_common or something
int stringViewCmp(const_string_view_t sv, const char* s) {
for (int i = 0; i < sv.len; ++i) {
const int d = sv.s[i] - s[i];
if (d != 0)
return d;
if (s[i] == '\0')
return 1;
}
// Check that both strings end the same
return '\0' - s[sv.len];
}
static int findMetricIndexByName( const_string_view_t name) {
for (int i = 0; i < g_speeds.metrics_count; ++i) {
if (stringViewCmp(name, g_speeds.metrics[i].name) == 0)
if (svCmp(name, g_speeds.metrics[i].name) == 0)
return i;
}
@ -327,7 +314,7 @@ static int findMetricIndexByName( const_string_view_t name) {
static int findGraphIndexByName( const_string_view_t name) {
for (int i = 0; i < g_speeds.graphs_count; ++i) {
if (stringViewCmp(name, g_speeds.graphs[i].name) == 0)
if (svCmp(name, g_speeds.graphs[i].name) == 0)
return i;
}

View File

@ -8,6 +8,7 @@
#include "r_speeds.h"
#include "profiler.h"
#include "unordered_roadmap.h"
#include "stringview.h"
#include "xash3d_mathlib.h"
#include "crtlib.h"
@ -28,10 +29,12 @@ static struct {
vk_texture_t all[MAX_TEXTURES];
urmom_desc_t all_desc;
} g_textures;
// FIXME imported from vk_textures.h
void unloadSkybox( void );
struct {
r_skybox_info_t info;
char current_name[MAX_STRING];
} skybox;
} g_textures;
static void createDefaultTextures( void );
static void destroyDefaultTextures( void );
@ -126,58 +129,6 @@ void R_TexturesShutdown( void )
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;
@ -284,19 +235,11 @@ static void createDefaultTextures( void )
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;
pic = Common_FakeImage( 4, 4, 1, IMAGE_HAS_COLOR | IMAGE_CUBEMAP );
memset(pic->buffer, 0, pic->size);
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 );
const qboolean is_placeholder = true;
R_VkTexturesSkyboxUpload( "skybox_placeholder", pic, kColorspaceGamma, is_placeholder );
}
}
@ -528,50 +471,6 @@ void BuildMipMap( byte *in, int srcWidth, int srcHeight, int srcDepth, int flags
}
}
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 )
{
@ -635,11 +534,16 @@ static int loadTextureInternalFromFile( const char *name, const byte *buf, size_
if( !pic )
goto cleanup;
if (pic->flags & IMAGE_CUBEMAP) {
ERR("%s: '%s' is invalid: cubemaps are not supported here", __FUNCTION__, name);
goto cleanup;
}
// Process flags, convert to rgba, etc
tex->flags = flags;
ProcessImage( tex, pic );
if( !R_VkTextureUpload( insert.index, tex, &pic, 1, colorspace_hint ))
if( !R_VkTextureUpload( insert.index, tex, pic, colorspace_hint ))
goto cleanup;
// New textures should have refcount = 1 regardless of refcount-aware calls
@ -759,6 +663,11 @@ int R_TextureUploadFromBuffer( const char *name, rgbdata_t *pic, texFlags_t flag
if( !pic )
return 0;
if (pic->flags & IMAGE_CUBEMAP) {
ERR("%s: '%s' is invalid: cubemaps are not supported here", __FUNCTION__, name);
return 0;
}
if( !checkTextureName( name ))
return 0;
@ -791,7 +700,7 @@ int R_TextureUploadFromBuffer( const char *name, rgbdata_t *pic, texFlags_t flag
ProcessImage( tex, pic );
if( !R_VkTextureUpload( insert.index, tex, &pic, 1, kColorspaceGamma ))
if( !R_VkTextureUpload( insert.index, tex, pic, kColorspaceGamma ))
{
if ( !update_only && insert.created )
urmomRemoveByIndex(&g_textures.all_desc, insert.index);
@ -808,144 +717,175 @@ int R_TextureUploadFromBuffer( const char *name, rgbdata_t *pic, texFlags_t flag
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},
};
static void skyboxParseInfo( const char *name ) {
// Start with default info
g_textures.skybox.info = (r_skybox_info_t) {
.exposure = 1.f,
};
#define SKYBOX_MISSED 0
#define SKYBOX_HLSTYLE 1
#define SKYBOX_Q1STYLE 2
char filename[MAX_STRING];
Q_snprintf(filename, sizeof(filename), "%s.mat", name);
byte *data = gEngine.fsapi->LoadFile( filename, 0, false );
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
qboolean success = false;
if (!data) {
INFO("Couldn't read skybox info '%s'", filename);
goto cleanup;
}
return SKYBOX_MISSED;
}
for (char *pos = (char*)data;;) {
char key[MAX_STRING];
char value[MAX_STRING];
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)
pos = COM_ParseFile(pos, key, sizeof(key));
if (!pos)
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 );
pos = COM_ParseFile(pos, value, sizeof(value));
if (!pos)
break;
if (Q_strcmp(key, "exposure") == 0) {
float exposure = 0;
if (1 != sscanf(value, "%f", &exposure)) {
ERR("Cannot parse exposure '%s' in skybox info '%s'", value, filename);
break;
}
g_textures.skybox.info.exposure = exposure;
DEBUG("Loaded skybox exposure=%f from '%s'", exposure, filename);
} else {
WARN("Unexpected key '%s' in skybox info '%s'", key, filename);
break;
}
DEBUG( "%s%s%s", prefix, g_skybox_info[i].suffix, i != 5 ? ", " : ". " );
success = true;
}
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 (data)
Mem_Free(data);
}
if (success) {
tglob.fCustomSkybox = true;
DEBUG( "Skybox done" );
} else {
ERR( "Skybox failed" );
unloadSkybox();
static qboolean skyboxLoad(const_string_view_t base, const char *prefix) {
qboolean success = false;
char loadname[MAX_STRING];
Q_snprintf( loadname, sizeof( loadname ), prefix, base.len, base.s );
rgbdata_t *pic = gEngine.FS_LoadImage( loadname, NULL, 0 );
if (!pic)
return false;
if (!(pic->flags & IMAGE_CUBEMAP)) {
ERR("%s: '%s' is invalid: skybox is expected to be a cubemap ", __FUNCTION__, loadname);
goto cleanup;
}
{
uint img_flags = pic->flags;
if( pic->type == PF_INDEXED_24 || pic->type == PF_INDEXED_32 )
img_flags |= IMAGE_FORCE_RGBA;
gEngine.Image_Process( &pic, 0, 0, img_flags, 0.f );
}
{
const qboolean is_placeholder = false;
success = R_VkTexturesSkyboxUpload( prefix, pic, kColorspaceGamma, is_placeholder );
}
if (success)
skyboxParseInfo(loadname);
cleanup:
if (pic)
gEngine.FS_FreeImage(pic);
if (success)
DEBUG( "Loaded skybox %s", prefix );
return success;
}
static const char *skybox_default = "desert";
static const char *skybox_prefixes[] = { "pbr/env/%s", "gfx/env/%s" };
static void skyboxUnload( void ) {
DEBUG("%s", __FUNCTION__);
R_VkTexturesSkyboxUnload();
g_textures.skybox.info = (r_skybox_info_t) {
.exposure = 1.f,
};
void R_TextureSetupSky( const char *skyboxname ) {
if( !COM_CheckString( skyboxname ))
{
unloadSkybox();
return; // clear old skybox
g_textures.skybox.current_name[0] = '\0';
}
static qboolean skyboxTryLoad( const char *skyboxname, qboolean force_reload ) {
// Check whether we even need skybox
if (!tglob.current_map_has_surf_sky) {
DEBUG("No SURF_DRAWSKY surfaces in this map, skipping loading skybox");
skyboxUnload();
return true;
}
for (int i = 0; i < ARRAYSIZE(skybox_prefixes); ++i) {
char loadname[MAX_STRING];
int style, len;
const_string_view_t basename = svStripExtension(svFromNullTerminated(skyboxname));
if (basename.len > 0 && basename.s[basename.len - 1] == '_')
basename.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 ( !basename.len ) {
skyboxUnload();
// No skybox
return true;
}
// 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 );
// Do not reload the same skybox
// TODO except explicit patches reload
if (!force_reload && svCmp(basename, g_textures.skybox.current_name) == 0)
return true;
// Try loading newer "PBR" upscaled skybox first
if (skyboxLoad(basename, "pbr/env/%.*s"))
goto success;
// Try loading original game skybox
if (skyboxLoad(basename, "gfx/env/%.*s"))
goto success;
skyboxUnload();
return false;
success:
svStrncpy(basename, g_textures.skybox.current_name, sizeof(g_textures.skybox.current_name));
return true;
}
static const char *k_skybox_default = "desert";
void skyboxSetup( const char *skyboxname, qboolean is_custom, qboolean force_reload ) {
DEBUG("%s: skyboxname='%s' is_custom=%d force_reload=%d", __FUNCTION__, skyboxname, is_custom, force_reload);
if (!skyboxTryLoad(skyboxname, force_reload)) {
WARN("missed or incomplete skybox '%s', trying default '%s'", skyboxname, k_skybox_default);
if (!skyboxTryLoad(k_skybox_default, force_reload)) {
ERR("Failed to load default skybox \"%s\"", k_skybox_default);
}
return;
}
tglob.fCustomSkybox = is_custom;
}
void R_TextureSetupCustomSky( const char *skyboxname ) {
const qboolean is_custom = true;
const qboolean force_reload = false;
skyboxSetup(skyboxname, is_custom, force_reload);
}
void R_TextureSetupSky( const char *skyboxname, qboolean force_reload ) {
const qboolean is_custom = false;
skyboxSetup(skyboxname, is_custom, force_reload);
}
r_skybox_info_t R_TexturesGetSkyboxInfo( void ) {
return g_textures.skybox.info;
}
// FIXME move to r_textures_extra.h

View File

@ -23,6 +23,8 @@ typedef struct vk_textures_global_s
// TODO wire it up for ref_interface_t return
qboolean fCustomSkybox;
qboolean current_map_has_surf_sky;
} vk_textures_global_t;
// TODO rename this consistently
@ -37,7 +39,7 @@ void R_TexturesShutdown( void );
int R_TextureFindByName( const char *name );
const char* R_TextureGetNameByIndex( unsigned int texnum );
void R_TextureSetupSky( const char *skyboxname );
void R_TextureSetupCustomSky( 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 );
@ -67,3 +69,10 @@ int R_TextureFindByNameLike( const char *texture_name );
struct vk_texture_s;
struct vk_texture_s *R_TextureGetByIndex( uint index );
void R_TextureSetupSky( const char *skyboxname, qboolean force_reload );
typedef struct r_skybox_info_s {
float exposure;
} r_skybox_info_t;
r_skybox_info_t R_TexturesGetSkyboxInfo( void );

View File

@ -201,6 +201,7 @@ struct UniformBuffer {
float ray_cone_width;
uint random_seed;
uint frame_counter;
float skybox_exposure;
uint debug_display_only;
};

View File

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

View File

@ -41,7 +41,7 @@ void main() {
const Kusok kusok = getKusok(geom.kusok_index);
if (kusok.material.tex_base_color == TEX_BASE_SKYBOX) {
payload.emissive.rgb = texture(skybox, gl_WorldRayDirectionEXT).rgb;
payload.emissive.rgb = texture(skybox, gl_WorldRayDirectionEXT).rgb * ubo.ubo.skybox_exposure;
return;
} else {
const vec4 color = getModelHeader(gl_InstanceID).color * kusok.material.base_color;

View File

@ -31,7 +31,7 @@ void primaryRayHit(rayQueryEXT rq, inout RayPayloadPrimary payload) {
if (kusok.material.tex_base_color == TEX_BASE_SKYBOX) {
// Mark as non-geometry
payload.hit_t.w = -payload.hit_t.w;
payload.emissive.rgb = texture(skybox, rayDirection).rgb;
payload.emissive.rgb = texture(skybox, rayDirection).rgb * ubo.ubo.skybox_exposure;
return;
} else {
payload.base_color_a = sampleTexture(material.tex_base_color, geom.uv, geom.uv_lods);

41
ref/vk/stringview.c Normal file
View File

@ -0,0 +1,41 @@
#include "stringview.h"
#include <string.h>
const_string_view_t svFromNullTerminated( const char *s ) {
return (const_string_view_t){.len = s?strlen(s):0, .s = s};
}
int svCmp(const_string_view_t sv, const char* s) {
for (int i = 0; i < sv.len; ++i) {
const int d = sv.s[i] - s[i];
if (d != 0)
return d;
if (s[i] == '\0')
return 1;
}
// Check that both strings end the same
return '\0' - s[sv.len];
}
const_string_view_t svStripExtension(const_string_view_t sv) {
for (int i = sv.len - 1; i >= 0; --i) {
const char c = sv.s[i];
if (c == '.')
return (const_string_view_t){ .len = i, .s = sv.s };
if (c == '/' || c == '\\' || c == ':')
break;
}
return sv;
}
#define MIN(a,b) ((a)<(b)?(a):(b))
void svStrncpy(const_string_view_t sv, char *dest, int size) {
const int to_copy = MIN(sv.len, size - 1);
memcpy(dest, sv.s, to_copy);
dest[to_copy] = '\0';
}

13
ref/vk/stringview.h Normal file
View File

@ -0,0 +1,13 @@
#pragma once
typedef struct {
const char *s;
int len;
} const_string_view_t;
const_string_view_t svFromNullTerminated( const char *s );
int svCmp(const_string_view_t sv, const char* s);
void svStrncpy(const_string_view_t sv, char *dest, int size);
const_string_view_t svStripExtension(const_string_view_t sv);

View File

@ -83,6 +83,7 @@ typedef struct {
int animated_count;
int conveyors_count;
int conveyors_vertices_count;
int sky_surfaces_count;
water_model_sizes_t water, side_water;
} model_sizes_t;
@ -992,8 +993,14 @@ static model_sizes_t computeSizes( const model_t *mod, qboolean is_worldmodel )
sizes.conveyors_count++;
sizes.conveyors_vertices_count += surf->numedges;
break;
case BrushSurface_Regular:
case BrushSurface_Sky:
sizes.sky_surfaces_count++;
// Do not count towards surfaces that we'll load (still need to count if for the purpose of loading skybox)
if (g_map_entities.remove_all_sky_surfaces)
continue;
break;
case BrushSurface_Regular:
break;
}
@ -1318,8 +1325,10 @@ static qboolean fillBrushSurfaces(fill_geometries_args_t args) {
break;
case BrushSurface_Conveyor:
break;
case BrushSurface_Regular:
case BrushSurface_Sky:
if (g_map_entities.remove_all_sky_surfaces)
continue;
case BrushSurface_Regular:
break;
}
@ -1610,6 +1619,11 @@ qboolean R_BrushModelLoad( model_t *mod, qboolean is_worldmodel ) {
const model_sizes_t sizes = computeSizes( mod, is_worldmodel );
if (is_worldmodel) {
tglob.current_map_has_surf_sky = sizes.sky_surfaces_count != 0;
DEBUG("sky_surfaces_count=%d, current_map_has_surf_sky=%d", sizes.sky_surfaces_count, tglob.current_map_has_surf_sky);
}
if (sizes.num_surfaces != 0) {
if (!createRenderModel(mod, bmodel, sizes, is_worldmodel)) {
ERR("Could not load brush model %s", mod->name);

View File

@ -28,12 +28,5 @@ inline static int clampi32(int v, int min, int max) {
return v;
}
typedef struct {
const char *s;
int len;
} const_string_view_t;
int stringViewCmp(const_string_view_t sv, const char* s);
extern ref_api_t gEngine;
extern ref_globals_t *gpGlobals;

View File

@ -1,5 +1,6 @@
#include "vk_logs.h"
#include "vk_cvar.h"
#include "stringview.h"
uint32_t g_log_debug_bits = 0;
@ -21,7 +22,7 @@ void R_LogSetVerboseModules( const char *p ) {
for (int i = 0; i < COUNTOF(g_log_module_pairs); ++i) {
const struct log_pair_t *const pair = g_log_module_pairs + i;
if (stringViewCmp(name, pair->name) == 0) {
if (svCmp(name, pair->name) == 0) {
gEngine.Con_Reportf("Enabling verbose logs for module \"%.*s\"\n", name.len, name.s);
bit = pair->bit;
break;

View File

@ -733,6 +733,11 @@ static void parseEntities( char *string, qboolean is_patch ) {
if (have_fields & Field__xvk_smoothing_group) {
addSmoothingGroup(&values);
}
if (have_fields & Field__xvk_remove_all_sky_surfaces) {
DEBUG("_xvk_remove_all_sky_surfaces=%d", values._xvk_remove_all_sky_surfaces);
g_map_entities.remove_all_sky_surfaces = values._xvk_remove_all_sky_surfaces;
}
}
}
break;

View File

@ -37,6 +37,7 @@
X(25, int, _xvk_smooth_entire_model, Int) \
X(26, int_array_t, _xvk_smoothing_excluded, IntArray) \
X(27, float, _xvk_tex_rotate, Float) \
X(28, int, _xvk_remove_all_sky_surfaces, Int) \
/* NOTE: not used
X(23, int, renderamt, Int) \
@ -175,6 +176,8 @@ typedef struct {
int excluded_count;
int excluded[MAX_EXCLUDED_SMOOTHING_SURFACES];
} smoothing;
qboolean remove_all_sky_surfaces;
} xvk_map_entities_t;
// TODO expose a bunch of things here as funtions, not as internal structures

View File

@ -559,7 +559,7 @@ static const ref_interface_t gReffuncs =
.R_GetTextureOriginalBuffer = R_GetTextureOriginalBuffer_UNUSED,
.GL_LoadTextureFromBuffer = R_TextureUploadFromBuffer,
.GL_ProcessTexture = GL_ProcessTexture_UNUSED,
.R_SetupSky = R_TextureSetupSky,
.R_SetupSky = R_TextureSetupCustomSky,
// 2D
R_Set2DMode,

View File

@ -118,13 +118,14 @@ static int findResourceOrEmptySlot(const char *name) {
return -1;
}
void VK_RayNewMap( void ) {
void VK_RayNewMapBegin( void ) {
RT_VkAccelNewMap();
RT_RayModel_Clear();
}
void VK_RayNewMapEnd( void ) {
g_rtx.res[ExternalResource_skybox].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_VkTexturesGetSkyboxDescriptorImageInfo(),
},
@ -132,7 +133,6 @@ void VK_RayNewMap( void ) {
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(),
},
@ -216,6 +216,7 @@ static void prepareUniformBuffer( const vk_ray_frame_render_args_t *args, int fr
ubo->res[1] = frame_height;
ubo->ray_cone_width = atanf((2.0f*tanf(DEG2RAD(fov_angle_y) * 0.5f)) / (float)frame_height);
ubo->frame_counter = frame_counter;
ubo->skybox_exposure = R_TexturesGetSkyboxInfo().exposure;
parseDebugDisplayValue();
if (g_rtx.debug.rt_debug_display_only_value) {

View File

@ -27,7 +27,8 @@ typedef struct {
} vk_ray_frame_render_args_t;
void VK_RayFrameEnd(const vk_ray_frame_render_args_t* args);
void VK_RayNewMap( void );
void VK_RayNewMapBegin( void );
void VK_RayNewMapEnd( void );
qboolean VK_RayInit( void );
void VK_RayShutdown( void );

View File

@ -87,30 +87,6 @@ static void loadLights( const model_t *const map ) {
RT_LightsLoadEnd();
}
// Clears all old map data
static void mapLoadBegin( const model_t *const map ) {
VK_EntityDataClear();
// Depends on VK_EntityDataClear()
R_StudioCacheClear();
R_GeometryBuffer_MapClear();
VK_ClearLightmap();
// This is to ensure that we have computed lightstyles properly
VK_RunLightStyles();
if (vk_core.rtx)
VK_RayNewMap();
RT_LightsNewMap(map);
}
static void mapLoadEnd(const model_t *const map) {
// TODO should we do something like R_BrushEndLoad?
VK_UploadLightmap();
}
static void preloadModels( void ) {
const int num_models = gEngine.EngineGetParm( PARM_NUMMODELS, 0 );
@ -145,8 +121,22 @@ static void preloadModels( void ) {
}
}
static void loadMap(const model_t* const map) {
mapLoadBegin(map);
static void loadMap(const model_t* const map, qboolean force_reload) {
VK_EntityDataClear();
// Depends on VK_EntityDataClear()
R_StudioCacheClear();
R_GeometryBuffer_MapClear();
VK_ClearLightmap();
// This is to ensure that we have computed lightstyles properly
VK_RunLightStyles();
if (vk_core.rtx)
VK_RayNewMapBegin();
RT_LightsNewMap(map);
R_SpriteNewMapFIXME();
@ -162,8 +152,22 @@ static void loadMap(const model_t* const map) {
preloadModels();
// Can only do after preloadModels(), as we need to know whether there are SURF_DRAWSKY
R_TextureSetupSky( gEngine.pfnGetMoveVars()->skyName, force_reload );
loadLights(map);
mapLoadEnd(map);
// TODO should we do something like R_BrushEndLoad?
VK_UploadLightmap();
// This is needed mainly for picking up skybox only after it has been loaded
// Most of the things here could be simplified if we had less imperative style for this renderer:
// - Group everything into modules with explicit dependencies, then init/shutdown/newmap order could
// be automatic and correct.
// - "Rendergraph"-like dependencies for resources (like textures, materials, skybox, ...). Then they
// could be loaded lazily when needed, and after all the needed info for them has been collected.
if (vk_core.rtx)
VK_RayNewMapEnd();
}
static void reloadPatches( void ) {
@ -176,7 +180,8 @@ static void reloadPatches( void ) {
R_BrushModelDestroyAll();
const model_t *const map = gEngine.pfnGetModelByIndex( 1 );
loadMap(map);
const qboolean force_reload = true;
loadMap(map, force_reload);
R_VkStagingFlushSync();
}
@ -260,9 +265,8 @@ 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));
R_TextureSetupSky( gEngine.pfnGetMoveVars()->skyName );
loadMap(map);
const qboolean force_reload = false;
loadMap(map, force_reload);
R_StudioResetPlayerModels();
}

View File

@ -52,20 +52,16 @@ static struct {
// 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);
// FIXME should be static
void unloadSkybox( void );
static qboolean uploadTexture(int index, vk_texture_t *tex, const rgbdata_t *layers, colorspace_hint_e colorspace_hint);
// 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 ) {
static void generateFallbackNoiseTextures( const 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);
@ -83,11 +79,11 @@ 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 = {
const rgbdata_t pic = {
.width = BLUE_NOISE_SIZE,
.height = BLUE_NOISE_SIZE,
.depth = BLUE_NOISE_SIZE,
.flags = TF_NOMIPMAP,
.flags = 0,
.type = PF_RGBA_32,
.size = blue_noise_size,
.buffer = (byte*)scratch,
@ -139,9 +135,8 @@ static void loadBlueNoiseTextures(void) {
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));
g_vktextures.blue_noise.flags = TF_NOMIPMAP;
ASSERT(uploadTexture(-1, &g_vktextures.blue_noise, &pic, kColorspaceLinear));
Mem_Free(scratch);
}
@ -189,7 +184,7 @@ qboolean R_VkTexturesInit( void ) {
static void textureDestroy( unsigned int index );
void R_VkTexturesShutdown( void ) {
unloadSkybox();
R_VkTexturesSkyboxUnload();
R_VkTextureDestroy(-1, &g_vktextures.cubemap_placeholder);
R_VkTextureDestroy(-1, &g_vktextures.blue_noise);
@ -466,32 +461,82 @@ static qboolean needToCreateImage( int index, vk_texture_t *tex, const r_vk_imag
return true;
}
static qboolean uploadTexture(int index, vk_texture_t *tex, rgbdata_t *const *const layers, int num_layers, qboolean cubemap, colorspace_hint_e colorspace_hint) {
static const char *getPFName(int pf_type) {
switch (pf_type) {
case PF_UNKNOWN: return "PF_UNKNOWN";
case PF_INDEXED_24: return "PF_INDEXED_24";
case PF_INDEXED_32: return "PF_INDEXED_32";
case PF_RGBA_32: return "PF_RGBA_32";
case PF_BGRA_32: return "PF_BGRA_32";
case PF_RGB_24: return "PF_RGB_24";
case PF_BGR_24: return "PF_BGR_24";
case PF_LUMINANCE: return "PF_LUMINANCE";
case PF_DXT1: return "PF_DXT1";
case PF_DXT3: return "PF_DXT3";
case PF_DXT5: return "PF_DXT5";
case PF_ATI2: return "PF_ATI2";
case PF_BC4_SIGNED: return "PF_BC4_SIGNED";
case PF_BC4_UNSIGNED: return "PF_BC4_UNSIGNED";
case PF_BC5_SIGNED: return "PF_BC5_SIGNED";
case PF_BC5_UNSIGNED: return "PF_BC5_UNSIGNED";
case PF_BC6H_SIGNED: return "PF_BC6H_SIGNED";
case PF_BC6H_UNSIGNED: return "PF_BC6H_UNSIGNED";
case PF_BC7_UNORM: return "PF_BC7_UNORM";
case PF_BC7_SRGB: return "PF_BC7_SRGB";
case PF_KTX2_RAW: return "PF_KTX2_RAW";
}
return "INVALID";
}
static const char* getColorspaceHintName(colorspace_hint_e ch) {
switch (ch) {
case kColorspaceGamma: return "gamma";
case kColorspaceLinear: return "linear";
case kColorspaceNative: return "native";
}
return "INVALID";
}
// xash imagelib cubemap layer order is not the one that vulkan expects
static const int g_remap_cube_layer[6] = {
/* ft = */ 3,
/* bk = */ 2,
/* up = */ 4,
/* dn = */ 5,
/* rt = */ 0,
/* lf = */ 1,
};
static qboolean uploadTexture(int index, vk_texture_t *tex, const rgbdata_t *pic, colorspace_hint_e colorspace_hint) {
tex->total_size = 0;
if (num_layers == 1 && layers[0]->type == PF_KTX2_RAW) {
if (!uploadRawKtx2(index, tex, layers[0]))
if (pic->type == PF_KTX2_RAW) {
if (!uploadRawKtx2(index, tex, pic))
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( width, height, depth, tex->flags, true ) : layers[0]->numMips;
const int width = pic->width;
const int height = pic->height;
const int depth = Q_max(1, pic->depth);
const qboolean compute_mips = !(tex->flags & TF_NOMIPMAP) && pic->type == PF_RGBA_32 && pic->numMips < 2;
const VkFormat format = VK_GetFormat(pic->type, colorspace_hint);
const int mipCount = compute_mips ? CalcMipmapCount( width, height, depth, tex->flags, true ) : Q_max(1, pic->numMips);
const qboolean is_cubemap = !!(pic->flags & IMAGE_CUBEMAP);
if (format == VK_FORMAT_UNDEFINED) {
ERR("Unsupported PF format %d", layers[0]->type);
ERR("Unsupported PF format %d", pic->type);
return false;
}
if (!validatePicLayers(TEX_NAME(tex), layers, num_layers))
return false;
DEBUG("Uploading texture[%d] %s, mips=%d(build=%d), layers=%d", index, TEX_NAME(tex), mipCount, compute_mips, num_layers);
DEBUG("Uploading texture[%d] %s, %dx%d fmt=%s(%s) cs=%s mips=%d(build=%d), is_cubemap=%d",
index, TEX_NAME(tex), width, height,
getPFName(pic->type), R_VkFormatName(format),
getColorspaceHintName(colorspace_hint),
mipCount, compute_mips, is_cubemap);
// TODO (not sure why, but GL does this)
// if( !ImageCompressed( layers->type ) && !FBitSet( tex->flags, TF_NOMIPMAP ) && FBitSet( layers->flags, IMAGE_ONEBIT_ALPHA ))
// if( !ImageCompressed( pic->type ) && !FBitSet( tex->flags, TF_NOMIPMAP ) && FBitSet( pic->flags, IMAGE_ONEBIT_ALPHA ))
// data = GL_ApplyFilter( data, tex->width, tex->height );
{
@ -501,13 +546,13 @@ static qboolean uploadTexture(int index, vk_texture_t *tex, rgbdata_t *const *co
.height = height,
.depth = depth,
.mips = mipCount,
.layers = num_layers,
.layers = is_cubemap ? 6 : 1,
.format = format,
.tiling = VK_IMAGE_TILING_OPTIMAL,
.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT,
.flags = 0
| ((layers[0]->flags & IMAGE_HAS_ALPHA) ? 0 : kVkImageFlagIgnoreAlpha)
| (cubemap ? kVkImageFlagIsCubemap : 0)
| ((pic->flags & IMAGE_HAS_ALPHA) ? 0 : kVkImageFlagIgnoreAlpha)
| (is_cubemap ? kVkImageFlagIsCubemap : 0)
| (colorspace_hint == kColorspaceGamma ? kVkImageFlagCreateUnormView : 0),
};
@ -522,17 +567,16 @@ static qboolean uploadTexture(int index, vk_texture_t *tex, rgbdata_t *const *co
{
R_VkImageUploadBegin(&tex->vk.image);
for (int layer = 0; layer < num_layers; ++layer) {
const rgbdata_t *const pic = layers[layer];
byte *buf = pic->buffer;
byte *buf = pic->buffer;
const int layers_count = is_cubemap ? 6 : 1;
for (int layer = 0; layer < layers_count; ++layer) {
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 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);
R_VkImageUploadSlice(&tex->vk.image, is_cubemap ? g_remap_cube_layer[layer] : 0, mip, mip_size, buf);
tex->total_size += mip_size;
// Build mip in place for the next mip level
@ -543,6 +587,10 @@ static qboolean uploadTexture(int index, vk_texture_t *tex, rgbdata_t *const *co
buf += mip_size;
}
}
if (compute_mips) {
buf += CalcImageSize(pic->type, width, height, depth);
}
}
R_VkImageUploadEnd(&tex->vk.image);
@ -556,8 +604,8 @@ static qboolean uploadTexture(int index, vk_texture_t *tex, rgbdata_t *const *co
return true;
}
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 );
qboolean R_VkTextureUpload(int index, vk_texture_t *tex, const rgbdata_t *pic, colorspace_hint_e colorspace_hint) {
return uploadTexture( index, tex, pic, colorspace_hint );
}
void R_VkTextureDestroy( int index, vk_texture_t *tex ) {
@ -581,13 +629,12 @@ void R_VkTextureDestroy( int index, vk_texture_t *tex ) {
// TODO tex->vk.descriptor_unorm = VK_NULL_HANDLE;
}
void unloadSkybox( void ) {
void R_VkTexturesSkyboxUnload(void) {
DEBUG("%s", __FUNCTION__);
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;
}
VkDescriptorImageInfo R_VkTexturesGetSkyboxDescriptorImageInfo( void ) {
@ -600,10 +647,12 @@ VkDescriptorImageInfo R_VkTexturesGetSkyboxDescriptorImageInfo( void ) {
};
}
qboolean R_VkTexturesSkyboxUpload( const char *name, rgbdata_t *const sides[6], colorspace_hint_e colorspace_hint, qboolean placeholder) {
qboolean R_VkTexturesSkyboxUpload( const char *name, const rgbdata_t *pic, 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);
dest->flags |= TF_NOMIPMAP;
ASSERT(pic->flags & IMAGE_CUBEMAP);
return uploadTexture(-1, dest, pic, colorspace_hint);
}
VkDescriptorSet R_VkTextureGetDescriptorUnorm( uint index ) {

View File

@ -34,9 +34,10 @@ typedef struct vk_texture_s
qboolean R_VkTexturesInit( void );
void R_VkTexturesShutdown( void );
qboolean R_VkTexturesSkyboxUpload( const char *name, rgbdata_t *const sides[6], colorspace_hint_e colorspace_hint, qboolean placeholder);
qboolean R_VkTexturesSkyboxUpload( const char *name, const rgbdata_t *pic, colorspace_hint_e colorspace_hint, qboolean placeholder);
void R_VkTexturesSkyboxUnload(void);
qboolean R_VkTextureUpload(int index, vk_texture_t *tex, rgbdata_t *const *const layers, int num_layers, colorspace_hint_e colorspace_hint);
qboolean R_VkTextureUpload(int index, vk_texture_t *tex, const rgbdata_t *pic, colorspace_hint_e colorspace_hint);
void R_VkTextureDestroy(int index, vk_texture_t *tex);
VkDescriptorImageInfo R_VkTexturesGetSkyboxDescriptorImageInfo( void );