JBR-8288 Vulkan: Synchronous render queue flush.

RQ doesn't expose async flush operation. All RQ flushes wait for the queue to be drained, effectively serializing queue flusher and EDT execution while still making it prone to deadlocks.
The periodic flush feature of the queue flusher thread is of no use as well, as every observable effect of RQ operation is already immediately followed by a forced flush.
As Vulkan functions have no restriction on the calling thread, keep it simple - lock the monitor and drain the queue synchronously.

(cherry picked from commit 9e0921c4269568f60156973a804fc12d0f26ab61)
This commit is contained in:
Nikita Gubarkov
2025-02-17 16:08:26 +01:00
committed by Alexey Ushakov
parent c5a3e6591e
commit 23b0860f75
3 changed files with 26 additions and 154 deletions

View File

@@ -26,89 +26,40 @@
package sun.java2d.vulkan;
import jdk.internal.misc.InnocuousThread;
import sun.java2d.pipe.RenderQueue;
import java.security.AccessController;
import java.security.PrivilegedAction;
import static sun.java2d.pipe.BufferedOpCodes.SYNC;
/**
* VK-specific implementation of RenderQueue. This class provides a
* single (daemon) thread that is responsible for periodically flushing
* the queue.
* VK-specific implementation of RenderQueue.
*/
public class VKRenderQueue extends RenderQueue {
private static VKRenderQueue theInstance;
private final QueueFlusher flusher;
@SuppressWarnings("removal")
private VKRenderQueue() {
/*
* The thread must be a member of a thread group
* which will not get GCed before VM exit.
*/
flusher = AccessController.doPrivileged((PrivilegedAction<QueueFlusher>) QueueFlusher::new);
}
private static final VKRenderQueue theInstance = new VKRenderQueue();
/**
* Returns the single VKRenderQueue instance. If it has not yet been
* initialized, this method will first construct the single instance
* before returning it.
* Returns the single VKRenderQueue instance.
*/
public static synchronized VKRenderQueue getInstance() {
if (theInstance == null) {
theInstance = new VKRenderQueue();
}
public static VKRenderQueue getInstance() {
return theInstance;
}
/**
* Flushes the single VKRenderQueue instance synchronously. If an
* VKRenderQueue has not yet been instantiated, this method is a no-op.
* This method is useful in the case of Toolkit.sync(), in which we want
* to flush the Vulkan pipeline, but only if the Vulkan pipeline is currently
* enabled.
* Flushes the single VKRenderQueue instance synchronously.
*/
public static void sync() {
if (theInstance != null) {
theInstance.lock();
try {
theInstance.ensureCapacity(4);
theInstance.getBuffer().putInt(SYNC);
theInstance.flushNow();
} finally {
theInstance.unlock();
}
theInstance.lock();
try {
theInstance.ensureCapacity(4);
theInstance.getBuffer().putInt(SYNC);
theInstance.flushNow();
} finally {
theInstance.unlock();
}
}
@Override
public void flushNow() {
// assert lock.isHeldByCurrentThread();
try {
flusher.flushNow();
} catch (Exception e) {
System.err.println("exception in flushNow:");
e.printStackTrace();
}
}
public void flushAndInvokeNow(Runnable r) {
// assert lock.isHeldByCurrentThread();
try {
flusher.flushAndInvokeNow(r);
} catch (Exception e) {
System.err.println("exception in flushAndInvokeNow:");
e.printStackTrace();
}
}
private native void flushBuffer(long buf, int limit);
private void flushBuffer() {
// assert lock.isHeldByCurrentThread();
int limit = buf.position();
if (limit > 0) {
@@ -121,97 +72,10 @@ public class VKRenderQueue extends RenderQueue {
refSet.clear();
}
private class QueueFlusher implements Runnable {
private boolean needsFlush;
private Runnable task;
private Error error;
private final Thread thread;
public QueueFlusher() {
thread = InnocuousThread.newThread("Java2D Queue Flusher", this);
thread.setDaemon(true);
thread.setPriority(Thread.MAX_PRIORITY);
thread.start();
}
public synchronized void flushNow() {
// wake up the flusher
needsFlush = true;
notify();
// wait for flush to complete
while (needsFlush) {
try {
wait();
} catch (InterruptedException e) {
}
}
// re-throw any error that may have occurred during the flush
if (error != null) {
throw error;
}
}
public synchronized void flushAndInvokeNow(Runnable task) {
this.task = task;
flushNow();
}
public synchronized void run() {
boolean timedOut = false;
while (true) {
while (!needsFlush) {
try {
timedOut = false;
/*
* Wait until we're woken up with a flushNow() call,
* or the timeout period elapses (so that we can
* flush the queue periodically).
*/
wait(100);
/*
* We will automatically flush the queue if the
* following conditions apply:
* - the wait() timed out
* - we can lock the queue (without blocking)
* - there is something in the queue to flush
* Otherwise, just continue (we'll flush eventually).
*/
if (!needsFlush && (timedOut = tryLock())) {
if (buf.position() > 0) {
needsFlush = true;
} else {
unlock();
}
}
} catch (InterruptedException e) {
}
}
try {
// reset the throwable state
error = null;
// flush the buffer now
flushBuffer();
// if there's a task, invoke that now as well
if (task != null) {
task.run();
}
} catch (Error e) {
error = e;
} catch (Exception x) {
System.err.println("exception in QueueFlusher:");
x.printStackTrace();
} finally {
if (timedOut) {
unlock();
}
task = null;
// allow the waiting thread to continue
needsFlush = false;
notify();
}
}
}
public void flushAndInvokeNow(Runnable r) {
flushNow();
r.run();
}
private native void flushBuffer(long buf, int limit);
}

View File

@@ -509,7 +509,7 @@ public class WLComponentPeer implements ComponentPeer {
SurfaceData.convertTo(WLSurfaceDataExt.class, surfaceData).commit();
}
});
Toolkit.getDefaultToolkit().sync();
((WLToolkit) Toolkit.getDefaultToolkit()).flush();
}
private boolean canPaintRoundedCorners() {

View File

@@ -37,6 +37,7 @@ import sun.awt.SunToolkit;
import sun.awt.UNIXToolkit;
import sun.awt.datatransfer.DataTransferer;
import sun.java2d.vulkan.VKInstance;
import sun.java2d.vulkan.VKRenderQueue;
import sun.util.logging.PlatformLogger;
import java.awt.*;
@@ -1050,6 +1051,13 @@ public class WLToolkit extends UNIXToolkit implements Runnable {
@Override
public void sync() {
if(VKInstance.isVulkanEnabled()) {
VKRenderQueue.sync();
}
flushImpl();
}
public void flush() {
flushImpl();
}