libds-tizen: Add ds_tdm_buffer_queue 56/278156/1
authorSeunghun Lee <shiin.lee@samsung.com>
Wed, 16 Mar 2022 04:18:21 +0000 (13:18 +0900)
committerSooChan Lim <sc1.lim@samsung.com>
Mon, 18 Jul 2022 05:58:19 +0000 (14:58 +0900)
A ds_tdm_buffer_queue is a buffer queue that is able to be acquired from
ds_tdm_output.

A ds_tdm_buffer_queue provides a handle of native queue to a user and
the user may pass it to a renderer. The renderer then should think of
native queue as tbm_surface_queue_h. With the tbm_surface_queue_h, the
renderer may dequeue a surface from it and draw on the dequeued buffer.
After finish drawing on the buffer, the renderer should enqueue it to
the tbm_surface_queue_h.

As soon as the renderer enqueues buffer to the tbm_surface_queue_h, the
user can recieve a notification that the buffer can be acquired from the
ds_tbm_buffer_queue using ds_tdm_buffer_queue_add_acquirable_listener().
Then user may acquire a buffer from the ds_tdm_buffer_queue and attach
it to a ds_output.

Note that although the renderer may enqueue a buffer on different thread
from the thread working on libds, but libds will call a callback
function which is registered using
ds_tdm_buffer_queue_add_acquirable_listener() on the thread working on
libds.

Change-Id: Ib50d2f11cdb27c0aa34b6beeecc73f012032c685

include/libds-tizen/backend/tdm.h
src/libds-tizen/backend/tdm/meson.build
src/libds-tizen/backend/tdm/output.c
src/libds-tizen/backend/tdm/tdm.h
src/libds-tizen/backend/tdm/tdm_buffer_queue.c [new file with mode: 0644]
src/libds-tizen/backend/tdm/tdm_buffer_queue.h [new file with mode: 0644]

index d2785b2..8c5c605 100644 (file)
@@ -2,14 +2,35 @@
 #define LIBDS_TIZEN_BACKEND_TDM_H
 
 #include <libds/backend.h>
+#include <libds/buffer.h>
 
 #ifdef __cplusplus
 extern "C" {
 #endif
 
+struct ds_tdm_output;
+
+struct ds_tdm_buffer_queue;
+
 struct ds_backend *
 ds_tdm_backend_create(struct wl_display *display);
 
+struct ds_tdm_output *
+ds_tdm_output_from_output(struct ds_output *ds_output);
+
+struct ds_tdm_buffer_queue *
+ds_tdm_output_get_buffer_queue(struct ds_tdm_output *output);
+
+void *
+ds_tdm_buffer_queue_get_native_queue(struct ds_tdm_buffer_queue *queue);
+
+struct ds_buffer *
+ds_tdm_buffer_queue_acquire(struct ds_tdm_buffer_queue *queue);
+
+void
+ds_tdm_buffer_queue_add_acquirable_listener(struct ds_tdm_buffer_queue *queue,
+        struct wl_listener *listener);
+
 #ifdef __cplusplus
 }
 #endif
index 39ffceb..932559f 100644 (file)
@@ -1,6 +1,7 @@
 libds_tizen_files += files(
   'backend.c',
   'output.c',
+  'tdm_buffer_queue.c',
 )
 
 libtdm = dependency('libtdm', required: true)
index f6b6250..10b9666 100644 (file)
@@ -7,6 +7,7 @@
 #include "libds-tizen/allocator/tbm.h"
 
 #include "tdm.h"
+#include "tdm_buffer_queue.h"
 
 static const struct ds_output_interface tdm_output_iface;
 static bool output_init_modes(struct ds_tdm_output *output);
@@ -17,6 +18,32 @@ static bool output_set_pending_fb(struct ds_tdm_output *output,
         struct ds_buffer *ds_buffer);
 static bool output_hwc_commit(struct ds_tdm_output *output);
 
