| /* |
| * Copyright 2020 Google Inc. |
| * |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #include "tools/gpu/vk/VkYcbcrSamplerHelper.h" |
| |
| #ifdef SK_VULKAN |
| |
| #include "include/gpu/GrDirectContext.h" |
| #include "src/gpu/GrDirectContextPriv.h" |
| #include "src/gpu/vk/GrVkGpu.h" |
| #include "src/gpu/vk/GrVkUtil.h" |
| |
| int VkYcbcrSamplerHelper::GetExpectedY(int x, int y, int width, int height) { |
| return 16 + (x + y) * 219 / (width + height - 2); |
| } |
| |
| std::pair<int, int> VkYcbcrSamplerHelper::GetExpectedUV(int x, int y, int width, int height) { |
| return { 16 + x * 224 / (width - 1), 16 + y * 224 / (height - 1) }; |
| } |
| |
| GrVkGpu* VkYcbcrSamplerHelper::vkGpu() { |
| return (GrVkGpu*) fDContext->priv().getGpu(); |
| } |
| |
| VkYcbcrSamplerHelper::VkYcbcrSamplerHelper(GrDirectContext* dContext) : fDContext(dContext) { |
| SkASSERT_RELEASE(dContext->backend() == GrBackendApi::kVulkan); |
| } |
| |
| VkYcbcrSamplerHelper::~VkYcbcrSamplerHelper() { |
| GrVkGpu* vkGpu = this->vkGpu(); |
| |
| if (fImage != VK_NULL_HANDLE) { |
| GR_VK_CALL(vkGpu->vkInterface(), DestroyImage(vkGpu->device(), fImage, nullptr)); |
| fImage = VK_NULL_HANDLE; |
| } |
| if (fImageMemory != VK_NULL_HANDLE) { |
| GR_VK_CALL(vkGpu->vkInterface(), FreeMemory(vkGpu->device(), fImageMemory, nullptr)); |
| fImageMemory = VK_NULL_HANDLE; |
| } |
| } |
| |
| bool VkYcbcrSamplerHelper::isYCbCrSupported() { |
| GrVkGpu* vkGpu = this->vkGpu(); |
| |
| return vkGpu->vkCaps().supportsYcbcrConversion(); |
| } |
| |
| bool VkYcbcrSamplerHelper::createBackendTexture(uint32_t width, uint32_t height) { |
| GrVkGpu* vkGpu = this->vkGpu(); |
| VkResult result; |
| |
| // Verify that the image format is supported. |
| VkFormatProperties formatProperties; |
| GR_VK_CALL(vkGpu->vkInterface(), |
| GetPhysicalDeviceFormatProperties(vkGpu->physicalDevice(), |
| VK_FORMAT_G8_B8R8_2PLANE_420_UNORM, |
| &formatProperties)); |
| if (!(formatProperties.linearTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT)) { |
| // VK_FORMAT_G8_B8R8_2PLANE_420_UNORM is not supported |
| return false; |
| } |
| |
| // Create YCbCr image. |
| VkImageCreateInfo vkImageInfo = {}; |
| vkImageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; |
| vkImageInfo.imageType = VK_IMAGE_TYPE_2D; |
| vkImageInfo.format = VK_FORMAT_G8_B8R8_2PLANE_420_UNORM; |
| vkImageInfo.extent = VkExtent3D{width, height, 1}; |
| vkImageInfo.mipLevels = 1; |
| vkImageInfo.arrayLayers = 1; |
| vkImageInfo.samples = VK_SAMPLE_COUNT_1_BIT; |
| vkImageInfo.tiling = VK_IMAGE_TILING_LINEAR; |
| vkImageInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | |
| VK_IMAGE_USAGE_TRANSFER_SRC_BIT; |
| vkImageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; |
| vkImageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; |
| |
| SkASSERT(fImage == VK_NULL_HANDLE); |
| GR_VK_CALL_RESULT(vkGpu, result, CreateImage(vkGpu->device(), &vkImageInfo, nullptr, &fImage)); |
| if (result != VK_SUCCESS) { |
| return false; |
| } |
| |
| VkMemoryRequirements requirements; |
| GR_VK_CALL(vkGpu->vkInterface(), GetImageMemoryRequirements(vkGpu->device(), |
| fImage, |
| &requirements)); |
| |
| uint32_t memoryTypeIndex = 0; |
| bool foundHeap = false; |
| VkPhysicalDeviceMemoryProperties phyDevMemProps; |
| GR_VK_CALL(vkGpu->vkInterface(), GetPhysicalDeviceMemoryProperties(vkGpu->physicalDevice(), |
| &phyDevMemProps)); |
| for (uint32_t i = 0; i < phyDevMemProps.memoryTypeCount && !foundHeap; ++i) { |
| if (requirements.memoryTypeBits & (1 << i)) { |
| // Map host-visible memory. |
| if (phyDevMemProps.memoryTypes[i].propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) { |
| memoryTypeIndex = i; |
| foundHeap = true; |
| } |
| } |
| } |
| if (!foundHeap) { |
| return false; |
| } |
| |
| VkMemoryAllocateInfo allocInfo = {}; |
| allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; |
| allocInfo.allocationSize = requirements.size; |
| allocInfo.memoryTypeIndex = memoryTypeIndex; |
| |
| SkASSERT(fImageMemory == VK_NULL_HANDLE); |
| GR_VK_CALL_RESULT(vkGpu, result, AllocateMemory(vkGpu->device(), &allocInfo, |
| nullptr, &fImageMemory)); |
| if (result != VK_SUCCESS) { |
| return false; |
| } |
| |
| void* mappedBuffer; |
| GR_VK_CALL_RESULT(vkGpu, result, MapMemory(vkGpu->device(), fImageMemory, 0u, |
| requirements.size, 0u, &mappedBuffer)); |
| if (result != VK_SUCCESS) { |
| return false; |
| } |
| |
| // Write Y channel. |
| VkImageSubresource subresource; |
| subresource.aspectMask = VK_IMAGE_ASPECT_PLANE_0_BIT; |
| subresource.mipLevel = 0; |
| subresource.arrayLayer = 0; |
| |
| VkSubresourceLayout yLayout; |
| GR_VK_CALL(vkGpu->vkInterface(), GetImageSubresourceLayout(vkGpu->device(), fImage, |
| &subresource, &yLayout)); |
| uint8_t* bufferData = reinterpret_cast<uint8_t*>(mappedBuffer) + yLayout.offset; |
| for (size_t y = 0; y < height; ++y) { |
| for (size_t x = 0; x < width; ++x) { |
| bufferData[y * yLayout.rowPitch + x] = GetExpectedY(x, y, width, height); |
| } |
| } |
| |
| // Write UV channels. |
| subresource.aspectMask = VK_IMAGE_ASPECT_PLANE_1_BIT; |
| VkSubresourceLayout uvLayout; |
| GR_VK_CALL(vkGpu->vkInterface(), GetImageSubresourceLayout(vkGpu->device(), fImage, |
| &subresource, &uvLayout)); |
| bufferData = reinterpret_cast<uint8_t*>(mappedBuffer) + uvLayout.offset; |
| for (size_t y = 0; y < height / 2; ++y) { |
| for (size_t x = 0; x < width / 2; ++x) { |
| auto [u, v] = GetExpectedUV(2*x, 2*y, width, height); |
| bufferData[y * uvLayout.rowPitch + x * 2] = u; |
| bufferData[y * uvLayout.rowPitch + x * 2 + 1] = v; |
| } |
| } |
| |
| VkMappedMemoryRange flushRange; |
| flushRange.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE; |
| flushRange.pNext = nullptr; |
| flushRange.memory = fImageMemory; |
| flushRange.offset = 0; |
| flushRange.size = VK_WHOLE_SIZE; |
| GR_VK_CALL_RESULT(vkGpu, result, FlushMappedMemoryRanges(vkGpu->device(), 1, &flushRange)); |
| if (result != VK_SUCCESS) { |
| return false; |
| } |
| GR_VK_CALL(vkGpu->vkInterface(), UnmapMemory(vkGpu->device(), fImageMemory)); |
| |
| // Bind image memory. |
| GR_VK_CALL_RESULT(vkGpu, result, BindImageMemory(vkGpu->device(), fImage, fImageMemory, 0u)); |
| if (result != VK_SUCCESS) { |
| return false; |
| } |
| |
| // Wrap the image into SkImage. |
| GrVkYcbcrConversionInfo ycbcrInfo = {vkImageInfo.format, |
| /*externalFormat=*/0, |
| VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_709, |
| VK_SAMPLER_YCBCR_RANGE_ITU_NARROW, |
| VK_CHROMA_LOCATION_COSITED_EVEN, |
| VK_CHROMA_LOCATION_COSITED_EVEN, |
| VK_FILTER_LINEAR, |
| false, |
| formatProperties.linearTilingFeatures}; |
| GrVkAlloc alloc; |
| alloc.fMemory = fImageMemory; |
| alloc.fOffset = 0; |
| alloc.fSize = requirements.size; |
| |
| GrVkImageInfo imageInfo = {fImage, |
| alloc, |
| VK_IMAGE_TILING_LINEAR, |
| VK_IMAGE_LAYOUT_UNDEFINED, |
| vkImageInfo.format, |
| vkImageInfo.usage, |
| 1 /* sample count */, |
| 1 /* levelCount */, |
| VK_QUEUE_FAMILY_IGNORED, |
| GrProtected::kNo, |
| ycbcrInfo}; |
| |
| fTexture = GrBackendTexture(width, height, imageInfo); |
| return true; |
| } |
| |
| #endif // SK_VULKAN |