user1841702 user1841702 - 1 year ago 119
Swift Question

iOS Metal - rendering a complete triangle

I am trying a tutorial on Metal from

This question only pertains to the first part of the tutorial where a triangle and later a cube are rendered.

I seem to have done everything as mentioned, create a vertex structure, node class, triangle (and then cube subclass), refactored my view controller and shader.

The app renders, however it renders to only half the screen like so:enter image description here

Here is my ViewController:

import UIKit
import Metal
import QuartzCore

class ViewController: UIViewController {

var device : MTLDevice! = nil
var metalLayer : CAMetalLayer! = nil
var vertexBuffer : MTLBuffer! = nil
var pipelineState : MTLRenderPipelineState! = nil
var commandQueue: MTLCommandQueue! = nil
var timer: CADisplayLink! = nil

var objectToDraw: Triangle!

override func viewDidLoad() {


override func didReceiveMemoryWarning() {
// Dispose of any resources that can be recreated.

func initialize_2(){

device = MTLCreateSystemDefaultDevice()
metalLayer = CAMetalLayer()
metalLayer.device = device
metalLayer.pixelFormat = .BGRA8Unorm
metalLayer.framebufferOnly = true
metalLayer.frame = view.layer.frame

objectToDraw = Triangle(device: device)

let defaultLibrary = device.newDefaultLibrary()
let fragmentProgram = defaultLibrary!.newFunctionWithName("basic_fragment")
let vertexProgram = defaultLibrary!.newFunctionWithName("basic_vertex")

let pipelineStateDescriptor = MTLRenderPipelineDescriptor()
pipelineStateDescriptor.vertexFunction = vertexProgram
pipelineStateDescriptor.fragmentFunction = fragmentProgram
pipelineStateDescriptor.colorAttachments[0].pixelFormat = .BGRA8Unorm

try pipelineState = device.newRenderPipelineStateWithDescriptor(pipelineStateDescriptor)
}catch is NSError{
print("Failed to create pipeline state")

commandQueue = device.newCommandQueue()

timer = CADisplayLink(target: self, selector: #selector(ViewController.gameloop_2))
timer.addToRunLoop(NSRunLoop.mainRunLoop(), forMode: NSDefaultRunLoopMode)


func gameloop_2() {
autoreleasepool {

func render_2(){
var drawable = metalLayer.nextDrawable()
objectToDraw.render(commandQueue, pipelineState: pipelineState, drawable: drawable!, clearColor: nil)

Here is my Vertex class:

import Foundation

struct Vertex{

var x, y, z : Float // position data
var r, g, b, a : Float // color data

func floatBuffer() -> [Float]{
return [x,y,x,r,g,b,a]

And the Node class:

import Foundation
import Metal
import QuartzCore

class Node {

let name: String
var vertexCount: Int
var vertexBuffer: MTLBuffer
var device: MTLDevice

init(name: String, vertices: Array<Vertex>, device: MTLDevice){
// 1
var vertexData = Array<Float>()
for vertex in vertices{
vertexData += vertex.floatBuffer()

// 2
let dataSize = vertexData.count * sizeofValue(vertexData[0])
vertexBuffer = device.newBufferWithBytes(vertexData, length: dataSize, options: [])

// 3 = name
self.device = device
vertexCount = vertices.count

func render(commandQueue: MTLCommandQueue, pipelineState: MTLRenderPipelineState, drawable: CAMetalDrawable, clearColor: MTLClearColor?){

let renderPassDescriptor = MTLRenderPassDescriptor()
renderPassDescriptor.colorAttachments[0].texture = drawable.texture
renderPassDescriptor.colorAttachments[0].loadAction = .Clear
renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColor(red: 0.0, green: 104.0/255.0, blue: 5.0/255.0, alpha: 1.0)
renderPassDescriptor.colorAttachments[0].storeAction = .Store

let commandBuffer = commandQueue.commandBuffer()

let renderEncoderOpt : MTLRenderCommandEncoder! = commandBuffer.renderCommandEncoderWithDescriptor(renderPassDescriptor)

if let renderEncoder = renderEncoderOpt {
renderEncoder.setVertexBuffer(vertexBuffer, offset: 0, atIndex: 0)
renderEncoder.drawPrimitives(.Triangle, vertexStart: 0, vertexCount: vertexCount, instanceCount: vertexCount/3)



And the Triangle class:

import Foundation
import Metal

class Triangle: Node {

init(device: MTLDevice){

let V0 = Vertex(x: 0.0, y: 1.0, z: 0.0, r: 1.0, g: 0.0, b: 0.0, a: 1.0)
let V1 = Vertex(x: -1.0, y: -1.0, z: 0.0, r: 0.0, g: 1.0, b: 0.0, a: 1.0)
let V2 = Vertex(x: 1.0, y: -1.0, z: 0.0, r: 0.0, g: 0.0, b: 1.0, a: 1.0)

var verticesArray = [V0,V1,V2]
super.init(name: "Triangle", vertices: verticesArray, device: device)


And my shader file:

#include <metal_stdlib>
using namespace metal;

struct VertexIn{
packed_float3 position;
packed_float4 color;

struct VertexOut{
float4 position [[position]];
float4 color;

vertex VertexOut basic_vertex(
const device VertexIn* vertex_array [[ buffer(0) ]],
unsigned int vid [[ vertex_id ]]) {

VertexIn VertexIn = vertex_array[vid];

VertexOut VertexOut;
VertexOut.position = float4(VertexIn.position,1);
VertexOut.color = VertexIn.color;

return VertexOut;

fragment half4 basic_fragment(VertexOut interpolated [[stage_in]]) {
return half4(interpolated.color[0], interpolated.color[1], interpolated.color[2], interpolated.color[3]);

I am trying to understand why the shader does not render a complete triangle. I am also (very) new to graphics programming so any help or direction would be most appreciated. Once again the link to the tutorial is :

Edit: I am running this on an iPad air, if that is going to make any difference. I also tried changing the coordinates in Triangle.swift, but obviously I am doing something wrong.

Answer Source

I can see a small mistake in the setting of vertices that could be causing this.

func floatBuffer() -> [Float]{
        return [x,y,x,r,g,b,a]
Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download