• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //
2 // Copyright 2019 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_state_cache.h:
7 //    Defines the class interface for StateCache, RenderPipelineCache and various
8 //    C struct versions of Metal sampler, depth stencil, render pass, render pipeline descriptors.
9 //
10 
11 #ifndef LIBANGLE_RENDERER_METAL_MTL_STATE_CACHE_H_
12 #define LIBANGLE_RENDERER_METAL_MTL_STATE_CACHE_H_
13 
14 #import <Metal/Metal.h>
15 
16 #include <unordered_map>
17 
18 #include "libANGLE/State.h"
19 #include "libANGLE/angletypes.h"
20 #include "libANGLE/renderer/metal/mtl_common.h"
21 #include "libANGLE/renderer/metal/mtl_context_device.h"
22 #include "libANGLE/renderer/metal/mtl_resources.h"
23 
24 static inline bool operator==(const MTLClearColor &lhs, const MTLClearColor &rhs);
25 
26 namespace angle
27 {
28 struct FeaturesMtl;
29 }
30 
31 namespace rx
32 {
33 class ContextMtl;
34 
35 namespace mtl
36 {
37 struct alignas(1) StencilDesc
38 {
39     bool operator==(const StencilDesc &rhs) const;
40 
41     // Set default values
42     void reset();
43 
44     // Use uint8_t instead of MTLStencilOperation to compact space
45     uint8_t stencilFailureOperation : 3;
46     uint8_t depthFailureOperation : 3;
47     uint8_t depthStencilPassOperation : 3;
48 
49     // Use uint8_t instead of MTLCompareFunction to compact space
50     uint8_t stencilCompareFunction : 3;
51 
52     uint8_t readMask : 8;
53     uint8_t writeMask : 8;
54 };
55 
56 struct alignas(4) DepthStencilDesc
57 {
58     DepthStencilDesc();
59     DepthStencilDesc(const DepthStencilDesc &src);
60     DepthStencilDesc(DepthStencilDesc &&src);
61 
62     DepthStencilDesc &operator=(const DepthStencilDesc &src);
63 
64     bool operator==(const DepthStencilDesc &rhs) const;
65 
66     // Set default values.
67     // Default is depth/stencil test disabled. Depth/stencil write enabled.
68     void reset();
69 
70     size_t hash() const;
71 
72     void updateDepthTestEnabled(const gl::DepthStencilState &dsState);
73     void updateDepthWriteEnabled(const gl::DepthStencilState &dsState);
74     void updateDepthCompareFunc(const gl::DepthStencilState &dsState);
75     void updateStencilTestEnabled(const gl::DepthStencilState &dsState);
76     void updateStencilFrontOps(const gl::DepthStencilState &dsState);
77     void updateStencilBackOps(const gl::DepthStencilState &dsState);
78     void updateStencilFrontFuncs(const gl::DepthStencilState &dsState);
79     void updateStencilBackFuncs(const gl::DepthStencilState &dsState);
80     void updateStencilFrontWriteMask(const gl::DepthStencilState &dsState);
81     void updateStencilBackWriteMask(const gl::DepthStencilState &dsState);
82 
83     StencilDesc backFaceStencil;
84     StencilDesc frontFaceStencil;
85 
86     // Use uint8_t instead of MTLCompareFunction to compact space
87     uint8_t depthCompareFunction : 3;
88     bool depthWriteEnabled : 1;
89 };
90 
91 struct alignas(4) SamplerDesc
92 {
93     SamplerDesc();
94     SamplerDesc(const SamplerDesc &src);
95     SamplerDesc(SamplerDesc &&src);
96 
97     explicit SamplerDesc(const gl::SamplerState &glState);
98 
99     SamplerDesc &operator=(const SamplerDesc &src);
100 
101     // Set default values. All filters are nearest, and addresModes are clamp to edge.
102     void reset();
103 
104     bool operator==(const SamplerDesc &rhs) const;
105 
106     size_t hash() const;
107 
108     // Use uint8_t instead of MTLSamplerAddressMode to compact space
109     uint8_t rAddressMode : 3;
110     uint8_t sAddressMode : 3;
111     uint8_t tAddressMode : 3;
112 
113     // Use uint8_t instead of MTLSamplerMinMagFilter to compact space
114     uint8_t minFilter : 1;
115     uint8_t magFilter : 1;
116     uint8_t mipFilter : 2;
117 
118     uint8_t maxAnisotropy : 5;
119 
120     // Use uint8_t instead of MTLCompareFunction to compact space
121     uint8_t compareFunction : 3;
122 };
123 
124 struct VertexAttributeDesc
125 {
126     inline bool operator==(const VertexAttributeDesc &rhs) const
127     {
128         return format == rhs.format && offset == rhs.offset && bufferIndex == rhs.bufferIndex;
129     }
130     inline bool operator!=(const VertexAttributeDesc &rhs) const { return !(*this == rhs); }
131 
132     // Use uint8_t instead of MTLVertexFormat to compact space
133     uint8_t format : 6;
134     // Offset is only used for default attributes buffer. So 8 bits are enough.
135     uint8_t offset : 8;
136     uint8_t bufferIndex : 5;
137 };
138 
139 struct VertexBufferLayoutDesc
140 {
141     inline bool operator==(const VertexBufferLayoutDesc &rhs) const
142     {
143         return stepFunction == rhs.stepFunction && stepRate == rhs.stepRate && stride == rhs.stride;
144     }
145     inline bool operator!=(const VertexBufferLayoutDesc &rhs) const { return !(*this == rhs); }
146 
147     uint32_t stepRate;
148     uint32_t stride;
149 
150     // Use uint8_t instead of MTLVertexStepFunction to compact space
151     uint8_t stepFunction;
152 };
153 
154 struct VertexDesc
155 {
156     VertexAttributeDesc attributes[kMaxVertexAttribs];
157     VertexBufferLayoutDesc layouts[kMaxVertexAttribs];
158 
159     uint8_t numAttribs;
160     uint8_t numBufferLayouts;
161 };
162 
163 struct BlendDesc
164 {
165     bool operator==(const BlendDesc &rhs) const;
166     BlendDesc &operator=(const BlendDesc &src) = default;
167 
168     // Set default values
169     void reset();
170     void reset(MTLColorWriteMask writeMask);
171 
172     void updateWriteMask(const uint8_t angleMask);
173 
174     // Use uint8_t instead of MTLColorWriteMask to compact space
175     uint8_t writeMask : 4;
176 
177     // Use uint8_t instead of MTLBlendOperation to compact space
178     uint8_t alphaBlendOperation : 3;
179     uint8_t rgbBlendOperation : 3;
180 
181     // Use uint8_t instead of MTLBlendFactor to compact space
182     // NOTE(hqle): enum MTLBlendFactorSource1Color and above are unused.
183     uint8_t destinationAlphaBlendFactor : 4;
184     uint8_t destinationRGBBlendFactor : 4;
185     uint8_t sourceAlphaBlendFactor : 4;
186     uint8_t sourceRGBBlendFactor : 4;
187 
188     bool blendingEnabled : 1;
189 };
190 
191 using BlendDescArray = std::array<BlendDesc, kMaxRenderTargets>;
192 using WriteMaskArray = std::array<uint8_t, kMaxRenderTargets>;
193 
194 struct alignas(2) RenderPipelineColorAttachmentDesc : public BlendDesc
195 {
196     bool operator==(const RenderPipelineColorAttachmentDesc &rhs) const;
197     inline bool operator!=(const RenderPipelineColorAttachmentDesc &rhs) const
198     {
199         return !(*this == rhs);
200     }
201 
202     // Set default values
203     void reset();
204     void reset(MTLPixelFormat format);
205     void reset(MTLPixelFormat format, MTLColorWriteMask writeMask);
206     void reset(MTLPixelFormat format, const BlendDesc &blendDesc);
207 
208     void update(const BlendDesc &blendDesc);
209 
210     // Use uint16_t instead of MTLPixelFormat to compact space
211     uint16_t pixelFormat : 16;
212 };
213 
214 struct RenderPipelineOutputDesc
215 {
216     bool operator==(const RenderPipelineOutputDesc &rhs) const;
217 
218     void updateEnabledDrawBuffers(gl::DrawBufferMask enabledBuffers);
219 
220     RenderPipelineColorAttachmentDesc colorAttachments[kMaxRenderTargets];
221 
222     // Use uint16_t instead of MTLPixelFormat to compact space
223     uint16_t depthAttachmentPixelFormat : 16;
224     uint16_t stencilAttachmentPixelFormat : 16;
225 
226     static_assert(kMaxRenderTargets <= 4, "kMaxRenderTargets must be <= 4");
227     uint8_t numColorAttachments : 3;
228     uint8_t sampleCount : 5;
229 };
230 
231 // Some SDK levels don't declare MTLPrimitiveTopologyClass. Needs to do compile time check here:
232 #if !(TARGET_OS_OSX || TARGET_OS_MACCATALYST) && \
233     (!defined(__IPHONE_12_0) || ANGLE_IOS_DEPLOY_TARGET < __IPHONE_12_0)
234 #    define ANGLE_MTL_PRIMITIVE_TOPOLOGY_CLASS_AVAILABLE 0
235 using PrimitiveTopologyClass                                     = uint32_t;
236 constexpr PrimitiveTopologyClass kPrimitiveTopologyClassTriangle = 0;
237 constexpr PrimitiveTopologyClass kPrimitiveTopologyClassPoint    = 0;
238 #else
239 #    define ANGLE_MTL_PRIMITIVE_TOPOLOGY_CLASS_AVAILABLE 1
240 using PrimitiveTopologyClass = MTLPrimitiveTopologyClass;
241 constexpr PrimitiveTopologyClass kPrimitiveTopologyClassTriangle =
242     MTLPrimitiveTopologyClassTriangle;
243 constexpr PrimitiveTopologyClass kPrimitiveTopologyClassPoint = MTLPrimitiveTopologyClassPoint;
244 #endif
245 
246 enum class RenderPipelineRasterization : uint32_t
247 {
248     // This flag is used for vertex shader not writing any stage output (e.g gl_Position).
249     // This will disable fragment shader stage. This is useful for transform feedback ouput vertex
250     // shader.
251     Disabled,
252 
253     // Fragment shader is enabled.
254     Enabled,
255 
256     // This flag is for rasterization discard emulation when vertex shader still writes to stage
257     // output. Disabled flag cannot be used in this case since Metal doesn't allow that. The
258     // emulation would insert a code snippet to move gl_Position out of clip space's visible area to
259     // simulate the discard.
260     EmulatedDiscard,
261 
262     EnumCount,
263 };
264 
265 template <typename T>
266 using RenderPipelineRasterStateMap = angle::PackedEnumMap<RenderPipelineRasterization, T>;
267 
268 struct alignas(4) RenderPipelineDesc
269 {
270     RenderPipelineDesc();
271     RenderPipelineDesc(const RenderPipelineDesc &src);
272     RenderPipelineDesc(RenderPipelineDesc &&src);
273 
274     RenderPipelineDesc &operator=(const RenderPipelineDesc &src);
275 
276     bool operator==(const RenderPipelineDesc &rhs) const;
277     size_t hash() const;
278     bool rasterizationEnabled() const;
279 
280     VertexDesc vertexDescriptor;
281 
282     RenderPipelineOutputDesc outputDescriptor;
283 
284     // Use uint8_t instead of PrimitiveTopologyClass to compact space.
285     uint8_t inputPrimitiveTopology : 2;
286 
287     bool alphaToCoverageEnabled : 1;
288 
289     // These flags are for emulation and do not correspond to any flags in
290     // MTLRenderPipelineDescriptor descriptor. These flags should be used by
291     // RenderPipelineCacheSpecializeShaderFactory.
292     RenderPipelineRasterization rasterizationType : 2;
293     bool emulateCoverageMask : 1;
294 };
295 
296 struct alignas(4) ProvokingVertexComputePipelineDesc
297 {
298     ProvokingVertexComputePipelineDesc();
299     ProvokingVertexComputePipelineDesc(const ProvokingVertexComputePipelineDesc &src);
300     ProvokingVertexComputePipelineDesc(const ProvokingVertexComputePipelineDesc &&src);
301 
302     ProvokingVertexComputePipelineDesc &operator=(const ProvokingVertexComputePipelineDesc &src);
303 
304     bool operator==(const ProvokingVertexComputePipelineDesc &rhs) const;
305     bool operator!=(const ProvokingVertexComputePipelineDesc &rhs) const;
306     size_t hash() const;
307 
308     gl::PrimitiveMode primitiveMode;
309     uint8_t elementType;
310     bool primitiveRestartEnabled;
311     bool generateIndices;
312 };
313 
314 struct RenderPassAttachmentDesc
315 {
316     RenderPassAttachmentDesc();
317     // Set default values
318     void reset();
319 
320     bool equalIgnoreLoadStoreOptions(const RenderPassAttachmentDesc &other) const;
321     bool operator==(const RenderPassAttachmentDesc &other) const;
322 
hasImplicitMSTextureRenderPassAttachmentDesc323     ANGLE_INLINE bool hasImplicitMSTexture() const { return implicitMSTexture.get(); }
324 
325     TextureRef texture;
326     // Implicit multisample texture that will be rendered into and discarded at the end of
327     // a render pass. Its result will be resolved into normal texture above.
328     TextureRef implicitMSTexture;
329     MipmapNativeLevel level;
330     uint32_t sliceOrDepth;
331 
332     // This attachment is blendable or not.
333     bool blendable;
334     MTLLoadAction loadAction;
335     MTLStoreAction storeAction;
336     MTLStoreActionOptions storeActionOptions;
337 };
338 
339 struct RenderPassColorAttachmentDesc : public RenderPassAttachmentDesc
340 {
341     inline bool operator==(const RenderPassColorAttachmentDesc &other) const
342     {
343         return RenderPassAttachmentDesc::operator==(other) && clearColor == other.clearColor;
344     }
345     inline bool operator!=(const RenderPassColorAttachmentDesc &other) const
346     {
347         return !(*this == other);
348     }
349     MTLClearColor clearColor = {0, 0, 0, 0};
350 };
351 
352 struct RenderPassDepthAttachmentDesc : public RenderPassAttachmentDesc
353 {
354     inline bool operator==(const RenderPassDepthAttachmentDesc &other) const
355     {
356         return RenderPassAttachmentDesc::operator==(other) && clearDepth == other.clearDepth;
357     }
358     inline bool operator!=(const RenderPassDepthAttachmentDesc &other) const
359     {
360         return !(*this == other);
361     }
362 
363     double clearDepth = 0;
364 };
365 
366 struct RenderPassStencilAttachmentDesc : public RenderPassAttachmentDesc
367 {
368     inline bool operator==(const RenderPassStencilAttachmentDesc &other) const
369     {
370         return RenderPassAttachmentDesc::operator==(other) && clearStencil == other.clearStencil;
371     }
372     inline bool operator!=(const RenderPassStencilAttachmentDesc &other) const
373     {
374         return !(*this == other);
375     }
376     uint32_t clearStencil = 0;
377 };
378 
379 //
380 // This is C++ equivalent of Objective-C MTLRenderPassDescriptor.
381 // We could use MTLRenderPassDescriptor directly, however, using C++ struct has benefits of fast
382 // copy, stack allocation, inlined comparing function, etc.
383 //
384 struct RenderPassDesc
385 {
386     RenderPassColorAttachmentDesc colorAttachments[kMaxRenderTargets];
387     RenderPassDepthAttachmentDesc depthAttachment;
388     RenderPassStencilAttachmentDesc stencilAttachment;
389 
390     void convertToMetalDesc(MTLRenderPassDescriptor *objCDesc) const;
391 
392     // This will populate the RenderPipelineOutputDesc with default blend state and
393     // MTLColorWriteMaskAll
394     void populateRenderPipelineOutputDesc(RenderPipelineOutputDesc *outDesc) const;
395     // This will populate the RenderPipelineOutputDesc with default blend state and the specified
396     // MTLColorWriteMask
397     void populateRenderPipelineOutputDesc(const WriteMaskArray &writeMaskArray,
398                                           RenderPipelineOutputDesc *outDesc) const;
399     // This will populate the RenderPipelineOutputDesc with the specified blend state
400     void populateRenderPipelineOutputDesc(const BlendDescArray &blendDescArray,
401                                           RenderPipelineOutputDesc *outDesc) const;
402 
403     bool equalIgnoreLoadStoreOptions(const RenderPassDesc &other) const;
404     bool operator==(const RenderPassDesc &other) const;
405     inline bool operator!=(const RenderPassDesc &other) const { return !(*this == other); }
406 
407     uint32_t numColorAttachments = 0;
408     uint32_t sampleCount         = 1;
409 };
410 
411 }  // namespace mtl
412 }  // namespace rx
413 
414 namespace std
415 {
416 
417 template <>
418 struct hash<rx::mtl::DepthStencilDesc>
419 {
420     size_t operator()(const rx::mtl::DepthStencilDesc &key) const { return key.hash(); }
421 };
422 
423 template <>
424 struct hash<rx::mtl::SamplerDesc>
425 {
426     size_t operator()(const rx::mtl::SamplerDesc &key) const { return key.hash(); }
427 };
428 
429 template <>
430 struct hash<rx::mtl::RenderPipelineDesc>
431 {
432     size_t operator()(const rx::mtl::RenderPipelineDesc &key) const { return key.hash(); }
433 };
434 
435 template <>
436 struct hash<rx::mtl::ProvokingVertexComputePipelineDesc>
437 {
438     size_t operator()(const rx::mtl::ProvokingVertexComputePipelineDesc &key) const
439     {
440         return key.hash();
441     }
442 };
443 }  // namespace std
444 
445 namespace rx
446 {
447 namespace mtl
448 {
449 
450 // Abstract factory to create specialized vertex & fragment shaders based on RenderPipelineDesc.
451 class RenderPipelineCacheSpecializeShaderFactory
452 {
453   public:
454     virtual ~RenderPipelineCacheSpecializeShaderFactory() = default;
455     // Get specialized shader for the render pipeline cache.
456     virtual angle::Result getSpecializedShader(ContextMtl *context,
457                                                gl::ShaderType shaderType,
458                                                const RenderPipelineDesc &renderPipelineDesc,
459                                                id<MTLFunction> *shaderOut) = 0;
460     // Check whether specialized shaders is required for the specified RenderPipelineDesc.
461     // If not, the render pipeline cache will use the supplied non-specialized shaders.
462     virtual bool hasSpecializedShader(gl::ShaderType shaderType,
463                                       const RenderPipelineDesc &renderPipelineDesc) = 0;
464 };
465 
466 // Abstract factory to create specialized provoking vertex compute shaders based off of
467 // compute shader pipeline descs
468 
469 class ProvokingVertexCacheSpecializeShaderFactory
470 {
471   public:
472     virtual ~ProvokingVertexCacheSpecializeShaderFactory() = default;
473     // Get specialized shader for the render pipeline cache.
474     virtual angle::Result getSpecializedShader(
475         Context *context,
476         gl::ShaderType shaderType,
477         const ProvokingVertexComputePipelineDesc &renderPipelineDesc,
478         id<MTLFunction> *shaderOut) = 0;
479     // Check whether specialized shaders is required for the specified RenderPipelineDesc.
480     // If not, the render pipeline cache will use the supplied non-specialized shaders.
481     virtual bool hasSpecializedShader(
482         gl::ShaderType shaderType,
483         const ProvokingVertexComputePipelineDesc &renderPipelineDesc) = 0;
484 };
485 
486 // Render pipeline state cache per shader program.
487 class RenderPipelineCache final : angle::NonCopyable
488 {
489   public:
490     RenderPipelineCache();
491     RenderPipelineCache(RenderPipelineCacheSpecializeShaderFactory *specializedShaderFactory);
492     ~RenderPipelineCache();
493 
494     // Set non-specialized vertex/fragment shader to be used by render pipeline cache to create
495     // render pipeline state. If the internal
496     // RenderPipelineCacheSpecializeShaderFactory.hasSpecializedShader() returns false for a
497     // particular RenderPipelineDesc, the render pipeline cache will use the non-specialized
498     // shaders.
499     void setVertexShader(ContextMtl *context, id<MTLFunction> shader);
500     void setFragmentShader(ContextMtl *context, id<MTLFunction> shader);
501 
502     // Get non-specialized shaders supplied via set*Shader().
503     id<MTLFunction> getVertexShader() { return mVertexShader; }
504     id<MTLFunction> getFragmentShader() { return mFragmentShader; }
505 
506     AutoObjCPtr<id<MTLRenderPipelineState>> getRenderPipelineState(ContextMtl *context,
507                                                                    const RenderPipelineDesc &desc);
508 
509     void clear();
510 
511   protected:
512     // Non-specialized vertex shader
513     AutoObjCPtr<id<MTLFunction>> mVertexShader;
514     // Non-specialized fragment shader
515     AutoObjCPtr<id<MTLFunction>> mFragmentShader;
516 
517   private:
518     void clearPipelineStates();
519     void recreatePipelineStates(ContextMtl *context);
520     AutoObjCPtr<id<MTLRenderPipelineState>> insertRenderPipelineState(
521         ContextMtl *context,
522         const RenderPipelineDesc &desc,
523         bool insertDefaultAttribLayout);
524     AutoObjCPtr<id<MTLRenderPipelineState>> createRenderPipelineState(
525         ContextMtl *context,
526         const RenderPipelineDesc &desc,
527         bool insertDefaultAttribLayout);
528 
529     bool hasDefaultAttribs(const RenderPipelineDesc &desc) const;
530 
531     // One table with default attrib and one table without.
532     angle::HashMap<RenderPipelineDesc, AutoObjCPtr<id<MTLRenderPipelineState>>>
533         mRenderPipelineStates[2];
534     RenderPipelineCacheSpecializeShaderFactory *mSpecializedShaderFactory;
535 };
536 
537 // render pipeline state cache per shader program
538 class ProvokingVertexComputePipelineCache final : angle::NonCopyable
539 {
540   public:
541     ProvokingVertexComputePipelineCache();
542     ProvokingVertexComputePipelineCache(
543         ProvokingVertexCacheSpecializeShaderFactory *specializedShaderFactory);
544     ~ProvokingVertexComputePipelineCache();
545 
546     // Set non-specialized vertex/fragment shader to be used by render pipeline cache to create
547     // render pipeline state. If the internal
548     // RenderPipelineCacheSpecializeShaderFactory.hasSpecializedShader() returns false for a
549     // particular RenderPipelineDesc, the render pipeline cache will use the non-specialized
550     // shaders.
551     void setComputeShader(ContextMtl *context, id<MTLFunction> shader);
552     id<MTLFunction> getComputeShader() { return mComputeShader; }
553 
554     AutoObjCPtr<id<MTLComputePipelineState>> getComputePipelineState(
555         ContextMtl *context,
556         const ProvokingVertexComputePipelineDesc &desc);
557 
558     void clear();
559 
560   protected:
561     // Non-specialized compute shader
562     AutoObjCPtr<id<MTLFunction>> mComputeShader;
563 
564   private:
565     void clearPipelineStates();
566     void recreatePipelineStates(ContextMtl *context);
567     AutoObjCPtr<id<MTLComputePipelineState>> insertComputePipelineState(
568         ContextMtl *context,
569         const ProvokingVertexComputePipelineDesc &desc);
570 
571     AutoObjCPtr<id<MTLComputePipelineState>> createComputePipelineState(
572         ContextMtl *context,
573         const ProvokingVertexComputePipelineDesc &desc);
574 
575     bool hasDefaultAttribs(const RenderPipelineDesc &desc) const;
576 
577     // One table with default attrib and one table without.
578     std::unordered_map<ProvokingVertexComputePipelineDesc, AutoObjCPtr<id<MTLComputePipelineState>>>
579         mComputePipelineStates;
580     ProvokingVertexCacheSpecializeShaderFactory *mSpecializedShaderFactory;
581 };
582 
583 class StateCache final : angle::NonCopyable
584 {
585   public:
586     StateCache(const angle::FeaturesMtl &features);
587     ~StateCache();
588 
589     // Null depth stencil state has depth/stecil read & write disabled.
590     AutoObjCPtr<id<MTLDepthStencilState>> getNullDepthStencilState(
591         const mtl::ContextDevice &device);
592     AutoObjCPtr<id<MTLDepthStencilState>> getDepthStencilState(const mtl::ContextDevice &device,
593                                                                const DepthStencilDesc &desc);
594     AutoObjCPtr<id<MTLSamplerState>> getSamplerState(const mtl::ContextDevice &device,
595                                                      const SamplerDesc &desc);
596     // Null sampler state uses default SamplerDesc
597     AutoObjCPtr<id<MTLSamplerState>> getNullSamplerState(ContextMtl *context);
598     AutoObjCPtr<id<MTLSamplerState>> getNullSamplerState(const mtl::ContextDevice &device);
599     void clear();
600 
601   private:
602     const angle::FeaturesMtl &mFeatures;
603 
604     AutoObjCPtr<id<MTLDepthStencilState>> mNullDepthStencilState = nil;
605     angle::HashMap<DepthStencilDesc, AutoObjCPtr<id<MTLDepthStencilState>>> mDepthStencilStates;
606     angle::HashMap<SamplerDesc, AutoObjCPtr<id<MTLSamplerState>>> mSamplerStates;
607 };
608 
609 }  // namespace mtl
610 }  // namespace rx
611 
612 static inline bool operator==(const rx::mtl::VertexDesc &lhs, const rx::mtl::VertexDesc &rhs)
613 {
614     if (lhs.numAttribs != rhs.numAttribs || lhs.numBufferLayouts != rhs.numBufferLayouts)
615     {
616         return false;
617     }
618     for (uint8_t i = 0; i < lhs.numAttribs; ++i)
619     {
620         if (lhs.attributes[i] != rhs.attributes[i])
621         {
622             return false;
623         }
624     }
625     for (uint8_t i = 0; i < lhs.numBufferLayouts; ++i)
626     {
627         if (lhs.layouts[i] != rhs.layouts[i])
628         {
629             return false;
630         }
631     }
632     return true;
633 }
634 
635 static inline bool operator==(const MTLClearColor &lhs, const MTLClearColor &rhs)
636 {
637     return lhs.red == rhs.red && lhs.green == rhs.green && lhs.blue == rhs.blue &&
638            lhs.alpha == rhs.alpha;
639 }
640 
641 #endif /* LIBANGLE_RENDERER_METAL_MTL_STATE_CACHE_H_ */
642