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