summaryrefslogtreecommitdiff
path: root/cad/src/graphics/drawing/gl_shaders.py
blob: d4980cc4c006dc455f800f3efb781ac6de55350e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
# Copyright 2004-2009 Nanorex, Inc.  See LICENSE file for details.
"""
gl_shaders.py - OpenGL shader objects.

    For shader concepts and links, see the Wikipedia introductions at:
     http://en.wikipedia.org/wiki/GLSL
     http://en.wikipedia.org/wiki/Graphics_pipeline

    The GLSL quick reference card and language specification in PDF are useful:
     http://www.mew.cx/glsl_quickref.pdf
     http://www.opengl.org/registry/doc/GLSLangSpec.Full.1.20.8.pdf

    Some details about different "shader models", i.e. versions of the
    "GPU assembly language" behind GLSL:
     http://en.wikipedia.org/wiki/Comparison_of_ATI_graphics_processing_units#DirectX_version_note
     http://en.wikipedia.org/wiki/Shader_Model_3#Shader_model_comparison

    For the OpenGL interface, see the API man pages and ARB specifications:
     http://www.opengl.org/sdk/docs/man/
     http://oss.sgi.com/projects/ogl-sample/registry/ARB/vertex_shader.txt
     http://oss.sgi.com/projects/ogl-sample/registry/ARB/fragment_shader.txt
     http://oss.sgi.com/projects/ogl-sample/registry/ARB/shader_objects.txt

    Useful man pages for OpenGL 2.1 are at:
     http://www.opengl.org/sdk/docs/man

    PyOpenGL versions are at:
     http://pyopengl.sourceforge.net/ctypes/pydoc/OpenGL.html

@author: Russ Fish
@version: $Id$
@copyright: 2004-2009 Nanorex, Inc.  See LICENSE file for details.

History:

Russ 081002: Added some Transform state to GLSphereShaderObject, and code to
  notice when there is not enough constant memory for a matrix block
  there. Added get_TEXTURE_XFORMS() and get_N_CONST_XFORMS() methods.

  Moved GLSphereBuffer to its own file.  Much of its code goes to the new
  superclass, GLPrimitiveBuffer, and its helper classes HunkBuffer and Hunk.
  The setupTransforms() method stays in GLSphereShaderObject.  updateRadii() and
  sendRadii() disappeared, subsumed into the GLPrimitiveBuffer Hunk logic.

  In the sphere vertex shader, I combined center_pt and radius per-vertex
  attributes into one center_rad vec4 attribute, rather than using two vec4's
  worth of VBO memory, and added opacity to the color, changing it from a vec3
  to a vec4.  Drawing pattern vertices are now relative to the center_pt and
  scaled by the radius attributes, handled by the shader.

Russ 090116: Factored GLShaderObject out of GLSphereShaderObject, and added
  GLCylinderShaderObject.  The only difference between them at this level is the
  GLSL source passed in during initialization.
"""

# Whether to use texture memory for transforms, or a uniform array of mat4s,
# or neither (no support for transforms).

TEXTURE_XFORMS = False
UNIFORM_XFORMS = False
assert not (TEXTURE_XFORMS and UNIFORM_XFORMS)

SUPPORTS_XFORMS = TEXTURE_XFORMS or UNIFORM_XFORMS

# Note [bruce 090306]: Current code never uses TransformControl, so I revised
# things to permit turning off both kinds of transform support, and I'm doing
# that now in case it improves anything on any graphics cards to leave out the
# associated GLSL code. (We still have a tiny bit of GLSL code and one extra
# uniform which are not needed when transforms are not supported.
# That would be easy to clean up, but is not important for now.)

# When UNIFORM_XFORMS, use a fixed-sized block of uniform memory for transforms.
# (This is a max, refined using GL_MAX_VERTEX_UNIFORM_COMPONENTS_ARB later.)
N_CONST_XFORMS = 270  # (Gets CPU bound at 275.  Dunno why.)
# Used in fine-tuning N_CONST_XFORMS to leave room for other GPU vars and temps.
# If you increase the complexity of the vertex shader program a little bit, and
# rendering slows down by 100x, maybe you ran out of room.  Try increasing this.
_VERTEX_SHADER_GPU_VAR_SLACK = 110

