载入中...
搜索中...
未找到
GraphicsParticles.cpp
浏览该文件的文档.
2
4
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"
9
10#include <algorithm>
11#include <array>
12#include <bit>
13#include <cstring>
14
15namespace eve::graphics::vulkan {
16
17namespace {
18
19struct alignas(16) ParticleMeta {
20 std::uint32_t vertexCount = 0;
21 std::uint32_t instanceCount = 0;
22 std::uint32_t firstVertex = 0;
23 std::uint32_t firstInstance = 0;
24 std::uint32_t alive = 0;
25 std::uint32_t spawned = 0;
26 std::uint32_t killed = 0;
27 std::uint32_t dropped = 0;
28};
29
30static_assert(sizeof(ParticleMeta) == 32, "particle metadata must match GLSL");
31
32struct alignas(16) ParticleComputePush {
33 float stepEmitterX[4]{};
34 float forces0[4]{};
35 float forces1[4]{};
36 float localOffset[2]{};
37 std::uint32_t capacity = 0;
38 std::uint32_t spawnCount = 0;
39};
40
41static_assert(sizeof(ParticleComputePush) == 64, "particle compute push layout must match GLSL");
42
43struct alignas(16) ParticleDrawPush {
44 float viewportCamera[4]{};
45 float cameraParticle[4]{};
46 float sizeMode[4]{};
47 float colorStart[4]{};
48 float colorEnd[4]{};
49 std::uint32_t flipbook[4]{};
50 float soft[4]{};
51};
52
53static_assert(sizeof(ParticleDrawPush) == 112, "particle draw push layout must match GLSL");
54
55struct alignas(16) ParticleSortPush {
56 std::uint32_t passKind = 0;
57 std::uint32_t sortMode = 0;
58 std::uint32_t paddedCount = 0;
59 std::uint32_t bitonicK = 0;
60 std::uint32_t bitonicJ = 0;
61 std::uint32_t _pad0 = 0;
62 float camera[2]{};
63};
64
65static_assert(sizeof(ParticleSortPush) == 32, "particle sort push layout must match GLSL");
66
67struct SortRecord {
68 std::uint32_t key = 0;
69 std::uint32_t index = 0;
70};
71
72static_assert(sizeof(SortRecord) == 8, "sort record must match GLSL");
73
74std::uint32_t nextPow2Count(std::uint32_t value) {
75 if (value <= 1u) return 1u;
76 return std::bit_ceil(value);
77}
78
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"),
85 };
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);
106
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;
118 info.layout = layout;
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");
122 return result.value;
123}
124
125void updateStorageBinding(vk::WriteDescriptorSet& write, vk::DescriptorBufferInfo& info, vk::DescriptorSet set,
126 std::uint32_t binding, vk::Buffer buffer, vk::DeviceSize size) {
127 info.buffer = buffer;
128 info.offset = 0;
129 info.range = size;
130 write.dstSet = set;
131 write.dstBinding = binding;
132 write.descriptorCount = 1;
133 write.descriptorType = vk::DescriptorType::eStorageBuffer;
134 write.pBufferInfo = &info;
135}
136
137} // namespace
138
140 struct Slot {
141 vkb::GenericBuffer state;
142 vkb::GenericBuffer meta;
143 vkb::GenericBuffer indirect;
144 vkb::GenericBuffer spawns;
145 vkb::GenericBuffer scratchState;
146 vkb::GenericBuffer scratchMeta;
147 vkb::GenericBuffer sortRecords;
148 vkb::GenericBuffer sortedIndices;
149 vk::DescriptorSet computeSet{};
150 vk::DescriptorSet drawSet{};
151 vk::DescriptorSet sortSet{};
154 vk::Buffer drawSortedIndices = nullptr;
155 vk::Buffer drawMeta = nullptr;
156 std::uint64_t serial = 0;
157 bool initialized = false;
158 bool hasSortedIndices = false;
159 };
160
161 std::uint32_t capacity = 0;
162 std::vector<Slot> slots;
164 bool pendingUpdate = false;
165 bool pendingReset = true;
167 std::vector<GpuParticleSpawn> spawns;
169 std::uint64_t statsSerial = 0;
170};
171
173 if (!supportsGpuParticles() || !gpuParticleComputePipeline_ || !gpuParticleAlphaPipeline_) return false;
174 if (activeCanvas != nullptr) return false;
175 return !(swapchainPassOpen && !sceneColorPassOpen);
176}
177
180 auto* resource = new GpuParticleResource();
181 resource->capacity = capacity;
182 const GpuParticleHandle handle = nextGpuParticleHandle_++;
183 gpuParticles_.emplace(handle, resource);
184 return handle;
185}
186
188 auto found = gpuParticles_.find(handle);
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);
196 }
197 if (!sets.empty() && descriptorPool) device->freeDescriptorSets(descriptorPool, sets);
198 delete found->second;
199 gpuParticles_.erase(found);
200}
201
203 auto found = gpuParticles_.find(handle);
204 if (found == gpuParticles_.end()) return;
205 auto& resource = *found->second;
206 resource.pendingReset = true;
207 resource.pendingUpdate = true;
208 resource.update = {};
209 resource.spawns.clear();
210 resource.stats = {};
211}
212
214 const GpuParticleSpawn* spawns, std::uint32_t spawnCount) {
215 if (!canSubmitGpuParticles()) return false;
216 auto found = gpuParticles_.find(handle);
217 if (found == gpuParticles_.end()) return false;
218 auto& resource = *found->second;
219 resource.update = update;
220 const std::uint32_t accepted = std::min(spawnCount, resource.capacity);
221 if (accepted > 0 && spawns)
222 resource.spawns.assign(spawns, spawns + accepted);
223 else
224 resource.spawns.clear();
225 resource.pendingUpdate = true;
226 return true;
227}
228
230 if (!canSubmitGpuParticles()) return false;
231 if (gpuParticles_.find(handle) == gpuParticles_.end()) return false;
232 const std::uint32_t index = std::uint32_t(gpuParticleDraws_.size());
233 gpuParticleDraws_.push_back({handle, draw});
234 auto& spans = recordingEngine3D_ ? engine3DSpans : overlaySpans;
235 spans.push_back({OverlayKind::GpuParticles, index, 0, 0});
236 return true;
237}
238
240 auto found = gpuParticles_.find(handle);
241 return found == gpuParticles_.end() ? GpuParticleStats{} : found->second->stats;
242}
243
244void Graphics::createGpuParticlePipelines() {
245 if (gpuParticleComputePipeline_ || !descriptorPool || !renderpass) return;
246
247 std::array<vk::DescriptorSetLayoutBinding, 5> computeBindings{};
248 for (std::uint32_t binding = 0; binding < computeBindings.size(); ++binding) {
249 computeBindings[binding] = {binding, vk::DescriptorType::eStorageBuffer, 1, vk::ShaderStageFlagBits::eCompute};
250 }
251 gpuParticleComputeSetLayout_ =
252 device->createDescriptorSetLayout(vk::DescriptorSetLayoutCreateInfo({}, computeBindings));
253
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),
262 };
263 gpuParticleDrawSetLayout_ = device->createDescriptorSetLayout(vk::DescriptorSetLayoutCreateInfo({}, drawBindings));
264
265 std::array<vk::DescriptorSetLayoutBinding, 4> sortBindings{};
266 for (std::uint32_t binding = 0; binding < sortBindings.size(); ++binding) {
267 sortBindings[binding] = {binding, vk::DescriptorType::eStorageBuffer, 1, vk::ShaderStageFlagBits::eCompute};
268 }
269 gpuParticleSortSetLayout_ =
270 device->createDescriptorSetLayout(vk::DescriptorSetLayoutCreateInfo({}, sortBindings));
271
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);
278
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{};
287 info.stage = stage;
288 info.layout = layout;
289 auto result = device->createComputePipeline({}, info);
290 device->destroyShaderModule(module);
291 return result.result == vk::Result::eSuccess ? result.value : vk::Pipeline{};
292 };
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_);
297
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_ =
305 createParticleDrawPipeline(device, renderpass, gpuParticleDrawLayout_, vert, frag, BlendMode::Alpha);
306 gpuParticleAdditivePipeline_ =
307 createParticleDrawPipeline(device, renderpass, gpuParticleDrawLayout_, vert, frag, BlendMode::Additive);
308 gpuParticlePremultipliedPipeline_ =
309 createParticleDrawPipeline(device, renderpass, gpuParticleDrawLayout_, vert, frag, BlendMode::Premultiplied);
310 gpuParticleMultiplyPipeline_ =
311 createParticleDrawPipeline(device, renderpass, gpuParticleDrawLayout_, vert, frag, BlendMode::Multiply);
312 gpuParticleOpaquePipeline_ =
313 createParticleDrawPipeline(device, renderpass, gpuParticleDrawLayout_, vert, frag, BlendMode::Opaque);
314 device->destroyShaderModule(vert);
315 device->destroyShaderModule(frag);
316}
317
318void Graphics::rebuildGpuParticleDrawPipelines(const vkb::BuiltRenderPass &target) {
319 if (!gpuParticleDrawLayout_ || !target) return;
320 auto destroyPipe = [&](vk::Pipeline &p) {
321 if (p) {
322 device->destroyPipeline(p);
323 p = nullptr;
324 }
325 };
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_ =
338 createParticleDrawPipeline(device, target, gpuParticleDrawLayout_, vert, frag, BlendMode::Alpha);
339 gpuParticleAdditivePipeline_ =
340 createParticleDrawPipeline(device, target, gpuParticleDrawLayout_, vert, frag, BlendMode::Additive);
341 gpuParticlePremultipliedPipeline_ =
342 createParticleDrawPipeline(device, target, gpuParticleDrawLayout_, vert, frag, BlendMode::Premultiplied);
343 gpuParticleMultiplyPipeline_ =
344 createParticleDrawPipeline(device, target, gpuParticleDrawLayout_, vert, frag, BlendMode::Multiply);
345 gpuParticleOpaquePipeline_ =
346 createParticleDrawPipeline(device, target, gpuParticleDrawLayout_, vert, frag, BlendMode::Opaque);
347 device->destroyShaderModule(vert);
348 device->destroyShaderModule(frag);
349}
350
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(
360 device, hdrOffscreenRenderPass, gpuParticleDrawLayout_, vert, frag, BlendMode::Alpha);
361 hdrGpuParticleAdditivePipeline_ = createParticleDrawPipeline(
362 device, hdrOffscreenRenderPass, gpuParticleDrawLayout_, vert, frag, BlendMode::Additive);
363 hdrGpuParticlePremultipliedPipeline_ = createParticleDrawPipeline(
364 device, hdrOffscreenRenderPass, gpuParticleDrawLayout_, vert, frag, BlendMode::Premultiplied);
365 hdrGpuParticleMultiplyPipeline_ = createParticleDrawPipeline(
366 device, hdrOffscreenRenderPass, gpuParticleDrawLayout_, vert, frag, BlendMode::Multiply);
367 hdrGpuParticleOpaquePipeline_ = createParticleDrawPipeline(
368 device, hdrOffscreenRenderPass, gpuParticleDrawLayout_, vert, frag, BlendMode::Opaque);
369 device->destroyShaderModule(vert);
370 device->destroyShaderModule(frag);
371}
372
373void Graphics::destroyGpuParticleResources() {
374 gpuParticleDraws_.clear();
375 for (auto& [handle, resource] : gpuParticles_) {
376 (void)handle;
377 std::vector<vk::DescriptorSet> sets;
378 for (const auto& slot : resource->slots) {
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);
382 }
383 if (!sets.empty() && descriptorPool) device->freeDescriptorSets(descriptorPool, sets);
384 delete resource;
385 }
386 gpuParticles_.clear();
387 auto destroyPipeline = [&](vk::Pipeline& pipeline) {
388 if (pipeline) device->destroyPipeline(pipeline);
389 pipeline = nullptr;
390 };
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;
415}
416
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;
423
424 auto latestDraw = [&](GpuParticleHandle handle) -> const GpuParticleDraw* {
425 for (auto it = gpuParticleDraws_.rbegin(); it != gpuParticleDraws_.rend(); ++it) {
426 if (it->handle == handle) return &it->draw;
427 }
428 return nullptr;
429 };
430
431 for (auto& [handle, owned] : gpuParticles_) {
432 auto& resource = *owned;
433 const GpuParticleDraw* drawRequest = latestDraw(handle);
434 GpuParticleSortMode sortMode = drawRequest ? drawRequest->sortMode : GpuParticleSortMode::None;
435 if (drawRequest && drawRequest->facing == GpuParticleFacingMode::Ribbon)
438 !(drawRequest && drawRequest->cameraEnabled))
440 const bool needsSort = sortMode != GpuParticleSortMode::None && gpuParticleSortPipeline_;
441 if (!resource.pendingUpdate && !resource.pendingReset && !needsSort) continue;
442 if (resource.slots.size() < requiredSlots) resource.slots.resize(requiredSlots);
443 const bool runUpdate = resource.pendingUpdate || resource.pendingReset;
444 auto& slot = runUpdate ? resource.slots[frameSlot % resource.slots.size()]
445 : resource.slots[std::size_t(std::max(resource.lastOutputSlot, 0))];
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);
452
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);
469 }
470 if (!slot.sortRecords.buffer) {
471 slot.sortRecords.allocate(frameToken(), device, BufferUsage::eStorageBuffer, sortBytes);
472 slot.sortedIndices.allocate(frameToken(), device, BufferUsage::eStorageBuffer, indexBytes);
473 }
474 slot.hasSortedIndices = false;
475
476 if (runUpdate) {
477 if (slot.initialized && slot.serial > resource.statsSerial) {
478 const auto* meta = static_cast<const ParticleMeta*>(slot.meta.map());
479 if (meta) {
480 resource.stats.alive = meta->alive;
481 resource.stats.instances = meta->instanceCount;
482 resource.stats.spawned = meta->spawned;
483 resource.stats.killed = meta->killed;
484 resource.stats.dropped = meta->dropped;
485 slot.meta.unmap();
486 resource.statsSerial = slot.serial;
487 }
488 }
489
490 if (!resource.spawns.empty()) slot.spawns.updateLocal(frameToken(), resource.spawns);
491
492 vk::Buffer previousState = slot.scratchState.buffer;
493 vk::Buffer previousMeta = slot.scratchMeta.buffer;
494 vk::AccessFlags previousStateAccess{};
495 vk::AccessFlags previousMetaAccess{};
496 if (!resource.pendingReset && resource.lastOutputSlot >= 0) {
497 auto& previous = resource.slots[std::size_t(resource.lastOutputSlot)];
498 if (&previous == &slot) {
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;
514 } else {
515 previousState = previous.state.buffer;
516 previousMeta = previous.meta.buffer;
517 previousStateAccess = vk::AccessFlagBits::eShaderWrite;
518 previousMetaAccess = vk::AccessFlagBits::eShaderWrite;
519 }
520 } else {
521 cb.fillBuffer(slot.scratchMeta.buffer, 0, metaBytes, 0);
522 previousMetaAccess = vk::AccessFlagBits::eTransferWrite;
523 }
524
525 cb.fillBuffer(slot.meta.buffer, 0, metaBytes, 0);
526
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{
531 previousStateAccess,
532 previousMetaAccess,
533 vk::AccessFlagBits::eHostWrite,
534 vk::AccessFlagBits::eTransferWrite,
535 vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite,
536 };
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,
541 };
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];
547 }
548 cb.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer | vk::PipelineStageFlagBits::eHost |
549 vk::PipelineStageFlagBits::eComputeShader | vk::PipelineStageFlagBits::eVertexShader,
550 vk::PipelineStageFlagBits::eComputeShader, {}, nullptr, toCompute, nullptr);
551
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();
558 }
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);
567
568 ParticleComputePush push{};
569 push.stepEmitterX[0] = resource.update.dt;
570 push.stepEmitterX[1] = resource.update.emitterX;
571 push.stepEmitterX[2] = resource.update.emitterY;
572 push.stepEmitterX[3] = resource.update.gravityX;
573 push.forces0[0] = resource.update.gravityY;
574 push.forces0[1] = resource.update.damping;
575 push.forces0[2] = resource.update.velocityLimit;
576 push.forces0[3] = resource.update.noiseStrength;
577 push.forces1[0] = resource.update.noiseFrequency;
578 push.forces1[1] = resource.update.noiseSpeed;
579 push.forces1[2] = resource.update.time;
580 push.forces1[3] = resource.update.frameRate;
581 push.localOffset[0] = resource.update.localOffsetX;
582 push.localOffset[1] = resource.update.localOffsetY;
583 push.capacity = resource.capacity;
584 push.spawnCount = std::uint32_t(resource.spawns.size());
585
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);
590
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);
606
607 slot.initialized = true;
608 slot.serial = ++resource.stats.submittedFrames;
609 resource.lastOutputSlot = int(frameSlot % resource.slots.size());
610 resource.pendingUpdate = false;
611 resource.pendingReset = false;
612 resource.spawns.clear();
613 } // runUpdate
614
615 if (needsSort && slot.initialized && slot.state.buffer && slot.sortRecords.buffer) {
616 if (!slot.sortSet) {
617 vk::DescriptorSetAllocateInfo allocate{};
618 allocate.descriptorPool = descriptorPool;
619 allocate.descriptorSetCount = 1;
620 allocate.pSetLayouts = &gpuParticleSortSetLayout_;
621 slot.sortSet = device->allocateDescriptorSets(allocate).front();
622 }
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);
630
631 ParticleSortPush sortPush{};
632 sortPush.sortMode = static_cast<std::uint32_t>(sortMode);
633 sortPush.paddedCount = paddedSort;
634 if (drawRequest) {
635 sortPush.camera[0] = drawRequest->cameraX;
636 sortPush.camera[1] = drawRequest->cameraY;
637 }
638
639 auto dispatchSort = [&](std::uint32_t passKind, std::uint32_t k = 0, std::uint32_t j = 0) {
640 sortPush.passKind = passKind;
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,
645 nullptr);
646 cb.pushConstants(gpuParticleSortLayout_, vk::ShaderStageFlagBits::eCompute, 0, sizeof(sortPush),
647 &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);
662 };
663
664 dispatchSort(0u);
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);
667 }
668 dispatchSort(2u);
669 slot.hasSortedIndices = true;
670
671 // Particle state/meta were read by the sort passes; make them visible to the vertex stage.
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);
683 }
684
685 if (runUpdate) {
686 if (needsSort) {
687 // Sort only reads meta; re-acquire TransferRead for the indirect copy.
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);
695 }
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);
705 }
706 }
707}
708
709void Graphics::drawGpuParticleRequest(vk::CommandBuffer cb, const GpuParticleDrawRequest& request) {
710 auto found = gpuParticles_.find(request.handle);
711 if (found == gpuParticles_.end()) return;
712 auto& resource = *found->second;
713 if (resource.lastOutputSlot < 0) return;
714 auto& slot = resource.slots[std::size_t(resource.lastOutputSlot)];
715 if (!slot.state.buffer || !slot.meta.buffer) return;
716
717 Texture* texture = request.draw.texture ? request.draw.texture : whiteTexture;
718 if (!texture || !texture->gpuHandle) return;
719 Texture* depthTexture = request.draw.sceneDepth ? request.draw.sceneDepth : whiteTexture;
720 if (!depthTexture || !depthTexture->gpuHandle) return;
721 auto* gpuTexture = static_cast<GpuTexture*>(texture->gpuHandle);
722 auto* gpuDepthTexture = static_cast<GpuTexture*>(depthTexture->gpuHandle);
723 if (!slot.drawSet) {
724 vk::DescriptorSetAllocateInfo allocate{};
725 allocate.descriptorPool = descriptorPool;
726 allocate.descriptorSetCount = 1;
727 allocate.pSetLayouts = &gpuParticleDrawSetLayout_;
728 slot.drawSet = device->allocateDescriptorSets(allocate).front();
729 }
730 // Binding 3 is always present in the vertex layout; bind a real buffer even when
731 // sorting is off (flipbook.w gates the indirection).
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);
735 }
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;
753 indices.range = VK_WHOLE_SIZE;
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;
788 }
789
790 vk::Pipeline pipeline = presentComposeActive_ && hdrGpuParticleAlphaPipeline_
791 ? hdrGpuParticleAlphaPipeline_
792 : gpuParticleAlphaPipeline_;
793 switch (request.draw.blend) {
795 pipeline = presentComposeActive_ && hdrGpuParticleAdditivePipeline_
796 ? hdrGpuParticleAdditivePipeline_
797 : gpuParticleAdditivePipeline_;
798 break;
800 pipeline = presentComposeActive_ && hdrGpuParticlePremultipliedPipeline_
801 ? hdrGpuParticlePremultipliedPipeline_
802 : gpuParticlePremultipliedPipeline_;
803 break;
805 pipeline = presentComposeActive_ && hdrGpuParticleMultiplyPipeline_
806 ? hdrGpuParticleMultiplyPipeline_
807 : gpuParticleMultiplyPipeline_;
808 break;
810 pipeline = presentComposeActive_ && hdrGpuParticleOpaquePipeline_
811 ? hdrGpuParticleOpaquePipeline_
812 : gpuParticleOpaquePipeline_;
813 break;
814 case BlendMode::Alpha:
815 default: break;
816 }
817
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);
821 push.viewportCamera[2] = request.draw.cameraX;
822 push.viewportCamera[3] = request.draw.cameraY;
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;
827 const bool ribbon = request.draw.facing == GpuParticleFacingMode::Ribbon;
828 push.sizeMode[0] = request.draw.sizeStart;
829 push.sizeMode[1] = request.draw.sizeEnd;
830 push.sizeMode[2] = ribbon ? request.draw.ribbonWidth : request.draw.stretchFactor;
831 push.sizeMode[3] = float(request.draw.facing);
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 : 0u;
838 push.soft[0] = request.draw.softParticles && request.draw.sceneDepth ? 1.f : 0.f;
839 push.soft[1] = request.draw.particleDepth;
840 push.soft[2] = std::max(request.draw.softFadeDistance, 1e-5f);
841 push.soft[3] = ribbon ? request.draw.ribbonMinSegmentLength : 0.f;
842
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));
848}
849
850} // namespace eve::graphics::vulkan
LogicalId target
double value
ActiveSource owned
glm::vec4 p[6]
std::uint64_t localOffset
std::string layout
std::uint32_t vertexCount
const GltfImportRequest & request
vkb::Device & device
vk::ShaderModule vert
vk::ShaderModule frag
std::uint32_t bitonicJ
std::uint32_t firstVertex
float cameraParticle[4]
std::uint32_t killed
float camera[2]
std::uint32_t alive
std::uint32_t _pad0
float forces0[4]
std::uint32_t passKind
float forces1[4]
std::uint32_t flipbook[4]
std::uint32_t spawnCount
std::uint32_t firstInstance
float soft[4]
std::uint32_t paddedCount
std::uint32_t instanceCount
float colorStart[4]
std::uint32_t dropped
std::uint32_t sortMode
std::uint32_t spawned
float stepEmitterX[4]
std::uint32_t capacity
float viewportCamera[4]
std::uint32_t bitonicK
float sizeMode[4]
float colorEnd[4]
std::uint32_t key
vk::UniqueImage image
float u
Definition Grass.cpp:233
std::vector< std::uint32_t > indices
float blend
MeshVfxBatchedDraw draw
graphics::Canvas * previous
uint32_t groups
Definition OnnxGpgpu.cpp:39
std::unique_ptr< gpgpu::GpuBuffer > buffer
Definition OnnxGpgpu.cpp:26
eve::action::ActionVfxBinding binding
PrimitiveHandle handle
bool found
std::string resource
LocalPageCacheEntry slots[ShadowConfig::kLocalSlots]
std::uint32_t count
float size
Definition TreeMesh.cpp:156
uint32_t index
bool recordingEngine3D_
True while RenderSystem3D is submitting (AO / engine overlays).
Definition Graphics.h:2249
void push(bool all)
Pushes .
bool supportsGpuParticles() const override
Supports gpu particles.
Definition Graphics.h:478
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.
Definition Graphics.cpp:885
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.
Definition BlendMode.h:8
GpuParticleSortMode
Transparent ordering for the resident GPU particle renderer.
WidgetDesc viewport(std::string id, float width, float height)
Viewport.
Definition Widget.cpp:443
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.
GpuTexture public API.
Definition Graphics.h:257
glm::uvec4 info