// // 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. // // mtl_state_cache.mm: // Implements StateCache, RenderPipelineCache and various // C struct versions of Metal sampler, depth stencil, render pass, render pipeline descriptors. // #include "libANGLE/renderer/metal/mtl_state_cache.h" #include #include "common/debug.h" #include "common/hash_utils.h" #include "libANGLE/renderer/metal/ContextMtl.h" #include "libANGLE/renderer/metal/mtl_resources.h" #include "libANGLE/renderer/metal/mtl_utils.h" #include "platform/FeaturesMtl.h" #define ANGLE_OBJC_CP_PROPERTY(DST, SRC, PROPERTY) \ (DST).PROPERTY = static_cast<__typeof__((DST).PROPERTY)>(ToObjC((SRC).PROPERTY)) #define ANGLE_PROP_EQ(LHS, RHS, PROP) ((LHS).PROP == (RHS).PROP) namespace rx { namespace mtl { namespace { template inline T ToObjC(const T p) { return p; } inline MTLStencilDescriptor *ToObjC(const StencilDesc &desc) { MTLStencilDescriptor *objCDesc = [[MTLStencilDescriptor alloc] init]; ANGLE_OBJC_CP_PROPERTY(objCDesc, desc, stencilFailureOperation); ANGLE_OBJC_CP_PROPERTY(objCDesc, desc, depthFailureOperation); ANGLE_OBJC_CP_PROPERTY(objCDesc, desc, depthStencilPassOperation); ANGLE_OBJC_CP_PROPERTY(objCDesc, desc, stencilCompareFunction); ANGLE_OBJC_CP_PROPERTY(objCDesc, desc, readMask); ANGLE_OBJC_CP_PROPERTY(objCDesc, desc, writeMask); return [objCDesc ANGLE_MTL_AUTORELEASE]; } MTLDepthStencilDescriptor *ToObjC(const DepthStencilDesc &desc) { MTLDepthStencilDescriptor *objCDesc = [[MTLDepthStencilDescriptor alloc] init]; ANGLE_OBJC_CP_PROPERTY(objCDesc, desc, backFaceStencil); ANGLE_OBJC_CP_PROPERTY(objCDesc, desc, frontFaceStencil); ANGLE_OBJC_CP_PROPERTY(objCDesc, desc, depthCompareFunction); ANGLE_OBJC_CP_PROPERTY(objCDesc, desc, depthWriteEnabled); return [objCDesc ANGLE_MTL_AUTORELEASE]; } MTLSamplerDescriptor *ToObjC(const SamplerDesc &desc) { MTLSamplerDescriptor *objCDesc = [[MTLSamplerDescriptor alloc] init]; ANGLE_OBJC_CP_PROPERTY(objCDesc, desc, rAddressMode); ANGLE_OBJC_CP_PROPERTY(objCDesc, desc, sAddressMode); ANGLE_OBJC_CP_PROPERTY(objCDesc, desc, tAddressMode); ANGLE_OBJC_CP_PROPERTY(objCDesc, desc, minFilter); ANGLE_OBJC_CP_PROPERTY(objCDesc, desc, magFilter); ANGLE_OBJC_CP_PROPERTY(objCDesc, desc, mipFilter); ANGLE_OBJC_CP_PROPERTY(objCDesc, desc, maxAnisotropy); ANGLE_OBJC_CP_PROPERTY(objCDesc, desc, compareFunction); return [objCDesc ANGLE_MTL_AUTORELEASE]; } MTLVertexAttributeDescriptor *ToObjC(const VertexAttributeDesc &desc) { MTLVertexAttributeDescriptor *objCDesc = [[MTLVertexAttributeDescriptor alloc] init]; ANGLE_OBJC_CP_PROPERTY(objCDesc, desc, format); ANGLE_OBJC_CP_PROPERTY(objCDesc, desc, offset); ANGLE_OBJC_CP_PROPERTY(objCDesc, desc, bufferIndex); ASSERT(desc.bufferIndex >= kVboBindingIndexStart); return [objCDesc ANGLE_MTL_AUTORELEASE]; } MTLVertexBufferLayoutDescriptor *ToObjC(const VertexBufferLayoutDesc &desc) { MTLVertexBufferLayoutDescriptor *objCDesc = [[MTLVertexBufferLayoutDescriptor alloc] init]; ANGLE_OBJC_CP_PROPERTY(objCDesc, desc, stepFunction); ANGLE_OBJC_CP_PROPERTY(objCDesc, desc, stepRate); ANGLE_OBJC_CP_PROPERTY(objCDesc, desc, stride); return [objCDesc ANGLE_MTL_AUTORELEASE]; } MTLVertexDescriptor *ToObjC(const VertexDesc &desc) { MTLVertexDescriptor *objCDesc = [[MTLVertexDescriptor alloc] init]; [objCDesc reset]; for (uint8_t i = 0; i < desc.numAttribs; ++i) { [objCDesc.attributes setObject:ToObjC(desc.attributes[i]) atIndexedSubscript:i]; } for (uint8_t i = 0; i < desc.numBufferLayouts; ++i) { // Ignore if stepFunction is kVertexStepFunctionInvalid. // If we don't set this slot, it will apparently be disabled by metal runtime. if (desc.layouts[i].stepFunction != kVertexStepFunctionInvalid) { [objCDesc.layouts setObject:ToObjC(desc.layouts[i]) atIndexedSubscript:i]; } } return [objCDesc ANGLE_MTL_AUTORELEASE]; } MTLRenderPipelineColorAttachmentDescriptor *ToObjC(const RenderPipelineColorAttachmentDesc &desc) { MTLRenderPipelineColorAttachmentDescriptor *objCDesc = [[MTLRenderPipelineColorAttachmentDescriptor alloc] init]; ANGLE_OBJC_CP_PROPERTY(objCDesc, desc, pixelFormat); ANGLE_OBJC_CP_PROPERTY(objCDesc, desc, writeMask); ANGLE_OBJC_CP_PROPERTY(objCDesc, desc, alphaBlendOperation); ANGLE_OBJC_CP_PROPERTY(objCDesc, desc, rgbBlendOperation); ANGLE_OBJC_CP_PROPERTY(objCDesc, desc, destinationAlphaBlendFactor); ANGLE_OBJC_CP_PROPERTY(objCDesc, desc, destinationRGBBlendFactor); ANGLE_OBJC_CP_PROPERTY(objCDesc, desc, sourceAlphaBlendFactor); ANGLE_OBJC_CP_PROPERTY(objCDesc, desc, sourceRGBBlendFactor); ANGLE_OBJC_CP_PROPERTY(objCDesc, desc, blendingEnabled); return [objCDesc ANGLE_MTL_AUTORELEASE]; } MTLRenderPipelineDescriptor *ToObjC(id vertexShader, id fragmentShader, const RenderPipelineDesc &desc) { MTLRenderPipelineDescriptor *objCDesc = [[MTLRenderPipelineDescriptor alloc] init]; [objCDesc reset]; ANGLE_OBJC_CP_PROPERTY(objCDesc, desc, vertexDescriptor); for (uint8_t i = 0; i < desc.outputDescriptor.numColorAttachments; ++i) { [objCDesc.colorAttachments setObject:ToObjC(desc.outputDescriptor.colorAttachments[i]) atIndexedSubscript:i]; } ANGLE_OBJC_CP_PROPERTY(objCDesc, desc.outputDescriptor, depthAttachmentPixelFormat); ANGLE_OBJC_CP_PROPERTY(objCDesc, desc.outputDescriptor, stencilAttachmentPixelFormat); ANGLE_OBJC_CP_PROPERTY(objCDesc, desc.outputDescriptor, sampleCount); #if ANGLE_MTL_PRIMITIVE_TOPOLOGY_CLASS_AVAILABLE ANGLE_OBJC_CP_PROPERTY(objCDesc, desc, inputPrimitiveTopology); #endif ANGLE_OBJC_CP_PROPERTY(objCDesc, desc, alphaToCoverageEnabled); // rasterizationEnabled will be true for both EmulatedDiscard & Enabled. objCDesc.rasterizationEnabled = desc.rasterizationEnabled(); objCDesc.vertexFunction = vertexShader; objCDesc.fragmentFunction = objCDesc.rasterizationEnabled ? fragmentShader : nil; return [objCDesc ANGLE_MTL_AUTORELEASE]; } id ToObjC(const TextureRef &texture) { auto textureRef = texture; return textureRef ? textureRef->get() : nil; } void BaseRenderPassAttachmentDescToObjC(const RenderPassAttachmentDesc &src, MTLRenderPassAttachmentDescriptor *dst) { const TextureRef &implicitMsTexture = src.implicitMSTexture; if (implicitMsTexture) { dst.texture = ToObjC(implicitMsTexture); dst.level = 0; dst.slice = 0; dst.depthPlane = 0; dst.resolveTexture = ToObjC(src.texture); dst.resolveLevel = src.level.get(); if (dst.resolveTexture.textureType == MTLTextureType3D) { dst.resolveDepthPlane = src.sliceOrDepth; dst.resolveSlice = 0; } else { dst.resolveSlice = src.sliceOrDepth; dst.resolveDepthPlane = 0; } } else { dst.texture = ToObjC(src.texture); dst.level = src.level.get(); if (dst.texture.textureType == MTLTextureType3D) { dst.depthPlane = src.sliceOrDepth; dst.slice = 0; } else { dst.slice = src.sliceOrDepth; dst.depthPlane = 0; } dst.resolveTexture = nil; dst.resolveLevel = 0; dst.resolveSlice = 0; dst.resolveDepthPlane = 0; } ANGLE_OBJC_CP_PROPERTY(dst, src, loadAction); ANGLE_OBJC_CP_PROPERTY(dst, src, storeAction); ANGLE_OBJC_CP_PROPERTY(dst, src, storeActionOptions); } void ToObjC(const RenderPassColorAttachmentDesc &desc, MTLRenderPassColorAttachmentDescriptor *objCDesc) { BaseRenderPassAttachmentDescToObjC(desc, objCDesc); ANGLE_OBJC_CP_PROPERTY(objCDesc, desc, clearColor); } void ToObjC(const RenderPassDepthAttachmentDesc &desc, MTLRenderPassDepthAttachmentDescriptor *objCDesc) { BaseRenderPassAttachmentDescToObjC(desc, objCDesc); ANGLE_OBJC_CP_PROPERTY(objCDesc, desc, clearDepth); } void ToObjC(const RenderPassStencilAttachmentDesc &desc, MTLRenderPassStencilAttachmentDescriptor *objCDesc) { BaseRenderPassAttachmentDescToObjC(desc, objCDesc); ANGLE_OBJC_CP_PROPERTY(objCDesc, desc, clearStencil); } } // namespace // StencilDesc implementation bool StencilDesc::operator==(const StencilDesc &rhs) const { return ANGLE_PROP_EQ(*this, rhs, stencilFailureOperation) && ANGLE_PROP_EQ(*this, rhs, depthFailureOperation) && ANGLE_PROP_EQ(*this, rhs, depthStencilPassOperation) && ANGLE_PROP_EQ(*this, rhs, stencilCompareFunction) && ANGLE_PROP_EQ(*this, rhs, readMask) && ANGLE_PROP_EQ(*this, rhs, writeMask); } void StencilDesc::reset() { stencilFailureOperation = depthFailureOperation = depthStencilPassOperation = MTLStencilOperationKeep; stencilCompareFunction = MTLCompareFunctionAlways; readMask = writeMask = std::numeric_limits::max() & mtl::kStencilMaskAll; } // DepthStencilDesc implementation DepthStencilDesc::DepthStencilDesc() { memset(this, 0, sizeof(*this)); } DepthStencilDesc::DepthStencilDesc(const DepthStencilDesc &src) { memcpy(this, &src, sizeof(*this)); } DepthStencilDesc::DepthStencilDesc(DepthStencilDesc &&src) { memcpy(this, &src, sizeof(*this)); } DepthStencilDesc &DepthStencilDesc::operator=(const DepthStencilDesc &src) { memcpy(this, &src, sizeof(*this)); return *this; } bool DepthStencilDesc::operator==(const DepthStencilDesc &rhs) const { return ANGLE_PROP_EQ(*this, rhs, backFaceStencil) && ANGLE_PROP_EQ(*this, rhs, frontFaceStencil) && ANGLE_PROP_EQ(*this, rhs, depthCompareFunction) && ANGLE_PROP_EQ(*this, rhs, depthWriteEnabled); } void DepthStencilDesc::reset() { frontFaceStencil.reset(); backFaceStencil.reset(); depthCompareFunction = MTLCompareFunctionAlways; depthWriteEnabled = true; } void DepthStencilDesc::updateDepthTestEnabled(const gl::DepthStencilState &dsState) { if (!dsState.depthTest) { depthCompareFunction = MTLCompareFunctionAlways; depthWriteEnabled = false; } else { updateDepthCompareFunc(dsState); updateDepthWriteEnabled(dsState); } } void DepthStencilDesc::updateDepthWriteEnabled(const gl::DepthStencilState &dsState) { depthWriteEnabled = dsState.depthTest && dsState.depthMask; } void DepthStencilDesc::updateDepthCompareFunc(const gl::DepthStencilState &dsState) { if (!dsState.depthTest) { return; } depthCompareFunction = GetCompareFunc(dsState.depthFunc); } void DepthStencilDesc::updateStencilTestEnabled(const gl::DepthStencilState &dsState) { if (!dsState.stencilTest) { frontFaceStencil.stencilCompareFunction = MTLCompareFunctionAlways; frontFaceStencil.depthFailureOperation = MTLStencilOperationKeep; frontFaceStencil.depthStencilPassOperation = MTLStencilOperationKeep; frontFaceStencil.writeMask = 0; backFaceStencil.stencilCompareFunction = MTLCompareFunctionAlways; backFaceStencil.depthFailureOperation = MTLStencilOperationKeep; backFaceStencil.depthStencilPassOperation = MTLStencilOperationKeep; backFaceStencil.writeMask = 0; } else { updateStencilFrontFuncs(dsState); updateStencilFrontOps(dsState); updateStencilFrontWriteMask(dsState); updateStencilBackFuncs(dsState); updateStencilBackOps(dsState); updateStencilBackWriteMask(dsState); } } void DepthStencilDesc::updateStencilFrontOps(const gl::DepthStencilState &dsState) { if (!dsState.stencilTest) { return; } frontFaceStencil.stencilFailureOperation = GetStencilOp(dsState.stencilFail); frontFaceStencil.depthFailureOperation = GetStencilOp(dsState.stencilPassDepthFail); frontFaceStencil.depthStencilPassOperation = GetStencilOp(dsState.stencilPassDepthPass); } void DepthStencilDesc::updateStencilBackOps(const gl::DepthStencilState &dsState) { if (!dsState.stencilTest) { return; } backFaceStencil.stencilFailureOperation = GetStencilOp(dsState.stencilBackFail); backFaceStencil.depthFailureOperation = GetStencilOp(dsState.stencilBackPassDepthFail); backFaceStencil.depthStencilPassOperation = GetStencilOp(dsState.stencilBackPassDepthPass); } void DepthStencilDesc::updateStencilFrontFuncs(const gl::DepthStencilState &dsState) { if (!dsState.stencilTest) { return; } frontFaceStencil.stencilCompareFunction = GetCompareFunc(dsState.stencilFunc); frontFaceStencil.readMask = dsState.stencilMask & mtl::kStencilMaskAll; } void DepthStencilDesc::updateStencilBackFuncs(const gl::DepthStencilState &dsState) { if (!dsState.stencilTest) { return; } backFaceStencil.stencilCompareFunction = GetCompareFunc(dsState.stencilBackFunc); backFaceStencil.readMask = dsState.stencilBackMask & mtl::kStencilMaskAll; } void DepthStencilDesc::updateStencilFrontWriteMask(const gl::DepthStencilState &dsState) { if (!dsState.stencilTest) { return; } frontFaceStencil.writeMask = dsState.stencilWritemask & mtl::kStencilMaskAll; } void DepthStencilDesc::updateStencilBackWriteMask(const gl::DepthStencilState &dsState) { if (!dsState.stencilTest) { return; } backFaceStencil.writeMask = dsState.stencilBackWritemask & mtl::kStencilMaskAll; } size_t DepthStencilDesc::hash() const { return angle::ComputeGenericHash(*this); } // SamplerDesc implementation SamplerDesc::SamplerDesc() { memset(this, 0, sizeof(*this)); } SamplerDesc::SamplerDesc(const SamplerDesc &src) { memcpy(this, &src, sizeof(*this)); } SamplerDesc::SamplerDesc(SamplerDesc &&src) { memcpy(this, &src, sizeof(*this)); } SamplerDesc::SamplerDesc(const gl::SamplerState &glState) : SamplerDesc() { rAddressMode = GetSamplerAddressMode(glState.getWrapR()); sAddressMode = GetSamplerAddressMode(glState.getWrapS()); tAddressMode = GetSamplerAddressMode(glState.getWrapT()); minFilter = GetFilter(glState.getMinFilter()); magFilter = GetFilter(glState.getMagFilter()); mipFilter = GetMipmapFilter(glState.getMinFilter()); maxAnisotropy = static_cast(glState.getMaxAnisotropy()); compareFunction = GetCompareFunc(glState.getCompareFunc()); } SamplerDesc &SamplerDesc::operator=(const SamplerDesc &src) { memcpy(this, &src, sizeof(*this)); return *this; } void SamplerDesc::reset() { rAddressMode = MTLSamplerAddressModeClampToEdge; sAddressMode = MTLSamplerAddressModeClampToEdge; tAddressMode = MTLSamplerAddressModeClampToEdge; minFilter = MTLSamplerMinMagFilterNearest; magFilter = MTLSamplerMinMagFilterNearest; mipFilter = MTLSamplerMipFilterNearest; maxAnisotropy = 1; compareFunction = MTLCompareFunctionNever; } bool SamplerDesc::operator==(const SamplerDesc &rhs) const { return ANGLE_PROP_EQ(*this, rhs, rAddressMode) && ANGLE_PROP_EQ(*this, rhs, sAddressMode) && ANGLE_PROP_EQ(*this, rhs, tAddressMode) && ANGLE_PROP_EQ(*this, rhs, minFilter) && ANGLE_PROP_EQ(*this, rhs, magFilter) && ANGLE_PROP_EQ(*this, rhs, mipFilter) && ANGLE_PROP_EQ(*this, rhs, maxAnisotropy) && ANGLE_PROP_EQ(*this, rhs, compareFunction); } size_t SamplerDesc::hash() const { return angle::ComputeGenericHash(*this); } // BlendDesc implementation bool BlendDesc::operator==(const BlendDesc &rhs) const { return ANGLE_PROP_EQ(*this, rhs, writeMask) && ANGLE_PROP_EQ(*this, rhs, alphaBlendOperation) && ANGLE_PROP_EQ(*this, rhs, rgbBlendOperation) && ANGLE_PROP_EQ(*this, rhs, destinationAlphaBlendFactor) && ANGLE_PROP_EQ(*this, rhs, destinationRGBBlendFactor) && ANGLE_PROP_EQ(*this, rhs, sourceAlphaBlendFactor) && ANGLE_PROP_EQ(*this, rhs, sourceRGBBlendFactor) && ANGLE_PROP_EQ(*this, rhs, blendingEnabled); } void BlendDesc::reset() { reset(MTLColorWriteMaskAll); } void BlendDesc::reset(MTLColorWriteMask _writeMask) { writeMask = _writeMask; blendingEnabled = false; alphaBlendOperation = rgbBlendOperation = MTLBlendOperationAdd; destinationAlphaBlendFactor = destinationRGBBlendFactor = MTLBlendFactorZero; sourceAlphaBlendFactor = sourceRGBBlendFactor = MTLBlendFactorOne; } void BlendDesc::updateWriteMask(const uint8_t angleMask) { ASSERT(angleMask == (angleMask & 0xF)); // ANGLE's packed color mask is abgr (matches Vulkan & D3D11), while Metal expects rgba. #if defined(__aarch64__) // ARM64 can reverse bits in a single instruction writeMask = __builtin_bitreverse8(angleMask) >> 4; #else /* On other architectures, Clang generates a polyfill that uses more instructions than the following expression optimized for a 4-bit value. (abgr * 0x41) & 0x14A: .......abgr + .abgr...... & 00101001010 = ..b.r..a.g. (b.r..a.g.) * 0x111: b.r..a.g. + b.r..a.g..... + b.r..a.g......... = b.r.bargbarg.a.g. ^^^^ */ writeMask = ((((angleMask * 0x41) & 0x14A) * 0x111) >> 7) & 0xF; #endif } // RenderPipelineColorAttachmentDesc implementation bool RenderPipelineColorAttachmentDesc::operator==( const RenderPipelineColorAttachmentDesc &rhs) const { if (!BlendDesc::operator==(rhs)) { return false; } return ANGLE_PROP_EQ(*this, rhs, pixelFormat); } void RenderPipelineColorAttachmentDesc::reset() { reset(MTLPixelFormatInvalid); } void RenderPipelineColorAttachmentDesc::reset(MTLPixelFormat format) { reset(format, MTLColorWriteMaskAll); } void RenderPipelineColorAttachmentDesc::reset(MTLPixelFormat format, MTLColorWriteMask _writeMask) { this->pixelFormat = format; BlendDesc::reset(_writeMask); } void RenderPipelineColorAttachmentDesc::reset(MTLPixelFormat format, const BlendDesc &blendDesc) { this->pixelFormat = format; BlendDesc::operator=(blendDesc); } void RenderPipelineColorAttachmentDesc::update(const BlendDesc &blendDesc) { BlendDesc::operator=(blendDesc); } // RenderPipelineOutputDesc implementation bool RenderPipelineOutputDesc::operator==(const RenderPipelineOutputDesc &rhs) const { if (numColorAttachments != rhs.numColorAttachments) { return false; } for (uint8_t i = 0; i < numColorAttachments; ++i) { if (colorAttachments[i] != rhs.colorAttachments[i]) { return false; } } return ANGLE_PROP_EQ(*this, rhs, depthAttachmentPixelFormat) && ANGLE_PROP_EQ(*this, rhs, stencilAttachmentPixelFormat); } void RenderPipelineOutputDesc::updateEnabledDrawBuffers(gl::DrawBufferMask enabledBuffers) { for (uint32_t colorIndex = 0; colorIndex < this->numColorAttachments; ++colorIndex) { if (!enabledBuffers.test(colorIndex)) { this->colorAttachments[colorIndex].writeMask = MTLColorWriteMaskNone; } } } // RenderPipelineDesc implementation RenderPipelineDesc::RenderPipelineDesc() { memset(this, 0, sizeof(*this)); outputDescriptor.sampleCount = 1; rasterizationType = RenderPipelineRasterization::Enabled; } RenderPipelineDesc::RenderPipelineDesc(const RenderPipelineDesc &src) { memcpy(this, &src, sizeof(*this)); } RenderPipelineDesc::RenderPipelineDesc(RenderPipelineDesc &&src) { memcpy(this, &src, sizeof(*this)); } RenderPipelineDesc &RenderPipelineDesc::operator=(const RenderPipelineDesc &src) { memcpy(this, &src, sizeof(*this)); return *this; } bool RenderPipelineDesc::operator==(const RenderPipelineDesc &rhs) const { // NOTE(hqle): Use a faster way to compare, i.e take into account // the number of active vertex attributes & render targets. // If that way is used, hash() method must be changed also. return memcmp(this, &rhs, sizeof(*this)) == 0; } size_t RenderPipelineDesc::hash() const { return angle::ComputeGenericHash(*this); } bool RenderPipelineDesc::rasterizationEnabled() const { return rasterizationType != RenderPipelineRasterization::Disabled; } // RenderPassDesc implementation RenderPassAttachmentDesc::RenderPassAttachmentDesc() { reset(); } void RenderPassAttachmentDesc::reset() { texture.reset(); implicitMSTexture.reset(); level = mtl::kZeroNativeMipLevel; sliceOrDepth = 0; blendable = false; loadAction = MTLLoadActionLoad; storeAction = MTLStoreActionStore; storeActionOptions = MTLStoreActionOptionNone; } bool RenderPassAttachmentDesc::equalIgnoreLoadStoreOptions( const RenderPassAttachmentDesc &other) const { return texture == other.texture && implicitMSTexture == other.implicitMSTexture && level == other.level && sliceOrDepth == other.sliceOrDepth && blendable == other.blendable; } bool RenderPassAttachmentDesc::operator==(const RenderPassAttachmentDesc &other) const { if (!equalIgnoreLoadStoreOptions(other)) { return false; } return loadAction == other.loadAction && storeAction == other.storeAction && storeActionOptions == other.storeActionOptions; } void RenderPassDesc::populateRenderPipelineOutputDesc(RenderPipelineOutputDesc *outDesc) const { WriteMaskArray writeMaskArray; writeMaskArray.fill(MTLColorWriteMaskAll); populateRenderPipelineOutputDesc(writeMaskArray, outDesc); } void RenderPassDesc::populateRenderPipelineOutputDesc(const WriteMaskArray &writeMaskArray, RenderPipelineOutputDesc *outDesc) const { // Default blend state with replaced color write masks. BlendDescArray blendDescArray; for (size_t i = 0; i < blendDescArray.size(); i++) { blendDescArray[i].reset(writeMaskArray[i]); } populateRenderPipelineOutputDesc(blendDescArray, outDesc); } void RenderPassDesc::populateRenderPipelineOutputDesc(const BlendDescArray &blendDescArray, RenderPipelineOutputDesc *outDesc) const { RenderPipelineOutputDesc &outputDescriptor = *outDesc; outputDescriptor.numColorAttachments = this->numColorAttachments; outputDescriptor.sampleCount = this->sampleCount; for (uint32_t i = 0; i < this->numColorAttachments; ++i) { auto &renderPassColorAttachment = this->colorAttachments[i]; auto texture = renderPassColorAttachment.texture; if (texture) { if (renderPassColorAttachment.blendable) { // Copy parameters from blend state outputDescriptor.colorAttachments[i].reset(texture->pixelFormat(), blendDescArray[i]); } else { // Disable blending if the attachment's render target doesn't support blending. // Force default blending state to reduce the number of unique states. outputDescriptor.colorAttachments[i].reset(texture->pixelFormat(), blendDescArray[i].writeMask); } // Combine the masks. This is useful when the texture is not supposed to have alpha // channel such as GL_RGB8, however, Metal doesn't natively support 24 bit RGB, so // we need to use RGBA texture, and then disable alpha write to this texture outputDescriptor.colorAttachments[i].writeMask &= texture->getColorWritableMask(); } else { outputDescriptor.colorAttachments[i].blendingEnabled = false; outputDescriptor.colorAttachments[i].pixelFormat = MTLPixelFormatInvalid; } } // Reset the unused output slots to ensure consistent hash value for (uint32_t i = this->numColorAttachments; i < kMaxRenderTargets; ++i) { outputDescriptor.colorAttachments[i].reset(); } auto depthTexture = this->depthAttachment.texture; outputDescriptor.depthAttachmentPixelFormat = depthTexture ? depthTexture->pixelFormat() : MTLPixelFormatInvalid; auto stencilTexture = this->stencilAttachment.texture; outputDescriptor.stencilAttachmentPixelFormat = stencilTexture ? stencilTexture->pixelFormat() : MTLPixelFormatInvalid; } bool RenderPassDesc::equalIgnoreLoadStoreOptions(const RenderPassDesc &other) const { if (numColorAttachments != other.numColorAttachments) { return false; } for (uint32_t i = 0; i < numColorAttachments; ++i) { auto &renderPassColorAttachment = colorAttachments[i]; auto &otherRPAttachment = other.colorAttachments[i]; if (!renderPassColorAttachment.equalIgnoreLoadStoreOptions(otherRPAttachment)) { return false; } } return depthAttachment.equalIgnoreLoadStoreOptions(other.depthAttachment) && stencilAttachment.equalIgnoreLoadStoreOptions(other.stencilAttachment); } bool RenderPassDesc::operator==(const RenderPassDesc &other) const { if (numColorAttachments != other.numColorAttachments) { return false; } for (uint32_t i = 0; i < numColorAttachments; ++i) { auto &renderPassColorAttachment = colorAttachments[i]; auto &otherRPAttachment = other.colorAttachments[i]; if (renderPassColorAttachment != (otherRPAttachment)) { return false; } } return depthAttachment == other.depthAttachment && stencilAttachment == other.stencilAttachment; } // Convert to Metal object void RenderPassDesc::convertToMetalDesc(MTLRenderPassDescriptor *objCDesc) const { ANGLE_MTL_OBJC_SCOPE { for (uint32_t i = 0; i < numColorAttachments; ++i) { ToObjC(colorAttachments[i], objCDesc.colorAttachments[i]); } for (uint32_t i = numColorAttachments; i < kMaxRenderTargets; ++i) { // Inactive render target objCDesc.colorAttachments[i].texture = nil; objCDesc.colorAttachments[i].level = 0; objCDesc.colorAttachments[i].slice = 0; objCDesc.colorAttachments[i].depthPlane = 0; objCDesc.colorAttachments[i].loadAction = MTLLoadActionDontCare; objCDesc.colorAttachments[i].storeAction = MTLStoreActionDontCare; } ToObjC(depthAttachment, objCDesc.depthAttachment); ToObjC(stencilAttachment, objCDesc.stencilAttachment); } } // RenderPipelineCache implementation RenderPipelineCache::RenderPipelineCache() : RenderPipelineCache(nullptr) {} RenderPipelineCache::RenderPipelineCache( RenderPipelineCacheSpecializeShaderFactory *specializedShaderFactory) : mSpecializedShaderFactory(specializedShaderFactory) {} RenderPipelineCache::~RenderPipelineCache() {} void RenderPipelineCache::setVertexShader(ContextMtl *context, id shader) { mVertexShader.retainAssign(shader); if (!shader) { clearPipelineStates(); return; } recreatePipelineStates(context); } void RenderPipelineCache::setFragmentShader(ContextMtl *context, id shader) { mFragmentShader.retainAssign(shader); if (!shader) { clearPipelineStates(); return; } recreatePipelineStates(context); } bool RenderPipelineCache::hasDefaultAttribs(const RenderPipelineDesc &rpdesc) const { const VertexDesc &desc = rpdesc.vertexDescriptor; for (uint8_t i = 0; i < desc.numAttribs; ++i) { if (desc.attributes[i].bufferIndex == kDefaultAttribsBindingIndex) { return true; } } return false; } AutoObjCPtr> RenderPipelineCache::getRenderPipelineState( ContextMtl *context, const RenderPipelineDesc &desc) { auto insertDefaultAttribLayout = hasDefaultAttribs(desc); int tableIdx = insertDefaultAttribLayout ? 1 : 0; auto &table = mRenderPipelineStates[tableIdx]; auto ite = table.find(desc); if (ite == table.end()) { return insertRenderPipelineState(context, desc, insertDefaultAttribLayout); } return ite->second; } AutoObjCPtr> RenderPipelineCache::insertRenderPipelineState( ContextMtl *context, const RenderPipelineDesc &desc, bool insertDefaultAttribLayout) { AutoObjCPtr> newState = createRenderPipelineState(context, desc, insertDefaultAttribLayout); if (!newState) { return nil; } int tableIdx = insertDefaultAttribLayout ? 1 : 0; auto re = mRenderPipelineStates[tableIdx].insert(std::make_pair(desc, newState)); if (!re.second) { return nil; } return re.first->second; } AutoObjCPtr> RenderPipelineCache::createRenderPipelineState( ContextMtl *context, const RenderPipelineDesc &originalDesc, bool insertDefaultAttribLayout) { ANGLE_MTL_OBJC_SCOPE { // Disable coverage if the render pipeline's sample count is only 1. RenderPipelineDesc desc = originalDesc; if (desc.outputDescriptor.sampleCount == 1) { // Disable sample coverage if the output is not multisample desc.emulateCoverageMask = false; desc.alphaToCoverageEnabled = false; } // Choose shader variant id vertShader = nil; id fragShader = nil; if (mSpecializedShaderFactory && mSpecializedShaderFactory->hasSpecializedShader(gl::ShaderType::Vertex, desc)) { if (IsError(mSpecializedShaderFactory->getSpecializedShader( context, gl::ShaderType::Vertex, desc, &vertShader))) { return nil; } } else { // Non-specialized version vertShader = mVertexShader; } if (mSpecializedShaderFactory && mSpecializedShaderFactory->hasSpecializedShader(gl::ShaderType::Fragment, desc)) { if (IsError(mSpecializedShaderFactory->getSpecializedShader( context, gl::ShaderType::Fragment, desc, &fragShader))) { return nil; } } else { // Non-specialized version fragShader = mFragmentShader; } if (!vertShader) { // Render pipeline without vertex shader is invalid. context->handleError(GL_INVALID_OPERATION, __FILE__, ANGLE_FUNCTION, __LINE__); return nil; } const mtl::ContextDevice &metalDevice = context->getMetalDevice(); // Convert to Objective-C desc: AutoObjCObj objCDesc = ToObjC(vertShader, fragShader, desc); // Validate Render Pipeline State: if (DeviceHasMaximumRenderTargetSize(metalDevice)) { // TODO: Is the use of NSUInteger in 32 bit systems ok without any overflow checking? NSUInteger maxSize = GetMaxRenderTargetSizeForDeviceInBytes(metalDevice); NSUInteger renderTargetSize = ComputeTotalSizeUsedForMTLRenderPipelineDescriptor(objCDesc, context, metalDevice); if (renderTargetSize > maxSize) { NSString *errorString = [NSString stringWithFormat:@"This set of render targets requires %lu bytes of " @"pixel storage. This device supports %lu bytes.", (unsigned long)renderTargetSize, (unsigned long)maxSize]; NSError *err = [NSError errorWithDomain:@"MTLValidationError" code:-1 userInfo:@{NSLocalizedDescriptionKey : errorString}]; context->handleError(err, __FILE__, ANGLE_FUNCTION, __LINE__); return nil; } } // Special attribute slot for default attribute if (insertDefaultAttribLayout) { MTLVertexBufferLayoutDescriptor *defaultAttribLayoutObjCDesc = [[MTLVertexBufferLayoutDescriptor alloc] init]; defaultAttribLayoutObjCDesc.stepFunction = MTLVertexStepFunctionConstant; defaultAttribLayoutObjCDesc.stepRate = 0; defaultAttribLayoutObjCDesc.stride = kDefaultAttributeSize * kMaxVertexAttribs; [objCDesc.get().vertexDescriptor.layouts setObject:[defaultAttribLayoutObjCDesc ANGLE_MTL_AUTORELEASE] atIndexedSubscript:kDefaultAttribsBindingIndex]; } // Create pipeline state NSError *err = nil; auto newState = metalDevice.newRenderPipelineStateWithDescriptor(objCDesc, &err); if (err) { context->handleError(err, __FILE__, ANGLE_FUNCTION, __LINE__); return nil; } return newState; } } void RenderPipelineCache::recreatePipelineStates(ContextMtl *context) { for (int hasDefaultAttrib = 0; hasDefaultAttrib <= 1; ++hasDefaultAttrib) { for (auto &ite : mRenderPipelineStates[hasDefaultAttrib]) { if (ite.second == nil) { continue; } ite.second = createRenderPipelineState(context, ite.first, hasDefaultAttrib); } } } void RenderPipelineCache::clear() { mVertexShader = nil; mFragmentShader = nil; clearPipelineStates(); } void RenderPipelineCache::clearPipelineStates() { mRenderPipelineStates[0].clear(); mRenderPipelineStates[1].clear(); } // ProvokingVertexPipelineDesc ProvokingVertexComputePipelineDesc::ProvokingVertexComputePipelineDesc() { memset(this, 0, sizeof(*this)); } ProvokingVertexComputePipelineDesc::ProvokingVertexComputePipelineDesc( const ProvokingVertexComputePipelineDesc &src) { memcpy(this, &src, sizeof(*this)); } ProvokingVertexComputePipelineDesc::ProvokingVertexComputePipelineDesc( const ProvokingVertexComputePipelineDesc &&src) { memcpy(this, &src, sizeof(*this)); } ProvokingVertexComputePipelineDesc &ProvokingVertexComputePipelineDesc::operator=( const ProvokingVertexComputePipelineDesc &src) { memcpy(this, &src, sizeof(*this)); return *this; } bool ProvokingVertexComputePipelineDesc::operator==( const ProvokingVertexComputePipelineDesc &rhs) const { return memcmp(this, &rhs, sizeof(*this)) == 0; } bool ProvokingVertexComputePipelineDesc::operator!=( const ProvokingVertexComputePipelineDesc &rhs) const { return !(*this == rhs); } size_t ProvokingVertexComputePipelineDesc::hash() const { return angle::ComputeGenericHash(*this); } ProvokingVertexComputePipelineCache::ProvokingVertexComputePipelineCache() : mComputeShader(nullptr) {} ProvokingVertexComputePipelineCache::ProvokingVertexComputePipelineCache( ProvokingVertexCacheSpecializeShaderFactory *specializedShaderFactory) : mComputeShader(nullptr), mSpecializedShaderFactory(specializedShaderFactory) {} void ProvokingVertexComputePipelineCache::setComputeShader(ContextMtl *context, id shader) { mComputeShader.retainAssign(shader); if (!shader) { clearPipelineStates(); return; } recreatePipelineStates(context); } void ProvokingVertexComputePipelineCache::clearPipelineStates() { mComputePipelineStates.clear(); } void ProvokingVertexComputePipelineCache::clear() { clearPipelineStates(); } AutoObjCPtr> ProvokingVertexComputePipelineCache::getComputePipelineState( ContextMtl *context, const ProvokingVertexComputePipelineDesc &desc) { auto &table = mComputePipelineStates; auto ite = table.find(desc); if (ite == table.end()) { return insertComputePipelineState(context, desc); } return ite->second; } AutoObjCPtr> ProvokingVertexComputePipelineCache::insertComputePipelineState( ContextMtl *context, const ProvokingVertexComputePipelineDesc &desc) { AutoObjCPtr> newState = createComputePipelineState(context, desc); auto re = mComputePipelineStates.insert(std::make_pair(desc, newState)); if (!re.second) { return nil; } return re.first->second; } void ProvokingVertexComputePipelineCache::recreatePipelineStates(ContextMtl *context) { for (auto &ite : mComputePipelineStates) { if (ite.second == nil) { continue; } ite.second = createComputePipelineState(context, ite.first); } } AutoObjCPtr> ProvokingVertexComputePipelineCache::createComputePipelineState( ContextMtl *context, const ProvokingVertexComputePipelineDesc &originalDesc) { ANGLE_MTL_OBJC_SCOPE { // Disable coverage if the render pipeline's sample count is only 1. ProvokingVertexComputePipelineDesc desc = originalDesc; id computeFunction = nil; // Special case for transform feedback shader, we've already created it! See // getTransformFeedbackRenderPipeline if (mSpecializedShaderFactory && mSpecializedShaderFactory->hasSpecializedShader(gl::ShaderType::Compute, desc)) { if (IsError(mSpecializedShaderFactory->getSpecializedShader( context, gl::ShaderType::Compute, desc, &computeFunction))) { return nil; } } else { // Non-specialized version computeFunction = mComputeShader; } if (!computeFunction) { // Render pipeline without vertex shader is invalid. context->handleError(GL_INVALID_OPERATION, __FILE__, ANGLE_FUNCTION, __LINE__); return nil; } const mtl::ContextDevice &metalDevice = context->getMetalDevice(); // Convert to Objective-C desc: NSError *err = nil; auto newState = metalDevice.newComputePipelineStateWithFunction(computeFunction, &err); if (err) { context->handleError(err, __FILE__, ANGLE_FUNCTION, __LINE__); return nil; } return newState; } } ProvokingVertexComputePipelineCache::~ProvokingVertexComputePipelineCache() {} // StateCache implementation StateCache::StateCache(const angle::FeaturesMtl &features) : mFeatures(features) {} StateCache::~StateCache() {} AutoObjCPtr> StateCache::getNullDepthStencilState( const mtl::ContextDevice &device) { if (!mNullDepthStencilState) { DepthStencilDesc desc; desc.reset(); ASSERT(desc.frontFaceStencil.stencilCompareFunction == MTLCompareFunctionAlways); desc.depthWriteEnabled = false; mNullDepthStencilState = getDepthStencilState(device, desc); } return mNullDepthStencilState; } AutoObjCPtr> StateCache::getDepthStencilState( const mtl::ContextDevice &device, const DepthStencilDesc &desc) { ANGLE_MTL_OBJC_SCOPE { auto ite = mDepthStencilStates.find(desc); if (ite == mDepthStencilStates.end()) { auto re = mDepthStencilStates.insert( std::make_pair(desc, device.newDepthStencilStateWithDescriptor(ToObjC(desc)))); if (!re.second) { return nil; } ite = re.first; } return ite->second; } } AutoObjCPtr> StateCache::getSamplerState(const mtl::ContextDevice &device, const SamplerDesc &desc) { ANGLE_MTL_OBJC_SCOPE { auto ite = mSamplerStates.find(desc); if (ite == mSamplerStates.end()) { AutoObjCObj objCDesc = ToObjC(desc); if (!mFeatures.allowRuntimeSamplerCompareMode.enabled) { // Runtime sampler compare mode is not supported, fallback to never. objCDesc.get().compareFunction = MTLCompareFunctionNever; } auto re = mSamplerStates.insert( std::make_pair(desc, device.newSamplerStateWithDescriptor(objCDesc))); if (!re.second) return nil; ite = re.first; } return ite->second; } } AutoObjCPtr> StateCache::getNullSamplerState(ContextMtl *context) { return getNullSamplerState(context->getMetalDevice()); } AutoObjCPtr> StateCache::getNullSamplerState(const mtl::ContextDevice &device) { SamplerDesc desc; desc.reset(); return getSamplerState(device, desc); } void StateCache::clear() { mNullDepthStencilState = nil; mDepthStencilStates.clear(); mSamplerStates.clear(); } } // namespace mtl } // namespace rx