Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

8238954: Improve performance of tiled snapshot rendering #112

Closed
wants to merge 20 commits into from
Closed
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
b914a48
Revert "8088198: Exception thrown from snapshot if dimensions are lar…
fthevenet Jan 31, 2020
e13b5a0
Use tiling in QuantumToolkit::renderToImage when requested image is l…
fthevenet Feb 5, 2020
9ec2d25
Added comments to computeOptimumTileSize. Also, Ensure isDivExact is …
fthevenet Mar 6, 2020
9c5b0d4
Avoid useless width and height calculation
fthevenet Mar 9, 2020
50ac8c2
Fixed code style and typo following review.
fthevenet Mar 14, 2020
7f5f289
Revert changes in import statements
fthevenet Mar 17, 2020
8c92ad4
Removed fully qualified name when declaring RTTexture
fthevenet Jun 12, 2020
15b261e
Fixed typo in comment
fthevenet Jun 12, 2020
1fb04e5
Use boolean[] instead of AtomicBoolean to effec pass-by-reference
fthevenet Jun 12, 2020
0642096
Revert changes to import statements
fthevenet Jun 12, 2020
8f0c2d2
Changed tile walking logic
fthevenet Jun 12, 2020
2951d53
Added tiled snapshots tests
fthevenet Jun 16, 2020
8274631
Removed trailing whitespaces
fthevenet Jun 17, 2020
4752d83
Changed wrong value for image size (for these tests we *don't* want i…
fthevenet Jun 17, 2020
a01aaa2
Crawling pixels in writableImage with parallel stream is a bad idea.
fthevenet Jun 19, 2020
efccc4d
Fill test image with a bilinear gradient instead of random noise.
fthevenet Jun 29, 2020
c0f7d14
Prevent attempt to render tiles with a 0 sized dimension.
fthevenet Jun 30, 2020
d808307
- Removed unused imports in Snapshot2Test
fthevenet Jul 1, 2020
7568d9c
Using for loops instead of while
fthevenet Jul 1, 2020
58cc200
Mark variables as final
fthevenet Jul 1, 2020
File filter...
Filter file types
Jump to…
Jump to file
Failed to load files.

Always

Just for now

@@ -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() {

@@ -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);
}
This conversation was marked as resolved by kevinrushforth

This comment has been minimized.

@arapte

arapte Mar 13, 2020 Member

The if (exactHeightDivFound.get()) and else block here look effectively same. Could you please take a re-look or explain what am I missing here.

This comment has been minimized.

@fthevenet

fthevenet Mar 14, 2020 Author Member

Functionally, both side of the condition actually do the exact same thing, but the side effects of which dimension is processed in the inner loop sometime have measurable consequences in terms of performances; I've added the following comment to the code to better explain this:

  // In order to minimize the number of time we have to resize the underlying
  // surface for capturing a tile, choose a dimension that has an exact divider
  // (if any) to be processed in the inner most loop.
  // E.g. looping on width then height in the example bellow requires four
  // surface resizing, whereas the opposite requires only two:
  //
  //       for (w;;)                    for (h;;)
  //           for(h;;)                     for(w;;)
  //       -----------------           -----------------
  //       |       |       |           |       |       |
  //       |   1   |   3   |           |   1   |   2   |
  //    h  |       |       |        h  |       |       |
  //       -----------------           -----------------
  //       |   2   |   4   |           |   3   |   4   |
  //       -----------------           -----------------
  //               w                           w

This comment has been minimized.

@arapte

arapte Mar 17, 2020 Member

Than for the additional comment, this will reduce the number of textures created by below call in renderTile()
com.sun.prism.RTTexture rt = tileImg.getRT(w, h, rf);

}
// 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);
}
@@ -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;
@@ -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();
@@ -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 {
@@ -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);
@@ -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;
}

/**
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.