mirror of
https://github.com/ScrelliCopter/NeHe-SDL_GPU.git
synced 2025-06-19 21:49:17 +10:00
swift: Implement lessons 1-7
This commit is contained in:
41
Package.swift
Normal file
41
Package.swift
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
// swift-tools-version: 6.0
|
||||||
|
|
||||||
|
import PackageDescription
|
||||||
|
|
||||||
|
let package = Package(
|
||||||
|
name: "NeHe-SDL_GPU",
|
||||||
|
products: [
|
||||||
|
.executable(name: "Lesson1", targets: [ "Lesson1" ]),
|
||||||
|
.executable(name: "Lesson2", targets: [ "Lesson2" ]),
|
||||||
|
.executable(name: "Lesson3", targets: [ "Lesson3" ]),
|
||||||
|
.executable(name: "Lesson4", targets: [ "Lesson4" ]),
|
||||||
|
.executable(name: "Lesson5", targets: [ "Lesson5" ]),
|
||||||
|
.executable(name: "Lesson6", targets: [ "Lesson6" ]),
|
||||||
|
.executable(name: "Lesson7", targets: [ "Lesson7" ]),
|
||||||
|
],
|
||||||
|
dependencies: [
|
||||||
|
.package(url: "https://github.com/GayPizzaSpecifications/SDL3Swift.git", branch: "main"),
|
||||||
|
],
|
||||||
|
targets: [
|
||||||
|
.target(
|
||||||
|
name: "NeHe",
|
||||||
|
dependencies: [ .product(name: "SDLSwift", package: "SDL3Swift") ],
|
||||||
|
path: "src/swift/NeHe"),
|
||||||
|
.executableTarget(name: "Lesson1", dependencies: [ "NeHe" ], path: "src/swift/Lesson1"),
|
||||||
|
.executableTarget(name: "Lesson2", dependencies: [ "NeHe" ], path: "src/swift/Lesson2", resources: [
|
||||||
|
.process("../../../data/shaders/lesson2.metallib") ]),
|
||||||
|
.executableTarget(name: "Lesson3", dependencies: [ "NeHe" ], path: "src/swift/Lesson3", resources: [
|
||||||
|
.process("../../../data/shaders/lesson3.metallib") ]),
|
||||||
|
.executableTarget(name: "Lesson4", dependencies: [ "NeHe" ], path: "src/swift/Lesson4", resources: [
|
||||||
|
.process("../../../data/shaders/lesson3.metallib") ]),
|
||||||
|
.executableTarget(name: "Lesson5", dependencies: [ "NeHe" ], path: "src/swift/Lesson5", resources: [
|
||||||
|
.process("../../../data/shaders/lesson3.metallib") ]),
|
||||||
|
.executableTarget(name: "Lesson6", dependencies: [ "NeHe" ], path: "src/swift/Lesson6", resources: [
|
||||||
|
.process("../../../data/shaders/lesson6.metallib"),
|
||||||
|
.process("../../../data/NeHe.bmp") ]),
|
||||||
|
.executableTarget(name: "Lesson7", dependencies: [ "NeHe" ], path: "src/swift/Lesson7", resources: [
|
||||||
|
.process("../../../data/shaders/lesson6.metallib"),
|
||||||
|
.process("../../../data/shaders/lesson7.metallib"),
|
||||||
|
.process("../../../data/Crate.bmp") ])
|
||||||
|
],
|
||||||
|
)
|
||||||
32
src/swift/Lesson1/lesson1.swift
Normal file
32
src/swift/Lesson1/lesson1.swift
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: (C) 2025 a dinosaur
|
||||||
|
* SPDX-License-Identifier: Zlib
|
||||||
|
*/
|
||||||
|
|
||||||
|
import SDLSwift
|
||||||
|
import NeHe
|
||||||
|
|
||||||
|
struct Lesson1: AppDelegate
|
||||||
|
{
|
||||||
|
func draw(ctx: inout NeHeContext, cmd: OpaquePointer,
|
||||||
|
swapchain: OpaquePointer, swapchainSize: Size<UInt32>) throws(NeHeError)
|
||||||
|
{
|
||||||
|
var colorInfo = SDL_GPUColorTargetInfo()
|
||||||
|
colorInfo.texture = swapchain
|
||||||
|
colorInfo.clear_color = SDL_FColor(r: 0.0, g: 0.0, b: 0.0, a: 0.5)
|
||||||
|
colorInfo.load_op = SDL_GPU_LOADOP_CLEAR
|
||||||
|
colorInfo.store_op = SDL_GPU_STOREOP_STORE
|
||||||
|
|
||||||
|
let pass = SDL_BeginGPURenderPass(cmd, &colorInfo, 1, nil);
|
||||||
|
SDL_EndGPURenderPass(pass);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@main struct Program: AppRunner
|
||||||
|
{
|
||||||
|
typealias Delegate = Lesson1
|
||||||
|
static let config = AppConfig(
|
||||||
|
title: "NeHe's OpenGL Framework",
|
||||||
|
width: 640,
|
||||||
|
height: 480)
|
||||||
|
}
|
||||||
172
src/swift/Lesson2/lesson2.swift
Normal file
172
src/swift/Lesson2/lesson2.swift
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: (C) 2025 a dinosaur
|
||||||
|
* SPDX-License-Identifier: Zlib
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import SDLSwift
|
||||||
|
import NeHe
|
||||||
|
import simd
|
||||||
|
|
||||||
|
struct Lesson2: AppDelegate
|
||||||
|
{
|
||||||
|
static let vertices =
|
||||||
|
[
|
||||||
|
// Triangle
|
||||||
|
SIMD3<Float>( 0.0, 1.0, 0.0), // Top
|
||||||
|
SIMD3<Float>(-1.0, -1.0, 0.0), // Bottom left
|
||||||
|
SIMD3<Float>( 1.0, -1.0, 0.0), // Bottom right
|
||||||
|
// Quad
|
||||||
|
SIMD3<Float>( -1.0, 1.0, 0.0), // Top left
|
||||||
|
SIMD3<Float>( 1.0, 1.0, 0.0), // Top right
|
||||||
|
SIMD3<Float>( 1.0, -1.0, 0.0), // Bottom right
|
||||||
|
SIMD3<Float>( -1.0, -1.0, 0.0), // Bottom left
|
||||||
|
]
|
||||||
|
|
||||||
|
static let indices: [Int16] =
|
||||||
|
[
|
||||||
|
// Triangle
|
||||||
|
0, 1, 2,
|
||||||
|
// Quad
|
||||||
|
3, 4, 5, 5, 6, 3,
|
||||||
|
]
|
||||||
|
|
||||||
|
var pso: OpaquePointer? = nil
|
||||||
|
var vtxBuffer: OpaquePointer? = nil
|
||||||
|
var idxBuffer: OpaquePointer? = nil
|
||||||
|
var projection: matrix_float4x4 = .init(1.0)
|
||||||
|
|
||||||
|
mutating func `init`(ctx: inout NeHeContext) throws(NeHeError)
|
||||||
|
{
|
||||||
|
let (vertexShader, fragmentShader) = try ctx.loadShaders(name: "lesson2", vertexUniforms: 1)
|
||||||
|
defer
|
||||||
|
{
|
||||||
|
SDL_ReleaseGPUShader(ctx.device, fragmentShader)
|
||||||
|
SDL_ReleaseGPUShader(ctx.device, vertexShader)
|
||||||
|
}
|
||||||
|
|
||||||
|
let vertexDescriptions: [SDL_GPUVertexBufferDescription] =
|
||||||
|
[
|
||||||
|
SDL_GPUVertexBufferDescription(
|
||||||
|
slot: 0,
|
||||||
|
pitch: UInt32(MemoryLayout<SIMD3<Float>>.stride),
|
||||||
|
input_rate: SDL_GPU_VERTEXINPUTRATE_VERTEX,
|
||||||
|
instance_step_rate: 0),
|
||||||
|
]
|
||||||
|
let vertexAttributes: [SDL_GPUVertexAttribute] =
|
||||||
|
[
|
||||||
|
SDL_GPUVertexAttribute(
|
||||||
|
location: 0,
|
||||||
|
buffer_slot: 0,
|
||||||
|
format: SDL_GPU_VERTEXELEMENTFORMAT_FLOAT3,
|
||||||
|
offset: 0),
|
||||||
|
]
|
||||||
|
let colourTargets: [SDL_GPUColorTargetDescription] =
|
||||||
|
[
|
||||||
|
SDL_GPUColorTargetDescription(
|
||||||
|
format: SDL_GetGPUSwapchainTextureFormat(ctx.device, ctx.window),
|
||||||
|
blend_state: SDL_GPUColorTargetBlendState())
|
||||||
|
]
|
||||||
|
var rasterizerDesc = SDL_GPURasterizerState()
|
||||||
|
rasterizerDesc.fill_mode = SDL_GPU_FILLMODE_FILL
|
||||||
|
rasterizerDesc.cull_mode = SDL_GPU_CULLMODE_NONE
|
||||||
|
rasterizerDesc.front_face = SDL_GPU_FRONTFACE_COUNTER_CLOCKWISE
|
||||||
|
var targetInfo = SDL_GPUGraphicsPipelineTargetInfo()
|
||||||
|
targetInfo.color_target_descriptions = colourTargets.withUnsafeBufferPointer(\.baseAddress!)
|
||||||
|
targetInfo.num_color_targets = UInt32(colourTargets.count)
|
||||||
|
|
||||||
|
var info = SDL_GPUGraphicsPipelineCreateInfo(
|
||||||
|
vertex_shader: vertexShader,
|
||||||
|
fragment_shader: fragmentShader,
|
||||||
|
vertex_input_state: SDL_GPUVertexInputState(
|
||||||
|
vertex_buffer_descriptions: vertexDescriptions.withUnsafeBufferPointer(\.baseAddress!),
|
||||||
|
num_vertex_buffers: UInt32(vertexDescriptions.count),
|
||||||
|
vertex_attributes: vertexAttributes.withUnsafeBufferPointer(\.baseAddress!),
|
||||||
|
num_vertex_attributes: UInt32(vertexAttributes.count)),
|
||||||
|
primitive_type: SDL_GPU_PRIMITIVETYPE_TRIANGLELIST,
|
||||||
|
rasterizer_state: rasterizerDesc,
|
||||||
|
multisample_state: SDL_GPUMultisampleState(),
|
||||||
|
depth_stencil_state: SDL_GPUDepthStencilState(),
|
||||||
|
target_info: targetInfo,
|
||||||
|
props: 0)
|
||||||
|
guard let pso = SDL_CreateGPUGraphicsPipeline(ctx.device, &info) else
|
||||||
|
{
|
||||||
|
throw .sdlError("SDL_CreateGPUGraphicsPipeline", String(cString: SDL_GetError()))
|
||||||
|
}
|
||||||
|
self.pso = pso
|
||||||
|
|
||||||
|
guard let cmd = SDL_AcquireGPUCommandBuffer(ctx.device) else
|
||||||
|
{
|
||||||
|
throw .sdlError("SDL_AcquireGPUCommandBuffer", String(cString: SDL_GetError()))
|
||||||
|
}
|
||||||
|
let pass = SDL_BeginGPUCopyPass(cmd)
|
||||||
|
defer
|
||||||
|
{
|
||||||
|
SDL_EndGPUCopyPass(pass)
|
||||||
|
SDL_SubmitGPUCommandBuffer(cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
try ctx.copyPass { (pass) throws(NeHeError) in
|
||||||
|
self.vtxBuffer = try pass.createBuffer(usage: SDL_GPU_BUFFERUSAGE_VERTEX, Self.vertices[...])
|
||||||
|
self.idxBuffer = try pass.createBuffer(usage: SDL_GPU_BUFFERUSAGE_INDEX, Self.indices[...])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func quit(ctx: NeHeContext)
|
||||||
|
{
|
||||||
|
SDL_ReleaseGPUBuffer(ctx.device, self.idxBuffer)
|
||||||
|
SDL_ReleaseGPUBuffer(ctx.device, self.vtxBuffer)
|
||||||
|
SDL_ReleaseGPUGraphicsPipeline(ctx.device, self.pso)
|
||||||
|
}
|
||||||
|
|
||||||
|
mutating func resize(size: Size<Int32>)
|
||||||
|
{
|
||||||
|
let aspect = Float(size.width) / Float(max(1, size.height))
|
||||||
|
self.projection = .perspective(fovy: 45, aspect: aspect, near: 0.1, far: 100)
|
||||||
|
}
|
||||||
|
|
||||||
|
func draw(ctx: inout NeHeContext, cmd: OpaquePointer,
|
||||||
|
swapchain: OpaquePointer, swapchainSize: Size<UInt32>) throws(NeHeError)
|
||||||
|
{
|
||||||
|
var colorInfo = SDL_GPUColorTargetInfo()
|
||||||
|
colorInfo.texture = swapchain
|
||||||
|
colorInfo.clear_color = SDL_FColor(r: 0.0, g: 0.0, b: 0.0, a: 0.5)
|
||||||
|
colorInfo.load_op = SDL_GPU_LOADOP_CLEAR
|
||||||
|
colorInfo.store_op = SDL_GPU_STOREOP_STORE
|
||||||
|
|
||||||
|
// Begin pass & bind pipeline state
|
||||||
|
let pass = SDL_BeginGPURenderPass(cmd, &colorInfo, 1, nil);
|
||||||
|
SDL_BindGPUGraphicsPipeline(pass, self.pso)
|
||||||
|
|
||||||
|
// Bind vertex & index buffers
|
||||||
|
let vtxBindings = [ SDL_GPUBufferBinding(buffer: self.vtxBuffer, offset: 0) ]
|
||||||
|
var idxBinding = SDL_GPUBufferBinding(buffer: self.idxBuffer, offset: 0)
|
||||||
|
SDL_BindGPUVertexBuffers(pass, 0,
|
||||||
|
vtxBindings.withUnsafeBufferPointer(\.baseAddress!), UInt32(vtxBindings.count))
|
||||||
|
SDL_BindGPUIndexBuffer(pass, &idxBinding, SDL_GPU_INDEXELEMENTSIZE_16BIT)
|
||||||
|
|
||||||
|
// Draw triangle 1.5 units to the left and 6 units into the camera
|
||||||
|
var model: simd_float4x4 = .translation(.init(-1.5, 0.0, -6.0))
|
||||||
|
var viewProj = self.projection * model
|
||||||
|
SDL_PushGPUVertexUniformData(cmd, 0, &viewProj, UInt32(MemoryLayout<simd_float4x4>.size))
|
||||||
|
SDL_DrawGPUIndexedPrimitives(pass, 3, 1, 0, 0, 0)
|
||||||
|
|
||||||
|
// Move to the right by 3 units and draw quad
|
||||||
|
model.translate(.init(3.0, 0.0, 0.0))
|
||||||
|
viewProj = self.projection * model
|
||||||
|
SDL_PushGPUVertexUniformData(cmd, 0, &viewProj, UInt32(MemoryLayout<simd_float4x4>.size))
|
||||||
|
SDL_DrawGPUIndexedPrimitives(pass, 6, 1, 3, 0, 0)
|
||||||
|
|
||||||
|
SDL_EndGPURenderPass(pass);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@main struct Program: AppRunner
|
||||||
|
{
|
||||||
|
typealias Delegate = Lesson2
|
||||||
|
static let config = AppConfig(
|
||||||
|
title: "NeHe's First Polygon Tutorial",
|
||||||
|
width: 640,
|
||||||
|
height: 480,
|
||||||
|
bundle: Bundle.module)
|
||||||
|
}
|
||||||
177
src/swift/Lesson3/lesson3.swift
Normal file
177
src/swift/Lesson3/lesson3.swift
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: (C) 2025 a dinosaur
|
||||||
|
* SPDX-License-Identifier: Zlib
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import SDLSwift
|
||||||
|
import NeHe
|
||||||
|
import simd
|
||||||
|
|
||||||
|
struct Lesson3: AppDelegate
|
||||||
|
{
|
||||||
|
struct Vertex
|
||||||
|
{
|
||||||
|
let position: SIMD3<Float>, color: SIMD4<Float>
|
||||||
|
|
||||||
|
init(_ position: SIMD3<Float>, _ color: SIMD4<Float>)
|
||||||
|
{
|
||||||
|
self.position = position
|
||||||
|
self.color = color
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static let vertices =
|
||||||
|
[
|
||||||
|
// Triangle
|
||||||
|
Vertex(.init( 0.0, 1.0, 0.0), .init(1.0, 0.0, 0.0, 1.0)), // Top (red)
|
||||||
|
Vertex(.init(-1.0, -1.0, 0.0), .init(0.0, 1.0, 0.0, 1.0)), // Bottom left (green)
|
||||||
|
Vertex(.init( 1.0, -1.0, 0.0), .init(0.0, 0.0, 1.0, 1.0)), // Bottom right (blue)
|
||||||
|
// Quad
|
||||||
|
Vertex(.init(-1.0, 1.0, 0.0), .init(0.5, 0.5, 1.0, 1.0)), // Top left
|
||||||
|
Vertex(.init( 1.0, 1.0, 0.0), .init(0.5, 0.5, 1.0, 1.0)), // Top right
|
||||||
|
Vertex(.init( 1.0, -1.0, 0.0), .init(0.5, 0.5, 1.0, 1.0)), // Bottom right
|
||||||
|
Vertex(.init(-1.0, -1.0, 0.0), .init(0.5, 0.5, 1.0, 1.0)), // Bottom left
|
||||||
|
]
|
||||||
|
|
||||||
|
static let indices: [Int16] =
|
||||||
|
[
|
||||||
|
// Triangle
|
||||||
|
0, 1, 2,
|
||||||
|
// Quad
|
||||||
|
3, 4, 5, 5, 6, 3,
|
||||||
|
]
|
||||||
|
|
||||||
|
var pso: OpaquePointer? = nil
|
||||||
|
var vtxBuffer: OpaquePointer? = nil
|
||||||
|
var idxBuffer: OpaquePointer? = nil
|
||||||
|
var projection: matrix_float4x4 = .init(1.0)
|
||||||
|
|
||||||
|
mutating func `init`(ctx: inout NeHeContext) throws(NeHeError)
|
||||||
|
{
|
||||||
|
let (vertexShader, fragmentShader) = try ctx.loadShaders(name: "lesson3", vertexUniforms: 1)
|
||||||
|
defer
|
||||||
|
{
|
||||||
|
SDL_ReleaseGPUShader(ctx.device, fragmentShader)
|
||||||
|
SDL_ReleaseGPUShader(ctx.device, vertexShader)
|
||||||
|
}
|
||||||
|
|
||||||
|
let vertexDescriptions: [SDL_GPUVertexBufferDescription] =
|
||||||
|
[
|
||||||
|
SDL_GPUVertexBufferDescription(
|
||||||
|
slot: 0,
|
||||||
|
pitch: UInt32(MemoryLayout<Vertex>.stride),
|
||||||
|
input_rate: SDL_GPU_VERTEXINPUTRATE_VERTEX,
|
||||||
|
instance_step_rate: 0),
|
||||||
|
]
|
||||||
|
let vertexAttributes: [SDL_GPUVertexAttribute] =
|
||||||
|
[
|
||||||
|
SDL_GPUVertexAttribute(
|
||||||
|
location: 0,
|
||||||
|
buffer_slot: 0,
|
||||||
|
format: SDL_GPU_VERTEXELEMENTFORMAT_FLOAT3,
|
||||||
|
offset: UInt32(MemoryLayout<Vertex>.offset(of: \.position)!)),
|
||||||
|
SDL_GPUVertexAttribute(
|
||||||
|
location: 1,
|
||||||
|
buffer_slot: 0,
|
||||||
|
format: SDL_GPU_VERTEXELEMENTFORMAT_FLOAT4,
|
||||||
|
offset: UInt32(MemoryLayout<Vertex>.offset(of: \.color)!)),
|
||||||
|
]
|
||||||
|
let colourTargets: [SDL_GPUColorTargetDescription] =
|
||||||
|
[
|
||||||
|
SDL_GPUColorTargetDescription(
|
||||||
|
format: SDL_GetGPUSwapchainTextureFormat(ctx.device, ctx.window),
|
||||||
|
blend_state: SDL_GPUColorTargetBlendState())
|
||||||
|
]
|
||||||
|
var rasterizerDesc = SDL_GPURasterizerState()
|
||||||
|
rasterizerDesc.fill_mode = SDL_GPU_FILLMODE_FILL
|
||||||
|
rasterizerDesc.cull_mode = SDL_GPU_CULLMODE_NONE
|
||||||
|
rasterizerDesc.front_face = SDL_GPU_FRONTFACE_COUNTER_CLOCKWISE
|
||||||
|
var targetInfo = SDL_GPUGraphicsPipelineTargetInfo()
|
||||||
|
targetInfo.color_target_descriptions = colourTargets.withUnsafeBufferPointer(\.baseAddress!)
|
||||||
|
targetInfo.num_color_targets = UInt32(colourTargets.count)
|
||||||
|
|
||||||
|
var info = SDL_GPUGraphicsPipelineCreateInfo(
|
||||||
|
vertex_shader: vertexShader,
|
||||||
|
fragment_shader: fragmentShader,
|
||||||
|
vertex_input_state: SDL_GPUVertexInputState(
|
||||||
|
vertex_buffer_descriptions: vertexDescriptions.withUnsafeBufferPointer(\.baseAddress!),
|
||||||
|
num_vertex_buffers: UInt32(vertexDescriptions.count),
|
||||||
|
vertex_attributes: vertexAttributes.withUnsafeBufferPointer(\.baseAddress!),
|
||||||
|
num_vertex_attributes: UInt32(vertexAttributes.count)),
|
||||||
|
primitive_type: SDL_GPU_PRIMITIVETYPE_TRIANGLELIST,
|
||||||
|
rasterizer_state: rasterizerDesc,
|
||||||
|
multisample_state: SDL_GPUMultisampleState(),
|
||||||
|
depth_stencil_state: SDL_GPUDepthStencilState(),
|
||||||
|
target_info: targetInfo,
|
||||||
|
props: 0)
|
||||||
|
guard let pso = SDL_CreateGPUGraphicsPipeline(ctx.device, &info) else
|
||||||
|
{
|
||||||
|
throw .sdlError("SDL_CreateGPUGraphicsPipeline", String(cString: SDL_GetError()))
|
||||||
|
}
|
||||||
|
self.pso = pso
|
||||||
|
|
||||||
|
try ctx.copyPass { (pass) throws(NeHeError) in
|
||||||
|
self.vtxBuffer = try pass.createBuffer(usage: SDL_GPU_BUFFERUSAGE_VERTEX, Self.vertices[...])
|
||||||
|
self.idxBuffer = try pass.createBuffer(usage: SDL_GPU_BUFFERUSAGE_INDEX, Self.indices[...])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func quit(ctx: NeHeContext)
|
||||||
|
{
|
||||||
|
SDL_ReleaseGPUBuffer(ctx.device, self.idxBuffer)
|
||||||
|
SDL_ReleaseGPUBuffer(ctx.device, self.vtxBuffer)
|
||||||
|
SDL_ReleaseGPUGraphicsPipeline(ctx.device, self.pso)
|
||||||
|
}
|
||||||
|
|
||||||
|
mutating func resize(size: Size<Int32>)
|
||||||
|
{
|
||||||
|
let aspect = Float(size.width) / Float(max(1, size.height))
|
||||||
|
self.projection = .perspective(fovy: 45, aspect: aspect, near: 0.1, far: 100)
|
||||||
|
}
|
||||||
|
|
||||||
|
func draw(ctx: inout NeHeContext, cmd: OpaquePointer,
|
||||||
|
swapchain: OpaquePointer, swapchainSize: Size<UInt32>) throws(NeHeError)
|
||||||
|
{
|
||||||
|
var colorInfo = SDL_GPUColorTargetInfo()
|
||||||
|
colorInfo.texture = swapchain
|
||||||
|
colorInfo.clear_color = SDL_FColor(r: 0.0, g: 0.0, b: 0.0, a: 0.5)
|
||||||
|
colorInfo.load_op = SDL_GPU_LOADOP_CLEAR
|
||||||
|
colorInfo.store_op = SDL_GPU_STOREOP_STORE
|
||||||
|
|
||||||
|
// Begin pass & bind pipeline state
|
||||||
|
let pass = SDL_BeginGPURenderPass(cmd, &colorInfo, 1, nil);
|
||||||
|
SDL_BindGPUGraphicsPipeline(pass, self.pso)
|
||||||
|
|
||||||
|
// Bind vertex & index buffers
|
||||||
|
let vtxBindings = [ SDL_GPUBufferBinding(buffer: self.vtxBuffer, offset: 0) ]
|
||||||
|
var idxBinding = SDL_GPUBufferBinding(buffer: self.idxBuffer, offset: 0)
|
||||||
|
SDL_BindGPUVertexBuffers(pass, 0,
|
||||||
|
vtxBindings.withUnsafeBufferPointer(\.baseAddress!), UInt32(vtxBindings.count))
|
||||||
|
SDL_BindGPUIndexBuffer(pass, &idxBinding, SDL_GPU_INDEXELEMENTSIZE_16BIT)
|
||||||
|
|
||||||
|
// Draw triangle 1.5 units to the left and 6 units into the camera
|
||||||
|
var model: simd_float4x4 = .translation(.init(-1.5, 0.0, -6.0))
|
||||||
|
var viewProj = self.projection * model
|
||||||
|
SDL_PushGPUVertexUniformData(cmd, 0, &viewProj, UInt32(MemoryLayout<simd_float4x4>.size))
|
||||||
|
SDL_DrawGPUIndexedPrimitives(pass, 3, 1, 0, 0, 0)
|
||||||
|
|
||||||
|
// Move to the right by 3 units and draw quad
|
||||||
|
model.translate(.init(3.0, 0.0, 0.0))
|
||||||
|
viewProj = self.projection * model
|
||||||
|
SDL_PushGPUVertexUniformData(cmd, 0, &viewProj, UInt32(MemoryLayout<simd_float4x4>.size))
|
||||||
|
SDL_DrawGPUIndexedPrimitives(pass, 6, 1, 3, 0, 0)
|
||||||
|
|
||||||
|
SDL_EndGPURenderPass(pass);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@main struct Program: AppRunner
|
||||||
|
{
|
||||||
|
typealias Delegate = Lesson3
|
||||||
|
static let config = AppConfig(
|
||||||
|
title: "NeHe's Color Tutorial",
|
||||||
|
width: 640,
|
||||||
|
height: 480,
|
||||||
|
bundle: Bundle.module)
|
||||||
|
}
|
||||||
183
src/swift/Lesson4/lesson4.swift
Normal file
183
src/swift/Lesson4/lesson4.swift
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: (C) 2025 a dinosaur
|
||||||
|
* SPDX-License-Identifier: Zlib
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import SDLSwift
|
||||||
|
import NeHe
|
||||||
|
import simd
|
||||||
|
|
||||||
|
struct Lesson4: AppDelegate
|
||||||
|
{
|
||||||
|
struct Vertex
|
||||||
|
{
|
||||||
|
let position: SIMD3<Float>, color: SIMD4<Float>
|
||||||
|
|
||||||
|
init(_ position: SIMD3<Float>, _ color: SIMD4<Float>)
|
||||||
|
{
|
||||||
|
self.position = position
|
||||||
|
self.color = color
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static let vertices =
|
||||||
|
[
|
||||||
|
// Triangle
|
||||||
|
Vertex(.init( 0.0, 1.0, 0.0), .init(1.0, 0.0, 0.0, 1.0)), // Top (red)
|
||||||
|
Vertex(.init(-1.0, -1.0, 0.0), .init(0.0, 1.0, 0.0, 1.0)), // Bottom left (green)
|
||||||
|
Vertex(.init( 1.0, -1.0, 0.0), .init(0.0, 0.0, 1.0, 1.0)), // Bottom right (blue)
|
||||||
|
// Quad
|
||||||
|
Vertex(.init(-1.0, 1.0, 0.0), .init(0.5, 0.5, 1.0, 1.0)), // Top left
|
||||||
|
Vertex(.init( 1.0, 1.0, 0.0), .init(0.5, 0.5, 1.0, 1.0)), // Top right
|
||||||
|
Vertex(.init( 1.0, -1.0, 0.0), .init(0.5, 0.5, 1.0, 1.0)), // Bottom right
|
||||||
|
Vertex(.init(-1.0, -1.0, 0.0), .init(0.5, 0.5, 1.0, 1.0)), // Bottom left
|
||||||
|
]
|
||||||
|
|
||||||
|
static let indices: [Int16] =
|
||||||
|
[
|
||||||
|
// Triangle
|
||||||
|
0, 1, 2,
|
||||||
|
// Quad
|
||||||
|
3, 4, 5, 5, 6, 3,
|
||||||
|
]
|
||||||
|
|
||||||
|
var pso: OpaquePointer? = nil
|
||||||
|
var vtxBuffer: OpaquePointer? = nil
|
||||||
|
var idxBuffer: OpaquePointer? = nil
|
||||||
|
var projection: matrix_float4x4 = .init(1.0)
|
||||||
|
|
||||||
|
var rotTri: Float = 0.0, rotQuad: Float = 0.0
|
||||||
|
|
||||||
|
mutating func `init`(ctx: inout NeHeContext) throws(NeHeError)
|
||||||
|
{
|
||||||
|
let (vertexShader, fragmentShader) = try ctx.loadShaders(name: "lesson3", vertexUniforms: 1)
|
||||||
|
defer
|
||||||
|
{
|
||||||
|
SDL_ReleaseGPUShader(ctx.device, fragmentShader)
|
||||||
|
SDL_ReleaseGPUShader(ctx.device, vertexShader)
|
||||||
|
}
|
||||||
|
|
||||||
|
let vertexDescriptions: [SDL_GPUVertexBufferDescription] =
|
||||||
|
[
|
||||||
|
SDL_GPUVertexBufferDescription(
|
||||||
|
slot: 0,
|
||||||
|
pitch: UInt32(MemoryLayout<Vertex>.stride),
|
||||||
|
input_rate: SDL_GPU_VERTEXINPUTRATE_VERTEX,
|
||||||
|
instance_step_rate: 0),
|
||||||
|
]
|
||||||
|
let vertexAttributes: [SDL_GPUVertexAttribute] =
|
||||||
|
[
|
||||||
|
SDL_GPUVertexAttribute(
|
||||||
|
location: 0,
|
||||||
|
buffer_slot: 0,
|
||||||
|
format: SDL_GPU_VERTEXELEMENTFORMAT_FLOAT3,
|
||||||
|
offset: UInt32(MemoryLayout<Vertex>.offset(of: \.position)!)),
|
||||||
|
SDL_GPUVertexAttribute(
|
||||||
|
location: 1,
|
||||||
|
buffer_slot: 0,
|
||||||
|
format: SDL_GPU_VERTEXELEMENTFORMAT_FLOAT4,
|
||||||
|
offset: UInt32(MemoryLayout<Vertex>.offset(of: \.color)!)),
|
||||||
|
]
|
||||||
|
let colourTargets: [SDL_GPUColorTargetDescription] =
|
||||||
|
[
|
||||||
|
SDL_GPUColorTargetDescription(
|
||||||
|
format: SDL_GetGPUSwapchainTextureFormat(ctx.device, ctx.window),
|
||||||
|
blend_state: SDL_GPUColorTargetBlendState())
|
||||||
|
]
|
||||||
|
var rasterizerDesc = SDL_GPURasterizerState()
|
||||||
|
rasterizerDesc.fill_mode = SDL_GPU_FILLMODE_FILL
|
||||||
|
rasterizerDesc.cull_mode = SDL_GPU_CULLMODE_NONE
|
||||||
|
rasterizerDesc.front_face = SDL_GPU_FRONTFACE_COUNTER_CLOCKWISE
|
||||||
|
var targetInfo = SDL_GPUGraphicsPipelineTargetInfo()
|
||||||
|
targetInfo.color_target_descriptions = colourTargets.withUnsafeBufferPointer(\.baseAddress!)
|
||||||
|
targetInfo.num_color_targets = UInt32(colourTargets.count)
|
||||||
|
|
||||||
|
var info = SDL_GPUGraphicsPipelineCreateInfo(
|
||||||
|
vertex_shader: vertexShader,
|
||||||
|
fragment_shader: fragmentShader,
|
||||||
|
vertex_input_state: SDL_GPUVertexInputState(
|
||||||
|
vertex_buffer_descriptions: vertexDescriptions.withUnsafeBufferPointer(\.baseAddress!),
|
||||||
|
num_vertex_buffers: UInt32(vertexDescriptions.count),
|
||||||
|
vertex_attributes: vertexAttributes.withUnsafeBufferPointer(\.baseAddress!),
|
||||||
|
num_vertex_attributes: UInt32(vertexAttributes.count)),
|
||||||
|
primitive_type: SDL_GPU_PRIMITIVETYPE_TRIANGLELIST,
|
||||||
|
rasterizer_state: rasterizerDesc,
|
||||||
|
multisample_state: SDL_GPUMultisampleState(),
|
||||||
|
depth_stencil_state: SDL_GPUDepthStencilState(),
|
||||||
|
target_info: targetInfo,
|
||||||
|
props: 0)
|
||||||
|
guard let pso = SDL_CreateGPUGraphicsPipeline(ctx.device, &info) else
|
||||||
|
{
|
||||||
|
throw .sdlError("SDL_CreateGPUGraphicsPipeline", String(cString: SDL_GetError()))
|
||||||
|
}
|
||||||
|
self.pso = pso
|
||||||
|
|
||||||
|
try ctx.copyPass { (pass) throws(NeHeError) in
|
||||||
|
self.vtxBuffer = try pass.createBuffer(usage: SDL_GPU_BUFFERUSAGE_VERTEX, Self.vertices[...])
|
||||||
|
self.idxBuffer = try pass.createBuffer(usage: SDL_GPU_BUFFERUSAGE_INDEX, Self.indices[...])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func quit(ctx: NeHeContext)
|
||||||
|
{
|
||||||
|
SDL_ReleaseGPUBuffer(ctx.device, self.idxBuffer)
|
||||||
|
SDL_ReleaseGPUBuffer(ctx.device, self.vtxBuffer)
|
||||||
|
SDL_ReleaseGPUGraphicsPipeline(ctx.device, self.pso)
|
||||||
|
}
|
||||||
|
|
||||||
|
mutating func resize(size: Size<Int32>)
|
||||||
|
{
|
||||||
|
let aspect = Float(size.width) / Float(max(1, size.height))
|
||||||
|
self.projection = .perspective(fovy: 45, aspect: aspect, near: 0.1, far: 100)
|
||||||
|
}
|
||||||
|
|
||||||
|
mutating func draw(ctx: inout NeHeContext, cmd: OpaquePointer,
|
||||||
|
swapchain: OpaquePointer, swapchainSize: Size<UInt32>) throws(NeHeError)
|
||||||
|
{
|
||||||
|
var colorInfo = SDL_GPUColorTargetInfo()
|
||||||
|
colorInfo.texture = swapchain
|
||||||
|
colorInfo.clear_color = SDL_FColor(r: 0.0, g: 0.0, b: 0.0, a: 0.5)
|
||||||
|
colorInfo.load_op = SDL_GPU_LOADOP_CLEAR
|
||||||
|
colorInfo.store_op = SDL_GPU_STOREOP_STORE
|
||||||
|
|
||||||
|
// Begin pass & bind pipeline state
|
||||||
|
let pass = SDL_BeginGPURenderPass(cmd, &colorInfo, 1, nil);
|
||||||
|
SDL_BindGPUGraphicsPipeline(pass, self.pso)
|
||||||
|
|
||||||
|
// Bind vertex & index buffers
|
||||||
|
let vtxBindings = [ SDL_GPUBufferBinding(buffer: self.vtxBuffer, offset: 0) ]
|
||||||
|
var idxBinding = SDL_GPUBufferBinding(buffer: self.idxBuffer, offset: 0)
|
||||||
|
SDL_BindGPUVertexBuffers(pass, 0,
|
||||||
|
vtxBindings.withUnsafeBufferPointer(\.baseAddress!), UInt32(vtxBindings.count))
|
||||||
|
SDL_BindGPUIndexBuffer(pass, &idxBinding, SDL_GPU_INDEXELEMENTSIZE_16BIT)
|
||||||
|
|
||||||
|
// Draw triangle 1.5 units to the left and 6 units into the camera
|
||||||
|
var model: simd_float4x4 = .translation(.init(-1.5, 0.0, -6.0))
|
||||||
|
model.rotate(angle: self.rotTri, axis: .init(0, 1, 0))
|
||||||
|
var viewProj = self.projection * model
|
||||||
|
SDL_PushGPUVertexUniformData(cmd, 0, &viewProj, UInt32(MemoryLayout<simd_float4x4>.size))
|
||||||
|
SDL_DrawGPUIndexedPrimitives(pass, 3, 1, 0, 0, 0)
|
||||||
|
|
||||||
|
// Draw quad 1.5 units to the right and 6 units in
|
||||||
|
model = .translation(.init(1.5, 0.0, -6.0))
|
||||||
|
model.rotate(angle: self.rotQuad, axis: .init(1, 0, 0))
|
||||||
|
viewProj = self.projection * model
|
||||||
|
SDL_PushGPUVertexUniformData(cmd, 0, &viewProj, UInt32(MemoryLayout<simd_float4x4>.size))
|
||||||
|
SDL_DrawGPUIndexedPrimitives(pass, 6, 1, 3, 0, 0)
|
||||||
|
|
||||||
|
SDL_EndGPURenderPass(pass);
|
||||||
|
|
||||||
|
self.rotTri += 0.2
|
||||||
|
self.rotQuad -= 0.15
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@main struct Program: AppRunner
|
||||||
|
{
|
||||||
|
typealias Delegate = Lesson4
|
||||||
|
static let config = AppConfig(
|
||||||
|
title: "NeHe's Rotation Tutorial",
|
||||||
|
width: 640,
|
||||||
|
height: 480,
|
||||||
|
bundle: Bundle.module)
|
||||||
|
}
|
||||||
230
src/swift/Lesson5/lesson5.swift
Normal file
230
src/swift/Lesson5/lesson5.swift
Normal file
@@ -0,0 +1,230 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: (C) 2025 a dinosaur
|
||||||
|
* SPDX-License-Identifier: Zlib
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import SDLSwift
|
||||||
|
import NeHe
|
||||||
|
import simd
|
||||||
|
|
||||||
|
struct Lesson5: AppDelegate
|
||||||
|
{
|
||||||
|
struct Vertex
|
||||||
|
{
|
||||||
|
let position: SIMD3<Float>, color: SIMD4<Float>
|
||||||
|
|
||||||
|
init(_ position: SIMD3<Float>, _ color: SIMD4<Float>)
|
||||||
|
{
|
||||||
|
self.position = position
|
||||||
|
self.color = color
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static let vertices =
|
||||||
|
[
|
||||||
|
// Pyramid
|
||||||
|
Vertex(.init( 0.0, 1.0, 0.0), .init(1.0, 0.0, 0.0, 1.0)), // Top of pyramid (Red)
|
||||||
|
Vertex(.init(-1.0, -1.0, 1.0), .init(0.0, 1.0, 0.0, 1.0)), // Front-left of pyramid (Green)
|
||||||
|
Vertex(.init( 1.0, -1.0, 1.0), .init(0.0, 0.0, 1.0, 1.0)), // Front-right of pyramid (Blue)
|
||||||
|
Vertex(.init( 1.0, -1.0, -1.0), .init(0.0, 1.0, 0.0, 1.0)), // Back-right of pyramid (Green)
|
||||||
|
Vertex(.init(-1.0, -1.0, -1.0), .init(0.0, 0.0, 1.0, 1.0)), // Back-left of pyramid (Blue)
|
||||||
|
// Cube
|
||||||
|
Vertex(.init( 1.0, 1.0, -1.0), .init(0.0, 1.0, 0.0, 1.0)), // Top-right of top face (Green)
|
||||||
|
Vertex(.init(-1.0, 1.0, -1.0), .init(0.0, 1.0, 0.0, 1.0)), // Top-left of top face (Green)
|
||||||
|
Vertex(.init(-1.0, 1.0, 1.0), .init(0.0, 1.0, 0.0, 1.0)), // Bottom-left of top face (Green)
|
||||||
|
Vertex(.init( 1.0, 1.0, 1.0), .init(0.0, 1.0, 0.0, 1.0)), // Bottom-right of top face (Green)
|
||||||
|
Vertex(.init( 1.0, -1.0, 1.0), .init(1.0, 0.5, 0.0, 1.0)), // Top-right of bottom face (Orange)
|
||||||
|
Vertex(.init(-1.0, -1.0, 1.0), .init(1.0, 0.5, 0.0, 1.0)), // Top-left of bottom face (Orange)
|
||||||
|
Vertex(.init(-1.0, -1.0, -1.0), .init(1.0, 0.5, 0.0, 1.0)), // Bottom-left of bottom face (Orange)
|
||||||
|
Vertex(.init( 1.0, -1.0, -1.0), .init(1.0, 0.5, 0.0, 1.0)), // Bottom-right of bottom face (Orange)
|
||||||
|
Vertex(.init( 1.0, 1.0, 1.0), .init(1.0, 0.0, 0.0, 1.0)), // Top-right of front face (Red)
|
||||||
|
Vertex(.init(-1.0, 1.0, 1.0), .init(1.0, 0.0, 0.0, 1.0)), // Top-left of front face (Red)
|
||||||
|
Vertex(.init(-1.0, -1.0, 1.0), .init(1.0, 0.0, 0.0, 1.0)), // Bottom-left of front face (Red)
|
||||||
|
Vertex(.init( 1.0, -1.0, 1.0), .init(1.0, 0.0, 0.0, 1.0)), // Bottom-right of front face (Red)
|
||||||
|
Vertex(.init( 1.0, -1.0, -1.0), .init(1.0, 1.0, 0.0, 1.0)), // Top-right of back face (Yellow)
|
||||||
|
Vertex(.init(-1.0, -1.0, -1.0), .init(1.0, 1.0, 0.0, 1.0)), // Top-left of back face (Yellow)
|
||||||
|
Vertex(.init(-1.0, 1.0, -1.0), .init(1.0, 1.0, 0.0, 1.0)), // Bottom-left of back face (Yellow)
|
||||||
|
Vertex(.init( 1.0, 1.0, -1.0), .init(1.0, 1.0, 0.0, 1.0)), // Bottom-right of back face (Yellow)
|
||||||
|
Vertex(.init(-1.0, 1.0, 1.0), .init(0.0, 0.0, 1.0, 1.0)), // Top-right of left face (Blue)
|
||||||
|
Vertex(.init(-1.0, 1.0, -1.0), .init(0.0, 0.0, 1.0, 1.0)), // Top-left of left face (Blue)
|
||||||
|
Vertex(.init(-1.0, -1.0, -1.0), .init(0.0, 0.0, 1.0, 1.0)), // Bottom-left of left face (Blue)
|
||||||
|
Vertex(.init(-1.0, -1.0, 1.0), .init(0.0, 0.0, 1.0, 1.0)), // Bottom-right of left face (Blue)
|
||||||
|
Vertex(.init( 1.0, 1.0, -1.0), .init(1.0, 0.0, 1.0, 1.0)), // Top-right of right face (Violet)
|
||||||
|
Vertex(.init( 1.0, 1.0, 1.0), .init(1.0, 0.0, 1.0, 1.0)), // Top-left of right face (Violet)
|
||||||
|
Vertex(.init( 1.0, -1.0, 1.0), .init(1.0, 0.0, 1.0, 1.0)), // Bottom-left of right face (Violet)
|
||||||
|
Vertex(.init( 1.0, -1.0, -1.0), .init(1.0, 0.0, 1.0, 1.0)), // Bottom-right of right face (Violet)
|
||||||
|
]
|
||||||
|
|
||||||
|
static let indices: [UInt16] =
|
||||||
|
[
|
||||||
|
// Pyramid
|
||||||
|
0, 1, 2, // Front
|
||||||
|
0, 2, 3, // Right
|
||||||
|
0, 3, 4, // Back
|
||||||
|
0, 4, 1, // Left
|
||||||
|
// Cube
|
||||||
|
5, 6, 7, 7, 8, 5, // Top
|
||||||
|
9, 10, 11, 11, 12, 9, // Bottom
|
||||||
|
13, 14, 15, 15, 16, 13, // Front
|
||||||
|
17, 18, 19, 19, 20, 17, // Back
|
||||||
|
21, 22, 23, 23, 24, 21, // Left
|
||||||
|
25, 26, 27, 27, 28, 25, // Right
|
||||||
|
]
|
||||||
|
|
||||||
|
var pso: OpaquePointer? = nil
|
||||||
|
var vtxBuffer: OpaquePointer? = nil
|
||||||
|
var idxBuffer: OpaquePointer? = nil
|
||||||
|
var projection: matrix_float4x4 = .init(1.0)
|
||||||
|
|
||||||
|
var rotTri: Float = 0.0, rotQuad: Float = 0.0
|
||||||
|
|
||||||
|
mutating func `init`(ctx: inout NeHeContext) throws(NeHeError)
|
||||||
|
{
|
||||||
|
let (vertexShader, fragmentShader) = try ctx.loadShaders(name: "lesson3", vertexUniforms: 1)
|
||||||
|
defer
|
||||||
|
{
|
||||||
|
SDL_ReleaseGPUShader(ctx.device, fragmentShader)
|
||||||
|
SDL_ReleaseGPUShader(ctx.device, vertexShader)
|
||||||
|
}
|
||||||
|
|
||||||
|
let vertexDescriptions: [SDL_GPUVertexBufferDescription] =
|
||||||
|
[
|
||||||
|
SDL_GPUVertexBufferDescription(
|
||||||
|
slot: 0,
|
||||||
|
pitch: UInt32(MemoryLayout<Vertex>.stride),
|
||||||
|
input_rate: SDL_GPU_VERTEXINPUTRATE_VERTEX,
|
||||||
|
instance_step_rate: 0),
|
||||||
|
]
|
||||||
|
let vertexAttributes: [SDL_GPUVertexAttribute] =
|
||||||
|
[
|
||||||
|
SDL_GPUVertexAttribute(
|
||||||
|
location: 0,
|
||||||
|
buffer_slot: 0,
|
||||||
|
format: SDL_GPU_VERTEXELEMENTFORMAT_FLOAT3,
|
||||||
|
offset: UInt32(MemoryLayout<Vertex>.offset(of: \.position)!)),
|
||||||
|
SDL_GPUVertexAttribute(
|
||||||
|
location: 1,
|
||||||
|
buffer_slot: 0,
|
||||||
|
format: SDL_GPU_VERTEXELEMENTFORMAT_FLOAT4,
|
||||||
|
offset: UInt32(MemoryLayout<Vertex>.offset(of: \.color)!)),
|
||||||
|
]
|
||||||
|
let colourTargets: [SDL_GPUColorTargetDescription] =
|
||||||
|
[
|
||||||
|
SDL_GPUColorTargetDescription(
|
||||||
|
format: SDL_GetGPUSwapchainTextureFormat(ctx.device, ctx.window),
|
||||||
|
blend_state: SDL_GPUColorTargetBlendState())
|
||||||
|
]
|
||||||
|
var rasterizerDesc = SDL_GPURasterizerState()
|
||||||
|
rasterizerDesc.fill_mode = SDL_GPU_FILLMODE_FILL
|
||||||
|
rasterizerDesc.cull_mode = SDL_GPU_CULLMODE_NONE
|
||||||
|
rasterizerDesc.front_face = SDL_GPU_FRONTFACE_COUNTER_CLOCKWISE
|
||||||
|
var depthStencilState = SDL_GPUDepthStencilState()
|
||||||
|
depthStencilState.compare_op = SDL_GPU_COMPAREOP_LESS_OR_EQUAL
|
||||||
|
depthStencilState.enable_depth_test = true
|
||||||
|
depthStencilState.enable_depth_write = true
|
||||||
|
var targetInfo = SDL_GPUGraphicsPipelineTargetInfo()
|
||||||
|
targetInfo.color_target_descriptions = colourTargets.withUnsafeBufferPointer(\.baseAddress!)
|
||||||
|
targetInfo.num_color_targets = UInt32(colourTargets.count)
|
||||||
|
targetInfo.depth_stencil_format = SDL_GPU_TEXTUREFORMAT_D16_UNORM
|
||||||
|
targetInfo.has_depth_stencil_target = true
|
||||||
|
|
||||||
|
var info = SDL_GPUGraphicsPipelineCreateInfo(
|
||||||
|
vertex_shader: vertexShader,
|
||||||
|
fragment_shader: fragmentShader,
|
||||||
|
vertex_input_state: SDL_GPUVertexInputState(
|
||||||
|
vertex_buffer_descriptions: vertexDescriptions.withUnsafeBufferPointer(\.baseAddress!),
|
||||||
|
num_vertex_buffers: UInt32(vertexDescriptions.count),
|
||||||
|
vertex_attributes: vertexAttributes.withUnsafeBufferPointer(\.baseAddress!),
|
||||||
|
num_vertex_attributes: UInt32(vertexAttributes.count)),
|
||||||
|
primitive_type: SDL_GPU_PRIMITIVETYPE_TRIANGLELIST,
|
||||||
|
rasterizer_state: rasterizerDesc,
|
||||||
|
multisample_state: SDL_GPUMultisampleState(),
|
||||||
|
depth_stencil_state: depthStencilState,
|
||||||
|
target_info: targetInfo,
|
||||||
|
props: 0)
|
||||||
|
guard let pso = SDL_CreateGPUGraphicsPipeline(ctx.device, &info) else
|
||||||
|
{
|
||||||
|
throw .sdlError("SDL_CreateGPUGraphicsPipeline", String(cString: SDL_GetError()))
|
||||||
|
}
|
||||||
|
self.pso = pso
|
||||||
|
|
||||||
|
try ctx.copyPass { (pass) throws(NeHeError) in
|
||||||
|
self.vtxBuffer = try pass.createBuffer(usage: SDL_GPU_BUFFERUSAGE_VERTEX, Self.vertices[...])
|
||||||
|
self.idxBuffer = try pass.createBuffer(usage: SDL_GPU_BUFFERUSAGE_INDEX, Self.indices[...])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func quit(ctx: NeHeContext)
|
||||||
|
{
|
||||||
|
SDL_ReleaseGPUBuffer(ctx.device, self.idxBuffer)
|
||||||
|
SDL_ReleaseGPUBuffer(ctx.device, self.vtxBuffer)
|
||||||
|
SDL_ReleaseGPUGraphicsPipeline(ctx.device, self.pso)
|
||||||
|
}
|
||||||
|
|
||||||
|
mutating func resize(size: Size<Int32>)
|
||||||
|
{
|
||||||
|
let aspect = Float(size.width) / Float(max(1, size.height))
|
||||||
|
self.projection = .perspective(fovy: 45, aspect: aspect, near: 0.1, far: 100)
|
||||||
|
}
|
||||||
|
|
||||||
|
mutating func draw(ctx: inout NeHeContext, cmd: OpaquePointer,
|
||||||
|
swapchain: OpaquePointer, swapchainSize: Size<UInt32>) throws(NeHeError)
|
||||||
|
{
|
||||||
|
var colorInfo = SDL_GPUColorTargetInfo()
|
||||||
|
colorInfo.texture = swapchain
|
||||||
|
colorInfo.clear_color = SDL_FColor(r: 0.0, g: 0.0, b: 0.0, a: 0.5)
|
||||||
|
colorInfo.load_op = SDL_GPU_LOADOP_CLEAR
|
||||||
|
colorInfo.store_op = SDL_GPU_STOREOP_STORE
|
||||||
|
|
||||||
|
var depthInfo = SDL_GPUDepthStencilTargetInfo()
|
||||||
|
depthInfo.texture = ctx.depthTexture
|
||||||
|
depthInfo.clear_depth = 1.0
|
||||||
|
depthInfo.load_op = SDL_GPU_LOADOP_CLEAR
|
||||||
|
depthInfo.store_op = SDL_GPU_STOREOP_DONT_CARE
|
||||||
|
depthInfo.stencil_load_op = SDL_GPU_LOADOP_DONT_CARE
|
||||||
|
depthInfo.stencil_store_op = SDL_GPU_STOREOP_DONT_CARE
|
||||||
|
depthInfo.cycle = true
|
||||||
|
|
||||||
|
// Begin pass & bind pipeline state
|
||||||
|
let pass = SDL_BeginGPURenderPass(cmd, &colorInfo, 1, &depthInfo);
|
||||||
|
SDL_BindGPUGraphicsPipeline(pass, self.pso)
|
||||||
|
|
||||||
|
// Bind vertex & index buffers
|
||||||
|
let vtxBindings = [ SDL_GPUBufferBinding(buffer: self.vtxBuffer, offset: 0) ]
|
||||||
|
var idxBinding = SDL_GPUBufferBinding(buffer: self.idxBuffer, offset: 0)
|
||||||
|
SDL_BindGPUVertexBuffers(pass, 0,
|
||||||
|
vtxBindings.withUnsafeBufferPointer(\.baseAddress!), UInt32(vtxBindings.count))
|
||||||
|
SDL_BindGPUIndexBuffer(pass, &idxBinding, SDL_GPU_INDEXELEMENTSIZE_16BIT)
|
||||||
|
|
||||||
|
// Draw triangle 1.5 units to the left and 6 units into the camera
|
||||||
|
var model: simd_float4x4 = .translation(.init(-1.5, 0.0, -6.0))
|
||||||
|
model.rotate(angle: self.rotTri, axis: .init(0, 1, 0))
|
||||||
|
var viewProj = self.projection * model
|
||||||
|
SDL_PushGPUVertexUniformData(cmd, 0, &viewProj, UInt32(MemoryLayout<simd_float4x4>.size))
|
||||||
|
SDL_DrawGPUIndexedPrimitives(pass, 12, 1, 0, 0, 0)
|
||||||
|
|
||||||
|
// Draw quad 1.5 units to the right and 7 units into the camera
|
||||||
|
model = .translation(.init(1.5, 0.0, -7.0))
|
||||||
|
model.rotate(angle: self.rotQuad, axis: .init(1, 1, 1))
|
||||||
|
viewProj = self.projection * model
|
||||||
|
SDL_PushGPUVertexUniformData(cmd, 0, &viewProj, UInt32(MemoryLayout<simd_float4x4>.size))
|
||||||
|
SDL_DrawGPUIndexedPrimitives(pass, 36, 1, 12, 0, 0)
|
||||||
|
|
||||||
|
SDL_EndGPURenderPass(pass);
|
||||||
|
|
||||||
|
self.rotTri += 0.2
|
||||||
|
self.rotQuad -= 0.15
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@main struct Program: AppRunner
|
||||||
|
{
|
||||||
|
typealias Delegate = Lesson5
|
||||||
|
static let config = AppConfig(
|
||||||
|
title: "NeHe's Solid Object Tutorial",
|
||||||
|
width: 640,
|
||||||
|
height: 480,
|
||||||
|
createDepthBuffer: SDL_GPU_TEXTUREFORMAT_D16_UNORM,
|
||||||
|
bundle: Bundle.module)
|
||||||
|
}
|
||||||
244
src/swift/Lesson6/lesson6.swift
Normal file
244
src/swift/Lesson6/lesson6.swift
Normal file
@@ -0,0 +1,244 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: (C) 2025 a dinosaur
|
||||||
|
* SPDX-License-Identifier: Zlib
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import SDLSwift
|
||||||
|
import NeHe
|
||||||
|
import simd
|
||||||
|
|
||||||
|
struct Lesson6: AppDelegate
|
||||||
|
{
|
||||||
|
struct Vertex
|
||||||
|
{
|
||||||
|
let position: SIMD3<Float>, texcoord: SIMD2<Float>
|
||||||
|
|
||||||
|
init(_ position: SIMD3<Float>, _ texcoord: SIMD2<Float>)
|
||||||
|
{
|
||||||
|
self.position = position
|
||||||
|
self.texcoord = texcoord
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static let vertices =
|
||||||
|
[
|
||||||
|
// Front Face
|
||||||
|
Vertex(.init(-1.0, -1.0, 1.0), .init(0.0, 0.0)),
|
||||||
|
Vertex(.init( 1.0, -1.0, 1.0), .init(1.0, 0.0)),
|
||||||
|
Vertex(.init( 1.0, 1.0, 1.0), .init(1.0, 1.0)),
|
||||||
|
Vertex(.init(-1.0, 1.0, 1.0), .init(0.0, 1.0)),
|
||||||
|
// Back Face
|
||||||
|
Vertex(.init(-1.0, -1.0, -1.0), .init(1.0, 0.0)),
|
||||||
|
Vertex(.init(-1.0, 1.0, -1.0), .init(1.0, 1.0)),
|
||||||
|
Vertex(.init( 1.0, 1.0, -1.0), .init(0.0, 1.0)),
|
||||||
|
Vertex(.init( 1.0, -1.0, -1.0), .init(0.0, 0.0)),
|
||||||
|
// Top Face
|
||||||
|
Vertex(.init(-1.0, 1.0, -1.0), .init(0.0, 1.0)),
|
||||||
|
Vertex(.init(-1.0, 1.0, 1.0), .init(0.0, 0.0)),
|
||||||
|
Vertex(.init( 1.0, 1.0, 1.0), .init(1.0, 0.0)),
|
||||||
|
Vertex(.init( 1.0, 1.0, -1.0), .init(1.0, 1.0)),
|
||||||
|
// Bottom Face
|
||||||
|
Vertex(.init(-1.0, -1.0, -1.0), .init(1.0, 1.0)),
|
||||||
|
Vertex(.init( 1.0, -1.0, -1.0), .init(0.0, 1.0)),
|
||||||
|
Vertex(.init( 1.0, -1.0, 1.0), .init(0.0, 0.0)),
|
||||||
|
Vertex(.init(-1.0, -1.0, 1.0), .init(1.0, 0.0)),
|
||||||
|
// Right face
|
||||||
|
Vertex(.init( 1.0, -1.0, -1.0), .init(1.0, 0.0)),
|
||||||
|
Vertex(.init( 1.0, 1.0, -1.0), .init(1.0, 1.0)),
|
||||||
|
Vertex(.init( 1.0, 1.0, 1.0), .init(0.0, 1.0)),
|
||||||
|
Vertex(.init( 1.0, -1.0, 1.0), .init(0.0, 0.0)),
|
||||||
|
// Left Face
|
||||||
|
Vertex(.init(-1.0, -1.0, -1.0), .init(0.0, 0.0)),
|
||||||
|
Vertex(.init(-1.0, -1.0, 1.0), .init(1.0, 0.0)),
|
||||||
|
Vertex(.init(-1.0, 1.0, 1.0), .init(1.0, 1.0)),
|
||||||
|
Vertex(.init(-1.0, 1.0, -1.0), .init(0.0, 1.0)),
|
||||||
|
]
|
||||||
|
|
||||||
|
static let indices: [UInt16] =
|
||||||
|
[
|
||||||
|
0, 1, 2, 2, 3, 0, // Front
|
||||||
|
4, 5, 6, 6, 7, 4, // Back
|
||||||
|
8, 9, 10, 10, 11, 8, // Top
|
||||||
|
12, 13, 14, 14, 15, 12, // Bottom
|
||||||
|
16, 17, 18, 18, 19, 16, // Right
|
||||||
|
20, 21, 22, 22, 23, 20 // Left
|
||||||
|
]
|
||||||
|
|
||||||
|
var pso: OpaquePointer? = nil
|
||||||
|
var vtxBuffer: OpaquePointer? = nil
|
||||||
|
var idxBuffer: OpaquePointer? = nil
|
||||||
|
var sampler: OpaquePointer? = nil
|
||||||
|
var texture: OpaquePointer? = nil
|
||||||
|
var projection: matrix_float4x4 = .init(1.0)
|
||||||
|
|
||||||
|
var rot: SIMD3<Float> = .init(repeating: 0.0)
|
||||||
|
|
||||||
|
mutating func `init`(ctx: inout NeHeContext) throws(NeHeError)
|
||||||
|
{
|
||||||
|
let (vertexShader, fragmentShader) = try ctx.loadShaders(name: "lesson6",
|
||||||
|
vertexUniforms: 1, fragmentSamplers: 1)
|
||||||
|
defer
|
||||||
|
{
|
||||||
|
SDL_ReleaseGPUShader(ctx.device, fragmentShader)
|
||||||
|
SDL_ReleaseGPUShader(ctx.device, vertexShader)
|
||||||
|
}
|
||||||
|
|
||||||
|
let vertexDescriptions: [SDL_GPUVertexBufferDescription] =
|
||||||
|
[
|
||||||
|
SDL_GPUVertexBufferDescription(
|
||||||
|
slot: 0,
|
||||||
|
pitch: UInt32(MemoryLayout<Vertex>.stride),
|
||||||
|
input_rate: SDL_GPU_VERTEXINPUTRATE_VERTEX,
|
||||||
|
instance_step_rate: 0),
|
||||||
|
]
|
||||||
|
let vertexAttributes: [SDL_GPUVertexAttribute] =
|
||||||
|
[
|
||||||
|
SDL_GPUVertexAttribute(
|
||||||
|
location: 0,
|
||||||
|
buffer_slot: 0,
|
||||||
|
format: SDL_GPU_VERTEXELEMENTFORMAT_FLOAT3,
|
||||||
|
offset: UInt32(MemoryLayout<Vertex>.offset(of: \.position)!)),
|
||||||
|
SDL_GPUVertexAttribute(
|
||||||
|
location: 1,
|
||||||
|
buffer_slot: 0,
|
||||||
|
format: SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2,
|
||||||
|
offset: UInt32(MemoryLayout<Vertex>.offset(of: \.texcoord)!)),
|
||||||
|
]
|
||||||
|
let colourTargets: [SDL_GPUColorTargetDescription] =
|
||||||
|
[
|
||||||
|
SDL_GPUColorTargetDescription(
|
||||||
|
format: SDL_GetGPUSwapchainTextureFormat(ctx.device, ctx.window),
|
||||||
|
blend_state: SDL_GPUColorTargetBlendState())
|
||||||
|
]
|
||||||
|
var rasterizerDesc = SDL_GPURasterizerState()
|
||||||
|
rasterizerDesc.fill_mode = SDL_GPU_FILLMODE_FILL
|
||||||
|
rasterizerDesc.cull_mode = SDL_GPU_CULLMODE_NONE
|
||||||
|
rasterizerDesc.front_face = SDL_GPU_FRONTFACE_COUNTER_CLOCKWISE
|
||||||
|
var depthStencilState = SDL_GPUDepthStencilState()
|
||||||
|
depthStencilState.compare_op = SDL_GPU_COMPAREOP_LESS_OR_EQUAL
|
||||||
|
depthStencilState.enable_depth_test = true
|
||||||
|
depthStencilState.enable_depth_write = true
|
||||||
|
var targetInfo = SDL_GPUGraphicsPipelineTargetInfo()
|
||||||
|
targetInfo.color_target_descriptions = colourTargets.withUnsafeBufferPointer(\.baseAddress!)
|
||||||
|
targetInfo.num_color_targets = UInt32(colourTargets.count)
|
||||||
|
targetInfo.depth_stencil_format = SDL_GPU_TEXTUREFORMAT_D16_UNORM
|
||||||
|
targetInfo.has_depth_stencil_target = true
|
||||||
|
|
||||||
|
var info = SDL_GPUGraphicsPipelineCreateInfo(
|
||||||
|
vertex_shader: vertexShader,
|
||||||
|
fragment_shader: fragmentShader,
|
||||||
|
vertex_input_state: SDL_GPUVertexInputState(
|
||||||
|
vertex_buffer_descriptions: vertexDescriptions.withUnsafeBufferPointer(\.baseAddress!),
|
||||||
|
num_vertex_buffers: UInt32(vertexDescriptions.count),
|
||||||
|
vertex_attributes: vertexAttributes.withUnsafeBufferPointer(\.baseAddress!),
|
||||||
|
num_vertex_attributes: UInt32(vertexAttributes.count)),
|
||||||
|
primitive_type: SDL_GPU_PRIMITIVETYPE_TRIANGLELIST,
|
||||||
|
rasterizer_state: rasterizerDesc,
|
||||||
|
multisample_state: SDL_GPUMultisampleState(),
|
||||||
|
depth_stencil_state: depthStencilState,
|
||||||
|
target_info: targetInfo,
|
||||||
|
props: 0)
|
||||||
|
guard let pso = SDL_CreateGPUGraphicsPipeline(ctx.device, &info) else
|
||||||
|
{
|
||||||
|
throw .sdlError("SDL_CreateGPUGraphicsPipeline", String(cString: SDL_GetError()))
|
||||||
|
}
|
||||||
|
self.pso = pso
|
||||||
|
|
||||||
|
var samplerInfo = SDL_GPUSamplerCreateInfo()
|
||||||
|
samplerInfo.min_filter = SDL_GPU_FILTER_LINEAR
|
||||||
|
samplerInfo.mag_filter = SDL_GPU_FILTER_LINEAR
|
||||||
|
guard let sampler = SDL_CreateGPUSampler(ctx.device, &samplerInfo) else
|
||||||
|
{
|
||||||
|
throw .sdlError("SDL_CreateGPUSampler", String(cString: SDL_GetError()))
|
||||||
|
}
|
||||||
|
self.sampler = sampler
|
||||||
|
|
||||||
|
try ctx.copyPass { (pass) throws(NeHeError) in
|
||||||
|
self.texture = try pass.createTextureFrom(bmpResource: "NeHe", flip: true)
|
||||||
|
self.vtxBuffer = try pass.createBuffer(usage: SDL_GPU_BUFFERUSAGE_VERTEX, Self.vertices[...])
|
||||||
|
self.idxBuffer = try pass.createBuffer(usage: SDL_GPU_BUFFERUSAGE_INDEX, Self.indices[...])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func quit(ctx: NeHeContext)
|
||||||
|
{
|
||||||
|
SDL_ReleaseGPUBuffer(ctx.device, self.idxBuffer)
|
||||||
|
SDL_ReleaseGPUBuffer(ctx.device, self.vtxBuffer)
|
||||||
|
SDL_ReleaseGPUTexture(ctx.device, self.texture)
|
||||||
|
SDL_ReleaseGPUSampler(ctx.device, self.sampler)
|
||||||
|
SDL_ReleaseGPUGraphicsPipeline(ctx.device, self.pso)
|
||||||
|
}
|
||||||
|
|
||||||
|
mutating func resize(size: Size<Int32>)
|
||||||
|
{
|
||||||
|
let aspect = Float(size.width) / Float(max(1, size.height))
|
||||||
|
self.projection = .perspective(fovy: 45, aspect: aspect, near: 0.1, far: 100)
|
||||||
|
}
|
||||||
|
|
||||||
|
mutating func draw(ctx: inout NeHeContext,
|
||||||
|
cmd: OpaquePointer, swapchain: OpaquePointer, swapchainSize: Size<UInt32>) throws(NeHeError)
|
||||||
|
{
|
||||||
|
var colorInfo = SDL_GPUColorTargetInfo()
|
||||||
|
colorInfo.texture = swapchain
|
||||||
|
colorInfo.clear_color = SDL_FColor(r: 0.0, g: 0.0, b: 0.0, a: 0.5)
|
||||||
|
colorInfo.load_op = SDL_GPU_LOADOP_CLEAR
|
||||||
|
colorInfo.store_op = SDL_GPU_STOREOP_STORE
|
||||||
|
|
||||||
|
var depthInfo = SDL_GPUDepthStencilTargetInfo()
|
||||||
|
depthInfo.texture = ctx.depthTexture
|
||||||
|
depthInfo.clear_depth = 1.0
|
||||||
|
depthInfo.load_op = SDL_GPU_LOADOP_CLEAR
|
||||||
|
depthInfo.store_op = SDL_GPU_STOREOP_DONT_CARE
|
||||||
|
depthInfo.stencil_load_op = SDL_GPU_LOADOP_DONT_CARE
|
||||||
|
depthInfo.stencil_store_op = SDL_GPU_STOREOP_DONT_CARE
|
||||||
|
depthInfo.cycle = true
|
||||||
|
|
||||||
|
// Begin pass & bind pipeline state
|
||||||
|
let pass = SDL_BeginGPURenderPass(cmd, &colorInfo, 1, &depthInfo);
|
||||||
|
SDL_BindGPUGraphicsPipeline(pass, self.pso)
|
||||||
|
|
||||||
|
// Bind texture
|
||||||
|
var textureBinding = SDL_GPUTextureSamplerBinding(texture: self.texture, sampler: self.sampler)
|
||||||
|
SDL_BindGPUFragmentSamplers(pass, 0, &textureBinding, 1)
|
||||||
|
|
||||||
|
// Bind vertex & index buffers
|
||||||
|
let vtxBindings = [ SDL_GPUBufferBinding(buffer: self.vtxBuffer, offset: 0) ]
|
||||||
|
var idxBinding = SDL_GPUBufferBinding(buffer: self.idxBuffer, offset: 0)
|
||||||
|
SDL_BindGPUVertexBuffers(pass, 0,
|
||||||
|
vtxBindings.withUnsafeBufferPointer(\.baseAddress!), UInt32(vtxBindings.count))
|
||||||
|
SDL_BindGPUIndexBuffer(pass, &idxBinding, SDL_GPU_INDEXELEMENTSIZE_16BIT)
|
||||||
|
|
||||||
|
struct Uniforms { var modelViewProj: simd_float4x4, color: SIMD4<Float> }
|
||||||
|
|
||||||
|
// Move cube 5 units into the screen and apply some rotations
|
||||||
|
var model: simd_float4x4 = .translation(.init(0.0, 0.0, -5.0))
|
||||||
|
model.rotate(angle: rot.x, axis: .init(1.0, 0.0, 0.0))
|
||||||
|
model.rotate(angle: rot.y, axis: .init(0.0, 1.0, 0.0))
|
||||||
|
model.rotate(angle: rot.z, axis: .init(0.0, 0.0, 1.0))
|
||||||
|
|
||||||
|
// Push shader uniforms
|
||||||
|
var u = Uniforms(
|
||||||
|
modelViewProj: self.projection * model,
|
||||||
|
color: .init(repeating: 1.0))
|
||||||
|
SDL_PushGPUVertexUniformData(cmd, 0, &u, UInt32(MemoryLayout<Uniforms>.size))
|
||||||
|
|
||||||
|
// Draw textured cube
|
||||||
|
SDL_DrawGPUIndexedPrimitives(pass, UInt32(Self.indices.count), 1, 0, 0, 0)
|
||||||
|
|
||||||
|
SDL_EndGPURenderPass(pass);
|
||||||
|
|
||||||
|
self.rot += .init(0.3, 0.2, 0.4)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@main struct Program: AppRunner
|
||||||
|
{
|
||||||
|
typealias Delegate = Lesson6
|
||||||
|
static let config = AppConfig(
|
||||||
|
title: "NeHe's Texture Mapping Tutorial",
|
||||||
|
width: 640,
|
||||||
|
height: 480,
|
||||||
|
createDepthBuffer: SDL_GPU_TEXTUREFORMAT_D16_UNORM,
|
||||||
|
bundle: Bundle.module)
|
||||||
|
}
|
||||||
323
src/swift/Lesson7/lesson7.swift
Normal file
323
src/swift/Lesson7/lesson7.swift
Normal file
@@ -0,0 +1,323 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: (C) 2025 a dinosaur
|
||||||
|
* SPDX-License-Identifier: Zlib
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import SDLSwift
|
||||||
|
import NeHe
|
||||||
|
import simd
|
||||||
|
|
||||||
|
struct Lesson7: AppDelegate
|
||||||
|
{
|
||||||
|
struct Vertex
|
||||||
|
{
|
||||||
|
let position: SIMD3<Float>, normal: SIMD3<Float>, texcoord: SIMD2<Float>
|
||||||
|
|
||||||
|
init(_ position: SIMD3<Float>, _ normal: SIMD3<Float>, _ texcoord: SIMD2<Float>)
|
||||||
|
{
|
||||||
|
self.position = position
|
||||||
|
self.normal = normal
|
||||||
|
self.texcoord = texcoord
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static let vertices =
|
||||||
|
[
|
||||||
|
// Front Face
|
||||||
|
Vertex(.init(-1.0, -1.0, 1.0), .init( 0.0, 0.0, 1.0), .init(0.0, 0.0)),
|
||||||
|
Vertex(.init( 1.0, -1.0, 1.0), .init( 0.0, 0.0, 1.0), .init(1.0, 0.0)),
|
||||||
|
Vertex(.init( 1.0, 1.0, 1.0), .init( 0.0, 0.0, 1.0), .init(1.0, 1.0)),
|
||||||
|
Vertex(.init(-1.0, 1.0, 1.0), .init( 0.0, 0.0, 1.0), .init(0.0, 1.0)),
|
||||||
|
// Back Face
|
||||||
|
Vertex(.init(-1.0, -1.0, -1.0), .init( 0.0, 0.0, -1.0), .init(1.0, 0.0)),
|
||||||
|
Vertex(.init(-1.0, 1.0, -1.0), .init( 0.0, 0.0, -1.0), .init(1.0, 1.0)),
|
||||||
|
Vertex(.init( 1.0, 1.0, -1.0), .init( 0.0, 0.0, -1.0), .init(0.0, 1.0)),
|
||||||
|
Vertex(.init( 1.0, -1.0, -1.0), .init( 0.0, 0.0, -1.0), .init(0.0, 0.0)),
|
||||||
|
// Top Face
|
||||||
|
Vertex(.init(-1.0, 1.0, -1.0), .init( 0.0, 1.0, 0.0), .init(0.0, 1.0)),
|
||||||
|
Vertex(.init(-1.0, 1.0, 1.0), .init( 0.0, 1.0, 0.0), .init(0.0, 0.0)),
|
||||||
|
Vertex(.init( 1.0, 1.0, 1.0), .init( 0.0, 1.0, 0.0), .init(1.0, 0.0)),
|
||||||
|
Vertex(.init( 1.0, 1.0, -1.0), .init( 0.0, 1.0, 0.0), .init(1.0, 1.0)),
|
||||||
|
// Bottom Face
|
||||||
|
Vertex(.init(-1.0, -1.0, -1.0), .init( 0.0, -1.0, 0.0), .init(1.0, 1.0)),
|
||||||
|
Vertex(.init( 1.0, -1.0, -1.0), .init( 0.0, -1.0, 0.0), .init(0.0, 1.0)),
|
||||||
|
Vertex(.init( 1.0, -1.0, 1.0), .init( 0.0, -1.0, 0.0), .init(0.0, 0.0)),
|
||||||
|
Vertex(.init(-1.0, -1.0, 1.0), .init( 0.0, -1.0, 0.0), .init(1.0, 0.0)),
|
||||||
|
// Right face
|
||||||
|
Vertex(.init( 1.0, -1.0, -1.0), .init( 1.0, 0.0, 0.0), .init(1.0, 0.0)),
|
||||||
|
Vertex(.init( 1.0, 1.0, -1.0), .init( 1.0, 0.0, 0.0), .init(1.0, 1.0)),
|
||||||
|
Vertex(.init( 1.0, 1.0, 1.0), .init( 1.0, 0.0, 0.0), .init(0.0, 1.0)),
|
||||||
|
Vertex(.init( 1.0, -1.0, 1.0), .init( 1.0, 0.0, 0.0), .init(0.0, 0.0)),
|
||||||
|
// Left Face
|
||||||
|
Vertex(.init(-1.0, -1.0, -1.0), .init(-1.0, 0.0, 0.0), .init(0.0, 0.0)),
|
||||||
|
Vertex(.init(-1.0, -1.0, 1.0), .init(-1.0, 0.0, 0.0), .init(1.0, 0.0)),
|
||||||
|
Vertex(.init(-1.0, 1.0, 1.0), .init(-1.0, 0.0, 0.0), .init(1.0, 1.0)),
|
||||||
|
Vertex(.init(-1.0, 1.0, -1.0), .init(-1.0, 0.0, 0.0), .init(0.0, 1.0)),
|
||||||
|
]
|
||||||
|
|
||||||
|
static let indices: [UInt16] =
|
||||||
|
[
|
||||||
|
0, 1, 2, 2, 3, 0, // Front
|
||||||
|
4, 5, 6, 6, 7, 4, // Back
|
||||||
|
8, 9, 10, 10, 11, 8, // Top
|
||||||
|
12, 13, 14, 14, 15, 12, // Bottom
|
||||||
|
16, 17, 18, 18, 19, 16, // Right
|
||||||
|
20, 21, 22, 22, 23, 20 // Left
|
||||||
|
]
|
||||||
|
|
||||||
|
var psoUnlit: OpaquePointer? = nil
|
||||||
|
var psoLight: OpaquePointer? = nil
|
||||||
|
var vtxBuffer: OpaquePointer? = nil
|
||||||
|
var idxBuffer: OpaquePointer? = nil
|
||||||
|
var samplers = [OpaquePointer?](repeating: nil, count: 3)
|
||||||
|
var texture: OpaquePointer? = nil
|
||||||
|
var projection: matrix_float4x4 = .init(1.0)
|
||||||
|
|
||||||
|
struct Light { let ambient: SIMD4<Float>, diffuse: SIMD4<Float>, position: SIMD4<Float> }
|
||||||
|
|
||||||
|
var lighting = false
|
||||||
|
var light = Light(
|
||||||
|
ambient: .init(0.5, 0.5, 0.5, 1.0),
|
||||||
|
diffuse: .init(1.0, 1.0, 1.0, 1.0),
|
||||||
|
position: .init(0.0, 0.0, 2.0, 1.0))
|
||||||
|
var filter = 0
|
||||||
|
|
||||||
|
var rot = SIMD2<Float>(repeating: 0.0)
|
||||||
|
var speed = SIMD2<Float>(repeating: 0.0)
|
||||||
|
var z: Float = -5.0
|
||||||
|
|
||||||
|
mutating func `init`(ctx: inout NeHeContext) throws(NeHeError)
|
||||||
|
{
|
||||||
|
let (vertexShaderUnlit, fragmentShaderUnlit) = try ctx.loadShaders(name: "lesson6",
|
||||||
|
vertexUniforms: 1, fragmentSamplers: 1)
|
||||||
|
defer
|
||||||
|
{
|
||||||
|
SDL_ReleaseGPUShader(ctx.device, fragmentShaderUnlit)
|
||||||
|
SDL_ReleaseGPUShader(ctx.device, vertexShaderUnlit)
|
||||||
|
}
|
||||||
|
let (vertexShaderLight, fragmentShaderLight) = try ctx.loadShaders(name: "lesson7",
|
||||||
|
vertexUniforms: 2, fragmentSamplers: 1)
|
||||||
|
defer
|
||||||
|
{
|
||||||
|
SDL_ReleaseGPUShader(ctx.device, fragmentShaderLight)
|
||||||
|
SDL_ReleaseGPUShader(ctx.device, vertexShaderLight)
|
||||||
|
}
|
||||||
|
|
||||||
|
let vertexDescriptions: [SDL_GPUVertexBufferDescription] =
|
||||||
|
[
|
||||||
|
SDL_GPUVertexBufferDescription(
|
||||||
|
slot: 0,
|
||||||
|
pitch: UInt32(MemoryLayout<Vertex>.stride),
|
||||||
|
input_rate: SDL_GPU_VERTEXINPUTRATE_VERTEX,
|
||||||
|
instance_step_rate: 0),
|
||||||
|
]
|
||||||
|
let vertexAttributes: [SDL_GPUVertexAttribute] =
|
||||||
|
[
|
||||||
|
SDL_GPUVertexAttribute(
|
||||||
|
location: 0,
|
||||||
|
buffer_slot: 0,
|
||||||
|
format: SDL_GPU_VERTEXELEMENTFORMAT_FLOAT3,
|
||||||
|
offset: UInt32(MemoryLayout<Vertex>.offset(of: \.position)!)),
|
||||||
|
SDL_GPUVertexAttribute(
|
||||||
|
location: 2,
|
||||||
|
buffer_slot: 0,
|
||||||
|
format: SDL_GPU_VERTEXELEMENTFORMAT_FLOAT3,
|
||||||
|
offset: UInt32(MemoryLayout<Vertex>.offset(of: \.normal)!)),
|
||||||
|
SDL_GPUVertexAttribute(
|
||||||
|
location: 1,
|
||||||
|
buffer_slot: 0,
|
||||||
|
format: SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2,
|
||||||
|
offset: UInt32(MemoryLayout<Vertex>.offset(of: \.texcoord)!)),
|
||||||
|
]
|
||||||
|
let colourTargets: [SDL_GPUColorTargetDescription] =
|
||||||
|
[
|
||||||
|
SDL_GPUColorTargetDescription(
|
||||||
|
format: SDL_GetGPUSwapchainTextureFormat(ctx.device, ctx.window),
|
||||||
|
blend_state: SDL_GPUColorTargetBlendState())
|
||||||
|
]
|
||||||
|
var rasterizerDesc = SDL_GPURasterizerState()
|
||||||
|
rasterizerDesc.fill_mode = SDL_GPU_FILLMODE_FILL
|
||||||
|
rasterizerDesc.cull_mode = SDL_GPU_CULLMODE_NONE
|
||||||
|
rasterizerDesc.front_face = SDL_GPU_FRONTFACE_COUNTER_CLOCKWISE
|
||||||
|
var depthStencilState = SDL_GPUDepthStencilState()
|
||||||
|
depthStencilState.compare_op = SDL_GPU_COMPAREOP_LESS_OR_EQUAL
|
||||||
|
depthStencilState.enable_depth_test = true
|
||||||
|
depthStencilState.enable_depth_write = true
|
||||||
|
var targetInfo = SDL_GPUGraphicsPipelineTargetInfo()
|
||||||
|
targetInfo.color_target_descriptions = colourTargets.withUnsafeBufferPointer(\.baseAddress!)
|
||||||
|
targetInfo.num_color_targets = UInt32(colourTargets.count)
|
||||||
|
targetInfo.depth_stencil_format = SDL_GPU_TEXTUREFORMAT_D16_UNORM
|
||||||
|
targetInfo.has_depth_stencil_target = true
|
||||||
|
|
||||||
|
var info = SDL_GPUGraphicsPipelineCreateInfo()
|
||||||
|
info.vertex_input_state = SDL_GPUVertexInputState(
|
||||||
|
vertex_buffer_descriptions: vertexDescriptions.withUnsafeBufferPointer(\.baseAddress!),
|
||||||
|
num_vertex_buffers: UInt32(vertexDescriptions.count),
|
||||||
|
vertex_attributes: vertexAttributes.withUnsafeBufferPointer(\.baseAddress!),
|
||||||
|
num_vertex_attributes: UInt32(vertexAttributes.count))
|
||||||
|
info.primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST
|
||||||
|
info.rasterizer_state = rasterizerDesc
|
||||||
|
info.depth_stencil_state = depthStencilState
|
||||||
|
info.target_info = targetInfo
|
||||||
|
|
||||||
|
// Create unlit pipeline
|
||||||
|
info.vertex_shader = vertexShaderUnlit
|
||||||
|
info.fragment_shader = fragmentShaderUnlit
|
||||||
|
guard let psoUnlit = SDL_CreateGPUGraphicsPipeline(ctx.device, &info) else
|
||||||
|
{
|
||||||
|
throw .sdlError("SDL_CreateGPUGraphicsPipeline", String(cString: SDL_GetError()))
|
||||||
|
}
|
||||||
|
self.psoUnlit = psoUnlit
|
||||||
|
|
||||||
|
// Create lit pipeline
|
||||||
|
info.vertex_shader = vertexShaderLight
|
||||||
|
info.fragment_shader = fragmentShaderLight
|
||||||
|
guard let psoLight = SDL_CreateGPUGraphicsPipeline(ctx.device, &info) else
|
||||||
|
{
|
||||||
|
throw .sdlError("SDL_CreateGPUGraphicsPipeline", String(cString: SDL_GetError()))
|
||||||
|
}
|
||||||
|
self.psoLight = psoLight
|
||||||
|
|
||||||
|
func createSampler(
|
||||||
|
filter: SDL_GPUFilter,
|
||||||
|
mipMode: SDL_GPUSamplerMipmapMode = SDL_GPU_SAMPLERMIPMAPMODE_NEAREST,
|
||||||
|
maxLod: Float = 0.0) throws(NeHeError) -> OpaquePointer
|
||||||
|
{
|
||||||
|
var samplerInfo = SDL_GPUSamplerCreateInfo()
|
||||||
|
samplerInfo.min_filter = filter
|
||||||
|
samplerInfo.mag_filter = filter
|
||||||
|
samplerInfo.mipmap_mode = mipMode
|
||||||
|
samplerInfo.max_lod = maxLod
|
||||||
|
guard let sampler = SDL_CreateGPUSampler(ctx.device, &samplerInfo) else
|
||||||
|
{
|
||||||
|
throw .sdlError("SDL_CreateGPUSampler", String(cString: SDL_GetError()))
|
||||||
|
}
|
||||||
|
return sampler
|
||||||
|
}
|
||||||
|
self.samplers[0] = try createSampler(filter: SDL_GPU_FILTER_NEAREST)
|
||||||
|
self.samplers[1] = try createSampler(filter: SDL_GPU_FILTER_LINEAR)
|
||||||
|
self.samplers[2] = try createSampler(filter: SDL_GPU_FILTER_LINEAR, maxLod: .greatestFiniteMagnitude)
|
||||||
|
|
||||||
|
try ctx.copyPass { (pass) throws(NeHeError) in
|
||||||
|
self.texture = try pass.createTextureFrom(bmpResource: "Crate", flip: true, genMipmaps: true)
|
||||||
|
self.vtxBuffer = try pass.createBuffer(usage: SDL_GPU_BUFFERUSAGE_VERTEX, Self.vertices[...])
|
||||||
|
self.idxBuffer = try pass.createBuffer(usage: SDL_GPU_BUFFERUSAGE_INDEX, Self.indices[...])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func quit(ctx: NeHeContext)
|
||||||
|
{
|
||||||
|
SDL_ReleaseGPUBuffer(ctx.device, self.idxBuffer)
|
||||||
|
SDL_ReleaseGPUBuffer(ctx.device, self.vtxBuffer)
|
||||||
|
SDL_ReleaseGPUTexture(ctx.device, self.texture)
|
||||||
|
self.samplers.reversed().forEach { SDL_ReleaseGPUSampler(ctx.device, $0) }
|
||||||
|
SDL_ReleaseGPUGraphicsPipeline(ctx.device, self.psoLight)
|
||||||
|
SDL_ReleaseGPUGraphicsPipeline(ctx.device, self.psoUnlit)
|
||||||
|
}
|
||||||
|
|
||||||
|
mutating func resize(size: Size<Int32>)
|
||||||
|
{
|
||||||
|
let aspect = Float(size.width) / Float(max(1, size.height))
|
||||||
|
self.projection = .perspective(fovy: 45, aspect: aspect, near: 0.1, far: 100)
|
||||||
|
}
|
||||||
|
|
||||||
|
mutating func draw(ctx: inout NeHeContext,
|
||||||
|
cmd: OpaquePointer, swapchain: OpaquePointer, swapchainSize: Size<UInt32>) throws(NeHeError)
|
||||||
|
{
|
||||||
|
var colorInfo = SDL_GPUColorTargetInfo()
|
||||||
|
colorInfo.texture = swapchain
|
||||||
|
colorInfo.clear_color = SDL_FColor(r: 0.0, g: 0.0, b: 0.0, a: 0.5)
|
||||||
|
colorInfo.load_op = SDL_GPU_LOADOP_CLEAR
|
||||||
|
colorInfo.store_op = SDL_GPU_STOREOP_STORE
|
||||||
|
|
||||||
|
var depthInfo = SDL_GPUDepthStencilTargetInfo()
|
||||||
|
depthInfo.texture = ctx.depthTexture
|
||||||
|
depthInfo.clear_depth = 1.0
|
||||||
|
depthInfo.load_op = SDL_GPU_LOADOP_CLEAR
|
||||||
|
depthInfo.store_op = SDL_GPU_STOREOP_DONT_CARE
|
||||||
|
depthInfo.stencil_load_op = SDL_GPU_LOADOP_DONT_CARE
|
||||||
|
depthInfo.stencil_store_op = SDL_GPU_STOREOP_DONT_CARE
|
||||||
|
depthInfo.cycle = true
|
||||||
|
|
||||||
|
// Begin pass & bind pipeline state
|
||||||
|
let pass = SDL_BeginGPURenderPass(cmd, &colorInfo, 1, &depthInfo);
|
||||||
|
SDL_BindGPUGraphicsPipeline(pass, self.lighting ? self.psoLight : self.psoUnlit)
|
||||||
|
|
||||||
|
// Bind texture
|
||||||
|
var textureBinding = SDL_GPUTextureSamplerBinding(texture: self.texture, sampler: self.samplers[self.filter])
|
||||||
|
SDL_BindGPUFragmentSamplers(pass, 0, &textureBinding, 1)
|
||||||
|
|
||||||
|
// Bind vertex & index buffers
|
||||||
|
let vtxBindings = [ SDL_GPUBufferBinding(buffer: self.vtxBuffer, offset: 0) ]
|
||||||
|
var idxBinding = SDL_GPUBufferBinding(buffer: self.idxBuffer, offset: 0)
|
||||||
|
SDL_BindGPUVertexBuffers(pass, 0,
|
||||||
|
vtxBindings.withUnsafeBufferPointer(\.baseAddress!), UInt32(vtxBindings.count))
|
||||||
|
SDL_BindGPUIndexBuffer(pass, &idxBinding, SDL_GPU_INDEXELEMENTSIZE_16BIT)
|
||||||
|
|
||||||
|
// Setup the cube's model matrix
|
||||||
|
var model: simd_float4x4 = .translation(.init(0.0, 0.0, self.z))
|
||||||
|
model.rotate(angle: rot.x, axis: .init(1.0, 0.0, 0.0))
|
||||||
|
model.rotate(angle: rot.y, axis: .init(0.0, 1.0, 0.0))
|
||||||
|
|
||||||
|
// Push shader uniforms
|
||||||
|
if self.lighting
|
||||||
|
{
|
||||||
|
struct Uniforms { var model: simd_float4x4, projection: simd_float4x4 }
|
||||||
|
var u = Uniforms(
|
||||||
|
model: model,
|
||||||
|
projection: self.projection)
|
||||||
|
SDL_PushGPUVertexUniformData(cmd, 0, &u, UInt32(MemoryLayout<Uniforms>.size))
|
||||||
|
SDL_PushGPUVertexUniformData(cmd, 1, &self.light, UInt32(MemoryLayout<Light>.size))
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
struct Uniforms { var modelViewProj: simd_float4x4, color: SIMD4<Float> }
|
||||||
|
var u = Uniforms(
|
||||||
|
modelViewProj: self.projection * model,
|
||||||
|
color: .init(repeating: 1.0))
|
||||||
|
SDL_PushGPUVertexUniformData(cmd, 0, &u, UInt32(MemoryLayout<Uniforms>.size))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw textured cube
|
||||||
|
SDL_DrawGPUIndexedPrimitives(pass, UInt32(Self.indices.count), 1, 0, 0, 0)
|
||||||
|
|
||||||
|
SDL_EndGPURenderPass(pass);
|
||||||
|
|
||||||
|
let keys = SDL_GetKeyboardState(nil)!
|
||||||
|
|
||||||
|
if keys[Int(SDL_SCANCODE_PAGEUP.rawValue)] { self.z -= 0.02 }
|
||||||
|
if keys[Int(SDL_SCANCODE_PAGEDOWN.rawValue)] { self.z += 0.02 }
|
||||||
|
if keys[Int(SDL_SCANCODE_UP.rawValue)] { speed.x -= 0.01 }
|
||||||
|
if keys[Int(SDL_SCANCODE_DOWN.rawValue)] { speed.x += 0.01 }
|
||||||
|
if keys[Int(SDL_SCANCODE_RIGHT.rawValue)] { speed.y += 0.1 }
|
||||||
|
if keys[Int(SDL_SCANCODE_LEFT.rawValue)] { speed.y -= 0.1 }
|
||||||
|
|
||||||
|
self.rot += self.speed
|
||||||
|
}
|
||||||
|
|
||||||
|
mutating func key(ctx: inout NeHeContext, key: SDL_Keycode, down: Bool, repeat: Bool)
|
||||||
|
{
|
||||||
|
guard down && !`repeat` else { return }
|
||||||
|
switch key
|
||||||
|
{
|
||||||
|
case SDLK_L:
|
||||||
|
self.lighting = !self.lighting
|
||||||
|
case SDLK_F:
|
||||||
|
self.filter = (self.filter + 1) % self.samplers.count
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@main struct Program: AppRunner
|
||||||
|
{
|
||||||
|
typealias Delegate = Lesson7
|
||||||
|
static let config = AppConfig(
|
||||||
|
title: "NeHe's Textures, Lighting & Keyboard Tutorial",
|
||||||
|
width: 640,
|
||||||
|
height: 480,
|
||||||
|
createDepthBuffer: SDL_GPU_TEXTUREFORMAT_D16_UNORM,
|
||||||
|
bundle: Bundle.module)
|
||||||
|
}
|
||||||
28
src/swift/NeHe/Application/AppConfig.swift
Normal file
28
src/swift/NeHe/Application/AppConfig.swift
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: (C) 2025 a dinosaur
|
||||||
|
* SPDX-License-Identifier: Zlib
|
||||||
|
*/
|
||||||
|
|
||||||
|
import SDLSwift
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public struct AppConfig: Sendable
|
||||||
|
{
|
||||||
|
let title: StaticString
|
||||||
|
let width: Int32
|
||||||
|
let height: Int32
|
||||||
|
let manageDepthFormat: SDL_GPUTextureFormat
|
||||||
|
|
||||||
|
let bundle: Bundle?
|
||||||
|
|
||||||
|
public init(title: StaticString, width: Int32, height: Int32,
|
||||||
|
createDepthBuffer depthFormat: SDL_GPUTextureFormat? = nil,
|
||||||
|
bundle: Bundle? = nil)
|
||||||
|
{
|
||||||
|
self.title = title
|
||||||
|
self.width = width
|
||||||
|
self.height = height
|
||||||
|
self.manageDepthFormat = depthFormat ?? SDL_GPU_TEXTUREFORMAT_INVALID
|
||||||
|
self.bundle = bundle
|
||||||
|
}
|
||||||
|
}
|
||||||
25
src/swift/NeHe/Application/AppDelegate.swift
Normal file
25
src/swift/NeHe/Application/AppDelegate.swift
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: (C) 2025 a dinosaur
|
||||||
|
* SPDX-License-Identifier: Zlib
|
||||||
|
*/
|
||||||
|
|
||||||
|
import SDLSwift
|
||||||
|
|
||||||
|
public protocol AppDelegate: ~Copyable
|
||||||
|
{
|
||||||
|
init()
|
||||||
|
mutating func `init`(ctx: inout NeHeContext) throws(NeHeError)
|
||||||
|
mutating func quit(ctx: NeHeContext)
|
||||||
|
mutating func resize(size: Size<Int32>)
|
||||||
|
mutating func draw(ctx: inout NeHeContext, cmd: OpaquePointer,
|
||||||
|
swapchain: OpaquePointer, swapchainSize: Size<UInt32>) throws(NeHeError)
|
||||||
|
mutating func key(ctx: inout NeHeContext, key: SDL_Keycode, down: Bool, repeat: Bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
public extension AppDelegate
|
||||||
|
{
|
||||||
|
mutating func `init`(ctx: inout NeHeContext) throws(NeHeError) {}
|
||||||
|
mutating func quit(ctx: NeHeContext) {}
|
||||||
|
mutating func resize(size: Size<Int32>) {}
|
||||||
|
mutating func key(ctx: inout NeHeContext, key: SDL_Keycode, down: Bool, repeat: Bool) {}
|
||||||
|
}
|
||||||
128
src/swift/NeHe/Application/AppRunner.swift
Normal file
128
src/swift/NeHe/Application/AppRunner.swift
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: (C) 2025 a dinosaur
|
||||||
|
* SPDX-License-Identifier: Zlib
|
||||||
|
*/
|
||||||
|
|
||||||
|
import SDLSwift
|
||||||
|
|
||||||
|
public protocol AppRunner
|
||||||
|
{
|
||||||
|
associatedtype Delegate: AppDelegate
|
||||||
|
static var config: AppConfig { get }
|
||||||
|
}
|
||||||
|
|
||||||
|
public extension AppRunner
|
||||||
|
{
|
||||||
|
static func main() throws -> Void
|
||||||
|
{
|
||||||
|
// Initialise SDL
|
||||||
|
guard SDL_Init(SDL_INIT_VIDEO) else
|
||||||
|
{
|
||||||
|
throw NeHeError.sdlError("SDL_Init", String(cString: SDL_GetError()))
|
||||||
|
}
|
||||||
|
defer { SDL_Quit() }
|
||||||
|
|
||||||
|
// Initialise GPU context
|
||||||
|
var ctx = try NeHeContext(
|
||||||
|
title: config.title,
|
||||||
|
width: config.width,
|
||||||
|
height: config.height,
|
||||||
|
bundle: config.bundle)
|
||||||
|
defer
|
||||||
|
{
|
||||||
|
SDL_ReleaseWindowFromGPUDevice(ctx.device, ctx.window)
|
||||||
|
SDL_DestroyGPUDevice(ctx.device)
|
||||||
|
SDL_DestroyWindow(ctx.window)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle depth buffer texture creation if requested
|
||||||
|
if config.manageDepthFormat != SDL_GPU_TEXTUREFORMAT_INVALID
|
||||||
|
{
|
||||||
|
var backbufWidth: Int32 = 0, backbufHeight: Int32 = 0
|
||||||
|
SDL_GetWindowSizeInPixels(ctx.window, &backbufWidth, &backbufHeight)
|
||||||
|
try ctx.setupDepthTexture(
|
||||||
|
size: .init(UInt32(backbufWidth), UInt32(backbufHeight)),
|
||||||
|
format: config.manageDepthFormat)
|
||||||
|
}
|
||||||
|
defer
|
||||||
|
{
|
||||||
|
if config.manageDepthFormat != SDL_GPU_TEXTUREFORMAT_INVALID,
|
||||||
|
let depthTexture = ctx.depthTexture
|
||||||
|
{
|
||||||
|
SDL_ReleaseGPUTexture(ctx.device, depthTexture)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialise app delegate
|
||||||
|
var app = Delegate()
|
||||||
|
try app.`init`(ctx: &ctx)
|
||||||
|
|
||||||
|
var fullscreen = false
|
||||||
|
|
||||||
|
// Enter main loop
|
||||||
|
quit: while true
|
||||||
|
{
|
||||||
|
// Process events
|
||||||
|
var event = SDL_Event()
|
||||||
|
while SDL_PollEvent(&event)
|
||||||
|
{
|
||||||
|
switch SDL_EventType(event.type)
|
||||||
|
{
|
||||||
|
case SDL_EVENT_QUIT:
|
||||||
|
break quit
|
||||||
|
case SDL_EVENT_WINDOW_ENTER_FULLSCREEN, SDL_EVENT_WINDOW_LEAVE_FULLSCREEN:
|
||||||
|
fullscreen = event.type == SDL_EVENT_WINDOW_ENTER_FULLSCREEN.rawValue
|
||||||
|
case SDL_EVENT_KEY_DOWN:
|
||||||
|
if event.key.key == SDLK_ESCAPE
|
||||||
|
{
|
||||||
|
break quit
|
||||||
|
}
|
||||||
|
if event.key.key == SDLK_F1
|
||||||
|
{
|
||||||
|
SDL_SetWindowFullscreen(ctx.window, !fullscreen)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
fallthrough
|
||||||
|
case SDL_EVENT_KEY_UP:
|
||||||
|
app.key(ctx: &ctx, key: event.key.key, down: event.key.down, repeat: event.key.repeat)
|
||||||
|
case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED:
|
||||||
|
app.resize(size: .init(width: event.window.data1, height: event.window.data2))
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let cmd = SDL_AcquireGPUCommandBuffer(ctx.device) else
|
||||||
|
{
|
||||||
|
throw NeHeError.sdlError("SDL_AcquireGPUCommandBuffer", String(cString: SDL_GetError()))
|
||||||
|
}
|
||||||
|
|
||||||
|
var swapchainTexture: OpaquePointer?, swapchainWidth: Int32 = 0, swapchainHeight: Int32 = 0
|
||||||
|
guard SDL_WaitAndAcquireGPUSwapchainTexture(cmd, ctx.window,
|
||||||
|
&swapchainTexture, &swapchainWidth, &swapchainHeight) else
|
||||||
|
{
|
||||||
|
let message = String(cString: SDL_GetError())
|
||||||
|
SDL_CancelGPUCommandBuffer(cmd)
|
||||||
|
throw NeHeError.sdlError("SDL_WaitAndAcquireGPUSwapchainTexture", message)
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let swapchain = swapchainTexture else
|
||||||
|
{
|
||||||
|
SDL_CancelGPUCommandBuffer(cmd)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
let swapchainSize = Size(width: UInt32(swapchainWidth), height: UInt32(swapchainHeight))
|
||||||
|
|
||||||
|
if config.manageDepthFormat != SDL_GPU_TEXTUREFORMAT_INVALID
|
||||||
|
&& ctx.depthTexture != nil
|
||||||
|
&& ctx.depthTextureSize != swapchainSize
|
||||||
|
{
|
||||||
|
try ctx.setupDepthTexture(size: swapchainSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
try app.draw(ctx: &ctx, cmd: cmd, swapchain: swapchain, swapchainSize: swapchainSize)
|
||||||
|
SDL_SubmitGPUCommandBuffer(cmd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
96
src/swift/NeHe/Matrix.swift
Normal file
96
src/swift/NeHe/Matrix.swift
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: (C) 2025 a dinosaur
|
||||||
|
* SPDX-License-Identifier: Zlib
|
||||||
|
*/
|
||||||
|
|
||||||
|
import simd
|
||||||
|
|
||||||
|
public extension simd_float4x4
|
||||||
|
{
|
||||||
|
static let identity: Self = matrix_identity_float4x4
|
||||||
|
|
||||||
|
@inlinable static func translation(_ position: SIMD3<Float>) -> Self
|
||||||
|
{
|
||||||
|
Self(
|
||||||
|
.init( 1, 0, 0, 0),
|
||||||
|
.init( 0, 1, 0, 0),
|
||||||
|
.init( 0, 0, 1, 0),
|
||||||
|
.init(position, 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
static func rotation(angle: Float, axis: SIMD3<Float>) -> Self
|
||||||
|
{
|
||||||
|
let r = simd_float3x3.makeGLRotation(angle, axis)
|
||||||
|
return Self(
|
||||||
|
.init(r.columns.0, 0),
|
||||||
|
.init(r.columns.1, 0),
|
||||||
|
.init(r.columns.2, 0),
|
||||||
|
.init( 0, 0, 0, 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
static func perspective(fovy: Float, aspect: Float, near: Float, far: Float) -> Self
|
||||||
|
{
|
||||||
|
let h = 1 / tan(fovy * (.pi / 180) * 0.5)
|
||||||
|
let w = h / aspect
|
||||||
|
let invClipRng = 1 / (far - near)
|
||||||
|
let zh = -(far + near) * invClipRng
|
||||||
|
let zl = -(2 * far * near) * invClipRng
|
||||||
|
|
||||||
|
return Self(
|
||||||
|
.init(w, 0, 0, 0),
|
||||||
|
.init(0, h, 0, 0),
|
||||||
|
.init(0, 0, zh, -1),
|
||||||
|
.init(0, 0, zl, 0))
|
||||||
|
}
|
||||||
|
|
||||||
|
@inlinable mutating func translate(_ offset: SIMD3<Float>)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
m = { [1 0 0 x]
|
||||||
|
[0 1 0 y]
|
||||||
|
[0 0 1 z]
|
||||||
|
[0 0 0 1] } * m
|
||||||
|
*/
|
||||||
|
self.columns.3 +=
|
||||||
|
offset.x * self.columns.0 +
|
||||||
|
offset.y * self.columns.1 +
|
||||||
|
offset.z * self.columns.2
|
||||||
|
}
|
||||||
|
|
||||||
|
mutating func rotate(angle: Float, axis: SIMD3<Float>)
|
||||||
|
{
|
||||||
|
let r = simd_float3x3.makeGLRotation(angle, axis)
|
||||||
|
|
||||||
|
// Set up temporaries
|
||||||
|
let (t0, t1, t2) = (self.columns.0, self.columns.1, self.columns.2)
|
||||||
|
|
||||||
|
// Partial matrix multiplication
|
||||||
|
self.columns.0 = r.columns.0.x * t0 + r.columns.0.y * t1 + r.columns.0.z * t2
|
||||||
|
self.columns.1 = r.columns.1.x * t0 + r.columns.1.y * t1 + r.columns.1.z * t2
|
||||||
|
self.columns.2 = r.columns.2.x * t0 + r.columns.2.y * t1 + r.columns.2.z * t2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate extension simd_float3x3
|
||||||
|
{
|
||||||
|
static func makeRotation(_ c: Float, _ s: Float, _ axis: SIMD3<Float>) -> Self
|
||||||
|
{
|
||||||
|
let rc = 1 - c
|
||||||
|
let rcv = rc * axis, sv = s * axis
|
||||||
|
return Self(
|
||||||
|
rcv * axis.x + .init( +c, +sv.z, -sv.y),
|
||||||
|
rcv * axis.y + .init(-sv.z, +c, +sv.x),
|
||||||
|
rcv * axis.z + .init(+sv.y, -sv.x, +c))
|
||||||
|
}
|
||||||
|
|
||||||
|
static func makeGLRotation(_ angle: Float, _ axis: SIMD3<Float>) -> Self
|
||||||
|
{
|
||||||
|
// Treat inputs like glRotatef
|
||||||
|
let theta = angle * (.pi / 180)
|
||||||
|
let axisMag = simd_length(axis)
|
||||||
|
let n = (abs(axisMag - 1) > .ulpOfOne)
|
||||||
|
? axis / axisMag
|
||||||
|
: axis
|
||||||
|
return Self.makeRotation(cos(theta), sin(theta), n)
|
||||||
|
}
|
||||||
|
}
|
||||||
229
src/swift/NeHe/NeHeContext.swift
Normal file
229
src/swift/NeHe/NeHeContext.swift
Normal file
@@ -0,0 +1,229 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: (C) 2025 a dinosaur
|
||||||
|
* SPDX-License-Identifier: Zlib
|
||||||
|
*/
|
||||||
|
|
||||||
|
import SDLSwift
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public struct NeHeContext
|
||||||
|
{
|
||||||
|
public var window: OpaquePointer
|
||||||
|
public var device: OpaquePointer
|
||||||
|
public var depthTexture: OpaquePointer?
|
||||||
|
public var depthTextureSize: Size<UInt32>
|
||||||
|
public let bundle: Bundle?
|
||||||
|
}
|
||||||
|
|
||||||
|
internal extension NeHeContext
|
||||||
|
{
|
||||||
|
init(title: StaticString, width: Int32, height: Int32, bundle: Bundle?) throws(NeHeError)
|
||||||
|
{
|
||||||
|
// Create window
|
||||||
|
let flags = SDL_WindowFlags(SDL_WINDOW_RESIZABLE) | SDL_WindowFlags(SDL_WINDOW_HIGH_PIXEL_DENSITY)
|
||||||
|
guard let window = SDL_CreateWindow(title.utf8Start, width, height, flags) else
|
||||||
|
{
|
||||||
|
throw .sdlError("SDL_CreateWindow", String(cString: SDL_GetError()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open GPU device
|
||||||
|
let formats =
|
||||||
|
SDL_GPU_SHADERFORMAT_METALLIB | SDL_GPU_SHADERFORMAT_MSL |
|
||||||
|
SDL_GPU_SHADERFORMAT_SPIRV | SDL_GPU_SHADERFORMAT_DXIL
|
||||||
|
guard let device = SDL_CreateGPUDevice(formats, true, nil) else
|
||||||
|
{
|
||||||
|
throw .sdlError("SDL_CreateGPUDevice", String(cString: SDL_GetError()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attach window to the GPU device
|
||||||
|
guard SDL_ClaimWindowForGPUDevice(device, window) else
|
||||||
|
{
|
||||||
|
throw .sdlError("SDL_ClaimWindowForGPUDevice", String(cString: SDL_GetError()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enable VSync
|
||||||
|
SDL_SetGPUSwapchainParameters(device, window,
|
||||||
|
SDL_GPU_SWAPCHAINCOMPOSITION_SDR,
|
||||||
|
SDL_GPU_PRESENTMODE_VSYNC)
|
||||||
|
|
||||||
|
self.window = window
|
||||||
|
self.device = device
|
||||||
|
self.depthTexture = nil
|
||||||
|
self.depthTextureSize = .zero
|
||||||
|
self.bundle = bundle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public extension NeHeContext
|
||||||
|
{
|
||||||
|
mutating func setupDepthTexture(size: Size<UInt32>,
|
||||||
|
format: SDL_GPUTextureFormat = SDL_GPU_TEXTUREFORMAT_D16_UNORM, clearDepth: Float = 1.0) throws(NeHeError)
|
||||||
|
{
|
||||||
|
if self.depthTexture != nil
|
||||||
|
{
|
||||||
|
SDL_ReleaseGPUTexture(self.device, self.depthTexture)
|
||||||
|
self.depthTexture = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
let props = SDL_CreateProperties()
|
||||||
|
guard props != 0 else
|
||||||
|
{
|
||||||
|
throw .sdlError("SDL_CreateProperties", String(cString: SDL_GetError()))
|
||||||
|
}
|
||||||
|
// Workaround for https://github.com/libsdl-org/SDL/issues/10758
|
||||||
|
SDL_SetFloatProperty(props, SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_DEPTH_FLOAT, clearDepth)
|
||||||
|
|
||||||
|
var info = SDL_GPUTextureCreateInfo(
|
||||||
|
type: SDL_GPU_TEXTURETYPE_2D,
|
||||||
|
format: format,
|
||||||
|
usage: SDL_GPU_TEXTUREUSAGE_DEPTH_STENCIL_TARGET,
|
||||||
|
width: size.width,
|
||||||
|
height: size.height,
|
||||||
|
layer_count_or_depth: 1,
|
||||||
|
num_levels: 1,
|
||||||
|
sample_count: SDL_GPU_SAMPLECOUNT_1,
|
||||||
|
props: props)
|
||||||
|
let newTexture = SDL_CreateGPUTexture(self.device, &info)
|
||||||
|
SDL_DestroyProperties(props)
|
||||||
|
guard let texture = newTexture else
|
||||||
|
{
|
||||||
|
throw .sdlError("SDL_CreateGPUTexture", String(cString: SDL_GetError()))
|
||||||
|
}
|
||||||
|
self.depthTexture = texture
|
||||||
|
self.depthTextureSize = size
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadShaders(name: String, vertexUniforms: UInt32 = 0, vertexStorage: UInt32 = 0, fragmentSamplers: UInt32 = 0)
|
||||||
|
throws(NeHeError) -> (vertex: OpaquePointer, fragment: OpaquePointer)
|
||||||
|
{
|
||||||
|
guard let bundle = self.bundle else { throw .fatalError("No bundle") }
|
||||||
|
|
||||||
|
var info = ShaderProgramCreateInfo(format: SDL_GPUShaderFormat(SDL_GPU_SHADERFORMAT_INVALID),
|
||||||
|
vertexUniforms: vertexUniforms,
|
||||||
|
vertexStorage: vertexStorage,
|
||||||
|
fragmentSamplers: fragmentSamplers)
|
||||||
|
|
||||||
|
let availableFormats = SDL_GetGPUShaderFormats(self.device)
|
||||||
|
if availableFormats & (SDL_GPU_SHADERFORMAT_METALLIB | SDL_GPU_SHADERFORMAT_MSL) != 0
|
||||||
|
{
|
||||||
|
if availableFormats & SDL_GPU_SHADERFORMAT_METALLIB == SDL_GPU_SHADERFORMAT_METALLIB
|
||||||
|
{
|
||||||
|
if let path = bundle.url(forResource: name, withExtension: "metallib"),
|
||||||
|
let lib = try? Data(contentsOf: path)
|
||||||
|
{
|
||||||
|
info.format = SDL_GPU_SHADERFORMAT_METALLIB
|
||||||
|
return (
|
||||||
|
try loadShaderBlob(lib, info, SDL_GPU_SHADERSTAGE_VERTEX, "VertexMain"),
|
||||||
|
try loadShaderBlob(lib, info, SDL_GPU_SHADERSTAGE_FRAGMENT, "FragmentMain"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if availableFormats & SDL_GPU_SHADERFORMAT_MSL == SDL_GPU_SHADERFORMAT_MSL
|
||||||
|
{
|
||||||
|
guard let path = bundle.url(forResource: name, withExtension: "metal"),
|
||||||
|
let src = try? Data(contentsOf: path) else
|
||||||
|
{
|
||||||
|
throw .fatalError("Failed to load metal shader")
|
||||||
|
}
|
||||||
|
info.format = SDL_GPU_SHADERFORMAT_MSL
|
||||||
|
return (
|
||||||
|
try loadShaderBlob(src, info, SDL_GPU_SHADERSTAGE_VERTEX, "VertexMain"),
|
||||||
|
try loadShaderBlob(src, info, SDL_GPU_SHADERSTAGE_FRAGMENT, "FragmentMain"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if availableFormats & SDL_GPU_SHADERFORMAT_SPIRV == SDL_GPU_SHADERFORMAT_SPIRV
|
||||||
|
{
|
||||||
|
info.format = SDL_GPU_SHADERFORMAT_SPIRV
|
||||||
|
return (
|
||||||
|
try loadShader(bundle.url(forResource: name, withExtension: "vtx.spv"),
|
||||||
|
info, SDL_GPU_SHADERSTAGE_VERTEX, "main"),
|
||||||
|
try loadShader(bundle.url(forResource: name, withExtension: "frg.spv"),
|
||||||
|
info, SDL_GPU_SHADERSTAGE_FRAGMENT, "main"))
|
||||||
|
}
|
||||||
|
else if availableFormats & SDL_GPU_SHADERFORMAT_DXIL == SDL_GPU_SHADERFORMAT_DXIL
|
||||||
|
{
|
||||||
|
info.format = SDL_GPU_SHADERFORMAT_DXIL
|
||||||
|
return (
|
||||||
|
try loadShader(bundle.url(forResource: name, withExtension: "vtx.dxb"),
|
||||||
|
info, SDL_GPU_SHADERSTAGE_VERTEX, "VertexMain"),
|
||||||
|
try loadShader(bundle.url(forResource: name, withExtension: "pxl.dxb"),
|
||||||
|
info, SDL_GPU_SHADERSTAGE_FRAGMENT, "PixelMain"))
|
||||||
|
|
||||||
|
}
|
||||||
|
throw .fatalError("No supported shader formats found")
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyPass(pass closure: (inout NeHeCopyPass) throws(NeHeError) -> Void) throws(NeHeError)
|
||||||
|
{
|
||||||
|
var pass = NeHeCopyPass(self)
|
||||||
|
try closure(&pass)
|
||||||
|
try pass.submit()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate extension NeHeContext
|
||||||
|
{
|
||||||
|
struct ShaderProgramCreateInfo
|
||||||
|
{
|
||||||
|
var format: SDL_GPUShaderFormat
|
||||||
|
var vertexUniforms: UInt32
|
||||||
|
var vertexStorage: UInt32
|
||||||
|
var fragmentSamplers: UInt32
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadShader(_ filePath: URL?, _ info: borrowing ShaderProgramCreateInfo,
|
||||||
|
_ type: SDL_GPUShaderStage, _ main: StaticString) throws(NeHeError) -> OpaquePointer
|
||||||
|
{
|
||||||
|
guard let path = filePath, let data = try? Data(contentsOf: path) else
|
||||||
|
{
|
||||||
|
throw .fatalError("Failed to open shader file")
|
||||||
|
}
|
||||||
|
return try loadShaderBlob(data, info, type, main)
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadShaderBlob(_ code: Data, _ info: ShaderProgramCreateInfo,
|
||||||
|
_ type: SDL_GPUShaderStage, _ main: StaticString) throws(NeHeError) -> OpaquePointer
|
||||||
|
{
|
||||||
|
guard let result = code.withContiguousStorageIfAvailable({ data in
|
||||||
|
main.withUTF8Buffer { $0.withMemoryRebound(to: Int8.self) { entryPoint in
|
||||||
|
var info = SDL_GPUShaderCreateInfo(
|
||||||
|
code_size: code.count,
|
||||||
|
code: data.baseAddress!,
|
||||||
|
entrypoint: entryPoint.baseAddress!,
|
||||||
|
format: info.format,
|
||||||
|
stage: type,
|
||||||
|
num_samplers: type == SDL_GPU_SHADERSTAGE_FRAGMENT ? info.fragmentSamplers : 0,
|
||||||
|
num_storage_textures: 0,
|
||||||
|
num_storage_buffers: type == SDL_GPU_SHADERSTAGE_VERTEX ? info.vertexStorage : 0,
|
||||||
|
num_uniform_buffers: type == SDL_GPU_SHADERSTAGE_VERTEX ? info.vertexUniforms : 0,
|
||||||
|
props: 0)
|
||||||
|
return SDL_CreateGPUShader(self.device, &info)
|
||||||
|
}}
|
||||||
|
}) else
|
||||||
|
{
|
||||||
|
throw .fatalError("Failed to convert shader blob to contiguous storage")
|
||||||
|
}
|
||||||
|
guard let shader = result else
|
||||||
|
{
|
||||||
|
throw .sdlError("SDL_CreateGPUShader", String(cString: SDL_GetError()))
|
||||||
|
}
|
||||||
|
return shader
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate extension URL
|
||||||
|
{
|
||||||
|
func join(_ pathComponent: String, isDirectory dir: Bool? = nil) -> URL
|
||||||
|
{
|
||||||
|
if #available(macOS 13, *)
|
||||||
|
{
|
||||||
|
let hint: DirectoryHint = dir == nil ? .inferFromPath : (dir! ? .isDirectory : .notDirectory)
|
||||||
|
return self.appending(path: pathComponent, directoryHint: hint)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return dir == nil
|
||||||
|
? self.appendingPathComponent(pathComponent)
|
||||||
|
: self.appendingPathComponent(pathComponent, isDirectory: dir!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
270
src/swift/NeHe/NeHeCopyPass.swift
Normal file
270
src/swift/NeHe/NeHeCopyPass.swift
Normal file
@@ -0,0 +1,270 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: (C) 2025 a dinosaur
|
||||||
|
* SPDX-License-Identifier: Zlib
|
||||||
|
*/
|
||||||
|
|
||||||
|
import SDLSwift
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public struct NeHeCopyPass: ~Copyable
|
||||||
|
{
|
||||||
|
private var device: OpaquePointer
|
||||||
|
private var bundle: Bundle?
|
||||||
|
fileprivate var copies: [Copy]
|
||||||
|
|
||||||
|
internal init(_ ctx: borrowing NeHeContext)
|
||||||
|
{
|
||||||
|
self.device = ctx.device
|
||||||
|
self.bundle = ctx.bundle
|
||||||
|
self.copies = .init()
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit
|
||||||
|
{
|
||||||
|
// Free transfer buffers
|
||||||
|
for copy in self.copies.reversed()
|
||||||
|
{
|
||||||
|
SDL_ReleaseGPUTransferBuffer(self.device, copy.xferBuffer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal func submit() throws(NeHeError)
|
||||||
|
{
|
||||||
|
guard let cmd = SDL_AcquireGPUCommandBuffer(self.device) else
|
||||||
|
{
|
||||||
|
let message = String(cString: SDL_GetError())
|
||||||
|
throw .sdlError("SDL_AcquireGPUCommandBuffer", message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Begin the copy pass
|
||||||
|
let pass = SDL_BeginGPUCopyPass(cmd)
|
||||||
|
|
||||||
|
// Upload data into the GPU buffer(s)
|
||||||
|
for copy in self.copies
|
||||||
|
{
|
||||||
|
var vtxSource = SDL_GPUTransferBufferLocation(transfer_buffer: copy.xferBuffer, offset: 0)
|
||||||
|
var vtxDestination = SDL_GPUBufferRegion(buffer: copy.buffer, offset: 0, size: copy.size)
|
||||||
|
SDL_UploadToGPUBuffer(pass, &vtxSource, &vtxDestination, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// End & submit the copy pass
|
||||||
|
SDL_EndGPUCopyPass(pass)
|
||||||
|
SDL_SubmitGPUCommandBuffer(cmd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public extension NeHeCopyPass
|
||||||
|
{
|
||||||
|
mutating func createBuffer<E>(usage: SDL_GPUBufferUsageFlags, _ elements: ArraySlice<E>) throws(NeHeError) -> OpaquePointer
|
||||||
|
{
|
||||||
|
// Create data buffer
|
||||||
|
let size = UInt32(MemoryLayout<E>.stride * elements.count)
|
||||||
|
var info = SDL_GPUBufferCreateInfo(usage: usage, size: size, props: 0)
|
||||||
|
guard let buffer = SDL_CreateGPUBuffer(self.device, &info) else
|
||||||
|
{
|
||||||
|
throw .sdlError("SDL_CreateGPUBuffer", String(cString: SDL_GetError()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create transfer buffer
|
||||||
|
var xferInfo = SDL_GPUTransferBufferCreateInfo(
|
||||||
|
usage: SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD,
|
||||||
|
size: size,
|
||||||
|
props: 0)
|
||||||
|
guard let xferBuffer = SDL_CreateGPUTransferBuffer(self.device, &xferInfo) else
|
||||||
|
{
|
||||||
|
let message = String(cString: SDL_GetError())
|
||||||
|
SDL_ReleaseGPUBuffer(self.device, buffer)
|
||||||
|
throw .sdlError("SDL_CreateGPUTransferBuffer", message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map transfer buffer and copy the data
|
||||||
|
guard let map = SDL_MapGPUTransferBuffer(self.device, xferBuffer, false) else
|
||||||
|
{
|
||||||
|
let message = String(cString: SDL_GetError())
|
||||||
|
SDL_ReleaseGPUTransferBuffer(self.device, xferBuffer)
|
||||||
|
SDL_ReleaseGPUBuffer(self.device, buffer)
|
||||||
|
throw .sdlError("SDL_MapGPUTransferBuffer", message)
|
||||||
|
}
|
||||||
|
elements.withUnsafeBufferPointer
|
||||||
|
{
|
||||||
|
map.assumingMemoryBound(to: E.self).initialize(from: $0.baseAddress!, count: $0.count)
|
||||||
|
}
|
||||||
|
SDL_UnmapGPUTransferBuffer(self.device, xferBuffer)
|
||||||
|
|
||||||
|
self.copies.append(.init(
|
||||||
|
buffer: buffer,
|
||||||
|
xferBuffer: xferBuffer,
|
||||||
|
size: size))
|
||||||
|
return buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
mutating func createTextureFrom(bmpResource name: String, flip: Bool = false, genMipmaps: Bool = false)
|
||||||
|
throws(NeHeError) -> OpaquePointer
|
||||||
|
{
|
||||||
|
// Load image into a surface
|
||||||
|
guard let bundle = self.bundle,
|
||||||
|
let pathURL = bundle.url(forResource: name, withExtension: "bmp")
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw .fatalError("Failed to load BMP resource")
|
||||||
|
}
|
||||||
|
let path = if #available(macOS 13.0, *) { pathURL.path(percentEncoded: false) } else { pathURL.path }
|
||||||
|
guard let image = SDL_LoadBMP(path) else
|
||||||
|
{
|
||||||
|
throw .sdlError("SDL_LoadBMP", String(cString: SDL_GetError()))
|
||||||
|
}
|
||||||
|
defer { SDL_DestroySurface(image) }
|
||||||
|
|
||||||
|
// Flip surface if requested
|
||||||
|
if flip
|
||||||
|
{
|
||||||
|
guard SDL_FlipSurface(image, SDL_FLIP_VERTICAL) else
|
||||||
|
{
|
||||||
|
throw .sdlError("SDL_FlipSurface", String(cString: SDL_GetError()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upload texture to GPU
|
||||||
|
return try self.createTextureFrom(surface: image, genMipmaps: genMipmaps)
|
||||||
|
}
|
||||||
|
|
||||||
|
mutating func createTextureFrom(surface: UnsafeMutablePointer<SDL_Surface>,
|
||||||
|
genMipmaps: Bool) throws(NeHeError) -> OpaquePointer
|
||||||
|
{
|
||||||
|
var info = SDL_GPUTextureCreateInfo()
|
||||||
|
info.type = SDL_GPU_TEXTURETYPE_2D
|
||||||
|
info.format = SDL_GPU_TEXTUREFORMAT_INVALID
|
||||||
|
info.usage = SDL_GPU_TEXTUREUSAGE_SAMPLER
|
||||||
|
info.width = UInt32(surface.pointee.w)
|
||||||
|
info.height = UInt32(surface.pointee.h)
|
||||||
|
info.layer_count_or_depth = 1
|
||||||
|
info.num_levels = 1
|
||||||
|
|
||||||
|
let needsConvert: Bool
|
||||||
|
(needsConvert, info.format) = switch surface.pointee.format
|
||||||
|
{
|
||||||
|
case SDL_PIXELFORMAT_RGBA32: (false, SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM)
|
||||||
|
case SDL_PIXELFORMAT_RGBA64: (false, SDL_GPU_TEXTUREFORMAT_R16G16B16A16_UNORM)
|
||||||
|
case SDL_PIXELFORMAT_RGB565: (false, SDL_GPU_TEXTUREFORMAT_B5G6R5_UNORM)
|
||||||
|
case SDL_PIXELFORMAT_ARGB1555: (false, SDL_GPU_TEXTUREFORMAT_B5G5R5A1_UNORM)
|
||||||
|
case SDL_PIXELFORMAT_BGRA4444: (false, SDL_GPU_TEXTUREFORMAT_B4G4R4A4_UNORM)
|
||||||
|
case SDL_PIXELFORMAT_BGRA32: (false, SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM)
|
||||||
|
case SDL_PIXELFORMAT_RGBA64_FLOAT: (false, SDL_GPU_TEXTUREFORMAT_R16G16B16A16_FLOAT)
|
||||||
|
case SDL_PIXELFORMAT_RGBA128_FLOAT: (false, SDL_GPU_TEXTUREFORMAT_R32G32B32A32_FLOAT)
|
||||||
|
default: (true, SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BYTESPERPIXEL(_ format: SDL_PixelFormat) -> UInt32
|
||||||
|
{
|
||||||
|
let isFourCC = format.rawValue != 0 && (((format.rawValue >> 28) & 0xF) != 1)
|
||||||
|
return isFourCC
|
||||||
|
? [
|
||||||
|
SDL_PIXELFORMAT_YUY2,
|
||||||
|
SDL_PIXELFORMAT_UYVY,
|
||||||
|
SDL_PIXELFORMAT_YVYU,
|
||||||
|
SDL_PIXELFORMAT_P010
|
||||||
|
].contains(format) ? 2 : 1
|
||||||
|
: format.rawValue & 0xFF
|
||||||
|
}
|
||||||
|
|
||||||
|
let data: UnsafeRawBufferPointer
|
||||||
|
let conv: UnsafeMutablePointer<SDL_Surface>? = nil
|
||||||
|
if needsConvert
|
||||||
|
{
|
||||||
|
// Convert pixel format if required
|
||||||
|
guard let conv = SDL_ConvertSurface(surface, SDL_PIXELFORMAT_ABGR8888) else
|
||||||
|
{
|
||||||
|
throw .sdlError("SDL_ConvertSurface", String(cString: SDL_GetError()))
|
||||||
|
}
|
||||||
|
let numPixels = Int(conv.pointee.w) * Int(conv.pointee.h)
|
||||||
|
data = .init(start: conv.pointee.pixels, count: Int(BYTESPERPIXEL(conv.pointee.format)) * numPixels)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
let numPixels = Int(surface.pointee.w) * Int(surface.pointee.h)
|
||||||
|
data = .init(start: surface.pointee.pixels, count: Int(BYTESPERPIXEL(surface.pointee.format)) * numPixels)
|
||||||
|
}
|
||||||
|
defer { SDL_DestroySurface(conv) }
|
||||||
|
|
||||||
|
if genMipmaps
|
||||||
|
{
|
||||||
|
info.usage |= SDL_GPU_TEXTUREUSAGE_COLOR_TARGET
|
||||||
|
// floor(log₂(max(𝑤,ℎ)) + 1
|
||||||
|
info.num_levels = 31 - UInt32(max(info.width, info.height).leadingZeroBitCount) + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return try self.createTextureFrom(pixels: data, createInfo: &info, genMipmaps: genMipmaps)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate extension NeHeCopyPass
|
||||||
|
{
|
||||||
|
func createTextureFrom(pixels: UnsafeRawBufferPointer,
|
||||||
|
createInfo info: inout SDL_GPUTextureCreateInfo, genMipmaps: Bool)
|
||||||
|
throws(NeHeError) -> OpaquePointer
|
||||||
|
{
|
||||||
|
guard let texture = SDL_CreateGPUTexture(self.device, &info) else
|
||||||
|
{
|
||||||
|
throw .sdlError("SDL_CreateGPUTexture", String(cString: SDL_GetError()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create and copy image data to a transfer buffer
|
||||||
|
var xferInfo = SDL_GPUTransferBufferCreateInfo(
|
||||||
|
usage: SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD,
|
||||||
|
size: UInt32(pixels.count),
|
||||||
|
props: 0)
|
||||||
|
guard let xferBuffer = SDL_CreateGPUTransferBuffer(self.device, &xferInfo) else
|
||||||
|
{
|
||||||
|
SDL_ReleaseGPUTexture(self.device, texture)
|
||||||
|
throw .sdlError("SDL_CreateGPUTransferBuffer", String(cString: SDL_GetError()))
|
||||||
|
}
|
||||||
|
defer { SDL_ReleaseGPUTransferBuffer(self.device, xferBuffer) }
|
||||||
|
|
||||||
|
guard let map = SDL_MapGPUTransferBuffer(self.device, xferBuffer, false) else
|
||||||
|
{
|
||||||
|
SDL_ReleaseGPUTexture(self.device, texture)
|
||||||
|
throw .sdlError("SDL_MapGPUTransferBuffer", String(cString: SDL_GetError()))
|
||||||
|
}
|
||||||
|
map.initializeMemory(as: UInt8.self,
|
||||||
|
from: pixels.withMemoryRebound(to: UInt8.self, \.baseAddress!),
|
||||||
|
count: pixels.count)
|
||||||
|
SDL_UnmapGPUTransferBuffer(self.device, xferBuffer)
|
||||||
|
|
||||||
|
// Upload the transfer data to the GPU resources
|
||||||
|
guard let cmd = SDL_AcquireGPUCommandBuffer(self.device) else
|
||||||
|
{
|
||||||
|
SDL_ReleaseGPUTexture(self.device, texture)
|
||||||
|
throw .sdlError("SDL_AcquireGPUCommandBuffer", String(cString: SDL_GetError()))
|
||||||
|
}
|
||||||
|
|
||||||
|
let pass = SDL_BeginGPUCopyPass(cmd)
|
||||||
|
var source = SDL_GPUTextureTransferInfo()
|
||||||
|
source.transfer_buffer = xferBuffer
|
||||||
|
source.offset = 0
|
||||||
|
var destination = SDL_GPUTextureRegion()
|
||||||
|
destination.texture = texture
|
||||||
|
destination.w = info.width
|
||||||
|
destination.h = info.height
|
||||||
|
destination.d = info.layer_count_or_depth
|
||||||
|
SDL_UploadToGPUTexture(pass, &source, &destination, false)
|
||||||
|
SDL_EndGPUCopyPass(pass)
|
||||||
|
|
||||||
|
if genMipmaps
|
||||||
|
{
|
||||||
|
SDL_GenerateMipmapsForGPUTexture(cmd, texture)
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_SubmitGPUCommandBuffer(cmd)
|
||||||
|
return texture
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate extension NeHeCopyPass
|
||||||
|
{
|
||||||
|
struct Copy
|
||||||
|
{
|
||||||
|
var buffer: OpaquePointer
|
||||||
|
var xferBuffer: OpaquePointer
|
||||||
|
var size: UInt32
|
||||||
|
}
|
||||||
|
}
|
||||||
24
src/swift/NeHe/NeHeError.swift
Normal file
24
src/swift/NeHe/NeHeError.swift
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: (C) 2025 a dinosaur
|
||||||
|
* SPDX-License-Identifier: Zlib
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public enum NeHeError: Error
|
||||||
|
{
|
||||||
|
case fatalError(StaticString)
|
||||||
|
case sdlError(StaticString, String)
|
||||||
|
}
|
||||||
|
|
||||||
|
extension NeHeError: LocalizedError
|
||||||
|
{
|
||||||
|
public var errorDescription: String?
|
||||||
|
{
|
||||||
|
switch self
|
||||||
|
{
|
||||||
|
case .fatalError(let why): "\(why)"
|
||||||
|
case .sdlError(let fname, let message): "\(fname): \(message)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
30
src/swift/NeHe/Size.swift
Normal file
30
src/swift/NeHe/Size.swift
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: (C) 2025 a dinosaur
|
||||||
|
* SPDX-License-Identifier: Zlib
|
||||||
|
*/
|
||||||
|
|
||||||
|
public struct Size<T: Numeric>: Equatable
|
||||||
|
{
|
||||||
|
public var width: T, height: T
|
||||||
|
|
||||||
|
public init(width: T, height: T)
|
||||||
|
{
|
||||||
|
self.width = width
|
||||||
|
self.height = height
|
||||||
|
}
|
||||||
|
|
||||||
|
@inline(__always) public init(_ w: T, _ h: T) { self.init(width: w, height: h) }
|
||||||
|
}
|
||||||
|
|
||||||
|
public extension Size
|
||||||
|
{
|
||||||
|
@inline(__always) static var zero: Self { Self(0, 0) }
|
||||||
|
}
|
||||||
|
|
||||||
|
public extension Size where T: BinaryInteger
|
||||||
|
{
|
||||||
|
init<O: BinaryInteger>(_ other: Size<O>)
|
||||||
|
{
|
||||||
|
self.init(T(other.width), T(other.height))
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user