1 /**
2  * Common code shared by SDL-based video drivers.
3  *
4  * License:
5  *   This Source Code Form is subject to the terms of
6  *   the Mozilla Public License, v. 2.0. If a copy of
7  *   the MPL was not distributed with this file, You
8  *   can obtain one at http://mozilla.org/MPL/2.0/.
9  *
10  * Authors:
11  *   Vladimir Panteleev <ae@cy.md>
12  */
13 
14 module ae.ui.video.sdl2common.video;
15 
16 import std.process : environment;
17 import std.string;
18 
19 import derelict.sdl2.sdl;
20 
21 import ae.ui.video.threadedvideo;
22 import ae.ui.app.application;
23 import ae.ui.shell.shell;
24 import ae.ui.shell.sdl2.shell;
25 
26 /// `Video` implementation for SDL2.
27 class SDL2CommonVideo : ThreadedVideo
28 {
29 	override void getScreenSize(out uint width, out uint height)
30 	{
31 		width = screenWidth;
32 		height = screenHeight;
33 	} ///
34 
35 	override void shutdown()
36 	{
37 		super.shutdown();
38 		if (window)
39 		{
40 			SDL_DestroyWindow(window);
41 			window = null;
42 		}
43 	} ///
44 
45 	SDL_Window* window; /// SDL window object.
46 	SDL_Renderer* renderer; /// SDL renderer object.
47 
48 protected:
49 	override @property bool initializeVideoInRenderThread()
50 	{
51 		return true;
52 	}
53 
54 	uint getSDLFlags     () { return 0; }
55 	SDL_RendererFlags getRendererFlags() { return SDL_RENDERER_PRESENTVSYNC; }
56 	void prepare() {}
57 
58 	uint screenWidth, screenHeight;
59 
60 	/// Main thread initialization.
61 	override void initMain(Application application)
62 	{
63 		SDL_WindowFlags flags = SDL_WINDOW_SHOWN;
64 		flags |= getSDLFlags();
65 
66 		auto settings = application.getShellSettings();
67 		screenWidth = screenHeight = 0;
68 		uint windowPosX = SDL_WINDOWPOS_UNDEFINED, windowPosY = SDL_WINDOWPOS_UNDEFINED;
69 
70 		final switch (settings.screenMode)
71 		{
72 			case ScreenMode.windowed:
73 				screenWidth  = settings.windowSizeX;
74 				screenHeight = settings.windowSizeY;
75 				windowPosX = settings.windowPosX == int.min ? SDL_WINDOWPOS_CENTERED : settings.windowPosX;
76 				windowPosY = settings.windowPosY == int.min ? SDL_WINDOWPOS_CENTERED : settings.windowPosY;
77 				break;
78 			case ScreenMode.maximized:
79 				flags |= SDL_WINDOW_MAXIMIZED;
80 				break;
81 			case ScreenMode.fullscreen:
82 				screenWidth  = settings.fullScreenX;
83 				screenHeight = settings.fullScreenY;
84 				flags |= SDL_WINDOW_FULLSCREEN;
85 				break;
86 			case ScreenMode.windowedFullscreen:
87 			{
88 				SDL_DisplayMode dm;
89 				sdlEnforce(SDL_GetDesktopDisplayMode(0, &dm)==0, "Can't get desktop display mode");
90 				windowPosX = 0;
91 				windowPosY = 0;
92 				screenWidth  = dm.w;
93 				screenHeight = dm.h;
94 				flags |= SDL_WINDOW_BORDERLESS;
95 				break;
96 			}
97 		}
98 
99 		if (application.isResizable())
100 			flags |= SDL_WINDOW_RESIZABLE;
101 
102 		if (window)
103 		{
104 			// We need to recreate the window if renderer flags,
105 			// such as SDL_WINDOW_OPENGL, have changed.
106 			// Also recreate when switching fullscreen modes.
107 			enum recreateMask =
108 				SDL_WINDOW_OPENGL |
109 				SDL_WINDOW_FULLSCREEN |
110 				SDL_WINDOW_BORDERLESS |
111 				SDL_WINDOW_RESIZABLE;
112 			if ((currentFlags & recreateMask) != (flags & recreateMask)
113 			 || (flags & SDL_WINDOW_FULLSCREEN))
114 			{
115 				SDL_DestroyWindow(window);
116 				window = null;
117 			}
118 		}
119 
120 		if (window)
121 		{
122 			// Adjust parameters of existing window.
123 
124 			if (windowPosX != SDL_WINDOWPOS_UNDEFINED && windowPosY != SDL_WINDOWPOS_UNDEFINED)
125 			{
126 				int currentX, currentY;
127 				SDL_GetWindowPosition(window, &currentX, &currentY);
128 				if (currentX != windowPosX || currentY != windowPosY)
129 					SDL_SetWindowPosition(window, windowPosX, windowPosY);
130 			}
131 			if (screenWidth && screenHeight)
132 			{
133 				int currentW, currentH;
134 				SDL_GetWindowSize(window, &currentW, &currentH);
135 				if (currentW != screenWidth || currentH != screenHeight)
136 					SDL_SetWindowSize(window, screenWidth, screenHeight);
137 			}
138 		}
139 		else
140 		{
141 			// Create a new window.
142 
143 			// Window must always be created in the main (SDL event) thread,
144 			// otherwise we get Win32 deadlocks due to messages being sent
145 			// to the render thread.
146 			// As a result, if the event thread does something that results
147 			// in a Windows message, the message gets put on the render thread
148 			// message queue. However, while waiting for the message to be
149 			// processed, the event thread holds the application global lock,
150 			// and the render thread is waiting on it - thus resulting in a
151 			// deadlock.
152 
153 			window = sdlEnforce(
154 				SDL_CreateWindow(
155 					toStringz(application.getName()),
156 					windowPosX, windowPosY,
157 					screenWidth, screenHeight,
158 					flags),
159 				"Can't create window");
160 		}
161 
162 		currentFlags = flags;
163 	}
164 
165 	/// Main/render thread initialization (depends on InitializeVideoInRenderThread).
166 	override void initVary()
167 	{
168 		prepare();
169 		renderer = sdlEnforce(SDL_CreateRenderer(window, -1, getRendererFlags()), "Can't create renderer");
170 	}
171 
172 	/// Main/render thread finalization (depends on InitializeVideoInRenderThread).
173 	override void doneVary()
174 	{
175 		SDL_DestroyRenderer(renderer); renderer = null;
176 	}
177 
178 	/// Main thread finalization.
179 	override void doneMain() {}
180 
181 private:
182 	uint currentFlags;
183 }