Upload rendered overlays as textures and display with glimagesink.

Change-Id: Ia62ba8028185a6760564123e5ec1ea70add1aa2f
diff --git a/debian/control b/debian/control
index e1a6b9f..179d058 100644
--- a/debian/control
+++ b/debian/control
@@ -14,10 +14,12 @@
          gstreamer1.0-plugins-bad,
          gstreamer1.0-plugins-good,
          gstreamer1.0-plugins-ugly,
+         python3-cairo,
          python3-numpy,
          python3-protobuf,
          python3-pil,
          python3-gi,
+         python3-gi-cairo,
          python3-gst-1.0,
          python3-edgetpu,
          weston-imx
diff --git a/edgetpuvision/apps.py b/edgetpuvision/apps.py
index 3aa052c..aacfcfc 100644
--- a/edgetpuvision/apps.py
+++ b/edgetpuvision/apps.py
@@ -43,7 +43,7 @@
                         default='/dev/video0:YUY2:1280x720:30/1')
     parser.add_argument('--downscale', type=float, default=2.0,
                         help='Downscale factor for .mp4 file rendering')
-    parser.add_argument('--display', type=Display, choices=Display, default=Display.FULLSCREEN,
+    parser.add_argument('--displaymode', type=Display, choices=Display, default=Display.FULLSCREEN,
                         help='Display mode')
     add_render_gen_args(parser)
     args = parser.parse_args()
@@ -51,5 +51,5 @@
     if not run_gen(render_gen(args),
                    source=args.source,
                    downscale=args.downscale,
-                   display=args.display):
+                   display=args.displaymode):
         print('Invalid source argument:', args.source)
diff --git a/edgetpuvision/camera.py b/edgetpuvision/camera.py
index 4b1c914..a5de10f 100644
--- a/edgetpuvision/camera.py
+++ b/edgetpuvision/camera.py
@@ -10,7 +10,6 @@
     def __init__(self, render_size, inference_size):
         self._layout = gstreamer.make_layout(inference_size, render_size)
 
-        self._loop = gstreamer.loop()
         self._thread = None
         self.render_overlay = None
 
@@ -36,12 +35,13 @@
 
         pipeline = self.make_pipeline(format, profile, inline_headers, bitrate, intra_period)
 
-        self._thread = threading.Thread(target=gstreamer.run_loop,
-                                        args=(self._loop, pipeline, self._layout, render_overlay, signals))
+        self._thread = threading.Thread(target=gstreamer.run_pipeline,
+                                        args=(pipeline, self._layout, render_overlay,
+                                              gstreamer.Display.NONE, signals))
         self._thread.start()
 
     def stop_recording(self):
-        self._loop.quit()
+        gstreamer.quit()
         self._thread.join()
 
     def make_pipeline(self, fmt, profile, inline_headers, bitrate, intra_period):
