illarion.graphics.lwjgl.RenderDisplayLWJGL.java Source code

Java tutorial

Introduction

Here is the source code for illarion.graphics.lwjgl.RenderDisplayLWJGL.java

Source

/*
 * This file is part of the Illarion Graphics Engine.
 *
 * Copyright  2011 - Illarion e.V.
 *
 * The Illarion Graphics Engine is free software: you can redistribute i and/or
 * modify it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or (at your
 * option) any later version.
 * 
 * The Illarion Graphics Engine is distributed in the hope that it will be
 * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
 * Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License along with
 * the Illarion Graphics Interface. If not, see <http://www.gnu.org/licenses/>.
 */
package illarion.graphics.lwjgl;

import java.awt.Canvas;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

import javolution.util.FastList;

import org.apache.log4j.Logger;
import org.lwjgl.LWJGLException;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode;
import org.lwjgl.opengl.GL11;
import org.lwjgl.util.glu.GLU;

import illarion.common.util.Rectangle;

import illarion.graphics.GraphicResolution;
import illarion.graphics.Graphics;
import illarion.graphics.RenderDisplay;
import illarion.graphics.generic.AbstractSprite;
import illarion.graphics.lwjgl.render.AbstractTextureRender;

/**
 * The LWJGL Render display implementation that allows accessing the LWJGL
 * render display and offers the possibility to toggle between full screen and
 * windowed mode.
 * 
 * @author Martin Karing
 * @version 2.00
 * @since 2.00
 */
public final class RenderDisplayLWJGL implements RenderDisplay { // NO_UCD
    /**
     * The logger instance that is used to log the error and debugging output.
     */
    private static final Logger LOGGER = Logger.getLogger(RenderDisplayLWJGL.class);

    /**
     * The maximal value for the synchronized frames per second.
     */
    private static final int MAX_FREQUENCY = 60;

    /**
     * The area limits that were set in place. They need to be stored in order
     * to roll them back one by one when resetting.
     */
    private final FastList<Rectangle> areaLimits = new FastList<Rectangle>();

    /**
     * This is the list of the raw area limits. This limits don't have the
     * intersection applied and are used for the proper calculation of the
     * offset.
     */
    private final FastList<Rectangle> areaLimitsRaw = new FastList<Rectangle>();

    /**
     * The list of found display types in the proper order.
     */
    private List<DisplayMode> displayModes;

    /**
     * selector if the full screen mode is possible or not.
     */
    private boolean fullscreenPossible = false;

    /**
     * The render area that is the render target.
     */
    private Canvas renderCanvas;

    /**
     * The height of the display that was selected.
     */
    private int resHeight = 0;

    /**
     * The width of the display that was selected.
     */
    private int resWidth = 0;

    @SuppressWarnings("nls")
    @Override
    public void addInputListener(final Object listener) {
        throw new GraphicsLWJGLException("Invalid input listener added");
    }

    /**
     * Apply a global offset to all following render actions.
     * 
     * @param offX the global x offset
     * @param offY the global y offset
     */
    @Override
    public void applyOffset(final float offX, final float offY) {
        GL11.glPushMatrix();
        GL11.glTranslatef(offX, offY, 0.f);
    }

    @Override
    public void applyScaling(final float scale) {
        GL11.glPushMatrix();
        if (!areaLimits.isEmpty()) {
            final Rectangle lastRect = areaLimits.getLast();
            if (scale > 1.f) {
                GL11.glTranslatef(-lastRect.getWidth() / scale, -lastRect.getHeight() / scale, 0);
            } else {
                GL11.glTranslatef(lastRect.getWidth() * scale, lastRect.getHeight() * scale, 0);
            }
        }
        GL11.glScalef(scale, scale, 1.f);
    }

    /**
     * Get the height of the render display.
     * 
     * @return the height of the render display
     */
    @Override
    public int getHeight() {
        return resHeight;
    }

    /**
     * Get the amount of objects drawn since the last call of that function.
     * 
     * @return the amount of objects drawn
     * @see illarion.graphics.RenderDisplay#getObjects()
     */
    @Override
    public int getObjects() {
        return AbstractSprite.getDrawnObjects();
    }

