5f0cda4ad4
This arrangement allows the backend texture to outlive the YCbCr SkImage. Change-Id: I34939d05bf1091c8efcacb687dc1900729d4cbe5 Reviewed-on: https://skia-review.googlesource.com/c/skia/+/296478 Reviewed-by: Greg Daniel <egdaniel@google.com> Commit-Queue: Robert Phillips <robertphillips@google.com>
197 lines
7.6 KiB
C++
197 lines
7.6 KiB
C++
/*
|
|
* 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/GrContext.h"
|
|
#include "src/gpu/GrContextPriv.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*) fContext->priv().getGpu();
|
|
}
|
|
|
|
VkYcbcrSamplerHelper::VkYcbcrSamplerHelper(GrContext* context) : fContext(context) {
|
|
SkASSERT_RELEASE(context->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;
|
|
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(fImageMemory, 0 /* offset */, requirements.size, 0 /* flags */);
|
|
GrVkImageInfo imageInfo(fImage, alloc, VK_IMAGE_TILING_LINEAR, VK_IMAGE_LAYOUT_UNDEFINED,
|
|
vkImageInfo.format, 1 /* levelCount */, VK_QUEUE_FAMILY_IGNORED,
|
|
GrProtected::kNo, ycbcrInfo);
|
|
|
|
fTexture = GrBackendTexture(width, height, imageInfo);
|
|
return true;
|
|
}
|
|
|
|
#endif // SK_VULKAN
|