#include "ecore_drm2_private.h"

#define FLIP_TIMEOUT 1.0

static Eina_Bool
_fb2_create(Ecore_Drm2_Fb *fb)
{
   uint32_t offsets[4] = { 0 };
   int r;

   r = sym_drmModeAddFB2(fb->fd, fb->w, fb->h, fb->format, fb->handles,
                         fb->strides, offsets, &fb->id, 0);

   if (r)
     return EINA_FALSE;

   return EINA_TRUE;
}

EAPI Ecore_Drm2_Fb *
ecore_drm2_fb_create(Ecore_Drm2_Device *dev, int width, int height, int depth, int bpp, unsigned int format)
{
   Ecore_Drm2_Fb *fb;
   struct drm_mode_create_dumb carg;
   struct drm_mode_destroy_dumb darg;
   struct drm_mode_map_dumb marg;
   int ret;

   EINA_SAFETY_ON_NULL_RETURN_VAL(dev, NULL);

   fb = calloc(1, sizeof(Ecore_Drm2_Fb));
   if (!fb) return NULL;

   fb->fd = dev->fd;
   fb->w = width;
   fb->h = height;
   fb->bpp = bpp;
   fb->depth = depth;
   fb->format = format;
   fb->ref = 1;

   memset(&carg, 0, sizeof(struct drm_mode_create_dumb));
   carg.bpp = bpp;
   carg.width = width;
   carg.height = height;

   ret = sym_drmIoctl(dev->fd, DRM_IOCTL_MODE_CREATE_DUMB, &carg);
   if (ret) goto err;

   fb->handles[0] = carg.handle;
   fb->sizes[0] = carg.size;
   fb->strides[0] = carg.pitch;

   if (!_fb2_create(fb))
     {
        ret =
          sym_drmModeAddFB(dev->fd, width, height, depth, bpp,
                           fb->strides[0], fb->handles[0], &fb->id);
        if (ret)
          {
             ERR("Could not add framebuffer: %m");
             goto add_err;
          }
     }

   memset(&marg, 0, sizeof(struct drm_mode_map_dumb));
   marg.handle = fb->handles[0];
   ret = sym_drmIoctl(dev->fd, DRM_IOCTL_MODE_MAP_DUMB, &marg);
   if (ret)
     {
        ERR("Could not map framebuffer: %m");
        goto map_err;
     }

   fb->mmap = mmap(NULL, fb->sizes[0], PROT_WRITE, MAP_SHARED, dev->fd, marg.offset);
   if (fb->mmap == MAP_FAILED)
     {
        ERR("Could not mmap framebuffer memory: %m");
        goto map_err;
     }

   return fb;

map_err:
   sym_drmModeRmFB(dev->fd, fb->id);
add_err:
   memset(&darg, 0, sizeof(struct drm_mode_destroy_dumb));
   darg.handle = fb->handles[0];
   sym_drmIoctl(dev->fd, DRM_IOCTL_MODE_DESTROY_DUMB, &darg);
err:
   free(fb);
   return NULL;
}

EAPI Ecore_Drm2_Fb *
ecore_drm2_fb_gbm_create(Ecore_Drm2_Device *dev, int width, int height, int depth, int bpp, unsigned int format, unsigned int handle, unsigned int stride, void *bo)
{
   Ecore_Drm2_Fb *fb;

   EINA_SAFETY_ON_NULL_RETURN_VAL(dev, NULL);

   fb = calloc(1, sizeof(Ecore_Drm2_Fb));
   if (!fb) return NULL;

   fb->gbm = EINA_TRUE;
   fb->gbm_bo = bo;

   fb->fd = dev->fd;
   fb->w = width;
   fb->h = height;
   fb->bpp = bpp;
   fb->depth = depth;
   fb->format = format;
   fb->strides[0] = stride;
   fb->sizes[0] = fb->strides[0] * fb->h;
   fb->handles[0] = handle;
   fb->ref = 1;

   if (!_fb2_create(fb))
     {
        if (sym_drmModeAddFB(dev->fd, width, height, depth, bpp,
                             fb->strides[0], fb->handles[0], &fb->id))
          {
             ERR("Could not add framebuffer: %m");
             goto err;
          }
     }
   return fb;

err:
   free(fb);
   return NULL;
}

