3

经过数小时的调试和分析,我终于设法找出了竞态条件的原因。解决它是另一回事!

为了查看实际的竞态条件,我在调试过程中录制了一段视频。从那以后,我加深了对这种情况的理解,因此请原谅糟糕的评论和作为调试过程的一部分实施的愚蠢机制。

http://screencast.com/t/aTAk1NOVanjR

因此,情况是:我们有一个表面的双缓冲实现(即 java.awt.Frame 或 Window),其中有一个正在进行的线程基本上连续循环,调用渲染进程(执行 UI 布局并将其渲染到后台缓冲区) ) 然后,在渲染后,将渲染区域从后缓冲区传送到屏幕。

这是双缓冲渲染的伪代码版本(Surface.java的完整版本第 824 行):

public RenderedRegions render() {
    // pseudo code
    RenderedRegions r = super.render();
    if (r==null) // nothing rendered
        return
    for (region in r)
        establish max bounds
    blit(max bounds)
    return r;
}

与任何 AWT 表面实现一样,它还实现(AWT.java 中的第 507 行 -链接限制 :( - 使用 Surface.java 链接,将 core/Surface.java 替换为 plat/AWT.java)绘制/更新覆盖也 blit从后台缓冲区到屏幕:

        public void paint(Graphics gr) {
            Rectangle r = gr.getClipBounds();
            refreshFromBackbuffer(r.x - leftInset, r.y - topInset, r.width, r.height);
        }

使用 drawImage() 函数实现了 Blitting(AWT.java 中的第 371 行):

    /** synchronized as otherwise it is possible to blit before images have been rendered to the backbuffer */
    public synchronized void blit(PixelBuffer s, int sx, int sy, int dx, int dy, int dx2, int dy2) {
        discoverInsets();
        try {
            window.getGraphics().drawImage(((AWTPixelBuffer)s).i,
                              dx + leftInset, dy + topInset,     // destination topleft corner
                              dx2 + leftInset, dy2 + topInset,   // destination bottomright corner
                              sx, sy,                            // source topleft corner
                              sx + (dx2 - dx), sy + (dy2 - dy),  // source bottomright corner
                              null);
        } catch (NullPointerException npe) { /* FIXME: handle this gracefully */ }
    }

(警告:这是我开始做假设的地方!)

这里的问题似乎是 drawImage 是异步的,并且首先调用来自 refreshBackBuffer() 通过绘制/更新的 blit,但随后发生

所以... blit 已经同步了。防止竞争条件的明显方法不起作用。:(

到目前为止,我已经提出了两种解决方案,但它们都不是理想的:

  1. 在下一个渲染过程中重新 blit
    缺点:性能下降,遇到竞争条件时仍然会出现一些闪烁(有效屏幕 -> 无效屏幕 -> 有效屏幕)

  2. 不要在绘制/更新时进行 blit,而是设置刷新边界并在下一次渲染过程中使用这些
    边界

这里(1)似乎是两害相权取其轻。 编辑:并且(2)不起作用,出现空白屏幕......(1)工作正常,但只是掩盖了可能仍然存在的问题。

我所希望的,并且由于我对同步以及如何使用它的理解不足而似乎无法想象的是一种锁定机制,它以某种方式解释了 drawImage() 的异步性质。

或者也许使用 ImageObserver?

请注意,由于应用程序的性质(Vexi,对于那些感兴趣的人,网站已过时,我只能使用 2 个超链接)渲染线程必须在绘制/更新之外 - 它具有单线程脚本模型和布局过程(渲染的子过程)调用脚本。

4

2 回答 2

1

更新:这里的好方法:AWT 自定义渲染 - 捕获平滑调整大小并消除调整大小闪烁


这里的答案是从线程中删除所有paint()blitting,即只从程序线程中的后缓冲区刷新。这与 Jochen Bedersdorfer 建议的答案相反,但他的答案永远不会对我们有用,因为程序有自己的脚本模型,它与驱动渲染的布局模型集成在一起,因此这一切都必须按顺序发生。

(推测)一些问题源于 Java 中带有加速图形芯片组的不太出色的多显示器支持,因为我在适应使用 BufferStrategy 时遇到了这个问题,这是一个 direct3d+Java 差异。

本质上paint()update()减少为阻塞调用。这工作得更好,但有一个缺点 - 没有平滑的调整大小。

private class InnerFrame extends Frame() {
    public void update(Graphics g) { }
    public void paint(Graphics g) { }
    ....
}

我最终使用了缓冲区策略,尽管我对这种方法不是 100% 满意,因为在我看来,渲染到图像,然后将完整图像复制到 BufferStrategy 然后执行show()到屏幕是低效的。

我还实现了一个基于 Swing 的替代方案,但我又不是特别喜欢这样。它使用带有 ImageIcon 的 JLabel,从而程序线程(不是 EDT)绘制到由 ImageIcon 包装的 Image。

我敢肯定有一个后续问题要问我什么时候有更多的时间来更有目的性地研究这个问题,但是现在我有两个工作实现或多或少地解决了此处发布的最初问题-我学到了heluva 很多发现他们。

于 2011-07-24T23:40:00.847 回答
0

不确定,但是如果你在 AWT 绘制线程中 Blit 会发生什么?

于 2011-02-10T01:42:00.893 回答