@@ 42,36 42,39 @@ extension MTLTexture {
typealias XImage = NSImage
#endif
- var cgImage: CGImage? {
+ var cgImage: CGImage? {
- assert(self.pixelFormat == .bgra8Unorm)
-
- // read texture as byte array
- let rowBytes = self.width * 4
- let length = rowBytes * self.height
- let bgraBytes = [UInt8](repeating: 0, count: length)
- let region = MTLRegionMake2D(0, 0, self.width, self.height)
- self.getBytes(UnsafeMutableRawPointer(mutating: bgraBytes), bytesPerRow: rowBytes, from: region, mipmapLevel: 0)
+ assert(self.pixelFormat == .bgra8Unorm)
+
+ // read texture as byte array
+ let rowBytes = self.width * 4
+ let length = rowBytes * self.height
+ let bgraBytes = [UInt8](repeating: 0, count: length)
+ let region = MTLRegionMake2D(0, 0, self.width, self.height)
+ self.getBytes(UnsafeMutableRawPointer(mutating: bgraBytes), bytesPerRow: rowBytes, from: region, mipmapLevel: 0)
- // use Accelerate framework to convert from BGRA to RGBA
- var bgraBuffer = vImage_Buffer(data: UnsafeMutableRawPointer(mutating: bgraBytes),
- height: vImagePixelCount(self.height), width: vImagePixelCount(self.width), rowBytes: rowBytes)
- let rgbaBytes = [UInt8](repeating: 0, count: length)
- var rgbaBuffer = vImage_Buffer(data: UnsafeMutableRawPointer(mutating: rgbaBytes),
- height: vImagePixelCount(self.height), width: vImagePixelCount(self.width), rowBytes: rowBytes)
- let map: [UInt8] = [2, 1, 0, 3]
- vImagePermuteChannels_ARGB8888(&bgraBuffer, &rgbaBuffer, map, 0)
+ // use Accelerate framework to convert from BGRA to RGBA
+ var bgraBuffer = vImage_Buffer(data: UnsafeMutableRawPointer(mutating: bgraBytes),
+ height: vImagePixelCount(self.height), width: vImagePixelCount(self.width), rowBytes: rowBytes)
+ let rgbaBytes = [UInt8](repeating: 0, count: length)
+ var rgbaBuffer = vImage_Buffer(data: UnsafeMutableRawPointer(mutating: rgbaBytes),
+ height: vImagePixelCount(self.height), width: vImagePixelCount(self.width), rowBytes: rowBytes)
+ let map: [UInt8] = [2, 1, 0, 3]
+ vImagePermuteChannels_ARGB8888(&bgraBuffer, &rgbaBuffer, map, 0)
- // create CGImage with RGBA
- let colorScape = CGColorSpaceCreateDeviceRGB()
- let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue)
- guard let data = CFDataCreate(nil, bgraBytes, length) else { return nil }
- guard let dataProvider = CGDataProvider(data: data) else { return nil }
- let cgImage = CGImage(width: self.width, height: self.height, bitsPerComponent: 8, bitsPerPixel: 32, bytesPerRow: rowBytes,
- space: colorScape, bitmapInfo: bitmapInfo, provider: dataProvider,
- decode: nil, shouldInterpolate: true, intent: .defaultIntent)
- return cgImage
- }
+ // flipping image virtically
+ let flippedBytes = rgbaBytes // share the buffer
+
+ // create CGImage with RGBA
+ let colorScape = CGColorSpaceCreateDeviceRGB()
+ let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue)
+ guard let data = CFDataCreate(nil, flippedBytes, length) else { return nil }
+ guard let dataProvider = CGDataProvider(data: data) else { return nil }
+ let cgImage = CGImage(width: self.width, height: self.height, bitsPerComponent: 8, bitsPerPixel: 32, bytesPerRow: rowBytes,
+ space: colorScape, bitmapInfo: bitmapInfo, provider: dataProvider,
+ decode: nil, shouldInterpolate: true, intent: .defaultIntent)
+ return cgImage
+ }
var image: XImage? {
guard let cgImage = self.cgImage else { return nil }
@@ 16,19 16,142 @@ float random(float n) {
return fract(sin(n) * 43758.5453123);
}
-float3 hsv2rgb(float3 c)
+// glsl mod <3
+float2 mod(float2 x, float2 y)
{
- const float4 K = float4(1.0, 0.66667, 0.33333, 3.0);
- float3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
- return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
+ return x - y * floor(x/y);
}
-// glsl mod <3
-float2 mod(float2 x, float2 y)
+float mod(float x, float y)
{
return x - y * floor(x/y);
}
+float3 mod(float3 x, float3 y)
+{
+ return x - y * floor(x/y);
+}
+
+float4 mod(float4 x, float4 y)
+{
+ return x - y * floor(x/y);
+}
+
+float3 hueShift(float3 color, float hue) {
+ const float3 k = float3(0.57735, 0.57735, 0.57735);
+ float cosAngle = cos(hue);
+ return float3(color * cosAngle + cross(k, color) * sin(hue) + k * dot(k, color) * (1.0 - cosAngle));
+}
+
+float4 permute(float4 x){return mod(((x*34.0)+1.0)*x, 289.0);}
+float4 taylorInvSqrt(float4 r){return 1.79284291400159 - 0.85373472095314 * r;}
+
+float snoise(float3 v){
+ const float2 C = float2(1.0/6.0, 1.0/3.0) ;
+ const float4 D = float4(0.0, 0.5, 1.0, 2.0);
+
+// First corner
+ float3 i = floor(v + dot(v, C.yyy) );
+ float3 x0 = v - i + dot(i, C.xxx) ;
+
+// Other corners
+ float3 g = step(x0.yzx, x0.xyz);
+ float3 l = 1.0 - g;
+ float3 i1 = min( g.xyz, l.zxy );
+ float3 i2 = max( g.xyz, l.zxy );
+
+ // x0 = x0 - 0. + 0.0 * C
+ float3 x1 = x0 - i1 + 1.0 * C.xxx;
+ float3 x2 = x0 - i2 + 2.0 * C.xxx;
+ float3 x3 = x0 - 1. + 3.0 * C.xxx;
+
+// Permutations
+ i = mod(i, 289.0 );
+ float4 p = permute( permute( permute(
+ i.z + float4(0.0, i1.z, i2.z, 1.0 ))
+ + i.y + float4(0.0, i1.y, i2.y, 1.0 ))
+ + i.x + float4(0.0, i1.x, i2.x, 1.0 ));
+
+// Gradients
+// ( N*N points uniformly over a square, mapped onto an octahedron.)
+ float n_ = 1.0/7.0; // N=7
+ float3 ns = n_ * D.wyz - D.xzx;
+
+ float4 j = p - 49.0 * floor(p * ns.z *ns.z); // mod(p,N*N)
+
+ float4 x_ = floor(j * ns.z);
+ float4 y_ = floor(j - 7.0 * x_ ); // mod(j,N)
+
+ float4 x = x_ *ns.x + ns.yyyy;
+ float4 y = y_ *ns.x + ns.yyyy;
+ float4 h = 1.0 - abs(x) - abs(y);
+
+ float4 b0 = float4( x.xy, y.xy );
+ float4 b1 = float4( x.zw, y.zw );
+
+ float4 s0 = floor(b0)*2.0 + 1.0;
+ float4 s1 = floor(b1)*2.0 + 1.0;
+ float4 sh = -step(h, float4(0.0));
+
+ float4 a0 = b0.xzyw + s0.xzyw*sh.xxyy ;
+ float4 a1 = b1.xzyw + s1.xzyw*sh.zzww ;
+
+ float3 p0 = float3(a0.xy,h.x);
+ float3 p1 = float3(a0.zw,h.y);
+ float3 p2 = float3(a1.xy,h.z);
+ float3 p3 = float3(a1.zw,h.w);
+
+//Normalise gradients
+ float4 norm = taylorInvSqrt(float4(dot(p0,p0), dot(p1,p1), dot(p2, p2), dot(p3,p3)));
+ p0 *= norm.x;
+ p1 *= norm.y;
+ p2 *= norm.z;
+ p3 *= norm.w;
+
+// Mix final noise value
+ float4 m = max(0.6 - float4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), 0.0);
+ m = m * m;
+ return 42.0 * dot( m*m, float4( dot(p0,x0), dot(p1,x1),
+ dot(p2,x2), dot(p3,x3) ) );
+}
+
+float3 curl(float3 pos) {
+ float3 eps = float3(1., 0., 0.);
+ float3 res = float3(0.);
+
+ float yxy1 = snoise(pos + eps.yxy);
+ float yxy2 = snoise(pos - eps.yxy);
+ float a = (yxy1 - yxy2) / (2. * eps.x);
+
+ float yyx1 = snoise(pos + eps.yyx);
+ float yyx2 = snoise(pos - eps.yyx);
+ float b = (yyx1 - yyx2) / (2. * eps.x);
+
+ res.x = a - b;
+
+ a = (yyx1 - yyx2) / (2. * eps.x);
+
+ float xyy1 = snoise(pos + eps.xyy);
+ float xyy2 = snoise(pos - eps.xyy);
+ b = (xyy1 - xyy2) / (2. * eps.x);
+
+ res.y = a - b;
+
+ a = (xyy1 - xyy2) / (2. * eps.x);
+ b = (yxy1 - yxy2) / (2. * eps.x);
+
+ res.z = a - b;
+
+ return res;
+}
+
+float3 hsv2rgb(float3 c)
+{
+ const float4 K = float4(1.0, 0.66667, 0.33333, 3.0);
+ float3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
+ return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
+}
+
kernel void setupParticles(device Particle *particles [[buffer(0)]],
device const int &width [[ buffer(1) ]],
device const int &height [[ buffer(2) ]],
@@ 38,11 161,37 @@ kernel void setupParticles(device Particle *particles [[buffer(0)]],
{
uint id = tgPos * tPerTg + tPos;
Particle p = particles[id];
- p.sensorHeading = random(id/803.134234)*26.28318;
- p.position = float2(width/2.0,height/2.0) + float2(cos(p.sensorHeading), sin(p.sensorHeading)) * (1.+random(id/150.52342309)*500);
+ p.active = 0;
+ p.position = float2(width/2.0,height/2.0) + (float2(cos(id*0.00235), sin(id*0.0059022))*(250+100));
+
+ p.sensorHeading = random(id*0.034) * 6.28318;
+
particles[id] = p;
}
+kernel void addParticles(device Particle *particles [[buffer(0)]],
+ device const int &width [[ buffer(1) ]],
+ device const int &height [[ buffer(2) ]],
+ device const float &startupProgress [[ buffer(3) ]],
+ device const int &offset [[ buffer(4) ]],
+ const uint tgPos [[ threadgroup_position_in_grid ]],
+ const uint tPerTg [[ threads_per_threadgroup ]],
+ const uint tPos [[ thread_position_in_threadgroup ]])
+{
+ uint id = tgPos * tPerTg + tPos + uint(offset);
+ Particle particle = particles[id];
+ particle.active = 1;
+ float halfWidth = width / 2.0;
+ float startX = width/4.0;
+ float2 pos = float2(halfWidth + cos(-.5+startupProgress * 3.14159 * 3.5) * halfWidth * (1.-startupProgress) * 0.75,
+ height/2.0 + sin(-.5+startupProgress * 3.14159 * 3.5) * halfWidth* (1.-startupProgress) * 0.75);
+
+ particle.position = pos;
+ particle.sensorHeading = -.5 + 3.14159 + startupProgress*3.14159*3.5 + random(id*0.104) ;
+ particles[id] = particle;
+}
+
+
kernel void updateParticles(device Particle *particles [[buffer(0)]],
device const float &time [[ buffer(1) ]],
device SimParameters *parameters [[buffer(2)]],
@@ 58,8 207,13 @@ kernel void updateParticles(device Particle *particles [[buffer(0)]],
// Create a copy of the current particle
uint id = tgPos * tPerTg + tPos;
+
+ if (id >= 131072*2) return;
Particle p = particles[id];
- uint2 texCoord = uint2(floor(p.position));
+
+ if (p.active == 0) return;
+
+ uint2 texCoord = uint2(p.position);
float sensorAngle = parameters->sensorAngle;
// Create coordinates for the sensors to sample the trail map
@@ 69,10 223,10 @@ kernel void updateParticles(device Particle *particles [[buffer(0)]],
float ccA = p.sensorHeading - sensorAngle;
float2 coordCounterClockwise = mod(p.position.xy + float2(cos(ccA), sin(ccA))*parameters->sensorDistance, viewSize);
- float trailMapSampleForward = inTrailMapTexture.read((uint2)floor(coordForward)).r;
- float trailMapSampleClockwise = inTrailMapTexture.read((uint2)floor(coordClockwise)).r;
- float trailMapSampleCounterClockwise = inTrailMapTexture.read((uint2)floor(coordCounterClockwise)).r;
- float4 color = mix(float4(0., 0., 0., 1.0), float4(1., 1., 1., 1.0), saturate(time*0.025));
+ float trailMapSampleForward = inTrailMapTexture.read((uint2)round(coordForward)).r;
+ float trailMapSampleClockwise = inTrailMapTexture.read((uint2)round(coordClockwise)).r;
+ float trailMapSampleCounterClockwise = inTrailMapTexture.read((uint2)round(coordCounterClockwise)).r;
+ float4 color = mix(float4(0., 0., 0., 1.0), float4(1., 1., 1., 1.0), saturate(time*0.1));
// Do nothing and move forward if most concentrated forward
if (trailMapSampleForward > trailMapSampleClockwise && trailMapSampleForward > trailMapSampleCounterClockwise) {
@@ 80,8 234,7 @@ kernel void updateParticles(device Particle *particles [[buffer(0)]],
}
// Rotate Randomly if not conentrated in center
else if (trailMapSampleClockwise > trailMapSampleForward && trailMapSampleCounterClockwise > trailMapSampleForward) {
- p.sensorHeading += random(p.position.x*1.333 + p.position.y*0.552) > 0.5 ? parameters->rotationAngle : -parameters->rotationAngle;
-
+ p.sensorHeading += random(id + time) > 0.5 ? parameters->rotationAngle : -parameters->rotationAngle;
}
// Rotate clockwise
else if (trailMapSampleCounterClockwise < trailMapSampleClockwise) {
@@ 92,15 245,16 @@ kernel void updateParticles(device Particle *particles [[buffer(0)]],
p.sensorHeading -= parameters->rotationAngle;
}
+ // Write particle color
outTexture.write(color, texCoord);
- outTexture.write(color, clamp(texCoord + uint2(0,1), uint2(0.), uint2(viewSize)));
- outTexture.write(color, clamp(texCoord + uint2(1,1), uint2(0.), uint2(viewSize)));
- outTexture.write(color, clamp(texCoord + uint2(1,0), uint2(0.), uint2(viewSize)));
- outTexture.write(color, clamp(texCoord + uint2(1,-1), uint2(0.), uint2(viewSize)));
- outTexture.write(color, clamp(texCoord + uint2(0,-1), uint2(0.), uint2(viewSize)));
- outTexture.write(color, clamp(texCoord + uint2(-1,-1), uint2(0.), uint2(viewSize)));
- outTexture.write(color, clamp(texCoord + uint2(-1,0), uint2(0.), uint2(viewSize)));
- outTexture.write(color, clamp(texCoord + uint2(-1,1), uint2(0.), uint2(viewSize)));
+ outTexture.write(color, clamp((uint2)(int2(texCoord) + int2(0,1)), uint2(0.), uint2(viewSize)));
+ outTexture.write(color, clamp((uint2)(int2(texCoord) + int2(1,1)), uint2(0.), uint2(viewSize)));
+ outTexture.write(color, clamp((uint2)(int2(texCoord) + int2(1,0)), uint2(0.), uint2(viewSize)));
+ outTexture.write(color, clamp((uint2)(int2(texCoord) + int2(1,-1)), uint2(0.), uint2(viewSize)));
+ outTexture.write(color, clamp((uint2)(int2(texCoord) + int2(0,-1)), uint2(0.), uint2(viewSize)));
+ outTexture.write(color, clamp((uint2)(int2(texCoord) + int2(-1,-1)), uint2(0.), uint2(viewSize)));
+ outTexture.write(color, clamp((uint2)(int2(texCoord) + int2(-1,0)), uint2(0.), uint2(viewSize)));
+ outTexture.write(color, clamp((uint2)(int2(texCoord) + int2(-1,1)), uint2(0.), uint2(viewSize)));
// Deposit
float trailMapSample = inTrailMapTexture.read(texCoord).r;
@@ 108,7 262,7 @@ kernel void updateParticles(device Particle *particles [[buffer(0)]],
outTrailMapTexture.write(float4(trailMapSample, 1., 0., 1.0), texCoord);
// Move
- p.position.xy = mod(p.position.xy + float2(cos(p.sensorHeading), sin(p.sensorHeading))*(parameters->movementOffset + random(id/520.421235) * parameters->movementOffsetRandomness), viewSize);
+ p.position.xy = mod(p.position.xy + float2(cos(p.sensorHeading), sin(p.sensorHeading))*(parameters->movementOffset + random(id*520.421235) * parameters->movementOffsetRandomness), viewSize);
particles[id] = p;
}
@@ 119,39 273,55 @@ kernel void texturePass(texture2d<float, access::sample> inTexture [[texture(0)]
texture2d<float, access::sample> inTrailTexture [[texture(2)]],
texture2d<float, access::write> outTrailTexture [[texture(3)]],
device TexturePassParameters *parameters [[buffer(0)]],
+ device const float &time [[buffer(1)]],
uint2 gid [[ thread_position_in_grid ]]) {
constexpr sampler colorSampler(coord::normalized,
address::repeat,
filter::linear);
- // Compute weighted sum of box of surrounding pixels
+
+ // Compute weighted sum of surrounding box
float3 col = float3(0.);
float3 colTrail = float3(0.);
float2 size = float2(inTexture.get_width(), inTexture.get_height());
float2 texel = 1. / size;
float2 uv = (float2)gid / size;
- float3 weight = mix(float3(0.11111111), float3(0.1111111111, 0.1, 0.075), saturate(length(uv*2-1)));
+
+ // Non uniform weights to created colored feedback.
+ float3 weight = mix(float3(0.11111111), float3(0.1111111111, 0.111111, 0.075).grb, saturate(2*length(uv*2-1)));
- for (float i = -1; i <= 1; i++ ){
- for (float j = -1; j <= 1; j++ ){
- float3 val = inTexture.sample(colorSampler, uv + float2(i,j) * texel).rgb;
+ for (int i = -1; i <= 1; i++) {
+ for (int j = -1; j <= 1; j++) {
+ float3 val = inTexture.read((uint2)(int2(gid) + int2(i,j))).rgb;
col += val * weight;
- val = inTrailTexture.sample(colorSampler, uv + float2(i,j) * texel).rgb;
+ val = inTrailTexture.read((uint2)(int2(gid) + int2(i,j))).rgb;
colTrail += val * weight;
}
}
// Add trail for mouse position
- if (length(uv - parameters->mousePosition) < 0.025) {
+ if (length(uv - parameters->mousePosition) < 0.05) {
colTrail = float3(1.0, 0.0, 0.0);
}
// Dampen
col.rgb *= parameters->additiveBlendFrameFactor;
colTrail *= parameters->additiveBlendTrailFactor;
+
+ col.rgb = hueShift(col.rgb, 0.175);
colTrail = clamp(colTrail, float3(0.01), float3(1.0));
outTexture.write(float4(col, 1.0), gid);
outTrailTexture.write(float4(colTrail, 1.0), gid);
}
+
+
+kernel void postFx(texture2d<float, access::sample> inTexture [[texture(0)]],
+ texture2d<float, access::write> outTexture [[texture(1)]],
+ uint2 gid [[ thread_position_in_grid ]]) {
+
+ float4 col = inTexture.read(gid);
+ col.rgb = 1.0 - col.rgb;
+ outTexture.write(col, gid);
+}
@@ 12,11 12,14 @@ import MetalPerformanceShaders
class Simulation: MTKView {
+ // Parameters
var simulationParameters: SimParameters
var texturePassParameters: TexturePassParameters
- var particleCount = 1024*64*2
+ var particleCount = 1024*64
+ var activeParticles = 0
+ var particlesPerFrame = 1024/4
- // Metal Devices
+ // Metal related variabbles
private var metalDevice: MTLDevice
private var commandQueue: MTLCommandQueue
private var metalLibrary: MTLLibrary
@@ 25,11 28,15 @@ class Simulation: MTKView {
private var stepFunction: MTLFunction
private var textureFunction: MTLFunction
private var initFunction: MTLFunction
-
+ private var addParticlesFunction: MTLFunction
+ private var postFxFunction: MTLFunction
+
// Compute Pipeline States
private var stepFunctionPipelineState: MTLComputePipelineState
private var initFunctionPipelineState: MTLComputePipelineState
private var textureFunctionPipelineState: MTLComputePipelineState
+ private var addParticlesFunctionPipelineState: MTLComputePipelineState
+ private var postFxPipelineState: MTLComputePipelineState
// Buffers and Textures
private var particleBuffer: MTLBuffer
@@ 39,6 46,8 @@ class Simulation: MTKView {
private var renderTextureSwap: MTLTexture?
private var frameNumber = 0
+ private var frameskip = 2
+ private var renderedFrameNumber = 0
private var mouseDown = false
// Render PNG properties
@@ 57,12 66,16 @@ class Simulation: MTKView {
self.stepFunction = metalLibrary.makeFunction(name: "updateParticles")!
self.textureFunction = metalLibrary.makeFunction(name: "texturePass")!
self.initFunction = metalLibrary.makeFunction(name: "setupParticles")!
+ self.addParticlesFunction = metalLibrary.makeFunction(name: "addParticles")!
+ self.postFxFunction = metalLibrary.makeFunction(name: "postFx")!
do
{
try self.stepFunctionPipelineState = self.metalDevice.makeComputePipelineState(function: self.stepFunction)
try self.initFunctionPipelineState = self.metalDevice.makeComputePipelineState(function: self.initFunction)
try self.textureFunctionPipelineState = self.metalDevice.makeComputePipelineState(function: self.textureFunction)
+ try self.addParticlesFunctionPipelineState = self.metalDevice.makeComputePipelineState(function: self.addParticlesFunction)
+ try self.postFxPipelineState = self.metalDevice.makeComputePipelineState(function: self.postFxFunction)
}
catch
{
@@ 70,8 83,8 @@ class Simulation: MTKView {
}
let rect = CGRect(x: 0, y: 0, width: width, height: height)
- self.simulationParameters = SimParameters(sensorAngle: 0.125,
- sensorDistance: 5.0,
+ self.simulationParameters = SimParameters(sensorAngle: 0.15,
+ sensorDistance: 8.0,
movementOffset: 1.1,
movementOffsetRandomness: 1.0,
rotationAngle: 0.5)
@@ 148,6 161,14 @@ extension Simulation {
let trailMapTexture = self.trailMapTexture,
let trailMapTextureSwap = self.trailMapTexture else { return }
+ if activeParticles < particleCount {
+ activeParticles += addParticles(commandEncoder: commandEncoder)
+ if activeParticles >= particleCount {
+ self.simulationParameters.movementOffset += 0.5;
+ self.simulationParameters.sensorAngle -= 0.035;
+ }
+ }
+
simulate(commandEncoder: commandEncoder)
dampenAndDecayTextures(drawableIn: renderTextureSwap,
@@ 155,13 176,11 @@ extension Simulation {
trailMapIn: trailMapTextureSwap,
trailMapOut: trailMapTexture,
commandEncoder: commandEncoder)
+
+ postFx(commandEncoder: commandEncoder, sourceTexture: renderTexture, targetTexture: currentDrawable.texture)
commandEncoder.endEncoding()
- blit(commandBuffer: commandBuffer,
- sourceTexture: renderTexture,
- targetTexture: currentDrawable.texture)
-
// Swap the textures
self.renderTexture = self.renderTextureSwap
self.trailMapTexture = self.trailMapTextureSwap
@@ 172,20 191,49 @@ extension Simulation {
commandBuffer.commit()
commandBuffer.waitUntilCompleted()
- if renderFrames && self.currentRenderFrame < self.numRenderFrames {
- saveFrame(texture: renderTexture, frame: self.currentRenderFrame, totalFrames: self.numRenderFrames)
+ if renderFrames && self.frameNumber % self.frameskip == 0 && self.currentRenderFrame < self.numRenderFrames {
+ saveFrame(texture: currentDrawable.texture, frame: self.currentRenderFrame, totalFrames: self.numRenderFrames)
self.currentRenderFrame += 1
}
self.frameNumber += 1
}
+ // Returns the amount of particles added
+ func addParticles(commandEncoder: MTLComputeCommandEncoder) -> Int {
+ var width = Int(self.drawableSize.width), height = Int(self.drawableSize.height)
+ var startupProgress = Float(activeParticles) / Float(particleCount)
+ var offset = activeParticles
+ var particlesPerFrame = self.particlesPerFrame
+
+ // Make sure we don't go over the amount of allocated particles
+ if activeParticles + particlesPerFrame > particleCount {
+ particlesPerFrame = particleCount - activeParticles
+ }
+
+ if particlesPerFrame > 0 {
+ commandEncoder.setComputePipelineState(self.addParticlesFunctionPipelineState)
+ commandEncoder.setBuffer(self.particleBuffer, offset: 0, index: 0)
+ commandEncoder.setBytes(&width, length: MemoryLayout<Int>.size, index: 1)
+ commandEncoder.setBytes(&height, length: MemoryLayout<Int>.size, index: 2)
+ commandEncoder.setBytes(&startupProgress, length: MemoryLayout<Float>.size, index: 3)
+ commandEncoder.setBytes(&offset, length: MemoryLayout<Int>.size, index: 4)
+
+ let threadgroupsPerGrid = MTLSize(width: (particlesPerFrame + self.addParticlesFunctionPipelineState.threadExecutionWidth - 1) / self.addParticlesFunctionPipelineState.threadExecutionWidth, height: 1, depth: 1)
+
+ let threadsPerThreadGroup = MTLSize(width: self.addParticlesFunctionPipelineState.threadExecutionWidth, height: 1, depth: 1)
+
+ commandEncoder.dispatchThreadgroups(threadgroupsPerGrid, threadsPerThreadgroup: threadsPerThreadGroup)
+ }
+ return particlesPerFrame
+ }
+
func simulate(commandEncoder: MTLComputeCommandEncoder) {
let simulationParametersBuffer = metalDevice.makeBuffer(bytes: &simulationParameters, length:MemoryLayout<SimParameters>.stride, options:[])
var frame = Float(self.frameNumber)
- let threadgroupsPerGrid = MTLSize(width: (particleCount + self.stepFunctionPipelineState.threadExecutionWidth - 1) / self.stepFunctionPipelineState.threadExecutionWidth, height: 1, depth: 1)
+ let threadgroupsPerGrid = MTLSize(width: (activeParticles + self.stepFunctionPipelineState.threadExecutionWidth - 1) / self.stepFunctionPipelineState.threadExecutionWidth, height: 1, depth: 1)
let threadsPerThreadgroup = MTLSize(width: self.initFunctionPipelineState.threadExecutionWidth, height: 1, depth: 1)
@@ 225,7 273,8 @@ extension Simulation {
}
let textureParametersBuffer = metalDevice.makeBuffer(bytes: &texturePassParameters, length:MemoryLayout<TexturePassParameters>.stride, options:[])
-
+ var frame = Float(self.frameNumber)
+
commandEncoder.setComputePipelineState(self.textureFunctionPipelineState)
commandEncoder.setTexture(drawableIn, index: 0)
@@ 233,6 282,7 @@ extension Simulation {
commandEncoder.setTexture(trailMapIn, index: 2)
commandEncoder.setTexture(trailMapOut, index: 3)
commandEncoder.setBuffer(textureParametersBuffer, offset: 0, index: 0)
+ commandEncoder.setBytes(&frame, length: MemoryLayout<Float>.size, index: 1)
let w = textureFunctionPipelineState.threadExecutionWidth
let h = textureFunctionPipelineState.maxTotalThreadsPerThreadgroup / w
@@ 250,6 300,20 @@ extension Simulation {
blitEncoder?.endEncoding()
}
+
+ func postFx(commandEncoder: MTLComputeCommandEncoder, sourceTexture: MTLTexture, targetTexture: MTLTexture) {
+ commandEncoder.setComputePipelineState(self.postFxPipelineState)
+
+ commandEncoder.setTexture(sourceTexture, index: 0)
+ commandEncoder.setTexture(targetTexture, index: 1)
+
+ let w = postFxPipelineState.threadExecutionWidth
+ let h = postFxPipelineState.maxTotalThreadsPerThreadgroup / w
+
+ let threadsPerThreadGroup = MTLSize(width: w, height: h, depth: 1)
+ let threadsPerGrid = MTLSize(width: sourceTexture.width, height: sourceTexture.height, depth: 1)
+ commandEncoder.dispatchThreads(threadsPerGrid, threadsPerThreadgroup: threadsPerThreadGroup)
+ }
}
// MARK: - Updating Parameters