diff --git a/edgetpuvision/gst_native.py b/edgetpuvision/gst_native.py
new file mode 100644
index 0000000..0e9dab3
--- /dev/null
+++ b/edgetpuvision/gst_native.py
@@ -0,0 +1,211 @@
+import cairo
+import contextlib
+import ctypes
+
+import gi
+gi.require_version('Gdk', '3.0')
+gi.require_version('GObject', '2.0')
+gi.require_version('Gst', '1.0')
+gi.require_version('GstBase', '1.0')
+gi.require_version('GstVideo', '1.0')
+from gi.repository import Gdk, GObject, Gst, GstBase, GstVideo
+
+Gdk.init([])
+
+# Gst.Buffer.map(Gst.MapFlags.WRITE) is broken, this is a workaround. See
+# http://lifestyletransfer.com/how-to-make-gstreamer-buffer-writable-in-python/
+# https://gitlab.gnome.org/GNOME/gobject-introspection/issues/69
+class GstMapInfo(ctypes.Structure):
+    _fields_ = [('memory', ctypes.c_void_p),                # GstMemory *memory
+                ('flags', ctypes.c_int),                    # GstMapFlags flags
+                ('data', ctypes.POINTER(ctypes.c_byte)),    # guint8 *data
+                ('size', ctypes.c_size_t),                  # gsize size
+                ('maxsize', ctypes.c_size_t),               # gsize maxsize
+                ('user_data', ctypes.c_void_p * 4),         # gpointer user_data[4]
+                ('_gst_reserved', ctypes.c_void_p * 4)]     # GST_PADDING
+
+# ctypes imports for missing or broken introspection APIs.
+libgst = ctypes.CDLL('libgstreamer-1.0.so.0')
+libgst.gst_context_writable_structure.restype = ctypes.c_void_p
+libgst.gst_context_writable_structure.argtypes = [ctypes.c_void_p]
+libgst.gst_structure_set.restype = ctypes.c_void_p
+libgst.gst_structure_set.argtypes = [ctypes.c_void_p, ctypes.c_char_p,
+        ctypes.c_int, ctypes.c_void_p, ctypes.c_void_p]
+GST_MAP_INFO_POINTER = ctypes.POINTER(GstMapInfo)
+libgst.gst_buffer_map.argtypes = [ctypes.c_void_p, GST_MAP_INFO_POINTER, ctypes.c_int]
+libgst.gst_buffer_map.restype = ctypes.c_int
+libgst.gst_buffer_unmap.argtypes = [ctypes.c_void_p, GST_MAP_INFO_POINTER]
+libgst.gst_buffer_unmap.restype = None
+libgst.gst_mini_object_is_writable.argtypes = [ctypes.c_void_p]
+libgst.gst_mini_object_is_writable.restype = ctypes.c_int
+
+libgdk = ctypes.CDLL('libgdk-3.so.0')
+libgdk.gdk_wayland_window_get_wl_surface.restype = ctypes.c_void_p
+libgdk.gdk_wayland_window_get_wl_surface.argtypes = [ctypes.c_void_p]
+libgdk.gdk_wayland_display_get_wl_display.restype = ctypes.c_void_p
+libgdk.gdk_wayland_display_get_wl_display.argtypes = [ctypes.c_void_p]
+
+libcairo = ctypes.CDLL('libcairo.so.2')
+libcairo.cairo_image_surface_create_for_data.restype = ctypes.c_void_p
+libcairo.cairo_image_surface_create_for_data.argtypes = [ctypes.c_void_p,
+        ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_int]
+libcairo.cairo_surface_flush.restype = None
+libcairo.cairo_surface_flush.argtypes = [ctypes.c_void_p]
+libcairo.cairo_surface_destroy.restype = None
+libcairo.cairo_surface_destroy.argtypes = [ctypes.c_void_p]
+libcairo.cairo_surface_status.restype = ctypes.c_int
+libcairo.cairo_surface_status.argtypes = [ctypes.c_void_p]
+libcairo.cairo_format_stride_for_width.restype = ctypes.c_int
+libcairo.cairo_format_stride_for_width.argtypes = [ctypes.c_int, ctypes.c_int]
+libcairo.cairo_create.restype = ctypes.c_void_p
+libcairo.cairo_create.argtypes = [ctypes.c_void_p]
+libcairo.cairo_destroy.restype = None
+libcairo.cairo_destroy.argtypes = [ctypes.c_void_p]
+libcairo.cairo_scale.restype = None
+libcairo.cairo_scale.argtypes = [ctypes.c_void_p, ctypes.c_double, ctypes.c_double]
+
+librsvg = ctypes.CDLL('librsvg-2.so.2')
+librsvg.rsvg_handle_new_from_data.restype = ctypes.c_void_p
+librsvg.rsvg_handle_new_from_data.argtypes = [ctypes.c_char_p, ctypes.c_size_t, ctypes.c_void_p]
+librsvg.rsvg_handle_render_cairo.restype = ctypes.c_bool
+librsvg.rsvg_handle_render_cairo.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
+librsvg.rsvg_handle_close.restype = ctypes.c_bool
+librsvg.rsvg_handle_close.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
+
+libgobject = ctypes.CDLL('libgobject-2.0.so')
+libgobject.g_object_unref.restype = None
+libgobject.g_object_unref.argtypes = [ctypes.c_void_p]
+
+def set_display_contexts(sink, widget):
+    handle = libgdk.gdk_wayland_window_get_wl_surface(hash(widget.get_window()))
+    sink.set_window_handle(handle)
+
+    wl_display = libgdk.gdk_wayland_display_get_wl_display(hash(Gdk.Display.get_default()))
+    context = Gst.Context.new('GstWaylandDisplayHandleContextType', True)
+    structure = libgst.gst_context_writable_structure(hash(context))
+    libgst.gst_structure_set(structure, ctypes.c_char_p('display'.encode()),
+            hash(GObject.TYPE_POINTER), wl_display, 0)
+    sink.set_context(context)
+
+@contextlib.contextmanager
+def _gst_buffer_map(buffer, flags):
+    ptr = hash(buffer)
+    if flags & Gst.MapFlags.WRITE and libgst.gst_mini_object_is_writable(ptr) == 0:
+        raise ValueError('Buffer not writable')
+
+    mapping = GstMapInfo()
+    success = libgst.gst_buffer_map(ptr, mapping, flags)
+    if not success:
+        raise RuntimeError('gst_buffer_map failed')
+    try:
+        yield ctypes.cast(mapping.data, ctypes.POINTER(ctypes.c_byte * mapping.size)).contents
+    finally:
+        libgst.gst_buffer_unmap(ptr, mapping)
+
+# GStreamer Element that attaches VideoOverlayComposition to buffers passing by.
+class OverlayInjector(GstBase.BaseTransform):
+    __gstmetadata__ = ('<longname>', '<class>', '<description>', '<author>')
+    __gsttemplates__ = (Gst.PadTemplate.new('src',
+                                               Gst.PadDirection.SRC,
+                                               Gst.PadPresence.ALWAYS,
+                                               Gst.Caps.new_any()),
+                        Gst.PadTemplate.new('sink',
+                                               Gst.PadDirection.SINK,
+                                               Gst.PadPresence.ALWAYS,
+                                               Gst.Caps.new_any()))
+
+    @staticmethod
+    def _plugin_init(plugin):
+        gtype = GObject.type_register(OverlayInjector)
+        Gst.Element.register(plugin, 'overlayinjector', 0, gtype)
+        return True
+
+    @staticmethod
+    def plugin_register():
+        version = Gst.version()
+        Gst.Plugin.register_static(
+            version[0], version[1],         # GStreamer version
+            '',                             # name
+            '',                             # description
+            OverlayInjector._plugin_init,   # init_func
+            '',                             # version
+            'unknown',                      # license
+            '',                             # source
+            '',                             # package
+            ''                              # origin
+        )
+
+    def __init__(self):
+        GstBase.BaseTransform.__init__(self)
+        GstBase.BaseTransform.set_in_place(self, True)
+        self.render_size = None
+        self.svg = None
+        self.rendered_svg = None
+        self.composition = None
+        self.scale_factor = 0.75
+
+    def set_svg(self, svg, render_size):
+        self.svg = svg
+        self.render_size = render_size
+
+    def do_transform_ip(self, frame_buf):
+        self.render()
+        if self.composition:
+            # Note: Buffer IS writable (ref is 1 in native land). However gst-python
+            # took an additional ref so it's now 2 and gst_buffer_is_writable
+            # returns false. We can't modify the buffer without fiddling with refcount.
+            if frame_buf.mini_object.refcount != 2:
+                return Gst.FlowReturn.ERROR
+            frame_buf.mini_object.refcount -= 1
+            GstVideo.buffer_add_video_overlay_composition_meta(frame_buf, self.composition)
+            frame_buf.mini_object.refcount += 1
+        return Gst.FlowReturn.OK
+
+
+    def render(self):
+        if not self.svg:
+            self.composition = None
+            self.rendered_svg = None
+            return
+
+        if self.svg == self.rendered_svg:
+            return
+
+        overlay_size = self.render_size * self.scale_factor
+        stride = libcairo.cairo_format_stride_for_width(
+                int(cairo.FORMAT_ARGB32), overlay_size.width)
+        overlay_buffer = Gst.Buffer.new_allocate(None,
+                stride * overlay_size.height)
+        with _gst_buffer_map(overlay_buffer, Gst.MapFlags.WRITE) as mapped:
+            # Fill with transparency and create surface from buffer.
+            ctypes.memset(ctypes.addressof(mapped), 0, ctypes.sizeof(mapped))
+            surface = libcairo.cairo_image_surface_create_for_data(
+                    ctypes.addressof(mapped),
+                    int(cairo.FORMAT_ARGB32),
+                    overlay_size.width,
+                    overlay_size.height,
+                    stride)
+
+            # Render the SVG overlay.
+            data = self.svg.encode('utf-8')
+            context = libcairo.cairo_create(surface)
+            libcairo.cairo_scale(context, self.scale_factor, self.scale_factor)
+            handle = librsvg.rsvg_handle_new_from_data(data, len(data), 0)
+            librsvg.rsvg_handle_render_cairo(handle, context)
+            librsvg.rsvg_handle_close(handle, 0)
+            libgobject.g_object_unref(handle)
+            libcairo.cairo_surface_flush(surface)
+            libcairo.cairo_surface_destroy(surface)
+            libcairo.cairo_destroy(context)
+
+            # Attach overlay to VideoOverlayComposition.
+            GstVideo.buffer_add_video_meta(overlay_buffer,
+                    GstVideo.VideoFrameFlags.NONE, GstVideo.VideoFormat.BGRA,
+                    overlay_size.width, overlay_size.height)
+            rect = GstVideo.VideoOverlayRectangle.new_raw(overlay_buffer,
+                    0, 0, self.render_size.width, self.render_size.height,
+                    GstVideo.VideoOverlayFormatFlags.PREMULTIPLIED_ALPHA)
+            self.composition = GstVideo.VideoOverlayComposition.new(rect)
+            self.rendered_svg = self.svg
+
+OverlayInjector.plugin_register()
diff --git a/edgetpuvision/gstreamer.py b/edgetpuvision/gstreamer.py
index cc561ca..64adcba 100644
--- a/edgetpuvision/gstreamer.py
+++ b/edgetpuvision/gstreamer.py
@@ -6,6 +6,7 @@
 import os
 import pathlib
 import queue
