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