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