MayaFlux 1.0.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
DataWriteProcessor.cpp
Go to the documentation of this file.
2
4#include "StagingUtils.hpp"
5
15
21
23
25
26namespace MayaFlux::Buffers {
27
28namespace {
29
30 void ensure_staging(std::shared_ptr<VKBuffer>& staging, size_t size)
31 {
32 if (!staging || staging->get_size_bytes() < size) {
33 staging = create_staging_buffer(size);
34 }
35 }
36
37}
38
40 : m_writes(std::make_unique<Memory::LockFreeQueue<WriteFn, k_write_queue_capacity>>())
41{
43}
44
46{
47 if (!m_writes->push(fn)) {
49 "DataWriteProcessor write queue is full; the write was dropped");
50 }
51}
52
53void DataWriteProcessor::write_buffer(std::shared_ptr<VKBuffer> target, Kakshya::DataVariant data, size_t dst_offset)
54{
55 auto payload = std::make_shared<Kakshya::DataVariant>(std::move(data));
56 enqueue_write([target = std::move(target), payload, dst_offset](DataWriteProcessor& self, const std::shared_ptr<VKBuffer>&) {
57 const auto bytes = Kakshya::convert_variant<uint8_t>(*payload);
58 if (!target || !target->is_initialized() || bytes.size_bytes() == 0
59 || dst_offset + bytes.size_bytes() > target->get_size_bytes()) {
61 "DataWriteProcessor write_buffer needs an initialized target with room for the data");
62 return;
63 }
64 if (is_device_local(target)) {
65 ensure_staging(self.m_write_staging, bytes.size_bytes());
66 }
67 upload_to_gpu(bytes.data(), bytes.size_bytes(), target, self.m_write_staging, dst_offset);
68 });
69}
70
72{
73 auto payload = std::make_shared<Kakshya::DataVariant>(std::move(data));
74 enqueue_write([name = std::move(name), payload](DataWriteProcessor& self, const std::shared_ptr<VKBuffer>& vk) {
75 const auto bytes = Kakshya::convert_variant<uint8_t>(*payload);
76 const size_t size = bytes.size_bytes();
77
78 if (const auto volume = std::dynamic_pointer_cast<VolumeGridBuffer>(vk);
79 volume && volume->has_field(name)) {
80 if (size != volume->get_field_bytes(name)) {
82 "DataWriteProcessor write_field '{}' needs exactly {} bytes", name, volume->get_field_bytes(name));
83 return;
84 }
85 volume->seed_raw(name, bytes.data(), size);
86 return;
87 }
88
89 if (const auto network = std::dynamic_pointer_cast<NetworkGeometryBuffer>(vk);
90 network && network->has_state(name)) {
91 if (size != network->get_state_bytes(name)) {
93 "DataWriteProcessor write_field '{}' needs exactly {} bytes", name, network->get_state_bytes(name));
94 return;
95 }
96 const auto slot = network->read_state_slot(name);
97 if (!slot.mapped_ptr) {
98 ensure_staging(self.m_write_staging, size);
99 }
100 upload_back_buffer(slot, bytes.data(), size, self.m_write_staging);
101 return;
102 }
103
105 "DataWriteProcessor write_field found no field named '{}' on the attached buffer", name);
106 });
107}
108
110{
111 auto payload = std::make_shared<Kakshya::DataVariant>(std::move(data));
112 enqueue_write([index, payload](DataWriteProcessor& self, const std::shared_ptr<VKBuffer>& vk) {
113 const auto bytes = Kakshya::convert_variant<uint8_t>(*payload);
114 const auto& slots = vk->get_buffer_resources().back_buffers;
115 if (index >= slots.size() || bytes.size_bytes() == 0) {
117 "DataWriteProcessor write_back_buffer needs an existing slot and nonempty data");
118 return;
119 }
120 if (!slots[index].mapped_ptr) {
121 ensure_staging(self.m_write_staging, bytes.size_bytes());
122 }
123 upload_back_buffer(slots[index], bytes.data(), bytes.size_bytes(), self.m_write_staging);
124 });
125}
126
128{
129 auto mesh = std::make_shared<Kakshya::MeshData>(std::move(data));
130 enqueue_write([mesh, slot = std::move(slot)](DataWriteProcessor&, const std::shared_ptr<VKBuffer>& vk) {
131 if (const auto mesh_buffer = std::dynamic_pointer_cast<MeshBuffer>(vk)) {
132 mesh_buffer->set_mesh_data(std::move(*mesh));
133 return;
134 }
135
136 if (const auto geometry_buffer = std::dynamic_pointer_cast<GeometryBuffer>(vk)) {
137 if (const auto node = std::dynamic_pointer_cast<Nodes::GpuSync::MeshWriterNode>(
138 geometry_buffer->get_geometry_node())) {
139 node->set_mesh(*mesh);
140 return;
141 }
142 }
143
144 if (const auto network_buffer = std::dynamic_pointer_cast<MeshNetworkBuffer>(vk)) {
145 const auto network = network_buffer->get_network();
146 const auto* found = network ? network->find_slot(slot) : nullptr;
147 if (found && found->node) {
148 found->node->set_mesh(*mesh);
149 return;
150 }
151 }
152
154 "DataWriteProcessor write_mesh needs a MeshBuffer, a GeometryBuffer driven by a MeshWriterNode, "
155 "or a MeshNetworkBuffer with a named slot that has a node");
156 });
157}
158
160{
161 auto payload = std::make_shared<Kakshya::ImageData>(std::move(image));
162 enqueue_write([payload](DataWriteProcessor&, const std::shared_ptr<VKBuffer>& vk) {
163 const auto texture = std::dynamic_pointer_cast<TextureBuffer>(vk);
164 if (!texture || payload->format != texture->get_format()
165 || payload->width != texture->get_width() || payload->height != texture->get_height()
166 || !payload->is_consistent()) {
168 "DataWriteProcessor write_pixels needs a TextureBuffer and an image of its format and size");
169 return;
170 }
171 texture->set_pixel_data(payload->data(), payload->byte_size());
172 });
173}
174
176{
177 auto payload = std::make_shared<Kakshya::ImageData>(std::move(image));
178 enqueue_write([payload](DataWriteProcessor& self, const std::shared_ptr<VKBuffer>& vk) {
180
181 const auto texture = std::dynamic_pointer_cast<TextureBuffer>(vk);
182 const auto gpu_image = texture ? resolve_gpu_image(*texture) : nullptr;
183 auto& loom = TextureLoom::instance();
184 const auto format = TextureLoom::writable_format(gpu_image);
185 if (!loom.is_initialized() || !format || payload->format != *format
186 || payload->width != gpu_image->get_width() || payload->height != gpu_image->get_height()
187 || !payload->is_consistent()) {
189 "DataWriteProcessor write_texture_pixels needs a TextureBuffer with a writable GPU image "
190 "and an image of its format and size");
191 return;
192 }
193
194 const size_t size = payload->byte_size();
195 if (!self.m_write_image_staging || self.m_write_image_staging->get_size_bytes() < size) {
197 }
198 if (self.m_write_image_staging) {
199 loom.upload_data(gpu_image, payload->data(), size, self.m_write_image_staging);
200 } else {
201 loom.upload_data(gpu_image, payload->data(), size);
202 }
203 });
204}
205
207{
208 m_data_pending.clear();
209 m_data_pending.push_back(std::move(variant));
210 m_data_dirty.test_and_set(std::memory_order_release);
211}
212
213void DataWriteProcessor::set_data(std::vector<Kakshya::DataVariant> variants)
214{
215 m_data_pending = std::move(variants);
216 m_data_dirty.test_and_set(std::memory_order_release);
217}
218
219void DataWriteProcessor::set_vertices(const void* data, size_t byte_count)
220{
221 const auto* src = static_cast<const uint8_t*>(data);
222 m_data_pending = { Kakshya::DataVariant { std::vector<uint8_t>(src, src + byte_count) } };
223 m_data_dirty.test_and_set(std::memory_order_release);
224}
225
226void DataWriteProcessor::set_texture(std::shared_ptr<Core::VKImage> image, std::string binding)
227{
228 m_pending_texture = PendingTexture { .image = std::move(image), .binding = std::move(binding) };
229 m_texture_dirty.test_and_set(std::memory_order_release);
230}
231
233{
234 m_pixel_pending = std::move(variant);
235 m_pixel_dirty.test_and_set(std::memory_order_release);
236}
237
239 uint32_t width,
240 uint32_t height,
242 std::string binding)
243{
247 m_tex_binding = std::move(binding);
248}
249
251{
252 return m_data_dirty.test(std::memory_order_acquire);
253}
254
259
260void DataWriteProcessor::on_attach(const std::shared_ptr<Buffer>& buffer)
261{
262 auto vk = std::dynamic_pointer_cast<VKBuffer>(buffer);
263 if (!vk) {
264 error<std::invalid_argument>(
267 std::source_location::current(),
268 "DataWriteProcessor requires a VKBuffer");
269 }
270
271 m_modality = vk->get_modality();
272 m_topology = vk->get_render_config().topology;
273
274 if (!vk->is_host_visible()) {
275 m_staging = create_staging_buffer(vk->get_size_bytes());
276 }
277
280 vk->set_vertex_layout(Kakshya::VertexLayout::for_raw());
281 } else {
282 switch (m_topology) {
285 vk->set_vertex_layout(Kakshya::VertexLayout::for_lines());
286 break;
289 vk->set_vertex_layout(Kakshya::VertexLayout::for_meshes());
290 break;
291 default:
292 vk->set_vertex_layout(Kakshya::VertexLayout::for_points());
293 break;
294 }
295 }
296 }
297
299 "DataWriteProcessor attached (modality: {}, staging: {})",
301 m_staging ? "yes" : "no");
302}
303
304void DataWriteProcessor::on_detach(const std::shared_ptr<Buffer>& /*buffer*/)
305{
306 while (m_writes->pop()) {
307 }
308 m_write_staging.reset();
309 m_write_image_staging.reset();
310 m_staging.reset();
311 m_pending_texture.reset();
312 m_data_pending.clear();
313 m_active.clear();
314
315 m_pixel_pending = {};
316 m_pixel_active = {};
318
320}
321
322void DataWriteProcessor::processing_function(const std::shared_ptr<Buffer>& buffer)
323{
324 auto vk = std::dynamic_pointer_cast<VKBuffer>(buffer);
325 if (!vk) {
327 "DataWriteProcessor attached to non-VKBuffer");
328 return;
329 }
330
331 while (auto write = m_writes->pop()) {
332 (*write)(*this, vk);
333 }
334
335 if (m_texture_dirty.test(std::memory_order_acquire)) {
336 m_texture_dirty.clear(std::memory_order_release);
337 if (m_pending_texture) {
338 if (auto rp = vk->get_render_processor()) {
339 rp->bind_texture(m_pending_texture->binding, m_pending_texture->image);
340 }
341 m_pending_texture.reset();
342 }
343 }
344
345 if (m_pixel_dirty.test(std::memory_order_acquire)) {
346 m_pixel_dirty.clear(std::memory_order_release);
349 }
350
351 if (m_gpu_texture) {
352 if (auto rp = vk->get_render_processor()) {
354 rp->bind_texture(m_tex_binding, m_gpu_texture);
356 }
357 }
358 }
359
360 if (!m_data_dirty.test(std::memory_order_acquire)) {
361 return;
362 }
363
364 m_data_dirty.clear(std::memory_order_release);
365 std::swap(m_active, m_data_pending);
366
367 if (m_active.empty()) {
368 return;
369 }
370
372
373 for (size_t i = 1; i < m_active.size(); ++i) {
374 if (i == 1 && m_tex_width > 0 && is_vertex_modality(m_modality)) {
375 upload_texture(vk, m_active[i]);
376 } else {
378 }
379 }
380}
381
382void DataWriteProcessor::upload_primary(const std::shared_ptr<VKBuffer>& vk, std::vector<Kakshya::DataVariant>& slots)
383{
385 upload_vertex(vk, slots);
386 } else if (is_texture_modality(m_modality)) {
387 upload_texture(vk, slots[0]);
388 } else {
389 upload_raw(vk, slots[0]);
390 }
391}
392
393void DataWriteProcessor::upload_secondary(const std::shared_ptr<VKBuffer>& vk, Kakshya::DataVariant& slot)
394{
395 auto bytes = Kakshya::convert_variant<uint8_t>(slot);
396 ensure_capacity(vk, bytes.size_bytes());
397 upload_to_gpu(bytes.data(), bytes.size_bytes(), vk, m_staging);
398}
399
400void DataWriteProcessor::upload_vertex(const std::shared_ptr<VKBuffer>& vk, std::vector<Kakshya::DataVariant>& slots)
401{
402 std::optional<Kakshya::VertexAccess> access;
403
404 switch (m_topology) {
407 access = Kakshya::as_line_vertex_access(slots);
408 break;
411 access = Kakshya::as_mesh_vertex_access(slots);
412 break;
413 default:
414 access = Kakshya::as_point_vertex_access(slots);
415 break;
416 }
417
418 if (!access) {
420 "DataWriteProcessor: vertex access failed for modality {}",
422 return;
423 }
424
425 ensure_capacity(vk, access->byte_count);
426 upload_to_gpu(access->data_ptr, access->byte_count, vk, m_staging);
427 vk->set_vertex_layout(access->layout);
428}
429
430void DataWriteProcessor::upload_texture(const std::shared_ptr<VKBuffer>& vk, Kakshya::DataVariant& slot)
431{
432 auto access = Kakshya::as_texture_access(slot);
433 if (!access) {
435 "DataWriteProcessor: as_texture_access failed for modality {}",
437 return;
438 }
439
440 m_last_texture_format = access->format;
441
442 if (m_tex_width == 0 || m_tex_height == 0) {
444 "DataWriteProcessor: setup_pixel_target() not called before texture upload");
445 return;
446 }
447
449
450 if (!m_gpu_texture) {
451 m_gpu_texture = loom.create_2d(m_tex_width, m_tex_height, m_tex_format, nullptr, 1);
452 if (!m_gpu_texture) {
454 "DataWriteProcessor: failed to create GPU texture {}x{}",
456 return;
457 }
459 if (auto rp = vk->get_render_processor()) {
460 rp->bind_texture(m_tex_binding, m_gpu_texture);
461 }
462 }
463
464 loom.upload_data(m_gpu_texture, access->data_ptr, access->byte_count, m_image_staging);
465}
466
467void DataWriteProcessor::upload_raw(const std::shared_ptr<VKBuffer>& vk, Kakshya::DataVariant& slot)
468{
469 Kakshya::DataAccess accessor(slot, {}, m_modality);
470 auto [ptr, bytes, fmt] = accessor.gpu_buffer();
471
472 ensure_capacity(vk, bytes);
473 upload_to_gpu(ptr, bytes, vk, m_staging);
474}
475
476void DataWriteProcessor::ensure_capacity(const std::shared_ptr<VKBuffer>& vk, size_t required)
477{
478 if (required <= vk->get_size_bytes()) {
479 return;
480 }
481
482 vk->resize(static_cast<size_t>(static_cast<float>(required) * 1.5F), false);
483
484 if (m_staging) {
485 m_staging = create_staging_buffer(vk->get_size_bytes());
486 }
487}
488
504
519
520} // namespace MayaFlux::Buffers
#define MF_ERROR(comp, ctx,...)
#define MF_RT_ERROR(comp, ctx,...)
#define MF_DEBUG(comp, ctx,...)
uint64_t payload
Definition Composite.cpp:18
Core::GlobalNetworkConfig network
Definition Config.cpp:43
IO::ImageData image
Definition Decoder.cpp:47
bool format
bool size
std::shared_ptr< NetworkGeometryBuffer > buffer
const uint32_t * src
std::string name
Definition VKDevice.cpp:123
uint32_t index
Definition VKDevice.cpp:122
const uint8_t * ptr
uint32_t width
uint32_t height
void upload_secondary(const std::shared_ptr< VKBuffer > &vk, Kakshya::DataVariant &slot)
std::vector< Kakshya::DataVariant > m_data_pending
void set_pixel_data(Kakshya::DataVariant variant)
Supply pixel data for the co-resident texture on the next cycle.
std::shared_ptr< VKBuffer > m_write_staging
void processing_function(const std::shared_ptr< Buffer > &buffer) override
The core processing function that must be implemented by derived classes.
void on_detach(const std::shared_ptr< Buffer > &buffer) override
Called when this processor is detached from a buffer.
static bool is_vertex_modality(Kakshya::DataModality m) noexcept
Portal::Graphics::ImageFormat m_tex_format
void write_back_buffer(size_t index, Kakshya::DataVariant data)
Write a raw back_buffers slot of the attached buffer.
void write_buffer(std::shared_ptr< VKBuffer > target, Kakshya::DataVariant data, size_t dst_offset=0)
Write bytes into another VKBuffer, such as a plain descriptor-bound buffer.
void set_vertices(const void *data, size_t byte_count)
Supply pre-packed interleaved vertex bytes for the next cycle.
std::function< void(DataWriteProcessor &, const std::shared_ptr< VKBuffer > &)> WriteFn
void upload_texture(const std::shared_ptr< VKBuffer > &vk, Kakshya::DataVariant &slot)
void write_texture_pixels(Kakshya::ImageData image)
Write pixels straight into the attached TextureBuffer's GPU image.
void ensure_capacity(const std::shared_ptr< VKBuffer > &vk, size_t required)
Portal::Graphics::PrimitiveTopology m_topology
void set_data(Kakshya::DataVariant variant)
Supply a single data value for the next cycle (slot 0).
std::shared_ptr< Core::VKImage > m_gpu_texture
void upload_raw(const std::shared_ptr< VKBuffer > &vk, Kakshya::DataVariant &slot)
std::optional< PendingTexture > m_pending_texture
void upload_vertex(const std::shared_ptr< VKBuffer > &vk, std::vector< Kakshya::DataVariant > &slots)
void setup_pixel_target(uint32_t width, uint32_t height, Portal::Graphics::ImageFormat format, std::string binding="texSampler")
Configure the pixel upload path for texture modalities.
std::shared_ptr< VKBuffer > m_write_image_staging
std::unique_ptr< Memory::LockFreeQueue< WriteFn, k_write_queue_capacity > > m_writes
void on_attach(const std::shared_ptr< Buffer > &buffer) override
Called when this processor is attached to a buffer.
std::vector< Kakshya::DataVariant > m_active
void write_pixels(Kakshya::ImageData image)
Write the retained host pixels of the attached TextureBuffer.
void write_mesh(Kakshya::MeshData data, std::string slot={})
Write mesh geometry to the attached buffer's authoritative copy.
void upload_primary(const std::shared_ptr< VKBuffer > &vk, std::vector< Kakshya::DataVariant > &slots)
void set_texture(std::shared_ptr< Core::VKImage > image, std::string binding)
Supply a texture to bind on the next graphics tick.
Kakshya::GpuDataFormat last_texture_format() const noexcept
Returns the GpuDataFormat resolved on the last texture upload.
bool has_pending() const noexcept
Returns true if a snapshot has been set and not yet consumed.
std::shared_ptr< VKBuffer > m_image_staging
static bool is_texture_modality(Kakshya::DataModality m) noexcept
void write_field(std::string name, Kakshya::DataVariant data)
Write a named field of the attached buffer.
Modality-aware multi-slot write processor for plain VKBuffer.
auto gpu_buffer() const
Get raw buffer info for GPU upload.
Type-erased accessor for NDData with semantic view construction.
Portal-level texture creation and management.
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< Core::VKImage > resolve_gpu_image(const T &buffer)
Resolve the GPU-resident image from any GpuImageSource buffer.
@ GRAPHICS_BACKEND
Standard graphics processing backend configuration.
std::shared_ptr< VKBuffer > create_staging_buffer(size_t size)
Create staging buffer for transfers.
bool is_device_local(const std::shared_ptr< VKBuffer > &buffer)
Check if buffer is device-local (staging needed)
void upload_to_gpu(const void *data, size_t size, const std::shared_ptr< VKBuffer > &target, const std::shared_ptr< VKBuffer > &staging, size_t dst_offset)
Upload raw data to GPU buffer (auto-detects host-visible vs device-local)
void upload_back_buffer(const VKBufferResources::GenerationSlot &slot, const void *data, size_t size, std::shared_ptr< VKBuffer > &staging)
Upload host memory into a raw back_buffers slot.
@ BufferProcessing
Buffer processing (Buffers::BufferManager, processing chains)
@ Buffers
Buffers, Managers, processors and processing chains.
ImageFormat
User-friendly image format enum.
Definition ImageData.hpp:12
std::optional< VertexAccess > as_line_vertex_access(std::span< const DataVariant > channels, const VertexAccessConfig &config)
Assemble line-vertex-compatible bytes from one or more data channels.
std::optional< VertexAccess > as_point_vertex_access(std::span< const DataVariant > channels, const VertexAccessConfig &config)
Assemble point-vertex-compatible bytes from one or more data channels.
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
DataModality
Data modality types for cross-modal analysis.
Definition NDData.hpp:132
@ SPECTRAL_2D
2D spectral data (time + frequency)
@ UNKNOWN
Unknown or undefined modality.
@ VERTICES_3D
3D vertex data (positions, normals, etc.)
@ VIDEO_GRAYSCALE
3D video (time + 2D grayscale)
@ VIDEO_COLOR
4D video (time + 2D + color)
@ IMAGE_COLOR
2D RGB/RGBA image
@ IMAGE_2D
2D image (grayscale or single channel)
std::string_view modality_to_string(DataModality modality)
Convert DataModality enum to string representation.
Definition NDData.cpp:102
GpuDataFormat
GPU data formats with explicit precision levels.
std::optional< VertexAccess > as_mesh_vertex_access(std::span< const DataVariant > channels, const VertexAccessConfig &config)
Assemble mesh-vertex-compatible bytes from one or more data channels.
std::optional< TextureAccess > as_texture_access(const DataVariant &variant)
Extract a TextureAccess from a DataVariant.
MAYAFLUX_API TextureLoom & get_texture_manager()
Get the global texture manager instance.
Raw image data loaded from file or read back from a GPU image.
Definition ImageData.hpp:55
Owning CPU-side representation of a loaded or generated mesh.
Definition MeshData.hpp:33
static VertexLayout for_raw(uint32_t stride=60)
Factory: layout for raw vertex data with common attributes.
static VertexLayout for_lines(uint32_t stride=60)
Factory: layout for LineVertex (position, color, thickness, uv, normal, tangent)
static VertexLayout for_meshes(uint32_t stride=60)
Factory: layout for MeshVertex (position, color, weight, uv, normal, tangent)
static VertexLayout for_points(uint32_t stride=60)
Factory: layout for PointVertex (position, color, size, uv, normal, tangent)