Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
andystanton committed Jun 3, 2024
0 parents commit eee874f
Show file tree
Hide file tree
Showing 15 changed files with 1,195 additions and 0 deletions.
35 changes: 35 additions & 0 deletions .github/workflows/run-tests-and-create-tag.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
name: Run Tests and Create Tag

on:
push:
branches: [ main ]
paths-ignore: [ '.gitignore', '.swift-format', 'README.md', 'LICENSE' ]

jobs:
run_tests_and_create_tag:
runs-on: macos-14
env:
GITHUB_TOKEN: ${{ secrets.CUSTOM_ACCESS_TOKEN }}
permissions:
contents: write
steps:
- name: Set XCode version
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: "15"
- name: Checkout project including tags
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Run tests
run: swift test
- name: Get next version
id: get-next-version
uses: paulhatch/semantic-version@v5.0.0-alpha2
with:
tag_prefix: "v"
search_commit_body: false
- uses: ncipollo/release-action@v1
with:
commit: main
tag: ${{ steps.get-next-version.outputs.version_tag }}
21 changes: 21 additions & 0 deletions .github/workflows/run-tests-pr.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
name: Run Tests PR

on:
pull_request:
branches: [ main ]
paths-ignore: [ '.gitignore', '.swift-format', 'README.md', 'LICENSE' ]

jobs:
run_tests_pr:
runs-on: macos-14
steps:
- name: Set XCode version
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: "15"
- name: Checkout project including tags
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Run tests
run: swift test
13 changes: 13 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
.DS_Store
/.build
/Packages
xcuserdata/
DerivedData/
.swiftpm/configuration/registries.json
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
.netrc
.build
.swiftpm
*.gputrace
*.pgm
*.png
15 changes: 15 additions & 0 deletions Package.resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"originHash" : "3291dbb33922ce36c22b90713335195658a0672ec9059869b5353bc1d0355a66",
"pins" : [
{
"identity" : "spmopensimplex2",
"kind" : "remoteSourceControl",
"location" : "https://github.com/andystanton/SPMOpenSimplex2.git",
"state" : {
"revision" : "54fef25b02f1b8afd827dd53495a1c12a77659a4",
"version" : "0.0.10"
}
}
],
"version" : 3
}
24 changes: 24 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// swift-tools-version: 5.10

import PackageDescription

let package = Package(
name: "SPMNoiseGeneration",
products: [
.library(
name: "SPMNoiseGeneration",
targets: ["SPMNoiseGeneration"]),
],
dependencies: [
.package(url: "https://github.com/andystanton/SPMOpenSimplex2.git", "0.0.0"..<"0.1.0")
],
targets: [
.target(
name: "SPMNoiseGeneration",
dependencies: [
.product(name: "SPMOpenSimplex2", package: "spmopensimplex2")]),
.testTarget(
name: "SPMNoiseGenerationTests",
dependencies: ["SPMNoiseGeneration"]),
]
)
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# SPM Fractal Noise

A Swift Package containing a fractional Brownian Motion implementation over a noise function.

38 changes: 38 additions & 0 deletions Sources/SPMNoiseGeneration/FractalNoise.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import SPMOpenSimplex2

public struct FractalOpenSimplex2NoiseParameters {
let openSimplex2Variant: OpenSimplex2Noise2Variant
let openSimplex3Variant: OpenSimplex2Noise3Variant
let openSimplex4Variant: OpenSimplex2Noise4Variant

init(
openSimplex2Variant: OpenSimplex2Noise2Variant = .standard,
openSimplex3Variant: OpenSimplex2Noise3Variant = .xy,
openSimplex4Variant: OpenSimplex2Noise4Variant = .xyz
) {
self.openSimplex2Variant = openSimplex2Variant
self.openSimplex3Variant = openSimplex3Variant
self.openSimplex4Variant = openSimplex4Variant
}
}

public enum FractalNoiseTypeParameters {
case OpenSimplex2(FractalOpenSimplex2NoiseParameters)
}

public struct FractalNoiseParameters {
let noiseTypeParameters: FractalNoiseTypeParameters

let octaves: Int32
let lacunarity: Float
let hurstExponent: Float

let startingAmplitude: Float
let startingFrequency: Float
}

public protocol FractalNoise {
func noise3(seed: Int32, coord: SIMD3<Float>, fractalNoiseParameters: FractalNoiseParameters) -> Float
func noise3(seed: Int32, coords: [SIMD3<Float>], fractalNoiseParameters: FractalNoiseParameters) -> [Float]
}

