summaryrefslogtreecommitdiff
path: root/cad/src/graphics/drawing/glprefs.py
blob: ac2496374330d30bb7b0281dc2ff665c667f9b0e (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
# Copyright 2004-2009 Nanorex, Inc.  See LICENSE file for details. 
"""
glprefs.py - class GLPrefs encapsulates prefs affecting all Chunk display lists

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

History:

080519 russ pulled the globals into a drawing_globals module and broke drawer.py
into 10 smaller chunks: glprefs.py setup_draw.py shape_vertices.py
ColorSorter.py CS_workers.py c_renderer.py CS_draw_primitives.py drawers.py
gl_lighting.py gl_buffers.py.

090303 bruce refactored this and related code, pulling in shader prefs.
"""

from utilities.prefs_constants import material_specular_highlights_prefs_key
from utilities.prefs_constants import material_specular_shininess_prefs_key
from utilities.prefs_constants import material_specular_finish_prefs_key
from utilities.prefs_constants import material_specular_brightness_prefs_key
from utilities.prefs_constants import selectionColorStyle_prefs_key
from utilities.prefs_constants import selectionColor_prefs_key
from utilities.prefs_constants import haloWidth_prefs_key

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

import foundation.env as env

from graphics.drawing.c_renderer import quux_module_import_succeeded

# ==

_choices = [Choice_boolean_False, Choice_boolean_True]

# ==

class GLPrefs:
    """
    This has not yet been split into its abstract and concrete classes,
    but is best described by specifying both aspects:
    
    Abstract: instances contain cached values of preferences or other settings
    which affect overall appearance of a GLPane, or content of most or all of
    its Chunks' display lists. There are public attributes for the prefs values,
    and public methods for setting up debug_prefs and updating cached prefs
    values. The update method should be called near the beginning of paintGL
    in order that the other methods and attributes have up to date values.
    Chunks can extract a change indicator (by calling materialprefs_summary)
    which combines all prefs values in which any changes need to invalidate
    all Chunk display lists.

    Concrete: the prefs/settings values are derived from specific env.prefs
    keys and/or debug_prefs. Only one instance is needed, and any number of
    GLPanes (or other "graphics environments") can use it (in current code,
    all GLPane_minimals and nothing else uses it).
    """
    # grantham 20051118; revised by bruce 051126, 090303

    # review: rename?? can't rename module to GLPrefs.py, might confuse svn on Macs.
    # gl_material_and_shader_prefs?? what is the general kind of prefs that belong here?
    # note: apply_material should probably be moved here (and perhaps made into
    # a method of this class and then of glpane). See comment near it for why.
    # [bruce 090304 comment]

    # ==
    
    # class constants and initial values of instance variables

    # Prefs related to experimental native C renderer (quux module in
    # cad/src/experimental/pyrex-opengl)
    #
    # [note: as of 090114, this hasn't been maintained for a long time,
    #  but it might still mostly work, so we'll keep it around for now
    #  as potentially useful example code]
    
    use_c_renderer = use_c_renderer_default = False
    use_c_renderer_prefs_key = "use_c_renderer_rev2"
    
    # ColorSorter control prefs (some)

    #russ 080403: Added drawing variant selection for unbatched spheres.
    use_drawing_variant = use_drawing_variant_default = 1 # DrawArrays from CPU RAM.
    use_drawing_variant_prefs_key = "use_drawing_variant"

    #russ 080819: Added.
    _use_sphere_shaders = use_sphere_shaders_default = True #bruce 090225 revised
    use_sphere_shaders_prefs_key = "v1.2/GLPane: use_sphere_shaders"

    #russ 090116: Added.
    _use_cylinder_shaders = use_cylinder_shaders_default = True #bruce 090303 revised
    use_cylinder_shaders_prefs_key = "v1.2/GLPane: use_cylinder_shaders"

    #russ 090223: Added.
    _use_cone_shaders = use_cone_shaders_default = True #bruce 090225 revised
    use_cone_shaders_prefs_key = "v1.2/GLPane: use_cone_shaders"

    # Russ 081002: Added.
    _use_batched_primitive_shaders = use_batched_primitive_shaders_default = True #bruce 090225 revised
    use_batched_primitive_shaders_prefs_key = "v1.2/GLPane: use_batched_primitive_shaders"

    # ==

    def __init__(self):
        self._setup()
        self.update()
        return

    # == setup methods

    def _setup(self):
        """
        Call this once at startup to make sure the appropriate
        debug_prefs (if any) and env.prefs default values are defined.

        If these are all changed into prefs_constants prefs
        rather than debug_prefs, this function won't be needed.
        """
        self._setup_c_renderer_prefs()

        # material prefs don't need setup since they're all in prefs_constants
        # which we import. They don't have class constant default values above
        # since they are all set in self.update() which this method calls.

        ##  self.override_material_specular = None
        ##     # Set 4-element sequence to override material specular component.
        ##  self.override_shininess = None
        ##     # If exists, overrides shininess.
        ##  self.override_light_specular = None
        ##     # Set to 4-element sequence to override light specular component.

        self._setup_shader_prefs()

        return
    
    def _setup_c_renderer_prefs(self):
        # 20060313 grantham Added use_c_renderer debug pref, can
        # take out when C renderer used by default.

        # REVIEW: is this still next-session, or does it work fine for same session now?
        # Guess: works for same-session after today's refactoring (once it works again at all).
        # So I modified the debug_pref menu text optimistically. [bruce 090304]
        
        if quux_module_import_succeeded:
            self.use_c_renderer = (
                debug_pref("GLPane: use native C renderer?", #bruce 090304 revised text
                           _choices[self.use_c_renderer_default],
                           prefs_key = self.use_c_renderer_prefs_key))
                #bruce 060323 removed non_debug = True for A7 release, and changed
                # its prefs_key so developers start over with the default value.

        return

    def _setup_shader_prefs(self):
        # note: as of bruce 090304 these all should work as same-session prefs,
        # and most of them have been tested that way.
        
        self.use_sphere_shaders_pref = debug_pref(
            "GLPane: use sphere-shaders?",
            _choices[self.use_sphere_shaders_default],
            non_debug = True,
            prefs_key = self.use_sphere_shaders_prefs_key )

        self.use_cylinder_shaders_pref = debug_pref(
            "GLPane: use cylinder-shaders?",
            _choices[self.use_cylinder_shaders_default],
            non_debug = True,
            prefs_key = self.use_cylinder_shaders_prefs_key )

        self.use_cone_shaders_pref = debug_pref(
            "GLPane: use cone-shaders?",
            _choices[self.use_cone_shaders_default],
            non_debug = True,
            prefs_key = self.use_cone_shaders_prefs_key )

        self.use_batched_primitive_shaders_pref = debug_pref(
            "GLPane: use batched primitive shaders?",
            _choices[ self.use_batched_primitive_shaders_default],
            non_debug = True,
            prefs_key = self.use_batched_primitive_shaders_prefs_key )

        #russ 080403: Added drawing variant selection for unbatched spheres
        # (update, bruce 090304: mainly of historical interest or for testing,
        #  but does matter on older machines that can't use shaders;
        #  could be extended to affect other primitives, but hasn't been
        #  as of 090304)
        variants = [
            "0. OpenGL 1.0 - glBegin/glEnd tri-strips vertex-by-vertex.",
            "1. OpenGL 1.1 - glDrawArrays from CPU RAM.",
            "2. OpenGL 1.1 - glDrawElements indexed arrays from CPU RAM.",
            "3. OpenGL 1.5 - glDrawArrays from graphics RAM VBO.",
            "4. OpenGL 1.5 - glDrawElements, verts in VBO, index in CPU.",
            "5. OpenGL 1.5 - VBO/IBO buffered glDrawElements.",
         ]
        self.use_drawing_variant = debug_pref(
            "GLPane: drawing method (unbatched spheres)",
            Choice(names = variants,
                   values = range(len(variants)),
                   defaultValue = self.use_drawing_variant_default),
            prefs_key = self.use_drawing_variant_prefs_key)
        return

    # ==
    
    def update(self): #bruce 051126, revised 090303
        """
        Update attributes from current drawing-related prefs stored in prefs db
        cache. This should be called at the start of each complete redraw, or
        whenever the user changes these global prefs values (whichever is more
        convenient).

        It is ok to call this more than once at those times --
        this will be a slight slowdown but cause no other harm. (Current code
        does this because more than one glpane can share one GLPrefs object,
        and because of a kluge which requires GLPane itself to update it twice.)

        (Note: When this is called during redraw, its prefs db accesses (like
        any others) record those prefs as potentially affecting what should be
        drawn, so that subsequent changes to those prefs values cause an
        automatic gl_update.)

        Using these attributes in drawing code (rather than directly accessing
        prefs db cache) is desirable for efficiency, since direct access to
        prefs db cache is a bit slow.  (Our drawing code still does that in
        other places -- those might also benefit from this system, though this
        will someday be moot when low-level drawing code gets rewritten in C.)
        """
        self._update_c_renderer_prefs()
        self._update_material_prefs()
        self._update_shader_prefs()
        return

    def _update_c_renderer_prefs(self):
        self.use_c_renderer = (
            quux_module_import_succeeded and
            env.prefs.get(self.use_c_renderer_prefs_key,
                          self.use_c_renderer_default)
         )

        if self.use_c_renderer:
            import quux
            quux.shapeRendererSetMaterialParameters(self.specular_whiteness,
                                                    self.specular_brightness,
                                                    self.specular_shininess )
            pass
        return

    def _update_material_prefs(self):
        self.enable_specular_highlights = not not env.prefs[
            material_specular_highlights_prefs_key] # boolean
        
        if self.enable_specular_highlights:
            self.override_light_specular = None # used in glpane
            # self.specular_shininess: float; shininess exponent for all
            # specular highlights
            self.specular_shininess = float(
                env.prefs[material_specular_shininess_prefs_key])
            # self.specular_whiteness: float; whiteness for all material
            # specular colors
            self.specular_whiteness = float(
                env.prefs[material_specular_finish_prefs_key])
            # self.specular_brightness: float; for all material specular colors
            self.specular_brightness = float(
                env.prefs[material_specular_brightness_prefs_key])
        else:
            self.override_light_specular = (0.0, 0.0, 0.0, 0.0) # used in glpane
            # Set these to reasonable values, though these attributes are
            # presumably never used in this case.  Don't access the prefs db in
            # this case, since that would cause UI prefs changes to do
            # unnecessary gl_updates.  (If we ever add a scenegraph node which
            # can enable specular highlights but use outside values for these
            # parameters, then to make it work correctly we'll need to revise
            # this code.)
            self.specular_shininess = 20.0
            self.specular_whiteness = 1.0
            self.specular_brightness = 1.0
            pass
        return

    def _update_shader_prefs(self):

        # Russ 080915: Extracted from GLPrefs.update() to break an import cycle.
        #
        # bruce 090303 renamed this from updatePrefsVars, refactored it
        # from a global function to a method of this class, and heavily
        # revised related code.
        
        # implem note: this is slightly faster than directly calling debug_pref
        # (though that needs to be done the first time, via _setup_shader_prefs);
        # and as of 090303 the split between this and _setup_shader_prefs is
        # confined to this module, so if we change some of these to non-debug_prefs
        # no external code should need to be changed. [bruce 090303 comment]
        
        self._use_sphere_shaders = env.prefs.get(
            self.use_sphere_shaders_prefs_key,
            self.use_sphere_shaders_default)

        self._use_cylinder_shaders = env.prefs.get(
            self.use_cylinder_shaders_prefs_key,
            self.use_cylinder_shaders_default)

        self._use_cone_shaders = env.prefs.get(
            self.use_cone_shaders_prefs_key,
            self.use_cone_shaders_default)

        self._use_batched_primitive_shaders = env.prefs.get(
            self.use_batched_primitive_shaders_prefs_key,
            self.use_batched_primitive_shaders_default)

        self.use_drawing_variant = env.prefs.get(
            self.use_drawing_variant_prefs_key,
            self.use_drawing_variant_default)

        return

    # ==
    
    def materialprefs_summary(self): #bruce 051126  ### TODO: RENAME; refactor to connect more strongly to apply_material
        """
        Return a Python data object (which can be correctly compared using '==')
        summarizing our cached pref values which affect the OpenGL output of
        apply_material. OpenGL display lists whose content includes output from
        apply_material need to be remade if our output differs from when they
        were first made.

        Note that any env.prefs value accessed directly (via env.prefs[key] or
        debug_pref()) when making a DL needn't be included in our output, since
        those accesses are usage-tracked and anything making a DL can
        independently subscribe to changes in them (see begin_tracking_usage).
        The only prefs values that doesn't work for are the ones cached in self,
        so only those values need to be included in this method's output.

        Note also that CSDLs (ColorSortedDisplayLists) may depend on fewer
        prefs values, since they are likely to use apply_material when drawn
        rather than when made. So our output may be overkill if used to
        invalidate them in the same circumstances in which it would invalidate
        a plain OpenGL DL.

        @note: only valid if self.update() has been called recently.
        """
        # bruce 090305 rewrote docstring to explain the purpose better.
        
        #### todo: optimizations (not urgent except for user experience
        #      when adjusting these prefs on a large model):
        #
        # * remove prefs from our output which are not needed according to the
        #   docstring above, as rewritten 090305.
        #
        # * if some prefs are only needed here for DLs but not for CSDLs,
        #   revise callers accordingly. (AFAIK no revision in this class itself
        #   makes sense then, unless we need to add a new analgous method to
        #   correspond to a new way of accessing a subset of our prefs values,
        #   for making CSDLs. But AFAIK, our values are used only for *drawing*
        #   CSDL shader primitives, never for *making* them (though they may be
        #   used for making CSDLs using DL primitives rather than shader
        #   primitives), so we're simply not needed in ChunkDrawer.havelist
        #   when it's not using apply_material in any plain DLs. Note that
        #   whether it's doing that or not depends on prefs settings normally
        #   only used inside ColorSorter, so it probably wants to ask
        #   ColorSorter for a "prefs summary" applicable to how it's making
        #   CSDLs at that time. This would always include
        #   self._use_batched_primitive_shaders, and when that's false,
        #   also include the "material prefs" which this method used to only
        #   include.)
        #
        # * clients and/or this method should condense the summary tuple
        #   into a single change indicator/counter, by comparing the tuple
        #   to the last one used. Clients can do this better in principle
        #   (if we're shared by several GLPanes which don't always redraw
        #   together).
        #
        # * include values in our output only when they matter based on
        #   other values. This is probably true already for the actual
        #   "material prefs".
        #
        # Summary: much of what we output is not needed, and needlessly
        # slows down some prefs changes, but this takes some analysis to fix.
        # If in doubt, including too much is better since it only slows down
        # prefs changes, whereas including too little causes bugs of lack of
        # chunk appearance update when it's required.
        
        res = (self.enable_specular_highlights,)
        if self.enable_specular_highlights:
            res = res + ( self.specular_shininess,
                          self.specular_whiteness,
                          self.specular_brightness )

        res += ( self.use_c_renderer,
                 self.use_drawing_variant,
                 self._use_sphere_shaders,
                 self._use_cylinder_shaders,
                 self._use_cone_shaders,
                 self._use_batched_primitive_shaders,
               )

        # russ 080530: Selection style preference controls select_dl contents.
        res += (env.prefs[selectionColorStyle_prefs_key],)
        res += (env.prefs[selectionColor_prefs_key],)
        res += (env.prefs[haloWidth_prefs_key],)

        return res

    # ==

    def sphereShader_desired(self): #bruce 090303
        """
        Based only on preferences, is it desired to use shaders
        for spheres in CSDLs?

        @note: only valid if self.update() has been called recently.
            This is ensured by current code by calling this via enable_shaders
            which is called only in configure_enabled_shaders, which is only
            called after its caller (GLPane in one call of paintGL)
            has called self.update.

        @see: drawing_globals.sphereShader_available()
        """
        return self._use_batched_primitive_shaders and self._use_sphere_shaders

    def cylinderShader_desired(self): #bruce 090303
        """
        Based only on preferences, is it desired to use shaders
        for cylinders in CSDLs?

        @note: only valid if self.update() has been called recently.

        @see: drawing_globals.cylinderShader_available()
        """
        return self._use_batched_primitive_shaders and self._use_cylinder_shaders

    def coneShader_desired(self): #bruce 090303
        """
        Based only on preferences, is it desired to use shaders
        for cones in CSDLs?

        @note: only valid if self.update() has been called recently.

        @see: drawing_globals.coneShader_available()
        """
        # this is intentionally not symmetric with cylinderShader_desired(),
        # since we want turning off cylinder shader pref to turn off all uses
        # of the cylinder shader, including cones.
        return self._use_batched_primitive_shaders and self._use_cylinder_shaders and \
               self._use_cone_shaders

    pass # end of class glprefs

# end