vulkano-rs / vulkano

Safe and rich Rust wrapper around the Vulkan API
Apache License 2.0
4.5k stars 432 forks source link

PushConstantRange reflection is overly simplistic. #2398

Closed charles-r-earp closed 11 months ago

charles-r-earp commented 11 months ago

Related to #756.

shader::reflect::entry_points calls shader::reflect::push_constant_requirements, which looks like this:

/// Extracts the `PushConstantRange` from `spirv`.
fn push_constant_requirements(spirv: &Spirv, stage: ShaderStage) -> Option<PushConstantRange> {
    spirv
        .iter_global()
        .find_map(|instruction| match *instruction {
            Instruction::TypePointer {
                ty,
                storage_class: StorageClass::PushConstant,
                ..
            } => {
                let id_info = spirv.id(ty);
                assert!(matches!(
                    id_info.instruction(),
                    Instruction::TypeStruct { .. }
                ));
                let start = offset_of_struct(spirv, ty);
                let end =
                    size_of_type(spirv, ty).expect("Found runtime-sized push constants") as u32;

                Some(PushConstantRange {
                    stages: stage.into(),
                    offset: start,
                    size: end - start,
                })
            }
            _ => None,
        })
}

This algorithm assumes that the first pointer of StorageClass::PushConstant will point to a TypeStruct, and that there is only one of these in the module.

I created tests to demonstrate:

