CpuAnalysisNode.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::OutputNodeRenderer
14 {
15  using texture_inputs = avnd::texture_input_introspection<Node_T>;
16  const GfxNode<Node_T>& parent;
17  Node_T state;
18  ossia::small_flat_map<const score::gfx::Port*, score::gfx::TextureRenderTarget, 2>
19  m_rts;
20 
21  std::vector<QRhiReadbackResult> m_readbacks;
22  ossia::time_value m_last_time{-1};
23 
24  GfxRenderer(const GfxNode<Node_T>& p)
25  : score::gfx::OutputNodeRenderer{}
26  , parent{p}
27  , m_readbacks(texture_inputs::size)
28  {
29  prepareNewState(state, parent);
30  }
31 
33  renderTargetForInput(const score::gfx::Port& p) override
34  {
35  auto it = m_rts.find(&p);
36  SCORE_ASSERT(it != m_rts.end());
37  return it->second;
38  }
39 
40  template <typename Tex>
41  void createInput(
42  score::gfx::RenderList& renderer, int k, const Tex& texture_spec, QSize size)
43  {
44  auto port = parent.input[k];
45  constexpr auto flags = QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource;
46  auto texture = renderer.state.rhi->newTexture(
47  gpp::qrhi::textureFormat<Tex>(), size, 1, flags);
48  SCORE_ASSERT(texture->create());
49  m_rts[port]
50  = score::gfx::createRenderTarget(renderer.state, texture, renderer.samples());
51  }
52 
53  QRhiTexture* texture(int k) const noexcept
54  {
55  auto port = parent.input[k];
56  auto it = m_rts.find(port);
57  SCORE_ASSERT(it != m_rts.end());
58  SCORE_ASSERT(it->second.texture);
59  return it->second.texture;
60  }
61 
62  void loadInputTexture(avnd::cpu_texture auto& cpu_tex, int k)
63  {
64  auto& buf = m_readbacks[k].data;
65  if(buf.size() != 4 * cpu_tex.width * cpu_tex.height)
66  {
67  cpu_tex.bytes = nullptr;
68  }
69  else
70  {
71  cpu_tex.bytes = reinterpret_cast<unsigned char*>(buf.data());
72  cpu_tex.changed = true;
73  }
74  }
75 
76  void init(score::gfx::RenderList& renderer, QRhiResourceUpdateBatch& res) override
77  {
78  if constexpr(requires { state.init(); })
79  {
80  parent.processControlIn(state, this->parent.last_message);
81  state.init();
82  }
83 
84  // Init input render targets
85  int k = 0;
86  avnd::cpu_texture_input_introspection<Node_T>::for_all(
87  avnd::get_inputs<Node_T>(state), [&]<typename F>(F& t) {
88  QSize sz = renderer.state.renderSize;
89  if constexpr(requires {
90  t.request_width;
91  t.request_height;
92  })
93  {
94  sz.rwidth() = t.request_width;
95  sz.rheight() = t.request_height;
96  }
97  createInput(renderer, k, t.texture, sz);
98  t.texture.width = sz.width();
99  t.texture.height = sz.height();
100  k++;
101  });
102  }
103 
104  void update(score::gfx::RenderList& renderer, QRhiResourceUpdateBatch& res) override {
105  bool updated = false;
106  /*
107  int k = 0;
108  avnd::cpu_texture_input_introspection<Node_T>::for_all(
109  avnd::get_inputs<Node_T>(state), [&]<typename F>(F& t) {
110  if constexpr(requires {
111  t.request_width;
112  t.request_height;
113  })
114  {
115  const auto tex = this->texture(k)->pixelSize();
116  if(tex.width() != t.request_width || tex.height() != t.request_height)
117  {
118  QSize sz{t.request_width, t.request_height};
119 
120  // Release
121  auto port = parent.input[k];
122 
123  m_rts[port].release();
124  createInput(renderer, k, t.texture, sz);
125 
126  t.texture.width = sz.width();
127  t.texture.height = sz.height();
128 
129  updated = true;
130  }
131  }
132  k++;
133  });
134 */
135  if(updated)
136  {
137  // We must notify the graph that the previous nodes have to be recomputed
138  }
139  }
140 
141  void release(score::gfx::RenderList& r) override
142  {
143  // Free inputs
144  // TODO investigate why reference does not work here:
145  for(auto [port, rt] : m_rts)
146  rt.release();
147  m_rts.clear();
148  }
149 
150  void inputAboutToFinish(
151  score::gfx::RenderList& renderer, const score::gfx::Port& p,
152  QRhiResourceUpdateBatch*& res) override
153  {
154  res = renderer.state.rhi->nextResourceUpdateBatch();
155  const auto& inputs = this->parent.input;
156  auto index_of_port = ossia::find(inputs, &p) - inputs.begin();
157  SCORE_ASSERT(index_of_port == 0);
158  {
159  auto tex = m_rts[&p].texture;
160  auto& readback = m_readbacks[index_of_port];
161  readback = {};
162  res->readBackTexture(QRhiReadbackDescription{tex}, &readback);
163  }
164  }
165 
166  void runInitialPasses(
167  score::gfx::RenderList& renderer, QRhiCommandBuffer& commands,
168  QRhiResourceUpdateBatch*& res, score::gfx::Edge& edge) override
169  {
170  auto& rhi = *renderer.state.rhi;
171 
172  // If we are paused, we don't run the processor implementation.
173  if(parent.last_message.token.date == m_last_time)
174  {
175  return;
176  }
177  m_last_time = parent.last_message.token.date;
178 
179  // Fetch input textures (if any)
180  {
181  // Insert a synchronisation point to allow readbacks to complete
182  rhi.finish();
183 
184  // Copy the readback output inside the structure
185  // TODO it would be much better to do this inside the readback's
186  // "completed" callback.
187  int k = 0;
188  avnd::cpu_texture_input_introspection<Node_T>::for_all(
189  avnd::get_inputs<Node_T>(state), [&](auto& t) {
190  loadInputTexture(t.texture, k);
191  k++;
192  });
193  }
194 
195  parent.processControlIn(state, this->parent.last_message);
196 
197  // Run the processor
198  state();
199 
200  // Copy the data to the model node
201  parent.processControlOut(this->state);
202  }
203 };
204 
205 template <typename Node_T>
206  requires(
207  avnd::texture_input_introspection<Node_T>::size > 0
208  && avnd::texture_output_introspection<Node_T>::size == 0)
209 struct GfxNode<Node_T> final : CustomGpuOutputNodeBase
210 {
211  oscr::ProcessModel<Node_T>& processModel;
212  GfxNode(
214  std::weak_ptr<Execution::ExecutionCommandQueue> q, Gfx::exec_controls ctls, int id)
215  : CustomGpuOutputNodeBase{std::move(q), std::move(ctls)}
216  , processModel{element}
217  {
218  this->instance = id;
219 
220  using texture_inputs = avnd::texture_input_introspection<Node_T>;
221 
222  // FIXME incorrect if we have other ports before, e.g. a float part followed by an image port
223  for(std::size_t i = 0; i < texture_inputs::size; i++)
224  {
225  this->input.push_back(
226  new score::gfx::Port{this, {}, score::gfx::Types::Image, {}});
227  }
228  }
229 
231  createRenderer(score::gfx::RenderList& r) const noexcept override
232  {
233  return new GfxRenderer<Node_T>{*this};
234  }
235 };
236 
237 }
238 #endif
Definition: score-plugin-avnd/Crousti/ProcessModel.hpp:551
Definition: OutputNode.hpp:11
List of nodes to be rendered to an output.
Definition: RenderList.hpp:19
RenderState & state
RenderState corresponding to this RenderList.
Definition: RenderList.hpp:89
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
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