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.
932 lines
33 KiB
C++
932 lines
33 KiB
C++
/*
|
|
* Copyright (c) 2026-present, the Ladybird developers.
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
// clang-format off
|
|
#include <vulkan/vulkan.h>
|
|
// clang-format on
|
|
|
|
#include <AK/Math.h>
|
|
#include <AK/Optional.h>
|
|
#include <LibGfx/SharedImageBuffer.h>
|
|
#include <UI/Qt/WebContentView.h>
|
|
|
|
#include <QByteArrayList>
|
|
#include <QCursor>
|
|
#include <QVersionNumber>
|
|
#include <QVulkanInstance>
|
|
#include <QVulkanWindow>
|
|
#include <QWidget>
|
|
#include <libdrm/drm_fourcc.h>
|
|
#include <sys/stat.h>
|
|
#include <unistd.h>
|
|
|
|
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 <WebContentViewLinuxFragShader.h>
|
|
#include <WebContentViewLinuxVertShader.h>
|
|
|
|
static Optional<u32> 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<PFN_vkGetMemoryFdPropertiesKHR>(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<DmaBufIdentity> dmabuf_identity_for(Gfx::LinuxDmaBufHandle const& dmabuf)
|
|
{
|
|
struct stat statbuf {};
|
|
if (fstat(dmabuf.file.fd(), &statbuf) < 0)
|
|
return {};
|
|
|
|
return DmaBufIdentity {
|
|
.device = static_cast<u64>(statbuf.st_dev),
|
|
.inode = static_cast<u64>(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<ImportedTexture> 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<u32>(dmabuf.size.width()),
|
|
.height = static_cast<u32>(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<ImportedTexture>();
|
|
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<float>(target_size.width()), static_cast<float>(target_size.height()) },
|
|
{ static_cast<float>(content_width), static_cast<float>(content_height) },
|
|
{ static_cast<float>(imported_texture->size.width()), static_cast<float>(imported_texture->size.height()) },
|
|
};
|
|
|
|
VkViewport viewport {
|
|
.x = 0.0f,
|
|
.y = 0.0f,
|
|
.width = static_cast<float>(target_size.width()),
|
|
.height = static_cast<float>(target_size.height()),
|
|
.minDepth = 0.0f,
|
|
.maxDepth = 1.0f,
|
|
};
|
|
VkRect2D scissor {
|
|
.offset = { 0, 0 },
|
|
.extent = { static_cast<u32>(target_size.width()), static_cast<u32>(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<ImportedTexture> 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<float>(background_color.red()) / 255.0f,
|
|
static_cast<float>(background_color.green()) / 255.0f,
|
|
static_cast<float>(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<u32>(target_size.width()),
|
|
.height = static_cast<u32>(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);
|
|
}
|
|
|
|
}
|