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