    /**
     * Get a list of possible resolutions the client is able to go along with.
     * 
     * @return a list of possible resolutions, the order is always the same at
     *         the same computer. The format is: width*height*bpp@freq
     */
    @Override
    public GraphicResolution[] getPossibleResolutions() {
        final List<DisplayMode> modesArray = getDisplayModes();

        final List<GraphicResolution> ret = new ArrayList<GraphicResolution>();
        for (final DisplayMode mode : modesArray) {
            if ((mode.getWidth() < 800) || (mode.getHeight() < 600) || (mode.getBitsPerPixel() <= 16)) {
                continue;
            }
            ret.add(new GraphicResolution(mode.getWidth(), mode.getHeight(), mode.getBitsPerPixel(),
                    mode.getFrequency()));
        }
        return ret.toArray(new GraphicResolution[ret.size()]);
    }

    /**
     * Get the rendering area Canvas. It is create in case it was not before and
     * set to the size that was chosen for the display.
     * 
     * @return the display canvas that is the target of the rendering actions
     * @see illarion.graphics.RenderDisplay#getRenderArea()
     */
    @Override
    public Canvas getRenderArea() {
        if (renderCanvas == null) {
            renderCanvas = new Canvas() {
                private static final long serialVersionUID = 1L;
                private final RenderManagerLWJGL manager = (RenderManagerLWJGL) Graphics.getInstance()
                        .getRenderManager();

                @Override
                public void paint(final java.awt.Graphics g) {
                    manager.paint();
                }

                @Override
                public void update(final java.awt.Graphics g) {
                    paint(g);
                }

                @Override
                public void validate() {
                    super.validate();
                    RenderDisplayLWJGL.this.setSize(getWidth(), getHeight());
                }
            };
            renderCanvas.setSize(resWidth, resHeight);
        }
        return renderCanvas;
    }

    /**
     * Get the width of the render display.
     * 
     * @return the width of the render display
     */
    @Override
    public int getWidth() {
        return resWidth;
    }

    @Override
    public void hideCursor() {
        // does nothing, cursor is always hidden
    }

    /**
     * Check if the display is currently working in full screen mode.
     * 
     * @return true in case the full screen mode is currently active
     * @see illarion.graphics.RenderDisplay#isFullscreen()
     */
    @Override
    public boolean isFullscreen() {
        return fullscreenPossible && Display.isFullscreen();
    }

    /**
     * LWJGL does not use listeners. So the check if the listener is supported
     * is done by checking the listener parameter against <code>null</code>.
     * 
     * @param listener the listener to test
     * @return <code>true</code> in case the listener parameter is
     *         <code>null</code>
     */
    @Override
    public boolean isInputListenerSupported(final Object listener) {
        return (listener == null);
    }

    @SuppressWarnings("nls")
    @Override
    public void removeInputListener(final Object listener) {
        throw new GraphicsLWJGLException("Invalid input listener removed");

    }

    /**
     * Reset the last applied global offset.
     */
    @Override
    public void resetOffset() {
        GL11.glPopMatrix();
    }

    @Override
    public void resetScaling() {
        GL11.glPopMatrix();
    }

    /**
     * Active the scissors mode to limit the render area.
     * 
     * @param x the x coordinate of the location of the render area
     * @param y the y coordinate of the location of the render area
     * @param width the width of the scissor area
     * @param height the height of the scissor area
     * @see illarion.graphics.RenderDisplay#setAreaLimit(int, int, int, int)
     */
    @Override
    public void setAreaLimit(final int x, final int y, final int width, final int height) {
        final Rectangle newRect = Rectangle.getInstance();
        final Rectangle newRectRaw = Rectangle.getInstance();

        newRect.set(x, y, width, height);
        newRectRaw.set(x, y, width, height);

        if (areaLimits.isEmpty()) {
            GL11.glEnable(GL11.GL_SCISSOR_TEST);
        } else {
            newRect.intersect(areaLimits.getLast());
        }
        GL11.glPushMatrix();
        GL11.glScissor(newRect.getLeft(), newRect.getBottom(), newRect.getWidth(), newRect.getHeight());

        if (!areaLimits.isEmpty()) {
            final Rectangle lastRect = areaLimitsRaw.getLast();
            GL11.glTranslatef(newRectRaw.getX() - lastRect.getX(), newRectRaw.getY() - lastRect.getY(), 0);
        } else {
            GL11.glTranslatef(newRectRaw.getX(), newRectRaw.getY(), 0);
        }
        areaLimits.addLast(newRect);
        areaLimitsRaw.addLast(newRectRaw);
    }

