/* vulkan_model_iqm.c iqm model processing for Vulkan Copyright (C) 2011 Bill Currie Author: Bill Currie 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 #endif #ifdef HAVE_STRINGS_H # include #endif #include #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; for (int i = 0; i < iqm->num_meshes; i++) { Vulkan_IQMRemoveSkin (ctx, &mesh->skins[i]); } Vulkan_IQMRemoveBones (ctx, iqm);//FIXME doesn't belong here (per-instance) 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 dummy_tex; tex_t *tex; if (!(tex = LoadImage (va (0, "textures/%s", str->str), 0))) { dummy_tex = (tex_t) { .width = 2, .height = 2, .format = tex_rgba, }; tex = &dummy_tex; } QFV_ResourceInitTexImage (image, material, 1, tex); 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); VkBufferImageCopy copy = { packet->offset, 0, 0, {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}, {0, 0, 0}, {tex->width, tex->height, 1}, }; dfunc->vkCmdCopyBufferToImage (packet->cmd, packet->stage->buffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ©); 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); Vulkan_IQMAddSkin (ctx, skin); } 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; iqmctx_t *ictx = ctx->iqm_context; 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 (size_t i = 0; i < ictx->frames.size * 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); Vulkan_IQMAddBones (ctx, iqm); //FIXME doesn't belong here (per-instance) } void Vulkan_Mod_IQMFinish (model_t *mod, vulkan_ctx_t *ctx) { qfv_device_t *device = ctx->device; iqmctx_t *ictx = ctx->iqm_context; 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) + ictx->frames.size * sizeof (VkDescriptorSet) + 2 * sizeof (qfv_resource_t) + num_objects * sizeof (qfv_resobj_t) + iqm->num_meshes * sizeof (qfv_iqm_skin_t)); mesh->bones_descriptors = (VkDescriptorSet *) &mesh[1]; mesh->bones = (qfv_resource_t *)&mesh->bones_descriptors[ictx->frames.size]; 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 = ictx->frames.size * iqm->num_joints * 3 * sizeof (vec4f_t), .usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_STORAGE_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; mesh->bones_buffer = mesh->bones->objects[0].buffer.buffer; iqm->extra_data = mesh; vulkan_iqm_load_textures (mod, iqm, mesh, ctx); vulkan_iqm_load_arrays (mod, iqm, mesh, ctx); }