1 /*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2021 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20 */
21
22 #include "../../SDL_internal.h"
23
24 #if SDL_VIDEO_DRIVER_RPI
25
26 /* References
27 * http://elinux.org/RPi_VideoCore_APIs
28 * https://github.com/raspberrypi/firmware/blob/master/opt/vc/src/hello_pi/hello_triangle/triangle.c
29 * http://cgit.freedesktop.org/wayland/weston/tree/src/rpi-renderer.c
30 * http://cgit.freedesktop.org/wayland/weston/tree/src/compositor-rpi.c
31 */
32
33 /* SDL internals */
34 #include "../SDL_sysvideo.h"
35 #include "SDL_version.h"
36 #include "SDL_syswm.h"
37 #include "SDL_loadso.h"
38 #include "SDL_events.h"
39 #include "../../events/SDL_mouse_c.h"
40 #include "../../events/SDL_keyboard_c.h"
41 #include "SDL_hints.h"
42
43 #ifdef SDL_INPUT_LINUXEV
44 #include "../../core/linux/SDL_evdev.h"
45 #endif
46
47 /* RPI declarations */
48 #include "SDL_rpivideo.h"
49 #include "SDL_rpievents_c.h"
50 #include "SDL_rpiopengles.h"
51 #include "SDL_rpimouse.h"
52
53 static void
RPI_Destroy(SDL_VideoDevice * device)54 RPI_Destroy(SDL_VideoDevice * device)
55 {
56 SDL_free(device->driverdata);
57 SDL_free(device);
58 }
59
60 static int
RPI_GetRefreshRate()61 RPI_GetRefreshRate()
62 {
63 TV_DISPLAY_STATE_T tvstate;
64 if (vc_tv_get_display_state( &tvstate ) == 0) {
65 //The width/height parameters are in the same position in the union
66 //for HDMI and SDTV
67 HDMI_PROPERTY_PARAM_T property;
68 property.property = HDMI_PROPERTY_PIXEL_CLOCK_TYPE;
69 vc_tv_hdmi_get_property(&property);
70 return property.param1 == HDMI_PIXEL_CLOCK_TYPE_NTSC ?
71 tvstate.display.hdmi.frame_rate * (1000.0f/1001.0f) :
72 tvstate.display.hdmi.frame_rate;
73 }
74 return 60; /* Failed to get display state, default to 60 */
75 }
76
77 static SDL_VideoDevice *
RPI_Create()78 RPI_Create()
79 {
80 SDL_VideoDevice *device;
81 SDL_VideoData *phdata;
82
83 /* Initialize SDL_VideoDevice structure */
84 device = (SDL_VideoDevice *) SDL_calloc(1, sizeof(SDL_VideoDevice));
85 if (device == NULL) {
86 SDL_OutOfMemory();
87 return NULL;
88 }
89
90 /* Initialize internal data */
91 phdata = (SDL_VideoData *) SDL_calloc(1, sizeof(SDL_VideoData));
92 if (phdata == NULL) {
93 SDL_OutOfMemory();
94 SDL_free(device);
95 return NULL;
96 }
97
98 device->driverdata = phdata;
99
100 /* Setup amount of available displays */
101 device->num_displays = 0;
102
103 /* Set device free function */
104 device->free = RPI_Destroy;
105
106 /* Setup all functions which we can handle */
107 device->VideoInit = RPI_VideoInit;
108 device->VideoQuit = RPI_VideoQuit;
109 device->GetDisplayModes = RPI_GetDisplayModes;
110 device->SetDisplayMode = RPI_SetDisplayMode;
111 device->CreateSDLWindow = RPI_CreateWindow;
112 device->CreateSDLWindowFrom = RPI_CreateWindowFrom;
113 device->SetWindowTitle = RPI_SetWindowTitle;
114 device->SetWindowIcon = RPI_SetWindowIcon;
115 device->SetWindowPosition = RPI_SetWindowPosition;
116 device->SetWindowSize = RPI_SetWindowSize;
117 device->ShowWindow = RPI_ShowWindow;
118 device->HideWindow = RPI_HideWindow;
119 device->RaiseWindow = RPI_RaiseWindow;
120 device->MaximizeWindow = RPI_MaximizeWindow;
121 device->MinimizeWindow = RPI_MinimizeWindow;
122 device->RestoreWindow = RPI_RestoreWindow;
123 device->DestroyWindow = RPI_DestroyWindow;
124 #if 0
125 device->GetWindowWMInfo = RPI_GetWindowWMInfo;
126 #endif
127 device->GL_LoadLibrary = RPI_GLES_LoadLibrary;
128 device->GL_GetProcAddress = RPI_GLES_GetProcAddress;
129 device->GL_UnloadLibrary = RPI_GLES_UnloadLibrary;
130 device->GL_CreateContext = RPI_GLES_CreateContext;
131 device->GL_MakeCurrent = RPI_GLES_MakeCurrent;
132 device->GL_SetSwapInterval = RPI_GLES_SetSwapInterval;
133 device->GL_GetSwapInterval = RPI_GLES_GetSwapInterval;
134 device->GL_SwapWindow = RPI_GLES_SwapWindow;
135 device->GL_DeleteContext = RPI_GLES_DeleteContext;
136 device->GL_DefaultProfileConfig = RPI_GLES_DefaultProfileConfig;
137
138 device->PumpEvents = RPI_PumpEvents;
139
140 return device;
141 }
142
143 VideoBootStrap RPI_bootstrap = {
144 "RPI",
145 "RPI Video Driver",
146 RPI_Create
147 };
148
149
150 /*****************************************************************************/
151 /* SDL Video and Display initialization/handling functions */
152 /*****************************************************************************/
153
154 static void
AddDispManXDisplay(const int display_id)155 AddDispManXDisplay(const int display_id)
156 {
157 DISPMANX_MODEINFO_T modeinfo;
158 DISPMANX_DISPLAY_HANDLE_T handle;
159 SDL_VideoDisplay display;
160 SDL_DisplayMode current_mode;
161 SDL_DisplayData *data;
162
163 handle = vc_dispmanx_display_open(display_id);
164 if (!handle) {
165 return; /* this display isn't available */
166 }
167
168 if (vc_dispmanx_display_get_info(handle, &modeinfo) < 0) {
169 vc_dispmanx_display_close(handle);
170 return;
171 }
172
173 /* RPI_GetRefreshRate() doesn't distinguish between displays. I'm not sure the hardware distinguishes either */
174 SDL_zero(current_mode);
175 current_mode.w = modeinfo.width;
176 current_mode.h = modeinfo.height;
177 current_mode.refresh_rate = RPI_GetRefreshRate();
178 /* 32 bpp for default */
179 current_mode.format = SDL_PIXELFORMAT_ABGR8888;
180
181 current_mode.driverdata = NULL;
182
183 SDL_zero(display);
184 display.desktop_mode = current_mode;
185 display.current_mode = current_mode;
186
187 /* Allocate display internal data */
188 data = (SDL_DisplayData *) SDL_calloc(1, sizeof(SDL_DisplayData));
189 if (data == NULL) {
190 vc_dispmanx_display_close(handle);
191 return; /* oh well */
192 }
193
194 data->dispman_display = handle;
195
196 display.driverdata = data;
197
198 SDL_AddVideoDisplay(&display, SDL_FALSE);
199 }
200
201 int
RPI_VideoInit(_THIS)202 RPI_VideoInit(_THIS)
203 {
204 /* Initialize BCM Host */
205 bcm_host_init();
206
207 AddDispManXDisplay(DISPMANX_ID_MAIN_LCD); /* your default display */
208 AddDispManXDisplay(DISPMANX_ID_FORCE_OTHER); /* an "other" display...maybe DSI-connected screen while HDMI is your main */
209
210 #ifdef SDL_INPUT_LINUXEV
211 if (SDL_EVDEV_Init() < 0) {
212 return -1;
213 }
214 #endif
215
216 RPI_InitMouse(_this);
217
218 return 1;
219 }
220
221 void
RPI_VideoQuit(_THIS)222 RPI_VideoQuit(_THIS)
223 {
224 #ifdef SDL_INPUT_LINUXEV
225 SDL_EVDEV_Quit();
226 #endif
227 }
228
229 void
RPI_GetDisplayModes(_THIS,SDL_VideoDisplay * display)230 RPI_GetDisplayModes(_THIS, SDL_VideoDisplay * display)
231 {
232 /* Only one display mode available, the current one */
233 SDL_AddDisplayMode(display, &display->current_mode);
234 }
235
236 int
RPI_SetDisplayMode(_THIS,SDL_VideoDisplay * display,SDL_DisplayMode * mode)237 RPI_SetDisplayMode(_THIS, SDL_VideoDisplay * display, SDL_DisplayMode * mode)
238 {
239 return 0;
240 }
241
242 static void
RPI_vsync_callback(DISPMANX_UPDATE_HANDLE_T u,void * data)243 RPI_vsync_callback(DISPMANX_UPDATE_HANDLE_T u, void *data)
244 {
245 SDL_WindowData *wdata = ((SDL_WindowData *) data);
246
247 SDL_LockMutex(wdata->vsync_cond_mutex);
248 SDL_CondSignal(wdata->vsync_cond);
249 SDL_UnlockMutex(wdata->vsync_cond_mutex);
250 }
251
252 int
RPI_CreateWindow(_THIS,SDL_Window * window)253 RPI_CreateWindow(_THIS, SDL_Window * window)
254 {
255 SDL_WindowData *wdata;
256 SDL_VideoDisplay *display;
257 SDL_DisplayData *displaydata;
258 VC_RECT_T dst_rect;
259 VC_RECT_T src_rect;
260 VC_DISPMANX_ALPHA_T dispman_alpha;
261 DISPMANX_UPDATE_HANDLE_T dispman_update;
262 uint32_t layer = SDL_RPI_VIDEOLAYER;
263 const char *env;
264
265 /* Disable alpha, otherwise the app looks composed with whatever dispman is showing (X11, console,etc) */
266 dispman_alpha.flags = DISPMANX_FLAGS_ALPHA_FIXED_ALL_PIXELS;
267 dispman_alpha.opacity = 0xFF;
268 dispman_alpha.mask = 0;
269
270 /* Allocate window internal data */
271 wdata = (SDL_WindowData *) SDL_calloc(1, sizeof(SDL_WindowData));
272 if (wdata == NULL) {
273 return SDL_OutOfMemory();
274 }
275 display = SDL_GetDisplayForWindow(window);
276 displaydata = (SDL_DisplayData *) display->driverdata;
277
278 /* Windows have one size for now */
279 window->w = display->desktop_mode.w;
280 window->h = display->desktop_mode.h;
281
282 /* OpenGL ES is the law here, buddy */
283 window->flags |= SDL_WINDOW_OPENGL;
284
285 /* Create a dispman element and associate a window to it */
286 dst_rect.x = 0;
287 dst_rect.y = 0;
288 dst_rect.width = window->w;
289 dst_rect.height = window->h;
290
291 src_rect.x = 0;
292 src_rect.y = 0;
293 src_rect.width = window->w << 16;
294 src_rect.height = window->h << 16;
295
296 env = SDL_GetHint(SDL_HINT_RPI_VIDEO_LAYER);
297 if (env) {
298 layer = SDL_atoi(env);
299 }
300
301 dispman_update = vc_dispmanx_update_start( 0 );
302 wdata->dispman_window.element = vc_dispmanx_element_add (dispman_update,
303 displaydata->dispman_display,
304 layer /* layer */,
305 &dst_rect,
306 0 /*src*/,
307 &src_rect,
308 DISPMANX_PROTECTION_NONE,
309 &dispman_alpha /*alpha*/,
310 0 /*clamp*/,
311 0 /*transform*/);
312 wdata->dispman_window.width = window->w;
313 wdata->dispman_window.height = window->h;
314 vc_dispmanx_update_submit_sync(dispman_update);
315
316 if (!_this->egl_data) {
317 if (SDL_GL_LoadLibrary(NULL) < 0) {
318 return -1;
319 }
320 }
321 wdata->egl_surface = SDL_EGL_CreateSurface(_this, (NativeWindowType) &wdata->dispman_window);
322
323 if (wdata->egl_surface == EGL_NO_SURFACE) {
324 return SDL_SetError("Could not create GLES window surface");
325 }
326
327 /* Start generating vsync callbacks if necesary */
328 wdata->double_buffer = SDL_FALSE;
329 if (SDL_GetHintBoolean(SDL_HINT_VIDEO_DOUBLE_BUFFER, SDL_FALSE)) {
330 wdata->vsync_cond = SDL_CreateCond();
331 wdata->vsync_cond_mutex = SDL_CreateMutex();
332 wdata->double_buffer = SDL_TRUE;
333 vc_dispmanx_vsync_callback(displaydata->dispman_display, RPI_vsync_callback, (void*)wdata);
334 }
335
336 /* Setup driver data for this window */
337 window->driverdata = wdata;
338
339 /* One window, it always has focus */
340 SDL_SetMouseFocus(window);
341 SDL_SetKeyboardFocus(window);
342
343 /* Window has been successfully created */
344 return 0;
345 }
346
347 void
RPI_DestroyWindow(_THIS,SDL_Window * window)348 RPI_DestroyWindow(_THIS, SDL_Window * window)
349 {
350 SDL_WindowData *data = (SDL_WindowData *) window->driverdata;
351 SDL_VideoDisplay *display = SDL_GetDisplayForWindow(window);
352 SDL_DisplayData *displaydata = (SDL_DisplayData *) display->driverdata;
353
354 if(data) {
355 if (data->double_buffer) {
356 /* Wait for vsync, and then stop vsync callbacks and destroy related stuff, if needed */
357 SDL_LockMutex(data->vsync_cond_mutex);
358 SDL_CondWait(data->vsync_cond, data->vsync_cond_mutex);
359 SDL_UnlockMutex(data->vsync_cond_mutex);
360
361 vc_dispmanx_vsync_callback(displaydata->dispman_display, NULL, NULL);
362
363 SDL_DestroyCond(data->vsync_cond);
364 SDL_DestroyMutex(data->vsync_cond_mutex);
365 }
366
367 #if SDL_VIDEO_OPENGL_EGL
368 if (data->egl_surface != EGL_NO_SURFACE) {
369 SDL_EGL_DestroySurface(_this, data->egl_surface);
370 }
371 #endif
372 SDL_free(data);
373 window->driverdata = NULL;
374 }
375 }
376
377 int
RPI_CreateWindowFrom(_THIS,SDL_Window * window,const void * data)378 RPI_CreateWindowFrom(_THIS, SDL_Window * window, const void *data)
379 {
380 return -1;
381 }
382
383 void
RPI_SetWindowTitle(_THIS,SDL_Window * window)384 RPI_SetWindowTitle(_THIS, SDL_Window * window)
385 {
386 }
387 void
RPI_SetWindowIcon(_THIS,SDL_Window * window,SDL_Surface * icon)388 RPI_SetWindowIcon(_THIS, SDL_Window * window, SDL_Surface * icon)
389 {
390 }
391 void
RPI_SetWindowPosition(_THIS,SDL_Window * window)392 RPI_SetWindowPosition(_THIS, SDL_Window * window)
393 {
394 }
395 void
RPI_SetWindowSize(_THIS,SDL_Window * window)396 RPI_SetWindowSize(_THIS, SDL_Window * window)
397 {
398 }
399 void
RPI_ShowWindow(_THIS,SDL_Window * window)400 RPI_ShowWindow(_THIS, SDL_Window * window)
401 {
402 }
403 void
RPI_HideWindow(_THIS,SDL_Window * window)404 RPI_HideWindow(_THIS, SDL_Window * window)
405 {
406 }
407 void
RPI_RaiseWindow(_THIS,SDL_Window * window)408 RPI_RaiseWindow(_THIS, SDL_Window * window)
409 {
410 }
411 void
RPI_MaximizeWindow(_THIS,SDL_Window * window)412 RPI_MaximizeWindow(_THIS, SDL_Window * window)
413 {
414 }
415 void
RPI_MinimizeWindow(_THIS,SDL_Window * window)416 RPI_MinimizeWindow(_THIS, SDL_Window * window)
417 {
418 }
419 void
RPI_RestoreWindow(_THIS,SDL_Window * window)420 RPI_RestoreWindow(_THIS, SDL_Window * window)
421 {
422 }
423
424 /*****************************************************************************/
425 /* SDL Window Manager function */
426 /*****************************************************************************/
427 #if 0
428 SDL_bool
429 RPI_GetWindowWMInfo(_THIS, SDL_Window * window, struct SDL_SysWMinfo *info)
430 {
431 if (info->version.major <= SDL_MAJOR_VERSION) {
432 return SDL_TRUE;
433 } else {
434 SDL_SetError("application not compiled with SDL %d.%d",
435 SDL_MAJOR_VERSION, SDL_MINOR_VERSION);
436 return SDL_FALSE;
437 }
438
439 /* Failed to get window manager information */
440 return SDL_FALSE;
441 }
442 #endif
443
444 #endif /* SDL_VIDEO_DRIVER_RPI */
445
446 /* vi: set ts=4 sw=4 expandtab: */
447