quakeforge/libs/models/brush/vulkan_model_brush.c
Bill Currie c5c44da727 [vulkan] Implement screenshot
Finally, I can brag about my progress on irc :)
2021-03-24 19:20:53 +09:00

446 lines
13 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/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;
for (int 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->glow) {
dfunc->vkDestroyImage (device->dev, tex->glow->image, 0);
dfunc->vkDestroyImageView (device->dev, tex->glow->view, 0);
}
}
dfunc->vkFreeMemory (device->dev, mctx->texture_memory, 0);
}
static void
transfer_mips (byte *dst, const void *_src, const texture_t *tx, byte *palette)
{
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;
Vulkan_ExpandPalette (dst, src + offset, palette, 2, count);
dst += count * 4;
width >>= 1;
height >>= 1;
}
}
static void
copy_mips (qfv_packet_t *packet, texture_t *tx, qfv_tex_t *tex,
qfv_devfuncs_t *dfunc)
{
// base copy
VkBufferImageCopy copy = {
tex->offset, tx->width, tx->height,
{VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 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_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,
tex->image,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
copies->size, copies->a);
}
static void
load_textures (model_t *mod, vulkan_ctx_t *ctx)
{
qfv_device_t *device = ctx->device;
qfv_devfuncs_t *dfunc = device->funcs;
modelctx_t *mctx = mod->data;
VkImage image = 0;
byte *buffer;
mod_brush_t *brush = &mod->brush;
size_t image_count = 0;
size_t copy_count = 0;
size_t memsize = 0;
for (int i = 0; i < brush->numtextures; i++) {
texture_t *tx = brush->textures[i];
if (!tx) {
continue;
}
vulktex_t *tex = tx->render;
tex->tex->offset = memsize;
memsize += QFV_GetImageSize (device, tex->tex->image);
image_count++;
copy_count += MIPLEVELS;
if (strncmp (tx->name, "sky", 3) == 0) {
copy_count += MIPLEVELS;
}
// just so we have one in the end
image = tex->tex->image;
if (tex->glow) {
copy_count += MIPLEVELS;
tex->glow->offset = memsize;
memsize += QFV_GetImageSize (device, tex->glow->image);
image_count++;
}
}
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);
buffer = QFV_PacketExtend (packet, memsize);
for (int i = 0; i < brush->numtextures; i++) {
texture_t *tx = brush->textures[i];
byte *palette = vid.palette32;
if (!tx) {
continue;
}
vulktex_t *tex = tx->render;
dfunc->vkBindImageMemory (device->dev, tex->tex->image, mem,
tex->tex->offset);
VkImageViewType type = VK_IMAGE_VIEW_TYPE_2D;
if (strncmp (tx->name, "sky", 3) == 0) {
palette = alloca (256 * 4);
memcpy (palette, vid.palette32, 256 * 4);
// sky's black is transparent
// this hits both layers, but so long as the screen is cleared
// to black, no one should notice :)
palette[3] = 0;
type = VK_IMAGE_VIEW_TYPE_2D_ARRAY;
}
tex->tex->view = QFV_CreateImageView (device, tex->tex->image,
type, VK_FORMAT_R8G8B8A8_UNORM,
VK_IMAGE_ASPECT_COLOR_BIT);
QFV_duSetObjectName (device, VK_OBJECT_TYPE_IMAGE_VIEW,
tex->tex->view,
va (ctx->va_ctx, "iview:%s:%s:tex",
mod->name, tx->name));
transfer_mips (buffer + tex->tex->offset, tx + 1, tx, palette);
if (tex->glow) {
dfunc->vkBindImageMemory (device->dev, tex->glow->image, mem,
tex->glow->offset);
// skys are unlit so never have a glow texture thus glow
// textures are always simple 2D
tex->glow->view = QFV_CreateImageView (device, tex->tex->image,
VK_IMAGE_VIEW_TYPE_2D,
VK_FORMAT_R8G8B8A8_UNORM,
VK_IMAGE_ASPECT_COLOR_BIT);
QFV_duSetObjectName (device, VK_OBJECT_TYPE_IMAGE_VIEW,
tex->glow->view,
va (ctx->va_ctx, "iview:%s:%s:glow",
mod->name, tx->name));
transfer_mips (buffer + tex->glow->offset, tex->glow->memory, tx,
palette);
}
}
// base barrier
VkImageMemoryBarrier barrier;
barrier = imageLayoutTransitionBarriers[qfv_LT_Undefined_to_TransferDst];
barrier.subresourceRange.levelCount = VK_REMAINING_MIP_LEVELS;
barrier.subresourceRange.layerCount = VK_REMAINING_ARRAY_LAYERS;
__auto_type barriers = QFV_AllocImageBarrierSet (image_count, malloc);
barriers->size = 0;
for (int i = 0; i < brush->numtextures; i++) {
texture_t *tx = brush->textures[i];
if (!tx) {
continue;
}
vulktex_t *tex = tx->render;
__auto_type b = &barriers->a[barriers->size++];
*b = barrier;
b->image = tex->tex->image;
if (tex->glow) {
b = &barriers->a[barriers->size++];
*b = barrier;
b->image = tex->glow->image;
}
}
qfv_pipelinestagepair_t stages;
stages = imageLayoutTransitionStages[qfv_LT_Undefined_to_TransferDst];
dfunc->vkCmdPipelineBarrier (packet->cmd, stages.src, stages.dst,
0, 0, 0, 0, 0,
barriers->size, barriers->a);
for (int i = 0, j = 0; i < brush->numtextures; i++) {
texture_t *tx = brush->textures[i];
if (!tx) {
continue;
}
vulktex_t *tex = tx->render;
__auto_type b = &barriers->a[j++];
b->oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
b->newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
b->srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
b->dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
copy_mips (packet, tx, tex->tex, dfunc);
if (tex->glow) {
b = &barriers->a[j++];
b->oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
b->newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
b->srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
b->dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
copy_mips (packet, tx, tex->glow, dfunc);
}
}
stages=imageLayoutTransitionStages[qfv_LT_TransferDst_to_ShaderReadOnly];
dfunc->vkCmdPipelineBarrier (packet->cmd, stages.src, stages.dst,
0, 0, 0, 0, 0,
barriers->size, barriers->a);
QFV_PacketSubmit (packet);
QFV_DestroyStagingBuffer (stage);
free (barriers);
}
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 (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 };
int layers = 1;
if (strncmp (tx->name, "sky", 3) == 0) {
layers = 2;
// 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));
if (layers > 1) {
// skys are unlit, so no fullbrights
return;
}
const char *name = va (ctx->va_ctx, "fb_%s", tx->name);
int size = (tx->width * tx->height * 85) / 64;
int fullbright_mark = Hunk_LowMark ();
byte *pixels = Hunk_AllocName (size, name);
if (!Mod_CalcFullbright ((byte *) (tx + 1), pixels, size)) {
Hunk_FreeToLowMark (fullbright_mark);
return;
}
tex->glow = tex->tex + 1;
tex->glow->image = QFV_CreateImage (device, 0, VK_IMAGE_TYPE_2D,
VK_FORMAT_R8G8B8A8_UNORM,
extent, 4, 1,
VK_SAMPLE_COUNT_1_BIT,
VK_IMAGE_USAGE_TRANSFER_DST_BIT
| VK_IMAGE_USAGE_SAMPLED_BIT);
QFV_duSetObjectName (device, VK_OBJECT_TYPE_IMAGE,
tex->glow->image,
va (ctx->va_ctx, "image:%s:%s:glow", mod->name,
tx->name));
// store the pointer to the fullbright data: memory will never be set to
// actual device memory because all of the textures will be loaded in one
// big buffer
tex->glow->memory = (VkDeviceMemory) pixels;
}
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 (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;
}
}