chromium/gpu/vulkan/win32/vulkan_surface_win32.cc

// Copyright 2020 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/vulkan/win32/vulkan_surface_win32.h"

#include <windows.h>

#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/memory/ref_counted.h"
#include "base/synchronization/waitable_event.h"
#include "base/threading/thread.h"
#include "base/threading/thread_checker.h"
#include "gpu/vulkan/vulkan_function_pointers.h"
#include "ui/gfx/win/window_impl.h"

namespace gpu {

namespace {

// It is set by WindowThread ctor, and cleared by WindowThread dtor.
VulkanSurfaceWin32::WindowThread* g_thread = nullptr;

// It is set by HiddenToplevelWindow ctor, and cleared by HiddenToplevelWindow
// dtor.
class HiddenToplevelWindow* g_initial_parent_window = nullptr;

class HiddenToplevelWindow : public gfx::WindowImpl,
                             public base::RefCounted<HiddenToplevelWindow> {
 public:
  HiddenToplevelWindow() : gfx::WindowImpl("VulkanHiddenToplevelWindow") {
    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
    DCHECK(!g_initial_parent_window);
    set_initial_class_style(CS_OWNDC);
    set_window_style(WS_POPUP | WS_DISABLED);
    Init(GetDesktopWindow(), gfx::Rect());
    g_initial_parent_window = this;
  }

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

 private:
  friend class base::RefCounted<HiddenToplevelWindow>;

  // gfx::WindowImpl:
  BOOL ProcessWindowMessage(HWND window,
                            UINT message,
                            WPARAM w_param,
                            LPARAM l_param,
                            LRESULT& result,
                            DWORD msg_map_id) override {
    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
    if (message == WM_CLOSE) {
      // Prevent closing the window, since external apps may get a handle to
      // this window and attempt to close it.
      result = 0;
      return true;
    }
    return false;
  }

  ~HiddenToplevelWindow() override {
    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
    DCHECK_EQ(g_initial_parent_window, this);
    g_initial_parent_window = nullptr;
  }

  THREAD_CHECKER(thread_checker_);
};

class ChildWindow : public gfx::WindowImpl {
 public:
  explicit ChildWindow(HWND parent_window)
      : gfx::WindowImpl("VulkanHiddenToplevelWindow") {
    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);

    // If there is no other ChildWindow instance, |g_initial_parent_window| will
    // be nullptr, and then we need to create one. If |g_initial_parent_window|
    // is not nullptr, so |g_initial_parent_window| will not be destroyed until
    // this ChildWindow is destroyed.
    initial_parent_window_ = g_initial_parent_window;
    if (!initial_parent_window_)
      initial_parent_window_ = base::MakeRefCounted<HiddenToplevelWindow>();

    set_initial_class_style(CS_OWNDC);
    set_window_style(WS_VISIBLE | WS_CHILD | WS_DISABLED);
    RECT window_rect;
    if (!GetClientRect(parent_window, &window_rect))
      PLOG(DFATAL) << "GetClientRect() failed.";
    gfx::Rect bounds(window_rect);
    bounds.set_origin(gfx::Point(0, 0));
    Init(initial_parent_window_->hwnd(), bounds);
  }

  ~ChildWindow() override { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); }

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

 private:
  // gfx::WindowImpl:
  BOOL ProcessWindowMessage(HWND window,
                            UINT message,
                            WPARAM w_param,
                            LPARAM l_param,
                            LRESULT& result,
                            DWORD msg_map_id) override {
    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
    switch (message) {
      case WM_ERASEBKGND:
        // Prevent windows from erasing the background.
        return true;
      case WM_PAINT:
        // Do not paint anything.
        PAINTSTRUCT paint;
        if (BeginPaint(window, &paint))
          EndPaint(window, &paint);
        return false;
      default:
        return false;
    }
  }

  // A temporary parent window for this child window, it will be reparented
  // by browser soon. All child windows share one initial parent window.
  // The initial parent window will be destroyed with the last child window.
  scoped_refptr<HiddenToplevelWindow> initial_parent_window_;

  THREAD_CHECKER(thread_checker_);
};

