5#include "graphics/shaders/particle_resident_comp_spv.inc"
6#include "graphics/shaders/particle_resident_frag_spv.inc"
7#include "graphics/shaders/particle_resident_sort_comp_spv.inc"
8#include "graphics/shaders/particle_resident_vert_spv.inc"
19struct alignas(16) ParticleMeta {
30static_assert(
sizeof(ParticleMeta) == 32,
"particle metadata must match GLSL");
32struct alignas(16) ParticleComputePush {
41static_assert(
sizeof(ParticleComputePush) == 64,
"particle compute push layout must match GLSL");
43struct alignas(16) ParticleDrawPush {
53static_assert(
sizeof(ParticleDrawPush) == 112,
"particle draw push layout must match GLSL");
55struct alignas(16) ParticleSortPush {
65static_assert(
sizeof(ParticleSortPush) == 32,
"particle sort push layout must match GLSL");
68 std::uint32_t
key = 0;
72static_assert(
sizeof(SortRecord) == 8,
"sort record must match GLSL");
74std::uint32_t nextPow2Count(std::uint32_t
value) {
75 if (
value <= 1u)
return 1u;
76 return std::bit_ceil(
value);
79vk::Pipeline createParticleDrawPipeline(vkb::Device&
device,
const vkb::BuiltRenderPass& renderPass,
80 vk::PipelineLayout
layout, vk::ShaderModule
vert, vk::ShaderModule
frag,
82 const std::array stages{
83 vk::PipelineShaderStageCreateInfo({}, vk::ShaderStageFlagBits::eVertex,
vert,
"main"),
84 vk::PipelineShaderStageCreateInfo({}, vk::ShaderStageFlagBits::eFragment,
frag,
"main"),
86 vk::PipelineVertexInputStateCreateInfo vertexInput{};
87 vk::PipelineInputAssemblyStateCreateInfo inputAssembly{};
88 inputAssembly.topology = vk::PrimitiveTopology::eTriangleList;
89 vk::Viewport
viewport(0.f, 0.f, 1.f, 1.f, 0.f, 1.f);
90 vk::Rect2D scissor({0, 0}, {1, 1});
91 vk::PipelineViewportStateCreateInfo viewportState({}, 1, &
viewport, 1, &scissor);
92 vk::PipelineRasterizationStateCreateInfo rasterizer{};
93 rasterizer.polygonMode = vk::PolygonMode::eFill;
94 rasterizer.cullMode = vk::CullModeFlagBits::eNone;
95 rasterizer.frontFace = vk::FrontFace::eCounterClockwise;
96 rasterizer.lineWidth = 1.f;
97 vk::PipelineMultisampleStateCreateInfo multisampling{};
98 multisampling.rasterizationSamples = vk::SampleCountFlagBits::e1;
99 vk::PipelineDepthStencilStateCreateInfo depthStencil{};
100 const auto attachment = makeBlendAttachment(
blend);
101 vk::PipelineColorBlendStateCreateInfo colorBlend{};
102 colorBlend.attachmentCount = 1;
103 colorBlend.pAttachments = &attachment;
104 const std::array dynamicStates{vk::DynamicState::eViewport, vk::DynamicState::eScissor};
105 vk::PipelineDynamicStateCreateInfo dynamicState({}, dynamicStates);
107 vk::GraphicsPipelineCreateInfo
info{};
108 info.stageCount = std::uint32_t(stages.size());
109 info.pStages = stages.data();
110 info.pVertexInputState = &vertexInput;
111 info.pInputAssemblyState = &inputAssembly;
112 info.pViewportState = &viewportState;
113 info.pRasterizationState = &rasterizer;
114 info.pMultisampleState = &multisampling;
115 info.pDepthStencilState = &depthStencil;
116 info.pColorBlendState = &colorBlend;
117 info.pDynamicState = &dynamicState;
119 info.renderPass = renderPass.handle;
120 const auto result =
device->createGraphicsPipeline(vk::PipelineCache{},
info);
121 if (result.result != vk::Result::eSuccess)
throw std::runtime_error(
"failed_create_gpu_particle_pipeline");
125void updateStorageBinding(vk::WriteDescriptorSet& write, vk::DescriptorBufferInfo&
info, vk::DescriptorSet set,
132 write.descriptorCount = 1;
133 write.descriptorType = vk::DescriptorType::eStorageBuffer;
134 write.pBufferInfo = &
info;
173 if (!
supportsGpuParticles() || !gpuParticleComputePipeline_ || !gpuParticleAlphaPipeline_)
return false;
174 if (activeCanvas !=
nullptr)
return false;
175 return !(swapchainPassOpen && !sceneColorPassOpen);
189 if (
found == gpuParticles_.end())
return;
191 std::vector<vk::DescriptorSet> sets;
192 for (
const auto& slot :
found->second->slots) {
193 if (slot.computeSet) sets.push_back(slot.computeSet);
194 if (slot.drawSet) sets.push_back(slot.drawSet);
195 if (slot.sortSet) sets.push_back(slot.sortSet);
197 if (!sets.empty() && descriptorPool)
device->freeDescriptorSets(descriptorPool, sets);
198 delete found->second;
199 gpuParticles_.erase(
found);
204 if (
found == gpuParticles_.end())
return;
217 if (
found == gpuParticles_.end())
return false;
221 if (accepted > 0 && spawns)
222 resource.spawns.assign(spawns, spawns + accepted);
231 if (gpuParticles_.find(
handle) == gpuParticles_.end())
return false;
232 const std::uint32_t
index = std::uint32_t(gpuParticleDraws_.size());
235 spans.push_back({OverlayKind::GpuParticles,
index, 0, 0});
244void Graphics::createGpuParticlePipelines() {
245 if (gpuParticleComputePipeline_ || !descriptorPool || !renderpass)
return;
247 std::array<vk::DescriptorSetLayoutBinding, 5> computeBindings{};
249 computeBindings[
binding] = {
binding, vk::DescriptorType::eStorageBuffer, 1, vk::ShaderStageFlagBits::eCompute};
251 gpuParticleComputeSetLayout_ =
252 device->createDescriptorSetLayout(vk::DescriptorSetLayoutCreateInfo({}, computeBindings));
254 const std::array drawBindings{
255 vk::DescriptorSetLayoutBinding(0, vk::DescriptorType::eCombinedImageSampler, 1,
256 vk::ShaderStageFlagBits::eFragment),
257 vk::DescriptorSetLayoutBinding(1, vk::DescriptorType::eStorageBuffer, 1, vk::ShaderStageFlagBits::eVertex),
258 vk::DescriptorSetLayoutBinding(2, vk::DescriptorType::eCombinedImageSampler, 1,
259 vk::ShaderStageFlagBits::eFragment),
260 vk::DescriptorSetLayoutBinding(3, vk::DescriptorType::eStorageBuffer, 1, vk::ShaderStageFlagBits::eVertex),
261 vk::DescriptorSetLayoutBinding(4, vk::DescriptorType::eStorageBuffer, 1, vk::ShaderStageFlagBits::eVertex),
263 gpuParticleDrawSetLayout_ =
device->createDescriptorSetLayout(vk::DescriptorSetLayoutCreateInfo({}, drawBindings));
265 std::array<vk::DescriptorSetLayoutBinding, 4> sortBindings{};
267 sortBindings[
binding] = {
binding, vk::DescriptorType::eStorageBuffer, 1, vk::ShaderStageFlagBits::eCompute};
269 gpuParticleSortSetLayout_ =
270 device->createDescriptorSetLayout(vk::DescriptorSetLayoutCreateInfo({}, sortBindings));
272 const auto computePush = pushConstantRange(vk::ShaderStageFlagBits::eCompute,
sizeof(ParticleComputePush));
273 gpuParticleComputeLayout_ = createPipelineLayout(device, gpuParticleComputeSetLayout_, &computePush);
274 const auto drawPush = pushConstantRange(vk::ShaderStageFlagBits::eVertex,
sizeof(ParticleDrawPush));
275 gpuParticleDrawLayout_ = createPipelineLayout(device, gpuParticleDrawSetLayout_, &drawPush);
276 const auto sortPush = pushConstantRange(vk::ShaderStageFlagBits::eCompute,
sizeof(ParticleSortPush));
277 gpuParticleSortLayout_ = createPipelineLayout(device, gpuParticleSortSetLayout_, &sortPush);
279 auto createComputePipeline = [&](
const std::uint32_t* words, std::size_t
count, vk::PipelineLayout
layout) {
280 const auto spv = std::vector<std::uint32_t>(words, words +
count);
281 vk::ShaderModule
module = vkb::PipelineBuilder::createShaderModule(device.instance, spv);
282 vk::PipelineShaderStageCreateInfo stage{};
283 stage.stage = vk::ShaderStageFlagBits::eCompute;
284 stage.module =
module;
285 stage.pName =
"main";
286 vk::ComputePipelineCreateInfo
info{};
289 auto result =
device->createComputePipeline({},
info);
290 device->destroyShaderModule(module);
291 return result.result == vk::Result::eSuccess ? result.value : vk::Pipeline{};
293 gpuParticleComputePipeline_ =
294 createComputePipeline(particle_resident_comp_spv, particle_resident_comp_spv_count, gpuParticleComputeLayout_);
295 gpuParticleSortPipeline_ = createComputePipeline(particle_resident_sort_comp_spv,
296 particle_resident_sort_comp_spv_count, gpuParticleSortLayout_);
298 const auto vertSpv = std::vector<std::uint32_t>(particle_resident_vert_spv,
299 particle_resident_vert_spv + particle_resident_vert_spv_count);
300 const auto fragSpv = std::vector<std::uint32_t>(particle_resident_frag_spv,
301 particle_resident_frag_spv + particle_resident_frag_spv_count);
302 vk::ShaderModule
vert = vkb::PipelineBuilder::createShaderModule(
device.instance, vertSpv);
303 vk::ShaderModule
frag = vkb::PipelineBuilder::createShaderModule(
device.instance, fragSpv);
304 gpuParticleAlphaPipeline_ =
306 gpuParticleAdditivePipeline_ =
308 gpuParticlePremultipliedPipeline_ =
310 gpuParticleMultiplyPipeline_ =
312 gpuParticleOpaquePipeline_ =
318void Graphics::rebuildGpuParticleDrawPipelines(
const vkb::BuiltRenderPass &
target) {
319 if (!gpuParticleDrawLayout_ || !
target)
return;
320 auto destroyPipe = [&](vk::Pipeline &
p) {
326 destroyPipe(gpuParticleAlphaPipeline_);
327 destroyPipe(gpuParticleAdditivePipeline_);
328 destroyPipe(gpuParticlePremultipliedPipeline_);
329 destroyPipe(gpuParticleMultiplyPipeline_);
330 destroyPipe(gpuParticleOpaquePipeline_);
331 const auto vertSpv = std::vector<std::uint32_t>(particle_resident_vert_spv,
332 particle_resident_vert_spv + particle_resident_vert_spv_count);
333 const auto fragSpv = std::vector<std::uint32_t>(particle_resident_frag_spv,
334 particle_resident_frag_spv + particle_resident_frag_spv_count);
335 vk::ShaderModule
vert = vkb::PipelineBuilder::createShaderModule(
device.instance, vertSpv);
336 vk::ShaderModule
frag = vkb::PipelineBuilder::createShaderModule(
device.instance, fragSpv);
337 gpuParticleAlphaPipeline_ =
339 gpuParticleAdditivePipeline_ =
341 gpuParticlePremultipliedPipeline_ =
343 gpuParticleMultiplyPipeline_ =
345 gpuParticleOpaquePipeline_ =
351void Graphics::ensureHdrGpuParticleDrawPipelines() {
352 if (hdrGpuParticleAlphaPipeline_ || !gpuParticleDrawLayout_ || !hdrOffscreenRenderPass)
return;
353 const auto vertSpv = std::vector<std::uint32_t>(particle_resident_vert_spv,
354 particle_resident_vert_spv + particle_resident_vert_spv_count);
355 const auto fragSpv = std::vector<std::uint32_t>(particle_resident_frag_spv,
356 particle_resident_frag_spv + particle_resident_frag_spv_count);
357 vk::ShaderModule
vert = vkb::PipelineBuilder::createShaderModule(
device.instance, vertSpv);
358 vk::ShaderModule
frag = vkb::PipelineBuilder::createShaderModule(
device.instance, fragSpv);
359 hdrGpuParticleAlphaPipeline_ = createParticleDrawPipeline(
361 hdrGpuParticleAdditivePipeline_ = createParticleDrawPipeline(
363 hdrGpuParticlePremultipliedPipeline_ = createParticleDrawPipeline(
365 hdrGpuParticleMultiplyPipeline_ = createParticleDrawPipeline(
367 hdrGpuParticleOpaquePipeline_ = createParticleDrawPipeline(
373void Graphics::destroyGpuParticleResources() {
374 gpuParticleDraws_.clear();
375 for (
auto& [
handle, resource] : gpuParticles_) {
377 std::vector<vk::DescriptorSet> sets;
379 if (slot.computeSet) sets.push_back(slot.computeSet);
380 if (slot.drawSet) sets.push_back(slot.drawSet);
381 if (slot.sortSet) sets.push_back(slot.sortSet);
383 if (!sets.empty() && descriptorPool)
device->freeDescriptorSets(descriptorPool, sets);
386 gpuParticles_.clear();
387 auto destroyPipeline = [&](vk::Pipeline& pipeline) {
388 if (pipeline)
device->destroyPipeline(pipeline);
391 destroyPipeline(gpuParticleComputePipeline_);
392 destroyPipeline(gpuParticleSortPipeline_);
393 destroyPipeline(gpuParticleAlphaPipeline_);
394 destroyPipeline(gpuParticleAdditivePipeline_);
395 destroyPipeline(gpuParticlePremultipliedPipeline_);
396 destroyPipeline(gpuParticleMultiplyPipeline_);
397 destroyPipeline(gpuParticleOpaquePipeline_);
398 destroyPipeline(hdrGpuParticleAlphaPipeline_);
399 destroyPipeline(hdrGpuParticleAdditivePipeline_);
400 destroyPipeline(hdrGpuParticlePremultipliedPipeline_);
401 destroyPipeline(hdrGpuParticleMultiplyPipeline_);
402 destroyPipeline(hdrGpuParticleOpaquePipeline_);
403 if (gpuParticleComputeLayout_)
device->destroyPipelineLayout(gpuParticleComputeLayout_);
404 if (gpuParticleDrawLayout_)
device->destroyPipelineLayout(gpuParticleDrawLayout_);
405 if (gpuParticleSortLayout_)
device->destroyPipelineLayout(gpuParticleSortLayout_);
406 gpuParticleComputeLayout_ =
nullptr;
407 gpuParticleDrawLayout_ =
nullptr;
408 gpuParticleSortLayout_ =
nullptr;
409 if (gpuParticleComputeSetLayout_)
device->destroyDescriptorSetLayout(gpuParticleComputeSetLayout_);
410 if (gpuParticleDrawSetLayout_)
device->destroyDescriptorSetLayout(gpuParticleDrawSetLayout_);
411 if (gpuParticleSortSetLayout_)
device->destroyDescriptorSetLayout(gpuParticleSortSetLayout_);
412 gpuParticleComputeSetLayout_ =
nullptr;
413 gpuParticleDrawSetLayout_ =
nullptr;
414 gpuParticleSortSetLayout_ =
nullptr;
417void Graphics::recordGpuParticleCompute(vk::CommandBuffer cb) {
418 if (!cb || !gpuParticleComputePipeline_)
return;
419 const std::size_t frameSlot = currentFrameSlot();
420 const std::size_t requiredSlots = std::max<std::size_t>(2, frameSlotCount());
421 using BufferUsage = vk::BufferUsageFlagBits;
422 using Memory = vk::MemoryPropertyFlagBits;
425 for (
auto it = gpuParticleDraws_.rbegin(); it != gpuParticleDraws_.rend(); ++it) {
426 if (it->handle ==
handle)
return &it->draw;
433 const GpuParticleDraw* drawRequest = latestDraw(
handle);
438 !(drawRequest && drawRequest->cameraEnabled))
441 if (!
resource.pendingUpdate && !
resource.pendingReset && !needsSort)
continue;
442 if (
resource.slots.size() < requiredSlots)
resource.slots.resize(requiredSlots);
446 if (!runUpdate &&
resource.lastOutputSlot < 0)
continue;
447 const vk::DeviceSize stateBytes = vk::DeviceSize(
resource.capacity) *
sizeof(GpuParticleSpawn);
448 const vk::DeviceSize metaBytes =
sizeof(ParticleMeta);
449 const std::uint32_t paddedSort = nextPow2Count(
resource.capacity);
450 const vk::DeviceSize sortBytes = vk::DeviceSize(paddedSort) *
sizeof(SortRecord);
451 const vk::DeviceSize indexBytes = vk::DeviceSize(
resource.capacity) *
sizeof(std::uint32_t);
453 if (!slot.state.buffer) {
454 const auto stateUsage = BufferUsage::eStorageBuffer | BufferUsage::eTransferSrc | BufferUsage::eTransferDst;
455 const auto metaUsage = BufferUsage::eStorageBuffer | BufferUsage::eTransferSrc | BufferUsage::eTransferDst;
456 slot.state.allocate(frameToken(),
device, stateUsage, stateBytes);
457 slot.scratchState.allocate(frameToken(),
device, stateUsage, stateBytes);
458 slot.meta.allocate(frameToken(),
device, metaUsage, metaBytes,
459 Memory::eHostVisible | Memory::eHostCoherent);
460 slot.indirect.allocate(frameToken(),
device, BufferUsage::eIndirectBuffer | BufferUsage::eTransferDst,
461 sizeof(vk::DrawIndirectCommand));
462 slot.scratchMeta.allocate(frameToken(),
device, metaUsage, metaBytes);
463 slot.spawns.allocate(frameToken(),
device, BufferUsage::eStorageBuffer, stateBytes,
464 Memory::eHostVisible | Memory::eHostCoherent);
465 slot.sortRecords.allocate(frameToken(),
device, BufferUsage::eStorageBuffer, sortBytes);
466 slot.sortedIndices.allocate(frameToken(),
device, BufferUsage::eStorageBuffer, indexBytes);
467 const ParticleMeta zero{};
468 slot.meta.updateLocal(frameToken(), zero);
470 if (!slot.sortRecords.buffer) {
471 slot.sortRecords.allocate(frameToken(),
device, BufferUsage::eStorageBuffer, sortBytes);
472 slot.sortedIndices.allocate(frameToken(),
device, BufferUsage::eStorageBuffer, indexBytes);
474 slot.hasSortedIndices =
false;
477 if (slot.initialized && slot.serial >
resource.statsSerial) {
478 const auto* meta =
static_cast<const ParticleMeta*
>(slot.meta.map());
481 resource.stats.instances = meta->instanceCount;
482 resource.stats.spawned = meta->spawned;
483 resource.stats.killed = meta->killed;
484 resource.stats.dropped = meta->dropped;
490 if (!
resource.spawns.empty()) slot.spawns.updateLocal(frameToken(),
resource.spawns);
492 vk::Buffer previousState = slot.scratchState.buffer;
493 vk::Buffer previousMeta = slot.scratchMeta.buffer;
494 vk::AccessFlags previousStateAccess{};
495 vk::AccessFlags previousMetaAccess{};
499 std::array<vk::BufferMemoryBarrier, 2> toTransfer{};
500 toTransfer[0].buffer = slot.state.buffer;
501 toTransfer[0].size = VK_WHOLE_SIZE;
502 toTransfer[0].srcAccessMask = vk::AccessFlagBits::eShaderWrite;
503 toTransfer[0].dstAccessMask = vk::AccessFlagBits::eTransferRead;
504 toTransfer[1].buffer = slot.meta.buffer;
505 toTransfer[1].size = VK_WHOLE_SIZE;
506 toTransfer[1].srcAccessMask = vk::AccessFlagBits::eShaderWrite;
507 toTransfer[1].dstAccessMask = vk::AccessFlagBits::eTransferRead;
508 cb.pipelineBarrier(vk::PipelineStageFlagBits::eComputeShader, vk::PipelineStageFlagBits::eTransfer, {},
509 nullptr, toTransfer,
nullptr);
510 cb.copyBuffer(slot.state.buffer, slot.scratchState.buffer, vk::BufferCopy{0, 0, stateBytes});
511 cb.copyBuffer(slot.meta.buffer, slot.scratchMeta.buffer, vk::BufferCopy{0, 0, metaBytes});
512 previousStateAccess = vk::AccessFlagBits::eTransferWrite;
513 previousMetaAccess = vk::AccessFlagBits::eTransferWrite;
515 previousState =
previous.state.buffer;
516 previousMeta =
previous.meta.buffer;
517 previousStateAccess = vk::AccessFlagBits::eShaderWrite;
518 previousMetaAccess = vk::AccessFlagBits::eShaderWrite;
521 cb.fillBuffer(slot.scratchMeta.buffer, 0, metaBytes, 0);
522 previousMetaAccess = vk::AccessFlagBits::eTransferWrite;
525 cb.fillBuffer(slot.meta.buffer, 0, metaBytes, 0);
527 std::array<vk::BufferMemoryBarrier, 5> toCompute{};
528 const std::array<vk::Buffer, 5> computeBuffers{previousState, previousMeta, slot.spawns.buffer,
529 slot.meta.buffer, slot.state.buffer};
530 const std::array<vk::AccessFlags, 5> sourceAccess{
533 vk::AccessFlagBits::eHostWrite,
534 vk::AccessFlagBits::eTransferWrite,
535 vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite,
537 const std::array<vk::AccessFlags, 5> destinationAccess{
538 vk::AccessFlagBits::eShaderRead, vk::AccessFlagBits::eShaderRead,
539 vk::AccessFlagBits::eShaderRead, vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite,
540 vk::AccessFlagBits::eShaderWrite,
542 for (std::size_t i = 0; i < toCompute.size(); ++i) {
543 toCompute[i].buffer = computeBuffers[i];
544 toCompute[i].size = VK_WHOLE_SIZE;
545 toCompute[i].srcAccessMask = sourceAccess[i];
546 toCompute[i].dstAccessMask = destinationAccess[i];
548 cb.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer | vk::PipelineStageFlagBits::eHost |
549 vk::PipelineStageFlagBits::eComputeShader | vk::PipelineStageFlagBits::eVertexShader,
550 vk::PipelineStageFlagBits::eComputeShader, {},
nullptr, toCompute,
nullptr);
552 if (!slot.computeSet) {
553 vk::DescriptorSetAllocateInfo allocate{};
554 allocate.descriptorPool = descriptorPool;
555 allocate.descriptorSetCount = 1;
556 allocate.pSetLayouts = &gpuParticleComputeSetLayout_;
557 slot.computeSet =
device->allocateDescriptorSets(allocate).front();
559 std::array<vk::DescriptorBufferInfo, 5> infos{};
560 std::array<vk::WriteDescriptorSet, 5> writes{};
561 updateStorageBinding(writes[0], infos[0], slot.computeSet, 0, previousState, stateBytes);
562 updateStorageBinding(writes[1], infos[1], slot.computeSet, 1, slot.state.buffer, stateBytes);
563 updateStorageBinding(writes[2], infos[2], slot.computeSet, 2, slot.spawns.buffer, stateBytes);
564 updateStorageBinding(writes[3], infos[3], slot.computeSet, 3, previousMeta, metaBytes);
565 updateStorageBinding(writes[4], infos[4], slot.computeSet, 4, slot.meta.buffer, metaBytes);
566 device->updateDescriptorSets(writes,
nullptr);
568 ParticleComputePush
push{};
584 push.spawnCount = std::uint32_t(
resource.spawns.size());
586 cb.bindPipeline(vk::PipelineBindPoint::eCompute, gpuParticleComputePipeline_);
587 cb.bindDescriptorSets(vk::PipelineBindPoint::eCompute, gpuParticleComputeLayout_, 0, slot.computeSet,
nullptr);
588 cb.pushConstants(gpuParticleComputeLayout_, vk::ShaderStageFlagBits::eCompute, 0,
sizeof(
push), &
push);
589 cb.dispatch((
resource.capacity + 63u) / 64u, 1, 1);
591 std::array<vk::BufferMemoryBarrier, 2> afterCompute{};
592 afterCompute[0].buffer = slot.state.buffer;
593 afterCompute[0].size = VK_WHOLE_SIZE;
594 afterCompute[0].srcAccessMask = vk::AccessFlagBits::eShaderWrite;
595 afterCompute[0].dstAccessMask = vk::AccessFlagBits::eShaderRead;
596 afterCompute[1].buffer = slot.meta.buffer;
597 afterCompute[1].size = VK_WHOLE_SIZE;
598 afterCompute[1].srcAccessMask = vk::AccessFlagBits::eShaderWrite;
599 afterCompute[1].dstAccessMask =
600 needsSort ? (vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eTransferRead)
601 : vk::AccessFlagBits::eTransferRead;
602 cb.pipelineBarrier(vk::PipelineStageFlagBits::eComputeShader,
603 needsSort ? vk::PipelineStageFlagBits::eComputeShader
604 : (vk::PipelineStageFlagBits::eVertexShader | vk::PipelineStageFlagBits::eTransfer),
605 {},
nullptr, afterCompute,
nullptr);
607 slot.initialized =
true;
608 slot.serial = ++
resource.stats.submittedFrames;
615 if (needsSort && slot.initialized && slot.state.buffer && slot.sortRecords.buffer) {
617 vk::DescriptorSetAllocateInfo allocate{};
618 allocate.descriptorPool = descriptorPool;
619 allocate.descriptorSetCount = 1;
620 allocate.pSetLayouts = &gpuParticleSortSetLayout_;
621 slot.sortSet =
device->allocateDescriptorSets(allocate).front();
623 std::array<vk::DescriptorBufferInfo, 4> sortInfos{};
624 std::array<vk::WriteDescriptorSet, 4> sortWrites{};
625 updateStorageBinding(sortWrites[0], sortInfos[0], slot.sortSet, 0, slot.state.buffer, stateBytes);
626 updateStorageBinding(sortWrites[1], sortInfos[1], slot.sortSet, 1, slot.meta.buffer, metaBytes);
627 updateStorageBinding(sortWrites[2], sortInfos[2], slot.sortSet, 2, slot.sortRecords.buffer, sortBytes);
628 updateStorageBinding(sortWrites[3], sortInfos[3], slot.sortSet, 3, slot.sortedIndices.buffer, indexBytes);
629 device->updateDescriptorSets(sortWrites,
nullptr);
631 ParticleSortPush sortPush{};
632 sortPush.sortMode =
static_cast<std::uint32_t
>(
sortMode);
633 sortPush.paddedCount = paddedSort;
635 sortPush.camera[0] = drawRequest->cameraX;
636 sortPush.camera[1] = drawRequest->cameraY;
639 auto dispatchSort = [&](std::uint32_t
passKind, std::uint32_t k = 0, std::uint32_t j = 0) {
641 sortPush.bitonicK = k;
642 sortPush.bitonicJ = j;
643 cb.bindPipeline(vk::PipelineBindPoint::eCompute, gpuParticleSortPipeline_);
644 cb.bindDescriptorSets(vk::PipelineBindPoint::eCompute, gpuParticleSortLayout_, 0, slot.sortSet,
646 cb.pushConstants(gpuParticleSortLayout_, vk::ShaderStageFlagBits::eCompute, 0,
sizeof(sortPush),
648 const std::uint32_t
groups =
649 passKind == 2u ? (
resource.capacity + 63u) / 64u : (paddedSort + 63u) / 64u;
650 cb.dispatch(
groups, 1, 1);
651 vk::BufferMemoryBarrier barrier{};
652 barrier.buffer =
passKind == 2u ? slot.sortedIndices.buffer : slot.sortRecords.buffer;
653 barrier.size = VK_WHOLE_SIZE;
654 barrier.srcAccessMask = vk::AccessFlagBits::eShaderWrite;
655 barrier.dstAccessMask =
656 passKind == 2u ? vk::AccessFlagBits::eShaderRead
657 : (vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite);
658 cb.pipelineBarrier(vk::PipelineStageFlagBits::eComputeShader,
659 passKind == 2u ? vk::PipelineStageFlagBits::eVertexShader
660 : vk::PipelineStageFlagBits::eComputeShader,
661 {},
nullptr, barrier,
nullptr);
665 for (std::uint32_t k = 2; k <= paddedSort; k <<= 1) {
666 for (std::uint32_t j = k >> 1; j > 0; j >>= 1) dispatchSort(1u, k, j);
669 slot.hasSortedIndices =
true;
672 std::array<vk::BufferMemoryBarrier, 2> toVertex{};
673 toVertex[0].buffer = slot.state.buffer;
674 toVertex[0].size = VK_WHOLE_SIZE;
675 toVertex[0].srcAccessMask = vk::AccessFlagBits::eShaderRead;
676 toVertex[0].dstAccessMask = vk::AccessFlagBits::eShaderRead;
677 toVertex[1].buffer = slot.meta.buffer;
678 toVertex[1].size = VK_WHOLE_SIZE;
679 toVertex[1].srcAccessMask = vk::AccessFlagBits::eShaderRead;
680 toVertex[1].dstAccessMask = vk::AccessFlagBits::eShaderRead;
681 cb.pipelineBarrier(vk::PipelineStageFlagBits::eComputeShader, vk::PipelineStageFlagBits::eVertexShader, {},
682 nullptr, toVertex,
nullptr);
688 vk::BufferMemoryBarrier metaToTransfer{};
689 metaToTransfer.buffer = slot.meta.buffer;
690 metaToTransfer.size = VK_WHOLE_SIZE;
691 metaToTransfer.srcAccessMask = vk::AccessFlagBits::eShaderRead;
692 metaToTransfer.dstAccessMask = vk::AccessFlagBits::eTransferRead;
693 cb.pipelineBarrier(vk::PipelineStageFlagBits::eComputeShader, vk::PipelineStageFlagBits::eTransfer, {},
694 nullptr, metaToTransfer,
nullptr);
696 cb.copyBuffer(slot.meta.buffer, slot.indirect.buffer,
697 vk::BufferCopy{0, 0, sizeof(vk::DrawIndirectCommand)});
698 vk::BufferMemoryBarrier indirectReady{};
699 indirectReady.buffer = slot.indirect.buffer;
700 indirectReady.size =
sizeof(vk::DrawIndirectCommand);
701 indirectReady.srcAccessMask = vk::AccessFlagBits::eTransferWrite;
702 indirectReady.dstAccessMask = vk::AccessFlagBits::eIndirectCommandRead;
703 cb.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eDrawIndirect, {},
704 nullptr, indirectReady,
nullptr);
709void Graphics::drawGpuParticleRequest(vk::CommandBuffer cb,
const GpuParticleDrawRequest&
request) {
711 if (
found == gpuParticles_.end())
return;
713 if (
resource.lastOutputSlot < 0)
return;
715 if (!slot.state.buffer || !slot.meta.buffer)
return;
718 if (!texture || !texture->gpuHandle)
return;
720 if (!depthTexture || !depthTexture->gpuHandle)
return;
721 auto* gpuTexture =
static_cast<GpuTexture*
>(texture->gpuHandle);
722 auto* gpuDepthTexture =
static_cast<GpuTexture*
>(depthTexture->gpuHandle);
724 vk::DescriptorSetAllocateInfo allocate{};
725 allocate.descriptorPool = descriptorPool;
726 allocate.descriptorSetCount = 1;
727 allocate.pSetLayouts = &gpuParticleDrawSetLayout_;
728 slot.drawSet =
device->allocateDescriptorSets(allocate).front();
732 if (!slot.sortedIndices.buffer) {
733 const vk::DeviceSize indexBytes = vk::DeviceSize(
resource.capacity) *
sizeof(std::uint32_t);
734 slot.sortedIndices.allocate(frameToken(),
device, vk::BufferUsageFlagBits::eStorageBuffer, indexBytes);
736 const bool drawDescriptorsDirty = slot.drawTexture != gpuTexture || slot.drawDepthTexture != gpuDepthTexture ||
737 slot.drawSortedIndices != slot.sortedIndices.buffer ||
738 slot.drawMeta != slot.meta.buffer;
739 if (drawDescriptorsDirty) {
740 vk::DescriptorImageInfo
image{};
741 image.sampler = gpuTexture->sampler;
742 image.imageView = gpuTexture->imageView();
743 image.imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal;
744 vk::DescriptorImageInfo depthImage{};
745 depthImage.sampler = gpuDepthTexture->sampler;
746 depthImage.imageView = gpuDepthTexture->imageView();
747 depthImage.imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal;
748 vk::DescriptorBufferInfo
state{};
749 state.buffer = slot.state.buffer;
750 state.range = VK_WHOLE_SIZE;
751 vk::DescriptorBufferInfo
indices{};
752 indices.buffer = slot.sortedIndices.buffer;
754 vk::DescriptorBufferInfo metaInfo{};
755 metaInfo.buffer = slot.meta.buffer;
756 metaInfo.range = VK_WHOLE_SIZE;
757 std::array<vk::WriteDescriptorSet, 5> writes{};
758 writes[0].dstSet = slot.drawSet;
759 writes[0].dstBinding = 0;
760 writes[0].descriptorCount = 1;
761 writes[0].descriptorType = vk::DescriptorType::eCombinedImageSampler;
762 writes[0].pImageInfo = &
image;
763 writes[1].dstSet = slot.drawSet;
764 writes[1].dstBinding = 1;
765 writes[1].descriptorCount = 1;
766 writes[1].descriptorType = vk::DescriptorType::eStorageBuffer;
767 writes[1].pBufferInfo = &
state;
768 writes[2].dstSet = slot.drawSet;
769 writes[2].dstBinding = 2;
770 writes[2].descriptorCount = 1;
771 writes[2].descriptorType = vk::DescriptorType::eCombinedImageSampler;
772 writes[2].pImageInfo = &depthImage;
773 writes[3].dstSet = slot.drawSet;
774 writes[3].dstBinding = 3;
775 writes[3].descriptorCount = 1;
776 writes[3].descriptorType = vk::DescriptorType::eStorageBuffer;
777 writes[3].pBufferInfo = &
indices;
778 writes[4].dstSet = slot.drawSet;
779 writes[4].dstBinding = 4;
780 writes[4].descriptorCount = 1;
781 writes[4].descriptorType = vk::DescriptorType::eStorageBuffer;
782 writes[4].pBufferInfo = &metaInfo;
783 device->updateDescriptorSets(writes,
nullptr);
784 slot.drawTexture = gpuTexture;
785 slot.drawDepthTexture = gpuDepthTexture;
786 slot.drawSortedIndices = slot.sortedIndices.buffer;
787 slot.drawMeta = slot.meta.buffer;
790 vk::Pipeline pipeline = presentComposeActive_ && hdrGpuParticleAlphaPipeline_
791 ? hdrGpuParticleAlphaPipeline_
792 : gpuParticleAlphaPipeline_;
795 pipeline = presentComposeActive_ && hdrGpuParticleAdditivePipeline_
796 ? hdrGpuParticleAdditivePipeline_
797 : gpuParticleAdditivePipeline_;
800 pipeline = presentComposeActive_ && hdrGpuParticlePremultipliedPipeline_
801 ? hdrGpuParticlePremultipliedPipeline_
802 : gpuParticlePremultipliedPipeline_;
805 pipeline = presentComposeActive_ && hdrGpuParticleMultiplyPipeline_
806 ? hdrGpuParticleMultiplyPipeline_
807 : gpuParticleMultiplyPipeline_;
810 pipeline = presentComposeActive_ && hdrGpuParticleOpaquePipeline_
811 ? hdrGpuParticleOpaquePipeline_
812 : gpuParticleOpaquePipeline_;
818 ParticleDrawPush
push{};
819 push.viewportCamera[0] = std::max(
request.draw.viewportWidth, 1.f);
820 push.viewportCamera[1] = std::max(
request.draw.viewportHeight, 1.f);
823 push.cameraParticle[0] =
request.draw.cameraZoom > 0.f ?
request.draw.cameraZoom : 1.f;
824 push.cameraParticle[1] =
request.draw.cameraEnabled ? 1.f : 0.f;
825 push.cameraParticle[2] =
request.draw.particleWidth;
826 push.cameraParticle[3] =
request.draw.particleHeight;
832 std::copy_n(
request.draw.colorStart, 4,
push.colorStart);
833 std::copy_n(
request.draw.colorEnd, 4,
push.colorEnd);
834 push.flipbook[0] = std::uint32_t(std::max(
request.draw.hframes, 1));
835 push.flipbook[1] = std::uint32_t(std::max(
request.draw.vframes, 1));
836 push.flipbook[2] = std::bit_cast<std::uint32_t>(
request.draw.axisRotationRadians);
837 push.flipbook[3] = slot.hasSortedIndices ? 1u : 0
u;
840 push.soft[2] = std::max(
request.draw.softFadeDistance, 1e-5f);
841 push.soft[3] = ribbon ?
request.draw.ribbonMinSegmentLength : 0.f;
843 cb.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline);
844 cb.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, gpuParticleDrawLayout_, 0, slot.drawSet,
nullptr);
845 cb.pushConstants(gpuParticleDrawLayout_, vk::ShaderStageFlagBits::eVertex, 0,
sizeof(
push), &
push);
846 vkCmdDrawIndirect(
static_cast<VkCommandBuffer
>(cb),
static_cast<VkBuffer
>(slot.indirect.buffer), 0, 1,
847 sizeof(VkDrawIndirectCommand));
std::uint64_t localOffset
std::uint32_t vertexCount
const GltfImportRequest & request
std::uint32_t firstVertex
std::uint32_t flipbook[4]
std::uint32_t firstInstance
std::uint32_t paddedCount
std::uint32_t instanceCount
std::vector< std::uint32_t > indices
graphics::Canvas * previous
std::unique_ptr< gpgpu::GpuBuffer > buffer
eve::action::ActionVfxBinding binding
LocalPageCacheEntry slots[ShadowConfig::kLocalSlots]
bool recordingEngine3D_
True while RenderSystem3D is submitting (AO / engine overlays).
void push(bool all)
Pushes .
bool supportsGpuParticles() const override
Supports gpu particles.
GpuParticleHandle createGpuParticleEmitter(std::uint32_t capacity) override
Creates gpu particle emitter.
bool drawGpuParticleEmitter(GpuParticleHandle handle, const GpuParticleDraw &draw) override
Draws gpu particle emitter.
void waitForSharedGpuResources()
Block until all in-flight GPU work (all frames) has completed.
GpuParticleStats getGpuParticleStats(GpuParticleHandle handle) const override
Returns the gpu particle stats.
bool updateGpuParticleEmitter(GpuParticleHandle handle, const GpuParticleUpdate &update, const GpuParticleSpawn *spawns, std::uint32_t spawnCount) override
Updates gpu particle emitter.
void releaseGpuParticleEmitter(GpuParticleHandle handle) override
Release gpu particle emitter.
void resetGpuParticleEmitter(GpuParticleHandle handle) override
Resets gpu particle emitter.
bool canSubmitGpuParticles() const override
Can submit gpu particles.
std::uint64_t GpuParticleHandle
Opaque backend-owned GPU particle emitter handle.
constexpr GpuParticleHandle kInvalidGpuParticleHandle
Invalid GPU particle handle returned when the backend cannot allocate the resource.
@ Ribbon
Connect birth-ordered neighbors into ribbon segments.
eve::BlendMode BlendMode
Compatibility alias for the shared 2D blend mode.
GpuParticleSortMode
Transparent ordering for the resident GPU particle renderer.
WidgetDesc viewport(std::string id, float width, float height)
Viewport.
Parameters for rendering a resident GPU particle emitter.
GPU particle state uploaded only for newly spawned particles.
Delayed non-blocking counters from a resident GPU emitter.
Parameters for one resident GPU simulation step.
vk::DescriptorSet sortSet
vk::Buffer drawSortedIndices
vk::DescriptorSet computeSet
vkb::GenericBuffer scratchState
vkb::GenericBuffer sortedIndices
vkb::GenericBuffer scratchMeta
vkb::GenericBuffer sortRecords
vkb::GenericBuffer spawns
vk::DescriptorSet drawSet
vkb::GenericBuffer indirect
GpuTexture * drawDepthTexture
std::uint64_t statsSerial
std::vector< Slot > slots
std::vector< GpuParticleSpawn > spawns