I'm trying to add a SCNGeometryTessellator
to some SceneKit geometry that uses a custom SCNProgram
. My geometry renders fine normally, but as soon as I add the SCNGeometryTessellator
, I see this error:
[SceneKit] Error: Compiler error while building render pipeline state for node <C3DNode:0x1053e9700 "(null)"
geometry: <C3DParametricGeometry<Plane>:0x1053e9160 "(null)"
mesh: <C3DMesh 0x282590620 "(null)"
element0: <C3DMeshElement 0x2823922b0 type:triangles primCount:200 channels:1 indexBytes:2 offset:0 acmr:0.605000 inst:1 dataSize:1200 shared:0x0>
source position (channel:0) : <C3DMeshSource 0x2837fbbf0(position) data:(0x281009ce0) mut:0 count:121 type:float3 divisor:0 mtl:0 offset:0 stride:32>
source normal (channel:0) : <C3DMeshSource 0x2837fb480(normal) data:(0x281009ce0) mut:0 count:121 type:float3 divisor:0 mtl:0 offset:12 stride:32>
source texcoord (channel:0) : <C3DMeshSource 0x2837fbb80(texcoord) data:(0x281009ce0) mut:0 count:121 type:float2 divisor:0 mtl:0 offset:24 stride:32>
renderable element0: <C3DMeshElement 0x2823922b0 type:triangles primCount:200 channels:1 indexBytes:2 offset:0 acmr:0.605000 inst:1 dataSize:1200 shared:0x0>
renderable source position: <C3DMeshSource 0x2837fbbf0(position) data:(0x281009ce0) mut:0 count:121 type:float3 divisor:0 mtl:0 offset:0 stride:32>
renderable source normal: <C3DMeshSource 0x2837fb480(normal) data:(0x281009ce0) mut:0 count:121 type:float3 divisor:0 mtl:0 offset:12 stride:32>
renderable source texcoord: <C3DMeshSource 0x2837fbb80(texcoord) data:(0x281009ce0) mut:0 count:121 type:float2 divisor:0 mtl:0 offset:24 stride:32>
>
mat0: <C3DMaterial 0x2837159d0 : "(null)", custom <C3DFXTechnique>>
>
>:
Error Domain=AGXMetalA14 Code=3 "Attribute 0 incompatible with MTLStepFunctionPerPatchControlPoint." UserInfo={NSLocalizedDescription=Attribute 0 incompatible with MTLStepFunctionPerPatchControlPoint.}
[SceneKit] Error: _executeProgram - no pipeline state
Here's how I create my geometry:
let program = SCNProgram()
program.vertexFunctionName = "myVertexShader"
program.fragmentFunctionName = "myFragmentShader"
let mat = SCNMaterial()
mat.program = previewProgram
let plane = SCNPlane()
plane.widthSegmentCount = 1
plane.heightSegmentCount = 1
plane.firstMaterial = mat
let tessellator = SCNGeometryTessellator()
tessellator.edgeTessellationFactor = 10.0
tessellator.insideTessellationFactor = 10.0
tessellator.smoothingMode = .pnTriangles
plane.tessellator = tessellator
// add plane node to scene...
The tesselator actually works if I remove the custom material, but I need to use a SCNProgram.
What is causing this error and how can I use SCNGeometryTessellator
?
Once you add a tessellator
an SCNGeometry
, your SCNProgram
needs to use a post-tessellation vertex function instead of a standard vertex function. The only doc I've found that really covers this is Apple's metal tessellation programming guide
While I'm not an expect on Metal tessellation, here's my understanding of the issue:
Let's say your normal vertex function looks something like this:
struct VertexInput {
float3 position [[attribute(SCNVertexSemanticPosition)]];
float3 normal [[attribute(SCNVertexSemanticNormal)]];
float2 texCoords [[attribute(SCNVertexSemanticTexcoord0)]];
};
vertex ColorInOut myVertexShader(VertexInput in [[ stage_in ]], ...) {
...
}
The error about Attribute 0 incompatible with MTLStepFunctionPerPatchControlPoint
is explaining that your input type (VertexInput
) does not match the expected vertex input for the current render pipeline. This is because once you add tessellation, the render pipeline changes to use a special post tessellation vertex function instead of a standard vertex function.
A post tessellation vertex function takes input from the tessellator instead of from your geometry. Here's the above vertex shader converted to a post tessellation vertex function:
struct PatchIn {
patch_control_point<VertexInput> control_points;
};
[[patch(triangle, 3)]]
vertex ColorInOut myVertexShader(PatchIn patchIn [[stage_in]],
float3 patch_coord [[ position_in_patch ]], ...)
{
// We have to compute the correct input positions from the tessellation data
// Note that there's probably a cleaner/better way to do this
// Barycentric coordinates
float u = patch_coord.x;
float v = patch_coord.y;
float w = patch_coord.z;
// Convert to cartesian coordinates
const float3 pos = float3(
u * patchIn.control_points[0].position.x + v * patchIn.control_points[1].position.x + w * patchIn.control_points[2].position.x,
u * patchIn.control_points[0].position.y + v * patchIn.control_points[1].position.y + w * patchIn.control_points[2].position.y,
u * patchIn.control_points[0].position.z + v * patchIn.control_points[1].position.z + w * patchIn.control_points[2].position.z);
...
}
After rewriting the vertex function to a post tessellation vertex function, your geometry should render with tessellation and your custom SCNProgram