/* GStreamer * Copyright (C) 2024 Seungha Yang * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "gstdwriterender_bitmap.h" #include "gstdwritebitmappool.h" #include "gstdwrite-renderer.h" #include GST_DEBUG_CATEGORY_EXTERN (dwrite_overlay_object_debug); #define GST_CAT_DEFAULT dwrite_overlay_object_debug /* *INDENT-OFF* */ using namespace Microsoft::WRL; struct GstDWriteBitmapRenderPrivate { ~GstDWriteBitmapRenderPrivate () { renderer = nullptr; dwrite_factory = nullptr; d2d_factory = nullptr; if (layout_pool) gst_buffer_pool_set_active (layout_pool, FALSE); gst_clear_object (&layout_pool); } ComPtr d2d_factory; ComPtr dwrite_factory; ComPtr renderer; GstBufferPool *layout_pool = nullptr; GstVideoInfo layout_info; GstVideoInfo info; }; /* *INDENT-ON* */ struct _GstDWriteBitmapRender { GstDWriteRender parent; GstDWriteBitmapRenderPrivate *priv; }; static void gst_dwrite_bitmap_render_finalize (GObject * object); static GstBuffer *gst_dwrite_bitmap_render_draw_layout (GstDWriteRender * render, IDWriteTextLayout * layout, gint x, gint y); static gboolean gst_dwrite_bitmap_render_blend (GstDWriteRender * render, GstBuffer * layout_buf, gint x, gint y, GstBuffer * output); static gboolean gst_dwrite_bitmap_render_update_device (GstDWriteRender * render, GstBuffer * buffer); static gboolean gst_dwrite_bitmap_render_handle_allocation_query (GstDWriteRender * render, GstElement * elem, GstQuery * query); static gboolean gst_dwrite_bitmap_render_can_inplace (GstDWriteRender * render, GstBuffer * buffer); #define gst_dwrite_bitmap_render_parent_class parent_class G_DEFINE_FINAL_TYPE (GstDWriteBitmapRender, gst_dwrite_bitmap_render, GST_TYPE_DWRITE_RENDER); static void gst_dwrite_bitmap_render_class_init (GstDWriteBitmapRenderClass * klass) { auto object_class = G_OBJECT_CLASS (klass); auto render_class = GST_DWRITE_RENDER_CLASS (klass); object_class->finalize = gst_dwrite_bitmap_render_finalize; render_class->draw_layout = gst_dwrite_bitmap_render_draw_layout; render_class->blend = gst_dwrite_bitmap_render_blend; render_class->update_device = gst_dwrite_bitmap_render_update_device; render_class->handle_allocation_query = gst_dwrite_bitmap_render_handle_allocation_query; render_class->can_inplace = gst_dwrite_bitmap_render_can_inplace; } static void gst_dwrite_bitmap_render_init (GstDWriteBitmapRender * self) { self->priv = new GstDWriteBitmapRenderPrivate (); } static void gst_dwrite_bitmap_render_finalize (GObject * object) { auto self = GST_DWRITE_BITMAP_RENDER (object); delete self->priv; G_OBJECT_CLASS (parent_class)->finalize (object); } static GstBufferPool * gst_dwrite_bitmap_render_create_pool (GstDWriteBitmapRender * self, const GstVideoInfo * info) { auto caps = gst_video_info_to_caps (info); if (!caps) { GST_ERROR_OBJECT (self, "Invalid info"); return nullptr; } auto pool = gst_dwrite_bitmap_pool_new (); auto config = gst_buffer_pool_get_config (pool); gst_buffer_pool_config_set_params (config, caps, info->size, 0, 0); gst_caps_unref (caps); if (!gst_buffer_pool_set_config (pool, config)) { GST_ERROR_OBJECT (self, "Couldn't set pool config"); gst_object_unref (pool); return nullptr; } if (!gst_buffer_pool_set_active (pool, TRUE)) { GST_ERROR_OBJECT (self, "Couldn't activate pool"); gst_object_unref (pool); return nullptr; } return pool; } static GstBuffer * gst_dwrite_bitmap_render_draw_layout (GstDWriteRender * render, IDWriteTextLayout * layout, gint x, gint y) { auto self = GST_DWRITE_BITMAP_RENDER (render); auto priv = self->priv; auto width = (gint) layout->GetMaxWidth (); auto height = (gint) layout->GetMaxHeight (); if (priv->layout_pool && (priv->layout_info.width != width || priv->layout_info.height != height)) { gst_buffer_pool_set_active (priv->layout_pool, FALSE); gst_clear_object (&priv->layout_pool); } if (!priv->layout_pool) { gst_video_info_set_format (&priv->layout_info, GST_VIDEO_FORMAT_BGRA, width, height); priv->layout_pool = gst_dwrite_bitmap_render_create_pool (self, &priv->layout_info); if (!priv->layout_pool) { GST_ERROR_OBJECT (self, "Couldn't create pool"); return nullptr; } } GstBuffer *layout_buf = nullptr; gst_buffer_pool_acquire_buffer (priv->layout_pool, &layout_buf, nullptr); if (!layout_buf) { GST_ERROR_OBJECT (self, "Couldn't acquire buffer"); return nullptr; } ComPtr < IDXGISurface > surface; auto bmem = (GstDWriteBitmapMemory *) gst_buffer_peek_memory (layout_buf, 0); static const D2D1_RENDER_TARGET_PROPERTIES props = { D2D1_RENDER_TARGET_TYPE_DEFAULT, DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED, 0, 0, D2D1_RENDER_TARGET_USAGE_NONE, D2D1_FEATURE_LEVEL_DEFAULT }; ComPtr < ID2D1RenderTarget > target; auto hr = priv->d2d_factory->CreateWicBitmapRenderTarget (bmem->bitmap, props, &target); if (FAILED (hr)) { GST_ERROR_OBJECT (self, "Couldn't create d2d render target"); gst_buffer_unref (layout_buf); return nullptr; } target->BeginDraw (); target->Clear (D2D1::ColorF (D2D1::ColorF::Black, 0.0)); priv->renderer->Draw (D2D1::Point2F (), D2D1::Rect (0, 0, width, height), layout, target.Get ()); target->EndDraw (); target = nullptr; return layout_buf; } static gboolean gst_dwrite_bitmap_render_blend (GstDWriteRender * render, GstBuffer * layout_buf, gint x, gint y, GstBuffer * output) { auto self = GST_DWRITE_BITMAP_RENDER (render); auto priv = self->priv; GstVideoFrame dst_frame, src_frame; gboolean ret; if (!gst_video_frame_map (&dst_frame, &priv->info, output, GST_MAP_WRITE)) { GST_ERROR_OBJECT (self, "Couldn't map output buffer"); return FALSE; } if (!gst_video_frame_map (&src_frame, &priv->layout_info, layout_buf, GST_MAP_READ)) { gst_video_frame_unmap (&dst_frame); GST_ERROR_OBJECT (self, "Couldn't map layout buffer"); return FALSE; } src_frame.info.flags = (GstVideoFlags) (src_frame.info.flags | GST_VIDEO_FLAG_PREMULTIPLIED_ALPHA); ret = gst_video_blend (&dst_frame, &src_frame, x, y, 1.0); gst_video_frame_unmap (&src_frame); gst_video_frame_unmap (&dst_frame); return ret; } static gboolean gst_dwrite_bitmap_render_update_device (GstDWriteRender * render, GstBuffer * buffer) { return FALSE; } static gboolean gst_dwrite_bitmap_render_handle_allocation_query (GstDWriteRender * render, GstElement * elem, GstQuery * query) { return TRUE; } static gboolean gst_dwrite_bitmap_render_can_inplace (GstDWriteRender * render, GstBuffer * buffer) { return TRUE; } GstDWriteRender * gst_dwrite_bitmap_render_new (const GstVideoInfo * info, ID2D1Factory * d2d_factory, IDWriteFactory * dwrite_factory) { auto self = (GstDWriteBitmapRender *) g_object_new (GST_TYPE_DWRITE_BITMAP_RENDER, nullptr); gst_object_ref_sink (self); auto priv = self->priv; priv->info = *info; priv->d2d_factory = d2d_factory; priv->dwrite_factory = dwrite_factory; IGstDWriteTextRenderer::CreateInstance (dwrite_factory, &priv->renderer); return GST_DWRITE_RENDER (self); }