MayaFlux 1.0.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
Chimera.cpp
Go to the documentation of this file.
1#include "Chimera.hpp"
2
3#include "BufferPipeline.hpp"
4
12
14
15namespace MayaFlux::Kriya {
16
18{
19 if (pipeline) {
20 pipeline->end();
21 }
22}
23
25{
26 return m_state ? m_state->layers.size() : 0;
27}
28
29void Chimera::level(size_t layer, double weight)
30{
31 if (m_state) {
32 m_state->buffer->set_weight(static_cast<uint32_t>(layer), static_cast<float>(weight));
33 }
34}
35
36void Chimera::params(size_t layer, const glm::vec4& values)
37{
38 if (m_state) {
39 m_state->buffer->set_layer_params(static_cast<uint32_t>(layer), values);
40 }
41}
42
43void Chimera::speed(size_t layer, double ratio)
44{
45 if (m_state) {
46 *m_state->layers.at(layer).ratio = ratio;
47 }
48}
49
50void Chimera::cut(size_t layer, double seconds)
51{
52 if (!m_state) {
53 return;
54 }
55
56 auto& target = m_state->layers.at(layer);
57 if (!target.ring && !target.collection) {
58 return;
59 }
60
61 if (target.lag) {
62 const double elapsed = std::max(
63 static_cast<double>(m_state->ticks) / m_state->frame_rate - target.delay, 0.0);
64 target.lag_offset = seconds - (*target.lag)(elapsed);
65 } else if (target.ring) {
66 target.position = static_cast<double>(target.ring->get_write_head())
67 - seconds * target.ring->get_frame_rate();
68 target.entered = true;
69 } else {
70 const double rate = target.collection_rate > 0.0 ? target.collection_rate : m_state->frame_rate;
71 target.position = static_cast<double>(target.collection->get_write_head()) - seconds * rate;
72 target.entered = true;
73 }
74}
75
77{
78 if (!m_state) {
79 return;
80 }
81
82 auto& target = m_state->layers.at(layer);
83 target.ring.reset();
84 target.collection.reset();
85 target.image.reset();
86 target.picture = std::make_shared<const Kakshya::ImageData>(std::move(image));
87 target.dirty = true;
88}
89
90void Chimera::set(size_t layer, std::shared_ptr<Core::VKImage> image)
91{
92 if (!m_state) {
93 return;
94 }
95
96 auto& target = m_state->layers.at(layer);
97 target.ring.reset();
98 target.collection.reset();
99 target.picture.reset();
100 target.image = std::move(image);
101}
102
103void Chimera::set(size_t layer, std::shared_ptr<Kakshya::DynamicVideoStream> ring)
104{
105 if (!m_state) {
106 return;
107 }
108
109 auto& target = m_state->layers.at(layer);
110 target.image.reset();
111 target.picture.reset();
112 target.collection.reset();
113 target.ring = std::move(ring);
114 target.entered = false;
115}
116
117void Chimera::set(size_t layer, std::shared_ptr<Kakshya::TextureCollection> collection, double frame_rate)
118{
119 if (!m_state) {
120 return;
121 }
122
123 auto& target = m_state->layers.at(layer);
124 target.image.reset();
125 target.picture.reset();
126 target.ring.reset();
127 target.collection = std::move(collection);
128 target.collection_rate = frame_rate;
129 target.entered = false;
130}
131
133{
134 if (m_state) {
135 m_state->halt();
136 }
137}
138
139void Chimera::stamp(State& state, uint32_t index, const glm::vec4& timing)
140{
141 if (state.buffer->has_layer_data()) {
142 state.buffer->set_layer_timing(index, timing);
143 }
144}
145
146void Chimera::tick(State& state, uint64_t frame)
147{
148 state.ticks = frame + 1;
149 const double now = static_cast<double>(frame) / state.frame_rate;
150
151 uint32_t index = 0;
152 for (auto& layer : state.layers) {
153 const uint32_t current = index++;
154
155 if (now < layer.delay) {
156 continue;
157 }
158
159 const auto age = static_cast<float>(now - layer.delay);
160
161 if (layer.picture) {
162 if (layer.dirty && state.buffer->submit_layer(current, *layer.picture)) {
163 layer.dirty = false;
164 stamp(state, current, glm::vec4(age, 0.0F, 0.0F, 1.0F));
165 }
166 continue;
167 }
168
169 if (frame % layer.interval != 0) {
170 continue;
171 }
172
173 if (layer.image) {
174 if (state.buffer->submit_layer(current, layer.image)) {
175 stamp(state, current, glm::vec4(age, 0.0F, 0.0F, 1.0F));
176 }
177 } else if (layer.ring) {
178 feed(state, layer, current, now - layer.delay);
179 } else if (layer.collection) {
180 collect(state, layer, current, now - layer.delay);
181 }
182 }
183}
184
185void Chimera::place(const State& state, Layer& layer, uint64_t head, double rate, double nearest, double elapsed)
186{
187 const double latest = static_cast<double>(head) - nearest;
188
189 if (layer.lag) {
190 const double behind = ((*layer.lag)(elapsed) + layer.lag_offset) * rate;
191 layer.position = static_cast<double>(head) - std::max(behind, nearest);
192 } else if (!layer.entered) {
193 layer.position = latest;
194 } else {
195 layer.position = std::min(layer.position + *layer.ratio * rate / state.frame_rate, latest);
196 }
197 layer.entered = true;
198}
199
200void Chimera::collect(State& state, Layer& layer, uint32_t index, double elapsed)
201{
202 const auto& collection = layer.collection;
203 const uint64_t head = collection->get_write_head();
204 if (head == 0) {
205 return;
206 }
207
208 const double rate = layer.collection_rate > 0.0 ? layer.collection_rate : state.frame_rate;
209 place(state, layer, head, rate, 1.0, elapsed);
210
211 const auto frame = static_cast<uint64_t>(std::max(std::floor(layer.position), 0.0));
212 const uint64_t ring = collection->get_spec().ring_layers;
213 if (frame >= head || (ring > 0 && frame + ring < head)) {
214 if (!layer.missed) {
216 "Chimera layer {} is outside the collection, holding its last frame", index);
217 layer.missed = true;
218 }
219 return;
220 }
221 layer.missed = false;
222
223 const auto slot = static_cast<uint32_t>(ring > 0 ? frame % ring : frame);
224 if (state.buffer->submit_layer(index, collection->to_image(slot))) {
225 stamp(state, index,
226 glm::vec4(static_cast<float>(elapsed), static_cast<float>(static_cast<double>(head) - layer.position),
227 static_cast<float>(layer.position), 1.0F));
228 }
229}
230
231void Chimera::feed(State& state, Layer& layer, uint32_t index, double elapsed)
232{
233 const auto& ring = layer.ring;
234 const uint64_t head = ring->get_write_head();
235 if (head < 3) {
236 return;
237 }
238
239 place(state, layer, head, ring->get_frame_rate(), 2.0, elapsed);
240
241 const double whole = std::max(std::floor(layer.position), 0.0);
242 const auto frame = static_cast<uint64_t>(whole);
243 if (!ring->is_frame_available(frame)) {
244 if (!layer.missed) {
246 "Chimera layer {} is outside the ring, holding its last frame", index);
247 layer.missed = true;
248 }
249 return;
250 }
251 layer.missed = false;
252
253 std::span<const uint8_t> pixels = ring->get_frame_pixels(frame);
254 if (pixels.empty()) {
255 return;
256 }
257
258 const double fraction = layer.position - whole;
259 if (layer.smooth && fraction > 0.0 && ring->is_frame_available(frame + 1)) {
260 const auto next = ring->get_frame_pixels(frame + 1);
261 layer.scratch.resize(pixels.size());
262 if (!next.empty()
263 && Kakshya::blend_frames(ring->get_format(), pixels, next, layer.scratch.data(), fraction)) {
264 pixels = std::span<const uint8_t>(layer.scratch);
265 }
266 }
267
268 const bool same_extent = ring->get_width() == state.buffer->get_width()
269 && ring->get_height() == state.buffer->get_height()
270 && ring->get_format() == state.buffer->get_format();
271
272 bool stored = false;
273 if (same_extent) {
274 stored = state.buffer->submit_layer(index, pixels);
275 } else if (auto image = Kakshya::ImageData::from_bytes(
276 ring->get_width(), ring->get_height(),
278 ring->get_format(), pixels)) {
279 stored = state.buffer->submit_layer(index, *image);
280 }
281
282 if (stored) {
283 stamp(state, index,
284 glm::vec4(static_cast<float>(elapsed), static_cast<float>(static_cast<double>(head) - layer.position),
285 static_cast<float>(layer.position), 1.0F));
286 }
287}
288
290 std::shared_ptr<Buffers::TextureArrayBuffer> buffer,
291 std::shared_ptr<BufferPipeline> pipeline)
292 : m_buffer(std::move(buffer))
293 , m_pipeline(std::move(pipeline))
294{
295}
296
297ChimeraBuilder& ChimeraBuilder::use_pipeline(std::shared_ptr<BufferPipeline> pipeline)
298{
299 m_pipeline = std::move(pipeline);
300 return *this;
301}
302
304{
305 if (!m_pipeline) {
307 "ChimeraBuilder::record needs a pipeline");
308 return *this;
309 }
310
311 m_pipeline >> std::move(operation);
312 return *this;
313}
314
315ChimeraBuilder& ChimeraBuilder::every(double seconds, std::function<void(Chimera&)> action)
316{
317 m_actions.push_back({ .seconds = seconds, .run = std::move(action) });
318 return *this;
319}
320
322{
323 m_render = std::move(config);
324 return *this;
325}
326
328{
329 if (m_buffer) {
330 m_buffer->enable_layer_data();
331 }
332 return *this;
333}
334
336{
337 auto& target = current();
338 target.ring.reset();
339 target.collection.reset();
340 target.image.reset();
341 target.picture.reset();
342 target.piped = true;
343 return *this;
344}
345
347{
348 if (m_layers.empty()) {
349 m_layers.emplace_back();
350 }
351 return m_layers.back();
352}
353
355{
356 m_layers.emplace_back();
357 return *this;
358}
359
360ChimeraBuilder& ChimeraBuilder::from(std::shared_ptr<Kakshya::DynamicVideoStream> ring)
361{
362 auto& target = current();
363 target.image.reset();
364 target.picture.reset();
365 target.collection.reset();
366 target.piped = false;
367 target.ring = std::move(ring);
368 return *this;
369}
370
371ChimeraBuilder& ChimeraBuilder::from(std::shared_ptr<Kakshya::TextureCollection> collection, double frame_rate)
372{
373 auto& target = current();
374 target.image.reset();
375 target.picture.reset();
376 target.ring.reset();
377 target.piped = false;
378 target.collection = std::move(collection);
379 target.collection_rate = frame_rate;
380 return *this;
381}
382
383ChimeraBuilder& ChimeraBuilder::from(std::shared_ptr<Core::VKImage> image)
384{
385 auto& target = current();
386 target.ring.reset();
387 target.collection.reset();
388 target.picture.reset();
389 target.piped = false;
390 target.image = std::move(image);
391 return *this;
392}
393
395{
396 auto& target = current();
397 target.ring.reset();
398 target.collection.reset();
399 target.image.reset();
400 target.piped = false;
401 target.picture = std::make_shared<const Kakshya::ImageData>(std::move(image));
402 target.dirty = true;
403 return *this;
404}
405
406ChimeraBuilder& ChimeraBuilder::from(const std::string& path)
407{
408 if (auto loaded = IO::ImageReader::load(path, 4)) {
409 return from(std::move(*loaded));
410 }
411
413 "ChimeraBuilder::from could not load '{}'", path);
414 return *this;
415}
416
418{
419 current().delay = std::max(seconds, 0.0);
420 return *this;
421}
422
424{
425 return lag(Kinesis::constant<double, double>(seconds));
426}
427
429{
430 current().lag = std::make_shared<const Kinesis::TimeMap>(std::move(seconds));
431 return *this;
432}
433
435{
436 *current().ratio = ratio;
437 return *this;
438}
439
441{
442 current().smooth = enable;
443 return *this;
444}
445
447{
448 current().interval = std::max(frames, 1U);
449 return *this;
450}
451
457
459{
461 return *this;
462}
463
465{
466 current().level = static_cast<float>(weight);
467 return *this;
468}
469
471{
472 Chimera set;
473
474 if (!m_buffer || m_layers.empty()) {
476 "ChimeraBuilder::start needs a buffer and at least one layer");
477 return set;
478 }
479
480 if (m_layers.size() > m_buffer->get_layer_count()) {
482 "ChimeraBuilder::start declared {} layers, the buffer holds {}",
483 m_layers.size(), m_buffer->get_layer_count());
484 return set;
485 }
486
487 if (!m_pipeline) {
489 "ChimeraBuilder::start needs a pipeline to feed the layers");
490 return set;
491 }
492
493 for (auto& layer : m_layers) {
494 if (!layer.piped) {
495 continue;
496 }
497
498 layer.ring = m_pipeline->get_graphics_stream();
499 if (!layer.ring) {
501 "ChimeraBuilder::start found no stream in the pipeline for from_pipeline");
502 return set;
503 }
504 }
505
506 auto state = std::make_shared<Chimera::State>();
507
508 state->buffer = m_buffer;
509 state->pipeline = m_pipeline;
510 state->layers = m_layers;
511 state->frame_rate = std::max(static_cast<double>(Vruta::s_registered_frame_rate), 1.0);
512
513 uint32_t index = 0;
514 for (const auto& layer : state->layers) {
515 m_buffer->set_weight(index, layer.level);
516 if (layer.fit) {
517 m_buffer->set_layer_fit(index, layer.fit);
518 }
519 if (layer.params) {
520 m_buffer->set_layer_params(index, *layer.params);
521 }
522 ++index;
523 }
524
525 if (m_render) {
526 m_buffer->setup_rendering(*m_render);
527 m_render.reset();
528 }
529
530 const std::weak_ptr<Chimera::State> weak = state;
532 [weak](Kakshya::DataVariant&, uint32_t cycle) {
533 if (const auto running = weak.lock()) {
534 Chimera::tick(*running, cycle);
535 }
536 },
538
539 for (const auto& action : m_actions) {
540 const auto cycles = static_cast<uint32_t>(std::max(std::round(action.seconds * state->frame_rate), 1.0));
541
542 auto timed = BufferOperation::dispatch_to(
543 [weak, run = action.run](Kakshya::DataVariant&, uint32_t cycle) {
544 const auto running = weak.lock();
545 if (!running || cycle == 0) {
546 return;
547 }
548
549 Chimera view;
550 view.m_state = running;
551 run(view);
552 },
554 timed.every_n_cycles(cycles);
555
556 m_pipeline >> std::move(timed);
557 }
558
559 m_pipeline->execute_frame_rate();
560
561 set.m_guard = std::shared_ptr<void>(nullptr, [state](void*) { state->halt(); });
562 set.m_state = std::move(state);
563 return set;
564}
565
566} // namespace MayaFlux::Kriya
#define MF_ERROR(comp, ctx,...)
#define MF_WARN(comp, ctx,...)
IO::ImageData image
Definition Decoder.cpp:47
const std::vector< float > * pixels
Definition Decoder.cpp:48
glm::vec2 current
float rate
std::shared_ptr< NetworkGeometryBuffer > buffer
uint32_t mode
double weight
std::vector< std::byte > values
Definition VDBWriter.cpp:26
uint32_t index
Definition VKDevice.cpp:122
static std::optional< ImageData > load(const std::string &path, int desired_channels=4, std::optional< Kakshya::Region > region=std::nullopt)
Load image from file (static utility)
BufferOperation & every_n_cycles(uint32_t n)
Set cycle interval for periodic execution.
static BufferOperation dispatch_to(OperationFunction handler, Buffers::ProcessingToken token=Buffers::ProcessingToken::AUDIO_BACKEND)
Create a dispatch operation for external processing.
Fundamental unit of operation in buffer processing pipelines.
ChimeraBuilder & layer_data()
Create the per-layer buffer the shader reads as layerData.
Definition Chimera.cpp:327
ChimeraBuilder & params(const glm::vec4 &values)
Params for the layer in the buffer's per-layer data, such as placement.
Definition Chimera.cpp:458
ChimeraBuilder & record(BufferOperation &&operation)
Add an operation to the builder's pipeline, ahead of the feeding.
Definition Chimera.cpp:303
ChimeraBuilder & speed(double ratio)
Play a layer without a lag at this multiple of the ring's frame rate.
Definition Chimera.cpp:434
std::optional< Portal::Graphics::RenderConfig > m_render
Definition Chimera.hpp:320
ChimeraBuilder & lag(double seconds)
Read the ring this many seconds behind its write head, at least two frames.
Definition Chimera.cpp:423
ChimeraBuilder & fit(Portal::Graphics::FitMode mode)
Fit frames of another extent into this layer in this way, instead of the buffer's.
Definition Chimera.cpp:452
std::vector< Action > m_actions
Definition Chimera.hpp:322
ChimeraBuilder & level(double weight)
Weight passed to the shader for the layer.
Definition Chimera.cpp:464
ChimeraBuilder & use_pipeline(std::shared_ptr< BufferPipeline > pipeline)
Replace the builder's pipeline with one made elsewhere.
Definition Chimera.cpp:297
ChimeraBuilder & render(Portal::Graphics::RenderConfig config)
Draw the buffer this way once start() runs.
Definition Chimera.cpp:321
Chimera::Layer & current()
Definition Chimera.cpp:346
ChimeraBuilder & enters_after(double seconds)
Seconds after start() before the layer begins to be fed.
Definition Chimera.cpp:417
ChimeraBuilder & smooth(bool enable=true)
Blend the two frames around a ring position, so slow or varying time does not step.
Definition Chimera.cpp:440
ChimeraBuilder & from(std::shared_ptr< Kakshya::DynamicVideoStream > ring)
Feed the layer from a ring that is being recorded.
Definition Chimera.cpp:360
std::shared_ptr< Buffers::TextureArrayBuffer > m_buffer
Definition Chimera.hpp:318
ChimeraBuilder & layer()
Open the next layer.
Definition Chimera.cpp:354
ChimeraBuilder(std::shared_ptr< Buffers::TextureArrayBuffer > buffer, std::shared_ptr< BufferPipeline > pipeline)
Definition Chimera.cpp:289
ChimeraBuilder & every_n_frames(uint32_t frames)
Refresh a ring or GPU image layer every this many frames.
Definition Chimera.cpp:446
Chimera start()
Start feeding every declared layer on the frame clock.
Definition Chimera.cpp:470
ChimeraBuilder & every(double seconds, std::function< void(Chimera &)> action)
Call an action on the running Chimera every this many seconds.
Definition Chimera.cpp:315
ChimeraBuilder & from_pipeline()
Feed the layer from the stream the pipeline records, read when start() runs.
Definition Chimera.cpp:335
std::shared_ptr< BufferPipeline > m_pipeline
Definition Chimera.hpp:319
std::vector< Chimera::Layer > m_layers
Definition Chimera.hpp:321
Declares the layers of a Chimera, in order, and starts feeding them.
Definition Chimera.hpp:190
static void tick(State &state, uint64_t frame)
Definition Chimera.cpp:146
void params(size_t layer, const glm::vec4 &values)
Set the params of one layer in the buffer's per-layer data.
Definition Chimera.cpp:36
void speed(size_t layer, double ratio)
Change the speed of a freely playing layer.
Definition Chimera.cpp:43
static void stamp(State &state, uint32_t index, const glm::vec4 &timing)
Definition Chimera.cpp:139
void level(size_t layer, double weight)
Set the weight of one layer in the buffer's push constants.
Definition Chimera.cpp:29
void set(size_t layer, Kakshya::ImageData image)
Replace the layer's content with a still image, delivered on the next frame.
Definition Chimera.cpp:76
void stop()
Stop feeding.
Definition Chimera.cpp:132
size_t layer_count() const
Number of layers.
Definition Chimera.cpp:24
static void collect(State &state, Layer &layer, uint32_t index, double elapsed)
Definition Chimera.cpp:200
static void feed(State &state, Layer &layer, uint32_t index, double elapsed)
Definition Chimera.cpp:231
void cut(size_t layer, double seconds)
Jump a ring or collection layer to a moment this many seconds behind the live head.
Definition Chimera.cpp:50
std::shared_ptr< State > m_state
Definition Chimera.hpp:159
static void place(const State &state, Layer &layer, uint64_t head, double rate, double nearest, double elapsed)
Definition Chimera.cpp:185
Assembles the layers of a TextureArrayBuffer from whatever feeds them, so a fragment shader can build...
Definition Chimera.hpp:51
static uint32_t get_channel_count(ImageFormat format)
Get the number of color channels for a given format.
void run()
Definition main.cpp:21
@ GRAPHICS_BACKEND
Standard graphics processing backend configuration.
@ Configuration
Configuration and parameter updates.
@ FileIO
Filesystem I/O operations.
@ CoroutineScheduling
Coroutine scheduling and temporal coordination (Vruta::TaskScheduler)
@ Kriya
Automatable tasks and fluent scheduling api for Nodes and Buffers.
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
bool blend_frames(ImageFormat format, std::span< const uint8_t > a, std::span< const uint8_t > b, uint8_t *out, double weight)
Linearly blend two frames of the same size and format.
FitMode
How a source extent is mapped onto a destination extent.
MAYAFLUX_API uint32_t s_registered_frame_rate
Definition Clock.cpp:10
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
Raw image data loaded from file or read back from a GPU image.
Definition ImageData.hpp:55
Typed, composable, stateless callable from domain D to range R.
Definition Tendency.hpp:22
std::shared_ptr< Kakshya::DynamicVideoStream > ring
Definition Chimera.hpp:121
std::optional< glm::vec4 > params
Definition Chimera.hpp:129
std::optional< Portal::Graphics::FitMode > fit
Definition Chimera.hpp:128
std::vector< uint8_t > scratch
Definition Chimera.hpp:130
std::shared_ptr< double > ratio
Definition Chimera.hpp:127
std::shared_ptr< Kakshya::TextureCollection > collection
Definition Chimera.hpp:122
std::shared_ptr< const Kinesis::TimeMap > lag
Definition Chimera.hpp:126
std::vector< Layer > layers
Definition Chimera.hpp:146
std::shared_ptr< BufferPipeline > pipeline
Definition Chimera.hpp:145
std::shared_ptr< Buffers::TextureArrayBuffer > buffer
Definition Chimera.hpp:144
Unified rendering configuration for graphics buffers.