// 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