blob: 659c75016cd5a7f2e5ce60ac8f14afd5ea5f31a9 [file]
// 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())
}
}