# Turns on a debug info message.
CHECK_TEXTURE_XFORM_LOADING = False # True  ## Never check in a True value.
#
# Note: due to an OpenGL or PyOpengl Bug, can't read back huge transform
# textures due to SIGSEGV killing Python in glGetTexImage.
#
# Traceback on MacOS 10.5.2 with 2178 transforms:
#   Exception Type:  EXC_BAD_ACCESS (SIGSEGV)
#   Exception Codes: KERN_INVALID_ADDRESS at 0x00000000251f5000
#   Thread 0 Crashed:
#   0   libSystem.B.dylib   0xffff0884 __memcpy + 228
#   1   libGLImage.dylib    0x913e8ac6 glgProcessPixelsWithProcessor + 326
#   2   GLEngine            0x1e9dbace glGetTexImage_Exec + 1534
#   3   libGL.dylib         0x9170dd4f glGetTexImage + 127
#
# And while we're on the subject, there is a crash that kills Python while
# *writing* matrices to graphics card RAM.  I suspect that libGLImage is running
# out of texture memory space.  I've looked for a method to verify that there is
# enough left, but have not found one.  Killing a bloated FireFox has helped.
#
# Traceback on MacOS 10.5.2 with only 349 transforms requested in this case.
#   Exception Type:  EXC_BAD_ACCESS (SIGSEGV)
#   Exception Codes: KERN_INVALID_ADDRESS at 0x0000000013b88000
#   Thread 0 Crashed:
#   0   libSystem.B.dylib     0xffff08a0 __memcpy + 256
#   1   libGLImage.dylib      0x913ea5e9 glgCopyRowsWithMemCopy(
#           GLGOperation const*, unsigned long, GLDPixelMode const*) + 121
#   2   libGLImage.dylib      0x913e8ac6 glgProcessPixelsWithProcessor + 326
#   3   GLEngine              0x1ea16198 gleTextureImagePut + 1752
#   4   GLEngine              0x1ea1f896 glTexSubImage2D_Exec + 1350
#   5   libGL.dylib           0x91708cdb glTexSubImage2D + 155


from geometry.VQT import A, norm
import utilities.EndUser as EndUser

from graphics.drawing.sphere_shader import sphereVertSrc, sphereFragSrc
from graphics.drawing.cylinder_shader import cylinderVertSrc, cylinderFragSrc

from graphics.drawing.patterned_drawing import isPatternedDrawing

import foundation.env as env

from utilities.prefs_constants import hoverHighlightingColor_prefs_key
from utilities.prefs_constants import hoverHighlightingColorStyle_prefs_key
from utilities.prefs_constants import HHS_SOLID, HHS_HALO
from utilities.prefs_constants import selectionColor_prefs_key
from utilities.prefs_constants import selectionColorStyle_prefs_key
from utilities.prefs_constants import SS_SOLID, SS_HALO
from utilities.prefs_constants import haloWidth_prefs_key

from utilities.debug_prefs import debug_pref
from utilities.debug_prefs import Choice_boolean_False, Choice_boolean_True

import numpy

from OpenGL.GL import GL_FLOAT
from OpenGL.GL import GL_FRAGMENT_SHADER
from OpenGL.GL import GL_MAX_VERTEX_UNIFORM_COMPONENTS_ARB
#from OpenGL.GL import GL_NEAREST
from OpenGL.GL import GL_RGBA
from OpenGL.GL import GL_RGBA32F_ARB
from OpenGL.GL import GL_TEXTURE_2D
#from OpenGL.GL import GL_TEXTURE_MAG_FILTER
#from OpenGL.GL import GL_TEXTURE_MIN_FILTER
from OpenGL.GL import GL_TRUE
from OpenGL.GL import GL_VERTEX_SHADER
from OpenGL.GL import GL_VALIDATE_STATUS

from OpenGL.GL import glBindTexture
#from OpenGL.GL import glEnable
from OpenGL.GL import glGenTextures
from OpenGL.GL import glGetInteger
from OpenGL.GL import glGetTexImage
from OpenGL.GL import glTexImage2D
from OpenGL.GL import glTexSubImage2D

