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); +} + +}