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