// // Copyright 2015 The ANGLE Project Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // // TextureGL.cpp: Implements the class methods for TextureGL. #include "libANGLE/renderer/gl/TextureGL.h" #include "common/bitset_utils.h" #include "common/debug.h" #include "common/utilities.h" #include "libANGLE/Context.h" #include "libANGLE/MemoryObject.h" #include "libANGLE/State.h" #include "libANGLE/Surface.h" #include "libANGLE/angletypes.h" #include "libANGLE/formatutils.h" #include "libANGLE/queryconversions.h" #include "libANGLE/renderer/gl/BlitGL.h" #include "libANGLE/renderer/gl/BufferGL.h" #include "libANGLE/renderer/gl/ContextGL.h" #include "libANGLE/renderer/gl/FramebufferGL.h" #include "libANGLE/renderer/gl/FunctionsGL.h" #include "libANGLE/renderer/gl/ImageGL.h" #include "libANGLE/renderer/gl/MemoryObjectGL.h" #include "libANGLE/renderer/gl/StateManagerGL.h" #include "libANGLE/renderer/gl/SurfaceGL.h" #include "libANGLE/renderer/gl/formatutilsgl.h" #include "libANGLE/renderer/gl/renderergl_utils.h" #include "platform/FeaturesGL.h" using angle::CheckedNumeric; namespace rx { namespace { size_t GetLevelInfoIndex(gl::TextureTarget target, size_t level) { return gl::IsCubeMapFaceTarget(target) ? ((level * gl::kCubeFaceCount) + gl::CubeMapTextureTargetToFaceIndex(target)) : level; } bool IsLUMAFormat(GLenum format) { return format == GL_LUMINANCE || format == GL_ALPHA || format == GL_LUMINANCE_ALPHA; } LUMAWorkaroundGL GetLUMAWorkaroundInfo(GLenum originalFormat, GLenum destinationFormat) { if (IsLUMAFormat(originalFormat)) { return LUMAWorkaroundGL(!IsLUMAFormat(destinationFormat), destinationFormat); } else { return LUMAWorkaroundGL(false, GL_NONE); } } bool GetDepthStencilWorkaround(GLenum format) { return format == GL_DEPTH_COMPONENT || format == GL_DEPTH_STENCIL; } bool GetEmulatedAlphaChannel(const angle::FeaturesGL &features, GLenum internalFormat) { return features.rgbDXT1TexturesSampleZeroAlpha.enabled && internalFormat == GL_COMPRESSED_RGB_S3TC_DXT1_EXT; } LevelInfoGL GetLevelInfo(const angle::FeaturesGL &features, GLenum originalInternalFormat, GLenum destinationInternalFormat) { GLenum originalFormat = gl::GetUnsizedFormat(originalInternalFormat); GLenum destinationFormat = gl::GetUnsizedFormat(destinationInternalFormat); return LevelInfoGL(originalFormat, destinationInternalFormat, GetDepthStencilWorkaround(originalFormat), GetLUMAWorkaroundInfo(originalFormat, destinationFormat), GetEmulatedAlphaChannel(features, originalFormat)); } gl::Texture::DirtyBits GetLevelWorkaroundDirtyBits() { gl::Texture::DirtyBits bits; bits.set(gl::Texture::DIRTY_BIT_SWIZZLE_RED); bits.set(gl::Texture::DIRTY_BIT_SWIZZLE_GREEN); bits.set(gl::Texture::DIRTY_BIT_SWIZZLE_BLUE); bits.set(gl::Texture::DIRTY_BIT_SWIZZLE_ALPHA); return bits; } size_t GetMaxLevelInfoCountForTextureType(gl::TextureType type) { switch (type) { case gl::TextureType::CubeMap: return (gl::IMPLEMENTATION_MAX_TEXTURE_LEVELS + 1) * gl::kCubeFaceCount; case gl::TextureType::External: return 1; default: return gl::IMPLEMENTATION_MAX_TEXTURE_LEVELS + 1; } } } // anonymous namespace LUMAWorkaroundGL::LUMAWorkaroundGL() : LUMAWorkaroundGL(false, GL_NONE) {} LUMAWorkaroundGL::LUMAWorkaroundGL(bool enabled_, GLenum workaroundFormat_) : enabled(enabled_), workaroundFormat(workaroundFormat_) {} LevelInfoGL::LevelInfoGL() : LevelInfoGL(GL_NONE, GL_NONE, false, LUMAWorkaroundGL(), false) {} LevelInfoGL::LevelInfoGL(GLenum sourceFormat_, GLenum nativeInternalFormat_, bool depthStencilWorkaround_, const LUMAWorkaroundGL &lumaWorkaround_, bool emulatedAlphaChannel_) : sourceFormat(sourceFormat_), nativeInternalFormat(nativeInternalFormat_), depthStencilWorkaround(depthStencilWorkaround_), lumaWorkaround(lumaWorkaround_), emulatedAlphaChannel(emulatedAlphaChannel_) {} TextureGL::TextureGL(const gl::TextureState &state, GLuint id) : TextureImpl(state), mAppliedSwizzle(state.getSwizzleState()), mAppliedSampler(state.getSamplerState()), mAppliedBaseLevel(state.getEffectiveBaseLevel()), mAppliedMaxLevel(state.getEffectiveMaxLevel()), mTextureID(id) { mLevelInfo.resize(GetMaxLevelInfoCountForTextureType(getType())); } TextureGL::~TextureGL() { ASSERT(mTextureID == 0); } void TextureGL::onDestroy(const gl::Context *context) { GetImplAs(context)->flushIfNecessaryBeforeDeleteTextures(); StateManagerGL *stateManager = GetStateManagerGL(context); stateManager->deleteTexture(mTextureID); mTextureID = 0; } angle::Result TextureGL::setImage(const gl::Context *context, const gl::ImageIndex &index, GLenum internalFormat, const gl::Extents &size, GLenum format, GLenum type, const gl::PixelUnpackState &unpack, gl::Buffer *unpackBuffer, const uint8_t *pixels) { const angle::FeaturesGL &features = GetFeaturesGL(context); gl::TextureTarget target = index.getTarget(); size_t level = static_cast(index.getLevelIndex()); if (features.unpackOverlappingRowsSeparatelyUnpackBuffer.enabled && unpackBuffer && unpack.rowLength != 0 && unpack.rowLength < size.width) { // The rows overlap in unpack memory. Upload the texture row by row to work around // driver bug. ANGLE_TRY( reserveTexImageToBeFilled(context, target, level, internalFormat, size, format, type)); if (size.width == 0 || size.height == 0 || size.depth == 0) { return angle::Result::Continue; } gl::Box area(0, 0, 0, size.width, size.height, size.depth); return setSubImageRowByRowWorkaround(context, target, level, area, format, type, unpack, unpackBuffer, 0, pixels); } if (features.unpackLastRowSeparatelyForPaddingInclusion.enabled) { bool apply = false; ANGLE_TRY(ShouldApplyLastRowPaddingWorkaround( GetImplAs(context), size, unpack, unpackBuffer, format, type, nativegl::UseTexImage3D(getType()), pixels, &apply)); // The driver will think the pixel buffer doesn't have enough data, work around this bug // by uploading the last row (and last level if 3D) separately. if (apply) { ANGLE_TRY(reserveTexImageToBeFilled(context, target, level, internalFormat, size, format, type)); if (size.width == 0 || size.height == 0 || size.depth == 0) { return angle::Result::Continue; } gl::Box area(0, 0, 0, size.width, size.height, size.depth); return setSubImagePaddingWorkaround(context, target, level, area, format, type, unpack, unpackBuffer, pixels); } } ANGLE_TRY(setImageHelper(context, target, level, internalFormat, size, format, type, pixels)); return angle::Result::Continue; } angle::Result TextureGL::setImageHelper(const gl::Context *context, gl::TextureTarget target, size_t level, GLenum internalFormat, const gl::Extents &size, GLenum format, GLenum type, const uint8_t *pixels) { ASSERT(TextureTargetToType(target) == getType()); const FunctionsGL *functions = GetFunctionsGL(context); StateManagerGL *stateManager = GetStateManagerGL(context); const angle::FeaturesGL &features = GetFeaturesGL(context); nativegl::TexImageFormat texImageFormat = nativegl::GetTexImageFormat(functions, features, internalFormat, format, type); stateManager->bindTexture(getType(), mTextureID); if (features.resetTexImage2DBaseLevel.enabled) { // setBaseLevel doesn't ever generate errors. (void)setBaseLevel(context, 0); } if (nativegl::UseTexImage2D(getType())) { ASSERT(size.depth == 1); ANGLE_GL_TRY_ALWAYS_CHECK( context, functions->texImage2D(nativegl::GetTextureBindingTarget(target), static_cast(level), texImageFormat.internalFormat, size.width, size.height, 0, texImageFormat.format, texImageFormat.type, pixels)); } else { ASSERT(nativegl::UseTexImage3D(getType())); ANGLE_GL_TRY_ALWAYS_CHECK( context, functions->texImage3D(ToGLenum(target), static_cast(level), texImageFormat.internalFormat, size.width, size.height, size.depth, 0, texImageFormat.format, texImageFormat.type, pixels)); } LevelInfoGL levelInfo = GetLevelInfo(features, internalFormat, texImageFormat.internalFormat); setLevelInfo(context, target, level, 1, levelInfo); if (features.setZeroLevelBeforeGenerateMipmap.enabled && getType() == gl::TextureType::_2D && level != 0 && mLevelInfo[0].nativeInternalFormat == GL_NONE) { // Only fill level zero if it's possible that mipmaps can be generated with this format const gl::InternalFormat &internalFormatInfo = gl::GetInternalFormatInfo(internalFormat, type); if (!internalFormatInfo.sized || (internalFormatInfo.filterSupport(context->getClientVersion(), context->getExtensions()) && internalFormatInfo.textureAttachmentSupport(context->getClientVersion(), context->getExtensions()))) { ANGLE_GL_TRY_ALWAYS_CHECK( context, functions->texImage2D(nativegl::GetTextureBindingTarget(target), 0, texImageFormat.internalFormat, 1, 1, 0, texImageFormat.format, texImageFormat.type, nullptr)); setLevelInfo(context, target, 0, 1, levelInfo); } } return angle::Result::Continue; } angle::Result TextureGL::reserveTexImageToBeFilled(const gl::Context *context, gl::TextureTarget target, size_t level, GLenum internalFormat, const gl::Extents &size, GLenum format, GLenum type) { StateManagerGL *stateManager = GetStateManagerGL(context); ANGLE_TRY(stateManager->setPixelUnpackBuffer(context, nullptr)); ANGLE_TRY(setImageHelper(context, target, level, internalFormat, size, format, type, nullptr)); return angle::Result::Continue; } angle::Result TextureGL::setSubImage(const gl::Context *context, const gl::ImageIndex &index, const gl::Box &area, GLenum format, GLenum type, const gl::PixelUnpackState &unpack, gl::Buffer *unpackBuffer, const uint8_t *pixels) { ASSERT(TextureTargetToType(index.getTarget()) == getType()); const FunctionsGL *functions = GetFunctionsGL(context); StateManagerGL *stateManager = GetStateManagerGL(context); const angle::FeaturesGL &features = GetFeaturesGL(context); nativegl::TexSubImageFormat texSubImageFormat = nativegl::GetTexSubImageFormat(functions, features, format, type); gl::TextureTarget target = index.getTarget(); size_t level = static_cast(index.getLevelIndex()); ASSERT(getLevelInfo(target, level).lumaWorkaround.enabled == GetLevelInfo(features, format, texSubImageFormat.format).lumaWorkaround.enabled); stateManager->bindTexture(getType(), mTextureID); if (features.unpackOverlappingRowsSeparatelyUnpackBuffer.enabled && unpackBuffer && unpack.rowLength != 0 && unpack.rowLength < area.width) { return setSubImageRowByRowWorkaround(context, target, level, area, format, type, unpack, unpackBuffer, 0, pixels); } if (features.unpackLastRowSeparatelyForPaddingInclusion.enabled) { gl::Extents size(area.width, area.height, area.depth); bool apply = false; ANGLE_TRY(ShouldApplyLastRowPaddingWorkaround( GetImplAs(context), size, unpack, unpackBuffer, format, type, nativegl::UseTexImage3D(getType()), pixels, &apply)); // The driver will think the pixel buffer doesn't have enough data, work around this bug // by uploading the last row (and last level if 3D) separately. if (apply) { return setSubImagePaddingWorkaround(context, target, level, area, format, type, unpack, unpackBuffer, pixels); } } if (features.uploadTextureDataInChunks.enabled) { return setSubImageRowByRowWorkaround( context, target, level, area, format, type, unpack, unpackBuffer, angle::FeaturesGL::kUploadTextureDataInChunksUploadSize, pixels); } if (nativegl::UseTexImage2D(getType())) { ASSERT(area.z == 0 && area.depth == 1); ANGLE_GL_TRY(context, functions->texSubImage2D(nativegl::GetTextureBindingTarget(target), static_cast(level), area.x, area.y, area.width, area.height, texSubImageFormat.format, texSubImageFormat.type, pixels)); } else { ASSERT(nativegl::UseTexImage3D(getType())); ANGLE_GL_TRY(context, functions->texSubImage3D( ToGLenum(target), static_cast(level), area.x, area.y, area.z, area.width, area.height, area.depth, texSubImageFormat.format, texSubImageFormat.type, pixels)); } return angle::Result::Continue; } angle::Result TextureGL::setSubImageRowByRowWorkaround(const gl::Context *context, gl::TextureTarget target, size_t level, const gl::Box &area, GLenum format, GLenum type, const gl::PixelUnpackState &unpack, const gl::Buffer *unpackBuffer, size_t maxBytesUploadedPerChunk, const uint8_t *pixels) { ContextGL *contextGL = GetImplAs(context); const FunctionsGL *functions = GetFunctionsGL(context); StateManagerGL *stateManager = GetStateManagerGL(context); const angle::FeaturesGL &features = GetFeaturesGL(context); gl::PixelUnpackState directUnpack = unpack; directUnpack.skipRows = 0; directUnpack.skipPixels = 0; directUnpack.skipImages = 0; ANGLE_TRY(stateManager->setPixelUnpackState(context, directUnpack)); ANGLE_TRY(stateManager->setPixelUnpackBuffer(context, unpackBuffer)); const gl::InternalFormat &glFormat = gl::GetInternalFormatInfo(format, type); GLuint rowBytes = 0; ANGLE_CHECK_GL_MATH(contextGL, glFormat.computeRowPitch(type, area.width, unpack.alignment, unpack.rowLength, &rowBytes)); GLuint imageBytes = 0; ANGLE_CHECK_GL_MATH(contextGL, glFormat.computeDepthPitch(area.height, unpack.imageHeight, rowBytes, &imageBytes)); bool useTexImage3D = nativegl::UseTexImage3D(getType()); GLuint skipBytes = 0; ANGLE_CHECK_GL_MATH(contextGL, glFormat.computeSkipBytes(type, rowBytes, imageBytes, unpack, useTexImage3D, &skipBytes)); GLint rowsPerChunk = std::min(std::max(static_cast(maxBytesUploadedPerChunk / rowBytes), 1), area.height); if (maxBytesUploadedPerChunk > 0 && rowsPerChunk < area.height) { ANGLE_PERF_WARNING(contextGL->getDebug(), GL_DEBUG_SEVERITY_LOW, "Chunking upload of texture data to work around driver hangs."); } nativegl::TexSubImageFormat texSubImageFormat = nativegl::GetTexSubImageFormat(functions, features, format, type); const uint8_t *pixelsWithSkip = pixels + skipBytes; if (useTexImage3D) { for (GLint image = 0; image < area.depth; ++image) { GLint imageByteOffset = image * imageBytes; for (GLint row = 0; row < area.height; row += rowsPerChunk) { GLint height = std::min(rowsPerChunk, area.height - row); GLint byteOffset = imageByteOffset + row * rowBytes; const GLubyte *rowPixels = pixelsWithSkip + byteOffset; ANGLE_GL_TRY(context, functions->texSubImage3D( ToGLenum(target), static_cast(level), area.x, row + area.y, image + area.z, area.width, height, 1, texSubImageFormat.format, texSubImageFormat.type, rowPixels)); } } } else { ASSERT(nativegl::UseTexImage2D(getType())); for (GLint row = 0; row < area.height; row += rowsPerChunk) { GLint height = std::min(rowsPerChunk, area.height - row); GLint byteOffset = row * rowBytes; const GLubyte *rowPixels = pixelsWithSkip + byteOffset; ANGLE_GL_TRY(context, functions->texSubImage2D( ToGLenum(target), static_cast(level), area.x, row + area.y, area.width, height, texSubImageFormat.format, texSubImageFormat.type, rowPixels)); } } return angle::Result::Continue; } angle::Result TextureGL::setSubImagePaddingWorkaround(const gl::Context *context, gl::TextureTarget target, size_t level, const gl::Box &area, GLenum format, GLenum type, const gl::PixelUnpackState &unpack, const gl::Buffer *unpackBuffer, const uint8_t *pixels) { ContextGL *contextGL = GetImplAs(context); const FunctionsGL *functions = GetFunctionsGL(context); StateManagerGL *stateManager = GetStateManagerGL(context); const gl::InternalFormat &glFormat = gl::GetInternalFormatInfo(format, type); GLuint rowBytes = 0; ANGLE_CHECK_GL_MATH(contextGL, glFormat.computeRowPitch(type, area.width, unpack.alignment, unpack.rowLength, &rowBytes)); GLuint imageBytes = 0; ANGLE_CHECK_GL_MATH(contextGL, glFormat.computeDepthPitch(area.height, unpack.imageHeight, rowBytes, &imageBytes)); bool useTexImage3D = nativegl::UseTexImage3D(getType()); GLuint skipBytes = 0; ANGLE_CHECK_GL_MATH(contextGL, glFormat.computeSkipBytes(type, rowBytes, imageBytes, unpack, useTexImage3D, &skipBytes)); ANGLE_TRY(stateManager->setPixelUnpackState(context, unpack)); ANGLE_TRY(stateManager->setPixelUnpackBuffer(context, unpackBuffer)); gl::PixelUnpackState directUnpack; directUnpack.alignment = 1; if (useTexImage3D) { // Upload all but the last slice if (area.depth > 1) { ANGLE_GL_TRY(context, functions->texSubImage3D(ToGLenum(target), static_cast(level), area.x, area.y, area.z, area.width, area.height, area.depth - 1, format, type, pixels)); } // Upload the last slice but its last row if (area.height > 1) { // Do not include skipBytes in the last image pixel start offset as it will be done by // the driver GLint lastImageOffset = (area.depth - 1) * imageBytes; const GLubyte *lastImagePixels = pixels + lastImageOffset; ANGLE_GL_TRY(context, functions->texSubImage3D( ToGLenum(target), static_cast(level), area.x, area.y, area.z + area.depth - 1, area.width, area.height - 1, 1, format, type, lastImagePixels)); } // Upload the last row of the last slice "manually" ANGLE_TRY(stateManager->setPixelUnpackState(context, directUnpack)); GLint lastRowOffset = skipBytes + (area.depth - 1) * imageBytes + (area.height - 1) * rowBytes; const GLubyte *lastRowPixels = pixels + lastRowOffset; ANGLE_GL_TRY(context, functions->texSubImage3D(ToGLenum(target), static_cast(level), area.x, area.y + area.height - 1, area.z + area.depth - 1, area.width, 1, 1, format, type, lastRowPixels)); } else { ASSERT(nativegl::UseTexImage2D(getType())); // Upload all but the last row if (area.height > 1) { ANGLE_GL_TRY(context, functions->texSubImage2D( ToGLenum(target), static_cast(level), area.x, area.y, area.width, area.height - 1, format, type, pixels)); } // Upload the last row "manually" ANGLE_TRY(stateManager->setPixelUnpackState(context, directUnpack)); GLint lastRowOffset = skipBytes + (area.height - 1) * rowBytes; const GLubyte *lastRowPixels = pixels + lastRowOffset; ANGLE_GL_TRY(context, functions->texSubImage2D(ToGLenum(target), static_cast(level), area.x, area.y + area.height - 1, area.width, 1, format, type, lastRowPixels)); } return angle::Result::Continue; } angle::Result TextureGL::setCompressedImage(const gl::Context *context, const gl::ImageIndex &index, GLenum internalFormat, const gl::Extents &size, const gl::PixelUnpackState &unpack, size_t imageSize, const uint8_t *pixels) { const FunctionsGL *functions = GetFunctionsGL(context); StateManagerGL *stateManager = GetStateManagerGL(context); const angle::FeaturesGL &features = GetFeaturesGL(context); gl::TextureTarget target = index.getTarget(); size_t level = static_cast(index.getLevelIndex()); ASSERT(TextureTargetToType(target) == getType()); nativegl::CompressedTexImageFormat compressedTexImageFormat = nativegl::GetCompressedTexImageFormat(functions, features, internalFormat); stateManager->bindTexture(getType(), mTextureID); if (nativegl::UseTexImage2D(getType())) { ASSERT(size.depth == 1); ANGLE_GL_TRY_ALWAYS_CHECK( context, functions->compressedTexImage2D(ToGLenum(target), static_cast(level), compressedTexImageFormat.internalFormat, size.width, size.height, 0, static_cast(imageSize), pixels)); } else { ASSERT(nativegl::UseTexImage3D(getType())); ANGLE_GL_TRY_ALWAYS_CHECK( context, functions->compressedTexImage3D(ToGLenum(target), static_cast(level), compressedTexImageFormat.internalFormat, size.width, size.height, size.depth, 0, static_cast(imageSize), pixels)); } LevelInfoGL levelInfo = GetLevelInfo(features, internalFormat, compressedTexImageFormat.internalFormat); ASSERT(!levelInfo.lumaWorkaround.enabled); setLevelInfo(context, target, level, 1, levelInfo); return angle::Result::Continue; } angle::Result TextureGL::setCompressedSubImage(const gl::Context *context, const gl::ImageIndex &index, const gl::Box &area, GLenum format, const gl::PixelUnpackState &unpack, size_t imageSize, const uint8_t *pixels) { const FunctionsGL *functions = GetFunctionsGL(context); StateManagerGL *stateManager = GetStateManagerGL(context); const angle::FeaturesGL &features = GetFeaturesGL(context); gl::TextureTarget target = index.getTarget(); size_t level = static_cast(index.getLevelIndex()); ASSERT(TextureTargetToType(target) == getType()); nativegl::CompressedTexSubImageFormat compressedTexSubImageFormat = nativegl::GetCompressedSubTexImageFormat(functions, features, format); stateManager->bindTexture(getType(), mTextureID); if (nativegl::UseTexImage2D(getType())) { ASSERT(area.z == 0 && area.depth == 1); ANGLE_GL_TRY(context, functions->compressedTexSubImage2D( ToGLenum(target), static_cast(level), area.x, area.y, area.width, area.height, compressedTexSubImageFormat.format, static_cast(imageSize), pixels)); } else { ASSERT(nativegl::UseTexImage3D(getType())); ANGLE_GL_TRY(context, functions->compressedTexSubImage3D( ToGLenum(target), static_cast(level), area.x, area.y, area.z, area.width, area.height, area.depth, compressedTexSubImageFormat.format, static_cast(imageSize), pixels)); } ASSERT( !getLevelInfo(target, level).lumaWorkaround.enabled && !GetLevelInfo(features, format, compressedTexSubImageFormat.format).lumaWorkaround.enabled); return angle::Result::Continue; } angle::Result TextureGL::copyImage(const gl::Context *context, const gl::ImageIndex &index, const gl::Rectangle &sourceArea, GLenum internalFormat, gl::Framebuffer *source) { ContextGL *contextGL = GetImplAs(context); const FunctionsGL *functions = GetFunctionsGL(context); StateManagerGL *stateManager = GetStateManagerGL(context); const angle::FeaturesGL &features = GetFeaturesGL(context); gl::TextureTarget target = index.getTarget(); size_t level = static_cast(index.getLevelIndex()); GLenum type = source->getImplementationColorReadType(context); nativegl::CopyTexImageImageFormat copyTexImageFormat = nativegl::GetCopyTexImageImageFormat(functions, features, internalFormat, type); stateManager->bindTexture(getType(), mTextureID); const FramebufferGL *sourceFramebufferGL = GetImplAs(source); gl::Extents fbSize = sourceFramebufferGL->getState().getReadAttachment()->getSize(); // Did the read area go outside the framebuffer? bool outside = sourceArea.x < 0 || sourceArea.y < 0 || sourceArea.x + sourceArea.width > fbSize.width || sourceArea.y + sourceArea.height > fbSize.height; // TODO: Find a way to initialize the texture entirely in the gl level with ensureInitialized. // Right now there is no easy way to pre-fill the texture when it is being redefined with // partially uninitialized data. bool requiresInitialization = outside && (context->isRobustResourceInitEnabled() || context->isWebGL()); // When robust resource initialization is enabled, the area outside the framebuffer must be // zeroed. We just zero the whole thing before copying into the area that overlaps the // framebuffer. if (requiresInitialization) { GLuint pixelBytes = gl::GetInternalFormatInfo(copyTexImageFormat.internalFormat, type).pixelBytes; angle::MemoryBuffer *zero; ANGLE_CHECK_GL_ALLOC( contextGL, context->getZeroFilledBuffer(sourceArea.width * sourceArea.height * pixelBytes, &zero)); gl::PixelUnpackState unpack; unpack.alignment = 1; ANGLE_TRY(stateManager->setPixelUnpackState(context, unpack)); ANGLE_TRY(stateManager->setPixelUnpackBuffer(context, nullptr)); ANGLE_GL_TRY_ALWAYS_CHECK( context, functions->texImage2D(ToGLenum(target), static_cast(level), copyTexImageFormat.internalFormat, sourceArea.width, sourceArea.height, 0, gl::GetUnsizedFormat(copyTexImageFormat.internalFormat), type, zero->data())); } // Clip source area to framebuffer and copy if remaining area is not empty. gl::Rectangle clippedArea; if (ClipRectangle(sourceArea, gl::Rectangle(0, 0, fbSize.width, fbSize.height), &clippedArea)) { // If fbo's read buffer and the target texture are the same texture but different levels, // and if the read buffer is a non-base texture level, then implementations glTexImage2D // may change the target texture and make the original texture mipmap incomplete, which in // turn makes the fbo incomplete. // To avoid that, we clamp BASE_LEVEL and MAX_LEVEL to the same texture level as the fbo's // read buffer attachment. See http://crbug.com/797235 const gl::FramebufferAttachment *readBuffer = source->getReadColorAttachment(); if (readBuffer && readBuffer->type() == GL_TEXTURE) { TextureGL *sourceTexture = GetImplAs(readBuffer->getTexture()); if (sourceTexture && sourceTexture->mTextureID == mTextureID) { GLuint attachedTextureLevel = readBuffer->mipLevel(); if (attachedTextureLevel != mState.getEffectiveBaseLevel()) { ANGLE_TRY(setBaseLevel(context, attachedTextureLevel)); ANGLE_TRY(setMaxLevel(context, attachedTextureLevel)); } } } LevelInfoGL levelInfo = GetLevelInfo(features, internalFormat, copyTexImageFormat.internalFormat); gl::Offset destOffset(clippedArea.x - sourceArea.x, clippedArea.y - sourceArea.y, 0); if (levelInfo.lumaWorkaround.enabled) { BlitGL *blitter = GetBlitGL(context); if (requiresInitialization) { ANGLE_TRY(blitter->copySubImageToLUMAWorkaroundTexture( context, mTextureID, getType(), target, levelInfo.sourceFormat, level, destOffset, clippedArea, source)); } else { ANGLE_TRY(blitter->copyImageToLUMAWorkaroundTexture( context, mTextureID, getType(), target, levelInfo.sourceFormat, level, clippedArea, copyTexImageFormat.internalFormat, source)); } } else { ASSERT(nativegl::UseTexImage2D(getType())); stateManager->bindFramebuffer(GL_READ_FRAMEBUFFER, sourceFramebufferGL->getFramebufferID()); if (features.emulateCopyTexImage2DFromRenderbuffers.enabled && readBuffer && readBuffer->type() == GL_RENDERBUFFER) { BlitGL *blitter = GetBlitGL(context); ANGLE_TRY(blitter->blitColorBufferWithShader( context, source, mTextureID, target, level, clippedArea, gl::Rectangle(destOffset.x, destOffset.y, clippedArea.width, clippedArea.height), GL_NEAREST, true)); } else if (requiresInitialization) { ANGLE_GL_TRY(context, functions->copyTexSubImage2D( ToGLenum(target), static_cast(level), destOffset.x, destOffset.y, clippedArea.x, clippedArea.y, clippedArea.width, clippedArea.height)); } else { ANGLE_GL_TRY_ALWAYS_CHECK( context, functions->copyTexImage2D(ToGLenum(target), static_cast(level), copyTexImageFormat.internalFormat, clippedArea.x, clippedArea.y, clippedArea.width, clippedArea.height, 0)); } } setLevelInfo(context, target, level, 1, levelInfo); } if (features.flushBeforeDeleteTextureIfCopiedTo.enabled) { contextGL->setNeedsFlushBeforeDeleteTextures(); } return angle::Result::Continue; } angle::Result TextureGL::copySubImage(const gl::Context *context, const gl::ImageIndex &index, const gl::Offset &destOffset, const gl::Rectangle &sourceArea, gl::Framebuffer *source) { const FunctionsGL *functions = GetFunctionsGL(context); StateManagerGL *stateManager = GetStateManagerGL(context); const angle::FeaturesGL &features = GetFeaturesGL(context); gl::TextureTarget target = index.getTarget(); size_t level = static_cast(index.getLevelIndex()); const FramebufferGL *sourceFramebufferGL = GetImplAs(source); // Clip source area to framebuffer. const gl::Extents fbSize = sourceFramebufferGL->getState().getReadAttachment()->getSize(); gl::Rectangle clippedArea; if (!ClipRectangle(sourceArea, gl::Rectangle(0, 0, fbSize.width, fbSize.height), &clippedArea)) { // nothing to do return angle::Result::Continue; } gl::Offset clippedOffset(destOffset.x + clippedArea.x - sourceArea.x, destOffset.y + clippedArea.y - sourceArea.y, destOffset.z); stateManager->bindTexture(getType(), mTextureID); GLenum framebufferTarget = stateManager->getHasSeparateFramebufferBindings() ? GL_READ_FRAMEBUFFER : GL_FRAMEBUFFER; stateManager->bindFramebuffer(framebufferTarget, sourceFramebufferGL->getFramebufferID()); const LevelInfoGL &levelInfo = getLevelInfo(target, level); if (levelInfo.lumaWorkaround.enabled) { BlitGL *blitter = GetBlitGL(context); ANGLE_TRY(blitter->copySubImageToLUMAWorkaroundTexture( context, mTextureID, getType(), target, levelInfo.sourceFormat, level, clippedOffset, clippedArea, source)); } else { if (nativegl::UseTexImage2D(getType())) { ASSERT(clippedOffset.z == 0); if (features.emulateCopyTexImage2DFromRenderbuffers.enabled && source->getReadColorAttachment() && source->getReadColorAttachment()->type() == GL_RENDERBUFFER) { BlitGL *blitter = GetBlitGL(context); ANGLE_TRY(blitter->blitColorBufferWithShader( context, source, mTextureID, target, level, clippedArea, gl::Rectangle(clippedOffset.x, clippedOffset.y, clippedArea.width, clippedArea.height), GL_NEAREST, true)); } else { ANGLE_GL_TRY(context, functions->copyTexSubImage2D( ToGLenum(target), static_cast(level), clippedOffset.x, clippedOffset.y, clippedArea.x, clippedArea.y, clippedArea.width, clippedArea.height)); } } else { ASSERT(nativegl::UseTexImage3D(getType())); ANGLE_GL_TRY(context, functions->copyTexSubImage3D( ToGLenum(target), static_cast(level), clippedOffset.x, clippedOffset.y, clippedOffset.z, clippedArea.x, clippedArea.y, clippedArea.width, clippedArea.height)); } } if (features.flushBeforeDeleteTextureIfCopiedTo.enabled) { ContextGL *contextGL = GetImplAs(context); contextGL->setNeedsFlushBeforeDeleteTextures(); } return angle::Result::Continue; } angle::Result TextureGL::copyTexture(const gl::Context *context, const gl::ImageIndex &index, GLenum internalFormat, GLenum type, GLint sourceLevel, bool unpackFlipY, bool unpackPremultiplyAlpha, bool unpackUnmultiplyAlpha, const gl::Texture *source) { gl::TextureTarget target = index.getTarget(); size_t level = static_cast(index.getLevelIndex()); const TextureGL *sourceGL = GetImplAs(source); const gl::ImageDesc &sourceImageDesc = sourceGL->mState.getImageDesc(NonCubeTextureTypeToTarget(source->getType()), sourceLevel); gl::Rectangle sourceArea(0, 0, sourceImageDesc.size.width, sourceImageDesc.size.height); ANGLE_TRY(reserveTexImageToBeFilled(context, target, level, internalFormat, sourceImageDesc.size, gl::GetUnsizedFormat(internalFormat), type)); const gl::InternalFormat &destFormatInfo = gl::GetInternalFormatInfo(internalFormat, type); return copySubTextureHelper(context, target, level, gl::Offset(0, 0, 0), sourceLevel, sourceArea, destFormatInfo, unpackFlipY, unpackPremultiplyAlpha, unpackUnmultiplyAlpha, source); } angle::Result TextureGL::copySubTexture(const gl::Context *context, const gl::ImageIndex &index, const gl::Offset &destOffset, GLint sourceLevel, const gl::Box &sourceBox, bool unpackFlipY, bool unpackPremultiplyAlpha, bool unpackUnmultiplyAlpha, const gl::Texture *source) { gl::TextureTarget target = index.getTarget(); size_t level = static_cast(index.getLevelIndex()); const gl::InternalFormat &destFormatInfo = *mState.getImageDesc(target, level).format.info; return copySubTextureHelper(context, target, level, destOffset, sourceLevel, sourceBox.toRect(), destFormatInfo, unpackFlipY, unpackPremultiplyAlpha, unpackUnmultiplyAlpha, source); } angle::Result TextureGL::copySubTextureHelper(const gl::Context *context, gl::TextureTarget target, size_t level, const gl::Offset &destOffset, GLint sourceLevel, const gl::Rectangle &sourceArea, const gl::InternalFormat &destFormat, bool unpackFlipY, bool unpackPremultiplyAlpha, bool unpackUnmultiplyAlpha, const gl::Texture *source) { const FunctionsGL *functions = GetFunctionsGL(context); const angle::FeaturesGL &features = GetFeaturesGL(context); BlitGL *blitter = GetBlitGL(context); TextureGL *sourceGL = GetImplAs(source); const gl::ImageDesc &sourceImageDesc = sourceGL->mState.getImageDesc(NonCubeTextureTypeToTarget(source->getType()), sourceLevel); if (features.flushBeforeDeleteTextureIfCopiedTo.enabled) { // Conservatively indicate that this workaround is necessary. Not clear // if it is on this code path, but added for symmetry. ContextGL *contextGL = GetImplAs(context); contextGL->setNeedsFlushBeforeDeleteTextures(); } // Check is this is a simple copySubTexture that can be done with a copyTexSubImage ASSERT(sourceGL->getType() == gl::TextureType::_2D || source->getType() == gl::TextureType::External || source->getType() == gl::TextureType::Rectangle); const LevelInfoGL &sourceLevelInfo = sourceGL->getLevelInfo(NonCubeTextureTypeToTarget(source->getType()), sourceLevel); bool needsLumaWorkaround = sourceLevelInfo.lumaWorkaround.enabled; const gl::InternalFormat &sourceFormatInfo = *sourceImageDesc.format.info; GLenum sourceFormat = sourceFormatInfo.format; bool sourceFormatContainSupersetOfDestFormat = (sourceFormat == destFormat.format && sourceFormat != GL_BGRA_EXT) || (sourceFormat == GL_RGBA && destFormat.format == GL_RGB); GLenum sourceComponentType = sourceFormatInfo.componentType; GLenum destComponentType = destFormat.componentType; bool destSRGB = destFormat.colorEncoding == GL_SRGB; if (!unpackFlipY && unpackPremultiplyAlpha == unpackUnmultiplyAlpha && !needsLumaWorkaround && sourceFormatContainSupersetOfDestFormat && sourceComponentType == destComponentType && !destSRGB && sourceGL->getType() == gl::TextureType::_2D) { bool copySucceeded = false; ANGLE_TRY(blitter->copyTexSubImage(context, sourceGL, sourceLevel, this, target, level, sourceArea, destOffset, ©Succeeded)); if (copySucceeded) { return angle::Result::Continue; } } // Check if the destination is renderable and copy on the GPU const LevelInfoGL &destLevelInfo = getLevelInfo(target, level); // todo(jonahr): http://crbug.com/773861 // Behavior for now is to fallback to CPU readback implementation if the destination texture // is a luminance format. The correct solution is to handle both source and destination in the // luma workaround. if (!destSRGB && !destLevelInfo.lumaWorkaround.enabled && nativegl::SupportsNativeRendering(functions, getType(), destLevelInfo.nativeInternalFormat)) { bool copySucceeded = false; ANGLE_TRY(blitter->copySubTexture( context, sourceGL, sourceLevel, sourceComponentType, mTextureID, target, level, destComponentType, sourceImageDesc.size, sourceArea, destOffset, needsLumaWorkaround, sourceLevelInfo.sourceFormat, unpackFlipY, unpackPremultiplyAlpha, unpackUnmultiplyAlpha, ©Succeeded)); if (copySucceeded) { return angle::Result::Continue; } } // Fall back to CPU-readback return blitter->copySubTextureCPUReadback( context, sourceGL, sourceLevel, sourceFormatInfo.sizedInternalFormat, this, target, level, destFormat.format, destFormat.type, sourceImageDesc.size, sourceArea, destOffset, needsLumaWorkaround, sourceLevelInfo.sourceFormat, unpackFlipY, unpackPremultiplyAlpha, unpackUnmultiplyAlpha); } angle::Result TextureGL::setStorage(const gl::Context *context, gl::TextureType type, size_t levels, GLenum internalFormat, const gl::Extents &size) { ContextGL *contextGL = GetImplAs(context); const FunctionsGL *functions = GetFunctionsGL(context); StateManagerGL *stateManager = GetStateManagerGL(context); const angle::FeaturesGL &features = GetFeaturesGL(context); nativegl::TexStorageFormat texStorageFormat = nativegl::GetTexStorageFormat(functions, features, internalFormat); stateManager->bindTexture(getType(), mTextureID); if (nativegl::UseTexImage2D(getType())) { ASSERT(size.depth == 1); if (functions->texStorage2D) { ANGLE_GL_TRY_ALWAYS_CHECK( context, functions->texStorage2D(ToGLenum(type), static_cast(levels), texStorageFormat.internalFormat, size.width, size.height)); } else { // Make sure no pixel unpack buffer is bound stateManager->bindBuffer(gl::BufferBinding::PixelUnpack, 0); const gl::InternalFormat &internalFormatInfo = gl::GetSizedInternalFormatInfo(internalFormat); // Internal format must be sized ASSERT(internalFormatInfo.sized); for (size_t level = 0; level < levels; level++) { gl::Extents levelSize(std::max(size.width >> level, 1), std::max(size.height >> level, 1), 1); if (getType() == gl::TextureType::_2D || getType() == gl::TextureType::Rectangle) { if (internalFormatInfo.compressed) { nativegl::CompressedTexSubImageFormat compressedTexImageFormat = nativegl::GetCompressedSubTexImageFormat(functions, features, internalFormat); GLuint dataSize = 0; ANGLE_CHECK_GL_MATH( contextGL, internalFormatInfo.computeCompressedImageSize(levelSize, &dataSize)); ANGLE_GL_TRY_ALWAYS_CHECK( context, functions->compressedTexImage2D( ToGLenum(type), static_cast(level), compressedTexImageFormat.format, levelSize.width, levelSize.height, 0, static_cast(dataSize), nullptr)); } else { nativegl::TexImageFormat texImageFormat = nativegl::GetTexImageFormat( functions, features, internalFormat, internalFormatInfo.format, internalFormatInfo.type); ANGLE_GL_TRY_ALWAYS_CHECK( context, functions->texImage2D(ToGLenum(type), static_cast(level), texImageFormat.internalFormat, levelSize.width, levelSize.height, 0, texImageFormat.format, texImageFormat.type, nullptr)); } } else { ASSERT(getType() == gl::TextureType::CubeMap); for (gl::TextureTarget face : gl::AllCubeFaceTextureTargets()) { if (internalFormatInfo.compressed) { nativegl::CompressedTexSubImageFormat compressedTexImageFormat = nativegl::GetCompressedSubTexImageFormat(functions, features, internalFormat); GLuint dataSize = 0; ANGLE_CHECK_GL_MATH(contextGL, internalFormatInfo.computeCompressedImageSize( levelSize, &dataSize)); ANGLE_GL_TRY_ALWAYS_CHECK( context, functions->compressedTexImage2D( ToGLenum(face), static_cast(level), compressedTexImageFormat.format, levelSize.width, levelSize.height, 0, static_cast(dataSize), nullptr)); } else { nativegl::TexImageFormat texImageFormat = nativegl::GetTexImageFormat( functions, features, internalFormat, internalFormatInfo.format, internalFormatInfo.type); ANGLE_GL_TRY_ALWAYS_CHECK( context, functions->texImage2D( ToGLenum(face), static_cast(level), texImageFormat.internalFormat, levelSize.width, levelSize.height, 0, texImageFormat.format, texImageFormat.type, nullptr)); } } } } } } else { ASSERT(nativegl::UseTexImage3D(getType())); if (functions->texStorage3D) { ANGLE_GL_TRY_ALWAYS_CHECK( context, functions->texStorage3D(ToGLenum(type), static_cast(levels), texStorageFormat.internalFormat, size.width, size.height, size.depth)); } else { // Make sure no pixel unpack buffer is bound stateManager->bindBuffer(gl::BufferBinding::PixelUnpack, 0); const gl::InternalFormat &internalFormatInfo = gl::GetSizedInternalFormatInfo(internalFormat); // Internal format must be sized ASSERT(internalFormatInfo.sized); for (GLsizei i = 0; i < static_cast(levels); i++) { gl::Extents levelSize( std::max(size.width >> i, 1), std::max(size.height >> i, 1), getType() == gl::TextureType::_3D ? std::max(size.depth >> i, 1) : size.depth); if (internalFormatInfo.compressed) { nativegl::CompressedTexSubImageFormat compressedTexImageFormat = nativegl::GetCompressedSubTexImageFormat(functions, features, internalFormat); GLuint dataSize = 0; ANGLE_CHECK_GL_MATH(contextGL, internalFormatInfo.computeCompressedImageSize( levelSize, &dataSize)); ANGLE_GL_TRY_ALWAYS_CHECK( context, functions->compressedTexImage3D( ToGLenum(type), i, compressedTexImageFormat.format, levelSize.width, levelSize.height, levelSize.depth, 0, static_cast(dataSize), nullptr)); } else { nativegl::TexImageFormat texImageFormat = nativegl::GetTexImageFormat( functions, features, internalFormat, internalFormatInfo.format, internalFormatInfo.type); ANGLE_GL_TRY_ALWAYS_CHECK( context, functions->texImage3D(ToGLenum(type), i, texImageFormat.internalFormat, levelSize.width, levelSize.height, levelSize.depth, 0, texImageFormat.format, texImageFormat.type, nullptr)); } } } } setLevelInfo(context, type, 0, levels, GetLevelInfo(features, internalFormat, texStorageFormat.internalFormat)); return angle::Result::Continue; } angle::Result TextureGL::setImageExternal(const gl::Context *context, const gl::ImageIndex &index, GLenum internalFormat, const gl::Extents &size, GLenum format, GLenum type) { const FunctionsGL *functions = GetFunctionsGL(context); const angle::FeaturesGL &features = GetFeaturesGL(context); gl::TextureTarget target = index.getTarget(); size_t level = static_cast(index.getLevelIndex()); nativegl::TexImageFormat texImageFormat = nativegl::GetTexImageFormat(functions, features, internalFormat, format, type); setLevelInfo(context, target, level, 1, GetLevelInfo(features, internalFormat, texImageFormat.internalFormat)); return angle::Result::Continue; } angle::Result TextureGL::setStorageMultisample(const gl::Context *context, gl::TextureType type, GLsizei samples, GLint internalformat, const gl::Extents &size, bool fixedSampleLocations) { const FunctionsGL *functions = GetFunctionsGL(context); StateManagerGL *stateManager = GetStateManagerGL(context); const angle::FeaturesGL &features = GetFeaturesGL(context); nativegl::TexStorageFormat texStorageFormat = nativegl::GetTexStorageFormat(functions, features, internalformat); stateManager->bindTexture(getType(), mTextureID); if (nativegl::UseTexImage2D(getType())) { ASSERT(size.depth == 1); if (functions->texStorage2DMultisample) { ANGLE_GL_TRY_ALWAYS_CHECK( context, functions->texStorage2DMultisample( ToGLenum(type), samples, texStorageFormat.internalFormat, size.width, size.height, gl::ConvertToGLBoolean(fixedSampleLocations))); } else { // texImage2DMultisample is similar to texStorage2DMultisample of es 3.1 core feature, // On macos and some old drivers which doesn't support OpenGL ES 3.1, the function can // be supported by ARB_texture_multisample or OpenGL 3.2 core feature. ANGLE_GL_TRY_ALWAYS_CHECK( context, functions->texImage2DMultisample( ToGLenum(type), samples, texStorageFormat.internalFormat, size.width, size.height, gl::ConvertToGLBoolean(fixedSampleLocations))); } } else { ASSERT(nativegl::UseTexImage3D(getType())); ANGLE_GL_TRY_ALWAYS_CHECK( context, functions->texStorage3DMultisample( ToGLenum(type), samples, texStorageFormat.internalFormat, size.width, size.height, size.depth, gl::ConvertToGLBoolean(fixedSampleLocations))); } setLevelInfo(context, type, 0, 1, GetLevelInfo(features, internalformat, texStorageFormat.internalFormat)); return angle::Result::Continue; } angle::Result TextureGL::setStorageExternalMemory(const gl::Context *context, gl::TextureType type, size_t levels, GLenum internalFormat, const gl::Extents &size, gl::MemoryObject *memoryObject, GLuint64 offset, GLbitfield createFlags, GLbitfield usageFlags) { // GL_ANGLE_external_objects_flags not supported. ASSERT(createFlags == 0); ASSERT(usageFlags == std::numeric_limits::max()); const FunctionsGL *functions = GetFunctionsGL(context); StateManagerGL *stateManager = GetStateManagerGL(context); const angle::FeaturesGL &features = GetFeaturesGL(context); MemoryObjectGL *memoryObjectGL = GetImplAs(memoryObject); nativegl::TexStorageFormat texStorageFormat = nativegl::GetTexStorageFormat(functions, features, internalFormat); stateManager->bindTexture(getType(), mTextureID); if (nativegl::UseTexImage2D(getType())) { ANGLE_GL_TRY_ALWAYS_CHECK( context, functions->texStorageMem2DEXT(ToGLenum(type), static_cast(levels), texStorageFormat.internalFormat, size.width, size.height, memoryObjectGL->getMemoryObjectID(), offset)); } else { ASSERT(nativegl::UseTexImage3D(getType())); ANGLE_GL_TRY_ALWAYS_CHECK( context, functions->texStorageMem3DEXT(ToGLenum(type), static_cast(levels), texStorageFormat.internalFormat, size.width, size.height, size.depth, memoryObjectGL->getMemoryObjectID(), offset)); } setLevelInfo(context, type, 0, levels, GetLevelInfo(features, internalFormat, texStorageFormat.internalFormat)); return angle::Result::Continue; } angle::Result TextureGL::setImageExternal(const gl::Context *context, gl::TextureType type, egl::Stream *stream, const egl::Stream::GLTextureDescription &desc) { ANGLE_GL_UNREACHABLE(GetImplAs(context)); return angle::Result::Stop; } angle::Result TextureGL::generateMipmap(const gl::Context *context) { const FunctionsGL *functions = GetFunctionsGL(context); StateManagerGL *stateManager = GetStateManagerGL(context); const angle::FeaturesGL &features = GetFeaturesGL(context); const GLuint effectiveBaseLevel = mState.getEffectiveBaseLevel(); const GLuint maxLevel = mState.getMipmapMaxLevel(); const gl::ImageDesc &baseLevelDesc = mState.getBaseLevelDesc(); const gl::InternalFormat &baseLevelInternalFormat = *baseLevelDesc.format.info; stateManager->bindTexture(getType(), mTextureID); if (baseLevelInternalFormat.colorEncoding == GL_SRGB && features.encodeAndDecodeSRGBForGenerateMipmap.enabled && getType() == gl::TextureType::_2D) { nativegl::TexImageFormat texImageFormat = nativegl::GetTexImageFormat( functions, features, baseLevelInternalFormat.internalFormat, baseLevelInternalFormat.format, baseLevelInternalFormat.type); // Manually allocate the mip levels of this texture if they don't exist GLuint levelCount = maxLevel - effectiveBaseLevel + 1; for (GLuint levelIdx = 1; levelIdx < levelCount; levelIdx++) { gl::Extents levelSize(std::max(baseLevelDesc.size.width >> levelIdx, 1), std::max(baseLevelDesc.size.height >> levelIdx, 1), 1); const gl::ImageDesc &levelDesc = mState.getImageDesc(gl::TextureTarget::_2D, effectiveBaseLevel + levelIdx); // Make sure no pixel unpack buffer is bound stateManager->bindBuffer(gl::BufferBinding::PixelUnpack, 0); if (levelDesc.size != levelSize || *levelDesc.format.info != baseLevelInternalFormat) { ANGLE_GL_TRY_ALWAYS_CHECK( context, functions->texImage2D( ToGLenum(getType()), effectiveBaseLevel + levelIdx, texImageFormat.internalFormat, levelSize.width, levelSize.height, 0, texImageFormat.format, texImageFormat.type, nullptr)); } } // Use the blitter to generate the mips BlitGL *blitter = GetBlitGL(context); ANGLE_TRY(blitter->generateSRGBMipmap(context, this, effectiveBaseLevel, levelCount, baseLevelDesc.size)); } else { ANGLE_GL_TRY_ALWAYS_CHECK(context, functions->generateMipmap(ToGLenum(getType()))); } setLevelInfo(context, getType(), effectiveBaseLevel, maxLevel - effectiveBaseLevel, getBaseLevelInfo()); return angle::Result::Continue; } angle::Result TextureGL::bindTexImage(const gl::Context *context, egl::Surface *surface) { ASSERT(getType() == gl::TextureType::_2D || getType() == gl::TextureType::Rectangle); StateManagerGL *stateManager = GetStateManagerGL(context); // Make sure this texture is bound stateManager->bindTexture(getType(), mTextureID); SurfaceGL *surfaceGL = GetImplAs(surface); const gl::Format &surfaceFormat = surface->getBindTexImageFormat(); setLevelInfo(context, getType(), 0, 1, LevelInfoGL(surfaceFormat.info->format, surfaceFormat.info->internalFormat, false, LUMAWorkaroundGL(), surfaceGL->hasEmulatedAlphaChannel())); return angle::Result::Continue; } angle::Result TextureGL::releaseTexImage(const gl::Context *context) { ANGLE_TRY(recreateTexture(context)); return angle::Result::Continue; } angle::Result TextureGL::setEGLImageTarget(const gl::Context *context, gl::TextureType type, egl::Image *image) { const angle::FeaturesGL &features = GetFeaturesGL(context); ImageGL *imageGL = GetImplAs(image); GLenum imageNativeInternalFormat = GL_NONE; ANGLE_TRY(imageGL->setTexture2D(context, type, this, &imageNativeInternalFormat)); setLevelInfo( context, type, 0, 1, GetLevelInfo(features, image->getFormat().info->internalFormat, imageNativeInternalFormat)); return angle::Result::Continue; } GLint TextureGL::getNativeID() const { return mTextureID; } angle::Result TextureGL::syncState(const gl::Context *context, const gl::Texture::DirtyBits &dirtyBits, gl::Command source) { if (dirtyBits.none() && mLocalDirtyBits.none()) { return angle::Result::Continue; } const FunctionsGL *functions = GetFunctionsGL(context); StateManagerGL *stateManager = GetStateManagerGL(context); stateManager->bindTexture(getType(), mTextureID); gl::Texture::DirtyBits syncDirtyBits = dirtyBits | mLocalDirtyBits; if (dirtyBits[gl::Texture::DIRTY_BIT_BASE_LEVEL] || dirtyBits[gl::Texture::DIRTY_BIT_MAX_LEVEL]) { // Don't know if the previous base level was using any workarounds, always re-sync the // workaround dirty bits syncDirtyBits |= GetLevelWorkaroundDirtyBits(); } for (auto dirtyBit : syncDirtyBits) { switch (dirtyBit) { case gl::Texture::DIRTY_BIT_MIN_FILTER: mAppliedSampler.setMinFilter(mState.getSamplerState().getMinFilter()); ANGLE_GL_TRY(context, functions->texParameteri( nativegl::GetTextureBindingTarget(getType()), GL_TEXTURE_MIN_FILTER, mAppliedSampler.getMinFilter())); break; case gl::Texture::DIRTY_BIT_MAG_FILTER: mAppliedSampler.setMagFilter(mState.getSamplerState().getMagFilter()); ANGLE_GL_TRY(context, functions->texParameteri( nativegl::GetTextureBindingTarget(getType()), GL_TEXTURE_MAG_FILTER, mAppliedSampler.getMagFilter())); break; case gl::Texture::DIRTY_BIT_WRAP_S: mAppliedSampler.setWrapS(mState.getSamplerState().getWrapS()); ANGLE_GL_TRY(context, functions->texParameteri( nativegl::GetTextureBindingTarget(getType()), GL_TEXTURE_WRAP_S, mAppliedSampler.getWrapS())); break; case gl::Texture::DIRTY_BIT_WRAP_T: mAppliedSampler.setWrapT(mState.getSamplerState().getWrapT()); ANGLE_GL_TRY(context, functions->texParameteri( nativegl::GetTextureBindingTarget(getType()), GL_TEXTURE_WRAP_T, mAppliedSampler.getWrapT())); break; case gl::Texture::DIRTY_BIT_WRAP_R: mAppliedSampler.setWrapR(mState.getSamplerState().getWrapR()); ANGLE_GL_TRY(context, functions->texParameteri( nativegl::GetTextureBindingTarget(getType()), GL_TEXTURE_WRAP_R, mAppliedSampler.getWrapR())); break; case gl::Texture::DIRTY_BIT_MAX_ANISOTROPY: mAppliedSampler.setMaxAnisotropy(mState.getSamplerState().getMaxAnisotropy()); ANGLE_GL_TRY(context, functions->texParameterf(nativegl::GetTextureBindingTarget(getType()), GL_TEXTURE_MAX_ANISOTROPY_EXT, mAppliedSampler.getMaxAnisotropy())); break; case gl::Texture::DIRTY_BIT_MIN_LOD: mAppliedSampler.setMinLod(mState.getSamplerState().getMinLod()); ANGLE_GL_TRY(context, functions->texParameterf( nativegl::GetTextureBindingTarget(getType()), GL_TEXTURE_MIN_LOD, mAppliedSampler.getMinLod())); break; case gl::Texture::DIRTY_BIT_MAX_LOD: mAppliedSampler.setMaxLod(mState.getSamplerState().getMaxLod()); ANGLE_GL_TRY(context, functions->texParameterf( nativegl::GetTextureBindingTarget(getType()), GL_TEXTURE_MAX_LOD, mAppliedSampler.getMaxLod())); break; case gl::Texture::DIRTY_BIT_COMPARE_MODE: mAppliedSampler.setCompareMode(mState.getSamplerState().getCompareMode()); ANGLE_GL_TRY(context, functions->texParameteri(nativegl::GetTextureBindingTarget(getType()), GL_TEXTURE_COMPARE_MODE, mAppliedSampler.getCompareMode())); break; case gl::Texture::DIRTY_BIT_COMPARE_FUNC: mAppliedSampler.setCompareFunc(mState.getSamplerState().getCompareFunc()); ANGLE_GL_TRY(context, functions->texParameteri(nativegl::GetTextureBindingTarget(getType()), GL_TEXTURE_COMPARE_FUNC, mAppliedSampler.getCompareFunc())); break; case gl::Texture::DIRTY_BIT_SRGB_DECODE: mAppliedSampler.setSRGBDecode(mState.getSamplerState().getSRGBDecode()); ANGLE_GL_TRY(context, functions->texParameteri(nativegl::GetTextureBindingTarget(getType()), GL_TEXTURE_SRGB_DECODE_EXT, mAppliedSampler.getSRGBDecode())); break; case gl::Texture::DIRTY_BIT_BORDER_COLOR: { const angle::ColorGeneric &borderColor(mState.getSamplerState().getBorderColor()); mAppliedSampler.setBorderColor(borderColor); switch (borderColor.type) { case angle::ColorGeneric::Type::Float: ANGLE_GL_TRY(context, functions->texParameterfv( nativegl::GetTextureBindingTarget(getType()), GL_TEXTURE_BORDER_COLOR, &borderColor.colorF.red)); break; case angle::ColorGeneric::Type::Int: ANGLE_GL_TRY(context, functions->texParameterIiv( nativegl::GetTextureBindingTarget(getType()), GL_TEXTURE_BORDER_COLOR, &borderColor.colorI.red)); break; case angle::ColorGeneric::Type::UInt: ANGLE_GL_TRY(context, functions->texParameterIuiv( nativegl::GetTextureBindingTarget(getType()), GL_TEXTURE_BORDER_COLOR, &borderColor.colorUI.red)); break; default: UNREACHABLE(); break; } break; } // Texture state case gl::Texture::DIRTY_BIT_SWIZZLE_RED: ANGLE_TRY(syncTextureStateSwizzle(context, functions, GL_TEXTURE_SWIZZLE_R, mState.getSwizzleState().swizzleRed, &mAppliedSwizzle.swizzleRed)); break; case gl::Texture::DIRTY_BIT_SWIZZLE_GREEN: ANGLE_TRY(syncTextureStateSwizzle(context, functions, GL_TEXTURE_SWIZZLE_G, mState.getSwizzleState().swizzleGreen, &mAppliedSwizzle.swizzleGreen)); break; case gl::Texture::DIRTY_BIT_SWIZZLE_BLUE: ANGLE_TRY(syncTextureStateSwizzle(context, functions, GL_TEXTURE_SWIZZLE_B, mState.getSwizzleState().swizzleBlue, &mAppliedSwizzle.swizzleBlue)); break; case gl::Texture::DIRTY_BIT_SWIZZLE_ALPHA: ANGLE_TRY(syncTextureStateSwizzle(context, functions, GL_TEXTURE_SWIZZLE_A, mState.getSwizzleState().swizzleAlpha, &mAppliedSwizzle.swizzleAlpha)); break; case gl::Texture::DIRTY_BIT_BASE_LEVEL: mAppliedBaseLevel = mState.getEffectiveBaseLevel(); ANGLE_GL_TRY(context, functions->texParameteri(nativegl::GetTextureBindingTarget(getType()), GL_TEXTURE_BASE_LEVEL, mAppliedBaseLevel)); break; case gl::Texture::DIRTY_BIT_MAX_LEVEL: mAppliedMaxLevel = mState.getEffectiveMaxLevel(); ANGLE_GL_TRY(context, functions->texParameteri(nativegl::GetTextureBindingTarget(getType()), GL_TEXTURE_MAX_LEVEL, mAppliedMaxLevel)); break; case gl::Texture::DIRTY_BIT_DEPTH_STENCIL_TEXTURE_MODE: { GLenum mDepthStencilTextureMode = mState.getDepthStencilTextureMode(); ANGLE_GL_TRY(context, functions->texParameteri( nativegl::GetTextureBindingTarget(getType()), GL_DEPTH_STENCIL_TEXTURE_MODE, mDepthStencilTextureMode)); break; } case gl::Texture::DIRTY_BIT_USAGE: break; case gl::Texture::DIRTY_BIT_LABEL: break; case gl::Texture::DIRTY_BIT_IMPLEMENTATION: // This special dirty bit is used to signal the front-end that the implementation // has local dirty bits. The real dirty bits are in mLocalDirty bits. break; case gl::Texture::DIRTY_BIT_BOUND_AS_IMAGE: // Only used for Vulkan. break; default: UNREACHABLE(); } } mAllModifiedDirtyBits |= syncDirtyBits; mLocalDirtyBits.reset(); return angle::Result::Continue; } bool TextureGL::hasAnyDirtyBit() const { return mLocalDirtyBits.any(); } angle::Result TextureGL::setBaseLevel(const gl::Context *context, GLuint baseLevel) { if (baseLevel != mAppliedBaseLevel) { const FunctionsGL *functions = GetFunctionsGL(context); StateManagerGL *stateManager = GetStateManagerGL(context); mAppliedBaseLevel = baseLevel; mLocalDirtyBits.set(gl::Texture::DIRTY_BIT_BASE_LEVEL); // Signal to the GL layer that the Impl has dirty bits. onStateChange(angle::SubjectMessage::DirtyBitsFlagged); stateManager->bindTexture(getType(), mTextureID); ANGLE_GL_TRY(context, functions->texParameteri(ToGLenum(getType()), GL_TEXTURE_BASE_LEVEL, baseLevel)); } return angle::Result::Continue; } angle::Result TextureGL::setMaxLevel(const gl::Context *context, GLuint maxLevel) { if (maxLevel != mAppliedMaxLevel) { const FunctionsGL *functions = GetFunctionsGL(context); StateManagerGL *stateManager = GetStateManagerGL(context); mAppliedMaxLevel = maxLevel; mLocalDirtyBits.set(gl::Texture::DIRTY_BIT_MAX_LEVEL); // Signal to the GL layer that the Impl has dirty bits. onStateChange(angle::SubjectMessage::DirtyBitsFlagged); stateManager->bindTexture(getType(), mTextureID); ANGLE_GL_TRY(context, functions->texParameteri(ToGLenum(getType()), GL_TEXTURE_MAX_LEVEL, maxLevel)); } return angle::Result::Continue; } angle::Result TextureGL::setMinFilter(const gl::Context *context, GLenum filter) { if (mAppliedSampler.setMinFilter(filter)) { const FunctionsGL *functions = GetFunctionsGL(context); StateManagerGL *stateManager = GetStateManagerGL(context); mLocalDirtyBits.set(gl::Texture::DIRTY_BIT_MIN_FILTER); // Signal to the GL layer that the Impl has dirty bits. onStateChange(angle::SubjectMessage::DirtyBitsFlagged); stateManager->bindTexture(getType(), mTextureID); ANGLE_GL_TRY(context, functions->texParameteri(ToGLenum(getType()), GL_TEXTURE_MIN_FILTER, filter)); } return angle::Result::Continue; } angle::Result TextureGL::setMagFilter(const gl::Context *context, GLenum filter) { if (mAppliedSampler.setMagFilter(filter)) { const FunctionsGL *functions = GetFunctionsGL(context); StateManagerGL *stateManager = GetStateManagerGL(context); mLocalDirtyBits.set(gl::Texture::DIRTY_BIT_MAG_FILTER); // Signal to the GL layer that the Impl has dirty bits. onStateChange(angle::SubjectMessage::DirtyBitsFlagged); stateManager->bindTexture(getType(), mTextureID); ANGLE_GL_TRY(context, functions->texParameteri(ToGLenum(getType()), GL_TEXTURE_MAG_FILTER, filter)); } return angle::Result::Continue; } angle::Result TextureGL::setSwizzle(const gl::Context *context, GLint swizzle[4]) { gl::SwizzleState resultingSwizzle = gl::SwizzleState(swizzle[0], swizzle[1], swizzle[2], swizzle[3]); if (resultingSwizzle != mAppliedSwizzle) { const FunctionsGL *functions = GetFunctionsGL(context); StateManagerGL *stateManager = GetStateManagerGL(context); mAppliedSwizzle = resultingSwizzle; mLocalDirtyBits.set(gl::Texture::DIRTY_BIT_SWIZZLE_RED); mLocalDirtyBits.set(gl::Texture::DIRTY_BIT_SWIZZLE_GREEN); mLocalDirtyBits.set(gl::Texture::DIRTY_BIT_SWIZZLE_BLUE); mLocalDirtyBits.set(gl::Texture::DIRTY_BIT_SWIZZLE_ALPHA); // Signal to the GL layer that the Impl has dirty bits. onStateChange(angle::SubjectMessage::DirtyBitsFlagged); stateManager->bindTexture(getType(), mTextureID); if (functions->standard == STANDARD_GL_ES) { ANGLE_GL_TRY(context, functions->texParameteri(ToGLenum(getType()), GL_TEXTURE_SWIZZLE_R, swizzle[0])); ANGLE_GL_TRY(context, functions->texParameteri(ToGLenum(getType()), GL_TEXTURE_SWIZZLE_G, swizzle[1])); ANGLE_GL_TRY(context, functions->texParameteri(ToGLenum(getType()), GL_TEXTURE_SWIZZLE_B, swizzle[2])); ANGLE_GL_TRY(context, functions->texParameteri(ToGLenum(getType()), GL_TEXTURE_SWIZZLE_A, swizzle[3])); } else { ANGLE_GL_TRY(context, functions->texParameteriv(ToGLenum(getType()), GL_TEXTURE_SWIZZLE_RGBA, swizzle)); } } return angle::Result::Continue; } angle::Result TextureGL::setBuffer(const gl::Context *context, GLenum internalFormat) { const FunctionsGL *functions = GetFunctionsGL(context); const gl::OffsetBindingPointer &bufferBinding = mState.getBuffer(); const gl::Buffer *buffer = bufferBinding.get(); const GLintptr offset = bufferBinding.getOffset(); const GLsizeiptr size = bufferBinding.getSize(); const GLuint bufferID = buffer ? GetImplAs(buffer)->getBufferID() : 0; // If buffer is not bound, use texBuffer to unbind it. If size is 0, texBuffer was used to // create this binding, so use the same function. This will allow the implementation to take // the current size of the buffer on every draw/dispatch call even if the buffer size changes. if (buffer == nullptr || size == 0) { ANGLE_GL_TRY(context, functions->texBuffer(GL_TEXTURE_BUFFER, internalFormat, bufferID)); } else { ANGLE_GL_TRY(context, functions->texBufferRange(GL_TEXTURE_BUFFER, internalFormat, bufferID, offset, GetBoundBufferAvailableSize(bufferBinding))); } return angle::Result::Continue; } GLenum TextureGL::getNativeInternalFormat(const gl::ImageIndex &index) const { return getLevelInfo(index.getTarget(), index.getLevelIndex()).nativeInternalFormat; } bool TextureGL::hasEmulatedAlphaChannel(const gl::ImageIndex &index) const { return getLevelInfo(index.getTargetOrFirstCubeFace(), index.getLevelIndex()) .emulatedAlphaChannel; } angle::Result TextureGL::recreateTexture(const gl::Context *context) { const FunctionsGL *functions = GetFunctionsGL(context); StateManagerGL *stateManager = GetStateManagerGL(context); stateManager->bindTexture(getType(), mTextureID); stateManager->deleteTexture(mTextureID); functions->genTextures(1, &mTextureID); stateManager->bindTexture(getType(), mTextureID); mLevelInfo.clear(); mLevelInfo.resize(GetMaxLevelInfoCountForTextureType(getType())); mAppliedSwizzle = gl::SwizzleState(); mAppliedSampler = gl::SamplerState::CreateDefaultForTarget(getType()); mAppliedBaseLevel = 0; mAppliedBaseLevel = gl::kInitialMaxLevel; mLocalDirtyBits = mAllModifiedDirtyBits; onStateChange(angle::SubjectMessage::SubjectChanged); return angle::Result::Continue; } angle::Result TextureGL::syncTextureStateSwizzle(const gl::Context *context, const FunctionsGL *functions, GLenum name, GLenum value, GLenum *outValue) { const LevelInfoGL &levelInfo = getBaseLevelInfo(); GLenum resultSwizzle = value; if (levelInfo.lumaWorkaround.enabled) { switch (value) { case GL_RED: case GL_GREEN: case GL_BLUE: if (levelInfo.sourceFormat == GL_LUMINANCE || levelInfo.sourceFormat == GL_LUMINANCE_ALPHA) { // Texture is backed by a RED or RG texture, point all color channels at the // red channel. ASSERT(levelInfo.lumaWorkaround.workaroundFormat == GL_RED || levelInfo.lumaWorkaround.workaroundFormat == GL_RG); resultSwizzle = GL_RED; } else { ASSERT(levelInfo.sourceFormat == GL_ALPHA); // Color channels are not supposed to exist, make them always sample 0. resultSwizzle = GL_ZERO; } break; case GL_ALPHA: if (levelInfo.sourceFormat == GL_LUMINANCE) { // Alpha channel is not supposed to exist, make it always sample 1. resultSwizzle = GL_ONE; } else if (levelInfo.sourceFormat == GL_ALPHA) { // Texture is backed by a RED texture, point the alpha channel at the red // channel. ASSERT(levelInfo.lumaWorkaround.workaroundFormat == GL_RED); resultSwizzle = GL_RED; } else { ASSERT(levelInfo.sourceFormat == GL_LUMINANCE_ALPHA); // Texture is backed by an RG texture, point the alpha channel at the green // channel. ASSERT(levelInfo.lumaWorkaround.workaroundFormat == GL_RG); resultSwizzle = GL_GREEN; } break; case GL_ZERO: case GL_ONE: // Don't modify the swizzle state when requesting ZERO or ONE. resultSwizzle = value; break; default: UNREACHABLE(); break; } } else if (levelInfo.depthStencilWorkaround) { switch (value) { case GL_RED: // Don't modify the swizzle state when requesting the red channel. resultSwizzle = value; break; case GL_GREEN: case GL_BLUE: if (context->getClientMajorVersion() <= 2) { // In OES_depth_texture/ARB_depth_texture, depth // textures are treated as luminance. resultSwizzle = GL_RED; } else { // In GLES 3.0, depth textures are treated as RED // textures, so green and blue should be 0. resultSwizzle = GL_ZERO; } break; case GL_ALPHA: // Depth textures should sample 1 from the alpha channel. resultSwizzle = GL_ONE; break; case GL_ZERO: case GL_ONE: // Don't modify the swizzle state when requesting ZERO or ONE. resultSwizzle = value; break; default: UNREACHABLE(); break; } } else if (levelInfo.emulatedAlphaChannel) { if (value == GL_ALPHA) { resultSwizzle = GL_ONE; } } *outValue = resultSwizzle; ANGLE_GL_TRY(context, functions->texParameteri(ToGLenum(getType()), name, resultSwizzle)); return angle::Result::Continue; } void TextureGL::setLevelInfo(const gl::Context *context, gl::TextureTarget target, size_t level, size_t levelCount, const LevelInfoGL &levelInfo) { ASSERT(levelCount > 0); bool updateWorkarounds = levelInfo.depthStencilWorkaround || levelInfo.lumaWorkaround.enabled || levelInfo.emulatedAlphaChannel; for (size_t i = level; i < level + levelCount; i++) { size_t index = GetLevelInfoIndex(target, i); ASSERT(index < mLevelInfo.size()); auto &curLevelInfo = mLevelInfo[index]; updateWorkarounds |= curLevelInfo.depthStencilWorkaround; updateWorkarounds |= curLevelInfo.lumaWorkaround.enabled; updateWorkarounds |= curLevelInfo.emulatedAlphaChannel; curLevelInfo = levelInfo; } if (updateWorkarounds) { mLocalDirtyBits |= GetLevelWorkaroundDirtyBits(); onStateChange(angle::SubjectMessage::DirtyBitsFlagged); } } void TextureGL::setLevelInfo(const gl::Context *context, gl::TextureType type, size_t level, size_t levelCount, const LevelInfoGL &levelInfo) { if (type == gl::TextureType::CubeMap) { for (gl::TextureTarget target : gl::AllCubeFaceTextureTargets()) { setLevelInfo(context, target, level, levelCount, levelInfo); } } else { setLevelInfo(context, NonCubeTextureTypeToTarget(type), level, levelCount, levelInfo); } } const LevelInfoGL &TextureGL::getLevelInfo(gl::TextureTarget target, size_t level) const { return mLevelInfo[GetLevelInfoIndex(target, level)]; } const LevelInfoGL &TextureGL::getBaseLevelInfo() const { GLint effectiveBaseLevel = mState.getEffectiveBaseLevel(); gl::TextureTarget target = getType() == gl::TextureType::CubeMap ? gl::kCubeMapTextureTargetMin : gl::NonCubeTextureTypeToTarget(getType()); return getLevelInfo(target, effectiveBaseLevel); } gl::TextureType TextureGL::getType() const { return mState.getType(); } angle::Result TextureGL::initializeContents(const gl::Context *context, const gl::ImageIndex &imageIndex) { ContextGL *contextGL = GetImplAs(context); const FunctionsGL *functions = GetFunctionsGL(context); StateManagerGL *stateManager = GetStateManagerGL(context); const angle::FeaturesGL &features = GetFeaturesGL(context); bool shouldUseClear = !nativegl::SupportsTexImage(getType()); GLenum nativeInternalFormat = getLevelInfo(imageIndex.getTarget(), imageIndex.getLevelIndex()).nativeInternalFormat; if ((features.allowClearForRobustResourceInit.enabled || shouldUseClear) && nativegl::SupportsNativeRendering(functions, mState.getType(), nativeInternalFormat)) { BlitGL *blitter = GetBlitGL(context); int levelDepth = mState.getImageDesc(imageIndex).size.depth; bool clearSucceeded = false; ANGLE_TRY(blitter->clearRenderableTexture(context, this, nativeInternalFormat, levelDepth, imageIndex, &clearSucceeded)); if (clearSucceeded) { return angle::Result::Continue; } } // Either the texture is not renderable or was incomplete when clearing, fall back to a data // upload ASSERT(nativegl::SupportsTexImage(getType())); const gl::ImageDesc &desc = mState.getImageDesc(imageIndex); const gl::InternalFormat &internalFormatInfo = *desc.format.info; gl::PixelUnpackState unpackState; unpackState.alignment = 1; ANGLE_TRY(stateManager->setPixelUnpackState(context, unpackState)); GLuint prevUnpackBuffer = stateManager->getBufferID(gl::BufferBinding::PixelUnpack); stateManager->bindBuffer(gl::BufferBinding::PixelUnpack, 0); stateManager->bindTexture(getType(), mTextureID); if (internalFormatInfo.compressed) { nativegl::CompressedTexSubImageFormat nativeSubImageFormat = nativegl::GetCompressedSubTexImageFormat(functions, features, internalFormatInfo.internalFormat); GLuint imageSize = 0; ANGLE_CHECK_GL_MATH(contextGL, internalFormatInfo.computeCompressedImageSize(desc.size, &imageSize)); angle::MemoryBuffer *zero; ANGLE_CHECK_GL_ALLOC(contextGL, context->getZeroFilledBuffer(imageSize, &zero)); // WebGL spec requires that zero data is uploaded to compressed textures even if it might // not result in zero color data. if (nativegl::UseTexImage2D(getType())) { ANGLE_GL_TRY(context, functions->compressedTexSubImage2D( ToGLenum(imageIndex.getTarget()), imageIndex.getLevelIndex(), 0, 0, desc.size.width, desc.size.height, nativeSubImageFormat.format, imageSize, zero->data())); } else { ASSERT(nativegl::UseTexImage3D(getType())); ANGLE_GL_TRY(context, functions->compressedTexSubImage3D( ToGLenum(imageIndex.getTarget()), imageIndex.getLevelIndex(), 0, 0, 0, desc.size.width, desc.size.height, desc.size.depth, nativeSubImageFormat.format, imageSize, zero->data())); } } else { nativegl::TexSubImageFormat nativeSubImageFormat = nativegl::GetTexSubImageFormat( functions, features, internalFormatInfo.format, internalFormatInfo.type); GLuint imageSize = 0; ANGLE_CHECK_GL_MATH(contextGL, internalFormatInfo.computePackUnpackEndByte( nativeSubImageFormat.type, desc.size, unpackState, nativegl::UseTexImage3D(getType()), &imageSize)); angle::MemoryBuffer *zero; ANGLE_CHECK_GL_ALLOC(contextGL, context->getZeroFilledBuffer(imageSize, &zero)); if (nativegl::UseTexImage2D(getType())) { if (features.uploadTextureDataInChunks.enabled) { gl::Box area(0, 0, 0, desc.size.width, desc.size.height, 1); ANGLE_TRY(setSubImageRowByRowWorkaround( context, imageIndex.getTarget(), imageIndex.getLevelIndex(), area, nativeSubImageFormat.format, nativeSubImageFormat.type, unpackState, nullptr, angle::FeaturesGL::kUploadTextureDataInChunksUploadSize, zero->data())); } else { ANGLE_GL_TRY(context, functions->texSubImage2D( ToGLenum(imageIndex.getTarget()), imageIndex.getLevelIndex(), 0, 0, desc.size.width, desc.size.height, nativeSubImageFormat.format, nativeSubImageFormat.type, zero->data())); } } else { ASSERT(nativegl::UseTexImage3D(getType())); ANGLE_GL_TRY(context, functions->texSubImage3D( ToGLenum(imageIndex.getTarget()), imageIndex.getLevelIndex(), 0, 0, 0, desc.size.width, desc.size.height, desc.size.depth, nativeSubImageFormat.format, nativeSubImageFormat.type, zero->data())); } } // Reset the pixel unpack state. Because this call is made after synchronizing dirty bits in a // glTexImage call, we need to make sure that the texture data to be uploaded later has the // expected unpack state. ANGLE_TRY(stateManager->setPixelUnpackState(context, context->getState().getUnpackState())); stateManager->bindBuffer(gl::BufferBinding::PixelUnpack, prevUnpackBuffer); return angle::Result::Continue; } GLint TextureGL::getRequiredExternalTextureImageUnits(const gl::Context *context) { const FunctionsGL *functions = GetFunctionsGL(context); StateManagerGL *stateManager = GetStateManagerGL(context); ASSERT(getType() == gl::TextureType::External); stateManager->bindTexture(getType(), mTextureID); GLint result = 0; functions->getTexParameteriv(ToGLenum(gl::NonCubeTextureTypeToTarget(getType())), GL_REQUIRED_TEXTURE_IMAGE_UNITS_OES, &result); return result; } } // namespace rx