MayaFlux 0.5.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
VolumeTransfer.hpp
Go to the documentation of this file.
1#pragma once
2
4
5namespace MayaFlux::Buffers {
6class VolumeGridBuffer;
7}
8
9namespace MayaFlux::Vruta {
10class TaskScheduler;
11}
12
13namespace MayaFlux::IO {
14
15/**
16 * @brief Download named fields from a GPU-resident volume into host VolumeData.
17 *
18 * Performs one blocking GPU->host transfer per field via
19 * VolumeGridBuffer::read_field, reading the current read slot of each. The
20 * calling thread must have command queue access, and the call costs a full
21 * round trip at each field's byte size. Not for per-frame use on the
22 * graphics thread.
23 *
24 * Fields with stride sizeof(float) land as scalars. Fields with stride
25 * sizeof(glm::vec4) are read into scratch storage and gathered into
26 * glm::vec3, discarding the padding component the GPU layout requires and
27 * nothing reads. That gather costs a second allocation at four thirds the
28 * output size, which is the price of doing the pack on the host.
29 *
30 * Any other stride is unrepresentable in VolumeData and is skipped with an
31 * error. A named field that was never declared is likewise skipped.
32 *
33 * @param volume Volume to read from.
34 * @param field_names Fields to download. Empty means every declared field,
35 * in declaration order.
36 * @return Populated VolumeData, or std::nullopt if nothing was downloaded
37 * or the result failed is_consistent().
38 */
39[[nodiscard]] std::optional<Kakshya::VolumeData> download_volume(
40 const std::shared_ptr<Buffers::VolumeGridBuffer>& volume,
41 const std::vector<std::string>& field_names = {});
42
43/**
44 * @brief Upload every field of a host VolumeData into a GPU-resident volume.
45 *
46 * The reverse of download_volume: for each field in @p data whose name is
47 * declared on @p volume, writes its bytes into the current write slot via
48 * VolumeGridBuffer::seed_raw. The calling thread must have command queue
49 * access, matching download_volume's requirement, since seed_raw records a
50 * transfer that must resolve before the next one can be issued.
51 *
52 * A scalar field (VolumeField holding vector<float>) uploads directly. A
53 * vector field is expanded from glm::vec3 to glm::vec4 first, zeroing the
54 * fourth component, because the GPU layout carries that padding and
55 * VolumeData does not — the inverse of the gather download_volume performs.
56 * That expansion costs an allocation at four thirds the input size.
57 *
58 * A field named in @p data but not declared on @p volume, or whose declared
59 * stride does not match what the field's variant implies (float for a
60 * scalar, vec4 for a vector), is skipped with an error; the rest of the
61 * upload proceeds. Neither VolumeData's lattice nor its cell count is
62 * checked against the volume's own — a mismatch surfaces as seed_raw's own
63 * size-mismatch error per field, not as a single upfront rejection.
64 *
65 * Each field is one seed_raw call rather than a batched transfer: simpler
66 * than building a seed_raw counterpart to read_fields' batching, at the
67 * cost of one staging round trip per field instead of one for the whole
68 * upload. Worth revisiting if upload_volume becomes a per-frame path rather
69 * than the one-shot load this exists for.
70 *
71 * @param data Source, typically from IO::VolumeReader::load().
72 * @param volume Destination. Fields must already be declared; this call
73 * never declares one.
74 * @return True if at least one field was uploaded.
75 */
76bool upload_volume(
77 const Kakshya::VolumeData& data,
78 const std::shared_ptr<Buffers::VolumeGridBuffer>& volume);
79
80/**
81 * @brief Save a volume directly to disk via the VolumeWriter registry.
82 *
83 * Combines download_volume() with VolumeWriterRegistry::create_writer(). The
84 * file extension selects the writer. Whether a given writer can express a
85 * given field is the writer's responsibility: a format with no vector grid
86 * type rejects a vector field rather than dropping it.
87 *
88 * Inherits download_volume's thread requirements.
89 *
90 * @param volume Volume to save.
91 * @param filepath Destination path with extension.
92 * @param options Format-specific writer options.
93 * @param field_names Fields to save. Empty means every declared field.
94 * @return True on success.
95 */
96bool save_volume(
97 const std::shared_ptr<Buffers::VolumeGridBuffer>& volume,
98 const std::string& filepath,
99 const VolumeWriteOptions& options = {},
100 const std::vector<std::string>& field_names = {});
101
102/**
103 * @brief Save already-downloaded VolumeData to disk via the registry.
104 *
105 * Pure CPU, no thread restrictions. For callers holding a VolumeData from
106 * download_volume, from a future reader, or built procedurally.
107 *
108 * ImageExport has no equivalent because IOManager::save_image(ImageData)
109 * covers that case asynchronously. A synchronous data-to-file path is worth
110 * having here: it is what a test exercising a writer calls, and what a
111 * shutdown flush calls.
112 */
113bool save_volume(
114 const Kakshya::VolumeData& data,
115 const std::string& filepath,
116 const VolumeWriteOptions& options = {});
117
118// ============================================================================
119// VolumeCapture
120// ============================================================================
121
122/**
123 * @brief Where a captured frame goes.
124 *
125 * Takes ownership so an asynchronous writer can move the data onto a
126 * worker. Defaults to a synchronous save_volume on the capturing thread.
127 * A caller with a task pool supplies its own; nothing here needs to know
128 * such a pool exists.
129 */
130using VolumeWriteHook = std::function<bool(
131 Kakshya::VolumeData&&, const std::string&, const VolumeWriteOptions&)>;
132
133/**
134 * @class VolumeCapture
135 * @brief Records a numbered .vdb sequence from a running volume.
136 *
137 * No volumetric format carries time, so an animation is a folder of files
138 * with a contiguous numeric suffix that a DCC steps through per scene frame.
139 *
140 * start() spawns a GraphicsRoutine that reads one frame and suspends on a
141 * FrameDelay, so capture advances on the frame clock with no polling and no
142 * driver loop. The routine resumes on the graphics thread, which is where
143 * command queue access lives, so the readback is legal by construction
144 * rather than by convention. stop() cancels the task by name.
145 *
146 * The frame counter belongs to the capture, not to the clock, so numbering
147 * stays contiguous whatever interval is used and whenever recording began.
148 *
149 * Readback is not free. Six fields at 128 cubed is roughly 76 MB per frame
150 * before compression, and both the transfer and the encode happen on the
151 * graphics thread. Capture on a coarse interval, and expect the frame it
152 * runs on to cost.
153 *
154 * A failed frame stops the capture. A sequence with a hole in it is worse
155 * than a short one: a DCC reading the gap either stops early or repeats a
156 * frame, and neither is visible until someone renders.
157 */
158class MAYAFLUX_API VolumeCapture {
159public:
160 /**
161 * @param scheduler Scheduler the capture routine is added to.
162 * @param volume Volume to record. Held for the capture's lifetime.
163 * @param path_pattern Destination with one std::format index field, such
164 * as "smoke.{:04}.vdb". Zero padding matters: an
165 * unpadded pattern sorts frame 10 before frame 2.
166 * The extension selects the writer.
167 * @param field_names Fields to record. Empty means every declared field.
168 * @param options Writer options, applied to every frame.
169 * @param write Optional hook to move the VolumeData onto a worker
170 */
172 Vruta::TaskScheduler& scheduler,
173 std::shared_ptr<Buffers::VolumeGridBuffer> volume,
174 std::string path_pattern,
175 std::vector<std::string> field_names = {},
176 VolumeWriteOptions options = {}, VolumeWriteHook write = nullptr);
177
179
180 VolumeCapture(const VolumeCapture&) = delete;
182
183 /**
184 * @brief Spawn the capture routine, resetting the frame counter.
185 * @param max_frames Stop after this many frames. Zero records until
186 * stop(), which at tens of megabytes per frame
187 * fills a disk given time.
188 * @param frame_interval Frames between captures. One records every
189 * frame; higher values thin the sequence, which
190 * is usually what a large lattice wants.
191 */
192 void start(uint32_t max_frames = 0, uint64_t frame_interval = 1);
193
194 /**
195 * @brief Cancel the capture routine. Frames already written are kept.
196 */
197 void stop();
198
199 [[nodiscard]] bool is_recording() const { return m_recording; }
200 [[nodiscard]] uint32_t frames_written() const { return m_frame; }
201
202private:
203 /**
204 * @brief One frame's readback and write. Called from the routine body.
205 * @return False on failure, which ends the routine.
206 */
207 bool capture_frame();
208
210 std::shared_ptr<Buffers::VolumeGridBuffer> m_volume;
211 std::string m_pattern;
212 std::vector<std::string> m_fields;
214
215 std::string m_task_name;
216 uint32_t m_frame {};
217 uint32_t m_max_frames {};
218 bool m_recording {};
220};
221
222} // namespace MayaFlux::IO
std::vector< std::string > m_fields
VolumeCapture & operator=(const VolumeCapture &)=delete
VolumeCapture(const VolumeCapture &)=delete
std::shared_ptr< Buffers::VolumeGridBuffer > m_volume
Vruta::TaskScheduler & m_scheduler
Records a numbered .vdb sequence from a running volume.
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::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.
Configuration for volume writing.
A lattice and every field sampled over it, held in host memory.