pub const RT_SKIN: &str = "// Compute skinning for ray tracing: single source for every backend.\n//\n// The main pass skins in the vertex shader, so no deformed-vertex buffer exists\n// for the BVH to trace against. This kernel produces one: it reads the bind-pose\n// skinned vertices + a per-object joint palette and writes posed (model-space)\n// plain `Vertex`s into a shared deformed buffer, which the RT\n// acceleration-structure build then traces. One dispatch per skinned object over\n// its vertex range; the deformed buffer mirrors the skinned vertex buffer\'s\n// indexing so the existing skinned index buffer addresses it directly.\n//\n// The mesh payloads are read and written as raw bytes rather than through\n// `StructuredBuffer<T>`, because a structured-buffer `float3` does not lay out\n// the same way on all three targets: Metal and DXIL pack it to 12 bytes, SPIR-V\n// pads it to 16, which would stride `SkinnedVertex` at 96 instead of 80. Byte\n// addressing has no layout rule to disagree over, so the one source reproduces\n// the CPU strides everywhere. `mesh_payload_offsets_match_the_kernel` in\n// `shader_layout` pins the constants below to the `#[repr(C)]` mirrors.\n\n// Which slice of the shared buffers this dispatch deforms.\nstruct SkinParams\n{\n // First vertex of this object in the shared buffers.\n uint vertex_base;\n // Vertices to deform this dispatch.\n uint vertex_count;\n // Palette size; joint indices are clamped below it.\n uint joint_count;\n // Morph targets in `morph_data`; 0 = no morphing.\n uint target_count;\n};\n\n// Byte stride and field offsets of `gfx::mesh_payload::SkinnedVertex`. `joints`\n// is four u16s, read as the two uints at `SKINNED_JOINTS`.\nstatic const uint SKINNED_STRIDE = 80;\nstatic const uint SKINNED_POS = 0;\nstatic const uint SKINNED_NORMAL = 12;\nstatic const uint SKINNED_TANGENT = 24;\nstatic const uint SKINNED_COLOR = 36;\nstatic const uint SKINNED_UV = 48;\nstatic const uint SKINNED_JOINTS = 56;\nstatic const uint SKINNED_WEIGHTS = 64;\n\n// Byte stride and field offsets of `gfx::mesh_payload::Vertex`, the layout the\n// static RT vertex fetchers read the deformed buffer back at.\nstatic const uint VERTEX_STRIDE = 56;\nstatic const uint VERTEX_POS = 0;\nstatic const uint VERTEX_NORMAL = 12;\nstatic const uint VERTEX_TANGENT = 24;\nstatic const uint VERTEX_COLOR = 36;\nstatic const uint VERTEX_UV = 48;\n\n// Byte stride and field offsets of `gfx::mesh_payload::MorphEntry`: one sparse\n// morph delta naming its target, a bind-space position + normal offset scaled by\n// that target\'s weight.\nstatic const uint MORPH_STRIDE = 28;\nstatic const uint MORPH_TARGET = 0;\nstatic const uint MORPH_POSITION = 4;\nstatic const uint MORPH_NORMAL = 16;\n\n// METAL_BINDINGS is a host difference, not a target one. The two branches carry\n// the same Vulkan bindings and differ only in the register numbers, because\n// Metal has one buffer index space where DirectX has three: a `t0` and a `u0`\n// that never collide on DXIL would both land on buffer(0). The Metal numbers are\n// the indices its encoder already binds; Vulkan pushes the params and DirectX\n// takes them as root constants at b0, where a bare push constant lands there.\n#ifdef METAL_BINDINGS\n[[vk::binding(0, 0)]] ByteAddressBuffer src : register(t0);\n[[vk::binding(1, 0)]] StructuredBuffer<float4x4> palette : register(t2);\n[[vk::binding(2, 0)]] RWByteAddressBuffer dst : register(u1);\n[[vk::binding(3, 0)]] ByteAddressBuffer morph_data : register(t4);\n[[vk::binding(4, 0)]] StructuredBuffer<float> morph_weights : register(t5);\n[[vk::push_constant]] ConstantBuffer<SkinParams> params : register(b3);\n#else\n[[vk::binding(0, 0)]] ByteAddressBuffer src : register(t0);\n[[vk::binding(1, 0)]] StructuredBuffer<float4x4> palette : register(t1);\n[[vk::binding(2, 0)]] RWByteAddressBuffer dst : register(u0);\n[[vk::binding(3, 0)]] ByteAddressBuffer morph_data : register(t2);\n[[vk::binding(4, 0)]] StructuredBuffer<float> morph_weights : register(t3);\n[[vk::push_constant]] ConstantBuffer<SkinParams> params;\n#endif\n\n// Byte offset of the entry list in the packed morph buffer\n// (`PayloadMorphs::packed_words`): `vertex_count + 1` uint entry offsets, then\n// the `MorphEntry` list at a 16-byte-aligned word.\nuint morph_entry_byte_base(uint vertex_count)\n{\n return ((vertex_count + 1u + 3u) & ~3u) * 4u;\n}\n\n// The four joint indices of one vertex: two u16s per word, low half first.\nuint4 unpack_joints(uint2 words)\n{\n return uint4(words.x & 0xFFFFu, words.x >> 16, words.y & 0xFFFFu, words.y >> 16);\n}\n\n[shader(\"compute\")]\n[numthreads(64, 1, 1)]\nvoid rt_skin(uint3 gid : SV_DispatchThreadID)\n{\n if (gid.x >= params.vertex_count)\n {\n return;\n }\n uint idx = params.vertex_base + gid.x;\n uint sbase = idx * SKINNED_STRIDE;\n\n float3 pos = asfloat(src.Load3(sbase + SKINNED_POS));\n float3 normal = asfloat(src.Load3(sbase + SKINNED_NORMAL));\n float3 tangent = asfloat(src.Load3(sbase + SKINNED_TANGENT));\n float3 color = asfloat(src.Load3(sbase + SKINNED_COLOR));\n float2 uv = asfloat(src.Load2(sbase + SKINNED_UV));\n uint4 joints = unpack_joints(src.Load2(sbase + SKINNED_JOINTS));\n float4 weights = asfloat(src.Load4(sbase + SKINNED_WEIGHTS));\n\n // Morph deltas apply in bind space, before the skin matrix. The sparse\n // buffer is vertex-major: this thread walks only the entries that touch its\n // own LOCAL vertex index.\n if (params.target_count != 0u)\n {\n uint first = morph_data.Load(gid.x * 4u);\n uint end = morph_data.Load(gid.x * 4u + 4u);\n uint ebase = morph_entry_byte_base(params.vertex_count);\n for (uint e = first; e < end; ++e)\n {\n uint dbase = ebase + e * MORPH_STRIDE;\n float w = morph_weights[morph_data.Load(dbase + MORPH_TARGET)];\n pos += w * asfloat(morph_data.Load3(dbase + MORPH_POSITION));\n normal += w * asfloat(morph_data.Load3(dbase + MORPH_NORMAL));\n }\n }\n normal = normalize(normal);\n\n uint last = params.joint_count == 0u ? 0u : params.joint_count - 1u;\n float4x4 skin = weights.x * palette[min(joints.x, last)]\n + weights.y * palette[min(joints.y, last)]\n + weights.z * palette[min(joints.z, last)]\n + weights.w * palette[min(joints.w, last)];\n float3x3 skin3 = (float3x3)skin;\n\n uint dbase = idx * VERTEX_STRIDE;\n dst.Store3(dbase + VERTEX_POS, asuint(mul(skin, float4(pos, 1.0)).xyz));\n dst.Store3(dbase + VERTEX_NORMAL, asuint(normalize(mul(skin3, normal))));\n dst.Store3(dbase + VERTEX_TANGENT, asuint(mul(skin3, tangent)));\n dst.Store3(dbase + VERTEX_COLOR, asuint(color));\n dst.Store2(dbase + VERTEX_UV, asuint(uv));\n}\n";Expand description
rt_skin.slang.