使用大 canvas 时 JavaFX 中的 NullPointers

NullPointers in JavaFX when using a large canvas

我在我的应用程序中使用 Canvas-objects,确切地说,StackPane 中有两个相同大小的对象。当这些对象变大时,JavaFX 在尝试 paint/render:

时开始崩溃
java.lang.NullPointerException: Cannot invoke "com.sun.prism.RTTexture.createGraphics()" because "<local9>" is null
at javafx.graphics/com.sun.javafx.sg.prism.NGCanvas$RenderBuf.validate(NGCanvas.java:214)
at javafx.graphics/com.sun.javafx.sg.prism.NGCanvas.initCanvas(NGCanvas.java:644)
at javafx.graphics/com.sun.javafx.sg.prism.NGCanvas.renderContent(NGCanvas.java:607)
at javafx.graphics/com.sun.javafx.sg.prism.NGNode.doRender(NGNode.java:2072)
(...)

和类似的(都与javafx.graphics/com.sun.javafx.sg.prism.NGCanvas相关)。现在,过去曾有过一些关于此类内容的讨论(例如 here),一半的互联网基本上只是用 关闭硬件加速 来回答这个问题,这就是标志就像 -Dprism.order=sw 那样。

我认为这不是答案。

我的图形硬件支持 16384x16384 纹理(至少在 OpenGL 驱动程序中)和 8GB 显存。我什至写了一个小测试程序来验证它是否是“真正的”支持,并且有效,因为我过去曾被一些劣质的集成笔记本电脑的图形设备烧毁,这些设备声称支持它是 OpenGL-v3 兼容的,但实际上当有人试图分配 8k+ 纹理时崩溃。 我的 PC 上只有一张显卡,所以 JavaFX 应用程序不可能使用错误的设备。

当我将 canvas 对象从 4156x4156 调整为 8252x8252 时,我的应用程序崩溃了。即使我们接受它将使用下一个更高的二次幂大小来呈现这些东西,我声称我的硬件应该能够处理这个设置。即使是多个 16384x16384 纹理也可以轻松地与其他内容一起放入内存。

我在 Windows 10,使用版本 15.0.1 中的 OpenJDK 和 JavaFX,尽管 Canvas 很旧,这应该不是问题。

有人知道这里到底发生了什么吗?

编辑: 正如评论中所建议的,我做了一些更精确的测试。对两个 canvas 对象使用硬编码大小的进一步手动测试揭示了以下内容:

将两者都设置为 4096x4096 和 4097x4097 一样有效。请记住,后者应该在内部使用纹理 8192x8192,因为这是显卡的工作方式。 内部表示在显卡上,不在其他任何地方。

然而这里变得有趣了:

将两个 canvas 对象之一设置为 8192x8192(并将另一个设置为 2x2)也能正常工作。将 BOTH 设置为 8192x8192 不会,导致上述 NULL 问题。

这表明它毕竟是一个内存问题,可能与分配有关。然而,正如所提到的,考虑到将两者都设置为 4097x4097 有效,图形设备应该能够处理这个并且似乎也能够处理。 我怀疑它在事件的 Java 端,并且可能与 Stack Pane 相关。 有人知道 StackPane 是否有某种内部像素阵列 或其他机制吗?可能检测到变化?显然,这将是非常不幸的,因为它 运行 会适得其反地使用硬件加速纹理。

此外,我还使用-Dprism.order=sw做了更精确的测试。从 4156x4156 到 8252x8252 的转换在该模式下确实有效。当进一步推动(仅用于实验目的)时,最终 运行 完全相同的问题,再次表明内存问题。

我可以选择只使用一个 canvas 并在其中更频繁地重绘以减少内存占用(我猜这里的性能权衡是可以的)但我首先想了解到底是什么在这里走得太错了...

由于您已确认您创建的纹理没有超过硬件的最大尺寸,听起来您可能 运行 没有 VRAM。据我所知,默认最大值为 512MB。您可以通过在 VM args 中传递以下内容来请求不同的最大值。

-Dprism.maxvram=xx

示例:对于 2 GB,您可以使用 -Dprism.maxvram=2G.