com.sun.scenario.effect.impl.Renderer Maven / Gradle / Ivy
/*
* Copyright (c) 2008, 2021, 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 com.sun.scenario.effect.impl;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import com.sun.javafx.geom.Rectangle;
import com.sun.javafx.geom.transform.Affine2D;
import com.sun.javafx.geom.transform.BaseTransform;
import com.sun.scenario.effect.Effect;
import com.sun.scenario.effect.Effect.AccelType;
import com.sun.scenario.effect.FilterContext;
import com.sun.scenario.effect.Filterable;
import com.sun.scenario.effect.FloatMap;
import com.sun.scenario.effect.ImageData;
import com.sun.scenario.effect.LockableResource;
public abstract class Renderer {
/**
* Enumeration describing the lifecycle states of the renderer.
* When the renderer is created, it will either start in the {@code OK}
* state, or it will start in the {@code NOTREADY} state until
* the first time it is used during rendering, then transition
* to the {@code OK} state.
*
* It could become {@code LOST} at some point. This may happen for
* example if the renderer is susceptible to display changes.
*
* When the renderer is in the {@code LOST} state it can't be used
* for rendering, instead a {@link #getBackupRenderer() backup renderer}
* must be used.
*
* Sometime later the renderer could enter the {@code DISPOSED} state,
* at which point it will be removed from the cache and a new renderer
* will be created for the particular context.
*
* Thus the lifecycle of a renderer is:
* [{@code NOTREADY} =>] {@code OK} [=> {@code LOST} [=> {@code DISPOSED}]]
*/
public static enum RendererState {
/**
* Renderer is not ready; it might be able to be used for rendering.
*/
NOTREADY,
/**
* Renderer can be used for rendering.
*/
OK,
/**
* Renderer is lost, a backup renderer must be used.
*/
LOST,
/**
* Renderer is disposed, it is no longer usable and must be replaced.
*/
DISPOSED
}
public static final String rootPkg = "com.sun.scenario.effect";
private static final Map rendererMap =
new HashMap(1);
private Map peerCache =
Collections.synchronizedMap(new HashMap(5));
private final ImagePool imagePool;
@SuppressWarnings("removal")
protected static final boolean verbose = AccessController.doPrivileged(
(PrivilegedAction) () -> Boolean.getBoolean("decora.verbose"));
protected Renderer() {
this.imagePool = new ImagePool();
}
/**
* Returns the {@link AccelType} used by default for peers of this renderer.
*
* Note that the Renderer may specialize in peers of this type, and
* it may create them in general by default, but the renderers all
* look for an Intrinsic peer for a given operation as well so the
* actual peer implementaiton for a given effect may sometimes differ
* from this {@code AccelType}. Care should be taken if the actual
* {@code AccelType} for a specific operation is needed, then the
* {@link EffectPeer#getAccelType()} should be consulted directly
* in those cases.
*
* @return the {@code AccelType} used by typical peers of this renderer
*/
public abstract AccelType getAccelType();
public abstract int getCompatibleWidth(int w);
public abstract int getCompatibleHeight(int h);
public abstract PoolFilterable createCompatibleImage(int w, int h);
public PoolFilterable getCompatibleImage(int w, int h) {
return imagePool.checkOut(this, w, h);
}
public void releaseCompatibleImage(Filterable image) {
if (image instanceof PoolFilterable) {
ImagePool pool = ((PoolFilterable) image).getImagePool();
if (pool != null) {
pool.checkIn((PoolFilterable) image);
return;
}
// } else {
// Error?
}
image.unlock();
}
/**
* This is a temporary workaround for a PowerVR SGX issue. See
* ImagePool for more details.
*/
public void releasePurgatory() {
imagePool.releasePurgatory();
}
/**
* Mainly used by {@code ImagePool} for the purpose of clearing
* an image before handing it back to the user.
*
* @param image the image to be cleared
*/
public abstract void clearImage(Filterable image);
/**
* Mainly used by the {@code Identity} effect for the purpose of
* creating a cached {@code ImageData} from the given platform-specific
* image (e.g. a {@code BufferedImage} wrapped in a {@code J2DImage}).
*
* @param fctx the filter context
* @param platformImage the platform-specific source image to be copied
* into the new {@code ImageData} object
* @return a new {@code ImageData}
*/
public abstract ImageData createImageData(FilterContext fctx,
Filterable src);
public ImageData transform(FilterContext fctx, ImageData img,
int xpow2scales, int ypow2scales)
{
if (!img.getTransform().isIdentity()) {
throw new InternalError("transform by powers of 2 requires untransformed source");
}
if ((xpow2scales | ypow2scales) == 0) {
return img;
}
Affine2D at = new Affine2D();
// Any amount of upscaling and up to 1 level of downscaling
// can be handled by the filters themselves...
while (xpow2scales < -1 || ypow2scales < -1) {
Rectangle origbounds = img.getUntransformedBounds();
Rectangle newbounds = new Rectangle(origbounds);
double xscale = 1.0;
double yscale = 1.0;
if (xpow2scales < 0) {
// To avoid loss, only scale down one step at a time
xscale = 0.5;
newbounds.width = (origbounds.width + 1) / 2;
newbounds.x /= 2;
xpow2scales++;
}
if (ypow2scales < 0) {
// To avoid loss, only scale down one step at a time
yscale = 0.5;
newbounds.height = (origbounds.height + 1) / 2;
newbounds.y /= 2;
ypow2scales++;
}
at.setToScale(xscale, yscale);
img = transform(fctx, img, at, origbounds, newbounds);
}
if ((xpow2scales | ypow2scales) != 0) {
// assert xscale >= -1 and yscale >= -1
double xscale = (xpow2scales < 0) ? 0.5 : 1 << xpow2scales;
double yscale = (ypow2scales < 0) ? 0.5 : 1 << ypow2scales;
at.setToScale(xscale, yscale);
img = img.transform(at);
}
return img;
}
public abstract Filterable transform(FilterContext fctx,
Filterable original,
BaseTransform transform,
Rectangle origBounds,
Rectangle xformBounds);
public abstract ImageData transform(FilterContext fctx, ImageData original,
BaseTransform transform,
Rectangle origBounds,
Rectangle xformBounds);
// NOTE: these two methods are only relevant to HW codepaths; should
// find a way to push them down a level...
public LockableResource createFloatTexture(int w, int h) {
throw new InternalError();
}
public void updateFloatTexture(LockableResource texture, FloatMap map) {
throw new InternalError();
}
/**
* Returns a (cached) instance of peer given the context, name and unroll
* count.
*
* @param fctx filter context - same as this renderer's context
* @param name not-unrolled name of the peer
* @param unrollCount
* @return cached peer for this name and unroll count
*/
public final synchronized EffectPeer
getPeerInstance(FilterContext fctx, String name, int unrollCount)
{
// first look for a previously cached peer using only the base name
// (e.g. GaussianBlur); software peers do not (currently) have
// unrolled loops, so this step should locate those...
EffectPeer peer = peerCache.get(name);
if (peer != null) {
return peer;
}
// failing that, if there is a positive unrollCount, we attempt
// to find a previously cached hardware peer for that unrollCount
if (unrollCount > 0) {
peer = peerCache.get(name + "_" + unrollCount);
if (peer != null) {
return peer;
}
}
peer = createPeer(fctx, name, unrollCount);
if (peer == null) {
throw new RuntimeException("Could not create peer " + name +
" for renderer " + this);
}
// use the peer's unique name as the hashmap key
peerCache.put(peer.getUniqueName(), peer);
return peer;
}
/**
* Returns this renderer's current state.
*
* @return the state
* @see RendererState
*/
public abstract RendererState getRendererState();
/**
* Creates a new peer given the context, name and unroll count.
*
* @param fctx context shared with the renderer
* @param name of the peer
* @param unrollCount unroll count
* @return new peer
*/
protected abstract EffectPeer createPeer(FilterContext fctx,
String name, int unrollCount);
/**
* Returns current cache of peers.
*/
protected Collection getPeers() {
return peerCache.values();
}
/**
* This method can be used by subclasses to create a backup renderer,
* either a SW (Java) renderer or an SSE (native) renderer, depending
* on what is available.
*
* @return an instance of Renderer that uses CPU filtering
*/
protected static Renderer getSoftwareRenderer() {
return RendererFactory.getSoftwareRenderer();
}
/**
* Returns an instance of backup renderer to be used if this renderer
* is in {@code LOST} state.
*
* @return backup renderer
*/
protected abstract Renderer getBackupRenderer();
/**
* Returns a {@code Renderer} instance that is most appropriate
* for the given size of the source data. The default implementation
* simply returns "this" renderer, but subclasses may override this
* method and return a different renderer depending on the size of
* the operation. For example, a GPU-based renderer may wish to
* return a software renderer for small-sized operations (because of
* lower overhead, etc).
*
* @param approxW approximate input width
* @param approxH approximate input height
* @return the {@code Renderer} best suited for this size
*/
protected Renderer getRendererForSize(Effect effect, int approxW, int approxH) {
return this;
}
/**
* Returns a renderer associated with given filter context based on the
* environment and flags set.
*
* Renderers are per filter context cached.
*
* @param fctx context to create the renderer for
* @return renderer
*/
public static synchronized Renderer getRenderer(FilterContext fctx) {
if (fctx == null) {
throw new IllegalArgumentException("FilterContext must be non-null");
}
Renderer r = rendererMap.get(fctx);
if (r != null) {
if (r.getRendererState() == RendererState.NOTREADY) {
return r;
}
if (r.getRendererState() == RendererState.OK) {
return r;
}
if (r.getRendererState() == RendererState.LOST) {
// use the backup while the renderer is in lost state, until
// it is disposed (or forever if it can't be disposed/reset)
// Note: we don't add it to the cache to prevent permanent
// association of the backup renderer and this filter context.
return r.getBackupRenderer();
}
if (r.getRendererState() == RendererState.DISPOSED) {
r = null;
// we remove disposed renderers below instead of here to cover
// cases where we never use a context which the disposed
// renderer is associated with
}
}
if (r == null) {
// clean up all disposed renderers first
Collection renderers = rendererMap.values();
for (Iterator iter = renderers.iterator(); iter.hasNext();)
{
Renderer ren = iter.next();
if (ren.getRendererState() == RendererState.DISPOSED) {
ren.imagePool.dispose();
iter.remove();
}
}
r = RendererFactory.createRenderer(fctx);
if (r == null) {
throw new RuntimeException("Error creating a Renderer");
} else {
if (verbose) {
String klassName = r.getClass().getName();
String rname = klassName.substring(klassName.lastIndexOf(".")+1);
Object screen = fctx.getReferent();
System.out.println("Created " + rname +
" (AccelType=" + r.getAccelType() +
") for " + screen);
}
}
rendererMap.put(fctx, r);
}
return r;
}
/**
* Returns a renderer that is most optimal for the approximate size
* of the filtering operation.
*
* @param fctx context to create the renderer for
* @param effect uses in the rendering
* @param approxW approximate input width
* @param approxH approximate input height
* @return renderer
*/
public static Renderer getRenderer(FilterContext fctx, Effect effect,
int approxW, int approxH) {
return getRenderer(fctx).getRendererForSize(effect, approxW, approxH);
}
/**
* Determines whether the passed {@code ImageData} is compatible with this
* renderer (that is, if it can be used as a input source for this
* renderer's peers).
*
* @param id {@code ImageData} to be checked
* @return true if this image data is compatible, false otherwise
*/
public abstract boolean isImageDataCompatible(ImageData id);
}