/* * Copyright (C) 2022 Pascal Nowack * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ #include "config.h" #include "grd-rdp-buffer-pool.h" #include "grd-context.h" #include "grd-egl-thread.h" #include "grd-rdp-legacy-buffer.h" #include "grd-rdp-renderer.h" #include "grd-rdp-server.h" #include "grd-rdp-surface.h" #include "grd-session-rdp.h" #include "grd-utils.h" typedef struct _BufferInfo { gboolean buffer_taken; } BufferInfo; struct _GrdRdpBufferPool { GObject parent; GrdRdpSurface *rdp_surface; uint32_t buffer_height; uint32_t buffer_stride; GSource *resize_pool_source; uint32_t minimum_pool_size; GSource *unmap_source; GMutex pool_mutex; GHashTable *buffer_table; uint32_t buffers_taken; }; G_DEFINE_TYPE (GrdRdpBufferPool, grd_rdp_buffer_pool, G_TYPE_OBJECT) GrdRdpSurface * grd_rdp_buffer_pool_get_surface (GrdRdpBufferPool *buffer_pool) { return buffer_pool->rdp_surface; } static gboolean add_buffer_to_pool (GrdRdpBufferPool *buffer_pool, gboolean preallocate_on_gpu) { GrdRdpLegacyBuffer *buffer; BufferInfo *buffer_info; buffer = grd_rdp_legacy_buffer_new (buffer_pool, buffer_pool->buffer_height, buffer_pool->buffer_stride, preallocate_on_gpu); if (!buffer) return FALSE; buffer_info = g_new0 (BufferInfo, 1); g_hash_table_insert (buffer_pool->buffer_table, buffer, buffer_info); return TRUE; } static gboolean fill_buffer_pool (GrdRdpBufferPool *buffer_pool) { uint32_t minimum_size = buffer_pool->minimum_pool_size; while (g_hash_table_size (buffer_pool->buffer_table) < minimum_size) { if (!add_buffer_to_pool (buffer_pool, TRUE)) return FALSE; } return TRUE; } gboolean grd_rdp_buffer_pool_resize_buffers (GrdRdpBufferPool *buffer_pool, uint32_t buffer_height, uint32_t buffer_stride) { g_autoptr (GMutexLocker) locker = NULL; locker = g_mutex_locker_new (&buffer_pool->pool_mutex); g_assert (buffer_pool->buffers_taken == 0); buffer_pool->buffer_height = buffer_height; buffer_pool->buffer_stride = buffer_stride; g_hash_table_remove_all (buffer_pool->buffer_table); if (!fill_buffer_pool (buffer_pool)) return FALSE; return TRUE; } static gboolean buffer_has_mapped_data (GrdRdpLegacyBuffer *buffer) { if (grd_rdp_legacy_buffer_get_mapped_cuda_pointer (buffer)) return TRUE; return FALSE; } GrdRdpLegacyBuffer * grd_rdp_buffer_pool_acquire (GrdRdpBufferPool *buffer_pool) { g_autoptr (GMutexLocker) locker = NULL; GHashTableIter iter; GrdRdpLegacyBuffer *buffer; BufferInfo *buffer_info; gboolean buffer_found = FALSE; g_assert (buffer_pool->buffer_height > 0); g_assert (buffer_pool->buffer_stride > 0); locker = g_mutex_locker_new (&buffer_pool->pool_mutex); if (g_hash_table_size (buffer_pool->buffer_table) <= buffer_pool->buffers_taken && !add_buffer_to_pool (buffer_pool, FALSE)) return NULL; g_hash_table_iter_init (&iter, buffer_pool->buffer_table); while (g_hash_table_iter_next (&iter, (gpointer *) &buffer, (gpointer *) &buffer_info)) { if (!buffer_info->buffer_taken && !buffer_has_mapped_data (buffer)) { buffer_found = TRUE; break; } } if (!buffer_found) { g_hash_table_iter_init (&iter, buffer_pool->buffer_table); while (g_hash_table_iter_next (&iter, (gpointer *) &buffer, (gpointer *) &buffer_info)) { if (!buffer_info->buffer_taken) break; } } g_assert (buffer); buffer_info->buffer_taken = TRUE; ++buffer_pool->buffers_taken; return buffer; } static gboolean should_resize_buffer_pool (GrdRdpBufferPool *buffer_pool) { uint32_t buffers_taken = buffer_pool->buffers_taken; uint32_t minimum_size = buffer_pool->minimum_pool_size; uint32_t current_pool_size; current_pool_size = g_hash_table_size (buffer_pool->buffer_table); if (current_pool_size > minimum_size && current_pool_size > buffers_taken) return TRUE; return FALSE; } void grd_rdp_buffer_pool_release_buffer (GrdRdpBufferPool *buffer_pool, GrdRdpLegacyBuffer *buffer) { BufferInfo *buffer_info; gboolean queue_pool_resize; gboolean queue_unmap; g_mutex_lock (&buffer_pool->pool_mutex); if (!g_hash_table_lookup_extended (buffer_pool->buffer_table, buffer, NULL, (gpointer *) &buffer_info)) g_assert_not_reached (); g_assert (buffer_info->buffer_taken); buffer_info->buffer_taken = FALSE; --buffer_pool->buffers_taken; queue_pool_resize = should_resize_buffer_pool (buffer_pool); queue_unmap = buffer_has_mapped_data (buffer); g_mutex_unlock (&buffer_pool->pool_mutex); if (queue_pool_resize) g_source_set_ready_time (buffer_pool->resize_pool_source, 0); if (queue_unmap) g_source_set_ready_time (buffer_pool->unmap_source, 0); } static gboolean resize_buffer_pool (gpointer user_data) { GrdRdpBufferPool *buffer_pool = user_data; GHashTableIter iter; BufferInfo *buffer_info; g_mutex_lock (&buffer_pool->pool_mutex); g_hash_table_iter_init (&iter, buffer_pool->buffer_table); while (should_resize_buffer_pool (buffer_pool) && g_hash_table_iter_next (&iter, NULL, (gpointer *) &buffer_info)) { if (!buffer_info->buffer_taken) g_hash_table_iter_remove (&iter); } g_mutex_unlock (&buffer_pool->pool_mutex); return G_SOURCE_CONTINUE; } static gboolean unmap_untaken_buffers (gpointer user_data) { GrdRdpBufferPool *buffer_pool = user_data; GHashTableIter iter; GrdRdpLegacyBuffer *buffer; BufferInfo *buffer_info; g_mutex_lock (&buffer_pool->pool_mutex); g_hash_table_iter_init (&iter, buffer_pool->buffer_table); while (g_hash_table_iter_next (&iter, (gpointer *) &buffer, (gpointer *) &buffer_info)) { if (!buffer_info->buffer_taken) grd_rdp_legacy_buffer_queue_resource_unmap (buffer); } g_mutex_unlock (&buffer_pool->pool_mutex); return G_SOURCE_CONTINUE; } static gboolean buffer_pool_source_dispatch (GSource *source, GSourceFunc callback, gpointer user_data) { g_source_set_ready_time (source, -1); return callback (user_data); } static GSourceFuncs buffer_pool_source_funcs = { .dispatch = buffer_pool_source_dispatch, }; GrdRdpBufferPool * grd_rdp_buffer_pool_new (GrdRdpSurface *rdp_surface, uint32_t minimum_size) { g_autoptr (GrdRdpBufferPool) buffer_pool = NULL; buffer_pool = g_object_new (GRD_TYPE_RDP_BUFFER_POOL, NULL); buffer_pool->rdp_surface = rdp_surface; buffer_pool->minimum_pool_size = minimum_size; buffer_pool->resize_pool_source = g_source_new (&buffer_pool_source_funcs, sizeof (GSource)); g_source_set_callback (buffer_pool->resize_pool_source, resize_buffer_pool, buffer_pool, NULL); g_source_set_ready_time (buffer_pool->resize_pool_source, -1); g_source_attach (buffer_pool->resize_pool_source, NULL); buffer_pool->unmap_source = g_source_new (&buffer_pool_source_funcs, sizeof (GSource)); g_source_set_callback (buffer_pool->unmap_source, unmap_untaken_buffers, buffer_pool, NULL); g_source_set_priority (buffer_pool->unmap_source, G_PRIORITY_LOW); g_source_set_ready_time (buffer_pool->unmap_source, -1); g_source_attach (buffer_pool->unmap_source, NULL); return g_steal_pointer (&buffer_pool); } static void on_sync_complete (gboolean success, gpointer user_data) { GrdSyncPoint *sync_point = user_data; grd_sync_point_complete (sync_point, success); } static void sync_egl_thread (GrdRdpBufferPool *buffer_pool) { GrdRdpRenderer *renderer = buffer_pool->rdp_surface->renderer; GrdSessionRdp *session_rdp = grd_rdp_renderer_get_session (renderer); GrdRdpServer *rdp_server = grd_session_rdp_get_server (session_rdp); GrdContext *context = grd_rdp_server_get_context (rdp_server); GrdEglThread *egl_thread = grd_context_get_egl_thread (context); GrdSyncPoint sync_point = {}; grd_sync_point_init (&sync_point); grd_egl_thread_sync (egl_thread, on_sync_complete, &sync_point, NULL); grd_sync_point_wait_for_completion (&sync_point); grd_sync_point_clear (&sync_point); } static void grd_rdp_buffer_pool_dispose (GObject *object) { GrdRdpBufferPool *buffer_pool = GRD_RDP_BUFFER_POOL (object); if (buffer_pool->unmap_source) { g_source_destroy (buffer_pool->unmap_source); g_clear_pointer (&buffer_pool->unmap_source, g_source_unref); } if (buffer_pool->resize_pool_source) { g_source_destroy (buffer_pool->resize_pool_source); g_clear_pointer (&buffer_pool->resize_pool_source, g_source_unref); } g_assert (buffer_pool->buffers_taken == 0); G_OBJECT_CLASS (grd_rdp_buffer_pool_parent_class)->dispose (object); } static void grd_rdp_buffer_pool_finalize (GObject *object) { GrdRdpBufferPool *buffer_pool = GRD_RDP_BUFFER_POOL (object); GrdRdpRenderer *renderer = buffer_pool->rdp_surface->renderer; GrdSessionRdp *session_rdp = grd_rdp_renderer_get_session (renderer); GrdRdpServer *rdp_server = grd_session_rdp_get_server (session_rdp); GrdContext *context = grd_rdp_server_get_context (rdp_server); GrdEglThread *egl_thread = grd_context_get_egl_thread (context); g_mutex_clear (&buffer_pool->pool_mutex); g_clear_pointer (&buffer_pool->buffer_table, g_hash_table_unref); /* * All buffers need to be destroyed, before the pool is freed to avoid use * after free by the EGL thread, when the RDP server is shut down and with it * the GrdHwAccelNvidia instance */ if (egl_thread) sync_egl_thread (buffer_pool); G_OBJECT_CLASS (grd_rdp_buffer_pool_parent_class)->finalize (object); } static void grd_rdp_buffer_pool_init (GrdRdpBufferPool *buffer_pool) { buffer_pool->buffer_table = g_hash_table_new_full (NULL, NULL, (GDestroyNotify) grd_rdp_legacy_buffer_free, g_free); g_mutex_init (&buffer_pool->pool_mutex); } static void grd_rdp_buffer_pool_class_init (GrdRdpBufferPoolClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->dispose = grd_rdp_buffer_pool_dispose; object_class->finalize = grd_rdp_buffer_pool_finalize; }