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 size_t allocated_bytes {};
18};
19
20//==============================================================================
21// PIMPL
22//==============================================================================
23
25 std::vector<VulkanBufferSlot> buffers;
26};
27
28//==============================================================================
29// File-local helpers
30//==============================================================================
31
32namespace {
33
34 uint32_t find_memory_type(vk::PhysicalDevice phys,
35 uint32_t type_filter,
36 vk::MemoryPropertyFlags props)
37 {
38 auto mem_props = phys.getMemoryProperties();
39 for (uint32_t i = 0; i < mem_props.memoryTypeCount; ++i) {
40 if ((type_filter & (1U << i))
41 && (mem_props.memoryTypes[i].propertyFlags & props) == props) {
42 return i;
43 }
44 }
45 error<std::runtime_error>(
48 std::source_location::current(),
49 "GpuResourceManager: no suitable memory type found");
50 }
51
52 void free_slot(vk::Device device, VulkanBufferSlot& slot)
53 {
54 if (slot.buffer) {
55 device.destroyBuffer(slot.buffer);
56 slot.buffer = vk::Buffer {};
57 }
58 if (slot.memory) {
59 device.freeMemory(slot.memory);
60 slot.memory = vk::DeviceMemory {};
61 }
62 slot.allocated_bytes = 0;
63 }
64
65 void allocate_slot(vk::Device device, vk::PhysicalDevice phys,
66 VulkanBufferSlot& slot, size_t byte_size)
67 {
68 free_slot(device, slot);
69
70 vk::BufferCreateInfo bi;
71 bi.size = byte_size;
72 bi.usage = vk::BufferUsageFlagBits::eStorageBuffer;
73 bi.sharingMode = vk::SharingMode::eExclusive;
74 slot.buffer = device.createBuffer(bi);
75
76 auto req = device.getBufferMemoryRequirements(slot.buffer);
77
78 vk::MemoryAllocateInfo ai;
79 ai.allocationSize = req.size;
80 ai.memoryTypeIndex = find_memory_type(phys, req.memoryTypeBits,
81 vk::MemoryPropertyFlagBits::eHostVisible
82 | vk::MemoryPropertyFlagBits::eHostCoherent);
83
84 slot.memory = device.allocateMemory(ai);
85 device.bindBufferMemory(slot.buffer, slot.memory, 0);
86 slot.allocated_bytes = byte_size;
87 }
88
89 void map_copy_unmap(vk::Device device, vk::DeviceMemory memory,
90 const void* src, size_t byte_size)
91 {
92 void* mapped = device.mapMemory(memory, 0, byte_size);
93 std::memcpy(mapped, src, byte_size);
94 device.unmapMemory(memory);
95 }
96
97} // anonymous namespace
98
99//==============================================================================
100// Lifecycle
101//==============================================================================
102
104
109
111 const std::vector<GpuBufferBinding>& bindings)
112{
113 if (m_ready) {
114 return true;
115 }
116
117 auto& foundry = Portal::Graphics::get_shader_foundry();
118 auto& compute_press = Portal::Graphics::get_compute_press();
119
120 m_shader_id = foundry.load_shader(config.shader_path);
123 "GpuResourceManager: failed to load shader '{}'", config.shader_path);
124 return false;
125 }
126
127 m_pipeline_id = compute_press.create_pipeline_auto(
129
132 "GpuResourceManager: failed to create pipeline for '{}'",
133 config.shader_path);
134 foundry.destroy_shader(m_shader_id);
136 return false;
137 }
138
139 m_descriptor_set_ids = compute_press.allocate_pipeline_descriptors(m_pipeline_id);
140 if (m_descriptor_set_ids.empty()) {
142 "GpuResourceManager: failed to allocate descriptor sets");
143 compute_press.destroy_pipeline(m_pipeline_id);
145 foundry.destroy_shader(m_shader_id);
147 return false;
148 }
149
150 m_impl = std::make_unique<GpuResourceManagerImpl>();
151 m_impl->buffers.resize(bindings.size());
152 m_buffer_slots.resize(bindings.size());
153 m_image_slots.resize(bindings.size());
154
155 m_ready = true;
156 return true;
157}
158
160{
161 if (!m_ready) {
162 return;
163 }
164
165 auto& foundry = Portal::Graphics::get_shader_foundry();
166 auto& compute_press = Portal::Graphics::get_compute_press();
167 auto device = foundry.get_device();
168
169 if (m_impl) {
170 for (auto& slot : m_impl->buffers) {
171 free_slot(device, slot);
172 }
173 }
174 m_impl.reset();
175 m_buffer_slots.clear();
176 m_image_slots.clear();
177
179 compute_press.destroy_pipeline(m_pipeline_id);
181 }
182
184 foundry.destroy_shader(m_shader_id);
186 }
187
188 m_descriptor_set_ids.clear();
189 m_ready = false;
190}
191
192//==============================================================================
193// Buffer operations
194//==============================================================================
195
196void GpuResourceManager::ensure_buffer(size_t index, size_t required_bytes)
197{
198 auto& vk_slot = m_impl->buffers[index];
199 if (vk_slot.allocated_bytes >= required_bytes) {
200 return;
201 }
202
203 auto& foundry = Portal::Graphics::get_shader_foundry();
204 allocate_slot(foundry.get_device(), foundry.get_physical_device(),
205 vk_slot, required_bytes);
206
207 m_buffer_slots[index].allocated_bytes = required_bytes;
208}
209
210void GpuResourceManager::upload(size_t index, const float* data, size_t byte_size)
211{
212 auto& foundry = Portal::Graphics::get_shader_foundry();
213 auto& vk_slot = m_impl->buffers[index];
214 map_copy_unmap(foundry.get_device(), vk_slot.memory, data, byte_size);
215}
216
217void GpuResourceManager::upload_raw(size_t index, const uint8_t* data, size_t byte_size)
218{
219 auto& foundry = Portal::Graphics::get_shader_foundry();
220 auto& vk_slot = m_impl->buffers[index];
221 map_copy_unmap(foundry.get_device(), vk_slot.memory, data, byte_size);
222}
223
224void GpuResourceManager::download(size_t index, float* dest, size_t byte_size)
225{
226 auto& foundry = Portal::Graphics::get_shader_foundry();
227 auto device = foundry.get_device();
228 auto& vk_slot = m_impl->buffers[index];
229
230 void* mapped = device.mapMemory(vk_slot.memory, 0, VK_WHOLE_SIZE);
231 std::memcpy(dest, mapped, byte_size);
232 device.unmapMemory(vk_slot.memory);
233}
234
236{
237 auto& foundry = Portal::Graphics::get_shader_foundry();
238 auto& vk_slot = m_impl->buffers[index];
239
240 foundry.update_descriptor_buffer(
242 spec.binding,
243 vk::DescriptorType::eStorageBuffer,
244 vk_slot.buffer, 0, vk_slot.allocated_bytes);
245}
246
248{
249 return m_buffer_slots[index].allocated_bytes;
250}
251
253 size_t index,
254 const std::shared_ptr<Core::VKImage>& image,
255 const GpuBufferBinding& spec)
256{
257 auto& foundry = Portal::Graphics::get_shader_foundry();
258
259 if (index >= m_image_slots.size())
260 m_image_slots.resize(index + 1);
261 m_image_slots[index] = image;
262
263 foundry.update_descriptor_storage_image(
265 spec.binding,
266 image->get_image_view(),
267 vk::ImageLayout::eGeneral);
268}
269
271 size_t index,
272 const std::shared_ptr<Core::VKImage>& image,
273 vk::Sampler sampler,
274 const GpuBufferBinding& spec)
275{
276 auto& foundry = Portal::Graphics::get_shader_foundry();
277
278 if (index >= m_image_slots.size())
279 m_image_slots.resize(index + 1);
280 m_image_slots[index] = image;
281
282 foundry.update_descriptor_image(
284 spec.binding,
285 image->get_image_view(),
286 sampler,
287 vk::ImageLayout::eShaderReadOnlyOptimal);
288}
289
291 const std::shared_ptr<Core::VKImage>& image,
292 vk::ImageLayout old_layout,
293 vk::ImageLayout new_layout)
294{
295 auto& foundry = Portal::Graphics::get_shader_foundry();
296 auto& backend = Portal::Graphics::get_texture_manager(); // TextureLoom -> backend ref
297
298 backend.transition_layout(
299 image,
300 old_layout,
301 new_layout,
302 1, 1, vk::ImageAspectFlagBits::eColor);
303}
304
305//==============================================================================
306// Dispatch
307//==============================================================================
308
310 const std::array<uint32_t, 3>& groups,
311 const std::vector<GpuBufferBinding>& bindings,
312 const uint8_t* push_constant_data,
313 size_t push_constant_size)
314{
315 auto& foundry = Portal::Graphics::get_shader_foundry();
316 auto& compute_press = Portal::Graphics::get_compute_press();
317
318 auto cmd_id = foundry.begin_commands(
320
321 compute_press.bind_all(
323 push_constant_data, push_constant_size);
324
325 compute_press.dispatch(cmd_id, groups[0], groups[1], groups[2]);
326
327 for (size_t i = 0; i < bindings.size(); ++i) {
328 const auto et = bindings[i].element_type;
331 const bool is_output = bindings[i].direction == GpuBufferBinding::Direction::OUTPUT
332 || bindings[i].direction == GpuBufferBinding::Direction::INPUT_OUTPUT;
333 if (is_output && !is_image) {
334 foundry.buffer_barrier(
335 cmd_id,
336 m_impl->buffers[i].buffer,
337 vk::AccessFlagBits::eShaderWrite,
338 vk::AccessFlagBits::eHostRead,
339 vk::PipelineStageFlagBits::eComputeShader,
340 vk::PipelineStageFlagBits::eHost);
341 }
342 }
343
344 foundry.submit_and_wait(cmd_id);
345}
346
348 uint32_t pass_count,
349 const std::array<uint32_t, 3>& groups,
350 const std::vector<GpuBufferBinding>& bindings,
351 const std::function<void(uint32_t pass, std::vector<uint8_t>&)>& push_constant_updater,
352 size_t push_constant_size,
353 const std::unordered_map<std::string, std::any>& execution_metadata)
354{
355 auto& foundry = Portal::Graphics::get_shader_foundry();
356 auto& compute_press = Portal::Graphics::get_compute_press();
357
358 const uint32_t workgroups_per_pass = groups[0] * groups[1] * groups[2];
359
360 const uint32_t default_passes = std::max(1U, 65536U / std::max(1U, workgroups_per_pass));
361 const uint32_t passes_per_batch = [&] {
362 auto it = execution_metadata.find("passes_per_batch");
363 if (it != execution_metadata.end())
364 return safe_any_cast_or_default<uint32_t>(it->second, default_passes);
365 return default_passes;
366 }();
367
368 for (uint32_t base = 0; base < pass_count; base += passes_per_batch) {
369 const uint32_t batch_end = std::min(base + passes_per_batch, pass_count);
370
371 auto cmd_id = foundry.begin_commands(
373
374 for (uint32_t pass = base; pass < batch_end; ++pass) {
375 std::vector<uint8_t> pc_data(push_constant_size);
376 push_constant_updater(pass, pc_data);
377
378 compute_press.bind_all(
380 pc_data.data(), push_constant_size);
381
382 compute_press.dispatch(cmd_id, groups[0], groups[1], groups[2]);
383
384 for (size_t i = 0; i < bindings.size(); ++i) {
385 if (bindings[i].direction == GpuBufferBinding::Direction::INPUT_OUTPUT) {
386 foundry.buffer_barrier(
387 cmd_id,
388 m_impl->buffers[i].buffer,
389 vk::AccessFlagBits::eShaderWrite | vk::AccessFlagBits::eShaderRead,
390 vk::AccessFlagBits::eShaderWrite | vk::AccessFlagBits::eShaderRead,
391 vk::PipelineStageFlagBits::eComputeShader,
392 vk::PipelineStageFlagBits::eComputeShader);
393 }
394 }
395 }
396
397 for (size_t i = 0; i < bindings.size(); ++i) {
398 if (bindings[i].direction == GpuBufferBinding::Direction::OUTPUT
399 || bindings[i].direction == GpuBufferBinding::Direction::INPUT_OUTPUT) {
400 foundry.buffer_barrier(
401 cmd_id,
402 m_impl->buffers[i].buffer,
403 vk::AccessFlagBits::eShaderWrite,
404 vk::AccessFlagBits::eHostRead,
405 vk::PipelineStageFlagBits::eComputeShader,
406 vk::PipelineStageFlagBits::eHost);
407 }
408 }
409
410 foundry.submit_and_wait(cmd_id);
411 }
412}
413
414} // namespace MayaFlux::Yantra
#define MF_ERROR(comp, ctx,...)
IO::ImageData image
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
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:43
Declares a single storage buffer the shader expects.
std::vector< VulkanBufferSlot > buffers
Plain-data description of the compute shader to dispatch.