Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Metal renderer #19

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Source/Engine/Color.swift
Original file line number Diff line number Diff line change
@@ -25,7 +25,7 @@ public extension Color {
static let clear = Color(r: 0, g: 0, b: 0, a: 0)
static let black = Color(r: 0, g: 0, b: 0)
static let white = Color(r: 255, g: 255, b: 255)
static let red = Color(r: 217, g: 87, b: 99)
static let red = Color(r: 255, g: 0, b: 0)
static let green = Color(r: 153, g: 229, b: 80)
static let yellow = Color(r: 251, g: 242, b: 54)
}
20 changes: 17 additions & 3 deletions Source/Rampage.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
@@ -66,6 +66,8 @@
01D09B0B22A7F7570052745A /* Texture.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01D09B0A22A7F7570052745A /* Texture.swift */; };
01D0F5D922F80E1600682CA1 /* RampageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01D0F5D822F80E1600682CA1 /* RampageTests.swift */; };
01D0F5F122FF095E00682CA1 /* Door.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01D0F5F022FF095E00682CA1 /* Door.swift */; };
01D4DF2325CAE9540081DEFB /* MetalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01D4DF2225CAE9540081DEFB /* MetalView.swift */; };
01D4DF2B25CAF0080081DEFB /* MetalView.metal in Sources */ = {isa = PBXBuildFile; fileRef = 01D4DF2A25CAF0080081DEFB /* MetalView.metal */; };
01DD25AA244FA74900D00FE5 /* Font.json in Resources */ = {isa = PBXBuildFile; fileRef = 01DD25A9244FA74900D00FE5 /* Font.json */; };
01DD25AC244FA85E00D00FE5 /* Font.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01DD25AB244FA85E00D00FE5 /* Font.swift */; };
01E3963A2342758D00D02236 /* Pushwall.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01E396392342758D00D02236 /* Pushwall.swift */; };
@@ -182,6 +184,9 @@
01D0F5D822F80E1600682CA1 /* RampageTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RampageTests.swift; sourceTree = "<group>"; };
01D0F5DA22F80E1600682CA1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
01D0F5F022FF095E00682CA1 /* Door.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Door.swift; sourceTree = "<group>"; };
01D4DF2225CAE9540081DEFB /* MetalView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetalView.swift; sourceTree = "<group>"; };
01D4DF2A25CAF0080081DEFB /* MetalView.metal */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.metal; path = MetalView.metal; sourceTree = "<group>"; };
01D4DF3525CAF4230081DEFB /* MetalView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MetalView.h; sourceTree = "<group>"; };
01DD25A9244FA74900D00FE5 /* Font.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = Font.json; sourceTree = "<group>"; };
01DD25AB244FA85E00D00FE5 /* Font.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Font.swift; sourceTree = "<group>"; };
01E396392342758D00D02236 /* Pushwall.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Pushwall.swift; sourceTree = "<group>"; };
@@ -270,6 +275,9 @@
016E41B4228E9A5B00ACF137 /* ViewController.swift */,
01D09AF422A482450052745A /* UIImage+Bitmap.swift */,
0199F57323E242D4003E3F08 /* SoundManager.swift */,
01D4DF2225CAE9540081DEFB /* MetalView.swift */,
01D4DF2A25CAF0080081DEFB /* MetalView.metal */,
01D4DF3525CAF4230081DEFB /* MetalView.h */,
016E41B6228E9A5B00ACF137 /* Main.storyboard */,
01D09B0022A493A70052745A /* Levels.json */,
01DD25A9244FA74900D00FE5 /* Font.json */,
@@ -454,7 +462,7 @@
attributes = {
DefaultBuildSystemTypeForWorkspace = Original;
LastSwiftUpdateCheck = 1010;
LastUpgradeCheck = 1130;
LastUpgradeCheck = 1230;
ORGANIZATIONNAME = "Nick Lockwood";
TargetAttributes = {
0108A65623F4D84C0075E1AF = {
@@ -564,9 +572,11 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
01D4DF2325CAE9540081DEFB /* MetalView.swift in Sources */,
01D09AF522A482450052745A /* UIImage+Bitmap.swift in Sources */,
0199F57423E242D4003E3F08 /* SoundManager.swift in Sources */,
016E41B5228E9A5B00ACF137 /* ViewController.swift in Sources */,
01D4DF2B25CAF0080081DEFB /* MetalView.metal in Sources */,
016E41B3228E9A5B00ACF137 /* AppDelegate.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -744,6 +754,7 @@
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
@@ -769,7 +780,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
@@ -805,6 +816,7 @@
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
@@ -824,7 +836,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = iphoneos;
@@ -848,6 +860,7 @@
);
PRODUCT_BUNDLE_IDENTIFIER = com.charcoaldesign.Rampage;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = Rampage/MetalView.h;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
@@ -867,6 +880,7 @@
);
PRODUCT_BUNDLE_IDENTIFIER = com.charcoaldesign.Rampage;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = Rampage/MetalView.h;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1130"
LastUpgradeVersion = "1230"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1130"
LastUpgradeVersion = "1230"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
42 changes: 42 additions & 0 deletions Source/Rampage/MetalView.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
//
// MetalView.h
// Rampage
//
// Created by Nick Lockwood on 03/02/2021.
// Copyright © 2021 Nick Lockwood. All rights reserved.
//

#ifndef MetalView_h
#define MetalView_h

#ifdef __METAL_VERSION__
#define NS_ENUM(_type, _name) enum _name : _type _name; enum _name : _type
#define NSInteger metal::int32_t
#else
#import <Foundation/Foundation.h>
#endif

#include <simd/simd.h>

typedef NS_ENUM(NSInteger, BufferIndex) {
BufferIndexMeshPositions = 0,
BufferIndexUniforms = 1
};

typedef NS_ENUM(NSInteger, VertexAttribute) {
VertexAttributePosition = 0,
VertexAttributeTexcoord = 1,
VertexAttributeColor = 2
};

typedef NS_ENUM(NSInteger, TextureIndex) {
TextureIndexColor = 0,
};

typedef struct {
matrix_float4x4 projectionMatrix;
matrix_float4x4 modelViewMatrix;
matrix_float4x4 orthoMatrix;
} Uniforms;

#endif /* MetalView_h */
78 changes: 78 additions & 0 deletions Source/Rampage/MetalView.metal
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
//
// Shaders.metal
// Rampage
//
// Created by Nick Lockwood on 03/02/2021.
// Copyright © 2021 Nick Lockwood. All rights reserved.
//

#include <metal_stdlib>
#include <simd/simd.h>

// Including header shared between this Metal shader code and Swift/C code executing Metal API commands
#import "MetalView.h"

using namespace metal;

typedef struct {
float3 position [[attribute(VertexAttributePosition)]];
float2 texCoord [[attribute(VertexAttributeTexcoord)]];
uchar4 color [[attribute(VertexAttributeColor)]];
} Vertex;

typedef struct {
float4 position [[position]];
float2 texCoord;
uchar4 color;
} ColorInOut;

vertex ColorInOut vertexShader(
device const Vertex *in [[buffer(BufferIndexMeshPositions)]],
constant Uniforms & uniforms [[buffer(BufferIndexUniforms)]],
const uint vid [[vertex_id]]
) {
ColorInOut out;

float4 position = float4(in[vid].position, 1.0);
out.position = uniforms.projectionMatrix * uniforms.modelViewMatrix * position;
out.texCoord = in[vid].texCoord;
out.color = in[vid].color;

return out;
}

vertex ColorInOut orthoVertexShader(
device const Vertex *in [[buffer(BufferIndexMeshPositions)]],
constant Uniforms & uniforms [[buffer(BufferIndexUniforms)]],
const uint vid [[vertex_id]]
) {
ColorInOut out;

float4 position = float4(in[vid].position, 1.0);
out.position = uniforms.orthoMatrix * position;
out.texCoord = in[vid].texCoord;
out.color = in[vid].color;

return out;
}

constexpr sampler colorSampler(mip_filter::linear,
mag_filter::nearest,
min_filter::linear);

fragment float4 fragmentShader(ColorInOut in [[stage_in]],
texture2d<half> colorMap [[texture(TextureIndexColor)]]) {
half4 colorSample = colorMap.sample(colorSampler, in.texCoord.xy);
return float4(colorSample) * float4(in.color) / 255;
}

fragment float4 effectFragmentShader(ColorInOut in [[stage_in]]) {
return float4(in.color) / 255;
}

fragment float4 fizzleFragmentShader(ColorInOut in [[stage_in]],
texture2d<half> colorMap [[texture(TextureIndexColor)]]) {
half4 colorSample = colorMap.sample(colorSampler, in.texCoord);
float4 colorIn = float4(in.color) / 255;
return colorSample.a * 0.99 < colorIn.a ? float4(colorIn.rgb, 1.0) : float4(0);
}
819 changes: 819 additions & 0 deletions Source/Rampage/MetalView.swift

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions Source/Rampage/UIImage+Bitmap.swift
Original file line number Diff line number Diff line change
@@ -10,7 +10,7 @@ import UIKit
import Engine
import Renderer

extension UIImage {
public extension UIImage {
convenience init?(bitmap: Bitmap) {
let alphaInfo = CGImageAlphaInfo.premultipliedLast
let bytesPerPixel = MemoryLayout<Color>.size
@@ -42,7 +42,7 @@ extension UIImage {
}
}

extension Bitmap {
public extension Bitmap {
init?(image: UIImage) {
guard let cgImage = image.cgImage else {
return nil
41 changes: 11 additions & 30 deletions Source/Rampage/ViewController.swift
Original file line number Diff line number Diff line change
@@ -8,7 +8,6 @@

import UIKit
import Engine
import Renderer

private let joystickRadius: Double = 40
private let maximumTimeStep: Double = 1 / 20
@@ -29,12 +28,6 @@ public func loadFont() -> Font {
return try! JSONDecoder().decode(Font.self, from: jsonData)
}

public func loadTextures() -> Textures {
return Textures(loader: { name in
Bitmap(image: UIImage(named: name)!)!
})
}

public extension SoundName {
var url: URL? {
return Bundle.main.url(forResource: rawValue, withExtension: "mp3")
@@ -50,10 +43,9 @@ func setUpAudio() {
}

class ViewController: UIViewController {
private let imageView = UIImageView()
private let metalView = MetalView()
private let panGesture = UIPanGestureRecognizer()
private let tapGesture = UITapGestureRecognizer()
private let textures = loadTextures()
private var game = Game(levels: loadLevels(), font: loadFont())
private var lastFrameTime = CACurrentMediaTime()
private var lastFiredTime = 0.0
@@ -66,7 +58,7 @@ class ViewController: UIViewController {
}

setUpAudio()
setUpImageView()
setUpMetalView()

let displayLink = CADisplayLink(target: self, selector: #selector(update))
displayLink.add(to: .main, forMode: .common)
@@ -127,32 +119,21 @@ class ViewController: UIViewController {
input.isFiring = false
}

let width = Int(imageView.bounds.width), height = Int(imageView.bounds.height)
var renderer = Renderer(width: width, height: height, textures: textures)
let insets = self.view.safeAreaInsets
renderer.safeArea = Rect(
min: Vector(x: Double(insets.left), y: Double(insets.top)),
max: renderer.bitmap.size - Vector(x: Double(insets.left), y: Double(insets.bottom))
)
renderer.draw(game)

imageView.image = UIImage(bitmap: renderer.bitmap)
metalView.draw(game)
}

@objc func fire(_ gestureRecognizer: UITapGestureRecognizer) {
lastFiredTime = CACurrentMediaTime()
}

func setUpImageView() {
view.addSubview(imageView)
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
imageView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
imageView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
imageView.heightAnchor.constraint(equalTo: view.heightAnchor).isActive = true
imageView.contentMode = .scaleAspectFit
imageView.backgroundColor = .black
imageView.layer.magnificationFilter = .nearest
func setUpMetalView() {
view.addSubview(metalView)
metalView.translatesAutoresizingMaskIntoConstraints = false
metalView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
metalView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
metalView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
metalView.heightAnchor.constraint(equalTo: view.heightAnchor).isActive = true
metalView.backgroundColor = .black
}
}

6 changes: 6 additions & 0 deletions Source/RampageTests/RampageTests.swift
Original file line number Diff line number Diff line change
@@ -11,6 +11,12 @@ import Engine
import Rampage
import Renderer

public func loadTextures() -> Textures {
return Textures(loader: { name in
Bitmap(image: UIImage(named: name)!)!
})
}

class RampageTests: XCTestCase {
let world = World(map: loadLevels()[0])
let textures = loadTextures()