/*
* Copyright 2016 MovingBlocks
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.terasology.rendering.opengl;
import com.google.common.base.Preconditions;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import java.util.Map;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.terasology.assets.ResourceUrn;
/**
* The FrameBuffersManager generates and maintains a number of Frame Buffer Objects (FBOs) used throughout the
* rendering engine.
* <p>
* In most instances Frame Buffers can be thought of as 2D arrays of pixels in GPU memory: shaders write to them or
* read from them. Some buffers are static and never change for the lifetime of the manager. Some buffers are dynamic:
* they get disposed and regenerated, i.e. in case the display resolution changes. Some buffers hold intermediate
* steps of the rendering process and the content of one buffer, "sceneFinal", is eventually sent to the display.
* <br/>
* At this stage no buffer can be added or deleted: the list of buffers and their characteristics is hardcoded.
* <br/>
* The existing set of public methods is primarily intended to allow communication between this manager and other parts
* of the rendering engine, most notably the PostProcessor and the GraphicState instances and the shaders system.
* <br/>
* An important exception is the takeScreenshot() method which prompts the renderer to eventually (not immediately)
* redirect its output to a file. This is the only public method that is intended to be used from outside the
* rendering engine.
* <p>
* Default FBOs:
* sceneOpaque: Primary FBO: most visual information eventually ends up here
* sceneOpaquePingPong: The sceneOpaque FBOs are swapped every frame, to use one for reading and the other for writing
* Notice that these two FBOs hold a number of buffers, for color, depth, normals, etc.
* sceneSkyBand0: two buffers used to generate a depth cue: things in the distance fades into the atmosphere's color.
* sceneSkyBand1:
* sceneReflectiveRefractive: used to render reflective and refractive surfaces, most obvious case being the water surface
* sceneReflected: the water surface displays a reflected version of the scene. This version is stored here.
* outline: greyscale depth-based rendering of object outlines
* ssao: greyscale screen-space ambient occlusion rendering
* ssaoBlurred: greyscale screen-space ambient occlusion rendering - blurred version
* scenePrePost: intermediate step, combining a number of renderings made available so far
* lightShafts: light shafts rendering
* sceneHighPass: a number of buffers to create the bloom effect
* sceneBloom0:
* sceneBloom1:
* sceneBloom2:
* sceneBlur0: a pair of buffers holding blurred versions of the rendered scene,
* sceneBlur1: also used for the bloom effect, but not only.
* ocUndistorted: if OculusRift support is enabled this buffer holds the side-by-side views
* for each eye, with no lens distortion applied.
* sceneFinal: the content of this buffer is eventually shown on the display or sent to a file if taking a screenshot
*/
/**
* TODO: fix above, add javadocs
*/
public abstract class AbstractFBOsManager implements BaseFBOsManager {
protected static final Logger logger = LoggerFactory.getLogger(AbstractFBOsManager.class);
protected Map<ResourceUrn, FBOConfig> fboConfigs = Maps.newHashMap();
protected Map<ResourceUrn, FBO> fboLookup = Maps.newHashMap();
protected Map<ResourceUrn, Integer> fboUsageCountMap = Maps.newHashMap();
private Set<FBOManagerSubscriber> fboManagerSubscribers = Sets.newHashSet();
protected FBO generateWithDimensions(FBOConfig fboConfig, FBO.Dimensions dimensions) {
fboConfig.setDimensions(dimensions);
FBO fbo = FBO.create(fboConfig);
// At this stage it's unclear what should be done in this circumstances as I (manu3d) do not know what
// the effects of using an incomplete FrameBuffer are. Throw an exception? Live with visual artifacts?
if (fbo.getStatus() == FBO.Status.INCOMPLETE) {
logger.error("FBO " + fboConfig.getName() + " is incomplete. Look earlier in the log for details.");
} else if (fbo.getStatus() == FBO.Status.UNEXPECTED) {
logger.error("FBO " + fboConfig.getName() + " has generated an unexpected status code. Look earlier in the log for details.");
}
fboLookup.put(fboConfig.getName(), fbo);
fboConfigs.put(fboConfig.getName(), fboConfig);
return fbo;
}
protected void notifySubscribers() {
for (FBOManagerSubscriber subscriber : fboManagerSubscribers) {
subscriber.update();
}
}
protected void retain(ResourceUrn resourceUrn) {
if (fboUsageCountMap.containsKey(resourceUrn)) {
int usageCount = fboUsageCountMap.get(resourceUrn) + 1;
fboUsageCountMap.put(resourceUrn, usageCount);
} else {
fboUsageCountMap.put(resourceUrn, 1);
}
}
/**
* TODO: add javadoc
*
* @param fboName
*/
@Override
public void release(ResourceUrn fboName) {
Preconditions.checkArgument(fboUsageCountMap.containsKey(fboName), "The given fbo is not used.");
if (fboUsageCountMap.get(fboName) != 1) {
int usageCount = fboUsageCountMap.get(fboName);
fboUsageCountMap.put(fboName, usageCount - 1);
} else {
get(fboName).dispose();
fboLookup.remove(fboName);
if (fboConfigs.containsKey(fboName)) {
fboConfigs.remove(fboName);
}
}
}
/**
* Binds the color texture of the FBO with the given name and returns true.
*
* If no FBO is associated with the given name, false is returned and an error is logged.
*
* @param fboName the urn of an FBO
* @return True if an FBO associated with the given name exists. False otherwise.
*/
public boolean bindFboColorTexture(ResourceUrn fboName) {
FBO fbo = fboLookup.get(fboName);
if (fbo != null) {
fbo.bindTexture();
return true;
}
logger.error("Failed to bind FBO color texture since the requested " + fboName + " FBO could not be found!");
return false;
}
/**
* Binds the depth texture of the FBO with the given name and returns true.
*
* If no FBO is associated with the given name, false is returned and an error is logged.
*
* @param fboName the urn of an FBO
* @return True if an FBO associated with the given name exists. False otherwise.
*/
public boolean bindFboDepthTexture(ResourceUrn fboName) {
FBO fbo = fboLookup.get(fboName);
if (fbo != null) {
fbo.bindDepthTexture();
return true;
}
logger.error("Failed to bind FBO depth texture since the requested " + fboName + " FBO could not be found!");
return false;
}
/**
* Binds the normals texture of the FBO with the given name and returns true.
*
* If no FBO is associated with the given name, false is returned and an error is logged.
*
* @param fboName the urn of an FBO
* @return True if an FBO associated with the given name exists. False otherwise.
*/
@Override
public boolean bindFboNormalsTexture(ResourceUrn fboName) {
FBO fbo = fboLookup.get(fboName);
if (fbo != null) {
fbo.bindNormalsTexture();
return true;
}
logger.error("Failed to bind FBO normals texture since the requested " + fboName + " FBO could not be found!");
return false;
}
/**
* Binds the light buffer texture of the FBO with the given name and returns true.
*
* If no FBO is associated with the given name, false is returned and an error is logged.
*
* @param fboName the urn of an FBO
* @return True if an FBO associated with the given name exists. False otherwise.
*/
@Override
public boolean bindFboLightBufferTexture(ResourceUrn fboName) {
FBO fbo = fboLookup.get(fboName);
if (fbo != null) {
fbo.bindLightBufferTexture();
return true;
}
logger.error("Failed to bind FBO light buffer texture since the requested " + fboName + " FBO could not be found!");
return false;
}
/**
* Returns an FBO given its name.
*
* If no FBO maps to the given name, null is returned and an error is logged.
*
* @param fboName
* @return an FBO or null
*/
@Override
public FBO get(ResourceUrn fboName) {
FBO fbo = fboLookup.get(fboName);
if (fbo == null) {
logger.error("Failed to retrieve FBO '" + fboName + "'!");
}
return fbo;
}
/**
* TODO: add javadocs
*
* @param subscriber
*/
@Override
public boolean subscribe(FBOManagerSubscriber subscriber) {
return fboManagerSubscribers.add(subscriber);
}
/**
* TODO: add javadocs
*
* @param subscriber
* @return
*/
@Override
public boolean unsubscribe(FBOManagerSubscriber subscriber) {
return fboManagerSubscribers.remove(subscriber);
}
}