    /**
     * Set the used screen resolution.
     * 
     * @param resolution the display settings to be used to render the screen.
     */
    @Override
    public void setDisplayMode(final GraphicResolution resolution) {
        resHeight = resolution.getHeight();
        resWidth = resolution.getWidth();
        ((RenderManagerLWJGL) Graphics.getInstance().getRenderManager()).setTargetFPS(resolution.getRefreshRate());
    }

    /**
     * Select a display mode by its index in the list of display modes. The list
     * can be fetched using {@link #getPossibleResolutions()}.
     * 
     * @param index the index of the display mode that shall be used in the list
     *            of existing display modes.
     */
    @Override
    public void setDisplayMode(final int index) {
        DisplayMode usedMode = null;
        try {
            usedMode = getDisplayModes().get(index);
        } catch (final Exception e1) {
            try {
                usedMode = getDisplayModes().get(0);
            } catch (final Exception e2) {
                fullscreenPossible = false;
                resWidth = 800;
                resHeight = 600;
                updateSyncFreq();
            }
        }

        if (usedMode != null) {
            resWidth = usedMode.getWidth();
            resHeight = usedMode.getHeight();
            updateSyncFreq();
            try {
                Display.setDisplayMode(usedMode);
                fullscreenPossible = true;
            } catch (final LWJGLException e) {
                fullscreenPossible = false;
            }
        }

        if (renderCanvas != null) {
            renderCanvas.setSize(resWidth, resHeight);
        }

        setupOpenGL();
    }

    /**
     * LWJGL does yet not include any special options, so this function does
     * nothing at all.
     */
    @Override
    public void setDisplayOptions(final String key, final String value) {
        // nothing to do
    }

    @Override
    public void showCursor() {
        // does nothing, cursor is always hidden
    }

    @Override
    public void shutdown() {
        if (renderCanvas != null) {
            renderCanvas.setVisible(false);
            if (Display.isCreated()) {
                Display.destroy();
            }
        }
    }

    /**
     * Start the rendering and and create the LWJGL display. This will try to
     * activate the display at all costs. In case its not possible to link with
     * the rendering canvas it will create a new window. Make sure the rendering
     * canvas returns true at its function
     * {@link java.awt.Canvas#isDisplayable()}.
     * 
     * @see illarion.graphics.RenderDisplay#startRendering()
     */
    @Override
    @SuppressWarnings("nls")
    public void startRendering() {
        if (Display.isCreated()) {
            return;
        }

        renderCanvas.setEnabled(true);
        renderCanvas.setVisible(true);
        try {
            Display.setParent(renderCanvas);
            Display.create();
            Display.makeCurrent();
        } catch (final LWJGLException e) {
            LOGGER.fatal("failed creating the display", e);
        }

        setupOpenGL();
        ((RenderManagerLWJGL) Graphics.getInstance().getRenderManager()).bind(this);
    }

    /**
     * Stop the rendering and destroy the LWJGL rendering window.
     * 
     * @see illarion.graphics.RenderDisplay#stopRendering()
     */
    @Override
    public void stopRendering() {
        // if (Display.isCreated()) {
        // Display.destroy();
        // }
        // renderCanvas.setEnabled(false);
    }

    /**
     * Check if the full screen mode is possible. That works only in case the
     * window mode that is set up is supported by the the graphic card.
     * 
     * @return true if the full screen mode is possible
     * @see illarion.graphics.RenderDisplay#supportsFullscreen()
     */
    @Override
    public boolean supportsFullscreen() {
        return fullscreenPossible;
    }

    /**
     * Switch between full screen mode and windowed mode in case its possible.
     * 
     * @see illarion.graphics.RenderDisplay#toogleFullscreen()
     */
    @Override
    @SuppressWarnings("nls")
    public void toogleFullscreen() {
        try {
            if (Display.isFullscreen()) {
                Display.setFullscreen(false);
                updateSyncFreq();
            } else if (fullscreenPossible) {
                // Display.setFullscreen(true);
                updateSyncFreq();
            }
        } catch (final LWJGLException e) {
            LOGGER.error("Failed switching to full screen.", e);
        }
    }

    /**
     * Disable the scissors mode that limits the render area.
     * 
     * @see illarion.graphics.RenderDisplay#unsetAreaLimit()
     */
    @Override
    public void unsetAreaLimit() {
        areaLimits.removeLast().recycle();
        areaLimitsRaw.removeLast().recycle();

        if (areaLimits.isEmpty()) {
            GL11.glDisable(GL11.GL_SCISSOR_TEST);
        } else {
            final Rectangle lastRect = areaLimits.getLast();
            GL11.glScissor(lastRect.getLeft(), lastRect.getBottom(), lastRect.getWidth(), lastRect.getHeight());
        }
        GL11.glPopMatrix();
    }

    /**
     * Update the display so the last drawn picture gets visible.
     */
    public void update() {
        AbstractTextureRender.finish();

        Display.update();

        // clean display for next render loop.
        GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_STENCIL_BUFFER_BIT);
        GL11.glLoadIdentity();
    }

    /**
     * Change the size of this display. The sole reason for this function is be
     * in existence is to avoid the creating of synthetic methods.
     * 
     * @param width the new width
     * @param height the new height
     */
    protected void setSize(final int width, final int height) {
        resWidth = width;
        resHeight = height;
    }

    /**
     * Setup the openGL render environment. Such as the view port the matrix for
     * the orthogonal view and so on.
     */
    protected void setupOpenGL() {
        if (!Display.isCreated()) {
            return;
        }

        // set the basic view port of the game. This should be the full size of
        // the client window
        GL11.glViewport(0, 0, resWidth, resHeight);

        // enable alpha blending based on the picture alpha channel
        GL11.glEnable(GL11.GL_BLEND);
        GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);

        // disable death test, we work in 2D anyway, there is no depth
        GL11.glDisable(GL11.GL_DEPTH_TEST);

        // switch to projection matrix to set up the orthogonal view that we
        // need
        GL11.glMatrixMode(GL11.GL_PROJECTION);

        // load the identity matrix to we have a clean start
        GL11.glLoadIdentity();

        // setup the orthogonal view
        GLU.gluOrtho2D(0, resWidth, 0, resHeight);

        // set clear color to black
        GL11.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);

        // sync frame (only works on windows)
        if (Graphics.NO_SLOWDOWN) {
            Display.setVSyncEnabled(false);
        } else {
            Display.setVSyncEnabled(true);
        }

        // clean up the screen
        GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);

        // switch to model view matrix, so we can place the sprites correctly
        GL11.glMatrixMode(GL11.GL_MODELVIEW);
        GL11.glLoadIdentity();
    }

    /**
     * Get all available display modes ordered by size.
     * 
     * @return the list of display modes ordered by size
     */
    @SuppressWarnings("nls")
    private List<DisplayMode> getDisplayModes() {
        if (displayModes != null) {
            return displayModes;
        }
        try {
            final DisplayMode[] modes = Display.getAvailableDisplayModes();
            final List<DisplayMode> modesArray = new ArrayList<DisplayMode>(modes.length);
            for (final DisplayMode mode : modes) {
                modesArray.add(mode);
            }
            Collections.sort(modesArray, new Comparator<DisplayMode>() {
                @Override
                public int compare(final DisplayMode o1, final DisplayMode o2) {
                    if (o1.getBitsPerPixel() < o2.getBitsPerPixel()) {
                        return -1;
                    } else if (o1.getBitsPerPixel() > o2.getBitsPerPixel()) {
                        return 1;
                    }
                    final int o1Size = o1.getWidth() * o1.getHeight();
                    final int o2Size = o2.getWidth() * o2.getHeight();
                    if (o1Size < o2Size) {
                        return -1;
                    } else if (o1Size > o2Size) {
                        return 1;
                    }
                    return 0;
                }
            });
            displayModes = modesArray;
            return modesArray;
        } catch (final LWJGLException e) {
            LOGGER.error("Error fetching display modes", e);
            return new ArrayList<DisplayMode>();
        }
    }

    /**
     * After a update of the sync frequency, update it to a new proper value at
     * or below the {@link #MAX_FREQUENCY} value.
     */
    private void updateSyncFreq() {
        int syncFreq = Display.getDisplayMode().getFrequency();

        while (syncFreq > MAX_FREQUENCY) {
            syncFreq >>= 1;
        }

        ((RenderManagerLWJGL) Graphics.getInstance().getRenderManager()).setTargetFPS(syncFreq);
    }
}