1// 2// Copyright 2023 The ANGLE Project Authors. All rights reserved. 3// Use of this source code is governed by a BSD-style license that can be 4// found in the LICENSE file. 5// 6// mtl_pipeline_cache.mm: 7// Defines classes for caching of mtl pipelines 8// 9 10#include "libANGLE/renderer/metal/mtl_pipeline_cache.h" 11 12#include "libANGLE/renderer/metal/ContextMtl.h" 13 14namespace rx 15{ 16namespace mtl 17{ 18 19namespace 20{ 21bool HasDefaultAttribs(const RenderPipelineDesc &rpdesc) 22{ 23 const VertexDesc &desc = rpdesc.vertexDescriptor; 24 for (uint8_t i = 0; i < desc.numAttribs; ++i) 25 { 26 if (desc.attributes[i].bufferIndex == kDefaultAttribsBindingIndex) 27 { 28 return true; 29 } 30 } 31 32 return false; 33} 34 35bool HasValidRenderTarget(const mtl::ContextDevice &device, 36 const MTLRenderPipelineDescriptor *descriptor) 37{ 38 const NSUInteger maxColorRenderTargets = GetMaxNumberOfRenderTargetsForDevice(device); 39 for (NSUInteger i = 0; i < maxColorRenderTargets; ++i) 40 { 41 auto colorAttachment = descriptor.colorAttachments[i]; 42 if (colorAttachment && colorAttachment.pixelFormat != MTLPixelFormatInvalid) 43 { 44 return true; 45 } 46 } 47 48 if (descriptor.depthAttachmentPixelFormat != MTLPixelFormatInvalid) 49 { 50 return true; 51 } 52 53 if (descriptor.stencilAttachmentPixelFormat != MTLPixelFormatInvalid) 54 { 55 return true; 56 } 57 58 return false; 59} 60 61angle::Result ValidateRenderPipelineState(ContextMtl *context, 62 const MTLRenderPipelineDescriptor *descriptor) 63{ 64 const mtl::ContextDevice &device = context->getMetalDevice(); 65 66 if (!context->getDisplay()->getFeatures().allowRenderpassWithoutAttachment.enabled && 67 !HasValidRenderTarget(device, descriptor)) 68 { 69 ANGLE_MTL_HANDLE_ERROR( 70 context, "Render pipeline requires at least one render target for this device.", 71 GL_INVALID_OPERATION); 72 return angle::Result::Stop; 73 } 74 75 // Ensure the device can support the storage requirement for render targets. 76 if (DeviceHasMaximumRenderTargetSize(device)) 77 { 78 NSUInteger maxSize = GetMaxRenderTargetSizeForDeviceInBytes(device); 79 NSUInteger renderTargetSize = 80 ComputeTotalSizeUsedForMTLRenderPipelineDescriptor(descriptor, context, device); 81 if (renderTargetSize > maxSize) 82 { 83 std::stringstream errorStream; 84 errorStream << "This set of render targets requires " << renderTargetSize 85 << " bytes of pixel storage. This device supports " << maxSize << " bytes."; 86 ANGLE_MTL_HANDLE_ERROR(context, errorStream.str().c_str(), GL_INVALID_OPERATION); 87 return angle::Result::Stop; 88 } 89 } 90 91 return angle::Result::Continue; 92} 93 94angle::Result CreateRenderPipelineState(ContextMtl *context, 95 const PipelineKey &key, 96 AutoObjCPtr<id<MTLRenderPipelineState>> *outRenderPipeline) 97{ 98 ANGLE_MTL_OBJC_SCOPE 99 { 100 ASSERT(key.isRenderPipeline()); 101 if (!key.vertexShader) 102 { 103 // Render pipeline without vertex shader is invalid. 104 ANGLE_MTL_HANDLE_ERROR(context, "Render pipeline without vertex shader is invalid.", 105 GL_INVALID_OPERATION); 106 return angle::Result::Stop; 107 } 108 109 const mtl::ContextDevice &metalDevice = context->getMetalDevice(); 110 111 auto objCDesc = key.pipelineDesc.createMetalDesc(key.vertexShader, key.fragmentShader); 112 113 ANGLE_TRY(ValidateRenderPipelineState(context, objCDesc)); 114 115 // Special attribute slot for default attribute 116 if (HasDefaultAttribs(key.pipelineDesc)) 117 { 118 auto defaultAttribLayoutObjCDesc = adoptObjCObj<MTLVertexBufferLayoutDescriptor>( 119 [[MTLVertexBufferLayoutDescriptor alloc] init]); 120 defaultAttribLayoutObjCDesc.get().stepFunction = MTLVertexStepFunctionConstant; 121 defaultAttribLayoutObjCDesc.get().stepRate = 0; 122 defaultAttribLayoutObjCDesc.get().stride = kDefaultAttributeSize * kMaxVertexAttribs; 123 124 [objCDesc.get().vertexDescriptor.layouts setObject:defaultAttribLayoutObjCDesc 125 atIndexedSubscript:kDefaultAttribsBindingIndex]; 126 } 127 // Create pipeline state 128 NSError *err = nil; 129 auto newState = metalDevice.newRenderPipelineStateWithDescriptor(objCDesc, &err); 130 if (err) 131 { 132 ANGLE_MTL_HANDLE_ERROR(context, mtl::FormatMetalErrorMessage(err).c_str(), 133 GL_INVALID_OPERATION); 134 return angle::Result::Stop; 135 } 136 137 *outRenderPipeline = newState; 138 return angle::Result::Continue; 139 } 140} 141 142angle::Result CreateComputePipelineState( 143 ContextMtl *context, 144 const PipelineKey &key, 145 AutoObjCPtr<id<MTLComputePipelineState>> *outComputePipeline) 146{ 147 ANGLE_MTL_OBJC_SCOPE 148 { 149 ASSERT(!key.isRenderPipeline()); 150 if (!key.computeShader) 151 { 152 ANGLE_MTL_HANDLE_ERROR(context, "Compute pipeline without a shader is invalid.", 153 GL_INVALID_OPERATION); 154 return angle::Result::Stop; 155 } 156 157 const mtl::ContextDevice &metalDevice = context->getMetalDevice(); 158 159 // Create pipeline state 160 NSError *err = nil; 161 auto newState = metalDevice.newComputePipelineStateWithFunction(key.computeShader, &err); 162 if (err) 163 { 164 ANGLE_MTL_HANDLE_ERROR(context, mtl::FormatMetalErrorMessage(err).c_str(), 165 GL_INVALID_OPERATION); 166 return angle::Result::Stop; 167 } 168 169 *outComputePipeline = newState; 170 return angle::Result::Continue; 171 } 172} 173 174} // namespace 175 176bool PipelineKey::isRenderPipeline() const 177{ 178 if (vertexShader) 179 { 180 ASSERT(!computeShader); 181 return true; 182 } 183 else 184 { 185 ASSERT(computeShader); 186 return false; 187 } 188} 189 190bool PipelineKey::operator==(const PipelineKey &rhs) const 191{ 192 if (isRenderPipeline() != rhs.isRenderPipeline()) 193 { 194 return false; 195 } 196 197 if (isRenderPipeline()) 198 { 199 return std::tie(vertexShader, fragmentShader, pipelineDesc) == 200 std::tie(rhs.vertexShader, rhs.fragmentShader, rhs.pipelineDesc); 201 } 202 else 203 { 204 return computeShader == rhs.computeShader; 205 } 206} 207 208size_t PipelineKey::hash() const 209{ 210 if (isRenderPipeline()) 211 { 212 return angle::HashMultiple(vertexShader.get(), fragmentShader.get(), pipelineDesc); 213 } 214 else 215 { 216 return angle::HashMultiple(computeShader.get()); 217 } 218} 219 220PipelineCache::PipelineCache() : mPipelineCache(kMaxPipelines) {} 221 222angle::Result PipelineCache::getRenderPipeline( 223 ContextMtl *context, 224 id<MTLFunction> vertexShader, 225 id<MTLFunction> fragmentShader, 226 const RenderPipelineDesc &desc, 227 AutoObjCPtr<id<MTLRenderPipelineState>> *outRenderPipeline) 228{ 229 PipelineKey key; 230 key.vertexShader.retainAssign(vertexShader); 231 key.fragmentShader.retainAssign(fragmentShader); 232 key.pipelineDesc = desc; 233 234 auto iter = mPipelineCache.Get(key); 235 if (iter != mPipelineCache.end()) 236 { 237 // Should be no way that this key matched a compute pipeline entry 238 ASSERT(iter->second.renderPipeline); 239 *outRenderPipeline = iter->second.renderPipeline; 240 return angle::Result::Continue; 241 } 242 243 angle::TrimCache(kMaxPipelines, kGCLimit, "render pipeline", &mPipelineCache); 244 245 PipelineVariant newPipeline; 246 ANGLE_TRY(CreateRenderPipelineState(context, key, &newPipeline.renderPipeline)); 247 248 iter = mPipelineCache.Put(std::move(key), std::move(newPipeline)); 249 250 *outRenderPipeline = iter->second.renderPipeline; 251 return angle::Result::Continue; 252} 253 254angle::Result PipelineCache::getComputePipeline( 255 ContextMtl *context, 256 id<MTLFunction> computeShader, 257 AutoObjCPtr<id<MTLComputePipelineState>> *outComputePipeline) 258{ 259 PipelineKey key; 260 key.computeShader.retainAssign(computeShader); 261 262 auto iter = mPipelineCache.Get(key); 263 if (iter != mPipelineCache.end()) 264 { 265 // Should be no way that this key matched a render pipeline entry 266 ASSERT(iter->second.computePipeline); 267 *outComputePipeline = iter->second.computePipeline; 268 return angle::Result::Continue; 269 } 270 271 angle::TrimCache(kMaxPipelines, kGCLimit, "render pipeline", &mPipelineCache); 272 273 PipelineVariant newPipeline; 274 ANGLE_TRY(CreateComputePipelineState(context, key, &newPipeline.computePipeline)); 275 276 iter = mPipelineCache.Put(std::move(key), std::move(newPipeline)); 277 278 *outComputePipeline = iter->second.computePipeline; 279 return angle::Result::Continue; 280} 281 282} // namespace mtl 283} // namespace rx 284