Merge lp:~linaro-graphics-wg/glmark2/buffer-update into lp:glmark2/2011.11

Proposed by Alexandros Frantzis
Status: Merged
Merged at revision: 149
Proposed branch: lp:~linaro-graphics-wg/glmark2/buffer-update
Merge into: lp:glmark2/2011.11
Diff against target: 1264 lines (+934/-36)
17 files modified
data/shaders/buffer-wireframe.frag (+17/-0)
data/shaders/buffer-wireframe.vert (+57/-0)
src/android.cpp (+4/-0)
src/canvas-android.cpp (+24/-0)
src/canvas-android.h (+3/-0)
src/canvas-x11-egl.cpp (+29/-0)
src/canvas-x11-egl.h (+1/-0)
src/canvas-x11-glx.cpp (+9/-0)
src/canvas-x11-glx.h (+1/-0)
src/gl-headers.cpp (+26/-0)
src/gl-headers.h (+15/-2)
src/main.cpp (+4/-0)
src/mesh.cpp (+226/-26)
src/mesh.h (+30/-6)
src/scene-buffer.cpp (+457/-0)
src/scene-build.cpp (+11/-2)
src/scene.h (+20/-0)
To merge this branch: bzr merge lp:~linaro-graphics-wg/glmark2/buffer-update
Reviewer Review Type Date Requested Status
Jesse Barker Approve
Review via email: mp+78869@code.launchpad.net

Description of the change

New scene to benchmark various aspects of buffer (VBO) update operations. It provides various options to control the details of the update process: whether to use MapBuffer or BufferSubData, how much of the buffer to update, how dispersed the update ranges are, the buffer usage hint, whether to use interleaved attribute storage and the size of the grid.

Visually the scene draws a grid modulated by a sine wave. The wave's details (wavelength, duty-cycle) are affected by some of the options to produce an interesting visual result. Furthermore, the grid is drawn using a GLSL wireframe technique which is very interesting in and of itself.

To post a comment you must log in.
Revision history for this message
Jesse Barker (jesse-barker) wrote :

Looks fine in general. Let's take it for 2011.10.

