1 /*
2 Copyright (C) 1996-2001 Id Software, Inc.
3 Copyright (C) 2002-2009 John Fitzgibbons and others
4 Copyright (C) 2007-2008 Kristian Duske
5 Copyright (C) 2010-2014 QuakeSpasm developers
6 Copyright (C) 2016 Axel Gneiting
7
8 This program is free software; you can redistribute it and/or
9 modify it under the terms of the GNU General Public License
10 as published by the Free Software Foundation; either version 2
11 of the License, or (at your option) any later version.
12
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
16
17 See the GNU General Public License for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, write to the Free Software
21 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
22 */
23
24 //gl_texmgr.c -- fitzquake's texture manager. manages texture images
25
26 #include "quakedef.h"
27 #include "gl_heap.h"
28
29 #define STB_IMAGE_RESIZE_IMPLEMENTATION
30 #define STB_IMAGE_RESIZE_STATIC
31 #include "stb_image_resize.h"
32
33 static cvar_t gl_max_size = {"gl_max_size", "0", CVAR_NONE};
34 static cvar_t gl_picmip = {"gl_picmip", "0", CVAR_NONE};
35
36 extern cvar_t vid_filter;
37 extern cvar_t vid_anisotropic;
38
39 #define MAX_MIPS 16
40 static int numgltextures;
41 static gltexture_t *active_gltextures, *free_gltextures;
42 gltexture_t *notexture, *nulltexture, *whitetexture, *greytexture;
43
44 unsigned int d_8to24table[256];
45 unsigned int d_8to24table_fbright[256];
46 unsigned int d_8to24table_fbright_fence[256];
47 unsigned int d_8to24table_nobright[256];
48 unsigned int d_8to24table_nobright_fence[256];
49 unsigned int d_8to24table_conchars[256];
50 unsigned int d_8to24table_shirt[256];
51 unsigned int d_8to24table_pants[256];
52
53 // Heap
54 #define TEXTURE_HEAP_SIZE_MB 32
55
56 static glheap_t ** texmgr_heaps;
57 static int num_texmgr_heaps;
58
59 static byte *image_resize_buffer;
60 static int image_resize_buffer_size;
61
62 /*
63 ================================================================================
64
65 COMMANDS
66
67 ================================================================================
68 */
69
70 /*
71 ===============
72 TexMgr_SetFilterModes
73 ===============
74 */
TexMgr_SetFilterModes(gltexture_t * glt)75 static void TexMgr_SetFilterModes (gltexture_t *glt)
76 {
77 VkDescriptorImageInfo image_info;
78 memset(&image_info, 0, sizeof(image_info));
79 image_info.imageView = glt->image_view;
80 image_info.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
81
82 VkSampler point_sampler = (vid_anisotropic.value == 1) ? vulkan_globals.point_aniso_sampler_lod_bias : vulkan_globals.point_sampler_lod_bias;
83 VkSampler linear_sampler = (vid_anisotropic.value == 1) ? vulkan_globals.linear_aniso_sampler_lod_bias : vulkan_globals.linear_sampler_lod_bias;
84
85 if (glt->flags & TEXPREF_NEAREST)
86 image_info.sampler = point_sampler;
87 else if (glt->flags & TEXPREF_LINEAR)
88 image_info.sampler = linear_sampler;
89 else
90 image_info.sampler = (vid_filter.value == 1) ? point_sampler : linear_sampler;
91
92 VkWriteDescriptorSet texture_write;
93 memset(&texture_write, 0, sizeof(texture_write));
94 texture_write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
95 texture_write.dstSet = glt->descriptor_set;
96 texture_write.dstBinding = 0;
97 texture_write.dstArrayElement = 0;
98 texture_write.descriptorCount = 1;
99 texture_write.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
100 texture_write.pImageInfo = &image_info;
101
102 vkUpdateDescriptorSets(vulkan_globals.device, 1, &texture_write, 0, NULL);
103 }
104
105 /*
106 ===============
107 TexMgr_UpdateTextureDescriptorSets
108 ===============
109 */
TexMgr_UpdateTextureDescriptorSets(void)110 void TexMgr_UpdateTextureDescriptorSets(void)
111 {
112 gltexture_t *glt;
113
114 for (glt = active_gltextures; glt; glt = glt->next)
115 TexMgr_SetFilterModes (glt);
116 }
117
118 /*
119 ===============
120 TexMgr_Imagelist_f -- report loaded textures
121 ===============
122 */
TexMgr_Imagelist_f(void)123 static void TexMgr_Imagelist_f (void)
124 {
125 float mb;
126 float texels = 0;
127 gltexture_t *glt;
128
129 for (glt = active_gltextures; glt; glt = glt->next)
130 {
131 Con_SafePrintf (" %4i x%4i %s\n", glt->width, glt->height, glt->name);
132 if (glt->flags & TEXPREF_MIPMAP)
133 texels += glt->width * glt->height * 4.0f / 3.0f;
134 else
135 texels += (glt->width * glt->height);
136 }
137
138 mb = (texels * 4) / 0x100000;
139 Con_Printf ("%i textures %i pixels %1.1f megabytes\n", numgltextures, (int)texels, mb);
140 }
141
142 /*
143 ================================================================================
144
145 TEXTURE MANAGER
146
147 ================================================================================
148 */
149
150 /*
151 ================
152 TexMgr_FindTexture
153 ================
154 */
TexMgr_FindTexture(qmodel_t * owner,const char * name)155 gltexture_t *TexMgr_FindTexture (qmodel_t *owner, const char *name)
156 {
157 gltexture_t *glt;
158
159 if (name)
160 {
161 for (glt = active_gltextures; glt; glt = glt->next)
162 {
163 if (glt->owner == owner && !strcmp (glt->name, name))
164 return glt;
165 }
166 }
167
168 return NULL;
169 }
170
171 /*
172 ================
173 TexMgr_NewTexture
174 ================
175 */
TexMgr_NewTexture(void)176 gltexture_t *TexMgr_NewTexture (void)
177 {
178 gltexture_t *glt;
179
180 glt = free_gltextures;
181 free_gltextures = glt->next;
182 glt->next = active_gltextures;
183 active_gltextures = glt;
184
185 numgltextures++;
186 return glt;
187 }
188
189 static void GL_DeleteTexture (gltexture_t *texture);
190
191 /*
192 ================
193 TexMgr_FreeTexture
194 ================
195 */
TexMgr_FreeTexture(gltexture_t * kill)196 void TexMgr_FreeTexture (gltexture_t *kill)
197 {
198 gltexture_t *glt;
199
200 if (kill == NULL)
201 {
202 Con_Printf ("TexMgr_FreeTexture: NULL texture\n");
203 return;
204 }
205
206 if (active_gltextures == kill)
207 {
208 active_gltextures = kill->next;
209 kill->next = free_gltextures;
210 free_gltextures = kill;
211
212 GL_DeleteTexture(kill);
213 numgltextures--;
214 return;
215 }
216
217 for (glt = active_gltextures; glt; glt = glt->next)
218 {
219 if (glt->next == kill)
220 {
221 glt->next = kill->next;
222 kill->next = free_gltextures;
223 free_gltextures = kill;
224
225 GL_DeleteTexture(kill);
226 numgltextures--;
227 return;
228 }
229 }
230
231 Con_Printf ("TexMgr_FreeTexture: not found\n");
232 }
233
234 /*
235 ================
236 TexMgr_FreeTextures
237
238 compares each bit in "flags" to the one in glt->flags only if that bit is active in "mask"
239 ================
240 */
TexMgr_FreeTextures(unsigned int flags,unsigned int mask)241 void TexMgr_FreeTextures (unsigned int flags, unsigned int mask)
242 {
243 gltexture_t *glt, *next;
244
245 for (glt = active_gltextures; glt; glt = next)
246 {
247 next = glt->next;
248 if ((glt->flags & mask) == (flags & mask))
249 TexMgr_FreeTexture (glt);
250 }
251 }
252
253 /*
254 ================
255 TexMgr_FreeTexturesForOwner
256 ================
257 */
TexMgr_FreeTexturesForOwner(qmodel_t * owner)258 void TexMgr_FreeTexturesForOwner (qmodel_t *owner)
259 {
260 gltexture_t *glt, *next;
261
262 for (glt = active_gltextures; glt; glt = next)
263 {
264 next = glt->next;
265 if (glt && glt->owner == owner)
266 TexMgr_FreeTexture (glt);
267 }
268 }
269
270 /*
271 ================
272 TexMgr_DeleteTextureObjects
273 ================
274 */
TexMgr_DeleteTextureObjects(void)275 void TexMgr_DeleteTextureObjects (void)
276 {
277 gltexture_t *glt;
278
279 for (glt = active_gltextures; glt; glt = glt->next)
280 {
281 GL_DeleteTexture (glt);
282 }
283 }
284
285 /*
286 ================================================================================
287
288 INIT
289
290 ================================================================================
291 */
292
293 /*
294 =================
295 TexMgr_LoadPalette -- johnfitz -- was VID_SetPalette, moved here, renamed, rewritten
296 =================
297 */
TexMgr_LoadPalette(void)298 void TexMgr_LoadPalette (void)
299 {
300 byte *pal, *src, *dst;
301 int i, mark;
302 FILE *f;
303
304 COM_FOpenFile ("gfx/palette.lmp", &f, NULL);
305 if (!f)
306 Sys_Error ("Couldn't load gfx/palette.lmp");
307
308 mark = Hunk_LowMark ();
309 pal = (byte *) Hunk_Alloc (768);
310 if (fread (pal, 1, 768, f) != 768)
311 Sys_Error ("Couldn't load gfx/palette.lmp");
312 fclose(f);
313
314 //standard palette, 255 is transparent
315 dst = (byte *)d_8to24table;
316 src = pal;
317 for (i = 0; i < 256; i++)
318 {
319 *dst++ = *src++;
320 *dst++ = *src++;
321 *dst++ = *src++;
322 *dst++ = 255;
323 }
324 ((byte *) &d_8to24table[255]) [3] = 0;
325
326 //fullbright palette, 0-223 are black (for additive blending)
327 src = pal + 224*3;
328 dst = (byte *) &d_8to24table_fbright[224];
329 for (i = 224; i < 256; i++)
330 {
331 *dst++ = *src++;
332 *dst++ = *src++;
333 *dst++ = *src++;
334 *dst++ = 255;
335 }
336 for (i = 0; i < 224; i++)
337 {
338 dst = (byte *) &d_8to24table_fbright[i];
339 dst[3] = 255;
340 dst[2] = dst[1] = dst[0] = 0;
341 }
342
343 //nobright palette, 224-255 are black (for additive blending)
344 dst = (byte *)d_8to24table_nobright;
345 src = pal;
346 for (i = 0; i < 256; i++)
347 {
348 *dst++ = *src++;
349 *dst++ = *src++;
350 *dst++ = *src++;
351 *dst++ = 255;
352 }
353 for (i = 224; i < 256; i++)
354 {
355 dst = (byte *) &d_8to24table_nobright[i];
356 dst[3] = 255;
357 dst[2] = dst[1] = dst[0] = 0;
358 }
359
360 //fullbright palette, for fence textures
361 memcpy(d_8to24table_fbright_fence, d_8to24table_fbright, 256*4);
362 d_8to24table_fbright_fence[255] = 0; // Alpha of zero.
363
364 //nobright palette, for fence textures
365 memcpy(d_8to24table_nobright_fence, d_8to24table_nobright, 256*4);
366 d_8to24table_nobright_fence[255] = 0; // Alpha of zero.
367
368 //conchars palette, 0 and 255 are transparent
369 memcpy(d_8to24table_conchars, d_8to24table, 256*4);
370 ((byte *) &d_8to24table_conchars[0]) [3] = 0;
371
372 Hunk_FreeToLowMark (mark);
373 }
374
375 /*
376 ================
377 TexMgr_NewGame
378 ================
379 */
TexMgr_NewGame(void)380 void TexMgr_NewGame (void)
381 {
382 TexMgr_FreeTextures (0, TEXPREF_PERSIST); //deletes all textures where TEXPREF_PERSIST is unset
383 TexMgr_LoadPalette ();
384 }
385
386 /*
387 ================
388 TexMgr_Init
389
390 must be called before any texture loading
391 ================
392 */
TexMgr_Init(void)393 void TexMgr_Init (void)
394 {
395 int i;
396 static byte notexture_data[16] = {159,91,83,255,0,0,0,255,0,0,0,255,159,91,83,255}; //black and pink checker
397 static byte nulltexture_data[16] = {127,191,255,255,0,0,0,255,0,0,0,255,127,191,255,255}; //black and blue checker
398 static byte whitetexture_data[16] = {255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255}; //white
399 static byte greytexture_data[16] = {127,127,127,255,127,127,127,255,127,127,127,255,127,127,127,255}; //50% grey
400 extern texture_t *r_notexture_mip, *r_notexture_mip2;
401
402 // init texture list
403 free_gltextures = (gltexture_t *) Hunk_AllocName (MAX_GLTEXTURES * sizeof(gltexture_t), "gltextures");
404 active_gltextures = NULL;
405 for (i = 0; i < MAX_GLTEXTURES - 1; i++)
406 free_gltextures[i].next = &free_gltextures[i+1];
407 free_gltextures[i].next = NULL;
408 numgltextures = 0;
409
410 // palette
411 TexMgr_LoadPalette ();
412
413 Cvar_RegisterVariable (&gl_max_size);
414 Cvar_RegisterVariable (&gl_picmip);
415 Cmd_AddCommand ("imagelist", &TexMgr_Imagelist_f);
416
417 // load notexture images
418 notexture = TexMgr_LoadImage (NULL, "notexture", 2, 2, SRC_RGBA, notexture_data, "", (src_offset_t)notexture_data, TEXPREF_NEAREST | TEXPREF_PERSIST | TEXPREF_NOPICMIP);
419 nulltexture = TexMgr_LoadImage (NULL, "nulltexture", 2, 2, SRC_RGBA, nulltexture_data, "", (src_offset_t)nulltexture_data, TEXPREF_NEAREST | TEXPREF_PERSIST | TEXPREF_NOPICMIP);
420 whitetexture = TexMgr_LoadImage (NULL, "whitetexture", 2, 2, SRC_RGBA, whitetexture_data, "", (src_offset_t)whitetexture_data, TEXPREF_NEAREST | TEXPREF_PERSIST | TEXPREF_NOPICMIP);
421 greytexture = TexMgr_LoadImage (NULL, "greytexture", 2, 2, SRC_RGBA, greytexture_data, "", (src_offset_t)greytexture_data, TEXPREF_NEAREST | TEXPREF_PERSIST | TEXPREF_NOPICMIP);
422
423 //have to assign these here becuase Mod_Init is called before TexMgr_Init
424 r_notexture_mip->gltexture = r_notexture_mip2->gltexture = notexture;
425 }
426
427 /*
428 ================================================================================
429
430 IMAGE LOADING
431
432 ================================================================================
433 */
434
435 /*
436 ================
437 TexMgr_Downsample
438 ================
439 */
TexMgr_Downsample(unsigned * data,int in_width,int in_height,int out_width,int out_height)440 static unsigned *TexMgr_Downsample (unsigned *data, int in_width, int in_height, int out_width, int out_height)
441 {
442 const int out_size_bytes = out_width * out_height * 4;
443
444 assert((out_width >= 1) && (out_width < in_width));
445 assert((out_height >= 1) && (out_height < in_height));
446
447 if (out_size_bytes > image_resize_buffer_size)
448 image_resize_buffer = realloc(image_resize_buffer, out_size_bytes);
449
450 stbir_resize_uint8((byte*)data, in_width, in_height, 0, (byte*)image_resize_buffer, out_width, out_height, 0, 4);
451 memcpy(data, image_resize_buffer, out_size_bytes);
452
453 return data;
454 }
455
456 /*
457 ===============
458 TexMgr_AlphaEdgeFix
459
460 eliminate pink edges on sprites, etc.
461 operates in place on 32bit data
462 ===============
463 */
TexMgr_AlphaEdgeFix(byte * data,int width,int height)464 static void TexMgr_AlphaEdgeFix (byte *data, int width, int height)
465 {
466 int i, j, n = 0, b, c[3] = {0,0,0},
467 lastrow, thisrow, nextrow,
468 lastpix, thispix, nextpix;
469 byte *dest = data;
470
471 for (i = 0; i < height; i++)
472 {
473 lastrow = width * 4 * ((i == 0) ? height-1 : i-1);
474 thisrow = width * 4 * i;
475 nextrow = width * 4 * ((i == height-1) ? 0 : i+1);
476
477 for (j = 0; j < width; j++, dest += 4)
478 {
479 if (dest[3]) //not transparent
480 continue;
481
482 lastpix = 4 * ((j == 0) ? width-1 : j-1);
483 thispix = 4 * j;
484 nextpix = 4 * ((j == width-1) ? 0 : j+1);
485
486 b = lastrow + lastpix; if (data[b+3]) {c[0] += data[b]; c[1] += data[b+1]; c[2] += data[b+2]; n++;}
487 b = thisrow + lastpix; if (data[b+3]) {c[0] += data[b]; c[1] += data[b+1]; c[2] += data[b+2]; n++;}
488 b = nextrow + lastpix; if (data[b+3]) {c[0] += data[b]; c[1] += data[b+1]; c[2] += data[b+2]; n++;}
489 b = lastrow + thispix; if (data[b+3]) {c[0] += data[b]; c[1] += data[b+1]; c[2] += data[b+2]; n++;}
490 b = nextrow + thispix; if (data[b+3]) {c[0] += data[b]; c[1] += data[b+1]; c[2] += data[b+2]; n++;}
491 b = lastrow + nextpix; if (data[b+3]) {c[0] += data[b]; c[1] += data[b+1]; c[2] += data[b+2]; n++;}
492 b = thisrow + nextpix; if (data[b+3]) {c[0] += data[b]; c[1] += data[b+1]; c[2] += data[b+2]; n++;}
493 b = nextrow + nextpix; if (data[b+3]) {c[0] += data[b]; c[1] += data[b+1]; c[2] += data[b+2]; n++;}
494
495 //average all non-transparent neighbors
496 if (n)
497 {
498 dest[0] = (byte)(c[0]/n);
499 dest[1] = (byte)(c[1]/n);
500 dest[2] = (byte)(c[2]/n);
501
502 n = c[0] = c[1] = c[2] = 0;
503 }
504 }
505 }
506 }
507
508 /*
509 ================
510 TexMgr_8to32
511 ================
512 */
TexMgr_8to32(byte * in,int pixels,unsigned int * usepal)513 static unsigned *TexMgr_8to32 (byte *in, int pixels, unsigned int *usepal)
514 {
515 int i;
516 unsigned *out, *data;
517
518 out = data = (unsigned *) Hunk_Alloc(pixels*4);
519
520 for (i = 0; i < pixels; i++)
521 *out++ = usepal[*in++];
522
523 return data;
524 }
525
526 /*
527 ================
528 TexMgr_DeriveNumMips
529 ================
530 */
TexMgr_DeriveNumMips(int width,int height)531 static int TexMgr_DeriveNumMips(int width, int height)
532 {
533 int num_mips = 0;
534 while(width >= 1 && height >= 1)
535 {
536 width /= 2;
537 height /= 2;
538 num_mips += 1;
539 }
540 return num_mips;
541 }
542
543 /*
544 ================
545 TexMgr_DeriveStagingSize
546 ================
547 */
TexMgr_DeriveStagingSize(int width,int height)548 static int TexMgr_DeriveStagingSize(int width, int height)
549 {
550 int size = 0;
551 while(width >= 1 && height >= 1)
552 {
553 size += width * height * 4;
554 width /= 2;
555 height /= 2;
556 }
557 return size;
558 }
559
TexMgr_PreMultiply32(byte * in,size_t width,size_t height)560 static byte *TexMgr_PreMultiply32(byte *in, size_t width, size_t height)
561 {
562 size_t pixels = width * height;
563 byte *out = (byte *) Hunk_Alloc(pixels*4);
564 byte *result = out;
565 while (pixels --> 0)
566 {
567 out[0] = (in[0]*in[3])>>8;
568 out[1] = (in[1]*in[3])>>8;
569 out[2] = (in[2]*in[3])>>8;
570 out[3] = in[3];
571 in += 4;
572 out += 4;
573 }
574 return result;
575 }
576
577 /*
578 ================
579 TexMgr_LoadImage32 -- handles 32bit source data
580 ================
581 */
TexMgr_LoadImage32(gltexture_t * glt,unsigned * data)582 static void TexMgr_LoadImage32 (gltexture_t *glt, unsigned *data)
583 {
584 GL_DeleteTexture(glt);
585
586 //do this before any rescaling
587 if (glt->flags & TEXPREF_PREMULTIPLY)
588 data = (unsigned*)TexMgr_PreMultiply32((byte*)data, glt->width, glt->height);
589
590 // mipmap down
591 int picmip = (glt->flags & TEXPREF_NOPICMIP) ? 0 : q_max((int)gl_picmip.value, 0);
592 int mipwidth = q_max(glt->width >> picmip, 1);
593 int mipheight = q_max(glt->height >> picmip, 1);
594
595 int maxsize = (int)vulkan_globals.device_properties.limits.maxImageDimension2D;
596 if ((mipwidth > maxsize) || (mipheight > maxsize))
597 {
598 if (mipwidth >= mipheight)
599 {
600 mipheight = q_max((mipheight * maxsize) / mipwidth, 1);
601 mipwidth = maxsize;
602 }
603 else
604 {
605 mipwidth = q_max((mipwidth * maxsize) / mipheight, 1);
606 mipheight = maxsize;
607 }
608 }
609
610 if ((int)glt->width != mipwidth || (int)glt->height != mipheight)
611 {
612 TexMgr_Downsample (data, glt->width, glt->height, mipwidth, mipheight);
613 glt->width = mipwidth;
614 glt->height = mipheight;
615 if (glt->flags & TEXPREF_ALPHA)
616 TexMgr_AlphaEdgeFix ((byte *)data, glt->width, glt->height);
617 }
618 int num_mips = (glt->flags & TEXPREF_MIPMAP) ? TexMgr_DeriveNumMips(glt->width, glt->height) : 1;
619
620 const qboolean warp_image = (glt->flags & TEXPREF_WARPIMAGE);
621 if (warp_image)
622 num_mips = WARPIMAGEMIPS;
623
624 // Check for sanity. This should never be reached.
625 if (num_mips > MAX_MIPS)
626 Sys_Error("Texture has over %d mips", MAX_MIPS);
627
628 VkResult err;
629
630 VkImageCreateInfo image_create_info;
631 memset(&image_create_info, 0, sizeof(image_create_info));
632 image_create_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
633 image_create_info.imageType = VK_IMAGE_TYPE_2D;
634 image_create_info.format = VK_FORMAT_R8G8B8A8_UNORM;
635 image_create_info.extent.width = glt->width;
636 image_create_info.extent.height = glt->height;
637 image_create_info.extent.depth = 1;
638 image_create_info.mipLevels = num_mips;
639 image_create_info.arrayLayers = 1;
640 image_create_info.samples = VK_SAMPLE_COUNT_1_BIT;
641 image_create_info.tiling = VK_IMAGE_TILING_OPTIMAL;
642 image_create_info.usage =
643 warp_image ? (VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_STORAGE_BIT)
644 : (VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT);
645 image_create_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
646 image_create_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
647
648 err = vkCreateImage(vulkan_globals.device, &image_create_info, NULL, &glt->image);
649 if (err != VK_SUCCESS)
650 Sys_Error("vkCreateImage failed");
651 GL_SetObjectName((uint64_t)glt->image, VK_OBJECT_TYPE_IMAGE, va("%s image", glt->name));
652
653 VkMemoryRequirements memory_requirements;
654 vkGetImageMemoryRequirements(vulkan_globals.device, glt->image, &memory_requirements);
655
656 uint32_t memory_type_index = GL_MemoryTypeFromProperties(memory_requirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, 0);
657 VkDeviceSize heap_size = TEXTURE_HEAP_SIZE_MB * (VkDeviceSize)1024 * (VkDeviceSize)1024;
658 VkDeviceSize aligned_offset = GL_AllocateFromHeaps(&num_texmgr_heaps, &texmgr_heaps, heap_size, memory_type_index, VULKAN_MEMORY_TYPE_DEVICE, memory_requirements.size,
659 memory_requirements.alignment, &glt->heap, &glt->heap_node, &num_vulkan_tex_allocations, "Textures Heap");
660 err = vkBindImageMemory(vulkan_globals.device, glt->image, glt->heap->memory.handle, aligned_offset);
661 if (err != VK_SUCCESS)
662 Sys_Error("vkBindImageMemory failed");
663
664 VkImageViewCreateInfo image_view_create_info;
665 memset(&image_view_create_info, 0, sizeof(image_view_create_info));
666 image_view_create_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
667 image_view_create_info.image = glt->image;
668 image_view_create_info.viewType = VK_IMAGE_VIEW_TYPE_2D;
669 image_view_create_info.format = VK_FORMAT_R8G8B8A8_UNORM;
670 image_view_create_info.components.r = VK_COMPONENT_SWIZZLE_R;
671 image_view_create_info.components.g = VK_COMPONENT_SWIZZLE_G;
672 image_view_create_info.components.b = VK_COMPONENT_SWIZZLE_B;
673 image_view_create_info.components.a = VK_COMPONENT_SWIZZLE_A;
674 image_view_create_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
675 image_view_create_info.subresourceRange.baseMipLevel = 0;
676 image_view_create_info.subresourceRange.levelCount = num_mips;
677 image_view_create_info.subresourceRange.baseArrayLayer = 0;
678 image_view_create_info.subresourceRange.layerCount = 1;
679
680 err = vkCreateImageView(vulkan_globals.device, &image_view_create_info, NULL, &glt->image_view);
681 if (err != VK_SUCCESS)
682 Sys_Error("vkCreateImageView failed");
683 GL_SetObjectName((uint64_t)glt->image_view, VK_OBJECT_TYPE_IMAGE_VIEW, va("%s image view", glt->name));
684
685 // Allocate and update descriptor for this texture
686 glt->descriptor_set = R_AllocateDescriptorSet(&vulkan_globals.single_texture_set_layout);
687 GL_SetObjectName((uint64_t)glt->descriptor_set, VK_OBJECT_TYPE_DESCRIPTOR_SET, va("%s desc set", glt->name));
688
689 TexMgr_SetFilterModes (glt);
690
691 // Don't upload data for warp image, will be updated by rendering
692 if (warp_image)
693 {
694 image_view_create_info.subresourceRange.levelCount = 1;
695 err = vkCreateImageView(vulkan_globals.device, &image_view_create_info, NULL, &glt->target_image_view);
696 if (err != VK_SUCCESS)
697 Sys_Error("vkCreateImageView failed");
698 GL_SetObjectName((uint64_t)glt->target_image_view, VK_OBJECT_TYPE_IMAGE_VIEW, va("%s target image view", glt->name));
699
700 VkFramebufferCreateInfo framebuffer_create_info;
701 memset(&framebuffer_create_info, 0, sizeof(framebuffer_create_info));
702 framebuffer_create_info.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
703 framebuffer_create_info.renderPass = vulkan_globals.warp_render_pass;
704 framebuffer_create_info.attachmentCount = 1;
705 framebuffer_create_info.pAttachments = &glt->target_image_view;
706 framebuffer_create_info.width = glt->width;
707 framebuffer_create_info.height = glt->height;
708 framebuffer_create_info.layers = 1;
709 err = vkCreateFramebuffer(vulkan_globals.device, &framebuffer_create_info, NULL, &glt->frame_buffer);
710 if (err != VK_SUCCESS)
711 Sys_Error("vkCreateFramebuffer failed");
712 GL_SetObjectName((uint64_t)glt->frame_buffer, VK_OBJECT_TYPE_FRAMEBUFFER, va("%s framebuffer", glt->name));
713
714 // Allocate and update descriptor for this texture
715 glt->warp_write_descriptor_set = R_AllocateDescriptorSet(&vulkan_globals.single_texture_cs_write_set_layout);
716 GL_SetObjectName((uint64_t)glt->warp_write_descriptor_set, VK_OBJECT_TYPE_DESCRIPTOR_SET, va("%s warp write desc set", glt->name));
717
718 VkDescriptorImageInfo output_image_info;
719 memset(&output_image_info, 0, sizeof(output_image_info));
720 output_image_info.imageView = glt->target_image_view;
721 output_image_info.imageLayout = VK_IMAGE_LAYOUT_GENERAL;
722
723 VkWriteDescriptorSet warp_image_write;
724 memset(&warp_image_write, 0, sizeof(warp_image_write));
725 warp_image_write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
726 warp_image_write.dstBinding = 0;
727 warp_image_write.dstArrayElement = 0;
728 warp_image_write.descriptorCount = 1;
729 warp_image_write.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
730 warp_image_write.dstSet = glt->warp_write_descriptor_set;
731 warp_image_write.pImageInfo = &output_image_info;
732
733 vkUpdateDescriptorSets(vulkan_globals.device, 1, &warp_image_write, 0, NULL);
734
735 return;
736 }
737 else {
738 glt->target_image_view = VK_NULL_HANDLE;
739 glt->warp_write_descriptor_set = VK_NULL_HANDLE;
740 }
741
742 glt->frame_buffer = VK_NULL_HANDLE;
743
744 // Upload
745 VkBufferImageCopy regions[MAX_MIPS];
746 memset(®ions, 0, sizeof(regions));
747
748 int staging_size = (glt->flags & TEXPREF_MIPMAP) ? TexMgr_DeriveStagingSize(mipwidth, mipheight) : (mipwidth * mipheight * 4);
749
750 VkBuffer staging_buffer;
751 VkCommandBuffer command_buffer;
752 int staging_offset;
753 unsigned char * staging_memory = R_StagingAllocate(staging_size, 4, &command_buffer, &staging_buffer, &staging_offset);
754
755 int num_regions = 0;
756 int mip_offset = 0;
757
758 if (glt->flags & TEXPREF_MIPMAP)
759 {
760 mipwidth = glt->width;
761 mipheight = glt->height;
762
763 while (mipwidth >= 1 && mipheight >= 1)
764 {
765 memcpy(staging_memory + mip_offset, data, mipwidth * mipheight * 4);
766 regions[num_regions].bufferOffset = staging_offset + mip_offset;
767 regions[num_regions].imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
768 regions[num_regions].imageSubresource.layerCount = 1;
769 regions[num_regions].imageSubresource.mipLevel = num_regions;
770 regions[num_regions].imageExtent.width = mipwidth;
771 regions[num_regions].imageExtent.height = mipheight;
772 regions[num_regions].imageExtent.depth = 1;
773
774 mip_offset += mipwidth * mipheight * 4;
775 num_regions += 1;
776
777 if (mipwidth > 1 && mipheight > 1)
778 TexMgr_Downsample(data, mipwidth, mipheight, mipwidth / 2, mipheight / 2);
779
780 mipwidth /= 2;
781 mipheight /= 2;
782 }
783 }
784 else
785 {
786 memcpy(staging_memory + mip_offset, data, mipwidth * mipheight * 4);
787 regions[0].bufferOffset = staging_offset + mip_offset;
788 regions[0].imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
789 regions[0].imageSubresource.layerCount = 1;
790 regions[0].imageSubresource.mipLevel = 0;
791 regions[0].imageExtent.width = mipwidth;
792 regions[0].imageExtent.height = mipheight;
793 regions[0].imageExtent.depth = 1;
794 }
795
796 VkImageMemoryBarrier image_memory_barrier;
797 memset(&image_memory_barrier, 0, sizeof(image_memory_barrier));
798 image_memory_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
799 image_memory_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
800 image_memory_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
801 image_memory_barrier.image = glt->image;
802 image_memory_barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
803 image_memory_barrier.subresourceRange.baseMipLevel = 0;
804 image_memory_barrier.subresourceRange.levelCount = num_mips;
805 image_memory_barrier.subresourceRange.baseArrayLayer = 0;
806 image_memory_barrier.subresourceRange.layerCount = 1;
807
808 image_memory_barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
809 image_memory_barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
810 image_memory_barrier.srcAccessMask = 0;
811 image_memory_barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
812 vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, NULL, 0, NULL, 1, &image_memory_barrier);
813
814 vkCmdCopyBufferToImage(command_buffer, staging_buffer, glt->image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, num_mips, regions);
815
816 image_memory_barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
817 image_memory_barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
818 image_memory_barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
819 image_memory_barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
820 vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, NULL, 0, NULL, 1, &image_memory_barrier);
821 }
822
823 /*
824 ================
825 TexMgr_LoadImage8 -- handles 8bit source data, then passes it to LoadImage32
826 ================
827 */
TexMgr_LoadImage8(gltexture_t * glt,byte * data)828 static void TexMgr_LoadImage8 (gltexture_t *glt, byte *data)
829 {
830 GL_DeleteTexture(glt);
831
832 extern cvar_t gl_fullbrights;
833 unsigned int *usepal;
834 int i;
835
836 // HACK HACK HACK -- taken from tomazquake
837 if (strstr(glt->name, "shot1sid") &&
838 glt->width == 32 && glt->height == 32 &&
839 CRC_Block(data, 1024) == 65393)
840 {
841 // This texture in b_shell1.bsp has some of the first 32 pixels painted white.
842 // They are invisible in software, but look really ugly in GL. So we just copy
843 // 32 pixels from the bottom to make it look nice.
844 memcpy (data, data + 32*31, 32);
845 }
846
847 // detect false alpha cases
848 if (glt->flags & TEXPREF_ALPHA && !(glt->flags & TEXPREF_CONCHARS))
849 {
850 for (i = 0; i < (int) (glt->width * glt->height); i++)
851 if (data[i] == 255) //transparent index
852 break;
853 if (i == (int) (glt->width * glt->height))
854 glt->flags -= TEXPREF_ALPHA;
855 }
856
857 // choose palette and padbyte
858 if (glt->flags & TEXPREF_FULLBRIGHT)
859 {
860 if (glt->flags & TEXPREF_ALPHA)
861 usepal = d_8to24table_fbright_fence;
862 else
863 usepal = d_8to24table_fbright;
864 }
865 else if (glt->flags & TEXPREF_NOBRIGHT && gl_fullbrights.value)
866 {
867 if (glt->flags & TEXPREF_ALPHA)
868 usepal = d_8to24table_nobright_fence;
869 else
870 usepal = d_8to24table_nobright;
871 }
872 else if (glt->flags & TEXPREF_CONCHARS)
873 {
874 usepal = d_8to24table_conchars;
875 }
876 else
877 {
878 usepal = d_8to24table;
879 }
880
881 // convert to 32bit
882 data = (byte *)TexMgr_8to32(data, glt->width * glt->height, usepal);
883
884 // fix edges
885 if (glt->flags & TEXPREF_ALPHA)
886 TexMgr_AlphaEdgeFix (data, glt->width, glt->height);
887
888 // upload it
889 TexMgr_LoadImage32 (glt, (unsigned *)data);
890 }
891
892 /*
893 ================
894 TexMgr_LoadLightmap -- handles lightmap data
895 ================
896 */
TexMgr_LoadLightmap(gltexture_t * glt,byte * data)897 static void TexMgr_LoadLightmap (gltexture_t *glt, byte *data)
898 {
899 TexMgr_LoadImage32(glt, (unsigned *)data);
900 }
901
902 /*
903 ================
904 TexMgr_LoadImage -- the one entry point for loading all textures
905 ================
906 */
TexMgr_LoadImage(qmodel_t * owner,const char * name,int width,int height,enum srcformat format,byte * data,const char * source_file,src_offset_t source_offset,unsigned flags)907 gltexture_t *TexMgr_LoadImage (qmodel_t *owner, const char *name, int width, int height, enum srcformat format,
908 byte *data, const char *source_file, src_offset_t source_offset, unsigned flags)
909 {
910 unsigned short crc;
911 gltexture_t *glt;
912 int mark;
913
914 if (isDedicated)
915 return NULL;
916
917 // cache check
918 switch (format)
919 {
920 case SRC_INDEXED:
921 crc = CRC_Block(data, width * height);
922 break;
923 case SRC_LIGHTMAP:
924 crc = CRC_Block(data, width * height * lightmap_bytes);
925 break;
926 case SRC_RGBA:
927 crc = CRC_Block(data, width * height * 4);
928 break;
929 default: /* not reachable but avoids compiler warnings */
930 crc = 0;
931 }
932 if ((flags & TEXPREF_OVERWRITE) && (glt = TexMgr_FindTexture (owner, name)))
933 {
934 if (glt->source_crc == crc)
935 return glt;
936 }
937 else
938 glt = TexMgr_NewTexture ();
939
940 // copy data
941 glt->owner = owner;
942 q_strlcpy (glt->name, name, sizeof(glt->name));
943 glt->width = width;
944 glt->height = height;
945 glt->flags = flags;
946 glt->shirt = -1;
947 glt->pants = -1;
948 q_strlcpy (glt->source_file, source_file, sizeof(glt->source_file));
949 glt->source_offset = source_offset;
950 glt->source_format = format;
951 glt->source_width = width;
952 glt->source_height = height;
953 glt->source_crc = crc;
954
955 //upload it
956 mark = Hunk_LowMark();
957
958 switch (glt->source_format)
959 {
960 case SRC_INDEXED:
961 TexMgr_LoadImage8 (glt, data);
962 break;
963 case SRC_LIGHTMAP:
964 TexMgr_LoadLightmap (glt, data);
965 break;
966 case SRC_RGBA:
967 TexMgr_LoadImage32 (glt, (unsigned *)data);
968 break;
969 }
970
971 Hunk_FreeToLowMark(mark);
972
973 return glt;
974 }
975
976 /*
977 ================================================================================
978
979 COLORMAPPING AND TEXTURE RELOADING
980
981 ================================================================================
982 */
983
984 /*
985 ================
986 TexMgr_ReloadImage -- reloads a texture, and colormaps it if needed
987 ================
988 */
TexMgr_ReloadImage(gltexture_t * glt,int shirt,int pants)989 void TexMgr_ReloadImage (gltexture_t *glt, int shirt, int pants)
990 {
991 byte translation[256];
992 byte *src, *dst, *data = NULL, *translated;
993 int mark, size, i;
994 //
995 // get source data
996 //
997 mark = Hunk_LowMark ();
998
999 if (glt->source_file[0] && glt->source_offset) {
1000 //lump inside file
1001 FILE *f;
1002 COM_FOpenFile(glt->source_file, &f, NULL);
1003 if (!f) goto invalid;
1004 fseek (f, glt->source_offset, SEEK_CUR);
1005 size = glt->source_width * glt->source_height;
1006 /* should be SRC_INDEXED, but no harm being paranoid: */
1007 if (glt->source_format == SRC_RGBA) {
1008 size *= 4;
1009 }
1010 else if (glt->source_format == SRC_LIGHTMAP) {
1011 size *= lightmap_bytes;
1012 }
1013 data = (byte *) Hunk_Alloc (size);
1014 if (fread (data, 1, size, f) != size)
1015 goto invalid;
1016 fclose (f);
1017 }
1018 else if (glt->source_file[0] && !glt->source_offset) {
1019 data = Image_LoadImage (glt->source_file, (int *)&glt->source_width, (int *)&glt->source_height); //simple file
1020 }
1021 else if (!glt->source_file[0] && glt->source_offset) {
1022 data = (byte *) glt->source_offset; //image in memory
1023 }
1024 if (!data) {
1025 invalid: Con_Printf ("TexMgr_ReloadImage: invalid source for %s\n", glt->name);
1026 Hunk_FreeToLowMark(mark);
1027 return;
1028 }
1029
1030 glt->width = glt->source_width;
1031 glt->height = glt->source_height;
1032 //
1033 // apply shirt and pants colors
1034 //
1035 // if shirt and pants are -1,-1, use existing shirt and pants colors
1036 // if existing shirt and pants colors are -1,-1, don't bother colormapping
1037 if (shirt > -1 && pants > -1)
1038 {
1039 if (glt->source_format == SRC_INDEXED)
1040 {
1041 glt->shirt = shirt;
1042 glt->pants = pants;
1043 }
1044 else
1045 Con_Printf ("TexMgr_ReloadImage: can't colormap a non SRC_INDEXED texture: %s\n", glt->name);
1046 }
1047 if (glt->shirt > -1 && glt->pants > -1)
1048 {
1049 //create new translation table
1050 for (i = 0; i < 256; i++)
1051 translation[i] = i;
1052
1053 shirt = glt->shirt * 16;
1054 if (shirt < 128)
1055 {
1056 for (i = 0; i < 16; i++)
1057 translation[TOP_RANGE+i] = shirt + i;
1058 }
1059 else
1060 {
1061 for (i = 0; i < 16; i++)
1062 translation[TOP_RANGE+i] = shirt+15-i;
1063 }
1064
1065 pants = glt->pants * 16;
1066 if (pants < 128)
1067 {
1068 for (i = 0; i < 16; i++)
1069 translation[BOTTOM_RANGE+i] = pants + i;
1070 }
1071 else
1072 {
1073 for (i = 0; i < 16; i++)
1074 translation[BOTTOM_RANGE+i] = pants+15-i;
1075 }
1076
1077 //translate texture
1078 size = glt->width * glt->height;
1079 dst = translated = (byte *) Hunk_Alloc (size);
1080 src = data;
1081
1082 for (i = 0; i < size; i++)
1083 *dst++ = translation[*src++];
1084
1085 data = translated;
1086 }
1087 //
1088 // upload it
1089 //
1090 switch (glt->source_format)
1091 {
1092 case SRC_INDEXED:
1093 TexMgr_LoadImage8 (glt, data);
1094 break;
1095 case SRC_LIGHTMAP:
1096 TexMgr_LoadLightmap (glt, data);
1097 break;
1098 case SRC_RGBA:
1099 TexMgr_LoadImage32 (glt, (unsigned *)data);
1100 break;
1101 }
1102
1103 Hunk_FreeToLowMark(mark);
1104 }
1105
1106 /*
1107 ================
1108 TexMgr_ReloadNobrightImages -- reloads all texture that were loaded with the nobright palette. called when gl_fullbrights changes
1109 ================
1110 */
TexMgr_ReloadNobrightImages(void)1111 void TexMgr_ReloadNobrightImages (void)
1112 {
1113 gltexture_t *glt;
1114
1115 for (glt = active_gltextures; glt; glt = glt->next)
1116 if (glt->flags & TEXPREF_NOBRIGHT)
1117 TexMgr_ReloadImage(glt, -1, -1);
1118 }
1119
1120 /*
1121 ================================================================================
1122
1123 TEXTURE BINDING / TEXTURE UNIT SWITCHING
1124
1125 ================================================================================
1126 */
1127
1128 typedef struct
1129 {
1130 VkImage image;
1131 VkImageView target_image_view;
1132 VkImageView image_view;
1133 VkFramebuffer frame_buffer;
1134 VkDescriptorSet descriptor_set;
1135 VkDescriptorSet warp_write_descriptor_set;
1136 glheap_t * heap;
1137 glheapnode_t * heap_node;
1138 } texture_garbage_t;
1139
1140 static int current_garbage_index;
1141 static int num_garbage_textures[2];
1142 static texture_garbage_t texture_garbage[MAX_GLTEXTURES][2];
1143
1144 /*
1145 ================
1146 TexMgr_CollectGarbage
1147 ================
1148 */
TexMgr_CollectGarbage(void)1149 void TexMgr_CollectGarbage (void)
1150 {
1151 int num;
1152 int i;
1153 texture_garbage_t * garbage;
1154
1155 current_garbage_index = (current_garbage_index + 1) % 2;
1156 num = num_garbage_textures[current_garbage_index];
1157 for (i=0; i<num; ++i)
1158 {
1159 garbage = &texture_garbage[i][current_garbage_index];
1160 if (garbage->frame_buffer != VK_NULL_HANDLE)
1161 vkDestroyFramebuffer(vulkan_globals.device, garbage->frame_buffer, NULL);
1162 if(garbage->target_image_view)
1163 vkDestroyImageView(vulkan_globals.device, garbage->target_image_view, NULL);
1164 vkDestroyImageView(vulkan_globals.device, garbage->image_view, NULL);
1165 vkDestroyImage(vulkan_globals.device, garbage->image, NULL);
1166 R_FreeDescriptorSet(garbage->descriptor_set, &vulkan_globals.single_texture_set_layout);
1167 if (garbage->warp_write_descriptor_set)
1168 R_FreeDescriptorSet(garbage->descriptor_set, &vulkan_globals.single_texture_cs_write_set_layout);
1169
1170 GL_FreeFromHeaps(num_texmgr_heaps, texmgr_heaps, garbage->heap, garbage->heap_node, &num_vulkan_tex_allocations);
1171 }
1172 num_garbage_textures[current_garbage_index] = 0;
1173
1174
1175 }
1176
1177 /*
1178 ================
1179 GL_DeleteTexture
1180 ================
1181 */
GL_DeleteTexture(gltexture_t * texture)1182 static void GL_DeleteTexture (gltexture_t *texture)
1183 {
1184 int garbage_index;
1185 texture_garbage_t * garbage;
1186
1187 if (texture->image_view == VK_NULL_HANDLE)
1188 return;
1189
1190 if (in_update_screen)
1191 {
1192 garbage_index = num_garbage_textures[current_garbage_index]++;
1193 garbage = &texture_garbage[garbage_index][current_garbage_index];
1194 garbage->image = texture->image;
1195 garbage->target_image_view = texture->target_image_view;
1196 garbage->image_view = texture->image_view;
1197 garbage->frame_buffer = texture->frame_buffer;
1198 garbage->descriptor_set = texture->descriptor_set;
1199 garbage->warp_write_descriptor_set = texture->warp_write_descriptor_set;
1200 garbage->heap = texture->heap;
1201 garbage->heap_node = texture->heap_node;
1202 }
1203 else
1204 {
1205 GL_WaitForDeviceIdle();
1206
1207 if (texture->frame_buffer != VK_NULL_HANDLE)
1208 vkDestroyFramebuffer(vulkan_globals.device, texture->frame_buffer, NULL);
1209 if(texture->target_image_view)
1210 vkDestroyImageView(vulkan_globals.device, texture->target_image_view, NULL);
1211 vkDestroyImageView(vulkan_globals.device, texture->image_view, NULL);
1212 vkDestroyImage(vulkan_globals.device, texture->image, NULL);
1213 R_FreeDescriptorSet(texture->descriptor_set, &vulkan_globals.single_texture_set_layout);
1214 if (texture->warp_write_descriptor_set)
1215 R_FreeDescriptorSet(texture->warp_write_descriptor_set, &vulkan_globals.single_texture_cs_write_set_layout);
1216
1217 GL_FreeFromHeaps(num_texmgr_heaps, texmgr_heaps, texture->heap, texture->heap_node, &num_vulkan_tex_allocations);
1218 }
1219
1220 texture->frame_buffer = VK_NULL_HANDLE;
1221 texture->target_image_view = VK_NULL_HANDLE;
1222 texture->image_view = VK_NULL_HANDLE;
1223 texture->image = VK_NULL_HANDLE;
1224 texture->heap = NULL;
1225 texture->heap_node = NULL;
1226 }
1227