// // 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. // #ifdef UNSAFE_BUFFERS_BUILD # pragma allow_unsafe_buffers #endif #include "libANGLE/renderer/metal/TextureMtl.h" #include #include #include "common/Color.h" #include "common/MemoryBuffer.h" #include "common/debug.h" #include "common/mathutil.h" #include "image_util/imageformats.h" #include "image_util/loadimage.h" #include "libANGLE/ErrorStrings.h" #include "libANGLE/Surface.h" #include "libANGLE/renderer/Format.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)); } 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); } } } } 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); } } } } } mtl::BlitCommandEncoder *GetBlitCommandEncoderForResources( ContextMtl *contextMtl, const std::initializer_list &resources) { if (std::none_of(resources.begin(), resources.end(), [contextMtl](const mtl::Resource *res) { return res->hasPendingRenderWorks(contextMtl); })) { // If no resource has pending render works waiting to be submitted, then it's safe to // create a blit encoder without ending current render pass. The blit commands // will run before any pending render commands. return contextMtl->getBlitCommandEncoderWithoutEndingRenderEncoder(); } return contextMtl->getBlitCommandEncoder(); } 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, &stagingBuffer)); uint8_t *pdst = stagingBuffer->map(contextMtl).data(); ConvertDepthStencilData(regionSize, textureAngleFormat, bytesPerRow, bytesPer2DImage, data, stagingAngleFormat, pixelWriteFunctionOverride, stagingBufferRowPitch, stagingBuffer2DImageSize, pdst); stagingBuffer->unmap(contextMtl); *bufferOut = stagingBuffer; *bufferRowPitchOut = stagingBufferRowPitch; *buffer2DImageSizeOut = stagingBuffer2DImageSize; return angle::Result::Continue; } 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, &stagingBuffer)); uint8_t *pdst = stagingBuffer->map(contextMtl).data(); 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, &stagingBuffer)); uint8_t *pdst = stagingBuffer->map(contextMtl).data(); CopyTextureData(regionSizeInBlocks, bytesPerBlockRow, bytesPer2DImage, data, stagingBufferRowPitch, stagingBuffer2DImageSize, pdst); stagingBuffer->unmap(contextMtl); *bufferOut = stagingBuffer; *bufferRowPitchOut = stagingBufferRowPitch; *buffer2DImageSizeOut = stagingBuffer2DImageSize; return angle::Result::Continue; } angle::Result SaturateDepth(ContextMtl *contextMtl, mtl::BufferRef srcBuffer, mtl::BufferRef dstBuffer, uint32_t srcBufferOffset, uint32_t srcPitch, MTLSize size) { static_assert(gl::IMPLEMENTATION_MAX_2D_TEXTURE_SIZE <= UINT_MAX); mtl::DepthSaturationParams params; params.srcBuffer = srcBuffer; params.dstBuffer = dstBuffer; params.srcBufferOffset = srcBufferOffset; params.dstWidth = static_cast(size.width); params.dstHeight = static_cast(size.height); params.srcPitch = srcPitch; ANGLE_TRY(contextMtl->getDisplay()->getUtils().saturateDepth(contextMtl, params)); return angle::Result::Continue; } // This will copy a buffer to: // - the respective level & slice of an original texture if the "dst" texture is a view. // - the "dst" texture if it is not a view. // Notes: // - dstSlice is a slice in the "dst" texture not original texture. // - dstLevel is a level in the "dst" texture not original texture. // This function is needed because some GPUs such as the ones having AMD Bronze driver // have a bug when copying a buffer to a view of a 3D texture. void CopyBufferToOriginalTextureIfDstIsAView(ContextMtl *contextMtl, mtl::BlitCommandEncoder *blitEncoder, const mtl::BufferRef &src, size_t srcOffset, size_t srcBytesPerRow, size_t srcBytesPerImage, MTLSize srcSize, const mtl::TextureRef &dst, const uint32_t dstSlice, const mtl::MipmapNativeLevel &dstLevel, MTLOrigin dstOrigin, MTLBlitOption blitOption) { mtl::TextureRef correctedTexture = dst; mtl::MipmapNativeLevel correctedLevel = dstLevel; uint32_t correctedSlice = dstSlice; // TODO(b/343734719): Simulator has bug in parentRelativeSlice() so skip this step // on simulator. if (!contextMtl->getDisplay()->isSimulator() && correctedTexture->parentTexture()) { correctedLevel = correctedLevel + correctedTexture->parentRelativeLevel().get(); correctedSlice += correctedTexture->parentRelativeSlice(); correctedTexture = correctedTexture->parentTexture(); } blitEncoder->copyBufferToTexture(src, srcOffset, srcBytesPerRow, srcBytesPerImage, srcSize, correctedTexture, correctedSlice, correctedLevel, dstOrigin, blitOption); } 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); // Depth and stencil textures cannot be of 3D type; // arrays and cube maps must be uploaded per-slice. ASSERT(region.size.depth == 1); // 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)); if (textureAngleFormat.id == angle::FormatID::D32_FLOAT) { ANGLE_TRY(SaturateDepth(contextMtl, stagingBuffer, stagingBuffer, 0, static_cast(region.size.width), region.size)); } // Copy staging buffer to texture. mtl::BlitCommandEncoder *encoder = GetBlitCommandEncoderForResources(contextMtl, {stagingBuffer.get(), texture.get()}); CopyBufferToOriginalTextureIfDstIsAView( contextMtl, encoder, 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); // Depth and stencil textures cannot be of 3D type; // arrays and cube maps must be uploaded per-slice. ASSERT(region.size.depth == 1); // 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_GL_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)); if (angleStagingDepthFormat.id == angle::FormatID::D32_FLOAT) { ANGLE_TRY(SaturateDepth(contextMtl, stagingDepthBuffer, stagingDepthBuffer, 0, static_cast(region.size.width), region.size)); } mtl::BlitCommandEncoder *encoder = GetBlitCommandEncoderForResources( contextMtl, {stagingDepthBuffer.get(), stagingStencilBuffer.get(), texture.get()}); CopyBufferToOriginalTextureIfDstIsAView( contextMtl, encoder, stagingDepthBuffer, 0, stagingDepthBufferRowPitch, stagingDepthBuffer2DImageSize, region.size, texture, slice, mipmapLevel, region.origin, MTLBlitOptionDepthFromDepthStencil); CopyBufferToOriginalTextureIfDstIsAView( contextMtl, encoder, stagingStencilBuffer, 0, stagingStencilBufferRowPitch, stagingStencilBuffer2DImageSize, region.size, texture, slice, mipmapLevel, region.origin, MTLBlitOptionStencilFromDepthStencil); return angle::Result::Continue; } 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 = GetBlitCommandEncoderForResources(contextMtl, {stagingBuffer.get(), texture.get()}); CopyBufferToOriginalTextureIfDstIsAView( contextMtl, encoder, stagingBuffer, 0, stagingBufferRowPitch, stagingBuffer2DImageSize, region.size, texture, slice, mipmapLevel, region.origin, 0); return angle::Result::Continue; } 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, bool avoidStagingBuffers, const mtl::TextureRef &texture) { ASSERT(texture && texture->valid()); ContextMtl *contextMtl = mtl::GetImpl(context); const mtl::Format &mtlFormat = contextMtl->getPixelFormat(textureAngleFormat.id); bool preferGPUInitialization = !avoidStagingBuffers && PreferStagedTextureUploads(context, texture, mtlFormat, mtl::StagingPurpose::Upload); if (texture->isCPUAccessible() && !preferGPUInitialization) { if (mtlFormat.isPVRTC()) { // Replace Region Validation: rowBytes must be 0 bytesPerRow = 0; } // If texture is CPU accessible, just call replaceRegion() directly. texture->replaceRegion(contextMtl, region, mipmapLevel, slice, data, bytesPerRow, bytesPer2DImage); return angle::Result::Continue; } // Texture is not CPU accessible or staging is forced due to a workaround if (!textureAngleFormat.depthBits && !textureAngleFormat.stencilBits) { // Upload color data ANGLE_TRY(UploadTextureContentsWithStagingBuffer(contextMtl, textureAngleFormat, region, mipmapLevel, slice, data, bytesPerRow, bytesPer2DImage, texture)); } else if (textureAngleFormat.depthBits && textureAngleFormat.stencilBits) { // Packed depth-stencil ANGLE_TRY(UploadPackedDepthStencilTextureContentsWithStagingBuffer( contextMtl, textureAngleFormat, region, mipmapLevel, slice, data, bytesPerRow, bytesPer2DImage, texture)); } else { // Depth or stencil 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_APPLE_UNUSED GLenum OverrideSwizzleValue(const gl::Context *context, GLenum swizzle, const mtl::Format &format, const gl::InternalFormat &glInternalFormat) { if (format.actualAngleFormat().hasDepthOrStencilBits()) { ASSERT(!format.swizzled); if (context->getState().getClientVersion() >= gl::ES_3_0 && glInternalFormat.sized) { // ES 3.1 spec: treat depth and stencil textures as red textures 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; } mtl::TextureRef &GetLayerLevelTextureView( TextureMtl::LayerLevelTextureViewVector *layerLevelTextureViews, uint32_t layer, uint32_t level, uint32_t layerCount, uint32_t levelCount) { // Lazily allocate the full layer and level count to not trigger any std::vector reallocations. if (layerLevelTextureViews->empty()) { layerLevelTextureViews->resize(layerCount); } ASSERT(layerLevelTextureViews->size() > layer); TextureMtl::TextureViewVector &levelTextureViews = (*layerLevelTextureViews)[layer]; if (levelTextureViews.empty()) { levelTextureViews.resize(levelCount); } ASSERT(levelTextureViews.size() > level); return levelTextureViews[level]; } } // namespace // TextureMtl::NativeTextureWrapper implementation. // This class uses GL level instead of mtl::MipmapNativeLevel. // It seamlessly translates GL level to native level based on the base GL information passed in the // constructor. The base GL level is unchanged thoughout the lifetime of this object. // Note that NativeTextureWrapper's base GL level doesn't necessarily mean it's the same as a GL // texture's real base level. // - If NativeTextureWrapper holds a native storage of a non-immutable texture, // its base GL level is indeed equal to the GL texture's base level. // - If NativeTextureWrapper holds a native storage of an immutable texture, // it base GL level is actually 0. // - If NativeTextureWrapper holds a view from base level to max level of a GL texture, // then its base GL level is equal to the GL texture's base level. class TextureMtl::NativeTextureWrapper : angle::NonCopyable { public: NativeTextureWrapper(mtl::TextureRef texture, GLuint baseGLLevel, const mtl::Format &format) : mNativeTexture(std::move(texture)), mBaseGLLevel(baseGLLevel), mFormat(format) { ASSERT(mNativeTexture && mNativeTexture->valid()); } operator const mtl::TextureRef &() const { return mNativeTexture; } const mtl::TextureRef &getNativeTexture() const { return mNativeTexture; } void replaceRegion(ContextMtl *context, const MTLRegion ®ion, GLuint glLevel, uint32_t slice, const uint8_t *data, size_t bytesPerRow, size_t bytesPer2DImage) { mNativeTexture->replaceRegion(context, region, getNativeLevel(glLevel), slice, data, bytesPerRow, bytesPer2DImage); } void getBytes(ContextMtl *context, size_t bytesPerRow, size_t bytesPer2DImage, const MTLRegion ®ion, GLuint glLevel, uint32_t slice, angle::Span dataOut) { mNativeTexture->getBytes(context, bytesPerRow, bytesPer2DImage, region, getNativeLevel(glLevel), slice, dataOut); } const mtl::Format &getFormat() const { return mFormat; } GLuint getBaseGLLevel() const { return mBaseGLLevel; } // Get max addressable GL level that this texture supports. GLuint getMaxSupportedGLLevel() const { return mBaseGLLevel + mipmapLevels() - 1; } // Check whether a GL level refers to a valid mip in this texture. bool isGLLevelSupported(GLuint glLevel) { return glLevel >= mBaseGLLevel && glLevel <= getMaxSupportedGLLevel(); } mtl::MipmapNativeLevel getNativeLevel(GLuint glLevel) const { return mtl::GetNativeMipLevel(glLevel, mBaseGLLevel); } GLuint getGLLevel(const mtl::MipmapNativeLevel &nativeLevel) const { return mtl::GetGLMipLevel(nativeLevel, mBaseGLLevel); } mtl::TextureRef getStencilView() { return mNativeTexture->getStencilView(); } MTLTextureType textureType() const { return mNativeTexture->textureType(); } MTLPixelFormat pixelFormat() const { return mNativeTexture->pixelFormat(); } uint32_t mipmapLevels() const { return mNativeTexture->mipmapLevels(); } uint32_t arrayLength() const { return mNativeTexture->arrayLength(); } uint32_t cubeFaces() const { return mNativeTexture->cubeFaces(); } uint32_t cubeFacesOrArrayLength() const { return mNativeTexture->cubeFacesOrArrayLength(); } uint32_t width(GLuint glLevel) const { return mNativeTexture->width(getNativeLevel(glLevel)); } uint32_t height(GLuint glLevel) const { return mNativeTexture->height(getNativeLevel(glLevel)); } uint32_t depth(GLuint glLevel) const { return mNativeTexture->depth(getNativeLevel(glLevel)); } gl::Extents size(GLuint glLevel) const { return mNativeTexture->size(getNativeLevel(glLevel)); } // Get width, height, depth, size at base level. uint32_t widthAt0() const { return width(mBaseGLLevel); } uint32_t heightAt0() const { return height(mBaseGLLevel); } uint32_t depthAt0() const { return depth(mBaseGLLevel); } gl::Extents sizeAt0() const { return size(mBaseGLLevel); } protected: mtl::TextureRef mNativeTexture; const GLuint mBaseGLLevel; const mtl::Format mFormat; }; // This class extends NativeTextureWrapper with support for view creation class TextureMtl::NativeTextureWrapperWithViewSupport : public NativeTextureWrapper { public: NativeTextureWrapperWithViewSupport(mtl::TextureRef texture, GLuint baseGLLevel, const mtl::Format &format) : NativeTextureWrapper(std::move(texture), baseGLLevel, format) {} // Create a view of one slice at a level. mtl::TextureRef createSliceMipView(uint32_t slice, GLuint glLevel) { return mNativeTexture->createSliceMipView(slice, getNativeLevel(glLevel)); } // Create a levels range view mtl::TextureRef createMipsView(GLuint glLevel, uint32_t levels) { return mNativeTexture->createMipsView(getNativeLevel(glLevel), levels); } // Create a view of a level. mtl::TextureRef createMipView(GLuint glLevel) { return mNativeTexture->createMipView(getNativeLevel(glLevel)); } // Create a view for a shader image binding. mtl::TextureRef createShaderImageView2D(GLuint glLevel, int layer, MTLPixelFormat format) { return mNativeTexture->createShaderImageView2D(getNativeLevel(glLevel), layer, format); } // Create a swizzled view mtl::TextureRef createMipsSwizzleView(GLuint glLevel, uint32_t levels, MTLPixelFormat format, const MTLTextureSwizzleChannels &swizzle) { return mNativeTexture->createMipsSwizzleView(getNativeLevel(glLevel), levels, format, swizzle); } }; // TextureMtl implementation TextureMtl::TextureMtl(const gl::TextureState &state) : TextureImpl(state) {} TextureMtl::~TextureMtl() = default; void TextureMtl::onDestroy(const gl::Context *context) { deallocateNativeStorage(/*keepImages=*/false); mBoundSurface = nullptr; } void TextureMtl::deallocateNativeStorage(bool keepImages, bool keepSamplerStateAndFormat) { if (!keepImages) { mTexImageDefs.clear(); mShaderImageViews.clear(); } else if (mNativeTextureStorage) { // Release native texture but keep its image definitions. retainImageDefinitions(); } mNativeTextureStorage = nullptr; mViewFromBaseToMaxLevel = nullptr; mSwizzleStencilSamplingView = 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 &samplesMapRenderTargets : mRenderTargets) { for (RenderTargetMtl &perSampleCountRenderTarget : samplesMapRenderTargets.second) { perSampleCountRenderTarget.reset(); } } for (auto &samplesMapMSTextures : mImplicitMSTextures) { for (mtl::TextureRef &perSampleCountMSTexture : samplesMapMSTextures.second) { perSampleCountMSTexture.reset(); } } for (mtl::TextureRef &view : mLevelViewsWithinBaseMax) { view.reset(); } if (!keepSamplerStateAndFormat) { mMetalSamplerState = nil; } } angle::Result TextureMtl::ensureNativeStorageCreated(const gl::Context *context) { if (mNativeTextureStorage) { return angle::Result::Continue; } // This should not be called from immutable texture. ASSERT(!isImmutableOrPBuffer()); ASSERT(mState.getType() != gl::TextureType::_2DMultisample); ASSERT(mState.getType() != gl::TextureType::_2DMultisampleArray); ContextMtl *contextMtl = mtl::GetImpl(context); // Create actual texture object: GLuint mips = mState.getMipmapMaxLevel() - mState.getEffectiveBaseLevel() + 1; gl::ImageDesc desc = mState.getBaseLevelDesc(); ANGLE_CHECK(contextMtl, desc.format.valid(), gl::err::kInternalError, GL_INVALID_OPERATION); angle::FormatID angleFormatId = angle::Format::InternalFormatToID(desc.format.info->sizedInternalFormat); mtl::Format format = contextMtl->getPixelFormat(angleFormatId); ANGLE_TRY(createNativeStorage(context, mState.getType(), mips, 0, desc.size, format)); // Transfer data from defined images to actual texture object int numCubeFaces = static_cast(mNativeTextureStorage->cubeFaces()); for (int face = 0; face < numCubeFaces; ++face) { for (mtl::MipmapNativeLevel actualMip = mtl::kZeroNativeMipLevel; actualMip.get() < mips; ++actualMip) { GLuint imageMipLevel = mNativeTextureStorage->getGLLevel(actualMip); 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 = mNativeTextureStorage->size(imageMipLevel); if (imageToTransfer && imageToTransfer->sizeAt0() == actualMipSize && imageToTransfer->arrayLength() == mNativeTextureStorage->arrayLength() && imageToTransfer->pixelFormat() == mNativeTextureStorage->pixelFormat()) { mtl::BlitCommandEncoder *encoder = GetBlitCommandEncoderForResources( contextMtl, {imageToTransfer.get(), mNativeTextureStorage->getNativeTexture().get()}); encoder->copyTexture(imageToTransfer, 0, mtl::kZeroNativeMipLevel, *mNativeTextureStorage, 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; } } } return angle::Result::Continue; } angle::Result TextureMtl::createNativeStorage(const gl::Context *context, gl::TextureType type, GLuint mips, GLuint samples, const gl::Extents &size, const mtl::Format &format) { ASSERT(samples == 0 || mips == 0); ContextMtl *contextMtl = mtl::GetImpl(context); // Create actual texture object: mSlices = 1; bool allowFormatView = format.hasDepthAndStencilBits() || needsFormatViewForPixelLocalStorage( contextMtl->getDisplay()->getNativePixelLocalStorageOptions(), format); mtl::TextureRef nativeTextureStorage; switch (type) { case gl::TextureType::_2D: ANGLE_TRY(mtl::Texture::Make2DTexture(contextMtl, format, size.width, size.height, mips, /** renderTargetOnly */ false, allowFormatView, &nativeTextureStorage)); break; case gl::TextureType::CubeMap: mSlices = 6; ANGLE_TRY(mtl::Texture::MakeCubeTexture(contextMtl, format, size.width, mips, /** renderTargetOnly */ false, allowFormatView, &nativeTextureStorage)); break; case gl::TextureType::_3D: ANGLE_TRY(mtl::Texture::Make3DTexture( contextMtl, format, size.width, size.height, size.depth, mips, /** renderTargetOnly */ false, allowFormatView, &nativeTextureStorage)); break; case gl::TextureType::_2DArray: mSlices = size.depth; ANGLE_TRY(mtl::Texture::Make2DArrayTexture( contextMtl, format, size.width, size.height, mips, mSlices, /** renderTargetOnly */ false, allowFormatView, &nativeTextureStorage)); break; case gl::TextureType::_2DMultisample: ANGLE_TRY(mtl::Texture::Make2DMSTexture( contextMtl, format, size.width, size.height, samples, /** renderTargetOnly */ false, allowFormatView, &nativeTextureStorage)); break; default: UNREACHABLE(); } if (mState.getImmutableFormat()) { mNativeTextureStorage = std::make_unique( std::move(nativeTextureStorage), /*baseGLLevel=*/0, format); } else { mNativeTextureStorage = std::make_unique( std::move(nativeTextureStorage), /*baseGLLevel=*/mState.getEffectiveBaseLevel(), format); } ANGLE_TRY(checkForEmulatedChannels(context, format, *mNativeTextureStorage)); ANGLE_TRY(createViewFromBaseToMaxLevel()); // 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; } ASSERT(mNativeTextureStorage); ContextMtl *contextMtl = mtl::GetImpl(context); mtl::SamplerDesc samplerDesc(mState.getSamplerState()); const mtl::Format &storageFormat = mNativeTextureStorage->getFormat(); if (storageFormat.actualAngleFormat().depthBits && !storageFormat.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; } // OpenGL ES 3.x: The rules for texel selection are modified // for cube maps so that texture wrap modes are ignored. if ((mState.getType() == gl::TextureType::CubeMap || mState.getType() == gl::TextureType::CubeMapArray) && context->getState().getClientVersion() >= gl::ES_3_0) { samplerDesc.rAddressMode = MTLSamplerAddressModeClampToEdge; samplerDesc.sAddressMode = MTLSamplerAddressModeClampToEdge; samplerDesc.tAddressMode = MTLSamplerAddressModeClampToEdge; } mMetalSamplerState = contextMtl->getDisplay()->getStateCache().getSamplerState( contextMtl->getMetalDevice(), samplerDesc); return angle::Result::Continue; } angle::Result TextureMtl::createViewFromBaseToMaxLevel() { ASSERT(mNativeTextureStorage); uint32_t maxLevel = std::min(mNativeTextureStorage->getMaxSupportedGLLevel(), mState.getEffectiveMaxLevel()); // In edge case where base level > max level, clamp up to base level. maxLevel = std::max(maxLevel, mState.getEffectiveBaseLevel()); mtl::TextureRef nativeViewFromBaseToMaxLevelRef; if (maxLevel == mNativeTextureStorage->getMaxSupportedGLLevel() && mState.getEffectiveBaseLevel() == mNativeTextureStorage->getBaseGLLevel()) { // If base & max level are the same in mNativeTextureStorage, we don't need // a dedicated view. Furthermore, Intel driver has some bugs when sampling a view // of a stencil texture. nativeViewFromBaseToMaxLevelRef = mNativeTextureStorage->getNativeTexture(); } else { uint32_t baseToMaxLevels = maxLevel - mState.getEffectiveBaseLevel() + 1; nativeViewFromBaseToMaxLevelRef = mNativeTextureStorage->createMipsView(mState.getEffectiveBaseLevel(), baseToMaxLevels); } mViewFromBaseToMaxLevel = std::make_unique( nativeViewFromBaseToMaxLevelRef, mState.getEffectiveBaseLevel(), mNativeTextureStorage->getFormat()); // Recreate in bindToShader() mSwizzleStencilSamplingView = nullptr; return angle::Result::Continue; } angle::Result TextureMtl::onBaseMaxLevelsChanged(const gl::Context *context) { if (!mNativeTextureStorage) { return angle::Result::Continue; } if (isImmutableOrPBuffer()) { // For immutable texture, only recreate base-max view. ANGLE_TRY(createViewFromBaseToMaxLevel()); // Invalidate base-max per level views so that they can be recreated // in generateMipmap() for (mtl::TextureRef &view : mLevelViewsWithinBaseMax) { view.reset(); } return angle::Result::Continue; } // Account for clamping in createViewFromBaseToMaxLevel() to avoid redundant storage // re-creation when max < base (e.g., base=7 with max toggling 1->2->3 should not re-create). GLuint effectiveMaxForStorage = std::max(mState.getMipmapMaxLevel(), mState.getEffectiveBaseLevel()); if (mState.getEffectiveBaseLevel() == mNativeTextureStorage->getBaseGLLevel() && effectiveMaxForStorage == mNativeTextureStorage->getMaxSupportedGLLevel()) { ASSERT(mState.getBaseLevelDesc().size == mNativeTextureStorage->sizeAt0()); // If effective level range remains the same, don't recreate the texture storage. // This might feel unnecessary at first since the front-end might prevent redundant base/max // level change already. However, there are cases that cause native storage to be created // before base/max level dirty bit is passed to Metal backend and lead to unwanted problems. // Example: // 1. texture with a non-default base/max level state is set. // 2. The texture is used first as a framebuffer attachment. This operation does not fully // sync the texture state and therefore does not unset base/max level dirty bits. // 3. The same texture is then used for sampling; this operation fully syncs the texture // state. Base/max level dirty bits may lead to recreating the texture storage thus // invalidating native render target references created in step 2. // 4. If the framebuffer created in step 2 is used again, its native render target // references will not be updated to point to the new storage because everything is in // sync from the frontend point of view. // 5. Note: if the new range is different, it is expected that native render target // references will be updated during draw framebuffer sync. return angle::Result::Continue; } ContextMtl *contextMtl = mtl::GetImpl(context); // We need to recreate a new native texture storage with number of levels = max level - base // level + 1. This can be achieved by simply deleting the old storage. The storage will be // lazily recreated later via ensureNativeStorageCreated(). // Note: We release the native texture storage but keep old image definitions. So that when the // storage is recreated, its levels can be recreated with data from the old image definitions // respectively. deallocateNativeStorage(/*keepImages=*/true, /*keepSamplerStateAndFormat=*/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) { ContextMtl *contextMtl = mtl::GetImpl(context); // Image at this level hasn't been defined yet. We need to define it: const gl::ImageDesc &desc = mState.getImageDesc(index); angle::FormatID angleFormatId = angle::Format::InternalFormatToID(desc.format.info->sizedInternalFormat); mtl::Format format = contextMtl->getPixelFormat(angleFormatId); ANGLE_TRY(redefineImage(context, index, format, desc.size)); } return angle::Result::Continue; } angle::Result TextureMtl::ensureLevelViewsWithinBaseMaxCreated() { ASSERT(mViewFromBaseToMaxLevel); for (mtl::MipmapNativeLevel mip = mtl::kZeroNativeMipLevel; mip.get() < mViewFromBaseToMaxLevel->mipmapLevels(); ++mip) { if (mLevelViewsWithinBaseMax[mip]) { continue; } GLuint mipGLLevel = mViewFromBaseToMaxLevel->getGLLevel(mip); if (mViewFromBaseToMaxLevel->textureType() != MTLTextureTypeCube && mTexImageDefs[0][mipGLLevel].image) { // Reuse texture image view. mLevelViewsWithinBaseMax[mip] = mTexImageDefs[0][mipGLLevel].image; } else { mLevelViewsWithinBaseMax[mip] = mNativeTextureStorage->createMipView(mipGLLevel); } } return angle::Result::Continue; } mtl::TextureRef TextureMtl::createImageViewFromTextureStorage(GLuint cubeFaceOrZero, GLuint glLevel) { mtl::TextureRef image; if (mNativeTextureStorage->textureType() == MTLTextureTypeCube) { // Cube texture's image is per face. image = mNativeTextureStorage->createSliceMipView(cubeFaceOrZero, glLevel); } else { if (mViewFromBaseToMaxLevel->isGLLevelSupported(glLevel)) { mtl::MipmapNativeLevel nativeLevel = mViewFromBaseToMaxLevel->getNativeLevel(glLevel); if (mLevelViewsWithinBaseMax[nativeLevel]) { // Reuse the native level view image = mLevelViewsWithinBaseMax[nativeLevel]; } } if (!image) { image = mNativeTextureStorage->createMipView(glLevel); } } return image; } void TextureMtl::retainImageDefinitions() { if (!mNativeTextureStorage) { return; } const GLuint mips = mNativeTextureStorage->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 = mNativeTextureStorage->getGLLevel(mip); ImageDefinitionMtl &imageDef = mTexImageDefs[face][imageMipLevel]; if (imageDef.image) { continue; } imageDef.image = createImageViewFromTextureStorage(face, imageMipLevel); imageDef.formatID = mNativeTextureStorage->getFormat().intendedFormatId; } } } 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 && mNativeTextureStorage) { // 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 (!mNativeTextureStorage->isGLLevelSupported(imageIndex.getLevelIndex())) { // Image outside native texture's mip levels is skipped. return imageDef; } imageDef.image = createImageViewFromTextureStorage(cubeFaceOrZero, imageIndex.getLevelIndex()); imageDef.formatID = mNativeTextureStorage->getFormat().intendedFormatId; } return imageDef; } angle::Result TextureMtl::getRenderTarget(ContextMtl *context, const gl::ImageIndex &imageIndex, GLsizei implicitSamples, RenderTargetMtl **renderTargetOut) { ASSERT(imageIndex.getType() == gl::TextureType::_2D || imageIndex.getType() == gl::TextureType::Rectangle || imageIndex.getType() == gl::TextureType::_2DMultisample || imageIndex.hasLayer()); const gl::RenderToTextureImageIndex renderToTextureIndex = implicitSamples <= 1 ? gl::RenderToTextureImageIndex::Default : static_cast(PackSampleCount(implicitSamples)); ImageDefinitionMtl &imageDef = getImageDefinition(imageIndex); mtl::Format format = context->getPixelFormat(imageDef.formatID); GLuint layer = GetImageLayerIndexFrom(imageIndex); RenderTargetMtl &rtt = mRenderTargets[imageIndex][renderToTextureIndex]; if (!rtt.getTexture()) { // Lazy initialization of render target: if (imageDef.image) { if (imageIndex.getType() == gl::TextureType::CubeMap) { // Cube map is special, the image is already the view of its layer rtt.set(imageDef.image, mtl::kZeroNativeMipLevel, 0, format); } else { rtt.set(imageDef.image, mtl::kZeroNativeMipLevel, layer, format); } } } if (implicitSamples > 1 && !rtt.getImplicitMSTexture()) { // This format must supports implicit resolve ANGLE_CHECK(context, format.getCaps().resolve, gl::err::kInternalError, GL_INVALID_VALUE); mtl::TextureRef &msTexture = mImplicitMSTextures[imageIndex][renderToTextureIndex]; if (!msTexture) { const gl::ImageDesc &desc = mState.getImageDesc(imageIndex); ANGLE_TRY(mtl::Texture::MakeMemoryLess2DMSTexture( context, format, desc.size.width, desc.size.height, implicitSamples, &msTexture)); } rtt.setImplicitMSTexture(msTexture); } *renderTargetOut = &rtt; return angle::Result::Continue; } 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, GL_NONE, 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, mState.getImmutableLevels(), 0, 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) { 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, 0, mState.getLevelZeroDesc().samples, mtlFormat, size); } angle::Result TextureMtl::setEGLImageTarget(const gl::Context *context, gl::TextureType type, egl::Image *image) { deallocateNativeStorage(/*keepImages=*/false); ContextMtl *contextMtl = mtl::GetImpl(context); ImageMtl *imageMtl = mtl::GetImpl(image); if (type != imageMtl->getImageTextureType()) { return angle::Result::Stop; } const angle::FormatID angleFormatId = angle::Format::InternalFormatToID(image->getFormat().info->sizedInternalFormat); mtl::Format imageFormat = contextMtl->getPixelFormat(angleFormatId); mNativeTextureStorage = std::make_unique( imageMtl->getTexture(), /*baseGLLevel=*/0, imageFormat); mSlices = mNativeTextureStorage->cubeFacesOrArrayLength(); ANGLE_TRY(ensureSamplerStateCreated(context)); ANGLE_TRY(createViewFromBaseToMaxLevel()); // 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(ensureNativeStorageCreated(context)); ContextMtl *contextMtl = mtl::GetImpl(context); if (!mViewFromBaseToMaxLevel) { return angle::Result::Continue; } const mtl::Format &format = mNativeTextureStorage->getFormat(); const mtl::FormatCaps &caps = format.getCaps(); bool sRGB = format.actualInternalFormat().colorEncoding == GL_SRGB; bool avoidGPUPath = contextMtl->getDisplay()->getFeatures().forceNonCSBaseMipmapGeneration.enabled && mViewFromBaseToMaxLevel->widthAt0() < 5; if (!avoidGPUPath && caps.writable && mState.getType() == gl::TextureType::_3D) { // http://anglebug.com/42263496. // Use compute for 3D mipmap generation. ANGLE_TRY(ensureLevelViewsWithinBaseMaxCreated()); ANGLE_TRY(contextMtl->getDisplay()->getUtils().generateMipmapCS( contextMtl, *mViewFromBaseToMaxLevel, sRGB, &mLevelViewsWithinBaseMax)); } else if (!avoidGPUPath && caps.filterable && caps.colorRenderable) { mtl::BlitCommandEncoder *blitEncoder = GetBlitCommandEncoderForResources( contextMtl, {mViewFromBaseToMaxLevel->getNativeTexture().get()}); blitEncoder->generateMipmapsForTexture(*mViewFromBaseToMaxLevel); } else { ANGLE_TRY(generateMipmapCPU(context)); } return angle::Result::Continue; } angle::Result TextureMtl::generateMipmapCPU(const gl::Context *context) { ASSERT(mViewFromBaseToMaxLevel); ContextMtl *contextMtl = mtl::GetImpl(context); const angle::Format &angleFormat = mViewFromBaseToMaxLevel->getFormat().actualAngleFormat(); // This format must have mip generation function. ANGLE_CHECK(contextMtl, angleFormat.mipGenerationFunction, gl::err::kInternalError, GL_INVALID_OPERATION); for (uint32_t slice = 0; slice < mSlices; ++slice) { GLuint baseGLLevel = mViewFromBaseToMaxLevel->getBaseGLLevel(); uint32_t prevLevelWidth = mViewFromBaseToMaxLevel->widthAt0(); uint32_t prevLevelHeight = mViewFromBaseToMaxLevel->heightAt0(); uint32_t prevLevelDepth = mViewFromBaseToMaxLevel->depthAt0(); size_t prevLevelRowPitch = angleFormat.pixelBytes * prevLevelWidth; size_t prevLevelDepthPitch = prevLevelRowPitch * prevLevelHeight; angle::MemoryBuffer prevLevelData; ANGLE_CHECK_GL_ALLOC(contextMtl, prevLevelData.resize(prevLevelDepthPitch * prevLevelDepth)); angle::MemoryBuffer dstLevelData; // Download base level data mViewFromBaseToMaxLevel->getBytes( contextMtl, prevLevelRowPitch, prevLevelDepthPitch, MTLRegionMake3D(0, 0, 0, prevLevelWidth, prevLevelHeight, prevLevelDepth), baseGLLevel, slice, prevLevelData.span()); for (GLuint mip = 1; mip < mViewFromBaseToMaxLevel->mipmapLevels(); ++mip) { GLuint glLevel = baseGLLevel + mip; uint32_t dstWidth = mViewFromBaseToMaxLevel->width(glLevel); uint32_t dstHeight = mViewFromBaseToMaxLevel->height(glLevel); uint32_t dstDepth = mViewFromBaseToMaxLevel->depth(glLevel); size_t dstRowPitch = angleFormat.pixelBytes * dstWidth; size_t dstDepthPitch = dstRowPitch * dstHeight; size_t dstDataSize = dstDepthPitch * dstDepth; ANGLE_CHECK_GL_ALLOC(contextMtl, dstLevelData.resize(dstDataSize)); // Generate mip level angleFormat.mipGenerationFunction(prevLevelWidth, prevLevelHeight, prevLevelDepth, prevLevelData.data(), prevLevelRowPitch, prevLevelDepthPitch, dstLevelData.data(), dstRowPitch, dstDepthPitch); mtl::MipmapNativeLevel nativeLevel = mViewFromBaseToMaxLevel->getNativeLevel(glLevel); // Upload to texture ANGLE_TRY(UploadTextureContents(context, angleFormat, MTLRegionMake3D(0, 0, 0, dstWidth, dstHeight, dstDepth), nativeLevel, slice, dstLevelData.data(), dstRowPitch, dstDepthPitch, false, *mViewFromBaseToMaxLevel)); prevLevelWidth = dstWidth; prevLevelHeight = dstHeight; prevLevelDepth = dstDepth; prevLevelRowPitch = dstRowPitch; prevLevelDepthPitch = dstDepthPitch; std::swap(prevLevelData, dstLevelData); } // for mip level } // For layers return angle::Result::Continue; } bool TextureMtl::needsFormatViewForPixelLocalStorage(const ShPixelLocalStorageOptions &plsOptions, const mtl::Format &format) const { // On iOS devices with GPU family 5 and later, Metal doesn't apply lossless compression to // the texture if we set MTLTextureUsagePixelFormatView. This shouldn't be a problem though // because iOS devices implement pixel local storage with framebuffer fetch instead of shader // images. if (plsOptions.type == ShPixelLocalStorageType::ImageLoadStore) { switch (format.metalFormat) { case MTLPixelFormatRGBA8Unorm: case MTLPixelFormatRGBA8Uint: case MTLPixelFormatRGBA8Sint: return !plsOptions.supportsNativeRGBA8ImageFormats; default: break; } } return false; } bool TextureMtl::isImmutableOrPBuffer() const { return mState.getImmutableFormat() || mBoundSurface; } angle::Result TextureMtl::setBaseLevel(const gl::Context *context, GLuint baseLevel) { return onBaseMaxLevelsChanged(context); } angle::Result TextureMtl::bindTexImage(const gl::Context *context, egl::Surface *surface) { deallocateNativeStorage(/*keepImages=*/false); mBoundSurface = surface; auto pBuffer = GetImplAs(surface); mtl::Format pbufferFormat = pBuffer->getColorFormat(); mNativeTextureStorage = std::make_unique( pBuffer->getColorTexture(), /*baseGLLevel=*/0, pbufferFormat); mSlices = mNativeTextureStorage->cubeFacesOrArrayLength(); ANGLE_TRY(ensureSamplerStateCreated(context)); ANGLE_TRY(createViewFromBaseToMaxLevel()); // Tell context to rebind textures ContextMtl *contextMtl = mtl::GetImpl(context); contextMtl->invalidateCurrentTextures(); return angle::Result::Continue; } angle::Result TextureMtl::releaseTexImage(const gl::Context *context) { deallocateNativeStorage(/*keepImages=*/false); mBoundSurface = nullptr; return angle::Result::Continue; } angle::Result TextureMtl::getAttachmentRenderTarget(const gl::Context *context, GLenum binding, const gl::ImageIndex &imageIndex, GLsizei samples, FramebufferAttachmentRenderTarget **rtOut) { ANGLE_TRY(ensureNativeStorageCreated(context)); ContextMtl *contextMtl = mtl::GetImpl(context); ANGLE_CHECK(contextMtl, mNativeTextureStorage, gl::err::kInternalError, GL_INVALID_OPERATION); RenderTargetMtl *rtt; ANGLE_TRY(getRenderTarget(contextMtl, imageIndex, samples, &rtt)); *rtOut = rtt; 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: case gl::Texture::DIRTY_BIT_DEPTH_STENCIL_TEXTURE_MODE: { // Recreate swizzle/stencil view. mSwizzleStencilSamplingView = nullptr; } break; default: break; } } ANGLE_TRY(ensureNativeStorageCreated(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(mNativeTextureStorage); ASSERT(mViewFromBaseToMaxLevel); float minLodClamp; float maxLodClamp; id samplerState; if (!mSwizzleStencilSamplingView) { ContextMtl *contextMtl = mtl::GetImpl(context); const angle::FeaturesMtl &features = contextMtl->getDisplay()->getFeatures(); const mtl::Format &format = mNativeTextureStorage->getFormat(); // Sampling from unused channels of depth and stencil textures is undefined in Metal. // ANGLE relies on swizzled views to enforce values required by OpenGL ES specs. Some // drivers fail to sample from a swizzled view of a stencil texture so skip this step. const bool skipStencilSwizzle = format.actualFormatId == angle::FormatID::S8_UINT && features.avoidStencilTextureSwizzle.enabled; if (!skipStencilSwizzle && (mState.getSwizzleState().swizzleRequired() || format.actualAngleFormat().hasDepthOrStencilBits() || format.swizzled) && features.hasTextureSwizzle.enabled) { const gl::InternalFormat &glInternalFormat = *mState.getBaseLevelDesc().format.info; MTLTextureSwizzleChannels swizzle = MTLTextureSwizzleChannelsMake( mtl::GetTextureSwizzle(OverrideSwizzleValue( context, mState.getSwizzleState().swizzleRed, format, glInternalFormat)), mtl::GetTextureSwizzle(OverrideSwizzleValue( context, mState.getSwizzleState().swizzleGreen, format, glInternalFormat)), mtl::GetTextureSwizzle(OverrideSwizzleValue( context, mState.getSwizzleState().swizzleBlue, format, glInternalFormat)), mtl::GetTextureSwizzle(OverrideSwizzleValue( context, mState.getSwizzleState().swizzleAlpha, format, glInternalFormat))); MTLPixelFormat pixelFormat = mViewFromBaseToMaxLevel->pixelFormat(); if (mState.isStencilMode()) { if (pixelFormat == MTLPixelFormatDepth32Float_Stencil8) { pixelFormat = MTLPixelFormatX32_Stencil8; } #if TARGET_OS_OSX || TARGET_OS_MACCATALYST else if (pixelFormat == MTLPixelFormatDepth24Unorm_Stencil8) { pixelFormat = MTLPixelFormatX24_Stencil8; } #endif } mSwizzleStencilSamplingView = mNativeTextureStorage->createMipsSwizzleView( mViewFromBaseToMaxLevel->getBaseGLLevel(), mViewFromBaseToMaxLevel->mipmapLevels(), pixelFormat, swizzle); } else { mSwizzleStencilSamplingView = mState.isStencilMode() ? mViewFromBaseToMaxLevel->getStencilView() : mViewFromBaseToMaxLevel->getNativeTexture(); } } 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, mSwizzleStencilSamplingView, textureSlotIndex); cmdEncoder->setSamplerState(shaderType, samplerState, minLodClamp, maxLodClamp, samplerSlotIndex); return angle::Result::Continue; } angle::Result TextureMtl::bindToShaderImage(const gl::Context *context, mtl::RenderCommandEncoder *cmdEncoder, gl::ShaderType shaderType, int textureSlotIndex, int level, int layer, GLenum format) { ASSERT(mNativeTextureStorage); ASSERT(mState.getImmutableFormat()); ASSERT(0 <= level && static_cast(level) < mState.getImmutableLevels()); ASSERT(0 <= layer && static_cast(layer) < mSlices); ContextMtl *contextMtl = mtl::GetImpl(context); angle::FormatID angleFormatId = angle::Format::InternalFormatToID(format); mtl::Format imageAccessFormat = contextMtl->getPixelFormat(angleFormatId); LayerLevelTextureViewVector &textureViewVector = mShaderImageViews[imageAccessFormat.metalFormat]; mtl::TextureRef &textureRef = GetLayerLevelTextureView(&textureViewVector, layer, level, mSlices, mState.getImmutableLevels()); if (textureRef == nullptr) { textureRef = mNativeTextureStorage->createShaderImageView2D(level, layer, imageAccessFormat.metalFormat); } cmdEncoder->setRWTexture(shaderType, textureRef, textureSlotIndex); 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 imageWithinNativeStorageLevels = false; if (mNativeTextureStorage && mNativeTextureStorage->isGLLevelSupported(index.getLevelIndex())) { imageWithinNativeStorageLevels = true; GLuint glLevel = index.getLevelIndex(); // 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. ASSERT(mNativeTextureStorage->textureType() == mtl::GetTextureType(index.getType())); if (mNativeTextureStorage->getFormat() != mtlFormat || size != mNativeTextureStorage->size(glLevel)) { // Keep other images deallocateNativeStorage(/*keepImages=*/true); } } // Early-out on empty textures, don't create a zero-sized storage. if (size.empty()) { return angle::Result::Continue; } ContextMtl *contextMtl = mtl::GetImpl(context); ImageDefinitionMtl &imageDef = getImageDefinition(index); // If native texture still exists, it means the size hasn't been changed, no need to create new // image if (mNativeTextureStorage && imageDef.image && imageWithinNativeStorageLevels) { ASSERT(imageDef.image->textureType() == mtl::GetTextureType(GetTextureImageType(index.getType())) && imageDef.formatID == mNativeTextureStorage->getFormat().intendedFormatId && imageDef.image->sizeAt0() == size); } else { imageDef.formatID = mtlFormat.intendedFormatId; bool allowFormatView = mtlFormat.hasDepthAndStencilBits() || needsFormatViewForPixelLocalStorage( contextMtl->getDisplay()->getNativePixelLocalStorageOptions(), mtlFormat); // 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, &imageDef.image)); break; case gl::TextureType::_3D: ANGLE_TRY(mtl::Texture::Make3DTexture( contextMtl, mtlFormat, size.width, size.height, size.depth, 1, /** renderTargetOnly */ false, allowFormatView, &imageDef.image)); break; case gl::TextureType::_2DArray: ANGLE_TRY(mtl::Texture::Make2DArrayTexture( contextMtl, mtlFormat, size.width, size.height, 1, size.depth, /** renderTargetOnly */ false, allowFormatView, &imageDef.image)); break; default: UNREACHABLE(); } // Make sure emulated channels are properly initialized in this newly allocated texture. ANGLE_TRY(checkForEmulatedChannels(context, mtlFormat, imageDef.image)); } // Tell context to rebind textures contextMtl->invalidateCurrentTextures(); return angle::Result::Continue; } angle::Result TextureMtl::setStorageImpl(const gl::Context *context, gl::TextureType type, GLuint mips, GLuint samples, const mtl::Format &mtlFormat, const gl::Extents &size) { // Don't need to hold old images data. deallocateNativeStorage(/*keepImages=*/false); ContextMtl *contextMtl = mtl::GetImpl(context); // Tell context to rebind textures contextMtl->invalidateCurrentTextures(); ANGLE_TRY(createNativeStorage(context, type, mips, samples, size, mtlFormat)); ANGLE_TRY(createViewFromBaseToMaxLevel()); 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)); ImageDefinitionMtl &imageDef = getImageDefinition(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)); // 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, imageDef)); } } 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, imageDef)); } 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 ImageDefinitionMtl &imageDef) { // If source pixels are luminance or RGB8, we need to convert them to RGBA ContextMtl *contextMtl = mtl::GetImpl(context); mtl::Format imageFormat = contextMtl->getPixelFormat(imageDef.formatID); if (imageFormat.needConversion(pixelsAngleFormat.id)) { return convertAndSetPerSliceSubImage(context, slice, mtlArea, internalFormat, type, pixelsAngleFormat, pixelsRowPitch, pixelsDepthPitch, unpackBuffer, pixels, imageDef); } // No conversion needed. 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 % imageFormat.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, imageDef); } BufferMtl *unpackBufferMtl = mtl::GetImpl(unpackBuffer); if (imageFormat.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->getBufferDataReadOnly(contextMtl, offset).data(); ANGLE_TRY(UploadTextureContents( context, imageFormat.actualAngleFormat(), mtlArea, mtl::kZeroNativeMipLevel, slice, clientData, pixelsRowPitch, pixelsDepthPitch, false, imageDef.image)); } else { mtl::BufferRef sourceBuffer = unpackBufferMtl->getCurrentBuffer(); // PVRTC1 blocks are stored in a reflected Morton order // and need to be linearized for buffer uploads in Metal. // This step is skipped for textures that have only one block. if (imageFormat.isPVRTC() && mtlArea.size.height > 4) { // PVRTC1 inherent requirement. ASSERT(gl::isPow2(mtlArea.size.width) && gl::isPow2(mtlArea.size.height)); // Metal-specific limitation enforced by ANGLE validation. ASSERT(mtlArea.size.width == mtlArea.size.height); static_assert(gl::IMPLEMENTATION_MAX_2D_TEXTURE_SIZE <= 262144, "The current kernel can handle up to 65536 blocks per dimension."); // Current command buffer implementation does not support 64-bit offsets. ANGLE_CHECK_GL_MATH(contextMtl, offset <= std::numeric_limits::max()); mtl::BufferRef stagingBuffer; ANGLE_TRY(mtl::Buffer::MakeBuffer(contextMtl, pixelsDepthPitch, &stagingBuffer)); mtl::BlockLinearizationParams params; params.srcBuffer = sourceBuffer; params.dstBuffer = stagingBuffer; params.srcBufferOffset = static_cast(offset); params.blocksWide = static_cast(mtlArea.size.width) / internalFormat.compressedBlockWidth; params.blocksHigh = static_cast(mtlArea.size.height) / internalFormat.compressedBlockHeight; // PVRTC1 textures always have at least 2 blocks in each dimension. // Enforce correct block layout for 8x8 textures that use 8x4 blocks. params.blocksWide = std::max(params.blocksWide, 2u); ANGLE_TRY(contextMtl->getDisplay()->getUtils().linearizeBlocks(contextMtl, params)); sourceBuffer = stagingBuffer; offset = 0; } else if (pixelsAngleFormat.id == angle::FormatID::D32_FLOAT) { // Current command buffer implementation does not support 64-bit offsets. ANGLE_CHECK_GL_MATH(contextMtl, offset <= std::numeric_limits::max()); size_t imageSize = pixelsRowPitch * mtlArea.size.height; mtl::BufferRef stagingBuffer; ANGLE_TRY(mtl::Buffer::MakeBuffer(contextMtl, imageSize, &stagingBuffer)); ASSERT(pixelsAngleFormat.pixelBytes == 4 && offset % 4 == 0); ANGLE_TRY(SaturateDepth(contextMtl, sourceBuffer, stagingBuffer, static_cast(offset), static_cast(pixelsRowPitch) / 4, mtlArea.size)); sourceBuffer = stagingBuffer; offset = 0; } size_t srcBytesPerImage = mtlArea.size.depth > 1 ? pixelsDepthPitch : 0; // Use blit encoder to copy mtl::BlitCommandEncoder *blitEncoder = GetBlitCommandEncoderForResources( contextMtl, {sourceBuffer.get(), imageDef.image.get()}); CopyBufferToOriginalTextureIfDstIsAView( contextMtl, blitEncoder, sourceBuffer, offset, pixelsRowPitch, srcBytesPerImage, mtlArea.size, imageDef.image, slice, mtl::kZeroNativeMipLevel, mtlArea.origin, imageFormat.isPVRTC() ? MTLBlitOptionRowLinearPVRTC : MTLBlitOptionNone); } } else { ANGLE_TRY(UploadTextureContents(context, imageFormat.actualAngleFormat(), mtlArea, mtl::kZeroNativeMipLevel, slice, pixels, pixelsRowPitch, pixelsDepthPitch, false, imageDef.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 ImageDefinitionMtl &imageDef) { ASSERT(imageDef.image && imageDef.image->valid()); ContextMtl *contextMtl = mtl::GetImpl(context); mtl::Format imageFormat = contextMtl->getPixelFormat(imageDef.formatID); if (unpackBuffer) { ANGLE_CHECK_GL_MATH(contextMtl, reinterpret_cast(pixels) <= std::numeric_limits::max()); uint32_t offset = static_cast(reinterpret_cast(pixels)); BufferMtl *unpackBufferMtl = mtl::GetImpl(unpackBuffer); if (!imageFormat.getCaps().isRenderable() || imageFormat.hasDepthOrStencilBits() || imageFormat.intendedAngleFormat().isBlock || !contextMtl->getDisplay()->getUtils().isPixelsUnpackSupported(pixelsAngleFormat)) { // Unsupported format, use CPU path. const uint8_t *clientData = unpackBufferMtl->getBufferDataReadOnly(contextMtl, offset).data(); ANGLE_TRY(convertAndSetPerSliceSubImage( context, slice, mtlArea, internalFormat, type, pixelsAngleFormat, pixelsRowPitch, pixelsDepthPitch, nullptr, clientData, imageDef)); } else { // Use shaders mtl::CopyPixelsFromBufferParams params; params.buffer = unpackBufferMtl->getCurrentBuffer(); params.bufferStartOffset = offset; params.bufferRowPitch = static_cast(pixelsRowPitch); params.bufferDepthPitch = static_cast(pixelsDepthPitch); params.texture = imageDef.image; params.textureArea = mtl::MTLRegionToGLRect(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 || mtlArea.origin.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.textureSliceOrDepth = std::max(slice, static_cast(mtlArea.origin.z)); for (uint32_t z = 0; z < static_cast(mtlArea.size.depth); ++z) { ANGLE_TRY(contextMtl->getDisplay()->getUtils().unpackPixelsWithDraw( context, pixelsAngleFormat, params)); params.textureSliceOrDepth++; params.bufferStartOffset += params.bufferDepthPitch; } } } // if (unpackBuffer) else { LoadImageFunctionInfo loadFunctionInfo = imageFormat.textureLoadFunctions ? imageFormat.textureLoadFunctions(type) : LoadImageFunctionInfo(); const angle::Format &dstFormat = angle::Format::Get(imageFormat.actualFormatId); size_t dstRowPitch; size_t dstDepthPitch; if (dstFormat.isBlock) { const gl::InternalFormat &dstFormatInfo = gl::GetSizedInternalFormatInfo(dstFormat.glInternalFormat); GLuint rowPitch; ANGLE_CHECK_GL_MATH(contextMtl, dstFormatInfo.computeCompressedImageRowPitch( static_cast(mtlArea.size.width), &rowPitch)); dstRowPitch = rowPitch; GLuint depthPitch; ANGLE_CHECK_GL_MATH(contextMtl, dstFormatInfo.computeCompressedImageDepthPitch( static_cast(mtlArea.size.height), static_cast(dstRowPitch), &depthPitch)); dstDepthPitch = depthPitch; } else { dstRowPitch = dstFormat.pixelBytes * mtlArea.size.width; dstDepthPitch = dstRowPitch * mtlArea.size.height; } // It is very important to avoid allocating a new buffer for each row during these // uploads. const bool kAvoidStagingBuffers = true; // Check if original image data is compressed: if (imageFormat.intendedAngleFormat().isBlock) { if (imageFormat.intendedFormatId != imageFormat.actualFormatId) { ASSERT(loadFunctionInfo.loadFunction); // Need to create a buffer to hold entire decompressed image. angle::MemoryBuffer decompressBuf; ANGLE_CHECK_GL_ALLOC(contextMtl, decompressBuf.resize(dstDepthPitch * mtlArea.size.depth)); // Decompress loadFunctionInfo.loadFunction(contextMtl->getImageLoadContext(), 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, kAvoidStagingBuffers, imageDef.image)); } else { // Assert that we're filling the level in it's entierety. ASSERT(mtlArea.size.width == static_cast(imageDef.image->sizeAt0().width)); ASSERT(mtlArea.size.height == static_cast(imageDef.image->sizeAt0().height)); ANGLE_TRY(UploadTextureContents( context, dstFormat, mtlArea, mtl::kZeroNativeMipLevel, slice, pixels, dstRowPitch, dstDepthPitch, kAvoidStagingBuffers, imageDef.image)); } } // if (imageFormat.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(contextMtl->getImageLoadContext(), mtlRow.size.width, 1, 1, psrc, pixelsRowPitch, 0, conversionRow.data(), dstRowPitch, 0); } else if (imageFormat.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, kAvoidStagingBuffers, imageDef.image)); } } } // if (imageFormat.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, GLenum binding, 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, GL_NONE, 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, GL_NONE, 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)); ContextMtl *contextMtl = mtl::GetImpl(context); mtl::Format format = contextMtl->getPixelFormat( angle::Format::InternalFormatToID(internalFormat.sizedInternalFormat)); if (!format.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(); RenderTargetMtl *imageRtt; ANGLE_TRY(getRenderTarget(contextMtl, index, /*implicitSamples=*/0, &imageRtt)); 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); mtl::Format format = contextMtl->getPixelFormat( angle::Format::InternalFormatToID(internalFormat.sizedInternalFormat)); const angle::Format &dstFormat = angle::Format::Get(format.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(); } // It is very important to avoid allocating a new buffer for each row during these // uploads. const bool kAvoidStagingBuffers = true; // 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, kAvoidStagingBuffers, 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(); mtl::Format format = contextMtl->getPixelFormat( angle::Format::InternalFormatToID(internalFormat.sizedInternalFormat)); if (!format.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; blitParams.transformLinearToSrgb = sourceAngleFormat.isSRGB; mtl::Format format = contextMtl->getPixelFormat( angle::Format::InternalFormatToID(internalFormat.sizedInternalFormat)); return displayMtl->getUtils().copyTextureWithDraw(context, cmdEncoder, sourceAngleFormat, format.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); mtl::Format format = contextMtl->getPixelFormat( angle::Format::InternalFormatToID(internalFormat.sizedInternalFormat)); const angle::Format &dstAngleFormat = format.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.span()); // 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, false, image)); return angle::Result::Continue; } } // namespace rx