1 /*
2     C-Dogs SDL
3     A port of the legendary (and fun) action/arcade cdogs.
4     Copyright (C) 1995 Ronny Wester
5     Copyright (C) 2003 Jeremy Chin
6     Copyright (C) 2003-2007 Lucas Martin-King
7 
8     This program is free software; you can redistribute it and/or modify
9     it under the terms of the GNU General Public License as published by
10     the Free Software Foundation; either version 2 of the License, or
11     (at your option) any later version.
12 
13     This program is distributed in the hope that it will be useful,
14     but WITHOUT ANY WARRANTY; without even the implied warranty of
15     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16     GNU General Public License for more details.
17 
18     You should have received a copy of the GNU General Public License
19     along with this program; if not, write to the Free Software
20     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
21 
22     This file incorporates work covered by the following copyright and
23     permission notice:
24 
25     Copyright (c) 2013-2019 Cong Xu
26     All rights reserved.
27 
28     Redistribution and use in source and binary forms, with or without
29     modification, are permitted provided that the following conditions are met:
30 
31     Redistributions of source code must retain the above copyright notice, this
32     list of conditions and the following disclaimer.
33     Redistributions in binary form must reproduce the above copyright notice,
34     this list of conditions and the following disclaimer in the documentation
35     and/or other materials provided with the distribution.
36 
37     THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
38     AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
39     IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
40     ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
41     LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
42     CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
43     SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
44     INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
45     CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
46     ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
47     POSSIBILITY OF SUCH DAMAGE.
48 */
49 #include "grafx.h"
50 
51 #include <stdio.h>
52 #include <stdlib.h>
53 #include <fcntl.h>
54 #include <sys/types.h>
55 
56 #include <SDL_events.h>
57 #ifdef __EMSCRIPTEN__
58 #include <SDL2/SDL_image.h>
59 #else
60 #include <SDL_image.h>
61 #endif
62 #include <SDL_mouse.h>
63 
64 #include "blit.h"
65 #include "config.h"
66 #include "defs.h"
67 #include "draw/drawtools.h"
68 #include "font_utils.h"
69 #include "grafx_bg.h"
70 #include "log.h"
71 #include "palette.h"
72 #include "files.h"
73 #include "utils.h"
74 
75 
76 GraphicsDevice gGraphicsDevice;
77 
GraphicsInit(GraphicsDevice * device,Config * c)78 void GraphicsInit(GraphicsDevice *device, Config *c)
79 {
80 	memset(device, 0, sizeof *device);
81 	GraphicsConfigSetFromConfig(&device->cachedConfig, c);
82 	device->cachedConfig.RestartFlags = RESTART_ALL;
83 }
84 
85 // Initialises the video subsystem.
86 // To prevent needless screen flickering, config is compared with cache
87 // to see if anything changed. If not, don't recreate the screen.
GraphicsInitialize(GraphicsDevice * g)88 void GraphicsInitialize(GraphicsDevice *g)
89 {
90 	if (g->IsInitialized && !g->cachedConfig.RestartFlags)
91 	{
92 		return;
93 	}
94 
95 	if (!g->IsWindowInitialized)
96 	{
97 		char buf[CDOGS_PATH_MAX];
98 		GetDataFilePath(buf, "graphics/cdogs_icon.bmp");
99 		g->icon = IMG_Load(buf);
100 		g->IsWindowInitialized = true;
101 	}
102 
103 	g->IsInitialized = false;
104 
105 	const int w = g->cachedConfig.Res.x;
106 	const int h = g->cachedConfig.Res.y;
107 
108 	const bool initWindow =
109 		!!(g->cachedConfig.RestartFlags & RESTART_WINDOW);
110 	const bool initTextures =
111 		!!(g->cachedConfig.RestartFlags &
112 		(RESTART_WINDOW | RESTART_SCALE_MODE));
113 	const bool initBrightness =
114 		!!(g->cachedConfig.RestartFlags &
115 		(RESTART_WINDOW | RESTART_SCALE_MODE | RESTART_BRIGHTNESS));
116 
117 	if (initWindow)
118 	{
119 		LOG(LM_GFX, LL_INFO, "graphics mode(%dx%d %dx%s)",
120 			w, h, g->cachedConfig.ScaleFactor,
121 			g->cachedConfig.Fullscreen ? " fullscreen" : "");
122 
123 		Uint32 windowFlags = SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI;
124 		Rect2i windowDim = Rect2iNew(
125 			svec2i(SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED),
126 			svec2i(
127 				ConfigGetInt(&gConfig, "Graphics.WindowWidth"),
128 				ConfigGetInt(&gConfig, "Graphics.WindowHeight")
129 			)
130 		);
131 		if (g->cachedConfig.Fullscreen)
132 		{
133 			windowFlags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
134 			windowDim.Pos = svec2i_zero();
135 		}
136 		LOG(LM_GFX, LL_DEBUG, "destroying previous renderer");
137 		WindowContextDestroy(&g->gameWindow);
138 		WindowContextDestroy(&g->secondWindow);
139 		SDL_FreeFormat(g->Format);
140 
141 		char title[32];
142 		sprintf(title, "C-Dogs SDL %s%s",
143 			g->cachedConfig.IsEditor ? "Editor " : "",
144 			CDOGS_SDL_VERSION);
145 		if (!WindowContextCreate(
146 				&g->gameWindow, windowDim, windowFlags, title, g->icon,
147 				svec2i(w, h)))
148 		{
149 			return;
150 		}
151 		if (g->cachedConfig.SecondWindow)
152 		{
153 			if (!WindowContextCreate(
154 					&g->secondWindow, windowDim, windowFlags, title, g->icon,
155 					svec2i(w, h)))
156 			{
157 				return;
158 			}
159 			WindowsAdjustPosition(&g->gameWindow, &g->secondWindow);
160 		}
161 
162 		g->Format = SDL_AllocFormat(SDL_PIXELFORMAT_ARGB8888);
163 
164 		// Need to reload textures due to them tied to the renderer (window)
165 		PicManagerReloadTextures(&gPicManager);
166 		FontLoadFromJSON(&gFont, "graphics/font.png", "graphics/font.json");
167 	}
168 
169 	if (initTextures)
170 	{
171 		if (!initWindow)
172 		{
173 			WindowContextDestroyTextures(&g->gameWindow);
174 			WindowContextDestroyTextures(&g->secondWindow);
175 			WindowContextInitTextures(&g->gameWindow, svec2i(w, h));
176 			WindowContextInitTextures(&g->secondWindow, svec2i(w, h));
177 		}
178 
179 		GraphicsResetClip(g->gameWindow.renderer);
180 
181 		// Set render scale mode
182 		const char *renderScaleQuality = "nearest";
183 		switch ((ScaleMode)ConfigGetEnum(&gConfig, "Graphics.ScaleMode"))
184 		{
185 		case SCALE_MODE_NN:
186 			renderScaleQuality = "nearest";
187 			break;
188 		case SCALE_MODE_BILINEAR:
189 			renderScaleQuality = "linear";
190 			break;
191 		default:
192 			CASSERT(false, "unknown scale mode");
193 			break;
194 		}
195 		LOG(LM_GFX, LL_DEBUG, "setting scale quality %s", renderScaleQuality);
196 		if (!SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, renderScaleQuality))
197 		{
198 			LOG(LM_GFX, LL_WARN, "cannot set render quality hint: %s",
199 				SDL_GetError());
200 		}
201 
202 		CFREE(g->buf);
203 		CCALLOC(g->buf, GraphicsGetMemSize(&g->cachedConfig));
204 		g->bkgTgt = WindowContextCreateTexture(
205 			&g->gameWindow, SDL_TEXTUREACCESS_TARGET, svec2i(w, h),
206 			SDL_BLENDMODE_NONE, 255, true);
207 		if (g->bkgTgt == NULL)
208 		{
209 			return;
210 		}
211 		g->bkg = WindowContextCreateTexture(
212 			&g->gameWindow, SDL_TEXTUREACCESS_STATIC, svec2i(w, h),
213 			SDL_BLENDMODE_BLEND, 255, true);
214 		if (g->bkg == NULL)
215 		{
216 			return;
217 		}
218 		if (g->cachedConfig.SecondWindow)
219 		{
220 			g->bkgTgt2 = WindowContextCreateTexture(
221 				&g->secondWindow, SDL_TEXTUREACCESS_TARGET, svec2i(w, h),
222 				SDL_BLENDMODE_NONE, 255, true);
223 			if (g->bkgTgt2 == NULL)
224 			{
225 				return;
226 			}
227 			g->bkg2 = WindowContextCreateTexture(
228 				&g->secondWindow, SDL_TEXTUREACCESS_STATIC, svec2i(w, h),
229 				SDL_BLENDMODE_BLEND, 255, true);
230 			if (g->bkg2 == NULL)
231 			{
232 				return;
233 			}
234 		}
235 
236 		g->screen = WindowContextCreateTexture(
237 			&g->gameWindow, SDL_TEXTUREACCESS_STREAMING, svec2i(w, h),
238 			SDL_BLENDMODE_BLEND, 255, false);
239 		if (g->screen == NULL)
240 		{
241 			return;
242 		}
243 
244 		g->hud = WindowContextCreateTexture(
245 			&g->gameWindow, SDL_TEXTUREACCESS_STREAMING, svec2i(w, h),
246 			SDL_BLENDMODE_BLEND, 255, false);
247 		if (g->hud == NULL)
248 		{
249 			return;
250 		}
251 		BlitClearBuf(g);
252 		BlitUpdateFromBuf(g, g->hud);
253 
254 		if (g->cachedConfig.SecondWindow)
255 		{
256 			g->hud2 = WindowContextCreateTexture(
257 				&g->secondWindow, SDL_TEXTUREACCESS_STREAMING, svec2i(w, h),
258 				SDL_BLENDMODE_BLEND, 255, false);
259 			if (g->hud2 == NULL)
260 			{
261 				return;
262 			}
263 			BlitClearBuf(g);
264 			BlitUpdateFromBuf(g, g->hud2);
265 		}
266 	}
267 
268 	if (initBrightness)
269 	{
270 		if (!initWindow && !initTextures)
271 		{
272 			SDL_DestroyTexture(g->brightnessOverlay);
273 		}
274 
275 		const int brightness = ConfigGetInt(&gConfig, "Graphics.Brightness");
276 		// Alpha is approximately 50% max
277 		const Uint8 alpha =
278 			(Uint8)(brightness > 0 ? brightness : -brightness) * 13;
279 		g->brightnessOverlay = WindowContextCreateTexture(
280 			&g->gameWindow, SDL_TEXTUREACCESS_STATIC, svec2i(w, h),
281 			SDL_BLENDMODE_BLEND, alpha, false);
282 		if (g->brightnessOverlay == NULL)
283 		{
284 			return;
285 		}
286 		const color_t overlayColour = brightness > 0 ? colorWhite : colorBlack;
287 		BlitFillBuf(g, overlayColour);
288 		BlitUpdateFromBuf(g, g->brightnessOverlay);
289 		g->cachedConfig.Brightness = brightness;
290 	}
291 
292 	g->IsInitialized = true;
293 	g->cachedConfig.Res.x = w;
294 	g->cachedConfig.Res.y = h;
295 	g->cachedConfig.RestartFlags = 0;
296 }
297 
GraphicsTerminate(GraphicsDevice * g)298 void GraphicsTerminate(GraphicsDevice *g)
299 {
300 	SDL_FreeSurface(g->icon);
301 	WindowContextDestroy(&g->gameWindow);
302 	WindowContextDestroy(&g->secondWindow);
303 	SDL_FreeFormat(g->Format);
304 	SDL_VideoQuit();
305 	CFREE(g->buf);
306 }
307 
GraphicsGetScreenSize(GraphicsConfig * config)308 int GraphicsGetScreenSize(GraphicsConfig *config)
309 {
310 	return config->Res.x * config->Res.y;
311 }
312 
GraphicsGetMemSize(GraphicsConfig * config)313 int GraphicsGetMemSize(GraphicsConfig *config)
314 {
315 	return GraphicsGetScreenSize(config) * sizeof(Uint32);
316 }
317 
GraphicsConfigSet(GraphicsConfig * c,struct vec2i windowSize,const bool fullscreen,const int scaleFactor,const ScaleMode scaleMode,const int brightness,const bool secondWindow)318 void GraphicsConfigSet(
319 	GraphicsConfig *c,
320 	struct vec2i windowSize, const bool fullscreen,
321 	const int scaleFactor, const ScaleMode scaleMode, const int brightness,
322 	const bool secondWindow)
323 {
324 #define SET(_lhs, _rhs, _flag) \
325 	if ((_lhs) != (_rhs)) \
326 	{ \
327 		(_lhs) = (_rhs); \
328 		c->RestartFlags |= (_flag); \
329 	}
330 	SET(c->Fullscreen, fullscreen, RESTART_WINDOW);
331 	if (c->Fullscreen)
332 	{
333 		// Set to native resolution
334 		SDL_DisplayMode dm;
335 		if (SDL_GetCurrentDisplayMode(0, &dm) != 0)
336 		{
337 			LOG(LM_GFX, LL_WARN, "cannot get display mode: %s",
338 				SDL_GetError());
339 		}
340 		else
341 		{
342 			windowSize = svec2i(dm.w, dm.h);
343 			ConfigSetInt(&gConfig, "Graphics.WindowWidth", dm.w);
344 			ConfigSetInt(&gConfig, "Graphics.WindowHeight", dm.h);
345 		}
346 	}
347 	SET(c->ScaleFactor, scaleFactor, RESTART_SCALE_MODE);
348 	SET(c->ScaleMode, scaleMode, RESTART_SCALE_MODE);
349 	SET(c->Brightness, brightness, RESTART_BRIGHTNESS);
350 	SET(c->SecondWindow, secondWindow, RESTART_WINDOW);
351 	const struct vec2i res = svec2i_scale_divide(windowSize, scaleFactor);
352 	if (!svec2i_is_equal(res, c->Res))
353 	{
354 		c->Res = res;
355 		c->RestartFlags |= RESTART_SCALE_MODE;
356 	}
357 }
358 
GraphicsConfigSetFromConfig(GraphicsConfig * gc,Config * c)359 void GraphicsConfigSetFromConfig(GraphicsConfig *gc, Config *c)
360 {
361 	GraphicsConfigSet(
362 		gc,
363 		svec2i(
364 			ConfigGetInt(c, "Graphics.WindowWidth"),
365 			ConfigGetInt(c, "Graphics.WindowHeight")),
366 		ConfigGetBool(c, "Graphics.Fullscreen"),
367 		ConfigGetInt(c, "Graphics.ScaleFactor"),
368 		(ScaleMode)ConfigGetEnum(c, "Graphics.ScaleMode"),
369 		ConfigGetInt(c, "Graphics.Brightness"),
370 		ConfigGetBool(c, "Graphics.SecondWindow"));
371 }
372 
GraphicsSetClip(SDL_Renderer * renderer,const Rect2i r)373 void GraphicsSetClip(SDL_Renderer *renderer, const Rect2i r)
374 {
375 	const SDL_Rect rect = { r.Pos.x, r.Pos.y, r.Size.x, r.Size.y };
376 	if (SDL_RenderSetClipRect(renderer, Rect2iIsZero(r) ? NULL : &rect) != 0)
377 	{
378 		LOG(LM_MAIN, LL_ERROR, "Could not set clip rect: %s", SDL_GetError());
379 	}
380 }
381 
GraphicsGetClip(SDL_Renderer * renderer)382 Rect2i GraphicsGetClip(SDL_Renderer *renderer)
383 {
384 	SDL_Rect rect;
385 	SDL_RenderGetClipRect(renderer, &rect);
386 	return Rect2iNew(svec2i(rect.x, rect.y), svec2i(rect.w, rect.h));
387 }
388 
GraphicsResetClip(SDL_Renderer * renderer)389 void GraphicsResetClip(SDL_Renderer *renderer)
390 {
391 	if (SDL_RenderSetClipRect(renderer, NULL) != 0)
392 	{
393 		LOG(LM_MAIN, LL_ERROR, "Could not reset clip rect: %s", SDL_GetError());
394 	}
395 }
396