mirror of
				https://github.com/GayPizzaSpecifications/voxelotl-engine.git
				synced 2025-11-04 10:59:39 +00:00 
			
		
		
		
	renderer: internal support for blend modes
This commit is contained in:
		@ -46,6 +46,8 @@ add_executable(Voxelotl MACOSX_BUNDLE
 | 
				
			|||||||
  Renderer/Environment.swift
 | 
					  Renderer/Environment.swift
 | 
				
			||||||
  Renderer/Mesh.swift
 | 
					  Renderer/Mesh.swift
 | 
				
			||||||
  Renderer/ModelBatch.swift
 | 
					  Renderer/ModelBatch.swift
 | 
				
			||||||
 | 
					  Renderer/BlendMode.swift
 | 
				
			||||||
 | 
					  Renderer/BlendFunc.swift
 | 
				
			||||||
  Renderer/ChunkRenderer.swift
 | 
					  Renderer/ChunkRenderer.swift
 | 
				
			||||||
  Renderer/Renderer.swift
 | 
					  Renderer/Renderer.swift
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -14,9 +14,9 @@ class Game: GameDelegate {
 | 
				
			|||||||
  func create(_ renderer: Renderer) {
 | 
					  func create(_ renderer: Renderer) {
 | 
				
			||||||
    self.chunkRenderer = ChunkRenderer(renderer: renderer)
 | 
					    self.chunkRenderer = ChunkRenderer(renderer: renderer)
 | 
				
			||||||
    self.chunkRenderer.material = .init(
 | 
					    self.chunkRenderer.material = .init(
 | 
				
			||||||
      ambient:  Color(rgba8888: 0x4F4F4F00).linear,
 | 
					      ambient:  Color(rgb888: 0x4F4F4F).linear,
 | 
				
			||||||
      diffuse:  Color(rgba8888: 0xDFDFDF00).linear,
 | 
					      diffuse:  Color(rgb888: 0xDFDFDF).linear,
 | 
				
			||||||
      specular: Color(rgba8888: 0x2F2F2F00).linear,
 | 
					      specular: Color(rgb888: 0x2F2F2F).linear,
 | 
				
			||||||
      gloss: 75)
 | 
					      gloss: 75)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    self.resetPlayer()
 | 
					    self.resetPlayer()
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										41
									
								
								Sources/Voxelotl/Renderer/BlendFunc.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								Sources/Voxelotl/Renderer/BlendFunc.swift
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,41 @@
 | 
				
			|||||||
 | 
					internal enum BlendFunc: Hashable {
 | 
				
			||||||
 | 
					  case off
 | 
				
			||||||
 | 
					  case on(src: BlendFuncSourceFactor = .one, dst: BlendFuncDestinationFactor = .zero, equation: BlendFuncEquation = .add)
 | 
				
			||||||
 | 
					  case separate(
 | 
				
			||||||
 | 
					    srcColor: BlendFuncSourceFactor,      srcAlpha: BlendFuncSourceFactor,
 | 
				
			||||||
 | 
					    dstColor: BlendFuncDestinationFactor, dstAlpha: BlendFuncDestinationFactor,
 | 
				
			||||||
 | 
					    equColor: BlendFuncEquation,          equAlpha: BlendFuncEquation)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					enum BlendFuncSourceFactor: Hashable {
 | 
				
			||||||
 | 
					  case zero, one
 | 
				
			||||||
 | 
					  case srcColor, oneMinusSrcColor
 | 
				
			||||||
 | 
					  case dstColor, oneMinusDstColor
 | 
				
			||||||
 | 
					  case srcAlpha, oneMinusSrcAlpha
 | 
				
			||||||
 | 
					  case dstAlpha, oneMinusDstAlpha
 | 
				
			||||||
 | 
					  /*
 | 
				
			||||||
 | 
					  case constantColor, oneMinusConstantColor
 | 
				
			||||||
 | 
					  case constantAlpha, oneMinusConstantAlpha
 | 
				
			||||||
 | 
					  */
 | 
				
			||||||
 | 
					  case srcAlphaSaturate
 | 
				
			||||||
 | 
					  case src1Color, oneMinusSrc1Color
 | 
				
			||||||
 | 
					  case src1Alpha, oneMinusSrc1Alpha
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					enum BlendFuncDestinationFactor: Hashable {
 | 
				
			||||||
 | 
					  case zero, one
 | 
				
			||||||
 | 
					  case srcColor, oneMinusSrcColor
 | 
				
			||||||
 | 
					  case dstColor, oneMinusDstColor
 | 
				
			||||||
 | 
					  case srcAlpha, oneMinusSrcAlpha
 | 
				
			||||||
 | 
					  case dstAlpha, oneMinusDstAlpha
 | 
				
			||||||
 | 
					  /*
 | 
				
			||||||
 | 
					  case constantColor, oneMinusConstantColor
 | 
				
			||||||
 | 
					  case constantAlpha, oneMinusConstantAlpha
 | 
				
			||||||
 | 
					  */
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					enum BlendFuncEquation: Hashable {
 | 
				
			||||||
 | 
					  case add
 | 
				
			||||||
 | 
					  case subtract, reverseSubtract
 | 
				
			||||||
 | 
					  case min, max
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										23
									
								
								Sources/Voxelotl/Renderer/BlendMode.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								Sources/Voxelotl/Renderer/BlendMode.swift
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,23 @@
 | 
				
			|||||||
 | 
					public enum BlendMode: Hashable {
 | 
				
			||||||
 | 
					  case none
 | 
				
			||||||
 | 
					  case normal
 | 
				
			||||||
 | 
					  case premultiplied
 | 
				
			||||||
 | 
					  case additive
 | 
				
			||||||
 | 
					  case screen
 | 
				
			||||||
 | 
					  case multiply
 | 
				
			||||||
 | 
					  case subtract
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					internal extension BlendMode {
 | 
				
			||||||
 | 
					  var function: BlendFunc {
 | 
				
			||||||
 | 
					    switch self {
 | 
				
			||||||
 | 
					    case .none: .off
 | 
				
			||||||
 | 
					    case .normal:        .on(src: .srcAlpha,         dst: .oneMinusSrcAlpha, equation: .add)
 | 
				
			||||||
 | 
					    case .premultiplied: .on(src: .one,              dst: .oneMinusSrcAlpha, equation: .add)
 | 
				
			||||||
 | 
					    case .additive:      .on(src: .srcAlpha,         dst: .one,              equation: .add)
 | 
				
			||||||
 | 
					    case .screen:        .on(src: .one,              dst: .oneMinusSrcColor, equation: .add)
 | 
				
			||||||
 | 
					    case .multiply:      .on(src: .dstColor,         dst: .one,              equation: .add)
 | 
				
			||||||
 | 
					    case .subtract:      .on(src: .oneMinusSrcAlpha, dst: .one,              equation: .subtract)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -10,14 +10,15 @@ fileprivate let depthFormat: MTLPixelFormat = .depth32Float
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
public class Renderer {
 | 
					public class Renderer {
 | 
				
			||||||
  private var device: MTLDevice
 | 
					  private var device: MTLDevice
 | 
				
			||||||
  private var layer: CAMetalLayer
 | 
					  private var _layer: CAMetalLayer
 | 
				
			||||||
  private var backBufferSize: Size<Int>
 | 
					  private var backBufferSize: Size<Int>
 | 
				
			||||||
  private var _clearColor: Color<Double>
 | 
					  private var _clearColor: Color<Double>
 | 
				
			||||||
  private var _aspectRatio: Float
 | 
					  private var _aspectRatio: Float
 | 
				
			||||||
  private var queue: MTLCommandQueue
 | 
					  private var queue: MTLCommandQueue
 | 
				
			||||||
  private var lib: MTLLibrary
 | 
					  private var lib: MTLLibrary
 | 
				
			||||||
 | 
					  private var _defaultShader: Shader, _shader2D: Shader
 | 
				
			||||||
  private let passDescription = MTLRenderPassDescriptor()
 | 
					  private let passDescription = MTLRenderPassDescriptor()
 | 
				
			||||||
  private var pso: MTLRenderPipelineState
 | 
					  private var _psos: [PipelineOptions: MTLRenderPipelineState]
 | 
				
			||||||
  private var depthStencilState: MTLDepthStencilState
 | 
					  private var depthStencilState: MTLDepthStencilState
 | 
				
			||||||
  private let _defaultStorageMode: MTLResourceOptions
 | 
					  private let _defaultStorageMode: MTLResourceOptions
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -54,7 +55,7 @@ public class Renderer {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  internal init(layer metalLayer: CAMetalLayer, size: Size<Int>) throws {
 | 
					  internal init(layer metalLayer: CAMetalLayer, size: Size<Int>) throws {
 | 
				
			||||||
    self.layer = metalLayer
 | 
					    self._layer = metalLayer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Select best Metal device
 | 
					    // Select best Metal device
 | 
				
			||||||
    guard let device = Self.createMetalDevice() else {
 | 
					    guard let device = Self.createMetalDevice() else {
 | 
				
			||||||
@ -74,8 +75,8 @@ public class Renderer {
 | 
				
			|||||||
    self._defaultStorageMode = .storageModeShared
 | 
					    self._defaultStorageMode = .storageModeShared
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    layer.device = device
 | 
					    self._layer.device = device
 | 
				
			||||||
    layer.pixelFormat = colorFormat
 | 
					    self._layer.pixelFormat = colorFormat
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Setup command queue
 | 
					    // Setup command queue
 | 
				
			||||||
    guard let queue = device.makeCommandQueue() else {
 | 
					    guard let queue = device.makeCommandQueue() else {
 | 
				
			||||||
@ -116,20 +117,16 @@ public class Renderer {
 | 
				
			|||||||
    } catch {
 | 
					    } catch {
 | 
				
			||||||
      throw RendererError.initFailure("Metal shader compilation failed:\n\(error.localizedDescription)")
 | 
					      throw RendererError.initFailure("Metal shader compilation failed:\n\(error.localizedDescription)")
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    let vertexProgram   = lib.makeFunction(name: "vertexMain")
 | 
					    self._defaultShader = .init(
 | 
				
			||||||
    let fragmentProgram = lib.makeFunction(name: "fragmentMain")
 | 
					      vertexProgram:   lib.makeFunction(name: "vertexMain"),
 | 
				
			||||||
 | 
					      fragmentProgram: lib.makeFunction(name: "fragmentMain"))
 | 
				
			||||||
 | 
					    self._shader2D = .init(
 | 
				
			||||||
 | 
					      vertexProgram:   lib.makeFunction(name: "vertex2DMain"),
 | 
				
			||||||
 | 
					      fragmentProgram: lib.makeFunction(name: "fragment2DMain"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Set up pipeline state
 | 
					    // Set up initial pipeline state
 | 
				
			||||||
    let pipeDescription = MTLRenderPipelineDescriptor()
 | 
					    self._psos = try [ .init(colorFormat: self._layer.pixelFormat, depthFormat: depthFormat, shader: self._defaultShader, blendFunc: .off) ]
 | 
				
			||||||
    pipeDescription.vertexFunction   = vertexProgram
 | 
					      .map { [$0: try $0.createPipeline(device)] }[0]
 | 
				
			||||||
    pipeDescription.fragmentFunction = fragmentProgram
 | 
					 | 
				
			||||||
    pipeDescription.colorAttachments[0].pixelFormat = layer.pixelFormat
 | 
					 | 
				
			||||||
    pipeDescription.depthAttachmentPixelFormat = depthFormat
 | 
					 | 
				
			||||||
    do {
 | 
					 | 
				
			||||||
      self.pso = try device.makeRenderPipelineState(descriptor: pipeDescription)
 | 
					 | 
				
			||||||
    } catch {
 | 
					 | 
				
			||||||
      throw RendererError.initFailure("Failed to create pipeline state: \(error.localizedDescription)")
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Create a default texture
 | 
					    // Create a default texture
 | 
				
			||||||
    do {
 | 
					    do {
 | 
				
			||||||
@ -155,6 +152,16 @@ public class Renderer {
 | 
				
			|||||||
    
 | 
					    
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  fileprivate func usePipeline(options pipeOpts: PipelineOptions) throws {
 | 
				
			||||||
 | 
					    if let exists = self._psos[pipeOpts] {
 | 
				
			||||||
 | 
					      self._encoder.setRenderPipelineState(exists)
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      let new = try pipeOpts.createPipeline(self.device)
 | 
				
			||||||
 | 
					      self._encoder.setRenderPipelineState(new)
 | 
				
			||||||
 | 
					      self._psos[pipeOpts] = new
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  func createMesh(_ mesh: Mesh<VertexPositionNormalColorTexcoord, UInt16>) -> RendererMesh? {
 | 
					  func createMesh(_ mesh: Mesh<VertexPositionNormalColorTexcoord, UInt16>) -> RendererMesh? {
 | 
				
			||||||
    if mesh.vertices.isEmpty || mesh.indices.isEmpty { return nil }
 | 
					    if mesh.vertices.isEmpty || mesh.indices.isEmpty { return nil }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -335,11 +342,11 @@ public class Renderer {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  func newFrame(_ frameFunc: (Renderer) -> Void) throws {
 | 
					  func newFrame(_ frameFunc: (Renderer) -> Void) throws {
 | 
				
			||||||
    try autoreleasepool {
 | 
					    try autoreleasepool {
 | 
				
			||||||
      guard let rt = layer.nextDrawable() else {
 | 
					      guard let rt = self._layer.nextDrawable() else {
 | 
				
			||||||
        throw RendererError.drawFailure("Failed to get next drawable render target")
 | 
					        throw RendererError.drawFailure("Failed to get next drawable render target")
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      passDescription.colorAttachments[0].clearColor  = MTLClearColor(self._clearColor)
 | 
					      passDescription.colorAttachments[0].clearColor = MTLClearColor(self._clearColor)
 | 
				
			||||||
      passDescription.colorAttachments[0].texture = rt.texture
 | 
					      passDescription.colorAttachments[0].texture = rt.texture
 | 
				
			||||||
      passDescription.depthAttachment.texture = self.depthTextures[self.currentFrame]
 | 
					      passDescription.depthAttachment.texture = self.depthTextures[self.currentFrame]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -359,7 +366,6 @@ public class Renderer {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
      encoder.setFrontFacing(.counterClockwise)  // OpenGL default
 | 
					      encoder.setFrontFacing(.counterClockwise)  // OpenGL default
 | 
				
			||||||
      encoder.setViewport(Self.makeViewport(rect: self.frame))
 | 
					      encoder.setViewport(Self.makeViewport(rect: self.frame))
 | 
				
			||||||
      encoder.setRenderPipelineState(pso)
 | 
					 | 
				
			||||||
      encoder.setDepthStencilState(depthStencilState)
 | 
					      encoder.setDepthStencilState(depthStencilState)
 | 
				
			||||||
      encoder.setFragmentTexture(cubeTexture ?? defaultTexture, index: 0)
 | 
					      encoder.setFragmentTexture(cubeTexture ?? defaultTexture, index: 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -383,7 +389,15 @@ public class Renderer {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  internal func setupBatch(environment: Environment, camera: Camera) {
 | 
					  internal func setupBatch(environment: Environment, camera: Camera) {
 | 
				
			||||||
    assert(self._encoder != nil, "startBatch can't be called outside of a frame being rendered")
 | 
					    assert(self._encoder != nil, "setupBatch can't be called outside of a frame being rendered")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    do {
 | 
				
			||||||
 | 
					      try self.usePipeline(options: PipelineOptions(
 | 
				
			||||||
 | 
					        colorFormat: self._layer.pixelFormat, depthFormat: depthFormat,
 | 
				
			||||||
 | 
					        shader: self._defaultShader, blendFunc: .off))
 | 
				
			||||||
 | 
					    } catch {
 | 
				
			||||||
 | 
					      printErr(error)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    var vertUniforms = VertexShaderUniforms(projView: camera.viewProjection)
 | 
					    var vertUniforms = VertexShaderUniforms(projView: camera.viewProjection)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -520,7 +534,7 @@ public struct RendererMesh: Hashable {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
extension MTLClearColor {
 | 
					fileprivate extension MTLClearColor {
 | 
				
			||||||
  init(_ color: Color<Double>) {
 | 
					  init(_ color: Color<Double>) {
 | 
				
			||||||
    self.init(red: color.r, green: color.g, blue: color.b, alpha: color.a)
 | 
					    self.init(red: color.r, green: color.g, blue: color.b, alpha: color.a)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@ -536,6 +550,127 @@ fileprivate extension MTLCullMode {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fileprivate struct Shader: Hashable {
 | 
				
			||||||
 | 
					  let vertexProgram: (any MTLFunction)?, fragmentProgram: (any MTLFunction)?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  static func == (lhs: Shader, rhs: Shader) -> Bool {
 | 
				
			||||||
 | 
					    lhs.vertexProgram?.hash == rhs.vertexProgram?.hash && lhs.fragmentProgram?.hash == rhs.fragmentProgram?.hash
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public func hash(into hasher: inout Hasher) {
 | 
				
			||||||
 | 
					    hasher.combine(self.vertexProgram?.hash ?? 0)
 | 
				
			||||||
 | 
					    hasher.combine(self.fragmentProgram?.hash ?? 0)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fileprivate struct PipelineOptions: Hashable {
 | 
				
			||||||
 | 
					  let colorFormat: MTLPixelFormat, depthFormat: MTLPixelFormat
 | 
				
			||||||
 | 
					  let shader: Shader
 | 
				
			||||||
 | 
					  let blendFunc: BlendFunc
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fileprivate extension PipelineOptions {
 | 
				
			||||||
 | 
					  func createPipeline(_ device: MTLDevice) throws -> MTLRenderPipelineState {
 | 
				
			||||||
 | 
					    let pipeDescription = MTLRenderPipelineDescriptor()
 | 
				
			||||||
 | 
					    pipeDescription.vertexFunction   = self.shader.vertexProgram
 | 
				
			||||||
 | 
					    pipeDescription.fragmentFunction = self.shader.fragmentProgram
 | 
				
			||||||
 | 
					    pipeDescription.colorAttachments[0].pixelFormat = self.colorFormat
 | 
				
			||||||
 | 
					    self.blendFunc.setBlend(colorAttachment: &pipeDescription.colorAttachments[0])
 | 
				
			||||||
 | 
					    pipeDescription.depthAttachmentPixelFormat = self.depthFormat
 | 
				
			||||||
 | 
					    do {
 | 
				
			||||||
 | 
					      return try device.makeRenderPipelineState(descriptor: pipeDescription)
 | 
				
			||||||
 | 
					    } catch {
 | 
				
			||||||
 | 
					      throw RendererError.initFailure("Failed to create pipeline state: \(error.localizedDescription)")
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fileprivate extension BlendFunc {
 | 
				
			||||||
 | 
					  func setBlend(colorAttachment: inout MTLRenderPipelineColorAttachmentDescriptor) {
 | 
				
			||||||
 | 
					    switch self {
 | 
				
			||||||
 | 
					    case .off:
 | 
				
			||||||
 | 
					      colorAttachment.isBlendingEnabled = false
 | 
				
			||||||
 | 
					    case .on(let srcFactor, let dstFactor, let equation):
 | 
				
			||||||
 | 
					      colorAttachment.isBlendingEnabled = true
 | 
				
			||||||
 | 
					      colorAttachment.rgbBlendOperation = .init(equation)
 | 
				
			||||||
 | 
					      colorAttachment.alphaBlendOperation = .init(equation)
 | 
				
			||||||
 | 
					      colorAttachment.sourceRGBBlendFactor = .init(srcFactor)
 | 
				
			||||||
 | 
					      colorAttachment.sourceAlphaBlendFactor = .init(srcFactor)
 | 
				
			||||||
 | 
					      colorAttachment.destinationRGBBlendFactor = .init(dstFactor)
 | 
				
			||||||
 | 
					      colorAttachment.destinationAlphaBlendFactor = .init(dstFactor)
 | 
				
			||||||
 | 
					    case .separate(let srcColor, let srcAlpha, let dstColor, let dstAlpha, let equColor, let equAlpha):
 | 
				
			||||||
 | 
					      colorAttachment.isBlendingEnabled = true
 | 
				
			||||||
 | 
					      colorAttachment.rgbBlendOperation = .init(equColor)
 | 
				
			||||||
 | 
					      colorAttachment.alphaBlendOperation = .init(equAlpha)
 | 
				
			||||||
 | 
					      colorAttachment.sourceRGBBlendFactor = .init(srcColor)
 | 
				
			||||||
 | 
					      colorAttachment.sourceAlphaBlendFactor = .init(srcAlpha)
 | 
				
			||||||
 | 
					      colorAttachment.destinationRGBBlendFactor = .init(dstColor)
 | 
				
			||||||
 | 
					      colorAttachment.destinationAlphaBlendFactor = .init(dstAlpha)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fileprivate extension MTLBlendOperation {
 | 
				
			||||||
 | 
					  init(_ equation: BlendFuncEquation) {
 | 
				
			||||||
 | 
					    self = switch equation {
 | 
				
			||||||
 | 
					    case .add:             .add
 | 
				
			||||||
 | 
					    case .subtract:        .subtract
 | 
				
			||||||
 | 
					    case .reverseSubtract: .reverseSubtract
 | 
				
			||||||
 | 
					    case .min:             .min
 | 
				
			||||||
 | 
					    case .max:             .max
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fileprivate extension MTLBlendFactor {
 | 
				
			||||||
 | 
					  init(_ source: BlendFuncSourceFactor) {
 | 
				
			||||||
 | 
					    self = switch source {
 | 
				
			||||||
 | 
					    case .zero:                  .zero
 | 
				
			||||||
 | 
					    case .one:                   .one
 | 
				
			||||||
 | 
					    case .srcColor:              .sourceColor
 | 
				
			||||||
 | 
					    case .oneMinusSrcColor:      .oneMinusSourceColor
 | 
				
			||||||
 | 
					    case .srcAlpha:              .sourceAlpha
 | 
				
			||||||
 | 
					    case .oneMinusSrcAlpha:      .oneMinusSourceAlpha
 | 
				
			||||||
 | 
					    case .dstColor:              .destinationColor
 | 
				
			||||||
 | 
					    case .oneMinusDstColor:      .oneMinusDestinationColor
 | 
				
			||||||
 | 
					    case .dstAlpha:              .destinationAlpha
 | 
				
			||||||
 | 
					    case .oneMinusDstAlpha:      .oneMinusDestinationAlpha
 | 
				
			||||||
 | 
					    case .srcAlphaSaturate:      .sourceAlphaSaturated
 | 
				
			||||||
 | 
					    /*
 | 
				
			||||||
 | 
					    case .constantColor:         .blendColor
 | 
				
			||||||
 | 
					    case .oneMinusConstantColor: .oneMinusBlendColor
 | 
				
			||||||
 | 
					    case .constantAlpha:         .blendAlpha
 | 
				
			||||||
 | 
					    case .oneMinusConstantAlpha: .oneMinusBlendAlpha
 | 
				
			||||||
 | 
					    */
 | 
				
			||||||
 | 
					    case .src1Color:             .source1Color
 | 
				
			||||||
 | 
					    case .oneMinusSrc1Color:     .oneMinusSource1Color
 | 
				
			||||||
 | 
					    case .src1Alpha:             .source1Alpha
 | 
				
			||||||
 | 
					    case .oneMinusSrc1Alpha:     .oneMinusSource1Alpha
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  init(_ destination: BlendFuncDestinationFactor) {
 | 
				
			||||||
 | 
					    self = switch destination {
 | 
				
			||||||
 | 
					    case .zero:                  .zero
 | 
				
			||||||
 | 
					    case .one:                   .one
 | 
				
			||||||
 | 
					    case .srcColor:              .sourceColor
 | 
				
			||||||
 | 
					    case .oneMinusSrcColor:      .oneMinusSourceColor
 | 
				
			||||||
 | 
					    case .srcAlpha:              .sourceAlpha
 | 
				
			||||||
 | 
					    case .oneMinusSrcAlpha:      .oneMinusSourceAlpha
 | 
				
			||||||
 | 
					    case .dstColor:              .destinationColor
 | 
				
			||||||
 | 
					    case .oneMinusDstColor:      .oneMinusDestinationColor
 | 
				
			||||||
 | 
					    case .dstAlpha:              .destinationAlpha
 | 
				
			||||||
 | 
					    case .oneMinusDstAlpha:      .oneMinusDestinationAlpha
 | 
				
			||||||
 | 
					    /*
 | 
				
			||||||
 | 
					    case .constantColor:         .blendColor
 | 
				
			||||||
 | 
					    case .oneMinusConstantColor: .oneMinusBlendColor
 | 
				
			||||||
 | 
					    case .constantAlpha:         .blendAlpha
 | 
				
			||||||
 | 
					    case .oneMinusConstantAlpha: .oneMinusBlendAlpha
 | 
				
			||||||
 | 
					    */
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
enum RendererError: Error {
 | 
					enum RendererError: Error {
 | 
				
			||||||
  case initFailure(_ message: String)
 | 
					  case initFailure(_ message: String)
 | 
				
			||||||
  case loadFailure(_ message: String)
 | 
					  case loadFailure(_ message: String)
 | 
				
			||||||
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user