/* |
|
* Copyright (c) 2005, 2016, 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.awt.AlphaComposite; |
|
import java.awt.Color; |
|
import java.awt.Composite; |
|
import java.awt.Paint; |
|
import java.awt.geom.AffineTransform; |
|
import sun.java2d.pipe.hw.AccelSurface; |
|
import sun.java2d.InvalidPipeException; |
|
import sun.java2d.SunGraphics2D; |
|
import sun.java2d.loops.XORComposite; |
|
import static sun.java2d.pipe.BufferedOpCodes.*; |
|
import static sun.java2d.pipe.BufferedRenderPipe.BYTES_PER_SPAN; |
|
import java.lang.annotation.Native; |
|
import java.lang.ref.Reference; |
|
import java.lang.ref.WeakReference; |
|
/** |
|
* Base context class for managing state in a single-threaded rendering |
|
* environment. Each state-setting operation (e.g. SET_COLOR) is added to |
|
* the provided RenderQueue, which will be processed at a later time by a |
|
* single thread. Note that the RenderQueue lock must be acquired before |
|
* calling the validate() method (or any other method in this class). See |
|
* the RenderQueue class comments for a sample usage scenario. |
|
* |
|
* @see RenderQueue |
|
*/ |
|
public abstract class BufferedContext { |
|
/* |
|
* The following flags help the internals of validate() determine |
|
* the appropriate (meaning correct, or optimal) code path when |
|
* setting up the current context. The flags can be bitwise OR'd |
|
* together as needed. |
|
*/ |
|
/** |
|
* Indicates that no flags are needed; take all default code paths. |
|
*/ |
|
@Native public static final int NO_CONTEXT_FLAGS = (0 << 0); |
|
/** |
|
* Indicates that the source surface (or color value, if it is a simple |
|
* rendering operation) is opaque (has an alpha value of 1.0). If this |
|
* flag is present, it allows us to disable blending in certain |
|
* situations in order to improve performance. |
|
*/ |
|
@Native public static final int SRC_IS_OPAQUE = (1 << 0); |
|
/** |
|
* Indicates that the operation uses an alpha mask, which may determine |
|
* the code path that is used when setting up the current paint state. |
|
*/ |
|
@Native public static final int USE_MASK = (1 << 1); |
|
protected RenderQueue rq; |
|
protected RenderBuffer buf; |
|
/** |
|
* This is a reference to the most recently validated BufferedContext. If |
|
* this value is null, it means that there is no current context. It is |
|
* provided here so that validate() only needs to do a quick reference |
|
* check to see if the BufferedContext passed to that method is the same |
|
* as the one we've cached here. |
|
*/ |
|
protected static BufferedContext currentContext; |
|
private Reference<AccelSurface> validSrcDataRef = new WeakReference<>(null); |
|
private Reference<AccelSurface> validDstDataRef = new WeakReference<>(null); |
|
private Reference<Region> validClipRef = new WeakReference<>(null); |
|
private Reference<Composite> validCompRef = new WeakReference<>(null); |
|
private Reference<Paint> validPaintRef = new WeakReference<>(null); |
|
// renamed from isValidatedPaintAColor as part of a work around for 6764257 |
|
private boolean isValidatedPaintJustAColor; |
|
private int validatedRGB; |
|
private int validatedFlags; |
|
private boolean xformInUse; |
|
private AffineTransform transform; |
|
protected BufferedContext(RenderQueue rq) { |
|
this.rq = rq; |
|
this.buf = rq.getBuffer(); |
|
} |
|
/** |
|
* Fetches the BufferedContextContext associated with the dst. surface |
|
* and validates the context using the given parameters. Most rendering |
|
* operations will call this method first in order to set the necessary |
|
* state before issuing rendering commands. |
|
* |
|
* Note: must be called while the RenderQueue lock is held. |
|
* |
|
* It's assumed that the type of surfaces has been checked by the Renderer |
|
* |
|
* @throws InvalidPipeException if either src or dest surface is not valid |
|
* or lost |
|
* @see RenderQueue#lock |
|
* @see RenderQueue#unlock |
|
*/ |
|
public static void validateContext(AccelSurface srcData, |
|
AccelSurface dstData, |
|
Region clip, Composite comp, |
|
AffineTransform xform, |
|
Paint paint, SunGraphics2D sg2d, |
|
int flags) |
|
{ |
|
// assert rq.lock.isHeldByCurrentThread(); |
|
BufferedContext context = dstData.getContext(); |
|
context.validate(srcData, dstData, |
|
clip, comp, xform, paint, sg2d, flags); |
|
} |
|
/** |
|
* Fetches the BufferedContextassociated with the surface |
|
* and disables all context state settings. |
|
* |
|
* Note: must be called while the RenderQueue lock is held. |
|
* |
|
* It's assumed that the type of surfaces has been checked by the Renderer |
|
* |
|
* @throws InvalidPipeException if the surface is not valid |
|
* or lost |
|
* @see RenderQueue#lock |
|
* @see RenderQueue#unlock |
|
*/ |
|
public static void validateContext(AccelSurface surface) { |
|
// assert rt.lock.isHeldByCurrentThread(); |
|
validateContext(surface, surface, |
|
null, null, null, null, null, NO_CONTEXT_FLAGS); |
|
} |
|
/** |
|
* Validates the given parameters against the current state for this |
|
* context. If this context is not current, it will be made current |
|
* for the given source and destination surfaces, and the viewport will |
|
* be updated. Then each part of the context state (clip, composite, |
|
* etc.) is checked against the previous value. If the value has changed |
|
* since the last call to validate(), it will be updated accordingly. |
|
* |
|
* Note that the SunGraphics2D parameter is only used for the purposes |
|
* of validating a (non-null) Paint parameter. In all other cases it |
|
* is safe to pass a null SunGraphics2D and it will be ignored. |
|
* |
|
* Note: must be called while the RenderQueue lock is held. |
|
* |
|
* It's assumed that the type of surfaces has been checked by the Renderer |
|
* |
|
* @throws InvalidPipeException if either src or dest surface is not valid |
|
* or lost |
|
*/ |
|
public void validate(AccelSurface srcData, AccelSurface dstData, |
|
Region clip, Composite comp, |
|
AffineTransform xform, |
|
Paint paint, SunGraphics2D sg2d, int flags) |
|
{ |
|
// assert rq.lock.isHeldByCurrentThread(); |
|
boolean updateClip = false; |
|
boolean updatePaint = false; |
|
if (!dstData.isValid() || |
|
dstData.isSurfaceLost() || srcData.isSurfaceLost()) |
|
{ |
|
invalidateContext(); |
|
throw new InvalidPipeException("bounds changed or surface lost"); |
|
} |
|
if (paint instanceof Color) { |
|
// REMIND: not 30-bit friendly |
|
int newRGB = ((Color)paint).getRGB(); |
|
if (isValidatedPaintJustAColor) { |
|
if (newRGB != validatedRGB) { |
|
validatedRGB = newRGB; |
|
updatePaint = true; |
|
} |
|
} else { |
|
validatedRGB = newRGB; |
|
updatePaint = true; |
|
isValidatedPaintJustAColor = true; |
|
} |
|
} else if (validPaintRef.get() != paint) { |
|
updatePaint = true; |
|
// this should be set when we are switching from paint to color |
|
// in which case this condition will be true |
|
isValidatedPaintJustAColor = false; |
|
} |
|
final AccelSurface validatedSrcData = validSrcDataRef.get(); |
|
final AccelSurface validatedDstData = validDstDataRef.get(); |
|
if ((currentContext != this) || |
|
(srcData != validatedSrcData) || |
|
(dstData != validatedDstData)) |
|
{ |
|
if (dstData != validatedDstData) { |
|
// the clip is dependent on the destination surface, so we |
|
// need to update it if we have a new destination surface |
|
updateClip = true; |
|
} |
|
if (paint == null) { |
|
// make sure we update the color state (otherwise, it might |
|
// not be updated if this is the first time the context |
|
// is being validated) |
|
updatePaint = true; |
|
} |
|
// update the current source and destination surfaces |
|
setSurfaces(srcData, dstData); |
|
currentContext = this; |
|
validSrcDataRef = new WeakReference<>(srcData); |
|
validDstDataRef = new WeakReference<>(dstData); |
|
} |
|
// validate clip |
|
final Region validatedClip = validClipRef.get(); |
|
if ((clip != validatedClip) || updateClip) { |
|
if (clip != null) { |
|
if (updateClip || |
|
validatedClip == null || |
|
!(validatedClip.isRectangular() && clip.isRectangular()) || |
|
((clip.getLoX() != validatedClip.getLoX() || |
|
clip.getLoY() != validatedClip.getLoY() || |
|
clip.getHiX() != validatedClip.getHiX() || |
|
clip.getHiY() != validatedClip.getHiY()))) |
|
{ |
|
setClip(clip); |
|
} |
|
} else { |
|
resetClip(); |
|
} |
|
validClipRef = new WeakReference<>(clip); |
|
} |
|
// validate composite (note that a change in the context flags |
|
// may require us to update the composite state, even if the |
|
// composite has not changed) |
|
if ((comp != validCompRef.get()) || (flags != validatedFlags)) { |
|
if (comp != null) { |
|
setComposite(comp, flags); |
|
} else { |
|
resetComposite(); |
|
} |
|
// the paint state is dependent on the composite state, so make |
|
// sure we update the color below |
|
updatePaint = true; |
|
validCompRef = new WeakReference<>(comp); |
|
validatedFlags = flags; |
|
} |
|
// validate transform |
|
boolean txChanged = false; |
|
if (xform == null) { |
|
if (xformInUse) { |
|
resetTransform(); |
|
xformInUse = false; |
|
txChanged = true; |
|
} else if (sg2d != null && !sg2d.transform.equals(transform)) { |
|
txChanged = true; |
|
} |
|
if (sg2d != null && txChanged) { |
|
transform = new AffineTransform(sg2d.transform); |
|
} |
|
} else { |
|
setTransform(xform); |
|
xformInUse = true; |
|
txChanged = true; |
|
} |
|
// non-Color paints may require paint revalidation |
|
if (!isValidatedPaintJustAColor && txChanged) { |
|
updatePaint = true; |
|
} |
|
// validate paint |
|
if (updatePaint) { |
|
if (paint != null) { |
|
BufferedPaints.setPaint(rq, sg2d, paint, flags); |
|
} else { |
|
BufferedPaints.resetPaint(rq); |
|
} |
|
validPaintRef = new WeakReference<>(paint); |
|
} |
|
// mark dstData dirty |
|
// REMIND: is this really needed now? we do it in SunGraphics2D.. |
|
dstData.markDirty(); |
|
} |
|
/** |
|
* Invalidates the surfaces associated with this context. This is |
|
* useful when the context is no longer needed, and we want to break |
|
* the chain caused by these surface references. |
|
* |
|
* Note: must be called while the RenderQueue lock is held. |
|
* |
|
* @see RenderQueue#lock |
|
* @see RenderQueue#unlock |
|
*/ |
|
private void invalidateSurfaces() { |
|
validSrcDataRef.clear(); |
|
validDstDataRef.clear(); |
|
} |
|
private void setSurfaces(AccelSurface srcData, |
|
AccelSurface dstData) |
|
{ |
|
// assert rq.lock.isHeldByCurrentThread(); |
|
rq.ensureCapacityAndAlignment(20, 4); |
|
buf.putInt(SET_SURFACES); |
|
buf.putLong(srcData.getNativeOps()); |
|
buf.putLong(dstData.getNativeOps()); |
|
} |
|
private void resetClip() { |
|
// assert rq.lock.isHeldByCurrentThread(); |
|
rq.ensureCapacity(4); |
|
buf.putInt(RESET_CLIP); |
|
} |
|
private void setClip(Region clip) { |
|
// assert rq.lock.isHeldByCurrentThread(); |
|
if (clip.isRectangular()) { |
|
rq.ensureCapacity(20); |
|
buf.putInt(SET_RECT_CLIP); |
|
buf.putInt(clip.getLoX()).putInt(clip.getLoY()); |
|
buf.putInt(clip.getHiX()).putInt(clip.getHiY()); |
|
} else { |
|
rq.ensureCapacity(28); // so that we have room for at least a span |
|
buf.putInt(BEGIN_SHAPE_CLIP); |
|
buf.putInt(SET_SHAPE_CLIP_SPANS); |
|
// include a placeholder for the span count |
|
int countIndex = buf.position(); |
|
buf.putInt(0); |
|
int spanCount = 0; |
|
int remainingSpans = buf.remaining() / BYTES_PER_SPAN; |
|
int span[] = new int[4]; |
|
SpanIterator si = clip.getSpanIterator(); |
|
while (si.nextSpan(span)) { |
|
if (remainingSpans == 0) { |
|
buf.putInt(countIndex, spanCount); |
|
rq.flushNow(); |
|
buf.putInt(SET_SHAPE_CLIP_SPANS); |
|
countIndex = buf.position(); |
|
buf.putInt(0); |
|
spanCount = 0; |
|
remainingSpans = buf.remaining() / BYTES_PER_SPAN; |
|
} |
|
buf.putInt(span[0]); // x1 |
|
buf.putInt(span[1]); // y1 |
|
buf.putInt(span[2]); // x2 |
|
buf.putInt(span[3]); // y2 |
|
spanCount++; |
|
remainingSpans--; |
|
} |
|
buf.putInt(countIndex, spanCount); |
|
rq.ensureCapacity(4); |
|
buf.putInt(END_SHAPE_CLIP); |
|
} |
|
} |
|
private void resetComposite() { |
|
// assert rq.lock.isHeldByCurrentThread(); |
|
rq.ensureCapacity(4); |
|
buf.putInt(RESET_COMPOSITE); |
|
} |
|
private void setComposite(Composite comp, int flags) { |
|
// assert rq.lock.isHeldByCurrentThread(); |
|
if (comp instanceof AlphaComposite) { |
|
AlphaComposite ac = (AlphaComposite)comp; |
|
rq.ensureCapacity(16); |
|
buf.putInt(SET_ALPHA_COMPOSITE); |
|
buf.putInt(ac.getRule()); |
|
buf.putFloat(ac.getAlpha()); |
|
buf.putInt(flags); |
|
} else if (comp instanceof XORComposite) { |
|
int xorPixel = ((XORComposite)comp).getXorPixel(); |
|
rq.ensureCapacity(8); |
|
buf.putInt(SET_XOR_COMPOSITE); |
|
buf.putInt(xorPixel); |
|
} else { |
|
throw new InternalError("not yet implemented"); |
|
} |
|
} |
|
private void resetTransform() { |
|
// assert rq.lock.isHeldByCurrentThread(); |
|
rq.ensureCapacity(4); |
|
buf.putInt(RESET_TRANSFORM); |
|
} |
|
private void setTransform(AffineTransform xform) { |
|
// assert rq.lock.isHeldByCurrentThread(); |
|
rq.ensureCapacityAndAlignment(52, 4); |
|
buf.putInt(SET_TRANSFORM); |
|
buf.putDouble(xform.getScaleX()); |
|
buf.putDouble(xform.getShearY()); |
|
buf.putDouble(xform.getShearX()); |
|
buf.putDouble(xform.getScaleY()); |
|
buf.putDouble(xform.getTranslateX()); |
|
buf.putDouble(xform.getTranslateY()); |
|
} |
|
/** |
|
* Resets this context's surfaces and all attributes. |
|
* |
|
* Note: must be called while the RenderQueue lock is held. |
|
* |
|
* @see RenderQueue#lock |
|
* @see RenderQueue#unlock |
|
*/ |
|
public void invalidateContext() { |
|
resetTransform(); |
|
resetComposite(); |
|
resetClip(); |
|
BufferedPaints.resetPaint(rq); |
|
invalidateSurfaces(); |
|
validCompRef.clear(); |
|
validClipRef.clear(); |
|
validPaintRef.clear(); |
|
isValidatedPaintJustAColor = false; |
|
xformInUse = false; |
|
} |
|
/** |
|
* Returns a singleton {@code RenderQueue} object used by the rendering |
|
* pipeline. |
|
* |
|
* @return a render queue |
|
* @see RenderQueue |
|
*/ |
|
public abstract RenderQueue getRenderQueue(); |
|
/** |
|
* Saves the the state of this context. |
|
* It may reset the current context. |
|
* |
|
* Note: must be called while the RenderQueue lock is held. |
|
* |
|
* @see RenderQueue#lock |
|
* @see RenderQueue#unlock |
|
*/ |
|
public abstract void saveState(); |
|
/** |
|
* Restores the native state of this context. |
|
* It may reset the current context. |
|
* |
|
* Note: must be called while the RenderQueue lock is held. |
|
* |
|
* @see RenderQueue#lock |
|
* @see RenderQueue#unlock |
|
*/ |
|
public abstract void restoreState(); |
|
} |