blob: 40f5ed4acdc1f8f2e7c53b270c1f810fe1e87e13 [file] [log] [blame]
/*
* Copyright (C) 2009 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "modules/webgl/WebGLTexture.h"
#include "modules/webgl/WebGLRenderingContextBase.h"
namespace blink {
WebGLTexture* WebGLTexture::create(WebGLRenderingContextBase* ctx)
{
return new WebGLTexture(ctx);
}
WebGLTexture::WebGLTexture(WebGLRenderingContextBase* ctx)
: WebGLSharedPlatform3DObject(ctx)
, m_target(0)
, m_isNPOT(false)
, m_isCubeComplete(false)
, m_isComplete(false)
, m_needToUseBlackTexture(false)
, m_isFloatType(false)
, m_isHalfFloatType(false)
, m_isWebGL2OrHigher(ctx->isWebGL2OrHigher())
, m_immutable(false)
, m_baseLevel(0)
, m_maxLevel(1000)
{
setObject(ctx->webContext()->createTexture());
}
WebGLTexture::~WebGLTexture()
{
// See the comment in WebGLObject::detachAndDeleteObject().
detachAndDeleteObject();
}
void WebGLTexture::setTarget(GLenum target, GLint maxLevel)
{
if (!object())
return;
// Target is finalized the first time bindTexture() is called.
if (m_target)
return;
switch (target) {
case GL_TEXTURE_2D:
case GL_TEXTURE_2D_ARRAY:
case GL_TEXTURE_3D:
m_target = target;
m_info.resize(1);
m_info[0].resize(maxLevel);
break;
case GL_TEXTURE_CUBE_MAP:
m_target = target;
m_info.resize(6);
for (int ii = 0; ii < 6; ++ii)
m_info[ii].resize(maxLevel);
break;
}
}
void WebGLTexture::setParameteri(GLenum pname, GLint param)
{
if (!object() || !m_target)
return;
switch (pname) {
case GL_TEXTURE_MIN_FILTER:
switch (param) {
case GL_NEAREST:
case GL_LINEAR:
case GL_NEAREST_MIPMAP_NEAREST:
case GL_LINEAR_MIPMAP_NEAREST:
case GL_NEAREST_MIPMAP_LINEAR:
case GL_LINEAR_MIPMAP_LINEAR:
m_samplerState.minFilter = param;
break;
}
break;
case GL_TEXTURE_MAG_FILTER:
switch (param) {
case GL_NEAREST:
case GL_LINEAR:
m_samplerState.magFilter = param;
break;
}
break;
case GL_TEXTURE_WRAP_R:
switch (param) {
case GL_CLAMP_TO_EDGE:
case GL_MIRRORED_REPEAT:
case GL_REPEAT:
m_samplerState.wrapR = param;
break;
}
break;
case GL_TEXTURE_WRAP_S:
switch (param) {
case GL_CLAMP_TO_EDGE:
case GL_MIRRORED_REPEAT:
case GL_REPEAT:
m_samplerState.wrapS = param;
break;
}
break;
case GL_TEXTURE_WRAP_T:
switch (param) {
case GL_CLAMP_TO_EDGE:
case GL_MIRRORED_REPEAT:
case GL_REPEAT:
m_samplerState.wrapT = param;
break;
}
break;
case GL_TEXTURE_BASE_LEVEL:
if (m_isWebGL2OrHigher && param >= 0)
m_baseLevel = param;
break;
case GL_TEXTURE_MAX_LEVEL:
if (m_isWebGL2OrHigher && param >= 0)
m_maxLevel = param;
break;
default:
return;
}
update();
}
void WebGLTexture::setParameterf(GLenum pname, GLfloat param)
{
if (!object() || !m_target)
return;
GLint iparam = static_cast<GLint>(param);
setParameteri(pname, iparam);
}
void WebGLTexture::setLevelInfo(GLenum target, GLint level, GLenum internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLenum type)
{
ASSERT(!m_immutable);
if (!object() || !m_target)
return;
// We assume level, internalFormat, width, height, depth, and type have all been
// validated already.
int index = mapTargetToIndex(target);
if (index < 0)
return;
m_info[index][level].setInfo(internalFormat, width, height, depth, type);
update();
}
void WebGLTexture::setTexStorageInfo(GLenum target, GLint levels, GLenum internalFormat, GLsizei width, GLsizei height, GLsizei depth)
{
ASSERT(!m_immutable);
// We assume level, internalFormat, width, height, and depth have all been
// validated already.
if (!object() || !m_target || target != m_target)
return;
GLenum type = getValidTypeForInternalFormat(internalFormat);
if (type == GL_NONE)
return;
for (size_t ii = 0; ii < m_info.size(); ++ii) {
GLsizei levelWidth = width;
GLsizei levelHeight = height;
GLsizei levelDepth = depth;
for (GLint level = 0; level < levels; ++level) {
LevelInfo& info = m_info[ii][level];
info.setInfo(internalFormat, levelWidth, levelHeight, levelDepth, type);
levelWidth = std::max(1, levelWidth >> 1);
levelHeight = std::max(1, levelHeight >> 1);
levelDepth = std::max(1, levelDepth >> 1);
}
}
update();
m_immutable = true;
}
void WebGLTexture::generateMipmapLevelInfo()
{
if (!object() || !m_target)
return;
if (!canGenerateMipmaps())
return;
if (!m_isComplete) {
for (size_t ii = 0; ii < m_info.size(); ++ii) {
const LevelInfo& info0 = m_info[ii][m_baseLevel];
GLsizei width = info0.width;
GLsizei height = info0.height;
GLsizei depth = info0.depth;
GLint levelCount = computeLevelCount(width, height, depth);
size_t maxLevel = 0;
if (m_baseLevel + levelCount > 0)
maxLevel = m_baseLevel + levelCount - 1;
maxLevel = m_isWebGL2OrHigher ? std::min(m_maxLevel, maxLevel) : maxLevel;
ASSERT(maxLevel < m_info[ii].size());
for (size_t level = m_baseLevel + 1; level <= maxLevel; ++level) {
width = std::max(1, width >> 1);
height = std::max(1, height >> 1);
depth = std::max(1, depth >> 1);
LevelInfo& info = m_info[ii][level];
info.setInfo(info0.internalFormat, width, height, depth, info0.type);
}
}
m_isComplete = true;
}
m_needToUseBlackTexture = false;
}
GLenum WebGLTexture::getInternalFormat(GLenum target, GLint level) const
{
const LevelInfo* info = getLevelInfo(target, level);
if (!info)
return 0;
return info->internalFormat;
}
GLenum WebGLTexture::getType(GLenum target, GLint level) const
{
const LevelInfo* info = getLevelInfo(target, level);
if (!info)
return 0;
return info->type;
}
GLsizei WebGLTexture::getWidth(GLenum target, GLint level) const
{
const LevelInfo* info = getLevelInfo(target, level);
if (!info)
return 0;
return info->width;
}
GLsizei WebGLTexture::getHeight(GLenum target, GLint level) const
{
const LevelInfo* info = getLevelInfo(target, level);
if (!info)
return 0;
return info->height;
}
GLsizei WebGLTexture::getDepth(GLenum target, GLint level) const
{
const LevelInfo* info = getLevelInfo(target, level);
if (!info)
return 0;
return info->depth;
}
bool WebGLTexture::isValid(GLenum target, GLint level) const
{
const LevelInfo* info = getLevelInfo(target, level);
if (!info)
return 0;
return info->valid;
}
bool WebGLTexture::isNPOT(GLsizei width, GLsizei height)
{
ASSERT(width >= 0 && height >= 0);
if (!width || !height)
return false;
if ((width & (width - 1)) || (height & (height - 1)))
return true;
return false;
}
bool WebGLTexture::isNPOT() const
{
if (!object())
return false;
return m_isNPOT;
}
bool WebGLTexture::needToUseBlackTexture(TextureExtensionFlag flag, const WebGLSamplerState* samplerState) const
{
ASSERT(samplerState);
if (!object())
return false;
if (m_needToUseBlackTexture)
return true;
if ((m_isFloatType && !(flag & TextureFloatLinearExtensionEnabled)) || (m_isHalfFloatType && !(flag && TextureHalfFloatLinearExtensionEnabled))) {
if (samplerState->magFilter != GL_NEAREST || (samplerState->minFilter != GL_NEAREST && samplerState->minFilter != GL_NEAREST_MIPMAP_NEAREST))
return true;
}
return false;
}
void WebGLTexture::deleteObjectImpl(WebGraphicsContext3D* context3d)
{
context3d->deleteTexture(m_object);
m_object = 0;
}
int WebGLTexture::mapTargetToIndex(GLenum target) const
{
if (m_target == GL_TEXTURE_2D) {
if (target == GL_TEXTURE_2D)
return 0;
} else if (m_target == GL_TEXTURE_CUBE_MAP) {
switch (target) {
case GL_TEXTURE_CUBE_MAP_POSITIVE_X:
return 0;
case GL_TEXTURE_CUBE_MAP_NEGATIVE_X:
return 1;
case GL_TEXTURE_CUBE_MAP_POSITIVE_Y:
return 2;
case GL_TEXTURE_CUBE_MAP_NEGATIVE_Y:
return 3;
case GL_TEXTURE_CUBE_MAP_POSITIVE_Z:
return 4;
case GL_TEXTURE_CUBE_MAP_NEGATIVE_Z:
return 5;
}
} else if (m_target == GL_TEXTURE_3D) {
if (target == GL_TEXTURE_3D)
return 0;
} else if (m_target == GL_TEXTURE_2D_ARRAY) {
if (target == GL_TEXTURE_2D_ARRAY)
return 0;
}
return -1;
}
bool WebGLTexture::canGenerateMipmaps()
{
if (!m_isWebGL2OrHigher && isNPOT())
return false;
if (m_baseLevel >= m_info[0].size())
return false;
if (m_info.size() > 1 && !m_isCubeComplete)
return false;
return true;
}
GLint WebGLTexture::computeLevelCount(GLsizei width, GLsizei height, GLsizei depth)
{
// return 1 + log2Floor(std::max(width, height));
GLsizei n = std::max(std::max(width, height), depth);
if (n <= 0)
return 0;
GLint log = 0;
GLsizei value = n;
for (int ii = 4; ii >= 0; --ii) {
int shift = (1 << ii);
GLsizei x = (value >> shift);
if (x) {
value = x;
log += shift;
}
}
ASSERT(value == 1);
return log + 1;
}
void WebGLTexture::update()
{
m_isNPOT = false;
for (size_t ii = 0; ii < m_info.size(); ++ii) {
if (isNPOT(m_info[ii][0].width, m_info[ii][0].height)) {
m_isNPOT = true;
break;
}
}
m_isComplete = true;
m_isCubeComplete = true;
if (m_baseLevel > m_maxLevel || m_baseLevel >= m_info[0].size()) {
m_isComplete = false;
}
else {
const LevelInfo& base = m_info[0][m_baseLevel];
size_t levelCount = computeLevelCount(base.width, base.height, base.depth);
size_t maxLevel = 0;
if (m_baseLevel + levelCount > 0)
maxLevel = m_baseLevel + levelCount - 1;
maxLevel = m_isWebGL2OrHigher ? std::min(m_maxLevel, maxLevel) : maxLevel;
for (size_t ii = 0; ii < m_info.size(); ++ii) {
const LevelInfo& info0 = m_info[ii][m_baseLevel];
if (!info0.valid
|| info0.width != base.width || info0.height != base.height || info0.depth != base.depth
|| info0.internalFormat != base.internalFormat || info0.type != base.type
|| (m_info.size() > 1 && info0.width != info0.height)) {
if (m_info.size() > 1)
m_isCubeComplete = false;
m_isComplete = false;
break;
}
if (!m_isComplete)
continue;
GLsizei width = info0.width;
GLsizei height = info0.height;
GLsizei depth = info0.depth;
ASSERT(maxLevel < m_info[ii].size());
for (size_t level = m_baseLevel + 1; level <= maxLevel; ++level) {
width = std::max(1, width >> 1);
height = std::max(1, height >> 1);
depth = std::max(1, depth >> 1);
const LevelInfo& info = m_info[ii][level];
if (!info.valid
|| info.width != width || info.height != height || info.depth != depth
|| info.internalFormat != info0.internalFormat || info.type != info0.type) {
m_isComplete = false;
break;
}
}
}
}
m_isFloatType = m_info[0][0].type == GL_FLOAT;
m_isHalfFloatType = m_info[0][0].type == GL_HALF_FLOAT_OES;
m_needToUseBlackTexture = false;
// If it is a Cube texture, check Cube Completeness first
if (m_info.size() > 1 && !m_isCubeComplete)
m_needToUseBlackTexture = true;
if (!m_isWebGL2OrHigher) {
// We can do these checks up front in WebGL 1 because there's no separate samplers.
// NPOT
if (m_isNPOT && ((m_samplerState.minFilter != GL_NEAREST && m_samplerState.minFilter != GL_LINEAR)
|| m_samplerState.wrapS != GL_CLAMP_TO_EDGE || m_samplerState.wrapT != GL_CLAMP_TO_EDGE))
m_needToUseBlackTexture = true;
// Completeness
if (!m_isComplete && m_samplerState.minFilter != GL_NEAREST && m_samplerState.minFilter != GL_LINEAR)
m_needToUseBlackTexture = true;
}
}
const WebGLTexture::LevelInfo* WebGLTexture::getLevelInfo(GLenum target, GLint level) const
{
if (!object() || !m_target)
return nullptr;
int targetIndex = mapTargetToIndex(target);
if (targetIndex < 0 || targetIndex >= static_cast<int>(m_info.size()))
return nullptr;
if (level < 0 || level >= static_cast<GLint>(m_info[targetIndex].size()))
return nullptr;
return &(m_info[targetIndex][level]);
}
// TODO(bajones): Logic surrounding relationship of internalFormat, format, and type needs to be revisisted for WebGL 2.0
GLenum WebGLTexture::getValidTypeForInternalFormat(GLenum internalFormat)
{
switch (internalFormat) {
case GL_R8:
return GL_UNSIGNED_BYTE;
case GL_R8_SNORM:
return GL_BYTE;
case GL_R16F:
return GL_HALF_FLOAT;
case GL_R32F:
return GL_FLOAT;
case GL_R8UI:
return GL_UNSIGNED_BYTE;
case GL_R8I:
return GL_BYTE;
case GL_R16UI:
return GL_UNSIGNED_SHORT;
case GL_R16I:
return GL_SHORT;
case GL_R32UI:
return GL_UNSIGNED_INT;
case GL_R32I:
return GL_INT;
case GL_RG8:
return GL_UNSIGNED_BYTE;
case GL_RG8_SNORM:
return GL_BYTE;
case GL_RG16F:
return GL_HALF_FLOAT;
case GL_RG32F:
return GL_FLOAT;
case GL_RG8UI:
return GL_UNSIGNED_BYTE;
case GL_RG8I:
return GL_BYTE;
case GL_RG16UI:
return GL_UNSIGNED_SHORT;
case GL_RG16I:
return GL_SHORT;
case GL_RG32UI:
return GL_UNSIGNED_INT;
case GL_RG32I:
return GL_INT;
case GL_RGB8:
return GL_UNSIGNED_BYTE;
case GL_SRGB8:
return GL_UNSIGNED_BYTE;
case GL_RGB565:
return GL_UNSIGNED_SHORT_5_6_5;
case GL_RGB8_SNORM:
return GL_BYTE;
case GL_R11F_G11F_B10F:
return GL_UNSIGNED_INT_10F_11F_11F_REV;
case GL_RGB9_E5:
return GL_UNSIGNED_INT_5_9_9_9_REV;
case GL_RGB16F:
return GL_HALF_FLOAT;
case GL_RGB32F:
return GL_FLOAT;
case GL_RGB8UI:
return GL_UNSIGNED_BYTE;
case GL_RGB8I:
return GL_BYTE;
case GL_RGB16UI:
return GL_UNSIGNED_SHORT;
case GL_RGB16I:
return GL_SHORT;
case GL_RGB32UI:
return GL_UNSIGNED_INT;
case GL_RGB32I:
return GL_INT;
case GL_RGBA8:
return GL_UNSIGNED_BYTE;
case GL_SRGB8_ALPHA8:
return GL_UNSIGNED_BYTE;
case GL_RGBA8_SNORM:
return GL_BYTE;
case GL_RGB5_A1:
return GL_UNSIGNED_SHORT_5_5_5_1;
case GL_RGBA4:
return GL_UNSIGNED_SHORT_4_4_4_4;
case GL_RGB10_A2:
return GL_UNSIGNED_INT_2_10_10_10_REV;
case GL_RGBA16F:
return GL_HALF_FLOAT;
case GL_RGBA32F:
return GL_FLOAT;
case GL_RGBA8UI:
return GL_UNSIGNED_BYTE;
case GL_RGBA8I:
return GL_BYTE;
case GL_RGB10_A2UI:
return GL_UNSIGNED_INT_2_10_10_10_REV;
case GL_RGBA16UI:
return GL_UNSIGNED_SHORT;
case GL_RGBA16I:
return GL_SHORT;
case GL_RGBA32I:
return GL_INT;
case GL_RGBA32UI:
return GL_UNSIGNED_INT;
case GL_DEPTH_COMPONENT16:
return GL_UNSIGNED_SHORT;
case GL_DEPTH_COMPONENT24:
return GL_UNSIGNED_INT;
case GL_DEPTH_COMPONENT32F:
return GL_FLOAT;
case GL_DEPTH24_STENCIL8:
return GL_UNSIGNED_INT_24_8;
case GL_DEPTH32F_STENCIL8:
return GL_FLOAT_32_UNSIGNED_INT_24_8_REV;
// Compressed types.
case GL_ATC_RGB_AMD:
case GL_ATC_RGBA_EXPLICIT_ALPHA_AMD:
case GL_ATC_RGBA_INTERPOLATED_ALPHA_AMD:
case GL_ETC1_RGB8_OES:
case GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG:
case GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG:
case GL_COMPRESSED_RGB_S3TC_DXT1_EXT:
case GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG:
case GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG:
case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT:
case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT:
case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
return GL_UNSIGNED_BYTE;
default:
return GL_NONE;
}
}
} // namespace blink