From 41d69586d2473c673d7a1d1025ae72ab2b8f83a9 Mon Sep 17 00:00:00 2001
From: Bill Currie <bill@taniwha.org>
Date: Sat, 24 Jun 2023 03:26:22 +0900
Subject: [PATCH] [vulkan] Get particles mostly working in the new system

The particles die instantly due to curFrame not updating (next commit),
but otherwise work nicely, especially sync is better (many thanks to
Darian for his help with understanding sync scope).
---
 include/QF/Vulkan/qf_particles.h              |   1 -
 libs/video/renderer/vulkan/render.c           |  15 +-
 libs/video/renderer/vulkan/rp_main_def.plist  |   1 +
 libs/video/renderer/vulkan/vulkan_main.c      |   1 -
 libs/video/renderer/vulkan/vulkan_particles.c | 461 ++++++------------
 5 files changed, 174 insertions(+), 305 deletions(-)

diff --git a/include/QF/Vulkan/qf_particles.h b/include/QF/Vulkan/qf_particles.h
index 8cef3fec0..f1663b50c 100644
--- a/include/QF/Vulkan/qf_particles.h
+++ b/include/QF/Vulkan/qf_particles.h
@@ -79,7 +79,6 @@ struct qfv_orenderframe_s;
 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 qfv_orenderframe_s *rFrame);
 void Vulkan_Particles_CreateRenderPasses (struct vulkan_ctx_s *ctx);
 
 #endif//__QF_Vulkan_qf_particles_h
diff --git a/libs/video/renderer/vulkan/render.c b/libs/video/renderer/vulkan/render.c
index d8ec6ecc8..d850ef5c7 100644
--- a/libs/video/renderer/vulkan/render.c
+++ b/libs/video/renderer/vulkan/render.c
@@ -127,6 +127,8 @@ run_renderpass (qfv_renderpass_t *rp, vulkan_ctx_t *ctx)
 	__auto_type job = rctx->job;
 
 	VkCommandBuffer cmd = QFV_GetCmdBuffer (ctx, false);
+	QFV_duSetObjectName (device, VK_OBJECT_TYPE_COMMAND_BUFFER, cmd,
+						 va (ctx->va_ctx, "cmd:render:%s", rp->label.name));
 	VkCommandBufferBeginInfo beginInfo = {
 		VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
 	};
@@ -188,11 +190,13 @@ run_compute_pipeline (qfv_pipeline_t *pipeline, VkCommandBuffer cmd,
 										0, 0);
 	}
 	vec4u_t     d = pipeline->dispatch;
-	dfunc->vkCmdDispatch (cmd, d[0], d[1], d[2]);
+	if (d[0] && d[1] && d[2]) {
+		dfunc->vkCmdDispatch (cmd, d[0], d[1], d[2]);
+	}
 }
 
 static void
-run_compute (qfv_compute_t *comp, vulkan_ctx_t *ctx)
+run_compute (qfv_compute_t *comp, vulkan_ctx_t *ctx, qfv_step_t *step)
 {
 	qfv_device_t *device = ctx->device;
 	qfv_devfuncs_t *dfunc = device->funcs;
@@ -200,6 +204,10 @@ run_compute (qfv_compute_t *comp, vulkan_ctx_t *ctx)
 	__auto_type job = rctx->job;
 
 	VkCommandBuffer cmd = QFV_GetCmdBuffer (ctx, false);
+	QFV_duSetObjectName (device, VK_OBJECT_TYPE_COMMAND_BUFFER, cmd,
+						 va (ctx->va_ctx, "cmd:compute:%s", step->label.name));
+	QFV_duCmdBeginLabel (device, cmd, step->label.name,
+						 {VEC4_EXP (step->label.color)});
 
 	VkCommandBufferBeginInfo beginInfo = {
 		.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
@@ -210,6 +218,7 @@ run_compute (qfv_compute_t *comp, vulkan_ctx_t *ctx)
 		__auto_type pipeline = &comp->pipelines[i];
 		run_compute_pipeline (pipeline, cmd, ctx);
 	}
+	QFV_duCmdEndLabel (device, cmd);
 	dfunc->vkEndCommandBuffer (cmd);
 	DARRAY_APPEND (&job->commands, cmd);
 }
@@ -236,7 +245,7 @@ QFV_RunRenderJob (vulkan_ctx_t *ctx)
 			run_renderpass (step->render->active, ctx);
 		}
 		if (step->compute) {
-			run_compute (step->compute, ctx);
+			run_compute (step->compute, ctx, step);
 		}
 		if (step->process) {
 			run_process (step->process, ctx);
diff --git a/libs/video/renderer/vulkan/rp_main_def.plist b/libs/video/renderer/vulkan/rp_main_def.plist
index 56beb01ea..b3dcce749 100644
--- a/libs/video/renderer/vulkan/rp_main_def.plist
+++ b/libs/video/renderer/vulkan/rp_main_def.plist
@@ -1457,6 +1457,7 @@ steps = {
 			tasks = (
 				{ func = update_framebuffer;
 				  params = ("\"main\""); },
+				{ func = particle_wait_physics; },
 			);
 		};
 	};
