Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
8238954: Improve performance of tiled snapshot rendering
Reviewed-by: arapte, kcr
  • Loading branch information
fthevenet authored and kevinrushforth committed Jul 1, 2020
1 parent 869ea40 commit 32584db
Show file tree
Hide file tree
Showing 3 changed files with 331 additions and 82 deletions.
Expand Up @@ -1478,6 +1478,62 @@ private void draw(Graphics g, int x, int y, int w, int h) {

}

private void renderTile(int x, int xOffset, int y, int yOffset, int w, int h,
IntBuffer buffer, ResourceFactory rf, QuantumImage tileImg, QuantumImage targetImg) {
RTTexture rt = tileImg.getRT(w, h, rf);
if (rt == null) {
return;
}
Graphics g = rt.createGraphics();
draw(g, x + xOffset, y + yOffset, w, h);
int[] pixels = rt.getPixels();
if (pixels != null) {
buffer.put(pixels);
} else {
rt.readPixels(buffer, rt.getContentX(), rt.getContentY(), w, h);
}
//Copy tile's pixels into the target image
targetImg.image.setPixels(xOffset, yOffset, w, h,
javafx.scene.image.PixelFormat.getIntArgbPreInstance(), buffer, w);
rt.unlock();
}

private void renderWholeImage(int x, int y, int w, int h, ResourceFactory rf, QuantumImage pImage) {
RTTexture rt = pImage.getRT(w, h, rf);
if (rt == null) {
return;
}
Graphics g = rt.createGraphics();
draw(g, x, y, w, h);
int[] pixels = rt.getPixels();
if (pixels != null) {
pImage.setImage(com.sun.prism.Image.fromIntArgbPreData(pixels, w, h));
} else {
IntBuffer ib = IntBuffer.allocate(w * h);
if (rt.readPixels(ib, rt.getContentX(), rt.getContentY(), w, h)) {
pImage.setImage(com.sun.prism.Image.fromIntArgbPreData(ib, w, h));
} else {
pImage.dispose();
pImage = null;
}
}
rt.unlock();
}


private int computeTileSize(int size, int maxSize) {
// If 'size' divided by either 2 or 3 produce an exact result
// and is lesser that the specified maxSize, then use this value
// as the tile size, as this makes the tiling process more efficient.
for (int n = 1; n <= 3; n++) {
int optimumSize = size / n;
if (optimumSize <= maxSize && optimumSize * n == size) {
return optimumSize;
}
}
return maxSize;
}

