/* vulkan_model_brush.c Vulkan support routines for model loading and caching Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to: Free Software Foundation, Inc. 59 Temple Place - Suite 330 Boston, MA 02111-1307, USA */ // models are the only shared resource between a client and server running // on the same machine. #ifdef HAVE_CONFIG_H # include "config.h" #endif #ifdef HAVE_STRING_H # include #endif #ifdef HAVE_STRINGS_H # include #endif #include "QF/cvar.h" #include "QF/dstring.h" #include "QF/image.h" #include "QF/qendian.h" #include "QF/quakefs.h" #include "QF/sys.h" #include "QF/va.h" #include "QF/Vulkan/qf_bsp.h" #include "QF/Vulkan/qf_model.h" #include "QF/Vulkan/qf_texture.h" #include "QF/Vulkan/barrier.h" #include "QF/Vulkan/command.h" #include "QF/Vulkan/debug.h" #include "QF/Vulkan/device.h" #include "QF/Vulkan/image.h" #include "QF/Vulkan/instance.h" #include "QF/Vulkan/staging.h" #include "qfalloca.h" #include "compat.h" #include "mod_internal.h" #include "r_internal.h" #include "vid_vulkan.h" static vulktex_t vulkan_notexture = { }; static void vulkan_brush_clear (model_t *mod, void *data) { modelctx_t *mctx = data; vulkan_ctx_t *ctx = mctx->ctx; qfv_device_t *device = ctx->device; qfv_devfuncs_t *dfunc = device->funcs; mod_brush_t *brush = &mod->brush; QFV_DeviceWaitIdle (device); for (unsigned i = 0; i < brush->numtextures; i++) { texture_t *tx = brush->textures[i]; if (!tx) { continue; } vulktex_t *tex = tx->render; dfunc->vkDestroyImage (device->dev, tex->tex->image, 0); dfunc->vkDestroyImageView (device->dev, tex->tex->view, 0); if (tex->descriptor) { Vulkan_FreeTexture (ctx, tex->descriptor); tex->descriptor = 0; } } dfunc->vkFreeMemory (device->dev, mctx->texture_memory, 0); } typedef int (*vprocess_t) (byte *, const byte *, size_t); static size_t mipsize (size_t size) { const int n = MIPLEVELS; return size * ((1 << (2 * n)) - 1) / (3 * (1 << (2 * n - 2))); } static void transfer_mips (byte *dst, const void *_src, const texture_t *tx, byte *palette, vprocess_t process) { const byte *src = _src; unsigned width = tx->width; unsigned height = tx->height; unsigned count, offset; for (int i = 0; i < MIPLEVELS; i++) { // mip offsets are relative to the texture pointer rather than the // end of the texture struct offset = tx->offsets[i] - sizeof (texture_t); count = width * height; // use the upper block of the destination as a temporary buffer for // the processed pixels. Vulkan_ExpandPalette works in a linearly // increasing manner thus the processed pixels will be overwritten // only after they have been read byte *tmp = dst + count * 3; process (tmp, src + offset, count); Vulkan_ExpandPalette (dst, tmp, palette, 2, count); dst += count * 4; width >>= 1; height >>= 1; } } static void copy_mips (qfv_packet_t *packet, texture_t *tx, size_t offset, VkImage image, int layer, qfv_devfuncs_t *dfunc) { // base copy VkBufferImageCopy copy = { offset, tx->width, tx->height, {VK_IMAGE_ASPECT_COLOR_BIT, 0, layer, 1}, {0, 0, 0}, {tx->width, tx->height, 1}, }; int is_sky = 0; int sky_offset = 0; size_t size = tx->width * tx->height * 4; int copy_count = MIPLEVELS; if (strncmp (tx->name, "sky", 3) == 0) { if (tx->width == 2 * tx->height) { copy.imageExtent.width /= 2; // sky layers are interleaved on each row sky_offset = tx->width * 4 / 2; } is_sky = 1; copy_count *= 2; } __auto_type copies = QFV_AllocBufferImageCopy (copy_count, alloca); copies->size = 0; for (int i = 0; i < MIPLEVELS; i++) { __auto_type c = &copies->a[copies->size++]; *c = copy; if (is_sky) { __auto_type c = &copies->a[copies->size++]; *c = copy; c->bufferOffset += sky_offset; c->imageSubresource.baseArrayLayer = 1; } copy.bufferOffset += size; size >>= 2; copy.bufferRowLength >>= 1; copy.bufferImageHeight >>= 1; copy.imageExtent.width >>= 1; copy.imageExtent.height >>= 1; copy.imageSubresource.mipLevel++; sky_offset >>= 1; } dfunc->vkCmdCopyBufferToImage (packet->cmd, packet->stage->buffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, copies->size, copies->a); } static void transfer_texture (texture_t *tx, VkImage image, qfv_packet_t *packet, byte *palette, qfv_devfuncs_t *dfunc) { byte *base = packet->stage->data; size_t layer_size = mipsize (tx->width * tx->height * 4); byte *dst = QFV_PacketExtend (packet, layer_size); qfv_imagebarrier_t ib = imageBarriers[qfv_LT_Undefined_to_TransferDst]; ib.barrier.image = image; ib.barrier.subresourceRange.levelCount = VK_REMAINING_MIP_LEVELS; ib.barrier.subresourceRange.layerCount = VK_REMAINING_ARRAY_LAYERS; dfunc->vkCmdPipelineBarrier (packet->cmd, ib.srcStages, ib.dstStages, 0, 0, 0, 0, 0, 1, &ib.barrier); if (strncmp (tx->name, "sky", 3) == 0) { transfer_mips (dst, tx + 1, tx, palette, (vprocess_t) memcpy); copy_mips (packet, tx, dst - base, image, 0, dfunc); } else { transfer_mips (dst, tx + 1, tx, palette, Mod_ClearFullbright); copy_mips (packet, tx, dst - base, image, 0, dfunc); byte *glow = QFV_PacketExtend (packet, layer_size); transfer_mips (glow, tx + 1, tx, palette, Mod_CalcFullbright); copy_mips (packet, tx, glow - base, image, 1, dfunc); } ib = imageBarriers[qfv_LT_TransferDst_to_ShaderReadOnly]; ib.barrier.image = image; ib.barrier.subresourceRange.levelCount = VK_REMAINING_MIP_LEVELS; ib.barrier.subresourceRange.layerCount = VK_REMAINING_ARRAY_LAYERS; dfunc->vkCmdPipelineBarrier (packet->cmd, ib.srcStages, ib.dstStages, 0, 0, 0, 0, 0, 1, &ib.barrier); } static void load_textures (model_t *mod, vulkan_ctx_t *ctx) { qfvPushDebug (ctx, va (ctx->va_ctx, "brush.load_textures: %s", mod->name)); qfv_device_t *device = ctx->device; qfv_devfuncs_t *dfunc = device->funcs; modelctx_t *mctx = mod->data; mod_brush_t *brush = &mod->brush; VkImage image = 0; byte sky_palette[256 * 4]; memcpy (sky_palette, vid.palette32, sizeof (sky_palette)); // sky's black is transparent // this hits both layers, but so long as the screen is cleared // to black, no one should notice :) sky_palette[3] = 0; size_t image_count = 0; size_t memsize = 0; for (unsigned i = 0; i < brush->numtextures; i++) { texture_t *tx = brush->textures[i]; if (!tx) { continue; } vulktex_t *tex = tx->render; memsize += QFV_GetImageSize (device, tex->tex->image); image_count++; // just so we have one in the end image = tex->tex->image; } VkDeviceMemory mem; mem = QFV_AllocImageMemory (device, image, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, memsize, 0); QFV_duSetObjectName (device, VK_OBJECT_TYPE_DEVICE_MEMORY, mem, va (ctx->va_ctx, "memory:%s:texture", mod->name)); mctx->texture_memory = mem; qfv_stagebuf_t *stage = QFV_CreateStagingBuffer (device, va (ctx->va_ctx, "brush:%s", mod->name), memsize, ctx->cmdpool); qfv_packet_t *packet = QFV_PacketAcquire (stage); size_t offset = 0; for (unsigned i = 0; i < brush->numtextures; i++) { texture_t *tx = brush->textures[i]; if (!tx) { continue; } qfv_tex_t *tex = ((vulktex_t *) tx->render)->tex; dfunc->vkBindImageMemory (device->dev, tex->image, mem, offset); offset += QFV_GetImageSize (device, tex->image); VkImageViewType type = VK_IMAGE_VIEW_TYPE_2D_ARRAY; tex->view = QFV_CreateImageView (device, tex->image, type, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_ASPECT_COLOR_BIT); QFV_duSetObjectName (device, VK_OBJECT_TYPE_IMAGE_VIEW, tex->view, va (ctx->va_ctx, "iview:%s:%s:tex", mod->name, tx->name)); byte *palette = vid.palette32; if (strncmp (tx->name, "sky", 3) == 0) { palette = sky_palette; } transfer_texture (tx, tex->image, packet, palette, dfunc); } QFV_PacketSubmit (packet); QFV_DestroyStagingBuffer (stage); qfvPopDebug (ctx); } void Vulkan_Mod_ProcessTexture (model_t *mod, texture_t *tx, vulkan_ctx_t *ctx) { qfv_device_t *device = ctx->device; if (!tx) { modelctx_t *mctx = Hunk_AllocName (0, sizeof (modelctx_t), mod->name); mctx->ctx = ctx; mod->clear = vulkan_brush_clear; mod->data = mctx; r_notexture_mip->render = &vulkan_notexture; load_textures (mod, ctx); return; } vulktex_t *tex = tx->render; tex->texture = tx; tex->tex = (qfv_tex_t *) (tex + 1); VkExtent3D extent = { tx->width, tx->height, 1 }; // Skies are two overlapping layers (one partly transparent), other // textures are split into main color and glow color on separate layers int layers = 2; if (strncmp (tx->name, "sky", 3) == 0) { // the sky texture is normally 2 side-by-side squares, but // some maps have just a single square if (tx->width == 2 * tx->height) { extent.width /= 2; } } tex->tex->image = QFV_CreateImage (device, 0, VK_IMAGE_TYPE_2D, VK_FORMAT_R8G8B8A8_UNORM, extent, 4, layers, VK_SAMPLE_COUNT_1_BIT, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT); QFV_duSetObjectName (device, VK_OBJECT_TYPE_IMAGE, tex->tex->image, va (ctx->va_ctx, "image:%s:%s:tex", mod->name, tx->name)); } void Vulkan_Mod_LoadLighting (model_t *mod, bsp_t *bsp, vulkan_ctx_t *ctx) { mod_brush_t *brush = &mod->brush; mod_lightmap_bytes = 3; if (!bsp->lightdatasize) { brush->lightdata = NULL; return; } byte d; byte *in, *out, *data; size_t i; int ver; QFile *lit_file; brush->lightdata = 0; if (mod_lightmap_bytes > 1) { // LordHavoc: check for a .lit file to load dstring_t *litfilename = dstring_new (); dstring_copystr (litfilename, mod->name); QFS_StripExtension (litfilename->str, litfilename->str); dstring_appendstr (litfilename, ".lit"); lit_file = QFS_VOpenFile (litfilename->str, 0, mod->vpath); data = (byte *) QFS_LoadHunkFile (lit_file); if (data) { if (data[0] == 'Q' && data[1] == 'L' && data[2] == 'I' && data[3] == 'T') { ver = LittleLong (((int32_t *) data)[1]); if (ver == 1) { Sys_MaskPrintf (SYS_dev, "%s loaded", litfilename->str); brush->lightdata = data + 8; } else { Sys_MaskPrintf (SYS_dev, "Unknown .lit file version (%d)\n", ver); } } else { Sys_MaskPrintf (SYS_dev, "Corrupt .lit file (old version?)\n"); } } dstring_delete (litfilename); } if (brush->lightdata || !bsp->lightdatasize) { return; } // LordHavoc: oh well, expand the white lighting data brush->lightdata = Hunk_AllocName (0, bsp->lightdatasize * 3, mod->name); in = bsp->lightdata; out = brush->lightdata; for (i = 0; i < bsp->lightdatasize ; i++) { d = *in++; *out++ = d; *out++ = d; *out++ = d; } }