CYD-UI
A C++ library for building native graphic user interfaces
Loading...
Searching...
No Matches
component_renderer.cppm
Go to the documentation of this file.
1// Copyright (c) 2024, Víctor Castillo Agüero.
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4module;
5#include <tracy/Tracy.hpp>
6#include "cyd_fabric_modules/headers/macros/async_events.h"
7#include <SDL3/SDL.h>
8
9export module cydui.components.renderer;
10
11import std;
12import fabric.logging;
13import fabric.wiring.signals;
14
16import cydui.graphics;
17export import cydui.components.base;
18
19namespace cydui::components {
20 export class component_renderer_t {
21 public:
22 using sptr = std::shared_ptr<component_renderer_t>;
23
24 static sptr make() {
25 return std::make_shared<component_renderer_t>();
26 }
27
28 fabric::wiring::output_signal<component_renderer_t, compositing::compositing_node_t*>
30
31 public:
37
38 bool is_compositing() const {
39 return is_compositing_.test();
40 }
41
42 std::mutex& compositing_mutex() {
43 return compositing_mtx;
44 }
45
46 void queue_render(const component_base_t::sptr& component) {
47 auto& data = component->get_data<render_data_t>();
48 data.graphics_dirty_ = true;
49 }
50
51 void render(graphics::window_t& window, const component_base_t::sptr& component) {
52 ZoneScopedN("Render Flow");
53 if (is_compositing_.test_and_set()) {
54 composite_is_outdated.test_and_set();
55 return;
56 }
57 update_fragments(component);
58 {
59 ZoneScopedN("Start Render");
60
62 [](
66 ) {
67 ZoneScopedN("Start layout render");
68 self->start_render(component_, w);
69 },
70 this,
71 &window,
72 component.get()
73 );
74 }
75 bool needs_compositing = false;
76 {
77 ZoneScopedN("Render");
78
79 needs_compositing = repaint(component.get(), &window);
80 }
81 if (needs_compositing) {
82 ZoneScopedN("Queuing Composition");
83 // compositing_tree->fix_dimensions();
85 [](
87 std::mutex* mtx,
88 std::atomic_flag* completion_flag,
89 std::atomic_flag* is_outdated,
92 ) -> fabric::task<> {
93 ZoneScopedN("Compositing Layout");
94 std::scoped_lock lock(*mtx);
95 auto&& [root_node, must_recompose] = self->compose(root_ptr.get(), w);
96
97 if (must_recompose) {
98 ZoneScopedN("Compositing Frame");
99 self->compositing_signal.emit(root_node);
100 }
101
102 completion_flag->clear();
103 if (is_outdated->test()) {
104 //-IMPORTANT: Need to make copy of pointers so they aren't passed
105 // as references which will not survive
106 component_renderer_t& s = *self;
107 graphics::window_t& ww = *w;
108 std::atomic_flag& flag = *is_outdated;
109 //---------------------------------------------------------------
110 w->bus->get_executor()->schedule(
111 [](
113 std::atomic_flag* is_outdated,
116 ) -> fabric::task<> {
117 selff->render(*w_, root_ptr_);
118 is_outdated->clear();
119 co_return;
120 },
121 &s,
122 &flag,
123 &ww,
124 root_ptr
125 );
126 }
127 co_return;
128 },
129 this,
130 &compositing_mtx,
131 &is_compositing_,
132 &composite_is_outdated,
133 &window,
134 component
135 );
136 }
137 }
138
139 void compose_all(graphics::window_t& window, const component_base_t::sptr& root_component) {
140 ZoneScopedN("Compose All");
141 if (is_compositing_.test_and_set()) {
142 composite_is_outdated.test_and_set();
143 return;
144 }
145
147 [](
149 std::mutex* mtx,
150 std::atomic_flag* completion_flag,
151 std::atomic_flag* is_outdated,
154 ) -> fabric::task<> {
155 ZoneScopedN("Compositing Layout");
156 std::scoped_lock lock(*mtx);
157 auto&& [root_node, must_recompose] = self->compose(root_ptr.get(), w);
158
159 if (must_recompose) {
160 ZoneScopedN("Compositing Frame");
161 self->compositing_signal.emit(root_node);
162 }
163
164 completion_flag->clear();
165 if (is_outdated->test()) {
166 //-IMPORTANT: Need to make copy of pointers so they aren't passed
167 // as references which will not survive
168 component_renderer_t& s = *self;
169 graphics::window_t& ww = *w;
170 std::atomic_flag& flag = *is_outdated;
171 //---------------------------------------------------------------
172 w->bus->get_executor()->schedule(
173 [](
175 std::atomic_flag* is_outdated,
178 ) -> fabric::task<> {
179 selff->render(*w_, root_ptr_);
180 is_outdated->clear();
181 co_return;
182 },
183 &s,
184 &flag,
185 &ww,
186 root_ptr
187 );
188 }
189 co_return;
190 },
191 this,
192 &compositing_mtx,
193 &is_compositing_,
194 &composite_is_outdated,
195 &window,
196 root_component
197 );
198 }
199
201 ZoneScopedN("Repaint Component");
202 auto* parent = component->parent.has_value()
203 ? &(component->parent.value()->get_data<render_data_t>().compositing_node_)
204 : nullptr;
205 queue_render(component);
206 update_fragment(component.get(), parent);
207 }
208
210 ZoneScopedN("Render All");
211 if (is_compositing_.test()) {
212 return false;
213 }
214
215 {
216 ZoneScopedN("Start Render");
217
219 [](
223 ) {
224 ZoneScopedN("Start render");
225 self->start_render(component_, w);
226 },
227 this,
228 &win,
229 root.get()
230 );
231 }
232
233 bool needs_compositing = false;
234 {
235 ZoneScopedN("Render");
236
237 needs_compositing = repaint(root.get(), &win);
238 }
239
240 return needs_compositing;
241 }
242
243 private:
244 void update_fragments(const component_base_t::sptr& component) {
245 ZoneScopedN("Fragments");
246 auto* parent = component->parent.has_value()
247 ? &(component->parent.value()->get_data<render_data_t>().compositing_node_)
248 : nullptr;
249 update_fragment(component.get(), parent);
250
251 for (auto& child: component->children) {
252 update_fragments(child);
253 }
254 }
255
256 bool update_compositing_operation(
257 component_base_t* component, compositing::compositing_node_t* parent_node
258 ) {
259 ZoneScopedN("Update Compose Op");
260 static auto get_num_value = [](const auto& it) -> auto {
261 return dimensions::get_value(it).template as<dimensions::screen::pixel>().value;
262 };
263
264 auto& data = component->get_data<render_data_t>();
265
266 auto old_op = data.compositing_node_.op;
267 bool old_is_flattened = data.compositing_node_.is_flattened_node();
268
269 auto& at = component->get_style();
270 data.compositing_node_.id = (unsigned long)(component->state().get());
271 data.compositing_node_.op = {
272 .x = static_cast<int>(get_num_value(at.x) + get_num_value(at.margin.left)),
273 .y = static_cast<int>(get_num_value(at.y) + get_num_value(at.margin.top)),
274 .orig_x = static_cast<int>(get_num_value(at.padding.left)),
275 .orig_y = static_cast<int>(get_num_value(at.padding.top)),
276 .w = static_cast<int>(get_num_value(at.width)),
277 .h = static_cast<int>(get_num_value(at.height)),
278 .rot = at.rotation.value_as_base_unit(), // dim->rot.val(),
279 .scale_x = 1.0, // dim->scale_x.val(),
280 .scale_y = 1.0, // dim->scale_y.val(),
281 .animated = component->state()->is_animated(),
282 };
283
284 data.compositing_node_.set_parent(parent_node);
285
286 return old_op != data.compositing_node_.op
287 or old_is_flattened != data.compositing_node_.is_flattened_node();
288 }
289
290 void
291 update_fragment(component_base_t* component, compositing::compositing_node_t* parent_node) {
292 ZoneScopedN("Update Fragment");
293 auto& data = component->get_data<render_data_t>();
294
295 if (update_compositing_operation(component, parent_node)) {
296 data.graphics_dirty_ = true;
297 }
298
299 if (data.graphics_dirty_) {
300 data.compositing_node_.mark_flattening_target_dirty();
301 paint_fragment(component, data.compositing_node_);
302 }
303 }
304
305 void
306 paint_fragment(component_base_t* component, compositing::compositing_node_t& compositing_node) {
307 auto get_num_value = [](const auto& it) -> auto {
308 return dimensions::get_value(it).template as<dimensions::screen::pixel>().value;
309 };
310
311 auto& fragment = compositing_node.graphics;
312 fragment.clear();
313
314 auto& at = component->get_style();
315 int half_top_border = at.border_width.top >> 1;
316 int half_bottom_border = (at.border_width.bottom >> 1) + (at.border_width.bottom & 1);
317 int half_left_border = at.border_width.left >> 1;
318 int half_right_border = (at.border_width.right >> 1) + (at.border_width.right & 1);
319
320 // The four border corners, in reading order (left -> right, top ->bottom)
321 int x1 = (half_left_border)-get_num_value(at.padding.left);
322 int y1 = (half_top_border)-get_num_value(at.padding.top);
323
324 int x2 = x1 + get_num_value(at.width) - (half_left_border) - (half_right_border);
325 int y2 = y1;
326
327 int x3 = x1;
328 int y3 = y1 + get_num_value(at.height) - (half_top_border) - (half_bottom_border);
329
330 int x4 = x2;
331 int y4 = y3;
332
333 fragment.draw<vg::rectangle>()
334 .x(-dimensions::get_value(at.padding.left))
335 .y(-dimensions::get_value(at.padding.top))
336 .w(dimensions::get_value(at.width))
337 .h(dimensions::get_value(at.height))
338 .fill(component->get_style().background);
339 fragment.draw<vg::line>()
340 .x1(x1 - half_left_border)
341 .y1(y1)
342 .x2(x2 + half_right_border)
343 .y2(y2)
344 .stroke(at.border.top)
345 .stroke_width(at.border_width.top)
346 .stroke_dasharray(at.border_dasharray.top);
347 fragment.draw<vg::line>()
348 .x1(x4 + half_right_border)
349 .y1(y4)
350 .x2(x3 - half_left_border)
351 .y2(y3)
352 .stroke(at.border.bottom)
353 .stroke_width(at.border_width.bottom)
354 .stroke_dasharray(at.border_dasharray.bottom);
355 fragment.draw<vg::line>()
356 .x1(x3)
357 .y1(y3 + half_bottom_border)
358 .x2(x1)
359 .y2(y1 - half_top_border)
360 .stroke(at.border.left)
361 .stroke_width(at.border_width.left)
362 .stroke_dasharray(at.border_dasharray.left);
363 fragment.draw<vg::line>()
364 .x1(x2)
365 .y1(y2 - half_top_border)
366 .x2(x4)
367 .y2(y4 + half_bottom_border)
368 .stroke(at.border.right)
369 .stroke_width(at.border_width.right)
370 .stroke_dasharray(at.border_dasharray.right);
371
372
373 component->get_event_dispatcher()->paint_fragment(fragment);
374
376 // if (!fragment.empty()) {
377 // for (const auto& elem: fragment.elements) {
378 // auto fp = elem->get_footprint();
379 // if ((fp.x + fp.w) > compositing_node.op.w) {
380 // compositing_node.op.w = fp.x.value_as_base_unit() + fp.w.value_as_base_unit();
381 // }
382 // if ((fp.y + fp.h) > compositing_node.op.h) {
383 // compositing_node.op.h = fp.y.value_as_base_unit() + fp.h.value_as_base_unit();
384 // }
385 // }
386 // }
387 }
388
389 void start_render(component_base_t* component, graphics::window_t* render_target) {
390 auto& data = component->get_data<render_data_t>();
391 if (data.graphics_dirty_ or data.compositing_node_.is_flattened_node()) {
392 data.compositing_node_.start_render(render_target);
393 }
394
395 for (auto& child: component->children) {
396 start_render(child.get(), render_target);
397 }
398 }
399
400 bool repaint(component_base_t* component, graphics::window_t* render_target) {
401 auto& data = component->get_data<render_data_t>();
402 if (data.graphics_dirty_ or data.compositing_node_.is_dirty_from_flattening()) {
403 data.graphics_dirty_ = false;
404 data.compositing_dirty_ = true;
405 data.compositing_node_.render(render_target);
406 }
407
408 for (auto& child: component->children) {
409 if (repaint(child.get(), render_target)) {
410 data.compositing_dirty_ = true;
411 }
412 }
413 return data.compositing_dirty_;
414 }
415
416 std::pair<compositing::compositing_node_t*, bool>
417 compose(component_base_t* component, graphics::window_t* render_target) {
418 auto& data = component->get_data<render_data_t>();
419 // compositing_node_.clear_composite_texture(render_target);
420 bool did_compositing = false;
421 if (data.compositing_dirty_) {
422 data.compositing_dirty_ = false;
423
424 data.compositing_node_.compose_own(render_target);
425
426 for (auto& child: component->children) {
427 auto&& [node, _] = compose(child.get(), render_target);
428 if (nullptr != node) {
429 data.compositing_node_.compose(render_target, node);
430 }
431 }
432
433 did_compositing = true;
434 }
435
436 if (data.compositing_node_.is_flattened_node()) {
437 return {nullptr, did_compositing};
438 } else {
439 return {&data.compositing_node_, did_compositing};
440 }
441 }
442
443 private:
444 std::atomic_flag is_compositing_{false};
445 std::atomic_flag composite_is_outdated{false};
446
447 std::mutex compositing_mtx{};
448 };
449} // namespace cydui::components
static auto schedule(auto &&fun, Args &&... args)
static auto run(auto &&fun, Args &&... args)
std::shared_ptr< component_base_t > sptr
void render(graphics::window_t &window, const component_base_t::sptr &component)
fabric::wiring::output_signal< component_renderer_t, compositing::compositing_node_t * > compositing_signal
void compose_all(graphics::window_t &window, const component_base_t::sptr &root_component)
std::shared_ptr< component_renderer_t > sptr
void repaint_component(const component_base_t::sptr &component)
void queue_render(const component_base_t::sptr &component)
bool render_all(graphics::window_t &win, const component_base_t::sptr &root)
const S & get_value(dimension< S > &dimension)
Definition api.cppm:24
enum cydui::compositing::compositing_operation_t::@321126061026340171363272107326127342244210103306 op
fabric::async::async_bus_t * bus
Definition window.cppm:36