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