chromium/gpu/command_buffer/service/shared_image/dxgi_swap_chain_image_backing.cc

// 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/feature_list.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 "gpu/command_buffer/common/mailbox.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 "third_party/skia/include/core/SkAlphaType.h"
#include "third_party/skia/include/gpu/GrTypes.h"
#include "ui/gfx/buffer_format_util.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_switches.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";
}  // namespace

// static
std::unique_ptr<DXGISwapChainImageBacking> DXGISwapChainImageBacking::Create(
    Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device,
    const Mailbox& mailbox,
    viz::SharedImageFormat format,
    DXGI_FORMAT internal_format,
    const gfx::Size& size,
    const gfx::ColorSpace& color_space,
    GrSurfaceOrigin surface_origin,
    SkAlphaType alpha_type,
    gpu::SharedImageUsageSet usage,
    std::string debug_label) {
  if (!d3d11_device) {
    return nullptr;
  }

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

  DXGI_SWAP_CHAIN_DESC1 desc = {};
  desc.Width = size.width();
  desc.Height = size.height();
  desc.Format = internal_format;
  desc.Stereo = FALSE;
  desc.SampleDesc.Count = 1;
  desc.BufferCount = gl::DirectCompositionRootSurfaceBufferCount();
  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(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_chain;
  HRESULT hr = dxgi_factory->CreateSwapChainForComposition(
      d3d11_device.Get(), &desc, nullptr, &dxgi_swap_chain);

  // 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);
    // Disable direct composition because SwapChain creation might fail again
    // next time.
    gl::SetDirectCompositionSwapChainFailed();
    return nullptr;
  }

  gl::LabelSwapChainAndBuffers(dxgi_swap_chain.Get(),
                               kDXGISwapChainImageBackingLabel);

  Microsoft::WRL::ComPtr<IDXGISwapChain3> swap_chain_3;
  if (SUCCEEDED(dxgi_swap_chain.As(&swap_chain_3))) {
    hr = swap_chain_3->SetColorSpace1(
        gfx::ColorSpaceWin::GetDXGIColorSpace(color_space));
    DCHECK_EQ(hr, S_OK) << ", SetColorSpace1 failed: "
                        << logging::SystemErrorCodeToString(hr);
    if (gl::DXGIWaitableSwapChainEnabled()) {
      hr = swap_chain_3->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 =
      !format.HasAlpha() ? desc.BufferCount : 0;

  return base::WrapUnique(new DXGISwapChainImageBacking(
      mailbox, format, size, color_space, surface_origin, alpha_type, usage,
      std::move(debug_label), std::move(d3d11_device),
      std::move(dxgi_swap_chain), buffers_need_alpha_initialization_count));
}

DXGISwapChainImageBacking::DXGISwapChainImageBacking(
    const Mailbox& mailbox,
    viz::SharedImageFormat format,
    const gfx::Size& size,
    const gfx::ColorSpace& color_space,
    GrSurfaceOrigin surface_origin,
    SkAlphaType alpha_type,
    gpu::SharedImageUsageSet usage,
    std::string debug_label,
    Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device,
    Microsoft::WRL::ComPtr<IDXGISwapChain1> dxgi_swap_chain,
    int buffers_need_alpha_initialization_count)
    : ClearTrackingSharedImageBacking(
          mailbox,
          format,
          size,
          color_space,
          surface_origin,
          alpha_type,
          usage,
          std::move(debug_label),
          gfx::BufferSizeForBufferFormat(size, ToBufferFormat(format)),
          /*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 = usage.Has(SHARED_IMAGE_USAGE_SCANOUT);
  const bool has_write = usage.Has(SHARED_IMAGE_USAGE_DISPLAY_WRITE);
  DCHECK(has_scanout);
  DCHECK(has_write);
}

DXGISwapChainImageBacking::~DXGISwapChainImageBacking() = default;

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(false)) {
      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(
    bool should_synchronize_present_with_vblank) {
  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();
  const bool force_present_interval_0 =
      base::FeatureList::IsEnabled(features::kDXGISwapChainPresentInterval0);
  UINT interval = first_swap_ || !should_synchronize_present_with_vblank ||
                          use_swap_chain_tearing || force_present_interval_0
                      ? 0
                      : 1;
  UINT flags = use_swap_chain_tearing ? DXGI_PRESENT_ALLOW_TEARING : 0;

  TRACE_EVENT2("gpu", "IDXGISwapChain1::Present1", "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, size(), color_space(), backbuffer_texture,
        GL_TEXTURE_2D, /*array_slice=*/0, /*plane_index=*/0, dxgi_swap_chain_);
    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 SkiaGraphiteDawnImageRepresentation::Create(
      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::SharedTextureMemoryBeginAccessDescriptor desc = {};
  desc.initialized = true;
  desc.nextInChain = &swapchain_begin_state;

  wgpu::Texture texture =
      CreateDawnSharedTexture(shared_texture_memory_, usage, internal_usage,
                              /*view_formats=*/{});
  if (!texture || !shared_texture_memory_.BeginAccess(texture, &desc)) {
    LOG(ERROR) << "Failed to begin access and produce WGPUTexture";
    return nullptr;
  }
  return texture;
}

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

}  // namespace gpu