diff --git a/libs/video/renderer/vulkan/vulkan_main.c b/libs/video/renderer/vulkan/vulkan_main.c
index a7dc50fec..d26af38aa 100644
--- a/libs/video/renderer/vulkan/vulkan_main.c
+++ b/libs/video/renderer/vulkan/vulkan_main.c
@@ -100,7 +100,6 @@ Vulkan_RenderView (qfv_orenderframe_t *rFrame)
 		Vulkan_DrawViewModel (ctx);
 	}
 	Vulkan_DrawWaterSurfaces (rFrame);
-	Vulkan_DrawParticles (rFrame);
 	Vulkan_Bsp_Flush (ctx);
 	Vulkan_Scene_Flush (ctx);
 }
diff --git a/libs/video/renderer/vulkan/vulkan_particles.c b/libs/video/renderer/vulkan/vulkan_particles.c
index 6bd22a0c9..a42a7d404 100644
--- a/libs/video/renderer/vulkan/vulkan_particles.c
+++ b/libs/video/renderer/vulkan/vulkan_particles.c
@@ -67,119 +67,6 @@ typedef struct {
 	float       dT;
 } particle_push_constants_t;
 
-static const char * __attribute__((used)) particle_pass_names[] = {
-	"draw",
-};
-
-static void
-particle_begin_subpass (VkPipeline pipeline, qfv_orenderframe_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_orenderframe_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)
 {
@@ -187,8 +74,7 @@ create_buffers (vulkan_ctx_t *ctx)
 	qfv_devfuncs_t *dfunc = device->funcs;
 	particlectx_t *pctx = ctx->particle_context;
 	size_t      mp = MaxParticles;
-	auto rctx = ctx->render_context;
-	size_t      frames = rctx->frames.size;
+	size_t      frames = pctx->frames.size;
 
 	pctx->resources = malloc (sizeof (qfv_resource_t)
 							  // states buffer
@@ -209,7 +95,7 @@ create_buffers (vulkan_ctx_t *ctx)
 	};
 	for (size_t i = 0; i < frames; i++) {
 		state_objs[i] = (qfv_resobj_t) {
-			.name = "states",
+			.name = va (ctx->va_ctx, "states:%zd", i),
 			.type = qfv_res_buffer,
 			.buffer = {
 				.size = mp * sizeof (qfv_particle_t),
@@ -218,7 +104,7 @@ create_buffers (vulkan_ctx_t *ctx)
 			},
 		};
 		param_objs[i] = (qfv_resobj_t) {
-			.name = "params",
+			.name = va (ctx->va_ctx, "param:%zd", i),
 			.type = qfv_res_buffer,
 			.buffer = {
 				.size = mp * sizeof (qfv_parameters_t),
@@ -226,7 +112,7 @@ create_buffers (vulkan_ctx_t *ctx)
 			},
 		};
 		system_objs[i] = (qfv_resobj_t) {
-			.name = "system",
+			.name = va (ctx->va_ctx, "system:%zd", i),
 			.type = qfv_res_buffer,
 			.buffer = {
 				.size = sizeof (qfv_particle_system_t),
@@ -277,19 +163,46 @@ create_buffers (vulkan_ctx_t *ctx)
 static void
 particles_draw (const exprval_t **params, exprval_t *result, exprctx_t *ectx)
 {
+	auto taskctx = (qfv_taskctx_t *) ectx;
+	auto ctx = taskctx->ctx;
+	auto device = ctx->device;
+	auto dfunc = device->funcs;
+	auto pctx = ctx->particle_context;
+	auto pframe = &pctx->frames.a[ctx->curFrame];
+	auto cmd = taskctx->cmd;
+
+	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);
+
+	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));
 }
 
 static void
-update_particles (const exprval_t **_params, exprval_t *result, exprctx_t *ectx)
+update_particles (const exprval_t **p, exprval_t *result, exprctx_t *ectx)
 {
-	__auto_type taskctx = (qfv_taskctx_t *) ectx;
-	vulkan_ctx_t *ctx = taskctx->ctx;
-	qfv_device_t *device = ctx->device;
-	qfv_devfuncs_t *dfunc = device->funcs;
-//	VkDevice    dev = device->dev;
-
-	particlectx_t *pctx = ctx->particle_context;
-	__auto_type pframe = &pctx->frames.a[ctx->curFrame];
+	auto taskctx = (qfv_taskctx_t *) ectx;
+	auto ctx = taskctx->ctx;
+	auto device = ctx->device;
+	auto dfunc = device->funcs;
+	auto pctx = ctx->particle_context;
+	auto pframe = &pctx->frames.a[ctx->curFrame];
 
 	qfv_packet_t *packet = QFV_PacketAcquire (pctx->stage);
 
@@ -310,9 +223,9 @@ update_particles (const exprval_t **_params, exprval_t *result, exprctx_t *ectx)
 		.vertexCount = 1,
 		.particleCount = numParticles,
 	};
-	__auto_type particles = (qfv_particle_t *) ((byte *)system + partoffs);
+	auto particles = (qfv_particle_t *) ((byte *) system + partoffs);
 	memcpy (particles, pctx->psystem->particles, partsize);
-	qfv_parameters_t *params = (qfv_parameters_t *)((byte *)system + paramoffs);
+	auto params = (qfv_parameters_t *) ((byte *) system + paramoffs);
 	memcpy (params, pctx->psystem->partparams, paramsize);
 
 	if (!numParticles) {
@@ -342,32 +255,98 @@ update_particles (const exprval_t **_params, exprval_t *result, exprctx_t *ectx)
 	};
 	dfunc->vkUpdateDescriptorSets (device->dev, 1, write, 0, 0);
 
-	__auto_type pipeline = taskctx->pipeline;
-	pipeline->dispatch = (vec4u_t) {1, 1, 1};
-	pipeline->num_descriptorsets = 3;
-	pipeline->descriptorsets[0] = pframe->curDescriptors;
-	pipeline->descriptorsets[1] = pframe->inDescriptors;
-	pipeline->descriptorsets[2] = pframe->newDescriptors;
+	dfunc->vkResetEvent (device->dev, pframe->updateEvent);
 
+	VkBufferMemoryBarrier pl_barrier[] = {
+		{ .sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
+			.srcAccessMask = VK_ACCESS_HOST_WRITE_BIT,
+			.dstAccessMask = VK_ACCESS_SHADER_READ_BIT,
+			.buffer = packet->stage->buffer,
+			.offset = sysoffs,
+			.size = paramoffs + paramsize,
+		},
+	};
+	dfunc->vkCmdPipelineBarrier (packet->cmd,
+								 VK_PIPELINE_STAGE_HOST_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);
 	QFV_PacketSubmit (packet);
 
 	pctx->psystem->numparticles = 0;
 }
 
