mirror of
https://git.code.sf.net/p/quake/quakeforge
synced 2025-02-27 22:21:00 +00:00
BSP textures are now two-layered with the albedo and emission in the two layers rather than two separate images. While this does increase memory usage for the textures themselves (most do not have fullbright pixels), it cuts down on image and image view handles (and shader resources).
391 lines
11 KiB
C
391 lines
11 KiB
C
/*
|
|
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 <string.h>
|
|
#endif
|
|
#ifdef HAVE_STRINGS_H
|
|
# include <strings.h>
|
|
#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);
|
|
}
|
|
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;
|
|
}
|
|
}
|