Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
137 changes: 102 additions & 35 deletions plume_metal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,30 @@ namespace plume {
return key.value;
}

// Same key as createClearPipelineKey, built from the formats a clear will use, so the cached
// pipeline can be found without allocating a render pipeline descriptor for every clear.
uint64_t clearPipelineKey(bool depthWriteEnabled, bool stencilWriteEnabled, uint32_t sampleCount,
const MTL::PixelFormat *colorFormats, uint32_t colorFormatCount, MTL::PixelFormat depthFormat) {
auto colorFormat = [&](uint32_t index) {
return (index < colorFormatCount) ? static_cast<uint64_t>(mapRenderFormat(colorFormats[index])) : 0llu;
};

ClearPipelineKey key;
key.value = 0;
key.depthClear = depthWriteEnabled ? 1 : 0;
key.stencilClear = stencilWriteEnabled ? 1 : 0;
key.msaaCount = sampleCount;
key.colorFormat0 = colorFormat(0);
key.colorFormat1 = colorFormat(1);
key.colorFormat2 = colorFormat(2);
key.colorFormat3 = colorFormat(3);
key.colorFormat4 = colorFormat(4);
key.colorFormat5 = colorFormat(5);
key.colorFormat6 = colorFormat(6);
key.depthFormat = static_cast<uint64_t>(mapRenderFormat(depthFormat));
return key.value;
}

