source: git/src/gla-gl.cc @ 8e59e9b9

stereo-2025
Last change on this file since 8e59e9b9 was fd0e32a, checked in by Olly Betts <olly@…>, 4 months ago

Raise wxWidgets requirement to >= 3.2.0

Previously it was >= 3.0.0 but it is starting to become hard to
keep everything working with 3.0.0 and the time and effort seems
better directed to other things.

The last 3.0.x release was 3.0.5.1 released 2020-05-02.

wx3.2.0 was released 2022-07-06 and it seems everywhere with wxWidgets
packages upgraded to 3.2.x some time ago.

  • Property mode set to 100644
File size: 49.7 KB
Line 
1//
2//  gla-gl.cc
3//
4//  OpenGL implementation for the GLA abstraction layer.
5//
6//  Copyright (C) 2002-2003,2005 Mark R. Shinwell
7//  Copyright (C) 2003-2025 Olly Betts
8//
9//  This program is free software; you can redistribute it and/or modify
10//  it under the terms of the GNU General Public License as published by
11//  the Free Software Foundation; either version 2 of the License, or
12//  (at your option) any later version.
13//
14//  This program is distributed in the hope that it will be useful,
15//  but WITHOUT ANY WARRANTY; without even the implied warranty of
16//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17//  GNU General Public License for more details.
18//
19//  You should have received a copy of the GNU General Public License
20//  along with this program; if not, write to the Free Software
21//  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
22//
23
24#include <config.h>
25
26#include <wx/confbase.h>
27#include <wx/image.h>
28
29#include <algorithm>
30
31#include "aven.h"
32#include "gla.h"
33#include "gllogerror.h"
34#include "message.h"
35#include "useful.h"
36
37#ifdef HAVE_GL_GL_H
38# include <GL/gl.h>
39#elif defined HAVE_OPENGL_GL_H
40# include <OpenGL/gl.h>
41#endif
42
43#ifdef HAVE_GL_GLU_H
44# include <GL/glu.h>
45#elif defined HAVE_OPENGL_GLU_H
46# include <OpenGL/glu.h>
47#endif
48
49#ifdef HAVE_GL_GLEXT_H
50# include <GL/glext.h>
51#elif defined HAVE_OPENGL_GLEXT_H
52# include <OpenGL/glext.h>
53#endif
54
55#ifndef GL_POINT_SIZE_MAX
56#define GL_POINT_SIZE_MAX 0x8127
57#endif
58#ifndef GL_POINT_SPRITE
59#define GL_POINT_SPRITE 0x8861
60#endif
61#ifndef GL_COORD_REPLACE
62#define GL_COORD_REPLACE 0x8862
63#endif
64// GL_POINT_SIZE_RANGE is deprecated in OpenGL 1.2 and later, and replaced by
65// GL_SMOOTH_POINT_SIZE_RANGE.
66#ifndef GL_SMOOTH_POINT_SIZE_RANGE
67#define GL_SMOOTH_POINT_SIZE_RANGE GL_POINT_SIZE_RANGE
68#endif
69// GL_POINT_SIZE_GRANULARITY is deprecated in OpenGL 1.2 and later, and
70// replaced by GL_SMOOTH_POINT_SIZE_GRANULARITY.
71#ifndef GL_SMOOTH_POINT_SIZE_GRANULARITY
72#define GL_SMOOTH_POINT_SIZE_GRANULARITY GL_POINT_SIZE_GRANULARITY
73#endif
74// GL_ALIASED_POINT_SIZE_RANGE was added in OpenGL 1.2.
75#ifndef GL_ALIASED_POINT_SIZE_RANGE
76#define GL_ALIASED_POINT_SIZE_RANGE 0x846D
77#endif
78
79using namespace std;
80
81const int BLOB_DIAMETER = 5;
82
83#define BLOB_TEXTURE \
84            o, o, o, o, o, o, o, o,\
85            o, o, o, o, o, o, o, o,\
86            o, o, I, I, I, o, o, o,\
87            o, I, I, I, I, I, o, o,\
88            o, I, I, I, I, I, o, o,\
89            o, I, I, I, I, I, o, o,\
90            o, o, I, I, I, o, o, o,\
91            o, o, o, o, o, o, o, o
92
93#define CROSS_TEXTURE \
94            o, o, o, o, o, o, o, o,\
95            I, o, o, o, o, o, I, o,\
96            o, I, o, o, o, I, o, o,\
97            o, o, I, o, I, o, o, o,\
98            o, o, o, I, o, o, o, o,\
99            o, o, I, o, I, o, o, o,\
100            o, I, o, o, o, I, o, o,\
101            I, o, o, o, o, o, I, o
102
103// Declared in gllogerror.h.
104bool opengl_initialised = false;
105
106static bool double_buffered = false;
107
108static const int* wx_gl_attribs = NULL;
109
110bool
111GLACanvas::check_visual()
112{
113    static const int wx_gl_attribs_full[] = {
114        WX_GL_DOUBLEBUFFER,
115        WX_GL_RGBA,
116        WX_GL_DEPTH_SIZE, 16,
117        0
118    };
119
120    // Use a double-buffered visual if available, as it will give much smoother
121    // animation.
122    double_buffered = true;
123    wx_gl_attribs = wx_gl_attribs_full;
124    if (!IsDisplaySupported(wx_gl_attribs)) {
125        ++wx_gl_attribs;
126        if (!IsDisplaySupported(wx_gl_attribs)) {
127            return false;
128        }
129        double_buffered = false;
130    }
131    return true;
132}
133
134string GetGLSystemDescription()
135{
136    // If OpenGL isn't initialised we may get a SEGV from glGetString.
137    if (!opengl_initialised)
138        return "No OpenGL information available yet - try opening a file.";
139    const char *p = (const char*)glGetString(GL_VERSION);
140    if (!p)
141        return "Couldn't read OpenGL version!";
142
143    string info;
144    info += "OpenGL ";
145    info += p;
146    info += '\n';
147    info += (const char*)glGetString(GL_VENDOR);
148    info += '\n';
149    info += (const char*)glGetString(GL_RENDERER);
150#if !(wxUSE_GLCANVAS_EGL-0) && \
151    (defined __WXGTK__ || defined __WXX11__ || defined __WXMOTIF__)
152    info += string_format("\nGLX %0.1f\n", wxGLCanvas::GetGLXVersion() * 0.1);
153#else
154    info += '\n';
155#endif
156
157    GLint red, green, blue;
158    glGetIntegerv(GL_RED_BITS, &red);
159    glGetIntegerv(GL_GREEN_BITS, &green);
160    glGetIntegerv(GL_BLUE_BITS, &blue);
161    GLint max_texture_size;
162    glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max_texture_size);
163    GLint max_viewport[2];
164    glGetIntegerv(GL_MAX_VIEWPORT_DIMS, max_viewport);
165    GLdouble point_size_range[2];
166    glGetDoublev(GL_SMOOTH_POINT_SIZE_RANGE, point_size_range);
167    GLdouble point_size_granularity;
168    glGetDoublev(GL_SMOOTH_POINT_SIZE_GRANULARITY, &point_size_granularity);
169    info += string_format("R%dG%dB%d\n"
170             "Max Texture size: %dx%d\n"
171             "Max Viewport size: %dx%d\n"
172             "Smooth Point Size %.3f-%.3f (granularity %.3f)",
173             (int)red, (int)green, (int)blue,
174             (int)max_texture_size, (int)max_texture_size,
175             (int)max_viewport[0], (int)max_viewport[1],
176             point_size_range[0], point_size_range[1],
177             point_size_granularity);
178    glGetDoublev(GL_ALIASED_POINT_SIZE_RANGE, point_size_range);
179    if (glGetError() != GL_INVALID_ENUM) {
180        info += string_format("\nAliased point size %.3f-%.3f",
181                              point_size_range[0], point_size_range[1]);
182    }
183
184    info += "\nDouble buffered: ";
185    if (double_buffered)
186        info += "true";
187    else
188        info += "false";
189
190    const GLubyte* gl_extensions = glGetString(GL_EXTENSIONS);
191    if (*gl_extensions) {
192        info += '\n';
193        info += (const char*)gl_extensions;
194    }
195    return info;
196}
197
198static bool
199glpoint_sprite_works()
200{
201    // Point sprites provide an easy, fast way for us to draw crosses by
202    // texture mapping GL points.
203    //
204    // If we have OpenGL >= 2.0 then we definitely have GL_POINT_SPRITE.
205    // Otherwise see if we have the GL_ARB_point_sprite or GL_NV_point_sprite
206    // extensions.
207    //
208    // The symbolic constants GL_POINT_SPRITE, GL_POINT_SPRITE_ARB, and
209    // GL_POINT_SPRITE_NV all give the same number so it doesn't matter
210    // which we use.
211    static bool glpoint_sprite = false;
212    static bool checked = false;
213    if (!checked) {
214        float maxSize = 0.0f;
215        glGetFloatv(GL_POINT_SIZE_MAX, &maxSize);
216        if (maxSize >= 8) {
217            glpoint_sprite = (atoi((const char *)glGetString(GL_VERSION)) >= 2);
218            if (!glpoint_sprite) {
219                const char * p = (const char *)glGetString(GL_EXTENSIONS);
220                while (true) {
221                    size_t l = 0;
222                    if (memcmp(p, "GL_ARB_point_sprite", 19) == 0) {
223                        l = 19;
224                    } else if (memcmp(p, "GL_NV_point_sprite", 18) == 0) {
225                        l = 18;
226                    }
227                    if (l) {
228                        p += l;
229                        if (*p == '\0' || *p == ' ') {
230                            glpoint_sprite = true;
231                            break;
232                        }
233                    }
234                    p = strchr(p + 1, ' ');
235                    if (!p) break;
236                    ++p;
237                }
238            }
239        }
240        checked = true;
241    }
242    return glpoint_sprite;
243}
244
245void
246log_gl_error(const wxChar * str, GLenum error_code)
247{
248    wxString msg;
249    switch (error_code) {
250        case GL_INVALID_ENUM:
251            msg = "Invalid OpenGL enumerated value";
252            break;
253        case GL_INVALID_VALUE:
254            msg = "Invalid OpenGL numeric argument value";
255            break;
256        case GL_INVALID_OPERATION:
257            msg = "Invalid OpenGL operation";
258            break;
259        case GL_INVALID_FRAMEBUFFER_OPERATION:
260            msg = "Invalid OpenGL framebuffer operation";
261            break;
262        case GL_OUT_OF_MEMORY:
263            msg = wmsg(/*Out of memory*/389);
264            break;
265        case GL_STACK_UNDERFLOW:
266            msg = "OpenGL stack underflow";
267            break;
268        case GL_STACK_OVERFLOW:
269            msg = "OpenGL stack overflow";
270            break;
271        default:
272            msg.Format("Unknown OpenGL error code: %d", int(error_code));
273            break;
274    }
275    wxLogError(str, msg);
276}
277
278//
279//  GLAPen
280//
281
282void GLAPen::SetColour(double red, double green, double blue)
283{
284    components[0] = red;
285    components[1] = green;
286    components[2] = blue;
287}
288
289double GLAPen::GetRed() const
290{
291    return components[0];
292}
293
294double GLAPen::GetGreen() const
295{
296    return components[1];
297}
298
299double GLAPen::GetBlue() const
300{
301    return components[2];
302}
303
304void GLAPen::Interpolate(const GLAPen& pen, double how_far)
305{
306    components[0] += how_far * (pen.GetRed() - components[0]);
307    components[1] += how_far * (pen.GetGreen() - components[1]);
308    components[2] += how_far * (pen.GetBlue() - components[2]);
309}
310
311struct ColourTriple {
312    // RGB triple: values are from 0-255 inclusive for each component.
313    unsigned char r, g, b;
314};
315
316// Order must match that in enum gla_colour[] in gla.h.
317static const ColourTriple COLOURS[] = {
318    { 0, 0, 0 },       // black
319    { 100, 100, 100 }, // grey
320    { 180, 180, 180 }, // light grey
321    { 140, 140, 140 }, // light grey 2
322    { 90, 90, 90 },    // dark grey
323    { 255, 255, 255 }, // white
324    { 0, 100, 255},    // turquoise
325    { 0, 255, 40 },    // green
326    { 150, 205, 224 }, // indicator 1
327    { 114, 149, 160 }, // indicator 2
328    { 255, 255, 0 },   // yellow
329    { 255, 0, 0 },     // red
330    { 40, 40, 255 },   // blue
331    { 255, 0, 255 },   // magenta
332};
333
334bool GLAList::need_to_generate() {
335    // Bail out if the list is already cached, or can't usefully be cached.
336    if (flags & (GLACanvas::CACHED|GLACanvas::NEVER_CACHE))
337        return false;
338
339    // Create a new OpenGL list to hold this sequence of drawing
340    // operations.
341    if (gl_list == 0) {
342        gl_list = glGenLists(1);
343        CHECK_GL_ERROR("GLAList::need_to_generate", "glGenLists");
344#ifdef GLA_DEBUG
345        printf("glGenLists(1) returned %u\n", (unsigned)gl_list);
346#endif
347        if (gl_list == 0) {
348            // If we can't create a list for any reason, fall back to just
349            // drawing directly, and flag the list as NEVER_CACHE as there's
350            // unlikely to be much point calling glGenLists() again.
351            flags = GLACanvas::NEVER_CACHE;
352            return false;
353        }
354
355        // We should have 256 lists for font drawing and a dozen or so for 2D
356        // and 3D lists.  So something is amiss if we've generated 1000 lists,
357        // probably a infinite loop in the lazy list mechanism.
358        assert(gl_list < 1000);
359    }
360    // https://www.opengl.org/resources/faq/technical/displaylist.htm advises:
361    //
362    // "Stay away from GL_COMPILE_AND_EXECUTE mode. Instead, create the
363    // list using GL_COMPILE mode, then execute it with glCallList()."
364    glNewList(gl_list, GL_COMPILE);
365    CHECK_GL_ERROR("GLAList::need_to_generate", "glNewList");
366    return true;
367}
368
369void GLAList::finalise(unsigned int list_flags)
370{
371    glEndList();
372    CHECK_GL_ERROR("GLAList::finalise", "glEndList");
373    if (list_flags & GLACanvas::NEVER_CACHE) {
374        glDeleteLists(gl_list, 1);
375        CHECK_GL_ERROR("GLAList::finalise", "glDeleteLists");
376        gl_list = 0;
377        flags = GLACanvas::NEVER_CACHE;
378    } else {
379        flags = list_flags | GLACanvas::CACHED;
380    }
381}
382
383bool GLAList::DrawList() const {
384    if ((flags & GLACanvas::CACHED) == 0)
385        return false;
386    glCallList(gl_list);
387    CHECK_GL_ERROR("GLAList::DrawList", "glCallList");
388    return true;
389}
390
391//
392//  GLACanvas
393//
394
395BEGIN_EVENT_TABLE(GLACanvas, wxGLCanvas)
396    EVT_SIZE(GLACanvas::OnSize)
397#ifdef wxHAS_DPI_INDEPENDENT_PIXELS
398    EVT_MOVE(GLACanvas::OnMove)
399#endif
400END_EVENT_TABLE()
401
402// Pass wxWANTS_CHARS so that the window gets cursor keys on MS Windows.
403GLACanvas::GLACanvas(wxWindow* parent, int id)
404    : wxGLCanvas(parent, id, wx_gl_attribs, wxDefaultPosition,
405                 wxDefaultSize, wxWANTS_CHARS),
406      ctx(this)
407{
408}
409
410GLACanvas::~GLACanvas()
411{
412    // Destructor.
413
414    if (m_Quadric) {
415        gluDeleteQuadric(m_Quadric);
416        CHECK_GL_ERROR("~GLACanvas", "gluDeleteQuadric");
417    }
418}
419
420void GLACanvas::FirstShow()
421{
422#ifdef wxHAS_DPI_INDEPENDENT_PIXELS
423    content_scale_factor = wxGLCanvas::GetContentScaleFactor();
424#endif
425
426    // Update our record of the client area size and centre.
427    GetClientSize(&x_size, &y_size);
428    x_size *= content_scale_factor;
429    y_size *= content_scale_factor;
430    if (x_size < 1) x_size = 1;
431    if (y_size < 1) y_size = 1;
432
433    ctx.SetCurrent(*this);
434    opengl_initialised = true;
435
436    // Set the background colour of the canvas to black.
437    glClearColor(0.0, 0.0, 0.0, 1.0);
438    CHECK_GL_ERROR("FirstShow", "glClearColor");
439
440    // Set viewport.
441    glViewport(0, 0, x_size, y_size);
442    CHECK_GL_ERROR("FirstShow", "glViewport");
443
444    save_hints = false;
445
446    vendor = wxString((const char *)glGetString(GL_VENDOR), wxConvUTF8);
447    renderer = wxString((const char *)glGetString(GL_RENDERER), wxConvUTF8);
448    {
449        wxConfigBase * cfg = wxConfigBase::Get();
450        wxString s;
451        if (cfg->Read(wxT("opengl_survex"), &s, wxString()) && s == wxT(VERSION) &&
452            cfg->Read(wxT("opengl_vendor"), &s, wxString()) && s == vendor &&
453            cfg->Read(wxT("opengl_renderer"), &s, wxString()) && s == renderer) {
454            // The survex version, vendor and renderer are the same as those
455            // we cached hints for, so use those hints.
456            int v;
457            if (cfg->Read(wxT("blob_method"), &v, 0) &&
458                (v == SPRITE || v == POINT || v == LINES)) {
459                // How to draw blobs.
460                blob_method = v;
461            }
462            if (cfg->Read(wxT("cross_method"), &v, 0) &&
463                (v == SPRITE || v == LINES)) {
464                // How to draw crosses.
465                cross_method = v;
466            }
467        }
468    }
469
470    if (m_Quadric) return;
471    // One time initialisation follows.
472
473    m_Quadric = gluNewQuadric();
474    CHECK_GL_ERROR("FirstShow", "gluNewQuadric");
475    if (!m_Quadric) {
476        abort(); // FIXME need to cope somehow
477    }
478
479    glShadeModel(GL_FLAT);
480    CHECK_GL_ERROR("FirstShow", "glShadeModel");
481    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); // So text works.
482    CHECK_GL_ERROR("FirstShow", "glPolygonMode");
483    //glAlphaFunc(GL_GREATER, 0.5f);
484    //CHECK_GL_ERROR("FirstShow", "glAlphaFunc");
485
486    // We want glReadPixels() to read from the front buffer (which is the
487    // default for single-buffered displays).
488    if (double_buffered) {
489        glReadBuffer(GL_FRONT);
490        CHECK_GL_ERROR("FirstShow", "glReadBuffer");
491    }
492
493    // Grey fog effect.
494    GLfloat fogcolour[4] = { 0.5, 0.5, 0.5, 1.0 };
495    glFogfv(GL_FOG_COLOR, fogcolour);
496    CHECK_GL_ERROR("FirstShow", "glFogfv");
497
498    // Linear fogging.
499    glFogi(GL_FOG_MODE, GL_LINEAR);
500    CHECK_GL_ERROR("FirstShow", "glFogi");
501
502    // Optimise for speed (compute fog per vertex).
503    glHint(GL_FOG_HINT, GL_FASTEST);
504    CHECK_GL_ERROR("FirstShow", "glHint");
505
506    // No padding on pixel packing and unpacking (default is to pad each
507    // line to a multiple of 4 bytes).
508    glPixelStorei(GL_UNPACK_ALIGNMENT, 1); // For setting texture maps.
509    CHECK_GL_ERROR("FirstShow", "glPixelStorei GL_UNPACK_ALIGNMENT");
510    glPixelStorei(GL_PACK_ALIGNMENT, 1); // For screengrabs and movies.
511    CHECK_GL_ERROR("FirstShow", "glPixelStorei GL_PACK_ALIGNMENT");
512
513    // Load font
514    wxString path = wmsg_cfgpth();
515    path += wxCONFIG_PATH_SEPARATOR;
516    path += wxT("unifont.pixelfont");
517    if (!m_Font.load(path, content_scale_factor >= 2)) {
518        // FIXME: do something better.
519        // We have this message available: Error in format of font file “%s”
520        fprintf(stderr, "Failed to parse compiled-in font data\n");
521        exit(1);
522    }
523
524    if (blob_method == UNKNOWN) {
525        // Check if we can use GL_POINTS to plot blobs at stations.
526        GLdouble point_size_range[2];
527        glGetDoublev(GL_SMOOTH_POINT_SIZE_RANGE, point_size_range);
528        CHECK_GL_ERROR("FirstShow", "glGetDoublev GL_SMOOTH_POINT_SIZE_RANGE");
529        if (point_size_range[0] <= BLOB_DIAMETER &&
530            point_size_range[1] >= BLOB_DIAMETER) {
531            blob_method = POINT;
532        } else {
533            blob_method = glpoint_sprite_works() ? SPRITE : LINES;
534        }
535        save_hints = true;
536    }
537
538    if (blob_method == POINT) {
539        glPointSize(BLOB_DIAMETER);
540        CHECK_GL_ERROR("FirstShow", "glPointSize");
541    }
542
543    if (cross_method == UNKNOWN) {
544        cross_method = glpoint_sprite_works() ? SPRITE : LINES;
545        save_hints = true;
546    }
547
548    if (cross_method == SPRITE) {
549        glGenTextures(1, &m_CrossTexture);
550        CHECK_GL_ERROR("FirstShow", "glGenTextures");
551        glBindTexture(GL_TEXTURE_2D, m_CrossTexture);
552        CHECK_GL_ERROR("FirstShow", "glBindTexture");
553        // Cross image for drawing crosses using texture mapped point sprites.
554        const unsigned char crossteximage[128] = {
555#define o 0,0
556#define I 255,255
557            CROSS_TEXTURE
558#undef o
559#undef I
560        };
561        glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
562        CHECK_GL_ERROR("FirstShow", "glPixelStorei");
563        glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
564        CHECK_GL_ERROR("FirstShow", "glTexEnvi");
565        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
566        CHECK_GL_ERROR("FirstShow", "glTexParameteri GL_TEXTURE_WRAP_S");
567        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
568        CHECK_GL_ERROR("FirstShow", "glTexParameteri GL_TEXTURE_WRAP_T");
569        glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE_ALPHA, 8, 8, 0, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, (GLvoid *)crossteximage);
570        CHECK_GL_ERROR("FirstShow", "glTexImage2D");
571        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
572        CHECK_GL_ERROR("FirstShow", "glTexParameteri GL_TEXTURE_MAG_FILTER");
573        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
574        CHECK_GL_ERROR("FirstShow", "glTexParameteri GL_TEXTURE_MIN_FILTER");
575    }
576
577    if (blob_method == SPRITE) {
578        glGenTextures(1, &m_BlobTexture);
579        CHECK_GL_ERROR("FirstShow", "glGenTextures");
580        glBindTexture(GL_TEXTURE_2D, m_BlobTexture);
581        CHECK_GL_ERROR("FirstShow", "glBindTexture");
582        // Image for drawing blobs using texture mapped point sprites.
583        const unsigned char blobteximage[128] = {
584#define o 0,0
585#define I 255,255
586            BLOB_TEXTURE
587#undef o
588#undef I
589        };
590        glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
591        CHECK_GL_ERROR("FirstShow", "glPixelStorei");
592        glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
593        CHECK_GL_ERROR("FirstShow", "glTexEnvi");
594        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
595        CHECK_GL_ERROR("FirstShow", "glTexParameteri GL_TEXTURE_WRAP_S");
596        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
597        CHECK_GL_ERROR("FirstShow", "glTexParameteri GL_TEXTURE_WRAP_T");
598        glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE_ALPHA, 8, 8, 0, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, (GLvoid *)blobteximage);
599        CHECK_GL_ERROR("FirstShow", "glTexImage2D");
600        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
601        CHECK_GL_ERROR("FirstShow", "glTexParameteri GL_TEXTURE_MAG_FILTER");
602        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
603        CHECK_GL_ERROR("FirstShow", "glTexParameteri GL_TEXTURE_MIN_FILTER");
604    }
605}
606
607void GLACanvas::Clear()
608{
609    // Clear the canvas.
610
611    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
612    CHECK_GL_ERROR("Clear", "glClear");
613}
614
615void GLACanvas::ClearNative()
616{
617    // Clear the canvas to the native background colour.
618
619    wxColour background_colour = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWFRAME);
620    glClearColor(background_colour.Red() / 255.,
621                 background_colour.Green() / 255.,
622                 background_colour.Blue() / 255.,
623                 1.0);
624    CHECK_GL_ERROR("ClearNative", "glClearColor");
625    glClear(GL_COLOR_BUFFER_BIT);
626    CHECK_GL_ERROR("ClearNative", "glClear");
627    glClearColor(0.0, 0.0, 0.0, 1.0);
628    CHECK_GL_ERROR("ClearNative", "glClearColor (2)");
629}
630
631void GLACanvas::SetScale(double scale)
632{
633    if (scale != m_Scale) {
634        for (auto & i : drawing_lists) {
635            i.invalidate_if(INVALIDATE_ON_SCALE);
636        }
637
638        m_Scale = scale;
639    }
640}
641
642#ifdef wxHAS_DPI_INDEPENDENT_PIXELS
643void GLACanvas::UpdateContentScaleFactor()
644{
645    double new_content_scale_factor = wxGLCanvas::GetContentScaleFactor();
646    if (new_content_scale_factor == content_scale_factor) return;
647
648    content_scale_factor = new_content_scale_factor;
649    for (auto& i : drawing_lists) {
650        i.invalidate_if(INVALIDATE_ON_HIDPI);
651    }
652}
653
654void GLACanvas::OnMove(wxMoveEvent & event)
655{
656    UpdateContentScaleFactor();
657    event.Skip();
658}
659#endif
660
661void GLACanvas::OnSize(wxSizeEvent & event)
662{
663    UpdateContentScaleFactor();
664
665    wxSize size = event.GetSize();
666
667    int new_w = size.GetWidth() * content_scale_factor;
668    int new_h = size.GetHeight() * content_scale_factor;
669    // The width and height go to zero when the panel is dragged right
670    // across so we clamp them to be at least 1 to avoid problems.
671    if (new_w < 1) new_w = 1;
672    if (new_h < 1) new_h = 1;
673    unsigned int mask = 0;
674    if (new_w != x_size) mask |= INVALIDATE_ON_X_RESIZE;
675    if (new_h != y_size) mask |= INVALIDATE_ON_Y_RESIZE;
676    if (mask) {
677        x_size = new_w;
678        y_size = new_h;
679        for (auto& i : drawing_lists) {
680            i.invalidate_if(mask);
681        }
682    }
683
684    event.Skip();
685
686    if (!opengl_initialised) return;
687
688    // Set viewport.
689    glViewport(0, 0, x_size, y_size);
690    CHECK_GL_ERROR("OnSize", "glViewport");
691}
692
693void GLACanvas::AddTranslationScreenCoordinates(int dx, int dy)
694{
695    // Translate the data by a given amount, specified in screen coordinates.
696
697    // Find out how far the translation takes us in data coordinates.
698    SetDataTransform();
699
700    double x0, y0, z0;
701    double x, y, z;
702    gluUnProject(0.0, 0.0, 0.0, modelview_matrix, projection_matrix, viewport,
703                 &x0, &y0, &z0);
704    CHECK_GL_ERROR("AddTranslationScreenCoordinates", "gluUnProject");
705    gluUnProject(dx, -dy, 0.0, modelview_matrix, projection_matrix, viewport,
706                 &x, &y, &z);
707    CHECK_GL_ERROR("AddTranslationScreenCoordinates", "gluUnProject (2)");
708
709    // Apply the translation.
710    AddTranslation(Vector3(x - x0, y - y0, z - z0));
711}
712
713void GLACanvas::SetVolumeDiameter(glaCoord diameter)
714{
715    // Set the size of the data drawing volume by giving the diameter of the
716    // smallest sphere containing it.
717
718    m_VolumeDiameter = max(glaCoord(1.0), diameter);
719}
720
721void GLACanvas::StartDrawing()
722{
723    // Prepare for a redraw operation.
724
725    ctx.SetCurrent(*this);
726    glDepthMask(GL_TRUE);
727
728    if (!save_hints) return;
729
730    // We want to check on the second redraw.
731    static int draw_count = 2;
732    if (--draw_count != 0) return;
733
734    if (cross_method != LINES) {
735        SetColour(col_WHITE);
736        Clear();
737        SetDataTransform();
738        BeginCrosses();
739        DrawCross(-m_Translation.GetX(), -m_Translation.GetY(), -m_Translation.GetZ());
740        EndCrosses();
741        static const unsigned char expected_cross[64 * 3] = {
742#define o 0,0,0
743#define I 255,255,255
744            CROSS_TEXTURE
745#undef o
746#undef I
747        };
748        if (!CheckVisualFidelity(expected_cross)) {
749            cross_method = LINES;
750            save_hints = true;
751        }
752    }
753
754    if (blob_method != LINES) {
755        SetColour(col_WHITE);
756        Clear();
757        SetDataTransform();
758        BeginBlobs();
759        DrawBlob(-m_Translation.GetX(), -m_Translation.GetY(), -m_Translation.GetZ());
760        EndBlobs();
761        static const unsigned char expected_blob[64 * 3] = {
762#define o 0,0,0
763#define I 255,255,255
764            BLOB_TEXTURE
765#undef o
766#undef I
767        };
768        if (!CheckVisualFidelity(expected_blob)) {
769            blob_method = LINES;
770            save_hints = true;
771        }
772    }
773
774    wxConfigBase * cfg = wxConfigBase::Get();
775    cfg->Write(wxT("opengl_survex"), wxT(VERSION));
776    cfg->Write(wxT("opengl_vendor"), vendor);
777    cfg->Write(wxT("opengl_renderer"), renderer);
778    cfg->Write(wxT("blob_method"), blob_method);
779    cfg->Write(wxT("cross_method"), cross_method);
780    cfg->Flush();
781    save_hints = false;
782}
783
784void GLACanvas::EnableSmoothPolygons(bool filled)
785{
786    // Prepare for drawing smoothly-shaded polygons.
787    // Only use this when required (in particular lines in lists may not be
788    // coloured correctly when this is enabled).
789
790    glPushAttrib(GL_ENABLE_BIT|GL_LIGHTING_BIT|GL_POLYGON_BIT);
791    if (filled) {
792        glShadeModel(GL_SMOOTH);
793        glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
794    } else {
795        glDisable(GL_LINE_SMOOTH);
796        glDisable(GL_TEXTURE_2D);
797        glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
798    }
799    CHECK_GL_ERROR("EnableSmoothPolygons", "glPolygonMode");
800
801    if (filled && m_SmoothShading) {
802        static const GLfloat mat_specular[] = { 0.2, 0.2, 0.2, 1.0 };
803        static const GLfloat light_position[] = { -1.0, -1.0, -1.0, 0.0 };
804        static const GLfloat light_ambient[] = { 0.3, 0.3, 0.3, 1.0 };
805        static const GLfloat light_diffuse[] = { 0.7, 0.7, 0.7, 1.0 };
806        glEnable(GL_COLOR_MATERIAL);
807        glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, mat_specular);
808        glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, 10.0);
809        glLightfv(GL_LIGHT0, GL_AMBIENT, light_ambient);
810        glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse);
811        glLightfv(GL_LIGHT0, GL_POSITION, light_position);
812        glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);
813        glEnable(GL_LIGHTING);
814        glEnable(GL_LIGHT0);
815    }
816}
817
818void GLACanvas::DisableSmoothPolygons()
819{
820    glPopAttrib();
821}
822
823void GLACanvas::PlaceNormal(const Vector3 &v)
824{
825    // Add a normal (for polygons etc.)
826
827    glNormal3d(v.GetX(), v.GetY(), v.GetZ());
828}
829
830void GLACanvas::SetDataTransform()
831{
832    // Set projection.
833    glMatrixMode(GL_PROJECTION);
834    CHECK_GL_ERROR("SetDataTransform", "glMatrixMode");
835    glLoadIdentity();
836    CHECK_GL_ERROR("SetDataTransform", "glLoadIdentity");
837
838    double aspect = double(y_size) / double(x_size);
839
840    GLdouble near_plane = 1.0;
841    if (m_Perspective) {
842        GLdouble lr = near_plane * tan(rad(25.0));
843        GLdouble far_plane = m_VolumeDiameter * 5 + near_plane; // FIXME: work out properly
844        GLdouble tb = lr * aspect;
845        glFrustum(-lr, lr, -tb, tb, near_plane, far_plane);
846        CHECK_GL_ERROR("SetViewportAndProjection", "glFrustum");
847    } else {
848        near_plane = 0.0;
849        assert(m_Scale != 0.0);
850        GLdouble lr = m_VolumeDiameter / m_Scale * 0.5;
851        GLdouble far_plane = m_VolumeDiameter + near_plane;
852        GLdouble tb = lr;
853        if (aspect >= 1.0) {
854            tb *= aspect;
855        } else {
856            lr /= aspect;
857        }
858        glOrtho(-lr, lr, -tb, tb, near_plane, far_plane);
859        CHECK_GL_ERROR("SetViewportAndProjection", "glOrtho");
860    }
861
862    // Set the modelview transform for drawing data.
863    glMatrixMode(GL_MODELVIEW);
864    CHECK_GL_ERROR("SetDataTransform", "glMatrixMode");
865    glLoadIdentity();
866    CHECK_GL_ERROR("SetDataTransform", "glLoadIdentity");
867    if (m_Perspective) {
868        glTranslated(0.0, 0.0, -near_plane);
869    } else {
870        glTranslated(0.0, 0.0, -0.5 * m_VolumeDiameter);
871    }
872    CHECK_GL_ERROR("SetDataTransform", "glTranslated");
873    // Get axes the correct way around (z upwards, y into screen)
874    glRotated(-90.0, 1.0, 0.0, 0.0);
875    CHECK_GL_ERROR("SetDataTransform", "glRotated");
876    glRotated(-m_Tilt, 1.0, 0.0, 0.0);
877    CHECK_GL_ERROR("SetDataTransform", "glRotated");
878    glRotated(m_Pan, 0.0, 0.0, 1.0);
879    CHECK_GL_ERROR("SetDataTransform", "glRotated");
880    if (m_Perspective) {
881        glTranslated(m_Translation.GetX(),
882                     m_Translation.GetY(),
883                     m_Translation.GetZ());
884        CHECK_GL_ERROR("SetDataTransform", "glTranslated");
885    }
886    if (z_stretch != 1.0) {
887        glScaled(1.0, 1.0, z_stretch);
888        CHECK_GL_ERROR("SetDataTransform", "glScaled");
889    }
890
891    // Save projection matrix.
892    glGetDoublev(GL_PROJECTION_MATRIX, projection_matrix);
893    CHECK_GL_ERROR("SetDataTransform", "glGetDoublev");
894
895    // Save viewport coordinates.
896    glGetIntegerv(GL_VIEWPORT, viewport);
897    CHECK_GL_ERROR("SetDataTransform", "glGetIntegerv");
898
899    // Save modelview matrix.
900    glGetDoublev(GL_MODELVIEW_MATRIX, modelview_matrix);
901    CHECK_GL_ERROR("SetDataTransform", "glGetDoublev");
902
903    if (!m_Perspective) {
904        // Adjust the translation so we don't change the Z position of the model
905        double X, Y, Z;
906        gluProject(m_Translation.GetX(),
907                   m_Translation.GetY(),
908                   m_Translation.GetZ(),
909                   modelview_matrix, projection_matrix, viewport,
910                   &X, &Y, &Z);
911        double Tx, Ty, Tz;
912        gluUnProject(X, Y, 0.5, modelview_matrix, projection_matrix, viewport,
913                     &Tx, &Ty, &Tz);
914        glTranslated(Tx, Ty, Tz);
915        CHECK_GL_ERROR("SetDataTransform", "glTranslated");
916        glGetDoublev(GL_MODELVIEW_MATRIX, modelview_matrix);
917    }
918
919    glEnable(GL_DEPTH_TEST);
920    CHECK_GL_ERROR("SetDataTransform", "glEnable GL_DEPTH_TEST");
921
922    if (m_Textured) {
923        glBindTexture(GL_TEXTURE_2D, m_Texture);
924        glEnable(GL_TEXTURE_2D);
925        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
926        CHECK_GL_ERROR("ToggleTextured", "glTexParameteri GL_TEXTURE_WRAP_S");
927        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
928        CHECK_GL_ERROR("ToggleTextured", "glTexParameteri GL_TEXTURE_WRAP_T");
929        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
930        CHECK_GL_ERROR("ToggleTextured", "glTexParameteri GL_TEXTURE_MAG_FILTER");
931        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
932                        GL_LINEAR_MIPMAP_LINEAR);
933        CHECK_GL_ERROR("ToggleTextured", "glTexParameteri GL_TEXTURE_MIN_FILTER");
934        glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
935    } else {
936        glDisable(GL_TEXTURE_2D);
937    }
938    if (m_Fog) {
939        glFogf(GL_FOG_START, near_plane);
940        glFogf(GL_FOG_END, near_plane + m_VolumeDiameter);
941        glEnable(GL_FOG);
942    } else {
943        glDisable(GL_FOG);
944    }
945
946    glEnable(GL_BLEND);
947    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
948    if (m_AntiAlias) {
949        glEnable(GL_LINE_SMOOTH);
950    } else {
951        glDisable(GL_LINE_SMOOTH);
952    }
953}
954
955void GLACanvas::SetIndicatorTransform()
956{
957    list_flags |= NEVER_CACHE;
958
959    // Set the modelview transform and projection for drawing indicators.
960
961    glDisable(GL_DEPTH_TEST);
962    CHECK_GL_ERROR("SetIndicatorTransform", "glDisable GL_DEPTH_TEST");
963    glDisable(GL_FOG);
964    CHECK_GL_ERROR("SetIndicatorTransform", "glDisable GL_FOG");
965
966    // Just a simple 2D projection.
967    glMatrixMode(GL_PROJECTION);
968    CHECK_GL_ERROR("SetIndicatorTransform", "glMatrixMode");
969    glLoadIdentity();
970    CHECK_GL_ERROR("SetIndicatorTransform", "glLoadIdentity (2)");
971    gluOrtho2D(0, x_size, 0, y_size);
972    CHECK_GL_ERROR("SetIndicatorTransform", "gluOrtho2D");
973
974    // No modelview transform.
975    glMatrixMode(GL_MODELVIEW);
976    CHECK_GL_ERROR("SetIndicatorTransform", "glMatrixMode");
977    glLoadIdentity();
978    CHECK_GL_ERROR("SetIndicatorTransform", "glLoadIdentity");
979
980    glDisable(GL_TEXTURE_2D);
981    CHECK_GL_ERROR("SetIndicatorTransform", "glDisable GL_TEXTURE_2D");
982    glDisable(GL_BLEND);
983    CHECK_GL_ERROR("SetIndicatorTransform", "glDisable GL_BLEND");
984    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
985    CHECK_GL_ERROR("SetIndicatorTransform", "glTexParameteri GL_TEXTURE_WRAP_S");
986    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
987    CHECK_GL_ERROR("SetIndicatorTransform", "glTexParameteri GL_TEXTURE_WRAP_T");
988    glAlphaFunc(GL_GREATER, 0.5f);
989    CHECK_GL_ERROR("SetIndicatorTransform", "glAlphaFunc");
990    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
991    CHECK_GL_ERROR("SetIndicatorTransform", "glTexParameteri GL_TEXTURE_MAG_FILTER");
992    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
993    CHECK_GL_ERROR("SetIndicatorTransform", "glTexParameteri GL_TEXTURE_MIN_FILTER");
994    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST);
995    CHECK_GL_ERROR("SetIndicatorTransform", "glHint");
996}
997
998void GLACanvas::FinishDrawing()
999{
1000    // Complete a redraw operation.
1001
1002    if (double_buffered) {
1003        SwapBuffers();
1004    } else {
1005        glFlush();
1006        CHECK_GL_ERROR("FinishDrawing", "glFlush");
1007    }
1008}
1009
1010void GLACanvas::DrawList(unsigned int l)
1011{
1012    // FIXME: uncomment to disable use of lists for debugging:
1013    // GenerateList(l); return;
1014    if (l >= drawing_lists.size()) drawing_lists.resize(l + 1);
1015
1016    // We generate the OpenGL lists lazily to minimise delays on startup.
1017    // So check if we need to generate the OpenGL list now.
1018    if (drawing_lists[l].need_to_generate()) {
1019        // Clear list_flags so that we can note what conditions to invalidate
1020        // the cached OpenGL list on.
1021        list_flags = 0;
1022
1023#ifdef GLA_DEBUG
1024        printf("generating list #%u... ", l);
1025        m_Vertices = 0;
1026#endif
1027        GenerateList(l);
1028#ifdef GLA_DEBUG
1029        printf("done (%d vertices)\n", m_Vertices);
1030#endif
1031        drawing_lists[l].finalise(list_flags);
1032    }
1033
1034    if (!drawing_lists[l].DrawList()) {
1035        // That list isn't cached (which means it probably can't usefully be
1036        // cached).
1037        GenerateList(l);
1038    }
1039}
1040
1041void GLACanvas::DrawListZPrepass(unsigned int l)
1042{
1043    glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
1044    DrawList(l);
1045    glDepthMask(GL_FALSE);
1046    glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
1047    glDepthFunc(GL_EQUAL);
1048    DrawList(l);
1049    glDepthMask(GL_TRUE);
1050    glDepthFunc(GL_LESS);
1051}
1052
1053void GLACanvas::DrawList2D(unsigned int l, glaCoord x, glaCoord y, double rotation)
1054{
1055    glMatrixMode(GL_PROJECTION);
1056    CHECK_GL_ERROR("DrawList2D", "glMatrixMode");
1057    glPushMatrix();
1058    CHECK_GL_ERROR("DrawList2D", "glPushMatrix");
1059    glTranslated(x, y, 0);
1060    CHECK_GL_ERROR("DrawList2D", "glTranslated");
1061    if (rotation != 0.0) {
1062        glRotated(rotation, 0, 0, -1);
1063        CHECK_GL_ERROR("DrawList2D", "glRotated");
1064    }
1065    DrawList(l);
1066    glMatrixMode(GL_PROJECTION);
1067    CHECK_GL_ERROR("DrawList2D", "glMatrixMode 2");
1068    glPopMatrix();
1069    CHECK_GL_ERROR("DrawList2D", "glPopMatrix");
1070}
1071
1072void GLACanvas::SetColour(const GLAPen& pen, double rgb_scale)
1073{
1074    // Set the colour for subsequent operations.
1075    glColor4f(pen.GetRed() * rgb_scale, pen.GetGreen() * rgb_scale,
1076              pen.GetBlue() * rgb_scale, alpha);
1077}
1078
1079void GLACanvas::SetColour(const GLAPen& pen)
1080{
1081    // Set the colour for subsequent operations.
1082    glColor4d(pen.components[0], pen.components[1], pen.components[2], alpha);
1083}
1084
1085void GLACanvas::SetColour(gla_colour colour, double rgb_scale)
1086{
1087    // Set the colour for subsequent operations.
1088    rgb_scale /= 255.0;
1089    glColor4f(COLOURS[colour].r * rgb_scale,
1090              COLOURS[colour].g * rgb_scale,
1091              COLOURS[colour].b * rgb_scale,
1092              alpha);
1093}
1094
1095void GLACanvas::SetColour(gla_colour colour)
1096{
1097    // Set the colour for subsequent operations.
1098    if (alpha == 1.0) {
1099        glColor3ubv(&COLOURS[colour].r);
1100    } else {
1101        glColor4ub(COLOURS[colour].r,
1102                   COLOURS[colour].g,
1103                   COLOURS[colour].b,
1104                   (unsigned char)(255 * alpha));
1105    }
1106}
1107
1108void GLACanvas::DrawText(glaCoord x, glaCoord y, glaCoord z, const wxString& str)
1109{
1110    // Draw a text string on the current buffer in the current font.
1111    glRasterPos3d(x, y, z);
1112    CHECK_GL_ERROR("DrawText", "glRasterPos3d");
1113    m_Font.write_string(str.data(), str.size());
1114}
1115
1116void GLACanvas::DrawIndicatorText(int x, int y, const wxString& str)
1117{
1118    glRasterPos2d(x, y);
1119    CHECK_GL_ERROR("DrawIndicatorText", "glRasterPos2d");
1120    m_Font.write_string(str.data(), str.size());
1121}
1122
1123void GLACanvas::GetTextExtent(const wxString& str, int * x_ext, int * y_ext) const
1124{
1125    m_Font.get_text_extent(str.data(), str.size(), x_ext, y_ext);
1126}
1127
1128void GLACanvas::BeginQuadrilaterals()
1129{
1130    // Commence drawing of quadrilaterals.
1131
1132    glBegin(GL_QUADS);
1133}
1134
1135void GLACanvas::EndQuadrilaterals()
1136{
1137    // Finish drawing of quadrilaterals.
1138
1139    glEnd();
1140    CHECK_GL_ERROR("EndQuadrilaterals", "glEnd GL_QUADS");
1141}
1142
1143void GLACanvas::BeginLines()
1144{
1145    // Commence drawing of a set of lines.
1146
1147    glBegin(GL_LINES);
1148}
1149
1150void GLACanvas::EndLines()
1151{
1152    // Finish drawing of a set of lines.
1153
1154    glEnd();
1155    CHECK_GL_ERROR("EndLines", "glEnd GL_LINES");
1156}
1157
1158void GLACanvas::BeginTriangles()
1159{
1160    // Commence drawing of a set of triangles.
1161
1162    glBegin(GL_TRIANGLES);
1163}
1164
1165void GLACanvas::EndTriangles()
1166{
1167    // Finish drawing of a set of triangles.
1168
1169    glEnd();
1170    CHECK_GL_ERROR("EndTriangles", "glEnd GL_TRIANGLES");
1171}
1172
1173void GLACanvas::BeginTriangleStrip()
1174{
1175    // Commence drawing of a triangle strip.
1176
1177    glBegin(GL_TRIANGLE_STRIP);
1178}
1179
1180void GLACanvas::EndTriangleStrip()
1181{
1182    // Finish drawing of a triangle strip.
1183
1184    glEnd();
1185    CHECK_GL_ERROR("EndTriangleStrip", "glEnd GL_TRIANGLE_STRIP");
1186}
1187
1188void GLACanvas::BeginPolyline()
1189{
1190    // Commence drawing of a polyline.
1191
1192    glBegin(GL_LINE_STRIP);
1193}
1194
1195void GLACanvas::EndPolyline()
1196{
1197    // Finish drawing of a polyline.
1198
1199    glEnd();
1200    CHECK_GL_ERROR("EndPolyline", "glEnd GL_LINE_STRIP");
1201}
1202
1203void GLACanvas::BeginPolyloop()
1204{
1205    // Commence drawing of a polyloop.
1206
1207    glBegin(GL_LINE_LOOP);
1208}
1209
1210void GLACanvas::EndPolyloop()
1211{
1212    // Finish drawing of a polyloop.
1213
1214    glEnd();
1215    CHECK_GL_ERROR("EndPolyloop", "glEnd GL_LINE_LOOP");
1216}
1217
1218void GLACanvas::BeginPolygon()
1219{
1220    // Commence drawing of a polygon.
1221
1222    glBegin(GL_POLYGON);
1223}
1224
1225void GLACanvas::EndPolygon()
1226{
1227    // Finish drawing of a polygon.
1228
1229    glEnd();
1230    CHECK_GL_ERROR("EndPolygon", "glEnd GL_POLYGON");
1231}
1232
1233void GLACanvas::BeginPoints()
1234{
1235    // Commence drawing points.
1236
1237    glPushAttrib(GL_POINT_BIT);
1238    CHECK_GL_ERROR("BeginPoints", "glPushAttrib");
1239    glPointSize(3);
1240    CHECK_GL_ERROR("BeginPoints", "glPointSize");
1241    glBegin(GL_POINTS);
1242}
1243
1244void GLACanvas::EndPoints()
1245{
1246    // Finish drawing points.
1247
1248    glEnd();
1249    CHECK_GL_ERROR("EndPoints", "glEnd GL_POINTS");
1250    glPopAttrib();
1251    CHECK_GL_ERROR("EndPoints", "glPopAttrib");
1252}
1253
1254void GLACanvas::PlaceVertex(glaCoord x, glaCoord y, glaCoord z)
1255{
1256    // Place a vertex for the current object being drawn.
1257
1258#ifdef GLA_DEBUG
1259    m_Vertices++;
1260#endif
1261    glVertex3d(x, y, z);
1262}
1263
1264void GLACanvas::PlaceVertex(glaCoord x, glaCoord y, glaCoord z,
1265                            glaTexCoord tex_x, glaTexCoord tex_y)
1266{
1267    // Place a vertex for the current object being drawn.
1268
1269#ifdef GLA_DEBUG
1270    m_Vertices++;
1271#endif
1272    glTexCoord2f(tex_x, tex_y);
1273    glVertex3d(x, y, z);
1274}
1275
1276void GLACanvas::PlaceIndicatorVertex(glaCoord x, glaCoord y)
1277{
1278    // Place a vertex for the current indicator object being drawn.
1279
1280    PlaceVertex(x, y, 0.0);
1281}
1282
1283void GLACanvas::BeginBlobs()
1284{
1285    // Commence drawing of a set of blobs.
1286    if (blob_method == SPRITE) {
1287        glPushAttrib(GL_ENABLE_BIT|GL_POINT_BIT);
1288        CHECK_GL_ERROR("BeginBlobs", "glPushAttrib");
1289        glBindTexture(GL_TEXTURE_2D, m_BlobTexture);
1290        CHECK_GL_ERROR("BeginBlobs", "glBindTexture");
1291        glEnable(GL_ALPHA_TEST);
1292        CHECK_GL_ERROR("BeginBlobs", "glEnable GL_ALPHA_TEST");
1293        glPointSize(8);
1294        CHECK_GL_ERROR("BeginBlobs", "glPointSize");
1295        glTexEnvi(GL_POINT_SPRITE, GL_COORD_REPLACE, GL_TRUE);
1296        CHECK_GL_ERROR("BeginBlobs", "glTexEnvi GL_POINT_SPRITE");
1297        glEnable(GL_TEXTURE_2D);
1298        CHECK_GL_ERROR("BeginBlobs", "glEnable GL_TEXTURE_2D");
1299        glEnable(GL_POINT_SPRITE);
1300        CHECK_GL_ERROR("BeginBlobs", "glEnable GL_POINT_SPRITE");
1301        glBegin(GL_POINTS);
1302    } else if (blob_method == POINT) {
1303        glPushAttrib(GL_ENABLE_BIT);
1304        CHECK_GL_ERROR("BeginBlobs", "glPushAttrib");
1305        glEnable(GL_ALPHA_TEST);
1306        CHECK_GL_ERROR("BeginBlobs", "glEnable GL_ALPHA_TEST");
1307        glEnable(GL_POINT_SMOOTH);
1308        CHECK_GL_ERROR("BeginBlobs", "glEnable GL_POINT_SMOOTH");
1309        glBegin(GL_POINTS);
1310    } else {
1311        glPushAttrib(GL_TRANSFORM_BIT|GL_VIEWPORT_BIT|GL_ENABLE_BIT);
1312        CHECK_GL_ERROR("BeginBlobs", "glPushAttrib");
1313        SetIndicatorTransform();
1314        glEnable(GL_DEPTH_TEST);
1315        CHECK_GL_ERROR("BeginBlobs", "glEnable GL_DEPTH_TEST");
1316        glBegin(GL_LINES);
1317    }
1318}
1319
1320void GLACanvas::EndBlobs()
1321{
1322    // Finish drawing of a set of blobs.
1323    glEnd();
1324    if (blob_method != LINES) {
1325        CHECK_GL_ERROR("EndBlobs", "glEnd GL_POINTS");
1326    } else {
1327        CHECK_GL_ERROR("EndBlobs", "glEnd GL_LINES");
1328    }
1329    glPopAttrib();
1330    CHECK_GL_ERROR("EndBlobs", "glPopAttrib");
1331}
1332
1333void GLACanvas::DrawBlob(glaCoord x, glaCoord y, glaCoord z)
1334{
1335    if (blob_method != LINES) {
1336        // Draw a marker.
1337        PlaceVertex(x, y, z);
1338    } else {
1339        double X, Y, Z;
1340        if (!Transform(Vector3(x, y, z), &X, &Y, &Z)) {
1341            printf("bad transform\n");
1342            return;
1343        }
1344        // Stuff behind us (in perspective view) will get clipped,
1345        // but we can save effort with a cheap check here.
1346        if (Z <= 0) return;
1347
1348        X -= BLOB_DIAMETER * 0.5;
1349        Y -= BLOB_DIAMETER * 0.5;
1350
1351        PlaceVertex(X, Y + 1, Z);
1352        PlaceVertex(X, Y + (BLOB_DIAMETER - 1), Z);
1353
1354        for (int i = 1; i < (BLOB_DIAMETER - 1); ++i) {
1355            PlaceVertex(X + i, Y, Z);
1356            PlaceVertex(X + i, Y + BLOB_DIAMETER, Z);
1357        }
1358
1359        PlaceVertex(X + (BLOB_DIAMETER - 1), Y + 1, Z);
1360        PlaceVertex(X + (BLOB_DIAMETER - 1), Y + (BLOB_DIAMETER - 1), Z);
1361    }
1362#ifdef GLA_DEBUG
1363    m_Vertices++;
1364#endif
1365}
1366
1367void GLACanvas::DrawBlob(glaCoord x, glaCoord y)
1368{
1369    if (blob_method != LINES) {
1370        // Draw a marker.
1371        PlaceVertex(x, y, 0);
1372    } else {
1373        x -= BLOB_DIAMETER * 0.5;
1374        y -= BLOB_DIAMETER * 0.5;
1375
1376        PlaceVertex(x, y + 1, 0);
1377        PlaceVertex(x, y + (BLOB_DIAMETER - 1), 0);
1378
1379        for (int i = 1; i < (BLOB_DIAMETER - 1); ++i) {
1380            PlaceVertex(x + i, y, 0);
1381            PlaceVertex(x + i, y + BLOB_DIAMETER, 0);
1382        }
1383
1384        PlaceVertex(x + (BLOB_DIAMETER - 1), y + 1, 0);
1385        PlaceVertex(x + (BLOB_DIAMETER - 1), y + (BLOB_DIAMETER - 1), 0);
1386    }
1387#ifdef GLA_DEBUG
1388    m_Vertices++;
1389#endif
1390}
1391
1392void GLACanvas::BeginCrosses()
1393{
1394    // Plot crosses.
1395    if (cross_method == SPRITE) {
1396        list_flags |= NEVER_CACHE;
1397        SetDataTransform();
1398        glPushAttrib(GL_ENABLE_BIT|GL_POINT_BIT);
1399        CHECK_GL_ERROR("BeginCrosses", "glPushAttrib");
1400        glBindTexture(GL_TEXTURE_2D, m_CrossTexture);
1401        CHECK_GL_ERROR("BeginCrosses", "glBindTexture");
1402        glEnable(GL_ALPHA_TEST);
1403        CHECK_GL_ERROR("BeginCrosses", "glEnable GL_ALPHA_TEST");
1404        glPointSize(8);
1405        CHECK_GL_ERROR("BeginCrosses", "glPointSize");
1406        glTexEnvi(GL_POINT_SPRITE, GL_COORD_REPLACE, GL_TRUE);
1407        CHECK_GL_ERROR("BeginCrosses", "glTexEnvi GL_POINT_SPRITE");
1408        glEnable(GL_TEXTURE_2D);
1409        CHECK_GL_ERROR("BeginCrosses", "glEnable GL_TEXTURE_2D");
1410        glEnable(GL_POINT_SPRITE);
1411        CHECK_GL_ERROR("BeginCrosses", "glEnable GL_POINT_SPRITE");
1412        glBegin(GL_POINTS);
1413    } else {
1414        // To get the crosses to appear at a constant size and orientation on
1415        // screen, we plot them in the Indicator transform coordinates (which
1416        // unfortunately means they can't be usefully put in an opengl display
1417        // list).
1418        glPushAttrib(GL_TRANSFORM_BIT|GL_VIEWPORT_BIT|GL_ENABLE_BIT);
1419        CHECK_GL_ERROR("BeginCrosses", "glPushAttrib 2");
1420        SetIndicatorTransform();
1421        // Align line drawing to pixel centres to get pixel-perfect rendering
1422        // (graphics card and driver bugs aside).
1423        glTranslated(-0.5, -0.5, 0);
1424        CHECK_GL_ERROR("BeginCrosses", "glTranslated");
1425        glEnable(GL_DEPTH_TEST);
1426        CHECK_GL_ERROR("BeginCrosses", "glEnable GL_DEPTH_TEST");
1427        glBegin(GL_LINES);
1428    }
1429}
1430
1431void GLACanvas::EndCrosses()
1432{
1433    glEnd();
1434    if (cross_method == SPRITE) {
1435        CHECK_GL_ERROR("EndCrosses", "glEnd GL_POINTS");
1436    } else {
1437        CHECK_GL_ERROR("EndCrosses", "glEnd GL_LINES");
1438    }
1439    glPopAttrib();
1440    CHECK_GL_ERROR("EndCrosses", "glPopAttrib");
1441}
1442
1443void GLACanvas::DrawCross(glaCoord x, glaCoord y, glaCoord z)
1444{
1445    if (cross_method == SPRITE) {
1446        // Draw a marker.
1447        PlaceVertex(x, y, z);
1448    } else {
1449        double X, Y, Z;
1450        if (!Transform(Vector3(x, y, z), &X, &Y, &Z)) {
1451            printf("bad transform\n");
1452            return;
1453        }
1454        // Stuff behind us (in perspective view) will get clipped,
1455        // but we can save effort with a cheap check here.
1456        if (Z <= 0) return;
1457
1458        // Round to integers before adding on the offsets for the
1459        // cross arms to avoid uneven crosses.
1460        X = rint(X);
1461        Y = rint(Y);
1462        // Need to extend lines by an extra pixel (which shouldn't get drawn by
1463        // the diamond-exit rule).
1464        PlaceVertex(X - 3, Y - 3, Z);
1465        PlaceVertex(X + 4, Y + 4, Z);
1466        PlaceVertex(X - 3, Y + 3, Z);
1467        PlaceVertex(X + 4, Y - 4, Z);
1468    }
1469#ifdef GLA_DEBUG
1470    m_Vertices++;
1471#endif
1472}
1473
1474void GLACanvas::DrawRing(glaCoord x, glaCoord y)
1475{
1476    // Draw an unfilled circle of radius 4
1477
1478    // Round to integers to get an even ring.
1479    x = rint(x);
1480    y = rint(y);
1481
1482    glBegin(GL_LINE_LOOP);
1483    PlaceIndicatorVertex(x + 3.5, y - 1.5);
1484    PlaceIndicatorVertex(x + 1.5, y - 3.5);
1485    PlaceIndicatorVertex(x - 1.5, y - 3.5);
1486    PlaceIndicatorVertex(x - 3.5, y - 1.5);
1487    PlaceIndicatorVertex(x - 3.5, y + 1.5);
1488    PlaceIndicatorVertex(x - 1.5, y + 3.5);
1489    PlaceIndicatorVertex(x + 1.5, y + 3.5);
1490    PlaceIndicatorVertex(x + 3.5, y + 1.5);
1491    glEnd();
1492    CHECK_GL_ERROR("DrawRing", "glEnd GL_LINE_LOOP");
1493}
1494
1495void GLACanvas::DrawRectangle(gla_colour fill, gla_colour edge,
1496                              glaCoord x0, glaCoord y0, glaCoord w, glaCoord h)
1497{
1498    // Draw a filled rectangle with an edge in the indicator plane.
1499    // (x0, y0) specify the bottom-left corner of the rectangle and (w, h) the
1500    // size.
1501
1502    SetColour(fill);
1503    BeginQuadrilaterals();
1504    PlaceIndicatorVertex(x0, y0);
1505    PlaceIndicatorVertex(x0 + w, y0);
1506    PlaceIndicatorVertex(x0 + w, y0 + h);
1507    PlaceIndicatorVertex(x0, y0 + h);
1508    EndQuadrilaterals();
1509
1510    if (edge != fill) {
1511        SetColour(edge);
1512        BeginPolyline();
1513        PlaceIndicatorVertex(x0, y0);
1514        PlaceIndicatorVertex(x0 + w, y0);
1515        PlaceIndicatorVertex(x0 + w, y0 + h);
1516        PlaceIndicatorVertex(x0, y0 + h);
1517        PlaceIndicatorVertex(x0, y0);
1518        EndPolyline();
1519    }
1520}
1521
1522void
1523GLACanvas::DrawShadedRectangle(const GLAPen & fill_bot, const GLAPen & fill_top,
1524                               glaCoord x0, glaCoord y0,
1525                               glaCoord w, glaCoord h)
1526{
1527    // Draw a graduated filled rectangle in the indicator plane.
1528    // (x0, y0) specify the bottom-left corner of the rectangle and (w, h) the
1529    // size.
1530
1531    glShadeModel(GL_SMOOTH);
1532    CHECK_GL_ERROR("DrawShadedRectangle", "glShadeModel GL_SMOOTH");
1533    BeginQuadrilaterals();
1534    SetColour(fill_bot);
1535    PlaceIndicatorVertex(x0, y0);
1536    PlaceIndicatorVertex(x0 + w, y0);
1537    SetColour(fill_top);
1538    PlaceIndicatorVertex(x0 + w, y0 + h);
1539    PlaceIndicatorVertex(x0, y0 + h);
1540    EndQuadrilaterals();
1541    glShadeModel(GL_FLAT);
1542    CHECK_GL_ERROR("DrawShadedRectangle", "glShadeModel GL_FLAT");
1543}
1544
1545void GLACanvas::DrawCircle(gla_colour edge, gla_colour fill,
1546                           glaCoord cx, glaCoord cy, glaCoord radius)
1547{
1548    // Draw a filled circle with an edge.
1549    SetColour(fill);
1550    glMatrixMode(GL_MODELVIEW);
1551    CHECK_GL_ERROR("DrawCircle", "glMatrixMode");
1552    glPushMatrix();
1553    CHECK_GL_ERROR("DrawCircle", "glPushMatrix");
1554    glTranslated(cx, cy, 0.0);
1555    CHECK_GL_ERROR("DrawCircle", "glTranslated");
1556    assert(m_Quadric);
1557    gluDisk(m_Quadric, 0.0, radius, 36, 1);
1558    CHECK_GL_ERROR("DrawCircle", "gluDisk");
1559    SetColour(edge);
1560    gluDisk(m_Quadric, radius - 1.0, radius, 36, 1);
1561    CHECK_GL_ERROR("DrawCircle", "gluDisk (2)");
1562    glPopMatrix();
1563    CHECK_GL_ERROR("DrawCircle", "glPopMatrix");
1564}
1565
1566void GLACanvas::DrawSemicircle(gla_colour edge, gla_colour fill,
1567                               glaCoord cx, glaCoord cy,
1568                               glaCoord radius, glaCoord start)
1569{
1570    // Draw a filled semicircle with an edge.
1571    // The semicircle extends from "start" deg to "start"+180 deg (increasing
1572    // clockwise, 0 deg upwards).
1573    SetColour(fill);
1574    glMatrixMode(GL_MODELVIEW);
1575    CHECK_GL_ERROR("DrawSemicircle", "glMatrixMode");
1576    glPushMatrix();
1577    CHECK_GL_ERROR("DrawSemicircle", "glPushMatrix");
1578    glTranslated(cx, cy, 0.0);
1579    CHECK_GL_ERROR("DrawSemicircle", "glTranslated");
1580    assert(m_Quadric);
1581    gluPartialDisk(m_Quadric, 0.0, radius, 36, 1, start, 180.0);
1582    CHECK_GL_ERROR("DrawSemicircle", "gluPartialDisk");
1583    SetColour(edge);
1584    gluPartialDisk(m_Quadric, radius - 1.0, radius, 36, 1, start, 180.0);
1585    CHECK_GL_ERROR("DrawSemicircle", "gluPartialDisk (2)");
1586    glPopMatrix();
1587    CHECK_GL_ERROR("DrawSemicircle", "glPopMatrix");
1588}
1589
1590void GLACanvas::EnableDashedLines()
1591{
1592    // Enable dashed lines, and start drawing in them.
1593
1594    glLineStipple(1, 0x3333);
1595    CHECK_GL_ERROR("EnableDashedLines", "glLineStipple");
1596    glEnable(GL_LINE_STIPPLE);
1597    CHECK_GL_ERROR("EnableDashedLines", "glEnable GL_LINE_STIPPLE");
1598}
1599
1600void GLACanvas::DisableDashedLines()
1601{
1602    glDisable(GL_LINE_STIPPLE);
1603    CHECK_GL_ERROR("DisableDashedLines", "glDisable GL_LINE_STIPPLE");
1604}
1605
1606bool GLACanvas::Transform(const Vector3 & v,
1607                          glaCoord* x_out, glaCoord* y_out, glaCoord* z_out) const
1608{
1609    // Convert from data coordinates to screen coordinates.
1610
1611    // Perform the projection.
1612    return gluProject(v.GetX(), v.GetY(), v.GetZ(),
1613                      modelview_matrix, projection_matrix, viewport,
1614                      x_out, y_out, z_out);
1615}
1616
1617void GLACanvas::ReverseTransform(double x, double y,
1618                                 glaCoord* x_out, glaCoord* y_out, glaCoord* z_out) const
1619{
1620    // Convert from screen coordinates to data coordinates.
1621
1622    // Perform the projection.
1623    gluUnProject(x, y, 0.0, modelview_matrix, projection_matrix, viewport,
1624                 x_out, y_out, z_out);
1625    CHECK_GL_ERROR("ReverseTransform", "gluUnProject");
1626}
1627
1628double GLACanvas::SurveyUnitsAcrossViewport() const
1629{
1630    // Measure the current viewport in survey units, taking into account the
1631    // current display scale.
1632
1633    assert(m_Scale != 0.0);
1634    list_flags |= INVALIDATE_ON_SCALE;
1635    double result = m_VolumeDiameter / m_Scale;
1636    if (y_size < x_size) {
1637        result = result * x_size / y_size;
1638    }
1639    return result;
1640}
1641
1642void GLACanvas::ToggleSmoothShading()
1643{
1644    m_SmoothShading = !m_SmoothShading;
1645}
1646
1647void GLACanvas::ToggleTextured()
1648{
1649    m_Textured = !m_Textured;
1650    if (m_Textured && m_Texture == 0) {
1651        glGenTextures(1, &m_Texture);
1652        CHECK_GL_ERROR("ToggleTextured", "glGenTextures");
1653
1654        glBindTexture(GL_TEXTURE_2D, m_Texture);
1655        CHECK_GL_ERROR("ToggleTextured", "glBindTexture");
1656
1657        ::wxInitAllImageHandlers();
1658
1659        wxImage img;
1660        wxString texture(wmsg_cfgpth());
1661        texture += wxCONFIG_PATH_SEPARATOR;
1662        texture += wxT("images");
1663        texture += wxCONFIG_PATH_SEPARATOR;
1664        texture += wxT("texture.png");
1665        if (!img.LoadFile(texture, wxBITMAP_TYPE_PNG)) {
1666            // FIXME
1667            fprintf(stderr, "Couldn't load image.\n");
1668            exit(1);
1669        }
1670
1671        // Generate mipmaps.
1672        gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGB, // was GL_LUMINANCE
1673                          img.GetWidth(), img.GetHeight(),
1674                          GL_RGB, GL_UNSIGNED_BYTE, img.GetData());
1675        CHECK_GL_ERROR("ToggleTextured", "gluBuild2DMipmaps");
1676
1677        glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
1678        CHECK_GL_ERROR("ToggleTextured", "glTexEnvi");
1679    }
1680}
1681
1682bool GLACanvas::SaveScreenshot(const wxString & fnm, wxBitmapType type) const
1683{
1684    const int width = x_size;
1685    const int height = y_size;
1686    unsigned char *pixels = (unsigned char *)malloc(3 * width * (height + 1));
1687    if (!pixels) return false;
1688    glReadPixels(0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, (GLvoid *)pixels);
1689    CHECK_GL_ERROR("SaveScreenshot", "glReadPixels");
1690    unsigned char * tmp_row = pixels + 3 * width * height;
1691    // We need to flip the image vertically - this approach should be more
1692    // efficient than using wxImage::Mirror(false) as that creates a new
1693    // wxImage object.
1694    for (int y = height / 2 - 1; y >= 0; --y) {
1695        unsigned char * upper = pixels + 3 * width * y;
1696        unsigned char * lower = pixels + 3 * width * (height - y - 1);
1697        memcpy(tmp_row, upper, 3 * width);
1698        memcpy(upper, lower, 3 * width);
1699        memcpy(lower, tmp_row, 3 * width);
1700    }
1701    // NB wxImage constructor calls free(pixels) for us.
1702    wxImage grab(width, height, pixels);
1703    return grab.SaveFile(fnm, type);
1704}
1705
1706bool GLACanvas::CheckVisualFidelity(const unsigned char * target) const
1707{
1708    unsigned char pixels[3 * 8 * 8];
1709    if (double_buffered) {
1710        glReadBuffer(GL_BACK);
1711        CHECK_GL_ERROR("FirstShow", "glReadBuffer");
1712    }
1713    glReadPixels(x_size / 2 - 4, y_size / 2 - 5, 8, 8,
1714                 GL_RGB, GL_UNSIGNED_BYTE, (GLvoid *)pixels);
1715    CHECK_GL_ERROR("CheckVisualFidelity", "glReadPixels");
1716    if (double_buffered) {
1717        glReadBuffer(GL_FRONT);
1718        CHECK_GL_ERROR("FirstShow", "glReadBuffer");
1719    }
1720#if 0
1721    // Show what got drawn and what was expected for debugging.
1722    for (int y = 0; y < 8; ++y) {
1723        for (int x = 0; x < 8; ++x) {
1724            int o = (y * 8 + x) * 3;
1725            printf("%c", pixels[o] ? 'X' : '.');
1726        }
1727        printf(" ");
1728        for (int x = 0; x < 8; ++x) {
1729            int o = (y * 8 + x) * 3;
1730            printf("%c", target[o] ? 'X' : '.');
1731        }
1732        printf("\n");
1733    }
1734#endif
1735    return (memcmp(pixels, target, sizeof(pixels)) == 0);
1736}
1737
1738void GLACanvas::ReadPixels(int width, int height, unsigned char * buf) const
1739{
1740    CHECK_GL_ERROR("ReadPixels", "glReadPixels");
1741    glReadPixels(0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, (GLvoid *)buf);
1742}
1743
1744void GLACanvas::PolygonOffset(bool on) const
1745{
1746    if (on) {
1747        glPolygonOffset(1.0, 1.0);
1748        glEnable(GL_POLYGON_OFFSET_FILL);
1749    } else {
1750        glDisable(GL_POLYGON_OFFSET_FILL);
1751    }
1752}
Note: See TracBrowser for help on using the repository browser.