// Copyright 2016 The SwiftShader Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Texture.cpp: Implements the Texture class and its derived classes // Texture2D, TextureCubeMap, Texture3D and Texture2DArray. Implements GL texture objects // and related functionality. [OpenGL ES 2.0.24] section 3.7 page 63. #include "Texture.h" #include "main.h" #include "mathutil.h" #include "Framebuffer.h" #include "Device.hpp" #include "Sampler.h" #include "Shader.h" #include "libEGL/Display.h" #include "common/Surface.hpp" #include "common/debug.h" #include namespace es2 { egl::Image*& ImageLevels::getNullImage() { static egl::Image* nullImage; nullImage = nullptr; return nullImage; } Texture::Texture(GLuint name) : egl::Texture(name) { mMinFilter = GL_NEAREST_MIPMAP_LINEAR; mMagFilter = GL_LINEAR; mWrapS = GL_REPEAT; mWrapT = GL_REPEAT; mWrapR = GL_REPEAT; mMaxAnisotropy = 1.0f; mBaseLevel = 0; mCompareFunc = GL_LEQUAL; mCompareMode = GL_NONE; mImmutableFormat = GL_FALSE; mImmutableLevels = 0; mMaxLevel = 1000; mMaxLOD = 1000; mMinLOD = -1000; mSwizzleR = GL_RED; mSwizzleG = GL_GREEN; mSwizzleB = GL_BLUE; mSwizzleA = GL_ALPHA; resource = new sw::Resource(0); } Texture::~Texture() { resource->destruct(); } sw::Resource *Texture::getResource() const { return resource; } // Returns true on successful filter state update (valid enum parameter) bool Texture::setMinFilter(GLenum filter) { switch(filter) { case GL_NEAREST_MIPMAP_NEAREST: case GL_LINEAR_MIPMAP_NEAREST: case GL_NEAREST_MIPMAP_LINEAR: case GL_LINEAR_MIPMAP_LINEAR: if((getTarget() == GL_TEXTURE_EXTERNAL_OES) || (getTarget() == GL_TEXTURE_RECTANGLE_ARB)) { return false; } // Fall through case GL_NEAREST: case GL_LINEAR: mMinFilter = filter; return true; default: return false; } } // Returns true on successful filter state update (valid enum parameter) bool Texture::setMagFilter(GLenum filter) { switch(filter) { case GL_NEAREST: case GL_LINEAR: mMagFilter = filter; return true; default: return false; } } // Returns true on successful wrap state update (valid enum parameter) bool Texture::setWrapS(GLenum wrap) { switch(wrap) { case GL_REPEAT: case GL_MIRRORED_REPEAT: if((getTarget() == GL_TEXTURE_EXTERNAL_OES) || (getTarget() == GL_TEXTURE_RECTANGLE_ARB)) { return false; } // Fall through case GL_CLAMP_TO_EDGE: mWrapS = wrap; return true; default: return false; } } // Returns true on successful wrap state update (valid enum parameter) bool Texture::setWrapT(GLenum wrap) { switch(wrap) { case GL_REPEAT: case GL_MIRRORED_REPEAT: if((getTarget() == GL_TEXTURE_EXTERNAL_OES) || (getTarget() == GL_TEXTURE_RECTANGLE_ARB)) { return false; } // Fall through case GL_CLAMP_TO_EDGE: mWrapT = wrap; return true; default: return false; } } // Returns true on successful wrap state update (valid enum parameter) bool Texture::setWrapR(GLenum wrap) { switch(wrap) { case GL_REPEAT: case GL_MIRRORED_REPEAT: if((getTarget() == GL_TEXTURE_EXTERNAL_OES) || (getTarget() == GL_TEXTURE_RECTANGLE_ARB)) { return false; } // Fall through case GL_CLAMP_TO_EDGE: mWrapR = wrap; return true; default: return false; } } // Returns true on successful max anisotropy update (valid anisotropy value) bool Texture::setMaxAnisotropy(float textureMaxAnisotropy) { textureMaxAnisotropy = std::min(textureMaxAnisotropy, MAX_TEXTURE_MAX_ANISOTROPY); if(textureMaxAnisotropy < 1.0f) { return false; } if(mMaxAnisotropy != textureMaxAnisotropy) { mMaxAnisotropy = textureMaxAnisotropy; } return true; } bool Texture::setBaseLevel(GLint baseLevel) { if(baseLevel < 0) { return false; } mBaseLevel = baseLevel; return true; } bool Texture::setCompareFunc(GLenum compareFunc) { switch(compareFunc) { case GL_LEQUAL: case GL_GEQUAL: case GL_LESS: case GL_GREATER: case GL_EQUAL: case GL_NOTEQUAL: case GL_ALWAYS: case GL_NEVER: mCompareFunc = compareFunc; return true; default: return false; } } bool Texture::setCompareMode(GLenum compareMode) { switch(compareMode) { case GL_COMPARE_REF_TO_TEXTURE: case GL_NONE: mCompareMode = compareMode; return true; default: return false; } } void Texture::makeImmutable(GLsizei levels) { mImmutableFormat = GL_TRUE; mImmutableLevels = levels; } bool Texture::setMaxLevel(GLint maxLevel) { mMaxLevel = maxLevel; return true; } bool Texture::setMaxLOD(GLfloat maxLOD) { mMaxLOD = maxLOD; return true; } bool Texture::setMinLOD(GLfloat minLOD) { mMinLOD = minLOD; return true; } bool Texture::setSwizzleR(GLenum swizzleR) { switch(swizzleR) { case GL_RED: case GL_GREEN: case GL_BLUE: case GL_ALPHA: case GL_ZERO: case GL_ONE: mSwizzleR = swizzleR; return true; default: return false; } } bool Texture::setSwizzleG(GLenum swizzleG) { switch(swizzleG) { case GL_RED: case GL_GREEN: case GL_BLUE: case GL_ALPHA: case GL_ZERO: case GL_ONE: mSwizzleG = swizzleG; return true; default: return false; } } bool Texture::setSwizzleB(GLenum swizzleB) { switch(swizzleB) { case GL_RED: case GL_GREEN: case GL_BLUE: case GL_ALPHA: case GL_ZERO: case GL_ONE: mSwizzleB = swizzleB; return true; default: return false; } } bool Texture::setSwizzleA(GLenum swizzleA) { switch(swizzleA) { case GL_RED: case GL_GREEN: case GL_BLUE: case GL_ALPHA: case GL_ZERO: case GL_ONE: mSwizzleA = swizzleA; return true; default: return false; } } GLsizei Texture::getDepth(GLenum target, GLint level) const { return 1; } egl::Image *Texture::createSharedImage(GLenum target, unsigned int level) { egl::Image *image = getRenderTarget(target, level); // Increments reference count if(image) { image->markShared(); } return image; } void Texture::setImage(GLenum format, GLenum type, const gl::PixelStorageModes &unpackParameters, const void *pixels, egl::Image *image) { if(pixels && image) { GLsizei depth = (getTarget() == GL_TEXTURE_3D_OES || getTarget() == GL_TEXTURE_2D_ARRAY) ? image->getDepth() : 1; image->loadImageData(0, 0, 0, image->getWidth(), image->getHeight(), depth, format, type, unpackParameters, pixels); } } void Texture::setCompressedImage(GLsizei imageSize, const void *pixels, egl::Image *image) { if(pixels && image && (imageSize > 0)) // imageSize's correlation to width and height is already validated with gl::ComputeCompressedSize() at the API level { GLsizei depth = (getTarget() == GL_TEXTURE_3D_OES || getTarget() == GL_TEXTURE_2D_ARRAY) ? image->getDepth() : 1; image->loadCompressedData(0, 0, 0, image->getWidth(), image->getHeight(), depth, imageSize, pixels); } } void Texture::subImage(GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const gl::PixelStorageModes &unpackParameters, const void *pixels, egl::Image *image) { if(!image) { return error(GL_INVALID_OPERATION); } if(pixels && width > 0 && height > 0 && depth > 0) { image->loadImageData(xoffset, yoffset, zoffset, width, height, depth, format, type, unpackParameters, pixels); } } void Texture::subImageCompressed(GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const void *pixels, egl::Image *image) { if(!image) { return error(GL_INVALID_OPERATION); } if(pixels && (imageSize > 0)) // imageSize's correlation to width and height is already validated with gl::ComputeCompressedSize() at the API level { image->loadCompressedData(xoffset, yoffset, zoffset, width, height, depth, imageSize, pixels); } } bool Texture::copy(egl::Image *source, const sw::SliceRect &sourceRect, GLint xoffset, GLint yoffset, GLint zoffset, egl::Image *dest) { Device *device = getDevice(); sw::SliceRect destRect(xoffset, yoffset, xoffset + (sourceRect.x1 - sourceRect.x0), yoffset + (sourceRect.y1 - sourceRect.y0), zoffset); sw::SliceRectF sourceRectF(static_cast(sourceRect.x0), static_cast(sourceRect.y0), static_cast(sourceRect.x1), static_cast(sourceRect.y1), sourceRect.slice); bool success = device->stretchRect(source, &sourceRectF, dest, &destRect, Device::ALL_BUFFERS); if(!success) { return error(GL_OUT_OF_MEMORY, false); } return true; } bool Texture::isMipmapFiltered(Sampler *sampler) const { GLenum minFilter = sampler ? sampler->getMinFilter() : mMinFilter; switch(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(minFilter); } return false; } Texture2D::Texture2D(GLuint name) : Texture(name) { mSurface = nullptr; mColorbufferProxy = nullptr; mProxyRefs = 0; } Texture2D::~Texture2D() { image.unbind(this); if(mSurface) { mSurface->setBoundTexture(nullptr); mSurface = nullptr; } mColorbufferProxy = nullptr; } // We need to maintain a count of references to renderbuffers acting as // proxies for this texture, so that we do not attempt to use a pointer // to a renderbuffer proxy which has been deleted. void Texture2D::addProxyRef(const Renderbuffer *proxy) { mProxyRefs++; } void Texture2D::releaseProxy(const Renderbuffer *proxy) { if(mProxyRefs > 0) { mProxyRefs--; } if(mProxyRefs == 0) { mColorbufferProxy = nullptr; } } void Texture2D::sweep() { int imageCount = 0; for(int i = 0; i < IMPLEMENTATION_MAX_TEXTURE_LEVELS; i++) { if(image[i] && image[i]->isChildOf(this)) { if(!image[i]->hasSingleReference()) { return; } imageCount++; } } if(imageCount == referenceCount) { destroy(); } } GLenum Texture2D::getTarget() const { return GL_TEXTURE_2D; } GLsizei Texture2D::getWidth(GLenum target, GLint level) const { ASSERT(target == getTarget()); return image[level] ? image[level]->getWidth() : 0; } GLsizei Texture2D::getHeight(GLenum target, GLint level) const { ASSERT(target == getTarget()); return image[level] ? image[level]->getHeight() : 0; } GLint Texture2D::getFormat(GLenum target, GLint level) const { ASSERT(target == getTarget()); return image[level] ? image[level]->getFormat() : GL_NONE; } int Texture2D::getTopLevel() const { int level = mBaseLevel; while(level < IMPLEMENTATION_MAX_TEXTURE_LEVELS && image[level]) { level++; } return level - 1; } bool Texture2D::requiresSync() const { for(int level = 0; level < IMPLEMENTATION_MAX_TEXTURE_LEVELS; level++) { if(image[level] && image[level]->requiresSync()) { return true; } } return false; } void Texture2D::setImage(GLint level, GLsizei width, GLsizei height, GLint internalformat, GLenum format, GLenum type, const gl::PixelStorageModes &unpackParameters, const void *pixels) { if(image[level]) { image[level]->release(); } image[level] = egl::Image::create(this, width, height, internalformat); if(!image[level]) { return error(GL_OUT_OF_MEMORY); } Texture::setImage(format, type, unpackParameters, pixels, image[level]); } void Texture2D::bindTexImage(gl::Surface *surface) { image.release(); image[0] = surface->getRenderTarget(); mSurface = surface; mSurface->setBoundTexture(this); } void Texture2D::releaseTexImage() { image.release(); if(mSurface) { mSurface->setBoundTexture(nullptr); mSurface = nullptr; } } void Texture2D::setCompressedImage(GLint level, GLenum format, GLsizei width, GLsizei height, GLsizei imageSize, const void *pixels) { if(image[level]) { image[level]->release(); } image[level] = egl::Image::create(this, width, height, format); if(!image[level]) { return error(GL_OUT_OF_MEMORY); } Texture::setCompressedImage(imageSize, pixels, image[level]); } void Texture2D::subImage(GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const gl::PixelStorageModes &unpackParameters, const void *pixels) { Texture::subImage(xoffset, yoffset, 0, width, height, 1, format, type, unpackParameters, pixels, image[level]); } void Texture2D::subImageCompressed(GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void *pixels) { Texture::subImageCompressed(xoffset, yoffset, 0, width, height, 1, format, imageSize, pixels, image[level]); } void Texture2D::copyImage(GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, Renderbuffer *source) { if(image[level]) { image[level]->release(); } image[level] = egl::Image::create(this, width, height, internalformat); if(!image[level]) { return error(GL_OUT_OF_MEMORY); } if(width != 0 && height != 0) { egl::Image *renderTarget = source->getRenderTarget(); if(!renderTarget) { ERR("Failed to retrieve the render target."); return error(GL_OUT_OF_MEMORY); } sw::SliceRect sourceRect(x, y, x + width, y + height, 0); sourceRect.clip(0, 0, renderTarget->getWidth(), renderTarget->getHeight()); copy(renderTarget, sourceRect, 0, 0, 0, image[level]); renderTarget->release(); } } void Texture2D::copySubImage(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height, Renderbuffer *source) { if(!image[level]) { return error(GL_INVALID_OPERATION); } if(xoffset + width > image[level]->getWidth() || yoffset + height > image[level]->getHeight() || zoffset != 0) { return error(GL_INVALID_VALUE); } if(width > 0 && height > 0) { egl::Image *renderTarget = source->getRenderTarget(); if(!renderTarget) { ERR("Failed to retrieve the render target."); return error(GL_OUT_OF_MEMORY); } sw::SliceRect sourceRect(x, y, x + width, y + height, 0); sourceRect.clip(0, 0, renderTarget->getWidth(), renderTarget->getHeight()); copy(renderTarget, sourceRect, xoffset, yoffset, zoffset, image[level]); renderTarget->release(); } } void Texture2D::setSharedImage(egl::Image *sharedImage) { if(sharedImage == image[0]) { return; } sharedImage->addRef(); if(image[0]) { image[0]->release(); } image[0] = sharedImage; } // Tests for 2D texture sampling completeness. [OpenGL ES 3.0.5] section 3.8.13 page 160. bool Texture2D::isSamplerComplete(Sampler *sampler) const { if(!image[mBaseLevel]) { return false; } GLsizei width = image[mBaseLevel]->getWidth(); GLsizei height = image[mBaseLevel]->getHeight(); if(width <= 0 || height <= 0) { return false; } if(isMipmapFiltered(sampler)) { if(!isMipmapComplete()) { return false; } } return true; } // Tests for 2D texture (mipmap) completeness. [OpenGL ES 3.0.5] section 3.8.13 page 160. bool Texture2D::isMipmapComplete() const { if(mBaseLevel > mMaxLevel) { return false; } GLsizei width = image[mBaseLevel]->getWidth(); GLsizei height = image[mBaseLevel]->getHeight(); int maxsize = std::max(width, height); int p = log2(maxsize) + mBaseLevel; int q = std::min(p, mMaxLevel); for(int level = mBaseLevel + 1; level <= q; level++) { if(!image[level]) { return false; } if(image[level]->getFormat() != image[mBaseLevel]->getFormat()) { return false; } int i = level - mBaseLevel; if(image[level]->getWidth() != std::max(1, width >> i)) { return false; } if(image[level]->getHeight() != std::max(1, height >> i)) { return false; } } return true; } bool Texture2D::isCompressed(GLenum target, GLint level) const { return IsCompressed(getFormat(target, level)); } bool Texture2D::isDepth(GLenum target, GLint level) const { return IsDepthTexture(getFormat(target, level)); } void Texture2D::generateMipmaps() { if(!image[mBaseLevel]) { return; // Image unspecified. Not an error. } if(image[mBaseLevel]->getWidth() == 0 || image[mBaseLevel]->getHeight() == 0) { return; // Zero dimension. Not an error. } int maxsize = std::max(image[mBaseLevel]->getWidth(), image[mBaseLevel]->getHeight()); int p = log2(maxsize) + mBaseLevel; int q = std::min(p, mMaxLevel); for(int i = mBaseLevel + 1; i <= q; i++) { if(image[i]) { image[i]->release(); } image[i] = egl::Image::create(this, std::max(image[mBaseLevel]->getWidth() >> i, 1), std::max(image[mBaseLevel]->getHeight() >> i, 1), image[mBaseLevel]->getFormat()); if(!image[i]) { return error(GL_OUT_OF_MEMORY); } getDevice()->stretchRect(image[i - 1], 0, image[i], 0, Device::ALL_BUFFERS | Device::USE_FILTER); } } egl::Image *Texture2D::getImage(unsigned int level) { return image[level]; } Renderbuffer *Texture2D::getRenderbuffer(GLenum target, GLint level) { if(target != getTarget()) { return error(GL_INVALID_OPERATION, (Renderbuffer*)nullptr); } if(!mColorbufferProxy) { mColorbufferProxy = new Renderbuffer(name, new RenderbufferTexture2D(this, level)); } else { mColorbufferProxy->setLevel(level); } return mColorbufferProxy; } egl::Image *Texture2D::getRenderTarget(GLenum target, unsigned int level) { ASSERT(target == getTarget()); ASSERT(level < IMPLEMENTATION_MAX_TEXTURE_LEVELS); if(image[level]) { image[level]->addRef(); } return image[level]; } bool Texture2D::isShared(GLenum target, unsigned int level) const { ASSERT(target == getTarget()); ASSERT(level < IMPLEMENTATION_MAX_TEXTURE_LEVELS); if(mSurface) // Bound to an EGLSurface { return true; } if(!image[level]) { return false; } return image[level]->isShared(); } Texture2DRect::Texture2DRect(GLuint name) : Texture2D(name) { mMinFilter = GL_LINEAR; mMagFilter = GL_LINEAR; mWrapS = GL_CLAMP_TO_EDGE; mWrapT = GL_CLAMP_TO_EDGE; mWrapR = GL_CLAMP_TO_EDGE; } GLenum Texture2DRect::getTarget() const { return GL_TEXTURE_RECTANGLE_ARB; } Renderbuffer *Texture2DRect::getRenderbuffer(GLenum target, GLint level) { if((target != getTarget()) || (level != 0)) { return error(GL_INVALID_OPERATION, (Renderbuffer*)nullptr); } if(!mColorbufferProxy) { mColorbufferProxy = new Renderbuffer(name, new RenderbufferTexture2DRect(this)); } return mColorbufferProxy; } TextureCubeMap::TextureCubeMap(GLuint name) : Texture(name) { for(int f = 0; f < 6; f++) { mFaceProxies[f] = nullptr; mFaceProxyRefs[f] = 0; } } TextureCubeMap::~TextureCubeMap() { for(int i = 0; i < 6; i++) { image[i].unbind(this); mFaceProxies[i] = nullptr; } } // We need to maintain a count of references to renderbuffers acting as // proxies for this texture, so that the texture is not deleted while // proxy references still exist. If the reference count drops to zero, // we set our proxy pointer null, so that a new attempt at referencing // will cause recreation. void TextureCubeMap::addProxyRef(const Renderbuffer *proxy) { for(int f = 0; f < 6; f++) { if(mFaceProxies[f] == proxy) { mFaceProxyRefs[f]++; } } } void TextureCubeMap::releaseProxy(const Renderbuffer *proxy) { for(int f = 0; f < 6; f++) { if(mFaceProxies[f] == proxy) { if(mFaceProxyRefs[f] > 0) { mFaceProxyRefs[f]--; } if(mFaceProxyRefs[f] == 0) { mFaceProxies[f] = nullptr; } } } } void TextureCubeMap::sweep() { int imageCount = 0; for(int f = 0; f < 6; f++) { for(int i = 0; i < IMPLEMENTATION_MAX_TEXTURE_LEVELS; i++) { if(image[f][i] && image[f][i]->isChildOf(this)) { if(!image[f][i]->hasSingleReference()) { return; } imageCount++; } } } if(imageCount == referenceCount) { destroy(); } } GLenum TextureCubeMap::getTarget() const { return GL_TEXTURE_CUBE_MAP; } GLsizei TextureCubeMap::getWidth(GLenum target, GLint level) const { int face = CubeFaceIndex(target); return image[face][level] ? image[face][level]->getWidth() : 0; } GLsizei TextureCubeMap::getHeight(GLenum target, GLint level) const { int face = CubeFaceIndex(target); return image[face][level] ? image[face][level]->getHeight() : 0; } GLint TextureCubeMap::getFormat(GLenum target, GLint level) const { int face = CubeFaceIndex(target); return image[face][level] ? image[face][level]->getFormat() : 0; } int TextureCubeMap::getTopLevel() const { int level = mBaseLevel; while(level < IMPLEMENTATION_MAX_TEXTURE_LEVELS && image[0][level]) { level++; } return level - 1; } bool TextureCubeMap::requiresSync() const { for(int level = 0; level < IMPLEMENTATION_MAX_TEXTURE_LEVELS; level++) { for(int face = 0; face < 6; face++) { if(image[face][level] && image[face][level]->requiresSync()) { return true; } } } return false; } void TextureCubeMap::setCompressedImage(GLenum target, GLint level, GLenum format, GLsizei width, GLsizei height, GLsizei imageSize, const void *pixels) { int face = CubeFaceIndex(target); if(image[face][level]) { image[face][level]->release(); } image[face][level] = egl::Image::create(this, width, height, 1, 1, format); if(!image[face][level]) { return error(GL_OUT_OF_MEMORY); } Texture::setCompressedImage(imageSize, pixels, image[face][level]); } void TextureCubeMap::subImage(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const gl::PixelStorageModes &unpackParameters, const void *pixels) { Texture::subImage(xoffset, yoffset, 0, width, height, 1, format, type, unpackParameters, pixels, image[CubeFaceIndex(target)][level]); } void TextureCubeMap::subImageCompressed(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void *pixels) { Texture::subImageCompressed(xoffset, yoffset, 0, width, height, 1, format, imageSize, pixels, image[CubeFaceIndex(target)][level]); } // Tests for cube map sampling completeness. [OpenGL ES 3.0.5] section 3.8.13 page 161. bool TextureCubeMap::isSamplerComplete(Sampler *sampler) const { for(int face = 0; face < 6; face++) { if(!image[face][mBaseLevel]) { return false; } } int size = image[0][mBaseLevel]->getWidth(); if(size <= 0) { return false; } if(!isMipmapFiltered(sampler)) { if(!isCubeComplete()) { return false; } } else { if(!isMipmapCubeComplete()) // Also tests for isCubeComplete() { return false; } } return true; } // Tests for cube texture completeness. [OpenGL ES 3.0.5] section 3.8.13 page 160. bool TextureCubeMap::isCubeComplete() const { if(image[0][mBaseLevel]->getWidth() <= 0 || image[0][mBaseLevel]->getHeight() != image[0][mBaseLevel]->getWidth()) { return false; } for(unsigned int face = 1; face < 6; face++) { if(image[face][mBaseLevel]->getWidth() != image[0][mBaseLevel]->getWidth() || image[face][mBaseLevel]->getWidth() != image[0][mBaseLevel]->getHeight() || image[face][mBaseLevel]->getFormat() != image[0][mBaseLevel]->getFormat()) { return false; } } return true; } bool TextureCubeMap::isMipmapCubeComplete() const { if(mBaseLevel > mMaxLevel) { return false; } if(!isCubeComplete()) { return false; } GLsizei size = image[0][mBaseLevel]->getWidth(); int p = log2(size) + mBaseLevel; int q = std::min(p, mMaxLevel); for(int face = 0; face < 6; face++) { for(int level = mBaseLevel + 1; level <= q; level++) { if(!image[face][level]) { return false; } if(image[face][level]->getFormat() != image[0][mBaseLevel]->getFormat()) { return false; } int i = level - mBaseLevel; if(image[face][level]->getWidth() != std::max(1, size >> i)) { return false; } } } return true; } void TextureCubeMap::updateBorders(int level) { egl::Image *posX = image[CubeFaceIndex(GL_TEXTURE_CUBE_MAP_POSITIVE_X)][level]; egl::Image *negX = image[CubeFaceIndex(GL_TEXTURE_CUBE_MAP_NEGATIVE_X)][level]; egl::Image *posY = image[CubeFaceIndex(GL_TEXTURE_CUBE_MAP_POSITIVE_Y)][level]; egl::Image *negY = image[CubeFaceIndex(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y)][level]; egl::Image *posZ = image[CubeFaceIndex(GL_TEXTURE_CUBE_MAP_POSITIVE_Z)][level]; egl::Image *negZ = image[CubeFaceIndex(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z)][level]; if(!posX || !negX || !posY || !negY || !posZ || !negZ) { return; } if(posX->getBorder() == 0) // Non-seamless cube map. { return; } if(!posX->hasDirtyContents() || !posY->hasDirtyContents() || !posZ->hasDirtyContents() || !negX->hasDirtyContents() || !negY->hasDirtyContents() || !negZ->hasDirtyContents()) { return; } // Copy top / bottom first. posX->copyCubeEdge(sw::Surface::BOTTOM, negY, sw::Surface::RIGHT); posY->copyCubeEdge(sw::Surface::BOTTOM, posZ, sw::Surface::TOP); posZ->copyCubeEdge(sw::Surface::BOTTOM, negY, sw::Surface::TOP); negX->copyCubeEdge(sw::Surface::BOTTOM, negY, sw::Surface::LEFT); negY->copyCubeEdge(sw::Surface::BOTTOM, negZ, sw::Surface::BOTTOM); negZ->copyCubeEdge(sw::Surface::BOTTOM, negY, sw::Surface::BOTTOM); posX->copyCubeEdge(sw::Surface::TOP, posY, sw::Surface::RIGHT); posY->copyCubeEdge(sw::Surface::TOP, negZ, sw::Surface::TOP); posZ->copyCubeEdge(sw::Surface::TOP, posY, sw::Surface::BOTTOM); negX->copyCubeEdge(sw::Surface::TOP, posY, sw::Surface::LEFT); negY->copyCubeEdge(sw::Surface::TOP, posZ, sw::Surface::BOTTOM); negZ->copyCubeEdge(sw::Surface::TOP, posY, sw::Surface::TOP); // Copy left / right after top and bottom are done. // The corner colors will be computed assuming top / bottom are already set. posX->copyCubeEdge(sw::Surface::RIGHT, negZ, sw::Surface::LEFT); posY->copyCubeEdge(sw::Surface::RIGHT, posX, sw::Surface::TOP); posZ->copyCubeEdge(sw::Surface::RIGHT, posX, sw::Surface::LEFT); negX->copyCubeEdge(sw::Surface::RIGHT, posZ, sw::Surface::LEFT); negY->copyCubeEdge(sw::Surface::RIGHT, posX, sw::Surface::BOTTOM); negZ->copyCubeEdge(sw::Surface::RIGHT, negX, sw::Surface::LEFT); posX->copyCubeEdge(sw::Surface::LEFT, posZ, sw::Surface::RIGHT); posY->copyCubeEdge(sw::Surface::LEFT, negX, sw::Surface::TOP); posZ->copyCubeEdge(sw::Surface::LEFT, negX, sw::Surface::RIGHT); negX->copyCubeEdge(sw::Surface::LEFT, negZ, sw::Surface::RIGHT); negY->copyCubeEdge(sw::Surface::LEFT, negX, sw::Surface::BOTTOM); negZ->copyCubeEdge(sw::Surface::LEFT, posX, sw::Surface::RIGHT); posX->markContentsClean(); posY->markContentsClean(); posZ->markContentsClean(); negX->markContentsClean(); negY->markContentsClean(); negZ->markContentsClean(); } bool TextureCubeMap::isCompressed(GLenum target, GLint level) const { return IsCompressed(getFormat(target, level)); } bool TextureCubeMap::isDepth(GLenum target, GLint level) const { return IsDepthTexture(getFormat(target, level)); } void TextureCubeMap::releaseTexImage() { UNREACHABLE(0); // Cube maps cannot have an EGL surface bound as an image } void TextureCubeMap::setImage(GLenum target, GLint level, GLsizei width, GLsizei height, GLint internalformat, GLenum format, GLenum type, const gl::PixelStorageModes &unpackParameters, const void *pixels) { int face = CubeFaceIndex(target); if(image[face][level]) { image[face][level]->release(); } image[face][level] = egl::Image::create(this, width, height, 1, 1, internalformat); if(!image[face][level]) { return error(GL_OUT_OF_MEMORY); } Texture::setImage(format, type, unpackParameters, pixels, image[face][level]); } void TextureCubeMap::copyImage(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, Renderbuffer *source) { int face = CubeFaceIndex(target); if(image[face][level]) { image[face][level]->release(); } image[face][level] = egl::Image::create(this, width, height, 1, 1, internalformat); if(!image[face][level]) { return error(GL_OUT_OF_MEMORY); } if(width != 0 && height != 0) { egl::Image *renderTarget = source->getRenderTarget(); if(!renderTarget) { ERR("Failed to retrieve the render target."); return error(GL_OUT_OF_MEMORY); } sw::SliceRect sourceRect(x, y, x + width, y + height, 0); sourceRect.clip(0, 0, renderTarget->getWidth(), renderTarget->getHeight()); copy(renderTarget, sourceRect, 0, 0, 0, image[face][level]); renderTarget->release(); } } egl::Image *TextureCubeMap::getImage(int face, unsigned int level) { return image[face][level]; } egl::Image *TextureCubeMap::getImage(GLenum face, unsigned int level) { return image[CubeFaceIndex(face)][level]; } void TextureCubeMap::copySubImage(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height, Renderbuffer *source) { int face = CubeFaceIndex(target); if(!image[face][level]) { return error(GL_INVALID_OPERATION); } GLsizei size = image[face][level]->getWidth(); if(xoffset + width > size || yoffset + height > size || zoffset != 0) { return error(GL_INVALID_VALUE); } if(width > 0 && height > 0) { egl::Image *renderTarget = source->getRenderTarget(); if(!renderTarget) { ERR("Failed to retrieve the render target."); return error(GL_OUT_OF_MEMORY); } sw::SliceRect sourceRect(x, y, x + width, y + height, 0); sourceRect.clip(0, 0, renderTarget->getWidth(), renderTarget->getHeight()); copy(renderTarget, sourceRect, xoffset, yoffset, zoffset, image[face][level]); renderTarget->release(); } } void TextureCubeMap::generateMipmaps() { if(!isCubeComplete()) { return error(GL_INVALID_OPERATION); } int p = log2(image[0][mBaseLevel]->getWidth()) + mBaseLevel; int q = std::min(p, mMaxLevel); for(int f = 0; f < 6; f++) { ASSERT(image[f][mBaseLevel]); for(int i = mBaseLevel + 1; i <= q; i++) { if(image[f][i]) { image[f][i]->release(); } image[f][i] = egl::Image::create(this, std::max(image[f][mBaseLevel]->getWidth() >> i, 1), std::max(image[f][mBaseLevel]->getHeight() >> i, 1), 1, 1, image[f][mBaseLevel]->getFormat()); if(!image[f][i]) { return error(GL_OUT_OF_MEMORY); } getDevice()->stretchRect(image[f][i - 1], 0, image[f][i], 0, Device::ALL_BUFFERS | Device::USE_FILTER); } } } Renderbuffer *TextureCubeMap::getRenderbuffer(GLenum target, GLint level) { if(!IsCubemapTextureTarget(target)) { return error(GL_INVALID_OPERATION, (Renderbuffer*)nullptr); } int face = CubeFaceIndex(target); if(!mFaceProxies[face]) { mFaceProxies[face] = new Renderbuffer(name, new RenderbufferTextureCubeMap(this, target, level)); } else { mFaceProxies[face]->setLevel(level); } return mFaceProxies[face]; } egl::Image *TextureCubeMap::getRenderTarget(GLenum target, unsigned int level) { ASSERT(IsCubemapTextureTarget(target)); ASSERT(level < IMPLEMENTATION_MAX_TEXTURE_LEVELS); int face = CubeFaceIndex(target); if(image[face][level]) { image[face][level]->addRef(); } return image[face][level]; } bool TextureCubeMap::isShared(GLenum target, unsigned int level) const { ASSERT(IsCubemapTextureTarget(target)); ASSERT(level < IMPLEMENTATION_MAX_TEXTURE_LEVELS); int face = CubeFaceIndex(target); if(!image[face][level]) { return false; } return image[face][level]->isShared(); } Texture3D::Texture3D(GLuint name) : Texture(name) { mSurface = nullptr; mColorbufferProxy = nullptr; mProxyRefs = 0; } Texture3D::~Texture3D() { image.unbind(this); if(mSurface) { mSurface->setBoundTexture(nullptr); mSurface = nullptr; } mColorbufferProxy = nullptr; } // We need to maintain a count of references to renderbuffers acting as // proxies for this texture, so that we do not attempt to use a pointer // to a renderbuffer proxy which has been deleted. void Texture3D::addProxyRef(const Renderbuffer *proxy) { mProxyRefs++; } void Texture3D::releaseProxy(const Renderbuffer *proxy) { if(mProxyRefs > 0) { mProxyRefs--; } if(mProxyRefs == 0) { mColorbufferProxy = nullptr; } } void Texture3D::sweep() { int imageCount = 0; for(int i = 0; i < IMPLEMENTATION_MAX_TEXTURE_LEVELS; i++) { if(image[i] && image[i]->isChildOf(this)) { if(!image[i]->hasSingleReference()) { return; } imageCount++; } } if(imageCount == referenceCount) { destroy(); } } GLenum Texture3D::getTarget() const { return GL_TEXTURE_3D_OES; } GLsizei Texture3D::getWidth(GLenum target, GLint level) const { ASSERT(target == getTarget()); return image[level] ? image[level]->getWidth() : 0; } GLsizei Texture3D::getHeight(GLenum target, GLint level) const { ASSERT(target == getTarget()); return image[level] ? image[level]->getHeight() : 0; } GLsizei Texture3D::getDepth(GLenum target, GLint level) const { ASSERT(target == getTarget()); return image[level] ? image[level]->getDepth() : 0; } GLint Texture3D::getFormat(GLenum target, GLint level) const { ASSERT(target == getTarget()); return image[level] ? image[level]->getFormat() : GL_NONE; } int Texture3D::getTopLevel() const { int level = mBaseLevel; while(level < IMPLEMENTATION_MAX_TEXTURE_LEVELS && image[level]) { level++; } return level - 1; } bool Texture3D::requiresSync() const { for(int level = 0; level < IMPLEMENTATION_MAX_TEXTURE_LEVELS; level++) { if(image[level] && image[level]->requiresSync()) { return true; } } return false; } void Texture3D::setImage(GLint level, GLsizei width, GLsizei height, GLsizei depth, GLint internalformat, GLenum format, GLenum type, const gl::PixelStorageModes &unpackParameters, const void *pixels) { if(image[level]) { image[level]->release(); } image[level] = egl::Image::create(this, width, height, depth, 0, internalformat); if(!image[level]) { return error(GL_OUT_OF_MEMORY); } Texture::setImage(format, type, unpackParameters, pixels, image[level]); } void Texture3D::releaseTexImage() { UNREACHABLE(0); // 3D textures cannot have an EGL surface bound as an image } void Texture3D::setCompressedImage(GLint level, GLenum format, GLsizei width, GLsizei height, GLsizei depth, GLsizei imageSize, const void *pixels) { if(image[level]) { image[level]->release(); } image[level] = egl::Image::create(this, width, height, depth, 0, format); if(!image[level]) { return error(GL_OUT_OF_MEMORY); } Texture::setCompressedImage(imageSize, pixels, image[level]); } void Texture3D::subImage(GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const gl::PixelStorageModes &unpackParameters, const void *pixels) { Texture::subImage(xoffset, yoffset, zoffset, width, height, depth, format, type, unpackParameters, pixels, image[level]); } void Texture3D::subImageCompressed(GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const void *pixels) { Texture::subImageCompressed(xoffset, yoffset, zoffset, width, height, depth, format, imageSize, pixels, image[level]); } void Texture3D::copyImage(GLint level, GLenum internalformat, GLint x, GLint y, GLint z, GLsizei width, GLsizei height, GLsizei depth, Renderbuffer *source) { if(image[level]) { image[level]->release(); } image[level] = egl::Image::create(this, width, height, depth, 0, internalformat); if(!image[level]) { return error(GL_OUT_OF_MEMORY); } if(width != 0 && height != 0 && depth != 0) { egl::Image *renderTarget = source->getRenderTarget(); if(!renderTarget) { ERR("Failed to retrieve the render target."); return error(GL_OUT_OF_MEMORY); } sw::SliceRect sourceRect(x, y, x + width, y + height, z); sourceRect.clip(0, 0, renderTarget->getWidth(), renderTarget->getHeight()); for(GLint sliceZ = 0; sliceZ < depth; sliceZ++, sourceRect.slice++) { copy(renderTarget, sourceRect, 0, 0, sliceZ, image[level]); } renderTarget->release(); } } void Texture3D::copySubImage(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height, Renderbuffer *source) { if(!image[level]) { return error(GL_INVALID_OPERATION); } if(xoffset + width > image[level]->getWidth() || yoffset + height > image[level]->getHeight() || zoffset >= image[level]->getDepth()) { return error(GL_INVALID_VALUE); } if(width > 0 && height > 0) { egl::Image *renderTarget = source->getRenderTarget(); if(!renderTarget) { ERR("Failed to retrieve the render target."); return error(GL_OUT_OF_MEMORY); } sw::SliceRect sourceRect = {x, y, x + width, y + height, 0}; sourceRect.clip(0, 0, renderTarget->getWidth(), renderTarget->getHeight()); copy(renderTarget, sourceRect, xoffset, yoffset, zoffset, image[level]); renderTarget->release(); } } void Texture3D::setSharedImage(egl::Image *sharedImage) { sharedImage->addRef(); if(image[0]) { image[0]->release(); } image[0] = sharedImage; } // Tests for 3D texture sampling completeness. [OpenGL ES 3.0.5] section 3.8.13 page 160. bool Texture3D::isSamplerComplete(Sampler *sampler) const { if(!image[mBaseLevel]) { return false; } GLsizei width = image[mBaseLevel]->getWidth(); GLsizei height = image[mBaseLevel]->getHeight(); GLsizei depth = image[mBaseLevel]->getDepth(); if(width <= 0 || height <= 0 || depth <= 0) { return false; } if(isMipmapFiltered(sampler)) { if(!isMipmapComplete()) { return false; } } return true; } // Tests for 3D texture (mipmap) completeness. [OpenGL ES 3.0.5] section 3.8.13 page 160. bool Texture3D::isMipmapComplete() const { if(mBaseLevel > mMaxLevel) { return false; } GLsizei width = image[mBaseLevel]->getWidth(); GLsizei height = image[mBaseLevel]->getHeight(); GLsizei depth = image[mBaseLevel]->getDepth(); bool isTexture2DArray = getTarget() == GL_TEXTURE_2D_ARRAY; int maxsize = isTexture2DArray ? std::max(width, height) : std::max(std::max(width, height), depth); int p = log2(maxsize) + mBaseLevel; int q = std::min(p, mMaxLevel); for(int level = mBaseLevel + 1; level <= q; level++) { if(!image[level]) { return false; } if(image[level]->getFormat() != image[mBaseLevel]->getFormat()) { return false; } int i = level - mBaseLevel; if(image[level]->getWidth() != std::max(1, width >> i)) { return false; } if(image[level]->getHeight() != std::max(1, height >> i)) { return false; } int levelDepth = isTexture2DArray ? depth : std::max(1, depth >> i); if(image[level]->getDepth() != levelDepth) { return false; } } return true; } bool Texture3D::isCompressed(GLenum target, GLint level) const { return IsCompressed(getFormat(target, level)); } bool Texture3D::isDepth(GLenum target, GLint level) const { return IsDepthTexture(getFormat(target, level)); } void Texture3D::generateMipmaps() { if(!image[mBaseLevel]) { return; // Image unspecified. Not an error. } if(image[mBaseLevel]->getWidth() == 0 || image[mBaseLevel]->getHeight() == 0 || image[mBaseLevel]->getDepth() == 0) { return; // Zero dimension. Not an error. } int maxsize = std::max(std::max(image[mBaseLevel]->getWidth(), image[mBaseLevel]->getHeight()), image[mBaseLevel]->getDepth()); int p = log2(maxsize) + mBaseLevel; int q = std::min(p, mMaxLevel); for(int i = mBaseLevel + 1; i <= q; i++) { if(image[i]) { image[i]->release(); } image[i] = egl::Image::create(this, std::max(image[mBaseLevel]->getWidth() >> i, 1), std::max(image[mBaseLevel]->getHeight() >> i, 1), std::max(image[mBaseLevel]->getDepth() >> i, 1), 0, image[mBaseLevel]->getFormat()); if(!image[i]) { return error(GL_OUT_OF_MEMORY); } getDevice()->stretchCube(image[i - 1], image[i]); } } egl::Image *Texture3D::getImage(unsigned int level) { return image[level]; } Renderbuffer *Texture3D::getRenderbuffer(GLenum target, GLint level) { if(target != getTarget()) { return error(GL_INVALID_OPERATION, (Renderbuffer*)nullptr); } if(!mColorbufferProxy) { mColorbufferProxy = new Renderbuffer(name, new RenderbufferTexture3D(this, level)); } else { mColorbufferProxy->setLevel(level); } return mColorbufferProxy; } egl::Image *Texture3D::getRenderTarget(GLenum target, unsigned int level) { ASSERT(target == getTarget()); ASSERT(level < IMPLEMENTATION_MAX_TEXTURE_LEVELS); if(image[level]) { image[level]->addRef(); } return image[level]; } bool Texture3D::isShared(GLenum target, unsigned int level) const { ASSERT(target == getTarget()); ASSERT(level < IMPLEMENTATION_MAX_TEXTURE_LEVELS); if(mSurface) // Bound to an EGLSurface { return true; } if(!image[level]) { return false; } return image[level]->isShared(); } Texture2DArray::Texture2DArray(GLuint name) : Texture3D(name) { } Texture2DArray::~Texture2DArray() { } GLenum Texture2DArray::getTarget() const { return GL_TEXTURE_2D_ARRAY; } void Texture2DArray::generateMipmaps() { if(!image[mBaseLevel]) { return; // Image unspecified. Not an error. } if(image[mBaseLevel]->getWidth() == 0 || image[mBaseLevel]->getHeight() == 0 || image[mBaseLevel]->getDepth() == 0) { return; // Zero dimension. Not an error. } int depth = image[mBaseLevel]->getDepth(); int maxsize = std::max(image[mBaseLevel]->getWidth(), image[mBaseLevel]->getHeight()); int p = log2(maxsize) + mBaseLevel; int q = std::min(p, mMaxLevel); for(int i = mBaseLevel + 1; i <= q; i++) { if(image[i]) { image[i]->release(); } GLsizei w = std::max(image[mBaseLevel]->getWidth() >> i, 1); GLsizei h = std::max(image[mBaseLevel]->getHeight() >> i, 1); image[i] = egl::Image::create(this, w, h, depth, 0, image[mBaseLevel]->getFormat()); if(!image[i]) { return error(GL_OUT_OF_MEMORY); } GLsizei srcw = image[i - 1]->getWidth(); GLsizei srch = image[i - 1]->getHeight(); for(int z = 0; z < depth; ++z) { sw::SliceRectF srcRect(0.0f, 0.0f, static_cast(srcw), static_cast(srch), z); sw::SliceRect dstRect(0, 0, w, h, z); getDevice()->stretchRect(image[i - 1], &srcRect, image[i], &dstRect, Device::ALL_BUFFERS | Device::USE_FILTER); } } } TextureExternal::TextureExternal(GLuint name) : Texture2D(name) { mMinFilter = GL_LINEAR; mMagFilter = GL_LINEAR; mWrapS = GL_CLAMP_TO_EDGE; mWrapT = GL_CLAMP_TO_EDGE; mWrapR = GL_CLAMP_TO_EDGE; } TextureExternal::~TextureExternal() { } GLenum TextureExternal::getTarget() const { return GL_TEXTURE_EXTERNAL_OES; } } NO_SANITIZE_FUNCTION egl::Image *createBackBuffer(int width, int height, sw::Format format, int multiSampleDepth) { if(width > es2::IMPLEMENTATION_MAX_RENDERBUFFER_SIZE || height > es2::IMPLEMENTATION_MAX_RENDERBUFFER_SIZE) { ERR("Invalid parameters: %dx%d", width, height); return nullptr; } GLenum internalformat = sw2es::ConvertBackBufferFormat(format); return egl::Image::create(width, height, internalformat, multiSampleDepth, false); } NO_SANITIZE_FUNCTION egl::Image *createBackBufferFromClientBuffer(const egl::ClientBuffer& clientBuffer) { if(clientBuffer.getWidth() > es2::IMPLEMENTATION_MAX_RENDERBUFFER_SIZE || clientBuffer.getHeight() > es2::IMPLEMENTATION_MAX_RENDERBUFFER_SIZE) { ERR("Invalid parameters: %dx%d", clientBuffer.getWidth(), clientBuffer.getHeight()); return nullptr; } return egl::Image::create(clientBuffer); } NO_SANITIZE_FUNCTION egl::Image *createDepthStencil(int width, int height, sw::Format format, int multiSampleDepth) { if(width > es2::IMPLEMENTATION_MAX_RENDERBUFFER_SIZE || height > es2::IMPLEMENTATION_MAX_RENDERBUFFER_SIZE) { ERR("Invalid parameters: %dx%d", width, height); return nullptr; } bool lockable = true; switch(format) { // case sw::FORMAT_D15S1: case sw::FORMAT_D24S8: case sw::FORMAT_D24X8: // case sw::FORMAT_D24X4S4: case sw::FORMAT_D24FS8: case sw::FORMAT_D32: case sw::FORMAT_D16: lockable = false; break; // case sw::FORMAT_S8_LOCKABLE: // case sw::FORMAT_D16_LOCKABLE: case sw::FORMAT_D32F_LOCKABLE: // case sw::FORMAT_D32_LOCKABLE: case sw::FORMAT_DF24S8: case sw::FORMAT_DF16S8: lockable = true; break; default: UNREACHABLE(format); } GLenum internalformat = sw2es::ConvertDepthStencilFormat(format); egl::Image *surface = egl::Image::create(width, height, internalformat, multiSampleDepth, lockable); if(!surface) { ERR("Out of memory"); return nullptr; } return surface; }