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
use super::*;

pub struct Program {
    pub(crate) ugli: Ugli,
    pub(crate) handle: raw::Program,
    pub(crate) attributes: HashMap<String, AttributeInfo>,
    pub(crate) uniforms: HashMap<String, UniformInfo>,
    phantom_data: PhantomData<*mut ()>,
}

#[derive(Debug)]
pub struct AttributeInfo {
    pub(crate) location: raw::UInt,
    #[allow(dead_code)]
    pub(crate) info: raw::ActiveInfo,
}

#[derive(Debug)]
pub struct UniformInfo {
    pub(crate) location: raw::UniformLocation,
    pub(crate) name: String,
    // pub(crate) info: raw::ActiveInfo,
    pub(crate) default: Option<UniformValue>,
}

impl Drop for Program {
    fn drop(&mut self) {
        self.ugli.inner.raw.delete_program(&self.handle);
    }
}

#[derive(Debug, thiserror::Error)]
#[error("Program link failed:\n{log}")]
pub struct ProgramLinkError {
    pub log: String,
}

impl Program {
    pub fn new<'a>(
        ugli: &Ugli,
        shaders: impl IntoIterator<Item = &'a Shader>,
    ) -> Result<Self, ProgramLinkError> {
        let shaders: Vec<&Shader> = shaders.into_iter().collect();
        let gl = &ugli.inner.raw;
        let mut program = Program {
            ugli: ugli.clone(),
            handle: gl.create_program().expect("Failed to create program"),
            uniforms: HashMap::new(),
            attributes: HashMap::new(),
            phantom_data: PhantomData,
        };
        for shader in &shaders {
            gl.attach_shader(&program.handle, &shader.handle);
        }
        gl.link_program(&program.handle);
        for shader in &shaders {
            gl.detach_shader(&program.handle, &shader.handle);
        }

        // Check for errors
        let link_status = gl.get_program_parameter_bool(&program.handle, raw::LINK_STATUS);
        if link_status == raw::FALSE {
            return Err(ProgramLinkError {
                log: gl.get_program_info_log(&program.handle),
            });
        }

        // Get attributes
        let attribute_count =
            gl.get_program_parameter_int(&program.handle, raw::ACTIVE_ATTRIBUTES) as usize;
        for index in 0..attribute_count {
            let info = gl.get_active_attrib(&program.handle, index as raw::UInt);
            let name = info.name.clone();
            let location = gl.get_attrib_location(&program.handle, &name);
            // TODO: why can't this be an assert?
            if location >= 0 {
                program.attributes.insert(
                    name,
                    AttributeInfo {
                        location: location as raw::UInt,
                        info,
                    },
                );
            }
        }

        // Get uniforms
        let uniform_count =
            gl.get_program_parameter_int(&program.handle, raw::ACTIVE_UNIFORMS) as usize;
        for index in 0..uniform_count {
            let info = gl.get_active_uniform(&program.handle, index as raw::UInt);
            for index in 0..info.size {
                let name = match info.size {
                    1 => info.name.clone(),
                    _ => format!("{}[{index}]", info.name.strip_suffix("[0]").unwrap()),
                };
                if let Some(location) = gl.get_uniform_location(&program.handle, &name) {
                    let default = UniformValue::get_value(gl, &program.handle, &location, &info);
                    // info!("{:?}", name);
                    program.uniforms.insert(
                        name.clone(),
                        UniformInfo {
                            location,
                            name,
                            // info,
                            default,
                        },
                    );
                }
            }
        }

        ugli.debug_check();
        Ok(program)
    }
    pub(crate) fn bind(&self) {
        self.ugli.inner.raw.use_program(&self.handle);
    }
}