blob: 6ea4c75f5e53f975fc6fd5ab60cab2a3be87abb3 [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/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_MTL_HANDLE_ERROR(
context, "Render pipeline requires at least one render target for this device.",
GL_INVALID_OPERATION);
return angle::Result::Stop;
}
// 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_MTL_HANDLE_ERROR(context, errorStream.str().c_str(), GL_INVALID_OPERATION);
return angle::Result::Stop;
}
}
return angle::Result::Continue;
}
angle::Result CreateRenderPipelineState(ContextMtl *context,
const PipelineKey &key,
AutoObjCPtr<id<MTLRenderPipelineState>> *outRenderPipeline)
{
ANGLE_MTL_OBJC_SCOPE
{
ASSERT(key.isRenderPipeline());
if (!key.vertexShader)
{
// Render pipeline without vertex shader is invalid.
ANGLE_MTL_HANDLE_ERROR(context, "Render pipeline without vertex shader is invalid.",
GL_INVALID_OPERATION);
return angle::Result::Stop;
}
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 = adoptObjCObj<MTLVertexBufferLayoutDescriptor>(
[[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);
if (err)
{
ANGLE_MTL_HANDLE_ERROR(context, mtl::FormatMetalErrorMessage(err).c_str(),
GL_INVALID_OPERATION);
return angle::Result::Stop;
}
*outRenderPipeline = newState;
return angle::Result::Continue;
}
}
angle::Result CreateComputePipelineState(
ContextMtl *context,
const PipelineKey &key,
AutoObjCPtr<id<MTLComputePipelineState>> *outComputePipeline)
{
ANGLE_MTL_OBJC_SCOPE
{
ASSERT(!key.isRenderPipeline());
if (!key.computeShader)
{
ANGLE_MTL_HANDLE_ERROR(context, "Compute pipeline without a shader is invalid.",
GL_INVALID_OPERATION);
return angle::Result::Stop;
}
const mtl::ContextDevice &metalDevice = context->getMetalDevice();
// Create pipeline state
NSError *err = nil;
auto newState = metalDevice.newComputePipelineStateWithFunction(key.computeShader, &err);
if (err)
{
ANGLE_MTL_HANDLE_ERROR(context, mtl::FormatMetalErrorMessage(err).c_str(),
GL_INVALID_OPERATION);
return angle::Result::Stop;
}
*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,
AutoObjCPtr<id<MTLRenderPipelineState>> *outRenderPipeline)
{
PipelineKey key;
key.vertexShader.retainAssign(vertexShader);
key.fragmentShader.retainAssign(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,
AutoObjCPtr<id<MTLComputePipelineState>> *outComputePipeline)
{
PipelineKey key;
key.computeShader.retainAssign(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