source: git/src/gla-gl.cc

Last change on this file was eadf5fd, checked in by Olly Betts <olly@…>, 33 hours ago

Fix HiDPI handling with wxMSW

Fixes #144, reported by Juan Corrin.

  • Property mode set to 100644
File size: 49.6 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    dpi_scale_factor = wxGLCanvas::GetDPIScaleFactor();
423
424    // Update our record of the client area size and centre.
425    GetClientSize(&x_size, &y_size);
426    x_size *= dpi_scale_factor;
427    y_size *= dpi_scale_factor;
428    if (x_size < 1) x_size = 1;
429    if (y_size < 1) y_size = 1;
430
431    ctx.SetCurrent(*this);
432    opengl_initialised = true;
433
434    // Set the background colour of the canvas to black.
435    glClearColor(0.0, 0.0, 0.0, 1.0);
436    CHECK_GL_ERROR("FirstShow", "glClearColor");
437
438    // Set viewport.
439    glViewport(0, 0, x_size, y_size);
440    CHECK_GL_ERROR("FirstShow", "glViewport");
441
442    save_hints = false;
443
444    vendor = wxString((const char *)glGetString(GL_VENDOR), wxConvUTF8);
445    renderer = wxString((const char *)glGetString(GL_RENDERER), wxConvUTF8);
446    {
447        wxConfigBase * cfg = wxConfigBase::Get();
448        wxString s;
449        if (cfg->Read(wxT("opengl_survex"), &s, wxString()) && s == wxT(VERSION) &&
450            cfg->Read(wxT("opengl_vendor"), &s, wxString()) && s == vendor &&
451            cfg->Read(wxT("opengl_renderer"), &s, wxString()) && s == renderer) {
452            // The survex version, vendor and renderer are the same as those
453            // we cached hints for, so use those hints.
454            int v;
455            if (cfg->Read(wxT("blob_method"), &v, 0) &&
456                (v == SPRITE || v == POINT || v == LINES)) {
457                // How to draw blobs.
458                blob_method = v;
459            }
460            if (cfg->Read(wxT("cross_method"), &v, 0) &&
461                (v == SPRITE || v == LINES)) {
462                // How to draw crosses.
463                cross_method = v;
464            }
465        }
466    }
467
468    if (m_Quadric) return;
469    // One time initialisation follows.
470
471    m_Quadric = gluNewQuadric();
472    CHECK_GL_ERROR("FirstShow", "gluNewQuadric");
473    if (!m_Quadric) {
474        abort(); // FIXME need to cope somehow
475    }
476
477    glShadeModel(GL_FLAT);
478    CHECK_GL_ERROR("FirstShow", "glShadeModel");
479    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); // So text works.
480    CHECK_GL_ERROR("FirstShow", "glPolygonMode");
481    //glAlphaFunc(GL_GREATER, 0.5f);
482    //CHECK_GL_ERROR("FirstShow", "glAlphaFunc");
483
484    // We want glReadPixels() to read from the front buffer (which is the
485    // default for single-buffered displays).
486    if (double_buffered) {
487        glReadBuffer(GL_FRONT);
488        CHECK_GL_ERROR("FirstShow", "glReadBuffer");
489    }
490
491    // Grey fog effect.
492    GLfloat fogcolour[4] = { 0.5, 0.5, 0.5, 1.0 };
493    glFogfv(GL_FOG_COLOR, fogcolour);
494    CHECK_GL_ERROR("FirstShow", "glFogfv");
495
496    // Linear fogging.
497    glFogi(GL_FOG_MODE, GL_LINEAR);
498    CHECK_GL_ERROR("FirstShow", "glFogi");
499
500    // Optimise for speed (compute fog per vertex).
501    glHint(GL_FOG_HINT, GL_FASTEST);
502    CHECK_GL_ERROR("FirstShow", "glHint");
503
504    // No padding on pixel packing and unpacking (default is to pad each
505    // line to a multiple of 4 bytes).
506    glPixelStorei(GL_UNPACK_ALIGNMENT, 1); // For setting texture maps.
507    CHECK_GL_ERROR("FirstShow", "glPixelStorei GL_UNPACK_ALIGNMENT");
508    glPixelStorei(GL_PACK_ALIGNMENT, 1); // For screengrabs and movies.
509    CHECK_GL_ERROR("FirstShow", "glPixelStorei GL_PACK_ALIGNMENT");
510
511    // Load font
512    wxString path = wmsg_cfgpth();
513    path += wxCONFIG_PATH_SEPARATOR;
514    path += wxT("unifont.pixelfont");
515    if (!m_Font.load(path, dpi_scale_factor >= 2)) {
516        // FIXME: do something better.
517        // We have this message available: Error in format of font file “%s”
518        fprintf(stderr, "Failed to parse compiled-in font data\n");
519        exit(1);
520    }
521
522    if (blob_method == UNKNOWN) {
523        // Check if we can use GL_POINTS to plot blobs at stations.
524        GLdouble point_size_range[2];
525        glGetDoublev(GL_SMOOTH_POINT_SIZE_RANGE, point_size_range);
526        CHECK_GL_ERROR("FirstShow", "glGetDoublev GL_SMOOTH_POINT_SIZE_RANGE");
527        if (point_size_range[0] <= BLOB_DIAMETER &&
528            point_size_range[1] >= BLOB_DIAMETER) {
529            blob_method = POINT;
530        } else {
531            blob_method = glpoint_sprite_works() ? SPRITE : LINES;
532        }
533        save_hints = true;
534    }
535
536    if (blob_method == POINT) {
537        glPointSize(BLOB_DIAMETER);
538        CHECK_GL_ERROR("FirstShow", "glPointSize");
539    }
540
541    if (cross_method == UNKNOWN) {
542        cross_method = glpoint_sprite_works() ? SPRITE : LINES;
543        save_hints = true;
544    }
545
546    if (cross_method == SPRITE) {
547        glGenTextures(1, &m_CrossTexture);
548        CHECK_GL_ERROR("FirstShow", "glGenTextures");
549        glBindTexture(GL_TEXTURE_2D, m_CrossTexture);
550        CHECK_GL_ERROR("FirstShow", "glBindTexture");
551        // Cross image for drawing crosses using texture mapped point sprites.
552        const unsigned char crossteximage[128] = {
553#define o 0,0
554#define I 255,255
555            CROSS_TEXTURE
556#undef o
557#undef I
558        };
559        glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
560        CHECK_GL_ERROR("FirstShow", "glPixelStorei");
561        glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
562        CHECK_GL_ERROR("FirstShow", "glTexEnvi");
563        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
564        CHECK_GL_ERROR("FirstShow", "glTexParameteri GL_TEXTURE_WRAP_S");
565        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
566        CHECK_GL_ERROR("FirstShow", "glTexParameteri GL_TEXTURE_WRAP_T");
567        glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE_ALPHA, 8, 8, 0, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, (GLvoid *)crossteximage);
568        CHECK_GL_ERROR("FirstShow", "glTexImage2D");
569        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
570        CHECK_GL_ERROR("FirstShow", "glTexParameteri GL_TEXTURE_MAG_FILTER");
571        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
572        CHECK_GL_ERROR("FirstShow", "glTexParameteri GL_TEXTURE_MIN_FILTER");
573    }
574
575    if (blob_method == SPRITE) {
576        glGenTextures(1, &m_BlobTexture);
577        CHECK_GL_ERROR("FirstShow", "glGenTextures");
578        glBindTexture(GL_TEXTURE_2D, m_BlobTexture);
579        CHECK_GL_ERROR("FirstShow", "glBindTexture");
580        // Image for drawing blobs using texture mapped point sprites.
581        const unsigned char blobteximage[128] = {
582#define o 0,0
583#define I 255,255
584            BLOB_TEXTURE
585#undef o
586#undef I
587        };
588        glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
589        CHECK_GL_ERROR("FirstShow", "glPixelStorei");
590        glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
591        CHECK_GL_ERROR("FirstShow", "glTexEnvi");
592        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
593        CHECK_GL_ERROR("FirstShow", "glTexParameteri GL_TEXTURE_WRAP_S");
594        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
595        CHECK_GL_ERROR("FirstShow", "glTexParameteri GL_TEXTURE_WRAP_T");
596        glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE_ALPHA, 8, 8, 0, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, (GLvoid *)blobteximage);
597        CHECK_GL_ERROR("FirstShow", "glTexImage2D");
598        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
599        CHECK_GL_ERROR("FirstShow", "glTexParameteri GL_TEXTURE_MAG_FILTER");
600        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
601        CHECK_GL_ERROR("FirstShow", "glTexParameteri GL_TEXTURE_MIN_FILTER");
602    }
603}
604
605void GLACanvas::Clear()
606{
607    // Clear the canvas.
608
609    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
610    CHECK_GL_ERROR("Clear", "glClear");
611}
612
613void GLACanvas::ClearNative()
614{
615    // Clear the canvas to the native background colour.
616
617    wxColour background_colour = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWFRAME);
618    glClearColor(background_colour.Red() / 255.,
619                 background_colour.Green() / 255.,
620                 background_colour.Blue() / 255.,
621                 1.0);
622    CHECK_GL_ERROR("ClearNative", "glClearColor");
623    glClear(GL_COLOR_BUFFER_BIT);
624    CHECK_GL_ERROR("ClearNative", "glClear");
625    glClearColor(0.0, 0.0, 0.0, 1.0);
626    CHECK_GL_ERROR("ClearNative", "glClearColor (2)");
627}
628
629void GLACanvas::SetScale(double scale)
630{
631    if (scale != m_Scale) {
632        for (auto & i : drawing_lists) {
633            i.invalidate_if(INVALIDATE_ON_SCALE);
634        }
635
636        m_Scale = scale;
637    }
638}
639
640void GLACanvas::UpdateDPIScaleFactor()
641{
642    double new_dpi_scale_factor = wxGLCanvas::GetDPIScaleFactor();
643    if (new_dpi_scale_factor == dpi_scale_factor) return;
644
645    dpi_scale_factor = new_dpi_scale_factor;
646    for (auto& i : drawing_lists) {
647        i.invalidate_if(INVALIDATE_ON_HIDPI);
648    }
649}
650
651void GLACanvas::OnMove(wxMoveEvent & event)
652{
653    UpdateDPIScaleFactor();
654    event.Skip();
655}
656
657void GLACanvas::OnSize(wxSizeEvent & event)
658{
659    UpdateDPIScaleFactor();
660
661    wxSize size = event.GetSize();
662
663    int new_w = size.GetWidth() * dpi_scale_factor;
664    int new_h = size.GetHeight() * dpi_scale_factor;
665    // The width and height go to zero when the panel is dragged right
666    // across so we clamp them to be at least 1 to avoid problems.
667    if (new_w < 1) new_w = 1;
668    if (new_h < 1) new_h = 1;
669    unsigned int mask = 0;
670    if (new_w != x_size) mask |= INVALIDATE_ON_X_RESIZE;
671    if (new_h != y_size) mask |= INVALIDATE_ON_Y_RESIZE;
672    if (mask) {
673        x_size = new_w;
674        y_size = new_h;
675        for (auto& i : drawing_lists) {
676            i.invalidate_if(mask);
677        }
678    }
679
680    event.Skip();
681
682    if (!opengl_initialised) return;
683
684    // Set viewport.
685    glViewport(0, 0, x_size, y_size);
686    CHECK_GL_ERROR("OnSize", "glViewport");
687}
688
689void GLACanvas::AddTranslationScreenCoordinates(int dx, int dy)
690{
691    // Translate the data by a given amount, specified in screen coordinates.
692
693    // Find out how far the translation takes us in data coordinates.
694    SetDataTransform();
695
696    double x0, y0, z0;
697    double x, y, z;
698    gluUnProject(0.0, 0.0, 0.0, modelview_matrix, projection_matrix, viewport,
699                 &x0, &y0, &z0);
700    CHECK_GL_ERROR("AddTranslationScreenCoordinates", "gluUnProject");
701    gluUnProject(dx, -dy, 0.0, modelview_matrix, projection_matrix, viewport,
702                 &x, &y, &z);
703    CHECK_GL_ERROR("AddTranslationScreenCoordinates", "gluUnProject (2)");
704
705    // Apply the translation.
706    AddTranslation(Vector3(x - x0, y - y0, z - z0));
707}
708
709void GLACanvas::SetVolumeDiameter(glaCoord diameter)
710{
711    // Set the size of the data drawing volume by giving the diameter of the
712    // smallest sphere containing it.
713
714    m_VolumeDiameter = max(glaCoord(1.0), diameter);
715}
716
717void GLACanvas::StartDrawing()
718{
719    // Prepare for a redraw operation.
720
721    ctx.SetCurrent(*this);
722    glDepthMask(GL_TRUE);
723
724    if (!save_hints) return;
725
726    // We want to check on the second redraw.
727    static int draw_count = 2;
728    if (--draw_count != 0) return;
729
730    if (cross_method != LINES) {
731        SetColour(col_WHITE);
732        Clear();
733        SetDataTransform();
734        BeginCrosses();
735        DrawCross(-m_Translation.GetX(), -m_Translation.GetY(), -m_Translation.GetZ());
736        EndCrosses();
737        static const unsigned char expected_cross[64 * 3] = {
738#define o 0,0,0
739#define I 255,255,255
740            CROSS_TEXTURE
741#undef o
742#undef I
743        };
744        if (!CheckVisualFidelity(expected_cross)) {
745            cross_method = LINES;
746            save_hints = true;
747        }
748    }
749
750    if (blob_method != LINES) {
751        SetColour(col_WHITE);
752        Clear();
753        SetDataTransform();
754        BeginBlobs();
755        DrawBlob(-m_Translation.GetX(), -m_Translation.GetY(), -m_Translation.GetZ());
756        EndBlobs();
757        static const unsigned char expected_blob[64 * 3] = {
758#define o 0,0,0
759#define I 255,255,255
760            BLOB_TEXTURE
761#undef o
762#undef I
763        };
764        if (!CheckVisualFidelity(expected_blob)) {
765            blob_method = LINES;
766            save_hints = true;
767        }
768    }
769
770    wxConfigBase * cfg = wxConfigBase::Get();
771    cfg->Write(wxT("opengl_survex"), wxT(VERSION));
772    cfg->Write(wxT("opengl_vendor"), vendor);
773    cfg->Write(wxT("opengl_renderer"), renderer);
774    cfg->Write(wxT("blob_method"), blob_method);
775    cfg->Write(wxT("cross_method"), cross_method);
776    cfg->Flush();
777    save_hints = false;
778}
779
780void GLACanvas::EnableSmoothPolygons(bool filled)
781{
782    // Prepare for drawing smoothly-shaded polygons.
783    // Only use this when required (in particular lines in lists may not be
784    // coloured correctly when this is enabled).
785
786    glPushAttrib(GL_ENABLE_BIT|GL_LIGHTING_BIT|GL_POLYGON_BIT);
787    if (filled) {
788        glShadeModel(GL_SMOOTH);
789        glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
790    } else {
791        glDisable(GL_LINE_SMOOTH);
792        glDisable(GL_TEXTURE_2D);
793        glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
794    }
795    CHECK_GL_ERROR("EnableSmoothPolygons", "glPolygonMode");
796
797    if (filled && m_SmoothShading) {
798        static const GLfloat mat_specular[] = { 0.2, 0.2, 0.2, 1.0 };
799        static const GLfloat light_position[] = { -1.0, -1.0, -1.0, 0.0 };
800        static const GLfloat light_ambient[] = { 0.3, 0.3, 0.3, 1.0 };
801        static const GLfloat light_diffuse[] = { 0.7, 0.7, 0.7, 1.0 };
802        glEnable(GL_COLOR_MATERIAL);
803        glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, mat_specular);
804        glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, 10.0);
805        glLightfv(GL_LIGHT0, GL_AMBIENT, light_ambient);
806        glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse);
807        glLightfv(GL_LIGHT0, GL_POSITION, light_position);
808        glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);
809        glEnable(GL_LIGHTING);
810        glEnable(GL_LIGHT0);
811    }
812}
813
814void GLACanvas::DisableSmoothPolygons()
815{
816    glPopAttrib();
817}
818
819void GLACanvas::PlaceNormal(const Vector3 &v)
820{
821    // Add a normal (for polygons etc.)
822
823    glNormal3d(v.GetX(), v.GetY(), v.GetZ());
824}
825
826void GLACanvas::SetDataTransform()
827{
828    // Set projection.
829    glMatrixMode(GL_PROJECTION);
830    CHECK_GL_ERROR("SetDataTransform", "glMatrixMode");
831    glLoadIdentity();
832    CHECK_GL_ERROR("SetDataTransform", "glLoadIdentity");
833
834    double aspect = double(y_size) / double(x_size);
835
836    GLdouble near_plane = 1.0;
837    if (m_Perspective) {
838        GLdouble lr = near_plane * tan(rad(25.0));
839        GLdouble far_plane = m_VolumeDiameter * 5 + near_plane; // FIXME: work out properly
840        GLdouble tb = lr * aspect;
841        glFrustum(-lr, lr, -tb, tb, near_plane, far_plane);
842        CHECK_GL_ERROR("SetViewportAndProjection", "glFrustum");
843    } else {
844        near_plane = 0.0;
845        assert(m_Scale != 0.0);
846        GLdouble lr = m_VolumeDiameter / m_Scale * 0.5;
847        GLdouble far_plane = m_VolumeDiameter + near_plane;
848        GLdouble tb = lr;
849        if (aspect >= 1.0) {
850            tb *= aspect;
851        } else {
852            lr /= aspect;
853        }
854        glOrtho(-lr, lr, -tb, tb, near_plane, far_plane);
855        CHECK_GL_ERROR("SetViewportAndProjection", "glOrtho");
856    }
857
858    // Set the modelview transform for drawing data.
859    glMatrixMode(GL_MODELVIEW);
860    CHECK_GL_ERROR("SetDataTransform", "glMatrixMode");
861    glLoadIdentity();
862    CHECK_GL_ERROR("SetDataTransform", "glLoadIdentity");
863    if (m_Perspective) {
864        glTranslated(0.0, 0.0, -near_plane);
865    } else {
866        glTranslated(0.0, 0.0, -0.5 * m_VolumeDiameter);
867    }
868    CHECK_GL_ERROR("SetDataTransform", "glTranslated");
869    // Get axes the correct way around (z upwards, y into screen)
870    glRotated(-90.0, 1.0, 0.0, 0.0);
871    CHECK_GL_ERROR("SetDataTransform", "glRotated");
872    glRotated(-m_Tilt, 1.0, 0.0, 0.0);
873    CHECK_GL_ERROR("SetDataTransform", "glRotated");
874    glRotated(m_Pan, 0.0, 0.0, 1.0);
875    CHECK_GL_ERROR("SetDataTransform", "glRotated");
876    if (m_Perspective) {
877        glTranslated(m_Translation.GetX(),
878                     m_Translation.GetY(),
879                     m_Translation.GetZ());
880        CHECK_GL_ERROR("SetDataTransform", "glTranslated");
881    }
882    if (z_stretch != 1.0) {
883        glScaled(1.0, 1.0, z_stretch);
884        CHECK_GL_ERROR("SetDataTransform", "glScaled");
885    }
886
887    // Save projection matrix.
888    glGetDoublev(GL_PROJECTION_MATRIX, projection_matrix);
889    CHECK_GL_ERROR("SetDataTransform", "glGetDoublev");
890
891    // Save viewport coordinates.
892    glGetIntegerv(GL_VIEWPORT, viewport);
893    CHECK_GL_ERROR("SetDataTransform", "glGetIntegerv");
894
895    // Save modelview matrix.
896    glGetDoublev(GL_MODELVIEW_MATRIX, modelview_matrix);
897    CHECK_GL_ERROR("SetDataTransform", "glGetDoublev");
898
899    if (!m_Perspective) {
900        // Adjust the translation so we don't change the Z position of the model
901        double X, Y, Z;
902        gluProject(m_Translation.GetX(),
903                   m_Translation.GetY(),
904                   m_Translation.GetZ(),
905                   modelview_matrix, projection_matrix, viewport,
906                   &X, &Y, &Z);
907        double Tx, Ty, Tz;
908        gluUnProject(X, Y, 0.5, modelview_matrix, projection_matrix, viewport,
909                     &Tx, &Ty, &Tz);
910        glTranslated(Tx, Ty, Tz);
911        CHECK_GL_ERROR("SetDataTransform", "glTranslated");
912        glGetDoublev(GL_MODELVIEW_MATRIX, modelview_matrix);
913    }
914
915    glEnable(GL_DEPTH_TEST);
916    CHECK_GL_ERROR("SetDataTransform", "glEnable GL_DEPTH_TEST");
917
918    if (m_Textured) {
919        glBindTexture(GL_TEXTURE_2D, m_Texture);
920        glEnable(GL_TEXTURE_2D);
921        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
922        CHECK_GL_ERROR("ToggleTextured", "glTexParameteri GL_TEXTURE_WRAP_S");
923        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
924        CHECK_GL_ERROR("ToggleTextured", "glTexParameteri GL_TEXTURE_WRAP_T");
925        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
926        CHECK_GL_ERROR("ToggleTextured", "glTexParameteri GL_TEXTURE_MAG_FILTER");
927        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
928                        GL_LINEAR_MIPMAP_LINEAR);
929        CHECK_GL_ERROR("ToggleTextured", "glTexParameteri GL_TEXTURE_MIN_FILTER");
930        glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
931    } else {
932        glDisable(GL_TEXTURE_2D);
933    }
934    if (m_Fog) {
935        glFogf(GL_FOG_START, near_plane);
936        glFogf(GL_FOG_END, near_plane + m_VolumeDiameter);
937        glEnable(GL_FOG);
938    } else {
939        glDisable(GL_FOG);
940    }
941
942    glEnable(GL_BLEND);
943    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
944    if (m_AntiAlias) {
945        glEnable(GL_LINE_SMOOTH);
946    } else {
947        glDisable(GL_LINE_SMOOTH);
948    }
949}
950
951void GLACanvas::SetIndicatorTransform()
952{
953    list_flags |= NEVER_CACHE;
954
955    // Set the modelview transform and projection for drawing indicators.
956
957    glDisable(GL_DEPTH_TEST);
958    CHECK_GL_ERROR("SetIndicatorTransform", "glDisable GL_DEPTH_TEST");
959    glDisable(GL_FOG);
960    CHECK_GL_ERROR("SetIndicatorTransform", "glDisable GL_FOG");
961
962    // Just a simple 2D projection.
963    glMatrixMode(GL_PROJECTION);
964    CHECK_GL_ERROR("SetIndicatorTransform", "glMatrixMode");
965    glLoadIdentity();
966    CHECK_GL_ERROR("SetIndicatorTransform", "glLoadIdentity (2)");
967    gluOrtho2D(0, x_size, 0, y_size);
968    CHECK_GL_ERROR("SetIndicatorTransform", "gluOrtho2D");
969
970    // No modelview transform.
971    glMatrixMode(GL_MODELVIEW);
972    CHECK_GL_ERROR("SetIndicatorTransform", "glMatrixMode");
973    glLoadIdentity();
974    CHECK_GL_ERROR("SetIndicatorTransform", "glLoadIdentity");
975
976    glDisable(GL_TEXTURE_2D);
977    CHECK_GL_ERROR("SetIndicatorTransform", "glDisable GL_TEXTURE_2D");
978    glDisable(GL_BLEND);
979    CHECK_GL_ERROR("SetIndicatorTransform", "glDisable GL_BLEND");
980    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
981    CHECK_GL_ERROR("SetIndicatorTransform", "glTexParameteri GL_TEXTURE_WRAP_S");
982    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
983    CHECK_GL_ERROR("SetIndicatorTransform", "glTexParameteri GL_TEXTURE_WRAP_T");
984    glAlphaFunc(GL_GREATER, 0.5f);
985    CHECK_GL_ERROR("SetIndicatorTransform", "glAlphaFunc");
986    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
987    CHECK_GL_ERROR("SetIndicatorTransform", "glTexParameteri GL_TEXTURE_MAG_FILTER");
988    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
989    CHECK_GL_ERROR("SetIndicatorTransform", "glTexParameteri GL_TEXTURE_MIN_FILTER");
990    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST);
991    CHECK_GL_ERROR("SetIndicatorTransform", "glHint");
992}
993
994void GLACanvas::FinishDrawing()
995{
996    // Complete a redraw operation.
997
998    if (double_buffered) {
999        SwapBuffers();
1000    } else {
1001        glFlush();
1002        CHECK_GL_ERROR("FinishDrawing", "glFlush");
1003    }
1004}
1005
1006void GLACanvas::DrawList(unsigned int l)
1007{
1008    // FIXME: uncomment to disable use of lists for debugging:
1009    // GenerateList(l); return;
1010    if (l >= drawing_lists.size()) drawing_lists.resize(l + 1);
1011
1012    // We generate the OpenGL lists lazily to minimise delays on startup.
1013    // So check if we need to generate the OpenGL list now.
1014    if (drawing_lists[l].need_to_generate()) {
1015        // Clear list_flags so that we can note what conditions to invalidate
1016        // the cached OpenGL list on.
1017        list_flags = 0;
1018
1019#ifdef GLA_DEBUG
1020        printf("generating list #%u... ", l);
1021        m_Vertices = 0;
1022#endif
1023        GenerateList(l);
1024#ifdef GLA_DEBUG
1025        printf("done (%d vertices)\n", m_Vertices);
1026#endif
1027        drawing_lists[l].finalise(list_flags);
1028    }
1029
1030    if (!drawing_lists[l].DrawList()) {
1031        // That list isn't cached (which means it probably can't usefully be
1032        // cached).
1033        GenerateList(l);
1034    }
1035}
1036
1037void GLACanvas::DrawListZPrepass(unsigned int l)
1038{
1039    glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
1040    DrawList(l);
1041    glDepthMask(GL_FALSE);
1042    glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
1043    glDepthFunc(GL_EQUAL);
1044    DrawList(l);
1045    glDepthMask(GL_TRUE);
1046    glDepthFunc(GL_LESS);
1047}
1048
1049void GLACanvas::DrawList2D(unsigned int l, glaCoord x, glaCoord y, double rotation)
1050{
1051    glMatrixMode(GL_PROJECTION);
1052    CHECK_GL_ERROR("DrawList2D", "glMatrixMode");
1053    glPushMatrix();
1054    CHECK_GL_ERROR("DrawList2D", "glPushMatrix");
1055    glTranslated(x, y, 0);
1056    CHECK_GL_ERROR("DrawList2D", "glTranslated");
1057    if (rotation != 0.0) {
1058        glRotated(rotation, 0, 0, -1);
1059        CHECK_GL_ERROR("DrawList2D", "glRotated");
1060    }
1061    DrawList(l);
1062    glMatrixMode(GL_PROJECTION);
1063    CHECK_GL_ERROR("DrawList2D", "glMatrixMode 2");
1064    glPopMatrix();
1065    CHECK_GL_ERROR("DrawList2D", "glPopMatrix");
1066}
1067
1068void GLACanvas::SetColour(const GLAPen& pen, double rgb_scale)
1069{
1070    // Set the colour for subsequent operations.
1071    glColor4f(pen.GetRed() * rgb_scale, pen.GetGreen() * rgb_scale,
1072              pen.GetBlue() * rgb_scale, alpha);
1073}
1074
1075void GLACanvas::SetColour(const GLAPen& pen)
1076{
1077    // Set the colour for subsequent operations.
1078    glColor4d(pen.components[0], pen.components[1], pen.components[2], alpha);
1079}
1080
1081void GLACanvas::SetColour(gla_colour colour, double rgb_scale)
1082{
1083    // Set the colour for subsequent operations.
1084    rgb_scale /= 255.0;
1085    glColor4f(COLOURS[colour].r * rgb_scale,
1086              COLOURS[colour].g * rgb_scale,
1087              COLOURS[colour].b * rgb_scale,
1088              alpha);
1089}
1090
1091void GLACanvas::SetColour(gla_colour colour)
1092{
1093    // Set the colour for subsequent operations.
1094    if (alpha == 1.0) {
1095        glColor3ubv(&COLOURS[colour].r);
1096    } else {
1097        glColor4ub(COLOURS[colour].r,
1098                   COLOURS[colour].g,
1099                   COLOURS[colour].b,
1100                   (unsigned char)(255 * alpha));
1101    }
1102}
1103
1104void GLACanvas::DrawText(glaCoord x, glaCoord y, glaCoord z, const wxString& str)
1105{
1106    // Draw a text string on the current buffer in the current font.
1107    glRasterPos3d(x, y, z);
1108    CHECK_GL_ERROR("DrawText", "glRasterPos3d");
1109    m_Font.write_string(str.data(), str.size());
1110}
1111
1112void GLACanvas::DrawIndicatorText(int x, int y, const wxString& str)
1113{
1114    glRasterPos2d(x, y);
1115    CHECK_GL_ERROR("DrawIndicatorText", "glRasterPos2d");
1116    m_Font.write_string(str.data(), str.size());
1117}
1118
1119void GLACanvas::GetTextExtent(const wxString& str, int * x_ext, int * y_ext) const
1120{
1121    m_Font.get_text_extent(str.data(), str.size(), x_ext, y_ext);
1122}
1123
1124void GLACanvas::BeginQuadrilaterals()
1125{
1126    // Commence drawing of quadrilaterals.
1127
1128    glBegin(GL_QUADS);
1129}
1130
1131void GLACanvas::EndQuadrilaterals()
1132{
1133    // Finish drawing of quadrilaterals.
1134
1135    glEnd();
1136    CHECK_GL_ERROR("EndQuadrilaterals", "glEnd GL_QUADS");
1137}
1138
1139void GLACanvas::BeginLines()
1140{
1141    // Commence drawing of a set of lines.
1142
1143    glBegin(GL_LINES);
1144}
1145
1146void GLACanvas::EndLines()
1147{
1148    // Finish drawing of a set of lines.
1149
1150    glEnd();
1151    CHECK_GL_ERROR("EndLines", "glEnd GL_LINES");
1152}
1153
1154void GLACanvas::BeginTriangles()
1155{
1156    // Commence drawing of a set of triangles.
1157
1158    glBegin(GL_TRIANGLES);
1159}
1160
1161void GLACanvas::EndTriangles()
1162{
1163    // Finish drawing of a set of triangles.
1164
1165    glEnd();
1166    CHECK_GL_ERROR("EndTriangles", "glEnd GL_TRIANGLES");
1167}
1168
1169void GLACanvas::BeginTriangleStrip()
1170{
1171    // Commence drawing of a triangle strip.
1172
1173    glBegin(GL_TRIANGLE_STRIP);
1174}
1175
1176void GLACanvas::EndTriangleStrip()
1177{
1178    // Finish drawing of a triangle strip.
1179
1180    glEnd();
1181    CHECK_GL_ERROR("EndTriangleStrip", "glEnd GL_TRIANGLE_STRIP");
1182}
1183
1184void GLACanvas::BeginPolyline()
1185{
1186    // Commence drawing of a polyline.
1187
1188    glBegin(GL_LINE_STRIP);
1189}
1190
1191void GLACanvas::EndPolyline()
1192{
1193    // Finish drawing of a polyline.
1194
1195    glEnd();
1196    CHECK_GL_ERROR("EndPolyline", "glEnd GL_LINE_STRIP");
1197}
1198
1199void GLACanvas::BeginPolyloop()
1200{
1201    // Commence drawing of a polyloop.
1202
1203    glBegin(GL_LINE_LOOP);
1204}
1205
1206void GLACanvas::EndPolyloop()
1207{
1208    // Finish drawing of a polyloop.
1209
1210    glEnd();
1211    CHECK_GL_ERROR("EndPolyloop", "glEnd GL_LINE_LOOP");
1212}
1213
1214void GLACanvas::BeginPolygon()
1215{
1216    // Commence drawing of a polygon.
1217
1218    glBegin(GL_POLYGON);
1219}
1220
1221void GLACanvas::EndPolygon()
1222{
1223    // Finish drawing of a polygon.
1224
1225    glEnd();
1226    CHECK_GL_ERROR("EndPolygon", "glEnd GL_POLYGON");
1227}
1228
1229void GLACanvas::BeginPoints()
1230{
1231    // Commence drawing points.
1232
1233    glPushAttrib(GL_POINT_BIT);
1234    CHECK_GL_ERROR("BeginPoints", "glPushAttrib");
1235    glPointSize(3);
1236    CHECK_GL_ERROR("BeginPoints", "glPointSize");
1237    glBegin(GL_POINTS);
1238}
1239
1240void GLACanvas::EndPoints()
1241{
1242    // Finish drawing points.
1243
1244    glEnd();
1245    CHECK_GL_ERROR("EndPoints", "glEnd GL_POINTS");
1246    glPopAttrib();
1247    CHECK_GL_ERROR("EndPoints", "glPopAttrib");
1248}
1249
1250void GLACanvas::PlaceVertex(glaCoord x, glaCoord y, glaCoord z)
1251{
1252    // Place a vertex for the current object being drawn.
1253
1254#ifdef GLA_DEBUG
1255    m_Vertices++;
1256#endif
1257    glVertex3d(x, y, z);
1258}
1259
1260void GLACanvas::PlaceVertex(glaCoord x, glaCoord y, glaCoord z,
1261                            glaTexCoord tex_x, glaTexCoord tex_y)
1262{
1263    // Place a vertex for the current object being drawn.
1264
1265#ifdef GLA_DEBUG
1266    m_Vertices++;
1267#endif
1268    glTexCoord2f(tex_x, tex_y);
1269    glVertex3d(x, y, z);
1270}
1271
1272void GLACanvas::PlaceIndicatorVertex(glaCoord x, glaCoord y)
1273{
1274    // Place a vertex for the current indicator object being drawn.
1275
1276    PlaceVertex(x, y, 0.0);
1277}
1278
1279void GLACanvas::BeginBlobs()
1280{
1281    // Commence drawing of a set of blobs.
1282    if (blob_method == SPRITE) {
1283        glPushAttrib(GL_ENABLE_BIT|GL_POINT_BIT);
1284        CHECK_GL_ERROR("BeginBlobs", "glPushAttrib");
1285        glBindTexture(GL_TEXTURE_2D, m_BlobTexture);
1286        CHECK_GL_ERROR("BeginBlobs", "glBindTexture");
1287        glEnable(GL_ALPHA_TEST);
1288        CHECK_GL_ERROR("BeginBlobs", "glEnable GL_ALPHA_TEST");
1289        glPointSize(8);
1290        CHECK_GL_ERROR("BeginBlobs", "glPointSize");
1291        glTexEnvi(GL_POINT_SPRITE, GL_COORD_REPLACE, GL_TRUE);
1292        CHECK_GL_ERROR("BeginBlobs", "glTexEnvi GL_POINT_SPRITE");
1293        glEnable(GL_TEXTURE_2D);
1294        CHECK_GL_ERROR("BeginBlobs", "glEnable GL_TEXTURE_2D");
1295        glEnable(GL_POINT_SPRITE);
1296        CHECK_GL_ERROR("BeginBlobs", "glEnable GL_POINT_SPRITE");
1297        glBegin(GL_POINTS);
1298    } else if (blob_method == POINT) {
1299        glPushAttrib(GL_ENABLE_BIT);
1300        CHECK_GL_ERROR("BeginBlobs", "glPushAttrib");
1301        glEnable(GL_ALPHA_TEST);
1302        CHECK_GL_ERROR("BeginBlobs", "glEnable GL_ALPHA_TEST");
1303        glEnable(GL_POINT_SMOOTH);
1304        CHECK_GL_ERROR("BeginBlobs", "glEnable GL_POINT_SMOOTH");
1305        glBegin(GL_POINTS);
1306    } else {
1307        glPushAttrib(GL_TRANSFORM_BIT|GL_VIEWPORT_BIT|GL_ENABLE_BIT);
1308        CHECK_GL_ERROR("BeginBlobs", "glPushAttrib");
1309        SetIndicatorTransform();
1310        glEnable(GL_DEPTH_TEST);
1311        CHECK_GL_ERROR("BeginBlobs", "glEnable GL_DEPTH_TEST");
1312        glBegin(GL_LINES);
1313    }
1314}
1315
1316void GLACanvas::EndBlobs()
1317{
1318    // Finish drawing of a set of blobs.
1319    glEnd();
1320    if (blob_method != LINES) {
1321        CHECK_GL_ERROR("EndBlobs", "glEnd GL_POINTS");
1322    } else {
1323        CHECK_GL_ERROR("EndBlobs", "glEnd GL_LINES");
1324    }
1325    glPopAttrib();
1326    CHECK_GL_ERROR("EndBlobs", "glPopAttrib");
1327}
1328
1329void GLACanvas::DrawBlob(glaCoord x, glaCoord y, glaCoord z)
1330{
1331    if (blob_method != LINES) {
1332        // Draw a marker.
1333        PlaceVertex(x, y, z);
1334    } else {
1335        double X, Y, Z;
1336        if (!Transform(Vector3(x, y, z), &X, &Y, &Z)) {
1337            printf("bad transform\n");
1338            return;
1339        }
1340        // Stuff behind us (in perspective view) will get clipped,
1341        // but we can save effort with a cheap check here.
1342        if (Z <= 0) return;
1343
1344        X -= BLOB_DIAMETER * 0.5;
1345        Y -= BLOB_DIAMETER * 0.5;
1346
1347        PlaceVertex(X, Y + 1, Z);
1348        PlaceVertex(X, Y + (BLOB_DIAMETER - 1), Z);
1349
1350        for (int i = 1; i < (BLOB_DIAMETER - 1); ++i) {
1351            PlaceVertex(X + i, Y, Z);
1352            PlaceVertex(X + i, Y + BLOB_DIAMETER, Z);
1353        }
1354
1355        PlaceVertex(X + (BLOB_DIAMETER - 1), Y + 1, Z);
1356        PlaceVertex(X + (BLOB_DIAMETER - 1), Y + (BLOB_DIAMETER - 1), Z);
1357    }
1358#ifdef GLA_DEBUG
1359    m_Vertices++;
1360#endif
1361}
1362
1363void GLACanvas::DrawBlob(glaCoord x, glaCoord y)
1364{
1365    if (blob_method != LINES) {
1366        // Draw a marker.
1367        PlaceVertex(x, y, 0);
1368    } else {
1369        x -= BLOB_DIAMETER * 0.5;
1370        y -= BLOB_DIAMETER * 0.5;
1371
1372        PlaceVertex(x, y + 1, 0);
1373        PlaceVertex(x, y + (BLOB_DIAMETER - 1), 0);
1374
1375        for (int i = 1; i < (BLOB_DIAMETER - 1); ++i) {
1376            PlaceVertex(x + i, y, 0);
1377            PlaceVertex(x + i, y + BLOB_DIAMETER, 0);
1378        }
1379
1380        PlaceVertex(x + (BLOB_DIAMETER - 1), y + 1, 0);
1381        PlaceVertex(x + (BLOB_DIAMETER - 1), y + (BLOB_DIAMETER - 1), 0);
1382    }
1383#ifdef GLA_DEBUG
1384    m_Vertices++;
1385#endif
1386}
1387
1388void GLACanvas::BeginCrosses()
1389{
1390    // Plot crosses.
1391    if (cross_method == SPRITE) {
1392        list_flags |= NEVER_CACHE;
1393        SetDataTransform();
1394        glPushAttrib(GL_ENABLE_BIT|GL_POINT_BIT);
1395        CHECK_GL_ERROR("BeginCrosses", "glPushAttrib");
1396        glBindTexture(GL_TEXTURE_2D, m_CrossTexture);
1397        CHECK_GL_ERROR("BeginCrosses", "glBindTexture");
1398        glEnable(GL_ALPHA_TEST);
1399        CHECK_GL_ERROR("BeginCrosses", "glEnable GL_ALPHA_TEST");
1400        glPointSize(8);
1401        CHECK_GL_ERROR("BeginCrosses", "glPointSize");
1402        glTexEnvi(GL_POINT_SPRITE, GL_COORD_REPLACE, GL_TRUE);
1403        CHECK_GL_ERROR("BeginCrosses", "glTexEnvi GL_POINT_SPRITE");
1404        glEnable(GL_TEXTURE_2D);
1405        CHECK_GL_ERROR("BeginCrosses", "glEnable GL_TEXTURE_2D");
1406        glEnable(GL_POINT_SPRITE);
1407        CHECK_GL_ERROR("BeginCrosses", "glEnable GL_POINT_SPRITE");
1408        glBegin(GL_POINTS);
1409    } else {
1410        // To get the crosses to appear at a constant size and orientation on
1411        // screen, we plot them in the Indicator transform coordinates (which
1412        // unfortunately means they can't be usefully put in an opengl display
1413        // list).
1414        glPushAttrib(GL_TRANSFORM_BIT|GL_VIEWPORT_BIT|GL_ENABLE_BIT);
1415        CHECK_GL_ERROR("BeginCrosses", "glPushAttrib 2");
1416        SetIndicatorTransform();
1417        // Align line drawing to pixel centres to get pixel-perfect rendering
1418        // (graphics card and driver bugs aside).
1419        glTranslated(-0.5, -0.5, 0);
1420        CHECK_GL_ERROR("BeginCrosses", "glTranslated");
1421        glEnable(GL_DEPTH_TEST);
1422        CHECK_GL_ERROR("BeginCrosses", "glEnable GL_DEPTH_TEST");
1423        glBegin(GL_LINES);
1424    }
1425}
1426
1427void GLACanvas::EndCrosses()
1428{
1429    glEnd();
1430    if (cross_method == SPRITE) {
1431        CHECK_GL_ERROR("EndCrosses", "glEnd GL_POINTS");
1432    } else {
1433        CHECK_GL_ERROR("EndCrosses", "glEnd GL_LINES");
1434    }
1435    glPopAttrib();
1436    CHECK_GL_ERROR("EndCrosses", "glPopAttrib");
1437}
1438
1439void GLACanvas::DrawCross(glaCoord x, glaCoord y, glaCoord z)
1440{
1441    if (cross_method == SPRITE) {
1442        // Draw a marker.
1443        PlaceVertex(x, y, z);
1444    } else {
1445        double X, Y, Z;
1446        if (!Transform(Vector3(x, y, z), &X, &Y, &Z)) {
1447            printf("bad transform\n");
1448            return;
1449        }
1450        // Stuff behind us (in perspective view) will get clipped,
1451        // but we can save effort with a cheap check here.
1452        if (Z <= 0) return;
1453
1454        // Round to integers before adding on the offsets for the
1455        // cross arms to avoid uneven crosses.
1456        X = rint(X);
1457        Y = rint(Y);
1458        // Need to extend lines by an extra pixel (which shouldn't get drawn by
1459        // the diamond-exit rule).
1460        PlaceVertex(X - 3, Y - 3, Z);
1461        PlaceVertex(X + 4, Y + 4, Z);
1462        PlaceVertex(X - 3, Y + 3, Z);
1463        PlaceVertex(X + 4, Y - 4, Z);
1464    }
1465#ifdef GLA_DEBUG
1466    m_Vertices++;
1467#endif
1468}
1469
1470void GLACanvas::DrawRing(glaCoord x, glaCoord y)
1471{
1472    // Draw an unfilled circle of radius 4
1473
1474    // Round to integers to get an even ring.
1475    x = rint(x);
1476    y = rint(y);
1477
1478    glBegin(GL_LINE_LOOP);
1479    PlaceIndicatorVertex(x + 3.5, y - 1.5);
1480    PlaceIndicatorVertex(x + 1.5, y - 3.5);
1481    PlaceIndicatorVertex(x - 1.5, y - 3.5);
1482    PlaceIndicatorVertex(x - 3.5, y - 1.5);
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    glEnd();
1488    CHECK_GL_ERROR("DrawRing", "glEnd GL_LINE_LOOP");
1489}
1490
1491void GLACanvas::DrawRectangle(gla_colour fill, gla_colour edge,
1492                              glaCoord x0, glaCoord y0, glaCoord w, glaCoord h)
1493{
1494    // Draw a filled rectangle with an edge in the indicator plane.
1495    // (x0, y0) specify the bottom-left corner of the rectangle and (w, h) the
1496    // size.
1497
1498    SetColour(fill);
1499    BeginQuadrilaterals();
1500    PlaceIndicatorVertex(x0, y0);
1501    PlaceIndicatorVertex(x0 + w, y0);
1502    PlaceIndicatorVertex(x0 + w, y0 + h);
1503    PlaceIndicatorVertex(x0, y0 + h);
1504    EndQuadrilaterals();
1505
1506    if (edge != fill) {
1507        SetColour(edge);
1508        BeginPolyline();
1509        PlaceIndicatorVertex(x0, y0);
1510        PlaceIndicatorVertex(x0 + w, y0);
1511        PlaceIndicatorVertex(x0 + w, y0 + h);
1512        PlaceIndicatorVertex(x0, y0 + h);
1513        PlaceIndicatorVertex(x0, y0);
1514        EndPolyline();
1515    }
1516}
1517
1518void
1519GLACanvas::DrawShadedRectangle(const GLAPen & fill_bot, const GLAPen & fill_top,
1520                               glaCoord x0, glaCoord y0,
1521                               glaCoord w, glaCoord h)
1522{
1523    // Draw a graduated filled rectangle in the indicator plane.
1524    // (x0, y0) specify the bottom-left corner of the rectangle and (w, h) the
1525    // size.
1526
1527    glShadeModel(GL_SMOOTH);
1528    CHECK_GL_ERROR("DrawShadedRectangle", "glShadeModel GL_SMOOTH");
1529    BeginQuadrilaterals();
1530    SetColour(fill_bot);
1531    PlaceIndicatorVertex(x0, y0);
1532    PlaceIndicatorVertex(x0 + w, y0);
1533    SetColour(fill_top);
1534    PlaceIndicatorVertex(x0 + w, y0 + h);
1535    PlaceIndicatorVertex(x0, y0 + h);
1536    EndQuadrilaterals();
1537    glShadeModel(GL_FLAT);
1538    CHECK_GL_ERROR("DrawShadedRectangle", "glShadeModel GL_FLAT");
1539}
1540
1541void GLACanvas::DrawCircle(gla_colour edge, gla_colour fill,
1542                           glaCoord cx, glaCoord cy, glaCoord radius)
1543{
1544    // Draw a filled circle with an edge.
1545    SetColour(fill);
1546    glMatrixMode(GL_MODELVIEW);
1547    CHECK_GL_ERROR("DrawCircle", "glMatrixMode");
1548    glPushMatrix();
1549    CHECK_GL_ERROR("DrawCircle", "glPushMatrix");
1550    glTranslated(cx, cy, 0.0);
1551    CHECK_GL_ERROR("DrawCircle", "glTranslated");
1552    assert(m_Quadric);
1553    gluDisk(m_Quadric, 0.0, radius, 36, 1);
1554    CHECK_GL_ERROR("DrawCircle", "gluDisk");
1555    SetColour(edge);
1556    gluDisk(m_Quadric, radius - 1.0, radius, 36, 1);
1557    CHECK_GL_ERROR("DrawCircle", "gluDisk (2)");
1558    glPopMatrix();
1559    CHECK_GL_ERROR("DrawCircle", "glPopMatrix");
1560}
1561
1562void GLACanvas::DrawSemicircle(gla_colour edge, gla_colour fill,
1563                               glaCoord cx, glaCoord cy,
1564                               glaCoord radius, glaCoord start)
1565{
1566    // Draw a filled semicircle with an edge.
1567    // The semicircle extends from "start" deg to "start"+180 deg (increasing
1568    // clockwise, 0 deg upwards).
1569    SetColour(fill);
1570    glMatrixMode(GL_MODELVIEW);
1571    CHECK_GL_ERROR("DrawSemicircle", "glMatrixMode");
1572    glPushMatrix();
1573    CHECK_GL_ERROR("DrawSemicircle", "glPushMatrix");
1574    glTranslated(cx, cy, 0.0);
1575    CHECK_GL_ERROR("DrawSemicircle", "glTranslated");
1576    assert(m_Quadric);
1577    gluPartialDisk(m_Quadric, 0.0, radius, 36, 1, start, 180.0);
1578    CHECK_GL_ERROR("DrawSemicircle", "gluPartialDisk");
1579    SetColour(edge);
1580    gluPartialDisk(m_Quadric, radius - 1.0, radius, 36, 1, start, 180.0);
1581    CHECK_GL_ERROR("DrawSemicircle", "gluPartialDisk (2)");
1582    glPopMatrix();
1583    CHECK_GL_ERROR("DrawSemicircle", "glPopMatrix");
1584}
1585
1586void GLACanvas::EnableDashedLines()
1587{
1588    // Enable dashed lines, and start drawing in them.
1589
1590    glLineStipple(1, 0x3333);
1591    CHECK_GL_ERROR("EnableDashedLines", "glLineStipple");
1592    glEnable(GL_LINE_STIPPLE);
1593    CHECK_GL_ERROR("EnableDashedLines", "glEnable GL_LINE_STIPPLE");
1594}
1595
1596void GLACanvas::DisableDashedLines()
1597{
1598    glDisable(GL_LINE_STIPPLE);
1599    CHECK_GL_ERROR("DisableDashedLines", "glDisable GL_LINE_STIPPLE");
1600}
1601
1602bool GLACanvas::Transform(const Vector3 & v,
1603                          glaCoord* x_out, glaCoord* y_out, glaCoord* z_out) const
1604{
1605    // Convert from data coordinates to screen coordinates.
1606
1607    // Perform the projection.
1608    return gluProject(v.GetX(), v.GetY(), v.GetZ(),
1609                      modelview_matrix, projection_matrix, viewport,
1610                      x_out, y_out, z_out);
1611}
1612
1613void GLACanvas::ReverseTransform(double x, double y,
1614                                 glaCoord* x_out, glaCoord* y_out, glaCoord* z_out) const
1615{
1616    // Convert from screen coordinates to data coordinates.
1617
1618    // Perform the projection.
1619    gluUnProject(x, y, 0.0, modelview_matrix, projection_matrix, viewport,
1620                 x_out, y_out, z_out);
1621    CHECK_GL_ERROR("ReverseTransform", "gluUnProject");
1622}
1623
1624double GLACanvas::SurveyUnitsAcrossViewport() const
1625{
1626    // Measure the current viewport in survey units, taking into account the
1627    // current display scale.
1628
1629    assert(m_Scale != 0.0);
1630    list_flags |= INVALIDATE_ON_SCALE;
1631    double result = m_VolumeDiameter / m_Scale;
1632    if (y_size < x_size) {
1633        result = result * x_size / y_size;
1634    }
1635    return result;
1636}
1637
1638void GLACanvas::ToggleSmoothShading()
1639{
1640    m_SmoothShading = !m_SmoothShading;
1641}
1642
1643void GLACanvas::ToggleTextured()
1644{
1645    m_Textured = !m_Textured;
1646    if (m_Textured && m_Texture == 0) {
1647        glGenTextures(1, &m_Texture);
1648        CHECK_GL_ERROR("ToggleTextured", "glGenTextures");
1649
1650        glBindTexture(GL_TEXTURE_2D, m_Texture);
1651        CHECK_GL_ERROR("ToggleTextured", "glBindTexture");
1652
1653        ::wxInitAllImageHandlers();
1654
1655        wxImage img;
1656        wxString texture(wmsg_cfgpth());
1657        texture += wxCONFIG_PATH_SEPARATOR;
1658        texture += wxT("images");
1659        texture += wxCONFIG_PATH_SEPARATOR;
1660        texture += wxT("texture.png");
1661        if (!img.LoadFile(texture, wxBITMAP_TYPE_PNG)) {
1662            // FIXME
1663            fprintf(stderr, "Couldn't load image.\n");
1664            exit(1);
1665        }
1666
1667        // Generate mipmaps.
1668        gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGB, // was GL_LUMINANCE
1669                          img.GetWidth(), img.GetHeight(),
1670                          GL_RGB, GL_UNSIGNED_BYTE, img.GetData());
1671        CHECK_GL_ERROR("ToggleTextured", "gluBuild2DMipmaps");
1672
1673        glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
1674        CHECK_GL_ERROR("ToggleTextured", "glTexEnvi");
1675    }
1676}
1677
1678bool GLACanvas::SaveScreenshot(const wxString & fnm, wxBitmapType type) const
1679{
1680    const int width = x_size;
1681    const int height = y_size;
1682    unsigned char *pixels = (unsigned char *)malloc(3 * width * (height + 1));
1683    if (!pixels) return false;
1684    glReadPixels(0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, (GLvoid *)pixels);
1685    CHECK_GL_ERROR("SaveScreenshot", "glReadPixels");
1686    unsigned char * tmp_row = pixels + 3 * width * height;
1687    // We need to flip the image vertically - this approach should be more
1688    // efficient than using wxImage::Mirror(false) as that creates a new
1689    // wxImage object.
1690    for (int y = height / 2 - 1; y >= 0; --y) {
1691        unsigned char * upper = pixels + 3 * width * y;
1692        unsigned char * lower = pixels + 3 * width * (height - y - 1);
1693        memcpy(tmp_row, upper, 3 * width);
1694        memcpy(upper, lower, 3 * width);
1695        memcpy(lower, tmp_row, 3 * width);
1696    }
1697    // NB wxImage constructor calls free(pixels) for us.
1698    wxImage grab(width, height, pixels);
1699    return grab.SaveFile(fnm, type);
1700}
1701
1702bool GLACanvas::CheckVisualFidelity(const unsigned char * target) const
1703{
1704    unsigned char pixels[3 * 8 * 8];
1705    if (double_buffered) {
1706        glReadBuffer(GL_BACK);
1707        CHECK_GL_ERROR("FirstShow", "glReadBuffer");
1708    }
1709    glReadPixels(x_size / 2 - 4, y_size / 2 - 5, 8, 8,
1710                 GL_RGB, GL_UNSIGNED_BYTE, (GLvoid *)pixels);
1711    CHECK_GL_ERROR("CheckVisualFidelity", "glReadPixels");
1712    if (double_buffered) {
1713        glReadBuffer(GL_FRONT);
1714        CHECK_GL_ERROR("FirstShow", "glReadBuffer");
1715    }
1716#if 0
1717    // Show what got drawn and what was expected for debugging.
1718    for (int y = 0; y < 8; ++y) {
1719        for (int x = 0; x < 8; ++x) {
1720            int o = (y * 8 + x) * 3;
1721            printf("%c", pixels[o] ? 'X' : '.');
1722        }
1723        printf(" ");
1724        for (int x = 0; x < 8; ++x) {
1725            int o = (y * 8 + x) * 3;
1726            printf("%c", target[o] ? 'X' : '.');
1727        }
1728        printf("\n");
1729    }
1730#endif
1731    return (memcmp(pixels, target, sizeof(pixels)) == 0);
1732}
1733
1734void GLACanvas::ReadPixels(int width, int height, unsigned char * buf) const
1735{
1736    CHECK_GL_ERROR("ReadPixels", "glReadPixels");
1737    glReadPixels(0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, (GLvoid *)buf);
1738}
1739
1740void GLACanvas::PolygonOffset(bool on) const
1741{
1742    if (on) {
1743        glPolygonOffset(1.0, 1.0);
1744        glEnable(GL_POLYGON_OFFSET_FILL);
1745    } else {
1746        glDisable(GL_POLYGON_OFFSET_FILL);
1747    }
1748}
Note: See TracBrowser for help on using the repository browser.