static void
_ecore_drm2_fb_destroy(Ecore_Drm2_Fb *fb)
{
   EINA_SAFETY_ON_NULL_RETURN(fb);

   if (!fb->dead) WRN("Destroying an fb that hasn't been discarded");

   if (fb->scanout_count)
     WRN("Destroyed fb on scanout %d times.", fb->scanout_count);

   if (fb->mmap) munmap(fb->mmap, fb->sizes[0]);

   if (fb->id) sym_drmModeRmFB(fb->fd, fb->id);

   if (!fb->gbm && !fb->dmabuf)
     {
        struct drm_mode_destroy_dumb darg;

        memset(&darg, 0, sizeof(struct drm_mode_destroy_dumb));
        darg.handle = fb->handles[0];
        sym_drmIoctl(fb->fd, DRM_IOCTL_MODE_DESTROY_DUMB, &darg);
     }

   free(fb);
}

void
_ecore_drm2_fb_ref(Ecore_Drm2_Fb *fb)
{
   fb->ref++;
}

void
_ecore_drm2_fb_deref(Ecore_Drm2_Fb *fb)
{
   fb->ref--;
   if (fb->ref) return;

   if (fb->status_handler)
     fb->status_handler(fb, ECORE_DRM2_FB_STATUS_DELETED, fb->status_data);

   _ecore_drm2_fb_destroy(fb);
}

EAPI void
ecore_drm2_fb_discard(Ecore_Drm2_Fb *fb)
{
   EINA_SAFETY_ON_NULL_RETURN(fb);
   EINA_SAFETY_ON_TRUE_RETURN(fb->ref < 1);

   fb->dead = EINA_TRUE;
   _ecore_drm2_fb_deref(fb);
}

EAPI void *
ecore_drm2_fb_data_get(Ecore_Drm2_Fb *fb)
{
   EINA_SAFETY_ON_NULL_RETURN_VAL(fb, NULL);
   EINA_SAFETY_ON_TRUE_RETURN_VAL(fb->dead, NULL);
   return fb->mmap;
}

EAPI unsigned int
ecore_drm2_fb_size_get(Ecore_Drm2_Fb *fb)
{
   EINA_SAFETY_ON_NULL_RETURN_VAL(fb, 0);
   EINA_SAFETY_ON_TRUE_RETURN_VAL(fb->dead, 0);
   return fb->sizes[0];
}

EAPI unsigned int
ecore_drm2_fb_stride_get(Ecore_Drm2_Fb *fb)
{
   EINA_SAFETY_ON_NULL_RETURN_VAL(fb, 0);
   EINA_SAFETY_ON_TRUE_RETURN_VAL(fb->dead, 0);

   return fb->strides[0];
}

EAPI void
ecore_drm2_fb_dirty(Ecore_Drm2_Fb *fb, Eina_Rectangle *rects, unsigned int count)
{
   EINA_SAFETY_ON_NULL_RETURN(fb);
   EINA_SAFETY_ON_NULL_RETURN(rects);
   EINA_SAFETY_ON_TRUE_RETURN(fb->dead);

#ifdef DRM_MODE_FEATURE_DIRTYFB
   drmModeClip *clip;
   unsigned int i = 0;
   int ret;

   clip = alloca(count * sizeof(drmModeClip));
   for (i = 0; i < count; i++)
     {
        clip[i].x1 = rects[i].x;
        clip[i].y1 = rects[i].y;
        clip[i].x2 = rects[i].w;
        clip[i].y2 = rects[i].h;
     }

   ret = sym_drmModeDirtyFB(fb->fd, fb->id, clip, count);
   if ((ret) && (ret == -EINVAL))
     WRN("Could not mark framebuffer as dirty: %m");
#endif
}

/* perhaps output is no longer a necessary parameter for this function */
void
_ecore_drm2_fb_buffer_release(Ecore_Drm2_Output *output EINA_UNUSED, Ecore_Drm2_Output_State *s)
{
   Ecore_Drm2_Fb *fb = s->fb;

   if (fb->status_handler)
     fb->status_handler(fb, ECORE_DRM2_FB_STATUS_RELEASE, fb->status_data);

   _ecore_drm2_fb_deref(fb);
   s->fb = NULL;
   if (_ecore_drm2_use_atomic)
     {
        if (s->atomic_req)
          sym_drmModeAtomicFree(s->atomic_req);
        s->atomic_req = NULL;
     }
}

