diff --git a/core/src/main/java/art/arcane/iris/studio/map/GpuFrames.java b/core/src/main/java/art/arcane/iris/studio/map/GpuFrames.java index acc472b47..58f608812 100644 --- a/core/src/main/java/art/arcane/iris/studio/map/GpuFrames.java +++ b/core/src/main/java/art/arcane/iris/studio/map/GpuFrames.java @@ -77,7 +77,7 @@ final class GpuFrames { if (!windows.get().drawInto(window)) { throw new IllegalStateException("GLX can't draw into the view's window"); } - return drawing.drawInto(new GpuWindowFrame(windows.get(), window, screen.seeThrough())); + return drawing.drawInto(new GpuWindowFrame(screen.seeThrough())); } catch (RuntimeException failure) { gpu.stopDrawingIntoWindows(failure); return Optional.empty(); @@ -85,6 +85,31 @@ final class GpuFrames { }); } + /** + * Puts the last frame drawn into the view's window on screen. Another view may have drawn into its own window + * since, so the view's window is made current again first. + * + * @return false when the window didn't take it, and the caller should draw a picture instead + */ + static boolean showOnScreen(ScreenTarget screen) { + return onCard(gpu -> { + Optional windows = gpu.windows(); + long window = screen.canvas().window(); + if (windows.isEmpty() || window == 0L) { + return Optional.empty(); + } + try { + if (!windows.get().drawInto(window) || !windows.get().show(window)) { + throw new IllegalStateException("The window didn't take the frame"); + } + return Optional.of(true); + } catch (RuntimeException failure) { + gpu.stopDrawingIntoWindows(failure); + return Optional.empty(); + } + }).isPresent(); + } + static void failOnError() { int error = glGetError(); if (error != GL_NO_ERROR) { @@ -119,7 +144,7 @@ final class GpuFrames { return offScreen; } - private static Optional onCard(CardWork work) { + private static Optional onCard(CardWork work) { if (failed) { return Optional.empty(); } @@ -145,7 +170,7 @@ final class GpuFrames { } @FunctionalInterface - private interface CardWork { - Optional drawOn(GpuContext gpu); + private interface CardWork { + Optional drawOn(GpuContext gpu); } } diff --git a/core/src/main/java/art/arcane/iris/studio/map/GpuWindowFrame.java b/core/src/main/java/art/arcane/iris/studio/map/GpuWindowFrame.java index 530b01c32..09172ac9d 100644 --- a/core/src/main/java/art/arcane/iris/studio/map/GpuWindowFrame.java +++ b/core/src/main/java/art/arcane/iris/studio/map/GpuWindowFrame.java @@ -28,13 +28,12 @@ import static org.lwjgl.opengl.GL30.*; /** * A frame drawn straight into a view's window. Of what was drawn, only the patches Swing paints where panels float - * over the window are read back. Only for the GPU thread. + * over the window are read back. The frame waits in the window's back buffer until {@link GpuFrames#showOnScreen}, + * so it can go on screen together with its patches. Only for the GPU thread. */ final class GpuWindowFrame implements GpuFrameTarget { private static final int WINDOW_FRAMEBUFFER = 0; - private final GlxDisplay windows; - private final long window; private final List seeThrough; private int width; private int height; @@ -42,9 +41,7 @@ final class GpuWindowFrame implements GpuFrameTarget { /** * @param seeThrough the parts of the view panels float over, which Swing paints instead of the window */ - GpuWindowFrame(GlxDisplay windows, long window, List seeThrough) { - this.windows = windows; - this.window = window; + GpuWindowFrame(List seeThrough) { this.seeThrough = seeThrough; } @@ -62,12 +59,7 @@ final class GpuWindowFrame implements GpuFrameTarget { @Override public OrbitPicture finish(OrbitProjection projection, double depthRange) { - OrbitPicture picture = OrbitPicture.onScreen(width, height, readPatches(), - GpuFrameTarget.depthInMiddle(projection, depthRange, width, height)); - if (!windows.show(window)) { - throw new IllegalStateException("The window didn't take the frame"); - } - return picture; + return OrbitPicture.onScreen(width, height, readPatches(), GpuFrameTarget.depthInMiddle(projection, depthRange, width, height)); } private List readPatches() { diff --git a/core/src/main/java/art/arcane/iris/studio/map/OrbitViewport.java b/core/src/main/java/art/arcane/iris/studio/map/OrbitViewport.java index 98a59a661..d9ac885eb 100644 --- a/core/src/main/java/art/arcane/iris/studio/map/OrbitViewport.java +++ b/core/src/main/java/art/arcane/iris/studio/map/OrbitViewport.java @@ -31,6 +31,7 @@ import java.awt.Graphics2D; import java.awt.GraphicsConfiguration; import java.awt.Point; import java.awt.RenderingHints; +import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ComponentAdapter; @@ -338,7 +339,7 @@ final class OrbitViewport extends JComponent { renderThread.execute(() -> { OrbitPicture drawn = draw(frameScene, screen, frameCamera, width, height, covered); boolean missedScreen = screen != null && !drawn.onScreen(); - EventQueue.invokeLater(() -> finishFrame(frameScene, frameCamera, drawn, missedScreen, requestedAtNanos)); + EventQueue.invokeLater(() -> finishFrame(frameScene, frameCamera, screen, drawn, missedScreen, requestedAtNanos)); }); } @@ -380,25 +381,40 @@ final class OrbitViewport extends JComponent { /** * Until the card is opened, nothing knows whether frames can go into the window; the first picture opens it. A - * frame that should have gone into the window but couldn't isn't tried again right away. The next frame is asked - * for before this one's patches are painted, so the card draws while Swing paints. + * frame that should have gone into the window but couldn't isn't tried again right away. A frame for a scene + * replaced meanwhile is never shown. */ - private void finishFrame(OrbitScene frameScene, OrbitCamera frameCamera, OrbitPicture drawn, boolean missedScreen, - long requestedAtNanos) { + private void finishFrame(OrbitScene frameScene, OrbitCamera frameCamera, ScreenTarget screen, OrbitPicture drawn, + boolean missedScreen, long requestedAtNanos) { rendering = false; measureFrameTime(System.nanoTime() - requestedAtNanos); - boolean shown = frameScene == scene; - if (shown) { + if (frameScene == scene) { takePicture(drawn, frameCamera); + paintPicture(drawn); + showInWindow(screen, drawn); } boolean canGoOnScreen = !missedScreen && canvas != null && !canvas.isVisible() && drawsOnScreen(); if (renderAgain || drawn.catchingUp() || canGoOnScreen) { renderAgain = false; requestFrame(); } - if (shown) { - paintPicture(drawn); + } + + /** + * A frame in the window only goes on screen once Swing has painted its patches, or the panels floating over the + * window would show the frame before. Swing and the card reach the screen through separate connections, so + * Swing's is flushed first. The render thread shows the frame before it draws the next one into the window. + */ + private void showInWindow(ScreenTarget screen, OrbitPicture drawn) { + if (!drawn.onScreen()) { + return; } + Toolkit.getDefaultToolkit().sync(); + renderThread.execute(() -> { + if (!GpuFrames.showOnScreen(screen)) { + EventQueue.invokeLater(this::requestFrame); + } + }); } // A whole picture replaces the window until frames go into it again.