xref: /aosp_15_r20/external/angle/src/libANGLE/renderer/metal/mtl_pipeline_cache.mm (revision 8975f5c5ed3d1c378011245431ada316dfb6f244)
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