Loading...
Searching...
No Matches
GpuUtils.hpp
1#pragma once
2
3#if SCORE_PLUGIN_GFX
4#include <Process/ExecutionContext.hpp>
5
6#include <Crousti/File.hpp>
7#include <Crousti/MessageBus.hpp>
8#include <Gfx/GfxExecNode.hpp>
9#include <Gfx/Graph/Node.hpp>
10#include <Gfx/Graph/OutputNode.hpp>
11#include <Gfx/Graph/RenderState.hpp>
12
13#include <QTimer>
14#include <QtGui/private/qrhi_p.h>
15
16#include <avnd/binding/ossia/port_run_postprocess.hpp>
17#include <avnd/binding/ossia/port_run_preprocess.hpp>
18#include <avnd/binding/ossia/soundfiles.hpp>
19#include <avnd/concepts/parameter.hpp>
20#include <avnd/introspection/input.hpp>
21#include <avnd/introspection/output.hpp>
22#include <fmt/format.h>
23#include <gpp/layout.hpp>
24
25#include <score_plugin_avnd_export.h>
26
27namespace gpp::qrhi
28{
29template <typename F>
30constexpr QRhiTexture::Format textureFormat()
31{
32 if constexpr(requires { std::string_view{F::format()}; })
33 {
34 constexpr std::string_view fmt = F::format();
35
36 if(fmt == "rgba" || fmt == "rgba8")
37 return QRhiTexture::RGBA8;
38 else if(fmt == "bgra" || fmt == "bgra8")
39 return QRhiTexture::BGRA8;
40 else if(fmt == "r8")
41 return QRhiTexture::R8;
42#if QT_VERSION >= QT_VERSION_CHECK(6, 4, 0)
43 else if(fmt == "rg8")
44 return QRhiTexture::RG8;
45#endif
46 else if(fmt == "r16")
47 return QRhiTexture::R16;
48#if QT_VERSION >= QT_VERSION_CHECK(6, 4, 0)
49 else if(fmt == "rg16")
50 return QRhiTexture::RG16;
51#endif
52 else if(fmt == "red_or_alpha8")
53 return QRhiTexture::RED_OR_ALPHA8;
54 else if(fmt == "rgba16f")
55 return QRhiTexture::RGBA16F;
56 else if(fmt == "rgba32f")
57 return QRhiTexture::RGBA32F;
58 else if(fmt == "r16f")
59 return QRhiTexture::R16F;
60 else if(fmt == "r32")
61 return QRhiTexture::R32F;
62#if QT_VERSION >= QT_VERSION_CHECK(6, 4, 0)
63 else if(fmt == "rgb10a2")
64 return QRhiTexture::RGB10A2;
65#endif
66
67 else if(fmt == "d16")
68 return QRhiTexture::D16;
69
70#if QT_VERSION >= QT_VERSION_CHECK(6, 4, 0)
71 else if(fmt == "d24")
72 return QRhiTexture::D24;
73 else if(fmt == "d24s8")
74 return QRhiTexture::D24S8;
75#endif
76 else if(fmt == "d32f")
77 return QRhiTexture::D32F;
78
79 else if(fmt == "bc1")
80 return QRhiTexture::BC1;
81 else if(fmt == "bc2")
82 return QRhiTexture::BC2;
83 else if(fmt == "bc3")
84 return QRhiTexture::BC3;
85 else if(fmt == "bc4")
86 return QRhiTexture::BC4;
87 else if(fmt == "bc5")
88 return QRhiTexture::BC5;
89 else if(fmt == "bc6h")
90 return QRhiTexture::BC6H;
91 else if(fmt == "bc7")
92 return QRhiTexture::BC7;
93 else if(fmt == "etc2_rgb8")
94 return QRhiTexture::ETC2_RGB8;
95 else if(fmt == "etc2_rgb8a1")
96 return QRhiTexture::ETC2_RGB8A1;
97 else if(fmt == "etc2_rgb8a8")
98 return QRhiTexture::ETC2_RGBA8;
99 else if(fmt == "astc_4x4")
100 return QRhiTexture::ASTC_4x4;
101 else if(fmt == "astc_5x4")
102 return QRhiTexture::ASTC_5x4;
103 else if(fmt == "astc_5x5")
104 return QRhiTexture::ASTC_5x5;
105 else if(fmt == "astc_6x5")
106 return QRhiTexture::ASTC_6x5;
107 else if(fmt == "astc_6x6")
108 return QRhiTexture::ASTC_6x6;
109 else if(fmt == "astc_8x5")
110 return QRhiTexture::ASTC_8x5;
111 else if(fmt == "astc_8x6")
112 return QRhiTexture::ASTC_8x6;
113 else if(fmt == "astc_8x8")
114 return QRhiTexture::ASTC_8x8;
115 else if(fmt == "astc_10x5")
116 return QRhiTexture::ASTC_10x5;
117 else if(fmt == "astc_10x6")
118 return QRhiTexture::ASTC_10x6;
119 else if(fmt == "astc_10x8")
120 return QRhiTexture::ASTC_10x8;
121 else if(fmt == "astc_10x10")
122 return QRhiTexture::ASTC_10x10;
123 else if(fmt == "astc_12x10")
124 return QRhiTexture::ASTC_12x10;
125 else if(fmt == "astc_12x12")
126 return QRhiTexture::ASTC_12x12;
127 else
128 return QRhiTexture::RGBA8;
129 }
130 else if constexpr(std::is_enum_v<typename F::format>)
131 {
132 if constexpr(requires { F::RGBA; } || requires { F::RGBA8; })
133 return QRhiTexture::RGBA8;
134 else if constexpr(requires { F::BGRA; } || requires { F::BGRA8; })
135 return QRhiTexture::BGRA8;
136 else if constexpr(requires { F::R8; } || requires { F::GRAYSCALE; })
137 return QRhiTexture::R8;
138#if QT_VERSION >= QT_VERSION_CHECK(6, 4, 0)
139 else if constexpr(requires { F::RG8; })
140 return QRhiTexture::RG8;
141#endif
142 else if constexpr(requires { F::R16; })
143 return QRhiTexture::R16;
144#if QT_VERSION >= QT_VERSION_CHECK(6, 4, 0)
145 else if constexpr(requires { F::RG16; })
146 return QRhiTexture::RG16;
147#endif
148 else if constexpr(requires { F::RED_OR_ALPHA8; })
149 return QRhiTexture::RED_OR_ALPHA8;
150 else if constexpr(requires { F::RGBA16F; })
151 return QRhiTexture::RGBA16F;
152 else if constexpr(requires { F::RGBA32F; })
153 return QRhiTexture::RGBA32F;
154 else if constexpr(requires { F::R16F; })
155 return QRhiTexture::R16F;
156 else if constexpr(requires { F::R32F; })
157 return QRhiTexture::R32F;
158#if QT_VERSION >= QT_VERSION_CHECK(6, 4, 0)
159 else if constexpr(requires { F::RGB10A2; })
160 return QRhiTexture::RGB10A2;
161#endif
162 else if constexpr(requires { F::D16; })
163 return QRhiTexture::D16;
164
165#if QT_VERSION >= QT_VERSION_CHECK(6, 4, 0)
166 else if constexpr(requires { F::D24; })
167 return QRhiTexture::D24;
168 else if constexpr(requires { F::D24S8; })
169 return QRhiTexture::D24S8;
170#endif
171 else if constexpr(requires { F::D32F; })
172 return QRhiTexture::D32F;
173
174 else if constexpr(requires { F::BC1; })
175 return QRhiTexture::BC1;
176 else if constexpr(requires { F::BC2; })
177 return QRhiTexture::BC2;
178 else if constexpr(requires { F::BC3; })
179 return QRhiTexture::BC3;
180 else if constexpr(requires { F::BC4; })
181 return QRhiTexture::BC4;
182 else if constexpr(requires { F::BC5; })
183 return QRhiTexture::BC5;
184 else if constexpr(requires { F::BC6H; })
185 return QRhiTexture::BC6H;
186 else if constexpr(requires { F::BC7; })
187 return QRhiTexture::BC7;
188 else if(requires { F::ETC2_RGB8; })
189 return QRhiTexture::ETC2_RGB8;
190 else if(requires { F::ETC2_RGB8A1; })
191 return QRhiTexture::ETC2_RGB8A1;
192 else if(requires { F::ETC2_RGB8A8; })
193 return QRhiTexture::ETC2_RGBA8;
194 else if(requires { F::ASTC_4X4; })
195 return QRhiTexture::ASTC_4x4;
196 else if(requires { F::ASTC_5X4; })
197 return QRhiTexture::ASTC_5x4;
198 else if(requires { F::ASTC_5X5; })
199 return QRhiTexture::ASTC_5x5;
200 else if(requires { F::ASTC_6X5; })
201 return QRhiTexture::ASTC_6x5;
202 else if(requires { F::ASTC_6X6; })
203 return QRhiTexture::ASTC_6x6;
204 else if(requires { F::ASTC_8X5; })
205 return QRhiTexture::ASTC_8x5;
206 else if(requires { F::ASTC_8X6; })
207 return QRhiTexture::ASTC_8x6;
208 else if(requires { F::ASTC_8X8; })
209 return QRhiTexture::ASTC_8x8;
210 else if(requires { F::ASTC_10X5; })
211 return QRhiTexture::ASTC_10x5;
212 else if(requires { F::ASTC_10X6; })
213 return QRhiTexture::ASTC_10x6;
214 else if(requires { F::ASTC_10X8; })
215 return QRhiTexture::ASTC_10x8;
216 else if(requires { F::ASTC_10X10; })
217 return QRhiTexture::ASTC_10x10;
218 else if(requires { F::ASTC_12X10; })
219 return QRhiTexture::ASTC_12x10;
220 else if(requires { F::ASTC_12X12; })
221 return QRhiTexture::ASTC_12x12;
222 else
223 return QRhiTexture::RGBA8;
224 }
225}
226
227struct DefaultPipeline
228{
229 struct layout
230 {
231 enum
232 {
233 graphics
234 };
235 struct vertex_input
236 {
237 struct
238 {
239 static constexpr auto name() { return "position"; }
240 static constexpr int location() { return 0; }
241 float data[2];
242 } pos;
243 };
244 struct vertex_output
245 {
246 };
247 struct fragment_input
248 {
249 };
250 struct bindings
251 {
252 };
253 };
254
255 static QString vertex()
256 {
257 return R"_(#version 450
258layout(location = 0) in vec2 position;
259out gl_PerVertex { vec4 gl_Position; };
260void main() {
261 gl_Position = vec4( position, 0.0, 1.0 );
262}
263)_";
264 }
265};
266
267template <typename C>
268constexpr auto usage()
269{
270 if constexpr(requires { C::vertex; })
271 return QRhiBuffer::VertexBuffer;
272 else if constexpr(requires { C::index; })
273 return QRhiBuffer::IndexBuffer;
274 else if constexpr(requires { C::ubo; })
275 return QRhiBuffer::UniformBuffer;
276 else if constexpr(requires { C::storage; })
277 return QRhiBuffer::StorageBuffer;
278 else
279 {
280 static_assert(C::unhandled);
281 throw;
282 }
283}
284
285template <typename C>
286constexpr auto buffer_type()
287{
288 if constexpr(requires { C::immutable; })
289 return QRhiBuffer::Immutable;
290 else if constexpr(requires { C::static_; })
291 return QRhiBuffer::Static;
292 else if constexpr(requires { C::dynamic; })
293 return QRhiBuffer::Dynamic;
294 else
295 {
296 static_assert(C::unhandled);
297 throw;
298 }
299}
300
301template <typename C>
302auto samples(C c)
303{
304 if constexpr(requires { C::samples; })
305 return c.samples;
306 else
307 return -1;
308}
309
310struct handle_release
311{
312 QRhi& rhi;
313 template <typename C>
314 void operator()(C command)
315 {
316 if constexpr(requires { C::deallocation; })
317 {
318 if constexpr(
319 requires { C::vertex; } || requires { C::index; } || requires { C::ubo; })
320 {
321 auto buf = reinterpret_cast<QRhiBuffer*>(command.handle);
322 buf->deleteLater();
323 }
324 else if constexpr(requires { C::sampler; })
325 {
326 auto buf = reinterpret_cast<QRhiSampler*>(command.handle);
327 buf->deleteLater();
328 }
329 else if constexpr(requires { C::texture; })
330 {
331 auto buf = reinterpret_cast<QRhiTexture*>(command.handle);
332 buf->deleteLater();
333 }
334 else
335 {
336 static_assert(C::unhandled);
337 }
338 }
339 else
340 {
341 static_assert(C::unhandled);
342 }
343 }
344};
345
346template <typename Self, typename Res>
347struct handle_dispatch
348{
349 Self& self;
350 QRhi& rhi;
351 QRhiCommandBuffer& cb;
352 QRhiResourceUpdateBatch*& res;
353 QRhiComputePipeline& pip;
354 template <typename C>
355 Res operator()(C command)
356 {
357 if constexpr(requires { C::compute; })
358 {
359 if constexpr(requires { C::dispatch; })
360 {
361 cb.dispatch(command.x, command.y, command.z);
362 return {};
363 }
364 else if constexpr(requires { C::begin; })
365 {
366 cb.beginComputePass(res);
367 res = nullptr;
368 cb.setComputePipeline(&pip);
369 cb.setShaderResources(pip.shaderResourceBindings());
370
371 return {};
372 }
373 else if constexpr(requires { C::end; })
374 {
375 cb.endComputePass(res);
376 res = nullptr;
377 rhi.finish();
378 return {};
379 }
380 else
381 {
382 static_assert(C::unhandled);
383 return {};
384 }
385 }
386 else if constexpr(requires { C::readback; })
387 {
388 // First handle the readback request
389 if constexpr(requires { C::request; })
390 {
391 if constexpr(requires { C::buffer; })
392 {
393 using ret = typename C::return_type;
394
395#if QT_VERSION < QT_VERSION_CHECK(6, 6, 0)
396 auto readback = new QRhiBufferReadbackResult;
397#else
398 auto readback = new QRhiReadbackResult;
399#endif
400 self.addReadback(readback);
401
402 // this is e.g. a buffer_awaiter
403 ret user_rb{.handle = reinterpret_cast<decltype(ret::handle)>(readback)};
404
405 // TODO: do it with coroutines like this for peak asyncess
406 // ret must be a coroutine type.
407 // When the GPU completes the work, "completed" is called:
408 // this will cause the coroutine to be filled with the data
409 // readback->completed = [=] {
410 // qDebug() << "alhamdullilah he will be baked";
411 // // store "data" in the coroutine
412 // };
413
414 auto next = rhi.nextResourceUpdateBatch();
415 auto buf = reinterpret_cast<QRhiBuffer*>(command.handle);
416
417 next->readBackBuffer(buf, command.offset, command.size, readback);
418 res = next;
419
420 return user_rb;
421 }
422 else if constexpr(requires { C::texture; })
423 {
424 using ret = typename C::return_type;
425 QRhiReadbackResult readback;
426 return ret{};
427 }
428 else
429 {
430 static_assert(C::unhandled);
431 return {};
432 }
433 }
434 else if constexpr(requires { C::await; })
435 {
436 if constexpr(requires { C::buffer; })
437 {
438 using ret = typename C::return_type;
439
440#if QT_VERSION < QT_VERSION_CHECK(6, 6, 0)
441 auto readback = reinterpret_cast<QRhiBufferReadbackResult*>(command.handle);
442#else
443 auto readback = reinterpret_cast<QRhiReadbackResult*>(command.handle);
444#endif
445
446 return ret{
447 .data = readback->data.data(), .size = (std::size_t)readback->data.size()};
448 }
449 else if constexpr(requires { C::texture; })
450 {
451 using ret = typename C::return_type;
452
453 auto readback = reinterpret_cast<QRhiReadbackResult*>(command.handle);
454
455 return ret{
456 .data = readback->data.data(), .size = (std::size_t)readback->data.size()};
457 }
458 }
459 }
460 else
461 {
462 static_assert(C::unhandled);
463 return {};
464 }
465
466 return {};
467 }
468};
469
470template <typename Self, typename Ret>
471struct handle_update
472{
473 Self& self;
474 QRhi& rhi;
475 QRhiResourceUpdateBatch& res;
476 std::vector<QRhiShaderResourceBinding>& srb;
477 bool& srb_touched;
478
479 template <typename C>
480 Ret operator()(C command)
481 {
482 if constexpr(requires { C::allocation; })
483 {
484 if constexpr(
485 requires { C::vertex; } || requires { C::index; })
486 {
487 auto buf = rhi.newBuffer(buffer_type<C>(), usage<C>(), command.size);
488 buf->create();
489 return reinterpret_cast<typename C::return_type>(buf);
490 }
491 else if constexpr(requires { C::sampler; })
492 {
493 auto buf = rhi.newSampler({}, {}, {}, {}, {});
494 buf->create();
495 return reinterpret_cast<typename C::return_type>(buf);
496 }
497 else if constexpr(
498 requires { C::ubo; } || requires { C::storage; })
499 {
500 auto buf = rhi.newBuffer(buffer_type<C>(), usage<C>(), command.size);
501 buf->create();
502
503 // Replace it in our bindings
504 score::gfx::replaceBuffer(srb, command.binding, buf);
505 srb_touched = true;
506 return reinterpret_cast<typename C::return_type>(buf);
507 }
508 else if constexpr(requires { C::texture; })
509 {
510 auto tex = rhi.newTexture(
511 QRhiTexture::RGBA8, QSize{command.width, command.height}, samples(command));
512 tex->create();
513
514 score::gfx::replaceTexture(srb, command.binding, tex);
515 srb_touched = true;
516 return reinterpret_cast<typename C::return_type>(tex);
517 }
518 else
519 {
520 static_assert(C::unhandled);
521 return {};
522 }
523 }
524 else if constexpr(requires { C::upload; })
525 {
526 if constexpr(requires { C::texture; })
527 {
528 QRhiTextureSubresourceUploadDescription sub(command.data, command.size);
529 res.uploadTexture(
530 reinterpret_cast<QRhiTexture*>(command.handle),
531 QRhiTextureUploadDescription{{0, 0, sub}});
532 }
533 else
534 {
535 auto buf = reinterpret_cast<QRhiBuffer*>(command.handle);
536 if constexpr(requires { C::dynamic; })
537 res.updateDynamicBuffer(buf, command.offset, command.size, command.data);
538 else if constexpr(
539 requires { C::static_; } || requires { C::immutable; })
540 res.uploadStaticBuffer(buf, command.offset, command.size, command.data);
541 else
542 {
543 static_assert(C::unhandled);
544 return {};
545 }
546 }
547 }
548 else if constexpr(requires { C::getter; })
549 {
550 if constexpr(requires { C::ubo; })
551 {
552 auto buf = self.createdUbos.at(command.binding);
553 return reinterpret_cast<typename C::return_type>(buf);
554 }
555 else
556 {
557 static_assert(C::unhandled);
558 return {};
559 }
560 }
561 else
562 {
563 handle_release{rhi}(command);
564 return {};
565 }
566 return {};
567 }
568};
569
570struct generate_shaders
571{
572 template <typename T, int N>
573 using vec = T[N];
574
575 static constexpr std::string_view field_type(float) { return "float"; }
576 static constexpr std::string_view field_type(const float (&)[2]) { return "vec2"; }
577 static constexpr std::string_view field_type(const float (&)[3]) { return "vec3"; }
578 static constexpr std::string_view field_type(const float (&)[4]) { return "vec4"; }
579
580 static constexpr std::string_view field_type(float*) { return "float"; }
581 static constexpr std::string_view field_type(vec<float, 2>*) { return "vec2"; }
582 static constexpr std::string_view field_type(vec<float, 3>*) { return "vec3"; }
583 static constexpr std::string_view field_type(vec<float, 4>*) { return "vec4"; }
584
585 static constexpr std::string_view field_type(int) { return "int"; }
586 static constexpr std::string_view field_type(const int (&)[2]) { return "ivec2"; }
587 static constexpr std::string_view field_type(const int (&)[3]) { return "ivec3"; }
588 static constexpr std::string_view field_type(const int (&)[4]) { return "ivec4"; }
589
590 static constexpr std::string_view field_type(int*) { return "int"; }
591 static constexpr std::string_view field_type(vec<int, 2>*) { return "ivec2"; }
592 static constexpr std::string_view field_type(vec<int, 3>*) { return "ivec3"; }
593 static constexpr std::string_view field_type(vec<int, 4>*) { return "ivec4"; }
594
595 static constexpr std::string_view field_type(uint32_t) { return "uint"; }
596 static constexpr std::string_view field_type(const uint32_t (&)[2]) { return "uvec2"; }
597 static constexpr std::string_view field_type(const uint32_t (&)[3]) { return "uvec3"; }
598 static constexpr std::string_view field_type(const uint32_t (&)[4]) { return "uvec4"; }
599
600 static constexpr std::string_view field_type(uint32_t*) { return "uint"; }
601 static constexpr std::string_view field_type(vec<uint32_t, 2>*) { return "uvec2"; }
602 static constexpr std::string_view field_type(vec<uint32_t, 3>*) { return "uvec3"; }
603 static constexpr std::string_view field_type(vec<uint32_t, 4>*) { return "uvec4"; }
604
605 static constexpr std::string_view field_type(avnd::xy_value auto) { return "vec2"; }
606
607 static constexpr bool field_array(float) { return false; }
608 static constexpr bool field_array(const float (&)[2]) { return false; }
609 static constexpr bool field_array(const float (&)[3]) { return false; }
610 static constexpr bool field_array(const float (&)[4]) { return false; }
611
612 static constexpr bool field_array(float*) { return true; }
613 static constexpr bool field_array(vec<float, 2>*) { return true; }
614 static constexpr bool field_array(vec<float, 3>*) { return true; }
615 static constexpr bool field_array(vec<float, 4>*) { return true; }
616
617 static constexpr bool field_array(int) { return false; }
618 static constexpr bool field_array(const int (&)[2]) { return false; }
619 static constexpr bool field_array(const int (&)[3]) { return false; }
620 static constexpr bool field_array(const int (&)[4]) { return false; }
621
622 static constexpr bool field_array(int*) { return true; }
623 static constexpr bool field_array(vec<int, 2>*) { return true; }
624 static constexpr bool field_array(vec<int, 3>*) { return true; }
625 static constexpr bool field_array(vec<int, 4>*) { return true; }
626
627 static constexpr bool field_array(uint32_t) { return false; }
628 static constexpr bool field_array(const uint32_t (&)[2]) { return false; }
629 static constexpr bool field_array(const uint32_t (&)[3]) { return false; }
630 static constexpr bool field_array(const uint32_t (&)[4]) { return false; }
631
632 static constexpr bool field_array(uint32_t*) { return true; }
633 static constexpr bool field_array(vec<uint32_t, 2>*) { return true; }
634 static constexpr bool field_array(vec<uint32_t, 3>*) { return true; }
635 static constexpr bool field_array(vec<uint32_t, 4>*) { return true; }
636
637 static constexpr bool field_array(avnd::xy_value auto) { return false; }
638
639 template <typename T>
640 static constexpr std::string_view image_qualifier()
641 {
642 if constexpr(requires { T::readonly; })
643 return "readonly";
644 else if constexpr(requires { T::writeonly; })
645 return "writeonly";
646 else
647 static_assert(T::readonly || T::writeonly);
648 }
649
650 struct write_input
651 {
652 std::string& shader;
653
654 template <typename T>
655 void operator()(const T& field)
656 {
657 shader += fmt::format(
658 "layout(location = {}) in {} {};\n", T::location(), field_type(field.data),
659 T::name());
660 }
661 };
662
663 struct write_output
664 {
665 std::string& shader;
666
667 template <typename T>
668 void operator()(const T& field)
669 {
670 if constexpr(requires { field.location(); })
671 {
672 shader += fmt::format(
673 "layout(location = {}) out {} {};\n", T::location(), field_type(field.data),
674 T::name());
675 }
676 }
677 };
678
679 struct write_binding
680 {
681 std::string& shader;
682
683 template <typename T>
684 void operator()(const T& field)
685 {
686 shader += fmt::format(
687 " {} {}{};\n", field_type(field.value), T::name(),
688 field_array(field.value) ? "[]" : "");
689 }
690 };
691
692 struct write_bindings
693 {
694 std::string& shader;
695
696 template <typename C>
697 void operator()(const C& field)
698 {
699 if constexpr(requires { C::sampler2D; })
700 {
701 shader += fmt::format(
702 "layout(binding = {}) uniform sampler2D {};\n\n", C::binding(), C::name());
703 }
704 else if constexpr(requires { C::image2D; })
705 {
706 shader += fmt::format(
707 "layout(binding = {}, {}) {} uniform image2D {};\n\n", C::binding(),
708 C::format(), image_qualifier<C>(), C::name());
709 }
710 else if constexpr(requires { C::ubo; })
711 {
712 shader += fmt::format(
713 "layout({}, binding = {}) uniform {}\n{{\n",
714 "std140" // TODO
715 ,
716 C::binding(), C::name());
717
718 boost::pfr::for_each_field(field, write_binding{shader});
719
720 shader += fmt::format("}};\n\n");
721 }
722 else if constexpr(requires { C::buffer; })
723 {
724 shader += fmt::format(
725 "layout({}, binding = {}) buffer {}\n{{\n",
726 "std140" // TODO
727 ,
728 C::binding(), C::name());
729
730 boost::pfr::for_each_field(field, write_binding{shader});
731
732 shader += fmt::format("}};\n\n");
733 }
734 }
735 };
736
737 template <typename T>
738 std::string vertex_shader(const T& lay)
739 {
740 using namespace gpp::qrhi;
741 std::string shader = "#version 450\n\n";
742
743 if constexpr(requires { lay.vertex_input; })
744 boost::pfr::for_each_field(lay.vertex_input, write_input{shader});
745 else if constexpr(requires { typename T::vertex_input; })
746 boost::pfr::for_each_field(typename T::vertex_input{}, write_input{shader});
747 else
748 boost::pfr::for_each_field(
749 DefaultPipeline::layout::vertex_input{}, write_input{shader});
750
751 if constexpr(requires { lay.vertex_output; })
752 boost::pfr::for_each_field(lay.vertex_output, write_output{shader});
753 else if constexpr(requires { typename T::vertex_output; })
754 boost::pfr::for_each_field(typename T::vertex_output{}, write_output{shader});
755
756 shader += "\n";
757
758 if constexpr(requires { lay.bindings; })
759 boost::pfr::for_each_field(lay.bindings, write_bindings{shader});
760 else if constexpr(requires { typename T::bindings; })
761 boost::pfr::for_each_field(typename T::bindings{}, write_bindings{shader});
762
763 return shader;
764 }
765
766 template <typename T>
767 std::string fragment_shader(const T& lay)
768 {
769 std::string shader = "#version 450\n\n";
770
771 if constexpr(requires { lay.fragment_input; })
772 boost::pfr::for_each_field(lay.fragment_input, write_input{shader});
773 else if constexpr(requires { typename T::fragment_input; })
774 boost::pfr::for_each_field(typename T::fragment_input{}, write_input{shader});
775
776 if constexpr(requires { lay.fragment_output; })
777 boost::pfr::for_each_field(lay.fragment_output, write_output{shader});
778 else if constexpr(requires { typename T::fragment_output; })
779 boost::pfr::for_each_field(typename T::fragment_output{}, write_output{shader});
780
781 shader += "\n";
782
783 if constexpr(requires { lay.bindings; })
784 boost::pfr::for_each_field(lay.bindings, write_bindings{shader});
785 else if constexpr(requires { typename T::bindings; })
786 boost::pfr::for_each_field(typename T::bindings{}, write_bindings{shader});
787
788 return shader;
789 }
790
791 template <typename T>
792 std::string compute_shader(const T& lay)
793 {
794 std::string fstr = "#version 450\n\n";
795
796 fstr += "layout(";
797 if constexpr(requires { T::local_size_x(); })
798 {
799 fstr += fmt::format("local_size_x = {}, ", T::local_size_x());
800 }
801 if constexpr(requires { T::local_size_y(); })
802 {
803 fstr += fmt::format("local_size_y = {}, ", T::local_size_y());
804 }
805 if constexpr(requires { T::local_size_z(); })
806 {
807 fstr += fmt::format("local_size_z = {}, ", T::local_size_z());
808 }
809
810 // Remove the last ", "
811 fstr.resize(fstr.size() - 2);
812 fstr += ") in;\n\n";
813
814 boost::pfr::for_each_field(lay.bindings, write_bindings{fstr});
815
816 return fstr;
817 }
818};
819}
820
821namespace oscr
822{
823struct GpuWorker
824{
825 template <typename Node>
826 static void initWorker(Node& state) noexcept
827 {
828 if constexpr(avnd::has_worker<Node>)
829 {
830 using worker_type = decltype(state.worker);
831
832 state.worker.request = [&state]<typename... Args>(Args&&... f) {
833 using type_of_result = decltype(worker_type::work(std::forward<Args>(f)...));
834 if constexpr(std::is_void_v<type_of_result>)
835 {
836 worker_type::work(std::forward<Args>(f)...);
837 }
838 else
839 {
840 // If the worker returns a std::function, it
841 // is to be invoked back in the processor DSP thread
842 auto res = worker_type::work(std::forward<Args>(f)...);
843 if(!res)
844 return;
845 res(state);
846 }
847 };
848 }
849 }
850};
851
852template <typename GpuNodeRenderer, typename Node>
853struct GpuProcessIns
854{
855 GpuNodeRenderer& gpu;
856 Node& state;
857 const score::gfx::Message& prev_mess;
858 const score::gfx::Message& mess;
859 const score::DocumentContext& ctx;
860
861 bool can_process_message(std::size_t N)
862 {
863 if(mess.input.size() <= N)
864 return false;
865
866 if(prev_mess.input.size() == mess.input.size())
867 {
868 auto& prev = prev_mess.input[N];
869 auto& next = mess.input[N];
870 if(prev.index() == 1 && next.index() == 1)
871 {
872 if(ossia::get<ossia::value>(prev) == ossia::get<ossia::value>(next))
873 {
874 return false;
875 }
876 }
877 }
878 return true;
879 }
880
881 void operator()(avnd::parameter auto& t, auto field_index)
882 {
883 if(!can_process_message(field_index))
884 return;
885
886 if(auto val = ossia::get_if<ossia::value>(&mess.input[field_index]))
887 {
888 oscr::from_ossia_value(t, *val, t.value);
889 if_possible(t.update(state));
890 }
891 }
892
893#if OSCR_HAS_MMAP_FILE_STORAGE
894 template <avnd::raw_file_port Field, std::size_t NField>
895 void operator()(Field& t, avnd::field_index<NField> field_index)
896 {
897 // FIXME we should be loading a file there
898 using node_type = std::remove_cvref_t<decltype(gpu.node())>;
899 using file_ports = avnd::raw_file_input_introspection<Node>;
900
901 if(!can_process_message(field_index))
902 return;
903
904 auto val = ossia::get_if<ossia::value>(&mess.input[field_index]);
905 if(!val)
906 return;
907
908 static constexpr bool has_text = requires { decltype(Field::file)::text; };
909 static constexpr bool has_mmap = requires { decltype(Field::file)::mmap; };
910
911 // First we can load it directly since execution hasn't started yet
912 if(auto hdl = loadRawfile(*val, ctx, has_text, has_mmap))
913 {
914 static constexpr auto N = file_ports::field_index_to_index(NField);
915 if constexpr(avnd::port_can_process<Field>)
916 {
917 // FIXME also do it when we get a run-time message from the exec engine,
918 // OSC, etc
919 auto func = executePortPreprocess<Field>(*hdl);
920 const_cast<node_type&>(gpu.node())
921 .file_loaded(
922 state, hdl, avnd::predicate_index<N>{}, avnd::field_index<NField>{});
923 if(func)
924 func(state);
925 }
926 else
927 {
928 const_cast<node_type&>(gpu.node())
929 .file_loaded(
930 state, hdl, avnd::predicate_index<N>{}, avnd::field_index<NField>{});
931 }
932 }
933 }
934#endif
935
936 template <avnd::texture_port Field, std::size_t NField>
937 void operator()(Field& t, avnd::field_index<NField> field_index)
938 {
939 using node_type = std::remove_cvref_t<decltype(gpu.node())>;
940 auto& node = const_cast<node_type&>(gpu.node());
941 auto val = ossia::get_if<ossia::render_target_spec>(&mess.input[field_index]);
942 if(!val)
943 return;
944 node.process(NField, *val);
945 }
946
947 void operator()(auto& t, auto field_index) = delete;
948};
949
950struct GpuControlIns
951{
952 template <typename Self, typename Node_T>
953 static void processControlIn(
954 Self& self, Node_T& state, score::gfx::Message& renderer_mess,
955 const score::gfx::Message& mess, const score::DocumentContext& ctx) noexcept
956 {
957 // Apply the controls
958 avnd::input_introspection<Node_T>::for_all_n(
959 avnd::get_inputs<Node_T>(state),
960 GpuProcessIns<Self, Node_T>{self, state, renderer_mess, mess, ctx});
961 renderer_mess = mess;
962 }
963};
964
965struct GpuControlOuts
966{
967 std::weak_ptr<Execution::ExecutionCommandQueue> queue;
968 Gfx::exec_controls control_outs;
969
970 int instance{};
971
972 template <typename Node_T>
973 void processControlOut(Node_T& state) const noexcept
974 {
975 if(!this->control_outs.empty())
976 {
977 auto q = this->queue.lock();
978 if(!q)
979 return;
980 auto& qq = *q;
981 int parm_k = 0;
982 avnd::parameter_output_introspection<Node_T>::for_all(
983 avnd::get_outputs(state), [&]<avnd::parameter T>(const T& t) {
984 qq.enqueue([v = oscr::to_ossia_value(t, t.value),
985 port = control_outs[parm_k]]() mutable {
986 std::swap(port->value, v);
987 port->changed = true;
988 });
989
990 parm_k++;
991 });
992 }
993 }
994};
995
996template <typename T>
997struct SCORE_PLUGIN_AVND_EXPORT GpuNodeElements
998{
999 [[no_unique_address]] oscr::soundfile_storage<T> soundfiles;
1000
1001 [[no_unique_address]] oscr::midifile_storage<T> midifiles;
1002
1003#if defined(OSCR_HAS_MMAP_FILE_STORAGE)
1004 [[no_unique_address]] oscr::raw_file_storage<T> rawfiles;
1005#endif
1006
1007 template <std::size_t N, std::size_t NField>
1008 void file_loaded(
1009 auto& state, const std::shared_ptr<oscr::raw_file_data>& hdl,
1010 avnd::predicate_index<N>, avnd::field_index<NField>)
1011 {
1012 this->rawfiles.load(
1013 state, hdl, avnd::predicate_index<N>{}, avnd::field_index<NField>{});
1014 }
1015};
1016
1017struct SCORE_PLUGIN_AVND_EXPORT CustomGfxNodeBase : score::gfx::NodeModel
1018{
1019 explicit CustomGfxNodeBase(const score::DocumentContext& ctx)
1020 : score::gfx::NodeModel{}
1021 , m_ctx{ctx}
1022 {
1023 }
1024 virtual ~CustomGfxNodeBase();
1025 const score::DocumentContext& m_ctx;
1026 score::gfx::Message last_message;
1027 void process(score::gfx::Message&& msg) override;
1029};
1030struct SCORE_PLUGIN_AVND_EXPORT CustomGfxOutputNodeBase : score::gfx::OutputNode
1031{
1032 virtual ~CustomGfxOutputNodeBase();
1033
1034 score::gfx::Message last_message;
1035 void process(score::gfx::Message&& msg) override;
1036};
1037struct CustomGpuNodeBase
1039 , GpuWorker
1040 , GpuControlIns
1041 , GpuControlOuts
1042{
1043 CustomGpuNodeBase(
1044 std::weak_ptr<Execution::ExecutionCommandQueue>&& q, Gfx::exec_controls&& ctls,
1045 const score::DocumentContext& ctx)
1046 : GpuControlOuts{std::move(q), std::move(ctls)}
1047 , m_ctx{ctx}
1048 {
1049 }
1050
1051 virtual ~CustomGpuNodeBase() = default;
1052
1053 const score::DocumentContext& m_ctx;
1054 QString vertex, fragment, compute;
1055 score::gfx::Message last_message;
1056 void process(score::gfx::Message&& msg) override;
1057};
1058
1059struct SCORE_PLUGIN_AVND_EXPORT CustomGpuOutputNodeBase
1061 , GpuWorker
1062 , GpuControlIns
1063 , GpuControlOuts
1064{
1065 CustomGpuOutputNodeBase(
1066 std::weak_ptr<Execution::ExecutionCommandQueue> q, Gfx::exec_controls&& ctls,
1067 const score::DocumentContext& ctx);
1068 virtual ~CustomGpuOutputNodeBase();
1069
1070 const score::DocumentContext& m_ctx;
1071 std::weak_ptr<score::gfx::RenderList> m_renderer{};
1072 std::shared_ptr<score::gfx::RenderState> m_renderState{};
1073 std::function<void()> m_update;
1074
1075 QString vertex, fragment, compute;
1076 score::gfx::Message last_message;
1077 void process(score::gfx::Message&& msg) override;
1079
1080 void setRenderer(std::shared_ptr<score::gfx::RenderList>) override;
1081 score::gfx::RenderList* renderer() const override;
1082
1083 void startRendering() override;
1084 void render() override;
1085 void stopRendering() override;
1086 bool canRender() const override;
1087 void onRendererChange() override;
1088
1089 void createOutput(
1090 score::gfx::GraphicsApi graphicsApi, std::function<void()> onReady,
1091 std::function<void()> onUpdate, std::function<void()> onResize) override;
1092
1093 void destroyOutput() override;
1094 std::shared_ptr<score::gfx::RenderState> renderState() const override;
1095
1096 Configuration configuration() const noexcept override;
1097};
1098
1099template <typename Node_T, typename Node>
1100void prepareNewState(Node_T& eff, const Node& parent)
1101{
1102 if constexpr(avnd::has_worker<Node_T>)
1103 {
1104 parent.initWorker(eff);
1105 }
1106 if constexpr(avnd::has_processor_to_gui_bus<Node_T>)
1107 {
1108 auto& process = parent.processModel;
1109 eff.send_message = [&process](auto&& b) mutable {
1110 // FIXME right now all the rendering is done in the UI thread, which is very MEH
1111 // this->in_edit([&process, bb = std::move(b)]() mutable {
1112
1113 if(process.to_ui)
1114 MessageBusSender{process.to_ui}(std::move(b));
1115 // });
1116 };
1117
1118 // FIXME GUI -> engine. See executor.hpp
1119 }
1120
1121 avnd::init_controls(eff);
1122
1123 if constexpr(avnd::can_prepare<Node_T>)
1124 {
1125 using prepare_type = avnd::first_argument<&Node_T::prepare>;
1126 prepare_type t;
1127 if_possible(t.instance = parent.instance);
1128 eff.prepare(t);
1129 }
1130}
1131
1132struct port_to_type_enum
1133{
1134 template <std::size_t I, avnd::cpu_texture_port F>
1135 constexpr auto operator()(avnd::field_reflection<I, F> p)
1136 {
1137 using texture_type = std::remove_cvref_t<decltype(F::texture)>;
1138 return avnd::cpu_fixed_format_texture<texture_type> ? score::gfx::Types::Image
1139 : score::gfx::Types::Buffer;
1140 }
1141 template <std::size_t I, avnd::sampler_port F>
1142 constexpr auto operator()(avnd::field_reflection<I, F> p)
1143 {
1144 return score::gfx::Types::Image;
1145 }
1146 template <std::size_t I, avnd::image_port F>
1147 constexpr auto operator()(avnd::field_reflection<I, F> p)
1148 {
1149 return score::gfx::Types::Image;
1150 }
1151 template <std::size_t I, avnd::attachment_port F>
1152 constexpr auto operator()(avnd::field_reflection<I, F> p)
1153 {
1154 return score::gfx::Types::Image;
1155 }
1156
1157 template <std::size_t I, avnd::geometry_port F>
1158 constexpr auto operator()(avnd::field_reflection<I, F> p)
1159 {
1160 return score::gfx::Types::Geometry;
1161 }
1162 template <std::size_t I, avnd::mono_audio_port F>
1163 constexpr auto operator()(avnd::field_reflection<I, F> p)
1164 {
1165 return score::gfx::Types::Audio;
1166 }
1167 template <std::size_t I, avnd::poly_audio_port F>
1168 constexpr auto operator()(avnd::field_reflection<I, F> p)
1169 {
1170 return score::gfx::Types::Audio;
1171 }
1172 template <std::size_t I, avnd::int_parameter F>
1173 constexpr auto operator()(avnd::field_reflection<I, F> p)
1174 {
1175 return score::gfx::Types::Int;
1176 }
1177 template <std::size_t I, avnd::enum_parameter F>
1178 constexpr auto operator()(avnd::field_reflection<I, F> p)
1179 {
1180 return score::gfx::Types::Int;
1181 }
1182 template <std::size_t I, avnd::float_parameter F>
1183 constexpr auto operator()(avnd::field_reflection<I, F> p)
1184 {
1185 return score::gfx::Types::Float;
1186 }
1187 template <std::size_t I, avnd::parameter F>
1188 constexpr auto operator()(avnd::field_reflection<I, F> p)
1189 {
1190 using value_type = std::remove_cvref_t<decltype(F::value)>;
1191
1192 if constexpr(std::is_aggregate_v<value_type>)
1193 {
1194 constexpr int sz = boost::pfr::tuple_size_v<value_type>;
1195 if constexpr(sz == 2)
1196 {
1197 return score::gfx::Types::Vec2;
1198 }
1199 else if constexpr(sz == 3)
1200 {
1201 return score::gfx::Types::Vec3;
1202 }
1203 else if constexpr(sz == 4)
1204 {
1205 return score::gfx::Types::Vec4;
1206 }
1207 }
1208 return score::gfx::Types::Empty;
1209 }
1210 template <std::size_t I, typename F>
1211 constexpr auto operator()(avnd::field_reflection<I, F> p)
1212 {
1213 return score::gfx::Types::Empty;
1214 }
1215};
1216
1217template <typename Node_T>
1218inline void initGfxPorts(auto* self, auto& input, auto& output)
1219{
1220 avnd::input_introspection<Node_T>::for_all(
1221 [self, &input]<typename Field, std::size_t I>(avnd::field_reflection<I, Field> f) {
1222 static constexpr auto type = port_to_type_enum{}(f);
1223 input.push_back(new score::gfx::Port{self, {}, type, {}});
1224 });
1225 avnd::output_introspection<Node_T>::for_all(
1226 [self,
1227 &output]<typename Field, std::size_t I>(avnd::field_reflection<I, Field> f) {
1228 static constexpr auto type = port_to_type_enum{}(f);
1229 output.push_back(new score::gfx::Port{self, {}, type, {}});
1230 });
1231}
1232
1233inline void inplaceMirror(unsigned char* bytes, int width, int height)
1234{
1235 if(width < 1 || height <= 1)
1236 return;
1237 const size_t row_size = width * 4;
1238
1239 auto temp_row = (unsigned char*)alloca(row_size);
1240 auto top = bytes;
1241 auto bottom = bytes + (height - 1) * row_size;
1242
1243 while(top < bottom)
1244 {
1245 memcpy(temp_row, top, row_size);
1246 memcpy(top, bottom, row_size);
1247 memcpy(bottom, temp_row, row_size);
1248
1249 top += row_size;
1250 bottom -= row_size;
1251 }
1252}
1253}
1254
1255#endif
Root data model for visual nodes.
Definition score-plugin-gfx/Gfx/Graph/Node.hpp:75
virtual void process(Message &&msg)
Process a message from the execution engine.
Definition Node.cpp:25
Common base class for most single-pass, simple nodes.
Definition score-plugin-gfx/Gfx/Graph/Node.hpp:230
Base class for sink nodes (QWindow, spout, syphon, NDI output, ...)
Definition OutputNode.hpp:24
List of nodes to be rendered to an output.
Definition RenderList.hpp:19
TreeNode< DeviceExplorerNode > Node
Definition DeviceNode.hpp:74
Definition Factories.hpp:19
GraphicsApi
Available graphics APIs to use.
Definition RenderState.hpp:17
Base toolkit upon which the software is built.
Definition Application.cpp:97
STL namespace.
Definition DocumentContext.hpp:18
Definition score-plugin-gfx/Gfx/Graph/Node.hpp:50
Port of a score::gfx::Node.
Definition score-plugin-gfx/Gfx/Graph/Utils.hpp:48