// Copyright 2020 The Chromium Authors. All rights reserved. // 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 #include "base/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 { 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; // 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(); 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 initial_parent_window_; THREAD_CHECKER(thread_checker_); }; void CreateChildWindow(HWND parent_window, std::unique_ptr* window, base::WaitableEvent* event) { *window = std::make_unique(parent_window); event->Signal(); } } // namespace class VulkanSurfaceWin32::WindowThread : public base::Thread, public base::RefCounted { 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() 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::Create( VkInstance vk_instance, HWND parent_window) { scoped_refptr 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(); 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 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( 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( base::PassKey(), vk_instance, surface, std::move(thread), std::move(window)); } VulkanSurfaceWin32::VulkanSurfaceWin32( base::PassKey pass_key, VkInstance vk_instance, VkSurfaceKHR vk_surface, scoped_refptr thread, std::unique_ptr 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