From 1823f6ececf7bb5ee0362477c9c7524f6596f794 Mon Sep 17 00:00:00 2001 From: Frederic Thevenet Date: Fri, 31 Jan 2020 14:11:47 +0000 Subject: [PATCH] 8088198: Exception thrown from snapshot if dimensions are larger than max texture size Reviewed-by: arapte, kcr --- .../src/main/java/javafx/scene/Scene.java | 84 +++++++++++++------ .../java/test/javafx/scene/Snapshot2Test.java | 13 +-- 2 files changed, 64 insertions(+), 33 deletions(-) diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/Scene.java b/modules/javafx.graphics/src/main/java/javafx/scene/Scene.java index 5a5e6a31ba3..1ff666dccf4 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/Scene.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/Scene.java @@ -1285,27 +1285,71 @@ static WritableImage doSnapshot(Scene scene, Node root, BaseTransform transform, boolean depthBuffer, Paint fill, Camera camera, WritableImage wimg) { - Toolkit tk = Toolkit.getToolkit(); - Toolkit.ImageRenderingContext context = new Toolkit.ImageRenderingContext(); - int xMin = (int)Math.floor(x); int yMin = (int)Math.floor(y); - int xMax = (int)Math.ceil(x + w); - int yMax = (int)Math.ceil(y + h); - int width = Math.max(xMax - xMin, 1); - int height = Math.max(yMax - yMin, 1); + int width; + int height; if (wimg == null) { + int xMax = (int)Math.ceil(x + w); + int yMax = (int)Math.ceil(y + h); + width = Math.max(xMax - xMin, 1); + height = Math.max(yMax - yMin, 1); wimg = new WritableImage(width, height); } else { width = (int)wimg.getWidth(); height = (int)wimg.getHeight(); } + // Attempt to capture snapshot + try { + wimg = doSnapshotTile(scene, xMin, yMin, width, height, root, transform, depthBuffer, fill, camera, wimg); + } catch (Exception e) { + // A first attempt to capture a snapshot failed, most likely because it is larger than + // maxTextureSize: retry by taking several snapshot tiles and merge them into single image + // (Addresses JDK-8088198) + int maxTextureSize = PrismSettings.maxTextureSize; + int numVerticalTiles = (int) Math.ceil(height / (double) maxTextureSize); + int numHorizontalTiles = (int) Math.ceil(width / (double) maxTextureSize); + for (int i = 0; i < numHorizontalTiles; i++) { + int xOffset = i * maxTextureSize; + int tileWidth = Math.min(maxTextureSize, width - xOffset); + for (int j = 0; j < numVerticalTiles; j++) { + int yOffset = j * maxTextureSize; + int tileHeight = Math.min(maxTextureSize, height - yOffset); + WritableImage tile = doSnapshotTile(scene, xMin + xOffset, yMin + yOffset, tileWidth, + tileHeight, root, transform, depthBuffer, fill, camera, null); + wimg.getPixelWriter().setPixels(xOffset, yOffset, tileWidth, tileHeight, tile.getPixelReader(), 0, 0); + } + } + } + + // if this scene belongs to some stage + // we need to mark the entire scene as dirty + // because dirty logic is buggy + if (scene != null && scene.peer != null) { + scene.setNeedsRepaint(); + } + + return wimg; + } + + /** + * Capture a single snapshot tile + */ + private static WritableImage doSnapshotTile(Scene scene, + int x, int y, int w, int h, + Node root, BaseTransform transform, boolean depthBuffer, + Paint fill, Camera camera, WritableImage tileImg) { + Toolkit tk = Toolkit.getToolkit(); + Toolkit.ImageRenderingContext context = new Toolkit.ImageRenderingContext(); + if (tileImg == null) { + tileImg = new WritableImage(w, h); + } setAllowPGAccess(true); - context.x = xMin; - context.y = yMin; - context.width = width; - context.height = height; + context.x = x; + context.y = y; + context.width = w; + context.height = h; context.transform = transform; context.depthBuffer = depthBuffer; context.root = root.getPeer(); @@ -1316,8 +1360,8 @@ static WritableImage doSnapshot(Scene scene, // temporarily adjust camera viewport to the snapshot size cameraViewWidth = camera.getViewWidth(); cameraViewHeight = camera.getViewHeight(); - camera.setViewWidth(width); - camera.setViewHeight(height); + camera.setViewWidth(w); + camera.setViewHeight(h); NodeHelper.updatePeer(camera); context.camera = camera.getPeer(); } else { @@ -1334,10 +1378,10 @@ static WritableImage doSnapshot(Scene scene, } Toolkit.WritableImageAccessor accessor = Toolkit.getWritableImageAccessor(); - context.platformImage = accessor.getTkImageLoader(wimg); + context.platformImage = accessor.getTkImageLoader(tileImg); setAllowPGAccess(false); Object tkImage = tk.renderToImage(context); - accessor.loadTkImage(wimg, tkImage); + accessor.loadTkImage(tileImg, tkImage); if (camera != null) { setAllowPGAccess(true); @@ -1346,15 +1390,7 @@ static WritableImage doSnapshot(Scene scene, NodeHelper.updatePeer(camera); setAllowPGAccess(false); } - - // if this scene belongs to some stage - // we need to mark the entire scene as dirty - // because dirty logic is buggy - if (scene != null && scene.peer != null) { - scene.setNeedsRepaint(); - } - - return wimg; + return tileImg; } /** diff --git a/tests/system/src/test/java/test/javafx/scene/Snapshot2Test.java b/tests/system/src/test/java/test/javafx/scene/Snapshot2Test.java index cf7b54a5bc6..5e11645aab3 100644 --- a/tests/system/src/test/java/test/javafx/scene/Snapshot2Test.java +++ b/tests/system/src/test/java/test/javafx/scene/Snapshot2Test.java @@ -41,7 +41,6 @@ import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -373,26 +372,22 @@ public void testSnapshotScaleNodeDefer() { doTestSnapshotScaleNodeDefer(3, 3); } - // TODO: Re-enable this test when RT-22073 is fixed - @Ignore @Test + @Test public void testSnapshotBigXScaleNodeImm() { doTestSnapshotScaleNodeImm(100, 1); } - // TODO: Re-enable this test when RT-22073 is fixed - @Ignore @Test + @Test public void testSnapshotBigXScaleNodeDefer() { doTestSnapshotScaleNodeDefer(100, 1); } - // TODO: Re-enable this test when RT-22073 is fixed - @Ignore @Test + @Test public void testSnapshotBigYScaleNodeImm() { doTestSnapshotScaleNodeImm(1, 200); } - // TODO: Re-enable this test when RT-22073 is fixed - @Ignore @Test + @Test public void testSnapshotBigYScaleNodeDefer() { doTestSnapshotScaleNodeDefer(1, 200); }