break up gameplay stuff

This commit is contained in:
2024-08-13 08:38:21 +10:00
parent 5b97a02288
commit dc88042a36
13 changed files with 448 additions and 132 deletions

View File

@ -4,16 +4,17 @@ import QuartzCore.CAMetalLayer
public class Application { public class Application {
private let cfg: ApplicationConfiguration private let cfg: ApplicationConfiguration
private let del: GameDelegate
private var window: OpaquePointer? = nil private var window: OpaquePointer? = nil
private var view: SDL_MetalView? = nil private var view: SDL_MetalView? = nil
private var renderer: Renderer? = nil private var renderer: Renderer? = nil
private var lastCounter: UInt64 = 0 private var lastCounter: UInt64 = 0
private var fpsCalculator = FPSCalculator() private var time: Duration = .zero
public init(delegate: GameDelegate, configuration: ApplicationConfiguration) {
public init(configuration: ApplicationConfiguration) {
self.cfg = configuration self.cfg = configuration
self.del = delegate
} }
private func initialize() -> ApplicationExecutionState { private func initialize() -> ApplicationExecutionState {
@ -48,7 +49,7 @@ public class Application {
do { do {
let layer = unsafeBitCast(SDL_Metal_GetLayer(view), to: CAMetalLayer.self) let layer = unsafeBitCast(SDL_Metal_GetLayer(view), to: CAMetalLayer.self)
layer.displaySyncEnabled = cfg.vsyncMode == .off ? false : true layer.displaySyncEnabled = cfg.vsyncMode == .off ? false : true
self.renderer = try Renderer(layer: layer, size: SIMD2<Int>(Int(backBufferWidth), Int(backBufferHeight))) self.renderer = try Renderer(layer: layer, size: .init(Int(backBufferWidth), Int(backBufferHeight)))
} catch RendererError.initFailure(let message) { } catch RendererError.initFailure(let message) {
printErr("Renderer init error: \(message)") printErr("Renderer init error: \(message)")
return .exitFailure return .exitFailure
@ -103,8 +104,9 @@ public class Application {
return .running return .running
case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED: case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED:
let backBufferSize = SIMD2(Int(event.window.data1), Int(event.window.data2)) let backBufferSize = Size(Int(event.window.data1), Int(event.window.data2))
renderer!.resize(size: backBufferSize) self.renderer!.resize(size: backBufferSize)
self.del.resize(backBufferSize)
return .running return .running
default: default:
@ -112,13 +114,17 @@ public class Application {
} }
} }
private func update(_ deltaTime: Double) -> ApplicationExecutionState { private func update() -> ApplicationExecutionState {
fpsCalculator.frame(deltaTime: deltaTime) { fps in let deltaTime = getDeltaTime()
print("FPS: \(fps)") time += deltaTime
} let gameTime = GameTime(total: time, delta: deltaTime)
del.update(gameTime)
do { do {
try renderer!.paint() try renderer!.beginFrame()
del.draw(renderer!, gameTime)
renderer!.endFrame()
} catch RendererError.drawFailure(let message) { } catch RendererError.drawFailure(let message) {
printErr("Renderer draw error: \(message)") printErr("Renderer draw error: \(message)")
return .exitFailure return .exitFailure
@ -130,11 +136,14 @@ public class Application {
return .running return .running
} }
private func getDeltaTime() -> Double { private func getDeltaTime() -> Duration {
let counter = SDL_GetPerformanceCounter() let counter = SDL_GetPerformanceCounter()
let divisor = 1.0 / Double(SDL_GetPerformanceFrequency()) defer {
defer { lastCounter = counter } lastCounter = counter
return Double(counter &- lastCounter) * divisor }
let difference = Double(counter &- lastCounter)
let divisor = Double(SDL_GetPerformanceFrequency())
return Duration.seconds(difference / divisor)
} }
func run() -> Int32 { func run() -> Int32 {
@ -149,9 +158,7 @@ public class Application {
break quit break quit
} }
} }
res = update()
let deltaTime = getDeltaTime()
res = update(deltaTime)
} }
return res == .exitSuccess ? 0 : 1 return res == .exitSuccess ? 0 : 1

View File

@ -8,13 +8,19 @@ add_executable(Voxelotl MACOSX_BUNDLE
FloatExtensions.swift FloatExtensions.swift
Matrix4x4.swift Matrix4x4.swift
Rectangle.swift
NSImageLoader.swift NSImageLoader.swift
Renderer.swift Renderer.swift
GameController.swift GameController.swift
Camera.swift Camera.swift
Player.swift
FPSCalculator.swift FPSCalculator.swift
GameDelegate.swift
Application.swift Application.swift
Game.swift
main.swift) main.swift)
set_source_files_properties( set_source_files_properties(

View File

@ -1,37 +1,113 @@
import simd import simd
struct Camera { public final class Camera {
private var position = SIMD3<Float>.zero private struct Dirty: OptionSet {
private var rotation = SIMD2<Float>.zero let rawValue: UInt8
var view: matrix_float4x4 { static let projection = Self(rawValue: 1 << 0)
.rotate(yawPitch: rotation) * .translate(-position) static let view = Self(rawValue: 1 << 1)
static let viewProj = Self(rawValue: 1 << 2)
} }
mutating func update(deltaTime: Float) { private var _position: SIMD3<Float>
if let pad = GameController.current?.state { private var _rotation: simd_quatf
let turning = pad.rightStick.radialDeadzone(min: 0.1, max: 1) private var _fieldOfView: Float
rotation += turning * deltaTime private var _aspectRatio: Float
if rotation.x < 0.0 { private var _zNearFar: ClosedRange<Float>
rotation.x += .pi * 2
} else if rotation.x > .pi * 2 {
rotation.x -= .pi * 2
}
rotation.y = rotation.y.clamp(-.pi * 0.5, .pi * 0.5)
let movement = pad.leftStick.cardinalDeadzone(min: 0.1, max: 1) private var _dirty: Dirty
private var _projection: matrix_float4x4
private var _view: matrix_float4x4
private var _viewProjection: matrix_float4x4
let rotc = cos(rotation.x), rots = sin(rotation.x) public var position: SIMD3<Float> {
position += .init( get { self._position }
movement.x * rotc - movement.y * rots, set(position) {
0, if self._position != position {
movement.y * rotc + movement.x * rots self._position = position
) * deltaTime self._dirty.insert(.view)
if pad.pressed(.back) {
position = .zero
rotation = .zero
} }
} }
} }
public var rotation: simd_quatf {
get { self._rotation }
set(rotation) {
if self._rotation != rotation {
self._rotation = rotation
self._dirty.insert(.view)
}
}
}
public var fieldOfView: Float {
get { self._fieldOfView.degrees }
set(fov) {
let fovRad = fov.radians
self._fieldOfView = fovRad
self._dirty.insert(.projection)
}
}
public var aspectRatio: Float {
get { self._aspectRatio }
set(aspect) {
if self._aspectRatio != aspect {
self._aspectRatio = aspect
self._dirty.insert(.projection)
}
}
}
public func setSize(_ size: Size<Int>) {
self.aspectRatio = Float(size.w) / Float(size.h)
}
public var range: ClosedRange<Float> {
get { self._zNearFar }
set(range) {
self._zNearFar = range
self._dirty.insert(.projection)
}
}
public var projection: matrix_float4x4 {
if self._dirty.contains(.projection) {
self._projection = .perspective(
verticalFov: self._fieldOfView,
aspect: self._aspectRatio,
near: self._zNearFar.lowerBound,
far: self._zNearFar.upperBound)
self._dirty.remove(.projection)
self._dirty.insert(.viewProj)
}
return self._projection
}
public var view: matrix_float4x4 {
if self._dirty.contains(.view) {
self._view = matrix_float4x4(rotation) * .translate(-position)
self._dirty.remove(.view)
self._dirty.insert(.viewProj)
}
return self._view
}
public var viewProjection: matrix_float4x4 {
if !self._dirty.isEmpty {
self._viewProjection = self.projection * self.view
self._dirty.remove(.viewProj)
}
return self._viewProjection
}
init(fov: Float, size: Size<Int>, range: ClosedRange<Float>) {
self._position = .zero
self._rotation = .identity
self._fieldOfView = fov.radians
self._aspectRatio = Float(size.w) / Float(size.h)
self._zNearFar = range
self._dirty = [ .projection, .view, .viewProj ]
self._projection = .init()
self._view = .init()
self._viewProjection = .init()
}
} }

View File

@ -1,18 +1,20 @@
import Foundation import Foundation
public struct FPSCalculator { public struct FPSCalculator {
private var _accumulator = 0.0 private var _accumulator = Duration.zero
private var _framesCount = 0 private var _framesCount = 0
public mutating func frame(deltaTime: Double, result: (_ fps: Int) -> Void) { public mutating func frame(deltaTime: Duration, result: (_ fps: Int) -> Void) {
_framesCount += 1 self._framesCount += 1
_accumulator += deltaTime self._accumulator += deltaTime
if _accumulator >= 1.0 { if self._accumulator >= Duration.seconds(1) {
result(_framesCount) result(self._framesCount)
_framesCount = 0 self._framesCount = 0
_accumulator = fmod(_accumulator, 1.0) self._accumulator = .init(
secondsComponent: 0,
attosecondsComponent: self._accumulator.components.attoseconds)
} }
} }
} }

View File

@ -0,0 +1,53 @@
import simd
struct Instance {
var position: SIMD3<Float> = .zero
var scale: SIMD3<Float> = .one
var rotation: simd_quatf = .identity
var color: SIMD4<Float> = .one
}
class Game: GameDelegate {
private var fpsCalculator = FPSCalculator()
var camera = Camera(fov: 60, size: .one, range: 0.03...25)
var player = Player()
var projection: matrix_float4x4 = .identity
func fixedUpdate(_ time: GameTime) {
}
func update(_ time: GameTime) {
let deltaTime = Float(time.delta.asFloat)
fpsCalculator.frame(deltaTime: time.delta) { fps in
print("FPS: \(fps)")
}
player.update(deltaTime: deltaTime)
camera.position = player.position
camera.rotation =
simd_quatf(angle: player.rotation.y, axis: .init(1, 0, 0)) *
simd_quatf(angle: player.rotation.x, axis: .init(0, 1, 0))
}
func draw(_ renderer: Renderer, _ time: GameTime) {
let totalTime = Float(time.total.asFloat)
let instances: [Instance] = [
Instance(
position: .init(0, sin(totalTime * 1.5) * 0.5, -2),
scale: .init(repeating: 0.25),
rotation: .init(angle: totalTime * 3.0, axis: .init(0, 1, 0)),
color: .init(0.5, 0.5, 1, 1)),
Instance(position: .init(0, -1, 0), scale: .init(10, 0.1, 10)),
Instance(position: .init(-2.5, 0, -3), color: .init(1, 0.5, 0.75, 1)),
Instance(position: .init(-2.5, -0.5, -5), color: .init(0.75, 1, 1, 1))
]
renderer.batch(instances: instances, camera: self.camera)
}
func resize(_ size: Size<Int>) {
self.camera.setSize(size)
}
}

View File

@ -0,0 +1,23 @@
public protocol GameDelegate {
func fixedUpdate(_ time: GameTime)
func update(_ time: GameTime)
func draw(_ renderer: Renderer, _ time: GameTime)
func resize(_ size: Size<Int>)
}
public extension GameDelegate {
func fixedUpdate(_ time: GameTime) {}
func update(_ time: GameTime) {}
func resize(_ size: Size<Int>) {}
}
public struct GameTime {
let total: Duration
let delta: Duration
}
extension Duration {
var asFloat: Double {
Double(components.seconds) + Double(components.attoseconds) * 1e-18
}
}

View File

@ -74,14 +74,14 @@ public extension simd_float4x4 {
.init(tx, ty, tz, 1)) .init(tx, ty, tz, 1))
} }
static func perspective(verticalFov fovY: T, aspect: T, near: T, far: T) -> Self { static func perspective(verticalFov fovY: T, aspect: T, near zNear: T, far zFar: T) -> Self {
let tanHalfFovY = tan(fovY * T(0.5)) let tanHalfFovY = tan(fovY * T(0.5))
let invClipRange = 1 / (near - far) let invClipRange = 1 / (zNear - zFar)
let y = 1 / tanHalfFovY let y = 1 / tanHalfFovY
let x = y / aspect let x = y / aspect
let z = far * invClipRange let z = zFar * invClipRange
let w = near * z // (far * near) * invClipRange let w = zNear * z
return .init( return .init(
.init(x, 0, 0, 0), .init(x, 0, 0, 0),
.init(0, y, 0, 0), .init(0, y, 0, 0),
@ -89,3 +89,7 @@ public extension simd_float4x4 {
.init(0, 0, w, 0)) .init(0, 0, w, 0))
} }
} }
extension simd_quatf {
static var identity: Self { .init(real: 1, imag: .zero) }
}

View File

@ -0,0 +1,36 @@
import simd
struct Player {
private var _position = SIMD3<Float>.zero
private var _rotation = SIMD2<Float>.zero
public var position: SIMD3<Float> { self._position }
public var rotation: SIMD2<Float> { self._rotation }
mutating func update(deltaTime: Float) {
if let pad = GameController.current?.state {
let turning = pad.rightStick.radialDeadzone(min: 0.1, max: 1)
_rotation += turning * deltaTime * 3.0
if _rotation.x < 0.0 {
_rotation.x += .pi * 2
} else if _rotation.x > .pi * 2 {
_rotation.x -= .pi * 2
}
_rotation.y = _rotation.y.clamp(-.pi * 0.5, .pi * 0.5)
let movement = pad.leftStick.cardinalDeadzone(min: 0.1, max: 1)
let rotc = cos(_rotation.x), rots = sin(_rotation.x)
_position += .init(
movement.x * rotc - movement.y * rots,
0,
movement.y * rotc + movement.x * rots
) * deltaTime * 3.0
if pad.pressed(.back) {
_position = .zero
_rotation = .zero
}
}
}
}

View File

@ -0,0 +1,99 @@
struct Point<T: AdditiveArithmetic>: Equatable {
var x: T, y: T
static var zero: Self { .init(.zero, .zero) }
init(_ x: T, _ y: T) {
self.x = x
self.y = y
}
@inline(__always) static func == (lhs: Self, rhs: Self) -> Bool { lhs.x == rhs.x && lhs.y == rhs.y }
@inline(__always) static func != (lhs: Self, rhs: Self) -> Bool { lhs.x != rhs.x || lhs.y != rhs.y }
}
extension Point where T: AdditiveArithmetic {
@inline(__always) static func + (lhs: Self, rhs: Self) -> Self { Self(lhs.x + rhs.x, lhs.y + rhs.y) }
@inline(__always) static func - (lhs: Self, rhs: Self) -> Self { Self(lhs.x - rhs.x, lhs.y - rhs.y) }
@inline(__always) static func += (lhs: inout Self, rhs: Self) { lhs.x += rhs.x; lhs.y += rhs.y }
@inline(__always) static func -= (lhs: inout Self, rhs: Self) { lhs.x -= rhs.x; lhs.y -= rhs.y }
}
extension SIMD2 where Scalar: AdditiveArithmetic {
init(_ point: Point<Scalar>) {
self.init(point.x, point.y)
}
}
public struct Size<T: AdditiveArithmetic>: Equatable {
var w: T, h: T
static var zero: Self { .init(.zero, .zero) }
init(_ w: T, _ h: T) {
self.w = w
self.h = h
}
@inline(__always) public static func == (lhs: Self, rhs: Self) -> Bool { lhs.w == rhs.w && lhs.h == rhs.h }
@inline(__always) public static func != (lhs: Self, rhs: Self) -> Bool { lhs.w != rhs.w || lhs.h != rhs.h }
}
extension Size where T: BinaryInteger {
static var one: Self { .init(T(1), T(1)) }
}
struct Rect<T: AdditiveArithmetic>: Equatable {
var x: T, y: T, w: T, h: T
var origin: Point<T> {
get { .init(self.x, self.y) }
set(point) { self.x = point.x; self.y = point.y }
}
var size: Size<T> {
get { .init(self.w, self.h) }
set(size) { self.w = size.w; self.h = size.h }
}
static var zero: Self { .init(origin: .zero, size: .zero) }
init(x: T, y: T, width: T, height: T) {
self.x = x
self.y = y
self.w = width
self.h = height
}
init(origin: Point<T>, size: Size<T>) {
self.x = origin.x
self.y = origin.y
self.w = size.w
self.h = size.h
}
@inline(__always) static func == (lhs: Self, rhs: Self) -> Bool {
lhs.x == rhs.x && lhs.y == rhs.y && lhs.w == rhs.w && lhs.h == rhs.h
}
}
extension Rect where T: AdditiveArithmetic {
var left: T { x }
var right: T { x + w }
var up: T { y }
var down: T { y + h }
}
struct Extent<T: AdditiveArithmetic>: Equatable {
var top: T, bottom: T, left: T, right: T
@inline(__always) static func == (lhs: Self, rhs: Self) -> Bool {
lhs.left == rhs.left && lhs.right == rhs.right && lhs.top == rhs.top && lhs.bottom == rhs.bottom
}
}
extension Extent where T: Comparable {
var size: Size<T> { .init(
right > left ? right - left : left - right,
bottom > top ? bottom - top : top - bottom) }
}

View File

@ -43,11 +43,11 @@ fileprivate let cubeIndices: [UInt16] = [
fileprivate let numFramesInFlight: Int = 3 fileprivate let numFramesInFlight: Int = 3
fileprivate let depthFormat: MTLPixelFormat = .depth16Unorm fileprivate let depthFormat: MTLPixelFormat = .depth16Unorm
class Renderer { public class Renderer {
private var device: MTLDevice private var device: MTLDevice
private var layer: CAMetalLayer private var layer: CAMetalLayer
private var viewport: MTLViewport private var backBufferSize: Size<Int>
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 let passDescription = MTLRenderPassDescriptor() private let passDescription = MTLRenderPassDescriptor()
@ -55,12 +55,19 @@ class Renderer {
private var depthStencilState: MTLDepthStencilState private var depthStencilState: MTLDepthStencilState
private var depthTextures: [MTLTexture] private var depthTextures: [MTLTexture]
private var _commandBuf: MTLCommandBuffer!
private var _encoder: MTLRenderCommandEncoder!
private var _rt: (any CAMetalDrawable)!
private var vtxBuffer: MTLBuffer, idxBuffer: MTLBuffer private var vtxBuffer: MTLBuffer, idxBuffer: MTLBuffer
private var defaultTexture: MTLTexture private var defaultTexture: MTLTexture
private var cubeTexture: MTLTexture? = nil private var cubeTexture: MTLTexture? = nil
private let inFlightSemaphore = DispatchSemaphore(value: numFramesInFlight) private let inFlightSemaphore = DispatchSemaphore(value: numFramesInFlight)
private var frame = 0 private var currentFrame = 0
var frame: Rect<Int> { .init(origin: .zero, size: self.backBufferSize) }
var aspectRatio: Float { self._aspectRatio }
fileprivate static func createMetalDevice() -> MTLDevice? { fileprivate static func createMetalDevice() -> MTLDevice? {
MTLCopyAllDevices().reduce(nil, { best, dev in MTLCopyAllDevices().reduce(nil, { best, dev in
@ -71,7 +78,7 @@ class Renderer {
}) })
} }
init(layer metalLayer: CAMetalLayer, size: SIMD2<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
@ -89,8 +96,8 @@ class Renderer {
} }
self.queue = queue self.queue = queue
self.viewport = Self.makeViewport(size: size) self.backBufferSize = size
self.aspectRatio = Float(size.x) / Float(size.y) self._aspectRatio = Float(self.backBufferSize.w) / Float(self.backBufferSize.w)
passDescription.colorAttachments[0].loadAction = .clear passDescription.colorAttachments[0].loadAction = .clear
passDescription.colorAttachments[0].storeAction = .store passDescription.colorAttachments[0].storeAction = .store
@ -239,12 +246,12 @@ class Renderer {
return newTexture return newTexture
} }
private static func createDepthTexture(_ device: MTLDevice, _ size: SIMD2<Int>, format: MTLPixelFormat private static func createDepthTexture(_ device: MTLDevice, _ size: Size<Int>, format: MTLPixelFormat
) -> MTLTexture? { ) -> MTLTexture? {
let texDescriptor = MTLTextureDescriptor.texture2DDescriptor( let texDescriptor = MTLTextureDescriptor.texture2DDescriptor(
pixelFormat: format, pixelFormat: format,
width: size.x, width: size.w,
height: size.y, height: size.h,
mipmapped: false) mipmapped: false)
texDescriptor.depth = 1 texDescriptor.depth = 1
texDescriptor.sampleCount = 1 texDescriptor.sampleCount = 1
@ -261,111 +268,110 @@ class Renderer {
return depthStencilTexture return depthStencilTexture
} }
static func makeViewport(size: SIMD2<Int>) -> MTLViewport { static func makeViewport(rect: Rect<Int>, znear: Double = 0.0, zfar: Double = 1.0) -> MTLViewport {
MTLViewport( MTLViewport(
originX: 0.0, originY: 0.0, originX: Double(rect.x),
width: Double(size.x), originY: Double(rect.y),
height: Double(size.y), width: Double(rect.w),
znear: 0, zfar: 1) height: Double(rect.h),
znear: znear, zfar: zfar)
} }
func resize(size: SIMD2<Int>) { func resize(size: Size<Int>) {
if Int(self.viewport.width) != size.x || Int(self.viewport.height) != size.y { if self.backBufferSize.w != size.w || self.backBufferSize.h != size.h {
self.depthTextures = (0..<numFramesInFlight).map { _ in self.depthTextures = (0..<numFramesInFlight).map { _ in
Self.createDepthTexture(device, size, format: depthFormat)! Self.createDepthTexture(device, size, format: depthFormat)!
} }
} }
self.aspectRatio = Float(size.x) / Float(size.y) self.backBufferSize = size
self.viewport = Self.makeViewport(size: size) self._aspectRatio = Float(self.backBufferSize.w) / Float(self.backBufferSize.h)
} }
//FIXME: temp func beginFrame() throws {
var camera = Camera()
var time: Float = 0
func paint() throws {
camera.update(deltaTime: 0.025)
#if true
let projection = matrix_float4x4.perspective(
verticalFov: Float(60.0).radians,
aspect: aspectRatio,
near: 0.03,
far: 25)
#else
let projection = matrix_float4x4.orthographic(
left: -aspectRatio, right: aspectRatio,
bottom: -1, top: 1,
near: -0.03, far: -25)
#endif
let view = camera.view
let instances: [ShaderInstance] = [
ShaderInstance(model: .translate(.init(0, sin(time * 0.5) * 0.5, -2)) * .rotate(y: time) * .scale(0.25), color: .init(0.5, 0.5, 1, 1)),
ShaderInstance(model: .translate(.init(0, -1, 0)) * .scale(.init(10, 0.1, 10)), color: .init(1, 1, 1, 1)),
ShaderInstance(model: .translate(.init(-2.5, 0, -3)), color: .init(1, 0.5, 0.75, 1)),
ShaderInstance(model: .translate(.init(-2.5, -0.5, -5)), color: .init(0.75, 1, 1, 1))
]
time += 0.025
var uniforms = ShaderUniforms(projView: projection * view)
// 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)
guard let rt = layer.nextDrawable() else { guard let rt = layer.nextDrawable() else {
throw RendererError.drawFailure("Failed to get next drawable render target") throw RendererError.drawFailure("Failed to get next drawable render target")
} }
self._rt = rt
passDescription.colorAttachments[0].texture = rt.texture passDescription.colorAttachments[0].texture = self._rt.texture
passDescription.depthAttachment.texture = self.depthTextures[self.frame] passDescription.depthAttachment.texture = self.depthTextures[self.currentFrame]
guard let commandBuf: MTLCommandBuffer = queue.makeCommandBuffer() else { guard let commandBuf: MTLCommandBuffer = queue.makeCommandBuffer() else {
throw RendererError.drawFailure("Failed to make command buffer from queue") throw RendererError.drawFailure("Failed to make command buffer from queue")
} }
commandBuf.addCompletedHandler { _ in self._commandBuf = commandBuf
self._commandBuf.addCompletedHandler { _ in
self.inFlightSemaphore.signal() self.inFlightSemaphore.signal()
} }
guard let encoder = commandBuf.makeRenderCommandEncoder(descriptor: passDescription) else { guard let encoder = self._commandBuf.makeRenderCommandEncoder(descriptor: passDescription) else {
throw RendererError.drawFailure("Failed to make render encoder from command buffer") throw RendererError.drawFailure("Failed to make render encoder from command buffer")
} }
self._encoder = encoder
encoder.setCullMode(.back) self._encoder.setCullMode(.back)
encoder.setFrontFacing(.counterClockwise) // OpenGL default self._encoder.setFrontFacing(.counterClockwise) // OpenGL default
encoder.setViewport(viewport) self._encoder.setViewport(Self.makeViewport(rect: self.frame))
encoder.setRenderPipelineState(pso) self._encoder.setRenderPipelineState(pso)
encoder.setDepthStencilState(depthStencilState) self._encoder.setDepthStencilState(depthStencilState)
encoder.setFragmentTexture(cubeTexture ?? defaultTexture, index: 0) self._encoder.setFragmentTexture(cubeTexture ?? defaultTexture, index: 0)
encoder.setVertexBuffer(vtxBuffer, }
func batch(instances: [Instance], camera: Camera) {
assert(instances.count < 52)
var uniforms = ShaderUniforms(projView: camera.viewProjection)
let instances = instances.map { (instance: Instance) -> ShaderInstance in
ShaderInstance(
model:
.translate(instance.position) *
matrix_float4x4(instance.rotation) *
.scale(instance.scale),
color: .init(
UInt8(instance.color.x * 0xFF),
UInt8(instance.color.y * 0xFF),
UInt8(instance.color.z * 0xFF),
UInt8(instance.color.w * 0xFF)))
}
self._encoder.setVertexBuffer(vtxBuffer,
offset: 0, offset: 0,
index: ShaderInputIdx.vertices.rawValue) index: ShaderInputIdx.vertices.rawValue)
// Ideal as long as our uniforms total 4 KB or less // Ideal as long as our uniforms total 4 KB or less
encoder.setVertexBytes(instances, self._encoder.setVertexBytes(instances,
length: instances.count * MemoryLayout<ShaderInstance>.stride, length: instances.count * MemoryLayout<ShaderInstance>.stride,
index: ShaderInputIdx.instance.rawValue) index: ShaderInputIdx.instance.rawValue)
encoder.setVertexBytes(&uniforms, self._encoder.setVertexBytes(&uniforms,
length: MemoryLayout<ShaderUniforms>.stride, length: MemoryLayout<ShaderUniforms>.stride,
index: ShaderInputIdx.uniforms.rawValue) index: ShaderInputIdx.uniforms.rawValue)
encoder.drawIndexedPrimitives( self._encoder.drawIndexedPrimitives(
type: .triangle, type: .triangle,
indexCount: cubeIndices.count, indexCount: cubeIndices.count,
indexType: .uint16, indexType: .uint16,
indexBuffer: idxBuffer, indexBuffer: idxBuffer,
indexBufferOffset: 0, indexBufferOffset: 0,
instanceCount: instances.count) instanceCount: instances.count)
}
encoder.endEncoding() func endFrame() {
commandBuf.present(rt) self._encoder.endEncoding()
commandBuf.commit() self._commandBuf.present(self._rt)
self._commandBuf.commit()
self.frame &+= 1 self._rt = nil
if self.frame == numFramesInFlight { self._encoder = nil
self.frame = 0 self._commandBuf = nil
self.currentFrame &+= 1
if self.currentFrame == numFramesInFlight {
self.currentFrame = 0
} }
} }
} }

View File

@ -1,6 +1,10 @@
import Darwin import Darwin
var rect = Rect(origin: .init(0, 0), size: .init(32, 32))
rect.origin += Point(10, 10)
let app = Application( let app = Application(
delegate: Game(),
configuration: ApplicationConfiguration( configuration: ApplicationConfiguration(
width: 1280, width: 1280,
height: 720, height: 720,

View File

@ -22,7 +22,7 @@ vertex FragmentInput vertexMain(
FragmentInput out; FragmentInput out;
out.position = ndc; out.position = ndc;
out.color = half4(i[instanceID].color); out.color = half4(i[instanceID].color) / 255.0;
out.normal = vtx[vertexID].normal; out.normal = vtx[vertexID].normal;
out.texCoord = vtx[vertexID].texCoord; out.texCoord = vtx[vertexID].texCoord;
return out; return out;

View File

@ -24,7 +24,7 @@ typedef struct {
typedef struct { typedef struct {
matrix_float4x4 model; matrix_float4x4 model;
vector_float4 color; vector_uchar4 color;
} ShaderInstance; } ShaderInstance;
typedef struct { typedef struct {