Loading...
Searching...
No Matches
TouchHistoryNode.hpp
1#pragma once
2
3#include <Gfx/Graph/Node.hpp>
4#include <Gfx/Graph/NodeRenderer.hpp>
5#include <Gfx/Graph/PointerState.hpp>
6#include <Gfx/Graph/RenderList.hpp>
7#include <Gfx/Graph/RenderState.hpp>
8#include <Gfx/Graph/Utils.hpp>
9
10#include <QtCore/qfloat16.h>
11
12#include <score_plugin_gfx_export.h>
13
14#include <chrono>
15#include <cstring>
16#include <memory>
17#include <vector>
18
19namespace score::gfx
20{
45struct SCORE_PLUGIN_GFX_EXPORT TouchHistoryNode : NodeModel
46{
47 static constexpr int columns = 32;
48 static constexpr int rows = 240;
49
50 explicit TouchHistoryNode(std::shared_ptr<PointerState> pointers)
51 : pointers{std::move(pointers)}
52 {
53 output.push_back(new Port{this, {}, Types::Image, {}});
54 }
55 ~TouchHistoryNode() override { m_materialData.release(); }
56
58 std::shared_ptr<PointerState> pointers;
59 const std::chrono::steady_clock::time_point epoch = std::chrono::steady_clock::now();
60
61 NodeRenderer* createRenderer(RenderList& r) const noexcept override;
62};
63
64class SCORE_PLUGIN_GFX_EXPORT TouchHistoryRenderer final : public GenericNodeRenderer
65{
66public:
67 explicit TouchHistoryRenderer(const TouchHistoryNode& node) noexcept
69 {
70 // See TouchHistoryNode: the four channels are copied as they are.
71 m_outputPremultiplied = true;
72 }
73
74 void initState(RenderList& renderer, QRhiResourceUpdateBatch& res) override;
75 void update(RenderList& renderer, QRhiResourceUpdateBatch& res, Edge* edge) override;
76 void releaseState(RenderList& r) override;
77
79 const std::vector<float>& history() const noexcept { return m_history; }
80
81private:
82 void upload(QRhiResourceUpdateBatch& res);
83
84 QRhiTexture* m_texture{};
85 bool m_half{};
86 int64_t m_frame{-1};
87 std::vector<float> m_history;
88};
89
91{
92 return new TouchHistoryRenderer{*this};
93}
94
95namespace touch_history_shaders
96{
97static const constexpr auto vertex = R"_(#version 450
98layout(location = 0) in vec2 position;
99layout(location = 1) in vec2 texcoord;
100layout(location = 0) out vec2 v_texcoord;
101
102layout(std140, binding = 0) uniform renderer_t {
103 mat4 clipSpaceCorrMatrix;
104 vec2 renderSize;
105} renderer;
106
107out gl_PerVertex { vec4 gl_Position; };
108
109void main()
110{
111 v_texcoord = texcoord;
112 gl_Position = renderer.clipSpaceCorrMatrix * vec4(position.xy, 0.0, 1.);
113// The uploaded buffer's row 0 is the top of the image on every backend (the
114// same correction as TexgenNode).
115#if !(defined(QSHADER_HLSL) || defined(QSHADER_MSL))
116 gl_Position.y = - gl_Position.y;
117#endif
118}
119)_";
120
121static const constexpr auto fragment = R"_(#version 450
122layout(location = 0) in vec2 v_texcoord;
123layout(location = 0) out vec4 fragColor;
124
125layout(std140, binding = 0) uniform renderer_t {
126 mat4 clipSpaceCorrMatrix;
127 vec2 renderSize;
128} renderer;
129
130layout(binding = 3) uniform sampler2D history;
131
132void main() { fragColor = texture(history, v_texcoord); }
133)_";
134}
135
136inline void
137TouchHistoryRenderer::initState(RenderList& renderer, QRhiResourceUpdateBatch& res)
138{
139 m_mesh = &renderer.defaultTriangle();
140 defaultMeshInit(renderer, *m_mesh, res);
141 processUBOInit(renderer);
142 m_material.init(renderer, node.input, m_samplers);
143 std::tie(m_vertexS, m_fragmentS) = score::gfx::makeShaders(
144 renderer.state, touch_history_shaders::vertex, touch_history_shaders::fragment);
145
146 auto& rhi = *renderer.state.rhi;
147 // main.js uses FLOAT where the device has it; half floats keep clip-space
148 // positions and times well enough elsewhere.
149 m_half = !rhi.isTextureFormatSupported(QRhiTexture::RGBA32F)
150 && rhi.isTextureFormatSupported(QRhiTexture::RGBA16F);
151 m_texture = rhi.newTexture(
152 m_half ? QRhiTexture::RGBA16F : QRhiTexture::RGBA32F,
153 QSize{TouchHistoryNode::columns, TouchHistoryNode::rows}, 1);
154 m_texture->setName("TouchHistoryRenderer::m_texture");
155 m_texture->create();
156
157 // main.js: NEAREST, CLAMP_TO_EDGE.
158 auto sampler = rhi.newSampler(
159 QRhiSampler::Nearest, QRhiSampler::Nearest, QRhiSampler::None,
160 QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge);
161 sampler->setName("TouchHistoryRenderer::sampler");
162 sampler->create();
163 m_samplers.push_back({sampler, m_texture});
164
165 m_history.assign(
166 std::size_t(TouchHistoryNode::columns) * TouchHistoryNode::rows * 4, 0.f);
167 m_frame = -1;
168 upload(res);
169 m_initialized = true;
170}
171
172inline void TouchHistoryRenderer::upload(QRhiResourceUpdateBatch& res)
173{
174 if(!m_texture)
175 return;
176 QByteArray bytes;
177 if(m_half)
178 {
179 bytes.resize(qsizetype(m_history.size() * sizeof(qfloat16)));
180 qFloatToFloat16(
181 reinterpret_cast<qfloat16*>(bytes.data()), m_history.data(),
182 qsizetype(m_history.size()));
183 }
184 else
185 {
186 bytes = QByteArray(
187 reinterpret_cast<const char*>(m_history.data()),
188 qsizetype(m_history.size() * sizeof(float)));
189 }
190 QRhiTextureSubresourceUploadDescription desc{bytes};
191 res.uploadTexture(m_texture, QRhiTextureUploadDescription{{0, 0, desc}});
192}
193
194inline void
195TouchHistoryRenderer::update(RenderList& renderer, QRhiResourceUpdateBatch& res, Edge*)
196{
197 defaultUBOUpdate(renderer, res);
198 if(m_frame == renderer.frame)
199 return;
200 m_frame = renderer.frame;
201
202 // One row per frame: the rows move up one, this frame's pointers go in the
203 // bottom row, with the time in w for every column (main.js
204 // updateSoundAndTouchHistory, then the history texture's update).
205 auto& n = static_cast<const TouchHistoryNode&>(node);
206 constexpr int rowFloats = TouchHistoryNode::columns * 4;
207 constexpr int last = TouchHistoryNode::rows - 1;
208 float* h = m_history.data();
209 std::memmove(h, h + rowFloats, std::size_t(last) * rowFloats * sizeof(float));
210 std::array<PointerState::Slot, PointerState::slots> slots{};
211 if(n.pointers)
212 slots = n.pointers->snapshot();
213 const float t = std::chrono::duration<float>(std::chrono::steady_clock::now() - n.epoch)
214 .count();
215 float* row = h + std::size_t(last) * rowFloats;
216 for(int c = 0; c < TouchHistoryNode::columns; c++)
217 {
218 row[c * 4 + 0] = slots[c].x;
219 row[c * 4 + 1] = slots[c].y;
220 row[c * 4 + 2] = slots[c].pressure;
221 row[c * 4 + 3] = t;
222 }
223 upload(res);
224}
225
227{
228 if(m_texture)
229 m_texture->deleteLater();
230 m_texture = nullptr;
231 m_history.clear();
232 m_frame = -1;
234}
235}
Generic renderer.
Definition NodeRenderer.hpp:330
void releaseState(RenderList &renderer) override
Definition NodeRenderer.cpp:421
std::vector< Port * > input
Input ports of that node.
Definition score-plugin-gfx/Gfx/Graph/Node.hpp:134
Common base class for most single-pass, simple nodes.
Definition score-plugin-gfx/Gfx/Graph/Node.hpp:313
Renderer for a given node.
Definition NodeRenderer.hpp:11
bool m_initialized
Definition NodeRenderer.hpp:260
List of nodes to be rendered to an output.
Definition RenderList.hpp:33
Definition TouchHistoryNode.hpp:65
void initState(RenderList &renderer, QRhiResourceUpdateBatch &res) override
Definition TouchHistoryNode.hpp:137
const std::vector< float > & history() const noexcept
The history, newest row last (the bottom of the image), as uploaded.
Definition TouchHistoryNode.hpp:79
void releaseState(RenderList &r) override
Definition TouchHistoryNode.hpp:226
Graphics rendering pipeline for ossia score.
Definition Filter/PreviewWidget.hpp:11
std::pair< QShader, QShader > makeShaders(const RenderState &v, QString vert, QString frag, int multiViewCount)
Get a pair of compiled vertex / fragment shaders from GLSL 4.5 sources.
Definition score-plugin-gfx/Gfx/Graph/Utils.cpp:1523
Connection between two score::gfx::Port.
Definition score-plugin-gfx/Gfx/Graph/Utils.hpp:236
Port of a score::gfx::Node.
Definition score-plugin-gfx/Gfx/Graph/Utils.hpp:215
The pointers of an output as a texture: vertexshaderart's touch history (greggman/vertexshaderart,...
Definition TouchHistoryNode.hpp:46
std::shared_ptr< PointerState > pointers
What the history records every frame.
Definition TouchHistoryNode.hpp:58
NodeRenderer * createRenderer(RenderList &r) const noexcept override
Create a renderer in a given context for this node.
Definition TouchHistoryNode.hpp:90