Edit

kc3-lang/angle/src/libANGLE/Texture.cpp

Branch :

  • Show log

    Commit

  • Author : Geoff Lang
    Date : 2015-09-02 11:31:54
    Hash : f826e1a3
    Message : Avoid copy of PixelUnpackState in Texture::setImage. The ternary operator determines that the common type is the value type, causing a copy of PixelUnpackState. If PixelUnpackState::pixelBuffer is non-null then there is an assertion failure in the destructor on any call to Texture::setImage. See:http://stackoverflow.com/a/30088030 BUG=angleproject:1149 Change-Id: I9654e65956339d6b6966ad65a98cf37ad4e00452 Reviewed-on: https://chromium-review.googlesource.com/297084 Reviewed-by: Jamie Madill <jmadill@chromium.org> Reviewed-by: Corentin Wallez <cwallez@chromium.org> Tested-by: Geoff Lang <geofflang@chromium.org>

  • src/libANGLE/Texture.cpp
  • //
    // Copyright (c) 2002-2014 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/Data.h"
    #include "libANGLE/Image.h"
    #include "libANGLE/Surface.h"
    #include "libANGLE/formatutils.h"
    
    namespace gl
    {
    
    bool IsMipmapFiltered(const gl::SamplerState &samplerState)
    {
        switch (samplerState.minFilter)
        {
          case GL_NEAREST:
          case GL_LINEAR:
            return false;
          case GL_NEAREST_MIPMAP_NEAREST:
          case GL_LINEAR_MIPMAP_NEAREST:
          case GL_NEAREST_MIPMAP_LINEAR:
          case GL_LINEAR_MIPMAP_LINEAR:
            return true;
          default: UNREACHABLE();
            return false;
        }
    }
    
    bool IsPointSampled(const gl::SamplerState &samplerState)
    {
        return (samplerState.magFilter == GL_NEAREST && (samplerState.minFilter == GL_NEAREST || samplerState.minFilter == GL_NEAREST_MIPMAP_NEAREST));
    }
    
    static size_t GetImageDescIndex(GLenum target, size_t level)
    {
        return IsCubeMapTextureTarget(target) ? ((level * 6) + CubeMapTextureTargetToLayerIndex(target)) : level;
    }
    
    Texture::Texture(rx::TextureImpl *impl, GLuint id, GLenum target)
        : egl::ImageSibling(id),
          mTexture(impl),
          mUsage(GL_NONE),
          mImmutableLevelCount(0),
          mTarget(target),
          mImageDescs(IMPLEMENTATION_MAX_TEXTURE_LEVELS * (target == GL_TEXTURE_CUBE_MAP ? 6 : 1)),
          mCompletenessCache(),
          mBoundSurface(NULL)
    {
    }
    
    Texture::~Texture()
    {
        if (mBoundSurface)
        {
            mBoundSurface->releaseTexImage(EGL_BACK_BUFFER);
            mBoundSurface = NULL;
        }
        SafeDelete(mTexture);
    }
    
    GLenum Texture::getTarget() const
    {
        return mTarget;
    }
    
    void Texture::setUsage(GLenum usage)
    {
        mUsage = usage;
        getImplementation()->setUsage(usage);
    }
    
    GLenum Texture::getUsage() const
    {
        return mUsage;
    }
    
    size_t Texture::getWidth(GLenum target, size_t level) const
    {
        ASSERT(target == mTarget || (mTarget == GL_TEXTURE_CUBE_MAP && IsCubeMapTextureTarget(target)));
        return getImageDesc(target, level).size.width;
    }
    
    size_t Texture::getHeight(GLenum target, size_t level) const
    {
        ASSERT(target == mTarget || (mTarget == GL_TEXTURE_CUBE_MAP && IsCubeMapTextureTarget(target)));
        return getImageDesc(target, level).size.height;
    }
    
    size_t Texture::getDepth(GLenum target, size_t level) const
    {
        ASSERT(target == mTarget || (mTarget == GL_TEXTURE_CUBE_MAP && IsCubeMapTextureTarget(target)));
        return getImageDesc(target, level).size.depth;
    }
    
    GLenum Texture::getInternalFormat(GLenum target, size_t level) const
    {
        ASSERT(target == mTarget || (mTarget == GL_TEXTURE_CUBE_MAP && IsCubeMapTextureTarget(target)));
        return getImageDesc(target, level).internalFormat;
    }
    
    bool Texture::isSamplerComplete(const SamplerState &samplerState, const Data &data) const
    {
        const ImageDesc &baseImageDesc = getImageDesc(getBaseImageTarget(), samplerState.baseLevel);
        const TextureCaps &textureCaps = data.textureCaps->get(baseImageDesc.internalFormat);
        if (!mCompletenessCache.cacheValid ||
            mCompletenessCache.samplerState != samplerState ||
            mCompletenessCache.filterable != textureCaps.filterable ||
            mCompletenessCache.clientVersion != data.clientVersion ||
            mCompletenessCache.supportsNPOT != data.extensions->textureNPOT)
        {
            mCompletenessCache.cacheValid = true;
            mCompletenessCache.samplerState = samplerState;
            mCompletenessCache.filterable = textureCaps.filterable;
            mCompletenessCache.clientVersion = data.clientVersion;
            mCompletenessCache.supportsNPOT = data.extensions->textureNPOT;
            mCompletenessCache.samplerComplete = computeSamplerCompleteness(samplerState, data);
        }
        return mCompletenessCache.samplerComplete;
    }
    
    bool Texture::isMipmapComplete() const
    {
        return computeMipmapCompleteness(mSamplerState);
    }
    
    // Tests for cube texture completeness. [OpenGL ES 2.0.24] section 3.7.10 page 81.
    bool Texture::isCubeComplete() const
    {
        ASSERT(mTarget == GL_TEXTURE_CUBE_MAP);
    
        const ImageDesc &baseImageDesc = getImageDesc(FirstCubeMapTextureTarget, 0);
        if (baseImageDesc.size.width == 0 || baseImageDesc.size.width != baseImageDesc.size.height)
        {
            return false;
        }
    
        for (GLenum face = FirstCubeMapTextureTarget + 1; face <= LastCubeMapTextureTarget; face++)
        {
            const ImageDesc &faceImageDesc = getImageDesc(face, 0);
            if (faceImageDesc.size.width != baseImageDesc.size.width ||
                faceImageDesc.size.height != baseImageDesc.size.height ||
                faceImageDesc.internalFormat != baseImageDesc.internalFormat)
            {
                return false;
            }
        }
    
        return true;
    }
    
    size_t Texture::getMipCompleteLevels() const
    {
        const ImageDesc &baseImageDesc = getImageDesc(getBaseImageTarget(), 0);
        if (mTarget == GL_TEXTURE_3D)
        {
            const int maxDim = std::max(std::max(baseImageDesc.size.width, baseImageDesc.size.height),
                                        baseImageDesc.size.depth);
            return log2(maxDim) + 1;
        }
        else
        {
            return log2(std::max(baseImageDesc.size.width, baseImageDesc.size.height)) + 1;
        }
    }
    
    bool Texture::isImmutable() const
    {
        return (mImmutableLevelCount > 0);
    }
    
    int Texture::immutableLevelCount()
    {
        return mImmutableLevelCount;
    }
    
    egl::Surface *Texture::getBoundSurface() const
    {
        return mBoundSurface;
    }
    
    Error Texture::setImage(Context *context,
                            GLenum target,
                            size_t level,
                            GLenum internalFormat,
                            const Extents &size,
                            GLenum format,
                            GLenum type,
                            const uint8_t *pixels)
    {
        ASSERT(target == mTarget || (mTarget == GL_TEXTURE_CUBE_MAP && IsCubeMapTextureTarget(target)));
    
        // Release from previous calls to eglBindTexImage, to avoid calling the Impl after
        releaseTexImageInternal();
        orphanImages();
    
        // Hack: allow nullptr for testing
        if (context != nullptr)
        {
            // Sync the unpack state
            context->syncRendererState(context->getState().unpackStateBitMask());
        }
    
        const PixelUnpackState defaultUnpack;
        const PixelUnpackState &unpack = context ? context->getState().getUnpackState() : defaultUnpack;
        Error error = mTexture->setImage(target, level, internalFormat, size, format, type, unpack, pixels);
        if (error.isError())
        {
            return error;
        }
    
        setImageDesc(target, level, ImageDesc(size, GetSizedInternalFormat(internalFormat, type)));
    
        return Error(GL_NO_ERROR);
    }
    
    Error Texture::setSubImage(Context *context,
                               GLenum target,
                               size_t level,
                               const Box &area,
                               GLenum format,
                               GLenum type,
                               const uint8_t *pixels)
    {
        ASSERT(target == mTarget || (mTarget == GL_TEXTURE_CUBE_MAP && IsCubeMapTextureTarget(target)));
    
        // Sync the unpack state
        context->syncRendererState(context->getState().unpackStateBitMask());
    
        const PixelUnpackState &unpack = context->getState().getUnpackState();
        return mTexture->setSubImage(target, level, area, format, type, unpack, pixels);
    }
    
    Error Texture::setCompressedImage(Context *context,
                                      GLenum target,
                                      size_t level,
                                      GLenum internalFormat,
                                      const Extents &size,
                                      size_t imageSize,
                                      const uint8_t *pixels)
    {
        ASSERT(target == mTarget || (mTarget == GL_TEXTURE_CUBE_MAP && IsCubeMapTextureTarget(target)));
    
        // Release from previous calls to eglBindTexImage, to avoid calling the Impl after
        releaseTexImageInternal();
        orphanImages();
    
        // Sync the unpack state
        context->syncRendererState(context->getState().unpackStateBitMask());
    
        const PixelUnpackState &unpack = context->getState().getUnpackState();
        Error error = mTexture->setCompressedImage(target, level, internalFormat, size, unpack, imageSize, pixels);
        if (error.isError())
        {
            return error;
        }
    
        setImageDesc(target, level, ImageDesc(size, GetSizedInternalFormat(internalFormat, GL_UNSIGNED_BYTE)));
    
        return Error(GL_NO_ERROR);
    }
    
    Error Texture::setCompressedSubImage(Context *context,
                                         GLenum target,
                                         size_t level,
                                         const Box &area,
                                         GLenum format,
                                         size_t imageSize,
                                         const uint8_t *pixels)
    {
        ASSERT(target == mTarget || (mTarget == GL_TEXTURE_CUBE_MAP && IsCubeMapTextureTarget(target)));
    
        // Sync the unpack state
        context->syncRendererState(context->getState().unpackStateBitMask());
    
        const PixelUnpackState &unpack = context->getState().getUnpackState();
        return mTexture->setCompressedSubImage(target, level, area, format, unpack, imageSize, pixels);
    }
    
    Error Texture::copyImage(GLenum target, size_t level, const Rectangle &sourceArea, GLenum internalFormat,
                             const Framebuffer *source)
    {
        ASSERT(target == mTarget || (mTarget == GL_TEXTURE_CUBE_MAP && IsCubeMapTextureTarget(target)));
    
        // Release from previous calls to eglBindTexImage, to avoid calling the Impl after
        releaseTexImageInternal();
        orphanImages();
    
        Error error = mTexture->copyImage(target, level, sourceArea, internalFormat, source);
        if (error.isError())
        {
            return error;
        }
    
        setImageDesc(target, level, ImageDesc(Extents(sourceArea.width, sourceArea.height, 1),
                                              GetSizedInternalFormat(internalFormat, GL_UNSIGNED_BYTE)));
    
        return Error(GL_NO_ERROR);
    }
    
    Error Texture::copySubImage(GLenum target, size_t level, const Offset &destOffset, const Rectangle &sourceArea,
                                const Framebuffer *source)
    {
        ASSERT(target == mTarget || (mTarget == GL_TEXTURE_CUBE_MAP && IsCubeMapTextureTarget(target)));
    
        return mTexture->copySubImage(target, level, destOffset, sourceArea, source);
    }
    
    Error Texture::setStorage(GLenum target, size_t levels, GLenum internalFormat, const Extents &size)
    {
        ASSERT(target == mTarget);
    
        // Release from previous calls to eglBindTexImage, to avoid calling the Impl after
        releaseTexImageInternal();
        orphanImages();
    
        Error error = mTexture->setStorage(target, levels, internalFormat, size);
        if (error.isError())
        {
            return error;
        }
    
        mImmutableLevelCount = static_cast<GLsizei>(levels);
        clearImageDescs();
        setImageDescChain(levels, size, internalFormat);
    
        return Error(GL_NO_ERROR);
    }
    
    
    Error Texture::generateMipmaps()
    {
        // Release from previous calls to eglBindTexImage, to avoid calling the Impl after
        releaseTexImageInternal();
    
        // EGL_KHR_gl_image states that images are only orphaned when generating mipmaps if the texture
        // is not mip complete.
        if (!isMipmapComplete())
        {
            orphanImages();
        }
    
        Error error = mTexture->generateMipmaps(getSamplerState());
        if (error.isError())
        {
            return error;
        }
    
        const ImageDesc &baseImageInfo = getImageDesc(getBaseImageTarget(), 0);
        size_t mipLevels = log2(std::max(std::max(baseImageInfo.size.width, baseImageInfo.size.height), baseImageInfo.size.depth)) + 1;
        setImageDescChain(mipLevels, baseImageInfo.size, baseImageInfo.internalFormat);
    
        return Error(GL_NO_ERROR);
    }
    
    void Texture::setImageDescChain(size_t levels, Extents baseSize, GLenum sizedInternalFormat)
    {
        for (int level = 0; level < static_cast<int>(levels); level++)
        {
            Extents levelSize(
                std::max<int>(baseSize.width >> level, 1), std::max<int>(baseSize.height >> level, 1),
                (mTarget == GL_TEXTURE_2D_ARRAY) ? baseSize.depth
                                                 : std::max<int>(baseSize.depth >> level, 1));
            ImageDesc levelInfo(levelSize, sizedInternalFormat);
    
            if (mTarget == GL_TEXTURE_CUBE_MAP)
            {
                for (GLenum face = FirstCubeMapTextureTarget; face <= LastCubeMapTextureTarget; face++)
                {
                    setImageDesc(face, level, levelInfo);
                }
            }
            else
            {
                setImageDesc(mTarget, level, levelInfo);
            }
        }
    }
    
    Texture::ImageDesc::ImageDesc()
        : ImageDesc(Extents(0, 0, 0), GL_NONE)
    {
    }
    
    Texture::ImageDesc::ImageDesc(const Extents &size, GLenum internalFormat)
        : size(size),
          internalFormat(internalFormat)
    {
    }
    
    const Texture::ImageDesc &Texture::getImageDesc(GLenum target, size_t level) const
    {
        size_t descIndex = GetImageDescIndex(target, level);
        ASSERT(descIndex < mImageDescs.size());
        return mImageDescs[descIndex];
    }
    
    void Texture::setImageDesc(GLenum target, size_t level, const ImageDesc &desc)
    {
        size_t descIndex = GetImageDescIndex(target, level);
        ASSERT(descIndex < mImageDescs.size());
        mImageDescs[descIndex] = desc;
        mCompletenessCache.cacheValid = false;
    }
    
    void Texture::clearImageDesc(GLenum target, size_t level)
    {
        setImageDesc(target, level, ImageDesc());
    }
    
    void Texture::clearImageDescs()
    {
        for (size_t descIndex = 0; descIndex < mImageDescs.size(); descIndex++)
        {
            mImageDescs[descIndex] = ImageDesc();
        }
        mCompletenessCache.cacheValid = false;
    }
    
    void Texture::bindTexImageFromSurface(egl::Surface *surface)
    {
        ASSERT(surface);
    
        if (mBoundSurface)
        {
            releaseTexImageFromSurface();
        }
    
        mTexture->bindTexImage(surface);
        mBoundSurface = surface;
    
        // Set the image info to the size and format of the surface
        ASSERT(mTarget == GL_TEXTURE_2D);
        Extents size(surface->getWidth(), surface->getHeight(), 1);
        ImageDesc desc(size, surface->getConfig()->renderTargetFormat);
        setImageDesc(mTarget, 0, desc);
    }
    
    void Texture::releaseTexImageFromSurface()
    {
        ASSERT(mBoundSurface);
        mBoundSurface = nullptr;
        mTexture->releaseTexImage();
    
        // Erase the image info for level 0
        ASSERT(mTarget == GL_TEXTURE_2D);
        clearImageDesc(mTarget, 0);
    }
    
    void Texture::releaseTexImageInternal()
    {
        if (mBoundSurface)
        {
            // Notify the surface
            mBoundSurface->releaseTexImageFromTexture();
    
            // Then, call the same method as from the surface
            releaseTexImageFromSurface();
        }
    }
    
    Error Texture::setEGLImageTarget(GLenum target, egl::Image *imageTarget)
    {
        ASSERT(target == mTarget);
        ASSERT(target == GL_TEXTURE_2D);
    
        // Release from previous calls to eglBindTexImage, to avoid calling the Impl after
        releaseTexImageInternal();
        orphanImages();
    
        Error error = mTexture->setEGLImageTarget(target, imageTarget);
        if (error.isError())
        {
            return error;
        }
    
        setTargetImage(imageTarget);
    
        Extents size(static_cast<int>(imageTarget->getWidth()),
                     static_cast<int>(imageTarget->getHeight()), 1);
        GLenum internalFormat = imageTarget->getInternalFormat();
        GLenum type           = GetInternalFormatInfo(internalFormat).type;
    
        clearImageDescs();
        setImageDesc(target, 0, ImageDesc(size, GetSizedInternalFormat(internalFormat, type)));
    
        return Error(GL_NO_ERROR);
    }
    
    GLenum Texture::getBaseImageTarget() const
    {
        return mTarget == GL_TEXTURE_CUBE_MAP ? FirstCubeMapTextureTarget : mTarget;
    }
    
    bool Texture::computeSamplerCompleteness(const SamplerState &samplerState, const Data &data) const
    {
        const ImageDesc &baseImageDesc = getImageDesc(getBaseImageTarget(), samplerState.baseLevel);
        if (baseImageDesc.size.width == 0 || baseImageDesc.size.height == 0 || baseImageDesc.size.depth == 0)
        {
            return false;
        }
    
        if (mTarget == GL_TEXTURE_CUBE_MAP && baseImageDesc.size.width != baseImageDesc.size.height)
        {
            return false;
        }
    
        const TextureCaps &textureCaps = data.textureCaps->get(baseImageDesc.internalFormat);
        if (!textureCaps.filterable && !IsPointSampled(samplerState))
        {
            return false;
        }
    
        bool npotSupport = data.extensions->textureNPOT || data.clientVersion >= 3;
        if (!npotSupport)
        {
            if ((samplerState.wrapS != GL_CLAMP_TO_EDGE && !gl::isPow2(baseImageDesc.size.width)) ||
                (samplerState.wrapT != GL_CLAMP_TO_EDGE && !gl::isPow2(baseImageDesc.size.height)))
            {
                return false;
            }
        }
    
        if (IsMipmapFiltered(samplerState))
        {
            if (!npotSupport)
            {
                if (!gl::isPow2(baseImageDesc.size.width) || !gl::isPow2(baseImageDesc.size.height))
                {
                    return false;
                }
            }
    
            if (!computeMipmapCompleteness(samplerState))
            {
                return false;
            }
        }
        else
        {
            if (mTarget == GL_TEXTURE_CUBE_MAP && !isCubeComplete())
            {
                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.
        const gl::InternalFormat &formatInfo = gl::GetInternalFormatInfo(baseImageDesc.internalFormat);
        if (formatInfo.depthBits > 0 && data.clientVersion > 2)
        {
            if (samplerState.compareMode == GL_NONE)
            {
                if ((samplerState.minFilter != GL_NEAREST && samplerState.minFilter != GL_NEAREST_MIPMAP_NEAREST) ||
                    samplerState.magFilter != GL_NEAREST)
                {
                    return false;
                }
            }
        }
    
        return true;
    }
    
    bool Texture::computeMipmapCompleteness(const gl::SamplerState &samplerState) const
    {
        size_t expectedMipLevels = getMipCompleteLevels();
    
        size_t maxLevel = std::min<size_t>(expectedMipLevels, samplerState.maxLevel + 1);
    
        for (size_t level = samplerState.baseLevel; level < maxLevel; level++)
        {
            if (mTarget == GL_TEXTURE_CUBE_MAP)
            {
                for (GLenum face = FirstCubeMapTextureTarget; face <= LastCubeMapTextureTarget; face++)
                {
                    if (!computeLevelCompleteness(face, level, samplerState))
                    {
                        return false;
                    }
                }
            }
            else
            {
                if (!computeLevelCompleteness(mTarget, level, samplerState))
                {
                    return false;
                }
            }
        }
    
        return true;
    }
    
    
    bool Texture::computeLevelCompleteness(GLenum target, size_t level, const gl::SamplerState &samplerState) const
    {
        ASSERT(level < IMPLEMENTATION_MAX_TEXTURE_LEVELS);
    
        if (isImmutable())
        {
            return true;
        }
    
        const ImageDesc &baseImageDesc = getImageDesc(getBaseImageTarget(), samplerState.baseLevel);
        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 (levelImageDesc.internalFormat != baseImageDesc.internalFormat)
        {
            return false;
        }
    
        if (levelImageDesc.size.width != std::max(1, baseImageDesc.size.width >> level))
        {
            return false;
        }
    
        if (levelImageDesc.size.height != std::max(1, baseImageDesc.size.height >> level))
        {
            return false;
        }
    
        if (mTarget == GL_TEXTURE_3D)
        {
            if (levelImageDesc.size.depth != std::max(1, baseImageDesc.size.depth >> level))
            {
                return false;
            }
        }
        else if (mTarget == GL_TEXTURE_2D_ARRAY)
        {
            if (levelImageDesc.size.depth != baseImageDesc.size.depth)
            {
                return false;
            }
        }
    
        return true;
    }
    
    Texture::SamplerCompletenessCache::SamplerCompletenessCache()
        : cacheValid(false),
          samplerState(),
          filterable(false),
          clientVersion(0),
          supportsNPOT(false),
          samplerComplete(false)
    {
    }
    
    GLsizei Texture::getAttachmentWidth(const gl::FramebufferAttachment::Target &target) const
    {
        return static_cast<GLsizei>(
            getWidth(target.textureIndex().type, target.textureIndex().mipIndex));
    }
    
    GLsizei Texture::getAttachmentHeight(const gl::FramebufferAttachment::Target &target) const
    {
        return static_cast<GLsizei>(
            getHeight(target.textureIndex().type, target.textureIndex().mipIndex));
    }
    
    GLenum Texture::getAttachmentInternalFormat(const gl::FramebufferAttachment::Target &target) const
    {
        return getInternalFormat(target.textureIndex().type, target.textureIndex().mipIndex);
    }
    
    GLsizei Texture::getAttachmentSamples(const gl::FramebufferAttachment::Target &/*target*/) const
    {
        // Multisample textures not currently supported
        return 0;
    }
    
    void Texture::onAttach()
    {
        addRef();
    }
    
    void Texture::onDetach()
    {
        release();
    }
    
    GLuint Texture::getId() const
    {
        return id();
    }
    }