2011-02-10 73 views
3

经过许多小时的调试和分析后,我终于设法找出竞争条件的原因。解决它是另一回事!Java AWT drawImage竞态条件 - 如何使用同步来避免它

为了看到实际的竞争状况,我在调试过程中录制了一段视频。我从那时起就进一步了解了这种情况,所以请原谅贫穷的评论和作为调试过程一部分而实施的愚蠢机制。

http://screencast.com/t/aTAk1NOVanjR

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

这里是双重的伪代码版本(完整版线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表面实现,它还实现(线507 AWT.java - 链接限制:( - 使用Surface.java链接,与高原/ AWT.java更换核心/ Surface.java)油漆/更新覆盖这也从位块传输的后备缓冲到屏幕上:

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

阻击器实现(使用的drawImage线371 AWT.java)()函数:

/** 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. 重块传送渲染过程
    缺点:性能损失,仍然得到了一点闪烁由于遇到竞争状态(有效屏幕 - >无效屏幕 - >有效屏幕)时

  2. 不要的blit油漆/更新,而不是设置刷新范围,并使用这些边界下一个渲染通道
    缺点:让黑当屏幕无效时闪烁主要应用程序线程正在追赶

这里(1)似乎是两个邪恶中较小的一个。 编辑:和(2)不起作用,获得空白屏幕......(1)工作正常,但只是掩盖了问题,可能仍然存在。

由于我对同步以及如何使用它的理解不够,我似乎无法想象它是一种锁定机制,它以某种方式说明了drawImage()的异步特性。

或者也许使用ImageObserver?

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

回答

0

更新:好办法在这里:AWT custom rendering - capture smooth resizes and eliminate resize flicker


的答案在这里是为了除去从paint()线程中的所有块传输,即永远只能从程序线程后备缓冲刷新。这与Jochen Bedersdorfer提出的答案是相反的,但他的回答对我们来说永远不会起作用,因为该程序有自己的脚本模型,它与驱动渲染的布局模型集成在一起,因此它们都必须按顺序发生。 (推测)一些问题源于Java中带有加速图形芯片组的不太好的多显示器支持,因为我在适应使用BufferStrategy时遇到了this problem,这是一种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包装的图像。

我敢肯定有一个后续的问题,我要问,当我有更多的时间来研究这个更多的目的,但现在我有两个工作的实施,随着贴在这里或多或少地址初始困境 - 我学到了很多发现他们的故事。

0

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

+0

我假设你的意思是'发生了什么 - 问题仍然存在。我只是尝试将blit代码复制到paint方法中,并且其行为相同。 (FWIW它并没有真正的区别,因为paint已经通过refreshFromBackBuffer间接地调用了blit,但为了彻底,我尝试了你的建议。) – 2011-02-10 01:50:24