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/ErrorStrings.h" 13#include "libANGLE/renderer/metal/ContextMtl.h" 14 15namespace rx 16{ 17namespace mtl 18{ 19 20namespace 21{ 22bool HasDefaultAttribs(const RenderPipelineDesc &rpdesc) 23{ 24 const VertexDesc &desc = rpdesc.vertexDescriptor; 25 for (uint8_t i = 0; i < desc.numAttribs; ++i) 26 { 27 if (desc.attributes[i].bufferIndex == kDefaultAttribsBindingIndex) 28 { 29 return true; 30 } 31 } 32 33 return false; 34} 35 36bool HasValidRenderTarget(const mtl::ContextDevice &device, 37 const MTLRenderPipelineDescriptor *descriptor) 38{ 39 const NSUInteger maxColorRenderTargets = GetMaxNumberOfRenderTargetsForDevice(device); 40 for (NSUInteger i = 0; i < maxColorRenderTargets; ++i) 41 { 42 auto colorAttachment = descriptor.colorAttachments[i]; 43 if (colorAttachment && colorAttachment.pixelFormat != MTLPixelFormatInvalid) 44 { 45 return true; 46 } 47 } 48 49 if (descriptor.depthAttachmentPixelFormat != MTLPixelFormatInvalid) 50 { 51 return true; 52 } 53 54 if (descriptor.stencilAttachmentPixelFormat != MTLPixelFormatInvalid) 55 { 56 return true; 57 } 58 59 return false; 60} 61 62angle::Result ValidateRenderPipelineState(ContextMtl *context, 63 const MTLRenderPipelineDescriptor *descriptor) 64{ 65 const mtl::ContextDevice &device = context->getMetalDevice(); 66 67 if (!context->getDisplay()->getFeatures().allowRenderpassWithoutAttachment.enabled && 68 !HasValidRenderTarget(device, descriptor)) 69 { 70 ANGLE_CHECK(context, false, 71 "Render pipeline requires at least one render target for this device.", 72 GL_INVALID_OPERATION); 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_CHECK(context, false, errorStream.str().c_str(), GL_INVALID_OPERATION); 87 } 88 } 89 return angle::Result::Continue; 90} 91 92angle::Result CreateRenderPipelineState( 93 ContextMtl *context, 94 const PipelineKey &key, 95 angle::ObjCPtr<id<MTLRenderPipelineState>> *outRenderPipeline) 96{ 97 ASSERT(key.isRenderPipeline()); 98 ANGLE_CHECK(context, key.vertexShader, gl::err::kInternalError, GL_INVALID_OPERATION); 99 ANGLE_MTL_OBJC_SCOPE 100 { 101 const mtl::ContextDevice &metalDevice = context->getMetalDevice(); 102 103 auto objCDesc = key.pipelineDesc.createMetalDesc(key.vertexShader, key.fragmentShader); 104 105 ANGLE_TRY(ValidateRenderPipelineState(context, objCDesc)); 106 107 // Special attribute slot for default attribute 108 if (HasDefaultAttribs(key.pipelineDesc)) 109 { 110 auto defaultAttribLayoutObjCDesc = 111 angle::adoptObjCPtr([[MTLVertexBufferLayoutDescriptor alloc] init]); 112 defaultAttribLayoutObjCDesc.get().stepFunction = MTLVertexStepFunctionConstant; 113 defaultAttribLayoutObjCDesc.get().stepRate = 0; 114 defaultAttribLayoutObjCDesc.get().stride = kDefaultAttributeSize * kMaxVertexAttribs; 115 116 [objCDesc.get().vertexDescriptor.layouts setObject:defaultAttribLayoutObjCDesc 117 atIndexedSubscript:kDefaultAttribsBindingIndex]; 118 } 119 // Create pipeline state 120 NSError *err = nil; 121 auto newState = metalDevice.newRenderPipelineStateWithDescriptor(objCDesc, &err); 122 ANGLE_MTL_CHECK(context, newState, err); 123 *outRenderPipeline = newState; 124 return angle::Result::Continue; 125 } 126} 127 128angle::Result CreateComputePipelineState( 129 ContextMtl *context, 130 const PipelineKey &key, 131 angle::ObjCPtr<id<MTLComputePipelineState>> *outComputePipeline) 132{ 133 ASSERT(!key.isRenderPipeline()); 134 ANGLE_CHECK(context, key.computeShader, gl::err::kInternalError, GL_INVALID_OPERATION); 135 ANGLE_MTL_OBJC_SCOPE 136 { 137 const mtl::ContextDevice &metalDevice = context->getMetalDevice(); 138 NSError *err = nil; 139 auto newState = metalDevice.newComputePipelineStateWithFunction(key.computeShader, &err); 140 ANGLE_MTL_CHECK(context, newState, err); 141 *outComputePipeline = newState; 142 return angle::Result::Continue; 143 } 144} 145 146} // namespace 147 148bool PipelineKey::isRenderPipeline() const 149{ 150 if (vertexShader) 151 { 152 ASSERT(!computeShader); 153 return true; 154 } 155 else 156 { 157 ASSERT(computeShader); 158 return false; 159 } 160} 161 162bool PipelineKey::operator==(const PipelineKey &rhs) const 163{ 164 if (isRenderPipeline() != rhs.isRenderPipeline()) 165 { 166 return false; 167 } 168 169 if (isRenderPipeline()) 170 { 171 return std::tie(vertexShader, fragmentShader, pipelineDesc) == 172 std::tie(rhs.vertexShader, rhs.fragmentShader, rhs.pipelineDesc); 173 } 174 else 175 { 176 return computeShader == rhs.computeShader; 177 } 178} 179 180size_t PipelineKey::hash() const 181{ 182 if (isRenderPipeline()) 183 { 184 return angle::HashMultiple(vertexShader.get(), fragmentShader.get(), pipelineDesc); 185 } 186 else 187 { 188 return angle::HashMultiple(computeShader.get()); 189 } 190} 191 192PipelineCache::PipelineCache() : mPipelineCache(kMaxPipelines) {} 193 194angle::Result PipelineCache::getRenderPipeline( 195 ContextMtl *context, 196 id<MTLFunction> vertexShader, 197 id<MTLFunction> fragmentShader, 198 const RenderPipelineDesc &desc, 199 angle::ObjCPtr<id<MTLRenderPipelineState>> *outRenderPipeline) 200{ 201 PipelineKey key; 202 key.vertexShader = std::move(vertexShader); 203 key.fragmentShader = std::move(fragmentShader); 204 key.pipelineDesc = desc; 205 206 auto iter = mPipelineCache.Get(key); 207 if (iter != mPipelineCache.end()) 208 { 209 // Should be no way that this key matched a compute pipeline entry 210 ASSERT(iter->second.renderPipeline); 211 *outRenderPipeline = iter->second.renderPipeline; 212 return angle::Result::Continue; 213 } 214 215 angle::TrimCache(kMaxPipelines, kGCLimit, "render pipeline", &mPipelineCache); 216 217 PipelineVariant newPipeline; 218 ANGLE_TRY(CreateRenderPipelineState(context, key, &newPipeline.renderPipeline)); 219 220 iter = mPipelineCache.Put(std::move(key), std::move(newPipeline)); 221 222 *outRenderPipeline = iter->second.renderPipeline; 223 return angle::Result::Continue; 224} 225 226angle::Result PipelineCache::getComputePipeline( 227 ContextMtl *context, 228 id<MTLFunction> computeShader, 229 angle::ObjCPtr<id<MTLComputePipelineState>> *outComputePipeline) 230{ 231 PipelineKey key; 232 key.computeShader = std::move(computeShader); 233 234 auto iter = mPipelineCache.Get(key); 235 if (iter != mPipelineCache.end()) 236 { 237 // Should be no way that this key matched a render pipeline entry 238 ASSERT(iter->second.computePipeline); 239 *outComputePipeline = iter->second.computePipeline; 240 return angle::Result::Continue; 241 } 242 243 angle::TrimCache(kMaxPipelines, kGCLimit, "render pipeline", &mPipelineCache); 244 245 PipelineVariant newPipeline; 246 ANGLE_TRY(CreateComputePipelineState(context, key, &newPipeline.computePipeline)); 247 248 iter = mPipelineCache.Put(std::move(key), std::move(newPipeline)); 249 250 *outComputePipeline = iter->second.computePipeline; 251 return angle::Result::Continue; 252} 253 254} // namespace mtl 255} // namespace rx 256