static void
_cb_mainloop_async_timer_del(void *data)
{
   Ecore_Drm2_Output *output = data;

   ecore_timer_del(output->flip_timeout);
   output->flip_timeout = NULL;
}

EAPI Eina_Bool
ecore_drm2_fb_flip_complete(Ecore_Drm2_Output *output)
{
   Eina_Bool plane_scanout;
   Ecore_Drm2_Fb *fb;

   EINA_SAFETY_ON_NULL_RETURN_VAL(output, EINA_FALSE);

   if (output->flip_timeout)
     {
        // XXX: output ref++
        ecore_main_loop_thread_safe_call_async
          (_cb_mainloop_async_timer_del, output);
     }
   if (!output->pending.fb) fprintf(stderr, "XXX--XXX eeeeek pending fb is NULL so current would become null ----------------------------------\n");
   if (output->current.fb && (output->current.fb != output->pending.fb))
     _ecore_drm2_fb_buffer_release(output, &output->current);

   output->current.fb = output->pending.fb;
   output->pending.fb = NULL;

   if (_ecore_drm2_use_atomic)
     {
        Eina_List *l, *ll;
        Ecore_Drm2_Plane *plane;

        output->current.atomic_req = output->pending.atomic_req;
        output->pending.atomic_req = NULL;

        EINA_LIST_FOREACH_SAFE(output->planes, l, ll, plane)
          {
             fb = plane->fb;
             plane_scanout = plane->scanout;
             if (!plane->dead)
               {
                  /* First time this plane is scanned out */
                  if (!plane->scanout)
                    fb->scanout_count++;

                  plane->scanout = EINA_TRUE;
                  if (fb->status_handler && (fb->scanout_count == 1) &&
                      (plane_scanout != plane->scanout))
                    fb->status_handler(fb, ECORE_DRM2_FB_STATUS_SCANOUT_ON,
                                       fb->status_data);
                  continue;
               }
             output->planes = eina_list_remove_list(output->planes, l);
             free(plane);
             if (!plane_scanout) continue;

             fb->scanout_count--;
             if (fb->status_handler && (fb->scanout_count == 0))
               fb->status_handler(fb, ECORE_DRM2_FB_STATUS_SCANOUT_OFF,
                                  fb->status_data);
          }
     }

   EINA_LIST_FREE(output->fbs, fb)
     _ecore_drm2_fb_deref(fb);
   output->fbs = NULL;

   return !!output->next.fb;
}

