| /* |
| * Copyright (c) 2010, 2013, 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.jules; |
| |
| import java.awt.*; |
| import java.awt.geom.*; |
| import java.util.concurrent.*; |
| import sun.java2d.pipe.*; |
| import sun.java2d.xr.*; |
| |
| public class JulesAATileGenerator implements AATileGenerator { |
| /* Threading stuff */ |
| static final ExecutorService rasterThreadPool = |
| Executors.newCachedThreadPool(); |
| static final int CPU_CNT = Runtime.getRuntime().availableProcessors(); |
| |
| static final boolean ENABLE_THREADING = false; |
| static final int THREAD_MIN = 16; |
| static final int THREAD_BEGIN = 16; |
| |
| IdleTileCache tileCache; |
| TileWorker worker; |
| boolean threaded = false; |
| int rasterTileCnt; |
| |
| /* Tiling */ |
| static final int TILE_SIZE = 32; |
| static final int TILE_SIZE_FP = 32 << 16; |
| int left, right, top, bottom, width, height; |
| int leftFP, topFP; |
| int tileCnt, tilesX, tilesY; |
| int currTilePos = 0; |
| TrapezoidList traps; |
| TileTrapContainer[] tiledTrapArray; |
| JulesTile mainTile; |
| |
| public JulesAATileGenerator(Shape s, AffineTransform at, Region clip, |
| BasicStroke bs, boolean thin, |
| boolean normalize, int[] bbox) { |
| JulesPathBuf buf = new JulesPathBuf(); |
| |
| if (bs == null) { |
| traps = buf.tesselateFill(s, at, clip); |
| } else { |
| traps = buf.tesselateStroke(s, bs, thin, false, true, at, clip); |
| } |
| |
| calculateArea(bbox); |
| bucketSortTraps(); |
| calculateTypicalAlpha(); |
| |
| threaded = ENABLE_THREADING && |
| rasterTileCnt >= THREAD_MIN && CPU_CNT >= 2; |
| if (threaded) { |
| tileCache = new IdleTileCache(); |
| worker = new TileWorker(this, THREAD_BEGIN, tileCache); |
| rasterThreadPool.execute(worker); |
| } |
| |
| mainTile = new JulesTile(); |
| } |
| |
| private static native long |
| rasterizeTrapezoidsNative(long pixmanImagePtr, int[] traps, |
| int[] trapPos, int trapCnt, |
| byte[] buffer, int xOff, int yOff); |
| |
| private static native void freePixmanImgPtr(long pixmanImgPtr); |
| |
| private void calculateArea(int[] bbox) { |
| tilesX = 0; |
| tilesY = 0; |
| tileCnt = 0; |
| bbox[0] = 0; |
| bbox[1] = 0; |
| bbox[2] = 0; |
| bbox[3] = 0; |
| |
| if (traps.getSize() > 0) { |
| left = traps.getLeft(); |
| right = traps.getRight(); |
| top = traps.getTop(); |
| bottom = traps.getBottom(); |
| leftFP = left << 16; |
| topFP = top << 16; |
| |
| bbox[0] = left; |
| bbox[1] = top; |
| bbox[2] = right; |
| bbox[3] = bottom; |
| |
| width = right - left; |
| height = bottom - top; |
| |
| if (width > 0 && height > 0) { |
| tilesX = (int) Math.ceil(((double) width) / TILE_SIZE); |
| tilesY = (int) Math.ceil(((double) height) / TILE_SIZE); |
| tileCnt = tilesY * tilesX; |
| tiledTrapArray = new TileTrapContainer[tileCnt]; |
| } else { |
| // If there is no area touched by the traps, don't |
| // render them. |
| traps.setSize(0); |
| } |
| } |
| } |
| |
| |
| private void bucketSortTraps() { |
| |
| for (int i = 0; i < traps.getSize(); i++) { |
| int top = traps.getTop(i) - XRUtils.XDoubleToFixed(this.top); |
| int bottom = traps.getBottom(i) - topFP; |
| int p1xLeft = traps.getP1XLeft(i) - leftFP; |
| int p2xLeft = traps.getP2XLeft(i) - leftFP; |
| int p1xRight = traps.getP1XRight(i) - leftFP; |
| int p2xRight = traps.getP2XRight(i) - leftFP; |
| |
| int minLeft = Math.min(p1xLeft, p2xLeft); |
| int maxRight = Math.max(p1xRight, p2xRight); |
| |
| maxRight = maxRight > 0 ? maxRight - 1 : maxRight; |
| bottom = bottom > 0 ? bottom - 1 : bottom; |
| |
| int startTileY = top / TILE_SIZE_FP; |
| int endTileY = bottom / TILE_SIZE_FP; |
| int startTileX = minLeft / TILE_SIZE_FP; |
| int endTileX = maxRight / TILE_SIZE_FP; |
| |
| for (int n = startTileY; n <= endTileY; n++) { |
| |
| for (int m = startTileX; m <= endTileX; m++) { |
| int trapArrayPos = n * tilesX + m; |
| TileTrapContainer trapTileList = tiledTrapArray[trapArrayPos]; |
| if (trapTileList == null) { |
| trapTileList = new TileTrapContainer(new GrowableIntArray(1, 16)); |
| tiledTrapArray[trapArrayPos] = trapTileList; |
| } |
| |
| trapTileList.getTraps().addInt(i); |
| } |
| } |
| } |
| } |
| |
| public void getAlpha(byte[] tileBuffer, int offset, int rowstride) { |
| JulesTile tile = null; |
| |
| if (threaded) { |
| tile = worker.getPreRasterizedTile(currTilePos); |
| } |
| |
| if (tile != null) { |
| System.arraycopy(tile.getImgBuffer(), 0, |
| tileBuffer, 0, tileBuffer.length); |
| tileCache.releaseTile(tile); |
| } else { |
| mainTile.setImgBuffer(tileBuffer); |
| rasterizeTile(currTilePos, mainTile); |
| } |
| |
| nextTile(); |
| } |
| |
| public void calculateTypicalAlpha() { |
| rasterTileCnt = 0; |
| |
| for (int index = 0; index < tileCnt; index++) { |
| |
| TileTrapContainer trapCont = tiledTrapArray[index]; |
| if (trapCont != null) { |
| GrowableIntArray trapList = trapCont.getTraps(); |
| |
| int tileAlpha = 127; |
| if (trapList == null || trapList.getSize() == 0) { |
| tileAlpha = 0; |
| } else if (doTrapsCoverTile(trapList, index)) { |
| tileAlpha = 0xff; |
| } |
| |
| if (tileAlpha == 127 || tileAlpha == 0xff) { |
| rasterTileCnt++; |
| } |
| |
| trapCont.setTileAlpha(tileAlpha); |
| } |
| } |
| } |
| |
| /* |
| * Optimization for large fills. Foutunatly cairo does generate an y-sorted |
| * list of trapezoids. This makes it quite simple to check whether a tile is |
| * fully covered by traps by: - Checking whether the tile is fully covered by |
| * traps vertically (trap 2 starts where trap 1 ended) - Checking whether all |
| * traps cover the tile horizontally This also works, when a single tile |
| * coveres the whole tile. |
| */ |
| protected boolean doTrapsCoverTile(GrowableIntArray trapList, int tileIndex) { |
| |
| // Don't bother optimizing tiles with lots of traps, usually it won't |
| // succeed anyway. |
| if (trapList.getSize() > TILE_SIZE) { |
| return false; |
| } |
| |
| int tileStartX = getXPos(tileIndex) * TILE_SIZE_FP + leftFP; |
| int tileStartY = getYPos(tileIndex) * TILE_SIZE_FP + topFP; |
| int tileEndX = tileStartX + TILE_SIZE_FP; |
| int tileEndY = tileStartY + TILE_SIZE_FP; |
| |
| // Check whether first tile covers the beginning of the tile vertically |
| int firstTop = traps.getTop(trapList.getInt(0)); |
| int firstBottom = traps.getBottom(trapList.getInt(0)); |
| if (firstTop > tileStartY || firstBottom < tileStartY) { |
| return false; |
| } |
| |
| // Initialize lastBottom with top, in order to pass the checks for the |
| // first iteration |
| int lastBottom = firstTop; |
| |
| for (int i = 0; i < trapList.getSize(); i++) { |
| int trapPos = trapList.getInt(i); |
| if (traps.getP1XLeft(trapPos) > tileStartX || |
| traps.getP2XLeft(trapPos) > tileStartX || |
| traps.getP1XRight(trapPos) < tileEndX || |
| traps.getP2XRight(trapPos) < tileEndX || |
| traps.getTop(trapPos) != lastBottom) |
| { |
| return false; |
| } |
| lastBottom = traps.getBottom(trapPos); |
| } |
| |
| // When the last trap covered the tileEnd vertically, the tile is fully |
| // covered |
| return lastBottom >= tileEndY; |
| } |
| |
| public int getTypicalAlpha() { |
| if (tiledTrapArray[currTilePos] == null) { |
| return 0; |
| } else { |
| return tiledTrapArray[currTilePos].getTileAlpha(); |
| } |
| } |
| |
| public void dispose() { |
| freePixmanImgPtr(mainTile.getPixmanImgPtr()); |
| |
| if (threaded) { |
| tileCache.disposeConsumerResources(); |
| worker.disposeConsumerResources(); |
| } |
| } |
| |
| protected JulesTile rasterizeTile(int tileIndex, JulesTile tile) { |
| int tileOffsetX = left + getXPos(tileIndex) * TILE_SIZE; |
| int tileOffsetY = top + getYPos(tileIndex) * TILE_SIZE; |
| TileTrapContainer trapCont = tiledTrapArray[tileIndex]; |
| GrowableIntArray trapList = trapCont.getTraps(); |
| |
| if (trapCont.getTileAlpha() == 127) { |
| long pixmanImgPtr = |
| rasterizeTrapezoidsNative(tile.getPixmanImgPtr(), |
| traps.getTrapArray(), |
| trapList.getArray(), |
| trapList.getSize(), |
| tile.getImgBuffer(), |
| tileOffsetX, tileOffsetY); |
| tile.setPixmanImgPtr(pixmanImgPtr); |
| } |
| |
| tile.setTilePos(tileIndex); |
| return tile; |
| } |
| |
| protected int getXPos(int arrayPos) { |
| return arrayPos % tilesX; |
| } |
| |
| protected int getYPos(int arrayPos) { |
| return arrayPos / tilesX; |
| } |
| |
| public void nextTile() { |
| currTilePos++; |
| } |
| |
| public int getTileHeight() { |
| return TILE_SIZE; |
| } |
| |
| public int getTileWidth() { |
| return TILE_SIZE; |
| } |
| |
| public int getTileCount() { |
| return tileCnt; |
| } |
| |
| public TileTrapContainer getTrapContainer(int index) { |
| return tiledTrapArray[index]; |
| } |
| } |