+static void
+wait_on_event (VkBuffer states, VkBuffer params, VkBuffer system,
+			   VkEvent event, bool draw, VkCommandBuffer cmd,
+			   qfv_devfuncs_t *dfunc)
+{
+	VkStructureType type = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER;
+	VkAccessFlags srcAccess = draw
+		? VK_ACCESS_SHADER_READ_BIT
+			| VK_ACCESS_SHADER_WRITE_BIT
+		: VK_ACCESS_SHADER_WRITE_BIT;
+	VkAccessFlags dstAccess = draw
+		? VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT
+		: VK_ACCESS_SHADER_READ_BIT
+			| VK_ACCESS_SHADER_WRITE_BIT;
+	VkBufferMemoryBarrier barrier[] = {
+		{ .sType = type,
+			.srcAccessMask = srcAccess, .dstAccessMask = dstAccess,
+			.buffer = states, .offset = 0, .size = VK_WHOLE_SIZE },
+		{ .sType = type,
+			.srcAccessMask = srcAccess, .dstAccessMask = dstAccess,
+			.buffer = params, .offset = 0, .size = VK_WHOLE_SIZE },
+		{ .sType = type,
+			.srcAccessMask = srcAccess, .dstAccessMask = dstAccess,
+			.buffer = system, .offset = 0, .size = VK_WHOLE_SIZE },
+	};
+	VkAccessFlags srcStage = draw
+		? VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT
+		: VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT;
+	VkAccessFlags dstStage = draw
+		? VK_PIPELINE_STAGE_DRAW_INDIRECT_BIT
+			| VK_PIPELINE_STAGE_VERTEX_INPUT_BIT
+		: VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT;
+	dfunc->vkCmdWaitEvents (cmd, 1, &event, srcStage, dstStage,
+							0, 0, 3, barrier, 0, 0);
+}
+
 static void
 particle_physics (const exprval_t **params, exprval_t *result, exprctx_t *ectx)
 {
-	__auto_type taskctx = (qfv_taskctx_t *) ectx;
-	vulkan_ctx_t *ctx = taskctx->ctx;
-	qfv_device_t *device = ctx->device;
+	auto taskctx = (qfv_taskctx_t *) ectx;
+	auto ctx = taskctx->ctx;
+	auto device = ctx->device;
+	auto dfunc = device->funcs;
+	auto pctx = ctx->particle_context;
+	auto pframe = &pctx->frames.a[ctx->curFrame];
+	auto cmd = taskctx->cmd;
 
-	particlectx_t *pctx = ctx->particle_context;
-	__auto_type pframe = &pctx->frames.a[ctx->curFrame];
+	dfunc->vkResetEvent (device->dev, pframe->physicsEvent);
+	wait_on_event (pframe->states, pframe->params, pframe->system,
+				   pframe->updateEvent, false, cmd, dfunc);
 
-	__auto_type pipeline = taskctx->pipeline;
-	pipeline->dispatch = (vec4u_t) {MaxParticles, 1, 1};
-	pipeline->num_descriptorsets = 1;
-	pipeline->descriptorsets[0] = pframe->curDescriptors;
+	VkDescriptorSet set[] = {
+		pframe->curDescriptors,
+	};
+	dfunc->vkCmdBindDescriptorSets (cmd, VK_PIPELINE_BIND_POINT_COMPUTE,
+									pctx->physics_layout, 0, 1, set, 0, 0);
 
 	particle_push_constants_t constants = {
 		.gravity = pctx->psystem->gravity,
@@ -381,8 +360,38 @@ particle_physics (const exprval_t **params, exprval_t *result, exprctx_t *ectx)
 			field_offset (particle_push_constants_t, dT),
 			sizeof (float), &constants.dT },
 	};
