1     let sizes : Map = array@
2         "bool" => 4
3         "int" => 4
4         "uint" => 4
5         "float" => 4
6         "vec2" => 8
7         "ivec2" => 8
8         "uvec2" => 8
9         "vec3" => 12
10        "vec4" => 16
11    
12    type Binding =
13        val descriptor_type : DescriptorType
14        val descriptor_count : u32
15        val set : u32
16        val binding : u32
17        val name : String
18        var stage_flags : ShaderStageFlags @mut
19    
20        def compare (other : Binding) = when
21            binding < other.binding -> Ordering/Less
22            binding > other.binding -> Ordering/Greater
23            descriptor_type as i32 < other.descriptor_type as i32 -> Ordering/Less
24            descriptor_type as i32 > other.descriptor_type as i32 -> Ordering/Greater
25            descriptor_count < other.descriptor_count -> Ordering/Less
26            descriptor_count > other.descriptor_count -> Ordering/Greater
27            else -> Ordering/Equal
28    
29        is Compare
30    
31    type Metadata =
32        val push_constant_stages : ShaderStageFlags
33        val push_constant_size : u32
34        val vertex_input_mask : u32
35        val fragment_output_mask : u32
36        val bindings : List<Binding>
37    
38    let remove_comment (text : String) =
39        if text.contains "//" then
40            let position = text.index_of "//"
41            text.take position
42        else
43            text
44    
45    let is_push_constant (text : String) = text.contains "push_constant"
46    
47    let get_binding_lines (text : String) =
48        text
49        |> lines
50        |> filter { l -> l.trim_start.starts_with "layout"
51                         && (l.contains "uniform" || l.contains " buffer ") }
52        |> map { remove_comment _ }
53        |> filter { not is_push_constant _ }
54    
55    let get_lines (text : String) (term : String) =
56        text
57        |> lines
58        |> filter { l -> l.trim_start.starts_with "layout" && l.contains term }
59        |> map { remove_comment _ }
60    
61    let get_content (text : String) =
62        let i = text.index_of '('
63        let j = text.index_of ')'
64        text.substring (i + 1) j
65    
66    let get_terms (text : String) =
67        let index = text.index_of ')'
68        let mut s = text.substring (index + 2) text.trim.length
69        if s.ends_with ';' then
70            s = s.drop_last 1
71    
72        s.split ' ' |> map { _.trim }
73    
74    let get_type (term_types : Map<String, DescriptorType>) (terms : List<String>) =
75        if (terms.contains "image2D" || terms.contains "image2DArray")
76           && terms.contains "writeonly"
77        then
78            return DescriptorType/StorageImage
79    
80        for term in terms do
81            if term_types.contains term then
82                return term_types[term]
83    
84        if terms.contains "uniform"
85        then DescriptorType/UniformBuffer
86        else DescriptorType/StorageBuffer
87    
88    let get_count (text : String) : u32 =
89        if not text.contains '[' then
90            return 1
91    
92        let i = text.index_of '['
93        let j = text.index_of ']'
94        let s = text.substring (i + 1) j
95    
96        u32.parse s
97    
98    let get_name (text : String) =
99        let s = text.trim_end
100       assert s.last == ';'
101       let index = s.last_index_of ' '
102       s.substring (index + 1) (s.length - 1)
103   
104   let get_binding (term_types : Map<String, DescriptorType>) (text : String) =
105       let content = get_content text
106       let parts = content.split ',' |> map { _.trim }
107       let terms = get_terms text
108       let descriptor_type = get_type term_types terms
109       let descriptor_count = get_count text
110       let name = get_name text
111       let mut set = 0
112       let mut binding = 0
113   
114       for part in parts do
115           if part.contains '=' then
116               let index = part.index_of '='
117               let mut (key, value) = part.split_at index
118               key = key.trim
119               value = value.drop 1 |> trim
120               if key == "set" then
121                   set = u32.parse value
122               else if key == "binding" then
123                   binding = u32.parse value
124   
125       Binding =descriptor_type
126               =descriptor_count
127               =set
128               =binding
129               =name
130               stage_flags = ShaderStageFlags@zero
131   
132   let get_int (text : String) (name : String) : i32 =
133       let content = get_content text
134       let parts = content.split ',' |> map { _.trim }
135       for part in parts do
136           if part.contains '=' then
137               let index = part.index_of '='
138               let mut (key, value) = part.split_at index
139               key = key.trim
140               if key == name then
141                   value = value.drop 1 |> trim
142                   let result = i32.parse value
143                   return result
144   
145       fail
146   
147   let get_mask (text : String) (term : String) =
148       let mut mask : u32 = 0
149       for s in get_lines text term do
150           let location = get_int s "location"
151           mask |= 1 << location
152   
153       mask
154   
155   let get_push_constant_lines (text : String) =
156       let lines = text.lines
157       let maybe_layout_line =
158           lines.try_find_index { l -> l.trim_start.starts_with "layout"
159                                       && l.contains "push_constant" }
160       let list = List.new
161       if maybe_layout_line.is_none then
162           return list
163   
164       let layout_line = maybe_layout_line.unwrap
165       let mut index = layout_line + 1
166       while not lines[index].contains '}' do
167           index += 1
168   
169       for i = layout_line + 1 until index do
170           let line = lines[i].trim
171           if line.is_not_empty then
172               list.add line
173   
174       list
175   
176   let get_size (text : String) : u32 =
177       let s = text.split ' ' |> first
178       let count = get_count text
179       sizes[s] * count
180   
181   let get_push_constant_size (text : String) : u32 =
182       let lines = get_push_constant_lines text
183       lines.map { get_size _ } |> sum
184   
185   let remove_blocks (s : String) =
186       let sb = StringBuilder.new
187       let mut level = 0
188       let mut remove_end_of_line = false
189       let mut is_push_constant = false
190   
191       for line in s.lines do
192           if not is_push_constant then
193               is_push_constant = line.contains "push_constant"
194   
195           for c in line.chars do
196               if is_push_constant then
197                   if c == '}' then
198                       is_push_constant = false
199   
200                   sb.append c
201               else
202                   if c == '{' then
203                       level += 1
204   
205                   else if c == '}' then
206                       level -= 1
207                       if level == 0 then
208                           remove_end_of_line = true
209   
210                   else if level == 0 then
211                       sb.append c
212   
213           if remove_end_of_line then
214               remove_end_of_line = false
215   
216           else if level == 0 then
217               sb.append '\n'
218   
219       sb.create
220   
221   module metadata
222   
223   def get (path : String) =
224       let text = path |> fs/read_text |> remove_blocks
225   
226       let term_types = Map<String, DescriptorType>.new
227       term_types.add "sampler" DescriptorType/Sampler
228                  add "sampler2D" DescriptorType/CombinedImageSampler
229                  add "sampler2DMS" DescriptorType/CombinedImageSampler
230                  add "sampler2DArray" DescriptorType/CombinedImageSampler
231                  add "image2D" DescriptorType/StorageImage
232                  add "image2DArray" DescriptorType/StorageImage
233                  add "texture2D" DescriptorType/SampledImage
234   
235       let stage_flags = when
236           path.ends_with ".vert" -> ShaderStageFlags/Vertex
237           path.ends_with ".frag" -> ShaderStageFlags/Fragment
238           path.ends_with ".comp" -> ShaderStageFlags/Compute
239           else -> fail
240   
241       let bindings = get_binding_lines text |> map { get_binding term_types _ }
242       bindings.sort_by_key { _.binding }
243       for binding in bindings do
244           binding.stage_flags = stage_flags
245   
246       let mut vertex_input_mask = 0
247       let mut fragment_output_mask = 0
248       let mut push_constant_stages = ShaderStageFlags@zero
249   
250       let push_constant_size = get_push_constant_size text
251       if path.ends_with ".vert" then
252           vertex_input_mask = get_mask text " in "
253           if push_constant_size > 0 then
254               push_constant_stages = ShaderStageFlags/Vertex
255   
256       else if path.ends_with ".frag" then
257           fragment_output_mask = get_mask text " out "
258           if push_constant_size > 0 then
259               push_constant_stages = ShaderStageFlags/Fragment
260   
261       else if path.ends_with ".comp" then
262           if push_constant_size > 0 then
263               push_constant_stages = ShaderStageFlags/Compute
264   
265       else
266           fail
267   
268       Metadata =push_constant_stages
269                =push_constant_size
270                =vertex_input_mask
271                =fragment_output_mask
272                =bindings
273