1 /*
2  * Copyright (c) 2015, 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.
8  *
9  * This code is distributed in the hope that it will be useful, but WITHOUT
10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12  * version 2 for more details (a copy is included in the LICENSE file that
13  * accompanied this code).
14  *
15  * You should have received a copy of the GNU General Public License version
16  * 2 along with this work; if not, write to the Free Software Foundation,
17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18  *
19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20  * or visit www.oracle.com if you need additional information or have any
21  * questions.
22  */
23 
24 import java.awt.BasicStroke;
25 import java.awt.Color;
26 import java.awt.Graphics2D;
27 import java.awt.RenderingHints;
28 import java.awt.geom.Path2D;
29 import static java.awt.geom.Path2D.WIND_NON_ZERO;
30 import java.awt.image.BufferedImage;
31 import java.io.File;
32 import java.io.IOException;
33 import javax.imageio.ImageIO;
34 
35 /**
36  * @test
37  * @summary Simple crash rendering test using huge GeneralPaths with the Marlin renderer
38  * @run main/othervm -mx512m CrashTest
39  * @ignore tests that take a long time and consumes 5Gb memory
40  * @run main/othervm -ms4g -mx4g CrashTest -slow
41 */
42 public class CrashTest {
43 
44     static final boolean SAVE_IMAGE = false;
45     static boolean USE_ROUND_CAPS_AND_JOINS = true;
46 
main(String[] args)47     public static void main(String[] args) {
48         boolean runSlowTests = (args.length != 0 && "-slow".equals(args[0]));
49 
50         // First display which renderer is tested:
51         System.setProperty("sun.java2d.renderer.verbose", "true");
52 
53         // try insane image sizes:
54 
55         // subpixel coords may overflow:
56         // check MAX_VALUE / (8 * 2); overflow may happen due to orientation flag
57         // But as it is impossible to allocate an image larger than 2Gb (byte) then
58         // it is also impossible to have rowAAChunk larger than 2Gb !
59 
60         // Disabled test as it consumes 4GB heap + offheap (2Gb) ie > 6Gb !
61         if (runSlowTests) {
62             testHugeImage((Integer.MAX_VALUE >> 4) - 100, 16);
63         }
64 
65         // larger than 23 bits: (RLE)
66         testHugeImage(8388608 + 1, 10);
67 
68         if (runSlowTests) {
69             test(0.1f, false, 0);
70             test(0.1f, true, 7f);
71         }
72 
73         // Exceed 2Gb OffHeap buffer for edges:
74         try {
75             USE_ROUND_CAPS_AND_JOINS = true;
76             test(0.1f, true, 0.1f);
77             System.out.println("Exception MISSING.");
78         }
79         catch (Throwable th) {
80             if (th instanceof ArrayIndexOutOfBoundsException) {
81                 System.out.println("ArrayIndexOutOfBoundsException expected.");
82             } else {
83                 throw new RuntimeException("Unexpected exception", th);
84             }
85         }
86     }
87 
test(final float lineStroke, final boolean useDashes, final float dashMinLen)88     private static void test(final float lineStroke,
89                              final boolean useDashes,
90                              final float dashMinLen)
91         throws ArrayIndexOutOfBoundsException
92     {
93         System.out.println("---\n" + "test: "
94             + "lineStroke=" + lineStroke
95             + ", useDashes=" + useDashes
96             +", dashMinLen=" + dashMinLen
97         );
98 
99         final BasicStroke stroke = createStroke(lineStroke, useDashes, dashMinLen);
100 
101         // TODO: test Dasher.firstSegmentsBuffer resizing ?
102 // array.dasher.firstSegmentsBuffer.d_float[2] sum: 6 avg: 3.0 [3 | 3]
103         /*
104          // Marlin growable arrays:
105          = new StatLong("array.dasher.firstSegmentsBuffer.d_float");
106          = new StatLong("array.stroker.polystack.curves.d_float");
107          = new StatLong("array.stroker.polystack.curveTypes.d_byte");
108          = new StatLong("array.marlincache.rowAAChunk.d_byte");
109          = new StatLong("array.marlincache.touchedTile.int");
110          = new StatLong("array.renderer.alphaline.int");
111          = new StatLong("array.renderer.crossings.int");
112          = new StatLong("array.renderer.aux_crossings.int");
113          = new StatLong("array.renderer.edgeBuckets.int");
114          = new StatLong("array.renderer.edgeBucketCounts.int");
115          = new StatLong("array.renderer.edgePtrs.int");
116          = new StatLong("array.renderer.aux_edgePtrs.int");
117          */
118         // size > 8192 (exceed both tile and buckets arrays)
119         final int size = 9000;
120         System.out.println("image size = " + size);
121 
122         final BufferedImage image = new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB);
123 
124         final Graphics2D g2d = (Graphics2D) image.getGraphics();
125         try {
126             g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
127             g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
128 
129             g2d.setClip(0, 0, size, size);
130             g2d.setBackground(Color.WHITE);
131             g2d.clearRect(0, 0, size, size);
132 
133             g2d.setStroke(stroke);
134             g2d.setColor(Color.BLACK);
135 
136             final long start = System.nanoTime();
137 
138             paint(g2d, size - 10f);
139 
140             final long time = System.nanoTime() - start;
141 
142             System.out.println("paint: duration= " + (1e-6 * time) + " ms.");
143 
144             if (SAVE_IMAGE) {
145                 try {
146                     final File file = new File("CrashTest-dash-" + useDashes + ".bmp");
147 
148                     System.out.println("Writing file: " + file.getAbsolutePath());
149                     ImageIO.write(image, "BMP", file);
150                 } catch (IOException ex) {
151                     System.out.println("Writing file failure:");
152                     ex.printStackTrace();
153                 }
154             }
155         } finally {
156             g2d.dispose();
157         }
158     }
159 
testHugeImage(final int width, final int height)160     private static void testHugeImage(final int width, final int height)
161         throws ArrayIndexOutOfBoundsException
162     {
163         System.out.println("---\n" + "testHugeImage: "
164             + "width=" + width + ", height=" + height);
165 
166         final BasicStroke stroke = createStroke(2.5f, false, 0);
167 
168         // size > 24bits (exceed both tile and buckets arrays)
169         System.out.println("image size = " + width + " x "+height);
170 
171         final BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY);
172 
173         final Graphics2D g2d = (Graphics2D) image.getGraphics();
174         try {
175             g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
176             g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
177 
178             g2d.setBackground(Color.WHITE);
179             g2d.clearRect(0, 0, width, height);
180 
181             g2d.setStroke(stroke);
182             g2d.setColor(Color.BLACK);
183 
184             final Path2D.Float path = new Path2D.Float(WIND_NON_ZERO, 32);
185             path.moveTo(0, 0);
186             path.lineTo(width, 0);
187             path.lineTo(width, height);
188             path.lineTo(0, height);
189             path.lineTo(0, 0);
190 
191             final long start = System.nanoTime();
192 
193             g2d.draw(path);
194 
195             final long time = System.nanoTime() - start;
196 
197             System.out.println("paint: duration= " + (1e-6 * time) + " ms.");
198 
199             if (SAVE_IMAGE) {
200                 try {
201                     final File file = new File("CrashTest-huge-"
202                         + width + "x" +height + ".bmp");
203 
204                     System.out.println("Writing file: " + file.getAbsolutePath());
205                     ImageIO.write(image, "BMP", file);
206                 } catch (IOException ex) {
207                     System.out.println("Writing file failure:");
208                     ex.printStackTrace();
209                 }
210             }
211         } finally {
212             g2d.dispose();
213         }
214     }
215 
paint(final Graphics2D g2d, final float size)216     private static void paint(final Graphics2D g2d, final float size) {
217         final double halfSize = size / 2.0;
218 
219         final Path2D.Float path = new Path2D.Float(WIND_NON_ZERO, 32 * 1024);
220 
221         // show cross:
222         path.moveTo(0, 0);
223         path.lineTo(size, size);
224 
225         path.moveTo(size, 0);
226         path.lineTo(0, size);
227 
228         path.moveTo(0, 0);
229         path.lineTo(size, 0);
230 
231         path.moveTo(0, 0);
232         path.lineTo(0, size);
233 
234         path.moveTo(0, 0);
235 
236         double r = size;
237 
238         final int ratio = 100;
239         int repeats = 1;
240 
241         int n = 0;
242 
243         while (r > 1.0) {
244             repeats *= ratio;
245 
246             if (repeats > 10000) {
247                 repeats = 10000;
248             }
249 
250             for (int i = 0; i < repeats; i++) {
251                 path.lineTo(halfSize - 0.5 * r + i * r / repeats,
252                             halfSize - 0.5 * r);
253                 n++;
254                 path.lineTo(halfSize - 0.5 * r + i * r / repeats + 0.1,
255                             halfSize + 0.5 * r);
256                 n++;
257             }
258 
259             r -= halfSize;
260         }
261         System.out.println("draw : " + n + " lines.");
262         g2d.draw(path);
263     }
264 
createStroke(final float width, final boolean useDashes, final float dashMinLen)265     private static BasicStroke createStroke(final float width,
266                                             final boolean useDashes,
267                                             final float dashMinLen) {
268         final float[] dashes;
269 
270         if (useDashes) {
271             // huge dash array (exceed Dasher.INITIAL_ARRAY)
272             dashes = new float[512];
273 
274             float cur = dashMinLen;
275             float step = 0.01f;
276 
277             for (int i = 0; i < dashes.length; i += 2) {
278                 dashes[i] = cur;
279                 dashes[i + 1] = cur;
280                 cur += step;
281             }
282         } else {
283             dashes = null;
284         }
285 
286         if (USE_ROUND_CAPS_AND_JOINS) {
287             // Use both round Caps & Joins:
288             return new BasicStroke(width, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 100.0f, dashes, 0.0f);
289         }
290         return new BasicStroke(width, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 100.0f, dashes, 0.0f);
291     }
292 }
293