MayaFlux 0.4.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
23//==============================================================================
24
26 std::vector<VulkanBufferSlot> buffers;
27};
28
29//==============================================================================
30// File-local helpers
31//==============================================================================
32
33namespace {
34
35 uint32_t find_memory_type(vk::PhysicalDevice phys,
36 uint32_t type_filter,
37 vk::MemoryPropertyFlags props)
38 {
39 auto mem_props = phys.getMemoryProperties();
40 for (uint32_t i = 0; i < mem_props.memoryTypeCount; ++i) {
41 if ((type_filter & (1U << i))
42 && (mem_props.memoryTypes[i].propertyFlags & props) == props) {
43 return i;
44 }
45 }
46 error<std::runtime_error>(
49 std::source_location::current(),
50 "GpuResourceManager: no suitable memory type found");
51 }
52
53 void free_slot(vk::Device device, VulkanBufferSlot& slot)
54 {
55 if (slot.mapped_ptr) {
56 device.unmapMemory(slot.memory);
57 slot.mapped_ptr = nullptr;
58 }
59 if (slot.buffer) {
60 device.destroyBuffer(slot.buffer);
61 slot.buffer = vk::Buffer {};
62 }
63 if (slot.memory) {
64 device.freeMemory(slot.memory);
65 slot.memory = vk::DeviceMemory {};
66 }
67 slot.allocated_bytes = 0;
68 }
69
70 void allocate_slot(vk::Device device, vk::PhysicalDevice phys,
71 VulkanBufferSlot& slot, size_t byte_size)
72 {
73 free_slot(device, slot);
74
75 vk::BufferCreateInfo bi;
76 bi.size = byte_size;
77 bi.usage = vk::BufferUsageFlagBits::eStorageBuffer;
78 bi.sharingMode = vk::SharingMode::eExclusive;
79 slot.buffer = device.createBuffer(bi);
80
81 auto req = device.getBufferMemoryRequirements(slot.buffer);
82
83 vk::MemoryAllocateInfo ai;
84 ai.allocationSize = req.size;
85 ai.memoryTypeIndex = find_memory_type(phys, req.memoryTypeBits,
86 vk::MemoryPropertyFlagBits::eHostVisible
87 | vk::MemoryPropertyFlagBits::eHostCoherent);
88
89 slot.memory = device.allocateMemory(ai);
90 device.bindBufferMemory(slot.buffer, slot.memory, 0);
91 slot.mapped_ptr = device.mapMemory(slot.memory, 0, VK_WHOLE_SIZE);
92 slot.allocated_bytes = byte_size;
93 }
94
95 [[nodiscard]] vk::DescriptorType element_type_to_vk(GpuBufferBinding::ElementType et)
96 {
97 switch (et) {
99 return vk::DescriptorType::eStorageImage;
101 return vk::DescriptorType::eCombinedImageSampler;
102 default:
103 return vk::DescriptorType::eStorageBuffer;
104 }
105 }
106
107} // anonymous namespace
108
109//==============================================================================
110// Lifecycle
111//==============================================================================
112
114
119
121 const std::vector<GpuBufferBinding>& bindings)
122{
123 if (m_ready)
124 return true;
125
126 auto& foundry = Portal::Graphics::get_shader_foundry();
127 auto& compute_press = Portal::Graphics::get_compute_press();
128
129 m_shader_id = foundry.load_shader(config.shader_path);
132 "GpuResourceManager: failed to load shader '{}'", config.shader_path);
133 return false;
134 }
135
136 std::map<uint32_t, std::vector<Portal::Graphics::DescriptorBindingInfo>> by_set;
137 for (const auto& b : bindings) {
138 const auto et = b.element_type;
141 if (is_image) {
142 by_set[b.set].push_back({
143 .set = b.set,
144 .binding = b.binding,
145 .type = element_type_to_vk(et),
146 });
147 }
148 }
149
150 for (const auto& b : bindings) {
151 const auto et = b.element_type;
154 if (!is_image) {
155 by_set[b.set].push_back({
156 .set = b.set,
157 .binding = b.binding,
158 .type = vk::DescriptorType::eStorageBuffer,
159 });
160 }
161 }
162
163 std::vector<std::vector<Portal::Graphics::DescriptorBindingInfo>> descriptor_sets;
164 descriptor_sets.reserve(by_set.size());
165 for (auto& [set_idx, set_bindings] : by_set)
166 descriptor_sets.push_back(std::move(set_bindings));
167
168 m_pipeline_id = compute_press.create_pipeline(
169 m_shader_id, descriptor_sets, config.push_constant_size);
170
173 "GpuResourceManager: failed to create pipeline for '{}'",
174 config.shader_path);
175 foundry.destroy_shader(m_shader_id);
177 return false;
178 }
179
180 m_descriptor_set_ids = compute_press.allocate_pipeline_descriptors(m_pipeline_id);
181 if (m_descriptor_set_ids.empty()) {
183 "GpuResourceManager: failed to allocate descriptor sets");
184 compute_press.destroy_pipeline(m_pipeline_id);
186 foundry.destroy_shader(m_shader_id);
188 return false;
189 }
190
191 m_impl = std::make_unique<GpuResourceManagerImpl>();
192 m_impl->buffers.resize(bindings.size());
193 m_buffer_slots.resize(bindings.size());
194 m_image_slots.resize(bindings.size());
195
196 m_ready = true;
197 return true;
198}
199
201{
202 if (!m_ready) {
203 return;
204 }
205
206 auto& foundry = Portal::Graphics::get_shader_foundry();
207 auto& compute_press = Portal::Graphics::get_compute_press();
208 auto device = foundry.get_device();
209
210 if (m_impl) {
211 for (auto& slot : m_impl->buffers) {
212 free_slot(device, slot);
213 }
214 }
215 m_impl.reset();
216 m_buffer_slots.clear();
217 m_image_slots.clear();
218
220 compute_press.destroy_pipeline(m_pipeline_id);
222 }
223
225 foundry.destroy_shader(m_shader_id);
227 }
228
229 m_descriptor_set_ids.clear();
230 m_ready = false;
231}
232
233//==============================================================================
234// Buffer operations
235//==============================================================================
236
237void GpuResourceManager::ensure_buffer(size_t index, size_t required_bytes)
238{
239 auto& vk_slot = m_impl->buffers[index];
240 if (vk_slot.allocated_bytes >= required_bytes) {
241 return;
242 }
243
244 auto& foundry = Portal::Graphics::get_shader_foundry();
245 allocate_slot(foundry.get_device(), foundry.get_physical_device(),
246 vk_slot, required_bytes);
247
248 m_buffer_slots[index].allocated_bytes = required_bytes;
249}
250
251void GpuResourceManager::upload(size_t index, const float* data, size_t byte_size)
252{
253 auto& vk_slot = m_impl->buffers[index];
254 std::memcpy(vk_slot.mapped_ptr, data, byte_size);
255}
256
257void GpuResourceManager::upload_raw(size_t index, const uint8_t* data, size_t byte_size)
258{
259 auto& vk_slot = m_impl->buffers[index];
260 std::memcpy(vk_slot.mapped_ptr, data, byte_size);
261}
262
263void GpuResourceManager::download(size_t index, float* dest, size_t byte_size)
264{
265 auto& vk_slot = m_impl->buffers[index];
266 std::memcpy(dest, vk_slot.mapped_ptr, byte_size);
267}
268
270{
271 auto& foundry = Portal::Graphics::get_shader_foundry();
272 auto& vk_slot = m_impl->buffers[index];
273
274 foundry.update_descriptor_buffer(
276 spec.binding,
277 vk::DescriptorType::eStorageBuffer,
278 vk_slot.buffer, 0, vk_slot.allocated_bytes);
279}
280
282{
283 return m_buffer_slots[index].allocated_bytes;
284}
285
287 size_t index,
288 const std::shared_ptr<Core::VKImage>& image,
289 const GpuBufferBinding& spec)
290{
291 auto& foundry = Portal::Graphics::get_shader_foundry();
292
293 if (index >= m_image_slots.size())
294 m_image_slots.resize(index + 1);
295 m_image_slots[index] = image;
296
297 foundry.update_descriptor_storage_image(
299 spec.binding,
300 image->get_image_view(),
301 vk::ImageLayout::eGeneral);
302}
303
305 size_t index,
306 const std::shared_ptr<Core::VKImage>& image,
307 vk::Sampler sampler,
308 const GpuBufferBinding& spec)
309{
310 auto& foundry = Portal::Graphics::get_shader_foundry();
311
312 if (index >= m_image_slots.size())
313 m_image_slots.resize(index + 1);
314 m_image_slots[index] = image;
315
316 foundry.update_descriptor_image(
318 spec.binding,
319 image->get_image_view(),
320 sampler,
321 vk::ImageLayout::eShaderReadOnlyOptimal);
322}
323
325 const std::shared_ptr<Core::VKImage>& image,
326 vk::ImageLayout old_layout,
327 vk::ImageLayout new_layout)
328{
329 auto& foundry = Portal::Graphics::get_shader_foundry();
330 auto& backend = Portal::Graphics::get_texture_manager(); // TextureLoom -> backend ref
331
332 backend.transition_layout(
333 image,
334 old_layout,
335 new_layout,
336 1, 1, vk::ImageAspectFlagBits::eColor);
337}
338
339//==============================================================================
340// Dispatch
341//==============================================================================
342
344 const std::array<uint32_t, 3>& groups,
345 const std::vector<GpuBufferBinding>& bindings,
346 const uint8_t* push_constant_data,
347 size_t push_constant_size)
348{
349 auto& foundry = Portal::Graphics::get_shader_foundry();
350 auto& compute_press = Portal::Graphics::get_compute_press();
351
352 auto cmd_id = foundry.begin_commands(
354
355 compute_press.bind_all(
357 push_constant_data, push_constant_size);
358
359 compute_press.dispatch(cmd_id, groups[0], groups[1], groups[2]);
360
361 for (size_t i = 0; i < bindings.size(); ++i) {
362 const auto et = bindings[i].element_type;
365 const bool is_output = bindings[i].direction == GpuBufferBinding::Direction::OUTPUT
366 || bindings[i].direction == GpuBufferBinding::Direction::INPUT_OUTPUT;
367 if (is_output && !is_image) {
368 foundry.buffer_barrier(
369 cmd_id,
370 m_impl->buffers[i].buffer,
371 vk::AccessFlagBits::eShaderWrite,
372 vk::AccessFlagBits::eHostRead,
373 vk::PipelineStageFlagBits::eComputeShader,
374 vk::PipelineStageFlagBits::eHost);
375 }
376 }
377
378 foundry.submit_and_wait(cmd_id);
379}
380
382 uint32_t pass_count,
383 const std::array<uint32_t, 3>& groups,
384 const std::vector<GpuBufferBinding>& bindings,
385 const std::function<void(uint32_t pass, std::vector<uint8_t>&)>& push_constant_updater,
386 size_t push_constant_size,
387 const std::unordered_map<std::string, std::any>& execution_metadata)
388{
389 auto& foundry = Portal::Graphics::get_shader_foundry();
390 auto& compute_press = Portal::Graphics::get_compute_press();
391
392 const uint32_t workgroups_per_pass = groups[0] * groups[1] * groups[2];
393
394 const uint32_t default_passes = std::max(1U, 65536U / std::max(1U, workgroups_per_pass));
395 const uint32_t passes_per_batch = [&] {
396 auto it = execution_metadata.find("passes_per_batch");
397 if (it != execution_metadata.end())
398 return safe_any_cast_or_default<uint32_t>(it->second, default_passes);
399 return default_passes;
400 }();
401
402 for (uint32_t base = 0; base < pass_count; base += passes_per_batch) {
403 const uint32_t batch_end = std::min(base + passes_per_batch, pass_count);
404
405 auto cmd_id = foundry.begin_commands(
407
408 for (uint32_t pass = base; pass < batch_end; ++pass) {
409 std::vector<uint8_t> pc_data(push_constant_size);
410 push_constant_updater(pass, pc_data);
411
412 compute_press.bind_all(
414 pc_data.data(), push_constant_size);
415
416 compute_press.dispatch(cmd_id, groups[0], groups[1], groups[2]);
417
418 for (size_t i = 0; i < bindings.size(); ++i) {
419 if (bindings[i].direction == GpuBufferBinding::Direction::INPUT_OUTPUT) {
420 foundry.buffer_barrier(
421 cmd_id,
422 m_impl->buffers[i].buffer,
423 vk::AccessFlagBits::eShaderWrite | vk::AccessFlagBits::eShaderRead,
424 vk::AccessFlagBits::eShaderWrite | vk::AccessFlagBits::eShaderRead,
425 vk::PipelineStageFlagBits::eComputeShader,
426 vk::PipelineStageFlagBits::eComputeShader);
427 }
428 }
429 }
430
431 for (size_t i = 0; i < bindings.size(); ++i) {
432 if (bindings[i].direction == GpuBufferBinding::Direction::OUTPUT
433 || bindings[i].direction == GpuBufferBinding::Direction::INPUT_OUTPUT) {
434 foundry.buffer_barrier(
435 cmd_id,
436 m_impl->buffers[i].buffer,
437 vk::AccessFlagBits::eShaderWrite,
438 vk::AccessFlagBits::eHostRead,
439 vk::PipelineStageFlagBits::eComputeShader,
440 vk::PipelineStageFlagBits::eHost);
441 }
442 }
443
444 foundry.submit_and_wait(cmd_id);
445 }
446}
447
449 const std::array<uint32_t, 3>& groups,
450 const std::vector<GpuBufferBinding>& bindings,
451 const uint8_t* push_constant_data,
452 size_t push_constant_size)
453{
454 auto& foundry = Portal::Graphics::get_shader_foundry();
455 auto& compute_press = Portal::Graphics::get_compute_press();
456
457 auto cmd_id = foundry.begin_commands(
459
460 compute_press.bind_all(
462 push_constant_data, push_constant_size);
463
464 compute_press.dispatch(cmd_id, groups[0], groups[1], groups[2]);
465
466 for (size_t i = 0; i < bindings.size(); ++i) {
467 const auto et = bindings[i].element_type;
470 const bool is_output = bindings[i].direction == GpuBufferBinding::Direction::OUTPUT
471 || bindings[i].direction == GpuBufferBinding::Direction::INPUT_OUTPUT;
472 if (is_output && !is_image) {
473 foundry.buffer_barrier(
474 cmd_id,
475 m_impl->buffers[i].buffer,
476 vk::AccessFlagBits::eShaderWrite,
477 vk::AccessFlagBits::eHostRead,
478 vk::PipelineStageFlagBits::eComputeShader,
479 vk::PipelineStageFlagBits::eHost);
480 }
481 }
482
483 return foundry.submit_async(cmd_id);
484}
485
486} // namespace MayaFlux::Yantra
#define MF_ERROR(comp, ctx,...)
IO::ImageData image
Definition Decoder.cpp:57
size_t b
size_t buffer_allocated_bytes(size_t index) const
void upload_raw(size_t index, const uint8_t *data, size_t byte_size)
void upload(size_t index, const float *data, size_t byte_size)
Portal::Graphics::ComputePipelineID m_pipeline_id
std::vector< std::shared_ptr< Core::VKImage > > m_image_slots
Portal::Graphics::FenceID dispatch_async(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::vector< Portal::Graphics::DescriptorSetID > m_descriptor_set_ids
void download(size_t index, float *dest, size_t byte_size)
void bind_image_storage(size_t index, const std::shared_ptr< Core::VKImage > &image, const GpuBufferBinding &spec)
Bind a storage image descriptor at the given slot index.
void dispatch_batched(uint32_t pass_count, const std::array< uint32_t, 3 > &groups, const std::vector< GpuBufferBinding > &bindings, const std::function< void(uint32_t pass, std::vector< uint8_t > &)> &push_constant_updater, size_t push_constant_size, const std::unordered_map< std::string, std::any > &execution_metadata={})
bool initialise(const GpuShaderConfig &config, const std::vector< GpuBufferBinding > &bindings)
void bind_image_sampled(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.
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 ensure_buffer(size_t index, size_t required_bytes)
void dispatch(const std::array< uint32_t, 3 > &groups, const std::vector< GpuBufferBinding > &bindings, const uint8_t *push_constant_data, size_t push_constant_size)
void bind_descriptor(size_t index, const GpuBufferBinding &spec)
std::unique_ptr< GpuResourceManagerImpl > m_impl
@ 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
MAYAFLUX_API ComputePress & get_compute_press()
bool is_image(const fs::path &filepath)
Definition Depot.cpp:108
ElementType
Element type the shader expects in this buffer.
Declares a single storage buffer the shader expects.
std::vector< VulkanBufferSlot > buffers
Plain-data description of the compute shader to dispatch.