1 /** 2 This module contains the rasterizer type. 3 4 Copyright Chris Jones 2020. 5 Distributed under the Boost Software License, Version 1.0. 6 See accompanying file Licence.txt or copy at... 7 https://www.boost.org/LICENSE_1_0.txt 8 */ 9 10 module dg2d.rasterizer; 11 12 import dg2d.misc; 13 import dg2d.path; 14 import dg2d.pathiterator; 15 import dg2d.rect; 16 17 import std.traits; 18 19 /* 20 Analitic antialiasing rasterizer. 21 ================================= 22 23 Internally works with 24:8 fixed point integer coordinates. 24 25 You need 8 bits fractional to get 256 levels of gray for almost 26 horizontal or almost vertical lines. Hence 24:8 fixed point. 27 28 It's a scanline based rasterizer. You add path data in the form 29 of lines and curves etc... Those are converted to edges. The edges 30 are converted to scanline coverage, then coverage is combined with 31 paint and blended to the destination pixels. 32 33 The coverage is stored in differentiated form, each cell in 34 the scanline stores the difference between cells rather than 35 the actual coverage. This means we dont have to track coverage 36 for long spans where nothing happens. 37 38 It also uses a bitmask to track changes in coverage. It's an idea 39 taken from Blend2D although i think my implementation is different. 40 Basically anywhere an edge crosses the current scanline the bit 41 for the leftmost pixel it touches is set in the mask. So we can 42 use a bitscan instruction to find long spans of unchanging 43 coverage and where we need to start processing the covrage again. 44 45 The mask uses 1 bit for every 4 pixels, because the blitters are 46 processing 4 pixels at once with SIMD. 47 48 Cliping is handled by having left and right clip buffers, any edges 49 crossing the left or righ boundry are spit at the boundry. Parts inside 50 are handled normaly, parts outside are added to the clip buffers so 51 that we keep track of what coverage they contribute. This is then 52 added to the scandelta at the start of processing each line. These 53 buffers use differentiated coverage. 54 */ 55 56 /* 57 Winding rule 58 Gradient repeat mode 59 */ 60 61 enum WindingRule 62 { 63 NonZero, 64 EvenOdd 65 } 66 67 enum RepeatMode 68 { 69 Pad, // final colour pads area past the ends of the gradient 70 Repeat, // gradient repeats ad nausium, oops I mean ad infinitum 71 Mirror // as above but alternate repeats are mirrored 72 } 73 74 /* 75 Delta mask stuff 76 what word type to use for mask 77 how many pixels per bit 78 how many pixels per word 79 bit mask for width of DMWord 80 */ 81 82 static if ((void*).sizeof == 4) 83 alias DMWord = uint; 84 else static if ((void*).sizeof == 8) 85 alias DMWord = ulong; 86 else 87 static assert(0); 88 89 private: 90 91 enum dmPixPerBit = 4; 92 enum dmPixPerWord = dmPixPerBit * 8 * DMWord.sizeof; 93 enum dmWordMask = 8 * DMWord.sizeof - 1; 94 95 /* 96 set a bit in the delta mask, 'x' is pixel cordinate, not bit index 97 */ 98 99 void DMSetBit(DMWord* mask, uint x) 100 { 101 mask[x/dmPixPerWord] |= (cast(DMWord)1) << ((x / dmPixPerBit) & dmWordMask); 102 } 103 104 void DMSetBitRange(DMWord* mask, uint x0, int x1) 105 { 106 while (x0 <= x1) 107 { 108 mask[x0/dmPixPerWord] |= (cast(DMWord)1) << ((x0 / dmPixPerBit) & dmWordMask); 109 x0+=4; 110 } 111 } 112 113 /* 114 Few constants for fixed point coordinates / gradients 115 */ 116 117 enum fpFracBits = 8; // 8 bits fractional 118 enum fpScale = 256.0f; // for converting from float 119 enum fpDXScale = 4294967296.0; // convert to dx gradient in 32:32 120 enum fpDYScale = 1073741824.0; // as above but div 4 121 122 /* 123 Blitter delegate. A callback that does the actual blitting once coverage 124 for the given scanline has been calculated. 125 delta - pointer to the delta buffer 126 mask - pointer to delta mask 127 x0 - start x 128 x1 - end x 129 y - y position 130 */ 131 132 public alias BlitFunc = void delegate(int* delta, DMWord* mask, int x0, int x1, int y); 133 134 /* 135 a*b/c, with the intermediate result of a*b in 64 bit 136 the asm version might be faster in 32 bit mode, havent tested yet, but the 137 plain D version is same speed with 64bit / LDC 138 */ 139 140 private: 141 142 /* 143 int MulDiv64(int a, int b, int c) 144 { 145 asm 146 { 147 mov EAX, a; 148 imul b; 149 idiv c; 150 } 151 } 152 */ 153 int MulDiv64(int a, int b, int c) 154 { 155 return cast(int) ((cast(long) a * b) / c); 156 } 157 158 /* 159 Rasterizer class. 160 */ 161 162 public: 163 164 class Rasterizer 165 { 166 this() 167 { 168 } 169 170 ~this() 171 { 172 } 173 174 /** 175 This sets the clip rectange, flushes any existing state and preps for 176 drawing. 177 178 The clip window left,top is inside, right,bottom is outside. So if the 179 window is 100,100 --> 200,200, then pixel 100,100 can be modified but 180 pixel 200,200 will not. 181 */ 182 183 /* 184 The rasterizer needs to allow for coordinates that fall directly on the 185 right side and bottom side of the clip even though those pixels are 186 techically outside. It's easier and faster to give the temporary buffers 187 a bit extra room for overspill than it is to check and special case 188 when it happens. 189 190 Also the delta buffer and two clip buffers use differentiated coverage 191 which also causes one extra pixel of overspill. If you differentiate a 192 sequence of length N you get a sequence of length N+1. Again it's easier 193 and faster to just allow for the overspill than it is to check for and 194 special case it. 195 */ 196 197 void initialise(int left, int top, int right, int bottom) 198 { 199 assert((left >= 0) && (left < right)); 200 assert((top >= 0) && (top < bottom)); 201 202 m_clipleft = left << fpFracBits; 203 m_cliptop = top << fpFracBits; 204 m_clipright = right << fpFracBits; 205 m_clipbottom = bottom << fpFracBits; 206 207 // reset edge buffer and Y extent tracking 208 209 m_edgepool.reset(); 210 m_yrmin = bottom; 211 m_yrmax = top; 212 213 // init buffers 214 215 m_scandelta.length = roundUpPow2((right+3)|63); 216 m_deltamask.length = roundUpPow2(1+right/dmPixPerWord); 217 m_buckets.length = roundUpPow2(bottom+1); 218 m_clipbfr_l.length = roundUpPow2((bottom+2)|63); 219 m_clipbfr_r.length = roundUpPow2((bottom+2)|63); 220 221 // init prev x,y and sub path start x,y 222 223 m_prevx = 0; 224 m_prevy = 0; 225 m_subpx = 0; 226 m_subpy = 0; 227 m_fprevx = 0; 228 m_fprevy = 0; 229 } 230 231 /** ditto */ 232 233 void initialise(IRect rect) 234 { 235 initialise(rect.left, rect.top, rect.right, rect.bottom); 236 } 237 238 // rasterize 239 240 void rasterize(BlitFunc blitter) 241 { 242 Edge dummy; 243 Edge* prev = &dummy; 244 Edge* edge = null; 245 246 int startx = (m_clipleft >> fpFracBits) & 0xFFFFFFFC; 247 int endx = ((m_clipright >> fpFracBits) + 3) & 0xFFFFFFFC; 248 int starty = m_yrmin >> fpFracBits; 249 int endy = (m_yrmax+255) >> fpFracBits; 250 251 int cl_acc,cr_acc; 252 int cl_pos = m_clipleft >> fpFracBits; 253 int cr_pos = m_clipright >> fpFracBits; 254 255 for (int y = starty; y < endy; y++) 256 { 257 m_deltamask[] = 0; 258 int ly = (y << fpFracBits) + 256; 259 260 // clip accumulator 261 262 cl_acc += m_clipbfr_l[y]; 263 m_clipbfr_l[y] = 0; 264 cr_acc += m_clipbfr_r[y]; 265 m_clipbfr_r[y] = 0; 266 267 if (cl_acc) DMSetBit(m_deltamask.ptr, cl_pos); 268 if (cr_acc) DMSetBit(m_deltamask.ptr, cr_pos); 269 270 m_scandelta[cl_pos] += cl_acc; 271 m_scandelta[cr_pos] += cr_acc; 272 273 // At this point 'prev' either points at 'dummy' or at the last node in 274 // active edges linked list, so we just add the new edges to it. 275 276 prev.next = m_buckets[y]; 277 m_buckets[y] = null; 278 279 // loop through the active edges 280 281 prev = &dummy; 282 edge = dummy.next; 283 284 while (edge) 285 { 286 int ny = void; 287 288 if (edge.y2 <= ly) 289 { 290 ny = edge.y2; 291 prev.next = edge.next; 292 } 293 else 294 { 295 ny = ly; 296 prev = edge; 297 } 298 299 int span = ny - edge.y; 300 long nx = edge.x + edge.dx * span; 301 302 int bpspan = span * ((cast(int)(edge.dy>>63))|1); 303 304 int x0 = cast(int)(edge.x >> 40); 305 int x1 = cast(int)(nx >> 40); 306 int steps = x1 - x0; 307 308 if (steps == 0) 309 { 310 DMSetBit(m_deltamask.ptr, x0); 311 312 int w = (edge.x >> 32) & 0xFF; 313 int v = (nx >> 32) & 0xFF; 314 int area = (bpspan * (512 - w - v)) >> 2; 315 m_scandelta[x0] += area; 316 x0++; 317 m_scandelta[x0] += bpspan * 128 - area; 318 } 319 else if (steps > 0) 320 { 321 //DMSetBit(m_deltamask.ptr, x0); 322 DMSetBitRange(m_deltamask.ptr, x0, x1); 323 324 int w = 256 - ((edge.x >> 32) & 0xFF); 325 long acc = w * edge.dy; 326 int area = cast(int)((w * acc) >> 32); 327 m_scandelta[x0] += area; 328 x0++; 329 acc += edge.dy << 7; 330 331 while (x0 < x1) 332 { 333 int lc = area; 334 area = cast(int)(acc >> 23); 335 m_scandelta[x0] += area - lc; 336 x0++; 337 acc += edge.dy << 8; 338 } 339 340 int q = (nx >> 32) & 0xFF; 341 int rect = bpspan * 128; 342 int lc = area; 343 area = rect - cast(int)((q * q * edge.dy) >> 32); 344 m_scandelta[x0] += area - lc; 345 x0++; 346 m_scandelta[x0] += rect - area; 347 } 348 else if (steps < 0) 349 { 350 //DMSetBit(m_deltamask.ptr, x1); 351 DMSetBitRange(m_deltamask.ptr, x1, x0); 352 353 int w = 256 - ((nx >> 32) & 0xFF); 354 long acc = w * edge.dy; 355 int area = cast(int)((w * acc) >> 32); 356 m_scandelta[x1] += area; 357 x1++; 358 acc += edge.dy << 7; 359 360 while (x1 < x0) 361 { 362 int lc = area; 363 area = cast(int)(acc >> 23); 364 m_scandelta[x1] += area - lc; 365 x1++; 366 acc += edge.dy << 8; 367 } 368 369 int q = (edge.x >> 32) & 0xFF; 370 int rect = bpspan * 128; 371 int lc = area; 372 area = rect - cast(int)((q * q * edge.dy) >> 32); 373 m_scandelta[x1] += area - lc; 374 x1++; 375 m_scandelta[x1] += rect - area; 376 } 377 378 edge.x = nx; 379 edge.y = ny; 380 edge = edge.next; 381 } 382 383 // Blit scanline 384 385 blitter(m_scandelta.ptr, m_deltamask.ptr, startx, endx, y); 386 387 // clear scandelta overspill 388 389 m_scandelta[endx] = 0; 390 391 version(assert) 392 { 393 foreach(e; m_scandelta) if (e != 0) 394 { 395 import std.stdio; 396 write(y, ' ',"scan delta error = "); 397 foreach(q; m_scandelta) 398 { 399 write(q,":"); 400 } 401 assert(0); 402 } 403 } 404 } 405 406 // clear clip buffers overspill 407 408 m_clipbfr_l[endy] = 0; 409 m_clipbfr_r[endy] = 0; 410 411 version(assert) 412 { 413 foreach(e; m_clipbfr_l) assert(e == 0); 414 foreach(e; m_clipbfr_r) assert(e == 0); 415 } 416 417 // clear m_buckets overspill, this is only needed because in very 418 // rare cases we could end up with an edge could end up on the 419 // bottom clip boundry after spliting an edge, these should really 420 // be removed in the clipping code 421 422 m_buckets[endy] = null; 423 424 version(assert) 425 { 426 foreach(e; m_buckets) assert(e == null); 427 } 428 429 m_edgepool.reset(); 430 } 431 432 /* 433 drawing methods 434 */ 435 436 void moveTo(double x, double y) 437 { 438 intMoveTo(cast(int)(x * fpScale), cast(int)(y * fpScale)); 439 m_fprevx = x; 440 m_fprevy = y; 441 } 442 443 void moveTo(float x, float y) 444 { 445 intMoveTo(cast(int)(x * fpScale), cast(int)(y * fpScale)); 446 m_fprevx = x; 447 m_fprevy = y; 448 } 449 450 void lineTo(double x, double y) 451 { 452 intLineTo(cast(int)(x * fpScale), cast(int)(y * fpScale)); 453 m_fprevx = x; 454 m_fprevy = y; 455 } 456 457 void lineTo(float x, float y) 458 { 459 intLineTo(cast(int)(x * fpScale), cast(int)(y * fpScale)); 460 m_fprevx = x; 461 m_fprevy = y; 462 } 463 464 void quadTo(double x1, double y1, double x2, double y2) 465 { 466 double x01 = (m_fprevx+x1)*0.5; 467 double y01 = (m_fprevy+y1)*0.5; 468 double x12 = (x1+x2)*0.5; 469 double y12 = (y1+y2)*0.5; 470 double xctr = (x01+x12)*0.5; 471 double yctr = (y01+y12)*0.5; 472 double err = (x1-xctr)*(x1-xctr)+(y1-yctr)*(y1-yctr); 473 474 if (err > 0.1) 475 { 476 quadTo(x01,y01,xctr,yctr); 477 quadTo(x12,y12,x2,y2); 478 } 479 else 480 { 481 intLineTo(cast(int)(x2 * fpScale), cast(int)(y2 * fpScale)); 482 } 483 484 m_fprevx = x2; 485 m_fprevy = y2; 486 } 487 488 void quadTo(float x1, float y1, float x2, float y2) 489 { 490 float x01 = (m_fprevx+x1)*0.5; 491 float y01 = (m_fprevy+y1)*0.5; 492 float x12 = (x1+x2)*0.5; 493 float y12 = (y1+y2)*0.5; 494 float xctr = (x01+x12)*0.5; 495 float yctr = (y01+y12)*0.5; 496 float err = (x1-xctr)*(x1-xctr)+(y1-yctr)*(y1-yctr); 497 498 if (err > 0.1) 499 { 500 quadTo(x01,y01,xctr,yctr); 501 quadTo(x12,y12,x2,y2); 502 } 503 else 504 { 505 intLineTo(cast(int)(x2 * fpScale), cast(int)(y2 * fpScale)); 506 } 507 508 m_fprevx = x2; 509 m_fprevy = y2; 510 } 511 512 void cubicTo(double x1, double y1, double x2, double y2, double x3, double y3) 513 { 514 double x01 = (m_fprevx+x1)*0.5; 515 double y01 = (m_fprevy+y1)*0.5; 516 double x12 = (x1+x2)*0.5; 517 double y12 = (y1+y2)*0.5; 518 double x23 = (x2+x3)*0.5; 519 double y23 = (y2+y3)*0.5; 520 521 double xc0 = (x01+x12)*0.5; 522 double yc0 = (y01+y12)*0.5; 523 double xc1 = (x12+x23)*0.5; 524 double yc1 = (y12+y23)*0.5; 525 double xctr = (xc0+xc1)*0.5; 526 double yctr = (yc0+yc1)*0.5; 527 528 // this flattenening test code was from a page on the antigrain geometry 529 // website. 530 531 double dx = x3-m_fprevx; 532 double dy = y3-m_fprevy; 533 534 double d2 = abs(((x1 - x3) * dy - (y1 - y3) * dx)); 535 double d3 = abs(((x2 - x3) * dy - (y2 - y3) * dx)); 536 537 if((d2 + d3)*(d2 + d3) <= 0.5 * (dx*dx + dy*dy)) 538 { 539 intLineTo(cast(int)(x3 * fpScale), cast(int)(y3 * fpScale)); 540 } 541 else 542 { 543 cubicTo(x01,y01,xc0,yc0,xctr,yctr); 544 cubicTo(xc1,yc1,x23,y23,x3,y3); 545 } 546 547 m_fprevx = x3; 548 m_fprevy = y3; 549 } 550 551 void cubicTo(float x1, float y1, float x2, float y2, float x3, float y3) 552 { 553 float x01 = (m_fprevx+x1)*0.5; 554 float y01 = (m_fprevy+y1)*0.5; 555 float x12 = (x1+x2)*0.5; 556 float y12 = (y1+y2)*0.5; 557 float x23 = (x2+x3)*0.5; 558 float y23 = (y2+y3)*0.5; 559 560 float xc0 = (x01+x12)*0.5; 561 float yc0 = (y01+y12)*0.5; 562 float xc1 = (x12+x23)*0.5; 563 float yc1 = (y12+y23)*0.5; 564 float xctr = (xc0+xc1)*0.5; 565 float yctr = (yc0+yc1)*0.5; 566 567 // this flattenening test code was from a page on the antigrain geometry 568 // website. 569 570 float dx = x3-m_fprevx; 571 float dy = y3-m_fprevy; 572 573 float d2 = abs(((x1 - x3) * dy - (y1 - y3) * dx)); 574 float d3 = abs(((x2 - x3) * dy - (y2 - y3) * dx)); 575 576 if((d2 + d3)*(d2 + d3) <= 0.5 * (dx*dx + dy*dy)) 577 { 578 intLineTo(cast(int)(x3 * fpScale), cast(int)(y3 * fpScale)); 579 } 580 else 581 { 582 cubicTo(x01,y01,xc0,yc0,xctr,yctr); 583 cubicTo(xc1,yc1,x23,y23,x3,y3); 584 } 585 586 m_fprevx = x3; 587 m_fprevy = y3; 588 } 589 590 void addPath(ref Path path) 591 { 592 for (int i = 0; i < path.length; i++) 593 { 594 if (path.cmd(i) == PathCmd.move) 595 { 596 moveTo(path[i].x, path[i].y); 597 } 598 else if (path.cmd(i) == PathCmd.line) 599 { 600 lineTo(path[i].x, path[i].y); 601 } 602 else if (path.cmd(i) == PathCmd.quad) 603 { 604 quadTo(path[i].x, path[i].y, 605 path[i+1].x, path[i+1].y 606 ); 607 i++; 608 } 609 else if (path.cmd(i) == PathCmd.cubic) 610 { 611 cubicTo(path[i].x, path[i].y, 612 path[i+1].x, path[i+1].y, 613 path[i+2].x, path[i+2].y 614 ); 615 i+=2; 616 } 617 } 618 } 619 620 void addPath(T)(T path) 621 { 622 more: 623 switch (path.cmd) 624 { 625 case PathCmd.move: 626 moveTo(path.x(0), path.y(0)); 627 path.next(); 628 goto more; 629 case PathCmd.line: 630 lineTo(path.x(1), path.y(1)); 631 path.next(); 632 goto more; 633 case PathCmd.quad: 634 quadTo(path.x(1), path.y(1), path.x(2), path.y(2)); 635 path.next(); 636 goto more; 637 case PathCmd.cubic: 638 cubicTo(path.x(1), path.y(1), path.x(2), path.y(2), path.x(3), path.y(3)); 639 path.next(); 640 goto more; 641 default: 642 } 643 } 644 645 void addPath2(T)(T path) 646 { 647 auto segs = path.segments; 648 more: 649 switch (segs.cmd) 650 { 651 case PathCmd.move: 652 moveTo(segs[0].x, segs[0].y); 653 segs.next(); 654 goto more; 655 case PathCmd.line: 656 lineTo(segs[1].x, segs[1].y); 657 segs.next(); 658 goto more; 659 case PathCmd.quad: 660 quadTo(segs[1].x, segs[1].y, segs[2].x, segs[2].y); 661 segs.next(); 662 goto more; 663 case PathCmd.cubic: 664 cubicTo(segs[1].x, segs[1].y, segs[2].x, segs[2].y, segs[3].x, segs[3].y); 665 segs.next(); 666 goto more; 667 default: 668 } 669 } 670 671 private: 672 673 // internal moveTo. Note this will close any existing subpath because 674 // unclosed paths cause bad things to happen. (visually at least) 675 676 void intMoveTo(int x, int y) 677 { 678 if ((m_prevx != m_subpx) || (m_prevy != m_subpy)) 679 { 680 // add debug message? unclosed paths are really an error at this point i think 681 intLineTo(m_subpx, m_subpy); 682 } 683 684 m_prevx = x; 685 m_prevy = y; 686 m_subpx = x; 687 m_subpy = y; 688 } 689 690 // internal lineTo, clips and adds the line to edge buckets and clip 691 // buffers as appropriate 692 693 void intLineTo(int x, int y) 694 { 695 // mixin for adding edges. For some reason LDC wouldnt inline this when 696 // it was a seperate function, and it was 15% slower that way 697 698 string addEdgeM(string x0, string y0, string x1, string y1, string dir) 699 { 700 string tmp = (dir == "+") ? (y1~"-"~y0) : (y0~"-"~y1); 701 return 702 "Edge* edge = m_edgepool.allocate();" ~ 703 "edge.dx = cast(long) (fpDXScale * ("~x1~"-"~x0~") / ("~y1~"-"~y0~"));" ~ 704 "edge.x = (cast(long) "~x0~") << 32;" ~ 705 "edge.y = "~y0~";" ~ 706 "edge.y2 = "~y1~";" ~ 707 "int by = "~y0~" >> fpFracBits;" ~ 708 "int xxx = max(abs("~x1~"-"~x0~"),1);" ~ 709 "edge.dy = cast(long) (fpDYScale * ("~tmp~") / xxx);" ~ 710 "edge.next = m_buckets[by];" ~ 711 "m_buckets[by] = edge;";// ~ 712 //debugEdgeCoords(x0,y0,x1,y1,dir); 713 } 714 715 // mixin for clip accumulator 716 717 string addToClip(string y0, string y1, string side, string dir) 718 { 719 return 720 "{ int i0 = "~y0~" >> fpFracBits;" ~ 721 "int f0 = ("~y0~" & 0xFF) << 7;" ~ 722 "int i1 = "~y1~" >> fpFracBits;" ~ 723 "int f1 = ("~y1~" & 0xFF) << 7;" ~ 724 "m_clipbfr_"~side~"[i0] "~dir~"= 32768-f0;" ~ 725 "m_clipbfr_"~side~"[i0+1] "~dir~"= f0;" ~ 726 "m_clipbfr_"~side~"[i1] "~dir~"= f1-32768;" ~ 727 "m_clipbfr_"~side~"[i1+1] "~dir~"= -f1; }"; 728 } 729 730 // handle upward and downward lines seperately 731 732 if (m_prevy < y) 733 { 734 int x0 = m_prevx, y0 = m_prevy, x1 = x, y1 = y; 735 736 // edge is outside clip box or horizontal 737 738 if ((y0 == y1) || (y0 >= m_clipbottom) || (y1 <= m_cliptop)) 739 { 740 goto finished; 741 } 742 743 // clip to top and bottom 744 745 if (y0 < m_cliptop) 746 { 747 x0 = x0 + MulDiv64(m_cliptop - y0, x1 - x0, y1 - y0); 748 y0 = m_cliptop; 749 } 750 751 if (y1 > m_clipbottom) 752 { 753 x1 = x0 + MulDiv64(m_clipbottom - y0, x1 - x0, y1 - y0); 754 y1 = m_clipbottom; 755 } 756 757 // track y extent 758 759 if (y0 < m_yrmin) m_yrmin = y0; 760 if (y1 > m_yrmax) m_yrmax = y1; 761 762 // generate horizontal zoning flags, these are set depending on where 763 // x0 and x1 are in respect of the clip box. 764 765 uint a = cast(uint)(x0<m_clipleft); 766 uint b = cast(uint)(x0>m_clipright); 767 uint c = cast(uint)(x1<m_clipleft); 768 uint d = cast(uint)(x1>m_clipright); 769 uint flags = a | (b*2) | (c*4) | (d*8); 770 771 if (flags == 0) // bit faster to pull no clip out front 772 { 773 mixin(addEdgeM("x0","y0","x1","y1","+")); 774 goto finished; 775 } 776 777 // note cliping here can occasionaly result in horizontals, and can 778 // ocaisionaly put a horizontal on bucket for clipbotttom, which is 779 // outside the drawable area, currently it allows it and zeros that 780 // bucket after rasterization. 781 782 switch (flags) 783 { 784 case (1): // 0001 --> x0 left, x1 center 785 int sy = y0 + MulDiv64(y1 - y0, m_clipleft - x0, x1 - x0); 786 mixin(addToClip("y0","sy","l","+")); 787 mixin(addEdgeM("m_clipleft","sy","x1","y1","+")); 788 break; 789 case (2): // 0010 --> x0 right, x1 center 790 int sy = y0 + MulDiv64(y1 - y0, m_clipright - x0, x1 - x0); 791 mixin(addToClip("y0","sy","r","+")); 792 mixin(addEdgeM("m_clipright","sy","x1","y1","+")); 793 break; 794 case (4): // 0100 --> x0 center, x1 left 795 int sy = y0 + MulDiv64(y1 - y0, m_clipleft - x0, x1 - x0); 796 mixin(addEdgeM("x0","y0","m_clipleft","sy","+")); 797 mixin(addToClip("sy","y1","l","+")); 798 break; 799 case (5): // 0101 --> x0 left, x1 left 800 mixin(addToClip("y0","y1","l","+")); 801 break; 802 case (6): // 0110 --> x0 right, x1 left 803 int sl = y0 + MulDiv64(y1 - y0, m_clipleft - x0, x1 - x0); 804 int sr = y0 + MulDiv64(y1 - y0, m_clipright - x0, x1 - x0); 805 mixin(addToClip("y0","sr","r","+")); 806 mixin(addEdgeM("m_clipright","sr","m_clipleft","sl","+")); 807 mixin(addToClip("sl","y1","l","+")); 808 break; 809 case (8): // 1000 --> x0 center, x1 right 810 int sy = y0 + MulDiv64(y1 - y0, m_clipright - x0, x1 - x0); 811 mixin(addEdgeM("x0","y0","m_clipright","sy","+")); 812 mixin(addToClip("sy","y1","r","+")); 813 break; 814 case (9): // 1001 --> x0 left, x1 right 815 int sl = y0 + MulDiv64(y1 - y0, m_clipleft - x0, x1 - x0); 816 int sr = y0 + MulDiv64(y1 - y0, m_clipright - x0, x1 - x0); 817 mixin(addToClip("y0","sl","l","+")); 818 mixin(addEdgeM("m_clipleft","sl","m_clipright","sr","+")); 819 mixin(addToClip("sr","y1","r","+")); 820 break; 821 case (10): // 1001 --> x0 right, x1 right 822 mixin(addToClip("y0","y1","r","+")); 823 break; 824 default: // everything else is NOP 825 break; 826 } 827 } 828 else 829 { 830 int x1 = m_prevx, y1 = m_prevy, x0 = x, y0 = y; 831 832 // edge is outside clip box or horizontal 833 834 if ((y0 == y1) || (y0 >= m_clipbottom) || (y1 <= m_cliptop)) 835 { 836 goto finished; 837 } 838 839 // clip to top and bottom 840 841 if (y0 < m_cliptop) 842 { 843 x0 = x0 + MulDiv64(m_cliptop - y0, x1 - x0, y1 - y0); 844 y0 = m_cliptop; 845 } 846 847 if (y1 > m_clipbottom) 848 { 849 x1 = x0 + MulDiv64(m_clipbottom - y0, x1 - x0, y1 - y0); 850 y1 = m_clipbottom; 851 } 852 853 // track y extent 854 855 if (y0 < m_yrmin) m_yrmin = y0; 856 if (y1 > m_yrmax) m_yrmax = y1; 857 858 // generate horizontal zoning flags, these are set depending on where 859 // x0 and x1 are in respect of the clip box. 860 861 uint a = cast(uint)(x0<m_clipleft); 862 uint b = cast(uint)(x0>m_clipright); 863 uint c = cast(uint)(x1<m_clipleft); 864 uint d = cast(uint)(x1>m_clipright); 865 uint flags = a | (b*2) | (c*4) | (d*8); 866 867 if (flags == 0) // bit faster to pull no clip out front 868 { 869 mixin(addEdgeM("x0","y0","x1","y1","-")); 870 goto finished; 871 } 872 873 // note cliping here can occasionaly result in horizontals, and can 874 // ocaisionaly put a horizontal on bucket for clipbotttom, which is 875 // outside the drawable area, currently it allows it and zeros that 876 // bucket after rasterization. 877 878 switch (flags) 879 { 880 case (1): // 0001 --> x0 left, x1 center 881 int sy = y0 + MulDiv64(y1 - y0, m_clipleft - x0, x1 - x0); 882 mixin(addToClip("y0","sy","l","-")); 883 mixin(addEdgeM("m_clipleft","sy","x1","y1","-")); 884 break; 885 case (2): // 0010 --> x0 right, x1 center 886 int sy = y0 + MulDiv64(y1 - y0, m_clipright - x0, x1 - x0); 887 mixin(addToClip("y0","sy","r","-")); 888 mixin(addEdgeM("m_clipright","sy","x1","y1","-")); 889 break; 890 case (4): // 0100 --> x0 center, x1 left 891 int sy = y0 + MulDiv64(y1 - y0, m_clipleft - x0, x1 - x0); 892 mixin(addEdgeM("x0","y0","m_clipleft","sy","-")); 893 mixin(addToClip("sy","y1","l","-")); 894 break; 895 case (5): // 0101 --> x0 left, x1 left 896 mixin(addToClip("y0","y1","l","-")); 897 break; 898 case (6): // 0110 --> x0 right, x1 left 899 int sl = y0 + MulDiv64(y1 - y0, m_clipleft - x0, x1 - x0); 900 int sr = y0 + MulDiv64(y1 - y0, m_clipright - x0, x1 - x0); 901 mixin(addToClip("y0","sr","r","-")); 902 mixin(addEdgeM("m_clipright","sr","m_clipleft","sl","-")); 903 mixin(addToClip("sl","y1","l","-")); 904 break; 905 case (8): // 1000 --> x0 center, x1 right 906 int sy = y0 + MulDiv64(y1 - y0, m_clipright - x0, x1 - x0); 907 mixin(addEdgeM("x0","y0","m_clipright","sy","-")); 908 mixin(addToClip("sy","y1","r","-")); 909 break; 910 case (9): // 1001 --> x0 left, x1 right 911 int sl = y0 + MulDiv64(y1 - y0, m_clipleft - x0, x1 - x0); 912 int sr = y0 + MulDiv64(y1 - y0, m_clipright - x0, x1 - x0); 913 mixin(addToClip("y0","sl","l","-")); 914 mixin(addEdgeM("m_clipleft","sl","m_clipright","sr","-")); 915 mixin(addToClip("sr","y1","r","-")); 916 break; 917 case (10): // 1001 --> x0 right, x1 right 918 mixin(addToClip("y0","y1","r","-")); 919 break; 920 default: // everything else is NOP 921 break; 922 } 923 } 924 925 finished: 926 927 m_prevx = x; 928 m_prevy = y; 929 930 // debugClipBuffer("L"); 931 } 932 933 // edge struct 934 935 struct Edge 936 { 937 long x, dx, dy; 938 int y, y2; 939 Edge* next; 940 } 941 942 ArenaAllocator!(Edge,100) m_edgepool; 943 944 Array!(Edge*) m_buckets; 945 Array!int m_scandelta; 946 Array!DMWord m_deltamask; 947 Array!int m_clipbfr_l; 948 Array!int m_clipbfr_r; 949 950 // clip rectangle, in 24:8 fixed point 951 952 int m_clipleft; 953 int m_cliptop; 954 int m_clipright; 955 int m_clipbottom; 956 957 // keeps track of y extent 958 959 int m_yrmin,m_yrmax; 960 961 // start of current subpath, 962 963 int m_subpx,m_subpy; 964 965 // previous x,y (internal coords) 966 967 int m_prevx,m_prevy; 968 969 // previous x,y float coords 970 971 float m_fprevx,m_fprevy; 972 973 // writeln various info for debug purposes 974 975 void debugEdgeCount() 976 { 977 import std.stdio; 978 int edgecount; 979 foreach(e; m_buckets) 980 while (e) 981 { 982 e = e.next; 983 edgecount++; 984 } 985 986 writeln("edgecount:", edgecount); 987 } 988 989 void debugClipBuffer(string l_or_r) 990 { 991 import std.stdio; 992 if (l_or_r == "L") 993 { 994 write("left clip = "); 995 foreach(e; m_clipbfr_l) 996 { 997 write(e,":"); 998 } 999 } 1000 else 1001 { 1002 write("right clip = "); 1003 foreach(e; m_clipbfr_r) 1004 { 1005 import std.stdio; write(e,":"); 1006 } 1007 } 1008 } 1009 1010 static string debugEdgeCoords(string x0, string y0, string x1, string y1, string dir) 1011 { 1012 return 1013 "import std.stdio;" ~ 1014 "writeln("~x0~",' ',"~y0~",' ',"~x1~",' ',"~y1~",' ',\""~dir~"\");"; 1015 } 1016 }