view mde/setup/Screen.d @ 98:49e7cfed4b34

All types of Option have been converted to use ValueContent classes, and their values can be displayed.
author Diggory Hardy <diggory.hardy@gmail.com>
date Wed, 12 Nov 2008 13:18:51 +0000
parents 30470bc19ca4
children ee209602770d
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;
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 VideoOptions : 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
        debug 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 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 (videoOpts.hardware()) flags |= SDL_HWSURFACE | SDL_DOUBLEBUF;
        else flags |= SDL_SWSURFACE;
        int w, h;
        if (videoOpts.fullscreen()) {
            flags |= SDL_FULLSCREEN;
            w = videoOpts.screenW();
            h = videoOpts.screenH();
        }
        else {
            if (videoOpts.resizable()) flags |= SDL_RESIZABLE;
            if (videoOpts.noFrame()) flags |= SDL_NOFRAME;
            w = videoOpts.windowW();
            h = videoOpts.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 (videoOpts.fullscreen()) {       // probably resizeEvent only called when not fullscreen
            videoOpts.screenW = w;
            videoOpts.screenH = h;
        } else {
            videoOpts.windowW = w;
            videoOpts.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 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");
        
        videoOpts = new VideoOptions;
        Options.addOptionsClass (videoOpts, "VideoOptions");
    }
    
    // DATA:
private:
    uint flags = 0;
    IDrawable[] drawables;
    Logger logger;
    VideoOptions videoOpts;
}