From 0607a181a8aa1830bef6178d35b3c374363ce905 Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Mon, 1 Jun 2026 11:45:00 +0200 Subject: [PATCH] UI/Qt: Present Linux DMABUF frames with QVulkanWindow Use a native QVulkanWindow child for Linux DMABUF presentation instead of making WebContentView itself a QRhiWidget. This keeps BrowserWindow on Qt's normal QWidget backing store path while sampling compositor DMABUF backing stores directly in Vulkan. Keep WebContentView's paintEvent as fallback for builds without Vulkan DMABUF support and for cases where the Vulkan instance or window cannot be created. Set an explicit Vulkan API version before creating the shared QVulkanInstance so validation layers do not reject Qt defaults. --- Meta/CMake/check_for_dependencies.cmake | 3 +- UI/Qt/CMakeLists.txt | 46 +- UI/Qt/Shaders/WebContentViewLinux.frag | 17 + UI/Qt/Shaders/WebContentViewLinux.vert | 31 + UI/Qt/WebContentView.cpp | 154 +++- UI/Qt/WebContentView.h | 34 +- UI/Qt/WebContentViewLinux.cpp | 932 ++++++++++++++++++++++++ 7 files changed, 1186 insertions(+), 31 deletions(-) create mode 100644 UI/Qt/Shaders/WebContentViewLinux.frag create mode 100644 UI/Qt/Shaders/WebContentViewLinux.vert create mode 100644 UI/Qt/WebContentViewLinux.cpp diff --git a/Meta/CMake/check_for_dependencies.cmake b/Meta/CMake/check_for_dependencies.cmake index f30fe34d2f..c83129b1b3 100644 --- a/Meta/CMake/check_for_dependencies.cmake +++ b/Meta/CMake/check_for_dependencies.cmake @@ -49,12 +49,13 @@ endif() if (NOT APPLE) find_package(VulkanHeaders CONFIG QUIET) find_package(Vulkan QUIET) + find_program(GLSLANG_VALIDATOR glslangValidator) if (VulkanHeaders_FOUND AND Vulkan_FOUND) set(HAS_VULKAN ON CACHE BOOL "" FORCE) add_cxx_compile_definitions(USE_VULKAN=1) # Sharable Vulkan images are currently only implemented on Linux and BSDs - if ((LINUX AND NOT ANDROID) OR BSD) + if (((LINUX AND NOT ANDROID) OR BSD) AND GLSLANG_VALIDATOR) set(USE_VULKAN_DMABUF_IMAGES ON CACHE BOOL "" FORCE) add_cxx_compile_definitions(USE_VULKAN_DMABUF_IMAGES=1) endif() diff --git a/UI/Qt/CMakeLists.txt b/UI/Qt/CMakeLists.txt index 6cdbaf00a9..582d029c38 100644 --- a/UI/Qt/CMakeLists.txt +++ b/UI/Qt/CMakeLists.txt @@ -4,8 +4,8 @@ set(CMAKE_AUTOUIC ON) find_package(Qt6 REQUIRED COMPONENTS Core Widgets) if (APPLE) - # The macOS QRhiWidget presentation path uses QRhi's private Metal native - # handle types to import IOSurfaces without CPU readback. + # The native presentation paths use platform GPU buffer handles directly + # instead of copying frames through CPU memory. set(QT_NO_PRIVATE_MODULE_WARNING ON) find_package(Qt6 REQUIRED COMPONENTS GuiPrivate) endif() @@ -42,6 +42,48 @@ if (APPLE) target_link_libraries(ladybird PRIVATE Qt::GuiPrivate "-framework Cocoa" "-framework IOSurface" "-framework Metal" "-framework QuartzCore") endif() +if (NOT APPLE AND USE_VULKAN_DMABUF_IMAGES AND Qt6_VERSION VERSION_GREATER_EQUAL 6.7.0) + set(WEB_CONTENT_VIEW_LINUX_VERT_SHADER "${CMAKE_CURRENT_SOURCE_DIR}/Shaders/WebContentViewLinux.vert") + set(WEB_CONTENT_VIEW_LINUX_FRAG_SHADER "${CMAKE_CURRENT_SOURCE_DIR}/Shaders/WebContentViewLinux.frag") + set(WEB_CONTENT_VIEW_LINUX_VERT_SHADER_HEADER "${CMAKE_CURRENT_BINARY_DIR}/WebContentViewLinuxVertShader.h") + set(WEB_CONTENT_VIEW_LINUX_FRAG_SHADER_HEADER "${CMAKE_CURRENT_BINARY_DIR}/WebContentViewLinuxFragShader.h") + + add_custom_command( + OUTPUT "${WEB_CONTENT_VIEW_LINUX_VERT_SHADER_HEADER}" + COMMAND "${GLSLANG_VALIDATOR}" + -V + --vn webcontent_vert_shader_spv + -o "${WEB_CONTENT_VIEW_LINUX_VERT_SHADER_HEADER}" + "${WEB_CONTENT_VIEW_LINUX_VERT_SHADER}" + DEPENDS "${WEB_CONTENT_VIEW_LINUX_VERT_SHADER}" + VERBATIM + ) + add_custom_command( + OUTPUT "${WEB_CONTENT_VIEW_LINUX_FRAG_SHADER_HEADER}" + COMMAND "${GLSLANG_VALIDATOR}" + -V + --vn webcontent_frag_shader_spv + -o "${WEB_CONTENT_VIEW_LINUX_FRAG_SHADER_HEADER}" + "${WEB_CONTENT_VIEW_LINUX_FRAG_SHADER}" + DEPENDS "${WEB_CONTENT_VIEW_LINUX_FRAG_SHADER}" + VERBATIM + ) + add_custom_target(generate_web_content_view_linux_shaders + DEPENDS + "${WEB_CONTENT_VIEW_LINUX_VERT_SHADER_HEADER}" + "${WEB_CONTENT_VIEW_LINUX_FRAG_SHADER_HEADER}" + ) + + target_sources(ladybird PRIVATE + WebContentViewLinux.cpp + "${WEB_CONTENT_VIEW_LINUX_VERT_SHADER_HEADER}" + "${WEB_CONTENT_VIEW_LINUX_FRAG_SHADER_HEADER}" + ) + target_include_directories(ladybird PRIVATE "${CMAKE_CURRENT_BINARY_DIR}") + target_link_libraries(ladybird PRIVATE Vulkan::Vulkan) + add_dependencies(ladybird generate_web_content_view_linux_shaders) +endif() + create_ladybird_bundle(ladybird) if (WIN32) diff --git a/UI/Qt/Shaders/WebContentViewLinux.frag b/UI/Qt/Shaders/WebContentViewLinux.frag new file mode 100644 index 0000000000..07eaf15656 --- /dev/null +++ b/UI/Qt/Shaders/WebContentViewLinux.frag @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2026-present, the Ladybird developers. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#version 450 + +layout(set = 0, binding = 0) uniform sampler2D web_content_texture; + +layout(location = 0) in vec2 texture_coordinates; +layout(location = 0) out vec4 output_color; + +void main() +{ + output_color = texture(web_content_texture, texture_coordinates); +} diff --git a/UI/Qt/Shaders/WebContentViewLinux.vert b/UI/Qt/Shaders/WebContentViewLinux.vert new file mode 100644 index 0000000000..096a11b040 --- /dev/null +++ b/UI/Qt/Shaders/WebContentViewLinux.vert @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2026-present, the Ladybird developers. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#version 450 + +layout(push_constant) uniform PushConstants { + vec2 target_size; + vec2 content_size; + vec2 source_size; +} push_constants; + +layout(location = 0) out vec2 texture_coordinates; + +void main() +{ + vec2 unit_positions[4] = vec2[]( + vec2(0.0, 0.0), + vec2(1.0, 0.0), + vec2(0.0, 1.0), + vec2(1.0, 1.0)); + + vec2 unit_position = unit_positions[gl_VertexIndex]; + vec2 target_position = unit_position * push_constants.content_size; + vec2 clip_position = target_position / push_constants.target_size * 2.0 - 1.0; + + gl_Position = vec4(clip_position, 0.0, 1.0); + texture_coordinates = target_position / push_constants.source_size; +} diff --git a/UI/Qt/WebContentView.cpp b/UI/Qt/WebContentView.cpp index 1b5c447e1f..395568638e 100644 --- a/UI/Qt/WebContentView.cpp +++ b/UI/Qt/WebContentView.cpp @@ -66,7 +66,7 @@ static QWidget* initial_web_content_view_parent([[maybe_unused]] QWidget* window WebContentView::WebContentView(QWidget* window, RefPtr parent_client, size_t page_index, WebContentViewInitialState initial_state) : WebContentViewBase(initial_web_content_view_parent(window)) { -#ifdef AK_OS_MACOS +#ifdef LADYBIRD_QT_USE_METAL_RHI_WIDGET // Keep the QRhiWidget out of the top-level QWidget backing store. If it is // parented before becoming native, Qt propagates its RHI config to the whole // browser window and uploads the full backing store texture on chrome repaints. @@ -85,6 +85,10 @@ WebContentView::WebContentView(QWidget* window, RefPtr WebContentView::current_paintable() const return Paintable { shared_image_buffer, bitmap_size }; } -#ifndef AK_OS_MACOS +void WebContentView::schedule_repaint() +{ +#ifdef LADYBIRD_QT_USE_VULKAN_WINDOW + schedule_vulkan_window_update(); +#else + update(); +#endif +} + +#ifndef LADYBIRD_QT_USE_RHI_WIDGET void WebContentView::paintEvent(QPaintEvent*) { QPainter painter(this); @@ -742,6 +757,9 @@ Optional WebContentView::tab_preview_pixmap(QSize const& maximum_size) void WebContentView::resizeEvent(QResizeEvent* event) { WebContentViewBase::resizeEvent(event); +#ifdef LADYBIRD_QT_USE_VULKAN_WINDOW + update_vulkan_window_geometry(); +#endif update_viewport_size(); handle_resize(); } @@ -888,64 +906,64 @@ void WebContentView::update_cursor(Gfx::Cursor cursor) cursor.visit([this](Gfx::StandardCursor standard_cursor) { switch (standard_cursor) { case Gfx::StandardCursor::Hidden: - setCursor(Qt::BlankCursor); + apply_web_content_cursor(Qt::BlankCursor); break; case Gfx::StandardCursor::Arrow: - setCursor(Qt::ArrowCursor); + apply_web_content_cursor(Qt::ArrowCursor); break; case Gfx::StandardCursor::Crosshair: - setCursor(Qt::CrossCursor); + apply_web_content_cursor(Qt::CrossCursor); break; case Gfx::StandardCursor::IBeam: - setCursor(Qt::IBeamCursor); + apply_web_content_cursor(Qt::IBeamCursor); break; case Gfx::StandardCursor::ResizeHorizontal: - setCursor(Qt::SizeHorCursor); + apply_web_content_cursor(Qt::SizeHorCursor); break; case Gfx::StandardCursor::ResizeVertical: - setCursor(Qt::SizeVerCursor); + apply_web_content_cursor(Qt::SizeVerCursor); break; case Gfx::StandardCursor::ResizeDiagonalTLBR: - setCursor(Qt::SizeFDiagCursor); + apply_web_content_cursor(Qt::SizeFDiagCursor); break; case Gfx::StandardCursor::ResizeDiagonalBLTR: - setCursor(Qt::SizeBDiagCursor); + apply_web_content_cursor(Qt::SizeBDiagCursor); break; case Gfx::StandardCursor::ResizeColumn: - setCursor(Qt::SplitHCursor); + apply_web_content_cursor(Qt::SplitHCursor); break; case Gfx::StandardCursor::ResizeRow: - setCursor(Qt::SplitVCursor); + apply_web_content_cursor(Qt::SplitVCursor); break; case Gfx::StandardCursor::Hand: - setCursor(Qt::PointingHandCursor); + apply_web_content_cursor(Qt::PointingHandCursor); break; case Gfx::StandardCursor::Help: - setCursor(Qt::WhatsThisCursor); + apply_web_content_cursor(Qt::WhatsThisCursor); break; case Gfx::StandardCursor::OpenHand: - setCursor(Qt::OpenHandCursor); + apply_web_content_cursor(Qt::OpenHandCursor); break; case Gfx::StandardCursor::Drag: - setCursor(Qt::ClosedHandCursor); + apply_web_content_cursor(Qt::ClosedHandCursor); break; case Gfx::StandardCursor::DragCopy: - setCursor(Qt::DragCopyCursor); + apply_web_content_cursor(Qt::DragCopyCursor); break; case Gfx::StandardCursor::Move: - setCursor(Qt::DragMoveCursor); + apply_web_content_cursor(Qt::DragMoveCursor); break; case Gfx::StandardCursor::Wait: - setCursor(Qt::BusyCursor); + apply_web_content_cursor(Qt::BusyCursor); break; case Gfx::StandardCursor::Disallowed: - setCursor(Qt::ForbiddenCursor); + apply_web_content_cursor(Qt::ForbiddenCursor); break; case Gfx::StandardCursor::Eyedropper: case Gfx::StandardCursor::Zoom: // FIXME: No corresponding Qt cursors, default to Arrow default: - setCursor(Qt::ArrowCursor); + apply_web_content_cursor(Qt::ArrowCursor); break; } }, [this](Gfx::ImageCursor const& image_cursor) { @@ -964,10 +982,18 @@ void WebContentView::update_cursor(Gfx::Cursor cursor) dbgln("Failed to set cursor: Couldn't create QPixmap from QImage."); return; } - setCursor(QCursor { qpixmap, image_cursor.hotspot.x(), image_cursor.hotspot.y() }); + apply_web_content_cursor(QCursor { qpixmap, image_cursor.hotspot.x(), image_cursor.hotspot.y() }); }); } +void WebContentView::apply_web_content_cursor(QCursor const& cursor) +{ + setCursor(cursor); +#ifdef LADYBIRD_QT_USE_VULKAN_WINDOW + set_vulkan_window_cursor(cursor); +#endif +} + Web::DevicePixelPoint WebContentView::node_picker_position_for(QSinglePointEvent const& event) const { return { event.position().x() * m_device_pixel_ratio, event.position().y() * m_device_pixel_ratio }; @@ -1022,7 +1048,7 @@ bool WebContentView::event(QEvent* event) if (event->type() == QEvent::PaletteChange || event->type() == QEvent::ApplicationPaletteChange || event->type() == QEvent::ThemeChange) { QTimer::singleShot(0, this, [this] { update_palette(); - update(); + schedule_repaint(); }); return WebContentViewBase::event(event); } @@ -1041,6 +1067,84 @@ bool WebContentView::event(QEvent* event) return WebContentViewBase::event(event); } +#ifdef LADYBIRD_QT_USE_VULKAN_WINDOW +bool WebContentView::handle_vulkan_window_event(QEvent* event) +{ + switch (event->type()) { + case QEvent::KeyPress: + keyPressEvent(static_cast(event)); + return true; + case QEvent::KeyRelease: + keyReleaseEvent(static_cast(event)); + return true; + case QEvent::MouseMove: + mouseMoveEvent(static_cast(event)); + return true; + case QEvent::MouseButtonPress: + mousePressEvent(static_cast(event)); + return true; + case QEvent::MouseButtonRelease: + mouseReleaseEvent(static_cast(event)); + return true; + case QEvent::MouseButtonDblClick: + mouseDoubleClickEvent(static_cast(event)); + return true; + case QEvent::Wheel: + wheelEvent(static_cast(event)); + return true; + case QEvent::Leave: + leaveEvent(event); + return true; + case QEvent::FocusIn: + focusInEvent(static_cast(event)); + return true; + case QEvent::FocusOut: + focusOutEvent(static_cast(event)); + return true; + case QEvent::InputMethod: + inputMethodEvent(static_cast(event)); + return true; + case QEvent::DragEnter: + dragEnterEvent(static_cast(event)); + return true; + case QEvent::DragMove: + dragMoveEvent(static_cast(event)); + return true; + case QEvent::DragLeave: + dragLeaveEvent(static_cast(event)); + return true; + case QEvent::Drop: + dropEvent(static_cast(event)); + return true; + case QEvent::NativeGesture: { + auto const& native_gesture_event = *static_cast(event); + if (native_gesture_event.gestureType() == Qt::ZoomNativeGesture) { + Web::PinchEvent pinch_event; + auto const local_position = mapFromGlobal(native_gesture_event.globalPosition()); + pinch_event.position = { local_position.x() * m_device_pixel_ratio, local_position.y() * m_device_pixel_ratio }; + pinch_event.modifiers = get_modifiers_from_qt_keyboard_modifiers(native_gesture_event.modifiers()); + pinch_event.scale_delta = native_gesture_event.value(); + enqueue_input_event(AK::move(pinch_event)); + return true; + } + return false; + } + case QEvent::ShortcutOverride: { + auto* key_event = static_cast(event); + if (is_browser_reserved_shortcut(*key_event)) { + event->ignore(); + return false; + } + + event->accept(); + return true; + } + default: + return false; + } +} +#endif + void WebContentView::enqueue_native_event(Web::MouseEvent::Type type, QSinglePointEvent const& event) { Web::DevicePixelPoint position = { event.position().x() * m_device_pixel_ratio, event.position().y() * m_device_pixel_ratio }; diff --git a/UI/Qt/WebContentView.h b/UI/Qt/WebContentView.h index d95155b664..dfff861a0a 100644 --- a/UI/Qt/WebContentView.h +++ b/UI/Qt/WebContentView.h @@ -24,6 +24,13 @@ #include #ifdef AK_OS_MACOS +# define LADYBIRD_QT_USE_METAL_RHI_WIDGET 1 +# define LADYBIRD_QT_USE_RHI_WIDGET 1 +#elif defined(USE_VULKAN_DMABUF_IMAGES) && QT_VERSION >= QT_VERSION_CHECK(6, 7, 0) +# define LADYBIRD_QT_USE_VULKAN_WINDOW 1 +#endif + +#ifdef LADYBIRD_QT_USE_RHI_WIDGET # include #else # include @@ -31,10 +38,11 @@ class QKeyEvent; class QSinglePointEvent; +class QCursor; namespace Ladybird { -#ifdef AK_OS_MACOS +#ifdef LADYBIRD_QT_USE_RHI_WIDGET using WebContentViewBase = QRhiWidget; #else using WebContentViewBase = QWidget; @@ -53,7 +61,7 @@ public: WebContentView(QWidget* window, RefPtr parent_client = nullptr, size_t page_index = 0, WebContentViewInitialState initial_state = {}); virtual ~WebContentView() override; -#ifndef AK_OS_MACOS +#ifndef LADYBIRD_QT_USE_RHI_WIDGET virtual void paintEvent(QPaintEvent*) override; #endif virtual void resizeEvent(QResizeEvent*) override; @@ -108,7 +116,7 @@ private: virtual Gfx::IntPoint to_content_position(Gfx::IntPoint widget_position) const override; virtual Gfx::IntPoint to_widget_position(Gfx::IntPoint content_position) const override; -#ifdef AK_OS_MACOS +#ifdef LADYBIRD_QT_USE_RHI_WIDGET // ^QRhiWidget virtual void initialize(QRhiCommandBuffer*) override; virtual void render(QRhiCommandBuffer*) override; @@ -124,6 +132,8 @@ private: void update_viewport_size(); void update_cursor(Gfx::Cursor cursor); + void apply_web_content_cursor(QCursor const&); + void schedule_repaint(); void update_compositor_display_metadata(); Web::DevicePixelPoint node_picker_position_for(QSinglePointEvent const&) const; @@ -164,6 +174,24 @@ private: Gfx::SharedImageBuffer const* m_imported_shared_image_buffer { nullptr }; unsigned long m_render_target_pixel_format { 0 }; #endif + +#ifdef LADYBIRD_QT_USE_VULKAN_WINDOW + struct VulkanRenderer; + struct VulkanWindow; + struct VulkanWindowRenderer; + friend struct VulkanWindow; + friend struct VulkanWindowRenderer; + + void create_vulkan_window(); + void destroy_vulkan_window(); + void schedule_vulkan_window_update(); + void update_vulkan_window_geometry(); + void set_vulkan_window_cursor(QCursor const&); + bool handle_vulkan_window_event(QEvent*); + + VulkanWindow* m_vulkan_window { nullptr }; + QWidget* m_vulkan_window_container { nullptr }; +#endif }; } diff --git a/UI/Qt/WebContentViewLinux.cpp b/UI/Qt/WebContentViewLinux.cpp new file mode 100644 index 0000000000..cbbaae7cca --- /dev/null +++ b/UI/Qt/WebContentViewLinux.cpp @@ -0,0 +1,932 @@ +/* + * Copyright (c) 2026-present, the Ladybird developers. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +// clang-format off +#include +// clang-format on + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Ladybird { + +static QByteArrayList vulkan_dmabuf_device_extensions() +{ + return { + "VK_KHR_external_memory", + "VK_KHR_external_memory_fd", + "VK_EXT_external_memory_dma_buf", + "VK_EXT_image_drm_format_modifier", + "VK_KHR_image_format_list", + }; +} + +static QVulkanInstance* vulkan_instance() +{ + static QVulkanInstance* instance = []() -> QVulkanInstance* { + auto* instance = new QVulkanInstance; + auto supported_version = instance->supportedApiVersion(); + if (supported_version >= QVersionNumber(1, 4)) + instance->setApiVersion(QVersionNumber(1, 4)); + else if (supported_version >= QVersionNumber(1, 3)) + instance->setApiVersion(QVersionNumber(1, 3)); + else if (supported_version >= QVersionNumber(1, 2)) + instance->setApiVersion(QVersionNumber(1, 2)); + else if (supported_version >= QVersionNumber(1, 1)) + instance->setApiVersion(QVersionNumber(1, 1)); + else + instance->setApiVersion(QVersionNumber(1, 0)); + + if (!instance->create()) { + delete instance; + return nullptr; + } + return instance; + }(); + return instance; +} + +#include +#include + +static Optional find_memory_type_index(VkPhysicalDevice physical_device, u32 memory_type_bits) +{ + VkPhysicalDeviceMemoryProperties memory_properties; + vkGetPhysicalDeviceMemoryProperties(physical_device, &memory_properties); + + for (u32 i = 0; i < memory_properties.memoryTypeCount; ++i) { + if (memory_type_bits & (1u << i)) + return i; + } + + return {}; +} + +static VkFormat vk_format_from_drm_format(u32 drm_format) +{ + switch (drm_format) { + case DRM_FORMAT_ARGB8888: + return VK_FORMAT_B8G8R8A8_UNORM; + default: + return VK_FORMAT_UNDEFINED; + } +} + +struct WebContentView::VulkanRenderer { + struct DmaBufIdentity { + u64 device { 0 }; + u64 inode { 0 }; + Gfx::IntSize size; + u32 drm_format { 0 }; + size_t pitch { 0 }; + u64 modifier { 0 }; + + bool operator==(DmaBufIdentity const&) const = default; + }; + + struct ImportedTexture { + VkDevice device { VK_NULL_HANDLE }; + VkDescriptorPool descriptor_pool { VK_NULL_HANDLE }; + DmaBufIdentity dmabuf_identity; + VkImage image { VK_NULL_HANDLE }; + VkDeviceMemory memory { VK_NULL_HANDLE }; + VkImageView image_view { VK_NULL_HANDLE }; + VkDescriptorSet descriptor_set { VK_NULL_HANDLE }; + Gfx::IntSize size; + + ~ImportedTexture() + { + if (descriptor_set != VK_NULL_HANDLE) + vkFreeDescriptorSets(device, descriptor_pool, 1, &descriptor_set); + if (image_view != VK_NULL_HANDLE) + vkDestroyImageView(device, image_view, nullptr); + if (image != VK_NULL_HANDLE) + vkDestroyImage(device, image, nullptr); + if (memory != VK_NULL_HANDLE) + vkFreeMemory(device, memory, nullptr); + } + }; + + struct PushConstants { + float target_size[2]; + float content_size[2]; + float source_size[2]; + }; + + ~VulkanRenderer() + { + release(); + } + + void release_imported_textures() + { + for (auto& texture : imported_textures) + texture = nullptr; + next_imported_texture_slot = 0; + } + + void release_pipeline() + { + if (pipeline != VK_NULL_HANDLE) { + vkDestroyPipeline(device, pipeline, nullptr); + pipeline = VK_NULL_HANDLE; + } + render_pass = VK_NULL_HANDLE; + } + + void release() + { + if (device != VK_NULL_HANDLE) + vkDeviceWaitIdle(device); + + release_imported_textures(); + release_pipeline(); + + if (descriptor_pool != VK_NULL_HANDLE) { + vkDestroyDescriptorPool(device, descriptor_pool, nullptr); + descriptor_pool = VK_NULL_HANDLE; + } + if (pipeline_layout != VK_NULL_HANDLE) { + vkDestroyPipelineLayout(device, pipeline_layout, nullptr); + pipeline_layout = VK_NULL_HANDLE; + } + if (descriptor_set_layout != VK_NULL_HANDLE) { + vkDestroyDescriptorSetLayout(device, descriptor_set_layout, nullptr); + descriptor_set_layout = VK_NULL_HANDLE; + } + if (sampler != VK_NULL_HANDLE) { + vkDestroySampler(device, sampler, nullptr); + sampler = VK_NULL_HANDLE; + } + + get_memory_fd_properties = nullptr; + physical_device = VK_NULL_HANDLE; + device = VK_NULL_HANDLE; + } + + bool prepare(QVulkanWindow& window) + { + auto new_device = window.device(); + auto new_physical_device = window.physicalDevice(); + auto new_render_pass = window.defaultRenderPass(); + + if (new_device == VK_NULL_HANDLE || new_physical_device == VK_NULL_HANDLE || new_render_pass == VK_NULL_HANDLE) + return false; + + if (device != new_device || physical_device != new_physical_device) { + release(); + device = new_device; + physical_device = new_physical_device; + get_memory_fd_properties = reinterpret_cast(vkGetDeviceProcAddr(device, "vkGetMemoryFdPropertiesKHR")); + if (!get_memory_fd_properties) + return false; + } + + if (!ensure_sampler() || !ensure_descriptor_set_layout() || !ensure_pipeline_layout() || !ensure_descriptor_pool()) + return false; + + if (render_pass != new_render_pass) { + release_pipeline(); + render_pass = new_render_pass; + } + + return ensure_pipeline(); + } + + bool ensure_sampler() + { + if (sampler != VK_NULL_HANDLE) + return true; + + VkSamplerCreateInfo sampler_info { + .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .magFilter = VK_FILTER_NEAREST, + .minFilter = VK_FILTER_NEAREST, + .mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST, + .addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, + .addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, + .addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, + .mipLodBias = 0.0f, + .anisotropyEnable = VK_FALSE, + .maxAnisotropy = 1.0f, + .compareEnable = VK_FALSE, + .compareOp = VK_COMPARE_OP_ALWAYS, + .minLod = 0.0f, + .maxLod = 0.0f, + .borderColor = VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK, + .unnormalizedCoordinates = VK_FALSE, + }; + return vkCreateSampler(device, &sampler_info, nullptr, &sampler) == VK_SUCCESS; + } + + bool ensure_descriptor_set_layout() + { + if (descriptor_set_layout != VK_NULL_HANDLE) + return true; + + VkDescriptorSetLayoutBinding binding { + .binding = 0, + .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + .descriptorCount = 1, + .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT, + .pImmutableSamplers = nullptr, + }; + VkDescriptorSetLayoutCreateInfo layout_info { + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .bindingCount = 1, + .pBindings = &binding, + }; + return vkCreateDescriptorSetLayout(device, &layout_info, nullptr, &descriptor_set_layout) == VK_SUCCESS; + } + + bool ensure_pipeline_layout() + { + if (pipeline_layout != VK_NULL_HANDLE) + return true; + + VkPushConstantRange push_constant_range { + .stageFlags = VK_SHADER_STAGE_VERTEX_BIT, + .offset = 0, + .size = sizeof(PushConstants), + }; + VkPipelineLayoutCreateInfo pipeline_layout_info { + .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .setLayoutCount = 1, + .pSetLayouts = &descriptor_set_layout, + .pushConstantRangeCount = 1, + .pPushConstantRanges = &push_constant_range, + }; + return vkCreatePipelineLayout(device, &pipeline_layout_info, nullptr, &pipeline_layout) == VK_SUCCESS; + } + + bool ensure_descriptor_pool() + { + if (descriptor_pool != VK_NULL_HANDLE) + return true; + + VkDescriptorPoolSize pool_size { + .type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + .descriptorCount = 8, + }; + VkDescriptorPoolCreateInfo pool_info { + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, + .pNext = nullptr, + .flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT, + .maxSets = 8, + .poolSizeCount = 1, + .pPoolSizes = &pool_size, + }; + return vkCreateDescriptorPool(device, &pool_info, nullptr, &descriptor_pool) == VK_SUCCESS; + } + + VkShaderModule create_shader_module(u32 const* code, size_t code_size) + { + VkShaderModuleCreateInfo shader_module_info { + .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .codeSize = code_size, + .pCode = code, + }; + + VkShaderModule shader_module { VK_NULL_HANDLE }; + if (vkCreateShaderModule(device, &shader_module_info, nullptr, &shader_module) != VK_SUCCESS) + return VK_NULL_HANDLE; + return shader_module; + } + + bool ensure_pipeline() + { + if (pipeline != VK_NULL_HANDLE) + return true; + + auto vertex_shader = create_shader_module(webcontent_vert_shader_spv, sizeof(webcontent_vert_shader_spv)); + auto fragment_shader = create_shader_module(webcontent_frag_shader_spv, sizeof(webcontent_frag_shader_spv)); + if (vertex_shader == VK_NULL_HANDLE || fragment_shader == VK_NULL_HANDLE) { + if (vertex_shader != VK_NULL_HANDLE) + vkDestroyShaderModule(device, vertex_shader, nullptr); + if (fragment_shader != VK_NULL_HANDLE) + vkDestroyShaderModule(device, fragment_shader, nullptr); + return false; + } + + VkPipelineShaderStageCreateInfo shader_stages[] { + { + .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .stage = VK_SHADER_STAGE_VERTEX_BIT, + .module = vertex_shader, + .pName = "main", + .pSpecializationInfo = nullptr, + }, + { + .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .stage = VK_SHADER_STAGE_FRAGMENT_BIT, + .module = fragment_shader, + .pName = "main", + .pSpecializationInfo = nullptr, + }, + }; + + VkPipelineVertexInputStateCreateInfo vertex_input { + .sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .vertexBindingDescriptionCount = 0, + .pVertexBindingDescriptions = nullptr, + .vertexAttributeDescriptionCount = 0, + .pVertexAttributeDescriptions = nullptr, + }; + VkPipelineInputAssemblyStateCreateInfo input_assembly { + .sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP, + .primitiveRestartEnable = VK_FALSE, + }; + VkPipelineViewportStateCreateInfo viewport_state { + .sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .viewportCount = 1, + .pViewports = nullptr, + .scissorCount = 1, + .pScissors = nullptr, + }; + VkPipelineRasterizationStateCreateInfo rasterization_state { + .sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .depthClampEnable = VK_FALSE, + .rasterizerDiscardEnable = VK_FALSE, + .polygonMode = VK_POLYGON_MODE_FILL, + .cullMode = VK_CULL_MODE_NONE, + .frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE, + .depthBiasEnable = VK_FALSE, + .depthBiasConstantFactor = 0.0f, + .depthBiasClamp = 0.0f, + .depthBiasSlopeFactor = 0.0f, + .lineWidth = 1.0f, + }; + VkPipelineMultisampleStateCreateInfo multisample_state { + .sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .rasterizationSamples = VK_SAMPLE_COUNT_1_BIT, + .sampleShadingEnable = VK_FALSE, + .minSampleShading = 1.0f, + .pSampleMask = nullptr, + .alphaToCoverageEnable = VK_FALSE, + .alphaToOneEnable = VK_FALSE, + }; + VkPipelineColorBlendAttachmentState color_blend_attachment { + .blendEnable = VK_FALSE, + .srcColorBlendFactor = VK_BLEND_FACTOR_ONE, + .dstColorBlendFactor = VK_BLEND_FACTOR_ZERO, + .colorBlendOp = VK_BLEND_OP_ADD, + .srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE, + .dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO, + .alphaBlendOp = VK_BLEND_OP_ADD, + .colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT, + }; + VkPipelineColorBlendStateCreateInfo color_blend_state { + .sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .logicOpEnable = VK_FALSE, + .logicOp = VK_LOGIC_OP_COPY, + .attachmentCount = 1, + .pAttachments = &color_blend_attachment, + .blendConstants = { 0.0f, 0.0f, 0.0f, 0.0f }, + }; + VkDynamicState dynamic_states[] { + VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_SCISSOR, + }; + VkPipelineDynamicStateCreateInfo dynamic_state { + .sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .dynamicStateCount = 2, + .pDynamicStates = dynamic_states, + }; + VkPipelineDepthStencilStateCreateInfo depth_stencil_state { + .sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .depthTestEnable = VK_FALSE, + .depthWriteEnable = VK_FALSE, + .depthCompareOp = VK_COMPARE_OP_ALWAYS, + .depthBoundsTestEnable = VK_FALSE, + .stencilTestEnable = VK_FALSE, + .front = {}, + .back = {}, + .minDepthBounds = 0.0f, + .maxDepthBounds = 1.0f, + }; + VkGraphicsPipelineCreateInfo pipeline_info { + .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .stageCount = 2, + .pStages = shader_stages, + .pVertexInputState = &vertex_input, + .pInputAssemblyState = &input_assembly, + .pTessellationState = nullptr, + .pViewportState = &viewport_state, + .pRasterizationState = &rasterization_state, + .pMultisampleState = &multisample_state, + .pDepthStencilState = &depth_stencil_state, + .pColorBlendState = &color_blend_state, + .pDynamicState = &dynamic_state, + .layout = pipeline_layout, + .renderPass = render_pass, + .subpass = 0, + .basePipelineHandle = VK_NULL_HANDLE, + .basePipelineIndex = -1, + }; + + auto result = vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipeline_info, nullptr, &pipeline); + vkDestroyShaderModule(device, vertex_shader, nullptr); + vkDestroyShaderModule(device, fragment_shader, nullptr); + return result == VK_SUCCESS; + } + + static Optional dmabuf_identity_for(Gfx::LinuxDmaBufHandle const& dmabuf) + { + struct stat statbuf {}; + if (fstat(dmabuf.file.fd(), &statbuf) < 0) + return {}; + + return DmaBufIdentity { + .device = static_cast(statbuf.st_dev), + .inode = static_cast(statbuf.st_ino), + .size = dmabuf.size, + .drm_format = dmabuf.drm_format, + .pitch = dmabuf.pitch, + .modifier = dmabuf.modifier, + }; + } + + ImportedTexture* imported_texture_for(Gfx::SharedImageBuffer const& shared_image_buffer) + { + auto const* dmabuf = shared_image_buffer.linux_dmabuf_handle(); + if (!dmabuf) + return nullptr; + + auto dmabuf_identity = dmabuf_identity_for(*dmabuf); + if (!dmabuf_identity.has_value()) + return nullptr; + + for (auto& imported_texture : imported_textures) { + if (imported_texture && imported_texture->dmabuf_identity == *dmabuf_identity) + return imported_texture.ptr(); + } + + auto imported_texture = import_texture(*dmabuf, *dmabuf_identity); + if (!imported_texture) + return nullptr; + + auto slot = next_imported_texture_slot; + imported_textures[slot] = AK::move(imported_texture); + next_imported_texture_slot = (next_imported_texture_slot + 1) % imported_texture_slot_count; + return imported_textures[slot].ptr(); + } + + OwnPtr import_texture(Gfx::LinuxDmaBufHandle const& dmabuf, DmaBufIdentity const& dmabuf_identity) + { + auto vk_format = vk_format_from_drm_format(dmabuf.drm_format); + if (vk_format == VK_FORMAT_UNDEFINED || dmabuf.modifier != DRM_FORMAT_MOD_LINEAR) + return {}; + + VkImageDrmFormatModifierListCreateInfoEXT modifier_list { + .sType = VK_STRUCTURE_TYPE_IMAGE_DRM_FORMAT_MODIFIER_LIST_CREATE_INFO_EXT, + .pNext = nullptr, + .drmFormatModifierCount = 1, + .pDrmFormatModifiers = &dmabuf.modifier, + }; + VkExternalMemoryImageCreateInfo external_memory_image_info { + .sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO, + .pNext = &modifier_list, + .handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT, + }; + VkImageCreateInfo image_info { + .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, + .pNext = &external_memory_image_info, + .flags = 0, + .imageType = VK_IMAGE_TYPE_2D, + .format = vk_format, + .extent = { + .width = static_cast(dmabuf.size.width()), + .height = static_cast(dmabuf.size.height()), + .depth = 1, + }, + .mipLevels = 1, + .arrayLayers = 1, + .samples = VK_SAMPLE_COUNT_1_BIT, + .tiling = VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT, + .usage = VK_IMAGE_USAGE_SAMPLED_BIT, + .sharingMode = VK_SHARING_MODE_EXCLUSIVE, + .queueFamilyIndexCount = 0, + .pQueueFamilyIndices = nullptr, + .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, + }; + + VkImage image { VK_NULL_HANDLE }; + if (vkCreateImage(device, &image_info, nullptr, &image) != VK_SUCCESS) + return {}; + + VkImageSubresource subresource { VK_IMAGE_ASPECT_MEMORY_PLANE_0_BIT_EXT, 0, 0 }; + VkSubresourceLayout subresource_layout {}; + vkGetImageSubresourceLayout(device, image, &subresource, &subresource_layout); + if (subresource_layout.rowPitch != dmabuf.pitch) { + vkDestroyImage(device, image, nullptr); + return {}; + } + + VkMemoryRequirements memory_requirements; + vkGetImageMemoryRequirements(device, image, &memory_requirements); + + VkMemoryFdPropertiesKHR memory_fd_properties { + .sType = VK_STRUCTURE_TYPE_MEMORY_FD_PROPERTIES_KHR, + .pNext = nullptr, + .memoryTypeBits = 0, + }; + if (get_memory_fd_properties(device, VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT, dmabuf.file.fd(), &memory_fd_properties) != VK_SUCCESS) { + vkDestroyImage(device, image, nullptr); + return {}; + } + + auto memory_type_index = find_memory_type_index(physical_device, memory_requirements.memoryTypeBits & memory_fd_properties.memoryTypeBits); + if (!memory_type_index.has_value()) { + vkDestroyImage(device, image, nullptr); + return {}; + } + + int imported_fd = dup(dmabuf.file.fd()); + if (imported_fd < 0) { + vkDestroyImage(device, image, nullptr); + return {}; + } + + VkMemoryDedicatedAllocateInfo dedicated_allocate_info { + .sType = VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO, + .pNext = nullptr, + .image = image, + .buffer = VK_NULL_HANDLE, + }; + VkImportMemoryFdInfoKHR import_memory_fd_info { + .sType = VK_STRUCTURE_TYPE_IMPORT_MEMORY_FD_INFO_KHR, + .pNext = &dedicated_allocate_info, + .handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT, + .fd = imported_fd, + }; + VkMemoryAllocateInfo memory_allocate_info { + .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, + .pNext = &import_memory_fd_info, + .allocationSize = memory_requirements.size, + .memoryTypeIndex = *memory_type_index, + }; + + VkDeviceMemory memory { VK_NULL_HANDLE }; + if (vkAllocateMemory(device, &memory_allocate_info, nullptr, &memory) != VK_SUCCESS) { + ::close(imported_fd); + vkDestroyImage(device, image, nullptr); + return {}; + } + + if (vkBindImageMemory(device, image, memory, 0) != VK_SUCCESS) { + vkFreeMemory(device, memory, nullptr); + vkDestroyImage(device, image, nullptr); + return {}; + } + + VkImageViewCreateInfo image_view_info { + .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .image = image, + .viewType = VK_IMAGE_VIEW_TYPE_2D, + .format = vk_format, + .components = { + .r = VK_COMPONENT_SWIZZLE_IDENTITY, + .g = VK_COMPONENT_SWIZZLE_IDENTITY, + .b = VK_COMPONENT_SWIZZLE_IDENTITY, + .a = VK_COMPONENT_SWIZZLE_IDENTITY, + }, + .subresourceRange = { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1, + }, + }; + VkImageView image_view { VK_NULL_HANDLE }; + if (vkCreateImageView(device, &image_view_info, nullptr, &image_view) != VK_SUCCESS) { + vkFreeMemory(device, memory, nullptr); + vkDestroyImage(device, image, nullptr); + return {}; + } + + VkDescriptorSet descriptor_set { VK_NULL_HANDLE }; + VkDescriptorSetAllocateInfo descriptor_set_allocate_info { + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, + .pNext = nullptr, + .descriptorPool = descriptor_pool, + .descriptorSetCount = 1, + .pSetLayouts = &descriptor_set_layout, + }; + if (vkAllocateDescriptorSets(device, &descriptor_set_allocate_info, &descriptor_set) != VK_SUCCESS) { + vkDestroyImageView(device, image_view, nullptr); + vkFreeMemory(device, memory, nullptr); + vkDestroyImage(device, image, nullptr); + return {}; + } + + VkDescriptorImageInfo descriptor_image_info { + .sampler = sampler, + .imageView = image_view, + .imageLayout = VK_IMAGE_LAYOUT_GENERAL, + }; + VkWriteDescriptorSet descriptor_write { + .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, + .pNext = nullptr, + .dstSet = descriptor_set, + .dstBinding = 0, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + .pImageInfo = &descriptor_image_info, + .pBufferInfo = nullptr, + .pTexelBufferView = nullptr, + }; + vkUpdateDescriptorSets(device, 1, &descriptor_write, 0, nullptr); + + auto imported_texture = make(); + imported_texture->device = device; + imported_texture->descriptor_pool = descriptor_pool; + imported_texture->dmabuf_identity = dmabuf_identity; + imported_texture->image = image; + imported_texture->memory = memory; + imported_texture->image_view = image_view; + imported_texture->descriptor_set = descriptor_set; + imported_texture->size = dmabuf.size; + return imported_texture; + } + + bool render(VkCommandBuffer command_buffer, Gfx::SharedImageBuffer const& shared_image_buffer, Gfx::IntSize bitmap_size, QSize target_size) + { + auto* imported_texture = imported_texture_for(shared_image_buffer); + if (!imported_texture) + return false; + + if (command_buffer == VK_NULL_HANDLE) + return false; + + auto content_width = min(bitmap_size.width(), target_size.width()); + auto content_height = min(bitmap_size.height(), target_size.height()); + if (content_width <= 0 || content_height <= 0) + return false; + + PushConstants push_constants { + { static_cast(target_size.width()), static_cast(target_size.height()) }, + { static_cast(content_width), static_cast(content_height) }, + { static_cast(imported_texture->size.width()), static_cast(imported_texture->size.height()) }, + }; + + VkViewport viewport { + .x = 0.0f, + .y = 0.0f, + .width = static_cast(target_size.width()), + .height = static_cast(target_size.height()), + .minDepth = 0.0f, + .maxDepth = 1.0f, + }; + VkRect2D scissor { + .offset = { 0, 0 }, + .extent = { static_cast(target_size.width()), static_cast(target_size.height()) }, + }; + + vkCmdBindPipeline(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); + vkCmdSetViewport(command_buffer, 0, 1, &viewport); + vkCmdSetScissor(command_buffer, 0, 1, &scissor); + vkCmdBindDescriptorSets(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layout, 0, 1, &imported_texture->descriptor_set, 0, nullptr); + vkCmdPushConstants(command_buffer, pipeline_layout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(push_constants), &push_constants); + vkCmdDraw(command_buffer, 4, 1, 0, 0); + return true; + } + + VkDevice device { VK_NULL_HANDLE }; + VkPhysicalDevice physical_device { VK_NULL_HANDLE }; + VkRenderPass render_pass { VK_NULL_HANDLE }; + VkSampler sampler { VK_NULL_HANDLE }; + VkDescriptorSetLayout descriptor_set_layout { VK_NULL_HANDLE }; + VkDescriptorPool descriptor_pool { VK_NULL_HANDLE }; + VkPipelineLayout pipeline_layout { VK_NULL_HANDLE }; + VkPipeline pipeline { VK_NULL_HANDLE }; + PFN_vkGetMemoryFdPropertiesKHR get_memory_fd_properties { nullptr }; + static constexpr size_t imported_texture_slot_count { 2 }; + OwnPtr imported_textures[imported_texture_slot_count]; + size_t next_imported_texture_slot { 0 }; +}; + +struct WebContentView::VulkanWindowRenderer final : public QVulkanWindowRenderer { + VulkanWindowRenderer(WebContentView& view, QVulkanWindow& window) + : m_view(view) + , m_window(window) + { + } + + virtual void releaseSwapChainResources() override + { + m_renderer.release_pipeline(); + } + + virtual void releaseResources() override + { + m_renderer.release(); + } + + virtual void logicalDeviceLost() override + { + m_renderer.release(); + } + + virtual void physicalDeviceLost() override + { + m_renderer.release(); + } + + virtual void startNextFrame() override + { + auto command_buffer = m_window.currentCommandBuffer(); + auto target_size = m_window.swapChainImageSize(); + auto render_pass = m_window.defaultRenderPass(); + auto framebuffer = m_window.currentFramebuffer(); + + if (command_buffer == VK_NULL_HANDLE || render_pass == VK_NULL_HANDLE || framebuffer == VK_NULL_HANDLE || target_size.isEmpty()) { + m_window.frameReady(); + return; + } + + auto background_color = m_view.page_background_color(); + VkClearColorValue clear_color { + { + static_cast(background_color.red()) / 255.0f, + static_cast(background_color.green()) / 255.0f, + static_cast(background_color.blue()) / 255.0f, + 1.0f, + }, + }; + VkClearDepthStencilValue clear_depth_stencil { 1.0f, 0 }; + VkClearValue clear_values[2] {}; + clear_values[0].color = clear_color; + clear_values[1].depthStencil = clear_depth_stencil; + + VkRenderPassBeginInfo render_pass_begin_info { + .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, + .pNext = nullptr, + .renderPass = render_pass, + .framebuffer = framebuffer, + .renderArea = { + .offset = { 0, 0 }, + .extent = { + .width = static_cast(target_size.width()), + .height = static_cast(target_size.height()), + }, + }, + .clearValueCount = 2, + .pClearValues = clear_values, + }; + vkCmdBeginRenderPass(command_buffer, &render_pass_begin_info, VK_SUBPASS_CONTENTS_INLINE); + + auto paintable = m_view.current_paintable(); + if (paintable.has_value() && !paintable->bitmap_size.is_empty() && m_renderer.prepare(m_window)) + m_renderer.render(command_buffer, *paintable->shared_image_buffer, paintable->bitmap_size, target_size); + + vkCmdEndRenderPass(command_buffer); + m_window.frameReady(); + } + + WebContentView& m_view; + QVulkanWindow& m_window; + VulkanRenderer m_renderer; +}; + +struct WebContentView::VulkanWindow final : public QVulkanWindow { + explicit VulkanWindow(WebContentView& view) + : m_view(&view) + { + } + + void clear_view() + { + m_view = nullptr; + } + + virtual QVulkanWindowRenderer* createRenderer() override + { + if (!m_view) + return nullptr; + return new VulkanWindowRenderer(*m_view, *this); + } + + virtual bool event(QEvent* event) override + { + if (m_view && m_view->handle_vulkan_window_event(event)) + return true; + return QVulkanWindow::event(event); + } + + WebContentView* m_view { nullptr }; +}; + +void WebContentView::create_vulkan_window() +{ + auto* instance = vulkan_instance(); + if (!instance) + return; + + m_vulkan_window = new VulkanWindow(*this); + m_vulkan_window->setVulkanInstance(instance); + m_vulkan_window->setDeviceExtensions(vulkan_dmabuf_device_extensions()); + m_vulkan_window->setPreferredColorFormats({ + VK_FORMAT_B8G8R8A8_UNORM, + VK_FORMAT_R8G8B8A8_UNORM, + }); + + m_vulkan_window_container = QWidget::createWindowContainer(m_vulkan_window, this); + m_vulkan_window_container->setAcceptDrops(true); + m_vulkan_window_container->setFocusPolicy(Qt::FocusPolicy::StrongFocus); + m_vulkan_window_container->setMouseTracking(true); + m_vulkan_window_container->setGeometry(rect()); + setFocusProxy(m_vulkan_window_container); +} + +void WebContentView::destroy_vulkan_window() +{ + setFocusProxy(nullptr); + if (m_vulkan_window) + m_vulkan_window->clear_view(); + + if (m_vulkan_window_container) { + delete m_vulkan_window_container; + m_vulkan_window_container = nullptr; + m_vulkan_window = nullptr; + return; + } + + delete m_vulkan_window; + m_vulkan_window = nullptr; +} + +void WebContentView::schedule_vulkan_window_update() +{ + if (m_vulkan_window) { + m_vulkan_window->requestUpdate(); + return; + } + + update(); +} + +void WebContentView::update_vulkan_window_geometry() +{ + if (m_vulkan_window_container) + m_vulkan_window_container->setGeometry(rect()); +} + +void WebContentView::set_vulkan_window_cursor(QCursor const& cursor) +{ + if (m_vulkan_window_container) + m_vulkan_window_container->setCursor(cursor); + if (m_vulkan_window) + m_vulkan_window->setCursor(cursor); +} + +}