28 import jdk.internal.misc.Unsafe;
29
30 /**
31 * An object used to cache pre-rendered complex paths.
32 *
33 * @see Renderer
34 */
35 public final class MarlinCache implements MarlinConst {
36
37 static final boolean FORCE_RLE = MarlinProperties.isForceRLE();
38 static final boolean FORCE_NO_RLE = MarlinProperties.isForceNoRLE();
39 // minimum width to try using RLE encoding:
40 static final int RLE_MIN_WIDTH
41 = Math.max(BLOCK_SIZE, MarlinProperties.getRLEMinWidth());
42 // maximum width for RLE encoding:
43 // values are stored as int [x|alpha] where alpha is 8 bits
44 static final int RLE_MAX_WIDTH = 1 << (24 - 1);
45
46 // 2048 (pixelSize) alpha values (width) x 32 rows (tile) = 64K bytes
47 // x1 instead of 4 bytes (RLE) ie 1/4 capacity or average good RLE compression
48 static final long INITIAL_CHUNK_ARRAY = TILE_SIZE * INITIAL_PIXEL_DIM; // 64K
49
50 // The alpha map used by this object (taken out of our map cache) to convert
51 // pixel coverage counts gotten from MarlinCache (which are in the range
52 // [0, maxalpha]) into alpha values, which are in [0,256).
53 static final byte[] ALPHA_MAP;
54
55 static final OffHeapArray ALPHA_MAP_UNSAFE;
56
57 static {
58 final byte[] _ALPHA_MAP = buildAlphaMap(MAX_AA_ALPHA);
59
60 ALPHA_MAP_UNSAFE = new OffHeapArray(_ALPHA_MAP, _ALPHA_MAP.length); // 1K
61 ALPHA_MAP =_ALPHA_MAP;
62
63 final Unsafe _unsafe = OffHeapArray.UNSAFE;
64 final long addr = ALPHA_MAP_UNSAFE.address;
65
66 for (int i = 0; i < _ALPHA_MAP.length; i++) {
67 _unsafe.putByte(addr + i, _ALPHA_MAP[i]);
68 }
69 }
70
71 int bboxX0, bboxY0, bboxX1, bboxY1;
72
73 // 1D dirty arrays
74 // row index in rowAAChunk[]
75 final long[] rowAAChunkIndex = new long[TILE_SIZE];
76 // first pixel (inclusive) for each row
77 final int[] rowAAx0 = new int[TILE_SIZE];
78 // last pixel (exclusive) for each row
79 final int[] rowAAx1 = new int[TILE_SIZE];
80 // encoding mode (0=raw, 1=RLE encoding) for each row
81 final int[] rowAAEnc = new int[TILE_SIZE];
82 // coded length (RLE encoding) for each row
83 final long[] rowAALen = new long[TILE_SIZE];
84 // last position in RLE decoding for each row (getAlpha):
85 final long[] rowAAPos = new long[TILE_SIZE];
86
87 // dirty off-heap array containing pixel coverages for (32) rows (packed)
88 // if encoding=raw, it contains alpha coverage values (val) as integer
89 // if encoding=RLE, it contains tuples (val, last x-coordinate exclusive)
90 // use rowAAx0/rowAAx1 to get row indices within this chunk
91 final OffHeapArray rowAAChunk;
92
93 // current position in rowAAChunk array
94 long rowAAChunkPos;
95
96 // touchedTile[i] is the sum of all the alphas in the tile with
97 // x=j*TILE_SIZE+bboxX0.
98 int[] touchedTile;
99
100 // per-thread renderer context
101 final RendererContext rdrCtx;
102
103 // touchedTile ref (clean)
104 private final IntArrayCache.Reference touchedTile_ref;
105
106 int tileMin, tileMax;
107
108 boolean useRLE = false;
109
110 MarlinCache(final RendererContext rdrCtx) {
111 this.rdrCtx = rdrCtx;
112
113 rowAAChunk = rdrCtx.newOffHeapArray(INITIAL_CHUNK_ARRAY); // 64K
114
115 touchedTile_ref = rdrCtx.newCleanIntArrayRef(INITIAL_ARRAY); // 1K = 1 tile line
116 touchedTile = touchedTile_ref.initial;
117
118 // tile used marks:
119 tileMin = Integer.MAX_VALUE;
120 tileMax = Integer.MIN_VALUE;
121 }
122
123 void init(int minx, int miny, int maxx, int maxy, int edgeSumDeltaY)
124 {
125 // assert maxy >= miny && maxx >= minx;
126 bboxX0 = minx;
127 bboxY0 = miny;
128 bboxX1 = maxx;
129 bboxY1 = maxy;
130
131 final int width = (maxx - minx);
132
133 if (FORCE_NO_RLE) {
134 useRLE = false;
135 } else if (FORCE_RLE) {
136 useRLE = true;
137 } else {
138 // heuristics: use both bbox area and complexity
139 // ie number of primitives:
140
141 // fast check min and max width (maxx < 23bits):
142 if (width <= RLE_MIN_WIDTH || width >= RLE_MAX_WIDTH) {
143 useRLE = false;
144 } else {
145 // perimeter approach: how fit the total length into given height:
146
147 // if stroking: meanCrossings /= 2 => divide edgeSumDeltaY by 2
148 final int heightSubPixel
149 = (((maxy - miny) << SUBPIXEL_LG_POSITIONS_Y) << rdrCtx.stroking);
150
151 // check meanDist > block size:
152 // check width / (meanCrossings - 1) >= RLE_THRESHOLD
153
154 // fast case: (meanCrossingPerPixel <= 2) means 1 span only
155 useRLE = (edgeSumDeltaY <= (heightSubPixel << 1))
156 // note: already checked (meanCrossingPerPixel <= 2)
157 // rewritten to avoid division:
158 || (width * heightSubPixel) >
159 ((edgeSumDeltaY - heightSubPixel) << BLOCK_SIZE_LG);
160
161 if (DO_TRACE && !useRLE) {
162 final float meanCrossings
163 = ((float) edgeSumDeltaY) / heightSubPixel;
164 final float meanDist = width / (meanCrossings - 1);
165
166 System.out.println("High complexity: "
167 + " for bbox[width = " + width
168 + " height = " + (maxy - miny)
169 + "] edgeSumDeltaY = " + edgeSumDeltaY
170 + " heightSubPixel = " + heightSubPixel
171 + " meanCrossings = "+ meanCrossings
172 + " meanDist = " + meanDist
173 + " width = " + (width * heightSubPixel)
174 + " <= criteria: " + ((edgeSumDeltaY - heightSubPixel) << BLOCK_SIZE_LG)
175 );
176 }
177 }
178 }
179
180 // the ceiling of (maxy - miny + 1) / TILE_SIZE;
181 final int nxTiles = (width + TILE_SIZE) >> TILE_SIZE_LG;
182
183 if (nxTiles > INITIAL_ARRAY) {
184 if (DO_STATS) {
185 rdrCtx.stats.stat_array_marlincache_touchedTile.add(nxTiles);
186 }
187 touchedTile = touchedTile_ref.getArray(nxTiles);
188 }
189 }
190
191 /**
192 * Disposes this cache:
193 * clean up before reusing this instance
194 */
195 void dispose() {
196 // Reset touchedTile if needed:
197 resetTileLine(0);
198
199 if (DO_STATS) {
200 rdrCtx.stats.totalOffHeap += rowAAChunk.length;
201 }
202
203 // Return arrays:
204 touchedTile = touchedTile_ref.putArray(touchedTile, 0, 0); // already zero filled
205
206 // At last: resize back off-heap rowAA to initial size
207 if (rowAAChunk.length != INITIAL_CHUNK_ARRAY) {
208 // note: may throw OOME:
209 rowAAChunk.resize(INITIAL_CHUNK_ARRAY);
210 }
211 if (DO_CLEAN_DIRTY) {
212 // Force zero-fill dirty arrays:
213 rowAAChunk.fill(BYTE_0);
214 }
215 }
216
217 void resetTileLine(final int pminY) {
218 // update bboxY0 to process a complete tile line [0 - 32]
219 bboxY0 = pminY;
220
221 // reset current pos
222 if (DO_STATS) {
223 rdrCtx.stats.stat_cache_rowAAChunk.add(rowAAChunkPos);
224 }
225 rowAAChunkPos = 0L;
226
227 // Reset touchedTile:
228 if (tileMin != Integer.MAX_VALUE) {
229 if (DO_STATS) {
230 rdrCtx.stats.stat_cache_tiles.add(tileMax - tileMin);
231 }
232 // clean only dirty touchedTile:
233 if (tileMax == 1) {
234 touchedTile[0] = 0;
235 } else {
236 IntArrayCache.fill(touchedTile, tileMin, tileMax, 0);
237 }
238 // reset tile used marks:
239 tileMin = Integer.MAX_VALUE;
240 tileMax = Integer.MIN_VALUE;
241 }
242
243 if (DO_CLEAN_DIRTY) {
244 // Force zero-fill dirty arrays:
245 rowAAChunk.fill(BYTE_0);
246 }
247 }
248
249 void clearAARow(final int y) {
250 // process tile line [0 - 32]
252
253 // update pixel range:
254 rowAAx0[row] = 0; // first pixel inclusive
255 rowAAx1[row] = 0; // last pixel exclusive
256 rowAAEnc[row] = 0; // raw encoding
257
258 // note: leave rowAAChunkIndex[row] undefined
259 // and rowAALen[row] & rowAAPos[row] (RLE)
260 }
261
262 /**
263 * Copy the given alpha data into the rowAA cache
264 * @param alphaRow alpha data to copy from
265 * @param y y pixel coordinate
266 * @param px0 first pixel inclusive x0
267 * @param px1 last pixel exclusive x1
268 */
269 void copyAARowNoRLE(final int[] alphaRow, final int y,
270 final int px0, final int px1)
271 {
272 if (DO_MONITORS) {
273 rdrCtx.stats.mon_rdr_copyAARow.start();
274 }
275
276 // skip useless pixels above boundary
277 final int px_bbox1 = FloatMath.min(px1, bboxX1);
278
279 if (DO_LOG_BOUNDS) {
280 MarlinUtils.logInfo("row = [" + px0 + " ... " + px_bbox1
281 + " (" + px1 + ") [ for y=" + y);
282 }
283
284 final int row = y - bboxY0;
285
286 // update pixel range:
287 rowAAx0[row] = px0; // first pixel inclusive
288 rowAAx1[row] = px_bbox1; // last pixel exclusive
289 rowAAEnc[row] = 0; // raw encoding
290
291 // get current position (bytes):
292 final long pos = rowAAChunkPos;
293 // update row index to current position:
294 rowAAChunkIndex[row] = pos;
295
296 // determine need array size:
297 // for RLE encoding, position must be aligned to 4 bytes (int):
298 // align - 1 = 3 so add +3 and round-off by mask ~3 = -4
299 final long needSize = pos + ((px_bbox1 - px0 + 3) & -4);
300
301 // update next position (bytes):
302 rowAAChunkPos = needSize;
303
304 // update row data:
305 final OffHeapArray _rowAAChunk = rowAAChunk;
306 // ensure rowAAChunk capacity:
307 if (_rowAAChunk.length < needSize) {
308 expandRowAAChunk(needSize);
309 }
310 if (DO_STATS) {
311 rdrCtx.stats.stat_cache_rowAA.add(px_bbox1 - px0);
312 }
313
314 // rowAA contains only alpha values for range[x0; x1[
315 final int[] _touchedTile = touchedTile;
316 final int _TILE_SIZE_LG = TILE_SIZE_LG;
317
318 final int from = px0 - bboxX0; // first pixel inclusive
319 final int to = px_bbox1 - bboxX0; // last pixel exclusive
320
321 final Unsafe _unsafe = OffHeapArray.UNSAFE;
322 final long SIZE_BYTE = 1L;
323 final long addr_alpha = ALPHA_MAP_UNSAFE.address;
324 long addr_off = _rowAAChunk.address + pos;
325
326 // compute alpha sum into rowAA:
327 for (int x = from, val = 0; x < to; x++) {
328 // alphaRow is in [0; MAX_COVERAGE]
329 val += alphaRow[x]; // [from; to[
330
331 // ensure values are in [0; MAX_AA_ALPHA] range
332 if (DO_AA_RANGE_CHECK) {
333 if (val < 0) {
334 System.out.println("Invalid coverage = " + val);
335 val = 0;
336 }
351 }
352 addr_off += SIZE_BYTE;
353 }
354
355 // update tile used marks:
356 int tx = from >> _TILE_SIZE_LG; // inclusive
357 if (tx < tileMin) {
358 tileMin = tx;
359 }
360
361 tx = ((to - 1) >> _TILE_SIZE_LG) + 1; // exclusive (+1 to be sure)
362 if (tx > tileMax) {
363 tileMax = tx;
364 }
365
366 if (DO_LOG_BOUNDS) {
367 MarlinUtils.logInfo("clear = [" + from + " ... " + to + "[");
368 }
369
370 // Clear alpha row for reuse:
371 IntArrayCache.fill(alphaRow, from, px1 - bboxX0, 0);
372
373 if (DO_MONITORS) {
374 rdrCtx.stats.mon_rdr_copyAARow.stop();
375 }
376 }
377
378 void copyAARowRLE_WithBlockFlags(final int[] blkFlags, final int[] alphaRow,
379 final int y, final int px0, final int px1)
380 {
381 if (DO_MONITORS) {
382 rdrCtx.stats.mon_rdr_copyAARow.start();
383 }
384
385 // Copy rowAA data into the piscesCache if one is present
386 final int _bboxX0 = bboxX0;
387
388 // process tile line [0 - 32]
389 final int row = y - bboxY0;
390 final int from = px0 - _bboxX0; // first pixel inclusive
391
392 // skip useless pixels above boundary
393 final int px_bbox1 = FloatMath.min(px1, bboxX1);
394 final int to = px_bbox1 - _bboxX0; // last pixel exclusive
395
396 if (DO_LOG_BOUNDS) {
397 MarlinUtils.logInfo("row = [" + px0 + " ... " + px_bbox1
398 + " (" + px1 + ") [ for y=" + y);
399 }
400
401 // get current position:
402 final long initialPos = startRLERow(row, px0, px_bbox1);
403
404 // determine need array size:
405 // pessimistic: max needed size = deltaX x 4 (1 int)
406 final long needSize = initialPos + ((to - from) << 2);
407
408 // update row data:
409 OffHeapArray _rowAAChunk = rowAAChunk;
410 // ensure rowAAChunk capacity:
411 if (_rowAAChunk.length < needSize) {
412 expandRowAAChunk(needSize);
413 }
414
415 final Unsafe _unsafe = OffHeapArray.UNSAFE;
416 final long SIZE_INT = 4L;
417 final long addr_alpha = ALPHA_MAP_UNSAFE.address;
418 long addr_off = _rowAAChunk.address + initialPos;
419
420 final int[] _touchedTile = touchedTile;
421 final int _TILE_SIZE_LG = TILE_SIZE_LG;
422 final int _BLK_SIZE_LG = BLOCK_SIZE_LG;
423
424 // traverse flagged blocks:
425 final int blkW = (from >> _BLK_SIZE_LG);
426 final int blkE = (to >> _BLK_SIZE_LG) + 1;
427
428 // Perform run-length encoding and store results in the piscesCache
429 int val = 0;
430 int cx0 = from;
431 int runLen;
432
433 final int _MAX_VALUE = Integer.MAX_VALUE;
434 int last_t0 = _MAX_VALUE;
435
436 int skip = 0;
437
438 for (int t = blkW, blk_x0, blk_x1, cx, delta; t <= blkE; t++) {
439 if (blkFlags[t] != 0) {
440 blkFlags[t] = 0;
441
442 if (last_t0 == _MAX_VALUE) {
443 last_t0 = t;
444 }
445 continue;
446 }
476 // special case to encode entries into a single int:
477 if (val == 0) {
478 _unsafe.putInt(addr_off,
479 ((_bboxX0 + cx) << 8)
480 );
481 } else {
482 _unsafe.putInt(addr_off,
483 ((_bboxX0 + cx) << 8)
484 | (((int) _unsafe.getByte(addr_alpha + val)) & 0xFF) // [0..255]
485 );
486
487 if (runLen == 1) {
488 _touchedTile[cx0 >> _TILE_SIZE_LG] += val;
489 } else {
490 touchTile(cx0, val, cx, runLen, _touchedTile);
491 }
492 }
493 addr_off += SIZE_INT;
494
495 if (DO_STATS) {
496 rdrCtx.stats.hist_tile_generator_encoding_runLen
497 .add(runLen);
498 }
499 cx0 = cx;
500 }
501
502 // alpha value = running sum of coverage delta:
503 val += delta;
504
505 // ensure values are in [0; MAX_AA_ALPHA] range
506 if (DO_AA_RANGE_CHECK) {
507 if (val < 0) {
508 System.out.println("Invalid coverage = " + val);
509 val = 0;
510 }
511 if (val > MAX_AA_ALPHA) {
512 System.out.println("Invalid coverage = " + val);
513 val = MAX_AA_ALPHA;
514 }
515 }
516 }
539 // special case to encode entries into a single int:
540 if (val == 0) {
541 _unsafe.putInt(addr_off,
542 ((_bboxX0 + to) << 8)
543 );
544 } else {
545 _unsafe.putInt(addr_off,
546 ((_bboxX0 + to) << 8)
547 | (((int) _unsafe.getByte(addr_alpha + val)) & 0xFF) // [0..255]
548 );
549
550 if (runLen == 1) {
551 _touchedTile[cx0 >> _TILE_SIZE_LG] += val;
552 } else {
553 touchTile(cx0, val, to, runLen, _touchedTile);
554 }
555 }
556 addr_off += SIZE_INT;
557
558 if (DO_STATS) {
559 rdrCtx.stats.hist_tile_generator_encoding_runLen.add(runLen);
560 }
561
562 long len = (addr_off - _rowAAChunk.address);
563
564 // update coded length as bytes:
565 rowAALen[row] = (len - initialPos);
566
567 // update current position:
568 rowAAChunkPos = len;
569
570 if (DO_STATS) {
571 rdrCtx.stats.stat_cache_rowAA.add(rowAALen[row]);
572 rdrCtx.stats.hist_tile_generator_encoding_ratio.add(
573 (100 * skip) / (blkE - blkW)
574 );
575 }
576
577 // update tile used marks:
578 int tx = from >> _TILE_SIZE_LG; // inclusive
579 if (tx < tileMin) {
580 tileMin = tx;
581 }
582
583 tx = ((to - 1) >> _TILE_SIZE_LG) + 1; // exclusive (+1 to be sure)
584 if (tx > tileMax) {
585 tileMax = tx;
586 }
587
588 // Clear alpha row for reuse:
589 if (px1 > bboxX1) {
590 alphaRow[to ] = 0;
591 alphaRow[to + 1] = 0;
592 }
593 if (DO_CHECKS) {
594 IntArrayCache.check(blkFlags, blkW, blkE, 0);
595 IntArrayCache.check(alphaRow, from, px1 - bboxX0, 0);
596 }
597
598 if (DO_MONITORS) {
599 rdrCtx.stats.mon_rdr_copyAARow.stop();
600 }
601 }
602
603 long startRLERow(final int row, final int x0, final int x1) {
604 // rows are supposed to be added by increasing y.
605 rowAAx0[row] = x0; // first pixel inclusive
606 rowAAx1[row] = x1; // last pixel exclusive
607 rowAAEnc[row] = 1; // RLE encoding
608 rowAAPos[row] = 0L; // position = 0
609
610 // update row index to current position:
611 return (rowAAChunkIndex[row] = rowAAChunkPos);
612 }
613
614 private void expandRowAAChunk(final long needSize) {
615 if (DO_STATS) {
616 rdrCtx.stats.stat_array_marlincache_rowAAChunk.add(needSize);
617 }
618
619 // note: throw IOOB if neededSize > 2Gb:
620 final long newSize = ArrayCacheConst.getNewLargeSize(rowAAChunk.length,
621 needSize);
622
623 rowAAChunk.resize(newSize);
624 }
625
626 private void touchTile(final int x0, final int val, final int x1,
627 final int runLen,
628 final int[] _touchedTile)
629 {
630 // the x and y of the current row, minus bboxX0, bboxY0
631 // process tile line [0 - 32]
632 final int _TILE_SIZE_LG = TILE_SIZE_LG;
633
634 // update touchedTile
635 int tx = (x0 >> _TILE_SIZE_LG);
636
637 // handle trivial case: same tile (x0, x0+runLen)
638 if (tx == (x1 >> _TILE_SIZE_LG)) {
639 // same tile:
640 _touchedTile[tx] += val * runLen;
641 return;
642 }
643
644 final int tx1 = (x1 - 1) >> _TILE_SIZE_LG;
645
646 if (tx <= tx1) {
647 final int nextTileXCoord = (tx + 1) << _TILE_SIZE_LG;
648 _touchedTile[tx++] += val * (nextTileXCoord - x0);
649 }
650 if (tx < tx1) {
651 // don't go all the way to tx1 - we need to handle the last
652 // tile as a special case (just like we did with the first
653 final int tileVal = (val << _TILE_SIZE_LG);
654 for (; tx < tx1; tx++) {
655 _touchedTile[tx] += tileVal;
656 }
657 }
658 // they will be equal unless x0 >> TILE_SIZE_LG == tx1
659 if (tx == tx1) {
660 final int txXCoord = tx << _TILE_SIZE_LG;
661 final int nextTileXCoord = (tx + 1) << _TILE_SIZE_LG;
662
663 final int lastXCoord = (nextTileXCoord <= x1) ? nextTileXCoord : x1;
664 _touchedTile[tx] += val * (lastXCoord - txXCoord);
665 }
666 }
667
668 int alphaSumInTile(final int x) {
669 return touchedTile[(x - bboxX0) >> TILE_SIZE_LG];
670 }
671
672 @Override
673 public String toString() {
674 return "bbox = ["
675 + bboxX0 + ", " + bboxY0 + " => "
676 + bboxX1 + ", " + bboxY1 + "]\n";
677 }
678
679 private static byte[] buildAlphaMap(final int maxalpha) {
680 // double size !
681 final byte[] alMap = new byte[maxalpha << 1];
682 final int halfmaxalpha = maxalpha >> 2;
683 for (int i = 0; i <= maxalpha; i++) {
684 alMap[i] = (byte) ((i * 255 + halfmaxalpha) / maxalpha);
685 // System.out.println("alphaMap[" + i + "] = "
686 // + Byte.toUnsignedInt(alMap[i]));
687 }
688 return alMap;
689 }
|
28 import jdk.internal.misc.Unsafe;
29
30 /**
31 * An object used to cache pre-rendered complex paths.
32 *
33 * @see Renderer
34 */
35 public final class MarlinCache implements MarlinConst {
36
37 static final boolean FORCE_RLE = MarlinProperties.isForceRLE();
38 static final boolean FORCE_NO_RLE = MarlinProperties.isForceNoRLE();
39 // minimum width to try using RLE encoding:
40 static final int RLE_MIN_WIDTH
41 = Math.max(BLOCK_SIZE, MarlinProperties.getRLEMinWidth());
42 // maximum width for RLE encoding:
43 // values are stored as int [x|alpha] where alpha is 8 bits
44 static final int RLE_MAX_WIDTH = 1 << (24 - 1);
45
46 // 2048 (pixelSize) alpha values (width) x 32 rows (tile) = 64K bytes
47 // x1 instead of 4 bytes (RLE) ie 1/4 capacity or average good RLE compression
48 static final long INITIAL_CHUNK_ARRAY = TILE_H * INITIAL_PIXEL_DIM; // 64K
49
50 // The alpha map used by this object (taken out of our map cache) to convert
51 // pixel coverage counts gotten from MarlinCache (which are in the range
52 // [0, maxalpha]) into alpha values, which are in [0,256).
53 static final byte[] ALPHA_MAP;
54
55 static final OffHeapArray ALPHA_MAP_UNSAFE;
56
57 static {
58 final byte[] _ALPHA_MAP = buildAlphaMap(MAX_AA_ALPHA);
59
60 ALPHA_MAP_UNSAFE = new OffHeapArray(_ALPHA_MAP, _ALPHA_MAP.length); // 1K
61 ALPHA_MAP =_ALPHA_MAP;
62
63 final Unsafe _unsafe = OffHeapArray.UNSAFE;
64 final long addr = ALPHA_MAP_UNSAFE.address;
65
66 for (int i = 0; i < _ALPHA_MAP.length; i++) {
67 _unsafe.putByte(addr + i, _ALPHA_MAP[i]);
68 }
69 }
70
71 int bboxX0, bboxY0, bboxX1, bboxY1;
72
73 // 1D dirty arrays
74 // row index in rowAAChunk[]
75 final long[] rowAAChunkIndex = new long[TILE_H];
76 // first pixel (inclusive) for each row
77 final int[] rowAAx0 = new int[TILE_H];
78 // last pixel (exclusive) for each row
79 final int[] rowAAx1 = new int[TILE_H];
80 // encoding mode (0=raw, 1=RLE encoding) for each row
81 final int[] rowAAEnc = new int[TILE_H];
82 // coded length (RLE encoding) for each row
83 final long[] rowAALen = new long[TILE_H];
84 // last position in RLE decoding for each row (getAlpha):
85 final long[] rowAAPos = new long[TILE_H];
86
87 // dirty off-heap array containing pixel coverages for (32) rows (packed)
88 // if encoding=raw, it contains alpha coverage values (val) as integer
89 // if encoding=RLE, it contains tuples (val, last x-coordinate exclusive)
90 // use rowAAx0/rowAAx1 to get row indices within this chunk
91 final OffHeapArray rowAAChunk;
92
93 // current position in rowAAChunk array
94 long rowAAChunkPos;
95
96 // touchedTile[i] is the sum of all the alphas in the tile with
97 // x=j*TILE_SIZE+bboxX0.
98 int[] touchedTile;
99
100 // per-thread renderer stats
101 final RendererStats rdrStats;
102
103 // touchedTile ref (clean)
104 private final IntArrayCache.Reference touchedTile_ref;
105
106 int tileMin, tileMax;
107
108 boolean useRLE = false;
109
110 MarlinCache(final IRendererContext rdrCtx) {
111 this.rdrStats = rdrCtx.stats();
112
113 rowAAChunk = rdrCtx.newOffHeapArray(INITIAL_CHUNK_ARRAY); // 64K
114
115 touchedTile_ref = rdrCtx.newCleanIntArrayRef(INITIAL_ARRAY); // 1K = 1 tile line
116 touchedTile = touchedTile_ref.initial;
117
118 // tile used marks:
119 tileMin = Integer.MAX_VALUE;
120 tileMax = Integer.MIN_VALUE;
121 }
122
123 void init(int minx, int miny, int maxx, int maxy)
124 {
125 // assert maxy >= miny && maxx >= minx;
126 bboxX0 = minx;
127 bboxY0 = miny;
128 bboxX1 = maxx;
129 bboxY1 = maxy;
130
131 final int width = (maxx - minx);
132
133 if (FORCE_NO_RLE) {
134 useRLE = false;
135 } else if (FORCE_RLE) {
136 useRLE = true;
137 } else {
138 // heuristics: use both bbox area and complexity
139 // ie number of primitives:
140
141 // fast check min and max width (maxx < 23bits):
142 if (width <= RLE_MIN_WIDTH || width >= RLE_MAX_WIDTH) {
143 useRLE = false;
144 } else {
145 useRLE = true;
146 }
147 }
148
149 // the ceiling of (maxy - miny + 1) / TILE_SIZE;
150 final int nxTiles = (width + TILE_W) >> TILE_W_LG;
151
152 if (nxTiles > INITIAL_ARRAY) {
153 if (DO_STATS) {
154 rdrStats.stat_array_marlincache_touchedTile.add(nxTiles);
155 }
156 touchedTile = touchedTile_ref.getArray(nxTiles);
157 }
158 }
159
160 /**
161 * Disposes this cache:
162 * clean up before reusing this instance
163 */
164 void dispose() {
165 // Reset touchedTile if needed:
166 resetTileLine(0);
167
168 if (DO_STATS) {
169 rdrStats.totalOffHeap += rowAAChunk.length;
170 }
171
172 // Return arrays:
173 touchedTile = touchedTile_ref.putArray(touchedTile, 0, 0); // already zero filled
174
175 // At last: resize back off-heap rowAA to initial size
176 if (rowAAChunk.length != INITIAL_CHUNK_ARRAY) {
177 // note: may throw OOME:
178 rowAAChunk.resize(INITIAL_CHUNK_ARRAY);
179 }
180 if (DO_CLEAN_DIRTY) {
181 // Force zero-fill dirty arrays:
182 rowAAChunk.fill(BYTE_0);
183 }
184 }
185
186 void resetTileLine(final int pminY) {
187 // update bboxY0 to process a complete tile line [0 - 32]
188 bboxY0 = pminY;
189
190 // reset current pos
191 if (DO_STATS) {
192 rdrStats.stat_cache_rowAAChunk.add(rowAAChunkPos);
193 }
194 rowAAChunkPos = 0L;
195
196 // Reset touchedTile:
197 if (tileMin != Integer.MAX_VALUE) {
198 if (DO_STATS) {
199 rdrStats.stat_cache_tiles.add(tileMax - tileMin);
200 }
201 // clean only dirty touchedTile:
202 if (tileMax == 1) {
203 touchedTile[0] = 0;
204 } else {
205 IntArrayCache.fill(touchedTile, tileMin, tileMax, 0);
206 }
207 // reset tile used marks:
208 tileMin = Integer.MAX_VALUE;
209 tileMax = Integer.MIN_VALUE;
210 }
211
212 if (DO_CLEAN_DIRTY) {
213 // Force zero-fill dirty arrays:
214 rowAAChunk.fill(BYTE_0);
215 }
216 }
217
218 void clearAARow(final int y) {
219 // process tile line [0 - 32]
221
222 // update pixel range:
223 rowAAx0[row] = 0; // first pixel inclusive
224 rowAAx1[row] = 0; // last pixel exclusive
225 rowAAEnc[row] = 0; // raw encoding
226
227 // note: leave rowAAChunkIndex[row] undefined
228 // and rowAALen[row] & rowAAPos[row] (RLE)
229 }
230
231 /**
232 * Copy the given alpha data into the rowAA cache
233 * @param alphaRow alpha data to copy from
234 * @param y y pixel coordinate
235 * @param px0 first pixel inclusive x0
236 * @param px1 last pixel exclusive x1
237 */
238 void copyAARowNoRLE(final int[] alphaRow, final int y,
239 final int px0, final int px1)
240 {
241 // skip useless pixels above boundary
242 final int px_bbox1 = FloatMath.min(px1, bboxX1);
243
244 if (DO_LOG_BOUNDS) {
245 MarlinUtils.logInfo("row = [" + px0 + " ... " + px_bbox1
246 + " (" + px1 + ") [ for y=" + y);
247 }
248
249 final int row = y - bboxY0;
250
251 // update pixel range:
252 rowAAx0[row] = px0; // first pixel inclusive
253 rowAAx1[row] = px_bbox1; // last pixel exclusive
254 rowAAEnc[row] = 0; // raw encoding
255
256 // get current position (bytes):
257 final long pos = rowAAChunkPos;
258 // update row index to current position:
259 rowAAChunkIndex[row] = pos;
260
261 // determine need array size:
262 // for RLE encoding, position must be aligned to 4 bytes (int):
263 // align - 1 = 3 so add +3 and round-off by mask ~3 = -4
264 final long needSize = pos + ((px_bbox1 - px0 + 3) & -4);
265
266 // update next position (bytes):
267 rowAAChunkPos = needSize;
268
269 // update row data:
270 final OffHeapArray _rowAAChunk = rowAAChunk;
271 // ensure rowAAChunk capacity:
272 if (_rowAAChunk.length < needSize) {
273 expandRowAAChunk(needSize);
274 }
275 if (DO_STATS) {
276 rdrStats.stat_cache_rowAA.add(px_bbox1 - px0);
277 }
278
279 // rowAA contains only alpha values for range[x0; x1[
280 final int[] _touchedTile = touchedTile;
281 final int _TILE_SIZE_LG = TILE_W_LG;
282
283 final int from = px0 - bboxX0; // first pixel inclusive
284 final int to = px_bbox1 - bboxX0; // last pixel exclusive
285
286 final Unsafe _unsafe = OffHeapArray.UNSAFE;
287 final long SIZE_BYTE = 1L;
288 final long addr_alpha = ALPHA_MAP_UNSAFE.address;
289 long addr_off = _rowAAChunk.address + pos;
290
291 // compute alpha sum into rowAA:
292 for (int x = from, val = 0; x < to; x++) {
293 // alphaRow is in [0; MAX_COVERAGE]
294 val += alphaRow[x]; // [from; to[
295
296 // ensure values are in [0; MAX_AA_ALPHA] range
297 if (DO_AA_RANGE_CHECK) {
298 if (val < 0) {
299 System.out.println("Invalid coverage = " + val);
300 val = 0;
301 }
316 }
317 addr_off += SIZE_BYTE;
318 }
319
320 // update tile used marks:
321 int tx = from >> _TILE_SIZE_LG; // inclusive
322 if (tx < tileMin) {
323 tileMin = tx;
324 }
325
326 tx = ((to - 1) >> _TILE_SIZE_LG) + 1; // exclusive (+1 to be sure)
327 if (tx > tileMax) {
328 tileMax = tx;
329 }
330
331 if (DO_LOG_BOUNDS) {
332 MarlinUtils.logInfo("clear = [" + from + " ... " + to + "[");
333 }
334
335 // Clear alpha row for reuse:
336 IntArrayCache.fill(alphaRow, from, px1 + 1 - bboxX0, 0);
337 }
338
339 void copyAARowRLE_WithBlockFlags(final int[] blkFlags, final int[] alphaRow,
340 final int y, final int px0, final int px1)
341 {
342 // Copy rowAA data into the piscesCache if one is present
343 final int _bboxX0 = bboxX0;
344
345 // process tile line [0 - 32]
346 final int row = y - bboxY0;
347 final int from = px0 - _bboxX0; // first pixel inclusive
348
349 // skip useless pixels above boundary
350 final int px_bbox1 = FloatMath.min(px1, bboxX1);
351 final int to = px_bbox1 - _bboxX0; // last pixel exclusive
352
353 if (DO_LOG_BOUNDS) {
354 MarlinUtils.logInfo("row = [" + px0 + " ... " + px_bbox1
355 + " (" + px1 + ") [ for y=" + y);
356 }
357
358 // get current position:
359 final long initialPos = startRLERow(row, px0, px_bbox1);
360
361 // determine need array size:
362 // pessimistic: max needed size = deltaX x 4 (1 int)
363 final long needSize = initialPos + ((to - from) << 2);
364
365 // update row data:
366 OffHeapArray _rowAAChunk = rowAAChunk;
367 // ensure rowAAChunk capacity:
368 if (_rowAAChunk.length < needSize) {
369 expandRowAAChunk(needSize);
370 }
371
372 final Unsafe _unsafe = OffHeapArray.UNSAFE;
373 final long SIZE_INT = 4L;
374 final long addr_alpha = ALPHA_MAP_UNSAFE.address;
375 long addr_off = _rowAAChunk.address + initialPos;
376
377 final int[] _touchedTile = touchedTile;
378 final int _TILE_SIZE_LG = TILE_W_LG;
379 final int _BLK_SIZE_LG = BLOCK_SIZE_LG;
380
381 // traverse flagged blocks:
382 final int blkW = (from >> _BLK_SIZE_LG);
383 final int blkE = (to >> _BLK_SIZE_LG) + 1;
384 // ensure last block flag = 0 to process final block:
385 blkFlags[blkE] = 0;
386
387 // Perform run-length encoding and store results in the piscesCache
388 int val = 0;
389 int cx0 = from;
390 int runLen;
391
392 final int _MAX_VALUE = Integer.MAX_VALUE;
393 int last_t0 = _MAX_VALUE;
394
395 int skip = 0;
396
397 for (int t = blkW, blk_x0, blk_x1, cx, delta; t <= blkE; t++) {
398 if (blkFlags[t] != 0) {
399 blkFlags[t] = 0;
400
401 if (last_t0 == _MAX_VALUE) {
402 last_t0 = t;
403 }
404 continue;
405 }
435 // special case to encode entries into a single int:
436 if (val == 0) {
437 _unsafe.putInt(addr_off,
438 ((_bboxX0 + cx) << 8)
439 );
440 } else {
441 _unsafe.putInt(addr_off,
442 ((_bboxX0 + cx) << 8)
443 | (((int) _unsafe.getByte(addr_alpha + val)) & 0xFF) // [0..255]
444 );
445
446 if (runLen == 1) {
447 _touchedTile[cx0 >> _TILE_SIZE_LG] += val;
448 } else {
449 touchTile(cx0, val, cx, runLen, _touchedTile);
450 }
451 }
452 addr_off += SIZE_INT;
453
454 if (DO_STATS) {
455 rdrStats.hist_tile_generator_encoding_runLen
456 .add(runLen);
457 }
458 cx0 = cx;
459 }
460
461 // alpha value = running sum of coverage delta:
462 val += delta;
463
464 // ensure values are in [0; MAX_AA_ALPHA] range
465 if (DO_AA_RANGE_CHECK) {
466 if (val < 0) {
467 System.out.println("Invalid coverage = " + val);
468 val = 0;
469 }
470 if (val > MAX_AA_ALPHA) {
471 System.out.println("Invalid coverage = " + val);
472 val = MAX_AA_ALPHA;
473 }
474 }
475 }
498 // special case to encode entries into a single int:
499 if (val == 0) {
500 _unsafe.putInt(addr_off,
501 ((_bboxX0 + to) << 8)
502 );
503 } else {
504 _unsafe.putInt(addr_off,
505 ((_bboxX0 + to) << 8)
506 | (((int) _unsafe.getByte(addr_alpha + val)) & 0xFF) // [0..255]
507 );
508
509 if (runLen == 1) {
510 _touchedTile[cx0 >> _TILE_SIZE_LG] += val;
511 } else {
512 touchTile(cx0, val, to, runLen, _touchedTile);
513 }
514 }
515 addr_off += SIZE_INT;
516
517 if (DO_STATS) {
518 rdrStats.hist_tile_generator_encoding_runLen.add(runLen);
519 }
520
521 long len = (addr_off - _rowAAChunk.address);
522
523 // update coded length as bytes:
524 rowAALen[row] = (len - initialPos);
525
526 // update current position:
527 rowAAChunkPos = len;
528
529 if (DO_STATS) {
530 rdrStats.stat_cache_rowAA.add(rowAALen[row]);
531 rdrStats.hist_tile_generator_encoding_ratio.add(
532 (100 * skip) / (blkE - blkW)
533 );
534 }
535
536 // update tile used marks:
537 int tx = from >> _TILE_SIZE_LG; // inclusive
538 if (tx < tileMin) {
539 tileMin = tx;
540 }
541
542 tx = ((to - 1) >> _TILE_SIZE_LG) + 1; // exclusive (+1 to be sure)
543 if (tx > tileMax) {
544 tileMax = tx;
545 }
546
547 // Clear alpha row for reuse:
548 alphaRow[to] = 0;
549 if (DO_CHECKS) {
550 IntArrayCache.check(blkFlags, blkW, blkE, 0);
551 IntArrayCache.check(alphaRow, from, px1 + 1 - bboxX0, 0);
552 }
553 }
554
555 long startRLERow(final int row, final int x0, final int x1) {
556 // rows are supposed to be added by increasing y.
557 rowAAx0[row] = x0; // first pixel inclusive
558 rowAAx1[row] = x1; // last pixel exclusive
559 rowAAEnc[row] = 1; // RLE encoding
560 rowAAPos[row] = 0L; // position = 0
561
562 // update row index to current position:
563 return (rowAAChunkIndex[row] = rowAAChunkPos);
564 }
565
566 private void expandRowAAChunk(final long needSize) {
567 if (DO_STATS) {
568 rdrStats.stat_array_marlincache_rowAAChunk.add(needSize);
569 }
570
571 // note: throw IOOB if neededSize > 2Gb:
572 final long newSize = ArrayCacheConst.getNewLargeSize(rowAAChunk.length,
573 needSize);
574
575 rowAAChunk.resize(newSize);
576 }
577
578 private void touchTile(final int x0, final int val, final int x1,
579 final int runLen,
580 final int[] _touchedTile)
581 {
582 // the x and y of the current row, minus bboxX0, bboxY0
583 // process tile line [0 - 32]
584 final int _TILE_SIZE_LG = TILE_W_LG;
585
586 // update touchedTile
587 int tx = (x0 >> _TILE_SIZE_LG);
588
589 // handle trivial case: same tile (x0, x0+runLen)
590 if (tx == (x1 >> _TILE_SIZE_LG)) {
591 // same tile:
592 _touchedTile[tx] += val * runLen;
593 return;
594 }
595
596 final int tx1 = (x1 - 1) >> _TILE_SIZE_LG;
597
598 if (tx <= tx1) {
599 final int nextTileXCoord = (tx + 1) << _TILE_SIZE_LG;
600 _touchedTile[tx++] += val * (nextTileXCoord - x0);
601 }
602 if (tx < tx1) {
603 // don't go all the way to tx1 - we need to handle the last
604 // tile as a special case (just like we did with the first
605 final int tileVal = (val << _TILE_SIZE_LG);
606 for (; tx < tx1; tx++) {
607 _touchedTile[tx] += tileVal;
608 }
609 }
610 // they will be equal unless x0 >> TILE_SIZE_LG == tx1
611 if (tx == tx1) {
612 final int txXCoord = tx << _TILE_SIZE_LG;
613 final int nextTileXCoord = (tx + 1) << _TILE_SIZE_LG;
614
615 final int lastXCoord = (nextTileXCoord <= x1) ? nextTileXCoord : x1;
616 _touchedTile[tx] += val * (lastXCoord - txXCoord);
617 }
618 }
619
620 int alphaSumInTile(final int x) {
621 return touchedTile[(x - bboxX0) >> TILE_W_LG];
622 }
623
624 @Override
625 public String toString() {
626 return "bbox = ["
627 + bboxX0 + ", " + bboxY0 + " => "
628 + bboxX1 + ", " + bboxY1 + "]\n";
629 }
630
631 private static byte[] buildAlphaMap(final int maxalpha) {
632 // double size !
633 final byte[] alMap = new byte[maxalpha << 1];
634 final int halfmaxalpha = maxalpha >> 2;
635 for (int i = 0; i <= maxalpha; i++) {
636 alMap[i] = (byte) ((i * 255 + halfmaxalpha) / maxalpha);
637 // System.out.println("alphaMap[" + i + "] = "
638 // + Byte.toUnsignedInt(alMap[i]));
639 }
640 return alMap;
641 }
|