+WL_EXPORT struct ds_tdm_output *
+ds_tdm_output_from_output(struct ds_output *ds_output)
+{
+    if (ds_output->iface != &tdm_output_iface) {
+        ds_err("Given ds_output is not for ds_tdm_output");
+        return NULL;
+    }
+
+    return (struct ds_tdm_output *)ds_output;
+}
+
+WL_EXPORT struct ds_tdm_buffer_queue *
+ds_tdm_output_get_buffer_queue(struct ds_tdm_output *output)
+{
+    if (output->queue)
+        return output->queue;
+
+    output->queue = create_buffer_queue(output);
+    if (!output->queue) {
+        ds_err("Could not create tbm_queue with output(%p)", output);
+        return NULL;
+    }
+
+    return output->queue;
+}
+
 struct ds_tdm_output *
 create_tdm_output(struct ds_tdm_backend *tdm, tdm_output *tdm_output)
 {
@@ -89,12 +116,25 @@ destroy_tdm_buffer(struct ds_tdm_buffer *buffer)
     if (buffer == NULL)
         return;
 
+    if (!buffer->released)
+        wl_list_remove(&buffer->buffer_release.link);
+
     wl_list_remove(&buffer->buffer_destroy.link);
     wl_list_remove(&buffer->link);
     free(buffer);
 }
 
 static void
+buffer_handle_buffer_release(struct wl_listener *listener, void *data)
+{
+    struct ds_tdm_buffer *buffer;
+
+    buffer = wl_container_of(listener, buffer, buffer_release);
+    wl_list_remove(&buffer->buffer_release.link);
+    buffer->released = true;
+}
+
+static void
 buffer_handle_buffer_destroy(struct wl_listener *listener, void *data)
 {
     struct ds_tdm_buffer *buffer;
@@ -120,7 +160,7 @@ create_tdm_buffer(struct ds_tdm_backend *backend, struct ds_buffer *ds_buffer)
         return NULL;
 
     buffer->surface = surface;
-    buffer->buffer = ds_buffer_lock(ds_buffer);
+    buffer->buffer = ds_buffer;
     wl_list_insert(&backend->buffers, &buffer->link);
 
     buffer->buffer_destroy.notify = buffer_handle_buffer_destroy;
@@ -137,37 +177,38 @@ get_or_create_tdm_buffer(struct ds_tdm_backend *backend,
 
     wl_list_for_each(buffer, &backend->buffers, link) {
         if (buffer->buffer == ds_buffer && buffer->released) {
-            buffer->released = false;
-            ds_buffer_lock(buffer->buffer);
-            return buffer;
+            goto out;
         }
     }
 
-    return create_tdm_buffer(backend, ds_buffer);
-}
+    buffer = create_tdm_buffer(backend, ds_buffer);
 
-static void
-tdm_buffer_release(struct ds_tdm_buffer *buffer)
-{
-    buffer->released = true;
-    ds_buffer_unlock(buffer->buffer);
+out:
+    buffer->released = false;
+
+    buffer->buffer_release.notify = buffer_handle_buffer_release;
+    ds_buffer_add_release_listener(ds_buffer, &buffer->buffer_release);
+
+    ds_buffer_lock(buffer->buffer);
+
+    return buffer;
 }
 
 static void
 output_update_front_buffer(struct ds_tdm_output *output)
 {
     if (output->front_buffer)
-        tdm_buffer_release(output->front_buffer);
+        ds_buffer_unlock(output->front_buffer);
     output->front_buffer = output->back_buffer;
     output->back_buffer = NULL;
 }
 
 static void
 output_attach_back_buffer(struct ds_tdm_output *output,
-        struct ds_tdm_buffer *buffer)
+        struct ds_buffer *buffer)
 {
     if (output->back_buffer)
-        tdm_buffer_release(output->back_buffer);
+        ds_buffer_unlock(output->back_buffer);
     output->back_buffer = buffer;
 }
 
@@ -192,9 +233,8 @@ output_iface_commit(struct ds_output *ds_output)
 
     if (!output_hwc_commit(output)) {
         ds_err("Could not commit tdm output");
-        if (output->back_buffer) {
-            tdm_buffer_release(output->back_buffer);
-        }
+        if (output->back_buffer)
+            ds_buffer_unlock(output->back_buffer);
     }
 
     return true;
