Path: blob/master/src/java.desktop/share/classes/sun/java2d/marlin/MarlinCache.java
41159 views
/*1* Copyright (c) 2007, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package sun.java2d.marlin;2627import jdk.internal.misc.Unsafe;2829/**30* An object used to cache pre-rendered complex paths.31*32* @see Renderer33*/34public final class MarlinCache implements MarlinConst {3536static final boolean FORCE_RLE = MarlinProperties.isForceRLE();37static final boolean FORCE_NO_RLE = MarlinProperties.isForceNoRLE();38// minimum width to try using RLE encoding:39static final int RLE_MIN_WIDTH40= Math.max(BLOCK_SIZE, MarlinProperties.getRLEMinWidth());41// maximum width for RLE encoding:42// values are stored as int [x|alpha] where alpha is 8 bits43static final int RLE_MAX_WIDTH = 1 << (24 - 1);4445// 4096 (pixels) alpha values (width) x 64 rows / 4 (tile) = 64K bytes46// x1 instead of 4 bytes (RLE) ie 1/4 capacity or average good RLE compression47static final long INITIAL_CHUNK_ARRAY = TILE_H * INITIAL_PIXEL_WIDTH >> 2; // 64K4849// The alpha map used by this object (taken out of our map cache) to convert50// pixel coverage counts gotten from MarlinCache (which are in the range51// [0, maxalpha]) into alpha values, which are in [0,256).52static final byte[] ALPHA_MAP;5354static final OffHeapArray ALPHA_MAP_UNSAFE;5556static {57final byte[] _ALPHA_MAP = buildAlphaMap(MAX_AA_ALPHA);5859ALPHA_MAP_UNSAFE = new OffHeapArray(_ALPHA_MAP, _ALPHA_MAP.length); // 1K60ALPHA_MAP =_ALPHA_MAP;6162final Unsafe _unsafe = OffHeapArray.UNSAFE;63final long addr = ALPHA_MAP_UNSAFE.address;6465for (int i = 0; i < _ALPHA_MAP.length; i++) {66_unsafe.putByte(addr + i, _ALPHA_MAP[i]);67}68}6970int bboxX0, bboxY0, bboxX1, bboxY1;7172// 1D dirty arrays73// row index in rowAAChunk[]74final long[] rowAAChunkIndex = new long[TILE_H];75// first pixel (inclusive) for each row76final int[] rowAAx0 = new int[TILE_H];77// last pixel (exclusive) for each row78final int[] rowAAx1 = new int[TILE_H];79// encoding mode (0=raw, 1=RLE encoding) for each row80final int[] rowAAEnc = new int[TILE_H];81// coded length (RLE encoding) for each row82final long[] rowAALen = new long[TILE_H];83// last position in RLE decoding for each row (getAlpha):84final long[] rowAAPos = new long[TILE_H];8586// dirty off-heap array containing pixel coverages for (32) rows (packed)87// if encoding=raw, it contains alpha coverage values (val) as integer88// if encoding=RLE, it contains tuples (val, last x-coordinate exclusive)89// use rowAAx0/rowAAx1 to get row indices within this chunk90final OffHeapArray rowAAChunk;9192// current position in rowAAChunk array93long rowAAChunkPos;9495// touchedTile[i] is the sum of all the alphas in the tile with96// x=j*TILE_SIZE+bboxX0.97int[] touchedTile;9899// per-thread renderer stats100final RendererStats rdrStats;101102// touchedTile ref (clean)103private final IntArrayCache.Reference touchedTile_ref;104105int tileMin, tileMax;106107boolean useRLE = false;108109MarlinCache(final RendererContext rdrCtx) {110this.rdrStats = rdrCtx.stats();111112rowAAChunk = rdrCtx.newOffHeapArray(INITIAL_CHUNK_ARRAY); // 64K113114touchedTile_ref = rdrCtx.newCleanIntArrayRef(INITIAL_ARRAY); // 1K = 1 tile line115touchedTile = touchedTile_ref.initial;116117// tile used marks:118tileMin = Integer.MAX_VALUE;119tileMax = Integer.MIN_VALUE;120}121122void init(int minx, int miny, int maxx, int maxy)123{124// assert maxy >= miny && maxx >= minx;125bboxX0 = minx;126bboxY0 = miny;127bboxX1 = maxx;128bboxY1 = maxy;129130final int width = (maxx - minx);131132if (FORCE_NO_RLE) {133useRLE = false;134} else if (FORCE_RLE) {135useRLE = true;136} else {137// heuristics: use both bbox area and complexity138// ie number of primitives:139140// fast check min and max width (maxx < 23bits):141useRLE = (width > RLE_MIN_WIDTH && width < RLE_MAX_WIDTH);142}143144// the ceiling of (maxy - miny + 1) / TILE_SIZE;145final int nxTiles = (width + TILE_W) >> TILE_W_LG;146147if (nxTiles > INITIAL_ARRAY) {148if (DO_STATS) {149rdrStats.stat_array_marlincache_touchedTile.add(nxTiles);150}151touchedTile = touchedTile_ref.getArray(nxTiles);152}153}154155/**156* Disposes this cache:157* clean up before reusing this instance158*/159void dispose() {160// Reset touchedTile if needed:161resetTileLine(0);162163if (DO_STATS) {164rdrStats.totalOffHeap += rowAAChunk.length;165}166167// Return arrays:168touchedTile = touchedTile_ref.putArray(touchedTile, 0, 0); // already zero filled169170// At last: resize back off-heap rowAA to initial size171if (rowAAChunk.length != INITIAL_CHUNK_ARRAY) {172// note: may throw OOME:173rowAAChunk.resize(INITIAL_CHUNK_ARRAY);174}175if (DO_CLEAN_DIRTY) {176// Force zero-fill dirty arrays:177rowAAChunk.fill(BYTE_0);178}179}180181void resetTileLine(final int pminY) {182// update bboxY0 to process a complete tile line [0 - 32]183bboxY0 = pminY;184185// reset current pos186if (DO_STATS) {187rdrStats.stat_cache_rowAAChunk.add(rowAAChunkPos);188}189rowAAChunkPos = 0L;190191// Reset touchedTile:192if (tileMin != Integer.MAX_VALUE) {193if (DO_STATS) {194rdrStats.stat_cache_tiles.add(tileMax - tileMin);195}196// clean only dirty touchedTile:197if (tileMax == 1) {198touchedTile[0] = 0;199} else {200IntArrayCache.fill(touchedTile, tileMin, tileMax, 0);201}202// reset tile used marks:203tileMin = Integer.MAX_VALUE;204tileMax = Integer.MIN_VALUE;205}206207if (DO_CLEAN_DIRTY) {208// Force zero-fill dirty arrays:209rowAAChunk.fill(BYTE_0);210}211}212213void clearAARow(final int y) {214// process tile line [0 - 32]215final int row = y - bboxY0;216217// update pixel range:218rowAAx0[row] = 0; // first pixel inclusive219rowAAx1[row] = 0; // last pixel exclusive220rowAAEnc[row] = 0; // raw encoding221222// note: leave rowAAChunkIndex[row] undefined223// and rowAALen[row] & rowAAPos[row] (RLE)224}225226/**227* Copy the given alpha data into the rowAA cache228* @param alphaRow alpha data to copy from229* @param y y pixel coordinate230* @param px0 first pixel inclusive x0231* @param px1 last pixel exclusive x1232*/233void copyAARowNoRLE(final int[] alphaRow, final int y,234final int px0, final int px1)235{236// skip useless pixels above boundary237final int px_bbox1 = FloatMath.min(px1, bboxX1);238239if (DO_LOG_BOUNDS) {240MarlinUtils.logInfo("row = [" + px0 + " ... " + px_bbox1241+ " (" + px1 + ") [ for y=" + y);242}243244final int row = y - bboxY0;245246// update pixel range:247rowAAx0[row] = px0; // first pixel inclusive248rowAAx1[row] = px_bbox1; // last pixel exclusive249rowAAEnc[row] = 0; // raw encoding250251// get current position (bytes):252final long pos = rowAAChunkPos;253// update row index to current position:254rowAAChunkIndex[row] = pos;255256// determine need array size:257// for RLE encoding, position must be aligned to 4 bytes (int):258// align - 1 = 3 so add +3 and round-off by mask ~3 = -4259final long needSize = pos + ((px_bbox1 - px0 + 3) & -4);260261// update next position (bytes):262rowAAChunkPos = needSize;263264// update row data:265final OffHeapArray _rowAAChunk = rowAAChunk;266// ensure rowAAChunk capacity:267if (_rowAAChunk.length < needSize) {268expandRowAAChunk(needSize);269}270if (DO_STATS) {271rdrStats.stat_cache_rowAA.add(px_bbox1 - px0);272}273274// rowAA contains only alpha values for range[x0; x1[275final int[] _touchedTile = touchedTile;276final int _TILE_SIZE_LG = TILE_W_LG;277278final int from = px0 - bboxX0; // first pixel inclusive279final int to = px_bbox1 - bboxX0; // last pixel exclusive280281final Unsafe _unsafe = OffHeapArray.UNSAFE;282final long SIZE_BYTE = 1L;283final long addr_alpha = ALPHA_MAP_UNSAFE.address;284long addr_off = _rowAAChunk.address + pos;285286// compute alpha sum into rowAA:287for (int x = from, val = 0; x < to; x++) {288// alphaRow is in [0; MAX_COVERAGE]289val += alphaRow[x]; // [from; to[290291// ensure values are in [0; MAX_AA_ALPHA] range292if (DO_AA_RANGE_CHECK) {293if (val < 0) {294MarlinUtils.logInfo("Invalid coverage = " + val);295val = 0;296}297if (val > MAX_AA_ALPHA) {298MarlinUtils.logInfo("Invalid coverage = " + val);299val = MAX_AA_ALPHA;300}301}302303// store alpha sum (as byte):304if (val == 0) {305_unsafe.putByte(addr_off, (byte)0); // [0-255]306} else {307_unsafe.putByte(addr_off, _unsafe.getByte(addr_alpha + val)); // [0-255]308309// update touchedTile310_touchedTile[x >> _TILE_SIZE_LG] += val;311}312addr_off += SIZE_BYTE;313}314315// update tile used marks:316int tx = from >> _TILE_SIZE_LG; // inclusive317if (tx < tileMin) {318tileMin = tx;319}320321tx = ((to - 1) >> _TILE_SIZE_LG) + 1; // exclusive (+1 to be sure)322if (tx > tileMax) {323tileMax = tx;324}325326if (DO_LOG_BOUNDS) {327MarlinUtils.logInfo("clear = [" + from + " ... " + to + "[");328}329330// Clear alpha row for reuse:331IntArrayCache.fill(alphaRow, from, px1 + 1 - bboxX0, 0);332}333334void copyAARowRLE_WithBlockFlags(final int[] blkFlags, final int[] alphaRow,335final int y, final int px0, final int px1)336{337// Copy rowAA data into the piscesCache if one is present338final int _bboxX0 = bboxX0;339340// process tile line [0 - 32]341final int row = y - bboxY0;342final int from = px0 - _bboxX0; // first pixel inclusive343344// skip useless pixels above boundary345final int px_bbox1 = FloatMath.min(px1, bboxX1);346final int to = px_bbox1 - _bboxX0; // last pixel exclusive347348if (DO_LOG_BOUNDS) {349MarlinUtils.logInfo("row = [" + px0 + " ... " + px_bbox1350+ " (" + px1 + ") [ for y=" + y);351}352353// get current position:354final long initialPos = startRLERow(row, px0, px_bbox1);355356// determine need array size:357// pessimistic: max needed size = deltaX x 4 (1 int)358final long needSize = initialPos + ((to - from) << 2);359360// update row data:361OffHeapArray _rowAAChunk = rowAAChunk;362// ensure rowAAChunk capacity:363if (_rowAAChunk.length < needSize) {364expandRowAAChunk(needSize);365}366367final Unsafe _unsafe = OffHeapArray.UNSAFE;368final long SIZE_INT = 4L;369final long addr_alpha = ALPHA_MAP_UNSAFE.address;370long addr_off = _rowAAChunk.address + initialPos;371372final int[] _touchedTile = touchedTile;373final int _TILE_SIZE_LG = TILE_W_LG;374final int _BLK_SIZE_LG = BLOCK_SIZE_LG;375376// traverse flagged blocks:377final int blkW = (from >> _BLK_SIZE_LG);378final int blkE = (to >> _BLK_SIZE_LG) + 1;379// ensure last block flag = 0 to process final block:380blkFlags[blkE] = 0;381382// Perform run-length encoding and store results in the piscesCache383int val = 0;384int cx0 = from;385int runLen;386387final int _MAX_VALUE = Integer.MAX_VALUE;388int last_t0 = _MAX_VALUE;389390int skip = 0;391392for (int t = blkW, blk_x0, blk_x1, cx, delta; t <= blkE; t++) {393if (blkFlags[t] != 0) {394blkFlags[t] = 0;395396if (last_t0 == _MAX_VALUE) {397last_t0 = t;398}399continue;400}401if (last_t0 != _MAX_VALUE) {402// emit blocks:403blk_x0 = FloatMath.max(last_t0 << _BLK_SIZE_LG, from);404last_t0 = _MAX_VALUE;405406// (last block pixel+1) inclusive => +1407blk_x1 = FloatMath.min((t << _BLK_SIZE_LG) + 1, to);408409for (cx = blk_x0; cx < blk_x1; cx++) {410if ((delta = alphaRow[cx]) != 0) {411alphaRow[cx] = 0;412413// not first rle entry:414if (cx != cx0) {415runLen = cx - cx0;416417// store alpha coverage (ensure within bounds):418// as [absX|val] where:419// absX is the absolute x-coordinate:420// note: last pixel exclusive (>= 0)421// note: it should check X is smaller than 23bits (overflow)!422423// check address alignment to 4 bytes:424if (DO_CHECK_UNSAFE) {425if ((addr_off & 3) != 0) {426MarlinUtils.logInfo("Misaligned Unsafe address: " + addr_off);427}428}429430// special case to encode entries into a single int:431if (val == 0) {432_unsafe.putInt(addr_off,433((_bboxX0 + cx) << 8)434);435} else {436_unsafe.putInt(addr_off,437((_bboxX0 + cx) << 8)438| (((int) _unsafe.getByte(addr_alpha + val)) & 0xFF) // [0-255]439);440441if (runLen == 1) {442_touchedTile[cx0 >> _TILE_SIZE_LG] += val;443} else {444touchTile(cx0, val, cx, runLen, _touchedTile);445}446}447addr_off += SIZE_INT;448449if (DO_STATS) {450rdrStats.hist_tile_generator_encoding_runLen451.add(runLen);452}453cx0 = cx;454}455456// alpha value = running sum of coverage delta:457val += delta;458459// ensure values are in [0; MAX_AA_ALPHA] range460if (DO_AA_RANGE_CHECK) {461if (val < 0) {462MarlinUtils.logInfo("Invalid coverage = " + val);463val = 0;464}465if (val > MAX_AA_ALPHA) {466MarlinUtils.logInfo("Invalid coverage = " + val);467val = MAX_AA_ALPHA;468}469}470}471}472} else if (DO_STATS) {473skip++;474}475}476477// Process remaining RLE run:478runLen = to - cx0;479480// store alpha coverage (ensure within bounds):481// as (int)[absX|val] where:482// absX is the absolute x-coordinate in bits 31 to 8 and val in bits 0..7483// note: last pixel exclusive (>= 0)484// note: it should check X is smaller than 23bits (overflow)!485486// check address alignment to 4 bytes:487if (DO_CHECK_UNSAFE) {488if ((addr_off & 3) != 0) {489MarlinUtils.logInfo("Misaligned Unsafe address: " + addr_off);490}491}492493// special case to encode entries into a single int:494if (val == 0) {495_unsafe.putInt(addr_off,496((_bboxX0 + to) << 8)497);498} else {499_unsafe.putInt(addr_off,500((_bboxX0 + to) << 8)501| (((int) _unsafe.getByte(addr_alpha + val)) & 0xFF) // [0-255]502);503504if (runLen == 1) {505_touchedTile[cx0 >> _TILE_SIZE_LG] += val;506} else {507touchTile(cx0, val, to, runLen, _touchedTile);508}509}510addr_off += SIZE_INT;511512if (DO_STATS) {513rdrStats.hist_tile_generator_encoding_runLen.add(runLen);514}515516long len = (addr_off - _rowAAChunk.address);517518// update coded length as bytes:519rowAALen[row] = (len - initialPos);520521// update current position:522rowAAChunkPos = len;523524if (DO_STATS) {525rdrStats.stat_cache_rowAA.add(rowAALen[row]);526rdrStats.hist_tile_generator_encoding_ratio.add(527(100 * skip) / (blkE - blkW)528);529}530531// update tile used marks:532int tx = from >> _TILE_SIZE_LG; // inclusive533if (tx < tileMin) {534tileMin = tx;535}536537tx = ((to - 1) >> _TILE_SIZE_LG) + 1; // exclusive (+1 to be sure)538if (tx > tileMax) {539tileMax = tx;540}541542// Clear alpha row for reuse:543alphaRow[to] = 0;544if (DO_CHECKS) {545IntArrayCache.check(blkFlags, blkW, blkE, 0);546IntArrayCache.check(alphaRow, from, px1 + 1 - bboxX0, 0);547}548}549550long startRLERow(final int row, final int x0, final int x1) {551// rows are supposed to be added by increasing y.552rowAAx0[row] = x0; // first pixel inclusive553rowAAx1[row] = x1; // last pixel exclusive554rowAAEnc[row] = 1; // RLE encoding555rowAAPos[row] = 0L; // position = 0556557// update row index to current position:558return (rowAAChunkIndex[row] = rowAAChunkPos);559}560561private void expandRowAAChunk(final long needSize) {562if (DO_STATS) {563rdrStats.stat_array_marlincache_rowAAChunk.add(needSize);564}565566// note: throw IOOB if neededSize > 2Gb:567final long newSize = ArrayCacheConst.getNewLargeSize(rowAAChunk.length,568needSize);569570rowAAChunk.resize(newSize);571}572573private void touchTile(final int x0, final int val, final int x1,574final int runLen,575final int[] _touchedTile)576{577// the x and y of the current row, minus bboxX0, bboxY0578// process tile line [0 - 32]579final int _TILE_SIZE_LG = TILE_W_LG;580581// update touchedTile582int tx = (x0 >> _TILE_SIZE_LG);583584// handle trivial case: same tile (x0, x0+runLen)585if (tx == (x1 >> _TILE_SIZE_LG)) {586// same tile:587_touchedTile[tx] += val * runLen;588return;589}590591final int tx1 = (x1 - 1) >> _TILE_SIZE_LG;592593if (tx <= tx1) {594final int nextTileXCoord = (tx + 1) << _TILE_SIZE_LG;595_touchedTile[tx++] += val * (nextTileXCoord - x0);596}597if (tx < tx1) {598// don't go all the way to tx1 - we need to handle the last599// tile as a special case (just like we did with the first600final int tileVal = (val << _TILE_SIZE_LG);601for (; tx < tx1; tx++) {602_touchedTile[tx] += tileVal;603}604}605// they will be equal unless x0 >> TILE_SIZE_LG == tx1606if (tx == tx1) {607final int txXCoord = tx << _TILE_SIZE_LG;608final int nextTileXCoord = (tx + 1) << _TILE_SIZE_LG;609610final int lastXCoord = (nextTileXCoord <= x1) ? nextTileXCoord : x1;611_touchedTile[tx] += val * (lastXCoord - txXCoord);612}613}614615int alphaSumInTile(final int x) {616return touchedTile[(x - bboxX0) >> TILE_W_LG];617}618619@Override620public String toString() {621return "bbox = ["622+ bboxX0 + ", " + bboxY0 + " => "623+ bboxX1 + ", " + bboxY1 + "]\n";624}625626private static byte[] buildAlphaMap(final int maxalpha) {627// double size !628final byte[] alMap = new byte[maxalpha << 1];629final int halfmaxalpha = maxalpha >> 2;630for (int i = 0; i <= maxalpha; i++) {631alMap[i] = (byte) ((i * 255 + halfmaxalpha) / maxalpha);632}633return alMap;634}635}636637638