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