-// SwiftShader Software Renderer\r
-//\r
-// Copyright(c) 2005-2013 TransGaming Inc.\r
-//\r
-// All rights reserved. No part of this software may be copied, distributed, transmitted,\r
-// transcribed, stored in a retrieval system, translated into any human or computer\r
-// language by any means, or disclosed to third parties without the explicit written\r
-// agreement of TransGaming Inc. Without such an agreement, no rights or licenses, express\r
-// or implied, including but not limited to any patent rights, are granted to you.\r
-//\r
-\r
-// Texture.cpp: Implements the Texture class and its derived classes\r
-// Texture2D and TextureCubeMap. Implements GL texture objects and related\r
-// functionality. [OpenGL ES 2.0.24] section 3.7 page 63.\r
-\r
-#include "Texture.h"\r
-\r
-#include "main.h"\r
-#include "mathutil.h"\r
-#include "Framebuffer.h"\r
-#include "Device.hpp"\r
-#include "libEGL/Display.h"\r
-#include "libEGL/Surface.h"\r
-#include "common/debug.h"\r
-\r
-#include <algorithm>\r
-\r
-namespace es1\r
-{\r
-\r
-Texture::Texture(GLuint id) : RefCountObject(id)\r
-{\r
- mMinFilter = GL_NEAREST_MIPMAP_LINEAR;\r
- mMagFilter = GL_LINEAR;\r
- mWrapS = GL_REPEAT;\r
- mWrapT = GL_REPEAT;\r
- mMaxAnisotropy = 1.0f;\r
-\r
- resource = new sw::Resource(0);\r
-}\r
-\r
-Texture::~Texture()\r
-{\r
- resource->destruct();\r
-}\r
-\r
-sw::Resource *Texture::getResource() const\r
-{\r
- return resource;\r
-}\r
-\r
-// Returns true on successful filter state update (valid enum parameter)\r
-bool Texture::setMinFilter(GLenum filter)\r
-{\r
- switch(filter)\r
- {\r
- case GL_NEAREST_MIPMAP_NEAREST:\r
- case GL_LINEAR_MIPMAP_NEAREST:\r
- case GL_NEAREST_MIPMAP_LINEAR:\r
- case GL_LINEAR_MIPMAP_LINEAR:\r
- if(getTarget() == GL_TEXTURE_EXTERNAL_OES)\r
- {\r
- return false;\r
- }\r
- // Fall through\r
- case GL_NEAREST:\r
- case GL_LINEAR:\r
- mMinFilter = filter;\r
- return true;\r
- default:\r
- return false;\r
- }\r
-}\r
-\r
-// Returns true on successful filter state update (valid enum parameter)\r
-bool Texture::setMagFilter(GLenum filter)\r
-{\r
- switch(filter)\r
- {\r
- case GL_NEAREST:\r
- case GL_LINEAR:\r
- mMagFilter = filter;\r
- return true;\r
- default:\r
- return false;\r
- }\r
-}\r
-\r
-// Returns true on successful wrap state update (valid enum parameter)\r
-bool Texture::setWrapS(GLenum wrap)\r
-{\r
- switch(wrap)\r
- {\r
- case GL_REPEAT:\r
- case GL_MIRRORED_REPEAT_OES:\r
- if(getTarget() == GL_TEXTURE_EXTERNAL_OES)\r
- {\r
- return false;\r
- }\r
- // Fall through\r
- case GL_CLAMP_TO_EDGE:\r
- mWrapS = wrap;\r
- return true;\r
- default:\r
- return false;\r
- }\r
-}\r
-\r
-// Returns true on successful wrap state update (valid enum parameter)\r
-bool Texture::setWrapT(GLenum wrap)\r
-{\r
- switch(wrap)\r
- {\r
- case GL_REPEAT:\r
- case GL_MIRRORED_REPEAT_OES:\r
- if(getTarget() == GL_TEXTURE_EXTERNAL_OES)\r
- {\r
- return false;\r
- }\r
- // Fall through\r
- case GL_CLAMP_TO_EDGE:\r
- mWrapT = wrap;\r
- return true;\r
- default:\r
- return false;\r
- }\r
-}\r
-\r
-// Returns true on successful max anisotropy update (valid anisotropy value)\r
-bool Texture::setMaxAnisotropy(float textureMaxAnisotropy)\r
-{\r
- textureMaxAnisotropy = std::min(textureMaxAnisotropy, MAX_TEXTURE_MAX_ANISOTROPY);\r
-\r
- if(textureMaxAnisotropy < 1.0f)\r
- {\r
- return false;\r
- }\r
- \r
- if(mMaxAnisotropy != textureMaxAnisotropy)\r
- {\r
- mMaxAnisotropy = textureMaxAnisotropy;\r
- }\r
-\r
- return true;\r
-}\r
-\r
-GLenum Texture::getMinFilter() const\r
-{\r
- return mMinFilter;\r
-}\r
-\r
-GLenum Texture::getMagFilter() const\r
-{\r
- return mMagFilter;\r
-}\r
-\r
-GLenum Texture::getWrapS() const\r
-{\r
- return mWrapS;\r
-}\r
-\r
-GLenum Texture::getWrapT() const\r
-{\r
- return mWrapT;\r
-}\r
-\r
-GLfloat Texture::getMaxAnisotropy() const\r
-{\r
- return mMaxAnisotropy;\r
-}\r
-\r
-egl::Image *Texture::createSharedImage(GLenum target, unsigned int level)\r
-{\r
- egl::Image *image = getRenderTarget(target, level); // Increments reference count\r
-\r
- if(image)\r
- {\r
- image->markShared();\r
- }\r
-\r
- return image;\r
-}\r
-\r
-void Texture::setImage(GLenum format, GLenum type, GLint unpackAlignment, const void *pixels, egl::Image *image)\r
-{\r
- if(pixels && image)\r
- {\r
- image->loadImageData(0, 0, image->getWidth(), image->getHeight(), format, type, unpackAlignment, pixels);\r
- }\r
-}\r
-\r
-void Texture::setCompressedImage(GLsizei imageSize, const void *pixels, egl::Image *image)\r
-{\r
- if(pixels && image)\r
- {\r
- image->loadCompressedData(0, 0, image->getWidth(), image->getHeight(), imageSize, pixels);\r
- }\r
-}\r
-\r
-void Texture::subImage(GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, GLint unpackAlignment, const void *pixels, egl::Image *image)\r
-{\r
- if(!image)\r
- {\r
- return error(GL_INVALID_OPERATION);\r
- }\r
-\r
- if(width + xoffset > image->getWidth() || height + yoffset > image->getHeight())\r
- {\r
- return error(GL_INVALID_VALUE);\r
- }\r
-\r
- if(IsCompressed(image->getFormat()))\r
- {\r
- return error(GL_INVALID_OPERATION);\r
- }\r
-\r
- if(format != image->getFormat())\r
- {\r
- return error(GL_INVALID_OPERATION);\r
- }\r
-\r
- if(pixels)\r
- {\r
- image->loadImageData(xoffset, yoffset, width, height, format, type, unpackAlignment, pixels);\r
- }\r
-}\r
-\r
-void Texture::subImageCompressed(GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void *pixels, egl::Image *image)\r
-{\r
- if(!image)\r
- {\r
- return error(GL_INVALID_OPERATION);\r
- }\r
-\r
- if(width + xoffset > image->getWidth() || height + yoffset > image->getHeight())\r
- {\r
- return error(GL_INVALID_VALUE);\r
- }\r
-\r
- if(format != image->getFormat())\r
- {\r
- return error(GL_INVALID_OPERATION);\r
- }\r
-\r
- if(pixels)\r
- {\r
- image->loadCompressedData(xoffset, yoffset, width, height, imageSize, pixels);\r
- }\r
-}\r
-\r
-bool Texture::copy(egl::Image *source, const sw::Rect &sourceRect, GLenum destFormat, GLint xoffset, GLint yoffset, egl::Image *dest)\r
-{\r
- Device *device = getDevice();\r
- \r
- sw::Rect destRect = {xoffset, yoffset, xoffset + (sourceRect.x1 - sourceRect.x0), yoffset + (sourceRect.y1 - sourceRect.y0)};\r
- bool success = device->stretchRect(source, &sourceRect, dest, &destRect, false);\r
-\r
- if(!success)\r
- {\r
- return error(GL_OUT_OF_MEMORY, false);\r
- }\r
-\r
- return true;\r
-}\r
-\r
-bool Texture::isMipmapFiltered() const\r
-{\r
- switch(mMinFilter)\r
- {\r
- case GL_NEAREST:\r
- case GL_LINEAR:\r
- return false;\r
- case GL_NEAREST_MIPMAP_NEAREST:\r
- case GL_LINEAR_MIPMAP_NEAREST:\r
- case GL_NEAREST_MIPMAP_LINEAR:\r
- case GL_LINEAR_MIPMAP_LINEAR:\r
- return true;\r
- default: UNREACHABLE();\r
- }\r
-\r
- return false;\r
-}\r
-\r
-Texture2D::Texture2D(GLuint id) : Texture(id)\r
-{\r
- for(int i = 0; i < MIPMAP_LEVELS; i++)\r
- {\r
- image[i] = 0;\r
- }\r
-\r
- mSurface = NULL;\r
-\r
- mColorbufferProxy = NULL;\r
- mProxyRefs = 0;\r
-}\r
-\r
-Texture2D::~Texture2D()\r
-{\r
- resource->lock(sw::DESTRUCT);\r
-\r
- for(int i = 0; i < MIPMAP_LEVELS; i++)\r
- {\r
- if(image[i])\r
- {\r
- image[i]->unbind();\r
- image[i] = 0;\r
- }\r
- }\r
-\r
- resource->unlock();\r
-\r
- if(mSurface)\r
- {\r
- mSurface->setBoundTexture(NULL);\r
- mSurface = NULL;\r
- }\r
-\r
- mColorbufferProxy = NULL;\r
-}\r
-\r
-// We need to maintain a count of references to renderbuffers acting as \r
-// proxies for this texture, so that we do not attempt to use a pointer \r
-// to a renderbuffer proxy which has been deleted.\r
-void Texture2D::addProxyRef(const Renderbuffer *proxy)\r
-{\r
- mProxyRefs++;\r
-}\r
-\r
-void Texture2D::releaseProxy(const Renderbuffer *proxy)\r
-{\r
- if(mProxyRefs > 0)\r
- {\r
- mProxyRefs--;\r
- }\r
-\r
- if(mProxyRefs == 0)\r
- {\r
- mColorbufferProxy = NULL;\r
- }\r
-}\r
-\r
-GLenum Texture2D::getTarget() const\r
-{\r
- return GL_TEXTURE_2D;\r
-}\r
-\r
-GLsizei Texture2D::getWidth(GLenum target, GLint level) const\r
-{\r
- ASSERT(target == GL_TEXTURE_2D);\r
- return image[level] ? image[level]->getWidth() : 0;\r
-}\r
-\r
-GLsizei Texture2D::getHeight(GLenum target, GLint level) const\r
-{\r
- ASSERT(target == GL_TEXTURE_2D);\r
- return image[level] ? image[level]->getHeight() : 0;\r
-}\r
-\r
-GLenum Texture2D::getFormat(GLenum target, GLint level) const\r
-{\r
- ASSERT(target == GL_TEXTURE_2D);\r
- return image[level] ? image[level]->getFormat() : 0;\r
-}\r
-\r
-GLenum Texture2D::getType(GLenum target, GLint level) const\r
-{\r
- ASSERT(target == GL_TEXTURE_2D);\r
- return image[level] ? image[level]->getType() : 0;\r
-}\r
-\r
-sw::Format Texture2D::getInternalFormat(GLenum target, GLint level) const\r
-{\r
- ASSERT(target == GL_TEXTURE_2D);\r
- return image[level] ? image[level]->getInternalFormat() : sw::FORMAT_NULL;\r
-}\r
-\r
-int Texture2D::getLevelCount() const\r
-{\r
- ASSERT(isSamplerComplete());\r
- int levels = 0;\r
-\r
- while(levels < MIPMAP_LEVELS && image[levels])\r
- {\r
- levels++;\r
- }\r
-\r
- return levels;\r
-}\r
-\r
-void Texture2D::setImage(GLint level, GLsizei width, GLsizei height, GLenum format, GLenum type, GLint unpackAlignment, const void *pixels)\r
-{\r
- if(image[level])\r
- {\r
- image[level]->unbind();\r
- }\r
-\r
- image[level] = new Image(this, width, height, format, type);\r
-\r
- if(!image[level])\r
- {\r
- return error(GL_OUT_OF_MEMORY);\r
- }\r
-\r
- Texture::setImage(format, type, unpackAlignment, pixels, image[level]);\r
-}\r
-\r
-void Texture2D::bindTexImage(egl::Surface *surface)\r
-{\r
- GLenum format;\r
-\r
- switch(surface->getInternalFormat())\r
- {\r
- case sw::FORMAT_A8R8G8B8:\r
- format = GL_RGBA;\r
- break;\r
- case sw::FORMAT_X8R8G8B8:\r
- format = GL_RGB;\r
- break;\r
- default:\r
- UNIMPLEMENTED();\r
- return;\r
- }\r
-\r
- for(int level = 0; level < MIPMAP_LEVELS; level++)\r
- {\r
- if(image[level])\r
- {\r
- image[level]->unbind();\r
- image[level] = 0;\r
- }\r
- }\r
-\r
- image[0] = surface->getRenderTarget();\r
-\r
- mSurface = surface;\r
- mSurface->setBoundTexture(this);\r
-}\r
-\r
-void Texture2D::releaseTexImage()\r
-{\r
- for(int level = 0; level < MIPMAP_LEVELS; level++)\r
- {\r
- if(image[level])\r
- {\r
- image[level]->unbind();\r
- image[level] = 0;\r
- }\r
- }\r
-}\r
-\r
-void Texture2D::setCompressedImage(GLint level, GLenum format, GLsizei width, GLsizei height, GLsizei imageSize, const void *pixels)\r
-{\r
- if(image[level])\r
- {\r
- image[level]->unbind();\r
- }\r
-\r
- image[level] = new Image(this, width, height, format, GL_UNSIGNED_BYTE);\r
-\r
- if(!image[level])\r
- {\r
- return error(GL_OUT_OF_MEMORY);\r
- }\r
-\r
- Texture::setCompressedImage(imageSize, pixels, image[level]);\r
-}\r
-\r
-void Texture2D::subImage(GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, GLint unpackAlignment, const void *pixels)\r
-{\r
- Texture::subImage(xoffset, yoffset, width, height, format, type, unpackAlignment, pixels, image[level]);\r
-}\r
-\r
-void Texture2D::subImageCompressed(GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void *pixels)\r
-{\r
- Texture::subImageCompressed(xoffset, yoffset, width, height, format, imageSize, pixels, image[level]);\r
-}\r
-\r
-void Texture2D::copyImage(GLint level, GLenum format, GLint x, GLint y, GLsizei width, GLsizei height, Framebuffer *source)\r
-{\r
- egl::Image *renderTarget = source->getRenderTarget();\r
-\r
- if(!renderTarget)\r
- {\r
- ERR("Failed to retrieve the render target.");\r
- return error(GL_OUT_OF_MEMORY);\r
- }\r
-\r
- if(image[level])\r
- {\r
- image[level]->unbind();\r
- }\r
-\r
- image[level] = new Image(this, width, height, format, GL_UNSIGNED_BYTE);\r
-\r
- if(!image[level])\r
- {\r
- return error(GL_OUT_OF_MEMORY);\r
- }\r
-\r
- if(width != 0 && height != 0)\r
- {\r
- sw::Rect sourceRect = {x, y, x + width, y + height};\r
- sourceRect.clip(0, 0, source->getColorbuffer()->getWidth(), source->getColorbuffer()->getHeight());\r
-\r
- copy(renderTarget, sourceRect, format, 0, 0, image[level]);\r
- }\r
-\r
- renderTarget->release();\r
-}\r
-\r
-void Texture2D::copySubImage(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height, Framebuffer *source)\r
-{\r
- if(!image[level])\r
- {\r
- return error(GL_INVALID_OPERATION);\r
- }\r
-\r
- if(xoffset + width > image[level]->getWidth() || yoffset + height > image[level]->getHeight())\r
- {\r
- return error(GL_INVALID_VALUE);\r
- }\r
-\r
- egl::Image *renderTarget = source->getRenderTarget();\r
-\r
- if(!renderTarget)\r
- {\r
- ERR("Failed to retrieve the render target.");\r
- return error(GL_OUT_OF_MEMORY);\r
- }\r
-\r
- sw::Rect sourceRect = {x, y, x + width, y + height};\r
- sourceRect.clip(0, 0, source->getColorbuffer()->getWidth(), source->getColorbuffer()->getHeight());\r
-\r
- copy(renderTarget, sourceRect, image[level]->getFormat(), xoffset, yoffset, image[level]);\r
-\r
- renderTarget->release();\r
-}\r
-\r
-void Texture2D::setImage(egl::Image *sharedImage)\r
-{\r
- sharedImage->addRef();\r
-\r
- if(image[0])\r
- {\r
- image[0]->unbind();\r
- }\r
-\r
- image[0] = sharedImage;\r
-}\r
-\r
-// Tests for 2D texture sampling completeness. [OpenGL ES 2.0.24] section 3.8.2 page 85.\r
-bool Texture2D::isSamplerComplete() const\r
-{\r
- if(!image[0])\r
- {\r
- return false;\r
- }\r
-\r
- GLsizei width = image[0]->getWidth();\r
- GLsizei height = image[0]->getHeight();\r
-\r
- if(width <= 0 || height <= 0)\r
- {\r
- return false;\r
- }\r
-\r
- if(isMipmapFiltered())\r
- {\r
- if(!isMipmapComplete())\r
- {\r
- return false;\r
- }\r
- }\r
-\r
- return true;\r
-}\r
-\r
-// Tests for 2D texture (mipmap) completeness. [OpenGL ES 2.0.24] section 3.7.10 page 81.\r
-bool Texture2D::isMipmapComplete() const\r
-{\r
- GLsizei width = image[0]->getWidth();\r
- GLsizei height = image[0]->getHeight();\r
-\r
- int q = log2(std::max(width, height));\r
-\r
- for(int level = 1; level <= q; level++)\r
- {\r
- if(!image[level])\r
- {\r
- return false;\r
- }\r
-\r
- if(image[level]->getFormat() != image[0]->getFormat())\r
- {\r
- return false;\r
- }\r
-\r
- if(image[level]->getType() != image[0]->getType())\r
- {\r
- return false;\r
- }\r
-\r
- if(image[level]->getWidth() != std::max(1, width >> level))\r
- {\r
- return false;\r
- }\r
-\r
- if(image[level]->getHeight() != std::max(1, height >> level))\r
- {\r
- return false;\r
- }\r
- }\r
-\r
- return true;\r
-}\r
-\r
-bool Texture2D::isCompressed(GLenum target, GLint level) const\r
-{\r
- return IsCompressed(getFormat(target, level));\r
-}\r
-\r
-bool Texture2D::isDepth(GLenum target, GLint level) const\r
-{\r
- return IsDepthTexture(getFormat(target, level));\r
-}\r
-\r
-void Texture2D::generateMipmaps()\r
-{\r
- if(!image[0])\r
- {\r
- return; // FIXME: error?\r
- }\r
-\r
- unsigned int q = log2(std::max(image[0]->getWidth(), image[0]->getHeight()));\r
- \r
- for(unsigned int i = 1; i <= q; i++)\r
- {\r
- if(image[i])\r
- {\r
- image[i]->unbind();\r
- }\r
-\r
- image[i] = new Image(this, std::max(image[0]->getWidth() >> i, 1), std::max(image[0]->getHeight() >> i, 1), image[0]->getFormat(), image[0]->getType());\r
-\r
- if(!image[i])\r
- {\r
- return error(GL_OUT_OF_MEMORY);\r
- }\r
-\r
- getDevice()->stretchRect(image[i - 1], 0, image[i], 0, true);\r
- }\r
-}\r
-\r
-egl::Image *Texture2D::getImage(unsigned int level)\r
-{\r
- return image[level];\r
-}\r
-\r
-Renderbuffer *Texture2D::getRenderbuffer(GLenum target)\r
-{\r
- if(target != GL_TEXTURE_2D)\r
- {\r
- return error(GL_INVALID_OPERATION, (Renderbuffer *)NULL);\r
- }\r
-\r
- if(mColorbufferProxy == NULL)\r
- {\r
- mColorbufferProxy = new Renderbuffer(id(), new RenderbufferTexture2D(this));\r
- }\r
-\r
- return mColorbufferProxy;\r
-}\r
-\r
-egl::Image *Texture2D::getRenderTarget(GLenum target, unsigned int level)\r
-{\r
- ASSERT(target == GL_TEXTURE_2D);\r
- ASSERT(level < IMPLEMENTATION_MAX_TEXTURE_LEVELS);\r
-\r
- if(image[level])\r
- {\r
- image[level]->addRef();\r
- }\r
-\r
- return image[level];\r
-}\r
-\r
-bool Texture2D::isShared(GLenum target, unsigned int level) const\r
-{\r
- ASSERT(target == GL_TEXTURE_2D);\r
- ASSERT(level < IMPLEMENTATION_MAX_TEXTURE_LEVELS);\r
-\r
- if(mSurface) // Bound to an EGLSurface\r
- {\r
- return true;\r
- }\r
-\r
- if(!image[level])\r
- {\r
- return false;\r
- }\r
-\r
- return image[level]->isShared();\r
-}\r
-\r
-TextureExternal::TextureExternal(GLuint id) : Texture2D(id)\r
-{\r
- mMinFilter = GL_LINEAR;\r
- mMagFilter = GL_LINEAR;\r
- mWrapS = GL_CLAMP_TO_EDGE;\r
- mWrapT = GL_CLAMP_TO_EDGE;\r
-}\r
-\r
-TextureExternal::~TextureExternal()\r
-{\r
-}\r
-\r
-GLenum TextureExternal::getTarget() const\r
-{\r
- return GL_TEXTURE_EXTERNAL_OES;\r
-}\r
-\r
-}\r
-\r
-// Exported functions for use by EGL\r
-extern "C"\r
-{\r
- egl::Image *createBackBuffer(int width, int height, const egl::Config *config)\r
- {\r
- if(config)\r
- {\r
- return new es1::Image(0, width, height, config->mAlphaSize ? GL_RGBA : GL_RGB, GL_UNSIGNED_BYTE);\r
- }\r
-\r
- return 0;\r
- }\r
-\r
- egl::Image *createDepthStencil(unsigned int width, unsigned int height, sw::Format format, int multiSampleDepth, bool discard)\r
- {\r
- if(width == 0 || height == 0 || height > OUTLINE_RESOLUTION)\r
- {\r
- ERR("Invalid parameters");\r
- return 0;\r
- }\r
- \r
- bool lockable = true;\r
-\r
- switch(format)\r
- {\r
- // case sw::FORMAT_D15S1:\r
- case sw::FORMAT_D24S8:\r
- case sw::FORMAT_D24X8:\r
- // case sw::FORMAT_D24X4S4:\r
- case sw::FORMAT_D24FS8:\r
- case sw::FORMAT_D32:\r
- case sw::FORMAT_D16:\r
- lockable = false;\r
- break;\r
- // case sw::FORMAT_S8_LOCKABLE:\r
- // case sw::FORMAT_D16_LOCKABLE:\r
- case sw::FORMAT_D32F_LOCKABLE:\r
- // case sw::FORMAT_D32_LOCKABLE:\r
- case sw::FORMAT_DF24S8:\r
- case sw::FORMAT_DF16S8:\r
- lockable = true;\r
- break;\r
- default:\r
- UNREACHABLE();\r
- }\r
-\r
- es1::Image *surface = new es1::Image(0, width, height, format, multiSampleDepth, lockable, true);\r
-\r
- if(!surface)\r
- {\r
- ERR("Out of memory");\r
- return 0;\r
- }\r
-\r
- return surface;\r
- }\r
-}\r
+// 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 and TextureCubeMap. 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 "libEGL/Display.h"
+#include "common/Surface.hpp"
+#include "common/debug.h"
+
+#include <algorithm>
+
+namespace es1
+{
+
+Texture::Texture(GLuint name) : egl::Texture(name)
+{
+ mMinFilter = GL_NEAREST_MIPMAP_LINEAR;
+ mMagFilter = GL_LINEAR;
+ mWrapS = GL_REPEAT;
+ mWrapT = GL_REPEAT;
+ mMaxAnisotropy = 1.0f;
+ generateMipmap = GL_FALSE;
+ cropRectU = 0;
+ cropRectV = 0;
+ cropRectW = 0;
+ cropRectH = 0;
+
+ 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)
+ {
+ 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_OES:
+ if(getTarget() == GL_TEXTURE_EXTERNAL_OES)
+ {
+ 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_OES:
+ if(getTarget() == GL_TEXTURE_EXTERNAL_OES)
+ {
+ return false;
+ }
+ // Fall through
+ case GL_CLAMP_TO_EDGE:
+ mWrapT = 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;
+}
+
+void Texture::setGenerateMipmap(GLboolean enable)
+{
+ generateMipmap = enable;
+}
+
+void Texture::setCropRect(GLint u, GLint v, GLint w, GLint h)
+{
+ cropRectU = u;
+ cropRectV = v;
+ cropRectW = w;
+ cropRectH = h;
+}
+
+GLenum Texture::getMinFilter() const
+{
+ return mMinFilter;
+}
+
+GLenum Texture::getMagFilter() const
+{
+ return mMagFilter;
+}
+
+GLenum Texture::getWrapS() const
+{
+ return mWrapS;
+}
+
+GLenum Texture::getWrapT() const
+{
+ return mWrapT;
+}
+
+GLfloat Texture::getMaxAnisotropy() const
+{
+ return mMaxAnisotropy;
+}
+
+GLboolean Texture::getGenerateMipmap() const
+{
+ return generateMipmap;
+}
+
+GLint Texture::getCropRectU() const
+{
+ return cropRectU;
+}
+
+GLint Texture::getCropRectV() const
+{
+ return cropRectV;
+}
+
+GLint Texture::getCropRectW() const
+{
+ return cropRectW;
+}
+
+GLint Texture::getCropRectH() const
+{
+ return cropRectH;
+}
+
+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(egl::Context *context, GLenum format, GLenum type, GLint unpackAlignment, const void *pixels, egl::Image *image)
+{
+ if(pixels && image)
+ {
+ egl::Image::UnpackInfo unpackInfo;
+ unpackInfo.alignment = unpackAlignment;
+ image->loadImageData(context, 0, 0, 0, image->getWidth(), image->getHeight(), 1, format, type, unpackInfo, pixels);
+ }
+}
+
+void Texture::setCompressedImage(GLsizei imageSize, const void *pixels, egl::Image *image)
+{
+ if(pixels && image)
+ {
+ image->loadCompressedData(0, 0, 0, image->getWidth(), image->getHeight(), 1, imageSize, pixels);
+ }
+}
+
+void Texture::subImage(egl::Context *context, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, GLint unpackAlignment, const void *pixels, egl::Image *image)
+{
+ if(!image)
+ {
+ return error(GL_INVALID_OPERATION);
+ }
+
+ if(width + xoffset > image->getWidth() || height + yoffset > image->getHeight())
+ {
+ return error(GL_INVALID_VALUE);
+ }
+
+ if(IsCompressed(image->getFormat()))
+ {
+ return error(GL_INVALID_OPERATION);
+ }
+
+ if(format != image->getFormat())
+ {
+ return error(GL_INVALID_OPERATION);
+ }
+
+ if(pixels)
+ {
+ egl::Image::UnpackInfo unpackInfo;
+ unpackInfo.alignment = unpackAlignment;
+ image->loadImageData(context, xoffset, yoffset, 0, width, height, 1, format, type, unpackInfo, pixels);
+ }
+}
+
+void Texture::subImageCompressed(GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void *pixels, egl::Image *image)
+{
+ if(!image)
+ {
+ return error(GL_INVALID_OPERATION);
+ }
+
+ if(width + xoffset > image->getWidth() || height + yoffset > image->getHeight())
+ {
+ return error(GL_INVALID_VALUE);
+ }
+
+ if(format != image->getFormat())
+ {
+ return error(GL_INVALID_OPERATION);
+ }
+
+ if(pixels)
+ {
+ image->loadCompressedData(xoffset, yoffset, 0, width, height, 1, imageSize, pixels);
+ }
+}
+
+bool Texture::copy(egl::Image *source, const sw::Rect &sourceRect, GLenum destFormat, GLint xoffset, GLint yoffset, egl::Image *dest)
+{
+ Device *device = getDevice();
+
+ sw::SliceRect destRect(xoffset, yoffset, xoffset + (sourceRect.x1 - sourceRect.x0), yoffset + (sourceRect.y1 - sourceRect.y0), 0);
+ sw::SliceRect sourceSliceRect(sourceRect);
+ bool success = device->stretchRect(source, &sourceSliceRect, dest, &destRect, false);
+
+ if(!success)
+ {
+ return error(GL_OUT_OF_MEMORY, false);
+ }
+
+ return true;
+}
+
+bool Texture::isMipmapFiltered() const
+{
+ switch(mMinFilter)
+ {
+ 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(mMinFilter);
+ }
+
+ return false;
+}
+
+Texture2D::Texture2D(GLuint name) : Texture(name)
+{
+ for(int i = 0; i < IMPLEMENTATION_MAX_TEXTURE_LEVELS; i++)
+ {
+ image[i] = nullptr;
+ }
+
+ mSurface = nullptr;
+
+ mColorbufferProxy = nullptr;
+ mProxyRefs = 0;
+}
+
+Texture2D::~Texture2D()
+{
+ for(int i = 0; i < IMPLEMENTATION_MAX_TEXTURE_LEVELS; i++)
+ {
+ if(image[i])
+ {
+ image[i]->unbind(this);
+ image[i] = nullptr;
+ }
+ }
+
+ 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 == GL_TEXTURE_2D);
+ return image[level] ? image[level]->getWidth() : 0;
+}
+
+GLsizei Texture2D::getHeight(GLenum target, GLint level) const
+{
+ ASSERT(target == GL_TEXTURE_2D);
+ return image[level] ? image[level]->getHeight() : 0;
+}
+
+GLenum Texture2D::getFormat(GLenum target, GLint level) const
+{
+ ASSERT(target == GL_TEXTURE_2D);
+ return image[level] ? image[level]->getFormat() : GL_NONE;
+}
+
+GLenum Texture2D::getType(GLenum target, GLint level) const
+{
+ ASSERT(target == GL_TEXTURE_2D);
+ return image[level] ? image[level]->getType() : GL_NONE;
+}
+
+sw::Format Texture2D::getInternalFormat(GLenum target, GLint level) const
+{
+ ASSERT(target == GL_TEXTURE_2D);
+ return image[level] ? image[level]->getInternalFormat() : sw::FORMAT_NULL;
+}
+
+int Texture2D::getLevelCount() const
+{
+ ASSERT(isSamplerComplete());
+ int levels = 0;
+
+ while(levels < IMPLEMENTATION_MAX_TEXTURE_LEVELS && image[levels])
+ {
+ levels++;
+ }
+
+ return levels;
+}
+
+void Texture2D::setImage(egl::Context *context, GLint level, GLsizei width, GLsizei height, GLenum format, GLenum type, GLint unpackAlignment, const void *pixels)
+{
+ if(image[level])
+ {
+ image[level]->release();
+ }
+
+ image[level] = egl::Image::create(this, width, height, format, type);
+
+ if(!image[level])
+ {
+ return error(GL_OUT_OF_MEMORY);
+ }
+
+ Texture::setImage(context, format, type, unpackAlignment, pixels, image[level]);
+}
+
+void Texture2D::bindTexImage(gl::Surface *surface)
+{
+ GLenum format;
+
+ switch(surface->getInternalFormat())
+ {
+ case sw::FORMAT_A8R8G8B8:
+ format = GL_BGRA_EXT;
+ break;
+ case sw::FORMAT_A8B8G8R8:
+ format = GL_RGBA;
+ break;
+ case sw::FORMAT_X8B8G8R8:
+ case sw::FORMAT_X8R8G8B8:
+ format = GL_RGB;
+ break;
+ default:
+ UNIMPLEMENTED();
+ return;
+ }
+
+ for(int level = 0; level < IMPLEMENTATION_MAX_TEXTURE_LEVELS; level++)
+ {
+ if(image[level])
+ {
+ image[level]->release();
+ image[level] = nullptr;
+ }
+ }
+
+ image[0] = surface->getRenderTarget();
+
+ mSurface = surface;
+ mSurface->setBoundTexture(this);
+}
+
+void Texture2D::releaseTexImage()
+{
+ for(int level = 0; level < IMPLEMENTATION_MAX_TEXTURE_LEVELS; level++)
+ {
+ if(image[level])
+ {
+ image[level]->release();
+ image[level] = 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, GL_UNSIGNED_BYTE);
+
+ if(!image[level])
+ {
+ return error(GL_OUT_OF_MEMORY);
+ }
+
+ Texture::setCompressedImage(imageSize, pixels, image[level]);
+}
+
+void Texture2D::subImage(egl::Context *context, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, GLint unpackAlignment, const void *pixels)
+{
+ Texture::subImage(context, xoffset, yoffset, width, height, format, type, unpackAlignment, 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, width, height, format, imageSize, pixels, image[level]);
+}
+
+void Texture2D::copyImage(GLint level, GLenum format, GLint x, GLint y, GLsizei width, GLsizei height, Framebuffer *source)
+{
+ egl::Image *renderTarget = source->getRenderTarget();
+
+ if(!renderTarget)
+ {
+ ERR("Failed to retrieve the render target.");
+ return error(GL_OUT_OF_MEMORY);
+ }
+
+ if(image[level])
+ {
+ image[level]->release();
+ }
+
+ image[level] = egl::Image::create(this, width, height, format, GL_UNSIGNED_BYTE);
+
+ if(!image[level])
+ {
+ return error(GL_OUT_OF_MEMORY);
+ }
+
+ if(width != 0 && height != 0)
+ {
+ sw::Rect sourceRect = {x, y, x + width, y + height};
+ sourceRect.clip(0, 0, source->getColorbuffer()->getWidth(), source->getColorbuffer()->getHeight());
+
+ copy(renderTarget, sourceRect, format, 0, 0, image[level]);
+ }
+
+ renderTarget->release();
+}
+
+void Texture2D::copySubImage(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height, Framebuffer *source)
+{
+ if(!image[level])
+ {
+ return error(GL_INVALID_OPERATION);
+ }
+
+ if(xoffset + width > image[level]->getWidth() || yoffset + height > image[level]->getHeight())
+ {
+ return error(GL_INVALID_VALUE);
+ }
+
+ egl::Image *renderTarget = source->getRenderTarget();
+
+ if(!renderTarget)
+ {
+ ERR("Failed to retrieve the render target.");
+ return error(GL_OUT_OF_MEMORY);
+ }
+
+ sw::Rect sourceRect = {x, y, x + width, y + height};
+ sourceRect.clip(0, 0, source->getColorbuffer()->getWidth(), source->getColorbuffer()->getHeight());
+
+ copy(renderTarget, sourceRect, image[level]->getFormat(), xoffset, yoffset, image[level]);
+
+ renderTarget->release();
+}
+
+void Texture2D::setSharedImage(egl::Image *sharedImage)
+{
+ sharedImage->addRef();
+
+ if(image[0])
+ {
+ image[0]->release();
+ }
+
+ image[0] = sharedImage;
+}
+
+// Tests for 2D texture sampling completeness. [OpenGL ES 2.0.24] section 3.8.2 page 85.
+bool Texture2D::isSamplerComplete() const
+{
+ if(!image[0])
+ {
+ return false;
+ }
+
+ GLsizei width = image[0]->getWidth();
+ GLsizei height = image[0]->getHeight();
+
+ if(width <= 0 || height <= 0)
+ {
+ return false;
+ }
+
+ if(isMipmapFiltered())
+ {
+ if(!generateMipmap && !isMipmapComplete())
+ {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+// Tests for 2D texture (mipmap) completeness. [OpenGL ES 2.0.24] section 3.7.10 page 81.
+bool Texture2D::isMipmapComplete() const
+{
+ GLsizei width = image[0]->getWidth();
+ GLsizei height = image[0]->getHeight();
+
+ int q = log2(std::max(width, height));
+
+ for(int level = 1; level <= q; level++)
+ {
+ if(!image[level])
+ {
+ return false;
+ }
+
+ if(image[level]->getFormat() != image[0]->getFormat())
+ {
+ return false;
+ }
+
+ if(image[level]->getType() != image[0]->getType())
+ {
+ return false;
+ }
+
+ if(image[level]->getWidth() != std::max(1, width >> level))
+ {
+ return false;
+ }
+
+ if(image[level]->getHeight() != std::max(1, height >> level))
+ {
+ 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[0])
+ {
+ return; // FIXME: error?
+ }
+
+ unsigned int q = log2(std::max(image[0]->getWidth(), image[0]->getHeight()));
+
+ for(unsigned int i = 1; i <= q; i++)
+ {
+ if(image[i])
+ {
+ image[i]->release();
+ }
+
+ image[i] = egl::Image::create(this, std::max(image[0]->getWidth() >> i, 1), std::max(image[0]->getHeight() >> i, 1), image[0]->getFormat(), image[0]->getType());
+
+ if(!image[i])
+ {
+ return error(GL_OUT_OF_MEMORY);
+ }
+
+ getDevice()->stretchRect(image[i - 1], 0, image[i], 0, true);
+ }
+}
+
+void Texture2D::autoGenerateMipmaps()
+{
+ if(generateMipmap && image[0]->hasDirtyMipmaps())
+ {
+ generateMipmaps();
+ image[0]->cleanMipmaps();
+ }
+}
+
+egl::Image *Texture2D::getImage(unsigned int level)
+{
+ return image[level];
+}
+
+Renderbuffer *Texture2D::getRenderbuffer(GLenum target)
+{
+ if(target != GL_TEXTURE_2D)
+ {
+ return error(GL_INVALID_OPERATION, (Renderbuffer*)nullptr);
+ }
+
+ if(!mColorbufferProxy)
+ {
+ mColorbufferProxy = new Renderbuffer(name, new RenderbufferTexture2D(this));
+ }
+
+ return mColorbufferProxy;
+}
+
+egl::Image *Texture2D::getRenderTarget(GLenum target, unsigned int level)
+{
+ ASSERT(target == GL_TEXTURE_2D);
+ 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 == GL_TEXTURE_2D);
+ ASSERT(level < IMPLEMENTATION_MAX_TEXTURE_LEVELS);
+
+ if(mSurface) // Bound to an EGLSurface
+ {
+ return true;
+ }
+
+ if(!image[level])
+ {
+ return false;
+ }
+
+ return image[level]->isShared();
+}
+
+TextureExternal::TextureExternal(GLuint name) : Texture2D(name)
+{
+ mMinFilter = GL_LINEAR;
+ mMagFilter = GL_LINEAR;
+ mWrapS = GL_CLAMP_TO_EDGE;
+ mWrapT = GL_CLAMP_TO_EDGE;
+}
+
+TextureExternal::~TextureExternal()
+{
+}
+
+GLenum TextureExternal::getTarget() const
+{
+ return GL_TEXTURE_EXTERNAL_OES;
+}
+
+}
+
+egl::Image *createBackBuffer(int width, int height, const egl::Config *config)
+{
+ if(config)
+ {
+ return egl::Image::create(width, height, config->mRenderTargetFormat, config->mSamples, false);
+ }
+
+ return nullptr;
+}
+
+egl::Image *createDepthStencil(unsigned int width, unsigned int height, sw::Format format, int multiSampleDepth, bool discard)
+{
+ if(height > sw::OUTLINE_RESOLUTION)
+ {
+ ERR("Invalid parameters: %dx%d", width, height);
+ return 0;
+ }
+
+ 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);
+ }
+
+ egl::Image *surface = egl::Image::create(width, height, format, multiSampleDepth, lockable);
+
+ if(!surface)
+ {
+ ERR("Out of memory");
+ return nullptr;
+ }
+
+ return surface;
+}