| // |
| // Copyright 2016 The ANGLE Project Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| // |
| // vk_format_utils: |
| // Helper for Vulkan format code. |
| |
| #include "libANGLE/renderer/vulkan/vk_format_utils.h" |
| |
| #include "libANGLE/Texture.h" |
| #include "libANGLE/formatutils.h" |
| #include "libANGLE/renderer/load_functions_table.h" |
| #include "libANGLE/renderer/vulkan/ContextVk.h" |
| #include "libANGLE/renderer/vulkan/RendererVk.h" |
| #include "libANGLE/renderer/vulkan/vk_caps_utils.h" |
| |
| namespace rx |
| { |
| namespace |
| { |
| void FillTextureFormatCaps(RendererVk *renderer, |
| angle::FormatID formatID, |
| gl::TextureCaps *outTextureCaps) |
| { |
| const VkPhysicalDeviceLimits &physicalDeviceLimits = |
| renderer->getPhysicalDeviceProperties().limits; |
| bool hasColorAttachmentFeatureBit = |
| renderer->hasImageFormatFeatureBits(formatID, VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT); |
| bool hasDepthAttachmentFeatureBit = renderer->hasImageFormatFeatureBits( |
| formatID, VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT); |
| |
| outTextureCaps->texturable = |
| renderer->hasImageFormatFeatureBits(formatID, VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT); |
| outTextureCaps->filterable = renderer->hasImageFormatFeatureBits( |
| formatID, VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT); |
| outTextureCaps->blendable = |
| renderer->hasImageFormatFeatureBits(formatID, VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT); |
| |
| // For renderbuffer and texture attachments we require transfer and sampling for |
| // GLES 2.0 CopyTexImage support. Sampling is also required for other features like |
| // blits and EGLImages. |
| outTextureCaps->textureAttachment = |
| outTextureCaps->texturable && |
| (hasColorAttachmentFeatureBit || hasDepthAttachmentFeatureBit); |
| outTextureCaps->renderbuffer = outTextureCaps->textureAttachment; |
| |
| if (outTextureCaps->renderbuffer) |
| { |
| if (hasColorAttachmentFeatureBit) |
| { |
| vk_gl::AddSampleCounts(physicalDeviceLimits.framebufferColorSampleCounts, |
| &outTextureCaps->sampleCounts); |
| } |
| if (hasDepthAttachmentFeatureBit) |
| { |
| // Some drivers report different depth and stencil sample counts. We'll AND those |
| // counts together, limiting all depth and/or stencil formats to the lower number of |
| // sample counts. |
| vk_gl::AddSampleCounts((physicalDeviceLimits.framebufferDepthSampleCounts & |
| physicalDeviceLimits.framebufferStencilSampleCounts), |
| &outTextureCaps->sampleCounts); |
| } |
| } |
| } |
| |
| bool HasFullBufferFormatSupport(RendererVk *renderer, angle::FormatID formatID) |
| { |
| // Note: GL_EXT_texture_buffer support uses the same vkBufferFormat that is determined by |
| // Format::initBufferFallback, which uses this function. That relies on the fact that formats |
| // required for GL_EXT_texture_buffer all have mandatory VERTEX_BUFFER feature support in |
| // Vulkan. If this function is changed to test for more features in such a way that makes any |
| // of those formats use a fallback format, the implementation of GL_EXT_texture_buffer must be |
| // modified not to use vkBufferFormat. |
| return renderer->hasBufferFormatFeatureBits(formatID, VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT); |
| } |
| |
| using SupportTest = bool (*)(RendererVk *renderer, angle::FormatID formatID); |
| |
| template <class FormatInitInfo> |
| int FindSupportedFormat(RendererVk *renderer, |
| const FormatInitInfo *info, |
| size_t skip, |
| int numInfo, |
| SupportTest hasSupport) |
| { |
| ASSERT(numInfo > 0); |
| const int last = numInfo - 1; |
| |
| for (int i = static_cast<int>(skip); i < last; ++i) |
| { |
| ASSERT(info[i].format != angle::FormatID::NONE); |
| if (hasSupport(renderer, info[i].format)) |
| return i; |
| } |
| |
| if (skip > 0 && !hasSupport(renderer, info[last].format)) |
| { |
| // We couldn't find a valid fallback, try again without skip |
| return FindSupportedFormat(renderer, info, 0, numInfo, hasSupport); |
| } |
| |
| ASSERT(info[last].format != angle::FormatID::NONE); |
| ASSERT(hasSupport(renderer, info[last].format)); |
| return last; |
| } |
| |
| bool HasNonFilterableTextureFormatSupport(RendererVk *renderer, angle::FormatID formatID) |
| { |
| constexpr uint32_t kBitsColor = |
| VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT; |
| constexpr uint32_t kBitsDepth = VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT; |
| |
| return renderer->hasImageFormatFeatureBits(formatID, kBitsColor) || |
| renderer->hasImageFormatFeatureBits(formatID, kBitsDepth); |
| } |
| } // anonymous namespace |
| |
| namespace vk |
| { |
| // Format implementation. |
| Format::Format() |
| : mIntendedFormatID(angle::FormatID::NONE), |
| mIntendedGLFormat(GL_NONE), |
| mActualSampleOnlyImageFormatID(angle::FormatID::NONE), |
| mActualRenderableImageFormatID(angle::FormatID::NONE), |
| mActualBufferFormatID(angle::FormatID::NONE), |
| mActualCompressedBufferFormatID(angle::FormatID::NONE), |
| mImageInitializerFunction(nullptr), |
| mTextureLoadFunctions(), |
| mRenderableTextureLoadFunctions(), |
| mVertexLoadFunction(nullptr), |
| mCompressedVertexLoadFunction(nullptr), |
| mVertexLoadRequiresConversion(false), |
| mCompressedVertexLoadRequiresConversion(false), |
| mVkBufferFormatIsPacked(false), |
| mVkFormatIsInt(false), |
| mVkFormatIsUnsigned(false) |
| {} |
| |
| void Format::initImageFallback(RendererVk *renderer, const ImageFormatInitInfo *info, int numInfo) |
| { |
| size_t skip = renderer->getFeatures().forceFallbackFormat.enabled ? 1 : 0; |
| SupportTest testFunction = HasNonRenderableTextureFormatSupport; |
| const angle::Format &format = angle::Format::Get(info[0].format); |
| if (format.isInt() || (format.isFloat() && format.redBits >= 32)) |
| { |
| // Integer formats don't support filtering in GL, so don't test for it. |
| // Filtering of 32-bit float textures is not supported on Android, and |
| // it's enabled by the extension OES_texture_float_linear, which is |
| // enabled automatically by examining format capabilities. |
| testFunction = HasNonFilterableTextureFormatSupport; |
| } |
| |
| int i = FindSupportedFormat(renderer, info, skip, static_cast<uint32_t>(numInfo), testFunction); |
| mActualSampleOnlyImageFormatID = info[i].format; |
| mImageInitializerFunction = info[i].initializer; |
| |
| // Set renderable format. |
| if (testFunction != HasNonFilterableTextureFormatSupport && !format.isSnorm() && |
| !format.isBlock) |
| { |
| // Rendering to SNORM textures is not supported on Android, and it's |
| // enabled by the extension EXT_render_snorm. |
| // Compressed textures also need to perform this check. |
| testFunction = HasFullTextureFormatSupport; |
| i = FindSupportedFormat(renderer, info, skip, static_cast<uint32_t>(numInfo), testFunction); |
| mActualRenderableImageFormatID = info[i].format; |
| } |
| } |
| |
| void Format::initBufferFallback(RendererVk *renderer, |
| const BufferFormatInitInfo *info, |
| int numInfo, |
| int compressedStartIndex) |
| { |
| { |
| size_t skip = renderer->getFeatures().forceFallbackFormat.enabled ? 1 : 0; |
| int i = FindSupportedFormat(renderer, info, skip, compressedStartIndex, |
| HasFullBufferFormatSupport); |
| |
| mActualBufferFormatID = info[i].format; |
| mVkBufferFormatIsPacked = info[i].vkFormatIsPacked; |
| mVertexLoadFunction = info[i].vertexLoadFunction; |
| mVertexLoadRequiresConversion = info[i].vertexLoadRequiresConversion; |
| } |
| |
| if (renderer->getFeatures().compressVertexData.enabled && compressedStartIndex < numInfo) |
| { |
| int i = FindSupportedFormat(renderer, info, compressedStartIndex, numInfo, |
| HasFullBufferFormatSupport); |
| |
| mActualCompressedBufferFormatID = info[i].format; |
| mVkCompressedBufferFormatIsPacked = info[i].vkFormatIsPacked; |
| mCompressedVertexLoadFunction = info[i].vertexLoadFunction; |
| mCompressedVertexLoadRequiresConversion = info[i].vertexLoadRequiresConversion; |
| } |
| } |
| |
| size_t Format::getVertexInputAlignment(bool compressed) const |
| { |
| const angle::Format &bufferFormat = getActualBufferFormat(compressed); |
| size_t pixelBytes = bufferFormat.pixelBytes; |
| return mVkBufferFormatIsPacked ? pixelBytes : (pixelBytes / bufferFormat.channelCount); |
| } |
| |
| bool HasEmulatedImageChannels(const angle::Format &intendedFormat, |
| const angle::Format &actualFormat) |
| { |
| return (intendedFormat.alphaBits == 0 && actualFormat.alphaBits > 0) || |
| (intendedFormat.blueBits == 0 && actualFormat.blueBits > 0) || |
| (intendedFormat.greenBits == 0 && actualFormat.greenBits > 0) || |
| (intendedFormat.depthBits == 0 && actualFormat.depthBits > 0) || |
| (intendedFormat.stencilBits == 0 && actualFormat.stencilBits > 0); |
| } |
| |
| bool HasEmulatedImageFormat(angle::FormatID intendedFormatID, angle::FormatID actualFormatID) |
| { |
| return actualFormatID != intendedFormatID; |
| } |
| |
| bool operator==(const Format &lhs, const Format &rhs) |
| { |
| return &lhs == &rhs; |
| } |
| |
| bool operator!=(const Format &lhs, const Format &rhs) |
| { |
| return &lhs != &rhs; |
| } |
| |
| // FormatTable implementation. |
| FormatTable::FormatTable() {} |
| |
| FormatTable::~FormatTable() {} |
| |
| void FormatTable::initialize(RendererVk *renderer, gl::TextureCapsMap *outTextureCapsMap) |
| { |
| for (size_t formatIndex = 0; formatIndex < angle::kNumANGLEFormats; ++formatIndex) |
| { |
| Format &format = mFormatData[formatIndex]; |
| const auto intendedFormatID = static_cast<angle::FormatID>(formatIndex); |
| const angle::Format &intendedAngleFormat = angle::Format::Get(intendedFormatID); |
| |
| format.initialize(renderer, intendedAngleFormat); |
| format.mIntendedFormatID = intendedFormatID; |
| |
| if (!format.valid()) |
| { |
| continue; |
| } |
| |
| // No sample-able or render-able formats, so nothing left to do. This includes skipping the |
| // rest of the loop for buffer-only formats, since they are not texturable. |
| if (format.mActualSampleOnlyImageFormatID == angle::FormatID::NONE) |
| { |
| continue; |
| } |
| |
| if (format.mActualRenderableImageFormatID == angle::FormatID::NONE) |
| { |
| // If renderable format was not set, it means there is no fallback format for |
| // renderable. We populate this the same formatID as sampleOnly formatID so that |
| // getActualFormatID() will be simpler. |
| format.mActualRenderableImageFormatID = format.mActualSampleOnlyImageFormatID; |
| } |
| |
| gl::TextureCaps textureCaps; |
| FillTextureFormatCaps(renderer, format.mActualSampleOnlyImageFormatID, &textureCaps); |
| |
| if (textureCaps.texturable) |
| { |
| format.mTextureLoadFunctions = GetLoadFunctionsMap( |
| format.mIntendedGLFormat, format.mActualSampleOnlyImageFormatID); |
| } |
| |
| if (format.mActualRenderableImageFormatID == format.mActualSampleOnlyImageFormatID) |
| { |
| outTextureCapsMap->set(intendedFormatID, textureCaps); |
| format.mRenderableTextureLoadFunctions = format.mTextureLoadFunctions; |
| } |
| else |
| { |
| FillTextureFormatCaps(renderer, format.mActualRenderableImageFormatID, &textureCaps); |
| outTextureCapsMap->set(intendedFormatID, textureCaps); |
| if (textureCaps.texturable) |
| { |
| format.mRenderableTextureLoadFunctions = GetLoadFunctionsMap( |
| format.mIntendedGLFormat, format.mActualRenderableImageFormatID); |
| } |
| } |
| } |
| } |
| |
| size_t GetImageCopyBufferAlignment(angle::FormatID actualFormatID) |
| { |
| // vkCmdCopyBufferToImage must have an offset that is a multiple of 4 as well as a multiple |
| // of the texel size (if uncompressed) or pixel block size (if compressed). |
| // https://www.khronos.org/registry/vulkan/specs/1.0/man/html/VkBufferImageCopy.html |
| // |
| // We need lcm(4, texelSize) (lcm = least common multiplier). For compressed images, |
| // |texelSize| would contain the block size. Since 4 is constant, this can be calculated as: |
| // |
| // | texelSize texelSize % 4 == 0 |
| // | 4 * texelSize texelSize % 4 == 1 |
| // lcm(4, texelSize) = < |
| // | 2 * texelSize texelSize % 4 == 2 |
| // | 4 * texelSize texelSize % 4 == 3 |
| // |
| // This means: |
| // |
| // - texelSize % 2 != 0 gives a 4x multiplier |
| // - else texelSize % 4 != 0 gives a 2x multiplier |
| // - else there's no multiplier. |
| // |
| const angle::Format &actualFormat = angle::Format::Get(actualFormatID); |
| |
| ASSERT(actualFormat.pixelBytes != 0); |
| const size_t texelSize = actualFormat.pixelBytes; |
| const size_t multiplier = texelSize % 2 != 0 ? 4 : texelSize % 4 != 0 ? 2 : 1; |
| const size_t alignment = multiplier * texelSize; |
| |
| return alignment; |
| } |
| |
| size_t GetValidImageCopyBufferAlignment(angle::FormatID intendedFormatID, |
| angle::FormatID actualFormatID) |
| { |
| constexpr size_t kMinimumAlignment = 16; |
| return (intendedFormatID == angle::FormatID::NONE) |
| ? kMinimumAlignment |
| : GetImageCopyBufferAlignment(actualFormatID); |
| } |
| |
| VkImageUsageFlags GetMaximalImageUsageFlags(RendererVk *renderer, angle::FormatID formatID) |
| { |
| constexpr VkFormatFeatureFlags kImageUsageFeatureBits = |
| VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | |
| VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT | |
| VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT; |
| VkFormatFeatureFlags featureBits = |
| renderer->getImageFormatFeatureBits(formatID, kImageUsageFeatureBits); |
| VkImageUsageFlags imageUsageFlags = 0; |
| if (featureBits & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT) |
| imageUsageFlags |= VK_IMAGE_USAGE_SAMPLED_BIT; |
| if (featureBits & VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT) |
| imageUsageFlags |= VK_IMAGE_USAGE_STORAGE_BIT; |
| if (featureBits & VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT) |
| imageUsageFlags |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; |
| if (featureBits & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT) |
| imageUsageFlags |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; |
| if (featureBits & VK_FORMAT_FEATURE_TRANSFER_SRC_BIT) |
| imageUsageFlags |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT; |
| if (featureBits & VK_FORMAT_FEATURE_TRANSFER_DST_BIT) |
| imageUsageFlags |= VK_IMAGE_USAGE_TRANSFER_DST_BIT; |
| imageUsageFlags |= VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT; |
| return imageUsageFlags; |
| } |
| } // namespace vk |
| |
| bool HasFullTextureFormatSupport(RendererVk *renderer, angle::FormatID formatID) |
| { |
| constexpr uint32_t kBitsColor = VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | |
| VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | |
| VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT; |
| |
| // In OpenGL ES, all renderable formats except 32-bit floating-point support blending. |
| // 32-bit floating-point case validation is handled by ANGLE's frontend. |
| uint32_t kBitsColorFull = kBitsColor; |
| switch (formatID) |
| { |
| case angle::FormatID::R32_FLOAT: |
| case angle::FormatID::R32G32_FLOAT: |
| case angle::FormatID::R32G32B32A32_FLOAT: |
| break; |
| default: |
| kBitsColorFull |= VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT; |
| break; |
| } |
| |
| constexpr uint32_t kBitsDepth = VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT; |
| |
| return renderer->hasImageFormatFeatureBits(formatID, kBitsColorFull) || |
| renderer->hasImageFormatFeatureBits(formatID, kBitsDepth); |
| } |
| |
| bool HasNonRenderableTextureFormatSupport(RendererVk *renderer, angle::FormatID formatID) |
| { |
| constexpr uint32_t kBitsColor = |
| VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT; |
| constexpr uint32_t kBitsDepth = VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT; |
| |
| return renderer->hasImageFormatFeatureBits(formatID, kBitsColor) || |
| renderer->hasImageFormatFeatureBits(formatID, kBitsDepth); |
| } |
| |
| GLenum GetSwizzleStateComponent(const gl::SwizzleState &swizzleState, GLenum component) |
| { |
| switch (component) |
| { |
| case GL_RED: |
| return swizzleState.swizzleRed; |
| case GL_GREEN: |
| return swizzleState.swizzleGreen; |
| case GL_BLUE: |
| return swizzleState.swizzleBlue; |
| case GL_ALPHA: |
| return swizzleState.swizzleAlpha; |
| default: |
| return component; |
| } |
| } |
| |
| gl::SwizzleState ApplySwizzle(const gl::SwizzleState &formatSwizzle, |
| const gl::SwizzleState &toApply) |
| { |
| gl::SwizzleState result; |
| |
| result.swizzleRed = GetSwizzleStateComponent(formatSwizzle, toApply.swizzleRed); |
| result.swizzleGreen = GetSwizzleStateComponent(formatSwizzle, toApply.swizzleGreen); |
| result.swizzleBlue = GetSwizzleStateComponent(formatSwizzle, toApply.swizzleBlue); |
| result.swizzleAlpha = GetSwizzleStateComponent(formatSwizzle, toApply.swizzleAlpha); |
| |
| return result; |
| } |
| |
| gl::SwizzleState GetFormatSwizzle(const angle::Format &angleFormat, const bool sized) |
| { |
| gl::SwizzleState internalSwizzle; |
| |
| if (angleFormat.isLUMA()) |
| { |
| GLenum swizzleRGB, swizzleA; |
| if (angleFormat.luminanceBits > 0) |
| { |
| swizzleRGB = GL_RED; |
| swizzleA = (angleFormat.alphaBits > 0 ? GL_GREEN : GL_ONE); |
| } |
| else |
| { |
| swizzleRGB = GL_ZERO; |
| swizzleA = GL_RED; |
| } |
| internalSwizzle.swizzleRed = swizzleRGB; |
| internalSwizzle.swizzleGreen = swizzleRGB; |
| internalSwizzle.swizzleBlue = swizzleRGB; |
| internalSwizzle.swizzleAlpha = swizzleA; |
| } |
| else |
| { |
| if (angleFormat.hasDepthOrStencilBits()) |
| { |
| // In OES_depth_texture/ARB_depth_texture, depth |
| // textures are treated as luminance. |
| // If the internalformat was not sized, use OES_depth_texture behavior |
| bool hasGB = (angleFormat.depthBits > 0) && !sized; |
| |
| internalSwizzle.swizzleRed = GL_RED; |
| internalSwizzle.swizzleGreen = hasGB ? GL_RED : GL_ZERO; |
| internalSwizzle.swizzleBlue = hasGB ? GL_RED : GL_ZERO; |
| internalSwizzle.swizzleAlpha = GL_ONE; |
| } |
| else |
| { |
| // Color bits are all zero for blocked formats |
| if (!angleFormat.isBlock) |
| { |
| // Set any missing channel to default in case the emulated format has that channel. |
| internalSwizzle.swizzleRed = angleFormat.redBits > 0 ? GL_RED : GL_ZERO; |
| internalSwizzle.swizzleGreen = angleFormat.greenBits > 0 ? GL_GREEN : GL_ZERO; |
| internalSwizzle.swizzleBlue = angleFormat.blueBits > 0 ? GL_BLUE : GL_ZERO; |
| internalSwizzle.swizzleAlpha = angleFormat.alphaBits > 0 ? GL_ALPHA : GL_ONE; |
| } |
| } |
| } |
| |
| return internalSwizzle; |
| } |
| } // namespace rx |