com.softwaresemantics.diyglsllwp.DIYGslSurface.java Source code

Java tutorial

Introduction

Here is the source code for com.softwaresemantics.diyglsllwp.DIYGslSurface.java

Source

/*******************************************************************************
 * Cyril M. Hansen 2013
 * 
 * Licences :
 * Creative Commons Attribution-ShareAlike 3.0
 * Creative Commons Attribution - Partage dans les Mmes Conditions 3.0 France
 * 
 * http://creativecommons.org/licenses/by-sa/3.0
 * http://creativecommons.org/licenses/by-sa/3.0/fr/
 * 
 * Sources :
 * https://github.com/cyrilmhansen/DIYGlslLwp
 ******************************************************************************/
package com.softwaresemantics.diyglsllwp;

import com.badlogic.gdx.ApplicationListener;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.Mesh;
import com.badlogic.gdx.graphics.Pixmap.Format;
import com.badlogic.gdx.graphics.VertexAttribute;
import com.badlogic.gdx.graphics.VertexAttributes.Usage;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.graphics.glutils.FrameBuffer;
import com.badlogic.gdx.graphics.glutils.ShaderProgram;
import com.badlogic.gdx.input.GestureDetector;
import com.badlogic.gdx.input.GestureDetector.GestureListener;
import com.badlogic.gdx.math.Vector2;

public class DIYGslSurface implements ApplicationListener, GestureListener {

    private static final String OPEN_GL_ES_2_0_REQUIRED = "OpenGL ES 2.0 required";

    private static final String ERROR_LOADING_SHADER = "Error loading shader";

    private static final String HARDWARE_TOO_SLOW_FOR_SHADER = "Hardware too slow for shader";

    private static final String DO_PROCESS_SCREEN_SHOT = "doProcessScreenShot";

    private static final String NO_EXCEPTION_MSG = "no exception msg";

    private String shaderProgram;

    private ShaderProgram shader;

    // Screen surface / 2 triangles
    Mesh mesh;

    private boolean timeDithering = false;
    private int timeDitheringFactor = 2;
    private int nbRender = 0;

    private boolean touchEnabled;

    private FrameBuffer m_fbo = null;
    private TextureRegion m_fboRegion = null;

    private SpriteBatch batch;
    private int renderSurfaceWidth;
    private int renderSurfaceHeight;

    private float m_fboScaler = 1.0f;
    private float epsilon = 0.00001f;

    // TODO : offer option / preserve ratio or fit shader to screen / for now
    // this is preserve ratio and truncate
    private int effectiveSurfaceWidth;
    private int effectiveSurfaceHeight;

    private float mouseCursorX;
    private float mouseCursorY;

    private long timeOrigin;

    private float time;

    private boolean doscreenShotRequest;
    private ScreenshotProcessor screenshotProc;

    private String errorMsg;

    private boolean showFPS = true;

    private BitmapFont font;

    private ClickHandler listener;

    private NativeCallback nativeCallback;

    private boolean timeLoop;

    private int timeLoopPeriod;

    private float speedFactor = 1.0f;

    private boolean forceMediump;

    private static boolean renderGuard = false;

    // GL ES 2.0 is required
    public boolean needsGL20() {
        return true;
    }

    /** detailed constructor */
    public DIYGslSurface(String shaderGLSL, boolean reductionFactorEnabled, int reductionFactor,
            boolean touchEnabled, boolean displayFPSLWP, boolean timeDither, int timeDitherFactor, boolean timeLoop,
            int timeLoopPeriod, boolean forceMediump, float speedFactor) {

        renderGuard = true;

        this.shaderProgram = shaderGLSL;
        this.m_fboScaler = reductionFactor;

        this.timeDithering = timeDither;
        this.timeDitheringFactor = timeDitherFactor;

        this.touchEnabled = touchEnabled;
        this.showFPS = displayFPSLWP;

        this.timeLoop = timeLoop;
        this.timeLoopPeriod = timeLoopPeriod;
        this.forceMediump = forceMediump;

        this.speedFactor = speedFactor;

    }

    public void updatePrefs(boolean reductionFactorEnabled, int reductionFactor, boolean touchEnabled,
            boolean displayFPSLWP, boolean timeDither, int timeDitherFactor, boolean timeLoop, int timeLoopPeriod,
            boolean forceMediump, float speedFactor) {

        this.m_fboScaler = reductionFactor;

        this.timeDithering = timeDither;
        this.timeDitheringFactor = timeDitherFactor;

        this.touchEnabled = touchEnabled;
        this.showFPS = displayFPSLWP;

        this.timeLoop = timeLoop;
        this.timeLoopPeriod = timeLoopPeriod;

        this.speedFactor = speedFactor;

        // change may require shader reload
        if (this.forceMediump != forceMediump) {
            this.forceMediump = forceMediump;
            setupShader();
        }

        updateRenderSurfaceSize();

        // Force re creation of framebuffer if needed
        this.m_fbo = null;

    }