+import signal
 import sys
 import termios
 import threading
@@ -14,22 +15,23 @@
 import numpy as np
 
 import gi
+gi.require_version('Gtk', '3.0')
 gi.require_version('GLib', '2.0')
 gi.require_version('GObject', '2.0')
 gi.require_version('Gst', '1.0')
 gi.require_version('GstBase', '1.0')
 gi.require_version('GstPbutils', '1.0')
-
-from gi.repository import GLib, GObject, Gst, GstBase
+from gi.repository import GLib, GObject, Gst, GstBase, Gtk
 
 GObject.threads_init()
-Gst.init(None)
+Gst.init([])
+Gtk.init([])
 
 from gi.repository import GstPbutils  # Must be called after Gst.init().
 
 from PIL import Image
 
-from .gst import *
+from .gst_native import set_display_contexts
 from .pipelines import *
 
 COMMAND_SAVE_FRAME = ' '
@@ -121,9 +123,6 @@
     assert len(streams) == 1
     return streams[0]
 
-def loop():
-    return GLib.MainLoop()
-
 @contextlib.contextmanager
 def pull_sample(sink):
     sample = sink.emit('pull-sample')
@@ -135,61 +134,29 @@
     buf.unmap(mapinfo)
 
 def new_sample_callback(process):
-    def callback(sink, pipeline, loop):
+    def callback(sink, pipeline):
         with pull_sample(sink) as (sample, data):
             process(data, caps_size(sample.get_caps()))
         return Gst.FlowReturn.OK
     return callback
 
