Skip to main content

MAIN_BINDLESS

Constant MAIN_BINDLESS 

Source
pub const MAIN_BINDLESS: &str = "// Bindless forward pass: vertex + fragment, single source for every backend.\n// Compile with -DPOOL_SIZE=<n> and -DMAX_PROBES=<n> (the bindless texture-pool\n// and reflection-probe array lengths the device binds).\n//\n// The shading body is shared; only the resource declarations differ per\n// binding model, selected by a define and bridged into the body through the\n// UPPER_CASE resource macros and the sampling accessors below:\n//\n//   default    - ParameterBlock layout: scene resources = Vulkan descriptor\n//                set 0 (bindings 0-16 in member order, combined image\n//                samplers), objects + texture pool = set 1. DXIL lowers each\n//                block to a register space.\n//   DXIL_SPLIT - same blocks with split texture + sampler pairs where the\n//                combined type\'s SampleCmp / GetDimensions lowerings are\n//                broken on DXIL.\n//   METAL_ABI  - the engine\'s Metal binding layout, which is what the host\n//                encoders write: discrete buffers pinned by register() (b/t\n//                numbers ARE the Metal buffer indices), the texture-only\n//                argument buffer at buffer(7), and the engine sampler block at\n//                buffer(10).\n//   DXIL_ABI   - the engine\'s DirectX bindless root signature: every register\n//                pinned to the layout in directx/init/pipelines.rs, and the\n//                object id taken from the b0 root constant the indirect command\n//                writes rather than from an instance-id builtin.\n//\n// A world Shader compiles from this same file with its hooks spliced at\n// SURFACE_VERTEX / SURFACE_FRAGMENT, so it lands on these slots by\n// construction and never names one.\n//\n// The records this binds are `main_types.slang` and the shading model it drives\n// is `main_shading.slang`.\n\n#ifndef POOL_SIZE\n#define POOL_SIZE 1024\n#endif\n#ifndef MAX_PROBES\n#define MAX_PROBES 8\n#endif\n\n// ---- Shared CPU-visible records ----\n\n{MAIN_TYPES}\n\n{PROBE_TYPES}\n\n// ---- Resource bindings ----\n\n#ifdef METAL_ABI\n\n// The engine\'s Metal argument buffer at buffer(7): texture handles only, in\n// the exact member order the host\'s argument encoder writes (tex_pool first,\n// then the shadow / IBL / SSAO / probe / LTC set). Samplers live in the\n// separate block below because indirect-command-buffer execution cannot see\n// encoder-bound sampler state.\nstruct BindlessTextures\n{\n    // Bindless texture pool: [albedo textures..] ++ [normal maps..]. The\n    // object record\'s albedo_index / normal_index address it directly.\n    Texture2D<float4> tex_pool[POOL_SIZE];\n    Texture2DArray<float4> shadow_map;\n    TextureCube<float4> irradiance_cube;\n    TextureCube<float4> prefilter_cube;\n    // Blurred SSAO occlusion (1x1 white when SSAO is disabled).\n    Texture2D<float4> ssao_tex;\n    // Local reflection-probe prefiltered radiance, one cube per probe; unused\n    // slices alias the sky prefilter.\n    TextureCube<float4> probe_cubes[MAX_PROBES];\n    // Spot shadow map array: one depth slice per shadow-casting spot light.\n    Texture2DArray<float4> spot_shadow_map;\n    // The two LTC lookup tables, sampled at (roughness, sqrt(1 - NdV)).\n    Texture2D<float4> ltc_matrix;\n    Texture2D<float4> ltc_magnitude;\n};\n\n// The engine\'s static samplers, mirrored from the host MTLSamplerDescriptors.\nstruct EngineSamplers\n{\n    SamplerState tex_sampler;\n    SamplerComparisonState shadow_sampler;\n    SamplerState cube_sampler;\n};\n\n// register() numbers pin the Metal buffer slots directly (b and t share the\n// index space there). The three pads reserve buffers 1-3 -- the vertex stream\n// rides buffer(1) and the layout keeps the legacy pass\'s gaps -- so the\n// argument buffer lands at buffer(7) and the sampler block at buffer(10), the\n// first free slots when their declarations are reached.\nConstantBuffer<ViewUniforms> view_cb : register(b0);\nConstantBuffer<float4> abi_pad1 : register(b1);\nConstantBuffer<float4> abi_pad2 : register(b2);\nConstantBuffer<float4> abi_pad3 : register(b3);\nConstantBuffer<LightUniforms> lights_cb : register(b4);\nConstantBuffer<ShadowUniforms> shadow_cb : register(b5);\nConstantBuffer<ProbeSet> probe_set_cb : register(b6);\nParameterBlock<BindlessTextures> tex;\n// Per-scene local lights (point + spot + area) for the forward pass.\nStructuredBuffer<GpuLight> local_lights_sb : register(t8);\nStructuredBuffer<GpuObjectData> objects_sb : register(t9);\nParameterBlock<EngineSamplers> samps;\nConstantBuffer<ClusterParams> cluster_cb : register(b11);\n// Per-cluster light-index lists the LightCull compute pass writes.\nStructuredBuffer<uint> cluster_list_sb : register(t12);\n// Spot shadow slice projections, indexed by GpuLight.shadow_index.\nStructuredBuffer<SpotShadowData> spot_shadows_sb : register(t13);\nStructuredBuffer<AreaLightData> area_lights_sb : register(t14);\n\n#define VIEW view_cb\n#define LIGHTS lights_cb\n#define SHADOW_UNI shadow_cb\n#define PROBE_SET probe_set_cb\n#define CLUSTER cluster_cb\n#define OBJECTS objects_sb\n#define LOCAL_LIGHTS local_lights_sb\n#define CLUSTER_LIST cluster_list_sb\n#define SPOT_SHADOWS spot_shadows_sb\n#define AREA_LIGHTS area_lights_sb\n\nfloat4 pool_sample(uint idx, float2 uv)\n{\n    return tex.tex_pool[idx].Sample(samps.tex_sampler, uv);\n}\nfloat shadow_map_cmp(float3 uv_layer, float ref)\n{\n    return tex.shadow_map.SampleCmp(samps.shadow_sampler, uv_layer, ref);\n}\nfloat spot_shadow_cmp(float3 uv_layer, float ref)\n{\n    return tex.spot_shadow_map.SampleCmp(samps.shadow_sampler, uv_layer, ref);\n}\nfloat2 shadow_map_size()\n{\n    uint w, h, e;\n    tex.shadow_map.GetDimensions(w, h, e);\n    return float2(float(w), float(h));\n}\nfloat2 spot_shadow_map_size()\n{\n    uint w, h, e;\n    tex.spot_shadow_map.GetDimensions(w, h, e);\n    return float2(float(w), float(h));\n}\nfloat ssao_sample(float2 uv)\n{\n    // The cube sampler doubles as the SSAO sampler: the occlusion texture\n    // wants linear + clamp-to-edge (a repeat sampler would wrap the border\n    // texels at the screen edges), which is exactly its filter state.\n    return tex.ssao_tex.Sample(samps.cube_sampler, uv).r;\n}\nfloat2 ssao_size()\n{\n    uint w, h;\n    tex.ssao_tex.GetDimensions(w, h);\n    return float2(float(w), float(h));\n}\nfloat3 irradiance_sample(float3 n)\n{\n    return tex.irradiance_cube.Sample(samps.cube_sampler, SKY_DIR(n)).rgb;\n}\nfloat3 prefilter_sample_level0(float3 dir)\n{\n    return tex.prefilter_cube.SampleLevel(samps.cube_sampler, SKY_DIR(dir), 0.0).rgb;\n}\nfloat3 prefilter_sample_bias(float3 dir, float lod)\n{\n    return tex.prefilter_cube.SampleBias(samps.cube_sampler, SKY_DIR(dir), lod).rgb;\n}\nfloat3 probe_cube_sample_bias(uint i, float3 dir, float lod)\n{\n    return tex.probe_cubes[i].SampleBias(samps.cube_sampler, dir, lod).rgb;\n}\nfloat4 ltc_matrix_sample(float2 uv)\n{\n    return tex.ltc_matrix.SampleLevel(samps.cube_sampler, uv, 0.0);\n}\nfloat2 ltc_magnitude_sample(float2 uv)\n{\n    return tex.ltc_magnitude.SampleLevel(samps.cube_sampler, uv, 0.0).xy;\n}\n\n#elif defined(DXIL_ABI)\n\n// Every register below is pinned to the bindless main root signature in\n// directx/init/pipelines.rs. Root constant at b0, root CBVs at b1-b5, root SRVs\n// at t1/t2/t3/t15/t17, descriptor tables for the rest, and the unbounded\n// texture pool in space1.\nstruct ObjectId { uint value; };\n\nConstantBuffer<ObjectId> objid_cb : register(b0);\nConstantBuffer<ViewUniforms> view_cb : register(b1);\nConstantBuffer<LightUniforms> lights_cb : register(b2);\nConstantBuffer<ShadowUniforms> shadow_cb : register(b3);\nConstantBuffer<ProbeSet> probe_set_cb : register(b4);\nConstantBuffer<ClusterParams> cluster_cb : register(b5);\n\nTexture2DArray<float> shadow_map : register(t0);\n// Per-scene local lights (point + spot + area) for the forward pass.\nStructuredBuffer<GpuLight> local_lights_sb : register(t1);\n// Per-cluster light-index lists the LightCull compute pass writes.\nStructuredBuffer<uint> cluster_list_sb : register(t2);\nStructuredBuffer<GpuObjectData> objects_sb : register(t3);\n// Blurred SSAO occlusion (1x1 white when SSAO is disabled).\nTexture2D<float4> ssao_tex : register(t4);\nTextureCube<float4> irradiance_cube : register(t5);\nTextureCube<float4> prefilter_cube : register(t6);\n// Local reflection-probe prefiltered radiance; unbaked slots hold the sky\n// prefilter cube, so a sample at any index is valid.\nTextureCube<float4> probe_cubes[MAX_PROBES] : register(t7);\n// Spot shadow slice projections, indexed by GpuLight.shadow_index.\nStructuredBuffer<SpotShadowData> spot_shadows_sb : register(t15);\nTexture2DArray<float> spot_shadow_map : register(t16);\nStructuredBuffer<AreaLightData> area_lights_sb : register(t17);\n// The two LTC lookup tables, sampled at (roughness, sqrt(1 - NdV)).\nTexture2D<float4> ltc_matrix : register(t18);\nTexture2D<float4> ltc_magnitude : register(t19);\n// Bindless texture pool: [albedo textures..] ++ [normal maps..]. Unbounded so\n// the shader never over-declares the host\'s per-frame descriptor region.\nTexture2D<float4> tex_pool[] : register(t0, space1);\n\nSamplerComparisonState shadow_sampler : register(s0);\nSamplerState linear_sampler : register(s1);\nSamplerState cube_sampler : register(s2);\n\n#define VIEW view_cb\n#define LIGHTS lights_cb\n#define SHADOW_UNI shadow_cb\n#define PROBE_SET probe_set_cb\n#define CLUSTER cluster_cb\n#define OBJECTS objects_sb\n#define LOCAL_LIGHTS local_lights_sb\n#define CLUSTER_LIST cluster_list_sb\n#define SPOT_SHADOWS spot_shadows_sb\n#define AREA_LIGHTS area_lights_sb\n\nfloat4 pool_sample(uint idx, float2 uv)\n{\n    return tex_pool[NonUniformResourceIndex(idx)].Sample(linear_sampler, uv);\n}\nfloat shadow_map_cmp(float3 uv_layer, float ref)\n{\n    return shadow_map.SampleCmp(shadow_sampler, uv_layer, ref);\n}\nfloat spot_shadow_cmp(float3 uv_layer, float ref)\n{\n    return spot_shadow_map.SampleCmp(shadow_sampler, uv_layer, ref);\n}\nfloat2 shadow_map_size()\n{\n    uint w, h, e;\n    shadow_map.GetDimensions(w, h, e);\n    return float2(float(w), float(h));\n}\nfloat2 spot_shadow_map_size()\n{\n    uint w, h, e;\n    spot_shadow_map.GetDimensions(w, h, e);\n    return float2(float(w), float(h));\n}\nfloat ssao_sample(float2 uv)\n{\n    // Clamp-to-edge filtering: a repeat sampler would wrap the border texels\n    // at the screen edges. The cube sampler carries exactly that state.\n    return ssao_tex.Sample(cube_sampler, uv).r;\n}\nfloat2 ssao_size()\n{\n    uint w, h;\n    ssao_tex.GetDimensions(w, h);\n    return float2(float(w), float(h));\n}\nfloat3 irradiance_sample(float3 n)\n{\n    return irradiance_cube.Sample(cube_sampler, SKY_DIR(n)).rgb;\n}\nfloat3 prefilter_sample_level0(float3 dir)\n{\n    return prefilter_cube.SampleLevel(cube_sampler, SKY_DIR(dir), 0.0).rgb;\n}\nfloat3 prefilter_sample_bias(float3 dir, float lod)\n{\n    return prefilter_cube.SampleBias(cube_sampler, SKY_DIR(dir), lod).rgb;\n}\nfloat3 probe_cube_sample_bias(uint i, float3 dir, float lod)\n{\n    return probe_cubes[i].SampleBias(cube_sampler, dir, lod).rgb;\n}\nfloat4 ltc_matrix_sample(float2 uv)\n{\n    return ltc_matrix.SampleLevel(cube_sampler, uv, 0.0);\n}\nfloat2 ltc_magnitude_sample(float2 uv)\n{\n    return ltc_magnitude.SampleLevel(cube_sampler, uv, 0.0).xy;\n}\n\n#else // ParameterBlock layout (Vulkan descriptor sets / DXIL register spaces)\n\nstruct SceneResources\n{\n    ConstantBuffer<ViewUniforms> view;\n    ConstantBuffer<LightUniforms> lights;\n    ConstantBuffer<ShadowUniforms> shadow_uni;\n    // The shadow arrays and the SSAO texture are combined texture-samplers on\n    // the targets whose descriptor model wants them fused (the engine\'s\n    // Vulkan layout uses COMBINED_IMAGE_SAMPLER; Metal lowers the pair\n    // itself), and split texture + sampler pairs on DXIL, where the combined\n    // type\'s SampleCmp / GetDimensions lowerings are broken.\n#ifdef DXIL_SPLIT\n    Texture2DArray<float4> shadow_map_t;\n    SamplerComparisonState shadow_map_s;\n#else\n    Sampler2DArray<float4> shadow_map;\n#endif\n    SamplerCube<float4> irradiance_cube;\n    SamplerCube<float4> prefilter_cube;\n    // Blurred SSAO occlusion (1x1 white when SSAO is disabled).\n#ifdef DXIL_SPLIT\n    Texture2D<float4> ssao_tex_t;\n    SamplerState ssao_tex_s;\n#else\n    Sampler2D<float4> ssao_tex;\n#endif\n    ConstantBuffer<ProbeSet> probe_set;\n    SamplerCube<float4> probe_cubes[MAX_PROBES];\n    // Per-scene local lights (point + spot + area) for the forward pass.\n    StructuredBuffer<GpuLight> local_lights;\n    ConstantBuffer<ClusterParams> cluster;\n    // Per-cluster light-index lists the LightCull compute pass writes.\n    StructuredBuffer<uint> cluster_list;\n    // Spot shadow depth array: one layer per shadow-casting spot.\n#ifdef DXIL_SPLIT\n    Texture2DArray<float4> spot_shadow_map_t;\n    SamplerComparisonState spot_shadow_map_s;\n#else\n    Sampler2DArray<float4> spot_shadow_map;\n#endif\n    StructuredBuffer<SpotShadowData> spot_shadows;\n    StructuredBuffer<AreaLightData> area_lights;\n    // The two LTC lookup tables, sampled at (roughness, sqrt(1 - NdV)).\n    Sampler2D<float4> ltc_matrix;\n    Sampler2D<float4> ltc_magnitude;\n};\n\nstruct ObjectResources\n{\n    StructuredBuffer<GpuObjectData> objects;\n    // Bindless texture pool: [albedo textures..] ++ [normal maps..]. The\n    // object record\'s albedo_index / normal_index address it directly.\n    Sampler2D<float4> tex_pool[POOL_SIZE];\n};\n\nParameterBlock<SceneResources> scene;\nParameterBlock<ObjectResources> objs;\n\n#define VIEW scene.view\n#define LIGHTS scene.lights\n#define SHADOW_UNI scene.shadow_uni\n#define PROBE_SET scene.probe_set\n#define CLUSTER scene.cluster\n#define OBJECTS objs.objects\n#define LOCAL_LIGHTS scene.local_lights\n#define CLUSTER_LIST scene.cluster_list\n#define SPOT_SHADOWS scene.spot_shadows\n#define AREA_LIGHTS scene.area_lights\n\n// NonUniformResourceIndex is required on the descriptor-indexing targets but\n// rejected by the Metal backend, where argument-buffer indexing needs no\n// annotation. The METAL_ABI block above never reaches this helper, but the\n// `metal` case keeps the default block target-portable too.\nuint nonuniform_index(uint i)\n{\n    __target_switch\n    {\n    case metal:\n        return i;\n    default:\n        return NonUniformResourceIndex(i);\n    }\n}\n\nfloat4 pool_sample(uint idx, float2 uv)\n{\n    return objs.tex_pool[nonuniform_index(idx)].Sample(uv);\n}\n\n// Sampling and size queries on the split-vs-combined resources above, so the\n// shader body stays identical across the two declaration forms.\n#ifdef DXIL_SPLIT\n\nfloat shadow_map_cmp(float3 uv_layer, float ref)\n{\n    return scene.shadow_map_t.SampleCmp(scene.shadow_map_s, uv_layer, ref);\n}\nfloat spot_shadow_cmp(float3 uv_layer, float ref)\n{\n    return scene.spot_shadow_map_t.SampleCmp(scene.spot_shadow_map_s, uv_layer, ref);\n}\nfloat2 shadow_map_size()\n{\n    uint w, h, e;\n    scene.shadow_map_t.GetDimensions(w, h, e);\n    return float2(float(w), float(h));\n}\nfloat2 spot_shadow_map_size()\n{\n    uint w, h, e;\n    scene.spot_shadow_map_t.GetDimensions(w, h, e);\n    return float2(float(w), float(h));\n}\nfloat ssao_sample(float2 uv)\n{\n    return scene.ssao_tex_t.Sample(scene.ssao_tex_s, uv).r;\n}\nfloat2 ssao_size()\n{\n    uint w, h;\n    scene.ssao_tex_t.GetDimensions(w, h);\n    return float2(float(w), float(h));\n}\n\n#else\n\nfloat shadow_map_cmp(float3 uv_layer, float ref)\n{\n    return scene.shadow_map.SampleCmp(uv_layer, ref);\n}\nfloat spot_shadow_cmp(float3 uv_layer, float ref)\n{\n    return scene.spot_shadow_map.SampleCmp(uv_layer, ref);\n}\n\n// The Metal target reads dimensions through inline MSL: the DXIL-style\n// GetDimensions overloads on combined types do not lower there. The resource\n// rides as a parameter so the inline form can reference it.\nfloat2 combined_array_size(Sampler2DArray<float4> s)\n{\n    __target_switch\n    {\n    case metal:\n        __intrinsic_asm \"float2((*$0).texture_0.get_width(), (*$0).texture_0.get_height())\";\n    default:\n        uint w, h, e;\n        s.GetDimensions(w, h, e);\n        return float2(float(w), float(h));\n    }\n}\nfloat2 combined_size(Sampler2D<float4> s)\n{\n    __target_switch\n    {\n    case metal:\n        __intrinsic_asm \"float2((*$0).texture_1.get_width(), (*$0).texture_1.get_height())\";\n    default:\n        uint w, h;\n        s.GetDimensions(w, h);\n        return float2(float(w), float(h));\n    }\n}\nfloat2 shadow_map_size()\n{\n    return combined_array_size(scene.shadow_map);\n}\nfloat2 spot_shadow_map_size()\n{\n    return combined_array_size(scene.spot_shadow_map);\n}\nfloat ssao_sample(float2 uv)\n{\n    return scene.ssao_tex.Sample(uv).r;\n}\nfloat2 ssao_size()\n{\n    return combined_size(scene.ssao_tex);\n}\n\n#endif\n\nfloat3 irradiance_sample(float3 n)\n{\n    return scene.irradiance_cube.Sample(SKY_DIR(n)).rgb;\n}\nfloat3 prefilter_sample_level0(float3 dir)\n{\n    return scene.prefilter_cube.SampleLevel(SKY_DIR(dir), 0.0).rgb;\n}\nfloat3 prefilter_sample_bias(float3 dir, float lod)\n{\n    return scene.prefilter_cube.SampleBias(SKY_DIR(dir), lod).rgb;\n}\nfloat3 probe_cube_sample_bias(uint i, float3 dir, float lod)\n{\n    return scene.probe_cubes[i].SampleBias(dir, lod).rgb;\n}\nfloat4 ltc_matrix_sample(float2 uv)\n{\n    return scene.ltc_matrix.SampleLevel(uv, 0.0);\n}\nfloat2 ltc_magnitude_sample(float2 uv)\n{\n    return scene.ltc_magnitude.SampleLevel(uv, 0.0).xy;\n}\n\n#endif // binding model\n\n{PROBE_COMMON}\n\n// The reflection tap `shade_surface` reads: the bound probe set where any probe\n// is baked, else the imported environment prefilter cube.\nfloat3 environment_specular(float3 world_pos, float3 R, float lod)\n{\n    return (PROBE_SET.count > 0u) ? probe_set_specular(world_pos, R, lod)\n                                  : prefilter_sample_bias(R, lod);\n}\n\n{MAIN_SHADING}\n\n// ---- The world\'s hooks ----\n\n// A world Shader defines these; the engine\'s defaults delegate to\n// `project_vertex` and `shade_surface`. Both stages compile from this one\n// variant, so both hooks are spliced here.\nVertexOut transform(float4x4 model, float3 pos, float3 normal, float3 tangent,\n                    float3 color, float2 uv);\nfloat4 shade(VertexOut in, GpuObjectData od);\n\n{SURFACE_VERTEX}\n\n{SURFACE_FRAGMENT}\n\n// ---- Vertex ----\n\n[shader(\"vertex\")]\nVertexOut vertex_main_bindless(\n    VertexIn v\n#ifdef DXIL_ABI\n    // DirectX writes the object id into the b0 root constant ahead of each\n    // indirect draw, so no instance-id builtin is read: SV_StartInstanceLocation\n    // would raise the DXIL floor to shader model 6.8 for nothing.\n    )\n{\n    uint oid = objid_cb.value;\n#else\n    ,\n    uint instance_id : SV_InstanceID,\n    uint first_instance : SV_StartInstanceLocation)\n{\n    uint oid = object_instance_index(instance_id, first_instance);\n#endif\n    VertexOut o = transform(OBJECTS[oid].model, v.pos, v.normal, v.tangent, v.color, v.uv);\n    o.object_id = oid;\n    return o;\n}\n\n// ---- Fragment ----\n\n// The object id joins the varyings fold: a world `shade` that ignores its\n// record lets the compiler drop the only read of `in.object_id`, and the\n// vertex output at its location then has no consumer.\n[shader(\"fragment\")]\nfloat4 fragment_main_bindless(VertexOut in) : SV_Target\n{\n    float4 shaded = shade(in, OBJECTS[in.object_id]);\n    return varyings_read(in) + float(in.object_id) > 1e30 ? float4(0.0) : shaded;\n}\n";
Expand description

main_bindless.slang.