1 /* 2 * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package sun.java2d.metal; 27 28 import sun.awt.util.ThreadGroupUtils; 29 import sun.java2d.pipe.RenderBuffer; 30 import sun.java2d.pipe.RenderQueue; 31 32 import java.security.AccessController; 33 import java.security.PrivilegedAction; 34 35 import static sun.java2d.pipe.BufferedOpCodes.DISPOSE_CONFIG; 36 import static sun.java2d.pipe.BufferedOpCodes.SYNC; 37 38 /** 39 * OGL-specific implementation of RenderQueue. This class provides a 40 * single (daemon) thread that is responsible for periodically flushing 41 * the queue, thus ensuring that only one thread communicates with the native 42 * OpenGL libraries for the entire process. 43 */ 44 public class MTLRenderQueue extends RenderQueue { 45 46 private static MTLRenderQueue theInstance; 47 private final QueueFlusher flusher; 48 49 private MTLRenderQueue() { 50 /* 51 * The thread must be a member of a thread group 52 * which will not get GCed before VM exit. 53 */ 54 flusher = AccessController.doPrivileged((PrivilegedAction<QueueFlusher>) QueueFlusher::new); 55 } 56 57 /** 58 * Returns the single MTLRenderQueue instance. If it has not yet been 59 * initialized, this method will first construct the single instance 60 * before returning it. 61 */ 62 public static synchronized MTLRenderQueue getInstance() { 63 if (theInstance == null) { 64 theInstance = new MTLRenderQueue(); 65 } 66 return theInstance; 67 } 68 69 /** 70 * Flushes the single MTLRenderQueue instance synchronously. If an 71 * MTLRenderQueue has not yet been instantiated, this method is a no-op. 72 * This method is useful in the case of Toolkit.sync(), in which we want 73 * to flush the OGL pipeline, but only if the OGL pipeline is currently 74 * enabled. Since this class has few external dependencies, callers need 75 * not be concerned that calling this method will trigger initialization 76 * of the OGL pipeline and related classes. 77 */ 78 public static void sync() { 79 if (theInstance != null) { 80 theInstance.lock(); 81 try { 82 theInstance.ensureCapacity(4); 83 theInstance.getBuffer().putInt(SYNC); 84 theInstance.flushNow(); 85 } finally { 86 theInstance.unlock(); 87 } 88 } 89 } 90 91 /** 92 * Disposes the native memory associated with the given native 93 * graphics config info pointer on the single queue flushing thread. 94 */ 95 public static void disposeGraphicsConfig(long pConfigInfo) { 96 MTLRenderQueue rq = getInstance(); 97 rq.lock(); 98 try { 99 // make sure we make the context associated with the given 100 // GraphicsConfig current before disposing the native resources 101 MTLContext.setScratchSurface(pConfigInfo); 102 103 RenderBuffer buf = rq.getBuffer(); 104 rq.ensureCapacityAndAlignment(12, 4); 105 buf.putInt(DISPOSE_CONFIG); 106 buf.putLong(pConfigInfo); 107 108 // this call is expected to complete synchronously, so flush now 109 rq.flushNow(); 110 } finally { 111 rq.unlock(); 112 } 113 } 114 115 /** 116 * Returns true if the current thread is the OGL QueueFlusher thread. 117 */ 118 public static boolean isQueueFlusherThread() { 119 return (Thread.currentThread() == getInstance().flusher.thread); 120 } 121 122 123 @Override 124 public void flushNow() { 125 // assert lock.isHeldByCurrentThread(); 126 try { 127 flusher.flushNow(); 128 } catch (Exception e) { 129 System.err.println("exception in flushNow:"); 130 e.printStackTrace(); 131 } 132 } 133 134 public void flushAndInvokeNow(Runnable r) { 135 // assert lock.isHeldByCurrentThread(); 136 try { 137 flusher.flushAndInvokeNow(r); 138 } catch (Exception e) { 139 System.err.println("exception in flushAndInvokeNow:"); 140 e.printStackTrace(); 141 } 142 } 143 144 private native void flushBuffer(long buf, int limit); 145 146 private void flushBuffer() { 147 // assert lock.isHeldByCurrentThread(); 148 int limit = buf.position(); 149 if (limit > 0) { 150 // process the queue 151 flushBuffer(buf.getAddress(), limit); 152 } 153 // reset the buffer position 154 buf.clear(); 155 // clear the set of references, since we no longer need them 156 refSet.clear(); 157 } 158 159 private class QueueFlusher implements Runnable { 160 private boolean needsFlush; 161 private Runnable task; 162 private Error error; 163 private final Thread thread; 164 165 public QueueFlusher() { 166 String name = "Java2D Queue Flusher"; 167 thread = new Thread(ThreadGroupUtils.getRootThreadGroup(), 168 this, name, 0, false); 169 thread.setDaemon(true); 170 thread.setPriority(Thread.MAX_PRIORITY); 171 thread.start(); 172 } 173 174 public synchronized void flushNow() { 175 // wake up the flusher 176 needsFlush = true; 177 notify(); 178 179 // wait for flush to complete 180 while (needsFlush) { 181 try { 182 wait(); 183 } catch (InterruptedException e) { 184 } 185 } 186 187 // re-throw any error that may have occurred during the flush 188 if (error != null) { 189 throw error; 190 } 191 } 192 193 public synchronized void flushAndInvokeNow(Runnable task) { 194 this.task = task; 195 flushNow(); 196 } 197 198 public synchronized void run() { 199 boolean timedOut = false; 200 while (true) { 201 while (!needsFlush) { 202 try { 203 timedOut = false; 204 /* 205 * Wait until we're woken up with a flushNow() call, 206 * or the timeout period elapses (so that we can 207 * flush the queue periodically). 208 */ 209 wait(100); 210 /* 211 * We will automatically flush the queue if the 212 * following conditions apply: 213 * - the wait() timed out 214 * - we can lock the queue (without blocking) 215 * - there is something in the queue to flush 216 * Otherwise, just continue (we'll flush eventually). 217 */ 218 if (!needsFlush && (timedOut = tryLock())) { 219 if (buf.position() > 0) { 220 needsFlush = true; 221 } else { 222 unlock(); 223 } 224 } 225 } catch (InterruptedException e) { 226 } 227 } 228 try { 229 // reset the throwable state 230 error = null; 231 // flush the buffer now 232 flushBuffer(); 233 // if there's a task, invoke that now as well 234 if (task != null) { 235 task.run(); 236 } 237 } catch (Error e) { 238 error = e; 239 } catch (Exception x) { 240 System.err.println("exception in QueueFlusher:"); 241 x.printStackTrace(); 242 } finally { 243 if (timedOut) { 244 unlock(); 245 } 246 task = null; 247 // allow the waiting thread to continue 248 needsFlush = false; 249 notify(); 250 } 251 } 252 } 253 } 254 }