/* * Copyright (c) 2007, 2018, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package sun.java2d.marlin; import jdk.internal.misc.Unsafe; /** * An object used to cache pre-rendered complex paths. * * @see Renderer */ public final class MarlinCache implements MarlinConst { static final boolean FORCE_RLE = MarlinProperties.isForceRLE(); static final boolean FORCE_NO_RLE = MarlinProperties.isForceNoRLE(); // minimum width to try using RLE encoding: static final int RLE_MIN_WIDTH = Math.max(BLOCK_SIZE, MarlinProperties.getRLEMinWidth()); // maximum width for RLE encoding: // values are stored as int [x|alpha] where alpha is 8 bits static final int RLE_MAX_WIDTH = 1 << (24 - 1); // 4096 (pixels) alpha values (width) x 64 rows / 4 (tile) = 64K bytes // x1 instead of 4 bytes (RLE) ie 1/4 capacity or average good RLE compression static final long INITIAL_CHUNK_ARRAY = TILE_H * INITIAL_PIXEL_WIDTH >> 2; // 64K // The alpha map used by this object (taken out of our map cache) to convert // pixel coverage counts gotten from MarlinCache (which are in the range // [0, maxalpha]) into alpha values, which are in [0,256). static final byte[] ALPHA_MAP; static final OffHeapArray ALPHA_MAP_UNSAFE; static { final byte[] _ALPHA_MAP = buildAlphaMap(MAX_AA_ALPHA); ALPHA_MAP_UNSAFE = new OffHeapArray(_ALPHA_MAP, _ALPHA_MAP.length); // 1K ALPHA_MAP =_ALPHA_MAP; final Unsafe _unsafe = OffHeapArray.UNSAFE; final long addr = ALPHA_MAP_UNSAFE.address; for (int i = 0; i < _ALPHA_MAP.length; i++) { _unsafe.putByte(addr + i, _ALPHA_MAP[i]); } } int bboxX0, bboxY0, bboxX1, bboxY1; // 1D dirty arrays // row index in rowAAChunk[] final long[] rowAAChunkIndex = new long[TILE_H]; // first pixel (inclusive) for each row final int[] rowAAx0 = new int[TILE_H]; // last pixel (exclusive) for each row final int[] rowAAx1 = new int[TILE_H]; // encoding mode (0=raw, 1=RLE encoding) for each row final int[] rowAAEnc = new int[TILE_H]; // coded length (RLE encoding) for each row final long[] rowAALen = new long[TILE_H]; // last position in RLE decoding for each row (getAlpha): final long[] rowAAPos = new long[TILE_H]; // dirty off-heap array containing pixel coverages for (32) rows (packed) // if encoding=raw, it contains alpha coverage values (val) as integer // if encoding=RLE, it contains tuples (val, last x-coordinate exclusive) // use rowAAx0/rowAAx1 to get row indices within this chunk final OffHeapArray rowAAChunk; // current position in rowAAChunk array long rowAAChunkPos; // touchedTile[i] is the sum of all the alphas in the tile with // x=j*TILE_SIZE+bboxX0. int[] touchedTile; // per-thread renderer stats final RendererStats rdrStats; // touchedTile ref (clean) private final IntArrayCache.Reference touchedTile_ref; int tileMin, tileMax; boolean useRLE = false; MarlinCache(final IRendererContext rdrCtx) { this.rdrStats = rdrCtx.stats(); rowAAChunk = rdrCtx.newOffHeapArray(INITIAL_CHUNK_ARRAY); // 64K touchedTile_ref = rdrCtx.newCleanIntArrayRef(INITIAL_ARRAY); // 1K = 1 tile line touchedTile = touchedTile_ref.initial; // tile used marks: tileMin = Integer.MAX_VALUE; tileMax = Integer.MIN_VALUE; } void init(int minx, int miny, int maxx, int maxy) { // assert maxy >= miny && maxx >= minx; bboxX0 = minx; bboxY0 = miny; bboxX1 = maxx; bboxY1 = maxy; final int width = (maxx - minx); if (FORCE_NO_RLE) { useRLE = false; } else if (FORCE_RLE) { useRLE = true; } else { // heuristics: use both bbox area and complexity // ie number of primitives: // fast check min and max width (maxx < 23bits): useRLE = (width > RLE_MIN_WIDTH && width < RLE_MAX_WIDTH); } // the ceiling of (maxy - miny + 1) / TILE_SIZE; final int nxTiles = (width + TILE_W) >> TILE_W_LG; if (nxTiles > INITIAL_ARRAY) { if (DO_STATS) { rdrStats.stat_array_marlincache_touchedTile.add(nxTiles); } touchedTile = touchedTile_ref.getArray(nxTiles); } } /** * Disposes this cache: * clean up before reusing this instance */ void dispose() { // Reset touchedTile if needed: resetTileLine(0); if (DO_STATS) { rdrStats.totalOffHeap += rowAAChunk.length; } // Return arrays: touchedTile = touchedTile_ref.putArray(touchedTile, 0, 0); // already zero filled // At last: resize back off-heap rowAA to initial size if (rowAAChunk.length != INITIAL_CHUNK_ARRAY) { // note: may throw OOME: rowAAChunk.resize(INITIAL_CHUNK_ARRAY); } if (DO_CLEAN_DIRTY) { // Force zero-fill dirty arrays: rowAAChunk.fill(BYTE_0); } } void resetTileLine(final int pminY) { // update bboxY0 to process a complete tile line [0 - 32] bboxY0 = pminY; // reset current pos if (DO_STATS) { rdrStats.stat_cache_rowAAChunk.add(rowAAChunkPos); } rowAAChunkPos = 0L; // Reset touchedTile: if (tileMin != Integer.MAX_VALUE) { if (DO_STATS) { rdrStats.stat_cache_tiles.add(tileMax - tileMin); } // clean only dirty touchedTile: if (tileMax == 1) { touchedTile[0] = 0; } else { IntArrayCache.fill(touchedTile, tileMin, tileMax, 0); } // reset tile used marks: tileMin = Integer.MAX_VALUE; tileMax = Integer.MIN_VALUE; } if (DO_CLEAN_DIRTY) { // Force zero-fill dirty arrays: rowAAChunk.fill(BYTE_0); } } void clearAARow(final int y) { // process tile line [0 - 32] final int row = y - bboxY0; // update pixel range: rowAAx0[row] = 0; // first pixel inclusive rowAAx1[row] = 0; // last pixel exclusive rowAAEnc[row] = 0; // raw encoding // note: leave rowAAChunkIndex[row] undefined // and rowAALen[row] & rowAAPos[row] (RLE) } /** * Copy the given alpha data into the rowAA cache * @param alphaRow alpha data to copy from * @param y y pixel coordinate * @param px0 first pixel inclusive x0 * @param px1 last pixel exclusive x1 */ void copyAARowNoRLE(final int[] alphaRow, final int y, final int px0, final int px1) { // skip useless pixels above boundary final int px_bbox1 = FloatMath.min(px1, bboxX1); if (DO_LOG_BOUNDS) { MarlinUtils.logInfo("row = [" + px0 + " ... " + px_bbox1 + " (" + px1 + ") [ for y=" + y); } final int row = y - bboxY0; // update pixel range: rowAAx0[row] = px0; // first pixel inclusive rowAAx1[row] = px_bbox1; // last pixel exclusive rowAAEnc[row] = 0; // raw encoding // get current position (bytes): final long pos = rowAAChunkPos; // update row index to current position: rowAAChunkIndex[row] = pos; // determine need array size: // for RLE encoding, position must be aligned to 4 bytes (int): // align - 1 = 3 so add +3 and round-off by mask ~3 = -4 final long needSize = pos + ((px_bbox1 - px0 + 3) & -4); // update next position (bytes): rowAAChunkPos = needSize; // update row data: final OffHeapArray _rowAAChunk = rowAAChunk; // ensure rowAAChunk capacity: if (_rowAAChunk.length < needSize) { expandRowAAChunk(needSize); } if (DO_STATS) { rdrStats.stat_cache_rowAA.add(px_bbox1 - px0); } // rowAA contains only alpha values for range[x0; x1[ final int[] _touchedTile = touchedTile; final int _TILE_SIZE_LG = TILE_W_LG; final int from = px0 - bboxX0; // first pixel inclusive final int to = px_bbox1 - bboxX0; // last pixel exclusive final Unsafe _unsafe = OffHeapArray.UNSAFE; final long SIZE_BYTE = 1L; final long addr_alpha = ALPHA_MAP_UNSAFE.address; long addr_off = _rowAAChunk.address + pos; // compute alpha sum into rowAA: for (int x = from, val = 0; x < to; x++) { // alphaRow is in [0; MAX_COVERAGE] val += alphaRow[x]; // [from; to[ // ensure values are in [0; MAX_AA_ALPHA] range if (DO_AA_RANGE_CHECK) { if (val < 0) { MarlinUtils.logInfo("Invalid coverage = " + val); val = 0; } if (val > MAX_AA_ALPHA) { MarlinUtils.logInfo("Invalid coverage = " + val); val = MAX_AA_ALPHA; } } // store alpha sum (as byte): if (val == 0) { _unsafe.putByte(addr_off, (byte)0); // [0-255] } else { _unsafe.putByte(addr_off, _unsafe.getByte(addr_alpha + val)); // [0-255] // update touchedTile _touchedTile[x >> _TILE_SIZE_LG] += val; } addr_off += SIZE_BYTE; } // update tile used marks: int tx = from >> _TILE_SIZE_LG; // inclusive if (tx < tileMin) { tileMin = tx; } tx = ((to - 1) >> _TILE_SIZE_LG) + 1; // exclusive (+1 to be sure) if (tx > tileMax) { tileMax = tx; } if (DO_LOG_BOUNDS) { MarlinUtils.logInfo("clear = [" + from + " ... " + to + "["); } // Clear alpha row for reuse: IntArrayCache.fill(alphaRow, from, px1 + 1 - bboxX0, 0); } void copyAARowRLE_WithBlockFlags(final int[] blkFlags, final int[] alphaRow, final int y, final int px0, final int px1) { // Copy rowAA data into the piscesCache if one is present final int _bboxX0 = bboxX0; // process tile line [0 - 32] final int row = y - bboxY0; final int from = px0 - _bboxX0; // first pixel inclusive // skip useless pixels above boundary final int px_bbox1 = FloatMath.min(px1, bboxX1); final int to = px_bbox1 - _bboxX0; // last pixel exclusive if (DO_LOG_BOUNDS) { MarlinUtils.logInfo("row = [" + px0 + " ... " + px_bbox1 + " (" + px1 + ") [ for y=" + y); } // get current position: final long initialPos = startRLERow(row, px0, px_bbox1); // determine need array size: // pessimistic: max needed size = deltaX x 4 (1 int) final long needSize = initialPos + ((to - from) << 2); // update row data: OffHeapArray _rowAAChunk = rowAAChunk; // ensure rowAAChunk capacity: if (_rowAAChunk.length < needSize) { expandRowAAChunk(needSize); } final Unsafe _unsafe = OffHeapArray.UNSAFE; final long SIZE_INT = 4L; final long addr_alpha = ALPHA_MAP_UNSAFE.address; long addr_off = _rowAAChunk.address + initialPos; final int[] _touchedTile = touchedTile; final int _TILE_SIZE_LG = TILE_W_LG; final int _BLK_SIZE_LG = BLOCK_SIZE_LG; // traverse flagged blocks: final int blkW = (from >> _BLK_SIZE_LG); final int blkE = (to >> _BLK_SIZE_LG) + 1; // ensure last block flag = 0 to process final block: blkFlags[blkE] = 0; // Perform run-length encoding and store results in the piscesCache int val = 0; int cx0 = from; int runLen; final int _MAX_VALUE = Integer.MAX_VALUE; int last_t0 = _MAX_VALUE; int skip = 0; for (int t = blkW, blk_x0, blk_x1, cx, delta; t <= blkE; t++) { if (blkFlags[t] != 0) { blkFlags[t] = 0; if (last_t0 == _MAX_VALUE) { last_t0 = t; } continue; } if (last_t0 != _MAX_VALUE) { // emit blocks: blk_x0 = FloatMath.max(last_t0 << _BLK_SIZE_LG, from); last_t0 = _MAX_VALUE; // (last block pixel+1) inclusive => +1 blk_x1 = FloatMath.min((t << _BLK_SIZE_LG) + 1, to); for (cx = blk_x0; cx < blk_x1; cx++) { if ((delta = alphaRow[cx]) != 0) { alphaRow[cx] = 0; // not first rle entry: if (cx != cx0) { runLen = cx - cx0; // store alpha coverage (ensure within bounds): // as [absX|val] where: // absX is the absolute x-coordinate: // note: last pixel exclusive (>= 0) // note: it should check X is smaller than 23bits (overflow)! // check address alignment to 4 bytes: if (DO_CHECK_UNSAFE) { if ((addr_off & 3) != 0) { MarlinUtils.logInfo("Misaligned Unsafe address: " + addr_off); } } // special case to encode entries into a single int: if (val == 0) { _unsafe.putInt(addr_off, ((_bboxX0 + cx) << 8) ); } else { _unsafe.putInt(addr_off, ((_bboxX0 + cx) << 8) | (((int) _unsafe.getByte(addr_alpha + val)) & 0xFF) // [0-255] ); if (runLen == 1) { _touchedTile[cx0 >> _TILE_SIZE_LG] += val; } else { touchTile(cx0, val, cx, runLen, _touchedTile); } } addr_off += SIZE_INT; if (DO_STATS) { rdrStats.hist_tile_generator_encoding_runLen .add(runLen); } cx0 = cx; } // alpha value = running sum of coverage delta: val += delta; // ensure values are in [0; MAX_AA_ALPHA] range if (DO_AA_RANGE_CHECK) { if (val < 0) { MarlinUtils.logInfo("Invalid coverage = " + val); val = 0; } if (val > MAX_AA_ALPHA) { MarlinUtils.logInfo("Invalid coverage = " + val); val = MAX_AA_ALPHA; } } } } } else if (DO_STATS) { skip++; } } // Process remaining RLE run: runLen = to - cx0; // store alpha coverage (ensure within bounds): // as (int)[absX|val] where: // absX is the absolute x-coordinate in bits 31 to 8 and val in bits 0..7 // note: last pixel exclusive (>= 0) // note: it should check X is smaller than 23bits (overflow)! // check address alignment to 4 bytes: if (DO_CHECK_UNSAFE) { if ((addr_off & 3) != 0) { MarlinUtils.logInfo("Misaligned Unsafe address: " + addr_off); } } // special case to encode entries into a single int: if (val == 0) { _unsafe.putInt(addr_off, ((_bboxX0 + to) << 8) ); } else { _unsafe.putInt(addr_off, ((_bboxX0 + to) << 8) | (((int) _unsafe.getByte(addr_alpha + val)) & 0xFF) // [0-255] ); if (runLen == 1) { _touchedTile[cx0 >> _TILE_SIZE_LG] += val; } else { touchTile(cx0, val, to, runLen, _touchedTile); } } addr_off += SIZE_INT; if (DO_STATS) { rdrStats.hist_tile_generator_encoding_runLen.add(runLen); } long len = (addr_off - _rowAAChunk.address); // update coded length as bytes: rowAALen[row] = (len - initialPos); // update current position: rowAAChunkPos = len; if (DO_STATS) { rdrStats.stat_cache_rowAA.add(rowAALen[row]); rdrStats.hist_tile_generator_encoding_ratio.add( (100 * skip) / (blkE - blkW) ); } // update tile used marks: int tx = from >> _TILE_SIZE_LG; // inclusive if (tx < tileMin) { tileMin = tx; } tx = ((to - 1) >> _TILE_SIZE_LG) + 1; // exclusive (+1 to be sure) if (tx > tileMax) { tileMax = tx; } // Clear alpha row for reuse: alphaRow[to] = 0; if (DO_CHECKS) { IntArrayCache.check(blkFlags, blkW, blkE, 0); IntArrayCache.check(alphaRow, from, px1 + 1 - bboxX0, 0); } } long startRLERow(final int row, final int x0, final int x1) { // rows are supposed to be added by increasing y. rowAAx0[row] = x0; // first pixel inclusive rowAAx1[row] = x1; // last pixel exclusive rowAAEnc[row] = 1; // RLE encoding rowAAPos[row] = 0L; // position = 0 // update row index to current position: return (rowAAChunkIndex[row] = rowAAChunkPos); } private void expandRowAAChunk(final long needSize) { if (DO_STATS) { rdrStats.stat_array_marlincache_rowAAChunk.add(needSize); } // note: throw IOOB if neededSize > 2Gb: final long newSize = ArrayCacheConst.getNewLargeSize(rowAAChunk.length, needSize); rowAAChunk.resize(newSize); } private void touchTile(final int x0, final int val, final int x1, final int runLen, final int[] _touchedTile) { // the x and y of the current row, minus bboxX0, bboxY0 // process tile line [0 - 32] final int _TILE_SIZE_LG = TILE_W_LG; // update touchedTile int tx = (x0 >> _TILE_SIZE_LG); // handle trivial case: same tile (x0, x0+runLen) if (tx == (x1 >> _TILE_SIZE_LG)) { // same tile: _touchedTile[tx] += val * runLen; return; } final int tx1 = (x1 - 1) >> _TILE_SIZE_LG; if (tx <= tx1) { final int nextTileXCoord = (tx + 1) << _TILE_SIZE_LG; _touchedTile[tx++] += val * (nextTileXCoord - x0); } if (tx < tx1) { // don't go all the way to tx1 - we need to handle the last // tile as a special case (just like we did with the first final int tileVal = (val << _TILE_SIZE_LG); for (; tx < tx1; tx++) { _touchedTile[tx] += tileVal; } } // they will be equal unless x0 >> TILE_SIZE_LG == tx1 if (tx == tx1) { final int txXCoord = tx << _TILE_SIZE_LG; final int nextTileXCoord = (tx + 1) << _TILE_SIZE_LG; final int lastXCoord = (nextTileXCoord <= x1) ? nextTileXCoord : x1; _touchedTile[tx] += val * (lastXCoord - txXCoord); } } int alphaSumInTile(final int x) { return touchedTile[(x - bboxX0) >> TILE_W_LG]; } @Override public String toString() { return "bbox = [" + bboxX0 + ", " + bboxY0 + " => " + bboxX1 + ", " + bboxY1 + "]\n"; } private static byte[] buildAlphaMap(final int maxalpha) { // double size ! final byte[] alMap = new byte[maxalpha << 1]; final int halfmaxalpha = maxalpha >> 2; for (int i = 0; i <= maxalpha; i++) { alMap[i] = (byte) ((i * 255 + halfmaxalpha) / maxalpha); } return alMap; } }