// // Copyright 2019 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. // // TextureMtl.mm: // Implements the class methods for TextureMtl. // #include "libANGLE/renderer/metal/TextureMtl.h" #include "common/Color.h" #include "common/MemoryBuffer.h" #include "common/debug.h" #include "common/mathutil.h" #include "image_util/imageformats.h" #include "libANGLE/Surface.h" #include "libANGLE/renderer/metal/BufferMtl.h" #include "libANGLE/renderer/metal/ContextMtl.h" #include "libANGLE/renderer/metal/DisplayMtl.h" #include "libANGLE/renderer/metal/FrameBufferMtl.h" #include "libANGLE/renderer/metal/ImageMtl.h" #include "libANGLE/renderer/metal/SamplerMtl.h" #include "libANGLE/renderer/metal/SurfaceMtl.h" #include "libANGLE/renderer/metal/mtl_common.h" #include "libANGLE/renderer/metal/mtl_format_utils.h" #include "libANGLE/renderer/metal/mtl_utils.h" #include "libANGLE/renderer/renderer_utils.h" namespace rx { namespace { gl::ImageIndex GetZeroLevelIndex(const mtl::TextureRef &image) { switch (image->textureType()) { case MTLTextureType2D: return gl::ImageIndex::Make2D(0); case MTLTextureTypeCube: return gl::ImageIndex::MakeFromType(gl::TextureType::CubeMap, 0); case MTLTextureType2DArray: return gl::ImageIndex::Make2DArray(0 /** entire layers */); case MTLTextureType2DMultisample: return gl::ImageIndex::Make2DMultisample(); case MTLTextureType3D: return gl::ImageIndex::Make3D(0 /** entire layers */); default: UNREACHABLE(); break; } return gl::ImageIndex(); } // Slice is ignored if texture type is not Cube or 2D array gl::ImageIndex GetCubeOrArraySliceMipIndex(const mtl::TextureRef &image, uint32_t slice, uint32_t level) { switch (image->textureType()) { case MTLTextureType2D: return gl::ImageIndex::Make2D(level); case MTLTextureTypeCube: { auto cubeFace = static_cast( static_cast(gl::TextureTarget::CubeMapPositiveX) + slice); return gl::ImageIndex::MakeCubeMapFace(cubeFace, level); } case MTLTextureType2DArray: return gl::ImageIndex::Make2DArray(level, slice); case MTLTextureType2DMultisample: return gl::ImageIndex::Make2DMultisample(); case MTLTextureType3D: return gl::ImageIndex::Make3D(level); default: UNREACHABLE(); break; } return gl::ImageIndex(); } // layer is ignored if texture type is not Cube or 2D array or 3D gl::ImageIndex GetLayerMipIndex(const mtl::TextureRef &image, uint32_t layer, uint32_t level) { switch (image->textureType()) { case MTLTextureType2D: return gl::ImageIndex::Make2D(level); case MTLTextureTypeCube: { auto cubeFace = static_cast( static_cast(gl::TextureTarget::CubeMapPositiveX) + layer); return gl::ImageIndex::MakeCubeMapFace(cubeFace, level); } case MTLTextureType2DArray: return gl::ImageIndex::Make2DArray(level, layer); case MTLTextureType2DMultisample: return gl::ImageIndex::Make2DMultisample(); case MTLTextureType3D: return gl::ImageIndex::Make3D(level, layer); default: UNREACHABLE(); break; } return gl::ImageIndex(); } GLuint GetImageLayerIndexFrom(const gl::ImageIndex &index) { switch (index.getType()) { case gl::TextureType::_2D: case gl::TextureType::_2DMultisample: case gl::TextureType::Rectangle: return 0; case gl::TextureType::CubeMap: return index.cubeMapFaceIndex(); case gl::TextureType::_2DArray: case gl::TextureType::_3D: return index.getLayerIndex(); default: UNREACHABLE(); } return 0; } GLuint GetImageCubeFaceIndexOrZeroFrom(const gl::ImageIndex &index) { switch (index.getType()) { case gl::TextureType::CubeMap: return index.cubeMapFaceIndex(); default: break; } return 0; } // Given texture type, get texture type of one image for a glTexImage call. // For example, for texture 2d, one image is also texture 2d. // for texture cube, one image is texture 2d. gl::TextureType GetTextureImageType(gl::TextureType texType) { switch (texType) { case gl::TextureType::CubeMap: return gl::TextureType::_2D; case gl::TextureType::_2D: case gl::TextureType::_2DArray: case gl::TextureType::_2DMultisample: case gl::TextureType::_3D: case gl::TextureType::Rectangle: return texType; default: UNREACHABLE(); return gl::TextureType::InvalidEnum; } } // D24X8 by default writes depth data to high 24 bits of 32 bit integers. However, Metal separate // depth stencil blitting expects depth data to be in low 24 bits of the data. void WriteDepthStencilToDepth24(const uint8_t *srcPtr, uint8_t *dstPtr) { auto src = reinterpret_cast(srcPtr); auto dst = reinterpret_cast(dstPtr); *dst = gl::floatToNormalized<24, uint32_t>(static_cast(src->depth)); } #if TARGET_OS_SIMULATOR void CopyTextureData(const MTLSize ®ionSize, size_t srcRowPitch, size_t src2DImageSize, const uint8_t *psrc, size_t destRowPitch, size_t dest2DImageSize, uint8_t *pdst) { { size_t rowCopySize = std::min(srcRowPitch, destRowPitch); for (NSUInteger d = 0; d < regionSize.depth; ++d) { for (NSUInteger r = 0; r < regionSize.height; ++r) { const uint8_t *pCopySrc = psrc + d * src2DImageSize + r * srcRowPitch; uint8_t *pCopyDst = pdst + d * dest2DImageSize + r * destRowPitch; memcpy(pCopyDst, pCopySrc, rowCopySize); } } } } #endif // TARGET_OS_SIMULATOR void ConvertDepthStencilData(const MTLSize ®ionSize, const angle::Format &srcAngleFormat, size_t srcRowPitch, size_t src2DImageSize, const uint8_t *psrc, const angle::Format &dstAngleFormat, rx::PixelWriteFunction pixelWriteFunctionOverride, size_t destRowPitch, size_t dest2DImageSize, uint8_t *pdst) { if (srcAngleFormat.id == dstAngleFormat.id) { size_t rowCopySize = std::min(srcRowPitch, destRowPitch); for (NSUInteger d = 0; d < regionSize.depth; ++d) { for (NSUInteger r = 0; r < regionSize.height; ++r) { const uint8_t *pCopySrc = psrc + d * src2DImageSize + r * srcRowPitch; uint8_t *pCopyDst = pdst + d * dest2DImageSize + r * destRowPitch; memcpy(pCopyDst, pCopySrc, rowCopySize); } } } else { rx::PixelWriteFunction pixelWriteFunction = pixelWriteFunctionOverride ? pixelWriteFunctionOverride : dstAngleFormat.pixelWriteFunction; // This is only for depth & stencil case. ASSERT(srcAngleFormat.depthBits || srcAngleFormat.stencilBits); ASSERT(srcAngleFormat.pixelReadFunction && pixelWriteFunction); // cache to store read result of source pixel angle::DepthStencil depthStencilData; auto sourcePixelReadData = reinterpret_cast(&depthStencilData); ASSERT(srcAngleFormat.pixelBytes <= sizeof(depthStencilData)); for (NSUInteger d = 0; d < regionSize.depth; ++d) { for (NSUInteger r = 0; r < regionSize.height; ++r) { for (NSUInteger c = 0; c < regionSize.width; ++c) { const uint8_t *sourcePixelData = psrc + d * src2DImageSize + r * srcRowPitch + c * srcAngleFormat.pixelBytes; uint8_t *destPixelData = pdst + d * dest2DImageSize + r * destRowPitch + c * dstAngleFormat.pixelBytes; srcAngleFormat.pixelReadFunction(sourcePixelData, sourcePixelReadData); pixelWriteFunction(sourcePixelReadData, destPixelData); } } } } } angle::Result CopyDepthStencilTextureContentsToStagingBuffer( ContextMtl *contextMtl, const angle::Format &textureAngleFormat, const angle::Format &stagingAngleFormat, rx::PixelWriteFunction pixelWriteFunctionOverride, const MTLSize ®ionSize, const uint8_t *data, size_t bytesPerRow, size_t bytesPer2DImage, size_t *bufferRowPitchOut, size_t *buffer2DImageSizeOut, mtl::BufferRef *bufferOut) { size_t stagingBufferRowPitch = regionSize.width * stagingAngleFormat.pixelBytes; size_t stagingBuffer2DImageSize = stagingBufferRowPitch * regionSize.height; size_t stagingBufferSize = stagingBuffer2DImageSize * regionSize.depth; mtl::BufferRef stagingBuffer; ANGLE_TRY(mtl::Buffer::MakeBuffer(contextMtl, stagingBufferSize, nullptr, &stagingBuffer)); uint8_t *pdst = stagingBuffer->map(contextMtl); ConvertDepthStencilData(regionSize, textureAngleFormat, bytesPerRow, bytesPer2DImage, data, stagingAngleFormat, pixelWriteFunctionOverride, stagingBufferRowPitch, stagingBuffer2DImageSize, pdst); stagingBuffer->unmap(contextMtl); *bufferOut = stagingBuffer; *bufferRowPitchOut = stagingBufferRowPitch; *buffer2DImageSizeOut = stagingBuffer2DImageSize; return angle::Result::Continue; } #if TARGET_OS_SIMULATOR angle::Result CopyTextureContentsToStagingBuffer(ContextMtl *contextMtl, const angle::Format &textureAngleFormat, const MTLSize ®ionSize, const uint8_t *data, size_t bytesPerRow, size_t bytesPer2DImage, size_t *bufferRowPitchOut, size_t *buffer2DImageSizeOut, mtl::BufferRef *bufferOut) { size_t stagingBufferRowPitch = regionSize.width * textureAngleFormat.pixelBytes; size_t stagingBuffer2DImageSize = stagingBufferRowPitch * regionSize.height; size_t stagingBufferSize = stagingBuffer2DImageSize * regionSize.depth; mtl::BufferRef stagingBuffer; ANGLE_TRY(mtl::Buffer::MakeBuffer(contextMtl, stagingBufferSize, nullptr, &stagingBuffer)); uint8_t *pdst = stagingBuffer->map(contextMtl); CopyTextureData(regionSize, bytesPerRow, bytesPer2DImage, data, stagingBufferRowPitch, stagingBuffer2DImageSize, pdst); stagingBuffer->unmap(contextMtl); *bufferOut = stagingBuffer; *bufferRowPitchOut = stagingBufferRowPitch; *buffer2DImageSizeOut = stagingBuffer2DImageSize; return angle::Result::Continue; } angle::Result CopyCompressedTextureContentsToStagingBuffer(ContextMtl *contextMtl, const angle::Format &textureAngleFormat, const MTLSize ®ionSizeInBlocks, const uint8_t *data, size_t bytesPerBlockRow, size_t bytesPer2DImage, size_t *bufferRowPitchOut, size_t *buffer2DImageSizeOut, mtl::BufferRef *bufferOut) { size_t stagingBufferRowPitch = bytesPerBlockRow; size_t stagingBuffer2DImageSize = bytesPer2DImage; size_t stagingBufferSize = stagingBuffer2DImageSize * regionSizeInBlocks.depth; mtl::BufferRef stagingBuffer; ANGLE_TRY(mtl::Buffer::MakeBuffer(contextMtl, stagingBufferSize, nullptr, &stagingBuffer)); uint8_t *pdst = stagingBuffer->map(contextMtl); CopyTextureData(regionSizeInBlocks, bytesPerBlockRow, bytesPer2DImage, data, stagingBufferRowPitch, stagingBuffer2DImageSize, pdst); stagingBuffer->unmap(contextMtl); *bufferOut = stagingBuffer; *bufferRowPitchOut = stagingBufferRowPitch; *buffer2DImageSizeOut = stagingBuffer2DImageSize; return angle::Result::Continue; } #endif angle::Result UploadDepthStencilTextureContentsWithStagingBuffer( ContextMtl *contextMtl, const angle::Format &textureAngleFormat, MTLRegion region, const mtl::MipmapNativeLevel &mipmapLevel, uint32_t slice, const uint8_t *data, size_t bytesPerRow, size_t bytesPer2DImage, const mtl::TextureRef &texture) { ASSERT(texture && texture->valid()); ASSERT(!texture->isCPUAccessible()); ASSERT(!textureAngleFormat.depthBits || !textureAngleFormat.stencilBits); // Compressed texture is not supporte atm ASSERT(!textureAngleFormat.isBlock); // Copy data to staging buffer size_t stagingBufferRowPitch; size_t stagingBuffer2DImageSize; mtl::BufferRef stagingBuffer; ANGLE_TRY(CopyDepthStencilTextureContentsToStagingBuffer( contextMtl, textureAngleFormat, textureAngleFormat, textureAngleFormat.pixelWriteFunction, region.size, data, bytesPerRow, bytesPer2DImage, &stagingBufferRowPitch, &stagingBuffer2DImageSize, &stagingBuffer)); // Copy staging buffer to texture. mtl::BlitCommandEncoder *encoder = contextMtl->getBlitCommandEncoder(); encoder->copyBufferToTexture(stagingBuffer, 0, stagingBufferRowPitch, stagingBuffer2DImageSize, region.size, texture, slice, mipmapLevel, region.origin, MTLBlitOptionNone); return angle::Result::Continue; } // Packed depth stencil upload using staging buffer angle::Result UploadPackedDepthStencilTextureContentsWithStagingBuffer( ContextMtl *contextMtl, const angle::Format &textureAngleFormat, MTLRegion region, const mtl::MipmapNativeLevel &mipmapLevel, uint32_t slice, const uint8_t *data, size_t bytesPerRow, size_t bytesPer2DImage, const mtl::TextureRef &texture) { ASSERT(texture && texture->valid()); ASSERT(!texture->isCPUAccessible()); ASSERT(textureAngleFormat.depthBits && textureAngleFormat.stencilBits); // We have to split the depth & stencil data into 2 buffers. angle::FormatID stagingDepthBufferFormatId; angle::FormatID stagingStencilBufferFormatId; // Custom depth write function. We cannot use those in imageformats.cpp since Metal has some // special cases. rx::PixelWriteFunction stagingDepthBufferWriteFunctionOverride = nullptr; switch (textureAngleFormat.id) { case angle::FormatID::D24_UNORM_S8_UINT: // D24_UNORM_X8_UINT writes depth data to high 24 bits. But Metal expects depth data to // be in low 24 bits. stagingDepthBufferFormatId = angle::FormatID::D24_UNORM_X8_UINT; stagingDepthBufferWriteFunctionOverride = WriteDepthStencilToDepth24; stagingStencilBufferFormatId = angle::FormatID::S8_UINT; break; case angle::FormatID::D32_FLOAT_S8X24_UINT: stagingDepthBufferFormatId = angle::FormatID::D32_FLOAT; stagingStencilBufferFormatId = angle::FormatID::S8_UINT; break; default: ANGLE_MTL_UNREACHABLE(contextMtl); } const angle::Format &angleStagingDepthFormat = angle::Format::Get(stagingDepthBufferFormatId); const angle::Format &angleStagingStencilFormat = angle::Format::Get(stagingStencilBufferFormatId); size_t stagingDepthBufferRowPitch, stagingStencilBufferRowPitch; size_t stagingDepthBuffer2DImageSize, stagingStencilBuffer2DImageSize; mtl::BufferRef stagingDepthbuffer, stagingStencilBuffer; // Copy depth data to staging depth buffer ANGLE_TRY(CopyDepthStencilTextureContentsToStagingBuffer( contextMtl, textureAngleFormat, angleStagingDepthFormat, stagingDepthBufferWriteFunctionOverride, region.size, data, bytesPerRow, bytesPer2DImage, &stagingDepthBufferRowPitch, &stagingDepthBuffer2DImageSize, &stagingDepthbuffer)); // Copy stencil data to staging stencil buffer ANGLE_TRY(CopyDepthStencilTextureContentsToStagingBuffer( contextMtl, textureAngleFormat, angleStagingStencilFormat, nullptr, region.size, data, bytesPerRow, bytesPer2DImage, &stagingStencilBufferRowPitch, &stagingStencilBuffer2DImageSize, &stagingStencilBuffer)); mtl::BlitCommandEncoder *encoder = contextMtl->getBlitCommandEncoder(); encoder->copyBufferToTexture(stagingDepthbuffer, 0, stagingDepthBufferRowPitch, stagingDepthBuffer2DImageSize, region.size, texture, slice, mipmapLevel, region.origin, MTLBlitOptionDepthFromDepthStencil); encoder->copyBufferToTexture(stagingStencilBuffer, 0, stagingStencilBufferRowPitch, stagingStencilBuffer2DImageSize, region.size, texture, slice, mipmapLevel, region.origin, MTLBlitOptionStencilFromDepthStencil); return angle::Result::Continue; } #if TARGET_OS_SIMULATOR angle::Result UploadTextureContentsWithStagingBuffer(ContextMtl *contextMtl, const angle::Format &textureAngleFormat, MTLRegion region, const mtl::MipmapNativeLevel &mipmapLevel, uint32_t slice, const uint8_t *data, size_t bytesPerRow, size_t bytesPer2DImage, const mtl::TextureRef &texture) { ASSERT(texture && texture->valid()); angle::FormatID stagingBufferFormatID = textureAngleFormat.id; const angle::Format &angleStagingFormat = angle::Format::Get(stagingBufferFormatID); size_t stagingBufferRowPitch; size_t stagingBuffer2DImageSize; mtl::BufferRef stagingBuffer; // Block-compressed formats need a bit of massaging for copy. if (textureAngleFormat.isBlock) { GLenum internalFormat = textureAngleFormat.glInternalFormat; const gl::InternalFormat &fmt = gl::GetSizedInternalFormatInfo(internalFormat); MTLRegion newRegion = region; bytesPerRow = (region.size.width + fmt.compressedBlockWidth - 1) / fmt.compressedBlockWidth * 16; bytesPer2DImage = (region.size.height + fmt.compressedBlockHeight - 1) / fmt.compressedBlockHeight * bytesPerRow; newRegion.size.width = (region.size.width + fmt.compressedBlockWidth - 1) / fmt.compressedBlockWidth; newRegion.size.height = (region.size.height + fmt.compressedBlockHeight - 1) / fmt.compressedBlockHeight; ANGLE_TRY(CopyCompressedTextureContentsToStagingBuffer( contextMtl, angleStagingFormat, newRegion.size, data, bytesPerRow, bytesPer2DImage, &stagingBufferRowPitch, &stagingBuffer2DImageSize, &stagingBuffer)); } // Copy to staging buffer before uploading to texture. else { ANGLE_TRY(CopyTextureContentsToStagingBuffer( contextMtl, angleStagingFormat, region.size, data, bytesPerRow, bytesPer2DImage, &stagingBufferRowPitch, &stagingBuffer2DImageSize, &stagingBuffer)); } mtl::BlitCommandEncoder *encoder = contextMtl->getBlitCommandEncoder(); encoder->copyBufferToTexture(stagingBuffer, 0, stagingBufferRowPitch, stagingBuffer2DImageSize, region.size, texture, slice, mipmapLevel, region.origin, 0); return angle::Result::Continue; } #endif // TARGET_OS_SIMULATOR angle::Result UploadTextureContents(const gl::Context *context, const angle::Format &textureAngleFormat, const MTLRegion ®ion, const mtl::MipmapNativeLevel &mipmapLevel, uint32_t slice, const uint8_t *data, size_t bytesPerRow, size_t bytesPer2DImage, const mtl::TextureRef &texture) { ASSERT(texture && texture->valid()); ContextMtl *contextMtl = mtl::GetImpl(context); #if TARGET_OS_SIMULATOR if (!textureAngleFormat.depthBits && !textureAngleFormat.stencilBits) { ANGLE_TRY(UploadTextureContentsWithStagingBuffer(contextMtl, textureAngleFormat, region, mipmapLevel, slice, data, bytesPerRow, bytesPer2DImage, texture)); return angle::Result::Continue; } #else if (texture->isCPUAccessible()) { // If texture is CPU accessible, just call replaceRegion() directly. texture->replaceRegion(contextMtl, region, mipmapLevel, slice, data, bytesPerRow, bytesPer2DImage); return angle::Result::Continue; } #endif // TARGET_OS_SIMULATOR ASSERT(textureAngleFormat.depthBits || textureAngleFormat.stencilBits); // Texture is not CPU accessible, we need to use staging buffer if (textureAngleFormat.depthBits && textureAngleFormat.stencilBits) { ANGLE_TRY(UploadPackedDepthStencilTextureContentsWithStagingBuffer( contextMtl, textureAngleFormat, region, mipmapLevel, slice, data, bytesPerRow, bytesPer2DImage, texture)); } else { ANGLE_TRY(UploadDepthStencilTextureContentsWithStagingBuffer( contextMtl, textureAngleFormat, region, mipmapLevel, slice, data, bytesPerRow, bytesPer2DImage, texture)); } return angle::Result::Continue; } // This might be unused on platform not supporting swizzle. ANGLE_MTL_UNUSED GLenum OverrideSwizzleValue(const gl::Context *context, GLenum swizzle, const mtl::Format &format, const gl::InternalFormat &glInternalFormat) { if (format.actualAngleFormat().depthBits) { ASSERT(!format.swizzled); if (context->getState().getClientMajorVersion() >= 3 && glInternalFormat.sized) { // ES 3.0 spec: treat depth texture as red texture during sampling. if (swizzle == GL_GREEN || swizzle == GL_BLUE) { return GL_NONE; } else if (swizzle == GL_ALPHA) { return GL_ONE; } } else { // https://www.khronos.org/registry/OpenGL/extensions/OES/OES_depth_texture.txt // Treat depth texture as luminance texture during sampling. if (swizzle == GL_GREEN || swizzle == GL_BLUE) { return GL_RED; } else if (swizzle == GL_ALPHA) { return GL_ONE; } } } else if (format.swizzled) { // Combine the swizzles switch (swizzle) { case GL_RED: return format.swizzle[0]; case GL_GREEN: return format.swizzle[1]; case GL_BLUE: return format.swizzle[2]; case GL_ALPHA: return format.swizzle[3]; default: break; } } return swizzle; } } // namespace // TextureMtl implementation TextureMtl::TextureMtl(const gl::TextureState &state) : TextureImpl(state) {} TextureMtl::~TextureMtl() = default; void TextureMtl::onDestroy(const gl::Context *context) { releaseTexture(true); } void TextureMtl::releaseTexture(bool releaseImages) { releaseTexture(releaseImages, false); } void TextureMtl::releaseTexture(bool releaseImages, bool releaseTextureObjectsOnly) { if (releaseImages) { mTexImageDefs.clear(); } else if (mNativeTexture) { // Release native texture but keep its old per face per mipmap level image views. retainImageDefinitions(); } mNativeTexture = nullptr; mNativeSwizzleSamplingView = nullptr; // Clear render target cache for each texture's image. We don't erase them because they // might still be referenced by a framebuffer. for (auto &sliceRenderTargets : mPerLayerRenderTargets) { for (RenderTargetMtl &mipRenderTarget : sliceRenderTargets.second) { mipRenderTarget.reset(); } } for (mtl::TextureRef &view : mNativeLevelViews) { view.reset(); } if (!releaseTextureObjectsOnly) { mMetalSamplerState = nil; mFormat = mtl::Format(); mIsPow2 = false; } } angle::Result TextureMtl::ensureTextureCreated(const gl::Context *context) { if (mNativeTexture) { return angle::Result::Continue; } ContextMtl *contextMtl = mtl::GetImpl(context); // Create actual texture object: mCurrentBaseLevel = mState.getEffectiveBaseLevel(); const GLuint mips = mState.getMipmapMaxLevel() - mCurrentBaseLevel + 1; gl::ImageDesc desc = mState.getBaseLevelDesc(); ANGLE_MTL_CHECK(contextMtl, desc.format.valid(), GL_INVALID_OPERATION); angle::FormatID angleFormatId = angle::Format::InternalFormatToID(desc.format.info->sizedInternalFormat); mFormat = contextMtl->getPixelFormat(angleFormatId); return createNativeTexture(context, mState.getType(), mips, desc.size); } angle::Result TextureMtl::createNativeTexture(const gl::Context *context, gl::TextureType type, GLuint mips, const gl::Extents &size) { ContextMtl *contextMtl = mtl::GetImpl(context); // Create actual texture object: mCurrentBaseLevel = mState.getEffectiveBaseLevel(); mCurrentMaxLevel = mState.getEffectiveMaxLevel(); mIsPow2 = gl::isPow2(size.width) && gl::isPow2(size.height) && gl::isPow2(size.depth); mSlices = 1; int numCubeFaces = 1; switch (type) { case gl::TextureType::_2D: ANGLE_TRY(mtl::Texture::Make2DTexture( contextMtl, mFormat, size.width, size.height, mips, /** renderTargetOnly */ false, /** allowFormatView */ mFormat.hasDepthAndStencilBits(), &mNativeTexture)); break; case gl::TextureType::CubeMap: mSlices = numCubeFaces = 6; ANGLE_TRY(mtl::Texture::MakeCubeTexture( contextMtl, mFormat, size.width, mips, /** renderTargetOnly */ false, /** allowFormatView */ mFormat.hasDepthAndStencilBits(), &mNativeTexture)); break; case gl::TextureType::_3D: ANGLE_TRY(mtl::Texture::Make3DTexture( contextMtl, mFormat, size.width, size.height, size.depth, mips, /** renderTargetOnly */ false, /** allowFormatView */ mFormat.hasDepthAndStencilBits(), &mNativeTexture)); break; case gl::TextureType::_2DArray: mSlices = size.depth; ANGLE_TRY(mtl::Texture::Make2DArrayTexture( contextMtl, mFormat, size.width, size.height, mips, mSlices, /** renderTargetOnly */ false, /** allowFormatView */ mFormat.hasDepthAndStencilBits(), &mNativeTexture)); break; default: UNREACHABLE(); } ANGLE_TRY(checkForEmulatedChannels(context, mFormat, mNativeTexture)); // Transfer data from images to actual texture object mtl::BlitCommandEncoder *encoder = nullptr; for (int face = 0; face < numCubeFaces; ++face) { for (mtl::MipmapNativeLevel actualMip = mtl::kZeroNativeMipLevel; actualMip.get() < mips; ++actualMip) { GLuint imageMipLevel = mtl::GetGLMipLevel(actualMip, mState.getEffectiveBaseLevel()); mtl::TextureRef &imageToTransfer = mTexImageDefs[face][imageMipLevel].image; // Only transfer if this mip & slice image has been defined and in correct size & // format. gl::Extents actualMipSize = mNativeTexture->size(actualMip); if (imageToTransfer && imageToTransfer->sizeAt0() == actualMipSize && imageToTransfer->pixelFormat() == mNativeTexture->pixelFormat()) { if (!encoder) { encoder = contextMtl->getBlitCommandEncoder(); } encoder->copyTexture(imageToTransfer, 0, mtl::kZeroNativeMipLevel, mNativeTexture, face, actualMip, imageToTransfer->arrayLength(), 1); // Invalidate texture image definition at this index so that we can make it a // view of the native texture at this index later. imageToTransfer = nullptr; } } } // Create sampler state ANGLE_TRY(ensureSamplerStateCreated(context)); return angle::Result::Continue; } angle::Result TextureMtl::ensureSamplerStateCreated(const gl::Context *context) { if (mMetalSamplerState) { return angle::Result::Continue; } ContextMtl *contextMtl = mtl::GetImpl(context); DisplayMtl *displayMtl = contextMtl->getDisplay(); mtl::SamplerDesc samplerDesc(mState.getSamplerState()); if (mFormat.actualAngleFormat().depthBits && !mFormat.getCaps().filterable) { // On devices not supporting filtering for depth textures, we need to convert to nearest // here. samplerDesc.minFilter = MTLSamplerMinMagFilterNearest; samplerDesc.magFilter = MTLSamplerMinMagFilterNearest; if (samplerDesc.mipFilter != MTLSamplerMipFilterNotMipmapped) { samplerDesc.mipFilter = MTLSamplerMipFilterNearest; } samplerDesc.maxAnisotropy = 1; } if (mState.getType() == gl::TextureType::Rectangle) { samplerDesc.rAddressMode = MTLSamplerAddressModeClampToEdge; samplerDesc.sAddressMode = MTLSamplerAddressModeClampToEdge; samplerDesc.tAddressMode = MTLSamplerAddressModeClampToEdge; } mMetalSamplerState = displayMtl->getStateCache().getSamplerState(displayMtl->getMetalDevice(), samplerDesc); return angle::Result::Continue; } angle::Result TextureMtl::onBaseMaxLevelsChanged(const gl::Context *context) { if (!mNativeTexture || (mCurrentBaseLevel == mState.getEffectiveBaseLevel() && mCurrentMaxLevel == mState.getEffectiveMaxLevel())) { return angle::Result::Continue; } ContextMtl *contextMtl = mtl::GetImpl(context); // Release native texture but keep old image definitions so that it can be recreated from old // image definitions with different base level releaseTexture(false, true); // Tell context to rebind textures contextMtl->invalidateCurrentTextures(); return angle::Result::Continue; } angle::Result TextureMtl::ensureImageCreated(const gl::Context *context, const gl::ImageIndex &index) { mtl::TextureRef &image = getImage(index); if (!image) { // Image at this level hasn't been defined yet. We need to define it: const gl::ImageDesc &desc = mState.getImageDesc(index); ANGLE_TRY(redefineImage(context, index, mFormat, desc.size)); } return angle::Result::Continue; } angle::Result TextureMtl::ensureNativeLevelViewsCreated() { ASSERT(mNativeTexture); const GLuint baseLevel = mState.getEffectiveBaseLevel(); for (mtl::MipmapNativeLevel mip = mtl::kZeroNativeMipLevel; mip.get() < mNativeTexture->mipmapLevels(); ++mip) { if (mNativeLevelViews[mip]) { continue; } if (mNativeTexture->textureType() != MTLTextureTypeCube && mTexImageDefs[0][mtl::GetGLMipLevel(mip, baseLevel)].image) { // Reuse texture image view. mNativeLevelViews[mip] = mTexImageDefs[0][mtl::GetGLMipLevel(mip, baseLevel)].image; } else { mNativeLevelViews[mip] = mNativeTexture->createMipView(mip); } } return angle::Result::Continue; } mtl::TextureRef TextureMtl::createImageViewFromNativeTexture( GLuint cubeFaceOrZero, const mtl::MipmapNativeLevel &nativeLevel) { mtl::TextureRef image; if (mNativeTexture->textureType() == MTLTextureTypeCube) { // Cube texture's image is per face. image = mNativeTexture->createSliceMipView(cubeFaceOrZero, nativeLevel); } else { if (mNativeLevelViews[nativeLevel]) { // Reuse the native level view image = mNativeLevelViews[nativeLevel]; } else { image = mNativeTexture->createMipView(nativeLevel); } } return image; } void TextureMtl::retainImageDefinitions() { if (!mNativeTexture) { return; } const GLuint mips = mNativeTexture->mipmapLevels(); int numCubeFaces = 1; switch (mState.getType()) { case gl::TextureType::CubeMap: numCubeFaces = 6; break; default: break; } // Create image view per cube face, per mip level for (int face = 0; face < numCubeFaces; ++face) { for (mtl::MipmapNativeLevel mip = mtl::kZeroNativeMipLevel; mip.get() < mips; ++mip) { GLuint imageMipLevel = mtl::GetGLMipLevel(mip, mCurrentBaseLevel); ImageDefinitionMtl &imageDef = mTexImageDefs[face][imageMipLevel]; if (imageDef.image) { continue; } imageDef.image = createImageViewFromNativeTexture(face, mip); imageDef.formatID = mFormat.intendedFormatId; } } } bool TextureMtl::isIndexWithinMinMaxLevels(const gl::ImageIndex &imageIndex) const { return imageIndex.getLevelIndex() >= static_cast(mState.getEffectiveBaseLevel()) && imageIndex.getLevelIndex() <= static_cast(mState.getEffectiveMaxLevel()); } mtl::MipmapNativeLevel TextureMtl::getNativeLevel(const gl::ImageIndex &imageIndex) const { int baseLevel = mState.getEffectiveBaseLevel(); return mtl::GetNativeMipLevel(imageIndex.getLevelIndex(), baseLevel); } mtl::TextureRef &TextureMtl::getImage(const gl::ImageIndex &imageIndex) { return getImageDefinition(imageIndex).image; } ImageDefinitionMtl &TextureMtl::getImageDefinition(const gl::ImageIndex &imageIndex) { GLuint cubeFaceOrZero = GetImageCubeFaceIndexOrZeroFrom(imageIndex); ImageDefinitionMtl &imageDef = mTexImageDefs[cubeFaceOrZero][imageIndex.getLevelIndex()]; if (!imageDef.image && mNativeTexture) { // If native texture is already created, and the image at this index is not available, // then create a view of native texture at this index, so that modifications of the image // are reflected back to native texture's respective index. if (!isIndexWithinMinMaxLevels(imageIndex)) { // Image below base level is skipped. return imageDef; } mtl::MipmapNativeLevel nativeLevel = getNativeLevel(imageIndex); if (nativeLevel.get() >= mNativeTexture->mipmapLevels()) { // Image outside native texture's mip levels is skipped. return imageDef; } imageDef.image = createImageViewFromNativeTexture(cubeFaceOrZero, nativeLevel); imageDef.formatID = mFormat.intendedFormatId; } return imageDef; } RenderTargetMtl &TextureMtl::getRenderTarget(const gl::ImageIndex &imageIndex) { ASSERT(imageIndex.getType() == gl::TextureType::_2D || imageIndex.getType() == gl::TextureType::Rectangle || imageIndex.getType() == gl::TextureType::_2DMultisample || imageIndex.hasLayer()); GLuint layer = GetImageLayerIndexFrom(imageIndex); RenderTargetMtl &rtt = mPerLayerRenderTargets[layer][imageIndex.getLevelIndex()]; if (!rtt.getTexture()) { // Lazy initialization of render target: mtl::TextureRef &image = getImage(imageIndex); if (image) { if (imageIndex.getType() == gl::TextureType::CubeMap) { // Cube map is special, the image is already the view of its layer rtt.set(image, mtl::kZeroNativeMipLevel, 0, mFormat); } else { rtt.set(image, mtl::kZeroNativeMipLevel, layer, mFormat); } } } return rtt; } angle::Result TextureMtl::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 gl::InternalFormat &dstFormatInfo = gl::GetInternalFormatInfo(internalFormat, type); return setImageImpl(context, index, dstFormatInfo, size, format, type, unpack, unpackBuffer, pixels); } angle::Result TextureMtl::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) { const gl::InternalFormat &formatInfo = gl::GetInternalFormatInfo(format, type); return setSubImageImpl(context, index, area, formatInfo, type, unpack, unpackBuffer, pixels); } angle::Result TextureMtl::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 gl::InternalFormat &formatInfo = gl::GetSizedInternalFormatInfo(internalFormat); const gl::State &glState = context->getState(); gl::Buffer *unpackBuffer = glState.getTargetBuffer(gl::BufferBinding::PixelUnpack); return setImageImpl(context, index, formatInfo, size, internalFormat, GL_UNSIGNED_BYTE, unpack, unpackBuffer, pixels); } angle::Result TextureMtl::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 gl::InternalFormat &formatInfo = gl::GetInternalFormatInfo(format, GL_UNSIGNED_BYTE); const gl::State &glState = context->getState(); gl::Buffer *unpackBuffer = glState.getTargetBuffer(gl::BufferBinding::PixelUnpack); return setSubImageImpl(context, index, area, formatInfo, GL_UNSIGNED_BYTE, unpack, unpackBuffer, pixels); } angle::Result TextureMtl::copyImage(const gl::Context *context, const gl::ImageIndex &index, const gl::Rectangle &sourceArea, GLenum internalFormat, gl::Framebuffer *source) { gl::Extents newImageSize(sourceArea.width, sourceArea.height, 1); const gl::InternalFormat &internalFormatInfo = gl::GetInternalFormatInfo(internalFormat, GL_UNSIGNED_BYTE); ContextMtl *contextMtl = mtl::GetImpl(context); angle::FormatID angleFormatId = angle::Format::InternalFormatToID(internalFormatInfo.sizedInternalFormat); const mtl::Format &mtlFormat = contextMtl->getPixelFormat(angleFormatId); FramebufferMtl *srcFramebufferMtl = mtl::GetImpl(source); RenderTargetMtl *srcReadRT = srcFramebufferMtl->getColorReadRenderTarget(context); RenderTargetMtl colorReadRT; if (srcReadRT) { // Need to duplicate RenderTargetMtl since the srcReadRT would be invalidated in // redefineImage(). This can happen if the source and this texture are the same texture. // Duplication ensures the copyImage() will be able to proceed even if the source texture // will be redefined. colorReadRT.duplicateFrom(*srcReadRT); } ANGLE_TRY(redefineImage(context, index, mtlFormat, newImageSize)); gl::Extents fbSize = source->getReadColorAttachment()->getSize(); gl::Rectangle fbRect(0, 0, fbSize.width, fbSize.height); if (context->isWebGL() && !fbRect.encloses(sourceArea)) { ANGLE_TRY(initializeContents(context, index)); } return copySubImageImpl(context, index, gl::Offset(0, 0, 0), sourceArea, internalFormatInfo, srcFramebufferMtl, &colorReadRT); } angle::Result TextureMtl::copySubImage(const gl::Context *context, const gl::ImageIndex &index, const gl::Offset &destOffset, const gl::Rectangle &sourceArea, gl::Framebuffer *source) { const gl::InternalFormat ¤tFormat = *mState.getImageDesc(index).format.info; FramebufferMtl *srcFramebufferMtl = mtl::GetImpl(source); RenderTargetMtl *colorReadRT = srcFramebufferMtl->getColorReadRenderTarget(context); return copySubImageImpl(context, index, destOffset, sourceArea, currentFormat, srcFramebufferMtl, colorReadRT); } angle::Result TextureMtl::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) { const gl::ImageDesc &sourceImageDesc = source->getTextureState().getImageDesc( NonCubeTextureTypeToTarget(source->getType()), sourceLevel); const gl::InternalFormat &internalFormatInfo = gl::GetInternalFormatInfo(internalFormat, type); // Only 2D textures are supported. ASSERT(sourceImageDesc.size.depth == 1); ContextMtl *contextMtl = mtl::GetImpl(context); angle::FormatID angleFormatId = angle::Format::InternalFormatToID(internalFormatInfo.sizedInternalFormat); const mtl::Format &mtlFormat = contextMtl->getPixelFormat(angleFormatId); ANGLE_TRY(redefineImage(context, index, mtlFormat, sourceImageDesc.size)); return copySubTextureImpl( context, index, gl::Offset(0, 0, 0), internalFormatInfo, sourceLevel, gl::Box(0, 0, 0, sourceImageDesc.size.width, sourceImageDesc.size.height, 1), unpackFlipY, unpackPremultiplyAlpha, unpackUnmultiplyAlpha, source); } angle::Result TextureMtl::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) { const gl::InternalFormat ¤tFormat = *mState.getImageDesc(index).format.info; return copySubTextureImpl(context, index, destOffset, currentFormat, sourceLevel, sourceBox, unpackFlipY, unpackPremultiplyAlpha, unpackUnmultiplyAlpha, source); } angle::Result TextureMtl::copyCompressedTexture(const gl::Context *context, const gl::Texture *source) { UNIMPLEMENTED(); return angle::Result::Stop; } angle::Result TextureMtl::setStorage(const gl::Context *context, gl::TextureType type, size_t mipmaps, GLenum internalFormat, const gl::Extents &size) { ContextMtl *contextMtl = mtl::GetImpl(context); const gl::InternalFormat &formatInfo = gl::GetSizedInternalFormatInfo(internalFormat); angle::FormatID angleFormatId = angle::Format::InternalFormatToID(formatInfo.sizedInternalFormat); const mtl::Format &mtlFormat = contextMtl->getPixelFormat(angleFormatId); return setStorageImpl(context, type, mipmaps, mtlFormat, size); } angle::Result TextureMtl::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, const void *imageCreateInfoPNext) { UNIMPLEMENTED(); return angle::Result::Stop; } angle::Result TextureMtl::setStorageMultisample(const gl::Context *context, gl::TextureType type, GLsizei samples, GLint internalformat, const gl::Extents &size, bool fixedSampleLocations) { UNIMPLEMENTED(); return angle::Result::Stop; } angle::Result TextureMtl::setEGLImageTarget(const gl::Context *context, gl::TextureType type, egl::Image *image) { releaseTexture(true); ContextMtl *contextMtl = mtl::GetImpl(context); ImageMtl *imageMtl = mtl::GetImpl(image); if (type != imageMtl->getImageTextureType()) { return angle::Result::Stop; } mNativeTexture = imageMtl->getTexture(); const angle::FormatID angleFormatId = angle::Format::InternalFormatToID(image->getFormat().info->sizedInternalFormat); mFormat = contextMtl->getPixelFormat(angleFormatId); mSlices = mNativeTexture->cubeFacesOrArrayLength(); gl::Extents size = mNativeTexture->sizeAt0(); mIsPow2 = gl::isPow2(size.width) && gl::isPow2(size.height) && gl::isPow2(size.depth); ANGLE_TRY(ensureSamplerStateCreated(context)); // Tell context to rebind textures contextMtl->invalidateCurrentTextures(); return angle::Result::Continue; } angle::Result TextureMtl::setImageExternal(const gl::Context *context, gl::TextureType type, egl::Stream *stream, const egl::Stream::GLTextureDescription &desc) { UNIMPLEMENTED(); return angle::Result::Stop; } angle::Result TextureMtl::generateMipmap(const gl::Context *context) { ANGLE_TRY(ensureTextureCreated(context)); ContextMtl *contextMtl = mtl::GetImpl(context); if (!mNativeTexture) { return angle::Result::Continue; } const mtl::FormatCaps &caps = mFormat.getCaps(); // bool sRGB = mFormat.actualInternalFormat().colorEncoding == GL_SRGB; bool avoidGPUPath = contextMtl->getDisplay()->getFeatures().forceNonCSBaseMipmapGeneration.enabled && mNativeTexture->widthAt0() < 5; if (!avoidGPUPath && caps.writable && mState.getType() == gl::TextureType::_3D) { // http://anglebug.com/4921. // Use compute for 3D mipmap generation. ANGLE_TRY(ensureNativeLevelViewsCreated()); ANGLE_TRY(contextMtl->getDisplay()->getUtils().generateMipmapCS(contextMtl, mNativeTexture, sRGB, &mNativeLevelViews)); } else if (!avoidGPUPath && caps.filterable && caps.colorRenderable) { mtl::BlitCommandEncoder *blitEncoder = contextMtl->getBlitCommandEncoder(); blitEncoder->generateMipmapsForTexture(mNativeTexture); } else { ANGLE_TRY(generateMipmapCPU(context)); } return angle::Result::Continue; } angle::Result TextureMtl::generateMipmapCPU(const gl::Context *context) { ASSERT(mNativeTexture && mNativeTexture->valid()); ContextMtl *contextMtl = mtl::GetImpl(context); const angle::Format &angleFormat = mFormat.actualAngleFormat(); // This format must have mip generation function. ANGLE_MTL_TRY(contextMtl, angleFormat.mipGenerationFunction); for (uint32_t slice = 0; slice < mSlices; ++slice) { mtl::MipmapNativeLevel maxMipLevel = mtl::GetNativeMipLevel(mNativeTexture->mipmapLevels() - 1, 0); const mtl::MipmapNativeLevel firstLevel = mtl::kZeroNativeMipLevel; uint32_t prevLevelWidth = mNativeTexture->widthAt0(); uint32_t prevLevelHeight = mNativeTexture->heightAt0(); uint32_t prevLevelDepth = mNativeTexture->depthAt0(); size_t prevLevelRowPitch = angleFormat.pixelBytes * prevLevelWidth; size_t prevLevelDepthPitch = prevLevelRowPitch * prevLevelHeight; std::unique_ptr prevLevelData(new (std::nothrow) uint8_t[prevLevelDepthPitch * prevLevelDepth]); ANGLE_CHECK_GL_ALLOC(contextMtl, prevLevelData); std::unique_ptr dstLevelData; // Download base level data mNativeTexture->getBytes( contextMtl, prevLevelRowPitch, prevLevelDepthPitch, MTLRegionMake3D(0, 0, 0, prevLevelWidth, prevLevelHeight, prevLevelDepth), firstLevel, slice, prevLevelData.get()); for (mtl::MipmapNativeLevel mip = firstLevel + 1; mip <= maxMipLevel; ++mip) { uint32_t dstWidth = mNativeTexture->width(mip); uint32_t dstHeight = mNativeTexture->height(mip); uint32_t dstDepth = mNativeTexture->depth(mip); size_t dstRowPitch = angleFormat.pixelBytes * dstWidth; size_t dstDepthPitch = dstRowPitch * dstHeight; size_t dstDataSize = dstDepthPitch * dstDepth; if (!dstLevelData) { // Allocate once and reuse the buffer dstLevelData.reset(new (std::nothrow) uint8_t[dstDataSize]); ANGLE_CHECK_GL_ALLOC(contextMtl, dstLevelData); } // Generate mip level angleFormat.mipGenerationFunction( prevLevelWidth, prevLevelHeight, 1, prevLevelData.get(), prevLevelRowPitch, prevLevelDepthPitch, dstLevelData.get(), dstRowPitch, dstDepthPitch); // Upload to texture ANGLE_TRY(UploadTextureContents( context, angleFormat, MTLRegionMake3D(0, 0, 0, dstWidth, dstHeight, dstDepth), mip, slice, dstLevelData.get(), dstRowPitch, dstDepthPitch, mNativeTexture)); prevLevelWidth = dstWidth; prevLevelHeight = dstHeight; prevLevelDepth = dstDepth; prevLevelRowPitch = dstRowPitch; prevLevelDepthPitch = dstDepthPitch; std::swap(prevLevelData, dstLevelData); } // for mip level } // For layers return angle::Result::Continue; } angle::Result TextureMtl::setBaseLevel(const gl::Context *context, GLuint baseLevel) { return onBaseMaxLevelsChanged(context); } angle::Result TextureMtl::bindTexImage(const gl::Context *context, egl::Surface *surface) { releaseTexture(true); auto pBuffer = GetImplAs(surface); mNativeTexture = pBuffer->getColorTexture(); mFormat = pBuffer->getColorFormat(); gl::Extents size = mNativeTexture->sizeAt0(); mIsPow2 = gl::isPow2(size.width) && gl::isPow2(size.height) && gl::isPow2(size.depth); ANGLE_TRY(ensureSamplerStateCreated(context)); // Tell context to rebind textures ContextMtl *contextMtl = mtl::GetImpl(context); contextMtl->invalidateCurrentTextures(); return angle::Result::Continue; } angle::Result TextureMtl::releaseTexImage(const gl::Context *context) { releaseTexture(true); return angle::Result::Continue; } angle::Result TextureMtl::getAttachmentRenderTarget(const gl::Context *context, GLenum binding, const gl::ImageIndex &imageIndex, GLsizei samples, FramebufferAttachmentRenderTarget **rtOut) { ANGLE_TRY(ensureTextureCreated(context)); ContextMtl *contextMtl = mtl::GetImpl(context); ANGLE_MTL_TRY(contextMtl, mNativeTexture); *rtOut = &getRenderTarget(imageIndex); return angle::Result::Continue; } angle::Result TextureMtl::syncState(const gl::Context *context, const gl::Texture::DirtyBits &dirtyBits, gl::Command source) { ContextMtl *contextMtl = mtl::GetImpl(context); for (size_t dirtyBit : dirtyBits) { switch (dirtyBit) { case gl::Texture::DIRTY_BIT_COMPARE_MODE: case gl::Texture::DIRTY_BIT_COMPARE_FUNC: // Tell context to rebind textures so that ProgramMtl has a chance to verify // depth texture compare mode. contextMtl->invalidateCurrentTextures(); // fall through OS_FALLTHROUGH; case gl::Texture::DIRTY_BIT_MIN_FILTER: case gl::Texture::DIRTY_BIT_MAG_FILTER: case gl::Texture::DIRTY_BIT_WRAP_S: case gl::Texture::DIRTY_BIT_WRAP_T: case gl::Texture::DIRTY_BIT_WRAP_R: case gl::Texture::DIRTY_BIT_MAX_ANISOTROPY: case gl::Texture::DIRTY_BIT_MIN_LOD: case gl::Texture::DIRTY_BIT_MAX_LOD: case gl::Texture::DIRTY_BIT_SRGB_DECODE: case gl::Texture::DIRTY_BIT_BORDER_COLOR: // Recreate sampler state mMetalSamplerState = nil; break; case gl::Texture::DIRTY_BIT_MAX_LEVEL: case gl::Texture::DIRTY_BIT_BASE_LEVEL: ANGLE_TRY(onBaseMaxLevelsChanged(context)); break; case gl::Texture::DIRTY_BIT_SWIZZLE_RED: case gl::Texture::DIRTY_BIT_SWIZZLE_GREEN: case gl::Texture::DIRTY_BIT_SWIZZLE_BLUE: case gl::Texture::DIRTY_BIT_SWIZZLE_ALPHA: { // Recreate swizzle view. mNativeSwizzleSamplingView = nullptr; } break; default: break; } } ANGLE_TRY(ensureTextureCreated(context)); ANGLE_TRY(ensureSamplerStateCreated(context)); return angle::Result::Continue; } angle::Result TextureMtl::bindToShader(const gl::Context *context, mtl::RenderCommandEncoder *cmdEncoder, gl::ShaderType shaderType, gl::Sampler *sampler, int textureSlotIndex, int samplerSlotIndex) { ASSERT(mNativeTexture); float minLodClamp; float maxLodClamp; id samplerState; if (!mNativeSwizzleSamplingView) { #if ANGLE_MTL_SWIZZLE_AVAILABLE ContextMtl *contextMtl = mtl::GetImpl(context); if ((mState.getSwizzleState().swizzleRequired() || mFormat.actualAngleFormat().depthBits || mFormat.swizzled) && contextMtl->getDisplay()->getFeatures().hasTextureSwizzle.enabled) { const gl::InternalFormat &glInternalFormat = *mState.getBaseLevelDesc().format.info; MTLTextureSwizzleChannels swizzle = MTLTextureSwizzleChannelsMake( mtl::GetTextureSwizzle(OverrideSwizzleValue( context, mState.getSwizzleState().swizzleRed, mFormat, glInternalFormat)), mtl::GetTextureSwizzle(OverrideSwizzleValue( context, mState.getSwizzleState().swizzleGreen, mFormat, glInternalFormat)), mtl::GetTextureSwizzle(OverrideSwizzleValue( context, mState.getSwizzleState().swizzleBlue, mFormat, glInternalFormat)), mtl::GetTextureSwizzle(OverrideSwizzleValue( context, mState.getSwizzleState().swizzleAlpha, mFormat, glInternalFormat))); mNativeSwizzleSamplingView = mNativeTexture->createSwizzleView(swizzle); } else #endif // ANGLE_MTL_SWIZZLE_AVAILABLE { mNativeSwizzleSamplingView = mNativeTexture; } } if (!sampler) { samplerState = mMetalSamplerState; minLodClamp = mState.getSamplerState().getMinLod(); maxLodClamp = mState.getSamplerState().getMaxLod(); } else { SamplerMtl *samplerMtl = mtl::GetImpl(sampler); samplerState = samplerMtl->getSampler(mtl::GetImpl(context)); minLodClamp = sampler->getSamplerState().getMinLod(); maxLodClamp = sampler->getSamplerState().getMaxLod(); } minLodClamp = std::max(minLodClamp, 0.f); cmdEncoder->setTexture(shaderType, mNativeSwizzleSamplingView, textureSlotIndex); cmdEncoder->setSamplerState(shaderType, samplerState, minLodClamp, maxLodClamp, samplerSlotIndex); return angle::Result::Continue; } angle::Result TextureMtl::redefineImage(const gl::Context *context, const gl::ImageIndex &index, const mtl::Format &mtlFormat, const gl::Extents &size) { bool imageWithinLevelRange = false; if (isIndexWithinMinMaxLevels(index) && mNativeTexture && mNativeTexture->valid()) { imageWithinLevelRange = true; mtl::MipmapNativeLevel nativeLevel = getNativeLevel(index); // Calculate the expected size for the index we are defining. If the size is different // from the given size, or the format is different, we are redefining the image so we // must release it. bool typeChanged = mNativeTexture->textureType() != mtl::GetTextureType(index.getType()); if (mFormat != mtlFormat || size != mNativeTexture->size(nativeLevel) || typeChanged) { // Keep other images data if texture type hasn't been changed. releaseTexture(typeChanged); } } // Early-out on empty textures, don't create a zero-sized storage. if (size.empty()) { return angle::Result::Continue; } ContextMtl *contextMtl = mtl::GetImpl(context); // Cache last defined image format: mFormat = mtlFormat; ImageDefinitionMtl &imageDef = getImageDefinition(index); // If native texture still exists, it means the size hasn't been changed, no need to create new // image if (mNativeTexture && imageDef.image && imageWithinLevelRange) { ASSERT(imageDef.image->textureType() == mtl::GetTextureType(GetTextureImageType(index.getType())) && imageDef.formatID == mFormat.intendedFormatId && imageDef.image->sizeAt0() == size); } else { imageDef.formatID = mtlFormat.intendedFormatId; // Create image to hold texture's data at this level & slice: switch (index.getType()) { case gl::TextureType::_2D: case gl::TextureType::CubeMap: ANGLE_TRY(mtl::Texture::Make2DTexture( contextMtl, mtlFormat, size.width, size.height, 1, /** renderTargetOnly */ false, /** allowFormatView */ mFormat.hasDepthAndStencilBits(), &imageDef.image)); break; case gl::TextureType::_3D: ANGLE_TRY(mtl::Texture::Make3DTexture( contextMtl, mtlFormat, size.width, size.height, size.depth, 1, /** renderTargetOnly */ false, /** allowFormatView */ mFormat.hasDepthAndStencilBits(), &imageDef.image)); break; case gl::TextureType::_2DArray: ANGLE_TRY(mtl::Texture::Make2DArrayTexture( contextMtl, mtlFormat, size.width, size.height, 1, size.depth, /** renderTargetOnly */ false, /** allowFormatView */ mFormat.hasDepthAndStencilBits(), &imageDef.image)); break; default: UNREACHABLE(); } } // Make sure emulated channels are properly initialized ANGLE_TRY(checkForEmulatedChannels(context, mtlFormat, imageDef.image)); // Tell context to rebind textures contextMtl->invalidateCurrentTextures(); return angle::Result::Continue; } // If mipmaps = 0, this function will create full mipmaps texture. angle::Result TextureMtl::setStorageImpl(const gl::Context *context, gl::TextureType type, size_t mipmaps, const mtl::Format &mtlFormat, const gl::Extents &size) { // Don't need to hold old images data. releaseTexture(true); ContextMtl *contextMtl = mtl::GetImpl(context); // Tell context to rebind textures contextMtl->invalidateCurrentTextures(); mFormat = mtlFormat; // Texture will be created later in ensureTextureCreated() return angle::Result::Continue; } angle::Result TextureMtl::setImageImpl(const gl::Context *context, const gl::ImageIndex &index, const gl::InternalFormat &dstFormatInfo, const gl::Extents &size, GLenum srcFormat, GLenum srcType, const gl::PixelUnpackState &unpack, gl::Buffer *unpackBuffer, const uint8_t *pixels) { ContextMtl *contextMtl = mtl::GetImpl(context); angle::FormatID angleFormatId = angle::Format::InternalFormatToID(dstFormatInfo.sizedInternalFormat); const mtl::Format &mtlFormat = contextMtl->getPixelFormat(angleFormatId); ANGLE_TRY(redefineImage(context, index, mtlFormat, size)); // Early-out on empty textures, don't create a zero-sized storage. if (size.empty()) { return angle::Result::Continue; } // Format of the supplied pixels. const gl::InternalFormat *srcFormatInfo; if (srcFormat != dstFormatInfo.format || srcType != dstFormatInfo.type) { srcFormatInfo = &gl::GetInternalFormatInfo(srcFormat, srcType); } else { srcFormatInfo = &dstFormatInfo; } return setSubImageImpl(context, index, gl::Box(0, 0, 0, size.width, size.height, size.depth), *srcFormatInfo, srcType, unpack, unpackBuffer, pixels); } angle::Result TextureMtl::setSubImageImpl(const gl::Context *context, const gl::ImageIndex &index, const gl::Box &area, const gl::InternalFormat &formatInfo, GLenum type, const gl::PixelUnpackState &unpack, gl::Buffer *unpackBuffer, const uint8_t *oriPixels) { if (!oriPixels && !unpackBuffer) { return angle::Result::Continue; } ContextMtl *contextMtl = mtl::GetImpl(context); ANGLE_TRY(ensureImageCreated(context, index)); mtl::TextureRef &image = getImage(index); GLuint sourceRowPitch = 0; GLuint sourceDepthPitch = 0; GLuint sourceSkipBytes = 0; ANGLE_CHECK_GL_MATH(contextMtl, formatInfo.computeRowPitch(type, area.width, unpack.alignment, unpack.rowLength, &sourceRowPitch)); ANGLE_CHECK_GL_MATH( contextMtl, formatInfo.computeDepthPitch(area.height, unpack.imageHeight, sourceRowPitch, &sourceDepthPitch)); ANGLE_CHECK_GL_MATH(contextMtl, formatInfo.computeSkipBytes(type, sourceRowPitch, sourceDepthPitch, unpack, index.usesTex3D(), &sourceSkipBytes)); // Check if partial image update is supported for this format if (!formatInfo.supportSubImage()) { // area must be the whole mip level sourceRowPitch = 0; gl::Extents size = image->sizeAt0(); if (area.x != 0 || area.y != 0 || area.width != size.width || area.height != size.height) { ANGLE_MTL_CHECK(contextMtl, false, GL_INVALID_OPERATION); } } // Get corresponding source data's ANGLE format angle::FormatID srcAngleFormatId; if (formatInfo.sizedInternalFormat == GL_DEPTH_COMPONENT24) { // GL_DEPTH_COMPONENT24 is special case, its supplied data is 32 bit depth. srcAngleFormatId = angle::FormatID::D32_UNORM; } else { srcAngleFormatId = angle::Format::InternalFormatToID(formatInfo.sizedInternalFormat); } const angle::Format &srcAngleFormat = angle::Format::Get(srcAngleFormatId); const uint8_t *usablePixels = oriPixels + sourceSkipBytes; // Upload to texture if (index.getType() == gl::TextureType::_2DArray) { // OpenGL unifies texture array and texture 3d's box area by using z & depth as array start // index & length for texture array. However, Metal treats them differently. We need to // handle them in separate code. MTLRegion mtlRegion = MTLRegionMake3D(area.x, area.y, 0, area.width, area.height, 1); for (int slice = 0; slice < area.depth; ++slice) { int sliceIndex = slice + area.z; const uint8_t *srcPixels = usablePixels + slice * sourceDepthPitch; ANGLE_TRY(setPerSliceSubImage(context, sliceIndex, mtlRegion, formatInfo, type, srcAngleFormat, sourceRowPitch, sourceDepthPitch, unpackBuffer, srcPixels, image)); } } else { MTLRegion mtlRegion = MTLRegionMake3D(area.x, area.y, area.z, area.width, area.height, area.depth); ANGLE_TRY(setPerSliceSubImage(context, 0, mtlRegion, formatInfo, type, srcAngleFormat, sourceRowPitch, sourceDepthPitch, unpackBuffer, usablePixels, image)); } return angle::Result::Continue; } angle::Result TextureMtl::setPerSliceSubImage(const gl::Context *context, int slice, const MTLRegion &mtlArea, const gl::InternalFormat &internalFormat, GLenum type, const angle::Format &pixelsAngleFormat, size_t pixelsRowPitch, size_t pixelsDepthPitch, gl::Buffer *unpackBuffer, const uint8_t *pixels, const mtl::TextureRef &image) { // If source pixels are luminance or RGB8, we need to convert them to RGBA if (mFormat.needConversion(pixelsAngleFormat.id)) { return convertAndSetPerSliceSubImage(context, slice, mtlArea, internalFormat, type, pixelsAngleFormat, pixelsRowPitch, pixelsDepthPitch, unpackBuffer, pixels, image); } // No conversion needed. ContextMtl *contextMtl = mtl::GetImpl(context); if (unpackBuffer) { uintptr_t offset = reinterpret_cast(pixels); GLuint minRowPitch; ANGLE_CHECK_GL_MATH(contextMtl, internalFormat.computeRowPitch( type, static_cast(mtlArea.size.width), /** aligment */ 1, /** rowLength */ 0, &minRowPitch)); if (offset % mFormat.actualAngleFormat().pixelBytes || pixelsRowPitch < minRowPitch) { // offset is not divisible by pixelByte or the source row pitch is smaller than minimum // row pitch, use convertAndSetPerSliceSubImage() function. return convertAndSetPerSliceSubImage(context, slice, mtlArea, internalFormat, type, pixelsAngleFormat, pixelsRowPitch, pixelsDepthPitch, unpackBuffer, pixels, image); } BufferMtl *unpackBufferMtl = mtl::GetImpl(unpackBuffer); if (mFormat.hasDepthAndStencilBits()) { // NOTE(hqle): packed depth & stencil texture cannot copy from buffer directly, needs // to split its depth & stencil data and copy separately. const uint8_t *clientData = unpackBufferMtl->getClientShadowCopyData(contextMtl); clientData += offset; ANGLE_TRY(UploadTextureContents(context, mFormat.actualAngleFormat(), mtlArea, mtl::kZeroNativeMipLevel, slice, clientData, pixelsRowPitch, pixelsDepthPitch, image)); } else { // Use blit encoder to copy mtl::BlitCommandEncoder *blitEncoder = contextMtl->getBlitCommandEncoder(); blitEncoder->copyBufferToTexture( unpackBufferMtl->getCurrentBuffer(), offset, pixelsRowPitch, pixelsDepthPitch, mtlArea.size, image, slice, mtl::kZeroNativeMipLevel, mtlArea.origin, mFormat.isPVRTC() ? mtl::kBlitOptionRowLinearPVRTC : MTLBlitOptionNone); } } else { // Upload texture data directly ANGLE_TRY(UploadTextureContents(context, mFormat.actualAngleFormat(), mtlArea, mtl::kZeroNativeMipLevel, slice, pixels, pixelsRowPitch, pixelsDepthPitch, image)); } return angle::Result::Continue; } angle::Result TextureMtl::convertAndSetPerSliceSubImage(const gl::Context *context, int slice, const MTLRegion &mtlArea, const gl::InternalFormat &internalFormat, GLenum type, const angle::Format &pixelsAngleFormat, size_t pixelsRowPitch, size_t pixelsDepthPitch, gl::Buffer *unpackBuffer, const uint8_t *pixels, const mtl::TextureRef &image) { ASSERT(image && image->valid()); ContextMtl *contextMtl = mtl::GetImpl(context); if (unpackBuffer) { ANGLE_MTL_CHECK(contextMtl, reinterpret_cast(pixels) <= std::numeric_limits::max(), GL_INVALID_OPERATION); uint32_t offset = static_cast(reinterpret_cast(pixels)); BufferMtl *unpackBufferMtl = mtl::GetImpl(unpackBuffer); if (!mFormat.getCaps().writable || mFormat.hasDepthOrStencilBits() || mFormat.intendedAngleFormat().isBlock) { // Unsupported format, use CPU path. const uint8_t *clientData = unpackBufferMtl->getClientShadowCopyData(contextMtl); clientData += offset; ANGLE_TRY(convertAndSetPerSliceSubImage(context, slice, mtlArea, internalFormat, type, pixelsAngleFormat, pixelsRowPitch, pixelsDepthPitch, nullptr, clientData, image)); } else { // Use compute shader mtl::CopyPixelsFromBufferParams params; params.buffer = unpackBufferMtl->getCurrentBuffer(); params.bufferStartOffset = offset; params.bufferRowPitch = static_cast(pixelsRowPitch); params.bufferDepthPitch = static_cast(pixelsDepthPitch); params.texture = image; params.textureArea = mtl::MTLRegionToGLBox(mtlArea); // If texture is not array, slice must be zero, if texture is array, mtlArea.origin.z // must be zero. // This is because this function uses Metal convention: where slice is only used for // array textures, and z layer of mtlArea.origin is only used for 3D textures. ASSERT(slice == 0 || params.textureArea.z == 0); // For mtl::RenderUtils we convert to OpenGL convention: z layer is used as either array // texture's slice or 3D texture's layer index. params.textureArea.z += slice; ANGLE_TRY(contextMtl->getDisplay()->getUtils().unpackPixelsFromBufferToTexture( contextMtl, pixelsAngleFormat, params)); } } // if (unpackBuffer) else { LoadImageFunctionInfo loadFunctionInfo = mFormat.textureLoadFunctions ? mFormat.textureLoadFunctions(type) : LoadImageFunctionInfo(); const angle::Format &dstFormat = angle::Format::Get(mFormat.actualFormatId); const size_t dstRowPitch = dstFormat.pixelBytes * mtlArea.size.width; // Check if original image data is compressed: if (mFormat.intendedAngleFormat().isBlock) { if (mFormat.intendedFormatId != mFormat.actualFormatId) { ASSERT(loadFunctionInfo.loadFunction); // Need to create a buffer to hold entire decompressed image. const size_t dstDepthPitch = dstRowPitch * mtlArea.size.height; angle::MemoryBuffer decompressBuf; ANGLE_CHECK_GL_ALLOC(contextMtl, decompressBuf.resize(dstDepthPitch * mtlArea.size.depth)); // Decompress loadFunctionInfo.loadFunction(mtlArea.size.width, mtlArea.size.height, mtlArea.size.depth, pixels, pixelsRowPitch, pixelsDepthPitch, decompressBuf.data(), dstRowPitch, dstDepthPitch); // Upload to texture ANGLE_TRY(UploadTextureContents( context, dstFormat, mtlArea, mtl::kZeroNativeMipLevel, slice, decompressBuf.data(), dstRowPitch, dstDepthPitch, image)); } else { // Assert that we're filling the level in it's entierety. ASSERT(mtlArea.size.width == static_cast(image->sizeAt0().width)); ASSERT(mtlArea.size.height == static_cast(image->sizeAt0().height)); const size_t dstDepthPitch = dstRowPitch * mtlArea.size.height; ANGLE_TRY(UploadTextureContents(context, dstFormat, mtlArea, mtl::kZeroNativeMipLevel, slice, pixels, dstRowPitch, dstDepthPitch, image)); } } // if (mFormat.intendedAngleFormat().isBlock) else { // Create scratch row buffer angle::MemoryBuffer conversionRow; ANGLE_CHECK_GL_ALLOC(contextMtl, conversionRow.resize(dstRowPitch)); // Convert row by row: MTLRegion mtlRow = mtlArea; mtlRow.size.height = mtlRow.size.depth = 1; for (NSUInteger d = 0; d < mtlArea.size.depth; ++d) { mtlRow.origin.z = mtlArea.origin.z + d; for (NSUInteger r = 0; r < mtlArea.size.height; ++r) { const uint8_t *psrc = pixels + d * pixelsDepthPitch + r * pixelsRowPitch; mtlRow.origin.y = mtlArea.origin.y + r; // Convert pixels if (loadFunctionInfo.loadFunction) { loadFunctionInfo.loadFunction(mtlRow.size.width, 1, 1, psrc, pixelsRowPitch, 0, conversionRow.data(), dstRowPitch, 0); } else if (mFormat.hasDepthOrStencilBits()) { ConvertDepthStencilData(mtlRow.size, pixelsAngleFormat, pixelsRowPitch, 0, psrc, dstFormat, nullptr, dstRowPitch, 0, conversionRow.data()); } else { CopyImageCHROMIUM(psrc, pixelsRowPitch, pixelsAngleFormat.pixelBytes, 0, pixelsAngleFormat.pixelReadFunction, conversionRow.data(), dstRowPitch, dstFormat.pixelBytes, 0, dstFormat.pixelWriteFunction, internalFormat.format, dstFormat.componentType, mtlRow.size.width, 1, 1, false, false, false); } // Upload to texture ANGLE_TRY(UploadTextureContents(context, dstFormat, mtlRow, mtl::kZeroNativeMipLevel, slice, conversionRow.data(), dstRowPitch, 0, image)); } } } // if (mFormat.intendedAngleFormat().isBlock) } // if (unpackBuffer) return angle::Result::Continue; } angle::Result TextureMtl::checkForEmulatedChannels(const gl::Context *context, const mtl::Format &mtlFormat, const mtl::TextureRef &texture) { bool emulatedChannels = mtl::IsFormatEmulated(mtlFormat); // For emulated channels that GL texture intends to not have, // we need to initialize their content. if (emulatedChannels) { uint32_t mipmaps = texture->mipmapLevels(); uint32_t layers = texture->cubeFacesOrArrayLength(); for (uint32_t layer = 0; layer < layers; ++layer) { for (uint32_t mip = 0; mip < mipmaps; ++mip) { auto index = mtl::ImageNativeIndex::FromBaseZeroGLIndex( GetCubeOrArraySliceMipIndex(texture, layer, mip)); ANGLE_TRY(mtl::InitializeTextureContents(context, texture, mtlFormat, index)); } } } return angle::Result::Continue; } angle::Result TextureMtl::initializeContents(const gl::Context *context, const gl::ImageIndex &index) { if (index.isLayered()) { // InitializeTextureContents is only able to initialize one layer at a time. const gl::ImageDesc &desc = mState.getImageDesc(index); uint32_t layerCount; if (index.isEntireLevelCubeMap()) { layerCount = 6; } else { layerCount = desc.size.depth; } gl::ImageIndexIterator ite = index.getLayerIterator(layerCount); while (ite.hasNext()) { gl::ImageIndex layerIndex = ite.next(); ANGLE_TRY(initializeContents(context, layerIndex)); } return angle::Result::Continue; } else if (index.getLayerCount() > 1) { for (int layer = 0; layer < index.getLayerCount(); ++layer) { int layerIdx = layer + index.getLayerIndex(); gl::ImageIndex layerIndex = gl::ImageIndex::MakeFromType(index.getType(), index.getLevelIndex(), layerIdx); ANGLE_TRY(initializeContents(context, layerIndex)); } return angle::Result::Continue; } ASSERT(index.getLayerCount() == 1 && !index.isLayered()); ANGLE_TRY(ensureImageCreated(context, index)); ContextMtl *contextMtl = mtl::GetImpl(context); ImageDefinitionMtl &imageDef = getImageDefinition(index); const mtl::TextureRef &image = imageDef.image; const mtl::Format &format = contextMtl->getPixelFormat(imageDef.formatID); // For Texture's image definition, we always use zero mip level. if (format.metalFormat == MTLPixelFormatInvalid) { return angle::Result::Stop; } return mtl::InitializeTextureContents( context, image, format, mtl::ImageNativeIndex::FromBaseZeroGLIndex( GetLayerMipIndex(image, GetImageLayerIndexFrom(index), /** level */ 0))); } angle::Result TextureMtl::copySubImageImpl(const gl::Context *context, const gl::ImageIndex &index, const gl::Offset &destOffset, const gl::Rectangle &sourceArea, const gl::InternalFormat &internalFormat, const FramebufferMtl *source, const RenderTargetMtl *colorReadRT) { if (!colorReadRT || !colorReadRT->getTexture()) { // Is this an error? return angle::Result::Continue; } gl::Extents fbSize = colorReadRT->getTexture()->size(colorReadRT->getLevelIndex()); gl::Rectangle clippedSourceArea; if (!ClipRectangle(sourceArea, gl::Rectangle(0, 0, fbSize.width, fbSize.height), &clippedSourceArea)) { return angle::Result::Continue; } // If negative offsets are given, clippedSourceArea ensures we don't read from those offsets. // However, that changes the sourceOffset->destOffset mapping. Here, destOffset is shifted by // the same amount as clipped to correct the error. const gl::Offset modifiedDestOffset(destOffset.x + clippedSourceArea.x - sourceArea.x, destOffset.y + clippedSourceArea.y - sourceArea.y, 0); ANGLE_TRY(ensureImageCreated(context, index)); if (!mFormat.getCaps().isRenderable()) { return copySubImageCPU(context, index, modifiedDestOffset, clippedSourceArea, internalFormat, source, colorReadRT); } // NOTE(hqle): Use compute shader. return copySubImageWithDraw(context, index, modifiedDestOffset, clippedSourceArea, internalFormat, source, colorReadRT); } angle::Result TextureMtl::copySubImageWithDraw(const gl::Context *context, const gl::ImageIndex &index, const gl::Offset &modifiedDestOffset, const gl::Rectangle &clippedSourceArea, const gl::InternalFormat &internalFormat, const FramebufferMtl *source, const RenderTargetMtl *colorReadRT) { ContextMtl *contextMtl = mtl::GetImpl(context); DisplayMtl *displayMtl = contextMtl->getDisplay(); const RenderTargetMtl &imageRtt = getRenderTarget(index); mtl::RenderCommandEncoder *cmdEncoder = contextMtl->getRenderTargetCommandEncoder(imageRtt); mtl::ColorBlitParams blitParams; blitParams.dstTextureSize = imageRtt.getTexture()->size(imageRtt.getLevelIndex()); blitParams.dstRect = gl::Rectangle(modifiedDestOffset.x, modifiedDestOffset.y, clippedSourceArea.width, clippedSourceArea.height); blitParams.dstScissorRect = blitParams.dstRect; blitParams.enabledBuffers.set(0); blitParams.src = colorReadRT->getTexture(); blitParams.srcLevel = colorReadRT->getLevelIndex(); blitParams.srcLayer = colorReadRT->getLayerIndex(); blitParams.srcNormalizedCoords = mtl::NormalizedCoords( clippedSourceArea, colorReadRT->getTexture()->size(blitParams.srcLevel)); blitParams.srcYFlipped = source->flipY(); blitParams.dstLuminance = internalFormat.isLUMA(); return displayMtl->getUtils().blitColorWithDraw( context, cmdEncoder, colorReadRT->getFormat()->actualAngleFormat(), blitParams); } angle::Result TextureMtl::copySubImageCPU(const gl::Context *context, const gl::ImageIndex &index, const gl::Offset &modifiedDestOffset, const gl::Rectangle &clippedSourceArea, const gl::InternalFormat &internalFormat, const FramebufferMtl *source, const RenderTargetMtl *colorReadRT) { mtl::TextureRef &image = getImage(index); ASSERT(image && image->valid()); ContextMtl *contextMtl = mtl::GetImpl(context); const angle::Format &dstFormat = angle::Format::Get(mFormat.actualFormatId); const int dstRowPitch = dstFormat.pixelBytes * clippedSourceArea.width; angle::MemoryBuffer conversionRow; ANGLE_CHECK_GL_ALLOC(contextMtl, conversionRow.resize(dstRowPitch)); gl::Rectangle srcRowArea = gl::Rectangle(clippedSourceArea.x, 0, clippedSourceArea.width, 1); MTLRegion mtlDstRowArea = MTLRegionMake2D(modifiedDestOffset.x, 0, clippedSourceArea.width, 1); uint32_t dstSlice = 0; switch (index.getType()) { case gl::TextureType::_2D: case gl::TextureType::CubeMap: dstSlice = 0; break; case gl::TextureType::_2DArray: ASSERT(index.hasLayer()); dstSlice = index.getLayerIndex(); break; case gl::TextureType::_3D: ASSERT(index.hasLayer()); dstSlice = 0; mtlDstRowArea.origin.z = index.getLayerIndex(); break; default: UNREACHABLE(); } // Copy row by row: for (int r = 0; r < clippedSourceArea.height; ++r) { mtlDstRowArea.origin.y = modifiedDestOffset.y + r; srcRowArea.y = clippedSourceArea.y + r; PackPixelsParams packParams(srcRowArea, dstFormat, dstRowPitch, false, nullptr, 0); // Read pixels from framebuffer to memory: gl::Rectangle flippedSrcRowArea = source->getCorrectFlippedReadArea(context, srcRowArea); ANGLE_TRY(source->readPixelsImpl(context, flippedSrcRowArea, packParams, colorReadRT, conversionRow.data())); // Upload to texture ANGLE_TRY(UploadTextureContents(context, dstFormat, mtlDstRowArea, mtl::kZeroNativeMipLevel, dstSlice, conversionRow.data(), dstRowPitch, 0, image)); } return angle::Result::Continue; } angle::Result TextureMtl::copySubTextureImpl(const gl::Context *context, const gl::ImageIndex &index, const gl::Offset &destOffset, const gl::InternalFormat &internalFormat, GLint sourceLevel, const gl::Box &sourceBox, bool unpackFlipY, bool unpackPremultiplyAlpha, bool unpackUnmultiplyAlpha, const gl::Texture *source) { // Only 2D textures are supported. ASSERT(sourceBox.depth == 1); ASSERT(source->getType() == gl::TextureType::_2D); gl::ImageIndex sourceIndex = gl::ImageIndex::Make2D(sourceLevel); ContextMtl *contextMtl = mtl::GetImpl(context); TextureMtl *sourceMtl = mtl::GetImpl(source); ANGLE_TRY(ensureImageCreated(context, index)); ANGLE_TRY(sourceMtl->ensureImageCreated(context, sourceIndex)); const ImageDefinitionMtl &srcImageDef = sourceMtl->getImageDefinition(sourceIndex); const mtl::TextureRef &sourceImage = srcImageDef.image; const mtl::Format &sourceFormat = contextMtl->getPixelFormat(srcImageDef.formatID); const angle::Format &sourceAngleFormat = sourceFormat.actualAngleFormat(); if (!mFormat.getCaps().isRenderable()) { return copySubTextureCPU(context, index, destOffset, internalFormat, mtl::kZeroNativeMipLevel, sourceBox, sourceAngleFormat, unpackFlipY, unpackPremultiplyAlpha, unpackUnmultiplyAlpha, sourceImage); } return copySubTextureWithDraw( context, index, destOffset, internalFormat, mtl::kZeroNativeMipLevel, sourceBox, sourceAngleFormat, unpackFlipY, unpackPremultiplyAlpha, unpackUnmultiplyAlpha, sourceImage); } angle::Result TextureMtl::copySubTextureWithDraw(const gl::Context *context, const gl::ImageIndex &index, const gl::Offset &destOffset, const gl::InternalFormat &internalFormat, const mtl::MipmapNativeLevel &sourceNativeLevel, const gl::Box &sourceBox, const angle::Format &sourceAngleFormat, bool unpackFlipY, bool unpackPremultiplyAlpha, bool unpackUnmultiplyAlpha, const mtl::TextureRef &sourceTexture) { ContextMtl *contextMtl = mtl::GetImpl(context); DisplayMtl *displayMtl = contextMtl->getDisplay(); mtl::TextureRef image = getImage(index); ASSERT(image && image->valid()); if (internalFormat.colorEncoding == GL_SRGB) { image = image->getLinearColorView(); } mtl::RenderCommandEncoder *cmdEncoder = contextMtl->getTextureRenderCommandEncoder( image, mtl::ImageNativeIndex::FromBaseZeroGLIndex(GetZeroLevelIndex(image))); mtl::ColorBlitParams blitParams; blitParams.dstTextureSize = image->sizeAt0(); blitParams.dstRect = gl::Rectangle(destOffset.x, destOffset.y, sourceBox.width, sourceBox.height); blitParams.dstScissorRect = blitParams.dstRect; blitParams.enabledBuffers.set(0); blitParams.src = sourceTexture; blitParams.srcLevel = sourceNativeLevel; blitParams.srcLayer = 0; blitParams.srcNormalizedCoords = mtl::NormalizedCoords(sourceBox.toRect(), sourceTexture->size(sourceNativeLevel)); blitParams.srcYFlipped = false; blitParams.dstLuminance = internalFormat.isLUMA(); blitParams.unpackFlipY = unpackFlipY; blitParams.unpackPremultiplyAlpha = unpackPremultiplyAlpha; blitParams.unpackUnmultiplyAlpha = unpackUnmultiplyAlpha; return displayMtl->getUtils().copyTextureWithDraw(context, cmdEncoder, sourceAngleFormat, mFormat.actualAngleFormat(), blitParams); } angle::Result TextureMtl::copySubTextureCPU(const gl::Context *context, const gl::ImageIndex &index, const gl::Offset &destOffset, const gl::InternalFormat &internalFormat, const mtl::MipmapNativeLevel &sourceNativeLevel, const gl::Box &sourceBox, const angle::Format &sourceAngleFormat, bool unpackFlipY, bool unpackPremultiplyAlpha, bool unpackUnmultiplyAlpha, const mtl::TextureRef &sourceTexture) { mtl::TextureRef &image = getImage(index); ASSERT(image && image->valid()); ContextMtl *contextMtl = mtl::GetImpl(context); const angle::Format &dstAngleFormat = mFormat.actualAngleFormat(); const int srcRowPitch = sourceAngleFormat.pixelBytes * sourceBox.width; const int srcImageSize = srcRowPitch * sourceBox.height; const int convRowPitch = dstAngleFormat.pixelBytes * sourceBox.width; const int convImageSize = convRowPitch * sourceBox.height; angle::MemoryBuffer conversionSrc, conversionDst; ANGLE_CHECK_GL_ALLOC(contextMtl, conversionSrc.resize(srcImageSize)); ANGLE_CHECK_GL_ALLOC(contextMtl, conversionDst.resize(convImageSize)); MTLRegion mtlSrcArea = MTLRegionMake2D(sourceBox.x, sourceBox.y, sourceBox.width, sourceBox.height); MTLRegion mtlDstArea = MTLRegionMake2D(destOffset.x, destOffset.y, sourceBox.width, sourceBox.height); // Read pixels from source to memory: sourceTexture->getBytes(contextMtl, srcRowPitch, 0, mtlSrcArea, sourceNativeLevel, 0, conversionSrc.data()); // Convert to destination format CopyImageCHROMIUM(conversionSrc.data(), srcRowPitch, sourceAngleFormat.pixelBytes, 0, sourceAngleFormat.pixelReadFunction, conversionDst.data(), convRowPitch, dstAngleFormat.pixelBytes, 0, dstAngleFormat.pixelWriteFunction, internalFormat.format, internalFormat.componentType, sourceBox.width, sourceBox.height, 1, unpackFlipY, unpackPremultiplyAlpha, unpackUnmultiplyAlpha); // Upload to texture ANGLE_TRY(UploadTextureContents(context, dstAngleFormat, mtlDstArea, mtl::kZeroNativeMipLevel, 0, conversionDst.data(), convRowPitch, 0, image)); return angle::Result::Continue; } }