61 changes: 61 additions & 0 deletions Sources/SPMNoiseGeneration/FractalNoiseCPU.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import SPMOpenSimplex2
import simd

public class FractalNoiseCPU {
private let openSimplex2: OpenSimplex2CPU

public init() {
self.openSimplex2 = OpenSimplex2CPU()
}

private func getNoise3Value(
seed: Int32,
coord: SIMD3<Float>,
noiseType: FractalNoiseTypeParameters
) -> Float {
switch noiseType {
case .OpenSimplex2(let parameters):
return openSimplex2.noise3(
seed: seed,
coord: coord,
variant: parameters.openSimplex3Variant)
}
}
}

extension FractalNoiseCPU: FractalNoise {
public func noise3(
seed: Int32,
coords: [SIMD3<Float>],
fractalNoiseParameters: FractalNoiseParameters
) -> [Float] {
return coords.map {
noise3(
seed: seed,
coord: $0,
fractalNoiseParameters: fractalNoiseParameters)
}
}

public func noise3(
seed: Int32,
coord: SIMD3<Float>,
fractalNoiseParameters: FractalNoiseParameters
) -> Float {
var fractalNoise = Float.zero
var amplitude = fractalNoiseParameters.startingAmplitude
var frequency = fractalNoiseParameters.startingFrequency
let gain = exp2(-fractalNoiseParameters.hurstExponent)

for _ in 0..<fractalNoiseParameters.octaves {
fractalNoise += amplitude * getNoise3Value(
seed: seed,
coord: coord * frequency,
noiseType: fractalNoiseParameters.noiseTypeParameters)

frequency *= fractalNoiseParameters.lacunarity
amplitude *= gain
}
return fractalNoise
}
}
152 changes: 152 additions & 0 deletions Sources/SPMNoiseGeneration/FractalNoiseMetal.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import SPMOpenSimplex2
import simd
import Metal

