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

#include "gpu/command_buffer/service/shared_image/dxgi_swap_chain_image_backing.h"

#include "base/debug/alias.h"
#include "base/debug/dump_without_crashing.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_functions.h"
#include "base/strings/stringprintf.h"
#include "base/synchronization/waitable_event.h"
#include "base/trace_event/trace_event.h"
#include "components/viz/common/resources/shared_image_format.h"
#include "gpu/command_buffer/common/mailbox.h"
#include "gpu/command_buffer/common/shared_image_info.h"
#include "gpu/command_buffer/common/shared_image_usage.h"
#include "gpu/command_buffer/service/memory_tracking.h"
#include "gpu/command_buffer/service/shared_context_state.h"
#include "gpu/command_buffer/service/shared_image/d3d_image_backing_factory.h"
#include "gpu/command_buffer/service/shared_image/d3d_image_utils.h"
#include "gpu/command_buffer/service/shared_image/dxgi_swap_chain_image_representation.h"
#include "gpu/command_buffer/service/shared_image/shared_image_backing.h"
#include "gpu/command_buffer/service/shared_image/shared_image_format_service_utils.h"
#include "gpu/command_buffer/service/shared_image/shared_image_manager.h"
#include "gpu/command_buffer/service/shared_image/skia_gl_image_representation.h"
#include "gpu/config/gpu_util.h"
#include "third_party/skia/include/core/SkAlphaType.h"
#include "third_party/skia/include/gpu/ganesh/GrTypes.h"
#include "ui/gfx/color_space.h"
#include "ui/gfx/color_space_win.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gl/direct_composition_support.h"
#include "ui/gl/gl_utils.h"

#if BUILDFLAG(SKIA_USE_DAWN)
#include "gpu/command_buffer/service/dawn_context_provider.h"
#include "gpu/command_buffer/service/shared_image/skia_graphite_dawn_image_representation.h"
#endif

