static void
gst_vaapi_surface_proxy_get_property(
    GObject    *object,
    guint       prop_id,
    GValue     *value,
    GParamSpec *pspec
)
{
    GstVaapiSurfaceProxy * const proxy = GST_VAAPI_SURFACE_PROXY(object);

    switch (prop_id) {
    case PROP_CONTEXT:
        g_value_set_pointer(value, gst_vaapi_surface_proxy_get_context(proxy));
        break;
    case PROP_SURFACE:
        g_value_set_pointer(value, gst_vaapi_surface_proxy_get_surface(proxy));
        break;
    case PROP_TIMESTAMP:
        g_value_set_uint64(value, gst_vaapi_surface_proxy_get_timestamp(proxy));
        break;
    default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
        break;
    }
}
static gboolean
renderer_process (App * app, RenderFrame * rfp)
{
  GError *error = NULL;
  GstVaapiSurface *surface;
  const GstVaapiRectangle *crop_rect;

#define SEND_ERROR(...)                                                 \
    do {                                                                \
        error = g_error_new(APP_ERROR, APP_ERROR_RENDERER, __VA_ARGS__); \
        goto send_error;                                                \
    } while (0)

  surface = gst_vaapi_surface_proxy_get_surface (rfp->proxy);
  if (!surface)
    SEND_ERROR ("failed to get decoded surface from render frame");

  ensure_window_size (app, surface);

  crop_rect = gst_vaapi_surface_proxy_get_crop_rect (rfp->proxy);
  if (g_use_pixmap && !ensure_pixmaps (app, surface, crop_rect))
    SEND_ERROR ("failed to create intermediate pixmaps");

  if (!gst_vaapi_surface_sync (surface))
    SEND_ERROR ("failed to sync decoded surface");

  if (G_LIKELY (!g_benchmark))
    renderer_wait_until (app, rfp->pts);

  if (G_UNLIKELY (g_use_pixmap)) {
    GstVaapiPixmap *const pixmap = app->pixmaps[app->pixmap_id];

    if (!gst_vaapi_pixmap_put_surface (pixmap, surface, crop_rect,
            GST_VAAPI_PICTURE_STRUCTURE_FRAME))
      SEND_ERROR ("failed to render to pixmap");

    if (!gst_vaapi_window_put_pixmap (app->window, pixmap, NULL, NULL))
      SEND_ERROR ("failed to render surface %" GST_VAAPI_ID_FORMAT,
          GST_VAAPI_ID_ARGS (pixmap));

    app->pixmap_id = (app->pixmap_id + 1) % G_N_ELEMENTS (app->pixmaps);
  } else if (!gst_vaapi_window_put_surface (app->window, surface,
          crop_rect, NULL, GST_VAAPI_PICTURE_STRUCTURE_FRAME))
    SEND_ERROR ("failed to render surface %" GST_VAAPI_ID_FORMAT,
        GST_VAAPI_ID_ARGS (gst_vaapi_surface_get_id (surface)));

  app->num_frames++;

  render_frame_replace (&app->last_frame, rfp);
  return TRUE;

#undef SEND_ERROR

send_error:
  app_send_error (app, error);
  return FALSE;
}
static gboolean
gst_vaapi_texture_upload (GstVideoGLTextureUploadMeta * meta,
    guint texture_id[4])
{
  GstVaapiVideoMeta *const vmeta =
      gst_buffer_get_vaapi_video_meta (meta->buffer);
  GstVaapiVideoMetaTexture *const meta_texture = meta->user_data;
  GstVaapiSurfaceProxy *const proxy =
      gst_vaapi_video_meta_get_surface_proxy (vmeta);
  GstVaapiSurface *const surface = gst_vaapi_surface_proxy_get_surface (proxy);
  GstVaapiDisplay *const dpy = GST_VAAPI_OBJECT_DISPLAY (surface);

  if (!gst_vaapi_display_has_opengl (dpy))
    return FALSE;

  if (!meta_texture->texture ||
      /* Check whether VA display changed */
      GST_VAAPI_OBJECT_DISPLAY (meta_texture->texture) != dpy ||
      /* Check whether texture id changed */
      gst_vaapi_texture_get_id (meta_texture->texture) != texture_id[0]) {
    /* FIXME: should we assume target? */
    GstVaapiTexture *const texture =
        gst_vaapi_texture_new_wrapped (dpy, texture_id[0],
        GL_TEXTURE_2D, meta_texture->gl_format, meta_texture->width,
        meta_texture->height);
    gst_vaapi_texture_replace (&meta_texture->texture, texture);
    if (!texture)
      return FALSE;
    gst_vaapi_texture_unref (texture);
  }

  gst_vaapi_texture_set_orientation_flags (meta_texture->texture,
      get_texture_orientation_flags (meta->texture_orientation));

  return gst_vaapi_texture_put_surface (meta_texture->texture, surface,
      gst_vaapi_surface_proxy_get_crop_rect (proxy),
      gst_vaapi_video_meta_get_render_flags (vmeta));
}
static int
app_run (App * app)
{
  GstVaapiImage *image;
  GstVaapiVideoPool *pool;
  GThread *buffer_thread;
  gsize id;
  int ret = EXIT_FAILURE;

  image = gst_vaapi_image_new (app->display, GST_VIDEO_FORMAT_I420,
      app->parser->width, app->parser->height);

  {
    GstVideoInfo vi;
    gst_video_info_set_format (&vi, GST_VIDEO_FORMAT_ENCODED,
        app->parser->width, app->parser->height);
    pool = gst_vaapi_surface_pool_new_full (app->display, &vi, 0);
  }

  buffer_thread = g_thread_new ("get buffer thread", get_buffer_thread, app);

  while (1) {
    GstVaapiSurfaceProxy *proxy;
    GstVaapiSurface *surface;

    if (!load_frame (app, image))
      break;

    if (!gst_vaapi_image_unmap (image))
      break;

    proxy =
        gst_vaapi_surface_proxy_new_from_pool (GST_VAAPI_SURFACE_POOL (pool));
    if (!proxy) {
      g_warning ("Could not get surface proxy from pool.");
      break;
    }
    surface = gst_vaapi_surface_proxy_get_surface (proxy);
    if (!surface) {
      g_warning ("Could not get surface from proxy.");
      break;
    }

    if (!gst_vaapi_surface_put_image (surface, image)) {
      g_warning ("Could not update surface");
      break;
    }

    if (!upload_frame (app->encoder, proxy)) {
      g_warning ("put frame failed");
      break;
    }

    app->read_frames++;
    id = gst_vaapi_surface_get_id (surface);
    g_debug ("input frame %d, surface id = %" G_GSIZE_FORMAT, app->read_frames,
        id);

    gst_vaapi_surface_proxy_unref (proxy);
  }

  app->input_stopped = TRUE;

  g_thread_join (buffer_thread);

  if (!app->encode_failed && feof (app->parser->fp))
    ret = EXIT_SUCCESS;

  gst_vaapi_video_pool_replace (&pool, NULL);
  gst_vaapi_object_unref (image);
  return ret;
}