MayaFlux 1.0.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
GpuResourceManager.cpp
Go to the documentation of this file.
2
5
7
8namespace MayaFlux::Yantra {
9
10//==============================================================================
11// Buffer slot
12//==============================================================================
13
15 vk::Buffer buffer;
16 vk::DeviceMemory memory;
17 void* mapped_ptr { nullptr };
18 size_t allocated_bytes {};
19};
20
21//==============================================================================
22// PIMPL — one per pipeline unit
23//==============================================================================
24
26 std::vector<VulkanBufferSlot> buffers;
27 std::unordered_map<std::string, VulkanBufferSlot> shared_buffers;
28};
29
30//==============================================================================
31// File-local helpers
32//==============================================================================
33
34namespace {
35
36 std::optional<uint32_t> try_memory_type(vk::PhysicalDevice phys,
37 uint32_t type_filter,
38 vk::MemoryPropertyFlags props)
39 {
40 auto mem_props = phys.getMemoryProperties();
41 for (uint32_t i = 0; i < mem_props.memoryTypeCount; ++i) {
42 if ((type_filter & (1U << i))
43 && (mem_props.memoryTypes[i].propertyFlags & props) == props) {
44 return i;
45 }
46 }
47 return std::nullopt;
48 }
49
50 uint32_t find_memory_type(vk::PhysicalDevice phys,
51 uint32_t type_filter,
52 vk::MemoryPropertyFlags props,
53 vk::MemoryPropertyFlags fallback_props = {})
54 {
55 if (const auto type = try_memory_type(phys, type_filter, props))
56 return *type;
57 if (fallback_props) {
58 if (const auto type = try_memory_type(phys, type_filter, fallback_props))
59 return *type;
60 }
61 error<std::runtime_error>(
64 std::source_location::current(),
65 "GpuResourceManager: no suitable memory type found");
66 }
67
68 void free_slot(vk::Device device, VulkanBufferSlot& slot)
69 {
70 if (slot.mapped_ptr) {
71 device.unmapMemory(slot.memory);
72 slot.mapped_ptr = nullptr;
73 }
74 if (slot.buffer) {
75 device.destroyBuffer(slot.buffer);
76 slot.buffer = vk::Buffer {};
77 }
78 if (slot.memory) {
79 device.freeMemory(slot.memory);
80 slot.memory = vk::DeviceMemory {};
81 }
82 slot.allocated_bytes = 0;
83 }
84
85 /**
86 * @brief Allocate and map a host visible buffer slot.
87 *
88 * By default the memory is host cached system memory, which suits buffers
89 * the host reads back. With prefer_device_local the slot first tries
90 * device local, host visible, host coherent memory (the BAR heap), so a
91 * buffer that only shaders touch is read and written at device speed
92 * instead of across the bus. When no such memory type exists, or the heap
93 * is full, the default memory is used, so the request never fails where
94 * the default would succeed. Host reads of device local memory are
95 * uncached, so it is meant for small or write only host access.
96 */
97 void allocate_slot(vk::Device device, vk::PhysicalDevice phys,
98 VulkanBufferSlot& slot, size_t byte_size,
99 vk::BufferUsageFlags extra_usage = vk::BufferUsageFlagBits::eStorageBuffer,
100 bool prefer_device_local = false)
101 {
102 free_slot(device, slot);
103
104 vk::BufferCreateInfo bi;
105 bi.size = byte_size;
106 bi.usage = extra_usage;
107 bi.sharingMode = vk::SharingMode::eExclusive;
108 slot.buffer = device.createBuffer(bi);
109
110 auto req = device.getBufferMemoryRequirements(slot.buffer);
111
112 vk::MemoryAllocateInfo ai;
113 ai.allocationSize = req.size;
114
115 if (prefer_device_local) {
116 const auto resident = try_memory_type(phys, req.memoryTypeBits,
117 vk::MemoryPropertyFlagBits::eDeviceLocal
118 | vk::MemoryPropertyFlagBits::eHostVisible
119 | vk::MemoryPropertyFlagBits::eHostCoherent);
120 if (resident) {
121 ai.memoryTypeIndex = *resident;
122 try {
123 slot.memory = device.allocateMemory(ai);
124 } catch (const vk::SystemError&) {
125 slot.memory = vk::DeviceMemory {};
126 }
127 }
128 }
129
130 if (!slot.memory) {
131 ai.memoryTypeIndex = find_memory_type(phys, req.memoryTypeBits,
132 vk::MemoryPropertyFlagBits::eHostVisible
133 | vk::MemoryPropertyFlagBits::eHostCoherent
134 | vk::MemoryPropertyFlagBits::eHostCached,
135 vk::MemoryPropertyFlagBits::eHostVisible
136 | vk::MemoryPropertyFlagBits::eHostCoherent);
137 slot.memory = device.allocateMemory(ai);
138 }
139
140 device.bindBufferMemory(slot.buffer, slot.memory, 0);
141 slot.mapped_ptr = device.mapMemory(slot.memory, 0, VK_WHOLE_SIZE);
142 slot.allocated_bytes = byte_size;
143 }
144
145 [[nodiscard]] vk::DescriptorType element_type_to_vk(GpuBufferBinding::ElementType et)
146 {
147 switch (et) {
149 return vk::DescriptorType::eStorageImage;
151 return vk::DescriptorType::eCombinedImageSampler;
152 default:
153 return vk::DescriptorType::eStorageBuffer;
154 }
155 }
156
157} // anonymous namespace
158
160 std::map<std::pair<uint32_t, size_t>, VulkanBufferSlot> slots;
161};
162
163//==============================================================================
164// Lifecycle
165//==============================================================================
166
168 : m_shared(std::make_unique<SharedBuffers>())
169{
170}
171
176
178{
179 auto it = m_units.find(key);
180 if (it == m_units.end()) {
181 error<std::runtime_error>(
184 std::source_location::current(),
185 "GpuResourceManager: no unit for key '{}' — call initialise() first", key);
186 }
187 return *it->second;
188}
189
191{
192 auto it = m_units.find(key);
193 return it == m_units.end() ? nullptr : it->second.get();
194}
195
196bool GpuResourceManager::is_ready(const std::string& key) const
197{
198 const auto* unit = find_unit(key);
199 return unit && unit->ready;
200}
201
202bool GpuResourceManager::initialise(const std::string& key,
203 const GpuComputeConfig& config,
204 const std::vector<GpuBufferBinding>& bindings)
205{
206 if (const auto* existing = find_unit(key); existing && existing->ready)
207 return true;
208
209 auto& foundry = Portal::Graphics::get_shader_foundry();
210 auto& compute_press = Portal::Graphics::get_compute_press();
211
212 auto unit = std::make_unique<PipelineUnit>();
213
215 unit->shader_id = config.shader_id;
216 } else {
217 unit->shader_id = foundry.load_shader(config.shader_path);
218 }
219
220 if (unit->shader_id == Portal::Graphics::INVALID_SHADER) {
222 "GpuResourceManager: failed to load shader '{}' for key '{}'",
223 config.shader_path.empty() ? "<generated>" : config.shader_path, key);
224 return false;
225 }
226
227 std::map<uint32_t, std::vector<Portal::Graphics::DescriptorBindingInfo>> by_set;
228 for (const auto& b : bindings) {
229 const auto et = b.element_type;
232 if (is_image) {
233 by_set[b.set].push_back({
234 .set = b.set,
235 .binding = b.binding,
236 .type = element_type_to_vk(et),
237 });
238 }
239 }
240
241 for (const auto& b : bindings) {
242 const auto et = b.element_type;
245 if (!is_image) {
246 by_set[b.set].push_back({
247 .set = b.set,
248 .binding = b.binding,
249 .type = vk::DescriptorType::eStorageBuffer,
250 });
251 }
252 }
253
254 std::vector<std::vector<Portal::Graphics::DescriptorBindingInfo>> descriptor_sets;
255 descriptor_sets.reserve(by_set.size());
256 for (auto& [set_idx, set_bindings] : by_set)
257 descriptor_sets.push_back(std::move(set_bindings));
258
259 unit->pipeline_id = compute_press.create_pipeline(
260 unit->shader_id, descriptor_sets, config.push_constant_size);
261
262 if (unit->pipeline_id == Portal::Graphics::INVALID_COMPUTE_PIPELINE) {
264 "GpuResourceManager: failed to create pipeline for key '{}'", key);
265 foundry.destroy_shader(unit->shader_id);
266 return false;
267 }
268
269 unit->descriptor_set_ids = compute_press.allocate_pipeline_descriptors(unit->pipeline_id);
270 if (unit->descriptor_set_ids.empty()) {
272 "GpuResourceManager: failed to allocate descriptor sets for key '{}'", key);
273 compute_press.destroy_pipeline(unit->pipeline_id);
274 foundry.destroy_shader(unit->shader_id);
275 return false;
276 }
277
278 unit->impl = std::make_unique<GpuResourceManagerImpl>();
279 size_t max_binding = 0;
280 for (const auto& b : bindings)
281 max_binding = std::max(max_binding, static_cast<size_t>(b.binding));
282 const size_t capacity = bindings.empty() ? 0 : max_binding + 1;
283 unit->impl->buffers.resize(capacity);
284 unit->buffer_slots.resize(capacity);
285 unit->image_slots.resize(capacity);
286 unit->ready = true;
287
288 m_units[key] = std::move(unit);
289 return true;
290}
291
292void GpuResourceManager::release(const std::string& key)
293{
294 auto it = m_units.find(key);
295 if (it == m_units.end())
296 return;
297
298 auto& unit = *it->second;
299 if (!unit.ready) {
300 m_units.erase(it);
301 return;
302 }
303
304 auto& foundry = Portal::Graphics::get_shader_foundry();
305 auto& compute_press = Portal::Graphics::get_compute_press();
306 auto device = foundry.get_device();
307
308 if (unit.impl) {
309 for (auto& slot : unit.impl->buffers)
310 free_slot(device, slot);
311 }
312 unit.impl.reset();
313 unit.buffer_slots.clear();
314 unit.image_slots.clear();
315
316 if (unit.pipeline_id != Portal::Graphics::INVALID_COMPUTE_PIPELINE)
317 compute_press.destroy_pipeline(unit.pipeline_id);
318
319 if (unit.shader_id != Portal::Graphics::INVALID_SHADER)
320 foundry.destroy_shader(unit.shader_id);
321
322 m_units.erase(it);
323}
324
326{
327 std::vector<std::string> keys;
328 keys.reserve(m_units.size());
329 for (const auto& [k, v] : m_units)
330 keys.push_back(k);
331 for (const auto& k : keys)
332 release(k);
333}
334
335//==============================================================================
336// Buffer operations
337//==============================================================================
338
339void GpuResourceManager::ensure_buffer(const std::string& key, size_t index,
340 size_t required_bytes, Portal::Graphics::BufferUsageHint usage_hint)
341{
342 auto& unit = unit_for(key);
343 auto& vk_slot = unit.impl->buffers[index];
344 if (vk_slot.allocated_bytes >= required_bytes) {
345 return;
346 }
347
348 auto& foundry = Portal::Graphics::get_shader_foundry();
349 allocate_slot(foundry.get_device(), foundry.get_physical_device(),
350 vk_slot, required_bytes, Portal::Graphics::to_buffer_usage_flags(usage_hint));
351
352 unit.buffer_slots[index].allocated_bytes = required_bytes;
353}
354
355void GpuResourceManager::upload(const std::string& key, size_t index, const float* data, size_t byte_size)
356{
357 auto& vk_slot = unit_for(key).impl->buffers[index];
358 std::memcpy(vk_slot.mapped_ptr, data, byte_size);
359}
360
361void GpuResourceManager::upload_raw(const std::string& key, size_t index, const uint8_t* data, size_t byte_size)
362{
363 auto& vk_slot = unit_for(key).impl->buffers[index];
364 std::memcpy(vk_slot.mapped_ptr, data, byte_size);
365}
366
367void GpuResourceManager::download(const std::string& key, size_t index, float* dest, size_t byte_size)
368{
369 auto& vk_slot = unit_for(key).impl->buffers[index];
370 std::memcpy(dest, vk_slot.mapped_ptr, byte_size);
371}
372
373void GpuResourceManager::bind_descriptor(const std::string& key, size_t index, const GpuBufferBinding& spec)
374{
375 auto& unit = unit_for(key);
376 auto& foundry = Portal::Graphics::get_shader_foundry();
377 auto& vk_slot = unit.impl->buffers[index];
378
379 foundry.update_descriptor_buffer(
380 unit.descriptor_set_ids[spec.set],
381 spec.binding,
382 vk::DescriptorType::eStorageBuffer,
383 vk_slot.buffer, 0, vk_slot.allocated_bytes);
384}
385
386void GpuResourceManager::bind_descriptors_batch(const std::string& key, const std::vector<GpuBufferBinding>& bindings)
387{
388 auto& unit = unit_for(key);
389 auto& foundry = Portal::Graphics::get_shader_foundry();
390
391 std::vector<Portal::Graphics::DescriptorBufferWrite> writes;
392 writes.reserve(bindings.size());
393
394 for (const auto& b : bindings) {
395 const auto et = b.element_type;
398 continue;
399 }
400
401 const auto shared_it = m_shared->slots.find({ b.set, static_cast<size_t>(b.binding) });
402 vk::Buffer buffer;
403 size_t allocated_bytes = 0;
404 if (shared_it != m_shared->slots.end()) {
405 buffer = shared_it->second.buffer;
406 allocated_bytes = shared_it->second.allocated_bytes;
407 } else {
408 if (static_cast<size_t>(b.binding) >= unit.impl->buffers.size()) {
409 continue;
410 }
411 const auto& vk_slot = unit.impl->buffers[b.binding];
412 buffer = vk_slot.buffer;
413 allocated_bytes = vk_slot.allocated_bytes;
414 }
415
416 if (!buffer)
417 continue;
418
419 writes.push_back({
420 .descriptor_set_id = unit.descriptor_set_ids[b.set],
421 .binding = b.binding,
422 .type = vk::DescriptorType::eStorageBuffer,
423 .buffer = buffer,
424 .offset = 0,
425 .size = allocated_bytes,
426 });
427 }
428
429 foundry.update_descriptor_buffers(writes);
430}
431
432size_t GpuResourceManager::buffer_allocated_bytes(const std::string& key, size_t index) const
433{
434 return find_unit(key)->buffer_slots[index].allocated_bytes;
435}
436
437void GpuResourceManager::ensure_shared_buffer(uint32_t set, size_t binding_index, size_t element_count,
440{
441 const size_t width = Portal::Graphics::element_type_bytes(element_type);
442 if (width == 0) {
443 error<std::runtime_error>(
446 std::source_location::current(),
447 "GpuResourceManager: ensure_shared_buffer requires a sized element_type");
448 }
449
450 auto& slot = m_shared->slots[{ set, binding_index }];
451 const size_t required_bytes = element_count * width;
452 if (slot.allocated_bytes >= required_bytes)
453 return;
454
455 auto& foundry = Portal::Graphics::get_shader_foundry();
456 const bool gpu_resident = usage_hint == Portal::Graphics::BufferUsageHint::DEVICE
458 allocate_slot(foundry.get_device(), foundry.get_physical_device(),
459 slot, required_bytes, Portal::Graphics::to_buffer_usage_flags(usage_hint), gpu_resident);
460}
461
463{
464 const auto it = m_shared->slots.find({ set, binding_index });
465 if (it == m_shared->slots.end())
466 return {};
467 return { .buffer = it->second.buffer, .mapped_ptr = it->second.mapped_ptr, .size_bytes = it->second.allocated_bytes };
468}
469
470void GpuResourceManager::bind_shared_descriptor(const std::string& key, uint32_t set, size_t binding_index, const GpuBufferBinding& spec)
471{
472 auto& unit = unit_for(key);
473 auto& foundry = Portal::Graphics::get_shader_foundry();
474 auto& slot = m_shared->slots.at({ set, binding_index });
475
476 foundry.update_descriptor_buffer(
477 unit.descriptor_set_ids[spec.set],
478 spec.binding,
479 vk::DescriptorType::eStorageBuffer,
480 slot.buffer, 0, slot.allocated_bytes);
481}
482
483void GpuResourceManager::download_shared(uint32_t set, size_t binding_index, void* dest, size_t byte_size)
484{
485 auto& slot = m_shared->slots.at({ set, binding_index });
486 std::memcpy(dest, slot.mapped_ptr, byte_size);
487}
488
489void GpuResourceManager::upload_shared_raw(uint32_t set, size_t binding_index, const uint8_t* data, size_t byte_size)
490{
491 auto& slot = m_shared->slots.at({ set, binding_index });
492 std::memcpy(slot.mapped_ptr, data, byte_size);
493}
494
496 const GpuBufferBinding& spec) const
497{
498 const auto it = m_shared->slots.find({ spec.set, static_cast<size_t>(spec.binding) });
499 vk::Buffer handle = it != m_shared->slots.end() ? it->second.buffer : vk::Buffer {};
501 .binding = spec,
502 .image = nullptr,
503 .buffer = handle,
504 };
505}
506
508 const std::string& key, size_t index,
509 const std::shared_ptr<Core::VKImage>& image,
510 const GpuBufferBinding& spec)
511{
512 auto& unit = unit_for(key);
513 auto& foundry = Portal::Graphics::get_shader_foundry();
514
515 if (index >= unit.image_slots.size())
516 unit.image_slots.resize(index + 1);
517 unit.image_slots[index] = image;
518
519 foundry.update_descriptor_storage_image(
520 unit.descriptor_set_ids[spec.set],
521 spec.binding,
522 image->get_image_view(),
523 vk::ImageLayout::eGeneral);
524}
525
527 const std::string& key, size_t index,
528 const std::shared_ptr<Core::VKImage>& image,
529 vk::Sampler sampler,
530 const GpuBufferBinding& spec)
531{
532 auto& unit = unit_for(key);
533 auto& foundry = Portal::Graphics::get_shader_foundry();
534
535 if (index >= unit.image_slots.size())
536 unit.image_slots.resize(index + 1);
537 unit.image_slots[index] = image;
538
539 foundry.update_descriptor_image(
540 unit.descriptor_set_ids[spec.set],
541 spec.binding,
542 image->get_image_view(),
543 sampler,
544 vk::ImageLayout::eShaderReadOnlyOptimal);
545}
546
547void GpuResourceManager::bind_images_batch(const std::string& key, const std::vector<ImageBind>& images)
548{
549 auto& unit = unit_for(key);
550 auto& foundry = Portal::Graphics::get_shader_foundry();
551
552 std::vector<Portal::Graphics::DescriptorImageWrite> writes;
553 writes.reserve(images.size());
554
555 for (const auto& entry : images) {
556 if (entry.index >= unit.image_slots.size())
557 unit.image_slots.resize(entry.index + 1);
558 unit.image_slots[entry.index] = entry.image;
559
560 const bool is_storage = entry.spec.element_type == GpuBufferBinding::ElementType::IMAGE_STORAGE;
561 writes.push_back({
562 .descriptor_set_id = unit.descriptor_set_ids[entry.spec.set],
563 .binding = entry.spec.binding,
564 .type = is_storage ? vk::DescriptorType::eStorageImage : vk::DescriptorType::eCombinedImageSampler,
565 .image_view = entry.image->get_image_view(),
566 .sampler = entry.sampler,
567 .layout = is_storage ? vk::ImageLayout::eGeneral : vk::ImageLayout::eShaderReadOnlyOptimal,
568 });
569 }
570
571 foundry.update_descriptor_images(writes);
572}
573
575 const std::shared_ptr<Core::VKImage>& image,
576 vk::ImageLayout old_layout,
577 vk::ImageLayout new_layout)
578{
579 auto& foundry = Portal::Graphics::get_shader_foundry();
580 auto& backend = Portal::Graphics::get_texture_manager(); // TextureLoom -> backend ref
581
582 backend.transition_layout(
583 image,
584 old_layout,
585 new_layout,
586 1, 1, vk::ImageAspectFlagBits::eColor);
587}
588
589//==============================================================================
590// Dispatch
591//==============================================================================
592
593void GpuResourceManager::dispatch(const std::string& key,
594 const std::array<uint32_t, 3>& groups,
595 const std::vector<GpuBufferBinding>& bindings,
596 const uint8_t* push_constant_data,
597 size_t push_constant_size)
598{
599 auto& unit = unit_for(key);
600 auto& foundry = Portal::Graphics::get_shader_foundry();
601 auto& compute_press = Portal::Graphics::get_compute_press();
602
603 auto cmd_id = foundry.begin_commands(
605
606 compute_press.bind_all(
607 cmd_id, unit.pipeline_id, unit.descriptor_set_ids,
608 push_constant_data, push_constant_size);
609
610 compute_press.dispatch(cmd_id, groups[0], groups[1], groups[2]);
611
612 for (const auto& b : bindings) {
613 const auto et = b.element_type;
616 const bool is_output = b.direction == GpuBufferBinding::Direction::OUTPUT
618
619 const bool is_shared = m_shared->slots.contains({ b.set, static_cast<size_t>(b.binding) });
620 if (is_output && !is_image && !is_shared
621 && static_cast<size_t>(b.binding) < unit.impl->buffers.size()
622 && unit.impl->buffers[b.binding].buffer) {
623 foundry.buffer_barrier(
624 cmd_id,
625 unit.impl->buffers[b.binding].buffer,
626 vk::AccessFlagBits::eShaderWrite,
627 vk::AccessFlagBits::eHostRead,
628 vk::PipelineStageFlagBits::eComputeShader,
629 vk::PipelineStageFlagBits::eHost);
630 }
631 }
632
633 foundry.submit_and_wait(cmd_id);
634}
635
636void GpuResourceManager::dispatch_batched(const std::string& key,
637 const std::array<uint32_t, 3>& groups,
638 const std::vector<GpuBufferBinding>& bindings,
639 size_t push_constant_size,
640 const ExecutionContext& ctx)
641{
642 const auto& params = safe_variant_get_or_throw<ChainedParams>(ctx.parameters,
643 "GpuResourceManager: dispatch_batched requires ChainedParams");
644
645 auto& unit = unit_for(key);
646 auto& foundry = Portal::Graphics::get_shader_foundry();
647 auto& compute_press = Portal::Graphics::get_compute_press();
648
649 const uint32_t workgroups_per_pass = groups[0] * groups[1] * groups[2];
650
651 const uint32_t default_passes = std::max(1U, 65536U / std::max(1U, workgroups_per_pass));
652
653 const uint32_t effective_passes_per_batch = params.passes_per_batch.value_or(default_passes);
654 for (uint32_t base = 0; base < params.pass_count; base += effective_passes_per_batch) {
655 const uint32_t batch_end = std::min(base + effective_passes_per_batch, params.pass_count);
656 auto cmd_id = foundry.begin_commands(
658
659 for (uint32_t pass = base; pass < batch_end; ++pass) {
660 std::vector<uint8_t> pc_data(push_constant_size);
661 params.pc_updater(pass, pc_data.data());
662 compute_press.bind_all(
663 cmd_id, unit.pipeline_id, unit.descriptor_set_ids,
664 pc_data.data(), push_constant_size);
665 compute_press.dispatch(cmd_id, groups[0], groups[1], groups[2]);
666
667 for (const auto& b : bindings) {
669 continue;
670
671 const bool is_image = b.element_type == GpuBufferBinding::ElementType::IMAGE_STORAGE
673 const bool is_shared = m_shared->slots.contains({ b.set, static_cast<size_t>(b.binding) });
674
675 if (is_image) {
676 if (static_cast<size_t>(b.binding) < unit.image_slots.size() && unit.image_slots[b.binding]) {
677 foundry.image_barrier(
678 cmd_id,
679 unit.image_slots[b.binding]->get_image(),
680 vk::ImageLayout::eGeneral,
681 vk::ImageLayout::eGeneral,
682 vk::AccessFlagBits::eShaderWrite | vk::AccessFlagBits::eShaderRead,
683 vk::AccessFlagBits::eShaderWrite | vk::AccessFlagBits::eShaderRead,
684 vk::PipelineStageFlagBits::eComputeShader,
685 vk::PipelineStageFlagBits::eComputeShader);
686 }
687 } else if (!is_shared && static_cast<size_t>(b.binding) < unit.impl->buffers.size()
688 && unit.impl->buffers[b.binding].buffer) {
689 foundry.buffer_barrier(
690 cmd_id,
691 unit.impl->buffers[b.binding].buffer,
692 vk::AccessFlagBits::eShaderWrite | vk::AccessFlagBits::eShaderRead,
693 vk::AccessFlagBits::eShaderWrite | vk::AccessFlagBits::eShaderRead,
694 vk::PipelineStageFlagBits::eComputeShader,
695 vk::PipelineStageFlagBits::eComputeShader);
696 }
697 }
698 }
699
700 for (const auto& b : bindings) {
703 const bool is_image = b.element_type == GpuBufferBinding::ElementType::IMAGE_STORAGE
705 const bool is_shared = m_shared->slots.contains({ b.set, static_cast<size_t>(b.binding) });
706 if (!is_image && !is_shared && static_cast<size_t>(b.binding) < unit.impl->buffers.size()
707 && unit.impl->buffers[b.binding].buffer) {
708 foundry.buffer_barrier(
709 cmd_id,
710 unit.impl->buffers[b.binding].buffer,
711 vk::AccessFlagBits::eShaderWrite,
712 vk::AccessFlagBits::eHostRead,
713 vk::PipelineStageFlagBits::eComputeShader,
714 vk::PipelineStageFlagBits::eHost);
715 }
716 }
717 }
718
719 foundry.submit_and_wait(cmd_id);
720 }
721}
722
724 uint32_t indirect_set, size_t indirect_binding,
725 const std::array<uint32_t, 3>& groups,
726 const std::vector<GpuBufferBinding>& bindings,
727 size_t push_constant_size,
728 const ExecutionContext& ctx)
729{
730 const auto& params = safe_variant_get_or_throw<ChainedIndirectParams>(ctx.parameters,
731 "GpuResourceManager: dispatch_batched_indirect requires ChainedIndirectParams");
732
733 auto& unit = unit_for(key);
734 auto& foundry = Portal::Graphics::get_shader_foundry();
735 auto& compute_press = Portal::Graphics::get_compute_press();
736
737 auto& indirect_slot = m_shared->slots.at({ indirect_set, indirect_binding });
738 const vk::Buffer indirect_buffer = indirect_slot.buffer;
739 const uint32_t init_cmd[3] = { groups[0], groups[1], groups[2] };
740 std::memcpy(indirect_slot.mapped_ptr, init_cmd, sizeof(init_cmd));
741
742 const uint32_t default_passes = std::max(1U, 65536U / std::max(1U, groups[0] * groups[1] * groups[2]));
743 const uint32_t effective_passes_per_batch = params.passes_per_batch.value_or(default_passes);
744
745 for (uint32_t base = 0; base < params.pass_count; base += effective_passes_per_batch) {
746 const uint32_t batch_end = std::min(base + effective_passes_per_batch, params.pass_count);
747 auto cmd_id = foundry.begin_commands(Portal::Graphics::ShaderFoundry::CommandBufferType::COMPUTE);
748
749 for (uint32_t pass = base; pass < batch_end; ++pass) {
750 std::vector<uint8_t> pc_data(push_constant_size);
751 params.pc_updater(pass, 1, pc_data.data());
752 compute_press.bind_all(cmd_id, unit.pipeline_id, unit.descriptor_set_ids, pc_data.data(), push_constant_size);
753 compute_press.dispatch_indirect(cmd_id, indirect_buffer);
754
755 for (const auto& b : bindings) {
756 const auto et = b.element_type;
759 const bool is_output = b.direction == GpuBufferBinding::Direction::OUTPUT
761 const bool is_shared = m_shared->slots.contains({ b.set, static_cast<size_t>(b.binding) });
762 if (is_output && !is_image && !is_shared
763 && static_cast<size_t>(b.binding) < unit.impl->buffers.size()
764 && unit.impl->buffers[b.binding].buffer) {
765 foundry.buffer_barrier(
766 cmd_id,
767 unit.impl->buffers[b.binding].buffer,
768 vk::AccessFlagBits::eShaderWrite,
769 vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite,
770 vk::PipelineStageFlagBits::eComputeShader,
771 vk::PipelineStageFlagBits::eComputeShader);
772 }
773 }
774 }
775 foundry.submit_and_wait(cmd_id);
776 }
777}
778
780 const std::array<uint32_t, 3>& groups,
781 const std::vector<GpuBufferBinding>& bindings,
782 const uint8_t* push_constant_data,
783 size_t push_constant_size)
784{
785 auto& unit = unit_for(key);
786 auto& foundry = Portal::Graphics::get_shader_foundry();
787 auto& compute_press = Portal::Graphics::get_compute_press();
788
789 auto cmd_id = foundry.begin_commands(
791
792 compute_press.bind_all(
793 cmd_id, unit.pipeline_id, unit.descriptor_set_ids,
794 push_constant_data, push_constant_size);
795
796 compute_press.dispatch(cmd_id, groups[0], groups[1], groups[2]);
797
798 for (const auto& b : bindings) {
799 const auto et = b.element_type;
802 const bool is_output = b.direction == GpuBufferBinding::Direction::OUTPUT
804 const bool is_shared = m_shared->slots.contains({ b.set, static_cast<size_t>(b.binding) });
805
806 if (is_output && !is_image && !is_shared
807 && static_cast<size_t>(b.binding) < unit.impl->buffers.size()
808 && unit.impl->buffers[b.binding].buffer) {
809 foundry.buffer_barrier(
810 cmd_id,
811 unit.impl->buffers[b.binding].buffer,
812 vk::AccessFlagBits::eShaderWrite,
813 vk::AccessFlagBits::eHostRead,
814 vk::PipelineStageFlagBits::eComputeShader,
815 vk::PipelineStageFlagBits::eHost);
816 }
817 }
818
819 return foundry.submit_async(cmd_id);
820}
821
823 const std::vector<std::string>& keys,
824 const std::vector<std::array<uint32_t, 3>>& groups_per_key,
825 const std::vector<std::vector<uint8_t>>& push_constants_per_key,
826 const std::vector<std::vector<Portal::Graphics::HazardResource>>& hazards_per_key,
827 const std::vector<Portal::Graphics::IndirectDispatch>& indirect_per_key)
828{
829 auto& foundry = Portal::Graphics::get_shader_foundry();
830 auto& compute_press = Portal::Graphics::get_compute_press();
831
832 std::vector<Portal::Graphics::ComputeStage> stages;
833 stages.reserve(keys.size());
834
835 for (size_t i = 0; i < keys.size(); ++i) {
836 auto& unit = unit_for(keys[i]);
837 stages.push_back(Portal::Graphics::ComputeStage {
838 .pipeline_id = unit.pipeline_id,
839 .descriptor_set_ids = unit.descriptor_set_ids,
840 .groups = groups_per_key[i],
841 .push_constant_data = push_constants_per_key[i],
842 .hazard_resources = hazards_per_key[i],
843 .indirect = i < indirect_per_key.size() ? indirect_per_key[i] : Portal::Graphics::IndirectDispatch {},
844 });
845 }
846
847 auto cmd_id = foundry.begin_commands(
849 compute_press.record_sequence(cmd_id, stages);
850 return cmd_id;
851}
852
854 const std::vector<std::string>& keys,
855 const std::vector<std::array<uint32_t, 3>>& groups_per_key,
856 const std::vector<std::vector<uint8_t>>& push_constants_per_key,
857 const std::vector<std::vector<Portal::Graphics::HazardResource>>& hazards_per_key,
858 const std::vector<Portal::Graphics::IndirectDispatch>& indirect_per_key)
859{
860 auto& foundry = Portal::Graphics::get_shader_foundry();
861 foundry.submit_and_wait(
862 record_sequence_commands(keys, groups_per_key, push_constants_per_key, hazards_per_key, indirect_per_key));
863}
864
866 const std::vector<std::string>& keys,
867 const std::vector<std::array<uint32_t, 3>>& groups_per_key,
868 const std::vector<std::vector<uint8_t>>& push_constants_per_key,
869 const std::vector<std::vector<Portal::Graphics::HazardResource>>& hazards_per_key,
870 const std::vector<Portal::Graphics::IndirectDispatch>& indirect_per_key)
871{
872 auto& foundry = Portal::Graphics::get_shader_foundry();
873 return foundry.submit_async(
874 record_sequence_commands(keys, groups_per_key, push_constants_per_key, hazards_per_key, indirect_per_key));
875}
876
877} // namespace MayaFlux::Yantra
#define MF_ERROR(comp, ctx,...)
IO::ImageData image
Definition Decoder.cpp:47
std::shared_ptr< NetworkGeometryBuffer > buffer
size_t b
vk::PhysicalDevice device
Definition VKDevice.cpp:121
vk::PhysicalDeviceType type
Definition VKDevice.cpp:126
uint32_t index
Definition VKDevice.cpp:122
uint32_t width
Cycle Behavior: The for_cycles(N) configuration controls how many times the capture operation execute...
void bind_image_sampled(const std::string &key, size_t index, const std::shared_ptr< Core::VKImage > &image, vk::Sampler sampler, const GpuBufferBinding &spec)
Bind a combined image+sampler descriptor at the given slot index.
std::unique_ptr< SharedBuffers > m_shared
void dispatch_batched_indirect(const std::string &key, uint32_t indirect_set, size_t indirect_binding, const std::array< uint32_t, 3 > &groups, const std::vector< GpuBufferBinding > &bindings, size_t push_constant_size, const ExecutionContext &ctx)
void download(const std::string &key, size_t index, float *dest, size_t byte_size)
bool initialise(const std::string &key, const GpuComputeConfig &config, const std::vector< GpuBufferBinding > &bindings)
Create (or confirm existing) pipeline for the given key.
bool is_ready(const std::string &key) const
void download_shared(uint32_t set, size_t binding_index, void *dest, size_t byte_size)
void cleanup()
Destroy every key currently held.
void bind_descriptors_batch(const std::string &key, const std::vector< GpuBufferBinding > &bindings)
Bind every non-image buffer binding in one Vulkan call.
void upload_shared_raw(uint32_t set, size_t binding_index, const uint8_t *data, size_t byte_size)
void release(const std::string &key)
Destroy the pipeline, shader, descriptor sets, and buffers for a single key, without affecting any ot...
void bind_images_batch(const std::string &key, const std::vector< ImageBind > &images)
Bind every image in one Vulkan call.
Portal::Graphics::HazardResource make_shared_buffer_hazard(const GpuBufferBinding &spec) const
void dispatch_sequence(const std::vector< std::string > &keys, const std::vector< std::array< uint32_t, 3 > > &groups_per_key, const std::vector< std::vector< uint8_t > > &push_constants_per_key, const std::vector< std::vector< Portal::Graphics::HazardResource > > &hazards_per_key, const std::vector< Portal::Graphics::IndirectDispatch > &indirect_per_key={})
Record a dispatch for each requested key into one command buffer via ComputePress::record_sequence,...
PipelineUnit & unit_for(const std::string &key)
void upload(const std::string &key, size_t index, const float *data, size_t byte_size)
Portal::Graphics::CommandBufferID record_sequence_commands(const std::vector< std::string > &keys, const std::vector< std::array< uint32_t, 3 > > &groups_per_key, const std::vector< std::vector< uint8_t > > &push_constants_per_key, const std::vector< std::vector< Portal::Graphics::HazardResource > > &hazards_per_key, const std::vector< Portal::Graphics::IndirectDispatch > &indirect_per_key)
Record one command buffer holding a dispatch per key, with the barriers each stage's hazards owe the ...
void transition_image(const std::shared_ptr< Core::VKImage > &image, vk::ImageLayout old_layout, vk::ImageLayout new_layout)
Transition a VKImage layout via an immediate command submission.
void bind_descriptor(const std::string &key, size_t index, const GpuBufferBinding &spec)
void dispatch(const std::string &key, const std::array< uint32_t, 3 > &groups, const std::vector< GpuBufferBinding > &bindings, const uint8_t *push_constant_data, size_t push_constant_size)
Portal::Graphics::FenceID dispatch_async(const std::string &key, const std::array< uint32_t, 3 > &groups, const std::vector< GpuBufferBinding > &bindings, const uint8_t *push_constant_data, size_t push_constant_size)
Submit a compute dispatch without blocking.
std::unordered_map< std::string, std::unique_ptr< PipelineUnit > > m_units
Portal::Graphics::FenceID dispatch_sequence_async(const std::vector< std::string > &keys, const std::vector< std::array< uint32_t, 3 > > &groups_per_key, const std::vector< std::vector< uint8_t > > &push_constants_per_key, const std::vector< std::vector< Portal::Graphics::HazardResource > > &hazards_per_key, const std::vector< Portal::Graphics::IndirectDispatch > &indirect_per_key={})
Non-blocking counterpart of dispatch_sequence.
void bind_shared_descriptor(const std::string &key, uint32_t set, size_t binding_index, const GpuBufferBinding &spec)
void dispatch_batched(const std::string &key, const std::array< uint32_t, 3 > &groups, const std::vector< GpuBufferBinding > &bindings, size_t push_constant_size, const ExecutionContext &ctx)
void ensure_shared_buffer(uint32_t set, size_t binding_index, size_t element_count, GpuBufferBinding::ElementType element_type, Portal::Graphics::BufferUsageHint usage_hint=Portal::Graphics::BufferUsageHint::COMPUTE_STORAGE)
void ensure_buffer(const std::string &key, size_t index, size_t required_bytes, Portal::Graphics::BufferUsageHint usage_hint=Portal::Graphics::BufferUsageHint::COMPUTE_STORAGE)
void upload_raw(const std::string &key, size_t index, const uint8_t *data, size_t byte_size)
Portal::Graphics::GpuBufferHandle shared_buffer_handle(uint32_t set, size_t binding_index) const
Non-owning view of the shared buffer at (set, binding_index).
size_t buffer_allocated_bytes(const std::string &key, size_t index) const
const PipelineUnit * find_unit(const std::string &key) const
void bind_image_storage(const std::string &key, size_t index, const std::shared_ptr< Core::VKImage > &image, const GpuBufferBinding &spec)
Bind a storage image descriptor at the given slot index.
@ BufferProcessing
Buffer processing (Buffers::BufferManager, processing chains)
@ Yantra
DSP algorithms, computational units, matrix operations, Grammar.
MAYAFLUX_API TextureLoom & get_texture_manager()
Get the global texture manager instance.
constexpr ShaderID INVALID_SHADER
MAYAFLUX_API ShaderFoundry & get_shader_foundry()
Get the global shader compiler instance.
constexpr ComputePipelineID INVALID_COMPUTE_PIPELINE
size_t element_type_bytes(GpuBufferBinding::ElementType et) noexcept
Byte width of one GpuBufferBinding::ElementType element.
BufferUsageHint
Semantic usage hint for buffer allocation and memory properties.
@ COMPUTE
Storage buffer for compute shaders (device-local)
@ DEVICE
Device-local GPU-only buffer.
vk::BufferUsageFlags to_buffer_usage_flags(BufferUsageHint hint)
Resolve the extra vk::BufferUsageFlags a BufferUsageHint requires, on top of whatever base usage the ...
MAYAFLUX_API ComputePress & get_compute_press()
bool is_image(const fs::path &filepath)
Definition Depot.cpp:136
ComputePipelineID pipeline_id
Pipeline to bind for this stage.
One pipeline dispatch within a ComputePress::record_sequence call.
ElementType
Element type the shader expects in this binding.
uint32_t binding
Binding index within the set.
Declares a single storage buffer or image binding a compute shader expects.
Non-owning view of a GPU storage buffer produced by a compute path.
Plain-data description of the compute shader to dispatch.
vk::Buffer buffer
Valid when binding.element_type is anything else.
GpuBufferBinding binding
Direction/element_type describing this resource.
One resource this stage's dispatch reads/writes that a later stage in the sequence depends on,...
Optional GPU-resident source of a stage's workgroup counts.
ExecutionParams parameters
Optional parameters specific to the execution mode.
Context information controlling how a compute operation executes.
std::unordered_map< std::string, VulkanBufferSlot > shared_buffers
std::vector< VulkanBufferSlot > buffers
std::unique_ptr< GpuResourceManagerImpl > impl
std::map< std::pair< uint32_t, size_t >, VulkanBufferSlot > slots