diff --git a/jme3-core/src/main/java/com/jme3/material/Material.java b/jme3-core/src/main/java/com/jme3/material/Material.java index 86e8ecca5e..50807db935 100644 --- a/jme3-core/src/main/java/com/jme3/material/Material.java +++ b/jme3-core/src/main/java/com/jme3/material/Material.java @@ -50,6 +50,7 @@ import com.jme3.shader.bufferobject.BufferObject; import com.jme3.texture.Image; import com.jme3.texture.Texture; +import com.jme3.texture.TextureImage; import com.jme3.texture.image.ColorSpace; import com.jme3.util.ListMap; import com.jme3.util.SafeArrayList; @@ -451,7 +452,7 @@ public MatParamTexture getTextureParam(String name) { } return null; } - + /** * Returns a collection of all parameters set on this material. * @@ -514,6 +515,10 @@ public void setParam(String name, VarType type, Object value) { if (technique != null) { technique.notifyParamChanged(name, type, value); } + if (type.isImageType()) { + // recompute sort id + sortingId = -1; + } } } @@ -859,9 +864,13 @@ private void updateShaderMaterialParameter(Renderer renderer, VarType type, Shad Uniform uniform = shader.getUniform(param.getPrefixedName()); if (!override && uniform.isSetByCurrentMaterial()) return; - if (type.isTextureType()) { + if (type.isTextureType() || type.isImageType()) { try { - renderer.setTexture(unit.textureUnit, (Texture) param.getValue()); + if (type.isTextureType()) { + renderer.setTexture(unit.textureUnit, (Texture) param.getValue()); + } else { + renderer.setTextureImage(unit.textureUnit, (TextureImage) param.getValue()); + } } catch (TextureUnitException exception) { int numTexParams = unit.textureUnit + 1; String message = "Too many texture parameters (" + numTexParams + ") assigned\n to " + toString(); diff --git a/jme3-core/src/main/java/com/jme3/renderer/Renderer.java b/jme3-core/src/main/java/com/jme3/renderer/Renderer.java index c92de923ae..acd0d599e1 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/Renderer.java +++ b/jme3-core/src/main/java/com/jme3/renderer/Renderer.java @@ -42,6 +42,7 @@ import com.jme3.texture.FrameBuffer; import com.jme3.texture.Image; import com.jme3.texture.Texture; +import com.jme3.texture.TextureImage; import com.jme3.util.NativeObject; import java.nio.ByteBuffer; import java.util.EnumMap; @@ -273,6 +274,15 @@ public interface Renderer { */ public void setTexture(int unit, Texture tex) throws TextureUnitException; + + /** + * Assigns a TextureImage to the specified texture unit. + * + * @param unit the index of the texture unit (≥0) + * @param tex the texture image to assign + * @throws TextureUnitException if the texture unit does not exist + */ + public void setTextureImage(int unit, TextureImage tex) throws TextureUnitException; /** * Modifies the given Texture with the given Image. diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/GL2.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/GL2.java index 89fea8c88c..21be3e5c39 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/opengl/GL2.java +++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/GL2.java @@ -74,6 +74,10 @@ public interface GL2 extends GL { public static final int GL_TEXTURE_WRAP_R = 0x8072; public static final int GL_VERTEX_PROGRAM_POINT_SIZE = 0x8642; public static final int GL_UNSIGNED_INT_8_8_8_8 = 0x8035; + + public static final int GL_READ_ONLY = 35000; + public static final int GL_WRITE_ONLY = 35001; + public static final int GL_READ_WRITE = 35002; /** *
Reference Page - This function is deprecated and unavailable in the Core profile
diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/GL4.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/GL4.java index 04ad52b2dd..17c0bc4f1c 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/opengl/GL4.java +++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/GL4.java @@ -100,4 +100,19 @@ public interface GL4 extends GL3 { * @param storageBlockBinding The index storage block binding to associate with the specified storage block. */ public void glShaderStorageBlockBinding(int program, int storageBlockIndex, int storageBlockBinding); + + /** + * Binds a single level of a texture to an image unit for the purpose of reading + * and writing it from shaders. + * + * @param unit image unit to bind to + * @param texture texture to bind to the image unit + * @param level level of the texture to bind + * @param layered true to bind all array elements + * @param layer if not layered, the layer to bind + * @param access access types that may be performed + * @param format format to use when performing formatted stores + */ + public void glBindImageTexture(int unit, int texture, int level, boolean layered, int layer, int access, int format); + } diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java index 60e6b30265..811bab583c 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java +++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java @@ -61,6 +61,7 @@ import com.jme3.texture.Texture2D; import com.jme3.texture.Texture.ShadowCompareMode; import com.jme3.texture.Texture.WrapAxis; +import com.jme3.texture.TextureImage; import com.jme3.texture.image.LastTextureState; import com.jme3.util.BufferUtils; import com.jme3.util.ListMap; @@ -2752,7 +2753,20 @@ public void setTexture(int unit, Texture tex) throws TextureUnitException { if (tex.getName() != null) glext.glObjectLabel(GL.GL_TEXTURE, tex.getImage().getId(), tex.getName()); } } - + + @Override + public void setTextureImage(int unit, TextureImage tex) throws TextureUnitException { + if (unit < 0 || unit >= RenderContext.maxTextureUnits) { + throw new TextureUnitException(); + } + WeakReference+ * Shader accesses that violate the hint may result in undefined behavior. + */ + public enum Access { + + /** + * The image can only be read from in a shader. + */ + ReadOnly(true, false, GL2.GL_READ_ONLY), + + /** + * The image can only be written to in a shader. + */ + WriteOnly(false, true, GL2.GL_WRITE_ONLY), + + /** + * The image can both be written to and read from in a shader. + */ + ReadWrite(true, true, GL2.GL_READ_WRITE); + + private final boolean read, write; + private final int glEnum; + + private Access(boolean read, boolean write, int glEnum) { + this.read = read; + this.write = write; + this.glEnum = glEnum; + } + + /** + * If true, the image can be read from in a shader. + * + * @return + */ + public boolean isRead() { + return read; + } + + /** + * If true, the image can be written to in a shader. + * + * @return + */ + public boolean isWrite() { + return write; + } + + /** + * Corresponding OpenGL enum. + * + * @return + */ + public int getGlEnum() { + return glEnum; + } + + } + + private Texture texture; + private int level, layer; + private Access access; + private boolean updateFlag = true; + + public TextureImage(Texture texture) { + this(texture, 0, -1, Access.ReadWrite); + } + + public TextureImage(Texture texture, Access access) { + this(texture, 0, -1, access); + } + + public TextureImage(Texture texture, int level, int layer) { + this(texture, level, layer, Access.ReadWrite); + } + + public TextureImage(Texture texture, int level, int layer, Access access) { + this.texture = Objects.requireNonNull(texture, "Underlying texture cannot be null"); + this.level = level; + this.layer = layer; + this.access = access; + if (this.level < 0) { + throw new IllegalArgumentException("Level cannot be less than zero."); + } + } + + /** + * Binds this texture image to the texture unit. + *
+ * Calling this is not completely sufficient for totally binding an image + * to an image unit. Additionally, the image must be bound beforehand using + * {@link GL2#glBindTexture(int, int)}. + * + * @param gl4 GL4 implementation (not null) + * @param texUtil utility used to convert JME's image format to the corresponding GL enum (not null) + * @param unit texture unit to bind to + */ + public void bindImage(GL4 gl4, TextureUtil texUtil, int unit) { + Image img = texture.getImage(); + gl4.glBindImageTexture(unit, img.getId(), level, isLayered(), Math.max(layer, 0), + access.getGlEnum(), texUtil.getImageFormat(img.getFormat(), false).internalFormat); + } + + /** + * Sets the update flag indicating this texture image needs rebinding. + */ + public void setUpdateNeeded() { + updateFlag = true; + } + + /** + * Clears the update flag and returns the update flag's value before + * it was cleared. + * + * @return + */ + public boolean clearUpdateNeeded() { + boolean f = updateFlag; + updateFlag = false; + return f; + } + + /** + * Sets the underlying texture wrapped by this TextureImage. + * + * @param texture wrapped texture (not null) + */ + public void setTexture(Texture texture) { + Objects.requireNonNull(texture, "Wrapped texture cannot be null."); + if (this.texture != texture) { + this.texture = texture; + updateFlag = true; + } + } + + /** + * Sets the image level to bind. + *
+ * The level controls which mipmap level will be bound to the texture unit, + * where zero corresponds to the base level of the texture. + *
+ * default=0 + * + * @param level level to bind (not negative) + */ + public void setLevel(int level) { + if (level < 0) { + throw new IllegalArgumentException("Texture image level cannot be negative."); + } + if (this.level != level) { + this.level = level; + updateFlag = true; + } + } + + /** + * Sets the image layer to bind. + *
+ * If the underlying texture is a one/two/three demensional array, + * cube map, cube map array, or two demensional multisample array, + * then this value specifies which layer of the array to bind. + *
+ * default=-1 + * + * @param layer layer to bind, or negative to bind all layers + */ + public void setLayer(int layer) { + if (this.layer != layer && (this.layer >= 0 || layer >= 0)) { + this.layer = layer; + updateFlag = true; + } + } + + /** + * Sets the shader access hint with which to bind the image. + * + * @param access + */ + public void setAccess(Access access) { + if (this.access != access) { + this.access = access; + updateFlag = true; + } + } + + /** + * Gets the underlying texture wrapped by this TextureImage. + * + * @return underlying texture + */ + public Texture getTexture() { + return texture; + } + + /** + * Gets the image belonging to the underlying texture. + * + * @return + */ + public Image getImage() { + return texture.getImage(); + } + + /** + * Gets the format of the image belonging to the underlying texture. + * + * @return + */ + public Image.Format getFormat() { + return texture.getImage().getFormat(); + } + + /** + * Gets the native id of the image belonging to the underlying texture. + * + * @return + */ + public int getImageId() { + return texture.getImage().getId(); + } + + /** + * Gets the level. + * + * @return + * @see #setLevel(int) + */ + public int getLevel() { + return level; + } + + /** + * Gets the layer. + * + * @return + * @see #setLayer(int) + */ + public int getLayer() { + return layer; + } + + /** + * Gets the access hint. + * + * @return + * @see #setAccess(com.jme3.texture.TextureImage.Access) + */ + public Access getAccess() { + return access; + } + + /** + * Returns true if all layers of an image will be bound, when + * {@code layer} is negative. + * + * @return + * @see #setLayer(int) + */ + public boolean isLayered() { + return layer < 0; + } + + /** + * Returns true if the update flag has been set indicating rebinding + * is required. + * + * @return + */ + public boolean isUpdateNeeded() { + return updateFlag; + } + +} diff --git a/jme3-examples/src/main/java/jme3test/texture/TestShaderImage.java b/jme3-examples/src/main/java/jme3test/texture/TestShaderImage.java new file mode 100644 index 0000000000..52f698c99d --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/texture/TestShaderImage.java @@ -0,0 +1,59 @@ +/* + * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license + * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template + */ +package jme3test.texture; + +import com.jme3.app.SimpleApplication; +import com.jme3.material.Material; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.shape.Box; +import com.jme3.scene.shape.Quad; +import com.jme3.shader.VarType; +import com.jme3.texture.Image; +import com.jme3.texture.Texture2D; +import com.jme3.texture.TextureImage; + +/** + * + * @author codex + */ +public class TestShaderImage extends SimpleApplication { + + private int frame = 0; + + public static void main(String[] args) { + new TestShaderImage().start(); + } + + @Override + public void simpleInitApp() { + + Geometry box = new Geometry("Box", new Box(1, 1, 1)); + Material mat = new Material(assetManager, "Materials/ImageTest.j3md"); + box.setMaterial(mat); + rootNode.attachChild(box); + + int width = context.getFramebufferWidth(); + int height = context.getFramebufferHeight(); + Texture2D target = new Texture2D(width, height, Image.Format.RGBA8); + TextureImage targetImage = new TextureImage(target, TextureImage.Access.WriteOnly); + mat.setParam("TargetImage", VarType.Image2D, targetImage); + + Geometry pic = new Geometry("gui_pic", new Quad(200, 200)); + pic.setLocalTranslation(0, height - 200, 0); + Material picMat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + picMat.setTexture("ColorMap", target); + pic.setMaterial(mat); + guiNode.attachChild(pic); + + } + @Override + public void simpleUpdate(float tpf) { + if (frame++ < 5) { + cam.lookAt(Vector3f.ZERO, Vector3f.UNIT_Y); + } + } + +} diff --git a/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglGL.java b/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglGL.java index 2dcf289584..97a806c3e3 100644 --- a/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglGL.java +++ b/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglGL.java @@ -59,7 +59,14 @@ public void glBindBuffer(int param1, int param2) { public void glBindTexture(int param1, int param2) { GL11.glBindTexture(param1, param2); } - + + @Override + public void glBindImageTexture(final int unit, final int texture, final int level, + final boolean layered, final int layer, + final int access, final int format) { + GL42.glBindImageTexture(unit, texture, level, layered, layer, access, format); + } + @Override public void glBlendEquationSeparate(int colorMode, int alphaMode){ GL20.glBlendEquationSeparate(colorMode,alphaMode); diff --git a/jme3-lwjgl3/src/main/java/com/jme3/renderer/lwjgl/LwjglGL.java b/jme3-lwjgl3/src/main/java/com/jme3/renderer/lwjgl/LwjglGL.java index 6c070c83da..d2c607d747 100644 --- a/jme3-lwjgl3/src/main/java/com/jme3/renderer/lwjgl/LwjglGL.java +++ b/jme3-lwjgl3/src/main/java/com/jme3/renderer/lwjgl/LwjglGL.java @@ -80,7 +80,14 @@ public void glBindBuffer(final int target, final int buffer) { public void glBindTexture(final int target, final int texture) { GL11.glBindTexture(target, texture); } - + + @Override + public void glBindImageTexture(final int unit, final int texture, final int level, + final boolean layered, final int layer, + final int access, final int format) { + GL42.glBindImageTexture(unit, texture, level, layered, layer, access, format); + } + @Override public void glBlendEquationSeparate(final int colorMode, final int alphaMode) { GL20.glBlendEquationSeparate(colorMode, alphaMode); @@ -653,4 +660,5 @@ public void glBindBufferBase(final int target, final int index, final int buffer public void glUniformBlockBinding(final int program, final int uniformBlockIndex, final int uniformBlockBinding) { GL31.glUniformBlockBinding(program, uniformBlockIndex, uniformBlockBinding); } + } diff --git a/jme3-testdata/src/main/resources/Materials/ImageTest.frag b/jme3-testdata/src/main/resources/Materials/ImageTest.frag new file mode 100644 index 0000000000..5e2339f06a --- /dev/null +++ b/jme3-testdata/src/main/resources/Materials/ImageTest.frag @@ -0,0 +1,11 @@ + +#import "Common/ShaderLib/GLSLCompat.glsllib" + +layout(RGBA8) uniform image2D m_TargetImage; + +void main() { + + gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); + imageStore(m_TargetImage, ivec2(gl_FragCoord.xy), vec4(0.0, 1.0, 0.0, 1.0)); + +} diff --git a/jme3-testdata/src/main/resources/Materials/ImageTest.j3md b/jme3-testdata/src/main/resources/Materials/ImageTest.j3md new file mode 100644 index 0000000000..0e11e66319 --- /dev/null +++ b/jme3-testdata/src/main/resources/Materials/ImageTest.j3md @@ -0,0 +1,20 @@ +MaterialDef ImageTest { + + MaterialParameters { + + Image2D TargetImage + + } + + Technique { + + VertexShader GLSL400 GLSL320 GLSL150 : Common/MatDefs/Misc/Unshaded.vert + FragmentShader GLSL400 GLSL320 GLSL150 : Materials/ImageTest.frag + + WorldParameters { + WorldViewProjectionMatrix + } + + } + +}