1/*
2 * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package sun.java2d.jules;
27
28import java.awt.*;
29import java.awt.geom.*;
30import java.util.concurrent.*;
31import sun.java2d.pipe.*;
32import sun.java2d.xr.*;
33
34public class JulesAATileGenerator implements AATileGenerator {
35    /* Threading stuff */
36    static final ExecutorService rasterThreadPool =
37                                          Executors.newCachedThreadPool();
38    static final int CPU_CNT = Runtime.getRuntime().availableProcessors();
39
40    static final boolean ENABLE_THREADING = false;
41    static final int THREAD_MIN = 16;
42    static final int THREAD_BEGIN = 16;
43
44    IdleTileCache tileCache;
45    TileWorker worker;
46    boolean threaded = false;
47    int rasterTileCnt;
48
49    /* Tiling */
50    static final int TILE_SIZE = 32;
51    static final int TILE_SIZE_FP = 32 << 16;
52    int left, right, top, bottom, width, height;
53    int leftFP, topFP;
54    int tileCnt, tilesX, tilesY;
55    int currTilePos = 0;
56    TrapezoidList traps;
57    TileTrapContainer[] tiledTrapArray;
58    JulesTile mainTile;
59
60    public JulesAATileGenerator(Shape s, AffineTransform at, Region clip,
61                                BasicStroke bs, boolean thin,
62                                boolean normalize, int[] bbox) {
63        JulesPathBuf buf = new JulesPathBuf();
64
65        if (bs == null) {
66            traps = buf.tesselateFill(s, at, clip);
67        } else {
68            traps = buf.tesselateStroke(s, bs, thin, false, true, at, clip);
69        }
70
71        calculateArea(bbox);
72        bucketSortTraps();
73        calculateTypicalAlpha();
74
75        threaded = ENABLE_THREADING &&
76                   rasterTileCnt >= THREAD_MIN && CPU_CNT >= 2;
77        if (threaded) {
78            tileCache = new IdleTileCache();
79            worker = new TileWorker(this, THREAD_BEGIN, tileCache);
80            rasterThreadPool.execute(worker);
81        }
82
83        mainTile = new JulesTile();
84    }
85
86    private static native long
87        rasterizeTrapezoidsNative(long pixmanImagePtr, int[] traps,
88                                  int[] trapPos, int trapCnt,
89                                  byte[] buffer, int xOff, int yOff);
90
91    private static native void freePixmanImgPtr(long pixmanImgPtr);
92
93    private void calculateArea(int[] bbox) {
94        tilesX = 0;
95        tilesY = 0;
96        tileCnt = 0;
97        bbox[0] = 0;
98        bbox[1] = 0;
99        bbox[2] = 0;
100        bbox[3] = 0;
101
102        if (traps.getSize() > 0) {
103            left = traps.getLeft();
104            right = traps.getRight();
105            top = traps.getTop();
106            bottom = traps.getBottom();
107            leftFP = left << 16;
108            topFP = top << 16;
109
110            bbox[0] = left;
111            bbox[1] = top;
112            bbox[2] = right;
113            bbox[3] = bottom;
114
115            width = right - left;
116            height = bottom - top;
117
118            if (width > 0 && height > 0) {
119                tilesX = (int) Math.ceil(((double) width) / TILE_SIZE);
120                tilesY = (int) Math.ceil(((double) height) / TILE_SIZE);
121                tileCnt = tilesY * tilesX;
122                tiledTrapArray = new TileTrapContainer[tileCnt];
123            } else {
124                // If there is no area touched by the traps, don't
125                // render them.
126                traps.setSize(0);
127            }
128        }
129    }
130
131
132    private void bucketSortTraps() {
133
134        for (int i = 0; i < traps.getSize(); i++) {
135            int top = traps.getTop(i) - XRUtils.XDoubleToFixed(this.top);
136            int bottom = traps.getBottom(i) - topFP;
137            int p1xLeft = traps.getP1XLeft(i) - leftFP;
138            int p2xLeft = traps.getP2XLeft(i) - leftFP;
139            int p1xRight = traps.getP1XRight(i) - leftFP;
140            int p2xRight = traps.getP2XRight(i) - leftFP;
141
142            int minLeft = Math.min(p1xLeft, p2xLeft);
143            int maxRight = Math.max(p1xRight, p2xRight);
144
145            maxRight = maxRight > 0 ? maxRight - 1 : maxRight;
146            bottom = bottom > 0 ? bottom - 1 : bottom;
147
148            int startTileY = top / TILE_SIZE_FP;
149            int endTileY = bottom / TILE_SIZE_FP;
150            int startTileX = minLeft / TILE_SIZE_FP;
151            int endTileX = maxRight / TILE_SIZE_FP;
152
153            for (int n = startTileY; n <= endTileY; n++) {
154
155                for (int m = startTileX; m <= endTileX; m++) {
156                    int trapArrayPos = n * tilesX + m;
157                    TileTrapContainer trapTileList = tiledTrapArray[trapArrayPos];
158                    if (trapTileList == null) {
159                        trapTileList = new TileTrapContainer(new GrowableIntArray(1, 16));
160                        tiledTrapArray[trapArrayPos] = trapTileList;
161                    }
162
163                    trapTileList.getTraps().addInt(i);
164                }
165            }
166        }
167    }
168
169    public void getAlpha(byte[] tileBuffer, int offset, int rowstride) {
170        JulesTile tile = null;
171
172        if (threaded) {
173            tile = worker.getPreRasterizedTile(currTilePos);
174        }
175
176        if (tile != null) {
177            System.arraycopy(tile.getImgBuffer(), 0,
178                             tileBuffer, 0, tileBuffer.length);
179            tileCache.releaseTile(tile);
180        } else {
181            mainTile.setImgBuffer(tileBuffer);
182            rasterizeTile(currTilePos, mainTile);
183        }
184
185        nextTile();
186    }
187
188    public void calculateTypicalAlpha() {
189        rasterTileCnt = 0;
190
191        for (int index = 0; index < tileCnt; index++) {
192
193            TileTrapContainer trapCont = tiledTrapArray[index];
194            if (trapCont != null) {
195                GrowableIntArray trapList = trapCont.getTraps();
196
197                int tileAlpha = 127;
198                if (trapList == null || trapList.getSize() == 0) {
199                    tileAlpha = 0;
200                } else if (doTrapsCoverTile(trapList, index)) {
201                    tileAlpha = 0xff;
202                }
203
204                if (tileAlpha == 127 || tileAlpha == 0xff) {
205                    rasterTileCnt++;
206                }
207
208                trapCont.setTileAlpha(tileAlpha);
209            }
210        }
211    }
212
213    /*
214     * Optimization for large fills. Foutunatly cairo does generate an y-sorted
215     * list of trapezoids. This makes it quite simple to check whether a tile is
216     * fully covered by traps by: - Checking whether the tile is fully covered by
217     * traps vertically (trap 2 starts where trap 1 ended) - Checking whether all
218     * traps cover the tile horizontally This also works, when a single tile
219     * coveres the whole tile.
220     */
221    protected boolean doTrapsCoverTile(GrowableIntArray trapList, int tileIndex) {
222
223        // Don't bother optimizing tiles with lots of traps, usually it won't
224        // succeed anyway.
225        if (trapList.getSize() > TILE_SIZE) {
226            return false;
227        }
228
229        int tileStartX = getXPos(tileIndex) * TILE_SIZE_FP + leftFP;
230        int tileStartY = getYPos(tileIndex) * TILE_SIZE_FP + topFP;
231        int tileEndX = tileStartX + TILE_SIZE_FP;
232        int tileEndY = tileStartY + TILE_SIZE_FP;
233
234        // Check whether first tile covers the beginning of the tile vertically
235        int firstTop = traps.getTop(trapList.getInt(0));
236        int firstBottom = traps.getBottom(trapList.getInt(0));
237        if (firstTop > tileStartY || firstBottom < tileStartY) {
238            return false;
239        }
240
241        // Initialize lastBottom with top, in order to pass the checks for the
242        // first iteration
243        int lastBottom = firstTop;
244
245        for (int i = 0; i < trapList.getSize(); i++) {
246            int trapPos = trapList.getInt(i);
247            if (traps.getP1XLeft(trapPos) > tileStartX ||
248                traps.getP2XLeft(trapPos) > tileStartX ||
249                traps.getP1XRight(trapPos) < tileEndX  ||
250                traps.getP2XRight(trapPos) < tileEndX  ||
251                 traps.getTop(trapPos) != lastBottom)
252            {
253                return false;
254            }
255            lastBottom = traps.getBottom(trapPos);
256        }
257
258        // When the last trap covered the tileEnd vertically, the tile is fully
259        // covered
260        return lastBottom >= tileEndY;
261    }
262
263    public int getTypicalAlpha() {
264        if (tiledTrapArray[currTilePos] == null) {
265            return 0;
266        } else {
267            return tiledTrapArray[currTilePos].getTileAlpha();
268        }
269    }
270
271    public void dispose() {
272        freePixmanImgPtr(mainTile.getPixmanImgPtr());
273
274        if (threaded) {
275            tileCache.disposeConsumerResources();
276            worker.disposeConsumerResources();
277        }
278    }
279
280    protected JulesTile rasterizeTile(int tileIndex, JulesTile tile) {
281        int tileOffsetX = left + getXPos(tileIndex) * TILE_SIZE;
282        int tileOffsetY = top + getYPos(tileIndex) * TILE_SIZE;
283        TileTrapContainer trapCont = tiledTrapArray[tileIndex];
284        GrowableIntArray trapList = trapCont.getTraps();
285
286        if (trapCont.getTileAlpha() == 127) {
287            long pixmanImgPtr =
288                 rasterizeTrapezoidsNative(tile.getPixmanImgPtr(),
289                                           traps.getTrapArray(),
290                                           trapList.getArray(),
291                                           trapList.getSize(),
292                                           tile.getImgBuffer(),
293                                           tileOffsetX, tileOffsetY);
294            tile.setPixmanImgPtr(pixmanImgPtr);
295        }
296
297        tile.setTilePos(tileIndex);
298        return tile;
299    }
300
301    protected int getXPos(int arrayPos) {
302        return arrayPos % tilesX;
303    }
304
305    protected int getYPos(int arrayPos) {
306        return arrayPos / tilesX;
307    }
308
309    public void nextTile() {
310        currTilePos++;
311    }
312
313    public int getTileHeight() {
314        return TILE_SIZE;
315    }
316
317    public int getTileWidth() {
318        return TILE_SIZE;
319    }
320
321    public int getTileCount() {
322        return tileCnt;
323    }
324
325    public TileTrapContainer getTrapContainer(int index) {
326        return tiledTrapArray[index];
327    }
328}
329