view mde/setup/Screen.d @ 137:9f035cd139c6

BIG commit. Major change: old Options class is gone, all content values are loaded and saved automatically. All options updated to reflect this, some changed. Content restrutured a lot: New IContent module, Content module includes more functionality. New ContentLoader module to manage content loading/saving/translation. Translation module moved to content dir and cut down to reflect current usage. File format unchanged except renames: FontOptions -> Font, VideoOptions -> Screen. Font render mode and LCD filter options are now enums. GUI loading needs to create content (and set type for enums), but doesn't save/load value. Some setup of mainSchedule moved to mde.mainLoop. Content callbacks are called on content change now. ContentLists are set up implicitly from content symbols. Not as fast but much easier! Bug-fix in the new MTTagReader. Renamed MT *Reader maker functions to avoid confusion in paths.d. New mde.setup.logger module to allow logger setup before any other module's static this().
author Diggory Hardy <diggory.hardy@gmail.com>
date Sat, 07 Feb 2009 12:46:03 +0000
parents 5ee69b3ed9c9
children
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.content.AStringContent;
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;
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 ();
    }
    
static:
    /** Init function to initialize SDL. */
    StageState init () {      // init func
        // Initialise SDL
        debug (SDLCalls) logger.trace ("Calling SDL_Init (SDL_INIT_VIDEO | SDL_INIT_JOYSTICK)");
        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 () {
        debug (SDLCalls) logger.trace ("Calling SDL_Quit ()");
        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 (hardware()) flags |= SDL_HWSURFACE | SDL_DOUBLEBUF;
        else flags |= SDL_SWSURFACE;
        int w, h;
        if (fullscreen()) {
            flags |= SDL_FULLSCREEN;
            w = screenW();
            h = screenH();
        }
        else {
            if (resizable()) flags |= SDL_RESIZABLE;
            if (noFrame()) flags |= SDL_NOFRAME;
            w = windowW();
            h = 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);
        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 (fullscreen()) {       // probably resizeEvent only called when not fullscreen
            screenW = w;
            screenH = h;
        } else {
            windowW = w;
            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);
        debug (SDLCalls) logger.trace ("Calling SDL_SetVideoMode ({}, {}, 32, 0x{:x})", 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");
        
        fullscreen = new BoolContent ("Screen.fullscreen");
        hardware = new BoolContent ("Screen.hardware");
        resizable = new BoolContent ("Screen.resizable");
        noFrame = new BoolContent ("Screen.noFrame");
        screenW = new IntContent ("Screen.screenW");
        screenH = new IntContent ("Screen.screenH");
        windowW = new IntContent ("Screen.windowW");
        windowH = new IntContent ("Screen.windowH");
    }
    
    // DATA:
private:
    uint flags = 0;
    IDrawable[] drawables;
    Logger logger;
    
    // Option values:
    BoolContent fullscreen, hardware, resizable, noFrame;
    IntContent screenW,screenH, windowW,windowH;
}