Any particular reason why the defaults for the scene itself don't match the overall glmark2 defaults for this scene (i.e., "glmark2 -b buffer" vs. "glmark2")?

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'data/shaders/buffer-wireframe.frag'
2--- data/shaders/buffer-wireframe.frag 1970-01-01 00:00:00 +0000
3+++ data/shaders/buffer-wireframe.frag 2011-10-10 16:32:12 +0000
4@@ -0,0 +1,17 @@
5+varying vec4 dist;
6+
7+const vec4 LINE_COLOR = vec4(1.0);
8+const vec4 TRIANGLE_COLOR = vec4(0.0, 0.5, 0.8, 0.8);
9+
10+void main(void)
11+{
12+ // Get the minimum distance of this fragment from a triangle edge.
13+ // We need to multiply with dist.w to undo the workaround we had
14+ // to perform to get linear interpolation (instead of perspective correct).
15+ float d = min(dist.x * dist.w, min(dist.y * dist.w, dist.z * dist.w));
16+
17+ // Get the intensity of the wireframe line
18+ float I = exp2(-2.0 * d * d);
19+
20+ gl_FragColor = mix(TRIANGLE_COLOR, LINE_COLOR, I);
21+}
22
23=== added file 'data/shaders/buffer-wireframe.vert'
24--- data/shaders/buffer-wireframe.vert 1970-01-01 00:00:00 +0000
25+++ data/shaders/buffer-wireframe.vert 2011-10-10 16:32:12 +0000
26@@ -0,0 +1,57 @@
27+// Wireframe shader based on:
28+// J. A. Bærentzen, S. Munk-Lund, M. Gjøl, and B. D. Larsen,
29+// “Two methods for antialiased wireframe drawing with hidden
30+// line removal,” in Proceedings of the Spring Conference in
31+// Computer Graphics, 2008.
32+//
33+// We are not using geometry shaders, though, as they are not
34+// available in GLES 2.0.
35+
36+attribute vec3 position;
37+// Coordinates of the triangle vertices this vertex belongs to
38+attribute vec3 tvertex0;
39+attribute vec3 tvertex1;
40+attribute vec3 tvertex2;
41+
42+uniform vec2 Viewport;
43+uniform mat4 ModelViewProjectionMatrix;
44+
45+varying vec4 dist;
46+
47+void main(void)
48+{
49+ // Get the clip coordinates of all vertices
50+ vec4 pos = ModelViewProjectionMatrix * vec4(position, 1.0);
51+ vec4 pos0 = ModelViewProjectionMatrix * vec4(tvertex0, 1.0);
52+ vec4 pos1 = ModelViewProjectionMatrix * vec4(tvertex1, 1.0);
53+ vec4 pos2 = ModelViewProjectionMatrix * vec4(tvertex2, 1.0);
54+
55+ // Get the screen coordinates of all vertices
56+ vec3 p = vec3(0.5 * Viewport * (pos.xy / pos.w), 0.0);
57+ vec3 p0 = vec3(0.5 * Viewport * (pos0.xy / pos0.w), 0.0);
58+ vec3 p1 = vec3(0.5 * Viewport * (pos1.xy / pos1.w), 0.0);
59+ vec3 p2 = vec3(0.5 * Viewport * (pos2.xy / pos2.w), 0.0);
60+
61+ // Get the vectors representing the edges of the current
62+ // triangle primitive. 'vN' is the edge opposite vertex N.
63+ vec3 v0 = p2 - p1;
64+ vec3 v1 = p2 - p0;
65+ vec3 v2 = p1 - p0;
66+
67+ // Calculate the distance of the current vertex from all
68+ // the triangle edges. The distance of point p from line
69+ // v is length(cross(p - p1, v)) / length(v), where
70+ // p1 is any of the two edge points of v.
71+ float d0 = length(cross(p - p1, v0)) / length(v0);
72+ float d1 = length(cross(p - p2, v1)) / length(v1);
73+ float d2 = length(cross(p - p0, v2)) / length(v2);
74+
75+ // OpenGL(ES) performs perspective-correct interpolation
76+ // (it divides by .w) but we want linear interpolation. To
77+ // work around this, we premultiply by pos.w here and then
78+ // multiple with the inverse (stored in dist.w) in the fragment
79+ // shader to undo this operation.
80+ dist = vec4(pos.w * d0, pos.w * d1, pos.w * d2, 1.0 / pos.w);
81+
82+ gl_Position = pos;
83+}
84
85=== modified file 'src/android.cpp'
86--- src/android.cpp 2011-09-16 08:29:57 +0000
87+++ src/android.cpp 2011-10-10 16:32:12 +0000
88@@ -45,6 +45,9 @@
89 "effect2d:kernel=1,1,1,1,1;1,1,1,1,1;1,1,1,1,1;",
90 "pulsar:quads=5:texture=false:light=false",
91 "desktop:windows=4:effect=blur:blur-radius=5:passes=1:separable=true",
92+ "buffer:update-fraction=0.5:update-dispersion=0.9:columns=200:update-method=map:interleave=false",
93+ "buffer:update-fraction=0.5:update-dispersion=0.9:columns=200:update-method=subdata:interleave=false",
94+ "buffer:update-fraction=0.5:update-dispersion=0.9:columns=200:update-method=map:interleave=true",
95 "conditionals:vertex-steps=0:fragment-steps=0",
96 "conditionals:vertex-steps=0:fragment-steps=5",
97 "conditionals:vertex-steps=5:fragment-steps=0",
98@@ -87,6 +90,7 @@
99 Benchmark::register_scene(*new SceneEffect2D(*g_canvas));
100 Benchmark::register_scene(*new ScenePulsar(*g_canvas));
101 Benchmark::register_scene(*new SceneDesktop(*g_canvas));
102+ Benchmark::register_scene(*new SceneBuffer(*g_canvas));
103
104 add_default_benchmarks(g_benchmarks);
105
106
107=== modified file 'src/canvas-android.cpp'
108--- src/canvas-android.cpp 2011-09-09 11:24:17 +0000
109+++ src/canvas-android.cpp 2011-10-10 16:32:12 +0000
110@@ -22,6 +22,7 @@
111 #include "canvas-android.h"
112 #include "log.h"
113 #include "options.h"
114+#include "gl-headers.h"
115 #include <EGL/egl.h>
116
117 #include <fstream>
118@@ -35,6 +36,8 @@
119 if (!eglSwapInterval(eglGetCurrentDisplay(), 0))
120 Log::info("** Failed to set swap interval. Results may be bounded above by refresh rate.\n");
121
122+ init_gl_extensions();
123+
124 glEnable(GL_DEPTH_TEST);
125 glDepthFunc(GL_LEQUAL);
126 glEnable(GL_CULL_FACE);
127@@ -120,3 +123,24 @@
128 1.0, 1024.0);
129 }
130
131+void
132+CanvasAndroid::init_gl_extensions()
133+{
134+ /*
135+ * Parse the extensions we care about from the extension string.
136+ * Don't even bother to get function pointers until we know the
137+ * extension is present.
138+ */
139+ std::string extString;
140+ const char* exts = reinterpret_cast<const char*>(glGetString(GL_EXTENSIONS));
141+ if (exts) {
142+ extString = exts;
143+ }
144+
145+ if (extString.find("GL_OES_mapbuffer") != std::string::npos) {
146+ GLExtensions::MapBuffer =
147+ reinterpret_cast<PFNGLMAPBUFFEROESPROC>(eglGetProcAddress("glMapBufferOES"));
148+ GLExtensions::UnmapBuffer =
149+ reinterpret_cast<PFNGLUNMAPBUFFEROESPROC>(eglGetProcAddress("glUnmapBufferOES"));
150+ }
151+}
152
153=== modified file 'src/canvas-android.h'
154--- src/canvas-android.h 2011-08-10 18:00:03 +0000
155+++ src/canvas-android.h 2011-10-10 16:32:12 +0000
156@@ -40,6 +40,9 @@
157 void write_to_file(std::string &filename);
158 bool should_quit();
159 void resize(int width, int height);
160+
161+private:
162+ void init_gl_extensions();
163 };
164
165 #endif
166
167=== modified file 'src/canvas-x11-egl.cpp'
168--- src/canvas-x11-egl.cpp 2011-06-30 12:24:25 +0000
169+++ src/canvas-x11-egl.cpp 2011-10-10 16:32:12 +0000
170@@ -194,6 +194,33 @@
171 return true;
172 }
173
174+void
175+CanvasX11EGL::init_gl_extensions()
176+{
177+#if USE_GLESv2
178+ /*
179+ * Parse the extensions we care about from the extension string.
180+ * Don't even bother to get function pointers until we know the
181+ * extension is present.
182+ */
183+ std::string extString;
184+ const char* exts = reinterpret_cast<const char*>(glGetString(GL_EXTENSIONS));
185+ if (exts) {
186+ extString = exts;
187+ }
188+
189+ if (extString.find("GL_OES_mapbuffer") != std::string::npos) {
190+ GLExtensions::MapBuffer =
191+ reinterpret_cast<PFNGLMAPBUFFEROESPROC>(eglGetProcAddress("glMapBufferOES"));
192+ GLExtensions::UnmapBuffer =
193+ reinterpret_cast<PFNGLUNMAPBUFFEROESPROC>(eglGetProcAddress("glUnmapBufferOES"));
194+ }
195+#elif USE_GL
196+ GLExtensions::MapBuffer = glMapBuffer;
197+ GLExtensions::UnmapBuffer = glUnmapBuffer;
198+#endif
199+}
200+
201 bool
202 CanvasX11EGL::make_current()
203 {
204@@ -211,5 +238,7 @@
205 if (!eglSwapInterval(egl_display_, 0))
206 Log::info("** Failed to set swap interval. Results may be bounded above by refresh rate.\n");
207
208+ init_gl_extensions();
209+
210 return true;
211 }
212
213=== modified file 'src/canvas-x11-egl.h'
214--- src/canvas-x11-egl.h 2011-06-30 11:45:52 +0000
215+++ src/canvas-x11-egl.h 2011-10-10 16:32:12 +0000
216@@ -44,6 +44,7 @@
217 bool ensure_egl_display();
218 bool ensure_egl_config();
219 bool ensure_egl_surface();
220+ void init_gl_extensions();
221
222 EGLDisplay egl_display_;
223 EGLSurface egl_surface_;
224
225=== modified file 'src/canvas-x11-glx.cpp'
226--- src/canvas-x11-glx.cpp 2011-06-30 12:24:25 +0000
227+++ src/canvas-x11-glx.cpp 2011-10-10 16:32:12 +0000
228@@ -187,6 +187,13 @@
229 return true;
230 }
231
232+void
233+CanvasX11GLX::init_gl_extensions()
234+{
235+ GLExtensions::MapBuffer = glMapBuffer;
236+ GLExtensions::UnmapBuffer = glUnmapBuffer;
237+}
238+
239 bool
240 CanvasX11GLX::ensure_glx_context()
241 {
242@@ -203,6 +210,8 @@
243 return false;
244 }
245
246+ init_gl_extensions();
247+
248 return true;
249 }
250
251
252=== modified file 'src/canvas-x11-glx.h'
253--- src/canvas-x11-glx.h 2011-06-30 12:17:09 +0000
254+++ src/canvas-x11-glx.h 2011-10-10 16:32:12 +0000
255@@ -45,6 +45,7 @@
256 void init_extensions();
257 bool ensure_glx_fbconfig();
258 bool ensure_glx_context();
259+ void init_gl_extensions();
260
261 GLXFBConfig glx_fbconfig_;
262 GLXContext glx_context_;
263
264=== added file 'src/gl-headers.cpp'
265--- src/gl-headers.cpp 1970-01-01 00:00:00 +0000
266+++ src/gl-headers.cpp 2011-10-10 16:32:12 +0000
267@@ -0,0 +1,26 @@
268+/*
269+ * Copyright © 2010-2011 Linaro Limited
270+ *
271+ * This file is part of the glmark2 OpenGL (ES) 2.0 benchmark.
272+ *
273+ * glmark2 is free software: you can redistribute it and/or modify it under the
274+ * terms of the GNU General Public License as published by the Free Software
275+ * Foundation, either version 3 of the License, or (at your option) any later
276+ * version.
277+ *
278+ * glmark2 is distributed in the hope that it will be useful, but WITHOUT ANY
279+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
280+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
281+ * details.
282+ *
283+ * You should have received a copy of the GNU General Public License along with
284+ * glmark2. If not, see <http://www.gnu.org/licenses/>.
285+ *
286+ * Authors:
287+ * Alexandros Frantzis (glmark2)
288+ */
289+#include "gl-headers.h"
290+
291+void* (*GLExtensions::MapBuffer) (GLenum target, GLenum access) = 0;
292+GLboolean (*GLExtensions::UnmapBuffer) (GLenum target) = 0;
293+
294
295=== modified file 'src/gl-headers.h'
296--- src/gl-headers.h 2011-06-30 13:33:37 +0000
297+++ src/gl-headers.h 2011-10-10 16:32:12 +0000
298@@ -22,13 +22,26 @@
299 #ifndef GLMARK2_GL_HEADERS_H_
300 #define GLMARK2_GL_HEADERS_H_
301
302+#define GL_GLEXT_PROTOTYPES
303+
304 #if USE_GL
305-#define GL_GLEXT_PROTOTYPES
306 #include <GL/gl.h>
307 #include <GL/glext.h>
308 #elif USE_GLESv2
309 #include <GLES2/gl2.h>
310 #include <GLES2/gl2ext.h>
311-#endif
312+#ifndef GL_WRITE_ONLY
313+#define GL_WRITE_ONLY GL_WRITE_ONLY_OES
314+#endif
315+#endif
316+
317+/**
318+ * Struct that holds pointers to functions that belong to extensions
319+ * in either GL2.0 or GLES2.0.
320+ */
321+struct GLExtensions {
322+ static void* (*MapBuffer) (GLenum target, GLenum access);
323+ static GLboolean (*UnmapBuffer) (GLenum target);
324+};
325
326 #endif
327
328=== modified file 'src/main.cpp'
329--- src/main.cpp 2011-09-20 12:52:21 +0000
330+++ src/main.cpp 2011-10-10 16:32:12 +0000
331@@ -56,6 +56,9 @@
332 "effect2d:kernel=1,1,1,1,1;1,1,1,1,1;1,1,1,1,1;",
333 "pulsar:quads=5:texture=false:light=false",
334 "desktop:windows=4:effect=blur:blur-radius=5:passes=1:separable=true",
335+ "buffer:update-fraction=0.5:update-dispersion=0.9:columns=200:update-method=map:interleave=false",
336+ "buffer:update-fraction=0.5:update-dispersion=0.9:columns=200:update-method=subdata:interleave=false",
337+ "buffer:update-fraction=0.5:update-dispersion=0.9:columns=200:update-method=map:interleave=true",
338 "conditionals:vertex-steps=0:fragment-steps=0",
339 "conditionals:vertex-steps=0:fragment-steps=5",
340 "conditionals:vertex-steps=5:fragment-steps=0",
341@@ -124,6 +127,7 @@
342 scenes.push_back(new SceneEffect2D(canvas));
343 scenes.push_back(new ScenePulsar(canvas));
344 scenes.push_back(new SceneDesktop(canvas));
345+ scenes.push_back(new SceneBuffer(canvas));
346
347 for (vector<Scene*>::const_iterator iter = scenes.begin();
348 iter != scenes.end();
349
350=== modified file 'src/mesh.cpp'
351--- src/mesh.cpp 2011-07-04 10:33:31 +0000
352+++ src/mesh.cpp 2011-10-10 16:32:12 +0000
353@@ -23,10 +23,12 @@
354 */
355 #include "mesh.h"
356 #include "log.h"
357+#include "gl-headers.h"
358
359
360 Mesh::Mesh() :
361- vertex_size_(0)
362+ vertex_size_(0), interleave_(false), vbo_update_method_(VBOUpdateMethodMap),
363+ vbo_usage_(VBOUsageStatic)
364 {
365 }
366
367@@ -165,6 +167,63 @@
368 vertices_.push_back(std::vector<float>(vertex_size_));
369 }
370
371+/**
372+ * Gets the mesh vertices.
373+ *
374+ * You should use the ::set_attrib() method to manipulate
375+ * the vertex data.
376+ *
377+ * You shouldn't resize the vector (change the number of vertices)
378+ * manually. Use ::next_vertex() instead.
379+ */
380+std::vector<std::vector<float> >&
381+Mesh::vertices()
382+{
383+ return vertices_;
384+}
385+
386+/**
387+ * Sets the VBO update method.
388+ *
389+ * The default value is VBOUpdateMethodMap.
390+ */
391+void
392+Mesh::vbo_update_method(Mesh::VBOUpdateMethod method)
393+{
394+ vbo_update_method_ = method;
395+}
396+
397+/**
398+ * Sets the VBO usage hint.
399+ *
400+ * The usage hint takes effect in the next call to ::build_vbo().
401+ *
402+ * The default value is VBOUsageStatic.
403+ */
404+void
405+Mesh::vbo_usage(Mesh::VBOUsage usage)
406+{
407+ vbo_usage_ = usage;
408+}
409+
410+/**
411+ * Sets the vertex attribute interleaving mode.
412+ *
413+ * If true the vertex attributes are going to be interleaved in a single
414+ * buffer. Otherwise they will be separated into different buffers (one
415+ * per attribute).
416+ *
417+ * Interleaving mode takes effect in the next call to ::build_array() or
418+ * ::build_vbo().
419+ *
420+ * @param interleave whether to interleave
421+ */
422+void
423+Mesh::interleave(bool interleave)
424+{
425+ interleave_ = interleave;
426+}
427+
428 void
429 Mesh::reset()
430 {
431@@ -180,11 +239,11 @@
432 }
433
434 void
435-Mesh::build_array(bool interleaved)
436+Mesh::build_array()
437 {
438 int nvertices = vertices_.size();
439
440- if (!interleaved) {
441+ if (!interleave_) {
442 /* Create an array for each attribute */
443 for (std::vector<std::pair<int, int> >::const_iterator ai = vertex_format_.begin();
444 ai != vertex_format_.end();
445@@ -229,16 +288,24 @@
446 }
447
448 void
449-Mesh::build_vbo(bool interleave)
450+Mesh::build_vbo()
451 {
452 delete_array();
453- build_array(interleave);
454+ build_array();
455
456 int nvertices = vertices_.size();
457
458 attrib_data_ptr_.clear();
459
460- if (!interleave) {
461+ GLenum buffer_usage;
462+ if (vbo_usage_ == Mesh::VBOUsageStream)
463+ buffer_usage = GL_STREAM_DRAW;
464+ if (vbo_usage_ == Mesh::VBOUsageDynamic)
465+ buffer_usage = GL_DYNAMIC_DRAW;
466+ else /* if (vbo_usage_ == Mesh::VBOUsageStatic) */
467+ buffer_usage = GL_STATIC_DRAW;
468+
469+ if (!interleave_) {
470 /* Create a vbo for each attribute */
471 for (std::vector<std::pair<int, int> >::const_iterator ai = vertex_format_.begin();
472 ai != vertex_format_.end();
473@@ -250,7 +317,7 @@
474 glGenBuffers(1, &vbo);
475 glBindBuffer(GL_ARRAY_BUFFER, vbo);
476 glBufferData(GL_ARRAY_BUFFER, nvertices * ai->first * sizeof(float),
477- data, GL_STATIC_DRAW);
478+ data, buffer_usage);
479
480 vbos_.push_back(vbo);
481 attrib_data_ptr_.push_back(0);
482@@ -279,6 +346,137 @@
483 delete_array();
484 }
485
486+/**
487+ * Updates ranges of a single vertex array.
488+ *
489+ * @param ranges the ranges of vertices to update
490+ * @param n the index of the vertex array to update
491+ * @param nfloats how many floats to update for each vertex
492+ * @param offset the offset (in floats) in the vertex data to start reading from
493+ */
494+void
495+Mesh::update_single_array(const std::vector<std::pair<size_t, size_t> >& ranges,
496+ size_t n, size_t nfloats, size_t offset)
497+{
498+ float *array(vertex_arrays_[n]);
499+
500+ /* Update supplied ranges */
501+ for (std::vector<std::pair<size_t, size_t> >::const_iterator ri = ranges.begin();
502+ ri != ranges.end();
503+ ri++)
504+ {
505+ /* Update the current range from the vertex data */
506+ float *dest(array + nfloats * ri->first);
507+ for (size_t n = ri->first; n <= ri->second; n++) {
508+ for (size_t i = 0; i < nfloats; i++)
509+ *dest++ = vertices_[n][offset + i];
510+ }
511+
512+ }
513+}
514+
515+/**
516+ * Updates ranges of the vertex arrays.
517+ *
518+ * @param ranges the ranges of vertices to update
519+ */
520+void
521+Mesh::update_array(const std::vector<std::pair<size_t, size_t> >& ranges)
522+{
523+ /* If we don't have arrays to update, create them */
524+ if (vertex_arrays_.empty()) {
525+ build_array();
526+ return;
527+ }
528+
529+ if (!interleave_) {
530+ for (size_t i = 0; i < vertex_arrays_.size(); i++) {
531+ update_single_array(ranges, i, vertex_format_[i].first,
532+ vertex_format_[i].second);
533+ }
534+ }
535+ else {
536+ update_single_array(ranges, 0, vertex_size_, 0);
537+ }
538+
539+}
540+
541+
542+/**
543+ * Updates ranges of a single VBO.
544+ *
545+ * This method use either glMapBuffer or glBufferSubData to perform
546+ * the update. The used method can be set with ::vbo_update_method().
547+ *
548+ * @param ranges the ranges of vertices to update
549+ * @param n the index of the vbo to update
550+ * @param nfloats how many floats to update for each vertex
551+ */
552+void
553+Mesh::update_single_vbo(const std::vector<std::pair<size_t, size_t> >& ranges,
554+ size_t n, size_t nfloats)
555+{
556+ float *src_start(vertex_arrays_[n]);
557+ float *dest_start(0);
558+
559+ glBindBuffer(GL_ARRAY_BUFFER, vbos_[n]);
560+
561+ if (vbo_update_method_ == VBOUpdateMethodMap) {
562+ dest_start = reinterpret_cast<float *>(
563+ GLExtensions::MapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY)
564+ );
565+ }
566+
567+ /* Update supplied ranges */
568+ for (std::vector<std::pair<size_t, size_t> >::const_iterator iter = ranges.begin();
569+ iter != ranges.end();
570+ iter++)
571+ {
572+ float *src(src_start + nfloats * iter->first);
573+ float *src_end(src_start + nfloats * (iter->second + 1));
574+
575+ if (vbo_update_method_ == VBOUpdateMethodMap) {
576+ float *dest(dest_start + nfloats * iter->first);
577+ std::copy(src, src_end, dest);
578+ }
579+ else if (vbo_update_method_ == VBOUpdateMethodSubData) {
580+ glBufferSubData(GL_ARRAY_BUFFER, nfloats * iter->first * sizeof(float),
581+ (src_end - src) * sizeof(float), src);
582+ }
583+ }
584+
585+ if (vbo_update_method_ == VBOUpdateMethodMap)
586+ GLExtensions::UnmapBuffer(GL_ARRAY_BUFFER);
587+}
588+
589+/**
590+ * Updates ranges of the VBOs.
591+ *
592+ * @param ranges the ranges of vertices to update
593+ */
594+void
595+Mesh::update_vbo(const std::vector<std::pair<size_t, size_t> >& ranges)
596+{
597+ /* If we don't have VBOs to update, create them */
598+ if (vbos_.empty()) {
599+ build_vbo();
600+ return;
601+ }
602+
603+ update_array(ranges);
604+
605+ if (!interleave_) {
606+ for (size_t i = 0; i < vbos_.size(); i++)
607+ update_single_vbo(ranges, i, vertex_format_[i].first);
608+ }
609+ else {
610+ update_single_vbo(ranges, 0, vertex_size_);
611+ }
612+
613+ glBindBuffer(GL_ARRAY_BUFFER, 0);
614+}
615+
616+
617 void
618 Mesh::delete_array()
619 {
620@@ -351,25 +549,27 @@
621 LibMatrix::vec3 c(a.x() + side_width, a.y(), 0);
622 LibMatrix::vec3 d(a.x() + side_width, a.y() - side_height, 0);
623
624- std::vector<float> ul(vertex_size_);
625- std::vector<float> ur(vertex_size_);
626- std::vector<float> ll(vertex_size_);
627- std::vector<float> lr(vertex_size_);
628-
629- set_attrib(0, a, &ul);
630- set_attrib(0, c, &ur);
631- set_attrib(0, b, &ll);
632- set_attrib(0, d, &lr);
633-
634- if (conf_func != 0)
635- conf_func(*this, i, j, n_x, n_y, ul, ur, lr, ll);
636-
637- next_vertex(); vertices_.back() = ul;
638- next_vertex(); vertices_.back() = ll;
639- next_vertex(); vertices_.back() = ur;
640- next_vertex(); vertices_.back() = ll;
641- next_vertex(); vertices_.back() = lr;
642- next_vertex(); vertices_.back() = ur;
643+ if (!conf_func) {
644+ std::vector<float> ul(vertex_size_);
645+ std::vector<float> ur(vertex_size_);
646+ std::vector<float> ll(vertex_size_);
647+ std::vector<float> lr(vertex_size_);
648+
649+ set_attrib(0, a, &ul);
650+ set_attrib(0, c, &ur);
651+ set_attrib(0, b, &ll);
652+ set_attrib(0, d, &lr);
653+
654+ next_vertex(); vertices_.back() = ul;
655+ next_vertex(); vertices_.back() = ll;
656+ next_vertex(); vertices_.back() = ur;
657+ next_vertex(); vertices_.back() = ll;
658+ next_vertex(); vertices_.back() = lr;
659+ next_vertex(); vertices_.back() = ur;
660+ }
661+ else {
662+ conf_func(*this, i, j, n_x, n_y, a, b, c, d);
663+ }
664 }
665 }
666 }
667
668=== modified file 'src/mesh.h'
669--- src/mesh.h 2011-07-04 10:33:31 +0000
670+++ src/mesh.h 2011-10-10 16:32:12 +0000
671@@ -44,10 +44,27 @@
672 void set_attrib(int pos, const LibMatrix::vec3 &v, std::vector<float> *vertex = 0);
673 void set_attrib(int pos, const LibMatrix::vec4 &v, std::vector<float> *vertex = 0);
674 void next_vertex();
675+ std::vector<std::vector<float> >& vertices();
676+
677+ enum VBOUpdateMethod {
678+ VBOUpdateMethodMap,
679+ VBOUpdateMethodSubData,
680+ };
681+ enum VBOUsage {
682+ VBOUsageStatic,
683+ VBOUsageStream,
684+ VBOUsageDynamic,
685+ };
686+
687+ void vbo_update_method(VBOUpdateMethod method);
688+ void vbo_usage(VBOUsage usage);
689+ void interleave(bool interleave);
690
691 void reset();
692- void build_array(bool interleaved = false);
693- void build_vbo(bool interleaved = false);
694+ void build_array();
695+ void build_vbo();
696+ void update_array(const std::vector<std::pair<size_t, size_t> >& ranges);
697+ void update_vbo(const std::vector<std::pair<size_t, size_t> >& ranges);
698 void delete_array();
699 void delete_vbo();
700
701@@ -55,10 +72,10 @@
702 void render_vbo();
703
704 typedef void (*grid_configuration_func)(Mesh &mesh, int x, int y, int n_x, int n_y,
705- std::vector<float> &upper_left,
706- std::vector<float> &upper_right,
707- std::vector<float> &lower_right,
708- std::vector<float> &lower_left);
709+ LibMatrix::vec3 &ul,
710+ LibMatrix::vec3 &ll,
711+ LibMatrix::vec3 &ur,
712+ LibMatrix::vec3 &lr);
713
714 void make_grid(int n_x, int n_y, double width, double height,
715 double spacing, grid_configuration_func conf_func = 0);
716@@ -66,6 +83,10 @@
717 private:
718 bool check_attrib(int pos, int size);
719 std::vector<float> &ensure_vertex();
720+ void update_single_array(const std::vector<std::pair<size_t, size_t> >& ranges,
721+ size_t n, size_t nfloats, size_t offset);
722+ void update_single_vbo(const std::vector<std::pair<size_t, size_t> >& ranges,
723+ size_t n, size_t nfloats);
724
725 std::vector<std::pair<int, int> > vertex_format_;
726 std::vector<int> attrib_locations_;
727@@ -77,6 +98,9 @@
728 std::vector<GLuint> vbos_;
729 std::vector<float *> attrib_data_ptr_;
730 int vertex_stride_;
731+ bool interleave_;
732+ VBOUpdateMethod vbo_update_method_;
733+ VBOUsage vbo_usage_;
734 };
735
736 #endif
737
738=== added file 'src/scene-buffer.cpp'
739--- src/scene-buffer.cpp 1970-01-01 00:00:00 +0000
740+++ src/scene-buffer.cpp 2011-10-10 16:32:12 +0000
741@@ -0,0 +1,457 @@
742+/*
743+ * Copyright © 2010-2011 Linaro Limited
744+ *
745+ * This file is part of the glmark2 OpenGL (ES) 2.0 benchmark.
746+ *
747+ * glmark2 is free software: you can redistribute it and/or modify it under the
748+ * terms of the GNU General Public License as published by the Free Software
749+ * Foundation, either version 3 of the License, or (at your option) any later
750+ * version.
751+ *
752+ * glmark2 is distributed in the hope that it will be useful, but WITHOUT ANY
753+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
754+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
755+ * details.
756+ *
757+ * You should have received a copy of the GNU General Public License along with
758+ * glmark2. If not, see <http://www.gnu.org/licenses/>.
759+ *
760+ * Authors:
761+ * Alexandros Frantzis (glmark2)
762+ */
763+#include "scene.h"
764+#include "log.h"
765+#include "mat.h"
766+#include "stack.h"
767+#include "shader-source.h"
768+#include "util.h"
769+#include "gl-headers.h"
770+#include <cmath>
771+
772+/***********************
773+ * Wave implementation *
774+ ***********************/
775+
776+/**
777+ * A callback used to set up the grid by the Wave class.
778+ * It is called for each "quad" of the grid.
779+ */
780+static void
781+wave_grid_conf(Mesh &mesh, int x, int y, int n_x, int n_y,
782+ LibMatrix::vec3 &ul,
783+ LibMatrix::vec3 &ll,
784+ LibMatrix::vec3 &ur,
785+ LibMatrix::vec3 &lr)
786+{
787+ (void)x; (void)y; (void)n_x; (void)n_y;
788+
789+ /*
790+ * Order matters here, so that Wave::vertex_length_index() can work.
791+ * Vertices of the triangles at index i that belong to length index i
792+ * are even, those that belong to i + 1 are odd.
793+ */
794+ const LibMatrix::vec3* t[] = {
795+ &ll, &ur, &ul, &ur, &ll, &lr
796+ };
797+
798+ for (int i = 0; i < 6; i++) {
799+ mesh.next_vertex();
800+ /*
801+ * Set the vertex position and the three vertex positions
802+ * of the triangle this vertex belongs to.
803+ */
804+ mesh.set_attrib(0, *t[i]);
805+ mesh.set_attrib(1, *t[3 * (i / 3)]);
806+ mesh.set_attrib(2, *t[3 * (i / 3) + 1]);
807+ mesh.set_attrib(3, *t[3 * (i / 3) + 2]);
808+ }
809+}
810+
811+/**
812+ * Renders a grid mesh modulated by a sine wave
813+ */
814+class WaveMesh
815+{
816+public:
817+ /**
818+ * Creates a wave mesh.
819+ *
820+ * @param length the total length of the grid (in model coordinates)
821+ * @param width the total width of the grid (in model coordinates)
822+ * @param nlength the number of length-wise grid subdivisions
823+ * @param nwidth the number of width-wise grid subdivisions
824+ * @param wavelength the wave length as a proportion of the length
825+ * @param duty_cycle the duty cycle ()
826+ */
827+ WaveMesh(double length, double width, size_t nlength, size_t nwidth,
828+ double wavelength, double duty_cycle) :
829+ length_(length), width_(width), nlength_(nlength), nwidth_(nwidth),
830+ wave_k_(2 * M_PI / (wavelength * length)),
831+ wave_period_(2.0 * M_PI / wave_k_),
832+ wave_full_period_(wave_period_ / duty_cycle),
833+ wave_velocity_(0.1 * length), displacement_(nlength + 1)
834+ {
835+ create_program();
836+ create_mesh();
837+ }
838+
839+
840+ ~WaveMesh() { reset(); }
841+
842+ /**
843+ * Updates the state of a wave mesh.
844+ *
845+ * @param elapsed the time elapsed since the beginning of the rendering
846+ */
847+ void update(double elapsed)
848+ {
849+ std::vector<std::vector<float> >& vertices(mesh_.vertices());
850+
851+ /* Figure out which length index ranges need update */
852+ std::vector<std::pair<size_t, size_t> > ranges;
853+
854+ for (size_t n = 0; n <= nlength_; n++) {
855+ double d(displacement(n, elapsed));
856+
857+ if (d != displacement_[n]) {
858+ if (ranges.size() > 0 && ranges.back().second == n - 1) {
859+ ranges.back().second = n;
860+ }
861+ else {
862+ ranges.push_back(
863+ std::pair<size_t, size_t>(n > 0 ? n - 1 : 0, n)
864+ );
865+ }
866+ }
867+
868+ displacement_[n] = d;
869+ }
870+
871+ /* Update the vertex data of the changed ranges */
872+ for (std::vector<std::pair<size_t, size_t> >::iterator iter = ranges.begin();
873+ iter != ranges.end();
874+ iter++)
875+ {
876+ /* First vertex of length index range */
877+ size_t vstart(iter->first * nwidth_ * 6 + (iter->first % 2));
878+ /*
879+ * First vertex not included in the range. We should also update all
880+ * vertices of triangles touching index i.
881+ */
882+ size_t vend((iter->second + (iter->second < nlength_)) * nwidth_ * 6);
883+
884+ for (size_t v = vstart; v < vend; v++) {
885+ size_t vt = 3 * (v / 3);
886+ vertices[v][0 * 3 + 2] = displacement_[vertex_length_index(v)];
887+ vertices[v][1 * 3 + 2] = displacement_[vertex_length_index(vt)];
888+ vertices[v][2 * 3 + 2] = displacement_[vertex_length_index(vt + 1)];
889+ vertices[v][3 * 3 + 2] = displacement_[vertex_length_index(vt + 2)];
890+ }
891+
892+ /* Update pair with actual vertex range */
893+ iter->first = vstart;
894+ iter->second = vend - 1;
895+ }
896+
897+ mesh_.update_vbo(ranges);
898+ }
899+
900+ Mesh& mesh() { return mesh_; }
901+ Program& program() { return program_; }
902+
903+ void reset()
904+ {
905+ program_.stop();
906+ program_.release();
907+ mesh_.reset();
908+ }
909+
910+private:
911+ Mesh mesh_;
912+ Program program_;
913+ double length_;
914+ double width_;
915+ size_t nlength_;
916+ size_t nwidth_;
917+ /* Wave parameters */
918+ double wave_k_;
919+ double wave_period_;
920+ double wave_full_period_;
921+ double wave_fill_;
922+ double wave_velocity_;
923+
924+ std::vector<double> displacement_;
925+
926+ /**
927+ * Calculates the length index of a vertex.
928+ */
929+ size_t vertex_length_index(size_t v)
930+ {
931+ return v / (6 * nwidth_) + (v % 2);
932+ }
933+
934+ /**
935+ * The sine wave function with duty-cycle.
936+ *
937+ * @param x the space coordinate
938+ *
939+ * @return the operation error code
940+ */
941+ double wave_func(double x)
942+ {
943+ double r(fmod(x, wave_full_period_));
944+ if (r < 0)
945+ r += wave_full_period_;
946+
947+ /*
948+ * Return either the sine value or 0.0, depending on the
949+ * wave duty cycle.
950+ */
951+ if (r > wave_period_)
952+ {
953+ return 0;
954+ }
955+ else
956+ {
957+ return 0.2 * std::sin(wave_k_ * r);
958+ }
959+ }
960+
961+ /**
962+ * Calculates the displacement of the wave.
963+ *
964+ * @param n the length index
965+ * @param elapsed the time elapsed since the beginning of the rendering
966+ *
967+ * @return the displacement at point n at time elapsed
968+ */
969+ double displacement(size_t n, double elapsed)
970+ {
971+ double x(n * length_ / nlength_);
972+
973+ return wave_func(x - wave_velocity_ * elapsed);
974+ }
975+
976+ /**
977+ * Creates the GL shader program.
978+ */
979+ void create_program()
980+ {
981+ /* Set up shaders */
982+ static const std::string vtx_shader_filename(
983+ GLMARK_DATA_PATH"/shaders/buffer-wireframe.vert");
984+ static const std::string frg_shader_filename(
985+ GLMARK_DATA_PATH"/shaders/buffer-wireframe.frag");
986+
987+ ShaderSource vtx_source(vtx_shader_filename);
988+ ShaderSource frg_source(frg_shader_filename);
989+
990+ if (!Scene::load_shaders_from_strings(program_, vtx_source.str(),
991+ frg_source.str()))
992+ {
993+ return;
994+ }
995+ }
996+
997+ /**
998+ * Creates the grid mesh.
999+ */
1000+ void create_mesh()
1001+ {
1002+ /*
1003+ * We need to pass the positions of all vertex of the triangle
1004+ * in order to draw the wireframe.
1005+ */
1006+ std::vector<int> vertex_format;
1007+ vertex_format.push_back(3); // Position of vertex
1008+ vertex_format.push_back(3); // Position of triangle vertex 0
1009+ vertex_format.push_back(3); // Position of triangle vertex 1
1010+ vertex_format.push_back(3); // Position of triangle vertex 2
1011+ mesh_.set_vertex_format(vertex_format);
1012+
1013+ std::vector<GLint> attrib_locations;
1014+ attrib_locations.push_back(program_["position"].location());
1015+ attrib_locations.push_back(program_["tvertex0"].location());
1016+ attrib_locations.push_back(program_["tvertex1"].location());
1017+ attrib_locations.push_back(program_["tvertex2"].location());
1018+ mesh_.set_attrib_locations(attrib_locations);
1019+
1020+ mesh_.make_grid(nlength_, nwidth_, length_, width_,
1021+ 0.0, wave_grid_conf);
1022+ }
1023+
1024+};
1025+
1026+/******************************
1027+ * SceneBuffer implementation *
1028+ ******************************/
1029+
1030+struct SceneBufferPrivate {
1031+ WaveMesh *wave;
1032+ ~SceneBufferPrivate() { delete wave; }
1033+};
1034+
1035+SceneBuffer::SceneBuffer(Canvas &pCanvas) :
1036+ Scene(pCanvas, "buffer")
1037+{
1038+ priv_ = new SceneBufferPrivate();
1039+ mOptions["interleave"] = Scene::Option("interleave", "false",
1040+ "Whether to interleave vertex attribute data [true,false]");
1041+ mOptions["update-method"] = Scene::Option("update-method", "map",
1042+ "[map,subdata]");
1043+ mOptions["update-fraction"] = Scene::Option("update-fraction", "1.0",
1044+ "The fraction of the mesh length that is updated at every iteration (0.0-1.0)");
1045+ mOptions["update-dispersion"] = Scene::Option("update-dispersion", "0.0",
1046+ "How dispersed the updates are [0.0 - 1.0]");
1047+ mOptions["columns"] = Scene::Option("columns", "100",
1048+ "The number of mesh subdivisions length-wise");
1049+ mOptions["rows"] = Scene::Option("rows", "20",
1050+ "The number of mesh subdisivisions width-wise");
1051+ mOptions["buffer-usage"] = Scene::Option("buffer-usage", "static",
1052+ "How the buffer will be used [static,stream,dynamic]");
1053+}
1054+
1055+SceneBuffer::~SceneBuffer()
1056+{
1057+ delete priv_;
1058+}
1059+
1060+int
1061+SceneBuffer::load()
1062+{
1063+ mRunning = false;
1064+
1065+ return 1;
1066+}
1067+
1068+void
1069+SceneBuffer::unload()
1070+{
1071+}
1072+
1073+void
1074+SceneBuffer::setup()
1075+{
1076+ using LibMatrix::vec3;
1077+
1078+ Scene::setup();
1079+
1080+ bool interleave = (mOptions["interleave"].value == "true");
1081+ Mesh::VBOUpdateMethod update_method;
1082+ Mesh::VBOUsage usage;
1083+ double update_fraction;
1084+ double update_dispersion;
1085+ size_t nlength;
1086+ size_t nwidth;
1087+
1088+ if (mOptions["update-method"].value == "map")
1089+ update_method = Mesh::VBOUpdateMethodMap;
1090+ else if (mOptions["update-method"].value == "subdata")
1091+ update_method = Mesh::VBOUpdateMethodSubData;
1092+ else
1093+ update_method = Mesh::VBOUpdateMethodMap;
1094+
1095+ if (mOptions["buffer-usage"].value == "static")
1096+ usage = Mesh::VBOUsageStatic;
1097+ else if (mOptions["buffer-usage"].value == "stream")
1098+ usage = Mesh::VBOUsageStream;
1099+ else
1100+ usage = Mesh::VBOUsageDynamic;
1101+
1102+ std::stringstream ss;
1103+ ss << mOptions["update-fraction"].value;
1104+ ss >> update_fraction;
1105+ ss.clear();
1106+ ss << mOptions["update-dispersion"].value;
1107+ ss >> update_dispersion;
1108+ ss.clear();
1109+ ss << mOptions["columns"].value;
1110+ ss >> nlength;
1111+ ss.clear();
1112+ ss << mOptions["rows"].value;
1113+ ss >> nwidth;
1114+
1115+ if (update_method == Mesh::VBOUpdateMethodMap &&
1116+ (GLExtensions::MapBuffer == 0 || GLExtensions::UnmapBuffer == 0))
1117+ {
1118+ Log::error("Requested MapBuffer VBO update method but GL_OES_mapbuffer"
1119+ "is not supported!");
1120+ return;
1121+ }
1122+
1123+ priv_->wave = new WaveMesh(5.0, 2.0, nlength, nwidth,
1124+ update_fraction * (1.0 - update_dispersion + 0.0001),
1125+ update_fraction);
1126+
1127+ priv_->wave->mesh().interleave(interleave);
1128+ priv_->wave->mesh().vbo_update_method(update_method);
1129+ priv_->wave->mesh().vbo_usage(usage);
1130+ priv_->wave->mesh().build_vbo();
1131+
1132+ priv_->wave->program().start();
1133+ priv_->wave->program()["Viewport"] = LibMatrix::vec2(mCanvas.width(), mCanvas.height());
1134+
1135+ glDisable(GL_CULL_FACE);
1136+
1137+ mCurrentFrame = 0;
1138+ mRunning = true;
1139+ mStartTime = Scene::get_timestamp_us() / 1000000.0;
1140+ mLastUpdateTime = mStartTime;
1141+}
1142+
1143+void
1144+SceneBuffer::teardown()
1145+{
1146+ delete priv_->wave;
1147+ priv_->wave = 0;
1148+
1149+ glEnable(GL_CULL_FACE);
1150+
1151+ Scene::teardown();
1152+}
1153+
1154+void
1155+SceneBuffer::update()
1156+{
1157+ double current_time = Scene::get_timestamp_us() / 1000000.0;
1158+ double elapsed_time = current_time - mStartTime;
1159+
1160+ mLastUpdateTime = current_time;
1161+
1162+ if (elapsed_time >= mDuration) {
1163+ mAverageFPS = mCurrentFrame / elapsed_time;
1164+ mRunning = false;
1165+ }
1166+
1167+ priv_->wave->update(elapsed_time);
1168+
1169+ mCurrentFrame++;
1170+}
1171+
1172+void
1173+SceneBuffer::draw()
1174+{
1175+ LibMatrix::Stack4 model_view;
1176+
1177+ // Load the ModelViewProjectionMatrix uniform in the shader
1178+ LibMatrix::mat4 model_view_proj(mCanvas.projection());
1179+ model_view.translate(0.0, 0.0, -4.0);
1180+ model_view.rotate(45.0, -1.0, -0.0, -0.0);
1181+ model_view_proj *= model_view.getCurrent();
1182+
1183+ priv_->wave->program()["ModelViewProjectionMatrix"] = model_view_proj;
1184+
1185+ // Load the NormalMatrix uniform in the shader. The NormalMatrix is the
1186+ // inverse transpose of the model view matrix.
1187+ LibMatrix::mat4 normal_matrix(model_view.getCurrent());
1188+ normal_matrix.inverse().transpose();
1189+ priv_->wave->program()["NormalMatrix"] = normal_matrix;
1190+
1191+ priv_->wave->mesh().render_vbo();
1192+}
1193+
1194+Scene::ValidationResult
1195+SceneBuffer::validate()
1196+{
1197+ return Scene::ValidationUnknown;
1198+}
1199
1200=== modified file 'src/scene-build.cpp'
1201--- src/scene-build.cpp 2011-09-21 09:52:37 +0000
1202+++ src/scene-build.cpp 2011-10-10 16:32:12 +0000
1203@@ -118,10 +118,13 @@
1204 mUseVbo = (mOptions["use-vbo"].value == "true");
1205 bool interleave = (mOptions["interleave"].value == "true");
1206
1207+ mMesh.vbo_update_method(Mesh::VBOUpdateMethodMap);
1208+ mMesh.interleave(interleave);
1209+
1210 if (mUseVbo)
1211- mMesh.build_vbo(interleave);
1212+ mMesh.build_vbo();
1213 else
1214- mMesh.build_array(interleave);
1215+ mMesh.build_array();
1216
1217 /* Calculate a projection matrix that is a good fit for the model */
1218 vec3 maxVec = model.maxVec();
1219@@ -194,10 +197,16 @@
1220 normal_matrix.inverse().transpose();
1221 mProgram["NormalMatrix"] = normal_matrix;
1222
1223+ std::vector<std::pair<size_t,size_t> > ranges;
1224+ ranges.push_back(std::pair<size_t,size_t>(2,
1225+ 0 * mMesh.vertices().size()/3 + 3));
1226+
1227 if (mUseVbo) {
1228+ mMesh.update_vbo(ranges);
1229 mMesh.render_vbo();
1230 }
1231 else {
1232+ mMesh.update_array(ranges);
1233 mMesh.render_array();
1234 }
1235 }
1236
1237=== modified file 'src/scene.h'
1238--- src/scene.h 2011-09-20 09:45:24 +0000
1239+++ src/scene.h 2011-10-10 16:32:12 +0000
1240@@ -353,4 +353,24 @@
1241 private:
1242 SceneDesktopPrivate *priv_;
1243 };
1244+
1245+struct SceneBufferPrivate;
1246+
1247+class SceneBuffer : public Scene
1248+{
1249+public:
1250+ SceneBuffer(Canvas &canvas);
1251+ int load();
1252+ void unload();
1253+ void setup();
1254+ void teardown();
1255+ void update();
1256+ void draw();
1257+ ValidationResult validate();
1258+
1259+ ~SceneBuffer();
1260+
1261+private:
1262+ SceneBufferPrivate *priv_;
1263+};
1264 #endif

Subscribers

People subscribed via source and target branches