    public void updateShader(String shaderGLSL) {
        // force mediump for float uniforms
        this.shaderProgram = shaderGLSL;

        setupShader();
    }

    /**
     * Constructor with default params, custom shader
     * 
     * @param forcedShaderProgram
     */
    public DIYGslSurface(String forcedShaderProgram) {
        renderGuard = true;

        this.shaderProgram = forcedShaderProgram;
    }

    /**
     * Constructor with default params and shader (demo mode)
     */
    public DIYGslSurface() {
        renderGuard = true;
    }

    public void create() {

        // GL 20 (GL2ES) Required
        if (!Gdx.graphics.isGL20Available()) {
            Gdx.app.log("DIYGslSurface", "isGL20Available returns false");
            if (nativeCallback != null) {
                nativeCallback.onRequirementFailure(null);
                // The Android specific code will decide to quit or not
                // Gdx.app.exit();
            }
        }

        Gdx.graphics.setVSync(true);

        Gdx.input.setInputProcessor(new GestureDetector(this));

        setupShader();

        effectiveSurfaceWidth = Gdx.graphics.getWidth();
        effectiveSurfaceHeight = Gdx.graphics.getHeight();

        updateRenderSurfaceSize();

        reserveRessources();

        // set the cursor somewhere else than @ 0,0
        // To be set as parameter ?
        mouseCursorX = 0.2f;
        mouseCursorY = 0.3f;
    }

    private void updateRenderSurfaceSize() {
        renderSurfaceWidth = effectiveSurfaceWidth;
        renderSurfaceHeight = effectiveSurfaceHeight;

        if (m_fboScaler > 1 + epsilon) {
            renderSurfaceWidth /= m_fboScaler;
            renderSurfaceHeight /= m_fboScaler;
        }
    }

    private void setupShader() {
        renderGuard = true;
        if (nativeCallback != null) {
            nativeCallback.notifyCompilation();
        }

        // setup shader and required associated data structures

        // setup verbose shader compile error messages
        ShaderProgram.pedantic = false;

        String effectiveFragShader = shaderProgram;

        errorMsg = null;
        if (shaderProgram != null) {
            try {
                if (forceMediump) {
                    effectiveFragShader = "precision mediump float;\n"
                            + effectiveFragShader.replaceAll("uniform%sfloat", "uniform mediump float");
                }
                shader = new CustomShader(shaderProgram);
            } catch (Exception ex) {
                // fall back to default shader
                // notify user
                errorMsg = ex.getMessage();
            }
        }

        if (shader == null) {
            // default shader, should have no problem
            try {
                shader = new HerokuSampleShader();
            } catch (Exception ex) {
                errorMsg = ex.getMessage();
            }
        }

        if (shader == null && nativeCallback != null) {
            // Android code called to notify the error
            nativeCallback.onRequirementFailure(errorMsg);
        }

        mesh = genFullViewRectangle();

        timeOrigin = System.currentTimeMillis();
        renderGuard = false;
        if (nativeCallback != null) {
            nativeCallback.notifyCompilationEnd();
        }
    }

    public void render() {

        // render of LWP can be called while the gallery is being setup
        // or the other way round
        // in this case, static data are not usable and we may crash

        // we exclude this by explicitely checking a guard
        if (renderGuard)
            return;

        // GL 20 Required
        if (!Gdx.graphics.isGL20Available()) {
            Gdx.app.log("DIYGslSurface", "isGL20Available returns false");

            // Gdx.app.exit()
            displayErrorMsg();
            return;
        }

        if (handleErrorAndSlowHardware()) {
            return;
        }

        initRenderFramebufferIfNeeded();

        // we do not render all frames in time dithering mode
        if (timeDithering) {
            if (nbRender % timeDitheringFactor != 0) {
                // don't clear view, don't render, don't process screenshots
                // we need vsync to ensure some delay before next frame ??
                return;
            }
        }

        try {

            // intermediate framebuffer is always used
            // like in web gallery
            m_fbo.begin();

            // Gdx.gl20.glClearColor(0, 0, 0, 0);
            Gdx.gl20.glClear(GL20.GL_COLOR_BUFFER_BIT);

            renderShaderonMesh();

            m_fbo.end();

        } catch (Exception ex) {
            String msg = ex.getMessage() != null ? ex.getMessage() : NO_EXCEPTION_MSG;
            Gdx.app.log("GDX render", msg, ex);
        }

        // scaled render
        batch.begin();
        try {
            batch.disableBlending();
            batch.draw(m_fboRegion, 0, 0, effectiveSurfaceWidth, effectiveSurfaceHeight);
            batch.enableBlending();

        } catch (Exception ex) {
            String msg = ex.getMessage() != null ? ex.getMessage() : NO_EXCEPTION_MSG;
            Gdx.app.log("m_fbo scale", msg);
        } finally {
            batch.end();
        }

        // FPS is drawn on real screen
        // in all cases
        if (showFPS) {
            batch.begin();
            font.draw(batch, "time:" + time, Gdx.graphics.getWidth() - 250, 15);
            font.draw(batch, "FPS:" + Gdx.graphics.getFramesPerSecond(), Gdx.graphics.getWidth() - 60, 15);
            batch.end();
        }

        // (Screenshot includes FPS)

        // Process snapshot requests if any
        // Proper gl context for this is only available in render() aka here
        if (doscreenShotRequest && screenshotProc != null) {
            try {
                screenshotProc.doProcessScreenShot();
            } catch (Exception ex) {
                Gdx.app.log(DO_PROCESS_SCREEN_SHOT, ex.getLocalizedMessage());
            } finally {
                doscreenShotRequest = false;
            }
        }

    }

