[vulkan] Get particle compute pipelines running

I don't yet know whether they actually work (not rendering yet), but the
system isn't locking up, and shutdown is clean, so at least resources
are handled correctly.
This commit is contained in:
Bill Currie 2022-11-28 00:52:07 +09:00
parent dd1b15c92f
commit d9b0ee22e6
7 changed files with 288 additions and 73 deletions

View file

@ -101,12 +101,17 @@ DEVICE_LEVEL_VULKAN_FUNCTION (vkEndCommandBuffer)
DEVICE_LEVEL_VULKAN_FUNCTION (vkResetCommandBuffer)
DEVICE_LEVEL_VULKAN_FUNCTION (vkCreateSemaphore)
DEVICE_LEVEL_VULKAN_FUNCTION (vkCreateFence)
DEVICE_LEVEL_VULKAN_FUNCTION (vkCreateEvent)
DEVICE_LEVEL_VULKAN_FUNCTION (vkWaitForFences)
DEVICE_LEVEL_VULKAN_FUNCTION (vkResetFences)
DEVICE_LEVEL_VULKAN_FUNCTION (vkGetFenceStatus)
DEVICE_LEVEL_VULKAN_FUNCTION (vkResetEvent)
DEVICE_LEVEL_VULKAN_FUNCTION (vkSetEvent)
DEVICE_LEVEL_VULKAN_FUNCTION (vkGetEventStatus)
DEVICE_LEVEL_VULKAN_FUNCTION (vkQueueSubmit)
DEVICE_LEVEL_VULKAN_FUNCTION (vkQueueWaitIdle)
DEVICE_LEVEL_VULKAN_FUNCTION (vkDeviceWaitIdle)
DEVICE_LEVEL_VULKAN_FUNCTION (vkDestroyEvent)
DEVICE_LEVEL_VULKAN_FUNCTION (vkDestroyFence)
DEVICE_LEVEL_VULKAN_FUNCTION (vkDestroySemaphore)
DEVICE_LEVEL_VULKAN_FUNCTION (vkFreeCommandBuffers)
@ -171,10 +176,15 @@ DEVICE_LEVEL_VULKAN_FUNCTION (vkCmdBeginRenderPass)
DEVICE_LEVEL_VULKAN_FUNCTION (vkCmdNextSubpass)
DEVICE_LEVEL_VULKAN_FUNCTION (vkCmdEndRenderPass)
DEVICE_LEVEL_VULKAN_FUNCTION (vkCmdBindPipeline)
DEVICE_LEVEL_VULKAN_FUNCTION (vkCmdDispatch)
DEVICE_LEVEL_VULKAN_FUNCTION (vkCmdClearColorImage)
DEVICE_LEVEL_VULKAN_FUNCTION (vkCmdExecuteCommands)
DEVICE_LEVEL_VULKAN_FUNCTION (vkCmdPushConstants)
DEVICE_LEVEL_VULKAN_FUNCTION (vkCmdResetEvent)
DEVICE_LEVEL_VULKAN_FUNCTION (vkCmdSetEvent)
DEVICE_LEVEL_VULKAN_FUNCTION (vkCmdWaitEvents)
DEVICE_LEVEL_VULKAN_FUNCTION (vkCmdSetViewport)
DEVICE_LEVEL_VULKAN_FUNCTION (vkCmdSetScissor)
DEVICE_LEVEL_VULKAN_FUNCTION (vkCmdBindVertexBuffers)

View file

