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
void updateVertexArray(const gl::Context *context);
angle::Result updateDefaultAttribute(size_t attribIndex);
angle::Result handleDirtyActiveTextures(const gl::Context *context);
angle::Result handleDirtyDefaultAttribs(const gl::Context *context);
angle::Result handleDirtyDriverUniforms(const gl::Context *context);
angle::Result handleDirtyDepthStencilState(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:
break;
case gl::State::DIRTY_BIT_IMAGE_BINDINGS:
// NOTE(hqle): properly handle GLSL images.
invalidateCurrentTextures();
break;
case gl::State::DIRTY_BIT_MULTISAMPLING:
// NOTE(hqle): MSAA feature.
......@@ -1185,6 +1187,7 @@ void ContextMtl::updateDrawFrameBufferBinding(const gl::Context *context)
void ContextMtl::onDrawFrameBufferChange(const gl::Context *context, FramebufferMtl *framebuffer)
{
const gl::State &glState = getState();
ASSERT(framebuffer == mtl::GetImpl(glState.getDrawFramebuffer()));
mDirtyBits.set(DIRTY_BIT_DRAW_FRAMEBUFFER);
......@@ -1251,12 +1254,19 @@ angle::Result ContextMtl::setupDraw(const gl::Context *context,
&lineLoopLastSegmentIndexBuffer));
}
// Must be called before the command buffer is started.
// Must be called before the render command encoder is started.
if (context->getStateCache().hasAnyActiveClientAttrib())
{
ANGLE_TRY(mVertexArray->updateClientAttribs(context, firstVertex, vertexOrIndexCount,
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())
{
......@@ -1277,20 +1287,19 @@ angle::Result ContextMtl::setupDraw(const gl::Context *context,
Optional<mtl::RenderPipelineDesc> changedPipelineDesc;
ANGLE_TRY(checkIfPipelineChanged(context, mode, &changedPipelineDesc));
bool textureChanged = false;
for (size_t bit : mDirtyBits)
{
switch (bit)
{
case DIRTY_BIT_TEXTURES:
// Already handled.
break;
case DIRTY_BIT_DEFAULT_ATTRIBS:
ANGLE_TRY(handleDirtyDefaultAttribs(context));
break;
case DIRTY_BIT_DRIVER_UNIFORMS:
ANGLE_TRY(handleDirtyDriverUniforms(context));
break;
case DIRTY_BIT_TEXTURES:
textureChanged = true;
break;
case DIRTY_BIT_DEPTH_STENCIL_DESC:
ANGLE_TRY(handleDirtyDepthStencilState(context));
break;
......@@ -1312,17 +1321,26 @@ angle::Result ContextMtl::setupDraw(const gl::Context *context,
case DIRTY_BIT_SCISSOR:
mRenderEncoder.setScissorRect(mScissorRect);
break;
case DIRTY_BIT_DRAW_FRAMEBUFFER:
// Already handled.
break;
case DIRTY_BIT_CULL_MODE:
mRenderEncoder.setCullMode(mCullMode);
break;
case DIRTY_BIT_WINDING:
mRenderEncoder.setFrontFacingWinding(mWinding);
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));
if (mode == gl::PrimitiveMode::LineLoop)
......@@ -1372,6 +1390,34 @@ angle::Result ContextMtl::genLineLoopLastSegment(const gl::Context *context,
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)
{
for (size_t attribIndex : mDirtyDefaultAttribsMask)
......@@ -1384,7 +1430,6 @@ angle::Result ContextMtl::handleDirtyDefaultAttribs(const gl::Context *context)
mRenderEncoder.setVertexData(mDefaultAttributes, mtl::kDefaultAttribsBindingIndex);
mDirtyDefaultAttribsMask.reset();
mDirtyBits.reset(DIRTY_BIT_DEFAULT_ATTRIBS);
return angle::Result::Continue;
}
......@@ -1414,7 +1459,6 @@ angle::Result ContextMtl::handleDirtyDriverUniforms(const gl::Context *context)
mRenderEncoder.setFragmentData(mDriverUniforms, mtl::kDriverUniformsBindingIndex);
mRenderEncoder.setVertexData(mDriverUniforms, mtl::kDriverUniformsBindingIndex);
mDirtyBits.reset(DIRTY_BIT_DRIVER_UNIFORMS);
return angle::Result::Continue;
}
......@@ -1441,7 +1485,6 @@ angle::Result ContextMtl::handleDirtyDepthStencilState(const gl::Context *contex
// Apply depth stencil state
mRenderEncoder.setDepthStencilState(getDisplay()->getDepthStencilState(dsDesc));
mDirtyBits.reset(DIRTY_BIT_DEPTH_STENCIL_DESC);
return angle::Result::Continue;
}
......@@ -1459,7 +1502,6 @@ angle::Result ContextMtl::handleDirtyDepthBias(const gl::Context *context)
0);
}
mDirtyBits.reset(DIRTY_BIT_DEPTH_BIAS);
return angle::Result::Continue;
}
......
......@@ -355,6 +355,7 @@ const gl::Extensions &DisplayMtl::getNativeExtensions() const
const mtl::TextureRef &DisplayMtl::getNullTexture(const gl::Context *context, gl::TextureType type)
{
// TODO(hqle): Use rx::IncompleteTextureSet.
ContextMtl *contextMtl = mtl::GetImpl(context);
if (!mNullTextures[type])
{
......@@ -367,13 +368,13 @@ const mtl::TextureRef &DisplayMtl::getNullTexture(const gl::Context *context, gl
switch (type)
{
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]->replaceRegion(contextMtl, region, 0, 0, zeroPixel,
sizeof(zeroPixel));
break;
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]));
for (int f = 0; f < 6; ++f)
{
......
......@@ -907,12 +907,12 @@ angle::Result ProgramMtl::updateTextures(const gl::Context *glContext,
switch (shaderType)
{
case gl::ShaderType::Vertex:
textureMtl->bindVertexShader(glContext, cmdEncoder, destBindingPoint,
destBindingPoint);
ANGLE_TRY(textureMtl->bindVertexShader(glContext, cmdEncoder,
destBindingPoint, destBindingPoint));
break;
case gl::ShaderType::Fragment:
textureMtl->bindFragmentShader(glContext, cmdEncoder, destBindingPoint,
destBindingPoint);
ANGLE_TRY(textureMtl->bindFragmentShader(
glContext, cmdEncoder, destBindingPoint, destBindingPoint));
break;
default:
UNREACHABLE();
......
......@@ -60,7 +60,8 @@ angle::Result RenderbufferMtl::setStorageImpl(const gl::Context *context,
if ((mTexture == nullptr || !mTexture->valid()) && (width != 0 && height != 0))
{
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);
}
......
......@@ -281,6 +281,11 @@ FramebufferImpl *SurfaceMtl::createDefaultFramebuffer(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();
}
......@@ -436,7 +441,7 @@ angle::Result SurfaceMtl::ensureDepthStencilSizeCorrect(const gl::Context *conte
if (mDepthFormat.valid() && (!mDepthTexture || mDepthTexture->size() != size))
{
ANGLE_TRY(mtl::Texture::Make2DTexture(contextMtl, mDepthFormat, size.width, size.height, 1,
true, &mDepthTexture));
true, false, &mDepthTexture));
mDepthRenderTarget.set(mDepthTexture, 0, 0, mDepthFormat);
fboDirtyBits->set(gl::Framebuffer::DIRTY_BIT_DEPTH_ATTACHMENT);
......@@ -451,7 +456,7 @@ angle::Result SurfaceMtl::ensureDepthStencilSizeCorrect(const gl::Context *conte
else
{
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);
......
......@@ -10,6 +10,8 @@
#ifndef LIBANGLE_RENDERER_METAL_TEXTUREMTL_H_
#define LIBANGLE_RENDERER_METAL_TEXTUREMTL_H_
#include <map>
#include "common/PackedEnums.h"
#include "libANGLE/renderer/TextureImpl.h"
#include "libANGLE/renderer/metal/RenderTargetMtl.h"
......@@ -140,19 +142,30 @@ class TextureMtl : public TextureImpl
angle::Result initializeContents(const gl::Context *context,
const gl::ImageIndex &imageIndex) override;
void bindVertexShader(const gl::Context *context,
mtl::RenderCommandEncoder *cmdEncoder,
int textureSlotIndex,
int samplerSlotIndex);
void bindFragmentShader(const gl::Context *context,
mtl::RenderCommandEncoder *cmdEncoder,
int textureSlotIndex,
int samplerSlotIndex);
// The texture's data is initially initialized and stored in an array
// of images through glTexImage*/glCopyTex* calls. During draw calls, the caller must make sure
// the actual texture is created by calling this method to transfer the stored images data
// to the actual texture.
angle::Result ensureTextureCreated(const gl::Context *context);
angle::Result bindVertexShader(const gl::Context *context,
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; }
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.
angle::Result setStorageImpl(const gl::Context *context,
......@@ -212,12 +225,17 @@ class TextureMtl : public TextureImpl
angle::Result generateMipmapCPU(const gl::Context *context);
mtl::Format mFormat;
mtl::TextureRef mTexture;
// The real texture used by Metal draw calls.
mtl::TextureRef mNativeTexture;
id<MTLSamplerState> mMetalSamplerState = nil;
std::vector<RenderTargetMtl> mLayeredRenderTargets;
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;
};
......
# 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
{
class CommandQueue;
class BlitCommandEncoder;
class Resource;
class Texture;
class Buffer;
......@@ -98,6 +99,7 @@ class Texture final : public Resource,
uint32_t height,
uint32_t mips /** use zero to create full mipmaps chain */,
bool renderTargetOnly,
bool allowTextureView,
TextureRef *refOut);
static angle::Result MakeCubeTexture(ContextMtl *context,
......@@ -105,6 +107,7 @@ class Texture final : public Resource,
uint32_t size,
uint32_t mips /** use zero to create full mipmaps chain */,
bool renderTargetOnly,
bool allowTextureView,
TextureRef *refOut);
static TextureRef MakeFromMetal(id<MTLTexture> metalTexture);
......@@ -123,8 +126,10 @@ class Texture final : public Resource,
uint32_t mipmapLevel,
uint8_t *dataOut);
// Create 2d view of a cube face
TextureRef createFaceView(uint32_t face);
// Create 2d view of a cube face which full range of mip levels.
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;
MTLPixelFormat pixelFormat() const;
......@@ -138,12 +143,15 @@ class Texture final : public Resource,
gl::Extents size(const gl::ImageIndex &index) const;
// For render target
MTLColorWriteMask getColorWritableMask() const { return mColorWritableMask; }
void setColorWritableMask(MTLColorWriteMask mask) { mColorWritableMask = mask; }
MTLColorWriteMask getColorWritableMask() const { return *mColorWritableMask; }
void setColorWritableMask(MTLColorWriteMask mask) { *mColorWritableMask = mask; }
// Change the wrapped metal object. Special case for swapchain image
void set(id<MTLTexture> metalTexture);
// sync content between CPU and GPU
void syncContent(ContextMtl *context, mtl::BlitCommandEncoder *encoder);
private:
using ParentClass = WrappedObject<id<MTLTexture>>;
......@@ -159,7 +167,8 @@ class Texture final : public Resource,
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>>
......
......@@ -96,6 +96,7 @@ angle::Result Texture::Make2DTexture(ContextMtl *context,
uint32_t height,
uint32_t mips,
bool renderTargetOnly,
bool allowTextureView,
TextureRef *refOut)
{
ANGLE_MTL_OBJC_SCOPE
......@@ -107,7 +108,7 @@ angle::Result Texture::Make2DTexture(ContextMtl *context,
mipmapped:mips == 0 || mips > 1];
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
if (!refOut || !refOut->get())
......@@ -124,6 +125,7 @@ angle::Result Texture::MakeCubeTexture(ContextMtl *context,
uint32_t size,
uint32_t mips,
bool renderTargetOnly,
bool allowTextureView,
TextureRef *refOut)
{
ANGLE_MTL_OBJC_SCOPE
......@@ -133,7 +135,7 @@ angle::Result Texture::MakeCubeTexture(ContextMtl *context,
size:size
mipmapped:mips == 0 || mips > 1];
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
if (!refOut || !refOut->get())
......@@ -151,6 +153,7 @@ TextureRef Texture::MakeFromMetal(id<MTLTexture> metalTexture)
}
Texture::Texture(id<MTLTexture> metalTexture)
: mColorWritableMask(std::make_shared<MTLColorWriteMask>(MTLColorWriteMaskAll))
{
set(metalTexture);
}
......@@ -160,6 +163,7 @@ Texture::Texture(ContextMtl *context,
uint32_t mips,
bool renderTargetOnly,
bool supportTextureView)
: mColorWritableMask(std::make_shared<MTLColorWriteMask>(MTLColorWriteMaskAll))
{
ANGLE_MTL_OBJC_SCOPE
{
......@@ -198,7 +202,8 @@ Texture::Texture(ContextMtl *context,
}
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
{
......@@ -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)
{
#if TARGET_OS_OSX || TARGET_OS_MACCATALYST
......@@ -220,10 +235,8 @@ void Texture::syncContent(ContextMtl *context)
if (this->isCPUReadMemDirty())
{
mtl::BlitCommandEncoder *blitEncoder = context->getBlitCommandEncoder();
if (blitEncoder)
{
blitEncoder->synchronizeResource(shared_from_this());
}
syncContent(context, blitEncoder);
this->resetCPUReadMemDirty();
}
#endif
......@@ -281,7 +294,7 @@ void Texture::getBytes(ContextMtl *context,
[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
{
......@@ -291,6 +304,24 @@ TextureRef Texture::createFaceView(uint32_t face)
return TextureRef(
new Texture(this, MTLTextureType2D, NSMakeRange(0, mipmapLevels()), face));
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;
}
}
......
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