MayaFlux 0.5.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
VolumeTransfer.cpp
Go to the documentation of this file.
1#include "VolumeTransfer.hpp"
2
3#include "FileWriter.hpp"
4
6
9
11
12namespace MayaFlux::IO {
13
14// ============================================================================
15// download_volume
16// ============================================================================
17
18std::optional<Kakshya::VolumeData> download_volume(
19 const std::shared_ptr<Buffers::VolumeGridBuffer>& volume,
20 const std::vector<std::string>& field_names)
21{
22 if (!volume) {
24 "download_volume: null volume");
25 return std::nullopt;
26 }
27
28 const auto names = field_names.empty() ? volume->get_field_names() : field_names;
29 if (names.empty()) {
31 "download_volume: volume declares no fields");
32 return std::nullopt;
33 }
34
36 result.lattice = volume->get_lattice();
37 result.fields.reserve(names.size());
38
39 const size_t cells = result.cell_count();
40
41 std::vector<std::string> batch_names;
42 std::vector<std::pair<void*, size_t>> batch_dsts;
43 std::vector<std::vector<glm::vec4>> padded;
44
45 batch_names.reserve(names.size());
46 batch_dsts.reserve(names.size());
47 padded.reserve(names.size());
48
49 for (const auto& name : names) {
50 if (!volume->has_field(name)) {
52 "download_volume: no field named '{}', skipped", name);
53 continue;
54 }
55
56 const size_t stride = volume->get_field_stride(name);
57
59 field.name = name;
60 field.semantics = volume->get_field_semantics(name);
61
62 if (stride == sizeof(float)) {
63 field.values = std::vector<float>(cells);
64 result.fields.push_back(std::move(field));
65 auto& values = *result.fields.back().as_scalar();
66 batch_names.push_back(name);
67 batch_dsts.emplace_back(values.data(), cells * sizeof(float));
68 continue;
69 }
70
71 if (stride == sizeof(glm::vec4)) {
72 field.values = std::vector<glm::vec3>(cells);
73 result.fields.push_back(std::move(field));
74 auto& scratch = padded.emplace_back(cells);
75 batch_names.push_back(name);
76 batch_dsts.emplace_back(scratch.data(), cells * sizeof(glm::vec4));
77 continue;
78 }
79
81 "download_volume: field '{}' has stride {}, which VolumeData "
82 "cannot represent (expected {} or {})",
83 name, stride, sizeof(float), sizeof(glm::vec4));
84 }
85
86 volume->read_fields(batch_names, batch_dsts);
87
88 size_t vector_index = 0;
89 for (auto& field : result.fields) {
90 auto* out = field.as_vector();
91 if (!out) {
92 continue;
93 }
94 const auto& src = padded[vector_index++];
95 for (size_t i = 0; i < cells; ++i) {
96 (*out)[i] = glm::vec3(src[i]);
97 }
98 }
99
100 if (result.fields.empty()) {
102 "download_volume: no field was downloaded");
103 return std::nullopt;
104 }
105
106 if (!result.is_consistent()) {
108 "download_volume: resulting VolumeData failed is_consistent()");
109 return std::nullopt;
110 }
111
113 "download_volume: {}x{}x{} lattice, {} of {} fields",
114 result.lattice.resolution.x, result.lattice.resolution.y,
115 result.lattice.resolution.z, result.fields.size(), names.size());
116
117 return result;
118}
119
120// ============================================================================
121// upload_volume
122// ============================================================================
123
125 const Kakshya::VolumeData& data,
126 const std::shared_ptr<Buffers::VolumeGridBuffer>& volume)
127{
128 if (!volume) {
130 "upload_volume: null volume");
131 return false;
132 }
133
134 if (!data.is_consistent()) {
136 "upload_volume: VolumeData failed is_consistent()");
137 return false;
138 }
139
140 size_t uploaded = 0;
141
142 for (const auto& field : data.fields) {
143 if (!volume->has_field(field.name)) {
145 "upload_volume: no field named '{}' on this volume, skipped", field.name);
146 continue;
147 }
148
149 const size_t stride = volume->get_field_stride(field.name);
150
151 if (const auto* scalars = field.as_scalar()) {
152 if (stride != sizeof(float)) {
154 "upload_volume: field '{}' has stride {}, but VolumeData holds a scalar "
155 "(expected {}), skipped",
156 field.name, stride, sizeof(float));
157 continue;
158 }
159 volume->seed_raw(field.name, scalars->data(), scalars->size() * sizeof(float));
160 ++uploaded;
161 continue;
162 }
163
164 const auto* vectors = field.as_vector();
165 if (stride != sizeof(glm::vec4)) {
167 "upload_volume: field '{}' has stride {}, but VolumeData holds a vector "
168 "(expected {}), skipped",
169 field.name, stride, sizeof(glm::vec4));
170 continue;
171 }
172
173 std::vector<glm::vec4> padded(vectors->size());
174 for (size_t i = 0; i < vectors->size(); ++i) {
175 padded[i] = glm::vec4((*vectors)[i], 0.0F);
176 }
177 volume->seed_raw(field.name, padded.data(), padded.size() * sizeof(glm::vec4));
178 ++uploaded;
179 }
180
181 if (uploaded == 0) {
183 "upload_volume: no field was uploaded");
184 return false;
185 }
186
188 "upload_volume: {} of {} fields", uploaded, data.fields.size());
189
190 return true;
191}
192
193// ============================================================================
194// save_volume
195// ============================================================================
196
198 const Kakshya::VolumeData& data,
199 const std::string& filepath,
200 const VolumeWriteOptions& options)
201{
202 auto writer = VolumeWriterRegistry::instance().create_writer(filepath);
203 if (!writer) {
205 "save_volume: no writer registered for extension of '{}'", filepath);
206 return false;
207 }
208
209 const bool ok = writer->write(filepath, data, options);
210 if (!ok) {
212 "save_volume: writer failed: {}", writer->get_last_error());
213 }
214 return ok;
215}
216
218 const std::shared_ptr<Buffers::VolumeGridBuffer>& volume,
219 const std::string& filepath,
220 const VolumeWriteOptions& options,
221 const std::vector<std::string>& field_names)
222{
223 auto data = download_volume(volume, field_names);
224 if (!data) {
225 return false;
226 }
227
228 return save_volume(*data, filepath, options);
229}
230
231// ============================================================================
232// VolumeCapture
233// ============================================================================
234
235namespace {
236 std::atomic<uint32_t> g_next_capture_id { 1 };
237}
238
240 Vruta::TaskScheduler& scheduler,
241 std::shared_ptr<Buffers::VolumeGridBuffer> volume,
242 std::string path_pattern,
243 std::vector<std::string> field_names,
244 VolumeWriteOptions options,
245 VolumeWriteHook write)
246 : m_scheduler(scheduler)
247 , m_volume(std::move(volume))
248 , m_pattern(std::move(path_pattern))
249 , m_fields(std::move(field_names))
250 , m_options(std::move(options))
251 , m_write(write ? std::move(write)
252 : [](Kakshya::VolumeData&& d, const std::string& p,
253 const VolumeWriteOptions& o) {
254 return save_volume(d, p, o);
255 })
256{
257}
258
263
265{
266 if (m_max_frames != 0 && m_frame >= m_max_frames) {
267 return false;
268 }
269
270 auto data = download_volume(m_volume, m_fields);
271 if (!data) {
273 "VolumeCapture: readback failed at frame {}", m_frame);
274 return false;
275 }
276
277 if (!m_write(std::move(*data), resolve_sequence_path(m_pattern, m_frame), m_options)) {
279 "VolumeCapture: write failed at frame {}", m_frame);
280 return false;
281 }
282
283 ++m_frame;
284 return true;
285}
286
287void VolumeCapture::start(uint32_t max_frames, uint64_t frame_interval)
288{
289 if (!m_volume) {
291 "VolumeCapture: cannot start, null volume");
292 return;
293 }
294
295 stop();
296
297 m_frame = 0;
298 m_max_frames = max_frames;
299 m_recording = true;
300 m_task_name = "volume_capture_"
301 + std::to_string(g_next_capture_id.fetch_add(1, std::memory_order_relaxed));
302
303 auto routine = [](Vruta::TaskScheduler&,
304 VolumeCapture* capture,
305 uint64_t interval) -> Vruta::GraphicsRoutine {
306 auto& p = co_await Kriya::GetGraphicsPromise {};
307 while (!p.should_terminate && capture->capture_frame()) {
308 co_await Kriya::FrameDelay { .frames_to_wait = interval };
309 }
310 capture->m_recording = false;
311 };
312
314 std::make_shared<Vruta::GraphicsRoutine>(
315 routine(m_scheduler, this, frame_interval)),
316 m_task_name, false);
317
319 "VolumeCapture: recording '{}' every {} frame(s), {}",
320 m_pattern, frame_interval,
321 max_frames == 0 ? std::string("unbounded")
322 : std::format("{} frames", max_frames));
323}
324
326{
327 if (!m_recording) {
328 return;
329 }
330
332 m_task_name.clear();
333 m_recording = false;
334
336 "VolumeCapture: stopped after {} frames", m_frame);
337}
338
339} // namespace MayaFlux::IO
#define MF_INFO(comp, ctx,...)
#define MF_ERROR(comp, ctx,...)
#define MF_DEBUG(comp, ctx,...)
const uint32_t * src
std::vector< float > * out
std::vector< std::byte > values
Definition VDBWriter.cpp:26
std::string name
Definition VKDevice.cpp:143
std::vector< std::string > m_fields
bool capture_frame()
One frame's readback and write.
VolumeCapture(Vruta::TaskScheduler &scheduler, std::shared_ptr< Buffers::VolumeGridBuffer > volume, std::string path_pattern, std::vector< std::string > field_names={}, VolumeWriteOptions options={}, VolumeWriteHook write=nullptr)
void start(uint32_t max_frames=0, uint64_t frame_interval=1)
Spawn the capture routine, resetting the frame counter.
std::shared_ptr< Buffers::VolumeGridBuffer > m_volume
Vruta::TaskScheduler & m_scheduler
void stop()
Cancel the capture routine.
Records a numbered .vdb sequence from a running volume.
std::unique_ptr< VolumeWriter > create_writer(const std::string &filepath) const
static VolumeWriterRegistry & instance()
A C++20 coroutine-based graphics processing task with frame-accurate timing.
Definition Routine.hpp:496
void add_task(const std::shared_ptr< Routine > &routine, const std::string &name="", bool initialize=false)
Add a routine to the scheduler based on its processing token.
Definition Scheduler.cpp:23
bool cancel_task(const std::shared_ptr< Routine > &routine)
Cancels and removes a task from the scheduler.
Token-based multimodal task scheduling system for unified coroutine processing.
Definition Scheduler.hpp:51
bool upload_volume(const Kakshya::VolumeData &data, const std::shared_ptr< Buffers::VolumeGridBuffer > &volume)
Upload every field of a host VolumeData into a GPU-resident volume.
bool save_volume(const Kakshya::VolumeData &data, const std::string &filepath, const VolumeWriteOptions &options)
Save already-downloaded VolumeData to disk via the registry.
std::string resolve_sequence_path(std::string_view pattern, uint64_t frame)
Substitute a frame index into a numbered output pattern.
std::optional< Kakshya::VolumeData > download_volume(const std::shared_ptr< Buffers::VolumeGridBuffer > &volume, const std::vector< std::string > &field_names)
Download named fields from a GPU-resident volume into host VolumeData.
std::function< bool(Kakshya::VolumeData &&, const std::string &, const VolumeWriteOptions &)> VolumeWriteHook
Where a captured frame goes.
@ FileIO
Filesystem I/O operations.
@ IO
Networking, file handling, streaming.
Configuration for volume writing.
bool is_consistent() const
Check that every field is densely populated over the lattice.
size_t cell_count() const
Cells in the lattice, which every field's element_count() must equal.
std::vector< VolumeField > fields
A lattice and every field sampled over it, held in host memory.
Kinesis::LatticeSemantics semantics
One named quantity sampled over a lattice, held in host memory.
glm::uvec3 resolution
Cell count per axis. Zero on any axis is invalid.
Definition Lattice.hpp:26
graphics-domain awaiter for frame-accurate timing delays
Templated awaitable for accessing a coroutine's promise object.