summaryrefslogtreecommitdiff
path: root/cad/src/commands/TestGraphics/test_selection_redraw.py
blob: 2c126815ff6d99248f3d7a9a4e3c1ee99a6f178b (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
# Copyright 2009 Nanorex, Inc.  See LICENSE file for details.
"""
test_selection_redraw.py - test case for frequent moving of CSDLs between DrawingSets

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

Results, as of 090106:

WARNING; these are measured while running Wing IDE; at least one of these results
(550 msec) is about 4x faster (140 msec) when NE1 is running directly as a developer.

* pure display time can be seen by making _INVERSE_FREQUENCY_OF_REVISION a very large value.
  for example, about 10 fps for 50k spheres. Not great, but tolerable. No easy way to optimize.

* the time to move CSDLS into other DrawingSets and recompute their caches is long --
  about 550 msec per frame, for 10k CSDLs. This needs to be optimized.
  I don't yet know what consumes the time, but since all caches are remade using Python
  it's not too surprising (and profiling should show the problem).

* the initial setup time (in which we make and fill 10k CSDLs) is *very* long
  (maybe half a minute). (The fps-printing code fails to measure this.)
  This too needs optimizing, since it will limit any NE1 operation which modifies
  lots of chunks locally, e.g. loading a file or minimizing. Probably we need to
  translate some of that code into C or Pyrex.
"""

import random

import foundation.env as env

from graphics.drawing.DrawingSet import DrawingSet
## from graphics.drawing.TransformControl import TransformControl
from graphics.drawing.ColorSorter import ColorSorter
from graphics.drawing.ColorSortedDisplayList import ColorSortedDisplayList
from graphics.drawing.CS_draw_primitives import drawsphere

from commands.TestGraphics.GraphicsTestCase import GraphicsTestCase

# ==

_NUM_DRAWINGSETS = 5

_PROBABILITY_OF_MOVE = 0.2

## _NUM_CSDLS_X = 10 # this is now a local variable from a test parameter
## _NUM_CSDLS_Y = 10
_NUM_SPHERES_PER_CSDL = 5

_INVERSE_FREQUENCY_OF_REVISION = 1 # must be an integer; only revise model every nth time;
    # WARNING: many reported timings predate this (as if it was 1); not settable in UI

# ==

class CSDL_holder(object):
    """
    Represent a CSDL in a particular DrawingSet.

    (CSDL itself can't be used for this, since a CSDL
    can be in more than one DrawingSet at a time.)
    """
    drawingset = None
    def __init__(self, x, y, n):
        self.csdl = ColorSortedDisplayList()
        # draw into it, based on x, y, n
        ColorSorter.start(None, self.csdl)
        for i in range(n):
            z = i - (_NUM_SPHERES_PER_CSDL - 1) / 2.0
            color = (random.uniform(0.2, 0.8),
                     0,
                     random.uniform(0.2, 0.8))
            pos = (x, y, z)
            radius = 0.25 # 0.5 would be touching the next ones
            drawsphere(color,
                       pos,
                       radius,
                       2 ## DRAWSPHERE_DETAIL_LEVEL
             )
        ColorSorter.finish(draw_now = False)
        return
    def change_drawingset(self, drawingset):
        old = self.drawingset
        new = drawingset
        self.drawingset = new
        if old is not new:
            if old:
                old.removeCSDL(self.csdl) #e remove_csdl?
            if new:
                new.addCSDL(self.csdl) #e add_csdl?
            pass
        return
    pass

class test_selection_redraw(GraphicsTestCase):
    """
    test case for frequent moving of CSDLs between DrawingSets:

    Exercise graphics similar to selection/deselection, rapid change of highlighting, etc,
    by moving CSDLs rapidly between several DrawingSets drawn in different styles.

    If this is too slow per-frame due to setup cost, we can ignore this at first
    since highlighting can be done differently
    and selection doesn't change during most frames.

    But it is still a useful test for correctness,
    and for speed of selection changes that need to be reasonably responsive
    even if not occurring on most frames.
    """
    # test results [bruce 090102, usual Mac]:
    # For my initial parameters of 10 x 10 chunks of 5 spheres,
    # in 5 drawingsets (one not drawn), changing 20% of styles each time,
    # I get about 20 msec per frame
    # (or 44 msec if 'Use batched primitive shaders?' is turned off).
    # But this is only 500 spheres, far less than realistic.
    def __init__(self, *params):
        GraphicsTestCase.__init__(self, *params)
            ### review: split into __init__ and setup methods? or use activate for the following?
        _NUM_CSDLS_X = _NUM_CSDLS_Y = self._params[0]
        # set up a lot of CSDLs, in wrappers that know which DrawingSet they're in (initially None)
        self._csdls = [ CSDL_holder((i % _NUM_CSDLS_X) - (_NUM_CSDLS_X - 1) / 2.0,
                                    ((i / _NUM_CSDLS_X) % _NUM_CSDLS_Y) - (_NUM_CSDLS_Y - 1) / 2.0,
                                    _NUM_SPHERES_PER_CSDL)
                        for i in range(_NUM_CSDLS_X * _NUM_CSDLS_Y) ]
        # set up our two DrawingSets (deselected first)
        self.drawingsets = [DrawingSet() for i in range(_NUM_DRAWINGSETS)]
        self.drawingsets[-1] = None # replace the last one with None, for hidden CSDLs
        # put all the CSDLs in the first one
        for csdl in self._csdls:
            csdl.change_drawingset(self.drawingsets[0])
        return
    def activate(self):
        # make sure the correct two DrawingSets will be drawn
        pass # implicit in our def of _draw_drawingsets
    def draw(self):
        # move some of the CSDLs from one to another DrawingSet
        # (for now, just put each one into a pseudorandomly chosen DrawingSet, out of all of them)
        if (env.redraw_counter % _INVERSE_FREQUENCY_OF_REVISION) != 0:
            return
        num_sets = len(self.drawingsets)
        csdls_to_move = self._csdls
        for csdl in csdls_to_move:
            if random.random() <= _PROBABILITY_OF_MOVE and num_sets > 1:
                # move this csdl to a new drawingset (enforced actually different)
                old = csdl.drawingset # always a DrawingSet (or None, if None is a member of self.drawingsets)
                new = old
                while new is old:
                    new = self.drawingsets[ random.randint(0, num_sets - 1) ]
                csdl.change_drawingset(new)
                pass
            continue
        return
    def _draw_drawingsets(self):
        # note: called after .draw
        for i in range(_NUM_DRAWINGSETS):
            # choose proper drawing style
            options = {}
            # note: default options are:
            ## options = dict(
            ##     highlighted = False, selected = False,
            ##     patterning = True, highlight_color = None, opacity = 1.0 )
            if i == 0: # todo: encapsulate the options with the DrawingSet
                # plain
                pass
            elif i == 1:
                # selected
                options = dict( selected = True)
            elif i == 2:
                # highlighted
                options = dict( highlighted = True)
            elif i == 3:
                # transparent
                options = dict( opacity = 0.5 )
            else:
                # default options
                pass
            if self.drawingsets[i]:
                self.drawingsets[i].draw(**options)
            continue
        return
    pass

# end