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, 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 state.worker.request = [ptr, &tq, &state]<typename... Args>(Args&&... f) {
840 using type_of_result = decltype(worker_type::work(std::forward<Args>(f)...));
841 tq.post([... ff = std::forward<Args>(f), &state, ptr]() mutable {
842 if constexpr(std::is_void_v<type_of_result>)
843 {
844 worker_type::work(std::forward<decltype(ff)>(ff)...);
845 }
846 else
847 {
848 // If the worker returns a std::function, it
849 // is to be invoked back in the processor DSP thread
850 auto res = worker_type::work(std::forward<decltype(ff)>(ff)...);
851 if(!res || !ptr)
852 return;
853
854 ossia::qt::run_async(
855 QCoreApplication::instance(),
856 [res = std::move(res), &state, ptr]() mutable {
857 if(ptr)
858 res(state);
859 });
860 }
861 });
862 };
863 }
864 }
865};
866
867template <typename GpuNodeRenderer, typename Node>
868struct GpuProcessIns
869{
870 GpuNodeRenderer& gpu;
871 Node& state;
872 const score::gfx::Message& prev_mess;
873 const score::gfx::Message& mess;
874 const score::DocumentContext& ctx;
875
876 bool can_process_message(std::size_t N)
877 {
878 if(mess.input.size() <= N)
879 return false;
880
881 if(prev_mess.input.size() == mess.input.size())
882 {
883 auto& prev = prev_mess.input[N];
884 auto& next = mess.input[N];
885 if(prev.index() == 1 && next.index() == 1)
886 {
887 if(ossia::get<ossia::value>(prev) == ossia::get<ossia::value>(next))
888 {
889 return false;
890 }
891 }
892 }
893 return true;
894 }
895
896 void operator()(avnd::parameter auto& t, auto field_index)
897 {
898 if(!can_process_message(field_index))
899 return;
900
901 if(auto val = ossia::get_if<ossia::value>(&mess.input[field_index]))
902 {
903 oscr::from_ossia_value(t, *val, t.value);
904 if_possible(t.update(state));
905 }
906 }
907
908#if OSCR_HAS_MMAP_FILE_STORAGE
909 template <avnd::raw_file_port Field, std::size_t NField>
910 void operator()(Field& t, avnd::field_index<NField> field_index)
911 {
912 // FIXME we should be loading a file there
913 using node_type = std::remove_cvref_t<decltype(gpu.node())>;
914 using file_ports = avnd::raw_file_input_introspection<Node>;
915
916 if(!can_process_message(field_index))
917 return;
918
919 auto val = ossia::get_if<ossia::value>(&mess.input[field_index]);
920 if(!val)
921 return;
922
923 static constexpr bool has_text = requires { decltype(Field::file)::text; };
924 static constexpr bool has_mmap = requires { decltype(Field::file)::mmap; };
925
926 // First we can load it directly since execution hasn't started yet
927 if(auto hdl = loadRawfile(*val, ctx, has_text, has_mmap))
928 {
929 static constexpr auto N = file_ports::field_index_to_index(NField);
930 if constexpr(avnd::port_can_process<Field>)
931 {
932 // FIXME also do it when we get a run-time message from the exec engine,
933 // OSC, etc
934 auto func = executePortPreprocess<Field>(*hdl);
935 const_cast<node_type&>(gpu.node())
936 .file_loaded(
937 state, hdl, avnd::predicate_index<N>{}, avnd::field_index<NField>{});
938 if(func)
939 func(state);
940 }
941 else
942 {
943 const_cast<node_type&>(gpu.node())
944 .file_loaded(
945 state, hdl, avnd::predicate_index<N>{}, avnd::field_index<NField>{});
946 }
947 }
948 }
949#endif
950
951 template <avnd::texture_port Field, std::size_t NField>
952 void operator()(Field& t, avnd::field_index<NField> field_index)
953 {
954 using node_type = std::remove_cvref_t<decltype(gpu.node())>;
955 auto& node = const_cast<node_type&>(gpu.node());
956 auto val = ossia::get_if<ossia::render_target_spec>(&mess.input[field_index]);
957 if(!val)
958 return;
959 node.process(NField, *val);
960 }
961
962 void operator()(auto& t, auto field_index) = delete;
963};
964
965struct GpuControlIns
966{
967 template <typename Self, typename Node_T>
968 static void processControlIn(
969 Self& self, Node_T& state, score::gfx::Message& renderer_mess,
970 const score::gfx::Message& mess, const score::DocumentContext& ctx) noexcept
971 {
972 // Apply the controls
973 avnd::input_introspection<Node_T>::for_all_n(
974 avnd::get_inputs<Node_T>(state),
975 GpuProcessIns<Self, Node_T>{self, state, renderer_mess, mess, ctx});
976 renderer_mess = mess;
977 }
978};
979
980struct GpuControlOuts
981{
982 std::weak_ptr<Execution::ExecutionCommandQueue> queue;
983 Gfx::exec_controls control_outs;
984
985 int instance{};
986
987 template <typename Node_T>
988 void processControlOut(Node_T& state) const noexcept
989 {
990 if(!this->control_outs.empty())
991 {
992 auto q = this->queue.lock();
993 if(!q)
994 return;
995 auto& qq = *q;
996 int parm_k = 0;
997 avnd::parameter_output_introspection<Node_T>::for_all(
998 avnd::get_outputs(state), [&]<avnd::parameter T>(const T& t) {
999 qq.enqueue([v = oscr::to_ossia_value(t, t.value),
1000 port = control_outs[parm_k]]() mutable {
1001 std::swap(port->value, v);
1002 port->changed = true;
1003 });
1004
1005 parm_k++;
1006 });
1007 }
1008 }
1009};
1010
1011template <typename T>
1012struct SCORE_PLUGIN_AVND_EXPORT GpuNodeElements
1013{
1014 [[no_unique_address]] oscr::soundfile_storage<T> soundfiles;
1015
1016 [[no_unique_address]] oscr::midifile_storage<T> midifiles;
1017
1018#if defined(OSCR_HAS_MMAP_FILE_STORAGE)
1019 [[no_unique_address]] oscr::raw_file_storage<T> rawfiles;
1020#endif
1021
1022 template <std::size_t N, std::size_t NField>
1023 void file_loaded(
1024 auto& state, const std::shared_ptr<oscr::raw_file_data>& hdl,
1025 avnd::predicate_index<N>, avnd::field_index<NField>)
1026 {
1027 this->rawfiles.load(
1028 state, hdl, avnd::predicate_index<N>{}, avnd::field_index<NField>{});
1029 }
1030};
1031
1032struct SCORE_PLUGIN_AVND_EXPORT CustomGfxNodeBase : score::gfx::NodeModel
1033{
1034 explicit CustomGfxNodeBase(const score::DocumentContext& ctx)
1035 : score::gfx::NodeModel{}
1036 , m_ctx{ctx}
1037 {
1038 }
1039 virtual ~CustomGfxNodeBase();
1040 const score::DocumentContext& m_ctx;
1041 score::gfx::Message last_message;
1042 void process(score::gfx::Message&& msg) override;
1044};
1045struct SCORE_PLUGIN_AVND_EXPORT CustomGfxOutputNodeBase : score::gfx::OutputNode
1046{
1047 virtual ~CustomGfxOutputNodeBase();
1048
1049 score::gfx::Message last_message;
1050 void process(score::gfx::Message&& msg) override;
1051};
1052struct CustomGpuNodeBase
1054 , GpuWorker
1055 , GpuControlIns
1056 , GpuControlOuts
1057{
1058 CustomGpuNodeBase(
1059 std::weak_ptr<Execution::ExecutionCommandQueue>&& q, Gfx::exec_controls&& ctls,
1060 const score::DocumentContext& ctx)
1061 : GpuControlOuts{std::move(q), std::move(ctls)}
1062 , m_ctx{ctx}
1063 {
1064 }
1065
1066 virtual ~CustomGpuNodeBase() = default;
1067
1068 const score::DocumentContext& m_ctx;
1069 QString vertex, fragment, compute;
1070 score::gfx::Message last_message;
1071 void process(score::gfx::Message&& msg) override;
1072};
1073
1074struct SCORE_PLUGIN_AVND_EXPORT CustomGpuOutputNodeBase
1076 , GpuWorker
1077 , GpuControlIns
1078 , GpuControlOuts
1079{
1080 CustomGpuOutputNodeBase(
1081 std::weak_ptr<Execution::ExecutionCommandQueue> q, Gfx::exec_controls&& ctls,
1082 const score::DocumentContext& ctx);
1083 virtual ~CustomGpuOutputNodeBase();
1084
1085 const score::DocumentContext& m_ctx;
1086 std::weak_ptr<score::gfx::RenderList> m_renderer{};
1087 std::shared_ptr<score::gfx::RenderState> m_renderState{};
1088 std::function<void()> m_update;
1089
1090 QString vertex, fragment, compute;
1091 score::gfx::Message last_message;
1092 void process(score::gfx::Message&& msg) override;
1094
1095 void setRenderer(std::shared_ptr<score::gfx::RenderList>) override;
1096 score::gfx::RenderList* renderer() const override;
1097
1098 void startRendering() override;
1099 void render() override;
1100 void stopRendering() override;
1101 bool canRender() const override;
1102 void onRendererChange() override;
1103
1104 void createOutput(
1105 score::gfx::GraphicsApi graphicsApi, std::function<void()> onReady,
1106 std::function<void()> onUpdate, std::function<void()> onResize) override;
1107
1108 void destroyOutput() override;
1109 std::shared_ptr<score::gfx::RenderState> renderState() const override;
1110
1111 Configuration configuration() const noexcept override;
1112};
1113
1114template <typename Node_T, typename Node>
1115void prepareNewState(Node_T& eff, const Node& parent)
1116{
1117 if constexpr(avnd::has_worker<Node_T>)
1118 {
1119 parent.initWorker(eff);
1120 }
1121 if constexpr(avnd::has_processor_to_gui_bus<Node_T>)
1122 {
1123 auto& process = parent.processModel;
1124 eff.send_message = [&process](auto&& b) mutable {
1125 // FIXME right now all the rendering is done in the UI thread, which is very MEH
1126 // this->in_edit([&process, bb = std::move(b)]() mutable {
1127
1128 if(process.to_ui)
1129 MessageBusSender{process.to_ui}(std::move(b));
1130 // });
1131 };
1132
1133 // FIXME GUI -> engine. See executor.hpp
1134 }
1135
1136 avnd::init_controls(eff);
1137
1138 if constexpr(avnd::can_prepare<Node_T>)
1139 {
1140 if constexpr(avnd::function_reflection<&Node_T::prepare>::count == 1)
1141 {
1142 using prepare_type = avnd::first_argument<&Node_T::prepare>;
1143 prepare_type t;
1144 if_possible(t.instance = parent.instance);
1145 eff.prepare(t);
1146 }
1147 else
1148 {
1149 eff.prepare();
1150 }
1151 }
1152}
1153
1154struct port_to_type_enum
1155{
1156 template <std::size_t I, avnd::cpu_texture_port F>
1157 constexpr auto operator()(avnd::field_reflection<I, F> p)
1158 {
1159 using texture_type = std::remove_cvref_t<decltype(F::texture)>;
1160 return avnd::cpu_fixed_format_texture<texture_type> ? score::gfx::Types::Image
1161 : score::gfx::Types::Buffer;
1162 }
1163 template <std::size_t I, avnd::sampler_port F>
1164 constexpr auto operator()(avnd::field_reflection<I, F> p)
1165 {
1166 return score::gfx::Types::Image;
1167 }
1168 template <std::size_t I, avnd::image_port F>
1169 constexpr auto operator()(avnd::field_reflection<I, F> p)
1170 {
1171 return score::gfx::Types::Image;
1172 }
1173 template <std::size_t I, avnd::attachment_port F>
1174 constexpr auto operator()(avnd::field_reflection<I, F> p)
1175 {
1176 return score::gfx::Types::Image;
1177 }
1178
1179 template <std::size_t I, avnd::geometry_port F>
1180 constexpr auto operator()(avnd::field_reflection<I, F> p)
1181 {
1182 return score::gfx::Types::Geometry;
1183 }
1184 template <std::size_t I, avnd::mono_audio_port F>
1185 constexpr auto operator()(avnd::field_reflection<I, F> p)
1186 {
1187 return score::gfx::Types::Audio;
1188 }
1189 template <std::size_t I, avnd::poly_audio_port F>
1190 constexpr auto operator()(avnd::field_reflection<I, F> p)
1191 {
1192 return score::gfx::Types::Audio;
1193 }
1194 template <std::size_t I, avnd::int_parameter F>
1195 constexpr auto operator()(avnd::field_reflection<I, F> p)
1196 {
1197 return score::gfx::Types::Int;
1198 }
1199 template <std::size_t I, avnd::enum_parameter F>
1200 constexpr auto operator()(avnd::field_reflection<I, F> p)
1201 {
1202 return score::gfx::Types::Int;
1203 }
1204 template <std::size_t I, avnd::float_parameter F>
1205 constexpr auto operator()(avnd::field_reflection<I, F> p)
1206 {
1207 return score::gfx::Types::Float;
1208 }
1209 template <std::size_t I, avnd::parameter F>
1210 constexpr auto operator()(avnd::field_reflection<I, F> p)
1211 {
1212 using value_type = std::remove_cvref_t<decltype(F::value)>;
1213
1214 if constexpr(std::is_aggregate_v<value_type>)
1215 {
1216 constexpr int sz = boost::pfr::tuple_size_v<value_type>;
1217 if constexpr(sz == 2)
1218 {
1219 return score::gfx::Types::Vec2;
1220 }
1221 else if constexpr(sz == 3)
1222 {
1223 return score::gfx::Types::Vec3;
1224 }
1225 else if constexpr(sz == 4)
1226 {
1227 return score::gfx::Types::Vec4;
1228 }
1229 }
1230 return score::gfx::Types::Empty;
1231 }
1232 template <std::size_t I, typename F>
1233 constexpr auto operator()(avnd::field_reflection<I, F> p)
1234 {
1235 return score::gfx::Types::Empty;
1236 }
1237};
1238
1239template <typename Node_T>
1240inline void initGfxPorts(auto* self, auto& input, auto& output)
1241{
1242 avnd::input_introspection<Node_T>::for_all(
1243 [self, &input]<typename Field, std::size_t I>(avnd::field_reflection<I, Field> f) {
1244 static constexpr auto type = port_to_type_enum{}(f);
1245 input.push_back(new score::gfx::Port{self, {}, type, {}});
1246 });
1247 avnd::output_introspection<Node_T>::for_all(
1248 [self,
1249 &output]<typename Field, std::size_t I>(avnd::field_reflection<I, Field> f) {
1250 static constexpr auto type = port_to_type_enum{}(f);
1251 output.push_back(new score::gfx::Port{self, {}, type, {}});
1252 });
1253}
1254
1255inline void
1256inplaceMirror(unsigned char* bytes, int width, int height, int bytes_per_pixel)
1257{
1258 if(width < 1 || height <= 1)
1259 return;
1260 const size_t row_size = width * bytes_per_pixel;
1261
1262 auto temp_row = (unsigned char*)alloca(row_size);
1263 auto top = bytes;
1264 auto bottom = bytes + (height - 1) * row_size;
1265
1266 while(top < bottom)
1267 {
1268 memcpy(temp_row, top, row_size);
1269 memcpy(top, bottom, row_size);
1270 memcpy(bottom, temp_row, row_size);
1271
1272 top += row_size;
1273 bottom -= row_size;
1274 }
1275}
1276}
1277
1278#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