Details ``` running 2 tests test push_uint_before_struct ... FAILED test multiple_entry_points ... FAILED failures: ---- push_uint_before_struct stdout ---- ; SPIR-V ; Version: 1.5 ; Generator: rspirv ; Bound: 13 OpMemoryModel Logical Vulkan OpEntryPoint GLCompute %9 "main1" %1 = OpTypeVoid %2 = OpTypeFunction %1 %1 %3 = OpTypeInt 32 0 %4 = OpTypePointer PushConstant %3 %5 = OpConstant %3 0 %6 = OpTypeStruct %3 %3 %7 = OpTypePointer PushConstant %6 %8 = OpVariable %7 PushConstant %9 = OpFunction %1 DontInline|Const %2 %10 = OpLabel %11 = OpAccessChain %4 %8 %5 %12 = OpLoad %3 %11 OpReturn OpFunctionEnd [reflect-push-constants-issue/src/lib.rs:40] &spirv = Spirv { version: 1.5.0, bound: 13, ids: { Id( 9, ): IdInfo { instruction: Function { result_type_id: Id( 1, ), result_id: Id( 9, ), function_control: FunctionControl { inline: false, dont_inline: true, pure: false, constant: true, opt_none_intel: false, }, function_type: Id( 2, ), }, names: [], decorations: [], members: [], }, Id( 4, ): IdInfo { instruction: TypePointer { result_id: Id( 4, ), storage_class: PushConstant, ty: Id( 3, ), }, names: [], decorations: [], members: [], }, Id( 7, ): IdInfo { instruction: TypePointer { result_id: Id( 7, ), storage_class: PushConstant, ty: Id( 6, ), }, names: [], decorations: [], members: [], }, Id( 3, ): IdInfo { instruction: TypeInt { result_id: Id( 3, ), width: 32, signedness: 0, }, names: [], decorations: [], members: [], }, Id( 1, ): IdInfo { instruction: TypeVoid { result_id: Id( 1, ), }, names: [], decorations: [], members: [], }, Id( 10, ): IdInfo { instruction: Label { result_id: Id( 10, ), }, names: [], decorations: [], members: [], }, Id( 6, ): IdInfo { instruction: TypeStruct { result_id: Id( 6, ), member_types: [ Id( 3, ), Id( 3, ), ], }, names: [], decorations: [], members: [ StructMemberInfo { names: [], decorations: [], }, StructMemberInfo { names: [], decorations: [], }, ], }, Id( 5, ): IdInfo { instruction: Constant { result_type_id: Id( 3, ), result_id: Id( 5, ), value: [ 0, ], }, names: [], decorations: [], members: [], }, Id( 12, ): IdInfo { instruction: Load { result_type_id: Id( 3, ), result_id: Id( 12, ), pointer: Id( 11, ), memory_access: None, }, names: [], decorations: [], members: [], }, Id( 8, ): IdInfo { instruction: Variable { result_type_id: Id( 7, ), result_id: Id( 8, ), storage_class: PushConstant, initializer: None, }, names: [], decorations: [], members: [], }, Id( 11, ): IdInfo { instruction: AccessChain { result_type_id: Id( 4, ), result_id: Id( 11, ), base: Id( 8, ), indexes: [ Id( 5, ), ], }, names: [], decorations: [], members: [], }, Id( 2, ): IdInfo { instruction: TypeFunction { result_id: Id( 2, ), return_type: Id( 1, ), parameter_types: [ Id( 1, ), ], }, names: [], decorations: [], members: [], }, }, instructions_capability: [], instructions_extension: [], instructions_ext_inst_import: [], instruction_memory_model: MemoryModel { addressing_model: Logical, memory_model: Vulkan, }, instructions_entry_point: [ EntryPoint { execution_model: GLCompute, entry_point: Id( 9, ), name: "main1", interface: [], }, ], instructions_execution_mode: [], instructions_name: [], instructions_decoration: [], instructions_global: [ TypeVoid { result_id: Id( 1, ), }, TypeFunction { result_id: Id( 2, ), return_type: Id( 1, ), parameter_types: [ Id( 1, ), ], }, TypeInt { result_id: Id( 3, ), width: 32, signedness: 0, }, TypePointer { result_id: Id( 4, ), storage_class: PushConstant, ty: Id( 3, ), }, Constant { result_type_id: Id( 3, ), result_id: Id( 5, ), value: [ 0, ], }, TypeStruct { result_id: Id( 6, ), member_types: [ Id( 3, ), Id( 3, ), ], }, TypePointer { result_id: Id( 7, ), storage_class: PushConstant, ty: Id( 6, ), }, Variable { result_type_id: Id( 7, ), result_id: Id( 8, ), storage_class: PushConstant, initializer: None, }, ], functions: { Id( 9, ): FunctionInfo { instructions: [ Function { result_type_id: Id( 1, ), result_id: Id( 9, ), function_control: FunctionControl { inline: false, dont_inline: true, pure: false, constant: true, opt_none_intel: false, }, function_type: Id( 2, ), }, Label { result_id: Id( 10, ), }, AccessChain { result_type_id: Id( 4, ), result_id: Id( 11, ), base: Id( 8, ), indexes: [ Id( 5, ), ], }, Load { result_type_id: Id( 3, ), result_id: Id( 12, ), pointer: Id( 11, ), memory_access: None, }, Return, FunctionEnd, ], entry_point: Some( EntryPoint { execution_model: GLCompute, entry_point: Id( 9, ), name: "main1", interface: [], }, ), execution_modes: [], }, }, } thread 'push_uint_before_struct' panicked at 'assertion failed: matches!(id_info.instruction(), Instruction :: TypeStruct { .. })', vulkano/src/shader/reflect.rs:864:17 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace ---- multiple_entry_points stdout ---- ; SPIR-V ; Version: 1.5 ; Generator: rspirv ; Bound: 15 OpMemoryModel Logical Vulkan OpEntryPoint GLCompute %9 "main1" OpEntryPoint GLCompute %13 "main2" %1 = OpTypeVoid %2 = OpTypeFunction %1 %1 %3 = OpTypeInt 32 0 %4 = OpConstant %3 0 %5 = OpTypeStruct %3 %3 %6 = OpTypePointer PushConstant %5 %7 = OpTypePointer PushConstant %3 %8 = OpVariable %6 PushConstant %9 = OpFunction %1 None %2 %10 = OpLabel %11 = OpAccessChain %7 %8 %4 %12 = OpLoad %3 %11 OpReturn OpFunctionEnd %13 = OpFunction %1 None %2 %14 = OpLabel OpReturn OpFunctionEnd [reflect-push-constants-issue/src/lib.rs:78] &spirv = Spirv { version: 1.5.0, bound: 15, ids: { Id( 8, ): IdInfo { instruction: Variable { result_type_id: Id( 6, ), result_id: Id( 8, ), storage_class: PushConstant, initializer: None, }, names: [], decorations: [], members: [], }, Id( 1, ): IdInfo { instruction: TypeVoid { result_id: Id( 1, ), }, names: [], decorations: [], members: [], }, Id( 10, ): IdInfo { instruction: Label { result_id: Id( 10, ), }, names: [], decorations: [], members: [], }, Id( 6, ): IdInfo { instruction: TypePointer { result_id: Id( 6, ), storage_class: PushConstant, ty: Id( 5, ), }, names: [], decorations: [], members: [], }, Id( 3, ): IdInfo { instruction: TypeInt { result_id: Id( 3, ), width: 32, signedness: 0, }, names: [], decorations: [], members: [], }, Id( 5, ): IdInfo { instruction: TypeStruct { result_id: Id( 5, ), member_types: [ Id( 3, ), Id( 3, ), ], }, names: [], decorations: [], members: [ StructMemberInfo { names: [], decorations: [], }, StructMemberInfo { names: [], decorations: [], }, ], }, Id( 9, ): IdInfo { instruction: Function { result_type_id: Id( 1, ), result_id: Id( 9, ), function_control: FunctionControl { inline: false, dont_inline: false, pure: false, constant: false, opt_none_intel: false, }, function_type: Id( 2, ), }, names: [], decorations: [], members: [], }, Id( 2, ): IdInfo { instruction: TypeFunction { result_id: Id( 2, ), return_type: Id( 1, ), parameter_types: [ Id( 1, ), ], }, names: [], decorations: [], members: [], }, Id( 12, ): IdInfo { instruction: Load { result_type_id: Id( 3, ), result_id: Id( 12, ), pointer: Id( 11, ), memory_access: None, }, names: [], decorations: [], members: [], }, Id( 11, ): IdInfo { instruction: AccessChain { result_type_id: Id( 7, ), result_id: Id( 11, ), base: Id( 8, ), indexes: [ Id( 4, ), ], }, names: [], decorations: [], members: [], }, Id( 13, ): IdInfo { instruction: Function { result_type_id: Id( 1, ), result_id: Id( 13, ), function_control: FunctionControl { inline: false, dont_inline: false, pure: false, constant: false, opt_none_intel: false, }, function_type: Id( 2, ), }, names: [], decorations: [], members: [], }, Id( 14, ): IdInfo { instruction: Label { result_id: Id( 14, ), }, names: [], decorations: [], members: [], }, Id( 4, ): IdInfo { instruction: Constant { result_type_id: Id( 3, ), result_id: Id( 4, ), value: [ 0, ], }, names: [], decorations: [], members: [], }, Id( 7, ): IdInfo { instruction: TypePointer { result_id: Id( 7, ), storage_class: PushConstant, ty: Id( 3, ), }, names: [], decorations: [], members: [], }, }, instructions_capability: [], instructions_extension: [], instructions_ext_inst_import: [], instruction_memory_model: MemoryModel { addressing_model: Logical, memory_model: Vulkan, }, instructions_entry_point: [ EntryPoint { execution_model: GLCompute, entry_point: Id( 9, ), name: "main1", interface: [], }, EntryPoint { execution_model: GLCompute, entry_point: Id( 13, ), name: "main2", interface: [], }, ], instructions_execution_mode: [], instructions_name: [], instructions_decoration: [], instructions_global: [ TypeVoid { result_id: Id( 1, ), }, TypeFunction { result_id: Id( 2, ), return_type: Id( 1, ), parameter_types: [ Id( 1, ), ], }, TypeInt { result_id: Id( 3, ), width: 32, signedness: 0, }, Constant { result_type_id: Id( 3, ), result_id: Id( 4, ), value: [ 0, ], }, TypeStruct { result_id: Id( 5, ), member_types: [ Id( 3, ), Id( 3, ), ], }, TypePointer { result_id: Id( 6, ), storage_class: PushConstant, ty: Id( 5, ), }, TypePointer { result_id: Id( 7, ), storage_class: PushConstant, ty: Id( 3, ), }, Variable { result_type_id: Id( 6, ), result_id: Id( 8, ), storage_class: PushConstant, initializer: None, }, ], functions: { Id( 9, ): FunctionInfo { instructions: [ Function { result_type_id: Id( 1, ), result_id: Id( 9, ), function_control: FunctionControl { inline: false, dont_inline: false, pure: false, constant: false, opt_none_intel: false, }, function_type: Id( 2, ), }, Label { result_id: Id( 10, ), }, AccessChain { result_type_id: Id( 7, ), result_id: Id( 11, ), base: Id( 8, ), indexes: [ Id( 4, ), ], }, Load { result_type_id: Id( 3, ), result_id: Id( 12, ), pointer: Id( 11, ), memory_access: None, }, Return, FunctionEnd, ], entry_point: Some( EntryPoint { execution_model: GLCompute, entry_point: Id( 9, ), name: "main1", interface: [], }, ), execution_modes: [], }, Id( 13, ): FunctionInfo { instructions: [ Function { result_type_id: Id( 1, ), result_id: Id( 13, ), function_control: FunctionControl { inline: false, dont_inline: false, pure: false, constant: false, opt_none_intel: false, }, function_type: Id( 2, ), }, Label { result_id: Id( 14, ), }, Return, FunctionEnd, ], entry_point: Some( EntryPoint { execution_model: GLCompute, entry_point: Id( 13, ), name: "main2", interface: [], }, ), execution_modes: [], }, }, } thread 'multiple_entry_points' panicked at 'Found runtime-sized push constants', vulkano/src/shader/reflect.rs:870:45 failures: multiple_entry_points push_uint_before_struct test result: FAILED. 0 passed; 2 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s ```