-def on_bus_message(bus, message, loop):
+def on_bus_message(bus, message):
     if message.type == Gst.MessageType.EOS:
-        loop.quit()
+        Gtk.main_quit()
     elif message.type == Gst.MessageType.WARNING:
         err, debug = message.parse_warning()
         sys.stderr.write('Warning: %s: %s\n' % (err, debug))
     elif message.type == Gst.MessageType.ERROR:
         err, debug = message.parse_error()
         sys.stderr.write('Error: %s: %s\n' % (err, debug))
-        loop.quit()
-    return True
-
-def run_pipeline(loop, pipeline, signals):
-    # Create pipeline
-    pipeline = describe(pipeline)
-    print(pipeline)
-    pipeline = Gst.parse_launch(pipeline)
-
-    # Attach signals
-    for name, signals in signals.items():
-        component = pipeline.get_by_name(name)
-        if component:
-            for signal_name, signal_handler in signals.items():
-                component.connect(signal_name, signal_handler, pipeline, loop)
-
-    # Set up a pipeline bus watch to catch errors.
-    bus = pipeline.get_bus()
-    bus.add_signal_watch()
-    bus.connect('message', on_bus_message, loop)
-
-    # Run pipeline.
-    pipeline.set_state(Gst.State.PLAYING)
-    try:
-        loop.run()
-    except KeyboardInterrupt:
-        pass
-    finally:
-        pipeline.set_state(Gst.State.NULL)
-
-    # Process all pending operations on the loop.
-    while loop.get_context().iteration(False):
-        pass
+        Gtk.main_quit()
 
 def on_keypress(fd, flags, commands):
     for ch in sys.stdin.read():
         commands.put(ch)
     return True
 
-def on_new_sample(sink, pipeline, loop, render_overlay, layout, images, commands):
+def on_new_sample(sink, pipeline, render_overlay, layout, images, commands):
     with pull_sample(sink) as (sample, data):
         custom_command = None
         save_frame = False
