From 37da17e3eb95c1588ef082313994cb0356c8d58c Mon Sep 17 00:00:00 2001 From: David Bernard Date: Wed, 5 Nov 2014 12:15:14 +0100 Subject: [PATCH] add support of DepthStencil into *Renderer + a test app - only tested on desktop with Lwjgl --- .../renderer/android/OGLESShaderRenderer.java | 24 +- .../java/com/jme3/texture/FrameBuffer.java | 9 +- .../src/main/java/com/jme3/texture/Image.java | 15 +- .../jme3test/renderer/TestDepthStencil.java | 297 ++++++++++++++++++ .../renderer/ios/IGLESShaderRenderer.java | 2 +- .../com/jme3/renderer/jogl/JoglRenderer.java | 35 +-- .../jme3/renderer/lwjgl/LwjglRenderer.java | 5 +- 7 files changed, 359 insertions(+), 28 deletions(-) create mode 100644 jme3-examples/src/main/java/jme3test/renderer/TestDepthStencil.java diff --git a/jme3-android/src/main/java/com/jme3/renderer/android/OGLESShaderRenderer.java b/jme3-android/src/main/java/com/jme3/renderer/android/OGLESShaderRenderer.java index 0655b347f..2e5b8903a 100644 --- a/jme3-android/src/main/java/com/jme3/renderer/android/OGLESShaderRenderer.java +++ b/jme3-android/src/main/java/com/jme3/renderer/android/OGLESShaderRenderer.java @@ -36,8 +36,18 @@ import android.os.Build; import com.jme3.asset.AndroidImageInfo; import com.jme3.light.LightList; import com.jme3.material.RenderState; -import com.jme3.math.*; -import com.jme3.renderer.*; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Matrix4f; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.math.Vector4f; +import com.jme3.renderer.Caps; +import com.jme3.renderer.IDList; +import com.jme3.renderer.RenderContext; +import com.jme3.renderer.Renderer; +import com.jme3.renderer.RendererException; +import com.jme3.renderer.Statistics; import com.jme3.renderer.android.TextureUtil.AndroidGLImageFormat; import com.jme3.scene.Mesh; import com.jme3.scene.Mesh.Mode; @@ -58,7 +68,11 @@ import com.jme3.texture.Texture.WrapAxis; import com.jme3.util.BufferUtils; import com.jme3.util.ListMap; import com.jme3.util.NativeObjectManager; -import java.nio.*; +import java.nio.Buffer; +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; +import java.nio.ShortBuffer; import java.util.EnumSet; import java.util.List; import java.util.logging.Level; @@ -1313,8 +1327,10 @@ public class OGLESShaderRenderer implements Renderer { private int convertAttachmentSlot(int attachmentSlot) { // can also add support for stencil here - if (attachmentSlot == -100) { + if (attachmentSlot == FrameBuffer.SLOT_DEPTH) { return GLES20.GL_DEPTH_ATTACHMENT; +// if (attachmentSlot == FrameBuffer.SLOT_DEPTH_STENCIL) { +// return GLES30.GL_DEPTH_STENCIL_ATTACHMENT; } else if (attachmentSlot == 0) { return GLES20.GL_COLOR_ATTACHMENT0; } else { diff --git a/jme3-core/src/main/java/com/jme3/texture/FrameBuffer.java b/jme3-core/src/main/java/com/jme3/texture/FrameBuffer.java index 591494cbd..ed053339c 100644 --- a/jme3-core/src/main/java/com/jme3/texture/FrameBuffer.java +++ b/jme3-core/src/main/java/com/jme3/texture/FrameBuffer.java @@ -72,6 +72,9 @@ import java.util.ArrayList; * @author Kirill Vainer */ public class FrameBuffer extends NativeObject { + public static int SLOT_UNDEF = -1; + public static int SLOT_DEPTH = -100; + public static int SLOT_DEPTH_STENCIL = -101; private int width = 0; private int height = 0; @@ -91,7 +94,7 @@ public class FrameBuffer extends NativeObject { Texture tex; Image.Format format; int id = -1; - int slot = -1; + int slot = SLOT_UNDEF; int face = -1; /** @@ -212,7 +215,7 @@ public class FrameBuffer extends NativeObject { throw new IllegalArgumentException("Depth buffer format must be depth."); depthBuf = new RenderBuffer(); - depthBuf.slot = -100; // -100 == special slot for DEPTH_BUFFER + depthBuf.slot = format.isDepthStencilFormat() ? SLOT_DEPTH_STENCIL : SLOT_DEPTH; depthBuf.format = format; } @@ -404,7 +407,7 @@ public class FrameBuffer extends NativeObject { checkSetTexture(tex, true); depthBuf = new RenderBuffer(); - depthBuf.slot = -100; // indicates GL_DEPTH_ATTACHMENT + depthBuf.slot = img.getFormat().isDepthStencilFormat() ? SLOT_DEPTH_STENCIL : SLOT_DEPTH; depthBuf.tex = tex; depthBuf.format = img.getFormat(); } diff --git a/jme3-core/src/main/java/com/jme3/texture/Image.java b/jme3-core/src/main/java/com/jme3/texture/Image.java index 069852408..42cbdfb10 100644 --- a/jme3-core/src/main/java/com/jme3/texture/Image.java +++ b/jme3-core/src/main/java/com/jme3/texture/Image.java @@ -31,7 +31,11 @@ */ package com.jme3.texture; -import com.jme3.export.*; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; import com.jme3.math.FastMath; import com.jme3.renderer.Caps; import com.jme3.renderer.Renderer; @@ -349,6 +353,13 @@ public class Image extends NativeObject implements Savable /*, Cloneable*/ { return isDepth; } + /** + * @return True if this format is a depth + stencil (packed) format, false otherwise. + */ + boolean isDepthStencilFormat() { + return this == Depth24Stencil8; + } + /** * @return True if this is a compressed image format, false if * uncompressed. @@ -365,6 +376,8 @@ public class Image extends NativeObject implements Savable /*, Cloneable*/ { return isFloatingPoint; } + + } // image attributes diff --git a/jme3-examples/src/main/java/jme3test/renderer/TestDepthStencil.java b/jme3-examples/src/main/java/jme3test/renderer/TestDepthStencil.java new file mode 100644 index 000000000..f90d4a469 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/renderer/TestDepthStencil.java @@ -0,0 +1,297 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.renderer; + +import com.jme3.app.SimpleApplication; +import com.jme3.material.Material; +import com.jme3.material.RenderState.StencilOperation; +import com.jme3.material.RenderState.TestFunction; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.post.SceneProcessor; +import com.jme3.renderer.Camera; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.ViewPort; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.scene.Geometry; +import com.jme3.scene.control.AbstractControl; +import com.jme3.scene.shape.Box; +import com.jme3.system.AppSettings; +import com.jme3.system.JmeContext.Type; +import com.jme3.texture.FrameBuffer; +import com.jme3.texture.Image.Format; +import com.jme3.texture.Texture2D; +import com.jme3.util.BufferUtils; +import com.jme3.util.Screenshots; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.awt.image.BufferedImage; +import java.nio.ByteBuffer; +import javax.swing.JFrame; +import javax.swing.JPanel; +import javax.swing.SwingUtilities; + +/** + * This test renders a scene to an offscreen framebuffer, then copies + * the contents to a Swing JFrame. Note that some parts are done inefficently, + * this is done to make the code more readable. + */ +public class TestDepthStencil extends SimpleApplication implements SceneProcessor { + private static String TOGGLE_STENCIL = "TOGGLE_STENCIL"; + + private Geometry offBox; + private float angle = 0; + + private FrameBuffer offBuffer; + private ViewPort offView; + private Texture2D offTex; + private Camera offCamera; + private ImageDisplay display; + private boolean enableStencil = false; + + private static final int width = 800, height = 600; + + private final ByteBuffer cpuBuf = BufferUtils.createByteBuffer(width * height * 4); + private final byte[] cpuArray = new byte[width * height * 4]; + private final BufferedImage image = new BufferedImage(width, height, + BufferedImage.TYPE_4BYTE_ABGR); + + private class ImageDisplay extends JPanel { + + private long t; + private long total; + private int frames; + private int fps; + + @Override + public void paintComponent(Graphics gfx) { + super.paintComponent(gfx); + Graphics2D g2d = (Graphics2D) gfx; + + if (t == 0) + t = timer.getTime(); + +// g2d.setBackground(Color.BLACK); +// g2d.clearRect(0,0,width,height); + + synchronized (image){ + g2d.drawImage(image, null, 0, 0); + } + + long t2 = timer.getTime(); + long dt = t2 - t; + total += dt; + frames ++; + t = t2; + + if (total > 1000){ + fps = frames; + total = 0; + frames = 0; + } + + g2d.setColor(Color.white); + g2d.drawString("FPS: "+fps, 0, getHeight() - 100); + g2d.drawString("Toggle Stencil : [SPACE] (" + enableStencil + ")", 0, getHeight() - 10); + } + } + + public static void main(String[] args){ + TestDepthStencil app = new TestDepthStencil(); + app.setPauseOnLostFocus(false); + AppSettings settings = new AppSettings(true); + settings.setResolution(1, 1); + app.setSettings(settings); + app.start(Type.OffscreenSurface); + } + + public void createDisplayFrame(){ + SwingUtilities.invokeLater(new Runnable(){ + public void run(){ + final JFrame frame = new JFrame("Render Display"); + display = new ImageDisplay(); + display.setPreferredSize(new Dimension(width, height)); + frame.getContentPane().add(display); + frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); + frame.addWindowListener(new WindowAdapter(){ + public void windowClosed(WindowEvent e){ + stop(); + } + }); + frame.addKeyListener(new KeyListener() { + public void keyTyped(KeyEvent ke) { + } + + public void keyPressed(KeyEvent ke) { + } + + public void keyReleased(KeyEvent ke) { + if (ke.getKeyCode() == KeyEvent.VK_SPACE) { + enableStencil = !enableStencil; + }else if (ke.getKeyCode() == KeyEvent.VK_ESCAPE) { + frame.setVisible(false); + } + } + }); + frame.pack(); + frame.setLocationRelativeTo(null); + frame.setResizable(false); + frame.setVisible(true); + } + }); + } + + public void updateImageContents(){ + cpuBuf.clear(); + renderer.readFrameBuffer(offBuffer, cpuBuf); + + synchronized (image) { + Screenshots.convertScreenShot(cpuBuf, image); + } + + if (display != null) + display.repaint(); + } + + public void setupOffscreenView(){ + offCamera = new Camera(width, height); + + // create a pre-view. a view that is rendered before the main view + offView = renderManager.createPreView("Offscreen View", offCamera); + offView.setBackgroundColor(ColorRGBA.DarkGray); + offView.setClearFlags(true, true, true); + + // this will let us know when the scene has been rendered to the + // frame buffer + offView.addProcessor(this); + + // create offscreen framebuffer + offBuffer = new FrameBuffer(width, height, 1); + + //setup framebuffer's cam + offCamera.setFrustumPerspective(45f, 1f, 1f, 1000f); + offCamera.setLocation(new Vector3f(0f, 0f, -5f)); + offCamera.lookAt(new Vector3f(0f, 0f, 0f), Vector3f.UNIT_Y); + + //setup framebuffer's texture +// offTex = new Texture2D(width, height, Format.RGBA8); + + //setup framebuffer to use renderbuffer + // this is faster for gpu -> cpu copies + offBuffer.setDepthBuffer(Format.Depth24Stencil8); + offBuffer.setColorBuffer(Format.RGBA8); +// offBuffer.setColorTexture(offTex); + + //set viewport to render to offscreen framebuffer + offView.setOutputFrameBuffer(offBuffer); + + // setup framebuffer's scene + Box boxMesh = new Box(Vector3f.ZERO, 1,1,1); + final Material material = assetManager.loadMaterial("Interface/Logo/Logo.j3m"); + offBox = new Geometry("box", boxMesh); + offBox.setMaterial(material); + offBox.addControl(new AbstractControl() { + @Override + protected void controlUpdate(float tpf) { + material.getAdditionalRenderState().setStencil(enableStencil, + StencilOperation.Keep, StencilOperation.Keep, StencilOperation.Keep, + StencilOperation.Keep, StencilOperation.Keep, StencilOperation.Keep, + TestFunction.Never, TestFunction.Never + //TestFunction.Always, TestFunction.Always + ); + } + + @Override + protected void controlRender(RenderManager rm, ViewPort vp) { + } + }); + + // attach the scene to the viewport to be rendered + offView.attachScene(offBox); + } + + @Override + public void simpleInitApp() { + setupOffscreenView(); + createDisplayFrame(); + } + + @Override + public void simpleUpdate(float tpf){ + Quaternion q = new Quaternion(); + angle += tpf; + angle %= FastMath.TWO_PI; + q.fromAngles(angle, 0, angle); + + offBox.setLocalRotation(q); + offBox.updateLogicalState(tpf); + offBox.updateGeometricState(); + } + + public void initialize(RenderManager rm, ViewPort vp) { + } + + public void reshape(ViewPort vp, int w, int h) { + } + + public boolean isInitialized() { + return true; + } + + public void preFrame(float tpf) { + } + + public void postQueue(RenderQueue rq) { + } + + /** + * Update the CPU image's contents after the scene has + * been rendered to the framebuffer. + */ + public void postFrame(FrameBuffer out) { + updateImageContents(); + } + + public void cleanup() { + } + + +} diff --git a/jme3-ios/src/main/java/com/jme3/renderer/ios/IGLESShaderRenderer.java b/jme3-ios/src/main/java/com/jme3/renderer/ios/IGLESShaderRenderer.java index ed78baae2..957569ddd 100644 --- a/jme3-ios/src/main/java/com/jme3/renderer/ios/IGLESShaderRenderer.java +++ b/jme3-ios/src/main/java/com/jme3/renderer/ios/IGLESShaderRenderer.java @@ -2235,7 +2235,7 @@ public class IGLESShaderRenderer implements Renderer { private int convertAttachmentSlot(int attachmentSlot) { // can also add support for stencil here - if (attachmentSlot == -100) { + if (attachmentSlot == FrameBuffer.SLOT_DEPTH) { return JmeIosGLES.GL_DEPTH_ATTACHMENT; } else if (attachmentSlot == 0) { return JmeIosGLES.GL_COLOR_ATTACHMENT0; diff --git a/jme3-jogl/src/main/java/com/jme3/renderer/jogl/JoglRenderer.java b/jme3-jogl/src/main/java/com/jme3/renderer/jogl/JoglRenderer.java index 78c090e05..28c3bde23 100644 --- a/jme3-jogl/src/main/java/com/jme3/renderer/jogl/JoglRenderer.java +++ b/jme3-jogl/src/main/java/com/jme3/renderer/jogl/JoglRenderer.java @@ -33,20 +33,26 @@ package com.jme3.renderer.jogl; import com.jme3.light.LightList; import com.jme3.material.RenderState; -import com.jme3.material.RenderState.StencilOperation; -import com.jme3.material.RenderState.TestFunction; -import com.jme3.math.*; -import com.jme3.renderer.*; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Matrix4f; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.math.Vector4f; +import com.jme3.renderer.Caps; +import com.jme3.renderer.IDList; +import com.jme3.renderer.RenderContext; +import com.jme3.renderer.Renderer; +import com.jme3.renderer.RendererException; +import com.jme3.renderer.Statistics; import com.jme3.scene.Mesh; import com.jme3.scene.Mesh.Mode; import com.jme3.scene.VertexBuffer; -import com.jme3.scene.VertexBuffer.Format; import com.jme3.scene.VertexBuffer.Type; import com.jme3.scene.VertexBuffer.Usage; import com.jme3.shader.Attribute; import com.jme3.shader.Shader; import com.jme3.shader.Shader.ShaderSource; -import com.jme3.shader.Shader.ShaderType; import com.jme3.shader.Uniform; import com.jme3.texture.FrameBuffer; import com.jme3.texture.FrameBuffer.RenderBuffer; @@ -56,16 +62,6 @@ import com.jme3.texture.Texture.WrapAxis; import com.jme3.util.BufferUtils; import com.jme3.util.ListMap; import com.jme3.util.NativeObjectManager; - -import java.nio.*; -import java.util.EnumSet; -import java.util.List; -import java.util.logging.Level; -import java.util.logging.Logger; - -import jme3tools.converters.MipMapGenerator; -import jme3tools.shader.ShaderDebug; - import java.nio.Buffer; import java.nio.ByteBuffer; import java.nio.FloatBuffer; @@ -74,7 +70,6 @@ import java.util.EnumSet; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; - import javax.media.nativewindow.NativeWindowFactory; import javax.media.opengl.GL; import javax.media.opengl.GL2; @@ -84,6 +79,8 @@ import javax.media.opengl.GL2ES3; import javax.media.opengl.GL2GL3; import javax.media.opengl.GL3; import javax.media.opengl.GLContext; +import jme3tools.converters.MipMapGenerator; +import jme3tools.shader.ShaderDebug; public class JoglRenderer implements Renderer { @@ -1508,8 +1505,10 @@ public class JoglRenderer implements Renderer { private int convertAttachmentSlot(int attachmentSlot) { // can also add support for stencil here - if (attachmentSlot == -100) { + if (attachmentSlot == FrameBuffer.SLOT_DEPTH) { return GL.GL_DEPTH_ATTACHMENT; + } else if (attachmentSlot == FrameBuffer.SLOT_DEPTH_STENCIL) { + return GL2ES3.GL_DEPTH_STENCIL_ATTACHMENT; } else if (attachmentSlot < 0 || attachmentSlot >= 16) { throw new UnsupportedOperationException("Invalid FBO attachment slot: " + attachmentSlot); } diff --git a/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglRenderer.java b/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglRenderer.java index fdc908347..fc0a1c294 100644 --- a/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglRenderer.java +++ b/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglRenderer.java @@ -65,6 +65,7 @@ import jme3tools.converters.MipMapGenerator; import jme3tools.shader.ShaderDebug; import static org.lwjgl.opengl.ARBDrawInstanced.*; +import static org.lwjgl.opengl.ARBFramebufferObject.*; import static org.lwjgl.opengl.ARBInstancedArrays.*; import static org.lwjgl.opengl.ARBMultisample.*; import static org.lwjgl.opengl.ARBTextureMultisample.*; @@ -1418,8 +1419,10 @@ public class LwjglRenderer implements Renderer { private int convertAttachmentSlot(int attachmentSlot) { // can also add support for stencil here - if (attachmentSlot == -100) { + if (attachmentSlot == FrameBuffer.SLOT_DEPTH) { return GL_DEPTH_ATTACHMENT_EXT; + } else if (attachmentSlot == FrameBuffer.SLOT_DEPTH_STENCIL) { + return GL_DEPTH_STENCIL_ATTACHMENT; } else if (attachmentSlot < 0 || attachmentSlot >= 16) { throw new UnsupportedOperationException("Invalid FBO attachment slot: " + attachmentSlot); }