mirror of
				https://github.com/GayPizzaSpecifications/voxelotl-engine.git
				synced 2025-11-04 02:59:37 +00:00 
			
		
		
		
	initial sprite batch implementation & testbed
This commit is contained in:
		@ -1,11 +1,13 @@
 | 
				
			|||||||
add_executable(Voxelotl MACOSX_BUNDLE
 | 
					set(SOURCES
 | 
				
			||||||
  # Resources
 | 
					  # Resources
 | 
				
			||||||
  Assets.xcassets
 | 
					  Assets.xcassets
 | 
				
			||||||
  test.png
 | 
					  test.png
 | 
				
			||||||
 | 
					  wireshark.png
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  # Shaders
 | 
					  # Shaders
 | 
				
			||||||
  shadertypes.h
 | 
					  shadertypes.h
 | 
				
			||||||
  shader.metal
 | 
					  shader.metal
 | 
				
			||||||
 | 
					  shader2D.metal
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  # Common utility library
 | 
					  # Common utility library
 | 
				
			||||||
  Common/ConcurrentDictionary.swift
 | 
					  Common/ConcurrentDictionary.swift
 | 
				
			||||||
@ -48,6 +50,8 @@ add_executable(Voxelotl MACOSX_BUNDLE
 | 
				
			|||||||
  Renderer/ModelBatch.swift
 | 
					  Renderer/ModelBatch.swift
 | 
				
			||||||
  Renderer/BlendMode.swift
 | 
					  Renderer/BlendMode.swift
 | 
				
			||||||
  Renderer/BlendFunc.swift
 | 
					  Renderer/BlendFunc.swift
 | 
				
			||||||
 | 
					  Renderer/Sprite.swift
 | 
				
			||||||
 | 
					  Renderer/SpriteBatch.swift
 | 
				
			||||||
  Renderer/ChunkRenderer.swift
 | 
					  Renderer/ChunkRenderer.swift
 | 
				
			||||||
  Renderer/Metal/BlendFuncExtension.swift
 | 
					  Renderer/Metal/BlendFuncExtension.swift
 | 
				
			||||||
  Renderer/Metal/ColorExtension.swift
 | 
					  Renderer/Metal/ColorExtension.swift
 | 
				
			||||||
@ -55,6 +59,8 @@ add_executable(Voxelotl MACOSX_BUNDLE
 | 
				
			|||||||
  Renderer/Metal/PipelineOptions.swift
 | 
					  Renderer/Metal/PipelineOptions.swift
 | 
				
			||||||
  Renderer/Metal/Shader.swift
 | 
					  Renderer/Metal/Shader.swift
 | 
				
			||||||
  Renderer/Metal/RendererMesh.swift
 | 
					  Renderer/Metal/RendererMesh.swift
 | 
				
			||||||
 | 
					  Renderer/Metal/RendererDynamicMesh.swift
 | 
				
			||||||
 | 
					  Renderer/Metal/RendererTexture2D.swift
 | 
				
			||||||
  Renderer/RendererError.swift
 | 
					  Renderer/RendererError.swift
 | 
				
			||||||
  Renderer/Renderer.swift
 | 
					  Renderer/Renderer.swift
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -80,6 +86,7 @@ add_executable(Voxelotl MACOSX_BUNDLE
 | 
				
			|||||||
  Camera.swift
 | 
					  Camera.swift
 | 
				
			||||||
  Player.swift
 | 
					  Player.swift
 | 
				
			||||||
  Game.swift
 | 
					  Game.swift
 | 
				
			||||||
 | 
					  SpriteTestGame.swift
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  # Core application classes
 | 
					  # Core application classes
 | 
				
			||||||
  GameDelegate.swift
 | 
					  GameDelegate.swift
 | 
				
			||||||
@ -90,12 +97,16 @@ add_executable(Voxelotl MACOSX_BUNDLE
 | 
				
			|||||||
  main.m
 | 
					  main.m
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					foreach (SOURCE IN LISTS SOURCES)
 | 
				
			||||||
 | 
					  if (SOURCE MATCHES "\\.metal$")
 | 
				
			||||||
    set_source_files_properties(
 | 
					    set_source_files_properties(
 | 
				
			||||||
  shader.metal PROPERTIES
 | 
					      "${SOURCE}" PROPERTIES
 | 
				
			||||||
      LANGUAGE METAL
 | 
					      LANGUAGE METAL
 | 
				
			||||||
  COMPILE_OPTIONS "-I${PROJECT_SOURCE_DIR}"
 | 
					      COMPILE_OPTIONS "-I${PROJECT_SOURCE_DIR}")
 | 
				
			||||||
)
 | 
					  endif()
 | 
				
			||||||
 | 
					endforeach()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					add_executable(Voxelotl MACOSX_BUNDLE ${SOURCES})
 | 
				
			||||||
target_include_directories(Voxelotl PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}")
 | 
					target_include_directories(Voxelotl PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}")
 | 
				
			||||||
target_link_libraries(Voxelotl PRIVATE SDLSwift)
 | 
					target_link_libraries(Voxelotl PRIVATE SDLSwift)
 | 
				
			||||||
target_compile_definitions(Voxelotl PRIVATE $<$<CONFIG:Debug>:DEBUG>)
 | 
					target_compile_definitions(Voxelotl PRIVATE $<$<CONFIG:Debug>:DEBUG>)
 | 
				
			||||||
@ -140,7 +151,11 @@ endif()
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
set_source_files_properties(Assets.xcassets PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
 | 
					set_source_files_properties(Assets.xcassets PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
 | 
				
			||||||
set_source_files_properties(module.modulemap PROPERTIES MACOSX_PACKAGE_LOCATION Modules)
 | 
					set_source_files_properties(module.modulemap PROPERTIES MACOSX_PACKAGE_LOCATION Modules)
 | 
				
			||||||
set_source_files_properties(test.png PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
 | 
					foreach (RESOURCE IN LISTS SOURCES)
 | 
				
			||||||
 | 
					  if (RESOURCE MATCHES "\\.png$")
 | 
				
			||||||
 | 
					    set_source_files_properties("${RESOURCE}" PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
 | 
				
			||||||
 | 
					  endif()
 | 
				
			||||||
 | 
					endforeach()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#TODO: should use TREE mode as documented in https://cmake.org/cmake/help/latest/command/source_group.html
 | 
					#TODO: should use TREE mode as documented in https://cmake.org/cmake/help/latest/command/source_group.html
 | 
				
			||||||
source_group("Resources" FILES Assets.xcassets test.png)
 | 
					source_group("Resources" FILES Assets.xcassets test.png)
 | 
				
			||||||
 | 
				
			|||||||
@ -22,3 +22,9 @@ extension Duration {
 | 
				
			|||||||
    Double(components.seconds) + Double(components.attoseconds) * 1e-18
 | 
					    Double(components.seconds) + Double(components.attoseconds) * 1e-18
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					extension Float {
 | 
				
			||||||
 | 
					  public init(_ value: Duration) {
 | 
				
			||||||
 | 
					    self = Float(value.asFloat)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -5,7 +5,7 @@ import Foundation
 | 
				
			|||||||
    Thread.current.qualityOfService = .userInteractive
 | 
					    Thread.current.qualityOfService = .userInteractive
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let app = Application(
 | 
					    let app = Application(
 | 
				
			||||||
      delegate: Game(),
 | 
					      delegate: SpriteTestGame(),
 | 
				
			||||||
      configuration: ApplicationConfiguration(
 | 
					      configuration: ApplicationConfiguration(
 | 
				
			||||||
      frame: Size(1280, 720),
 | 
					      frame: Size(1280, 720),
 | 
				
			||||||
      title: "Voxelotl Demo",
 | 
					      title: "Voxelotl Demo",
 | 
				
			||||||
 | 
				
			|||||||
@ -21,3 +21,9 @@ public struct VertexPositionNormalColorTexcoord: Vertex {
 | 
				
			|||||||
  var color:    SIMD4<Float>
 | 
					  var color:    SIMD4<Float>
 | 
				
			||||||
  var texCoord: SIMD2<Float>
 | 
					  var texCoord: SIMD2<Float>
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public struct VertexPosition2DTexcoordColor: Vertex {
 | 
				
			||||||
 | 
					  var position: SIMD2<Float>
 | 
				
			||||||
 | 
					  var texCoord: SIMD2<Float>
 | 
				
			||||||
 | 
					  var color:    SIMD4<Float>
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										70
									
								
								Sources/Voxelotl/Renderer/Metal/RendererDynamicMesh.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								Sources/Voxelotl/Renderer/Metal/RendererDynamicMesh.swift
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,70 @@
 | 
				
			|||||||
 | 
					import Metal
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public struct RendererDynamicMesh<VertexType: Vertex, IndexType: UnsignedInteger> {
 | 
				
			||||||
 | 
					  private weak var _renderer: Renderer!
 | 
				
			||||||
 | 
					  internal let _vertBufs: [MTLBuffer], _idxBufs: [MTLBuffer]
 | 
				
			||||||
 | 
					  private var _numVertices: Int = 0, _numIndices: Int = 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public let vertexCapacity: Int, indexCapacity: Int
 | 
				
			||||||
 | 
					  public var vertexCount: Int { self._numVertices }
 | 
				
			||||||
 | 
					  public var indexCount: Int { self._numIndices }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  init(renderer: Renderer, _ vertBufs: [MTLBuffer], _ idxBufs: [MTLBuffer]) {
 | 
				
			||||||
 | 
					    self._renderer = renderer
 | 
				
			||||||
 | 
					    self._vertBufs = vertBufs
 | 
				
			||||||
 | 
					    self._idxBufs = idxBufs
 | 
				
			||||||
 | 
					    self.vertexCapacity = self._vertBufs.map { $0.length }.min()! / MemoryLayout<VertexType>.stride
 | 
				
			||||||
 | 
					    self.indexCapacity = self._idxBufs.map { $0.length }.min()! / MemoryLayout<IndexType>.stride
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public mutating func clear() {
 | 
				
			||||||
 | 
					    self._numVertices = 0
 | 
				
			||||||
 | 
					    self._numIndices = 0
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public mutating func insert(vertices: [VertexType]) {
 | 
				
			||||||
 | 
					    self.insert(vertices: vertices[...])
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public mutating func insert(vertices: ArraySlice<VertexType>) {
 | 
				
			||||||
 | 
					    assert(self._numVertices + vertices.count < self.vertexCapacity)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let vertexBuffer: MTLBuffer = self._vertBufs[self._renderer.currentFrame]
 | 
				
			||||||
 | 
					    vertexBuffer.contents().withMemoryRebound(to: VertexType.self, capacity: self.vertexCapacity) { vertexData in
 | 
				
			||||||
 | 
					      for i in 0..<vertices.count {
 | 
				
			||||||
 | 
					        vertexData[self._numVertices + i] = vertices[i]
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#if os(macOS)
 | 
				
			||||||
 | 
					    if self._renderer.isManagedStorage {
 | 
				
			||||||
 | 
					      let stride = MemoryLayout<VertexType>.stride
 | 
				
			||||||
 | 
					      vertexBuffer.didModifyRange(stride * self._numVertices..<stride * vertices.count)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    self._numVertices += vertices.count
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public mutating func insert(indices: [IndexType], baseVertex: Int = 0) {
 | 
				
			||||||
 | 
					    assert(self._numIndices + indices.count < self.indexCapacity)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let indexBuffer: MTLBuffer = self._idxBufs[self._renderer.currentFrame]
 | 
				
			||||||
 | 
					    let base = IndexType(baseVertex)
 | 
				
			||||||
 | 
					    indexBuffer.contents().withMemoryRebound(to: IndexType.self, capacity: self.indexCapacity) { indexData in
 | 
				
			||||||
 | 
					      for i in 0..<indices.count {
 | 
				
			||||||
 | 
					        indexData[self._numIndices + i] = base + indices[i]
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#if os(macOS)
 | 
				
			||||||
 | 
					    if self._renderer.isManagedStorage {
 | 
				
			||||||
 | 
					      let stride = MemoryLayout<VertexType>.stride
 | 
				
			||||||
 | 
					      indexBuffer.didModifyRange(stride * self._numIndices..<stride * indices.count)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    self._numIndices += indices.count
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										20
									
								
								Sources/Voxelotl/Renderer/Metal/RendererTexture2D.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								Sources/Voxelotl/Renderer/Metal/RendererTexture2D.swift
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,20 @@
 | 
				
			|||||||
 | 
					import Metal
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public struct RendererTexture2D: Hashable {
 | 
				
			||||||
 | 
					  internal let _textureBuffer: MTLTexture
 | 
				
			||||||
 | 
					  public let size: Size<Int>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  internal init(metalTexture: MTLTexture, size: Size<Int>) {
 | 
				
			||||||
 | 
					    self._textureBuffer = metalTexture
 | 
				
			||||||
 | 
					    self.size = size
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public static func == (lhs: Self, rhs: Self) -> Bool {
 | 
				
			||||||
 | 
					    lhs._textureBuffer.gpuResourceID._impl == rhs._textureBuffer.gpuResourceID._impl && lhs.size == rhs.size
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public func hash(into hasher: inout Hasher) {
 | 
				
			||||||
 | 
					    hasher.combine(self._textureBuffer.hash)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -19,7 +19,7 @@ public class Renderer {
 | 
				
			|||||||
  private var _defaultShader: Shader, _shader2D: Shader
 | 
					  private var _defaultShader: Shader, _shader2D: Shader
 | 
				
			||||||
  private let passDescription = MTLRenderPassDescriptor()
 | 
					  private let passDescription = MTLRenderPassDescriptor()
 | 
				
			||||||
  private var _psos: [PipelineOptions: MTLRenderPipelineState]
 | 
					  private var _psos: [PipelineOptions: MTLRenderPipelineState]
 | 
				
			||||||
  private var depthStencilState: MTLDepthStencilState
 | 
					  private var _depthStencilEnabled: MTLDepthStencilState, _depthStencilDisabled: MTLDepthStencilState
 | 
				
			||||||
  private let _defaultStorageMode: MTLResourceOptions
 | 
					  private let _defaultStorageMode: MTLResourceOptions
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private var depthTextures: [MTLTexture]
 | 
					  private var depthTextures: [MTLTexture]
 | 
				
			||||||
@ -28,11 +28,14 @@ public class Renderer {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  private var _encoder: MTLRenderCommandEncoder! = nil
 | 
					  private var _encoder: MTLRenderCommandEncoder! = nil
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private var defaultTexture: MTLTexture
 | 
					  private var defaultTexture: RendererTexture2D
 | 
				
			||||||
  private var cubeTexture: MTLTexture? = nil
 | 
					  private var cubeTexture: RendererTexture2D? = nil
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private let inFlightSemaphore = DispatchSemaphore(value: numFramesInFlight)
 | 
					  private let inFlightSemaphore = DispatchSemaphore(value: numFramesInFlight)
 | 
				
			||||||
  private var currentFrame = 0
 | 
					  private var _currentFrame = 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  internal var currentFrame: Int { self._currentFrame }
 | 
				
			||||||
 | 
					  internal var isManagedStorage: Bool { self._defaultStorageMode == .storageModeManaged }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  var frame: Rect<Int> { .init(origin: .zero, size: self.backBufferSize) }
 | 
					  var frame: Rect<Int> { .init(origin: .zero, size: self.backBufferSize) }
 | 
				
			||||||
  var aspectRatio: Float { self._aspectRatio }
 | 
					  var aspectRatio: Float { self._aspectRatio }
 | 
				
			||||||
@ -101,15 +104,16 @@ public class Renderer {
 | 
				
			|||||||
      return depthStencilTexture
 | 
					      return depthStencilTexture
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    //self._instances = [MTLBuffer?](repeating: nil, count: numFramesInFlight)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let stencilDepthDescription = MTLDepthStencilDescriptor()
 | 
					    let stencilDepthDescription = MTLDepthStencilDescriptor()
 | 
				
			||||||
    stencilDepthDescription.depthCompareFunction = .less  // OpenGL default
 | 
					    stencilDepthDescription.depthCompareFunction = .less  // OpenGL default
 | 
				
			||||||
    stencilDepthDescription.isDepthWriteEnabled  = true
 | 
					    stencilDepthDescription.isDepthWriteEnabled  = true
 | 
				
			||||||
    guard let depthStencilState = device.makeDepthStencilState(descriptor: stencilDepthDescription) else {
 | 
					    guard let depthStencilEnabled = device.makeDepthStencilState(descriptor: stencilDepthDescription),
 | 
				
			||||||
 | 
					      let depthStencilDisabled = device.makeDepthStencilState(descriptor: MTLDepthStencilDescriptor())
 | 
				
			||||||
 | 
					    else {
 | 
				
			||||||
      throw RendererError.initFailure("Failed to create depth stencil state")
 | 
					      throw RendererError.initFailure("Failed to create depth stencil state")
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    self.depthStencilState = depthStencilState
 | 
					    self._depthStencilEnabled = depthStencilEnabled
 | 
				
			||||||
 | 
					    self._depthStencilDisabled = depthStencilDisabled
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Create shader library & grab functions
 | 
					    // Create shader library & grab functions
 | 
				
			||||||
    do {
 | 
					    do {
 | 
				
			||||||
@ -226,9 +230,51 @@ public class Renderer {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  internal func createDynamicMesh<VertexType: Vertex, IndexType: UnsignedInteger>(
 | 
				
			||||||
 | 
					    vertexCapacity: Int, indexCapacity: Int
 | 
				
			||||||
 | 
					  ) -> RendererDynamicMesh<VertexType, IndexType>? {
 | 
				
			||||||
 | 
					    let vertexBuffers: [MTLBuffer], indexBuffers: [MTLBuffer]
 | 
				
			||||||
 | 
					    do {
 | 
				
			||||||
 | 
					      let byteCapacity = MemoryLayout<VertexType>.stride * vertexCapacity
 | 
				
			||||||
 | 
					      vertexBuffers = try Self.createDynamicBuffer(self.device, capacity: byteCapacity, self._defaultStorageMode)
 | 
				
			||||||
 | 
					    } catch {
 | 
				
			||||||
 | 
					      printErr("Failed to create vertex buffer")
 | 
				
			||||||
 | 
					      return nil
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    do {
 | 
				
			||||||
 | 
					      let byteCapacity = MemoryLayout<IndexType>.stride * indexCapacity
 | 
				
			||||||
 | 
					      indexBuffers =  try Self.createDynamicBuffer(self.device, capacity: byteCapacity, self._defaultStorageMode)
 | 
				
			||||||
 | 
					    } catch {
 | 
				
			||||||
 | 
					      printErr("Failed to create index buffer")
 | 
				
			||||||
 | 
					      return nil
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return .init(renderer: self, vertexBuffers, indexBuffers)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private static func createDynamicBuffer(_ device: MTLDevice, capacity: Int, _ transitoryOpt: MTLResourceOptions
 | 
				
			||||||
 | 
					  ) throws -> [MTLBuffer] {
 | 
				
			||||||
 | 
					    try autoreleasepool {
 | 
				
			||||||
 | 
					      try (0..<numFramesInFlight).map { _ in
 | 
				
			||||||
 | 
					        guard let buffer = device.makeBuffer(length: capacity, options: transitoryOpt) else {
 | 
				
			||||||
 | 
					          throw RendererError.initFailure("Failed to create buffer")
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return buffer
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public func loadTexture(resourcePath path: String) -> RendererTexture2D? {
 | 
				
			||||||
 | 
					    do {
 | 
				
			||||||
 | 
					      return try Self.loadTexture(self.device, self.queue, resourcePath: path, self._defaultStorageMode)
 | 
				
			||||||
 | 
					    } catch {
 | 
				
			||||||
 | 
					      printErr(error)
 | 
				
			||||||
 | 
					      return nil
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  static func loadTexture(_ device: MTLDevice, _ queue: MTLCommandQueue, resourcePath path: String,
 | 
					  static func loadTexture(_ device: MTLDevice, _ queue: MTLCommandQueue, resourcePath path: String,
 | 
				
			||||||
    _ transitoryOpt: MTLResourceOptions
 | 
					    _ transitoryOpt: MTLResourceOptions
 | 
				
			||||||
  ) throws -> MTLTexture {
 | 
					  ) throws -> RendererTexture2D {
 | 
				
			||||||
    do {
 | 
					    do {
 | 
				
			||||||
      return try loadTexture(device, queue, url: Bundle.main.getResource(path), transitoryOpt)
 | 
					      return try loadTexture(device, queue, url: Bundle.main.getResource(path), transitoryOpt)
 | 
				
			||||||
    } catch ContentError.resourceNotFound(let message) {
 | 
					    } catch ContentError.resourceNotFound(let message) {
 | 
				
			||||||
@ -238,7 +284,7 @@ public class Renderer {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  static func loadTexture(_ device: MTLDevice, _ queue: MTLCommandQueue, url imageUrl: URL,
 | 
					  static func loadTexture(_ device: MTLDevice, _ queue: MTLCommandQueue, url imageUrl: URL,
 | 
				
			||||||
    _ transitoryOpt: MTLResourceOptions
 | 
					    _ transitoryOpt: MTLResourceOptions
 | 
				
			||||||
  ) throws -> MTLTexture {
 | 
					  ) throws -> RendererTexture2D {
 | 
				
			||||||
    do {
 | 
					    do {
 | 
				
			||||||
      return try loadTexture(device, queue, image2D: try NSImageLoader.open(url: imageUrl), transitoryOpt)
 | 
					      return try loadTexture(device, queue, image2D: try NSImageLoader.open(url: imageUrl), transitoryOpt)
 | 
				
			||||||
    } catch ImageLoaderError.openFailed(let message) {
 | 
					    } catch ImageLoaderError.openFailed(let message) {
 | 
				
			||||||
@ -248,7 +294,7 @@ public class Renderer {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  static func loadTexture(_ device: MTLDevice, _ queue: MTLCommandQueue, image2D image: Image2D,
 | 
					  static func loadTexture(_ device: MTLDevice, _ queue: MTLCommandQueue, image2D image: Image2D,
 | 
				
			||||||
    _ transitoryOpt: MTLResourceOptions
 | 
					    _ transitoryOpt: MTLResourceOptions
 | 
				
			||||||
  ) throws -> MTLTexture {
 | 
					  ) throws -> RendererTexture2D {
 | 
				
			||||||
    try autoreleasepool {
 | 
					    try autoreleasepool {
 | 
				
			||||||
      let texDesc = MTLTextureDescriptor()
 | 
					      let texDesc = MTLTextureDescriptor()
 | 
				
			||||||
      texDesc.width  = image.width
 | 
					      texDesc.width  = image.width
 | 
				
			||||||
@ -292,7 +338,7 @@ public class Renderer {
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
      cmdBuffer.commit()
 | 
					      cmdBuffer.commit()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      return newTexture
 | 
					      return .init(metalTexture: newTexture, size: .init(image.width, image.height))
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -348,7 +394,7 @@ public class Renderer {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
      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]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // Lock the semaphore here if too many frames are "in flight"
 | 
					      // Lock the semaphore here if too many frames are "in flight"
 | 
				
			||||||
      _ = inFlightSemaphore.wait(timeout: .distantFuture)
 | 
					      _ = inFlightSemaphore.wait(timeout: .distantFuture)
 | 
				
			||||||
@ -366,8 +412,7 @@ 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.setDepthStencilState(depthStencilState)
 | 
					      encoder.setFragmentTexture(cubeTexture?._textureBuffer ?? defaultTexture._textureBuffer, index: 0)
 | 
				
			||||||
      encoder.setFragmentTexture(cubeTexture ?? defaultTexture, index: 0)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
      self._encoder = encoder
 | 
					      self._encoder = encoder
 | 
				
			||||||
      frameFunc(self)
 | 
					      frameFunc(self)
 | 
				
			||||||
@ -377,9 +422,9 @@ public class Renderer {
 | 
				
			|||||||
      commandBuf.present(rt)
 | 
					      commandBuf.present(rt)
 | 
				
			||||||
      commandBuf.commit()
 | 
					      commandBuf.commit()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      self.currentFrame &+= 1
 | 
					      self._currentFrame &+= 1
 | 
				
			||||||
      if self.currentFrame == numFramesInFlight {
 | 
					      if self._currentFrame == numFramesInFlight {
 | 
				
			||||||
        self.currentFrame = 0
 | 
					        self._currentFrame = 0
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@ -388,6 +433,10 @@ public class Renderer {
 | 
				
			|||||||
    return ModelBatch(self)
 | 
					    return ModelBatch(self)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  func createSpriteBatch() -> SpriteBatch {
 | 
				
			||||||
 | 
					    return SpriteBatch(self)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  internal func setupBatch(environment: Environment, camera: Camera) {
 | 
					  internal func setupBatch(environment: Environment, camera: Camera) {
 | 
				
			||||||
    assert(self._encoder != nil, "setupBatch 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")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -404,6 +453,7 @@ public class Renderer {
 | 
				
			|||||||
    self._cameraPos = camera.position
 | 
					    self._cameraPos = camera.position
 | 
				
			||||||
    self._directionalDir = simd_normalize(environment.lightDirection)
 | 
					    self._directionalDir = simd_normalize(environment.lightDirection)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    self._encoder.setDepthStencilState(self._depthStencilEnabled)
 | 
				
			||||||
    self._encoder.setCullMode(.init(environment.cullFace))
 | 
					    self._encoder.setCullMode(.init(environment.cullFace))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Ideal as long as our uniforms total 4 KB or less
 | 
					    // Ideal as long as our uniforms total 4 KB or less
 | 
				
			||||||
@ -412,6 +462,50 @@ public class Renderer {
 | 
				
			|||||||
      index: VertexShaderInputIdx.uniforms.rawValue)
 | 
					      index: VertexShaderInputIdx.uniforms.rawValue)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  internal func setupBatch(blendMode: BlendMode, frame: Rect<Float>) {
 | 
				
			||||||
 | 
					    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._shader2D, blendFunc: blendMode.function))
 | 
				
			||||||
 | 
					    } catch {
 | 
				
			||||||
 | 
					      printErr(error)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    self._encoder.setDepthStencilState(self._depthStencilDisabled)
 | 
				
			||||||
 | 
					    self._encoder.setCullMode(.none)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    var uniforms = Shader2DUniforms(projection: .orthographic(
 | 
				
			||||||
 | 
					      left: frame.left, right: frame.right,
 | 
				
			||||||
 | 
					      bottom: frame.down, top: frame.up,
 | 
				
			||||||
 | 
					      near: 1, far: -1))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Ideal as long as our uniforms total 4 KB or less
 | 
				
			||||||
 | 
					    self._encoder.setVertexBytes(&uniforms,
 | 
				
			||||||
 | 
					      length: MemoryLayout<Shader2DUniforms>.stride,
 | 
				
			||||||
 | 
					      index: VertexShaderInputIdx.uniforms.rawValue)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  internal func submit(
 | 
				
			||||||
 | 
					    mesh: RendererDynamicMesh<SpriteBatch.VertexType, SpriteBatch.IndexType>,
 | 
				
			||||||
 | 
					    texture: RendererTexture2D?,
 | 
				
			||||||
 | 
					    offset: Int, count: Int
 | 
				
			||||||
 | 
					  ) {
 | 
				
			||||||
 | 
					    assert(self._encoder != nil, "submit can't be called outside of a frame being rendered")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    self._encoder.setFragmentTexture(texture?._textureBuffer ?? defaultTexture._textureBuffer, index: 0)
 | 
				
			||||||
 | 
					    self._encoder.setVertexBuffer(mesh._vertBufs[self._currentFrame],
 | 
				
			||||||
 | 
					      offset: 0,
 | 
				
			||||||
 | 
					      index: VertexShaderInputIdx.vertices.rawValue)
 | 
				
			||||||
 | 
					    self._encoder.drawIndexedPrimitives(
 | 
				
			||||||
 | 
					      type:              .triangle,
 | 
				
			||||||
 | 
					      indexCount:        count,
 | 
				
			||||||
 | 
					      indexType:         .uint16,  // Careful!
 | 
				
			||||||
 | 
					      indexBuffer:       mesh._idxBufs[self._currentFrame],
 | 
				
			||||||
 | 
					      indexBufferOffset: MemoryLayout<SpriteBatch.IndexType>.stride * offset)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  internal func submit(mesh: RendererMesh, instance: ModelBatch.Instance, material: Material) {
 | 
					  internal func submit(mesh: RendererMesh, instance: ModelBatch.Instance, material: Material) {
 | 
				
			||||||
    assert(self._encoder != nil, "submit can't be called outside of a frame being rendered")
 | 
					    assert(self._encoder != nil, "submit can't be called outside of a frame being rendered")
 | 
				
			||||||
    var instanceData = VertexShaderInstance(
 | 
					    var instanceData = VertexShaderInstance(
 | 
				
			||||||
@ -452,16 +546,16 @@ public class Renderer {
 | 
				
			|||||||
    let instancesBytes = numInstances * MemoryLayout<VertexShaderInstance>.stride
 | 
					    let instancesBytes = numInstances * MemoryLayout<VertexShaderInstance>.stride
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // (Re)create instance buffer if needed
 | 
					    // (Re)create instance buffer if needed
 | 
				
			||||||
    if self._instances[self.currentFrame] == nil || instancesBytes > self._instances[self.currentFrame]!.length {
 | 
					    if self._instances[self._currentFrame] == nil || instancesBytes > self._instances[self._currentFrame]!.length {
 | 
				
			||||||
      guard let instanceBuffer = self.device.makeBuffer(
 | 
					      guard let instanceBuffer = self.device.makeBuffer(
 | 
				
			||||||
        length: instancesBytes,
 | 
					        length: instancesBytes,
 | 
				
			||||||
        options: self._defaultStorageMode)
 | 
					        options: self._defaultStorageMode)
 | 
				
			||||||
      else {
 | 
					      else {
 | 
				
			||||||
        fatalError("Failed to (re)create instance buffer")
 | 
					        fatalError("Failed to (re)create instance buffer")
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      self._instances[self.currentFrame] = instanceBuffer
 | 
					      self._instances[self._currentFrame] = instanceBuffer
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    let instanceBuffer = self._instances[self.currentFrame]!
 | 
					    let instanceBuffer = self._instances[self._currentFrame]!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Convert & upload instance data to the GPU
 | 
					    // Convert & upload instance data to the GPU
 | 
				
			||||||
    //FIXME: currently will misbehave if batch is called more than once
 | 
					    //FIXME: currently will misbehave if batch is called more than once
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										22
									
								
								Sources/Voxelotl/Renderer/Sprite.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								Sources/Voxelotl/Renderer/Sprite.swift
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,22 @@
 | 
				
			|||||||
 | 
					public struct Sprite {
 | 
				
			||||||
 | 
					  public struct Flip: OptionSet {
 | 
				
			||||||
 | 
					    public let rawValue: UInt16
 | 
				
			||||||
 | 
					    public init(rawValue: UInt16) {
 | 
				
			||||||
 | 
					      self.rawValue = rawValue
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static let none: Self = Self(rawValue: 0)
 | 
				
			||||||
 | 
					    public static let x: Self    = Self(rawValue: 1 << 0)
 | 
				
			||||||
 | 
					    public static let y: Self    = Self(rawValue: 1 << 1)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  var texture: RendererTexture2D
 | 
				
			||||||
 | 
					  var position: SIMD2<Float>
 | 
				
			||||||
 | 
					  var scale: SIMD2<Float>
 | 
				
			||||||
 | 
					  var origin: SIMD2<Float>
 | 
				
			||||||
 | 
					  var shear: SIMD2<Float> = .zero
 | 
				
			||||||
 | 
					  var angle: Float
 | 
				
			||||||
 | 
					  var depth: Int
 | 
				
			||||||
 | 
					  var flip: Flip
 | 
				
			||||||
 | 
					  var color: Color<Float>
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										324
									
								
								Sources/Voxelotl/Renderer/SpriteBatch.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										324
									
								
								Sources/Voxelotl/Renderer/SpriteBatch.swift
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,324 @@
 | 
				
			|||||||
 | 
					import simd
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public typealias Affine2D = simd_float2x2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public struct SpriteBatch {
 | 
				
			||||||
 | 
					  public typealias VertexType = VertexPosition2DTexcoordColor
 | 
				
			||||||
 | 
					  public typealias IndexType = UInt16
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private weak var _renderer: Renderer!
 | 
				
			||||||
 | 
					  private var _active = ActiveState.inactive
 | 
				
			||||||
 | 
					  private var _blendMode = BlendMode.none
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private var _mesh: RendererDynamicMesh<VertexType, IndexType>
 | 
				
			||||||
 | 
					  private var _instances = [SpriteInstance]()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public var viewport: Rect<Float>? = nil
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  internal init(_ renderer: Renderer) {
 | 
				
			||||||
 | 
					    self._renderer = renderer
 | 
				
			||||||
 | 
					    self._mesh = renderer.createDynamicMesh(vertexCapacity: 4096, indexCapacity: 4096)!
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  //MARK: - Public API
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  //TODO: Sort
 | 
				
			||||||
 | 
					  //FIXME: currently will misbehave if begin is called more than once per frame
 | 
				
			||||||
 | 
					  public mutating func begin(blendMode: BlendMode = .normal) {
 | 
				
			||||||
 | 
					    assert(self._active == .inactive, "call to SpriteBatch.begin without first calling end")
 | 
				
			||||||
 | 
					    self._blendMode = blendMode
 | 
				
			||||||
 | 
					    self._active = .begin
 | 
				
			||||||
 | 
					    self._mesh.clear()
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public mutating func end() {
 | 
				
			||||||
 | 
					    assert(self._active != .inactive, "call to SpriteBatch.end without first calling begin")
 | 
				
			||||||
 | 
					    if !self._instances.isEmpty {
 | 
				
			||||||
 | 
					      self.flush()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    self._active = .inactive
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public mutating func draw(_ sprite: Sprite) {
 | 
				
			||||||
 | 
					    assert(self._active != .inactive, "call to SpriteBatch.draw without calling begin")
 | 
				
			||||||
 | 
					    fatalError("TODO")
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public mutating func draw(_ texture: RendererTexture2D, position: SIMD2<Float>) {
 | 
				
			||||||
 | 
					    assert(self._active != .inactive, "call to SpriteBatch.draw without calling begin")
 | 
				
			||||||
 | 
					    self.drawQuad(texture, position: position, size: Size<Float>(texture.size))
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public mutating func draw(_ texture: RendererTexture2D, position: SIMD2<Float>,
 | 
				
			||||||
 | 
					    scale: SIMD2<Float>,
 | 
				
			||||||
 | 
					    angle: Float = 0.0, origin: SIMD2<Float> = .zero,
 | 
				
			||||||
 | 
					    flip: Sprite.Flip = .none,
 | 
				
			||||||
 | 
					    color: Color<Float> = .white
 | 
				
			||||||
 | 
					    //depth: Int = 0
 | 
				
			||||||
 | 
					  ) {
 | 
				
			||||||
 | 
					    assert(self._active != .inactive, "call to SpriteBatch.draw without calling begin")
 | 
				
			||||||
 | 
					    let size = Size<Float>(texture.size)
 | 
				
			||||||
 | 
					    let color = color.linear
 | 
				
			||||||
 | 
					    if angle != 0 {
 | 
				
			||||||
 | 
					      let bias = SIMD2(origin) / SIMD2(size)
 | 
				
			||||||
 | 
					      self.drawQuad(texture, position: position, angle: angle, size: size * scale, offset: bias, color: color)
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      self.drawQuad(texture, position: position - origin, size: size * scale, color: color)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public mutating func draw(_ texture: RendererTexture2D, position: SIMD2<Float>,
 | 
				
			||||||
 | 
					    scale: Float = 1.0,
 | 
				
			||||||
 | 
					    angle: Float = 0.0, origin: SIMD2<Float> = .zero,
 | 
				
			||||||
 | 
					    flip: Sprite.Flip = .none,
 | 
				
			||||||
 | 
					    color: Color<Float> = .white
 | 
				
			||||||
 | 
					    //depth: Int = 0
 | 
				
			||||||
 | 
					  ) {
 | 
				
			||||||
 | 
					    assert(self._active != .inactive, "call to SpriteBatch.draw without calling begin")
 | 
				
			||||||
 | 
					    let size = Size<Float>(texture.size)
 | 
				
			||||||
 | 
					    let color = color.linear
 | 
				
			||||||
 | 
					    if angle != 0 {
 | 
				
			||||||
 | 
					      let bias = SIMD2(origin) / SIMD2(size)
 | 
				
			||||||
 | 
					      self.drawQuad(texture, position: position, angle: angle, size: size * Size(scalar: scale), offset: bias, color: color)
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      self.drawQuad(texture, position: position - origin, size: size * scale, color: color)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public mutating func draw(_ texture: RendererTexture2D, destination: Rect<Float>?) {
 | 
				
			||||||
 | 
					    assert(self._active != .inactive, "call to SpriteBatch.draw without calling begin")
 | 
				
			||||||
 | 
					    let rect = destination ?? self.viewport ?? Rect<Float>(self._renderer.frame)
 | 
				
			||||||
 | 
					    self.drawQuad(texture,
 | 
				
			||||||
 | 
					      p00: SIMD2(rect.left,  rect.up),   p10: SIMD2(rect.right, rect.up),
 | 
				
			||||||
 | 
					      p01: SIMD2(rect.left,  rect.down), p11: SIMD2(rect.right, rect.down))
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public mutating func draw(_ texture: RendererTexture2D, transform: simd_float3x3) {
 | 
				
			||||||
 | 
					    assert(self._active != .inactive, "call to SpriteBatch.draw without calling begin")
 | 
				
			||||||
 | 
					    let w = Float(texture.size.w), h = Float(texture.size.h)
 | 
				
			||||||
 | 
					    self.drawQuad(texture,
 | 
				
			||||||
 | 
					      p00: (transform * .init(0, 0, 1)).xy,
 | 
				
			||||||
 | 
					      p10: (transform * .init(w, 0, 1)).xy,
 | 
				
			||||||
 | 
					      p01: (transform * .init(0, h, 1)).xy,
 | 
				
			||||||
 | 
					      p11: (transform * .init(w, h, 1)).xy)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public mutating func draw(_ texture: RendererTexture2D, transform: simd_float3x3,
 | 
				
			||||||
 | 
					    flip: Sprite.Flip = .none,
 | 
				
			||||||
 | 
					    color: Color<Float> = .white
 | 
				
			||||||
 | 
					  ) {
 | 
				
			||||||
 | 
					    assert(self._active != .inactive, "call to SpriteBatch.draw without calling begin")
 | 
				
			||||||
 | 
					    let w = Float(texture.size.w), h = Float(texture.size.h)
 | 
				
			||||||
 | 
					    self.drawQuad(texture,
 | 
				
			||||||
 | 
					      p00: (transform * .init(0, 0, 1)).xy,
 | 
				
			||||||
 | 
					      p10: (transform * .init(w, 0, 1)).xy,
 | 
				
			||||||
 | 
					      p01: (transform * .init(0, h, 1)).xy,
 | 
				
			||||||
 | 
					      p11: (transform * .init(w, h, 1)).xy,
 | 
				
			||||||
 | 
					      flip: flip, color: color.linear)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public mutating func draw(_ texture: RendererTexture2D, source: Rect<Float>, position: SIMD2<Float>) {
 | 
				
			||||||
 | 
					    assert(self._active != .inactive, "call to SpriteBatch.draw without calling begin")
 | 
				
			||||||
 | 
					    let size = source.size
 | 
				
			||||||
 | 
					    self.drawQuad(texture, source,
 | 
				
			||||||
 | 
					      p00: .init(position.x, position.y),
 | 
				
			||||||
 | 
					      p10: .init(position.x + size.w, position.y),
 | 
				
			||||||
 | 
					      p01: .init(position.x, position.y + size.h),
 | 
				
			||||||
 | 
					      p11: position + SIMD2(size))
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public mutating func draw(_ texture: RendererTexture2D, source: Rect<Float>, position: SIMD2<Float>,
 | 
				
			||||||
 | 
					    scale: SIMD2<Float>, color: Color<Float> = .white
 | 
				
			||||||
 | 
					  ) {
 | 
				
			||||||
 | 
					    assert(self._active != .inactive, "call to SpriteBatch.draw without calling begin")
 | 
				
			||||||
 | 
					    let size = source.size * scale
 | 
				
			||||||
 | 
					    self.drawQuad(texture, source,
 | 
				
			||||||
 | 
					      p00: .init(position.x, position.y),
 | 
				
			||||||
 | 
					      p10: .init(position.x + size.w, position.y),
 | 
				
			||||||
 | 
					      p01: .init(position.x, position.y + size.h),
 | 
				
			||||||
 | 
					      p11: position + SIMD2(size),
 | 
				
			||||||
 | 
					      color: color.linear)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  public mutating func draw(_ texture: RendererTexture2D, source: Rect<Float>, position: SIMD2<Float>,
 | 
				
			||||||
 | 
					    scale: Float = 1.0, color: Color<Float> = .white
 | 
				
			||||||
 | 
					  ) {
 | 
				
			||||||
 | 
					    assert(self._active != .inactive, "call to SpriteBatch.draw without calling begin")
 | 
				
			||||||
 | 
					    let size = source.size * scale
 | 
				
			||||||
 | 
					    self.drawQuad(texture, source,
 | 
				
			||||||
 | 
					      p00: .init(position.x, position.y),
 | 
				
			||||||
 | 
					      p10: .init(position.x + size.w, position.y),
 | 
				
			||||||
 | 
					      p01: .init(position.x, position.y + size.h),
 | 
				
			||||||
 | 
					      p11: position + SIMD2(size),
 | 
				
			||||||
 | 
					      color: color.linear)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  //TODO: Everything
 | 
				
			||||||
 | 
					  //public mutating func draw(_ texture: RendererTexture2D, source: Rect<Float>, position: SIMD2<Float>, scale: SIMD2<Float>, angle: Float = 0.0, origin: Point<Int> = .zero, flip: Sprite.Flip = .none, color: Color<Float> = .white, depth: Int = 0) {
 | 
				
			||||||
 | 
					  //public mutating func draw(_ texture: RendererTexture2D, source: Rect<Float>, position: SIMD2<Float>, scale: Float = 1.0, angle: Float = 0.0, origin: Point<Int> = .zero, flip: Sprite.Flip = .none, color: Color<Float> = .white, depth: Int = 0) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public mutating func draw(_ texture: RendererTexture2D, source: Rect<Float>, destination: Rect<Float>?) {
 | 
				
			||||||
 | 
					    assert(self._active != .inactive, "call to SpriteBatch.draw without calling begin")
 | 
				
			||||||
 | 
					    let dst = destination ?? self.viewport ?? Rect<Float>(self._renderer.frame)
 | 
				
			||||||
 | 
					    self.drawQuad(texture, source,
 | 
				
			||||||
 | 
					      p00: SIMD2(dst.left,  dst.up),   p10: SIMD2(dst.right, dst.up),
 | 
				
			||||||
 | 
					      p01: SIMD2(dst.left,  dst.down), p11: SIMD2(dst.right, dst.down))
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public mutating func draw(_ texture: RendererTexture2D, source: Rect<Float>, destination: Rect<Float>?,
 | 
				
			||||||
 | 
					    color: Color<Float> = .white
 | 
				
			||||||
 | 
					  ) {
 | 
				
			||||||
 | 
					    assert(self._active != .inactive, "call to SpriteBatch.draw without calling begin")
 | 
				
			||||||
 | 
					    let dst = destination ?? self.viewport ?? Rect<Float>(self._renderer.frame)
 | 
				
			||||||
 | 
					    self.drawQuad(texture, source,
 | 
				
			||||||
 | 
					      p00: SIMD2(dst.left,  dst.up),   p10: SIMD2(dst.right, dst.up),
 | 
				
			||||||
 | 
					      p01: SIMD2(dst.left,  dst.down), p11: SIMD2(dst.right, dst.down),
 | 
				
			||||||
 | 
					      color: color.linear)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  //TODO: Destination with rotation
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public mutating func draw(_ texture: RendererTexture2D, source: Rect<Float>, transform: simd_float3x3) {
 | 
				
			||||||
 | 
					    assert(self._active != .inactive, "call to SpriteBatch.draw without calling begin")
 | 
				
			||||||
 | 
					    let w = source.size.w, h = source.size.h
 | 
				
			||||||
 | 
					    self.drawQuad(texture, source,
 | 
				
			||||||
 | 
					      p00: (transform * .init(0, 0, 1)).xy,
 | 
				
			||||||
 | 
					      p10: (transform * .init(w, 0, 1)).xy,
 | 
				
			||||||
 | 
					      p01: (transform * .init(0, h, 1)).xy,
 | 
				
			||||||
 | 
					      p11: (transform * .init(w, h, 1)).xy)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public mutating func draw(_ texture: RendererTexture2D, source: Rect<Float>, transform: simd_float3x3,
 | 
				
			||||||
 | 
					    flip: Sprite.Flip = .none, color: Color<Float> = .white
 | 
				
			||||||
 | 
					  ) {
 | 
				
			||||||
 | 
					    assert(self._active != .inactive, "call to SpriteBatch.draw without calling begin")
 | 
				
			||||||
 | 
					    let w = source.size.w, h = source.size.h
 | 
				
			||||||
 | 
					    self.drawQuad(texture, source,
 | 
				
			||||||
 | 
					      p00: (transform * .init(0, 0, 1)).xy,
 | 
				
			||||||
 | 
					      p10: (transform * .init(w, 0, 1)).xy,
 | 
				
			||||||
 | 
					      p01: (transform * .init(0, h, 1)).xy,
 | 
				
			||||||
 | 
					      p11: (transform * .init(w, h, 1)).xy,
 | 
				
			||||||
 | 
					      color: color.linear)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public mutating func draw(_ texture: RendererTexture2D, vertices: [VertexType]) {
 | 
				
			||||||
 | 
					    assert(self._active != .inactive, "call to SpriteBatch.draw without calling begin")
 | 
				
			||||||
 | 
					    let base = self._mesh.vertexCount
 | 
				
			||||||
 | 
					    self._mesh.insert(vertices: vertices)
 | 
				
			||||||
 | 
					    self._mesh.insert(indices: (0..<vertices.count).map(IndexType.init), baseVertex: base)
 | 
				
			||||||
 | 
					    self._instances.append(.init(texture: texture, size: UInt16(vertices.count)))
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public mutating func draw(_ texture: RendererTexture2D, vertices: ArraySlice<VertexType>) {
 | 
				
			||||||
 | 
					    assert(self._active != .inactive, "call to SpriteBatch.draw without calling begin")
 | 
				
			||||||
 | 
					    let base = self._mesh.vertexCount
 | 
				
			||||||
 | 
					    self._mesh.insert(vertices: vertices)
 | 
				
			||||||
 | 
					    self._mesh.insert(indices: (0..<vertices.count).map(IndexType.init), baseVertex: base)
 | 
				
			||||||
 | 
					    self._instances.append(.init(texture: texture, size: UInt16(vertices.count)))
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public mutating func draw(_ texture: RendererTexture2D, mesh: Mesh<VertexType, IndexType>) {
 | 
				
			||||||
 | 
					    assert(self._active != .inactive, "call to SpriteBatch.draw without calling begin")
 | 
				
			||||||
 | 
					    let base = self._mesh.vertexCount
 | 
				
			||||||
 | 
					    self._mesh.insert(vertices: mesh.vertices)
 | 
				
			||||||
 | 
					    self._mesh.insert(indices: mesh.indices, baseVertex: base)
 | 
				
			||||||
 | 
					    self._instances.append(.init(texture: texture, size: UInt16(mesh.indices.count)))
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  //MARK: - Private implementation
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private mutating func flush() {
 | 
				
			||||||
 | 
					    assert(self._instances.count > 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if self._active == .begin {
 | 
				
			||||||
 | 
					      self._renderer.setupBatch(blendMode: self._blendMode, frame: self.viewport ?? .init(self._renderer.frame))
 | 
				
			||||||
 | 
					      self._active = .active
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    var base = 0, offset = 0
 | 
				
			||||||
 | 
					    var prevTexture: RendererTexture2D! = nil
 | 
				
			||||||
 | 
					    for instance in self._instances {
 | 
				
			||||||
 | 
					      if prevTexture != nil && prevTexture != instance.texture {
 | 
				
			||||||
 | 
					        self._renderer.submit(mesh: self._mesh, texture: prevTexture, offset: base, count: offset - base)
 | 
				
			||||||
 | 
					        base = offset
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      offset += Int(instance.size)
 | 
				
			||||||
 | 
					      prevTexture = instance.texture
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    self._renderer.submit(mesh: self._mesh, texture: prevTexture, offset: base, count: offset - base)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    self._instances.removeAll(keepingCapacity: true)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private mutating func drawQuad(_ texture: RendererTexture2D,
 | 
				
			||||||
 | 
					    position: SIMD2<Float>, size: Size<Float>, color: Color<Float> = .white
 | 
				
			||||||
 | 
					  ) {
 | 
				
			||||||
 | 
					    self.drawQuad(texture,
 | 
				
			||||||
 | 
					      p00: position,
 | 
				
			||||||
 | 
					      p10: .init(position.x + size.w, position.y),
 | 
				
			||||||
 | 
					      p01: .init(position.x, position.y + size.h),
 | 
				
			||||||
 | 
					      p11: .init(position.x + size.w, position.y + size.h), color: color)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private mutating func drawQuad(_ texture: RendererTexture2D,
 | 
				
			||||||
 | 
					    position: SIMD2<Float>, angle: Float, size: Size<Float>, offset bias: SIMD2<Float>, color: Color<Float> = .white
 | 
				
			||||||
 | 
					  ) {
 | 
				
			||||||
 | 
					    let (tc, ts) = (cos(angle), sin(angle))
 | 
				
			||||||
 | 
					    let rotate = matrix_float2x2(
 | 
				
			||||||
 | 
					      .init( tc, ts),
 | 
				
			||||||
 | 
					      .init(-ts, tc))
 | 
				
			||||||
 | 
					    let right = SIMD2<Float>(size.w, 0) * rotate
 | 
				
			||||||
 | 
					    let down  = SIMD2<Float>(0, size.h) * rotate
 | 
				
			||||||
 | 
					    self.drawQuad(texture,
 | 
				
			||||||
 | 
					      p00: position - right *       bias.x - down *      bias.y,
 | 
				
			||||||
 | 
					      p10: position + right * (1 - bias.x) - down *      bias.y,
 | 
				
			||||||
 | 
					      p01: position - right *       bias.x + down * (1 - bias.y),
 | 
				
			||||||
 | 
					      p11: position + right * (1 - bias.x) + down * (1 - bias.y), color: color)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private mutating func drawQuad(_ texture: RendererTexture2D,
 | 
				
			||||||
 | 
					    p00: SIMD2<Float>, p10: SIMD2<Float>, p01: SIMD2<Float>, p11: SIMD2<Float>,
 | 
				
			||||||
 | 
					    flip: Sprite.Flip, color: Color<Float> = .white
 | 
				
			||||||
 | 
					  ) {
 | 
				
			||||||
 | 
					    let flipX = flip.contains(.x), flipY = flip.contains(.y)
 | 
				
			||||||
 | 
					    self.drawQuad(texture, p00: p00, p10: p10, p01: p01, p11: p11,
 | 
				
			||||||
 | 
					      t00: .init(flipX ? 1 : 0, flipY ? 0 : 1),
 | 
				
			||||||
 | 
					      t10: .init(flipX ? 0 : 1, flipY ? 0 : 1),
 | 
				
			||||||
 | 
					      t01: .init(flipX ? 1 : 0, flipY ? 1 : 0),
 | 
				
			||||||
 | 
					      t11: .init(flipX ? 0 : 1, flipY ? 1 : 0),
 | 
				
			||||||
 | 
					      color: color)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private mutating func drawQuad(_ texture: RendererTexture2D, _ source: Rect<Float>,
 | 
				
			||||||
 | 
					    p00: SIMD2<Float>, p10: SIMD2<Float>, p01: SIMD2<Float>, p11: SIMD2<Float>,
 | 
				
			||||||
 | 
					    color: Color<Float> = .white
 | 
				
			||||||
 | 
					  ) {
 | 
				
			||||||
 | 
					    let invSize = 1 / Size<Float>(texture.size)
 | 
				
			||||||
 | 
					    let st = Extent(source) * invSize
 | 
				
			||||||
 | 
					    self.drawQuad(texture, p00: p00, p10: p10, p01: p01, p11: p11,
 | 
				
			||||||
 | 
					      t00: SIMD2(st.left,  st.top),    t10: SIMD2(st.right, st.top),
 | 
				
			||||||
 | 
					      t01: SIMD2(st.left,  st.bottom), t11: SIMD2(st.right, st.bottom), color: color)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private mutating func drawQuad(_ texture: RendererTexture2D,
 | 
				
			||||||
 | 
					    p00: SIMD2<Float>, p10: SIMD2<Float>, p01: SIMD2<Float>, p11: SIMD2<Float>,
 | 
				
			||||||
 | 
					    t00: SIMD2<Float> = SIMD2(0, 1), t10: SIMD2<Float> = SIMD2(1, 1),
 | 
				
			||||||
 | 
					    t01: SIMD2<Float> = SIMD2(0, 0), t11: SIMD2<Float> = SIMD2(1, 0),
 | 
				
			||||||
 | 
					    color: Color<Float> = .white
 | 
				
			||||||
 | 
					  ) {
 | 
				
			||||||
 | 
					    let color = SIMD4(color)
 | 
				
			||||||
 | 
					    let base = self._mesh.vertexCount
 | 
				
			||||||
 | 
					    self._mesh.insert(vertices: zip([ p00, p01, p10, p11 ], [ t00, t01, t10, t11 ])
 | 
				
			||||||
 | 
					      .map { .init(position: $0, texCoord: $1, color: color) })
 | 
				
			||||||
 | 
					    self._mesh.insert(indices: [ 0, 1, 2,  2, 1, 3 ], baseVertex: base)
 | 
				
			||||||
 | 
					    self._instances.append(.init(texture: texture, size: 6))
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  internal struct SpriteInstance {
 | 
				
			||||||
 | 
					    let texture: RendererTexture2D
 | 
				
			||||||
 | 
					    let size: IndexType
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  internal enum ActiveState {
 | 
				
			||||||
 | 
					    case inactive, begin, active
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										106
									
								
								Sources/Voxelotl/SpriteTestGame.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										106
									
								
								Sources/Voxelotl/SpriteTestGame.swift
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,106 @@
 | 
				
			|||||||
 | 
					import Foundation
 | 
				
			||||||
 | 
					import simd
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					internal class SpriteTestGame: GameDelegate {
 | 
				
			||||||
 | 
					  private var spriteBatch: SpriteBatch!
 | 
				
			||||||
 | 
					  private var player = TestPlayer(position: .one * 10)
 | 
				
			||||||
 | 
					  private var texture: RendererTexture2D!
 | 
				
			||||||
 | 
					  private var wireShark: RendererTexture2D!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private static let levelWidth = 40, levelHeight = 23
 | 
				
			||||||
 | 
					  private var level: [UInt8]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  init() {
 | 
				
			||||||
 | 
					    self.level = .init(repeating: 0, count: Self.levelWidth * Self.levelHeight)
 | 
				
			||||||
 | 
					    for i in 0..<Self.levelWidth { self.level[i + (Self.levelHeight - 1) * Self.levelWidth] = 1 }
 | 
				
			||||||
 | 
					    for i in 0..<Self.levelWidth { self.level[i + (Self.levelHeight - 2) * Self.levelWidth] = 1 }
 | 
				
			||||||
 | 
					    for i in 17...20 { self.level[10 + (i) * Self.levelWidth] = 1 }
 | 
				
			||||||
 | 
					    for i in 17...20 { self.level[14 + (i) * Self.levelWidth] = 1 }
 | 
				
			||||||
 | 
					    for i in 11...13 { self.level[i + (17) * Self.levelWidth] = 1 }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  func create(_ renderer: Renderer) {
 | 
				
			||||||
 | 
					    self.spriteBatch = renderer.createSpriteBatch()
 | 
				
			||||||
 | 
					    // Uncomment to squeesh
 | 
				
			||||||
 | 
					    //self.spriteBatch.viewport = .init(renderer.frame)
 | 
				
			||||||
 | 
					    renderer.clearColor = .init(hue: 301.2, saturation: 0.357, value: 0.488).linear // .magenta.mix(.white, 0.4).mix(.black, 0.8)
 | 
				
			||||||
 | 
					    self.texture = renderer.loadTexture(resourcePath: "test.png")
 | 
				
			||||||
 | 
					    self.wireShark = renderer.loadTexture(resourcePath: "wireshark.png")
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  func update(_ time: GameTime) {
 | 
				
			||||||
 | 
					    if let pad = GameController.current?.state {
 | 
				
			||||||
 | 
					      self.player.position += pad.leftStick.radialDeadzone(min: 0.1, max: 1) * 1000 * Float(time.delta)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    self.player.rotate += Float(time.delta)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  func draw(_ renderer: Renderer, _ time: GameTime) {
 | 
				
			||||||
 | 
					    self.spriteBatch.begin(blendMode: .premultiplied)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Draw background
 | 
				
			||||||
 | 
					    self.spriteBatch.draw(self.texture,
 | 
				
			||||||
 | 
					      source: .init(
 | 
				
			||||||
 | 
					        origin: .init(scalar: fmod(player.rotate, 32)),
 | 
				
			||||||
 | 
					        size:   (spriteBatch.viewport?.size ?? Size<Float>(renderer.frame.size)) * 0.01),
 | 
				
			||||||
 | 
					      destination: nil,
 | 
				
			||||||
 | 
					      color: .init(renderer.clearColor).setAlpha(0.7))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Draw level
 | 
				
			||||||
 | 
					    let scale: Float = 64
 | 
				
			||||||
 | 
					    for y in 0..<Self.levelHeight {
 | 
				
			||||||
 | 
					      for x in 0..<Self.levelWidth {
 | 
				
			||||||
 | 
					        if level[x + Self.levelWidth * y] == 1 {
 | 
				
			||||||
 | 
					          self.spriteBatch.draw(self.texture, destination: .init(origin: Point(Point(x, y)) * scale, size: .init(scalar: scale)))
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Draw wireshark (controllable)
 | 
				
			||||||
 | 
					    self.spriteBatch.draw(self.wireShark,
 | 
				
			||||||
 | 
					      position: player.position,
 | 
				
			||||||
 | 
					      scale: .init(sin(player.rotate * 5), cos(player.rotate * 3)),
 | 
				
			||||||
 | 
					      angle: player.rotate, origin: .init(250, 275))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Sliding door test
 | 
				
			||||||
 | 
					    self.spriteBatch.draw(self.texture, source: .init(
 | 
				
			||||||
 | 
					        origin: .init(4 + cos(player.rotate / 1.2) * 4, 0),
 | 
				
			||||||
 | 
					        size:   .init(4 + cos(player.rotate / 1.3) * 4, 16)),
 | 
				
			||||||
 | 
					      transform: .init(
 | 
				
			||||||
 | 
					        .init( 24,    0, 0),
 | 
				
			||||||
 | 
					        .init(  0,   12, 0),
 | 
				
			||||||
 | 
					        .init(704, 1152, 1)), color: .red.mix(.white, 0.3))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Draw mouse cursor
 | 
				
			||||||
 | 
					    var mpos = Mouse.position
 | 
				
			||||||
 | 
					    if self.spriteBatch.viewport != nil {
 | 
				
			||||||
 | 
					      mpos /= SIMD2(Size<Float>(renderer.frame.size))
 | 
				
			||||||
 | 
					      mpos *= SIMD2(self.spriteBatch.viewport!.size)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    let inter = 0.5 + sin(player.rotate * 10) * 0.5
 | 
				
			||||||
 | 
					    let color = Color<Float>.green.mix(.white, 0.3)
 | 
				
			||||||
 | 
					    let mesh = Mesh<VertexPosition2DTexcoordColor, UInt16>.init(vertices: [
 | 
				
			||||||
 | 
					      .init(position: mpos, texCoord: .zero, color: .one),
 | 
				
			||||||
 | 
					      .init(position: mpos + .init(50, 0) + .init(-50,  50) * inter, texCoord: .zero, color: SIMD4(color)),
 | 
				
			||||||
 | 
					      .init(position: mpos + .init(0, 50) + .init( 50, -50) * inter, texCoord: .zero, color: SIMD4(color)),
 | 
				
			||||||
 | 
					      .init(position: mpos + .init(80, 80), texCoord: .zero, color: .zero)
 | 
				
			||||||
 | 
					    ], indices: [ 0, 1, 2,  1, 2, 3 ])
 | 
				
			||||||
 | 
					    if Mouse.down(.left) {
 | 
				
			||||||
 | 
					      self.spriteBatch.draw(self.texture, mesh: mesh)
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      self.spriteBatch.draw(self.texture, vertices: mesh.vertices[..<3])
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    self.spriteBatch.end()
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fileprivate struct TestPlayer {
 | 
				
			||||||
 | 
					  var position: SIMD2<Float>
 | 
				
			||||||
 | 
					  var rotate: Float = 0
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fileprivate extension Color {
 | 
				
			||||||
 | 
					  func setAlpha(_ newAlpha: T) -> Self {
 | 
				
			||||||
 | 
					    return .init(r: r, g: g, b: b, a: newAlpha)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										28
									
								
								Sources/Voxelotl/shader2D.metal
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								Sources/Voxelotl/shader2D.metal
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,28 @@
 | 
				
			|||||||
 | 
					#include "shadertypes.h"
 | 
				
			||||||
 | 
					#include <metal_stdlib>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct FragmentInput {
 | 
				
			||||||
 | 
					  float4 position [[position]];
 | 
				
			||||||
 | 
					  float2 texCoord;
 | 
				
			||||||
 | 
					  half4  color;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					vertex FragmentInput vertex2DMain(uint vertexID [[vertex_id]],
 | 
				
			||||||
 | 
					  device const Vertex2D* vtx [[buffer(VertexShaderInputIdxVertices)]],
 | 
				
			||||||
 | 
					  constant Shader2DUniforms& u [[buffer(VertexShaderInputIdxUniforms)]]
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
					  FragmentInput out;
 | 
				
			||||||
 | 
					  out.position = u.projection * float4(vtx[vertexID].position, 0.0, 1.0);
 | 
				
			||||||
 | 
					  out.texCoord = vtx[vertexID].texCoord;
 | 
				
			||||||
 | 
					  out.color    = half4(vtx[vertexID].color);
 | 
				
			||||||
 | 
					  return out;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fragment half4 fragment2DMain(FragmentInput in [[stage_in]],
 | 
				
			||||||
 | 
					  metal::texture2d<half, metal::access::sample> texture [[texture(0)]]
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
					  constexpr metal::sampler sampler(metal::address::repeat, metal::filter::linear);
 | 
				
			||||||
 | 
					  half4 texel = texture.sample(sampler, in.texCoord);
 | 
				
			||||||
 | 
					  return texel * in.color;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -45,4 +45,16 @@ typedef struct {
 | 
				
			|||||||
  float specularIntensity;
 | 
					  float specularIntensity;
 | 
				
			||||||
} FragmentShaderUniforms;
 | 
					} FragmentShaderUniforms;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#pragma mark - UI & 2D Shader
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typedef struct {
 | 
				
			||||||
 | 
					  vector_float2 position;
 | 
				
			||||||
 | 
					  vector_float2 texCoord;
 | 
				
			||||||
 | 
					  vector_float4 color;
 | 
				
			||||||
 | 
					} Vertex2D;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typedef struct {
 | 
				
			||||||
 | 
					  matrix_float4x4 projection;
 | 
				
			||||||
 | 
					} Shader2DUniforms;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#endif//SHADERTYPES_H
 | 
					#endif//SHADERTYPES_H
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										
											BIN
										
									
								
								Sources/Voxelotl/wireshark.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Sources/Voxelotl/wireshark.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 124 KiB  | 
		Reference in New Issue
	
	Block a user