### Substitute the PyOpenGL 3.0.0b3 versions, which work on Windows as well as MacOS.
##from OpenGL.GL.ARB.shader_objects import glAttachObjectARB
##from OpenGL.GL.ARB.shader_objects import glCompileShaderARB
##from OpenGL.GL.ARB.shader_objects import glCreateProgramObjectARB
##from OpenGL.GL.ARB.shader_objects import glCreateShaderObjectARB
##from OpenGL.GL.ARB.shader_objects import glGetInfoLogARB
##from OpenGL.GL.ARB.shader_objects import glGetObjectParameterivARB
##from OpenGL.GL.ARB.shader_objects import glGetUniformLocationARB
##from OpenGL.GL.ARB.shader_objects import glGetUniformivARB
##from OpenGL.GL.ARB.shader_objects import glLinkProgramARB
##from OpenGL.GL.ARB.shader_objects import glShaderSourceARB
##from OpenGL.GL.ARB.shader_objects import glUniform1fARB
##from OpenGL.GL.ARB.shader_objects import glUniform1iARB
##from OpenGL.GL.ARB.shader_objects import glUniform3fvARB
##from OpenGL.GL.ARB.shader_objects import glUniform4fvARB
##from OpenGL.GL.ARB.shader_objects import glUniformMatrix4fvARB
##from OpenGL.GL.ARB.shader_objects import glUseProgramObjectARB
##from OpenGL.GL.ARB.shader_objects import glValidateProgramARB
##from OpenGL.GL.ARB.vertex_shader import glGetAttribLocationARB
from graphics.drawing.shader_objects_patch import glAttachObjectARB
from graphics.drawing.shader_objects_patch import glCompileShaderARB
from graphics.drawing.shader_objects_patch import glCreateProgramObjectARB
from graphics.drawing.shader_objects_patch import glCreateShaderObjectARB
from graphics.drawing.shader_objects_patch import glGetInfoLogARB
from graphics.drawing.shader_objects_patch import glGetObjectParameterivARB
from graphics.drawing.shader_objects_patch import glGetUniformLocationARB
from graphics.drawing.shader_objects_patch import glGetUniformivARB
from graphics.drawing.shader_objects_patch import glLinkProgramARB
from graphics.drawing.shader_objects_patch import glShaderSourceARB
from graphics.drawing.shader_objects_patch import glUniform1fARB
from graphics.drawing.shader_objects_patch import glUniform1iARB
from graphics.drawing.shader_objects_patch import glUniform3fvARB
from graphics.drawing.shader_objects_patch import glUniform4fvARB
from graphics.drawing.shader_objects_patch import glUniformMatrix4fvARB
from graphics.drawing.shader_objects_patch import glUseProgramObjectARB
from graphics.drawing.shader_objects_patch import glValidateProgramARB
from graphics.drawing.vertex_shader_patch import glGetAttribLocationARB

_warnedVars = {}

# Drawing_style constants.
DS_NORMAL = 0
DS_OVERRIDE_COLOR = 1
DS_PATTERN = 2
DS_HALO = 3