@@ -202,7 +169,7 @@
             print('Render size: %d x %d' % layout.render_size)
             print('Inference size: %d x %d' % layout.inference_size)
         elif  command == COMMAND_QUIT:
-            loop.quit()
+            Gtk.main_quit()
         else:
             custom_command = command
 
@@ -210,7 +177,7 @@
                              command=custom_command)
         overlay = pipeline.get_by_name('overlay')
         if overlay:
-            overlay.set_property('data', svg)
+            overlay.set_svg(svg, layout.render_size)
 
         if save_frame:
             images.put((data, layout.inference_size, svg))
@@ -230,7 +197,7 @@
     result = get_pipeline(source, inference_size, downscale, display)
     if result:
         layout, pipeline = result
-        run_loop(loop(), pipeline, layout, render_overlay)
+        run_pipeline(pipeline, layout, render_overlay, display)
         return True
 
     return False
@@ -254,7 +221,7 @@
     if display is Display.NONE:
         return camera_headless_pipeline(fmt, layout)
     else:
-        return camera_display_pipeline(fmt, layout, display is Display.FULLSCREEN)
+        return camera_display_pipeline(fmt, layout)
 
 def file_pipline(is_image, filename, layout, display):
     if display is Display.NONE:
@@ -265,14 +232,55 @@
     else:
         fullscreen = display is Display.FULLSCREEN
         if is_image:
-            return image_display_pipeline(filename, layout, fullscreen)
+            return image_display_pipeline(filename, layout)
         else:
-            return video_display_pipeline(filename, layout, fullscreen)
+            return video_display_pipeline(filename, layout)
 
-def run_loop(loop, pipeline, layout, render_overlay, signals=None):
+def quit():
+    Gtk.main_quit()
+
+def run_pipeline(pipeline, layout, render_overlay, display, signals=None):
     signals = signals or {}
     commands = queue.Queue()
 
+    # Create pipeline
+    pipeline = describe(pipeline)
+    print(pipeline)
+    pipeline = Gst.parse_launch(pipeline)
+
+    if display is not Display.NONE:
+        # Workaround for https://gitlab.gnome.org/GNOME/gtk/issues/844 in gtk3 < 3.24.
+        widget_draws = 123
+        def on_widget_draw(widget, cairo):
+            nonlocal widget_draws
+            if widget_draws:
+                 widget.queue_draw()
+                 widget_draws -= 1
+            return False
+
+        # Needed to account for window chrome etc.
+        def on_widget_configure(widget, event, glsink):
+            allocation = widget.get_allocation()
+            glsink.set_render_rectangle(allocation.x, allocation.y,
+                    allocation.width, allocation.height)
+            return False
+
+        window = Gtk.Window(Gtk.WindowType.TOPLEVEL)
+        window.set_default_size(layout.render_size.width, layout.render_size.height)
+        if display is Display.FULLSCREEN:
+            window.fullscreen()
+
+        drawing_area = Gtk.DrawingArea()
+        window.add(drawing_area)
+        drawing_area.realize()
+
+        glsink = pipeline.get_by_name('glsink')
+        set_display_contexts(glsink, drawing_area)
+        drawing_area.connect('draw', on_widget_draw)
+        drawing_area.connect('configure-event', on_widget_configure, glsink)
+        window.connect('delete-event', Gtk.main_quit)
+        window.show_all()
+
     with contextlib.ExitStack() as stack:
         images = stack.enter_context(Worker(save_frame))
 
@@ -281,11 +289,36 @@
             GLib.io_add_watch(sys.stdin.fileno(), GLib.IO_IN, on_keypress, commands)
             stack.enter_context(term_raw_mode(sys.stdin.fileno()))
 
-        run_pipeline(loop, pipeline, {'appsink':
+        signals = {'appsink':
             {'new-sample': functools.partial(on_new_sample,
                 render_overlay=functools.partial(render_overlay, layout=layout),
                 layout=layout,
                 images=images,
                 commands=commands)},
             **signals
-        })
+        }
+
+        for name, signals in signals.items():
+            component = pipeline.get_by_name(name)
+            if component:
+                for signal_name, signal_handler in signals.items():
+                    component.connect(signal_name, signal_handler, pipeline)
+
+        # Set up a pipeline bus watch to catch errors.
+        bus = pipeline.get_bus()
+        bus.add_signal_watch()
+        bus.connect('message', on_bus_message)
+
+        # Run pipeline.
+        GLib.unix_signal_add(GLib.PRIORITY_DEFAULT, signal.SIGINT, Gtk.main_quit)
+        pipeline.set_state(Gst.State.PLAYING)
+        try:
+            Gtk.main()
+        except KeyboardInterrupt:
+            pass
+        finally:
+            pipeline.set_state(Gst.State.NULL)
+
+        # Process all pending MainContext operations.
+        while GLib.MainContext.default().iteration(False):
+            pass
diff --git a/edgetpuvision/pipelines.py b/edgetpuvision/pipelines.py
index 3596ca6..d79bf2b 100644
--- a/edgetpuvision/pipelines.py
+++ b/edgetpuvision/pipelines.py
@@ -13,14 +13,25 @@
              framerate='%d/%d' % fmt.framerate),
     ]
 