Eina_Bool
_fb_atomic_flip_test(Ecore_Drm2_Output *output)
{
   int ret = 0;
   Eina_List *l;
   Ecore_Drm2_Crtc_State *cstate;
   Ecore_Drm2_Plane_State *pstate;
   Ecore_Drm2_Plane *plane;
   drmModeAtomicReq *req = NULL;
   uint32_t flags = DRM_MODE_ATOMIC_NONBLOCK | DRM_MODE_ATOMIC_ALLOW_MODESET |
     DRM_MODE_ATOMIC_TEST_ONLY;

   if (!_ecore_drm2_use_atomic) return EINA_FALSE;

   req = sym_drmModeAtomicAlloc();
   if (!req) return EINA_FALSE;

   sym_drmModeAtomicSetCursor(req, 0);

   cstate = output->crtc_state;

   ret =
     sym_drmModeAtomicAddProperty(req, cstate->obj_id, cstate->mode.id,
                                  cstate->mode.value);
   if (ret < 0) goto err;

   ret =
     sym_drmModeAtomicAddProperty(req, cstate->obj_id, cstate->active.id,
                                  cstate->active.value);
   if (ret < 0) goto err;

   if (cstate->background.id)
     {
        ret =
          sym_drmModeAtomicAddProperty(req, cstate->obj_id,
                                       cstate->background.id,
                                       cstate->background.value);
        if (ret < 0) goto err;
     }

   EINA_LIST_FOREACH(output->planes, l, plane)
     {
        pstate = plane->state;

        if (!pstate->in_use)
          {
             pstate->cid.value = 0;
             pstate->fid.value = 0;
             pstate->sw.value = 0;
             pstate->sh.value = 0;
             pstate->cx.value = 0;
             pstate->cy.value = 0;
             pstate->cw.value = 0;
             pstate->ch.value = 0;
          }

        ret =
          sym_drmModeAtomicAddProperty(req, pstate->obj_id,
                                       pstate->cid.id, pstate->cid.value);
        if (ret < 0) goto err;

        ret =
          sym_drmModeAtomicAddProperty(req, pstate->obj_id,
                                       pstate->fid.id, pstate->fid.value);
        if (ret < 0) goto err;

        ret =
          sym_drmModeAtomicAddProperty(req, pstate->obj_id,
                                       pstate->sx.id, pstate->sx.value);
        if (ret < 0) goto err;

        ret =
          sym_drmModeAtomicAddProperty(req, pstate->obj_id,
                                       pstate->sy.id, pstate->sy.value);
        if (ret < 0) goto err;

        ret =
          sym_drmModeAtomicAddProperty(req, pstate->obj_id,
                                       pstate->sw.id, pstate->sw.value);
        if (ret < 0) goto err;

        ret =
          sym_drmModeAtomicAddProperty(req, pstate->obj_id,
                                       pstate->sh.id, pstate->sh.value);
        if (ret < 0) goto err;

        ret =
          sym_drmModeAtomicAddProperty(req, pstate->obj_id,
                                       pstate->cx.id, pstate->cx.value);
        if (ret < 0) goto err;

        ret =
          sym_drmModeAtomicAddProperty(req, pstate->obj_id,
                                       pstate->cy.id, pstate->cy.value);
        if (ret < 0) goto err;

        ret =
          sym_drmModeAtomicAddProperty(req, pstate->obj_id,
                                       pstate->cw.id, pstate->cw.value);
        if (ret < 0) goto err;

        ret =
          sym_drmModeAtomicAddProperty(req, pstate->obj_id,
                                       pstate->ch.id, pstate->ch.value);
        if (ret < 0) goto err;

#if 0
        /* XXX: Disable hardware plane rotation for now as this has broken
         * recently. The break happens because of an invalid argument,
         * ie: the value being sent from pstate->rotation_map ends up being
         * incorrect for some reason. I suspect the breakage to be from
         * kernel drivers (linux 4.20.0) but have not confirmed that version */
        if ((pstate->rotation.id) &&
            (pstate->type.value == DRM_PLANE_TYPE_PRIMARY))
          {
             DBG("Plane %d Atomic Rotation: %lu",
                 pstate->obj_id, pstate->rotation.value);
             ret =
               sym_drmModeAtomicAddProperty(req, pstate->obj_id,
                                            pstate->rotation.id,
                                            pstate->rotation_map[pstate->rotation.value]);
             if (ret < 0) goto err;
          }
#endif
     }

   ret =
     sym_drmModeAtomicCommit(output->fd, req, flags, output);
   if (ret < 0) goto err;

   /* clear any previous request */
   if (output->prep.atomic_req)
     sym_drmModeAtomicFree(output->prep.atomic_req);

   output->prep.atomic_req = req;
   return EINA_TRUE;

err:
   DBG("Failed Atomic Test: %m");
   sym_drmModeAtomicFree(req);

   return EINA_FALSE;
}

static int _fb_atomic_flip(Ecore_Drm2_Output *output);
static int _fb_flip(Ecore_Drm2_Output *output);

static Eina_Bool
_cb_flip_timeout(void *data)
{
   Ecore_Drm2_Output *output = data;

   output->flip_timeout = NULL;
   ERR("flip event callback timout %0.2fsec - try again", FLIP_TIMEOUT);
   if (_ecore_drm2_use_atomic) _fb_atomic_flip(output);
   else _fb_flip(output);
   return EINA_FALSE;
}

static void
_cb_mainloop_async_timer_reset(void *data)
{
   Ecore_Drm2_Output *output = data;
   if (output->flip_timeout) ecore_timer_del(output->flip_timeout);
   output->flip_timeout = ecore_timer_add(FLIP_TIMEOUT, _cb_flip_timeout, output);
}

