19 const std::shared_ptr<Buffers::VolumeGridBuffer>& volume,
20 const std::vector<std::string>& field_names)
24 "download_volume: null volume");
28 const auto names = field_names.empty() ? volume->get_field_names() : field_names;
31 "download_volume: volume declares no fields");
36 result.
lattice = volume->get_lattice();
37 result.
fields.reserve(names.size());
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;
45 batch_names.reserve(names.size());
46 batch_dsts.reserve(names.size());
47 padded.reserve(names.size());
49 for (
const auto&
name : names) {
50 if (!volume->has_field(
name)) {
52 "download_volume: no field named '{}', skipped",
name);
56 const size_t stride = volume->get_field_stride(
name);
62 if (stride ==
sizeof(
float)) {
63 field.
values = std::vector<float>(cells);
64 result.
fields.push_back(std::move(field));
66 batch_names.push_back(
name);
67 batch_dsts.emplace_back(
values.data(), cells *
sizeof(
float));
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));
81 "download_volume: field '{}' has stride {}, which VolumeData "
82 "cannot represent (expected {} or {})",
83 name, stride,
sizeof(
float),
sizeof(glm::vec4));
86 volume->read_fields(batch_names, batch_dsts);
88 size_t vector_index = 0;
89 for (
auto& field : result.
fields) {
90 auto*
out = field.as_vector();
94 const auto&
src = padded[vector_index++];
95 for (
size_t i = 0; i < cells; ++i) {
96 (*out)[i] = glm::vec3(
src[i]);
100 if (result.
fields.empty()) {
102 "download_volume: no field was downloaded");
108 "download_volume: resulting VolumeData failed is_consistent()");
113 "download_volume: {}x{}x{} lattice, {} of {} fields",
126 const std::shared_ptr<Buffers::VolumeGridBuffer>& volume)
130 "upload_volume: null volume");
136 "upload_volume: VolumeData failed is_consistent()");
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);
149 const size_t stride = volume->get_field_stride(field.name);
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));
159 volume->seed_raw(field.name, scalars->data(), scalars->size() *
sizeof(
float));
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));
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);
177 volume->seed_raw(field.name, padded.data(), padded.size() *
sizeof(glm::vec4));
183 "upload_volume: no field was uploaded");
188 "upload_volume: {} of {} fields", uploaded, data.
fields.size());
199 const std::string& filepath,
205 "save_volume: no writer registered for extension of '{}'", filepath);
209 const bool ok = writer->write(filepath, data, options);
212 "save_volume: writer failed: {}", writer->get_last_error());
218 const std::shared_ptr<Buffers::VolumeGridBuffer>& volume,
219 const std::string& filepath,
221 const std::vector<std::string>& field_names)
236 std::atomic<uint32_t> g_next_capture_id { 1 };
241 std::shared_ptr<Buffers::VolumeGridBuffer> volume,
242 std::string path_pattern,
243 std::vector<std::string> field_names,
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,
273 "VolumeCapture: readback failed at frame {}",
m_frame);
279 "VolumeCapture: write failed at frame {}",
m_frame);
291 "VolumeCapture: cannot start, null volume");
301 + std::to_string(g_next_capture_id.fetch_add(1, std::memory_order_relaxed));
307 while (!p.should_terminate && capture->capture_frame()) {
310 capture->m_recording =
false;
314 std::make_shared<Vruta::GraphicsRoutine>(
319 "VolumeCapture: recording '{}' every {} frame(s), {}",
321 max_frames == 0 ? std::string(
"unbounded")
322 : std::format(
"{} frames", max_frames));
336 "VolumeCapture: stopped after {} frames",
m_frame);
#define MF_INFO(comp, ctx,...)
#define MF_ERROR(comp, ctx,...)
#define MF_DEBUG(comp, ctx,...)
std::vector< float > * out
std::vector< std::byte > values
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)
VolumeWriteOptions m_options
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.
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.
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.
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.
Kinesis::Lattice3D lattice
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.
graphics-domain awaiter for frame-accurate timing delays
Templated awaitable for accessing a coroutine's promise object.