Common/Shaders.metal
/* |
Copyright (C) 2016 Apple Inc. All Rights Reserved. |
See LICENSE.txt for this sample’s licensing information |
Abstract: |
Shader functions for the Game of Life sample. Define the core of the GPU-based simulation |
and describe how to draw the current game state to the screen |
*/ |
#include <metal_stdlib> |
#include <simd/simd.h> |
using namespace metal; |
// Set of vectors to the eight neighbors of a grid cell |
constant float2 kNeighborDirections[] = |
{ |
float2(-1, -1), float2(-1, 0), float2(-1, 1), |
float2( 0, -1), /* center */ float2( 0, 1), |
float2( 1, -1), float2( 1, 0), float2( 1, 1), |
}; |
// Likelihood that a random cell will become alive when interaction happens at an adjacent cell |
constant float kSpawnProbability = 0.444; |
// Values that represent "aliveness" and "maximum deadness" |
constant int kCellValueAlive = 0; |
constant int kCellValueDead = 255; |
typedef struct |
{ |
packed_float2 position; |
packed_float2 texCoords; |
} VertexIn; |
typedef struct { |
float4 position [[position]]; |
float2 texCoords; |
} FragmentVertex; |
vertex FragmentVertex lighting_vertex(device VertexIn *vertexArray [[buffer(0)]], |
uint vertexIndex [[vertex_id]]) |
{ |
FragmentVertex out; |
out.position = float4(vertexArray[vertexIndex].position, 0, 1); |
out.texCoords = vertexArray[vertexIndex].texCoords; |
return out; |
} |
fragment half4 lighting_fragment(FragmentVertex in [[stage_in]], |
texture2d<uint, access::sample> gameGrid [[texture(0)]], |
texture2d<half, access::sample> colorMap [[texture(1)]]) |
{ |
// We sample the game grid to get the value of a cell. The cell is alive |
// if its value is exactly 0; otherwise the value represents the number |
// of simulation steps the cell has been dead. We normalize this to between 0 and 1. |
constexpr sampler nearestSampler(coord::normalized, filter::nearest); |
float deadTime = gameGrid.sample(nearestSampler, in.texCoords).r / 255.0; |
// In order to color the simulation, we map the aliveness of a cell onto |
// a color with a 1D texture that contains a gradient. |
half4 color = colorMap.sample(nearestSampler, float2(deadTime, 0)); |
return color; |
} |
/// This utility function transforms a 2D integral vector into a random-looking |
/// number between 0 and 1 with roughly uniform distribution |
static float hash(int2 v){ |
return fract(sin(dot(float2(v), float2(12.9898, 78.233))) * 43758.5453); |
} |
kernel void activate_random_neighbors(texture2d<uint, access::write> writeTexture [[texture(0)]], |
constant uint2 *cellPositions [[buffer(0)]], |
ushort2 gridPosition [[thread_position_in_grid]]) |
{ |
// Iterate over the eight neighbors of this grid cell, quasi-randomly setting each neighbor |
// to either alive or maximally dead |
for (ushort i = 0; i < 8; ++i) |
{ |
int2 neighborPosition = int2(cellPositions[gridPosition.x]) + int2(kNeighborDirections[i]); |
ushort cellValue = hash(neighborPosition) < kSpawnProbability ? kCellValueAlive : kCellValueDead; |
writeTexture.write(cellValue, uint2(neighborPosition)); |
} |
} |
/// This kernel function runs one step of a Game of Life simulation, reading |
/// the previous game state from one texture, and writing the updated state |
/// into another texture |
kernel void game_of_life(texture2d<uint, access::sample> readTexture [[texture(0)]], |
texture2d<uint, access::write> writeTexture [[texture(1)]], |
sampler wrapSampler [[sampler(0)]], |
ushort2 gridPosition [[thread_position_in_grid]]) |
{ |
ushort width = readTexture.get_width(); |
ushort height = readTexture.get_height(); |
float2 bounds(width, height); |
float2 position = float2(gridPosition); |
// Don't perform the update or the write if we would be going out of bounds of the grid |
if (gridPosition.x < width && gridPosition.y < height) |
{ |
// Count up the number of neighbors of this cell that are alive |
ushort neighbors = 0; |
for (int i = 0; i < 8; ++i) |
{ |
// Sample from the current game state texture, wrapping around edges if necessary |
float2 coords = (position + kNeighborDirections[i] + float2(0.5)) / bounds; |
ushort cellValue = readTexture.sample(wrapSampler, coords).r; |
neighbors += (cellValue == kCellValueAlive) ? 1 : 0; |
} |
// Determine if this cell is itself alive |
ushort deadFrames = readTexture.read(uint2(position)).r; |
/* |
The rules of the Game of Life: |
Any live cell with fewer than two live neighbours dies, as if caused by under-population. |
Any live cell with two or three live neighbours lives on to the next generation. |
Any live cell with more than three live neighbours dies, as if by over-population. |
Any dead cell with exactly three live neighbours becomes a live cell, as if by reproduction. |
*/ |
bool alive = (deadFrames == 0 && (neighbors == 2 || neighbors == 3)) || (deadFrames > 0 && (neighbors == 3)); |
// If we are alive, keep our value at 0; otherwise increment the number of frames we've been dead |
ushort cellValue = alive ? kCellValueAlive : deadFrames + 1; |
// Finally, write the new "aliveness" of this cell into the next game state texture |
writeTexture.write(cellValue, uint2(position)); |
} |
} |
Copyright © 2016 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2016-09-13