@ -3,6 +3,7 @@
#include "QF/darray.h"
#include "QF/image.h"
#include "QF/render.h"
#include "QF/Vulkan/command.h"
@ -37,14 +38,15 @@ typedef enum {
typedef struct particleframe_s {
VkCommandBuffer compute;
VkSemaphore physSem;
VkSemaphore drawSem;
VkSemaphore updateSem;
VkBuffer state;
VkEvent physicsEvent;
VkEvent updateEvent;
VkBuffer states;
VkBuffer params;
VkBuffer system;
VkDescriptorSet descriptors;
VkDescriptorSet curDescriptors;
VkDescriptorSet inDescriptors;
VkDescriptorSet newDescriptors;
qfv_cmdbufferset_t cmdSet;
} particleframe_t;
@ -58,7 +60,7 @@ typedef struct particlectx_s {
VkPipeline update;
VkPipeline draw;
VkDeviceMemory memory;
struct qfv_resource_s *resources;
struct qfv_stagebuf_s *stage;
VkDescriptorPool pool;
@ -77,5 +79,6 @@ struct psystem_s *Vulkan_ParticleSystem (struct vulkan_ctx_s *ctx);
void Vulkan_Particles_Init (struct vulkan_ctx_s *ctx);
void Vulkan_Particles_Shutdown (struct vulkan_ctx_s *ctx);
void Vulkan_DrawParticles (struct vulkan_ctx_s *ctx);
void Vulkan_Particles_CreateRenderPasses (struct vulkan_ctx_s *ctx);
#endif//__QF_Vulkan_qf_particles_h

View file

@ -39,6 +39,7 @@
*/
enum {
QFV_rp_particles,
QFV_rp_shadowmap,
QFV_rp_preoutput,
QFV_rp_main,

View file

@ -178,11 +178,11 @@
};
particle_pool = {
flags = 0;
maxSets = $frames.size;
maxSets = "3z * $frames.size";
bindings = (
{
type = storage_buffer;
descriptorCount = 3;
descriptorCount = "3z * 3z * $frames.size";
},
);
};
@ -499,7 +499,7 @@
{
stageFlags = compute;
offset = 0;
size = "16 * 4 + 4";
size = "4 * 4 + 4";
},
);
};

View file

