1# Copyright (C) 2006-2015, Parrot Foundation.
2
3=head1 NAME
4
5sdl/mandel.pir - Display Mandelbrot Set Using SDL
6
7=head1 SYNOPSIS
8
9To run this file, run the following command from the Parrot directory:
10
11  $ ./parrot examples/sdl/mandel.pir [ options ]
12
13=head1 DESCRIPTION
14
15This is a PIR program which displays the Mandelbrot Set, using SDL.
16
17=head2 Options
18
19  --quit, -q      ... quit immediately (useful for benchmarking)
20  --threads       ... non-working code to run 2 calculation threads
21
22=head1 KEYBOARD/MOUSE COMMANDS
23
24  q          ... quit application
25  r          ... reset to intial coors and scale
26  <but-left> ... zoom in, center at click
27  <but-mid>  ... center at click
28  <but-right> .. zoom out, center at click
29  <keypad+>  ... increase bailout limit by 100
30  <keypad->  ... decrease bailout limit by 100
31
32=cut
33
34.sub _main :main
35    .param pmc argv
36    .local pmc opts, app, event, handler
37    'load_sdl_libs'()
38    opts = 'get_opts'(argv)
39    app  = 'make_app'(opts)
40    app.'calc'()
41    $I0 = opts['quit']
42    if $I0 goto ex
43    event = getattribute app, 'event'
44    handler = getattribute app, 'event_handler'
45    event.'process_events'(handler, app)
46ex:
47    app.'quit'()
48.end
49
50# utils
51.sub 'load_sdl_libs'
52    # load the necessary libraries
53    load_bytecode "SDL/App.pir"
54    load_bytecode "SDL/Rect.pir"
55    load_bytecode "SDL/Color.pir"
56    load_bytecode "SDL/EventHandler.pir"
57    load_bytecode "SDL/Event.pir"
58    load_bytecode "SDL/Surface.pir"
59    load_bytecode "Getopt/Obj.pir"
60.end
61
62# cmd line processing
63.sub 'get_opts'
64    .param pmc argv
65    .local pmc opts, getopts
66    getopts = new ['Getopt';'Obj']
67    push getopts, "quit|q"
68    push getopts, "threads"
69    $S0 = shift argv
70    opts = getopts."get_options"(argv)
71    .return (opts)
72.end
73
74# create the application
75.sub 'make_app'
76    .param pmc opts
77    # create an SDL::App subclass
78    .local pmc app, cl
79    cl = subclass ['SDL'; 'App'], 'Mandel'
80    addattribute cl, 'xstart'
81    addattribute cl, 'ystart'
82    addattribute cl, 'xend'
83    addattribute cl, 'yend'
84    addattribute cl, 'scale'
85    addattribute cl, 'rect'
86    addattribute cl, 'raw_palette'
87    addattribute cl, 'event'
88    addattribute cl, 'event_handler'
89    addattribute cl, 'limit'
90    addattribute cl, 'opts'
91    # instantiate, seel also __init below
92    app = new 'Mandel'
93    setattribute app, 'opts', opts
94    .return (app)
95.end
96
97.namespace ['Mandel']
98
99# init the Mandel app instance
100.sub __init :method
101    .local int w, h
102    .local num scale, xstart, ystart
103    # mandelbrot set is width [-2, 0.25] height [ -1, 1]
104    # round up, scale *200
105    xstart = -2.0
106    ystart = -1.0
107    scale = 200
108    w = 600
109    h = 400
110    self.'init'( 'height' => h, 'width' => w, 'bpp' => 0, 'flags' => 1 )
111    $P0 = new 'Float'
112    $P0 = xstart
113    setattribute self, 'xstart', $P0
114    $P0 = new 'Float'
115    $P0 = ystart
116    setattribute self, 'ystart', $P0
117    $P0 = new 'Float'
118    $P0 = 1.0 # XXX calc from above
119    setattribute self, 'xend', $P0
120    $P0 = new 'Float'
121    $P0 = 1.0
122    setattribute self, 'yend', $P0
123    $P0 = new 'Float'
124    $P0 = scale
125    setattribute self, 'scale', $P0
126    $P0 = new 'Integer'
127    $P0 = 200
128    setattribute self, 'limit', $P0
129
130    .local pmc rect, main_screen
131    main_screen = self.'surface'()
132
133    # create an SDL::Rect representing the entire main screen
134    .local pmc rect
135    rect = new ['SDL'; 'Rect']
136    rect.'init'( 'height' => h, 'width' => w, 'x' => 0, 'y' => 0 )
137    setattribute self, 'rect', rect
138
139    .local pmc palette, raw_palette, black
140    palette = self.'create_palette'()
141    raw_palette = self.'create_rawpalette'(palette)
142    setattribute self, 'raw_palette', raw_palette
143    # draw the background
144    black = palette[0]
145    main_screen.'fill_rect'( rect, black )
146    main_screen.'update_rect'( rect )
147
148    self.'init_events'()
149.end
150
151# accessors for some attribs
152.sub 'xstart' :method
153    .param num x     :optional
154    .param int has_x   :opt_flag
155    $P0 = getattribute self, 'xstart'
156    unless has_x goto get
157    $P0 = x
158get:
159    x = $P0
160    .return (x)
161.end
162
163.sub 'ystart' :method
164    .param num y     :optional
165    .param int has_y   :opt_flag
166    $P0 = getattribute self, 'ystart'
167    unless has_y goto get
168    $P0 = y
169get:
170    y = $P0
171    .return (y)
172.end
173
174.sub 'xend' :method
175    .param num x     :optional
176    .param int has_x   :opt_flag
177    $P0 = getattribute self, 'xend'
178    unless has_x goto get
179    $P0 = x
180get:
181    x = $P0
182    .return (x)
183.end
184
185.sub 'yend' :method
186    .param num y     :optional
187    .param int has_y   :opt_flag
188    $P0 = getattribute self, 'yend'
189    unless has_y goto get
190    $P0 = y
191get:
192    y = $P0
193    .return (y)
194.end
195
196.sub 'scale' :method
197    .param num s     :optional
198    .param int has_s   :opt_flag
199    $P0 = getattribute self, 'scale'
200    unless has_s goto get
201    $P0 = s
202get:
203    s = $P0
204    .return (s)
205.end
206
207.sub 'limit' :method
208    .param int l     :optional
209    .param int has_l   :opt_flag
210    $P0 = getattribute self, 'limit'
211    unless has_l goto get
212    $P0 = l
213get:
214    l = $P0
215    .return (l)
216.end
217
218.sub 'calc' :method
219    .local pmc main_screen, raw_palette, rect, pixels
220    .local int w, h, x, y, pal_elems, raw_c, k, limit
221    .local num xstart, ystart, scale
222    # fetch the SDL::Surface representing the main window
223    main_screen = self.'surface'()
224    h = main_screen.'height'()
225    w = main_screen.'width'()
226    # lock the raw framebuffer
227    xstart = self.'xstart'()
228    ystart = self.'ystart'()
229    scale =  self.'scale'()
230    limit =  self.'limit'()
231    raw_palette = getattribute self, 'raw_palette'
232    rect        = getattribute self, 'rect'
233    pal_elems = elements raw_palette
234    # prefetch pixels
235    pixels = main_screen.'pixels'()
236    # start calculation
237    .local pmc args
238    args = new 'FixedPMCArray'
239    set args, 10
240    args[0] = w
241    args[1] = xstart
242    args[2] = ystart
243    args[3] = scale
244    args[4] = limit
245    args[5] = pal_elems
246    args[6] = raw_palette
247    args[7] = pixels
248    args[8] = main_screen
249    args[9] = rect
250    $P0 = getattribute self, 'opts'
251    $I0 = $P0['threads']
252    unless $I0 goto plain
253    main_screen.'lock'()
254    .local pmc thr
255    .local int h2
256    h2 = h / 2
257    # TODO: Task
258    thr = new 'ParrotThread'
259    .const 'Sub' raw_calc_f = 'raw_calc'
260    .include 'cloneflags.pasm'
261    .local int flags
262    flags  = .PARROT_CLONE_CODE
263    flags |= .PARROT_CLONE_CLASSES
264    thr.'run'(flags, raw_calc_f, h2, h, args)
265    raw_calc(0, h2, args)
266    thr.'join'()
267    main_screen.'unlock'()
268    .return()
269plain:
270    main_screen.'lock'()
271    raw_calc(0, h, args)
272    main_screen.'unlock'()
273.end
274
275.sub raw_calc
276    .param int y0
277    .param int h
278    .param pmc args
279
280    .local int w, x, y, pal_elems, raw_c, k, limit, offs_y
281    .local num xstart, ystart, scale
282    .local pmc raw_palette, pixels, main_screen, rect
283    .local num z, Z, t, c, C, zz, ZZ
284    w = args[0]
285    xstart = args[1]
286    ystart = args[2]
287    scale =  args[3]
288    limit =  args[4]
289    pal_elems = args[5]
290    raw_palette = args[6]
291    pixels = args[7]
292    main_screen = args[8]
293    rect = args[9]
294    y = y0
295loop_y:
296    offs_y = w * y
297    C = y / scale	# Im c part
298    C += ystart
299    x = 0
300loop_x:
301    c = x / scale   # re c part
302    c += xstart
303    z = 0.0
304    Z = 0.0   # Z(0) = 0
305    k = 0
306    # iteration loop, calculate
307    # Z(k+1) = Z(k)^2 + c
308    # bailout if abs(Z) > 2 or iteration limit of k is exceeded
309    zz = 0.0  # z*z
310    ZZ = 0.0  # Z*Z
311loop_k:
312    # z = zz - ZZ + c
313    t = zz - ZZ
314    t += c
315
316    # Z = 2*z*Z + C
317    Z *= 2.0
318    Z *= z
319    Z += C
320
321    # z = t
322    z = t
323
324    # if (z*z + Z*Z > 4) break;
325    zz = z * z
326    ZZ = Z * Z
327    $N1 = zz + ZZ
328    if $N1 > 4.0 goto set_pix
329    inc k
330    if k < limit goto loop_k	# iterations
331    k = 0
332set_pix:
333    $I0 = k % pal_elems
334    raw_c = raw_palette[$I0]
335    $I0 = offs_y + x
336    # main_screen.'draw_pixel'(x, y, raw_c) -->
337    pixels[0; $I0] = raw_c
338    inc x
339    if x < w goto loop_x
340    # update the screen on each line
341    main_screen.'update_rect'( rect )
342    inc y
343    if y < h goto loop_y
344
345.end
346
347.sub 'recalc' :method
348    .param int x
349    .param int y
350    .param int but
351    .local int w, h
352    .local num xstart, ystart, xend, yend, scale, fx, fy, dx, dy
353    .local num ds, mx, my, dx2, dy2
354    .local pmc main_screen
355    main_screen = self.'surface'()
356    h = main_screen.'height'()
357    w = main_screen.'width'()
358    xstart = self.'xstart'()
359    ystart = self.'ystart'()
360    xend = self.'xend'()
361    yend = self.'yend'()
362    scale =  self.'scale'()
363    # use x,y as center of new calculation
364    dx = xend - xstart
365    dy = yend - ystart
366    # relative factor of new midpoint
367    fx = x / w   # 0..1
368    fy = y / h
369    fx -= 0.5    # -0.5 .. +0.5
370    fy -= 0.5
371    fx *= dx     # cvt to mandel coors
372    fy *= dy
373    dx2 = dx / 2.0
374    dy2 = dy / 2.0
375    mx = xstart + dx2    # midpoint
376    my = ystart + dy2
377    mx += fx             # new midpoint
378    my += fy
379    ds = 1.0
380    if but == 1 goto zoom_in
381    if but == 2 goto done
382    ds = 0.5
383    goto done
384zoom_in:
385    ds = 2.0
386done:
387    dx2 /= ds
388    dy2 /= ds
389    xstart = mx - dx2
390    ystart = my - dy2
391    self.'xstart'(xstart)
392    self.'ystart'(ystart)
393    xend = mx + dx2
394    yend = my + dy2
395    self.'xend'(xend)
396    self.'yend'(yend)
397    scale *= ds
398    self.'scale'(scale)
399    self.'calc'()
400.end
401
402# init event system
403.sub 'init_events' :method
404    .local pmc event, args, event_handler
405    event = new ['SDL'; 'Event']
406    event.'init'()
407    setattribute self, 'event', event
408
409    $P0 = subclass ['SDL'; 'EventHandler'], ['Mandel'; 'EventHandler']
410    event_handler = new ['Mandel'; 'EventHandler']
411    event_handler.'init'(self)	# XXX unused
412    setattribute self, 'event_handler', event_handler
413.end
414
415# sort by adding raw r+g+b values
416.sub bright
417    .param pmc l
418    .param pmc r
419    .local int cr, cl, br_l, br_r
420    cl = l
421    br_l = cl & 0xff
422    cl >>= 8
423    $I0 = cl & 0xff
424    br_l += $I0
425    cl >>= 8
426    $I0 = cl & 0xff
427    br_l += $I0
428    cr = r
429    br_r = cr & 0xff
430    cr >>= 8
431    $I0 = cr & 0xff
432    br_r += $I0
433    cr >>= 8
434    $I0 = cr & 0xff
435    br_r += $I0
436    $I0 = cmp br_l, br_r
437    .return ($I0)
438.end
439
440# create a 8x8x8 palette
441.sub create_palette :method
442    .local pmc palette, col, main_screen
443    main_screen = self.'surface'()
444    .local int r, g, b
445    palette = new 'ResizablePMCArray'
446    r = 0
447loop_r:
448    g = 0
449loop_g:
450    b = 0
451loop_b:
452    col = new ['SDL'; 'Color']
453    col.'init'( 'r' => r, 'g' => g, 'b' => b )
454    push palette, col
455    b += 36
456    if b <= 255 goto loop_b
457    g += 36
458    if g <= 255 goto loop_g
459    r += 36
460    if r <= 255 goto loop_r
461    .const 'Sub' by_bright = "bright"
462    palette.'sort'(by_bright)
463    .return (palette)
464.end
465
466# create raw_palette with surface colors
467.sub create_rawpalette :method
468    .param pmc palette
469    .local int i, n, raw_c
470    .local pmc raw_palette, col, main_screen
471    main_screen = self.'surface'()
472    n = elements palette
473    raw_palette = new 'FixedIntegerArray'
474    raw_palette = n
475    i = 0
476loop:
477    col = palette[i]
478    raw_c = col.'color_for_surface'( main_screen )
479    raw_palette[i] = raw_c
480    inc i
481    if i < n goto loop
482    .return (raw_palette)
483.end
484
485.namespace ['Mandel'; 'EventHandler']
486
487.sub key_down_q :method
488    .param pmc app
489    app.'quit'()
490    end
491.end
492
493# reset to default
494.sub key_down_r :method
495    .param pmc app
496    app.'xstart'(-2.0)
497    app.'ystart'(-1.0)
498    app.'xend'(1.0)
499    app.'yend'(1.0)
500    app.'scale'(200)
501    app.'limit'(200)
502    app.'calc'()
503.end
504
505# keypad +/- change bailout limit
506.sub key_down_kp_plus :method
507    .param pmc app
508    .local int limit
509    limit = app.'limit'()
510    limit += 100
511    app.'limit'(limit)
512    print "limit +\n"
513    app.'calc'()
514.end
515
516.sub key_down_kp_minus :method
517    .param pmc app
518    .local int limit
519    limit = app.'limit'()
520    if limit <= 100 goto ignore
521    limit -= 100
522    app.'limit'(limit)
523    print "limit -\n"
524    app.'calc'()
525ignore:
526.end
527
528.sub mouse_button_up :method
529    .param pmc event
530    .param pmc app
531
532    .local int b, x, y
533    event = event.'event'( 'MouseButton' )
534    b = event['state']
535    x = event['x']
536    y = event['y']
537    app.'recalc'(x, y, b)
538.end
539
540=head1 AUTHOR
541
542leo
543
544=head1 OPTIMIZATIONS
545
546Runtimes for x86_64 AMD X2@2000
547600 x 400 pixels,  200 iterations, 2s delay subtracted
548
549=head2 Algorithm optimizations
550
551Plain runcore and unoptimized parrot:
552
553  Original version based on sdl/raw_pixels   21s
554  Create raw_palette                         12s
555  Prefetch raw_surface                       10s        [1]
556  Optimize calculation loop (zz, ZZ)          9s        [2]
557  use raw pixels array                                  [3]
558
559=head2 Parrot based optimizations
560
561Optimized build
562
563  [2] plain  runcore 64 bit                  3.0s
564  [2] plain  runcore 32 bit                  3.6s
565  [1] -R jit                                 1.1s
566  [2] -R jit                                 0.8s
567  [3] -R jit                                 0.5s
568
569=head1 SEE ALSO
570
571L<http://en.wikipedia.org/wiki/Mandelbrot_set>
572
573If you want faster mandelbrot with interactive zooming use Xaos:
574
575L<http://xaos.sourceforge.net/english.php>
576
577=cut
578
579# Local Variables:
580#   mode: pir
581#   fill-column: 100
582# End:
583# vim: expandtab shiftwidth=4 ft=pir:
584