depth buffer implementation

This commit is contained in:
2024-08-07 16:36:23 +10:00
parent 3b33842260
commit fbff9b77fd
4 changed files with 125 additions and 33 deletions

View File

@ -36,26 +36,25 @@ public class Application {
return .exitFailure return .exitFailure
} }
// Create Metal renderer
view = SDL_Metal_CreateView(window)
do {
let layer = unsafeBitCast(SDL_Metal_GetLayer(view), to: CAMetalLayer.self)
layer.displaySyncEnabled = cfg.vsyncMode == .off ? false : true
self.renderer = try Renderer(layer: layer)
} catch RendererError.initFailure(let message) {
printErr("Renderer init error: \(message)")
return .exitFailure
} catch {
printErr("Renderer init error: unexpected error")
}
// Get window metrics // Get window metrics
var backBufferWidth: Int32 = 0, backBufferHeight: Int32 = 0 var backBufferWidth: Int32 = 0, backBufferHeight: Int32 = 0
guard SDL_GetWindowSizeInPixels(window, &backBufferWidth, &backBufferHeight) >= 0 else { guard SDL_GetWindowSizeInPixels(window, &backBufferWidth, &backBufferHeight) >= 0 else {
printErr("SDL_GetWindowSizeInPixels() error: \(String(cString: SDL_GetError()))") printErr("SDL_GetWindowSizeInPixels() error: \(String(cString: SDL_GetError()))")
return .exitFailure return .exitFailure
} }
renderer!.resize(size: SIMD2<Int>(Int(backBufferWidth), Int(backBufferHeight)))
// Create Metal renderer
view = SDL_Metal_CreateView(window)
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)))
} catch RendererError.initFailure(let message) {
printErr("Renderer init error: \(message)")
return .exitFailure
} catch {
printErr("Renderer init error: unexpected error")
}
lastCounter = SDL_GetPerformanceCounter() lastCounter = SDL_GetPerformanceCounter()
return .running return .running

View File

@ -1,4 +1,7 @@
public extension FloatingPoint { public extension FloatingPoint {
@inline(__always) var degrees: Self { self * (180 / Self.pi) } @inline(__always) var degrees: Self { self * (180 / Self.pi) }
@inline(__always) var radians: Self { self * (Self.pi / 180) } @inline(__always) var radians: Self { self * (Self.pi / 180) }
@inline(__always) func lerp(_ a: Self, _ b: Self) -> Self { b * self + a * (1 - self) }
@inline(__always) func mlerp(_ a: Self, _ b: Self) -> Self { a + (b - a) * self }
} }

View File