namespace gpu {

namespace {
const char* kDXGISwapChainImageBackingLabel = "DXGISwapChainImageBacking";
}  // anonymous namespace

// static
std::unique_ptr<DXGISwapChainImageBacking> DXGISwapChainImageBacking::Create(
    Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device,
    const Mailbox& mailbox,
    const SharedImageInfo& si_info,
    DXGI_FORMAT internal_format,
    GrContextType gr_context_type) {
  if (!d3d11_device) {
    return nullptr;
  }

  Microsoft::WRL::ComPtr<IDXGIDevice> dxgi_device;
  HRESULT hr = d3d11_device.As(&dxgi_device);
  CHECK_EQ(hr, S_OK);
  Microsoft::WRL::ComPtr<IDXGIAdapter> dxgi_adapter;
  hr = dxgi_device->GetAdapter(&dxgi_adapter);
  CHECK_EQ(hr, S_OK);
  Microsoft::WRL::ComPtr<IDXGIFactory2> dxgi_factory;
  dxgi_adapter->GetParent(IID_PPV_ARGS(&dxgi_factory));
  DCHECK(dxgi_factory);

  DXGI_SWAP_CHAIN_DESC1 desc = {};
  desc.Width = si_info.size.width();
  desc.Height = si_info.size.height();
  desc.Format = internal_format;
  desc.Stereo = FALSE;
  desc.SampleDesc.Count = 1;
  desc.BufferCount =
      gpu::DirectCompositionRootSurfaceBufferCount(gr_context_type);
  desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT |
                     /* Needed to bind to GL texture */ DXGI_USAGE_SHADER_INPUT;
  desc.Scaling = DXGI_SCALING_STRETCH;
  desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;
  desc.AlphaMode = SkAlphaTypeIsOpaque(si_info.alpha_type)
                       ? DXGI_ALPHA_MODE_IGNORE
                       : DXGI_ALPHA_MODE_PREMULTIPLIED;
  desc.Flags = 0;
  if (gl::DirectCompositionSwapChainTearingEnabled()) {
    desc.Flags |= DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING;
  }
  if (gl::DXGIWaitableSwapChainEnabled()) {
    desc.Flags |= DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT;
  }

  Microsoft::WRL::ComPtr<IDXGISwapChain1> dxgi_swap_chain1;
  hr = dxgi_factory->CreateSwapChainForComposition(d3d11_device.Get(), &desc,
                                                   nullptr, &dxgi_swap_chain1);

  // If CreateSwapChainForComposition fails, we cannot draw to the
  // browser window. Return false after disabling Direct Composition support
  // and let the Renderer handle it. Either the GPU command buffer or the GPU
  // process will be restarted.
  if (FAILED(hr)) {
    DLOG(ERROR) << "CreateSwapChainForComposition failed: "
                << logging::SystemErrorCodeToString(hr);
    return nullptr;
  }

  Microsoft::WRL::ComPtr<IDXGISwapChain3> dxgi_swap_chain;
  CHECK_EQ(dxgi_swap_chain1.As(&dxgi_swap_chain), S_OK);
  gl::LabelSwapChainAndBuffers(dxgi_swap_chain.Get(),
                               kDXGISwapChainImageBackingLabel);

  hr = dxgi_swap_chain->SetColorSpace1(
      gfx::ColorSpaceWin::GetDXGIColorSpace(si_info.color_space));
  DCHECK_EQ(hr, S_OK) << ", SetColorSpace1 failed: "
                      << logging::SystemErrorCodeToString(hr);
  if (gl::DXGIWaitableSwapChainEnabled()) {
    hr = dxgi_swap_chain->SetMaximumFrameLatency(
        gl::GetDXGIWaitableSwapChainMaxQueuedFrames());
    DCHECK_EQ(hr, S_OK) << ", SetMaximumFrameLatency failed: "
                        << logging::SystemErrorCodeToString(hr);
  }

  // When |format| has no alpha (e.g. RGBX) but |internal_format| does, we wrap
  // the back buffer in ANGLE as |GL_RGB|. To ensure shaders that sample from
  // it see an opaque color, it needs to have 1.f in the alpha channel.
  // When |format| has alpha, we can rely on DirectRenderer to ensure all pixels
  // are initialized before use.
  int buffers_need_alpha_initialization_count =
      !si_info.format.HasAlpha() ? desc.BufferCount : 0;

  return base::WrapUnique(new DXGISwapChainImageBacking(
      mailbox, si_info, std::move(d3d11_device), std::move(dxgi_swap_chain),
      buffers_need_alpha_initialization_count));
}

DXGISwapChainImageBacking::DXGISwapChainImageBacking(
    const Mailbox& mailbox,
    const SharedImageInfo& si_info,
    Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device,
    Microsoft::WRL::ComPtr<IDXGISwapChain3> dxgi_swap_chain,
    int buffers_need_alpha_initialization_count)
    : ClearTrackingSharedImageBacking(
          mailbox,
          si_info,
          si_info.format.EstimatedSizeInBytes(si_info.size),
          /*is_thread_safe=*/false),
      d3d11_device_(std::move(d3d11_device)),
      dxgi_swap_chain_(std::move(dxgi_swap_chain)),
      buffers_need_alpha_initialization_count_(
          buffers_need_alpha_initialization_count) {
  const bool has_scanout = si_info.usage.Has(SHARED_IMAGE_USAGE_SCANOUT);
  const bool has_write = si_info.usage.Has(SHARED_IMAGE_USAGE_DISPLAY_WRITE);
  DCHECK(has_scanout);
  DCHECK(has_write);
}

DXGISwapChainImageBacking::~DXGISwapChainImageBacking() {
  if (cached_wgpu_texture_) {
    cached_wgpu_texture_.Destroy();
  }
}

SharedImageBackingType DXGISwapChainImageBacking::GetType() const {
  return SharedImageBackingType::kDXGISwapChain;
}

void DXGISwapChainImageBacking::Update(
    std::unique_ptr<gfx::GpuFence> in_fence) {
  DCHECK(!in_fence);
}

bool DXGISwapChainImageBacking::DidBeginWriteAccess(
    const gfx::Rect& swap_rect) {
  if (pending_swap_rect_.has_value()) {
    // Force a Present if there's already a pending swap rect. For normal usage
    // of this backing, we normally expect one Skia write access to overlay read
    // access.
    // We'll log a message so it appears in the GPU log in case it aids in
    // debugging.
    LOG(WARNING) << "Multiple skia write accesses per overlay access, flushing "
                    "pending swap.";
    if (!Present()) {
      return false;
    }
  }

  gfx::Rect pending_swap_rect = swap_rect;

  std::optional<SkColor4f> initialize_color;

  // SharedImage allows an incomplete first draw so long as we only read from
  // the part that we've previously drawn to. However, IDXGISwapChain requires a
  // full draw on the first |Present1|. To make an incomplete first draw valid,
  // we'll initialize all the pixels and expand the swap rect.
  const gfx::Rect full_swap_rect = gfx::Rect(size());
  if (!IsCleared() && swap_rect != full_swap_rect) {
#if DCHECK_IS_ON()
    initialize_color = SkColors::kBlue;
#else
    initialize_color = SkColors::kTransparent;
#endif

    // Ensure that the next swap contains the entire swap chain since we just
    // cleared it.
    pending_swap_rect = full_swap_rect;
  }

  // See comment in |DXGISwapChainImageBacking::Create| for why we need this.
  if (buffers_need_alpha_initialization_count_ > 0) {
    // We only need to write the alpha channel, but we clear since it's simpler
    // and are guaranteed to not have pixels we need to preserve before the
    // first write to each buffer.
#if DCHECK_IS_ON()
    initialize_color = SkColors::kBlue;
#else
    initialize_color = SkColors::kBlack;
#endif

    // We don't need to modify the swap rect in this case since |Present1| will
    // copy the contents outside the swap rect from the previous buffer and
    // we've already forced a full swap on the first buffer above.
  }

  if (initialize_color.has_value()) {
    // To clear only uninitialized buffers, this must happen after |Present|s of
    // outstanding draws, including the one above.
    if (!D3DImageBackingFactory::ClearBackBufferToColor(
            dxgi_swap_chain_.Get(), initialize_color.value())) {
      LOG(ERROR) << "Could not initialize back buffer alpha";
      return false;
    }

    if (buffers_need_alpha_initialization_count_ > 0) {
      buffers_need_alpha_initialization_count_--;
    }
  }

  pending_swap_rect_ = {pending_swap_rect};

  return true;
}

bool DXGISwapChainImageBacking::Present() {
  if (!pending_swap_rect_.has_value() || pending_swap_rect_.value().IsEmpty()) {
    DVLOG(1) << "Skipping present without an update rect";
    return true;
  }

  HRESULT hr, device_removed_reason;
  const bool use_swap_chain_tearing =
      gl::DirectCompositionSwapChainTearingEnabled();
  // Always present with interval 0, i.e. don't synchronize with vblank. Frames
  // may be discarded if they are presented more frequently than one per vblank.
  const UINT interval = 0;
  UINT flags = use_swap_chain_tearing ? DXGI_PRESENT_ALLOW_TEARING : 0;

  TRACE_EVENT2("gpu", "DXGISwapChainImageBacking::Present", "has_alpha",
               !SkAlphaTypeIsOpaque(alpha_type()), "dirty_rect",
               pending_swap_rect_->ToString());
  DXGI_PRESENT_PARAMETERS params = {};
  RECT dirty_rect = pending_swap_rect_.value().ToRECT();
  params.DirtyRectsCount = 1;
  params.pDirtyRects = &dirty_rect;
  hr = dxgi_swap_chain_->Present1(interval, flags, &params);

  // Ignore DXGI_STATUS_OCCLUDED since that's not an error but only
  // indicates that the window is occluded and we can stop rendering.
  if (FAILED(hr) && hr != DXGI_STATUS_OCCLUDED) {
    LOG(ERROR) << "Present1 failed: " << logging::SystemErrorCodeToString(hr);
    return false;
  }

  if (first_swap_) {
    // Wait for the GPU to finish executing its commands before
    // committing the DirectComposition tree, or else the swapchain
    // may flicker black when it's first presented.
    first_swap_ = false;
    Microsoft::WRL::ComPtr<IDXGIDevice2> dxgi_device2;
    d3d11_device_.As(&dxgi_device2);
    DCHECK(dxgi_device2);
    base::WaitableEvent event(base::WaitableEvent::ResetPolicy::AUTOMATIC,
                              base::WaitableEvent::InitialState::NOT_SIGNALED);
    hr = dxgi_device2->EnqueueSetEvent(event.handle());
    if (SUCCEEDED(hr)) {
      event.Wait();
    } else {
      device_removed_reason = d3d11_device_->GetDeviceRemovedReason();
      base::debug::Alias(&hr);
      base::debug::Alias(&device_removed_reason);
      base::debug::DumpWithoutCrashing();
    }
  }

  pending_swap_rect_.reset();

  return true;
}

std::unique_ptr<OverlayImageRepresentation>
DXGISwapChainImageBacking::ProduceOverlay(SharedImageManager* manager,
                                          MemoryTypeTracker* tracker) {
  return std::make_unique<DXGISwapChainOverlayImageRepresentation>(
      manager, this, tracker);
}

std::unique_ptr<SkiaGaneshImageRepresentation>
DXGISwapChainImageBacking::ProduceSkiaGanesh(
    SharedImageManager* manager,
    MemoryTypeTracker* tracker,
    scoped_refptr<SharedContextState> context_state) {
  TRACE_EVENT0("gpu", "DXGISwapChainImageBacking::ProduceSkiaGanesh");

  if (!gl_texture_holder_) {
    Microsoft::WRL::ComPtr<ID3D11Texture2D> backbuffer_texture;
    HRESULT hr =
        dxgi_swap_chain_->GetBuffer(0, IID_PPV_ARGS(&backbuffer_texture));
    if (FAILED(hr)) {
      DLOG(ERROR) << "GetBuffer(0) failed: "
                  << logging::SystemErrorCodeToString(hr);
      return nullptr;
    }

    auto gl_format_desc = context_state->GetGLFormatCaps().ToGLFormatDesc(
        format(), /*plane_index=*/0);
    gl_texture_holder_ = D3DImageBacking::CreateGLTexture(
        gl_format_desc, backbuffer_texture, GL_TEXTURE_2D, /*array_slice=*/0,
        /*plane_index=*/0);
    if (!gl_texture_holder_) {
      LOG(ERROR) << "Failed to create GL texture.";
      return nullptr;
    }
  }

  return SkiaGLImageRepresentationDXGISwapChain::Create(
      std::make_unique<GLTexturePassthroughDXGISwapChainBufferRepresentation>(
          manager, this, tracker, gl_texture_holder_),
      std::move(context_state), manager, this, tracker);
}

std::unique_ptr<SkiaGraphiteImageRepresentation>
DXGISwapChainImageBacking::ProduceSkiaGraphite(
    SharedImageManager* manager,
    MemoryTypeTracker* tracker,
    scoped_refptr<SharedContextState> context_state) {
#if BUILDFLAG(SKIA_USE_DAWN)
  DCHECK_EQ(context_state->gr_context_type(), GrContextType::kGraphiteDawn);

  auto device = context_state->dawn_context_provider()->GetDevice();
  if (!shared_texture_memory_) {
    Microsoft::WRL::ComPtr<ID3D11Texture2D> backbuffer_texture;
    HRESULT hr =
        dxgi_swap_chain_->GetBuffer(0, IID_PPV_ARGS(&backbuffer_texture));
    if (FAILED(hr)) {
      DLOG(ERROR) << "GetBuffer(0) failed: "
                  << logging::SystemErrorCodeToString(hr);
      return nullptr;
    }

    shared_texture_memory_ =
        CreateDawnSharedTextureMemory(device, backbuffer_texture);
    if (!shared_texture_memory_) {
      LOG(ERROR) << "Failed to create shared texture memory.";
      return nullptr;
    }
  }

  auto dawn_representation = std::make_unique<DawnRepresentationDXGISwapChain>(
      manager, this, tracker, device, wgpu::BackendType::D3D11);

  return std::make_unique<SkiaGraphiteDawnImageRepresentation>(
      std::move(dawn_representation), context_state,
      context_state->gpu_main_graphite_recorder(), manager, this, tracker);
#else
  NOTREACHED();
#endif  // BUILDFLAG(SKIA_USE_DAWN)
}

wgpu::Texture DXGISwapChainImageBacking::BeginAccessDawn(
    const wgpu::Device& device,
    wgpu::TextureUsage usage,
    wgpu::TextureUsage internal_usage,
    const gfx::Rect& update_rect) {
  DidBeginWriteAccess(update_rect);

  CHECK(shared_texture_memory_);
  wgpu::SharedTextureMemoryD3DSwapchainBeginState swapchain_begin_state = {};
  swapchain_begin_state.isSwapchain = true;

  wgpu::SharedTextureMemoryD3D11BeginState d3d11_begin_state = {};
  d3d11_begin_state.requiresEndAccessFence = false;
  swapchain_begin_state.nextInChain = &d3d11_begin_state;

  wgpu::SharedTextureMemoryBeginAccessDescriptor desc = {};
  desc.initialized = true;
  desc.nextInChain = &swapchain_begin_state;

  if (!cached_wgpu_texture_ || cached_wgpu_texture_usage_ != usage) {
    if (cached_wgpu_texture_) {
      cached_wgpu_texture_.Destroy();
    }
    // Only Graphite should use this backing, thus internal_usage should be
    // none.
    CHECK_EQ(internal_usage, wgpu::TextureUsage::None);

    cached_wgpu_texture_ =
        CreateDawnSharedTexture(shared_texture_memory_, usage, internal_usage,
                                /*view_formats=*/{});
    cached_wgpu_texture_usage_ = usage;
  }

  if (!cached_wgpu_texture_ ||
      (shared_texture_memory_.BeginAccess(cached_wgpu_texture_, &desc) !=
       wgpu::Status::Success)) {
    LOG(ERROR) << "Failed to begin access and produce WGPUTexture";
    return nullptr;
  }
  return cached_wgpu_texture_;
}

void DXGISwapChainImageBacking::EndAccessDawn(const wgpu::Device& device,
                                              wgpu::Texture texture) {
  DCHECK_EQ(cached_wgpu_texture_.Get(), texture.Get());
  wgpu::SharedTextureMemoryEndAccessState end_state = {};
  shared_texture_memory_.EndAccess(texture, &end_state);
}

}  // namespace gpu
