Get all the pieces together
Still doesn't render though. :\
This commit is contained in:
parent
8a664fff70
commit
67134c157b
5 changed files with 158 additions and 22 deletions
|
@ -14,6 +14,7 @@
|
|||
C0C15AA0218DE0B2007494E2 /* Renderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0C15A9F218DE0B2007494E2 /* Renderer.swift */; };
|
||||
C0C15AA2218DE21E007494E2 /* Math.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0C15AA1218DE21E007494E2 /* Math.swift */; };
|
||||
C0C15AA4218DE615007494E2 /* Terrain.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0C15AA3218DE615007494E2 /* Terrain.swift */; };
|
||||
C0C15AA6218DF065007494E2 /* Shaders.metal in Sources */ = {isa = PBXBuildFile; fileRef = C0C15AA5218DF065007494E2 /* Shaders.metal */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
|
@ -27,6 +28,7 @@
|
|||
C0C15A9F218DE0B2007494E2 /* Renderer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Renderer.swift; sourceTree = "<group>"; };
|
||||
C0C15AA1218DE21E007494E2 /* Math.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Math.swift; sourceTree = "<group>"; };
|
||||
C0C15AA3218DE615007494E2 /* Terrain.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Terrain.swift; sourceTree = "<group>"; };
|
||||
C0C15AA5218DF065007494E2 /* Shaders.metal */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.metal; path = Shaders.metal; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
|
@ -64,6 +66,7 @@
|
|||
C0C15A9F218DE0B2007494E2 /* Renderer.swift */,
|
||||
C0C15AA3218DE615007494E2 /* Terrain.swift */,
|
||||
C0C15AA1218DE21E007494E2 /* Math.swift */,
|
||||
C0C15AA5218DF065007494E2 /* Shaders.metal */,
|
||||
C0C15A8F218DDD87007494E2 /* Assets.xcassets */,
|
||||
C0C15A91218DDD87007494E2 /* MainMenu.xib */,
|
||||
C0C15A94218DDD87007494E2 /* Info.plist */,
|
||||
|
@ -142,6 +145,7 @@
|
|||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
C0C15AA6218DF065007494E2 /* Shaders.metal in Sources */,
|
||||
C0C15A9D218DDDC3007494E2 /* TerrainViewController.swift in Sources */,
|
||||
C0C15AA0218DE0B2007494E2 /* Renderer.swift in Sources */,
|
||||
C0C15AA4218DE615007494E2 /* Terrain.swift in Sources */,
|
||||
|
|
|
@ -12,10 +12,67 @@ import MetalKit
|
|||
|
||||
class Renderer: NSObject, MTKViewDelegate {
|
||||
|
||||
let device: MTLDevice
|
||||
var device: MTLDevice!
|
||||
var library: MTLLibrary!
|
||||
var commandQueue: MTLCommandQueue!
|
||||
var renderPipeline: MTLRenderPipelineState!
|
||||
|
||||
init(device: MTLDevice) {
|
||||
var terrainGridSize = CGSize(width: 11, height: 11)
|
||||
var terrain = Terrain()
|
||||
|
||||
func setupMetal(withDevice device: MTLDevice, pixelFormat: MTLPixelFormat) {
|
||||
self.device = device
|
||||
|
||||
guard let queue = device.makeCommandQueue() else {
|
||||
fatalError("Unable to create Metal command queue")
|
||||
}
|
||||
queue.label = "Terrain"
|
||||
self.commandQueue = queue
|
||||
|
||||
let bundle = Bundle(for: type(of: self))
|
||||
guard let library = try? device.makeDefaultLibrary(bundle: bundle) else {
|
||||
fatalError("Unable to create default Metal library")
|
||||
}
|
||||
self.library = library
|
||||
|
||||
setupRenderPipeline(withDevice: device, library: library, pixelFormat: pixelFormat)
|
||||
}
|
||||
|
||||
func setupRenderPipeline(withDevice device: MTLDevice, library: MTLLibrary, pixelFormat: MTLPixelFormat) {
|
||||
let vertexShader = library.makeFunction(name: "passthroughVertex")
|
||||
let fragmentShader = library.makeFunction(name: "passthroughFragment")
|
||||
|
||||
let desc = MTLRenderPipelineDescriptor()
|
||||
desc.label = "Pixel Pipeline"
|
||||
desc.vertexFunction = vertexShader
|
||||
desc.fragmentFunction = fragmentShader
|
||||
if let renderAttachment = desc.colorAttachments[0] {
|
||||
renderAttachment.pixelFormat = pixelFormat
|
||||
// Pulled all this from SO. I don't know what it means, but it makes the alpha channel work.
|
||||
// TODO: Learn what this means???
|
||||
// https://stackoverflow.com/q/43727335/1174185
|
||||
renderAttachment.isBlendingEnabled = true
|
||||
renderAttachment.alphaBlendOperation = .add
|
||||
renderAttachment.rgbBlendOperation = .add
|
||||
renderAttachment.sourceRGBBlendFactor = .sourceAlpha
|
||||
renderAttachment.sourceAlphaBlendFactor = .sourceAlpha
|
||||
renderAttachment.destinationRGBBlendFactor = .oneMinusSourceAlpha
|
||||
renderAttachment.destinationAlphaBlendFactor = .oneMinusSourceAlpha
|
||||
}
|
||||
|
||||
do {
|
||||
renderPipeline = try device.makeRenderPipelineState(descriptor: desc)
|
||||
} catch let e {
|
||||
print("Couldn't set up pixel pipeline! \(e)")
|
||||
renderPipeline = nil
|
||||
}
|
||||
}
|
||||
|
||||
func prepareToRender() {
|
||||
guard let buffer = device.makeBuffer(length: terrain.minimumBufferSize(forGridSize: terrainGridSize), options: .storageModeShared) else {
|
||||
fatalError("Couldn't create terrain buffer")
|
||||
}
|
||||
terrain.generateVertexes(intoBuffer: buffer, size: terrainGridSize)
|
||||
}
|
||||
|
||||
func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {
|
||||
|
@ -23,7 +80,29 @@ class Renderer: NSObject, MTKViewDelegate {
|
|||
}
|
||||
|
||||
func draw(in view: MTKView) {
|
||||
// TODO.
|
||||
guard let buffer = commandQueue.makeCommandBuffer() else {
|
||||
return
|
||||
}
|
||||
|
||||
var didEncode = false
|
||||
buffer.label = "Terrain"
|
||||
|
||||
if let renderPass = view.currentRenderPassDescriptor {
|
||||
if let encoder = buffer.makeRenderCommandEncoder(descriptor: renderPass) {
|
||||
encoder.label = "Terrain"
|
||||
encoder.setRenderPipelineState(renderPipeline)
|
||||
encoder.setVertexBuffer(terrain.buffer, offset: 0, index: 0)
|
||||
encoder.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: terrain.vertexCount(forGridSize: terrainGridSize))
|
||||
encoder.setTriangleFillMode(.lines)
|
||||
encoder.endEncoding()
|
||||
didEncode = true
|
||||
}
|
||||
}
|
||||
|
||||
if didEncode, let drawable = view.currentDrawable {
|
||||
buffer.present(drawable)
|
||||
}
|
||||
buffer.commit()
|
||||
}
|
||||
|
||||
}
|
||||
|
|
35
Terrain/Shaders.metal
Normal file
35
Terrain/Shaders.metal
Normal file
|
@ -0,0 +1,35 @@
|
|||
//
|
||||
// Shaders.metal
|
||||
// Terrain
|
||||
//
|
||||
// Created by Eryn Wells on 11/3/18.
|
||||
// Copyright © 2018 Eryn Wells. All rights reserved.
|
||||
//
|
||||
|
||||
#include <metal_stdlib>
|
||||
using namespace metal;
|
||||
|
||||
// From HelloCompute sample code project.
|
||||
// Vertex shader outputs and per-fragmeht inputs. Includes clip-space position and vertex outputs interpolated by rasterizer and fed to each fragment genterated by clip-space primitives.
|
||||
struct RasterizerData {
|
||||
// The [[position]] attribute qualifier of this member indicates this value is the clip space position of the vertex when this structure is returned from the vertex shader.
|
||||
float4 position [[position]];
|
||||
|
||||
// Since this member does not have a special attribute qualifier, the rasterizer will interpolate its value with values of other vertices making up the triangle and pass that interpolated value to the fragment shader for each fragment in that triangle.
|
||||
float2 textureCoordinate;
|
||||
};
|
||||
|
||||
vertex RasterizerData passthroughVertex(constant packed_float3 *vertexes [[buffer(0)]],
|
||||
uint vid [[vertex_id]])
|
||||
{
|
||||
constant packed_float3 &v = vertexes[vid];
|
||||
RasterizerData out;
|
||||
out.position = float4(v, 1.0);
|
||||
out.textureCoordinate = float2();
|
||||
return out;
|
||||
}
|
||||
|
||||
fragment half4 passthroughFragment(RasterizerData in [[stage_in]])
|
||||
{
|
||||
return half4(1.0);
|
||||
}
|
|
@ -9,40 +9,52 @@
|
|||
import Cocoa
|
||||
|
||||
class Terrain: NSObject {
|
||||
static let vertexesPerCell = 6
|
||||
|
||||
var buffer: MTLBuffer?
|
||||
|
||||
override init() {
|
||||
super.init()
|
||||
}
|
||||
|
||||
func vertexCount(forGridSize size: CGSize) -> Int {
|
||||
let (width, height) = (Int(size.width), Int(size.height))
|
||||
let count = Terrain.vertexesPerCell * width * height
|
||||
return count
|
||||
}
|
||||
|
||||
func minimumBufferSize(forGridSize size: CGSize) -> Int {
|
||||
let float3Stride = MemoryLayout<Float3>.stride
|
||||
let count = vertexCount(forGridSize: size)
|
||||
let minimumLength = float3Stride * count
|
||||
return minimumLength
|
||||
}
|
||||
|
||||
/// Generate a grid of triangles and place the result in the given buffer.
|
||||
/// @param buffer The buffer to copy the results into
|
||||
/// @param size The size of the grid. Each square is a pair of triangles.
|
||||
func generateVertexes(intoBuffer buffer: MTLBuffer, size: CGSize) {
|
||||
let VertexesPerCell = 6
|
||||
|
||||
let float3Stride = MemoryLayout<Float3>.stride
|
||||
let (width, height) = (Int(size.width), Int(size.height))
|
||||
let expectedCount = VertexesPerCell * width * height
|
||||
let expectedLength = float3Stride * expectedCount
|
||||
let expectedLength = minimumBufferSize(forGridSize: size)
|
||||
guard buffer.length >= expectedLength else {
|
||||
fatalError("Terrain.generateVertexes: buffer must be at least \(expectedLength) bytes to fix grid of size \(size)")
|
||||
}
|
||||
|
||||
var vertexes = [Float3]()
|
||||
vertexes.reserveCapacity(expectedCount)
|
||||
vertexes.reserveCapacity(vertexCount(forGridSize: size))
|
||||
|
||||
let (cellWidth, cellHeight) = (Float(2.0) / Float(width), Float(2.0) / Float(height))
|
||||
let (cellWidth, cellHeight) = (Float(2.0) / Float(size.width), Float(2.0) / Float(size.height))
|
||||
for y in 0..<Int(size.height) {
|
||||
for x in 0..<Int(size.width) {
|
||||
let base = VertexesPerCell * (y * width + x)
|
||||
vertexes[base+0] = Float3(x: Float(x) * cellWidth, y: Float(y) * cellHeight, z: 0.0)
|
||||
vertexes[base+1] = Float3(x: Float(x) * cellWidth, y: Float(y) * cellHeight + cellHeight, z: 0.0)
|
||||
vertexes[base+2] = Float3(x: Float(x) * cellWidth + cellWidth, y: Float(y) * cellHeight, z: 0.0)
|
||||
vertexes[base+3] = Float3(x: Float(x) * cellWidth + cellWidth, y: Float(y) * cellHeight, z: 0.0)
|
||||
vertexes[base+4] = Float3(x: Float(x) * cellWidth, y: Float(y) * cellHeight + cellHeight, z: 0.0)
|
||||
vertexes[base+5] = Float3(x: Float(x) * cellWidth + cellWidth, y: Float(y) * cellHeight + cellHeight, z: 0.0)
|
||||
vertexes.append(Float3(x: Float(x) * cellWidth, y: Float(y) * cellHeight, z: 0.0))
|
||||
vertexes.append(Float3(x: Float(x) * cellWidth, y: Float(y) * cellHeight + cellHeight, z: 0.0))
|
||||
vertexes.append(Float3(x: Float(x) * cellWidth + cellWidth, y: Float(y) * cellHeight, z: 0.0))
|
||||
vertexes.append(Float3(x: Float(x) * cellWidth + cellWidth, y: Float(y) * cellHeight, z: 0.0))
|
||||
vertexes.append(Float3(x: Float(x) * cellWidth, y: Float(y) * cellHeight + cellHeight, z: 0.0))
|
||||
vertexes.append(Float3(x: Float(x) * cellWidth + cellWidth, y: Float(y) * cellHeight + cellHeight, z: 0.0))
|
||||
}
|
||||
}
|
||||
|
||||
self.buffer = buffer
|
||||
memcpy(buffer.contents(), vertexes, expectedLength)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,11 +29,17 @@ class TerrainViewController: NSViewController {
|
|||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
guard let device = metalView.device else {
|
||||
fatalError("Couldn't get device from Metal view")
|
||||
}
|
||||
renderer = Renderer(device: device)
|
||||
renderer = Renderer()
|
||||
metalView.delegate = renderer
|
||||
}
|
||||
|
||||
override func viewWillAppear() {
|
||||
super.viewWillAppear()
|
||||
guard let device = metalView.device else {
|
||||
return
|
||||
}
|
||||
renderer.setupMetal(withDevice: device, pixelFormat: metalView.colorPixelFormat)
|
||||
renderer.prepareToRender()
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue