view mde/setup/Screen.d @ 91:4d5d53e4f881

Shared alignment for dynamic content lists - finally implemented! Lots of smaller changes too. Some debugging improvements. When multiple .mtt files are read for merging, files with invalid headers are ignored and no error is thrown so long as at least one file os valid.
author Diggory Hardy <diggory.hardy@gmail.com>
date Thu, 16 Oct 2008 17:43:48 +0100
parents 79d816b3e2d2
children 9520cc0448e5
line wrap: on
line source

/* LICENSE BLOCK
Part of mde: a Modular D game-oriented Engine
Copyright © 2007-2008 Diggory Hardy

This program is free software: you can redistribute it and/or modify it under the terms
of the GNU General Public License as published by the Free Software Foundation, either
version 2 of the License, or (at your option) any later version.

This program 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 this program.  If not, see <http://www.gnu.org/licenses/>. */

/** Screen: SDL & OpenGL setup/cleanup, drawing.
 *
 * Has an interface by which other code can hook in for drawing and resize notifications. */
module mde.setup.Screen;

import mde.setup.exception;
import mde.lookup.Options;
import imde = mde.imde;
debug (drawGlyphCache) import mde.font.font;

import tango.util.log.Log : Log, Logger;
import tango.stdc.stringz;
import tango.time.Time;     // TimeSpan (type only; unused)

import derelict.sdl.sdl;
import derelict.opengl.gl;	// for loading a later gl version
import derelict.util.exception;

/** Currently just used as a namespace. Potential for multiple screen support later? */
struct Screen {
    // TYPES (these mustn't be static):
    /** Interface for anything hooking into the screen for drawing, etc. */
    interface IDrawable {
        /** Called on window creation and whenever the window manager resizes the window, before
         * the new size is set. The new size is passed.
         *
         * The parameters were passed as ref to allow the called function to change them, but SDL
         * didn't appear able to resize the window (on X11), so this was dropped. */
        void sizeEvent (int w, int h);
        
        /** Called you guess when :-) */
        void draw ();
    }
    
    /** All video options. */
    class OptionsVideo : Options {
        mixin (impl!("bool fullscreen,hardware,resizable,noFrame; int screenW,screenH,windowW,windowH;"));
    }
    
static:
    /** Init function to initialize SDL. */
    StageState init () {      // init func
        // Initialise SDL
        if (SDL_Init (SDL_INIT_VIDEO | SDL_INIT_JOYSTICK /+| SDL_INIT_EVENTTHREAD+/)) {
            logger.fatal ("SDL initialisation failed:");
            char* msg = SDL_GetError ();
            logger.fatal (msg ? fromStringz(msg) : "no reason available");
            
            throw new InitException ("SDL Initialization failed");
        }
        return StageState.ACTIVE;
    }
    /** SDL shutdown */
    StageState cleanup () {
        SDL_Quit();
        return StageState.INACTIVE;
    }
    /** Init function to set up a window with OpenGL support. */
    StageState initWindow () {
        //BEGIN Create window and initialize OpenGL
        // Window creation flags and size
        flags = SDL_OPENGL;
        if (vidOpts.hardware) flags |= SDL_HWSURFACE | SDL_DOUBLEBUF;
        else flags |= SDL_SWSURFACE;
        int w, h;
        if (vidOpts.fullscreen) {
            flags |= SDL_FULLSCREEN;
            w = vidOpts.screenW;
            h = vidOpts.screenH;
        }
        else {
            if (vidOpts.resizable) flags |= SDL_RESIZABLE;
            if (vidOpts.noFrame) flags |= SDL_NOFRAME;
            w = vidOpts.windowW;
            h = vidOpts.windowH;
        }
        
        // OpenGL attributes
        SDL_GL_SetAttribute(SDL_GL_RED_SIZE,    5);
        SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE,  6);
        SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE,   5);
        SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE,  16);
        SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER,1);
        
        // Open a window
        if (setWindow (w, h)) {
            throw new InitException ("Failed to open a window");
        }
        
        /* Now (must be done after GL context is created) we can try to load later version.
         * The initial loading provides opengl 1.1 features.
         *
         * 1.4 is now used for glBlendColor (coloured text).
         *
         * Currently the latest version used is 1.3; adjust this as necessary. However, before using
         * features from any OpenGL version > 1.1 a check must be made on what was loaded by calling
         * DerelictGL.availableVersion(). Note that availableVersion() could be used instead to load
         * the highest supported version but this way we know what we're getting. */
        if (DerelictGL.availableVersion < GLVersion.Version13) {
            throw new InitException ("Required at least OpenGL 1.3; didn't get this.");
        }
        /+try {
            DerelictGL.loadVersions(GLVersion.Version14);
        } catch (SharedLibProcLoadException e) {
            logger.warn ("Loading OpenGL version 1.4 failed:");
            logger.warn (e.msg);
            
            setInitFailure ();
            return;
        }+/
        
        // OpenGL stuff:
        glDisable(GL_LIGHTING);
        glDisable(GL_DEPTH_TEST);
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glEnable(GL_TEXTURE_2D);
        glShadeModel(GL_SMOOTH);
        
        glClearColor (0.0f, 0.0f, 0.0f, 0.0f);
        
        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();
        
        // Used for font rendering:
        glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        //NOTE: wrap mode may have an effect, but shouldn't be noticed...
        
        // Window-manager settings
        SDL_WM_SetCaption (toStringz ("mde"), null);
        // SDL_WM_GrabInput (use later)
        //END Create window and initialize OpenGL
        return StageState.ACTIVE;
    }
    
    
    /** Called when a resize event occurs (when the window manager resizes the window). */
    void resizeEvent (int w, int h) {
        // Save new size to config
        if (vidOpts.fullscreen) {       // probably resizeEvent only called when not fullscreen
            vidOpts.set!(int) ("screenW", w);
            vidOpts.set!(int) ("screenH", h);
        } else {
            vidOpts.set!(int) ("windowW", w);
            vidOpts.set!(int) ("windowH", h);
        }
        
        if (setWindow (w,h))
            imde.run = false;
    }
    
    /** Add a drawable element to the screen (see IDrawable interface).
     *
     * Should be called before Init to get the initial size (sizeEvent is only called when the size
     * is set). Currently no means to remove drawables, and not really designed for more than one.
     */
    void addDrawable (IDrawable d) {
        drawables ~= d;
    }
    
    /** Drawing function */
    void draw (TimeSpan) {
        glClear(GL_COLOR_BUFFER_BIT);
        
        foreach (d; drawables) {
            try {
                d.draw;
            } catch (Exception e) {
                logger.error ("Drawable failed to draw: "~e.msg);
            }
        }
        
        debug (drawGlyphCache) FontStyle.drawTexture;
        
        // Error check:
        GLenum err = glGetError();
        while (err != GL_NO_ERROR) {
            logger.error ("GL error: {}", err);
            err = glGetError();
        }
        
        glFinish();         // Use Finish rather than Flush to make sure gl is ready to swap buffers
        SDL_GL_SwapBuffers();
    }
    
    /** Set a new window size. Returns true on failure due to the different ways this must be
     * handled. */
    private bool setWindow (int w, int h) {
        foreach (d; drawables) {        // Tell all drawables the new window size.
            try {
                d.sizeEvent (w,h);
            } catch (Exception e) {
                logger.error ("Drawable failed while setting size: "~e.msg);
            }
        }
        
        //debug logger.trace ("Setting video mode {}x{}, 32-bit, flags: {}", w,h,flags);
        if (SDL_SetVideoMode (w, h, 32, flags) is null) {
            logger.fatal ("Unable to set video mode:");
            char* msg = SDL_GetError ();
            logger.fatal (msg ? fromStringz(msg) : "no reason available");
            
            // Print a load of info:
            logger.info ("Available video modes:");
            SDL_Rect** modes = SDL_ListModes (null, SDL_FULLSCREEN);
            if (modes is null) logger.info ("None!");
            else if (modes is cast(SDL_Rect**) -1) logger.info ("All modes are available");
            else {
                for (uint i = 0; modes[i] !is null; ++i) {
                    logger.info ("\t{}x{}", modes[i].w, modes[i].h);
                }
            }
            
            SDL_VideoInfo* vi = SDL_GetVideoInfo ();
            if (vi !is null) {
                logger.info ("Video info:");
                logger.info ("Hardware surface support: "~ (vi.flags & SDL_HWSURFACE ? "yes" : "no"));
                logger.info ("Video memory: {}", vi.video_mem);
                
                if (vi.vfmt !is null) {
                    logger.info ("Best video mode:");
                    logger.info ("Bits per pixel: {}", vi.vfmt.BitsPerPixel);
                }
            }
            
            return true;
        }
        
        // Reset the projection and viewport
        glMatrixMode (GL_PROJECTION);
        glLoadIdentity ();
        
        glViewport (0,0,w,h);
        
        // Make the top-left the origin (see gui/GUI notes.txt):
        // Note that this only affects vertex operations − direct rasterisation operations are
        // unaffected!
        glOrtho (0.0,w, h,0.0, -1.0, 1.0);
        
        glMatrixMode(GL_MODELVIEW);
        return false;
    }
    
    static this() {
        logger = Log.getLogger ("mde.setup.Screen");
        
        vidOpts = new OptionsVideo;
        Options.addOptionsClass (vidOpts, "video");
    }
    
    // DATA:
private:
    uint flags = 0;
    IDrawable[] drawables;
    Logger logger;
    OptionsVideo vidOpts;
}