Commit 53e45360 by Le Hoang Quyen Committed by Commit Bot

Metal: support texture's incomplete image definitions.

glTexImage*, glCopyImage* will copy data to the image at the respective index, then during draw call, the image data will be transferred to real Metal texture. Test done: MipmapTest.DefineValidExtraLevelAndUseItLater For implementation notes, see src/libANGLE/renderer/metal/doc/TextureDataCompleteness.md Bug: angleproject:2634 Change-Id: I0ca24c8beff2e109a794260c436985e9f4650d83 Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/1906609 Commit-Queue: Jamie Madill <jmadill@chromium.org> Reviewed-by: 's avatarJamie Madill <jmadill@chromium.org>
parent 24d7e23e
...@@ -300,6 +300,7 @@ class ContextMtl : public ContextImpl, public mtl::Context ...@@ -300,6 +300,7 @@ class ContextMtl : public ContextImpl, public mtl::Context
void updateVertexArray(const gl::Context *context); void updateVertexArray(const gl::Context *context);
angle::Result updateDefaultAttribute(size_t attribIndex); angle::Result updateDefaultAttribute(size_t attribIndex);
angle::Result handleDirtyActiveTextures(const gl::Context *context);
angle::Result handleDirtyDefaultAttribs(const gl::Context *context); angle::Result handleDirtyDefaultAttribs(const gl::Context *context);
angle::Result handleDirtyDriverUniforms(const gl::Context *context); angle::Result handleDirtyDriverUniforms(const gl::Context *context);
angle::Result handleDirtyDepthStencilState(const gl::Context *context); angle::Result handleDirtyDepthStencilState(const gl::Context *context);
......
...@@ -572,6 +572,8 @@ angle::Result ContextMtl::syncState(const gl::Context *context, ...@@ -572,6 +572,8 @@ angle::Result ContextMtl::syncState(const gl::Context *context,
case gl::State::DIRTY_BIT_ATOMIC_COUNTER_BUFFER_BINDING: case gl::State::DIRTY_BIT_ATOMIC_COUNTER_BUFFER_BINDING:
break; break;
case gl::State::DIRTY_BIT_IMAGE_BINDINGS: case gl::State::DIRTY_BIT_IMAGE_BINDINGS:
// NOTE(hqle): properly handle GLSL images.
invalidateCurrentTextures();
break; break;
case gl::State::DIRTY_BIT_MULTISAMPLING: case gl::State::DIRTY_BIT_MULTISAMPLING:
// NOTE(hqle): MSAA feature. // NOTE(hqle): MSAA feature.
...@@ -1185,6 +1187,7 @@ void ContextMtl::updateDrawFrameBufferBinding(const gl::Context *context) ...@@ -1185,6 +1187,7 @@ void ContextMtl::updateDrawFrameBufferBinding(const gl::Context *context)
void ContextMtl::onDrawFrameBufferChange(const gl::Context *context, FramebufferMtl *framebuffer) void ContextMtl::onDrawFrameBufferChange(const gl::Context *context, FramebufferMtl *framebuffer)
{ {
const gl::State &glState = getState(); const gl::State &glState = getState();
ASSERT(framebuffer == mtl::GetImpl(glState.getDrawFramebuffer()));
mDirtyBits.set(DIRTY_BIT_DRAW_FRAMEBUFFER); mDirtyBits.set(DIRTY_BIT_DRAW_FRAMEBUFFER);
...@@ -1251,12 +1254,19 @@ angle::Result ContextMtl::setupDraw(const gl::Context *context, ...@@ -1251,12 +1254,19 @@ angle::Result ContextMtl::setupDraw(const gl::Context *context,
&lineLoopLastSegmentIndexBuffer)); &lineLoopLastSegmentIndexBuffer));
} }
// Must be called before the command buffer is started. // Must be called before the render command encoder is started.
if (context->getStateCache().hasAnyActiveClientAttrib()) if (context->getStateCache().hasAnyActiveClientAttrib())
{ {
ANGLE_TRY(mVertexArray->updateClientAttribs(context, firstVertex, vertexOrIndexCount, ANGLE_TRY(mVertexArray->updateClientAttribs(context, firstVertex, vertexOrIndexCount,
instanceCount, indexTypeOrNone, indices)); instanceCount, indexTypeOrNone, indices));
} }
// This must be called before render command encoder is started.
bool textureChanged = false;
if (mDirtyBits.test(DIRTY_BIT_TEXTURES))
{
textureChanged = true;
ANGLE_TRY(handleDirtyActiveTextures(context));
}
if (!mRenderEncoder.valid()) if (!mRenderEncoder.valid())
{ {
...@@ -1277,20 +1287,19 @@ angle::Result ContextMtl::setupDraw(const gl::Context *context, ...@@ -1277,20 +1287,19 @@ angle::Result ContextMtl::setupDraw(const gl::Context *context,
Optional<mtl::RenderPipelineDesc> changedPipelineDesc; Optional<mtl::RenderPipelineDesc> changedPipelineDesc;
ANGLE_TRY(checkIfPipelineChanged(context, mode, &changedPipelineDesc)); ANGLE_TRY(checkIfPipelineChanged(context, mode, &changedPipelineDesc));
bool textureChanged = false;
for (size_t bit : mDirtyBits) for (size_t bit : mDirtyBits)
{ {
switch (bit) switch (bit)
{ {
case DIRTY_BIT_TEXTURES:
// Already handled.
break;
case DIRTY_BIT_DEFAULT_ATTRIBS: case DIRTY_BIT_DEFAULT_ATTRIBS:
ANGLE_TRY(handleDirtyDefaultAttribs(context)); ANGLE_TRY(handleDirtyDefaultAttribs(context));
break; break;
case DIRTY_BIT_DRIVER_UNIFORMS: case DIRTY_BIT_DRIVER_UNIFORMS:
ANGLE_TRY(handleDirtyDriverUniforms(context)); ANGLE_TRY(handleDirtyDriverUniforms(context));
break; break;
case DIRTY_BIT_TEXTURES:
textureChanged = true;
break;
case DIRTY_BIT_DEPTH_STENCIL_DESC: case DIRTY_BIT_DEPTH_STENCIL_DESC:
ANGLE_TRY(handleDirtyDepthStencilState(context)); ANGLE_TRY(handleDirtyDepthStencilState(context));
break; break;
...@@ -1312,17 +1321,26 @@ angle::Result ContextMtl::setupDraw(const gl::Context *context, ...@@ -1312,17 +1321,26 @@ angle::Result ContextMtl::setupDraw(const gl::Context *context,
case DIRTY_BIT_SCISSOR: case DIRTY_BIT_SCISSOR:
mRenderEncoder.setScissorRect(mScissorRect); mRenderEncoder.setScissorRect(mScissorRect);
break; break;
case DIRTY_BIT_DRAW_FRAMEBUFFER:
// Already handled.
break;
case DIRTY_BIT_CULL_MODE: case DIRTY_BIT_CULL_MODE:
mRenderEncoder.setCullMode(mCullMode); mRenderEncoder.setCullMode(mCullMode);
break; break;
case DIRTY_BIT_WINDING: case DIRTY_BIT_WINDING:
mRenderEncoder.setFrontFacingWinding(mWinding); mRenderEncoder.setFrontFacingWinding(mWinding);
break; break;
case DIRTY_BIT_RENDER_PIPELINE:
// Already handled. See checkIfPipelineChanged().
break;
default:
UNREACHABLE();
break;
} }
mDirtyBits.reset(bit);
} }
mDirtyBits.reset();
ANGLE_TRY(mProgram->setupDraw(context, &mRenderEncoder, changedPipelineDesc, textureChanged)); ANGLE_TRY(mProgram->setupDraw(context, &mRenderEncoder, changedPipelineDesc, textureChanged));
if (mode == gl::PrimitiveMode::LineLoop) if (mode == gl::PrimitiveMode::LineLoop)
...@@ -1372,6 +1390,34 @@ angle::Result ContextMtl::genLineLoopLastSegment(const gl::Context *context, ...@@ -1372,6 +1390,34 @@ angle::Result ContextMtl::genLineLoopLastSegment(const gl::Context *context,
return angle::Result::Continue; return angle::Result::Continue;
} }
angle::Result ContextMtl::handleDirtyActiveTextures(const gl::Context *context)
{
const gl::State &glState = mState;
const gl::Program *program = glState.getProgram();
const gl::ActiveTexturePointerArray &textures = glState.getActiveTexturesCache();
const gl::ActiveTextureMask &activeTextures = program->getActiveSamplersMask();
for (size_t textureUnit : activeTextures)
{
gl::Texture *texture = textures[textureUnit];
if (texture == nullptr)
{
continue;
}
TextureMtl *textureMtl = mtl::GetImpl(texture);
// Make sure texture's images update will be transferred to GPU.
ANGLE_TRY(textureMtl->ensureTextureCreated(context));
// The binding of this texture will be done by ProgramMtl.
}
return angle::Result::Continue;
}
angle::Result ContextMtl::handleDirtyDefaultAttribs(const gl::Context *context) angle::Result ContextMtl::handleDirtyDefaultAttribs(const gl::Context *context)
{ {
for (size_t attribIndex : mDirtyDefaultAttribsMask) for (size_t attribIndex : mDirtyDefaultAttribsMask)
...@@ -1384,7 +1430,6 @@ angle::Result ContextMtl::handleDirtyDefaultAttribs(const gl::Context *context) ...@@ -1384,7 +1430,6 @@ angle::Result ContextMtl::handleDirtyDefaultAttribs(const gl::Context *context)
mRenderEncoder.setVertexData(mDefaultAttributes, mtl::kDefaultAttribsBindingIndex); mRenderEncoder.setVertexData(mDefaultAttributes, mtl::kDefaultAttribsBindingIndex);
mDirtyDefaultAttribsMask.reset(); mDirtyDefaultAttribsMask.reset();
mDirtyBits.reset(DIRTY_BIT_DEFAULT_ATTRIBS);
return angle::Result::Continue; return angle::Result::Continue;
} }
...@@ -1414,7 +1459,6 @@ angle::Result ContextMtl::handleDirtyDriverUniforms(const gl::Context *context) ...@@ -1414,7 +1459,6 @@ angle::Result ContextMtl::handleDirtyDriverUniforms(const gl::Context *context)
mRenderEncoder.setFragmentData(mDriverUniforms, mtl::kDriverUniformsBindingIndex); mRenderEncoder.setFragmentData(mDriverUniforms, mtl::kDriverUniformsBindingIndex);
mRenderEncoder.setVertexData(mDriverUniforms, mtl::kDriverUniformsBindingIndex); mRenderEncoder.setVertexData(mDriverUniforms, mtl::kDriverUniformsBindingIndex);
mDirtyBits.reset(DIRTY_BIT_DRIVER_UNIFORMS);
return angle::Result::Continue; return angle::Result::Continue;
} }
...@@ -1441,7 +1485,6 @@ angle::Result ContextMtl::handleDirtyDepthStencilState(const gl::Context *contex ...@@ -1441,7 +1485,6 @@ angle::Result ContextMtl::handleDirtyDepthStencilState(const gl::Context *contex
// Apply depth stencil state // Apply depth stencil state
mRenderEncoder.setDepthStencilState(getDisplay()->getDepthStencilState(dsDesc)); mRenderEncoder.setDepthStencilState(getDisplay()->getDepthStencilState(dsDesc));
mDirtyBits.reset(DIRTY_BIT_DEPTH_STENCIL_DESC);
return angle::Result::Continue; return angle::Result::Continue;
} }
...@@ -1459,7 +1502,6 @@ angle::Result ContextMtl::handleDirtyDepthBias(const gl::Context *context) ...@@ -1459,7 +1502,6 @@ angle::Result ContextMtl::handleDirtyDepthBias(const gl::Context *context)
0); 0);
} }
mDirtyBits.reset(DIRTY_BIT_DEPTH_BIAS);
return angle::Result::Continue; return angle::Result::Continue;
} }
......
...@@ -355,6 +355,7 @@ const gl::Extensions &DisplayMtl::getNativeExtensions() const ...@@ -355,6 +355,7 @@ const gl::Extensions &DisplayMtl::getNativeExtensions() const
const mtl::TextureRef &DisplayMtl::getNullTexture(const gl::Context *context, gl::TextureType type) const mtl::TextureRef &DisplayMtl::getNullTexture(const gl::Context *context, gl::TextureType type)
{ {
// TODO(hqle): Use rx::IncompleteTextureSet.
ContextMtl *contextMtl = mtl::GetImpl(context); ContextMtl *contextMtl = mtl::GetImpl(context);
if (!mNullTextures[type]) if (!mNullTextures[type])
{ {
...@@ -367,13 +368,13 @@ const mtl::TextureRef &DisplayMtl::getNullTexture(const gl::Context *context, gl ...@@ -367,13 +368,13 @@ const mtl::TextureRef &DisplayMtl::getNullTexture(const gl::Context *context, gl
switch (type) switch (type)
{ {
case gl::TextureType::_2D: case gl::TextureType::_2D:
(void)(mtl::Texture::Make2DTexture(contextMtl, rgbaFormat, 1, 1, 1, false, (void)(mtl::Texture::Make2DTexture(contextMtl, rgbaFormat, 1, 1, 1, false, false,
&mNullTextures[type])); &mNullTextures[type]));
mNullTextures[type]->replaceRegion(contextMtl, region, 0, 0, zeroPixel, mNullTextures[type]->replaceRegion(contextMtl, region, 0, 0, zeroPixel,
sizeof(zeroPixel)); sizeof(zeroPixel));
break; break;
case gl::TextureType::CubeMap: case gl::TextureType::CubeMap:
(void)(mtl::Texture::MakeCubeTexture(contextMtl, rgbaFormat, 1, 1, false, (void)(mtl::Texture::MakeCubeTexture(contextMtl, rgbaFormat, 1, 1, false, false,
&mNullTextures[type])); &mNullTextures[type]));
for (int f = 0; f < 6; ++f) for (int f = 0; f < 6; ++f)
{ {
......
...@@ -907,12 +907,12 @@ angle::Result ProgramMtl::updateTextures(const gl::Context *glContext, ...@@ -907,12 +907,12 @@ angle::Result ProgramMtl::updateTextures(const gl::Context *glContext,
switch (shaderType) switch (shaderType)
{ {
case gl::ShaderType::Vertex: case gl::ShaderType::Vertex:
textureMtl->bindVertexShader(glContext, cmdEncoder, destBindingPoint, ANGLE_TRY(textureMtl->bindVertexShader(glContext, cmdEncoder,
destBindingPoint); destBindingPoint, destBindingPoint));
break; break;
case gl::ShaderType::Fragment: case gl::ShaderType::Fragment:
textureMtl->bindFragmentShader(glContext, cmdEncoder, destBindingPoint, ANGLE_TRY(textureMtl->bindFragmentShader(
destBindingPoint); glContext, cmdEncoder, destBindingPoint, destBindingPoint));
break; break;
default: default:
UNREACHABLE(); UNREACHABLE();
......
...@@ -60,7 +60,8 @@ angle::Result RenderbufferMtl::setStorageImpl(const gl::Context *context, ...@@ -60,7 +60,8 @@ angle::Result RenderbufferMtl::setStorageImpl(const gl::Context *context,
if ((mTexture == nullptr || !mTexture->valid()) && (width != 0 && height != 0)) if ((mTexture == nullptr || !mTexture->valid()) && (width != 0 && height != 0))
{ {
ANGLE_TRY(mtl::Texture::Make2DTexture(contextMtl, mFormat, static_cast<uint32_t>(width), ANGLE_TRY(mtl::Texture::Make2DTexture(contextMtl, mFormat, static_cast<uint32_t>(width),
static_cast<uint32_t>(height), 1, false, &mTexture)); static_cast<uint32_t>(height), 1, false, false,
&mTexture));
mRenderTarget.set(mTexture, 0, 0, mFormat); mRenderTarget.set(mTexture, 0, 0, mFormat);
} }
......
...@@ -281,6 +281,11 @@ FramebufferImpl *SurfaceMtl::createDefaultFramebuffer(const gl::Context *context ...@@ -281,6 +281,11 @@ FramebufferImpl *SurfaceMtl::createDefaultFramebuffer(const gl::Context *context
egl::Error SurfaceMtl::makeCurrent(const gl::Context *context) egl::Error SurfaceMtl::makeCurrent(const gl::Context *context)
{ {
angle::Result result = obtainNextDrawable(context);
if (result != angle::Result::Continue)
{
return egl::EglBadCurrentSurface();
}
return egl::NoError(); return egl::NoError();
} }
...@@ -436,7 +441,7 @@ angle::Result SurfaceMtl::ensureDepthStencilSizeCorrect(const gl::Context *conte ...@@ -436,7 +441,7 @@ angle::Result SurfaceMtl::ensureDepthStencilSizeCorrect(const gl::Context *conte
if (mDepthFormat.valid() && (!mDepthTexture || mDepthTexture->size() != size)) if (mDepthFormat.valid() && (!mDepthTexture || mDepthTexture->size() != size))
{ {
ANGLE_TRY(mtl::Texture::Make2DTexture(contextMtl, mDepthFormat, size.width, size.height, 1, ANGLE_TRY(mtl::Texture::Make2DTexture(contextMtl, mDepthFormat, size.width, size.height, 1,
true, &mDepthTexture)); true, false, &mDepthTexture));
mDepthRenderTarget.set(mDepthTexture, 0, 0, mDepthFormat); mDepthRenderTarget.set(mDepthTexture, 0, 0, mDepthFormat);
fboDirtyBits->set(gl::Framebuffer::DIRTY_BIT_DEPTH_ATTACHMENT); fboDirtyBits->set(gl::Framebuffer::DIRTY_BIT_DEPTH_ATTACHMENT);
...@@ -451,7 +456,7 @@ angle::Result SurfaceMtl::ensureDepthStencilSizeCorrect(const gl::Context *conte ...@@ -451,7 +456,7 @@ angle::Result SurfaceMtl::ensureDepthStencilSizeCorrect(const gl::Context *conte
else else
{ {
ANGLE_TRY(mtl::Texture::Make2DTexture(contextMtl, mStencilFormat, size.width, ANGLE_TRY(mtl::Texture::Make2DTexture(contextMtl, mStencilFormat, size.width,
size.height, 1, true, &mStencilTexture)); size.height, 1, true, false, &mStencilTexture));
} }
mStencilRenderTarget.set(mStencilTexture, 0, 0, mStencilFormat); mStencilRenderTarget.set(mStencilTexture, 0, 0, mStencilFormat);
......
...@@ -10,6 +10,8 @@ ...@@ -10,6 +10,8 @@
#ifndef LIBANGLE_RENDERER_METAL_TEXTUREMTL_H_ #ifndef LIBANGLE_RENDERER_METAL_TEXTUREMTL_H_
#define LIBANGLE_RENDERER_METAL_TEXTUREMTL_H_ #define LIBANGLE_RENDERER_METAL_TEXTUREMTL_H_
#include <map>
#include "common/PackedEnums.h" #include "common/PackedEnums.h"
#include "libANGLE/renderer/TextureImpl.h" #include "libANGLE/renderer/TextureImpl.h"
#include "libANGLE/renderer/metal/RenderTargetMtl.h" #include "libANGLE/renderer/metal/RenderTargetMtl.h"
...@@ -140,19 +142,30 @@ class TextureMtl : public TextureImpl ...@@ -140,19 +142,30 @@ class TextureMtl : public TextureImpl
angle::Result initializeContents(const gl::Context *context, angle::Result initializeContents(const gl::Context *context,
const gl::ImageIndex &imageIndex) override; const gl::ImageIndex &imageIndex) override;
void bindVertexShader(const gl::Context *context, // The texture's data is initially initialized and stored in an array
mtl::RenderCommandEncoder *cmdEncoder, // of images through glTexImage*/glCopyTex* calls. During draw calls, the caller must make sure
int textureSlotIndex, // the actual texture is created by calling this method to transfer the stored images data
int samplerSlotIndex); // to the actual texture.
void bindFragmentShader(const gl::Context *context, angle::Result ensureTextureCreated(const gl::Context *context);
mtl::RenderCommandEncoder *cmdEncoder,
int textureSlotIndex, angle::Result bindVertexShader(const gl::Context *context,
int samplerSlotIndex); mtl::RenderCommandEncoder *cmdEncoder,
int textureSlotIndex,
int samplerSlotIndex);
angle::Result bindFragmentShader(const gl::Context *context,
mtl::RenderCommandEncoder *cmdEncoder,
int textureSlotIndex,
int samplerSlotIndex);
const mtl::Format &getFormat() const { return mFormat; } const mtl::Format &getFormat() const { return mFormat; }
private: private:
void releaseTexture(); void releaseTexture(bool releaseImages);
// Ensure image at given index is created:
angle::Result ensureImageCreated(const gl::Context *context, const gl::ImageIndex &index);
angle::Result checkForEmulatedChannels(const gl::Context *context,
const mtl::Format &mtlFormat,
const mtl::TextureRef &texture);
// If levels = 0, this function will create full mipmaps texture. // If levels = 0, this function will create full mipmaps texture.
angle::Result setStorageImpl(const gl::Context *context, angle::Result setStorageImpl(const gl::Context *context,
...@@ -212,12 +225,17 @@ class TextureMtl : public TextureImpl ...@@ -212,12 +225,17 @@ class TextureMtl : public TextureImpl
angle::Result generateMipmapCPU(const gl::Context *context); angle::Result generateMipmapCPU(const gl::Context *context);
mtl::Format mFormat; mtl::Format mFormat;
mtl::TextureRef mTexture; // The real texture used by Metal draw calls.
mtl::TextureRef mNativeTexture;
id<MTLSamplerState> mMetalSamplerState = nil; id<MTLSamplerState> mMetalSamplerState = nil;
std::vector<RenderTargetMtl> mLayeredRenderTargets; std::vector<RenderTargetMtl> mLayeredRenderTargets;
std::vector<mtl::TextureRef> mLayeredTextureViews; std::vector<mtl::TextureRef> mLayeredTextureViews;
// Stored images array defined by glTexImage/glCopy*.
// Once the images array is complete, they will be transferred to real texture object.
std::map<int, gl::TexLevelArray<mtl::TextureRef>> mTexImages;
bool mIsPow2 = false; bool mIsPow2 = false;
}; };
......
...@@ -66,6 +66,54 @@ MTLColorWriteMask GetColorWriteMask(const mtl::Format &mtlFormat, bool *emulated ...@@ -66,6 +66,54 @@ MTLColorWriteMask GetColorWriteMask(const mtl::Format &mtlFormat, bool *emulated
return colorWritableMask; return colorWritableMask;
} }
gl::ImageIndex GetImageBaseLevelIndex(const mtl::TextureRef &image)
{
gl::ImageIndex imageBaseIndex;
switch (image->textureType())
{
case MTLTextureType2D:
imageBaseIndex = gl::ImageIndex::Make2D(0);
break;
default:
UNREACHABLE();
break;
}
return imageBaseIndex;
}
GLuint GetImageLayerIndex(const gl::ImageIndex &index)
{
switch (index.getType())
{
case gl::TextureType::_2D:
return 0;
case gl::TextureType::CubeMap:
return index.cubeMapFaceIndex();
default:
UNREACHABLE();
}
return 0;
}
// Given texture type, get texture type of one image at a slice and a level.
// For example, for texture 2d, one image is also texture 2d.
// for texture cube, one image is texture 2d.
// For texture 2d array, one image is texture 2d.
gl::TextureType GetTextureImageType(gl::TextureType texType)
{
switch (texType)
{
case gl::TextureType::_2D:
case gl::TextureType::CubeMap:
return gl::TextureType::_2D;
default:
UNREACHABLE();
return gl::TextureType::InvalidEnum;
}
}
} // namespace } // namespace
// TextureMtl implementation // TextureMtl implementation
...@@ -77,20 +125,126 @@ void TextureMtl::onDestroy(const gl::Context *context) ...@@ -77,20 +125,126 @@ void TextureMtl::onDestroy(const gl::Context *context)
{ {
mMetalSamplerState = nil; mMetalSamplerState = nil;
releaseTexture(); releaseTexture(true);
} }
void TextureMtl::releaseTexture() void TextureMtl::releaseTexture(bool releaseImages)
{ {
mFormat = mtl::Format(); mFormat = mtl::Format();
mTexture = nullptr;
mNativeTexture = nullptr;
for (RenderTargetMtl &rt : mLayeredRenderTargets)
{
rt.set(nullptr);
}
if (releaseImages)
{
mTexImages.clear();
}
mLayeredRenderTargets.clear();
mLayeredTextureViews.clear(); mLayeredTextureViews.clear();
mIsPow2 = false; mIsPow2 = false;
} }
angle::Result TextureMtl::ensureTextureCreated(const gl::Context *context)
{
if (mNativeTexture)
{
return angle::Result::Continue;
}
ContextMtl *contextMtl = mtl::GetImpl(context);
// Create actual texture object:
int layers = 0;
const GLuint mips = mState.getMipmapMaxLevel() + 1;
const gl::ImageDesc &desc = mState.getBaseLevelDesc();
mIsPow2 = gl::isPow2(desc.size.width) && gl::isPow2(desc.size.height);
switch (mState.getType())
{
case gl::TextureType::_2D:
layers = 1;
ANGLE_TRY(mtl::Texture::Make2DTexture(contextMtl, mFormat, desc.size.width,
desc.size.height, mips, false, true,
&mNativeTexture));
mLayeredRenderTargets.resize(1);
mLayeredRenderTargets[0].set(mNativeTexture, 0, 0, mFormat);
mLayeredTextureViews.resize(1);
mLayeredTextureViews[0] = mNativeTexture;
break;
case gl::TextureType::CubeMap:
layers = 6;
ANGLE_TRY(mtl::Texture::MakeCubeTexture(contextMtl, mFormat, desc.size.width, mips,
false, true, &mNativeTexture));
mLayeredRenderTargets.resize(gl::kCubeFaceCount);
mLayeredTextureViews.resize(gl::kCubeFaceCount);
for (uint32_t f = 0; f < gl::kCubeFaceCount; ++f)
{
mLayeredTextureViews[f] = mNativeTexture->createCubeFaceView(f);
mLayeredRenderTargets[f].set(mLayeredTextureViews[f], 0, 0, mFormat);
}
break;
default:
UNREACHABLE();
}
ANGLE_TRY(checkForEmulatedChannels(context, mFormat, mNativeTexture));
// Transfer data from images to actual texture object
mtl::BlitCommandEncoder *encoder = nullptr;
for (int layer = 0; layer < layers; ++layer)
{
for (GLuint mip = 0; mip < mips; ++mip)
{
mtl::TextureRef &imageToTransfer = mTexImages[layer][mip];
// Only transfer if this mip & slice image has been defined and in correct size &
// format.
gl::Extents actualMipSize = mNativeTexture->size(mip);
if (imageToTransfer && imageToTransfer->size() == actualMipSize &&
imageToTransfer->pixelFormat() == mNativeTexture->pixelFormat())
{
MTLSize mtlSize =
MTLSizeMake(actualMipSize.width, actualMipSize.height, actualMipSize.depth);
MTLOrigin mtlOrigin = MTLOriginMake(0, 0, 0);
if (!encoder)
{
encoder = contextMtl->getBlitCommandEncoder();
}
encoder->copyTexture(mNativeTexture, layer, mip, mtlOrigin, mtlSize,
imageToTransfer, 0, 0, mtlOrigin);
}
imageToTransfer = nullptr;
// Make this image the actual texture object's view at this mip and slice.
// So that in future, glTexSubImage* will update the actual texture
// directly.
mTexImages[layer][mip] = mNativeTexture->createSliceMipView(layer, mip);
}
}
return angle::Result::Continue;
}
angle::Result TextureMtl::ensureImageCreated(const gl::Context *context,
const gl::ImageIndex &index)
{
mtl::TextureRef &image = mTexImages[GetImageLayerIndex(index)][index.getLevelIndex()];
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::setImage(const gl::Context *context, angle::Result TextureMtl::setImage(const gl::Context *context,
const gl::ImageIndex &index, const gl::ImageIndex &index,
GLenum internalFormat, GLenum internalFormat,
...@@ -280,8 +434,10 @@ angle::Result TextureMtl::setImageExternal(const gl::Context *context, ...@@ -280,8 +434,10 @@ angle::Result TextureMtl::setImageExternal(const gl::Context *context,
angle::Result TextureMtl::generateMipmap(const gl::Context *context) angle::Result TextureMtl::generateMipmap(const gl::Context *context)
{ {
ANGLE_TRY(ensureTextureCreated(context));
ContextMtl *contextMtl = mtl::GetImpl(context); ContextMtl *contextMtl = mtl::GetImpl(context);
if (!mTexture) if (!mNativeTexture)
{ {
return angle::Result::Continue; return angle::Result::Continue;
} }
...@@ -292,7 +448,7 @@ angle::Result TextureMtl::generateMipmap(const gl::Context *context) ...@@ -292,7 +448,7 @@ angle::Result TextureMtl::generateMipmap(const gl::Context *context)
if (textureCaps.filterable && textureCaps.renderbuffer) if (textureCaps.filterable && textureCaps.renderbuffer)
{ {
mtl::BlitCommandEncoder *blitEncoder = contextMtl->getBlitCommandEncoder(); mtl::BlitCommandEncoder *blitEncoder = contextMtl->getBlitCommandEncoder();
blitEncoder->generateMipmapsForTexture(mTexture); blitEncoder->generateMipmapsForTexture(mNativeTexture);
} }
else else
{ {
...@@ -304,7 +460,7 @@ angle::Result TextureMtl::generateMipmap(const gl::Context *context) ...@@ -304,7 +460,7 @@ angle::Result TextureMtl::generateMipmap(const gl::Context *context)
angle::Result TextureMtl::generateMipmapCPU(const gl::Context *context) angle::Result TextureMtl::generateMipmapCPU(const gl::Context *context)
{ {
ASSERT(mTexture && mTexture->valid()); ASSERT(mNativeTexture && mNativeTexture->valid());
ASSERT(mLayeredTextureViews.size() <= std::numeric_limits<uint32_t>::max()); ASSERT(mLayeredTextureViews.size() <= std::numeric_limits<uint32_t>::max());
uint32_t layers = static_cast<uint32_t>(mLayeredTextureViews.size()); uint32_t layers = static_cast<uint32_t>(mLayeredTextureViews.size());
...@@ -316,11 +472,11 @@ angle::Result TextureMtl::generateMipmapCPU(const gl::Context *context) ...@@ -316,11 +472,11 @@ angle::Result TextureMtl::generateMipmapCPU(const gl::Context *context)
// NOTE(hqle): Support base level of ES 3.0. // NOTE(hqle): Support base level of ES 3.0.
for (uint32_t layer = 0; layer < layers; ++layer) for (uint32_t layer = 0; layer < layers; ++layer)
{ {
int maxMipLevel = static_cast<int>(mTexture->mipmapLevels()) - 1; int maxMipLevel = static_cast<int>(mNativeTexture->mipmapLevels()) - 1;
int firstLevel = 0; int firstLevel = 0;
uint32_t prevLevelWidth = mTexture->width(); uint32_t prevLevelWidth = mNativeTexture->width();
uint32_t prevLevelHeight = mTexture->height(); uint32_t prevLevelHeight = mNativeTexture->height();
size_t prevLevelRowPitch = angleFormat.pixelBytes * prevLevelWidth; size_t prevLevelRowPitch = angleFormat.pixelBytes * prevLevelWidth;
std::unique_ptr<uint8_t[]> prevLevelData(new (std::nothrow) std::unique_ptr<uint8_t[]> prevLevelData(new (std::nothrow)
uint8_t[prevLevelRowPitch * prevLevelHeight]); uint8_t[prevLevelRowPitch * prevLevelHeight]);
...@@ -334,8 +490,8 @@ angle::Result TextureMtl::generateMipmapCPU(const gl::Context *context) ...@@ -334,8 +490,8 @@ angle::Result TextureMtl::generateMipmapCPU(const gl::Context *context)
for (int mip = firstLevel + 1; mip <= maxMipLevel; ++mip) for (int mip = firstLevel + 1; mip <= maxMipLevel; ++mip)
{ {
uint32_t dstWidth = mTexture->width(mip); uint32_t dstWidth = mNativeTexture->width(mip);
uint32_t dstHeight = mTexture->height(mip); uint32_t dstHeight = mNativeTexture->height(mip);
size_t dstRowPitch = angleFormat.pixelBytes * dstWidth; size_t dstRowPitch = angleFormat.pixelBytes * dstWidth;
size_t dstDataSize = dstRowPitch * dstHeight; size_t dstDataSize = dstRowPitch * dstHeight;
...@@ -352,8 +508,8 @@ angle::Result TextureMtl::generateMipmapCPU(const gl::Context *context) ...@@ -352,8 +508,8 @@ angle::Result TextureMtl::generateMipmapCPU(const gl::Context *context)
dstLevelData.get(), dstRowPitch, 0); dstLevelData.get(), dstRowPitch, 0);
// Upload to texture // Upload to texture
mTexture->replaceRegion(contextMtl, MTLRegionMake2D(0, 0, dstWidth, dstHeight), mip, mNativeTexture->replaceRegion(contextMtl, MTLRegionMake2D(0, 0, dstWidth, dstHeight),
layer, dstLevelData.get(), dstRowPitch); mip, layer, dstLevelData.get(), dstRowPitch);
prevLevelWidth = dstWidth; prevLevelWidth = dstWidth;
prevLevelHeight = dstHeight; prevLevelHeight = dstHeight;
...@@ -394,15 +550,13 @@ angle::Result TextureMtl::getAttachmentRenderTarget(const gl::Context *context, ...@@ -394,15 +550,13 @@ angle::Result TextureMtl::getAttachmentRenderTarget(const gl::Context *context,
GLsizei samples, GLsizei samples,
FramebufferAttachmentRenderTarget **rtOut) FramebufferAttachmentRenderTarget **rtOut)
{ {
ANGLE_TRY(ensureTextureCreated(context));
// NOTE(hqle): Support MSAA. // NOTE(hqle): Support MSAA.
// Non-zero mip level attachments are an ES 3.0 feature. // Non-zero mip level attachments are an ES 3.0 feature.
ASSERT(imageIndex.getLevelIndex() == 0); ASSERT(imageIndex.getLevelIndex() == 0);
ContextMtl *contextMtl = mtl::GetImpl(context); ContextMtl *contextMtl = mtl::GetImpl(context);
if (!mTexture) ANGLE_MTL_TRY(contextMtl, mNativeTexture);
{
ANGLE_MTL_CHECK(contextMtl, false, GL_INVALID_OPERATION);
}
switch (imageIndex.getType()) switch (imageIndex.getType())
{ {
...@@ -439,17 +593,20 @@ angle::Result TextureMtl::syncState(const gl::Context *context, ...@@ -439,17 +593,20 @@ angle::Result TextureMtl::syncState(const gl::Context *context,
// NOTE(hqle): Metal doesn't support swizzle on many devices. Skip for now. // NOTE(hqle): Metal doesn't support swizzle on many devices. Skip for now.
} }
ANGLE_TRY(ensureTextureCreated(context));
mMetalSamplerState = display->getStateCache().getSamplerState( mMetalSamplerState = display->getStateCache().getSamplerState(
display->getMetalDevice(), mtl::SamplerDesc(mState.getSamplerState())); display->getMetalDevice(), mtl::SamplerDesc(mState.getSamplerState()));
return angle::Result::Continue; return angle::Result::Continue;
} }
void TextureMtl::bindVertexShader(const gl::Context *context, angle::Result TextureMtl::bindVertexShader(const gl::Context *context,
mtl::RenderCommandEncoder *cmdEncoder, mtl::RenderCommandEncoder *cmdEncoder,
int textureSlotIndex, int textureSlotIndex,
int samplerSlotIndex) int samplerSlotIndex)
{ {
ASSERT(mNativeTexture);
// ES 2.0: non power of two texture won't have any mipmap. // ES 2.0: non power of two texture won't have any mipmap.
// We don't support OES_texture_npot atm. // We don't support OES_texture_npot atm.
float maxLodClamp = FLT_MAX; float maxLodClamp = FLT_MAX;
...@@ -458,15 +615,18 @@ void TextureMtl::bindVertexShader(const gl::Context *context, ...@@ -458,15 +615,18 @@ void TextureMtl::bindVertexShader(const gl::Context *context,
maxLodClamp = 0; maxLodClamp = 0;
} }
cmdEncoder->setVertexTexture(mTexture, textureSlotIndex); cmdEncoder->setVertexTexture(mNativeTexture, textureSlotIndex);
cmdEncoder->setVertexSamplerState(mMetalSamplerState, 0, maxLodClamp, samplerSlotIndex); cmdEncoder->setVertexSamplerState(mMetalSamplerState, 0, maxLodClamp, samplerSlotIndex);
return angle::Result::Continue;
} }
void TextureMtl::bindFragmentShader(const gl::Context *context, angle::Result TextureMtl::bindFragmentShader(const gl::Context *context,
mtl::RenderCommandEncoder *cmdEncoder, mtl::RenderCommandEncoder *cmdEncoder,
int textureSlotIndex, int textureSlotIndex,
int samplerSlotIndex) int samplerSlotIndex)
{ {
ASSERT(mNativeTexture);
// ES 2.0: non power of two texture won't have any mipmap. // ES 2.0: non power of two texture won't have any mipmap.
// We don't support OES_texture_npot atm. // We don't support OES_texture_npot atm.
float maxLodClamp = FLT_MAX; float maxLodClamp = FLT_MAX;
...@@ -475,8 +635,10 @@ void TextureMtl::bindFragmentShader(const gl::Context *context, ...@@ -475,8 +635,10 @@ void TextureMtl::bindFragmentShader(const gl::Context *context,
maxLodClamp = 0; maxLodClamp = 0;
} }
cmdEncoder->setFragmentTexture(mTexture, textureSlotIndex); cmdEncoder->setFragmentTexture(mNativeTexture, textureSlotIndex);
cmdEncoder->setFragmentSamplerState(mMetalSamplerState, 0, maxLodClamp, samplerSlotIndex); cmdEncoder->setFragmentSamplerState(mMetalSamplerState, 0, maxLodClamp, samplerSlotIndex);
return angle::Result::Continue;
} }
angle::Result TextureMtl::redefineImage(const gl::Context *context, angle::Result TextureMtl::redefineImage(const gl::Context *context,
...@@ -484,17 +646,19 @@ angle::Result TextureMtl::redefineImage(const gl::Context *context, ...@@ -484,17 +646,19 @@ angle::Result TextureMtl::redefineImage(const gl::Context *context,
const mtl::Format &mtlFormat, const mtl::Format &mtlFormat,
const gl::Extents &size) const gl::Extents &size)
{ {
if (mTexture) if (mNativeTexture)
{ {
if (mTexture->valid()) if (mNativeTexture->valid())
{ {
// Calculate the expected size for the index we are defining. If the size is different // 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 // from the given size, or the format is different, we are redefining the image so we
// must release it. // must release it.
if (mFormat != mtlFormat || size != mTexture->size(index) || bool typeChanged =
mTexture->textureType() != mtl::GetTextureType(index.getType())) mNativeTexture->textureType() != mtl::GetTextureType(index.getType());
if (mFormat != mtlFormat || size != mNativeTexture->size(index) || typeChanged)
{ {
releaseTexture(); // Keep other images data if texture type hasn't been changed.
releaseTexture(typeChanged);
} }
} }
} }
...@@ -505,18 +669,37 @@ angle::Result TextureMtl::redefineImage(const gl::Context *context, ...@@ -505,18 +669,37 @@ angle::Result TextureMtl::redefineImage(const gl::Context *context,
return angle::Result::Continue; return angle::Result::Continue;
} }
if (!mTexture) ContextMtl *contextMtl = mtl::GetImpl(context);
// Cache last defined image format:
mFormat = mtlFormat;
mtl::TextureRef &image = mTexImages[GetImageLayerIndex(index)][index.getLevelIndex()];
// If actual texture exists, it means the size hasn't been changed, no need to create new image
if (mNativeTexture && image)
{
ASSERT(image->textureType() == mtl::GetTextureType(GetTextureImageType(index.getType())) &&
image->pixelFormat() == mFormat.metalFormat && image->size() == size);
}
else
{ {
gl::Extents baseSize; // Create image to hold texture's data at this level & slice:
baseSize.width = size.width << index.getLevelIndex(); switch (index.getType())
baseSize.height = size.height << index.getLevelIndex(); {
case gl::TextureType::_2D:
case gl::TextureType::CubeMap:
ANGLE_TRY(mtl::Texture::Make2DTexture(contextMtl, mtlFormat, size.width,
size.height, 1, false, false, &image));
break;
default:
UNREACHABLE();
}
}
mIsPow2 = gl::isPow2(size.width) && gl::isPow2(size.height); // Make sure emulated channels are properly initialized
// ES 2.0: don't support mip map filtering with non power of two textures ANGLE_TRY(checkForEmulatedChannels(context, mtlFormat, image));
size_t mipmaps = mIsPow2 ? 0 : 1;
ANGLE_TRY(setStorageImpl(context, index.getType(), mipmaps, mtlFormat, baseSize)); // Tell context to rebind textures
} contextMtl->invalidateCurrentTextures();
return angle::Result::Continue; return angle::Result::Continue;
} }
...@@ -528,81 +711,20 @@ angle::Result TextureMtl::setStorageImpl(const gl::Context *context, ...@@ -528,81 +711,20 @@ angle::Result TextureMtl::setStorageImpl(const gl::Context *context,
const mtl::Format &mtlFormat, const mtl::Format &mtlFormat,
const gl::Extents &size) const gl::Extents &size)
{ {
ContextMtl *contextMtl = mtl::GetImpl(context); if (mNativeTexture)
if (mTexture)
{ {
releaseTexture(); // Don't need to hold old images data.
releaseTexture(true);
} }
MTLTextureType mtlType = mtl::GetTextureType(type); ContextMtl *contextMtl = mtl::GetImpl(context);
switch (mtlType) // Tell context to rebind textures
{ contextMtl->invalidateCurrentTextures();
case MTLTextureType2D:
ANGLE_TRY(mtl::Texture::Make2DTexture(contextMtl, mtlFormat, size.width, size.height,
static_cast<uint32_t>(mipmaps), false,
&mTexture));
mLayeredRenderTargets.resize(1);
mLayeredRenderTargets[0].set(mTexture, 0, 0, mFormat);
mLayeredTextureViews.resize(1);
mLayeredTextureViews[0] = mTexture;
break;
case MTLTextureTypeCube:
ANGLE_TRY(mtl::Texture::MakeCubeTexture(contextMtl, mtlFormat, size.width,
static_cast<uint32_t>(mipmaps), false,
&mTexture));
mLayeredRenderTargets.resize(gl::kCubeFaceCount);
mLayeredTextureViews.resize(gl::kCubeFaceCount);
for (uint32_t f = 0; f < gl::kCubeFaceCount; ++f)
{
mLayeredTextureViews[f] = mTexture->createFaceView(f);
mLayeredRenderTargets[f].set(mLayeredTextureViews[f], 0, 0, mFormat);
}
break;
default:
{
ANGLE_MTL_CHECK(contextMtl, false, GL_INVALID_ENUM);
}
}
mFormat = mtlFormat; mFormat = mtlFormat;
bool emulatedChannels = false; // Texture will be created later in ensureTextureCreated()
MTLColorWriteMask colorWritableMask = GetColorWriteMask(mtlFormat, &emulatedChannels);
mTexture->setColorWritableMask(colorWritableMask);
// For emulated channels that GL texture intends to not have,
// we need to initialize their content.
if (emulatedChannels)
{
if (mipmaps == 0)
{
mipmaps = mTexture->mipmapLevels();
}
int layers = mtlType == MTLTextureTypeCube ? 6 : 1;
for (int layer = 0; layer < layers; ++layer)
{
auto cubeFace = static_cast<gl::TextureTarget>(
static_cast<int>(gl::TextureTarget::CubeMapPositiveX) + layer);
for (uint32_t mip = 0; mip < mipmaps; ++mip)
{
gl::ImageIndex index;
if (layers > 1)
{
index = gl::ImageIndex::MakeCubeMapFace(cubeFace, mip);
}
else
{
index = gl::ImageIndex::Make2D(mip);
}
ANGLE_TRY(mtl::InitializeTextureContents(context, mTexture, mFormat, index));
}
}
}
return angle::Result::Continue; return angle::Result::Continue;
} }
...@@ -644,20 +766,23 @@ angle::Result TextureMtl::setSubImageImpl(const gl::Context *context, ...@@ -644,20 +766,23 @@ angle::Result TextureMtl::setSubImageImpl(const gl::Context *context,
{ {
return angle::Result::Continue; return angle::Result::Continue;
} }
ASSERT(mTexture && mTexture->valid());
ASSERT(unpack.skipRows == 0 && unpack.skipPixels == 0 && unpack.skipImages == 0); ASSERT(unpack.skipRows == 0 && unpack.skipPixels == 0 && unpack.skipImages == 0);
ContextMtl *contextMtl = mtl::GetImpl(context); ContextMtl *contextMtl = mtl::GetImpl(context);
angle::FormatID angleFormatId = angle::FormatID angleFormatId =
angle::Format::InternalFormatToID(formatInfo.sizedInternalFormat); angle::Format::InternalFormatToID(formatInfo.sizedInternalFormat);
const mtl::Format &mtlFormat = contextMtl->getPixelFormat(angleFormatId); const mtl::Format &mtlSrcFormat = contextMtl->getPixelFormat(angleFormatId);
if (mFormat.metalFormat != mtlFormat.metalFormat) if (mFormat.metalFormat != mtlSrcFormat.metalFormat)
{ {
ANGLE_MTL_CHECK(contextMtl, false, GL_INVALID_OPERATION); ANGLE_MTL_CHECK(contextMtl, false, GL_INVALID_OPERATION);
} }
ANGLE_TRY(ensureImageCreated(context, index));
mtl::TextureRef &image = mTexImages[GetImageLayerIndex(index)][index.getLevelIndex()];
GLuint sourceRowPitch = 0; GLuint sourceRowPitch = 0;
ANGLE_CHECK_GL_MATH(contextMtl, formatInfo.computeRowPitch(type, area.width, unpack.alignment, ANGLE_CHECK_GL_MATH(contextMtl, formatInfo.computeRowPitch(type, area.width, unpack.alignment,
unpack.rowLength, &sourceRowPitch)); unpack.rowLength, &sourceRowPitch));
...@@ -666,7 +791,7 @@ angle::Result TextureMtl::setSubImageImpl(const gl::Context *context, ...@@ -666,7 +791,7 @@ angle::Result TextureMtl::setSubImageImpl(const gl::Context *context,
{ {
// area must be the whole mip level // area must be the whole mip level
sourceRowPitch = 0; sourceRowPitch = 0;
gl::Extents size = mTexture->size(index); gl::Extents size = image->size(index);
if (area.x != 0 || area.y != 0 || area.width != size.width || area.height != size.height) if (area.x != 0 || area.y != 0 || area.width != size.width || area.height != size.height)
{ {
ANGLE_MTL_CHECK(contextMtl, false, GL_INVALID_OPERATION); ANGLE_MTL_CHECK(contextMtl, false, GL_INVALID_OPERATION);
...@@ -687,9 +812,7 @@ angle::Result TextureMtl::setSubImageImpl(const gl::Context *context, ...@@ -687,9 +812,7 @@ angle::Result TextureMtl::setSubImageImpl(const gl::Context *context,
} }
// Upload to texture // Upload to texture
mTexture->replaceRegion(contextMtl, mtlRegion, index.getLevelIndex(), image->replaceRegion(contextMtl, mtlRegion, 0, 0, pixels, sourceRowPitch);
index.hasLayer() ? index.cubeMapFaceIndex() : 0, pixels,
sourceRowPitch);
return angle::Result::Continue; return angle::Result::Continue;
} }
...@@ -702,9 +825,9 @@ angle::Result TextureMtl::convertAndSetSubImage(const gl::Context *context, ...@@ -702,9 +825,9 @@ angle::Result TextureMtl::convertAndSetSubImage(const gl::Context *context,
size_t pixelsRowPitch, size_t pixelsRowPitch,
const uint8_t *pixels) const uint8_t *pixels)
{ {
ASSERT(mTexture && mTexture->valid()); mtl::TextureRef &image = mTexImages[GetImageLayerIndex(index)][index.getLevelIndex()];
ASSERT(mTexture->textureType() == MTLTextureType2D || ASSERT(image && image->valid());
mTexture->textureType() == MTLTextureTypeCube); ASSERT(image->textureType() == MTLTextureType2D);
ContextMtl *contextMtl = mtl::GetImpl(context); ContextMtl *contextMtl = mtl::GetImpl(context);
...@@ -729,18 +852,55 @@ angle::Result TextureMtl::convertAndSetSubImage(const gl::Context *context, ...@@ -729,18 +852,55 @@ angle::Result TextureMtl::convertAndSetSubImage(const gl::Context *context,
false, false, false); false, false, false);
// Upload to texture // Upload to texture
mTexture->replaceRegion(contextMtl, mtlRow, index.getLevelIndex(), image->replaceRegion(contextMtl, mtlRow, 0, 0, conversionRow.data(), dstRowPitch);
index.hasLayer() ? index.cubeMapFaceIndex() : 0,
conversionRow.data(), dstRowPitch);
} }
return angle::Result::Continue; return angle::Result::Continue;
} }
angle::Result TextureMtl::checkForEmulatedChannels(const gl::Context *context,
const mtl::Format &mtlFormat,
const mtl::TextureRef &texture)
{
bool emulatedChannels = false;
MTLColorWriteMask colorWritableMask = GetColorWriteMask(mtlFormat, &emulatedChannels);
texture->setColorWritableMask(colorWritableMask);
// For emulated channels that GL texture intends to not have,
// we need to initialize their content.
if (emulatedChannels)
{
uint32_t mipmaps = texture->mipmapLevels();
int layers = texture->textureType() == MTLTextureTypeCube ? 6 : 1;
for (int layer = 0; layer < layers; ++layer)
{
auto cubeFace = static_cast<gl::TextureTarget>(
static_cast<int>(gl::TextureTarget::CubeMapPositiveX) + layer);
for (uint32_t mip = 0; mip < mipmaps; ++mip)
{
gl::ImageIndex index;
if (layers > 1)
{
index = gl::ImageIndex::MakeCubeMapFace(cubeFace, mip);
}
else
{
index = gl::ImageIndex::Make2D(mip);
}
ANGLE_TRY(mtl::InitializeTextureContents(context, texture, mFormat, index));
}
}
}
return angle::Result::Continue;
}
angle::Result TextureMtl::initializeContents(const gl::Context *context, angle::Result TextureMtl::initializeContents(const gl::Context *context,
const gl::ImageIndex &imageIndex) const gl::ImageIndex &index)
{ {
return mtl::InitializeTextureContents(context, mTexture, mFormat, imageIndex); mtl::TextureRef &image = mTexImages[GetImageLayerIndex(index)][index.getLevelIndex()];
return mtl::InitializeTextureContents(context, image, mFormat, GetImageBaseLevelIndex(image));
} }
angle::Result TextureMtl::copySubImageImpl(const gl::Context *context, angle::Result TextureMtl::copySubImageImpl(const gl::Context *context,
...@@ -750,8 +910,6 @@ angle::Result TextureMtl::copySubImageImpl(const gl::Context *context, ...@@ -750,8 +910,6 @@ angle::Result TextureMtl::copySubImageImpl(const gl::Context *context,
const gl::InternalFormat &internalFormat, const gl::InternalFormat &internalFormat,
gl::Framebuffer *source) gl::Framebuffer *source)
{ {
ASSERT(mTexture && mTexture->valid());
gl::Extents fbSize = source->getReadColorAttachment()->getSize(); gl::Extents fbSize = source->getReadColorAttachment()->getSize();
gl::Rectangle clippedSourceArea; gl::Rectangle clippedSourceArea;
if (!ClipRectangle(sourceArea, gl::Rectangle(0, 0, fbSize.width, fbSize.height), if (!ClipRectangle(sourceArea, gl::Rectangle(0, 0, fbSize.width, fbSize.height),
...@@ -766,6 +924,8 @@ angle::Result TextureMtl::copySubImageImpl(const gl::Context *context, ...@@ -766,6 +924,8 @@ angle::Result TextureMtl::copySubImageImpl(const gl::Context *context,
const gl::Offset modifiedDestOffset(destOffset.x + clippedSourceArea.x - sourceArea.x, const gl::Offset modifiedDestOffset(destOffset.x + clippedSourceArea.x - sourceArea.x,
destOffset.y + clippedSourceArea.y - sourceArea.y, 0); destOffset.y + clippedSourceArea.y - sourceArea.y, 0);
ANGLE_TRY(ensureImageCreated(context, index));
if (!mtl::Format::FormatRenderable(mFormat.metalFormat)) if (!mtl::Format::FormatRenderable(mFormat.metalFormat))
{ {
return copySubImageCPU(context, index, modifiedDestOffset, clippedSourceArea, return copySubImageCPU(context, index, modifiedDestOffset, clippedSourceArea,
...@@ -796,11 +956,15 @@ angle::Result TextureMtl::copySubImageWithDraw(const gl::Context *context, ...@@ -796,11 +956,15 @@ angle::Result TextureMtl::copySubImageWithDraw(const gl::Context *context,
return angle::Result::Continue; return angle::Result::Continue;
} }
mtl::RenderCommandEncoder *cmdEncoder = contextMtl->getRenderCommandEncoder(mTexture, index); mtl::TextureRef &image = mTexImages[GetImageLayerIndex(index)][index.getLevelIndex()];
ASSERT(image && image->valid());
mtl::RenderCommandEncoder *cmdEncoder =
contextMtl->getRenderCommandEncoder(image, GetImageBaseLevelIndex(image));
mtl::BlitParams blitParams; mtl::BlitParams blitParams;
blitParams.dstOffset = modifiedDestOffset; blitParams.dstOffset = modifiedDestOffset;
blitParams.dstColorMask = mTexture->getColorWritableMask(); blitParams.dstColorMask = image->getColorWritableMask();
blitParams.src = colorReadRT->getTexture(); blitParams.src = colorReadRT->getTexture();
blitParams.srcLevel = static_cast<uint32_t>(colorReadRT->getLevelIndex()); blitParams.srcLevel = static_cast<uint32_t>(colorReadRT->getLevelIndex());
...@@ -820,8 +984,18 @@ angle::Result TextureMtl::copySubImageCPU(const gl::Context *context, ...@@ -820,8 +984,18 @@ angle::Result TextureMtl::copySubImageCPU(const gl::Context *context,
const gl::InternalFormat &internalFormat, const gl::InternalFormat &internalFormat,
gl::Framebuffer *source) gl::Framebuffer *source)
{ {
mtl::TextureRef &image = mTexImages[GetImageLayerIndex(index)][index.getLevelIndex()];
ASSERT(image && image->valid());
ContextMtl *contextMtl = mtl::GetImpl(context); ContextMtl *contextMtl = mtl::GetImpl(context);
FramebufferMtl *framebufferMtl = mtl::GetImpl(source); FramebufferMtl *framebufferMtl = mtl::GetImpl(source);
RenderTargetMtl *colorReadRT = framebufferMtl->getColorReadRenderTarget();
if (!colorReadRT || !colorReadRT->getTexture())
{
// Is this an error?
return angle::Result::Continue;
}
const angle::Format &dstFormat = angle::Format::Get(mFormat.actualFormatId); const angle::Format &dstFormat = angle::Format::Get(mFormat.actualFormatId);
const int dstRowPitch = dstFormat.pixelBytes * clippedSourceArea.width; const int dstRowPitch = dstFormat.pixelBytes * clippedSourceArea.width;
...@@ -845,9 +1019,7 @@ angle::Result TextureMtl::copySubImageCPU(const gl::Context *context, ...@@ -845,9 +1019,7 @@ angle::Result TextureMtl::copySubImageCPU(const gl::Context *context,
conversionRow.data())); conversionRow.data()));
// Upload to texture // Upload to texture
mTexture->replaceRegion(contextMtl, mtlDstRowArea, index.getLevelIndex(), image->replaceRegion(contextMtl, mtlDstRowArea, 0, 0, conversionRow.data(), dstRowPitch);
index.hasLayer() ? index.cubeMapFaceIndex() : 0,
conversionRow.data(), dstRowPitch);
} }
return angle::Result::Continue; return angle::Result::Continue;
......
# Texture data completeness's handling in the Metal back-end
The OpenGL spec allows a texture's images to be defined without consistent size and format through
glTexImage*, glCopyImage* calls. The texture doesn't need to be complete when created.
The OpenGL context checks the texture's images during draw calls. It considers the texture complete
if the images are consistent in size and format. Then it uses the texture for rendering.
Metal textures (i.e. MTLTexture) on the other hand require consistent defined images at all times.
MTLTextures are always created complete.
This is an overview of how the Metal back-end implements images' management for a texture to make
sure it is GL spec conformant (TextureMtl):
1. Initially:
* no actual MTLTexture is created yet.
* glTexImage/glCopyImage(slice,level):
* a single image (`images[slice][level]`: 2D/3D MTLTexture no mipmap + single slice) is
created to store data for the texture at this level/slice.
* glTexSubImage/glCopyTexSubImage(slice,level):
* modifies the data of `images[slice][level]`;
2. If the texture is complete at Draw/generateMip/FBO attachment call:
* an actual MTLTexture object is created. We can call it "native" texture, i.e. the real texture
that will be consumed by Metal draw calls.
- `images[0][0]` --> copy to actual texture's slice 0, level 0.
- `images[0][1]` --> copy to actual texture's slice 0, level 1.
- `images[0][2]` --> copy to actual texture's slice 0, level 2.
- ...
* The images will be destroyed, then re-created to become texture views of the actual texture at
the specified level/slice.
- `images[0][0]` -> view of actual texture's slice 0, level 0.
- `images[0][1]` -> view of actual texture's slice 0, level 1.
- `images[0][2]` -> view of actual texture's slice 0, level 2.
- ...
3. After texture is complete:
* glTexSubImage/glCopyTexSubImage(slice,level):
* `images[slice][level]`'s content is modified, which means the actual texture's content at
respective slice & level is modified also. Since the former is a view of the latter at given
slice & level.
* glTexImage/glCopyImage(slice,level):
* If size != `images[slice][level]`.size():
- Destroy the actual texture (the other views are kept intact), recreate
`images[slice][level]` as single image same as initial stage. The other views are kept
intact so that texture data at those slice & level can be reused later.
* else:
- behaves as glTexSubImage/glCopyTexSubImage(slice,level).
...@@ -33,6 +33,7 @@ namespace mtl ...@@ -33,6 +33,7 @@ namespace mtl
{ {
class CommandQueue; class CommandQueue;
class BlitCommandEncoder;
class Resource; class Resource;
class Texture; class Texture;
class Buffer; class Buffer;
...@@ -98,6 +99,7 @@ class Texture final : public Resource, ...@@ -98,6 +99,7 @@ class Texture final : public Resource,
uint32_t height, uint32_t height,
uint32_t mips /** use zero to create full mipmaps chain */, uint32_t mips /** use zero to create full mipmaps chain */,
bool renderTargetOnly, bool renderTargetOnly,
bool allowTextureView,
TextureRef *refOut); TextureRef *refOut);
static angle::Result MakeCubeTexture(ContextMtl *context, static angle::Result MakeCubeTexture(ContextMtl *context,
...@@ -105,6 +107,7 @@ class Texture final : public Resource, ...@@ -105,6 +107,7 @@ class Texture final : public Resource,
uint32_t size, uint32_t size,
uint32_t mips /** use zero to create full mipmaps chain */, uint32_t mips /** use zero to create full mipmaps chain */,
bool renderTargetOnly, bool renderTargetOnly,
bool allowTextureView,
TextureRef *refOut); TextureRef *refOut);
static TextureRef MakeFromMetal(id<MTLTexture> metalTexture); static TextureRef MakeFromMetal(id<MTLTexture> metalTexture);
...@@ -123,8 +126,10 @@ class Texture final : public Resource, ...@@ -123,8 +126,10 @@ class Texture final : public Resource,
uint32_t mipmapLevel, uint32_t mipmapLevel,
uint8_t *dataOut); uint8_t *dataOut);
// Create 2d view of a cube face // Create 2d view of a cube face which full range of mip levels.
TextureRef createFaceView(uint32_t face); TextureRef createCubeFaceView(uint32_t face);
// Create a view of one slice at a level.
TextureRef createSliceMipView(uint32_t slice, uint32_t level);
MTLTextureType textureType() const; MTLTextureType textureType() const;
MTLPixelFormat pixelFormat() const; MTLPixelFormat pixelFormat() const;
...@@ -138,12 +143,15 @@ class Texture final : public Resource, ...@@ -138,12 +143,15 @@ class Texture final : public Resource,
gl::Extents size(const gl::ImageIndex &index) const; gl::Extents size(const gl::ImageIndex &index) const;
// For render target // For render target
MTLColorWriteMask getColorWritableMask() const { return mColorWritableMask; } MTLColorWriteMask getColorWritableMask() const { return *mColorWritableMask; }
void setColorWritableMask(MTLColorWriteMask mask) { mColorWritableMask = mask; } void setColorWritableMask(MTLColorWriteMask mask) { *mColorWritableMask = mask; }
// Change the wrapped metal object. Special case for swapchain image // Change the wrapped metal object. Special case for swapchain image
void set(id<MTLTexture> metalTexture); void set(id<MTLTexture> metalTexture);
// sync content between CPU and GPU
void syncContent(ContextMtl *context, mtl::BlitCommandEncoder *encoder);
private: private:
using ParentClass = WrappedObject<id<MTLTexture>>; using ParentClass = WrappedObject<id<MTLTexture>>;
...@@ -159,7 +167,8 @@ class Texture final : public Resource, ...@@ -159,7 +167,8 @@ class Texture final : public Resource,
void syncContent(ContextMtl *context); void syncContent(ContextMtl *context);
MTLColorWriteMask mColorWritableMask = MTLColorWriteMaskAll; // This property is shared between this object and its views:
std::shared_ptr<MTLColorWriteMask> mColorWritableMask;
}; };
class Buffer final : public Resource, public WrappedObject<id<MTLBuffer>> class Buffer final : public Resource, public WrappedObject<id<MTLBuffer>>
......
...@@ -96,6 +96,7 @@ angle::Result Texture::Make2DTexture(ContextMtl *context, ...@@ -96,6 +96,7 @@ angle::Result Texture::Make2DTexture(ContextMtl *context,
uint32_t height, uint32_t height,
uint32_t mips, uint32_t mips,
bool renderTargetOnly, bool renderTargetOnly,
bool allowTextureView,
TextureRef *refOut) TextureRef *refOut)
{ {
ANGLE_MTL_OBJC_SCOPE ANGLE_MTL_OBJC_SCOPE
...@@ -107,7 +108,7 @@ angle::Result Texture::Make2DTexture(ContextMtl *context, ...@@ -107,7 +108,7 @@ angle::Result Texture::Make2DTexture(ContextMtl *context,
mipmapped:mips == 0 || mips > 1]; mipmapped:mips == 0 || mips > 1];
SetTextureSwizzle(context, format, desc); SetTextureSwizzle(context, format, desc);
refOut->reset(new Texture(context, desc, mips, renderTargetOnly, false)); refOut->reset(new Texture(context, desc, mips, renderTargetOnly, allowTextureView));
} // ANGLE_MTL_OBJC_SCOPE } // ANGLE_MTL_OBJC_SCOPE
if (!refOut || !refOut->get()) if (!refOut || !refOut->get())
...@@ -124,6 +125,7 @@ angle::Result Texture::MakeCubeTexture(ContextMtl *context, ...@@ -124,6 +125,7 @@ angle::Result Texture::MakeCubeTexture(ContextMtl *context,
uint32_t size, uint32_t size,
uint32_t mips, uint32_t mips,
bool renderTargetOnly, bool renderTargetOnly,
bool allowTextureView,
TextureRef *refOut) TextureRef *refOut)
{ {
ANGLE_MTL_OBJC_SCOPE ANGLE_MTL_OBJC_SCOPE
...@@ -133,7 +135,7 @@ angle::Result Texture::MakeCubeTexture(ContextMtl *context, ...@@ -133,7 +135,7 @@ angle::Result Texture::MakeCubeTexture(ContextMtl *context,
size:size size:size
mipmapped:mips == 0 || mips > 1]; mipmapped:mips == 0 || mips > 1];
SetTextureSwizzle(context, format, desc); SetTextureSwizzle(context, format, desc);
refOut->reset(new Texture(context, desc, mips, renderTargetOnly, true)); refOut->reset(new Texture(context, desc, mips, renderTargetOnly, allowTextureView));
} // ANGLE_MTL_OBJC_SCOPE } // ANGLE_MTL_OBJC_SCOPE
if (!refOut || !refOut->get()) if (!refOut || !refOut->get())
...@@ -151,6 +153,7 @@ TextureRef Texture::MakeFromMetal(id<MTLTexture> metalTexture) ...@@ -151,6 +153,7 @@ TextureRef Texture::MakeFromMetal(id<MTLTexture> metalTexture)
} }
Texture::Texture(id<MTLTexture> metalTexture) Texture::Texture(id<MTLTexture> metalTexture)
: mColorWritableMask(std::make_shared<MTLColorWriteMask>(MTLColorWriteMaskAll))
{ {
set(metalTexture); set(metalTexture);
} }
...@@ -160,6 +163,7 @@ Texture::Texture(ContextMtl *context, ...@@ -160,6 +163,7 @@ Texture::Texture(ContextMtl *context,
uint32_t mips, uint32_t mips,
bool renderTargetOnly, bool renderTargetOnly,
bool supportTextureView) bool supportTextureView)
: mColorWritableMask(std::make_shared<MTLColorWriteMask>(MTLColorWriteMaskAll))
{ {
ANGLE_MTL_OBJC_SCOPE ANGLE_MTL_OBJC_SCOPE
{ {
...@@ -198,7 +202,8 @@ Texture::Texture(ContextMtl *context, ...@@ -198,7 +202,8 @@ Texture::Texture(ContextMtl *context,
} }
Texture::Texture(Texture *original, MTLTextureType type, NSRange mipmapLevelRange, uint32_t slice) Texture::Texture(Texture *original, MTLTextureType type, NSRange mipmapLevelRange, uint32_t slice)
: Resource(original) : Resource(original),
mColorWritableMask(original->mColorWritableMask) // Share color write mask property
{ {
ANGLE_MTL_OBJC_SCOPE ANGLE_MTL_OBJC_SCOPE
{ {
...@@ -211,6 +216,16 @@ Texture::Texture(Texture *original, MTLTextureType type, NSRange mipmapLevelRang ...@@ -211,6 +216,16 @@ Texture::Texture(Texture *original, MTLTextureType type, NSRange mipmapLevelRang
} }
} }
void Texture::syncContent(ContextMtl *context, mtl::BlitCommandEncoder *blitEncoder)
{
#if TARGET_OS_OSX || TARGET_OS_MACCATALYST
if (blitEncoder)
{
blitEncoder->synchronizeResource(shared_from_this());
}
#endif
}
void Texture::syncContent(ContextMtl *context) void Texture::syncContent(ContextMtl *context)
{ {
#if TARGET_OS_OSX || TARGET_OS_MACCATALYST #if TARGET_OS_OSX || TARGET_OS_MACCATALYST
...@@ -220,10 +235,8 @@ void Texture::syncContent(ContextMtl *context) ...@@ -220,10 +235,8 @@ void Texture::syncContent(ContextMtl *context)
if (this->isCPUReadMemDirty()) if (this->isCPUReadMemDirty())
{ {
mtl::BlitCommandEncoder *blitEncoder = context->getBlitCommandEncoder(); mtl::BlitCommandEncoder *blitEncoder = context->getBlitCommandEncoder();
if (blitEncoder) syncContent(context, blitEncoder);
{
blitEncoder->synchronizeResource(shared_from_this());
}
this->resetCPUReadMemDirty(); this->resetCPUReadMemDirty();
} }
#endif #endif
...@@ -281,7 +294,7 @@ void Texture::getBytes(ContextMtl *context, ...@@ -281,7 +294,7 @@ void Texture::getBytes(ContextMtl *context,
[get() getBytes:dataOut bytesPerRow:bytesPerRow fromRegion:region mipmapLevel:mipmapLevel]; [get() getBytes:dataOut bytesPerRow:bytesPerRow fromRegion:region mipmapLevel:mipmapLevel];
} }
TextureRef Texture::createFaceView(uint32_t face) TextureRef Texture::createCubeFaceView(uint32_t face)
{ {
ANGLE_MTL_OBJC_SCOPE ANGLE_MTL_OBJC_SCOPE
{ {
...@@ -291,6 +304,24 @@ TextureRef Texture::createFaceView(uint32_t face) ...@@ -291,6 +304,24 @@ TextureRef Texture::createFaceView(uint32_t face)
return TextureRef( return TextureRef(
new Texture(this, MTLTextureType2D, NSMakeRange(0, mipmapLevels()), face)); new Texture(this, MTLTextureType2D, NSMakeRange(0, mipmapLevels()), face));
default: default:
UNREACHABLE();
return nullptr;
}
}
}
TextureRef Texture::createSliceMipView(uint32_t slice, uint32_t level)
{
ANGLE_MTL_OBJC_SCOPE
{
switch (textureType())
{
case MTLTextureTypeCube:
case MTLTextureType2D:
return TextureRef(
new Texture(this, MTLTextureType2D, NSMakeRange(level, 1), slice));
default:
UNREACHABLE();
return nullptr; return nullptr;
} }
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment