Source code

Revision control

Copy as Markdown

Other Tools

//
// 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.
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.
// 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