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

#include "components/viz/service/display_embedder/buffer_queue.h"

#include <utility>

#include "base/metrics/histogram_macros.h"
#include "base/trace_event/trace_event.h"
#include "components/viz/service/display/skia_output_surface.h"
#include "gpu/command_buffer/common/shared_image_usage.h"

namespace viz {

BufferQueue::BufferQueue(SkiaOutputSurface* skia_output_surface,
                         gpu::SurfaceHandle surface_handle,
                         size_t number_of_buffers,
                         bool is_protected)
    : skia_output_surface_(skia_output_surface),
      surface_handle_(surface_handle),
      number_of_buffers_(number_of_buffers),
      is_protected_(is_protected) {}

BufferQueue::~BufferQueue() {
  FreeAllBuffers();
}

gpu::Mailbox BufferQueue::GetCurrentBuffer() {
  if (!current_buffer_) {
    current_buffer_ = GetNextBuffer();
  }
  DCHECK(current_buffer_);
  return current_buffer_->mailbox;
}

void BufferQueue::UpdateBufferDamage(const gfx::Rect& damage) {
  if (displayed_buffer_) {
    displayed_buffer_->damage.Union(damage);
  }
  for (auto& buffer : available_buffers_) {
    buffer->damage.Union(damage);
  }
  for (auto& buffer : in_flight_buffers_) {
    if (buffer) {
      buffer->damage.Union(damage);
    }
  }
}

gfx::Rect BufferQueue::CurrentBufferDamage() const {
  if (current_buffer_) {
    return current_buffer_->damage;
  }

  // In case there is no current_buffer_, we get the damage from the buffer
  // that will be set as current_buffer_ by the next call to GetNextBuffer.
  if (!available_buffers_.empty()) {
    return available_buffers_.front()->damage;
  }

  // If we can't determine which buffer will be the next current_buffer_, we
  // conservatively invalidate the whole buffer.
  return gfx::Rect(size_);
}

void BufferQueue::SwapBuffers() {
  if (current_buffer_) {
    current_buffer_->damage = gfx::Rect();
  }
  // Note: In the case of an empty-swap frame, GetCurrentBuffer() was not called
  // this frame and current_buffer_ will be nullptr. We will still push nullptr
  // into in_flight_buffers_ so the queue is kept in sync when
  // SwapBuffersComplete() is called.
  in_flight_buffers_.push_back(std::move(current_buffer_));
}

void BufferQueue::SwapBuffersComplete(bool did_present) {
  CHECK(!in_flight_buffers_.empty());
  auto in_flight_buffer = std::move(in_flight_buffers_.front());
  in_flight_buffers_.pop_front();

  if (did_present) {
    if (displayed_buffer_) {
      available_buffers_.push_back(std::move(displayed_buffer_));
    }
    displayed_buffer_ = std::move(in_flight_buffer);
  } else {
    // The GPU thread decided to skip swap. The last in flight buffer was not
    // presented so don't switch out `displayed_buffer_`.
    if (in_flight_buffer) {
      available_buffers_.push_back(std::move(in_flight_buffer));
    }
    // Since skipped swap can sometimes be due to failure to draw to the primary
    // plane, add full damage to ensure that primary plane buffers are fully
    // redrawn.
    UpdateBufferDamage(gfx::Rect(size_));
  }

  if (buffers_can_be_purged_) {
    for (auto& buffer : available_buffers_) {
      if (SetBufferPurgeable(*buffer, true)) {
        // Set a single available buffer to purgeable each swap.
        break;
      }
    }
  }
}

bool BufferQueue::Reshape(const gfx::Size& size,
                          const gfx::ColorSpace& color_space,
                          RenderPassAlphaType alpha_type,
                          SharedImageFormat format) {
  if (size == size_ && color_space == color_space_ &&
      alpha_type == alpha_type_ && format == format_) {
    return false;
  }

  size_ = size;
  color_space_ = color_space;
  alpha_type_ = alpha_type;
  format_ = format;

  if (buffers_destroyed_) {
    return true;
  }

  if (buffers_can_be_purged_) {
    // If buffers are purgeable wait to recreate until they will be used again.
    DestroyBuffers();
    return true;
  }

  FreeAllBuffers();
  AllocateBuffers(number_of_buffers_);

  return true;
}

void BufferQueue::RecreateBuffers() {
  if (buffers_destroyed_) {
    return;
  }

  if (buffers_can_be_purged_) {
    // If buffers are purgeable wait to recreate until they will be used again.
    DestroyBuffers();
    return;
  }

  FreeAllBuffers();
  AllocateBuffers(number_of_buffers_);
}

void BufferQueue::FreeAllBuffers() {
  TRACE_EVENT("viz", __PRETTY_FUNCTION__);
  FreeBuffer(std::move(displayed_buffer_));
  FreeBuffer(std::move(current_buffer_));

  // This is intentionally not emptied since the swap buffers acks are still
  // expected to arrive.
  for (auto& buffer : in_flight_buffers_) {
    FreeBuffer(std::move(buffer));
  }

  for (auto& buffer : available_buffers_) {
    FreeBuffer(std::move(buffer));
  }
  available_buffers_.clear();
}

void BufferQueue::FreeBuffer(std::unique_ptr<AllocatedBuffer> buffer) {
  if (!buffer) {
    return;
  }
  DCHECK(!buffer->mailbox.IsZero());
  skia_output_surface_->DestroySharedImage(buffer->mailbox);
}

bool BufferQueue::SetBufferPurgeable(AllocatedBuffer& buffer, bool purgeable) {
  if (buffer.purgeable == purgeable) {
    return false;
  }

  skia_output_surface_->SetSharedImagePurgeable(buffer.mailbox, purgeable);
  buffer.purgeable = purgeable;
  return true;
}

void BufferQueue::AllocateBuffers(size_t n) {
  DCHECK(format_);

  const gpu::SharedImageUsageSet usage =
      gpu::SHARED_IMAGE_USAGE_DISPLAY_READ |
      gpu::SHARED_IMAGE_USAGE_DISPLAY_WRITE | gpu::SHARED_IMAGE_USAGE_SCANOUT |
      (is_protected_ ? gpu::SHARED_IMAGE_USAGE_PROTECTED_VIDEO
                     : gpu::SharedImageUsageSet());

  available_buffers_.reserve(available_buffers_.size() + n);
  for (size_t i = 0; i < n; ++i) {
    const gpu::Mailbox mailbox = skia_output_surface_->CreateSharedImage(
        format_.value(), size_, color_space_, alpha_type_, usage,
        "VizBufferQueue", surface_handle_);
    DCHECK(!mailbox.IsZero());

    available_buffers_.push_back(
        std::make_unique<AllocatedBuffer>(mailbox, gfx::Rect(size_)));
  }
}

std::unique_ptr<BufferQueue::AllocatedBuffer> BufferQueue::GetNextBuffer() {
  RecreateBuffersIfDestroyed();

  DCHECK(!available_buffers_.empty());

  std::unique_ptr<AllocatedBuffer> buffer =
      std::move(available_buffers_.front());
  available_buffers_.pop_front();
  return buffer;
}

gpu::Mailbox BufferQueue::GetLastSwappedBuffer() {
  if (buffers_destroyed_) {
    // Buffers will not be destroyed on platforms where we need to use a buffer
    // for overlay testing (Ash).
    return gpu::Mailbox();
  }

  // The last swapped buffer will generally be in `displayed_buffer_`, unless
  // the last completed swap was empty or there haven't been any completed swaps
  // since Reshape() was last called.
  if (displayed_buffer_) {
    return displayed_buffer_->mailbox;
  }

  // If displayed_buffer_ is null then any available buffer will do.
  if (!available_buffers_.empty()) {
    return available_buffers_.back()->mailbox;
  }

  // If there's nothing displayed or available, then we should have no buffers
  // allocated because Reshape() hasn't been called yet, so a zero-mailbox is
  // returned.
  // If any buffers are in flight at this point then BufferQueue is being used
  // incorrectly. We should not be Swap()ing all available buffers before
  // receiving any SwapBuffersComplete() calls.
  DCHECK(in_flight_buffers_.empty());
  return gpu::Mailbox();
}

void BufferQueue::EnsureMinNumberOfBuffers(size_t n) {
  if (n <= number_of_buffers_) {
    return;
  }

  // If Reshape hasn't been called yet we can't allocate the buffers.
  if (!size_.IsEmpty() && !buffers_destroyed_) {
    AllocateBuffers(n - number_of_buffers_);
  }
  number_of_buffers_ = n;
}

int BufferQueue::GetCurrentAllocatedBuffers() const {
  if (size_.IsEmpty() || buffers_destroyed_) {
    return 0;
  }
  return number_of_buffers_;
}

void BufferQueue::DestroyBuffers() {
  if (buffers_destroyed_) {
    return;
  }
  buffers_destroyed_ = true;
  destroyed_timer_ = base::ElapsedTimer();
  FreeAllBuffers();
}

void BufferQueue::SetBuffersPurgeable() {
  if (buffers_can_be_purged_) {
    return;
  }
  buffers_can_be_purged_ = true;
}

void BufferQueue::RecreateBuffersIfDestroyed() {
  if (buffers_can_be_purged_) {
    // Mark buffers as not purgeable. It's possible they were destroyed and
    // `available_buffers_` is empty.
    buffers_can_be_purged_ = false;
    for (auto& buffer : available_buffers_) {
      SetBufferPurgeable(*buffer, false);
    }
  }

  if (buffers_destroyed_) {
    buffers_destroyed_ = false;
    AllocateBuffers(number_of_buffers_);
    base::TimeDelta elapsed = destroyed_timer_->Elapsed();
    UMA_HISTOGRAM_TIMES("Compositing.BufferQueue.TimeUntilBuffersRecreatedMs",
                        elapsed);
    destroyed_timer_.reset();
  }
}

BufferQueue::AllocatedBuffer::AllocatedBuffer(const gpu::Mailbox& mailbox,
                                              const gfx::Rect& rect)
    : mailbox(mailbox), damage(rect) {}

BufferQueue::AllocatedBuffer::~AllocatedBuffer() = default;

}  // namespace viz
