// // 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.
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));
}
// 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);
// 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))
{ returnfalse;
}
// 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)
{ returnfalse;
}
}
}
// 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)
{ returnfalse;
}
}
returntrue;
}
// 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)
{ returntrue;
}
if (!mImmutableFormat && mBaseLevel > mMaxLevel)
{ returnfalse;
} const ImageDesc &baseImageDesc = getImageDesc(getBaseImageTarget(), getEffectiveBaseLevel()); if (baseImageDesc.size.width == 0 || baseImageDesc.size.height == 0 ||
baseImageDesc.size.depth == 0)
{ returnfalse;
} // 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 (IsMipmapSupported(mType) && IsMipmapFiltered(samplerState.getMinFilter()))
{ if (!npotSupport)
{ if (!isPow2(baseImageDesc.size.width) || !isPow2(baseImageDesc.size.height))
{ returnfalse;
}
}
if (!computeMipmapCompleteness())
{ returnfalse;
}
} else
{ if (mType == TextureType::CubeMap && !isCubeComplete())
{ returnfalse;
}
}
// 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)
{ returnfalse;
}
}
// 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;
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);
}
// 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);
}
}
// 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));
}
// 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);
}
}
// 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::Result Texture::copyCompressedTexture(Context *context, const Texture *source)
{ // Release from previous calls to eglBindTexImage, to avoid calling the Impl after
ANGLE_TRY(releaseTexImageInternal(context));
// 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);
// 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));
}
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());
// 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);
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();
// 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;
}
// 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;
}
// 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)
{ returntrue;
}
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())
{ returntrue;
}
// 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());
}
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);
}
// 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);
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;
}
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;
}
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);
} elseif (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);
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;
}
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.