| // 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 <windows.h> |
| |
| #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<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 |