mirror of
https://github.com/GayPizzaSpecifications/voxelotl-engine.git
synced 2025-08-02 13:00:53 +00:00
break up gameplay stuff
This commit is contained in:
parent
5b97a02288
commit
dc88042a36
@ -4,16 +4,17 @@ import QuartzCore.CAMetalLayer
|
||||
|
||||
public class Application {
|
||||
private let cfg: ApplicationConfiguration
|
||||
private let del: GameDelegate
|
||||
|
||||
private var window: OpaquePointer? = nil
|
||||
private var view: SDL_MetalView? = nil
|
||||
private var renderer: Renderer? = nil
|
||||
private var lastCounter: UInt64 = 0
|
||||
private var fpsCalculator = FPSCalculator()
|
||||
private var time: Duration = .zero
|
||||
|
||||
|
||||
public init(configuration: ApplicationConfiguration) {
|
||||
public init(delegate: GameDelegate, configuration: ApplicationConfiguration) {
|
||||
self.cfg = configuration
|
||||
self.del = delegate
|
||||
}
|
||||
|
||||
private func initialize() -> ApplicationExecutionState {
|
||||
@ -48,7 +49,7 @@ public class Application {
|
||||
do {
|
||||
let layer = unsafeBitCast(SDL_Metal_GetLayer(view), to: CAMetalLayer.self)
|
||||
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) {
|
||||
printErr("Renderer init error: \(message)")
|
||||
return .exitFailure
|
||||
@ -103,8 +104,9 @@ public class Application {
|
||||
return .running
|
||||
|
||||
case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED:
|
||||
let backBufferSize = SIMD2(Int(event.window.data1), Int(event.window.data2))
|
||||
renderer!.resize(size: backBufferSize)
|
||||
let backBufferSize = Size(Int(event.window.data1), Int(event.window.data2))
|
||||
self.renderer!.resize(size: backBufferSize)
|
||||
self.del.resize(backBufferSize)
|
||||
return .running
|
||||
|
||||
default:
|
||||
@ -112,13 +114,17 @@ public class Application {
|
||||
}
|
||||
}
|
||||
|
||||
private func update(_ deltaTime: Double) -> ApplicationExecutionState {
|
||||
fpsCalculator.frame(deltaTime: deltaTime) { fps in
|
||||
print("FPS: \(fps)")
|
||||
}
|
||||
private func update() -> ApplicationExecutionState {
|
||||
let deltaTime = getDeltaTime()
|
||||
time += deltaTime
|
||||
let gameTime = GameTime(total: time, delta: deltaTime)
|
||||
|
||||
del.update(gameTime)
|
||||
|
||||
do {
|
||||
try renderer!.paint()
|
||||
try renderer!.beginFrame()
|
||||
del.draw(renderer!, gameTime)
|
||||
renderer!.endFrame()
|
||||
} catch RendererError.drawFailure(let message) {
|
||||
printErr("Renderer draw error: \(message)")
|
||||
return .exitFailure
|
||||
@ -130,11 +136,14 @@ public class Application {
|
||||
return .running
|
||||
}
|
||||
|
||||
private func getDeltaTime() -> Double {
|
||||
private func getDeltaTime() -> Duration {
|
||||
let counter = SDL_GetPerformanceCounter()
|
||||
let divisor = 1.0 / Double(SDL_GetPerformanceFrequency())
|
||||
defer { lastCounter = counter }
|
||||
return Double(counter &- lastCounter) * divisor
|
||||
defer {
|
||||
lastCounter = counter
|
||||
}
|
||||
let difference = Double(counter &- lastCounter)
|
||||
let divisor = Double(SDL_GetPerformanceFrequency())
|
||||
return Duration.seconds(difference / divisor)
|
||||
}
|
||||
|
||||
func run() -> Int32 {
|
||||
@ -149,9 +158,7 @@ public class Application {
|
||||
break quit
|
||||
}
|
||||
}
|
||||
|
||||
let deltaTime = getDeltaTime()
|
||||
res = update(deltaTime)
|
||||
res = update()
|
||||
}
|
||||
|
||||
return res == .exitSuccess ? 0 : 1
|
||||
|
@ -8,13 +8,19 @@ add_executable(Voxelotl MACOSX_BUNDLE
|
||||
|
||||
FloatExtensions.swift
|
||||
Matrix4x4.swift
|
||||
Rectangle.swift
|
||||
|
||||
NSImageLoader.swift
|
||||
Renderer.swift
|
||||
GameController.swift
|
||||
Camera.swift
|
||||
Player.swift
|
||||
FPSCalculator.swift
|
||||
GameDelegate.swift
|
||||
Application.swift
|
||||
|
||||
Game.swift
|
||||
|
||||
main.swift)
|
||||
|
||||
set_source_files_properties(
|
||||
|
@ -1,37 +1,113 @@
|
||||
import simd
|
||||
|
||||
struct Camera {
|
||||
private var position = SIMD3<Float>.zero
|
||||
private var rotation = SIMD2<Float>.zero
|
||||
public final class Camera {
|
||||
private struct Dirty: OptionSet {
|
||||
let rawValue: UInt8
|
||||
|
||||
var view: matrix_float4x4 {
|
||||
.rotate(yawPitch: rotation) * .translate(-position)
|
||||
static let projection = Self(rawValue: 1 << 0)
|
||||
static let view = Self(rawValue: 1 << 1)
|
||||
static let viewProj = Self(rawValue: 1 << 2)
|
||||
}
|
||||
|
||||
mutating func update(deltaTime: Float) {
|
||||
if let pad = GameController.current?.state {
|
||||
let turning = pad.rightStick.radialDeadzone(min: 0.1, max: 1)
|
||||
rotation += turning * deltaTime
|
||||
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)
|
||||
private var _position: SIMD3<Float>
|
||||
private var _rotation: simd_quatf
|
||||
private var _fieldOfView: Float
|
||||
private var _aspectRatio: Float
|
||||
private var _zNearFar: ClosedRange<Float>
|
||||
|
||||
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)
|
||||
position += .init(
|
||||
movement.x * rotc - movement.y * rots,
|
||||
0,
|
||||
movement.y * rotc + movement.x * rots
|
||||
) * deltaTime
|
||||
|
||||
if pad.pressed(.back) {
|
||||
position = .zero
|
||||
rotation = .zero
|
||||
public var position: SIMD3<Float> {
|
||||
get { self._position }
|
||||
set(position) {
|
||||
if self._position != position {
|
||||
self._position = position
|
||||
self._dirty.insert(.view)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
@ -1,18 +1,20 @@
|
||||
import Foundation
|
||||
|
||||
public struct FPSCalculator {
|
||||
private var _accumulator = 0.0
|
||||
private var _accumulator = Duration.zero
|
||||
private var _framesCount = 0
|
||||
|
||||
public mutating func frame(deltaTime: Double, result: (_ fps: Int) -> Void) {
|
||||
_framesCount += 1
|
||||
_accumulator += deltaTime
|
||||
public mutating func frame(deltaTime: Duration, result: (_ fps: Int) -> Void) {
|
||||
self._framesCount += 1
|
||||
self._accumulator += deltaTime
|
||||
|
||||
if _accumulator >= 1.0 {
|
||||
result(_framesCount)
|
||||
if self._accumulator >= Duration.seconds(1) {
|
||||
result(self._framesCount)
|
||||
|
||||
_framesCount = 0
|
||||
_accumulator = fmod(_accumulator, 1.0)
|
||||
self._framesCount = 0
|
||||
self._accumulator = .init(
|
||||
secondsComponent: 0,
|
||||
attosecondsComponent: self._accumulator.components.attoseconds)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
53
Sources/Voxelotl/Game.swift
Normal file
53
Sources/Voxelotl/Game.swift
Normal 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)
|
||||
}
|
||||
}
|
23
Sources/Voxelotl/GameDelegate.swift
Normal file
23
Sources/Voxelotl/GameDelegate.swift
Normal 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
|
||||
}
|
||||
}
|
@ -68,20 +68,20 @@ public extension simd_float4x4 {
|
||||
let x = 2 * invWidth, y = 2 * invHeight, z = invDepth
|
||||
|
||||
return .init(
|
||||
.init( x, 0, 0, 0),
|
||||
.init( 0, y, 0, 0),
|
||||
.init( 0, 0, z, 0),
|
||||
.init(tx, ty, tz, 1))
|
||||
.init( x, 0, 0, 0),
|
||||
.init( 0, y, 0, 0),
|
||||
.init( 0, 0, z, 0),
|
||||
.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 invClipRange = 1 / (near - far)
|
||||
let invClipRange = 1 / (zNear - zFar)
|
||||
|
||||
let y = 1 / tanHalfFovY
|
||||
let x = y / aspect
|
||||
let z = far * invClipRange
|
||||
let w = near * z // (far * near) * invClipRange
|
||||
let z = zFar * invClipRange
|
||||
let w = zNear * z
|
||||
return .init(
|
||||
.init(x, 0, 0, 0),
|
||||
.init(0, y, 0, 0),
|
||||
@ -89,3 +89,7 @@ public extension simd_float4x4 {
|
||||
.init(0, 0, w, 0))
|
||||
}
|
||||
}
|
||||
|
||||
extension simd_quatf {
|
||||
static var identity: Self { .init(real: 1, imag: .zero) }
|
||||
}
|
||||
|
36
Sources/Voxelotl/Player.swift
Normal file
36
Sources/Voxelotl/Player.swift
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
99
Sources/Voxelotl/Rectangle.swift
Normal file
99
Sources/Voxelotl/Rectangle.swift
Normal 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) }
|
||||
}
|
@ -43,11 +43,11 @@ fileprivate let cubeIndices: [UInt16] = [
|
||||
fileprivate let numFramesInFlight: Int = 3
|
||||
fileprivate let depthFormat: MTLPixelFormat = .depth16Unorm
|
||||
|
||||
class Renderer {
|
||||
public class Renderer {
|
||||
private var device: MTLDevice
|
||||
private var layer: CAMetalLayer
|
||||
private var viewport: MTLViewport
|
||||
private var aspectRatio: Float
|
||||
private var backBufferSize: Size<Int>
|
||||
private var _aspectRatio: Float
|
||||
private var queue: MTLCommandQueue
|
||||
private var lib: MTLLibrary
|
||||
private let passDescription = MTLRenderPassDescriptor()
|
||||
@ -55,12 +55,19 @@ class Renderer {
|
||||
private var depthStencilState: MTLDepthStencilState
|
||||
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 defaultTexture: MTLTexture
|
||||
private var cubeTexture: MTLTexture? = nil
|
||||
|
||||
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? {
|
||||
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
|
||||
|
||||
// Select best Metal device
|
||||
@ -89,8 +96,8 @@ class Renderer {
|
||||
}
|
||||
self.queue = queue
|
||||
|
||||
self.viewport = Self.makeViewport(size: size)
|
||||
self.aspectRatio = Float(size.x) / Float(size.y)
|
||||
self.backBufferSize = size
|
||||
self._aspectRatio = Float(self.backBufferSize.w) / Float(self.backBufferSize.w)
|
||||
|
||||
passDescription.colorAttachments[0].loadAction = .clear
|
||||
passDescription.colorAttachments[0].storeAction = .store
|
||||
@ -239,12 +246,12 @@ class Renderer {
|
||||
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? {
|
||||
let texDescriptor = MTLTextureDescriptor.texture2DDescriptor(
|
||||
pixelFormat: format,
|
||||
width: size.x,
|
||||
height: size.y,
|
||||
width: size.w,
|
||||
height: size.h,
|
||||
mipmapped: false)
|
||||
texDescriptor.depth = 1
|
||||
texDescriptor.sampleCount = 1
|
||||
@ -261,111 +268,110 @@ class Renderer {
|
||||
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(
|
||||
originX: 0.0, originY: 0.0,
|
||||
width: Double(size.x),
|
||||
height: Double(size.y),
|
||||
znear: 0, zfar: 1)
|
||||
originX: Double(rect.x),
|
||||
originY: Double(rect.y),
|
||||
width: Double(rect.w),
|
||||
height: Double(rect.h),
|
||||
znear: znear, zfar: zfar)
|
||||
}
|
||||
|
||||
func resize(size: SIMD2<Int>) {
|
||||
if Int(self.viewport.width) != size.x || Int(self.viewport.height) != size.y {
|
||||
func resize(size: Size<Int>) {
|
||||
if self.backBufferSize.w != size.w || self.backBufferSize.h != size.h {
|
||||
self.depthTextures = (0..<numFramesInFlight).map { _ in
|
||||
Self.createDepthTexture(device, size, format: depthFormat)!
|
||||
}
|
||||
}
|
||||
|
||||
self.aspectRatio = Float(size.x) / Float(size.y)
|
||||
self.viewport = Self.makeViewport(size: size)
|
||||
self.backBufferSize = size
|
||||
self._aspectRatio = Float(self.backBufferSize.w) / Float(self.backBufferSize.h)
|
||||
}
|
||||
|
||||
//FIXME: temp
|
||||
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)
|
||||
|
||||
func beginFrame() throws {
|
||||
// Lock the semaphore here if too many frames are "in flight"
|
||||
_ = inFlightSemaphore.wait(timeout: .distantFuture)
|
||||
|
||||
guard let rt = layer.nextDrawable() else {
|
||||
throw RendererError.drawFailure("Failed to get next drawable render target")
|
||||
}
|
||||
self._rt = rt
|
||||
|
||||
passDescription.colorAttachments[0].texture = rt.texture
|
||||
passDescription.depthAttachment.texture = self.depthTextures[self.frame]
|
||||
passDescription.colorAttachments[0].texture = self._rt.texture
|
||||
passDescription.depthAttachment.texture = self.depthTextures[self.currentFrame]
|
||||
|
||||
guard let commandBuf: MTLCommandBuffer = queue.makeCommandBuffer() else {
|
||||
throw RendererError.drawFailure("Failed to make command buffer from queue")
|
||||
}
|
||||
commandBuf.addCompletedHandler { _ in
|
||||
self._commandBuf = commandBuf
|
||||
self._commandBuf.addCompletedHandler { _ in
|
||||
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")
|
||||
}
|
||||
self._encoder = encoder
|
||||
|
||||
encoder.setCullMode(.back)
|
||||
encoder.setFrontFacing(.counterClockwise) // OpenGL default
|
||||
encoder.setViewport(viewport)
|
||||
encoder.setRenderPipelineState(pso)
|
||||
encoder.setDepthStencilState(depthStencilState)
|
||||
self._encoder.setCullMode(.back)
|
||||
self._encoder.setFrontFacing(.counterClockwise) // OpenGL default
|
||||
self._encoder.setViewport(Self.makeViewport(rect: self.frame))
|
||||
self._encoder.setRenderPipelineState(pso)
|
||||
self._encoder.setDepthStencilState(depthStencilState)
|
||||
|
||||
encoder.setFragmentTexture(cubeTexture ?? defaultTexture, index: 0)
|
||||
encoder.setVertexBuffer(vtxBuffer,
|
||||
self._encoder.setFragmentTexture(cubeTexture ?? defaultTexture, index: 0)
|
||||
}
|
||||
|
||||
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,
|
||||
index: ShaderInputIdx.vertices.rawValue)
|
||||
|
||||
// Ideal as long as our uniforms total 4 KB or less
|
||||
encoder.setVertexBytes(instances,
|
||||
self._encoder.setVertexBytes(instances,
|
||||
length: instances.count * MemoryLayout<ShaderInstance>.stride,
|
||||
index: ShaderInputIdx.instance.rawValue)
|
||||
encoder.setVertexBytes(&uniforms,
|
||||
self._encoder.setVertexBytes(&uniforms,
|
||||
length: MemoryLayout<ShaderUniforms>.stride,
|
||||
index: ShaderInputIdx.uniforms.rawValue)
|
||||
|
||||
encoder.drawIndexedPrimitives(
|
||||
self._encoder.drawIndexedPrimitives(
|
||||
type: .triangle,
|
||||
indexCount: cubeIndices.count,
|
||||
indexType: .uint16,
|
||||
indexBuffer: idxBuffer,
|
||||
indexBufferOffset: 0,
|
||||
instanceCount: instances.count)
|
||||
}
|
||||
|
||||
encoder.endEncoding()
|
||||
commandBuf.present(rt)
|
||||
commandBuf.commit()
|
||||
func endFrame() {
|
||||
self._encoder.endEncoding()
|
||||
self._commandBuf.present(self._rt)
|
||||
self._commandBuf.commit()
|
||||
|
||||
self.frame &+= 1
|
||||
if self.frame == numFramesInFlight {
|
||||
self.frame = 0
|
||||
self._rt = nil
|
||||
self._encoder = nil
|
||||
self._commandBuf = nil
|
||||
|
||||
self.currentFrame &+= 1
|
||||
if self.currentFrame == numFramesInFlight {
|
||||
self.currentFrame = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,10 @@
|
||||
import Darwin
|
||||
|
||||
var rect = Rect(origin: .init(0, 0), size: .init(32, 32))
|
||||
rect.origin += Point(10, 10)
|
||||
|
||||
let app = Application(
|
||||
delegate: Game(),
|
||||
configuration: ApplicationConfiguration(
|
||||
width: 1280,
|
||||
height: 720,
|
||||
|
@ -22,7 +22,7 @@ vertex FragmentInput vertexMain(
|
||||
|
||||
FragmentInput out;
|
||||
out.position = ndc;
|
||||
out.color = half4(i[instanceID].color);
|
||||
out.color = half4(i[instanceID].color) / 255.0;
|
||||
out.normal = vtx[vertexID].normal;
|
||||
out.texCoord = vtx[vertexID].texCoord;
|
||||
return out;
|
||||
|
@ -24,7 +24,7 @@ typedef struct {
|
||||
|
||||
typedef struct {
|
||||
matrix_float4x4 model;
|
||||
vector_float4 color;
|
||||
vector_uchar4 color;
|
||||
} ShaderInstance;
|
||||
|
||||
typedef struct {
|
||||
|
Loading…
Reference in New Issue
Block a user