• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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