quakeforge/libs/video/renderer/vulkan/vulkan_renderpass.c
Bill Currie 370c36f6cc [vulkan] Fix some memory leaks
And deal with a shutdown order issue causing cvars to crash on shutdown
(due to the hash links being freed too early).
2023-03-05 18:31:30 +09:00

380 lines
11 KiB
C

/*
renderpass.c
Vulkan render pass and frame buffer functions
Copyright (C) 2020 Bill Currie <bill@taniwha.org>
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
#include "QF/cvar.h"
#include "QF/hash.h"
#include "QF/plist.h"
#include "QF/va.h"
#include "QF/Vulkan/command.h"
#include "QF/Vulkan/debug.h"
#include "QF/Vulkan/device.h"
#include "QF/Vulkan/image.h"
#include "QF/Vulkan/qf_renderpass.h"
#include "vid_vulkan.h"
#include "vkparse.h"
static plitem_t *
get_rp_item (vulkan_ctx_t *ctx, qfv_renderpass_t *rp, const char *name)
{
rp->renderpassDef = Vulkan_GetConfig (ctx, rp->name);
plitem_t *item = rp->renderpassDef;
if (!item) {
Sys_Printf ("error loading %s\n", rp->name);
} else if ((item = PL_ObjectForKey (item, name))) {
Sys_MaskPrintf (SYS_vulkan_parse, "Found %s def\n", name);
}
return item;
}
static size_t
get_image_size (VkImage image, qfv_device_t *device)
{
qfv_devfuncs_t *dfunc = device->funcs;
size_t size;
size_t align;
VkMemoryRequirements requirements;
dfunc->vkGetImageMemoryRequirements (device->dev, image, &requirements);
size = requirements.size;
align = requirements.alignment - 1;
size = (size + align) & ~(align);
return size;
}
static void
destroy_framebuffers (vulkan_ctx_t *ctx, qfv_renderpass_t *rp)
{
qfv_device_t *device = ctx->device;
qfv_devfuncs_t *dfunc = device->funcs;
for (size_t i = 0; i < rp->framebuffers->size; i++) {
dfunc->vkDestroyFramebuffer (device->dev, rp->framebuffers->a[i], 0);
}
free (rp->framebuffers);
rp->framebuffers = 0;
}
void
QFV_RenderPass_CreateAttachments (qfv_renderpass_t *renderpass)
{
vulkan_ctx_t *ctx = renderpass->vulkan_ctx;
qfv_device_t *device = ctx->device;
qfv_devfuncs_t *dfunc = device->funcs;
__auto_type rp = renderpass;
if (rp->output.image) {
// if output has an image, then the view is owned by the renderpass
dfunc->vkDestroyImageView (device->dev, rp->output.view, 0);
dfunc->vkDestroyImage (device->dev, rp->output.image, 0);
free (rp->output.view_list);
rp->output.view_list = 0;
rp->output.view = 0;
rp->output.image = 0;
}
if (rp->attachment_views) {
for (size_t i = 0; i < rp->attachment_views->size; i++) {
dfunc->vkDestroyImageView (device->dev,
rp->attachment_views->a[i], 0);
}
free (rp->attachment_views);
rp->attachment_views = 0;
}
if (rp->attachment_images) {
for (size_t i = 0; i < rp->attachment_images->size; i++) {
dfunc->vkDestroyImage (device->dev, rp->attachment_images->a[i], 0);
}
free (rp->attachment_images);
rp->attachment_images = 0;
}
plitem_t *output_def = get_rp_item (ctx, rp, "output");
plitem_t *images_def = get_rp_item (ctx, rp, "images");
plitem_t *views_def = get_rp_item (ctx, rp, "imageViews");
plitem_t *rp_def = rp->renderpassDef;
size_t memSize = 0;
VkImage ref_image = 0;
if (output_def) {
// QFV_ParseOutput clears the structure, but extent and frames need to
// be preserved
qfv_output_t output = rp->output;
QFV_ParseOutput (ctx, &output, output_def, rp_def);
rp->output.format = output.format;
rp->output.finalLayout = output.finalLayout;
plitem_t *image = PL_ObjectForKey (output_def, "image");
Vulkan_Script_SetOutput (ctx, &rp->output);
rp->output.image = QFV_ParseImage (ctx, image, rp_def);
memSize += get_image_size (rp->output.image, device);
ref_image = rp->output.image;
}
if (images_def) {
__auto_type images = QFV_ParseImageSet (ctx, images_def, rp_def);
rp->attachment_images = images;
ref_image = images->a[0];
for (size_t i = 0; i < images->size; i++) {
memSize += get_image_size (images->a[i], device);
}
}
VkDeviceMemory mem = rp->attachmentMemory;
if (memSize > rp->attachmentMemory_size) {
if (rp->attachmentMemory) {
dfunc->vkFreeMemory (device->dev, rp->attachmentMemory, 0);
}
rp->attachmentMemory_size = memSize;
mem = QFV_AllocImageMemory (device, ref_image,
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
memSize, 0);
rp->attachmentMemory = mem;
QFV_duSetObjectName (device, VK_OBJECT_TYPE_DEVICE_MEMORY,
mem, "memory:framebuffers");
}
size_t offset = 0;
if (rp->output.image) {
QFV_BindImageMemory (device, rp->output.image, mem, offset);
offset += get_image_size (rp->output.image, device);
plitem_t *view = PL_ObjectForKey (output_def, "view");
Vulkan_Script_SetOutput (ctx, &rp->output);
rp->output.view = QFV_ParseImageView (ctx, view, rp_def);
rp->output.view_list = malloc (rp->output.frames
* sizeof (VkImageView));
QFV_duSetObjectName (device, VK_OBJECT_TYPE_IMAGE,
rp->output.image,
va (ctx->va_ctx, "image:%s:output", rp->name));
QFV_duSetObjectName (ctx->device, VK_OBJECT_TYPE_IMAGE_VIEW,
rp->output.view,
va (ctx->va_ctx, "iview:%s:output", rp->name));
for (uint32_t i = 0; i < rp->output.frames; i++) {
rp->output.view_list[i] = rp->output.view;
}
}
if (rp->attachment_images) {
__auto_type images = rp->attachment_images;
for (size_t i = 0; i < images->size; i++) {
QFV_BindImageMemory (device, images->a[i], mem, offset);
offset += get_image_size (images->a[i], device);
}
}
if (views_def) {
__auto_type views = QFV_ParseImageViewSet (ctx, views_def, rp_def);
rp->attachment_views = views;
}
}
void
QFV_RenderPass_CreateRenderPass (qfv_renderpass_t *renderpass)
{
vulkan_ctx_t *ctx = renderpass->vulkan_ctx;
__auto_type rp = renderpass;
plitem_t *rp_cfg = get_rp_item (ctx, rp, "renderpass");
if (rp_cfg) {
hashtab_t *tab = ctx->script_context->renderpasses;
const char *path;
path = va (ctx->va_ctx, "$"QFV_PROPERTIES".%s", rp->name);
__auto_type renderpass = (VkRenderPass) QFV_GetHandle (tab, path);
if (renderpass) {
rp->renderpass = renderpass;
} else {
Vulkan_Script_SetOutput (ctx, &rp->output);
rp->renderpass = QFV_ParseRenderPass (ctx, rp_cfg,
rp->renderpassDef);
QFV_AddHandle (tab, path, (uint64_t) rp->renderpass);
QFV_duSetObjectName (ctx->device, VK_OBJECT_TYPE_RENDER_PASS,
rp->renderpass, va (ctx->va_ctx,
"renderpass:%s",
rp->name));
}
rp->subpassCount = PL_A_NumObjects (PL_ObjectForKey (rp_cfg,
"subpasses"));
}
plitem_t *item = get_rp_item (ctx, rp, "clearValues");
rp->clearValues = QFV_ParseClearValues (ctx, item, rp->renderpassDef);
}
void
QFV_RenderPass_CreateFramebuffer (qfv_renderpass_t *renderpass)
{
vulkan_ctx_t *ctx = renderpass->vulkan_ctx;
__auto_type rp = renderpass;
if (renderpass->framebuffers) {
destroy_framebuffers (ctx, renderpass);
}
plitem_t *fb_def = get_rp_item (ctx, rp, "framebuffer");
plitem_t *rp_def = rp->renderpassDef;
if (fb_def) {
rp->framebuffers = QFV_AllocFrameBuffers (rp->output.frames, malloc);
for (size_t i = 0; i < rp->framebuffers->size; i++) {
rp->output.view = rp->output.view_list[i];
Vulkan_Script_SetOutput (ctx, &rp->output);
rp->framebuffers->a[i] = QFV_ParseFramebuffer (ctx, fb_def, rp_def);
}
}
int width = rp->output.extent.width;
int height = rp->output.extent.height;
rp->viewport = (VkViewport) { 0, 0, width, height, 0, 1 };
rp->scissor = (VkRect2D) { {0, 0}, {width, height} };
rp->renderArea = (VkRect2D) { {0, 0}, {width, height} };
}
static void
init_renderframe (vulkan_ctx_t *ctx, qfv_renderpass_t *rp,
qfv_renderframe_t *rFrame)
{
rFrame->vulkan_ctx = ctx;
rFrame->renderpass = rp;
rFrame->subpassContents = VK_SUBPASS_CONTENTS_SECONDARY_COMMAND_BUFFERS;
rFrame->framebuffer = 0;
rFrame->subpassCount = rp->subpassCount;
rFrame->subpassInfo = 0;
if (rp->subpass_info) {
rFrame->subpassInfo = rp->subpass_info->a;
}
rFrame->subpassCmdSets = malloc (rp->subpassCount
* sizeof (qfv_cmdbufferset_t));
for (size_t j = 0; j < rp->subpassCount; j++) {
DARRAY_INIT (&rFrame->subpassCmdSets[j], 4);
}
}
static void
destroy_attachments (vulkan_ctx_t *ctx, qfv_renderpass_t *rp)
{
qfv_device_t *device = ctx->device;
qfv_devfuncs_t *dfunc = device->funcs;
if (rp->output.image) {
// if output has an image, then the view is owned by the renderpass
dfunc->vkDestroyImageView (device->dev, rp->output.view, 0);
dfunc->vkDestroyImage (device->dev, rp->output.image, 0);
free (rp->output.view_list);
rp->output.view_list = 0;
rp->output.view = 0;
rp->output.image = 0;
}
if (rp->attachment_views) {
for (size_t i = 0; i < rp->attachment_views->size; i++) {
dfunc->vkDestroyImageView (device->dev,
rp->attachment_views->a[i], 0);
}
}
if (rp->attachment_images) {
for (size_t i = 0; i < rp->attachment_images->size; i++) {
dfunc->vkDestroyImage (device->dev, rp->attachment_images->a[i], 0);
}
}
dfunc->vkFreeMemory (device->dev, rp->attachmentMemory, 0);
free (rp->attachment_images);
free (rp->attachment_views);
}
static void
destroy_renderframes (vulkan_ctx_t *ctx, qfv_renderpass_t *rp)
{
for (size_t i = 0; i < rp->frames.size; i++) {
__auto_type rFrame = &rp->frames.a[i];
for (int j = 0; j < rFrame->subpassCount; j++) {
DARRAY_CLEAR (&rFrame->subpassCmdSets[j]);
}
free (rFrame->subpassCmdSets);
}
}
qfv_renderpass_t *
QFV_RenderPass_New (vulkan_ctx_t *ctx, const char *name, qfv_draw_t function)
{
qfv_renderpass_t *rp = calloc (1, sizeof (qfv_renderpass_t));
rp->vulkan_ctx = ctx;
rp->name = name;
rp->draw = function;
rp->renderpassDef = Vulkan_GetConfig (ctx, rp->name);
plitem_t *rp_info = get_rp_item (ctx, rp, "info");
if (rp_info) {
plitem_t *subpass_info = PL_ObjectForKey (rp_info, "subpass_info");
if (subpass_info) {
rp->subpass_info = QFV_ParseSubpasses (ctx, subpass_info,
rp->renderpassDef);
if (rp->subpass_info->size < rp->subpassCount) {
Sys_Printf ("warning:%s:%d: insufficient entries in "
"subpass_info\n", rp->name, PL_Line (subpass_info));
}
if (!rp->subpassCount) {
rp->subpassCount = rp->subpass_info->size;
}
}
plitem_t *color = PL_ObjectForKey (rp_info, "color");
if (color) {
QFV_ParseRGBA (ctx, (float *)&rp->color, color, rp->renderpassDef);
}
}
if (!rp->subpassCount) {
rp->subpassCount = 1;
}
DARRAY_INIT (&rp->frames, 4);
DARRAY_RESIZE (&rp->frames, ctx->frames.size);
for (size_t i = 0; i < rp->frames.size; i++) {
init_renderframe (ctx, rp, &rp->frames.a[i]);
}
return rp;
}
void
QFV_RenderPass_Delete (qfv_renderpass_t *renderpass)
{
vulkan_ctx_t *ctx = renderpass->vulkan_ctx;
qfv_device_t *device = ctx->device;
qfv_devfuncs_t *dfunc = device->funcs;
destroy_attachments (ctx, renderpass);
dfunc->vkDestroyRenderPass (device->dev, renderpass->renderpass, 0);
destroy_renderframes (ctx, renderpass);
if (renderpass->framebuffers) {
destroy_framebuffers (ctx, renderpass);
}
DARRAY_CLEAR (&renderpass->frames);
free (renderpass->clearValues);
free (renderpass);
}