// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef MEDIA_GPU_CHROMEOS_MAILBOX_VIDEO_FRAME_CONVERTER_H_
#define MEDIA_GPU_CHROMEOS_MAILBOX_VIDEO_FRAME_CONVERTER_H_

#include "base/callback_forward.h"
#include "base/containers/queue.h"
#include "base/containers/small_map.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "gpu/command_buffer/common/mailbox.h"
#include "media/gpu/chromeos/video_frame_converter.h"
#include "media/gpu/media_gpu_export.h"

namespace base {
class Location;
class SingleThreadTaskRunner;
}  // namespace base

namespace gpu {
class GpuChannel;
class CommandBufferStub;
}  // namespace gpu

namespace media {

class VideoFrame;

// This class is used for converting DMA-buf backed VideoFrames to mailbox-based
// VideoFrames. See ConvertFrame() for more details.
// After conversion, the mailbox VideoFrame will retain a reference of the
// VideoFrame passed to ConvertFrame().
class MEDIA_GPU_EXPORT MailboxVideoFrameConverter : public VideoFrameConverter {
 public:
  using UnwrapFrameCB =
      base::RepeatingCallback<VideoFrame*(const VideoFrame& wrapped_frame)>;
  using GetCommandBufferStubCB =
      base::RepeatingCallback<gpu::CommandBufferStub*()>;
  using GetGpuChannelCB =
      base::RepeatingCallback<base::WeakPtr<gpu::GpuChannel>()>;

  // Creates a MailboxVideoFrameConverter instance. The callers will send
  // wrapped VideoFrames to ConvertFrame(), |unwrap_frame_cb| is the callback
  // used to get the original, unwrapped, VideoFrame. |gpu_task_runner| is the
  // task runner of the GPU main thread. Returns nullptr if any argument is
  // invalid.
  static std::unique_ptr<VideoFrameConverter> Create(
      UnwrapFrameCB unwrap_frame_cb,
      scoped_refptr<base::SingleThreadTaskRunner> gpu_task_runner,
      GetCommandBufferStubCB get_stub_cb);

  // Enqueues |frame| to be converted to a gpu::Mailbox backed VideoFrame.
  // |frame| must wrap a DMA-buf backed VideoFrame that is retrieved via
  // |unwrap_frame_cb_|. The generated gpu::Mailbox-based VideoFrame is kept
  // alive until the original (i.e. the unwrapped) DMA-Buf based VideoFrame one
  // goes out of scope.
  void ConvertFrame(scoped_refptr<VideoFrame> frame) override;
  void AbortPendingFrames() override;
  bool HasPendingFrames() const override;

 private:
  friend class MailboxVideoFrameConverterTest;

  // Use VideoFrame::unique_id() as internal VideoFrame indexing.
  using UniqueID = decltype(std::declval<VideoFrame>().unique_id());

  // A self-cleaning SharedImage, with move-only semantics.
  class ScopedSharedImage;

  MailboxVideoFrameConverter(
      UnwrapFrameCB unwrap_frame_cb,
      scoped_refptr<base::SingleThreadTaskRunner> gpu_task_runner,
      GetGpuChannelCB get_gpu_channel_cb);
  // Destructor runs on the GPU main thread.
  ~MailboxVideoFrameConverter() override;
  void Destroy() override;
  void DestroyOnGPUThread();

  // TODO(crbug.com/998279): replace s/OnGPUThread/OnGPUTaskRunner/.
  bool InitializeOnGPUThread();

  // Wraps |mailbox| and |frame| into a new VideoFrame and sends it via
  // |output_cb_|.
  void WrapMailboxAndVideoFrameAndOutput(VideoFrame* origin_frame,
                                         scoped_refptr<VideoFrame> frame,
                                         const gpu::Mailbox& mailbox);

  // ConvertFrame() delegates to this method to GenerateSharedImageOnGPUThread()
  // or just UpdateSharedImageOnGPUThread(), then to jump back to
  // WrapMailboxAndVideoFrameAndOutput().
  void ConvertFrameOnGPUThread(VideoFrame* origin_frame,
                               scoped_refptr<VideoFrame> frame,
                               ScopedSharedImage* stored_shared_image);

