fix memory leaks + minor refactor

This commit is contained in:
2024-08-13 21:04:16 +10:00
parent dc88042a36
commit 60ced3691d
4 changed files with 123 additions and 127 deletions

View File

@ -31,15 +31,15 @@ public class Application {
if cfg.flags.contains(.highDPI) { if cfg.flags.contains(.highDPI) {
windowFlags |= SDL_WindowFlags(SDL_WINDOW_HIGH_PIXEL_DENSITY) windowFlags |= SDL_WindowFlags(SDL_WINDOW_HIGH_PIXEL_DENSITY)
} }
window = SDL_CreateWindow(cfg.title, cfg.width, cfg.height, windowFlags) window = SDL_CreateWindow(cfg.title, cfg.frame.w, cfg.frame.h, windowFlags)
guard window != nil else { guard window != nil else {
printErr("SDL_CreateWindow() error: \(String(cString: SDL_GetError()))") printErr("SDL_CreateWindow() error: \(String(cString: SDL_GetError()))")
return .exitFailure return .exitFailure
} }
// Get window metrics // Get window metrics
var backBufferWidth: Int32 = 0, backBufferHeight: Int32 = 0 var backBuffer = Size<Int32>.zero
guard SDL_GetWindowSizeInPixels(window, &backBufferWidth, &backBufferHeight) >= 0 else { guard SDL_GetWindowSizeInPixels(window, &backBuffer.w, &backBuffer.h) >= 0 else {
printErr("SDL_GetWindowSizeInPixels() error: \(String(cString: SDL_GetError()))") printErr("SDL_GetWindowSizeInPixels() error: \(String(cString: SDL_GetError()))")
return .exitFailure return .exitFailure
} }
@ -49,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: .init(Int(backBufferWidth), Int(backBufferHeight))) self.renderer = try Renderer(layer: layer, size: Size<Int>(backBuffer))
} catch RendererError.initFailure(let message) { } catch RendererError.initFailure(let message) {
printErr("Renderer init error: \(message)") printErr("Renderer init error: \(message)")
return .exitFailure return .exitFailure
@ -122,9 +122,9 @@ public class Application {
del.update(gameTime) del.update(gameTime)
do { do {
try renderer!.beginFrame() try renderer!.newFrame {
del.draw(renderer!, gameTime) del.draw($0, 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
@ -182,15 +182,13 @@ public struct ApplicationConfiguration {
case adaptive case adaptive
} }
let width: Int32 let frame: Size<Int32>
let height: Int32
let title: String let title: String
let flags: Flags let flags: Flags
let vsyncMode: VSyncMode let vsyncMode: VSyncMode
public init(width: Int32, height: Int32, title: String, flags: Flags, vsyncMode: VSyncMode) { public init(frame: Size<Int32>, title: String, flags: Flags, vsyncMode: VSyncMode) {
self.width = width self.frame = frame
self.height = height
self.title = title self.title = title
self.flags = flags self.flags = flags
self.vsyncMode = vsyncMode self.vsyncMode = vsyncMode

View File

@ -42,6 +42,10 @@ public struct Size<T: AdditiveArithmetic>: Equatable {
extension Size where T: BinaryInteger { extension Size where T: BinaryInteger {
static var one: Self { .init(T(1), T(1)) } static var one: Self { .init(T(1), T(1)) }
init<O>(_ other: Size<O>) where O: BinaryInteger {
self.init(T(other.w), T(other.h))
}
} }
struct Rect<T: AdditiveArithmetic>: Equatable { struct Rect<T: AdditiveArithmetic>: Equatable {

View File

@ -55,9 +55,7 @@ public 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! = nil
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
@ -201,6 +199,7 @@ public class Renderer {
} }
static func loadTexture(_ device: MTLDevice, _ queue: MTLCommandQueue, image2D image: Image2D) throws -> MTLTexture { static func loadTexture(_ device: MTLDevice, _ queue: MTLCommandQueue, image2D image: Image2D) throws -> MTLTexture {
try autoreleasepool {
let texDesc = MTLTextureDescriptor() let texDesc = MTLTextureDescriptor()
texDesc.width = image.width texDesc.width = image.width
texDesc.height = image.height texDesc.height = image.height
@ -245,9 +244,11 @@ public class Renderer {
return newTexture return newTexture
} }
}
private static func createDepthTexture(_ device: MTLDevice, _ size: Size<Int>, format: MTLPixelFormat private static func createDepthTexture(_ device: MTLDevice, _ size: Size<Int>, format: MTLPixelFormat
) -> MTLTexture? { ) -> MTLTexture? {
autoreleasepool {
let texDescriptor = MTLTextureDescriptor.texture2DDescriptor( let texDescriptor = MTLTextureDescriptor.texture2DDescriptor(
pixelFormat: format, pixelFormat: format,
width: size.w, width: size.w,
@ -256,17 +257,18 @@ public class Renderer {
texDescriptor.depth = 1 texDescriptor.depth = 1
texDescriptor.sampleCount = 1 texDescriptor.sampleCount = 1
texDescriptor.usage = [ .renderTarget, .shaderRead ] texDescriptor.usage = [ .renderTarget, .shaderRead ]
#if !NDEBUG #if !NDEBUG
texDescriptor.storageMode = .private texDescriptor.storageMode = .private
#else #else
texDescriptor.storageMode = .memoryless texDescriptor.storageMode = .memoryless
#endif #endif
guard let depthStencilTexture = device.makeTexture(descriptor: texDescriptor) else { return nil } guard let depthStencilTexture = device.makeTexture(descriptor: texDescriptor) else { return nil }
depthStencilTexture.label = "Depth buffer" depthStencilTexture.label = "Depth buffer"
return depthStencilTexture return depthStencilTexture
} }
}
static func makeViewport(rect: Rect<Int>, znear: Double = 0.0, zfar: Double = 1.0) -> MTLViewport { static func makeViewport(rect: Rect<Int>, znear: Double = 0.0, zfar: Double = 1.0) -> MTLViewport {
MTLViewport( MTLViewport(
@ -288,41 +290,56 @@ public class Renderer {
self._aspectRatio = Float(self.backBufferSize.w) / Float(self.backBufferSize.h) self._aspectRatio = Float(self.backBufferSize.w) / Float(self.backBufferSize.h)
} }
func beginFrame() throws { func newFrame(_ frameFunc: (Renderer) -> Void) throws {
// Lock the semaphore here if too many frames are "in flight" try autoreleasepool {
_ = 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 = self._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"
_ = inFlightSemaphore.wait(timeout: .distantFuture)
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")
} }
self._commandBuf = commandBuf commandBuf.addCompletedHandler { _ in
self._commandBuf.addCompletedHandler { _ in
self.inFlightSemaphore.signal() self.inFlightSemaphore.signal()
} }
guard let encoder = self._commandBuf.makeRenderCommandEncoder(descriptor: passDescription) else { guard let encoder = 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")
} }
encoder.setCullMode(.back)
encoder.setFrontFacing(.counterClockwise) // OpenGL default
encoder.setViewport(Self.makeViewport(rect: self.frame))
encoder.setRenderPipelineState(pso)
encoder.setDepthStencilState(depthStencilState)
encoder.setFragmentTexture(cubeTexture ?? defaultTexture, index: 0)
encoder.setVertexBuffer(vtxBuffer,
offset: 0,
index: ShaderInputIdx.vertices.rawValue)
self._encoder = encoder self._encoder = encoder
frameFunc(self)
self._encoder = nil
self._encoder.setCullMode(.back) encoder.endEncoding()
self._encoder.setFrontFacing(.counterClockwise) // OpenGL default commandBuf.present(rt)
self._encoder.setViewport(Self.makeViewport(rect: self.frame)) commandBuf.commit()
self._encoder.setRenderPipelineState(pso)
self._encoder.setDepthStencilState(depthStencilState)
self._encoder.setFragmentTexture(cubeTexture ?? defaultTexture, index: 0) self.currentFrame &+= 1
if self.currentFrame == numFramesInFlight {
self.currentFrame = 0
}
}
} }
func batch(instances: [Instance], camera: Camera) { func batch(instances: [Instance], camera: Camera) {
assert(self._encoder != nil, "batch can't be called outside of a frame being rendered")
assert(instances.count < 52) assert(instances.count < 52)
var uniforms = ShaderUniforms(projView: camera.viewProjection) var uniforms = ShaderUniforms(projView: camera.viewProjection)
@ -339,10 +356,6 @@ public class Renderer {
UInt8(instance.color.w * 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 // Ideal as long as our uniforms total 4 KB or less
self._encoder.setVertexBytes(instances, self._encoder.setVertexBytes(instances,
length: instances.count * MemoryLayout<ShaderInstance>.stride, length: instances.count * MemoryLayout<ShaderInstance>.stride,
@ -359,21 +372,6 @@ public class Renderer {
indexBufferOffset: 0, indexBufferOffset: 0,
instanceCount: instances.count) instanceCount: instances.count)
} }
func endFrame() {
self._encoder.endEncoding()
self._commandBuf.present(self._rt)
self._commandBuf.commit()
self._rt = nil
self._encoder = nil
self._commandBuf = nil
self.currentFrame &+= 1
if self.currentFrame == numFramesInFlight {
self.currentFrame = 0
}
}
} }
enum RendererError: Error { enum RendererError: Error {

View File

@ -1,13 +1,9 @@
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(), delegate: Game(),
configuration: ApplicationConfiguration( configuration: ApplicationConfiguration(
width: 1280, frame: Size(1280, 720),
height: 720,
title: "Voxelotl Demo", title: "Voxelotl Demo",
flags: [ .resizable, .highDPI ], flags: [ .resizable, .highDPI ],
vsyncMode: .on(interval: 1))) vsyncMode: .on(interval: 1)))