-def display_sink(fullscreen, sync=False):
-    return Sink('kms' if fullscreen else 'wayland', sync=sync),
+def display_sink(sync=False):
+    return Sink('glimage', sync=sync, name='glsink'),
 
 def h264_sink():
     return Sink('app', name='h264sink', emit_signals=True, max_buffers=1, drop=False, sync=False)
 
-def inference_pipeline(layout):
+def inference_pipeline(layout, stillimage=False):
     size = max_inner_size(layout.render_size, layout.inference_size)
+    if stillimage:
+        return [
+            Filter('videoconvert'),
+            Filter('videoscale'),
+            Caps('video/x-raw', format='RGB', width=size.width, height=size.height),
+            Filter('videobox', autocrop=True),
+            Caps('video/x-raw', width=layout.inference_size.width, height=layout.inference_size.height),
+            Filter('imagefreeze'),
+            Sink('app', name='appsink', emit_signals=True, max_buffers=1, drop=True, sync=False),
+        ]
+
     return [
         Filter('glfilterbin', filter='glcolorscale'),
         Caps('video/x-raw', format='RGBA', width=size.width, height=size.height),
@@ -32,26 +43,24 @@
     ]
 
 # Display
-def image_display_pipeline(filename, layout, fullscreen):
+def image_display_pipeline(filename, layout):
     return (
         [decoded_file_src(filename),
          Tee(name='t')],
         [Pad('t'),
          Queue(),
-         Filter('imagefreeze'),
          Filter('videoconvert'),
          Filter('videoscale'),
-         Caps('video/x-raw', width=layout.render_size.width, height=layout.render_size.height),
-         Filter('rsvgoverlay', name='overlay'),
-         display_sink(fullscreen)],
+         Caps('video/x-raw', format='RGBA', width=layout.render_size.width, height=layout.render_size.height),
+         Filter('imagefreeze'),
+         Filter('overlayinjector', name='overlay'),
+         display_sink()],
         [Pad('t'),
          Queue(),
-         Filter('imagefreeze'),
-         Filter('glupload'),
-         inference_pipeline(layout)],
+         inference_pipeline(layout, stillimage=True)],
     )
 
-def video_display_pipeline(filename, layout, fullscreen):
+def video_display_pipeline(filename, layout,):
     return (
         [decoded_file_src(filename),
          Filter('glupload'),
@@ -59,15 +68,15 @@
         [Pad('t'),
          Queue(max_size_buffers=1),
          Filter('glfilterbin', filter='glcolorscale'),
-         Filter('rsvgoverlay', name='overlay'),
+         Filter('overlayinjector', name='overlay'),
          Caps('video/x-raw', width=layout.render_size.width, height=layout.render_size.height),
-         display_sink(fullscreen)],
+         display_sink()],
         [Pad('t'),
          Queue(max_size_buffers=1, leaky='downstream'),
          inference_pipeline(layout)],
     )
 
-def camera_display_pipeline(fmt, layout, fullscreen):
+def camera_display_pipeline(fmt, layout):
     return (
         [v4l2_src(fmt),
          Filter('glupload'),
@@ -75,8 +84,8 @@
         [Pad(name='t'),
          Queue(max_size_buffers=1, leaky='downstream'),
          Filter('glfilterbin', filter='glcolorscale'),
-         Filter('rsvgoverlay', name='overlay'),
-         display_sink(fullscreen)],
+         Filter('overlayinjector', name='overlay'),
+         display_sink()],
         [Pad(name='t'),
          Queue(max_size_buffers=1, leaky='downstream'),
          inference_pipeline(layout)],
@@ -142,4 +151,4 @@
         [Pad('t'),
          Queue(),
          inference_pipeline(layout)],
-    )
\ No newline at end of file
+    )