quakeforge/libs/video/renderer/vulkan/vulkan_particles.c
Bill Currie 9b609469ed [vulkan] Prevent particle update buffers escaping the staging buffer
The escape was actually harmless as the buffers would not be read due to
the particle count being 0 (thus why the buffers were at the end of the
staging buffer: no space was allocated for them, only for the system
buffer, but their offsets were just past the system buffer). However,
the validation layers quite rightly did not like that. Thus, the two
buffers are pointed to the system buffer so all three descriptors are
always valid.
2022-12-02 12:46:45 +09:00

526 lines
17 KiB
C

/*
vulkan_particles.c
Quake specific Vulkan particle manager
Copyright (C) 2021 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
#ifdef HAVE_MATH_H
# include <math.h>
#endif
#ifdef HAVE_STRING_H
# include <string.h>
#endif
#ifdef HAVE_STRINGS_H
# include <strings.h>
#endif
#include "QF/cvar.h"
#include "QF/render.h"
#include "QF/va.h"
#include "QF/Vulkan/buffer.h"
#include "QF/Vulkan/debug.h"
#include "QF/Vulkan/descriptor.h"
#include "QF/Vulkan/device.h"
#include "QF/Vulkan/instance.h"
#include "QF/Vulkan/resource.h"
#include "QF/Vulkan/staging.h"
#include "QF/Vulkan/qf_matrices.h"
#include "QF/Vulkan/qf_palette.h"
#include "QF/Vulkan/qf_particles.h"
#include "QF/Vulkan/qf_renderpass.h"
#include "QF/Vulkan/qf_translucent.h"
#include "r_internal.h"
#include "vid_vulkan.h"
//FIXME make dynamic
#define MaxParticles 2048
typedef struct {
vec4f_t gravity;
float dT;
} particle_push_constants_t;
static const char * __attribute__((used)) particle_pass_names[] = {
"draw",
};
static void
particle_begin_subpass (VkPipeline pipeline, qfv_renderframe_t *rFrame)
{
vulkan_ctx_t *ctx = rFrame->vulkan_ctx;
qfv_device_t *device = ctx->device;
qfv_devfuncs_t *dfunc = device->funcs;
particlectx_t *pctx = ctx->particle_context;
uint32_t curFrame = ctx->curFrame;
particleframe_t *pframe = &pctx->frames.a[curFrame];
VkCommandBuffer cmd = pframe->cmdSet.a[0];
dfunc->vkResetCommandBuffer (cmd, 0);
VkCommandBufferInheritanceInfo inherit = {
VK_STRUCTURE_TYPE_COMMAND_BUFFER_INHERITANCE_INFO, 0,
rFrame->renderpass->renderpass, QFV_passTranslucentFrag,
rFrame->framebuffer,
0, 0, 0,
};
VkCommandBufferBeginInfo beginInfo = {
VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, 0,
VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT
| VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT, &inherit,
};
dfunc->vkBeginCommandBuffer (cmd, &beginInfo);
QFV_duCmdBeginLabel (device, cmd, va (ctx->va_ctx, "particles:%s", "draw"),
{ 0.6, 0.5, 0, 1});
dfunc->vkCmdBindPipeline (cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
VkDescriptorSet sets[] = {
Vulkan_Matrix_Descriptors (ctx, ctx->curFrame),
Vulkan_Palette_Descriptor (ctx),
Vulkan_Translucent_Descriptors (ctx, ctx->curFrame),
};
dfunc->vkCmdBindDescriptorSets (cmd, VK_PIPELINE_BIND_POINT_GRAPHICS,
pctx->draw_layout, 0, 3, sets, 0, 0);
dfunc->vkCmdSetViewport (cmd, 0, 1, &rFrame->renderpass->viewport);
dfunc->vkCmdSetScissor (cmd, 0, 1, &rFrame->renderpass->scissor);
}
static void
particle_end_subpass (VkCommandBuffer cmd, vulkan_ctx_t *ctx)
{
qfv_device_t *device = ctx->device;
qfv_devfuncs_t *dfunc = device->funcs;
QFV_duCmdEndLabel (device, cmd);
dfunc->vkEndCommandBuffer (cmd);
}
void
Vulkan_DrawParticles (qfv_renderframe_t *rFrame)
{
vulkan_ctx_t *ctx = rFrame->vulkan_ctx;
qfv_device_t *device = ctx->device;
qfv_devfuncs_t *dfunc = device->funcs;
particlectx_t *pctx = ctx->particle_context;
uint32_t curFrame = ctx->curFrame;
particleframe_t *pframe = &pctx->frames.a[curFrame];
VkCommandBuffer cmd = pframe->cmdSet.a[0];
DARRAY_APPEND (&rFrame->subpassCmdSets[QFV_passTranslucentFrag],
pframe->cmdSet.a[0]);
particle_begin_subpass (pctx->draw, rFrame);
/*
VkBufferMemoryBarrier barrier[] = {
{ VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER, 0,
VK_ACCESS_SHADER_READ_BIT
| VK_ACCESS_SHADER_WRITE_BIT,
VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT,
0, 0,
pframe->states, 0, VK_WHOLE_SIZE },
{ VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER, 0,
VK_ACCESS_SHADER_READ_BIT
| VK_ACCESS_SHADER_WRITE_BIT,
VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT,
0, 0,
pframe->params, 0, VK_WHOLE_SIZE },
{ VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER, 0,
VK_ACCESS_SHADER_READ_BIT
| VK_ACCESS_SHADER_WRITE_BIT,
VK_ACCESS_INDIRECT_COMMAND_READ_BIT,
0, 0,
pframe->system, 0, VK_WHOLE_SIZE },
};
dfunc->vkCmdWaitEvents (cmd, 1, &pframe->physicsEvent,
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
VK_PIPELINE_STAGE_DRAW_INDIRECT_BIT
| VK_PIPELINE_STAGE_VERTEX_INPUT_BIT,
0, 0,
3, barrier,
0, 0);
*/
mat4f_t mat;
mat4fidentity (mat);
qfv_push_constants_t push_constants[] = {
{ VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof (mat4f_t), &mat },
};
QFV_PushConstants (device, cmd, pctx->draw_layout, 1, push_constants);
VkDeviceSize offsets[] = { 0 };
VkBuffer buffers[] = {
pframe->states,
};
dfunc->vkCmdBindVertexBuffers (cmd, 0, 1, buffers, offsets);
dfunc->vkCmdDrawIndirect (cmd, pframe->system, 0, 1,
sizeof (qfv_particle_system_t));
particle_end_subpass (cmd, ctx);
}
static void
create_buffers (vulkan_ctx_t *ctx)
{
qfv_device_t *device = ctx->device;
qfv_devfuncs_t *dfunc = device->funcs;
particlectx_t *pctx = ctx->particle_context;
size_t mp = MaxParticles;
size_t frames = ctx->frames.size;
pctx->resources = malloc (sizeof (qfv_resource_t)
// states buffer
+ frames * sizeof (qfv_resobj_t)
// params buffer
+ frames * sizeof (qfv_resobj_t)
// system buffer
+ frames * sizeof (qfv_resobj_t));
__auto_type state_objs = (qfv_resobj_t *) &pctx->resources[1];
__auto_type param_objs = &state_objs[frames];
__auto_type system_objs = &param_objs[frames];
pctx->resources[0] = (qfv_resource_t) {
.name = "particles",
.va_ctx = ctx->va_ctx,
.memory_properties = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
.num_objects = 3 * frames,
.objects = state_objs,
};
for (size_t i = 0; i < frames; i++) {
state_objs[i] = (qfv_resobj_t) {
.name = "states",
.type = qfv_res_buffer,
.buffer = {
.size = mp * sizeof (qfv_particle_t),
.usage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT
| VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
},
};
param_objs[i] = (qfv_resobj_t) {
.name = "params",
.type = qfv_res_buffer,
.buffer = {
.size = mp * sizeof (qfv_parameters_t),
.usage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
},
};
system_objs[i] = (qfv_resobj_t) {
.name = "system",
.type = qfv_res_buffer,
.buffer = {
.size = sizeof (qfv_particle_system_t),
.usage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT
| VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT,
},
};
}
QFV_CreateResource (device, pctx->resources);
size_t stageSize = (pctx->resources->size / frames)*(frames + 1);
pctx->stage = QFV_CreateStagingBuffer (device, "particles", stageSize,
ctx->cmdpool);
for (size_t i = 0; i < frames; i++) {
__auto_type pframe = &pctx->frames.a[i];
pframe->states = state_objs[i].buffer.buffer;
pframe->params = param_objs[i].buffer.buffer;
pframe->system = system_objs[i].buffer.buffer;
}
for (size_t i = 0; i < frames; i++) {
__auto_type curr = &pctx->frames.a[i];
__auto_type prev = &pctx->frames.a[(i + frames - 1) % frames];
VkDescriptorBufferInfo bufferInfo[] = {
{ curr->states, 0, VK_WHOLE_SIZE },
{ curr->params, 0, VK_WHOLE_SIZE },
{ curr->system, 0, VK_WHOLE_SIZE },
{ prev->states, 0, VK_WHOLE_SIZE },
{ prev->params, 0, VK_WHOLE_SIZE },
{ prev->system, 0, VK_WHOLE_SIZE },
};
VkWriteDescriptorSet write[] = {
{ VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, 0,
curr->curDescriptors, 0, 0, 3,
VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
.pBufferInfo = bufferInfo + 0
},
{ VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, 0,
curr->inDescriptors, 0, 0, 3,
VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
.pBufferInfo = bufferInfo + 3
},
};
dfunc->vkUpdateDescriptorSets (device->dev, 2, write, 0, 0);
}
}
void
Vulkan_Particles_Init (vulkan_ctx_t *ctx)
{
qfv_device_t *device = ctx->device;
qfv_devfuncs_t *dfunc = device->funcs;
qfvPushDebug (ctx, "particles init");
particlectx_t *pctx = calloc (1, sizeof (particlectx_t));
ctx->particle_context = pctx;
pctx->psystem = &r_psystem;
size_t frames = ctx->frames.size;
DARRAY_INIT (&pctx->frames, frames);
DARRAY_RESIZE (&pctx->frames, frames);
pctx->frames.grow = 0;
pctx->physics = Vulkan_CreateComputePipeline (ctx, "partphysics");
pctx->update = Vulkan_CreateComputePipeline (ctx, "partupdate");
pctx->draw = Vulkan_CreateGraphicsPipeline (ctx, "partdraw");
pctx->physics_layout = Vulkan_CreatePipelineLayout (ctx,
"partphysics_layout");
pctx->update_layout = Vulkan_CreatePipelineLayout (ctx,
"partupdate_layout");
pctx->draw_layout = Vulkan_CreatePipelineLayout (ctx, "partdraw_layout");
pctx->pool = Vulkan_CreateDescriptorPool (ctx, "particle_pool");
pctx->setLayout = Vulkan_CreateDescriptorSetLayout (ctx, "particle_set");
__auto_type layouts = QFV_AllocDescriptorSetLayoutSet (3 * frames, alloca);
for (size_t i = 0; i < layouts->size; i++) {
layouts->a[i] = pctx->setLayout;
}
__auto_type sets = QFV_AllocateDescriptorSet (device, pctx->pool, layouts);
for (size_t i = 0; i < frames; i++) {
__auto_type pframe = &pctx->frames.a[i];
pframe->curDescriptors = sets->a[i * 3 + 0];
pframe->inDescriptors = sets->a[i * 3 + 1];
pframe->newDescriptors = sets->a[i * 3 + 2];
DARRAY_INIT (&pframe->cmdSet, QFV_particleNumPasses);
DARRAY_RESIZE (&pframe->cmdSet, QFV_particleNumPasses);
pframe->cmdSet.grow = 0;
QFV_AllocateCommandBuffers (device, ctx->cmdpool, 1, &pframe->cmdSet);
for (int j = 0; j < QFV_particleNumPasses; j++) {
QFV_duSetObjectName (device, VK_OBJECT_TYPE_COMMAND_BUFFER,
pframe->cmdSet.a[j],
va (ctx->va_ctx, "cmd:particle:%zd:%s", i,
particle_pass_names[j]));
}
VkEventCreateInfo event = { VK_STRUCTURE_TYPE_EVENT_CREATE_INFO };
dfunc->vkCreateEvent (device->dev, &event, 0, &pframe->physicsEvent);
dfunc->vkCreateEvent (device->dev, &event, 0, &pframe->updateEvent);
QFV_duSetObjectName (device, VK_OBJECT_TYPE_EVENT,
pframe->physicsEvent,
va (ctx->va_ctx, "event:particle:physics:%zd", i));
QFV_duSetObjectName (device, VK_OBJECT_TYPE_EVENT,
pframe->updateEvent,
va (ctx->va_ctx, "event:particle:update:%zd", i));
}
free (sets);
create_buffers (ctx);
qfvPopDebug (ctx);
}
void
Vulkan_Particles_Shutdown (vulkan_ctx_t *ctx)
{
qfv_device_t *device = ctx->device;
qfv_devfuncs_t *dfunc = device->funcs;
particlectx_t *pctx = ctx->particle_context;
size_t frames = ctx->frames.size;
for (size_t i = 0; i < frames; i++) {
__auto_type pframe = &pctx->frames.a[i];
dfunc->vkDestroyEvent (device->dev, pframe->updateEvent, 0);
dfunc->vkDestroyEvent (device->dev, pframe->physicsEvent, 0);
}
QFV_DestroyStagingBuffer (pctx->stage);
QFV_DestroyResource (device, pctx->resources);
free (pctx->resources);
dfunc->vkDestroyPipeline (device->dev, pctx->physics, 0);
dfunc->vkDestroyPipeline (device->dev, pctx->update, 0);
dfunc->vkDestroyPipeline (device->dev, pctx->draw, 0);
free (pctx->frames.a);
free (pctx);
}
psystem_t *__attribute__((pure))//FIXME?
Vulkan_ParticleSystem (vulkan_ctx_t *ctx)
{
return ctx->particle_context->psystem; //FIXME support more
}
static void
particles_update (qfv_renderframe_t *rFrame)
{
vulkan_ctx_t *ctx = rFrame->vulkan_ctx;
qfv_device_t *device = ctx->device;
qfv_devfuncs_t *dfunc = device->funcs;
particlectx_t *pctx = ctx->particle_context;
__auto_type pframe = &pctx->frames.a[ctx->curFrame];
qfv_packet_t *packet = QFV_PacketAcquire (pctx->stage);
__auto_type limits = &device->physDev->properties->limits;
VkMemoryRequirements req = {
.alignment = limits->minStorageBufferOffsetAlignment
};
uint32_t numParticles = min (MaxParticles, pctx->psystem->numparticles);
size_t syssize = sizeof (qfv_particle_system_t);
size_t partoffs = QFV_NextOffset (syssize, &req);
size_t partsize = sizeof (qfv_particle_t) * numParticles;
size_t paramoffs = QFV_NextOffset (partoffs + partsize, &req);
size_t paramsize = sizeof (qfv_parameters_t) * numParticles;
size_t size = paramoffs + paramsize;
qfv_particle_system_t *system = QFV_PacketExtend (packet, size);
*system = (qfv_particle_system_t) {
.vertexCount = 1,
.particleCount = numParticles,
};
__auto_type particles = (qfv_particle_t *) ((byte *)system + partoffs);
memcpy (particles, pctx->psystem->particles, partsize);
qfv_parameters_t *params = (qfv_parameters_t *)((byte *)system + paramoffs);
memcpy (params, pctx->psystem->partparams, paramsize);
if (!numParticles) {
// if there are no particles, then no space for the particle states or
// parameters has been allocated in the staging buffer, so map the
// two buffers over the system buffer. This avoids either buffer being
// just past the end of the staging buffer (which the validation layers
// (correctly) do not like).
// This is fine because the two buffers are only read by the the
// compute shader.
partsize = paramsize = syssize;
partoffs = paramoffs = 0;
}
size_t sysoffs = packet->offset;
VkDescriptorBufferInfo bufferInfo[] = {
{ packet->stage->buffer, sysoffs + partoffs, partsize},
{ packet->stage->buffer, sysoffs + paramoffs, paramsize},
{ packet->stage->buffer, sysoffs, syssize },
};
VkWriteDescriptorSet write[] = {
{ VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, 0,
pframe->newDescriptors, 0, 0, 3,
VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
.pBufferInfo = bufferInfo
},
};
dfunc->vkUpdateDescriptorSets (device->dev, 1, write, 0, 0);
dfunc->vkResetEvent (device->dev, pframe->updateEvent);
dfunc->vkResetEvent (device->dev, pframe->physicsEvent);
VkBufferMemoryBarrier pl_barrier[] = {
{ VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER, 0,
0,
VK_ACCESS_SHADER_READ_BIT,
0, 0,
packet->stage->buffer, sysoffs, paramoffs + paramsize },
};
dfunc->vkCmdPipelineBarrier (packet->cmd,
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
0,
0, 0,
1, pl_barrier,
0, 0);
dfunc->vkCmdBindPipeline (packet->cmd, VK_PIPELINE_BIND_POINT_COMPUTE,
pctx->update);
VkDescriptorSet set[3] = {
pframe->curDescriptors,
pframe->inDescriptors,
pframe->newDescriptors,
};
dfunc->vkCmdBindDescriptorSets (packet->cmd, VK_PIPELINE_BIND_POINT_COMPUTE,
pctx->update_layout, 0, 3, set, 0, 0);
dfunc->vkCmdDispatch (packet->cmd, 1, 1, 1);
dfunc->vkCmdSetEvent (packet->cmd, pframe->updateEvent,
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT);
VkBufferMemoryBarrier ev_barrier[] = {
{ VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER, 0,
VK_ACCESS_SHADER_WRITE_BIT,
VK_ACCESS_SHADER_READ_BIT
| VK_ACCESS_SHADER_WRITE_BIT,
0, 0,
pframe->states, 0, VK_WHOLE_SIZE },
{ VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER, 0,
VK_ACCESS_SHADER_WRITE_BIT,
VK_ACCESS_SHADER_READ_BIT
| VK_ACCESS_SHADER_WRITE_BIT,
0, 0,
pframe->params, 0, VK_WHOLE_SIZE },
{ VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER, 0,
VK_ACCESS_SHADER_WRITE_BIT,
VK_ACCESS_SHADER_READ_BIT
| VK_ACCESS_SHADER_WRITE_BIT,
0, 0,
pframe->system, 0, VK_WHOLE_SIZE },
};
dfunc->vkCmdWaitEvents (packet->cmd, 1, &pframe->updateEvent,
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
0, 0,
3, ev_barrier,
0, 0);
dfunc->vkCmdBindPipeline (packet->cmd, VK_PIPELINE_BIND_POINT_COMPUTE,
pctx->physics);
dfunc->vkCmdBindDescriptorSets (packet->cmd, VK_PIPELINE_BIND_POINT_COMPUTE,
pctx->physics_layout, 0, 1, set, 0, 0);
particle_push_constants_t constants = {
.gravity = pctx->psystem->gravity,
.dT = vr_data.frametime,
};
qfv_push_constants_t push_constants[] = {
{ VK_SHADER_STAGE_COMPUTE_BIT,
field_offset (particle_push_constants_t, gravity),
sizeof (vec4f_t), &constants.gravity },
{ VK_SHADER_STAGE_COMPUTE_BIT,
field_offset (particle_push_constants_t, dT),
sizeof (float), &constants.dT },
};
QFV_PushConstants (device, packet->cmd, pctx->physics_layout,
2, push_constants);
dfunc->vkCmdDispatch (packet->cmd, MaxParticles, 1, 1);
//dfunc->vkCmdSetEvent (packet->cmd, pframe->physicsEvent,
// VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT);
QFV_PacketSubmit (packet);
pctx->psystem->numparticles = 0;
}
void
Vulkan_Particles_CreateRenderPasses (vulkan_ctx_t *ctx)
{
__auto_type rp = QFV_RenderPass_New (ctx, "particles", particles_update);
rp->order = QFV_rp_particles;
DARRAY_APPEND (&ctx->renderPasses, rp);
}