FrameBuffer.java revision 59b2e6871c65f58fdad78cd7229c292f6a177578
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
33package com.jme3.texture;
34
35import com.jme3.renderer.Caps;
36import com.jme3.renderer.Renderer;
37import com.jme3.texture.Image.Format;
38import com.jme3.util.NativeObject;
39import 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 */
75public 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