params() {
25 | return this.params;
26 | }
27 |
28 | public InputStream es2Source() {
29 | return this.es2Source;
30 | }
31 |
32 | public InputStream d3dSource() {
33 | return this.d3dSource;
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/src/main/java/de/teragam/jfxshader/effect/EffectDependencies.java:
--------------------------------------------------------------------------------
1 | package de.teragam.jfxshader.effect;
2 |
3 | import java.lang.annotation.Documented;
4 | import java.lang.annotation.ElementType;
5 | import java.lang.annotation.Retention;
6 | import java.lang.annotation.RetentionPolicy;
7 | import java.lang.annotation.Target;
8 |
9 | @Documented
10 | @Target(ElementType.TYPE)
11 | @Retention(RetentionPolicy.RUNTIME)
12 | public @interface EffectDependencies {
13 | Class extends ShaderEffectPeer>>[] value();
14 | }
15 |
--------------------------------------------------------------------------------
/src/main/java/de/teragam/jfxshader/effect/EffectPeer.java:
--------------------------------------------------------------------------------
1 | package de.teragam.jfxshader.effect;
2 |
3 | import java.lang.annotation.Documented;
4 | import java.lang.annotation.ElementType;
5 | import java.lang.annotation.Retention;
6 | import java.lang.annotation.RetentionPolicy;
7 | import java.lang.annotation.Target;
8 |
9 | import com.sun.prism.PixelFormat;
10 | import com.sun.prism.Texture;
11 |
12 | import de.teragam.jfxshader.ImagePoolPolicy;
13 |
14 | @Documented
15 | @Target(ElementType.TYPE)
16 | @Retention(RetentionPolicy.RUNTIME)
17 | public @interface EffectPeer {
18 |
19 | /**
20 | * @return The unique name of the peer.
21 | */
22 | String value();
23 |
24 | /**
25 | * @return The pixel format of the target texture.
26 | * Defaults to {@link PixelFormat#INT_ARGB_PRE}.
27 | */
28 | PixelFormat targetFormat() default PixelFormat.INT_ARGB_PRE;
29 |
30 | /**
31 | * @return The wrap mode of the target texture.
32 | * Defaults to {@link Texture.WrapMode#CLAMP_TO_ZERO}.
33 | */
34 | Texture.WrapMode targetWrapMode() default Texture.WrapMode.CLAMP_TO_ZERO;
35 |
36 | /**
37 | * Enables mipmaps for the target texture.
38 | * Defaults to {@code false}.
39 | *
40 | * Note: For Opengl, due to the use of deprecated functions, mipmaps are only generated if the
41 | * version of OpenGL is below 3.1.
42 | *
43 | * @return Whether mipmaps should be generated for the target texture.
44 | */
45 | boolean targetMipmaps() default false;
46 |
47 | /**
48 | * @return The image pool policy for the target texture.
49 | * Defaults to {@link ImagePoolPolicy#LENIENT}.
50 | * @see ImagePoolPolicy
51 | */
52 | ImagePoolPolicy targetPoolPolicy() default ImagePoolPolicy.LENIENT;
53 |
54 | /**
55 | * @return Whether the peer is a singleton and should be reused for all instances of the effect.
56 | * Defaults to {@code true}.
57 | */
58 | boolean singleton() default true;
59 | }
60 |
--------------------------------------------------------------------------------
/src/main/java/de/teragam/jfxshader/effect/EffectRenderer.java:
--------------------------------------------------------------------------------
1 | package de.teragam.jfxshader.effect;
2 |
3 | import java.lang.annotation.Documented;
4 | import java.lang.annotation.ElementType;
5 | import java.lang.annotation.Inherited;
6 | import java.lang.annotation.Retention;
7 | import java.lang.annotation.RetentionPolicy;
8 | import java.lang.annotation.Target;
9 |
10 | @Inherited
11 | @Documented
12 | @Target(ElementType.TYPE)
13 | @Retention(RetentionPolicy.RUNTIME)
14 | public @interface EffectRenderer {
15 | Class extends IEffectRenderer> value();
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/java/de/teragam/jfxshader/effect/IEffectRenderer.java:
--------------------------------------------------------------------------------
1 | package de.teragam.jfxshader.effect;
2 |
3 | import com.sun.javafx.geom.Rectangle;
4 | import com.sun.javafx.geom.transform.BaseTransform;
5 | import com.sun.scenario.effect.FilterContext;
6 | import com.sun.scenario.effect.ImageData;
7 | import com.sun.scenario.effect.impl.state.RenderState;
8 |
9 | public interface IEffectRenderer {
10 |
11 | ImageData render(InternalEffect effect, FilterContext fctx, BaseTransform transform, Rectangle outputClip, RenderState rstate, ImageData... inputs);
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/src/main/java/de/teragam/jfxshader/effect/InternalEffect.java:
--------------------------------------------------------------------------------
1 | package de.teragam.jfxshader.effect;
2 |
3 | import java.util.LinkedHashMap;
4 | import java.util.Map;
5 |
6 | import com.sun.javafx.geom.Point2D;
7 | import com.sun.javafx.geom.Rectangle;
8 | import com.sun.javafx.geom.transform.BaseTransform;
9 | import com.sun.scenario.effect.Blend;
10 | import com.sun.scenario.effect.Effect;
11 | import com.sun.scenario.effect.FilterContext;
12 | import com.sun.scenario.effect.ImageData;
13 | import com.sun.scenario.effect.impl.EffectPeer;
14 | import com.sun.scenario.effect.impl.state.RenderState;
15 |
16 | import de.teragam.jfxshader.ShaderController;
17 | import de.teragam.jfxshader.effect.internal.ShaderEffectBase;
18 | import de.teragam.jfxshader.util.Reflect;
19 |
20 | public class InternalEffect extends Blend {
21 |
22 | private final int maxInputs;
23 | private final ShaderEffect effect;
24 | private final Map> peerMap;
25 |
26 | public InternalEffect(ShaderEffect effect, int inputs) {
27 | super(Mode.SRC_OVER, null, null);
28 | this.effect = effect;
29 | this.peerMap = new LinkedHashMap<>();
30 | this.maxInputs = inputs;
31 | for (int i = 0; i < inputs; i++) {
32 | this.setInput(i, null);
33 | }
34 | }
35 |
36 | @Override
37 | public ImageData filter(FilterContext fctx, BaseTransform transform, Rectangle outputClip, Object renderHelper, Effect defaultInput) {
38 | ShaderController.register(fctx, this.effect);
39 | return super.filter(fctx, transform, outputClip, renderHelper, defaultInput);
40 | }
41 |
42 | @Override
43 | public ImageData filterImageDatas(FilterContext fctx, BaseTransform transform, Rectangle outputClip, RenderState rstate, ImageData... inputs) {
44 | return ShaderController.getEffectRenderer(this.getEffect()).render(this, fctx, transform, outputClip, rstate, inputs);
45 | }
46 |
47 | public ShaderEffect getEffect() {
48 | return this.effect;
49 | }
50 |
51 | @Override
52 | public void setInput(int index, Effect input) {
53 | if (index < this.maxInputs) {
54 | super.setInput(index, input);
55 | }
56 | }
57 |
58 | @Override
59 | public RenderState getRenderState(FilterContext fctx, BaseTransform transform, Rectangle outputClip, Object renderHelper, Effect defaultInput) {
60 | return this.effect.getRenderState();
61 | }
62 |
63 | @Override
64 | public boolean reducesOpaquePixels() {
65 | boolean allReduces = true;
66 | for (final Effect input : this.getInputs()) {
67 | if (input == null || !input.reducesOpaquePixels()) {
68 | allReduces = false;
69 | break;
70 | }
71 | }
72 | return allReduces;
73 | }
74 |
75 | @Override
76 | public Point2D transform(Point2D p, Effect defaultInput) {
77 | final Effect input = Reflect.on(Effect.class).method("getDefaultedInput", int.class, Effect.class).invoke(this, 0, defaultInput);
78 | return input.transform(p, defaultInput);
79 | }
80 |
81 | @Override
82 | public Point2D untransform(Point2D p, Effect defaultInput) {
83 | final Effect input = Reflect.on(Effect.class).method("getDefaultedInput", int.class, Effect.class).invoke(this, 0, defaultInput);
84 | return input.untransform(p, defaultInput);
85 | }
86 |
87 | @Override
88 | public void setBottomInput(Effect bottomInput) {
89 | // no-op
90 | }
91 |
92 | @Override
93 | public void setTopInput(Effect topInput) {
94 | // no-op
95 | }
96 |
97 | /**
98 | * This override is necessary to detect shader updates, because {@link javafx.scene.effect.Effect#update() Effect.update()} cannot be overridden when
99 | * using the java module system.
100 | *
101 | * setMode() is called by {@link javafx.scene.effect.Blend#update() Blend.update()}, which is the chosen parent of {@link ShaderEffectBase}.
102 | */
103 | @Override
104 | public void setMode(Mode mode) {
105 | if (this.effect != null) {
106 | ((ShaderEffectBase) this.effect.getFXEffect()).updateInputs();
107 | }
108 | }
109 |
110 | public Map> getPeerMap() {
111 | return this.peerMap;
112 | }
113 |
114 | }
115 |
--------------------------------------------------------------------------------
/src/main/java/de/teragam/jfxshader/effect/OneSamplerEffect.java:
--------------------------------------------------------------------------------
1 | package de.teragam.jfxshader.effect;
2 |
3 | import javafx.beans.property.ObjectProperty;
4 | import javafx.scene.effect.Effect;
5 |
6 | public abstract class OneSamplerEffect extends ShaderEffect {
7 | protected OneSamplerEffect() {
8 | super(1);
9 | }
10 |
11 | public void setInput(Effect value) {
12 | this.inputProperty().set(value);
13 | }
14 |
15 | public Effect getInput() {
16 | return this.inputProperty().get();
17 | }
18 |
19 | public ObjectProperty inputProperty() {
20 | return super.getInputProperty(0);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/main/java/de/teragam/jfxshader/effect/ShaderEffect.java:
--------------------------------------------------------------------------------
1 | package de.teragam.jfxshader.effect;
2 |
3 | import javafx.beans.property.BooleanProperty;
4 | import javafx.beans.property.DoubleProperty;
5 | import javafx.beans.property.IntegerProperty;
6 | import javafx.beans.property.ObjectProperty;
7 | import javafx.beans.property.SimpleBooleanProperty;
8 | import javafx.beans.property.SimpleDoubleProperty;
9 | import javafx.beans.property.SimpleIntegerProperty;
10 | import javafx.beans.property.SimpleObjectProperty;
11 | import javafx.scene.effect.Effect;
12 |
13 | import com.sun.scenario.effect.impl.state.RenderState;
14 |
15 | import de.teragam.jfxshader.ShaderController;
16 | import de.teragam.jfxshader.effect.internal.DefaultEffectRenderer;
17 | import de.teragam.jfxshader.effect.internal.ShaderEffectBase;
18 |
19 | @EffectRenderer(DefaultEffectRenderer.class)
20 | public abstract class ShaderEffect {
21 |
22 | private final ShaderEffectBase effectBase;
23 | private final int inputs;
24 |
25 | protected ShaderEffect(int inputs) {
26 | ShaderController.injectEffectAccessor();
27 | this.inputs = inputs;
28 | this.effectBase = new ShaderEffectBase(this, inputs);
29 | }
30 |
31 | public Effect getFXEffect() {
32 | return this.effectBase;
33 | }
34 |
35 | public ShaderEffect copy() {
36 | throw new UnsupportedOperationException("No copy functionality specified");
37 | }
38 |
39 | public void markDirty() {
40 | this.effectBase.markDirty();
41 | }
42 |
43 | public int getInputs() {
44 | return this.inputs;
45 | }
46 |
47 | public RenderState getRenderState() {
48 | return RenderState.RenderSpaceRenderState;
49 | }
50 |
51 | protected ObjectProperty getInputProperty(int index) {
52 | if (this.inputs > 0 && index == 0) {
53 | return this.effectBase.topInputProperty();
54 | } else if (this.inputs > 1 && index == 1) {
55 | return this.effectBase.bottomInputProperty();
56 | } else {
57 | throw new IllegalArgumentException(String.format("Only indexes 0 to %d are supported. Requested was %d.", this.inputs - 1, index));
58 | }
59 | }
60 |
61 | protected void setContinuousRendering(boolean continuousRendering) {
62 | this.effectBase.setContinuousRendering(continuousRendering);
63 | }
64 |
65 | protected DoubleProperty createEffectDoubleProperty(double value, String name) {
66 | return new SimpleDoubleProperty(ShaderEffect.this, name, value) {
67 |
68 | @Override
69 | public void invalidated() {
70 | ShaderEffect.this.markDirty();
71 | }
72 |
73 | };
74 | }
75 |
76 | protected IntegerProperty createEffectIntegerProperty(int value, String name) {
77 | return new SimpleIntegerProperty(ShaderEffect.this, name, value) {
78 |
79 | @Override
80 | public void invalidated() {
81 | ShaderEffect.this.markDirty();
82 | }
83 |
84 | };
85 | }
86 |
87 | protected BooleanProperty createEffectBooleanProperty(boolean value, String name) {
88 | return new SimpleBooleanProperty(ShaderEffect.this, name, value) {
89 |
90 | @Override
91 | public void invalidated() {
92 | ShaderEffect.this.markDirty();
93 | }
94 |
95 | };
96 | }
97 |
98 | protected ObjectProperty createEffectObjectProperty(T value, String name) {
99 | return new SimpleObjectProperty<>(ShaderEffect.this, name, value) {
100 |
101 | @Override
102 | public void invalidated() {
103 | ShaderEffect.this.markDirty();
104 | }
105 |
106 | };
107 | }
108 |
109 | }
110 |
--------------------------------------------------------------------------------
/src/main/java/de/teragam/jfxshader/effect/ShaderEffectPeer.java:
--------------------------------------------------------------------------------
1 | package de.teragam.jfxshader.effect;
2 |
3 | import com.sun.scenario.effect.impl.state.RenderState;
4 |
5 | import de.teragam.jfxshader.effect.internal.PPSMultiSamplerPeer;
6 |
7 | public abstract class ShaderEffectPeer extends PPSMultiSamplerPeer {
8 |
9 | protected ShaderEffectPeer(ShaderEffectPeerConfig config) {
10 | super(config);
11 | }
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/src/main/java/de/teragam/jfxshader/effect/ShaderEffectPeerConfig.java:
--------------------------------------------------------------------------------
1 | package de.teragam.jfxshader.effect;
2 |
3 | import com.sun.prism.PixelFormat;
4 | import com.sun.prism.Texture;
5 | import com.sun.scenario.effect.FilterContext;
6 | import com.sun.scenario.effect.impl.Renderer;
7 |
8 | import de.teragam.jfxshader.ImagePoolPolicy;
9 |
10 | public final class ShaderEffectPeerConfig {
11 |
12 | private final FilterContext filterContext;
13 | private final Renderer renderer;
14 | private final String shaderName;
15 | private final PixelFormat targetFormat;
16 | private final Texture.WrapMode targetWrapMode;
17 | private final boolean targetMipmaps;
18 | private final ImagePoolPolicy targetPoolPolicy;
19 |
20 | public ShaderEffectPeerConfig(FilterContext filterContext, Renderer renderer, String shaderName, PixelFormat targetFormat, Texture.WrapMode targetWrapMode,
21 | boolean targetMipmaps, ImagePoolPolicy targetPoolPolicy) {
22 | this.filterContext = filterContext;
23 | this.renderer = renderer;
24 | this.shaderName = shaderName;
25 | this.targetFormat = targetFormat;
26 | this.targetWrapMode = targetWrapMode;
27 | this.targetMipmaps = targetMipmaps;
28 | this.targetPoolPolicy = targetPoolPolicy;
29 | }
30 |
31 | public FilterContext getFilterContext() {
32 | return this.filterContext;
33 | }
34 |
35 | public Renderer getRenderer() {
36 | return this.renderer;
37 | }
38 |
39 | public String getShaderName() {
40 | return this.shaderName;
41 | }
42 |
43 | public PixelFormat getTargetFormat() {
44 | return this.targetFormat;
45 | }
46 |
47 | public Texture.WrapMode getTargetWrapMode() {
48 | return this.targetWrapMode;
49 | }
50 |
51 | public boolean isTargetMipmaps() {
52 | return this.targetMipmaps;
53 | }
54 |
55 | public ImagePoolPolicy getTargetPoolPolicy() {
56 | return this.targetPoolPolicy;
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/main/java/de/teragam/jfxshader/effect/TwoSamplerEffect.java:
--------------------------------------------------------------------------------
1 | package de.teragam.jfxshader.effect;
2 |
3 | import javafx.beans.property.ObjectProperty;
4 | import javafx.scene.effect.Effect;
5 |
6 | public abstract class TwoSamplerEffect extends ShaderEffect {
7 |
8 | protected TwoSamplerEffect() {
9 | super(2);
10 | }
11 |
12 | public void setPrimaryInput(Effect value) {
13 | this.primaryInputProperty().set(value);
14 | }
15 |
16 | public Effect getPrimaryInput() {
17 | return this.primaryInputProperty().get();
18 | }
19 |
20 | public ObjectProperty primaryInputProperty() {
21 | return super.getInputProperty(0);
22 | }
23 |
24 | public void setSecondaryInput(Effect value) {
25 | this.secondaryInputProperty().set(value);
26 | }
27 |
28 | public Effect getSecondaryInput() {
29 | return this.secondaryInputProperty().get();
30 | }
31 |
32 | public ObjectProperty secondaryInputProperty() {
33 | return super.getInputProperty(1);
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/src/main/java/de/teragam/jfxshader/effect/internal/DefaultEffectRenderer.java:
--------------------------------------------------------------------------------
1 | package de.teragam.jfxshader.effect.internal;
2 |
3 | import com.sun.javafx.geom.Rectangle;
4 | import com.sun.javafx.geom.transform.BaseTransform;
5 | import com.sun.scenario.effect.FilterContext;
6 | import com.sun.scenario.effect.ImageData;
7 | import com.sun.scenario.effect.impl.state.RenderState;
8 |
9 | import de.teragam.jfxshader.effect.IEffectRenderer;
10 | import de.teragam.jfxshader.effect.InternalEffect;
11 |
12 | public class DefaultEffectRenderer implements IEffectRenderer {
13 |
14 | @Override
15 | public ImageData render(InternalEffect effect, FilterContext fctx, BaseTransform transform, Rectangle outputClip, RenderState rstate, ImageData... inputs) {
16 | return effect.getPeerMap().values().stream().findFirst()
17 | .map(peer -> peer.filter(effect, rstate, transform, outputClip, inputs))
18 | .orElse(new ImageData(fctx, null, null));
19 | }
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/src/main/java/de/teragam/jfxshader/effect/internal/EffectRenderTimer.java:
--------------------------------------------------------------------------------
1 | package de.teragam.jfxshader.effect.internal;
2 |
3 | import java.lang.ref.WeakReference;
4 | import java.util.HashMap;
5 | import java.util.Iterator;
6 | import java.util.Map;
7 | import java.util.UUID;
8 |
9 | import javafx.animation.AnimationTimer;
10 |
11 | public final class EffectRenderTimer {
12 |
13 | private static final EffectRenderTimer INSTANCE = new EffectRenderTimer();
14 |
15 | private final Map> effects;
16 |
17 | private EffectRenderTimer() {
18 | this.effects = new HashMap<>();
19 | final AnimationTimer timer = new AnimationTimer() {
20 | @Override
21 | public void handle(long now) {
22 | for (final Iterator> it = EffectRenderTimer.this.effects.values().iterator(); it.hasNext(); ) {
23 | final ShaderEffectBase e = it.next().get();
24 | if (e != null) {
25 | if (e.isContinuousRendering()) {
26 | e.markDirty();
27 | } else {
28 | it.remove();
29 | }
30 | } else {
31 | it.remove();
32 | }
33 | }
34 | }
35 | };
36 | timer.start();
37 | }
38 |
39 | public void register(ShaderEffectBase effect) {
40 | this.effects.put(effect.getEffectID(), new WeakReference<>(effect));
41 | }
42 |
43 | public static EffectRenderTimer getInstance() {
44 | return INSTANCE;
45 | }
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/src/main/java/de/teragam/jfxshader/effect/internal/PPSMultiSamplerPeer.java:
--------------------------------------------------------------------------------
1 | package de.teragam.jfxshader.effect.internal;
2 |
3 | import java.util.ArrayList;
4 | import java.util.Arrays;
5 | import java.util.EnumMap;
6 | import java.util.List;
7 | import java.util.Objects;
8 | import java.util.function.BiFunction;
9 |
10 | import com.sun.javafx.geom.Rectangle;
11 | import com.sun.javafx.geom.transform.BaseTransform;
12 | import com.sun.prism.PixelFormat;
13 | import com.sun.prism.RTTexture;
14 | import com.sun.prism.Texture;
15 | import com.sun.prism.impl.BaseContext;
16 | import com.sun.prism.impl.BaseGraphics;
17 | import com.sun.prism.impl.VertexBuffer;
18 | import com.sun.prism.impl.ps.BaseShaderContext;
19 | import com.sun.prism.impl.ps.BaseShaderGraphics;
20 | import com.sun.prism.ps.ShaderGraphics;
21 | import com.sun.scenario.effect.Effect;
22 | import com.sun.scenario.effect.ImageData;
23 | import com.sun.scenario.effect.impl.EffectPeer;
24 | import com.sun.scenario.effect.impl.PoolFilterable;
25 | import com.sun.scenario.effect.impl.prism.PrDrawable;
26 | import com.sun.scenario.effect.impl.prism.ps.PPSDrawable;
27 | import com.sun.scenario.effect.impl.prism.ps.PPSRenderer;
28 | import com.sun.scenario.effect.impl.state.RenderState;
29 |
30 | import de.teragam.jfxshader.ImagePoolPolicy;
31 | import de.teragam.jfxshader.JFXShader;
32 | import de.teragam.jfxshader.ShaderController;
33 | import de.teragam.jfxshader.ShaderDeclaration;
34 | import de.teragam.jfxshader.effect.InternalEffect;
35 | import de.teragam.jfxshader.effect.ShaderEffect;
36 | import de.teragam.jfxshader.effect.ShaderEffectPeerConfig;
37 | import de.teragam.jfxshader.exception.ShaderException;
38 | import de.teragam.jfxshader.exception.TextureCreationException;
39 | import de.teragam.jfxshader.util.Reflect;
40 |
41 | public abstract class PPSMultiSamplerPeer extends EffectPeer {
42 |
43 | private static final EnumMap FORMAT_IMAGE_POOL_MAP = new EnumMap<>(PixelFormat.class);
44 |
45 | private JFXShader shader;
46 | private PPSDrawable drawable;
47 | private BaseTransform transform;
48 | private Rectangle outputClip;
49 | private boolean invalidateShader;
50 |
51 | private final ArrayList textureCoords;
52 | private final ShaderEffectPeerConfig config;
53 |
54 | private final int checkTextureOpMask;
55 |
56 | protected PPSMultiSamplerPeer(ShaderEffectPeerConfig options) {
57 | super(options.getFilterContext(), options.getRenderer(), options.getShaderName());
58 | this.textureCoords = new ArrayList<>();
59 | this.config = Objects.requireNonNull(options, "ShaderEffectPeerConfig must not be null");
60 | this.checkTextureOpMask = Reflect.on(BaseShaderContext.class).getFieldValue("CHECK_TEXTURE_OP_MASK", null);
61 | }
62 |
63 | @Override
64 | public void dispose() {
65 | if (this.shader != null) {
66 | this.shader.dispose();
67 | }
68 | }
69 |
70 | private JFXShader createShader() {
71 | return ShaderController.createShader(super.getFilterContext(), this.createShaderDeclaration());
72 | }
73 |
74 | protected abstract ShaderDeclaration createShaderDeclaration();
75 |
76 | protected abstract void updateShader(JFXShader shader, S effect);
77 |
78 | public BaseTransform getTransform() {
79 | return this.transform;
80 | }
81 |
82 | protected void setTransform(BaseTransform transform) {
83 | this.transform = transform;
84 | }
85 |
86 | public PPSDrawable getDrawable() {
87 | return this.drawable;
88 | }
89 |
90 | protected void setOutputClip(Rectangle outputClip) {
91 | this.outputClip = outputClip;
92 | }
93 |
94 | public Rectangle getOutputClip() {
95 | return this.outputClip;
96 | }
97 |
98 | public float[] getTextureCoords(int inputIndex) {
99 | return Arrays.copyOf(this.textureCoords.get(inputIndex), this.textureCoords.get(inputIndex).length);
100 | }
101 |
102 | @Override
103 | public boolean isOriginUpperLeft() {
104 | return super.isOriginUpperLeft();
105 | }
106 |
107 | @Override
108 | protected final PPSRenderer getRenderer() {
109 | return (PPSRenderer) super.getRenderer();
110 | }
111 |
112 | @Override
113 | public final ImageData filter(final Effect effect, final T renderState, final BaseTransform transform, final Rectangle outputClip,
114 | final ImageData... inputs) {
115 | this.setEffect(effect);
116 | this.setRenderState(renderState);
117 | this.setTransform(transform);
118 | this.setOutputClip(outputClip);
119 | this.setDestBounds(this.getResultBounds(transform, outputClip, inputs));
120 | return this.filterImpl(inputs);
121 | }
122 |
123 | protected ImageData filterImpl(ImageData... inputs) {
124 | final Rectangle dstBounds = this.getDestBounds();
125 | final int dstw = dstBounds.width;
126 | final int dsth = dstBounds.height;
127 | final PPSRenderer renderer = this.getRenderer();
128 | final PPSDrawable dst = this.getCompatibleImage(dstw, dsth, this.config.getTargetFormat(), this.config.getTargetWrapMode(),
129 | this.config.isTargetMipmaps(), this.config.getTargetPoolPolicy());
130 | this.drawable = dst;
131 | if (dst == null) {
132 | this.markLost(renderer);
133 | return new ImageData(this.getFilterContext(), null, dstBounds);
134 | }
135 | this.setDestNativeBounds(dst.getPhysicalWidth(), dst.getPhysicalHeight());
136 |
137 | final ArrayList coords = new ArrayList<>();
138 | this.textureCoords.clear();
139 | final ArrayList coordLength = new ArrayList<>();
140 | final ArrayList textures = new ArrayList<>();
141 | for (int i = 0; i < Math.min(inputs.length, 2); i++) {
142 | final PrDrawable srcTexture = (PrDrawable) inputs[i].getUntransformedImage();
143 | if (srcTexture == null || srcTexture.getTextureObject() == null) {
144 | this.markLost(renderer);
145 | return new ImageData(this.getFilterContext(), dst, dstBounds);
146 | }
147 | final Rectangle srcBounds = inputs[i].getUntransformedBounds();
148 | final Texture prTexture = srcTexture.getTextureObject();
149 | final BaseTransform srcTransform = inputs[i].getTransform();
150 | this.setInputBounds(i, srcBounds);
151 | this.setInputTransform(i, srcTransform);
152 | this.setInputNativeBounds(i, srcTexture.getNativeBounds());
153 |
154 | final float[] srcRect = new float[8];
155 | final int srcCoords = this.getTextureCoordinates(0, srcRect, srcBounds.x, srcBounds.y, srcTexture.getPhysicalWidth(),
156 | srcTexture.getPhysicalHeight(), dstBounds, srcTransform);
157 |
158 | final float txOff = ((float) prTexture.getContentX()) / prTexture.getPhysicalWidth();
159 | final float tyOff = ((float) prTexture.getContentY()) / prTexture.getPhysicalHeight();
160 | if (srcCoords < 8) {
161 | coords.add(new float[]{txOff + srcRect[0], tyOff + srcRect[1], txOff + srcRect[2], tyOff + srcRect[3], txOff + srcRect[2], tyOff + srcRect[1],
162 | txOff + srcRect[0], tyOff + srcRect[3]});
163 | coordLength.add(4);
164 | this.textureCoords.add(new float[]{txOff + srcRect[0], tyOff + srcRect[1], txOff + srcRect[2], tyOff + srcRect[3]});
165 | } else {
166 | coords.add(new float[]{txOff + srcRect[0], tyOff + srcRect[1], txOff + srcRect[2], tyOff + srcRect[3], txOff + srcRect[4], tyOff + srcRect[5],
167 | txOff + srcRect[6], tyOff + srcRect[7]});
168 | coordLength.add(8);
169 | this.textureCoords.add(new float[]{txOff + srcRect[0], tyOff + srcRect[1], txOff + srcRect[4], tyOff + srcRect[5],
170 | txOff + srcRect[6], tyOff + srcRect[7], txOff + srcRect[2], tyOff + srcRect[3]});
171 | }
172 | textures.add(srcTexture.getTextureObject());
173 | }
174 | for (int i = 2; i < Math.min(inputs.length, ShaderController.MAX_BOUND_TEXTURES); i++) {
175 | final PrDrawable srcTexture = (PrDrawable) inputs[i].getUntransformedImage();
176 | if (srcTexture == null || srcTexture.getTextureObject() == null) {
177 | this.markLost(renderer);
178 | return new ImageData(this.getFilterContext(), dst, dstBounds);
179 | }
180 | textures.add(srcTexture.getTextureObject());
181 | }
182 | final ShaderGraphics g = dst.createGraphics();
183 | if (g == null) {
184 | this.markLost(renderer);
185 | return new ImageData(this.getFilterContext(), dst, dstBounds);
186 | }
187 |
188 | if (this.invalidateShader && this.shader != null) {
189 | this.shader.dispose();
190 | this.shader = null;
191 | this.invalidateShader = false;
192 | }
193 | if (this.shader == null) {
194 | this.shader = this.createShader();
195 | }
196 | if (this.shader == null || !this.shader.isValid()) {
197 | this.markLost(renderer);
198 | return new ImageData(this.getFilterContext(), dst, dstBounds);
199 | }
200 | g.setExternalShader(this.shader);
201 | this.updateShader(this.shader, (S) ((InternalEffect) super.getEffect()).getEffect());
202 | this.drawTextures((float) dstw, (float) dsth, textures, coords, coordLength, (BaseShaderGraphics) g);
203 | g.setExternalShader(null);
204 |
205 | return new ImageData(this.getFilterContext(), dst, dstBounds);
206 | }
207 |
208 | private void markLost(PPSRenderer renderer) {
209 | Reflect.on(PPSRenderer.class).method("markLost").invoke(renderer);
210 | }
211 |
212 | private void drawTextures(float dx2, float dy2, List textures, List coords, List coordLength, BaseShaderGraphics g) {
213 | final BaseTransform xform = g.getTransformNoClone();
214 | if (textures.isEmpty()) {
215 | return;
216 | }
217 | final BaseContext context = Reflect.on(BaseGraphics.class).getFieldValue("context", g);
218 | if (context.isDisposed() || !(context instanceof BaseShaderContext)) {
219 | return;
220 | }
221 | ShaderController.ensureTextureCapacity(this.getFilterContext(), (BaseShaderContext) context);
222 | Reflect.on(BaseShaderContext.class).method("checkState").invoke(context, g, this.checkTextureOpMask, xform, this.shader.getObject());
223 | for (int i = 0; i < Math.min(textures.size(), ShaderController.MAX_BOUND_TEXTURES); i++) {
224 | Reflect.on(BaseShaderContext.class).method("setTexture").invoke(context, i, textures.get(i));
225 | }
226 | Reflect.on(BaseShaderContext.class).method("updatePerVertexColor").invoke(context, null, g.getExtraAlpha());
227 | final VertexBuffer vb = context.getVertexBuffer();
228 | switch (coords.size()) {
229 | case 0:
230 | return;
231 | case 1:
232 | final float[] c = coords.get(0);
233 | if (coordLength.get(0) < 8) {
234 | vb.addQuad(0, 0, dx2, dy2, c[0], c[1], c[2], c[3]);
235 | } else {
236 | vb.addMappedQuad(0, 0, dx2, dy2, c[0], c[1], c[4], c[5], c[6], c[7], c[2], c[3]);
237 | }
238 | break;
239 | default:
240 | final float[] c1 = coords.get(0);
241 | final float[] c2 = coords.get(1);
242 | if (coordLength.get(0) < 8 && coordLength.get(1) < 8) {
243 | vb.addQuad(0, 0, dx2, dy2, c1[0], c1[1], c1[2], c1[3], c2[0], c2[1], c2[2], c2[3]);
244 | } else {
245 | vb.addMappedQuad(0, 0, dx2, dy2, c1[0], c1[1], c1[4], c1[5], c1[6], c1[7], c1[2], c1[3], c2[0], c2[1], c2[4], c2[5], c2[6], c2[7],
246 | c2[2], c2[3]);
247 | }
248 | }
249 | }
250 |
251 | public PPSDrawable getCompatibleImage(int width, int height, PixelFormat format, Texture.WrapMode wrapMode, boolean mipmaps,
252 | ImagePoolPolicy poolPolicy) {
253 | if (format == PixelFormat.INT_ARGB_PRE && !mipmaps && poolPolicy == ImagePoolPolicy.LENIENT) {
254 | return this.getRenderer().getCompatibleImage(width, height);
255 | } else {
256 | final BiFunction imageFactory = (w, h) -> {
257 | if (!this.validateRenderer()) {
258 | return null;
259 | }
260 | try {
261 | return (PPSDrawable) Reflect.on(PPSDrawable.class).method("create", RTTexture.class)
262 | .invoke(null, ShaderController.createRTTexture(this.getFilterContext(), format, wrapMode, w, h, mipmaps));
263 | } catch (TextureCreationException e) {
264 | return null;
265 | }
266 | };
267 | PPSMultiSamplerPeer.FORMAT_IMAGE_POOL_MAP.computeIfAbsent(format, f -> new PolicyBasedImagePool());
268 | final PolicyBasedImagePool pool = PPSMultiSamplerPeer.FORMAT_IMAGE_POOL_MAP.get(format);
269 | return (PPSDrawable) pool.checkOut(this.getRenderer(), width, height, mipmaps, imageFactory, poolPolicy);
270 | }
271 | }
272 |
273 | /**
274 | * Clones the given texture and returns the clone with the specified dimensions and pixel format.
275 | *
276 | * In JavaFX, the actual textures that are used for rendering are often larger than the content they contain due to the use of an internal texture pool.
277 | * To render shader effects, the texture coordinates for the input textures are fitted to the content size, but the texture size is not.
278 | * This may cause problems for shaders that modify the texture coordinates and rely on the texture coordinates being in the range [0, 1] for the whole
279 | * input texture.
280 | * By cloning the texture and setting the texture size to the content size, the texture coordinates will be in the range [0, 1].
281 | *
282 | * Alternatively, the calculated texture coordinates can be loaded into the shader as a uniform variable.
283 | *
284 | * @see ImagePoolPolicy
285 | */
286 | public PrDrawable cloneTexture(PrDrawable srcTexture, int width, int height, PixelFormat dstFormat) {
287 | final PrDrawable fittedTexture = this.getCompatibleImage(width, height, dstFormat,
288 | srcTexture.getTextureObject().getWrapMode(),
289 | srcTexture.getTextureObject().getUseMipmap(), ImagePoolPolicy.EXACT);
290 | if (fittedTexture == null || fittedTexture.getTextureObject() == null || !this.validateRenderer()) {
291 | return null;
292 | }
293 | fittedTexture.createGraphics().blit(srcTexture.getTextureObject(), null, 0, 0, width, height, 0, 0, width, height);
294 | return fittedTexture;
295 | }
296 |
297 | /**
298 | * Queues the invalidation and disposal of the shader.
299 | * It will be recreated with {@link PPSMultiSamplerPeer#createShaderDeclaration()} when needed.
300 | */
301 | protected void invalidateShader() {
302 | if (this.shader != null) {
303 | this.invalidateShader = true;
304 | }
305 | }
306 |
307 | private boolean validateRenderer() {
308 | try {
309 | return (boolean) Reflect.on(PPSRenderer.class).method("validate").invoke(this.getRenderer());
310 | } catch (ShaderException ignored) {
311 | return false;
312 | }
313 | }
314 |
315 | }
316 |
--------------------------------------------------------------------------------
/src/main/java/de/teragam/jfxshader/effect/internal/PolicyBasedImagePool.java:
--------------------------------------------------------------------------------
1 | package de.teragam.jfxshader.effect.internal;
2 |
3 | import java.lang.ref.SoftReference;
4 | import java.util.Iterator;
5 | import java.util.List;
6 | import java.util.function.BiFunction;
7 |
8 | import com.sun.prism.Texture;
9 | import com.sun.scenario.effect.impl.ImagePool;
10 | import com.sun.scenario.effect.impl.PoolFilterable;
11 | import com.sun.scenario.effect.impl.Renderer;
12 | import com.sun.scenario.effect.impl.prism.PrTexture;
13 |
14 | import de.teragam.jfxshader.ImagePoolPolicy;
15 | import de.teragam.jfxshader.exception.ShaderException;
16 | import de.teragam.jfxshader.util.Reflect;
17 |
18 | public class PolicyBasedImagePool {
19 |
20 | private final ImagePool imagePool;
21 | private final List> unlocked;
22 | private final List> locked;
23 |
24 | public PolicyBasedImagePool() {
25 | this.imagePool = Reflect.on(ImagePool.class).constructor().create();
26 | try {
27 | this.unlocked = Reflect.on(ImagePool.class).getFieldValue("unlocked", this.imagePool);
28 | this.locked = Reflect.on(ImagePool.class).getFieldValue("locked", this.imagePool);
29 | } catch (ShaderException e) {
30 | throw new ShaderException("Failed to initialize PixelFormatImagePool", e);
31 | }
32 | }
33 |
34 | public synchronized PoolFilterable checkOut(Renderer renderer, int w, int h) {
35 | return this.checkOut(renderer, w, h, false, renderer::createCompatibleImage, ImagePoolPolicy.LENIENT);
36 | }
37 |
38 | public synchronized PoolFilterable checkOut(Renderer renderer, int w, int h, boolean targetMipmaps,
39 | BiFunction drawableSupplier, ImagePoolPolicy policy) {
40 | if (w <= 0 || h <= 0) {
41 | w = h = 1;
42 | }
43 | final int quant = policy.getQuantization();
44 | w = renderer.getCompatibleWidth(((w + quant - 1) / quant) * quant);
45 | h = renderer.getCompatibleHeight(((h + quant - 1) / quant) * quant);
46 |
47 | final int finalW = w;
48 | final int finalH = h;
49 |
50 | Reflect.on(ImagePool.class).processFieldValue("numAccessed", null, numCheckedOut -> (long) numCheckedOut + 1);
51 | Reflect.on(ImagePool.class).processFieldValue("pixelsAccessed", null, pixelsAccessed -> (long) pixelsAccessed + ((long) finalW) * finalH);
52 |
53 | SoftReference chosenEntry = null;
54 | PoolFilterable chosenImage = null;
55 | int minDiff = Integer.MAX_VALUE;
56 | final Iterator> entries = this.unlocked.iterator();
57 | while (entries.hasNext()) {
58 | final SoftReference entry = entries.next();
59 | final PoolFilterable filterable = entry.get();
60 | if (filterable == null) {
61 | entries.remove();
62 | } else {
63 | final int ew = filterable.getMaxContentWidth();
64 | final int eh = filterable.getMaxContentHeight();
65 | final int diff = (ew - w) * (eh - h);
66 | final boolean lenient = ew >= w && eh >= h && ew * eh / 2 <= w * h && (chosenEntry == null || diff < minDiff);
67 | final boolean exact = ew == w && eh == h;
68 | if ((policy.isApproximateMatch() && lenient) || (!policy.isApproximateMatch() && exact)) {
69 | final Texture tex = Reflect.on(PrTexture.class).getFieldValue("tex", filterable);
70 | if (tex.getUseMipmap() == targetMipmaps) {
71 | filterable.lock();
72 | if (filterable.isLost()) {
73 | entries.remove();
74 | continue;
75 | }
76 | if (chosenImage != null) {
77 | chosenImage.unlock();
78 | }
79 | chosenEntry = entry;
80 | chosenImage = filterable;
81 | minDiff = diff;
82 | if (exact) {
83 | break;
84 | }
85 | }
86 | }
87 | }
88 | }
89 |
90 | if (chosenEntry != null) {
91 | this.unlocked.remove(chosenEntry);
92 | this.locked.add(chosenEntry);
93 | renderer.clearImage(chosenImage);
94 | return chosenImage;
95 | }
96 |
97 | this.locked.removeIf(entry -> entry.get() == null);
98 |
99 | PoolFilterable img = null;
100 | try {
101 | img = drawableSupplier.apply(w, h);
102 | } catch (OutOfMemoryError ignored) {
103 | Reflect.on(ImagePool.class).method("pruneCache").invoke(null);
104 | try {
105 | img = drawableSupplier.apply(w, h);
106 | } catch (OutOfMemoryError ignoredEx) {
107 | // ignored
108 | }
109 | }
110 |
111 | if (img != null) {
112 | img.setImagePool(this.imagePool);
113 | this.locked.add(new SoftReference<>(img));
114 | Reflect.on(ImagePool.class).processFieldValue("numCreated", null, numCreated -> (long) numCreated + 1);
115 | Reflect.on(ImagePool.class).processFieldValue("pixelsCreated", null, pixelsCreated -> (long) pixelsCreated + ((long) finalW) * finalH);
116 | }
117 | return img;
118 | }
119 |
120 | }
121 |
--------------------------------------------------------------------------------
/src/main/java/de/teragam/jfxshader/effect/internal/RTTTextureHelper.java:
--------------------------------------------------------------------------------
1 | package de.teragam.jfxshader.effect.internal;
2 |
3 | import com.sun.prism.PixelFormat;
4 | import com.sun.prism.Texture;
5 | import com.sun.prism.impl.BaseTexture;
6 | import com.sun.prism.impl.ManagedResource;
7 |
8 | import de.teragam.jfxshader.exception.ShaderException;
9 | import de.teragam.jfxshader.util.Reflect;
10 |
11 | public class RTTTextureHelper {
12 |
13 | protected RTTTextureHelper() {}
14 |
15 | protected static > void fillTexture(BaseTexture texture, T resource, PixelFormat format, Texture.WrapMode wrapMode,
16 | int physicalWidth, int physicalHeight, int contentX, int contentY,
17 | int contentWidth, int contentHeight, int maxContentWidth, int maxContentHeight,
18 | boolean useMipmap) {
19 | try {
20 | final Reflect> reflect = Reflect.on(BaseTexture.class);
21 | reflect.setFieldValue("resource", texture, resource);
22 | reflect.setFieldValue("format", texture, format);
23 | reflect.setFieldValue("wrapMode", texture, wrapMode);
24 | reflect.setFieldValue("physicalWidth", texture, physicalWidth);
25 | reflect.setFieldValue("physicalHeight", texture, physicalHeight);
26 | reflect.setFieldValue("contentX", texture, contentX);
27 | reflect.setFieldValue("contentY", texture, contentY);
28 | reflect.setFieldValue("contentWidth", texture, contentWidth);
29 | reflect.setFieldValue("contentHeight", texture, contentHeight);
30 | reflect.setFieldValue("maxContentWidth", texture, maxContentWidth);
31 | reflect.setFieldValue("maxContentHeight", texture, maxContentHeight);
32 | reflect.setFieldValue("useMipmap", texture, useMipmap);
33 | texture.setLinearFiltering(true);
34 | } catch (ShaderException e) {
35 | throw new ShaderException("Could not fill texture", e);
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/main/java/de/teragam/jfxshader/effect/internal/ShaderEffectBase.java:
--------------------------------------------------------------------------------
1 | package de.teragam.jfxshader.effect.internal;
2 |
3 | import java.util.List;
4 | import java.util.Map;
5 | import java.util.UUID;
6 |
7 | import javafx.beans.property.ObjectProperty;
8 | import javafx.scene.Node;
9 | import javafx.scene.effect.Blend;
10 | import javafx.scene.effect.Effect;
11 |
12 | import com.sun.javafx.effect.EffectDirtyBits;
13 | import com.sun.javafx.geom.BaseBounds;
14 | import com.sun.javafx.geom.transform.BaseTransform;
15 | import com.sun.javafx.scene.BoundsAccessor;
16 | import com.sun.scenario.effect.impl.EffectPeer;
17 | import com.sun.scenario.effect.impl.state.RenderState;
18 |
19 | import de.teragam.jfxshader.effect.InternalEffect;
20 | import de.teragam.jfxshader.effect.ShaderEffect;
21 | import de.teragam.jfxshader.util.Reflect;
22 |
23 | public class ShaderEffectBase extends Blend {
24 |
25 | public static final int MAX_INPUTS = 2;
26 | private static final Reflect EFFECT_REFLECT = Reflect.on(Effect.class);
27 |
28 | private final ShaderEffect effect;
29 | private final InternalEffect peer;
30 | private final UUID effectID;
31 |
32 | private boolean continuousRendering;
33 |
34 | public ShaderEffectBase(ShaderEffect effect, int inputs) {
35 | if (inputs < 1 || inputs > MAX_INPUTS) {
36 | throw new IllegalArgumentException(String.format("Only 1 to %d inputs are supported. Requested were %d.", MAX_INPUTS, inputs));
37 | }
38 | this.effectID = UUID.randomUUID();
39 | this.effect = effect;
40 | this.peer = new InternalEffect(effect, inputs);
41 | EFFECT_REFLECT.setFieldValue("peer", this, this.peer);
42 | }
43 |
44 | public void setContinuousRendering(boolean continuousRendering) {
45 | if (continuousRendering && !this.continuousRendering) {
46 | EffectRenderTimer.getInstance().register(this);
47 | }
48 | this.continuousRendering = continuousRendering;
49 | }
50 |
51 | public boolean isContinuousRendering() {
52 | return this.continuousRendering;
53 | }
54 |
55 | public ShaderEffect getJFXShaderEffect() {
56 | return this.effect;
57 | }
58 |
59 | public void markDirty() {
60 | EFFECT_REFLECT.method("markDirty", EffectDirtyBits.class).invoke(this, EffectDirtyBits.EFFECT_DIRTY);
61 | }
62 |
63 | public void updateInputs() {
64 | final List> properties = List.of(this.topInputProperty(), this.bottomInputProperty());
65 | for (int i = 0; i < properties.size(); i++) {
66 | final ObjectProperty property = properties.get(i);
67 | if (property != null) {
68 | final Effect localInput = property.get();
69 | if (localInput != null) {
70 | EFFECT_REFLECT.method("sync").invoke(localInput);
71 | }
72 | this.peer.setInput(i, localInput == null ? null : (com.sun.scenario.effect.Effect) EFFECT_REFLECT.method("getPeer").invoke(localInput));
73 | }
74 | }
75 | }
76 |
77 | public Map> getPeerMap() {
78 | return this.peer.getPeerMap();
79 | }
80 |
81 | public UUID getEffectID() {
82 | return this.effectID;
83 | }
84 |
85 | public static BaseBounds getInputBounds(BaseBounds bounds, BaseTransform tx, Node node, BoundsAccessor boundsAccessor, Effect input) {
86 | return EFFECT_REFLECT.method("getInputBounds").invoke(null, bounds, tx, node, boundsAccessor, input);
87 | }
88 |
89 | }
90 |
--------------------------------------------------------------------------------
/src/main/java/de/teragam/jfxshader/effect/internal/d3d/D3DRTTextureHelper.java:
--------------------------------------------------------------------------------
1 | package de.teragam.jfxshader.effect.internal.d3d;
2 |
3 | import com.sun.prism.PixelFormat;
4 | import com.sun.prism.RTTexture;
5 | import com.sun.prism.ResourceFactory;
6 | import com.sun.prism.Texture;
7 | import com.sun.prism.d3d.D3DTextureData;
8 | import com.sun.prism.impl.BaseResourceFactory;
9 | import com.sun.prism.impl.BaseResourcePool;
10 | import com.sun.prism.impl.BaseTexture;
11 | import com.sun.prism.impl.DisposerManagedResource;
12 | import com.sun.prism.impl.PrismSettings;
13 | import com.sun.prism.impl.ps.BaseShaderContext;
14 |
15 | import de.teragam.jfxshader.effect.internal.RTTTextureHelper;
16 | import de.teragam.jfxshader.exception.TextureCreationException;
17 | import de.teragam.jfxshader.util.Reflect;
18 |
19 | public final class D3DRTTextureHelper extends RTTTextureHelper {
20 |
21 | private D3DRTTextureHelper() {}
22 |
23 | public static RTTexture createD3DRTTexture(BaseResourceFactory factory, PixelFormat format, Texture.WrapMode wrapMode, int width, int height,
24 | boolean useMipmap) {
25 | final Class> d3DResourceFactoryClass = Reflect.resolveClass("com.sun.prism.d3d.D3DResourceFactory");
26 | if (!d3DResourceFactoryClass.isInstance(factory)) {
27 | throw new TextureCreationException("Factory is not a D3DResourceFactory");
28 | }
29 | if (factory.isDisposed()) {
30 | throw new TextureCreationException("Failed to create texture: The factory is disposed.");
31 | }
32 | final BaseResourcePool d3dVramPool = Reflect.on("com.sun.prism.d3d.D3DVramPool").getFieldValue("instance", null);
33 | if (!d3dVramPool.prepareForAllocation(D3DRTTextureHelper.estimateTextureSize(width, height, format))) {
34 | throw new TextureCreationException("Failed to create texture: Not enough VRAM.");
35 | }
36 |
37 | int createWidth = width;
38 | int createHeight = height;
39 | if (PrismSettings.forcePow2) {
40 | createWidth = D3DRTTextureHelper.nextPowerOfTwo(createWidth);
41 | createHeight = D3DRTTextureHelper.nextPowerOfTwo(createHeight);
42 | }
43 | final BaseShaderContext context = Reflect.on(d3DResourceFactoryClass).getFieldValue("context", factory);
44 | final long contextHandle = Reflect.on("com.sun.prism.d3d.D3DContext").getFieldValue("pContext", context);
45 | final long pResource = (long) Reflect.on(d3DResourceFactoryClass).method("nCreateTexture")
46 | .invoke(null, contextHandle, format.ordinal(), Texture.Usage.DEFAULT.ordinal(), true, createWidth, createHeight, 0, useMipmap);
47 |
48 | if (pResource == 0L) {
49 | throw new TextureCreationException("Failed to create texture.");
50 | }
51 |
52 | final int textureWidth = (int) Reflect.on(d3DResourceFactoryClass).method("nGetTextureWidth").invoke(null, pResource);
53 | final int textureHeight = (int) Reflect.on(d3DResourceFactoryClass).method("nGetTextureHeight").invoke(null, pResource);
54 | final RTTexture rtt = Reflect.on("com.sun.prism.d3d.D3DRTTexture").allocateInstance();
55 | final DisposerManagedResource resource = Reflect.>on("com.sun.prism.d3d.D3DTextureResource")
56 | .constructor().create(Reflect.on(D3DTextureData.class).constructor()
57 | .create(context, pResource, true, textureWidth, textureHeight, format, 0));
58 | RTTTextureHelper.fillTexture((BaseTexture>) rtt, resource, PixelFormat.INT_ARGB_PRE, wrapMode,
59 | textureWidth, textureHeight, 0, 0, width, height,
60 | textureWidth, textureHeight, useMipmap);
61 | rtt.setOpaque(false);
62 | rtt.createGraphics().clear();
63 | return rtt;
64 | }
65 |
66 | public static long getDevice(ResourceFactory factory) {
67 | final Class> d3DResourceFactoryClass = Reflect.resolveClass("com.sun.prism.d3d.D3DResourceFactory");
68 | if (!d3DResourceFactoryClass.isInstance(factory)) {
69 | throw new TextureCreationException("Factory is not a D3DResourceFactory");
70 | }
71 | final BaseShaderContext context = Reflect.on(d3DResourceFactoryClass).getFieldValue("context", factory);
72 | final long contextHandle = Reflect.on("com.sun.prism.d3d.D3DContext").getFieldValue("pContext", context);
73 | return (long) Reflect.on(d3DResourceFactoryClass).method("nGetDevice").invoke(null, contextHandle);
74 | }
75 |
76 | public static long estimateTextureSize(long width, long height, PixelFormat format) {
77 | return width * height * format.getBytesPerPixelUnit();
78 | }
79 |
80 | private static int nextPowerOfTwo(int val) {
81 | int i = 1;
82 | while (i < val) {
83 | i *= 2;
84 | }
85 | return i;
86 | }
87 |
88 | }
89 |
--------------------------------------------------------------------------------
/src/main/java/de/teragam/jfxshader/effect/internal/es2/ES2RTTextureHelper.java:
--------------------------------------------------------------------------------
1 | package de.teragam.jfxshader.effect.internal.es2;
2 |
3 | import java.nio.Buffer;
4 |
5 | import com.sun.prism.PixelFormat;
6 | import com.sun.prism.RTTexture;
7 | import com.sun.prism.Texture;
8 | import com.sun.prism.impl.BaseResourceFactory;
9 | import com.sun.prism.impl.BaseTexture;
10 | import com.sun.prism.impl.DisposerManagedResource;
11 | import com.sun.prism.impl.PrismSettings;
12 | import com.sun.prism.impl.PrismTrace;
13 | import com.sun.prism.impl.TextureResourcePool;
14 | import com.sun.prism.impl.ps.BaseShaderContext;
15 |
16 | import de.teragam.jfxshader.effect.internal.RTTTextureHelper;
17 | import de.teragam.jfxshader.exception.TextureCreationException;
18 | import de.teragam.jfxshader.util.MethodInvocationWrapper;
19 | import de.teragam.jfxshader.util.Reflect;
20 |
21 | public class ES2RTTextureHelper extends RTTTextureHelper {
22 |
23 | private static final int GL_TEXTURE_2D = 50;
24 | private static final int GL_LINEAR = 53;
25 |
26 | private ES2RTTextureHelper() {}
27 |
28 | public static Object getGLContext(BaseResourceFactory factory) {
29 | if (!Reflect.resolveClass("com.sun.prism.es2.ES2ResourceFactory").isInstance(factory)) {
30 | throw new TextureCreationException("Factory is not a ES2ResourceFactory");
31 | }
32 | final BaseShaderContext context = ReflectionES2Helper.getInstance().getContext(factory);
33 | return Reflect.on(context.getClass()).getFieldValue("glContext", context);
34 | }
35 |
36 | public static RTTexture createES2RTTexture(BaseResourceFactory factory, PixelFormat format, Texture.WrapMode wrapMode, int width, int height,
37 | boolean useMipmap) {
38 | if (!Reflect.resolveClass("com.sun.prism.es2.ES2ResourceFactory").isInstance(factory)) {
39 | throw new TextureCreationException("Factory is not a ES2ResourceFactory");
40 | }
41 | final BaseShaderContext es2Context = ReflectionES2Helper.getInstance().getContext(factory);
42 | final GLContext glContext = Reflect.createProxy(ES2RTTextureHelper.getGLContext(factory),
43 | Reflect.resolveClass("com.sun.prism.es2.GLContext"), GLContext.class);
44 |
45 | final boolean pad;
46 | switch (wrapMode) {
47 | case CLAMP_NOT_NEEDED:
48 | pad = false;
49 | break;
50 | case CLAMP_TO_ZERO:
51 | pad = !glContext.canClampToZero();
52 | break;
53 | default:
54 | case CLAMP_TO_EDGE:
55 | case REPEAT:
56 | throw new IllegalArgumentException("wrap mode not supported for RT textures: " + wrapMode);
57 | case CLAMP_TO_EDGE_SIMULATED:
58 | case CLAMP_TO_ZERO_SIMULATED:
59 | case REPEAT_SIMULATED:
60 | throw new IllegalArgumentException("Cannot request simulated wrap mode: " + wrapMode);
61 | }
62 |
63 | final int contentX;
64 | final int contentY;
65 | final int paddedW;
66 | final int paddedH;
67 | if (pad) {
68 | contentX = 1;
69 | contentY = 1;
70 | paddedW = width + 2;
71 | paddedH = height + 2;
72 | wrapMode = wrapMode.simulatedVersion();
73 | } else {
74 | contentX = 0;
75 | contentY = 0;
76 | paddedW = width;
77 | paddedH = height;
78 | }
79 |
80 | final int maxSize = glContext.getMaxTextureSize();
81 | int texWidth;
82 | int texHeight;
83 | if (glContext.canCreateNonPowTwoTextures()) {
84 | texWidth = (paddedW <= maxSize) ? paddedW : 0;
85 | texHeight = (paddedH <= maxSize) ? paddedH : 0;
86 | } else {
87 | texWidth = ES2RTTextureHelper.nextPowerOfTwo(paddedW, maxSize);
88 | texHeight = ES2RTTextureHelper.nextPowerOfTwo(paddedH, maxSize);
89 | }
90 |
91 | if (texWidth == 0 || texHeight == 0) {
92 | throw new TextureCreationException(
93 | "Requested texture dimensions (" + width + "x" + height + ") "
94 | + "require dimensions (" + texWidth + "x" + texHeight + ") "
95 | + "that exceed maximum texture size (" + maxSize + ")");
96 | }
97 |
98 | texWidth = Math.max(texWidth, PrismSettings.minRTTSize);
99 | texHeight = Math.max(texHeight, PrismSettings.minRTTSize);
100 | final TextureResourcePool> es2VramPool = Reflect.on("com.sun.prism.es2.ES2VramPool").getFieldValue("instance", null);
101 | final long size = es2VramPool.estimateTextureSize(texWidth, texHeight, format);
102 | if (!es2VramPool.prepareForAllocation(size)) {
103 | throw new TextureCreationException("Failed to create texture: Not enough VRAM.");
104 | }
105 |
106 | glContext.setActiveTextureUnit(0);
107 | final int savedFBO = glContext.getBoundFBO();
108 | final int savedTex = glContext.getBoundTexture();
109 |
110 | final int nativeTexID = glContext.genAndBindTexture();
111 | if (nativeTexID == 0L) {
112 | throw new TextureCreationException("Failed to create texture.");
113 | }
114 |
115 | final boolean result = ReflectionES2Helper.getInstance()
116 | .uploadPixels(glContext.getObject(), GL_TEXTURE_2D, null, format, texWidth, texHeight, contentX,
117 | contentY, 0, 0, width, height, 0, true, useMipmap);
118 | if (!result) {
119 | throw new TextureCreationException("Failed to create texture.");
120 | }
121 |
122 | glContext.texParamsMinMax(GL_LINEAR, useMipmap);
123 |
124 | final int nativeFBOID = glContext.createFBO(nativeTexID);
125 | if (nativeFBOID == 0) {
126 | glContext.deleteTexture(nativeTexID);
127 | throw new TextureCreationException("Failed to attach FBO to texture.");
128 | }
129 |
130 | final int padding = pad ? 2 : 0;
131 | final int maxContentW = texWidth - padding;
132 | final int maxContentH = texHeight - padding;
133 | final Object texData = Reflect.on("com.sun.prism.es2.ES2RTTextureData").constructor()
134 | .create(es2Context, nativeTexID, nativeFBOID, texWidth, texHeight, size);
135 | final DisposerManagedResource> texRes = (DisposerManagedResource>) Reflect.on("com.sun.prism.es2.ES2TextureResource")
136 | .constructor(Reflect.resolveClass("com.sun.prism.es2.ES2TextureData")).create(texData);
137 | final RTTexture es2RTT = ReflectionES2Helper.getInstance().createTexture(es2Context, texRes, format, wrapMode, texWidth, texHeight, contentX, contentY,
138 | width, height, maxContentW, maxContentH, useMipmap);
139 |
140 | glContext.bindFBO(savedFBO);
141 | glContext.setBoundTexture(savedTex);
142 | return es2RTT;
143 | }
144 |
145 | private static int nextPowerOfTwo(int val, int max) {
146 | if (val > max) {
147 | return 0;
148 | }
149 | int i = 1;
150 | while (i < val) {
151 | i *= 2;
152 | }
153 | return i;
154 | }
155 |
156 | private static class ReflectionES2Helper {
157 |
158 | private static ReflectionES2Helper instance;
159 |
160 | private final MethodInvocationWrapper uploadPixelsMethod;
161 |
162 | public ReflectionES2Helper() {
163 | this.uploadPixelsMethod = Reflect.on("com.sun.prism.es2.ES2Texture").method("uploadPixels",
164 | Reflect.resolveClass("com.sun.prism.es2.GLContext"), int.class, Buffer.class, PixelFormat.class, int.class, int.class, int.class, int.class,
165 | int.class, int.class, int.class, int.class, int.class, boolean.class, boolean.class);
166 | }
167 |
168 | public static ReflectionES2Helper getInstance() {
169 | if (ReflectionES2Helper.instance == null) {
170 | ReflectionES2Helper.instance = new ReflectionES2Helper();
171 | }
172 | return ReflectionES2Helper.instance;
173 | }
174 |
175 | public BaseShaderContext getContext(BaseResourceFactory factory) {
176 | return Reflect.on("com.sun.prism.es2.ES2ResourceFactory").getFieldValue("context", factory);
177 | }
178 |
179 | public boolean uploadPixels(Object glCtx, int target, Buffer pixels, PixelFormat format, int texw, int texh, int dstx, int dsty, int srcx, int srcy,
180 | int srcw, int srch, int srcscan, boolean create, boolean useMipmap) {
181 | return this.uploadPixelsMethod.invoke(null, glCtx, target, pixels, format, texw, texh, dstx, dsty, srcx, srcy, srcw, srch, srcscan,
182 | create, useMipmap);
183 | }
184 |
185 | private RTTexture createTexture(BaseShaderContext es2Context, DisposerManagedResource> resource, PixelFormat format, Texture.WrapMode wrapMode,
186 | int physicalWidth, int physicalHeight, int contentX, int contentY, int contentWidth, int contentHeight,
187 | int maxContentWidth, int maxContentHeight, boolean useMipmap) {
188 | final RTTexture texture = Reflect.on("com.sun.prism.es2.ES2RTTexture").allocateInstance();
189 | texture.setOpaque(false);
190 | Reflect.on("com.sun.prism.es2.ES2Texture").setFieldValue("context", texture, es2Context);
191 | RTTTextureHelper.fillTexture((BaseTexture super DisposerManagedResource>>) texture, resource, PixelFormat.INT_ARGB_PRE, wrapMode,
192 | physicalWidth, physicalHeight, contentX, contentY, contentWidth, contentHeight, maxContentWidth, maxContentHeight, useMipmap);
193 | PrismTrace.rttCreated(Reflect.on("com.sun.prism.es2.ES2RTTextureData").getFieldValue("fboID", resource.getResource()),
194 | physicalWidth, physicalHeight, format.getBytesPerPixelUnit());
195 | return texture;
196 | }
197 | }
198 | }
199 |
--------------------------------------------------------------------------------
/src/main/java/de/teragam/jfxshader/effect/internal/es2/GLContext.java:
--------------------------------------------------------------------------------
1 | package de.teragam.jfxshader.effect.internal.es2;
2 |
3 | import de.teragam.jfxshader.util.ReflectProxy;
4 |
5 | public interface GLContext extends ReflectProxy {
6 |
7 | boolean canClampToZero();
8 |
9 | int getMaxTextureSize();
10 |
11 | boolean canCreateNonPowTwoTextures();
12 |
13 | void setActiveTextureUnit(int unit);
14 |
15 | int getBoundFBO();
16 |
17 | int getBoundTexture();
18 |
19 | int genAndBindTexture();
20 |
21 | void texParamsMinMax(int pname, boolean useMipmap);
22 |
23 | int createFBO(int texID);
24 |
25 | void deleteTexture(int tID);
26 |
27 | void bindFBO(int nativeFBOID);
28 |
29 | void setBoundTexture(int texid);
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/src/main/java/de/teragam/jfxshader/exception/ShaderCreationException.java:
--------------------------------------------------------------------------------
1 | package de.teragam.jfxshader.exception;
2 |
3 | public class ShaderCreationException extends ShaderException {
4 |
5 | public ShaderCreationException(String message) {
6 | super(message);
7 | }
8 |
9 | public ShaderCreationException(String message, Throwable cause) {
10 | super(message, cause);
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/main/java/de/teragam/jfxshader/exception/ShaderException.java:
--------------------------------------------------------------------------------
1 | package de.teragam.jfxshader.exception;
2 |
3 | public class ShaderException extends RuntimeException {
4 |
5 | public ShaderException(String message) {
6 | super(message);
7 | }
8 |
9 | public ShaderException(String message, Throwable cause) {
10 | super(message, cause);
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/main/java/de/teragam/jfxshader/exception/TextureCreationException.java:
--------------------------------------------------------------------------------
1 | package de.teragam.jfxshader.exception;
2 |
3 | public class TextureCreationException extends ShaderException {
4 |
5 | public TextureCreationException(String message) {
6 | super(message);
7 | }
8 |
9 | public TextureCreationException(String message, Throwable cause) {
10 | super(message, cause);
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/main/java/de/teragam/jfxshader/material/MaterialDependency.java:
--------------------------------------------------------------------------------
1 | package de.teragam.jfxshader.material;
2 |
3 | import java.lang.annotation.Documented;
4 | import java.lang.annotation.ElementType;
5 | import java.lang.annotation.Retention;
6 | import java.lang.annotation.RetentionPolicy;
7 | import java.lang.annotation.Target;
8 |
9 | @Documented
10 | @Target(ElementType.TYPE)
11 | @Retention(RetentionPolicy.RUNTIME)
12 | public @interface MaterialDependency {
13 |
14 | Class extends ShaderMaterialPeer>> value();
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/java/de/teragam/jfxshader/material/ShaderMaterial.java:
--------------------------------------------------------------------------------
1 | package de.teragam.jfxshader.material;
2 |
3 | import javafx.beans.Observable;
4 | import javafx.beans.property.BooleanProperty;
5 | import javafx.beans.property.DoubleProperty;
6 | import javafx.beans.property.IntegerProperty;
7 | import javafx.beans.property.ObjectProperty;
8 | import javafx.beans.property.SimpleBooleanProperty;
9 | import javafx.beans.property.SimpleDoubleProperty;
10 | import javafx.beans.property.SimpleIntegerProperty;
11 | import javafx.beans.property.SimpleObjectProperty;
12 | import javafx.scene.image.Image;
13 | import javafx.scene.paint.Material;
14 |
15 | import com.sun.javafx.beans.event.AbstractNotifyListener;
16 | import com.sun.javafx.tk.Toolkit;
17 |
18 | import de.teragam.jfxshader.MaterialController;
19 | import de.teragam.jfxshader.material.internal.ShaderMaterialBase;
20 |
21 | public abstract class ShaderMaterial {
22 |
23 | private final ShaderMaterialBase materialBase;
24 |
25 | protected ShaderMaterial() {
26 | MaterialController.setup3D();
27 | this.materialBase = new ShaderMaterialBase(this);
28 | }
29 |
30 | public Material getFXMaterial() {
31 | return this.materialBase;
32 | }
33 |
34 | private void markDirty() {
35 | this.materialBase.setDirty(true);
36 | }
37 |
38 | protected DoubleProperty createMaterialDoubleProperty(double value, String name) {
39 | return new SimpleDoubleProperty(ShaderMaterial.this, name, value) {
40 | @Override
41 | public void invalidated() {
42 | ShaderMaterial.this.markDirty();
43 | }
44 | };
45 | }
46 |
47 | protected IntegerProperty createMaterialIntegerProperty(int value, String name) {
48 | return new SimpleIntegerProperty(ShaderMaterial.this, name, value) {
49 | @Override
50 | public void invalidated() {
51 | ShaderMaterial.this.markDirty();
52 | }
53 | };
54 | }
55 |
56 | protected BooleanProperty createMaterialBooleanProperty(boolean value, String name) {
57 | return new SimpleBooleanProperty(ShaderMaterial.this, name, value) {
58 | @Override
59 | public void invalidated() {
60 | ShaderMaterial.this.markDirty();
61 | }
62 | };
63 | }
64 |
65 | protected ObjectProperty createMaterialObjectProperty(T value, String name) {
66 | return new SimpleObjectProperty<>(ShaderMaterial.this, name, value) {
67 | @Override
68 | public void invalidated() {
69 | ShaderMaterial.this.markDirty();
70 | }
71 | };
72 | }
73 |
74 | protected ObjectProperty createMaterialImageProperty(Image value, String name) {
75 | return new SimpleObjectProperty<>(ShaderMaterial.this, name, value) {
76 |
77 | private boolean needsListeners;
78 | private Image lastImage;
79 |
80 | @Override
81 | public void invalidated() {
82 | final Image image = this.get();
83 |
84 | if (this.needsListeners) {
85 | Toolkit.getImageAccessor().getImageProperty(this.lastImage)
86 | .removeListener(ShaderMaterial.this.platformImageChangeListener.getWeakListener());
87 | }
88 |
89 | this.needsListeners = image != null && (Toolkit.getImageAccessor().isAnimation(image) || image.getProgress() < 1);
90 | if (this.needsListeners) {
91 | Toolkit.getImageAccessor().getImageProperty(image).
92 | addListener(ShaderMaterial.this.platformImageChangeListener.getWeakListener());
93 | }
94 |
95 | this.lastImage = image;
96 |
97 | ShaderMaterial.this.markDirty();
98 | }
99 | };
100 | }
101 |
102 | private final AbstractNotifyListener platformImageChangeListener = new AbstractNotifyListener() {
103 | @Override
104 | public void invalidated(Observable valueModel) {
105 | ShaderMaterial.this.markDirty();
106 | }
107 | };
108 |
109 | }
110 |
--------------------------------------------------------------------------------
/src/main/java/de/teragam/jfxshader/material/ShaderMaterialPeer.java:
--------------------------------------------------------------------------------
1 | package de.teragam.jfxshader.material;
2 |
3 | import java.util.HashMap;
4 | import java.util.Map;
5 |
6 | import javafx.scene.image.Image;
7 |
8 | import com.sun.javafx.geom.transform.BaseTransform;
9 | import com.sun.javafx.sg.prism.NGCamera;
10 | import com.sun.javafx.sg.prism.NGLightBase;
11 | import com.sun.prism.Graphics;
12 | import com.sun.prism.MeshView;
13 | import com.sun.prism.impl.ps.BaseShaderContext;
14 | import com.sun.scenario.effect.impl.prism.PrFilterContext;
15 |
16 | import de.teragam.jfxshader.JFXShader;
17 | import de.teragam.jfxshader.ShaderController;
18 | import de.teragam.jfxshader.ShaderDeclaration;
19 | import de.teragam.jfxshader.material.internal.AbstractShaderMaterialPeerRenderer;
20 | import de.teragam.jfxshader.material.internal.ShaderMeshView;
21 | import de.teragam.jfxshader.material.internal.d3d.D3DShaderMaterialPeerRenderer;
22 | import de.teragam.jfxshader.material.internal.es2.ES2ShaderMaterialPeerRenderer;
23 | import de.teragam.jfxshader.material.internal.es2.ES2ShaderMeshView;
24 |
25 | public abstract class ShaderMaterialPeer {
26 |
27 | private JFXShader vertexShader;
28 | private JFXShader pixelShader;
29 | private JFXShader es2Shader;
30 |
31 | private BaseTransform transform;
32 | private NGLightBase[] lights;
33 | private NGCamera camera;
34 |
35 | private final Map imageIndexMap;
36 | private final AbstractShaderMaterialPeerRenderer peerRenderer;
37 |
38 | protected ShaderMaterialPeer() {
39 | this.imageIndexMap = new HashMap<>();
40 | if (ShaderController.isGLSLSupported()) {
41 | this.peerRenderer = new ES2ShaderMaterialPeerRenderer();
42 | } else {
43 | this.peerRenderer = new D3DShaderMaterialPeerRenderer();
44 | }
45 | }
46 |
47 | public Map getES2ShaderAttributes() {
48 | final Map attributes = new HashMap<>();
49 | attributes.put("pos", 0);
50 | attributes.put("texCoords", 1);
51 | attributes.put("tangent", 2);
52 | return attributes;
53 | }
54 |
55 | protected void setSamplerImage(Image image, int index) {
56 | this.imageIndexMap.put(index, image);
57 | }
58 |
59 | public final void filter(Graphics g, MeshView meshView, BaseShaderContext context) {
60 | this.transform = g.getTransformNoClone();
61 | this.lights = g.getLights();
62 | this.camera = g.getCameraNoClone();
63 |
64 | if (ShaderController.isHLSLSupported()) {
65 | this.filterD3D(g, (ShaderMeshView) meshView, context);
66 | } else if (ShaderController.isGLSLSupported()) {
67 | this.filterES2(g, (ES2ShaderMeshView) meshView, context);
68 | }
69 | }
70 |
71 | protected void filterD3D(Graphics g, ShaderMeshView meshView, BaseShaderContext context) {
72 | if (this.vertexShader == null) {
73 | this.vertexShader = this.createVertexShader(g);
74 | }
75 | if (this.pixelShader == null) {
76 | this.pixelShader = this.createPixelShader(g);
77 | }
78 | this.vertexShader.enable();
79 | this.pixelShader.enable();
80 | this.imageIndexMap.clear();
81 | this.peerRenderer.updateMatrices(g, context);
82 | this.updateShader(this.vertexShader, this.pixelShader, (T) meshView.getMaterial().getShaderMaterial());
83 | this.peerRenderer.render(g, meshView, context, this.imageIndexMap);
84 | }
85 |
86 | protected void filterES2(Graphics g, ES2ShaderMeshView meshView, BaseShaderContext context) {
87 | if (this.es2Shader == null) {
88 | this.es2Shader = this.createES2ShaderProgram(g);
89 | }
90 | this.es2Shader.enable();
91 | this.imageIndexMap.clear();
92 | this.peerRenderer.updateMatrices(g, context);
93 | this.updateShader(this.es2Shader, this.es2Shader, (T) meshView.getMaterial().getShaderMaterial());
94 | this.peerRenderer.render(g, meshView, context, this.imageIndexMap);
95 | }
96 |
97 | private JFXShader createPixelShader(Graphics g) {
98 | return ShaderController.createShader(PrFilterContext.getInstance(g.getAssociatedScreen()), this.createPixelShaderDeclaration());
99 | }
100 |
101 | private JFXShader createVertexShader(Graphics g) {
102 | return ShaderController.createVertexShader(PrFilterContext.getInstance(g.getAssociatedScreen()), this.createVertexShaderDeclaration());
103 | }
104 |
105 | private JFXShader createES2ShaderProgram(Graphics g) {
106 | return ShaderController.createES2ShaderProgram(PrFilterContext.getInstance(g.getAssociatedScreen()),
107 | this.createVertexShaderDeclaration(), this.createPixelShaderDeclaration(), this.getES2ShaderAttributes());
108 | }
109 |
110 | protected BaseTransform getTransform() {
111 | return this.transform;
112 | }
113 |
114 | protected NGLightBase[] getLights() {
115 | return this.lights;
116 | }
117 |
118 | protected NGCamera getCamera() {
119 | return this.camera;
120 | }
121 |
122 | protected float[] getWorldMatrix() {
123 | return this.peerRenderer.getWorldMatrix();
124 | }
125 |
126 | protected float[] getViewProjectionMatrix() {
127 | return this.peerRenderer.getViewProjectionMatrix();
128 | }
129 |
130 | public abstract ShaderDeclaration createPixelShaderDeclaration();
131 |
132 | public abstract ShaderDeclaration createVertexShaderDeclaration();
133 |
134 | protected abstract void updateShader(JFXShader vertexShader, JFXShader pixelShader, T material);
135 |
136 | }
137 |
--------------------------------------------------------------------------------
/src/main/java/de/teragam/jfxshader/material/internal/AbstractShaderMaterialPeerRenderer.java:
--------------------------------------------------------------------------------
1 | package de.teragam.jfxshader.material.internal;
2 |
3 | import java.util.Map;
4 |
5 | import javafx.scene.image.Image;
6 |
7 | import com.sun.javafx.geom.transform.Affine3D;
8 | import com.sun.javafx.geom.transform.BaseTransform;
9 | import com.sun.javafx.geom.transform.GeneralTransform3D;
10 | import com.sun.javafx.tk.Toolkit;
11 | import com.sun.prism.Graphics;
12 | import com.sun.prism.Texture;
13 | import com.sun.prism.impl.ps.BaseShaderContext;
14 |
15 | import de.teragam.jfxshader.util.Reflect;
16 |
17 | public abstract class AbstractShaderMaterialPeerRenderer {
18 |
19 | private float[] worldMatrix = new float[16];
20 | private float[] viewProjectionMatrix = new float[16];
21 |
22 | private final GeneralTransform3D worldTx = new GeneralTransform3D();
23 | private final GeneralTransform3D scratchTx = new GeneralTransform3D();
24 | private final Affine3D scratchAffine3DTx = new Affine3D();
25 |
26 | public void updateMatrices(Graphics g, BaseShaderContext context) {
27 | final Reflect extends BaseShaderContext> contextReflect = Reflect.on(context.getClass());
28 | final GeneralTransform3D projViewTx = contextReflect.getFieldValue("projViewTx", context);
29 | this.scratchTx.set(projViewTx);
30 | final float pixelScaleFactorX = g.getPixelScaleFactorX();
31 | final float pixelScaleFactorY = g.getPixelScaleFactorY();
32 | if (pixelScaleFactorX != 1.0 || pixelScaleFactorY != 1.0) {
33 | this.scratchTx.scale(pixelScaleFactorX, pixelScaleFactorY, 1.0);
34 | }
35 | this.viewProjectionMatrix = this.convertMatrix(this.scratchTx);
36 |
37 | final BaseTransform xform = g.getTransformNoClone();
38 | if (pixelScaleFactorX != 1.0 || pixelScaleFactorY != 1.0) {
39 | this.scratchAffine3DTx.setToIdentity();
40 | this.scratchAffine3DTx.scale(1.0 / pixelScaleFactorX, 1.0 / pixelScaleFactorY);
41 | this.scratchAffine3DTx.concatenate(xform);
42 | this.updateWorldTransform(this.scratchAffine3DTx);
43 | } else {
44 | this.updateWorldTransform(xform);
45 | }
46 | this.worldMatrix = this.convertMatrix(this.worldTx);
47 | }
48 |
49 | public abstract void render(Graphics g, ShaderMeshView meshView, BaseShaderContext context, Map imageIndexMap);
50 |
51 | protected abstract float[] convertMatrix(GeneralTransform3D src);
52 |
53 | protected Texture getTexture(BaseShaderContext context, Image image, boolean useMipmap) {
54 | if (image == null) {
55 | return null;
56 | }
57 | final com.sun.prism.Image platformImage = (com.sun.prism.Image) Toolkit.getImageAccessor().getPlatformImage(image);
58 | return platformImage == null ? null : context.getResourceFactory().getCachedTexture(platformImage, Texture.WrapMode.REPEAT, useMipmap);
59 | }
60 |
61 | private void updateWorldTransform(BaseTransform xform) {
62 | this.worldTx.setIdentity();
63 | if ((xform != null) && (!xform.isIdentity())) {
64 | this.worldTx.mul(xform);
65 | }
66 | }
67 |
68 | public float[] getWorldMatrix() {
69 | return this.worldMatrix;
70 | }
71 |
72 | public float[] getViewProjectionMatrix() {
73 | return this.viewProjectionMatrix;
74 | }
75 |
76 | }
77 |
--------------------------------------------------------------------------------
/src/main/java/de/teragam/jfxshader/material/internal/BaseMeshHelper.java:
--------------------------------------------------------------------------------
1 | package de.teragam.jfxshader.material.internal;
2 |
3 | import com.sun.prism.impl.Disposer;
4 |
5 | public interface BaseMeshHelper extends Disposer.Record {
6 | boolean buildNativeGeometry(float[] vertexBuffer, int vertexBufferLength, int[] indexBufferInt, int indexBufferLength);
7 |
8 | boolean buildNativeGeometry(float[] vertexBuffer, int vertexBufferLength, short[] indexBufferShort, int indexBufferLength);
9 | }
10 |
--------------------------------------------------------------------------------
/src/main/java/de/teragam/jfxshader/material/internal/InternalBasePhongMaterial.java:
--------------------------------------------------------------------------------
1 | package de.teragam.jfxshader.material.internal;
2 |
3 | import com.sun.prism.TextureMap;
4 | import com.sun.prism.impl.BasePhongMaterial;
5 | import com.sun.prism.impl.Disposer;
6 |
7 | import de.teragam.jfxshader.material.ShaderMaterial;
8 |
9 | public class InternalBasePhongMaterial extends BasePhongMaterial {
10 |
11 | private final ShaderMaterial material;
12 |
13 | public InternalBasePhongMaterial(ShaderMaterial material, Disposer.Record disposerRecord) {
14 | super(disposerRecord);
15 | this.material = material;
16 | }
17 |
18 | public static InternalBasePhongMaterial create(ShaderMaterial material) {
19 | return new InternalBasePhongMaterial(material, () -> {});
20 | }
21 |
22 | public ShaderMaterial getShaderMaterial() {
23 | return this.material;
24 | }
25 |
26 | @Override
27 | public void setDiffuseColor(float r, float g, float b, float a) {
28 | // Not needed
29 | }
30 |
31 | @Override
32 | public void setSpecularColor(boolean set, float r, float g, float b, float a) {
33 | // Not needed
34 | }
35 |
36 | @Override
37 | public void setTextureMap(TextureMap map) {
38 | // Not needed
39 | }
40 |
41 | @Override
42 | public void lockTextureMaps() {
43 | // Not needed
44 | }
45 |
46 | @Override
47 | public void unlockTextureMaps() {
48 | // Not needed
49 | }
50 |
51 | @Override
52 | public void dispose() {
53 | this.disposerRecord.dispose();
54 | }
55 |
56 | }
57 |
--------------------------------------------------------------------------------
/src/main/java/de/teragam/jfxshader/material/internal/InternalNGBox.java:
--------------------------------------------------------------------------------
1 | package de.teragam.jfxshader.material.internal;
2 |
3 | import com.sun.javafx.sg.prism.NGBox;
4 | import com.sun.prism.Graphics;
5 |
6 | public class InternalNGBox extends NGBox {
7 | @Override
8 | protected void renderContent(Graphics g) {
9 | super.renderContent(MeshProxyHelper.createGraphicsProxy(g, this));
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/main/java/de/teragam/jfxshader/material/internal/InternalNGCylinder.java:
--------------------------------------------------------------------------------
1 | package de.teragam.jfxshader.material.internal;
2 |
3 | import com.sun.javafx.sg.prism.NGCylinder;
4 | import com.sun.prism.Graphics;
5 |
6 | public class InternalNGCylinder extends NGCylinder {
7 | @Override
8 | protected void renderContent(Graphics g) {
9 | super.renderContent(MeshProxyHelper.createGraphicsProxy(g, this));
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/main/java/de/teragam/jfxshader/material/internal/InternalNGMeshView.java:
--------------------------------------------------------------------------------
1 | package de.teragam.jfxshader.material.internal;
2 |
3 | import com.sun.javafx.sg.prism.NGMeshView;
4 | import com.sun.prism.Graphics;
5 |
6 | public class InternalNGMeshView extends NGMeshView {
7 | @Override
8 | protected void renderContent(Graphics g) {
9 | super.renderContent(MeshProxyHelper.createGraphicsProxy(g, this));
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/main/java/de/teragam/jfxshader/material/internal/InternalNGPhongMaterial.java:
--------------------------------------------------------------------------------
1 | package de.teragam.jfxshader.material.internal;
2 |
3 | import com.sun.javafx.sg.prism.NGPhongMaterial;
4 |
5 | import de.teragam.jfxshader.material.ShaderMaterial;
6 |
7 | public class InternalNGPhongMaterial extends NGPhongMaterial {
8 |
9 | private final ShaderMaterial material;
10 |
11 | public InternalNGPhongMaterial(ShaderMaterial material) {
12 | this.material = material;
13 | }
14 |
15 | public ShaderMaterial getMaterial() {
16 | return this.material;
17 | }
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/java/de/teragam/jfxshader/material/internal/InternalNGSphere.java:
--------------------------------------------------------------------------------
1 | package de.teragam.jfxshader.material.internal;
2 |
3 | import com.sun.javafx.sg.prism.NGSphere;
4 | import com.sun.prism.Graphics;
5 |
6 | public class InternalNGSphere extends NGSphere {
7 | @Override
8 | protected void renderContent(Graphics g) {
9 | super.renderContent(MeshProxyHelper.createGraphicsProxy(g, this));
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/main/java/de/teragam/jfxshader/material/internal/MeshProxyHelper.java:
--------------------------------------------------------------------------------
1 | package de.teragam.jfxshader.material.internal;
2 |
3 | import java.lang.reflect.InvocationHandler;
4 | import java.lang.reflect.Proxy;
5 |
6 | import com.sun.javafx.sg.prism.NGPhongMaterial;
7 | import com.sun.javafx.sg.prism.NGShape3D;
8 | import com.sun.javafx.sg.prism.NGTriangleMesh;
9 | import com.sun.prism.Graphics;
10 | import com.sun.prism.Mesh;
11 | import com.sun.prism.MeshView;
12 | import com.sun.prism.ResourceFactory;
13 | import com.sun.prism.impl.BaseMesh;
14 |
15 | import de.teragam.jfxshader.ShaderController;
16 | import de.teragam.jfxshader.material.ShaderMaterial;
17 | import de.teragam.jfxshader.material.internal.es2.ES2ShaderMeshView;
18 | import de.teragam.jfxshader.material.internal.es2.InternalES2BasePhongMaterial;
19 | import de.teragam.jfxshader.util.Reflect;
20 |
21 | public final class MeshProxyHelper {
22 |
23 | private MeshProxyHelper() {}
24 |
25 | public static Graphics createGraphicsProxy(Graphics g, NGShape3D shape) {
26 | final NGPhongMaterial material = Reflect.on(NGShape3D.class).getFieldValue("material", shape);
27 | final MeshView meshView = Reflect.on(NGShape3D.class).getFieldValue("meshView", shape);
28 | if (!(material instanceof InternalNGPhongMaterial)) {
29 | if (meshView instanceof ShaderMeshView) {
30 | MeshProxyHelper.resetShape(shape);
31 | }
32 | return g;
33 | } else {
34 | if (ShaderController.isHLSLSupported()) {
35 | MeshProxyHelper.cleanNestedD3DMesh(shape);
36 | }
37 | if (!(meshView instanceof ShaderMeshView) && meshView != null) {
38 | MeshProxyHelper.resetShape(shape);
39 | }
40 | final Object proxyFactory = MeshProxyHelper.createResourceFactoryProxy(g.getResourceFactory(), ((InternalNGPhongMaterial) material).getMaterial());
41 | final Object proxyGraphics = Proxy.newProxyInstance(Graphics.class.getClassLoader(), new Class>[]{Graphics.class, GraphicsHelper.class},
42 | (proxy, method, args) -> {
43 | if ("getResourceFactory".equals(method.getName())) {
44 | return proxyFactory;
45 | }
46 | if ("getRawGraphics".equals(method.getName())) {
47 | return g;
48 | }
49 | return method.invoke(g, args);
50 | });
51 |
52 | return (Graphics) proxyGraphics;
53 | }
54 | }
55 |
56 | private static void cleanNestedD3DMesh(NGShape3D shape) {
57 | final NGTriangleMesh mesh = Reflect.on(NGShape3D.class).getFieldValue("mesh", shape);
58 | if (mesh != null) {
59 | final Mesh internalMesh = Reflect.on(NGTriangleMesh.class).getFieldValue("mesh", mesh);
60 | if (Reflect.resolveClass("com.sun.prism.d3d.D3DMesh").isInstance(internalMesh)) {
61 | internalMesh.dispose();
62 | Reflect.on(NGTriangleMesh.class).setFieldValue("mesh", mesh, null);
63 | }
64 | }
65 | }
66 |
67 | private static void resetShape(NGShape3D shape) {
68 | final MeshView meshView = Reflect.on(NGShape3D.class).getFieldValue("meshView", shape);
69 | if (meshView != null) {
70 | meshView.dispose();
71 | Reflect.on(NGShape3D.class).setFieldValue("meshView", shape, null);
72 | final NGTriangleMesh mesh = Reflect.on(NGShape3D.class).getFieldValue("mesh", shape);
73 | if (mesh != null) {
74 | final Mesh internalMesh = Reflect.on(NGTriangleMesh.class).getFieldValue("mesh", mesh);
75 | if (internalMesh != null) {
76 | internalMesh.dispose();
77 | Reflect.on(NGTriangleMesh.class).setFieldValue("mesh", mesh, null);
78 | }
79 | }
80 | }
81 | final NGPhongMaterial material = Reflect.on(NGShape3D.class).getFieldValue("material", shape);
82 | if (material != null && Reflect.on(NGPhongMaterial.class).getFieldValue("material", material) != null) {
83 | Reflect.on(NGPhongMaterial.class).method("disposeMaterial").invoke(material);
84 | }
85 | }
86 |
87 | private static ResourceFactory createResourceFactoryProxy(ResourceFactory rf, ShaderMaterial material) {
88 | final InvocationHandler handler;
89 | if (ShaderController.isHLSLSupported()) {
90 | handler = (proxy, method, args) -> {
91 | if ("createMesh".equals(method.getName())) {
92 | return ShaderBaseMesh.create(rf);
93 | }
94 | if ("createMeshView".equals(method.getName())) {
95 | return ShaderMeshView.create((ShaderBaseMesh) args[0]);
96 | }
97 | if ("createPhongMaterial".equals(method.getName())) {
98 | return InternalBasePhongMaterial.create(material);
99 | }
100 | return method.invoke(rf, args);
101 | };
102 | } else {
103 | handler = (proxy, method, args) -> {
104 | if ("createMeshView".equals(method.getName())) {
105 | return ES2ShaderMeshView.create(rf, (BaseMesh) args[0]);
106 | }
107 | if ("createPhongMaterial".equals(method.getName())) {
108 | return InternalES2BasePhongMaterial.create(rf, material);
109 | }
110 | return method.invoke(rf, args);
111 | };
112 | }
113 | return (ResourceFactory) Proxy.newProxyInstance(ResourceFactory.class.getClassLoader(), new Class>[]{ResourceFactory.class}, handler);
114 | }
115 |
116 | public interface GraphicsHelper {
117 | Graphics getRawGraphics();
118 | }
119 |
120 | }
121 |
--------------------------------------------------------------------------------
/src/main/java/de/teragam/jfxshader/material/internal/ShaderBaseMesh.java:
--------------------------------------------------------------------------------
1 | package de.teragam.jfxshader.material.internal;
2 |
3 | import com.sun.prism.ResourceFactory;
4 | import com.sun.prism.impl.BaseMesh;
5 |
6 | import de.teragam.jfxshader.MaterialController;
7 | import de.teragam.jfxshader.material.internal.d3d.D3DBaseMeshHelper;
8 |
9 | public class ShaderBaseMesh extends BaseMesh {
10 |
11 | private static int count = 0;
12 |
13 | private final BaseMeshHelper meshHelper;
14 |
15 | protected ShaderBaseMesh(BaseMeshHelper meshHelper) {
16 | super(meshHelper);
17 | this.meshHelper = meshHelper;
18 | count++;
19 | }
20 |
21 | public BaseMeshHelper getMeshHelper() {
22 | return this.meshHelper;
23 | }
24 |
25 | public static ShaderBaseMesh create(ResourceFactory resourceFactory) {
26 | return new ShaderBaseMesh(new D3DBaseMeshHelper(MaterialController.getD3DDevice(resourceFactory)));
27 | }
28 |
29 | @Override
30 | public boolean buildNativeGeometry(float[] vertexBuffer, int vertexBufferLength, int[] indexBufferInt, int indexBufferLength) {
31 | return this.meshHelper.buildNativeGeometry(vertexBuffer, vertexBufferLength, indexBufferInt, indexBufferLength);
32 | }
33 |
34 | @Override
35 | public boolean buildNativeGeometry(float[] vertexBuffer, int vertexBufferLength, short[] indexBufferShort, int indexBufferLength) {
36 | return this.meshHelper.buildNativeGeometry(vertexBuffer, vertexBufferLength, indexBufferShort, indexBufferLength);
37 | }
38 |
39 | @Override
40 | public int getCount() {
41 | return ShaderBaseMesh.count;
42 | }
43 |
44 | @Override
45 | public void dispose() {
46 | this.disposerRecord.dispose();
47 | ShaderBaseMesh.count--;
48 | }
49 |
50 | }
51 |
--------------------------------------------------------------------------------
/src/main/java/de/teragam/jfxshader/material/internal/ShaderMaterialBase.java:
--------------------------------------------------------------------------------
1 | package de.teragam.jfxshader.material.internal;
2 |
3 | import javafx.scene.paint.Material;
4 | import javafx.scene.paint.PhongMaterial;
5 |
6 | import com.sun.javafx.sg.prism.NGPhongMaterial;
7 |
8 | import de.teragam.jfxshader.material.ShaderMaterial;
9 | import de.teragam.jfxshader.util.Reflect;
10 |
11 | public class ShaderMaterialBase extends PhongMaterial {
12 |
13 | private final ShaderMaterial material;
14 |
15 | public ShaderMaterialBase(ShaderMaterial material) {
16 | this.material = material;
17 | }
18 |
19 | public void setDirty(boolean value) {
20 | Reflect.on(Material.class).method("setDirty").invoke(this, value);
21 | }
22 |
23 | public boolean isDirty() {
24 | return (boolean) Reflect.on(Material.class).method("isDirty").invoke(this);
25 | }
26 |
27 | public ShaderMaterial getJFXShaderMaterial() {
28 | return this.material;
29 | }
30 |
31 | private NGPhongMaterial peer;
32 |
33 | public NGPhongMaterial getInternalNGMaterial() {
34 | if (this.peer == null) {
35 | this.peer = new InternalNGPhongMaterial(this.material);
36 | }
37 | return this.peer;
38 | }
39 |
40 | public void updateMaterial() {
41 | this.setDirty(false);
42 | }
43 |
44 | @Override
45 | public String toString() {
46 | return "ShaderMaterialBase [material=" + this.material + "]";
47 | }
48 |
49 | }
50 |
--------------------------------------------------------------------------------
/src/main/java/de/teragam/jfxshader/material/internal/ShaderMeshView.java:
--------------------------------------------------------------------------------
1 | package de.teragam.jfxshader.material.internal;
2 |
3 | import com.sun.prism.Graphics;
4 | import com.sun.prism.Material;
5 | import com.sun.prism.impl.BaseGraphics;
6 | import com.sun.prism.impl.BaseMesh;
7 | import com.sun.prism.impl.BaseMeshView;
8 | import com.sun.prism.impl.Disposer;
9 | import com.sun.prism.impl.ps.BaseShaderContext;
10 |
11 | import de.teragam.jfxshader.MaterialController;
12 | import de.teragam.jfxshader.util.Reflect;
13 |
14 | public class ShaderMeshView extends BaseMeshView {
15 |
16 | private final BaseMesh mesh;
17 | private InternalBasePhongMaterial material;
18 | private int cullingMode;
19 | private boolean wireframe;
20 |
21 | public ShaderMeshView(BaseMesh mesh, Disposer.Record disposerRecord) {
22 | super(disposerRecord);
23 | this.mesh = mesh;
24 | }
25 |
26 | public static ShaderMeshView create(BaseMesh mesh) {
27 | return new ShaderMeshView(mesh, () -> {});
28 | }
29 |
30 | @Override
31 | public void setCullingMode(int mode) {
32 | this.cullingMode = mode;
33 | }
34 |
35 | @Override
36 | public void setMaterial(Material material) {
37 | this.material = (InternalBasePhongMaterial) material;
38 | }
39 |
40 | @Override
41 | public void setWireframe(boolean wireframe) {
42 | this.wireframe = wireframe;
43 | }
44 |
45 | @Override
46 | public void setAmbientLight(float r, float g, float b) {
47 | // Not needed
48 | }
49 |
50 | @Override
51 | public void setLight(int index, float x, float y, float z, float r, float g, float b, float w, float ca, float la, float qa, float isAttenuated,
52 | float maxRange, float dirX, float dirY, float dirZ, float innerAngle, float outerAngle, float falloff) {
53 | // Not needed
54 | }
55 |
56 | @Override
57 | public void render(Graphics g) {
58 | if (g instanceof MeshProxyHelper.GraphicsHelper) {
59 | final Graphics rawGraphics = ((MeshProxyHelper.GraphicsHelper) g).getRawGraphics();
60 | final BaseShaderContext context = Reflect.on(BaseGraphics.class).getFieldValue("context", rawGraphics);
61 | MaterialController.getPeer(this.getMaterial().getShaderMaterial()).filter(rawGraphics, this, context);
62 | }
63 | }
64 |
65 | @Override
66 | public boolean isValid() {
67 | return true;
68 | }
69 |
70 | @Override
71 | public void dispose() {
72 | if (this.mesh != null) {
73 | this.mesh.dispose();
74 | }
75 | this.material = null;
76 | }
77 |
78 | public BaseMesh getMesh() {
79 | return this.mesh;
80 | }
81 |
82 | public int getCullingMode() {
83 | return this.cullingMode;
84 | }
85 |
86 | public boolean isWireframe() {
87 | return this.wireframe;
88 | }
89 |
90 | public InternalBasePhongMaterial getMaterial() {
91 | return this.material;
92 | }
93 |
94 | }
95 |
--------------------------------------------------------------------------------
/src/main/java/de/teragam/jfxshader/material/internal/d3d/D3D9Types.java:
--------------------------------------------------------------------------------
1 | package de.teragam.jfxshader.material.internal.d3d;
2 |
3 | public final class D3D9Types {
4 |
5 | private D3D9Types() {}
6 |
7 | // D3DFORMAT constants
8 | public static final int D3DFMT_INDEX16 = 0x65;
9 | public static final int D3DFMT_INDEX32 = 0x66;
10 |
11 | // D3DFVF constants
12 | public static final int D3DFVF_XYZ = 0x2;
13 | public static final int D3DFVF_TEXCOUNT_SHIFT = 0x8;
14 | public static final int D3DFVF_TEXTUREFORMAT4 = 0x2;
15 |
16 | public static int D3DFVF_TEXCOORDSIZE4(int coordIndex) {
17 | return D3DFVF_TEXTUREFORMAT4 << (coordIndex * 2 + 16);
18 | }
19 |
20 | // D3DUSAGE constants
21 | public static final int D3DUSAGE_WRITEONLY = 0x8;
22 |
23 | // D3DFILLMODE constants
24 | public static final int D3DFILL_POINT = 0x1;
25 | public static final int D3DFILL_WIREFRAME = 0x2;
26 | public static final int D3DFILL_SOLID = 0x3;
27 |
28 | // D3DRENDERSTATETYPE constants
29 | public static final int D3DRS_FILLMODE = 0x8;
30 | public static final int D3DRS_CULLMODE = 0x16;
31 |
32 | // D3DPRIMITIVETYPE constants
33 | public static final int D3DPT_POINTLIST = 0x1;
34 | public static final int D3DPT_LINELIST = 0x2;
35 | public static final int D3DPT_LINESTRIP = 0x3;
36 | public static final int D3DPT_TRIANGLELIST = 0x4;
37 | public static final int D3DPT_TRIANGLESTRIP = 0x5;
38 | public static final int D3DPT_TRIANGLEFAN = 0x6;
39 |
40 | // D3DCULL constants
41 | public static final int D3DCULL_NONE = 0x1;
42 | public static final int D3DCULL_CW = 0x2;
43 | public static final int D3DCULL_CCW = 0x3;
44 |
45 | // D3DPOOL constants
46 | public static final int D3DPOOL_DEFAULT = 0x0;
47 | public static final int D3DPOOL_MANAGED = 0x1;
48 | public static final int D3DPOOL_SYSTEMMEM = 0x2;
49 | public static final int D3DPOOL_SCRATCH = 0x3;
50 |
51 | }
52 |
--------------------------------------------------------------------------------
/src/main/java/de/teragam/jfxshader/material/internal/d3d/D3DBaseMeshHelper.java:
--------------------------------------------------------------------------------
1 | package de.teragam.jfxshader.material.internal.d3d;
2 |
3 | import de.teragam.jfxshader.material.internal.BaseMeshHelper;
4 |
5 | public final class D3DBaseMeshHelper implements BaseMeshHelper {
6 |
7 | private long vertexBufferHandle;
8 | private long indexBufferHandle;
9 | private long numVertices;
10 | private long numIndices;
11 | private final Direct3DDevice9 device;
12 |
13 | public D3DBaseMeshHelper(Direct3DDevice9 device) {
14 | this.device = device;
15 | }
16 |
17 | private static final int PRIMITIVE_VERTEX_SIZE = 36;
18 |
19 | @Override
20 | public boolean buildNativeGeometry(float[] vertexBuffer, int vertexBufferLength, int[] indexBufferInt, int indexBufferLength) {
21 | if (!this.buildVertexBuffer(vertexBuffer, vertexBufferLength)) {
22 | return false;
23 | }
24 |
25 | final int indexSize = indexBufferLength * 4;
26 | if (this.numIndices != indexBufferLength) {
27 | this.device.releaseResource(this.indexBufferHandle);
28 | this.indexBufferHandle = this.device.createIndexBuffer(indexSize, D3D9Types.D3DUSAGE_WRITEONLY, D3D9Types.D3DFMT_INDEX32,
29 | D3D9Types.D3DPOOL_DEFAULT, 0);
30 | this.numIndices = indexBufferLength;
31 | if (this.device.getResultCode() != 0 || this.indexBufferHandle == 0) {
32 | return false;
33 | }
34 | }
35 | this.device.uploadIndexBufferDataInt(this.indexBufferHandle, indexBufferInt, indexSize);
36 |
37 | return this.device.getResultCode() == 0;
38 | }
39 |
40 | @Override
41 | public boolean buildNativeGeometry(float[] vertexBuffer, int vertexBufferLength, short[] indexBufferShort, int indexBufferLength) {
42 | if (!this.buildVertexBuffer(vertexBuffer, vertexBufferLength)) {
43 | return false;
44 | }
45 |
46 | final int indexSize = indexBufferLength * 4;
47 | if (this.numIndices != indexBufferLength) {
48 | this.device.releaseResource(this.indexBufferHandle);
49 | this.indexBufferHandle = this.device.createIndexBuffer(indexSize, D3D9Types.D3DUSAGE_WRITEONLY, D3D9Types.D3DFMT_INDEX16,
50 | D3D9Types.D3DPOOL_DEFAULT, 0);
51 | this.numIndices = indexBufferLength;
52 | if (this.device.getResultCode() != 0 || this.indexBufferHandle == 0) {
53 | return false;
54 | }
55 | }
56 | this.device.uploadIndexBufferDataShort(this.indexBufferHandle, indexBufferShort, indexSize);
57 |
58 | return this.device.getResultCode() == 0;
59 | }
60 |
61 | @Override
62 | public void dispose() {
63 | this.device.releaseResource(this.vertexBufferHandle);
64 | if (this.device.getResultCode() == 0) {
65 | this.vertexBufferHandle = 0;
66 | }
67 | this.device.releaseResource(this.indexBufferHandle);
68 | if (this.device.getResultCode() == 0) {
69 | this.indexBufferHandle = 0;
70 | }
71 | }
72 |
73 | private boolean buildVertexBuffer(float[] vertexBuffer, int vertexBufferLength) {
74 | final int size = vertexBufferLength * 4;
75 | final int vbCount = vertexBufferLength / PRIMITIVE_VERTEX_SIZE;
76 | if (this.numVertices != vbCount) {
77 | this.device.releaseResource(this.vertexBufferHandle);
78 | final int fvf = D3D9Types.D3DFVF_XYZ | (2 << D3D9Types.D3DFVF_TEXCOUNT_SHIFT) | D3D9Types.D3DFVF_TEXCOORDSIZE4(1);
79 | this.vertexBufferHandle = this.device.createVertexBuffer(size, D3D9Types.D3DUSAGE_WRITEONLY, fvf,
80 | D3D9Types.D3DPOOL_DEFAULT, 0);
81 | this.numVertices = vbCount;
82 | if (this.device.getResultCode() != 0 || this.vertexBufferHandle == 0) {
83 | return false;
84 | }
85 | }
86 | this.device.uploadVertexBufferData(this.vertexBufferHandle, vertexBuffer, size);
87 | return this.device.getResultCode() == 0;
88 | }
89 |
90 | public long getVertexBufferHandle() {
91 | return this.vertexBufferHandle;
92 | }
93 |
94 | public long getIndexBufferHandle() {
95 | return this.indexBufferHandle;
96 | }
97 |
98 | public long getNumVertices() {
99 | return this.numVertices;
100 | }
101 |
102 | public long getNumIndices() {
103 | return this.numIndices;
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/src/main/java/de/teragam/jfxshader/material/internal/d3d/D3DShaderMaterialPeerRenderer.java:
--------------------------------------------------------------------------------
1 | package de.teragam.jfxshader.material.internal.d3d;
2 |
3 | import java.util.ArrayList;
4 | import java.util.List;
5 | import java.util.Map;
6 |
7 | import javafx.scene.image.Image;
8 |
9 | import com.sun.javafx.geom.transform.GeneralTransform3D;
10 | import com.sun.prism.Graphics;
11 | import com.sun.prism.Texture;
12 | import com.sun.prism.impl.ps.BaseShaderContext;
13 |
14 | import de.teragam.jfxshader.MaterialController;
15 | import de.teragam.jfxshader.exception.ShaderException;
16 | import de.teragam.jfxshader.material.internal.AbstractShaderMaterialPeerRenderer;
17 | import de.teragam.jfxshader.material.internal.ShaderBaseMesh;
18 | import de.teragam.jfxshader.material.internal.ShaderMeshView;
19 | import de.teragam.jfxshader.util.Reflect;
20 |
21 | public class D3DShaderMaterialPeerRenderer extends AbstractShaderMaterialPeerRenderer {
22 |
23 | /**
24 | * Size of a vertex in bytes.
25 | * struct PRISM_VERTEX_3D {
26 | * float x, y, z;
27 | * float tu, tv;
28 | * float nx, ny, nz, nw;
29 | * };
30 | */
31 | private static final int PRIMITIVE_VERTEX_SIZE = 4 * 3 + 4 * 2 + 4 * 4;
32 |
33 | @Override
34 | public void render(Graphics g, ShaderMeshView meshView, BaseShaderContext context, Map imageIndexMap) {
35 | final ShaderBaseMesh mesh = (ShaderBaseMesh) meshView.getMesh();
36 |
37 | final Direct3DDevice9 device = MaterialController.getD3DDevice(g.getResourceFactory());
38 | device.setFVF(D3D9Types.D3DFVF_XYZ | (2 << D3D9Types.D3DFVF_TEXCOUNT_SHIFT) | D3D9Types.D3DFVF_TEXCOORDSIZE4(1));
39 | if (device.getResultCode() != 0) {
40 | throw new ShaderException("Failed to set FVF");
41 | }
42 | final List textures = new ArrayList<>();
43 | imageIndexMap.forEach((index, image) -> {
44 | final Texture texture = this.getTexture(context, image, false);
45 | device.setTexture(index, getD3DTextureHandle(texture));
46 | if (texture != null) {
47 | textures.add(texture);
48 | }
49 | });
50 |
51 | final int cullMode = translateCullMode(meshView.getCullingMode());
52 | final int previousRenderState = device.getRenderState(D3D9Types.D3DRS_CULLMODE);
53 | if (previousRenderState != cullMode) {
54 | device.setRenderState(D3D9Types.D3DRS_CULLMODE, cullMode);
55 | }
56 | final int fillMode = meshView.isWireframe() ? D3D9Types.D3DFILL_WIREFRAME : D3D9Types.D3DFILL_SOLID;
57 | final int previousFillMode = device.getRenderState(D3D9Types.D3DRS_FILLMODE);
58 | if (previousFillMode != fillMode) {
59 | device.setRenderState(D3D9Types.D3DRS_FILLMODE, fillMode);
60 | }
61 | final D3DBaseMeshHelper meshHelper = (D3DBaseMeshHelper) mesh.getMeshHelper();
62 | device.setStreamSource(0, meshHelper.getVertexBufferHandle(), 0, PRIMITIVE_VERTEX_SIZE);
63 | device.setIndices(meshHelper.getIndexBufferHandle());
64 | device.drawIndexedPrimitive(D3D9Types.D3DPT_TRIANGLELIST, 0, 0, (int) meshHelper.getNumVertices(), 0,
65 | (int) (meshHelper.getNumIndices() / 3));
66 |
67 | if (previousRenderState != cullMode) {
68 | device.setRenderState(D3D9Types.D3DRS_CULLMODE, previousRenderState);
69 | }
70 | if (previousFillMode != fillMode) {
71 | device.setRenderState(D3D9Types.D3DRS_FILLMODE, previousFillMode);
72 | }
73 |
74 | textures.forEach(Texture::unlock);
75 | }
76 |
77 | @Override
78 | protected float[] convertMatrix(GeneralTransform3D src) {
79 | final float[] rawMatrix = new float[16];
80 | for (int i = 0; i < rawMatrix.length; i++) {
81 | rawMatrix[i] = (float) src.get(i);
82 | }
83 | return rawMatrix;
84 | }
85 |
86 | private static long getD3DTextureHandle(Texture texture) {
87 | if (texture == null) {
88 | return 0;
89 | }
90 | return Reflect.on("com.sun.prism.d3d.D3DTexture").method("getNativeTextureObject").invoke(texture);
91 | }
92 |
93 | private static int translateCullMode(int cullFace) {
94 | switch (cullFace) {
95 | case 0:
96 | return D3D9Types.D3DCULL_NONE;
97 | case 1:
98 | return D3D9Types.D3DCULL_CW;
99 | case 2:
100 | return D3D9Types.D3DCULL_CCW;
101 | default:
102 | throw new IllegalArgumentException("Unknown cull mode: " + cullFace);
103 | }
104 | }
105 |
106 | }
107 |
--------------------------------------------------------------------------------
/src/main/java/de/teragam/jfxshader/material/internal/d3d/D3DVertexShader.java:
--------------------------------------------------------------------------------
1 | package de.teragam.jfxshader.material.internal.d3d;
2 |
3 | import java.io.IOException;
4 | import java.io.InputStream;
5 | import java.nio.FloatBuffer;
6 | import java.nio.IntBuffer;
7 | import java.util.Map;
8 | import java.util.Optional;
9 |
10 | import com.sun.prism.impl.Disposer;
11 |
12 | import de.teragam.jfxshader.JFXShader;
13 | import de.teragam.jfxshader.exception.ShaderException;
14 |
15 | public class D3DVertexShader implements JFXShader {
16 |
17 | private final Direct3DDevice9 device;
18 | private final long nativeHandle;
19 | private final Map registers;
20 |
21 | private boolean valid;
22 |
23 | public D3DVertexShader(Direct3DDevice9 device, long nativeHandle, Map registers) {
24 | this.device = device;
25 | this.nativeHandle = nativeHandle;
26 | this.registers = registers;
27 | this.valid = nativeHandle != 0;
28 | if (this.valid) {
29 | Disposer.addRecord(this, this::dispose);
30 | }
31 | }
32 |
33 | public static long init(Direct3DDevice9 device, InputStream source) {
34 | try (source) {
35 | final long nativeHandle = device.createVertexShader(source.readAllBytes());
36 | if (nativeHandle == 0) {
37 | throw new ShaderException("Failed to create vertex shader");
38 | }
39 | return nativeHandle;
40 | } catch (IOException e) {
41 | throw new ShaderException("Failed to read vertex shader source", e);
42 | }
43 | }
44 |
45 | @Override
46 | public void enable() {
47 | this.device.setVertexShader(this.nativeHandle);
48 | this.validate();
49 | }
50 |
51 | @Override
52 | public void disable() {
53 | this.device.setVertexShader(0);
54 | this.validate();
55 | }
56 |
57 | @Override
58 | public boolean isValid() {
59 | return this.valid;
60 | }
61 |
62 | @Override
63 | public void setConstant(String name, int i0) {
64 | this.setConstants(name, i0);
65 | }
66 |
67 | @Override
68 | public void setConstant(String name, int i0, int i1) {
69 | this.setConstants(name, i0, i1);
70 | }
71 |
72 | @Override
73 | public void setConstant(String name, int i0, int i1, int i2) {
74 | this.setConstants(name, i0, i1, i2);
75 | }
76 |
77 | @Override
78 | public void setConstant(String name, int i0, int i1, int i2, int i3) {
79 | this.setConstants(name, i0, i1, i2, i3);
80 | }
81 |
82 | @Override
83 | public void setConstants(String name, IntBuffer buf, int off, int count) {
84 | final int[] data = new int[count * 4];
85 | buf.get(data, off * 4, count * 4);
86 | this.device.setVertexShaderConstantI(this.getRegister(name), data, count);
87 | this.validate();
88 | }
89 |
90 | private void setConstants(String name, int... data) {
91 | if (data.length > 0) {
92 | this.device.setVertexShaderConstantI(this.getRegister(name), data, Math.max(1, data.length / 4));
93 | this.validate();
94 | }
95 | }
96 |
97 | @Override
98 | public void setConstant(String name, float f0) {
99 | this.setConstants(name, f0);
100 | }
101 |
102 | @Override
103 | public void setConstant(String name, float f0, float f1) {
104 | this.setConstants(name, f0, f1);
105 | }
106 |
107 | @Override
108 | public void setConstant(String name, float f0, float f1, float f2) {
109 | this.setConstants(name, f0, f1, f2);
110 | }
111 |
112 | @Override
113 | public void setConstant(String name, float f0, float f1, float f2, float f3) {
114 | this.setConstants(name, f0, f1, f2, f3);
115 | }
116 |
117 | @Override
118 | public void setConstants(String name, FloatBuffer buf, int off, int count) {
119 | final float[] data = new float[count * 4];
120 | buf.get(data, off * 4, count * 4);
121 | this.device.setVertexShaderConstantF(this.getRegister(name), data, count);
122 | this.validate();
123 | }
124 |
125 | private void setConstants(String name, float... data) {
126 | if (data.length > 0) {
127 | this.device.setVertexShaderConstantF(this.getRegister(name), data, Math.max(1, data.length / 4));
128 | this.validate();
129 | }
130 | }
131 |
132 | @Override
133 | public void dispose() {
134 | this.valid = false;
135 | this.device.releaseResource(this.nativeHandle);
136 | this.validate();
137 | }
138 |
139 | private void validate() {
140 | if (this.device.getResultCode() != 0) {
141 | this.valid = false;
142 | throw new ShaderException("Vertex shader operation failed");
143 | }
144 | }
145 |
146 | private int getRegister(String name) {
147 | return Optional.ofNullable(this.registers.get(name)).orElseThrow(() -> new ShaderException("Register not found for: " + name));
148 | }
149 |
150 | @Override
151 | public void setMatrix(String name, float[] buf, int vector4fCount) {
152 | this.device.setVertexShaderConstantF(this.getRegister(name), buf, vector4fCount);
153 | this.validate();
154 | }
155 |
156 | @Override
157 | public Object getObject() {
158 | return this;
159 | }
160 | }
161 |
--------------------------------------------------------------------------------
/src/main/java/de/teragam/jfxshader/material/internal/d3d/Direct3DDevice9.java:
--------------------------------------------------------------------------------
1 | package de.teragam.jfxshader.material.internal.d3d;
2 |
3 | public class Direct3DDevice9 {
4 |
5 | private final long handle;
6 |
7 | // The HRESULT code of the last operation. Will be set by the native code.
8 | private int resultCode;
9 |
10 | public Direct3DDevice9(long handle) {
11 | this.handle = handle;
12 | }
13 |
14 | public long getHandle() {
15 | return this.handle;
16 | }
17 |
18 | /**
19 | * @return the HRESULT code of the last operation.
20 | */
21 | public int getResultCode() {
22 | return this.resultCode;
23 | }
24 |
25 | public native void setFVF(int fvf);
26 |
27 | public native void setVertexShader(long pShader);
28 |
29 | public native long createVertexShader(byte[] pFunction);
30 |
31 | public native void setVertexShaderConstantF(int startRegister, float[] pConstantData, int vector4fCount);
32 |
33 | public native void setVertexShaderConstantI(int startRegister, int[] pConstantData, int vector4iCount);
34 |
35 | public native void setPixelShader(long pShader);
36 |
37 | public native void setPixelShaderConstantF(int startRegister, float[] pConstantData, int vector4fCount);
38 |
39 | public native void setTexture(int stage, long pTexture);
40 |
41 | public native void setRenderState(int state, int value);
42 |
43 | public native int getRenderState(int state);
44 |
45 | public native void setRenderTarget(int renderTargetIndex, long pRenderTarget);
46 |
47 | public native long getRenderTarget(int renderTargetIndex);
48 |
49 | public native long getSurfaceLevel(long pTexture, int level);
50 |
51 | public native void setStreamSource(int streamNumber, long pStreamData, int offsetInBytes, int stride);
52 |
53 | public native void setIndices(long pIndexData);
54 |
55 | public native void drawIndexedPrimitive(int primitiveType, int baseVertexIndex, int minVertexIndex, int numVertices, int startIndex, int primCount);
56 |
57 | public native void releaseResource(long pResource);
58 |
59 | public native long createVertexBuffer(int length, int usage, int fvf, int pool, long pSharedHandle);
60 |
61 | public native long createIndexBuffer(int length, int usage, int format, int pool, long pSharedHandle);
62 |
63 | public native void uploadVertexBufferData(long pVertexBuffer, float[] vertexBuffer, int vertexBufferLength);
64 |
65 | public native void uploadIndexBufferDataInt(long pIndexBuffer, int[] indexBufferInt, int indexBufferLength);
66 |
67 | public native void uploadIndexBufferDataShort(long pIndexBuffer, short[] indexBufferShort, int indexBufferLength);
68 |
69 | }
70 |
--------------------------------------------------------------------------------
/src/main/java/de/teragam/jfxshader/material/internal/es2/ES2ShaderMaterialPeerRenderer.java:
--------------------------------------------------------------------------------
1 | package de.teragam.jfxshader.material.internal.es2;
2 |
3 | import java.util.ArrayList;
4 | import java.util.List;
5 | import java.util.Map;
6 |
7 | import javafx.scene.image.Image;
8 |
9 | import com.sun.javafx.geom.transform.GeneralTransform3D;
10 | import com.sun.prism.Graphics;
11 | import com.sun.prism.Texture;
12 | import com.sun.prism.impl.ps.BaseShaderContext;
13 |
14 | import de.teragam.jfxshader.material.internal.AbstractShaderMaterialPeerRenderer;
15 | import de.teragam.jfxshader.material.internal.ShaderMeshView;
16 | import de.teragam.jfxshader.util.Reflect;
17 |
18 | public class ES2ShaderMaterialPeerRenderer extends AbstractShaderMaterialPeerRenderer {
19 |
20 | @Override
21 | public void render(Graphics g, ShaderMeshView meshView, BaseShaderContext context, Map imageIndexMap) {
22 | if (!(meshView instanceof ES2ShaderMeshView)) {
23 | throw new IllegalArgumentException("meshView must be an instance of ES2ShaderMeshView");
24 | }
25 | final List textures = new ArrayList<>();
26 | imageIndexMap.forEach((index, image) -> {
27 | final Texture texture = this.getTexture(context, image, false);
28 | Reflect.on(BaseShaderContext.class).method("updateTexture").invoke(context, index, texture);
29 | if (texture != null) {
30 | textures.add(texture);
31 | }
32 | });
33 | final Reflect extends BaseShaderContext> contextReflect = Reflect.on(context.getClass());
34 | final Object glContext = contextReflect.getFieldValue("glContext", context);
35 | Reflect.on("com.sun.prism.es2.GLContext").method("renderMeshView").invoke(glContext, ((ES2ShaderMeshView) meshView).getNativeHandle());
36 |
37 | textures.forEach(Texture::unlock);
38 | }
39 |
40 | @Override
41 | protected float[] convertMatrix(GeneralTransform3D src) {
42 | final float[] rawMatrix = new float[16];
43 | rawMatrix[0] = (float) src.get(0); // Scale X
44 | rawMatrix[1] = (float) src.get(4); // Shear Y
45 | rawMatrix[2] = (float) src.get(8);
46 | rawMatrix[3] = (float) src.get(12);
47 | rawMatrix[4] = (float) src.get(1); // Shear X
48 | rawMatrix[5] = (float) src.get(5); // Scale Y
49 | rawMatrix[6] = (float) src.get(9);
50 | rawMatrix[7] = (float) src.get(13);
51 | rawMatrix[8] = (float) src.get(2);
52 | rawMatrix[9] = (float) src.get(6);
53 | rawMatrix[10] = (float) src.get(10);
54 | rawMatrix[11] = (float) src.get(14);
55 | rawMatrix[12] = (float) src.get(3); // Translate X
56 | rawMatrix[13] = (float) src.get(7); // Translate Y
57 | rawMatrix[14] = (float) src.get(11);
58 | rawMatrix[15] = (float) src.get(15);
59 | return rawMatrix;
60 | }
61 |
62 | }
63 |
--------------------------------------------------------------------------------
/src/main/java/de/teragam/jfxshader/material/internal/es2/ES2ShaderMeshView.java:
--------------------------------------------------------------------------------
1 | package de.teragam.jfxshader.material.internal.es2;
2 |
3 | import com.sun.prism.Material;
4 | import com.sun.prism.ResourceFactory;
5 | import com.sun.prism.impl.BaseMesh;
6 | import com.sun.prism.impl.Disposer;
7 | import com.sun.prism.impl.ps.BaseShaderContext;
8 |
9 | import de.teragam.jfxshader.exception.ShaderException;
10 | import de.teragam.jfxshader.material.internal.ShaderMeshView;
11 | import de.teragam.jfxshader.util.Reflect;
12 |
13 | public class ES2ShaderMeshView extends ShaderMeshView {
14 |
15 | private final long nativeHandle;
16 | private final BaseShaderContext es2Context;
17 |
18 | public ES2ShaderMeshView(long nativeHandle, BaseMesh mesh, Disposer.Record disposerRecord, BaseShaderContext es2Context) {
19 | super(mesh, disposerRecord);
20 | this.nativeHandle = nativeHandle;
21 | this.es2Context = es2Context;
22 | }
23 |
24 | public static ES2ShaderMeshView create(ResourceFactory rf, BaseMesh mesh) {
25 | if (!Reflect.resolveClass("com.sun.prism.es2.ES2ResourceFactory").isInstance(rf)) {
26 | throw new ShaderException("Factory is not a ES2ResourceFactory");
27 | }
28 | final BaseShaderContext es2Context = Reflect.on(rf.getClass()).getFieldValue("context", rf);
29 | final long nativeHandle = Reflect.on(es2Context.getClass()).method("createES2MeshView").invoke(es2Context, mesh);
30 | return new ES2ShaderMeshView(nativeHandle, mesh, new ES2MeshViewDisposerRecord(es2Context, nativeHandle), es2Context);
31 | }
32 |
33 | @Override
34 | public void setCullingMode(int mode) {
35 | super.setCullingMode(mode);
36 | Reflect.on(this.es2Context.getClass()).method("setCullingMode").invoke(this.es2Context, this.nativeHandle, mode);
37 | }
38 |
39 | @Override
40 | public void setWireframe(boolean wireframe) {
41 | super.setWireframe(wireframe);
42 | Reflect.on(this.es2Context.getClass()).method("setWireframe").invoke(this.es2Context, this.nativeHandle, wireframe);
43 | }
44 |
45 | @Override
46 | public void setMaterial(Material material) {
47 | super.setMaterial(material);
48 | final Reflect> contextReflect = Reflect.on(this.es2Context.getClass());
49 | final Object glContext = contextReflect.getFieldValue("glContext", this.es2Context);
50 | Reflect.on("com.sun.prism.es2.GLContext").method("setMaterial")
51 | .invoke(glContext, this.nativeHandle, ((InternalES2BasePhongMaterial) material).getNativeHandle());
52 | }
53 |
54 | public long getNativeHandle() {
55 | return this.nativeHandle;
56 | }
57 |
58 | public static class ES2MeshViewDisposerRecord implements Disposer.Record {
59 |
60 | private final BaseShaderContext context;
61 | private long nativeHandle;
62 |
63 | ES2MeshViewDisposerRecord(BaseShaderContext context, long nativeHandle) {
64 | this.context = context;
65 | this.nativeHandle = nativeHandle;
66 | }
67 |
68 | @Override
69 | public void dispose() {
70 | if (this.nativeHandle != 0L) {
71 | Reflect.on(this.context.getClass()).method("releaseES2MeshView").invoke(this.context, this.nativeHandle);
72 | this.nativeHandle = 0L;
73 | }
74 | }
75 | }
76 |
77 | }
78 |
--------------------------------------------------------------------------------
/src/main/java/de/teragam/jfxshader/material/internal/es2/InternalES2BasePhongMaterial.java:
--------------------------------------------------------------------------------
1 | package de.teragam.jfxshader.material.internal.es2;
2 |
3 | import com.sun.prism.ResourceFactory;
4 | import com.sun.prism.impl.Disposer;
5 | import com.sun.prism.impl.ps.BaseShaderContext;
6 |
7 | import de.teragam.jfxshader.exception.ShaderException;
8 | import de.teragam.jfxshader.material.ShaderMaterial;
9 | import de.teragam.jfxshader.material.internal.InternalBasePhongMaterial;
10 | import de.teragam.jfxshader.util.Reflect;
11 |
12 | public class InternalES2BasePhongMaterial extends InternalBasePhongMaterial {
13 |
14 | private final long nativeHandle;
15 |
16 | public InternalES2BasePhongMaterial(long nativeHandle, ShaderMaterial material, Disposer.Record disposerRecord) {
17 | super(material, disposerRecord);
18 | this.nativeHandle = nativeHandle;
19 | }
20 |
21 | public static InternalES2BasePhongMaterial create(ResourceFactory rf, ShaderMaterial material) {
22 | if (!Reflect.resolveClass("com.sun.prism.es2.ES2ResourceFactory").isInstance(rf)) {
23 | throw new ShaderException("Factory is not a ES2ResourceFactory");
24 | }
25 | final BaseShaderContext es2Context = Reflect.on(rf.getClass()).getFieldValue("context", rf);
26 | final long nativeHandle = Reflect.on(es2Context.getClass()).method("createES2PhongMaterial").invoke(es2Context);
27 | return new InternalES2BasePhongMaterial(nativeHandle, material, () -> {
28 | if (nativeHandle != 0L) {
29 | Reflect.on(es2Context.getClass()).method("releaseES2PhongMaterial").invoke(es2Context, nativeHandle);
30 | }
31 | });
32 | }
33 |
34 | public long getNativeHandle() {
35 | return this.nativeHandle;
36 | }
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/src/main/java/de/teragam/jfxshader/renderstate/SubResolutionRenderState.java:
--------------------------------------------------------------------------------
1 | package de.teragam.jfxshader.renderstate;
2 |
3 | import com.sun.javafx.geom.Rectangle;
4 | import com.sun.javafx.geom.transform.BaseTransform;
5 | import com.sun.scenario.effect.impl.state.RenderState;
6 |
7 | public class SubResolutionRenderState implements RenderState {
8 |
9 | private final double resolutionDividend;
10 |
11 | public SubResolutionRenderState(double resolutionDividend) {
12 | this.resolutionDividend = resolutionDividend;
13 | }
14 |
15 |
16 | @Override
17 | public EffectCoordinateSpace getEffectTransformSpace() {
18 | return EffectCoordinateSpace.CustomSpace;
19 | }
20 |
21 | @Override
22 | public BaseTransform getInputTransform(BaseTransform filterTransform) {
23 | final BaseTransform baseTransform = filterTransform.copy();
24 | baseTransform.setToIdentity();
25 | baseTransform.deriveWithScale(1 / this.resolutionDividend, 1 / this.resolutionDividend, 1);
26 | return baseTransform;
27 | }
28 |
29 | @Override
30 | public BaseTransform getResultTransform(BaseTransform filterTransform) {
31 | return filterTransform.copy().deriveWithScale(this.resolutionDividend, this.resolutionDividend, 1);
32 | }
33 |
34 | @Override
35 | public Rectangle getInputClip(int i, Rectangle filterClip) {
36 | return filterClip;
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/src/main/java/de/teragam/jfxshader/samples/effects/BlendShapes.java:
--------------------------------------------------------------------------------
1 | package de.teragam.jfxshader.samples.effects;
2 |
3 | import java.util.ArrayList;
4 | import java.util.Collections;
5 | import java.util.List;
6 |
7 | import javafx.beans.property.BooleanProperty;
8 | import javafx.beans.property.ObjectProperty;
9 | import javafx.geometry.Rectangle2D;
10 |
11 | import de.teragam.jfxshader.effect.EffectDependencies;
12 | import de.teragam.jfxshader.effect.TwoSamplerEffect;
13 |
14 | @EffectDependencies(BlendShapesEffectPeer.class)
15 | public class BlendShapes extends TwoSamplerEffect {
16 |
17 | private final List> shapes;
18 | private final BooleanProperty invertMask;
19 |
20 | public BlendShapes() {
21 | this.shapes = new ArrayList<>();
22 | this.invertMask = super.createEffectBooleanProperty(false, "invertMask");
23 | }
24 |
25 | public ObjectProperty createShapeProperty() {
26 | final ObjectProperty property = super.createEffectObjectProperty(null, "shape");
27 | this.shapes.add(property);
28 | return property;
29 | }
30 |
31 | public List> getBlendShapes() {
32 | return Collections.unmodifiableList(this.shapes);
33 | }
34 |
35 | public boolean isInvertMask() {
36 | return this.invertMaskProperty().get();
37 | }
38 |
39 | public BooleanProperty invertMaskProperty() {
40 | return this.invertMask;
41 | }
42 |
43 | public void setInvertMask(boolean invertMask) {
44 | this.invertMaskProperty().set(invertMask);
45 | }
46 |
47 | public static class Shape {
48 |
49 | private final Rectangle2D bounds;
50 | private final double width;
51 | private final double feather;
52 | private final double opacity;
53 |
54 | public Shape(Rectangle2D bounds, double width, double feather, double opacity) {
55 | this.bounds = bounds;
56 | this.width = width;
57 | this.feather = feather;
58 | this.opacity = opacity;
59 | }
60 |
61 | public Rectangle2D getBounds() {
62 | return this.bounds;
63 | }
64 |
65 | public double getWidth() {
66 | return this.width;
67 | }
68 |
69 | public double getFeather() {
70 | return this.feather;
71 | }
72 |
73 | public double getOpacity() {
74 | return this.opacity;
75 | }
76 |
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/main/java/de/teragam/jfxshader/samples/effects/BlendShapesEffectPeer.java:
--------------------------------------------------------------------------------
1 | package de.teragam.jfxshader.samples.effects;
2 |
3 | import java.nio.FloatBuffer;
4 | import java.util.HashMap;
5 | import java.util.List;
6 |
7 | import javafx.beans.property.ObjectProperty;
8 | import javafx.geometry.Rectangle2D;
9 |
10 | import com.sun.scenario.effect.impl.BufferUtil;
11 |
12 | import de.teragam.jfxshader.JFXShader;
13 | import de.teragam.jfxshader.ShaderDeclaration;
14 | import de.teragam.jfxshader.effect.EffectPeer;
15 | import de.teragam.jfxshader.effect.ShaderEffectPeer;
16 | import de.teragam.jfxshader.effect.ShaderEffectPeerConfig;
17 |
18 | @EffectPeer("BlendShapes")
19 | class BlendShapesEffectPeer extends ShaderEffectPeer {
20 |
21 | private FloatBuffer rects;
22 | private FloatBuffer ops;
23 |
24 | protected BlendShapesEffectPeer(ShaderEffectPeerConfig config) {
25 | super(config);
26 | }
27 |
28 | @Override
29 | protected ShaderDeclaration createShaderDeclaration() {
30 | final HashMap samplers = new HashMap<>();
31 | samplers.put("botImg", 0);
32 | samplers.put("topImg", 1);
33 | final HashMap params = new HashMap<>();
34 | params.put("count", 0);
35 | params.put("rects", 1);
36 | params.put("ops", 9);
37 | params.put("scale", 17);
38 | params.put("invertMask", 18);
39 | return new ShaderDeclaration(samplers, params,
40 | BlendShapes.class.getResourceAsStream("/de/teragam/jfxshader/samples/effects/blendshapes/blendshapes.frag"),
41 | BlendShapes.class.getResourceAsStream("/de/teragam/jfxshader/samples/effects/blendshapes/blendshapes.obj"));
42 | }
43 |
44 | @Override
45 | protected void updateShader(JFXShader shader, BlendShapes effect) {
46 | if (this.rects == null) {
47 | this.rects = BufferUtil.newFloatBuffer(8 * 4);
48 | this.ops = BufferUtil.newFloatBuffer(8 * 4);
49 | }
50 | this.rects.clear();
51 | this.ops.clear();
52 | final List> effectShapes = effect.getBlendShapes();
53 | int effectiveCount = 0;
54 | for (int i = 0; i < Math.min(effectShapes.size(), 8); i++) {
55 | final BlendShapes.Shape shape = effectShapes.get(i).get();
56 | if (shape != null) {
57 | effectiveCount++;
58 | final Rectangle2D bounds = shape.getBounds();
59 | this.rects.put(new float[]{(float) bounds.getMinX(), (float) bounds.getMinY(), (float) bounds.getMaxX(), (float) bounds.getMaxY()});
60 | this.ops.put(new float[]{(float) shape.getWidth(), (float) shape.getFeather(), (float) shape.getOpacity(), 0F});
61 | }
62 | }
63 | for (int i = effectiveCount; i < 8; i++) {
64 | this.rects.put(new float[]{0, 0, 0, 0});
65 | this.ops.put(new float[]{0, 0, 0, 0});
66 | }
67 | this.rects.rewind();
68 | this.ops.rewind();
69 | shader.setConstant("count", effectiveCount);
70 | shader.setConstants("rects", this.rects, 0, 8);
71 | shader.setConstants("ops", this.ops, 0, 8);
72 | final double scaleX = Math.hypot(this.getTransform().getMxx(), this.getTransform().getMyx());
73 | final double scaleY = Math.hypot(this.getTransform().getMxy(), this.getTransform().getMyy());
74 | final double scale = Math.max(scaleX, scaleY);
75 | shader.setConstant("scale", (float) scale); // Compensation for scale transforms like dpi scaling
76 | shader.setConstant("invertMask", effect.isInvertMask() ? 1 : 0);
77 | }
78 |
79 | }
80 |
--------------------------------------------------------------------------------
/src/main/java/de/teragam/jfxshader/samples/effects/Pixelate.java:
--------------------------------------------------------------------------------
1 | package de.teragam.jfxshader.samples.effects;
2 |
3 | import javafx.beans.property.DoubleProperty;
4 |
5 | import de.teragam.jfxshader.effect.EffectDependencies;
6 | import de.teragam.jfxshader.effect.OneSamplerEffect;
7 |
8 | @EffectDependencies(PixelateEffectPeer.class)
9 | public class Pixelate extends OneSamplerEffect {
10 |
11 | private final DoubleProperty offsetX;
12 | private final DoubleProperty offsetY;
13 | private final DoubleProperty pixelWidth;
14 | private final DoubleProperty pixelHeight;
15 |
16 | public Pixelate() {
17 | this(10, 10);
18 | }
19 |
20 | public Pixelate(double pixelWidth, double pixelHeight) {
21 | this.pixelWidth = super.createEffectDoubleProperty(pixelWidth, "pixelWidth");
22 | this.pixelHeight = super.createEffectDoubleProperty(pixelHeight, "pixelHeight");
23 | this.offsetX = super.createEffectDoubleProperty(0.0, "offsetX");
24 | this.offsetY = super.createEffectDoubleProperty(0.0, "offsetY");
25 | }
26 |
27 | public double getOffsetX() {
28 | return this.offsetX.get();
29 | }
30 |
31 | public DoubleProperty offsetXProperty() {
32 | return this.offsetX;
33 | }
34 |
35 | public void setOffsetX(double offsetX) {
36 | this.offsetX.set(offsetX);
37 | }
38 |
39 | public double getOffsetY() {
40 | return this.offsetY.get();
41 | }
42 |
43 | public DoubleProperty offsetYProperty() {
44 | return this.offsetY;
45 | }
46 |
47 | public void setOffsetY(double offsetY) {
48 | this.offsetY.set(offsetY);
49 | }
50 |
51 | public double getPixelWidth() {
52 | return this.pixelWidth.get();
53 | }
54 |
55 | public DoubleProperty pixelWidthProperty() {
56 | return this.pixelWidth;
57 | }
58 |
59 | public void setPixelWidth(double pixelWidth) {
60 | this.pixelWidth.set(pixelWidth);
61 | }
62 |
63 | public double getPixelHeight() {
64 | return this.pixelHeight.get();
65 | }
66 |
67 | public DoubleProperty pixelHeightProperty() {
68 | return this.pixelHeight;
69 | }
70 |
71 | public void setPixelHeight(double pixelHeight) {
72 | this.pixelHeight.set(pixelHeight);
73 | }
74 |
75 | }
76 |
--------------------------------------------------------------------------------
/src/main/java/de/teragam/jfxshader/samples/effects/PixelateEffectPeer.java:
--------------------------------------------------------------------------------
1 | package de.teragam.jfxshader.samples.effects;
2 |
3 |
4 | import java.util.Map;
5 |
6 | import de.teragam.jfxshader.JFXShader;
7 | import de.teragam.jfxshader.ShaderDeclaration;
8 | import de.teragam.jfxshader.effect.EffectPeer;
9 | import de.teragam.jfxshader.effect.ShaderEffectPeer;
10 | import de.teragam.jfxshader.effect.ShaderEffectPeerConfig;
11 |
12 | @EffectPeer("Pixelate")
13 | class PixelateEffectPeer extends ShaderEffectPeer {
14 |
15 | protected PixelateEffectPeer(ShaderEffectPeerConfig config) {
16 | super(config);
17 | }
18 |
19 | @Override
20 | protected ShaderDeclaration createShaderDeclaration() {
21 | final Map samplers = Map.of("baseImg", 0);
22 | final Map params = Map.of("pixelSize", 0, "offset", 1, "resolution", 2, "texCoords", 3, "viewport", 4);
23 | return new ShaderDeclaration(samplers, params, Pixelate.class.getResourceAsStream("/de/teragam/jfxshader/samples/effects/pixelate/pixelate.frag"),
24 | Pixelate.class.getResourceAsStream("/de/teragam/jfxshader/samples/effects/pixelate/pixelate.obj"));
25 | }
26 |
27 | @Override
28 | protected void updateShader(JFXShader shader, Pixelate effect) {
29 | shader.setConstant("pixelSize", Math.max((float) effect.getPixelWidth(), 1), Math.max((float) effect.getPixelHeight(), 1));
30 | shader.setConstant("offset", (float) effect.getOffsetX(), (float) effect.getOffsetY());
31 | shader.setConstant("resolution", (float) this.getDestNativeBounds().width, (float) this.getDestNativeBounds().height);
32 | final float[] texCoords = this.getTextureCoords(0);
33 | shader.setConstant("texCoords", texCoords[0], texCoords[1], texCoords[2], texCoords[3]);
34 | final double scaleX = Math.hypot(this.getTransform().getMxx(), this.getTransform().getMyx());
35 | final double scaleY = Math.hypot(this.getTransform().getMxy(), this.getTransform().getMyy());
36 | shader.setConstant("viewport", 0f, 0f, (float) scaleX, (float) scaleY);
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/src/main/java/de/teragam/jfxshader/samples/effects/ProxyShaderEffect.java:
--------------------------------------------------------------------------------
1 | package de.teragam.jfxshader.samples.effects;
2 |
3 | import java.util.Objects;
4 | import java.util.concurrent.atomic.AtomicBoolean;
5 | import java.util.concurrent.atomic.AtomicReference;
6 | import java.util.function.BiConsumer;
7 | import java.util.function.Supplier;
8 |
9 | import javafx.application.Platform;
10 |
11 | import de.teragam.jfxshader.JFXShader;
12 | import de.teragam.jfxshader.ShaderDeclaration;
13 | import de.teragam.jfxshader.effect.EffectDependencies;
14 | import de.teragam.jfxshader.effect.EffectPeer;
15 | import de.teragam.jfxshader.effect.ShaderEffectPeer;
16 | import de.teragam.jfxshader.effect.ShaderEffectPeerConfig;
17 | import de.teragam.jfxshader.effect.TwoSamplerEffect;
18 |
19 | @EffectDependencies(ProxyShaderEffect.ProxyShaderEffectPeer.class)
20 | public class ProxyShaderEffect extends TwoSamplerEffect {
21 |
22 | private final AtomicReference> declarationSupplier;
23 | private final AtomicBoolean invalidateDeclaration;
24 | private final AtomicReference>> shaderConsumer;
25 |
26 | public ProxyShaderEffect() {
27 | this(null);
28 | }
29 |
30 | public ProxyShaderEffect(Supplier declarationSupplier) {
31 | this.declarationSupplier = new AtomicReference<>(declarationSupplier);
32 | this.invalidateDeclaration = new AtomicBoolean();
33 | this.shaderConsumer = new AtomicReference<>();
34 | }
35 |
36 | public void setShaderDeclarationSupplier(Supplier declarationSupplier) {
37 | this.declarationSupplier.set(declarationSupplier);
38 | }
39 |
40 | public void invalidateShaderDeclaration() {
41 | this.invalidateDeclaration.set(true);
42 | Platform.runLater(this::markDirty);
43 | }
44 |
45 | public void setShaderConsumer(BiConsumer> shaderConsumer) {
46 | this.shaderConsumer.set(shaderConsumer);
47 | }
48 |
49 | @Override
50 | public void setContinuousRendering(boolean continuousRendering) {
51 | super.setContinuousRendering(continuousRendering);
52 | }
53 |
54 | @EffectPeer(value = "ProxyShaderEffect", singleton = false)
55 | protected class ProxyShaderEffectPeer extends ShaderEffectPeer {
56 |
57 | protected ProxyShaderEffectPeer(ShaderEffectPeerConfig config) {
58 | super(config);
59 | }
60 |
61 | @Override
62 | protected ShaderDeclaration createShaderDeclaration() {
63 | return Objects.requireNonNull(ProxyShaderEffect.this.declarationSupplier.get(), "The ShaderDeclaration supplier cannot be null").get();
64 | }
65 |
66 | @Override
67 | protected void updateShader(JFXShader shader, ProxyShaderEffect effect) {
68 | if (ProxyShaderEffect.this.invalidateDeclaration.getAndSet(false)) {
69 | this.invalidateShader();
70 | }
71 | final BiConsumer> consumer = ProxyShaderEffect.this.shaderConsumer.get();
72 | if (consumer != null) {
73 | consumer.accept(shader, this);
74 | }
75 | }
76 | }
77 |
78 | }
79 |
--------------------------------------------------------------------------------
/src/main/java/de/teragam/jfxshader/samples/effects/ZoomRadialBlur.java:
--------------------------------------------------------------------------------
1 | package de.teragam.jfxshader.samples.effects;
2 |
3 | import javafx.beans.property.DoubleProperty;
4 | import javafx.beans.property.IntegerProperty;
5 |
6 | import de.teragam.jfxshader.effect.EffectDependencies;
7 | import de.teragam.jfxshader.effect.OneSamplerEffect;
8 |
9 | @EffectDependencies(ZoomRadialBlurEffectPeer.class)
10 | public class ZoomRadialBlur extends OneSamplerEffect {
11 |
12 | private final DoubleProperty strength;
13 | private final IntegerProperty blurSteps;
14 | private final DoubleProperty centerX;
15 | private final DoubleProperty centerY;
16 |
17 | public ZoomRadialBlur() {
18 | this(100.0);
19 | }
20 |
21 | public ZoomRadialBlur(double strength) {
22 | this(strength, 16);
23 | }
24 |
25 | public ZoomRadialBlur(double strength, int blurSteps) {
26 | this.strength = super.createEffectDoubleProperty(strength, "strength");
27 | this.blurSteps = super.createEffectIntegerProperty(blurSteps, "blurSteps");
28 | this.centerX = super.createEffectDoubleProperty(0.0, "centerX");
29 | this.centerY = super.createEffectDoubleProperty(0.0, "centerY");
30 | }
31 |
32 | public double getStrength() {
33 | return this.strength.get();
34 | }
35 |
36 | public DoubleProperty strengthProperty() {
37 | return this.strength;
38 | }
39 |
40 | public void setStrength(double strength) {
41 | this.strength.set(strength);
42 | }
43 |
44 | public int getBlurSteps() {
45 | return this.blurSteps.get();
46 | }
47 |
48 | public IntegerProperty blurStepsProperty() {
49 | return this.blurSteps;
50 | }
51 |
52 | public void setBlurSteps(int blurSteps) {
53 | this.blurSteps.set(blurSteps);
54 | }
55 |
56 | public double getCenterX() {
57 | return this.centerX.get();
58 | }
59 |
60 | public DoubleProperty centerXProperty() {
61 | return this.centerX;
62 | }
63 |
64 | public void setCenterX(double centerX) {
65 | this.centerX.set(centerX);
66 | }
67 |
68 | public double getCenterY() {
69 | return this.centerY.get();
70 | }
71 |
72 | public DoubleProperty centerYProperty() {
73 | return this.centerY;
74 | }
75 |
76 | public void setCenterY(double centerY) {
77 | this.centerY.set(centerY);
78 | }
79 |
80 | }
81 |
--------------------------------------------------------------------------------
/src/main/java/de/teragam/jfxshader/samples/effects/ZoomRadialBlurEffectPeer.java:
--------------------------------------------------------------------------------
1 | package de.teragam.jfxshader.samples.effects;
2 |
3 | import java.util.Map;
4 |
5 | import de.teragam.jfxshader.JFXShader;
6 | import de.teragam.jfxshader.ShaderDeclaration;
7 | import de.teragam.jfxshader.effect.EffectPeer;
8 | import de.teragam.jfxshader.effect.ShaderEffectPeer;
9 | import de.teragam.jfxshader.effect.ShaderEffectPeerConfig;
10 |
11 | @EffectPeer(value = "ZoomRadialBlur")
12 | class ZoomRadialBlurEffectPeer extends ShaderEffectPeer {
13 |
14 | protected ZoomRadialBlurEffectPeer(ShaderEffectPeerConfig config) {
15 | super(config);
16 | }
17 |
18 | @Override
19 | protected ShaderDeclaration createShaderDeclaration() {
20 | final Map samplers = Map.of("baseImg", 0);
21 | final Map params = Map.of("resolution", 0, "center", 1, "viewport", 2, "texCoords", 3, "blurSteps", 4, "strength", 5);
22 | return new ShaderDeclaration(samplers, params,
23 | ZoomRadialBlurEffectPeer.class.getResourceAsStream("/de/teragam/jfxshader/samples/effects/zoomradialblur/zoomradialblur.frag"),
24 | ZoomRadialBlurEffectPeer.class.getResourceAsStream("/de/teragam/jfxshader/samples/effects/zoomradialblur/zoomradialblur.obj"));
25 | }
26 |
27 | @Override
28 | protected void updateShader(JFXShader shader, ZoomRadialBlur effect) {
29 | shader.setConstant("resolution", (float) this.getDestBounds().width, (float) this.getDestBounds().height);
30 | shader.setConstant("center", (float) effect.getCenterX(), (float) effect.getCenterY());
31 | final double scaleX = Math.hypot(this.getTransform().getMxx(), this.getTransform().getMyx());
32 | final double scaleY = Math.hypot(this.getTransform().getMxy(), this.getTransform().getMyy());
33 | shader.setConstant("viewport", 0f, 0f, (float) scaleX, (float) scaleY);
34 | final float[] texCoords = this.getTextureCoords(0);
35 | shader.setConstant("texCoords", texCoords[0], texCoords[1], texCoords[2], texCoords[3]);
36 | shader.setConstant("blurSteps", Math.max(Math.min(effect.getBlurSteps(), 64), 1));
37 | shader.setConstant("strength", (float) effect.getStrength());
38 | }
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/src/main/java/de/teragam/jfxshader/samples/materials/FresnelMaterial.java:
--------------------------------------------------------------------------------
1 | package de.teragam.jfxshader.samples.materials;
2 |
3 | import javafx.beans.property.DoubleProperty;
4 | import javafx.beans.property.ObjectProperty;
5 | import javafx.scene.image.Image;
6 | import javafx.scene.paint.Color;
7 |
8 | import de.teragam.jfxshader.material.MaterialDependency;
9 | import de.teragam.jfxshader.material.ShaderMaterial;
10 |
11 | @MaterialDependency(FresnelMaterialPeer.class)
12 | public class FresnelMaterial extends ShaderMaterial {
13 |
14 | private final DoubleProperty power;
15 | private final ObjectProperty glowColor;
16 | private final ObjectProperty diffuseImage;
17 |
18 | public FresnelMaterial() {
19 | this(2.0);
20 | }
21 |
22 | public FresnelMaterial(double power) {
23 | this.power = super.createMaterialDoubleProperty(power, "power");
24 | this.glowColor = super.createMaterialObjectProperty(Color.WHITE, "diffuseColor");
25 | this.diffuseImage = super.createMaterialImageProperty(null, "diffuseImage");
26 | }
27 |
28 | public double getPower() {
29 | return this.powerProperty().get();
30 | }
31 |
32 | public DoubleProperty powerProperty() {
33 | return this.power;
34 | }
35 |
36 | public void setPower(double power) {
37 | this.powerProperty().set(power);
38 | }
39 |
40 | public Image getDiffuseImage() {
41 | return this.diffuseImageProperty().get();
42 | }
43 |
44 | public ObjectProperty diffuseImageProperty() {
45 | return this.diffuseImage;
46 | }
47 |
48 | public void setDiffuseImage(Image diffuseImage) {
49 | this.diffuseImageProperty().set(diffuseImage);
50 | }
51 |
52 | public Color getGlowColor() {
53 | return this.glowColorProperty().get();
54 | }
55 |
56 | public ObjectProperty glowColorProperty() {
57 | return this.glowColor;
58 | }
59 |
60 | public void setGlowColor(Color glowColor) {
61 | this.glowColorProperty().set(glowColor);
62 | }
63 |
64 | }
65 |
--------------------------------------------------------------------------------
/src/main/java/de/teragam/jfxshader/samples/materials/FresnelMaterialPeer.java:
--------------------------------------------------------------------------------
1 | package de.teragam.jfxshader.samples.materials;
2 |
3 | import java.util.HashMap;
4 | import java.util.Map;
5 |
6 | import com.sun.javafx.geom.Vec3d;
7 |
8 | import de.teragam.jfxshader.JFXShader;
9 | import de.teragam.jfxshader.ShaderDeclaration;
10 | import de.teragam.jfxshader.material.ShaderMaterialPeer;
11 |
12 | public class FresnelMaterialPeer extends ShaderMaterialPeer {
13 |
14 | @Override
15 | public ShaderDeclaration createPixelShaderDeclaration() {
16 | final Map samplers = new HashMap<>();
17 | samplers.put("diffuseImage", 0);
18 | final Map params = new HashMap<>();
19 | params.put("color", 0);
20 | params.put("strength", 1);
21 | return new ShaderDeclaration(samplers, params, FresnelMaterialPeer.class.getResourceAsStream(
22 | "/de/teragam/jfxshader/samples/materials/fresnel/fresnel.frag"),
23 | FresnelMaterialPeer.class.getResourceAsStream("/de/teragam/jfxshader/samples/materials/fresnel/fresnel.ps.obj"));
24 | }
25 |
26 | @Override
27 | public ShaderDeclaration createVertexShaderDeclaration() {
28 | final Map params = new HashMap<>();
29 | params.put("viewProjectionMatrix", 0);
30 | params.put("camPos", 4);
31 | params.put("worldMatrix", 35);
32 | return new ShaderDeclaration(null, params, ShaderMaterialPeer.class.getResourceAsStream("/de/teragam/jfxshader/samples/materials/fresnel/fresnel.vert"),
33 | ShaderMaterialPeer.class.getResourceAsStream("/de/teragam/jfxshader/samples/materials/fresnel/fresnel.vs.obj"));
34 | }
35 |
36 | @Override
37 | public void updateShader(JFXShader vertexShader, JFXShader pixelShader, FresnelMaterial material) {
38 | final Vec3d camPos = this.getCamera().getPositionInWorld(null);
39 | vertexShader.setConstant("camPos", (float) camPos.x, (float) camPos.y, (float) camPos.z);
40 | vertexShader.setMatrix("viewProjectionMatrix", this.getViewProjectionMatrix(), 4);
41 | vertexShader.setMatrix("worldMatrix", this.getWorldMatrix(), 4);
42 |
43 | this.setSamplerImage(material.getDiffuseImage(), 0);
44 |
45 | pixelShader.setConstant("strength", (float) material.getPower());
46 | pixelShader.setConstant("color", (float) material.getGlowColor().getRed(), (float) material.getGlowColor().getGreen(),
47 | (float) material.getGlowColor().getBlue(), (float) material.getGlowColor().getOpacity());
48 | }
49 |
50 | }
51 |
--------------------------------------------------------------------------------
/src/main/java/de/teragam/jfxshader/util/ConstructorInvocationWrapper.java:
--------------------------------------------------------------------------------
1 | package de.teragam.jfxshader.util;
2 |
3 | @FunctionalInterface
4 | public interface ConstructorInvocationWrapper {
5 | T create(Object... args);
6 | }
7 |
--------------------------------------------------------------------------------
/src/main/java/de/teragam/jfxshader/util/MethodInvocationWrapper.java:
--------------------------------------------------------------------------------
1 | package de.teragam.jfxshader.util;
2 |
3 | @FunctionalInterface
4 | public interface MethodInvocationWrapper {
5 | T invoke(Object instance, Object... args);
6 | }
7 |
--------------------------------------------------------------------------------
/src/main/java/de/teragam/jfxshader/util/Reflect.java:
--------------------------------------------------------------------------------
1 | package de.teragam.jfxshader.util;
2 |
3 | import java.lang.reflect.Constructor;
4 | import java.lang.reflect.Field;
5 | import java.lang.reflect.InvocationHandler;
6 | import java.lang.reflect.Method;
7 | import java.lang.reflect.Proxy;
8 | import java.util.ArrayList;
9 | import java.util.Arrays;
10 | import java.util.List;
11 | import java.util.Map;
12 | import java.util.Objects;
13 | import java.util.Optional;
14 | import java.util.concurrent.ConcurrentHashMap;
15 | import java.util.function.UnaryOperator;
16 |
17 | import javafx.util.Pair;
18 |
19 | import de.teragam.jfxshader.exception.ShaderException;
20 |
21 | @SuppressWarnings("unchecked")
22 | public class Reflect {
23 |
24 | private final Class clazz;
25 |
26 | private Reflect(Class clazz) {
27 | this.clazz = clazz;
28 | }
29 |
30 | public static Reflect on(Class clazz) {
31 | return new Reflect<>(clazz);
32 | }
33 |
34 | public static Reflect on(String clazzName) {
35 | return (Reflect) on(resolveClass(clazzName));
36 | }
37 |
38 | private static final Map, String>, Field> CLASS_FIELD_CONCURRENT_HASH_MAP = new ConcurrentHashMap<>();
39 | private static final Map CLASS_METHOD_CONCURRENT_HASH_MAP = new ConcurrentHashMap<>();
40 |
41 | public Field getField(String fieldName) {
42 | return CLASS_FIELD_CONCURRENT_HASH_MAP.computeIfAbsent(new Pair<>(this.clazz, fieldName), c -> {
43 | try {
44 | final Field field = this.clazz.getDeclaredField(fieldName);
45 | field.trySetAccessible();
46 | return field;
47 | } catch (NoSuchFieldException e) {
48 | throw new ShaderException(String.format("Could not get declared field %s of class %s", fieldName, this.clazz.getName()), e);
49 | }
50 | });
51 | }
52 |
53 | public Method getMethod(String methodName, Class>... parameterTypes) {
54 | return CLASS_METHOD_CONCURRENT_HASH_MAP.computeIfAbsent(new ReflectMethodCache(this.clazz, methodName, parameterTypes), c -> {
55 | try {
56 | final Method method = this.clazz.getDeclaredMethod(methodName, parameterTypes);
57 | method.trySetAccessible();
58 | return method;
59 | } catch (NoSuchMethodException e) {
60 | final Optional methodOpt = Arrays.stream(this.clazz.getDeclaredMethods()).filter(m -> m.getName().equals(methodName)).findFirst();
61 | final Method method = methodOpt.orElseThrow(
62 | () -> new ShaderException(String.format("Could not get declared method %s of class %s", methodName, this.clazz.getName()), e));
63 | method.trySetAccessible();
64 | return method;
65 | }
66 | });
67 | }
68 |
69 | public T getFieldValue(String fieldName, Object instance) {
70 | try {
71 | return (T) this.getField(fieldName).get(instance);
72 | } catch (ReflectiveOperationException e) {
73 | throw new ShaderException(String.format("Could not access field %s of class %s", fieldName, this.clazz.getName()), e);
74 | }
75 | }
76 |
77 | public void setFieldValue(String fieldName, Object instance, Object value) {
78 | try {
79 | this.getField(fieldName).set(instance, value);
80 | } catch (ReflectiveOperationException e) {
81 | throw new ShaderException(String.format("Could not access field %s of class %s", fieldName, this.clazz.getName()), e);
82 | }
83 | }
84 |
85 | public void processFieldValue(String fieldName, Object instance, UnaryOperator processor) {
86 | try {
87 | final Field field = this.getField(fieldName);
88 | field.set(instance, processor.apply((T) field.get(instance)));
89 | } catch (ReflectiveOperationException e) {
90 | throw new ShaderException(String.format("Could not access field %s of class %s", fieldName, this.clazz.getName()), e);
91 | }
92 | }
93 |
94 | public MethodInvocationWrapper method(String methodName, Class>... parameterTypes) {
95 | return (instance, args) -> {
96 | try {
97 | return (T) this.getMethod(methodName, parameterTypes).invoke(instance, args);
98 | } catch (ReflectiveOperationException e) {
99 | throw new ShaderException(String.format("Could not invoke method %s of class %s", methodName, this.clazz.getName()), e);
100 | }
101 | };
102 | }
103 |
104 | public static Class> resolveClass(String className) {
105 | try {
106 | return Class.forName(className);
107 | } catch (ClassNotFoundException e) {
108 | throw new ShaderException(String.format("Could not resolve class %s", className), e);
109 | }
110 | }
111 |
112 | public C allocateInstance() {
113 | final Reflect> unsafeReflect = Reflect.on("sun.misc.Unsafe");
114 | final Object unsafe = unsafeReflect.getFieldValue("theUnsafe", null);
115 | return ((MethodInvocationWrapper) unsafeReflect.method("allocateInstance", Class.class)).invoke(unsafe, this.clazz);
116 | }
117 |
118 | public boolean hasConstructor(Class>... parameterTypes) {
119 | return Arrays.stream(this.clazz.getDeclaredConstructors()).anyMatch(c -> Arrays.equals(c.getParameterTypes(), parameterTypes));
120 | }
121 |
122 | public Constructor getConstructor(Class>... parameterTypes) {
123 | try {
124 | final Constructor constructor = this.clazz.getDeclaredConstructor(parameterTypes);
125 | constructor.trySetAccessible();
126 | return constructor;
127 | } catch (NoSuchMethodException e) {
128 | throw new ShaderException(String.format("Could not get constructor of class %s", this.clazz.getName()), e);
129 | }
130 | }
131 |
132 | public ConstructorInvocationWrapper constructor(Class>... parameterTypes) {
133 | return args -> {
134 | try {
135 | if (parameterTypes.length == 0 && args.length != 0) {
136 | final Class>[] runtimeParameterTypes = Arrays.stream(args)
137 | .map(obj -> Objects.requireNonNull(obj, "Argument cannot be null"))
138 | .map(Object::getClass)
139 | .map(Reflect::convertToPrimitiveClass).toArray(Class>[]::new);
140 | return this.getConstructor(runtimeParameterTypes).newInstance(args);
141 | }
142 | return this.getConstructor(parameterTypes).newInstance(args);
143 | } catch (ReflectiveOperationException e) {
144 | throw new ShaderException(String.format("Could not create instance of class %s", this.clazz.getName()), e);
145 | }
146 | };
147 | }
148 |
149 | public static void addOpens(String fullyQualifiedPackageName, String module, Module currentModule) {
150 | addOpensOrExports(fullyQualifiedPackageName, module, currentModule, true);
151 | }
152 |
153 | public static void addExports(String fullyQualifiedPackageName, String module, Module currentModule) {
154 | addOpensOrExports(fullyQualifiedPackageName, module, currentModule, false);
155 | }
156 |
157 | private static void addOpensOrExports(String fullyQualifiedPackageName, String module, Module currentModule, boolean open) {
158 | try {
159 | final Optional moduleOpt = ModuleLayer.boot().findModule(module);
160 | if (moduleOpt.isEmpty()) {
161 | throw new IllegalStateException("Could not find module " + module);
162 | }
163 | final Method addOpensMethodImpl = Reflect.on(Module.class).getMethod(open ? "implAddOpens" : "implAddExports", String.class, Module.class);
164 | class OffsetProvider {
165 | int first;
166 | }
167 | final Object unsafe = Reflect.on("sun.misc.Unsafe").getFieldValue("theUnsafe", null);
168 | final long firstFieldOffset = (long) Reflect.on(unsafe.getClass()).method("objectFieldOffset", Field.class)
169 | .invoke(unsafe, OffsetProvider.class.getDeclaredField("first"));
170 | Reflect.on(unsafe.getClass()).method("putBooleanVolatile", Object.class, long.class, boolean.class)
171 | .invoke(unsafe, addOpensMethodImpl, firstFieldOffset, true);
172 | addOpensMethodImpl.invoke(moduleOpt.get(), fullyQualifiedPackageName, currentModule);
173 | } catch (ReflectiveOperationException ex) {
174 | throw new ShaderException("Could not add opens or exports", ex);
175 | }
176 | }
177 |
178 | private static Class> convertToPrimitiveClass(Class> clazz) {
179 | if (clazz == Integer.class) {
180 | return int.class;
181 | } else if (clazz == Long.class) {
182 | return long.class;
183 | } else if (clazz == Float.class) {
184 | return float.class;
185 | } else if (clazz == Double.class) {
186 | return double.class;
187 | } else if (clazz == Boolean.class) {
188 | return boolean.class;
189 | } else if (clazz == Byte.class) {
190 | return byte.class;
191 | } else if (clazz == Short.class) {
192 | return short.class;
193 | } else if (clazz == Character.class) {
194 | return char.class;
195 | }
196 | return clazz;
197 | }
198 |
199 | public static P createProxy(Object object, Class> methodBaseClass, Class
proxyInterface) {
200 | final Reflect> reflect = Reflect.on(methodBaseClass);
201 | return createProxy(object, proxyInterface, (proxy, method, args) -> reflect.method(method.getName(), method.getParameterTypes()).invoke(object, args));
202 | }
203 |
204 | public static
P createProxy(Object object, Class
proxyInterface, InvocationHandler invocationHandler) {
205 | final List> interfaces = new ArrayList<>(Arrays.asList(proxyInterface.getInterfaces()));
206 | if (proxyInterface.isInterface()) {
207 | interfaces.add(proxyInterface);
208 | }
209 | return (P) Proxy.newProxyInstance(proxyInterface.getClassLoader(), interfaces.toArray(new Class>[0]), (proxy, method, args) -> {
210 | if ("getObject".equals(method.getName())) {
211 | return object;
212 | } else {
213 | return invocationHandler.invoke(proxy, method, args);
214 | }
215 | });
216 | }
217 |
218 | }
219 |
--------------------------------------------------------------------------------
/src/main/java/de/teragam/jfxshader/util/ReflectMethodCache.java:
--------------------------------------------------------------------------------
1 | package de.teragam.jfxshader.util;
2 |
3 | import java.util.Arrays;
4 | import java.util.Objects;
5 |
6 | final class ReflectMethodCache {
7 | private final Class> clazz;
8 | private final String methodName;
9 | private final Class>[] parameterTypes;
10 |
11 | public ReflectMethodCache(Class> clazz, String methodName, Class>[] parameterTypes) {
12 | this.clazz = clazz;
13 | this.methodName = methodName;
14 | this.parameterTypes = parameterTypes;
15 | }
16 |
17 | @Override
18 | public boolean equals(Object o) {
19 | if (this == o) {
20 | return true;
21 | }
22 | if (o == null || this.getClass() != o.getClass()) {
23 | return false;
24 | }
25 |
26 | final ReflectMethodCache that = (ReflectMethodCache) o;
27 | if (!Objects.equals(this.clazz, that.clazz) || !Objects.equals(this.methodName, that.methodName)) {
28 | return false;
29 | }
30 | // Probably incorrect - comparing Object[] arrays with Arrays.equals
31 | return Arrays.equals(this.parameterTypes, that.parameterTypes);
32 | }
33 |
34 | @Override
35 | public int hashCode() {
36 | int result = this.clazz != null ? this.clazz.hashCode() : 0;
37 | result = 31 * result + (this.methodName != null ? this.methodName.hashCode() : 0);
38 | result = 31 * result + Arrays.hashCode(this.parameterTypes);
39 | return result;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/main/java/de/teragam/jfxshader/util/ReflectProxy.java:
--------------------------------------------------------------------------------
1 | package de.teragam.jfxshader.util;
2 |
3 | public interface ReflectProxy {
4 |
5 | /**
6 | * @return the object that is proxied by this proxy
7 | */
8 | Object getObject();
9 |
10 | }
11 |
--------------------------------------------------------------------------------
/src/main/java/module-info.java:
--------------------------------------------------------------------------------
1 | module de.teragam.jfxshader {
2 | requires javafx.base;
3 | requires javafx.graphics;
4 |
5 | exports de.teragam.jfxshader;
6 | exports de.teragam.jfxshader.effect;
7 | exports de.teragam.jfxshader.material;
8 | exports de.teragam.jfxshader.exception;
9 | exports de.teragam.jfxshader.renderstate;
10 | exports de.teragam.jfxshader.samples.effects;
11 | exports de.teragam.jfxshader.samples.materials;
12 | }
13 |
--------------------------------------------------------------------------------
/src/main/native-d3d/IDirect3DDevice9Wrapper.cpp:
--------------------------------------------------------------------------------
1 | #include "de_teragam_jfxshader_material_internal_d3d_Direct3DDevice9.h"
2 | #include
3 |
4 | jfieldID deviceField = NULL;
5 | jfieldID resultCodeField = NULL;
6 |
7 | IDirect3DDevice9* getDevice(JNIEnv *env, jobject obj) {
8 | if (deviceField == NULL) {
9 | deviceField = env->GetFieldID(env->GetObjectClass(obj), "handle", "J");
10 | }
11 | return (IDirect3DDevice9*) env->GetLongField(obj, deviceField);
12 | }
13 |
14 | void setResultCode(JNIEnv *env, jobject obj, HRESULT resultCode) {
15 | if (resultCodeField == NULL) {
16 | resultCodeField = env->GetFieldID(env->GetObjectClass(obj), "resultCode", "I");
17 | }
18 | env->SetIntField(obj, resultCodeField, resultCode);
19 | }
20 |
21 | JNIEXPORT void JNICALL Java_de_teragam_jfxshader_material_internal_d3d_Direct3DDevice9_setFVF(JNIEnv *env, jobject obj, jint fvf) {
22 | setResultCode(env, obj, getDevice(env, obj)->SetFVF(fvf));
23 | }
24 |
25 | JNIEXPORT void JNICALL Java_de_teragam_jfxshader_material_internal_d3d_Direct3DDevice9_setVertexShader(JNIEnv *env, jobject obj, jlong pShader) {
26 | setResultCode(env, obj, getDevice(env, obj)->SetVertexShader((IDirect3DVertexShader9*) pShader));
27 | }
28 |
29 | JNIEXPORT jlong JNICALL Java_de_teragam_jfxshader_material_internal_d3d_Direct3DDevice9_createVertexShader(JNIEnv *env, jobject obj, jbyteArray pFunction) {
30 | IDirect3DVertexShader9* pShader = NULL;
31 | jbyte* pFunctionBytes = env->GetByteArrayElements(pFunction, NULL);
32 | setResultCode(env, obj, getDevice(env, obj)->CreateVertexShader((DWORD*) pFunctionBytes, &pShader));
33 | env->ReleaseByteArrayElements(pFunction, pFunctionBytes, JNI_ABORT);
34 | return (jlong) pShader;
35 | }
36 |
37 | JNIEXPORT void JNICALL Java_de_teragam_jfxshader_material_internal_d3d_Direct3DDevice9_setVertexShaderConstantF(JNIEnv *env, jobject obj, jint startRegister, jfloatArray pConstantData, jint vector4fCount) {
38 | jfloat* pConstantDataFloats = env->GetFloatArrayElements(pConstantData, NULL);
39 | setResultCode(env, obj, getDevice(env, obj)->SetVertexShaderConstantF(startRegister, (float*) pConstantDataFloats, vector4fCount));
40 | env->ReleaseFloatArrayElements(pConstantData, pConstantDataFloats, JNI_ABORT);
41 | }
42 |
43 | JNIEXPORT void JNICALL Java_de_teragam_jfxshader_material_internal_d3d_Direct3DDevice9_setVertexShaderConstantI(JNIEnv *env, jobject obj, jint startRegister, jintArray pConstantData, jint vector4iCount) {
44 | jint* pConstantDataInts = env->GetIntArrayElements(pConstantData, NULL);
45 | setResultCode(env, obj, getDevice(env, obj)->SetVertexShaderConstantI(startRegister, (int*) pConstantDataInts, vector4iCount));
46 | env->ReleaseIntArrayElements(pConstantData, pConstantDataInts, JNI_ABORT);
47 | }
48 |
49 | JNIEXPORT void JNICALL Java_de_teragam_jfxshader_material_internal_d3d_Direct3DDevice9_setPixelShader(JNIEnv *env, jobject obj, jlong pShader) {
50 | setResultCode(env, obj, getDevice(env, obj)->SetPixelShader((IDirect3DPixelShader9*) pShader));
51 | }
52 |
53 | JNIEXPORT void JNICALL Java_de_teragam_jfxshader_material_internal_d3d_Direct3DDevice9_setPixelShaderConstantF(JNIEnv *env, jobject obj, jint startRegister, jfloatArray pConstantData, jint vector4fCount) {
54 | jfloat* pConstantDataFloats = env->GetFloatArrayElements(pConstantData, NULL);
55 | setResultCode(env, obj, getDevice(env, obj)->SetPixelShaderConstantF(startRegister, (float*) pConstantDataFloats, vector4fCount));
56 | env->ReleaseFloatArrayElements(pConstantData, pConstantDataFloats, JNI_ABORT);
57 | }
58 |
59 | JNIEXPORT void JNICALL Java_de_teragam_jfxshader_material_internal_d3d_Direct3DDevice9_setTexture(JNIEnv *env, jobject obj, jint stage, jlong pTexture) {
60 | setResultCode(env, obj, getDevice(env, obj)->SetTexture(stage, (IDirect3DBaseTexture9*) pTexture));
61 | }
62 |
63 | JNIEXPORT void JNICALL Java_de_teragam_jfxshader_material_internal_d3d_Direct3DDevice9_setRenderState(JNIEnv *env, jobject obj, jint state, jint value) {
64 | setResultCode(env, obj, getDevice(env, obj)->SetRenderState((D3DRENDERSTATETYPE) state, value));
65 | }
66 |
67 | JNIEXPORT jint JNICALL Java_de_teragam_jfxshader_material_internal_d3d_Direct3DDevice9_getRenderState(JNIEnv *env, jobject obj, jint state) {
68 | DWORD value = 0;
69 | setResultCode(env, obj, getDevice(env, obj)->GetRenderState((D3DRENDERSTATETYPE) state, &value));
70 | return value;
71 | }
72 |
73 | JNIEXPORT void JNICALL Java_de_teragam_jfxshader_material_internal_d3d_Direct3DDevice9_setRenderTarget(JNIEnv *env, jobject obj, jint renderTargetIndex, jlong pRenderTarget) {
74 | setResultCode(env, obj, getDevice(env, obj)->SetRenderTarget(renderTargetIndex, (IDirect3DSurface9*) pRenderTarget));
75 | }
76 |
77 | JNIEXPORT jlong JNICALL Java_de_teragam_jfxshader_material_internal_d3d_Direct3DDevice9_getRenderTarget(JNIEnv *env, jobject obj, jint renderTargetIndex) {
78 | IDirect3DSurface9* pRenderTarget = NULL;
79 | setResultCode(env, obj, getDevice(env, obj)->GetRenderTarget(renderTargetIndex, &pRenderTarget));
80 | return (jlong) pRenderTarget;
81 | }
82 |
83 | JNIEXPORT jlong JNICALL Java_de_teragam_jfxshader_material_internal_d3d_Direct3DDevice9_getSurfaceLevel(JNIEnv *env, jobject obj, jlong pTexture, jint level) {
84 | IDirect3DSurface9* pSurface = NULL;
85 | setResultCode(env, obj, ((IDirect3DTexture9*) pTexture)->GetSurfaceLevel(level, &pSurface));
86 | return (jlong) pSurface;
87 | }
88 |
89 | JNIEXPORT void JNICALL Java_de_teragam_jfxshader_material_internal_d3d_Direct3DDevice9_setStreamSource(JNIEnv *env, jobject obj, jint streamNumber, jlong pStreamData, jint offsetInBytes, jint stride) {
90 | setResultCode(env, obj, getDevice(env, obj)->SetStreamSource(streamNumber, (IDirect3DVertexBuffer9*) pStreamData, offsetInBytes, stride));
91 | }
92 |
93 | JNIEXPORT void JNICALL Java_de_teragam_jfxshader_material_internal_d3d_Direct3DDevice9_setIndices(JNIEnv *env, jobject obj, jlong pIndexData) {
94 | setResultCode(env, obj, getDevice(env, obj)->SetIndices((IDirect3DIndexBuffer9*) pIndexData));
95 | }
96 |
97 | JNIEXPORT void JNICALL Java_de_teragam_jfxshader_material_internal_d3d_Direct3DDevice9_drawIndexedPrimitive(JNIEnv *env, jobject obj, jint primitiveType, jint baseVertexIndex, jint minVertexIndex, jint numVertices, jint startIndex, jint primCount) {
98 | setResultCode(env, obj, getDevice(env, obj)->DrawIndexedPrimitive((D3DPRIMITIVETYPE) primitiveType, baseVertexIndex, minVertexIndex, numVertices, startIndex, primCount));
99 | }
100 |
101 | JNIEXPORT void JNICALL Java_de_teragam_jfxshader_material_internal_d3d_Direct3DDevice9_releaseResource(JNIEnv *env, jobject obj, jlong pResource) {
102 | if (pResource != 0) {
103 | setResultCode(env, obj, ((IUnknown*) pResource)->Release());
104 | }
105 | }
106 |
107 | JNIEXPORT jlong JNICALL Java_de_teragam_jfxshader_material_internal_d3d_Direct3DDevice9_createVertexBuffer(JNIEnv *env, jobject obj, jint length, jint usage, jint fvf, jint pool, jlong pSharedHandle) {
108 | IDirect3DVertexBuffer9* pVertexBuffer = nullptr;
109 | setResultCode(env, obj, getDevice(env, obj)->CreateVertexBuffer(length, usage, fvf, (D3DPOOL) pool, &pVertexBuffer, nullptr));
110 | return (jlong) pVertexBuffer;
111 | }
112 |
113 | JNIEXPORT jlong JNICALL Java_de_teragam_jfxshader_material_internal_d3d_Direct3DDevice9_createIndexBuffer(JNIEnv *env, jobject obj, jint length, jint usage, jint format, jint pool, jlong pSharedHandle) {
114 | IDirect3DIndexBuffer9* pIndexBuffer = NULL;
115 | setResultCode(env, obj, getDevice(env, obj)->CreateIndexBuffer(length, usage, (D3DFORMAT) format, (D3DPOOL) pool, &pIndexBuffer, NULL));
116 | return (jlong) pIndexBuffer;
117 | }
118 |
119 | JNIEXPORT void JNICALL Java_de_teragam_jfxshader_material_internal_d3d_Direct3DDevice9_uploadVertexBufferData(JNIEnv *env, jobject obj, jlong pVertexBuffer, jfloatArray pVertexData, jint vertexBufferLength) {
120 | float* vertexBuffer = (float*) (env->GetPrimitiveArrayCritical(pVertexData, 0));
121 | void* pLockedVertexBuffer = NULL;
122 | setResultCode(env, obj, ((IDirect3DVertexBuffer9*) pVertexBuffer)->Lock(0, vertexBufferLength, &pLockedVertexBuffer, 0));
123 | memcpy(pLockedVertexBuffer, vertexBuffer, vertexBufferLength);
124 | ((IDirect3DVertexBuffer9*) pVertexBuffer)->Unlock();
125 | env->ReleasePrimitiveArrayCritical(pVertexData, vertexBuffer, 0);
126 | }
127 |
128 | JNIEXPORT void JNICALL Java_de_teragam_jfxshader_material_internal_d3d_Direct3DDevice9_uploadIndexBufferDataInt(JNIEnv *env, jobject obj, jlong pIndexBuffer, jintArray pIndexData, jint indexBufferLength) {
129 | int* indexBuffer = (int*) (env->GetPrimitiveArrayCritical(pIndexData, 0));
130 | void* pLockedIndexBuffer = NULL;
131 | setResultCode(env, obj, ((IDirect3DIndexBuffer9*) pIndexBuffer)->Lock(0, indexBufferLength, &pLockedIndexBuffer, 0));
132 | memcpy(pLockedIndexBuffer, indexBuffer, indexBufferLength);
133 | ((IDirect3DIndexBuffer9*) pIndexBuffer)->Unlock();
134 | env->ReleasePrimitiveArrayCritical(pIndexData, indexBuffer, 0);
135 | }
136 |
137 | JNIEXPORT void JNICALL Java_de_teragam_jfxshader_material_internal_d3d_Direct3DDevice9_uploadIndexBufferDataShort(JNIEnv *env, jobject obj, jlong pIndexBuffer, jshortArray pIndexData, jint indexBufferLength) {
138 | short* indexBuffer = (short*) (env->GetPrimitiveArrayCritical(pIndexData, 0));
139 | void* pLockedIndexBuffer = NULL;
140 | setResultCode(env, obj, ((IDirect3DIndexBuffer9*) pIndexBuffer)->Lock(0, indexBufferLength, &pLockedIndexBuffer, 0));
141 | memcpy(pLockedIndexBuffer, indexBuffer, indexBufferLength);
142 | ((IDirect3DIndexBuffer9*) pIndexBuffer)->Unlock();
143 | env->ReleasePrimitiveArrayCritical(pIndexData, indexBuffer, 0);
144 | }
145 |
--------------------------------------------------------------------------------
/src/main/resources/de/teragam/jfxshader/samples/effects/blendshapes/blendshapes.frag:
--------------------------------------------------------------------------------
1 | #ifdef GL_ES
2 | #extension GL_OES_standard_derivatives: enable
3 | #ifdef GL_FRAGMENT_PRECISION_HIGH
4 | precision highp float;
5 | precision highp int;
6 | #else
7 | precision mediump float;
8 | precision mediump int;
9 | #endif
10 | #else
11 | #define highp
12 | #define mediump
13 | #define lowp
14 | #endif
15 | varying vec2 texCoord0;
16 | varying vec2 texCoord1;
17 | uniform vec4 jsl_pixCoordOffset;
18 | uniform sampler2D botImg;
19 | uniform sampler2D topImg;
20 | uniform int count;
21 | uniform vec4 rects[8];
22 | uniform vec4 ops[8];
23 | uniform float scale;
24 | uniform int invertMask;
25 |
26 | float map(float value, float min1, float max1, float min2, float max2) {
27 | return min2 + (value - min1) * (max2 - min2) / (max1 - min1);
28 | }
29 |
30 | float insideRect(vec2 p, vec4 rect, vec4 ops) {
31 | float dx = max(rect.x - p.x, max(p.x - rect.z, 0.0));
32 | float dy = max(rect.y - p.y, max(p.y - rect.w, 0.0));
33 | float d = sqrt(dx * dx + dy * dy);
34 | if (ops.x == 0.0) {
35 | return 1.0 - clamp(ceil(d), 0.0, 1.0);
36 | } else {
37 | return map(clamp(d / ops.x, 0.0, 1.0), ops.y, 1.0, 1.0, 0.0);
38 | }
39 | }
40 |
41 | void main() {
42 | vec2 pixcoord = vec2(gl_FragCoord.x - jsl_pixCoordOffset.x, ((jsl_pixCoordOffset.z - gl_FragCoord.y) * jsl_pixCoordOffset.w) - jsl_pixCoordOffset.y);
43 |
44 | vec4 bot = texture2D(botImg, texCoord0);
45 | vec4 top = texture2D(topImg, texCoord1);
46 | float factor = 0.0;
47 | for (int i = 0; i < count; i++) {
48 | // The scale is used to compensate for dpi scaling
49 | factor += insideRect(pixcoord / scale, rects[i], ops[i]) * ops[i].z;
50 | }
51 | factor = clamp(factor, 0.0, 1.0);
52 | if (invertMask == 1) {
53 | factor = 1.0 - factor;
54 | }
55 | gl_FragColor = bot * factor + top * (1.0 - factor);
56 | }
57 |
--------------------------------------------------------------------------------
/src/main/resources/de/teragam/jfxshader/samples/effects/blendshapes/blendshapes.hlsl:
--------------------------------------------------------------------------------
1 | sampler2D botImg : register(s0);
2 | sampler2D topImg : register(s1);
3 | int count : register(c0);
4 | float4 rects[8] : register(c1);
5 | float4 ops[8] : register(c9);
6 | float scale : register(c17);
7 | int invertMask : register(c18);
8 |
9 | float map(float value, float min1, float max1, float min2, float max2) {
10 | return min2 + (value - min1) * (max2 - min2) / (max1 - min1);
11 | }
12 |
13 | float insideRect(float2 p, float4 rect, float4 ops) {
14 | float dx = max(rect.x - p.x, max(p.x - rect.z, 0.0));
15 | float dy = max(rect.y - p.y, max(p.y - rect.w, 0.0));
16 | float d = sqrt(dx * dx + dy * dy);
17 | if (ops.x == 0.0) {
18 | return 1.0 - clamp(ceil(d), 0.0, 1.0);
19 | } else {
20 | return map(clamp(d / ops.x, 0.0, 1.0), ops.y, 1.0, 1.0, 0.0);
21 | }
22 | }
23 |
24 | void main(in float2 pos0 : TEXCOORD0, in float2 pos1 : TEXCOORD1, in float2 pixcoord : VPOS, in float4 jsl_vertexColor : COLOR0, out float4 color : COLOR0) {
25 | float4 bot = tex2D(botImg, pos0);
26 | float4 top = tex2D(topImg, pos1);
27 | float factor = 0.0;
28 | for (int i = 0; i < 8; i++){
29 | if (i >= count) {
30 | break;
31 | }
32 | // The scale is used to compensate for dpi scaling
33 | factor += insideRect(pixcoord / scale, rects[i], ops[i]) * ops[i].z;
34 | }
35 | factor = clamp(factor, 0.0, 1.0);
36 | if (invertMask == 1) {
37 | factor = 1.0 - factor;
38 | }
39 | color = bot * factor + top * (1.0 - factor);
40 | }
41 |
--------------------------------------------------------------------------------
/src/main/resources/de/teragam/jfxshader/samples/effects/blendshapes/blendshapes.obj:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Teragam/JFXShader/cfe8752990f17fbcd14f68525f95d2ee74110668/src/main/resources/de/teragam/jfxshader/samples/effects/blendshapes/blendshapes.obj
--------------------------------------------------------------------------------
/src/main/resources/de/teragam/jfxshader/samples/effects/pixelate/pixelate.frag:
--------------------------------------------------------------------------------
1 | varying vec2 texCoord0;
2 |
3 | uniform sampler2D baseImg;
4 | uniform vec2 pixelSize;
5 | uniform vec2 offset;
6 | uniform vec2 resolution;
7 | uniform vec4 texCoords;
8 | uniform vec4 viewport;
9 |
10 | void main() {
11 | vec2 scaledPixelSize = (pixelSize / resolution) * viewport.zw;
12 | vec2 scaledOffset = (offset / resolution) * viewport.zw;
13 | vec2 pixelCoord = floor((texCoord0 + scaledOffset) / scaledPixelSize) * scaledPixelSize;
14 | vec2 clampedTexCoord = clamp(pixelCoord + scaledPixelSize * 0.5 + texCoords.xy, 1.0 / resolution, texCoords.zw - 1.0 / resolution);
15 | gl_FragColor = texture2D(baseImg, clampedTexCoord);
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/resources/de/teragam/jfxshader/samples/effects/pixelate/pixelate.hlsl:
--------------------------------------------------------------------------------
1 | sampler2D baseImg : register(s0);
2 |
3 | float2 pixelSize : register(c0);
4 | float2 offset : register(c1);
5 | float2 resolution : register(c2);
6 | float4 texCoords : register(c3);
7 | float4 viewport : register(c4);
8 |
9 | void main(float2 texCoord : TEXCOORD0, out float4 color : SV_Target) {
10 | float2 scaledPixelSize = (pixelSize / resolution) * viewport.zw;
11 | float2 scaledOffset = (offset / resolution) * viewport.zw;
12 | float2 pixelCoord = floor((texCoord + scaledOffset) / scaledPixelSize) * scaledPixelSize;
13 | color = tex2D(baseImg, clamp(pixelCoord + scaledPixelSize * 0.5 + texCoords.xy, 1.0 / resolution, texCoords.zw - 1.0 / resolution));
14 | }
15 |
--------------------------------------------------------------------------------
/src/main/resources/de/teragam/jfxshader/samples/effects/pixelate/pixelate.obj:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Teragam/JFXShader/cfe8752990f17fbcd14f68525f95d2ee74110668/src/main/resources/de/teragam/jfxshader/samples/effects/pixelate/pixelate.obj
--------------------------------------------------------------------------------
/src/main/resources/de/teragam/jfxshader/samples/effects/zoomradialblur/zoomradialblur.frag:
--------------------------------------------------------------------------------
1 | varying vec2 texCoord0;
2 |
3 | uniform sampler2D baseImg;
4 | uniform vec2 resolution;
5 | uniform vec2 center;
6 | uniform vec4 viewport;
7 | uniform vec4 texCoords;
8 | uniform int blurSteps;
9 | uniform float strength;
10 |
11 | void main() {
12 | vec2 scaledCenter = ((center / resolution + texCoords.xy) * texCoords.zw) * viewport.zw;
13 | vec2 focus = texCoord0 - scaledCenter;
14 | float calcStrength = strength * (1.0 / max(resolution.x, resolution.y)) * viewport.z;
15 |
16 | vec4 outColor = vec4(0.0, 0.0, 0.0, 0.0);
17 |
18 | for (int i = 0; i < blurSteps; i++) {
19 | float power = 1.0 - calcStrength * float(i) / float(blurSteps);
20 | vec2 texCoord = focus * power + scaledCenter;
21 | if (texCoord.x < texCoords.x || texCoord.y < texCoords.y || texCoord.x > texCoords.z || texCoord.y > texCoords.w) {
22 | break;
23 | }
24 | outColor += texture2D(baseImg, texCoord);
25 | }
26 |
27 | outColor *= 1.0 / float(blurSteps);
28 | gl_FragColor = outColor;
29 | }
30 |
--------------------------------------------------------------------------------
/src/main/resources/de/teragam/jfxshader/samples/effects/zoomradialblur/zoomradialblur.hlsl:
--------------------------------------------------------------------------------
1 | sampler2D baseImg : register(s0);
2 |
3 | float2 resolution : register(c0);
4 | float2 center : register(c1);
5 | float4 viewport : register(c2);
6 | float4 texCoords : register(c3);
7 | int blurSteps : register(c4);
8 | float strength : register(c5);
9 |
10 | void main(float2 pos0 : TEXCOORD0, float2 pos1 : TEXCOORD1, float2 pixcoord : VPOS, float4 jsl_vertexColor : COLOR0, out float4 color : SV_Target) {
11 |
12 | float2 scaledCenter = ((center / resolution + texCoords.xy) * texCoords.zw) * viewport.zw;
13 | float2 focus = pos0 - scaledCenter;
14 | float calcStrength = strength * (1.0 / max(resolution.x, resolution.y)) * viewport.z;
15 |
16 | float4 outColor = float4(0.0, 0.0, 0.0, 0.0);
17 |
18 | [unroll]
19 | for (int i = 0; i < 64; i++) {
20 | if (i >= blurSteps) {
21 | break;
22 | }
23 | float power = 1.0 - calcStrength * float(i) / float(blurSteps);
24 | float2 texCoord = focus * power + scaledCenter;
25 | if (texCoord.x < texCoords.x || texCoord.y < texCoords.y || texCoord.x > texCoords.z || texCoord.y > texCoords.w) {
26 | break;
27 | }
28 | outColor += tex2D(baseImg, texCoord);
29 | }
30 |
31 | outColor *= 1.0 / float(blurSteps);
32 | color = outColor;
33 | }
34 |
--------------------------------------------------------------------------------
/src/main/resources/de/teragam/jfxshader/samples/effects/zoomradialblur/zoomradialblur.obj:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Teragam/JFXShader/cfe8752990f17fbcd14f68525f95d2ee74110668/src/main/resources/de/teragam/jfxshader/samples/effects/zoomradialblur/zoomradialblur.obj
--------------------------------------------------------------------------------
/src/main/resources/de/teragam/jfxshader/samples/materials/fresnel/fresnel.frag:
--------------------------------------------------------------------------------
1 | varying vec2 oTexCoords;
2 | varying vec3 eyePos;
3 |
4 | uniform sampler2D diffuseImage;
5 | uniform vec4 color;
6 | uniform float strength;
7 |
8 | void main() {
9 | vec3 n = vec3(0, 0, -1);
10 | float d = 1.0 - clamp(dot(n, normalize(eyePos)), 0.0, 1.0);
11 | d = pow(d, strength);
12 | vec4 tex = texture2D(diffuseImage, oTexCoords);
13 | gl_FragColor = tex * (1.0 - d) + color * d;
14 | }
15 |
--------------------------------------------------------------------------------
/src/main/resources/de/teragam/jfxshader/samples/materials/fresnel/fresnel.ps.hlsl:
--------------------------------------------------------------------------------
1 | sampler diffuseImage : register(s0);
2 | float4 color : register(c0);
3 | float strength : register(c1);
4 |
5 | struct PixelInput {
6 | float4 pos : SV_POSITION;
7 | float2 oTexCoords : TEXCOORD0;
8 | float3 eyePos : TEXCOORD1;
9 | };
10 |
11 | float4 main(PixelInput input) : SV_Target {
12 | float3 n = float3(0, 0, -1);
13 | float d = 1.0 - clamp(dot(n, normalize(input.eyePos)), 0.0, 1.0);
14 | d = pow(d, strength);
15 | float4 tex = tex2D(diffuseImage, input.oTexCoords);
16 | return tex * (1.0 - d) + color * d;
17 | }
18 |
--------------------------------------------------------------------------------
/src/main/resources/de/teragam/jfxshader/samples/materials/fresnel/fresnel.ps.obj:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Teragam/JFXShader/cfe8752990f17fbcd14f68525f95d2ee74110668/src/main/resources/de/teragam/jfxshader/samples/materials/fresnel/fresnel.ps.obj
--------------------------------------------------------------------------------
/src/main/resources/de/teragam/jfxshader/samples/materials/fresnel/fresnel.vert:
--------------------------------------------------------------------------------
1 | uniform mat4 viewProjectionMatrix;
2 | uniform mat4 worldMatrix;
3 | uniform vec3 camPos;
4 | uniform vec3 ambientColor;
5 |
6 | attribute vec3 pos;
7 | attribute vec2 texCoords;
8 | attribute vec4 tangent;
9 |
10 | varying vec2 oTexCoords;
11 | varying vec3 eyePos;
12 |
13 | void main() {
14 | vec3 t1 = tangent.xyz * tangent.yzx;
15 | t1 *= 2.0;
16 | vec3 t2 = tangent.zxy * tangent.www;
17 | t2 *= 2.0;
18 | vec3 t3 = tangent.xyz * tangent.xyz;
19 | t3 *= 2.0;
20 | vec3 t4 = 1.0-(t3+t3.yzx);
21 |
22 | vec3 r1 = t1 + t2;
23 | vec3 r2 = t1 - t2;
24 |
25 | mat3 sWorldMatrix = mat3(worldMatrix[0].xyz, worldMatrix[1].xyz, worldMatrix[2].xyz);
26 | eyePos = sWorldMatrix * vec3(t4.y, r1.x, r2.z);
27 |
28 | mat4 mvpMatrix = viewProjectionMatrix * worldMatrix;
29 |
30 | oTexCoords = texCoords;
31 | gl_Position = mvpMatrix * vec4(pos,1.0);
32 | }
33 |
--------------------------------------------------------------------------------
/src/main/resources/de/teragam/jfxshader/samples/materials/fresnel/fresnel.vs.hlsl:
--------------------------------------------------------------------------------
1 | float4x4 viewProjectionMatrix : register(c0);
2 | float4 camPos : register(c4);
3 | float4x3 worldMatrix : register(c35);
4 |
5 | struct VertexInput {
6 | float4 pos : POSITION;
7 | float2 texCoords : TEXCOORD0;
8 | float4 modelVertexNormal : TEXCOORD1;
9 | };
10 |
11 | struct VertexOutput {
12 | float4 pos : SV_POSITION;
13 | float2 oTexCoords : TEXCOORD0;
14 | float3 eyePos : TEXCOORD1;
15 | };
16 |
17 | void quatToMatrix(float4 q, out float3 N[3]) {
18 | float3 t1 = q.xyz * q.yzx * 2;
19 | float3 t2 = q.zxy * q.www * 2;
20 | float3 t3 = q.xyz * q.xyz * 2;
21 | float3 t4 = 1 - (t3 + t3.yzx);
22 |
23 | float3 r1 = t1 + t2;
24 | float3 r2 = t1 - t2;
25 |
26 | N[0] = float3(t4.y, r1.x, r2.z);
27 | N[1] = float3(r2.x, t4.z, r1.y);
28 | N[2] = float3(r1.z, r2.y, t4.x);
29 | N[2] *= (q.w >= 0) ? 1 : -1;
30 | }
31 |
32 | VertexOutput main(VertexInput input) {
33 | VertexOutput output;
34 |
35 | float3 n[3];
36 | quatToMatrix(input.modelVertexNormal, n);
37 | for (int i = 0; i != 3; ++i) {
38 | n[i] = mul(n[i], (float3x3) worldMatrix);
39 | }
40 | output.eyePos = n[0];
41 |
42 | float3 worldVertexPos = mul(input.pos, worldMatrix);
43 |
44 | output.oTexCoords = input.texCoords;
45 | output.pos = mul(float4(worldVertexPos, 1.0), viewProjectionMatrix);
46 |
47 | return output;
48 | }
49 |
--------------------------------------------------------------------------------
/src/main/resources/de/teragam/jfxshader/samples/materials/fresnel/fresnel.vs.obj:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Teragam/JFXShader/cfe8752990f17fbcd14f68525f95d2ee74110668/src/main/resources/de/teragam/jfxshader/samples/materials/fresnel/fresnel.vs.obj
--------------------------------------------------------------------------------