text: /*****************************************************************************
   * display.c: "vout display" management
   *****************************************************************************
   * Copyright (C) 2009 Laurent Aimar
   *
   * Authors: Laurent Aimar <fenrir _AT_ videolan _DOT_ org>
   *
   * This program is free software; you can redistribute it and/or modify it
   * under the terms of the GNU Lesser General Public License as published by
   * the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
   *
   * You should have received a copy of the GNU Lesser General Public License
   * along with this program; if not, write to the Free Software Foundation,
   * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
   *****************************************************************************/
  
  /*****************************************************************************
   * Preamble
   *****************************************************************************/
  #ifdef HAVE_CONFIG_H
  # include "config.h"
  #endif
  #include <assert.h>
  #include <stdatomic.h>
  
  #include <vlc_common.h>
  #include <vlc_vout_display.h>
  #include <vlc_vout.h>
  #include <vlc_block.h>
  #include <vlc_modules.h>
  #include <vlc_filter.h>
  #include <vlc_picture_pool.h>
  #include <vlc_codec.h>
  
  #include <libvlc.h>
  
  #include "display.h"
  #include "window.h"
  
  /*****************************************************************************
   * FIXME/TODO see how to have direct rendering here (interact with vout.c)
   *****************************************************************************/
  static picture_t *VideoBufferNew(filter_t *filter)
  {
      vout_display_t *vd = filter->owner.sys;
      const video_format_t *fmt = &filter->fmt_out.video;
  
      assert(vd->fmt->i_chroma == fmt->i_chroma &&
             vd->fmt->i_width  == fmt->i_width  &&
             vd->fmt->i_height == fmt->i_height);
  
      picture_pool_t *pool = vout_GetPool(vd, 3);
      if (!pool)
          return NULL;
      return picture_pool_Get(pool);
  }
  
  static int vout_display_Control(vout_display_t *vd, int query)
  {
      return vd->ops->control(vd, query);
  }
  
  /*****************************************************************************
   *
   *****************************************************************************/
  
  /* */
  void vout_display_GetDefaultDisplaySize(unsigned *width, unsigned *height,
                                          const video_format_t *source,
                                          const vout_display_cfg_t *cfg)
  {
      /* Requested by the user */
      if (cfg->display.width != 0 && cfg->display.height != 0) {
          *width  = cfg->display.width;
          *height = cfg->display.height;
      } else if (cfg->display.width != 0) {
          *width  = cfg->display.width;
          *height = (int64_t)source->i_visible_height * source->i_sar_den * cfg->display.width * cfg->display.sar.num /
              source->i_visible_width / source->i_sar_num / cfg->display.sar.den;
      } else if (cfg->display.height != 0) {
          *width  = (int64_t)source->i_visible_width * source->i_sar_num * cfg->display.height * cfg->display.sar.den /
              source->i_visible_height / source->i_sar_den / cfg->display.sar.num;
          *height = cfg->display.height;
      }
      /* Size reported by the window module */
      else if (cfg->window_props.width != 0 && cfg->window_props.height != 0) {
          *width = cfg->window_props.width;
          *height = cfg->window_props.height;
      }
      /* Use the original video size */
      else if (source->i_sar_num >= source->i_sar_den) {
          *width  = (int64_t)source->i_visible_width * source->i_sar_num * cfg->display.sar.den / source->i_sar_den / cfg->display.sar.num;
          *height = source->i_visible_height;
      } else {
          *width  = source->i_visible_width;
          *height = (int64_t)source->i_visible_height * source->i_sar_den * cfg->display.sar.num / source->i_sar_num / cfg->display.sar.den;
      }
  
      *width  = *width  * cfg->zoom.num / cfg->zoom.den;
      *height = *height * cfg->zoom.num / cfg->zoom.den;
  
      if (ORIENT_IS_SWAP(source->orientation)) {
  
          unsigned store = *width;
          *width = *height;
          *height = store;
      }
  }
  
  /* */
  void vout_display_PlacePicture(vout_display_place_t *place,
                                 const video_format_t *source,
                                 const vout_display_cfg_t *cfg)
  {
      /* vout_display_PlacePicture() is called from vd plugins. They should not
       * care about the initial window properties. */
      assert(cfg->window_props.width == 0 && cfg->window_props.height == 0);
  
      /* */
      memset(place, 0, sizeof(*place));
      if (cfg->display.width == 0 || cfg->display.height == 0)
          return;
  
      /* */
      unsigned display_width;
      unsigned display_height;
  
      video_format_t source_rot;
      video_format_ApplyRotation(&source_rot, source);
      source = &source_rot;
  
      if (cfg->is_display_filled) {
          display_width  = cfg->display.width;
          display_height = cfg->display.height;
      } else {
          vout_display_cfg_t cfg_tmp = *cfg;
  
          cfg_tmp.display.width  = 0;
          cfg_tmp.display.height = 0;
          vout_display_GetDefaultDisplaySize(&display_width, &display_height,
                                             source, &cfg_tmp);
      }
  
      const unsigned width  = source->i_visible_width;
      const unsigned height = source->i_visible_height;
      /* Compute the height if we use the width to fill up display_width */
      const int64_t scaled_height = (int64_t)height * display_width  * cfg->display.sar.num * source->i_sar_den / (width  * source->i_sar_num * cfg->display.sar.den);
      /* And the same but switching width/height */
      const int64_t scaled_width  = (int64_t)width  * display_height * cfg->display.sar.den * source->i_sar_num / (height * source->i_sar_den * cfg->display.sar.num);
  
      if (source->projection_mode == PROJECTION_MODE_RECTANGULAR) {
          /* We keep the solution that avoid filling outside the display */
          if (scaled_width <= cfg->display.width) {
              place->width  = scaled_width;
              place->height = display_height;
          } else {
              place->width  = display_width;
              place->height = scaled_height;
          }
      } else {
          /* No need to preserve an aspect ratio for 360 video.
           * They can fill the display. */
          place->width  = display_width;
          place->height = display_height;
      }
  
      /*  Compute position */
      switch (cfg->align.horizontal) {
      case VLC_VIDEO_ALIGN_LEFT:
          place->x = 0;
          break;
      case VLC_VIDEO_ALIGN_RIGHT:
          place->x = cfg->display.width - place->width;
          break;
      default:
          place->x = ((int)cfg->display.width - (int)place->width) / 2;
          break;
      }
  
      switch (cfg->align.vertical) {
      case VLC_VIDEO_ALIGN_TOP:
          place->y = 0;
          break;
      case VLC_VIDEO_ALIGN_BOTTOM:
          place->y = cfg->display.height - place->height;
          break;
      default:
          place->y = ((int)cfg->display.height - (int)place->height) / 2;
          break;
      }
  }
  
  void vout_display_TranslateMouseState(vout_display_t *vd, vlc_mouse_t *video,
                                        const vlc_mouse_t *window)
  {
      vout_display_place_t place;
  
      /* Translate window coordinates to video coordinates */
      vout_display_PlacePicture(&place, vd->source, vd->cfg);
  
      if (place.width <= 0 || place.height <= 0) {
          memset(video, 0, sizeof (*video));
          return;
      }
  
      const int wx = window->i_x, wy = window->i_y;
      int x, y;
  
      switch (vd->source->orientation) {
          case ORIENT_TOP_LEFT:
              x = wx;
              y = wy;
              break;
          case ORIENT_TOP_RIGHT:
              x = place.width - wx;
              y = wy;
              break;
          case ORIENT_BOTTOM_LEFT:
              x = wx;
              y = place.height - wy;
              break;
          case ORIENT_BOTTOM_RIGHT:
              x = place.width - wx;
              y = place.height - wy;
              break;
          case ORIENT_LEFT_TOP:
              x = wy;
              y = wx;
              break;
          case ORIENT_LEFT_BOTTOM:
              x = wy;
              y = place.width - wx;
              break;
          case ORIENT_RIGHT_TOP:
              x = place.height - wy;
              y = wx;
              break;
          case ORIENT_RIGHT_BOTTOM:
              x = place.height - wy;
              y = place.width - wx;
              break;
          default:
              vlc_assert_unreachable();
      }
  
      video->i_x = vd->source->i_x_offset
          + (int64_t)(x - place.x) * vd->source->i_visible_width / place.width;
      video->i_y = vd->source->i_y_offset
          + (int64_t)(y - place.y) * vd->source->i_visible_height / place.height;
      video->i_pressed = window->i_pressed;
      video->b_double_click = window->b_double_click;
  }
  
  typedef struct {
      vout_display_t  display;
  
      /* */
      vout_display_cfg_t cfg;
  
      struct {
          int      left;
          int      top;
          int      right;
          int      bottom;
          unsigned num;
          unsigned den;
      } crop;
  
      /* */
      video_format_t source;
      video_format_t display_fmt;
      vlc_video_context *src_vctx;
       /* filters to convert the vout source to fmt, NULL means no conversion
        * can be done and nothing will be displayed */
      filter_chain_t *converters;
      picture_pool_t *pool;
  } vout_display_priv_t;
  
  static int vout_display_start(void *func, bool forced, va_list ap)
  {
      vout_display_open_cb activate = func;
      vout_display_priv_t *osys = va_arg(ap, vout_display_priv_t *);
      const vout_display_cfg_t *cfg = &osys->cfg;
      vout_display_t *vd = &osys->display;
      vlc_video_context *context = osys->src_vctx;
  
      /* Picture buffer does not have the concept of aspect ratio */
      video_format_Copy(&osys->display_fmt, vd->source);
      vd->obj.force = forced; /* TODO: pass to activate() instead? */
  
      int ret = activate(vd, cfg, &osys->display_fmt, context);
      if (ret != VLC_SUCCESS) {
          video_format_Clean(&osys->display_fmt);
          vlc_objres_clear(VLC_OBJECT(vd));
      }
      return ret;
  }
  
  static vlc_decoder_device * DisplayHoldDecoderDevice(vlc_object_t *o, void *sys)
  {
      VLC_UNUSED(o);
      vout_display_t *vd = sys;
      vout_display_priv_t *osys = container_of(vd, vout_display_priv_t, display);
      return osys->src_vctx ? vlc_video_context_HoldDevice(osys->src_vctx) : NULL;
  }
  
  static const struct filter_video_callbacks vout_display_filter_cbs = {
      VideoBufferNew, DisplayHoldDecoderDevice,
  };
  
  static int VoutDisplayCreateRender(vout_display_t *vd)
  {
      vout_display_priv_t *osys = container_of(vd, vout_display_priv_t, display);
      filter_owner_t owner = {
          .video = &vout_display_filter_cbs,
          .sys = vd,
      };
  
      osys->converters = filter_chain_NewVideo(vd, false, &owner);
      if (unlikely(osys->converters == NULL))
          return -1;
  
      video_format_t v_src = osys->source;
      v_src.i_sar_num = 0;
      v_src.i_sar_den = 0;
  
      video_format_t v_dst = *vd->fmt;
      v_dst.i_sar_num = 0;
      v_dst.i_sar_den = 0;
  
      video_format_t v_dst_cmp = v_dst;
      if ((v_src.i_chroma == VLC_CODEC_J420 && v_dst.i_chroma == VLC_CODEC_I420) ||
          (v_src.i_chroma == VLC_CODEC_J422 && v_dst.i_chroma == VLC_CODEC_I422) ||
          (v_src.i_chroma == VLC_CODEC_J440 && v_dst.i_chroma == VLC_CODEC_I440) ||
          (v_src.i_chroma == VLC_CODEC_J444 && v_dst.i_chroma == VLC_CODEC_I444))
          v_dst_cmp.i_chroma = v_src.i_chroma;
  
      const bool convert = memcmp(&v_src, &v_dst_cmp, sizeof(v_src)) != 0;
      if (!convert)
          return 0;
  
      msg_Dbg(vd, "A filter to adapt decoder %4.4s to display %4.4s is needed",
              (const char *)&v_src.i_chroma, (const char *)&v_dst.i_chroma);
  
      /* */
      es_format_t src;
      es_format_InitFromVideo(&src, &v_src);
  
      /* */
      int ret = -1;
  
      for (int i = 0; i < 1 + (v_dst_cmp.i_chroma != v_dst.i_chroma); i++) {
          es_format_t dst;
  
          es_format_InitFromVideo(&dst, i == 0 ? &v_dst : &v_dst_cmp);
  
          filter_chain_Reset(osys->converters, &src, osys->src_vctx, &dst);
          ret = filter_chain_AppendConverter(osys->converters, &dst);
          es_format_Clean(&dst);
          if (ret == 0)
              break;
      }
      es_format_Clean(&src);
  
      if (ret != 0) {
          msg_Err(vd, "Failed to adapt decoder format to display");
          filter_chain_Delete(osys->converters);
          osys->converters = NULL;
      }
      return ret;
  }
  
  static void VoutDisplayCropRatio(int *left, int *top, int *right, int *bottom,
                                   const video_format_t *source,
                                   unsigned num, unsigned den)
  {
      unsigned scaled_width  = (uint64_t)source->i_visible_height * num * source->i_sar_den / den / source->i_sar_num;
      unsigned scaled_height = (uint64_t)source->i_visible_width  * den * source->i_sar_num / num / source->i_sar_den;
  
      if (scaled_width < source->i_visible_width) {
          *left   = (source->i_visible_width - scaled_width) / 2;
          *top    = 0;
          *right  = *left + scaled_width;
          *bottom = *top  + source->i_visible_height;
      } else {
          *left   = 0;
          *top    = (source->i_visible_height - scaled_height) / 2;
          *right  = *left + source->i_visible_width;
          *bottom = *top  + scaled_height;
      }
  }
  
  /**
   * It retreives a picture pool from the display
   */
  picture_pool_t *vout_GetPool(vout_display_t *vd, unsigned count)
  {
      vout_display_priv_t *osys = container_of(vd, vout_display_priv_t, display);
  
      if (osys->pool == NULL)
          osys->pool = picture_pool_NewFromFormat(vd->fmt, count);
      return osys->pool;
  }
  
  bool vout_IsDisplayFiltered(vout_display_t *vd)
  {
      vout_display_priv_t *osys = container_of(vd, vout_display_priv_t, display);
  
      return osys->converters == NULL || !filter_chain_IsEmpty(osys->converters);
  }
  
  picture_t *vout_ConvertForDisplay(vout_display_t *vd, picture_t *picture)
  {
      vout_display_priv_t *osys = container_of(vd, vout_display_priv_t, display);
  
      if (osys->converters == NULL) {
          picture_Release(picture);
          return NULL;
      }
  
      return filter_chain_VideoFilter(osys->converters, picture);
  }
  
  picture_t *vout_display_Prepare(vout_display_t *vd, picture_t *picture,
                                  subpicture_t *subpic, vlc_tick_t date)
  {
      assert(subpic == NULL); /* TODO */
      picture = vout_ConvertForDisplay(vd, picture);
  
      if (picture != NULL && vd->ops->prepare != NULL)
          vd->ops->prepare(vd, picture, subpic, date);
      return picture;
  }
  
  void vout_FilterFlush(vout_display_t *vd)
  {
      vout_display_priv_t *osys = container_of(vd, vout_display_priv_t, display);
  
      if (osys->converters != NULL)
          filter_chain_VideoFlush(osys->converters);
  }
  
  static void vout_display_Reset(vout_display_t *vd)
  {
      vout_display_priv_t *osys = container_of(vd, vout_display_priv_t, display);
  
      if (osys->converters != NULL) {
          filter_chain_Delete(osys->converters);
          osys->converters = NULL;
      }
  
      if (osys->pool != NULL) {
          picture_pool_Release(osys->pool);
          osys->pool = NULL;
      }
  
      assert(vd->ops->reset_pictures);
      if (vd->ops->reset_pictures(vd, &osys->display_fmt) != VLC_SUCCESS
       || VoutDisplayCreateRender(vd))
          msg_Err(vd, "Failed to adjust render format");
  }
  
  static int vout_UpdateSourceCrop(vout_display_t *vd)
  {
      vout_display_priv_t *osys = container_of(vd, vout_display_priv_t, display);
      video_format_t fmt = osys->source;
      unsigned crop_num = osys->crop.num;
      unsigned crop_den = osys->crop.den;
  
      if (crop_num != 0 && crop_den != 0) {
          VoutDisplayCropRatio(&osys->crop.left,  &osys->crop.top,
                               &osys->crop.right, &osys->crop.bottom,
                               &osys->source, crop_num, crop_den);
      }
  
      const int right_max  = osys->source.i_x_offset
                             + osys->source.i_visible_width;
      const int bottom_max = osys->source.i_y_offset
                             + osys->source.i_visible_height;
      int left = VLC_CLIP((int)osys->source.i_x_offset + osys->crop.left,
                            0, right_max - 1);
      int top  = VLC_CLIP((int)osys->source.i_y_offset + osys->crop.top,
                          0, bottom_max - 1);
      int right, bottom;
  
      if (osys->crop.right <= 0)
          right = (int)(osys->source.i_x_offset + osys->source.i_visible_width) + osys->crop.right;
      else
          right = (int)osys->source.i_x_offset + osys->crop.right;
      right = VLC_CLIP(right, left + 1, right_max);
  
      if (osys->crop.bottom <= 0)
          bottom = (int)(osys->source.i_y_offset + osys->source.i_visible_height) + osys->crop.bottom;
      else
          bottom = (int)osys->source.i_y_offset + osys->crop.bottom;
      bottom = VLC_CLIP(bottom, top + 1, bottom_max);
  
      osys->source.i_x_offset       = left;
      osys->source.i_y_offset       = top;
      osys->source.i_visible_width  = right - left;
      osys->source.i_visible_height = bottom - top;
      video_format_Print(VLC_OBJECT(vd), "SOURCE ", &fmt);
      video_format_Print(VLC_OBJECT(vd), "CROPPED ", &osys->source);
  
      int ret = vout_display_Control(vd, VOUT_DISPLAY_CHANGE_SOURCE_CROP);
      osys->crop.left   = left - osys->source.i_x_offset;
      osys->crop.top    = top  - osys->source.i_y_offset;
      /* FIXME for right/bottom we should keep the 'type' border vs window */
      osys->crop.right  = right -
                          (osys->source.i_x_offset + osys->source.i_visible_width);
      osys->crop.bottom = bottom -
                          (osys->source.i_y_offset + osys->source.i_visible_height);
      osys->crop.num    = crop_num;
      osys->crop.den    = crop_den;
      return ret;
  }
  
  static int vout_SetSourceAspect(vout_display_t *vd,
                                  unsigned sar_num, unsigned sar_den)
  {
      vout_display_priv_t *osys = container_of(vd, vout_display_priv_t, display);
      int ret = 0;
  
      if (sar_num > 0 && sar_den > 0) {
          osys->source.i_sar_num = sar_num;
          osys->source.i_sar_den = sar_den;
      } else {
          osys->source.i_sar_num = osys->source.i_sar_num;
          osys->source.i_sar_den = osys->source.i_sar_den;
      }
  
      if (vout_display_Control(vd, VOUT_DISPLAY_CHANGE_SOURCE_ASPECT))
          ret = -1;
  
      /* If a crop ratio is requested, recompute the parameters */
      if (osys->crop.num != 0 && osys->crop.den != 0
       && vout_UpdateSourceCrop(vd))
          ret = -1;
  
      return ret;
  }
  
  void VoutFixFormatAR(video_format_t *fmt)
  {
      vlc_ureduce( &fmt->i_sar_num, &fmt->i_sar_den,
                   fmt->i_sar_num,  fmt->i_sar_den, 50000 );
      if (fmt->i_sar_num <= 0 || fmt->i_sar_den <= 0) {
          fmt->i_sar_num = 1;
          fmt->i_sar_den = 1;
      }
  }
  
  void vout_UpdateDisplaySourceProperties(vout_display_t *vd, const video_format_t *source)
  {
      vout_display_priv_t *osys = container_of(vd, vout_display_priv_t, display);
      int err1 = 0, err2 = 0;
  
      video_format_t fixed_src = *source;
      VoutFixFormatAR( &fixed_src );
      if (fixed_src.i_sar_num * osys->source.i_sar_den !=
          fixed_src.i_sar_den * osys->source.i_sar_num) {
  
          osys->source.i_sar_num = fixed_src.i_sar_num;
          osys->source.i_sar_den = fixed_src.i_sar_den;
  
          /* FIXME it will override any AR that the user would have forced */
          err1 = vout_SetSourceAspect(vd, osys->source.i_sar_num,
                                      osys->source.i_sar_den);
      }
      if (source->i_x_offset       != osys->source.i_x_offset ||
          source->i_y_offset       != osys->source.i_y_offset ||
          source->i_visible_width  != osys->source.i_visible_width ||
          source->i_visible_height != osys->source.i_visible_height) {
  
          video_format_CopyCrop(&osys->source, source);
  
          /* Force the vout to reapply the current user crop settings
           * over the new decoder crop settings. */
          err2 = vout_UpdateSourceCrop(vd);
      }
  
      if (err1 || err2)
          vout_display_Reset(vd);
  }
  
  void vout_display_SetSize(vout_display_t *vd, unsigned width, unsigned height)
  {
      vout_display_priv_t *osys = container_of(vd, vout_display_priv_t, display);
  
      osys->cfg.display.width  = width;
      osys->cfg.display.height = height;
      if (vout_display_Control(vd, VOUT_DISPLAY_CHANGE_DISPLAY_SIZE))
          vout_display_Reset(vd);
  }
  
  void vout_SetDisplayFilled(vout_display_t *vd, bool is_filled)
  {
      vout_display_priv_t *osys = container_of(vd, vout_display_priv_t, display);
  
      if (is_filled == osys->cfg.is_display_filled)
          return; /* nothing to do */
  
      osys->cfg.is_display_filled = is_filled;
      if (vout_display_Control(vd, VOUT_DISPLAY_CHANGE_DISPLAY_FILLED))
          vout_display_Reset(vd);
  }
  
  void vout_SetDisplayZoom(vout_display_t *vd, unsigned num, unsigned den)
  {
      vout_display_priv_t *osys = container_of(vd, vout_display_priv_t, display);
  
      if (!osys->cfg.is_display_filled
       && osys->cfg.zoom.num == num && osys->cfg.zoom.den == den)
          return; /* nothing to do */
  
      osys->cfg.zoom.num = num;
      osys->cfg.zoom.den = den;
      if (vout_display_Control(vd, VOUT_DISPLAY_CHANGE_ZOOM))
          vout_display_Reset(vd);
  }
  
  void vout_SetDisplayAspect(vout_display_t *vd, unsigned dar_num, unsigned dar_den)
  {
      vout_display_priv_t *osys = container_of(vd, vout_display_priv_t, display);
  
      unsigned sar_num, sar_den;
      if (dar_num > 0 && dar_den > 0) {
          sar_num = dar_num * osys->source.i_visible_height;
          sar_den = dar_den * osys->source.i_visible_width;
          vlc_ureduce(&sar_num, &sar_den, sar_num, sar_den, 0);
      } else {
          sar_num = 0;
          sar_den = 0;
      }
  
      if (vout_SetSourceAspect(vd, sar_num, sar_den))
          vout_display_Reset(vd);
  }
  
  void vout_SetDisplayCrop(vout_display_t *vd,
                           unsigned crop_num, unsigned crop_den,
                           unsigned left, unsigned top, int right, int bottom)
  {
      vout_display_priv_t *osys = container_of(vd, vout_display_priv_t, display);
  
      if (osys->crop.left  != (int)left  || osys->crop.top != (int)top ||
          osys->crop.right != right || osys->crop.bottom != bottom ||
          (crop_num != 0 && crop_den != 0 &&
           (crop_num != osys->crop.num || crop_den != osys->crop.den))) {
  
          osys->crop.left   = left;
          osys->crop.top    = top;
          osys->crop.right  = right;
          osys->crop.bottom = bottom;
          osys->crop.num    = crop_num;
          osys->crop.den    = crop_den;
  
          if (vout_UpdateSourceCrop(vd))
              vout_display_Reset(vd);
      }
  }
  
  void vout_SetDisplayViewpoint(vout_display_t *vd,
                                const vlc_viewpoint_t *p_viewpoint)
  {
      vout_display_priv_t *osys = container_of(vd, vout_display_priv_t, display);
  
      if (osys->cfg.viewpoint.yaw   != p_viewpoint->yaw ||
          osys->cfg.viewpoint.pitch != p_viewpoint->pitch ||
          osys->cfg.viewpoint.roll  != p_viewpoint->roll ||
          osys->cfg.viewpoint.fov   != p_viewpoint->fov) {
          vlc_viewpoint_t old_vp = osys->cfg.viewpoint;
  
          osys->cfg.viewpoint = *p_viewpoint;
  
          if (vd->ops->set_viewpoint)
          {
              if (vd->ops->set_viewpoint(vd, &osys->cfg.viewpoint)) {
                  msg_Err(vd, "Failed to change Viewpoint");
                  osys->cfg.viewpoint = old_vp;
              }
          }
      }
  }
  
  vout_display_t *vout_display_New(vlc_object_t *parent,
                                   const video_format_t *source,
                                   vlc_video_context *vctx,
                                   const vout_display_cfg_t *cfg,
                                   const char *module,
                                   const vout_display_owner_t *owner)
  {
      vout_display_priv_t *osys = vlc_custom_create(parent, sizeof (*osys),
                                                    "vout display");
      if (unlikely(osys == NULL))
          return NULL;
  
      unsigned display_width, display_height;
      vout_display_GetDefaultDisplaySize(&display_width, &display_height,
                                         source, cfg);
  
      osys->cfg = *cfg;
      /* The window size was used for the initial setup. Now it can be dropped in
       * favor of the calculated display size. */
      osys->cfg.display.width = display_width;
      osys->cfg.display.height = display_height;
      osys->cfg.window_props.width = osys->cfg.window_props.height = 0;
  
      osys->pool = NULL;
  
      video_format_Copy(&osys->source, source);
      osys->crop.left   = 0;
      osys->crop.top    = 0;
      osys->crop.right  = 0;
      osys->crop.bottom = 0;
      osys->crop.num = 0;
      osys->crop.den = 0;
  
      osys->src_vctx = vctx ? vlc_video_context_Hold( vctx ) : NULL;
  
      /* */
      vout_display_t *vd = &osys->display;
      vd->source = &osys->source;
      vd->fmt = &osys->display_fmt;
      vd->info = (vout_display_info_t){ };
      vd->cfg = &osys->cfg;
      vd->ops = NULL;
      vd->sys = NULL;
      if (owner)
          vd->owner = *owner;
  
      if (vlc_module_load(vd, "vout display", module, module && *module != '\0',
                          vout_display_start, osys) == NULL)
          goto error;
  
  #if defined(__OS2__)
      if ((var_GetBool(parent, "fullscreen")
        || var_GetBool(parent, "video-wallpaper"))
       && vout_display_Control(vd, VOUT_DISPLAY_CHANGE_FULLSCREEN) == VLC_SUCCESS)
          osys->cfg.is_fullscreen = true;
  
      if (var_InheritBool(parent, "video-on-top"))
          vout_display_Control(vd, VOUT_DISPLAY_CHANGE_WINDOW_STATE);
  #endif
  
      if (VoutDisplayCreateRender(vd)) {
          if (vd->ops->close != NULL)
              vd->ops->close(vd);
          vlc_objres_clear(VLC_OBJECT(vd));
          video_format_Clean(&osys->display_fmt);
          goto error;
      }
      return vd;
  error:
      video_format_Clean(&osys->source);
      vlc_object_delete(vd);
      return NULL;
  }
  
  void vout_display_Delete(vout_display_t *vd)
  {
      vout_display_priv_t *osys = container_of(vd, vout_display_priv_t, display);
  
      if (osys->src_vctx)
      {
          vlc_video_context_Release( osys->src_vctx );
          osys->src_vctx = NULL;
      }
  
      if (osys->converters != NULL)
          filter_chain_Delete(osys->converters);
  
      if (osys->pool != NULL)
          picture_pool_Release(osys->pool);
  
      if (vd->ops->close != NULL)
          vd->ops->close(vd);
      vlc_objres_clear(VLC_OBJECT(vd));
  
      video_format_Clean(&osys->source);
      video_format_Clean(&osys->display_fmt);
      vlc_object_delete(vd);
  }
  