@ -43,18 +43,36 @@ public extension simd_float4x4 {
.init(0, 0, 0, 1)) .init(0, 0, 0, 1))
} }
static func perspective(verticalFov: T, aspect: T, near: T, far: T) -> Self { static func orthographic(left: T, right: T, bottom: T, top: T, near: T, far: T) -> Self {
let h = 1 / tan(verticalFov * T(0.5)) let
let w = h / aspect invWidth = 1 / (right - left),
invHeight = 1 / (top - bottom),
let invClipRange = 1 / (far - near) invDepth = 1 / (far - near)
let z = -(far + near) * invClipRange let
let z2 = -(2 * far * near) * invClipRange tx = -(right + left) * invWidth,
ty = -(top + bottom) * invHeight,
tz = -near * invDepth
let x = 2 * invWidth, y = 2 * invHeight, z = invDepth
return .init( return .init(
.init(w, 0, 0, 0), .init( x, 0, 0, 0),
.init(0, h, 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 {
let tanHalfFovY = tan(fovY * T(0.5))
let invClipRange = 1 / (near - far)
let y = 1 / tanHalfFovY
let x = y / aspect
let z = far * invClipRange
let w = near * z // (far * near) * invClipRange
return .init(
.init(x, 0, 0, 0),
.init(0, y, 0, 0),
.init(0, 0, z, -1), .init(0, 0, z, -1),
.init(0, 0, z2, 0)) .init(0, 0, w, 0))
} }
} }

View File

@ -41,13 +41,18 @@ fileprivate let cubeIndices: [UInt16] = [
] ]
class Renderer { class Renderer {
private let depthFormat: MTLPixelFormat = .depth16Unorm
private var device: MTLDevice private var device: MTLDevice
private var layer: CAMetalLayer private var layer: CAMetalLayer
private var viewport = MTLViewport() private var viewport = MTLViewport()
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()
private var pso: MTLRenderPipelineState private var pso: MTLRenderPipelineState
private var depthStencilState: MTLDepthStencilState
private var depthStencilTexture: MTLTexture
private var vtxBuffer: MTLBuffer, idxBuffer: MTLBuffer private var vtxBuffer: MTLBuffer, idxBuffer: MTLBuffer
private var defaultTexture: MTLTexture private var defaultTexture: MTLTexture
@ -62,7 +67,7 @@ class Renderer {
}) })
} }
init(layer metalLayer: CAMetalLayer) throws { init(layer metalLayer: CAMetalLayer, size: SIMD2<Int>) throws {
self.layer = metalLayer self.layer = metalLayer
// Select best Metal device // Select best Metal device
@ -79,9 +84,36 @@ class Renderer {
throw RendererError.initFailure("Failed to create command queue") throw RendererError.initFailure("Failed to create command queue")
} }
self.queue = queue self.queue = queue
passDescription.colorAttachments[0].loadAction = MTLLoadAction.clear
passDescription.colorAttachments[0].storeAction = MTLStoreAction.store self.viewport = MTLViewport(
originX: 0.0,
originY: 0.0,
width: Double(size.x),
height: Double(size.y),
znear: 1.0,
zfar: -1.0)
self.aspectRatio = Float(size.x) / Float(size.y)
passDescription.colorAttachments[0].loadAction = .clear
passDescription.colorAttachments[0].storeAction = .store
passDescription.colorAttachments[0].clearColor = MTLClearColorMake(0.1, 0.1, 0.1, 1.0) passDescription.colorAttachments[0].clearColor = MTLClearColorMake(0.1, 0.1, 0.1, 1.0)
passDescription.depthAttachment.loadAction = .clear
passDescription.depthAttachment.storeAction = .dontCare
passDescription.depthAttachment.clearDepth = 1.0
guard let depthStencilTexture = Self.createDepthTexture(device, size, format: depthFormat) else {
throw RendererError.initFailure("Failed to create depth buffer")
}
self.depthStencilTexture = depthStencilTexture
passDescription.depthAttachment.texture = self.depthStencilTexture
let stencilDepthDescription = MTLDepthStencilDescriptor()
stencilDepthDescription.depthCompareFunction = .less // OpenGL default
stencilDepthDescription.isDepthWriteEnabled = true
guard let depthStencilState = device.makeDepthStencilState(descriptor: stencilDepthDescription) else {
throw RendererError.initFailure("Failed to create depth stencil state")
}
self.depthStencilState = depthStencilState
// Create shader library & grab functions // Create shader library & grab functions
do { do {
@ -97,6 +129,7 @@ class Renderer {
pipeDescription.vertexFunction = vertexProgram pipeDescription.vertexFunction = vertexProgram
pipeDescription.fragmentFunction = fragmentProgram pipeDescription.fragmentFunction = fragmentProgram
pipeDescription.colorAttachments[0].pixelFormat = layer.pixelFormat pipeDescription.colorAttachments[0].pixelFormat = layer.pixelFormat
pipeDescription.depthAttachmentPixelFormat = depthFormat
do { do {
self.pso = try device.makeRenderPipelineState(descriptor: pipeDescription) self.pso = try device.makeRenderPipelineState(descriptor: pipeDescription)
} catch { } catch {
@ -207,7 +240,37 @@ class Renderer {
return newTexture return newTexture
} }
private static func createDepthTexture(_ device: MTLDevice, _ size: SIMD2<Int>, format: MTLPixelFormat
) -> MTLTexture? {
let texDescriptor = MTLTextureDescriptor.texture2DDescriptor(
pixelFormat: format,
width: size.x,
height: size.y,
mipmapped: false)
texDescriptor.depth = 1
texDescriptor.sampleCount = 1
texDescriptor.usage = [ .renderTarget, .shaderRead ]
#if !NDEBUG
texDescriptor.storageMode = .private
#else
texDescriptor.storageMode = .memoryless
#endif
guard let depthStencilTexture = device.makeTexture(descriptor: texDescriptor) else { return nil }
depthStencilTexture.label = "Depth buffer"
return depthStencilTexture
}
func resize(size: SIMD2<Int>) { func resize(size: SIMD2<Int>) {
if Int(self.viewport.width) != size.x || Int(self.viewport.height) != size.y {
if let depthStencilTexture = Self.createDepthTexture(device, size, format: depthFormat) {
self.depthStencilTexture = depthStencilTexture
passDescription.depthAttachment.texture = self.depthStencilTexture
}
}
self.aspectRatio = Float(size.x) / Float(size.y)
self.viewport = MTLViewport( self.viewport = MTLViewport(
originX: 0.0, originX: 0.0,
originY: 0.0, originY: 0.0,
@ -220,14 +283,21 @@ class Renderer {
var time: Float = 0 //FIXME: temp var time: Float = 0 //FIXME: temp
func paint() throws { func paint() throws {
#if true
let projection = matrix_float4x4.perspective( let projection = matrix_float4x4.perspective(
verticalFov: Float(90.0).radians, verticalFov: Float(90.0).radians,
aspect: Float(self.viewport.width / self.viewport.height), aspect: aspectRatio,
near: 0.1, near: 0.003,
far: 10) far: 4)
#else
let projection = matrix_float4x4.orthographic(
left: -aspectRatio, right: aspectRatio,
bottom: -1, top: 1,
near: 0, far: -4)
#endif
let view = matrix_float4x4.identity let view = matrix_float4x4.identity
let model: matrix_float4x4 = let model: matrix_float4x4 =
.translate(.init(0, sin(time * 0.5) * 0.5, -2)) * .translate(.init(0, sin(time * 0.5) * 0.75, -2)) *
.scale(0.5) * .scale(0.5) *
.rotate(y: time) .rotate(y: time)
@ -248,9 +318,11 @@ class Renderer {
throw RendererError.drawFailure("Failed to make render encoder from command buffer") throw RendererError.drawFailure("Failed to make render encoder from command buffer")
} }
encoder.setCullMode(.back)
encoder.setFrontFacing(.counterClockwise) // OpenGL default
encoder.setViewport(viewport) encoder.setViewport(viewport)
encoder.setCullMode(MTLCullMode.none)
encoder.setRenderPipelineState(pso) encoder.setRenderPipelineState(pso)
encoder.setDepthStencilState(depthStencilState)
encoder.setFragmentTexture(cubeTexture ?? defaultTexture, index: 0) encoder.setFragmentTexture(cubeTexture ?? defaultTexture, index: 0)
encoder.setVertexBuffer(vtxBuffer, encoder.setVertexBuffer(vtxBuffer,