Enable YCbCr sampler support on platforms other than Android

Previously YCbCr Vulkan samplers were supported only on Android for
external images, while Vulkan requires YCbCr sampler for I420 YUV image
formats such as VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM,
VK_FORMAT_G8_B8R8_2PLANE_420_UNORM.
This CL:
 - Adds VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM and
   VK_FORMAT_G8_B8R8_2PLANE_420_UNORM as supported Vulkan formats
 - Updates GrVkYcbcrConversionInfo to add fFormat field and allow
   fExternalFormat=0.
 - Removes assertions format=VK_FORMAT_UNDEFINED for all images that
   have ycbcr info.

Bug: chromium:981022
Change-Id: I3989f72e918a257a081b81ae20429a6771229ad6
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/230918
Commit-Queue: Sergey Ulanov <sergeyu@chromium.org>
Commit-Queue: Greg Daniel <egdaniel@google.com>
Auto-Submit: Sergey Ulanov <sergeyu@chromium.org>
Reviewed-by: Greg Daniel <egdaniel@google.com>
This commit is contained in:
Sergey Ulanov 2019-08-09 10:26:30 -07:00 committed by Skia Commit-Bot
parent 81536f2184
commit cc4573dfed
23 changed files with 633 additions and 109 deletions

View File

@ -21,3 +21,6 @@ Milestone 78
* Modify GrBackendFormat getters to not return internal pointers. Use an enum class for GL formats.
* Expose GrContext::dump() when SK_ENABLE_DUMP_GPU is defined.
* Vulkan backend now supports YCbCr sampler for I420 Vulkan images that are not
backed by external images.

View File

@ -303,6 +303,7 @@ tests_sources = [
"$_tests/VkPriorityExtensionTest.cpp",
"$_tests/VkProtectedContextTest.cpp",
"$_tests/VkWrapTests.cpp",
"$_tests/VkYcbcrSamplerTest.cpp",
"$_tests/VptrTest.cpp",
"$_tests/WindowRectanglesTest.cpp",
"$_tests/WritePixelsTest.cpp",

View File

@ -69,9 +69,6 @@ public:
return GrBackendFormat(format, GrVkYcbcrConversionInfo());
}
// This is used for external textures and the VkFormat is assumed to be VK_FORMAT_UNDEFINED.
// This call is only supported on Android since the GrVkYcbcrConvesionInfo contains an android
// external format.
static GrBackendFormat MakeVk(const GrVkYcbcrConversionInfo& ycbcrInfo);
#ifdef SK_DAWN

View File

