Skip to content

Instantly share code, notes, and snippets.

@mike-neko
Last active October 11, 2021 00:43
Show Gist options
  • Save mike-neko/2ecd87c48e9eb82e095eb4f9620717a0 to your computer and use it in GitHub Desktop.
Save mike-neko/2ecd87c48e9eb82e095eb4f9620717a0 to your computer and use it in GitHub Desktop.
Tessellation in Metal
#include <metal_stdlib>
using namespace metal;
#pragma mark Structs
// ControlPoint
struct ControlPoint {
float4 position [[ attribute(0) ]];
};
// Patch
struct PatchIn {
patch_control_point<ControlPoint> controlPoints;
};
// VertexShader → FragmentShader
struct VertexOut {
float4 position [[ position ]];
};
#pragma mark - Kernel Function
/*
TessellationFactorsをバッファへ設定する
factor: 設定するTessellationFactors
x...edgeFactor
y...insideFactor
MTLTriangleTessellationFactorsHalf: 出力先
*/
kernel void tessellationFactorsCompute(constant float2& factor [[ buffer(0) ]],
device MTLTriangleTessellationFactorsHalf* factors [[ buffer(1) ]]) {
factors[0].edgeTessellationFactor[0] = factor.x;
factors[0].edgeTessellationFactor[1] = factor.x;
factors[0].edgeTessellationFactor[2] = factor.x;
factors[0].insideTessellationFactor = factor.y;
}
#pragma mark Post-Tessellation Vertex Function
/*
三角形用のPost-TessellationVertexShader
*/
[[ patch(triangle, 3) ]]
vertex VertexOut tessellationTriangleVertex(PatchIn patchIn [[ stage_in ]],
float3 patchCoord [[ position_in_patch ]]) {
// 座標変換
auto u = patchCoord[0];
auto v = patchCoord[1];
auto w = patchCoord[2];
auto position = u * patchIn.controlPoints[0].position
+ v * patchIn.controlPoints[1].position
+ w * patchIn.controlPoints[2].position;
VertexOut out;
out.position = float4(position);
out.position.w = 1;
return out;
}
#pragma mark Fragment Function
fragment half4 colorFragment(VertexOut in [[ stage_in ]]) {
return half4(1, 1, 1, 1); // 白で出力するだけ
}
import UIKit
import MetalKit
class ViewController: UIViewController, MTKViewDelegate {
/// StoryBoardに配置済のMTKView
@IBOutlet weak var mtkView: MTKView!
/// edgeTessellationFactorの設定値
var edgeFactor = Float(2)
/// insideTessellationFactorの設定値
var insideFactor = Float(2)
var commandQueue: MTLCommandQueue!
var computePipeline: MTLComputePipelineState!
var renderPipeline: MTLRenderPipelineState!
var controlPointsBuffer: MTLBuffer!
var tessellationFactorBuffer: MTLBuffer!
override func viewDidLoad() {
super.viewDidLoad()
let device = MTLCreateSystemDefaultDevice()!
commandQueue = device.makeCommandQueue()!
let library = device.makeDefaultLibrary()!
// MTKViewの初期設定
mtkView.sampleCount = 4
mtkView.depthStencilPixelFormat = .depth32Float_stencil8
mtkView.colorPixelFormat = .bgra8Unorm
mtkView.clearColor = MTLClearColorMake(0.3, 0.3, 0.3, 1)
mtkView.device = device
mtkView.delegate = self
// MTLVertexDescriptorの設定
let vertexDescriptor = MTLVertexDescriptor()
vertexDescriptor.attributes[0].format = .float4
vertexDescriptor.attributes[0].offset = 0
vertexDescriptor.attributes[0].bufferIndex = 0
vertexDescriptor.layouts[0].stride = MemoryLayout<float4>.stride
vertexDescriptor.layouts[0].stepFunction = .perPatchControlPoint
// MTLRenderPipelineDescriptorの設定
let renderDescriptor = MTLRenderPipelineDescriptor()
renderDescriptor.vertexDescriptor = vertexDescriptor
renderDescriptor.sampleCount = mtkView.sampleCount
renderDescriptor.colorAttachments[0].pixelFormat = mtkView.colorPixelFormat
renderDescriptor.vertexFunction = library.makeFunction(name: "tessellationTriangleVertex")
renderDescriptor.fragmentFunction = library.makeFunction(name: "colorFragment")
renderDescriptor.depthAttachmentPixelFormat = mtkView.depthStencilPixelFormat
renderDescriptor.stencilAttachmentPixelFormat = mtkView.depthStencilPixelFormat
renderDescriptor.isTessellationFactorScaleEnabled = false
renderDescriptor.tessellationFactorFormat = .half
renderDescriptor.tessellationControlPointIndexType = .none
renderDescriptor.tessellationFactorStepFunction = .constant
renderDescriptor.tessellationOutputWindingOrder = .clockwise
renderDescriptor.tessellationPartitionMode = .fractionalEven
renderDescriptor.maxTessellationFactor = 16 // ios
// 描画用のRenderPipelineの生成
renderPipeline = try! device.makeRenderPipelineState(descriptor: renderDescriptor)
// ControlPointの位置をMTLBufferへ設定
let positions = [
float4(arrayLiteral: -0.8, -0.8, 0.0, 1.0),
float4(arrayLiteral: 0.0, 0.8, 0.0, 1.0),
float4(arrayLiteral: 0.8, -0.8, 0.0, 1.0),
]
controlPointsBuffer = positions.withUnsafeBufferPointer {
device.makeBuffer(bytes: $0.baseAddress!,
length: positions.count * MemoryLayout<float4>.size,
options: .storageModeShared) // ios
}
// TessellationFactors設定用のComputePipelineの生成
let kernel = library.makeFunction(name: "tessellationFactorsCompute")!
computePipeline = try! device.makeComputePipelineState(function: kernel)
// TessellationFactors用のMTLBufferを作成
tessellationFactorBuffer = device.makeBuffer(length: MemoryLayout<MTLTriangleTessellationFactorsHalf>.size,
options: .storageModePrivate)
// 描画開始
mtkView.draw()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func draw(in view: MTKView) {
let commandBuffer = commandQueue.makeCommandBuffer()!
// TessellationFactors設定用のComputeKernel
let computeCommandEncoder = commandBuffer.makeComputeCommandEncoder()!
computeCommandEncoder.setComputePipelineState(computePipeline)
var factor = float2(edgeFactor, insideFactor)
withUnsafePointer(to: &factor) {
computeCommandEncoder.setBytes($0, length: MemoryLayout<float2>.size, index: 0)
}
computeCommandEncoder.setBuffer(tessellationFactorBuffer, offset: 0, index: 1)
computeCommandEncoder.dispatchThreadgroups(MTLSize(width: 1, height: 1, depth: 1),
threadsPerThreadgroup: MTLSize(width: 1, height: 1, depth: 1))
computeCommandEncoder.endEncoding()
// 描画
let renderDescriptor = mtkView.currentRenderPassDescriptor!
let renderEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: renderDescriptor)!
renderEncoder.setRenderPipelineState(renderPipeline)
renderEncoder.setTriangleFillMode(.lines) // ワイヤーフレームで描画
renderEncoder.setVertexBuffer(controlPointsBuffer, offset: 0, index: 0)
renderEncoder.setTessellationFactorBuffer(tessellationFactorBuffer, offset: 0, instanceStride: 0)
renderEncoder.drawPatches(numberOfPatchControlPoints: 3, patchStart: 0, patchCount: 1, patchIndexBuffer: nil, patchIndexBufferOffset: 0, instanceCount: 1, baseInstance: 0)
renderEncoder.endEncoding()
commandBuffer.present(mtkView.currentDrawable!)
commandBuffer.commit()
}
func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {
}
}
@t10-13rocket
Copy link

LGTM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment