summaryrefslogtreecommitdiff
path: root/cad/src/exprs/DisplayListChunk.py
blob: 9dff5cd93fc95ff3e8b571986414a28e7b8a22b2 (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
# Copyright 2006-2009 Nanorex, Inc.  See LICENSE file for details. 
"""
DisplayListChunk.py

@author: Bruce
@version: $Id$
@copyright: 2006-2009 Nanorex, Inc.  See LICENSE file for details. 

History:

061231 prototyped this code in NewInval.py

070102 moved it here 1-2 days ago, and revised it to seem correct; imports/reloads ok, but otherwise untested

070103 works, with important caveats re Highlightable (described below)

080215 renamed from DisplistChunk to DisplayListChunk

==

Still needed:
- maybe: some renamings, as commented
- provisions for highlighting (see just below in this docstring)
- include texture/image contents in the same scheme -- analogous to displist contents.
  Requires giving them compatible owner objects and treating those like we treat the ones
  for directly called display lists, aka sublists.

==

caveats re Highlightable [as of 070103]:

a Highlightable inside a DisplayListChunk only works properly (without highlight-position errors)
if no coord transform occurs from the DisplayListChunk to the Highlightable,
and no trackballing or other motion of whole thing from the last time the instance was made.
The second condition is impossible to guarantee, so in practice,
you should not use a Highlightable inside a DisplayListChunk until this is fixed.

Theory: the gl matrix is saved only once and only as it was when the displist was compiled.
Note, the rules would be even weirder (I predict) if nested display lists were involved --
then we'd need no transforms in any of them, re their calls of the next, for current code to work.

The best fix is the one planned on paper a few weeks ago -- have ExprsMeta decorate draw methods
with something that can call only the needed ones in a special scan for implementing draw_in_abs_coords.
(Idea now: the tree whose paths from root need to be marked, to implem that, is the tree of original
calls of draw methods. This was supposed to equal the ipath-suffix tree. Hopefully it does. If not,
the decorated draw methods themselves could generate ipath-like things for this purpose -- or really,
just pointers to the draw-method-owning parents in each one, to be stored in the objects with the draw methods.)

Update, 081202:

If you suspect this bug causes some unwanted behavior, you can test that by
temporarily setting the debug_pref "disable DisplayListChunk?" and seeing if
that fixes the unwanted behavior. (But it might also cause a large slowdown.)

But if there are bugs from the other nesting order of interaction, i.e. a DisplayListChunk
inside a Highlightable, that would fix those too. I'm not sure whether there are.

For example, as of 081202 morning, disabling DisplayListChunks fixes the following bug:
mousewheel zoom makes checkboxes inside DrawInCorner (and perhaps other
checkboxes) either not highlight, or if they are close enough to the center
of the screen (I guess), highlight in the wrong size and place (only where
the highlight image overlaps the original image, which happens for the top two
checkboxes when you zoom in by one wheel click). The bug is "reset" by
"clear state and reload" even if the zoom is still in effect, but additional
zoom causes the bug again. But panning or rotating the view does not cause
the bug (for reasons I don't fully understand, except that it must be related
to the fact that those don't alter the projection matrix but zoom does).

But I don't fully understand what causes this bug, and I don't know for sure
in which order the nesting is or whether being inside DrawInCorner is part of
the bug's cause. I do know it's alternatively fixable by using projection = True
in the Highlightable, so I'm going to change that to default True
(see comments there for reasoning and related plans).
As for the cause, it's probably related that only zoom, not pan or trackball,
caused the bug, and only zoom modifies the projection matrix.
But I don't have a complete explanation of either the bug or the fix.

*If* this bug comes from Highlightable inside DisplayListChunk,
maybe it could be fixed more optimally by detecting when that happens at runtime,
and setting projection = True only then? Maybe try this someday.
"""

from OpenGL.GL import GL_COMPILE

from utilities.debug_prefs import Choice_boolean_False
from utilities.debug_prefs import debug_pref
from utilities.debug import print_compact_traceback

from foundation.changes import SelfUsageTrackingMixin # defines track_use, track_inval; maintains a private __subslist on self
from foundation.changes import SubUsageTrackingMixin # defines begin_tracking_usage, end_tracking_usage; doesn't use self

from exprs.attr_decl_macros import Arg, ArgOrOption
from exprs.instance_helpers import DelegatingInstanceOrExpr
from exprs.widget2d import Widget
from exprs.py_utils import printfyi

##e comment during devel -- see also some comments in lvals-outtakes.py and DisplayListChunk-outtakes.py (in cvs, only during devel)
# (including long docstrings/comments that might be correct, but are unreviewed and partly redundant now)

# ==

# moved class GLPane_mixin_for_DisplayListChunk from here into GLPane.py, bruce 070110

# ==

###@@@ Q: do we separate the object to own a displist, below, and the one to represent various Instances,
# like one for DisplayListChunk and one for defining a displist-subroutine?
# (Do these even differ? [062023 addendum: One way they differ: whether highlighting treats their instances as separate objects.
#   But it might turn out this is just a matter of whether we use a DisplayListChunk inside something for highlighting
#   which pushes its own glname for everything inside it, crossing it with whatever ones are inside it rather than being
#   overridden by them (just a flag on the name which matters when it's interpreted).])
# If we do, is the class below even an Instance with a draw method? (I doubt it. I bet it's an internal displist-owner helper object.)

#e digr: someday: it might be good for glpane to have a dict from allocated dlist names to their owning objects.
# Could we use displist names as keys, for that purpose?

class DisplayListChunk( DelegatingInstanceOrExpr, SelfUsageTrackingMixin, SubUsageTrackingMixin ):
    """
    #doc
    [Note: the implicit value for SelfUsageTrackingMixin is the total drawing effect of calling our displist in immediate mode.
     This has an inval flag and invalidator, but we also have another pair of those for our display list's OpenGL contents,
     making this more complicated a use of that mixin than class Lval or its other uses.]
    [old long docstring and comment moved to outtakes file since unreviewed and partly redundant, 070102]
    """
    # default values of instance variables
    _direct_sublists_dict = 2 # intentional error if this default value is taken seriously
    contents_valid = False
    drawing_effects_valid = False
        # we don't yet have a specific flag for validity of sublist drawing effects (unknown when not contents_valid);
        # that doesn't matter for now; a useful optim someday would be a dict of all invalid-effects direct sublists #e
    
    # args
    delegate = Arg(Widget)
    
    # options
    debug_prints = ArgOrOption(str, None) # flag to do debug prints, and (unless a boolean) name for doing them

    def _C__debug_print_name(self): # compute self._debug_print_name
        """
        return False (meaning don't do debug prints), or a name string to prefix them with
        """
        # use this to say when we emit a calllist (and if so, immediate or into what other displist), and/or recompile our list
        #e and use in repr
        if self.debug_prints:
            if self.debug_prints == True:
                return "%r" % self ###BUG (suspected): this looks like an infrecur. Not sure if ever tested. [070110 comment]
            return str(self.debug_prints)
        return False
    
    def _init_instance(self):
        self._key = id(self) # set attribute to use as dict key (could probably use display list name, but it's not allocated yet)
        self.glpane = self.env.glpane #e refile into superclass??
        self._disabled = not hasattr(self.glpane, 'glGenLists')
        if self._disabled:
            # this should never happen after the merge of GLPane_overrider into GLPane done today [070110]
            print "bug: %r is disabled since its GLPane is missing required methods" % self
        return
        
    def _C_displist(self): # compute method for self.displist
        ### WARNING: this doesn't recycle displists when instances are remade at same ipath (but it probably should),
        # and it never frees them. To recycle them, just change it to use transient_state.
        # When we start using more than one GL Context which share display lists, we'll have to revise this somehow.
        #
        ### NOTE: usage tracking should turn up nothing -- we use nothing
        """
        allocate a new display list name (a 32-bit int) in our GL context
        """
        if self.displist_disabled(): # revised this cond (true more often), 070215
            printfyi("bug: why does .displist get requested in a disabled DisplayListChunk??") # (i never saw this)
            return 0
        
        self.glpane.makeCurrent() # not sure when this compute method might get called, so make sure our GL context is current
        displist = self.glpane.glGenLists(1) # allocate the display list name [#k does this do makeCurrent??]
        # make sure it's a nonzero int or long
        assert type(displist) in (type(1), type(1L))
        assert displist, "error: allocated displist was zero"
        if self._debug_print_name:
            print "%s: allocated display list name %r" % (self._debug_print_name, displist)
        return displist

    def __repr__(self):
        try:
            if not self._e_is_instance:
                return super(DisplayListChunk, self).__repr__()
            _debug_print_name = self._debug_print_name # only legal on an instance
        except:
            print "exception in self._debug_print_name discarded" #e print more, at least id(self), maybe traceback, if this happens
            # (but don't print self or you might infrecur!!)
            ##e can we print self.displist and/or self.delegate?
            return "<%s at %#x>" % (self.__class__.__name__, id(self))
        else:
            return "<%s(%s) at %#x>" % (self.__class__.__name__, _debug_print_name or "<no name>", id(self))
        pass

    def displist_disabled(self): #070215 split this out, modified it to notice _exprs__warpfuncs
        """
        Is the use of our displist (or of all displists) disabled at the moment?
        """
        return self._disabled or \
               debug_pref("disable DisplayListChunk?", Choice_boolean_False, prefs_key = True) or \
               getattr(self.env.glpane, '_exprs__warpfuncs', None) ###BUG: this will be too inefficient a response for nice dragging.
    
    def draw(self):
        """
        Basically, we draw by emitting glCallList, whether our caller is currently
        compiling another display list or executing OpenGL in immediate mode.
           But if our total drawing effects are invalid (i.e. our own list and/or some list it calls
        has invalid contents), we must do some extra things, and do them differently in those two cases.
           In immediate mode, if our own display list contents are invalid, we have to recompile it (without
        executing it) and only later emit the call, since compiling it is the only way to find out what other
        display lists it currently calls (when using the current widget expr .draw() API). Further, whether
        or not we recompiled it, we have to make sure any other lists it calls are valid, recompiling them
        if not, even though they too might call other lists we can't discover until we recompile them.
           Since OpenGL doesn't permit nested compiles of display lists, we use a helper function:
        given a set of display lists, make sure all of them are ok to call (by compiling zero or more of them,
        one at a time), extending this set recursively to all lists they indirectly call, as found out when
        recompiling them. This involves calling .draw methods of arbitrary widgets, with a glpane flag telling
        them we're compiling a display list. (In fact, we tell them its controller object, e.g. self,
        so they can record info that helps with later redrawing them for highlighting or transparency.)
           When not in immediate mode, it means we're inside the recursive algorithm described above,
        which is compiling some other display list that calls us. We can safely emit our glCallList before or after
        our other effects (since it's not executed immediately), but we also have to add ourselves to
        the set of displists directly called by whatever list is being compiled. (Doing that allows the
        recursive algorithm to add those to the set it needs to check for validity. That alg can optim by
        only doing that to the ones we know might be invalid, but it has to record them as sublists of
        the list being compiled whether or not we're valid now, since it might use that later when we're
        no longer valid.)
           We do all that via methods and/or private dynamic vars in self.glpane. ###doc which
           Note that the recursive algorithm may call us more than once. We should, of course, emit a glCallList
        every time, but get recompiled by the recursive algorithm at most once, whether that happens due to side effects
        here or in the algorithm or both. (Both might be possible, depending on what optims are implemented. ###k)
           Note that recursive display list calls are possible
        (due to bugs in client code), even though they are OpenGL errors if present when executed. (BTW, it's possible
        for cycles to exist transiently in display list contents without an error, if this only happens when
        some but not all display lists have been updated after a change. This needs no noticing or handling in the code.)
        """ 
        # docstring revised 070102.
        
        _debug_print_name = self._debug_print_name
        
        if self.displist_disabled():
            self.drawkid( self.delegate) ## self.delegate.draw()
            # I hope it's ok that this has no explicit effect on usage tracking or inval propogation... I think so.
            # It's equivalent to wrapping the whole thing in an If on this cond, so it must be ok.
            return
        
        self.displist
            # make sure we have a display list allocated
            # (this calls the compute method to allocate one if necessary)
            # [probably not needed explicitly, but might as well get it over with at the beginning]
            
            ###e NOTE: if someday we keep more than one displist, compiled under different drawing conditions in dynenv
            # (e.g. different effective values of glpane._exprs__warpfuncs),
            # then some or all of our attrs need splitting by the case of which displist to use,
            # and we also have to avoid usage-tracking the attrs that eval that case in the same way
            # as those we use when compiling displists. (As if we're If(cond, displistchunk1, displistchunk2),
            #  but also making sure nothing in displistchunk1 tracks those same attrs in a way we see as our own usage,
            #  or if it does, making sure we don't subscribe inval of a displist to that, by adding new code
            #  to end_tracking_usage to handle those usages specially, having known what they were by tracking cond, perhaps --
            #  or by explicit description, in case cond also tracks other stuff which doesn't show up in its value.
            #  Or another way, if the cond-value-attrs are special (e.g. glpane._exprs__warpfuncs),
            #  is for them to not be natively usage-tracked, but only when we eval cond (not sure this is ok re outsider tracking
            #  of them -- maybe better to track them except when we set a specialcase dynenv flag next to them,
            #  which they notice to decide whether to track uses, which we set when inside the specific case for the cond-value.)
            #  [070215]

        # are we being compiled into another display list?
        parent_dlist = self.glpane.compiling_displist_owned_by # note: a dlist owner or None, not a dlist name
        
        if parent_dlist:
            # We're being compiled into a display list (owned by parent_dlist); tell parent that its list calls ours.
            # (Note: even if we're fully valid now, we have to tell it,
            #  since it uses this info not only now if we're invalid, but later,
            #  when we might have become invalid. If we ever have a concept of
            #  our content and/or drawing effects being "finalized", we can optim
            #  in that case, by reducing what we report here.)
            if _debug_print_name:
                print "%s: compiling glCallList into parent list %r" % (_debug_print_name, parent_dlist)

            parent_dlist.__you_called_dlist( self) #e optim: inline this
                # (note: this will also make sure the alg recompiles us and whatever lists we turn out to call,
                #  before calling any list that calls us, if our drawing effects are not valid now.)
            
        elif self.glpane.compiling_displist:
            print "warning: compiling dlist %r with no owner" % self.glpane.compiling_displist
            #e If this ever happens, decide then whether anything but glCallList is needed.
            # (Can it happen when compiling a "fixed display list"? Not likely if we define that using a widget expr.)
            
        else:
            # immediate mode -- do all needed recompiles before emitting the glCallList,
            # and make sure glpane will be updated if anything used by our total drawing effect changes.
            if _debug_print_name:
                print "%s: prepare to emit glCallList in immediate mode" % (_debug_print_name, )
            
            self.glpane.ensure_dlist_ready_to_call( self) # (this might print "compiling glCallList" for sublists)
                # note: does transclose starting with self, calls _recompile_if_needed_and_return_sublists_dict
            self.track_use() # defined in SelfUsageTrackingMixin; compare to class Lval
                # This makes sure the GLPane itself (or whatever GL context we're drawing into, if it has proper usage tracking)
                # knows it needs to redraw if our drawing effects change.
                # Note: this draw method only does track_use in immediate mode (since it would be wrong otherwise),
                # but when compiling displists, it can be done by the external recursive algorithm via track_use_of_drawing_effects.
        if _debug_print_name:
            print "%s: emit glCallList(%r)" % (_debug_print_name, self.displist)
        self.do_glCallList() #e optim: inline this
        return # from draw

        # some old comments which might still be useful:
            
            # 061023 comments: analogous to Lval.get_value, both in .draw always being such, and in what's happening in following code.

            # Another issue - intermediate levels in a formula might need no lval objs, only ordinary compute calls,
            # unless they have something to memoize or inval at that level... do ordinary draw methods, when shared,
            # need this (their own capturing of inval flag, one for opengl and one for total effect,
            # and their own pair of usage lists too, one of called lists which can be scanned)??

    # this doesn't work due to quirks of Python:
    ## track_use_of_drawing_effects = track_use # this is semipublic; track_use itself (defined in SelfUsageTrackingMixin) is private
    # so rather than slow it down by a def,
    # I'll just rename the calls track_use but comment them as really track_use_of_drawing_effects

    def _your_drawing_effects_are_valid(self): ##e should inline as optim
        """
        """
        assert self.contents_valid
            # this failed 070104 with the exception shown in long string below, when I used clear "button" (070103 kluge) on one node...
            # theory: the world.nodelist change (by clear button run inside draw method, which is illegal -- that's the kluge)
            # invalidated it right when it was being recompiled (or just after, but before the overall recomp alg sent this call).
            # So that kluge has to go [later: it's gone],
            # and the underlying error of changing an input to an ongoing recompute has to be better detected. ####e
        self.drawing_effects_valid = True
        return

# the exception mentioned above: 
    """
atom_debug: fyi: <OneTimeSubsList(<LvalForState(<World#16291(i)>|transient.nodelist|('world', (0, (0, 'NullIpath')))) at 0x101a7b98>) at 0x10583850>'s 
event already occurred, fulfilling new subs 
<bound method usage_tracker_obj.standard_inval of <usage_tracker_obj(<DisplayListChunk(<no name>) at 0x111b7670>) at 0x112086e8>> immediately: 

[atom.py:414] [GLPane.py:1847] [GLPane.py:1884] [testmode.py:67] [testdraw.py:251] [GLPane_overrider.py:127]
[GLPane_overrider.py:138] [GLPane_overrider.py:298] [testmode.py:75] [testdraw.py:275] [testdraw.py:385]
[testdraw.py:350] [testdraw.py:384] [testdraw.py:530] [test.py:1231] [Overlay.py:57] [Overlay.py:57]
[Overlay.py:57] [DisplayListChunk.py:286] [DisplayListChunk.py:124] [state_utils.py:156] [DisplayListChunk.py:116]
[DisplayListChunk.py:358] [changes.py:352] [changes.py:421] [changes.py:72]

exception in testdraw.py's drawfunc call ignored: exceptions.AssertionError:

[testdraw.py:350] [testdraw.py:384] [testdraw.py:530] [test.py:1231] [Overlay.py:57] [Overlay.py:57]
[Overlay.py:57] [DisplayListChunk.py:286] [DisplayListChunk.py:127] [DisplayListChunk.py:314]
"""
    def __you_called_dlist(self, dlist):
        self.__new_sublists_dict[ dlist._key ] = dlist
            # note: this will intentionally fail if called at wrong time, since self.__new_sublists_dict won't be a dict then
        return
    
    def _recompile_if_needed_and_return_sublists_dict(self):
        """
        [private helper method for glpane.ensure_dlist_ready_to_call()]
        Ensure updatedness of our displist's contents (i.e. the OpenGL instructions last emitted for it)
        and of our record of its direct sublists (a dict of owners of other displists it directly calls).
        Return the dict of direct sublists.
           As an optim [mostly nim], it's ok to return a subset of that, which includes all direct sublists
        whose drawing effects might be invalid. (In particular, if our own drawing effects are valid,
        except perhaps for our own displist's contents, it's ok to return {}. [That's the part of this optim we do.])
        """ 
        # doc revised 070102
        if not self.contents_valid:
            # we need to recompile our own displist.
            if self._debug_print_name:
                print "%s: compiling our displist(%r)" % (self._debug_print_name, self.displist)
            
            self._direct_sublists_dict = 3 # intentional error if this temporary value is used as a dict
                # (note: this might detect the error of a recursive direct or indirect call -- untested ##k)
            self.__new_sublists_dict = new_sublists_dict = {}
                # this is added to by draw methods of owners of display lists we call
            mc = self.begin_tracking_usage()
                # Note: we use this twice here, for different implicit values, which is ok since it doesn't store anything on self.
                # [#e i should make these non-methods to clarify that.]
                # This begin/end pair is to track whatever affects the OpenGL commands we compile;
                # the one below is to track the total drawing effects of the display lists called during that
                # (or more generally, any other drawing effects not included in that, but tracking for any other
                #  kinds of effects, like contents of textures we draw to the screen ### WHICH MIGHT MATTER, is nim).
                #
                # Note that track_use and track_inval do NOT have that property -- they store self.__subslist.
            try:
                self.recompile_our_displist()
                    # render our contents into our displist using glNewList, self.drawkid( self.delegate), glEndList
                    # note: has try/except so always does endlist ##e make it tell us if error but no exception??
            finally:
                self.contents_valid = True
                    # Q: is it ok that we do this now but caller might look at it?
                    # A: yes, since caller never needs to see it -- it's effectively private.
                self.end_tracking_usage( mc, self.invalidate_contents) # same invalidator even if exception during recompile or its draw
                self._direct_sublists_dict = dict(new_sublists_dict)
                    #e optim: this copy is only for bug-safety in case something kept a ref and modifies it later
                self.__new_sublists_dict = 4 # illegal dict value

            mc2 = self.begin_tracking_usage() # this tracks how our drawing effects depend on those of the sublists we call
            try:
                for sublist in self._direct_sublists_dict.itervalues():
                    sublist.track_use() # really track_use_of_drawing_effects (note: that's tracked into the global env, as always for track_use)
            finally:
                self.end_tracking_usage( mc2, self.invalidate_drawing_effects )
                    # this subscribes invalidate_drawing_effects to inval of total effects of all sublists
                    # (effectively including indirectly called ones too);
                    # the only thing it doesn't cover is subscribing it to inval of our own displist's contents,
                    # so we manually call it in invalidate_contents.
        if self.drawing_effects_valid:
            if debug_pref("DisplayListChunk: permit optim 070204?", Choice_boolean_False):
                ###BUG: this old optim seems to cause the bug 070203 -- I don't know why, but disabling it seems to fix the bug ###k
                print "doing optim 070204"#e and listnames [#e only print if the optim makes a difference?]
                return {} # optim; only possible when self.contents_valid,
                    # tho if we had a separate flag for sublist contents alone,
                    # we could correctly use that here as a better optim #e
            else:
                pass ## print "not doing optim 070204"#e and listnames
        return self._direct_sublists_dict

    def invalidate_contents(self):
        """
        [private] 
        called when something changes which might affect 
        the sequence of OpenGL commands that should be compiled into self.displist
        """
        if self.contents_valid:
            self.contents_valid = False
            self.invalidate_drawing_effects()
        else:
            # this clause would not be needed except for fear of bugs; it detects/complains/works around certain bugs.
            if self.drawing_effects_valid:
                print "bug: self.drawing_effects_valid when not self.contents_valid. Error, but invalidate_drawing_effects anyway."
                self.invalidate_drawing_effects()
            pass
        return
    
    def invalidate_drawing_effects(self):
        # note: compare to class Lval
        if self.drawing_effects_valid:
            self.drawing_effects_valid = False
            # propogate inval to whoever used our drawing effects
            self.track_inval() # (defined in SelfUsageTrackingMixin)
        return

    def recompile_our_displist(self):
        """
        [private] call glNewList/draw/glEndList in the appropriate way, 
        but do no usage tracking or valid-setting
        """
        glpane = self.glpane
        displist = self.displist
        glpane.glNewList(displist, GL_COMPILE, owner = self)
            # note: not normally a glpane method, but we'll turn all gl calls into methods so that glpane can intercept
            # the ones it wants to (e.g. in this case, so it can update glpane.compiling_displist)
            # note: we have no correct way to use GL_COMPILE_AND_EXECUTE, as explained in draw docstring
        try:
            self.drawkid( self.delegate) ## self.delegate.draw()
        except:
            print_compact_traceback("exception while compiling display list for %r ignored, but terminates the list: " % self )
            ###e should restore both stack depths and as much gl state as we can
            # (but what if the list contents are not *supposed* to preserve stack depth? then it'd be better to imitate their intent re depths)
            pass
        glpane.glEndList(displist)
            # note: glEndList doesn't normally have an arg, but this arg lets glpane version of that method do more error-checking
        return
    
    def do_glCallList(self):
        """
        emit a call of our display list, whether or not we're called in immediate mode
        """
        self.glpane.glCallList( self.displist)
        return
    
    pass # end of class DisplayListChunk

# end