void CreateChildWindow(HWND parent_window,
                       std::unique_ptr<ChildWindow>* window,
                       base::WaitableEvent* event) {
  *window = std::make_unique<ChildWindow>(parent_window);
  event->Signal();
}

}  // namespace

class VulkanSurfaceWin32::WindowThread : public base::Thread,
                                         public base::RefCounted<WindowThread> {
 public:
  WindowThread() : base::Thread("VulkanWindowThread") {
    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
    DCHECK(!g_thread);
    g_thread = this;
    base::Thread::Options options(base::MessagePumpType::UI, 0);
    StartWithOptions(std::move(options));
  }

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

 private:
  friend class base::RefCounted<WindowThread>;

  ~WindowThread() override {
    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
    DCHECK_EQ(g_thread, this);
    Stop();
    g_thread = nullptr;
  }

  THREAD_CHECKER(thread_checker_);
};

// static
std::unique_ptr<VulkanSurfaceWin32> VulkanSurfaceWin32::Create(
    VkInstance vk_instance,
    HWND parent_window) {
  scoped_refptr<WindowThread> thread = g_thread;
  if (!thread) {
    // If there is no other VulkanSurfaceWin32, g_thread will be nullptr, and
    // we need to create a thread for running child window message loop.
    // Otherwise keep a ref of g_thread, so it will not be destroyed until the
    // this VulkanSurfaceWin32 is destroyed.
    thread = base::MakeRefCounted<WindowThread>();
    g_thread = thread.get();
  }

  // vkCreateSwapChainKHR() fails in sandbox with a window which is created by
  // other process with NVIDIA driver. Workaround the problem by creating a
  // child window and use it to create vulkan surface.
  // TODO(penghuang): Only apply this workaround with NVIDIA GPU?
  // https://crbug.com/1068742
  std::unique_ptr<ChildWindow> window;
  base::WaitableEvent event;
  thread->task_runner()->PostTask(
      FROM_HERE,
      base::BindOnce(&CreateChildWindow, parent_window, &window, &event));
  event.Wait();

  VkSurfaceKHR surface;
  VkWin32SurfaceCreateInfoKHR surface_create_info = {
      .sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR,
      .hinstance = reinterpret_cast<HINSTANCE>(
          GetWindowLongPtr(window->hwnd(), GWLP_HINSTANCE)),
      .hwnd = window->hwnd(),
  };
  VkResult result = vkCreateWin32SurfaceKHR(vk_instance, &surface_create_info,
                                            nullptr, &surface);
  if (VK_SUCCESS != result) {
    LOG(DFATAL) << "vkCreatWin32SurfaceKHR() failed: " << result;
    return nullptr;
  }
  return std::make_unique<VulkanSurfaceWin32>(
      base::PassKey<VulkanSurfaceWin32>(), vk_instance, surface,
      std::move(thread), std::move(window));
}

VulkanSurfaceWin32::VulkanSurfaceWin32(
    base::PassKey<VulkanSurfaceWin32> pass_key,
    VkInstance vk_instance,
    VkSurfaceKHR vk_surface,
    scoped_refptr<WindowThread> thread,
    std::unique_ptr<gfx::WindowImpl> window)
    : VulkanSurface(vk_instance, window->hwnd(), vk_surface),
      thread_(std::move(thread)),
      window_(std::move(window)) {}

VulkanSurfaceWin32::~VulkanSurfaceWin32() {
  thread_->task_runner()->DeleteSoon(FROM_HERE, std::move(window_));
}

bool VulkanSurfaceWin32::Reshape(const gfx::Size& size,
                                 gfx::OverlayTransform pre_transform) {
  DCHECK_EQ(pre_transform, gfx::OVERLAY_TRANSFORM_NONE);
  constexpr auto kFlags = SWP_NOMOVE | SWP_NOACTIVATE | SWP_NOCOPYBITS |
                          SWP_NOOWNERZORDER | SWP_NOZORDER;
  if (!SetWindowPos(window_->hwnd(), nullptr, 0, 0, size.width(), size.height(),
                    kFlags)) {
    PLOG(DFATAL) << "SetWindowPos() failed";
    return false;
  }
  return VulkanSurface::Reshape(size, pre_transform);
}

}  // namespace gpu