36 uint32_t find_memory_type(vk::PhysicalDevice phys,
38 vk::MemoryPropertyFlags props,
39 vk::MemoryPropertyFlags fallback_props = {})
41 auto mem_props = phys.getMemoryProperties();
42 for (uint32_t i = 0; i < mem_props.memoryTypeCount; ++i) {
43 if ((type_filter & (1U << i))
44 && (mem_props.memoryTypes[i].propertyFlags & props) == props) {
49 for (uint32_t i = 0; i < mem_props.memoryTypeCount; ++i) {
50 if ((type_filter & (1U << i))
51 && (mem_props.memoryTypes[i].propertyFlags & fallback_props) == fallback_props) {
56 error<std::runtime_error>(
59 std::source_location::current(),
60 "GpuResourceManager: no suitable memory type found");
63 void free_slot(vk::Device device, VulkanBufferSlot& slot)
65 if (slot.mapped_ptr) {
66 device.unmapMemory(slot.memory);
67 slot.mapped_ptr =
nullptr;
70 device.destroyBuffer(slot.buffer);
71 slot.buffer = vk::Buffer {};
74 device.freeMemory(slot.memory);
75 slot.memory = vk::DeviceMemory {};
77 slot.allocated_bytes = 0;
80 void allocate_slot(vk::Device device, vk::PhysicalDevice phys,
81 VulkanBufferSlot& slot,
size_t byte_size,
82 vk::BufferUsageFlags extra_usage = vk::BufferUsageFlagBits::eStorageBuffer)
84 free_slot(device, slot);
86 vk::BufferCreateInfo bi;
88 bi.usage = extra_usage;
89 bi.sharingMode = vk::SharingMode::eExclusive;
90 slot.buffer = device.createBuffer(bi);
92 auto req = device.getBufferMemoryRequirements(slot.buffer);
94 vk::MemoryAllocateInfo ai;
95 ai.allocationSize = req.size;
96 ai.memoryTypeIndex = find_memory_type(phys, req.memoryTypeBits,
97 vk::MemoryPropertyFlagBits::eHostVisible
98 | vk::MemoryPropertyFlagBits::eHostCoherent
99 | vk::MemoryPropertyFlagBits::eHostCached,
100 vk::MemoryPropertyFlagBits::eHostVisible
101 | vk::MemoryPropertyFlagBits::eHostCoherent);
103 slot.memory = device.allocateMemory(ai);
104 device.bindBufferMemory(slot.buffer, slot.memory, 0);
105 slot.mapped_ptr = device.mapMemory(slot.memory, 0, VK_WHOLE_SIZE);
106 slot.allocated_bytes = byte_size;
113 return vk::DescriptorType::eStorageImage;
115 return vk::DescriptorType::eCombinedImageSampler;
117 return vk::DescriptorType::eStorageBuffer;
145 error<std::runtime_error>(
148 std::source_location::current(),
149 "GpuResourceManager: no unit for key '{}' — call initialise() first", key);
157 return it ==
m_units.end() ? nullptr : it->second.get();
163 return unit && unit->ready;
168 const std::vector<GpuBufferBinding>& bindings)
176 auto unit = std::make_unique<PipelineUnit>();
181 unit->shader_id = foundry.load_shader(config.
shader_path);
186 "GpuResourceManager: failed to load shader '{}' for key '{}'",
191 std::map<uint32_t, std::vector<Portal::Graphics::DescriptorBindingInfo>> by_set;
192 for (
const auto&
b : bindings) {
193 const auto et =
b.element_type;
197 by_set[
b.set].push_back({
199 .binding =
b.binding,
200 .type = element_type_to_vk(et),
205 for (
const auto&
b : bindings) {
206 const auto et =
b.element_type;
210 by_set[
b.set].push_back({
212 .binding =
b.binding,
213 .type = vk::DescriptorType::eStorageBuffer,
218 std::vector<std::vector<Portal::Graphics::DescriptorBindingInfo>> descriptor_sets;
219 descriptor_sets.reserve(by_set.size());
220 for (
auto& [set_idx, set_bindings] : by_set)
221 descriptor_sets.push_back(std::move(set_bindings));
223 unit->pipeline_id = compute_press.create_pipeline(
228 "GpuResourceManager: failed to create pipeline for key '{}'", key);
229 foundry.destroy_shader(unit->shader_id);
233 unit->descriptor_set_ids = compute_press.allocate_pipeline_descriptors(unit->pipeline_id);
234 if (unit->descriptor_set_ids.empty()) {
236 "GpuResourceManager: failed to allocate descriptor sets for key '{}'", key);
237 compute_press.destroy_pipeline(unit->pipeline_id);
238 foundry.destroy_shader(unit->shader_id);
242 unit->impl = std::make_unique<GpuResourceManagerImpl>();
243 size_t max_binding = 0;
244 for (
const auto&
b : bindings)
245 max_binding = std::max(max_binding,
static_cast<size_t>(
b.binding));
246 const size_t capacity = bindings.empty() ? 0 : max_binding + 1;
247 unit->impl->buffers.resize(capacity);
248 unit->buffer_slots.resize(capacity);
249 unit->image_slots.resize(capacity);
252 m_units[key] = std::move(unit);
262 auto& unit = *it->second;
270 auto device = foundry.get_device();
273 for (
auto& slot : unit.impl->buffers)
274 free_slot(device, slot);
277 unit.buffer_slots.clear();
278 unit.image_slots.clear();
281 compute_press.destroy_pipeline(unit.pipeline_id);
284 foundry.destroy_shader(unit.shader_id);
291 std::vector<std::string> keys;
295 for (
const auto&
k : keys)
307 auto& vk_slot = unit.impl->buffers[index];
308 if (vk_slot.allocated_bytes >= required_bytes) {
313 allocate_slot(foundry.get_device(), foundry.get_physical_device(),
316 unit.buffer_slots[index].allocated_bytes = required_bytes;
322 std::memcpy(vk_slot.mapped_ptr, data, byte_size);
328 std::memcpy(vk_slot.mapped_ptr, data, byte_size);
334 std::memcpy(dest, vk_slot.mapped_ptr, byte_size);
341 auto& vk_slot = unit.impl->buffers[index];
343 foundry.update_descriptor_buffer(
344 unit.descriptor_set_ids[spec.
set],
346 vk::DescriptorType::eStorageBuffer,
347 vk_slot.buffer, 0, vk_slot.allocated_bytes);
361 error<std::runtime_error>(
364 std::source_location::current(),
365 "GpuResourceManager: ensure_shared_buffer requires a sized element_type");
368 auto& slot =
m_shared->slots[{ set, binding_index }];
369 const size_t required_bytes = element_count *
width;
370 if (slot.allocated_bytes >= required_bytes)
374 allocate_slot(foundry.get_device(), foundry.get_physical_device(),
382 auto& slot =
m_shared->slots.at({ set, binding_index });
384 foundry.update_descriptor_buffer(
385 unit.descriptor_set_ids[spec.
set],
387 vk::DescriptorType::eStorageBuffer,
388 slot.buffer, 0, slot.allocated_bytes);
393 auto& slot =
m_shared->slots.at({ set, binding_index });
394 std::memcpy(dest, slot.mapped_ptr, byte_size);
399 auto& slot =
m_shared->slots.at({ set, binding_index });
400 std::memcpy(slot.mapped_ptr, data, byte_size);
406 const auto it =
m_shared->slots.find({ spec.
set,
static_cast<size_t>(spec.
binding) });
407 vk::Buffer handle = it !=
m_shared->slots.end() ? it->second.
buffer : vk::Buffer {};
416 const std::string& key,
size_t index,
417 const std::shared_ptr<Core::VKImage>&
image,
423 if (index >= unit.image_slots.size())
424 unit.image_slots.resize(index + 1);
425 unit.image_slots[index] =
image;
427 foundry.update_descriptor_storage_image(
428 unit.descriptor_set_ids[spec.
set],
430 image->get_image_view(),
431 vk::ImageLayout::eGeneral);
435 const std::string& key,
size_t index,
436 const std::shared_ptr<Core::VKImage>&
image,
443 if (index >= unit.image_slots.size())
444 unit.image_slots.resize(index + 1);
445 unit.image_slots[index] =
image;
447 foundry.update_descriptor_image(
448 unit.descriptor_set_ids[spec.
set],
450 image->get_image_view(),
452 vk::ImageLayout::eShaderReadOnlyOptimal);
456 const std::shared_ptr<Core::VKImage>&
image,
457 vk::ImageLayout old_layout,
458 vk::ImageLayout new_layout)
463 backend.transition_layout(
467 1, 1, vk::ImageAspectFlagBits::eColor);
475 const std::array<uint32_t, 3>& groups,
476 const std::vector<GpuBufferBinding>& bindings,
477 const uint8_t* push_constant_data,
478 size_t push_constant_size)
484 auto cmd_id = foundry.begin_commands(
487 compute_press.bind_all(
488 cmd_id, unit.pipeline_id, unit.descriptor_set_ids,
489 push_constant_data, push_constant_size);
491 compute_press.dispatch(cmd_id, groups[0], groups[1], groups[2]);
493 for (
const auto&
b : bindings) {
494 const auto et =
b.element_type;
500 const bool is_shared =
m_shared->slots.contains({
b.set,
static_cast<size_t>(
b.binding) });
501 if (is_output && !
is_image && !is_shared
502 &&
static_cast<size_t>(
b.binding) < unit.impl->buffers.size()) {
503 foundry.buffer_barrier(
505 unit.impl->buffers[
b.binding].buffer,
506 vk::AccessFlagBits::eShaderWrite,
507 vk::AccessFlagBits::eHostRead,
508 vk::PipelineStageFlagBits::eComputeShader,
509 vk::PipelineStageFlagBits::eHost);
513 foundry.submit_and_wait(cmd_id);
517 const std::array<uint32_t, 3>& groups,
518 const std::vector<GpuBufferBinding>& bindings,
519 size_t push_constant_size,
522 const auto& params = safe_variant_get_or_throw<ChainedParams>(ctx.
parameters,
523 "GpuResourceManager: dispatch_batched requires ChainedParams");
529 const uint32_t workgroups_per_pass = groups[0] * groups[1] * groups[2];
531 const uint32_t default_passes = std::max(1U, 65536U / std::max(1U, workgroups_per_pass));
533 const uint32_t effective_passes_per_batch = params.passes_per_batch.value_or(default_passes);
534 for (uint32_t base = 0; base < params.pass_count; base += effective_passes_per_batch) {
535 const uint32_t batch_end = std::min(base + effective_passes_per_batch, params.pass_count);
536 auto cmd_id = foundry.begin_commands(
540 std::vector<uint8_t> pc_data(push_constant_size);
541 params.pc_updater(
pass, pc_data.data());
542 compute_press.bind_all(
543 cmd_id, unit.pipeline_id, unit.descriptor_set_ids,
544 pc_data.data(), push_constant_size);
545 compute_press.dispatch(cmd_id, groups[0], groups[1], groups[2]);
547 for (
const auto&
b : bindings) {
553 const bool is_shared =
m_shared->slots.contains({
b.set,
static_cast<size_t>(
b.binding) });
556 if (
static_cast<size_t>(
b.binding) < unit.image_slots.size() && unit.image_slots[
b.binding]) {
557 foundry.image_barrier(
559 unit.image_slots[
b.binding]->get_image(),
560 vk::ImageLayout::eGeneral,
561 vk::ImageLayout::eGeneral,
562 vk::AccessFlagBits::eShaderWrite | vk::AccessFlagBits::eShaderRead,
563 vk::AccessFlagBits::eShaderWrite | vk::AccessFlagBits::eShaderRead,
564 vk::PipelineStageFlagBits::eComputeShader,
565 vk::PipelineStageFlagBits::eComputeShader);
567 }
else if (!is_shared &&
static_cast<size_t>(
b.binding) < unit.impl->buffers.size()) {
568 foundry.buffer_barrier(
570 unit.impl->buffers[
b.binding].buffer,
571 vk::AccessFlagBits::eShaderWrite | vk::AccessFlagBits::eShaderRead,
572 vk::AccessFlagBits::eShaderWrite | vk::AccessFlagBits::eShaderRead,
573 vk::PipelineStageFlagBits::eComputeShader,
574 vk::PipelineStageFlagBits::eComputeShader);
579 for (
const auto&
b : bindings) {
584 const bool is_shared =
m_shared->slots.contains({
b.set,
static_cast<size_t>(
b.binding) });
585 if (!
is_image && !is_shared &&
static_cast<size_t>(
b.binding) < unit.impl->buffers.size()) {
586 foundry.buffer_barrier(
588 unit.impl->buffers[
b.binding].buffer,
589 vk::AccessFlagBits::eShaderWrite,
590 vk::AccessFlagBits::eHostRead,
591 vk::PipelineStageFlagBits::eComputeShader,
592 vk::PipelineStageFlagBits::eHost);
597 foundry.submit_and_wait(cmd_id);
602 uint32_t indirect_set,
size_t indirect_binding,
603 const std::array<uint32_t, 3>& groups,
604 const std::vector<GpuBufferBinding>& bindings,
605 size_t push_constant_size,
608 const auto& params = safe_variant_get_or_throw<ChainedIndirectParams>(ctx.
parameters,
609 "GpuResourceManager: dispatch_batched_indirect requires ChainedIndirectParams");
615 auto& indirect_slot =
m_shared->slots.at({ indirect_set, indirect_binding });
616 const vk::Buffer indirect_buffer = indirect_slot.buffer;
617 const uint32_t init_cmd[3] = { groups[0], groups[1], groups[2] };
618 std::memcpy(indirect_slot.mapped_ptr, init_cmd,
sizeof(init_cmd));
620 const uint32_t default_passes = std::max(1U, 65536U / std::max(1U, groups[0] * groups[1] * groups[2]));
621 const uint32_t effective_passes_per_batch = params.passes_per_batch.value_or(default_passes);
623 for (uint32_t base = 0; base < params.pass_count; base += effective_passes_per_batch) {
624 const uint32_t batch_end = std::min(base + effective_passes_per_batch, params.pass_count);
628 std::vector<uint8_t> pc_data(push_constant_size);
629 params.pc_updater(
pass, 1, pc_data.data());
630 compute_press.bind_all(cmd_id, unit.pipeline_id, unit.descriptor_set_ids, pc_data.data(), push_constant_size);
631 compute_press.dispatch_indirect(cmd_id, indirect_buffer);
633 for (
const auto&
b : bindings) {
634 const auto et =
b.element_type;
639 const bool is_shared =
m_shared->slots.contains({
b.set,
static_cast<size_t>(
b.binding) });
640 if (is_output && !
is_image && !is_shared
641 &&
static_cast<size_t>(
b.binding) < unit.impl->buffers.size()) {
642 foundry.buffer_barrier(
644 unit.impl->buffers[
b.binding].buffer,
645 vk::AccessFlagBits::eShaderWrite,
646 vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite,
647 vk::PipelineStageFlagBits::eComputeShader,
648 vk::PipelineStageFlagBits::eComputeShader);
652 foundry.submit_and_wait(cmd_id);
657 const std::array<uint32_t, 3>& groups,
658 const std::vector<GpuBufferBinding>& bindings,
659 const uint8_t* push_constant_data,
660 size_t push_constant_size)
666 auto cmd_id = foundry.begin_commands(
669 compute_press.bind_all(
670 cmd_id, unit.pipeline_id, unit.descriptor_set_ids,
671 push_constant_data, push_constant_size);
673 compute_press.dispatch(cmd_id, groups[0], groups[1], groups[2]);
675 for (
const auto&
b : bindings) {
676 const auto et =
b.element_type;
682 foundry.buffer_barrier(
684 unit.impl->buffers[
b.binding].buffer,
685 vk::AccessFlagBits::eShaderWrite,
686 vk::AccessFlagBits::eHostRead,
687 vk::PipelineStageFlagBits::eComputeShader,
688 vk::PipelineStageFlagBits::eHost);
692 return foundry.submit_async(cmd_id);
696 const std::vector<std::string>& keys,
697 const std::vector<std::array<uint32_t, 3>>& groups_per_key,
698 const std::vector<std::vector<uint8_t>>& push_constants_per_key,
699 const std::vector<std::vector<Portal::Graphics::HazardResource>>& hazards_per_key)
704 std::vector<Portal::Graphics::ComputeStage> stages;
705 stages.reserve(keys.size());
707 for (
size_t i = 0; i < keys.size(); ++i) {
711 .descriptor_set_ids = unit.descriptor_set_ids,
712 .groups = groups_per_key[i],
713 .push_constant_data = push_constants_per_key[i],
714 .hazard_resources = hazards_per_key[i],
718 auto cmd_id = foundry.begin_commands(
720 compute_press.record_sequence(cmd_id, stages);
721 foundry.submit_and_wait(cmd_id);
#define MF_ERROR(comp, ctx,...)
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 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...
Portal::Graphics::HazardResource make_shared_buffer_hazard(const GpuBufferBinding &spec) const
PipelineUnit & unit_for(const std::string &key)
void upload(const std::string &key, size_t index, const float *data, size_t byte_size)
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.
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)
Record a dispatch for each requested key into one command buffer via ComputePress::record_sequence,...
std::unordered_map< std::string, std::unique_ptr< PipelineUnit > > m_units
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)
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.
size_t element_type_bytes(GpuBufferBinding::ElementType et) noexcept
Byte width of one GpuBufferBinding::ElementType element.
constexpr ShaderID INVALID_SHADER
MAYAFLUX_API ShaderFoundry & get_shader_foundry()
Get the global shader compiler instance.
constexpr ComputePipelineID INVALID_COMPUTE_PIPELINE
BufferUsageHint
Semantic usage hint for buffer allocation and memory properties.
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)
ComputePipelineID pipeline_id
Pipeline to bind for this stage.
One pipeline dispatch within a ComputePress::record_sequence call.
uint32_t set
Descriptor set index.
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.
size_t push_constant_size
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,...
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::vector< BufferSlot > buffer_slots
std::unique_ptr< GpuResourceManagerImpl > impl
std::map< std::pair< uint32_t, size_t >, VulkanBufferSlot > slots