This issue might not occur if using glsl or another language that only supports 1 entry point per module, and which might order the PushConstant struct pointer declarations first before the pointers to the fields.

Solution

A more robust implementation would look for a Variable of StorageClass::PushConstant used in an EntryPoint function, and then trace the variable to its pointer type, then lookup what struct that points to.

charles-r-earp commented 10 months ago

With #2402, "push_uint_before_struct" passes, however "multiple_entry_points" does not, after adding an additional assertion that the push constant range is correct.

It looks like the push constant range is reflected globally, not per entry point, which in this test "main1" uses a push constant with 2 u32s (size = 8), and "main2" doesn't. But the test shows that both get the same range of 8.

Details ``` running 2 tests test push_uint_before_struct ... ok test multiple_entry_points ... FAILED failures: ---- multiple_entry_points stdout ---- ; SPIR-V ; Version: 1.5 ; Generator: rspirv ; Bound: 15 OpMemoryModel Logical Vulkan OpEntryPoint GLCompute %9 "main1" OpEntryPoint GLCompute %13 "main2" OpMemberDecorate %5 0 Offset 0 OpMemberDecorate %5 1 Offset 4 %1 = OpTypeVoid %2 = OpTypeFunction %1 %1 %3 = OpTypeInt 32 0 %4 = OpConstant %3 0 %5 = OpTypeStruct %3 %3 %6 = OpTypePointer PushConstant %5 %7 = OpTypePointer PushConstant %3 %8 = OpVariable %6 PushConstant %9 = OpFunction %1 None %2 %10 = OpLabel %11 = OpAccessChain %7 %8 %4 %12 = OpLoad %3 %11 OpReturn OpFunctionEnd %13 = OpFunction %1 None %2 %14 = OpLabel OpReturn OpFunctionEnd [reflect-push-constants-issue/src/lib.rs:105] &spirv = Spirv { version: 1.5.0, bound: 15, ids: { Id( 5, ): IdInfo { instruction: TypeStruct { result_id: Id( 5, ), member_types: [ Id( 3, ), Id( 3, ), ], }, names: [], decorations: [], members: [ StructMemberInfo { names: [], decorations: [ MemberDecorate { structure_type: Id( 5, ), member: 0, decoration: Offset { byte_offset: 0, }, }, ], }, StructMemberInfo { names: [], decorations: [ MemberDecorate { structure_type: Id( 5, ), member: 1, decoration: Offset { byte_offset: 4, }, }, ], }, ], }, Id( 8, ): IdInfo { instruction: Variable { result_type_id: Id( 6, ), result_id: Id( 8, ), storage_class: PushConstant, initializer: None, }, names: [], decorations: [], members: [], }, Id( 1, ): IdInfo { instruction: TypeVoid { result_id: Id( 1, ), }, names: [], decorations: [], members: [], }, Id( 14, ): IdInfo { instruction: Label { result_id: Id( 14, ), }, names: [], decorations: [], members: [], }, Id( 4, ): IdInfo { instruction: Constant { result_type_id: Id( 3, ), result_id: Id( 4, ), value: [ 0, ], }, names: [], decorations: [], members: [], }, Id( 11, ): IdInfo { instruction: AccessChain { result_type_id: Id( 7, ), result_id: Id( 11, ), base: Id( 8, ), indexes: [ Id( 4, ), ], }, names: [], decorations: [], members: [], }, Id( 3, ): IdInfo { instruction: TypeInt { result_id: Id( 3, ), width: 32, signedness: 0, }, names: [], decorations: [], members: [], }, Id( 12, ): IdInfo { instruction: Load { result_type_id: Id( 3, ), result_id: Id( 12, ), pointer: Id( 11, ), memory_access: None, }, names: [], decorations: [], members: [], }, Id( 13, ): IdInfo { instruction: Function { result_type_id: Id( 1, ), result_id: Id( 13, ), function_control: FunctionControl { inline: false, dont_inline: false, pure: false, constant: false, opt_none_intel: false, }, function_type: Id( 2, ), }, names: [], decorations: [], members: [], }, Id( 9, ): IdInfo { instruction: Function { result_type_id: Id( 1, ), result_id: Id( 9, ), function_control: FunctionControl { inline: false, dont_inline: false, pure: false, constant: false, opt_none_intel: false, }, function_type: Id( 2, ), }, names: [], decorations: [], members: [], }, Id( 2, ): IdInfo { instruction: TypeFunction { result_id: Id( 2, ), return_type: Id( 1, ), parameter_types: [ Id( 1, ), ], }, names: [], decorations: [], members: [], }, Id( 10, ): IdInfo { instruction: Label { result_id: Id( 10, ), }, names: [], decorations: [], members: [], }, Id( 7, ): IdInfo { instruction: TypePointer { result_id: Id( 7, ), storage_class: PushConstant, ty: Id( 3, ), }, names: [], decorations: [], members: [], }, Id( 6, ): IdInfo { instruction: TypePointer { result_id: Id( 6, ), storage_class: PushConstant, ty: Id( 5, ), }, names: [], decorations: [], members: [], }, }, instructions_capability: [], instructions_extension: [], instructions_ext_inst_import: [], instruction_memory_model: MemoryModel { addressing_model: Logical, memory_model: Vulkan, }, instructions_entry_point: [ EntryPoint { execution_model: GLCompute, entry_point: Id( 9, ), name: "main1", interface: [], }, EntryPoint { execution_model: GLCompute, entry_point: Id( 13, ), name: "main2", interface: [], }, ], instructions_execution_mode: [], instructions_name: [], instructions_decoration: [ MemberDecorate { structure_type: Id( 5, ), member: 0, decoration: Offset { byte_offset: 0, }, }, MemberDecorate { structure_type: Id( 5, ), member: 1, decoration: Offset { byte_offset: 4, }, }, ], instructions_global: [ TypeVoid { result_id: Id( 1, ), }, TypeFunction { result_id: Id( 2, ), return_type: Id( 1, ), parameter_types: [ Id( 1, ), ], }, TypeInt { result_id: Id( 3, ), width: 32, signedness: 0, }, Constant { result_type_id: Id( 3, ), result_id: Id( 4, ), value: [ 0, ], }, TypeStruct { result_id: Id( 5, ), member_types: [ Id( 3, ), Id( 3, ), ], }, TypePointer { result_id: Id( 6, ), storage_class: PushConstant, ty: Id( 5, ), }, TypePointer { result_id: Id( 7, ), storage_class: PushConstant, ty: Id( 3, ), }, Variable { result_type_id: Id( 6, ), result_id: Id( 8, ), storage_class: PushConstant, initializer: None, }, ], functions: { Id( 9, ): FunctionInfo { instructions: [ Function { result_type_id: Id( 1, ), result_id: Id( 9, ), function_control: FunctionControl { inline: false, dont_inline: false, pure: false, constant: false, opt_none_intel: false, }, function_type: Id( 2, ), }, Label { result_id: Id( 10, ), }, AccessChain { result_type_id: Id( 7, ), result_id: Id( 11, ), base: Id( 8, ), indexes: [ Id( 4, ), ], }, Load { result_type_id: Id( 3, ), result_id: Id( 12, ), pointer: Id( 11, ), memory_access: None, }, Return, FunctionEnd, ], entry_point: Some( EntryPoint { execution_model: GLCompute, entry_point: Id( 9, ), name: "main1", interface: [], }, ), execution_modes: [], }, Id( 13, ): FunctionInfo { instructions: [ Function { result_type_id: Id( 1, ), result_id: Id( 13, ), function_control: FunctionControl { inline: false, dont_inline: false, pure: false, constant: false, opt_none_intel: false, }, function_type: Id( 2, ), }, Label { result_id: Id( 14, ), }, Return, FunctionEnd, ], entry_point: Some( EntryPoint { execution_model: GLCompute, entry_point: Id( 13, ), name: "main2", interface: [], }, ), execution_modes: [], }, }, } [reflect-push-constants-issue/src/lib.rs:107] &entry_points = { Id( 13, ): EntryPointInfo { name: "main2", execution_model: GLCompute, descriptor_binding_requirements: {}, push_constant_requirements: Some( PushConstantRange { stages: COMPUTE, offset: 0, size: 8, }, ), input_interface: ShaderInterface { elements: [], }, output_interface: ShaderInterface { elements: [], }, }, Id( 9, ): EntryPointInfo { name: "main1", execution_model: GLCompute, descriptor_binding_requirements: {}, push_constant_requirements: Some( PushConstantRange { stages: COMPUTE, offset: 0, size: 8, }, ), input_interface: ShaderInterface { elements: [], }, output_interface: ShaderInterface { elements: [], }, }, } thread 'multiple_entry_points' panicked at reflect-push-constants-issue/src/lib.rs:118:5: assertion `left == right` failed left: Some(PushConstantRange { stages: COMPUTE, offset: 0, size: 8 }) right: None note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace failures: multiple_entry_points ```
marc0246 commented 10 months ago

There's a TODO Rua left in the PR, I'm not sure how to go about that myself.