• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 package com.jme3.post;
33 
34 import com.jme3.asset.AssetManager;
35 import com.jme3.export.*;
36 import com.jme3.material.Material;
37 import com.jme3.renderer.*;
38 import com.jme3.renderer.queue.RenderQueue;
39 import com.jme3.texture.FrameBuffer;
40 import com.jme3.texture.Image.Format;
41 import com.jme3.texture.Texture2D;
42 import com.jme3.ui.Picture;
43 import java.io.IOException;
44 import java.util.ArrayList;
45 import java.util.Collection;
46 import java.util.Iterator;
47 import java.util.List;
48 
49 /**
50  * A FilterPostProcessor is a processor that can apply several {@link Filter}s to a rendered scene<br>
51  * It manages a list of filters that will be applied in the order in which they've been added to the list
52  * @author Rémy Bouquet aka Nehon
53  */
54 public class FilterPostProcessor implements SceneProcessor, Savable {
55 
56     private RenderManager renderManager;
57     private Renderer renderer;
58     private ViewPort viewPort;
59     private FrameBuffer renderFrameBufferMS;
60     private int numSamples = 1;
61     private FrameBuffer renderFrameBuffer;
62     private Texture2D filterTexture;
63     private Texture2D depthTexture;
64     private List<Filter> filters = new ArrayList<Filter>();
65     private AssetManager assetManager;
66     private Camera filterCam = new Camera(1, 1);
67     private Picture fsQuad;
68     private boolean computeDepth = false;
69     private FrameBuffer outputBuffer;
70     private int width;
71     private int height;
72     private float bottom;
73     private float left;
74     private float right;
75     private float top;
76     private int originalWidth;
77     private int originalHeight;
78     private int lastFilterIndex = -1;
79     private boolean cameraInit = false;
80 
81     /**
82      * Create a FilterProcessor
83      * @param assetManager the assetManager
84      */
FilterPostProcessor(AssetManager assetManager)85     public FilterPostProcessor(AssetManager assetManager) {
86         this.assetManager = assetManager;
87     }
88 
89     /**
90      * Don't use this constructor use {@link FilterPostProcessor(AssetManager assetManager)}<br>
91      * This constructor is used for serialization only
92      */
FilterPostProcessor()93     public FilterPostProcessor() {
94     }
95 
96     /**
97      * Adds a filter to the filters list<br>
98      * @param filter the filter to add
99      */
addFilter(Filter filter)100     public void addFilter(Filter filter) {
101         filters.add(filter);
102 
103         if (isInitialized()) {
104             initFilter(filter, viewPort);
105         }
106 
107         setFilterState(filter, filter.isEnabled());
108 
109     }
110 
111     /**
112      * removes this filters from the filters list
113      * @param filter
114      */
removeFilter(Filter filter)115     public void removeFilter(Filter filter) {
116         filters.remove(filter);
117         filter.cleanup(renderer);
118         updateLastFilterIndex();
119     }
120 
getFilterIterator()121     public Iterator<Filter> getFilterIterator() {
122         return filters.iterator();
123     }
124 
initialize(RenderManager rm, ViewPort vp)125     public void initialize(RenderManager rm, ViewPort vp) {
126         renderManager = rm;
127         renderer = rm.getRenderer();
128         viewPort = vp;
129         fsQuad = new Picture("filter full screen quad");
130 
131         Camera cam = vp.getCamera();
132 
133         //save view port diensions
134         left = cam.getViewPortLeft();
135         right = cam.getViewPortRight();
136         top = cam.getViewPortTop();
137         bottom = cam.getViewPortBottom();
138         originalWidth = cam.getWidth();
139         originalHeight = cam.getHeight();
140         //first call to reshape
141         reshape(vp, cam.getWidth(), cam.getHeight());
142     }
143 
144     /**
145      * init the given filter
146      * @param filter
147      * @param vp
148      */
initFilter(Filter filter, ViewPort vp)149     private void initFilter(Filter filter, ViewPort vp) {
150         filter.setProcessor(this);
151         if (filter.isRequiresDepthTexture()) {
152             if (!computeDepth && renderFrameBuffer != null) {
153                 depthTexture = new Texture2D(width, height, Format.Depth24);
154                 renderFrameBuffer.setDepthTexture(depthTexture);
155             }
156             computeDepth = true;
157             filter.init(assetManager, renderManager, vp, width, height);
158             filter.setDepthTexture(depthTexture);
159         } else {
160             filter.init(assetManager, renderManager, vp, width, height);
161         }
162     }
163 
164     /**
165      * renders a filter on a fullscreen quad
166      * @param r
167      * @param buff
168      * @param mat
169      */
renderProcessing(Renderer r, FrameBuffer buff, Material mat)170     private void renderProcessing(Renderer r, FrameBuffer buff, Material mat) {
171         if (buff == outputBuffer) {
172             fsQuad.setWidth(width);
173             fsQuad.setHeight(height);
174             filterCam.resize(originalWidth, originalHeight, true);
175             fsQuad.setPosition(left * originalWidth, bottom * originalHeight);
176         } else {
177             fsQuad.setWidth(buff.getWidth());
178             fsQuad.setHeight(buff.getHeight());
179             filterCam.resize(buff.getWidth(), buff.getHeight(), true);
180             fsQuad.setPosition(0, 0);
181         }
182 
183         if (mat.getAdditionalRenderState().isDepthWrite()) {
184             mat.getAdditionalRenderState().setDepthTest(false);
185             mat.getAdditionalRenderState().setDepthWrite(false);
186         }
187 
188         fsQuad.setMaterial(mat);
189         fsQuad.updateGeometricState();
190 
191         renderManager.setCamera(filterCam, true);
192         r.setFrameBuffer(buff);
193         r.clearBuffers(false, true, true);
194         renderManager.renderGeometry(fsQuad);
195 
196     }
197 
isInitialized()198     public boolean isInitialized() {
199         return viewPort != null;
200     }
201 
postQueue(RenderQueue rq)202     public void postQueue(RenderQueue rq) {
203 
204         for (Iterator<Filter> it = filters.iterator(); it.hasNext();) {
205             Filter filter = it.next();
206             if (filter.isEnabled()) {
207                 filter.postQueue(renderManager, viewPort);
208             }
209         }
210 
211     }
212     Picture pic = new Picture("debug");
213 
214     /**
215      * iterate through the filter list and renders filters
216      * @param r
217      * @param sceneFb
218      */
renderFilterChain(Renderer r, FrameBuffer sceneFb)219     private void renderFilterChain(Renderer r, FrameBuffer sceneFb) {
220         Texture2D tex = filterTexture;
221         FrameBuffer buff = sceneFb;
222         boolean msDepth = depthTexture != null && depthTexture.getImage().getMultiSamples() > 1;
223         for (int i = 0; i < filters.size(); i++) {
224             Filter filter = filters.get(i);
225             if (filter.isEnabled()) {
226                 if (filter.getPostRenderPasses() != null) {
227                     for (Iterator<Filter.Pass> it1 = filter.getPostRenderPasses().iterator(); it1.hasNext();) {
228                         Filter.Pass pass = it1.next();
229                         pass.beforeRender();
230                         if (pass.requiresSceneAsTexture()) {
231                             pass.getPassMaterial().setTexture("Texture", tex);
232                             if (tex.getImage().getMultiSamples() > 1) {
233                                 pass.getPassMaterial().setInt("NumSamples", tex.getImage().getMultiSamples());
234                             } else {
235                                 pass.getPassMaterial().clearParam("NumSamples");
236 
237                             }
238                         }
239                         if (pass.requiresDepthAsTexture()) {
240                             pass.getPassMaterial().setTexture("DepthTexture", depthTexture);
241                             if (msDepth) {
242                                 pass.getPassMaterial().setInt("NumSamplesDepth", depthTexture.getImage().getMultiSamples());
243                             } else {
244                                 pass.getPassMaterial().clearParam("NumSamplesDepth");
245                             }
246                         }
247                         renderProcessing(r, pass.getRenderFrameBuffer(), pass.getPassMaterial());
248                     }
249                 }
250 
251                 filter.postFrame(renderManager, viewPort, buff, sceneFb);
252 
253                 Material mat = filter.getMaterial();
254                 if (msDepth && filter.isRequiresDepthTexture()) {
255                     mat.setInt("NumSamplesDepth", depthTexture.getImage().getMultiSamples());
256                 }
257 
258                 if (filter.isRequiresSceneTexture()) {
259                     mat.setTexture("Texture", tex);
260                     if (tex.getImage().getMultiSamples() > 1) {
261                         mat.setInt("NumSamples", tex.getImage().getMultiSamples());
262                     } else {
263                         mat.clearParam("NumSamples");
264                     }
265                 }
266 
267                 buff = outputBuffer;
268                 if (i != lastFilterIndex) {
269                     buff = filter.getRenderFrameBuffer();
270                     tex = filter.getRenderedTexture();
271 
272                 }
273                 renderProcessing(r, buff, mat);
274             }
275         }
276     }
277 
postFrame(FrameBuffer out)278     public void postFrame(FrameBuffer out) {
279 
280         FrameBuffer sceneBuffer = renderFrameBuffer;
281         if (renderFrameBufferMS != null && !renderer.getCaps().contains(Caps.OpenGL31)) {
282             renderer.copyFrameBuffer(renderFrameBufferMS, renderFrameBuffer);
283         } else if (renderFrameBufferMS != null) {
284             sceneBuffer = renderFrameBufferMS;
285         }
286         renderFilterChain(renderer, sceneBuffer);
287         renderer.setFrameBuffer(outputBuffer);
288 
289         //viewport can be null if no filters are enabled
290         if (viewPort != null) {
291             renderManager.setCamera(viewPort.getCamera(), false);
292         }
293 
294     }
295 
preFrame(float tpf)296     public void preFrame(float tpf) {
297         if (filters.isEmpty() || lastFilterIndex == -1) {
298             //If the camera is initialized and there are no filter to render, the camera viewport is restored as it was
299             if (cameraInit) {
300                 viewPort.getCamera().resize(originalWidth, originalHeight, true);
301                 viewPort.getCamera().setViewPort(left, right, bottom, top);
302                 viewPort.setOutputFrameBuffer(outputBuffer);
303                 cameraInit = false;
304             }
305 
306         } else {
307             if (renderFrameBufferMS != null) {
308                 viewPort.setOutputFrameBuffer(renderFrameBufferMS);
309             } else {
310                 viewPort.setOutputFrameBuffer(renderFrameBuffer);
311             }
312             //init of the camera if it wasn't already
313             if (!cameraInit) {
314                 viewPort.getCamera().resize(width, height, true);
315                 viewPort.getCamera().setViewPort(0, 1, 0, 1);
316             }
317         }
318 
319         for (Iterator<Filter> it = filters.iterator(); it.hasNext();) {
320             Filter filter = it.next();
321             if (filter.isEnabled()) {
322                 filter.preFrame(tpf);
323             }
324         }
325 
326     }
327 
328     /**
329      * sets the filter to enabled or disabled
330      * @param filter
331      * @param enabled
332      */
setFilterState(Filter filter, boolean enabled)333     protected void setFilterState(Filter filter, boolean enabled) {
334         if (filters.contains(filter)) {
335             filter.enabled = enabled;
336             updateLastFilterIndex();
337         }
338     }
339 
340     /**
341      * compute the index of the last filter to render
342      */
updateLastFilterIndex()343     private void updateLastFilterIndex() {
344         lastFilterIndex = -1;
345         for (int i = filters.size() - 1; i >= 0 && lastFilterIndex == -1; i--) {
346             if (filters.get(i).isEnabled()) {
347                 lastFilterIndex = i;
348                 return;
349             }
350         }
351         if (lastFilterIndex == -1) {
352             cleanup();
353         }
354     }
355 
cleanup()356     public void cleanup() {
357         if (viewPort != null) {
358             //reseting the viewport camera viewport to its initial value
359             viewPort.getCamera().resize(originalWidth, originalHeight, true);
360             viewPort.getCamera().setViewPort(left, right, bottom, top);
361             viewPort.setOutputFrameBuffer(outputBuffer);
362             viewPort = null;
363             for (Filter filter : filters) {
364                 filter.cleanup(renderer);
365             }
366         }
367 
368     }
369 
reshape(ViewPort vp, int w, int h)370     public void reshape(ViewPort vp, int w, int h) {
371         //this has no effect at first init but is useful when resizing the canvas with multi views
372         Camera cam = vp.getCamera();
373         cam.setViewPort(left, right, bottom, top);
374         //resizing the camera to fit the new viewport and saving original dimensions
375         cam.resize(w, h, false);
376         left = cam.getViewPortLeft();
377         right = cam.getViewPortRight();
378         top = cam.getViewPortTop();
379         bottom = cam.getViewPortBottom();
380         originalWidth = w;
381         originalHeight = h;
382         cam.setViewPort(0, 1, 0, 1);
383 
384         //computing real dimension of the viewport and resizing he camera
385         width = (int) (w * (Math.abs(right - left)));
386         height = (int) (h * (Math.abs(bottom - top)));
387         width = Math.max(1, width);
388         height = Math.max(1, height);
389         cam.resize(width, height, false);
390         cameraInit = true;
391         computeDepth = false;
392 
393         if (renderFrameBuffer == null) {
394             outputBuffer = viewPort.getOutputFrameBuffer();
395         }
396 
397         Collection<Caps> caps = renderer.getCaps();
398 
399         //antialiasing on filters only supported in opengl 3 due to depth read problem
400         if (numSamples > 1 && caps.contains(Caps.FrameBufferMultisample)) {
401             renderFrameBufferMS = new FrameBuffer(width, height, numSamples);
402             if (caps.contains(Caps.OpenGL31)) {
403                 Texture2D msColor = new Texture2D(width, height, numSamples, Format.RGBA8);
404                 Texture2D msDepth = new Texture2D(width, height, numSamples, Format.Depth);
405                 renderFrameBufferMS.setDepthTexture(msDepth);
406                 renderFrameBufferMS.setColorTexture(msColor);
407                 filterTexture = msColor;
408                 depthTexture = msDepth;
409             } else {
410                 renderFrameBufferMS.setDepthBuffer(Format.Depth);
411                 renderFrameBufferMS.setColorBuffer(Format.RGBA8);
412             }
413         }
414 
415         if (numSamples <= 1 || !caps.contains(Caps.OpenGL31)) {
416             renderFrameBuffer = new FrameBuffer(width, height, 1);
417             renderFrameBuffer.setDepthBuffer(Format.Depth);
418             filterTexture = new Texture2D(width, height, Format.RGBA8);
419             renderFrameBuffer.setColorTexture(filterTexture);
420         }
421 
422         for (Iterator<Filter> it = filters.iterator(); it.hasNext();) {
423             Filter filter = it.next();
424             initFilter(filter, vp);
425         }
426 
427         if (renderFrameBufferMS != null) {
428             viewPort.setOutputFrameBuffer(renderFrameBufferMS);
429         } else {
430             viewPort.setOutputFrameBuffer(renderFrameBuffer);
431         }
432     }
433 
434     /**
435      * return the number of samples for antialiasing
436      * @return numSamples
437      */
getNumSamples()438     public int getNumSamples() {
439         return numSamples;
440     }
441 
442     /**
443      *
444      * Removes all the filters from this processor
445      */
removeAllFilters()446     public void removeAllFilters() {
447         filters.clear();
448         updateLastFilterIndex();
449     }
450 
451     /**
452      * Sets the number of samples for antialiasing
453      * @param numSamples the number of Samples
454      */
setNumSamples(int numSamples)455     public void setNumSamples(int numSamples) {
456         if (numSamples <= 0) {
457             throw new IllegalArgumentException("numSamples must be > 0");
458         }
459 
460         this.numSamples = numSamples;
461     }
462 
463     /**
464      * Sets the asset manager for this processor
465      * @param assetManager
466      */
setAssetManager(AssetManager assetManager)467     public void setAssetManager(AssetManager assetManager) {
468         this.assetManager = assetManager;
469     }
470 
write(JmeExporter ex)471     public void write(JmeExporter ex) throws IOException {
472         OutputCapsule oc = ex.getCapsule(this);
473         oc.write(numSamples, "numSamples", 0);
474         oc.writeSavableArrayList((ArrayList) filters, "filters", null);
475     }
476 
read(JmeImporter im)477     public void read(JmeImporter im) throws IOException {
478         InputCapsule ic = im.getCapsule(this);
479         numSamples = ic.readInt("numSamples", 0);
480         filters = ic.readSavableArrayList("filters", null);
481         for (Filter filter : filters) {
482             filter.setProcessor(this);
483             setFilterState(filter, filter.isEnabled());
484         }
485         assetManager = im.getAssetManager();
486     }
487 
488     /**
489      * For internal use only<br>
490      * returns the depth texture of the scene
491      * @return
492      */
getDepthTexture()493     public Texture2D getDepthTexture() {
494         return depthTexture;
495     }
496 
497     /**
498      * For internal use only<br>
499      * returns the rendered texture of the scene
500      * @return
501      */
getFilterTexture()502     public Texture2D getFilterTexture() {
503         return filterTexture;
504     }
505 }
506