class GLShaderObject(object):
    """
    Base class for managing OpenGL shaders.
    """

    # default values of per-subclass constants

    _has_uniform_debug_code = False
        # whether this shader has the uniform variable 'debug_code'
        # (a boolean which enables debug behavior)
        # (it might be better to set this by asking OpenGL whether
        #  that uniform exists, after loading the shader source)

    # initial values of instance variables

    error = False # set for shader creation/compilation error
    _active = False # whether this is GL's current shader now

    # Cached info for blocks of transforms.
    n_transforms = None        # Size of the block.
    transform_memory = None    # Texture memory handle.

    def __init__(self, shaderName, shaderVertSrc, shaderFragSrc):

        # note: on any error, we set self.error, print a message, and return.
        # exceptions will be caught by caller and also set self.error,
        # but result in less specific printed error messages.

        # Configure the max constant RAM used for a "uniform" transforms block.
        if UNIFORM_XFORMS:
            global N_CONST_XFORMS
            oldNCX = N_CONST_XFORMS
            maxComponents = glGetInteger(GL_MAX_VERTEX_UNIFORM_COMPONENTS_ARB)
            N_CONST_XFORMS = min(
                N_CONST_XFORMS,
                # Each matrix has 16 components. Leave slack for other vars too.
                (maxComponents - _VERTEX_SHADER_GPU_VAR_SLACK) / 16)
            if N_CONST_XFORMS <= 0:
                print (
                    "N_CONST_XFORMS not positive, is %d. %d max components." %
                    (N_CONST_XFORMS, maxComponents))

                # Now, we think this means we should use display lists instead.
                print "error: not enough shader constant memory"
                self.error = True
                return

            elif N_CONST_XFORMS == oldNCX:
                print ("N_CONST_XFORMS unchanged at %d. %d max components." %
                       (N_CONST_XFORMS, maxComponents))
            else:
                print (
                    "N_CONST_XFORMS changed to %d, was %d. %d max components." %
                       (N_CONST_XFORMS, oldNCX, maxComponents))
                pass
            pass

        # Version statement has to come first in GLSL source.
        prefix = """// requires GLSL version 1.20
                    #version 120
                    """
        # Insert preprocessor constants before both shader source code strings
        # (using a constant number of lines, to preserve GLSL line numbers)
        if UNIFORM_XFORMS:
            prefix += "#define UNIFORM_XFORMS\n"
            prefix += "#define N_CONST_XFORMS %d\n" % N_CONST_XFORMS
        elif TEXTURE_XFORMS:
            prefix += "#define TEXTURE_XFORMS\n"
            prefix += "\n"
        else:
            prefix += "\n\n"
            pass

        # GLSL on the nVidia GeForce 7000 only supports constant array
        # subscripts, and subscripting by a loop index variable.
        if not debug_pref("GLPane: shaders with only constant subscripts?",
                      Choice_boolean_True, prefs_key = True):
            prefix += "#define FULL_SUBSCRIPTING\n"
        else:
            prefix += "\n" # To keep the shader line numbers unchanged.
            pass

        if debug_pref("GLPane: simulate GLSL syntax error (next session)",
                      Choice_boolean_False, prefs_key = True):
            prefix += "}\n"

        # remove whitespace before and after each prefix line [bruce 090306]
        prefix = '\n'.join( [line.strip() for line in prefix.split('\n')] )
        assert prefix[-1] == '\n'

        # Pass the source strings to the shader compiler.
        self.vertShader = self.createShader(shaderName, GL_VERTEX_SHADER,
                                            prefix + shaderVertSrc)
        self.fragShader = self.createShader(shaderName, GL_FRAGMENT_SHADER,
                                            prefix + shaderFragSrc)
        if self.error:          # May be set by createShader.
            return              # Can't do anything good after an error.
        # Link the compiled shaders into a shader program.
        self.progObj = glCreateProgramObjectARB()
        glAttachObjectARB(self.progObj, self.vertShader)
        glAttachObjectARB(self.progObj, self.fragShader)
        try:
            glLinkProgramARB(self.progObj) # Checks status, raises error if bad.
        except:
            self.error = True
            print shaderName, "shader program link error"
            print glGetInfoLogARB(self.progObj)
            return              # Can't do anything good after an error.

        # Optional, may be useful for debugging.
        glValidateProgramARB(self.progObj)
        status = glGetObjectParameterivARB(self.progObj, GL_VALIDATE_STATUS)
        if (not status):
            self.error = True
            print "Shader program validation error"
            print glGetInfoLogARB(self.progObj)
            return              # Can't do anything good after an error.

        return

    def createShader(self, shaderName, shaderType, shaderSrc):
        """
        Create, load, and compile a shader.
        """
        shader = glCreateShaderObjectARB(shaderType)
        glShaderSourceARB(shader, shaderSrc)
        try:
            glCompileShaderARB(shader)    # Checks status, raises error if bad.
        except:
            self.error = True
            types = {GL_VERTEX_SHADER:"vertex", GL_FRAGMENT_SHADER:"fragment"}
            print ("\n%s %s shader program compilation error" %
                   (shaderName, types[shaderType]))
            print glGetInfoLogARB(shader)
            pass
        return shader

    def configShader(self, glpane):
        """
        Fill in uniform variables in the shader self, before using it to draw.

        @param glpane: The current glpane, containing NE1 graphics context
            information related to the drawing environment. This is used to
            find proper values for uniform variables we set in the shader.
        """
        # Can't do anything good after an error loading the shader programs.
        if self.error:
            return

        # Shader needs to be active to set uniform variables.
        wasActive = self._active
        if not wasActive:
            self.setActive(True)
            pass

        # Debugging control.
        if self._has_uniform_debug_code:
            # review: use _has_uniform("debug_code") instead?
            glUniform1iARB(
                self._uniform("debug_code"),
                int(debug_pref("GLPane: shader debug graphics?",
                               Choice_boolean_False, prefs_key = True)))

        # Default override_opacity, multiplies the normal color alpha component.
        glUniform1fARB(self._uniform("override_opacity"), 1.0)

        # Russ 081208: Consider caching the glpane pointer.  GLPane_minimal
        # inherits from QGLWidget, which includes the OpenGL graphics context.
        # Currently we share 'display list context' and related information
        # across two kinds of OpenGL contexts, the main GLPane and the
        # ThumbViews used to select atom types, show clipboard parts, and maybe
        # more.  In the future it may be more complicated.  Then we may need to
        # be more specific about accounting for what's in particular contexts.

        # XXX Hook in full NE1 lighting scheme and material settings.
        # Material is [ambient, diffuse, specular, shininess].
        glUniform4fvARB(self._uniform("material"), 1, [0.3, 0.6, 0.5, 20.0])
        glUniform1iARB(self._uniform("perspective"), (1, 0)[glpane.ortho])

        # See GLPane._setup_projection().
        vdist = glpane.vdist
        # See GLPane_minimal.setDepthRange_Normal().
        near = vdist * (glpane.near + glpane.DEPTH_TWEAK)
        far = vdist * glpane.far
        glUniform4fvARB(self._uniform("clip"), 1,
                        [near, far, 0.5*(far + near), 1.0/(far - near)])
        # The effect of setDepthRange_Highlighting() is done as the shaders
        # set the gl_FragDepth during a highlighted drawing style.
        glUniform1fARB(self._uniform("DEPTH_TWEAK"), glpane.DEPTH_TWEAK)

        # Pixel width of window for halo drawing calculations.
        self.window_width = glpane.width

        # Single light for now.
        # XXX Get NE1 lighting environment state.
        glUniform4fvARB(self._uniform("intensity"), 1, [1.0, 0.0, 0.0, 0.0])
        light0 = A([-1.0, 1.0, 1.0])
        glUniform3fvARB(self._uniform("light0"), 1, light0)
        # Blinn shading highlight vector, halfway between the light and the eye.
        eye = A([0.0, 0.0, 1.0])
        halfway0 = norm((eye + light0) / 2.0)
        glUniform3fvARB(self._uniform("light0H"), 1, halfway0)

        if not wasActive:
            self.setActive(False)
        return

    def setupDraw(self, highlighted = False, selected = False,
             patterning = True, highlight_color = None, opacity = 1.0):
        """
        Set up for hover-highlighting and selection drawing styles.
        There is similar code in CSDL.draw(), which has similar arguments.

        XXX Does Solid and halo now, need to implement patterned drawing too.
        """
        # Shader needs to be active to set uniform variables.
        wasActive = self._active
        if not wasActive:
            self.setActive(True)
            pass

        patterned_highlighting = (False and # XXX
                                  patterning and
                                  isPatternedDrawing(highlight = highlighted))
           # note: patterned_highlighting variable is not yet used here [bruce 090304 comment]

        halo_selection = (selected and
                          env.prefs[selectionColorStyle_prefs_key] == SS_HALO)
        halo_highlighting = (highlighted and
                             env.prefs[hoverHighlightingColorStyle_prefs_key] ==
                             HHS_HALO)

        # Halo drawing style is used for hover-highlighing and halo-selection.
        drawing_style = DS_NORMAL       # Solid drawing by default.
        if halo_highlighting or halo_selection:
            drawing_style = DS_HALO

            # Halo drawing was first implemented with wide-line drawing, which
            # extends to both sides of the polygon edge.  The halo is actually
            # half the wide-line width that is specified by the setting.
            # XXX The setting should be changed to give the halo width instead.
            halo_width = env.prefs[haloWidth_prefs_key] / 2.0

            # The halo width is specified in viewport pixels, as is the window
            # width the viewport transform maps onto.  In post-projection and
            # clipping normalized device coords (+-1), it's a fraction of the
            # window half-width of 1.0 .
            ndc_halo_width = halo_width / (self.window_width / 2.0)
            glUniform1fARB(self._uniform("ndc_halo_width"), ndc_halo_width)

        elif highlighted or selected:
            drawing_style = DS_NORMAL   # Non-halo highlighting or selection.
        glUniform1iARB(self._uniform("drawing_style"), drawing_style)

        # Color for selection or highlighted drawing.
        override_color = None
        if highlighted:
            if highlight_color is None: # Default highlight color.
                override_color = env.prefs[hoverHighlightingColor_prefs_key]
            else:                       # Highlight color passed as an argument.
                override_color = highlight_color
        elif selected:
            override_color = env.prefs[selectionColor_prefs_key]
            pass
        if override_color:
            if len(override_color) == 3:
                override_color += (opacity,)
                pass
            glUniform4fvARB(self._uniform("override_color"), 1, override_color)
            pass

        if not wasActive:
            self.setActive(False)
            pass
        return

    def setPicking(self, tf):
        """
        Controls glnames-as-color drawing mode for mouseover picking.

        There seems to be no way to access the GL name
        stack in shaders.  Instead, for mouseover, draw shader
        primitives with glnames as colors in glRenderMode(GL_RENDER),
        then read back the pixel color (glname) and depth value.

        @param tf: Boolean, draw glnames-as-color if True.
        """
        # Shader needs to be active to set uniform variables.
        wasActive = self._active
        if not wasActive:
            self.setActive(True)
            pass

        glUniform1iARB(self._uniform("draw_for_mouseover"), int(tf))

        if not wasActive:
            self.setActive(False)
            pass
        return

    def _has_uniform(self, name): #bruce 090309
        """
        @return: whether the specified uniform (input)
                 shader variable has a location.
        @rtype: boolean

        @note: this can differ for different platforms based on the
            level of optimization done by their implementation of GLSL
            (if the uniform is allocated but not used by our shaders).

        @see: _uniform

        """
        loc = glGetUniformLocationARB(self.progObj, name)
        return (loc != -1)

    def _uniform(self, name): #bruce 090302 renamed from self.uniform()
        """
        Return location of a uniform (input) shader variable.
        If it's not found, return -1 and warn once per session for end users,
        or raise an AssertionError for developers.

        @see: _has_uniform
        """
        loc = glGetUniformLocationARB(self.progObj, name)
        if loc == -1:
            # The glGetUniformLocationARB man page says:
            #   "name must be an active uniform variable name in program that is
            #    not a structure, an array of structures, or a subcomponent of a
            #    vector or a matrix."
            #
            # It "returns -1 if the name does not correspond to an active
            # uniform variable in program".  Then getUniform ignores it: "If
            # location is equal to -1, the data passed in will be silently
            # ignored and the specified uniform variable will not be changed."
            #
            # Lets not be so quiet.
            msg = "Invalid or unused uniform shader variable name '%s'." % name
            if EndUser.enableDeveloperFeatures():
                # Developers want to know of a mismatch between their Python
                # and shader programs as soon as possible.  Not doing so causes
                # logic errors, or at least incorrect assumptions, to silently
                # propagate through the code and makes debugging more difficult.
                assert 0, msg
            else:
                # End users on the other hand, may value program stability more.
                # Just print a warning if the program is released.  Do it only
                # once per session, since this will be in the inner draw loop!
                global _warnedVars
                if name not in _warnedVars:
                    print "Warning:", msg
                    _warnedVars += [name]
                    pass
                pass
            pass
        return loc

    def getUniformInt(self, name):
        """
        Return value of a uniform (input) shader variable.
        """
        return glGetUniformivARB(self.progObj, self._uniform(name))

    def _has_shader_attribute(self, name): #bruce 090309 [untested, not currently used]
        """
        @return: whether the specified attribute (per-vertex input)
                 shader variable has a location.
        @rtype: boolean

        @note: this can differ for different platforms based on the
            level of optimization done by their implementation of GLSL
            (if the attribute is allocated but not used by our shaders).

        @see: attributeLocation
        """
        loc = glGetAttribLocationARB(self.progObj, name)
        return (loc != -1)

    def attributeLocation(self, name): #bruce 090302 renamed from self.attribute()
        """
        Return location of an attribute (per-vertex input) shader variable.
        If it's not found, return -1 and warn once per session for end users,
        or raise an AssertionError for developers.

        @see: _has_shader_attribute
        """
        loc = glGetAttribLocationARB(self.progObj, name)
        if loc == -1:
            msg = "Invalid or unused attribute variable name '%s'." % name
            if EndUser.enableDeveloperFeatures():
                # Developers want to know of a mismatch between their Python
                # and shader programs as soon as possible.  Not doing so causes
                # logic errors, or at least incorrect assumptions, to silently
                # propagate through the code and makes debugging more difficult.
                assert 0, msg
            else:
                # End users on the other hand, may value program stability more.
                # Just print a warning if the program is released.  Do it only
                # once per session, since this will be in the inner draw loop!
                global _warnedVars
                if name not in _warnedVars:
                    print "Warning:", msg
                    _warnedVars += [name]
                    pass
                pass
            pass
        return loc

    def setActive(self, on): #bruce 090302 renamed from self.use()
        """
        Activate or deactivate a shader.
        """
        if on:
            glUseProgramObjectARB(self.progObj)
        else:
            glUseProgramObjectARB(0)
            pass
        self._active = on
        return

    def get_TEXTURE_XFORMS(self):
        return TEXTURE_XFORMS

    def get_UNIFORM_XFORMS(self):
        return UNIFORM_XFORMS

    def get_N_CONST_XFORMS(self):
        return N_CONST_XFORMS

    def supports_transforms(self): #bruce 090309
        return SUPPORTS_XFORMS

    def setupTransforms(self, transforms):
        # note: this is only called from test_drawing.py (as of before 090302)
        """
        Fill a block of transforms.

        Depending on the setting of TEXTURE_XFORMS and UNIFORM_XFORMS, the
        transforms are either in texture memory, or in a uniform array of mat4s
        ("constant memory"), or unsupported (error if we need any here).

        @param transforms: A list of transform matrices, where each transform is
            a flattened list (or Numpy array) of 16 numbers.
        """
        self.n_transforms = nTransforms = len(transforms)

        if not self.supports_transforms():
            assert not nTransforms, "%r doesn't support transforms" % self
            return

        self.setActive(True)                # Must activate before setting uniforms.

        assert self._has_uniform("n_transforms") # redundant with following
        glUniform1iARB(self._uniform("n_transforms"), self.n_transforms)

        # The shader bypasses transform logic if n_transforms is 0.
        # (Then location coordinates are in global modeling coordinates.)
        if nTransforms > 0:
            if UNIFORM_XFORMS:
                # Load into constant memory.  The GL_EXT_bindable_uniform
                # extension supports sharing this array of mat4s through a VBO.
                # XXX Need to bank-switch this data if more than N_CONST_XFORMS.
                C_transforms = numpy.array(transforms, dtype = numpy.float32)
                glUniformMatrix4fvARB(self._uniform("transforms"),
                                      # Don't over-run the array size.
                                      min(len(transforms), N_CONST_XFORMS),
                                      GL_TRUE, # Transpose.
                                      C_transforms)
            elif TEXTURE_XFORMS:
                # Generate a texture ID and bind the texture unit to it.
                self.transform_memory = glGenTextures(1)
                glBindTexture(GL_TEXTURE_2D, self.transform_memory)
                ## These seem to have no effect with a vertex shader.
                ## glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
                ## glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
                # XXX Needed? glEnable(GL_TEXTURE_2D)

                # Load the transform data into the texture.
                #
                # Problem: SIGSEGV kills Python in gleTextureImagePut under
                # glTexImage2D with more than 250 transforms (16,000 bytes.)
                # Maybe there's a 16-bit signed size calculation underthere, that's
                # overflowing the sign bit... Work around by sending transforms to
                # the texture unit in batches with glTexSubImage2D.)
                glTexImage2D(GL_TEXTURE_2D, 0, # Level zero - base image, no mipmap.
                             GL_RGBA32F_ARB,   # Internal format is floating point.
                             # Column major storage: width = N, height = 4 * RGBA.
                             nTransforms, 4 * 4, 0, # No border.
                             # Data format and type, null pointer to allocate space.
                             GL_RGBA, GL_FLOAT, None)
                # XXX Split this off into a setTransforms method.
                batchSize = 250
                nBatches = (nTransforms + batchSize-1) / batchSize
                for i in range(nBatches):
                    xStart = batchSize * i
                    xEnd = min(nTransforms, xStart + batchSize)
                    xSize = xEnd - xStart
                    glTexSubImage2D(GL_TEXTURE_2D, 0,
                                    # Subimage x and y offsets and sizes.
                                    xStart, 0, xSize, 4 * 4,
                                    # List of matrices is flattened into a sequence.
                                    GL_RGBA, GL_FLOAT, transforms[xStart:xEnd])
                    continue
                # Read back to check proper loading.
                if CHECK_TEXTURE_XFORM_LOADING:
                    mats = glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_FLOAT)
                    nMats = len(mats)
                    print "setupTransforms\n[[",
                    for i in range(nMats):
                        nElts = len(mats[i])
                        perLine = 8
                        nLines = (nElts + perLine-1) / perLine
                        for line in range(nLines):
                            jStart = perLine * line
                            jEnd = min(nElts, jStart + perLine)
                            for j in range(jStart, jEnd):
                                print "%.2f" % mats[i][j],
                                continue
                            if line < nLines-1:
                                print "\n  ",
                                pass
                        if i < nMats-1:
                            print "]\n [",
                            pass
                        continue
                    print "]]"
                pass
            else:
                # should never happen if SUPPORTS_XFORMS is defined correctly
                assert 0, "can't setupTransforms unless UNIFORM_XFORMS or TEXTURE_XFORMS is set"
            pass
        self.setActive(False)                # Deactivate again.
        return

    pass # End of class GLShaderObject.

