blob: 34b832d9c75423ea78e0ce5c535cb9ec3ebff642 [file] [log] [blame]
//
// Copyright 2023 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// mtl_pipeline_cache.mm:
// Defines classes for caching of mtl pipelines
//
#include "libANGLE/renderer/metal/mtl_pipeline_cache.h"
#include "libANGLE/ErrorStrings.h"
#include "libANGLE/renderer/metal/ContextMtl.h"
namespace rx
{
namespace mtl
{
namespace
{
bool HasDefaultAttribs(const RenderPipelineDesc &rpdesc)
{
const VertexDesc &desc = rpdesc.vertexDescriptor;
for (uint8_t i = 0; i < desc.numAttribs; ++i)
{
if (desc.attributes[i].bufferIndex == kDefaultAttribsBindingIndex)
{
return true;
}
}
return false;
}
bool HasValidRenderTarget(const mtl::ContextDevice &device,
const MTLRenderPipelineDescriptor *descriptor)
{
const NSUInteger maxColorRenderTargets = GetMaxNumberOfRenderTargetsForDevice(device);
for (NSUInteger i = 0; i < maxColorRenderTargets; ++i)
{
auto colorAttachment = descriptor.colorAttachments[i];
if (colorAttachment && colorAttachment.pixelFormat != MTLPixelFormatInvalid)
{
return true;
}
}
if (descriptor.depthAttachmentPixelFormat != MTLPixelFormatInvalid)
{
return true;
}
if (descriptor.stencilAttachmentPixelFormat != MTLPixelFormatInvalid)
{
return true;
}
return false;
}
angle::Result ValidateRenderPipelineState(ContextMtl *context,
const MTLRenderPipelineDescriptor *descriptor)
{
const mtl::ContextDevice &device = context->getMetalDevice();
if (!context->getDisplay()->getFeatures().allowRenderpassWithoutAttachment.enabled &&
!HasValidRenderTarget(device, descriptor))
{
ANGLE_CHECK(context, false,
"Render pipeline requires at least one render target for this device.",
GL_INVALID_OPERATION);
}
// Ensure the device can support the storage requirement for render targets.
if (DeviceHasMaximumRenderTargetSize(device))
{
NSUInteger maxSize = GetMaxRenderTargetSizeForDeviceInBytes(device);
NSUInteger renderTargetSize =
ComputeTotalSizeUsedForMTLRenderPipelineDescriptor(descriptor, context, device);
if (renderTargetSize > maxSize)
{
std::stringstream errorStream;
errorStream << "This set of render targets requires " << renderTargetSize
<< " bytes of pixel storage. This device supports " << maxSize << " bytes.";
ANGLE_CHECK(context, false, errorStream.str().c_str(), GL_INVALID_OPERATION);
}
}
return angle::Result::Continue;
}
angle::Result CreateRenderPipelineState(
ContextMtl *context,
const PipelineKey &key,
angle::ObjCPtr<id<MTLRenderPipelineState>> *outRenderPipeline)
{
ASSERT(key.isRenderPipeline());
ANGLE_CHECK(context, key.vertexShader, gl::err::kInternalError, GL_INVALID_OPERATION);
ANGLE_MTL_OBJC_SCOPE
{
const mtl::ContextDevice &metalDevice = context->getMetalDevice();
auto objCDesc = key.pipelineDesc.createMetalDesc(key.vertexShader, key.fragmentShader);
ANGLE_TRY(ValidateRenderPipelineState(context, objCDesc));
// Special attribute slot for default attribute
if (HasDefaultAttribs(key.pipelineDesc))
{
auto defaultAttribLayoutObjCDesc =
angle::adoptObjCPtr([[MTLVertexBufferLayoutDescriptor alloc] init]);
defaultAttribLayoutObjCDesc.get().stepFunction = MTLVertexStepFunctionConstant;
defaultAttribLayoutObjCDesc.get().stepRate = 0;
defaultAttribLayoutObjCDesc.get().stride = kDefaultAttributeSize * kMaxVertexAttribs;
[objCDesc.get().vertexDescriptor.layouts setObject:defaultAttribLayoutObjCDesc
atIndexedSubscript:kDefaultAttribsBindingIndex];
}
// Create pipeline state
NSError *err = nil;
auto newState = metalDevice.newRenderPipelineStateWithDescriptor(objCDesc, &err);
ANGLE_MTL_CHECK(context, newState, err);
*outRenderPipeline = newState;
return angle::Result::Continue;
}
}
angle::Result CreateComputePipelineState(
ContextMtl *context,
const PipelineKey &key,
angle::ObjCPtr<id<MTLComputePipelineState>> *outComputePipeline)
{
ASSERT(!key.isRenderPipeline());
ANGLE_CHECK(context, key.computeShader, gl::err::kInternalError, GL_INVALID_OPERATION);
ANGLE_MTL_OBJC_SCOPE
{
const mtl::ContextDevice &metalDevice = context->getMetalDevice();
NSError *err = nil;
auto newState = metalDevice.newComputePipelineStateWithFunction(key.computeShader, &err);
ANGLE_MTL_CHECK(context, newState, err);
*outComputePipeline = newState;
return angle::Result::Continue;
}
}
} // namespace
bool PipelineKey::isRenderPipeline() const
{
if (vertexShader)
{
ASSERT(!computeShader);
return true;
}
else
{
ASSERT(computeShader);
return false;
}
}
bool PipelineKey::operator==(const PipelineKey &rhs) const
{
if (isRenderPipeline() != rhs.isRenderPipeline())
{
return false;
}
if (isRenderPipeline())
{
return std::tie(vertexShader, fragmentShader, pipelineDesc) ==
std::tie(rhs.vertexShader, rhs.fragmentShader, rhs.pipelineDesc);
}
else
{
return computeShader == rhs.computeShader;
}
}
size_t PipelineKey::hash() const
{
if (isRenderPipeline())
{
return angle::HashMultiple(vertexShader.get(), fragmentShader.get(), pipelineDesc);
}
else
{
return angle::HashMultiple(computeShader.get());
}
}
PipelineCache::PipelineCache() : mPipelineCache(kMaxPipelines) {}
angle::Result PipelineCache::getRenderPipeline(
ContextMtl *context,
id<MTLFunction> vertexShader,
id<MTLFunction> fragmentShader,
const RenderPipelineDesc &desc,
angle::ObjCPtr<id<MTLRenderPipelineState>> *outRenderPipeline)
{
PipelineKey key;
key.vertexShader = std::move(vertexShader);
key.fragmentShader = std::move(fragmentShader);
key.pipelineDesc = desc;
auto iter = mPipelineCache.Get(key);
if (iter != mPipelineCache.end())
{
// Should be no way that this key matched a compute pipeline entry
ASSERT(iter->second.renderPipeline);
*outRenderPipeline = iter->second.renderPipeline;
return angle::Result::Continue;
}
angle::TrimCache(kMaxPipelines, kGCLimit, "render pipeline", &mPipelineCache);
PipelineVariant newPipeline;
ANGLE_TRY(CreateRenderPipelineState(context, key, &newPipeline.renderPipeline));
iter = mPipelineCache.Put(std::move(key), std::move(newPipeline));
*outRenderPipeline = iter->second.renderPipeline;
return angle::Result::Continue;
}
angle::Result PipelineCache::getComputePipeline(
ContextMtl *context,
id<MTLFunction> computeShader,
angle::ObjCPtr<id<MTLComputePipelineState>> *outComputePipeline)
{
PipelineKey key;
key.computeShader = std::move(computeShader);
auto iter = mPipelineCache.Get(key);
if (iter != mPipelineCache.end())
{
// Should be no way that this key matched a render pipeline entry
ASSERT(iter->second.computePipeline);
*outComputePipeline = iter->second.computePipeline;
return angle::Result::Continue;
}
angle::TrimCache(kMaxPipelines, kGCLimit, "render pipeline", &mPipelineCache);
PipelineVariant newPipeline;
ANGLE_TRY(CreateComputePipelineState(context, key, &newPipeline.computePipeline));
iter = mPipelineCache.Put(std::move(key), std::move(newPipeline));
*outComputePipeline = iter->second.computePipeline;
return angle::Result::Continue;
}
} // namespace mtl
} // namespace rx