##// END OF EJS Templates
Fix memory leak in opengl series handling in qml...
Miikka Heikkinen -
r2827:2dcfcd4a9f69
parent child
Show More
@@ -1,317 +1,321
1 1 /****************************************************************************
2 2 **
3 3 ** Copyright (C) 2015 The Qt Company Ltd
4 4 ** All rights reserved.
5 5 ** For any questions to The Qt Company, please use contact form at http://qt.io
6 6 **
7 7 ** This file is part of the Qt Charts module.
8 8 **
9 9 ** Licensees holding valid commercial license for Qt may use this file in
10 10 ** accordance with the Qt License Agreement provided with the Software
11 11 ** or, alternatively, in accordance with the terms contained in a written
12 12 ** agreement between you and The Qt Company.
13 13 **
14 14 ** If you have questions regarding the use of this file, please use
15 15 ** contact form at http://qt.io
16 16 **
17 17 ****************************************************************************/
18 18
19 19 #include "declarativerendernode.h"
20 20
21 21 #include <QtGui/QOpenGLContext>
22 22 #include <QtGui/QOpenGLFunctions>
23 23 #include <QtGui/QOpenGLFramebufferObjectFormat>
24 24 #include <QtGui/QOpenGLFramebufferObject>
25 25 #include <QOpenGLShaderProgram>
26 26 #include <QtGui/QOpenGLBuffer>
27 27
28 28 //#define QDEBUG_TRACE_GL_FPS
29 29 #ifdef QDEBUG_TRACE_GL_FPS
30 30 # include <QElapsedTimer>
31 31 #endif
32 32
33 33 QT_CHARTS_BEGIN_NAMESPACE
34 34
35 35 // This node draws the xy series data on a transparent background using OpenGL.
36 36 // It is used as a child node of the chart node.
37 37 DeclarativeRenderNode::DeclarativeRenderNode(QQuickWindow *window) :
38 38 QObject(),
39 39 QSGSimpleTextureNode(),
40 40 m_texture(0),
41 41 m_window(window),
42 42 m_textureOptions(QQuickWindow::TextureHasAlphaChannel),
43 43 m_textureSize(1, 1),
44 44 m_recreateFbo(false),
45 45 m_fbo(0),
46 46 m_program(0),
47 47 m_shaderAttribLoc(-1),
48 48 m_colorUniformLoc(-1),
49 49 m_minUniformLoc(-1),
50 50 m_deltaUniformLoc(-1),
51 51 m_pointSizeUniformLoc(-1),
52 52 m_renderNeeded(true)
53 53 {
54 54 initializeOpenGLFunctions();
55 55
56 56 // Our texture node must have a texture, so use a default one pixel texture
57 57 GLuint defaultTexture = 0;
58 58 glGenTextures(1, &defaultTexture);
59 59 glBindTexture(GL_TEXTURE_2D, defaultTexture);
60 60 uchar buf[4] = { 0, 0, 0, 0 };
61 61 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, &buf);
62 62
63 63 QQuickWindow::CreateTextureOptions defaultTextureOptions = QQuickWindow::CreateTextureOptions(
64 64 QQuickWindow::TextureHasAlphaChannel | QQuickWindow::TextureOwnsGLTexture);
65 65 m_texture = m_window->createTextureFromId(defaultTexture, QSize(1, 1), defaultTextureOptions);
66 66
67 67 setTexture(m_texture);
68 68 setFiltering(QSGTexture::Linear);
69 69 setTextureCoordinatesTransform(QSGSimpleTextureNode::MirrorVertically);
70 70 }
71 71
72 72 DeclarativeRenderNode::~DeclarativeRenderNode()
73 73 {
74 74 delete m_texture;
75 75 delete m_fbo;
76 76
77 77 delete m_program;
78 78 m_program = 0;
79 79
80 80 cleanXYSeriesResources(0);
81 81 }
82 82
83 83 static const char *vertexSource =
84 84 "attribute highp vec2 points;\n"
85 85 "uniform highp vec2 min;\n"
86 86 "uniform highp vec2 delta;\n"
87 87 "uniform highp float pointSize;\n"
88 88 "void main() {\n"
89 89 " vec2 normalPoint = vec2(-1, -1) + ((points - min) / delta);\n"
90 90 " gl_Position = vec4(normalPoint, 0, 1);\n"
91 91 " gl_PointSize = pointSize;\n"
92 92 "}";
93 93 static const char *fragmentSource =
94 94 "uniform highp vec3 color;\n"
95 95 "void main() {\n"
96 96 " gl_FragColor = vec4(color,1);\n"
97 97 "}\n";
98 98
99 99 // Must be called on render thread and in context
100 100 void DeclarativeRenderNode::initGL()
101 101 {
102 102 recreateFBO();
103 103
104 104 m_program = new QOpenGLShaderProgram;
105 105 m_program->addShaderFromSourceCode(QOpenGLShader::Vertex, vertexSource);
106 106 m_program->addShaderFromSourceCode(QOpenGLShader::Fragment, fragmentSource);
107 107 m_program->bindAttributeLocation("points", 0);
108 108 m_program->link();
109 109
110 110 m_program->bind();
111 111 m_colorUniformLoc = m_program->uniformLocation("color");
112 112 m_minUniformLoc = m_program->uniformLocation("min");
113 113 m_deltaUniformLoc = m_program->uniformLocation("delta");
114 114 m_pointSizeUniformLoc = m_program->uniformLocation("pointSize");
115 115
116 116 // Create a vertex array object. In OpenGL ES 2.0 and OpenGL 2.x
117 117 // implementations this is optional and support may not be present
118 118 // at all. Nonetheless the below code works in all cases and makes
119 119 // sure there is a VAO when one is needed.
120 120 m_vao.create();
121 121 QOpenGLVertexArrayObject::Binder vaoBinder(&m_vao);
122 122
123 123 #if !defined(QT_OPENGL_ES_2)
124 124 if (!QOpenGLContext::currentContext()->isOpenGLES()) {
125 125 // Make it possible to change point primitive size and use textures with them in
126 126 // the shaders. These are implicitly enabled in ES2.
127 127 // Qt Quick doesn't change these flags, so it should be safe to just enable them
128 128 // at initialization.
129 129 glEnable(GL_PROGRAM_POINT_SIZE);
130 130 }
131 131 #endif
132 132
133 133 m_program->release();
134 134 }
135 135
136 136 void DeclarativeRenderNode::recreateFBO()
137 137 {
138 138 QOpenGLFramebufferObjectFormat fboFormat;
139 139 fboFormat.setAttachment(QOpenGLFramebufferObject::NoAttachment);
140 140 delete m_fbo;
141 141 m_fbo = new QOpenGLFramebufferObject(m_textureSize.width(),
142 142 m_textureSize.height(),
143 143 fboFormat);
144 144
145 145 delete m_texture;
146 146 m_texture = m_window->createTextureFromId(m_fbo->texture(), m_textureSize, m_textureOptions);
147 147 setTexture(m_texture);
148 148
149 149 m_recreateFbo = false;
150 150 }
151 151
152 152 // Must be called on render thread and in context
153 153 void DeclarativeRenderNode::setTextureSize(const QSize &size)
154 154 {
155 155 m_textureSize = size;
156 156 m_recreateFbo = true;
157 157 m_renderNeeded = true;
158 158 }
159 159
160 160 // Must be called on render thread while gui thread is blocked, and in context
161 161 void DeclarativeRenderNode::setSeriesData(bool mapDirty, const GLXYDataMap &dataMap)
162 162 {
163 163 if (mapDirty) {
164 164 // Series have changed, recreate map, but utilize old data where feasible
165 165 GLXYDataMap oldMap = m_xyDataMap;
166 166 m_xyDataMap.clear();
167 167
168 168 GLXYDataMapIterator i(dataMap);
169 169 while (i.hasNext()) {
170 170 i.next();
171 171 GLXYSeriesData *data = oldMap.take(i.key());
172 172 const GLXYSeriesData *newData = i.value();
173 173 if (!data || newData->dirty) {
174 174 data = new GLXYSeriesData;
175 175 data->array = newData->array;
176 176 data->color = newData->color;
177 177 data->dirty = newData->dirty;
178 178 data->width = newData->width;
179 179 data->type = newData->type;
180 180 data->min = newData->min;
181 181 data->delta = newData->delta;
182 182 }
183 183 m_xyDataMap.insert(i.key(), data);
184 184 }
185 185 // Delete remaining old data
186 186 i = oldMap;
187 187 while (i.hasNext()) {
188 188 i.next();
189 189 delete i.value();
190 190 cleanXYSeriesResources(i.key());
191 191 }
192 192 } else {
193 193 // Series have not changed, so just copy dirty data over
194 194 GLXYDataMapIterator i(dataMap);
195 195 while (i.hasNext()) {
196 196 i.next();
197 197 const GLXYSeriesData *newData = i.value();
198 198 if (i.value()->dirty) {
199 199 GLXYSeriesData *data = m_xyDataMap.value(i.key());
200 200 if (data) {
201 201 data->array = newData->array;
202 202 data->color = newData->color;
203 203 data->dirty = newData->dirty;
204 204 data->width = newData->width;
205 205 data->type = newData->type;
206 206 data->min = newData->min;
207 207 data->delta = newData->delta;
208 208 }
209 209 }
210 210 }
211 211 }
212 212 markDirty(DirtyMaterial);
213 213 m_renderNeeded = true;
214 214 }
215 215
216 216 void DeclarativeRenderNode::renderGL()
217 217 {
218 218 glClearColor(0, 0, 0, 0);
219 219
220 220 QOpenGLVertexArrayObject::Binder vaoBinder(&m_vao);
221 221 m_program->bind();
222 222 m_fbo->bind();
223 223
224 224 glClear(GL_COLOR_BUFFER_BIT);
225 225 glEnableVertexAttribArray(0);
226 226
227 227 glViewport(0, 0, m_textureSize.width(), m_textureSize.height());
228 228
229 229 GLXYDataMapIterator i(m_xyDataMap);
230 230 while (i.hasNext()) {
231 231 i.next();
232 232 QOpenGLBuffer *vbo = m_seriesBufferMap.value(i.key());
233 233 GLXYSeriesData *data = i.value();
234 234
235 235 m_program->setUniformValue(m_colorUniformLoc, data->color);
236 236 m_program->setUniformValue(m_minUniformLoc, data->min);
237 237 m_program->setUniformValue(m_deltaUniformLoc, data->delta);
238 238
239 239 if (!vbo) {
240 240 vbo = new QOpenGLBuffer;
241 241 m_seriesBufferMap.insert(i.key(), vbo);
242 242 vbo->create();
243 243 }
244 244 vbo->bind();
245 245 if (data->dirty) {
246 246 vbo->allocate(data->array.constData(), data->array.count() * sizeof(GLfloat));
247 247 data->dirty = false;
248 248 }
249 249
250 250 glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, 0);
251 251 if (data->type == QAbstractSeries::SeriesTypeLine) {
252 252 glLineWidth(data->width);
253 253 glDrawArrays(GL_LINE_STRIP, 0, data->array.size() / 2);
254 254 } else { // Scatter
255 255 m_program->setUniformValue(m_pointSizeUniformLoc, data->width);
256 256 glDrawArrays(GL_POINTS, 0, data->array.size() / 2);
257 257 }
258 258 vbo->release();
259 259 }
260 260
261 261 #ifdef QDEBUG_TRACE_GL_FPS
262 262 static QElapsedTimer stopWatch;
263 263 static int frameCount = -1;
264 264 if (frameCount == -1) {
265 265 stopWatch.start();
266 266 frameCount = 0;
267 267 }
268 268 frameCount++;
269 269 int elapsed = stopWatch.elapsed();
270 270 if (elapsed >= 1000) {
271 271 elapsed = stopWatch.restart();
272 272 qreal fps = qreal(0.1 * int(10000.0 * (qreal(frameCount) / qreal(elapsed))));
273 273 qDebug() << "FPS:" << fps;
274 274 frameCount = 0;
275 275 }
276 276 #endif
277 277
278 278 markDirty(DirtyMaterial);
279 279 m_window->resetOpenGLState();
280 280 }
281 281
282 282 // Must be called on render thread as response to beforeRendering signal
283 283 void DeclarativeRenderNode::render()
284 284 {
285 285 if (m_renderNeeded) {
286 286 if (m_xyDataMap.size()) {
287 287 if (!m_program)
288 288 initGL();
289 289 if (m_recreateFbo)
290 290 recreateFBO();
291 291 renderGL();
292 292 } else {
293 293 if (rect() != QRectF()) {
294 294 glClearColor(0, 0, 0, 0);
295 295 m_fbo->bind();
296 296 glClear(GL_COLOR_BUFFER_BIT);
297 297
298 298 // If last series was removed, zero out the node rect
299 299 setRect(QRectF());
300 300 }
301 301 }
302 302 m_renderNeeded = false;
303 303 }
304 304 }
305 305
306 306 void DeclarativeRenderNode::cleanXYSeriesResources(const QXYSeries *series)
307 307 {
308 308 if (series) {
309 309 delete m_seriesBufferMap.take(series);
310 delete m_xyDataMap.take(series);
310 311 } else {
311 312 foreach (QOpenGLBuffer *buffer, m_seriesBufferMap.values())
312 313 delete buffer;
313 314 m_seriesBufferMap.clear();
315 foreach (GLXYSeriesData *data, m_xyDataMap.values())
316 delete data;
317 m_xyDataMap.clear();
314 318 }
315 319 }
316 320
317 321 QT_CHARTS_END_NAMESPACE
General Comments 0
You need to be logged in to leave comments. Login now