1 //          Copyright Ferdinand Majerech 2014.
2 // Distributed under the Boost Software License, Version 1.0.
3 //    (See accompanying file LICENSE_1_0.txt or copy at
4 //          http://www.boost.org/LICENSE_1_0.txt)
5 
6 /// Manages windowing and GL and provides access to convenience OpenGL wrappers.
7 module platform.videodevice;
8 
9 
10 import std.algorithm;
11 import std.exception;
12 import std.experimental.logger;
13 import std.typecons;
14 
15 import derelict.opengl3.gl3;
16 import derelict.sdl2.sdl;
17 
18 
19 import gfmod.opengl.opengl;
20 
21 
22 /// Manages windowing and GL and provides access to convenience OpenGL wrappers.
23 class VideoDevice
24 {
25 private:
26     // The game log.
27     Logger log_;
28 
29     // OpenGL wrapper managing GL versions and information.
30 
31     OpenGL gl_;
32 
33     // The main game window.
34     SDL_Window* window_;
35 
36     // Zero-terminated title of the main game window.
37     char[1024] windowTitle_;
38 
39     // Window width.
40     size_t width_;
41 
42     // Window height.
43     size_t height_;
44 
45     // OpenGL context.
46     SDL_GLContext context_;
47 
48 public:
49     /**
50      * Construct a VideoDevice with specified OpenGL drawing to window.
51      *
52      * Note that to fully construct a VideoDevice you also need to call initWindow()
53      * and initGL().
54      *
55      * Params:
56      *
57      * log    = The game log.
58      */
59     this(Logger log) @safe pure nothrow @nogc
60     {
61         windowTitle_[] = 0;
62         log_    = log;
63     }
64 
65     /// Destroy the VideoDevice. Must be called by the user (use e.g. scoped!).
66     ~this()
67     {
68         if(gl_ !is null)                   { destroy(gl_); }
69         if(context_ != SDL_GLContext.init) { SDL_GL_DeleteContext(context_); }
70         if(window_ !is null)               { SDL_DestroyWindow(window_); }
71     }
72 
73     import platform.sdl2;
74     /**
75      * Initialize the GL window.
76      *
77      * Must be called before initGL.
78      */
79     bool initWindow(size_t width, size_t height, Flag!"fullscreen" fullscreen)
80         @trusted nothrow
81     {
82         assert(window_ is null, "Double initialization of the main window");
83         window_ = createGLWindow(width, height, fullscreen);
84         width_  = width;
85         height_ = height;
86 
87         // Exit if window creation fails.
88         if(null is window_)
89         {
90             log_.critical("Failed to create the application window").assumeWontThrow;
91             return false;
92         }
93         log_.infof("Created a%s window with dimensions %s x %s",
94                    fullscreen ? " fullscreen" : "", width, height).assumeWontThrow;
95         return true;
96     }
97 
98     /** Resize the viewport.
99      *
100      * Should be called after the window is resized.
101      *
102      * See_Also: InputDevice.resized
103      */
104     void resizeViewport(int width, int height) @system nothrow @nogc
105     {
106         width_  = width;
107         height_ = height;
108         glViewport(0, 0, width, height);
109     }
110 
111     /**
112      * Initialize the OpenGL context.
113      *
114      * Returns: true on success, false on failure.
115      */
116     bool initGL() @trusted nothrow
117     {
118         assert(window_ !is null, "Can't initialize GL without an initialized window");
119         assert(gl_ is null, "Double initialization of GL");
120 
121         SDL_GLContext context;
122         try
123         {
124             gl_      = new OpenGL(log_);
125             context_ = SDL_GL_CreateContext(window_);
126             if(gl_.reload() < GLVersion.GL30)
127             {
128                 log_.critical("Required OpenGL version 3.0 could not be loaded.");
129                 return false;
130             }
131         }
132         catch(OpenGLException e)
133         {
134             log_.critical("Failed to initialize OpenGL: ", e).assumeWontThrow;
135             return false;
136         }
137         catch(Exception e)
138         {
139             assert(false, "Unexpected exception in initGL: " ~ e.msg);
140         }
141         return true;
142     }
143 
144     /// Get window height.
145     ///
146     /// Signed because screen size is often multiplied by negative numbers.
147     long height() @safe pure nothrow const @nogc
148     {
149         return height_;
150     }
151 
152     /// Get window width.
153     ///
154     /// Signed because screen size is often multiplied by negative numbers.
155     long width() @safe pure nothrow const @nogc
156     {
157         return width_;
158     }
159 
160     /// Get access to the OpenGL API.
161     OpenGL gl() @safe pure nothrow @nogc 
162     {
163         assert(gl_ !is null, "Trying to access GL before it is initialized");
164         return gl_;
165     }
166 
167     /// Set the main window title. Any characters past 1023 will be truncated.
168     void windowTitle(string rhs) @trusted nothrow @nogc
169     {
170         // Zero-terminating in a fixed-size buffer.
171         const titleLength = min(rhs.length, windowTitle_.length - 1);
172         windowTitle_[0 .. titleLength] = rhs[0 .. titleLength];
173         windowTitle_[titleLength] = 0;
174         SDL_SetWindowTitle(window_, windowTitle_.ptr);
175     }
176 
177     /// Swap the front and back buffer at the end of a frame.
178     void swapBuffers() @trusted nothrow
179     {
180         assert(window_ !is null, "Can't swap buffers without an initialized window");
181         SDL_GL_SwapWindow(window_);
182     }
183 }