mirror of
https://github.com/GayPizzaSpecifications/voxelotl-engine.git
synced 2025-08-03 13:11:33 +00:00
fix memory leaks + minor refactor
This commit is contained in:
@ -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
|
||||||
|
@ -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 {
|
||||||
|
@ -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,
|
||||||
@ -267,6 +268,7 @@ public class Renderer {
|
|||||||
|
|
||||||
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 {
|
||||||
|
@ -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)))
|
||||||
|
Reference in New Issue
Block a user