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 */
32package com.jme3.post;
33
34import com.jme3.asset.AssetManager;
35import com.jme3.export.*;
36import com.jme3.material.Material;
37import com.jme3.renderer.Caps;
38import com.jme3.renderer.RenderManager;
39import com.jme3.renderer.Renderer;
40import com.jme3.renderer.ViewPort;
41import com.jme3.texture.FrameBuffer;
42import com.jme3.texture.Image.Format;
43import com.jme3.texture.Texture;
44import com.jme3.texture.Texture2D;
45import java.io.IOException;
46import java.util.Collection;
47import java.util.Iterator;
48import java.util.List;
49
50/**
51 * Filters are 2D effects applied to the rendered scene.<br>
52 * The filter is fed with the rendered scene image rendered in an offscreen frame buffer.<br>
53 * This texture is applied on a fullscreen quad, with a special material.<br>
54 * This material uses a shader that aplly the desired effect to the scene texture.<br>
55 * <br>
56 * This class is abstract, any Filter must extend it.<br>
57 * Any filter holds a frameBuffer and a texture<br>
58 * The getMaterial must return a Material that use a GLSL shader immplementing the desired effect<br>
59 *
60 * @author Rémy Bouquet aka Nehon
61 */
62public abstract class Filter implements Savable {
63
64
65    private String name;
66    protected Pass defaultPass;
67    protected List<Pass> postRenderPasses;
68    protected Material material;
69    protected boolean enabled = true;
70    protected FilterPostProcessor processor;
71
72    public Filter(String name) {
73        this.name = name;
74    }
75
76    /**
77     * Inner class Pass
78     * Pass are like filters in filters.
79     * Some filters will need multiple passes before the final render
80     */
81    public class Pass {
82
83        protected FrameBuffer renderFrameBuffer;
84        protected Texture2D renderedTexture;
85        protected Texture2D depthTexture;
86        protected Material passMaterial;
87
88        /**
89         * init the pass called internally
90         * @param renderer
91         * @param width
92         * @param height
93         * @param textureFormat
94         * @param depthBufferFormat
95         * @param numSamples
96         */
97        public void init(Renderer renderer, int width, int height, Format textureFormat, Format depthBufferFormat, int numSamples, boolean renderDepth) {
98            Collection<Caps> caps = renderer.getCaps();
99            if (numSamples > 1 && caps.contains(Caps.FrameBufferMultisample) && caps.contains(Caps.OpenGL31)) {
100                renderFrameBuffer = new FrameBuffer(width, height, numSamples);
101                renderedTexture = new Texture2D(width, height, numSamples, textureFormat);
102                renderFrameBuffer.setDepthBuffer(depthBufferFormat);
103                if (renderDepth) {
104                    depthTexture = new Texture2D(width, height, numSamples, depthBufferFormat);
105                    renderFrameBuffer.setDepthTexture(depthTexture);
106                }
107            } else {
108                renderFrameBuffer = new FrameBuffer(width, height, 1);
109                renderedTexture = new Texture2D(width, height, textureFormat);
110                renderFrameBuffer.setDepthBuffer(depthBufferFormat);
111                if (renderDepth) {
112                    depthTexture = new Texture2D(width, height, depthBufferFormat);
113                    renderFrameBuffer.setDepthTexture(depthTexture);
114                }
115            }
116
117            renderFrameBuffer.setColorTexture(renderedTexture);
118
119
120        }
121
122        /**
123         *  init the pass called internally
124         * @param renderer
125         * @param width
126         * @param height
127         * @param textureFormat
128         * @param depthBufferFormat
129         */
130        public void init(Renderer renderer, int width, int height, Format textureFormat, Format depthBufferFormat) {
131            init(renderer, width, height, textureFormat, depthBufferFormat, 1);
132        }
133
134        public void init(Renderer renderer, int width, int height, Format textureFormat, Format depthBufferFormat, int numSamples) {
135            init(renderer, width, height, textureFormat, depthBufferFormat, numSamples, false);
136        }
137
138        /**
139         *  init the pass called internally
140         * @param renderer
141         * @param width
142         * @param height
143         * @param textureFormat
144         * @param depthBufferFormat
145         * @param numSample
146         * @param material
147         */
148        public void init(Renderer renderer, int width, int height, Format textureFormat, Format depthBufferFormat, int numSample, Material material) {
149            init(renderer, width, height, textureFormat, depthBufferFormat, numSample);
150            passMaterial = material;
151        }
152
153        public boolean requiresSceneAsTexture() {
154            return false;
155        }
156
157        public boolean requiresDepthAsTexture() {
158            return false;
159        }
160
161        public void beforeRender() {
162        }
163
164        public FrameBuffer getRenderFrameBuffer() {
165            return renderFrameBuffer;
166        }
167
168        public void setRenderFrameBuffer(FrameBuffer renderFrameBuffer) {
169            this.renderFrameBuffer = renderFrameBuffer;
170        }
171
172        public Texture2D getDepthTexture() {
173            return depthTexture;
174        }
175
176        public Texture2D getRenderedTexture() {
177            return renderedTexture;
178        }
179
180        public void setRenderedTexture(Texture2D renderedTexture) {
181            this.renderedTexture = renderedTexture;
182        }
183
184        public Material getPassMaterial() {
185            return passMaterial;
186        }
187
188        public void setPassMaterial(Material passMaterial) {
189            this.passMaterial = passMaterial;
190        }
191
192        public void cleanup(Renderer r) {
193        }
194    }
195
196    /**
197     * returns the default pass texture format
198     * @return
199     */
200    protected Format getDefaultPassTextureFormat() {
201        return Format.RGBA8;
202    }
203
204    /**
205     * returns the default pass depth format
206     * @return
207     */
208    protected Format getDefaultPassDepthFormat() {
209        return Format.Depth;
210    }
211
212    /**
213     * contruct a Filter
214     */
215    protected Filter() {
216        this("filter");
217    }
218
219    /**
220     *
221     * initialize this filter
222     * use InitFilter for overriding filter initialization
223     * @param manager the assetManager
224     * @param renderManager the renderManager
225     * @param vp the viewport
226     * @param w the width
227     * @param h the height
228     */
229    protected final void init(AssetManager manager, RenderManager renderManager, ViewPort vp, int w, int h) {
230        //  cleanup(renderManager.getRenderer());
231        defaultPass = new Pass();
232        defaultPass.init(renderManager.getRenderer(), w, h, getDefaultPassTextureFormat(), getDefaultPassDepthFormat());
233        initFilter(manager, renderManager, vp, w, h);
234    }
235
236    /**
237     * cleanup this filter
238     * @param r
239     */
240    protected final void cleanup(Renderer r) {
241        processor = null;
242        if (defaultPass != null) {
243            defaultPass.cleanup(r);
244        }
245        if (postRenderPasses != null) {
246            for (Iterator<Pass> it = postRenderPasses.iterator(); it.hasNext();) {
247                Pass pass = it.next();
248                pass.cleanup(r);
249            }
250        }
251        cleanUpFilter(r);
252    }
253
254    /**
255     * Initialization of sub classes filters
256     * This method is called once when the filter is added to the FilterPostProcessor
257     * It should contain Material initializations and extra passes initialization
258     * @param manager the assetManager
259     * @param renderManager the renderManager
260     * @param vp the viewPort where this filter is rendered
261     * @param w the width of the filter
262     * @param h the height of the filter
263     */
264    protected abstract void initFilter(AssetManager manager, RenderManager renderManager, ViewPort vp, int w, int h);
265
266    /**
267     * override this method if you have some cleanup to do
268     * @param r the renderer
269     */
270    protected void cleanUpFilter(Renderer r) {
271    }
272
273    /**
274     * Must return the material used for this filter.
275     * this method is called every frame.
276     *
277     * @return the material used for this filter.
278     */
279    protected abstract Material getMaterial();
280
281    /**
282     * Override if you want to do something special with the depth texture;
283     * @param depthTexture
284     */
285    protected void setDepthTexture(Texture depthTexture){
286        getMaterial().setTexture("DepthTexture", depthTexture);
287    }
288
289    /**
290     * Override this method if you want to make a pre pass, before the actual rendering of the frame
291     * @param renderManager
292     * @param viewPort
293     */
294    protected void postQueue(RenderManager renderManager, ViewPort viewPort) {
295    }
296
297    /**
298     * Override this method if you want to modify parameters according to tpf before the rendering of the frame.
299     * This is usefull for animated filters
300     * Also it can be the place to render pre passes
301     * @param tpf the time used to render the previous frame
302     */
303    protected void preFrame(float tpf) {
304    }
305
306    /**
307     * Override this method if you want to make a pass just after the frame has been rendered and just before the filter rendering
308     * @param renderManager
309     * @param viewPort
310     * @param prevFilterBuffer
311     * @param sceneBuffer
312     */
313    protected void postFrame(RenderManager renderManager, ViewPort viewPort, FrameBuffer prevFilterBuffer, FrameBuffer sceneBuffer) {
314    }
315
316    /**
317     * Override this method if you want to save extra properties when the filter is saved else only basic properties of the filter will be saved
318     * This method should always begin by super.write(ex);
319     * @param ex
320     * @throws IOException
321     */
322    public void write(JmeExporter ex) throws IOException {
323        OutputCapsule oc = ex.getCapsule(this);
324        oc.write(name, "name", "");
325        oc.write(enabled, "enabled", true);
326    }
327
328    /**
329     * Override this method if you want to load extra properties when the filter
330     * is loaded else only basic properties of the filter will be loaded
331     * This method should always begin by super.read(im);
332     */
333    public void read(JmeImporter im) throws IOException {
334        InputCapsule ic = im.getCapsule(this);
335        name = ic.readString("name", "");
336        enabled = ic.readBoolean("enabled", true);
337    }
338
339    /**
340     * returns the name of the filter
341     * @return
342     */
343    public String getName() {
344        return name;
345    }
346
347    /**
348     * Sets the name of the filter
349     * @param name
350     */
351    public void setName(String name) {
352        this.name = name;
353    }
354
355    /**
356     * returns the default pass frame buffer
357     * @return
358     */
359    protected FrameBuffer getRenderFrameBuffer() {
360        return defaultPass.renderFrameBuffer;
361    }
362
363    /**
364     * sets the default pas frame buffer
365     * @param renderFrameBuffer
366     */
367    protected void setRenderFrameBuffer(FrameBuffer renderFrameBuffer) {
368        this.defaultPass.renderFrameBuffer = renderFrameBuffer;
369    }
370
371    /**
372     * returns the rendered texture of this filter
373     * @return
374     */
375    protected Texture2D getRenderedTexture() {
376        return defaultPass.renderedTexture;
377    }
378
379    /**
380     * sets the rendered texture of this filter
381     * @param renderedTexture
382     */
383    protected void setRenderedTexture(Texture2D renderedTexture) {
384        this.defaultPass.renderedTexture = renderedTexture;
385    }
386
387    /**
388     * Override this method and return true if your Filter needs the depth texture
389     *
390     * @return true if your Filter need the depth texture
391     */
392    protected boolean isRequiresDepthTexture() {
393        return false;
394    }
395
396    /**
397     * Override this method and return false if your Filter does not need the scene texture
398     *
399     * @return false if your Filter does not need the scene texture
400     */
401    protected boolean isRequiresSceneTexture() {
402        return true;
403    }
404
405    /**
406     * returns the list of the postRender passes
407     * @return
408     */
409    protected List<Pass> getPostRenderPasses() {
410        return postRenderPasses;
411    }
412
413    /**
414     * Enable or disable this filter
415     * @param enabled true to enable
416     */
417    public void setEnabled(boolean enabled) {
418        if (processor != null) {
419            processor.setFilterState(this, enabled);
420        } else {
421            this.enabled = enabled;
422        }
423    }
424
425    /**
426     * returns ttrue if the filter is enabled
427     * @return enabled
428     */
429    public boolean isEnabled() {
430        return enabled;
431    }
432
433    /**
434     * sets a reference to the FilterPostProcessor ti which this filter is attached
435     * @param proc
436     */
437    protected void setProcessor(FilterPostProcessor proc) {
438        processor = proc;
439    }
440}
441