@@ -217,10 +257,13 @@ output_destroy(struct ds_tdm_output *output)
     }
 
     if (output->back_buffer)
-        ds_buffer_unlock(output->back_buffer->buffer);
+        ds_buffer_unlock(output->back_buffer);
 
     if (output->front_buffer)
-        ds_buffer_unlock(output->front_buffer->buffer);
+        ds_buffer_unlock(output->front_buffer);
+
+    if (output->queue)
+        buffer_queue_destroy(output->queue);
 
     free(output);
 }
@@ -306,24 +349,38 @@ static bool
 output_set_pending_fb(struct ds_tdm_output *output,
         struct ds_buffer *ds_buffer)
 {
+    struct ds_tdm_queue_buffer *queue_buffer;
     struct ds_tdm_buffer *buffer;
+    tbm_surface_h surface = NULL;
     tdm_region fb_damage;
     tdm_error err;
 
-    buffer = get_or_create_tdm_buffer(output->backend, ds_buffer);
-    if (!buffer)
-        return false;
+    if (output->queue) {
+        queue_buffer = buffer_queue_find_buffer(output->queue, ds_buffer);
+        if (queue_buffer) {
+            ds_buffer_lock(ds_buffer);
+            surface = queue_buffer->surface;
+        }
+    }
+
+    if (!surface) {
+        buffer = get_or_create_tdm_buffer(output->backend, ds_buffer);
+        if (!buffer)
+            return false;
+
+        surface = buffer->surface;
+    }
 
     memset(&fb_damage, 0, sizeof(fb_damage));
     err = tdm_hwc_set_client_target_buffer(output->tdm.hwc,
-            buffer->surface, fb_damage);
+            surface, fb_damage);
     if (err != TDM_ERROR_NONE) {
         ds_err("Could not set hwc client target buffer");
-        ds_buffer_unlock(buffer->buffer);
+        ds_buffer_unlock(ds_buffer);
         return false;
     }
 
-    output_attach_back_buffer(output, buffer);
+    output_attach_back_buffer(output, ds_buffer);
 
     return true;
 }
index 275089c..f19483f 100644 (file)
@@ -40,7 +40,8 @@ struct ds_tdm_output
     struct ds_output base;
 
     struct ds_tdm_backend *backend;
