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