/* * 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(); } bool can_render(Gfx::SharedImageBuffer const& shared_image_buffer) { return imported_texture_for(shared_image_buffer); } 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 paintable = m_view.current_paintable(); bool can_render = paintable.has_value() && !paintable->bitmap_size.is_empty() && m_renderer.prepare(m_window) && m_renderer.can_render(*paintable->shared_image_buffer); if (!can_render) { if (m_view.m_vulkan_window_container) m_view.m_vulkan_window_container->hide(); m_view.update(); 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); bool rendered = m_renderer.render(command_buffer, *paintable->shared_image_buffer, paintable->bitmap_size, target_size); vkCmdEndRenderPass(command_buffer); if (!rendered) { if (m_view.m_vulkan_window_container) m_view.m_vulkan_window_container->hide(); m_view.update(); } 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()); m_vulkan_window_container->hide(); 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; } bool WebContentView::current_paintable_can_use_vulkan_window() const { auto paintable = current_paintable(); if (!paintable.has_value()) return false; return paintable->shared_image_buffer->linux_dmabuf_handle(); } void WebContentView::schedule_vulkan_window_update() { if (m_vulkan_window) { if (!current_paintable_can_use_vulkan_window()) { if (m_vulkan_window_container) m_vulkan_window_container->hide(); update(); return; } if (m_vulkan_window_container && !m_vulkan_window_container->isVisible()) { m_vulkan_window_container->setGeometry(rect()); m_vulkan_window_container->show(); } 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); } }