@Override
public void run() {

Expand All @@ -1497,44 +1553,87 @@ public void run() {
}

boolean errored = false;
// A temp QuantumImage used only as a RTT cache for rendering tiles.
QuantumImage tileRttCache = null;
try {
QuantumImage pImage = (params.platformImage instanceof QuantumImage) ?
(QuantumImage)params.platformImage : new QuantumImage((com.sun.prism.Image)null);

com.sun.prism.RTTexture rt = pImage.getRT(w, h, rf);

if (rt == null) {
return;
}

Graphics g = rt.createGraphics();

draw(g, x, y, w, h);

int[] pixels = pImage.rt.getPixels();
(QuantumImage) params.platformImage : new QuantumImage((com.sun.prism.Image) null);

int maxTextureSize = rf.getMaximumTextureSize();
if (h > maxTextureSize || w > maxTextureSize) {
tileRttCache = new QuantumImage((com.sun.prism.Image) null);
// The requested size for the snapshot is too big to fit a single texture,
// so we need to take several snapshot tiles and merge them into pImage
if (pImage.image == null) {
pImage.setImage(com.sun.prism.Image.fromIntArgbPreData(IntBuffer.allocate(w * h), w, h));
}

if (pixels != null) {
pImage.setImage(com.sun.prism.Image.fromIntArgbPreData(pixels, w, h));
} else {
IntBuffer ib = IntBuffer.allocate(w*h);
if (pImage.rt.readPixels(ib, pImage.rt.getContentX(),
pImage.rt.getContentY(), w, h))
{
pImage.setImage(com.sun.prism.Image.fromIntArgbPreData(ib, w, h));
} else {
pImage.dispose();
pImage = null;
// M represents the middle set of tiles each with a size of tileW x tileH.
// R is the right hand column of tiles,
// B is the bottom row,
// C is the corner:
// +-----------+-----------+ . +-------+
// | | | . | |
// | M | M | . | R |
// | | | . | |
// +-----------+-----------+ . +-------+
// | | | . | |
// | M | M | . | R |
// | | | . | |
// +-----------+-----------+ . +-------+
// . . . .
// +-----------+-----------+ . +-------+
// | B | B | . | C |
// +-----------+-----------+ . +-------+
final int mTileWidth = computeTileSize(w, maxTextureSize);
final int mTileHeight = computeTileSize(h, maxTextureSize);
IntBuffer buffer = IntBuffer.allocate(mTileWidth * mTileHeight);
// Walk through all same-size "M" tiles
int mTileXOffset = 0;
int mTileYOffset = 0;
for (mTileXOffset = 0; (mTileXOffset + mTileWidth) <= w; mTileXOffset += mTileWidth) {
for (mTileYOffset = 0; (mTileYOffset + mTileHeight) <= h; mTileYOffset += mTileHeight) {
renderTile(x, mTileXOffset, y, mTileYOffset, mTileWidth, mTileHeight,
buffer, rf, tileRttCache, pImage);
}
}
// Walk through remaining same-height "R" tiles, if any
final int rTileXOffset = mTileXOffset;
final int rTileWidth = w - rTileXOffset;
if (rTileWidth > 0) {
for (int rTileYOffset = 0; (rTileYOffset + mTileHeight) <= h; rTileYOffset += mTileHeight) {
renderTile(x, rTileXOffset, y, rTileYOffset, rTileWidth, mTileHeight,
buffer, rf, tileRttCache, pImage);
}
}
// Walk through remaining same-width "B" tiles, if any
final int bTileYOffset = mTileYOffset;
final int bTileHeight = h - bTileYOffset;
if (bTileHeight > 0) {
for (int bTileXOffset = 0; (bTileXOffset + mTileWidth) <= w; bTileXOffset += mTileWidth) {
renderTile(x, bTileXOffset, y, bTileYOffset, mTileWidth, bTileHeight,
buffer, rf, tileRttCache, pImage);
}
}
// Render corner "C" tile if needed
if (rTileWidth > 0 && bTileHeight > 0) {
renderTile(x, rTileXOffset, y, bTileYOffset, rTileWidth, bTileHeight,
buffer, rf, tileRttCache, pImage);
}
}

rt.unlock();

else {
// The requested size for the snapshot fits max texture size,
// so we can directly render it in the target image.
renderWholeImage(x, y, w, h, rf, pImage);
}
params.platformImage = pImage;

} catch (Throwable t) {
errored = true;
t.printStackTrace(System.err);
} finally {
if (tileRttCache != null) {
tileRttCache.dispose();
}
Disposer.cleanUp();
rf.getTextureResourcePool().freeDisposalRequestedAndCheckResources(errored);
}
Expand Down
74 changes: 20 additions & 54 deletions modules/javafx.graphics/src/main/java/javafx/scene/Scene.java
Expand Up @@ -1285,6 +1285,9 @@ 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 width;
Expand All @@ -1300,56 +1303,11 @@ static WritableImage doSnapshot(Scene scene,
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 = x;
context.y = y;
context.width = w;
context.height = h;
context.x = xMin;
context.y = yMin;
context.width = width;
context.height = height;
context.transform = transform;
context.depthBuffer = depthBuffer;
context.root = root.getPeer();
Expand All @@ -1360,8 +1318,8 @@ private static WritableImage doSnapshotTile(Scene scene,
// temporarily adjust camera viewport to the snapshot size
cameraViewWidth = camera.getViewWidth();
cameraViewHeight = camera.getViewHeight();
camera.setViewWidth(w);
camera.setViewHeight(h);
camera.setViewWidth(width);
camera.setViewHeight(height);
NodeHelper.updatePeer(camera);
context.camera = camera.getPeer();
} else {
Expand All @@ -1378,10 +1336,10 @@ private static WritableImage doSnapshotTile(Scene scene,
}

Toolkit.WritableImageAccessor accessor = Toolkit.getWritableImageAccessor();
context.platformImage = accessor.getTkImageLoader(tileImg);
context.platformImage = accessor.getTkImageLoader(wimg);
setAllowPGAccess(false);
Object tkImage = tk.renderToImage(context);
accessor.loadTkImage(tileImg, tkImage);
accessor.loadTkImage(wimg, tkImage);

if (camera != null) {
setAllowPGAccess(true);
Expand All @@ -1390,7 +1348,15 @@ private static WritableImage doSnapshotTile(Scene scene,
NodeHelper.updatePeer(camera);
setAllowPGAccess(false);
}
return tileImg;

// 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;
}

/**
Expand Down

0 comments on commit 32584db

Please sign in to comment.