CpuGeneratorNode.hpp
1 #pragma once
2 
3 #if SCORE_PLUGIN_GFX
4 #include <Crousti/GfxNode.hpp>
5 
6 namespace oscr
7 {
8 
9 template <typename Node_T>
10  requires(
11  avnd::texture_input_introspection<Node_T>::size == 0
12  && avnd::texture_output_introspection<Node_T>::size > 0)
13 struct GfxRenderer<Node_T> final : score::gfx::GenericNodeRenderer
14 {
15  using texture_outputs = avnd::texture_output_introspection<Node_T>;
16  const GfxNode<Node_T>& parent;
17  Node_T state;
18  score::gfx::Message m_last_message{};
19  ossia::time_value m_last_time{-1};
20 
21  GfxRenderer(const GfxNode<Node_T>& p)
22  : score::gfx::GenericNodeRenderer{p}
23  , parent{p}
24  {
25  prepareNewState(state, parent);
26  }
27 
29  renderTargetForInput(const score::gfx::Port& p) override
30  {
31  return {};
32  }
33 
34  template <typename Tex>
35  void
36  createOutput(score::gfx::RenderList& renderer, const Tex& texture_spec, QSize size)
37  {
38  auto& rhi = *renderer.state.rhi;
39  QRhiTexture* texture = &renderer.emptyTexture();
40  if(size.width() > 0 && size.height() > 0)
41  {
42  texture = rhi.newTexture(
43  gpp::qrhi::textureFormat<Tex>(), size, 1, QRhiTexture::Flag{});
44 
45  texture->create();
46  }
47 
48  auto sampler = rhi.newSampler(
49  QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None,
50  QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge);
51 
52  sampler->create();
53  this->m_samplers.push_back({sampler, texture});
54  }
55 
56  template <avnd::cpu_texture Tex>
57  QRhiTexture* updateTexture(score::gfx::RenderList& renderer, int k, const Tex& cpu_tex)
58  {
59  auto& [sampler, texture] = this->m_samplers[k];
60  if(texture)
61  {
62  auto sz = texture->pixelSize();
63  if(cpu_tex.width == sz.width() && cpu_tex.height == sz.height())
64  return texture;
65  }
66 
67  // Check the texture size
68  if(cpu_tex.width > 0 && cpu_tex.height > 0)
69  {
70  QRhiTexture* oldtex = texture;
71  QRhiTexture* newtex = renderer.state.rhi->newTexture(
72  gpp::qrhi::textureFormat<Tex>(), QSize{cpu_tex.width, cpu_tex.height}, 1,
73  QRhiTexture::Flag{});
74  newtex->create();
75  for(auto& [edge, pass] : this->m_p)
76  if(pass.srb)
77  score::gfx::replaceTexture(*pass.srb, sampler, newtex);
78  texture = newtex;
79 
80  if(oldtex && oldtex != &renderer.emptyTexture())
81  {
82  oldtex->deleteLater();
83  }
84 
85  return newtex;
86  }
87  else
88  {
89  for(auto& [edge, pass] : this->m_p)
90  if(pass.srb)
91  score::gfx::replaceTexture(*pass.srb, sampler, &renderer.emptyTexture());
92 
93  return &renderer.emptyTexture();
94  }
95  }
96 
97  void uploadOutputTexture(
98  score::gfx::RenderList& renderer, int k, avnd::cpu_texture auto& cpu_tex,
99  QRhiResourceUpdateBatch* res)
100  {
101  if(cpu_tex.changed)
102  {
103  if(auto texture = updateTexture(renderer, k, cpu_tex))
104  {
105  // Upload it
106  {
107  QRhiTextureSubresourceUploadDescription sd(
108  cpu_tex.bytes, cpu_tex.width * cpu_tex.height * 4);
109  QRhiTextureUploadDescription desc{QRhiTextureUploadEntry{0, 0, sd}};
110 
111  res->uploadTexture(texture, desc);
112  }
113 
114  cpu_tex.changed = false;
115  k++;
116  }
117  }
118  }
119 
120  void init(score::gfx::RenderList& renderer, QRhiResourceUpdateBatch& res) override
121  {
122  if constexpr(requires { state.prepare(); })
123  {
124  parent.processControlIn(
125  *this, state, m_last_message, this->parent.last_message, this->parent.m_ctx);
126  state.prepare();
127  }
128 
129  const auto& mesh = renderer.defaultTriangle();
130  this->defaultMeshInit(renderer, mesh, res);
131  this->processUBOInit(renderer);
132  this->m_material.init(renderer, this->node.input, this->m_samplers);
133  std::tie(this->m_vertexS, this->m_fragmentS)
134  = score::gfx::makeShaders(renderer.state, generic_texgen_vs, generic_texgen_fs);
135 
136  // Init textures for the outputs
137  avnd::cpu_texture_output_introspection<Node_T>::for_all(
138  avnd::get_outputs<Node_T>(state), [&](auto& t) {
139  createOutput(renderer, t.texture, QSize{t.texture.width, t.texture.height});
140  });
141 
142  this->defaultPassesInit(renderer, mesh);
143  }
144 
145  void update(score::gfx::RenderList& renderer, QRhiResourceUpdateBatch& res) override
146  {
147  this->defaultUBOUpdate(renderer, res);
148  }
149 
150  void release(score::gfx::RenderList& r) override
151  {
152  // Free outputs
153  for(auto& [sampl, texture] : this->m_samplers)
154  {
155  if(texture != &r.emptyTexture())
156  texture->deleteLater();
157  texture = nullptr;
158  }
159 
160  this->defaultRelease(r);
161  }
162 
163  void runInitialPasses(
164  score::gfx::RenderList& renderer, QRhiCommandBuffer& commands,
165  QRhiResourceUpdateBatch*& res, score::gfx::Edge& edge) override
166  {
167  // If we are paused, we don't run the processor implementation.
168  if(parent.last_message.token.date == m_last_time)
169  {
170  return;
171  }
172  m_last_time = parent.last_message.token.date;
173 
174  parent.processControlIn(
175  *this, state, m_last_message, this->parent.last_message, parent.m_ctx);
176 
177  // Run the processor
178  state();
179 
180  // Upload output textures
181  int k = 0;
182  avnd::cpu_texture_output_introspection<Node_T>::for_all(
183  avnd::get_outputs<Node_T>(state), [&](auto& t) {
184  uploadOutputTexture(renderer, k, t.texture, res);
185  k++;
186  });
187 
188  commands.resourceUpdate(res);
189  res = renderer.state.rhi->nextResourceUpdateBatch();
190 
191  // Copy the data to the model node
192  parent.processControlOut(this->state);
193  }
194 };
195 
196 template <typename Node_T>
197  requires(avnd::texture_input_introspection<Node_T>::size == 0
198  && avnd::texture_output_introspection<Node_T>::size > 0)
199 struct GfxNode<Node_T> final
200  : CustomGfxNodeBase
201  , GpuWorker
202  , GpuControlIns
203  , GpuControlOuts
204  , GpuNodeElements<Node_T>
205 {
206  oscr::ProcessModel<Node_T>& processModel;
207  GfxNode(
209  std::weak_ptr<Execution::ExecutionCommandQueue> q, Gfx::exec_controls ctls, int id,
210  const score::DocumentContext& ctx)
211  : CustomGfxNodeBase{ctx}
212  , GpuControlOuts{std::move(q), std::move(ctls)}
213  , processModel{element}
214  {
215  this->instance = id;
216 
217  using texture_outputs = avnd::texture_output_introspection<Node_T>;
218 
219  // FIXME incorrect if we have other ports before, e.g. a float part followed by an image port
220  for(std::size_t i = 0; i < texture_outputs::size; i++)
221  {
222  this->output.push_back(
223  new score::gfx::Port{this, {}, score::gfx::Types::Image, {}});
224  }
225  }
226 
228  createRenderer(score::gfx::RenderList& r) const noexcept override
229  {
230  return new GfxRenderer<Node_T>{*this};
231  }
232 };
233 }
234 #endif
Definition: score-plugin-avnd/Crousti/ProcessModel.hpp:77
Renderer for a given node.
Definition: NodeRenderer.hpp:11
List of nodes to be rendered to an output.
Definition: RenderList.hpp:19
const score::gfx::Mesh & defaultTriangle() const noexcept
A triangle mesh correct for this API.
Definition: RenderList.cpp:250
RenderState & state
RenderState corresponding to this RenderList.
Definition: RenderList.hpp:89
QRhiTexture & emptyTexture() const noexcept
Texture to use when a texture is missing.
Definition: RenderList.hpp:111
Definition: Factories.hpp:19
std::pair< QShader, QShader > makeShaders(const RenderState &v, QString vert, QString frag)
Get a pair of compiled vertex / fragment shaders from GLSL 4.5 sources.
Definition: score-plugin-gfx/Gfx/Graph/Utils.cpp:342
Base toolkit upon which the software is built.
Definition: Application.cpp:90
Definition: DocumentContext.hpp:18
Connection between two score::gfx::Port.
Definition: score-plugin-gfx/Gfx/Graph/Utils.hpp:66
Definition: score-plugin-gfx/Gfx/Graph/Node.hpp:51
Port of a score::gfx::Node.
Definition: score-plugin-gfx/Gfx/Graph/Utils.hpp:48
Useful abstraction for storing all the data related to a render target.
Definition: score-plugin-gfx/Gfx/Graph/Utils.hpp:111