MayaFlux 1.0.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
DataReadProcessor.cpp
Go to the documentation of this file.
2
3#include "StagingUtils.hpp"
4
19
20namespace MayaFlux::Buffers {
21
23 std::shared_ptr<VKBuffer> source;
26 std::vector<uint8_t> bytes;
27 std::optional<Kakshya::VertexLayout> layout;
28 std::optional<Kakshya::MeshData> mesh;
29 std::optional<Kakshya::ImageData> image;
31
36};
37
41
46
48
50{
52 return;
53 }
54
55 m_request.drain([](ReadRequest&) { });
56 m_request.push(ReadRequest { .fill = std::move(fill) });
57}
58
59void DataReadProcessor::read_bytes(size_t byte_count)
60{
61 arm([byte_count](DataReadProcessor& self, const std::shared_ptr<VKBuffer>& vk, PendingRead& pending) {
62 pending.handle = gpu_buffer_handle(vk);
63 return submit_read(pending, self.m_staging, vk->get_size_bytes(), byte_count,
64 vk->is_host_visible(), is_transfer_source(vk));
65 });
66}
67
68void DataReadProcessor::read_buffer(std::shared_ptr<VKBuffer> source, size_t byte_count)
69{
70 arm([source = std::move(source), byte_count](DataReadProcessor& self, const std::shared_ptr<VKBuffer>&, PendingRead& pending) {
71 if (!source || !source->is_initialized()) {
72 MF_RT_ERROR(Journal::Component::Buffers, Journal::Context::BufferProcessing,
73 "DataReadProcessor read_buffer requires an initialized source buffer");
74 return false;
75 }
76 pending.source = source;
77 pending.handle = gpu_buffer_handle(source);
78 return submit_read(pending, self.m_staging, source->get_size_bytes(), byte_count,
79 source->is_host_visible(), is_transfer_source(source));
80 });
81}
82
84{
85 arm([name = std::move(name)](DataReadProcessor& self, const std::shared_ptr<VKBuffer>& vk, PendingRead& pending) {
86 if (const auto volume = std::dynamic_pointer_cast<VolumeGridBuffer>(vk);
87 volume && volume->has_field(name)) {
88 const size_t bytes = volume->get_field_bytes(name);
89 pending.handle = {
90 .buffer = volume->read_handle(name),
91 .mapped_ptr = nullptr,
92 .size_bytes = bytes,
93 };
94 return submit_read(pending, self.m_staging, bytes, 0, false, true);
95 }
96
97 if (const auto network = std::dynamic_pointer_cast<NetworkGeometryBuffer>(vk);
98 network && network->has_state(name)) {
99 const size_t bytes = network->get_state_bytes(name);
100 pending.handle = gpu_buffer_handle(network->read_state_slot(name), bytes);
101 return submit_read(pending, self.m_staging, bytes, 0, true, true);
102 }
103
105 "DataReadProcessor read_field found no field named '{}' on the attached buffer", name);
106 return false;
107 });
108}
109
110void DataReadProcessor::read_back_buffer(size_t index, size_t byte_count)
111{
112 arm([index, byte_count](DataReadProcessor& self, const std::shared_ptr<VKBuffer>& vk, PendingRead& pending) {
113 const auto& slots = vk->get_buffer_resources().back_buffers;
114 if (index >= slots.size() || byte_count == 0) {
115 MF_RT_ERROR(Journal::Component::Buffers, Journal::Context::BufferProcessing,
116 "DataReadProcessor read_back_buffer needs an existing slot and a nonzero byte count");
117 return false;
118 }
119 pending.handle = gpu_buffer_handle(slots[index], byte_count);
120 return submit_read(pending, self.m_staging, byte_count, byte_count, true, true);
121 });
122}
123
125{
126 arm([](DataReadProcessor& self, const std::shared_ptr<VKBuffer>& vk, PendingRead& pending) {
127 if (std::dynamic_pointer_cast<TextureBuffer>(vk)) {
129 "DataReadProcessor read_vertices does not read TextureBuffer's display quad");
130 return false;
131 }
132 if (vk->has_index_buffer()) {
134 "DataReadProcessor read_vertices cannot read an indexed buffer; use read_mesh");
135 return false;
136 }
137 pending.layout = resolve_vertex_layout(vk);
138 if (!pending.layout) {
140 "DataReadProcessor requires a valid vertex layout within the primary allocation");
141 return false;
142 }
143 pending.handle = gpu_buffer_handle(vk);
144 return submit_read(pending, self.m_staging, vk->get_size_bytes(),
145 static_cast<size_t>(pending.layout->vertex_count) * pending.layout->stride_bytes,
146 vk->is_host_visible(), is_transfer_source(vk));
147 });
148}
149
151{
152 arm([](DataReadProcessor& self, const std::shared_ptr<VKBuffer>& vk, PendingRead& pending) {
153 const auto keep_cpu_mesh = [&pending](std::optional<Kakshya::MeshData> mesh) {
154 if (!mesh) {
156 "DataReadProcessor read_mesh found no geometry to read");
157 return false;
158 }
159 pending.mesh = std::move(mesh);
160 return true;
161 };
162
163 if (const auto mesh_buffer = std::dynamic_pointer_cast<MeshBuffer>(vk)) {
164 return keep_cpu_mesh(mesh_buffer->get_mesh_data());
165 }
166
167 if (const auto network_buffer = std::dynamic_pointer_cast<MeshNetworkBuffer>(vk)) {
168 const auto network = network_buffer->get_network();
169 return keep_cpu_mesh(network ? network->get_mesh_data() : std::nullopt);
170 }
171
172 if (const auto geometry_buffer = std::dynamic_pointer_cast<GeometryBuffer>(vk)) {
173 const auto node = std::dynamic_pointer_cast<Nodes::GpuSync::MeshWriterNode>(
174 geometry_buffer->get_geometry_node());
175 return keep_cpu_mesh(node ? node->get_mesh_data() : std::nullopt);
176 }
177
178 std::optional<uint32_t> live_count;
179 if (const auto compute_mesh = std::dynamic_pointer_cast<ComputeMeshBuffer>(vk)) {
180 live_count = compute_mesh->get_live_vertex_count();
181 } else if (const auto volume = std::dynamic_pointer_cast<VolumeGridBuffer>(vk)) {
182 live_count = volume->get_live_vertex_count();
183 }
184
185 if (!live_count || *live_count == 0) {
187 "DataReadProcessor read_mesh requires a mesh, geometry or mesh network buffer, "
188 "or a compute mesh or volume surface with live vertices");
189 return false;
190 }
191 pending.triangle_soup = true;
192 pending.handle = gpu_buffer_handle(vk);
193 return submit_read(pending, self.m_staging, vk->get_size_bytes(),
194 static_cast<size_t>(*live_count) * sizeof(Kakshya::MeshVertex),
195 vk->is_host_visible(), is_transfer_source(vk));
196 });
197}
198
200{
201 arm([](DataReadProcessor&, const std::shared_ptr<VKBuffer>& vk, PendingRead& pending) {
202 const auto texture = std::dynamic_pointer_cast<TextureBuffer>(vk);
203 if (!texture || texture->get_pixel_data().empty()) {
205 "DataReadProcessor requires host pixel data for a host pixel read");
206 return false;
207 }
208
209 const auto format = texture->get_format();
211 texture->get_width(), texture->get_height(),
213 format, texture->get_pixel_data());
214 if (!pending.image) {
216 "DataReadProcessor host pixel data does not match the texture's format and size");
217 return false;
218 }
219 return true;
220 });
221}
222
224{
225 arm([](DataReadProcessor& self, const std::shared_ptr<VKBuffer>& vk, PendingRead& pending) {
227
228 std::shared_ptr<Core::VKImage> image;
229 if (const auto texture = std::dynamic_pointer_cast<TextureBuffer>(vk)) {
230 image = resolve_gpu_image(*texture);
231 } else if (const auto node_texture = std::dynamic_pointer_cast<NodeTextureBuffer>(vk)) {
232 image = resolve_gpu_image(*node_texture);
233 }
234
235 auto& loom = TextureLoom::instance();
236 if (!loom.is_initialized()) {
238 "DataReadProcessor texture pixel reads require an initialized TextureLoom");
239 return false;
240 }
241
242 const auto format = TextureLoom::readable_format(image);
243 if (!format) {
245 "DataReadProcessor texture pixel reads require an initialized TextureBuffer or NodeTextureBuffer "
246 "image that is a single-layer 2D image with a mapped format and transfer-source usage");
247 return false;
248 }
249
250 const auto original_layout = image->get_current_layout();
251 if (original_layout != vk::ImageLayout::eShaderReadOnlyOptimal
252 && original_layout != vk::ImageLayout::eGeneral) {
254 "DataReadProcessor texture image has no readable pixel data");
255 return false;
256 }
257
258 const size_t byte_count = TextureLoom::calculate_image_size(
259 image->get_width(), image->get_height(), 1, *format);
260 if (!self.m_texture_staging || self.m_texture_staging->get_size_bytes() < byte_count) {
262 }
263
264 vk::PipelineStageFlags restore_stage = vk::PipelineStageFlagBits::eFragmentShader;
265 if (original_layout == vk::ImageLayout::eGeneral) {
266 restore_stage |= vk::PipelineStageFlagBits::eComputeShader;
267 }
268
269 pending.image = loom.download_image(image, self.m_texture_staging, original_layout, restore_stage);
270 return pending.image.has_value();
271 });
272}
273
274bool DataReadProcessor::has_result() const noexcept
275{
276 return m_result_ready.load(std::memory_order_acquire);
277}
278
279bool DataReadProcessor::is_pending() const noexcept
280{
281 return !m_request.empty() || m_result_occupied.load(std::memory_order_acquire);
282}
283
284std::unique_ptr<DataReadProcessor::PendingRead> DataReadProcessor::collect_result(ReadKind kind)
285{
286 if (!m_result_ready.load(std::memory_order_acquire)) {
287 return nullptr;
288 }
289
290 std::unique_ptr<PendingRead> result;
291 m_result.sweep(
292 [kind](std::unique_ptr<PendingRead>& pending) {
293 switch (kind) {
294 case ReadKind::Bytes:
295 return !pending->mesh;
297 return pending->layout.has_value();
298 case ReadKind::Mesh:
299 return pending->mesh.has_value() || pending->triangle_soup;
300 case ReadKind::Pixels:
301 return pending->image.has_value();
302 }
303 return false;
304 },
305 [this, &result](std::unique_ptr<PendingRead>& pending) {
306 if (pending->transfer) {
308 pending->transfer,
309 generation_slot(pending->handle),
310 pending->bytes.data(),
311 pending->bytes.size(),
312 m_staging);
313 }
314 result = std::move(pending);
315 });
316 if (result) {
317 m_result_ready.store(false, std::memory_order_release);
318 m_result_occupied.store(false, std::memory_order_release);
319 }
320 return result;
321}
322
323std::optional<Kakshya::DataVariant> DataReadProcessor::resolve_bytes()
324{
325 auto pending = collect_result(ReadKind::Bytes);
326 if (!pending) {
327 return std::nullopt;
328 }
329
330 if (pending->image) {
331 const auto* begin = static_cast<const uint8_t*>(pending->image->data());
332 return Kakshya::DataVariant { std::vector<uint8_t>(begin, begin + pending->image->byte_size()) };
333 }
334
335 return Kakshya::DataVariant { std::move(pending->bytes) };
336}
337
338std::optional<std::vector<Kakshya::DataVariant>> DataReadProcessor::resolve_vertices()
339{
340 auto pending = collect_result(ReadKind::Vertices);
341 if (!pending || !pending->layout) {
342 return std::nullopt;
343 }
344
345 std::vector<Kakshya::DataVariant> channels;
347 if (!insertion.insert_interleaved(pending->bytes, *pending->layout)) {
348 return std::nullopt;
349 }
350
351 return channels;
352}
353
354std::optional<Kakshya::MeshData> DataReadProcessor::resolve_mesh()
355{
356 auto pending = collect_result(ReadKind::Mesh);
357 if (!pending) {
358 return std::nullopt;
359 }
360
361 if (pending->mesh) {
362 return std::move(pending->mesh);
363 }
364
365 return triangle_soup_mesh(pending->bytes);
366}
367
368std::optional<Kakshya::ImageData> DataReadProcessor::resolve_pixels()
369{
370 auto pending = collect_result(ReadKind::Pixels);
371 if (!pending) {
372 return std::nullopt;
373 }
374
375 return std::move(pending->image);
376}
377
378bool DataReadProcessor::is_compatible_with(const std::shared_ptr<Buffer>& buffer) const
379{
380 const auto vk_buffer = std::dynamic_pointer_cast<VKBuffer>(buffer);
381 if (!vk_buffer) {
382 return false;
383 }
384
385 if (std::dynamic_pointer_cast<MeshBuffer>(buffer)
386 || std::dynamic_pointer_cast<MeshNetworkBuffer>(buffer)
387 || std::dynamic_pointer_cast<GeometryBuffer>(buffer)
388 || std::dynamic_pointer_cast<NodeTextureBuffer>(buffer)) {
389 return true;
390 }
391
392 return vk_buffer->is_host_visible() || is_transfer_source(vk_buffer);
393}
394
395void DataReadProcessor::on_attach(const std::shared_ptr<Buffer>& buffer)
396{
398 error<std::invalid_argument>(
401 std::source_location::current(),
402 "DataReadProcessor requires a VKBuffer with readable primary storage");
403 }
404}
405
406void DataReadProcessor::on_detach(const std::shared_ptr<Buffer>&)
407{
408 m_request.drain([](ReadRequest&) { });
409 m_result.drain([](std::unique_ptr<PendingRead>&) { });
410 m_result_ready.store(false, std::memory_order_release);
411 m_result_occupied.store(false, std::memory_order_release);
412 m_staging.reset();
413 m_texture_staging.reset();
414}
415
417 PendingRead& pending,
418 std::shared_ptr<VKBuffer>& staging,
419 size_t available_bytes,
420 size_t byte_count,
421 bool mapped,
422 bool transferable)
423{
424 if (byte_count == 0) {
425 byte_count = available_bytes;
426 }
427
428 if (byte_count > available_bytes || !pending.handle.buffer) {
430 "DataReadProcessor requested {} bytes from a {} byte buffer",
431 byte_count, available_bytes);
432 return false;
433 }
434
435 pending.bytes.resize(byte_count);
436
437 if (byte_count != 0 && mapped && pending.handle.mapped_ptr) {
438 std::memcpy(pending.bytes.data(), pending.handle.mapped_ptr, byte_count);
439 } else if (byte_count != 0) {
440 if (!transferable) {
442 "DataReadProcessor source has no mapped memory or transfer-source usage");
443 return false;
444 }
445
447 generation_slot(pending.handle), byte_count, staging);
448
449 if (!pending.transfer) {
451 "DataReadProcessor failed to submit a device-local read");
452 return false;
453 }
454 }
455
456 return true;
457}
458
459void DataReadProcessor::processing_function(const std::shared_ptr<Buffer>& buffer)
460{
463 "DataReadProcessor attached to an incompatible buffer");
464 return;
465 }
466
467 if (m_result_occupied.load(std::memory_order_acquire)) {
468 return;
469 }
470
471 const auto vk_buffer = std::static_pointer_cast<VKBuffer>(buffer);
472 if (!vk_buffer->is_initialized()) {
473 return;
474 }
475
476 std::optional<ReadRequest> request;
477 m_request.drain([&request](ReadRequest& next) {
478 if (!request) {
479 request.emplace(std::move(next));
480 }
481 });
482 if (!request) {
483 return;
484 }
485
486 auto pending = std::make_unique<PendingRead>();
487 pending->source = vk_buffer;
488
489 if (!request->fill(*this, vk_buffer, *pending)) {
490 return;
491 }
492
493 m_result_occupied.store(true, std::memory_order_release);
494 m_result.push(std::move(pending));
495 m_result_ready.store(true, std::memory_order_release);
496}
497
498}
#define MF_RT_ERROR(comp, ctx,...)
Core::GlobalNetworkConfig network
Definition Config.cpp:43
IO::ImageData image
Definition Decoder.cpp:47
bool format
std::shared_ptr< NetworkGeometryBuffer > buffer
AttributeKind kind
std::string name
Definition VKDevice.cpp:123
uint32_t index
Definition VKDevice.cpp:122
uint32_t channels
std::shared_ptr< VKBuffer > m_texture_staging
void read_buffer(std::shared_ptr< VKBuffer > source, size_t byte_count=0)
Read another VKBuffer, such as a descriptor-bound or instance buffer.
void read_back_buffer(size_t index, size_t byte_count)
Read a raw back_buffers slot of the attached buffer.
std::optional< std::vector< Kakshya::DataVariant > > resolve_vertices()
Finish a read_vertices() read as typed channels.
static bool submit_read(PendingRead &pending, std::shared_ptr< VKBuffer > &staging, size_t available_bytes, size_t byte_count, bool mapped, bool transferable)
void on_attach(const std::shared_ptr< Buffer > &buffer) override
Called when this processor is attached to a buffer.
void processing_function(const std::shared_ptr< Buffer > &buffer) override
The core processing function that must be implemented by derived classes.
std::optional< Kakshya::DataVariant > resolve_bytes()
Finish the read as raw bytes.
std::optional< Kakshya::ImageData > resolve_pixels()
Finish a pixel read as typed ImageData with its format and dimensions.
Memory::SweepList< std::unique_ptr< PendingRead > > m_result
bool has_result() const noexcept
Whether a requested read has been submitted.
void read_bytes(size_t byte_count=0)
Read the attached buffer's primary storage.
void read_texture_pixels()
Download the base level of a TextureBuffer or NodeTextureBuffer image.
Memory::SweepList< ReadRequest > m_request
std::shared_ptr< VKBuffer > m_staging
std::optional< Kakshya::MeshData > resolve_mesh()
Finish a read_mesh() read as MeshData.
std::function< bool(DataReadProcessor &, const std::shared_ptr< VKBuffer > &, PendingRead &)> ReadFill
void read_host_pixels()
Read the host pixel data a TextureBuffer retains.
std::unique_ptr< PendingRead > collect_result(ReadKind kind)
void read_vertices()
Read the attached buffer's declared vertices; the layout count may represent capacity.
bool is_pending() const noexcept
Whether a read is armed, or its result has not been resolved yet.
bool is_compatible_with(const std::shared_ptr< Buffer > &buffer) const override
Checks if this processor can handle the specified buffer type.
void read_mesh()
Read the attached mesh.
void on_detach(const std::shared_ptr< Buffer > &buffer) override
Called when this processor is detached from a buffer.
void read_field(std::string name)
Read the current read slot of a named field on the attached buffer.
Reads selected buffer storage into owned host data, one snapshot per request.
bool insert_interleaved(std::span< const uint8_t > vertex_bytes, const VertexLayout &layout)
Replace the channel list with the attributes of interleaved vertices.
Write counterpart to VertexAccess: splits interleaved vertex bytes into typed channels.
static uint32_t get_channel_count(ImageFormat format)
Get the number of color channels for a given format.
Portal-level texture creation and management.
void resolve_transfer(TransferHandle &handle)
Wait for a transfer and release its resources.
std::shared_ptr< VKBuffer > create_image_staging_buffer(size_t size)
Allocate a persistent host-visible staging buffer sized for repeated streaming uploads to an image of...
std::shared_ptr< void > TransferHandle
Opaque handle to an in-flight transfer.
std::shared_ptr< Core::VKImage > resolve_gpu_image(const T &buffer)
Resolve the GPU-resident image from any GpuImageSource buffer.
Portal::Graphics::GpuBufferHandle gpu_buffer_handle(const std::shared_ptr< VKBuffer > &buffer)
View a buffer's primary GPU storage without reading it back.
@ GRAPHICS_BACKEND
Standard graphics processing backend configuration.
std::optional< Kakshya::MeshData > triangle_soup_mesh(std::span< const uint8_t > vertex_bytes)
Wrap non-indexed triangle vertices as MeshData.
VKBufferResources::GenerationSlot generation_slot(const Portal::Graphics::GpuBufferHandle &handle)
Express a buffer handle as the slot the back_buffers transfer helpers take.
std::optional< Kakshya::VertexLayout > resolve_vertex_layout(const std::shared_ptr< VKBuffer > &source)
Resolve the vertex extent of a buffer's primary storage for reading.
void resolve_back_buffer_read(TransferHandle &handle, const VKBufferResources::GenerationSlot &slot, void *data, size_t size, std::shared_ptr< VKBuffer > &staging, size_t staging_offset)
Wait for a recorded download, then copy it out of staging.
bool is_transfer_source(const std::shared_ptr< VKBuffer > &buffer)
Whether a buffer's memory can be the source of a transfer.
TransferHandle download_back_buffer_async(const VKBufferResources::GenerationSlot &slot, size_t size, std::shared_ptr< VKBuffer > &staging, size_t staging_offset)
Record a slot-to-staging copy without waiting for completion.
@ BufferProcessing
Buffer processing (Buffers::BufferManager, processing chains)
@ Buffers
Buffers, Managers, processors and processing chains.
std::variant< std::vector< double >, std::vector< float >, std::vector< uint8_t >, std::vector< uint16_t >, std::vector< uint32_t >, std::vector< std::complex< float > >, std::vector< std::complex< double > >, std::vector< glm::vec2 >, std::vector< glm::vec3 >, std::vector< glm::vec4 >, std::vector< glm::mat4 > > DataVariant
Multi-type data storage for different precision needs.
Definition NDData.hpp:70
std::optional< Kakshya::VertexLayout > layout
static std::optional< ImageData > from_bytes(uint32_t width, uint32_t height, uint32_t channels, ImageFormat format, std::span< const uint8_t > bytes)
Build an ImageData from raw pixel bytes in format's layout.
Definition ImageData.cpp:57
Vertex type for indexed triangle mesh primitives (TRIANGLE_LIST topology)
Non-owning view of a GPU storage buffer produced by a compute path.