mirror of
https://git.code.sf.net/p/quake/quakeforge
synced 2025-03-04 00:11:12 +00:00
Despite the base IQM specification not supporting blend-shapes, I think IQM will become the basis for QF's generic model representation (at least for the more advanced renderers). After my experience with .mu models (KSP) and unity mesh objects (both normal and skinned), and reviewing the IQM spec, it looks like with the addition of support for blend-shapes, IQM is actually pretty good. This is just the preliminary work to get standard IQM models loading in vulkan (seems to work, along with unloading), and they very basics into the renderer (most likely not working: not tested yet). The rest of the renderer seems to be unaffected, though, which is good.
468 lines
14 KiB
C
468 lines
14 KiB
C
/*
|
|
vulkan_model_iqm.c
|
|
|
|
iqm model processing for Vulkan
|
|
|
|
Copyright (C) 2011 Bill Currie <bill@taniwha.org>
|
|
|
|
Author: Bill Currie <bill@taniwha.org>
|
|
Date: 2022/05/03
|
|
|
|
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
|
|
|
|
*/
|
|
#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 <stdlib.h>
|
|
|
|
#include "QF/dstring.h"
|
|
#include "QF/image.h"
|
|
#include "QF/quakefs.h"
|
|
#include "QF/va.h"
|
|
|
|
#include "QF/Vulkan/barrier.h"
|
|
#include "QF/Vulkan/device.h"
|
|
#include "QF/Vulkan/image.h"
|
|
#include "QF/Vulkan/resource.h"
|
|
#include "QF/Vulkan/staging.h"
|
|
#include "QF/Vulkan/qf_iqm.h"
|
|
#include "QF/Vulkan/qf_texture.h"
|
|
|
|
#include "mod_internal.h"
|
|
#include "r_shared.h"
|
|
#include "vid_vulkan.h"
|
|
|
|
static byte null_texture[] = {
|
|
204, 204, 204, 255,
|
|
204, 204, 204, 255,
|
|
204, 204, 204, 255,
|
|
204, 204, 204, 255,
|
|
};
|
|
#if 0
|
|
static byte null_normmap[] = {
|
|
127, 127, 255, 255,
|
|
127, 127, 255, 255,
|
|
127, 127, 255, 255,
|
|
127, 127, 255, 255,
|
|
};
|
|
#endif
|
|
static void
|
|
vulkan_iqm_clear (model_t *mod, void *data)
|
|
{
|
|
vulkan_ctx_t *ctx = data;
|
|
qfv_device_t *device = ctx->device;
|
|
iqm_t *iqm = (iqm_t *) mod->aliashdr;
|
|
qfv_iqm_t *mesh = iqm->extra_data;
|
|
|
|
mod->needload = true;
|
|
|
|
QFV_DestroyResource (device, mesh->bones);
|
|
QFV_DestroyResource (device, mesh->mesh);
|
|
free (mesh);
|
|
Mod_FreeIQM (iqm);
|
|
}
|
|
|
|
static void
|
|
vulkan_iqm_init_image (iqm_t *iqm, int meshnum, qfv_resobj_t *image)
|
|
{
|
|
const char *material = iqm->text + iqm->meshes[meshnum].material;
|
|
dstring_t *str = dstring_new ();
|
|
dstring_copystr (str, material);
|
|
QFS_StripExtension (str->str, str->str);
|
|
|
|
tex_t *tex;
|
|
if ((tex = LoadImage (va (0, "textures/%s", str->str), 0))) {
|
|
*image = (qfv_resobj_t) {
|
|
.name = material,
|
|
.type = qfv_res_image,
|
|
.image = {
|
|
.type = VK_IMAGE_TYPE_2D,
|
|
.format = QFV_ImageFormat (tex->format),
|
|
.extent = {
|
|
.width = tex->width,
|
|
.height = tex->height,
|
|
.depth = 1,
|
|
},
|
|
.num_mipmaps = QFV_MipLevels (tex->width, tex->height),
|
|
.num_layers = 1,
|
|
.samples = VK_SAMPLE_COUNT_1_BIT,
|
|
.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT
|
|
| VK_IMAGE_USAGE_TRANSFER_SRC_BIT
|
|
| VK_IMAGE_USAGE_SAMPLED_BIT,
|
|
},
|
|
};
|
|
} else {
|
|
*image = (qfv_resobj_t) {
|
|
.name = material,
|
|
.type = qfv_res_image,
|
|
.image = {
|
|
.type = VK_IMAGE_TYPE_2D,
|
|
.format = QFV_ImageFormat (tex_rgba),
|
|
.extent = {
|
|
.width = 2,
|
|
.height = 2,
|
|
.depth = 1,
|
|
},
|
|
.num_mipmaps = QFV_MipLevels (2, 2),
|
|
.num_layers = 1,
|
|
.samples = VK_SAMPLE_COUNT_1_BIT,
|
|
.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT
|
|
| VK_IMAGE_USAGE_SAMPLED_BIT,
|
|
},
|
|
};
|
|
}
|
|
dstring_delete (str);
|
|
}
|
|
|
|
static void
|
|
iqm_transfer_texture (tex_t *tex, VkImage image, qfv_stagebuf_t *stage,
|
|
qfv_device_t *device)
|
|
{
|
|
qfv_devfuncs_t *dfunc = device->funcs;
|
|
|
|
if (tex->format != tex_rgb && tex->format != tex_rgba) {
|
|
Sys_Error ("can't transfer iqm image");
|
|
}
|
|
// FIXME correct only for rgb and rgba
|
|
size_t layer_size = tex->width * tex->height * tex->format;
|
|
|
|
qfv_packet_t *packet = QFV_PacketAcquire (stage);
|
|
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);
|
|
memcpy (dst, tex->data, layer_size);
|
|
|
|
int mipLevels = QFV_MipLevels (tex->width, tex->height);
|
|
if (mipLevels == 1) {
|
|
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);
|
|
} else {
|
|
QFV_GenerateMipMaps (device, packet->cmd, image, mipLevels,
|
|
tex->width, tex->height, 1);
|
|
}
|
|
QFV_PacketSubmit (packet);
|
|
}
|
|
|
|
static void
|
|
vulkan_iqm_load_textures (model_t *mod, iqm_t *iqm, qfv_iqm_t *mesh,
|
|
vulkan_ctx_t *ctx)
|
|
{
|
|
qfv_device_t *device = ctx->device;
|
|
dstring_t *str = dstring_new ();
|
|
tex_t *tex;
|
|
size_t buff_size = 0;
|
|
qfv_resobj_t *objects = mesh->mesh->objects;
|
|
|
|
for (int i = 0; i < iqm->num_meshes; i++) {
|
|
int image_ind = 3 + 2 * i;
|
|
VkExtent3D extent = objects[image_ind].image.extent;
|
|
// probably 3 or 4 bytes per pixel FIXME
|
|
buff_size = max (buff_size, extent.width * extent.height * 4);
|
|
}
|
|
|
|
qfv_stagebuf_t *stage = QFV_CreateStagingBuffer (device,
|
|
va (ctx->va_ctx, "iqm:%s",
|
|
mod->name),
|
|
4 * buff_size,
|
|
ctx->cmdpool);
|
|
|
|
for (int i = 0; i < iqm->num_meshes; i++) {
|
|
int image_ind = 3 + 2 * i;
|
|
__auto_type image = &objects[image_ind].image;
|
|
__auto_type view = &objects[image_ind + 1].image_view;
|
|
qfv_iqm_skin_t *skin = &mesh->skins[i];
|
|
*skin = (qfv_iqm_skin_t) {
|
|
.view = view->view,
|
|
.colora = { 255, 255, 255, 255 },
|
|
.colorb = { 255, 255, 255, 255 },
|
|
};
|
|
|
|
dstring_copystr (str, iqm->text + iqm->meshes[i].material);
|
|
QFS_StripExtension (str->str, str->str);
|
|
if (!(tex = LoadImage (va (0, "textures/%s", str->str), 1))) {
|
|
tex_t null_tex = {
|
|
.width = 2,
|
|
.height = 2,
|
|
.format = tex_rgba,
|
|
.data = null_texture,
|
|
};
|
|
tex = &null_tex;
|
|
}
|
|
iqm_transfer_texture (tex, image->image, stage, device);
|
|
}
|
|
dstring_delete (str);
|
|
QFV_DestroyStagingBuffer (stage);
|
|
}
|
|
|
|
static void
|
|
vulkan_iqm_load_arrays (model_t *mod, iqm_t *iqm, qfv_iqm_t *mesh,
|
|
vulkan_ctx_t *ctx)
|
|
{
|
|
qfv_device_t *device = ctx->device;
|
|
qfv_devfuncs_t *dfunc = device->funcs;
|
|
size_t geom_size = iqm->num_verts * sizeof (iqmgvert_t);
|
|
size_t rend_size = iqm->num_verts * sizeof (iqmrvert_t);
|
|
size_t elem_size = iqm->num_elements * sizeof (uint16_t);
|
|
size_t buff_size = geom_size + rend_size + elem_size + 1024;
|
|
qfv_stagebuf_t *stage = QFV_CreateStagingBuffer (device,
|
|
va (ctx->va_ctx, "iqm:%s",
|
|
mod->name),
|
|
buff_size, ctx->cmdpool);
|
|
qfv_packet_t *gpacket = QFV_PacketAcquire (stage);
|
|
iqmgvert_t *gverts = QFV_PacketExtend (gpacket, geom_size);
|
|
qfv_packet_t *rpacket = QFV_PacketAcquire (stage);
|
|
iqmrvert_t *rverts = QFV_PacketExtend (rpacket, rend_size);
|
|
qfv_packet_t *epacket = QFV_PacketAcquire (stage);
|
|
uint16_t *elements = QFV_PacketExtend (epacket, elem_size);
|
|
//FIXME this whole thing is silly, but some person went and interleaved
|
|
//all the vertex data prematurely
|
|
for (int i = 0; i < iqm->num_verts; i++) {
|
|
byte *data = iqm->vertices + i * iqm->stride;
|
|
iqmgvert_t *gv = gverts + i;
|
|
iqmrvert_t *rv = rverts + i;
|
|
for (int j = 0; j < iqm->num_arrays; j++) {
|
|
__auto_type va = &iqm->vertexarrays[j];
|
|
// FIXME assumes standard iqm sizes
|
|
size_t size = 0;
|
|
switch (va->type) {
|
|
case IQM_POSITION:
|
|
size = sizeof (gv->vertex);
|
|
memcpy (gv->vertex, data, size);
|
|
break;
|
|
case IQM_TEXCOORD:
|
|
size = sizeof (rv->uv);
|
|
memcpy (rv->uv, data, size);
|
|
break;
|
|
case IQM_NORMAL:
|
|
size = sizeof (rv->normal);
|
|
memcpy (rv->normal, data, size);
|
|
break;
|
|
case IQM_TANGENT:
|
|
size = sizeof (rv->tangent);
|
|
memcpy (rv->tangent, data, size);
|
|
break;
|
|
case IQM_BLENDINDEXES:
|
|
size = sizeof (gv->bones);
|
|
memcpy (gv->bones, data, size);
|
|
break;
|
|
case IQM_BLENDWEIGHTS:
|
|
size = sizeof (gv->weights);
|
|
memcpy (gv->weights, data, size);
|
|
break;
|
|
case IQM_COLOR:
|
|
size = sizeof (rv->color);
|
|
memcpy (rv->color, data, size);
|
|
break;
|
|
case IQM_CUSTOM:
|
|
// FIXME model loader doesn't handle these, so nothing to do
|
|
break;
|
|
}
|
|
data += size;
|
|
}
|
|
}
|
|
memcpy (elements, iqm->elements, elem_size);
|
|
|
|
qfv_bufferbarrier_t bb[] = {
|
|
bufferBarriers[qfv_BB_Unknown_to_TransferWrite],
|
|
bufferBarriers[qfv_BB_Unknown_to_TransferWrite],
|
|
bufferBarriers[qfv_BB_Unknown_to_TransferWrite],
|
|
};
|
|
bb[0].barrier.buffer = mesh->geom_buffer;
|
|
bb[0].barrier.size = geom_size;
|
|
bb[1].barrier.buffer = mesh->rend_buffer;
|
|
bb[1].barrier.size = rend_size;
|
|
bb[2].barrier.buffer = mesh->index_buffer;
|
|
bb[2].barrier.size = elem_size;
|
|
VkBufferCopy copy_region[] = {
|
|
{ gpacket->offset, 0, geom_size },
|
|
{ rpacket->offset, 0, rend_size },
|
|
{ epacket->offset, 0, elem_size },
|
|
};
|
|
|
|
dfunc->vkCmdPipelineBarrier (gpacket->cmd, bb[0].srcStages, bb[0].dstStages,
|
|
0, 0, 0, 1, &bb[0].barrier, 0, 0);
|
|
dfunc->vkCmdPipelineBarrier (rpacket->cmd, bb[0].srcStages, bb[0].dstStages,
|
|
0, 0, 0, 1, &bb[1].barrier, 0, 0);
|
|
dfunc->vkCmdPipelineBarrier (epacket->cmd, bb[0].srcStages, bb[0].dstStages,
|
|
0, 0, 0, 1, &bb[2].barrier, 0, 0);
|
|
dfunc->vkCmdCopyBuffer (gpacket->cmd, stage->buffer,
|
|
mesh->geom_buffer, 1, ©_region[0]);
|
|
dfunc->vkCmdCopyBuffer (rpacket->cmd, stage->buffer,
|
|
mesh->rend_buffer, 1, ©_region[1]);
|
|
dfunc->vkCmdCopyBuffer (epacket->cmd, stage->buffer,
|
|
mesh->index_buffer, 1, ©_region[2]);
|
|
bb[0] = bufferBarriers[qfv_BB_TransferWrite_to_VertexAttrRead];
|
|
bb[1] = bufferBarriers[qfv_BB_TransferWrite_to_VertexAttrRead];
|
|
bb[2] = bufferBarriers[qfv_BB_TransferWrite_to_VertexAttrRead];
|
|
bb[0].barrier.buffer = mesh->geom_buffer;
|
|
bb[0].barrier.size = geom_size;
|
|
bb[1].barrier.buffer = mesh->rend_buffer;
|
|
bb[1].barrier.size = rend_size;
|
|
bb[2].barrier.buffer = mesh->index_buffer;
|
|
bb[2].barrier.size = elem_size;
|
|
dfunc->vkCmdPipelineBarrier (gpacket->cmd, bb[0].srcStages, bb[0].dstStages,
|
|
0, 0, 0, 1, &bb[0].barrier, 0, 0);
|
|
dfunc->vkCmdPipelineBarrier (rpacket->cmd, bb[0].srcStages, bb[0].dstStages,
|
|
0, 0, 0, 1, &bb[1].barrier, 0, 0);
|
|
dfunc->vkCmdPipelineBarrier (epacket->cmd, bb[0].srcStages, bb[0].dstStages,
|
|
0, 0, 0, 1, &bb[2].barrier, 0, 0);
|
|
QFV_PacketSubmit (gpacket);
|
|
QFV_PacketSubmit (rpacket);
|
|
QFV_PacketSubmit (epacket);
|
|
QFV_DestroyStagingBuffer (stage);
|
|
|
|
vec4f_t *bone_data;
|
|
dfunc->vkMapMemory (device->dev, mesh->bones->memory, 0, VK_WHOLE_SIZE,
|
|
0, (void **)&bone_data);
|
|
for (int i = 0; i < 3 * iqm->num_joints; i++) {
|
|
vec4f_t *bone = bone_data + i * 3;
|
|
bone[0] = (vec4f_t) {1, 0, 0, 0};
|
|
bone[1] = (vec4f_t) {0, 1, 0, 0};
|
|
bone[2] = (vec4f_t) {0, 0, 1, 0};
|
|
}
|
|
VkMappedMemoryRange range = {
|
|
VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE, 0,
|
|
mesh->bones->memory, 0, VK_WHOLE_SIZE,
|
|
};
|
|
dfunc->vkFlushMappedMemoryRanges (device->dev, 1, &range);
|
|
|
|
dfunc->vkUnmapMemory (device->dev, mesh->bones->memory);
|
|
}
|
|
|
|
void
|
|
Vulkan_Mod_IQMFinish (model_t *mod, vulkan_ctx_t *ctx)
|
|
{
|
|
qfv_device_t *device = ctx->device;
|
|
iqm_t *iqm = (iqm_t *) mod->aliashdr;
|
|
mod->clear = vulkan_iqm_clear;
|
|
mod->data = ctx;
|
|
|
|
// FIXME assumes only one texture per mesh (currently the case, but
|
|
// when materials are added...)
|
|
// 2 is for image + image view
|
|
int num_objects = 4 + 2 * iqm->num_meshes;
|
|
qfv_iqm_t *mesh = calloc (1, sizeof (qfv_iqm_t)
|
|
+ 2 * sizeof (qfv_resource_t)
|
|
+ num_objects * sizeof (qfv_resobj_t)
|
|
+ iqm->num_meshes * sizeof (qfv_iqm_skin_t));
|
|
mesh->bones = (qfv_resource_t *) &mesh[1];
|
|
mesh->mesh = &mesh->bones[1];
|
|
|
|
mesh->bones[0] = (qfv_resource_t) {
|
|
.name = mod->name,
|
|
.va_ctx = ctx->va_ctx,
|
|
.memory_properties = VK_MEMORY_PROPERTY_HOST_CACHED_BIT,
|
|
.num_objects = 1,
|
|
.objects = (qfv_resobj_t *) &mesh->bones[2],
|
|
};
|
|
mesh->bones->objects[0] = (qfv_resobj_t) {
|
|
.name = "bones",
|
|
.type = qfv_res_buffer,
|
|
.buffer = {
|
|
.size = 3 * iqm->num_joints * 3 * sizeof (vec4f_t),
|
|
.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT
|
|
| VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
|
|
},
|
|
};
|
|
|
|
mesh->mesh[0] = (qfv_resource_t) {
|
|
.name = "mesh",
|
|
.va_ctx = ctx->va_ctx,
|
|
.memory_properties = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
|
|
.num_objects = num_objects - 1,
|
|
.objects = mesh->bones->objects + 1,
|
|
};
|
|
mesh->mesh->objects[0] = (qfv_resobj_t) {
|
|
.name = "geom",
|
|
.type = qfv_res_buffer,
|
|
.buffer = {
|
|
.size = iqm->num_verts * sizeof (iqmgvert_t),
|
|
.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT
|
|
| VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
|
|
},
|
|
};
|
|
mesh->mesh->objects[1] = (qfv_resobj_t) {
|
|
.name = "rend",
|
|
.type = qfv_res_buffer,
|
|
.buffer = {
|
|
.size = iqm->num_verts * sizeof (iqmrvert_t),
|
|
.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT
|
|
| VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
|
|
},
|
|
};
|
|
mesh->mesh->objects[2] = (qfv_resobj_t) {
|
|
.name = "index",
|
|
.type = qfv_res_buffer,
|
|
.buffer = {
|
|
.size = iqm->num_elements * sizeof (uint16_t),
|
|
.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT
|
|
| VK_BUFFER_USAGE_INDEX_BUFFER_BIT,
|
|
},
|
|
};
|
|
|
|
for (int i = 0; i < iqm->num_meshes; i++) {
|
|
int image_ind = 3 + 2 * i;
|
|
__auto_type image = &mesh->mesh->objects[image_ind];
|
|
vulkan_iqm_init_image (iqm, i, image);
|
|
|
|
mesh->mesh->objects[image_ind + 1] = (qfv_resobj_t) {
|
|
.name = "view",
|
|
.type = qfv_res_image_view,
|
|
.image_view = {
|
|
.image = image_ind,
|
|
.type = VK_IMAGE_VIEW_TYPE_2D,
|
|
.format = mesh->mesh->objects[image_ind].image.format,
|
|
.aspect = VK_IMAGE_ASPECT_COLOR_BIT,
|
|
},
|
|
};
|
|
}
|
|
|
|
mesh->skins = (qfv_iqm_skin_t *) &mesh->bones->objects[num_objects];
|
|
|
|
QFV_CreateResource (device, mesh->mesh);
|
|
QFV_CreateResource (device, mesh->bones);
|
|
mesh->geom_buffer = mesh->mesh->objects[0].buffer.buffer;
|
|
mesh->rend_buffer = mesh->mesh->objects[1].buffer.buffer;
|
|
mesh->index_buffer = mesh->mesh->objects[2].buffer.buffer;
|
|
|
|
vulkan_iqm_load_textures (mod, iqm, mesh, ctx);
|
|
vulkan_iqm_load_arrays (mod, iqm, mesh, ctx);
|
|
|
|
iqm->extra_data = mesh;
|
|
}
|