Home | History | Annotate | Download | only in post
      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 Rmy 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      */
     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      */
     93     public FilterPostProcessor() {
     94     }
     95 
     96     /**
     97      * Adds a filter to the filters list<br>
     98      * @param filter the filter to add
     99      */
    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      */
    115     public void removeFilter(Filter filter) {
    116         filters.remove(filter);
    117         filter.cleanup(renderer);
    118         updateLastFilterIndex();
    119     }
    120 
    121     public Iterator<Filter> getFilterIterator() {
    122         return filters.iterator();
    123     }
    124 
    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      */
    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      */
    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 
    198     public boolean isInitialized() {
    199         return viewPort != null;
    200     }
    201 
    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      */
    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 
    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 
    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      */
    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      */
    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 
    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 
    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      */
    438     public int getNumSamples() {
    439         return numSamples;
    440     }
    441 
    442     /**
    443      *
    444      * Removes all the filters from this processor
    445      */
    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      */
    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      */
    467     public void setAssetManager(AssetManager assetManager) {
    468         this.assetManager = assetManager;
    469     }
    470 
    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 
    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      */
    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      */
    502     public Texture2D getFilterTexture() {
    503         return filterTexture;
    504     }
    505 }
    506