| /* |
| * Copyright (C) 2018 Apple Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' |
| * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, |
| * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS |
| * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF |
| * THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #import "config.h" |
| #import "GPURenderPassEncoder.h" |
| |
| #if ENABLE(WEBGPU) |
| |
| #import "GPUBuffer.h" |
| #import "GPUColor.h" |
| #import "GPUCommandBuffer.h" |
| #import "GPURenderPassDescriptor.h" |
| #import "GPURenderPipeline.h" |
| #import "Logging.h" |
| #import "WHLSLVertexBufferIndexCalculator.h" |
| #import <Foundation/Foundation.h> |
| #import <Metal/Metal.h> |
| #import <wtf/BlockObjCExceptions.h> |
| #import <wtf/CheckedArithmetic.h> |
| |
| namespace WebCore { |
| |
| static MTLLoadAction loadActionForGPULoadOp(GPULoadOp op) |
| { |
| switch (op) { |
| case GPULoadOp::Clear: |
| return MTLLoadActionClear; |
| case GPULoadOp::Load: |
| return MTLLoadActionLoad; |
| } |
| |
| ASSERT_NOT_REACHED(); |
| } |
| |
| static MTLStoreAction storeActionForGPUStoreOp(GPUStoreOp op) |
| { |
| switch (op) { |
| case GPUStoreOp::Store: |
| return MTLStoreActionStore; |
| } |
| |
| ASSERT_NOT_REACHED(); |
| } |
| |
| static bool populateMtlColorAttachmentsArray(MTLRenderPassColorAttachmentDescriptorArray *array, const Vector<GPURenderPassColorAttachmentDescriptor>& descriptors, const char* const functionName) |
| { |
| #if LOG_DISABLED |
| UNUSED_PARAM(functionName); |
| #endif |
| |
| for (unsigned i = 0; i < descriptors.size(); ++i) { |
| const auto& descriptor = descriptors[i]; |
| if (!descriptor.attachment->platformTexture()) { |
| LOG(WebGPU, "%s: Invalid MTLTexture for color attachment %u!", functionName, i); |
| return false; |
| } |
| const auto& color = descriptor.clearColor; |
| |
| BEGIN_BLOCK_OBJC_EXCEPTIONS; |
| |
| auto mtlAttachment = retainPtr([array objectAtIndexedSubscript:i]); |
| [mtlAttachment setTexture:descriptor.attachment->platformTexture()]; |
| [mtlAttachment setClearColor:MTLClearColorMake(color.r, color.g, color.b, color.a)]; |
| [mtlAttachment setLoadAction:loadActionForGPULoadOp(descriptor.loadOp)]; |
| [mtlAttachment setStoreAction:storeActionForGPUStoreOp(descriptor.storeOp)]; |
| |
| END_BLOCK_OBJC_EXCEPTIONS; |
| } |
| |
| return true; |
| } |
| |
| static bool populateMtlDepthStencilAttachment(MTLRenderPassDepthAttachmentDescriptor *mtlAttachment, const GPURenderPassDepthStencilAttachmentDescriptor& descriptor, const char* const functionName) |
| { |
| #if LOG_DISABLED |
| UNUSED_PARAM(functionName); |
| #endif |
| |
| if (!descriptor.attachment->platformTexture()) { |
| LOG(WebGPU, "%s: Invalid MTLTexture for depth attachment!", functionName); |
| return false; |
| } |
| |
| BEGIN_BLOCK_OBJC_EXCEPTIONS; |
| |
| [mtlAttachment setTexture:descriptor.attachment->platformTexture()]; |
| [mtlAttachment setClearDepth:descriptor.clearDepth]; |
| [mtlAttachment setLoadAction:loadActionForGPULoadOp(descriptor.depthLoadOp)]; |
| [mtlAttachment setStoreAction:storeActionForGPUStoreOp(descriptor.depthStoreOp)]; |
| |
| END_BLOCK_OBJC_EXCEPTIONS; |
| |
| return true; |
| } |
| |
| static void useAttachments(GPUCommandBuffer& buffer, GPURenderPassDescriptor&& descriptor) |
| { |
| for (auto& colorAttachment : descriptor.colorAttachments) |
| buffer.useTexture(WTFMove(colorAttachment.attachment)); |
| if (descriptor.depthStencilAttachment) |
| buffer.useTexture(WTFMove((*descriptor.depthStencilAttachment).attachment)); |
| } |
| |
| RefPtr<GPURenderPassEncoder> GPURenderPassEncoder::tryCreate(Ref<GPUCommandBuffer>&& buffer, GPURenderPassDescriptor&& descriptor) |
| { |
| const char* const functionName = "GPURenderPassEncoder::tryCreate()"; |
| |
| // Only one command encoder may be active at a time. |
| if (buffer->isEncodingPass()) { |
| LOG(WebGPU, "%s: Existing pass encoder must be ended first!"); |
| return nullptr; |
| } |
| |
| RetainPtr<MTLRenderPassDescriptor> mtlDescriptor; |
| |
| BEGIN_BLOCK_OBJC_EXCEPTIONS; |
| |
| mtlDescriptor = adoptNS([MTLRenderPassDescriptor new]); |
| |
| END_BLOCK_OBJC_EXCEPTIONS; |
| |
| if (!mtlDescriptor) { |
| LOG(WebGPU, "%s: Unable to create MTLRenderPassDescriptor!", functionName); |
| return nullptr; |
| } |
| |
| if (!populateMtlColorAttachmentsArray(mtlDescriptor.get().colorAttachments, descriptor.colorAttachments, functionName)) |
| return nullptr; |
| |
| if (descriptor.depthStencilAttachment |
| && !populateMtlDepthStencilAttachment(mtlDescriptor.get().depthAttachment, *descriptor.depthStencilAttachment, functionName)) |
| return nullptr; |
| |
| buffer->endBlitEncoding(); |
| |
| RetainPtr<MTLRenderCommandEncoder> mtlEncoder; |
| |
| BEGIN_BLOCK_OBJC_EXCEPTIONS; |
| |
| mtlEncoder = [buffer->platformCommandBuffer() renderCommandEncoderWithDescriptor:mtlDescriptor.get()]; |
| |
| END_BLOCK_OBJC_EXCEPTIONS; |
| |
| if (!mtlEncoder) { |
| LOG(WebGPU, "%s: Unable to create MTLRenderCommandEncoder!", functionName); |
| return nullptr; |
| } |
| |
| // All is well; ensure GPUCommandBuffer is aware of new attachments. |
| useAttachments(buffer, WTFMove(descriptor)); |
| |
| return adoptRef(new GPURenderPassEncoder(WTFMove(buffer), WTFMove(mtlEncoder))); |
| } |
| |
| GPURenderPassEncoder::GPURenderPassEncoder(Ref<GPUCommandBuffer>&& commandBuffer, RetainPtr<MTLRenderCommandEncoder>&& encoder) |
| : GPUProgrammablePassEncoder(WTFMove(commandBuffer)) |
| , m_platformRenderPassEncoder(WTFMove(encoder)) |
| { |
| } |
| |
| const MTLCommandEncoder *GPURenderPassEncoder::platformPassEncoder() const |
| { |
| return m_platformRenderPassEncoder.get(); |
| } |
| |
| void GPURenderPassEncoder::setPipeline(Ref<const GPURenderPipeline>&& pipeline) |
| { |
| if (!m_platformRenderPassEncoder) { |
| LOG(WebGPU, "GPURenderPassEncoder::setPipeline(): Invalid operation: Encoding is ended!"); |
| return; |
| } |
| |
| // FIXME: Metal throws an error if the MTLPipelineState's attachment formats do not match the MTLCommandEncoder's attachment formats. |
| |
| BEGIN_BLOCK_OBJC_EXCEPTIONS; |
| |
| if (pipeline->depthStencilState()) |
| [m_platformRenderPassEncoder setDepthStencilState:pipeline->depthStencilState()]; |
| |
| [m_platformRenderPassEncoder setRenderPipelineState:pipeline->platformRenderPipeline()]; |
| |
| END_BLOCK_OBJC_EXCEPTIONS; |
| |
| m_pipeline = WTFMove(pipeline); |
| } |
| |
| void GPURenderPassEncoder::setBlendColor(const GPUColor& color) |
| { |
| if (!m_platformRenderPassEncoder) { |
| LOG(WebGPU, "GPURenderPassEncoder::setBlendColor(): Invalid operation: Encoding is ended!"); |
| return; |
| } |
| |
| BEGIN_BLOCK_OBJC_EXCEPTIONS; |
| [m_platformRenderPassEncoder setBlendColorRed:color.r green:color.g blue:color.b alpha:color.a]; |
| END_BLOCK_OBJC_EXCEPTIONS; |
| } |
| |
| void GPURenderPassEncoder::setViewport(float x, float y, float width, float height, float minDepth, float maxDepth) |
| { |
| if (!m_platformRenderPassEncoder) { |
| LOG(WebGPU, "GPURenderPassEncoder::setViewport(): Invalid operation: Encoding is ended!"); |
| return; |
| } |
| |
| BEGIN_BLOCK_OBJC_EXCEPTIONS; |
| [m_platformRenderPassEncoder setViewport: { x, y, width, height, minDepth, maxDepth }]; |
| END_BLOCK_OBJC_EXCEPTIONS; |
| } |
| |
| void GPURenderPassEncoder::setScissorRect(unsigned x, unsigned y, unsigned width, unsigned height) |
| { |
| if (!m_platformRenderPassEncoder) { |
| LOG(WebGPU, "GPURenderPassEncoder::setScissorRect(): Invalid operation: Encoding is ended!"); |
| return; |
| } |
| |
| BEGIN_BLOCK_OBJC_EXCEPTIONS; |
| [m_platformRenderPassEncoder setScissorRect: { x, y, width, height }]; |
| END_BLOCK_OBJC_EXCEPTIONS; |
| } |
| |
| void GPURenderPassEncoder::setIndexBuffer(GPUBuffer& buffer, uint64_t offset) |
| { |
| if (!m_platformRenderPassEncoder) { |
| LOG(WebGPU, "GPURenderPassEncoder::setIndexBuffer(): Invalid operation: Encoding is ended!"); |
| return; |
| } |
| if (offset >= buffer.byteLength() || offset % 4) { |
| LOG(WebGPU, "GPURenderPassEncoder::setIndexBuffer(): Invalid offset!"); |
| return; |
| } |
| ASSERT(buffer.platformBuffer()); |
| // Buffer must be cached to provide it to Metal via drawIndexedPrimitives. |
| m_indexBuffer = makeRefPtr(buffer); |
| m_indexBufferOffset = offset; |
| } |
| |
| void GPURenderPassEncoder::setVertexBuffers(unsigned index, const Vector<Ref<GPUBuffer>>& buffers, const Vector<uint64_t>& offsets) |
| { |
| if (!m_platformRenderPassEncoder) { |
| LOG(WebGPU, "GPURenderPassEncoder::setVertexBuffers(): Invalid operation: Encoding is ended!"); |
| return; |
| } |
| |
| ASSERT(buffers.size() && offsets.size() == buffers.size()); |
| |
| BEGIN_BLOCK_OBJC_EXCEPTIONS; |
| |
| auto mtlBuffers = buffers.map([this] (auto& buffer) { |
| commandBuffer().useBuffer(buffer.copyRef()); |
| ASSERT(buffer->platformBuffer()); |
| return buffer->platformBuffer(); |
| }); |
| |
| auto indexRanges = NSMakeRange(WHLSL::Metal::calculateVertexBufferIndex(index), buffers.size()); |
| |
| [m_platformRenderPassEncoder setVertexBuffers:mtlBuffers.data() offsets:(const NSUInteger *)offsets.data() withRange:indexRanges]; |
| |
| END_BLOCK_OBJC_EXCEPTIONS; |
| } |
| |
| static MTLPrimitiveType mtlPrimitiveTypeForGPUPrimitiveTopology(GPUPrimitiveTopology type) |
| { |
| switch (type) { |
| case GPUPrimitiveTopology::PointList: |
| return MTLPrimitiveTypePoint; |
| case GPUPrimitiveTopology::LineList: |
| return MTLPrimitiveTypeLine; |
| case GPUPrimitiveTopology::LineStrip: |
| return MTLPrimitiveTypeLineStrip; |
| case GPUPrimitiveTopology::TriangleList: |
| return MTLPrimitiveTypeTriangle; |
| case GPUPrimitiveTopology::TriangleStrip: |
| return MTLPrimitiveTypeTriangleStrip; |
| } |
| |
| ASSERT_NOT_REACHED(); |
| } |
| |
| void GPURenderPassEncoder::draw(unsigned vertexCount, unsigned instanceCount, unsigned firstVertex, unsigned firstInstance) |
| { |
| if (!m_platformRenderPassEncoder) { |
| LOG(WebGPU, "GPURenderPassEncoder::draw(): Invalid operation: Encoding is ended!"); |
| return; |
| } |
| if (!m_pipeline) { |
| LOG(WebGPU, "GPURenderPassEncoder::draw(): No valid GPURenderPipeline found!"); |
| return; |
| } |
| |
| BEGIN_BLOCK_OBJC_EXCEPTIONS; |
| [m_platformRenderPassEncoder |
| drawPrimitives:mtlPrimitiveTypeForGPUPrimitiveTopology(m_pipeline->primitiveTopology()) |
| vertexStart:firstVertex |
| vertexCount:vertexCount |
| instanceCount:instanceCount |
| baseInstance:firstInstance]; |
| END_BLOCK_OBJC_EXCEPTIONS; |
| } |
| |
| static MTLIndexType mtlIndexTypeForGPUIndexFormat(GPUIndexFormat format) |
| { |
| switch (format) { |
| case GPUIndexFormat::Uint16: |
| return MTLIndexTypeUInt16; |
| case GPUIndexFormat::Uint32: |
| return MTLIndexTypeUInt32; |
| } |
| |
| ASSERT_NOT_REACHED(); |
| } |
| |
| void GPURenderPassEncoder::drawIndexed(unsigned indexCount, unsigned instanceCount, unsigned firstIndex, int baseVertex, unsigned firstInstance) |
| { |
| #if !LOG_DISABLED |
| const char* const functionName = "GPURenderPassEncoder::drawIndexed()"; |
| #endif |
| if (!m_platformRenderPassEncoder) { |
| LOG(WebGPU, "%s: Invalid operation: Encoding is ended!", functionName); |
| return; |
| } |
| if (!m_pipeline) { |
| LOG(WebGPU, "%s: No valid GPURenderPipeline found!", functionName); |
| return; |
| } |
| if (!m_pipeline->indexFormat()) { |
| LOG(WebGPU, "%s: No GPUIndexFormat specified!", functionName); |
| return; |
| } |
| if (!m_indexBuffer || !m_indexBuffer->platformBuffer()) { |
| LOG(WebGPU, "%s: No valid index buffer set!", functionName); |
| return; |
| } |
| |
| auto indexByteSize = (m_pipeline->indexFormat() == GPUIndexFormat::Uint16) ? sizeof(uint16_t) : sizeof(uint32_t); |
| |
| // This calculation cannot overflow as firstIndex is bounded to 32 bits, and indexByteSize to sizeof(uint32_t). |
| uint64_t firstIndexOffset = firstIndex * indexByteSize; |
| |
| // This call ensures that neither argument nor their sum will overflow NSUInteger. |
| auto totalOffset = checkedSum<NSUInteger>(firstIndexOffset, m_indexBufferOffset); |
| if (totalOffset.hasOverflowed() || totalOffset >= m_indexBuffer->byteLength()) { |
| LOG(WebGPU, "%s: Invalid firstIndex!", functionName); |
| return; |
| } |
| |
| commandBuffer().useBuffer(makeRef(*m_indexBuffer)); |
| |
| BEGIN_BLOCK_OBJC_EXCEPTIONS; |
| [m_platformRenderPassEncoder |
| drawIndexedPrimitives:mtlPrimitiveTypeForGPUPrimitiveTopology(m_pipeline->primitiveTopology()) |
| indexCount:indexCount |
| indexType:mtlIndexTypeForGPUIndexFormat(*m_pipeline->indexFormat()) |
| indexBuffer:m_indexBuffer->platformBuffer() |
| indexBufferOffset:totalOffset.unsafeGet() |
| instanceCount:instanceCount |
| baseVertex:baseVertex |
| baseInstance:firstInstance]; |
| END_BLOCK_OBJC_EXCEPTIONS; |
| } |
| |
| #if USE(METAL) |
| |
| void GPURenderPassEncoder::useResource(const MTLResource *resource, unsigned usage) |
| { |
| ASSERT(m_platformRenderPassEncoder); |
| |
| BEGIN_BLOCK_OBJC_EXCEPTIONS; |
| [m_platformRenderPassEncoder useResource:resource usage:usage]; |
| END_BLOCK_OBJC_EXCEPTIONS; |
| } |
| |
| void GPURenderPassEncoder::setVertexBuffer(const MTLBuffer *buffer, unsigned offset, unsigned index) |
| { |
| ASSERT(m_platformRenderPassEncoder); |
| |
| BEGIN_BLOCK_OBJC_EXCEPTIONS; |
| [m_platformRenderPassEncoder setVertexBuffer:buffer offset:offset atIndex:index]; |
| END_BLOCK_OBJC_EXCEPTIONS; |
| } |
| |
| void GPURenderPassEncoder::setFragmentBuffer(const MTLBuffer *buffer, unsigned offset, unsigned index) |
| { |
| ASSERT(m_platformRenderPassEncoder); |
| |
| BEGIN_BLOCK_OBJC_EXCEPTIONS; |
| [m_platformRenderPassEncoder setFragmentBuffer:buffer offset:offset atIndex:index]; |
| END_BLOCK_OBJC_EXCEPTIONS; |
| } |
| |
| #endif // USE(METAL) |
| |
| } // namespace WebCore |
| |
| #endif // ENABLE(WEBGPU) |