Posted by
rndmbt on
Nov 23, 2023; 12:45pm
URL: https://forum.jogamp.org/Shader-Uniform-Sampler2D-Update-in-TextureUnitState-not-applied-tp4043151.html
Hello Jogamp Java3D Community,
I am currently trying to implement a "Render To Texture" pass in immediate mode rendering, so I can decide on custom masks, do PostFX and in general have control over multiple shader passes. Right now I am at the stage, where I can use GraphicsContext3D.readRaster(Raster) after GraphicsContext3D.draw(Shape3D) to get an ImageComponent2D of the masks I need from a first shader pass. I can then create a Java3D Texture2D and set the TextureUnitState's Texture of the second pass shader. My problem at this point is tat if I just do Texture2D.setImage(Raster.getImage()) to a already existing Texture2D object and then update the TextureUnitState of the second pass shader with .getTextureUnitState(0).setTexture(Texture2D) (doing this each frame), the Sampler2D uniform in the shader does not seem to update. I can get the Sampler2D uniform to update by creating a new Texture2D object before setting the image. But then another problem arises which is a memory leak.
By debugging I found that the texture Image is indeed updated (in the case of not creating a new Texture2D object every frame) but the TextureUnitState might not communicate with the gpu in that case. Or what are your Ideas? I hope you can help me with this. :)
Here is a small test showing the problem:
(uncommenting the line
tex = new Texture2D(Texture.BASE_LEVEL, Texture.RGBA, width, height);
in the render method makes it work with a memory leak.
I am using Java3D 1.7.2 from here:
https://jogamp.org/deployment/maven-java3d)
```
public class CustomRenderingTest extends JFrame implements Runnable {
int width = 800;
int height = 800;
Raster drawRaster;
JPanel drawingPanel;
Canvas3D canvas;
GraphicsContext3D gc;
Texture2D tex;
Shape3D shape;
Transform3D transformSpin;
Alpha rotAlpha;
AppearancePass1 appearancePass1;
AppearancePass2 appearanceTemp;
public CustomRenderingTest() {
initWindow();
new Thread(this).start();
}
@Override
public void run() {
gc = canvas.getGraphicsContext3D();
Sphere s = new Sphere(0.8f);
GeometryInfo info = new GeometryInfo((GeometryArray) s.getShape().getGeometry());
BufferedImage bImg = new BufferedImage(width, height, BufferedImage.TYPE_4BYTE_ABGR);
ImageComponent2D imageBuffer = new ImageComponent2D(ImageComponent.FORMAT_RGBA, bImg, true, true);
imageBuffer.setCapability(ImageComponent2D.ALLOW_IMAGE_READ | ImageComponent2D.ALLOW_IMAGE_WRITE);
drawRaster = new Raster(new Point3f(0.0f, 0.0f, 0.0f), Raster.RASTER_COLOR, 0, 0, width, height, imageBuffer, null);
drawRaster.setCapability(Raster.ALLOW_IMAGE_WRITE | Raster.ALLOW_IMAGE_READ | Raster.ALLOW_SIZE_READ | Raster.ALLOW_SIZE_WRITE);
appearancePass1 = new AppearancePass1();
shape = new Shape3D(info.getGeometryArray(), new AppearancePass2(bImg));
tex = new Texture2D(1,Texture.RGBA, width, height);
tex.setCapability(Texture.ALLOW_IMAGE_WRITE);
transformSpin = new Transform3D();
rotAlpha = new Alpha(-1, 6000);
while (true) {
render();
Thread.yield();
}
}
private void render() {
// first, apply transforms
double angle = rotAlpha.value() * 2.0*Math.PI;
transformSpin.rotY(angle);
// then render the mask pass
gc.clear();
gc.setModelTransform(transformSpin);
appearanceTemp = (AppearancePass2) shape.getAppearance(); // store the shape's individual appearance for later use (but it must be a individually configured Pass2 appearance)
shape.setAppearance(appearancePass1);
gc.draw(shape);
// after drawing the masks, read framebuffer to drawRaster and update the Pass2 shader's uniform with it
gc.readRaster(drawRaster);
// tex = new Texture2D(Texture.BASE_LEVEL, Texture.RGBA, width, height);
tex.setImage(0, drawRaster.getImage());
appearanceTemp.updateMaskBufferTexture(tex);
// clear the screen to draw the second pass
gc.clear();
shape.setAppearance(appearanceTemp);
gc.draw(shape);
// repeat, if another pass needed (like postFX that need more information than from the mask pass)
// last action = swap
canvas.swap();
}
private void initWindow() {
drawingPanel = new JPanel();
setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
setTitle("Immediate Multipass Rendering Test");
drawingPanel.setLayout(new BorderLayout());
drawingPanel.setPreferredSize(new java.awt.Dimension(width, height));
getContentPane().add(drawingPanel, java.awt.BorderLayout.CENTER);
pack();
GraphicsConfiguration config = SimpleUniverse.getPreferredConfiguration();
canvas = new Canvas3D(config);
canvas.stopRenderer();
SimpleUniverse universe = new SimpleUniverse(canvas);
universe.getViewingPlatform().setNominalViewingTransform();
drawingPanel.add(canvas, java.awt.BorderLayout.CENTER);
}
public static void main(String args[]) {
System.setProperty("sun.awt.noerasebackground", "true");
java.awt.EventQueue.invokeLater(() -> new CustomRenderingTest().setVisible(true));
}
/////////////////////////////////////////////////////////////////// SHADER STUFF
public static class AppearancePass1 extends ShaderAppearance {
public AppearancePass1() {
super();
final Shader vertexShader = loadShader("shaders/Pass1.vert", Shader.SHADING_LANGUAGE_GLSL, Shader.SHADER_TYPE_VERTEX);
final Shader fragmentShader = loadShader("shaders/Pass1.frag", Shader.SHADING_LANGUAGE_GLSL, Shader.SHADER_TYPE_FRAGMENT);
final Shader[] shaders = new Shader[]{vertexShader, fragmentShader};
if (vertexShader == null || fragmentShader == null) throw new RuntimeException("Unable to load Pass1 shader");
final GLSLShaderProgram glslShaderProgram = new GLSLShaderProgram();
glslShaderProgram.setShaders(shaders);
setShaderProgram(glslShaderProgram);
}
}
public static class AppearancePass2 extends ShaderAppearance {
public AppearancePass2(BufferedImage pass1Masks) {
super();
final Shader testShaderVert = loadShader("shaders/Pass2.vert", Shader.SHADING_LANGUAGE_GLSL, Shader.SHADER_TYPE_VERTEX);
final Shader testShaderFrag = loadShader("shaders/Pass2.frag", Shader.SHADING_LANGUAGE_GLSL, Shader.SHADER_TYPE_FRAGMENT);
final Shader[] shaders = new Shader[]{testShaderVert, testShaderFrag};
if (testShaderFrag == null) throw new RuntimeException("Unable to load Pass2 shader");
final GLSLShaderProgram glslShaderProgram = new GLSLShaderProgram();
glslShaderProgram.setShaders(shaders);
setShaderProgram(glslShaderProgram);
setCapability(Appearance.ALLOW_RENDERING_ATTRIBUTES_WRITE);
setCapability(ShaderAppearance.ALLOW_TEXTURE_WRITE);
final GLSLShaderProgram glslShader = new GLSLShaderProgram();
glslShader.setShaders(shaders);
setShaderProgram(glslShader);
TextureLoader loader = new TextureLoader(pass1Masks, TextureLoader.BY_REFERENCE | TextureLoader.Y_UP | TextureLoader.ALLOW_NON_POWER_OF_TWO);
Texture2D tex = (Texture2D) loader.getTexture();
ImageComponent2D imageComp = (ImageComponent2D) tex.getImage(0);
tex.setCapability(Texture.ALLOW_IMAGE_WRITE);
tex.setBoundaryModeS(Texture.CLAMP);
tex.setBoundaryModeT(Texture.CLAMP);
tex.setBoundaryColor(1.0f, 1.0f, 1.0f, 1.0f);
tex.setImage(0, imageComp);
TextureAttributes texAttr = new TextureAttributes();
texAttr.setTextureMode(TextureAttributes.REPLACE);
TextureUnitState[] tus = new TextureUnitState[1];
tus[0] = new TextureUnitState();
tus[0].setCapability(TextureUnitState.ALLOW_STATE_WRITE | TextureUnitState.ALLOW_STATE_READ);
tus[0].setTexture(tex);
tus[0].setTextureAttributes(texAttr);
setTextureUnitState(tus);
final String[] shaderAttrNames = new String[]{"masks", "screenWidth", "screenHeight"};
glslShader.setShaderAttrNames(shaderAttrNames);
ShaderAttributeSet shaderAttributeSet = new ShaderAttributeSet();
shaderAttributeSet.put(new ShaderAttributeValue("masks", 0)); // TextureUnitState index 0
shaderAttributeSet.put(new ShaderAttributeValue("screenWidth", 800));
shaderAttributeSet.put(new ShaderAttributeValue("screenHeight", 800)); // width and height will be updated by buffer every frame, so initialization size is not important.
setShaderAttributeSet(shaderAttributeSet);
}
public void updateMaskBufferTexture(Texture t) {
TextureUnitState tus = this.getTextureUnitState(0);
tus.setTexture(t);
((ShaderAttributeObject) getShaderAttributeSet().get("screenWidth")).setValue(t.getWidth());
((ShaderAttributeObject) getShaderAttributeSet().get("screenHeight")).setValue(t.getHeight());
}
}
public static Shader loadShader(String shaderFileName, final int shaderLanguage, final int shaderType) {
//... working correctly
}
}
```
Pass1.vert file content :
```
varying vec3 normal;
void main() {
normal = vec3(normalize(gl_Normal * gl_NormalMatrix));
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
```
Pass1.frag file content :
```
varying vec3 normal;
void main() {
gl_FragColor = vec4(vec3(normalize(normal)),1.0);
}
```
Pass2.vert file content :
```
void main() {
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
```
Pass2.frag file content :
```
uniform sampler2D masks;
uniform int screenWidth;
uniform int screenHeight;
void main() {
vec4 tex = texture2D(masks, vec2(gl_FragCoord.x / screenWidth, gl_FragCoord.y / screenHeight));
gl_FragColor = tex;
}
```