--- /dev/null 2014-02-21 17:01:54.000000000 -0800 +++ new/modules/graphics/src/main/java/com/sun/scenario/effect/LinearConvolveCoreEffect.java 2014-02-21 17:01:54.000000000 -0800 @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2014, 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; + +import com.sun.javafx.geom.Rectangle; +import com.sun.javafx.geom.transform.BaseTransform; +import com.sun.scenario.effect.impl.EffectPeer; +import com.sun.scenario.effect.impl.Renderer; +import com.sun.scenario.effect.impl.state.LinearConvolveKernel; +import com.sun.scenario.effect.impl.state.LinearConvolveRenderState; + +/** + * An intermediate mix-in super class that performs the multi-pass filtering + * algorithm common to all linear convolution filters such as Gaussian, + * Box, and Motion Blurs and Shadows. In particular, it is used for all + * filters that use the LinearConvolve and LinearConvolveShadow shader peers. + */ +public abstract class LinearConvolveCoreEffect + extends CoreEffect +{ + public LinearConvolveCoreEffect(Effect input) { + super(input); + } + + @Override + public final LinearConvolveRenderState + getRenderState(FilterContext fctx, + BaseTransform transform, + Rectangle outputClip, + Object renderHelper, + Effect defaultInput) + { + return getState().getRenderState(transform); + } + + @Override + abstract LinearConvolveKernel getState(); + + @Override + public ImageData filterImageDatas(FilterContext fctx, + BaseTransform transform, + Rectangle outputClip, + LinearConvolveRenderState lcrstate, + ImageData... inputs) + { + ImageData src = inputs[0]; + src.addref(); + if (lcrstate.isNop()) { + return src; + } + Rectangle approxBounds = inputs[0].getUntransformedBounds(); + int approxW = approxBounds.width; + int approxH = approxBounds.height; + Rectangle filterClip = outputClip; + + Renderer r = Renderer.getRenderer(fctx, this, approxW, approxH); + for (int pass = 0; pass < 2; pass++) { + src = lcrstate.validatePassInput(src, pass); + EffectPeer peer = lcrstate.getPassPeer(r, fctx); + if (peer != null) { + peer.setPass(pass); + ImageData res = peer.filter(this, lcrstate, transform, filterClip, src); + src.unref(); + src = res; + if (!src.validate(fctx)) { + src.unref(); + return src; + } + } + } + + return src; + } +} --- /dev/null 2014-02-21 17:01:55.000000000 -0800 +++ new/modules/graphics/src/main/java/com/sun/scenario/effect/impl/state/RenderState.java 2014-02-21 17:01:55.000000000 -0800 @@ -0,0 +1,222 @@ +/* + * Copyright (c) 2014, 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.state; + +import com.sun.javafx.geom.Rectangle; +import com.sun.javafx.geom.transform.BaseTransform; + +/** + * A class that encapsulates all of the information needed to plan and execute + * a single filter operation. An instance of the class is instantiated at + * the start of an {@link Effect.filter()} operation and it is queried for + * various pieces of information required to perform that operation including, + * but not limited to, the coordinate space to be used for the child input + * {@code Effect} operations and the coordinate transform to be applied to + * the resulting final {@code ImageData} object produced as the result of that + * filter operation. Some effect-specific subclasses may also plan and supply + * information about how the various pixels are to be computed in the inner + * loops of the effect filter algorithm. + */ +public interface RenderState { + /** + * This enum characterizes the types of coordinate spaces that will be + * used for the filter operation (i.e. handed to the dependent input + * {@code Effect} objects) and applied to the result of the filter. + */ + public static enum EffectCoordinateSpace { + /** + * The {@link RenderState} object will specify an IDENTITY transform + * for the input transform and the original filter transform as + * the result transform. + */ + UserSpace, + + /** + * The {@link RenderState} object will specify custom transform objects + * for both the input transform and the result transform with the only + * constraint that the two will concatenate to produce the original + * filter transform. + *
+         *     BaseTransform inputtx = getInputTransform(filtertx);
+         *     BaseTransform resulttx = getResultTransform(filtertx);
+         *     // Ignoring the potential for concatenate to modify the
+         *     // return values from the above two methods, this virtual
+         *     // assert statement is just for the sake of documenting
+         *     // the intended constraints.
+         *     assert(filtertx.equalsApproximately(resulttx.concatenate(inputtx)));
+         * 
+ */ + CustomSpace, + + /** + * The {@link RenderState} object will specify the original filter + * transform as the input transform and an IDENTITY transform for + * the result transform. + */ + RenderSpace, + } + + /** + * A helper implementation of {@link RenderState} that handles the + * case of {@code EffectCoordinateSpace.UserSpace} and passes along + * the outputClip to the inputs unmodified. + */ + public static final RenderState UserSpaceRenderState = + new RenderState() { + @Override + public EffectCoordinateSpace getEffectTransformSpace() { + return EffectCoordinateSpace.UserSpace; + } + + @Override + public BaseTransform getInputTransform(BaseTransform filterTransform) { + return BaseTransform.IDENTITY_TRANSFORM; + } + + @Override + public BaseTransform getResultTransform(BaseTransform filterTransform) { + return filterTransform; + } + + @Override + public Rectangle getInputClip(int i, Rectangle filterClip) { + return filterClip; + } + }; + + /** + * A helper implementation of {@link RenderState} that handles the + * case of {@code EffectCoordinateSpace.UserSpace} and passes along + * the outputClip to the inputs unmodified. + */ + public static final RenderState UnclippedUserSpaceRenderState = + new RenderState() { + @Override + public EffectCoordinateSpace getEffectTransformSpace() { + return EffectCoordinateSpace.UserSpace; + } + + @Override + public BaseTransform getInputTransform(BaseTransform filterTransform) { + return BaseTransform.IDENTITY_TRANSFORM; + } + + @Override + public BaseTransform getResultTransform(BaseTransform filterTransform) { + return filterTransform; + } + + @Override + public Rectangle getInputClip(int i, Rectangle filterClip) { + return null; + } + }; + + /** + * A helper implementation of {@link RenderState} that handles the + * case of {@code EffectCoordinateSpace.RenderSpace} and passes along + * the outputClip to the inputs unmodified. + */ + public static final RenderState RenderSpaceRenderState = + new RenderState() { + @Override + public EffectCoordinateSpace getEffectTransformSpace() { + return EffectCoordinateSpace.RenderSpace; + } + + @Override + public BaseTransform getInputTransform(BaseTransform filterTransform) { + return filterTransform; + } + + @Override + public BaseTransform getResultTransform(BaseTransform filterTransform) { + return BaseTransform.IDENTITY_TRANSFORM; + } + + @Override + public Rectangle getInputClip(int i, Rectangle filterClip) { + // REMIND: Need to factor out a few implementations here... + return filterClip; + } + }; + + /** + * Return a hint indicating which coordinate space should be used for + * the pixel filtering for this particular filtering operation. + * The {@link #getEffectTransform(com.sun.javafx.geom.transform.BaseTransform) + * getEffectTransform()} and {@link #getResultTransform(com.sun.javafx.geom.transform.BaseTransform) + * getResultTransform()} methods will always be used to get the actual + * transforms to be used to get input data and transform the results, but + * this method can help to set the expectations of the caller to optimize + * techniques. + * + * @return an {@link EffectSpace} value to describe the expected output + * from the {@code getEffectTransform(...)} and {@code getResultTransform(...)} + * methods. + */ + public EffectCoordinateSpace getEffectTransformSpace(); + + /** + * Return the transform that should be used to obtain pixel input from the + * {@code Effect} inputs for this filter operation. + * The returned transform is handed to all input {@code Effect} objects + * to obtain pixel data for the inputs. + * Typically, the output of {@code getInputTransform(transform)} and + * {@code getResultTransform(transform)} could be concatenated to produce + * the original {@code filterTransform}. + * + * @param filterTransform the {@code BaseTransform} object for the filter operation + * @return the {@code BaseTransform} object to use for the input effects + */ + public BaseTransform getInputTransform(BaseTransform filterTransform); + + /** + * Return the transform that should be used to transform the results of + * the filter operation. + * The returned transform is combined with the resulting filter result + * texture to produce an output ImageData object. + * Typically, the output of {@code getInputTransform(transform)} and + * {@code getResultTransform(transform)} could be concatenated to produce + * the original {@code filterTransform}. + * + * @param filterTransform the {@code BaseTransform} object for the filter operation + * @return the {@code BaseTransform} object to be applied to the result + * texture + */ + public BaseTransform getResultTransform(BaseTransform filterTransform); + + /** + * Return the clip for the indicated input based on the indicated output + * clip. + * + * @param i the index of the input being processed + * @param filterClip the output clip supplied to the given filter operation + * @return the required rectangle from the indicated input to provide + * enough pixels to produce the indicated output clip + */ + public Rectangle getInputClip(int i, Rectangle filterClip); +} --- old/modules/graphics/src/main/java/com/sun/scenario/effect/GaussianBlur.java 2014-02-21 17:01:56.000000000 -0800 +++ new/modules/graphics/src/main/java/com/sun/scenario/effect/GaussianBlur.java 2014-02-21 17:01:56.000000000 -0800 @@ -33,12 +33,13 @@ import com.sun.javafx.geom.transform.BaseTransform; import com.sun.scenario.effect.impl.Renderer; import com.sun.scenario.effect.impl.state.GaussianBlurState; +import com.sun.scenario.effect.impl.state.LinearConvolveKernel; /** * A blur effect using a Gaussian convolution kernel, with a configurable * radius. */ -public class GaussianBlur extends CoreEffect { +public class GaussianBlur extends LinearConvolveCoreEffect { private GaussianBlurState state = new GaussianBlurState(); @@ -84,7 +85,7 @@ } @Override - Object getState() { + LinearConvolveKernel getState() { return state; } @@ -163,39 +164,6 @@ } @Override - public ImageData filterImageDatas(FilterContext fctx, - BaseTransform transform, - Rectangle outputClip, - ImageData... inputs) - { - return state.filterImageDatas(this, fctx, transform, outputClip, inputs); - } - - @Override - public boolean operatesInUserSpace() { - return true; - } - - @Override - protected Rectangle getInputClip(int inputIndex, - BaseTransform transform, - Rectangle outputClip) - { - // A blur needs as much "fringe" data from its input as it creates - // around its output so we use the same expansion as is used in the - // result bounds. - if (outputClip != null) { - int hpad = state.getPad(0); - int vpad = state.getPad(1); - if ((hpad | vpad) != 0) { - outputClip = new Rectangle(outputClip); - outputClip.grow(hpad, vpad); - } - } - return outputClip; - } - - @Override public boolean reducesOpaquePixels() { if (!state.isNop()) { return true; --- old/modules/graphics/src/main/java/com/sun/scenario/effect/GaussianShadow.java 2014-02-21 17:01:57.000000000 -0800 +++ new/modules/graphics/src/main/java/com/sun/scenario/effect/GaussianShadow.java 2014-02-21 17:01:57.000000000 -0800 @@ -33,6 +33,8 @@ import com.sun.javafx.geom.transform.BaseTransform; import com.sun.scenario.effect.impl.Renderer; import com.sun.scenario.effect.impl.state.GaussianShadowState; +import com.sun.scenario.effect.impl.state.LinearConvolveKernel; +import com.sun.scenario.effect.impl.state.LinearConvolveRenderState; /** * A blurred shadow effect using a Gaussian convolution kernel, with a @@ -110,7 +112,7 @@ @Override - Object getState() { + LinearConvolveKernel getState() { return state; } @@ -358,39 +360,6 @@ } @Override - public ImageData filterImageDatas(FilterContext fctx, - BaseTransform transform, - Rectangle outputClip, - ImageData... inputs) - { - return state.filterImageDatas(this, fctx, transform, outputClip, inputs); - } - - @Override - public boolean operatesInUserSpace() { - return true; - } - - @Override - protected Rectangle getInputClip(int inputIndex, - BaseTransform transform, - Rectangle outputClip) - { - // A blur needs as much "fringe" data from its input as it creates - // around its output so we use the same expansion as is used in the - // result bounds. - if (outputClip != null) { - int hpad = state.getPad(0); - int vpad = state.getPad(1); - if ((hpad | vpad) != 0) { - outputClip = new Rectangle(outputClip); - outputClip.grow(hpad, vpad); - } - } - return outputClip; - } - - @Override public boolean reducesOpaquePixels() { return true; } --- old/modules/graphics/src/main/java/com/sun/scenario/effect/BoxBlur.java 2014-02-21 17:01:58.000000000 -0800 +++ new/modules/graphics/src/main/java/com/sun/scenario/effect/BoxBlur.java 2014-02-21 17:01:58.000000000 -0800 @@ -33,13 +33,14 @@ import com.sun.javafx.geom.transform.BaseTransform; import com.sun.scenario.effect.impl.Renderer; import com.sun.scenario.effect.impl.state.BoxBlurState; +import com.sun.scenario.effect.impl.state.LinearConvolveKernel; /** * A blur effect using a box-shaped convolution kernel, with a configurable * size for each dimension of the kernel and a number of passes to control * the quality of the blur. */ -public class BoxBlur extends CoreEffect { +public class BoxBlur extends LinearConvolveCoreEffect { private final BoxBlurState state = new BoxBlurState(); @@ -54,7 +55,7 @@ * */ public BoxBlur() { - this(1, 1); + this(1.0f, 1.0f); } /** @@ -72,7 +73,7 @@ * @throws IllegalArgumentException if either {@code hsize} * or {@code vsize} is outside the allowable range */ - public BoxBlur(int hsize, int vsize) { + public BoxBlur(float hsize, float vsize) { this(hsize, vsize, 1, DefaultInput); } @@ -93,7 +94,7 @@ * or {@code vsize} or {@code passes} * is outside the allowable range */ - public BoxBlur(int hsize, int vsize, int passes) { + public BoxBlur(float hsize, float vsize, int passes) { this(hsize, vsize, passes, DefaultInput); } @@ -111,7 +112,7 @@ * or {@code vsize} or {@code passes} * is outside the allowable range */ - public BoxBlur(int hsize, int vsize, int passes, Effect input) { + public BoxBlur(float hsize, float vsize, int passes, Effect input) { super(input); setHorizontalSize(hsize); setVerticalSize(vsize); @@ -119,7 +120,7 @@ } @Override - Object getState() { + LinearConvolveKernel getState() { return state; } @@ -149,7 +150,7 @@ * * @return the horizontal size of the effect kernel */ - public int getHorizontalSize() { + public float getHorizontalSize() { return state.getHsize(); } @@ -166,8 +167,7 @@ * @throws IllegalArgumentException if {@code hsize} * is outside the allowable range */ - public void setHorizontalSize(int hsize) { - int old = state.getHsize(); + public final void setHorizontalSize(float hsize) { state.setHsize(hsize); } @@ -176,7 +176,7 @@ * * @return the vertical size of the effect kernel */ - public int getVerticalSize() { + public float getVerticalSize() { return state.getVsize(); } @@ -193,8 +193,7 @@ * @throws IllegalArgumentException if {@code vsize} * is outside the allowable range */ - public void setVerticalSize(int vsize) { - int old = state.getVsize(); + public final void setVerticalSize(float vsize) { state.setVsize(vsize); } @@ -224,8 +223,7 @@ * @throws IllegalArgumentException if {@code passes} is outside the * allowable range */ - public void setPasses(int passes) { - int old = state.getBlurPasses(); + public final void setPasses(int passes) { state.setBlurPasses(passes); } @@ -257,39 +255,6 @@ } @Override - public ImageData filterImageDatas(FilterContext fctx, - BaseTransform transform, - Rectangle outputClip, - ImageData... inputs) - { - return state.filterImageDatas(this, fctx, transform, outputClip, inputs); - } - - @Override - public boolean operatesInUserSpace() { - return true; - } - - @Override - protected Rectangle getInputClip(int inputIndex, - BaseTransform transform, - Rectangle outputClip) - { - // A blur needs as much "fringe" data from its input as it creates - // around its output so we use the same expansion as is used in the - // result bounds. - if (outputClip != null) { - int hgrow = state.getKernelSize(0) / 2; - int vgrow = state.getKernelSize(1) / 2; - if ((hgrow | vgrow) != 0) { - outputClip = new Rectangle(outputClip); - outputClip.grow(hgrow, vgrow); - } - } - return outputClip; - } - - @Override public boolean reducesOpaquePixels() { if (!state.isNop()) { return true; --- old/modules/graphics/src/main/java/com/sun/scenario/effect/BoxShadow.java 2014-02-21 17:01:59.000000000 -0800 +++ new/modules/graphics/src/main/java/com/sun/scenario/effect/BoxShadow.java 2014-02-21 17:01:59.000000000 -0800 @@ -33,6 +33,7 @@ import com.sun.javafx.geom.transform.BaseTransform; import com.sun.scenario.effect.impl.Renderer; import com.sun.scenario.effect.impl.state.BoxShadowState; +import com.sun.scenario.effect.impl.state.LinearConvolveKernel; /** * A shadow effect using a box-shaped convolution kernel, with a configurable @@ -121,7 +122,7 @@ } @Override - Object getState() { + LinearConvolveKernel getState() { return state; } @@ -151,7 +152,7 @@ * * @return the horizontal size of the effect kernel */ - public int getHorizontalSize() { + public float getHorizontalSize() { return state.getHsize(); } @@ -168,8 +169,7 @@ * @throws IllegalArgumentException if {@code hsize} * is outside the allowable range */ - public void setHorizontalSize(int hsize) { - int old = state.getHsize(); + public final void setHorizontalSize(float hsize) { state.setHsize(hsize); } @@ -178,7 +178,7 @@ * * @return the vertical size of the effect kernel */ - public int getVerticalSize() { + public float getVerticalSize() { return state.getVsize(); } @@ -195,8 +195,7 @@ * @throws IllegalArgumentException if {@code vsize} * is outside the allowable range */ - public void setVerticalSize(int vsize) { - int old = state.getVsize(); + public final void setVerticalSize(float vsize) { state.setVsize(vsize); } @@ -226,8 +225,7 @@ * @throws IllegalArgumentException if {@code passes} is outside the * allowable range */ - public void setPasses(int passes) { - int old = state.getBlurPasses(); + public final void setPasses(int passes) { state.setBlurPasses(passes); } @@ -252,8 +250,7 @@ * @param color the shadow color * @throws IllegalArgumentException if {@code color} is null */ - public void setColor(Color4f color) { - Color4f old = state.getShadowColor(); + public final void setColor(Color4f color) { state.setShadowColor(color); } @@ -288,8 +285,7 @@ * @throws IllegalArgumentException if {@code spread} is outside the * allowable range */ - public void setSpread(float spread) { - float old = state.getSpread(); + public final void setSpread(float spread) { state.setSpread(spread); } @@ -385,39 +381,6 @@ } @Override - public ImageData filterImageDatas(FilterContext fctx, - BaseTransform transform, - Rectangle outputClip, - ImageData... inputs) - { - return state.filterImageDatas(this, fctx, transform, outputClip, inputs); - } - - @Override - public boolean operatesInUserSpace() { - return true; - } - - @Override - protected Rectangle getInputClip(int inputIndex, - BaseTransform transform, - Rectangle outputClip) - { - // A blur needs as much "fringe" data from its input as it creates - // around its output so we use the same expansion as is used in the - // result bounds. - if (outputClip != null) { - int hgrow = state.getKernelSize(0) / 2; - int vgrow = state.getKernelSize(1) / 2; - if ((hgrow | vgrow) != 0) { - outputClip = new Rectangle(outputClip); - outputClip.grow(hgrow, vgrow); - } - } - return outputClip; - } - - @Override public boolean reducesOpaquePixels() { return true; } --- old/modules/graphics/src/main/java/com/sun/scenario/effect/MotionBlur.java 2014-02-21 17:02:00.000000000 -0800 +++ new/modules/graphics/src/main/java/com/sun/scenario/effect/MotionBlur.java 2014-02-21 17:02:00.000000000 -0800 @@ -32,13 +32,14 @@ import com.sun.javafx.geom.Rectangle; import com.sun.javafx.geom.transform.BaseTransform; import com.sun.scenario.effect.impl.Renderer; +import com.sun.scenario.effect.impl.state.LinearConvolveKernel; import com.sun.scenario.effect.impl.state.MotionBlurState; /** * A motion blur effect using a Gaussian convolution kernel, with a * configurable radius and angle. */ -public class MotionBlur extends CoreEffect { +public class MotionBlur extends LinearConvolveCoreEffect { private MotionBlurState state = new MotionBlurState(); @@ -89,7 +90,7 @@ } @Override - Object getState() { + LinearConvolveKernel getState() { return state; } @@ -140,7 +141,6 @@ * allowable range */ public void setRadius(float radius) { - float old = state.getRadius(); state.setRadius(radius); } @@ -165,7 +165,6 @@ * @param angle the angle of the motion effect, in radians */ public void setAngle(float angle) { - float old = state.getAngle(); state.setAngle(angle); } @@ -193,39 +192,6 @@ } @Override - public ImageData filterImageDatas(FilterContext fctx, - BaseTransform transform, - Rectangle outputClip, - ImageData... inputs) - { - return state.filterImageDatas(this, fctx, transform, outputClip, inputs); - } - - @Override - public boolean operatesInUserSpace() { - return true; - } - - @Override - protected Rectangle getInputClip(int inputIndex, - BaseTransform transform, - Rectangle outputClip) - { - // A blur needs as much "fringe" data from its input as it creates - // around its output so we use the same expansion as is used in the - // result bounds. - if (outputClip != null) { - int hpad = state.getHPad(); - int vpad = state.getVPad(); - if ((hpad | vpad) != 0) { - outputClip = new Rectangle(outputClip); - outputClip.grow(hpad, vpad); - } - } - return outputClip; - } - - @Override public boolean reducesOpaquePixels() { if (!state.isNop()) { return true; --- /dev/null 2014-02-21 17:02:01.000000000 -0800 +++ new/modules/graphics/src/main/java/com/sun/scenario/effect/impl/state/LinearConvolveRenderState.java 2014-02-21 17:02:00.000000000 -0800 @@ -0,0 +1,335 @@ +/* + * Copyright (c) 2014, 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.state; + +import com.sun.javafx.geom.Rectangle; +import com.sun.scenario.effect.Color4f; +import com.sun.scenario.effect.FilterContext; +import com.sun.scenario.effect.ImageData; +import com.sun.scenario.effect.impl.EffectPeer; +import com.sun.scenario.effect.impl.Renderer; +import java.nio.FloatBuffer; + +/** + * The {@code LinearConvolveRenderState} object manages the strategies of + * applying a 1 or 2 pass linear convolution to an input and calculates the + * necessary data for the filter shader to compute the convolution. + * The object is constructed based on the transform that was provided for + * the entire filter operation and determines its strategy. + * Methods prefixed by {@code getInput*()} return information about the + * general plan for obtaining and managing the input source image. + * After the input effect is called with the information from the + * {@code getInput*()} methods and its result {@code ImageData} is obtained, + * the {@code validatePassInput()} method is used to examine the size and + * transform of the supplied input and determine the parameters needed to + * perform the convolution for the first pass. + * Once validated, the methods prefixed by {@code getPass*()} return information + * for applying the convolution for that validated pass. + * If necessary, the {@code validatePassInput()} method is called on the + * results of the first pass to calculate further data for the second pass. + * Finally the {@code getResultTransform()} method is used to possibly transform + * the final resulting {@code ImageData} of the last pass. + */ +public abstract class LinearConvolveRenderState implements RenderState { + public static final int MAX_KERNEL_SIZE = 128; + + static final float MIN_EFFECT_RADIUS = 1.0f / 256.0f; + + static final float[] BLACK_COMPONENTS = + Color4f.BLACK.getPremultipliedRGBComponents(); + + public enum PassType { + /** + * The kernel on this pass will be applied horizontally with + * the kernel centered symmetrically around each pixel. + * The specific conditions indicated by this type are: + * + */ + HORIZONTAL_CENTERED, + + /** + * The kernel on this pass will be applied vertically with + * the kernel centered symmetrically around each pixel. + * The specific conditions indicated by this type are: + * + */ + VERTICAL_CENTERED, + + /** + * The kernel on this pass can be applied in any direction or with + * any kind of offset. + * No assumptions are made about the offset and delta of the kernel + * vector. + */ + GENERAL_VECTOR, + }; + + /** + * Returns the peer sample count for a given kernel size. There are + * only a few peers defined to operate on specific sizes of convolution + * kernel. If there are peers defined only for kernel sizes of 8 and 16 + * and a given effect has a linear convolution kernel with 5 weights, + * then the peer for size 8 will be used and the buffer of weights must + * be padded out to the appropriate size with 0s so that the shader + * constant pool will be fully initialized and the extra unneeded + * convolution samples will be ignored by the 0 weights. + * + * @param ksize the number of computed convolution kernel weights + * @return the number of convolution weights which will be applied by + * the associated peer. + */ + public static int getPeerSize(int ksize) { + if (ksize < 32) return ((ksize + 3) & (~3)); + if (ksize <= MAX_KERNEL_SIZE) return ((ksize + 31) & (~31)); + throw new RuntimeException("No peer available for kernel size: "+ksize); + } + + /** + * Returns true if summing v over size pixels ends up close enough to + * 0.0 that we will not have shifted the sampling by enough to see any + * changes. + * "Close enough" in this context is measured by whether or not using + * the coordinate in a linear interpolating sampling operation on 8-bit + * per sample images will cause the next pixel over to be blended in. + * + * @param v the value being summed across the pixels + * @param size the number of pixels being summed across + * @return true if the accumulated value will be negligible + */ + static boolean nearZero(float v, int size) { + return (Math.abs(v * size) < 1.0/512.0); + } + + /** + * Returns true if summing v over size pixels ends up close enough to + * size.0 that we will not have shifted the sampling by enough to see any + * changes. + * "Close enough" in this context is measured by whether or not using + * the coordinate in a linear interpolating sampling operation on 8-bit + * per sample images will cause the next pixel over to be blended in. + * + * @param v the value being summed across the pixels + * @param size the number of pixels being summed across + * @return true if the accumulated value will be close enough to size + */ + static boolean nearOne(float v, int size) { + return (Math.abs(v * size - size) < 1.0/512.0); + } + + /** + * Returns true if this is a shadow convolution operation where a + * constant color is substituted for the color components of the + * output. + * This value is dependent only on the original {@code Effect} from which + * this {@code RenderState} was instantiated and does not vary as the + * filter operation progresses. + * + * @return true if this is a shadow operation + */ + public abstract boolean isShadow(); + + /** + * Returns the {@code Color4f} representing the shadow color if this + * is a shadow operation. + * This value is dependent only on the original {@code Effect} from which + * this {@code RenderState} was instantiated and does not vary as the + * filter operation progresses. + * + * @return the {@code Color4f} for the shadow color, or null + */ + public abstract Color4f getShadowColor(); + + /** + * Returns the size of the desired convolution kernel for the given pass + * as it would be applied in the coordinate space indicated by the + * {@link #getInputKernelSize(int)} method. + * This value is calculated at the start of the render operation and + * does not vary as the filter operation progresses, but it may not + * represent the actual kernel size used when the indicated pass actually + * occurs if the {@link #validatePassInput()} method needs to choose + * different values when it sees the incoming image source. + * + * @param pass the pass for which the intended kernel size is desired + * @return the intended kernel size for the requested pass + */ + public abstract int getInputKernelSize(int pass); + + /** + * Returns true if the resulting operation is globally a NOP operation. + * This condition is calculated at the start of the render operation and + * is based on whether the perturbations of the convolution kernel would + * be noticeable at all in the coordinate space of the output. + * + * @return true if the operation is a global NOP + */ + public abstract boolean isNop(); + + /** + * Validates the {@code RenderState} object for a given pass of the + * convolution. + * The supplied source image is provided so that the {@code RenderState} + * object can determine if it needs to change its strategy for how the + * convolution operation will be performed and to scale its data for + * the {@code getPass*()} methods relative to the source dimensions and + * transform. + * + * @param src the {@code ImageData} object supplied by the source effect + * @param pass the pass of the operation being applied (usually horizontal + * for pass 0 and vertical for pass 1) + * @return the {@code ImageData} to be used for the actual convolution + * operation + */ + public abstract ImageData validatePassInput(ImageData src, int pass); + + /** + * Returns true if the operation of the currently validated pass would + * be a NOP operation. + * + * @return true if the current pass is a NOP + */ + public abstract boolean isPassNop(); + + /** + * Return the {@code EffectPeer} to be used to perform the currently + * validated pass of the convolution operation, or null if this pass + * is a NOP. + * + * @param r the {@code Renderer} being used for this filter operation + * @param fctx the {@code FilterContext} being used for this filter operation + * @return the {@code EffectPeer} to use for this pass, or null + */ + public EffectPeer getPassPeer(Renderer r, FilterContext fctx) { + if (isPassNop()) { + return null; + } + int ksize = getPassKernelSize(); + int psize = getPeerSize(ksize); + String opname = isShadow() ? "LinearConvolveShadow" : "LinearConvolve"; + return r.getPeerInstance(fctx, opname, psize); + } + + /** + * Returns the size of the scaled result image needed to hold the output + * for the currently validated pass with the indicated input dimensions. + * The image may be further scaled after the shader operation is through + * to obtain the final result bounds. + * This value is only of use to the actual shader to understand exactly + * how much room to allocate for the shader result. + * + * @param srcdimension the bounds of the input image + * @return the bounds of the result image + */ + public abstract Rectangle getPassResultBounds(Rectangle srcdimension); + + /** + * Return a hint about the way that the weights will be applied to the + * pixels for the currently validated pass. + * + * @return the appropriate {@link PassType} that describes the filtering + * operation for this pass of the algorithm + */ + public PassType getPassType() { + return PassType.GENERAL_VECTOR; + } + + /** + * A {@link FloatBuffer} padded out to the required size as specified by + * the {@link #getPeerSize()} method filled with the convolution weights + * needed for the currently validated pass. + * + * @return a {@code FloatBuffer} containing the kernel convolution weights + */ + public abstract FloatBuffer getPassWeights(); + + /** + * Returns the maximum number of valid float4 elements that should be + * referenced from the buffer returned by getWeights() for the currently + * validated pass. + * + * @return the maximum number of valid float4 elements in the weights buffer + */ + public abstract int getPassWeightsArrayLength(); + + /** + * Returns an array of 4 floats used to initialize a float4 Shader + * constant with the relative starting location of the first weight + * in the convolution kernel and the incremental offset between each + * sample to be weighted and accumulated. The values are stored in + * the array in the following order: + *
+     *     shadervec.x = vector[0] = incdx // X delta between subsequent samples
+     *     shadervec.y = vector[1] = incdy // Y delta between subsequent samples
+     *     shadervec.z = vector[2] = startdx // X offset to first convolution sample
+     *     shadervec.w = vector[3] = startdy // Y offset to first convolution sample
+     * 
+ * These values are used in the shader loop as follows: + *
+     *     samplelocation = outputpixellocation.xy + shadervec.zw;
+     *     for (each weight) {
+     *         sum += weight * sample(samplelocation.xy);
+     *         samplelocation.xy += shadervec.xy;
+     *     }
+     * 
+ * The values are relative to the texture coordinate space which are + * normalized to the range [0,1] over the source texture. + * + * @return an array of 4 floats representing + * {@code [ incdx, incdy, startdx, startdy ]} + */ + public abstract float[] getPassVector(); + + /** + * For a shadow convolution operation, return the 4 float versions of + * the color components, in the range {@code [0, 1]} for the shadow color + * to be substituted for the input colors. + * This method will only be called if {@link #isShadow()} returns true. + * + * @return the array of 4 floats representing the shadow color components + */ + public abstract float[] getPassShadowColorComponents(); + + /** + * Returns the appropriate kernel size for the pass that was last + * validated using validateInput(). + * + * @return the pixel kernel size of the current pass + */ + public abstract int getPassKernelSize(); +} --- /dev/null 2014-02-21 17:02:01.000000000 -0800 +++ new/modules/graphics/src/main/java/com/sun/scenario/effect/impl/state/GaussianRenderState.java 2014-02-21 17:02:01.000000000 -0800 @@ -0,0 +1,538 @@ +/* + * Copyright (c) 2014, 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.state; + +import com.sun.javafx.geom.Rectangle; +import com.sun.javafx.geom.transform.Affine2D; +import com.sun.javafx.geom.transform.BaseTransform; +import com.sun.javafx.geom.transform.NoninvertibleTransformException; +import com.sun.scenario.effect.Color4f; +import com.sun.scenario.effect.Filterable; +import com.sun.scenario.effect.ImageData; +import com.sun.scenario.effect.impl.BufferUtil; +import java.nio.FloatBuffer; + +/** + */ +public class GaussianRenderState extends LinearConvolveRenderState { + public static final float MAX_RADIUS = (MAX_KERNEL_SIZE - 1) / 2; + + // General variables representing the convolve operation + private boolean isShadow; + private Color4f shadowColor; + private float spread; + + // Values specific to this operation, calculated from the rendering context + private EffectCoordinateSpace space; + private BaseTransform inputtx; + private BaseTransform resulttx; + private float inputRadiusX; // expected radius given inputtx + private float inputRadiusY; + private float spreadPass; + + // Values specific to a given filter pass + private int validatedPass; + private PassType passType; + private float passRadius; // actual radius for src ImageData + private FloatBuffer weights; + private float samplevectors[]; // dx, dy for pixel sampling, both passes + private float weightsValidRadius; + private float weightsValidSpread; + + static FloatBuffer getGaussianWeights(FloatBuffer weights, + int pad, + float radius, + float spread) + { + int r = pad; + int klen = (r * 2) + 1; + if (weights == null) { + weights = BufferUtil.newFloatBuffer(128); + } + weights.clear(); + float sigma = radius / 3; + float sigma22 = 2 * sigma * sigma; + if (sigma22 < Float.MIN_VALUE) { + // Avoid divide by 0 below (it can generate NaN values). + sigma22 = Float.MIN_VALUE; + } + float total = 0.0F; + for (int row = -r; row <= r; row++) { + float kval = (float) Math.exp(-(row * row) / sigma22); + weights.put(kval); + total += kval; + } + total += (weights.get(0) - total) * spread; + for (int i = 0; i < klen; i++) { + weights.put(i, weights.get(i) / total); + } + int limit = getPeerSize(klen); + while (weights.position() < limit) { + weights.put(0.0F); + } + weights.limit(limit); + weights.rewind(); + return weights; + } + + /** + * Constructs a {@link RenderState} for a 2 dimensional Gaussian convolution. + * + * @param xradius the Gaussian radius along the user space X axis + * @param yradius the Gaussian radius along the user space Y axis + * @param spread the spread amount + * @param isShadow true if this is a shadow operation + * @param shadowColor the color of the shadow operation + * @param filtertx the transform applied to the filter operation + */ + public GaussianRenderState(float xradius, float yradius, float spread, + boolean isShadow, Color4f shadowColor, BaseTransform filtertx) + { + /* + * The operation starts as a description of the size of a (pair of) + * Gaussian kernels measured relative to that user space coordinate + * system and to be applied horizontally and vertically in that same + * space. The presence of a filter transform can mean that the + * direction we apply the gaussian convolutions could change as well + * as the new size of that Gaussian distribution curve relative to + * the pixels produced under that transform. + * + * We will track the direction and size of the Gaussian as we traverse + * different coordinate spaces with the intent that eventually we + * will perform the math of the convolution with weights calculated + * for one sample per pixel in the indicated direction and applied as + * closely to the intended final filter transform as we can achieve + * with the following caveats: + * + * - There is a maximum kernel size that the hardware pixel shaders + * can apply so we will try to keep the scaling of the filtered + * pixels low enough that we do not exceed that data limitation. + * + * - Software prefers to apply these weights along horizontal and + * vertical vectors, but can apply them in an arbitrary direction + * if need be. + * + * - If the Gaussian kernel is large enough, then applying a smaller + * Gaussian kernel to a downscaled input is indistinguishable to + * applying the larger kernel to a larger scaled input. Our maximum + * kernel size is large enough for this effect to be hidden if we + * max out the kernel. + * + * - We can tell the inputs what transform we want them to use, but + * they can always produce output under a different transform and + * then return a result with a "post-processing" trasnform to be + * applied (as we are doing here ourselves). Thus, we can plan + * how we want to apply the convolution weights and samples here, + * but we will have to reevaluate our actions when the actual + * input pixels are created later. + * + * - If we are blurring enough to trigger the MAX_RADIUS exceptions + * then we can blur at a nice axis-aligned orientation (which is + * preferred for the software versions of the shaders) and perform + * any rotation and skewing in the final post-processing result + * transform as that amount of blurring will quite effectively cover + * up any distortion that would occur by not rendering at the + * appropriate angles. + * + * To achieve this we start out with untransformed sample vectors + * which are unit vectors along the X and Y axes. We transform them + * into the requested filter space, adjust the kernel size and see + * if we can support that kernel size. If it is too large of a + * projected kernel, then we request the input at a smaller scale + * and perform a maximum kernel convolution on it and then indicate + * that this result will need to be scaled by the caller. When this + * method is done we will have computed what we need to do to the + * input pixels when they come in if the inputtx was honored, otherwise + * we may have to adjust the values further in {@link @validateInput()}. + */ + this.isShadow = isShadow; + this.shadowColor = shadowColor; + this.spread = spread; + double mxx = filtertx.getMxx(); + double mxy = filtertx.getMxy(); + double myx = filtertx.getMyx(); + double myy = filtertx.getMyy(); + // Transformed unit axis vectors are essentially (mxx, myx) and (mxy, myy). + double txScaleX = Math.hypot(mxx, myx); + double txScaleY = Math.hypot(mxy, myy); + boolean scaled = false; + float scaledRadiusX = (float) (xradius * txScaleX); + float scaledRadiusY = (float) (yradius * txScaleY); + if (scaledRadiusX < MIN_EFFECT_RADIUS && scaledRadiusY < MIN_EFFECT_RADIUS) { + // Entire blur is essentially a NOP in device space, we should + // set up the values to force NOP processing rather than relying + // on calculations to do it for us. + this.inputRadiusX = 0.0f; + this.inputRadiusY = 0.0f; + this.spreadPass = 0; + this.space = EffectCoordinateSpace.RenderSpace; + this.inputtx = filtertx; + this.resulttx = BaseTransform.IDENTITY_TRANSFORM; + this.samplevectors = new float[] { 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f }; + } else { + if (scaledRadiusX > MAX_RADIUS) { + scaledRadiusX = MAX_RADIUS; + txScaleX = MAX_RADIUS / xradius; + scaled = true; + } + if (scaledRadiusY > MAX_RADIUS) { + scaledRadiusY = MAX_RADIUS; + txScaleY = MAX_RADIUS / yradius; + scaled = true; + } + this.inputRadiusX = (float) scaledRadiusX; + this.inputRadiusY = (float) scaledRadiusY; + // We need to apply the spread on only one pass + // Prefer pass1 if r1 is not tiny (or at least bigger than r0) + // Otherwise use pass 0 so that it doesn't disappear + this.spreadPass = (inputRadiusY > 1f || inputRadiusY >= inputRadiusX) ? 1 : 0; + if (scaled) { + this.space = EffectCoordinateSpace.CustomSpace; + this.inputtx = BaseTransform.getScaleInstance(txScaleX, txScaleY); + this.resulttx = filtertx + .copy() + .deriveWithScale(1.0 / txScaleX, 1.0 / txScaleY, 1.0); + // assert resulttx.deriveWithConcatenation(inputtx).equals(filtertx) + this.samplevectors = new float[] { 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f }; + } else { + this.space = EffectCoordinateSpace.RenderSpace; + this.inputtx = filtertx; + this.resulttx = BaseTransform.IDENTITY_TRANSFORM; + // These values should produce 2 normalized unit vectors in the + // direction of the transformed axis vectors. + this.samplevectors = new float[] { (float) (mxx / txScaleX), + (float) (myx / txScaleX), + (float) (mxy / txScaleY), + (float) (myy / txScaleY), + 0.0f, 0.0f }; + } + } + // If the input honors our requested transforms then samplevectors + // will be the unit vectors in the correct direction to sample by + // pixel distances in the input texture and the inputRadii will be + // the correct Gaussian dimension to blur them. + } + + /** + * Constructs a {@link RenderState} for a single dimensional, directional + * Gaussian convolution (as for a MotionBlur operation). + * + * @param radius the Gaussian radius along the indicated direction + * @param dx the delta X of the unit vector along which to apply the convolution + * @param dy the delta Y of the unit vector along which to apply the convolution + * @param filtertx the transform applied to the filter operation + */ + public GaussianRenderState(float radius, float dx, float dy, BaseTransform filtertx) { + // This is a special case of the above 2 dimensional Gaussian, most of + // the same strategies and caveats apply except as relevant to our + // directional single-axis peculiarities + this.isShadow = false; + this.spread = 0.0f; + double mxx = filtertx.getMxx(); + double mxy = filtertx.getMxy(); + double myx = filtertx.getMyx(); + double myy = filtertx.getMyy(); + // Manually transform the unit vector and determine its added "scale" + double tdx = mxx * dx + mxy * dy; + double tdy = myx * dx + myy * dy; + double txScale = Math.hypot(tdx, tdy); + boolean scaled = false; + float scaledRadius = (float) (radius * txScale); + if (scaledRadius < MIN_EFFECT_RADIUS) { + this.inputRadiusX = 0.0f; + this.inputRadiusY = 0.0f; + this.spreadPass = 0; + this.space = EffectCoordinateSpace.RenderSpace; + this.inputtx = filtertx; + this.resulttx = BaseTransform.IDENTITY_TRANSFORM; + this.samplevectors = new float[] { 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f }; + } else { + if (scaledRadius > MAX_RADIUS) { + scaledRadius = MAX_RADIUS; + txScale = MAX_RADIUS / radius; + scaled = true; + } + this.inputRadiusX = scaledRadius; + this.inputRadiusY = 0.0f; + this.spreadPass = 0; + if (scaled) { + // Since this is a highly directed blur and any change in + // scaling perpendicular to the blur angle could result in + // visible artifacts not absorbed by the Gaussian convolution, + // we will try to focus any changes in intermediate scaling + // on just that direction that the blur is applied along. + // We will need to calculate 2 disjoint scale factors, one + // along the blur (already calculated in txScale) and one + // perpendicular to that vector, then we will provide the + // inputs with an animorphically scaled coordinate system + // that uses a smaller scale along the direction of the blur + // and as close as possible to the original scale in the + // orthogonal direction... + // Determine the orthogonal scale factor: + double odx = mxy * dx - mxx * dy; + double ody = myy * dx - myx * dy; + double txOScale = Math.hypot(odx, ody); + this.space = EffectCoordinateSpace.CustomSpace; + Affine2D a2d = new Affine2D(); + a2d.scale(txScale, txOScale); + a2d.rotate(dx, -dy); + BaseTransform a2di; + try { + a2di = a2d.createInverse(); + } catch (NoninvertibleTransformException ex) { + a2di = BaseTransform.IDENTITY_TRANSFORM; + } + this.inputtx = a2d; + this.resulttx = filtertx + .copy() + .deriveWithConcatenation(a2di); + // assert resulttx.deriveWithConcatenation(inputtx).equals(filtertx) + this.samplevectors = new float[] { 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f }; + } else { + this.space = EffectCoordinateSpace.RenderSpace; + this.inputtx = filtertx; + this.resulttx = BaseTransform.IDENTITY_TRANSFORM; + // These values should produce a normalized unit vector in the + // direction of the transformed sample vector. + this.samplevectors = new float[] { (float) (tdx / txScale), + (float) (tdy / txScale), + 0.0f, 0.0f, 0.0f, 0.0f }; + } + } + // If the input honors our requested transforms then samplevectors + // will be the unit vector in the correct direction to sample by + // pixel distances in the input texture and the inputRadiusX will be + // the correct Gaussian dimension to blur them. + // The second vector in samplevectors is ignored since the associated + // inputRadiusY is hard-coded to 0. + } + + @Override + public boolean isShadow() { + return isShadow; + } + + @Override + public Color4f getShadowColor() { + return shadowColor; + } + + @Override + public float[] getPassShadowColorComponents() { + return (validatedPass == 0) + ? BLACK_COMPONENTS + : shadowColor.getPremultipliedRGBComponents(); + } + + @Override + public EffectCoordinateSpace getEffectTransformSpace() { + return space; + } + + @Override + public BaseTransform getInputTransform(BaseTransform filterTransform) { + return inputtx; + } + + @Override + public BaseTransform getResultTransform(BaseTransform filterTransform) { + return resulttx; + } + + @Override + public Rectangle getInputClip(int i, Rectangle filterClip) { + if (filterClip != null) { + double dx0 = samplevectors[0] * inputRadiusX; + double dy0 = samplevectors[1] * inputRadiusX; + double dx1 = samplevectors[2] * inputRadiusY; + double dy1 = samplevectors[3] * inputRadiusY; + int padx = (int) Math.ceil(dx0+dx1); + int pady = (int) Math.ceil(dy0+dy1); + if ((padx | pady) != 0) { + filterClip = new Rectangle(filterClip); + filterClip.grow(padx, pady); + } + } + return filterClip; + } + + @Override + public ImageData validatePassInput(ImageData src, int pass) { + this.validatedPass = pass; + Filterable f = src.getUntransformedImage(); + BaseTransform srcTx = src.getTransform(); + float iRadius = (pass == 0) ? inputRadiusX : inputRadiusY; + int vecindex = pass * 2; + if (srcTx.isTranslateOrIdentity()) { + // The input effect gave us exactly what we wanted, proceed as planned + this.passRadius = iRadius; + samplevectors[4] = samplevectors[vecindex]; + samplevectors[5] = samplevectors[vecindex+1]; + if (validatedPass == 0) { + if ( nearOne(samplevectors[4], f.getPhysicalWidth()) && + nearZero(samplevectors[5], f.getPhysicalWidth())) + { + passType = PassType.HORIZONTAL_CENTERED; + } else { + passType = PassType.GENERAL_VECTOR; + } + } else { + if (nearZero(samplevectors[4], f.getPhysicalHeight()) && + nearOne(samplevectors[5], f.getPhysicalHeight())) + { + passType = PassType.VERTICAL_CENTERED; + } else { + passType = PassType.GENERAL_VECTOR; + } + } + } else { + // The input produced a texture that requires transformation, + // reevaluate our radii. + // First (inverse) transform our sample vectors from the intended + // srcTx space back into the actual pixel space of the src texture. + // Then evaluate their length and attempt to absorb as much of any + // implicit scaling that would happen into our final pixelRadii, + // but if we overflow the maximum supportable radius then we will + // just have to sample sparsely with a longer than unit vector. + // REMIND: we should also downsample the texture by powers of + // 2 if our sampling will be more sparse than 1 sample per 2 + // pixels. + passType = PassType.GENERAL_VECTOR; + try { + srcTx.inverseDeltaTransform(samplevectors, vecindex, samplevectors, 4, 1); + } catch (NoninvertibleTransformException ex) { + this.passRadius = 0.0f; + samplevectors[4] = samplevectors[5] = 0.0f; + return src; + } + double srcScale = Math.hypot(samplevectors[4], samplevectors[5]); + float pRad = (float) (iRadius * srcScale); + if (pRad > MAX_RADIUS) { + pRad = MAX_RADIUS; + srcScale = MAX_RADIUS / iRadius; + } + this.passRadius = pRad; + // For a pixelRadius that was less than MAX_RADIUS, the following + // lines renormalize the un-transformed vectors back into unit + // vectors in the proper direction and we absorbed their length + // into the pixelRadius that we will apply for the Gaussian weights. + // If we clipped the pixelRadius to MAX_RADIUS, then they will not + // actually end up as unit vectors, but they will represent the + // proper sampling deltas for the indicated radius (which should + // be MAX_RADIUS in that case). + samplevectors[4] /= srcScale; + samplevectors[5] /= srcScale; + } + samplevectors[4] /= f.getPhysicalWidth(); + samplevectors[5] /= f.getPhysicalHeight(); + return src; + } + + @Override + public Rectangle getPassResultBounds(Rectangle srcdimension) { + // Note that the pass vector and the pass radius may be adjusted for + // a transformed input, but our output will be in the untransformed + // "filter" coordinate space so we need to use the "input" values that + // are in that same coordinate space. + double r = (validatedPass == 0) ? inputRadiusX : inputRadiusY; + int i = validatedPass * 2; + double dx = samplevectors[i+0] * r; + double dy = samplevectors[i+1] * r; + int padx = (int) Math.ceil(Math.abs(dx)); + int pady = (int) Math.ceil(Math.abs(dy)); + Rectangle ret = new Rectangle(srcdimension); + ret.grow(padx, pady); + return ret; + } + + @Override + public PassType getPassType() { + return passType; + } + + @Override + public float[] getPassVector() { + float xoff = samplevectors[4]; // / srcNativeBounds.width; + float yoff = samplevectors[5]; // / srcNativeBounds.height; + int ksize = getPassKernelSize(); + int center = ksize / 2; + float ret[] = new float[4]; + ret[0] = xoff; + ret[1] = yoff; + ret[2] = -center * xoff; + ret[3] = -center * yoff; + return ret; + } + + @Override + public int getPassWeightsArrayLength() { + validateWeights(); + return weights.limit() / 4; + } + + @Override + public FloatBuffer getPassWeights() { + validateWeights(); + weights.rewind(); + return weights; + } + + @Override + public int getInputKernelSize(int pass) { + return 1 + 2 * (int) Math.ceil((pass == 0) ? inputRadiusX : inputRadiusY); + } + + @Override + public int getPassKernelSize() { + return 1 + 2 * (int) Math.ceil(passRadius); + } + + @Override + public boolean isNop() { + if (isShadow) return false; + return inputRadiusX < MIN_EFFECT_RADIUS + && inputRadiusY < MIN_EFFECT_RADIUS; + } + + @Override + public boolean isPassNop() { + if (isShadow && validatedPass == 1) return false; + return (passRadius) < MIN_EFFECT_RADIUS; + } + + private void validateWeights() { + float r = passRadius; + float s = (validatedPass == spreadPass) ? spread : 0f; + if (weights == null || + weightsValidRadius != r || + weightsValidSpread != s) + { + weights = getGaussianWeights(weights, (int) Math.ceil(r), r, s); + weightsValidRadius = r; + weightsValidSpread = s; + } + } +} --- /dev/null 2014-02-21 17:02:02.000000000 -0800 +++ new/modules/graphics/src/main/java/com/sun/scenario/effect/impl/state/BoxRenderState.java 2014-02-21 17:02:02.000000000 -0800 @@ -0,0 +1,579 @@ +/* + * Copyright (c) 2014, 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.state; + +import com.sun.javafx.geom.Rectangle; +import com.sun.javafx.geom.transform.BaseTransform; +import com.sun.javafx.geom.transform.NoninvertibleTransformException; +import com.sun.scenario.effect.Color4f; +import com.sun.scenario.effect.Effect; +import com.sun.scenario.effect.FilterContext; +import com.sun.scenario.effect.Filterable; +import com.sun.scenario.effect.ImageData; +import com.sun.scenario.effect.impl.BufferUtil; +import com.sun.scenario.effect.impl.EffectPeer; +import com.sun.scenario.effect.impl.Renderer; +import java.nio.FloatBuffer; + +/** + * The RenderState for a box filter kernel that can be applied using a + * standard linear convolution kernel. + * A box filter has a size that represents how large of an area around a + * given pixel should be averaged. If the size is 1.0 then just the pixel + * itself should be averaged and the operation is a NOP. Values smaller + * than that are automatically treated as 1.0/NOP. + * For any odd size, the kernel weights the center pixel and an equal number + * of pixels on either side of it equally, so the weights for size 2N+1 are: + * [ {N copes of 1.0} 1.0 {N more copies of 1.0} ] + * As the size grows past that integer size, we must then add another kernel + * weight entry on both sides of the existing array of 1.0 weights and give + * them a fractional weight of half of the amount we exceeded the last odd + * size, so the weights for some size (2N+1)+e (e for epsilon) are: + * [ e/2.0 {2*N+1 copies of 1.0} e/2.0 ] + * As the size continues to grow, when it reaches the next even size, we get + * weights for size 2*N+1+1 to be: + * [ 0.5 {2*N+1 copies of 1.0} 0.5 ] + * and as the size continues to grow and approaches the next odd number, we + * see that 2(N+1)+1 == 2N+2+1 == 2N+1 + 2, so (e) approaches 2 and the + * numbers on each end of the weights array approach e/2.0 == 1.0 and we end + * up back at the pattern for an odd size again: + * [ 1.0 {2*N+1 copies of 1.0} 1.0 ] + * + * *************************** + * SOFTWARE LIMITATION CAVEAT: + * *************************** + * + * Note that the highly optimized software filters for BoxBlur/Shadow will + * actually do a very optimized "running sum" operation that is only currently + * implemented for equal weighted kernels. Also, until recently we had always + * been rounding down the size by casting it to an integer at a high level (in + * the FX layer peer synchronization code), so for now the software filters + * may only implement a subset of the above theory and new optimized loops that + * allow partial sums on the first and last values will need to be written. + * Until then we will be rounding the sizes to an odd size, but only in the + * sw loops. + */ +public class BoxRenderState extends LinearConvolveRenderState { + public static final int MAX_BOX_SIZES[] = { + getMaxSizeForKernelSize(MAX_KERNEL_SIZE, 0), + getMaxSizeForKernelSize(MAX_KERNEL_SIZE, 1), + getMaxSizeForKernelSize(MAX_KERNEL_SIZE, 2), + getMaxSizeForKernelSize(MAX_KERNEL_SIZE, 3), + }; + + private final boolean isShadow; + private final int blurPasses; + private final float spread; + private Color4f shadowColor; + + private EffectCoordinateSpace space; + private BaseTransform inputtx; + private BaseTransform resulttx; + private final float inputSizeH; + private final float inputSizeV; + private final int spreadPass; + private float samplevectors[]; + + private int validatedPass; + private float passSize; + private FloatBuffer weights; + private float weightsValidSize; + private float weightsValidSpread; + private boolean swCompatible; // true if we can use the sw peers + + public static int getMaxSizeForKernelSize(int kernelSize, int blurPasses) { + if (blurPasses == 0) { + return Integer.MAX_VALUE; + } + // Kernel sizes are always odd, so if the supplied ksize is even then + // we need to use ksize-1 to compute the max as that is actually the + // largest kernel we will be able to produce that is no larger than + // ksize for any given pass size. + int passSize = (kernelSize - 1) | 1; + passSize = ((passSize - 1) / blurPasses) | 1; + assert getKernelSize(passSize, blurPasses) <= kernelSize; + return passSize; + } + + public static int getKernelSize(int passSize, int blurPasses) { + int kernelSize = (passSize < 1) ? 1 : passSize; + kernelSize = (kernelSize-1) * blurPasses + 1; + kernelSize |= 1; + return kernelSize; + } + + public BoxRenderState(float hsize, float vsize, int blurPasses, float spread, + boolean isShadow, Color4f shadowColor, BaseTransform filtertx) + { + /* + * The operation starts as a description of the size of a (pair of) + * box filter kernels measured relative to that user space coordinate + * system and to be applied horizontally and vertically in that same + * space. The presence of a filter transform can mean that the + * direction we apply the box convolutions could change as well + * as the new size of the box summations relative to the pixels + * produced under that transform. + * + * Since the box filter is best described by the summation of a range + * of discrete pixels horizontally and vertically, and since the + * software algorithms vastly prefer applying the sums horizontally + * and vertically to groups of whole pixels using an incremental "add + * the next pixel at the front edge of the box and subtract the pixel + * that is at the back edge of the box" technique, we will constrain + * our box size to an integer size and attempt to force the inputs + * to produce an axis aligned intermediate image. But, in the end, + * we must be prepared for an arbitrary transform on the input image + * which essentially means being able to back off to an arbitrary + * invocation on the associated LinearConvolvePeer from the software + * hand-written Box peers. + * + * We will track the direction and size of the box as we traverse + * different coordinate spaces with the intent that eventually we + * will perform the math of the convolution with weights calculated + * for one sample per pixel in the indicated direction and applied as + * closely to the intended final filter transform as we can achieve + * with the following caveats (very similar to the caveats for the + * more general GaussianRenderState): + * + * - There is a maximum kernel size that the hardware pixel shaders + * can apply so we will try to keep the scaling of the filtered + * pixels low enough that we do not exceed that data limitation. + * + * - Software vastly prefers to apply these weights along horizontal + * and vertical vectors, but can apply them in an arbitrary direction + * if need be by backing off to the generic LinearConvolvePeer. + * + * - If the box is large enough, then applying a smaller box kernel + * to a downscaled input is close enough to applying the larger box + * to a larger scaled input. Our maximum kernel size is large enough + * for this effect to be hidden if we max out the kernel. + * + * - We can tell the inputs what transform we want them to use, but + * they can always produce output under a different transform and + * then return a result with a "post-processing" trasnform to be + * applied (as we are doing here ourselves). Thus, we can plan + * how we want to apply the convolution weights and samples here, + * but we will have to reevaluate our actions when the actual + * input pixels are created later. + * + * - We will try to blur at a nice axis-aligned orientation (which is + * preferred for the software versions of the shaders) and perform + * any rotation and skewing in the final post-processing result + * transform as that amount of blurring will quite effectively cover + * up any distortion that would occur by not rendering at the + * appropriate angles. + * + * To achieve this we start out with untransformed sample vectors + * which are unit vectors along the X and Y axes. We transform them + * into the requested filter space, adjust the kernel size and see + * if we can support that kernel size. If it is too large of a + * projected kernel, then we request the input at a smaller scale + * and perform a maximum kernel convolution on it and then indicate + * that this result will need to be scaled by the caller. When this + * method is done we will have computed what we need to do to the + * input pixels when they come in if the inputtx was honored, otherwise + * we may have to adjust the values further in {@link @validateInput()}. + */ + this.isShadow = isShadow; + this.shadowColor = shadowColor; + this.spread = spread; + this.blurPasses = blurPasses; + double txScaleX = Math.hypot(filtertx.getMxx(), filtertx.getMyx()); + double txScaleY = Math.hypot(filtertx.getMxy(), filtertx.getMyy()); + float fSizeH = (float) (hsize * txScaleX); + float fSizeV = (float) (vsize * txScaleY); + int maxPassSize = MAX_BOX_SIZES[blurPasses]; + if (fSizeH > maxPassSize) { + txScaleX = maxPassSize / hsize; + fSizeH = maxPassSize; + } + if (fSizeV > maxPassSize) { + txScaleY = maxPassSize / vsize; + fSizeV = maxPassSize; + } + this.inputSizeH = fSizeH; + this.inputSizeV = fSizeV; + this.spreadPass = (fSizeV > 1) ? 1 : 0; + // We always want to use an unrotated space to do our filtering, so + // we interpose our scaled-only space in all cases, but we do check + // if it happens to be equivalent (ignoring translations) to the + // original filtertx so we can avoid introducing extra layers of + // transforms. + boolean custom = (txScaleX != filtertx.getMxx() || + 0.0 != filtertx.getMyx() || + txScaleY != filtertx.getMyy() || + 0.0 != filtertx.getMxy()); + if (custom) { + this.space = EffectCoordinateSpace.CustomSpace; + this.inputtx = BaseTransform.getScaleInstance(txScaleX, txScaleY); + this.resulttx = filtertx + .copy() + .deriveWithScale(1.0 / txScaleX, 1.0 / txScaleY, 1.0); + } else { + this.space = EffectCoordinateSpace.RenderSpace; + this.inputtx = filtertx; + this.resulttx = BaseTransform.IDENTITY_TRANSFORM; + } + // assert inputtx.mxy == inputtx.myx == 0.0 + } + + public int getBoxPixelSize(int pass) { + float size = passSize; + if (size < 1.0f) size = 1.0f; + int boxsize = ((int) Math.ceil(size)) | 1; + return boxsize; + } + + public int getBlurPasses() { + return blurPasses; + } + + public float getSpread() { + return spread; + } + + @Override + public boolean isShadow() { + return isShadow; + } + + @Override + public Color4f getShadowColor() { + return shadowColor; + } + + @Override + public float[] getPassShadowColorComponents() { + return (validatedPass == 0) + ? BLACK_COMPONENTS + : shadowColor.getPremultipliedRGBComponents(); + } + + @Override + public EffectCoordinateSpace getEffectTransformSpace() { + return space; + } + + @Override + public BaseTransform getInputTransform(BaseTransform filterTransform) { + return inputtx; + } + + @Override + public BaseTransform getResultTransform(BaseTransform filterTransform) { + return resulttx; + } + + @Override + public EffectPeer getPassPeer(Renderer r, FilterContext fctx) { + if (isPassNop()) { + return null; + } + int ksize = getPassKernelSize(); + int psize = getPeerSize(ksize); + Effect.AccelType actype = r.getAccelType(); + String name; + switch (actype) { + case NONE: + case SIMD: + if (swCompatible && spread == 0.0f) { + name = isShadow() ? "BoxShadow" : "BoxBlur"; + break; + } + /* FALLS THROUGH */ + default: + name = isShadow() ? "LinearConvolveShadow" : "LinearConvolve"; + break; + } + EffectPeer peer = r.getPeerInstance(fctx, name, psize); + return peer; + } + + @Override + public Rectangle getInputClip(int i, Rectangle filterClip) { + if (filterClip != null) { + int klenh = ((int) Math.ceil(Math.max(inputSizeH, 1.0))) | 1; + int klenv = ((int) Math.ceil(Math.max(inputSizeV, 1.0))) | 1; + if ((klenh | klenv) > 1) { + filterClip = new Rectangle(filterClip); + // We actually want to grow them by (klen-1)/2, but since we + // have forced the klen sizes to be odd above, a simple integer + // divide by 2 is enough... + filterClip.grow(klenh/2, klenv/2); + } + } + return filterClip; + } + + @Override + public ImageData validatePassInput(ImageData src, int pass) { + this.validatedPass = pass; + BaseTransform srcTx = src.getTransform(); + samplevectors = new float[2]; + samplevectors[pass] = 1.0f; + float iSize = (pass == 0) ? inputSizeH : inputSizeV; + if (srcTx.isTranslateOrIdentity()) { + this.swCompatible = true; + this.passSize = iSize; + } else { + // The input produced a texture that requires transformation, + // reevaluate our box sizes. + // First (inverse) transform our sample vectors from the intended + // srcTx space back into the actual pixel space of the src texture. + // Then evaluate their length and attempt to absorb as much of any + // implicit scaling that would happen into our final pixelSizes, + // but if we overflow the maximum supportable pass size then we will + // just have to sample sparsely with a longer than unit vector. + // REMIND: we should also downsample the texture by powers of + // 2 if our sampling will be more sparse than 1 sample per 2 + // pixels. + try { + srcTx.inverseDeltaTransform(samplevectors, 0, samplevectors, 0, 1); + } catch (NoninvertibleTransformException ex) { + this.passSize = 0.0f; + samplevectors[0] = samplevectors[1] = 0.0f; + this.swCompatible = true; + return src; + } + double srcScale = Math.hypot(samplevectors[0], samplevectors[1]); + float pSize = (float) (iSize * srcScale); + pSize *= srcScale; + int maxPassSize = MAX_BOX_SIZES[blurPasses]; + if (pSize > maxPassSize) { + pSize = maxPassSize; + srcScale = maxPassSize / iSize; + } + this.passSize = pSize; + // For a pixelSize that was less than maxPassSize, the following + // lines renormalize the un-transformed vector back into a unit + // vector in the proper direction and we absorbed its length + // into the pixelSize that we will apply for the box filter weights. + // If we clipped the pixelSize to maxPassSize, then it will not + // actually end up as a unit vector, but it will represent the + // proper sampling deltas for the indicated box size (which should + // be maxPassSize in that case). + samplevectors[0] /= srcScale; + samplevectors[1] /= srcScale; + // If we are still sampling by an axis aligned unit vector, then the + // optimized software filters can still do their "incremental sum" + // magic. + // REMIND: software loops could actually do an infinitely sized + // kernel with only memory requirements getting in the way, but + // the values being tested here are constrained by the limits of + // the hardware peers. It is not clear how to fix this since we + // have to choose how to proceed before we have enough information + // to know if the inputs will be cooperative enough to assume + // software limits, and then once we get here, we may have already + // constrained ourselves into a situation where we must use the + // hardware peers. Still, there may be more "fighting" we can do + // to hold on to compatibility with the software loops perhaps? + Rectangle srcSize = src.getUntransformedBounds(); + if (pass == 0) { + this.swCompatible = nearOne(samplevectors[0], srcSize.width) + && nearZero(samplevectors[1], srcSize.width); + } else { + this.swCompatible = nearZero(samplevectors[0], srcSize.height) + && nearOne(samplevectors[1], srcSize.height); + } + } + Filterable f = src.getUntransformedImage(); + samplevectors[0] /= f.getPhysicalWidth(); + samplevectors[1] /= f.getPhysicalHeight(); + return src; + } + + @Override + public Rectangle getPassResultBounds(Rectangle srcdimension) { + // Note that the pass vector and the pass radius may be adjusted for + // a transformed input, but our output will be in the untransformed + // "filter" coordinate space so we need to use the "input" values that + // are in that same coordinate space. + Rectangle ret = new Rectangle(srcdimension); + if (validatedPass == 0) { + ret.grow(getInputKernelSize(0) / 2, 0); + } else { + ret.grow(0, getInputKernelSize(1) / 2); + } + return ret; + } + + @Override + public float[] getPassVector() { + float xoff = samplevectors[0]; + float yoff = samplevectors[1]; + int ksize = getPassKernelSize(); + int center = ksize / 2; + float ret[] = new float[4]; + ret[0] = xoff; + ret[1] = yoff; + ret[2] = -center * xoff; + ret[3] = -center * yoff; + return ret; + } + + @Override + public int getPassWeightsArrayLength() { + validateWeights(); + return weights.limit() / 4; + } + + @Override + public FloatBuffer getPassWeights() { + validateWeights(); + weights.rewind(); + return weights; + } + + private void validateWeights() { + float pSize; + if (blurPasses == 0) { + pSize = 1.0f; + } else { + pSize = passSize; + // 1.0f is the minimum size and is a NOP (each pixel averaged + // over itself) + if (pSize < 1.0f) pSize = 1.0f; + } + float passSpread = (validatedPass == spreadPass) ? spread : 0f; + if (weights != null && + weightsValidSize == pSize && + weightsValidSpread == passSpread) + { + return; + } + + // round klen up to a full pixel size and make sure it is odd so + // that we center the kernel around each pixel center (1.0 of the + // total size/weight is centered on the current pixel and then + // the remainder is split (size-1.0)/2 on each side. + // If the size is 2, then we don't want to average each pair of + // pixels together (weights: 0.5, 0.5), instead we want to take each + // pixel and average in half of each of its neighbors with it + // (weights: 0.25, 0.5, 0.25). + int klen = ((int) Math.ceil(pSize)) | 1; + int totalklen = klen; + for (int p = 1; p < blurPasses; p++) { + totalklen += klen - 1; + } + double ik[] = new double[totalklen]; + for (int i = 0; i < klen; i++) { + ik[i] = 1.0; + } + // The sum of the ik[] array is now klen, but we want the sum to + // be size. The worst case difference will be less than 2.0 since + // the klen length is the ceil of the actual size possibly bumped up + // to an odd number. Thus it can have been bumped up by no more than + // 2.0. If there is an excess, we need to take half of it out of each + // of the two end weights (first and last). + double excess = klen - pSize; + if (excess > 0.0) { + // assert (excess * 0.5 < 1.0) + ik[0] = ik[klen-1] = 1.0 - excess * 0.5; + } + int filledklen = klen; + for (int p = 1; p < blurPasses; p++) { + filledklen += klen - 1; + int i = filledklen - 1; + while (i > klen) { + double sum = ik[i]; + for (int k = 1; k < klen; k++) { + sum += ik[i-k]; + } + ik[i--] = sum; + } + while (i > 0) { + double sum = ik[i]; + for (int k = 0; k < i; k++) { + sum += ik[k]; + } + ik[i--] = sum; + } + } + // assert (filledklen == totalklen == ik.length) + double sum = 0.0; + for (int i = 0; i < ik.length; i++) { + sum += ik[i]; + } + // We need to apply the spread on only one pass + // Prefer pass1 if r1 is not trivial + // Otherwise use pass 0 so that it doesn't disappear + sum += (1.0 - sum) * passSpread; + + if (weights == null) { + // peersize(MAX_KERNEL_SIZE) rounded up to the next multiple of 4 + int maxbufsize = getPeerSize(MAX_KERNEL_SIZE); + maxbufsize = (maxbufsize + 3) & (~3); + weights = BufferUtil.newFloatBuffer(maxbufsize); + } + weights.clear(); + for (int i = 0; i < ik.length; i++) { + weights.put((float) (ik[i] / sum)); + } + int limit = getPeerSize(ik.length); + while (weights.position() < limit) { + weights.put(0f); + } + weights.limit(limit); + weights.rewind(); + } + + @Override + public int getInputKernelSize(int pass) { + float size = (pass == 0) ? inputSizeH : inputSizeV; + if (size < 1.0f) size = 1.0f; + int klen = ((int) Math.ceil(size)) | 1; + int totalklen = 1; + for (int p = 0; p < blurPasses; p++) { + totalklen += klen - 1; + } + return totalklen; + } + + @Override + public int getPassKernelSize() { + float size = passSize; + if (size < 1.0f) size = 1.0f; + int klen = ((int) Math.ceil(size)) | 1; + int totalklen = 1; + for (int p = 0; p < blurPasses; p++) { + totalklen += klen - 1; + } + return totalklen; + } + + @Override + public boolean isNop() { + if (isShadow) return false; + return (blurPasses == 0 + || (inputSizeH <= 1.0f && inputSizeV <= 1.0f)); + } + + @Override + public boolean isPassNop() { + if (isShadow && validatedPass == 1) return false; + return (blurPasses == 0 || (passSize) <= 1.0f); + } +} --- old/modules/graphics/src/main/jsl-decora/LinearConvolve.jsl 2014-02-21 17:02:03.000000000 -0800 +++ new/modules/graphics/src/main/jsl-decora/LinearConvolve.jsl 2014-02-21 17:02:03.000000000 -0800 @@ -24,39 +24,29 @@ */ << -private LinearConvolveKernel getKernel() { - return (LinearConvolveKernel) AccessHelper.getState(getEffect()); -} - -public int getPow2ScaleX(LinearConvolveKernel kernel) { - return kernel.getPow2ScaleX(); -} - -public int getPow2ScaleY(LinearConvolveKernel kernel) { - return kernel.getPow2ScaleY(); -} - public Rectangle getResultBounds(com.sun.javafx.geom.transform.BaseTransform transform, com.sun.javafx.geom.Rectangle outputClip, com.sun.scenario.effect.ImageData... inputDatas) { - return getKernel().getScaledResultBounds(inputDatas[0].getTransformedBounds(outputClip), getPass()); + return getRenderState().getPassResultBounds(inputDatas[0].getTransformedBounds(outputClip)); } private int getCount() { - return (getKernel().getScaledKernelSize(getPass()) + 3) / 4; + // The shader consumes weights 4 at a time so we need to round the + // kernel size to the next multiple of 4. + return (getRenderState().getPassKernelSize() + 3) / 4; } private float[] getOffset() { - return getKernel().getVector(getInputNativeBounds(0), getInputTransform(0), getPass()); + return getRenderState().getPassVector(); } private FloatBuffer getWeights() { - return getKernel().getWeights(getPass()); + return getRenderState().getPassWeights(); } private int getWeightsArrayLength() { - return getKernel().getWeightsArrayLength(getPass()); + return getRenderState().getPassWeightsArrayLength(); } >> @@ -68,10 +58,10 @@ // offset.w = dy offset to first weighted convolution sample param float4 offset; // value for each location in the offsets array: -// weights[i].x = weight for pos0 + offset + i*direction*4+0 -// weights[i].y = weight for pos0 + offset + i*direction*4+1 -// weights[i].z = weight for pos0 + offset + i*direction*4+2 -// weights[i].w = weight for pos0 + offset + i*direction*4+3 +// weights[i].x = weight for pos0 + offset.zw + (i*4+0)*offset.xy +// weights[i].y = weight for pos0 + offset.zw + (i*4+1)*offset.xy +// weights[i].z = weight for pos0 + offset.zw + (i*4+2)*offset.xy +// weights[i].w = weight for pos0 + offset.zw + (i*4+3)*offset.xy param float4 weights[%d]; void main() --- old/modules/graphics/src/main/jsl-decora/LinearConvolveShadow.jsl 2014-02-21 17:02:04.000000000 -0800 +++ new/modules/graphics/src/main/jsl-decora/LinearConvolveShadow.jsl 2014-02-21 17:02:04.000000000 -0800 @@ -24,43 +24,31 @@ */ << -private LinearConvolveKernel getKernel() { - return (LinearConvolveKernel) AccessHelper.getState(getEffect()); -} - -public int getPow2ScaleX(LinearConvolveKernel kernel) { - return kernel.getPow2ScaleX(); -} - -public int getPow2ScaleY(LinearConvolveKernel kernel) { - return kernel.getPow2ScaleY(); -} - public Rectangle getResultBounds(com.sun.javafx.geom.transform.BaseTransform transform, com.sun.javafx.geom.Rectangle outputClip, com.sun.scenario.effect.ImageData... inputDatas) { - return getKernel().getScaledResultBounds(inputDatas[0].getTransformedBounds(outputClip), getPass()); + return getRenderState().getPassResultBounds(inputDatas[0].getTransformedBounds(outputClip)); } private int getCount() { - return (getKernel().getScaledKernelSize(getPass()) + 3) / 4; + return (getRenderState().getPassKernelSize() + 3) / 4; } private float[] getOffset() { - return getKernel().getVector(getInputNativeBounds(0), getInputTransform(0), getPass()); + return getRenderState().getPassVector(); } private FloatBuffer getWeights() { - return getKernel().getWeights(getPass()); + return getRenderState().getPassWeights(); } private int getWeightsArrayLength() { - return getKernel().getWeightsArrayLength(getPass()); + return getRenderState().getPassWeightsArrayLength(); } private float[] getShadowColor() { - return getKernel().getShadowColorComponents(getPass()); + return getRenderState().getPassShadowColorComponents(); } >> @@ -73,10 +61,10 @@ param float4 offset; param float4 shadowColor; // value for each location in the offsets array: -// weights[i].x = weight for pos0 + offset + i*direction*4+0 -// weights[i].y = weight for pos0 + offset + i*direction*4+1 -// weights[i].z = weight for pos0 + offset + i*direction*4+2 -// weights[i].w = weight for pos0 + offset + i*direction*4+3 +// weights[i].x = weight for pos0 + offset.zw + (i*4+0)*offset.xy +// weights[i].y = weight for pos0 + offset.zw + (i*4+1)*offset.xy +// weights[i].z = weight for pos0 + offset.zw + (i*4+2)*offset.xy +// weights[i].w = weight for pos0 + offset.zw + (i*4+3)*offset.xy param float4 weights[%d]; void main() --- old/modules/graphics/src/main/java/com/sun/scenario/effect/impl/state/LinearConvolveKernel.java 2014-02-21 17:02:05.000000000 -0800 +++ new/modules/graphics/src/main/java/com/sun/scenario/effect/impl/state/LinearConvolveKernel.java 2014-02-21 17:02:05.000000000 -0800 @@ -25,15 +25,8 @@ package com.sun.scenario.effect.impl.state; -import java.nio.FloatBuffer; import com.sun.javafx.geom.Rectangle; import com.sun.javafx.geom.transform.BaseTransform; -import com.sun.scenario.effect.Color4f; -import com.sun.scenario.effect.Effect; -import com.sun.scenario.effect.FilterContext; -import com.sun.scenario.effect.ImageData; -import com.sun.scenario.effect.impl.EffectPeer; -import com.sun.scenario.effect.impl.Renderer; /** * The helper class for defining a 1 dimensional linear convolution kernel @@ -42,68 +35,6 @@ * convolutions. */ public abstract class LinearConvolveKernel { - public static final int MAX_KERNEL_SIZE = 128; - - public enum PassType { - /** - * The kernel on this pass will be applied horizontally with - * the kernel centered symmetrically around each pixel. - * The specific conditions indicated by this type are: - * - */ - HORIZONTAL_CENTERED, - - /** - * The kernel on this pass will be applied vertically with - * the kernel centered symmetrically around each pixel. - * The specific conditions indicated by this type are: - * - */ - VERTICAL_CENTERED, - - /** - * The kernel on this pass can be applied in any direction or with - * any kind of offset. - * No assumptions are made about the offset and delta of the kernel - * vector. - */ - GENERAL_VECTOR, - }; - - /** - * Returns the peer sample count for a given kernel size. There are - * only a few peers defined to operate on specific sizes of convolution - * kernel. If there are peers defined only for kernel sizes of 8 and 16 - * and a given effect has a linear convolution kernel with 5 weights, - * then the peer for size 8 will be used and the buffer of weights must - * be padded out to the appropriate size with 0s so that the shader - * constant pool will be fully initialized and the extra unneeded - * convolution samples will be ignored by the 0 weights. - * - * @param ksize the number of computed convolution kernel weights - * @return the number of convolution weights which will be applied by - * the associated peer. - */ - public static int getPeerSize(int ksize) { - if (ksize < 32) return ((ksize + 3) & (~3)); - if (ksize <= MAX_KERNEL_SIZE) return ((ksize + 31) & (~31)); - throw new RuntimeException("No peer available for kernel size: "+ksize); - } - /** * Returns true if this is a LinearConvolveShadow operation, or false * if the operation is a regular LinearConvolve. @@ -115,16 +46,6 @@ } /** - * Returns the number of linear convolution passes the algorithm must make - * to complete its work. Most subclasses will use only 1 or 2 passes - * (typically broken down into a horizontal pass and a vertical pass as - * necessary). - * - * @return the number of passes to be made - */ - public abstract int getNumberOfPasses(); - - /** * Returns true if the entire operation of this linear convolution * would have no effect on the source data. * @@ -135,30 +56,6 @@ } /** - * Returns true if the operation of a particular pass of this linear - * convolution would have no effect on the source data. - * - * @param pass the algorithm pass being performed - * @return true if the given pass is a NOP - */ - public boolean isNop(int pass) { - return false; - } - - /** - * Returns the {@link PassType} that indicates the assumptions that - * can be made in optimizing the application of the kernel on this - * pass. - * - * @param pass the algorithm pass being performed - * @return the {@link PassType} that describes the kernel vector for - * this pass - */ - public PassType getPassType(int pass) { - return PassType.GENERAL_VECTOR; - } - - /** * Returns the size of the output image needed for a given input * image dimensions and a given pass of the algorithm. * @@ -169,51 +66,6 @@ public abstract Rectangle getResultBounds(Rectangle srcdimension, int pass); /** - * Returns the size of the scaled result image needed to hold the output - * for a given input image dimensions and a given pass of the algorithm. - * The image may be further scaled after the shader operation is through - * to obtain the final result bounds. - * This value is only of use to the actual shader to understand exactly - * how much room to allocate for the shader result. - * - * @param srcdimension the bounds of the input image - * @param pass the algorithm pass being performed - * @return the bounds of the result image - */ - public Rectangle getScaledResultBounds(Rectangle srcdimension, int pass) { - return getResultBounds(srcdimension, pass); - } - - /** - * Returns an array of 4 floats used to initialize a float4 Shader - * constant with the relative starting location of the first weight - * in the convolution kernel and the incremental offset between each - * sample to be weighted and convolved. The values are stored in - * the array in the following order: - *
-     *     shadervec.x = vector[0] = incdx // X offset between subsequent samples
-     *     shadervec.y = vector[1] = incdy // Y offset between subsequent samples
-     *     shadervec.z = vector[2] = startdx // X offset to first convolution sample
-     *     shadervec.w = vector[3] = startdy // Y offset to first convolution sample
-     * 
- * These values are used in the shader loop as follows: - *
-     *     samplelocation = outputpixellocation.xy + shadervec.zw;
-     *     for (each weight) {
-     *         sum += weight * sample(samplelocation.xy);
-     *         samplelocation.xy += shadervec.xy;
-     *     }
-     *
-     * @param srcnativedimensions the native dimensions (including unused
-     *                            padding) of the input source
-     * @param pass the pass of the algorithm being performed
-     * @return an array of 4 floats representing
-     *         {@code [ incdx, incdy, startdx, startdy ]}
-     */
-    public abstract float[] getVector(Rectangle srcnativedimensions,
-                                      BaseTransform transform, int pass);
-
-    /**
      * Returns the size of the kernel for a given pass.
      *
      * @param pass the pass of the algorithm being performed
@@ -221,208 +73,5 @@
      */
     public abstract int getKernelSize(int pass);
 
-    /**
-     * Returns the size of the kernel used in the shader for a given pass,
-     * taking into account the scaling specified by the getPow2ScaleXY methods.
-     *
-     * @param pass the pass of the algorithm being performed
-     * @return the size of the kernel for the scaled operation
-     */
-    public int getScaledKernelSize(int pass) {
-        return getKernelSize(pass);
-    }
-
-    /**
-     * Returns the number of power of 2 scales along the X axis.
-     * Positive numbers mean to scale the image larger by the indicated
-     * factors of 2.0.
-     * Negative numbers mean to scale the image smaller by the indicated
-     * factors of 0.5.
-     * Overall the image will be scaled by {@code pow(2.0, getPow2ScaleX())}.
-     * 

- * The kernel specified by the {@link #getWeights()} method will be - * relative to the scale factor recommended by this method. - * This scaling allows larger kernels to be reduced in size to save - * computation if the resolution reduction will not alter the quality - * of the convolution (eg. for blur convolutions). - * - * @return the power of 2.0 by which to scale the source image along the - * X axis. - */ - public int getPow2ScaleX() { - return 0; - } - - /** - * Returns the number of power of 2 scales along the Y axis. - * Positive numbers mean to scale the image larger by the indicated - * factors of 2.0. - * Negative numbers mean to scale the image smaller by the indicated - * factors of 0.5. - * Overall the image will be scaled by {@code pow(2.0, getPow2ScaleY())}. - *

- * The kernel specified by the {@link #getWeights()} method will be - * relative to the scale factor recommended by this method. - * This scaling allows larger kernels to be reduced in size to save - * computation if the resolution reduction will not alter the quality - * of the convolution (eg. for blur convolutions). - * - * @return the power of 2.0 by which to scale the source image along the - * Y axis. - */ - public int getPow2ScaleY() { - return 0; - } - - /** - * A {@link FloatBuffer} padded out to the required size as specified by - * the {@link #getPeerSize()} method. - * - * @param pass the pass of the algorithm being performed - * @return a {@code FloatBuffer} containing the kernel convolution weights - */ - public abstract FloatBuffer getWeights(int pass); - - /** - * Returns the maximum number of valid float4 elements that should be - * referenced from the buffer returned by getWeights() for the given pass. - * - * @param pass the pass of the algorithm being performed - * @return the maximum number of valid float4 elements in the weights buffer - */ - public int getWeightsArrayLength(int pass) { - int ksize = getScaledKernelSize(pass); - int psize = getPeerSize(ksize); - return psize / 4; - } - - final static float[] BLACK_COMPONENTS = - Color4f.BLACK.getPremultipliedRGBComponents(); - - /** - * Returns the color components to be used for a linearly convolved shadow. - * Only the LinearConvolveShadow shader uses this method. State - * subclasses that are only intended to be used with the LinearConvolve - * shader do not need to override this method. - * - * @param pass the pass of the algorithm being performed - * @return the color components for the shadow color for the given pass - */ - public float[] getShadowColorComponents(int pass) { - return BLACK_COMPONENTS; - } - - public EffectPeer getPeer(Renderer r, FilterContext fctx, int pass) { - if (isNop(pass)) { - return null; - } - int ksize = getScaledKernelSize(pass); - int psize = getPeerSize(ksize); - String opname = isShadow() ? "LinearConvolveShadow" : "LinearConvolve"; - return r.getPeerInstance(fctx, opname, psize); - } - - public Rectangle transform(Rectangle clip, - int xpow2scales, int ypow2scales) - { - // Modeled after Renderer.transform(fctx, img, hscale, vscale) - if (clip == null || (xpow2scales | ypow2scales) == 0) { - return clip; - } - clip = new Rectangle(clip); - if (xpow2scales < 0) { - xpow2scales = -xpow2scales; - clip.width = (clip.width + (1 << xpow2scales) - 1) >> xpow2scales; - clip.x >>= xpow2scales; - } else if (xpow2scales > 0) { - clip.width = clip.width << xpow2scales; - clip.x <<= xpow2scales; - } - if (ypow2scales < 0) { - ypow2scales = -ypow2scales; - clip.height = (clip.height + (1 << ypow2scales) - 1) >> ypow2scales; - clip.y >>= ypow2scales; - } else if (ypow2scales > 0) { - clip.height = clip.height << ypow2scales; - clip.y <<= ypow2scales; - } - return clip; - } - - public ImageData filterImageDatas(Effect effect, - FilterContext fctx, - BaseTransform transform, - Rectangle outputClip, - ImageData... inputs) - { - ImageData src = inputs[0]; - src.addref(); - if (isNop()) { - return src; - } - Rectangle approxBounds = inputs[0].getUntransformedBounds(); - int approxW = approxBounds.width; - int approxH = approxBounds.height; - Renderer r = Renderer.getRenderer(fctx, effect, approxW, approxH); - EffectPeer peer0 = getPeer(r, fctx, 0); - EffectPeer peer1 = getPeer(r, fctx, 1); - int hscale = 0; - int vscale = 0; - if (peer0 instanceof LinearConvolvePeer) { - hscale = ((LinearConvolvePeer) peer0).getPow2ScaleX(this); - } - if (peer1 instanceof LinearConvolvePeer) { - vscale = ((LinearConvolvePeer) peer1).getPow2ScaleY(this); - } - Rectangle filterClip = outputClip; - if ((hscale | vscale) != 0) { - src = r.transform(fctx, src, hscale, vscale); - if (!src.validate(fctx)) { - src.unref(); - return src; - } - filterClip = transform(outputClip, hscale, vscale); - } - if (filterClip != null) { - // The inputClip was already grown by the padding when the - // inputs were filtered, but now we need to make sure that - // the peers pass out padded results from the already padded - // input data, so we grow the clip here by the size of the - // scaled kernel padding. - int hgrow = getScaledKernelSize(0) / 2; - int vgrow = getScaledKernelSize(1) / 2; - if ((hgrow | vgrow) != 0) { - if (filterClip == outputClip) { - filterClip = new Rectangle(outputClip); - } - filterClip.grow(hgrow, vgrow); - } - } - if (peer0 != null) { - peer0.setPass(0); - ImageData res = peer0.filter(effect, transform, filterClip, src); - src.unref(); - src = res; - if (!src.validate(fctx)) { - src.unref(); - return src; - } - } - - if (peer1 != null) { - peer1.setPass(1); - ImageData res = peer1.filter(effect, transform, filterClip, src); - src.unref(); - src = res; - if (!src.validate(fctx)) { - src.unref(); - return src; - } - } - - if ((hscale | vscale) != 0) { - src = r.transform(fctx, src, -hscale, -vscale); - } - return src; - } + public abstract LinearConvolveRenderState getRenderState(BaseTransform filtertx); } --- old/modules/graphics/src/main/java/com/sun/scenario/effect/impl/state/GaussianBlurState.java 2014-02-21 17:02:06.000000000 -0800 +++ new/modules/graphics/src/main/java/com/sun/scenario/effect/impl/state/GaussianBlurState.java 2014-02-21 17:02:06.000000000 -0800 @@ -25,17 +25,16 @@ package com.sun.scenario.effect.impl.state; -import java.nio.FloatBuffer; -import com.sun.scenario.effect.impl.BufferUtil; +import com.sun.javafx.geom.transform.BaseTransform; +import com.sun.scenario.effect.Color4f; /** * The state and implementation class for calculating 1 dimensional - * linear convolution kernels for performing gaussian blurs. + * linear convolution kernels for performing Gaussian blurs. */ public class GaussianBlurState extends HVSeparableKernel { private float hradius; private float vradius; - private FloatBuffer weights; void checkRadius(float radius) { if (radius < 0f || radius > 63f) { @@ -80,23 +79,10 @@ return hradius == 0 && vradius == 0; } - @Override - public boolean isNop(int pass) { - return getRadius(pass) == 0; - } - public int getPad(int pass) { return (int) Math.ceil(getRadius(pass)); } - public int getScaledPad(int pass) { - return getPad(pass); - } - - public float getScaledRadius(int pass) { - return getRadius(pass); - } - @Override public int getKernelSize(int pass) { return getPad(pass) * 2 + 1; @@ -106,54 +92,14 @@ return 0f; } + public Color4f getShadowColor() { + return null; + } + @Override - public FloatBuffer getWeights(int pass) { - float r0 = getScaledRadius(0); - float r1 = getScaledRadius(1); - // We need to apply the spread on only one pass - // Prefer pass1 if r1 is not tiny (or at least bigger than r0) - // Otherwise use pass 0 so that it doesn't disappear - int spreadpass = (r1 > 1f || r1 >= r0) ? 1 : 0; - - float r = (pass == 0) ? r0 : r1; - float s = (pass == spreadpass) ? getSpread() : 0f; - weights = getGaussianWeights(weights, getScaledPad(pass), r, s); - return weights; - } - - static FloatBuffer getGaussianWeights(FloatBuffer weights, - int pad, - float radius, - float spread) - { - int r = pad; - int klen = (r * 2) + 1; - if (weights == null) { - weights = BufferUtil.newFloatBuffer(128); - } - weights.clear(); - float sigma = radius / 3; - float sigma22 = 2 * sigma * sigma; - if (sigma22 < Float.MIN_VALUE) { - // Avoid divide by 0 below (it can generate NaN values). - sigma22 = Float.MIN_VALUE; - } - float total = 0.0F; - for (int row = -r; row <= r; row++) { - float kval = (float) Math.exp(-(row * row) / sigma22); - weights.put(kval); - total += kval; - } - total += (weights.get(0) - total) * spread; - for (int i = 0; i < klen; i++) { - weights.put(i, weights.get(i) / total); - } - int limit = getPeerSize(klen); - while (weights.position() < limit) { - weights.put(0.0F); - } - weights.limit(limit); - weights.rewind(); - return weights; + public LinearConvolveRenderState getRenderState(BaseTransform filtertx) { + return new GaussianRenderState(hradius, vradius, getSpread(), + this instanceof GaussianShadowState, getShadowColor(), + filtertx); } } --- old/modules/graphics/src/main/java/com/sun/scenario/effect/impl/state/BoxBlurState.java 2014-02-21 17:02:07.000000000 -0800 +++ new/modules/graphics/src/main/java/com/sun/scenario/effect/impl/state/BoxBlurState.java 2014-02-21 17:02:07.000000000 -0800 @@ -25,40 +25,35 @@ package com.sun.scenario.effect.impl.state; -import java.nio.FloatBuffer; -import com.sun.scenario.effect.Effect.AccelType; -import com.sun.scenario.effect.FilterContext; -import com.sun.scenario.effect.impl.BufferUtil; -import com.sun.scenario.effect.impl.EffectPeer; -import com.sun.scenario.effect.impl.Renderer; +import com.sun.javafx.geom.transform.BaseTransform; +import com.sun.scenario.effect.Color4f; /** * The state and implementation class for calculating 1 dimensional * linear convolution kernels for performing multi-pass box blurs. */ public class BoxBlurState extends HVSeparableKernel { - private int hsize; - private int vsize; + private float hsize; + private float vsize; private int blurPasses; - private FloatBuffer weights; - public int getHsize() { + public float getHsize() { return hsize; } - public void setHsize(int hsize) { - if (hsize < 0 || hsize > 255) { + public void setHsize(float hsize) { + if (hsize < 0.0f || hsize > 255.0f) { throw new IllegalArgumentException("Blur size must be in the range [0,255]"); } this.hsize = hsize; } - public int getVsize() { + public float getVsize() { return vsize; } - public void setVsize(int vsize) { - if (vsize < 0 || vsize > 255) { + public void setVsize(float vsize) { + if (vsize < 0.0f || vsize > 255.0f) { throw new IllegalArgumentException("Blur size must be in the range [0,255]"); } this.vsize = vsize; @@ -75,153 +70,31 @@ this.blurPasses = blurPasses; } + public Color4f getShadowColor() { + return null; + } + public float getSpread() { return 0.0f; } @Override - public boolean isNop() { - return (blurPasses == 0 || (hsize <= 1 && vsize <= 1)); + public LinearConvolveRenderState getRenderState(BaseTransform filtertx) { + return new BoxRenderState(hsize, vsize, blurPasses, getSpread(), + isShadow(), getShadowColor(), filtertx); } @Override - public boolean isNop(int pass) { - return (blurPasses == 0 || ((pass == 0) ? hsize : vsize) <= 1); + public boolean isNop() { + return (blurPasses == 0 || (hsize <= 1 && vsize <= 1)); } + @Override public int getKernelSize(int pass) { - int ksize = pass == 0 ? hsize : vsize; + int ksize = (int) (pass == 0 ? hsize : vsize); if (ksize < 1) ksize = 1; ksize = (ksize-1) * blurPasses + 1; ksize |= 1; return ksize; } - - private int getScaleVal(int pass, boolean needScale) { - int ksize = getKernelSize(pass); - int scale = 0; - while (ksize > 128) { - ksize = ((ksize + 1) / 2) | 1; - scale--; - } - return needScale ? scale : ksize; - } - - public int getPow2Scale(int pass) { - return getScaleVal(pass, true); - } - - @Override - public int getScaledKernelSize(int pass) { - return getScaleVal(pass, false); - } - - @Override - public int getPow2ScaleX() { - return getPow2Scale(0); - } - - @Override - public int getPow2ScaleY() { - return getPow2Scale(1); - } - - public FloatBuffer getWeights(int pass) { - int klen = pass == 0 ? hsize : vsize; - if (klen < 1 || blurPasses == 0) klen = 1; - long ik[] = new long[klen]; - for (int i = 0; i < klen; i++) { - ik[i] = 1; - } - for (int p = 1; p < blurPasses; p++) { - long ik2[] = new long[ik.length + klen-1]; - for (int i = 0; i < ik.length; i++) { - for (int k = 0; k < klen; k++) { - ik2[i+k] += ik[i]; - } - } - ik = ik2; - } - if ((ik.length & 1) == 0) { - // If kernel length is odd then it is centered on a pixel. - // If kernel length is even, then it is not centered on a pixel - // and the weights are applied to a sample between pixels. - // Instead of trying to sample between pixels we instead - // distribute the weights by half a pixel by averaging - // adjacent values together and lengthening the kernel by a pixel. - long ik2[] = new long[ik.length + 1]; - for (int i = 0; i < ik.length; i++) { - ik2[i] += ik[i]; - ik2[i+1] += ik[i]; - } - ik = ik2; - } - int scale = getPow2Scale(pass); - while (scale < 0) { - int newlen = ((ik.length + 1) / 2) | 1; - int skewi = (newlen * 2 - ik.length) / 2; - long ik2[] = new long[newlen]; - for (int i = 0; i < ik.length; i++) { - ik2[skewi / 2] += ik[i]; - skewi++; - ik2[skewi / 2] += ik[i]; - } - ik = ik2; - scale++; - } - double sum = 0.0; - for (int i = 0; i < ik.length; i++) { - sum += ik[i]; - } - // We need to apply the spread on only one pass - // Prefer pass1 if r1 is not trivial - // Otherwise use pass 0 so that it doesn't disappear - int spreadpass = (vsize > 1) ? 1 : 0; - float s = (pass == spreadpass) ? getSpread() : 0f; - sum += (ik[0] - sum) * s; - - if (weights == null) { - // peersize(MAX_KERNEL_SIZE) rounded up to the next multiple of 4 - int maxbufsize = LinearConvolveKernel.MAX_KERNEL_SIZE; - maxbufsize = LinearConvolveKernel.getPeerSize(maxbufsize); - maxbufsize = (maxbufsize + 3) & (~3); - weights = BufferUtil.newFloatBuffer(maxbufsize); - } - weights.clear(); - for (int i = 0; i < ik.length; i++) { - weights.put((float) (ik[i] / sum)); - } - int limit = getPeerSize(ik.length); - while (weights.position() < limit) { - weights.put(0f); - } - weights.limit(limit); - weights.rewind(); - return weights; - } - - @Override - public EffectPeer getPeer(Renderer r, FilterContext fctx, int pass) { - int ksize = getScaledKernelSize(pass); - if (ksize <= 1) { - // The result of ksize being too <= 1 is a NOP - // so we return null here to skip the corresponding - // filter pass. - return null; - } - int psize = getPeerSize(ksize); - AccelType actype = r.getAccelType(); - String name; - switch (actype) { - case NONE: - case SIMD: - name = "BoxBlur"; - break; - default: - name = "LinearConvolve"; - break; - } - EffectPeer peer = r.getPeerInstance(fctx, name, psize); - return peer; - } } --- old/modules/graphics/src/main/java/com/sun/scenario/effect/impl/state/BoxShadowState.java 2014-02-21 17:02:08.000000000 -0800 +++ new/modules/graphics/src/main/java/com/sun/scenario/effect/impl/state/BoxShadowState.java 2014-02-21 17:02:08.000000000 -0800 @@ -26,10 +26,6 @@ package com.sun.scenario.effect.impl.state; import com.sun.scenario.effect.Color4f; -import com.sun.scenario.effect.Effect.AccelType; -import com.sun.scenario.effect.FilterContext; -import com.sun.scenario.effect.impl.EffectPeer; -import com.sun.scenario.effect.impl.Renderer; /** * The helper class for defining a 1 dimensional linear convolution shadow @@ -42,6 +38,7 @@ private Color4f shadowColor; private float spread; + @Override public Color4f getShadowColor() { return shadowColor; } @@ -73,52 +70,7 @@ } @Override - public boolean isNop(int pass) { - // Only the first pass of a shadow can be a NOP since the second - // pass always replaces the colors if nothing else. - return (pass == 0) && super.isNop(pass); - } - - @Override public boolean isShadow() { return true; } - - @Override - public float[] getShadowColorComponents(int pass) { - return (pass == 0) - ? BLACK_COMPONENTS - : shadowColor.getPremultipliedRGBComponents(); - } - - @Override - public EffectPeer getPeer(Renderer r, FilterContext fctx, int pass) { - int ksize = getScaledKernelSize(pass); - if (pass == 0 && ksize <= 1) { - // The result of ksize being too <= 1 is a NOP - // so we return null here to skip the corresponding - // filter pass. - // Note that we can only skip the first pass, the - // second pass is always required to convert the - // colors of the source into the desired shadow color. - return null; - } - int psize = getPeerSize(ksize); - AccelType actype = r.getAccelType(); - String name; - switch (actype) { - case NONE: - case SIMD: - if (spread == 0.0f) { - name = "BoxShadow"; - break; - } - /* FALLS THROUGH */ - default: - name = "LinearConvolveShadow"; - break; - } - EffectPeer peer = r.getPeerInstance(fctx, name, psize); - return peer; - } } --- old/modules/graphics/src/main/java/com/sun/scenario/effect/impl/state/GaussianShadowState.java 2014-02-21 17:02:09.000000000 -0800 +++ new/modules/graphics/src/main/java/com/sun/scenario/effect/impl/state/GaussianShadowState.java 2014-02-21 17:02:09.000000000 -0800 @@ -29,7 +29,7 @@ /** * The helper class for defining a 1 dimensional linear convolution shadow - * kernel for the gaussian Shadow shader. This class leverages the + * kernel for the Gaussian Shadow shader. This class leverages the * {@link GaussianBlurState} class for defining the kernel and simply stores * additional {@code shadowColor} and {@code spread} properties for the * associated support methods for the shadow version of the shader. @@ -45,52 +45,7 @@ } } - private int getPow2Scale(int pass) { - // Technically a radius of 127 would be scaled once down to a radius - // of 63.5 which generates a kernel of length 129, but we will be - // fudging it so that it only generates the central 127 values and - // the last value should be so close to 3*sigma to not be noticeable. - // This avoids a special "extra" scaling just for values > 126. - // So, we only ever scale at most once and then just short the kernel - // for those last couple of values. - return (getRadius(pass) > 63) ? -1 : 0; - } - - @Override - public int getPow2ScaleX() { - return getPow2Scale(0); - } - - @Override - public int getPow2ScaleY() { - return getPow2Scale(1); - } - - @Override - public float getScaledRadius(int pass) { - float r = getRadius(pass); - int s = getPow2Scale(pass); - while (s < 0) { - r /= 2; - s++; - } - return r; - } - @Override - public int getScaledPad(int pass) { - float r = getScaledRadius(pass); - // A radius of 127 will be scaled back to 63.5 which technically - // requires a pad of 64 and a kernel size of 129, but we need to - // clip the kernel at 128 and thus the pad at 63. - return (r > 63.0f) ? 63 : (int) Math.ceil(r); - } - - @Override - public int getScaledKernelSize(int pass) { - return getScaledPad(pass) * 2 + 1; - } - public Color4f getShadowColor() { return shadowColor; } @@ -122,21 +77,7 @@ } @Override - public boolean isNop(int pass) { - // Only the first pass of a shadow can be a NOP since the second - // pass always replaces the colors if nothing else. - return (pass == 0) && super.isNop(pass); - } - - @Override public boolean isShadow() { return true; } - - @Override - public float[] getShadowColorComponents(int pass) { - return (pass == 0) - ? BLACK_COMPONENTS - : shadowColor.getPremultipliedRGBComponents(); - } } --- old/modules/graphics/src/main/java/com/sun/scenario/effect/impl/state/HVSeparableKernel.java 2014-02-21 17:02:10.000000000 -0800 +++ new/modules/graphics/src/main/java/com/sun/scenario/effect/impl/state/HVSeparableKernel.java 2014-02-21 17:02:10.000000000 -0800 @@ -26,8 +26,6 @@ package com.sun.scenario.effect.impl.state; import com.sun.javafx.geom.Rectangle; -import com.sun.javafx.geom.transform.BaseTransform; -import com.sun.javafx.geom.transform.NoninvertibleTransformException; /** * An abstract helper intermediate implementation class for @@ -36,18 +34,6 @@ */ public abstract class HVSeparableKernel extends LinearConvolveKernel { @Override - public final int getNumberOfPasses() { - return 2; - } - - @Override - public PassType getPassType(int pass) { - return (pass == 0) - ? PassType.HORIZONTAL_CENTERED - : PassType.VERTICAL_CENTERED; - } - - @Override public final Rectangle getResultBounds(Rectangle srcdimension, int pass) { int ksize = getKernelSize(pass); Rectangle ret = new Rectangle(srcdimension); @@ -58,45 +44,4 @@ } return ret; } - - @Override - public final Rectangle getScaledResultBounds(Rectangle srcdimension, int pass) { - int ksize = getScaledKernelSize(pass); - Rectangle ret = new Rectangle(srcdimension); - if (pass == 0) { - ret.grow(ksize/2, 0); - } else { - ret.grow(0, ksize/2); - } - return ret; - } - - @Override - public final float[] getVector(Rectangle srcnativedimensions, - BaseTransform transform, int pass) - { - float ret[] = new float[4]; - float ps; - if (transform.isTranslateOrIdentity()) { - ps = 1f; - } else { - ret[0] = (pass == 0) ? 1f : 0f; - ret[1] = (pass == 1) ? 1f : 0f; - try { - transform.inverseDeltaTransform(ret, 0, ret, 0, 1); - ps = (float) Math.hypot(ret[0], ret[1]); - } catch (NoninvertibleTransformException e) { - ps = 0f; - } - } - float xoff = (pass == 0) ? ps / srcnativedimensions.width : 0f; - float yoff = (pass == 1) ? ps / srcnativedimensions.height : 0f; - int ksize = getScaledKernelSize(pass); - int center = ksize / 2; - ret[0] = xoff; - ret[1] = yoff; - ret[2] = -center * xoff; - ret[3] = -center * yoff; - return ret; - } } --- old/modules/graphics/src/main/java/com/sun/scenario/effect/impl/state/MotionBlurState.java 2014-02-21 17:02:11.000000000 -0800 +++ new/modules/graphics/src/main/java/com/sun/scenario/effect/impl/state/MotionBlurState.java 2014-02-21 17:02:10.000000000 -0800 @@ -25,15 +25,12 @@ package com.sun.scenario.effect.impl.state; -import java.nio.FloatBuffer; import com.sun.javafx.geom.Rectangle; import com.sun.javafx.geom.transform.BaseTransform; -import com.sun.javafx.geom.transform.NoninvertibleTransformException; public class MotionBlurState extends LinearConvolveKernel { private float radius; private float angle; - private FloatBuffer weights; public float getRadius() { return radius; @@ -41,7 +38,7 @@ public void setRadius(float radius) { if (radius < 0f || radius > 63f) { - throw new IllegalArgumentException("Radius must be in the range [1,63]"); + throw new IllegalArgumentException("Radius must be in the range [0,63]"); } this.radius = radius; } @@ -63,8 +60,10 @@ } @Override - public int getNumberOfPasses() { - return 1; + public LinearConvolveRenderState getRenderState(BaseTransform filtertx) { + float dx = (float) Math.cos(angle); + float dy = (float) Math.sin(angle); + return new GaussianRenderState(radius, dx, dy, filtertx); } @Override @@ -73,11 +72,6 @@ } @Override - public boolean isNop(int pass) { - return (pass > 0 || radius == 0f); - } - - @Override public int getKernelSize(int pass) { return ((int) Math.ceil(radius)) * 2 + 1; } @@ -88,42 +82,4 @@ ret.grow(getHPad(), getVPad()); return ret; } - - @Override - public float[] getVector(Rectangle srcnativedimensions, - BaseTransform transform, int pass) - { -// float xoff = (float) (Math.cos(angle) / srcnativedimensions.width); -// float yoff = (float) (Math.sin(angle) / srcnativedimensions.height); - float ret[] = new float[4]; - float cos = (float) Math.cos(angle); - float sin = (float) Math.sin(angle); - if (!transform.isTranslateOrIdentity()) { - ret[0] = cos; - ret[1] = sin; - try { - transform.inverseDeltaTransform(ret, 0, ret, 0, 1); - cos = ret[0]; - sin = ret[1]; - } catch (NoninvertibleTransformException e) { - sin = cos = 0f; - } - } - float xoff = cos / srcnativedimensions.width; - float yoff = sin / srcnativedimensions.height; - int ksize = getScaledKernelSize(pass); - int center = ksize / 2; - ret[0] = xoff; - ret[1] = yoff; - ret[2] = -center * xoff; - ret[3] = -center * yoff; - return ret; - } - - @Override - public FloatBuffer getWeights(int pass) { - int pad = (int) Math.ceil(radius); - weights = GaussianBlurState.getGaussianWeights(weights, pad, radius, 0f); - return weights; - } } --- old/modules/graphics/src/main/java/com/sun/javafx/sg/prism/NGGroup.java 2014-02-21 17:02:12.000000000 -0800 +++ new/modules/graphics/src/main/java/com/sun/javafx/sg/prism/NGGroup.java 2014-02-21 17:02:11.000000000 -0800 @@ -249,7 +249,7 @@ bot = top; } else { ImageData newbot = - b.filterImageDatas(fctx, transform, rclip, bot, top); + b.filterImageDatas(fctx, transform, rclip, null, bot, top); bot.unref(); top.unref(); bot = newbot; --- old/modules/graphics/src/main/java/com/sun/scenario/effect/AbstractShadow.java 2014-02-21 17:02:13.000000000 -0800 +++ new/modules/graphics/src/main/java/com/sun/scenario/effect/AbstractShadow.java 2014-02-21 17:02:12.000000000 -0800 @@ -39,7 +39,7 @@ * set to something different then the radius parameter will be an average * of the corresponding individual dimensional radius values. */ -public abstract class AbstractShadow extends CoreEffect { +public abstract class AbstractShadow extends LinearConvolveCoreEffect { public AbstractShadow(Effect input) { super(input); } --- old/modules/graphics/src/main/java/com/sun/scenario/effect/Blend.java 2014-02-21 17:02:14.000000000 -0800 +++ new/modules/graphics/src/main/java/com/sun/scenario/effect/Blend.java 2014-02-21 17:02:13.000000000 -0800 @@ -28,6 +28,7 @@ import com.sun.javafx.geom.Point2D; import com.sun.javafx.geom.Rectangle; import com.sun.javafx.geom.transform.BaseTransform; +import com.sun.scenario.effect.impl.state.RenderState; /** * An effect that blends the two inputs together using one of the @@ -574,18 +575,20 @@ } @Override - protected Rectangle getInputClip(int inputIndex, - BaseTransform transform, - Rectangle outputClip) + public RenderState getRenderState(FilterContext fctx, + BaseTransform transform, + Rectangle outputClip, + Object renderHelper, + Effect defaultInput) { // A blend operation operates on its inputs pixel-by-pixel // with no expansion or contraction. // RT-27563 - // TODO: For blend modes which "intersect" their inputs, we - // could further restrict the amount we ask for each input to the - // intersection of the two input bounds, but for now we will - // simply pass along the output clip as the input clip. - return outputClip; + // TODO: The RenderSpaceRenderState object uses the output clip unchanged + // for its inputs, but we could further restrict the amount we ask for + // each input to the intersection of the two input bounds, but for now we + // will simply let it pass along the output clip as the input clip. + return RenderState.RenderSpaceRenderState; } @Override --- old/modules/graphics/src/main/java/com/sun/scenario/effect/Brightpass.java 2014-02-21 17:02:15.000000000 -0800 +++ new/modules/graphics/src/main/java/com/sun/scenario/effect/Brightpass.java 2014-02-21 17:02:14.000000000 -0800 @@ -27,6 +27,7 @@ import com.sun.javafx.geom.Rectangle; import com.sun.javafx.geom.transform.BaseTransform; +import com.sun.scenario.effect.impl.state.RenderState; /** * An effect that filters out (i.e., replaces with a transparent value) all @@ -115,14 +116,13 @@ } @Override - protected Rectangle getInputClip(int inputIndex, - BaseTransform transform, - Rectangle outputClip) + public RenderState getRenderState(FilterContext fctx, + BaseTransform transform, + Rectangle outputClip, + Object renderHelper, + Effect defaultInput) { - // Trivially, this effect simply modifies the alpha values of the - // pixels of the input on a 1:1 basis so the input clip is the same - // as the output clip. - return outputClip; + return RenderState.RenderSpaceRenderState; } @Override --- old/modules/graphics/src/main/java/com/sun/scenario/effect/ColorAdjust.java 2014-02-21 17:02:16.000000000 -0800 +++ new/modules/graphics/src/main/java/com/sun/scenario/effect/ColorAdjust.java 2014-02-21 17:02:15.000000000 -0800 @@ -27,6 +27,7 @@ import com.sun.javafx.geom.Rectangle; import com.sun.javafx.geom.transform.BaseTransform; +import com.sun.scenario.effect.impl.state.RenderState; /** * An effect that allows for per-pixel adjustments of hue, saturation, @@ -209,14 +210,13 @@ } @Override - protected Rectangle getInputClip(int inputIndex, - BaseTransform transform, - Rectangle outputClip) + public RenderState getRenderState(FilterContext fctx, + BaseTransform transform, + Rectangle outputClip, + Object renderHelper, + Effect defaultInput) { - // Trivially, this effect simply modifies the colors of the pixels - // of the input on a 1:1 basis so the input clip is the same as the - // output clip. - return outputClip; + return RenderState.RenderSpaceRenderState; } @Override --- old/modules/graphics/src/main/java/com/sun/scenario/effect/CoreEffect.java 2014-02-21 17:02:17.000000000 -0800 +++ new/modules/graphics/src/main/java/com/sun/scenario/effect/CoreEffect.java 2014-02-21 17:02:16.000000000 -0800 @@ -29,12 +29,13 @@ import com.sun.scenario.effect.impl.Renderer; import com.sun.javafx.geom.Rectangle; import com.sun.javafx.geom.transform.BaseTransform; +import com.sun.scenario.effect.impl.state.RenderState; /** * Package-private base class for built-in effects, i.e., those that are * backed by an EffectPeer implementation. */ -abstract class CoreEffect extends FilterEffect { +abstract class CoreEffect extends FilterEffect { private String peerKey; private int peerCount = -1; @@ -99,9 +100,10 @@ public ImageData filterImageDatas(FilterContext fctx, BaseTransform transform, Rectangle outputClip, + T rstate, ImageData... inputs) { - return getPeer(fctx, inputs).filter(this, transform, outputClip, inputs); + return getPeer(fctx, inputs).filter(this, rstate, transform, outputClip, inputs); } @Override --- old/modules/graphics/src/main/java/com/sun/scenario/effect/Crop.java 2014-02-21 17:02:17.000000000 -0800 +++ new/modules/graphics/src/main/java/com/sun/scenario/effect/Crop.java 2014-02-21 17:02:17.000000000 -0800 @@ -31,6 +31,7 @@ import com.sun.javafx.geom.DirtyRegionPool; import com.sun.javafx.geom.Rectangle; import com.sun.javafx.geom.transform.BaseTransform; +import com.sun.scenario.effect.impl.state.RenderState; /** * An effect that returns a cropped version of the input. @@ -176,21 +177,26 @@ if (!id.validate(fctx)) { return new ImageData(fctx, null, null); } - ImageData ret = filterImageDatas(fctx, transform, cropRect, id); + RenderState rstate = getRenderState(fctx, transform, cropRect, renderHelper, defaultInput); + ImageData ret = filterImageDatas(fctx, transform, cropRect, rstate, id); id.unref(); return ret; } @Override - protected Rectangle getInputClip(int inputIndex, - BaseTransform transform, - Rectangle outputClip) + public RenderState getRenderState(FilterContext fctx, + BaseTransform transform, + Rectangle outputClip, + Object renderHelper, + Effect defaultInput) { // RT-27564 // TODO: Since we also crop to the "crop input" and since cropping // is a form of clipping, we could further restrict the bounds we - // ask from the content input here... - return outputClip; + // ask from the content input here, but for now we will use the stock + // RenderSpaceRenderState object which simply passes along the output + // clip to the inputs unmodified. + return RenderState.RenderSpaceRenderState; } @Override --- old/modules/graphics/src/main/java/com/sun/scenario/effect/DisplacementMap.java 2014-02-21 17:02:18.000000000 -0800 +++ new/modules/graphics/src/main/java/com/sun/scenario/effect/DisplacementMap.java 2014-02-21 17:02:18.000000000 -0800 @@ -32,6 +32,7 @@ import com.sun.javafx.geom.Rectangle; import com.sun.javafx.geom.RectBounds; import com.sun.javafx.geom.transform.BaseTransform; +import com.sun.scenario.effect.impl.state.RenderState; /** * An effect that shifts each pixel according to an (x,y) distance from @@ -245,11 +246,6 @@ this.wrap = wrap; } - @Override - public boolean operatesInUserSpace() { - return true; - } - /** * Transform the specified point {@code p} from the coordinate space * of the primary content input to the coordinate space of the effect @@ -323,24 +319,26 @@ public ImageData filterImageDatas(FilterContext fctx, BaseTransform transform, Rectangle outputClip, + RenderState rstate, ImageData... inputs) { // NOTE: The floatmap is mapped to the entire output bounds so // we need to use unclipped math in the peers to get the // texture mapping right. - return super.filterImageDatas(fctx, transform, null, inputs); + return super.filterImageDatas(fctx, transform, null, rstate, inputs); } - @Override - protected Rectangle getInputClip(int inputIndex, - BaseTransform transform, - Rectangle outputClip) + public RenderState getRenderState(FilterContext fctx, + BaseTransform transform, + Rectangle outputClip, + Object renderHelper, + Effect defaultInput) { - // NOTE: Scan the map and calculate all possible input points + // NOTE: We could scan the map and calculate all possible input points // that might contribute to the clipped output? Until then, any // pixel in the output could come from any pixel in the input so // we will need the full input bounds to proceed... - return null; + return RenderState.UnclippedUserSpaceRenderState; } @Override --- old/modules/graphics/src/main/java/com/sun/scenario/effect/FilterEffect.java 2014-02-21 17:02:19.000000000 -0800 +++ new/modules/graphics/src/main/java/com/sun/scenario/effect/FilterEffect.java 2014-02-21 17:02:19.000000000 -0800 @@ -31,12 +31,13 @@ import com.sun.javafx.geom.Rectangle; import com.sun.javafx.geom.transform.BaseTransform; import com.sun.javafx.geom.transform.NoninvertibleTransformException; +import com.sun.scenario.effect.impl.state.RenderState; /** * The implementation base class for {@link Effect} subclasses that operate * by filtering the inputs at the pixel level. */ -public abstract class FilterEffect extends Effect { +public abstract class FilterEffect extends Effect { protected FilterEffect() { super(); } @@ -49,18 +50,14 @@ super(input1, input2); } - public boolean operatesInUserSpace() { - return false; - } - + @Override public BaseBounds getBounds(BaseTransform transform, - Effect defaultInput) + Effect defaultInput) { int numinputs = getNumInputs(); - BaseTransform inputtx = transform; - if (operatesInUserSpace()) { - inputtx = BaseTransform.IDENTITY_TRANSFORM; - } + RenderState rstate = getRenderState(null, transform, null, + null, defaultInput); + BaseTransform inputtx = rstate.getInputTransform(transform); BaseBounds ret; if (numinputs == 1) { Effect input = getDefaultedInput(0, defaultInput); @@ -73,16 +70,9 @@ } ret = combineBounds(inputBounds); } - if (inputtx != transform) { - ret = transformBounds(transform, ret); - } - return ret; + return transformBounds(rstate.getResultTransform(transform), ret); } - protected abstract Rectangle getInputClip(int inputIndex, - BaseTransform transform, - Rectangle outputBounds); - protected static Rectangle untransformClip(BaseTransform transform, Rectangle clip) { @@ -144,6 +134,30 @@ return transformedBounds; } + /** + * Returns the object representing the rendering strategy and state for + * the filter operation characterized by the specified arguments. + * This call can also be used to get a state object for non-rendering + * cases like querying the bounds of an operation in which case the + * {@link FilterContext} object may be null. + * {@code outputClip} and {@code renderHelper} may always be null just + * as they may be null for a given filter operation. + * + * @param fctx the context object that would be used by the Renderer + * if this call is preparing for a render operation, or null + * @param transform the transform for the output of this operation + * @param outputClip the clip rectangle that may restrict this operation, or null + * @param renderHelper the rendering helper object that can be used to shortcut + * this operation under certain conditions, or null + * @param defaultInput the {@link Effect} to be used in place of any null inputs + * @return + */ + public abstract T getRenderState(FilterContext fctx, + BaseTransform transform, + Rectangle outputClip, + Object renderHelper, + Effect defaultInput); + @Override public ImageData filter(FilterContext fctx, BaseTransform transform, @@ -151,22 +165,23 @@ Object renderHelper, Effect defaultInput) { + T rstate = getRenderState(fctx, transform, outputClip, + renderHelper, defaultInput); int numinputs = getNumInputs(); ImageData inputDatas[] = new ImageData[numinputs]; - Rectangle inputClip; - BaseTransform inputtx; - if (operatesInUserSpace()) { - inputClip = untransformClip(transform, outputClip); - inputtx = BaseTransform.IDENTITY_TRANSFORM; + Rectangle filterClip; + BaseTransform inputtx = rstate.getInputTransform(transform); + BaseTransform resulttx = rstate.getResultTransform(transform); + if (resulttx.isIdentity()) { + filterClip = outputClip; } else { - inputClip = outputClip; - inputtx = transform; + filterClip = untransformClip(resulttx, outputClip); } for (int i = 0; i < numinputs; i++) { Effect input = getDefaultedInput(i, defaultInput); inputDatas[i] = input.filter(fctx, inputtx, - getInputClip(i, inputtx, inputClip), + rstate.getInputClip(i, filterClip), null, defaultInput); if (!inputDatas[i].validate(fctx)) { for (int j = 0; j <= i; j++) { @@ -175,18 +190,18 @@ return new ImageData(fctx, null, null); } } - ImageData ret = filterImageDatas(fctx, inputtx, inputClip, inputDatas); + ImageData ret = filterImageDatas(fctx, inputtx, filterClip, rstate, inputDatas); for (int i = 0; i < numinputs; i++) { inputDatas[i].unref(); } - if (inputtx != transform) { + if (!resulttx.isIdentity()) { if (renderHelper instanceof ImageDataRenderer) { ImageDataRenderer renderer = (ImageDataRenderer) renderHelper; - renderer.renderImage(ret, transform, fctx); + renderer.renderImage(ret, resulttx, fctx); ret.unref(); ret = null; } else { - ret = ret.transform(transform); + ret = ret.transform(resulttx); } } return ret; @@ -205,5 +220,6 @@ protected abstract ImageData filterImageDatas(FilterContext fctx, BaseTransform transform, Rectangle outputClip, + T rstate, ImageData... inputDatas); } --- old/modules/graphics/src/main/java/com/sun/scenario/effect/Flood.java 2014-02-21 17:02:20.000000000 -0800 +++ new/modules/graphics/src/main/java/com/sun/scenario/effect/Flood.java 2014-02-21 17:02:20.000000000 -0800 @@ -32,6 +32,7 @@ import com.sun.javafx.geom.RectBounds; import com.sun.javafx.geom.Rectangle; import com.sun.javafx.geom.transform.BaseTransform; +import com.sun.scenario.effect.impl.state.RenderState; /** * An effect that renders a rectangular region that is filled ("flooded") @@ -164,12 +165,16 @@ } @Override - protected Rectangle getInputClip(int inputIndex, - BaseTransform transform, - Rectangle outputClip) + public RenderState getRenderState(FilterContext fctx, + BaseTransform transform, + Rectangle outputClip, + Object renderHelper, + Effect defaultInput) { - // TODO: Intersect with the flood bounds? - return outputClip; + // TODO: Intersect with the flood bounds? For now just use the + // stock RenderSpaceRenderState which performs no modifications + // on the output bounds for its inputs. + return RenderState.RenderSpaceRenderState; } @Override --- old/modules/graphics/src/main/java/com/sun/scenario/effect/ImageData.java 2014-02-21 17:02:21.000000000 -0800 +++ new/modules/graphics/src/main/java/com/sun/scenario/effect/ImageData.java 2014-02-21 17:02:21.000000000 -0800 @@ -217,4 +217,16 @@ return image != null && Renderer.getRenderer(fctx).isImageDataCompatible(this); } + + @Override + public String toString() { + return "ImageData{" + + "sharedOwner=" + sharedOwner + + ", fctx=" + fctx + + ", refcount=" + refcount + + ", image=" + image + + ", bounds=" + bounds + + ", transform=" + transform + + ", reusable=" + reusable + '}'; + } } --- old/modules/graphics/src/main/java/com/sun/scenario/effect/InvertMask.java 2014-02-21 17:02:22.000000000 -0800 +++ new/modules/graphics/src/main/java/com/sun/scenario/effect/InvertMask.java 2014-02-21 17:02:22.000000000 -0800 @@ -31,6 +31,7 @@ import com.sun.javafx.geom.RectBounds; import com.sun.javafx.geom.Rectangle; import com.sun.javafx.geom.transform.BaseTransform; +import com.sun.scenario.effect.impl.state.RenderState; /** * An effect that returns a mask that is the inverse of the input (i.e., @@ -219,26 +220,48 @@ } @Override - protected Rectangle getInputClip(int inputIndex, - BaseTransform transform, - Rectangle outputClip) + public RenderState getRenderState(FilterContext fctx, + BaseTransform transform, + Rectangle outputClip, + Object renderHelper, + Effect defaultInput) { - // Typically the mask gets padded by synthetic opaque mask data - // that is computed from the lack of input pixels in the padded - // area. But in the case where a clip has cut down on the - // amount of data we are generating then the padding for this - // particular (clipped) operation may not be synthetic, rather it - // may actually represent inversions of real input pixels. Thus, - // the clip for the input needs to make sure it includes any - // valid input pixels that may appear not just in the output - // clip, but also in its padded fringe. - if (outputClip != null) { - if (pad != 0) { - outputClip = new Rectangle(outputClip); - outputClip.grow(pad, pad); + return new RenderState() { + @Override + public EffectCoordinateSpace getEffectTransformSpace() { + return EffectCoordinateSpace.UserSpace; } - } - return outputClip; + + @Override + public BaseTransform getInputTransform(BaseTransform filterTransform) { + return BaseTransform.IDENTITY_TRANSFORM; + } + + @Override + public BaseTransform getResultTransform(BaseTransform filterTransform) { + return filterTransform; + } + + @Override + public Rectangle getInputClip(int i, Rectangle filterClip) { + // Typically the mask gets padded by synthetic opaque mask data + // that is computed from the lack of input pixels in the padded + // area. But in the case where a clip has cut down on the + // amount of data we are generating then the padding for this + // particular (clipped) operation may not be synthetic, rather it + // may actually represent inversions of real input pixels. Thus, + // the clip for the input needs to make sure it includes any + // valid input pixels that may appear not just in the output + // clip, but also in its padded fringe. + if (filterClip != null) { + if (pad != 0) { + filterClip = new Rectangle(filterClip); + filterClip.grow(pad, pad); + } + } + return filterClip; + } + }; } @Override --- old/modules/graphics/src/main/java/com/sun/scenario/effect/Merge.java 2014-02-21 17:02:23.000000000 -0800 +++ new/modules/graphics/src/main/java/com/sun/scenario/effect/Merge.java 2014-02-21 17:02:23.000000000 -0800 @@ -28,6 +28,7 @@ import com.sun.javafx.geom.Point2D; import com.sun.javafx.geom.Rectangle; import com.sun.javafx.geom.transform.BaseTransform; +import com.sun.scenario.effect.impl.state.RenderState; /** * An effect that merges two inputs together into one result. This produces @@ -166,7 +167,9 @@ if (!topimg.validate(fctx)) { return new ImageData(fctx, null, null); } - ImageData ret = filterImageDatas(fctx, transform, outputClip, + RenderState rstate = getRenderState(fctx, transform, outputClip, + renderHelper, defaultInput); + ImageData ret = filterImageDatas(fctx, transform, outputClip, rstate, botimg, topimg); botimg.unref(); topimg.unref(); @@ -174,14 +177,13 @@ } @Override - protected Rectangle getInputClip(int inputIndex, - BaseTransform transform, - Rectangle outputClip) + public RenderState getRenderState(FilterContext fctx, + BaseTransform transform, + Rectangle outputClip, + Object renderHelper, + Effect defaultInput) { - // This no longer matters since we completely override the - // filter method so the FilterEffect.filter() method no longer - // comes into play. - throw new InternalError("Merge.getInputClip should not be called"); + return RenderState.RenderSpaceRenderState; } @Override --- old/modules/graphics/src/main/java/com/sun/scenario/effect/PerspectiveTransform.java 2014-02-21 17:02:24.000000000 -0800 +++ new/modules/graphics/src/main/java/com/sun/scenario/effect/PerspectiveTransform.java 2014-02-21 17:02:24.000000000 -0800 @@ -33,6 +33,7 @@ import com.sun.javafx.geom.RectBounds; import com.sun.javafx.geom.Rectangle; import com.sun.javafx.geom.transform.BaseTransform; +import com.sun.scenario.effect.impl.state.RenderState; public class PerspectiveTransform extends CoreEffect { private float tx[][] = new float[3][3]; @@ -183,11 +184,6 @@ } @Override - public boolean operatesInUserSpace() { - return true; - } - - @Override public ImageData filter(FilterContext fctx, BaseTransform transform, Rectangle outputClip, @@ -195,8 +191,10 @@ Effect defaultInput) { setupTransforms(transform); + RenderState rstate = getRenderState(fctx, transform, outputClip, + renderHelper, defaultInput); Effect input = getDefaultedInput(0, defaultInput); - Rectangle inputClip = getInputClip(0, transform, outputClip); + Rectangle inputClip = rstate.getInputClip(0, outputClip); ImageData inputData = input.filter(fctx, BaseTransform.IDENTITY_TRANSFORM, inputClip, null, defaultInput); @@ -204,7 +202,7 @@ inputData.unref(); return new ImageData(fctx, null, inputData.getUntransformedBounds()); } - ImageData ret = filterImageDatas(fctx, transform, outputClip, inputData); + ImageData ret = filterImageDatas(fctx, transform, outputClip, rstate, inputData); inputData.unref(); return ret; } @@ -260,15 +258,18 @@ } @Override - protected Rectangle getInputClip(int inputIndex, - BaseTransform transform, - Rectangle outputClip) + public RenderState getRenderState(FilterContext fctx, + BaseTransform transform, + Rectangle outputClip, + Object renderHelper, + Effect defaultInput) { // RT-27402 - // TODO: Inverse map the bounds through the perspective - // transform to see what portions of the source contribute - // to it. - return null; + // TODO: We could inverse map the output bounds through the perspective + // transform to see what portions of the input contribute to the result, + // but until we implement such a process we will just use the stock + // object that specifies no clipping of the inputs. + return RenderState.UnclippedUserSpaceRenderState; } @Override --- old/modules/graphics/src/main/java/com/sun/scenario/effect/PhongLighting.java 2014-02-21 17:02:25.000000000 -0800 +++ new/modules/graphics/src/main/java/com/sun/scenario/effect/PhongLighting.java 2014-02-21 17:02:25.000000000 -0800 @@ -31,6 +31,7 @@ import com.sun.javafx.geom.Point2D; import com.sun.javafx.geom.Rectangle; import com.sun.javafx.geom.transform.BaseTransform; +import com.sun.scenario.effect.impl.state.RenderState; import com.sun.scenario.effect.light.Light; /** @@ -295,15 +296,18 @@ } @Override - protected Rectangle getInputClip(int inputIndex, - BaseTransform transform, - Rectangle outputClip) + public RenderState getRenderState(FilterContext fctx, + BaseTransform transform, + Rectangle outputClip, + Object renderHelper, + Effect defaultInput) { // RT-27564 - // TODO: Since only the content input is used for the output - // bounds we could attempt to factor the bounds of the content - // input in our answer here for the other inputs. - return outputClip; + // TODO: Since only the content input is used for the output bounds + // we could attempt to factor the bounds of the content input in our + // answer for the getInputClip() method of the RenderState, but for + // now we will just use the stock RenderSpaceRenderState object. + return RenderState.RenderSpaceRenderState; } @Override --- old/modules/graphics/src/main/java/com/sun/scenario/effect/Reflection.java 2014-02-21 17:02:26.000000000 -0800 +++ new/modules/graphics/src/main/java/com/sun/scenario/effect/Reflection.java 2014-02-21 17:02:26.000000000 -0800 @@ -32,6 +32,7 @@ import com.sun.javafx.geom.RectBounds; import com.sun.javafx.geom.Rectangle; import com.sun.javafx.geom.transform.BaseTransform; +import com.sun.scenario.effect.impl.state.RenderState; /** * An effect that renders a reflected version of the input below the @@ -231,11 +232,6 @@ } @Override - public boolean operatesInUserSpace() { - return true; - } - - @Override public Point2D transform(Point2D p, Effect defaultInput) { return getDefaultedInput(0, defaultInput).transform(p, defaultInput); } @@ -246,15 +242,17 @@ } @Override - protected Rectangle getInputClip(int inputIndex, - BaseTransform transform, - Rectangle outputClip) + public RenderState getRenderState(FilterContext fctx, + BaseTransform transform, + Rectangle outputClip, + Object renderHelper, + Effect defaultInput) { // RT-27405 - // TODO: Calculate which parts are needed based on the two - // ways that the input is rendered into this ouput rectangle. - // For now, just ask for the entire input. - return null; + // TODO: We could calculate which parts are needed based on the two + // ways that the input is rendered into this ouput rectangle. For now, + // we will just use the stock object that requests unclipped inputs. + return RenderState.UnclippedUserSpaceRenderState; } @Override --- old/modules/graphics/src/main/java/com/sun/scenario/effect/SepiaTone.java 2014-02-21 17:02:27.000000000 -0800 +++ new/modules/graphics/src/main/java/com/sun/scenario/effect/SepiaTone.java 2014-02-21 17:02:27.000000000 -0800 @@ -27,6 +27,7 @@ import com.sun.javafx.geom.Rectangle; import com.sun.javafx.geom.transform.BaseTransform; +import com.sun.scenario.effect.impl.state.RenderState; /** * A filter that produces a sepia tone effect, similar to antique photographs. @@ -111,14 +112,13 @@ } @Override - protected Rectangle getInputClip(int inputIndex, - BaseTransform transform, - Rectangle outputClip) + public RenderState getRenderState(FilterContext fctx, + BaseTransform transform, + Rectangle outputClip, + Object renderHelper, + Effect defaultInput) { - // Trivially, this effect simply modifies the colors of the pixels - // of the input on a 1:1 basis so the input clip is the same as the - // output clip. - return outputClip; + return RenderState.RenderSpaceRenderState; } @Override --- old/modules/graphics/src/main/java/com/sun/scenario/effect/ZoomRadialBlur.java 2014-02-21 17:02:28.000000000 -0800 +++ new/modules/graphics/src/main/java/com/sun/scenario/effect/ZoomRadialBlur.java 2014-02-21 17:02:28.000000000 -0800 @@ -30,6 +30,7 @@ import com.sun.javafx.geom.DirtyRegionPool; import com.sun.javafx.geom.Rectangle; import com.sun.javafx.geom.transform.BaseTransform; +import com.sun.scenario.effect.impl.state.RenderState; /** * Zoom radial blur effect with a configurable center and radius of the kernel. @@ -189,27 +190,25 @@ public ImageData filterImageDatas(FilterContext fctx, BaseTransform transform, Rectangle outputClip, + RenderState rstate, ImageData... inputs) { Rectangle bnd = inputs[0].getUntransformedBounds(); state.updateDeltas(1f/bnd.width, 1f/bnd.height); - return super.filterImageDatas(fctx, transform, outputClip, inputs); + return super.filterImageDatas(fctx, transform, outputClip, rstate, inputs); } @Override - public boolean operatesInUserSpace() { - return true; - } - - @Override - protected Rectangle getInputClip(int inputIndex, - BaseTransform transform, - Rectangle outputClip) + public RenderState getRenderState(FilterContext fctx, + BaseTransform transform, + Rectangle outputClip, + Object renderHelper, + Effect defaultInput) { // This effect does not appear to expand bounds as it operates, // the blur may zoom "outward", but only to the edge of the // original image... - return outputClip; + return RenderState.UserSpaceRenderState; } @Override --- old/modules/graphics/src/main/java/com/sun/scenario/effect/impl/EffectPeer.java 2014-02-21 17:02:29.000000000 -0800 +++ new/modules/graphics/src/main/java/com/sun/scenario/effect/impl/EffectPeer.java 2014-02-21 17:02:29.000000000 -0800 @@ -32,16 +32,21 @@ import com.sun.javafx.geom.Rectangle; import com.sun.javafx.geom.transform.BaseTransform; import com.sun.javafx.geom.transform.NoninvertibleTransformException; +import com.sun.scenario.effect.impl.state.RenderState; /** * The abstract base class for all {@code Effect} implementation peers. + * + * @param an optional subclass of RenderState that can be assumed as the + * return value for the getRenderState() method */ -public abstract class EffectPeer { +public abstract class EffectPeer { private final FilterContext fctx; private final Renderer renderer; private final String uniqueName; private Effect effect; + private T renderState; private int pass; protected EffectPeer(FilterContext fctx, Renderer renderer, String uniqueName) { @@ -58,6 +63,7 @@ } public abstract ImageData filter(Effect effect, + T renderState, BaseTransform transform, Rectangle outputClip, ImageData... inputs); @@ -101,6 +107,14 @@ this.effect = effect; } + protected T getRenderState() { + return renderState; + } + + protected void setRenderState(T renderState) { + this.renderState = renderState; + } + public final int getPass() { return pass; } --- old/modules/graphics/src/main/java/com/sun/scenario/effect/impl/prism/PrCropPeer.java 2014-02-21 17:02:30.000000000 -0800 +++ new/modules/graphics/src/main/java/com/sun/scenario/effect/impl/prism/PrCropPeer.java 2014-02-21 17:02:30.000000000 -0800 @@ -33,6 +33,7 @@ import com.sun.scenario.effect.ImageData; import com.sun.scenario.effect.impl.EffectPeer; import com.sun.scenario.effect.impl.Renderer; +import com.sun.scenario.effect.impl.state.RenderState; public class PrCropPeer extends EffectPeer { @@ -42,6 +43,7 @@ @Override public ImageData filter(Effect effect, + RenderState rstate, BaseTransform transform, Rectangle outputClip, ImageData... inputs) --- old/modules/graphics/src/main/java/com/sun/scenario/effect/impl/prism/PrFloodPeer.java 2014-02-21 17:02:31.000000000 -0800 +++ new/modules/graphics/src/main/java/com/sun/scenario/effect/impl/prism/PrFloodPeer.java 2014-02-21 17:02:31.000000000 -0800 @@ -36,6 +36,7 @@ import com.sun.scenario.effect.ImageData; import com.sun.scenario.effect.impl.EffectPeer; import com.sun.scenario.effect.impl.Renderer; +import com.sun.scenario.effect.impl.state.RenderState; public class PrFloodPeer extends EffectPeer { @@ -45,6 +46,7 @@ @Override public ImageData filter(Effect effect, + RenderState rstate, BaseTransform transform, Rectangle outputClip, ImageData... inputs) --- old/modules/graphics/src/main/java/com/sun/scenario/effect/impl/prism/PrMergePeer.java 2014-02-21 17:02:32.000000000 -0800 +++ new/modules/graphics/src/main/java/com/sun/scenario/effect/impl/prism/PrMergePeer.java 2014-02-21 17:02:32.000000000 -0800 @@ -34,6 +34,7 @@ import com.sun.javafx.geom.Rectangle; import com.sun.javafx.geom.transform.BaseTransform; import com.sun.prism.Graphics; +import com.sun.scenario.effect.impl.state.RenderState; public class PrMergePeer extends EffectPeer { @@ -43,6 +44,7 @@ @Override public ImageData filter(Effect effect, + RenderState rstate, BaseTransform transform, Rectangle outputClip, ImageData... inputs) --- old/modules/graphics/src/main/java/com/sun/scenario/effect/impl/prism/PrReflectionPeer.java 2014-02-21 17:02:33.000000000 -0800 +++ new/modules/graphics/src/main/java/com/sun/scenario/effect/impl/prism/PrReflectionPeer.java 2014-02-21 17:02:33.000000000 -0800 @@ -35,6 +35,7 @@ import com.sun.scenario.effect.Reflection; import com.sun.scenario.effect.impl.EffectPeer; import com.sun.scenario.effect.impl.Renderer; +import com.sun.scenario.effect.impl.state.RenderState; public class PrReflectionPeer extends EffectPeer { @@ -44,6 +45,7 @@ @Override public ImageData filter(Effect effect, + RenderState rstate, BaseTransform transform, Rectangle outputClip, ImageData... inputs) --- old/modules/graphics/src/main/java/com/sun/scenario/effect/impl/prism/ps/PPSEffectPeer.java 2014-02-21 17:02:34.000000000 -0800 +++ new/modules/graphics/src/main/java/com/sun/scenario/effect/impl/prism/ps/PPSEffectPeer.java 2014-02-21 17:02:34.000000000 -0800 @@ -33,8 +33,9 @@ import com.sun.scenario.effect.ImageData; import com.sun.scenario.effect.impl.EffectPeer; import com.sun.scenario.effect.impl.Renderer; +import com.sun.scenario.effect.impl.state.RenderState; -public abstract class PPSEffectPeer extends EffectPeer { +public abstract class PPSEffectPeer extends EffectPeer { protected PPSEffectPeer(FilterContext fctx, Renderer r, String shaderName) { super(fctx, r, shaderName); @@ -42,11 +43,13 @@ @Override public final ImageData filter(final Effect effect, + final T renderState, final BaseTransform transform, final Rectangle outputClip, final ImageData... inputs) { setEffect(effect); + setRenderState(renderState); setDestBounds(getResultBounds(transform, outputClip, inputs)); return filterImpl(inputs); } --- old/modules/graphics/src/main/java/com/sun/scenario/effect/impl/prism/ps/PPSOneSamplerPeer.java 2014-02-21 17:02:35.000000000 -0800 +++ new/modules/graphics/src/main/java/com/sun/scenario/effect/impl/prism/ps/PPSOneSamplerPeer.java 2014-02-21 17:02:35.000000000 -0800 @@ -35,8 +35,9 @@ import com.sun.scenario.effect.ImageData; import com.sun.scenario.effect.impl.Renderer; import com.sun.scenario.effect.impl.prism.PrTexture; +import com.sun.scenario.effect.impl.state.RenderState; -public abstract class PPSOneSamplerPeer extends PPSEffectPeer { +public abstract class PPSOneSamplerPeer extends PPSEffectPeer { private Shader shader; --- old/modules/graphics/src/main/java/com/sun/scenario/effect/impl/prism/ps/PPStoPSWDisplacementMapPeer.java 2014-02-21 17:02:36.000000000 -0800 +++ new/modules/graphics/src/main/java/com/sun/scenario/effect/impl/prism/ps/PPStoPSWDisplacementMapPeer.java 2014-02-21 17:02:36.000000000 -0800 @@ -36,6 +36,7 @@ import com.sun.scenario.effect.impl.prism.PrDrawable; import com.sun.scenario.effect.impl.prism.PrRenderer; import com.sun.scenario.effect.impl.prism.PrTexture; +import com.sun.scenario.effect.impl.state.RenderState; public class PPStoPSWDisplacementMapPeer extends EffectPeer { PrRenderer softwareRenderer; @@ -48,8 +49,11 @@ } @Override - public ImageData filter(Effect effect, BaseTransform transform, - Rectangle outputClip, ImageData... inputs) + public ImageData filter(Effect effect, + RenderState rstate, + BaseTransform transform, + Rectangle outputClip, + ImageData... inputs) { ImageData input = inputs[0]; PrTexture srcTex = (PrTexture) input.getUntransformedImage(); @@ -62,7 +66,7 @@ // The software peer will return a PrDrawable that can produce a // prism Texture on demand as needed. - ImageData ret = softwarePeer.filter(effect, transform, outputClip, heapinput); + ImageData ret = softwarePeer.filter(effect, rstate, transform, outputClip, heapinput); // Note that heapinput should not be unref()ed since it shares the // rtt with input/srcTex and we do not want it to dispose the rtt. return ret; --- old/modules/graphics/src/main/java/com/sun/scenario/effect/impl/sw/java/JSWBoxBlurPeer.java 2014-02-21 17:02:37.000000000 -0800 +++ new/modules/graphics/src/main/java/com/sun/scenario/effect/impl/sw/java/JSWBoxBlurPeer.java 2014-02-21 17:02:37.000000000 -0800 @@ -33,38 +33,33 @@ import com.sun.scenario.effect.Effect; import com.sun.scenario.effect.FilterContext; import com.sun.scenario.effect.ImageData; -import com.sun.scenario.effect.BoxBlur; import com.sun.scenario.effect.impl.HeapImage; import com.sun.scenario.effect.impl.Renderer; import com.sun.javafx.geom.Rectangle; import com.sun.javafx.geom.transform.BaseTransform; +import com.sun.scenario.effect.impl.state.BoxRenderState; -public class JSWBoxBlurPeer extends JSWEffectPeer { +public class JSWBoxBlurPeer extends JSWEffectPeer { public JSWBoxBlurPeer(FilterContext fctx, Renderer r, String uniqueName) { super(fctx, r, uniqueName); } @Override - protected final BoxBlur getEffect() { - return (BoxBlur)super.getEffect(); - } - - @Override public ImageData filter(Effect effect, + BoxRenderState brstate, BaseTransform transform, Rectangle outputClip, ImageData... inputs) { + setRenderState(brstate); // NOTE: for now, all input images must be TYPE_INT_ARGB_PRE - setEffect(effect); - Rectangle dstBounds = getResultBounds(transform, outputClip, inputs); // Calculate the amount the image grows on each iteration (size-1) boolean horizontal = (getPass() == 0); - int hinc = horizontal ? getEffect().getHorizontalSize() - 1 : 0; - int vinc = horizontal ? 0 : getEffect().getVerticalSize() - 1; - int iterations = getEffect().getPasses(); + int hinc = horizontal ? brstate.getBoxPixelSize(0) - 1 : 0; + int vinc = horizontal ? 0 : brstate.getBoxPixelSize(1) - 1; + int iterations = brstate.getBlurPasses(); if (iterations < 1 || (hinc < 1 && vinc < 1)) { inputs[0].addref(); return inputs[0]; @@ -74,7 +69,7 @@ int growx = (hinc * iterations + 1) & (~0x1); int growy = (vinc * iterations + 1) & (~0x1); - // Assert: ((FilterEffect) effect).operatesInUserSpace()... + // Assert: rstate.getEffectTransformSpace() == UserSpace // NOTE: We could still have a transformed ImageData for other reasons... HeapImage src = (HeapImage)inputs[0].getUntransformedImage(); Rectangle srcr = inputs[0].getUntransformedBounds(); --- old/modules/graphics/src/main/java/com/sun/scenario/effect/impl/sw/java/JSWBoxShadowPeer.java 2014-02-21 17:02:38.000000000 -0800 +++ new/modules/graphics/src/main/java/com/sun/scenario/effect/impl/sw/java/JSWBoxShadowPeer.java 2014-02-21 17:02:38.000000000 -0800 @@ -38,36 +38,32 @@ import com.sun.scenario.effect.impl.Renderer; import com.sun.javafx.geom.Rectangle; import com.sun.javafx.geom.transform.BaseTransform; +import com.sun.scenario.effect.impl.state.BoxRenderState; -public class JSWBoxShadowPeer extends JSWEffectPeer { +public class JSWBoxShadowPeer extends JSWEffectPeer { public JSWBoxShadowPeer(FilterContext fctx, Renderer r, String uniqueName) { super(fctx, r, uniqueName); } @Override - protected final BoxShadow getEffect() { - return (BoxShadow)super.getEffect(); - } - - @Override public ImageData filter(Effect effect, + BoxRenderState brstate, BaseTransform transform, Rectangle outputClip, ImageData... inputs) { + setRenderState(brstate); // NOTE: for now, all input images must be TYPE_INT_ARGB_PRE - setEffect(effect); - Rectangle dstBounds = getResultBounds(transform, outputClip, inputs); // Calculate the amount the image grows on each iteration (size-1) boolean horizontal = (getPass() == 0); - int hinc = horizontal ? getEffect().getHorizontalSize() - 1 : 0; - int vinc = horizontal ? 0 : getEffect().getVerticalSize() - 1; + int hinc = horizontal ? brstate.getBoxPixelSize(0) - 1 : 0; + int vinc = horizontal ? 0 : brstate.getBoxPixelSize(1) - 1; if (hinc < 0) hinc = 0; if (vinc < 0) vinc = 0; - int iterations = getEffect().getPasses(); - float spread = getEffect().getSpread(); + int iterations = brstate.getBlurPasses(); + float spread = brstate.getSpread(); if (horizontal && (iterations < 1 || (hinc < 1 && vinc < 1))) { inputs[0].addref(); return inputs[0]; @@ -77,7 +73,7 @@ int growx = (hinc * iterations + 1) & (~0x1); int growy = (vinc * iterations + 1) & (~0x1); - // Assert: ((FilterEffect) effect).operatesInUserSpace()... + // Assert: rstate.getEffectTransformSpace() == UserSpace // NOTE: We could still have a transformed ImageData for other reasons... HeapImage src = (HeapImage)inputs[0].getUntransformedImage(); Rectangle srcr = inputs[0].getUntransformedBounds(); @@ -114,7 +110,7 @@ spread); } else { float shadowColor[] = - getEffect().getColor().getPremultipliedRGBComponents(); + brstate.getShadowColor().getPremultipliedRGBComponents(); if (shadowColor[3] == 1f && shadowColor[0] == 0f && shadowColor[1] == 0f && --- old/modules/graphics/src/main/java/com/sun/scenario/effect/impl/sw/java/JSWEffectPeer.java 2014-02-21 17:02:39.000000000 -0800 +++ new/modules/graphics/src/main/java/com/sun/scenario/effect/impl/sw/java/JSWEffectPeer.java 2014-02-21 17:02:39.000000000 -0800 @@ -28,8 +28,9 @@ import com.sun.scenario.effect.FilterContext; import com.sun.scenario.effect.impl.EffectPeer; import com.sun.scenario.effect.impl.Renderer; +import com.sun.scenario.effect.impl.state.RenderState; -public abstract class JSWEffectPeer extends EffectPeer { +public abstract class JSWEffectPeer extends EffectPeer { protected JSWEffectPeer(FilterContext fctx, Renderer r, String uniqueName) { super(fctx, r, uniqueName); --- old/modules/graphics/src/main/java/com/sun/scenario/effect/impl/sw/java/JSWLinearConvolvePeer.java 2014-02-21 17:02:40.000000000 -0800 +++ new/modules/graphics/src/main/java/com/sun/scenario/effect/impl/sw/java/JSWLinearConvolvePeer.java 2014-02-21 17:02:40.000000000 -0800 @@ -38,67 +38,34 @@ import com.sun.scenario.effect.impl.Renderer; import com.sun.javafx.geom.Rectangle; import com.sun.javafx.geom.transform.BaseTransform; -import com.sun.scenario.effect.impl.state.AccessHelper; -import com.sun.scenario.effect.impl.state.LinearConvolveKernel; -import com.sun.scenario.effect.impl.state.LinearConvolveKernel.PassType; -import com.sun.scenario.effect.impl.state.LinearConvolvePeer; +import com.sun.scenario.effect.impl.state.LinearConvolveRenderState; +import com.sun.scenario.effect.impl.state.LinearConvolveRenderState.PassType; -public class JSWLinearConvolvePeer extends JSWEffectPeer implements LinearConvolvePeer { +public class JSWLinearConvolvePeer extends JSWEffectPeer { public JSWLinearConvolvePeer(FilterContext fctx, Renderer r, String uniqueName) { super(fctx, r, uniqueName); } - @Override - protected final Effect getEffect() { - return (Effect)super.getEffect(); - } - - protected LinearConvolveKernel getKernel() { - return (LinearConvolveKernel) AccessHelper.getState(getEffect()); - } - - public int getPow2ScaleX(LinearConvolveKernel kernel) { - return kernel.getPow2ScaleX(); - } - - public int getPow2ScaleY(LinearConvolveKernel kernel) { - return kernel.getPow2ScaleY(); - } - - @Override - public Rectangle getResultBounds(BaseTransform transform, - Rectangle outputClip, - ImageData... inputDatas) + private Rectangle getResultBounds(LinearConvolveRenderState lcrstate, + Rectangle outputClip, + ImageData... inputDatas) { Rectangle r = inputDatas[0].getTransformedBounds(null); - r = getKernel().getScaledResultBounds(r, getPass()); + r = lcrstate.getPassResultBounds(r); r.intersectWith(outputClip); return r; } - private int getCount() { - return getKernel().getScaledKernelSize(getPass()); - } - - private float[] getOffset() { - return getKernel().getVector(getInputNativeBounds(0), - getInputTransform(0), - getPass()); - } - - private FloatBuffer getWeights() { - return getKernel().getWeights(getPass()); - } - @Override public ImageData filter(Effect effect, + LinearConvolveRenderState lcrstate, BaseTransform transform, Rectangle outputClip, ImageData... inputs) { - setEffect(effect); - Rectangle dstRawBounds = getResultBounds(transform, null, inputs); + setRenderState(lcrstate); + Rectangle dstRawBounds = getResultBounds(lcrstate, null, inputs); Rectangle dstBounds = new Rectangle(dstRawBounds); dstBounds.intersectWith(outputClip); setDestBounds(dstBounds); @@ -115,7 +82,7 @@ Rectangle src0Bounds = inputs[0].getUntransformedBounds(); BaseTransform src0Transform = inputs[0].getTransform(); Rectangle src0NativeBounds = new Rectangle(0, 0, srcw, srch); - // Assert: ((FilterEffect) effect).operatesInUserSpace()... + // Assert: rstate.getEffectTransformSpace() == UserSpace setInputBounds(0, src0Bounds); setInputTransform(0, src0Transform); setInputNativeBounds(0, src0NativeBounds); @@ -125,10 +92,10 @@ int dstscan = dst.getScanlineStride(); int[] dstPixels = dst.getPixelArray(); - int count = getCount(); - FloatBuffer weights_buf = getWeights(); + int count = lcrstate.getPassKernelSize(); + FloatBuffer weights_buf = lcrstate.getPassWeights(); - PassType type = getKernel().getPassType(getPass()); + PassType type = lcrstate.getPassType(); if (!src0Transform.isIdentity() || !dstBounds.contains(dstRawBounds.x, dstRawBounds.y)) { @@ -137,7 +104,10 @@ // and transforms... type = PassType.GENERAL_VECTOR; } - type = PassType.GENERAL_VECTOR; + if (count >= 0) { + // REMIND: Why was this hard-coded? + type = PassType.GENERAL_VECTOR; + } if (type == PassType.HORIZONTAL_CENTERED) { float[] weights_arr = new float[count * 2]; weights_buf.get(weights_arr, 0, count); @@ -179,7 +149,7 @@ dyrow = (srcRect[7] - srcRect[1]) * srch / dstBounds.height; } - float[] offset_arr = getOffset(); + float[] offset_arr = lcrstate.getPassVector(); float deltax = offset_arr[0] * srcw; float deltay = offset_arr[1] * srch; float offsetx = offset_arr[2] * srcw; @@ -241,7 +211,7 @@ } } - /** + /* * In the nomenclature of the argument list for this method, "row" refers * to the coordinate which increments once for each new stream of single * axis data that we are blurring in a single pass. And "col" refers to --- old/modules/graphics/src/main/java/com/sun/scenario/effect/impl/sw/java/JSWLinearConvolveShadowPeer.java 2014-02-21 17:02:41.000000000 -0800 +++ new/modules/graphics/src/main/java/com/sun/scenario/effect/impl/sw/java/JSWLinearConvolveShadowPeer.java 2014-02-21 17:02:41.000000000 -0800 @@ -39,7 +39,7 @@ } private float[] getShadowColor() { - return getKernel().getShadowColorComponents(getPass()); + return getRenderState().getPassShadowColorComponents(); } @Override @@ -91,7 +91,7 @@ } } - /** + /* * In the nomenclature of the argument list for this method, "row" refers * to the coordinate which increments once for each new stream of single * axis data that we are blurring in a single pass. And "col" refers to --- old/modules/graphics/src/main/java/com/sun/scenario/effect/impl/sw/sse/SSEBoxBlurPeer.java 2014-02-21 17:02:42.000000000 -0800 +++ new/modules/graphics/src/main/java/com/sun/scenario/effect/impl/sw/sse/SSEBoxBlurPeer.java 2014-02-21 17:02:42.000000000 -0800 @@ -33,39 +33,34 @@ import com.sun.scenario.effect.Effect; import com.sun.scenario.effect.FilterContext; import com.sun.scenario.effect.ImageData; -import com.sun.scenario.effect.BoxBlur; import com.sun.scenario.effect.impl.HeapImage; import com.sun.scenario.effect.impl.Renderer; import com.sun.javafx.geom.Rectangle; import com.sun.javafx.geom.transform.BaseTransform; +import com.sun.scenario.effect.impl.state.BoxRenderState; -public class SSEBoxBlurPeer extends SSEEffectPeer { +public class SSEBoxBlurPeer extends SSEEffectPeer { public SSEBoxBlurPeer(FilterContext fctx, Renderer r, String uniqueName) { super(fctx, r, uniqueName); } @Override - protected final BoxBlur getEffect() { - return (BoxBlur)super.getEffect(); - } - - @Override public ImageData filter(Effect effect, + BoxRenderState brstate, BaseTransform transform, Rectangle outputClip, ImageData... inputs) { - setEffect(effect); - + setRenderState(brstate); // NOTE: for now, all input images must be TYPE_INT_ARGB_PRE boolean horizontal = (getPass() == 0); // Calculate the amount the image grows on each iteration (size-1) - int hinc = horizontal ? getEffect().getHorizontalSize() - 1 : 0; - int vinc = horizontal ? 0 : getEffect().getVerticalSize() - 1; - int iterations = getEffect().getPasses(); + int hinc = horizontal ? brstate.getBoxPixelSize(0) - 1 : 0; + int vinc = horizontal ? 0 : brstate.getBoxPixelSize(1) - 1; + int iterations = brstate.getBlurPasses(); if (iterations < 1 || (hinc < 1 && vinc < 1)) { inputs[0].addref(); return inputs[0]; @@ -75,7 +70,7 @@ int growx = (hinc * iterations + 1) & (~0x1); int growy = (vinc * iterations + 1) & (~0x1); - // Assert: ((FilterEffect) effect).operatesInUserSpace()... + // Assert: rstate.getEffectTransformSpace() == UserSpace // NOTE: We could still have a transformed ImageData for other reasons... HeapImage src = (HeapImage)inputs[0].getUntransformedImage(); Rectangle srcr = inputs[0].getUntransformedBounds(); --- old/modules/graphics/src/main/java/com/sun/scenario/effect/impl/sw/sse/SSEBoxShadowPeer.java 2014-02-21 17:02:43.000000000 -0800 +++ new/modules/graphics/src/main/java/com/sun/scenario/effect/impl/sw/sse/SSEBoxShadowPeer.java 2014-02-21 17:02:43.000000000 -0800 @@ -38,35 +38,32 @@ import com.sun.scenario.effect.impl.Renderer; import com.sun.javafx.geom.Rectangle; import com.sun.javafx.geom.transform.BaseTransform; +import com.sun.scenario.effect.impl.state.BoxRenderState; -public class SSEBoxShadowPeer extends SSEEffectPeer { +public class SSEBoxShadowPeer extends SSEEffectPeer { public SSEBoxShadowPeer(FilterContext fctx, Renderer r, String uniqueName) { super(fctx, r, uniqueName); } @Override - protected final BoxShadow getEffect() { - return (BoxShadow)super.getEffect(); - } - - @Override public ImageData filter(Effect effect, + BoxRenderState brstate, BaseTransform transform, Rectangle outputClip, ImageData... inputs) { + setRenderState(brstate); // NOTE: for now, all input images must be TYPE_INT_ARGB_PRE - setEffect(effect); // Calculate the amount the image grows on each iteration (size-1) boolean horizontal = (getPass() == 0); - int hinc = horizontal ? getEffect().getHorizontalSize() - 1 : 0; - int vinc = horizontal ? 0 : getEffect().getVerticalSize() - 1; + int hinc = horizontal ? brstate.getBoxPixelSize(0) - 1 : 0; + int vinc = horizontal ? 0 : brstate.getBoxPixelSize(1) - 1; if (hinc < 0) hinc = 0; if (vinc < 0) vinc = 0; - int iterations = getEffect().getPasses(); - float spread = getEffect().getSpread(); + int iterations = brstate.getBlurPasses(); + float spread = brstate.getSpread(); if (horizontal && (iterations < 1 || (hinc < 1 && vinc < 1))) { inputs[0].addref(); return inputs[0]; @@ -76,7 +73,7 @@ int growx = (hinc * iterations + 1) & (~0x1); int growy = (vinc * iterations + 1) & (~0x1); - // Assert: ((FilterEffect) effect).operatesInUserSpace()... + // Assert: rstate.getEffectTransformSpace() == UserSpace // NOTE: We could still have a transformed ImageData for other reasons... HeapImage src = (HeapImage)inputs[0].getUntransformedImage(); Rectangle srcr = inputs[0].getUntransformedBounds(); @@ -113,7 +110,7 @@ spread); } else { float shadowColor[] = - getEffect().getColor().getPremultipliedRGBComponents(); + brstate.getShadowColor().getPremultipliedRGBComponents(); if (shadowColor[3] == 1f && shadowColor[0] == 0f && shadowColor[1] == 0f && --- old/modules/graphics/src/main/java/com/sun/scenario/effect/impl/sw/sse/SSEEffectPeer.java 2014-02-21 17:02:44.000000000 -0800 +++ new/modules/graphics/src/main/java/com/sun/scenario/effect/impl/sw/sse/SSEEffectPeer.java 2014-02-21 17:02:44.000000000 -0800 @@ -28,8 +28,9 @@ import com.sun.scenario.effect.FilterContext; import com.sun.scenario.effect.impl.EffectPeer; import com.sun.scenario.effect.impl.Renderer; +import com.sun.scenario.effect.impl.state.RenderState; -public abstract class SSEEffectPeer extends EffectPeer { +public abstract class SSEEffectPeer extends EffectPeer { protected SSEEffectPeer(FilterContext fctx, Renderer r, String uniqueName) { super(fctx, r, uniqueName); --- old/modules/graphics/src/main/java/com/sun/scenario/effect/impl/sw/sse/SSELinearConvolvePeer.java 2014-02-21 17:02:45.000000000 -0800 +++ new/modules/graphics/src/main/java/com/sun/scenario/effect/impl/sw/sse/SSELinearConvolvePeer.java 2014-02-21 17:02:45.000000000 -0800 @@ -38,64 +38,25 @@ import com.sun.scenario.effect.impl.Renderer; import com.sun.javafx.geom.Rectangle; import com.sun.javafx.geom.transform.BaseTransform; -import com.sun.scenario.effect.impl.state.AccessHelper; -import com.sun.scenario.effect.impl.state.LinearConvolveKernel; -import com.sun.scenario.effect.impl.state.LinearConvolveKernel.PassType; -import com.sun.scenario.effect.impl.state.LinearConvolvePeer; +import com.sun.scenario.effect.impl.state.LinearConvolveRenderState; +import com.sun.scenario.effect.impl.state.LinearConvolveRenderState.PassType; -public class SSELinearConvolvePeer extends SSEEffectPeer implements LinearConvolvePeer { +public class SSELinearConvolvePeer extends SSEEffectPeer { public SSELinearConvolvePeer(FilterContext fctx, Renderer r, String uniqueName) { super(fctx, r, uniqueName); } @Override - protected final Effect getEffect() { - return (Effect)super.getEffect(); - } - - protected LinearConvolveKernel getKernel() { - return (LinearConvolveKernel) AccessHelper.getState(getEffect()); - } - - public int getPow2ScaleX(LinearConvolveKernel kernel) { - return kernel.getPow2ScaleX(); - } - - public int getPow2ScaleY(LinearConvolveKernel kernel) { - return kernel.getPow2ScaleY(); - } - - @Override - public Rectangle getResultBounds(BaseTransform transform, - Rectangle outputClip, - ImageData... inputDatas) - { - return getKernel().getScaledResultBounds(inputDatas[0].getTransformedBounds(outputClip), getPass()); - } - - private int getCount() { - return getKernel().getScaledKernelSize(getPass()); - } - - private float[] getOffset() { - return getKernel().getVector(getInputNativeBounds(0), - getInputTransform(0), - getPass()); - } - - private FloatBuffer getWeights() { - return getKernel().getWeights(getPass()); - } - - @Override public ImageData filter(Effect effect, + LinearConvolveRenderState lcrstate, BaseTransform transform, Rectangle outputClip, ImageData... inputs) { - setEffect(effect); - Rectangle dstRawBounds = getResultBounds(transform, null, inputs); + setRenderState(lcrstate); + Rectangle dstRawBounds = inputs[0].getTransformedBounds(null); + dstRawBounds = lcrstate.getPassResultBounds(dstRawBounds); Rectangle dstBounds = new Rectangle(dstRawBounds); dstBounds.intersectWith(outputClip); setDestBounds(dstBounds); @@ -112,7 +73,7 @@ Rectangle src0Bounds = inputs[0].getUntransformedBounds(); BaseTransform src0Transform = inputs[0].getTransform(); Rectangle src0NativeBounds = new Rectangle(0, 0, srcw, srch); - // Assert: ((FilterEffect) effect).operatesInUserSpace()... + // Assert: rstate.getEffectTransformSpace() == UserSpace setInputBounds(0, src0Bounds); setInputTransform(0, src0Transform); setInputNativeBounds(0, src0NativeBounds); @@ -122,10 +83,10 @@ int dstscan = dst.getScanlineStride(); int[] dstPixels = dst.getPixelArray(); - int count = getCount(); - FloatBuffer weights_buf = getWeights(); + int count = lcrstate.getPassKernelSize(); + FloatBuffer weights_buf = lcrstate.getPassWeights(); - PassType type = getKernel().getPassType(getPass()); + PassType type = lcrstate.getPassType(); if (!src0Transform.isIdentity() || !dstBounds.contains(dstRawBounds.x, dstRawBounds.y)) { @@ -175,7 +136,7 @@ dyrow = (srcRect[7] - srcRect[1]) * srch / dstBounds.height; } - float[] offset_arr = getOffset(); + float[] offset_arr = lcrstate.getPassVector(); float deltax = offset_arr[0] * srcw; float deltay = offset_arr[1] * srch; float offsetx = offset_arr[2] * srcw; @@ -202,7 +163,7 @@ float deltax, float deltay, float dxcol, float dycol, float dxrow, float dyrow); - /** + /* * In the nomenclature of the argument list for this method, "row" refers * to the coordinate which increments once for each new stream of single * axis data that we are blurring in a single pass. And "col" refers to --- old/modules/graphics/src/main/java/com/sun/scenario/effect/impl/sw/sse/SSELinearConvolveShadowPeer.java 2014-02-21 17:02:46.000000000 -0800 +++ new/modules/graphics/src/main/java/com/sun/scenario/effect/impl/sw/sse/SSELinearConvolveShadowPeer.java 2014-02-21 17:02:45.000000000 -0800 @@ -39,7 +39,7 @@ } private float[] getShadowColor() { - return getKernel().getShadowColorComponents(getPass()); + return getRenderState().getPassShadowColorComponents(); } private static native void --- old/modules/graphics/src/main/java/javafx/scene/effect/BoxBlur.java 2014-02-21 17:02:47.000000000 -0800 +++ new/modules/graphics/src/main/java/javafx/scene/effect/BoxBlur.java 2014-02-21 17:02:46.000000000 -0800 @@ -276,12 +276,12 @@ return iterations; } - private int getClampedWidth() { - return Utils.clamp(0, (int) getWidth(), 255); + private float getClampedWidth() { + return (float) Utils.clamp(0, getWidth(), 255); } - private int getClampedHeight() { - return Utils.clamp(0, (int) getHeight(), 255); + private float getClampedHeight() { + return (float) Utils.clamp(0, getHeight(), 255); } private int getClampedIterations() { --- old/modules/graphics/src/main/java/javafx/scene/effect/Effect.java 2014-02-21 17:02:48.000000000 -0800 +++ new/modules/graphics/src/main/java/javafx/scene/effect/Effect.java 2014-02-21 17:02:47.000000000 -0800 @@ -271,7 +271,8 @@ } // utility method used in calculation of bounds in BoxBlur and DropShadow effects - static int getKernelSize(int ksize, int iterations) { + static int getKernelSize(float fsize, int iterations) { + int ksize = (int) Math.ceil(fsize); if (ksize < 1) ksize = 1; ksize = (ksize-1) * iterations + 1; ksize |= 1; --- old/modules/graphics/src/main/java/com/sun/scenario/effect/impl/state/LinearConvolvePeer.java 2014-02-21 17:02:48.000000000 -0800 +++ /dev/null 2014-02-21 17:02:49.000000000 -0800 @@ -1,56 +0,0 @@ -/* - * Copyright (c) 2009, 2013, 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.state; - -public interface LinearConvolvePeer { - /** - * Returns the number of power of 2 scales along the X axis. - * Positive numbers mean to scale the image larger by the indicated - * factors of 2.0. - * Negative numbers mean to scale the image smaller by the indicated - * factors of 0.5. - * Overall the image will be scaled by {@code pow(2.0, getPow2ScaleX())}. - *

- * @param kernel the {@code LinearConvolveKernel} instance for the operation. - * @return the power of 2.0 by which to scale the source image along the - * X axis. - */ - public int getPow2ScaleX(LinearConvolveKernel kernel); - - /** - * Returns the number of power of 2 scales along the Y axis. - * Positive numbers mean to scale the image larger by the indicated - * factors of 2.0. - * Negative numbers mean to scale the image smaller by the indicated - * factors of 0.5. - * Overall the image will be scaled by {@code pow(2.0, getPow2ScaleY())}. - *

- * @param kernel the {@code LinearConvolveKernel} instance for the operation. - * @return the power of 2.0 by which to scale the source image along the - * Y axis. - */ - public int getPow2ScaleY(LinearConvolveKernel kernel); -} --- old/modules/graphics/src/main/java/com/sun/scenario/effect/impl/state/LinearConvolveShadowPeer.java 2014-02-21 17:02:49.000000000 -0800 +++ /dev/null 2014-02-21 17:02:49.000000000 -0800 @@ -1,30 +0,0 @@ -/* - * Copyright (c) 2009, 2013, 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.state; - -public interface LinearConvolveShadowPeer { - -} --- old/modules/graphics/src/main/jsl-decora/CompileLinearConvolve.java 2014-02-21 17:02:50.000000000 -0800 +++ new/modules/graphics/src/main/jsl-decora/CompileLinearConvolve.java 2014-02-21 17:02:49.000000000 -0800 @@ -25,7 +25,7 @@ import com.sun.scenario.effect.compiler.JSLC; import com.sun.scenario.effect.compiler.JSLC.JSLCInfo; -import com.sun.scenario.effect.impl.state.LinearConvolveKernel; +import com.sun.scenario.effect.impl.state.LinearConvolveRenderState; import java.io.File; /** @@ -37,7 +37,7 @@ /* * The basic idea here is to create a few different versions of the * LinearConvolve hardware effect peers, based on the kernel size. - * The LinearConvolveKernel state class contains the algorithm that + * The LinearConvolveRenderState state class contains the algorithm that * determines how many peers should be generated and at which optimized * sizes. */ @@ -51,11 +51,11 @@ long basetime = baseFile.lastModified(); // output one hardware shader for each unrolled size (as determined - // by the LinearConvolveKernel quantization algorithm) + // by the LinearConvolveRenderState quantization algorithm) jslcinfo.outTypes = (outTypes & JSLC.OUT_HW_SHADERS); int lastpeersize = -1; - for (int i = 1; i < LinearConvolveKernel.MAX_KERNEL_SIZE; i += 4) { - int peersize = LinearConvolveKernel.getPeerSize(i); + for (int i = 1; i < LinearConvolveRenderState.MAX_KERNEL_SIZE; i += 4) { + int peersize = LinearConvolveRenderState.getPeerSize(i); if (peersize != lastpeersize) { String source = String.format(base, peersize/4, peersize/4); jslcinfo.peerName = name + "_" + peersize; @@ -68,8 +68,9 @@ // each of the shaders generated above) jslcinfo.outTypes = (outTypes & JSLC.OUT_HW_PEERS); jslcinfo.peerName = name; - jslcinfo.interfaceName = "LinearConvolvePeer"; - int peersize = LinearConvolveKernel.MAX_KERNEL_SIZE / 4; + jslcinfo.genericsName = "LinearConvolveRenderState"; + jslcinfo.interfaceName = null; // "LinearConvolvePeer"; + int peersize = LinearConvolveRenderState.MAX_KERNEL_SIZE / 4; String genericbase = String.format(base, peersize, 0); JSLC.compile(jslcinfo, genericbase, basetime); --- old/buildSrc/src/main/java/com/sun/scenario/effect/compiler/JSLC.java 2014-02-21 17:02:51.000000000 -0800 +++ new/buildSrc/src/main/java/com/sun/scenario/effect/compiler/JSLC.java 2014-02-21 17:02:50.000000000 -0800 @@ -181,6 +181,7 @@ throws Exception { int outTypes = jslcinfo.outTypes; + String genericsName = jslcinfo.genericsName; String interfaceName = jslcinfo.interfaceName; String peerName = jslcinfo.peerName; String shaderName = jslcinfo.shaderName; @@ -210,7 +211,7 @@ if (jslcinfo.force || outOfDate(outFile, sourceTime)) { if (pinfo == null) pinfo = getParserInfo(stream); JSWBackend javaBackend = new JSWBackend(pinfo.parser, pinfo.program); - String genCode = javaBackend.getGenCode(shaderName, peerName, interfaceName); + String genCode = javaBackend.getGenCode(shaderName, peerName, genericsName, interfaceName); write(genCode, outFile); } } @@ -227,7 +228,7 @@ if (pinfo == null) pinfo = getParserInfo(stream); SSEBackend sseBackend = new SSEBackend(pinfo.parser, pinfo.program); SSEBackend.GenCode gen = - sseBackend.getGenCode(shaderName, peerName, interfaceName); + sseBackend.getGenCode(shaderName, peerName, genericsName, interfaceName); // write impl class if (outFileStale) { @@ -253,7 +254,7 @@ if (pinfo == null) pinfo = getParserInfo(stream); MEBackend sseBackend = new MEBackend(pinfo.parser, pinfo.program); MEBackend.GenCode gen = - sseBackend.getGenCode(shaderName, peerName, interfaceName); + sseBackend.getGenCode(shaderName, peerName, genericsName, interfaceName); // write impl class if (outFileStale) { @@ -272,7 +273,7 @@ if (jslcinfo.force || outOfDate(outFile, sourceTime)) { if (pinfo == null) pinfo = getParserInfo(stream); PrismBackend prismBackend = new PrismBackend(pinfo.parser, pinfo.program); - String genCode = prismBackend.getGlueCode(shaderName, peerName, interfaceName); + String genCode = prismBackend.getGlueCode(shaderName, peerName, genericsName, interfaceName); write(genCode, outFile); } } @@ -312,6 +313,7 @@ public List srcDirs = new ArrayList(); public String shaderName; public String peerName; + public String genericsName; public String interfaceName; public String pkgName = rootPkg; public Map outNameMap = initDefaultInfoMap(); --- old/buildSrc/src/main/java/com/sun/scenario/effect/compiler/backend/prism/PrismBackend.java 2014-02-21 17:02:51.000000000 -0800 +++ new/buildSrc/src/main/java/com/sun/scenario/effect/compiler/backend/prism/PrismBackend.java 2014-02-21 17:02:51.000000000 -0800 @@ -62,9 +62,11 @@ public String getGlueCode(String effectName, String peerName, + String genericsName, String interfaceName) { Map vars = parser.getSymbolTable().getGlobalVariables(); + StringBuilder genericsDecl = new StringBuilder(); StringBuilder interfaceDecl = new StringBuilder(); StringBuilder samplerLinear = new StringBuilder(); StringBuilder samplerInit = new StringBuilder(); @@ -116,6 +118,10 @@ throw new RuntimeException("Must use zero, one, or two samplers (for now)"); } + if (genericsName != null) { + genericsDecl.append("<"+genericsName+">"); + } + if (interfaceName != null) { interfaceDecl.append("implements "+interfaceName); } @@ -124,6 +130,7 @@ glue.setAttribute("effectName", effectName); glue.setAttribute("peerName", peerName); glue.setAttribute("superClass", superClass); + glue.setAttribute("genericsDecl", genericsDecl.toString()); glue.setAttribute("interfaceDecl", interfaceDecl.toString()); glue.setAttribute("usercode", usercode.toString()); glue.setAttribute("samplerLinear", samplerLinear.toString()); --- old/buildSrc/src/main/java/com/sun/scenario/effect/compiler/backend/sw/java/JSWBackend.java 2014-02-21 17:02:52.000000000 -0800 +++ new/buildSrc/src/main/java/com/sun/scenario/effect/compiler/backend/sw/java/JSWBackend.java 2014-02-21 17:02:52.000000000 -0800 @@ -63,9 +63,11 @@ public final String getGenCode(String effectName, String peerName, + String genericsName, String interfaceName) { Map vars = parser.getSymbolTable().getGlobalVariables(); + StringBuilder genericsDecl = new StringBuilder(); StringBuilder interfaceDecl = new StringBuilder(); StringBuilder constants = new StringBuilder(); StringBuilder samplers = new StringBuilder(); @@ -208,7 +210,11 @@ posIncrY.append("pos" + i + "_y += inc" + i + "_y;\n"); } } - + + if (genericsName != null) { + genericsDecl.append("<"+genericsName+">"); + } + if (interfaceName != null) { interfaceDecl.append("implements "+interfaceName); } @@ -218,6 +224,7 @@ StringTemplate glue = group.getInstanceOf("glue"); glue.setAttribute("effectName", effectName); glue.setAttribute("peerName", peerName); + glue.setAttribute("genericsDecl", genericsDecl.toString()); glue.setAttribute("interfaceDecl", interfaceDecl.toString()); glue.setAttribute("usercode", usercode.toString()); glue.setAttribute("samplers", samplers.toString()); --- old/buildSrc/src/main/java/com/sun/scenario/effect/compiler/backend/sw/me/MEBackend.java 2014-02-21 17:02:53.000000000 -0800 +++ new/buildSrc/src/main/java/com/sun/scenario/effect/compiler/backend/sw/me/MEBackend.java 2014-02-21 17:02:53.000000000 -0800 @@ -79,9 +79,11 @@ public final GenCode getGenCode(String effectName, String peerName, + String genericsName, String interfaceName) { Map vars = parser.getSymbolTable().getGlobalVariables(); + StringBuilder genericsDecl = new StringBuilder(); StringBuilder interfaceDecl = new StringBuilder(); StringBuilder constants = new StringBuilder(); StringBuilder samplers = new StringBuilder(); @@ -264,6 +266,10 @@ } } + if (genericsName != null) { + genericsDecl.append("<"+genericsName+">"); + } + if (interfaceName != null) { interfaceDecl.append("implements "+interfaceName); } @@ -273,6 +279,7 @@ StringTemplate jglue = group.getInstanceOf("glue"); jglue.setAttribute("effectName", effectName); jglue.setAttribute("peerName", peerName); + jglue.setAttribute("genericsDecl", genericsDecl.toString()); jglue.setAttribute("interfaceDecl", interfaceDecl.toString()); jglue.setAttribute("usercode", usercode.toString()); jglue.setAttribute("samplers", samplers.toString()); --- old/buildSrc/src/main/java/com/sun/scenario/effect/compiler/backend/sw/sse/SSEBackend.java 2014-02-21 17:02:54.000000000 -0800 +++ new/buildSrc/src/main/java/com/sun/scenario/effect/compiler/backend/sw/sse/SSEBackend.java 2014-02-21 17:02:54.000000000 -0800 @@ -94,9 +94,11 @@ public final GenCode getGenCode(String effectName, String peerName, + String genericsName, String interfaceName) { Map vars = parser.getSymbolTable().getGlobalVariables(); + StringBuilder genericsDecl = new StringBuilder(); StringBuilder interfaceDecl = new StringBuilder(); StringBuilder constants = new StringBuilder(); StringBuilder samplers = new StringBuilder(); @@ -309,6 +311,10 @@ } } + if (genericsName != null) { + genericsDecl.append("<"+genericsName+">"); + } + if (interfaceName != null) { interfaceDecl.append("implements "+interfaceName); } @@ -318,6 +324,7 @@ StringTemplate jglue = group.getInstanceOf("glue"); jglue.setAttribute("effectName", effectName); jglue.setAttribute("peerName", peerName); + jglue.setAttribute("genericsDecl", genericsDecl.toString()); jglue.setAttribute("interfaceDecl", interfaceDecl.toString()); jglue.setAttribute("usercode", usercode.toString()); jglue.setAttribute("samplers", samplers.toString()); --- old/buildSrc/src/main/resources/com/sun/scenario/effect/compiler/backend/prism/PrismGlue.stg 2014-02-21 17:02:55.000000000 -0800 +++ new/buildSrc/src/main/resources/com/sun/scenario/effect/compiler/backend/prism/PrismGlue.stg 2014-02-21 17:02:55.000000000 -0800 @@ -1,6 +1,6 @@ group PrismGlue; -glue(effectName,peerName,superClass,interfaceDecl, +glue(effectName,peerName,superClass,genericsDecl,interfaceDecl, usercode,samplerLinear,samplerInit, paramInit,paramUpdate,isPixcoordUsed) ::= << /* @@ -46,7 +46,7 @@ import com.sun.scenario.effect.impl.state.*; import com.sun.javafx.geom.Rectangle; -public class PPS$peerName$Peer extends $superClass$ $interfaceDecl$ { +public class PPS$peerName$Peer extends $superClass$$genericsDecl$ $interfaceDecl$ { public PPS$peerName$Peer(FilterContext fctx, Renderer r, String shaderName) { super(fctx, r, shaderName); --- old/buildSrc/src/main/resources/com/sun/scenario/effect/compiler/backend/sw/java/JSWGlue.stg 2014-02-21 17:02:56.000000000 -0800 +++ new/buildSrc/src/main/resources/com/sun/scenario/effect/compiler/backend/sw/java/JSWGlue.stg 2014-02-21 17:02:56.000000000 -0800 @@ -1,6 +1,6 @@ group JSWGlue; -glue(effectName,peerName,interfaceDecl, +glue(effectName,peerName,genericsDecl,interfaceDecl, usercode,samplers,cleanup,srcRects,constants, pixInitY,pixInitX,posDecls,posInitY,posIncrY,posInitX,posIncrX, body) ::= << @@ -49,7 +49,7 @@ import com.sun.javafx.geom.Rectangle; import com.sun.javafx.geom.transform.BaseTransform; -public class JSW$peerName$Peer extends JSWEffectPeer $interfaceDecl$ { +public class JSW$peerName$Peer extends JSWEffectPeer$genericsDecl$ $interfaceDecl$ { public JSW$peerName$Peer(FilterContext fctx, Renderer r, String uniqueName) { super(fctx, r, uniqueName); @@ -64,6 +64,7 @@ @Override public ImageData filter(Effect effect, + RenderState rstate, BaseTransform transform, Rectangle outputClip, ImageData... inputs) --- old/buildSrc/src/main/resources/com/sun/scenario/effect/compiler/backend/sw/me/MEJavaGlue.stg 2014-02-21 17:02:57.000000000 -0800 +++ new/buildSrc/src/main/resources/com/sun/scenario/effect/compiler/backend/sw/me/MEJavaGlue.stg 2014-02-21 17:02:57.000000000 -0800 @@ -1,6 +1,6 @@ group MEJavaGlue; -glue(effectName,peerName,interfaceDecl, +glue(effectName,peerName,genericsDecl,interfaceDecl, usercode,samplers,srcRects,constants,params,paramDecls) ::= << /* * Copyright (c) 2008, 2013, Oracle and/or its affiliates. All rights reserved. @@ -49,7 +49,7 @@ import com.sun.javafx.geom.AffineTransform; import com.sun.javafx.geom.Rectangle; -public class ME$peerName$Peer extends MEEffectPeer $interfaceName$ { +public class ME$peerName$Peer extends MEEffectPeer$genericsDecl$ $interfaceDecl$ { public ME$peerName$Peer(FilterContext fctx, Renderer r, String uniqueName) { super(fctx, r, uniqueName); --- old/buildSrc/src/main/resources/com/sun/scenario/effect/compiler/backend/sw/sse/SSEJavaGlue.stg 2014-02-21 17:02:58.000000000 -0800 +++ new/buildSrc/src/main/resources/com/sun/scenario/effect/compiler/backend/sw/sse/SSEJavaGlue.stg 2014-02-21 17:02:58.000000000 -0800 @@ -1,6 +1,6 @@ group SSEJavaGlue; -glue(effectName,peerName,interfaceDecl, +glue(effectName,peerName,genericsDecl,interfaceDecl, usercode,samplers,cleanup,srcRects,constants,params,paramDecls) ::= << /* * Copyright (c) 2008, 2013, Oracle and/or its affiliates. All rights reserved. @@ -47,7 +47,7 @@ import com.sun.javafx.geom.Rectangle; import com.sun.javafx.geom.transform.BaseTransform; -public class SSE$peerName$Peer extends SSEEffectPeer $interfaceDecl$ { +public class SSE$peerName$Peer extends SSEEffectPeer$genericsDecl$ $interfaceDecl$ { public SSE$peerName$Peer(FilterContext fctx, Renderer r, String uniqueName) { super(fctx, r, uniqueName); @@ -62,6 +62,7 @@ @Override public ImageData filter(Effect effect, + RenderState rstate, BaseTransform transform, Rectangle outputClip, ImageData... inputs)