org.terasology.rendering.opengl.GraphicState.java Source code

Java tutorial

Introduction

Here is the source code for org.terasology.rendering.opengl.GraphicState.java

Source

/*
 * Copyright 2015 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 org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL20;
import org.terasology.math.geom.Vector3f;
import org.terasology.rendering.opengl.FBO.Dimensions;

import java.nio.IntBuffer;

import static org.lwjgl.opengl.EXTFramebufferObject.GL_COLOR_ATTACHMENT0_EXT;
import static org.lwjgl.opengl.EXTFramebufferObject.GL_FRAMEBUFFER_EXT;
import static org.lwjgl.opengl.EXTFramebufferObject.glBindFramebufferEXT;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL11.GL_CULL_FACE;
import static org.lwjgl.opengl.GL11.GL_LEQUAL;
import static org.lwjgl.opengl.GL20.glStencilOpSeparate;

/**
 * The GraphicState class aggregates a number of methods setting the OpenGL state
 * before and after rendering passes.
 *
 * In many circumstances these methods do little more than binding the appropriate
 * Frame Buffer Object (FBO) and texture buffers so the OpenGL implementation and
 * the shaders know where to read from and write to. In some cases more involved
 * OpenGL state changes occur, i.e. in the lighting-related methods.
 *
 * Also, most methods come in pairs named preRenderSetup*() and postRenderCleanup*(),
 * reflecting their use before or after an actual render takes place. Usually the first
 * method changes an unspecified default state while the second method reinstates it.
 *
 * A number of FBO references are kept up to date through the refreshDynamicFBOs() and
 * setSceneShadowMap() methods. At this stage the LwjglRenderingProcess class is tasked
 * with running these methods whenever its FBOs change.
 */
// TODO: update comment when the BuffersManager comes online.
public class GraphicState {
    // As this class pretty much always deals with OpenGL states, it did occur to me
    // that it might be better called OpenGLState or something along that line. I
    // eventually decided for GraphicState as it resides in the rendering.opengl
    // package anyway and rendering.opengl.OpenGLState felt cumbersome. --emanuele3d

    private LwjglRenderingProcess renderingProcess;
    private Dimensions fullScale;
    private Buffers buffers = new Buffers();

    /**
     * Graphic State constructor.
     *
     * This constructor only sets the internal reference to the rendering process. It does not obtain
     * all the references to FBOs nor it initializes the other instance it requires to operate correctly.
     * As such, it relies on the caller to make sure that at the appropriate time (when the buffers are
     * available) refreshDynamicFBOs() and setSceneShadowMap() are called and the associated internal
     * FBO references are initialized.
     *
     * @param renderingProcess An instance of the LwjglRenderingProcess class, used to obtain references to its FBOs.
     */
    public GraphicState(LwjglRenderingProcess renderingProcess) {
        // a reference to the renderingProcess is not strictly necessary, as it is used only
        // in refreshDynamicFBOs() and it could be passed as argument to it. We do it this
        // way however to maintain similarity with the way the PostProcessor will work.
        // TODO: update the comment above when the PostProcessor is in place.
        this.renderingProcess = renderingProcess;
    }

    /**
     * This method disposes of a GraphicState instance by simply nulling a number of internal references.
     *
     * It is probably not strictly necessary as the Garbage Collection mechanism should be able to dispose
     * instances of this class without much trouble once they are out of scope. But it is probably good
     * form to include and use a dispose() method, to make it explicit when an instance will no longer be
     * useful.
     */
    public void dispose() {
        renderingProcess = null;
        fullScale = null;
        buffers = null;
    }

    /**
     * Used to initialize and eventually refresh the internal references to FBOs primarily
     * held by the LwjglRenderingProcess instance.
     *
     * Instances of the GraphicState class cannot operate unless this method has been called
     * at least once, the FBOs retrieved through it not null. It then needs to be called again
     * every time the LwjglRenderingProcess instance changes its FBOs. This occurs whenever
     * the display resolution changes or when a screenshot is taken with a resolution that
     * is different from that of the display.
     */
    public void refreshDynamicFBOs() {
        buffers.sceneOpaque = renderingProcess.getFBO("sceneOpaque");
        buffers.sceneReflectiveRefractive = renderingProcess.getFBO("sceneReflectiveRefractive");
        buffers.sceneReflected = renderingProcess.getFBO("sceneReflected");
        fullScale = buffers.sceneOpaque.dimensions();
    }

    /**
     * Used to initialize and update the internal reference to the Shadow Map FBO.
     *
     * Gets called every time the LwjglRenderingProcess instance changes the ShadowMap FBO.
     * This will occur whenever the ShadowMap resolution is changed, i.e. via the rendering
     * settings.
     *
     * @param newShadowMap
     */
    public void setSceneShadowMap(FBO newShadowMap) {
        buffers.sceneShadowMap = newShadowMap;
    }

