1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
use crate::error::{SpirvCrossError, ToContextError};
use crate::handle::{Handle, VariableId};
use crate::{error, Compiler, PhantomCompiler};
use spirv_cross_sys as sys;
use std::slice;

/// A proof that [`Compiler::create_dummy_sampler_for_combined_images`] was called.
#[derive(Debug, Copy, Clone)]
pub struct BuiltDummySamplerProof {
    /// The handle to a sampler object, if one was needed to be created.
    pub sampler_id: Option<Handle<VariableId>>,
    label: Handle<()>,
}

/// Iterator for [`CombinedImageSampler`].
pub struct CombinedImageSamplerIter<'a>(
    PhantomCompiler<'a>,
    slice::Iter<'a, sys::spvc_combined_image_sampler>,
);

impl Iterator for CombinedImageSamplerIter<'_> {
    type Item = CombinedImageSampler;

    fn next(&mut self) -> Option<Self::Item> {
        self.1.next().map(|c| {
            let combined_id = self.0.create_handle(c.combined_id);
            let image_id = self.0.create_handle(c.image_id);
            let sampler_id = self.0.create_handle(c.sampler_id);

            CombinedImageSampler {
                combined_id,
                image_id,
                sampler_id,
            }
        })
    }
}

/// A combined image sampler.
pub struct CombinedImageSampler {
    /// A handle to the created combined image sampler.
    pub combined_id: Handle<VariableId>,
    /// A handle to the split image of the combined image sampler.
    pub image_id: Handle<VariableId>,
    /// A handle to the split sampler of the combined image sampler.
    pub sampler_id: Handle<VariableId>,
}

impl<'ctx, T> Compiler<'ctx, T> {
    /// Analyzes all OpImageFetch (texelFetch) opcodes and checks if there are instances where
    /// said instruction is used without a combined image sampler.
    /// GLSL targets do not support the use of texelFetch without a sampler.
    /// To work around this, we must inject a dummy sampler which can be used to form a sampler2D at the call-site of
    /// texelFetch as necessary.
    ///
    /// This must be called to obtain a proof to call [`Compiler::build_combined_image_samplers`].
    ///
    /// The proof contains the ID of a sampler object, if one dummy sampler is necessary. This ID can
    /// be decorated with set/bindings as desired before compiling.
    pub fn create_dummy_sampler_for_combined_images(
        &mut self,
    ) -> error::Result<BuiltDummySamplerProof> {
        unsafe {
            let mut var_id = VariableId::from(0);
            sys::spvc_compiler_build_dummy_sampler_for_combined_images(
                self.ptr.as_ptr(),
                &mut var_id,
            )
            .ok(&*self)?;

            let sampler_id = self.create_handle_if_not_zero(var_id);

            Ok(BuiltDummySamplerProof {
                sampler_id,
                label: self.create_handle(()),
            })
        }
    }

    /// Analyzes all separate image and samplers used from the currently selected entry point,
    /// and re-routes them all to a combined image sampler instead.
    /// This is required to "support" separate image samplers in targets which do not natively support
    /// this feature, like GLSL/ESSL.
    ///
    /// This call will add new sampled images to the SPIR-V,
    /// so it will appear in reflection if [`Compiler::shader_resources`] is called after.
    ///
    /// If any image/sampler remapping was found, no separate image/samplers will appear in the decompiled output,
    /// but will still appear in reflection.
    ///
    /// The resulting samplers will be void of any decorations like name, descriptor sets and binding points,
    /// so this can be added before compilation if desired.
    ///
    /// Combined image samplers originating from this set are always considered active variables.
    /// Arrays of separate samplers are not supported, but arrays of separate images are supported.
    /// Array of images + sampler -> Array of combined image samplers.
    ///
    /// [`Compiler::create_dummy_sampler_for_combined_images`] must be called before this to obtain
    /// a proof that a dummy sampler, if necessary, was created. Passing in a smuggled proof from
    /// a different compiler instance will result in an error.
    pub fn build_combined_image_samplers(
        &mut self,
        proof: BuiltDummySamplerProof,
    ) -> error::Result<()> {
        // check for smuggling
        if !self.handle_is_valid(&proof.label) {
            return Err(SpirvCrossError::InvalidOperation(String::from(
                "The provided proof of building combined image samplers is invalid",
            )));
        }

        unsafe {
            sys::spvc_compiler_build_combined_image_samplers(self.ptr.as_ptr()).ok(self)?;

            Ok(())
        }
    }

    /// Gets a remapping for the combined image samplers.
    pub fn combined_image_samplers(&self) -> error::Result<CombinedImageSamplerIter<'ctx>> {
        unsafe {
            let mut samplers = std::ptr::null();
            let mut size = 0;

            // SAFETY: 'ctx is sound here.
            // https://github.com/KhronosGroup/SPIRV-Cross/blob/main/spirv_cross_c.cpp#L2497
            sys::spvc_compiler_get_combined_image_samplers(
                self.ptr.as_ptr(),
                &mut samplers,
                &mut size,
            )
            .ok(self)?;
            let slice = slice::from_raw_parts(samplers, size);
            Ok(CombinedImageSamplerIter(self.phantom(), slice.iter()))
        }
    }
}

#[cfg(test)]
mod test {
    use crate::error::SpirvCrossError;
    use crate::Compiler;
    use crate::{targets, Module, SpirvCrossContext};

    static BASIC_SPV: &[u8] = include_bytes!("../../basic.spv");

    #[test]
    pub fn test_combined_image_sampler_build() -> Result<(), SpirvCrossError> {
        let spv = SpirvCrossContext::new()?;
        let vec = Vec::from(BASIC_SPV);
        let words = Module::from_words(bytemuck::cast_slice(&vec));

        let mut compiler: Compiler<targets::None> = spv.create_compiler(words)?;

        let proof = compiler.create_dummy_sampler_for_combined_images()?;
        compiler.build_combined_image_samplers(proof)?;

        // match ty.inner {
        //     TypeInner::Struct(ty) => {
        //         compiler.get_type(ty.members[0].id)?;
        //     }
        //     TypeInner::Vector { .. } => {}
        //     _ => {}
        // }
        Ok(())
    }
}