blob: 288b3705df82ec58a88cdb5f4df89e0e1cd5bab3 [file] [log] [blame]
//
// 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 &regionSize,
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 &regionSize,
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 &regionSize,
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 &regionSize,
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 &regionSizeInBlocks,
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 &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());
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 &currentFormat = *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 &currentFormat = *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