  // Populates a ScopedSharedImage from a DMA-buf backed |video_frame|.
  // |video_frame| must be kept alive for the duration of this method. This
  // method runs on |gpu_task_runner_|. Returns true if the SharedImage could be
  // created successfully; false otherwise (and OnError() is called).
  bool GenerateSharedImageOnGPUThread(VideoFrame* video_frame,
                                      const gfx::Rect& destination_visible_rect,
                                      ScopedSharedImage* shared_image);

  // Registers the mapping between a DMA-buf VideoFrame and the SharedImage.
  // |origin_frame| must be kept alive for the duration of this method. After
  // this method returns, |scoped_shared_image| will be owned by |origin_frame|.
  // This guarantees that the SharedImage lives as long as the associated
  // DMA-buf even if MailboxVideoFrameConverter dies.
  void RegisterSharedImage(
      VideoFrame* origin_frame,
      std::unique_ptr<ScopedSharedImage> scoped_shared_image);
  // Unregisters the |origin_frame_id| and associated SharedImage.
  // |scoped_shared_image| is passed to guarantee that the SharedImage is alive
  // until after we delete the pointer from |shared_images_|.
  void UnregisterSharedImage(
      UniqueID origin_frame_id,
      std::unique_ptr<ScopedSharedImage> scoped_shared_image);

  // Updates the SharedImage associated to |mailbox|. Returns true if the update
  // could be carried out, false otherwise.
  bool UpdateSharedImageOnGPUThread(const gpu::Mailbox& mailbox);

  // Waits on |sync_token|, keeping |frame| alive until it is signalled. It
  // trampolines threads to |gpu_task_runner| if necessary.
  void WaitOnSyncTokenAndReleaseFrameOnGPUThread(
      scoped_refptr<VideoFrame> frame,
      const gpu::SyncToken& sync_token);

  // Invoked when any error occurs. |msg| is the error message.
  void OnError(const base::Location& location, const std::string& msg);

  // In DmabufVideoFramePool, we recycle the unused frames. To do that, each
  // time a frame is requested from the pool it is wrapped inside another frame.
  // A destruction callback is then added to this wrapped frame to automatically
  // return it to the pool upon destruction. Unfortunately this means that a new
  // frame is returned each time, and we need a way to uniquely identify the
  // underlying frame to avoid converting the same frame multiple times.
  // |unwrap_frame_cb_| is used to get the origin frame.
  UnwrapFrameCB unwrap_frame_cb_;

  const scoped_refptr<base::SingleThreadTaskRunner> gpu_task_runner_;
  const GetGpuChannelCB get_gpu_channel_cb_;

  // |gpu_channel_| will outlive CommandBufferStub, keep the former as a WeakPtr
  // to guarantee proper resource cleanup. To be dereferenced on
  // |gpu_task_runner_| only.
  base::WeakPtr<gpu::GpuChannel> gpu_channel_;

  // Mapping from the unique id of the frame to its corresponding SharedImage.
  // Accessed only on |parent_task_runner_|. The ScopedSharedImages are owned by
  // the unwrapped DMA-buf VideoFrames so that they can be used even after
  // MailboxVideoFrameConverter dies (e.g., there may still be compositing
  // commands that need the shared images).
  base::small_map<std::map<UniqueID, ScopedSharedImage*>> shared_images_;

  // The queue of input frames and the unique_id of their origin frame.
  // Accessed only on |parent_task_runner_|.
  // TODO(crbug.com/998279): remove this member entirely.
  base::queue<std::pair<scoped_refptr<VideoFrame>, UniqueID>>
      input_frame_queue_;

  // The weak pointer of this, bound to |parent_task_runner_|.
  // Used at the VideoFrame destruction callback.
  base::WeakPtr<MailboxVideoFrameConverter> parent_weak_this_;
  // The weak pointer of this, bound to |gpu_task_runner_|.
  // Used to generate SharedImages on the GPU main thread.
  base::WeakPtr<MailboxVideoFrameConverter> gpu_weak_this_;
  base::WeakPtrFactory<MailboxVideoFrameConverter> parent_weak_this_factory_{
      this};
  base::WeakPtrFactory<MailboxVideoFrameConverter> gpu_weak_this_factory_{this};

  DISALLOW_COPY_AND_ASSIGN(MailboxVideoFrameConverter);
};

}  // namespace media
#endif  // MEDIA_GPU_CHROMEOS_MAILBOX_VIDEO_FRAME_CONVERTER_H_