static int
_fb_atomic_flip(Ecore_Drm2_Output *output)
{
   int res = 0;
   uint32_t flags =
     DRM_MODE_ATOMIC_NONBLOCK | DRM_MODE_PAGE_FLIP_EVENT |
     DRM_MODE_ATOMIC_ALLOW_MODESET;

   if (!_ecore_drm2_use_atomic) return -1;

   /* If we have no req yet, we're flipping to current state.
    * rebuild the current state in the prep state */
   if (!output->prep.atomic_req) _fb_atomic_flip_test(output);

   /* Still no req is a bad situation */
   EINA_SAFETY_ON_NULL_RETURN_VAL(output->prep.atomic_req, -1);

   // sometimes we get a EBUSY ... so try again a few times.
   int i;
   for (i = 0; i < 10; i++)
     {
        res =
          sym_drmModeAtomicCommit(output->fd, output->prep.atomic_req, flags,
                                  output);
        if (res == 0) break;
        else ERR("DRM atomic commit failed - retry #%i", i + 1);
        usleep(100);
     }

   if (res < 0)
     {
        ERR("Failed Atomic Commit: %m");
        return -1;
     }
   else
     {
        // XXX: output ref++
        ecore_main_loop_thread_safe_call_async
          (_cb_mainloop_async_timer_reset, output);
     }

   return 0;
}

static int
_fb_flip(Ecore_Drm2_Output *output)
{
   Ecore_Drm2_Fb *fb;
   Eina_Bool repeat;
   int count = 0;
   int ret = 0;

   fb = output->prep.fb;
   if (!fb)
     {
        fb =  output->pending.fb;
        ERR("Trying to flip NULL fb - fallback to pending fb");
     }
   if (!fb)
     {
        ERR("Pending fb is also NULL, give up flipping");
        return ret;
     }

   if ((!output->current.fb) ||
       (output->current.fb->strides[0] != fb->strides[0]))
     {
        ret =
          sym_drmModeSetCrtc(fb->fd, output->crtc_id, fb->id,
                             0, 0, &output->conn_id, 1,
                             &output->current_mode->info);
        if (ret)
          {
             ERR("Failed to set Mode %dx%d for Output %s: %m",
                 output->current_mode->width, output->current_mode->height,
                 output->name);
             return ret;
          }

        if (output->current.fb)
          _ecore_drm2_fb_buffer_release(output, &output->current);
        output->current.fb = fb;
        _ecore_drm2_fb_ref(output->current.fb);
        output->next.fb = NULL;
        /* We used to return here, but now that the ticker is fixed this
         * can leave us hanging waiting for a tick to happen forever.
         * Instead, we now fall through to the flip path to make sure
         * even this first set can cause a flip callback.
         */
     }

   do
     {
        static Eina_Bool bugged_about_bug = EINA_FALSE;
        repeat = EINA_FALSE;
        ret = sym_drmModePageFlip(fb->fd, output->crtc_id, fb->id,
                                  DRM_MODE_PAGE_FLIP_EVENT, output);
        /* Some drivers (RPI - looking at you) are broken and produce
         * flip events before they are ready for another flip, so be
         * a little robust in the face of badness and try a few times
         * until we can flip or we give up (100 tries with a yield
         * between each try). We can't expect everyone to run the
         * latest bleeding edge kernel IF a workaround is possible
         * in userspace, so do this.
         * We only report this as an ERR once since if it will
         * generate a huge amount of spam otherwise. */
        if ((ret < 0) && (errno == EBUSY))
          {
             repeat = EINA_TRUE;
             if (count == 0 && !bugged_about_bug)
               {
                  ERR("Pageflip fail - EBUSY from drmModePageFlip - "
                      "This is either a kernel bug or an EFL one.");
                  bugged_about_bug = EINA_TRUE;
               }
             count++;
             if (count > 500)
               {
                  ERR("Pageflip EBUSY for %i tries - give up", count);
                  break;
               }
             usleep(100);
          }
        else
          {
             // XXX: output ref++
             ecore_main_loop_thread_safe_call_async
               (_cb_mainloop_async_timer_reset, output);
          }
     }
   while (repeat);

   if ((ret == 0) && (count > 0))
     DBG("Pageflip finally succeeded after %i tries due to EBUSY", count);

   if ((ret < 0) && (errno != EBUSY))
     {
        ERR("Pageflip Failed for Crtc %u on Connector %u: %m",
            output->crtc_id, output->conn_id);
        return ret;
     }
   else if (ret < 0)
     {
        output->next.fb = fb;
        _ecore_drm2_fb_ref(output->next.fb);
     }

   return 0;
}