@ -67,14 +67,37 @@ private:
// object for an VkExternalFormatANDROID.
struct GrVkYcbcrConversionInfo {
GrVkYcbcrConversionInfo()
: fYcbcrModel(VK_SAMPLER_YCBCR_MODEL_CONVERSION_RGB_IDENTITY)
: fFormat(VK_FORMAT_UNDEFINED)
, fExternalFormat(0)
, fYcbcrModel(VK_SAMPLER_YCBCR_MODEL_CONVERSION_RGB_IDENTITY)
, fYcbcrRange(VK_SAMPLER_YCBCR_RANGE_ITU_FULL)
, fXChromaOffset(VK_CHROMA_LOCATION_COSITED_EVEN)
, fYChromaOffset(VK_CHROMA_LOCATION_COSITED_EVEN)
, fChromaFilter(VK_FILTER_NEAREST)
, fForceExplicitReconstruction(false)
, fExternalFormat(0)
, fExternalFormatFeatures(0) {}
, fForceExplicitReconstruction(false) {}
GrVkYcbcrConversionInfo(VkFormat format,
int64_t externalFormat,
VkSamplerYcbcrModelConversion ycbcrModel,
VkSamplerYcbcrRange ycbcrRange,
VkChromaLocation xChromaOffset,
VkChromaLocation yChromaOffset,
VkFilter chromaFilter,
VkBool32 forceExplicitReconstruction,
VkFormatFeatureFlags formatFeatures)
: fFormat(format)
, fExternalFormat(externalFormat)
, fYcbcrModel(ycbcrModel)
, fYcbcrRange(ycbcrRange)
, fXChromaOffset(xChromaOffset)
, fYChromaOffset(yChromaOffset)
, fChromaFilter(chromaFilter)
, fForceExplicitReconstruction(forceExplicitReconstruction)
, fFormatFeatures(formatFeatures) {
SkASSERT(fYcbcrModel != VK_SAMPLER_YCBCR_MODEL_CONVERSION_RGB_IDENTITY);
// Either format or externalFormat must be specified.
SkASSERT((fFormat != VK_FORMAT_UNDEFINED) ^ (externalFormat != 0));
}
GrVkYcbcrConversionInfo(VkSamplerYcbcrModelConversion ycbcrModel,
VkSamplerYcbcrRange ycbcrRange,
@ -84,35 +107,35 @@ struct GrVkYcbcrConversionInfo {
VkBool32 forceExplicitReconstruction,
uint64_t externalFormat,
VkFormatFeatureFlags externalFormatFeatures)
: fYcbcrModel(ycbcrModel)
, fYcbcrRange(ycbcrRange)
, fXChromaOffset(xChromaOffset)
, fYChromaOffset(yChromaOffset)
, fChromaFilter(chromaFilter)
, fForceExplicitReconstruction(forceExplicitReconstruction)
, fExternalFormat(externalFormat)
, fExternalFormatFeatures(externalFormatFeatures) {
SkASSERT(fExternalFormat);
}
: GrVkYcbcrConversionInfo(VK_FORMAT_UNDEFINED, externalFormat, ycbcrModel, ycbcrRange,
xChromaOffset, yChromaOffset, chromaFilter,
forceExplicitReconstruction, externalFormatFeatures) {}
bool operator==(const GrVkYcbcrConversionInfo& that) const {
// Invalid objects are not required to have all other fields intialized or matching.
// Invalid objects are not required to have all other fields initialized or matching.
if (!this->isValid() && !that.isValid()) {
return true;
}
return this->fYcbcrModel == that.fYcbcrModel &&
return this->fFormat == that.fFormat &&
this->fExternalFormat == that.fExternalFormat &&
this->fYcbcrModel == that.fYcbcrModel &&
this->fYcbcrRange == that.fYcbcrRange &&
this->fXChromaOffset == that.fXChromaOffset &&
this->fYChromaOffset == that.fYChromaOffset &&
this->fChromaFilter == that.fChromaFilter &&
this->fForceExplicitReconstruction == that.fForceExplicitReconstruction &&
this->fExternalFormat == that.fExternalFormat;
// We don't check fExternalFormatFeatures here since all matching external formats must have
// the same format features at least in terms of how they effect ycbcr sampler conversion.
this->fForceExplicitReconstruction == that.fForceExplicitReconstruction;
}
bool operator!=(const GrVkYcbcrConversionInfo& that) const { return !(*this == that); }
bool isValid() const { return fExternalFormat != 0; }
bool isValid() const { return fYcbcrModel != VK_SAMPLER_YCBCR_MODEL_CONVERSION_RGB_IDENTITY; }
// Format of the source image. Must be set to VK_FORMAT_UNDEFINED for external images or
// a valid image format otherwise.
VkFormat fFormat;
// The external format. Must be non-zero for external images, zero otherwise.
// Should be compatible to be used in a VkExternalFormatANDROID struct.
uint64_t fExternalFormat;
VkSamplerYcbcrModelConversion fYcbcrModel;
VkSamplerYcbcrRange fYcbcrRange;
@ -120,11 +143,10 @@ struct GrVkYcbcrConversionInfo {
VkChromaLocation fYChromaOffset;
VkFilter fChromaFilter;
VkBool32 fForceExplicitReconstruction;
// The external format should be compatible to be used in a VkExternalFormatANDROID struct
uint64_t fExternalFormat;
// The format features here should be those returned by a call to
// For external images format features here should be those returned by a call to
// vkAndroidHardwareBufferFormatPropertiesANDROID
VkFormatFeatureFlags fExternalFormatFeatures;
VkFormatFeatureFlags fFormatFeatures;
};
struct GrVkImageInfo {

View File

@ -138,7 +138,7 @@ GrBackendFormat GetBackendFormat(GrContext* context, AHardwareBuffer* hardwareBu
ycbcrConversion.fYChromaOffset = hwbFormatProps.suggestedYChromaOffset;
ycbcrConversion.fForceExplicitReconstruction = VK_FALSE;
ycbcrConversion.fExternalFormat = hwbFormatProps.externalFormat;
ycbcrConversion.fExternalFormatFeatures = hwbFormatProps.formatFeatures;
ycbcrConversion.fFormatFeatures = hwbFormatProps.formatFeatures;
if (VK_FORMAT_FEATURE_SAMPLED_IMAGE_YCBCR_CONVERSION_LINEAR_FILTER_BIT &
hwbFormatProps.formatFeatures) {
ycbcrConversion.fChromaFilter = VK_FILTER_LINEAR;

View File

@ -92,11 +92,8 @@ GrGLFormat GrBackendFormat::asGLFormat() const {
}
GrBackendFormat GrBackendFormat::MakeVk(const GrVkYcbcrConversionInfo& ycbcrInfo) {
#ifdef SK_BUILD_FOR_ANDROID
return GrBackendFormat(VK_FORMAT_UNDEFINED, ycbcrInfo);
#else
return GrBackendFormat();
#endif
SkASSERT(ycbcrInfo.isValid());
return GrBackendFormat(ycbcrInfo.fFormat, ycbcrInfo);
}
GrBackendFormat::GrBackendFormat(VkFormat vkFormat, const GrVkYcbcrConversionInfo& ycbcrInfo)
@ -109,7 +106,7 @@ GrBackendFormat::GrBackendFormat(VkFormat vkFormat, const GrVkYcbcrConversionInf
, fTextureType(GrTextureType::k2D) {
fVk.fFormat = vkFormat;
fVk.fYcbcrConversionInfo = ycbcrInfo;
if (fVk.fYcbcrConversionInfo.isValid()) {
if (fVk.fYcbcrConversionInfo.isValid() && fVk.fYcbcrConversionInfo.fExternalFormat) {
fTextureType = GrTextureType::kExternal;
}
}
@ -564,7 +561,7 @@ GrBackendFormat GrBackendTexture::getBackendFormat() const {
case GrBackendApi::kVulkan: {
auto info = fVkInfo.snapImageInfo();
if (info.fYcbcrConversionInfo.isValid()) {
SkASSERT(info.fFormat == VK_FORMAT_UNDEFINED);
SkASSERT(info.fFormat == info.fYcbcrConversionInfo.fFormat);
return GrBackendFormat::MakeVk(info.fYcbcrConversionInfo);
}
return GrBackendFormat::MakeVk(info.fFormat);
@ -848,7 +845,7 @@ GrBackendFormat GrBackendRenderTarget::getBackendFormat() const {
case GrBackendApi::kVulkan: {
auto info = fVkInfo.snapImageInfo();
if (info.fYcbcrConversionInfo.isValid()) {
SkASSERT(info.fFormat == VK_FORMAT_UNDEFINED);
SkASSERT(info.fFormat == info.fYcbcrConversionInfo.fFormat);
return GrBackendFormat::MakeVk(info.fYcbcrConversionInfo);
}
return GrBackendFormat::MakeVk(info.fFormat);

View File

@ -81,7 +81,6 @@ void GrPrimitiveProcessor::TextureSampler::reset(GrTextureType textureType,
fTextureType = textureType;
fConfig = config;
fExtraSamplerKey = extraSamplerKey;
SkASSERT(!fExtraSamplerKey || textureType == GrTextureType::kExternal);
}
void GrPrimitiveProcessor::TextureSampler::reset(GrTextureType textureType,

View File

@ -71,7 +71,6 @@ static void add_sampler_keys(GrProcessorKeyBuilder* b, const GrFragmentProcessor
uint32_t extraSamplerKey = gpu->getExtraSamplerKeyForProgram(
sampler.samplerState(), sampler.proxy()->backendFormat());
if (extraSamplerKey) {
SkASSERT(sampler.proxy()->textureType() == GrTextureType::kExternal);
// We first mark the normal sampler key with last bit to flag that it has an extra
// sampler key. We then add both keys.
SkASSERT((samplerKey & (1 << 31)) == 0);
@ -95,7 +94,6 @@ static void add_sampler_keys(GrProcessorKeyBuilder* b, const GrPrimitiveProcesso
sampler.textureType(), sampler.swizzle(), sampler.config(), caps);
uint32_t extraSamplerKey = sampler.extraSamplerKey();
if (extraSamplerKey) {
SkASSERT(sampler.textureType() == GrTextureType::kExternal);
// We first mark the normal sampler key with last bit to flag that it has an extra
// sampler key. We then add both keys.
SkASSERT((samplerKey & (1 << 31)) == 0);

View File

@ -335,18 +335,15 @@ void GrVkCaps::init(const GrContextOptions& contextOptions, const GrVkInterface*
auto ycbcrFeatures =
get_extension_feature_struct<VkPhysicalDeviceSamplerYcbcrConversionFeatures>(
features,
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SAMPLER_YCBCR_CONVERSION_FEATURES);
features, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SAMPLER_YCBCR_CONVERSION_FEATURES);
if (ycbcrFeatures && ycbcrFeatures->samplerYcbcrConversion &&
fSupportsAndroidHWBExternalMemory &&
(physicalDeviceVersion >= VK_MAKE_VERSION(1, 1, 0) ||
(extensions.hasExtension(VK_KHR_SAMPLER_YCBCR_CONVERSION_EXTENSION_NAME, 1) &&
this->supportsMaintenance1() &&
this->supportsBindMemory2() &&
this->supportsMemoryRequirements2() &&
this->supportsPhysicalDeviceProperties2()))) {
this->supportsMaintenance1() && this->supportsBindMemory2() &&
this->supportsMemoryRequirements2() && this->supportsPhysicalDeviceProperties2()))) {
fSupportsYcbcrConversion = true;
}
// We always push back the default GrVkYcbcrConversionInfo so that the case of no conversion
// will return a key of 0.
fYcbcrInfos.push_back(GrVkYcbcrConversionInfo());
@ -650,6 +647,8 @@ static constexpr VkFormat kVkFormats[] = {
VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK,
VK_FORMAT_R16_UNORM,
VK_FORMAT_R16G16_UNORM,
VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM,
VK_FORMAT_G8_B8R8_2PLANE_420_UNORM,
// Experimental (for Y416 and mutant P016/P010)
VK_FORMAT_R16G16B16A16_UNORM,
VK_FORMAT_R16G16_SFLOAT,
@ -967,6 +966,36 @@ void GrVkCaps::initFormatTable(const GrVkInterface* interface, VkPhysicalDevice
}
}
}
// Format: VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM
{
auto& info = this->getFormatInfo(VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM);
if (SkToBool(info.fOptimalFlags & FormatInfo::kTextureable_Flag)) {
info.fColorTypeInfoCount = 1;
info.fColorTypeInfos.reset(new ColorTypeInfo[info.fColorTypeInfoCount]());
int ctIdx = 0;
// Format: VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM, Surface: kRGB_888x
{
auto& ctInfo = info.fColorTypeInfos[ctIdx++];
ctInfo.fColorType = GrColorType::kRGB_888x;
ctInfo.fFlags = ColorTypeInfo::kUploadData_Flag;
}
}
}
// Format: VK_FORMAT_G8_B8R8_2PLANE_420_UNORM
{
auto& info = this->getFormatInfo(VK_FORMAT_G8_B8R8_2PLANE_420_UNORM);
if (SkToBool(info.fOptimalFlags & FormatInfo::kTextureable_Flag)) {
info.fColorTypeInfoCount = 1;
info.fColorTypeInfos.reset(new ColorTypeInfo[info.fColorTypeInfoCount]());
int ctIdx = 0;
// Format: VK_FORMAT_G8_B8R8_2PLANE_420_UNORM, Surface: kRGB_888x
{
auto& ctInfo = info.fColorTypeInfos[ctIdx++];
ctInfo.fColorType = GrColorType::kRGB_888x;
ctInfo.fFlags = ColorTypeInfo::kUploadData_Flag;
}
}
}
// Format: VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK
{
// No supported GrColorTypes.
@ -1203,6 +1232,11 @@ GrCaps::SupportedWrite GrVkCaps::supportedWritePixelsColorType(GrColorType surfa
return {GrColorType::kUnknown, 0};
}
if (GrVkFormatNeedsYcbcrSampler(vkFormat)) {
return {GrColorType::kUnknown, 0};
}
// The VkBufferImageCopy bufferOffset field must be both a multiple of 4 and of a single texel.
size_t offsetAlignment = align_to_4(GrVkBytesPerFormat(vkFormat));
@ -1250,21 +1284,23 @@ bool GrVkCaps::onSurfaceSupportsWritePixels(const GrSurface* surface) const {
}
static GrPixelConfig validate_image_info(VkFormat format, GrColorType ct, bool hasYcbcrConversion) {
if (format == VK_FORMAT_UNDEFINED) {
// If the format is undefined then it is only valid as an external image which requires that
// we have a valid VkYcbcrConversion.
if (hasYcbcrConversion) {
if (hasYcbcrConversion) {
if (GrVkFormatNeedsYcbcrSampler(format)) {
return kRGB_888X_GrPixelConfig;
}
// Format may be undefined for external images, which are required to have YCbCr conversion.
if (VK_FORMAT_UNDEFINED == format) {
// We don't actually care what the color type or config are since we won't use those
// values for external textures. However, for read pixels we will draw to a non ycbcr
// texture of this config so we set RGBA here for that.
return kRGBA_8888_GrPixelConfig;
} else {
return kUnknown_GrPixelConfig;
}
return kUnknown_GrPixelConfig;
}
if (hasYcbcrConversion) {
// We only support having a ycbcr conversion for external images.
if (VK_FORMAT_UNDEFINED == format) {
return kUnknown_GrPixelConfig;
}
@ -1485,6 +1521,10 @@ GrCaps::SupportedRead GrVkCaps::onSupportedReadPixelsColorType(
return {GrColorType::kUnknown, 0};
}
if (GrVkFormatNeedsYcbcrSampler(vkFormat)) {
return {GrColorType::kUnknown, 0};
}
// The VkBufferImageCopy bufferOffset field must be both a multiple of 4 and of a single texel.
size_t offsetAlignment = align_to_4(GrVkBytesPerFormat(vkFormat));

View File

@ -253,7 +253,7 @@ private:
std::unique_ptr<ColorTypeInfo[]> fColorTypeInfos;
int fColorTypeInfoCount = 0;
};
static const size_t kNumVkFormats = 18;
static const size_t kNumVkFormats = 20;
FormatInfo fFormatTable[kNumVkFormats];
FormatInfo& getFormatInfo(VkFormat);

View File

@ -1137,7 +1137,7 @@ static bool check_image_info(const GrVkCaps& caps,
}
if (info.fYcbcrConversionInfo.isValid()) {
if (!caps.supportsYcbcrConversion() || info.fFormat != VK_NULL_HANDLE) {
if (!caps.supportsYcbcrConversion()) {
return false;
}
}
@ -1875,6 +1875,11 @@ GrBackendTexture GrVkGpu::createBackendTexture(int w, int h,
return GrBackendTexture();
}
if (GrVkFormatNeedsYcbcrSampler(vkFormat)) {
SkDebugf("Can't create BackendTexture that requires Ycbcb sampler.\n");
return GrBackendTexture();
}
GrVkImageInfo info;
if (!this->createVkImageForBackendSurface(vkFormat, w, h, true,
GrRenderable::kYes == renderable, mipMapped, srcData,
@ -2630,7 +2635,11 @@ uint32_t GrVkGpu::getExtraSamplerKeyForProgram(const GrSamplerState& samplerStat
const GrVkSampler* sampler = this->resourceProvider().findOrCreateCompatibleSampler(
samplerState, *ycbcrInfo);
return sampler->uniqueID();
uint32_t result = sampler->uniqueID();
sampler->unref(this);
return result;
}
void GrVkGpu::storeVkPipelineCacheData() {

View File

@ -57,7 +57,7 @@ public:
VkFormat imageFormat() const { return fInfo.fFormat; }
GrBackendFormat getBackendFormat() const {
if (fResource && this->ycbcrConversionInfo().isValid()) {
SkASSERT(this->imageFormat() == VK_FORMAT_UNDEFINED);
SkASSERT(this->imageFormat() == this->ycbcrConversionInfo().fFormat);
return GrBackendFormat::MakeVk(this->ycbcrConversionInfo());
}
SkASSERT(this->imageFormat() != VK_FORMAT_UNDEFINED);

View File

@ -19,7 +19,7 @@ const GrVkImageView* GrVkImageView::Create(GrVkGpu* gpu, VkImage image, VkFormat
GrVkSamplerYcbcrConversion* ycbcrConversion = nullptr;
if (ycbcrInfo.isValid()) {
SkASSERT(gpu->vkCaps().supportsYcbcrConversion() && format == VK_FORMAT_UNDEFINED);
SkASSERT(gpu->vkCaps().supportsYcbcrConversion() && format == ycbcrInfo.fFormat);
ycbcrConversion =
gpu->resourceProvider().findOrCreateCompatibleSamplerYcbcrConversion(ycbcrInfo);

View File

@ -388,12 +388,16 @@ void GrVkResourceProvider::destroyResources(bool deviceLost) {
fExternalRenderPasses.reset();
// Iterate through all store GrVkSamplers and unref them before resetting the hash.
SkTDynamicHash<GrVkSampler, GrVkSampler::Key>::Iter iter(&fSamplers);
for (; !iter.done(); ++iter) {
for (decltype(fSamplers)::Iter iter(&fSamplers); !iter.done(); ++iter) {
(*iter).unref(fGpu);
}
fSamplers.reset();
for (decltype(fYcbcrConversions)::Iter iter(&fYcbcrConversions); !iter.done(); ++iter) {
(*iter).unref(fGpu);
}
fYcbcrConversions.reset();
fPipelineStateCache->release();
GR_VK_CALL(fGpu->vkInterface(), DestroyPipelineCache(fGpu->device(), fPipelineCache, nullptr));
@ -462,6 +466,11 @@ void GrVkResourceProvider::abandonResources() {
}
fSamplers.reset();
for (decltype(fYcbcrConversions)::Iter iter(&fYcbcrConversions); !iter.done(); ++iter) {
(*iter).unrefAndAbandon();
}
fYcbcrConversions.reset();
fPipelineStateCache->abandon();
fPipelineCache = VK_NULL_HANDLE;

View File

@ -83,8 +83,7 @@ GrVkSampler* GrVkSampler::Create(GrVkGpu* gpu, const GrSamplerState& samplerStat
createInfo.pNext = &conversionInfo;
const VkFormatFeatureFlags& flags = ycbcrInfo.fExternalFormatFeatures;
VkFormatFeatureFlags flags = ycbcrInfo.fFormatFeatures;
if (!SkToBool(flags & VK_FORMAT_FEATURE_SAMPLED_IMAGE_YCBCR_CONVERSION_LINEAR_FILTER_BIT)) {
createInfo.magFilter = VK_FILTER_NEAREST;
createInfo.minFilter = VK_FILTER_NEAREST;

View File

@ -14,11 +14,9 @@ GrVkSamplerYcbcrConversion* GrVkSamplerYcbcrConversion::Create(
if (!gpu->vkCaps().supportsYcbcrConversion()) {
return nullptr;
}
// We only support creating ycbcr conversion for external formats;
SkASSERT(info.fExternalFormat);
#ifdef SK_DEBUG
const VkFormatFeatureFlags& featureFlags = info.fExternalFormatFeatures;
const VkFormatFeatureFlags& featureFlags = info.fFormatFeatures;
if (info.fXChromaOffset == VK_CHROMA_LOCATION_MIDPOINT ||
info.fYChromaOffset == VK_CHROMA_LOCATION_MIDPOINT) {
SkASSERT(featureFlags & VK_FORMAT_FEATURE_MIDPOINT_CHROMA_SAMPLES_BIT);
@ -36,36 +34,50 @@ GrVkSamplerYcbcrConversion* GrVkSamplerYcbcrConversion::Create(
}
#endif
#ifdef SK_BUILD_FOR_ANDROID
VkExternalFormatANDROID externalFormat;
externalFormat.sType = VK_STRUCTURE_TYPE_EXTERNAL_FORMAT_ANDROID;
externalFormat.pNext = nullptr;
externalFormat.externalFormat = info.fExternalFormat;
VkSamplerYcbcrConversionCreateInfo ycbcrCreateInfo;
memset(&ycbcrCreateInfo, 0, sizeof(VkSamplerYcbcrConversionCreateInfo));
ycbcrCreateInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_CREATE_INFO;
ycbcrCreateInfo.pNext = &externalFormat;
ycbcrCreateInfo.format = VK_FORMAT_UNDEFINED;
ycbcrCreateInfo.pNext = nullptr;
ycbcrCreateInfo.format = info.fFormat;
ycbcrCreateInfo.ycbcrModel = info.fYcbcrModel;
ycbcrCreateInfo.ycbcrRange = info.fYcbcrRange;
// Componets is ignored for external format conversions;
// ycbcrCreateInfo.components = {0, 0, 0, 0};
// Components is ignored for external format conversions. For all other formats identity swizzle
// is used. It can be added to GrVkYcbcrConversionInfo if necessary.
ycbcrCreateInfo.components = {VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY,
VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY};
ycbcrCreateInfo.xChromaOffset = info.fXChromaOffset;
ycbcrCreateInfo.yChromaOffset = info.fYChromaOffset;
ycbcrCreateInfo.chromaFilter = info.fChromaFilter;
ycbcrCreateInfo.forceExplicitReconstruction = info.fForceExplicitReconstruction;
#ifdef SK_BUILD_FOR_ANDROID
VkExternalFormatANDROID externalFormat;
if (info.fExternalFormat) {
// Format must not be specified for external images.
SkASSERT(info.fFormat == VK_FORMAT_UNDEFINED);
externalFormat.sType = VK_STRUCTURE_TYPE_EXTERNAL_FORMAT_ANDROID;
externalFormat.pNext = nullptr;
externalFormat.externalFormat = info.fExternalFormat;
ycbcrCreateInfo.pNext = &externalFormat;
}
#else
// External images are supported only on Android;
SkASSERT(!info.fExternalFormat);
#endif
if (!info.fExternalFormat) {
SkASSERT(info.fFormat != VK_FORMAT_UNDEFINED);
}
VkSamplerYcbcrConversion conversion;
GR_VK_CALL(gpu->vkInterface(), CreateSamplerYcbcrConversion(gpu->device(), &ycbcrCreateInfo,
nullptr, &conversion));
if (conversion == VK_NULL_HANDLE) {
return nullptr;
}
return new GrVkSamplerYcbcrConversion(conversion, GenerateKey(info));
#else
return nullptr;
#endif
}
void GrVkSamplerYcbcrConversion::freeGPUData(GrVkGpu* gpu) const {
@ -96,6 +108,5 @@ GrVkSamplerYcbcrConversion::Key GrVkSamplerYcbcrConversion::GenerateKey(
ycbcrKey |= (static_cast<uint8_t>(ycbcrInfo.fChromaFilter) << kChromaFilterShift);
ycbcrKey |= (static_cast<uint8_t>(ycbcrInfo.fForceExplicitReconstruction) << kReconShift);
return {ycbcrInfo.fExternalFormat, ycbcrKey};
return Key{ycbcrInfo.fFormat, ycbcrInfo.fExternalFormat, ycbcrKey};
}

View File

@ -22,18 +22,21 @@ public:
VkSamplerYcbcrConversion ycbcrConversion() const { return fYcbcrConversion; }
struct Key {
Key() : fExternalFormat(0), fConversionKey(0) {}
Key(uint64_t externalFormat, uint8_t conversionKey) {
Key() : fVkFormat(VK_FORMAT_UNDEFINED), fExternalFormat(0), fConversionKey(0) {}
Key(VkFormat vkFormat, uint64_t externalFormat, uint8_t conversionKey) {
memset(this, 0, sizeof(Key));
fVkFormat = vkFormat;
fExternalFormat = externalFormat;
fConversionKey = conversionKey;
}
VkFormat fVkFormat;
uint64_t fExternalFormat;
uint8_t fConversionKey;
bool operator==(const Key& that) const {
return this->fExternalFormat == that.fExternalFormat &&
return this->fVkFormat == that.fVkFormat &&
this->fExternalFormat == that.fExternalFormat &&
this->fConversionKey == that.fConversionKey;
}
};

View File

@ -204,6 +204,16 @@ static void get_ubo_aligned_offset(uint32_t* uniformOffset,
}
}
GrVkUniformHandler::~GrVkUniformHandler() {
GrVkGpu* gpu = static_cast<GrVkPipelineStateBuilder*>(fProgramBuilder)->gpu();
for (decltype(fSamplers)::Iter iter(&fSamplers); iter.next();) {
if (iter->fImmutableSampler) {
iter->fImmutableSampler->unref(gpu);
iter->fImmutableSampler = nullptr;
}
}
}
GrGLSLUniformHandler::UniformHandle GrVkUniformHandler::internalAddUniformArray(
uint32_t visibility,
GrSLType type,
@ -282,10 +292,9 @@ GrGLSLUniformHandler::SamplerHandle GrVkUniformHandler::addSampler(const GrTextu
info.fVisibility = kFragment_GrShaderFlag;
info.fUBOffset = 0;
// Check if we are dealing with an external texture and store the needed information if so
// Check if we are dealing with an external texture and store the needed information if so.
const GrVkTexture* vkTexture = static_cast<const GrVkTexture*>(texture);
if (vkTexture->ycbcrConversionInfo().isValid()) {
SkASSERT(type == GrTextureType::kExternal);
GrVkGpu* gpu = static_cast<GrVkPipelineStateBuilder*>(fProgramBuilder)->gpu();
info.fImmutableSampler = gpu->resourceProvider().findOrCreateCompatibleSampler(
state, vkTexture->ycbcrConversionInfo());

View File

@ -39,12 +39,13 @@ public:
uint32_t fVisibility;
// fUBOffset is only valid if the GrSLType of the fVariable is not a sampler
uint32_t fUBOffset;
// The SamplerState, maxMipLevel, and ycbcrInfo are only valid if the GrSLType is a sampler
// and that sampler is used for sampling an external image with a ycbcr conversion.
// fImmutableSampler is used for sampling an image with a ycbcr conversion.
const GrVkSampler* fImmutableSampler = nullptr;
};
typedef GrTAllocator<UniformInfo> UniformInfoArray;
~GrVkUniformHandler() override;
const GrShaderVar& getUniformVariable(UniformHandle u) const override {
return fUniforms[u.toIndex()].fVariable;
}

View File

@ -101,30 +101,32 @@ bool GrPixelConfigToVkFormat(GrPixelConfig config, VkFormat* format) {
#ifdef SK_DEBUG
bool GrVkFormatColorTypePairIsValid(VkFormat format, GrColorType colorType) {
switch (format) {
case VK_FORMAT_R8G8B8A8_UNORM: return GrColorType::kRGBA_8888 == colorType ||
GrColorType::kRGB_888x == colorType;
case VK_FORMAT_B8G8R8A8_UNORM: return GrColorType::kBGRA_8888 == colorType;
case VK_FORMAT_R8G8B8A8_SRGB: return GrColorType::kRGBA_8888_SRGB == colorType;
case VK_FORMAT_R8G8B8_UNORM: return GrColorType::kRGB_888x == colorType;
case VK_FORMAT_R8G8_UNORM: return GrColorType::kRG_88 == colorType;
case VK_FORMAT_A2B10G10R10_UNORM_PACK32: return GrColorType::kRGBA_1010102 == colorType;
case VK_FORMAT_R5G6B5_UNORM_PACK16: return GrColorType::kBGR_565 == colorType;
case VK_FORMAT_R8G8B8A8_UNORM: return GrColorType::kRGBA_8888 == colorType ||
GrColorType::kRGB_888x == colorType;
case VK_FORMAT_B8G8R8A8_UNORM: return GrColorType::kBGRA_8888 == colorType;
case VK_FORMAT_R8G8B8A8_SRGB: return GrColorType::kRGBA_8888_SRGB == colorType;
case VK_FORMAT_R8G8B8_UNORM: return GrColorType::kRGB_888x == colorType;
case VK_FORMAT_R8G8_UNORM: return GrColorType::kRG_88 == colorType;
case VK_FORMAT_A2B10G10R10_UNORM_PACK32: return GrColorType::kRGBA_1010102 == colorType;
case VK_FORMAT_R5G6B5_UNORM_PACK16: return GrColorType::kBGR_565 == colorType;
// R4G4B4A4 is not required to be supported so we actually
// store RGBA_4444 data as B4G4R4A4.
case VK_FORMAT_B4G4R4A4_UNORM_PACK16: return GrColorType::kABGR_4444 == colorType;
case VK_FORMAT_R4G4B4A4_UNORM_PACK16: return GrColorType::kABGR_4444 == colorType;
case VK_FORMAT_B4G4R4A4_UNORM_PACK16: return GrColorType::kABGR_4444 == colorType;
case VK_FORMAT_R4G4B4A4_UNORM_PACK16: return GrColorType::kABGR_4444 == colorType;
case VK_FORMAT_R8_UNORM: return GrColorType::kAlpha_8 == colorType ||
GrColorType::kGray_8 == colorType;
case VK_FORMAT_R32G32B32A32_SFLOAT: return GrColorType::kRGBA_F32 == colorType;
case VK_FORMAT_R16G16B16A16_SFLOAT: return GrColorType::kRGBA_F16 == colorType ||
GrColorType::kRGBA_F16_Clamped == colorType;
case VK_FORMAT_R16_SFLOAT: return GrColorType::kAlpha_F16 == colorType;
case VK_FORMAT_R16_UNORM: return GrColorType::kR_16 == colorType;
case VK_FORMAT_R16G16_UNORM: return GrColorType::kRG_1616 == colorType;
GrColorType::kGray_8 == colorType;
case VK_FORMAT_R32G32B32A32_SFLOAT: return GrColorType::kRGBA_F32 == colorType;
case VK_FORMAT_R16G16B16A16_SFLOAT: return GrColorType::kRGBA_F16 == colorType ||
GrColorType::kRGBA_F16_Clamped == colorType;
case VK_FORMAT_R16_SFLOAT: return GrColorType::kAlpha_F16 == colorType;
case VK_FORMAT_R16_UNORM: return GrColorType::kR_16 == colorType;
case VK_FORMAT_R16G16_UNORM: return GrColorType::kRG_1616 == colorType;
case VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM: return GrColorType::kRGB_888x == colorType;
case VK_FORMAT_G8_B8R8_2PLANE_420_UNORM: return GrColorType::kRGB_888x == colorType;
// Experimental (for Y416 and mutant P016/P010)
case VK_FORMAT_R16G16B16A16_UNORM: return GrColorType::kRGBA_16161616 == colorType;
case VK_FORMAT_R16G16_SFLOAT: return GrColorType::kRG_F16 == colorType;
default: return false;
case VK_FORMAT_R16G16B16A16_UNORM: return GrColorType::kRGBA_16161616 == colorType;
case VK_FORMAT_R16G16_SFLOAT: return GrColorType::kRG_F16 == colorType;
default: return false;
}
SkUNREACHABLE;
@ -149,6 +151,8 @@ bool GrVkFormatIsSupported(VkFormat format) {
case VK_FORMAT_R16_SFLOAT:
case VK_FORMAT_R16_UNORM:
case VK_FORMAT_R16G16_UNORM:
case VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM:
case VK_FORMAT_G8_B8R8_2PLANE_420_UNORM:
// Experimental (for Y416 and mutant P016/P010)
case VK_FORMAT_R16G16B16A16_UNORM:
case VK_FORMAT_R16G16_SFLOAT:
@ -158,6 +162,11 @@ bool GrVkFormatIsSupported(VkFormat format) {
}
}
bool GrVkFormatNeedsYcbcrSampler(VkFormat format) {
return format == VK_FORMAT_G8_B8R8_2PLANE_420_UNORM ||
format == VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM;
}
bool GrSampleCountToVkSampleCount(uint32_t samples, VkSampleCountFlagBits* vkSamples) {
SkASSERT(samples >= 1);
switch (samples) {

View File

@ -36,6 +36,8 @@ bool GrPixelConfigToVkFormat(GrPixelConfig config, VkFormat* format);
bool GrVkFormatIsSupported(VkFormat);
bool GrVkFormatNeedsYcbcrSampler(VkFormat format);
#ifdef SK_DEBUG
/**
* Returns true if the passed in VkFormat and GrColorType are compatible with each other.

View File

@ -0,0 +1,414 @@
/*
* Copyright 2019 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "include/core/SkTypes.h"
#if SK_SUPPORT_GPU && defined(SK_VULKAN)
#include "include/core/SkImage.h"
#include "include/core/SkSurface.h"
#include "include/gpu/GrContext.h"
#include "include/gpu/vk/GrVkBackendContext.h"
#include "include/gpu/vk/GrVkExtensions.h"
#include "tests/Test.h"
#include "tools/gpu/vk/VkTestUtils.h"
const size_t kImageWidth = 8;
const size_t kImageHeight = 8;
static int getY(size_t x, size_t y) {
return 16 + (x + y) * 219 / (kImageWidth + kImageHeight - 2);
}
static int getU(size_t x, size_t y) { return 16 + x * 224 / (kImageWidth - 1); }
static int getV(size_t x, size_t y) { return 16 + y * 224 / (kImageHeight - 1); }
#define DECLARE_VK_PROC(name) PFN_vk##name fVk##name
#define ACQUIRE_INST_VK_PROC(name) \
fVk##name = reinterpret_cast<PFN_vk##name>(getProc("vk" #name, fBackendContext.fInstance,\
VK_NULL_HANDLE)); \
if (fVk##name == nullptr) { \
ERRORF(reporter, "Function ptr for vk%s could not be acquired\n", #name); \
return false; \
}
#define ACQUIRE_DEVICE_VK_PROC(name) \
fVk##name = reinterpret_cast<PFN_vk##name>(getProc("vk" #name, VK_NULL_HANDLE, fDevice)); \
if (fVk##name == nullptr) { \
ERRORF(reporter, "Function ptr for vk%s could not be acquired\n", #name); \
return false; \
}
class VkYcbcrSamplerTestHelper {
public:
VkYcbcrSamplerTestHelper() {}
~VkYcbcrSamplerTestHelper();
bool init(skiatest::Reporter* reporter);
sk_sp<SkImage> createI420Image(skiatest::Reporter* reporter);
GrContext* getGrContext() { return fGrContext.get(); }
private:
GrVkExtensions fExtensions;
VkPhysicalDeviceFeatures2 fFeatures = {};
VkDebugReportCallbackEXT fDebugCallback = VK_NULL_HANDLE;
DECLARE_VK_PROC(DestroyInstance);
DECLARE_VK_PROC(DeviceWaitIdle);
DECLARE_VK_PROC(DestroyDevice);
DECLARE_VK_PROC(GetPhysicalDeviceFormatProperties);
DECLARE_VK_PROC(GetPhysicalDeviceMemoryProperties);
DECLARE_VK_PROC(CreateImage);
DECLARE_VK_PROC(DestroyImage);
DECLARE_VK_PROC(GetImageMemoryRequirements);
DECLARE_VK_PROC(AllocateMemory);
DECLARE_VK_PROC(FreeMemory);
DECLARE_VK_PROC(BindImageMemory);
DECLARE_VK_PROC(MapMemory);
DECLARE_VK_PROC(UnmapMemory);
DECLARE_VK_PROC(FlushMappedMemoryRanges);
DECLARE_VK_PROC(GetImageSubresourceLayout);
VkDevice fDevice = VK_NULL_HANDLE;
PFN_vkDestroyDebugReportCallbackEXT fDestroyDebugCallback = nullptr;
GrVkBackendContext fBackendContext;
sk_sp<GrContext> fGrContext;
VkImage fImage = VK_NULL_HANDLE;
VkDeviceMemory fImageMemory = VK_NULL_HANDLE;
GrBackendTexture texture;
};
VkYcbcrSamplerTestHelper::~VkYcbcrSamplerTestHelper() {
fGrContext.reset();
if (fImage != VK_NULL_HANDLE) {
fVkDestroyImage(fDevice, fImage, nullptr);
fImage = VK_NULL_HANDLE;
}
if (fImageMemory != VK_NULL_HANDLE) {
fVkFreeMemory(fDevice, fImageMemory, nullptr);
fImageMemory = VK_NULL_HANDLE;
}
fBackendContext.fMemoryAllocator.reset();
if (fDevice != VK_NULL_HANDLE) {
fVkDeviceWaitIdle(fDevice);
fVkDestroyDevice(fDevice, nullptr);
fDevice = VK_NULL_HANDLE;
}
if (fDebugCallback != VK_NULL_HANDLE) {
fDestroyDebugCallback(fBackendContext.fInstance, fDebugCallback, nullptr);
}
if (fBackendContext.fInstance != VK_NULL_HANDLE) {
fVkDestroyInstance(fBackendContext.fInstance, nullptr);
fBackendContext.fInstance = VK_NULL_HANDLE;
}
sk_gpu_test::FreeVulkanFeaturesStructs(&fFeatures);
}
bool VkYcbcrSamplerTestHelper::init(skiatest::Reporter* reporter) {
PFN_vkGetInstanceProcAddr instProc;
PFN_vkGetDeviceProcAddr devProc;
if (!sk_gpu_test::LoadVkLibraryAndGetProcAddrFuncs(&instProc, &devProc)) {
ERRORF(reporter, "Failed to load Vulkan");
return false;
}
auto getProc = [&instProc, &devProc](const char* proc_name,
VkInstance instance, VkDevice device) {
if (device != VK_NULL_HANDLE) {
return devProc(device, proc_name);
}
return instProc(instance, proc_name);
};
fFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
fFeatures.pNext = nullptr;
fBackendContext.fInstance = VK_NULL_HANDLE;
fBackendContext.fDevice = VK_NULL_HANDLE;
if (!sk_gpu_test::CreateVkBackendContext(getProc, &fBackendContext, &fExtensions, &fFeatures,
&fDebugCallback, nullptr, sk_gpu_test::CanPresentFn(),
false)) {
return false;
}
fDevice = fBackendContext.fDevice;
if (fDebugCallback != VK_NULL_HANDLE) {
fDestroyDebugCallback = reinterpret_cast<PFN_vkDestroyDebugReportCallbackEXT>(
instProc(fBackendContext.fInstance, "vkDestroyDebugReportCallbackEXT"));
}
ACQUIRE_INST_VK_PROC(DestroyInstance)
ACQUIRE_INST_VK_PROC(DeviceWaitIdle)
ACQUIRE_INST_VK_PROC(DestroyDevice)
ACQUIRE_INST_VK_PROC(GetPhysicalDeviceFormatProperties)
ACQUIRE_INST_VK_PROC(GetPhysicalDeviceMemoryProperties)
ACQUIRE_DEVICE_VK_PROC(CreateImage)
ACQUIRE_DEVICE_VK_PROC(DestroyImage)
ACQUIRE_DEVICE_VK_PROC(GetImageMemoryRequirements)
ACQUIRE_DEVICE_VK_PROC(AllocateMemory)
ACQUIRE_DEVICE_VK_PROC(FreeMemory)
ACQUIRE_DEVICE_VK_PROC(BindImageMemory)
ACQUIRE_DEVICE_VK_PROC(MapMemory)
ACQUIRE_DEVICE_VK_PROC(UnmapMemory)
ACQUIRE_DEVICE_VK_PROC(FlushMappedMemoryRanges)
ACQUIRE_DEVICE_VK_PROC(GetImageSubresourceLayout)
if (!fExtensions.hasExtension(VK_KHR_SAMPLER_YCBCR_CONVERSION_EXTENSION_NAME, 1)) {
return false;
}
fGrContext = GrContext::MakeVulkan(fBackendContext);
if (!fGrContext) {
return false;
}
return true;
}
sk_sp<SkImage> VkYcbcrSamplerTestHelper::createI420Image(skiatest::Reporter* reporter) {
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{kImageWidth, kImageHeight, 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;
REPORTER_ASSERT(reporter, fImage == VK_NULL_HANDLE);
if (fVkCreateImage(fDevice, &vkImageInfo, nullptr, &fImage) != VK_SUCCESS) {
ERRORF(reporter, "Failed to allocate I420 image");
return nullptr;
}
VkMemoryRequirements requirements;
fVkGetImageMemoryRequirements(fDevice, fImage, &requirements);
uint32_t memoryTypeIndex = 0;
bool foundHeap = false;
VkPhysicalDeviceMemoryProperties phyDevMemProps;
fVkGetPhysicalDeviceMemoryProperties(fBackendContext.fPhysicalDevice, &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) {
ERRORF(reporter, "Failed to find valid heap for imported memory");
return nullptr;
}
VkMemoryAllocateInfo allocInfo = {};
allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
allocInfo.allocationSize = requirements.size;
allocInfo.memoryTypeIndex = memoryTypeIndex;
REPORTER_ASSERT(reporter, fImageMemory == VK_NULL_HANDLE);
if (fVkAllocateMemory(fDevice, &allocInfo, nullptr, &fImageMemory) != VK_SUCCESS) {
ERRORF(reporter, "Failed to allocate VkDeviceMemory.");
return nullptr;
}
void* mappedBuffer;
if (fVkMapMemory(fDevice, fImageMemory, 0u, requirements.size, 0u, &mappedBuffer) !=
VK_SUCCESS) {
ERRORF(reporter, "Failed to map Vulkan memory.");
return nullptr;
}
// Write Y channel.
VkImageSubresource subresource;
subresource.aspectMask = VK_IMAGE_ASPECT_PLANE_0_BIT;
subresource.mipLevel = 0;
subresource.arrayLayer = 0;
VkSubresourceLayout yLayout;
fVkGetImageSubresourceLayout(fDevice, fImage, &subresource, &yLayout);
uint8_t* bufferData = reinterpret_cast<uint8_t*>(mappedBuffer) + yLayout.offset;
for (size_t y = 0; y < kImageHeight; ++y) {
for (size_t x = 0; x < kImageWidth; ++x) {
bufferData[y * yLayout.rowPitch + x] = getY(x, y);
}
}
// Write UV channels.
subresource.aspectMask = VK_IMAGE_ASPECT_PLANE_1_BIT;
VkSubresourceLayout uvLayout;
fVkGetImageSubresourceLayout(fDevice, fImage, &subresource, &uvLayout);
bufferData = reinterpret_cast<uint8_t*>(mappedBuffer) + uvLayout.offset;
for (size_t y = 0; y < kImageHeight / 2; ++y) {
for (size_t x = 0; x < kImageWidth / 2; ++x) {
bufferData[y * uvLayout.rowPitch + x * 2] = getU(x * 2, y * 2);
bufferData[y * uvLayout.rowPitch + x * 2 + 1] = getV(x * 2, y * 2);
}
}
VkMappedMemoryRange flushRange;
flushRange.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE;
flushRange.pNext = nullptr;
flushRange.memory = fImageMemory;
flushRange.offset = 0;
flushRange.size = VK_WHOLE_SIZE;
if (fVkFlushMappedMemoryRanges(fDevice, 1, &flushRange) != VK_SUCCESS) {
ERRORF(reporter, "Failed to flush buffer memory.");
return nullptr;
}
fVkUnmapMemory(fDevice, fImageMemory);
// Bind image memory.
if (fVkBindImageMemory(fDevice, fImage, fImageMemory, 0u) != VK_SUCCESS) {
ERRORF(reporter, "Failed to bind VkImage memory.");
return nullptr;
}
// Get format flags for the image format.
VkFormatProperties formatProperties;
fVkGetPhysicalDeviceFormatProperties(fBackendContext.fPhysicalDevice, vkImageInfo.format,
&formatProperties);
// 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);
texture = GrBackendTexture(kImageWidth, kImageHeight, imageInfo);
sk_sp<SkImage> image = SkImage::MakeFromTexture(fGrContext.get(),
texture,
kTopLeft_GrSurfaceOrigin,
kRGB_888x_SkColorType,
kPremul_SkAlphaType,
nullptr);
if (!image) {
ERRORF(reporter, "Failed to wrap VkImage with SkImage");
return nullptr;
}
return image;
}
static int round_and_clamp(float x) {
int r = static_cast<int>(round(x));
if (r > 255) return 255;
if (r < 0) return 0;
return r;
}
DEF_GPUTEST(VkYCbcrSampler_DrawImageWithYcbcrSampler, reporter, options) {
VkYcbcrSamplerTestHelper helper;
if (!helper.init(reporter)) {
return;
}
sk_sp<SkImage> srcImage = helper.createI420Image(reporter);
if (!srcImage) {
return;
}
sk_sp<SkSurface> surface = SkSurface::MakeRenderTarget(
helper.getGrContext(), SkBudgeted::kNo,
SkImageInfo::Make(kImageWidth, kImageHeight, kN32_SkColorType, kPremul_SkAlphaType));
if (!surface) {
ERRORF(reporter, "Failed to create target SkSurface");
return;
}
surface->getCanvas()->drawImage(srcImage, 0, 0);
surface->flush();
std::vector<uint8_t> readbackData(kImageWidth * kImageHeight * 4);
if (!surface->readPixels(SkImageInfo::Make(kImageWidth, kImageHeight, kRGBA_8888_SkColorType,
kOpaque_SkAlphaType),
readbackData.data(), kImageWidth * 4, 0, 0)) {
ERRORF(reporter, "Readback failed");
return;
}
// Allow resulting color to be off by 1 in each channel as some Vulkan implementations do not
// round YCbCr sampler result properly.
const int kColorTolerance = 1;
// Verify results only for pixels with even coordinates, since others use
// interpolated U & V channels.
for (size_t y = 0; y < kImageHeight; y += 2) {
for (size_t x = 0; x < kImageWidth; x += 2) {
// createI420Image() initializes the image with VK_SAMPLER_YCBCR_RANGE_ITU_NARROW.
float yChannel = (static_cast<float>(getY(x, y)) - 16.0) / 219.0;
float uChannel = (static_cast<float>(getU(x, y)) - 128.0) / 224.0;
float vChannel = (static_cast<float>(getV(x, y)) - 128.0) / 224.0;
// BR.709 conversion as specified in
// https://www.khronos.org/registry/DataFormat/specs/1.2/dataformat.1.2.html#MODEL_YUV
int expectedR = round_and_clamp((yChannel + 1.5748f * vChannel) * 255.0);
int expectedG = round_and_clamp((yChannel - 0.13397432f / 0.7152f * uChannel -
0.33480248f / 0.7152f * vChannel) *
255.0);
int expectedB = round_and_clamp((yChannel + 1.8556f * uChannel) * 255.0);
int r = readbackData[(y * kImageWidth + x) * 4];
if (abs(r - expectedR) > kColorTolerance) {
ERRORF(reporter, "R should be %d, but is %d at (%d, %d)", expectedR, r, x, y);
}
int g = readbackData[(y * kImageWidth + x) * 4 + 1];
if (abs(g - expectedG) > kColorTolerance) {
ERRORF(reporter, "G should be %d, but is %d at (%d, %d)", expectedG, g, x, y);
}
int b = readbackData[(y * kImageWidth + x) * 4 + 2];
if (abs(b - expectedB) > kColorTolerance) {
ERRORF(reporter, "B should be %d, but is %d at (%d, %d)", expectedB, b, x, y);
}
}
}
}
// Verifies that it's not possible to allocate Ycbcr texture directly.
DEF_GPUTEST(VkYCbcrSampler_NoYcbcrSurface, reporter, options) {
VkYcbcrSamplerTestHelper helper;
if (!helper.init(reporter)) {
return;
}
GrBackendTexture texture = helper.getGrContext()->createBackendTexture(
kImageWidth, kImageHeight, GrBackendFormat::MakeVk(VK_FORMAT_G8_B8R8_2PLANE_420_UNORM),
GrMipMapped::kNo, GrRenderable::kNo, GrProtected::kNo);
if (texture.isValid()) {
ERRORF(reporter,
"GrContext::createBackendTexture() didn't fail as expected for Ycbcr format.");
}
}
#endif // SK_SUPPORT_GPU && defined(SK_VULKAN)

View File

@ -381,6 +381,7 @@ static bool setup_features(GrVkGetProc getProc, VkInstance inst, VkPhysicalDevic
sizeof(VkPhysicalDeviceSamplerYcbcrConversionFeatures));
ycbcrFeature->sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SAMPLER_YCBCR_CONVERSION_FEATURES;
ycbcrFeature->pNext = nullptr;
ycbcrFeature->samplerYcbcrConversion = VK_TRUE;
*tailPNext = ycbcrFeature;
tailPNext = &ycbcrFeature->pNext;
}