NS::UInteger alignmentForRenderFormat(MTL::Device *device, RenderFormat format) {
const auto deviceAlignment = device->minimumLinearTextureAlignmentForPixelFormat(mapPixelFormat(format));

Expand Down Expand Up @@ -2725,6 +2749,14 @@ namespace plume {
}

void MetalCommandList::setFramebuffer(const RenderFramebuffer *framebuffer) {
// Rebinding the target of the open render encoder is a no-op. Ending the encoder here would
// start a new render pass per draw for callers that set the framebuffer before every draw,
// and render pass creation dominates encoding time on Apple GPUs.
if ((framebuffer != nullptr) && (framebuffer == targetFramebuffer) && (activeType == EncoderType::Render) &&
(activeRenderEncoder != nullptr) && !pendingClears.active) {
return;
}

MetalAutoreleasePool releasePool;
endOtherEncoders(EncoderType::Render);
endActiveRenderEncoder();
Expand Down Expand Up @@ -2797,34 +2829,45 @@ namespace plume {
// Process clears
activeRenderEncoder->pushDebugGroup(MTLSTR("ColorClear"));

MTL::RenderPipelineDescriptor* pipelineDesc = MTL::RenderPipelineDescriptor::alloc()->init();
pipelineDesc->setVertexFunction(device->clearVertexFunction);
pipelineDesc->setFragmentFunction(device->clearColorFunction);
pipelineDesc->setRasterSampleCount(targetFramebuffer->colorAttachments[attachmentIndex].sampleCount);
const bool hasDepth = targetFramebuffer->depthAttachment.format != RenderFormat::UNKNOWN;
const MTL::PixelFormat depthPixelFormat = hasDepth ? targetFramebuffer->depthAttachment.getTexture()->pixelFormat() : MTL::PixelFormatInvalid;
if (hasDepth) {
// Cached device state; creating one here per clear leaked a depth-stencil state each time.
activeRenderEncoder->setDepthStencilState(device->clearColorDepthState);
}

// Look up the cached pipeline before allocating a descriptor; only a cache miss builds one.
MTL::PixelFormat colorFormats[7] = {};
if (attachmentIndex < 7) {
colorFormats[attachmentIndex] = targetFramebuffer->colorAttachments[attachmentIndex].getTexture()->pixelFormat();
}
const uint32_t sampleCount = targetFramebuffer->colorAttachments[attachmentIndex].sampleCount;
const uint64_t pipelineKey = clearPipelineKey(false, false, sampleCount, colorFormats, 7, depthPixelFormat);
const MTL::RenderPipelineState *pipelineState = device->findClearRenderPipelineState(pipelineKey);
if (pipelineState == nullptr) {
MTL::RenderPipelineDescriptor* pipelineDesc = MTL::RenderPipelineDescriptor::alloc()->init();
pipelineDesc->setVertexFunction(device->clearVertexFunction);
pipelineDesc->setFragmentFunction(device->clearColorFunction);
pipelineDesc->setRasterSampleCount(sampleCount);

MTL::RenderPipelineColorAttachmentDescriptor *pipelineColorAttachment = pipelineDesc->colorAttachments()->object(attachmentIndex);
pipelineColorAttachment->setPixelFormat(targetFramebuffer->colorAttachments[attachmentIndex].getTexture()->pixelFormat());
pipelineColorAttachment->setBlendingEnabled(false);
MTL::RenderPipelineColorAttachmentDescriptor *pipelineColorAttachment = pipelineDesc->colorAttachments()->object(attachmentIndex);
pipelineColorAttachment->setPixelFormat(targetFramebuffer->colorAttachments[attachmentIndex].getTexture()->pixelFormat());
pipelineColorAttachment->setBlendingEnabled(false);

// Set pixel format for depth attachment if we have one, with write disabled
if (targetFramebuffer->depthAttachment.format != RenderFormat::UNKNOWN) {
pipelineDesc->setDepthAttachmentPixelFormat(targetFramebuffer->depthAttachment.getTexture()->pixelFormat());
if (RenderFormatIsStencil(targetFramebuffer->depthAttachment.format)) {
pipelineDesc->setStencilAttachmentPixelFormat(pipelineDesc->depthAttachmentPixelFormat());
// Set pixel format for depth attachment if we have one, with write disabled
if (hasDepth) {
pipelineDesc->setDepthAttachmentPixelFormat(depthPixelFormat);
if (RenderFormatIsStencil(targetFramebuffer->depthAttachment.format)) {
pipelineDesc->setStencilAttachmentPixelFormat(depthPixelFormat);
}
}
MTL::DepthStencilDescriptor *depthStencilDescriptor = MTL::DepthStencilDescriptor::alloc()->init();
depthStencilDescriptor->setDepthWriteEnabled(false);
const MTL::DepthStencilState *depthStencilState = device->mtl->newDepthStencilState(depthStencilDescriptor);
activeRenderEncoder->setDepthStencilState(depthStencilState);

depthStencilDescriptor->release();
pipelineState = device->getOrCreateClearRenderPipelineState(pipelineDesc);
pipelineDesc->release();
}

const MTL::RenderPipelineState *pipelineState = device->getOrCreateClearRenderPipelineState(pipelineDesc);
activeRenderEncoder->setRenderPipelineState(pipelineState);

setCommonClearState();
pipelineDesc->release();

// Generate vertices for each rect
const uint32_t rectCount = clearRectsCount > 0 ? clearRectsCount : 1;
Expand Down Expand Up @@ -2897,23 +2940,35 @@ namespace plume {
// Process clears
activeRenderEncoder->pushDebugGroup(MTLSTR("DepthClear"));

MTL::RenderPipelineDescriptor* pipelineDesc = MTL::RenderPipelineDescriptor::alloc()->init();
pipelineDesc->setVertexFunction(device->clearVertexFunction);
pipelineDesc->setFragmentFunction(device->clearDepthFunction);
pipelineDesc->setDepthAttachmentPixelFormat(targetFramebuffer->depthAttachment.getTexture()->pixelFormat());
if (RenderFormatIsStencil(targetFramebuffer->depthAttachment.format)) {
pipelineDesc->setStencilAttachmentPixelFormat(pipelineDesc->depthAttachmentPixelFormat());
// Look up the cached pipeline before allocating a descriptor; only a cache miss builds one.
const MTL::PixelFormat depthPixelFormat = targetFramebuffer->depthAttachment.getTexture()->pixelFormat();
const uint32_t colorCount = static_cast<uint32_t>(std::min<size_t>(targetFramebuffer->colorAttachments.size(), 7));
MTL::PixelFormat colorFormats[7] = {};
for (uint32_t j = 0; j < colorCount; j++) {
colorFormats[j] = targetFramebuffer->colorAttachments[j].getTexture()->pixelFormat();
}
pipelineDesc->setRasterSampleCount(targetFramebuffer->depthAttachment.sampleCount);
const uint64_t pipelineKey = clearPipelineKey(clearDepth, clearStencil, targetFramebuffer->depthAttachment.sampleCount, colorFormats, colorCount, depthPixelFormat);
const MTL::RenderPipelineState *pipelineState = device->findClearRenderPipelineState(pipelineKey);
if (pipelineState == nullptr) {
MTL::RenderPipelineDescriptor* pipelineDesc = MTL::RenderPipelineDescriptor::alloc()->init();
pipelineDesc->setVertexFunction(device->clearVertexFunction);
pipelineDesc->setFragmentFunction(device->clearDepthFunction);
pipelineDesc->setDepthAttachmentPixelFormat(depthPixelFormat);
if (RenderFormatIsStencil(targetFramebuffer->depthAttachment.format)) {
pipelineDesc->setStencilAttachmentPixelFormat(depthPixelFormat);
}
pipelineDesc->setRasterSampleCount(targetFramebuffer->depthAttachment.sampleCount);

// Set color attachment pixel formats with write disabled
for (uint32_t j = 0; j < targetFramebuffer->colorAttachments.size(); j++) {
MTL::RenderPipelineColorAttachmentDescriptor *pipelineColorAttachment = pipelineDesc->colorAttachments()->object(j);
pipelineColorAttachment->setPixelFormat(targetFramebuffer->colorAttachments[j].getTexture()->pixelFormat());
pipelineColorAttachment->setWriteMask(MTL::ColorWriteMaskNone);
}
// Set color attachment pixel formats with write disabled
for (uint32_t j = 0; j < targetFramebuffer->colorAttachments.size(); j++) {
MTL::RenderPipelineColorAttachmentDescriptor *pipelineColorAttachment = pipelineDesc->colorAttachments()->object(j);
pipelineColorAttachment->setPixelFormat(targetFramebuffer->colorAttachments[j].getTexture()->pixelFormat());
pipelineColorAttachment->setWriteMask(MTL::ColorWriteMaskNone);
}

const MTL::RenderPipelineState *pipelineState = device->getOrCreateClearRenderPipelineState(pipelineDesc, clearDepth, clearStencil);
pipelineState = device->getOrCreateClearRenderPipelineState(pipelineDesc, clearDepth, clearStencil);
pipelineDesc->release();
}
activeRenderEncoder->setRenderPipelineState(pipelineState);
if (clearDepth && clearStencil) {
activeRenderEncoder->setDepthStencilState(device->clearDepthStencilState);
Expand All @@ -2924,7 +2979,6 @@ namespace plume {
}

setCommonClearState();
pipelineDesc->release();

// Generate vertices for each rect
const uint32_t rectCount = clearRectsCount > 0 ? clearRectsCount : 1;
Expand Down Expand Up @@ -4132,6 +4186,13 @@ namespace plume {

depthDescriptor->release();
stencilDescriptor->release();

// Partial color clears with a depth attachment: no depth or stencil writes, depth test always.
MTL::DepthStencilDescriptor *colorClearDepthDescriptor = MTL::DepthStencilDescriptor::alloc()->init();
colorClearDepthDescriptor->setDepthWriteEnabled(false);
colorClearDepthDescriptor->setDepthCompareFunction(MTL::CompareFunctionAlways);
clearColorDepthState = mtl->newDepthStencilState(colorClearDepthDescriptor);
colorClearDepthDescriptor->release();
clearShaderLibrary->release();
}

Expand All @@ -4157,6 +4218,12 @@ namespace plume {
return inserted_it->second;
}

MTL::RenderPipelineState* MetalDevice::findClearRenderPipelineState(uint64_t pipelineKey) {
std::lock_guard lock(clearPipelineStateMutex);
const auto it = clearRenderPipelineStates.find(pipelineKey);
return (it != clearRenderPipelineStates.end()) ? it->second : nullptr;
}

void MetalDevice::addResource(MTL::Resource *resource, bool addressable) {
if (residencySet != nullptr || addressable) {
std::lock_guard lock(resourcesMutex);
Expand Down
3 changes: 3 additions & 0 deletions plume_metal.h
Original file line number Diff line number Diff line change
Expand Up @@ -711,6 +711,8 @@ namespace plume {
MTL::DepthStencilState *clearDepthState;
MTL::DepthStencilState *clearStencilState;
MTL::DepthStencilState *clearDepthStencilState;
// Depth attached but not written (partial color clears); created once instead of per clear.
MTL::DepthStencilState *clearColorDepthState = nullptr;

std::mutex clearPipelineStateMutex;
std::unordered_map<uint64_t, MTL::RenderPipelineState *> clearRenderPipelineStates;
Expand Down Expand Up @@ -765,6 +767,7 @@ namespace plume {
void createClearShaderLibrary();

MTL::RenderPipelineState* getOrCreateClearRenderPipelineState(MTL::RenderPipelineDescriptor *pipelineDesc, bool depthWriteEnabled = false, bool stencilWriteEnabled = false);
MTL::RenderPipelineState* findClearRenderPipelineState(uint64_t pipelineKey);

void addResource(MTL::Resource *resource, bool addressable = false);
void removeResource(MTL::Resource *resource, bool addressable = false);
Expand Down