<i18n dev> RFR: JDK-8303950: [macos]Translucent Windows Flicker on Repaint

Jeremy duke at openjdk.org
Tue Mar 14 07:07:16 UTC 2023


On Sun, 12 Mar 2023 17:35:40 GMT, Jeremy <duke at openjdk.org> wrote:

> I'm confident about the new test case for this ticket, but the resolution is more invasive than I'd like. (It works, though.)
> 
> In short:
> This introduces a new RenderingHint (in SunHints) to bypass the call in Window to `gg2d.fillRect(0, 0, getWidth(), getHeight());`
> 
> I left more detailed notes here about the proposed resolution:
> https://github.com/openjdk/jdk/commit/1991fdac5dbf76ddaf73cc78a9f7c38370c9674c
> 
> I'm open to suggestions if anyone has a more elegant proposal to prevent the monitor from refreshing too soon?

> I would like to clarify two question:
> 
>  1. I think most of the opaque/non-transparent components fill the background by the opaque color(including windows/frames/etc), why the problem is not reproduced in that case?

Great question. (I wasn't sure, so I had to explore this.)

The RepaintManager interprets a repaint request for any given JComponent and finds its nearest opaque ancestor to repaint.

I modified my test so the JLabel is opaque and added a debugger breakpoint in RepaintManager.paintDirtyRegions. RepaintManager enters this block of code:

                        if (dirtyComponent instanceof JComponent) {
                            ((JComponent)dirtyComponent).paintImmediately(
                                rect.x,rect.y,rect.width, rect.height);
                        }
                        else if (dirtyComponent.isShowing()) {
                            Graphics g = JComponent.safelyGetGraphics(
                                    dirtyComponent, dirtyComponent);
                            // If the Graphics goes away, it means someone disposed of
                            // the window, don't do anything.
                            if (g != null) {
                                g.setClip(rect.x, rect.y, rect.width, rect.height);
                                try {
                                    dirtyComponent.paint(g);
                                } finally {
                                    g.dispose();
                                }
                            }
                        }


So when my JLabel was opaque: we entered the first branch (where `dirtyComponent` is the JLabel). Here Swing tightly controls this process, and eventually we call `c.paintToOffscreen`, where c is the JLabel and the Graphics2D is a reference to RepaintManager's offscreen image. Once the JComponent is finished, then the PaintManager calls:

                            final Graphics2D g2d = (Graphics2D) g;
                            final Composite oldComposite = g2d.getComposite();
                            g2d.setComposite(AlphaComposite.Src);
                            g2d.drawImage(image, x, y, c);
                            g2d.setComposite(oldComposite);

This time `g` is the Window's Graphics2D that relates to the actual surface data on the monitor.

Specifically the stacktrace I'm seeing in JDK 19 when I repainted my opaque JLabel is:

paintDoubleBufferedImpl:1663, RepaintManager$PaintManager (javax.swing)
paintDoubleBuffered:1631, RepaintManager$PaintManager (javax.swing)
paint:1569, RepaintManager$PaintManager (javax.swing)
paint:1336, RepaintManager (javax.swing)
_paintImmediately:5266, JComponent (javax.swing)
paintImmediately:5076, JComponent (javax.swing)
run:878, RepaintManager$4 (javax.swing)
run:861, RepaintManager$4 (javax.swing)
executePrivileged:776, AccessController (java.security)
doPrivileged:399, AccessController (java.security)
doIntersectionPrivilege:86, ProtectionDomain$JavaSecurityAccessImpl (java.security)
paintDirtyRegions:861, RepaintManager (javax.swing)
paintDirtyRegions:834, RepaintManager (javax.swing)
prePaintDirtyRegions:784, RepaintManager (javax.swing)
run:1897, RepaintManager$ProcessingRunnable (javax.swing)
dispatch$$$capture:318, InvocationEvent (java.awt.event)
dispatch:-1, InvocationEvent (java.awt.event)


>  2. Why the double-buffer in the RepaintManager does not handle this? If no buffers are used, then probably we should have one to render everything to it and then blit to Window?

In the original unit test: the JLabel is not opaque, so the `dirtyComponent` we set out to repaint was our root JWindow. So we entered the second branch in the code above and called `dirtyComponent.paint(g)`.

Once we entered Swing rendering code: all our rendering really was double-buffered to the appropriate offscreen image. But the problem is Window.java has that one call to `fillRect` that is outside of Swing's rendering model. (So in a way this is a subtle clash of Swing vs AWT code.)

-------------

PR: https://git.openjdk.org/jdk/pull/12993


More information about the i18n-dev mailing list