break up gameplay stuff

This commit is contained in:
a dinosaur 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 {
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

View File

@ -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(

View File

@ -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()
}
}

View File

@ -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)
}
}
}

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

@ -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) }
}

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 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
}
}
}

View File

@ -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,

View File

@ -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;

View File

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