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          */
getFormat()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          */
getTexture()107         public Texture getTexture(){
108             return tex;
109         }
110 
111         /**
112          * Do not use.
113          */
getId()114         public int getId() {
115             return id;
116         }
117 
118         /**
119          * Do not use.
120          */
setId(int id)121         public void setId(int id){
122             this.id = id;
123         }
124 
125         /**
126          * Do not use.
127          */
getSlot()128         public int getSlot() {
129             return slot;
130         }
131 
resetObject()132         public void resetObject(){
133             id = -1;
134         }
135 
createDestructableClone()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
toString()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      */
FrameBuffer(int width, int height, int samples)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 
FrameBuffer(FrameBuffer src)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      */
setDepthBuffer(Image.Format format)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      */
setColorBuffer(Image.Format format)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 
checkSetTexture(Texture tex, boolean depth)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      */
setMultiTarget(boolean enabled)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      */
isMultiTarget()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      */
setTargetIndex(int index)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      */
getTargetIndex()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      */
setColorTexture(Texture2D tex)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      */
clearColorTargets()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      */
addColorTexture(Texture2D tex)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      */
setDepthTexture(Texture2D tex)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      */
getNumColorBuffers()370     public int getNumColorBuffers(){
371         return colorBufs.size();
372     }
373 
374     /**
375      * @param index
376      * @return The color buffer at the given index.
377      */
getColorBuffer(int index)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      */
getColorBuffer()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      */
getDepthBuffer()397     public RenderBuffer getDepthBuffer() {
398         return depthBuf;
399     }
400 
401     /**
402      * @return The height in pixels of this framebuffer.
403      */
getHeight()404     public int getHeight() {
405         return height;
406     }
407 
408     /**
409      * @return The width in pixels of this framebuffer.
410      */
getWidth()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      */
getSamples()419     public int getSamples() {
420         return samples;
421     }
422 
423     @Override
toString()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
resetObject()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
deleteObject(Object rendererObject)453     public void deleteObject(Object rendererObject) {
454         ((Renderer)rendererObject).deleteFrameBuffer(this);
455     }
456 
createDestructableClone()457     public NativeObject createDestructableClone(){
458         return new FrameBuffer(this);
459     }
460 }
461