public class FractalNoiseMetal {
private let commandQueue: MTLCommandQueue!
private let device: MTLDevice!

private let noise2Pipeline: MTLComputePipelineState?
private let noise3Pipeline: MTLComputePipelineState?
private let noise4Pipeline: MTLComputePipelineState?

public init(
device: MTLDevice? = nil,
commandQueue: MTLCommandQueue? = nil
) {
self.device = device ?? MTLCreateSystemDefaultDevice()!
self.commandQueue = commandQueue ?? self.device.makeCommandQueue()

let kernel = """
#include <metal_stdlib>
using namespace metal;
\(OpenSimplex2MetalShaderLoader(dimensionality: [.two, .three, .four]).shader)
\(FractalNoiseMetalShaderLoader(dimensionality: [.two, .three, .four]).shader)
"""

let library = try! self.device.makeLibrary(source: kernel, options: nil)

if let noise2Function = library.makeFunction(name: FractalNoiseMetalShaderLoader.noise2FunctionName) {
noise2Pipeline = try! self.device.makeComputePipelineState(function: noise2Function)
} else {
noise2Pipeline = nil
}
if let noise3Function = library.makeFunction(name: FractalNoiseMetalShaderLoader.noise3FunctionName) {
noise3Pipeline = try! self.device.makeComputePipelineState(function: noise3Function)
} else {
noise3Pipeline = nil
}
if let noise4Function = library.makeFunction(name: FractalNoiseMetalShaderLoader.noise4FunctionName) {
noise4Pipeline = try! self.device.makeComputePipelineState(function: noise4Function)
} else {
noise4Pipeline = nil
}
}

private func executeNoiseFunction(
pipeline: MTLComputePipelineState,
seed: Int32,
fractalNoiseParameters: FractalNoiseParameters,
inBuffer: MTLBuffer,
inputCount: Int
) -> [Float] {
let outByteLength = MemoryLayout<Float>.stride * inputCount
let outBuffer = device.makeBuffer(length: outByteLength, options: [.storageModeShared])!

guard let commandBuffer = commandQueue.makeCommandBuffer(),
let commandEncoder = commandBuffer.makeComputeCommandEncoder() else {
return []
}

let (noiseType, noiseTypeParameters) = switch fractalNoiseParameters.noiseTypeParameters {
case .OpenSimplex2(let params):
(FractalNoiseMetalType.OpenSimplex2,
FractalNoiseMetalTypeParameters.OpenSimplex2(
OpenSimplex2MetalParameters(
seed: seed,
noise2Variant: params.openSimplex2Variant.toMetalVariant(),
noise3Variant: params.openSimplex3Variant.toMetalVariant(),
noise4Variant: params.openSimplex4Variant.toMetalVariant())))
}

var uniforms = FractalNoiseMetalParameters(
lacunarity: fractalNoiseParameters.lacunarity,
gain: exp2(-fractalNoiseParameters.hurstExponent),
startingAmplitude: fractalNoiseParameters.startingAmplitude,
startingFrequency: fractalNoiseParameters.startingFrequency,
octaves: fractalNoiseParameters.octaves,
noiseType: noiseType,
noiseTypeParameters: noiseTypeParameters)

commandEncoder.setComputePipelineState(pipeline)
commandEncoder.setBytes(&uniforms, length: MemoryLayout<FractalNoiseMetalParameters>.stride, index: 0)
commandEncoder.setBuffer(inBuffer, offset: 0, index: 1)
commandEncoder.setBuffer(outBuffer, offset: 0, index: 2)

let groupWidth = inputCount > pipeline.maxTotalThreadsPerThreadgroup
? pipeline.maxTotalThreadsPerThreadgroup
: inputCount
let groupSize = MTLSize(width: groupWidth, height: 1, depth: 1)

let (groupCount, groupCountRemainder) = inputCount.quotientAndRemainder(dividingBy: groupWidth)
let finalGroupCount = groupCount + (groupCountRemainder > 0 ? 1 : 0)

let gridSize = MTLSize(width: groupWidth * finalGroupCount, height: 1, depth: 1)

commandEncoder.dispatchThreads(gridSize, threadsPerThreadgroup: groupSize)

commandEncoder.endEncoding()

commandBuffer.commit()
commandBuffer.waitUntilCompleted()

return downloadFromBuffer(outBuffer, count: inputCount)
}

private func uploadToBuffer<T>(_ buffer: MTLBuffer, data: [T], offset: Int = 0) {
let memoryPointer = buffer.contents().advanced(by: offset)
memcpy(memoryPointer, data, MemoryLayout<T>.stride * data.count)
}

private func downloadFromBuffer<T>(_ buffer: MTLBuffer, count: Int, offset: Int = 0) -> [T] {
let memoryPointer = buffer.contents().advanced(by: offset)
let typedPointer = memoryPointer.bindMemory(to: T.self, capacity: MemoryLayout<T>.stride * count)
let bufferedPointer = UnsafeBufferPointer(start: typedPointer, count: count)
return Array(bufferedPointer)
}
}

extension FractalNoiseMetal: FractalNoise {
public func noise3(
seed: Int32,
coord: SIMD3<Float>,
fractalNoiseParameters: FractalNoiseParameters
) -> Float {
return noise3(
seed: seed,
coords: [coord],
fractalNoiseParameters: fractalNoiseParameters)[0]
}

public func noise3(
seed: Int32,
coords: [SIMD3<Float>],
fractalNoiseParameters: FractalNoiseParameters
) -> [Float] {
guard let pipeline = noise3Pipeline else {
return []
}
let inByteLength = MemoryLayout<SIMD3<Float>>.stride * coords.count
let inBuffer = device.makeBuffer(length: inByteLength, options: [.storageModeShared])!
uploadToBuffer(inBuffer, data: coords, offset: 0)

return executeNoiseFunction(
pipeline: pipeline,
seed: seed,
fractalNoiseParameters: fractalNoiseParameters,
inBuffer: inBuffer,
inputCount: coords.count)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
public class FractalNoiseMetalNoise2: FractalNoiseMetalNoiseShader {
static var functionName: String = "fractalNoise2"

static var metalFunction: String =
"""
kernel void \(functionName)(
constant FractalNoiseMetalParameters &uniforms [[ buffer(0) ]],
constant const float2 * in [[ buffer(1) ]],
device float * out [[ buffer(2) ]],
uint2 thread_position_in_grid [[ thread_position_in_grid ]]
) {
int index = thread_position_in_grid.x;
}
"""

static var metalFunctionWithNormal: String? = nil

static var functionWithNormalName: String? = nil

static var baseFunction: String =
"""
"""
}

Loading

0 comments on commit eee874f

Please sign in to comment.