    /**
     * Initial clearing of a couple of important Frame Buffers. Then binds back the Display.
     */
    // It's unclear why these buffers need to be cleared while all the others don't...
    public void initialClearing() {
        buffers.sceneOpaque.bind();
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
        buffers.sceneReflectiveRefractive.bind();
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        bindDisplay();
    }

    /**
     * Readies the state to render the Opaque scene.
     *
     * The opaque scene includes a number of successive passes including the backdrop (i.e. the skysphere),
     * the landscape (chunks/blocks), additional objects associated with the landscape (i.e. other players/fauna),
     * overlays (i.e. the cursor-cube) and the geometry associated with the first person view, i.e. the objects
     * held in hand.
     */
    public void preRenderSetupSceneOpaque() {
        buffers.sceneOpaque.bind();
        setRenderBufferMask(buffers.sceneOpaque, true, true, true);
    }

    /**
     * Resets the state after the rendering of the Opaque scene.
     *
     * See preRenderSetupSceneOpaque() for more details about
     * the Opaque scene rendering.
     */
    public void postRenderCleanupSceneOpaque() {
        setRenderBufferMask(buffers.sceneOpaque, true, true, true); // TODO: probably redundant - verify
        bindDisplay();
    }

    /**
     * Sets the state to render in wireframe.
     *
     * @param wireframeIsEnabledInRenderingDebugConfig If True enables wireframe rendering. False, does nothing.
     */
    public void enableWireframeIf(boolean wireframeIsEnabledInRenderingDebugConfig) {
        if (wireframeIsEnabledInRenderingDebugConfig) {
            GL11.glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
        }
    }

    /**
     * Disables wireframe rendering. Used together with enableWireFrameIf().
     *
     * @param wireframeIsEnabledInRenderingDebugConfig If True disables wireframe rendering. False, does nothing.
     */
    public void disableWireframeIf(boolean wireframeIsEnabledInRenderingDebugConfig) {
        if (wireframeIsEnabledInRenderingDebugConfig) {
            GL11.glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
        }
    }

    /**
     * Sets the state for the rendering of the Shadow Map.
     *
     * Currently a single shadow map is produced around the player. The shadows are cast
     * by the main directional light, which at this stage is either the sun or the moon.
     */
    public void preRenderSetupSceneShadowMap() {
        buffers.sceneShadowMap.bind();

        setViewportToSizeOf(buffers.sceneShadowMap);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        GL11.glDisable(GL_CULL_FACE);
    }

    /**
     * Resets the state after the rendering of the Shadow Map.
     * See preRenderSetupSceneShadowMap() for some more information.
     */
    public void postRenderCleanupSceneShadowMap() {
        GL11.glEnable(GL_CULL_FACE);
        bindDisplay();
        setViewportToWholeDisplay();
    }

    /**
     * Sets the state for the rendering of the Reflected Scene.
     *
     * This is effectively an inverted rendering of the backdrop and
     * the opaque chunks of the landscape, to be used in water reflections.
     */
    public void preRenderSetupReflectedScene() {
        buffers.sceneReflected.bind();

        setViewportToSizeOf(buffers.sceneReflected);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        GL11.glCullFace(GL11.GL_FRONT);
    }

    /**
     * Resets the state after the rendering of the Reflected Scene.
     * See preRenderSetupReflectedScene() for some more information.
     */
    public void postRenderCleanupReflectedScene() {
        GL11.glCullFace(GL11.GL_BACK);
        bindDisplay();
        setViewportToWholeDisplay();
    }

    /**
     * Sets the state to render the Backdrop. At this stage the backdrop is the SkySphere
     * plus the SkyBands passes.
     *
     * The backdrop is the only rendering that has three state-changing methods.
     * This is due to the SkySphere requiring a state and the SkyBands requiring a slightly
     * different one.
     */
    public void preRenderSetupBackdrop() {
        setRenderBufferMask(buffers.sceneOpaque, true, false, false);
    }

    /**
     * Sets the state to generate the SkyBands.
     *
     * See preRenderSetupBackdrop() for some more information.
     */
    public void midRenderChangesBackdrop() {
        setRenderBufferMask(buffers.sceneOpaque, true, true, true);
    }

    /**
     * Resets the state after the rendering of the Backdrop.
     *
     * See preRenderSetupBackdrop() for some more information.
     */
    public void postRenderCleanupBackdrop() {
        buffers.sceneOpaque.bind();
    }