# ==

class GLSphereShaderObject(GLShaderObject):
    """
    An analytic sphere shader.

    This raster-converts analytic spheres, defined by a center point and radius.
    (Some people call that a ray-tracer, but unlike a real ray-tracer it can't
    send rays through the scene for reflection/refraction, nor toward the lights
    to compute shadows.)
    """
    def __init__(self):
        super(GLSphereShaderObject, self).__init__(
            "Sphere", sphereVertSrc, sphereFragSrc)
        return
    pass # End of class GLSphereShaderObject.

class GLCylinderShaderObject(GLShaderObject):
    """
    An analytic cylinder shader.

    This shader raster-converts analytic tapered cylinders, defined by two
    end-points determining an axis line-segment, and two radii at the
    end-points.  A constant-radius cylinder is tapered by perspective anyway, so
    the same shader does cones as well as cylinders.

    The rendered cylinders are smooth, with no polygon facets.  Exact shading, Z
    depth, and normals are calculated in parallel in the GPU for each pixel.

    (Some people call that a ray-tracer, but unlike a real ray-tracer it can't
    send rays through the scene for reflection/refraction, nor toward the lights
    to compute shadows.)
    """
    _has_uniform_debug_code = True
    def __init__(self):
        super(GLCylinderShaderObject, self).__init__(
            "Cylinder", cylinderVertSrc, cylinderFragSrc)
        return
    pass # End of class GLCylinderShaderObject.