CpuFilterNode.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_inputs = avnd::texture_input_introspection<Node_T>;
16  using texture_outputs = avnd::texture_output_introspection<Node_T>;
17  const GfxNode<Node_T>& parent;
18  Node_T state;
19  ossia::small_flat_map<const score::gfx::Port*, score::gfx::TextureRenderTarget, 2>
20  m_rts;
21 
22  std::vector<QRhiReadbackResult> m_readbacks;
23  ossia::time_value m_last_time{-1};
24 
25  GfxRenderer(const GfxNode<Node_T>& p)
26  : score::gfx::GenericNodeRenderer{p}
27  , parent{p}
28  , m_readbacks(texture_inputs::size)
29  {
30  prepareNewState(state, parent);
31  }
32 
34  renderTargetForInput(const score::gfx::Port& p) override
35  {
36  auto it = m_rts.find(&p);
37  SCORE_ASSERT(it != m_rts.end());
38  return it->second;
39  }
40 
41  template <typename Tex>
42  void createInput(
43  score::gfx::RenderList& renderer, int k, const Tex& texture_spec, QSize size)
44  {
45  auto port = parent.input[k];
46  constexpr auto flags = QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource;
47  auto texture = renderer.state.rhi->newTexture(
48  gpp::qrhi::textureFormat<Tex>(), size, 1, flags);
49  SCORE_ASSERT(texture->create());
50  m_rts[port]
51  = score::gfx::createRenderTarget(renderer.state, texture, renderer.samples());
52  }
53 
54  template <typename Tex>
55  void
56  createOutput(score::gfx::RenderList& renderer, const Tex& texture_spec, QSize size)
57  {
58  auto& rhi = *renderer.state.rhi;
59  QRhiTexture* texture = &renderer.emptyTexture();
60  if(size.width() > 0 && size.height() > 0)
61  {
62  texture = rhi.newTexture(
63  gpp::qrhi::textureFormat<Tex>(), size, 1, QRhiTexture::Flag{});
64 
65  texture->create();
66  }
67 
68  auto sampler = rhi.newSampler(
69  QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None,
70  QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge);
71 
72  sampler->create();
73  this->m_samplers.push_back({sampler, texture});
74  }
75 
76  template <avnd::cpu_texture Tex>
77  QRhiTexture* updateTexture(score::gfx::RenderList& renderer, int k, const Tex& cpu_tex)
78  {
79  auto& [sampler, texture] = this->m_samplers[k];
80  if(texture)
81  {
82  auto sz = texture->pixelSize();
83  if(cpu_tex.width == sz.width() && cpu_tex.height == sz.height())
84  return texture;
85  }
86 
87  // Check the texture size
88  if(cpu_tex.width > 0 && cpu_tex.height > 0)
89  {
90  QRhiTexture* oldtex = texture;
91  QRhiTexture* newtex = renderer.state.rhi->newTexture(
92  gpp::qrhi::textureFormat<Tex>(), QSize{cpu_tex.width, cpu_tex.height}, 1,
93  QRhiTexture::Flag{});
94  newtex->create();
95  for(auto& [edge, pass] : this->m_p)
96  if(pass.srb)
97  score::gfx::replaceTexture(*pass.srb, sampler, newtex);
98  texture = newtex;
99 
100  if(oldtex && oldtex != &renderer.emptyTexture())
101  {
102  oldtex->deleteLater();
103  }
104 
105  return newtex;
106  }
107  else
108  {
109  for(auto& [edge, pass] : this->m_p)
110  if(pass.srb)
111  score::gfx::replaceTexture(*pass.srb, sampler, &renderer.emptyTexture());
112 
113  return &renderer.emptyTexture();
114  }
115  }
116 
117  void uploadOutputTexture(
118  score::gfx::RenderList& renderer, int k, avnd::cpu_texture auto& cpu_tex,
119  QRhiResourceUpdateBatch* res)
120  {
121  if(cpu_tex.changed)
122  {
123  if(auto texture = updateTexture(renderer, k, cpu_tex))
124  {
125  // Upload it
126  {
127  QRhiTextureSubresourceUploadDescription sd(
128  cpu_tex.bytes, cpu_tex.width * cpu_tex.height * 4);
129  QRhiTextureUploadDescription desc{QRhiTextureUploadEntry{0, 0, sd}};
130 
131  res->uploadTexture(texture, desc);
132  }
133 
134  cpu_tex.changed = false;
135  k++;
136  }
137  }
138  }
139 
140  void loadInputTexture(avnd::cpu_texture auto& cpu_tex, int k)
141  {
142  auto& buf = m_readbacks[k].data;
143  if(buf.size() != 4 * cpu_tex.width * cpu_tex.height)
144  {
145  cpu_tex.bytes = nullptr;
146  }
147  else
148  {
149  cpu_tex.bytes = reinterpret_cast<unsigned char*>(buf.data());
150  cpu_tex.changed = true;
151  }
152  }
153 
154  void init(score::gfx::RenderList& renderer, QRhiResourceUpdateBatch& res) override
155  {
156  if constexpr(requires { state.init(); })
157  {
158  state.init();
159  }
160 
161  const auto& mesh = renderer.defaultTriangle();
162  this->defaultMeshInit(renderer, mesh, res);
163  this->processUBOInit(renderer);
164  this->m_material.init(renderer, this->node.input, this->m_samplers);
165  std::tie(this->m_vertexS, this->m_fragmentS)
166  = score::gfx::makeShaders(renderer.state, generic_texgen_vs, generic_texgen_fs);
167 
168  {
169  // Init input render targets
170  int k = 0;
171  avnd::cpu_texture_input_introspection<Node_T>::for_all(
172  avnd::get_inputs<Node_T>(state), [&]<typename F>(F& t) {
173  auto sz = renderer.state.renderSize;
174  createInput(renderer, k, t.texture, sz);
175  t.texture.width = sz.width();
176  t.texture.height = sz.height();
177  k++;
178  });
179  }
180 
181  {
182  // Init textures for the outputs
183  avnd::cpu_texture_output_introspection<Node_T>::for_all(
184  avnd::get_outputs<Node_T>(state), [&](auto& t) {
185  createOutput(renderer, t.texture, QSize{t.texture.width, t.texture.height});
186  });
187  }
188 
189  this->defaultPassesInit(renderer, mesh);
190  }
191 
192  void update(score::gfx::RenderList& renderer, QRhiResourceUpdateBatch& res) override
193  {
194  this->defaultUBOUpdate(renderer, res);
195  }
196 
197  void release(score::gfx::RenderList& r) override
198  {
199  // Free outputs
200  for(auto& [sampl, texture] : this->m_samplers)
201  {
202  if(texture != &r.emptyTexture())
203  texture->deleteLater();
204  texture = nullptr;
205  }
206 
207  // Free inputs
208  // TODO investigate why reference does not work here:
209  for(auto [port, rt] : m_rts)
210  rt.release();
211  m_rts.clear();
212 
213  this->defaultRelease(r);
214  }
215 
216  void inputAboutToFinish(
217  score::gfx::RenderList& renderer, const score::gfx::Port& p,
218  QRhiResourceUpdateBatch*& res) override
219  {
220  res = renderer.state.rhi->nextResourceUpdateBatch();
221  const auto& inputs = this->node.input;
222  auto index_of_port = ossia::find(inputs, &p) - inputs.begin();
223  SCORE_ASSERT(index_of_port == 0);
224  {
225  auto tex = m_rts[&p].texture;
226  auto& readback = m_readbacks[index_of_port];
227  readback = {};
228  res->readBackTexture(QRhiReadbackDescription{tex}, &readback);
229  }
230  }
231 
232  void runInitialPasses(
233  score::gfx::RenderList& renderer, QRhiCommandBuffer& commands,
234  QRhiResourceUpdateBatch*& res, score::gfx::Edge& edge) override
235  {
236  auto& rhi = *renderer.state.rhi;
237 
238  // If we are paused, we don't run the processor implementation.
239  if(parent.last_message.token.date == m_last_time)
240  {
241  return;
242  }
243  m_last_time = parent.last_message.token.date;
244 
245  // Fetch input textures (if any)
246  {
247  // Insert a synchronisation point to allow readbacks to complete
248  rhi.finish();
249 
250  // Copy the readback output inside the structure
251  // TODO it would be much better to do this inside the readback's
252  // "completed" callback.
253  int k = 0;
254  avnd::cpu_texture_input_introspection<Node_T>::for_all(
255  avnd::get_inputs<Node_T>(state), [&](auto& t) {
256  loadInputTexture(t.texture, k);
257  k++;
258  });
259  }
260 
261  parent.processControlIn(state, this->parent.last_message);
262 
263  // Run the processor
264  state();
265 
266  // Upload output textures
267  {
268  int k = 0;
269  avnd::cpu_texture_output_introspection<Node_T>::for_all(
270  avnd::get_outputs<Node_T>(state), [&](auto& t) {
271  uploadOutputTexture(renderer, k, t.texture, res);
272  k++;
273  });
274 
275  commands.resourceUpdate(res);
276  res = renderer.state.rhi->nextResourceUpdateBatch();
277  }
278 
279  // Copy the data to the model node
280  parent.processControlOut(this->state);
281  }
282 };
283 
284 template <typename Node_T>
285  requires(avnd::texture_input_introspection<Node_T>::size > 0
286  && avnd::texture_output_introspection<Node_T>::size > 0)
287 struct GfxNode<Node_T> final
288  : CustomGfxNodeBase
289  , GpuWorker
290  , GpuControlIns
291  , GpuControlOuts
292 {
293  oscr::ProcessModel<Node_T>& processModel;
294  GfxNode(
296  std::weak_ptr<Execution::ExecutionCommandQueue> q, Gfx::exec_controls ctls, int id)
297  : GpuControlOuts{std::move(q), std::move(ctls)}
298  , processModel{element}
299  {
300  this->instance = id;
301 
302  using texture_inputs = avnd::texture_input_introspection<Node_T>;
303  using texture_outputs = avnd::texture_output_introspection<Node_T>;
304 
305  // FIXME incorrect if we have other ports before, e.g. a float part followed by an image port
306  for(std::size_t i = 0; i < texture_inputs::size; i++)
307  {
308  this->input.push_back(
309  new score::gfx::Port{this, {}, score::gfx::Types::Image, {}});
310  }
311  for(std::size_t i = 0; i < texture_outputs::size; i++)
312  {
313  this->output.push_back(
314  new score::gfx::Port{this, {}, score::gfx::Types::Image, {}});
315  }
316  }
317 
319  createRenderer(score::gfx::RenderList& r) const noexcept override
320  {
321  return new GfxRenderer<Node_T>{*this};
322  }
323 };
324 }
325 #endif
Definition: score-plugin-avnd/Crousti/ProcessModel.hpp:551
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:247
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
TextureRenderTarget createRenderTarget(const RenderState &state, QRhiTexture *tex, int samples)
Create a render target from a texture.
Definition: score-plugin-gfx/Gfx/Graph/Utils.cpp:10
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:334
Base toolkit upon which the software is built.
Definition: Application.cpp:90
Connection between two score::gfx::Port.
Definition: score-plugin-gfx/Gfx/Graph/Utils.hpp:64
Port of a score::gfx::Node.
Definition: score-plugin-gfx/Gfx/Graph/Utils.hpp:46
Useful abstraction for storing all the data related to a render target.
Definition: score-plugin-gfx/Gfx/Graph/Utils.hpp:109