1 #include "platform/screen.h"
2
3 #include "core/calc.h"
4 #include "core/config.h"
5 #include "game/settings.h"
6 #include "game/system.h"
7 #include "graphics/graphics.h"
8 #include "graphics/screen.h"
9 #include "input/cursor.h"
10 #include "platform/android/android.h"
11 #include "platform/cursor.h"
12 #include "platform/haiku/haiku.h"
13 #include "platform/switch/switch.h"
14 #include "platform/vita/vita.h"
15
16 #include "SDL.h"
17
18 #include <stdlib.h>
19
20 static struct {
21 SDL_Window *window;
22 SDL_Renderer *renderer;
23 SDL_Texture *texture;
24 SDL_Texture *cursors[CURSOR_MAX];
25 } SDL;
26
27 static struct {
28 int x;
29 int y;
30 int centered;
31 } window_pos = {0, 0, 1};
32
33 static struct {
34 const int WIDTH;
35 const int HEIGHT;
36 } MINIMUM = {640, 480};
37
38 static int scale_percentage = 100;
39 static color_t *framebuffer;
40
scale_logical_to_pixels(int logical_value)41 static int scale_logical_to_pixels(int logical_value)
42 {
43 return logical_value * scale_percentage / 100;
44 }
45
scale_pixels_to_logical(int pixel_value)46 static int scale_pixels_to_logical(int pixel_value)
47 {
48 return pixel_value * 100 / scale_percentage;
49 }
50
get_max_scale_percentage(int pixel_width,int pixel_height)51 static int get_max_scale_percentage(int pixel_width, int pixel_height)
52 {
53 int width_scale_pct = pixel_width * 100 / MINIMUM.WIDTH;
54 int height_scale_pct = pixel_height * 100 / MINIMUM.HEIGHT;
55 return SDL_min(width_scale_pct, height_scale_pct);
56 }
57
set_scale_percentage(int new_scale,int pixel_width,int pixel_height)58 static void set_scale_percentage(int new_scale, int pixel_width, int pixel_height)
59 {
60 #ifdef __vita__
61 scale_percentage = 100;
62 #else
63 scale_percentage = calc_bound(new_scale, 50, 500);
64 #endif
65
66 if (!pixel_width || !pixel_height) {
67 return;
68 }
69
70 int max_scale_pct = get_max_scale_percentage(pixel_width, pixel_height);
71 if (max_scale_pct < scale_percentage) {
72 scale_percentage = max_scale_pct;
73 SDL_Log("Maximum scale of %i applied", scale_percentage);
74 }
75
76 SDL_SetWindowMinimumSize(SDL.window,
77 scale_logical_to_pixels(MINIMUM.WIDTH), scale_logical_to_pixels(MINIMUM.HEIGHT));
78
79 const char *scale_quality = "linear";
80 #ifndef __APPLE__
81 // Scale using nearest neighbour when we scale a multiple of 100%: makes it look sharper.
82 // But not on MacOS: users are used to the linear interpolation since that's what Apple also does.
83 if (scale_percentage % 100 == 0) {
84 scale_quality = "nearest";
85 }
86 #endif
87 SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, scale_quality);
88 }
89
90 #ifdef __ANDROID__
set_scale_for_screen(int pixel_width,int pixel_height)91 static void set_scale_for_screen(int pixel_width, int pixel_height)
92 {
93 set_scale_percentage(android_get_screen_density() * 100, pixel_width, pixel_height);
94 config_set(CONFIG_SCREEN_CURSOR_SCALE, scale_percentage);
95 if (SDL.texture) {
96 system_init_cursors(scale_percentage);
97 }
98 SDL_Log("Auto-setting scale to %i", scale_percentage);
99 }
100 #endif
101
platform_screen_create(const char * title,int display_scale_percentage)102 int platform_screen_create(const char *title, int display_scale_percentage)
103 {
104 set_scale_percentage(display_scale_percentage, 0, 0);
105
106 int width, height;
107 int fullscreen = system_is_fullscreen_only() ? 1 : setting_fullscreen();
108 if (fullscreen) {
109 SDL_DisplayMode mode;
110 SDL_GetDesktopDisplayMode(0, &mode);
111 width = mode.w;
112 height = mode.h;
113 } else {
114 setting_window(&width, &height);
115 width = scale_logical_to_pixels(width);
116 height = scale_logical_to_pixels(height);
117 }
118
119 platform_screen_destroy();
120
121 #ifdef __ANDROID__
122 // Fix for wrong colors on some android devices
123 SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5);
124 SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 6);
125 SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5);
126 #endif
127
128 SDL_Log("Creating screen %d x %d, %s, driver: %s", width, height,
129 fullscreen ? "fullscreen" : "windowed", SDL_GetCurrentVideoDriver());
130 Uint32 flags = SDL_WINDOW_RESIZABLE;
131
132 #if SDL_VERSION_ATLEAST(2, 0, 1)
133 flags |= SDL_WINDOW_ALLOW_HIGHDPI;
134 #endif
135
136 if (fullscreen) {
137 flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
138 }
139 SDL.window = SDL_CreateWindow(title,
140 SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
141 width, height, flags);
142
143 if (!SDL.window) {
144 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Unable to create window: %s", SDL_GetError());
145 return 0;
146 }
147
148 if (system_is_fullscreen_only()) {
149 SDL_GetWindowSize(SDL.window, &width, &height);
150 }
151
152 SDL_Log("Creating renderer");
153 SDL.renderer = SDL_CreateRenderer(SDL.window, -1, SDL_RENDERER_PRESENTVSYNC);
154 if (!SDL.renderer) {
155 SDL_Log("Unable to create renderer, trying software renderer: %s", SDL_GetError());
156 SDL.renderer = SDL_CreateRenderer(SDL.window, -1, SDL_RENDERER_SOFTWARE);
157 if (!SDL.renderer) {
158 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Unable to create renderer: %s", SDL_GetError());
159 return 0;
160 }
161 }
162
163 #if !defined(__APPLE__)
164 if (fullscreen && SDL_GetNumVideoDisplays() > 1) {
165 SDL_SetWindowGrab(SDL.window, SDL_TRUE);
166 }
167 #endif
168
169 set_scale_percentage(display_scale_percentage, width, height);
170 return platform_screen_resize(width, height);
171 }
172
destroy_screen_texture(void)173 static void destroy_screen_texture(void)
174 {
175 SDL_DestroyTexture(SDL.texture);
176 SDL.texture = 0;
177 }
178
platform_screen_destroy(void)179 void platform_screen_destroy(void)
180 {
181 if (SDL.texture) {
182 destroy_screen_texture();
183 }
184 if (SDL.renderer) {
185 SDL_DestroyRenderer(SDL.renderer);
186 SDL.renderer = 0;
187 }
188 if (SDL.window) {
189 SDL_DestroyWindow(SDL.window);
190 SDL.window = 0;
191 }
192 }
193
platform_screen_resize(int pixel_width,int pixel_height)194 int platform_screen_resize(int pixel_width, int pixel_height)
195 {
196 #ifdef __ANDROID__
197 set_scale_for_screen(pixel_width, pixel_height);
198 #endif
199
200 int logical_width = scale_pixels_to_logical(pixel_width);
201 int logical_height = scale_pixels_to_logical(pixel_height);
202
203 if (SDL.texture) {
204 if (logical_width == screen_width() && logical_height == screen_height()) {
205 return 1;
206 }
207 destroy_screen_texture();
208 }
209
210 SDL_RenderSetLogicalSize(SDL.renderer, logical_width, logical_height);
211
212 setting_set_display(setting_fullscreen(), logical_width, logical_height);
213 SDL.texture = SDL_CreateTexture(SDL.renderer,
214 SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING,
215 logical_width, logical_height);
216
217 if (SDL.texture) {
218 SDL_Log("Texture created: %d x %d", logical_width, logical_height);
219 screen_set_resolution(logical_width, logical_height);
220 return 1;
221 } else {
222 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Unable to create texture: %s", SDL_GetError());
223 return 0;
224 }
225 }
226
system_scale_display(int display_scale_percentage)227 int system_scale_display(int display_scale_percentage)
228 {
229 int width, height;
230 SDL_GetWindowSize(SDL.window, &width, &height);
231 set_scale_percentage(display_scale_percentage, width, height);
232 platform_screen_resize(width, height);
233 return scale_percentage;
234 }
235
system_get_max_display_scale(void)236 int system_get_max_display_scale(void)
237 {
238 int width, height;
239 SDL_GetWindowSize(SDL.window, &width, &height);
240 return get_max_scale_percentage(width, height);
241 }
242
platform_screen_move(int x,int y)243 void platform_screen_move(int x, int y)
244 {
245 if (!setting_fullscreen()) {
246 window_pos.x = x;
247 window_pos.y = y;
248 window_pos.centered = 0;
249 }
250 }
251
platform_screen_set_fullscreen(void)252 void platform_screen_set_fullscreen(void)
253 {
254 SDL_GetWindowPosition(SDL.window, &window_pos.x, &window_pos.y);
255 int display = SDL_GetWindowDisplayIndex(SDL.window);
256 SDL_DisplayMode mode;
257 SDL_GetDesktopDisplayMode(display, &mode);
258 SDL_Log("User to fullscreen %d x %d on display %d", mode.w, mode.h, display);
259 if (0 != SDL_SetWindowFullscreen(SDL.window, SDL_WINDOW_FULLSCREEN_DESKTOP)) {
260 SDL_Log("Unable to enter fullscreen: %s", SDL_GetError());
261 return;
262 }
263 SDL_SetWindowDisplayMode(SDL.window, &mode);
264
265 #if !defined(__APPLE__)
266 if (SDL_GetNumVideoDisplays() > 1) {
267 SDL_SetWindowGrab(SDL.window, SDL_TRUE);
268 }
269 #endif
270 setting_set_display(1, mode.w, mode.h);
271 }
272
platform_screen_set_windowed(void)273 void platform_screen_set_windowed(void)
274 {
275 if (system_is_fullscreen_only()) {
276 return;
277 }
278 int logical_width, logical_height;
279 setting_window(&logical_width, &logical_height);
280 int pixel_width = scale_logical_to_pixels(logical_width);
281 int pixel_height = scale_logical_to_pixels(logical_height);
282 int display = SDL_GetWindowDisplayIndex(SDL.window);
283 SDL_Log("User to windowed %d x %d on display %d", pixel_width, pixel_height, display);
284 SDL_SetWindowFullscreen(SDL.window, 0);
285 SDL_SetWindowSize(SDL.window, pixel_width, pixel_height);
286 if (window_pos.centered) {
287 platform_screen_center_window();
288 }
289 if (SDL_GetWindowGrab(SDL.window) == SDL_TRUE) {
290 SDL_SetWindowGrab(SDL.window, SDL_FALSE);
291 }
292 setting_set_display(0, pixel_width, pixel_height);
293 }
294
platform_screen_set_window_size(int logical_width,int logical_height)295 void platform_screen_set_window_size(int logical_width, int logical_height)
296 {
297 if (system_is_fullscreen_only()) {
298 return;
299 }
300 int pixel_width = scale_logical_to_pixels(logical_width);
301 int pixel_height = scale_logical_to_pixels(logical_height);
302 int display = SDL_GetWindowDisplayIndex(SDL.window);
303 if (setting_fullscreen()) {
304 SDL_SetWindowFullscreen(SDL.window, 0);
305 } else {
306 SDL_GetWindowPosition(SDL.window, &window_pos.x, &window_pos.y);
307 }
308 if (SDL_GetWindowFlags(SDL.window) & SDL_WINDOW_MAXIMIZED) {
309 SDL_RestoreWindow(SDL.window);
310 }
311 SDL_SetWindowSize(SDL.window, pixel_width, pixel_height);
312 if (window_pos.centered) {
313 platform_screen_center_window();
314 }
315 SDL_Log("User resize to %d x %d on display %d", pixel_width, pixel_height, display);
316 if (SDL_GetWindowGrab(SDL.window) == SDL_TRUE) {
317 SDL_SetWindowGrab(SDL.window, SDL_FALSE);
318 }
319 setting_set_display(0, pixel_width, pixel_height);
320 }
321
platform_screen_center_window(void)322 void platform_screen_center_window(void)
323 {
324 int display = SDL_GetWindowDisplayIndex(SDL.window);
325 SDL_SetWindowPosition(SDL.window,
326 SDL_WINDOWPOS_CENTERED_DISPLAY(display), SDL_WINDOWPOS_CENTERED_DISPLAY(display));
327 window_pos.centered = 1;
328 }
329
330 #ifdef PLATFORM_USE_SOFTWARE_CURSOR
draw_software_mouse_cursor(void)331 static void draw_software_mouse_cursor(void)
332 {
333 const mouse *mouse = mouse_get();
334 if (!mouse->is_touch) {
335 cursor_shape current_cursor_shape = platform_cursor_get_current_shape();
336 const cursor *c = input_cursor_data(current_cursor_shape, platform_cursor_get_current_scale());
337 if (c) {
338 int size = platform_cursor_get_texture_size(c->width, c->height);
339 size = calc_adjust_with_percentage(size, calc_percentage(100, scale_percentage));
340 SDL_Rect dst;
341 dst.x = mouse->x - c->hotspot_x;
342 dst.y = mouse->y - c->hotspot_y;
343 dst.w = size;
344 dst.h = size;
345 SDL_RenderCopy(SDL.renderer, SDL.cursors[current_cursor_shape], NULL, &dst);
346 }
347 }
348 }
349 #endif
350
351 #ifdef _WIN32
platform_screen_recreate_texture(void)352 void platform_screen_recreate_texture(void)
353 {
354 // On Windows, if ctrl + alt + del is pressed during fullscreen, the rendering context may be lost for a few frames
355 // after restoring the window, preventing the texture from being recreated. This forces an attempt to recreate the
356 // texture every frame to bypass that issue.
357 if (!SDL.texture && SDL.renderer && setting_fullscreen()) {
358 SDL_DisplayMode mode;
359 SDL_GetWindowDisplayMode(SDL.window, &mode);
360 screen_set_resolution(scale_pixels_to_logical(mode.w), scale_pixels_to_logical(mode.h));
361 SDL.texture = SDL_CreateTexture(SDL.renderer,
362 SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING,
363 screen_width(), screen_height());
364 }
365 }
366 #endif
367
platform_screen_clear(void)368 void platform_screen_clear(void)
369 {
370 SDL_RenderClear(SDL.renderer);
371 }
372
platform_screen_update(void)373 void platform_screen_update(void)
374 {
375 SDL_RenderClear(SDL.renderer);
376 #ifndef __vita__
377 SDL_UpdateTexture(SDL.texture, NULL, graphics_canvas(), screen_width() * 4);
378 #endif
379 SDL_RenderCopy(SDL.renderer, SDL.texture, NULL, NULL);
380 #ifdef PLATFORM_USE_SOFTWARE_CURSOR
381 draw_software_mouse_cursor();
382 #endif
383 }
384
platform_screen_render(void)385 void platform_screen_render(void)
386 {
387 SDL_RenderPresent(SDL.renderer);
388 }
389
platform_screen_generate_mouse_cursor_texture(int cursor_id,int scale,const color_t * cursor_colors)390 void platform_screen_generate_mouse_cursor_texture(int cursor_id, int scale, const color_t *cursor_colors)
391 {
392 if (SDL.cursors[cursor_id]) {
393 SDL_DestroyTexture(SDL.cursors[cursor_id]);
394 SDL.cursors[cursor_id] = 0;
395 }
396 const cursor *c = input_cursor_data(cursor_id, scale);
397 int size = platform_cursor_get_texture_size(c->width, c->height);
398 SDL.cursors[cursor_id] = SDL_CreateTexture(SDL.renderer,
399 SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STATIC,
400 size, size);
401 if (!SDL.cursors[cursor_id]) {
402 return;
403 }
404 SDL_UpdateTexture(SDL.cursors[cursor_id], NULL, cursor_colors, size * sizeof(color_t));
405 SDL_SetTextureBlendMode(SDL.cursors[cursor_id], SDL_BLENDMODE_BLEND);
406 }
407
system_set_mouse_position(int * x,int * y)408 void system_set_mouse_position(int *x, int *y)
409 {
410 *x = calc_bound(*x, 0, screen_width() - 1);
411 *y = calc_bound(*y, 0, screen_height() - 1);
412 SDL_WarpMouseInWindow(SDL.window, scale_logical_to_pixels(*x), scale_logical_to_pixels(*y));
413 }
414
system_is_fullscreen_only(void)415 int system_is_fullscreen_only(void)
416 {
417 #if defined(__ANDROID__) || defined(__SWITCH__) || defined(__vita__)
418 return 1;
419 #else
420 return 0;
421 #endif
422 }
423
system_create_framebuffer(int width,int height)424 color_t *system_create_framebuffer(int width, int height)
425 {
426 #ifdef __vita__
427 int pitch;
428 SDL_LockTexture(SDL.texture, NULL, (void **) &framebuffer, &pitch);
429 SDL_UnlockTexture(SDL.texture);
430 #else
431 free(framebuffer);
432 framebuffer = (color_t *) malloc((size_t) width * height * sizeof(color_t));
433 #endif
434 return framebuffer;
435 }
436