@ -19,38 +19,38 @@ struct Parameters {
vec4 ramp; // [rate, max, alpha rate, scale rate]
};
layout(std140, set = 0, binding = 0) buffer InStates {
Particle particles[];
} inState;
layout(std140, set = 0, binding = 1) buffer InParameters {
Parameters parameters[];
} inParameters;
//doubles as VkDrawIndirectCommand
layout(std140, set = 0, binding = 2) buffer InSystem {
uint vertexCount;
uint particleCount; //instanceCount
uint firstVertex;
uint firstInstance;
} inSystem;
layout(std140, set = 1, binding = 0) buffer OutStates {
layout(std140, set = 0, binding = 0) buffer OutStates {
Particle particles[];
} outStates;
layout(std140, set = 1, binding = 1) buffer OutParameters {
layout(std140, set = 0, binding = 1) buffer OutParameters {
Parameters parameters[];
} outParameters;
//doubles as VkDrawIndirectCommand
layout(std140, set = 1, binding = 2) buffer OutSystem {
layout(std140, set = 0, binding = 2) buffer OutSystem {
uint vertexCount;
uint particleCount; //instanceCount
uint firstVertex;
uint firstInstance;
} outSystem;
layout(std140, set = 1, binding = 0) buffer InStates {
Particle particles[];
} inStates;
layout(std140, set = 1, binding = 1) buffer InParameters {
Parameters parameters[];
} inParameters;
//doubles as VkDrawIndirectCommand
layout(std140, set = 1, binding = 2) buffer InSystem {
uint vertexCount;
uint particleCount; //instanceCount
uint firstVertex;
uint firstInstance;
} inSystem;
layout(std140, set = 2, binding = 0) buffer NewStates {
Particle particles[];
} newStates;
@ -85,21 +85,24 @@ main ()
uint j = 0;
// compact existing partles removing dead particles
for (uint i = 0; i < inSystem.particleCount; i++) {
if (is_dead (inState.particles[i], inParameters.parameters[i])) {
if (is_dead (inStates.particles[i], inParameters.parameters[i])) {
continue;
}
outStates.particles[j] = inState.particles[i];
outStates.particles[j] = inStates.particles[i];
outParameters.parameters[j] = inParameters.parameters[i];
j++;
}
// inject any new particles that aren't DOA
for (uint i = 0; i < newSystem.particleCount && j < MaxParticles; i++) {
if (is_dead (inState.particles[i], inParameters.parameters[i])) {
if (is_dead (newStates.particles[i], newParameters.parameters[i])) {
continue;
}
outStates.particles[j] = newStates.particles[i];
outParameters.parameters[j] = newParameters.parameters[i];
j++;
}
outSystem.vertexCount = newSystem.vertexCount;
outSystem.particleCount = j;
outSystem.firstVertex = newSystem.firstVertex;
outSystem.firstInstance = newSystem.firstInstance;
}

View file

@ -44,16 +44,25 @@
#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_particles.h"
#include "QF/Vulkan/qf_renderpass.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",
};
@ -69,48 +78,90 @@ 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 size = 0;
size_t mp = MaxParticles;
size_t frames = ctx->frames.size;
VkMemoryRequirements stReq, parmReq, sysReq;
for (size_t i = 0; i < pctx->frames.size; i++) {
__auto_type pframe = &pctx->frames.a[i];
pframe->state
= QFV_CreateBuffer (device, sizeof (qfv_particle_t) * mp,
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT
| VK_BUFFER_USAGE_VERTEX_BUFFER_BIT);
pframe->params
= QFV_CreateBuffer (device, sizeof (qfv_particle_t) * mp,
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT);
pframe->system
= QFV_CreateBuffer (device, sizeof (qfv_particle_t) * mp,
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT
| VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT);
dfunc->vkGetBufferMemoryRequirements (device->dev, pframe->state,
&stReq);
dfunc->vkGetBufferMemoryRequirements (device->dev, pframe->params,
&parmReq);
dfunc->vkGetBufferMemoryRequirements (device->dev, pframe->system,
&sysReq);
size = QFV_NextOffset (size + stReq.size, &stReq);
size = QFV_NextOffset (size + parmReq.size, &parmReq);
size = QFV_NextOffset (size + sysReq.size, &sysReq);
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,
},
};
}
size_t stageSize = (size / pctx->frames.size)*(pctx->frames.size + 1);
QFV_CreateResource (device, pctx->resources);
size_t stageSize = (pctx->resources->size / frames)*(frames + 1);
pctx->stage = QFV_CreateStagingBuffer (device, "particles", stageSize,
ctx->cmdpool);
pctx->memory = QFV_AllocBufferMemory (device, pctx->frames.a[0].state,
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
size, 0);
size_t offset = 0;
for (size_t i = 0; i < pctx->frames.size; i++) {
for (size_t i = 0; i < frames; i++) {
__auto_type pframe = &pctx->frames.a[i];
QFV_BindBufferMemory (device, pframe->state, pctx->memory, offset);
offset = QFV_NextOffset (offset + stReq.size, &parmReq);
QFV_BindBufferMemory (device, pframe->params, pctx->memory, offset);
offset = QFV_NextOffset (offset + parmReq.size, &sysReq);
QFV_BindBufferMemory (device, pframe->system, pctx->memory, offset);
offset = QFV_NextOffset (offset + sysReq.size, &stReq);
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);
}
}
@ -118,6 +169,7 @@ void
Vulkan_Particles_Init (vulkan_ctx_t *ctx)
{
qfv_device_t *device = ctx->device;
qfv_devfuncs_t *dfunc = device->funcs;
qfvPushDebug (ctx, "particles init");
@ -141,9 +193,19 @@ Vulkan_Particles_Init (vulkan_ctx_t *ctx)
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;
@ -156,7 +218,18 @@ Vulkan_Particles_Init (vulkan_ctx_t *ctx)
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);
}
@ -167,16 +240,17 @@ 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 < pctx->frames.size; i++) {
for (size_t i = 0; i < frames; i++) {
__auto_type pframe = &pctx->frames.a[i];
free (pframe->cmdSet.a);
dfunc->vkDestroyBuffer (device->dev, pframe->state, 0);
dfunc->vkDestroyBuffer (device->dev, pframe->params, 0);
dfunc->vkDestroyBuffer (device->dev, pframe->system, 0);
dfunc->vkDestroyEvent (device->dev, pframe->updateEvent, 0);
dfunc->vkDestroyEvent (device->dev, pframe->physicsEvent, 0);
}
dfunc->vkFreeMemory (device->dev, pctx->memory, 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);
@ -190,3 +264,125 @@ 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);
VkDescriptorBufferInfo bufferInfo[] = {
{ packet->stage->buffer, 0, syssize },
{ packet->stage->buffer, partoffs, partsize ? partsize : 1},
{ packet->stage->buffer, paramoffs, paramsize ? paramsize : 1},
};
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);
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 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, 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, 8, 8, 8);
dfunc->vkCmdSetEvent (packet->cmd, pframe->physicsEvent,
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT);
QFV_PacketSubmit (packet);
}
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);
}

View file

@ -54,6 +54,7 @@
#include "QF/Vulkan/qf_lighting.h"
#include "QF/Vulkan/qf_main.h"
#include "QF/Vulkan/qf_output.h"
#include "QF/Vulkan/qf_particles.h"
#include "QF/Vulkan/qf_renderpass.h"
#include "QF/Vulkan/qf_vid.h"
@ -166,6 +167,7 @@ Vulkan_CreateRenderPasses (vulkan_ctx_t *ctx)
{
Vulkan_Output_CreateRenderPasses (ctx);
Vulkan_Main_CreateRenderPasses (ctx);
Vulkan_Particles_CreateRenderPasses (ctx);
Vulkan_Lighting_CreateRenderPasses (ctx);
heapsort (ctx->renderPasses.a, ctx->renderPasses.size,