mirror of
https://git.code.sf.net/p/quake/quakeforge
synced 2024-11-30 08:00:51 +00:00
3a2560e4c1
Sub-models and instance models need an instance data buffer, but this gets the basics working (and the proof of concept). Using arrays like this actually simplified a lot of the code, and will make it easy to get transparency without turbulence (just another queue).
393 lines
11 KiB
C
393 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 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;
|
|
|
|
if (mod->brush.numtextures) {
|
|
load_textures (mod, ctx);
|
|
}
|
|
return;
|
|
}
|
|
|
|
vulktex_t *tex = tx->render;
|
|
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;
|
|
}
|
|
}
|