/*
* Copyright 2017 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.dag.stateChanges;
import org.terasology.assets.ResourceUrn;
import org.terasology.rendering.assets.material.Material;
import org.terasology.rendering.dag.RenderPipelineTask;
import org.terasology.rendering.dag.StateChange;
import org.terasology.rendering.opengl.BaseFBOsManager;
import org.terasology.rendering.opengl.DefaultDynamicFBOs;
import org.terasology.rendering.opengl.FBO;
import org.terasology.rendering.opengl.FBOManagerSubscriber;
import java.util.Objects;
import java.util.logging.Logger;
import static org.lwjgl.opengl.GL11.GL_TEXTURE_2D;
import static org.lwjgl.opengl.GL11.glBindTexture;
import static org.lwjgl.opengl.GL13.GL_TEXTURE0;
import static org.lwjgl.opengl.GL13.glActiveTexture;
import static org.terasology.rendering.dag.AbstractNode.getMaterial;
/**
* This state change implementation sets a texture attached to an FBO as the input for a material.
*/
public class SetInputTextureFromFBO implements StateChange, FBOManagerSubscriber {
private static final Logger logger = Logger.getLogger("SetInputTextureFromFBO");
public enum FboTexturesTypes {
ColorTexture,
DepthRenderBuffer,
DepthTexture,
NormalsTexture,
LightAccumulationTexture,
}
private int textureSlot;
private ResourceUrn fboURN;
private FBO inputFbo;
private DefaultDynamicFBOs defaultFBO; // TODO: remove/change references to default FBOs when they are no longer implemented as enums.
private FboTexturesTypes textureType;
private BaseFBOsManager fbosManager;
private ResourceUrn materialURN;
private String parameterName;
private SetInputTextureFromFBO defaultInstance;
private Task task;
/**
* Constructs and returns an instance of this class, according to the parameters provided.
*
* Instances of this class can be added to the list of desired state changes of a node, to set an FBO-attached
* texture as input to the enabled material.
*
* @param textureSlot an integer representing the number to add to GL_TEXTURE0 to identify a texture unit on the GPU.
* @param fboURN an URN identifying an FBO.
* @param textureType one of the types available through the FboTextureType enum.
* @param fbosManager the BaseFBOsManager instance that will send change notifications via the update() method of this class.
* @param materialURN an URN identifying a Material instance.
* @param parameterName the name of a variable in the shader program used to sample the texture.
*/
public SetInputTextureFromFBO(int textureSlot, ResourceUrn fboURN, FboTexturesTypes textureType, BaseFBOsManager fbosManager,
ResourceUrn materialURN, String parameterName) {
this.textureSlot = textureSlot;
this.textureType = textureType;
this.fboURN = fboURN;
this.inputFbo = fbosManager.get(fboURN);
this.materialURN = materialURN;
this.parameterName = parameterName;
this.fbosManager = fbosManager;
fbosManager.subscribe(this);
}
// TODO: either take advantage of this constructor or remove it. Note: it will probably be removed.
/**
* Warning: seemingly disfunctional - deprecated for the time being.
*/
public SetInputTextureFromFBO(int textureSlot, DefaultDynamicFBOs defaultDynamicFbo, FboTexturesTypes textureType, BaseFBOsManager fbosManager,
ResourceUrn materialURN, String parameterName) {
this.textureSlot = textureSlot;
this.textureType = textureType;
this.defaultFBO = defaultDynamicFbo;
this.inputFbo = defaultDynamicFbo.getFbo();
this.fboURN = defaultDynamicFbo.getName();
this.inputFbo = fbosManager.get(fboURN);
this.materialURN = materialURN;
this.parameterName = parameterName;
this.fbosManager = fbosManager;
fbosManager.subscribe(this);
}
private SetInputTextureFromFBO(int textureSlot, ResourceUrn materialURN, String parameterName) {
this.textureSlot = textureSlot;
this.materialURN = materialURN;
this.parameterName = parameterName;
defaultInstance = this;
}
@Override
public RenderPipelineTask generateTask() {
if (task == null) {
if (this != defaultInstance) {
task = new Task(this.textureSlot, fetchTextureId(), this.materialURN, this.parameterName);
} else {
task = new Task(this.textureSlot, 0, this.materialURN, this.parameterName);
}
}
return task;
}
@Override
public StateChange getDefaultInstance() {
if (defaultInstance == null) {
defaultInstance = new SetInputTextureFromFBO(this.textureSlot, materialURN, this.parameterName);
}
return defaultInstance;
}
@Override
public boolean isTheDefaultInstance() {
return this.equals(defaultInstance);
}
private int fetchTextureId() {
// TODO: make checks to verify the FBOs has the requested buffer;
if (inputFbo != null) {
switch (textureType) {
case ColorTexture:
return inputFbo.colorBufferTextureId;
case DepthRenderBuffer:
return inputFbo.depthStencilRboId;
case DepthTexture:
return inputFbo.depthStencilTextureId;
case NormalsTexture:
return inputFbo.normalsBufferTextureId;
case LightAccumulationTexture:
return inputFbo.lightBufferTextureId;
}
} else {
logger.warning("FBOs manager has no record of an FBO named " + this.fboURN.toString());
}
return 0;
}
/**
* Normally called by the FBO manager provided on construction, when the FBOs are regenerated.
*
* This method refreshes the task's reference to the FBO attachment, so that they are always up to date.
*/
@Override
public void update() {
if (defaultFBO == null) {
inputFbo = fbosManager.get(fboURN);
} else {
this.inputFbo = defaultFBO.getFbo();
this.fboURN = defaultFBO.getName();
}
// If a node taking advantage of this state change is disabled when a game is started, task is null.
// This is due to the task list generator not having yet called the generateTask() method.
if (task != null) {
task.setTextureId(fetchTextureId());
}
}
@Override
public String toString() {
if (this != defaultInstance) {
return String.format("%30s: slot %s, fbo %s, textureType %s, fboManager %s, material %s, parameter '%s'", this.getClass().getSimpleName(),
textureSlot, fboURN.toString(), textureType.name(), fbosManager.toString(), materialURN.toString(), parameterName);
} else {
return String.format("%30s: slot %s, textureId 0, material %s, parameter '%s'", this.getClass().getSimpleName(),
textureSlot, materialURN.toString(), parameterName);
}
}
@Override
public int hashCode() {
return Objects.hash(textureSlot, fboURN, textureType, fbosManager, materialURN, parameterName);
}
@Override
public boolean equals(Object other) {
return (other instanceof SetInputTextureFromFBO)
&& this.textureSlot == ((SetInputTextureFromFBO) other).textureSlot
&& this.fboURN == ((SetInputTextureFromFBO) other).fboURN
&& this.textureType == ((SetInputTextureFromFBO) other).textureType
&& this.fbosManager == ((SetInputTextureFromFBO) other).fbosManager
&& this.materialURN.equals(((SetInputTextureFromFBO) other).materialURN)
&& this.parameterName.equals(((SetInputTextureFromFBO) other).parameterName);
}
/**
* Instances of this class do the actual work of activating a given texture unit,
* binding the appropriate texture to it and configuring a material to use it as input.
*/
protected class Task implements RenderPipelineTask {
private int textureSlot;
private int textureUnit;
private int textureId;
private Material material;
private String parameterName;
/**
* Constructs an instance of this inner class, according to the given parameters.
*
* @param textureSlot an integer indirectly identifying a texture unit on the GPU (textureUnit = GL_TEXTURE0 + textureSlot).
* @param textureId the opengl id of a texture, usually obtained via glGenTextures().
* @param materialURN an URN identifying a material.
* @param parameterName the name of a variable in the shader program used to sample the texture.
*/
protected Task(int textureSlot, int textureId, ResourceUrn materialURN, String parameterName) {
this.textureSlot = textureSlot;
this.textureUnit = GL_TEXTURE0 + textureSlot; // this way textureSlot can be defined with small, positive integers.
this.textureId = textureId;
this.material = getMaterial(materialURN);
this.parameterName = parameterName;
}
/**
* This method is used when FBOs have changed and the id of the texture attached to one needs refreshing.
*
* @param textureId an integer identifying a texture buffer on the GPU, originally obtained via glGenTextures().
*/
protected void setTextureId(int textureId) {
this.textureId = textureId;
}
/**
* Activates the texture unit GL_TEXTURE0 + textureSlot, binds the GL_TEXTURE_2D identified by textureId to it
* and sets the material provided on construction to sample the texture via the parameterName also provided on
* construction.
*/
@Override
public void execute() {
glActiveTexture(textureUnit);
glBindTexture(GL_TEXTURE_2D, textureId);
material.setInt(parameterName, textureUnit, true);
}
@Override
public String toString() {
return String.format("%30s: textureSlot %s (unit %s), textureId %s, material %s, parameter %s", this.getClass().getSimpleName(),
textureSlot, textureUnit, textureId, material.getUrn(), parameterName);
}
}
}