blob: 60c0be1ddc05c08e21ec95f04cfab0822ea400c6 [file] [log] [blame]
/*
* 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/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*) 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;
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