    private void displayErrorMsg() {
        batch.begin();
        font.draw(batch, OPEN_GL_ES_2_0_REQUIRED, 50, 50);
        batch.end();
    }

    private boolean handleErrorAndSlowHardware() {
        // If rendering is too slow, display an error
        if (Gdx.graphics.getDeltaTime() > 5 && Gdx.graphics.getFramesPerSecond() < 2) {
            shader = null;
            errorMsg = HARDWARE_TOO_SLOW_FOR_SHADER;
        }

        // If missing shader, display error
        if (shader == null) {
            if (errorMsg == null) {
                errorMsg = ERROR_LOADING_SHADER;
            }

            batch.begin();
            font.draw(batch, errorMsg, 50, 50);
            batch.end();

            return true;
        }

        return false;
    }

    /**
     * called in render only
     * 
     * in all other cases, we want to force full reinit for optimization reasons
     */
    private void initRenderFramebufferIfNeeded() {
        if (m_fbo == null) {
            forceNewRenderBuffer();
        }
    }

    private void forceNewRenderBuffer() {
        // RGB888 = No alpha channel needed
        m_fbo = new FrameBuffer(Format.RGB888, renderSurfaceWidth, renderSurfaceHeight, false);
        m_fboRegion = new TextureRegion(m_fbo.getColorBufferTexture());

        // view is y flipped without this
        m_fboRegion.flip(false, true);
    }

    /**
     * handler for GDX resize event
     */
    public void resize(int width, int height) {
        this.effectiveSurfaceWidth = width;
        this.effectiveSurfaceHeight = height;

        updateRenderSurfaceSize();

        if (m_fbo != null) {
            m_fbo.dispose();
        }

        forceNewRenderBuffer();
    }

    /**
     * render shader on mesh, update shader vars for frame
     */
    private void renderShaderonMesh() {

        shader.begin();

        shader.setUniformf("resolution", renderSurfaceWidth, renderSurfaceHeight);

        time = (float) ((System.currentTimeMillis() - timeOrigin) * speedFactor / 1000.0d);

        // Process optional time loop
        if (timeLoop && (time > timeLoopPeriod)) {
            timeOrigin = timeOrigin + (timeLoopPeriod * 1000);
        }

        shader.setUniformf("time", time);
        shader.setUniformf("mouse", mouseCursorX, mouseCursorY);

        // render surface as mesh
        mesh.render(shader, GL20.GL_TRIANGLES);

        shader.end();

    }

    /**
     * @param maxX
     * @param maxY
     * @return mesh setup for rectangular view
     */
    public static Mesh genFullViewRectangle() {

        float x1 = -1.0f, y1 = -1.0f;
        float x2 = 1.0f, y2 = 1.0f;
        float z0 = 0f;
        float u1 = 0.0f, v1 = 0.0f;
        float u2 = 1.0f, v2 = 1.0f;

        Mesh mesh = new Mesh(true, 4, 6, new VertexAttribute(Usage.Position, 3, ShaderProgram.POSITION_ATTRIBUTE),
                new VertexAttribute(Usage.TextureCoordinates, 2, ShaderProgram.TEXCOORD_ATTRIBUTE + "0"));

        mesh.setVertices(new float[] { x1, y1, z0, u1, v1, x2, y1, z0, u2, v1, x2, y2, z0, u2, v2,
                // x1, y1, z0, u1, v1,
                // x2, y2, z0, u2, v2,
                x1, y2, z0, u1, v2 });

        // use indices to simplify vertices array
        mesh.setIndices(new short[] { 0, 1, 2, 0, 2, 3 });

        return mesh;
    }