EAPI int
ecore_drm2_fb_flip(Ecore_Drm2_Fb *fb, Ecore_Drm2_Output *output)
{
   int ret = -1;

   EINA_SAFETY_ON_NULL_RETURN_VAL(output, -1);
   EINA_SAFETY_ON_NULL_RETURN_VAL(output->current_mode, -1);

   if (!output->enabled) return -1;

   if (fb) _ecore_drm2_fb_ref(fb);

   if (output->pending.fb)
     {
        if (output->next.fb)
          _ecore_drm2_fb_buffer_release(output, &output->next);
        output->next.fb = fb;
        return 0;
     }
   if (!fb)
     {
        fb = output->next.fb;
        output->next.fb = NULL;
     }

   /* So we can generate a tick by flipping to the current fb */
   if (!fb) fb = output->current.fb;

   if (output->next.fb)
     _ecore_drm2_fb_buffer_release(output, &output->next);

   /* If we don't have an fb to set by now, BAIL! */
   if (!fb) return -1;

   output->prep.fb = fb;

   if (_ecore_drm2_use_atomic)
     ret = _fb_atomic_flip(output);
   else
     ret = _fb_flip(output);

   if (ret)
     {
        if (output->prep.fb != output->current.fb)
          _ecore_drm2_fb_buffer_release(output, &output->prep);
        return ret;
     }
   output->pending.fb = output->prep.fb;
   output->prep.fb = NULL;

   if (_ecore_drm2_use_atomic)
     {
        output->pending.atomic_req = output->prep.atomic_req;
        output->prep.atomic_req = NULL;
     }

   return 0;
}

EAPI Eina_Bool
ecore_drm2_fb_busy_get(Ecore_Drm2_Fb *fb)
{
   EINA_SAFETY_ON_NULL_RETURN_VAL(fb, EINA_FALSE);
   EINA_SAFETY_ON_TRUE_RETURN_VAL(fb->dead, EINA_FALSE);

   return !!(fb->ref - 1);
}

EAPI Eina_Bool
ecore_drm2_fb_release(Ecore_Drm2_Output *o, Eina_Bool panic)
{
   EINA_SAFETY_ON_NULL_RETURN_VAL(o, EINA_FALSE);

   if (o->next.fb)
     {
        _ecore_drm2_fb_buffer_release(o, &o->next);
        return EINA_TRUE;
     }
   if (!panic) return EINA_FALSE;

   /* This has been demoted to DBG from WRN because we
    * call this function to reclaim all buffers on a
    * surface resize.
    */
   DBG("Buffer release request when no next buffer");
   /* If we have to release these we're going to see tearing.
    * Try to reclaim in decreasing order of visual awfulness
    */
   if (o->current.fb)
     {
        _ecore_drm2_fb_buffer_release(o, &o->current);
        return EINA_TRUE;
     }

   if (o->pending.fb)
     {
        _ecore_drm2_fb_buffer_release(o, &o->pending);
        return EINA_TRUE;
     }

   return EINA_FALSE;
}

EAPI void *
ecore_drm2_fb_bo_get(Ecore_Drm2_Fb *fb)
{
   EINA_SAFETY_ON_NULL_RETURN_VAL(fb, NULL);
   EINA_SAFETY_ON_TRUE_RETURN_VAL(fb->dead, NULL);

   return fb->gbm_bo;
}

EAPI Ecore_Drm2_Fb *
ecore_drm2_fb_dmabuf_import(Ecore_Drm2_Device *dev, int width, int height, int depth, int bpp, unsigned int format, unsigned int strides[4], int dmabuf_fd[4], int dmabuf_fd_count)
{
   int i;
   Ecore_Drm2_Fb *fb;

   EINA_SAFETY_ON_NULL_RETURN_VAL(dev, NULL);

   fb = calloc(1, sizeof(Ecore_Drm2_Fb));
   if (!fb) return NULL;

   for (i = 0; i < dmabuf_fd_count; i++)
     if (sym_drmPrimeFDToHandle(dev->fd, dmabuf_fd[i], &fb->handles[i]))
       goto fail;

   fb->dmabuf = EINA_TRUE;
   fb->fd = dev->fd;
   fb->w = width;
   fb->h = height;
   fb->bpp = bpp;
   fb->depth = depth;
   fb->format = format;
   fb->ref = 1;

   memcpy(&fb->strides, strides, sizeof(fb->strides));
   if (_fb2_create(fb)) return fb;

fail:
   free(fb);
   return NULL;
}

EAPI void
ecore_drm2_fb_status_handler_set(Ecore_Drm2_Fb *fb, Ecore_Drm2_Fb_Status_Handler handler, void *data)
{
   fb->status_handler = handler;
   fb->status_data = data;
}