-	QFV_PushConstants (device, taskctx->cmd, pipeline->layout,
-					   2, push_constants);
+	QFV_PushConstants (device, cmd, pctx->physics_layout, 2, push_constants);
+	dfunc->vkCmdDispatch (cmd, MaxParticles, 1, 1);
+	dfunc->vkCmdSetEvent (cmd, pframe->physicsEvent,
+						  VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT);
+}
+
+static void
+particle_wait_physics (const exprval_t **params, exprval_t *result,
+					   exprctx_t *ectx)
+{
+	auto taskctx = (qfv_taskctx_t *) ectx;
+	auto ctx = taskctx->ctx;
+	auto device = ctx->device;
+	auto dfunc = device->funcs;
+	auto pctx = ctx->particle_context;
+	auto pframe = &pctx->frames.a[ctx->curFrame];
+
+	auto cmd = QFV_GetCmdBuffer (ctx, false);
+	QFV_duSetObjectName (device, VK_OBJECT_TYPE_COMMAND_BUFFER, cmd,
+						 va (ctx->va_ctx, "cmd:particle_wait_physics:%d",
+							 ctx->curFrame));
+	QFV_AppendCmdBuffer (ctx, cmd);
+	VkCommandBufferBeginInfo beginInfo = {
+		.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
+		.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
+	};
+	dfunc->vkBeginCommandBuffer (cmd, &beginInfo);
+
+	wait_on_event (pframe->states, pframe->params, pframe->system,
+				   pframe->physicsEvent, true, cmd, dfunc);
+
+	dfunc->vkEndCommandBuffer (cmd);
 }
 
 static exprfunc_t particles_draw_func[] = {
@@ -397,10 +406,15 @@ static exprfunc_t particle_physics_func[] = {
 	{ .func = particle_physics },
 	{}
 };
+static exprfunc_t particle_wait_physics_func[] = {
+	{ .func = particle_wait_physics },
+	{}
+};
 static exprsym_t particles_task_syms[] = {
 	{ "particles_draw", &cexpr_function, particles_draw_func },
 	{ "update_particles", &cexpr_function, update_particles_func },
 	{ "particle_physics", &cexpr_function, particle_physics_func },
+	{ "particle_wait_physics", &cexpr_function, particle_wait_physics_func },
 	{}
 };
 
@@ -417,8 +431,7 @@ Vulkan_Particles_Init (vulkan_ctx_t *ctx)
 	ctx->particle_context = pctx;
 	pctx->psystem = &r_psystem;
 
-	auto rctx = ctx->render_context;
-	size_t      frames = rctx->frames.size;
+	size_t      frames = ctx->render_context->frames.size;
 	DARRAY_INIT (&pctx->frames, frames);
 	DARRAY_RESIZE (&pctx->frames, frames);
 	pctx->frames.grow = 0;
@@ -448,19 +461,6 @@ Vulkan_Particles_Init (vulkan_ctx_t *ctx)
 		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);
@@ -510,145 +510,6 @@ Vulkan_ParticleSystem (vulkan_ctx_t *ctx)
 static void
 particles_update (qfv_orenderframe_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 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