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