glamor_fbo.c revision 35c4bbdf
1/*
2 * Copyright © 2009 Intel Corporation
3 * Copyright © 1998 Keith Packard
4 *
5 * Permission is hereby granted, free of charge, to any person obtaining a
6 * copy of this software and associated documentation files (the "Software"),
7 * to deal in the Software without restriction, including without limitation
8 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
9 * and/or sell copies of the Software, and to permit persons to whom the
10 * Software is furnished to do so, subject to the following conditions:
11 *
12 * The above copyright notice and this permission notice (including the next
13 * paragraph) shall be included in all copies or substantial portions of the
14 * Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
19 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
22 * IN THE SOFTWARE.
23 *
24 * Authors:
25 *    Zhigang Gong <zhigang.gong@gmail.com>
26 *
27 */
28
29#include <stdlib.h>
30
31#include "glamor_priv.h"
32
33#define GLAMOR_CACHE_EXPIRE_MAX 100
34
35#define GLAMOR_CACHE_DEFAULT    0
36#define GLAMOR_CACHE_EXACT_SIZE 1
37
38//#define NO_FBO_CACHE 1
39#define FBO_CACHE_THRESHOLD  (256*1024*1024)
40
41/* Loop from the tail to the head. */
42#define xorg_list_for_each_entry_reverse(pos, head, member)             \
43    for (pos = __container_of((head)->prev, pos, member);               \
44         &pos->member != (head);                                        \
45         pos = __container_of(pos->member.prev, pos, member))
46
47#define xorg_list_for_each_entry_safe_reverse(pos, tmp, head, member)   \
48    for (pos = __container_of((head)->prev, pos, member),               \
49         tmp = __container_of(pos->member.prev, pos, member);           \
50         &pos->member != (head);                                        \
51         pos = tmp, tmp = __container_of(pos->member.prev, tmp, member))
52
53inline static int
54cache_wbucket(int size)
55{
56    int order = __fls(size / 32);
57
58    if (order >= CACHE_BUCKET_WCOUNT)
59        order = CACHE_BUCKET_WCOUNT - 1;
60    return order;
61}
62
63inline static int
64cache_hbucket(int size)
65{
66    int order = __fls(size / 32);
67
68    if (order >= CACHE_BUCKET_HCOUNT)
69        order = CACHE_BUCKET_HCOUNT - 1;
70    return order;
71}
72
73static int
74cache_format(GLenum format)
75{
76    switch (format) {
77    case GL_ALPHA:
78    case GL_LUMINANCE:
79    case GL_RED:
80        return 2;
81    case GL_RGB:
82        return 1;
83    case GL_RGBA:
84        return 0;
85    default:
86        return -1;
87    }
88}
89
90static glamor_pixmap_fbo *
91glamor_pixmap_fbo_cache_get(glamor_screen_private *glamor_priv,
92                            int w, int h, GLenum format)
93{
94    struct xorg_list *cache;
95    glamor_pixmap_fbo *fbo_entry, *ret_fbo = NULL;
96    int n_format;
97
98#ifdef NO_FBO_CACHE
99    return NULL;
100#else
101    n_format = cache_format(format);
102    if (n_format == -1)
103        return NULL;
104    cache = &glamor_priv->fbo_cache[n_format]
105        [cache_wbucket(w)]
106        [cache_hbucket(h)];
107
108    xorg_list_for_each_entry(fbo_entry, cache, list) {
109        if (fbo_entry->width == w && fbo_entry->height == h) {
110
111            DEBUGF("Request w %d h %d format %x \n", w, h, format);
112            DEBUGF("got cache entry %p w %d h %d fbo %d tex %d format %x\n",
113                   fbo_entry, fbo_entry->width, fbo_entry->height,
114                   fbo_entry->fb, fbo_entry->tex, fbo_entry->format);
115            assert(format == fbo_entry->format);
116            xorg_list_del(&fbo_entry->list);
117            ret_fbo = fbo_entry;
118            break;
119        }
120    }
121
122    if (ret_fbo)
123        glamor_priv->fbo_cache_watermark -= ret_fbo->width * ret_fbo->height;
124
125    assert(glamor_priv->fbo_cache_watermark >= 0);
126
127    return ret_fbo;
128#endif
129}
130
131static void
132glamor_purge_fbo(glamor_screen_private *glamor_priv,
133                 glamor_pixmap_fbo *fbo)
134{
135    glamor_make_current(glamor_priv);
136
137    if (fbo->fb)
138        glDeleteFramebuffers(1, &fbo->fb);
139    if (fbo->tex)
140        glDeleteTextures(1, &fbo->tex);
141
142    free(fbo);
143}
144
145static void
146glamor_pixmap_fbo_cache_put(glamor_screen_private *glamor_priv,
147                            glamor_pixmap_fbo *fbo)
148{
149    struct xorg_list *cache;
150    int n_format;
151
152#ifdef NO_FBO_CACHE
153    glamor_purge_fbo(fbo);
154    return;
155#else
156    n_format = cache_format(fbo->format);
157
158    if (fbo->fb == 0 || fbo->external || n_format == -1
159        || glamor_priv->fbo_cache_watermark >= FBO_CACHE_THRESHOLD) {
160        glamor_priv->tick += GLAMOR_CACHE_EXPIRE_MAX;
161        glamor_fbo_expire(glamor_priv);
162        glamor_purge_fbo(glamor_priv, fbo);
163        return;
164    }
165
166    cache = &glamor_priv->fbo_cache[n_format]
167        [cache_wbucket(fbo->width)]
168        [cache_hbucket(fbo->height)];
169    DEBUGF
170        ("Put cache entry %p to cache %p w %d h %d format %x fbo %d tex %d \n",
171         fbo, cache, fbo->width, fbo->height, fbo->format, fbo->fb, fbo->tex);
172
173    glamor_priv->fbo_cache_watermark += fbo->width * fbo->height;
174    xorg_list_add(&fbo->list, cache);
175    fbo->expire = glamor_priv->tick + GLAMOR_CACHE_EXPIRE_MAX;
176#endif
177}
178
179static int
180glamor_pixmap_ensure_fb(glamor_screen_private *glamor_priv,
181                        glamor_pixmap_fbo *fbo)
182{
183    int status, err = 0;
184
185    glamor_make_current(glamor_priv);
186
187    if (fbo->fb == 0)
188        glGenFramebuffers(1, &fbo->fb);
189    assert(fbo->tex != 0);
190    glBindFramebuffer(GL_FRAMEBUFFER, fbo->fb);
191    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
192                           GL_TEXTURE_2D, fbo->tex, 0);
193    status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
194    if (status != GL_FRAMEBUFFER_COMPLETE) {
195        const char *str;
196
197        switch (status) {
198        case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
199            str = "incomplete attachment";
200            break;
201        case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
202            str = "incomplete/missing attachment";
203            break;
204        case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER:
205            str = "incomplete draw buffer";
206            break;
207        case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER:
208            str = "incomplete read buffer";
209            break;
210        case GL_FRAMEBUFFER_UNSUPPORTED:
211            str = "unsupported";
212            break;
213        case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE:
214            str = "incomplete multiple";
215            break;
216        default:
217            str = "unknown error";
218            break;
219        }
220
221        glamor_fallback("glamor: Failed to create fbo, %s\n", str);
222        err = -1;
223    }
224
225    return err;
226}
227
228glamor_pixmap_fbo *
229glamor_create_fbo_from_tex(glamor_screen_private *glamor_priv,
230                           int w, int h, GLenum format, GLint tex, int flag)
231{
232    glamor_pixmap_fbo *fbo;
233
234    fbo = calloc(1, sizeof(*fbo));
235    if (fbo == NULL)
236        return NULL;
237
238    xorg_list_init(&fbo->list);
239
240    fbo->tex = tex;
241    fbo->width = w;
242    fbo->height = h;
243    fbo->external = FALSE;
244    fbo->format = format;
245
246    if (flag == CREATE_PIXMAP_USAGE_SHARED)
247        fbo->external = TRUE;
248
249    if (flag != GLAMOR_CREATE_FBO_NO_FBO) {
250        if (glamor_pixmap_ensure_fb(glamor_priv, fbo) != 0) {
251            glamor_purge_fbo(glamor_priv, fbo);
252            fbo = NULL;
253        }
254    }
255
256    return fbo;
257}
258
259void
260glamor_fbo_expire(glamor_screen_private *glamor_priv)
261{
262    struct xorg_list *cache;
263    glamor_pixmap_fbo *fbo_entry, *tmp;
264    int i, j, k;
265
266    for (i = 0; i < CACHE_FORMAT_COUNT; i++)
267        for (j = 0; j < CACHE_BUCKET_WCOUNT; j++)
268            for (k = 0; k < CACHE_BUCKET_HCOUNT; k++) {
269                cache = &glamor_priv->fbo_cache[i][j][k];
270                xorg_list_for_each_entry_safe_reverse(fbo_entry, tmp, cache,
271                                                      list) {
272                    if (GLAMOR_TICK_AFTER(fbo_entry->expire, glamor_priv->tick)) {
273                        break;
274                    }
275
276                    glamor_priv->fbo_cache_watermark -=
277                        fbo_entry->width * fbo_entry->height;
278                    xorg_list_del(&fbo_entry->list);
279                    DEBUGF("cache %p fbo %p expired %d current %d \n", cache,
280                           fbo_entry, fbo_entry->expire, glamor_priv->tick);
281                    glamor_purge_fbo(glamor_priv, fbo_entry);
282                }
283            }
284
285}
286
287void
288glamor_init_pixmap_fbo(ScreenPtr screen)
289{
290    glamor_screen_private *glamor_priv;
291    int i, j, k;
292
293    glamor_priv = glamor_get_screen_private(screen);
294    for (i = 0; i < CACHE_FORMAT_COUNT; i++)
295        for (j = 0; j < CACHE_BUCKET_WCOUNT; j++)
296            for (k = 0; k < CACHE_BUCKET_HCOUNT; k++) {
297                xorg_list_init(&glamor_priv->fbo_cache[i][j][k]);
298            }
299    glamor_priv->fbo_cache_watermark = 0;
300}
301
302void
303glamor_fini_pixmap_fbo(ScreenPtr screen)
304{
305    struct xorg_list *cache;
306    glamor_screen_private *glamor_priv;
307    glamor_pixmap_fbo *fbo_entry, *tmp;
308    int i, j, k;
309
310    glamor_priv = glamor_get_screen_private(screen);
311    for (i = 0; i < CACHE_FORMAT_COUNT; i++)
312        for (j = 0; j < CACHE_BUCKET_WCOUNT; j++)
313            for (k = 0; k < CACHE_BUCKET_HCOUNT; k++) {
314                cache = &glamor_priv->fbo_cache[i][j][k];
315                xorg_list_for_each_entry_safe_reverse(fbo_entry, tmp, cache,
316                                                      list) {
317                    xorg_list_del(&fbo_entry->list);
318                    glamor_purge_fbo(glamor_priv, fbo_entry);
319                }
320            }
321}
322
323void
324glamor_destroy_fbo(glamor_screen_private *glamor_priv,
325                   glamor_pixmap_fbo *fbo)
326{
327    xorg_list_del(&fbo->list);
328    glamor_pixmap_fbo_cache_put(glamor_priv, fbo);
329
330}
331
332static int
333_glamor_create_tex(glamor_screen_private *glamor_priv,
334                   int w, int h, GLenum format)
335{
336    unsigned int tex;
337
338    glamor_make_current(glamor_priv);
339    glGenTextures(1, &tex);
340    glBindTexture(GL_TEXTURE_2D, tex);
341    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
342    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
343    if (format == glamor_priv->one_channel_format && format == GL_RED)
344        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_A, GL_RED);
345    glamor_priv->suppress_gl_out_of_memory_logging = true;
346    glTexImage2D(GL_TEXTURE_2D, 0, format, w, h, 0,
347                 format, GL_UNSIGNED_BYTE, NULL);
348    glamor_priv->suppress_gl_out_of_memory_logging = false;
349
350    if (glGetError() == GL_OUT_OF_MEMORY) {
351        if (!glamor_priv->logged_any_fbo_allocation_failure) {
352            LogMessageVerb(X_WARNING, 0, "glamor: Failed to allocate %dx%d "
353                           "FBO due to GL_OUT_OF_MEMORY.\n", w, h);
354            LogMessageVerb(X_WARNING, 0,
355                           "glamor: Expect reduced performance.\n");
356            glamor_priv->logged_any_fbo_allocation_failure = true;
357        }
358        glDeleteTextures(1, &tex);
359        return 0;
360    }
361
362    return tex;
363}
364
365glamor_pixmap_fbo *
366glamor_create_fbo(glamor_screen_private *glamor_priv,
367                  int w, int h, GLenum format, int flag)
368{
369    glamor_pixmap_fbo *fbo;
370    GLint tex = 0;
371
372    if (flag == GLAMOR_CREATE_FBO_NO_FBO || flag == CREATE_PIXMAP_USAGE_SHARED)
373        goto new_fbo;
374
375    fbo = glamor_pixmap_fbo_cache_get(glamor_priv, w, h, format);
376    if (fbo)
377        return fbo;
378 new_fbo:
379    tex = _glamor_create_tex(glamor_priv, w, h, format);
380    if (!tex)
381        return NULL;
382    fbo = glamor_create_fbo_from_tex(glamor_priv, w, h, format, tex, flag);
383
384    return fbo;
385}
386
387/**
388 * Create storage for the w * h region, using FBOs of the GL's maximum
389 * supported size.
390 */
391glamor_pixmap_fbo *
392glamor_create_fbo_array(glamor_screen_private *glamor_priv,
393                         int w, int h, GLenum format, int flag,
394                         int block_w, int block_h,
395                         glamor_pixmap_private *priv)
396{
397    int block_wcnt;
398    int block_hcnt;
399    glamor_pixmap_fbo **fbo_array;
400    BoxPtr box_array;
401    int i, j;
402
403    priv->block_w = block_w;
404    priv->block_h = block_h;
405
406    block_wcnt = (w + block_w - 1) / block_w;
407    block_hcnt = (h + block_h - 1) / block_h;
408
409    box_array = calloc(block_wcnt * block_hcnt, sizeof(box_array[0]));
410    if (box_array == NULL)
411        return NULL;
412
413    fbo_array = calloc(block_wcnt * block_hcnt, sizeof(glamor_pixmap_fbo *));
414    if (fbo_array == NULL) {
415        free(box_array);
416        return FALSE;
417    }
418    for (i = 0; i < block_hcnt; i++) {
419        int block_y1, block_y2;
420        int fbo_w, fbo_h;
421
422        block_y1 = i * block_h;
423        block_y2 = (block_y1 + block_h) > h ? h : (block_y1 + block_h);
424        fbo_h = block_y2 - block_y1;
425
426        for (j = 0; j < block_wcnt; j++) {
427            box_array[i * block_wcnt + j].x1 = j * block_w;
428            box_array[i * block_wcnt + j].y1 = block_y1;
429            box_array[i * block_wcnt + j].x2 =
430                (j + 1) * block_w > w ? w : (j + 1) * block_w;
431            box_array[i * block_wcnt + j].y2 = block_y2;
432            fbo_w =
433                box_array[i * block_wcnt + j].x2 - box_array[i * block_wcnt +
434                                                             j].x1;
435            fbo_array[i * block_wcnt + j] = glamor_create_fbo(glamor_priv,
436                                                              fbo_w, fbo_h,
437                                                              format,
438                                                              GLAMOR_CREATE_PIXMAP_FIXUP);
439            if (fbo_array[i * block_wcnt + j] == NULL)
440                goto cleanup;
441        }
442    }
443
444    priv->box = box_array[0];
445    priv->box_array = box_array;
446    priv->fbo_array = fbo_array;
447    priv->block_wcnt = block_wcnt;
448    priv->block_hcnt = block_hcnt;
449    return fbo_array[0];
450
451 cleanup:
452    for (i = 0; i < block_wcnt * block_hcnt; i++)
453        if (fbo_array[i])
454            glamor_destroy_fbo(glamor_priv, fbo_array[i]);
455    free(box_array);
456    free(fbo_array);
457    return NULL;
458}
459
460glamor_pixmap_fbo *
461glamor_pixmap_detach_fbo(glamor_pixmap_private *pixmap_priv)
462{
463    glamor_pixmap_fbo *fbo;
464
465    if (pixmap_priv == NULL)
466        return NULL;
467
468    fbo = pixmap_priv->fbo;
469    if (fbo == NULL)
470        return NULL;
471
472    pixmap_priv->fbo = NULL;
473    return fbo;
474}
475
476/* The pixmap must not be attached to another fbo. */
477void
478glamor_pixmap_attach_fbo(PixmapPtr pixmap, glamor_pixmap_fbo *fbo)
479{
480    glamor_pixmap_private *pixmap_priv;
481
482    pixmap_priv = glamor_get_pixmap_private(pixmap);
483
484    if (pixmap_priv->fbo)
485        return;
486
487    pixmap_priv->fbo = fbo;
488
489    switch (pixmap_priv->type) {
490    case GLAMOR_TEXTURE_ONLY:
491    case GLAMOR_TEXTURE_DRM:
492        pixmap_priv->gl_fbo = GLAMOR_FBO_NORMAL;
493        pixmap->devPrivate.ptr = NULL;
494    default:
495        break;
496    }
497}
498
499void
500glamor_pixmap_destroy_fbo(PixmapPtr pixmap)
501{
502    ScreenPtr screen = pixmap->drawable.pScreen;
503    glamor_screen_private *glamor_priv = glamor_get_screen_private(screen);
504    glamor_pixmap_private *priv = glamor_get_pixmap_private(pixmap);
505    glamor_pixmap_fbo *fbo;
506
507    if (glamor_pixmap_priv_is_large(priv)) {
508        int i;
509
510        for (i = 0; i < priv->block_wcnt * priv->block_hcnt; i++)
511            glamor_destroy_fbo(glamor_priv, priv->fbo_array[i]);
512        free(priv->fbo_array);
513    }
514    else {
515        fbo = glamor_pixmap_detach_fbo(priv);
516        if (fbo)
517            glamor_destroy_fbo(glamor_priv, fbo);
518    }
519}
520
521Bool
522glamor_pixmap_ensure_fbo(PixmapPtr pixmap, GLenum format, int flag)
523{
524    glamor_screen_private *glamor_priv;
525    glamor_pixmap_private *pixmap_priv;
526    glamor_pixmap_fbo *fbo;
527
528    glamor_priv = glamor_get_screen_private(pixmap->drawable.pScreen);
529    pixmap_priv = glamor_get_pixmap_private(pixmap);
530    if (pixmap_priv->fbo == NULL) {
531
532        fbo = glamor_create_fbo(glamor_priv, pixmap->drawable.width,
533                                pixmap->drawable.height, format, flag);
534        if (fbo == NULL)
535            return FALSE;
536
537        glamor_pixmap_attach_fbo(pixmap, fbo);
538    }
539    else {
540        /* We do have a fbo, but it may lack of fb or tex. */
541        if (!pixmap_priv->fbo->tex)
542            pixmap_priv->fbo->tex =
543                _glamor_create_tex(glamor_priv, pixmap->drawable.width,
544                                   pixmap->drawable.height, format);
545
546        if (flag != GLAMOR_CREATE_FBO_NO_FBO && pixmap_priv->fbo->fb == 0)
547            if (glamor_pixmap_ensure_fb(glamor_priv, pixmap_priv->fbo) != 0)
548                return FALSE;
549    }
550
551    return TRUE;
552}
553
554_X_EXPORT void
555glamor_pixmap_exchange_fbos(PixmapPtr front, PixmapPtr back)
556{
557    glamor_pixmap_private *front_priv, *back_priv;
558    glamor_pixmap_fbo *temp_fbo;
559
560    front_priv = glamor_get_pixmap_private(front);
561    back_priv = glamor_get_pixmap_private(back);
562    temp_fbo = front_priv->fbo;
563    front_priv->fbo = back_priv->fbo;
564    back_priv->fbo = temp_fbo;
565}
566