File: rendering.py

package info (click to toggle)
mypaint 2.0.1-14
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 27,884 kB
  • sloc: python: 43,893; cpp: 6,931; xml: 2,475; sh: 473; makefile: 25
file content (343 lines) | stat: -rwxr-xr-x 9,443 bytes parent folder | download | duplicates (4)
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
#!/usr/bin/env python

# Imports:

from __future__ import division, print_function
from os.path import join
import sys
import time
import math
import cairo
from collections import namedtuple
import unittest

from . import paths
from lib import mypaintlib
from lib.document import Document
from lib.pycompat import xrange, PY3


TEST_BIGIMAGE = "bigimage.ora"


# Helpers:

def _scroll(tdw, model, width=1920, height=1080,
            zoom=1.0, mirrored=False, rotation=0.0,
            turns=8, turn_steps=8, turn_radius=0.3,
            save_pngs=False,
            set_modes=None,
            use_background=True):
    """Test scroll performance

    Scroll around in a circle centred on the virtual display, testing
    the same sort of render that's used for display - albeit to an
    in-memory surface.

    This tests rendering and cache performance quite well, though it
    discounts Cairo acceleration.

    """
    num_undos_needed = 0
    if set_modes:
        for path, mode in set_modes.items():
            model.select_layer(path=path)
            num_undos_needed += 1
            model.set_current_layer_mode(mode)
            num_undos_needed += 1
            assert model.layer_stack.deepget(path, None).mode == mode
    model.layer_stack.background_visible = use_background
    model.layer_stack._render_cache.clear()

    radius = min(width, height) * turn_radius
    fakealloc = namedtuple("FakeAlloc", ["x", "y", "width", "height"])
    alloc = fakealloc(0, 0, width, height)
    tdw.set_allocation(alloc)
    surf = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height)

    tdw.set_rotation(rotation)
    tdw.set_zoom(zoom)
    tdw.set_mirrored(mirrored)
    tdw.recenter_document()

    clock_func = time.perf_counter if PY3 else time.clock
    start = clock_func()
    cx, cy = tdw.get_center()
    last_x = cx
    last_y = cy
    nframes = 0
    for turn_i in xrange(turns):
        for step_i in xrange(turn_steps):
            t = 2 * math.pi * (step_i / turn_steps)
            x = cx + math.cos(t) * radius
            y = cy + math.sin(t) * radius
            dx = x - last_x
            dy = y - last_y
            cr = cairo.Context(surf)
            cr.rectangle(*alloc)
            cr.clip()
            tdw.scroll(dx, dy)
            tdw.renderer._draw_cb(tdw, cr)
            surf.flush()
            last_x = x
            last_y = y
            if save_pngs:
                filename = "/tmp/scroll-%03d-%03d.png" % (turn_i, step_i)
                surf.write_to_png(filename)
            nframes += 1
    dt = clock_func() - start
    for i in range(num_undos_needed):
        model.undo()
    if set_modes:
        for path in set_modes.keys():
            mode = model.layer_stack.deepget(path, None).mode
            assert mode == mypaintlib.CombineNormal
    return (nframes, dt)


# Test cases:

class Scroll (unittest.TestCase):
    """Not-quite headless raw panning/scrolling performance tests."""

    def test_5x_1rev(self):
        self._run_test(
            _scroll,
            zoom=5.0,
            turns=1,
        )

    def test_5x_30revs(self):
        self._run_test(
            _scroll,
            zoom=5.0,
            turns=30,
        )

    def test_1x_1rev(self):
        self._run_test(
            _scroll,
            zoom=1.0,
            turns=1,
        )

    def test_1x_30revs(self):
        self._run_test(
            _scroll,
            zoom=1.0,
            turns=30,
        )

    # Circles using the defaults at different zooms
    # At the time of writing, the default radius is 0.3 times the height
    # of a typical 1920x1080 screen.

    def test_0x10(self):
        # Figure is not clipped by the edges of the screen
        self._run_test(
            _scroll,
            zoom=0.10,
        )

    def test_0x25(self):
        # Figure is clipped at the top and bottom of the circle,
        # but "only just" (in reality, tens of tiles)
        self._run_test(
            _scroll,
            zoom=0.25,
        )

    def test_0x50(self):
        # Figure fits comfortably within the width of the screen
        # at this zoom
        self._run_test(
            _scroll,
            zoom=0.5,
        )

    def test_1x(self):
        # No blank tiles visible onscreen at 100% zoom and above.
        self._run_test(
            _scroll,
            zoom=1.0,
        ),

    def test_2x(self):
        self._run_test(
            _scroll,
            zoom=2.0,
        )

    def test_8x(self):
        self._run_test(
            _scroll,
            zoom=8.0,
        )

    def test_16x(self):
        self._run_test(
            _scroll,
            zoom=16.0,
        )

    def test_32x(self):
        self._run_test(
            _scroll,
            zoom=32.0,
        )

    def test_64x(self):
        self._run_test(
            _scroll,
            zoom=64.0,
        )

    # "lazy" means taking more steps, emulating the user panning more slowly
    # For this test it just means that more tiles from one frame to the
    # next have the same identity.

    def test_1x_lazy_all_onscreen(self):
        self._run_test(
            _scroll,
            zoom=1.0,
            turn_steps=16, turns=3,  # 3 lazy turns
            turn_radius=0.1,
        )

    def test_1x_lazy_all_onscreen_masks(self):
        self._run_test(
            _scroll,
            zoom=1.0,
            turn_steps=16, turns=3,  # 3 lazy turns
            turn_radius=0.1,
            set_modes={
                (10,): mypaintlib.CombineDestinationIn,
                (5,): mypaintlib.CombineDestinationIn,
            },
        )

    def test_1x_lazy_all_onscreen_nobg(self):
        self._run_test(
            _scroll,
            zoom=1.0,
            turn_steps=16, turns=3,  # 3 lazy turns
            turn_radius=0.1,
            use_background=False,
        )

    def test_1x_lazy_mostly_onscreen(self):
        self._run_test(
            _scroll,
            zoom=1.0,
            turn_steps=16, turns=3,  # 3 lazy turns
            turn_radius=1,  # circles show some empty space
        )

    def test_1x_lazy_mostly_onscreen_masks(self):
        self._run_test(
            _scroll,
            zoom=1.0,
            turn_steps=16, turns=3,  # 3 lazy turns
            turn_radius=1,  # circles show some empty space
            set_modes={
                (10,): mypaintlib.CombineDestinationIn,
                (5,): mypaintlib.CombineDestinationIn,
            },
        )

    def test_1x_lazy_mostly_onscreen_nobg(self):
        self._run_test(
            _scroll,
            zoom=1.0,
            turn_steps=16, turns=3,  # 3 lazy turns
            turn_radius=1,  # circles show some empty space
            use_background=False,
        )

    def test_1x_lazy_mostly_offscreen(self):
        self._run_test(
            _scroll,
            zoom=1.0,
            turn_steps=16, turns=3,  # 3 lazy turns
            turn_radius=2,  # now mostly empty space outside the image
        )

    def test_1x_lazy_mostly_offscreen_masks(self):
        self._run_test(
            _scroll,
            zoom=1.0,
            turn_steps=16, turns=3,  # 3 lazy turns
            turn_radius=2,  # now mostly empty space outside the image
            set_modes={
                (10,): mypaintlib.CombineDestinationIn,
                (5,): mypaintlib.CombineDestinationIn,
            },
        )

    def test_1x_lazy_mostly_offscreen_nobg(self):
        self._run_test(
            _scroll,
            zoom=1.0,
            turn_steps=16, turns=3,  # 3 lazy turns
            turn_radius=2,  # now mostly empty space outside the image
            use_background=False,
        )

    @classmethod
    def setUpClass(cls):
        # The tdw import below just segfaults on my system right now, if
        # there's no X11 display available. Be careful about proceeding.

        cls._tdw = None
        cls._model = None

        from lib.gibindings import Gdk
        if Gdk.Display.get_default() is None:
            return

        try:
            import gui.tileddrawwidget
        except Exception:
            return

        class TiledDrawWidget (gui.tileddrawwidget.TiledDrawWidget):
            """Monkeypatched TDW for testing purposes"""

            def __init__(self, *args, **kwargs):
                gui.tileddrawwidget.TiledDrawWidget\
                    .__init__(self, *args, **kwargs)
                self.renderer.get_allocation = self._get_allocation

            def set_allocation(self, alloc):
                self._alloc = alloc

            def _get_allocation(self):
                return self._alloc

        tdw = TiledDrawWidget()
        tdw.zoom_max = 64.0
        tdw.zoom_min = 1.0 / 16
        model = Document(painting_only=True)
        model.load(join(paths.TESTS_DIR, TEST_BIGIMAGE))
        tdw.set_model(model)
        cls._model = model
        cls._tdw = tdw

    @classmethod
    def tearDownClass(cls):
        if cls._model:
            cls._model.cleanup()

    def _run_test(self, func, **kwargs):
        if not (self._tdw and self._model):
            self.skipTest("no GUI or unable to import TDW class")
        nframes, dt = func(self._tdw, self._model, **kwargs)
        if dt <= 0:
            msg = "0s"
        else:
            msg = "%0.3fs, %0.1ffps" % (dt, nframes / dt)
        print(msg, end=", ", file=sys.stderr)


if __name__ == '__main__':
    unittest.main()