From 1f135f709ba63609e4eee6776a23a5fa50e66156 Mon Sep 17 00:00:00 2001 From: treeform Date: Wed, 12 Aug 2026 20:41:45 -0700 Subject: [PATCH] add portable shader instance IDs --- src/shady/backends/dx12.nim | 13 +++++++-- src/shady/backends/glsl.nim | 2 +- src/shady/backends/metal4.nim | 7 ++++- src/shady/backends/shared.nim | 14 +++++++-- tests/test_backends.nim | 54 +++++++++++++++++++++++++++++++++++ tests/test_metal.nim | 8 +++++- 6 files changed, 90 insertions(+), 8 deletions(-) diff --git a/src/shady/backends/dx12.nim b/src/shady/backends/dx12.nim index 1c90264..1ddf958 100644 --- a/src/shady/backends/dx12.nim +++ b/src/shady/backends/dx12.nim @@ -121,6 +121,8 @@ proc hlslSemantic*(name: string, index: int, isOutput: bool): string = " : SV_IsFrontFace" of "gl_VertexID": " : SV_VertexID" + of "gl_InstanceID": + " : SV_InstanceID" else: let base = semanticBase(name) if base.startsWith("SV_"): @@ -133,8 +135,8 @@ proc nextSemanticIndex( counts: var Table[string, int] ): int = case name - of "fragColor", "gl_FragColor", "gl_Position", "gl_FragCoord", "gl_VertexID", - "gl_FrontFacing": + of "fragColor", "gl_FragColor", "gl_Position", "gl_FragCoord", + "gl_VertexID", "gl_InstanceID", "gl_FrontFacing": return 0 else: let base = semanticBase(name) @@ -291,6 +293,13 @@ proc emitHlslEntry*( else: result.add ",\n" result.add " uint gl_VertexID : SV_VertexID" + if not params.hasParam("gl_InstanceID") and "gl_InstanceID" in bodyCode: + if first: + result.add "\n" + first = false + else: + result.add ",\n" + result.add " uint gl_InstanceID : SV_InstanceID" if not first: result.add "\n" result.add ") {\n" diff --git a/src/shady/backends/glsl.nim b/src/shady/backends/glsl.nim index bc1d662..34a5d0a 100644 --- a/src/shady/backends/glsl.nim +++ b/src/shady/backends/glsl.nim @@ -75,7 +75,7 @@ proc glslTypeDefault*(t: string): string = const glslGlobals* = [ "gl_Position", "gl_FragCoord", "gl_GlobalInvocationID", - "gl_VertexID", "gl_FrontFacing", + "gl_VertexID", "gl_InstanceID", "gl_FrontFacing", ] const glslFunctions* = [ diff --git a/src/shady/backends/metal4.nim b/src/shady/backends/metal4.nim index 71b6292..1bb39da 100644 --- a/src/shady/backends/metal4.nim +++ b/src/shady/backends/metal4.nim @@ -102,6 +102,8 @@ proc metalAttribute*(name: string, index: int, isOutput: bool): string = " [[position]]" of "gl_VertexID": " [[vertex_id]]" + of "gl_InstanceID": + " [[instance_id]]" of "gl_FrontFacing": " [[front_facing]]" else: @@ -196,7 +198,7 @@ proc hasParam(params: openArray[EntryParam], name: string): bool = return true proc isSpecialInput(p: EntryParam): bool = - p.name in ["gl_VertexID", "gl_FrontFacing"] + p.name in ["gl_VertexID", "gl_InstanceID", "gl_FrontFacing"] proc outputLocals(params: openArray[EntryParam], level: int): string = let indent = repeat(" ", level) @@ -322,6 +324,9 @@ proc emitMetalEntry*( if not params.hasParam("gl_VertexID") and "gl_VertexID" in bodyCode: result.addComma(first) result.add " uint gl_VertexID [[vertex_id]]" + if not params.hasParam("gl_InstanceID") and "gl_InstanceID" in bodyCode: + result.addComma(first) + result.add " uint gl_InstanceID [[instance_id]]" for resource in resourceParams(uniforms, textures, stage): result.addComma(first) result.add " " diff --git a/src/shady/backends/shared.nim b/src/shady/backends/shared.nim index 7a9b165..8d0fd7f 100644 --- a/src/shady/backends/shared.nim +++ b/src/shady/backends/shared.nim @@ -177,7 +177,7 @@ proc typeDefault(t: string, n: NimNode): string = hlslTypeDefault(t) of langMetal: metalTypeDefault(t) - if result.len == 0: + if result.len == 0 and (t.len == 0 or not t[0].isUpperAscii): err "no typeDefault " & t, n ## Simply SKIP these functions. @@ -200,6 +200,11 @@ proc isVectorAccess(s: string): bool = proc procRename(t: string): string = ## Some shader proc names don't match Nim names, rename here. + if isVulkan(): + if t == "gl_VertexID": + return "gl_VertexIndex" + if t == "gl_InstanceID": + return "gl_InstanceIndex" case shaderTarget.language of langGlsl: glslProcRename(t) @@ -681,8 +686,10 @@ proc toCode(n: NimNode, res: var string, level = 0) = res.add " = " n[j + 2].toCode(res) else: - res.add " = " - res.add typeDefault(typeStr, n[j]) + let defaultValue = typeDefault(typeStr, n[j]) + if defaultValue.len > 0: + res.add " = " + res.add defaultValue of nnkReturnStmt: res.addIndent level @@ -1551,6 +1558,7 @@ var ## GLSL globals. gl_Position*: Vec4 gl_VertexID*: int32 + gl_InstanceID*: int32 gl_FrontFacing*: bool proc mat3*(m: Mat4): Mat3 = diff --git a/tests/test_backends.nim b/tests/test_backends.nim index acc1453..7d48884 100644 --- a/tests/test_backends.nim +++ b/tests/test_backends.nim @@ -54,6 +54,60 @@ block: doAssert "float4 position [[position]];" in msl doAssert "output.position = gl_Position;" in msl +block: + proc instancedVertex(gl_Position: var Vec4) = + ## Exercises portable vertex and instance identifiers. + let + vertex = float32(gl_VertexID) + instance = float32(gl_InstanceID) + gl_Position = vec4(vertex, instance, 0.0, 1.0) + + let glsl = toShader(instancedVertex, glsl4Desktop, shaderVertex) + doAssert "gl_VertexID" in glsl + doAssert "gl_InstanceID" in glsl + + let web = toShader(instancedVertex, glsl3WebGL, shaderVertex) + doAssert "gl_VertexID" in web + doAssert "gl_InstanceID" in web + + let vulkan = toShader(instancedVertex, vulkanGlsl450, shaderVertex) + doAssert "gl_VertexIndex" in vulkan + doAssert "gl_InstanceIndex" in vulkan + + let hlsl = toHLSL(instancedVertex, shaderVertex) + doAssert "uint gl_VertexID : SV_VertexID" in hlsl + doAssert "uint gl_InstanceID : SV_InstanceID" in hlsl + + let msl = toMSL(instancedVertex, shaderVertex) + doAssert "uint gl_VertexID [[vertex_id]]" in msl + doAssert "uint gl_InstanceID [[instance_id]]" in msl + +block: + type Params = object + offset: float32 + + proc readOffset(params: Params): float32 = + ## Returns a field from a shader-local parameter structure. + params.offset + + proc structuredVertex(gl_Position: var Vec4) = + ## Exercises an uninitialized shader-local parameter structure. + var params: Params + params.offset = gl_InstanceID.float32 + gl_Position = vec4(readOffset(params), 0.0, 0.0, 1.0) + + let glsl = toShader(structuredVertex, glsl4Desktop, shaderVertex) + doAssert "struct Params" in glsl + doAssert "Params params;" in glsl + + let hlsl = toHLSL(structuredVertex, shaderVertex) + doAssert "struct Params" in hlsl + doAssert "Params params;" in hlsl + + let msl = toMSL(structuredVertex, shaderVertex) + doAssert "struct Params" in msl + doAssert "Params params;" in msl + block: var atlas: Uniform[Sampler2d] diff --git a/tests/test_metal.nim b/tests/test_metal.nim index 37693d8..6d5b499 100644 --- a/tests/test_metal.nim +++ b/tests/test_metal.nim @@ -8,7 +8,13 @@ when defined(macosx) and defined(shadyRunMetal): gl_Position: var Vec4, vertColor: var Vec3 ) = - gl_Position = vec4(vPos.x, vPos.y, vPos.z, 1.0) + let instanceOffset = gl_InstanceID.float32 * 0.001 + gl_Position = vec4( + vPos.x + instanceOffset, + vPos.y, + vPos.z, + 1.0 + ) vertColor = vCol proc fragmentMain(fragColor: var Vec4) =