【问题标题】:JavaFX GrowableDataBuffer, Canvas performance hiccupJavaFX GrowableDataBuffer,画布性能打嗝
【发布时间】:2018-07-30 19:09:33
【问题描述】:

所以我有一个应用程序(一个游戏),它可以在网格中绘制许多分层的 PNG 来显示屏幕。

    for (Image anImage : image) {
        if ((x + offset + width) >= 0 && x + offset <= canvasWidth) { 
            gc.drawImage(anImage, x + offset, y, width, height);
            drawn++;
        } else {
            segmentsSkipped++;
        }
        offset += width;
    }
    //        if (drawn == 1) gc.drawImage(image[0], x + offset, y, width, height);

这进展很快,而且是一个简单的循环。我在跑

-Djavafx.animation.fullspeed=true

我无法提供 SSCE,因为适当的示例需要滚动等和精灵才能正确再现。正如您在代码中看到的,为了减少绘制操作,我正在测试每个网格段是否在画布区域内,而不是在画布区域外绘制。这给了我大约 30FPS 的使用改进,但导致了一个奇怪的问题:通常在循环的每个循环中,在 4K 监视器上,渲染器“跳过”5 部分网格的 3 部分 - (如预期的那样)。即渲染器正在将两个图像绘制到屏幕上。据我在实践中可以隔离,当跳过 4 个部分时会发生打嗝(即绘制的单个图像会填满整个屏幕)。滚动中有一个明显的打嗝。有时这很重要,而且总是很明显。在更高分辨率(超过 4k)上,随着 2 次移动到 3 个跳过的部分,小跳过很明显。

图片大小为 2800 in x。太大而无法在非 DX12 卡上一次调用。我的温度解决方案是强制另一个绘图调用,正如您在注释代码部分中看到的那样。这有助于解决问题。我的怀疑,这是一个完整的猜测,是 GrowableDataBuffer 正在迅速变化,因为图形所需区域的大小翻了一番。我在 GraphicsContext 中闲逛了一下,这“可能”似乎是问题的候选者,因为它似乎在 n^2 处增长。

我的温度。解决方案可能是可行的,因为即使在不同的分辨率下,绘制了不同数量的网格,在这种尺寸下总是需要至少 2 块,这将为大的 n^2 纹理留出空间。但我更喜欢黑客攻击较少的解决方案。我确实尝试过访问缓冲区,但它是受包保护的。

我想知道——如果这是问题所在——有没有建议 GrowableDataBuffer 不要缩小,并保持它更大的大小?或者也许有人知道是什么原因造成的。

【问题讨论】:

  • 您是否尝试连接 VM 监视器(例如 VisualVM)来尝试查找原因?它可能与堆内存和 GC 有关,在这种情况下分配更多的初始/最大堆空间可能会有所帮助。
  • 好的,谢谢你,Itai。我试过一个VisualVM。该应用程序调整得很好,但使用了大量的 RAM。我已经在这个游戏上工作了一段时间,没有足够的 RAM 集确实会导致类似的问题。但我设置了 -Xms4G 和 -Dprism.targetvram=1G
  • 所以打嗝与 GC 激活和/或已用堆空间减少不一致?
  • 很难说。但我有一些疑问,因为记忆明智,在这些问题点上没有发生任何其他重要的事情。就 VisualVM 而言,故障很难在游戏之外重现 - 并且是毫秒事件。使用第二台监视器可以很容易地跟踪尖峰:我将对其进行研究。我需要将玩家角色在世界各地运行到渲染器仅绘制一个网格图像的区域。打嗝恰好发生在这一点附近。
  • 我在渲染大量粒子时遇到了类似的症状(使用 canvas.fillRect())。性能绝对不错,但会突然在大约 120`000 个粒子时停止,甚至移动 FX 窗口也非常缓慢。

标签: java javafx javafx-8


【解决方案1】:

“hack”的另一个(但更合乎逻辑的)实现是还绘制那些稍微偏离屏幕并且可以在接下来的几帧中出现在屏幕上的背景图块:

for (Image anImage : image) {
    if ((x + offset + width) >= -RENDER_MARGIN && x + offset <= (canvasWidth + RENDER_MARGIN)) { 
        gc.drawImage(anImage, x + offset, y, width, height);
        drawn++;
    } else {
        segmentsSkipped++;
    }
    offset += width;
}

常量RENDER_MARGIN 从而定义了一个图块可以在屏幕外并且仍被绘制的像素数。实际值取决于您的滚动速度。

然而,我建议改进渲染逻辑,通过使用方法GraphicsContext.drawImage(Image img, double sx, double sy, double sw, double sh, double dx, double dy, double dw, double dh) 保持GrowableDataBuffer 的大小不变,该方法允许您定义源和目标区域,即仅绘制当前帧屏幕上图像的确切区域。

题外话:我建议将你的背景图像分成更小的块,以减少内存使用并可能提高整体渲染性能。

【讨论】:

  • 您的“但是”是一种间接解决方案,应确保 GBD @ 恒定大小。但它假定绘制 NGCanvas.DRAW_SUBIMAGE 和计算矩形交点不会增加显着的开销。我会测试并报告。但是为什么你认为将相同的图形渲染成更小的部分会有所帮助?我测试了各种块大小,小块只是简单地增加了更多的工作到 CPU 上。 FPS 较慢。鉴于在现代 GPU 上,应用程序以 30% 的使用率运行,这是一个糟糕的解决方案。另外考虑到现在透支为零,我看不到任何内存节省。
  • 我同意,计算矩形交点会增加开销,但您只需计算两次,即左右图像。
  • 我喜欢使用完整的 drawImage 方法的想法,这很好。代码很快。在FPS方面,我没有区别。保存的像素绘画对性能没有影响。无论如何,API 中都会裁剪像素重绘,所以这是有道理的。在我的 10 系列 NVidia 卡上,我得到 250FPS,看起来很流畅。重要的是 GDB 现在必须稳定,因为故障已经消失。我建议将您的答案编辑到最基本的内容。
猜你喜欢
  • 1970-01-01
  • 2021-06-24
  • 2014-05-18
  • 2012-01-17
  • 1970-01-01
  • 1970-01-01
  • 2017-12-04
  • 2015-01-28
  • 2012-10-06
相关资源
最近更新 更多