diff --git a/RELEASE_NOTES.txt b/RELEASE_NOTES.txt index 4784038d31..98892fd59d 100644 --- a/RELEASE_NOTES.txt +++ b/RELEASE_NOTES.txt @@ -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. diff --git a/gn/tests.gni b/gn/tests.gni index 5815c042bf..951a04e417 100644 --- a/gn/tests.gni +++ b/gn/tests.gni @@ -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", diff --git a/include/gpu/GrBackendSurface.h b/include/gpu/GrBackendSurface.h index 1695f4c828..e98f6af2c2 100644 --- a/include/gpu/GrBackendSurface.h +++ b/include/gpu/GrBackendSurface.h @@ -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 diff --git a/include/gpu/vk/GrVkTypes.h b/include/gpu/vk/GrVkTypes.h index c22799893f..9abf14d8d9 100644 --- a/include/gpu/vk/GrVkTypes.h +++ b/include/gpu/vk/GrVkTypes.h @@ -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 { diff --git a/src/gpu/GrAHardwareBufferUtils.cpp b/src/gpu/GrAHardwareBufferUtils.cpp index 47899e8961..77a5ed067a 100644 --- a/src/gpu/GrAHardwareBufferUtils.cpp +++ b/src/gpu/GrAHardwareBufferUtils.cpp @@ -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; diff --git a/src/gpu/GrBackendSurface.cpp b/src/gpu/GrBackendSurface.cpp index 412d8dae55..fa95520811 100644 --- a/src/gpu/GrBackendSurface.cpp +++ b/src/gpu/GrBackendSurface.cpp @@ -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); diff --git a/src/gpu/GrPrimitiveProcessor.cpp b/src/gpu/GrPrimitiveProcessor.cpp index 32790b9965..becab188b4 100644 --- a/src/gpu/GrPrimitiveProcessor.cpp +++ b/src/gpu/GrPrimitiveProcessor.cpp @@ -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, diff --git a/src/gpu/GrProgramDesc.cpp b/src/gpu/GrProgramDesc.cpp index b826125f5e..d20d6e17c0 100644 --- a/src/gpu/GrProgramDesc.cpp +++ b/src/gpu/GrProgramDesc.cpp @@ -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); diff --git a/src/gpu/vk/GrVkCaps.cpp b/src/gpu/vk/GrVkCaps.cpp index e6118b099c..3b6184f700 100644 --- a/src/gpu/vk/GrVkCaps.cpp +++ b/src/gpu/vk/GrVkCaps.cpp @@ -335,18 +335,15 @@ void GrVkCaps::init(const GrContextOptions& contextOptions, const GrVkInterface* auto ycbcrFeatures = get_extension_feature_struct( - 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)); diff --git a/src/gpu/vk/GrVkCaps.h b/src/gpu/vk/GrVkCaps.h index 938eff59c3..2cc1083911 100644 --- a/src/gpu/vk/GrVkCaps.h +++ b/src/gpu/vk/GrVkCaps.h @@ -253,7 +253,7 @@ private: std::unique_ptr fColorTypeInfos; int fColorTypeInfoCount = 0; }; - static const size_t kNumVkFormats = 18; + static const size_t kNumVkFormats = 20; FormatInfo fFormatTable[kNumVkFormats]; FormatInfo& getFormatInfo(VkFormat); diff --git a/src/gpu/vk/GrVkGpu.cpp b/src/gpu/vk/GrVkGpu.cpp index 04e39ca926..db47cd69fb 100644 --- a/src/gpu/vk/GrVkGpu.cpp +++ b/src/gpu/vk/GrVkGpu.cpp @@ -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() { diff --git a/src/gpu/vk/GrVkImage.h b/src/gpu/vk/GrVkImage.h index 222fdd5a50..8f448639c6 100644 --- a/src/gpu/vk/GrVkImage.h +++ b/src/gpu/vk/GrVkImage.h @@ -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); diff --git a/src/gpu/vk/GrVkImageView.cpp b/src/gpu/vk/GrVkImageView.cpp index 73b54e3e95..ce7b1c5458 100644 --- a/src/gpu/vk/GrVkImageView.cpp +++ b/src/gpu/vk/GrVkImageView.cpp @@ -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); diff --git a/src/gpu/vk/GrVkResourceProvider.cpp b/src/gpu/vk/GrVkResourceProvider.cpp index 8befc5b35f..cb217190e2 100644 --- a/src/gpu/vk/GrVkResourceProvider.cpp +++ b/src/gpu/vk/GrVkResourceProvider.cpp @@ -388,12 +388,16 @@ void GrVkResourceProvider::destroyResources(bool deviceLost) { fExternalRenderPasses.reset(); // Iterate through all store GrVkSamplers and unref them before resetting the hash. - SkTDynamicHash::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; diff --git a/src/gpu/vk/GrVkSampler.cpp b/src/gpu/vk/GrVkSampler.cpp index 4f702c936a..8e2d62dd31 100644 --- a/src/gpu/vk/GrVkSampler.cpp +++ b/src/gpu/vk/GrVkSampler.cpp @@ -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; diff --git a/src/gpu/vk/GrVkSamplerYcbcrConversion.cpp b/src/gpu/vk/GrVkSamplerYcbcrConversion.cpp index 8757ac5aba..f33f5216dc 100644 --- a/src/gpu/vk/GrVkSamplerYcbcrConversion.cpp +++ b/src/gpu/vk/GrVkSamplerYcbcrConversion.cpp @@ -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(ycbcrInfo.fChromaFilter) << kChromaFilterShift); ycbcrKey |= (static_cast(ycbcrInfo.fForceExplicitReconstruction) << kReconShift); - return {ycbcrInfo.fExternalFormat, ycbcrKey}; + return Key{ycbcrInfo.fFormat, ycbcrInfo.fExternalFormat, ycbcrKey}; } - diff --git a/src/gpu/vk/GrVkSamplerYcbcrConversion.h b/src/gpu/vk/GrVkSamplerYcbcrConversion.h index 1b5777253a..cf7a2c5995 100644 --- a/src/gpu/vk/GrVkSamplerYcbcrConversion.h +++ b/src/gpu/vk/GrVkSamplerYcbcrConversion.h @@ -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; } }; diff --git a/src/gpu/vk/GrVkUniformHandler.cpp b/src/gpu/vk/GrVkUniformHandler.cpp index 32210d645c..cee1706196 100644 --- a/src/gpu/vk/GrVkUniformHandler.cpp +++ b/src/gpu/vk/GrVkUniformHandler.cpp @@ -204,6 +204,16 @@ static void get_ubo_aligned_offset(uint32_t* uniformOffset, } } +GrVkUniformHandler::~GrVkUniformHandler() { + GrVkGpu* gpu = static_cast(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(texture); if (vkTexture->ycbcrConversionInfo().isValid()) { - SkASSERT(type == GrTextureType::kExternal); GrVkGpu* gpu = static_cast(fProgramBuilder)->gpu(); info.fImmutableSampler = gpu->resourceProvider().findOrCreateCompatibleSampler( state, vkTexture->ycbcrConversionInfo()); diff --git a/src/gpu/vk/GrVkUniformHandler.h b/src/gpu/vk/GrVkUniformHandler.h index 8d80f20733..f2809709a9 100644 --- a/src/gpu/vk/GrVkUniformHandler.h +++ b/src/gpu/vk/GrVkUniformHandler.h @@ -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 UniformInfoArray; + ~GrVkUniformHandler() override; + const GrShaderVar& getUniformVariable(UniformHandle u) const override { return fUniforms[u.toIndex()].fVariable; } diff --git a/src/gpu/vk/GrVkUtil.cpp b/src/gpu/vk/GrVkUtil.cpp index 395c02f039..80214ac294 100644 --- a/src/gpu/vk/GrVkUtil.cpp +++ b/src/gpu/vk/GrVkUtil.cpp @@ -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) { diff --git a/src/gpu/vk/GrVkUtil.h b/src/gpu/vk/GrVkUtil.h index d58f5855fe..dded319042 100644 --- a/src/gpu/vk/GrVkUtil.h +++ b/src/gpu/vk/GrVkUtil.h @@ -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. diff --git a/tests/VkYcbcrSamplerTest.cpp b/tests/VkYcbcrSamplerTest.cpp new file mode 100644 index 0000000000..a2ad96ede1 --- /dev/null +++ b/tests/VkYcbcrSamplerTest.cpp @@ -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(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(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 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 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( + 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 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(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(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 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(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 srcImage = helper.createI420Image(reporter); + if (!srcImage) { + return; + } + + sk_sp 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 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(getY(x, y)) - 16.0) / 219.0; + float uChannel = (static_cast(getU(x, y)) - 128.0) / 224.0; + float vChannel = (static_cast(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) diff --git a/tools/gpu/vk/VkTestUtils.cpp b/tools/gpu/vk/VkTestUtils.cpp index 1a8066f66b..5b7e8c29ae 100644 --- a/tools/gpu/vk/VkTestUtils.cpp +++ b/tools/gpu/vk/VkTestUtils.cpp @@ -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; }