-
Bug
-
Resolution: Unresolved
-
P4
-
jfx11
-
x86_64
-
windows_10
ADDITIONAL SYSTEM INFORMATION :
Windows 10
We've seen the issue across various hardware configurations, from high-end workstations with high performance graphics down to small virtual machines.
A DESCRIPTION OF THE PROBLEM :
This looks like a Windows-specific issue when using the d3d rendering pipeline.
It boils down to passing `null` to `WritableImage.loadTkImage()` in `Scene.doSnapshot()`. The `null` that's passed comes from `QuantumToolkit.renderImage()`. This method effectively returns whatever value is at `params.platformImage`, and this is set a little further up in the method (specifically the `run` method of the anonymous `Runnable` in this method). I've extracted the relevant section here:
```
int[] pixels = pImage.rt.getPixels();
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;
}
}
rt.unlock();
params.platformImage = pImage;
```
`pImage` is set to `null` if `pImage.rt.getPixels()` returns `null` _and_ `pImage.rt.readPixels()` returns `false`. In this situation, `renderImage()` returns `null` which is then passed to `WritableImage.loadTkImage()`, which then throws the exception.
This situation can happen on Windows. `D3DRTTexture` appears to be the implementation for `pImage.rt.getPixels()`, and this will always return null.
The implementation for `readPixels()` will return false under a number of conditions in native code. It looks like `DEVICELOST`, `DEVICENOTRESET`, and `OUTOFVIDEOMEMORY` are the common cases.
We see this a surprising amount when taking snapshots of small labels, containing 1 or 2 words. These are very small snapshots we're creating, and we've seen it happen with graphics cards with 12GB of VRAM, so there may be a deeper problem with how JavaFX interacts with D3D.
Obviously there's not a lot to be done about running out of VRAM, but I do feel like there are improvements that can be made here:
1. Fail earlier, with a more useful exception that explains exactly what went wrong. This would give us more information to be able to feed back to our customers, and allow us to better guide them towards a fix/workaround.
2. Fail more gracefully in the higher levels of JavaFX. The actual exception comes as a result of an `instanceof` check that fails whith a null input. Could there be a null check just before this method call with some kind of fallback texture?
Here's a stacktrace:
```
java.lang.IllegalArgumentException: Unrecognized image loader: null
at javafx.scene.image.WritableImage.loadTkImage(WritableImage.java:240)
at javafx.scene.image.WritableImage.access$000(WritableImage.java:46)
at javafx.scene.image.WritableImage$1.loadTkImage(WritableImage.java:51)
at javafx.scene.Scene.doSnapshot(Scene.java:1236)
at javafx.scene.Node.doSnapshot(Node.java:1864)
at javafx.scene.Node.snapshot(Node.java:1942)
```
Windows 10
We've seen the issue across various hardware configurations, from high-end workstations with high performance graphics down to small virtual machines.
A DESCRIPTION OF THE PROBLEM :
This looks like a Windows-specific issue when using the d3d rendering pipeline.
It boils down to passing `null` to `WritableImage.loadTkImage()` in `Scene.doSnapshot()`. The `null` that's passed comes from `QuantumToolkit.renderImage()`. This method effectively returns whatever value is at `params.platformImage`, and this is set a little further up in the method (specifically the `run` method of the anonymous `Runnable` in this method). I've extracted the relevant section here:
```
int[] pixels = pImage.rt.getPixels();
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;
}
}
rt.unlock();
params.platformImage = pImage;
```
`pImage` is set to `null` if `pImage.rt.getPixels()` returns `null` _and_ `pImage.rt.readPixels()` returns `false`. In this situation, `renderImage()` returns `null` which is then passed to `WritableImage.loadTkImage()`, which then throws the exception.
This situation can happen on Windows. `D3DRTTexture` appears to be the implementation for `pImage.rt.getPixels()`, and this will always return null.
The implementation for `readPixels()` will return false under a number of conditions in native code. It looks like `DEVICELOST`, `DEVICENOTRESET`, and `OUTOFVIDEOMEMORY` are the common cases.
We see this a surprising amount when taking snapshots of small labels, containing 1 or 2 words. These are very small snapshots we're creating, and we've seen it happen with graphics cards with 12GB of VRAM, so there may be a deeper problem with how JavaFX interacts with D3D.
Obviously there's not a lot to be done about running out of VRAM, but I do feel like there are improvements that can be made here:
1. Fail earlier, with a more useful exception that explains exactly what went wrong. This would give us more information to be able to feed back to our customers, and allow us to better guide them towards a fix/workaround.
2. Fail more gracefully in the higher levels of JavaFX. The actual exception comes as a result of an `instanceof` check that fails whith a null input. Could there be a null check just before this method call with some kind of fallback texture?
Here's a stacktrace:
```
java.lang.IllegalArgumentException: Unrecognized image loader: null
at javafx.scene.image.WritableImage.loadTkImage(WritableImage.java:240)
at javafx.scene.image.WritableImage.access$000(WritableImage.java:46)
at javafx.scene.image.WritableImage$1.loadTkImage(WritableImage.java:51)
at javafx.scene.Scene.doSnapshot(Scene.java:1236)
at javafx.scene.Node.doSnapshot(Node.java:1864)
at javafx.scene.Node.snapshot(Node.java:1942)
```