slang icon indicating copy to clipboard operation
slang copied to clipboard

Reflection for Atomics is not implemented

Open Devon7925 opened this issue 10 months ago • 3 comments

Currently the reflection data for atomic values is incorrect. Atomic<T> should be reflected as T.

Devon7925 avatar Feb 03 '25 22:02 Devon7925

I tried some trivial shaders with Atomic<int> and they seem to be reflected as integers for HLSL and SPIRV targets.

@Devon7925 Can you provide a repro for your use case?

jhelferty-nv avatar Jun 13 '25 21:06 jhelferty-nv

shader.slang

RWStructuredBuffer<Atomic<uint>> bar;

[shader("compute")]
[numthreads(64, 1, 1)]
void main(uint2 dispatchThreadId : SV_DispatchThreadID)
{
}

main.rs

use slang::Downcast;

fn main() {
    let global_session = slang::GlobalSession::new().unwrap();

    let search_path = std::ffi::CString::new(".").unwrap();

    // All compiler options are available through this builder.
    let session_options = slang::CompilerOptions::default()
        .optimization(slang::OptimizationLevel::High)
        .matrix_layout_row(true);

    let target_desc = slang::TargetDesc::default()
        .format(slang::CompileTarget::Dxil)
        .profile(global_session.find_profile("sm_6_5"));

    let targets = [target_desc];
    let search_paths = [search_path.as_ptr()];

    let session_desc = slang::SessionDesc::default()
        .targets(&targets)
        .search_paths(&search_paths)
        .options(&session_options);

    let session = global_session.create_session(&session_desc).unwrap();
    let module = session.load_module("shader.slang").unwrap();
    let entry_point = module.find_entry_point_by_name("main").unwrap();

    let program = session.create_composite_component_type(&[
        module.downcast().clone(), entry_point.downcast().clone(),
    ]).unwrap();

    let linked_program = program.link().unwrap();

    // Entry point to the reflection API.
    let reflection = linked_program.layout(0).unwrap();
    
    let parameter_reflection_type = reflection.parameters().next().unwrap().ty().unwrap();
    println!("Outer type name: {}", parameter_reflection_type.name()); // RWStructuredBuffer
    println!("Inner kind: {:?}", parameter_reflection_type.element_type().kind()); // Struct
    println!("Inner type name: {}", parameter_reflection_type.element_type().name()); // Atomic
    println!("Field count: {}", parameter_reflection_type.element_type().field_count()); // 0
    println!("Inner kind: {:?}", parameter_reflection_type.resource_result_type().kind()); // Struct
    println!("Inner type name: {}", parameter_reflection_type.resource_result_type().name()); // Atomic
    let parameter_reflection_type_layout = reflection.parameters().next().unwrap().type_layout();
    println!("Inner Layout type kind: {:?}", parameter_reflection_type_layout.element_type_layout().kind()); // Scalar
    println!("Inner Layout scalar type: {:?}", parameter_reflection_type_layout.element_type_layout().scalar_type().unwrap()); // Uint32
}

Cargo.toml

[package]
name = "slang-atomic-reflection"
version = "0.1.0"
edition = "2024"

[dependencies]
slang = { git = "https://github.com/FloatyMonkey/slang-rs" }

Devon7925 avatar Jun 13 '25 22:06 Devon7925

Thanks Devon, I was able to reproduce what you're seeing with your sample.

I'm a bit new to the reflection API myself, so I walked through the calls to try and understand what's going on. I then compared against what the reflection-api example does.

The first "Inner Kind" that returns a struct is doing this (on the left is rust, comment is underlying C function prefaced by the type returned):

    .parameters().next()        // VariableLayout   spReflection_GetParameterByIndex
    .ty() //.variable()         // Variable         spReflectionVariableLayout_GetVariable
          //.ty()               // Type             spReflectionVariable_GetType
    .element_type()             // Type             spReflectionType_GetElementType
    .kind()                     // TypeKind         spReflectionType_GetKind

Note that the .ty() above is expanded by the rust bindings to a .variable().ty()

The -reflection-json parameter and the C++ reflection-api example from the slang repository use different walks:

    ->getParameterByIndex(1)    // VariableLayout   spReflection_GetParameterByIndex
    ->getTypeLayout()           // TypeLayout       spReflectionVariableLayout_GetTypeLayout
    ->getElementTypeLayout()    // TypeLayout       spReflectionTypeLayout_GetElementTypeLayout
    ->getType()                 // Type             spReflectionTypeLayout_GetType
    ->getKind()                 // TypeKind         spReflectionType_GetKind

There are some intermediate steps I've skipped. Once it has the TypeLayout, it checks the kind, and gets resource, then gets the resource shape and based on that gets the Element TypeLayout.

This is the same as your "Inner layout" walk. I think that's the preferred one for now:

    .parameters().next()        // VariableLayout   spReflection_GetParameterByIndex
    .type_layout()              // TypeLayout       spReflectionVariableLayout_GetTypeLayout
        // check: .kind() == resource
        // check: .ty().resource_shape() & SLANG_RESOURCE_BASE_SHAPE_MASK
    .element_type_layout()      // TypeLayout       spReflectionTypeLayout_GetElementTypeLayout
    .ty()                       // Type             spReflectionTypeLayout_GetType
    .kind()                     // TypeKind         spReflectionType_GetKind

I'm looking into why the walks have different results.

jhelferty-nv avatar Jun 24 '25 13:06 jhelferty-nv