Add "scaled" and "pixelperfect" layer sizes.
[sdl_omap.git] / src / video / omapdss / osdl_video.c
1 /*
2  * (C) GraÅžvydas "notaz" Ignotas, 2010-2012
3  *
4  * This work is licensed under the terms of the GNU LGPL, version 2.1 or later.
5  * See the COPYING file in the top-level directory.
6  */
7
8 #include <stdio.h>
9 #include <stdlib.h>
10 #include <string.h>
11 #include <strings.h>
12 #include <ctype.h>
13 #include <sys/types.h>
14 #include <sys/stat.h>
15 #include <fcntl.h>
16 #include <sys/ioctl.h>
17 #include <unistd.h>
18 #include <linux/fb.h>
19
20 #include "osdl.h"
21 #include "omapfb.h"
22 #include "linux/fbdev.h"
23 #include "linux/xenv.h"
24
25 #define MIN(a, b) ( ((a) < (b)) ? (a) : (b) )
26
27 struct omapfb_state {
28         struct omapfb_plane_info pi;
29         struct omapfb_mem_info mi;
30         struct omapfb_plane_info pi_old;
31         struct omapfb_mem_info mi_old;
32 };
33
34 static const char *get_fb_device(void)
35 {
36         const char *fbname = getenv("SDL_FBDEV");
37         if (fbname == NULL)
38                 fbname = "/dev/fb1";
39
40         return fbname;
41 }
42
43 static int osdl_setup_omapfb(struct omapfb_state *ostate, int fd, int enabled,
44         int x, int y, int w, int h, int mem, int *buffer_count)
45 {
46         struct omapfb_plane_info pi;
47         struct omapfb_mem_info mi;
48         int mem_blocks = *buffer_count;
49         int ret;
50
51         memset(&pi, 0, sizeof(pi));
52         memset(&mi, 0, sizeof(mi));
53
54         ret = ioctl(fd, OMAPFB_QUERY_PLANE, &pi);
55         if (ret != 0) {
56                 err_perror("QUERY_PLANE");
57                 return -1;
58         }
59
60         ret = ioctl(fd, OMAPFB_QUERY_MEM, &mi);
61         if (ret != 0) {
62                 err_perror("QUERY_MEM");
63                 return -1;
64         }
65
66         /* must disable when changing stuff */
67         if (pi.enabled) {
68                 pi.enabled = 0;
69                 ret = ioctl(fd, OMAPFB_SETUP_PLANE, &pi);
70                 if (ret != 0)
71                         err_perror("SETUP_PLANE");
72         }
73
74         if (mi.size < mem * mem_blocks) {
75                 for (; mem_blocks > 0; mem_blocks--) {
76                         mi.size = mem * mem_blocks;
77                         ret = ioctl(fd, OMAPFB_SETUP_MEM, &mi);
78                         if (ret == 0)
79                                 break;
80                 }
81                 if (ret != 0 || mem_blocks <= 0) {
82                         err("failed to allocate at least %d bytes of vram:\n", mem);
83                         err_perror("SETUP_MEM");
84                         return -1;
85                 }
86         }
87         *buffer_count = mem_blocks;
88
89         pi.pos_x = x;
90         pi.pos_y = y;
91         pi.out_width = w;
92         pi.out_height = h;
93         pi.enabled = enabled;
94
95         ret = ioctl(fd, OMAPFB_SETUP_PLANE, &pi);
96         if (ret != 0) {
97                 err_perror("SETUP_PLANE");
98                 err("(%d %d %d %d)\n", x, y, w, h);
99                 return -1;
100         }
101
102         ostate->pi = pi;
103         ostate->mi = mi;
104         return 0;
105 }
106
107 static int read_sysfs(const char *fname, char *buff, size_t size)
108 {
109         FILE *f;
110         int ret;
111
112         f = fopen(fname, "r");
113         if (f == NULL) {
114                 err_perror("open %s: ", fname);
115                 return -1;
116         }
117
118         ret = fread(buff, 1, size - 1, f);
119         fclose(f);
120         if (ret <= 0) {
121                 err_perror("read %s: ", fname);
122                 return -1;
123         }
124
125         buff[ret] = 0;
126         for (ret--; ret >= 0 && isspace(buff[ret]); ret--)
127                 buff[ret] = 0;
128
129         return 0;
130 }
131
132 int read_vscreeninfo(const char *fbname, int *w, int *h)
133 {
134         struct fb_var_screeninfo fbvar;
135         int ret, fd;
136
137         fd = open(fbname, O_RDWR);
138         if (fd == -1) {
139                 err_perror("open %s", fbname);
140                 return -1;
141         }
142
143         ret = ioctl(fd, FBIOGET_VSCREENINFO, &fbvar);
144         close(fd);
145
146         if (ret == -1) {
147                 err_perror("ioctl %s", fbname);
148                 return -1;
149         }
150
151         if (fbvar.xres == 0 || fbvar.yres == 0)
152                 return -1;
153
154         *w = fbvar.xres;
155         *h = fbvar.yres;
156         return 0;
157 }
158
159 int osdl_video_detect_screen(struct SDL_PrivateVideoData *pdata)
160 {
161         int fb_id, overlay_id = -1, screen_id = -1;
162         char buff[64], screen_name[64];
163         const char *fbname;
164         struct stat status;
165         int fd, i, ret;
166         int w, h;
167         FILE *f;
168
169         pdata->phys_w = pdata->phys_h = 0;
170
171         fbname = get_fb_device();
172
173         /* Figure out screen resolution, we need to know default resolution
174          * to report to SDL and for centering stuff.
175          * The only way to achieve this seems to be walking some sysfs files.. */
176         ret = stat(fbname, &status);
177         if (ret != 0) {
178                 err_perror("can't stat %s", fbname);
179                 goto skip_screen;
180         }
181         fb_id = minor(status.st_rdev);
182
183         snprintf(buff, sizeof(buff), "/sys/class/graphics/fb%d/overlays", fb_id);
184         f = fopen(buff, "r");
185         if (f == NULL) {
186                 err("can't open %s, skip screen detection", buff);
187                 goto skip_screen;
188         }
189
190         ret = fscanf(f, "%d", &overlay_id);
191         fclose(f);
192         if (ret != 1) {
193                 err("can't parse %s, skip screen detection", buff);
194                 goto skip_screen;
195         }
196
197         snprintf(buff, sizeof(buff), "/sys/devices/platform/omapdss/overlay%d/manager", overlay_id);
198         ret = read_sysfs(buff, screen_name, sizeof(screen_name));
199         if (ret < 0) {
200                 err("skip screen detection");
201                 goto skip_screen;
202         }
203
204         for (i = 0; ; i++) {
205                 snprintf(buff, sizeof(buff), "/sys/devices/platform/omapdss/display%d/name", i);
206                 ret = read_sysfs(buff, buff, sizeof(buff));
207                 if (ret < 0)
208                         break;
209
210                 if (strcmp(screen_name, buff) == 0) {
211                         screen_id = i;
212                         break;
213                 }
214         }
215
216         if (screen_id < 0) {
217                 err("could not find screen");
218                 goto skip_screen;
219         }
220
221         snprintf(buff, sizeof(buff), "/sys/devices/platform/omapdss/display%d/timings", screen_id);
222         f = fopen(buff, "r");
223         if (f == NULL) {
224                 err("can't open %s, skip screen detection", buff);
225                 goto skip_screen;
226         }
227
228         ret = fscanf(f, "%*d,%d/%*d/%*d/%*d,%d/%*d/%*d/%*d", &w, &h);
229         fclose(f);
230         if (ret != 2) {
231                 err("can't parse %s (%d), skip screen detection", buff, ret);
232                 goto skip_screen;
233         }
234
235         log("detected %dx%d '%s' (%d) screen attached to fb %d and overlay %d",
236                 w, h, screen_name, screen_id, fb_id, overlay_id);
237
238         pdata->phys_w = w;
239         pdata->phys_h = h;
240         return 0;
241
242 skip_screen:
243         /* attempt to extract this from FB then */
244         ret = read_vscreeninfo(fbname, &pdata->phys_w, &pdata->phys_h);
245         if (ret != 0 && strcmp(fbname, "/dev/fb0") != 0) {
246                 /* last resort */
247                 ret = read_vscreeninfo("/dev/fb0", &pdata->phys_w, &pdata->phys_h);
248         }
249
250         if (ret != 0) {
251                 err("VSCREENINFO has nothing meaningful");
252                 return -1;
253         }
254
255         return 0;
256 }
257
258 static int osdl_setup_omap_layer(struct SDL_PrivateVideoData *pdata,
259                 const char *fbname, int width, int height, int bpp, int *buffer_count)
260 {
261         int x = 0, y = 0, w = width, h = height; /* layer size and pos */
262         int screen_w = w, screen_h = h;
263         int tmp_w, tmp_h;
264         const char *tmp;
265         int retval = -1;
266         int ret, fd;
267
268         pdata->layer_x = pdata->layer_y = pdata->layer_w = pdata->layer_h = 0;
269
270         if (pdata->phys_w != 0)
271                 screen_w = pdata->phys_w;
272         if (pdata->phys_h != 0)
273                 screen_h = pdata->phys_h;
274
275         fd = open(fbname, O_RDWR);
276         if (fd == -1) {
277                 err_perror("open %s", fbname);
278                 return -1;
279         }
280
281         /* FIXME: assuming layer doesn't change here */
282         if (pdata->saved_layer == NULL) {
283                 struct omapfb_state *slayer;
284                 slayer = calloc(1, sizeof(*slayer));
285                 if (slayer == NULL)
286                         goto out;
287
288                 ret = ioctl(fd, OMAPFB_QUERY_PLANE, &slayer->pi_old);
289                 if (ret != 0) {
290                         err_perror("QUERY_PLANE");
291                         goto out;
292                 }
293
294                 ret = ioctl(fd, OMAPFB_QUERY_MEM, &slayer->mi_old);
295                 if (ret != 0) {
296                         err_perror("QUERY_MEM");
297                         goto out;
298                 }
299
300                 pdata->saved_layer = slayer;
301         }
302
303         tmp = getenv("SDL_OMAP_LAYER_SIZE");
304         if (tmp != NULL) {
305                 if (strcasecmp(tmp, "fullscreen") == 0) {
306                         w = screen_w, h = screen_h;
307                 }
308                 else if (strcasecmp(tmp, "scaled") == 0) {
309                         float factor = MIN(((float)screen_w) / width, ((float)screen_h) / height);
310                         w = (int)(factor*width), h = (int)(factor*height);
311                 }
312                 else if (strcasecmp(tmp, "pixelperfect") == 0) {
313                         float factor = MIN(((float)screen_w) / width, ((float)screen_h) / height);
314                         w = ((int)factor) * width, h = ((int)factor) * height;
315                         /* factor < 1.f => 0x0 layer, so fall back to 'scaled' */
316                         if (!w || !h) {
317                                 w = (int)(factor * width), h = (int)(factor * height);
318                         }
319                 }
320                 else if (sscanf(tmp, "%dx%d", &tmp_w, &tmp_h) == 2) {
321                         w = tmp_w, h = tmp_h;
322                 }
323                 else {
324                         err("layer size specified incorrectly, "
325                                 "should be like 800x480");
326                 }
327         }
328
329         /* the layer can't be set larger than screen */
330         tmp_w = w, tmp_h = h;
331         if (w > screen_w)
332                 w = screen_w;
333         if (h > screen_h)
334                 h = screen_h;
335         if (w != tmp_w || h != tmp_h)
336                 log("layer resized %dx%d -> %dx%d to fit screen", tmp_w, tmp_h, w, h);
337
338         x = screen_w / 2 - w / 2;
339         y = screen_h / 2 - h / 2;
340         ret = osdl_setup_omapfb(pdata->saved_layer, fd, 1, x, y, w, h,
341                                 width * height * ((bpp + 7) / 8), buffer_count);
342         if (ret == 0) {
343                 pdata->layer_x = x;
344                 pdata->layer_y = y;
345                 pdata->layer_w = w;
346                 pdata->layer_h = h;
347         }
348
349         retval = ret;
350 out:
351         close(fd);
352         return retval;
353 }
354
355 void *osdl_video_set_mode(struct SDL_PrivateVideoData *pdata,
356                           int border_l, int border_r, int border_t, int border_b,
357                           int width, int height, int bpp, int *doublebuf,
358                           const char *wm_title)
359 {
360         int buffers_try, buffers_set;
361         const char *fbname;
362         void *result;
363         int ret;
364
365         fbname = get_fb_device();
366
367         if (pdata->fbdev != NULL) {
368                 vout_fbdev_finish(pdata->fbdev);
369                 pdata->fbdev = NULL;
370         }
371
372         buffers_try = buffers_set = 1;
373         if (*doublebuf)
374                 /* actually try tripple buffering for reduced chance of tearing */
375                 buffers_try = buffers_set = 3;
376
377         ret = osdl_setup_omap_layer(pdata, fbname, width, height, bpp, &buffers_set);
378         if (ret < 0)
379                 goto fail;
380
381         pdata->fbdev = vout_fbdev_init(fbname, &width, &height, bpp, buffers_set);
382         if (pdata->fbdev == NULL)
383                 goto fail;
384
385         if (border_l | border_r | border_t | border_b) {
386                 width -= border_l + border_r;
387                 height -= border_t + border_b;
388                 result = vout_fbdev_resize(pdata->fbdev, width, height, bpp,
389                                 border_l, border_r, border_t, border_b, buffers_set);
390         }
391         else {
392                 result = osdl_video_flip(pdata);
393         }
394         if (result == NULL)
395                 goto fail;
396
397         if (!pdata->xenv_up) {
398                 int xenv_flags = XENV_CAP_KEYS | XENV_CAP_MOUSE;
399                 ret = xenv_init(&xenv_flags, wm_title);
400                 if (ret == 0) {
401                         pdata->xenv_up = 1;
402                         pdata->xenv_mouse = (xenv_flags & XENV_CAP_MOUSE) ? 1 : 0;
403                 }
404         }
405
406         if (buffers_try != buffers_set) {
407                 log("only %d/%d buffers available, expect tearing\n",
408                         buffers_set, buffers_try);
409                 *doublebuf = buffers_set > 1;
410         }
411
412         return result;
413
414 fail:
415         osdl_video_finish(pdata);
416         return NULL;
417 }
418
419 void *osdl_video_flip(struct SDL_PrivateVideoData *pdata)
420 {
421         void *ret;
422
423         if (pdata->fbdev == NULL)
424                 return NULL;
425
426         ret = vout_fbdev_flip(pdata->fbdev);
427
428         if (pdata->cfg_force_vsync)
429                 vout_fbdev_wait_vsync(pdata->fbdev);
430
431         return ret;
432 }
433
434 int osdl_video_pause(struct SDL_PrivateVideoData *pdata, int is_pause)
435 {
436         struct omapfb_state *state = pdata->saved_layer;
437         struct omapfb_plane_info pi;
438         struct omapfb_mem_info mi;
439         int enabled;
440         int fd = -1;
441         int ret;
442
443         if (pdata->fbdev != NULL)
444                 fd = vout_fbdev_get_fd(pdata->fbdev);
445         if (fd == -1) {
446                 err("bad fd %d", fd);
447                 return -1;
448         }
449         if (state == NULL) {
450                 err("missing layer state\n");
451                 return -1;
452         }
453
454         if (is_pause) {
455                 ret = vout_fbdev_save(pdata->fbdev);
456                 if (ret != 0)
457                         return ret;
458                 pi = state->pi_old;
459                 mi = state->mi_old;
460                 enabled = pi.enabled;
461         } else {
462                 pi = state->pi;
463                 mi = state->mi;
464                 enabled = 1;
465         }
466         pi.enabled = 0;
467         ret = ioctl(fd, OMAPFB_SETUP_PLANE, &pi);
468         if (ret != 0) {
469                 err_perror("SETUP_PLANE");
470                 return -1;
471         }
472
473         ret = ioctl(fd, OMAPFB_SETUP_MEM, &mi);
474         if (ret != 0)
475                 err_perror("SETUP_MEM");
476
477         if (!is_pause) {
478                 ret = vout_fbdev_restore(pdata->fbdev);
479                 if (ret != 0) {
480                         err("fbdev_restore failed\n");
481                         return ret;
482                 }
483         }
484
485         if (enabled) {
486                 pi.enabled = 1;
487                 ret = ioctl(fd, OMAPFB_SETUP_PLANE, &pi);
488                 if (ret != 0) {
489                         err_perror("SETUP_PLANE");
490                         return -1;
491                 }
492         }
493
494         return 0;
495 }
496
497 void osdl_video_finish(struct SDL_PrivateVideoData *pdata)
498 {
499         static const char *fbname;
500
501         fbname = get_fb_device();
502         if (pdata->fbdev != NULL) {
503                 vout_fbdev_finish(pdata->fbdev);
504                 pdata->fbdev = NULL;
505         }
506
507         /* restore the OMAP layer */
508         if (pdata->saved_layer != NULL) {
509                 struct omapfb_state *slayer = pdata->saved_layer;
510                 int fd;
511
512                 fd = open(fbname, O_RDWR);
513                 if (fd != -1) {
514                         int enabled = slayer->pi_old.enabled;
515
516                         /* be sure to disable while setting up */
517                         slayer->pi_old.enabled = 0;
518                         ioctl(fd, OMAPFB_SETUP_PLANE, &slayer->pi_old);
519                         ioctl(fd, OMAPFB_SETUP_MEM, &slayer->mi_old);
520                         if (enabled) {
521                                 slayer->pi_old.enabled = enabled;
522                                 ioctl(fd, OMAPFB_SETUP_PLANE, &slayer->pi_old);
523                         }
524                         close(fd);
525                 }
526                 free(slayer);
527                 pdata->saved_layer = NULL;
528         }
529
530         if (pdata->xenv_up) {
531                 xenv_finish();
532                 pdata->xenv_up = 0;
533         }
534 }
535