    /**
     * Sets the state to render the First Person View.
     *
     * This generally comprises the objects held in hand, i.e. a pick, an axe, a torch and so on.
     */
    public void preRenderSetupFirstPerson() {
        GL11.glPushMatrix();
        GL11.glLoadIdentity();
        GL11.glDepthFunc(GL11.GL_ALWAYS);
    }

    /**
     * Resets the state after the render of the First Person View.
     *
     * See preRenderSetupFirstPerson() for some more information.
     */
    public void postRenderClenaupFirstPerson() {
        GL11.glDepthFunc(GL_LEQUAL);
        GL11.glPopMatrix();
    }

    // TODO: figure how lighting works and what this does
    public void preRenderSetupLightGeometryStencil() {
        buffers.sceneOpaque.bind();
        setRenderBufferMask(buffers.sceneOpaque, false, false, false);
        glDepthMask(false);

        glClear(GL_STENCIL_BUFFER_BIT);

        glCullFace(GL_FRONT);
        glDisable(GL_CULL_FACE);

        glEnable(GL_STENCIL_TEST);
        glStencilFunc(GL_ALWAYS, 0, 0);

        glStencilOpSeparate(GL_BACK, GL_KEEP, GL_INCR, GL_KEEP);
        glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_DECR, GL_KEEP);
    }

    // TODO: figure how lighting works and what this does
    public void postRenderCleanupLightGeometryStencil() {
        setRenderBufferMask(buffers.sceneOpaque, true, true, true);
        bindDisplay();
    }

    // TODO: figure how lighting works and what this does
    public void preRenderSetupLightGeometry() {
        buffers.sceneOpaque.bind();

        // Only write to the light buffer
        setRenderBufferMask(buffers.sceneOpaque, false, false, true);

        glStencilFunc(GL_NOTEQUAL, 0, 0xFF);

        glDepthMask(true);
        glDisable(GL_DEPTH_TEST);

        glEnable(GL_BLEND);
        glBlendFunc(GL_ONE, GL_ONE);

        glEnable(GL_CULL_FACE);
        glCullFace(GL_FRONT);
    }

    // TODO: figure how lighting works and what this does
    public void postRenderCleanupLightGeometry() {
        glDisable(GL_STENCIL_TEST);
        glCullFace(GL_BACK);

        bindDisplay();
    }

    // TODO: figure how lighting works and what this does
    public void preRenderSetupDirectionalLights() {
        buffers.sceneOpaque.bind();
    }

    // TODO: figure how lighting works and what this does
    public void postRenderCleanupDirectionalLights() {
        glDisable(GL_BLEND);
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

        glEnable(GL_DEPTH_TEST);

        setRenderBufferMask(buffers.sceneOpaque, true, true, true);
        bindDisplay();
    }

    /**
     * Sets the state for the rendering of the reflective/refractive features of the scene.
     *
     * At this stage this is the surface of water bodies, reflecting the sky and (if enabled)
     * the surrounding landscape, and refracting the underwater scenery.
     *
     * If the isHeadUnderWater argument is set to True, the state is further modified to
     * accommodate the rendering of the water surface from an underwater point of view.
     *
     * @param isHeadUnderWater Set to True if the point of view is underwater, to render the water surface correctly.
     */
    public void preRenderSetupSceneReflectiveRefractive(boolean isHeadUnderWater) {
        buffers.sceneReflectiveRefractive.bind();

        // Make sure the water surface is rendered if the player is underwater.
        if (isHeadUnderWater) {
            GL11.glDisable(GL11.GL_CULL_FACE);
        }
    }

    /**
     * Resets the state after the rendering of the reflective/refractive features of the scene.
     *
     * See preRenderSetupSceneReflectiveRefractive() for additional information.
     *
     * @param isHeadUnderWater Set to True if the point of view is underwater, for some additional resetting.
     */
    public void postRenderCleanupSceneReflectiveRefractive(boolean isHeadUnderWater) {
        if (isHeadUnderWater) {
            GL11.glEnable(GL11.GL_CULL_FACE);
        }

        bindDisplay();
    }

    /**
     * Sets the state for the rendering of objects or portions of objects having some degree of transparency.
     *
     * Generally speaking objects drawn with this state will have their color blended with the background
     * color, depending on their opacity. I.e. a 25% opaque foreground object will provide 25% of its
     * color while the background will provide the remaining 75%. The sum of the two RGBA vectors gets
     * written onto the output buffer.
     *
     * Important note: this method disables writing to the Depth Buffer. This is why filters relying on
     * depth information (i.e. DoF) have problems with transparent objects: the depth of their pixels is
     * found to be that of the background. This is an unresolved (unresolv-able?) issue that would only
     * be reversed, not eliminated, by re-enabling writing to the Depth Buffer.
     */
    public void preRenderSetupSimpleBlendMaterials() {
        buffers.sceneOpaque.bind();
        setRenderBufferMask(buffers.sceneOpaque, true, true, true);

        GL11.glEnable(GL_BLEND);
        GL11.glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // (*)
        GL11.glDepthMask(false);

        // (*) In this context SRC is Foreground. This effectively says:
        // Resulting RGB = ForegroundRGB * ForegroundAlpha + BackgroundRGB * (1 - ForegroundAlpha)
        // Which might still look complicated, but it's actually the most typical alpha-driven composite.
        // A neat tool to play with this settings can be found here: http://www.andersriggelsen.dk/glblendfunc.php
    }

    /**
     * Resets the state after the rendering of semi-opaque/semi-transparent objects.
     *
     * See preRenderSetupSimpleBlendMaterials() for additional information.
     */
    public void postRenderCleanupSimpleBlendMaterials() {
        GL11.glDisable(GL_BLEND);
        GL11.glDepthMask(true);

        setRenderBufferMask(buffers.sceneOpaque, true, true, true); // TODO: review - this might be redundant.
        bindDisplay();
    }

    /**
     * Sets the state prior to the rendering of a chunk.
     *
     * In practice this just positions the chunk appropriately, relative to the camera.
     *
     * @param chunkPositionRelativeToCamera Effectively: chunkCoordinates * chunkDimensions - cameraCoordinate
     */
    public void preRenderSetupChunk(Vector3f chunkPositionRelativeToCamera) {
        GL11.glPushMatrix();
        GL11.glTranslatef(chunkPositionRelativeToCamera.x, chunkPositionRelativeToCamera.y,
                chunkPositionRelativeToCamera.z);
    }

    /**
     * Resets the state after the rendering of a chunk.
     *
     * See preRenderSetupChunk() for additional information.
     */
    public void postRenderCleanupChunk() {
        GL11.glPopMatrix();
    }

    /**
     * Once an FBO is bound, opengl commands will act on it, i.e. by drawing on it.
     * Meanwhile shaders might output not just colors but additional per-pixel data. This method establishes on which
     * of an FBOs attachments, subsequent opengl commands and shaders will draw on.
     *
     * @param fbo The FBO holding the attachments to be set or unset for drawing.
     * @param color If True the color buffer is set as drawable. If false subsequent commands and shaders won't be able to draw on it.
     * @param normal If True the normal buffer is set as drawable. If false subsequent commands and shaders won't be able to draw on it.
     * @param lightBuffer If True the light buffer is set as drawable. If false subsequent commands and shaders won't be able to draw on it.
     */
    // TODO: verify if this can become part of the FBO.bind() method.
    public void setRenderBufferMask(FBO fbo, boolean color, boolean normal, boolean lightBuffer) {
        if (fbo == null) {
            return;
        }

        int attachmentId = 0;

        IntBuffer bufferIds = BufferUtils.createIntBuffer(3);

        if (fbo.colorBufferTextureId != 0) {
            if (color) {
                bufferIds.put(GL_COLOR_ATTACHMENT0_EXT + attachmentId);
            }
            attachmentId++;
        }
        if (fbo.normalsBufferTextureId != 0) {
            if (normal) {
                bufferIds.put(GL_COLOR_ATTACHMENT0_EXT + attachmentId);
            }
            attachmentId++;
        }
        if (fbo.lightBufferTextureId != 0) {
            if (lightBuffer) {
                bufferIds.put(GL_COLOR_ATTACHMENT0_EXT + attachmentId);
            }
        }

        bufferIds.flip();

        GL20.glDrawBuffers(bufferIds);
    }

    /**
     * Sets the viewport of the currently bound FBO to the size of the Display.
     * 
     * Which might mean the full screen, the full size of the window or the
     * currently set screenshot size if a screenshot is being taken.
     */
    // TODO: verify if this can become part of the bindDisplay() method.
    public void setViewportToWholeDisplay() {
        glViewport(0, 0, fullScale.width(), fullScale.height());
    }

    /**
     * Sets the viewport of the currently bound FBO to the dimensions of the FBO
     * given as parameter.
     *
     * @param fbo The FBO whose dimensions will be matched by the viewport of the currently bound FBO.
     */
    // TODO: verify if this can become part of the FBO.bind() method.
    public void setViewportToSizeOf(FBO fbo) {
        glViewport(0, 0, fbo.width(), fbo.height());
    }

    /**
     * Unbinds any currently bound FBO and binds the default Frame Buffer,
     * which is usually the Display (be it the full screen or a window).
     */
    public void bindDisplay() {
        glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
    }

    private class Buffers {
        public FBO sceneOpaque;
        public FBO sceneReflectiveRefractive;
        public FBO sceneReflected;
        public FBO sceneShadowMap;
    }
}