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

#include "remoting/client/dual_buffer_frame_consumer.h"

#include <memory>

#include "base/check.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/location.h"
#include "base/task/single_thread_task_runner.h"

namespace remoting {

namespace {

// The implementation is mostly the same as webrtc::BasicDesktopFrame, except
// that it has an extra padding of one row at the end of the buffer. This is
// to workaround a bug in iOS' implementation of glTexSubimage2D that causes
// occasional SIGSEGV.
//
// glTexSubimage2D is supposed to only read
// kBytesPerPixel * width * (height - 1) bytes from the buffer but it seems to
// be reading more than that, which may end up reading protected memory.
//
// See details in crbug.com/778550
class PaddedDesktopFrame : public webrtc::DesktopFrame {
 public:
  explicit PaddedDesktopFrame(webrtc::DesktopSize size);

  PaddedDesktopFrame(const PaddedDesktopFrame&) = delete;
  PaddedDesktopFrame& operator=(const PaddedDesktopFrame&) = delete;

  ~PaddedDesktopFrame() override;

  // Creates a PaddedDesktopFrame that contains copy of |frame|.
  static std::unique_ptr<webrtc::DesktopFrame> CopyOf(
      const webrtc::DesktopFrame& frame);
};

PaddedDesktopFrame::PaddedDesktopFrame(webrtc::DesktopSize size)
    : DesktopFrame(
          size,
          kBytesPerPixel * size.width(),
          new uint8_t[kBytesPerPixel * size.width() * (size.height() + 1)],
          nullptr) {}

PaddedDesktopFrame::~PaddedDesktopFrame() {
  delete[] data_;
}

// static
std::unique_ptr<webrtc::DesktopFrame> PaddedDesktopFrame::CopyOf(
    const webrtc::DesktopFrame& frame) {
  std::unique_ptr<PaddedDesktopFrame> result =
      std::make_unique<PaddedDesktopFrame>(frame.size());
  for (int y = 0; y < frame.size().height(); ++y) {
    memcpy(result->data() + y * result->stride(),
           frame.data() + y * frame.stride(),
           frame.size().width() * kBytesPerPixel);
  }
  result->CopyFrameInfoFrom(frame);
  return result;
}

}  // namespace

DualBufferFrameConsumer::DualBufferFrameConsumer(
    RenderCallback callback,
    scoped_refptr<base::SingleThreadTaskRunner> task_runner,
    protocol::FrameConsumer::PixelFormat format)
    : callback_(std::move(callback)),
      task_runner_(task_runner),
      pixel_format_(format) {
  weak_ptr_ = weak_factory_.GetWeakPtr();
  thread_checker_.DetachFromThread();
}

DualBufferFrameConsumer::~DualBufferFrameConsumer() {
  DCHECK(thread_checker_.CalledOnValidThread());
}

void DualBufferFrameConsumer::RequestFullDesktopFrame() {
  DCHECK(thread_checker_.CalledOnValidThread());
  if (!buffers_[0]) {
    return;
  }
  DCHECK(buffers_[0]->size().equals(buffers_[1]->size()));
  // This creates a copy of buffers_[0] and merges area defined in
  // |buffer_1_mask_| from buffers_[1] into the copy.
  std::unique_ptr<webrtc::DesktopFrame> full_frame =
      PaddedDesktopFrame::CopyOf(*buffers_[0]);
  webrtc::DesktopRect desktop_rect =
        webrtc::DesktopRect::MakeSize(buffers_[0]->size());
  for (webrtc::DesktopRegion::Iterator i(buffer_1_mask_); !i.IsAtEnd();
       i.Advance()) {
    full_frame->CopyPixelsFrom(*buffers_[1], i.rect().top_left(),
                               i.rect());
  }
  full_frame->mutable_updated_region()->SetRect(desktop_rect);

  RunRenderCallback(std::move(full_frame), base::DoNothing());
}

std::unique_ptr<webrtc::DesktopFrame> DualBufferFrameConsumer::AllocateFrame(
    const webrtc::DesktopSize& size) {
  DCHECK(thread_checker_.CalledOnValidThread());

  // Both buffers are reallocated whenever screen size changes.
  if (!buffers_[0] || !buffers_[0]->size().equals(size)) {
    buffers_[0] = webrtc::SharedDesktopFrame::Wrap(
        std::make_unique<PaddedDesktopFrame>(size));
    buffers_[1] = webrtc::SharedDesktopFrame::Wrap(
        std::make_unique<PaddedDesktopFrame>(size));
    buffer_1_mask_.Clear();
    current_buffer_ = 0;
  } else {
    current_buffer_ = (current_buffer_ + 1) % 2;
  }
  return buffers_[current_buffer_]->Share();
}

void DualBufferFrameConsumer::DrawFrame(
    std::unique_ptr<webrtc::DesktopFrame> frame,
    base::OnceClosure done) {
  DCHECK(thread_checker_.CalledOnValidThread());
  webrtc::SharedDesktopFrame* shared_frame =
      reinterpret_cast<webrtc::SharedDesktopFrame*> (frame.get());
  if (shared_frame->GetUnderlyingFrame() == buffers_[1]->GetUnderlyingFrame()) {
    buffer_1_mask_.AddRegion(frame->updated_region());
  } else if (shared_frame->GetUnderlyingFrame() ==
      buffers_[0]->GetUnderlyingFrame()) {
    buffer_1_mask_.Subtract(frame->updated_region());
  }
  RunRenderCallback(std::move(frame), std::move(done));
}

protocol::FrameConsumer::PixelFormat
DualBufferFrameConsumer::GetPixelFormat() {
  return pixel_format_;
}

base::WeakPtr<DualBufferFrameConsumer> DualBufferFrameConsumer::GetWeakPtr() {
  return weak_ptr_;
}

void DualBufferFrameConsumer::RunRenderCallback(
    std::unique_ptr<webrtc::DesktopFrame> frame,
    base::OnceClosure done) {
  if (!task_runner_) {
    callback_.Run(std::move(frame), std::move(done));
    return;
  }

  task_runner_->PostTask(
      FROM_HERE,
      base::BindOnce(
          callback_, std::move(frame),
          base::BindOnce(base::IgnoreResult(&base::TaskRunner::PostTask),
                         base::SingleThreadTaskRunner::GetCurrentDefault(),
                         FROM_HERE, std::move(done))));
}

}  // namespace remoting