summaryrefslogtreecommitdiffstats
path: root/gfx/angle/checkout/src/libANGLE/Texture.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'gfx/angle/checkout/src/libANGLE/Texture.cpp')
-rw-r--r--gfx/angle/checkout/src/libANGLE/Texture.cpp2494
1 files changed, 2494 insertions, 0 deletions
diff --git a/gfx/angle/checkout/src/libANGLE/Texture.cpp b/gfx/angle/checkout/src/libANGLE/Texture.cpp
new file mode 100644
index 0000000000..89930d1c29
--- /dev/null
+++ b/gfx/angle/checkout/src/libANGLE/Texture.cpp
@@ -0,0 +1,2494 @@
+//
+// Copyright 2002 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.
+//
+
+// Texture.cpp: Implements the gl::Texture class. [OpenGL ES 2.0.24] section 3.7 page 63.
+
+#include "libANGLE/Texture.h"
+
+#include "common/mathutil.h"
+#include "common/utilities.h"
+#include "libANGLE/Config.h"
+#include "libANGLE/Context.h"
+#include "libANGLE/Image.h"
+#include "libANGLE/State.h"
+#include "libANGLE/Surface.h"
+#include "libANGLE/formatutils.h"
+#include "libANGLE/renderer/GLImplFactory.h"
+#include "libANGLE/renderer/TextureImpl.h"
+
+namespace gl
+{
+
+namespace
+{
+constexpr angle::SubjectIndex kBufferSubjectIndex = 2;
+static_assert(kBufferSubjectIndex != rx::kTextureImageImplObserverMessageIndex, "Index collision");
+static_assert(kBufferSubjectIndex != rx::kTextureImageSiblingMessageIndex, "Index collision");
+
+bool IsPointSampled(const SamplerState &samplerState)
+{
+ return (samplerState.getMagFilter() == GL_NEAREST &&
+ (samplerState.getMinFilter() == GL_NEAREST ||
+ samplerState.getMinFilter() == GL_NEAREST_MIPMAP_NEAREST));
+}
+
+size_t GetImageDescIndex(TextureTarget target, size_t level)
+{
+ return IsCubeMapFaceTarget(target) ? (level * 6 + CubeMapTextureTargetToFaceIndex(target))
+ : level;
+}
+
+InitState DetermineInitState(const Context *context, Buffer *unpackBuffer, const uint8_t *pixels)
+{
+ // Can happen in tests.
+ if (!context || !context->isRobustResourceInitEnabled())
+ {
+ return InitState::Initialized;
+ }
+
+ return (!pixels && !unpackBuffer) ? InitState::MayNeedInit : InitState::Initialized;
+}
+} // namespace
+
+GLenum ConvertToNearestFilterMode(GLenum filterMode)
+{
+ switch (filterMode)
+ {
+ case GL_LINEAR:
+ return GL_NEAREST;
+ case GL_LINEAR_MIPMAP_NEAREST:
+ return GL_NEAREST_MIPMAP_NEAREST;
+ case GL_LINEAR_MIPMAP_LINEAR:
+ return GL_NEAREST_MIPMAP_LINEAR;
+ default:
+ return filterMode;
+ }
+}
+
+GLenum ConvertToNearestMipFilterMode(GLenum filterMode)
+{
+ switch (filterMode)
+ {
+ case GL_LINEAR_MIPMAP_LINEAR:
+ return GL_LINEAR_MIPMAP_NEAREST;
+ case GL_NEAREST_MIPMAP_LINEAR:
+ return GL_NEAREST_MIPMAP_NEAREST;
+ default:
+ return filterMode;
+ }
+}
+
+bool IsMipmapSupported(const TextureType &type)
+{
+ if (type == TextureType::_2DMultisample || type == TextureType::Buffer)
+ {
+ return false;
+ }
+ return true;
+}
+
+SwizzleState::SwizzleState()
+ : swizzleRed(GL_RED), swizzleGreen(GL_GREEN), swizzleBlue(GL_BLUE), swizzleAlpha(GL_ALPHA)
+{}
+
+SwizzleState::SwizzleState(GLenum red, GLenum green, GLenum blue, GLenum alpha)
+ : swizzleRed(red), swizzleGreen(green), swizzleBlue(blue), swizzleAlpha(alpha)
+{}
+
+bool SwizzleState::swizzleRequired() const
+{
+ return swizzleRed != GL_RED || swizzleGreen != GL_GREEN || swizzleBlue != GL_BLUE ||
+ swizzleAlpha != GL_ALPHA;
+}
+
+bool SwizzleState::operator==(const SwizzleState &other) const
+{
+ return swizzleRed == other.swizzleRed && swizzleGreen == other.swizzleGreen &&
+ swizzleBlue == other.swizzleBlue && swizzleAlpha == other.swizzleAlpha;
+}
+
+bool SwizzleState::operator!=(const SwizzleState &other) const
+{
+ return !(*this == other);
+}
+
+TextureState::TextureState(TextureType type)
+ : mType(type),
+ mSamplerState(SamplerState::CreateDefaultForTarget(type)),
+ mSrgbOverride(SrgbOverride::Default),
+ mBaseLevel(0),
+ mMaxLevel(kInitialMaxLevel),
+ mDepthStencilTextureMode(GL_DEPTH_COMPONENT),
+ mHasBeenBoundAsImage(false),
+ mIs3DAndHasBeenBoundAs2DImage(false),
+ mHasBeenBoundAsAttachment(false),
+ mImmutableFormat(false),
+ mImmutableLevels(0),
+ mUsage(GL_NONE),
+ mHasProtectedContent(false),
+ mImageDescs((IMPLEMENTATION_MAX_TEXTURE_LEVELS + 1) * (type == TextureType::CubeMap ? 6 : 1)),
+ mCropRect(0, 0, 0, 0),
+ mGenerateMipmapHint(GL_FALSE),
+ mInitState(InitState::Initialized),
+ mCachedSamplerFormat(SamplerFormat::InvalidEnum),
+ mCachedSamplerCompareMode(GL_NONE),
+ mCachedSamplerFormatValid(false)
+{}
+
+TextureState::~TextureState() {}
+
+bool TextureState::swizzleRequired() const
+{
+ return mSwizzleState.swizzleRequired();
+}
+
+GLuint TextureState::getEffectiveBaseLevel() const
+{
+ if (mImmutableFormat)
+ {
+ // GLES 3.0.4 section 3.8.10
+ return std::min(mBaseLevel, mImmutableLevels - 1);
+ }
+ // Some classes use the effective base level to index arrays with level data. By clamping the
+ // effective base level to max levels these arrays need just one extra item to store properties
+ // that should be returned for all out-of-range base level values, instead of needing special
+ // handling for out-of-range base levels.
+ return std::min(mBaseLevel, static_cast<GLuint>(IMPLEMENTATION_MAX_TEXTURE_LEVELS));
+}
+
+GLuint TextureState::getEffectiveMaxLevel() const
+{
+ if (mImmutableFormat)
+ {
+ // GLES 3.0.4 section 3.8.10
+ GLuint clampedMaxLevel = std::max(mMaxLevel, getEffectiveBaseLevel());
+ clampedMaxLevel = std::min(clampedMaxLevel, mImmutableLevels - 1);
+ return clampedMaxLevel;
+ }
+ return mMaxLevel;
+}
+
+GLuint TextureState::getMipmapMaxLevel() const
+{
+ const ImageDesc &baseImageDesc = getImageDesc(getBaseImageTarget(), getEffectiveBaseLevel());
+ GLuint expectedMipLevels = 0;
+ if (mType == TextureType::_3D)
+ {
+ const int maxDim = std::max(std::max(baseImageDesc.size.width, baseImageDesc.size.height),
+ baseImageDesc.size.depth);
+ expectedMipLevels = static_cast<GLuint>(log2(maxDim));
+ }
+ else
+ {
+ expectedMipLevels = static_cast<GLuint>(
+ log2(std::max(baseImageDesc.size.width, baseImageDesc.size.height)));
+ }
+
+ return std::min<GLuint>(getEffectiveBaseLevel() + expectedMipLevels, getEffectiveMaxLevel());
+}
+
+bool TextureState::setBaseLevel(GLuint baseLevel)
+{
+ if (mBaseLevel != baseLevel)
+ {
+ mBaseLevel = baseLevel;
+ return true;
+ }
+ return false;
+}
+
+bool TextureState::setMaxLevel(GLuint maxLevel)
+{
+ if (mMaxLevel != maxLevel)
+ {
+ mMaxLevel = maxLevel;
+ return true;
+ }
+
+ return false;
+}
+
+// Tests for cube texture completeness. [OpenGL ES 2.0.24] section 3.7.10 page 81.
+// According to [OpenGL ES 3.0.5] section 3.8.13 Texture Completeness page 160 any
+// per-level checks begin at the base-level.
+// For OpenGL ES2 the base level is always zero.
+bool TextureState::isCubeComplete() const
+{
+ ASSERT(mType == TextureType::CubeMap);
+
+ angle::EnumIterator<TextureTarget> face = kCubeMapTextureTargetMin;
+ const ImageDesc &baseImageDesc = getImageDesc(*face, getEffectiveBaseLevel());
+ if (baseImageDesc.size.width == 0 || baseImageDesc.size.width != baseImageDesc.size.height)
+ {
+ return false;
+ }
+
+ ++face;
+
+ for (; face != kAfterCubeMapTextureTargetMax; ++face)
+ {
+ const ImageDesc &faceImageDesc = getImageDesc(*face, getEffectiveBaseLevel());
+ if (faceImageDesc.size.width != baseImageDesc.size.width ||
+ faceImageDesc.size.height != baseImageDesc.size.height ||
+ !Format::SameSized(faceImageDesc.format, baseImageDesc.format))
+ {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+const ImageDesc &TextureState::getBaseLevelDesc() const
+{
+ ASSERT(mType != TextureType::CubeMap || isCubeComplete());
+ return getImageDesc(getBaseImageTarget(), getEffectiveBaseLevel());
+}
+
+const ImageDesc &TextureState::getLevelZeroDesc() const
+{
+ ASSERT(mType != TextureType::CubeMap || isCubeComplete());
+ return getImageDesc(getBaseImageTarget(), 0);
+}
+
+void TextureState::setCrop(const Rectangle &rect)
+{
+ mCropRect = rect;
+}
+
+const Rectangle &TextureState::getCrop() const
+{
+ return mCropRect;
+}
+
+void TextureState::setGenerateMipmapHint(GLenum hint)
+{
+ mGenerateMipmapHint = hint;
+}
+
+GLenum TextureState::getGenerateMipmapHint() const
+{
+ return mGenerateMipmapHint;
+}
+
+SamplerFormat TextureState::computeRequiredSamplerFormat(const SamplerState &samplerState) const
+{
+ const ImageDesc &baseImageDesc = getImageDesc(getBaseImageTarget(), getEffectiveBaseLevel());
+ if ((baseImageDesc.format.info->format == GL_DEPTH_COMPONENT ||
+ baseImageDesc.format.info->format == GL_DEPTH_STENCIL) &&
+ samplerState.getCompareMode() != GL_NONE)
+ {
+ return SamplerFormat::Shadow;
+ }
+ else
+ {
+ switch (baseImageDesc.format.info->componentType)
+ {
+ case GL_UNSIGNED_NORMALIZED:
+ case GL_SIGNED_NORMALIZED:
+ case GL_FLOAT:
+ return SamplerFormat::Float;
+ case GL_INT:
+ return SamplerFormat::Signed;
+ case GL_UNSIGNED_INT:
+ return SamplerFormat::Unsigned;
+ default:
+ return SamplerFormat::InvalidEnum;
+ }
+ }
+}
+
+bool TextureState::computeSamplerCompleteness(const SamplerState &samplerState,
+ const State &state) const
+{
+ // Buffer textures cannot be incomplete.
+ if (mType == TextureType::Buffer)
+ {
+ return true;
+ }
+
+ // Check for all non-format-based completeness rules
+ if (!computeSamplerCompletenessForCopyImage(samplerState, state))
+ {
+ return false;
+ }
+
+ const ImageDesc &baseImageDesc = getImageDesc(getBaseImageTarget(), getEffectiveBaseLevel());
+
+ // According to es 3.1 spec, texture is justified as incomplete if sized internalformat is
+ // unfilterable(table 20.11) and filter is not GL_NEAREST(8.16). The default value of minFilter
+ // is NEAREST_MIPMAP_LINEAR and magFilter is LINEAR(table 20.11,). For multismaple texture,
+ // filter state of multisample texture is ignored(11.1.3.3). So it shouldn't be judged as
+ // incomplete texture. So, we ignore filtering for multisample texture completeness here.
+ if (!IsMultisampled(mType) &&
+ !baseImageDesc.format.info->filterSupport(state.getClientVersion(),
+ state.getExtensions()) &&
+ !IsPointSampled(samplerState))
+ {
+ return false;
+ }
+
+ // OpenGLES 3.0.2 spec section 3.8.13 states that a texture is not mipmap complete if:
+ // The internalformat specified for the texture arrays is a sized internal depth or
+ // depth and stencil format (see table 3.13), the value of TEXTURE_COMPARE_-
+ // MODE is NONE, and either the magnification filter is not NEAREST or the mini-
+ // fication filter is neither NEAREST nor NEAREST_MIPMAP_NEAREST.
+ if (!IsMultisampled(mType) && baseImageDesc.format.info->depthBits > 0 &&
+ state.getClientMajorVersion() >= 3)
+ {
+ // Note: we restrict this validation to sized types. For the OES_depth_textures
+ // extension, due to some underspecification problems, we must allow linear filtering
+ // for legacy compatibility with WebGL 1.
+ // See http://crbug.com/649200
+ if (samplerState.getCompareMode() == GL_NONE && baseImageDesc.format.info->sized)
+ {
+ if ((samplerState.getMinFilter() != GL_NEAREST &&
+ samplerState.getMinFilter() != GL_NEAREST_MIPMAP_NEAREST) ||
+ samplerState.getMagFilter() != GL_NEAREST)
+ {
+ return false;
+ }
+ }
+ }
+
+ // OpenGLES 3.1 spec section 8.16 states that a texture is not mipmap complete if:
+ // The internalformat specified for the texture is DEPTH_STENCIL format, the value of
+ // DEPTH_STENCIL_TEXTURE_MODE is STENCIL_INDEX, and either the magnification filter is
+ // not NEAREST or the minification filter is neither NEAREST nor NEAREST_MIPMAP_NEAREST.
+ // However, the ES 3.1 spec differs from the statement above, because it is incorrect.
+ // See the issue at https://github.com/KhronosGroup/OpenGL-API/issues/33.
+ // For multismaple texture, filter state of multisample texture is ignored(11.1.3.3).
+ // So it shouldn't be judged as incomplete texture. So, we ignore filtering for multisample
+ // texture completeness here.
+ if (!IsMultisampled(mType) && baseImageDesc.format.info->depthBits > 0 &&
+ mDepthStencilTextureMode == GL_STENCIL_INDEX)
+ {
+ if ((samplerState.getMinFilter() != GL_NEAREST &&
+ samplerState.getMinFilter() != GL_NEAREST_MIPMAP_NEAREST) ||
+ samplerState.getMagFilter() != GL_NEAREST)
+ {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+// CopyImageSubData has more lax rules for texture completeness: format-based completeness rules are
+// ignored, so a texture can still be considered complete even if it violates format-specific
+// conditions
+bool TextureState::computeSamplerCompletenessForCopyImage(const SamplerState &samplerState,
+ const State &state) const
+{
+ // Buffer textures cannot be incomplete.
+ if (mType == TextureType::Buffer)
+ {
+ return true;
+ }
+
+ if (!mImmutableFormat && mBaseLevel > mMaxLevel)
+ {
+ return false;
+ }
+ const ImageDesc &baseImageDesc = getImageDesc(getBaseImageTarget(), getEffectiveBaseLevel());
+ if (baseImageDesc.size.width == 0 || baseImageDesc.size.height == 0 ||
+ baseImageDesc.size.depth == 0)
+ {
+ return false;
+ }
+ // The cases where the texture is incomplete because base level is out of range should be
+ // handled by the above condition.
+ ASSERT(mBaseLevel < IMPLEMENTATION_MAX_TEXTURE_LEVELS || mImmutableFormat);
+
+ if (mType == TextureType::CubeMap && baseImageDesc.size.width != baseImageDesc.size.height)
+ {
+ return false;
+ }
+
+ bool npotSupport = state.getExtensions().textureNpotOES || state.getClientMajorVersion() >= 3;
+ if (!npotSupport)
+ {
+ if ((samplerState.getWrapS() != GL_CLAMP_TO_EDGE &&
+ samplerState.getWrapS() != GL_CLAMP_TO_BORDER && !isPow2(baseImageDesc.size.width)) ||
+ (samplerState.getWrapT() != GL_CLAMP_TO_EDGE &&
+ samplerState.getWrapT() != GL_CLAMP_TO_BORDER && !isPow2(baseImageDesc.size.height)))
+ {
+ return false;
+ }
+ }
+
+ if (IsMipmapSupported(mType) && IsMipmapFiltered(samplerState.getMinFilter()))
+ {
+ if (!npotSupport)
+ {
+ if (!isPow2(baseImageDesc.size.width) || !isPow2(baseImageDesc.size.height))
+ {
+ return false;
+ }
+ }
+
+ if (!computeMipmapCompleteness())
+ {
+ return false;
+ }
+ }
+ else
+ {
+ if (mType == TextureType::CubeMap && !isCubeComplete())
+ {
+ return false;
+ }
+ }
+
+ // From GL_OES_EGL_image_external_essl3: If state is present in a sampler object bound to a
+ // texture unit that would have been rejected by a call to TexParameter* for the texture bound
+ // to that unit, the behavior of the implementation is as if the texture were incomplete. For
+ // example, if TEXTURE_WRAP_S or TEXTURE_WRAP_T is set to anything but CLAMP_TO_EDGE on the
+ // sampler object bound to a texture unit and the texture bound to that unit is an external
+ // texture and EXT_EGL_image_external_wrap_modes is not enabled, the texture will be considered
+ // incomplete.
+ // Sampler object state which does not affect sampling for the type of texture bound
+ // to a texture unit, such as TEXTURE_WRAP_R for an external texture, does not affect
+ // completeness.
+ if (mType == TextureType::External)
+ {
+ if (!state.getExtensions().EGLImageExternalWrapModesEXT)
+ {
+ if (samplerState.getWrapS() != GL_CLAMP_TO_EDGE ||
+ samplerState.getWrapT() != GL_CLAMP_TO_EDGE)
+ {
+ return false;
+ }
+ }
+
+ if (samplerState.getMinFilter() != GL_LINEAR && samplerState.getMinFilter() != GL_NEAREST)
+ {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool TextureState::computeMipmapCompleteness() const
+{
+ const GLuint maxLevel = getMipmapMaxLevel();
+
+ for (GLuint level = getEffectiveBaseLevel(); level <= maxLevel; level++)
+ {
+ if (mType == TextureType::CubeMap)
+ {
+ for (TextureTarget face : AllCubeFaceTextureTargets())
+ {
+ if (!computeLevelCompleteness(face, level))
+ {
+ return false;
+ }
+ }
+ }
+ else
+ {
+ if (!computeLevelCompleteness(NonCubeTextureTypeToTarget(mType), level))
+ {
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+bool TextureState::computeLevelCompleteness(TextureTarget target, size_t level) const
+{
+ ASSERT(level < IMPLEMENTATION_MAX_TEXTURE_LEVELS);
+
+ if (mImmutableFormat)
+ {
+ return true;
+ }
+
+ const ImageDesc &baseImageDesc = getImageDesc(getBaseImageTarget(), getEffectiveBaseLevel());
+ if (baseImageDesc.size.width == 0 || baseImageDesc.size.height == 0 ||
+ baseImageDesc.size.depth == 0)
+ {
+ return false;
+ }
+
+ const ImageDesc &levelImageDesc = getImageDesc(target, level);
+ if (levelImageDesc.size.width == 0 || levelImageDesc.size.height == 0 ||
+ levelImageDesc.size.depth == 0)
+ {
+ return false;
+ }
+
+ if (!Format::SameSized(levelImageDesc.format, baseImageDesc.format))
+ {
+ return false;
+ }
+
+ ASSERT(level >= getEffectiveBaseLevel());
+ const size_t relativeLevel = level - getEffectiveBaseLevel();
+ if (levelImageDesc.size.width != std::max(1, baseImageDesc.size.width >> relativeLevel))
+ {
+ return false;
+ }
+
+ if (levelImageDesc.size.height != std::max(1, baseImageDesc.size.height >> relativeLevel))
+ {
+ return false;
+ }
+
+ if (mType == TextureType::_3D)
+ {
+ if (levelImageDesc.size.depth != std::max(1, baseImageDesc.size.depth >> relativeLevel))
+ {
+ return false;
+ }
+ }
+ else if (IsArrayTextureType(mType))
+ {
+ if (levelImageDesc.size.depth != baseImageDesc.size.depth)
+ {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+TextureTarget TextureState::getBaseImageTarget() const
+{
+ return mType == TextureType::CubeMap ? kCubeMapTextureTargetMin
+ : NonCubeTextureTypeToTarget(mType);
+}
+
+GLuint TextureState::getEnabledLevelCount() const
+{
+ GLuint levelCount = 0;
+ const GLuint baseLevel = getEffectiveBaseLevel();
+ const GLuint maxLevel = std::min(getEffectiveMaxLevel(), getMipmapMaxLevel());
+
+ // The mip chain will have either one or more sequential levels, or max levels,
+ // but not a sparse one.
+ Optional<Extents> expectedSize;
+ for (size_t enabledLevel = baseLevel; enabledLevel <= maxLevel; ++enabledLevel, ++levelCount)
+ {
+ // Note: for cube textures, we only check the first face.
+ TextureTarget target = TextureTypeToTarget(mType, 0);
+ size_t descIndex = GetImageDescIndex(target, enabledLevel);
+ const Extents &levelSize = mImageDescs[descIndex].size;
+
+ if (levelSize.empty())
+ {
+ break;
+ }
+ if (expectedSize.valid())
+ {
+ Extents newSize = expectedSize.value();
+ newSize.width = std::max(1, newSize.width >> 1);
+ newSize.height = std::max(1, newSize.height >> 1);
+
+ if (!IsArrayTextureType(mType))
+ {
+ newSize.depth = std::max(1, newSize.depth >> 1);
+ }
+
+ if (newSize != levelSize)
+ {
+ break;
+ }
+ }
+ expectedSize = levelSize;
+ }
+
+ return levelCount;
+}
+
+ImageDesc::ImageDesc()
+ : ImageDesc(Extents(0, 0, 0), Format::Invalid(), 0, GL_TRUE, InitState::Initialized)
+{}
+
+ImageDesc::ImageDesc(const Extents &size, const Format &format, const InitState initState)
+ : size(size), format(format), samples(0), fixedSampleLocations(GL_TRUE), initState(initState)
+{}
+
+ImageDesc::ImageDesc(const Extents &size,
+ const Format &format,
+ const GLsizei samples,
+ const bool fixedSampleLocations,
+ const InitState initState)
+ : size(size),
+ format(format),
+ samples(samples),
+ fixedSampleLocations(fixedSampleLocations),
+ initState(initState)
+{}
+
+GLint ImageDesc::getMemorySize() const
+{
+ // Assume allocated size is around width * height * depth * samples * pixelBytes
+ angle::CheckedNumeric<GLint> levelSize = 1;
+ levelSize *= format.info->pixelBytes;
+ levelSize *= size.width;
+ levelSize *= size.height;
+ levelSize *= size.depth;
+ levelSize *= std::max(samples, 1);
+ return levelSize.ValueOrDefault(std::numeric_limits<GLint>::max());
+}
+
+const ImageDesc &TextureState::getImageDesc(TextureTarget target, size_t level) const
+{
+ size_t descIndex = GetImageDescIndex(target, level);
+ ASSERT(descIndex < mImageDescs.size());
+ return mImageDescs[descIndex];
+}
+
+void TextureState::setImageDesc(TextureTarget target, size_t level, const ImageDesc &desc)
+{
+ size_t descIndex = GetImageDescIndex(target, level);
+ ASSERT(descIndex < mImageDescs.size());
+ mImageDescs[descIndex] = desc;
+ if (desc.initState == InitState::MayNeedInit)
+ {
+ mInitState = InitState::MayNeedInit;
+ }
+ else
+ {
+ // Scan for any uninitialized images. If there are none, set the init state of the entire
+ // texture to initialized. The cost of the scan is only paid after doing image
+ // initialization which is already very expensive.
+ bool allImagesInitialized = true;
+
+ for (const ImageDesc &initDesc : mImageDescs)
+ {
+ if (initDesc.initState == InitState::MayNeedInit)
+ {
+ allImagesInitialized = false;
+ break;
+ }
+ }
+
+ if (allImagesInitialized)
+ {
+ mInitState = InitState::Initialized;
+ }
+ }
+}
+
+// Note that an ImageIndex that represents an entire level of a cube map corresponds to 6
+// ImageDescs, so if the cube map is cube complete, we return the ImageDesc of the first cube
+// face, and we don't allow using this function when the cube map is not cube complete.
+const ImageDesc &TextureState::getImageDesc(const ImageIndex &imageIndex) const
+{
+ if (imageIndex.isEntireLevelCubeMap())
+ {
+ ASSERT(isCubeComplete());
+ const GLint levelIndex = imageIndex.getLevelIndex();
+ return getImageDesc(kCubeMapTextureTargetMin, levelIndex);
+ }
+
+ return getImageDesc(imageIndex.getTarget(), imageIndex.getLevelIndex());
+}
+
+void TextureState::setImageDescChain(GLuint baseLevel,
+ GLuint maxLevel,
+ Extents baseSize,
+ const Format &format,
+ InitState initState)
+{
+ for (GLuint level = baseLevel; level <= maxLevel; level++)
+ {
+ int relativeLevel = (level - baseLevel);
+ Extents levelSize(std::max<int>(baseSize.width >> relativeLevel, 1),
+ std::max<int>(baseSize.height >> relativeLevel, 1),
+ (IsArrayTextureType(mType))
+ ? baseSize.depth
+ : std::max<int>(baseSize.depth >> relativeLevel, 1));
+ ImageDesc levelInfo(levelSize, format, initState);
+
+ if (mType == TextureType::CubeMap)
+ {
+ for (TextureTarget face : AllCubeFaceTextureTargets())
+ {
+ setImageDesc(face, level, levelInfo);
+ }
+ }
+ else
+ {
+ setImageDesc(NonCubeTextureTypeToTarget(mType), level, levelInfo);
+ }
+ }
+}
+
+void TextureState::setImageDescChainMultisample(Extents baseSize,
+ const Format &format,
+ GLsizei samples,
+ bool fixedSampleLocations,
+ InitState initState)
+{
+ ASSERT(mType == TextureType::_2DMultisample || mType == TextureType::_2DMultisampleArray);
+ ImageDesc levelInfo(baseSize, format, samples, fixedSampleLocations, initState);
+ setImageDesc(NonCubeTextureTypeToTarget(mType), 0, levelInfo);
+}
+
+void TextureState::clearImageDesc(TextureTarget target, size_t level)
+{
+ setImageDesc(target, level, ImageDesc());
+}
+
+void TextureState::clearImageDescs()
+{
+ for (size_t descIndex = 0; descIndex < mImageDescs.size(); descIndex++)
+ {
+ mImageDescs[descIndex] = ImageDesc();
+ }
+}
+
+Texture::Texture(rx::GLImplFactory *factory, TextureID id, TextureType type)
+ : RefCountObject(factory->generateSerial(), id),
+ mState(type),
+ mTexture(factory->createTexture(mState)),
+ mImplObserver(this, rx::kTextureImageImplObserverMessageIndex),
+ mBufferObserver(this, kBufferSubjectIndex),
+ mBoundSurface(nullptr),
+ mBoundStream(nullptr)
+{
+ mImplObserver.bind(mTexture);
+
+ // Initially assume the implementation is dirty.
+ mDirtyBits.set(DIRTY_BIT_IMPLEMENTATION);
+}
+
+void Texture::onDestroy(const Context *context)
+{
+ if (mBoundSurface)
+ {
+ ANGLE_SWALLOW_ERR(mBoundSurface->releaseTexImage(context, EGL_BACK_BUFFER));
+ mBoundSurface = nullptr;
+ }
+ if (mBoundStream)
+ {
+ mBoundStream->releaseTextures();
+ mBoundStream = nullptr;
+ }
+
+ egl::RefCountObjectReleaser<egl::Image> releaseImage;
+ (void)orphanImages(context, &releaseImage);
+
+ mState.mBuffer.set(context, nullptr, 0, 0);
+
+ if (mTexture)
+ {
+ mTexture->onDestroy(context);
+ }
+}
+
+Texture::~Texture()
+{
+ SafeDelete(mTexture);
+}
+
+angle::Result Texture::setLabel(const Context *context, const std::string &label)
+{
+ mState.mLabel = label;
+ return mTexture->onLabelUpdate(context);
+}
+
+const std::string &Texture::getLabel() const
+{
+ return mState.mLabel;
+}
+
+void Texture::setSwizzleRed(const Context *context, GLenum swizzleRed)
+{
+ if (mState.mSwizzleState.swizzleRed != swizzleRed)
+ {
+ mState.mSwizzleState.swizzleRed = swizzleRed;
+ signalDirtyState(DIRTY_BIT_SWIZZLE_RED);
+ }
+}
+
+GLenum Texture::getSwizzleRed() const
+{
+ return mState.mSwizzleState.swizzleRed;
+}
+
+void Texture::setSwizzleGreen(const Context *context, GLenum swizzleGreen)
+{
+ if (mState.mSwizzleState.swizzleGreen != swizzleGreen)
+ {
+ mState.mSwizzleState.swizzleGreen = swizzleGreen;
+ signalDirtyState(DIRTY_BIT_SWIZZLE_GREEN);
+ }
+}
+
+GLenum Texture::getSwizzleGreen() const
+{
+ return mState.mSwizzleState.swizzleGreen;
+}
+
+void Texture::setSwizzleBlue(const Context *context, GLenum swizzleBlue)
+{
+ if (mState.mSwizzleState.swizzleBlue != swizzleBlue)
+ {
+ mState.mSwizzleState.swizzleBlue = swizzleBlue;
+ signalDirtyState(DIRTY_BIT_SWIZZLE_BLUE);
+ }
+}
+
+GLenum Texture::getSwizzleBlue() const
+{
+ return mState.mSwizzleState.swizzleBlue;
+}
+
+void Texture::setSwizzleAlpha(const Context *context, GLenum swizzleAlpha)
+{
+ if (mState.mSwizzleState.swizzleAlpha != swizzleAlpha)
+ {
+ mState.mSwizzleState.swizzleAlpha = swizzleAlpha;
+ signalDirtyState(DIRTY_BIT_SWIZZLE_ALPHA);
+ }
+}
+
+GLenum Texture::getSwizzleAlpha() const
+{
+ return mState.mSwizzleState.swizzleAlpha;
+}
+
+void Texture::setMinFilter(const Context *context, GLenum minFilter)
+{
+ if (mState.mSamplerState.setMinFilter(minFilter))
+ {
+ signalDirtyState(DIRTY_BIT_MIN_FILTER);
+ }
+}
+
+GLenum Texture::getMinFilter() const
+{
+ return mState.mSamplerState.getMinFilter();
+}
+
+void Texture::setMagFilter(const Context *context, GLenum magFilter)
+{
+ if (mState.mSamplerState.setMagFilter(magFilter))
+ {
+ signalDirtyState(DIRTY_BIT_MAG_FILTER);
+ }
+}
+
+GLenum Texture::getMagFilter() const
+{
+ return mState.mSamplerState.getMagFilter();
+}
+
+void Texture::setWrapS(const Context *context, GLenum wrapS)
+{
+ if (mState.mSamplerState.setWrapS(wrapS))
+ {
+ signalDirtyState(DIRTY_BIT_WRAP_S);
+ }
+}
+
+GLenum Texture::getWrapS() const
+{
+ return mState.mSamplerState.getWrapS();
+}
+
+void Texture::setWrapT(const Context *context, GLenum wrapT)
+{
+ if (mState.mSamplerState.getWrapT() == wrapT)
+ return;
+ if (mState.mSamplerState.setWrapT(wrapT))
+ {
+ signalDirtyState(DIRTY_BIT_WRAP_T);
+ }
+}
+
+GLenum Texture::getWrapT() const
+{
+ return mState.mSamplerState.getWrapT();
+}
+
+void Texture::setWrapR(const Context *context, GLenum wrapR)
+{
+ if (mState.mSamplerState.setWrapR(wrapR))
+ {
+ signalDirtyState(DIRTY_BIT_WRAP_R);
+ }
+}
+
+GLenum Texture::getWrapR() const
+{
+ return mState.mSamplerState.getWrapR();
+}
+
+void Texture::setMaxAnisotropy(const Context *context, float maxAnisotropy)
+{
+ if (mState.mSamplerState.setMaxAnisotropy(maxAnisotropy))
+ {
+ signalDirtyState(DIRTY_BIT_MAX_ANISOTROPY);
+ }
+}
+
+float Texture::getMaxAnisotropy() const
+{
+ return mState.mSamplerState.getMaxAnisotropy();
+}
+
+void Texture::setMinLod(const Context *context, GLfloat minLod)
+{
+ if (mState.mSamplerState.setMinLod(minLod))
+ {
+ signalDirtyState(DIRTY_BIT_MIN_LOD);
+ }
+}
+
+GLfloat Texture::getMinLod() const
+{
+ return mState.mSamplerState.getMinLod();
+}
+
+void Texture::setMaxLod(const Context *context, GLfloat maxLod)
+{
+ if (mState.mSamplerState.setMaxLod(maxLod))
+ {
+ signalDirtyState(DIRTY_BIT_MAX_LOD);
+ }
+}
+
+GLfloat Texture::getMaxLod() const
+{
+ return mState.mSamplerState.getMaxLod();
+}
+
+void Texture::setCompareMode(const Context *context, GLenum compareMode)
+{
+ if (mState.mSamplerState.setCompareMode(compareMode))
+ {
+ signalDirtyState(DIRTY_BIT_COMPARE_MODE);
+ }
+}
+
+GLenum Texture::getCompareMode() const
+{
+ return mState.mSamplerState.getCompareMode();
+}
+
+void Texture::setCompareFunc(const Context *context, GLenum compareFunc)
+{
+ if (mState.mSamplerState.setCompareFunc(compareFunc))
+ {
+ signalDirtyState(DIRTY_BIT_COMPARE_FUNC);
+ }
+}
+
+GLenum Texture::getCompareFunc() const
+{
+ return mState.mSamplerState.getCompareFunc();
+}
+
+void Texture::setSRGBDecode(const Context *context, GLenum sRGBDecode)
+{
+ if (mState.mSamplerState.setSRGBDecode(sRGBDecode))
+ {
+ signalDirtyState(DIRTY_BIT_SRGB_DECODE);
+ }
+}
+
+GLenum Texture::getSRGBDecode() const
+{
+ return mState.mSamplerState.getSRGBDecode();
+}
+
+void Texture::setSRGBOverride(const Context *context, GLenum sRGBOverride)
+{
+ SrgbOverride oldOverride = mState.mSrgbOverride;
+ mState.mSrgbOverride = (sRGBOverride == GL_SRGB) ? SrgbOverride::SRGB : SrgbOverride::Default;
+ if (mState.mSrgbOverride != oldOverride)
+ {
+ signalDirtyState(DIRTY_BIT_SRGB_OVERRIDE);
+ }
+}
+
+GLenum Texture::getSRGBOverride() const
+{
+ return (mState.mSrgbOverride == SrgbOverride::SRGB) ? GL_SRGB : GL_NONE;
+}
+
+const SamplerState &Texture::getSamplerState() const
+{
+ return mState.mSamplerState;
+}
+
+angle::Result Texture::setBaseLevel(const Context *context, GLuint baseLevel)
+{
+ if (mState.setBaseLevel(baseLevel))
+ {
+ ANGLE_TRY(mTexture->setBaseLevel(context, mState.getEffectiveBaseLevel()));
+ signalDirtyState(DIRTY_BIT_BASE_LEVEL);
+ }
+
+ return angle::Result::Continue;
+}
+
+GLuint Texture::getBaseLevel() const
+{
+ return mState.mBaseLevel;
+}
+
+void Texture::setMaxLevel(const Context *context, GLuint maxLevel)
+{
+ if (mState.setMaxLevel(maxLevel))
+ {
+ signalDirtyState(DIRTY_BIT_MAX_LEVEL);
+ }
+}
+
+GLuint Texture::getMaxLevel() const
+{
+ return mState.mMaxLevel;
+}
+
+void Texture::setDepthStencilTextureMode(const Context *context, GLenum mode)
+{
+ if (mState.mDepthStencilTextureMode != mode)
+ {
+ mState.mDepthStencilTextureMode = mode;
+ signalDirtyState(DIRTY_BIT_DEPTH_STENCIL_TEXTURE_MODE);
+ }
+}
+
+GLenum Texture::getDepthStencilTextureMode() const
+{
+ return mState.mDepthStencilTextureMode;
+}
+
+bool Texture::getImmutableFormat() const
+{
+ return mState.mImmutableFormat;
+}
+
+GLuint Texture::getImmutableLevels() const
+{
+ return mState.mImmutableLevels;
+}
+
+void Texture::setUsage(const Context *context, GLenum usage)
+{
+ mState.mUsage = usage;
+ signalDirtyState(DIRTY_BIT_USAGE);
+}
+
+GLenum Texture::getUsage() const
+{
+ return mState.mUsage;
+}
+
+void Texture::setProtectedContent(Context *context, bool hasProtectedContent)
+{
+ mState.mHasProtectedContent = hasProtectedContent;
+}
+
+bool Texture::hasProtectedContent() const
+{
+ return mState.mHasProtectedContent;
+}
+
+const TextureState &Texture::getTextureState() const
+{
+ return mState;
+}
+
+const Extents &Texture::getExtents(TextureTarget target, size_t level) const
+{
+ ASSERT(TextureTargetToType(target) == mState.mType);
+ return mState.getImageDesc(target, level).size;
+}
+
+size_t Texture::getWidth(TextureTarget target, size_t level) const
+{
+ ASSERT(TextureTargetToType(target) == mState.mType);
+ return mState.getImageDesc(target, level).size.width;
+}
+
+size_t Texture::getHeight(TextureTarget target, size_t level) const
+{
+ ASSERT(TextureTargetToType(target) == mState.mType);
+ return mState.getImageDesc(target, level).size.height;
+}
+
+size_t Texture::getDepth(TextureTarget target, size_t level) const
+{
+ ASSERT(TextureTargetToType(target) == mState.mType);
+ return mState.getImageDesc(target, level).size.depth;
+}
+
+const Format &Texture::getFormat(TextureTarget target, size_t level) const
+{
+ ASSERT(TextureTargetToType(target) == mState.mType);
+ return mState.getImageDesc(target, level).format;
+}
+
+GLsizei Texture::getSamples(TextureTarget target, size_t level) const
+{
+ ASSERT(TextureTargetToType(target) == mState.mType);
+ return mState.getImageDesc(target, level).samples;
+}
+
+bool Texture::getFixedSampleLocations(TextureTarget target, size_t level) const
+{
+ ASSERT(TextureTargetToType(target) == mState.mType);
+ return mState.getImageDesc(target, level).fixedSampleLocations;
+}
+
+GLuint Texture::getMipmapMaxLevel() const
+{
+ return mState.getMipmapMaxLevel();
+}
+
+bool Texture::isMipmapComplete() const
+{
+ return mState.computeMipmapCompleteness();
+}
+
+egl::Surface *Texture::getBoundSurface() const
+{
+ return mBoundSurface;
+}
+
+egl::Stream *Texture::getBoundStream() const
+{
+ return mBoundStream;
+}
+
+GLint Texture::getMemorySize() const
+{
+ GLint implSize = mTexture->getMemorySize();
+ if (implSize > 0)
+ {
+ return implSize;
+ }
+
+ angle::CheckedNumeric<GLint> size = 0;
+ for (const ImageDesc &imageDesc : mState.mImageDescs)
+ {
+ size += imageDesc.getMemorySize();
+ }
+ return size.ValueOrDefault(std::numeric_limits<GLint>::max());
+}
+
+GLint Texture::getLevelMemorySize(TextureTarget target, GLint level) const
+{
+ GLint implSize = mTexture->getLevelMemorySize(target, level);
+ if (implSize > 0)
+ {
+ return implSize;
+ }
+
+ return mState.getImageDesc(target, level).getMemorySize();
+}
+
+void Texture::signalDirtyStorage(InitState initState)
+{
+ mState.mInitState = initState;
+ invalidateCompletenessCache();
+ mState.mCachedSamplerFormatValid = false;
+ onStateChange(angle::SubjectMessage::SubjectChanged);
+}
+
+void Texture::signalDirtyState(size_t dirtyBit)
+{
+ mDirtyBits.set(dirtyBit);
+ invalidateCompletenessCache();
+ mState.mCachedSamplerFormatValid = false;
+
+ if (dirtyBit == DIRTY_BIT_BASE_LEVEL || dirtyBit == DIRTY_BIT_MAX_LEVEL)
+ {
+ onStateChange(angle::SubjectMessage::SubjectChanged);
+ }
+ else
+ {
+ onStateChange(angle::SubjectMessage::DirtyBitsFlagged);
+ }
+}
+
+angle::Result Texture::setImage(Context *context,
+ const PixelUnpackState &unpackState,
+ Buffer *unpackBuffer,
+ TextureTarget target,
+ GLint level,
+ GLenum internalFormat,
+ const Extents &size,
+ GLenum format,
+ GLenum type,
+ const uint8_t *pixels)
+{
+ ASSERT(TextureTargetToType(target) == mState.mType);
+
+ // Release from previous calls to eglBindTexImage, to avoid calling the Impl after
+ ANGLE_TRY(releaseTexImageInternal(context));
+
+ egl::RefCountObjectReleaser<egl::Image> releaseImage;
+ ANGLE_TRY(orphanImages(context, &releaseImage));
+
+ ImageIndex index = ImageIndex::MakeFromTarget(target, level, size.depth);
+
+ ANGLE_TRY(mTexture->setImage(context, index, internalFormat, size, format, type, unpackState,
+ unpackBuffer, pixels));
+
+ InitState initState = DetermineInitState(context, unpackBuffer, pixels);
+ mState.setImageDesc(target, level, ImageDesc(size, Format(internalFormat, type), initState));
+
+ ANGLE_TRY(handleMipmapGenerationHint(context, level));
+
+ signalDirtyStorage(initState);
+
+ return angle::Result::Continue;
+}
+
+angle::Result Texture::setSubImage(Context *context,
+ const PixelUnpackState &unpackState,
+ Buffer *unpackBuffer,
+ TextureTarget target,
+ GLint level,
+ const Box &area,
+ GLenum format,
+ GLenum type,
+ const uint8_t *pixels)
+{
+ ASSERT(TextureTargetToType(target) == mState.mType);
+
+ ImageIndex index = ImageIndex::MakeFromTarget(target, level, area.depth);
+ ANGLE_TRY(ensureSubImageInitialized(context, index, area));
+
+ ANGLE_TRY(mTexture->setSubImage(context, index, area, format, type, unpackState, unpackBuffer,
+ pixels));
+
+ ANGLE_TRY(handleMipmapGenerationHint(context, level));
+
+ onStateChange(angle::SubjectMessage::ContentsChanged);
+
+ return angle::Result::Continue;
+}
+
+angle::Result Texture::setCompressedImage(Context *context,
+ const PixelUnpackState &unpackState,
+ TextureTarget target,
+ GLint level,
+ GLenum internalFormat,
+ const Extents &size,
+ size_t imageSize,
+ const uint8_t *pixels)
+{
+ ASSERT(TextureTargetToType(target) == mState.mType);
+
+ // Release from previous calls to eglBindTexImage, to avoid calling the Impl after
+ ANGLE_TRY(releaseTexImageInternal(context));
+
+ egl::RefCountObjectReleaser<egl::Image> releaseImage;
+ ANGLE_TRY(orphanImages(context, &releaseImage));
+
+ ImageIndex index = ImageIndex::MakeFromTarget(target, level, size.depth);
+
+ ANGLE_TRY(mTexture->setCompressedImage(context, index, internalFormat, size, unpackState,
+ imageSize, pixels));
+
+ Buffer *unpackBuffer = context->getState().getTargetBuffer(BufferBinding::PixelUnpack);
+
+ InitState initState = DetermineInitState(context, unpackBuffer, pixels);
+ mState.setImageDesc(target, level, ImageDesc(size, Format(internalFormat), initState));
+ signalDirtyStorage(initState);
+
+ return angle::Result::Continue;
+}
+
+angle::Result Texture::setCompressedSubImage(const Context *context,
+ const PixelUnpackState &unpackState,
+ TextureTarget target,
+ GLint level,
+ const Box &area,
+ GLenum format,
+ size_t imageSize,
+ const uint8_t *pixels)
+{
+ ASSERT(TextureTargetToType(target) == mState.mType);
+
+ ImageIndex index = ImageIndex::MakeFromTarget(target, level, area.depth);
+ ANGLE_TRY(ensureSubImageInitialized(context, index, area));
+
+ ANGLE_TRY(mTexture->setCompressedSubImage(context, index, area, format, unpackState, imageSize,
+ pixels));
+
+ onStateChange(angle::SubjectMessage::ContentsChanged);
+
+ return angle::Result::Continue;
+}
+
+angle::Result Texture::copyImage(Context *context,
+ TextureTarget target,
+ GLint level,
+ const Rectangle &sourceArea,
+ GLenum internalFormat,
+ Framebuffer *source)
+{
+ ASSERT(TextureTargetToType(target) == mState.mType);
+
+ // Release from previous calls to eglBindTexImage, to avoid calling the Impl after
+ ANGLE_TRY(releaseTexImageInternal(context));
+
+ egl::RefCountObjectReleaser<egl::Image> releaseImage;
+ ANGLE_TRY(orphanImages(context, &releaseImage));
+
+ ImageIndex index = ImageIndex::MakeFromTarget(target, level, 1);
+
+ const InternalFormat &internalFormatInfo =
+ GetInternalFormatInfo(internalFormat, GL_UNSIGNED_BYTE);
+
+ // Most if not all renderers clip these copies to the size of the source framebuffer, leaving
+ // other pixels untouched. For safety in robust resource initialization, assume that that
+ // clipping is going to occur when computing the region for which to ensure initialization. If
+ // the copy lies entirely off the source framebuffer, initialize as though a zero-size box is
+ // going to be set during the copy operation.
+ Box destBox;
+ bool forceCopySubImage = false;
+ if (context->isRobustResourceInitEnabled())
+ {
+ const FramebufferAttachment *sourceReadAttachment = source->getReadColorAttachment();
+ Extents fbSize = sourceReadAttachment->getSize();
+ // Force using copySubImage when the source area is out of bounds AND
+ // we're not copying to and from the same texture
+ forceCopySubImage = ((sourceArea.x < 0) || (sourceArea.y < 0) ||
+ ((sourceArea.x + sourceArea.width) > fbSize.width) ||
+ ((sourceArea.y + sourceArea.height) > fbSize.height)) &&
+ (sourceReadAttachment->getResource() != this);
+ Rectangle clippedArea;
+ if (ClipRectangle(sourceArea, Rectangle(0, 0, fbSize.width, fbSize.height), &clippedArea))
+ {
+ const Offset clippedOffset(clippedArea.x - sourceArea.x, clippedArea.y - sourceArea.y,
+ 0);
+ destBox = Box(clippedOffset.x, clippedOffset.y, clippedOffset.z, clippedArea.width,
+ clippedArea.height, 1);
+ }
+ }
+
+ InitState initState = DetermineInitState(context, nullptr, nullptr);
+
+ // If we need to initialize the destination texture we split the call into a create call,
+ // an initializeContents call, and then a copySubImage call. This ensures the destination
+ // texture exists before we try to clear it.
+ Extents size(sourceArea.width, sourceArea.height, 1);
+ if (forceCopySubImage || doesSubImageNeedInit(context, index, destBox))
+ {
+ ANGLE_TRY(mTexture->setImage(context, index, internalFormat, size,
+ internalFormatInfo.format, internalFormatInfo.type,
+ PixelUnpackState(), nullptr, nullptr));
+ mState.setImageDesc(target, level, ImageDesc(size, Format(internalFormatInfo), initState));
+ ANGLE_TRY(ensureSubImageInitialized(context, index, destBox));
+ ANGLE_TRY(mTexture->copySubImage(context, index, Offset(), sourceArea, source));
+ }
+ else
+ {
+ ANGLE_TRY(mTexture->copyImage(context, index, sourceArea, internalFormat, source));
+ }
+
+ mState.setImageDesc(target, level,
+ ImageDesc(size, Format(internalFormatInfo), InitState::Initialized));
+
+ ANGLE_TRY(handleMipmapGenerationHint(context, level));
+
+ // Because this could affect the texture storage we might need to init other layers/levels.
+ signalDirtyStorage(initState);
+
+ return angle::Result::Continue;
+}
+
+angle::Result Texture::copySubImage(Context *context,
+ const ImageIndex &index,
+ const Offset &destOffset,
+ const Rectangle &sourceArea,
+ Framebuffer *source)
+{
+ ASSERT(TextureTargetToType(index.getTarget()) == mState.mType);
+
+ // Most if not all renderers clip these copies to the size of the source framebuffer, leaving
+ // other pixels untouched. For safety in robust resource initialization, assume that that
+ // clipping is going to occur when computing the region for which to ensure initialization. If
+ // the copy lies entirely off the source framebuffer, initialize as though a zero-size box is
+ // going to be set during the copy operation. Note that this assumes that
+ // ensureSubImageInitialized ensures initialization of the entire destination texture, and not
+ // just a sub-region.
+ Box destBox;
+ if (context->isRobustResourceInitEnabled())
+ {
+ Extents fbSize = source->getReadColorAttachment()->getSize();
+ Rectangle clippedArea;
+ if (ClipRectangle(sourceArea, Rectangle(0, 0, fbSize.width, fbSize.height), &clippedArea))
+ {
+ const Offset clippedOffset(destOffset.x + clippedArea.x - sourceArea.x,
+ destOffset.y + clippedArea.y - sourceArea.y, 0);
+ destBox = Box(clippedOffset.x, clippedOffset.y, clippedOffset.z, clippedArea.width,
+ clippedArea.height, 1);
+ }
+ }
+
+ ANGLE_TRY(ensureSubImageInitialized(context, index, destBox));
+
+ ANGLE_TRY(mTexture->copySubImage(context, index, destOffset, sourceArea, source));
+ ANGLE_TRY(handleMipmapGenerationHint(context, index.getLevelIndex()));
+
+ onStateChange(angle::SubjectMessage::ContentsChanged);
+
+ return angle::Result::Continue;
+}
+
+angle::Result Texture::copyRenderbufferSubData(Context *context,
+ const gl::Renderbuffer *srcBuffer,
+ GLint srcLevel,
+ GLint srcX,
+ GLint srcY,
+ GLint srcZ,
+ GLint dstLevel,
+ GLint dstX,
+ GLint dstY,
+ GLint dstZ,
+ GLsizei srcWidth,
+ GLsizei srcHeight,
+ GLsizei srcDepth)
+{
+ ANGLE_TRY(mTexture->copyRenderbufferSubData(context, srcBuffer, srcLevel, srcX, srcY, srcZ,
+ dstLevel, dstX, dstY, dstZ, srcWidth, srcHeight,
+ srcDepth));
+
+ signalDirtyStorage(InitState::Initialized);
+
+ return angle::Result::Continue;
+}
+
+angle::Result Texture::copyTextureSubData(Context *context,
+ const gl::Texture *srcTexture,
+ GLint srcLevel,
+ GLint srcX,
+ GLint srcY,
+ GLint srcZ,
+ GLint dstLevel,
+ GLint dstX,
+ GLint dstY,
+ GLint dstZ,
+ GLsizei srcWidth,
+ GLsizei srcHeight,
+ GLsizei srcDepth)
+{
+ ANGLE_TRY(mTexture->copyTextureSubData(context, srcTexture, srcLevel, srcX, srcY, srcZ,
+ dstLevel, dstX, dstY, dstZ, srcWidth, srcHeight,
+ srcDepth));
+
+ signalDirtyStorage(InitState::Initialized);
+
+ return angle::Result::Continue;
+}
+
+angle::Result Texture::copyTexture(Context *context,
+ TextureTarget target,
+ GLint level,
+ GLenum internalFormat,
+ GLenum type,
+ GLint sourceLevel,
+ bool unpackFlipY,
+ bool unpackPremultiplyAlpha,
+ bool unpackUnmultiplyAlpha,
+ Texture *source)
+{
+ ASSERT(TextureTargetToType(target) == mState.mType);
+ ASSERT(source->getType() != TextureType::CubeMap);
+
+ // Release from previous calls to eglBindTexImage, to avoid calling the Impl after
+ ANGLE_TRY(releaseTexImageInternal(context));
+
+ egl::RefCountObjectReleaser<egl::Image> releaseImage;
+ ANGLE_TRY(orphanImages(context, &releaseImage));
+
+ // Initialize source texture.
+ // Note: we don't have a way to notify which portions of the image changed currently.
+ ANGLE_TRY(source->ensureInitialized(context));
+
+ ImageIndex index = ImageIndex::MakeFromTarget(target, level, ImageIndex::kEntireLevel);
+
+ ANGLE_TRY(mTexture->copyTexture(context, index, internalFormat, type, sourceLevel, unpackFlipY,
+ unpackPremultiplyAlpha, unpackUnmultiplyAlpha, source));
+
+ const auto &sourceDesc =
+ source->mState.getImageDesc(NonCubeTextureTypeToTarget(source->getType()), sourceLevel);
+ const InternalFormat &internalFormatInfo = GetInternalFormatInfo(internalFormat, type);
+ mState.setImageDesc(
+ target, level,
+ ImageDesc(sourceDesc.size, Format(internalFormatInfo), InitState::Initialized));
+
+ signalDirtyStorage(InitState::Initialized);
+
+ return angle::Result::Continue;
+}
+
+angle::Result Texture::copySubTexture(const Context *context,
+ TextureTarget target,
+ GLint level,
+ const Offset &destOffset,
+ GLint sourceLevel,
+ const Box &sourceBox,
+ bool unpackFlipY,
+ bool unpackPremultiplyAlpha,
+ bool unpackUnmultiplyAlpha,
+ Texture *source)
+{
+ ASSERT(TextureTargetToType(target) == mState.mType);
+
+ // Ensure source is initialized.
+ ANGLE_TRY(source->ensureInitialized(context));
+
+ Box destBox(destOffset.x, destOffset.y, destOffset.z, sourceBox.width, sourceBox.height,
+ sourceBox.depth);
+ ImageIndex index = ImageIndex::MakeFromTarget(target, level, sourceBox.depth);
+ ANGLE_TRY(ensureSubImageInitialized(context, index, destBox));
+
+ ANGLE_TRY(mTexture->copySubTexture(context, index, destOffset, sourceLevel, sourceBox,
+ unpackFlipY, unpackPremultiplyAlpha, unpackUnmultiplyAlpha,
+ source));
+
+ onStateChange(angle::SubjectMessage::ContentsChanged);
+
+ return angle::Result::Continue;
+}
+
+angle::Result Texture::copyCompressedTexture(Context *context, const Texture *source)
+{
+ // Release from previous calls to eglBindTexImage, to avoid calling the Impl after
+ ANGLE_TRY(releaseTexImageInternal(context));
+
+ egl::RefCountObjectReleaser<egl::Image> releaseImage;
+ ANGLE_TRY(orphanImages(context, &releaseImage));
+
+ ANGLE_TRY(mTexture->copyCompressedTexture(context, source));
+
+ ASSERT(source->getType() != TextureType::CubeMap && getType() != TextureType::CubeMap);
+ const auto &sourceDesc =
+ source->mState.getImageDesc(NonCubeTextureTypeToTarget(source->getType()), 0);
+ mState.setImageDesc(NonCubeTextureTypeToTarget(getType()), 0, sourceDesc);
+
+ return angle::Result::Continue;
+}
+
+angle::Result Texture::setStorage(Context *context,
+ TextureType type,
+ GLsizei levels,
+ GLenum internalFormat,
+ const Extents &size)
+{
+ ASSERT(type == mState.mType);
+
+ // Release from previous calls to eglBindTexImage, to avoid calling the Impl after
+ ANGLE_TRY(releaseTexImageInternal(context));
+
+ egl::RefCountObjectReleaser<egl::Image> releaseImage;
+ ANGLE_TRY(orphanImages(context, &releaseImage));
+
+ mState.mImmutableFormat = true;
+ mState.mImmutableLevels = static_cast<GLuint>(levels);
+ mState.clearImageDescs();
+ InitState initState = DetermineInitState(context, nullptr, nullptr);
+ mState.setImageDescChain(0, static_cast<GLuint>(levels - 1), size, Format(internalFormat),
+ initState);
+
+ ANGLE_TRY(mTexture->setStorage(context, type, levels, internalFormat, size));
+
+ // Changing the texture to immutable can trigger a change in the base and max levels:
+ // GLES 3.0.4 section 3.8.10 pg 158:
+ // "For immutable-format textures, levelbase is clamped to the range[0;levels],levelmax is then
+ // clamped to the range[levelbase;levels].
+ mDirtyBits.set(DIRTY_BIT_BASE_LEVEL);
+ mDirtyBits.set(DIRTY_BIT_MAX_LEVEL);
+
+ signalDirtyStorage(initState);
+
+ return angle::Result::Continue;
+}
+
+angle::Result Texture::setImageExternal(Context *context,
+ TextureTarget target,
+ GLint level,
+ GLenum internalFormat,
+ const Extents &size,
+ GLenum format,
+ GLenum type)
+{
+ ASSERT(TextureTargetToType(target) == mState.mType);
+
+ // Release from previous calls to eglBindTexImage, to avoid calling the Impl after
+ ANGLE_TRY(releaseTexImageInternal(context));
+
+ egl::RefCountObjectReleaser<egl::Image> releaseImage;
+ ANGLE_TRY(orphanImages(context, &releaseImage));
+
+ ImageIndex index = ImageIndex::MakeFromTarget(target, level, size.depth);
+
+ ANGLE_TRY(mTexture->setImageExternal(context, index, internalFormat, size, format, type));
+
+ InitState initState = InitState::Initialized;
+ mState.setImageDesc(target, level, ImageDesc(size, Format(internalFormat, type), initState));
+
+ ANGLE_TRY(handleMipmapGenerationHint(context, level));
+
+ signalDirtyStorage(initState);
+
+ return angle::Result::Continue;
+}
+
+angle::Result Texture::setStorageMultisample(Context *context,
+ TextureType type,
+ GLsizei samplesIn,
+ GLint internalFormat,
+ const Extents &size,
+ bool fixedSampleLocations)
+{
+ ASSERT(type == mState.mType);
+
+ // Release from previous calls to eglBindTexImage, to avoid calling the Impl after
+ ANGLE_TRY(releaseTexImageInternal(context));
+
+ egl::RefCountObjectReleaser<egl::Image> releaseImage;
+ ANGLE_TRY(orphanImages(context, &releaseImage));
+
+ // Potentially adjust "samples" to a supported value
+ const TextureCaps &formatCaps = context->getTextureCaps().get(internalFormat);
+ GLsizei samples = formatCaps.getNearestSamples(samplesIn);
+
+ mState.mImmutableFormat = true;
+ mState.mImmutableLevels = static_cast<GLuint>(1);
+ mState.clearImageDescs();
+ InitState initState = DetermineInitState(context, nullptr, nullptr);
+ mState.setImageDescChainMultisample(size, Format(internalFormat), samples, fixedSampleLocations,
+ initState);
+
+ ANGLE_TRY(mTexture->setStorageMultisample(context, type, samples, internalFormat, size,
+ fixedSampleLocations));
+ signalDirtyStorage(initState);
+
+ return angle::Result::Continue;
+}
+
+angle::Result Texture::setStorageExternalMemory(Context *context,
+ TextureType type,
+ GLsizei levels,
+ GLenum internalFormat,
+ const Extents &size,
+ MemoryObject *memoryObject,
+ GLuint64 offset,
+ GLbitfield createFlags,
+ GLbitfield usageFlags,
+ const void *imageCreateInfoPNext)
+{
+ ASSERT(type == mState.mType);
+
+ // Release from previous calls to eglBindTexImage, to avoid calling the Impl after
+ ANGLE_TRY(releaseTexImageInternal(context));
+
+ egl::RefCountObjectReleaser<egl::Image> releaseImage;
+ ANGLE_TRY(orphanImages(context, &releaseImage));
+
+ ANGLE_TRY(mTexture->setStorageExternalMemory(context, type, levels, internalFormat, size,
+ memoryObject, offset, createFlags, usageFlags,
+ imageCreateInfoPNext));
+
+ mState.mImmutableFormat = true;
+ mState.mImmutableLevels = static_cast<GLuint>(levels);
+ mState.clearImageDescs();
+ mState.setImageDescChain(0, static_cast<GLuint>(levels - 1), size, Format(internalFormat),
+ InitState::Initialized);
+
+ // Changing the texture to immutable can trigger a change in the base and max levels:
+ // GLES 3.0.4 section 3.8.10 pg 158:
+ // "For immutable-format textures, levelbase is clamped to the range[0;levels],levelmax is then
+ // clamped to the range[levelbase;levels].
+ mDirtyBits.set(DIRTY_BIT_BASE_LEVEL);
+ mDirtyBits.set(DIRTY_BIT_MAX_LEVEL);
+
+ signalDirtyStorage(InitState::Initialized);
+
+ return angle::Result::Continue;
+}
+
+angle::Result Texture::generateMipmap(Context *context)
+{
+ // Release from previous calls to eglBindTexImage, to avoid calling the Impl after
+ ANGLE_TRY(releaseTexImageInternal(context));
+
+ // EGL_KHR_gl_image states that images are only orphaned when generating mipmaps if the texture
+ // is not mip complete.
+ egl::RefCountObjectReleaser<egl::Image> releaseImage;
+ if (!isMipmapComplete())
+ {
+ ANGLE_TRY(orphanImages(context, &releaseImage));
+ }
+
+ const GLuint baseLevel = mState.getEffectiveBaseLevel();
+ const GLuint maxLevel = mState.getMipmapMaxLevel();
+
+ if (maxLevel <= baseLevel)
+ {
+ return angle::Result::Continue;
+ }
+
+ // If any dimension is zero, this is a no-op:
+ //
+ // > Otherwise, if level_base is not defined, or if any dimension is zero, all mipmap levels are
+ // > left unchanged. This is not an error.
+ const ImageDesc &baseImageInfo = mState.getImageDesc(mState.getBaseImageTarget(), baseLevel);
+ if (baseImageInfo.size.empty())
+ {
+ return angle::Result::Continue;
+ }
+
+ // Clear the base image(s) immediately if needed
+ if (context->isRobustResourceInitEnabled())
+ {
+ ImageIndexIterator it =
+ ImageIndexIterator::MakeGeneric(mState.mType, baseLevel, baseLevel + 1,
+ ImageIndex::kEntireLevel, ImageIndex::kEntireLevel);
+ while (it.hasNext())
+ {
+ const ImageIndex index = it.next();
+ const ImageDesc &desc = mState.getImageDesc(index.getTarget(), index.getLevelIndex());
+
+ if (desc.initState == InitState::MayNeedInit)
+ {
+ ANGLE_TRY(initializeContents(context, GL_NONE, index));
+ }
+ }
+ }
+
+ ANGLE_TRY(syncState(context, Command::GenerateMipmap));
+ ANGLE_TRY(mTexture->generateMipmap(context));
+
+ // Propagate the format and size of the base mip to the smaller ones. Cube maps are guaranteed
+ // to have faces of the same size and format so any faces can be picked.
+ mState.setImageDescChain(baseLevel, maxLevel, baseImageInfo.size, baseImageInfo.format,
+ InitState::Initialized);
+
+ signalDirtyStorage(InitState::Initialized);
+
+ return angle::Result::Continue;
+}
+
+angle::Result Texture::bindTexImageFromSurface(Context *context, egl::Surface *surface)
+{
+ ASSERT(surface);
+
+ if (mBoundSurface)
+ {
+ ANGLE_TRY(releaseTexImageFromSurface(context));
+ }
+
+ mBoundSurface = surface;
+
+ // Set the image info to the size and format of the surface
+ ASSERT(mState.mType == TextureType::_2D || mState.mType == TextureType::Rectangle);
+ Extents size(surface->getWidth(), surface->getHeight(), 1);
+ ImageDesc desc(size, surface->getBindTexImageFormat(), InitState::Initialized);
+ mState.setImageDesc(NonCubeTextureTypeToTarget(mState.mType), 0, desc);
+ mState.mHasProtectedContent = surface->hasProtectedContent();
+
+ ANGLE_TRY(mTexture->bindTexImage(context, surface));
+
+ signalDirtyStorage(InitState::Initialized);
+ return angle::Result::Continue;
+}
+
+angle::Result Texture::releaseTexImageFromSurface(const Context *context)
+{
+ ASSERT(mBoundSurface);
+ mBoundSurface = nullptr;
+ ANGLE_TRY(mTexture->releaseTexImage(context));
+
+ // Erase the image info for level 0
+ ASSERT(mState.mType == TextureType::_2D || mState.mType == TextureType::Rectangle);
+ mState.clearImageDesc(NonCubeTextureTypeToTarget(mState.mType), 0);
+ mState.mHasProtectedContent = false;
+ signalDirtyStorage(InitState::Initialized);
+ return angle::Result::Continue;
+}
+
+void Texture::bindStream(egl::Stream *stream)
+{
+ ASSERT(stream);
+
+ // It should not be possible to bind a texture already bound to another stream
+ ASSERT(mBoundStream == nullptr);
+
+ mBoundStream = stream;
+
+ ASSERT(mState.mType == TextureType::External);
+}
+
+void Texture::releaseStream()
+{
+ ASSERT(mBoundStream);
+ mBoundStream = nullptr;
+}
+
+angle::Result Texture::acquireImageFromStream(const Context *context,
+ const egl::Stream::GLTextureDescription &desc)
+{
+ ASSERT(mBoundStream != nullptr);
+ ANGLE_TRY(mTexture->setImageExternal(context, mState.mType, mBoundStream, desc));
+
+ Extents size(desc.width, desc.height, 1);
+ mState.setImageDesc(NonCubeTextureTypeToTarget(mState.mType), 0,
+ ImageDesc(size, Format(desc.internalFormat), InitState::Initialized));
+ signalDirtyStorage(InitState::Initialized);
+ return angle::Result::Continue;
+}
+
+angle::Result Texture::releaseImageFromStream(const Context *context)
+{
+ ASSERT(mBoundStream != nullptr);
+ ANGLE_TRY(mTexture->setImageExternal(context, mState.mType, nullptr,
+ egl::Stream::GLTextureDescription()));
+
+ // Set to incomplete
+ mState.clearImageDesc(NonCubeTextureTypeToTarget(mState.mType), 0);
+ signalDirtyStorage(InitState::Initialized);
+ return angle::Result::Continue;
+}
+
+angle::Result Texture::releaseTexImageInternal(Context *context)
+{
+ if (mBoundSurface)
+ {
+ // Notify the surface
+ egl::Error eglErr = mBoundSurface->releaseTexImageFromTexture(context);
+ // TODO(jmadill): Remove this once refactor is complete. http://anglebug.com/3041
+ if (eglErr.isError())
+ {
+ context->handleError(GL_INVALID_OPERATION, "Error releasing tex image from texture",
+ __FILE__, ANGLE_FUNCTION, __LINE__);
+ }
+
+ // Then, call the same method as from the surface
+ ANGLE_TRY(releaseTexImageFromSurface(context));
+ }
+ return angle::Result::Continue;
+}
+
+angle::Result Texture::setEGLImageTargetImpl(Context *context,
+ TextureType type,
+ GLuint levels,
+ egl::Image *imageTarget)
+{
+ ASSERT(type == mState.mType);
+
+ // Release from previous calls to eglBindTexImage, to avoid calling the Impl after
+ ANGLE_TRY(releaseTexImageInternal(context));
+
+ egl::RefCountObjectReleaser<egl::Image> releaseImage;
+ ANGLE_TRY(orphanImages(context, &releaseImage));
+
+ setTargetImage(context, imageTarget);
+
+ auto initState = imageTarget->sourceInitState();
+
+ mState.clearImageDescs();
+ mState.setImageDescChain(0, levels - 1, imageTarget->getExtents(), imageTarget->getFormat(),
+ initState);
+ mState.mHasProtectedContent = imageTarget->hasProtectedContent();
+
+ ANGLE_TRY(mTexture->setEGLImageTarget(context, type, imageTarget));
+
+ signalDirtyStorage(initState);
+
+ return angle::Result::Continue;
+}
+
+angle::Result Texture::setEGLImageTarget(Context *context,
+ TextureType type,
+ egl::Image *imageTarget)
+{
+ ASSERT(type == TextureType::_2D || type == TextureType::External ||
+ type == TextureType::_2DArray);
+
+ return setEGLImageTargetImpl(context, type, 1u, imageTarget);
+}
+
+angle::Result Texture::setStorageEGLImageTarget(Context *context,
+ TextureType type,
+ egl::Image *imageTarget,
+ const GLint *attrib_list)
+{
+ ASSERT(type == TextureType::External || type == TextureType::_3D || type == TextureType::_2D ||
+ type == TextureType::_2DArray || type == TextureType::CubeMap ||
+ type == TextureType::CubeMapArray);
+
+ ANGLE_TRY(setEGLImageTargetImpl(context, type, imageTarget->getLevelCount(), imageTarget));
+
+ mState.mImmutableLevels = imageTarget->getLevelCount();
+ mState.mImmutableFormat = true;
+
+ // Changing the texture to immutable can trigger a change in the base and max levels:
+ // GLES 3.0.4 section 3.8.10 pg 158:
+ // "For immutable-format textures, levelbase is clamped to the range[0;levels],levelmax is then
+ // clamped to the range[levelbase;levels].
+ mDirtyBits.set(DIRTY_BIT_BASE_LEVEL);
+ mDirtyBits.set(DIRTY_BIT_MAX_LEVEL);
+
+ return angle::Result::Continue;
+}
+
+Extents Texture::getAttachmentSize(const ImageIndex &imageIndex) const
+{
+ // As an ImageIndex that represents an entire level of a cube map corresponds to 6 ImageDescs,
+ // we only allow querying ImageDesc on a complete cube map, and this ImageDesc is exactly the
+ // one that belongs to the first face of the cube map.
+ if (imageIndex.isEntireLevelCubeMap())
+ {
+ // A cube map texture is cube complete if the following conditions all hold true:
+ // - The levelbase arrays of each of the six texture images making up the cube map have
+ // identical, positive, and square dimensions.
+ if (!mState.isCubeComplete())
+ {
+ return Extents();
+ }
+ }
+
+ return mState.getImageDesc(imageIndex).size;
+}
+
+Format Texture::getAttachmentFormat(GLenum /*binding*/, const ImageIndex &imageIndex) const
+{
+ // As an ImageIndex that represents an entire level of a cube map corresponds to 6 ImageDescs,
+ // we only allow querying ImageDesc on a complete cube map, and this ImageDesc is exactly the
+ // one that belongs to the first face of the cube map.
+ if (imageIndex.isEntireLevelCubeMap())
+ {
+ // A cube map texture is cube complete if the following conditions all hold true:
+ // - The levelbase arrays were each specified with the same effective internal format.
+ if (!mState.isCubeComplete())
+ {
+ return Format::Invalid();
+ }
+ }
+ return mState.getImageDesc(imageIndex).format;
+}
+
+GLsizei Texture::getAttachmentSamples(const ImageIndex &imageIndex) const
+{
+ // We do not allow querying TextureTarget by an ImageIndex that represents an entire level of a
+ // cube map (See comments in function TextureTypeToTarget() in ImageIndex.cpp).
+ if (imageIndex.isEntireLevelCubeMap())
+ {
+ return 0;
+ }
+
+ return getSamples(imageIndex.getTarget(), imageIndex.getLevelIndex());
+}
+
+bool Texture::isRenderable(const Context *context,
+ GLenum binding,
+ const ImageIndex &imageIndex) const
+{
+ if (isEGLImageTarget())
+ {
+ return ImageSibling::isRenderable(context, binding, imageIndex);
+ }
+
+ // Surfaces bound to textures are always renderable. This avoids issues with surfaces with ES3+
+ // formats not being renderable when bound to textures in ES2 contexts.
+ if (mBoundSurface)
+ {
+ return true;
+ }
+
+ return getAttachmentFormat(binding, imageIndex)
+ .info->textureAttachmentSupport(context->getClientVersion(), context->getExtensions());
+}
+
+bool Texture::getAttachmentFixedSampleLocations(const ImageIndex &imageIndex) const
+{
+ // We do not allow querying TextureTarget by an ImageIndex that represents an entire level of a
+ // cube map (See comments in function TextureTypeToTarget() in ImageIndex.cpp).
+ if (imageIndex.isEntireLevelCubeMap())
+ {
+ return true;
+ }
+
+ // ES3.1 (section 9.4) requires that the value of TEXTURE_FIXED_SAMPLE_LOCATIONS should be
+ // the same for all attached textures.
+ return getFixedSampleLocations(imageIndex.getTarget(), imageIndex.getLevelIndex());
+}
+
+void Texture::setBorderColor(const Context *context, const ColorGeneric &color)
+{
+ mState.mSamplerState.setBorderColor(color);
+ signalDirtyState(DIRTY_BIT_BORDER_COLOR);
+}
+
+const ColorGeneric &Texture::getBorderColor() const
+{
+ return mState.mSamplerState.getBorderColor();
+}
+
+GLint Texture::getRequiredTextureImageUnits(const Context *context) const
+{
+ // Only external texture types can return non-1.
+ if (mState.mType != TextureType::External)
+ {
+ return 1;
+ }
+
+ return mTexture->getRequiredExternalTextureImageUnits(context);
+}
+
+void Texture::setCrop(const Rectangle &rect)
+{
+ mState.setCrop(rect);
+}
+
+const Rectangle &Texture::getCrop() const
+{
+ return mState.getCrop();
+}
+
+void Texture::setGenerateMipmapHint(GLenum hint)
+{
+ mState.setGenerateMipmapHint(hint);
+}
+
+GLenum Texture::getGenerateMipmapHint() const
+{
+ return mState.getGenerateMipmapHint();
+}
+
+angle::Result Texture::setBuffer(const gl::Context *context,
+ gl::Buffer *buffer,
+ GLenum internalFormat)
+{
+ // Use 0 to indicate that the size is taken from whatever size the buffer has when the texture
+ // buffer is used.
+ return setBufferRange(context, buffer, internalFormat, 0, 0);
+}
+
+angle::Result Texture::setBufferRange(const gl::Context *context,
+ gl::Buffer *buffer,
+ GLenum internalFormat,
+ GLintptr offset,
+ GLsizeiptr size)
+{
+ mState.mImmutableFormat = true;
+ mState.mBuffer.set(context, buffer, offset, size);
+ ANGLE_TRY(mTexture->setBuffer(context, internalFormat));
+
+ mState.clearImageDescs();
+ if (buffer == nullptr)
+ {
+ mBufferObserver.reset();
+ InitState initState = DetermineInitState(context, nullptr, nullptr);
+ signalDirtyStorage(initState);
+ return angle::Result::Continue;
+ }
+
+ size = GetBoundBufferAvailableSize(mState.mBuffer);
+
+ mState.mImmutableLevels = static_cast<GLuint>(1);
+ InternalFormat internalFormatInfo = GetSizedInternalFormatInfo(internalFormat);
+ Format format(internalFormat);
+ Extents extents(static_cast<GLuint>(size / internalFormatInfo.pixelBytes), 1, 1);
+ InitState initState = buffer->initState();
+ mState.setImageDesc(TextureTarget::Buffer, 0, ImageDesc(extents, format, initState));
+
+ signalDirtyStorage(initState);
+
+ // Observe modifications to the buffer, so that extents can be updated.
+ mBufferObserver.bind(buffer);
+
+ return angle::Result::Continue;
+}
+
+const OffsetBindingPointer<Buffer> &Texture::getBuffer() const
+{
+ return mState.mBuffer;
+}
+
+void Texture::onAttach(const Context *context, rx::Serial framebufferSerial)
+{
+ addRef();
+
+ // Duplicates allowed for multiple attachment points. See the comment in the header.
+ mBoundFramebufferSerials.push_back(framebufferSerial);
+
+ if (!mState.mHasBeenBoundAsAttachment)
+ {
+ mDirtyBits.set(DIRTY_BIT_BOUND_AS_ATTACHMENT);
+ mState.mHasBeenBoundAsAttachment = true;
+ }
+}
+
+void Texture::onDetach(const Context *context, rx::Serial framebufferSerial)
+{
+ // Erase first instance. If there are multiple bindings, leave the others.
+ ASSERT(isBoundToFramebuffer(framebufferSerial));
+ mBoundFramebufferSerials.remove_and_permute(framebufferSerial);
+
+ release(context);
+}
+
+GLuint Texture::getId() const
+{
+ return id().value;
+}
+
+GLuint Texture::getNativeID() const
+{
+ return mTexture->getNativeID();
+}
+
+angle::Result Texture::syncState(const Context *context, Command source)
+{
+ ASSERT(hasAnyDirtyBit() || source == Command::GenerateMipmap);
+ ANGLE_TRY(mTexture->syncState(context, mDirtyBits, source));
+ mDirtyBits.reset();
+ mState.mInitState = InitState::Initialized;
+ return angle::Result::Continue;
+}
+
+rx::FramebufferAttachmentObjectImpl *Texture::getAttachmentImpl() const
+{
+ return mTexture;
+}
+
+bool Texture::isSamplerComplete(const Context *context, const Sampler *optionalSampler)
+{
+ const auto &samplerState =
+ optionalSampler ? optionalSampler->getSamplerState() : mState.mSamplerState;
+ const auto &contextState = context->getState();
+
+ if (contextState.getContextID() != mCompletenessCache.context ||
+ !mCompletenessCache.samplerState.sameCompleteness(samplerState))
+ {
+ mCompletenessCache.context = context->getState().getContextID();
+ mCompletenessCache.samplerState = samplerState;
+ mCompletenessCache.samplerComplete =
+ mState.computeSamplerCompleteness(samplerState, contextState);
+ }
+
+ return mCompletenessCache.samplerComplete;
+}
+
+// CopyImageSubData requires that we ignore format-based completeness rules
+bool Texture::isSamplerCompleteForCopyImage(const Context *context,
+ const Sampler *optionalSampler) const
+{
+ const gl::SamplerState &samplerState =
+ optionalSampler ? optionalSampler->getSamplerState() : mState.mSamplerState;
+ const gl::State &contextState = context->getState();
+ return mState.computeSamplerCompletenessForCopyImage(samplerState, contextState);
+}
+
+Texture::SamplerCompletenessCache::SamplerCompletenessCache()
+ : context({0}), samplerState(), samplerComplete(false)
+{}
+
+void Texture::invalidateCompletenessCache() const
+{
+ mCompletenessCache.context = {0};
+}
+
+angle::Result Texture::ensureInitialized(const Context *context)
+{
+ if (!context->isRobustResourceInitEnabled() || mState.mInitState == InitState::Initialized)
+ {
+ return angle::Result::Continue;
+ }
+
+ bool anyDirty = false;
+
+ ImageIndexIterator it =
+ ImageIndexIterator::MakeGeneric(mState.mType, 0, IMPLEMENTATION_MAX_TEXTURE_LEVELS + 1,
+ ImageIndex::kEntireLevel, ImageIndex::kEntireLevel);
+ while (it.hasNext())
+ {
+ const ImageIndex index = it.next();
+ ImageDesc &desc =
+ mState.mImageDescs[GetImageDescIndex(index.getTarget(), index.getLevelIndex())];
+ if (desc.initState == InitState::MayNeedInit && !desc.size.empty())
+ {
+ ASSERT(mState.mInitState == InitState::MayNeedInit);
+ ANGLE_TRY(initializeContents(context, GL_NONE, index));
+ desc.initState = InitState::Initialized;
+ anyDirty = true;
+ }
+ }
+ if (anyDirty)
+ {
+ signalDirtyStorage(InitState::Initialized);
+ }
+ mState.mInitState = InitState::Initialized;
+
+ return angle::Result::Continue;
+}
+
+InitState Texture::initState(GLenum /*binding*/, const ImageIndex &imageIndex) const
+{
+ // As an ImageIndex that represents an entire level of a cube map corresponds to 6 ImageDescs,
+ // we need to check all the related ImageDescs.
+ if (imageIndex.isEntireLevelCubeMap())
+ {
+ const GLint levelIndex = imageIndex.getLevelIndex();
+ for (TextureTarget cubeFaceTarget : AllCubeFaceTextureTargets())
+ {
+ if (mState.getImageDesc(cubeFaceTarget, levelIndex).initState == InitState::MayNeedInit)
+ {
+ return InitState::MayNeedInit;
+ }
+ }
+ return InitState::Initialized;
+ }
+
+ return mState.getImageDesc(imageIndex).initState;
+}
+
+void Texture::setInitState(GLenum binding, const ImageIndex &imageIndex, InitState initState)
+{
+ // As an ImageIndex that represents an entire level of a cube map corresponds to 6 ImageDescs,
+ // we need to update all the related ImageDescs.
+ if (imageIndex.isEntireLevelCubeMap())
+ {
+ const GLint levelIndex = imageIndex.getLevelIndex();
+ for (TextureTarget cubeFaceTarget : AllCubeFaceTextureTargets())
+ {
+ setInitState(binding, ImageIndex::MakeCubeMapFace(cubeFaceTarget, levelIndex),
+ initState);
+ }
+ }
+ else
+ {
+ ImageDesc newDesc = mState.getImageDesc(imageIndex);
+ newDesc.initState = initState;
+ mState.setImageDesc(imageIndex.getTarget(), imageIndex.getLevelIndex(), newDesc);
+ }
+}
+
+void Texture::setInitState(InitState initState)
+{
+ for (ImageDesc &imageDesc : mState.mImageDescs)
+ {
+ // Only modify defined images, undefined images will remain in the initialized state
+ if (!imageDesc.size.empty())
+ {
+ imageDesc.initState = initState;
+ }
+ }
+ mState.mInitState = initState;
+}
+
+bool Texture::doesSubImageNeedInit(const Context *context,
+ const ImageIndex &imageIndex,
+ const Box &area) const
+{
+ if (!context->isRobustResourceInitEnabled() || mState.mInitState == InitState::Initialized)
+ {
+ return false;
+ }
+
+ // Pre-initialize the texture contents if necessary.
+ const ImageDesc &desc = mState.getImageDesc(imageIndex);
+ if (desc.initState != InitState::MayNeedInit)
+ {
+ return false;
+ }
+
+ ASSERT(mState.mInitState == InitState::MayNeedInit);
+ return !area.coversSameExtent(desc.size);
+}
+
+angle::Result Texture::ensureSubImageInitialized(const Context *context,
+ const ImageIndex &imageIndex,
+ const Box &area)
+{
+ if (doesSubImageNeedInit(context, imageIndex, area))
+ {
+ // NOTE: do not optimize this to only initialize the passed area of the texture, or the
+ // initialization logic in copySubImage will be incorrect.
+ ANGLE_TRY(initializeContents(context, GL_NONE, imageIndex));
+ }
+ // Note: binding is ignored for textures.
+ setInitState(GL_NONE, imageIndex, InitState::Initialized);
+ return angle::Result::Continue;
+}
+
+angle::Result Texture::handleMipmapGenerationHint(Context *context, int level)
+{
+ if (getGenerateMipmapHint() == GL_TRUE && level == 0)
+ {
+ ANGLE_TRY(generateMipmap(context));
+ }
+
+ return angle::Result::Continue;
+}
+
+void Texture::onSubjectStateChange(angle::SubjectIndex index, angle::SubjectMessage message)
+{
+ switch (message)
+ {
+ case angle::SubjectMessage::ContentsChanged:
+ if (index != kBufferSubjectIndex)
+ {
+ // ContentsChange originates from TextureStorage11::resolveAndReleaseTexture
+ // which resolves the underlying multisampled texture if it exists and so
+ // Texture will signal dirty storage to invalidate its own cache and the
+ // attached framebuffer's cache.
+ signalDirtyStorage(InitState::Initialized);
+ }
+ break;
+ case angle::SubjectMessage::DirtyBitsFlagged:
+ signalDirtyState(DIRTY_BIT_IMPLEMENTATION);
+
+ // Notify siblings that we are dirty.
+ if (index == rx::kTextureImageImplObserverMessageIndex)
+ {
+ notifySiblings(message);
+ }
+ break;
+ case angle::SubjectMessage::SubjectChanged:
+ mState.mInitState = InitState::MayNeedInit;
+ signalDirtyState(DIRTY_BIT_IMPLEMENTATION);
+ onStateChange(angle::SubjectMessage::ContentsChanged);
+
+ // Notify siblings that we are dirty.
+ if (index == rx::kTextureImageImplObserverMessageIndex)
+ {
+ notifySiblings(message);
+ }
+ else if (index == kBufferSubjectIndex)
+ {
+ const gl::Buffer *buffer = mState.mBuffer.get();
+ ASSERT(buffer != nullptr);
+
+ // Update cached image desc based on buffer size.
+ GLsizeiptr size = GetBoundBufferAvailableSize(mState.mBuffer);
+
+ ImageDesc desc = mState.getImageDesc(TextureTarget::Buffer, 0);
+ const GLuint pixelBytes = desc.format.info->pixelBytes;
+ desc.size.width = static_cast<GLuint>(size / pixelBytes);
+
+ mState.setImageDesc(TextureTarget::Buffer, 0, desc);
+ }
+ break;
+ case angle::SubjectMessage::StorageReleased:
+ // When the TextureStorage is released, it needs to update the
+ // RenderTargetCache of the Framebuffer attaching this Texture.
+ // This is currently only for D3D back-end. See http://crbug.com/1234829
+ if (index == rx::kTextureImageImplObserverMessageIndex)
+ {
+ onStateChange(angle::SubjectMessage::StorageReleased);
+ }
+ break;
+ case angle::SubjectMessage::SubjectMapped:
+ case angle::SubjectMessage::SubjectUnmapped:
+ case angle::SubjectMessage::BindingChanged:
+ ASSERT(index == kBufferSubjectIndex);
+ break;
+ case angle::SubjectMessage::InitializationComplete:
+ ASSERT(index == rx::kTextureImageImplObserverMessageIndex);
+ setInitState(InitState::Initialized);
+ break;
+ case angle::SubjectMessage::InternalMemoryAllocationChanged:
+ // Need to mark the texture dirty to give the back end a chance to handle the new
+ // buffer. For example, the Vulkan back end needs to create a new buffer view that
+ // points to the newly allocated buffer and update the texture descriptor set.
+ signalDirtyState(DIRTY_BIT_IMPLEMENTATION);
+ break;
+ default:
+ UNREACHABLE();
+ break;
+ }
+}
+
+GLenum Texture::getImplementationColorReadFormat(const Context *context) const
+{
+ return mTexture->getColorReadFormat(context);
+}
+
+GLenum Texture::getImplementationColorReadType(const Context *context) const
+{
+ return mTexture->getColorReadType(context);
+}
+
+bool Texture::isCompressedFormatEmulated(const Context *context,
+ TextureTarget target,
+ GLint level) const
+{
+ if (!getFormat(target, level).info->compressed)
+ {
+ // If it isn't compressed, the remaining logic won't work
+ return false;
+ }
+
+ GLenum implFormat = getImplementationColorReadFormat(context);
+
+ // Check against the list of formats used to emulate compressed textures
+ return IsEmulatedCompressedFormat(implFormat);
+}
+
+angle::Result Texture::getTexImage(const Context *context,
+ const PixelPackState &packState,
+ Buffer *packBuffer,
+ TextureTarget target,
+ GLint level,
+ GLenum format,
+ GLenum type,
+ void *pixels)
+{
+ // No-op if the image level is empty.
+ if (getExtents(target, level).empty())
+ {
+ return angle::Result::Continue;
+ }
+
+ return mTexture->getTexImage(context, packState, packBuffer, target, level, format, type,
+ pixels);
+}
+
+angle::Result Texture::getCompressedTexImage(const Context *context,
+ const PixelPackState &packState,
+ Buffer *packBuffer,
+ TextureTarget target,
+ GLint level,
+ void *pixels)
+{
+ // No-op if the image level is empty.
+ if (getExtents(target, level).empty())
+ {
+ return angle::Result::Continue;
+ }
+
+ return mTexture->getCompressedTexImage(context, packState, packBuffer, target, level, pixels);
+}
+
+void Texture::onBindAsImageTexture()
+{
+ if (!mState.mHasBeenBoundAsImage)
+ {
+ mDirtyBits.set(DIRTY_BIT_BOUND_AS_IMAGE);
+ mState.mHasBeenBoundAsImage = true;
+ }
+}
+
+void Texture::onBind3DTextureAs2DImage()
+{
+ if (!mState.mIs3DAndHasBeenBoundAs2DImage)
+ {
+ mDirtyBits.set(DIRTY_BIT_BOUND_AS_IMAGE);
+ mState.mIs3DAndHasBeenBoundAs2DImage = true;
+ }
+}
+
+} // namespace gl