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