| // |
| // Copyright 2019 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_command_buffer.mm: |
| // Implementations of Metal framework's MTLCommandBuffer, MTLCommandQueue, |
| // MTLCommandEncoder's wrappers. |
| // |
| |
| #include "libANGLE/renderer/metal/mtl_command_buffer.h" |
| |
| #include <cassert> |
| |
| #include "common/debug.h" |
| #include "libANGLE/renderer/metal/mtl_resources.h" |
| |
| namespace rx |
| { |
| namespace mtl |
| { |
| |
| // CommandQueue implementation |
| void CommandQueue::reset() |
| { |
| finishAllCommands(); |
| ParentClass::reset(); |
| } |
| |
| void CommandQueue::set(id<MTLCommandQueue> metalQueue) |
| { |
| finishAllCommands(); |
| |
| ParentClass::set(metalQueue); |
| } |
| |
| void CommandQueue::finishAllCommands() |
| { |
| { |
| // Copy to temp list |
| std::lock_guard<std::mutex> lg(mLock); |
| |
| for (CmdBufferQueueEntry &metalBufferEntry : mMetalCmdBuffers) |
| { |
| mMetalCmdBuffersTmp.push_back(metalBufferEntry); |
| } |
| |
| mMetalCmdBuffers.clear(); |
| } |
| |
| // Wait for command buffers to finish |
| for (CmdBufferQueueEntry &metalBufferEntry : mMetalCmdBuffersTmp) |
| { |
| [metalBufferEntry.buffer waitUntilCompleted]; |
| } |
| mMetalCmdBuffersTmp.clear(); |
| } |
| |
| void CommandQueue::ensureResourceReadyForCPU(const ResourceRef &resource) |
| { |
| if (!resource) |
| { |
| return; |
| } |
| |
| ensureResourceReadyForCPU(resource.get()); |
| } |
| |
| void CommandQueue::ensureResourceReadyForCPU(Resource *resource) |
| { |
| mLock.lock(); |
| while (isResourceBeingUsedByGPU(resource) && !mMetalCmdBuffers.empty()) |
| { |
| CmdBufferQueueEntry metalBufferEntry = mMetalCmdBuffers.front(); |
| mMetalCmdBuffers.pop_front(); |
| mLock.unlock(); |
| |
| ANGLE_MTL_LOG("Waiting for MTLCommandBuffer %llu:%p", metalBufferEntry.serial, |
| metalBufferEntry.buffer.get()); |
| [metalBufferEntry.buffer waitUntilCompleted]; |
| |
| mLock.lock(); |
| } |
| mLock.unlock(); |
| |
| // This can happen if the resource is read then write in the same command buffer. |
| // So it is the responsitibily of outer code to ensure the command buffer is commit before |
| // the resource can be read or written again |
| ASSERT(!isResourceBeingUsedByGPU(resource)); |
| } |
| |
| bool CommandQueue::isResourceBeingUsedByGPU(const Resource *resource) const |
| { |
| if (!resource) |
| { |
| return false; |
| } |
| |
| return mCompletedBufferSerial.load(std::memory_order_relaxed) < |
| resource->getCommandBufferQueueSerial().load(std::memory_order_relaxed); |
| } |
| |
| AutoObjCPtr<id<MTLCommandBuffer>> CommandQueue::makeMetalCommandBuffer(uint64_t *queueSerialOut) |
| { |
| ANGLE_MTL_OBJC_SCOPE |
| { |
| AutoObjCPtr<id<MTLCommandBuffer>> metalCmdBuffer = [get() commandBuffer]; |
| |
| std::lock_guard<std::mutex> lg(mLock); |
| |
| uint64_t serial = mQueueSerialCounter++; |
| |
| mMetalCmdBuffers.push_back({metalCmdBuffer, serial}); |
| |
| ANGLE_MTL_LOG("Created MTLCommandBuffer %llu:%p", serial, metalCmdBuffer.get()); |
| |
| [metalCmdBuffer addCompletedHandler:^(id<MTLCommandBuffer> buf) { |
| onCommandBufferCompleted(buf, serial); |
| }]; |
| |
| [metalCmdBuffer enqueue]; |
| |
| ASSERT(metalCmdBuffer); |
| |
| *queueSerialOut = serial; |
| |
| return metalCmdBuffer; |
| } |
| } |
| |
| void CommandQueue::onCommandBufferCompleted(id<MTLCommandBuffer> buf, uint64_t serial) |
| { |
| std::lock_guard<std::mutex> lg(mLock); |
| |
| ANGLE_MTL_LOG("Completed MTLCommandBuffer %llu:%p", serial, buf); |
| |
| if (mCompletedBufferSerial >= serial) |
| { |
| // Already handled. |
| return; |
| } |
| |
| while (!mMetalCmdBuffers.empty() && mMetalCmdBuffers.front().serial <= serial) |
| { |
| CmdBufferQueueEntry metalBufferEntry = mMetalCmdBuffers.front(); |
| ANGLE_UNUSED_VARIABLE(metalBufferEntry); |
| ANGLE_MTL_LOG("Popped MTLCommandBuffer %llu:%p", metalBufferEntry.serial, |
| metalBufferEntry.buffer.get()); |
| |
| mMetalCmdBuffers.pop_front(); |
| } |
| |
| mCompletedBufferSerial.store( |
| std::max(mCompletedBufferSerial.load(std::memory_order_relaxed), serial), |
| std::memory_order_relaxed); |
| } |
| |
| // CommandBuffer implementation |
| CommandBuffer::CommandBuffer(CommandQueue *cmdQueue) : mCmdQueue(*cmdQueue) {} |
| |
| CommandBuffer::~CommandBuffer() |
| { |
| finish(); |
| cleanup(); |
| } |
| |
| bool CommandBuffer::valid() const |
| { |
| std::lock_guard<std::mutex> lg(mLock); |
| |
| return validImpl(); |
| } |
| |
| void CommandBuffer::commit() |
| { |
| std::lock_guard<std::mutex> lg(mLock); |
| commitImpl(); |
| } |
| |
| void CommandBuffer::finish() |
| { |
| commit(); |
| [get() waitUntilCompleted]; |
| } |
| |
| void CommandBuffer::present(id<CAMetalDrawable> presentationDrawable) |
| { |
| [get() presentDrawable:presentationDrawable]; |
| } |
| |
| void CommandBuffer::setWriteDependency(const ResourceRef &resource) |
| { |
| if (!resource) |
| { |
| return; |
| } |
| |
| std::lock_guard<std::mutex> lg(mLock); |
| |
| if (!validImpl()) |
| { |
| return; |
| } |
| |
| resource->setUsedByCommandBufferWithQueueSerial(mQueueSerial, true); |
| } |
| |
| void CommandBuffer::setReadDependency(const ResourceRef &resource) |
| { |
| if (!resource) |
| { |
| return; |
| } |
| |
| std::lock_guard<std::mutex> lg(mLock); |
| |
| if (!validImpl()) |
| { |
| return; |
| } |
| |
| resource->setUsedByCommandBufferWithQueueSerial(mQueueSerial, false); |
| } |
| |
| void CommandBuffer::restart() |
| { |
| uint64_t serial = 0; |
| auto metalCmdBuffer = mCmdQueue.makeMetalCommandBuffer(&serial); |
| |
| std::lock_guard<std::mutex> lg(mLock); |
| |
| set(metalCmdBuffer); |
| mQueueSerial = serial; |
| mCommitted = false; |
| |
| ASSERT(metalCmdBuffer); |
| } |
| |
| /** private use only */ |
| void CommandBuffer::set(id<MTLCommandBuffer> metalBuffer) |
| { |
| ParentClass::set(metalBuffer); |
| } |
| |
| void CommandBuffer::setActiveCommandEncoder(CommandEncoder *encoder) |
| { |
| mActiveCommandEncoder = encoder; |
| } |
| |
| void CommandBuffer::invalidateActiveCommandEncoder(CommandEncoder *encoder) |
| { |
| mActiveCommandEncoder.compare_exchange_strong(encoder, nullptr); |
| } |
| |
| void CommandBuffer::cleanup() |
| { |
| mActiveCommandEncoder = nullptr; |
| |
| ParentClass::set(nil); |
| } |
| |
| bool CommandBuffer::validImpl() const |
| { |
| if (!ParentClass::valid()) |
| { |
| return false; |
| } |
| |
| return !mCommitted; |
| } |
| |
| void CommandBuffer::commitImpl() |
| { |
| if (!validImpl()) |
| { |
| return; |
| } |
| |
| // End the current encoder |
| if (mActiveCommandEncoder.load(std::memory_order_relaxed)) |
| { |
| mActiveCommandEncoder.load(std::memory_order_relaxed)->endEncoding(); |
| mActiveCommandEncoder = nullptr; |
| } |
| |
| // Do the actual commit |
| [get() commit]; |
| |
| ANGLE_MTL_LOG("Committed MTLCommandBuffer %llu:%p", mQueueSerial, get()); |
| |
| mCommitted = true; |
| } |
| |
| // CommandEncoder implementation |
| CommandEncoder::CommandEncoder(CommandBuffer *cmdBuffer, Type type) |
| : mType(type), mCmdBuffer(*cmdBuffer) |
| {} |
| |
| CommandEncoder::~CommandEncoder() |
| { |
| reset(); |
| } |
| |
| void CommandEncoder::endEncoding() |
| { |
| [get() endEncoding]; |
| reset(); |
| } |
| |
| void CommandEncoder::reset() |
| { |
| ParentClass::reset(); |
| |
| mCmdBuffer.invalidateActiveCommandEncoder(this); |
| } |
| |
| void CommandEncoder::set(id<MTLCommandEncoder> metalCmdEncoder) |
| { |
| ParentClass::set(metalCmdEncoder); |
| |
| // Set this as active encoder |
| cmdBuffer().setActiveCommandEncoder(this); |
| } |
| |
| CommandEncoder &CommandEncoder::markResourceBeingWrittenByGPU(const BufferRef &buffer) |
| { |
| cmdBuffer().setWriteDependency(buffer); |
| return *this; |
| } |
| |
| CommandEncoder &CommandEncoder::markResourceBeingWrittenByGPU(const TextureRef &texture) |
| { |
| cmdBuffer().setWriteDependency(texture); |
| return *this; |
| } |
| |
| // RenderCommandEncoder implemtation |
| RenderCommandEncoder::RenderCommandEncoder(CommandBuffer *cmdBuffer) |
| : CommandEncoder(cmdBuffer, RENDER) |
| {} |
| RenderCommandEncoder::~RenderCommandEncoder() {} |
| |
| void RenderCommandEncoder::endEncoding() |
| { |
| if (!valid()) |
| return; |
| |
| // Now is the time to do the actual store option setting. |
| auto metalEncoder = get(); |
| for (uint32_t i = 0; i < mRenderPassDesc.numColorAttachments; ++i) |
| { |
| if (mRenderPassDesc.colorAttachments[i].storeAction == MTLStoreActionUnknown) |
| { |
| // If storeAction hasn't been set for this attachment, we set to dontcare. |
| mRenderPassDesc.colorAttachments[i].storeAction = MTLStoreActionDontCare; |
| } |
| |
| // Only initial unknown store action can change the value now. |
| if (mColorInitialStoreActions[i] == MTLStoreActionUnknown) |
| { |
| [metalEncoder setColorStoreAction:mRenderPassDesc.colorAttachments[i].storeAction |
| atIndex:i]; |
| } |
| } |
| |
| if (mRenderPassDesc.depthAttachment.storeAction == MTLStoreActionUnknown) |
| { |
| // If storeAction hasn't been set for this attachment, we set to dontcare. |
| mRenderPassDesc.depthAttachment.storeAction = MTLStoreActionDontCare; |
| } |
| if (mDepthInitialStoreAction == MTLStoreActionUnknown) |
| { |
| [metalEncoder setDepthStoreAction:mRenderPassDesc.depthAttachment.storeAction]; |
| } |
| |
| if (mRenderPassDesc.stencilAttachment.storeAction == MTLStoreActionUnknown) |
| { |
| // If storeAction hasn't been set for this attachment, we set to dontcare. |
| mRenderPassDesc.stencilAttachment.storeAction = MTLStoreActionDontCare; |
| } |
| if (mStencilInitialStoreAction == MTLStoreActionUnknown) |
| { |
| [metalEncoder setStencilStoreAction:mRenderPassDesc.stencilAttachment.storeAction]; |
| } |
| |
| CommandEncoder::endEncoding(); |
| |
| // reset state |
| mRenderPassDesc = RenderPassDesc(); |
| } |
| |
| inline void RenderCommandEncoder::initWriteDependencyAndStoreAction(const TextureRef &texture, |
| MTLStoreAction *storeActionOut) |
| { |
| if (texture) |
| { |
| cmdBuffer().setWriteDependency(texture); |
| // Set initial store action to unknown so that we can change it later when the encoder ends. |
| *storeActionOut = MTLStoreActionUnknown; |
| } |
| else |
| { |
| // Texture is invalid, use don'tcare store action |
| *storeActionOut = MTLStoreActionDontCare; |
| } |
| } |
| |
| RenderCommandEncoder &RenderCommandEncoder::restart(const RenderPassDesc &desc) |
| { |
| if (valid()) |
| { |
| if (mRenderPassDesc == desc) |
| { |
| // no change, skip |
| return *this; |
| } |
| |
| // finish current encoder |
| endEncoding(); |
| } |
| |
| if (!cmdBuffer().valid()) |
| { |
| reset(); |
| return *this; |
| } |
| |
| mRenderPassDesc = desc; |
| |
| ANGLE_MTL_OBJC_SCOPE |
| { |
| // mask writing dependency |
| for (uint32_t i = 0; i < mRenderPassDesc.numColorAttachments; ++i) |
| { |
| initWriteDependencyAndStoreAction(mRenderPassDesc.colorAttachments[i].texture, |
| &mRenderPassDesc.colorAttachments[i].storeAction); |
| mColorInitialStoreActions[i] = mRenderPassDesc.colorAttachments[i].storeAction; |
| } |
| |
| initWriteDependencyAndStoreAction(mRenderPassDesc.depthAttachment.texture, |
| &mRenderPassDesc.depthAttachment.storeAction); |
| mDepthInitialStoreAction = mRenderPassDesc.depthAttachment.storeAction; |
| |
| initWriteDependencyAndStoreAction(mRenderPassDesc.stencilAttachment.texture, |
| &mRenderPassDesc.stencilAttachment.storeAction); |
| mStencilInitialStoreAction = mRenderPassDesc.stencilAttachment.storeAction; |
| |
| // Create objective C object |
| mtl::AutoObjCObj<MTLRenderPassDescriptor> objCDesc = ToMetalObj(mRenderPassDesc); |
| |
| ANGLE_MTL_LOG("Creating new render command encoder with desc: %@", objCDesc.get()); |
| |
| id<MTLRenderCommandEncoder> metalCmdEncoder = |
| [cmdBuffer().get() renderCommandEncoderWithDescriptor:objCDesc]; |
| |
| set(metalCmdEncoder); |
| |
| // Set the actual store action |
| for (uint32_t i = 0; i < desc.numColorAttachments; ++i) |
| { |
| setColorStoreAction(desc.colorAttachments[i].storeAction, i); |
| } |
| |
| setDepthStencilStoreAction(desc.depthAttachment.storeAction, |
| desc.stencilAttachment.storeAction); |
| |
| // Verify that it was created successfully |
| ASSERT(get()); |
| } // ANGLE_MTL_OBJC_SCOPE |
| |
| return *this; |
| } |
| |
| RenderCommandEncoder &RenderCommandEncoder::setRenderPipelineState(id<MTLRenderPipelineState> state) |
| { |
| [get() setRenderPipelineState:state]; |
| |
| return *this; |
| } |
| RenderCommandEncoder &RenderCommandEncoder::setTriangleFillMode(MTLTriangleFillMode mode) |
| { |
| [get() setTriangleFillMode:mode]; |
| |
| return *this; |
| } |
| RenderCommandEncoder &RenderCommandEncoder::setFrontFacingWinding(MTLWinding winding) |
| { |
| [get() setFrontFacingWinding:winding]; |
| |
| return *this; |
| } |
| RenderCommandEncoder &RenderCommandEncoder::setCullMode(MTLCullMode mode) |
| { |
| [get() setCullMode:mode]; |
| |
| return *this; |
| } |
| |
| RenderCommandEncoder &RenderCommandEncoder::setDepthStencilState(id<MTLDepthStencilState> state) |
| { |
| [get() setDepthStencilState:state]; |
| |
| return *this; |
| } |
| RenderCommandEncoder &RenderCommandEncoder::setDepthBias(float depthBias, |
| float slopeScale, |
| float clamp) |
| { |
| [get() setDepthBias:depthBias slopeScale:slopeScale clamp:clamp]; |
| |
| return *this; |
| } |
| RenderCommandEncoder &RenderCommandEncoder::setStencilRefVals(uint32_t frontRef, uint32_t backRef) |
| { |
| [get() setStencilFrontReferenceValue:frontRef backReferenceValue:backRef]; |
| |
| return *this; |
| } |
| |
| RenderCommandEncoder &RenderCommandEncoder::setStencilRefVal(uint32_t ref) |
| { |
| return setStencilRefVals(ref, ref); |
| } |
| |
| RenderCommandEncoder &RenderCommandEncoder::setViewport(const MTLViewport &viewport) |
| { |
| [get() setViewport:viewport]; |
| |
| return *this; |
| } |
| |
| RenderCommandEncoder &RenderCommandEncoder::setScissorRect(const MTLScissorRect &rect) |
| { |
| [get() setScissorRect:rect]; |
| |
| return *this; |
| } |
| |
| RenderCommandEncoder &RenderCommandEncoder::setBlendColor(float r, float g, float b, float a) |
| { |
| [get() setBlendColorRed:r green:g blue:b alpha:a]; |
| |
| return *this; |
| } |
| |
| RenderCommandEncoder &RenderCommandEncoder::setVertexBuffer(const BufferRef &buffer, |
| uint32_t offset, |
| uint32_t index) |
| { |
| if (index >= kMaxShaderBuffers) |
| { |
| return *this; |
| } |
| |
| cmdBuffer().setReadDependency(buffer); |
| |
| [get() setVertexBuffer:(buffer ? buffer->get() : nil) offset:offset atIndex:index]; |
| |
| return *this; |
| } |
| |
| RenderCommandEncoder &RenderCommandEncoder::setVertexBytes(const uint8_t *bytes, |
| size_t size, |
| uint32_t index) |
| { |
| if (index >= kMaxShaderBuffers) |
| { |
| return *this; |
| } |
| |
| [get() setVertexBytes:bytes length:size atIndex:index]; |
| |
| return *this; |
| } |
| |
| RenderCommandEncoder &RenderCommandEncoder::setVertexSamplerState(id<MTLSamplerState> state, |
| float lodMinClamp, |
| float lodMaxClamp, |
| uint32_t index) |
| { |
| if (index >= kMaxShaderSamplers) |
| { |
| return *this; |
| } |
| |
| [get() setVertexSamplerState:state |
| lodMinClamp:lodMinClamp |
| lodMaxClamp:lodMaxClamp |
| atIndex:index]; |
| |
| return *this; |
| } |
| RenderCommandEncoder &RenderCommandEncoder::setVertexTexture(const TextureRef &texture, |
| uint32_t index) |
| { |
| if (index >= kMaxShaderSamplers) |
| { |
| return *this; |
| } |
| |
| cmdBuffer().setReadDependency(texture); |
| [get() setVertexTexture:(texture ? texture->get() : nil) atIndex:index]; |
| |
| return *this; |
| } |
| |
| RenderCommandEncoder &RenderCommandEncoder::setFragmentBuffer(const BufferRef &buffer, |
| uint32_t offset, |
| uint32_t index) |
| { |
| if (index >= kMaxShaderBuffers) |
| { |
| return *this; |
| } |
| |
| cmdBuffer().setReadDependency(buffer); |
| |
| [get() setFragmentBuffer:(buffer ? buffer->get() : nil) offset:offset atIndex:index]; |
| |
| return *this; |
| } |
| |
| RenderCommandEncoder &RenderCommandEncoder::setFragmentBytes(const uint8_t *bytes, |
| size_t size, |
| uint32_t index) |
| { |
| if (index >= kMaxShaderBuffers) |
| { |
| return *this; |
| } |
| |
| [get() setFragmentBytes:bytes length:size atIndex:index]; |
| |
| return *this; |
| } |
| |
| RenderCommandEncoder &RenderCommandEncoder::setFragmentSamplerState(id<MTLSamplerState> state, |
| float lodMinClamp, |
| float lodMaxClamp, |
| uint32_t index) |
| { |
| if (index >= kMaxShaderSamplers) |
| { |
| return *this; |
| } |
| |
| [get() setFragmentSamplerState:state |
| lodMinClamp:lodMinClamp |
| lodMaxClamp:lodMaxClamp |
| atIndex:index]; |
| |
| return *this; |
| } |
| RenderCommandEncoder &RenderCommandEncoder::setFragmentTexture(const TextureRef &texture, |
| uint32_t index) |
| { |
| if (index >= kMaxShaderSamplers) |
| { |
| return *this; |
| } |
| |
| cmdBuffer().setReadDependency(texture); |
| [get() setFragmentTexture:(texture ? texture->get() : nil) atIndex:index]; |
| |
| return *this; |
| } |
| |
| RenderCommandEncoder &RenderCommandEncoder::draw(MTLPrimitiveType primitiveType, |
| uint32_t vertexStart, |
| uint32_t vertexCount) |
| { |
| [get() drawPrimitives:primitiveType vertexStart:vertexStart vertexCount:vertexCount]; |
| |
| return *this; |
| } |
| |
| RenderCommandEncoder &RenderCommandEncoder::drawInstanced(MTLPrimitiveType primitiveType, |
| uint32_t vertexStart, |
| uint32_t vertexCount, |
| uint32_t instances) |
| { |
| [get() drawPrimitives:primitiveType |
| vertexStart:vertexStart |
| vertexCount:vertexCount |
| instanceCount:instances]; |
| |
| return *this; |
| } |
| |
| RenderCommandEncoder &RenderCommandEncoder::drawIndexed(MTLPrimitiveType primitiveType, |
| uint32_t indexCount, |
| MTLIndexType indexType, |
| const BufferRef &indexBuffer, |
| size_t bufferOffset) |
| { |
| if (!indexBuffer) |
| { |
| return *this; |
| } |
| |
| cmdBuffer().setReadDependency(indexBuffer); |
| [get() drawIndexedPrimitives:primitiveType |
| indexCount:indexCount |
| indexType:indexType |
| indexBuffer:indexBuffer->get() |
| indexBufferOffset:bufferOffset]; |
| |
| return *this; |
| } |
| |
| RenderCommandEncoder &RenderCommandEncoder::drawIndexedInstanced(MTLPrimitiveType primitiveType, |
| uint32_t indexCount, |
| MTLIndexType indexType, |
| const BufferRef &indexBuffer, |
| size_t bufferOffset, |
| uint32_t instances) |
| { |
| if (!indexBuffer) |
| { |
| return *this; |
| } |
| |
| cmdBuffer().setReadDependency(indexBuffer); |
| [get() drawIndexedPrimitives:primitiveType |
| indexCount:indexCount |
| indexType:indexType |
| indexBuffer:indexBuffer->get() |
| indexBufferOffset:bufferOffset |
| instanceCount:instances]; |
| |
| return *this; |
| } |
| |
| RenderCommandEncoder &RenderCommandEncoder::drawIndexedInstancedBaseVertex( |
| MTLPrimitiveType primitiveType, |
| uint32_t indexCount, |
| MTLIndexType indexType, |
| const BufferRef &indexBuffer, |
| size_t bufferOffset, |
| uint32_t instances, |
| uint32_t baseVertex) |
| { |
| if (!indexBuffer) |
| { |
| return *this; |
| } |
| |
| cmdBuffer().setReadDependency(indexBuffer); |
| [get() drawIndexedPrimitives:primitiveType |
| indexCount:indexCount |
| indexType:indexType |
| indexBuffer:indexBuffer->get() |
| indexBufferOffset:bufferOffset |
| instanceCount:instances |
| baseVertex:baseVertex |
| baseInstance:0]; |
| |
| return *this; |
| } |
| |
| RenderCommandEncoder &RenderCommandEncoder::setColorStoreAction(MTLStoreAction action, |
| uint32_t colorAttachmentIndex) |
| { |
| if (colorAttachmentIndex >= mRenderPassDesc.numColorAttachments) |
| { |
| return *this; |
| } |
| |
| // We only store the options, will defer the actual setting until the encoder finishes |
| mRenderPassDesc.colorAttachments[colorAttachmentIndex].storeAction = action; |
| |
| return *this; |
| } |
| |
| RenderCommandEncoder &RenderCommandEncoder::setColorStoreAction(MTLStoreAction action) |
| { |
| for (uint32_t i = 0; i < mRenderPassDesc.numColorAttachments; ++i) |
| { |
| setColorStoreAction(action, i); |
| } |
| return *this; |
| } |
| |
| RenderCommandEncoder &RenderCommandEncoder::setDepthStencilStoreAction( |
| MTLStoreAction depthStoreAction, |
| MTLStoreAction stencilStoreAction) |
| { |
| // We only store the options, will defer the actual setting until the encoder finishes |
| mRenderPassDesc.depthAttachment.storeAction = depthStoreAction; |
| mRenderPassDesc.stencilAttachment.storeAction = stencilStoreAction; |
| |
| return *this; |
| } |
| |
| RenderCommandEncoder &RenderCommandEncoder::setDepthStoreAction(MTLStoreAction action) |
| { |
| // We only store the options, will defer the actual setting until the encoder finishes |
| mRenderPassDesc.depthAttachment.storeAction = action; |
| |
| return *this; |
| } |
| |
| RenderCommandEncoder &RenderCommandEncoder::setStencilStoreAction(MTLStoreAction action) |
| { |
| // We only store the options, will defer the actual setting until the encoder finishes |
| mRenderPassDesc.stencilAttachment.storeAction = action; |
| |
| return *this; |
| } |
| |
| // BlitCommandEncoder |
| BlitCommandEncoder::BlitCommandEncoder(CommandBuffer *cmdBuffer) : CommandEncoder(cmdBuffer, BLIT) |
| {} |
| |
| BlitCommandEncoder::~BlitCommandEncoder() {} |
| |
| BlitCommandEncoder &BlitCommandEncoder::restart() |
| { |
| ANGLE_MTL_OBJC_SCOPE |
| { |
| if (valid()) |
| { |
| // no change, skip |
| return *this; |
| } |
| |
| if (!cmdBuffer().valid()) |
| { |
| reset(); |
| return *this; |
| } |
| |
| // Create objective C object |
| set([cmdBuffer().get() blitCommandEncoder]); |
| |
| // Verify that it was created successfully |
| ASSERT(get()); |
| |
| return *this; |
| } |
| } |
| |
| BlitCommandEncoder &BlitCommandEncoder::copyTexture(const TextureRef &dst, |
| uint32_t dstSlice, |
| uint32_t dstLevel, |
| MTLOrigin dstOrigin, |
| MTLSize dstSize, |
| const TextureRef &src, |
| uint32_t srcSlice, |
| uint32_t srcLevel, |
| MTLOrigin srcOrigin) |
| { |
| if (!src || !dst) |
| { |
| return *this; |
| } |
| |
| cmdBuffer().setReadDependency(src); |
| cmdBuffer().setWriteDependency(dst); |
| [get() copyFromTexture:src->get() |
| sourceSlice:srcSlice |
| sourceLevel:srcLevel |
| sourceOrigin:srcOrigin |
| sourceSize:dstSize |
| toTexture:dst->get() |
| destinationSlice:dstSlice |
| destinationLevel:dstLevel |
| destinationOrigin:dstOrigin]; |
| |
| return *this; |
| } |
| |
| BlitCommandEncoder &BlitCommandEncoder::generateMipmapsForTexture(const TextureRef &texture) |
| { |
| if (!texture) |
| { |
| return *this; |
| } |
| |
| cmdBuffer().setWriteDependency(texture); |
| [get() generateMipmapsForTexture:texture->get()]; |
| |
| return *this; |
| } |
| BlitCommandEncoder &BlitCommandEncoder::synchronizeResource(const TextureRef &texture) |
| { |
| if (!texture) |
| { |
| return *this; |
| } |
| |
| #if TARGET_OS_OSX || TARGET_OS_MACCATALYST |
| // Only MacOS has separated storage for resource on CPU and GPU and needs explicit |
| // synchronization |
| cmdBuffer().setWriteDependency(texture); |
| [get() synchronizeResource:texture->get()]; |
| #endif |
| return *this; |
| } |
| |
| // ComputeCommandEncoder implementation |
| ComputeCommandEncoder::ComputeCommandEncoder(CommandBuffer *cmdBuffer) |
| : CommandEncoder(cmdBuffer, COMPUTE) |
| {} |
| ComputeCommandEncoder::~ComputeCommandEncoder() {} |
| |
| ComputeCommandEncoder &ComputeCommandEncoder::restart() |
| { |
| ANGLE_MTL_OBJC_SCOPE |
| { |
| if (valid()) |
| { |
| // no change, skip |
| return *this; |
| } |
| |
| if (!cmdBuffer().valid()) |
| { |
| reset(); |
| return *this; |
| } |
| |
| // Create objective C object |
| set([cmdBuffer().get() computeCommandEncoder]); |
| |
| // Verify that it was created successfully |
| ASSERT(get()); |
| |
| return *this; |
| } |
| } |
| |
| ComputeCommandEncoder &ComputeCommandEncoder::setComputePipelineState( |
| id<MTLComputePipelineState> state) |
| { |
| [get() setComputePipelineState:state]; |
| return *this; |
| } |
| |
| ComputeCommandEncoder &ComputeCommandEncoder::setBuffer(const BufferRef &buffer, |
| uint32_t offset, |
| uint32_t index) |
| { |
| if (index >= kMaxShaderBuffers) |
| { |
| return *this; |
| } |
| |
| // NOTE(hqle): Assume compute shader both reads and writes to this buffer for now. |
| cmdBuffer().setReadDependency(buffer); |
| cmdBuffer().setWriteDependency(buffer); |
| |
| [get() setBuffer:(buffer ? buffer->get() : nil) offset:offset atIndex:index]; |
| |
| return *this; |
| } |
| |
| ComputeCommandEncoder &ComputeCommandEncoder::setBytes(const uint8_t *bytes, |
| size_t size, |
| uint32_t index) |
| { |
| if (index >= kMaxShaderBuffers) |
| { |
| return *this; |
| } |
| |
| [get() setBytes:bytes length:size atIndex:index]; |
| |
| return *this; |
| } |
| |
| ComputeCommandEncoder &ComputeCommandEncoder::setSamplerState(id<MTLSamplerState> state, |
| float lodMinClamp, |
| float lodMaxClamp, |
| uint32_t index) |
| { |
| if (index >= kMaxShaderSamplers) |
| { |
| return *this; |
| } |
| |
| [get() setSamplerState:state lodMinClamp:lodMinClamp lodMaxClamp:lodMaxClamp atIndex:index]; |
| |
| return *this; |
| } |
| ComputeCommandEncoder &ComputeCommandEncoder::setTexture(const TextureRef &texture, uint32_t index) |
| { |
| if (index >= kMaxShaderSamplers) |
| { |
| return *this; |
| } |
| |
| // NOTE(hqle): Assume compute shader both reads and writes to this texture for now. |
| cmdBuffer().setReadDependency(texture); |
| cmdBuffer().setWriteDependency(texture); |
| [get() setTexture:(texture ? texture->get() : nil) atIndex:index]; |
| |
| return *this; |
| } |
| |
| ComputeCommandEncoder &ComputeCommandEncoder::dispatch(MTLSize threadGroupsPerGrid, |
| MTLSize threadsPerGroup) |
| { |
| [get() dispatchThreadgroups:threadGroupsPerGrid threadsPerThreadgroup:threadsPerGroup]; |
| return *this; |
| } |
| |
| ComputeCommandEncoder &ComputeCommandEncoder::dispatchNonUniform(MTLSize threadsPerGrid, |
| MTLSize threadsPerGroup) |
| { |
| [get() dispatchThreads:threadsPerGrid threadsPerThreadgroup:threadsPerGroup]; |
| return *this; |
| } |
| |
| } |
| } |