/* * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package sun.java2d.metal; import sun.awt.util.ThreadGroupUtils; import sun.java2d.pipe.RenderBuffer; import sun.java2d.pipe.RenderQueue; import java.security.AccessController; import java.security.PrivilegedAction; import static sun.java2d.pipe.BufferedOpCodes.DISPOSE_CONFIG; import static sun.java2d.pipe.BufferedOpCodes.SYNC; /** * OGL-specific implementation of RenderQueue. This class provides a * single (daemon) thread that is responsible for periodically flushing * the queue, thus ensuring that only one thread communicates with the native * OpenGL libraries for the entire process. */ public class MTLRenderQueue extends RenderQueue { private static MTLRenderQueue theInstance; private final QueueFlusher flusher; private MTLRenderQueue() { /* * The thread must be a member of a thread group * which will not get GCed before VM exit. */ flusher = AccessController.doPrivileged((PrivilegedAction) QueueFlusher::new); } /** * Returns the single MTLRenderQueue instance. If it has not yet been * initialized, this method will first construct the single instance * before returning it. */ public static synchronized MTLRenderQueue getInstance() { if (theInstance == null) { theInstance = new MTLRenderQueue(); } return theInstance; } /** * Flushes the single MTLRenderQueue instance synchronously. If an * MTLRenderQueue 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 OGL pipeline, but only if the OGL pipeline is currently * enabled. Since this class has few external dependencies, callers need * not be concerned that calling this method will trigger initialization * of the OGL pipeline and related classes. */ public static void sync() { if (theInstance != null) { theInstance.lock(); try { theInstance.ensureCapacity(4); theInstance.getBuffer().putInt(SYNC); theInstance.flushNow(); } finally { theInstance.unlock(); } } } /** * Disposes the native memory associated with the given native * graphics config info pointer on the single queue flushing thread. */ public static void disposeGraphicsConfig(long pConfigInfo) { MTLRenderQueue rq = getInstance(); rq.lock(); try { // make sure we make the context associated with the given // GraphicsConfig current before disposing the native resources MTLContext.setScratchSurface(pConfigInfo); RenderBuffer buf = rq.getBuffer(); rq.ensureCapacityAndAlignment(12, 4); buf.putInt(DISPOSE_CONFIG); buf.putLong(pConfigInfo); // this call is expected to complete synchronously, so flush now rq.flushNow(); } finally { rq.unlock(); } } /** * Returns true if the current thread is the OGL QueueFlusher thread. */ public static boolean isQueueFlusherThread() { return (Thread.currentThread() == getInstance().flusher.thread); } @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) { // process the queue flushBuffer(buf.getAddress(), limit); } // reset the buffer position buf.clear(); // clear the set of references, since we no longer need them refSet.clear(); } private class QueueFlusher implements Runnable { private boolean needsFlush; private Runnable task; private Error error; private final Thread thread; public QueueFlusher() { String name = "Java2D Queue Flusher"; thread = new Thread(ThreadGroupUtils.getRootThreadGroup(), this, name, 0, false); 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(); } } } } }