From 060a4d32a64a775deb451abfeee74eff906672a3 Mon Sep 17 00:00:00 2001 From: bourgesl Date: Wed, 9 May 2018 22:23:49 +0200 Subject: [PATCH] merge: profile setting (quality/speed) + render queue tuning (buffer / delay) --- src/main/java/org/marlin/pisces/DDasher.java | 6 +- src/main/java/org/marlin/pisces/DHelpers.java | 20 +- .../marlin/pisces/DMarlinRenderingEngine.java | 4 + src/main/java/org/marlin/pisces/Dasher.java | 6 +- src/main/java/org/marlin/pisces/Helpers.java | 20 +- .../org/marlin/pisces/MarlinProperties.java | 119 +++++++- .../marlin/pisces/MarlinRenderingEngine.java | 4 + .../sun/java2d/opengl/OGLRenderQueue.java | 272 ++++++++++++++++++ .../java/sun/java2d/pipe/RenderQueue.java | 271 +++++++++++++++++ 9 files changed, 686 insertions(+), 36 deletions(-) create mode 100644 src/main/java/sun/java2d/opengl/OGLRenderQueue.java create mode 100644 src/main/java/sun/java2d/pipe/RenderQueue.java diff --git a/src/main/java/org/marlin/pisces/DDasher.java b/src/main/java/org/marlin/pisces/DDasher.java index b42e1f0..3ff9c0f 100644 --- a/src/main/java/org/marlin/pisces/DDasher.java +++ b/src/main/java/org/marlin/pisces/DDasher.java @@ -269,6 +269,9 @@ public void moveTo(final double x0, final double y0) { private void emitSeg(double[] buf, int off, int type) { switch (type) { + case 4: + out.lineTo(buf[off], buf[off + 1]); + return; case 8: out.curveTo(buf[off ], buf[off + 1], buf[off + 2], buf[off + 3], @@ -278,9 +281,6 @@ private void emitSeg(double[] buf, int off, int type) { out.quadTo(buf[off ], buf[off + 1], buf[off + 2], buf[off + 3]); return; - case 4: - out.lineTo(buf[off], buf[off + 1]); - return; default: } } diff --git a/src/main/java/org/marlin/pisces/DHelpers.java b/src/main/java/org/marlin/pisces/DHelpers.java index b67c200..0c1c1e3 100644 --- a/src/main/java/org/marlin/pisces/DHelpers.java +++ b/src/main/java/org/marlin/pisces/DHelpers.java @@ -765,17 +765,17 @@ void pullAll(final DPathConsumer2D io) { io.lineTo(_curves[e], _curves[e+1]); e += 2; continue; - case TYPE_QUADTO: - io.quadTo(_curves[e], _curves[e+1], - _curves[e+2], _curves[e+3]); - e += 4; - continue; case TYPE_CUBICTO: io.curveTo(_curves[e], _curves[e+1], _curves[e+2], _curves[e+3], _curves[e+4], _curves[e+5]); e += 6; continue; + case TYPE_QUADTO: + io.quadTo(_curves[e], _curves[e+1], + _curves[e+2], _curves[e+3]); + e += 4; + continue; default: } } @@ -807,17 +807,17 @@ void popAll(final DPathConsumer2D io) { e -= 2; io.lineTo(_curves[e], _curves[e+1]); continue; - case TYPE_QUADTO: - e -= 4; - io.quadTo(_curves[e], _curves[e+1], - _curves[e+2], _curves[e+3]); - continue; case TYPE_CUBICTO: e -= 6; io.curveTo(_curves[e], _curves[e+1], _curves[e+2], _curves[e+3], _curves[e+4], _curves[e+5]); continue; + case TYPE_QUADTO: + e -= 4; + io.quadTo(_curves[e], _curves[e+1], + _curves[e+2], _curves[e+3]); + continue; default: } } diff --git a/src/main/java/org/marlin/pisces/DMarlinRenderingEngine.java b/src/main/java/org/marlin/pisces/DMarlinRenderingEngine.java index 386fb25..11f0c86 100644 --- a/src/main/java/org/marlin/pisces/DMarlinRenderingEngine.java +++ b/src/main/java/org/marlin/pisces/DMarlinRenderingEngine.java @@ -1094,6 +1094,10 @@ private static void logSettings(final String reClass) { logInfo("sun.java2d.renderer.pixelHeight = " + MarlinConst.INITIAL_PIXEL_HEIGHT); + logInfo("sun.java2d.renderer.profile = " + + (MarlinProperties.isProfileQuality() ? + "quality" : "speed")); + logInfo("sun.java2d.renderer.subPixel_log2_X = " + MarlinConst.SUBPIXEL_LG_POSITIONS_X); logInfo("sun.java2d.renderer.subPixel_log2_Y = " diff --git a/src/main/java/org/marlin/pisces/Dasher.java b/src/main/java/org/marlin/pisces/Dasher.java index 2629b06..004f40d 100644 --- a/src/main/java/org/marlin/pisces/Dasher.java +++ b/src/main/java/org/marlin/pisces/Dasher.java @@ -270,6 +270,9 @@ public void moveTo(final float x0, final float y0) { private void emitSeg(float[] buf, int off, int type) { switch (type) { + case 4: + out.lineTo(buf[off], buf[off + 1]); + return; case 8: out.curveTo(buf[off ], buf[off + 1], buf[off + 2], buf[off + 3], @@ -279,9 +282,6 @@ private void emitSeg(float[] buf, int off, int type) { out.quadTo(buf[off ], buf[off + 1], buf[off + 2], buf[off + 3]); return; - case 4: - out.lineTo(buf[off], buf[off + 1]); - return; default: } } diff --git a/src/main/java/org/marlin/pisces/Helpers.java b/src/main/java/org/marlin/pisces/Helpers.java index 1674ace..6d0d38e 100644 --- a/src/main/java/org/marlin/pisces/Helpers.java +++ b/src/main/java/org/marlin/pisces/Helpers.java @@ -773,17 +773,17 @@ void pullAll(final PathConsumer2D io) { io.lineTo(_curves[e], _curves[e+1]); e += 2; continue; - case TYPE_QUADTO: - io.quadTo(_curves[e], _curves[e+1], - _curves[e+2], _curves[e+3]); - e += 4; - continue; case TYPE_CUBICTO: io.curveTo(_curves[e], _curves[e+1], _curves[e+2], _curves[e+3], _curves[e+4], _curves[e+5]); e += 6; continue; + case TYPE_QUADTO: + io.quadTo(_curves[e], _curves[e+1], + _curves[e+2], _curves[e+3]); + e += 4; + continue; default: } } @@ -815,17 +815,17 @@ void popAll(final PathConsumer2D io) { e -= 2; io.lineTo(_curves[e], _curves[e+1]); continue; - case TYPE_QUADTO: - e -= 4; - io.quadTo(_curves[e], _curves[e+1], - _curves[e+2], _curves[e+3]); - continue; case TYPE_CUBICTO: e -= 6; io.curveTo(_curves[e], _curves[e+1], _curves[e+2], _curves[e+3], _curves[e+4], _curves[e+5]); continue; + case TYPE_QUADTO: + e -= 4; + io.quadTo(_curves[e], _curves[e+1], + _curves[e+2], _curves[e+3]); + continue; default: } } diff --git a/src/main/java/org/marlin/pisces/MarlinProperties.java b/src/main/java/org/marlin/pisces/MarlinProperties.java index a07c781..59fbedd 100644 --- a/src/main/java/org/marlin/pisces/MarlinProperties.java +++ b/src/main/java/org/marlin/pisces/MarlinProperties.java @@ -26,15 +26,82 @@ package org.marlin.pisces; import java.security.AccessController; +import java.security.PrivilegedAction; import static org.marlin.pisces.MarlinUtils.logInfo; import sun.security.action.GetPropertyAction; public final class MarlinProperties { + private static Boolean SUPPORT_LARGE_TILES = null; + private MarlinProperties() { // no-op } + // Specific AWT / Java2D Graphics Environment checks + + private static boolean supportsLargeTiles() { + if (SUPPORT_LARGE_TILES != null) { + return SUPPORT_LARGE_TILES.booleanValue(); + } + boolean useLargeTiles = false; + try { + // Due to bootstrapping issue with GraphicsEnvironment static initializers, + // Try detecting the awt / java2d pipeline in action using only + // system & env properties: + if (isHeadless()) { + useLargeTiles = true; + } else { + final String osName = getString("os.name", ""); + // Ignore Windows / Mac, only Linux: + if ("Linux".equals(osName)) { + final boolean useGL = getBoolean("sun.java2d.opengl", "false"); + final boolean useXR = getBoolean("sun.java2d.xrender", "true"); + if (!useGL && useXR) { + // !OpenGL AND XRender on Linux: + useLargeTiles = true; + } + } + } + } finally { + SUPPORT_LARGE_TILES = Boolean.valueOf(useLargeTiles); + } + return useLargeTiles; + } + + private static boolean isHeadless() { + // Mimics java.awt.GraphicsEnvironment.getHeadlessProperty(): + return AccessController.doPrivileged( + new PrivilegedAction() { + @Override + public Boolean run() { + String nm = System.getProperty("java.awt.headless"); + + if (nm == null) { + String osName = System.getProperty("os.name"); + if (osName.contains("OS X") && "sun.awt.HToolkit".equals( + System.getProperty("awt.toolkit"))) + { + return Boolean.TRUE; + } else { + final String display = System.getenv("DISPLAY"); + return Boolean.valueOf( + ("Linux".equals(osName) || + "SunOS".equals(osName) || + "FreeBSD".equals(osName) || + "NetBSD".equals(osName) || + "OpenBSD".equals(osName) || + "AIX".equals(osName)) && + (display == null || display.trim().isEmpty())); + } + } else { + return Boolean.valueOf(nm); + } + } + } + ); + } + // marlin system properties public static boolean isUseThreadLocal() { @@ -77,6 +144,25 @@ public static int getInitialPixelHeight() { 64); } + /** + * Return true if the profile is 'quality' (default) over 'speed' + * + * @return true if the profile is 'quality' (default), false otherwise + */ + public static boolean isProfileQuality() { + final String key = "sun.java2d.renderer.profile"; + final String profile = getString(key, "quality"); + if ("quality".equals(profile)) { + return true; + } + if ("speed".equals(profile)) { + return false; + } + logInfo("Invalid value for " + key + " = " + profile + + "; expect value in [quality, speed] !"); + return true; + } + /** * Return the log(2) corresponding to subpixel on x-axis * @@ -91,30 +177,36 @@ public static int getSubPixel_Log2_X() { * Return the log(2) corresponding to subpixel on y-axis * * @return 0 (1 subpixels) < initial pixel size < 8 (256 subpixels) - * (3 by default ie 8 subpixels) + * (3 by default ie 8 subpixels for the quality profile) + * (2 by default ie 4 subpixels for the speed profile) */ public static int getSubPixel_Log2_Y() { - return getInteger("sun.java2d.renderer.subPixel_log2_Y", 3, 0, 8); + final int def = isProfileQuality() ? 3 : 2; + return getInteger("sun.java2d.renderer.subPixel_log2_Y", def, 0, 8); } /** * Return the log(2) corresponding to the square tile size in pixels * * @return 3 (8x8 pixels) < tile size < 10 (1024x1024 pixels) - * (5 by default ie 32x32 pixels) + * (6 by default ie 128x64 pixels if large tile supported) + * (5 by default ie 32x32 pixels otherwise) */ public static int getTileSize_Log2() { - return getInteger("sun.java2d.renderer.tileSize_log2", 5, 3, 10); + final int def = supportsLargeTiles() ? 6 : 5; + return getInteger("sun.java2d.renderer.tileSize_log2", def, 3, 10); } /** * Return the log(2) corresponding to the tile width in pixels * * @return 3 (8 pixels) < tile width < 10 (1024 pixels) - * (5 by default ie 32x32 pixels) + * (7 by default ie 128x64 pixels if large tile supported) + * (5 by default ie 32x32 pixels otherwise) */ public static int getTileWidth_Log2() { - return getInteger("sun.java2d.renderer.tileWidth_log2", 5, 3, 10); + final int def = supportsLargeTiles() ? 7 : 5; + return getInteger("sun.java2d.renderer.tileWidth_log2", def, 3, 10); } /** @@ -221,24 +313,31 @@ public static boolean isLogUnsafeMalloc() { } // quality settings - public static float getCurveLengthError() { return getFloat("sun.java2d.renderer.curve_len_err", 0.01f, 1e-6f, 1.0f); } public static float getCubicDecD2() { - return getFloat("sun.java2d.renderer.cubic_dec_d2", 1.0f, 1e-5f, 4.0f); + final float def = isProfileQuality() ? 1.0f : 2.5f; + return getFloat("sun.java2d.renderer.cubic_dec_d2", def, 1e-5f, 4.0f); } public static float getCubicIncD1() { - return getFloat("sun.java2d.renderer.cubic_inc_d1", 0.2f, 1e-6f, 1.0f); + final float def = isProfileQuality() ? 0.2f : 0.5f; + return getFloat("sun.java2d.renderer.cubic_inc_d1", def, 1e-6f, 1.0f); } public static float getQuadDecD2() { - return getFloat("sun.java2d.renderer.quad_dec_d2", 0.5f, 1e-5f, 4.0f); + final float def = isProfileQuality() ? 0.5f : 1.0f; + return getFloat("sun.java2d.renderer.quad_dec_d2", def, 1e-5f, 4.0f); } // system property utilities + static String getString(final String key, final String def) { + return AccessController.doPrivileged( + new GetPropertyAction(key, def)); + } + static boolean getBoolean(final String key, final String def) { return Boolean.valueOf(AccessController.doPrivileged( new GetPropertyAction(key, def))); diff --git a/src/main/java/org/marlin/pisces/MarlinRenderingEngine.java b/src/main/java/org/marlin/pisces/MarlinRenderingEngine.java index 890ab5b..858ee4a 100644 --- a/src/main/java/org/marlin/pisces/MarlinRenderingEngine.java +++ b/src/main/java/org/marlin/pisces/MarlinRenderingEngine.java @@ -1091,6 +1091,10 @@ private static void logSettings(final String reClass) { logInfo("sun.java2d.renderer.pixelHeight = " + MarlinConst.INITIAL_PIXEL_HEIGHT); + logInfo("sun.java2d.renderer.profile = " + + (MarlinProperties.isProfileQuality() ? + "quality" : "speed")); + logInfo("sun.java2d.renderer.subPixel_log2_X = " + MarlinConst.SUBPIXEL_LG_POSITIONS_X); logInfo("sun.java2d.renderer.subPixel_log2_Y = " diff --git a/src/main/java/sun/java2d/opengl/OGLRenderQueue.java b/src/main/java/sun/java2d/opengl/OGLRenderQueue.java new file mode 100644 index 0000000..3cfdda8 --- /dev/null +++ b/src/main/java/sun/java2d/opengl/OGLRenderQueue.java @@ -0,0 +1,272 @@ +/* + * Copyright (c) 2005, 2018, 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.opengl; + +//import sun.awt.util.ThreadGroupUtils; +import sun.java2d.pipe.RenderBuffer; +import sun.java2d.pipe.RenderQueue; + +import static sun.java2d.pipe.BufferedOpCodes.*; +import java.security.AccessController; +import java.security.PrivilegedAction; +import org.marlin.pisces.MarlinUtils; + +/** + * 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 final class OGLRenderQueue extends RenderQueue { + final static long FLUSH_DELAY = getInteger("sun.java2d.opengl.flushDelay", 100, 1, 1000); // 100 in jdk + + static { + System.out.println("RenderQueue: sun.java2d.opengl.flushDelay = " + FLUSH_DELAY); + } + + private static OGLRenderQueue theInstance; + final QueueFlusher flusher; + + private OGLRenderQueue() { + super(); + /* + * The thread must be a member of a thread group + * which will not get GCed before VM exit. + */ + flusher = AccessController.doPrivileged(new PrivilegedAction() { + @Override + public QueueFlusher run() { + return new QueueFlusher( + // ThreadGroupUtils.getRootThreadGroup() + MarlinUtils.getRootThreadGroup() + ); + } + }); + } + + /** + * Returns the single OGLRenderQueue instance. If it has not yet been + * initialized, this method will first construct the single instance + * before returning it. + */ + public static synchronized OGLRenderQueue getInstance() { + if (theInstance == null) { + theInstance = new OGLRenderQueue(); + theInstance.flusher.start(); + } + return theInstance; + } + + /** + * Flushes the single OGLRenderQueue instance synchronously. If an + * OGLRenderQueue 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) { + OGLRenderQueue rq = getInstance(); + rq.lock(); + try { + // make sure we make the context associated with the given + // GraphicsConfig current before disposing the native resources + OGLContext.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); + } + + @Override + public void flushNow() { + // assert lock.isHeldByCurrentThread(); + try { + flusher.flushNow(); + } catch (Exception e) { + System.err.println("exception in flushNow:"); + e.printStackTrace(); + } + } + + @Override + 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 queue + clear(); + } + + private final class QueueFlusher extends Thread { + private boolean needsFlush = false; + private Runnable task; + private Error error; + + QueueFlusher(ThreadGroup threadGroup) { + super(threadGroup, "Java2D Queue Flusher"); + setDaemon(true); + setPriority(Thread.MAX_PRIORITY); + } + + public synchronized void flushNow() { + // wake up the flusher + needsFlush = true; + + notify(); + // wait for flush to complete + while (needsFlush) { + try { + wait(); + } catch (InterruptedException e) { + // ignored + } + } + + // 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(); + } + + @Override + public synchronized void run() { + boolean locked = false; + + while (true) { + + while (!needsFlush) { + try { + /* + * Wait until we're woken up with a flushNow() call, + * or the timeout period elapses (so that we can + * flush the queue periodically). + */ + wait(FLUSH_DELAY); + /* + * 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 && (locked = tryLock())) { + if (buf.position() > 0) { + needsFlush = true; + } else { + locked = false; + unlock(); + } + } + } catch (InterruptedException e) { + // ignored + } + } + // locked by either this thread (see locked flag) or by waiting thread: + // TODO: check lock is always acquired when needsFlush = true ? + 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(); + task = null; + } + } catch (Error e) { + error = e; + } catch (Exception ex) { + System.err.println("exception in QueueFlusher:"); + ex.printStackTrace(); + } finally { + if (locked) { + locked = false; + unlock(); + } + // allow the waiting thread to continue + needsFlush = false; + notify(); + } + } + } + } +} diff --git a/src/main/java/sun/java2d/pipe/RenderQueue.java b/src/main/java/sun/java2d/pipe/RenderQueue.java new file mode 100644 index 0000000..4afcc76 --- /dev/null +++ b/src/main/java/sun/java2d/pipe/RenderQueue.java @@ -0,0 +1,271 @@ +/* + * Copyright (c) 2005, 2018, 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.pipe; + +import java.security.AccessController; +import java.util.HashSet; +import java.util.Set; + +import sun.awt.SunToolkit; +import sun.security.action.GetPropertyAction; + +/** + * The RenderQueue class encapsulates a RenderBuffer on which rendering + * operations are enqueued. Note that the RenderQueue lock must be acquired + * before performing any operations on the queue (e.g. enqueuing an operation + * or flushing the queue). A sample usage scenario follows: + * + * public void drawSomething(...) { + * rq.lock(); + * try { + * ctx.validate(...); + * rq.ensureCapacity(4); + * rq.getBuffer().putInt(DRAW_SOMETHING); + * ... + * } finally { + * rq.unlock(); + * } + * } + * + * If you are enqueuing an operation that involves 8-byte parameters (i.e. + * long or double values), it is imperative that you ensure proper + * alignment of the underlying RenderBuffer. This can be accomplished + * simply by providing an offset to the first 8-byte parameter in your + * operation to the ensureCapacityAndAlignment() method. For example: + * + * public void drawStuff(...) { + * rq.lock(); + * try { + * RenderBuffer buf = rq.getBuffer(); + * ctx.validate(...); + * // 28 total bytes in the operation, 12 bytes to the first long + * rq.ensureCapacityAndAlignment(28, 12); + * buf.putInt(DRAW_STUFF); + * buf.putInt(x).putInt(y); + * buf.putLong(addr1); + * buf.putLong(addr2); + * } finally { + * rq.unlock(); + * } + * } + */ +public abstract class RenderQueue { + + /** The size of the underlying buffer, in bytes. */ + private static final int BUFFER_SIZE; + + static { + // 1M instead of 32K (D3D / OGL) for high-end GPU + BUFFER_SIZE = align(getInteger("sun.java2d.render.bufferSize", 1024 * 1024, 32 * 1024, 16 * 1024 * 1024), 1024); + + System.out.println("RenderQueue: sun.java2d.render.bufferSize = " + BUFFER_SIZE); + } + + /** The underlying buffer for this queue. */ + protected final RenderBuffer buf; + + /** + * A Set containing hard references to Objects that must stay alive until + * the queue has been completely flushed. + */ + protected final Set refSet; + + protected RenderQueue() { + refSet = new HashSet(1024); // large enough (LBO) ? + buf = RenderBuffer.allocate(BUFFER_SIZE); + } + + protected final void clear() { + // reset the buffer position + buf.clear(); + // clear the set of references, since we no longer need them + refSet.clear(); + } + + /** + * Locks the queue for read/write access. + */ + public final void lock() { + /* + * Implementation note: In theory we should have two separate locks: + * one lock to synchronize access to the RenderQueue, and then a + * separate lock (the AWT lock) that only needs to be acquired when + * we are about to flush the queue (using native windowing system + * operations). In practice it has been difficult to enforce the + * correct lock ordering; sometimes AWT will have already acquired + * the AWT lock before grabbing the RQ lock (see 6253009), while the + * expected order should be RQ lock and then AWT lock. Due to this + * issue, using two separate locks is prone to deadlocks. Therefore, + * to solve this issue we have decided to eliminate the separate RQ + * lock and instead just acquire the AWT lock here. (Someday it might + * be nice to go back to the old two-lock system, but that would + * require potentially risky changes to AWT to ensure that it never + * acquires the AWT lock before calling into 2D code that wants to + * acquire the RQ lock.) + */ + SunToolkit.awtLock(); + } + + /** + * Attempts to lock the queue. If successful, this method returns true, + * indicating that the caller is responsible for calling + * {@code unlock}; otherwise this method returns false. + */ + public final boolean tryLock() { + return SunToolkit.awtTryLock(); + } + + /** + * Unlocks the queue. + */ + public final void unlock() { + SunToolkit.awtUnlock(); + } + + /** + * Adds the given Object to the set of hard references, which will + * prevent that Object from being disposed until the queue has been + * flushed completely. This is useful in cases where some enqueued + * data could become invalid if the reference Object were garbage + * collected before the queue could be processed. (For example, keeping + * a hard reference to a FontStrike will prevent any enqueued glyph + * images associated with that strike from becoming invalid before the + * queue is flushed.) The reference set will be cleared immediately + * after the queue is flushed each time. + */ + public final void addReference(Object ref) { + refSet.add(ref); + } + + /** + * Returns the encapsulated RenderBuffer object. + */ + public final RenderBuffer getBuffer() { + return buf; + } + + /** + * Ensures that there will be enough room on the underlying buffer + * for the following operation. If the operation will not fit given + * the remaining space, the buffer will be flushed immediately, leaving + * an empty buffer for the impending operation. + * + * @param opsize size (in bytes) of the following operation + */ + public final void ensureCapacity(int opsize) { + if (buf.remaining() < opsize) { + flushNow(); + } + } + + /** + * Convenience method that is equivalent to calling ensureCapacity() + * followed by ensureAlignment(). The ensureCapacity() call allows for an + * extra 4 bytes of space in case the ensureAlignment() method needs to + * insert a NOOP token on the buffer. + * + * @param opsize size (in bytes) of the following operation + * @param first8ByteValueOffset offset (in bytes) from the current + * position to the first 8-byte value used in the following operation + */ + public final void ensureCapacityAndAlignment(int opsize, + int first8ByteValueOffset) + { + ensureCapacity(opsize + 4); + ensureAlignment(first8ByteValueOffset); + } + + /** + * Inserts a 4-byte NOOP token when necessary to ensure that all 8-byte + * parameters for the following operation are added to the underlying + * buffer with an 8-byte memory alignment. + * + * @param first8ByteValueOffset offset (in bytes) from the current + * position to the first 8-byte value used in the following operation + */ + public final void ensureAlignment(int first8ByteValueOffset) { + int first8ByteValuePosition = buf.position() + first8ByteValueOffset; + if ((first8ByteValuePosition & 7) != 0) { + buf.putInt(BufferedOpCodes.NOOP); + } + } + + /** + * Immediately processes each operation currently pending on the buffer. + * This method will block until the entire buffer has been flushed. The + * queue lock must be acquired before calling this method. + */ + public abstract void flushNow(); + + /** + * Immediately processes each operation currently pending on the buffer, + * and then invokes the provided task. This method will block until the + * entire buffer has been flushed and the provided task has been executed. + * The queue lock must be acquired before calling this method. + */ + public abstract void flushAndInvokeNow(Runnable task); + + /** + * Updates the current position of the underlying buffer, and then + * flushes the queue immediately. This method is useful when native code + * has added data to the queue and needs to flush immediately. + */ + public void flushNow(int position) { + buf.position(position); + flushNow(); + } + + + // system property utilities + public static int getInteger(final String key, final int def, + final int min, final int max) + { + final String property = AccessController.doPrivileged( + new GetPropertyAction(key)); + + int value = def; + if (property != null) { + try { + value = Integer.decode(property); + } catch (NumberFormatException e) { + System.out.println("Invalid integer value for " + key + " = " + property); + } + } + + // check for invalid values + if ((value < min) || (value > max)) { + System.out.println("Invalid value for " + key + " = " + value + + "; expected value in range[" + min + ", " + max + "] !"); + value = def; + } + return value; + } + + protected static int align(final int val, final int norm) { + final int ceil = (int)Math.ceil( ((float) val) / norm); + return ceil * norm; + } +}