-    struct ds_tdm_buffer *front_buffer, *back_buffer;
+    struct ds_tdm_buffer_queue *queue;
+    struct ds_buffer *front_buffer, *back_buffer;
 
     struct {
         tdm_output *output;
@@ -57,6 +58,8 @@ struct ds_tdm_buffer
     struct ds_buffer *buffer;
     tbm_surface_h surface;
     struct wl_list link; // ds_wl_backend.buffers
+
+    struct wl_listener buffer_release;
     struct wl_listener buffer_destroy;
 
     bool released;
diff --git a/src/libds-tizen/backend/tdm/tdm_buffer_queue.c b/src/libds-tizen/backend/tdm/tdm_buffer_queue.c
new file mode 100644 (file)
index 0000000..7a2a1aa
--- /dev/null
@@ -0,0 +1,311 @@
+#include <assert.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <sys/eventfd.h>
+
+#include "libds/log.h"
+#include "libds/interfaces/buffer.h"
+
+#include "tdm.h"
+#include "tdm_buffer_queue.h"
+
+static void
+buffer_queue_handle_acquirable(tbm_surface_queue_h surface_queue,
+        void *data);
+static struct ds_tdm_queue_buffer *
+create_queue_buffer(struct ds_tdm_buffer_queue *queue,
+        tbm_surface_h surface);
+static void queue_buffer_destroy(struct ds_tdm_queue_buffer *buffer);
+static void queue_buffer_drop(struct ds_tdm_queue_buffer *buffer);
+static struct ds_buffer *
+queue_buffer_acquire(struct ds_tdm_queue_buffer *buffer);
+static int buffer_queue_handle_acquirable_efd(int fd, uint32_t mask,
+        void *data);
+
+WL_EXPORT void *
+ds_tdm_buffer_queue_get_native_queue(struct ds_tdm_buffer_queue *queue)
+{
+    return (void *)queue->tbm_surface_queue;
+}
+
+WL_EXPORT struct ds_buffer *
+ds_tdm_buffer_queue_acquire(struct ds_tdm_buffer_queue *queue)
+{
+    struct ds_tdm_queue_buffer *buffer;
+    tbm_surface_h surface;
+    tbm_surface_queue_error_e err;
+
+    if (!tbm_surface_queue_can_acquire(queue->tbm_surface_queue, 0))
+        return NULL;
+
+    err = tbm_surface_queue_acquire(queue->tbm_surface_queue, &surface);
+    if (err != TBM_SURFACE_QUEUE_ERROR_NONE ||
+            surface == NULL) {
+        ds_err("Could not acquire tbm_surface from queue(%p)", queue);
+        return NULL;
+    }
+
+    wl_list_for_each(buffer, &queue->buffers, link) {
+        if (buffer->surface == surface)
+            return queue_buffer_acquire(buffer);
+    }
+
+    buffer = create_queue_buffer(queue, surface);
+    if (!buffer) {
+        ds_err("Could not create tbm_queue_buffer with queue(%p)", queue);
+        return NULL;
+    }
+
+    wl_list_insert(&queue->buffers, &buffer->link);
+
+    return queue_buffer_acquire(buffer);
+}
+
+WL_EXPORT void
+ds_tdm_buffer_queue_add_acquirable_listener(struct ds_tdm_buffer_queue *queue,
+        struct wl_listener *listener)
+{
+    wl_signal_add(&queue->events.acquirable, listener);
+}
+
+struct ds_tdm_buffer_queue *
+create_buffer_queue(struct ds_tdm_output *output)
+{
+    struct ds_tdm_buffer_queue *queue;
+    tdm_error err;
+
+    queue = calloc(1, sizeof *queue);
+    if (!queue)
+        return NULL;
+
+    wl_list_init(&queue->buffers);
+
+    wl_signal_init(&queue->events.acquirable);
+
+    queue->tbm_surface_queue =
+        tdm_hwc_get_client_target_buffer_queue(output->tdm.hwc, &err);
+    if (err != TDM_ERROR_NONE ||
+            queue->tbm_surface_queue == NULL) {
+        ds_err("Could not get target buffer queue: err(%d)", err);
+        free(queue);
+        return NULL;
+    }
+
+    tbm_surface_queue_reset(queue->tbm_surface_queue,
+            output->base.pending.mode->width,
+            output->base.pending.mode->height,
+            tbm_surface_queue_get_format(queue->tbm_surface_queue));
+
+    /* The callback function for tbm_surface_queue_add_acquirable_cb() may be
+     * called on different thread. This eventfd is to emit a signal of
+     * events.acquirable on the thread getting this buffer queue. */
+    queue->acquirable_efd = eventfd(0, EFD_NONBLOCK);
+    if (queue->acquirable_efd < 0) {
+        ds_log_errno(DS_ERR,
+                "Could not create eventfd for acquirable callback");
+        free(queue);
+        return NULL;
+    }
+
+    queue->acquirable_source = wl_event_loop_add_fd(
+            wl_display_get_event_loop(output->backend->wl_display),
+            queue->acquirable_efd,
+            WL_EVENT_READABLE,
+            buffer_queue_handle_acquirable_efd,
+            queue);
+
+    tbm_surface_queue_add_acquirable_cb(queue->tbm_surface_queue,
+            buffer_queue_handle_acquirable, (void *)queue);
+
+    return queue;
+}
+
+void
+buffer_queue_destroy(struct ds_tdm_buffer_queue *queue)
+{
+    struct ds_tdm_queue_buffer *buffer, *buffer_tmp;
+
+    wl_list_for_each_safe(buffer, buffer_tmp, &queue->buffers, link)
+        queue_buffer_drop(buffer);
+
+    wl_event_source_remove(queue->acquirable_source);
+    close(queue->acquirable_efd);
+    tbm_surface_queue_destroy(queue->tbm_surface_queue);
+    free(queue);
+}
+
+struct ds_tdm_queue_buffer *
+buffer_queue_find_buffer(struct ds_tdm_buffer_queue *queue,
+        struct ds_buffer *ds_buffer)
+{
+    struct ds_tdm_queue_buffer *buffer;
+
+    wl_list_for_each(buffer, &queue->buffers, link) {
+        if (&buffer->base == ds_buffer)
+            return buffer;
+    }
+
+    return NULL;
+}
+
+static void
+buffer_queue_handle_acquirable(tbm_surface_queue_h surface_queue, void *data)
+{
+    struct ds_tdm_buffer_queue *queue = data;
+    uint64_t acquirable = 1;
+    int ret;
+
+    ret = write(queue->acquirable_efd, &acquirable, sizeof(acquirable));
+    if (ret < 0)
+        ds_log_errno(DS_ERR, "Could not write eventfd for acquirable buffer");
+}
+
+static const struct ds_buffer_interface queue_buffer_iface;
+
+static struct ds_tdm_queue_buffer *
+create_queue_buffer(struct ds_tdm_buffer_queue *queue, tbm_surface_h surface)
+{
+    struct ds_tdm_queue_buffer *buffer;
+
+    buffer = calloc(1, sizeof *buffer);
+    if (!buffer)
+        return NULL;
+
+    ds_buffer_init(&buffer->base, &queue_buffer_iface,
+            tbm_surface_get_width(surface),
+            tbm_surface_get_height(surface));
+
+    buffer->queue = queue;
+    buffer->surface = surface;
+
+    return buffer;
+}
+
+static void
+queue_buffer_destroy(struct ds_tdm_queue_buffer *buffer)
+{
+    free(buffer);
+}
+
+static struct ds_tdm_queue_buffer *
+queue_buffer_from_buffer(struct ds_buffer *ds_buffer)
+{
+    assert(ds_buffer->iface == &queue_buffer_iface);
+    return (struct ds_tdm_queue_buffer *)ds_buffer;
+}
+
+static void
+queue_buffer_iface_destroy(struct ds_buffer *ds_buffer)
+{
+    struct ds_tdm_queue_buffer *buffer;
+
+    buffer = queue_buffer_from_buffer(ds_buffer);
+    queue_buffer_destroy(buffer);
+}
+
+static bool
+queue_buffer_iface_begin_data_ptr_access(struct ds_buffer *ds_buffer,
+        uint32_t flags, void **data, uint32_t *format, size_t *stride)
+{
+    struct ds_tdm_queue_buffer *buffer;
+    tbm_surface_info_s info;
+    int tbm_access_flags = 0;
+    int ret;
+
+    buffer = queue_buffer_from_buffer(ds_buffer);
+
+    if (!buffer->surface) {
+        ds_err("No tbm_surface. It's a dropped buffer(%p)", buffer);
+        return false;
+    }
+
+    if (flags & DS_BUFFER_DATA_PTR_ACCESS_READ)
+        tbm_access_flags |= TBM_OPTION_READ;
+    else if (flags & DS_BUFFER_DATA_PTR_ACCESS_WRITE)
+        tbm_access_flags |= TBM_OPTION_WRITE;
+
+    ret = tbm_surface_map(buffer->surface, tbm_access_flags, &info);
+    if (ret != TBM_SURFACE_ERROR_NONE) {
+        ds_err("Could not map tbm_surface of buffer(%p)", buffer);
+        return false;
+    }
+
+    *data = info.planes[0].ptr;
+    *format = info.format;
+    *stride = info.planes[0].stride;
+
+    return true;
+}
+
+static void
+queue_buffer_iface_end_data_ptr_access(struct ds_buffer *ds_buffer)
+{
+    struct ds_tdm_queue_buffer *buffer;
+
+    buffer = queue_buffer_from_buffer(ds_buffer);
+    if (!buffer->surface) {
+        ds_err("No tbm_surface. It's a dropped buffer(%p)", buffer);
+        return;
+    }
+
+    tbm_surface_unmap(buffer->surface);
+}
+
+static const struct ds_buffer_interface queue_buffer_iface =
+{
+    .destroy = queue_buffer_iface_destroy,
+    .begin_data_ptr_access = queue_buffer_iface_begin_data_ptr_access,
+    .end_data_ptr_access = queue_buffer_iface_end_data_ptr_access,
+};
+
+static void
+queue_buffer_handle_buffer_release(struct wl_listener *listener, void *data)
+{
+    struct ds_tdm_queue_buffer *buffer;
+
+    buffer = wl_container_of(listener, buffer, buffer_release);
+
+    wl_list_remove(&buffer->buffer_release.link);
+    buffer->acquired = false;
+
+    tbm_surface_queue_release(buffer->queue->tbm_surface_queue,
+            buffer->surface);
+}
+
+static void
+queue_buffer_drop(struct ds_tdm_queue_buffer *buffer)
+{
+    if (buffer->acquired)
+        wl_list_remove(&buffer->buffer_release.link);
+
+    wl_list_remove(&buffer->link);
+    ds_buffer_drop(&buffer->base);
+    buffer->surface = NULL;
+}
+
+static struct ds_buffer *
+queue_buffer_acquire(struct ds_tdm_queue_buffer *buffer)
+{
+    assert(!buffer->acquired);
+
+    buffer->acquired = true;
+    buffer->buffer_release.notify = queue_buffer_handle_buffer_release;
+    ds_buffer_add_release_listener(&buffer->base, &buffer->buffer_release);
+
+    return ds_buffer_lock(&buffer->base);
+}
+
+static int
+buffer_queue_handle_acquirable_efd(int fd, uint32_t mask, void *data)
+{
+    struct ds_tdm_buffer_queue *queue = data;
+    uint64_t acquirable_event;
+
+    if (read(fd, &acquirable_event, sizeof(acquirable_event)) < 0 &&
+            errno != EAGAIN)
+        return -1;
+
+    wl_signal_emit(&queue->events.acquirable, queue);
+
+    return 0;
+}
diff --git a/src/libds-tizen/backend/tdm/tdm_buffer_queue.h b/src/libds-tizen/backend/tdm/tdm_buffer_queue.h
new file mode 100644 (file)
index 0000000..774b7c3
--- /dev/null
@@ -0,0 +1,45 @@
+#ifndef DS_TIZEN_BACKEND_TDM_BUFFER_QUEUE_H
+#define DS_TIZEN_BACKEND_TBM_BUFFER_QUEUE_H
+
+#include <tbm_surface_queue.h>
+#include <libds/interfaces/buffer.h>
+
+#include "tdm.h"
+
+struct ds_tdm_buffer_queue
+{
+    tbm_surface_queue_h tbm_surface_queue;
+    struct wl_event_source *acquirable_source;
+
+    struct wl_list buffers; // ds_tdm_queue_buffer.link
+
+    struct {
+        struct wl_signal acquirable;
+    } events;
+
+    int acquirable_efd;
+};
+
+struct ds_tdm_queue_buffer
+{
+    struct ds_buffer base;
+
+    struct ds_tdm_output *output;
+    struct ds_tdm_buffer_queue *queue;
+    tbm_surface_h surface;
+
+    struct wl_list link;
+    struct wl_listener buffer_release;
+
+    bool acquired;
+};
+
+struct ds_tdm_buffer_queue *create_buffer_queue(struct ds_tdm_output *output);
+
+void buffer_queue_destroy(struct ds_tdm_buffer_queue *queue);
+
+struct ds_tdm_queue_buffer *
+buffer_queue_find_buffer(struct ds_tdm_buffer_queue *queue,
+        struct ds_buffer *ds_buffer);
+
+#endif