Home | History | Annotate | Download | only in texture
      1 /*
      2  * Copyright (c) 2009-2010 jMonkeyEngine
      3  * All rights reserved.
      4  *
      5  * Redistribution and use in source and binary forms, with or without
      6  * modification, are permitted provided that the following conditions are
      7  * met:
      8  *
      9  * * Redistributions of source code must retain the above copyright
     10  *   notice, this list of conditions and the following disclaimer.
     11  *
     12  * * Redistributions in binary form must reproduce the above copyright
     13  *   notice, this list of conditions and the following disclaimer in the
     14  *   documentation and/or other materials provided with the distribution.
     15  *
     16  * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
     17  *   may be used to endorse or promote products derived from this software
     18  *   without specific prior written permission.
     19  *
     20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
     22  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     23  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
     24  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
     25  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     26  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
     27  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
     28  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
     29  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
     30  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     31  */
     32 
     33 package com.jme3.texture;
     34 
     35 import com.jme3.renderer.Caps;
     36 import com.jme3.renderer.Renderer;
     37 import com.jme3.texture.Image.Format;
     38 import com.jme3.util.NativeObject;
     39 import java.util.ArrayList;
     40 
     41 /**
     42  * <p>
     43  * <code>FrameBuffer</code>s are rendering surfaces allowing
     44  * off-screen rendering and render-to-texture functionality.
     45  * Instead of the scene rendering to the screen, it is rendered into the
     46  * FrameBuffer, the result can be either a texture or a buffer.
     47  * <p>
     48  * A <code>FrameBuffer</code> supports two methods of rendering,
     49  * using a {@link Texture} or using a buffer.
     50  * When using a texture, the result of the rendering will be rendered
     51  * onto the texture, after which the texture can be placed on an object
     52  * and rendered as if the texture was uploaded from disk.
     53  * When using a buffer, the result is rendered onto
     54  * a buffer located on the GPU, the data of this buffer is not accessible
     55  * to the user. buffers are useful if one
     56  * wishes to retrieve only the color content of the scene, but still desires
     57  * depth testing (which requires a depth buffer).
     58  * Buffers can be copied to other framebuffers
     59  * including the main screen, by using
     60  * {@link Renderer#copyFrameBuffer(com.jme3.texture.FrameBuffer, com.jme3.texture.FrameBuffer) }.
     61  * The content of a {@link RenderBuffer} can be retrieved by using
     62  * {@link Renderer#readFrameBuffer(com.jme3.texture.FrameBuffer, java.nio.ByteBuffer) }.
     63  * <p>
     64  * <code>FrameBuffer</code>s have several attachment points, there are
     65  * several <em>color</em> attachment points and a single <em>depth</em>
     66  * attachment point.
     67  * The color attachment points support image formats such as
     68  * {@link Format#RGBA8}, allowing rendering the color content of the scene.
     69  * The depth attachment point requires a depth image format.
     70  *
     71  * @see Renderer#setFrameBuffer(com.jme3.texture.FrameBuffer)
     72  *
     73  * @author Kirill Vainer
     74  */
     75 public class FrameBuffer extends NativeObject {
     76 
     77     private int width = 0;
     78     private int height = 0;
     79     private int samples = 1;
     80     private ArrayList<RenderBuffer> colorBufs = new ArrayList<RenderBuffer>();
     81     private RenderBuffer depthBuf = null;
     82     private int colorBufIndex = 0;
     83 
     84     /**
     85      * <code>RenderBuffer</code> represents either a texture or a
     86      * buffer that will be rendered to. <code>RenderBuffer</code>s
     87      * are attached to an attachment slot on a <code>FrameBuffer</code>.
     88      */
     89     public class RenderBuffer {
     90 
     91         Texture tex;
     92         Image.Format format;
     93         int id = -1;
     94         int slot = -1;
     95 
     96         /**
     97          * @return The image format of the render buffer.
     98          */
     99         public Format getFormat() {
    100             return format;
    101         }
    102 
    103         /**
    104          * @return The texture to render to for this <code>RenderBuffer</code>
    105          * or null if content should be rendered into a buffer.
    106          */
    107         public Texture getTexture(){
    108             return tex;
    109         }
    110 
    111         /**
    112          * Do not use.
    113          */
    114         public int getId() {
    115             return id;
    116         }
    117 
    118         /**
    119          * Do not use.
    120          */
    121         public void setId(int id){
    122             this.id = id;
    123         }
    124 
    125         /**
    126          * Do not use.
    127          */
    128         public int getSlot() {
    129             return slot;
    130         }
    131 
    132         public void resetObject(){
    133             id = -1;
    134         }
    135 
    136         public RenderBuffer createDestructableClone(){
    137             if (tex != null){
    138                 return null;
    139             }else{
    140                 RenderBuffer destructClone =  new RenderBuffer();
    141                 destructClone.id = id;
    142                 return destructClone;
    143             }
    144         }
    145 
    146         @Override
    147         public String toString(){
    148             if (tex != null){
    149                 return "TextureTarget[format=" + format + "]";
    150             }else{
    151                 return "BufferTarget[format=" + format + "]";
    152             }
    153         }
    154     }
    155 
    156     /**
    157      * <p>
    158      * Creates a new FrameBuffer with the given width, height, and number
    159      * of samples. If any textures are attached to this FrameBuffer, then
    160      * they must have the same number of samples as given in this constructor.
    161      * <p>
    162      * Note that if the {@link Renderer} does not expose the
    163      * {@link Caps#NonPowerOfTwoTextures}, then an exception will be thrown
    164      * if the width and height arguments are not power of two.
    165      *
    166      * @param width The width to use
    167      * @param height The height to use
    168      * @param samples The number of samples to use for a multisampled
    169      * framebuffer, or 1 if the framebuffer should be singlesampled.
    170      *
    171      * @throws IllegalArgumentException If width or height are not positive.
    172      */
    173     public FrameBuffer(int width, int height, int samples){
    174         super(FrameBuffer.class);
    175         if (width <= 0 || height <= 0)
    176                 throw new IllegalArgumentException("FrameBuffer must have valid size.");
    177 
    178         this.width = width;
    179         this.height = height;
    180         this.samples = samples == 0 ? 1 : samples;
    181     }
    182 
    183     protected FrameBuffer(FrameBuffer src){
    184         super(FrameBuffer.class, src.id);
    185         /*
    186         for (RenderBuffer renderBuf : src.colorBufs){
    187             RenderBuffer clone = renderBuf.createDestructableClone();
    188             if (clone != null)
    189                 this.colorBufs.add(clone);
    190         }
    191 
    192         this.depthBuf = src.depthBuf.createDestructableClone();
    193          */
    194     }
    195 
    196     /**
    197      * Enables the use of a depth buffer for this <code>FrameBuffer</code>.
    198      *
    199      * @param format The format to use for the depth buffer.
    200      * @throws IllegalArgumentException If <code>format</code> is not a depth format.
    201      */
    202     public void setDepthBuffer(Image.Format format){
    203         if (id != -1)
    204             throw new UnsupportedOperationException("FrameBuffer already initialized.");
    205 
    206         if (!format.isDepthFormat())
    207             throw new IllegalArgumentException("Depth buffer format must be depth.");
    208 
    209         depthBuf = new RenderBuffer();
    210         depthBuf.slot = -100; // -100 == special slot for DEPTH_BUFFER
    211         depthBuf.format = format;
    212     }
    213 
    214     /**
    215      * Enables the use of a color buffer for this <code>FrameBuffer</code>.
    216      *
    217      * @param format The format to use for the color buffer.
    218      * @throws IllegalArgumentException If <code>format</code> is not a color format.
    219      */
    220     public void setColorBuffer(Image.Format format){
    221         if (id != -1)
    222             throw new UnsupportedOperationException("FrameBuffer already initialized.");
    223 
    224         if (format.isDepthFormat())
    225             throw new IllegalArgumentException("Color buffer format must be color/luminance.");
    226 
    227         RenderBuffer colorBuf = new RenderBuffer();
    228         colorBuf.slot = 0;
    229         colorBuf.format = format;
    230 
    231         colorBufs.clear();
    232         colorBufs.add(colorBuf);
    233     }
    234 
    235     private void checkSetTexture(Texture tex, boolean depth){
    236         Image img = tex.getImage();
    237         if (img == null)
    238             throw new IllegalArgumentException("Texture not initialized with RTT.");
    239 
    240         if (depth && !img.getFormat().isDepthFormat())
    241             throw new IllegalArgumentException("Texture image format must be depth.");
    242         else if (!depth && img.getFormat().isDepthFormat())
    243             throw new IllegalArgumentException("Texture image format must be color/luminance.");
    244 
    245         // check that resolution matches texture resolution
    246         if (width != img.getWidth() || height != img.getHeight())
    247             throw new IllegalArgumentException("Texture image resolution " +
    248                                                "must match FB resolution");
    249 
    250         if (samples != tex.getImage().getMultiSamples())
    251             throw new IllegalStateException("Texture samples must match framebuffer samples");
    252     }
    253 
    254     /**
    255      * If enabled, any shaders rendering into this <code>FrameBuffer</code>
    256      * will be able to write several results into the renderbuffers
    257      * by using the <code>gl_FragData</code> array. Every slot in that
    258      * array maps into a color buffer attached to this framebuffer.
    259      *
    260      * @param enabled True to enable MRT (multiple rendering targets).
    261      */
    262     public void setMultiTarget(boolean enabled){
    263         if (enabled) colorBufIndex = -1;
    264         else colorBufIndex = 0;
    265     }
    266 
    267     /**
    268      * @return True if MRT (multiple rendering targets) is enabled.
    269      * @see FrameBuffer#setMultiTarget(boolean)
    270      */
    271     public boolean isMultiTarget(){
    272         return colorBufIndex == -1;
    273     }
    274 
    275     /**
    276      * If MRT is not enabled ({@link FrameBuffer#setMultiTarget(boolean) } is false)
    277      * then this specifies the color target to which the scene should be rendered.
    278      * <p>
    279      * By default the value is 0.
    280      *
    281      * @param index The color attachment index.
    282      * @throws IllegalArgumentException If index is negative or doesn't map
    283      * to any attachment on this framebuffer.
    284      */
    285     public void setTargetIndex(int index){
    286         if (index < 0 || index >= 16)
    287             throw new IllegalArgumentException("Target index must be between 0 and 16");
    288 
    289         if (colorBufs.size() < index)
    290             throw new IllegalArgumentException("The target at " + index + " is not set!");
    291 
    292         colorBufIndex = index;
    293         setUpdateNeeded();
    294     }
    295 
    296     /**
    297      * @return The color target to which the scene should be rendered.
    298      *
    299      * @see FrameBuffer#setTargetIndex(int)
    300      */
    301     public int getTargetIndex(){
    302         return colorBufIndex;
    303     }
    304 
    305     /**
    306      * Set the color texture to use for this framebuffer.
    307      * This automatically clears all existing textures added previously
    308      * with {@link FrameBuffer#addColorTexture(com.jme3.texture.Texture2D) }
    309      * and adds this texture as the only target.
    310      *
    311      * @param tex The color texture to set.
    312      */
    313     public void setColorTexture(Texture2D tex){
    314         clearColorTargets();
    315         addColorTexture(tex);
    316     }
    317 
    318     /**
    319      * Clears all color targets that were set or added previously.
    320      */
    321     public void clearColorTargets(){
    322         colorBufs.clear();
    323     }
    324 
    325     /**
    326      * Add a color texture to use for this framebuffer.
    327      * If MRT is enabled, then each subsequently added texture can be
    328      * rendered to through a shader that writes to the array <code>gl_FragData</code>.
    329      * If MRT is not enabled, then the index set with {@link FrameBuffer#setTargetIndex(int) }
    330      * is rendered to by the shader.
    331      *
    332      * @param tex The texture to add.
    333      */
    334     public void addColorTexture(Texture2D tex) {
    335         if (id != -1)
    336             throw new UnsupportedOperationException("FrameBuffer already initialized.");
    337 
    338         Image img = tex.getImage();
    339         checkSetTexture(tex, false);
    340 
    341         RenderBuffer colorBuf = new RenderBuffer();
    342         colorBuf.slot = colorBufs.size();
    343         colorBuf.tex = tex;
    344         colorBuf.format = img.getFormat();
    345 
    346         colorBufs.add(colorBuf);
    347     }
    348 
    349     /**
    350      * Set the depth texture to use for this framebuffer.
    351      *
    352      * @param tex The color texture to set.
    353      */
    354     public void setDepthTexture(Texture2D tex){
    355         if (id != -1)
    356             throw new UnsupportedOperationException("FrameBuffer already initialized.");
    357 
    358         Image img = tex.getImage();
    359         checkSetTexture(tex, true);
    360 
    361         depthBuf = new RenderBuffer();
    362         depthBuf.slot = -100; // indicates GL_DEPTH_ATTACHMENT
    363         depthBuf.tex = tex;
    364         depthBuf.format = img.getFormat();
    365     }
    366 
    367     /**
    368      * @return The number of color buffers attached to this texture.
    369      */
    370     public int getNumColorBuffers(){
    371         return colorBufs.size();
    372     }
    373 
    374     /**
    375      * @param index
    376      * @return The color buffer at the given index.
    377      */
    378     public RenderBuffer getColorBuffer(int index){
    379         return colorBufs.get(index);
    380     }
    381 
    382     /**
    383      * @return The first color buffer attached to this FrameBuffer, or null
    384      * if no color buffers are attached.
    385      */
    386     public RenderBuffer getColorBuffer() {
    387         if (colorBufs.isEmpty())
    388             return null;
    389 
    390         return colorBufs.get(0);
    391     }
    392 
    393     /**
    394      * @return The depth buffer attached to this FrameBuffer, or null
    395      * if no depth buffer is attached
    396      */
    397     public RenderBuffer getDepthBuffer() {
    398         return depthBuf;
    399     }
    400 
    401     /**
    402      * @return The height in pixels of this framebuffer.
    403      */
    404     public int getHeight() {
    405         return height;
    406     }
    407 
    408     /**
    409      * @return The width in pixels of this framebuffer.
    410      */
    411     public int getWidth() {
    412         return width;
    413     }
    414 
    415     /**
    416      * @return The number of samples when using a multisample framebuffer, or
    417      * 1 if this is a singlesampled framebuffer.
    418      */
    419     public int getSamples() {
    420         return samples;
    421     }
    422 
    423     @Override
    424     public String toString(){
    425         StringBuilder sb = new StringBuilder();
    426         String mrtStr = colorBufIndex >= 0 ? "" + colorBufIndex : "mrt";
    427         sb.append("FrameBuffer[format=").append(width).append("x").append(height)
    428           .append("x").append(samples).append(", drawBuf=").append(mrtStr).append("]\n");
    429         if (depthBuf != null)
    430             sb.append("Depth => ").append(depthBuf).append("\n");
    431         for (RenderBuffer colorBuf : colorBufs){
    432             sb.append("Color(").append(colorBuf.slot)
    433               .append(") => ").append(colorBuf).append("\n");
    434         }
    435         return sb.toString();
    436     }
    437 
    438     @Override
    439     public void resetObject() {
    440         this.id = -1;
    441 
    442         for (int i = 0; i < colorBufs.size(); i++) {
    443             colorBufs.get(i).resetObject();
    444         }
    445 
    446         if (depthBuf != null)
    447             depthBuf.resetObject();
    448 
    449         setUpdateNeeded();
    450     }
    451 
    452     @Override
    453     public void deleteObject(Object rendererObject) {
    454         ((Renderer)rendererObject).deleteFrameBuffer(this);
    455     }
    456 
    457     public NativeObject createDestructableClone(){
    458         return new FrameBuffer(this);
    459     }
    460 }
    461