| // Copyright (C) 2021-2024 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. |
| |
| private import CxxStdlib |
| import Metal |
| import WebGPU_Internal.Buffer |
| import WebGPU_Internal.CommandEncoder |
| import WebGPU_Internal.CxxBridging |
| import WebGPU_Internal.QuerySet |
| import WebGPU_Internal.TextureOrTextureView |
| import WebGPU_Private.WebGPU |
| |
| typealias String = Swift.String |
| |
| @_expose(Cxx) |
| func commandEncoderClearBuffer( |
| _ commandEncoder: WebGPU.CommandEncoder, |
| buffer: WebGPU.Buffer, |
| offset: UInt64, |
| size: inout UInt64 |
| ) { |
| commandEncoder.clearBuffer(buffer: buffer, offset: offset, size: &size) |
| } |
| |
| @_expose(Cxx) |
| func commandEncoderResolveQuerySet( |
| _ commandEncoder: WebGPU.CommandEncoder, |
| querySet: WebGPU.QuerySet, |
| firstQuery: UInt32, |
| queryCount: UInt32, |
| destination: WebGPU.Buffer, |
| destinationOffset: UInt64 |
| ) { |
| commandEncoder.resolveQuerySet( |
| querySet, |
| firstQuery: firstQuery, |
| queryCount: queryCount, |
| destination: destination, |
| destinationOffset: destinationOffset |
| ) |
| } |
| |
| @_expose(Cxx) |
| func commandEncoderCopyBufferToTexture( |
| _ commandEncoder: WebGPU.CommandEncoder, |
| source: WGPUImageCopyBuffer, |
| destination: WGPUImageCopyTexture, |
| copySize: WGPUExtent3D |
| ) { |
| commandEncoder.copyBufferToTexture(source: source, destination: destination, copySize: copySize) |
| } |
| |
| @_expose(Cxx) |
| func commandEncoderCopyTextureToBuffer( |
| commandEncoder: WebGPU.CommandEncoder, |
| source: WGPUImageCopyTexture, |
| destination: WGPUImageCopyBuffer, |
| copySize: WGPUExtent3D |
| ) { |
| commandEncoder.copyTextureToBuffer(source: source, destination: destination, copySize: copySize) |
| } |
| |
| @_expose(Cxx) |
| func commandEncoderCopyTextureToTexture( |
| _ commandEncoder: WebGPU.CommandEncoder, |
| source: WGPUImageCopyTexture, |
| destination: WGPUImageCopyTexture, |
| copySize: WGPUExtent3D |
| ) { |
| commandEncoder.copyTextureToTexture(source: source, destination: destination, copySize: copySize) |
| } |
| |
| @_expose(Cxx) |
| func commandEncoderCopyBufferToBuffer( |
| _ commandEncoder: WebGPU.CommandEncoder, |
| source: WebGPU.Buffer, |
| sourceOffset: UInt64, |
| destination: WebGPU.Buffer, |
| destinationOffset: UInt64, |
| size: UInt64 |
| ) { |
| commandEncoder.copyBufferToBuffer( |
| source: source, |
| sourceOffset: sourceOffset, |
| destination: destination, |
| destinationOffset: destinationOffset, |
| size: size |
| ) |
| } |
| |
| @_expose(Cxx) |
| func commandEncoderBeginRenderPass( |
| _ commandEncoder: WebGPU.CommandEncoder, |
| descriptor: WGPURenderPassDescriptor, |
| ) -> CxxBridging.RefRenderPassEncoder { |
| commandEncoder.beginRenderPass(descriptor: descriptor) |
| } |
| |
| @_expose(Cxx) |
| func commandEncoderBeginComputePass( |
| _ commandEncoder: WebGPU.CommandEncoder, |
| descriptor: WGPUComputePassDescriptor, |
| ) -> CxxBridging.RefComputePassEncoder { |
| commandEncoder.beginComputePass(descriptor: descriptor) |
| } |
| |
| @_expose(Cxx) |
| func commandEncoderRunClearEncoder( |
| _ commandEncoder: WebGPU.CommandEncoder, |
| attachmentsToClear: NSMutableDictionary, |
| depthStencilAttachmentToClear: inout (any MTLTexture)?, |
| depthAttachmentToClear: Bool, |
| stencilAttachmentToClear: Bool, |
| depthClearValue: Double, |
| stencilClearValue: UInt32, |
| existingEncoder: (any MTLRenderCommandEncoder)? |
| ) { |
| guard let dInput = attachmentsToClear as? [NSNumber: TextureAndClearColor] else { |
| preconditionFailure("Dictionary not convertible") |
| } |
| |
| return commandEncoder.runClearEncoder( |
| attachmentsToClear: dInput, |
| depthStencilAttachmentToClear: &depthStencilAttachmentToClear, |
| depthAttachmentToClear: depthAttachmentToClear, |
| stencilAttachmentToClear: stencilAttachmentToClear, |
| depthClearValue: depthClearValue, |
| stencilClearValue: stencilClearValue, |
| existingEncoder: existingEncoder |
| ) |
| } |
| |
| @_expose(Cxx) |
| func commandEncoderClearTextureIfNeeded(_ commandEncoder: WebGPU.CommandEncoder, destination: WGPUImageCopyTexture, slice: UInt) { |
| commandEncoder.clearTextureIfNeeded(destination: destination, slice: slice) |
| } |
| |
| @_expose(Cxx) |
| func commandEncoderFinish( |
| _ commandEncoder: WebGPU.CommandEncoder, |
| descriptor: WGPUCommandBufferDescriptor |
| ) -> CxxBridging.RefCommandBuffer { |
| commandEncoder.finish(descriptor: descriptor) |
| } |
| |
| extension WebGPU.TextureOrTextureView { |
| init(_ attachment: WGPURenderPassColorAttachment?) { |
| if let view = attachment?.view { |
| self.init(WebGPU.fromAPI(view)) |
| return |
| } |
| |
| if let texture = attachment?.texture { |
| self.init(WebGPU.fromAPI(texture)) |
| return |
| } |
| |
| fatalError() |
| } |
| |
| init(_ attachment: WGPURenderPassDepthStencilAttachment?) { |
| if let view = attachment?.view { |
| self.init(WebGPU.fromAPI(view)) |
| return |
| } |
| |
| if let texture = attachment?.texture { |
| self.init(WebGPU.fromAPI(texture)) |
| return |
| } |
| |
| fatalError() |
| } |
| } |
| |
| extension WebGPU.CommandEncoder { |
| private func validateFinishError() -> String? { |
| if !isValid() { |
| return "GPUCommandEncoder.finish: encoder is not valid" |
| } |
| |
| if getEncoderState() != WebGPU.CommandsMixin.EncoderState.Open { |
| return "GPUCommandEncoder.finish: encoder state is '\(encoderStateName() ?? "")', expected 'Open'" |
| } |
| |
| if m_debugGroupStackSize != 0 { |
| return "GPUCommandEncoder.finish: encoder stack size '\(m_debugGroupStackSize)'" |
| } |
| |
| // FIXME: "Every usage scope contained in this must satisfy the usage scope validation." |
| |
| return nil |
| } |
| |
| func finish(descriptor: WGPUCommandBufferDescriptor) -> CxxBridging.RefCommandBuffer { |
| if !isValid() || (m_existingCommandEncoder != nil && m_existingCommandEncoder !== m_blitCommandEncoder) { |
| setEncoderState(WebGPU.CommandsMixin.EncoderState.Ended) |
| discardCommandBuffer() |
| m_device.ptr() |
| .generateAValidationError(m_lastErrorString != nil ? m_lastErrorString! as String : String("Invalid CommandEncoder")) |
| return WebGPU.CommandBuffer.createInvalid(m_device.ptr()) |
| } |
| |
| // https://gpuweb.github.io/gpuweb/#dom-gpucommandencoder-finish |
| let validationFailedError = validateFinishError() |
| |
| _ = getEncoderState() |
| |
| setEncoderState(WebGPU.CommandsMixin.EncoderState.Ended) |
| if validationFailedError != nil { |
| discardCommandBuffer() |
| m_device.ptr() |
| .generateAValidationError(m_lastErrorString != nil ? m_lastErrorString! as String : validationFailedError) |
| return WebGPU.CommandBuffer.createInvalid(m_device.ptr()) |
| } |
| |
| finalizeBlitCommandEncoder() |
| |
| let commandBuffer = m_commandBuffer |
| m_commandBuffer = nil |
| m_existingCommandEncoder = nil |
| |
| commandBuffer?.label = CxxBridging.convertWTFStringToNSString(descriptor.label) |
| |
| #if arch(x86_64) && (WTF_PLATFORM_MAC || WTF_PLATFORM_MACCATALYST) |
| if (m_managedBuffers?.count ?? 0) != 0 || (m_managedTextures?.count ?? 0) != 0 { |
| let blitCommandEncoder = commandBuffer?.makeBlitCommandEncoder() |
| |
| // swift-format-ignore: AlwaysUseLowerCamelCase |
| if let m_managedBuffers { |
| for buffer in m_managedBuffers { |
| // FIXME: `NSSet` should not be used, and then this cast will not be needed. |
| // This is safe because `m_managedBuffers` is a `NSMutableSet<id<MTLBuffer>>`. |
| // swift-format-ignore: NeverForceUnwrap |
| blitCommandEncoder?.synchronize(resource: buffer as! any MTLBuffer) |
| } |
| } |
| |
| // swift-format-ignore: AlwaysUseLowerCamelCase |
| if let m_managedTextures { |
| for texture in m_managedTextures { |
| // FIXME: `NSSet` should not be used, and then this cast will not be needed. |
| // This is safe because `m_managedTextures` is a `NSMutableSet<id<MTLTexture>>`. |
| // swift-format-ignore: NeverForceUnwrap |
| blitCommandEncoder?.synchronize(resource: texture as! any MTLTexture) |
| } |
| } |
| |
| blitCommandEncoder?.endEncoding() |
| } |
| #endif // arch(x86_64) && (WTF_PLATFORM_MAC || WTF_PLATFORM_MACCATALYST) |
| |
| let result = createCommandBuffer(commandBuffer, m_device.ptr(), m_sharedEvent, m_sharedEventSignalValue) |
| m_sharedEvent = nil |
| m_cachedCommandBuffer = CxxBridging.commandBufferThreadSafeWeakPtr(result.ptr()) |
| result.ptr().setBufferMapCount(m_bufferMapCount) |
| if m_makeSubmitInvalid { |
| result.ptr().makeInvalid(m_lastErrorString as? String ?? "Invalid CommandEncoder") |
| } |
| |
| return result |
| } |
| |
| fileprivate func clearTextureIfNeeded(destination: WGPUImageCopyTexture, slice: UInt) { |
| WebGPU.CommandEncoder.clearTextureIfNeeded(destination, slice, m_device.ptr(), m_blitCommandEncoder) |
| } |
| |
| private func clearTextureIfNeeded( |
| destination: WGPUImageCopyTexture, |
| slice: UInt, |
| device: WebGPU.Device, |
| blitCommandEncoder: (any MTLBlitCommandEncoder)? |
| ) { |
| let texture = WebGPU.fromAPI(destination.texture) |
| let mipLevel = UInt(destination.mipLevel) |
| clearTextureIfNeeded(texture, mipLevel, slice, device, blitCommandEncoder) |
| } |
| |
| private func clearTextureIfNeeded( |
| _ texture: WebGPU.Texture, |
| _ mipLevel: UInt, |
| _ slice: UInt, |
| _ device: WebGPU.Device, |
| _ blitCommandEncoder: (any MTLBlitCommandEncoder)? |
| ) { |
| if blitCommandEncoder == nil || texture.previouslyCleared(UInt32(mipLevel), UInt32(slice)) { |
| return |
| } |
| |
| texture.setPreviouslyCleared(UInt32(mipLevel), UInt32(slice), true) |
| let logicalExtent = texture.logicalMiplevelSpecificTextureExtent(UInt32(mipLevel)) |
| if logicalExtent.width == 0 { |
| return |
| } |
| if texture.dimension() != WGPUTextureDimension_1D && logicalExtent.height == 0 { |
| return |
| } |
| if texture.dimension() == WGPUTextureDimension_3D && logicalExtent.depthOrArrayLayers == 0 { |
| return |
| } |
| |
| guard let mtlTexture = texture.texture() else { |
| return |
| } |
| |
| var textureFormat = texture.format() |
| if mtlTexture.pixelFormat == .depth32Float_stencil8 || mtlTexture.pixelFormat == .x32_stencil8 { |
| textureFormat = WGPUTextureFormat_Depth32Float |
| } |
| |
| let physicalExtent = WebGPU.Texture.physicalTextureExtent(texture.dimension(), textureFormat, logicalExtent) |
| var sourceBytesPerRow = WebGPU.Texture.bytesPerRow(textureFormat, physicalExtent.width, texture.sampleCount()) |
| let depth = UInt(texture.dimension() == WGPUTextureDimension_3D ? physicalExtent.depthOrArrayLayers : 1) |
| var didOverflow = false |
| var checkedBytesPerImage = sourceBytesPerRow |
| (checkedBytesPerImage, didOverflow) = sourceBytesPerRow.multipliedReportingOverflow(by: UInt(physicalExtent.height)) |
| if didOverflow { |
| return |
| } |
| let bytesPerImage = checkedBytesPerImage |
| var bufferLength = bytesPerImage |
| (bufferLength, didOverflow) = bufferLength.multipliedReportingOverflow(by: depth) |
| if didOverflow { |
| return |
| } |
| if bufferLength == 0 { |
| return |
| } |
| |
| guard let temporaryBuffer = device.safeCreateBuffer(bufferLength) else { |
| return |
| } |
| |
| var sourceSize: MTLSize |
| var sourceBytesPerImage: UInt = 0 |
| var mutableSlice = slice |
| switch texture.dimension() { |
| case WGPUTextureDimension_1D: |
| sourceSize = MTLSize(width: Int(logicalExtent.width), height: 1, depth: 1) |
| case WGPUTextureDimension_2D: |
| sourceSize = MTLSize(width: Int(logicalExtent.width), height: Int(logicalExtent.height), depth: 1) |
| case WGPUTextureDimension_3D: |
| sourceSize = MTLSize( |
| width: Int(logicalExtent.width), |
| height: Int(logicalExtent.height), |
| depth: Int(logicalExtent.depthOrArrayLayers) |
| ) |
| sourceBytesPerImage = bytesPerImage |
| mutableSlice = 0 |
| case WGPUTextureDimension_Force32: |
| fatalError() |
| default: |
| fatalError() |
| } |
| |
| var options: MTLBlitOption = [] |
| if mtlTexture.pixelFormat == .depth32Float_stencil8 { |
| options = .depthFromDepthStencil |
| } |
| |
| if mutableSlice >= mtlTexture.arrayLength { |
| return |
| } |
| |
| blitCommandEncoder? |
| .copy( |
| from: temporaryBuffer, |
| sourceOffset: 0, |
| sourceBytesPerRow: Int(sourceBytesPerRow), |
| sourceBytesPerImage: Int(sourceBytesPerImage), |
| sourceSize: sourceSize, |
| to: mtlTexture, |
| destinationSlice: Int(mutableSlice), |
| destinationLevel: Int(mipLevel), |
| destinationOrigin: MTLOrigin(x: 0, y: 0, z: 0), |
| options: options, |
| ) |
| |
| if !options.isEmpty { |
| // FIXME: maybe it needs to be another value that is not sizeof. |
| sourceBytesPerRow /= UInt(MemoryLayout<Float>.stride) |
| sourceBytesPerImage /= UInt(MemoryLayout<Float>.stride) |
| |
| blitCommandEncoder? |
| .copy( |
| from: temporaryBuffer, |
| sourceOffset: 0, |
| sourceBytesPerRow: Int(sourceBytesPerRow), |
| sourceBytesPerImage: Int(sourceBytesPerImage), |
| sourceSize: sourceSize, |
| to: mtlTexture, |
| destinationSlice: Int(mutableSlice), |
| destinationLevel: Int(mipLevel), |
| destinationOrigin: MTLOrigin(x: 0, y: 0, z: 0), |
| options: .stencilFromDepthStencil |
| ) |
| } |
| } |
| |
| func runClearEncoder( |
| attachmentsToClear: [NSNumber: TextureAndClearColor], |
| depthStencilAttachmentToClear: inout (any MTLTexture)?, |
| depthAttachmentToClear: Bool, |
| stencilAttachmentToClear: Bool, |
| depthClearValue: Double, |
| stencilClearValue: UInt32, |
| existingEncoder: (any MTLRenderCommandEncoder)? |
| ) { |
| func createSimplePso( |
| attachmentsToClear: [NSNumber: TextureAndClearColor], |
| depthStencilAttachmentToClear: (any MTLTexture)?, |
| depthAttachmentToClear: Bool, |
| stencilAttachmentToClear: Bool, |
| device: WebGPU.Device |
| ) -> ((any MTLRenderPipelineState)?, (any MTLDepthStencilState)?) { |
| let mtlRenderPipelineDescriptor = MTLRenderPipelineDescriptor() |
| |
| var sampleCount: UInt = 0 |
| var depthStencilDescriptor: MTLDepthStencilDescriptor? = nil |
| for (key, textureAndClearColor) in attachmentsToClear { |
| let t = textureAndClearColor.texture |
| sampleCount = UInt(t.sampleCount) |
| |
| let mtlColorAttachment = mtlRenderPipelineDescriptor.colorAttachments[key.intValue] |
| mtlColorAttachment?.pixelFormat = t.pixelFormat |
| mtlColorAttachment?.isBlendingEnabled = false |
| } |
| |
| if let depthStencilAttachmentToClear { |
| depthStencilDescriptor = MTLDepthStencilDescriptor() |
| sampleCount = UInt(depthStencilAttachmentToClear.sampleCount) |
| mtlRenderPipelineDescriptor.depthAttachmentPixelFormat = |
| (!depthAttachmentToClear || WebGPU.Device.isStencilOnlyFormat(depthStencilAttachmentToClear.pixelFormat)) |
| ? .invalid : depthStencilAttachmentToClear.pixelFormat |
| depthStencilDescriptor?.isDepthWriteEnabled = false |
| |
| if stencilAttachmentToClear |
| && (depthStencilAttachmentToClear.pixelFormat == .depth32Float_stencil8 |
| || depthStencilAttachmentToClear.pixelFormat == .stencil8 |
| || depthStencilAttachmentToClear.pixelFormat == .x32_stencil8) |
| { |
| mtlRenderPipelineDescriptor.stencilAttachmentPixelFormat = depthStencilAttachmentToClear.pixelFormat |
| } |
| } |
| |
| mtlRenderPipelineDescriptor.vertexFunction = WebGPU.Device.nopVertexFunction(device.device()) |
| mtlRenderPipelineDescriptor.fragmentFunction = nil |
| |
| precondition(sampleCount != 0, "sampleCount must be non-zero") |
| mtlRenderPipelineDescriptor.rasterSampleCount = Int(sampleCount) |
| mtlRenderPipelineDescriptor.inputPrimitiveTopology = .point |
| |
| guard let deviceMetal = device.device() else { |
| fatalError() |
| } |
| |
| var pso: (any MTLRenderPipelineState)? = nil |
| var depthStencil: (any MTLDepthStencilState)? = nil |
| do { |
| pso = try deviceMetal.makeRenderPipelineState(descriptor: mtlRenderPipelineDescriptor) |
| depthStencil = depthStencilDescriptor.flatMap { deviceMetal.makeDepthStencilState(descriptor: $0) } |
| } catch { |
| fatalError(error.localizedDescription) |
| } |
| |
| return (pso, depthStencil) |
| } |
| |
| if attachmentsToClear.isEmpty && !depthAttachmentToClear && !stencilAttachmentToClear { |
| return endEncoding(existingEncoder) |
| } |
| if !stencilAttachmentToClear && !depthAttachmentToClear { |
| depthStencilAttachmentToClear = nil |
| } |
| |
| let device = m_device.ptr().device() |
| guard device != nil else { |
| return endEncoding(existingEncoder) |
| } |
| var clearRenderCommandEncoder = existingEncoder |
| if clearRenderCommandEncoder == nil { |
| let clearDescriptor = MTLRenderPassDescriptor() |
| if depthAttachmentToClear { |
| clearDescriptor.depthAttachment.loadAction = .clear |
| clearDescriptor.depthAttachment.storeAction = .store |
| clearDescriptor.depthAttachment.clearDepth = depthClearValue |
| clearDescriptor.depthAttachment.texture = depthStencilAttachmentToClear |
| } |
| |
| if stencilAttachmentToClear { |
| clearDescriptor.stencilAttachment.loadAction = .clear |
| clearDescriptor.stencilAttachment.storeAction = .store |
| clearDescriptor.stencilAttachment.clearStencil = stencilClearValue |
| clearDescriptor.stencilAttachment.texture = depthStencilAttachmentToClear |
| } else if depthAttachmentToClear, let dsTexture = depthStencilAttachmentToClear { |
| // Depth is being pre-cleared but stencil has valid data (previously cleared). |
| // Load the stencil to preserve it — without this, the pre-clear pass uses |
| // MTLLoadActionDontCare for stencil, silently discarding what was written. |
| let fmt = dsTexture.pixelFormat |
| let hasStencil = |
| (fmt == .depth32Float_stencil8 |
| || fmt == .x32_stencil8 |
| || fmt == .stencil8) |
| if hasStencil { |
| clearDescriptor.stencilAttachment.loadAction = .load |
| clearDescriptor.stencilAttachment.storeAction = .store |
| clearDescriptor.stencilAttachment.texture = depthStencilAttachmentToClear |
| } |
| } |
| |
| if attachmentsToClear.count == 0 { |
| guard let depthStencilAttachmentToClear else { |
| fatalError() |
| } |
| clearDescriptor.defaultRasterSampleCount = depthStencilAttachmentToClear.sampleCount |
| clearDescriptor.renderTargetWidth = depthStencilAttachmentToClear.width |
| clearDescriptor.renderTargetHeight = depthStencilAttachmentToClear.height |
| } |
| for (key, textureAndClearColor) in attachmentsToClear { |
| let t = textureAndClearColor.texture |
| let mtlAttachment = clearDescriptor.colorAttachments[key.intValue] |
| mtlAttachment?.loadAction = .clear |
| mtlAttachment?.storeAction = .store |
| mtlAttachment?.clearColor = textureAndClearColor.clearColor |
| mtlAttachment?.texture = t |
| mtlAttachment?.level = 0 |
| mtlAttachment?.slice = 0 |
| mtlAttachment?.depthPlane = Int(textureAndClearColor.depthPlane) |
| } |
| clearRenderCommandEncoder = m_commandBuffer?.makeRenderCommandEncoder(descriptor: clearDescriptor) |
| setExistingEncoder(clearRenderCommandEncoder) |
| } |
| |
| let (pso, depthStencil) = createSimplePso( |
| attachmentsToClear: attachmentsToClear, |
| depthStencilAttachmentToClear: depthStencilAttachmentToClear, |
| depthAttachmentToClear: depthAttachmentToClear, |
| stencilAttachmentToClear: stencilAttachmentToClear, |
| device: m_device.ptr() |
| ) |
| |
| guard let pso else { |
| fatalError("pso should not be nil") |
| } |
| |
| clearRenderCommandEncoder?.setRenderPipelineState(pso) |
| if let depthStencil { |
| clearRenderCommandEncoder?.setDepthStencilState(depthStencil) |
| } |
| clearRenderCommandEncoder?.setCullMode(.none) |
| clearRenderCommandEncoder?.drawPrimitives(type: .point, vertexStart: 0, vertexCount: 1, instanceCount: 1, baseInstance: 0) |
| m_device.ptr().getQueue().ptr().endEncoding(clearRenderCommandEncoder, m_commandBuffer) |
| setExistingEncoder(nil) |
| } |
| |
| private func timestampWriteIndex(writeIndex: UInt32) -> Int { |
| writeIndex == WGPU_QUERY_SET_INDEX_UNDEFINED ? 0 : Int(UInt(writeIndex)) |
| } |
| |
| private func timestampWriteIndex( |
| writeIndex: UInt32, |
| defaultValue: Int, |
| offset: UInt32 |
| ) -> Int { |
| writeIndex == WGPU_QUERY_SET_INDEX_UNDEFINED ? defaultValue : Int(UInt(writeIndex + offset)) |
| } |
| |
| private func errorValidatingCopyBufferToBuffer( |
| source: WebGPU.Buffer, |
| sourceOffset: UInt64, |
| destination: WebGPU.Buffer, |
| destinationOffset: UInt64, |
| size: UInt64 |
| ) -> String? { |
| func errorString(_ format: String) -> String { |
| "GPUCommandEncoder.copyBufferToBuffer: \(format)" |
| } |
| if !source.isDestroyed() && !CxxBridging.isValidToUseWithBufferCommandEncoder(source, self) { |
| return errorString("source buffer is not valid") |
| } |
| |
| if !destination.isDestroyed() && !CxxBridging.isValidToUseWithBufferCommandEncoder(destination, self) { |
| return errorString("destination buffer is not valid") |
| } |
| |
| if source.usage() & WGPUBufferUsage_CopySrc.rawValue == 0 { |
| return errorString("source usage does not have COPY_SRC") |
| } |
| |
| if destination.usage() & WGPUBufferUsage_CopyDst.rawValue == 0 { |
| return errorString("destination usage does not have COPY_DST") |
| } |
| |
| if destination.state() == WebGPU.Buffer.State.MappingPending || source.state() == WebGPU.Buffer.State.MappingPending { |
| return errorString("destination state is not unmapped or source state is not unmapped") |
| } |
| |
| if size % 4 != 0 { |
| return errorString("size is not a multiple of 4") |
| } |
| |
| if sourceOffset % 4 != 0 { |
| return errorString("source offset is not a multiple of 4") |
| } |
| |
| if destinationOffset % 4 != 0 { |
| return errorString("destination offset is not a multiple of 4") |
| } |
| var sourceEnd = sourceOffset |
| var didOverflow = false |
| (sourceEnd, didOverflow) = sourceEnd.addingReportingOverflow(size) |
| if didOverflow { |
| return errorString("source size + offset overflows") |
| } |
| var destinationEnd = destinationOffset |
| (destinationEnd, didOverflow) = destinationEnd.addingReportingOverflow(size) |
| if didOverflow { |
| return errorString("destination size + offset overflows") |
| } |
| |
| if source.initialSize() < sourceEnd { |
| return errorString("source size + offset overflows") |
| } |
| |
| if destination.initialSize() < destinationEnd { |
| return errorString("destination size + offset overflows") |
| } |
| // FIXME: rdar://138415945 |
| if CxxBridging.areBuffersEqual(source, destination) { |
| return errorString("source equals destination not valid") |
| } |
| |
| return nil |
| } |
| |
| private func areCopyCompatible(format1: WGPUTextureFormat, format2: WGPUTextureFormat) -> Bool { |
| // https://gpuweb.github.io/gpuweb/#copy-compatible |
| format1 == format2 ? true : WebGPU.Texture.removeSRGBSuffix(format1) == WebGPU.Texture.removeSRGBSuffix(format2) |
| } |
| |
| private func errorValidatingCopyTextureToTexture( |
| source: WGPUImageCopyTexture, |
| destination: WGPUImageCopyTexture, |
| copySize: WGPUExtent3D |
| ) -> String? { |
| func refersToAllAspects(format: WGPUTextureFormat, aspect: WGPUTextureAspect) -> Bool { |
| switch aspect { |
| case WGPUTextureAspect_All: |
| return true |
| case WGPUTextureAspect_StencilOnly: |
| return WebGPU.Texture.containsStencilAspect(format) && !WebGPU.Texture.containsDepthAspect(format) |
| case WGPUTextureAspect_DepthOnly: |
| return WebGPU.Texture.containsDepthAspect(format) && !WebGPU.Texture.containsStencilAspect(format) |
| case WGPUTextureAspect_Force32: |
| assertionFailure() |
| return false |
| default: |
| assertionFailure() |
| return false |
| } |
| } |
| func errorString(_ error: String) -> String { |
| "GPUCommandEncoder.copyTextureToTexture: \(error)" |
| } |
| let sourceTexture = WebGPU.fromAPI(source.texture) |
| if !CxxBridging.isValidToUseWithTextureCommandEncoder(sourceTexture, self) { |
| return errorString("source texture is not valid to use with this GPUCommandEncoder") |
| } |
| |
| let destinationTexture = WebGPU.fromAPI(destination.texture) |
| if !CxxBridging.isValidToUseWithTextureCommandEncoder(destinationTexture, self) { |
| return errorString("destination texture is not valid to use with this GPUCommandEncoder") |
| } |
| |
| if let error = WebGPU.Texture.errorValidatingImageCopyTexture(source, copySize) { |
| return errorString(error) |
| } |
| |
| if sourceTexture.usage() & WGPUTextureUsage_CopySrc.rawValue == 0 { |
| return errorString("source texture usage does not contain CopySrc") |
| } |
| |
| if let error = WebGPU.Texture.errorValidatingImageCopyTexture(destination, copySize) { |
| return errorString(error) |
| } |
| |
| if destinationTexture.usage() & WGPUTextureUsage_CopyDst.rawValue == 0 { |
| return errorString("destination texture usage does not contain CopyDst") |
| } |
| |
| if sourceTexture.sampleCount() != destinationTexture.sampleCount() { |
| return errorString("destination texture sample count does not equal source texture sample count") |
| } |
| |
| if !areCopyCompatible(format1: sourceTexture.format(), format2: destinationTexture.format()) { |
| return errorString("destination texture and source texture are not copy compatible") |
| } |
| |
| let srcIsDepthOrStencil = WebGPU.Texture.isDepthOrStencilFormat(sourceTexture.format()) |
| let dstIsDepthOrStencil = WebGPU.Texture.isDepthOrStencilFormat(destinationTexture.format()) |
| |
| if srcIsDepthOrStencil { |
| if !refersToAllAspects(format: sourceTexture.format(), aspect: source.aspect) |
| || !refersToAllAspects(format: destinationTexture.format(), aspect: destination.aspect) |
| { |
| return errorString("source or destination do not refer to a single copy aspect") |
| } |
| } else { |
| if source.aspect != WGPUTextureAspect_All { |
| return errorString("source aspect is not All") |
| } |
| if !dstIsDepthOrStencil { |
| if destination.aspect != WGPUTextureAspect_All { |
| return errorString("destination aspect is not All") |
| } |
| } |
| } |
| |
| if let error = WebGPU.Texture.errorValidatingTextureCopyRange(source, copySize) { |
| return errorString(error) |
| } |
| |
| if let error = WebGPU.Texture.errorValidatingTextureCopyRange(destination, copySize) { |
| return errorString(error) |
| } |
| |
| // https://gpuweb.github.io/gpuweb/#abstract-opdef-set-of-subresources-for-texture-copy |
| if source.texture === destination.texture { |
| // Mip levels are never ranges. |
| if source.mipLevel == destination.mipLevel { |
| switch WebGPU.fromAPI(source.texture).dimension() { |
| case WGPUTextureDimension_1D: |
| return errorString("can't copy 1D texture to itself") |
| case WGPUTextureDimension_2D: |
| let sourceRange = source.origin.z..<(source.origin.z + copySize.depthOrArrayLayers) |
| let destinationRange = destination.origin.z..<(destination.origin.z + copySize.depthOrArrayLayers) |
| if sourceRange.overlaps(destinationRange) { |
| return errorString("can't copy 2D texture to itself with overlapping array range") |
| } |
| case WGPUTextureDimension_3D: |
| return errorString("can't copy 3D texture to itself") |
| case WGPUTextureDimension_Force32: |
| assertionFailure() |
| return errorString("unknown texture format") |
| default: |
| assertionFailure() |
| return errorString("Default. Should not be reached") |
| } |
| } |
| } |
| |
| return nil |
| } |
| |
| private func errorValidatingCopyTextureToBuffer( |
| source: WGPUImageCopyTexture, |
| destination: WGPUImageCopyBuffer, |
| copySize: WGPUExtent3D |
| ) -> String? { |
| func errorString(_ error: String) -> String { |
| "GPUCommandEncoder.copyTextureToBuffer: \(error)" |
| } |
| let sourceTexture = WebGPU.fromAPI(source.texture) |
| |
| if !CxxBridging.isValidToUseWithTextureCommandEncoder(sourceTexture, self) { |
| return errorString("source texture is not valid to use with this GPUCommandEncoder") |
| } |
| |
| if let error = WebGPU.Texture.errorValidatingImageCopyTexture(source, copySize) { |
| return errorString(error) |
| } |
| |
| if sourceTexture.usage() & WGPUTextureUsage_CopySrc.rawValue == 0 { |
| return errorString("sourceTexture usage does not contain CopySrc") |
| } |
| |
| if sourceTexture.sampleCount() != 1 { |
| return errorString("sourceTexture sample count != 1") |
| } |
| |
| var aspectSpecificFormat = sourceTexture.format() |
| |
| if WebGPU.Texture.isDepthOrStencilFormat(sourceTexture.format()) { |
| if !WebGPU.Texture.refersToSingleAspect(sourceTexture.format(), source.aspect) { |
| return errorString("copying to depth stencil texture with more than one aspect") |
| } |
| |
| if !WebGPU.Texture.isValidDepthStencilCopySource(sourceTexture.format(), source.aspect) { |
| return errorString("copying to depth stencil texture, validDepthStencilCopySource fails") |
| } |
| |
| aspectSpecificFormat = WebGPU.Texture.aspectSpecificFormat(sourceTexture.format(), source.aspect) |
| } |
| |
| if let error = errorValidatingImageCopyBuffer(imageCopyBuffer: destination) { |
| return errorString(error) |
| } |
| |
| if WebGPU.fromAPI(destination.buffer).usage() & WGPUBufferUsage_CopyDst.rawValue == 0 { |
| return errorString("destination buffer usage does not contain CopyDst") |
| } |
| |
| if let error = WebGPU.Texture.errorValidatingTextureCopyRange(source, copySize) { |
| return errorString(error) |
| } |
| |
| if !WebGPU.Texture.isDepthOrStencilFormat(sourceTexture.format()) { |
| let texelBlockSize = WebGPU.Texture.texelBlockSize(sourceTexture.format()) |
| if destination.layout.offset % texelBlockSize.value() != 0 { |
| return errorString("destination.layout.offset is not a multiple of texelBlockSize") |
| } |
| } |
| |
| if WebGPU.Texture.isDepthOrStencilFormat(sourceTexture.format()) { |
| if destination.layout.offset % 4 != 0 { |
| return errorString("destination.layout.offset is not a multiple of 4") |
| } |
| } |
| |
| if let error = WebGPU.Texture.errorValidatingLinearTextureData( |
| destination.layout, |
| WebGPU.fromAPI(destination.buffer).initialSize(), |
| aspectSpecificFormat, |
| copySize |
| ) { |
| return errorString(error) |
| } |
| return nil |
| } |
| |
| private func errorValidatingImageCopyBuffer(imageCopyBuffer: WGPUImageCopyBuffer) -> String? { |
| // https://gpuweb.github.io/gpuweb/#abstract-opdef-validating-gpuimagecopybuffer |
| let buffer = WebGPU.fromAPI(imageCopyBuffer.buffer) |
| if !CxxBridging.isValidToUseWithBufferCommandEncoder(buffer, self) { |
| return "buffer is not valid" |
| } |
| |
| if imageCopyBuffer.layout.bytesPerRow != WGPU_COPY_STRIDE_UNDEFINED && (imageCopyBuffer.layout.bytesPerRow % 256 != 0) { |
| return "imageCopyBuffer.layout.bytesPerRow is not a multiple of 256" |
| } |
| |
| return nil |
| } |
| |
| private func errorValidatingCopyBufferToTexture( |
| source: WGPUImageCopyBuffer, |
| destination: WGPUImageCopyTexture, |
| copySize: WGPUExtent3D |
| ) -> String? { |
| func errorString(_ error: String) -> String { |
| "GPUCommandEncoder.copyBufferToTexture: \(error)" |
| } |
| let destinationTexture = WebGPU.fromAPI(destination.texture) |
| let sourceBuffer = WebGPU.fromAPI(source.buffer) |
| |
| if let error = errorValidatingImageCopyBuffer(imageCopyBuffer: source) { |
| return errorString(error) |
| } |
| |
| if sourceBuffer.usage() & WGPUBufferUsage_CopySrc.rawValue == 0 { |
| return errorString("source usage does not contain CopySrc") |
| } |
| |
| if !CxxBridging.isValidToUseWithTextureCommandEncoder(destinationTexture, self) { |
| return errorString("destination texture is not valid to use with this GPUCommandEncoder") |
| } |
| |
| if let error = WebGPU.Texture.errorValidatingImageCopyTexture(destination, copySize) { |
| return errorString(error) |
| } |
| |
| if destinationTexture.usage() & WGPUTextureUsage_CopyDst.rawValue == 0 { |
| return errorString("destination usage does not contain CopyDst") |
| } |
| |
| if destinationTexture.sampleCount() != 1 { |
| return errorString("destination sample count is not one") |
| } |
| |
| var aspectSpecificFormat = destinationTexture.format() |
| |
| if WebGPU.Texture.isDepthOrStencilFormat(destinationTexture.format()) { |
| if !WebGPU.Texture.refersToSingleAspect(destinationTexture.format(), destination.aspect) { |
| return errorString("destination aspect refers to more than one asepct") |
| } |
| |
| if !WebGPU.Texture.isValidDepthStencilCopyDestination(destinationTexture.format(), destination.aspect) { |
| return errorString("destination is not valid depthStencilCopyDestination") |
| } |
| |
| aspectSpecificFormat = WebGPU.Texture.aspectSpecificFormat(destinationTexture.format(), destination.aspect) |
| } |
| |
| if let error = WebGPU.Texture.errorValidatingTextureCopyRange(destination, copySize) { |
| return errorString(error) |
| } |
| |
| if !WebGPU.Texture.isDepthOrStencilFormat(destinationTexture.format()) { |
| let texelBlockSize = WebGPU.Texture.texelBlockSize(destinationTexture.format()) |
| if source.layout.offset % texelBlockSize.value() != 0 { |
| return errorString("source.layout.offset is not a multiple of texelBlockSize") |
| } |
| } |
| |
| if WebGPU.Texture.isDepthOrStencilFormat(destinationTexture.format()) { |
| if source.layout.offset % 4 != 0 { |
| return errorString("source.layout.offset is not a multiple of four for depth stencil format") |
| } |
| } |
| |
| if let error = WebGPU.Texture.errorValidatingLinearTextureData( |
| source.layout, |
| WebGPU.fromAPI(source.buffer).initialSize(), |
| aspectSpecificFormat, |
| copySize |
| ) { |
| return errorString(error) |
| } |
| return nil |
| } |
| |
| private func errorValidatingRenderPassDescriptor(descriptor: WGPURenderPassDescriptor) -> String? { |
| if let wgpuOcclusionQuery = descriptor.occlusionQuerySet { |
| let occlusionQuery = WebGPU.fromAPI(wgpuOcclusionQuery) |
| if !CxxBridging.isValidToUseWithQuerySetCommandEncoder(occlusionQuery, self) { |
| return "occlusion query does not match the device" |
| } |
| if occlusionQuery.type() != WGPUQueryType_Occlusion { |
| return "occlusion query type is not occlusion" |
| } |
| } |
| let collection = CollectionOfOne(descriptor) |
| let descriptorSpan = collection.span |
| if let timestampWrites = wgpuGetRenderPassDescriptorTimestampWrites(descriptorSpan)?[0] { |
| return errorValidatingTimestampWrites( |
| timestampWrites: WGPUComputePassTimestampWrites( |
| querySet: timestampWrites.querySet, |
| beginningOfPassWriteIndex: timestampWrites.beginningOfPassWriteIndex, |
| endOfPassWriteIndex: timestampWrites.endOfPassWriteIndex |
| ) |
| ) |
| } |
| return nil |
| } |
| |
| private func errorValidatingTimestampWrites(timestampWrites: WGPUComputePassTimestampWrites) -> String? { |
| if !m_device.ptr().hasFeature(WGPUFeatureName_TimestampQuery) { |
| return "device does not have timestamp query feature" |
| } |
| |
| let querySet = WebGPU.fromAPI(timestampWrites.querySet) |
| if querySet.type() != WGPUQueryType_Timestamp { |
| return "query type is not timestamp but \(querySet.type())" |
| } |
| |
| if !CxxBridging.isValidToUseWithQuerySetCommandEncoder(querySet, self) { |
| return "device mismatch" |
| } |
| |
| let querySetCount = querySet.count() |
| let beginningOfPassWriteIndex = timestampWriteIndex(writeIndex: timestampWrites.beginningOfPassWriteIndex) |
| let endOfPassWriteIndex = timestampWriteIndex(writeIndex: timestampWrites.endOfPassWriteIndex) |
| if beginningOfPassWriteIndex >= querySetCount || endOfPassWriteIndex >= querySetCount |
| || timestampWrites.beginningOfPassWriteIndex == timestampWrites.endOfPassWriteIndex |
| { |
| return |
| "writeIndices mismatch: beginningOfPassWriteIndex(\(beginningOfPassWriteIndex) >= querySetCount(\(querySetCount) || endOfPassWriteIndex(\(endOfPassWriteIndex)) >= querySetCount(\(querySetCount)) || timestampWrite.beginningOfPassWriteIndex(\(timestampWrites.beginningOfPassWriteIndex) == timestampWrite.endOfPassWriteIndex(\(timestampWrites.endOfPassWriteIndex))" |
| } |
| |
| return nil |
| } |
| |
| private func errorValidatingComputePassDescriptor(descriptor: WGPUComputePassDescriptor) -> String? { |
| let collection = CollectionOfOne(descriptor) |
| if let timestampWrites = wgpuGetComputePassDescriptorTimestampWrites(collection.span)?[0] { |
| return errorValidatingTimestampWrites(timestampWrites: timestampWrites) |
| } |
| return nil |
| } |
| |
| private func loadAction(loadOp: WGPULoadOp, readOnly: UInt32 = 0) -> MTLLoadAction { |
| switch loadOp { |
| case WGPULoadOp_Load: |
| return .load |
| case WGPULoadOp_Clear: |
| return .clear |
| case WGPULoadOp_Undefined: |
| return readOnly != 0 ? .load : .dontCare |
| case WGPULoadOp_Force32: |
| assertionFailure() |
| return .dontCare |
| default: |
| assertionFailure() |
| return .dontCare |
| } |
| } |
| |
| private func storeAction(storeOp: WGPUStoreOp, hasResolveTarget: Bool = false) -> MTLStoreAction { |
| switch storeOp { |
| case WGPUStoreOp_Store: |
| return hasResolveTarget ? .storeAndMultisampleResolve : .store |
| case WGPUStoreOp_Discard: |
| return hasResolveTarget ? .multisampleResolve : .dontCare |
| case WGPUStoreOp_Undefined: |
| return hasResolveTarget ? .multisampleResolve : .dontCare |
| case WGPUStoreOp_Force32: |
| assertionFailure() |
| return .dontCare |
| default: |
| assertionFailure() |
| return .dontCare |
| } |
| } |
| |
| private func isMultisampleTexture(texture: any MTLTexture) -> Bool { |
| texture.textureType == .type2DMultisample || texture.textureType == .type2DMultisampleArray |
| } |
| |
| func beginRenderPass(descriptor: WGPURenderPassDescriptor) -> CxxBridging.RefRenderPassEncoder { |
| let collection = CollectionOfOne(descriptor) |
| let descriptorSpan = collection.span |
| let maxDrawCount = descriptorSpan[0].maxDrawCount |
| |
| guard prepareTheEncoderState() else { |
| self.generateInvalidEncoderStateError() |
| return WebGPU.RenderPassEncoder.createInvalid(self, m_device.ptr(), "encoder state is not valid") |
| } |
| |
| if let error = errorValidatingRenderPassDescriptor(descriptor: descriptor) { |
| return WebGPU.RenderPassEncoder.createInvalid(self, m_device.ptr(), error) |
| } |
| |
| if let commandBuffer = m_commandBuffer, commandBuffer.status.rawValue >= MTLCommandBufferStatus.enqueued.rawValue { |
| return WebGPU.RenderPassEncoder.createInvalid(self, m_device.ptr(), "command buffer has already been committed") |
| } |
| |
| let mtlDescriptor = MTLRenderPassDescriptor() |
| var counterSampleBuffer = WebGPU.QuerySet.CounterSampleBuffer() |
| if let wgpuTimestampWrites = wgpuGetRenderPassDescriptorTimestampWrites(descriptorSpan)?[0] { |
| let wgpuQuerySet = wgpuTimestampWrites.querySet |
| let timestampsWrites = WebGPU.fromAPI(wgpuQuerySet) |
| counterSampleBuffer = timestampsWrites.counterSampleBufferWithOffset() |
| timestampsWrites.setCommandEncoder(self) |
| } |
| |
| if m_device.ptr().enableEncoderTimestamps() || counterSampleBuffer.buffer != nil { |
| if let buffer = counterSampleBuffer.buffer { |
| // FIXME: (rdar://170907276) Prove that the result of `wgpuGetRenderPassDescriptorTimestampWrites` can never be nil. |
| // swift-format-ignore: NeverForceUnwrap |
| let timestampWrites = wgpuGetRenderPassDescriptorTimestampWrites(descriptorSpan)![0] |
| mtlDescriptor.sampleBufferAttachments[0].sampleBuffer = buffer |
| mtlDescriptor.sampleBufferAttachments[0].startOfVertexSampleIndex = timestampWriteIndex( |
| writeIndex: timestampWrites.beginningOfPassWriteIndex, |
| defaultValue: MTLCounterDontSample, |
| offset: counterSampleBuffer.offset |
| ) |
| mtlDescriptor.sampleBufferAttachments[0].endOfVertexSampleIndex = timestampWriteIndex( |
| writeIndex: timestampWrites.endOfPassWriteIndex, |
| defaultValue: MTLCounterDontSample, |
| offset: counterSampleBuffer.offset |
| ) |
| mtlDescriptor.sampleBufferAttachments[0].startOfFragmentSampleIndex = |
| mtlDescriptor.sampleBufferAttachments[0].endOfVertexSampleIndex |
| mtlDescriptor.sampleBufferAttachments[0].endOfFragmentSampleIndex = |
| mtlDescriptor.sampleBufferAttachments[0].endOfVertexSampleIndex |
| m_device.ptr().trackTimestampsBuffer(m_commandBuffer, buffer) |
| } else { |
| mtlDescriptor.sampleBufferAttachments[0].sampleBuffer = |
| counterSampleBuffer.buffer ?? m_device.ptr().timestampsBuffer(m_commandBuffer, 4) |
| mtlDescriptor.sampleBufferAttachments[0].startOfVertexSampleIndex = 0 |
| mtlDescriptor.sampleBufferAttachments[0].endOfVertexSampleIndex = 1 |
| mtlDescriptor.sampleBufferAttachments[0].startOfFragmentSampleIndex = 2 |
| mtlDescriptor.sampleBufferAttachments[0].endOfFragmentSampleIndex = 3 |
| } |
| } |
| |
| guard descriptor.colorAttachmentCount <= 8 else { |
| return WebGPU.RenderPassEncoder.createInvalid(self, m_device.ptr(), "color attachment count is > 8") |
| } |
| |
| finalizeBlitCommandEncoder() |
| |
| var attachmentsToClear: [NSNumber: TextureAndClearColor] = [:] |
| var zeroColorTargets = true |
| var bytesPerSample: UInt32 = 0 |
| let maxColorAttachmentBytesPerSample = m_device.ptr().limitsCopy().maxColorAttachmentBytesPerSample |
| var textureWidth: UInt32 = 0 |
| var textureHeight: UInt32 = 0 |
| var sampleCount: UInt32 = 0 |
| |
| var depthSlices: [UInt64: Set<UInt64>] = [:] |
| // FIXME: it shouldn't be necessary to pass colorAttachmentCount here |
| var compositorTextureSlice: UInt32 = 0 |
| if descriptor.colorAttachmentCount != 0 { |
| let attachments = wgpuGetRenderPassDescriptorColorAttachments(descriptorSpan, descriptor.colorAttachmentCount) |
| for i in 0..<attachments.count { |
| let attachment = attachments[i] |
| |
| if attachment.view == nil && attachment.texture == nil { |
| continue |
| } |
| |
| // MTLRenderPassColorAttachmentDescriptorArray is bounds-checked internally, so this is guaranteed to be non-nil. |
| // swift-format-ignore: NeverForceUnwrap |
| let mtlAttachment = mtlDescriptor.colorAttachments[i]! |
| |
| mtlAttachment.clearColor = MTLClearColor( |
| red: attachment.clearValue.r, |
| green: attachment.clearValue.g, |
| blue: attachment.clearValue.b, |
| alpha: attachment.clearValue.a |
| ) |
| |
| var texture = WebGPU.TextureOrTextureView(attachment) |
| if !CxxBridging.isValidToUseWith(texture, self) { |
| return WebGPU.RenderPassEncoder.createInvalid(self, m_device.ptr(), "device mismatch") |
| } |
| |
| if textureWidth != 0 |
| && (texture.width() != textureWidth || texture.height() != textureHeight || sampleCount != texture.sampleCount()) |
| { |
| return WebGPU.RenderPassEncoder.createInvalid(self, m_device.ptr(), "texture size does not match") |
| } |
| |
| textureWidth = texture.width() |
| textureHeight = texture.height() |
| sampleCount = texture.sampleCount() |
| let textureFormat = texture.format() |
| bytesPerSample = roundUpToMultipleOfNonPowerOfTwoUInt32UInt32( |
| WebGPU.Texture.renderTargetPixelByteAlignment(textureFormat), |
| bytesPerSample |
| ) |
| bytesPerSample += WebGPU.Texture.renderTargetPixelByteCost(textureFormat) |
| if bytesPerSample > maxColorAttachmentBytesPerSample { |
| return WebGPU.RenderPassEncoder.createInvalid(self, m_device.ptr(), "total bytes per sample exceeds limit") |
| } |
| |
| let textureIsDestroyed = texture.isDestroyed() |
| if !textureIsDestroyed { |
| if (texture.usage() & WGPUTextureUsage_RenderAttachment.rawValue) == 0 |
| || !WebGPU.Texture.isColorRenderableFormat(textureFormat, m_device.ptr()) |
| { |
| return WebGPU.RenderPassEncoder.createInvalid(self, m_device.ptr(), "color attachment is not renderable") |
| } |
| |
| if !WebGPU.isRenderableTextureView(texture, attachment.loadOp, attachment.storeOp) { |
| return WebGPU.RenderPassEncoder.createInvalid(self, m_device.ptr(), "texture view is not renderable") |
| } |
| } |
| texture.setCommandEncoder(self) |
| |
| let mtlTexture = texture.texture() |
| mtlAttachment.texture = mtlTexture |
| if mtlAttachment.texture == nil { |
| if !textureIsDestroyed { |
| return WebGPU.RenderPassEncoder.createInvalid(self, m_device.ptr(), "color attachment's texture is nil") |
| } |
| continue |
| } |
| mtlAttachment.level = 0 |
| mtlAttachment.slice = 0 |
| var depthSliceOrArrayLayer: UInt64 = 0 |
| // FIXME: (rdar://170907318) This should be changed to `if let` when possible. |
| if var depthSlice = Optional(fromCxx: attachment.depthSlice) { |
| if !texture.is3DTexture() { |
| return WebGPU.RenderPassEncoder.createInvalid(self, m_device.ptr(), "depthSlice specified on 2D texture") |
| } |
| depthSliceOrArrayLayer = textureIsDestroyed ? 0 : UInt64(depthSlice) |
| if depthSliceOrArrayLayer >= texture.depthOrArrayLayers() { |
| return WebGPU.RenderPassEncoder.createInvalid( |
| self, |
| m_device.ptr(), |
| "depthSlice is greater than texture's depth or array layers" |
| ) |
| } |
| } else { |
| if texture.is3DTexture() { |
| return WebGPU.RenderPassEncoder.createInvalid( |
| self, |
| m_device.ptr(), |
| "textureDimension is 3D and no depth slice is specified" |
| ) |
| } |
| depthSliceOrArrayLayer = UInt64(textureIsDestroyed ? 0 : texture.baseArrayLayer()) |
| } |
| let bridgedTexture = texture.parentTexture().gpuResourceID._impl |
| let baseMipLevel = textureIsDestroyed ? 0 : texture.baseMipLevel() |
| let depthAndMipLevel: UInt64 = depthSliceOrArrayLayer | (UInt64(baseMipLevel) << 32) |
| |
| guard depthSlices[bridgedTexture, default: []].insert(depthAndMipLevel).inserted else { |
| return WebGPU.RenderPassEncoder.createInvalid( |
| self, |
| m_device.ptr(), |
| "attempting to render to overlapping color attachment" |
| ) |
| } |
| |
| mtlAttachment.depthPlane = texture.is3DTexture() ? Int(depthSliceOrArrayLayer) : 0 |
| mtlAttachment.slice = 0 |
| mtlAttachment.loadAction = loadAction(loadOp: attachment.loadOp) |
| mtlAttachment.storeAction = storeAction( |
| storeOp: attachment.storeOp, |
| hasResolveTarget: attachment.resolveTarget != nil || attachment.resolveTexture != nil |
| ) |
| |
| zeroColorTargets = false |
| var textureToClear: (any MTLTexture)? = nil |
| if mtlAttachment.loadAction == .load && !texture.previouslyCleared() { |
| textureToClear = mtlAttachment.texture |
| } |
| |
| var compositorTexture = texture |
| if attachment.resolveTarget != nil || attachment.resolveTexture != nil { |
| var resolveTarget = |
| attachment.resolveTarget != nil |
| ? WebGPU.TextureOrTextureView(WebGPU.fromAPI(attachment.resolveTarget)) |
| : WebGPU.TextureOrTextureView(WebGPU.fromAPI(attachment.resolveTexture)) |
| compositorTexture = resolveTarget |
| |
| if !CxxBridging.isValidToUseWith(resolveTarget, self) { |
| return WebGPU.RenderPassEncoder.createInvalid(self, m_device.ptr(), "resolve target created from different device") |
| } |
| resolveTarget.setCommandEncoder(self) |
| let resolveTexture = resolveTarget.texture() |
| // FIXME: (rdar://170907318) This should be changed to `guard let` when possible. |
| guard var resolveTexture, var mtlTexture else { |
| return WebGPU.RenderPassEncoder.createInvalid(self, m_device.ptr(), "resolveTexture/mtlTexture is nil") |
| } |
| |
| if mtlTexture.sampleCount == 1 |
| || resolveTexture.sampleCount != 1 |
| || isMultisampleTexture(texture: resolveTexture) |
| || !isMultisampleTexture(texture: mtlTexture) |
| || !WebGPU.isRenderableTextureView(resolveTarget, attachment.loadOp, attachment.storeOp) |
| || mtlTexture.pixelFormat != resolveTexture.pixelFormat |
| || !WebGPU.Texture.supportsResolve(resolveTarget.format(), m_device.ptr()) |
| { |
| return WebGPU.RenderPassEncoder.createInvalid(self, m_device.ptr(), "resolve target is invalid") |
| } |
| |
| mtlAttachment.resolveTexture = resolveTexture |
| mtlAttachment.resolveLevel = 0 |
| mtlAttachment.resolveSlice = 0 |
| mtlAttachment.resolveDepthPlane = 0 |
| if resolveTarget.width() != texture.width() || resolveTarget.height() != texture.height() { |
| return WebGPU.RenderPassEncoder.createInvalid(self, m_device.ptr(), "resolve target dimensions are invalid") |
| } |
| } |
| |
| if let rateMap = compositorTexture.rasterizationMapForSlice(compositorTexture.parentRelativeSlice()) { |
| mtlDescriptor.rasterizationRateMap = rateMap |
| compositorTextureSlice = compositorTexture.parentRelativeSlice() |
| } |
| |
| // FIXME: (rdar://170907318) This should be changed to `if let` when possible. |
| if var textureToClear { |
| let textureWithResolve = TextureAndClearColor(texture: textureToClear) |
| attachmentsToClear[i as NSNumber] = textureWithResolve |
| texture.setPreviouslyCleared() |
| // FIXME: (rdar://170907318) This should be changed to `if let` when possible. |
| if var resolveTarget = attachment.resolveTarget { |
| // FIXME: rdar://138042799 remove default argument. |
| WebGPU.fromAPI(resolveTarget).setPreviouslyCleared(0, 0) |
| } |
| // FIXME: (rdar://170907318) This should be changed to `if let` when possible. |
| if var resolveTexture = attachment.resolveTexture { |
| WebGPU.fromAPI(resolveTexture).setPreviouslyCleared() |
| } |
| } |
| } |
| } |
| |
| var depthReadOnly = false |
| var stencilReadOnly = false |
| var hasStencilComponent = false |
| var hasDepthComponent = false |
| var depthStencilAttachmentToClear: (any MTLTexture)? = nil |
| var depthAttachmentToClear = false |
| let optionalAttachment = wgpuGetRenderPassDescriptorDepthStencilAttachment(descriptorSpan)?[0] |
| if optionalAttachment != nil { |
| // swift-format-ignore: NeverForceUnwrap |
| let attachment = optionalAttachment! |
| let textureView = WebGPU.TextureOrTextureView(attachment) |
| if !CxxBridging.isValidToUseWith(textureView, self) { |
| return WebGPU.RenderPassEncoder.createInvalid(self, m_device.ptr(), "depth stencil texture device mismatch") |
| } |
| let metalDepthStencilTexture = textureView.texture() |
| let textureFormat = textureView.format() |
| hasStencilComponent = WebGPU.Texture.containsStencilAspect(textureFormat) |
| hasDepthComponent = WebGPU.Texture.containsDepthAspect(textureFormat) |
| let isDestroyed = textureView.isDestroyed() |
| if !isDestroyed { |
| if textureWidth != 0 |
| && (textureView.width() != textureWidth || textureView.height() != textureHeight |
| || sampleCount != textureView.sampleCount()) |
| { |
| return WebGPU.RenderPassEncoder.createInvalid(self, m_device.ptr(), "depth stencil texture dimensions mismatch") |
| } |
| if textureView.arrayLayerCount() > 1 || textureView.mipLevelCount() > 1 { |
| return WebGPU.RenderPassEncoder.createInvalid( |
| self, |
| m_device.ptr(), |
| "depth stencil texture has more than one array layer or mip level" |
| ) |
| } |
| |
| if !WebGPU.Texture.isDepthStencilRenderableFormat( |
| textureView.format(), |
| m_device.ptr() |
| ) || !WebGPU.isRenderableTextureView(textureView, attachment.depthLoadOp, attachment.depthStoreOp) { |
| return WebGPU.RenderPassEncoder.createInvalid(self, m_device.ptr(), "depth stencil texture is not renderable") |
| } |
| } |
| |
| depthReadOnly = attachment.depthReadOnly != 0 |
| if hasDepthComponent { |
| // This is safe because the `depthAttachment` property is `null_resettable` in Objective C. |
| // swift-format-ignore: NeverForceUnwrap |
| let mtlAttachment = mtlDescriptor.depthAttachment! |
| |
| let clearDepth = WebGPU.RenderPassEncoder |
| .quantizedDepthValue(Double(attachment.depthClearValue), textureView.format()) |
| .clamped(to: 0...1) |
| |
| mtlAttachment.clearDepth = attachment.depthLoadOp == WGPULoadOp_Clear ? clearDepth : 1.0 |
| mtlAttachment.texture = metalDepthStencilTexture |
| mtlAttachment.level = 0 |
| mtlAttachment.loadAction = loadAction(loadOp: attachment.depthLoadOp, readOnly: attachment.depthReadOnly) |
| mtlAttachment.storeAction = storeAction(storeOp: attachment.depthStoreOp) |
| |
| if mtlDescriptor.rasterizationRateMap != nil && metalDepthStencilTexture?.sampleCount ?? 1 > 1 { |
| if let depthTexture = m_device.ptr().getXRViewSubImageDepthTexture() { |
| mtlAttachment.resolveTexture = depthTexture |
| mtlAttachment.storeAction = storeAction(storeOp: attachment.depthStoreOp, hasResolveTarget: true) |
| mtlAttachment.resolveSlice = Int(compositorTextureSlice) |
| } |
| } |
| |
| if mtlAttachment.loadAction == .load && mtlAttachment.storeAction == .dontCare && !textureView.previouslyCleared() { |
| depthStencilAttachmentToClear = mtlAttachment.texture |
| depthAttachmentToClear = mtlAttachment.texture != nil |
| } |
| } |
| |
| if !isDestroyed { |
| if hasDepthComponent && !depthReadOnly { |
| if attachment.depthLoadOp == WGPULoadOp_Undefined || attachment.depthStoreOp == WGPUStoreOp_Undefined { |
| return WebGPU.RenderPassEncoder.createInvalid(self, m_device.ptr(), "depth load and store op were not specified") |
| } |
| } else if attachment.depthLoadOp != WGPULoadOp_Undefined || attachment.depthStoreOp != WGPUStoreOp_Undefined { |
| return WebGPU.RenderPassEncoder.createInvalid(self, m_device.ptr(), "depth load and store op were specified") |
| } |
| } |
| |
| if attachment.depthLoadOp == WGPULoadOp_Clear && (attachment.depthClearValue < 0 || attachment.depthClearValue > 1) { |
| return WebGPU.RenderPassEncoder.createInvalid(self, m_device.ptr(), "depth clear value is invalid") |
| } |
| |
| if zeroColorTargets { |
| if isDestroyed { |
| return WebGPU.RenderPassEncoder.createInvalid(self, m_device.ptr(), "no color targets and depth-stencil texture is destroyed") |
| } |
| // FIXME: (rdar://170907318) This should be changed to `guard let` when possible. |
| guard var metalDepthStencilTexture, metalDepthStencilTexture.sampleCount > 0 else { |
| return WebGPU.RenderPassEncoder.createInvalid(self, m_device.ptr(), "no color targets and depth-stencil texture is nil") |
| } |
| |
| mtlDescriptor.defaultRasterSampleCount = metalDepthStencilTexture.sampleCount |
| mtlDescriptor.renderTargetWidth = metalDepthStencilTexture.width |
| mtlDescriptor.renderTargetHeight = metalDepthStencilTexture.height |
| } |
| } |
| |
| var stencilAttachmentToClear = false |
| if let attachment = wgpuGetRenderPassDescriptorDepthStencilAttachment(descriptorSpan)?[0] { |
| // This is safe because the `stencilAttachment` property is `null_resettable` in Objective C. |
| // swift-format-ignore: NeverForceUnwrap |
| let mtlAttachment = mtlDescriptor.stencilAttachment! |
| stencilReadOnly = attachment.stencilReadOnly != 0 |
| var textureView = WebGPU.TextureOrTextureView(attachment) |
| if hasStencilComponent { |
| mtlAttachment.texture = textureView.texture() |
| } |
| mtlAttachment.clearStencil = attachment.stencilClearValue |
| mtlAttachment.loadAction = loadAction(loadOp: attachment.stencilLoadOp, readOnly: attachment.stencilReadOnly) |
| mtlAttachment.storeAction = storeAction(storeOp: attachment.stencilStoreOp) |
| let isDestroyed = textureView.isDestroyed() |
| if !isDestroyed { |
| if hasStencilComponent && !stencilReadOnly { |
| if attachment.stencilLoadOp == WGPULoadOp_Undefined || attachment.stencilStoreOp == WGPUStoreOp_Undefined { |
| return WebGPU.RenderPassEncoder.createInvalid(self, m_device.ptr(), "stencil load and store op were not specified") |
| } |
| } else if attachment.stencilLoadOp != WGPULoadOp_Undefined || attachment.stencilStoreOp != WGPUStoreOp_Undefined { |
| return WebGPU.RenderPassEncoder.createInvalid(self, m_device.ptr(), "stencil load and store op were specified") |
| } |
| } |
| |
| textureView.setCommandEncoder(self) |
| |
| if hasStencilComponent |
| && mtlAttachment.loadAction == .load |
| && mtlAttachment.storeAction == .dontCare |
| && !textureView.previouslyCleared() |
| { |
| depthStencilAttachmentToClear = mtlAttachment.texture |
| stencilAttachmentToClear = mtlAttachment.texture != nil |
| } |
| } |
| |
| if zeroColorTargets && mtlDescriptor.renderTargetWidth == 0 { |
| return WebGPU.RenderPassEncoder.createInvalid(self, m_device.ptr(), "zero color and depth targets") |
| } |
| |
| var visibilityResultBufferSize: UInt = 0 |
| var visibilityResultBuffer: (any MTLBuffer)? = nil |
| if let wgpuOcclusionQuery = descriptor.occlusionQuerySet { |
| let occlusionQuery = WebGPU.fromAPI(wgpuOcclusionQuery) |
| occlusionQuery.setCommandEncoder(self) |
| if occlusionQuery.type() != WGPUQueryType_Occlusion { |
| return WebGPU.RenderPassEncoder.createInvalid( |
| self, |
| m_device.ptr(), |
| "querySet for occlusion query was not of type occlusion" |
| ) |
| } |
| mtlDescriptor.visibilityResultBuffer = occlusionQuery.visibilityBuffer() |
| visibilityResultBuffer = mtlDescriptor.visibilityResultBuffer |
| visibilityResultBufferSize = occlusionQuery.isDestroyed() ? UInt.max : UInt(occlusionQuery.visibilityBuffer().length) |
| } |
| |
| if attachmentsToClear.count != 0 || depthStencilAttachmentToClear != nil { |
| let attachment = wgpuGetRenderPassDescriptorDepthStencilAttachment(descriptorSpan)?[0] |
| if attachment != nil && depthStencilAttachmentToClear != nil { |
| var texture = WebGPU.TextureOrTextureView(attachment) |
| texture.setPreviouslyCleared() |
| } |
| |
| // FIXME: rdar://138042799 remove default argument. |
| runClearEncoder( |
| attachmentsToClear: attachmentsToClear, |
| depthStencilAttachmentToClear: &depthStencilAttachmentToClear, |
| depthAttachmentToClear: depthAttachmentToClear, |
| stencilAttachmentToClear: stencilAttachmentToClear, |
| depthClearValue: 0, |
| stencilClearValue: 0, |
| existingEncoder: nil, |
| ) |
| } |
| |
| if !m_device.ptr().isValid() { |
| return WebGPU.RenderPassEncoder.createInvalid( |
| self, |
| m_device.ptr(), |
| "GPUDevice was invalid, this will be an error submitting the command buffer" |
| ) |
| } |
| |
| let mtlRenderCommandEncoder = m_commandBuffer?.makeRenderCommandEncoder(descriptor: mtlDescriptor) |
| if m_existingCommandEncoder != nil { |
| assertionFailure("!m_existingCommandEncoder") |
| } |
| setExistingEncoder(mtlRenderCommandEncoder) |
| return WebGPU.RenderPassEncoder.create( |
| mtlRenderCommandEncoder, |
| descriptor, |
| visibilityResultBufferSize, |
| depthReadOnly, |
| stencilReadOnly, |
| self, |
| visibilityResultBuffer, |
| maxDrawCount, |
| m_device.ptr(), |
| mtlDescriptor |
| ) |
| } |
| |
| static func hasValidDimensions(dimension: WGPUTextureDimension, width: UInt, height: UInt, depth: UInt) -> Bool { |
| switch dimension { |
| case WGPUTextureDimension_1D: |
| width != 0 |
| case WGPUTextureDimension_2D: |
| width != 0 && height != 0 |
| case WGPUTextureDimension_3D: |
| width != 0 && height != 0 && depth != 0 |
| default: |
| true |
| } |
| } |
| |
| func copyBufferToBuffer( |
| source: WebGPU.Buffer, |
| sourceOffset: UInt64, |
| destination: WebGPU.Buffer, |
| destinationOffset: UInt64, |
| size: UInt64, |
| ) { |
| // https://gpuweb.github.io/gpuweb/#dom-gpucommandencoder-copybuffertobuffer |
| guard prepareTheEncoderState() else { |
| generateInvalidEncoderStateError() |
| return |
| } |
| |
| if let error = self.errorValidatingCopyBufferToBuffer( |
| source: source, |
| sourceOffset: sourceOffset, |
| destination: destination, |
| destinationOffset: destinationOffset, |
| size: size |
| ) { |
| makeInvalid(error) |
| return |
| } |
| |
| // FIXME: rdar://138042799 remove default argument. |
| source.setCommandEncoder(self, false) |
| destination.setCommandEncoder(self, false) |
| destination.indirectBufferInvalidated(self) |
| guard size != 0, !source.isDestroyed() && !destination.isDestroyed() else { |
| return |
| } |
| |
| guard let blitCommandEncoder = ensureBlitCommandEncoder() else { |
| return |
| } |
| blitCommandEncoder.copy( |
| from: source.buffer(), |
| sourceOffset: Int(sourceOffset), |
| to: destination.buffer(), |
| destinationOffset: Int(destinationOffset), |
| size: Int(size) |
| ) |
| } |
| |
| func copyTextureToBuffer(source: WGPUImageCopyTexture, destination: WGPUImageCopyBuffer, copySize: WGPUExtent3D) { |
| // https://gpuweb.github.io/gpuweb/#dom-gpucommandencoder-copytexturetobuffer |
| |
| guard prepareTheEncoderState() else { |
| generateInvalidEncoderStateError() |
| return |
| } |
| |
| let sourceTexture = WebGPU.fromAPI(source.texture) |
| if let error = errorValidatingCopyTextureToBuffer(source: source, destination: destination, copySize: copySize) { |
| makeInvalid(error) |
| return |
| } |
| |
| let apiDestinationBuffer = WebGPU.fromAPI(destination.buffer) |
| sourceTexture.setCommandEncoder(self) |
| apiDestinationBuffer.setCommandEncoder(self, false) |
| apiDestinationBuffer.indirectBufferInvalidated(self) |
| guard !sourceTexture.isDestroyed() && !apiDestinationBuffer.isDestroyed() else { |
| return |
| } |
| |
| var options: MTLBlitOption = [] |
| switch source.aspect { |
| case WGPUTextureAspect_All: |
| break |
| case WGPUTextureAspect_StencilOnly: |
| options = .stencilFromDepthStencil |
| case WGPUTextureAspect_DepthOnly: |
| options = .depthFromDepthStencil |
| case WGPUTextureAspect_Force32: |
| return |
| default: |
| return |
| } |
| |
| let logicalSize = sourceTexture.logicalMiplevelSpecificTextureExtent(source.mipLevel) |
| let widthForMetal = logicalSize.width < source.origin.x ? 0 : min(copySize.width, logicalSize.width - source.origin.x) |
| let heightForMetal = logicalSize.height < source.origin.y ? 0 : min(copySize.height, logicalSize.height - source.origin.y) |
| let depthForMetal = |
| logicalSize.depthOrArrayLayers < source.origin.z |
| ? 0 : min(copySize.depthOrArrayLayers, logicalSize.depthOrArrayLayers - source.origin.z) |
| |
| guard let destinationBuffer = apiDestinationBuffer.buffer() else { |
| return |
| } |
| var destinationBytesPerRow = UInt(destination.layout.bytesPerRow) |
| if destinationBytesPerRow == WGPU_COPY_STRIDE_UNDEFINED { |
| destinationBytesPerRow = UInt(destinationBuffer.length) |
| } |
| |
| let sourceTextureFormat = sourceTexture.format() |
| let aspectSpecificFormat = WebGPU.Texture.aspectSpecificFormat(sourceTextureFormat, source.aspect) |
| let blockSize = WebGPU.Texture.texelBlockSize(aspectSpecificFormat) |
| let textureDimension = sourceTexture.dimension() |
| var didOverflow: Bool |
| switch textureDimension { |
| case WGPUTextureDimension_1D: |
| if !blockSize.hasOverflowed() { |
| var product: UInt32 = blockSize.value() |
| (product, didOverflow) = product.multipliedReportingOverflow(by: self.m_device.ptr().limitsCopy().maxTextureDimension1D) |
| if !didOverflow { |
| destinationBytesPerRow = min(destinationBytesPerRow, UInt(product)) |
| } |
| } |
| case WGPUTextureDimension_2D, WGPUTextureDimension_3D: |
| if !blockSize.hasOverflowed() { |
| var product: UInt32 = blockSize.value() |
| (product, didOverflow) = product.multipliedReportingOverflow(by: self.m_device.ptr().limitsCopy().maxTextureDimension2D) |
| if !didOverflow { |
| destinationBytesPerRow = min(destinationBytesPerRow, UInt(product)) |
| } |
| } |
| case WGPUTextureDimension_Force32: |
| break |
| default: |
| break |
| } |
| |
| destinationBytesPerRow = roundUpToMultipleOfNonPowerOfTwoCheckedUInt32UnsignedLong(blockSize, destinationBytesPerRow) |
| if textureDimension == WGPUTextureDimension_3D && copySize.depthOrArrayLayers <= 1 && copySize.height <= 1 { |
| destinationBytesPerRow = 0 |
| } |
| |
| var rowsPerImage = destination.layout.rowsPerImage |
| if rowsPerImage == WGPU_COPY_STRIDE_UNDEFINED { |
| rowsPerImage = heightForMetal != 0 ? heightForMetal : 1 |
| } |
| var destinationBytesPerImage = UInt(rowsPerImage) |
| (destinationBytesPerImage, didOverflow) = destinationBytesPerImage.multipliedReportingOverflow(by: destinationBytesPerRow) |
| guard !didOverflow else { |
| return |
| } |
| |
| let maxDestinationBytesPerRow = textureDimension == WGPUTextureDimension_3D ? (2048 * blockSize.value()) : destinationBytesPerRow |
| if destinationBytesPerRow > maxDestinationBytesPerRow { |
| for z in 0..<copySize.depthOrArrayLayers { |
| var zPlusOriginZ = z |
| (zPlusOriginZ, didOverflow) = zPlusOriginZ.addingReportingOverflow(source.origin.z) |
| guard !didOverflow else { |
| return |
| } |
| var zTimesDestinationBytesPerImage = z |
| guard destinationBytesPerImage <= UInt32.max else { |
| return |
| } |
| (zTimesDestinationBytesPerImage, didOverflow) = zTimesDestinationBytesPerImage.multipliedReportingOverflow( |
| by: UInt32(destinationBytesPerImage) |
| ) |
| guard !didOverflow else { |
| return |
| } |
| for y in 0..<copySize.height { |
| var yPlusOriginY = source.origin.y |
| (yPlusOriginY, didOverflow) = yPlusOriginY.addingReportingOverflow(y) |
| guard !didOverflow else { |
| return |
| } |
| var yTimesDestinationBytesPerRow = y |
| guard destinationBytesPerRow <= UInt32.max else { |
| return |
| } |
| (yTimesDestinationBytesPerRow, didOverflow) = yTimesDestinationBytesPerRow.multipliedReportingOverflow( |
| by: UInt32(destinationBytesPerRow) |
| ) |
| guard !didOverflow else { |
| return |
| } |
| let newSource = WGPUImageCopyTexture( |
| texture: source.texture, |
| mipLevel: source.mipLevel, |
| origin: WGPUOrigin3D(x: source.origin.x, y: yPlusOriginY, z: zPlusOriginZ), |
| aspect: source.aspect |
| ) |
| var tripleSum = UInt64(destination.layout.offset) |
| (tripleSum, didOverflow) = tripleSum.addingReportingOverflow(UInt64(zTimesDestinationBytesPerImage)) |
| guard !didOverflow else { |
| return |
| } |
| (tripleSum, didOverflow) = tripleSum.addingReportingOverflow(UInt64(yTimesDestinationBytesPerRow)) |
| guard !didOverflow else { |
| return |
| } |
| let newDestination = WGPUImageCopyBuffer( |
| layout: WGPUTextureDataLayout( |
| offset: tripleSum, |
| bytesPerRow: UInt32(WGPU_COPY_STRIDE_UNDEFINED), |
| rowsPerImage: UInt32(WGPU_COPY_STRIDE_UNDEFINED) |
| ), |
| buffer: destination.buffer |
| ) |
| self.copyTextureToBuffer( |
| source: newSource, |
| destination: newDestination, |
| copySize: WGPUExtent3D( |
| width: copySize.width, |
| height: 1, |
| depthOrArrayLayers: 1 |
| ) |
| ) |
| } |
| } |
| return |
| } |
| |
| guard let blitCommandEncoder = ensureBlitCommandEncoder() else { |
| return |
| } |
| |
| for layer in 0..<copySize.depthOrArrayLayers { |
| var originZPlusLayer = UInt(source.origin.z) |
| (originZPlusLayer, didOverflow) = originZPlusLayer.addingReportingOverflow(UInt(layer)) |
| guard !didOverflow else { |
| return |
| } |
| let sourceSlice = sourceTexture.dimension() == WGPUTextureDimension_3D ? 0 : originZPlusLayer |
| if !sourceTexture.previouslyCleared(source.mipLevel, UInt32(sourceSlice)) { |
| clearTextureIfNeeded(destination: source, slice: sourceSlice) |
| } |
| } |
| |
| guard |
| Self.hasValidDimensions( |
| dimension: sourceTexture.dimension(), |
| width: UInt(widthForMetal), |
| height: UInt(heightForMetal), |
| depth: UInt(depthForMetal) |
| ) |
| else { |
| return |
| } |
| |
| guard destinationBuffer.length >= WebGPU.Texture.bytesPerRow(aspectSpecificFormat, widthForMetal, sourceTexture.sampleCount()) |
| else { |
| return |
| } |
| |
| switch sourceTexture.dimension() { |
| case WGPUTextureDimension_1D: |
| // https://developer.apple.com/documentation/metal/mtlblitcommandencoder/1400756-copyfromtexture?language=objc |
| // "When you copy to a 1D texture, height and depth must be 1." |
| let sourceSize = MTLSize(width: Int(widthForMetal), height: 1, depth: 1) |
| let sourceOrigin = MTLOrigin(x: Int(source.origin.x), y: 0, z: 0) |
| for layer in 0..<copySize.depthOrArrayLayers { |
| var layerTimesDestinationBytesPerImage = UInt(layer) |
| (layerTimesDestinationBytesPerImage, didOverflow) = layerTimesDestinationBytesPerImage.multipliedReportingOverflow( |
| by: destinationBytesPerImage |
| ) |
| guard !didOverflow else { |
| return |
| } |
| var destinationOffset = UInt(destination.layout.offset) |
| (destinationOffset, didOverflow) = destinationOffset.addingReportingOverflow(layerTimesDestinationBytesPerImage) |
| guard !didOverflow else { |
| return |
| } |
| var sourceSlice = UInt(source.origin.z) |
| (sourceSlice, didOverflow) = sourceSlice.addingReportingOverflow(UInt(layer)) |
| guard !didOverflow else { |
| return |
| } |
| var widthTimesBlockSize = UInt(widthForMetal) |
| (widthTimesBlockSize, didOverflow) = widthTimesBlockSize.multipliedReportingOverflow(by: blockSize.value()) |
| guard !didOverflow else { |
| return |
| } |
| var sum = UInt(destinationOffset) |
| (sum, didOverflow) = sum.addingReportingOverflow(UInt(widthTimesBlockSize)) |
| guard !didOverflow else { |
| return |
| } |
| if sum > destinationBuffer.length { |
| continue |
| } |
| blitCommandEncoder |
| .copy( |
| from: sourceTexture.texture(), |
| sourceSlice: Int(sourceSlice), |
| sourceLevel: Int(source.mipLevel), |
| sourceOrigin: sourceOrigin, |
| sourceSize: sourceSize, |
| to: destinationBuffer, |
| destinationOffset: Int(destinationOffset), |
| destinationBytesPerRow: Int(destinationBytesPerRow), |
| destinationBytesPerImage: Int(destinationBytesPerImage), |
| options: options |
| ) |
| } |
| case WGPUTextureDimension_2D: |
| // https://developer.apple.com/documentation/metal/mtlblitcommandencoder/1400756-copyfromtexture?language=objc |
| // "When you copy to a 2D texture, depth must be 1." |
| let sourceSize = MTLSizeMake(Int(widthForMetal), Int(heightForMetal), 1) |
| let sourceOrigin = MTLOriginMake(Int(source.origin.x), Int(source.origin.y), 0) |
| for layer in 0..<copySize.depthOrArrayLayers { |
| var layerTimesBytesPerImage = UInt(layer) |
| (layerTimesBytesPerImage, didOverflow) = layerTimesBytesPerImage.multipliedReportingOverflow(by: destinationBytesPerImage) |
| guard !didOverflow else { |
| return |
| } |
| var destinationOffset = UInt(destination.layout.offset) |
| (destinationOffset, didOverflow) = destinationOffset.addingReportingOverflow(layerTimesBytesPerImage) |
| guard !didOverflow else { |
| return |
| } |
| var sourceSlice = UInt(source.origin.z) |
| (sourceSlice, didOverflow) = sourceSlice.addingReportingOverflow(UInt(layer)) |
| guard !didOverflow else { |
| return |
| } |
| blitCommandEncoder.copy( |
| from: sourceTexture.texture(), |
| sourceSlice: Int(sourceSlice), |
| sourceLevel: Int(source.mipLevel), |
| sourceOrigin: sourceOrigin, |
| sourceSize: sourceSize, |
| to: destinationBuffer, |
| destinationOffset: Int(destinationOffset), |
| destinationBytesPerRow: Int(destinationBytesPerRow), |
| destinationBytesPerImage: Int(destinationBytesPerImage), |
| options: options |
| ) |
| } |
| case WGPUTextureDimension_3D: |
| let sourceSize = MTLSize(width: Int(widthForMetal), height: Int(heightForMetal), depth: Int(depthForMetal)) |
| let sourceOrigin = MTLOrigin(x: Int(source.origin.x), y: Int(source.origin.y), z: Int(source.origin.z)) |
| let destinationOffset = UInt(destination.layout.offset) |
| blitCommandEncoder |
| .copy( |
| from: sourceTexture.texture(), |
| sourceSlice: 0, |
| sourceLevel: Int(source.mipLevel), |
| sourceOrigin: sourceOrigin, |
| sourceSize: sourceSize, |
| to: destinationBuffer, |
| destinationOffset: Int(destinationOffset), |
| destinationBytesPerRow: Int(destinationBytesPerRow), |
| destinationBytesPerImage: Int(destinationBytesPerImage), |
| options: options, |
| ) |
| case WGPUTextureDimension_Force32: |
| return |
| default: |
| return |
| } |
| } |
| |
| func copyBufferToTexture(source: WGPUImageCopyBuffer, destination: WGPUImageCopyTexture, copySize: WGPUExtent3D) { |
| guard prepareTheEncoderState() else { |
| generateInvalidEncoderStateError() |
| return |
| } |
| let destinationTexture = WebGPU.fromAPI(destination.texture) |
| |
| if let error = errorValidatingCopyBufferToTexture(source: source, destination: destination, copySize: copySize) { |
| makeInvalid(error) |
| return |
| } |
| let apiBuffer = WebGPU.fromAPI(source.buffer) |
| apiBuffer.setCommandEncoder(self, false) |
| destinationTexture.setCommandEncoder(self) |
| |
| guard |
| copySize.width != 0 || copySize.height != 0 || copySize.depthOrArrayLayers != 0, |
| !apiBuffer.isDestroyed(), |
| !destinationTexture.isDestroyed() |
| else { |
| return |
| } |
| |
| guard let blitCommandEncoder = ensureBlitCommandEncoder() else { |
| return |
| } |
| var sourceBytesPerRow = UInt(source.layout.bytesPerRow) |
| guard let sourceBuffer = apiBuffer.buffer() else { |
| return |
| } |
| if sourceBytesPerRow == WGPU_COPY_STRIDE_UNDEFINED { |
| sourceBytesPerRow = UInt(sourceBuffer.length) |
| } |
| let aspectSpecificFormat = WebGPU.Texture.aspectSpecificFormat(destinationTexture.format(), destination.aspect) |
| let blockSize = WebGPU.Texture.texelBlockSize(aspectSpecificFormat) |
| // Interesting that swift imports this.. becase I think it knows how to manage WebGPU.Device |
| // It will not import raw pointers it does not know how to manage. |
| let device = m_device.ptr() |
| switch destinationTexture.dimension() { |
| case WGPUTextureDimension_1D: |
| if !blockSize.hasOverflowed() { |
| // swift cannot infer .value()'s type |
| let blockSizeValue: UInt32 = blockSize.value() |
| let (result, didOverflow) = blockSizeValue.multipliedReportingOverflow(by: device.limitsCopy().maxTextureDimension1D) |
| if !didOverflow { |
| sourceBytesPerRow = min(sourceBytesPerRow, UInt(result)) |
| } |
| } |
| case WGPUTextureDimension_2D, WGPUTextureDimension_3D: |
| if !blockSize.hasOverflowed() { |
| // swift cannot infer .value()'s type |
| let blockSizeValue: UInt32 = blockSize.value() |
| let (result, didOverflow) = blockSizeValue.multipliedReportingOverflow(by: device.limitsCopy().maxTextureDimension2D) |
| if !didOverflow { |
| sourceBytesPerRow = min(sourceBytesPerRow, UInt(result)) |
| } |
| } |
| case WGPUTextureDimension_Force32: |
| break |
| default: |
| break |
| } |
| |
| var options: MTLBlitOption = [] |
| switch destination.aspect { |
| case WGPUTextureAspect_StencilOnly: |
| options = .stencilFromDepthStencil |
| case WGPUTextureAspect_DepthOnly: |
| options = .depthFromDepthStencil |
| case WGPUTextureAspect_All: |
| break |
| case WGPUTextureAspect_Force32: |
| return |
| default: |
| return |
| } |
| let logicalSize = WebGPU.fromAPI(destination.texture).logicalMiplevelSpecificTextureExtent(destination.mipLevel) |
| let widthForMetal = logicalSize.width < destination.origin.x ? 0 : min(copySize.width, logicalSize.width - destination.origin.x) |
| let heightForMetal = logicalSize.height < destination.origin.y ? 0 : min(copySize.height, logicalSize.height - destination.origin.y) |
| let depthForMetal = |
| logicalSize.depthOrArrayLayers < destination.origin.z |
| ? 0 : min(copySize.depthOrArrayLayers, logicalSize.depthOrArrayLayers - destination.origin.z) |
| var rowsPerImage = source.layout.rowsPerImage |
| if rowsPerImage == WGPU_COPY_STRIDE_UNDEFINED { |
| rowsPerImage = heightForMetal != 0 ? rowsPerImage : 1 |
| } |
| var sourceBytesPerImage: UInt |
| var didOverflow: Bool |
| (sourceBytesPerImage, didOverflow) = UInt(rowsPerImage).multipliedReportingOverflow(by: sourceBytesPerRow) |
| guard !didOverflow else { |
| return |
| } |
| let mtlDestinationTexture = destinationTexture.texture() |
| let textureDimension = destinationTexture.dimension() |
| |
| let sliceCount = textureDimension == WGPUTextureDimension_3D ? 1 : copySize.depthOrArrayLayers |
| for layer in 0..<sliceCount { |
| var originPlusLayer = destination.origin.z |
| (originPlusLayer, didOverflow) = originPlusLayer.addingReportingOverflow(layer) |
| if didOverflow { |
| return |
| } |
| let destinationSlice = destinationTexture.dimension() == WGPUTextureDimension_3D ? 0 : originPlusLayer |
| |
| guard let mtlDestinationTexture else { |
| fatalError("mtlDestinationTexture is nil") |
| } |
| |
| precondition(mtlDestinationTexture.parent == nil, "mtlDestinationTexture.parentTexture is not nil") |
| |
| if WebGPU.Queue.writeWillCompletelyClear( |
| textureDimension, |
| widthForMetal, |
| logicalSize.width, |
| heightForMetal, |
| logicalSize.height, |
| depthForMetal, |
| logicalSize.depthOrArrayLayers |
| ) { |
| // FIXME: rdar://138042799 remove default argument. |
| destinationTexture.setPreviouslyCleared(destination.mipLevel, destinationSlice, true) |
| } else { |
| clearTextureIfNeeded(destination: destination, slice: UInt(destinationSlice)) |
| } |
| } |
| let maxSourceBytesPerRow = |
| textureDimension == WGPUTextureDimension_3D ? (2048 * blockSize.value()) : sourceBytesPerRow |
| if textureDimension == WGPUTextureDimension_3D && copySize.depthOrArrayLayers <= 1 && copySize.height <= 1 { |
| sourceBytesPerRow = 0 |
| } |
| if sourceBytesPerRow > maxSourceBytesPerRow { |
| for z in 0..<copySize.depthOrArrayLayers { |
| var destinationOriginPlusZ = destination.origin.z |
| (destinationOriginPlusZ, didOverflow) = destinationOriginPlusZ.addingReportingOverflow(z) |
| guard !didOverflow else { |
| return |
| } |
| var zTimesSourceBytesPerImage = z |
| guard let sourceBytesPerImageU32 = UInt32(exactly: sourceBytesPerImage) else { |
| return |
| } |
| (zTimesSourceBytesPerImage, didOverflow) = zTimesSourceBytesPerImage.multipliedReportingOverflow(by: sourceBytesPerImageU32) |
| guard !didOverflow else { |
| return |
| } |
| guard let sourceBytesPerRowU32 = UInt32(exactly: sourceBytesPerRow) else { |
| return |
| } |
| for y in 0..<copySize.height { |
| var yTimesSourceBytesPerRow = y |
| (yTimesSourceBytesPerRow, didOverflow) = yTimesSourceBytesPerRow.multipliedReportingOverflow(by: sourceBytesPerRowU32) |
| guard !didOverflow else { |
| return |
| } |
| var tripleSum = UInt64(zTimesSourceBytesPerImage) |
| (tripleSum, didOverflow) = tripleSum.addingReportingOverflow(UInt64(yTimesSourceBytesPerRow)) |
| guard !didOverflow else { |
| return |
| } |
| (tripleSum, didOverflow) = tripleSum.addingReportingOverflow(UInt64(source.layout.offset)) |
| guard !didOverflow else { |
| return |
| } |
| let newSource = WGPUImageCopyBuffer( |
| layout: WGPUTextureDataLayout( |
| offset: tripleSum, |
| bytesPerRow: UInt32(WGPU_COPY_STRIDE_UNDEFINED), |
| rowsPerImage: UInt32(WGPU_COPY_STRIDE_UNDEFINED) |
| ), |
| buffer: source.buffer, |
| ) |
| var destinationOriginPlusY = y |
| (destinationOriginPlusY, didOverflow) = destinationOriginPlusY.addingReportingOverflow(destination.origin.y) |
| guard !didOverflow else { |
| return |
| } |
| let newDestination = WGPUImageCopyTexture( |
| texture: destination.texture, |
| mipLevel: destination.mipLevel, |
| origin: WGPUOrigin3D( |
| x: destination.origin.x, |
| y: destinationOriginPlusY, |
| z: destinationOriginPlusZ |
| ), |
| aspect: destination.aspect |
| ) |
| copyBufferToTexture( |
| source: newSource, |
| destination: newDestination, |
| copySize: WGPUExtent3D(width: copySize.width, height: 1, depthOrArrayLayers: 1), |
| ) |
| } |
| } |
| return |
| } |
| |
| guard sourceBuffer.length >= WebGPU.Texture.bytesPerRow(aspectSpecificFormat, widthForMetal, destinationTexture.sampleCount()) |
| else { |
| return |
| } |
| |
| switch destinationTexture.dimension() { |
| case WGPUTextureDimension_1D: |
| // https://developer.apple.com/documentation/metal/mtlblitcommandencoder/1400771-copyfrombuffer?language=objc |
| // "When you copy to a 1D texture, height and depth must be 1." |
| let sourceSize = MTLSize(width: Int(widthForMetal), height: 1, depth: 1) |
| guard widthForMetal != 0 else { |
| return |
| } |
| |
| let destinationOrigin = MTLOrigin(x: Int(destination.origin.x), y: 0, z: 0) |
| var widthTimesBlockSize = widthForMetal |
| (widthTimesBlockSize, didOverflow) = widthTimesBlockSize.multipliedReportingOverflow(by: blockSize.value()) |
| guard !didOverflow else { |
| return |
| } |
| sourceBytesPerRow = min(sourceBytesPerRow, UInt(widthTimesBlockSize)) |
| for layer in 0..<copySize.depthOrArrayLayers { |
| var layerTimesSourceBytesPerImage = UInt(layer) |
| (layerTimesSourceBytesPerImage, didOverflow) = layerTimesSourceBytesPerImage.multipliedReportingOverflow( |
| by: sourceBytesPerImage |
| ) |
| guard !didOverflow else { |
| return |
| } |
| |
| var sourceOffset = UInt(source.layout.offset) |
| (sourceOffset, didOverflow) = sourceOffset.addingReportingOverflow(layerTimesSourceBytesPerImage) |
| guard !didOverflow else { |
| return |
| } |
| var destinationSlice = UInt(destination.origin.z) |
| (destinationSlice, didOverflow) = destinationSlice.addingReportingOverflow(UInt(layer)) |
| guard !didOverflow else { |
| return |
| } |
| var sourceOffsetPlusSourceBytesPerRow = sourceOffset |
| (sourceOffsetPlusSourceBytesPerRow, didOverflow) = sourceOffsetPlusSourceBytesPerRow.addingReportingOverflow( |
| sourceBytesPerRow |
| ) |
| guard !didOverflow else { |
| return |
| } |
| if sourceOffsetPlusSourceBytesPerRow > sourceBuffer.length { |
| continue |
| } |
| |
| // FIXME: (rdar://170907276) Prove that `mtlDestinationTexture` can never be nil. |
| // swift-format-ignore: NeverForceUnwrap |
| blitCommandEncoder |
| .copy( |
| from: sourceBuffer, |
| sourceOffset: Int(sourceOffset), |
| sourceBytesPerRow: Int(sourceBytesPerRow), |
| sourceBytesPerImage: Int(sourceBytesPerImage), |
| sourceSize: sourceSize, |
| to: mtlDestinationTexture!, |
| destinationSlice: Int(destinationSlice), |
| destinationLevel: Int(destination.mipLevel), |
| destinationOrigin: destinationOrigin, |
| options: options |
| ) |
| } |
| case WGPUTextureDimension_2D: |
| // https://developer.apple.com/documentation/metal/mtlblitcommandencoder/1400771-copyfrombuffer?language=objc |
| // "When you copy to a 2D texture, depth must be 1." |
| let sourceSize = MTLSize(width: Int(widthForMetal), height: Int(heightForMetal), depth: 1) |
| guard widthForMetal != 0 && heightForMetal != 0 else { |
| return |
| } |
| |
| let destinationOrigin = MTLOrigin(x: Int(destination.origin.x), y: Int(destination.origin.y), z: 0) |
| for layer in 0..<copySize.depthOrArrayLayers { |
| var layerTimesSourceBytesPerImage = UInt(layer) |
| (layerTimesSourceBytesPerImage, didOverflow) = layerTimesSourceBytesPerImage.multipliedReportingOverflow( |
| by: sourceBytesPerImage |
| ) |
| guard !didOverflow else { |
| return |
| } |
| var sourceOffset = UInt(source.layout.offset) |
| (sourceOffset, didOverflow) = sourceOffset.addingReportingOverflow(layerTimesSourceBytesPerImage) |
| guard !didOverflow else { |
| return |
| } |
| var destinationSlice = UInt(destination.origin.z) |
| (destinationSlice, didOverflow) = destinationSlice.addingReportingOverflow(UInt(layer)) |
| guard !didOverflow else { |
| return |
| } |
| |
| // FIXME: (rdar://170907276) Prove that `mtlDestinationTexture` can never be nil. |
| // swift-format-ignore: NeverForceUnwrap |
| blitCommandEncoder |
| .copy( |
| from: sourceBuffer, |
| sourceOffset: Int(sourceOffset), |
| sourceBytesPerRow: Int(sourceBytesPerRow), |
| sourceBytesPerImage: Int(sourceBytesPerImage), |
| sourceSize: sourceSize, |
| to: mtlDestinationTexture!, |
| destinationSlice: Int(destinationSlice), |
| destinationLevel: Int(destination.mipLevel), |
| destinationOrigin: destinationOrigin, |
| options: options |
| ) |
| } |
| |
| case WGPUTextureDimension_3D: |
| let sourceSize = MTLSize(width: Int(widthForMetal), height: Int(heightForMetal), depth: Int(depthForMetal)) |
| guard widthForMetal != 0 && heightForMetal != 0 && depthForMetal != 0 else { |
| return |
| } |
| |
| let destinationOrigin = MTLOrigin(x: Int(destination.origin.x), y: Int(destination.origin.y), z: Int(destination.origin.z)) |
| let sourceOffset = UInt(source.layout.offset) |
| |
| // FIXME: (rdar://170907276) Prove that `mtlDestinationTexture` can never be nil. |
| // swift-format-ignore: NeverForceUnwrap |
| blitCommandEncoder.copy( |
| from: sourceBuffer, |
| sourceOffset: Int(sourceOffset), |
| sourceBytesPerRow: Int(sourceBytesPerRow), |
| sourceBytesPerImage: Int(sourceBytesPerImage), |
| sourceSize: sourceSize, |
| to: mtlDestinationTexture!, |
| destinationSlice: 0, |
| destinationLevel: Int(destination.mipLevel), |
| destinationOrigin: destinationOrigin, |
| options: options |
| ) |
| case WGPUTextureDimension_Force32: |
| assertionFailure() |
| return |
| default: |
| assertionFailure() |
| return |
| } |
| } |
| |
| func clearBuffer(buffer: WebGPU.Buffer, offset: UInt64, size: inout UInt64) { |
| guard self.prepareTheEncoderState() else { |
| self.generateInvalidEncoderStateError() |
| return |
| } |
| if size == .max { |
| let initialSize = buffer.initialSize() |
| let (subtractionResult, didOverflow) = initialSize.subtractingReportingOverflow(offset) |
| if didOverflow { |
| self.m_device.ptr().generateAValidationError("CommandEncoder::clearBuffer(): offset > buffer.size") |
| return |
| } |
| size = subtractionResult |
| } |
| |
| if !self.validateClearBuffer(buffer, offset, size) { |
| self.makeInvalid("GPUCommandEncoder.clearBuffer validation failed") |
| return |
| } |
| // FIXME: rdar://138042799 need to pass in the default argument. |
| buffer.setCommandEncoder(self, false) |
| buffer.indirectBufferInvalidated(self) |
| guard let offsetInt = Int(exactly: offset), let sizeInt = Int(exactly: size) else { |
| return |
| } |
| let range = offsetInt..<(offsetInt + sizeInt) |
| if buffer.isDestroyed() || sizeInt == 0 || range.upperBound > buffer.buffer().length { |
| return |
| } |
| guard let blitCommandEncoder = ensureBlitCommandEncoder() else { |
| return |
| } |
| blitCommandEncoder.fill(buffer: buffer.buffer(), range: range, value: 0) |
| } |
| |
| func resolveQuerySet( |
| _ querySet: WebGPU.QuerySet, |
| firstQuery: UInt32, |
| queryCount: UInt32, |
| destination: WebGPU.Buffer, |
| destinationOffset: UInt64 |
| ) { |
| guard self.prepareTheEncoderState() else { |
| self.generateInvalidEncoderStateError() |
| return |
| } |
| |
| let isValid = validateResolveQuerySet( |
| querySet: querySet, |
| firstQuery: firstQuery, |
| queryCount: queryCount, |
| destination: destination, |
| destinationOffset: destinationOffset |
| ) |
| |
| guard |
| isValid, |
| CxxBridging.isValidToUseWithQuerySetCommandEncoder(querySet, self), |
| CxxBridging.isValidToUseWithBufferCommandEncoder(destination, self) |
| else { |
| makeInvalid("GPUCommandEncoder.resolveQuerySet validation failed") |
| return |
| } |
| |
| querySet.setCommandEncoder(self) |
| // FIXME: rdar://138042799 need to pass in the default argument. |
| destination.setCommandEncoder(self, false) |
| destination.indirectBufferInvalidated(self) |
| |
| if querySet.isDestroyed() || destination.isDestroyed() || queryCount == 0 { |
| return |
| } |
| |
| if querySet.type() == WGPUQueryType_Occlusion { |
| guard let blitCommandEncoder = ensureBlitCommandEncoder() else { |
| return |
| } |
| guard |
| let sourceOffset = Int(exactly: 8 * firstQuery), |
| let destinationOffsetChecked = Int(exactly: destinationOffset), |
| let size = Int(exactly: 8 * queryCount) |
| else { |
| return |
| } |
| blitCommandEncoder.copy( |
| from: querySet.visibilityBuffer(), |
| sourceOffset: sourceOffset, |
| to: destination.buffer(), |
| destinationOffset: destinationOffsetChecked, |
| size: size |
| ) |
| } |
| |
| if querySet.type() == WGPUQueryType_Timestamp { |
| // FIXME: https://bugs.webkit.org/show_bug.cgi?id=283385 - https://bugs.webkit.org/show_bug.cgi?id=283088 should be reverted when the blocking issue is resolved |
| finalizeBlitCommandEncoder() |
| let workaround = m_device.ptr().resolveTimestampsSharedEvent() |
| // The signal value does not matter, the event alone prevents reordering. |
| m_commandBuffer?.encodeSignalEvent(workaround, value: 1) |
| m_commandBuffer?.encodeWaitForEvent(workaround, value: 1) |
| guard ensureBlitCommandEncoder() != nil else { |
| return |
| } |
| let counterSampleBuffer = querySet.counterSampleBufferWithOffset() |
| guard let buffer = counterSampleBuffer.buffer else { |
| return |
| } |
| |
| m_blitCommandEncoder? |
| .resolveCounters( |
| buffer, |
| range: Int(firstQuery + counterSampleBuffer.offset)..<Int(firstQuery + counterSampleBuffer.offset + queryCount), |
| destinationBuffer: destination.buffer(), |
| destinationOffset: Int(destinationOffset) |
| ) |
| } |
| } |
| |
| func validateResolveQuerySet( |
| querySet: WebGPU.QuerySet, |
| firstQuery: UInt32, |
| queryCount: UInt32, |
| destination: WebGPU.Buffer, |
| destinationOffset: UInt64 |
| ) -> Bool { |
| if !querySet.isDestroyed() && !querySet.isValid() { |
| return false |
| } |
| if !destination.isDestroyed() && !destination.isValid() { |
| return false |
| } |
| if (destination.usage() & WGPUBufferUsage_QueryResolve.rawValue) == 0 { |
| return false |
| } |
| |
| if firstQuery >= querySet.count() { |
| return false |
| } |
| |
| let (countEnd, didOverflow) = firstQuery.addingReportingOverflow(queryCount) |
| if didOverflow || countEnd > querySet.count() { |
| return false |
| } |
| |
| if (destinationOffset % 256) != 0 { |
| return false |
| } |
| |
| let (queryCountTimes8, didOverflowMul) = UInt64(queryCount).multipliedReportingOverflow(by: 8) |
| if didOverflowMul { |
| return false |
| } |
| |
| let (countTimes8PlusOffset, didOverflowSum) = destinationOffset.addingReportingOverflow(UInt64(queryCountTimes8)) |
| if didOverflowSum || countTimes8PlusOffset > destination.initialSize() { |
| return false |
| } |
| |
| return true |
| } |
| |
| func copyTextureToTexture(source: WGPUImageCopyTexture, destination: WGPUImageCopyTexture, copySize: WGPUExtent3D) { |
| // https://gpuweb.github.io/gpuweb/#dom-gpucommandencoder-copytexturetotexture |
| |
| guard self.prepareTheEncoderState() else { |
| self.generateInvalidEncoderStateError() |
| return |
| } |
| if let error = self.errorValidatingCopyTextureToTexture(source: source, destination: destination, copySize: copySize) { |
| self.makeInvalid(error) |
| return |
| } |
| |
| let sourceTexture = WebGPU.fromAPI(source.texture) |
| let destinationTexture = WebGPU.fromAPI(destination.texture) |
| sourceTexture.setCommandEncoder(self) |
| destinationTexture.setCommandEncoder(self) |
| |
| guard !sourceTexture.isDestroyed(), !destinationTexture.isDestroyed() else { |
| return |
| } |
| |
| guard let blitCommandEncoder = ensureBlitCommandEncoder() else { |
| return |
| } |
| |
| let destinationTextureDimension = destinationTexture.dimension() |
| let sliceCount = destinationTextureDimension == WGPUTextureDimension_3D ? 1 : copySize.depthOrArrayLayers |
| let destinationLogicalSize = destinationTexture.logicalMiplevelSpecificTextureExtent(destination.mipLevel) |
| var didOverflow: Bool |
| for layer in 0..<sliceCount { |
| var sourceOriginPlusLayer = UInt(source.origin.z) |
| (sourceOriginPlusLayer, didOverflow) = sourceOriginPlusLayer.addingReportingOverflow(UInt(layer)) |
| guard !didOverflow else { |
| return |
| } |
| let sourceSlice = sourceTexture.dimension() == WGPUTextureDimension_3D ? 0 : sourceOriginPlusLayer |
| self.clearTextureIfNeeded(destination: source, slice: sourceSlice) |
| var destinationOriginPlusLayer = UInt(destination.origin.z) |
| (destinationOriginPlusLayer, didOverflow) = destinationOriginPlusLayer.addingReportingOverflow(UInt(layer)) |
| guard !didOverflow else { |
| return |
| } |
| let destinationSlice: UInt = destinationTexture.dimension() == WGPUTextureDimension_3D ? 0 : destinationOriginPlusLayer |
| if WebGPU.Queue.writeWillCompletelyClear( |
| destinationTextureDimension, |
| copySize.width, |
| destinationLogicalSize.width, |
| copySize.height, |
| destinationLogicalSize.height, |
| copySize.depthOrArrayLayers, |
| destinationLogicalSize.depthOrArrayLayers |
| ) { |
| guard let destinationSliceUInt32 = UInt32(exactly: destinationSlice) else { |
| return |
| } |
| // FIXME: rdar://138042799 remove default argument. |
| destinationTexture.setPreviouslyCleared(destination.mipLevel, destinationSliceUInt32, true) |
| } else { |
| self.clearTextureIfNeeded(destination: destination, slice: destinationSlice) |
| } |
| } |
| guard |
| let mtlDestinationTexture = destinationTexture.texture(), |
| let mtlSourceTexture = WebGPU.fromAPI(source.texture).texture() |
| else { |
| return |
| } |
| |
| // FIXME(PERFORMANCE): Is it actually faster to use the -[MTLBlitCommandEncoder copyFromTexture:...toTexture:...levelCount:] |
| // variant, where possible, rather than calling the other variant in a loop? |
| switch sourceTexture.dimension() { |
| case WGPUTextureDimension_1D: |
| // https://developer.apple.com/documentation/metal/mtlblitcommandencoder/1400756-copyfromtexture?language=objc |
| // "When you copy to a 1D texture, height and depth must be 1." |
| let sourceSize = MTLSize(width: Int(copySize.width), height: 1, depth: 1) |
| guard sourceSize.width != 0 else { |
| return |
| } |
| |
| let sourceOrigin = MTLOrigin(x: Int(source.origin.x), y: 0, z: 0) |
| let destinationOrigin = MTLOrigin(x: Int(destination.origin.x), y: 0, z: 0) |
| for layer in 0..<copySize.depthOrArrayLayers { |
| var sourceSlice = UInt(source.origin.z) |
| (sourceSlice, didOverflow) = sourceSlice.addingReportingOverflow(UInt(layer)) |
| guard !didOverflow else { |
| return |
| } |
| var destinationSlice = UInt(destination.origin.z) |
| (destinationSlice, didOverflow) = destinationSlice.addingReportingOverflow(UInt(layer)) |
| guard !didOverflow else { |
| return |
| } |
| if destinationSlice >= mtlDestinationTexture.arrayLength || sourceSlice >= mtlSourceTexture.arrayLength { |
| continue |
| } |
| blitCommandEncoder.copy( |
| from: mtlSourceTexture, |
| sourceSlice: Int(sourceSlice), |
| sourceLevel: Int(source.mipLevel), |
| sourceOrigin: sourceOrigin, |
| sourceSize: sourceSize, |
| to: mtlDestinationTexture, |
| destinationSlice: Int(destinationSlice), |
| destinationLevel: Int(destination.mipLevel), |
| destinationOrigin: destinationOrigin |
| ) |
| } |
| case WGPUTextureDimension_2D: |
| // https://developer.apple.com/documentation/metal/mtlblitcommandencoder/1400756-copyfromtexture?language=objc |
| // "When you copy to a 2D texture, depth must be 1." |
| let sourceSize = MTLSize(width: Int(copySize.width), height: Int(copySize.height), depth: 1) |
| guard sourceSize.width != 0, sourceSize.height != 0 else { |
| return |
| } |
| |
| let sourceOrigin = MTLOrigin(x: Int(source.origin.x), y: Int(source.origin.y), z: 0) |
| let destinationOrigin = MTLOrigin(x: Int(destination.origin.x), y: Int(destination.origin.y), z: 0) |
| |
| for layer in 0..<copySize.depthOrArrayLayers { |
| var sourceSlice = UInt(source.origin.z) |
| (sourceSlice, didOverflow) = sourceSlice.addingReportingOverflow(UInt(layer)) |
| guard !didOverflow else { |
| return |
| } |
| var destinationSlice = UInt(destination.origin.z) |
| (destinationSlice, didOverflow) = destinationSlice.addingReportingOverflow(UInt(layer)) |
| guard !didOverflow else { |
| return |
| } |
| if destinationSlice >= mtlDestinationTexture.arrayLength || sourceSlice >= mtlSourceTexture.arrayLength { |
| continue |
| } |
| blitCommandEncoder.copy( |
| from: mtlSourceTexture, |
| sourceSlice: Int(sourceSlice), |
| sourceLevel: Int(source.mipLevel), |
| sourceOrigin: sourceOrigin, |
| sourceSize: (sourceSize), |
| to: mtlDestinationTexture, |
| destinationSlice: Int(destinationSlice), |
| destinationLevel: Int(destination.mipLevel), |
| destinationOrigin: destinationOrigin |
| ) |
| } |
| case WGPUTextureDimension_3D: |
| let sourceSize = MTLSize(width: Int(copySize.width), height: Int(copySize.height), depth: Int(copySize.depthOrArrayLayers)) |
| guard sourceSize.width != 0, sourceSize.height != 0, sourceSize.depth != 0 else { |
| return |
| } |
| var originPlusSourceSize = UInt32(destination.origin.z) |
| guard let sourceSizeDepthUInt32 = UInt32(exactly: sourceSize.depth) else { |
| return |
| } |
| (originPlusSourceSize, didOverflow) = originPlusSourceSize.addingReportingOverflow(sourceSizeDepthUInt32) |
| guard !didOverflow else { |
| return |
| } |
| guard let mtlDestinationTextureDepthUInt32 = UInt32(exactly: mtlDestinationTexture.depth) else { |
| return |
| } |
| guard originPlusSourceSize <= min(destinationLogicalSize.depthOrArrayLayers, mtlDestinationTextureDepthUInt32) else { |
| makeInvalid( |
| "GPUCommandEncoder.copyTextureToTexture: destination.origin.z + sourceSize.depth > destinationLogicalSize.depthOrArrayLayers" |
| ) |
| return |
| } |
| |
| let sourceOrigin = MTLOrigin(x: Int(source.origin.x), y: Int(source.origin.y), z: Int(source.origin.z)) |
| let destinationOrigin = MTLOrigin(x: Int(destination.origin.x), y: Int(destination.origin.y), z: Int(destination.origin.z)) |
| blitCommandEncoder.copy( |
| from: mtlSourceTexture, |
| sourceSlice: 0, |
| sourceLevel: Int(source.mipLevel), |
| sourceOrigin: sourceOrigin, |
| sourceSize: (sourceSize), |
| to: mtlDestinationTexture, |
| destinationSlice: 0, |
| destinationLevel: Int(destination.mipLevel), |
| destinationOrigin: destinationOrigin |
| ) |
| case WGPUTextureDimension_Force32: |
| assertionFailure() |
| return |
| default: |
| assertionFailure() |
| return |
| } |
| } |
| |
| func beginComputePass(descriptor: WGPUComputePassDescriptor) -> CxxBridging.RefComputePassEncoder { |
| let collection = CollectionOfOne(descriptor) |
| |
| guard prepareTheEncoderState() else { |
| self.generateInvalidEncoderStateError() |
| return WebGPU.ComputePassEncoder.createInvalid(self, m_device.ptr(), "encoder state is invalid") |
| } |
| |
| if let error = self.errorValidatingComputePassDescriptor(descriptor: descriptor) { |
| return WebGPU.ComputePassEncoder.createInvalid(self, m_device.ptr(), String(error)) |
| } |
| |
| // FIXME: Use accessor functions instead of member variables directly. |
| // swift-format-ignore: AlwaysUseLowerCamelCase |
| if let m_commandBuffer, m_commandBuffer.status.rawValue >= MTLCommandBufferStatus.enqueued.rawValue { |
| return WebGPU.ComputePassEncoder.createInvalid(self, m_device.ptr(), "command buffer has already been committed") |
| } |
| |
| self.finalizeBlitCommandEncoder() |
| |
| guard m_device.ptr().isValid() else { |
| return WebGPU.ComputePassEncoder.createInvalid( |
| self, |
| m_device.ptr(), |
| "GPUDevice was invalid, this will be an error submitting the command buffer" |
| ) |
| } |
| let computePassDescriptor = MTLComputePassDescriptor() |
| computePassDescriptor.dispatchType = .serial |
| var counterSampleBuffer = WebGPU.QuerySet.CounterSampleBuffer() |
| if let wgpuTimestampWrites = wgpuGetComputePassDescriptorTimestampWrites(collection.span)?[0] { |
| let timestampsWrites = WebGPU.fromAPI(wgpuTimestampWrites.querySet) |
| counterSampleBuffer = timestampsWrites.counterSampleBufferWithOffset() |
| timestampsWrites.setCommandEncoder(self) |
| } |
| |
| if m_device.ptr().enableEncoderTimestamps() || counterSampleBuffer.buffer != nil { |
| // FIXME: (rdar://170907276) Prove that the result of `wgpuGetComputePassDescriptorTimestampWrites` can never be nil. |
| // swift-format-ignore: NeverForceUnwrap |
| let timestampWrites = wgpuGetComputePassDescriptorTimestampWrites(collection.span)![0] |
| computePassDescriptor.sampleBufferAttachments[0].sampleBuffer = |
| counterSampleBuffer.buffer ?? m_device.ptr().timestampsBuffer(m_commandBuffer, 2) |
| |
| computePassDescriptor.sampleBufferAttachments[0].startOfEncoderSampleIndex = timestampWriteIndex( |
| writeIndex: timestampWrites.beginningOfPassWriteIndex, |
| defaultValue: MTLCounterDontSample, |
| offset: counterSampleBuffer.offset |
| ) |
| |
| computePassDescriptor.sampleBufferAttachments[0].endOfEncoderSampleIndex = timestampWriteIndex( |
| writeIndex: timestampWrites.endOfPassWriteIndex, |
| defaultValue: MTLCounterDontSample, |
| offset: counterSampleBuffer.offset |
| ) |
| |
| if let buffer = counterSampleBuffer.buffer { |
| m_device.ptr().trackTimestampsBuffer(m_commandBuffer, buffer) |
| } |
| } |
| guard let computeCommandEncoder = m_commandBuffer?.makeComputeCommandEncoder(descriptor: computePassDescriptor) else { |
| return WebGPU.ComputePassEncoder.createInvalid(self, m_device.ptr(), "computeCommandEncoder is null") |
| } |
| |
| self.setExistingEncoder(computeCommandEncoder) |
| // FIXME: Figure out a way so that WTFString does not override String in the global |
| // namespace. At the moment it is and that's why we need this. |
| computeCommandEncoder.label = CxxBridging.convertWTFStringToNSString(descriptor.label) |
| |
| return WebGPU.ComputePassEncoder.create(computeCommandEncoder, descriptor, self, m_device.ptr()) |
| } |
| } |