    static class HerokuSampleShader extends CustomShader {
        private static final String DATA_SHADERS_HEROKUWIGGLE1_FRAG = "data/shaders/herokuwiggle1.frag";

        public HerokuSampleShader() {
            super(Gdx.files.internal(DATA_SHADERS_HEROKUWIGGLE1_FRAG).readString());
        }
    }

    private static class CustomShader extends ShaderProgram {
        private static final String SHADER_COMPILATION_FAILED = "Shader compilation failed:\n";
        private static final String DATA_SHADERS_HEROKUBASE_VERT = "data/shaders/herokubase.vert";

        public CustomShader(String customFragShader) {

            super(Gdx.files.internal(DATA_SHADERS_HEROKUBASE_VERT).readString(), customFragShader);

            if (!isCompiled()) {
                throw new RuntimeException(SHADER_COMPILATION_FAILED + getLog());
            }
        }

    }

    /**
     * handler for android dispose event
     */
    public void dispose() {
        freeRessourcesIfAny();
    }

    /**
     * handler for android pause event
     */
    public void pause() {
        freeRessourcesIfAny();
    }

    private void freeRessourcesIfAny() {

        // Free shader
        if (shader != null) {
            shader.dispose();
        }

        // Free ressources if any used
        if (m_fbo != null) {
            m_fbo.dispose();
            m_fbo = null;
        }

        m_fboRegion = null;

        // batch.dispose();
    }

    /**
     * handler for android resume event
     */
    public void resume() {

        Gdx.app.log("DIYGslSurface", "resume called");

        if (nativeCallback != null) {
            nativeCallback.onResumeGDX();
        }

        // Force recreation of buffers
        m_fbo = null;

        reserveRessources();
    }

    private void reserveRessources() {

        setupShader();

        batch = new SpriteBatch();
        font = new BitmapFont();

        forceNewRenderBuffer();

        setupShader();

    }

    // handler for ???
    public void previewStateChange(boolean isPreview) {
    }

    /**
     * handler for touchDown Update cursor position if useful
     */
    public boolean touchDown(float x, float y, int pointer, int button) {
        if (listener != null) {
            listener.onClick((int) x, (int) y);
        }
        if (touchEnabled) {
            // update mouse cursor pos
            mouseCursorX = x * 1.0f / renderSurfaceWidth;
            mouseCursorY = y * 1.0f / renderSurfaceHeight;
            return true;
        } else {
            // ignore event
            return false;
        }
    }

    /**
     * tap event handler (ignored)
     */
    public boolean tap(float x, float y, int count, int button) {
        return false;
    }

    /**
     * longPress event handler (ignored)
     */
    public boolean longPress(float x, float y) {
        // ignore event
        return false;
    }

    /**
     * fling event handler (ignored)
     */
    public boolean fling(float velocityX, float velocityY, int button) {
        // ignore event
        return false;
    }

    /**
     * pan event handler
     */
    public boolean pan(float x, float y, float deltaX, float deltaY) {
        if (touchEnabled) {
            // cam.translate(deltaX, deltaY);
            return true;
        } else {
            // ignore event
            return false;
        }
    }

    /**
     * zoom event handler
     */
    public boolean zoom(float initialDistance, float distance) {
        if (touchEnabled) {
            // float scaleInv = initialDistance / distance;
            // projectionUser.scale(scale, scale, 1.0f);
            // cam.zoom *= scaleInv;
            return true;
        } else {
            // ignore event
            return false;
        }
    }

    /**
     * pinch event handler
     */
    public boolean pinch(Vector2 initialPointer1, Vector2 initialPointer2, Vector2 pointer1, Vector2 pointer2) {
        // ignore event
        return false;
    }

    // Screen shot management
    // Screen shot processing is differed in render method due to gl details
    // data is then provided to screenshotProc callback

    public boolean isDoscreenShotRequest() {
        return doscreenShotRequest;
    }

    public void setDoscreenShotRequest(boolean doscreenShotRequest) {
        this.doscreenShotRequest = doscreenShotRequest;
    }

    public ScreenshotProcessor getScreenshotProc() {
        return screenshotProc;
    }

    public void setScreenshotProc(ScreenshotProcessor screenshotProc) {
        this.screenshotProc = screenshotProc;
    }

    public void addClickHandler(ClickHandler listener) {
        this.listener = listener;

    }

    public void setNativeCallback(NativeCallback callback) {
        this.nativeCallback = callback;
    }

    public static boolean isRenderGuard() {
        return renderGuard;
    }

    public static void setRenderGuard(boolean renderGuard) {
        DIYGslSurface.renderGuard = renderGuard;
    }

}