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:
commit
7e0553d408
|
@ -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 ))
|
||||
{
|
||||
|
|
|
@ -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 )
|
||||
|
|
|
@ -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 }
|
||||
};
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -201,6 +201,7 @@ struct UniformBuffer {
|
|||
float ray_cone_width;
|
||||
uint random_seed;
|
||||
uint frame_counter;
|
||||
float skybox_exposure;
|
||||
|
||||
uint debug_display_only;
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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';
|
||||
}
|
|
@ -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);
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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 ) {
|
||||
|
|
|
@ -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 );
|
||||
|
|
Loading…
Reference in New Issue