This is page 9 of 9. Use http://codebase.md/mehmetoguzderin/shaderc-vkrunner-mcp?lines=true&page={x} to view the full context.
# Directory Structure
```
├── .devcontainer
│ ├── devcontainer.json
│ ├── docker-compose.yml
│ └── Dockerfile
├── .gitattributes
├── .github
│ └── workflows
│ └── build-push-image.yml
├── .gitignore
├── .vscode
│ └── mcp.json
├── Cargo.lock
├── Cargo.toml
├── Dockerfile
├── LICENSE
├── README.adoc
├── shaderc-vkrunner-mcp.jpg
├── src
│ └── main.rs
└── vkrunner
├── .editorconfig
├── .gitignore
├── .gitlab-ci.yml
├── build.rs
├── Cargo.toml
├── COPYING
├── examples
│ ├── compute-shader.shader_test
│ ├── cooperative-matrix.shader_test
│ ├── depth-buffer.shader_test
│ ├── desc_set_and_binding.shader_test
│ ├── entrypoint.shader_test
│ ├── float-framebuffer.shader_test
│ ├── frexp.shader_test
│ ├── geometry.shader_test
│ ├── indices.shader_test
│ ├── layouts.shader_test
│ ├── properties.shader_test
│ ├── push-constants.shader_test
│ ├── require-subgroup-size.shader_test
│ ├── row-major.shader_test
│ ├── spirv.shader_test
│ ├── ssbo.shader_test
│ ├── tolerance.shader_test
│ ├── tricolore.shader_test
│ ├── ubo.shader_test
│ ├── vertex-data-piglit.shader_test
│ └── vertex-data.shader_test
├── include
│ ├── vk_video
│ │ ├── vulkan_video_codec_av1std_decode.h
│ │ ├── vulkan_video_codec_av1std_encode.h
│ │ ├── vulkan_video_codec_av1std.h
│ │ ├── vulkan_video_codec_h264std_decode.h
│ │ ├── vulkan_video_codec_h264std_encode.h
│ │ ├── vulkan_video_codec_h264std.h
│ │ ├── vulkan_video_codec_h265std_decode.h
│ │ ├── vulkan_video_codec_h265std_encode.h
│ │ ├── vulkan_video_codec_h265std.h
│ │ └── vulkan_video_codecs_common.h
│ └── vulkan
│ ├── vk_platform.h
│ ├── vulkan_core.h
│ └── vulkan.h
├── precompile-script.py
├── README.md
├── scripts
│ └── update-vulkan.sh
├── src
│ └── main.rs
├── test-build.sh
└── vkrunner
├── allocate_store.rs
├── buffer.rs
├── compiler
│ └── fake_process.rs
├── compiler.rs
├── config.rs
├── context.rs
├── enum_table.rs
├── env_var_test.rs
├── executor.rs
├── fake_vulkan.rs
├── features.rs
├── flush_memory.rs
├── format_table.rs
├── format.rs
├── half_float.rs
├── hex.rs
├── inspect.rs
├── lib.rs
├── logger.rs
├── make-enums.py
├── make-features.py
├── make-formats.py
├── make-pipeline-key-data.py
├── make-vulkan-funcs-data.py
├── parse_num.rs
├── pipeline_key_data.rs
├── pipeline_key.rs
├── pipeline_set.rs
├── requirements.rs
├── result.rs
├── script.rs
├── shader_stage.rs
├── slot.rs
├── small_float.rs
├── source.rs
├── stream.rs
├── temp_file.rs
├── tester.rs
├── tolerance.rs
├── util.rs
├── vbo.rs
├── vk.rs
├── vulkan_funcs_data.rs
├── vulkan_funcs.rs
├── window_format.rs
└── window.rs
```
# Files
--------------------------------------------------------------------------------
/vkrunner/vkrunner/script.rs:
--------------------------------------------------------------------------------
```rust
1 | // vkrunner
2 | //
3 | // Copyright (C) 2018 Intel Corporation
4 | // Copyright 2023 Neil Roberts
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a
7 | // copy of this software and associated documentation files (the "Software"),
8 | // to deal in the Software without restriction, including without limitation
9 | // the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 | // and/or sell copies of the Software, and to permit persons to whom the
11 | // Software is furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice (including the next
14 | // paragraph) shall be included in all copies or substantial portions of the
15 | // Software.
16 | //
17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
20 | // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
23 | // DEALINGS IN THE SOFTWARE.
24 |
25 | use crate::vbo;
26 | use crate::source::Source;
27 | use crate::stream::{Stream, StreamError};
28 | use crate::tolerance::Tolerance;
29 | use crate::pipeline_key;
30 | use crate::shader_stage::{Stage, N_STAGES};
31 | use crate::slot;
32 | use crate::requirements::{Requirements, CooperativeMatrix};
33 | use crate::window_format::WindowFormat;
34 | use crate::format::Format;
35 | use crate::hex;
36 | use crate::parse_num;
37 | use crate::vk;
38 | use crate::config::Config;
39 | use std::cell::RefCell;
40 | use std::fmt;
41 |
42 | #[derive(Debug, Clone)]
43 | pub enum Shader {
44 | Glsl(String),
45 | Spirv(String),
46 | Binary(Vec<u32>),
47 | }
48 |
49 | #[derive(Debug, Clone, Copy, PartialEq, Eq)]
50 | pub(crate) enum BufferType {
51 | Ubo,
52 | Ssbo,
53 | }
54 |
55 | #[derive(Debug, Clone)]
56 | pub(crate) struct Buffer {
57 | pub desc_set: u32,
58 | pub binding: u32,
59 | pub buffer_type: BufferType,
60 | pub size: usize,
61 | }
62 |
63 | #[derive(Debug)]
64 | pub struct Script {
65 | stages: [Box<[Shader]>; N_STAGES],
66 | commands: Box<[Command]>,
67 | pipeline_keys: Box<[pipeline_key::Key]>,
68 | requirements: Requirements,
69 | window_format: WindowFormat,
70 | vertex_data: Option<vbo::Vbo>,
71 | indices: Box<[u16]>,
72 | buffers: Box<[Buffer]>,
73 | }
74 |
75 | #[derive(Debug, Clone, PartialEq)]
76 | pub(crate) enum Operation {
77 | DrawRect {
78 | x: f32,
79 | y: f32,
80 | w: f32,
81 | h: f32,
82 | pipeline_key: usize,
83 | },
84 | DrawArrays {
85 | topology: vk::VkPrimitiveTopology,
86 | indexed: bool,
87 | vertex_count: u32,
88 | instance_count: u32,
89 | first_vertex: u32,
90 | first_instance: u32,
91 | pipeline_key: usize,
92 | },
93 | DispatchCompute {
94 | x: u32,
95 | y: u32,
96 | z: u32,
97 | pipeline_key: usize,
98 | },
99 | ProbeRect {
100 | n_components: u32,
101 | x: u32,
102 | y: u32,
103 | w: u32,
104 | h: u32,
105 | color: [f64; 4],
106 | tolerance: Tolerance,
107 | },
108 | ProbeSsbo {
109 | desc_set: u32,
110 | binding: u32,
111 | comparison: slot::Comparison,
112 | offset: usize,
113 | slot_type: slot::Type,
114 | layout: slot::Layout,
115 | values: Box<[u8]>,
116 | tolerance: Tolerance,
117 | },
118 | SetPushCommand {
119 | offset: usize,
120 | data: Box<[u8]>,
121 | },
122 | SetBufferData {
123 | desc_set: u32,
124 | binding: u32,
125 | offset: usize,
126 | data: Box<[u8]>,
127 | },
128 | Clear {
129 | color: [f32; 4],
130 | depth: f32,
131 | stencil: u32,
132 | },
133 | }
134 |
135 | #[derive(Debug, Clone, PartialEq)]
136 | pub(crate) struct Command {
137 | pub line_num: usize,
138 | pub op: Operation,
139 | }
140 |
141 | #[derive(Debug)]
142 | pub(crate) enum LoadError {
143 | Stream(StreamError),
144 | Vbo { line_num: usize, detail: vbo::Error },
145 | Invalid { line_num: usize, message: String },
146 | Hex { line_num: usize, detail: hex::ParseError },
147 | Number { line_num: usize, detail: parse_num::ParseError },
148 | }
149 |
150 | impl From<StreamError> for LoadError {
151 | fn from(error: StreamError) -> LoadError {
152 | LoadError::Stream(error)
153 | }
154 | }
155 |
156 | impl fmt::Display for LoadError {
157 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
158 | match self {
159 | LoadError::Stream(e) => e.fmt(f),
160 | LoadError::Vbo { line_num, detail } => {
161 | write!(f, "line {}: {}", line_num, detail)
162 | },
163 | LoadError::Invalid { line_num, message } => {
164 | write!(f, "line {}: {}", line_num, message)
165 | },
166 | LoadError::Hex { line_num, detail } => {
167 | write!(f, "line {}: {}", line_num, detail)
168 | },
169 | LoadError::Number { line_num, detail } => {
170 | write!(f, "line {}: {}", line_num, detail)
171 | },
172 | }
173 | }
174 | }
175 |
176 | #[derive(Clone, Copy, Debug)]
177 | enum Section {
178 | None = 0,
179 | Comment,
180 | Require,
181 | Shader,
182 | VertexData,
183 | Indices,
184 | Test,
185 | }
186 |
187 | #[derive(PartialEq, Eq, Debug)]
188 | enum MatchResult {
189 | // The line was successfully parsed
190 | Matched,
191 | // The line does not match the command handled by this method so
192 | // the parser should try parsing it as something else.
193 | NotMatched,
194 | }
195 |
196 | // Macro to handle the match result. The calling function should be a
197 | // Result<(), T> type and the function to call should return
198 | // Result<MatchResult, T>. If the called function matched the line or
199 | // returned an error then it will cause the calling function to
200 | // return. Otherwise it can continue to try the next function.
201 | macro_rules! handle_match_result {
202 | ($func:expr) => {
203 | match $func? {
204 | MatchResult::NotMatched => (),
205 | MatchResult::Matched => return Ok(()),
206 | }
207 | };
208 | }
209 |
210 | macro_rules! parse_num_func {
211 | ($func:ident, $type:ty) => {
212 | fn $func<'b>(&self, s: &'b str) -> Result<($type, &'b str), LoadError> {
213 | parse_num::$func(s).or_else(|detail| Err(LoadError::Number {
214 | line_num: self.stream.line_num(),
215 | detail,
216 | }))
217 | }
218 | };
219 | }
220 |
221 | // Result returned by a line parser method. It can either succeed,
222 | // report that the line isn’t intended as this type of item, or report
223 | // an error.
224 | type ParseResult = Result<MatchResult, LoadError>;
225 |
226 | struct Loader<'a> {
227 | stream: Stream<'a>,
228 | current_section: Section,
229 | had_sections: u32,
230 | current_source: RefCell<Option<Shader>>,
231 | current_stage: Stage,
232 | stages: [Vec::<Shader>; N_STAGES],
233 | tolerance: Tolerance,
234 | clear_color: [f32; 4],
235 | clear_depth: f32,
236 | clear_stencil: u32,
237 | commands: Vec<Command>,
238 | pipeline_keys: Vec<pipeline_key::Key>,
239 | current_key: pipeline_key::Key,
240 | push_layout: slot::Layout,
241 | ubo_layout: slot::Layout,
242 | ssbo_layout: slot::Layout,
243 | vertex_data: Option<vbo::Vbo>,
244 | vbo_parser: Option<vbo::Parser>,
245 | indices: Vec<u16>,
246 | requirements: Requirements,
247 | window_format: WindowFormat,
248 | buffers: Vec<Buffer>,
249 | }
250 |
251 | const DEFAULT_PUSH_LAYOUT: slot::Layout = slot::Layout {
252 | std: slot::LayoutStd::Std430,
253 | major: slot::MajorAxis::Column
254 | };
255 |
256 | const DEFAULT_UBO_LAYOUT: slot::Layout = slot::Layout {
257 | std: slot::LayoutStd::Std140,
258 | major: slot::MajorAxis::Column
259 | };
260 |
261 | const DEFAULT_SSBO_LAYOUT: slot::Layout = slot::Layout {
262 | std: slot::LayoutStd::Std430,
263 | major: slot::MajorAxis::Column
264 | };
265 |
266 | static STAGE_NAMES: [(Stage, &'static str); N_STAGES] = [
267 | (Stage::Vertex, "vertex"),
268 | (Stage::TessCtrl, "tessellation control"),
269 | (Stage::TessEval, "tessellation evaluation"),
270 | (Stage::Geometry, "geometry"),
271 | (Stage::Fragment, "fragment"),
272 | (Stage::Compute, "compute"),
273 | ];
274 |
275 | // Mapping of topology name to Vulkan topology enum, sorted
276 | // alphabetically so we can do a binary search.
277 | static TOPOLOGY_NAMES: [(&'static str, vk::VkPrimitiveTopology); 22] = [
278 | // GL names used in Piglit
279 | ("GL_LINES", vk::VK_PRIMITIVE_TOPOLOGY_LINE_LIST),
280 | ("GL_LINES_ADJACENCY", vk::VK_PRIMITIVE_TOPOLOGY_LINE_LIST_WITH_ADJACENCY),
281 | ("GL_LINE_STRIP", vk::VK_PRIMITIVE_TOPOLOGY_LINE_STRIP),
282 | ("GL_LINE_STRIP_ADJACENCY",
283 | vk::VK_PRIMITIVE_TOPOLOGY_LINE_STRIP_WITH_ADJACENCY),
284 | ("GL_PATCHES", vk::VK_PRIMITIVE_TOPOLOGY_PATCH_LIST),
285 | ("GL_POINTS", vk::VK_PRIMITIVE_TOPOLOGY_POINT_LIST),
286 | ("GL_TRIANGLES", vk::VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST),
287 | ("GL_TRIANGLES_ADJACENCY",
288 | vk::VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST_WITH_ADJACENCY),
289 | ("GL_TRIANGLE_FAN", vk::VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN),
290 | ("GL_TRIANGLE_STRIP", vk::VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP),
291 | ("GL_TRIANGLE_STRIP_ADJACENCY",
292 | vk::VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP_WITH_ADJACENCY),
293 | // Vulkan names
294 | ("LINE_LIST", vk::VK_PRIMITIVE_TOPOLOGY_LINE_LIST),
295 | ("LINE_LIST_WITH_ADJACENCY",
296 | vk::VK_PRIMITIVE_TOPOLOGY_LINE_LIST_WITH_ADJACENCY),
297 | ("LINE_STRIP", vk::VK_PRIMITIVE_TOPOLOGY_LINE_STRIP),
298 | ("LINE_STRIP_WITH_ADJACENCY",
299 | vk::VK_PRIMITIVE_TOPOLOGY_LINE_STRIP_WITH_ADJACENCY),
300 | ("PATCH_LIST", vk::VK_PRIMITIVE_TOPOLOGY_PATCH_LIST),
301 | ("POINT_LIST", vk::VK_PRIMITIVE_TOPOLOGY_POINT_LIST),
302 | ("TRIANGLE_FAN", vk::VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN),
303 | ("TRIANGLE_LIST", vk::VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST),
304 | ("TRIANGLE_LIST_WITH_ADJACENCY",
305 | vk::VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST_WITH_ADJACENCY),
306 | ("TRIANGLE_STRIP", vk::VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP),
307 | ("TRIANGLE_STRIP_WITH_ADJACENCY",
308 | vk::VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP_WITH_ADJACENCY),
309 | ];
310 |
311 | static PASSTHROUGH_VERTEX_SHADER: [u32; 69] = [
312 | 0x07230203, 0x00010000, 0x00070000, 0x0000000c, 0x00000000, 0x00020011,
313 | 0x00000001, 0x0003000e, 0x00000000, 0x00000001, 0x0007000f, 0x00000000,
314 | 0x00000001, 0x6e69616d, 0x00000000, 0x00000002, 0x00000003, 0x00040047,
315 | 0x00000002, 0x0000001e, 0x00000000, 0x00040047, 0x00000003, 0x0000000b,
316 | 0x00000000, 0x00020013, 0x00000004, 0x00030021, 0x00000005, 0x00000004,
317 | 0x00030016, 0x00000006, 0x00000020, 0x00040017, 0x00000007, 0x00000006,
318 | 0x00000004, 0x00040020, 0x00000008, 0x00000001, 0x00000007, 0x00040020,
319 | 0x00000009, 0x00000003, 0x00000007, 0x0004003b, 0x00000008, 0x00000002,
320 | 0x00000001, 0x0004003b, 0x00000009, 0x00000003, 0x00000003, 0x00050036,
321 | 0x00000004, 0x00000001, 0x00000000, 0x00000005, 0x000200f8, 0x0000000a,
322 | 0x0004003d, 0x00000007, 0x0000000b, 0x00000002, 0x0003003e, 0x00000003,
323 | 0x0000000b, 0x000100fd, 0x00010038
324 | ];
325 |
326 | impl Shader {
327 | fn is_spirv(&self) -> bool {
328 | match self {
329 | Shader::Binary(_) | Shader::Spirv(_) => true,
330 | Shader::Glsl(_) => false,
331 | }
332 | }
333 | }
334 |
335 | macro_rules! error_at_line {
336 | ($loader:expr, $($format_arg:expr),+) => {
337 | LoadError::Invalid {
338 | line_num: $loader.stream.line_num(),
339 | message: format!($($format_arg),+),
340 | }
341 | };
342 | }
343 |
344 | // Utility like String::strip_prefix except that it additionally
345 | // strips any leading whitespace and checks that the prefix is followed
346 | // either by the end of the string or some whitespace. The returned
347 | // tail will include the trailing whitespace if there is any.
348 | fn strip_word_prefix<'a, 'b>(s: &'a str, prefix: &'b str) -> Option<&'a str> {
349 | match s.trim_start().strip_prefix(prefix) {
350 | Some(tail) => match tail.chars().next() {
351 | // Allow the end of the string
352 | None => Some(tail),
353 | // Allow another character if it’s whitespace
354 | Some(ch) => if ch.is_whitespace() {
355 | Some(tail)
356 | } else {
357 | None
358 | },
359 | },
360 | None => None,
361 | }
362 | }
363 |
364 | // Calls strip_word_prefix for each word in the prefix string so that
365 | // for example if you call `strip_words_prefix(s, "hot potato")` then
366 | // it is the same as `strip_word_prefix` except that there can be any
367 | // number of spaces between the “hot” and “potato”.
368 | fn strip_words_prefix<'a, 'b>(
369 | mut s: &'a str,
370 | prefix: &'b str
371 | ) -> Option<&'a str> {
372 | for word in prefix.split_whitespace() {
373 | match strip_word_prefix(s, word) {
374 | None => return None,
375 | Some(tail) => s = tail,
376 | }
377 | }
378 |
379 | Some(s)
380 | }
381 |
382 | // Gets the next word from the string and returns it along with the
383 | // string tail, or None if the string doesn’t have any non-whitespace
384 | // characters
385 | fn next_word(s: &str) -> Option<(&str, &str)> {
386 | let s = s.trim_start();
387 |
388 | match s.split_whitespace().next() {
389 | Some(word) => Some((word, &s[word.len()..])),
390 | None => None,
391 | }
392 | }
393 |
394 | // Remove comments (ie, # upto the end of the line) and trim leading
395 | // and trailing whitespace. If the line ends up empty, return None,
396 | // otherwise return the trimmed string.
397 | fn trim_line_or_skip(line: &str) -> Option<&str> {
398 | // Remove comments
399 | let line = line
400 | .split_once('#')
401 | .and_then(|(line, _tail)| Some(line))
402 | .unwrap_or(line)
403 | .trim();
404 |
405 | if line.is_empty() {
406 | None
407 | } else {
408 | Some(line)
409 | }
410 | }
411 |
412 | static NAMES_TO_VK_COMPONENT_TYPE: [(&'static str, vk::VkComponentTypeKHR); 24] = [
413 | ("bfloat16", vk::VK_COMPONENT_TYPE_BFLOAT16_KHR),
414 | ("bfloat16_t", vk::VK_COMPONENT_TYPE_BFLOAT16_KHR),
415 | ("double", vk::VK_COMPONENT_TYPE_FLOAT64_KHR),
416 | ("float", vk::VK_COMPONENT_TYPE_FLOAT32_KHR),
417 | ("float16", vk::VK_COMPONENT_TYPE_FLOAT16_KHR),
418 | ("float16_t", vk::VK_COMPONENT_TYPE_FLOAT16_KHR),
419 | ("float32", vk::VK_COMPONENT_TYPE_FLOAT32_KHR),
420 | ("float64", vk::VK_COMPONENT_TYPE_FLOAT64_KHR),
421 | ("int", vk::VK_COMPONENT_TYPE_SINT32_KHR),
422 | ("int16_t", vk::VK_COMPONENT_TYPE_SINT16_KHR),
423 | ("int64_t", vk::VK_COMPONENT_TYPE_SINT64_KHR),
424 | ("int8_t", vk::VK_COMPONENT_TYPE_SINT8_KHR),
425 | ("sint16", vk::VK_COMPONENT_TYPE_SINT16_KHR),
426 | ("sint32", vk::VK_COMPONENT_TYPE_SINT32_KHR),
427 | ("sint64", vk::VK_COMPONENT_TYPE_SINT64_KHR),
428 | ("sint8", vk::VK_COMPONENT_TYPE_SINT8_KHR),
429 | ("uint", vk::VK_COMPONENT_TYPE_UINT32_KHR),
430 | ("uint16", vk::VK_COMPONENT_TYPE_UINT16_KHR),
431 | ("uint16_t", vk::VK_COMPONENT_TYPE_UINT16_KHR),
432 | ("uint32", vk::VK_COMPONENT_TYPE_UINT32_KHR),
433 | ("uint64", vk::VK_COMPONENT_TYPE_UINT64_KHR),
434 | ("uint64_t", vk::VK_COMPONENT_TYPE_UINT64_KHR),
435 | ("uint8", vk::VK_COMPONENT_TYPE_UINT8_KHR),
436 | ("uint8_t", vk::VK_COMPONENT_TYPE_UINT8_KHR),
437 | ];
438 |
439 | fn vk_component_type_from_name(type_name: &str) -> Option<vk::VkComponentTypeKHR> {
440 | match NAMES_TO_VK_COMPONENT_TYPE.binary_search_by(
441 | |&(name, _)| name.cmp(type_name)
442 | ) {
443 | Ok(pos) => Some(NAMES_TO_VK_COMPONENT_TYPE[pos].1),
444 | Err(_) => None,
445 | }
446 | }
447 |
448 | impl<'a> Loader<'a> {
449 | fn new(source: &Source) -> Result<Loader, LoadError> {
450 | Ok(Loader {
451 | stream: Stream::new(source)?,
452 | current_section: Section::None,
453 | had_sections: 0,
454 | current_source: RefCell::new(None),
455 | current_stage: Stage::Vertex,
456 | stages: Default::default(),
457 | tolerance: Default::default(),
458 | clear_color: [0.0f32; 4],
459 | clear_depth: 1.0,
460 | clear_stencil: 0,
461 | commands: Vec::new(),
462 | pipeline_keys: Vec::new(),
463 | current_key: Default::default(),
464 | push_layout: DEFAULT_PUSH_LAYOUT,
465 | ubo_layout: DEFAULT_UBO_LAYOUT,
466 | ssbo_layout: DEFAULT_SSBO_LAYOUT,
467 | vertex_data: None,
468 | vbo_parser: None,
469 | indices: Vec::new(),
470 | requirements: Requirements::new(),
471 | window_format: Default::default(),
472 | buffers: Vec::new(),
473 | })
474 | }
475 |
476 | fn end_shader(&mut self) {
477 | self.stages[self.current_stage as usize]
478 | .push(self.current_source.take().unwrap());
479 | }
480 |
481 | fn end_vertex_data(&mut self) -> Result<(), LoadError> {
482 | match self.vbo_parser.take().unwrap().into_vbo() {
483 | Ok(vbo) => {
484 | self.vertex_data.replace(vbo);
485 | Ok(())
486 | },
487 | Err(e) => Err(LoadError::Vbo {
488 | line_num: self.stream.line_num(),
489 | detail: e,
490 | }),
491 | }
492 | }
493 |
494 | fn end_section(&mut self) -> Result<(), LoadError> {
495 | match self.current_section {
496 | Section::None => (),
497 | Section::Comment => (),
498 | Section::Require => (),
499 | Section::Shader => self.end_shader(),
500 | Section::VertexData => self.end_vertex_data()?,
501 | Section::Indices => (),
502 | Section::Test => (),
503 | }
504 |
505 | self.current_section = Section::None;
506 |
507 | Ok(())
508 | }
509 |
510 | fn set_current_section(&mut self, section: Section) {
511 | self.had_sections |= 1 << (section as u32);
512 | self.current_section = section;
513 | }
514 |
515 | fn is_stage_name<'b>(
516 | line: &'b str,
517 | suffix: &str
518 | ) -> Option<(Stage, &'b str)> {
519 | assert_eq!(STAGE_NAMES.len(), N_STAGES);
520 |
521 | for &(stage, name) in STAGE_NAMES.iter() {
522 | if let Some(tail) = strip_words_prefix(line, name) {
523 | if let Some(tail) = strip_word_prefix(tail, suffix) {
524 | return Some((stage, tail));
525 | }
526 | }
527 | }
528 |
529 | None
530 | }
531 |
532 | fn check_add_shader(
533 | &mut self,
534 | stage: Stage,
535 | shader: &Shader
536 | ) -> Result<(), LoadError> {
537 | if let Some(other) = self.stages[stage as usize].last() {
538 | if other.is_spirv() || shader.is_spirv() {
539 | return Err(error_at_line!(
540 | self,
541 | "SPIR-V source can not be linked with other shaders in the \
542 | same stage"
543 | ));
544 | }
545 | }
546 |
547 | Ok(())
548 | }
549 |
550 | fn process_stage_header(&mut self, section_name: &str) -> ParseResult {
551 | let (stage, tail) =
552 | match Loader::is_stage_name(section_name, "shader") {
553 | Some(v) => v,
554 | None => return Ok(MatchResult::NotMatched),
555 | };
556 |
557 | let (source, tail) =
558 | if let Some(tail) = strip_word_prefix(tail, "spirv") {
559 | (Shader::Spirv(String::new()), tail)
560 | } else if let Some(tail) = strip_word_prefix(tail, "binary") {
561 | (Shader::Binary(Vec::new()), tail)
562 | } else {
563 | (Shader::Glsl(String::new()), tail)
564 | };
565 |
566 | if !tail.trim_end().is_empty() {
567 | return Ok(MatchResult::NotMatched);
568 | };
569 |
570 | self.check_add_shader(stage, &source)?;
571 |
572 | self.current_source.replace(Some(source));
573 | self.set_current_section(Section::Shader);
574 | self.current_stage = stage;
575 |
576 | Ok(MatchResult::Matched)
577 | }
578 |
579 | fn add_passthrough_vertex_shader(&mut self) -> Result<(), LoadError> {
580 | let source = Shader::Binary(PASSTHROUGH_VERTEX_SHADER.to_vec());
581 |
582 | self.check_add_shader(Stage::Vertex, &source)?;
583 |
584 | // The passthrough vertex shader section shouldn’t have any data
585 | self.set_current_section(Section::None);
586 |
587 | self.stages[Stage::Vertex as usize].push(source);
588 |
589 | Ok(())
590 | }
591 |
592 | fn process_section_name(
593 | &mut self,
594 | section_name: &str,
595 | ) -> Result<(), LoadError> {
596 | if self.process_stage_header(section_name)? == MatchResult::Matched {
597 | return Ok(());
598 | }
599 |
600 | let section_name = section_name.trim();
601 |
602 | if section_name == "vertex shader passthrough" {
603 | self.add_passthrough_vertex_shader()?;
604 | return Ok(());
605 | }
606 |
607 | if section_name == "comment" {
608 | self.set_current_section(Section::Comment);
609 | return Ok(());
610 | }
611 |
612 | if section_name == "require" {
613 | // The require section must come first because the “test”
614 | // section uses the window size while parsing the
615 | // commands.
616 | if self.had_sections & !(1 << (Section::Comment as u32)) != 0 {
617 | return Err(error_at_line!(
618 | self,
619 | "[require] must be the first section"
620 | ));
621 | }
622 | self.set_current_section(Section::Require);
623 | return Ok(());
624 | }
625 |
626 | if section_name == "test" {
627 | self.set_current_section(Section::Test);
628 | return Ok(());
629 | }
630 |
631 | if section_name == "indices" {
632 | self.set_current_section(Section::Indices);
633 | return Ok(());
634 | }
635 |
636 | if section_name == "vertex data" {
637 | if !self.vertex_data.is_none() {
638 | return Err(error_at_line!(
639 | self,
640 | "Duplicate vertex data section"
641 | ));
642 | }
643 | self.set_current_section(Section::VertexData);
644 | self.vbo_parser = Some(vbo::Parser::new());
645 | return Ok(());
646 | }
647 |
648 | Err(error_at_line!(self, "Unknown section “{}”", section_name))
649 | }
650 |
651 | fn process_section_header(&mut self, line: &str) -> ParseResult {
652 | if !line.starts_with('[') {
653 | return Ok(MatchResult::NotMatched);
654 | }
655 |
656 | self.end_section()?;
657 |
658 | let section_name = match line.find(']') {
659 | None => return Err(error_at_line!(self, "Missing ‘]’")),
660 | Some(pos) => {
661 | match line.trim_end().split_at(pos) {
662 | // Match when the tail is the closing bracket
663 | // followed by nothing.
664 | (before, "]") => {
665 | // Remove the opening ‘[’
666 | &before[1..]
667 | },
668 | // Otherwise there must have been garbage after
669 | // the section header
670 | _ => {
671 | return Err(error_at_line!(
672 | self,
673 | "Trailing data after ‘]’"
674 | ));
675 | },
676 | }
677 | }
678 | };
679 |
680 | self.process_section_name(section_name)?;
681 |
682 | Ok(MatchResult::Matched)
683 | }
684 |
685 | fn process_none_line(&self, line: &str) -> Result<(), LoadError> {
686 | match trim_line_or_skip(line) {
687 | Some(_) => Err(error_at_line!(self, "expected empty line")),
688 | None => Ok(()),
689 | }
690 | }
691 |
692 | fn parse_format(&self, line: &str) -> Result<&'static Format, LoadError> {
693 | let line = line.trim_start();
694 |
695 | if line.is_empty() {
696 | return Err(error_at_line!(self, "Missing format name"));
697 | }
698 |
699 | match Format::lookup_by_name(line) {
700 | None => Err(error_at_line!(self, "Unknown format: {}", line)),
701 | Some(f) => Ok(f),
702 | }
703 | }
704 |
705 | fn parse_half_float<'b>(
706 | &self,
707 | s: &'b str
708 | ) -> Result<(u16, &'b str), LoadError> {
709 | hex::parse_half_float(s).or_else(|detail| Err(LoadError::Hex {
710 | line_num: self.stream.line_num(),
711 | detail,
712 | }))
713 | }
714 |
715 | fn parse_f32<'b>(&self, s: &'b str) -> Result<(f32, &'b str), LoadError> {
716 | hex::parse_f32(s).or_else(|detail| Err(LoadError::Hex {
717 | line_num: self.stream.line_num(),
718 | detail,
719 | }))
720 | }
721 |
722 | fn parse_f64<'b>(&self, s: &'b str) -> Result<(f64, &'b str), LoadError> {
723 | hex::parse_f64(s).or_else(|detail| Err(LoadError::Hex {
724 | line_num: self.stream.line_num(),
725 | detail,
726 | }))
727 | }
728 |
729 | parse_num_func!(parse_u8, u8);
730 | parse_num_func!(parse_u16, u16);
731 | parse_num_func!(parse_u32, u32);
732 | parse_num_func!(parse_u64, u64);
733 | parse_num_func!(parse_i8, i8);
734 | parse_num_func!(parse_i16, i16);
735 | parse_num_func!(parse_i32, i32);
736 | parse_num_func!(parse_i64, i64);
737 |
738 | fn parse_fbsize(&self, line: &str) -> Result<(usize, usize), LoadError> {
739 | let (width, tail) = self.parse_u32(line)?;
740 | let (height, tail) = self.parse_u32(tail)?;
741 |
742 | if tail.is_empty() {
743 | Ok((width as usize, height as usize))
744 | } else {
745 | Err(error_at_line!(self, "Invalid fbsize"))
746 | }
747 | }
748 |
749 | fn parse_version(&self, line: &str) -> Result<(u32, u32, u32), LoadError> {
750 | if let Some((version_str, tail)) = next_word(line) {
751 | let mut parts = [0u32; 3];
752 |
753 | 'parse_parts: {
754 | for (i, part) in version_str.split('.').enumerate() {
755 | if i >= parts.len() {
756 | break 'parse_parts;
757 | }
758 |
759 | parts[i] = match part.parse::<u32>() {
760 | Ok(v) => v,
761 | Err(_) => break 'parse_parts,
762 | };
763 | }
764 |
765 | if tail.trim_start().len() != 0 {
766 | break 'parse_parts;
767 | }
768 |
769 | return Ok((parts[0], parts[1], parts[2]));
770 | }
771 | }
772 |
773 | Err(error_at_line!(self, "Invalid Vulkan version"))
774 | }
775 |
776 | fn parse_vk_component_type(&self, line: &str) -> Result<vk::VkComponentTypeKHR, LoadError> {
777 | let (type_name, _) = match next_word(line) {
778 | None => return Err(error_at_line!(self, "Expected component type name")),
779 | Some(v) => v,
780 | };
781 |
782 | match vk_component_type_from_name(type_name) {
783 | None => Err(error_at_line!(self, "Invalid component type name '{}'", type_name)),
784 | Some(t) => Ok(t),
785 | }
786 | }
787 |
788 | fn parse_scope(&self, line: &str) -> Result<vk::VkScopeKHR, LoadError> {
789 | let (scope_name, _) = match next_word(line) {
790 | None => return Err(error_at_line!(self, "Expected component type name")),
791 | Some(v) => v,
792 | };
793 |
794 | let scope = match scope_name {
795 | "device" => vk::VK_SCOPE_DEVICE_KHR,
796 | "workgroup" => vk::VK_SCOPE_WORKGROUP_KHR,
797 | "subgroup" => vk::VK_SCOPE_SUBGROUP_KHR,
798 | "queue_family" => vk::VK_SCOPE_QUEUE_FAMILY_KHR,
799 | _ => return Err(error_at_line!(self, "Invalid scope '{}'", scope_name)),
800 | };
801 |
802 | Ok(scope)
803 | }
804 |
805 | fn parse_cooperative_matrix_req(&self, line: &str) -> Result<CooperativeMatrix, LoadError> {
806 | let mut req = CooperativeMatrix::default();
807 | req.line = line.to_string();
808 | for p in line.split_whitespace() {
809 | if let Some(v) = p.strip_prefix("m=") {
810 | let (v, _) = self.parse_u32(v)?;
811 | req.m_size = Some(v);
812 | } else if let Some(v) = p.strip_prefix("n=") {
813 | let (v, _) = self.parse_u32(v)?;
814 | req.n_size = Some(v);
815 | } else if let Some(v) = p.strip_prefix("k=") {
816 | let (v, _) = self.parse_u32(v)?;
817 | req.k_size = Some(v);
818 | } else if let Some(v) = p.strip_prefix("a=") {
819 | req.a_type = Some(self.parse_vk_component_type(v)?);
820 | } else if let Some(v) = p.strip_prefix("b=") {
821 | req.b_type = Some(self.parse_vk_component_type(v)?);
822 | } else if let Some(v) = p.strip_prefix("c=") {
823 | req.c_type = Some(self.parse_vk_component_type(v)?);
824 | } else if let Some(v) = p.strip_prefix("result=") {
825 | req.result_type = Some(self.parse_vk_component_type(v)?);
826 | } else if let Some(v) = p.strip_prefix("saturating_accumulation=") {
827 | req.saturating_accumulation =
828 | match v.trim() {
829 | "true" => Some(vk::VK_TRUE),
830 | "false" => Some(vk::VK_FALSE),
831 | _ => return Err(error_at_line!(self, "When present, saturating_accumulation needs to be either 'false' or 'true'; found invalid value '{}'", v.trim())),
832 | }
833 | } else if let Some(v) = p.strip_prefix("scope=") {
834 | req.result_type = Some(self.parse_scope(v)?);
835 | } else {
836 | return Err(error_at_line!(self, "Invalid cooperative matrix requirement '{}'", p));
837 | }
838 | }
839 |
840 | Ok(req)
841 | }
842 |
843 | fn parse_desc_set_and_binding<'b>(
844 | &self,
845 | line: &'b str
846 | ) -> Result<(u32, u32, &'b str), LoadError> {
847 | let (part_a, tail) = self.parse_u32(line)?;
848 |
849 | let (desc_set, binding, tail) =
850 | if let Some(tail) = tail.strip_prefix(':') {
851 | let (part_b, tail) = self.parse_u32(tail)?;
852 | (part_a, part_b, tail)
853 | } else {
854 | (0, part_a, tail)
855 | };
856 |
857 | if let Some(c) = tail.chars().next() {
858 | if !c.is_whitespace() {
859 | return Err(error_at_line!(self, "Invalid buffer binding"));
860 | }
861 | }
862 |
863 | Ok((desc_set, binding, tail))
864 | }
865 |
866 | fn parse_glsl_type<'b>(
867 | &self,
868 | line: &'b str
869 | ) -> Result<(slot::Type, &'b str), LoadError> {
870 | let (type_name, line) = match next_word(line) {
871 | None => return Err(error_at_line!(self, "Expected GLSL type name")),
872 | Some(v) => v,
873 | };
874 |
875 | match slot::Type::from_glsl_type(type_name) {
876 | None => Err(error_at_line!(
877 | self,
878 | "Invalid GLSL type name: {}",
879 | type_name
880 | )),
881 | Some(t) => Ok((t, line)),
882 | }
883 | }
884 |
885 | fn parse_slot_base_type<'b>(
886 | &self,
887 | line: &'b str,
888 | base_type: slot::BaseType,
889 | buf: &mut [u8],
890 | ) -> Result<&'b str, LoadError> {
891 | match base_type {
892 | slot::BaseType::Int => {
893 | let (value, tail) = self.parse_i32(line)?;
894 | buf.copy_from_slice(&value.to_ne_bytes());
895 | Ok(tail)
896 | },
897 | slot::BaseType::UInt => {
898 | let (value, tail) = self.parse_u32(line)?;
899 | buf.copy_from_slice(&value.to_ne_bytes());
900 | Ok(tail)
901 | },
902 | slot::BaseType::Int8 => {
903 | let (value, tail) = self.parse_i8(line)?;
904 | buf.copy_from_slice(&value.to_ne_bytes());
905 | Ok(tail)
906 | },
907 | slot::BaseType::UInt8 => {
908 | let (value, tail) = self.parse_u8(line)?;
909 | buf.copy_from_slice(&value.to_ne_bytes());
910 | Ok(tail)
911 | },
912 | slot::BaseType::Int16 => {
913 | let (value, tail) = self.parse_i16(line)?;
914 | buf.copy_from_slice(&value.to_ne_bytes());
915 | Ok(tail)
916 | },
917 | slot::BaseType::UInt16 => {
918 | let (value, tail) = self.parse_u16(line)?;
919 | buf.copy_from_slice(&value.to_ne_bytes());
920 | Ok(tail)
921 | },
922 | slot::BaseType::Int64 => {
923 | let (value, tail) = self.parse_i64(line)?;
924 | buf.copy_from_slice(&value.to_ne_bytes());
925 | Ok(tail)
926 | },
927 | slot::BaseType::UInt64 => {
928 | let (value, tail) = self.parse_u64(line)?;
929 | buf.copy_from_slice(&value.to_ne_bytes());
930 | Ok(tail)
931 | },
932 | slot::BaseType::Float16 => {
933 | let (value, tail) = self.parse_half_float(line)?;
934 | buf.copy_from_slice(&value.to_ne_bytes());
935 | Ok(tail)
936 | },
937 | slot::BaseType::Float => {
938 | let (value, tail) = self.parse_f32(line)?;
939 | buf.copy_from_slice(&value.to_ne_bytes());
940 | Ok(tail)
941 | },
942 | slot::BaseType::Double => {
943 | let (value, tail) = self.parse_f64(line)?;
944 | buf.copy_from_slice(&value.to_ne_bytes());
945 | Ok(tail)
946 | },
947 | }
948 | }
949 |
950 | fn parse_slot_values(
951 | &self,
952 | mut line: &str,
953 | slot_type: slot::Type,
954 | layout: slot::Layout,
955 | stride: usize,
956 | ) -> Result<Box<[u8]>, LoadError> {
957 | let mut buffer = Vec::<u8>::new();
958 | let mut n_values = 0;
959 | let type_size = slot_type.size(layout);
960 | let base_type = slot_type.base_type();
961 |
962 | loop {
963 | buffer.resize(n_values * stride + type_size, 0);
964 |
965 | let base_offset = buffer.len() - type_size;
966 |
967 | for offset in slot_type.offsets(layout) {
968 | line = self.parse_slot_base_type(
969 | line,
970 | base_type,
971 | &mut buffer[base_offset + offset
972 | ..base_offset + offset + base_type.size()],
973 | )?;
974 | }
975 |
976 | if line.trim_start().is_empty() {
977 | break Ok(buffer.into_boxed_slice())
978 | }
979 |
980 | n_values += 1;
981 | }
982 | }
983 |
984 | fn parse_buffer_subdata(
985 | &self,
986 | line: &str,
987 | slot_type: slot::Type,
988 | layout: slot::Layout,
989 | ) -> Result<Box<[u8]>, LoadError> {
990 | self.parse_slot_values(
991 | line,
992 | slot_type,
993 | layout,
994 | slot_type.array_stride(layout),
995 | )
996 | }
997 |
998 | fn is_valid_extension_or_feature_name(line: &str) -> bool {
999 | for ch in line.chars() {
1000 | if !ch.is_ascii_alphanumeric() && ch != '_' {
1001 | return false;
1002 | }
1003 | }
1004 |
1005 | true
1006 | }
1007 |
1008 | fn process_require_line(&mut self, line: &str) -> Result<(), LoadError> {
1009 | let line = match trim_line_or_skip(line) {
1010 | Some(l) => l,
1011 | None => return Ok(()),
1012 | };
1013 |
1014 | if let Some(tail) = strip_word_prefix(line, "framebuffer") {
1015 | self.window_format.color_format = self.parse_format(tail)?;
1016 | return Ok(());
1017 | }
1018 |
1019 | if let Some(tail) = strip_word_prefix(line, "depthstencil") {
1020 | self.window_format.depth_stencil_format =
1021 | Some(self.parse_format(tail)?);
1022 | return Ok(());
1023 | }
1024 |
1025 | if let Some(tail) = strip_word_prefix(line, "fbsize") {
1026 | let (width, height) = self.parse_fbsize(tail)?;
1027 | self.window_format.width = width;
1028 | self.window_format.height = height;
1029 | return Ok(());
1030 | }
1031 |
1032 | if let Some(tail) = strip_word_prefix(line, "vulkan") {
1033 | let (major, minor, patch) = self.parse_version(tail)?;
1034 | self.requirements.add_version(major, minor, patch);
1035 | return Ok(());
1036 | }
1037 |
1038 | if let Some(tail) = strip_word_prefix(line, "subgroup_size") {
1039 | self.requirements.add("subgroupSizeControl");
1040 | let (size, _) = self.parse_u32(tail)?;
1041 | if !size.is_power_of_two() {
1042 | return Err(error_at_line!(self, "Required subgroup size must be a power of two"));
1043 | }
1044 | self.requirements.add_version(1, 1, 0);
1045 | self.requirements.required_subgroup_size = Some(size);
1046 | return Ok(());
1047 | }
1048 |
1049 | if let Some(tail) = strip_word_prefix(line, "cooperative_matrix") {
1050 | self.requirements.add("VK_KHR_cooperative_matrix");
1051 | self.requirements.add("cooperativeMatrix");
1052 | let req = self.parse_cooperative_matrix_req(tail)?;
1053 | self.requirements.add_cooperative_matrix_req(req);
1054 | return Ok(());
1055 | }
1056 |
1057 | if Loader::is_valid_extension_or_feature_name(line) {
1058 | self.requirements.add(line);
1059 | return Ok(());
1060 | }
1061 |
1062 | Err(error_at_line!(self, "Invalid require line"))
1063 | }
1064 |
1065 | fn decode_binary(
1066 | &self,
1067 | data: &mut Vec<u32>,
1068 | line: &str
1069 | ) -> Result<(), LoadError> {
1070 | let line = match trim_line_or_skip(line) {
1071 | Some(l) => l,
1072 | None => return Ok(()),
1073 | };
1074 |
1075 | for part in line.split_whitespace() {
1076 | let value = match u32::from_str_radix(part, 16) {
1077 | Ok(v) => v,
1078 | Err(_) => return Err(error_at_line!(
1079 | self,
1080 | "Invalid hex value: {}",
1081 | part
1082 | )),
1083 | };
1084 |
1085 | data.push(value);
1086 | }
1087 |
1088 | Ok(())
1089 | }
1090 |
1091 | fn process_shader_line(&self, line: &str) -> Result<(), LoadError> {
1092 | match self.current_source.borrow_mut().as_mut().unwrap() {
1093 | Shader::Glsl(s) => s.push_str(line),
1094 | Shader::Spirv(s) => s.push_str(line),
1095 | Shader::Binary(data) => self.decode_binary(data, line)?,
1096 | }
1097 |
1098 | Ok(())
1099 | }
1100 |
1101 | fn process_vertex_data_line(
1102 | &mut self,
1103 | line: &str
1104 | ) -> Result<(), LoadError> {
1105 | match self.vbo_parser.as_mut().unwrap().parse_line(line) {
1106 | Ok(()) => Ok(()),
1107 | Err(e) => Err(LoadError::Vbo {
1108 | line_num: self.stream.line_num(),
1109 | detail: e,
1110 | }),
1111 | }
1112 | }
1113 |
1114 | fn process_indices_line(
1115 | &mut self,
1116 | line: &str,
1117 | ) -> Result<(), LoadError> {
1118 | let mut line = match trim_line_or_skip(line) {
1119 | Some(l) => l,
1120 | None => return Ok(()),
1121 | };
1122 |
1123 | loop {
1124 | line = line.trim_start();
1125 |
1126 | if line.is_empty() {
1127 | break Ok(());
1128 | }
1129 |
1130 | let (value, tail) = self.parse_u16(line)?;
1131 |
1132 | self.indices.push(value);
1133 | line = tail;
1134 | }
1135 | }
1136 |
1137 | fn get_buffer(
1138 | &mut self,
1139 | desc_set: u32,
1140 | binding: u32,
1141 | buffer_type: BufferType,
1142 | ) -> Result<&mut Buffer, LoadError> {
1143 | let position = if let Some(pos) = self.buffers.iter().position(
1144 | |b| b.desc_set == desc_set && b.binding == binding
1145 | ) {
1146 | if self.buffers[pos].buffer_type != buffer_type {
1147 | return Err(error_at_line!(
1148 | self,
1149 | "Buffer binding point {}:{} used with different type",
1150 | desc_set,
1151 | binding
1152 | ));
1153 | }
1154 |
1155 | pos
1156 | } else {
1157 | self.buffers.push(Buffer {
1158 | desc_set,
1159 | binding,
1160 | buffer_type,
1161 | size: 0
1162 | });
1163 |
1164 | self.buffers.len() - 1
1165 | };
1166 |
1167 | Ok(&mut self.buffers[position])
1168 | }
1169 |
1170 | fn layout_for_buffer_type(&self, buffer_type: BufferType) -> slot::Layout {
1171 | match buffer_type {
1172 | BufferType::Ubo => self.ubo_layout,
1173 | BufferType::Ssbo => self.ssbo_layout,
1174 | }
1175 | }
1176 |
1177 | // Adds the given key to the list of pipeline keys if it’s not
1178 | // already there and returns its index, otherwise just drops the
1179 | // new key returns the index of the existing key.
1180 | fn add_pipeline_key(&mut self, key: pipeline_key::Key) -> usize {
1181 | for (pos, existing_key) in self.pipeline_keys.iter().enumerate() {
1182 | if existing_key.eq(&key) {
1183 | return pos;
1184 | }
1185 | }
1186 |
1187 | self.pipeline_keys.push(key);
1188 | self.pipeline_keys.len() - 1
1189 | }
1190 |
1191 | fn process_set_buffer_subdata(
1192 | &mut self,
1193 | desc_set: u32,
1194 | binding: u32,
1195 | buffer_type: BufferType,
1196 | line: &str,
1197 | ) -> Result<(), LoadError> {
1198 | let (value_type, line) = self.parse_glsl_type(line)?;
1199 | let (offset, line) = self.parse_u32(line)?;
1200 | let data = self.parse_buffer_subdata(
1201 | line,
1202 | value_type,
1203 | self.layout_for_buffer_type(buffer_type),
1204 | )?;
1205 |
1206 | let buffer = self.get_buffer(desc_set, binding, buffer_type)?;
1207 |
1208 | let min_buffer_size = offset as usize + data.len();
1209 |
1210 | if buffer.size < min_buffer_size {
1211 | buffer.size = min_buffer_size;
1212 | }
1213 |
1214 | self.commands.push(Command {
1215 | line_num: self.stream.line_num(),
1216 | op: Operation::SetBufferData {
1217 | desc_set,
1218 | binding,
1219 | offset: offset as usize,
1220 | data,
1221 | },
1222 | });
1223 |
1224 | Ok(())
1225 | }
1226 |
1227 | fn process_set_buffer_size(
1228 | &mut self,
1229 | desc_set: u32,
1230 | binding: u32,
1231 | buffer_type: BufferType,
1232 | size: usize,
1233 | ) -> Result<(), LoadError> {
1234 | let buffer = self.get_buffer(desc_set, binding, buffer_type)?;
1235 |
1236 | if size > buffer.size {
1237 | buffer.size = size;
1238 | }
1239 |
1240 | Ok(())
1241 | }
1242 |
1243 | fn parse_probe_parts<'b>(
1244 | &self,
1245 | line: &'b str,
1246 | n_parts: usize,
1247 | relative: bool,
1248 | ) -> Result<([u32; 4], &'b str), LoadError> {
1249 | let mut line = match line.trim_start().strip_prefix('(') {
1250 | None => return Err(error_at_line!(self, "Expected ‘(’")),
1251 | Some(tail) => tail,
1252 | };
1253 |
1254 | let mut result = [0; 4];
1255 |
1256 | for i in 0..n_parts {
1257 | if relative {
1258 | let (value, tail) = self.parse_f32(line)?;
1259 | let multiplier = if i & 1 == 0 {
1260 | self.window_format.width
1261 | } else {
1262 | self.window_format.height
1263 | };
1264 | result[i] = (value * multiplier as f32) as u32;
1265 | line = tail;
1266 | } else {
1267 | let (value, tail) = self.parse_u32(line)?;
1268 | result[i] = value;
1269 | line = tail;
1270 | }
1271 |
1272 | if i < n_parts - 1 {
1273 | line = match line.trim_start().strip_prefix(',') {
1274 | None => return Err(error_at_line!(self, "Expected ‘,’")),
1275 | Some(tail) => tail,
1276 | };
1277 | }
1278 | }
1279 |
1280 | match line.trim_start().strip_prefix(')') {
1281 | None => Err(error_at_line!(self, "Expected ‘)’")),
1282 | Some(tail) => Ok((result, tail)),
1283 | }
1284 | }
1285 |
1286 | fn parse_color<'b>(
1287 | &self,
1288 | line: &'b str,
1289 | n_parts: usize,
1290 | ) -> Result<([f64; 4], &'b str), LoadError> {
1291 | let mut line = match line.trim_start().strip_prefix('(') {
1292 | None => return Err(error_at_line!(self, "Expected ‘(’")),
1293 | Some(tail) => tail,
1294 | };
1295 |
1296 | let mut result = [0.0; 4];
1297 |
1298 | for i in 0..n_parts {
1299 | let (value, tail) = self.parse_f64(line)?;
1300 | result[i] = value;
1301 | line = tail;
1302 |
1303 | if i < n_parts - 1 {
1304 | line = match line.trim_start().strip_prefix(',') {
1305 | None => return Err(error_at_line!(self, "Expected ‘,’")),
1306 | Some(tail) => tail,
1307 | };
1308 | }
1309 | }
1310 |
1311 | match line.trim_start().strip_prefix(')') {
1312 | None => Err(error_at_line!(self, "Expected ‘)’")),
1313 | Some(tail) => Ok((result, tail)),
1314 | }
1315 | }
1316 |
1317 | fn process_probe(
1318 | &mut self,
1319 | line: &str,
1320 | ) -> Result<MatchResult, LoadError> {
1321 | let (relative, line) = match strip_word_prefix(line, "relative") {
1322 | None => (false, line),
1323 | Some(tail) => (true, tail),
1324 | };
1325 |
1326 | let line = match strip_word_prefix(line, "probe") {
1327 | Some(l) => l,
1328 | None => return Ok(MatchResult::NotMatched),
1329 | };
1330 |
1331 | enum RegionType {
1332 | Point,
1333 | Rect,
1334 | All,
1335 | }
1336 |
1337 | let (region_type, line) =
1338 | if let Some(tail) = strip_word_prefix(line, "rect") {
1339 | (RegionType::Rect, tail)
1340 | } else if let Some(tail) = strip_word_prefix(line, "all") {
1341 | (RegionType::All, tail)
1342 | } else {
1343 | (RegionType::Point, line)
1344 | };
1345 |
1346 | let (n_components, line) =
1347 | if let Some(tail) = strip_word_prefix(line, "rgb") {
1348 | (3, tail)
1349 | } else if let Some(tail) = strip_word_prefix(line, "rgba") {
1350 | (4, tail)
1351 | } else {
1352 | return Err(error_at_line!(
1353 | self,
1354 | "Expected rgb or rgba in probe command"
1355 | ));
1356 | };
1357 |
1358 | let (x, y, w, h, color, line) = match region_type {
1359 | RegionType::All => {
1360 | if relative {
1361 | return Err(error_at_line!(
1362 | self,
1363 | "‘all’ can’t be used with a relative probe"
1364 | ));
1365 | }
1366 |
1367 | // For some reason the “probe all” command has a
1368 | // different syntax for the color
1369 |
1370 | let mut color = [0.0; 4];
1371 | let mut line = line;
1372 |
1373 | for i in 0..n_components {
1374 | let (part, tail) = self.parse_f64(line)?;
1375 | color[i] = part;
1376 | line = tail;
1377 | }
1378 |
1379 | (
1380 | 0,
1381 | 0,
1382 | self.window_format.width as u32,
1383 | self.window_format.height as u32,
1384 | color,
1385 | line
1386 | )
1387 | },
1388 | RegionType::Point => {
1389 | let (parts, tail) = self.parse_probe_parts(line, 2, relative)?;
1390 | let (color, tail) = self.parse_color(tail, n_components)?;
1391 | (parts[0], parts[1], 1, 1, color, tail)
1392 | },
1393 | RegionType::Rect => {
1394 | let (parts, tail) = self.parse_probe_parts(line, 4, relative)?;
1395 | let (color, tail) = self.parse_color(tail, n_components)?;
1396 | (parts[0], parts[1], parts[2], parts[3], color, tail)
1397 | },
1398 | };
1399 |
1400 | if !line.is_empty() {
1401 | return Err(error_at_line!(
1402 | self,
1403 | "Extra data after probe command"
1404 | ));
1405 | }
1406 |
1407 | self.commands.push(Command {
1408 | line_num: self.stream.line_num(),
1409 | op: Operation::ProbeRect {
1410 | n_components: n_components as u32,
1411 | x,
1412 | y,
1413 | w,
1414 | h,
1415 | color,
1416 | tolerance: self.tolerance.clone(),
1417 | },
1418 | });
1419 |
1420 | Ok(MatchResult::Matched)
1421 | }
1422 |
1423 | fn process_push(
1424 | &mut self,
1425 | line: &str,
1426 | ) -> Result<MatchResult, LoadError> {
1427 | let line = match strip_word_prefix(line, "push") {
1428 | Some(l) => l,
1429 | None => {
1430 | // Allow “uniform” as an alias for “push” for
1431 | // compatibility with shader-runner scripts.
1432 | match strip_word_prefix(line, "uniform") {
1433 | Some(l) => l,
1434 | None => return Ok(MatchResult::NotMatched),
1435 | }
1436 | },
1437 | };
1438 |
1439 | let (value_type, line) = self.parse_glsl_type(line)?;
1440 | let (offset, line) = self.parse_u32(line)?;
1441 | let data = self.parse_buffer_subdata(
1442 | line,
1443 | value_type,
1444 | self.push_layout,
1445 | )?;
1446 |
1447 | self.commands.push(Command {
1448 | line_num: self.stream.line_num(),
1449 | op: Operation::SetPushCommand {
1450 | offset: offset as usize,
1451 | data,
1452 | },
1453 | });
1454 |
1455 | Ok(MatchResult::Matched)
1456 | }
1457 |
1458 | fn process_uniform_ubo(
1459 | &mut self,
1460 | line: &str,
1461 | ) -> Result<MatchResult, LoadError> {
1462 | let line = match strip_words_prefix(line, "uniform ubo") {
1463 | Some(l) => l,
1464 | None => return Ok(MatchResult::NotMatched),
1465 | };
1466 |
1467 | let (desc_set, binding, line) = self.parse_desc_set_and_binding(line)?;
1468 |
1469 | self.process_set_buffer_subdata(
1470 | desc_set,
1471 | binding,
1472 | BufferType::Ubo,
1473 | line
1474 | )?;
1475 |
1476 | Ok(MatchResult::Matched)
1477 | }
1478 |
1479 | fn process_draw_rect(
1480 | &mut self,
1481 | line: &str,
1482 | ) -> Result<MatchResult, LoadError> {
1483 | let mut line = match strip_words_prefix(line, "draw rect") {
1484 | Some(l) => l,
1485 | None => return Ok(MatchResult::NotMatched),
1486 | };
1487 |
1488 | let mut ortho = false;
1489 | let mut patch = false;
1490 |
1491 | loop {
1492 | if let Some(tail) = strip_word_prefix(line, "ortho") {
1493 | ortho = true;
1494 | line = tail;
1495 | } else if let Some(tail) = strip_word_prefix(line, "patch") {
1496 | patch = true;
1497 | line = tail;
1498 | } else {
1499 | break;
1500 | }
1501 | };
1502 |
1503 | let (mut x, line) = self.parse_f32(line)?;
1504 | let (mut y, line) = self.parse_f32(line)?;
1505 | let (mut w, line) = self.parse_f32(line)?;
1506 | let (mut h, line) = self.parse_f32(line)?;
1507 |
1508 | if !line.trim_end().is_empty() {
1509 | return Err(error_at_line!(self, "Extra data at end of line"));
1510 | }
1511 |
1512 | if ortho {
1513 | let width = self.window_format.width as f32;
1514 | let height = self.window_format.width as f32;
1515 | x = (x * 2.0 / width) - 1.0;
1516 | y = (y * 2.0 / height) - 1.0;
1517 | w *= 2.0 / width;
1518 | h *= 2.0 / height;
1519 | }
1520 |
1521 | let mut key = self.current_key.clone();
1522 | key.set_pipeline_type(pipeline_key::Type::Graphics);
1523 | key.set_source(pipeline_key::Source::Rectangle);
1524 | key.set_topology(if patch {
1525 | vk::VK_PRIMITIVE_TOPOLOGY_PATCH_LIST
1526 | } else {
1527 | vk::VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP
1528 | });
1529 | key.set_patch_control_points(4);
1530 | let pipeline_key = self.add_pipeline_key(key);
1531 |
1532 | self.commands.push(Command {
1533 | line_num: self.stream.line_num(),
1534 | op: Operation::DrawRect {
1535 | x,
1536 | y,
1537 | w,
1538 | h,
1539 | pipeline_key,
1540 | }
1541 | });
1542 |
1543 | Ok(MatchResult::Matched)
1544 | }
1545 |
1546 | fn lookup_topology(
1547 | &self,
1548 | name: &str,
1549 | ) -> Result<vk::VkPrimitiveTopology, LoadError> {
1550 | match TOPOLOGY_NAMES.binary_search_by(
1551 | |&(probe, _)| probe.cmp(name)
1552 | ) {
1553 | Ok(pos) => Ok(TOPOLOGY_NAMES[pos].1),
1554 | Err(_pos) => Err(error_at_line!(
1555 | self,
1556 | "Unknown topology: {}",
1557 | name
1558 | )),
1559 | }
1560 | }
1561 |
1562 | fn process_draw_arrays(
1563 | &mut self,
1564 | line: &str,
1565 | ) -> Result<MatchResult, LoadError> {
1566 | let mut line = match strip_words_prefix(line, "draw arrays") {
1567 | Some(l) => l,
1568 | None => return Ok(MatchResult::NotMatched),
1569 | };
1570 |
1571 | let mut instanced = false;
1572 | let mut indexed = false;
1573 |
1574 | loop {
1575 | if let Some(tail) = strip_word_prefix(line, "instanced") {
1576 | instanced = true;
1577 | line = tail;
1578 | } else if let Some(tail) = strip_word_prefix(line, "indexed") {
1579 | indexed = true;
1580 | line = tail;
1581 | } else {
1582 | break;
1583 | }
1584 | }
1585 |
1586 | let (topology_name, line) = match next_word(line) {
1587 | Some(v) => v,
1588 | None => return Err(error_at_line!(self, "Expected topology name")),
1589 | };
1590 |
1591 | let topology = self.lookup_topology(topology_name)?;
1592 |
1593 | let (first_vertex, line) = self.parse_u32(line)?;
1594 | let (vertex_count, line) = self.parse_u32(line)?;
1595 | let (instance_count, line) = if instanced {
1596 | self.parse_u32(line)?
1597 | } else {
1598 | (1, line)
1599 | };
1600 |
1601 | if !line.is_empty() {
1602 | return Err(error_at_line!(self, "Extra data at end of line"));
1603 | }
1604 |
1605 | let mut key = self.current_key.clone();
1606 | key.set_pipeline_type(pipeline_key::Type::Graphics);
1607 | key.set_source(pipeline_key::Source::VertexData);
1608 | key.set_topology(topology);
1609 | let pipeline_key = self.add_pipeline_key(key);
1610 |
1611 | self.commands.push(Command {
1612 | line_num: self.stream.line_num(),
1613 | op: Operation::DrawArrays {
1614 | topology,
1615 | indexed,
1616 | first_vertex,
1617 | vertex_count,
1618 | first_instance: 0,
1619 | instance_count,
1620 | pipeline_key,
1621 | }
1622 | });
1623 |
1624 | Ok(MatchResult::Matched)
1625 | }
1626 |
1627 | fn process_compute(
1628 | &mut self,
1629 | line: &str,
1630 | ) -> Result<MatchResult, LoadError> {
1631 | let line = match strip_word_prefix(line, "compute") {
1632 | Some(l) => l,
1633 | None => return Ok(MatchResult::NotMatched),
1634 | };
1635 |
1636 | let (x, line) = self.parse_u32(line)?;
1637 | let (y, line) = self.parse_u32(line)?;
1638 | let (z, line) = self.parse_u32(line)?;
1639 |
1640 | if !line.is_empty() {
1641 | return Err(error_at_line!(self, "Extra data at end of line"));
1642 | }
1643 |
1644 | let mut key = self.current_key.clone();
1645 | key.set_pipeline_type(pipeline_key::Type::Compute);
1646 | let pipeline_key = self.add_pipeline_key(key);
1647 |
1648 | self.commands.push(Command {
1649 | line_num: self.stream.line_num(),
1650 | op: Operation::DispatchCompute {
1651 | x,
1652 | y,
1653 | z,
1654 | pipeline_key,
1655 | },
1656 | });
1657 |
1658 | Ok(MatchResult::Matched)
1659 | }
1660 |
1661 | fn process_buffer_command(
1662 | &mut self,
1663 | line: &str,
1664 | ) -> Result<MatchResult, LoadError> {
1665 | let (line, buffer_type) =
1666 | if let Some(tail) = strip_word_prefix(line, "ssbo") {
1667 | (tail, BufferType::Ssbo)
1668 | } else if let Some(tail) = strip_word_prefix(line, "ubo") {
1669 | (tail, BufferType::Ubo)
1670 | } else {
1671 | return Ok(MatchResult::NotMatched);
1672 | };
1673 |
1674 | let (desc_set, binding, line) = self.parse_desc_set_and_binding(line)?;
1675 |
1676 | let line = line.trim_start();
1677 |
1678 | if let Some(line) = strip_word_prefix(line, "subdata") {
1679 | self.process_set_buffer_subdata(
1680 | desc_set,
1681 | binding,
1682 | buffer_type,
1683 | line
1684 | )?;
1685 | Ok(MatchResult::Matched)
1686 | } else {
1687 | let (size, tail) = self.parse_u32(line)?;
1688 |
1689 | if tail.is_empty() {
1690 | self.process_set_buffer_size(
1691 | desc_set,
1692 | binding,
1693 | buffer_type,
1694 | size as usize
1695 | )?;
1696 | Ok(MatchResult::Matched)
1697 | } else {
1698 | Err(error_at_line!(self, "Invalid buffer command"))
1699 | }
1700 | }
1701 | }
1702 |
1703 | fn process_probe_ssbo(
1704 | &mut self,
1705 | line: &str,
1706 | ) -> Result<MatchResult, LoadError> {
1707 | let line = match strip_words_prefix(line, "probe ssbo") {
1708 | Some(l) => l,
1709 | None => return Ok(MatchResult::NotMatched),
1710 | };
1711 |
1712 | let (slot_type, line) = self.parse_glsl_type(line)?;
1713 | let (desc_set, binding, line) = self.parse_desc_set_and_binding(line)?;
1714 | let (offset, line) = self.parse_u32(line)?;
1715 |
1716 | let (operator, line) = match next_word(line) {
1717 | None => return Err(error_at_line!(
1718 | self,
1719 | "Expected comparison operator"
1720 | )),
1721 | Some(v) => v,
1722 | };
1723 |
1724 | let comparison = match slot::Comparison::from_operator(operator) {
1725 | None => return Err(error_at_line!(
1726 | self,
1727 | "Unknown comparison operator: {}",
1728 | operator
1729 | )),
1730 | Some(c) => c,
1731 | };
1732 |
1733 | let type_size = slot_type.size(self.ssbo_layout);
1734 |
1735 | let values = self.parse_slot_values(
1736 | line,
1737 | slot_type,
1738 | self.ssbo_layout,
1739 | type_size, // stride (tightly packed)
1740 | )?;
1741 |
1742 | self.commands.push(Command {
1743 | line_num: self.stream.line_num(),
1744 | op: Operation::ProbeSsbo {
1745 | desc_set,
1746 | binding,
1747 | comparison,
1748 | offset: offset as usize,
1749 | slot_type,
1750 | layout: self.ssbo_layout,
1751 | values,
1752 | tolerance: self.tolerance.clone(),
1753 | }
1754 | });
1755 |
1756 | Ok(MatchResult::Matched)
1757 | }
1758 |
1759 | fn process_clear(
1760 | &mut self,
1761 | line: &str,
1762 | ) -> Result<MatchResult, LoadError> {
1763 | if line != "clear" {
1764 | return Ok(MatchResult::NotMatched);
1765 | }
1766 |
1767 | self.commands.push(Command {
1768 | line_num: self.stream.line_num(),
1769 | op: Operation::Clear {
1770 | color: self.clear_color,
1771 | depth: self.clear_depth,
1772 | stencil: self.clear_stencil,
1773 | },
1774 | });
1775 |
1776 | Ok(MatchResult::Matched)
1777 | }
1778 |
1779 | fn process_clear_values(
1780 | &mut self,
1781 | line: &str,
1782 | ) -> Result<MatchResult, LoadError> {
1783 | let line = match strip_word_prefix(line, "clear") {
1784 | Some(l) => l,
1785 | None => return Ok(MatchResult::NotMatched),
1786 | };
1787 |
1788 | if let Some(line) = strip_word_prefix(line, "color") {
1789 | let (r, tail) = self.parse_f32(line)?;
1790 | let (g, tail) = self.parse_f32(tail)?;
1791 | let (b, tail) = self.parse_f32(tail)?;
1792 | let (a, tail) = self.parse_f32(tail)?;
1793 |
1794 | return if tail.is_empty() {
1795 | self.clear_color = [r, g, b, a];
1796 | Ok(MatchResult::Matched)
1797 | } else {
1798 | Err(error_at_line!(self, "Invalid clear color command"))
1799 | }
1800 | } else if let Some(line) = strip_word_prefix(line, "depth") {
1801 | let (depth, tail) = self.parse_f32(line)?;
1802 |
1803 | if tail.is_empty() {
1804 | self.clear_depth = depth;
1805 | Ok(MatchResult::Matched)
1806 | } else {
1807 | Err(error_at_line!(self, "Invalid clear depth command"))
1808 | }
1809 | } else if let Some(line) = strip_word_prefix(line, "stencil") {
1810 | let (stencil, tail) = self.parse_u32(line)?;
1811 |
1812 | if tail.is_empty() {
1813 | self.clear_stencil = stencil;
1814 | Ok(MatchResult::Matched)
1815 | } else {
1816 | Err(error_at_line!(self, "Invalid clear stencil command"))
1817 | }
1818 | } else {
1819 | Ok(MatchResult::NotMatched)
1820 | }
1821 | }
1822 |
1823 | fn process_pipeline_property(
1824 | &mut self,
1825 | line: &str,
1826 | ) -> Result<MatchResult, LoadError> {
1827 | let (key, value) = match next_word(line) {
1828 | Some(k) => k,
1829 | None => return Ok(MatchResult::NotMatched),
1830 | };
1831 |
1832 | let value = value.trim_start();
1833 |
1834 | match self.current_key.set(key, value) {
1835 | Ok(()) => Ok(MatchResult::Matched),
1836 | Err(pipeline_key::SetPropertyError::NotFound { .. }) => {
1837 | Ok(MatchResult::NotMatched)
1838 | },
1839 | Err(pipeline_key::SetPropertyError::InvalidValue { .. }) => {
1840 | Err(error_at_line!(self, "Invalid value: {}", value))
1841 | },
1842 | }
1843 | }
1844 |
1845 | fn process_layout(
1846 | &mut self,
1847 | line: &str
1848 | ) -> Result<MatchResult, LoadError> {
1849 | let (line, layout, default) =
1850 | if let Some(tail) = strip_words_prefix(line, "push layout") {
1851 | (tail, &mut self.push_layout, DEFAULT_PUSH_LAYOUT)
1852 | } else if let Some(tail) = strip_words_prefix(line, "ubo layout") {
1853 | (tail, &mut self.ubo_layout, DEFAULT_UBO_LAYOUT)
1854 | } else if let Some(tail) = strip_words_prefix(line, "ssbo layout") {
1855 | (tail, &mut self.ssbo_layout, DEFAULT_SSBO_LAYOUT)
1856 | } else {
1857 | return Ok(MatchResult::NotMatched);
1858 | };
1859 |
1860 | // Reinitialise the layout with the default values in case not
1861 | // all of the properties are specified
1862 | *layout = default;
1863 |
1864 | for token in line.split_whitespace() {
1865 | if token == "std140" {
1866 | layout.std = slot::LayoutStd::Std140;
1867 | } else if token == "std430" {
1868 | layout.std = slot::LayoutStd::Std430;
1869 | } else if token == "row_major" {
1870 | layout.major = slot::MajorAxis::Row;
1871 | } else if token == "column_major" {
1872 | layout.major = slot::MajorAxis::Column;
1873 | } else {
1874 | return Err(error_at_line!(
1875 | self,
1876 | "Unknown layout parameter “{}”",
1877 | token
1878 | ));
1879 | }
1880 | }
1881 |
1882 | Ok(MatchResult::Matched)
1883 | }
1884 |
1885 | fn process_tolerance(
1886 | &mut self,
1887 | line: &str
1888 | ) -> Result<MatchResult, LoadError> {
1889 | let mut line = match strip_word_prefix(line, "tolerance") {
1890 | Some(l) => l,
1891 | None => return Ok(MatchResult::NotMatched),
1892 | };
1893 |
1894 | let mut is_percent = false;
1895 | let mut n_args = 0usize;
1896 | let mut value = [0.0f64; 4];
1897 |
1898 | loop {
1899 | line = line.trim_start();
1900 |
1901 | if line.is_empty() {
1902 | break;
1903 | }
1904 |
1905 | if n_args >= 4 {
1906 | return Err(error_at_line!(
1907 | self,
1908 | "tolerance command has extra arguments"
1909 | ));
1910 | }
1911 |
1912 | let (component, tail) = self.parse_f64(line)?;
1913 | value[n_args] = component;
1914 | line = tail;
1915 |
1916 | let this_is_percent = if let Some(tail) = line.strip_prefix('%') {
1917 | line = tail;
1918 | true
1919 | } else {
1920 | false
1921 | };
1922 |
1923 | if n_args > 0 && this_is_percent != is_percent {
1924 | return Err(error_at_line!(
1925 | self,
1926 | "Either all tolerance values must be a percentage or none"
1927 | ));
1928 | }
1929 |
1930 | is_percent = this_is_percent;
1931 |
1932 | n_args += 1;
1933 | }
1934 |
1935 | if n_args == 1 {
1936 | let first_value = value[0];
1937 | value[1..].fill(first_value);
1938 | } else if n_args != 4 {
1939 | return Err(error_at_line!(
1940 | self,
1941 | "There must be either 1 or 4 tolerance values"
1942 | ));
1943 | }
1944 |
1945 | self.tolerance = Tolerance::new(value, is_percent);
1946 |
1947 | Ok(MatchResult::Matched)
1948 | }
1949 |
1950 | fn process_entrypoint(
1951 | &mut self,
1952 | line: &str,
1953 | ) -> Result<MatchResult, LoadError> {
1954 | let (stage, line) =
1955 | match Loader::is_stage_name(line, "entrypoint") {
1956 | Some(v) => v,
1957 | None => return Ok(MatchResult::NotMatched),
1958 | };
1959 |
1960 | let entrypoint = line.trim_start();
1961 |
1962 | if entrypoint.is_empty() {
1963 | return Err(error_at_line!(self, "Missing entrypoint name"));
1964 | }
1965 |
1966 | self.current_key.set_entrypoint(stage, entrypoint.to_owned());
1967 |
1968 | Ok(MatchResult::Matched)
1969 | }
1970 |
1971 | fn process_patch_parameter_vertices(
1972 | &mut self,
1973 | line: &str,
1974 | ) -> Result<MatchResult, LoadError> {
1975 | let line = match strip_words_prefix(line, "patch parameter vertices") {
1976 | Some(l) => l,
1977 | None => return Ok(MatchResult::NotMatched),
1978 | };
1979 |
1980 | let (pcp, tail) = self.parse_u32(line)?;
1981 |
1982 | if tail.is_empty() {
1983 | self.current_key.set_patch_control_points(pcp);
1984 | Ok(MatchResult::Matched)
1985 | } else {
1986 | Err(error_at_line!(
1987 | self,
1988 | "Invalid patch parameter vertices command"
1989 | ))
1990 | }
1991 | }
1992 |
1993 | fn process_test_line(&mut self, line: &str) -> Result<(), LoadError> {
1994 | let line = match trim_line_or_skip(line) {
1995 | Some(l) => l,
1996 | None => return Ok(()),
1997 | };
1998 |
1999 | // Try each of the possible test line processing functions in
2000 | // turn until one of them matches or returns an error.
2001 | handle_match_result!(self.process_probe_ssbo(line));
2002 | handle_match_result!(self.process_probe(line));
2003 | handle_match_result!(self.process_uniform_ubo(line));
2004 | handle_match_result!(self.process_layout(line));
2005 | handle_match_result!(self.process_push(line));
2006 | handle_match_result!(self.process_draw_rect(line));
2007 | handle_match_result!(self.process_draw_arrays(line));
2008 | handle_match_result!(self.process_entrypoint(line));
2009 | handle_match_result!(self.process_compute(line));
2010 | handle_match_result!(self.process_buffer_command(line));
2011 | handle_match_result!(self.process_clear(line));
2012 | handle_match_result!(self.process_pipeline_property(line));
2013 | handle_match_result!(self.process_clear_values(line));
2014 | handle_match_result!(self.process_tolerance(line));
2015 | handle_match_result!(self.process_patch_parameter_vertices(line));
2016 |
2017 | Err(error_at_line!(self, "Invalid test command"))
2018 | }
2019 |
2020 | fn process_line(&mut self, line: &str) -> Result<(), LoadError> {
2021 | if self.process_section_header(line)? == MatchResult::Matched {
2022 | return Ok(());
2023 | }
2024 |
2025 | match self.current_section {
2026 | Section::None => self.process_none_line(line),
2027 | Section::Comment => Ok(()),
2028 | Section::Require => self.process_require_line(line),
2029 | Section::Shader => self.process_shader_line(line),
2030 | Section::VertexData => self.process_vertex_data_line(line),
2031 | Section::Indices => self.process_indices_line(line),
2032 | Section::Test => self.process_test_line(line),
2033 | }
2034 | }
2035 |
2036 | fn parse(mut self) -> Result<Script, LoadError> {
2037 | let mut line = String::new();
2038 |
2039 | loop {
2040 | line.clear();
2041 |
2042 | if self.stream.read_line(&mut line)? == 0 {
2043 | break;
2044 | }
2045 |
2046 | self.process_line(&line)?;
2047 | }
2048 |
2049 | self.end_section()?;
2050 |
2051 | self.buffers.sort_by(|a, b| {
2052 | a.desc_set
2053 | .cmp(&b.desc_set)
2054 | .then_with(|| a.binding.cmp(&b.binding))
2055 | });
2056 |
2057 | Ok(Script {
2058 | stages: self.stages.map(|stage| stage.into_boxed_slice()),
2059 | commands: self.commands.into_boxed_slice(),
2060 | pipeline_keys: self.pipeline_keys.into_boxed_slice(),
2061 | requirements: self.requirements,
2062 | window_format: self.window_format,
2063 | vertex_data: self.vertex_data,
2064 | indices: self.indices.into_boxed_slice(),
2065 | buffers: self.buffers.into_boxed_slice(),
2066 | })
2067 | }
2068 | }
2069 |
2070 | impl Script {
2071 | fn load_or_error(source: &Source) -> Result<Script, LoadError> {
2072 | Loader::new(source)?.parse()
2073 | }
2074 |
2075 | pub fn load(config: &Config, source: &Source) -> Option<Script> {
2076 | match Script::load_or_error(source) {
2077 | Err(e) => {
2078 | let logger = config.logger();
2079 |
2080 | use std::fmt::Write;
2081 |
2082 | let _ = writeln!(
2083 | logger.borrow_mut(),
2084 | "{}",
2085 | e
2086 | );
2087 |
2088 | None
2089 | },
2090 | Ok(script) => Some(script),
2091 | }
2092 | }
2093 |
2094 | pub fn shaders(&self, stage: Stage) -> &[Shader] {
2095 | &*self.stages[stage as usize]
2096 | }
2097 |
2098 | pub(crate) fn commands(&self) -> &[Command] {
2099 | &*self.commands
2100 | }
2101 |
2102 | pub(crate) fn pipeline_keys(&self) -> &[pipeline_key::Key] {
2103 | &*self.pipeline_keys
2104 | }
2105 |
2106 | pub(crate) fn requirements(&self) -> &Requirements {
2107 | &self.requirements
2108 | }
2109 |
2110 | pub(crate) fn window_format(&self) -> &WindowFormat {
2111 | &self.window_format
2112 | }
2113 |
2114 | pub(crate) fn vertex_data(&self) -> Option<&vbo::Vbo> {
2115 | self.vertex_data.as_ref()
2116 | }
2117 |
2118 | pub(crate) fn indices(&self) -> &[u16] {
2119 | &*self.indices
2120 | }
2121 |
2122 | pub(crate) fn buffers(&self) -> &[Buffer] {
2123 | &*self.buffers
2124 | }
2125 |
2126 | pub fn replace_shaders_stage_binary(
2127 | &mut self,
2128 | stage: Stage,
2129 | source: &[u32]
2130 | ) {
2131 | let new_shaders = vec![Shader::Binary(source.to_vec())];
2132 | self.stages[stage as usize] = new_shaders.into_boxed_slice();
2133 | }
2134 | }
2135 |
2136 | #[cfg(test)]
2137 | mod test {
2138 | use super::*;
2139 | use std::fs;
2140 | use std::io;
2141 | use std::ffi::CStr;
2142 | use crate::requirements::make_version;
2143 |
2144 | #[test]
2145 | fn test_strip_word_prefix() {
2146 | assert_eq!(strip_word_prefix("potato", "potato"), Some(""));
2147 | assert_eq!(strip_word_prefix(" potato", "potato"), Some(""));
2148 | assert_eq!(strip_word_prefix(" potato ", "potato"), Some(" "));
2149 | assert_eq!(strip_word_prefix(" \t potato\t", "potato"), Some("\t"));
2150 | assert_eq!(strip_word_prefix("potato-party", "potato"), None);
2151 | assert_eq!(strip_word_prefix("potato party", "potato"), Some(" party"));
2152 | assert_eq!(strip_word_prefix("potaty", "potato"), None);
2153 | assert_eq!(strip_word_prefix("hotpotato", "potato"), None);
2154 | assert_eq!(strip_word_prefix("potatopie", "potato"), None);
2155 |
2156 | assert_eq!(strip_words_prefix("potato", "potato"), Some(""));
2157 | assert_eq!(strip_words_prefix("potato pie", "potato pie"), Some(""));
2158 | assert_eq!(strip_words_prefix("potato pie", "potato pie"), Some(""));
2159 | assert_eq!(strip_words_prefix("potato pie ", "potato pie"), Some(" "));
2160 | assert_eq!(strip_words_prefix("potato pies", "potato pie"), None);
2161 | assert_eq!(strip_words_prefix(" hot potato ", "hot potato"), Some(" "));
2162 | }
2163 |
2164 | #[test]
2165 | fn test_trim_line_or_skip() {
2166 | assert_eq!(trim_line_or_skip("potato"), Some("potato"));
2167 | assert_eq!(trim_line_or_skip(" potato \r\n"), Some("potato"));
2168 | assert_eq!(trim_line_or_skip(" potato # pie \n\n"), Some("potato"));
2169 | assert_eq!(trim_line_or_skip(" potato# pie # pie"), Some("potato"));
2170 | assert_eq!(trim_line_or_skip(""), None);
2171 | assert_eq!(trim_line_or_skip(" \t \n"), None);
2172 | assert_eq!(trim_line_or_skip("# comment"), None);
2173 | assert_eq!(trim_line_or_skip(" # comment "), None);
2174 | }
2175 |
2176 | fn check_error(source: &str, error: &str) {
2177 | let source = Source::from_string(source.to_string());
2178 | let load_error = Script::load_or_error(
2179 | &source
2180 | ).unwrap_err().to_string();
2181 | assert_eq!(error, load_error);
2182 | }
2183 |
2184 | fn script_from_string(source: String) -> Script {
2185 | let source = Source::from_string(source);
2186 | Script::load_or_error(&source).unwrap()
2187 | }
2188 |
2189 | fn check_test_command(source: &str, op: Operation) -> Script {
2190 | let script = script_from_string(format!("[test]\n{}", source));
2191 | assert_eq!(script.commands().len(), 1);
2192 | assert_eq!(script.commands()[0].line_num, 2);
2193 | assert_eq!(script.commands()[0].op, op);
2194 | script
2195 | }
2196 |
2197 | fn check_test_command_error(source: &str, error: &str) {
2198 | let source_string = format!("[test]\n{}", source);
2199 | let error = format!("line 2: {}", error);
2200 | check_error(&source_string, &error);
2201 | }
2202 |
2203 | #[test]
2204 | fn test_probe() {
2205 | check_test_command(
2206 | " relative probe rect rgb \
2207 | ( 1.0,2.0, 3.0, 4.0 ) \
2208 | (5, 6, 7)",
2209 | Operation::ProbeRect {
2210 | n_components: 3,
2211 | x: WindowFormat::default().width as u32,
2212 | y: WindowFormat::default().height as u32 * 2,
2213 | w: WindowFormat::default().width as u32 * 3,
2214 | h: WindowFormat::default().height as u32 * 4,
2215 | color: [5.0, 6.0, 7.0, 0.0],
2216 | tolerance: Tolerance::default(),
2217 | },
2218 | );
2219 | check_test_command(
2220 | "probe rect rgb (1, 2, 3, 4) (5, 6, 7)",
2221 | Operation::ProbeRect {
2222 | n_components: 3,
2223 | x: 1,
2224 | y: 2,
2225 | w: 3,
2226 | h: 4,
2227 | color: [5.0, 6.0, 7.0, 0.0],
2228 | tolerance: Tolerance::default(),
2229 | },
2230 | );
2231 | check_test_command(
2232 | "relative probe rgb (1.0, 2.0) (3, 4, 5)",
2233 | Operation::ProbeRect {
2234 | n_components: 3,
2235 | x: WindowFormat::default().width as u32,
2236 | y: WindowFormat::default().height as u32 * 2,
2237 | w: 1,
2238 | h: 1,
2239 | color: [3.0, 4.0, 5.0, 0.0],
2240 | tolerance: Tolerance::default(),
2241 | },
2242 | );
2243 | check_test_command(
2244 | "probe rgba (1, 2) (3, 4, 5, 6)",
2245 | Operation::ProbeRect {
2246 | n_components: 4,
2247 | x: 1,
2248 | y: 2,
2249 | w: 1,
2250 | h: 1,
2251 | color: [3.0, 4.0, 5.0, 6.0],
2252 | tolerance: Tolerance::default(),
2253 | },
2254 | );
2255 | check_test_command(
2256 | "probe all rgba \t 8 9 0x3FF0000000000000 -12.0",
2257 | Operation::ProbeRect {
2258 | n_components: 4,
2259 | x: 0,
2260 | y: 0,
2261 | w: WindowFormat::default().width as u32,
2262 | h: WindowFormat::default().height as u32,
2263 | color: [8.0, 9.0, 1.0, -12.0],
2264 | tolerance: Tolerance::default(),
2265 | },
2266 | );
2267 |
2268 | check_test_command_error(
2269 | "probe rgbw (1, 2) (3, 4, 5, 6)",
2270 | "Expected rgb or rgba in probe command",
2271 | );
2272 | check_test_command_error(
2273 | "relative probe all rgb 3 4 5",
2274 | "‘all’ can’t be used with a relative probe",
2275 | );
2276 | check_test_command_error(
2277 | "probe all rgb 3 4 5 BOO!",
2278 | "Extra data after probe command",
2279 | );
2280 | check_test_command_error(
2281 | "probe all rgb",
2282 | "cannot parse float from empty string",
2283 | );
2284 | check_test_command_error(
2285 | "probe rgb (1, 2) NOW 3 4 5",
2286 | "Expected ‘(’",
2287 | );
2288 | check_test_command_error(
2289 | "probe rgb (1, 2) (3 4 5)",
2290 | "Expected ‘,’",
2291 | );
2292 | check_test_command_error(
2293 | "probe rgb (1, 2) (3, 4, 5, 6)",
2294 | "Expected ‘)’",
2295 | );
2296 | check_test_command_error(
2297 | "probe rgb point=(3, 4) (10, 20, 30)",
2298 | "Expected ‘(’",
2299 | );
2300 | check_test_command_error(
2301 | "probe rgb (3 4) (10, 20, 30)",
2302 | "Expected ‘,’",
2303 | );
2304 | check_test_command_error(
2305 | "probe rgb (1, 2, 3) (3, 4, 5, 6)",
2306 | "Expected ‘)’",
2307 | );
2308 | check_test_command_error(
2309 | "probe rect rgb (1, 2, 3, 4, 5) (3, 4, 5)",
2310 | "Expected ‘)’",
2311 | );
2312 | check_test_command_error(
2313 | "relative probe rgb (, 2) (3, 4, 5)",
2314 | "cannot parse float from empty string",
2315 | );
2316 | check_test_command_error(
2317 | "probe rgb (, 2) (3, 4, 5)",
2318 | "cannot parse integer from empty string",
2319 | );
2320 | check_test_command_error(
2321 | "probe rgb (1, 2) (, 4, 5)",
2322 | "cannot parse float from empty string",
2323 | );
2324 | check_test_command_error(
2325 | "probe rect rgb (1, 2, 1, 2) (, 4, 5)",
2326 | "cannot parse float from empty string",
2327 | );
2328 | }
2329 |
2330 | #[test]
2331 | fn test_push() {
2332 | check_test_command(
2333 | "push u8vec4 8 4 5 6 7",
2334 | Operation::SetPushCommand {
2335 | offset: 8,
2336 | data: Box::new([4, 5, 6, 7]),
2337 | },
2338 | );
2339 | check_test_command(
2340 | "uniform u8vec3 8 4 5 6 7 8 9",
2341 | Operation::SetPushCommand {
2342 | offset: 8,
2343 | data: Box::new([4, 5, 6, 0, 7, 8, 9]),
2344 | },
2345 | );
2346 |
2347 | check_test_command_error(
2348 | "push u17vec4 8 4 5 6 7",
2349 | "Invalid GLSL type name: u17vec4",
2350 | );
2351 | check_test_command_error(
2352 | "push uint8_t",
2353 | "cannot parse integer from empty string",
2354 | );
2355 | check_test_command_error(
2356 | "push uint8_t 8",
2357 | "cannot parse integer from empty string",
2358 | );
2359 | }
2360 |
2361 | #[test]
2362 | fn test_uniform_ubo() {
2363 | let script = check_test_command(
2364 | "uniform ubo 1:2 u8vec2 8 1 2 3 4 5 6",
2365 | Operation::SetBufferData {
2366 | desc_set: 1,
2367 | binding: 2,
2368 | offset: 8,
2369 | data: Box::new([
2370 | 1, 2,
2371 | // Padding for std140 layout
2372 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
2373 | 3, 4,
2374 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
2375 | 5, 6
2376 | ]),
2377 | }
2378 | );
2379 |
2380 | assert_eq!(script.buffers().len(), 1);
2381 | assert_eq!(script.buffers()[0].size, 8 + 16 * 2 + 2);
2382 | assert_eq!(script.buffers()[0].buffer_type, BufferType::Ubo);
2383 |
2384 | check_test_command(
2385 | "uniform ubo 9 u8vec2 1000042 1 2",
2386 | Operation::SetBufferData {
2387 | desc_set: 0,
2388 | binding: 9,
2389 | offset: 1000042,
2390 | data: Box::new([1, 2]),
2391 | }
2392 | );
2393 |
2394 | check_test_command_error(
2395 | "uniform ubo 9:2:3 uint8_t 1 1",
2396 | "Invalid buffer binding",
2397 | );
2398 | check_test_command_error(
2399 | "uniform ubo 9: uint8_t 1 1",
2400 | "cannot parse integer from empty string",
2401 | );
2402 | check_test_command_error(
2403 | "uniform ubo :9 uint8_t 1 1",
2404 | "cannot parse integer from empty string",
2405 | );
2406 | check_test_command_error(
2407 | "uniform ubo 1 uint8_t",
2408 | "cannot parse integer from empty string",
2409 | );
2410 | check_test_command_error(
2411 | "uniform ubo 1 uint8_t 8",
2412 | "cannot parse integer from empty string",
2413 | );
2414 | check_test_command_error(
2415 | "uniform ubo 1 uint63_t",
2416 | "Invalid GLSL type name: uint63_t",
2417 | );
2418 | }
2419 |
2420 | #[test]
2421 | fn test_buffer_command() {
2422 | let script = check_test_command(
2423 | "ubo 1 subdata u8vec2 8 1 2 3 4 5 6",
2424 | Operation::SetBufferData {
2425 | desc_set: 0,
2426 | binding: 1,
2427 | offset: 8,
2428 | data: Box::new([
2429 | 1, 2,
2430 | // Padding for std140 layout
2431 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
2432 | 3, 4,
2433 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
2434 | 5, 6
2435 | ]),
2436 | }
2437 | );
2438 |
2439 | assert_eq!(script.buffers().len(), 1);
2440 | assert_eq!(script.buffers()[0].size, 8 + 16 * 2 + 2);
2441 | assert_eq!(script.buffers()[0].buffer_type, BufferType::Ubo);
2442 |
2443 | let script = check_test_command(
2444 | "ssbo 1 subdata u8vec2 8 1 2 3 4 5 6",
2445 | Operation::SetBufferData {
2446 | desc_set: 0,
2447 | binding: 1,
2448 | offset: 8,
2449 | data: Box::new([
2450 | // No padding for std430 layout
2451 | 1, 2, 3, 4, 5, 6
2452 | ]),
2453 | }
2454 | );
2455 |
2456 | assert_eq!(script.buffers().len(), 1);
2457 | assert_eq!(script.buffers()[0].size, 8 + 6);
2458 | assert_eq!(script.buffers()[0].buffer_type, BufferType::Ssbo);
2459 |
2460 | let script = script_from_string(
2461 | "[test]\n\
2462 | ssbo 1 subdata uint8_t 1 2 3 4 5 6\n\
2463 | # Set the size to a size that is smaller than the one set with\n\
2464 | # the subdata\n\
2465 | ssbo 1 4".to_string()
2466 | );
2467 | assert_eq!(script.buffers().len(), 1);
2468 | assert_eq!(script.buffers()[0].size, 6);
2469 |
2470 | let script = script_from_string(
2471 | "[test]\n\
2472 | ssbo 1 subdata uint8_t 1 2 3 4 5 6\n\
2473 | # Set the size to a size that is greater than the one set with\n\
2474 | # the subdata\n\
2475 | ssbo 1 4000".to_string()
2476 | );
2477 | assert_eq!(script.buffers().len(), 1);
2478 | assert_eq!(script.buffers()[0].size, 4000);
2479 |
2480 | check_test_command_error(
2481 | "ubo 1: subdata u8vec2 8 1 2 3 4 5 6",
2482 | "cannot parse integer from empty string",
2483 | );
2484 |
2485 | check_error(
2486 | "[test]\n\
2487 | ubo 1 subdata u8vec2 8 1 2 3 4 5 6\n\
2488 | ssbo 1 subdata u8vec2 8 1 2 3 4 5 6",
2489 | "line 3: Buffer binding point 0:1 used with different type"
2490 | );
2491 |
2492 | check_error(
2493 | "[test]\n\
2494 | ubo 1 8\n\
2495 | ssbo 1 8",
2496 | "line 3: Buffer binding point 0:1 used with different type"
2497 | );
2498 |
2499 | check_test_command_error(
2500 | "ubo 1",
2501 | "cannot parse integer from empty string",
2502 | );
2503 | check_test_command_error(
2504 | "ubo 1 1 potato",
2505 | "Invalid buffer command",
2506 | );
2507 | check_test_command_error(
2508 | "ubo 1 subdata",
2509 | "Expected GLSL type name",
2510 | );
2511 | }
2512 |
2513 | #[test]
2514 | fn test_draw_rect() {
2515 | let script = check_test_command(
2516 | " draw rect 1 2 3 4 ",
2517 | Operation::DrawRect {
2518 | x: 1.0,
2519 | y: 2.0,
2520 | w: 3.0,
2521 | h: 4.0,
2522 | pipeline_key: 0,
2523 | }
2524 | );
2525 | assert_eq!(script.pipeline_keys().len(), 1);
2526 | assert_eq!(
2527 | script.pipeline_keys()[0].pipeline_type(),
2528 | pipeline_key::Type::Graphics,
2529 | );
2530 | let create_info = script.pipeline_keys()[0].to_create_info();
2531 | unsafe {
2532 | let create_info =
2533 | &*(create_info.as_ptr()
2534 | as *const vk::VkGraphicsPipelineCreateInfo);
2535 | assert_eq!(
2536 | (*create_info.pInputAssemblyState).topology,
2537 | vk::VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP,
2538 | );
2539 | assert_eq!(
2540 | (*create_info.pTessellationState).patchControlPoints,
2541 | 4
2542 | );
2543 | };
2544 |
2545 | let script = check_test_command(
2546 | "draw rect ortho patch 0 0 250 250",
2547 | Operation::DrawRect {
2548 | x: -1.0,
2549 | y: -1.0,
2550 | w: WindowFormat::default().width as f32 * 2.0 / 250.0,
2551 | h: WindowFormat::default().height as f32 * 2.0 / 250.0,
2552 | pipeline_key: 0,
2553 | }
2554 | );
2555 | let create_info = script.pipeline_keys()[0].to_create_info();
2556 | unsafe {
2557 | let create_info =
2558 | &*(create_info.as_ptr()
2559 | as *const vk::VkGraphicsPipelineCreateInfo);
2560 | assert_eq!(
2561 | (*create_info.pInputAssemblyState).topology,
2562 | vk::VK_PRIMITIVE_TOPOLOGY_PATCH_LIST,
2563 | );
2564 | };
2565 |
2566 | // Same test again but with the words inversed
2567 | let script = check_test_command(
2568 | "draw rect patch ortho 0 0 250 250",
2569 | Operation::DrawRect {
2570 | x: -1.0,
2571 | y: -1.0,
2572 | w: WindowFormat::default().width as f32 * 2.0 / 250.0,
2573 | h: WindowFormat::default().height as f32 * 2.0 / 250.0,
2574 | pipeline_key: 0,
2575 | }
2576 | );
2577 | let create_info = script.pipeline_keys()[0].to_create_info();
2578 | unsafe {
2579 | let create_info =
2580 | &*(create_info.as_ptr()
2581 | as *const vk::VkGraphicsPipelineCreateInfo);
2582 | assert_eq!(
2583 | (*create_info.pInputAssemblyState).topology,
2584 | vk::VK_PRIMITIVE_TOPOLOGY_PATCH_LIST,
2585 | );
2586 | };
2587 |
2588 | check_test_command_error(
2589 | "draw rect",
2590 | "cannot parse float from empty string",
2591 | );
2592 | check_test_command_error(
2593 | "draw rect 0",
2594 | "cannot parse float from empty string",
2595 | );
2596 | check_test_command_error(
2597 | "draw rect 0 0",
2598 | "cannot parse float from empty string",
2599 | );
2600 | check_test_command_error(
2601 | "draw rect 0 0 0",
2602 | "cannot parse float from empty string",
2603 | );
2604 | check_test_command_error(
2605 | "draw rect 0 0 0 0 foo",
2606 | "Extra data at end of line",
2607 | );
2608 | }
2609 |
2610 | fn test_slot_base_type(glsl_type: &str, value: &str, values: &[u8]) {
2611 | let source = format!("ssbo 0 subdata {} 0 {}", glsl_type, value);
2612 | check_test_command(
2613 | &source,
2614 | Operation::SetBufferData {
2615 | desc_set: 0,
2616 | binding: 0,
2617 | offset: 0,
2618 | data: values.to_vec().into_boxed_slice(),
2619 | }
2620 | );
2621 |
2622 | let source = format!(
2623 | "ssbo 0 subdata {} 0 0xfffffffffffffffff",
2624 | glsl_type
2625 | );
2626 | check_test_command_error(
2627 | &source,
2628 | "number too large to fit in target type"
2629 | );
2630 | }
2631 |
2632 | #[test]
2633 | fn test_all_slot_base_types() {
2634 | test_slot_base_type("int", "-12", &(-12i32).to_ne_bytes());
2635 | test_slot_base_type("uint", "0xffffffff", &0xffffffffu32.to_ne_bytes());
2636 | test_slot_base_type("int8_t", "-128", &(-128i8).to_ne_bytes());
2637 | test_slot_base_type("uint8_t", "42", &42u8.to_ne_bytes());
2638 | test_slot_base_type("int16_t", "-32768", &(-32768i16).to_ne_bytes());
2639 | test_slot_base_type("uint16_t", "1000", &1000u16.to_ne_bytes());
2640 | test_slot_base_type("int64_t", "-1", &(-1i64).to_ne_bytes());
2641 | test_slot_base_type("uint64_t", "1000", &1000u64.to_ne_bytes());
2642 | test_slot_base_type("float16_t", "1.0", &0x3c00u16.to_ne_bytes());
2643 | test_slot_base_type("float", "1.0", &1.0f32.to_ne_bytes());
2644 | test_slot_base_type("double", "2.0", &2.0f64.to_ne_bytes());
2645 | }
2646 |
2647 | #[test]
2648 | fn test_all_topologies() {
2649 | for &(name, topology) in TOPOLOGY_NAMES.iter() {
2650 | let command = format!("draw arrays {} 8 6", name);
2651 | let script = check_test_command(
2652 | &command,
2653 | Operation::DrawArrays {
2654 | topology,
2655 | indexed: false,
2656 | vertex_count: 6,
2657 | first_vertex: 8,
2658 | instance_count: 1,
2659 | first_instance: 0,
2660 | pipeline_key: 0,
2661 | },
2662 | );
2663 | let create_info = script.pipeline_keys()[0].to_create_info();
2664 | unsafe {
2665 | let create_info =
2666 | &*(create_info.as_ptr()
2667 | as *const vk::VkGraphicsPipelineCreateInfo);
2668 | assert_eq!(
2669 | (*create_info.pInputAssemblyState).topology,
2670 | topology,
2671 | );
2672 | };
2673 | }
2674 | }
2675 |
2676 | #[test]
2677 | fn test_draw_arrays() {
2678 | check_test_command(
2679 | " draw arrays POINT_LIST 8 6 # comment",
2680 | Operation::DrawArrays {
2681 | topology: vk::VK_PRIMITIVE_TOPOLOGY_POINT_LIST,
2682 | indexed: false,
2683 | first_vertex: 8,
2684 | vertex_count: 6,
2685 | instance_count: 1,
2686 | first_instance: 0,
2687 | pipeline_key: 0,
2688 | },
2689 | );
2690 |
2691 | check_test_command(
2692 | "draw arrays indexed GL_TRIANGLES 1 2",
2693 | Operation::DrawArrays {
2694 | topology: vk::VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,
2695 | indexed: true,
2696 | first_vertex: 1,
2697 | vertex_count: 2,
2698 | instance_count: 1,
2699 | first_instance: 0,
2700 | pipeline_key: 0,
2701 | },
2702 | );
2703 |
2704 | check_test_command(
2705 | "draw arrays instanced TRIANGLE_LIST 1 2 56",
2706 | Operation::DrawArrays {
2707 | topology: vk::VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,
2708 | indexed: false,
2709 | first_vertex: 1,
2710 | vertex_count: 2,
2711 | instance_count: 56,
2712 | first_instance: 0,
2713 | pipeline_key: 0,
2714 | },
2715 | );
2716 |
2717 | check_test_command_error(
2718 | "draw arrays",
2719 | "Expected topology name",
2720 | );
2721 | check_test_command_error(
2722 | "draw arrays OBLONG_LIST",
2723 | "Unknown topology: OBLONG_LIST",
2724 | );
2725 | check_test_command_error(
2726 | "draw arrays TRIANGLE_LIST",
2727 | "cannot parse integer from empty string",
2728 | );
2729 | check_test_command_error(
2730 | "draw arrays TRIANGLE_LIST 1",
2731 | "cannot parse integer from empty string",
2732 | );
2733 | check_test_command_error(
2734 | "draw arrays instanced TRIANGLE_LIST 1 2",
2735 | "cannot parse integer from empty string",
2736 | );
2737 | check_test_command_error(
2738 | "draw arrays instanced TRIANGLE_LIST 1 2 3 foo",
2739 | "Extra data at end of line",
2740 | );
2741 | }
2742 |
2743 | #[test]
2744 | fn test_compute() {
2745 | let script = check_test_command(
2746 | "compute 1 2 3",
2747 | Operation::DispatchCompute {
2748 | x: 1,
2749 | y: 2,
2750 | z: 3,
2751 | pipeline_key: 0,
2752 | },
2753 | );
2754 | assert_eq!(script.pipeline_keys().len(), 1);
2755 | assert_eq!(
2756 | script.pipeline_keys()[0].pipeline_type(),
2757 | pipeline_key::Type::Compute,
2758 | );
2759 |
2760 | check_test_command_error(
2761 | "compute",
2762 | "cannot parse integer from empty string",
2763 | );
2764 | check_test_command_error(
2765 | "compute 1",
2766 | "cannot parse integer from empty string",
2767 | );
2768 | check_test_command_error(
2769 | "compute 1 2",
2770 | "cannot parse integer from empty string",
2771 | );
2772 | check_test_command_error(
2773 | "compute 1 2 3 foo",
2774 | "Extra data at end of line",
2775 | );
2776 | }
2777 |
2778 | fn check_probe_ssbo_comparison(name: &str, comparison: slot::Comparison) {
2779 | let command = format!("probe ssbo uint8_t 1 2 {} 3", name);
2780 | check_test_command(
2781 | &command,
2782 | Operation::ProbeSsbo {
2783 | desc_set: 0,
2784 | binding: 1,
2785 | offset: 2,
2786 | slot_type: slot::Type::UInt8,
2787 | comparison: comparison,
2788 | layout: slot::Layout {
2789 | std: slot::LayoutStd::Std430,
2790 | major: slot::MajorAxis::Column,
2791 | },
2792 | values: Box::new([3]),
2793 | tolerance: Tolerance::default(),
2794 | },
2795 | );
2796 | }
2797 |
2798 | #[test]
2799 | fn test_probe_ssbo() {
2800 | check_test_command(
2801 | " probe ssbo u8vec2 1:3 42 == 5 6 # comment ",
2802 | Operation::ProbeSsbo {
2803 | desc_set: 1,
2804 | binding: 3,
2805 | offset: 42,
2806 | slot_type: slot::Type::U8Vec2,
2807 | comparison: slot::Comparison::Equal,
2808 | layout: slot::Layout {
2809 | std: slot::LayoutStd::Std430,
2810 | major: slot::MajorAxis::Column,
2811 | },
2812 | values: Box::new([5, 6]),
2813 | tolerance: Tolerance::default(),
2814 | },
2815 | );
2816 |
2817 | check_probe_ssbo_comparison("==", slot::Comparison::Equal);
2818 | check_probe_ssbo_comparison("~=", slot::Comparison::FuzzyEqual);
2819 | check_probe_ssbo_comparison("!=", slot::Comparison::NotEqual);
2820 | check_probe_ssbo_comparison("<", slot::Comparison::Less);
2821 | check_probe_ssbo_comparison(">=", slot::Comparison::GreaterEqual);
2822 | check_probe_ssbo_comparison(">", slot::Comparison::Greater);
2823 | check_probe_ssbo_comparison("<=", slot::Comparison::LessEqual);
2824 |
2825 | check_test_command_error(
2826 | "probe ssbo uint33_t 1 42 == 5",
2827 | "Invalid GLSL type name: uint33_t",
2828 | );
2829 | check_test_command_error(
2830 | "probe ssbo uint16_t :1 1 == 5",
2831 | "cannot parse integer from empty string",
2832 | );
2833 | check_test_command_error(
2834 | "probe ssbo uint16_t 1 foo == 5",
2835 | "invalid digit found in string",
2836 | );
2837 | check_test_command_error(
2838 | "probe ssbo uint16_t 1 1",
2839 | "Expected comparison operator",
2840 | );
2841 | check_test_command_error(
2842 | "probe ssbo uint16_t 1 1 (=) 5",
2843 | "Unknown comparison operator: (=)",
2844 | );
2845 | check_test_command_error(
2846 | "probe ssbo uint16_t 1 1 ==",
2847 | "cannot parse integer from empty string",
2848 | );
2849 | }
2850 |
2851 | #[test]
2852 | fn test_clear() {
2853 | check_test_command(
2854 | " clear # comment ",
2855 | Operation::Clear {
2856 | color: [0.0, 0.0, 0.0, 0.0],
2857 | depth: 1.0,
2858 | stencil: 0,
2859 | },
2860 | );
2861 | }
2862 |
2863 | #[test]
2864 | fn test_clear_values() {
2865 | let script = script_from_string(
2866 | "[test]\n\
2867 | clear color 1 2 3 4\n\
2868 | clear depth 64.0 # comment\n\
2869 | clear stencil 32\n\
2870 | clear\n\
2871 | clear color 5 6 7 8\n\
2872 | clear depth 10\n\
2873 | clear stencil 33\n\
2874 | clear".to_string(),
2875 | );
2876 | assert_eq!(script.commands().len(), 2);
2877 | assert_eq!(
2878 | script.commands()[0].op,
2879 | Operation::Clear {
2880 | color: [1.0, 2.0, 3.0, 4.0],
2881 | depth: 64.0,
2882 | stencil: 32,
2883 | },
2884 | );
2885 | assert_eq!(
2886 | script.commands()[1].op,
2887 | Operation::Clear {
2888 | color: [5.0, 6.0, 7.0, 8.0],
2889 | depth: 10.0,
2890 | stencil: 33,
2891 | },
2892 | );
2893 |
2894 | check_test_command_error(
2895 | "clear color",
2896 | "cannot parse float from empty string",
2897 | );
2898 | check_test_command_error(
2899 | "clear color 1",
2900 | "cannot parse float from empty string",
2901 | );
2902 | check_test_command_error(
2903 | "clear color 1 2",
2904 | "cannot parse float from empty string",
2905 | );
2906 | check_test_command_error(
2907 | "clear color 1 2 3",
2908 | "cannot parse float from empty string",
2909 | );
2910 | check_test_command_error(
2911 | "clear color 1 2 3 4 foo",
2912 | "Invalid clear color command",
2913 | );
2914 | check_test_command_error(
2915 | "clear depth",
2916 | "cannot parse float from empty string",
2917 | );
2918 | check_test_command_error(
2919 | "clear depth 1 foo",
2920 | "Invalid clear depth command",
2921 | );
2922 | check_test_command_error(
2923 | "clear stencil",
2924 | "cannot parse integer from empty string",
2925 | );
2926 | check_test_command_error(
2927 | "clear stencil 1 foo",
2928 | "Invalid clear stencil command",
2929 | );
2930 | check_test_command_error(
2931 | "clear the dining table",
2932 | "Invalid test command",
2933 | );
2934 | }
2935 |
2936 | fn pipeline_line_width(key: &pipeline_key::Key) -> f32 {
2937 | let create_info = key.to_create_info();
2938 | unsafe {
2939 | let create_info =
2940 | &*(create_info.as_ptr()
2941 | as *const vk::VkGraphicsPipelineCreateInfo);
2942 | (*create_info.pRasterizationState).lineWidth
2943 | }
2944 | }
2945 |
2946 | fn pipeline_depth_bias_clamp(key: &pipeline_key::Key) -> f32 {
2947 | let create_info = key.to_create_info();
2948 | unsafe {
2949 | let create_info =
2950 | &*(create_info.as_ptr()
2951 | as *const vk::VkGraphicsPipelineCreateInfo);
2952 | (*create_info.pRasterizationState).depthBiasClamp
2953 | }
2954 | }
2955 |
2956 | #[test]
2957 | fn test_pipeline_properties() {
2958 | let script = script_from_string(
2959 | "[test]\n\
2960 | lineWidth 4.0\n\
2961 | depthBiasClamp 8.0\n\
2962 | draw rect 1 1 2 3\n\
2963 | compute 1 1 1\n\
2964 | \n\
2965 | lineWidth 2.0\n\
2966 | draw rect 1 1 2 3\n\
2967 | # The properties don’t affect the compute pipeline so this\n\
2968 | # resuse the same pipeline\n\
2969 | compute 1 1 1\n\
2970 | \n\
2971 | # Put the line width back to what it was for the first pipeline.\n\
2972 | # This should reuse the same pipeline.
2973 | lineWidth 4.0\n\
2974 | draw rect 1 1 2 3".to_string()
2975 | );
2976 | assert_eq!(script.pipeline_keys().len(), 3);
2977 |
2978 | assert_eq!(
2979 | script.pipeline_keys()[0].pipeline_type(),
2980 | pipeline_key::Type::Graphics,
2981 | );
2982 | assert_eq!(pipeline_line_width(&script.pipeline_keys()[0]), 4.0);
2983 | assert_eq!(pipeline_depth_bias_clamp(&script.pipeline_keys()[0]), 8.0);
2984 |
2985 | assert_eq!(
2986 | script.pipeline_keys()[1].pipeline_type(),
2987 | pipeline_key::Type::Compute,
2988 | );
2989 |
2990 | assert_eq!(
2991 | script.pipeline_keys()[2].pipeline_type(),
2992 | pipeline_key::Type::Graphics,
2993 | );
2994 | assert_eq!(pipeline_line_width(&script.pipeline_keys()[2]), 2.0);
2995 | assert_eq!(pipeline_depth_bias_clamp(&script.pipeline_keys()[2]), 8.0);
2996 |
2997 | // Test processing a line without a key directly because it’s
2998 | // probably not really possible to trigger this code any other
2999 | // way.
3000 | let source = Source::from_string(String::new());
3001 | let mut loader = Loader::new(&source).unwrap();
3002 | assert!(matches!(
3003 | loader.process_pipeline_property(""),
3004 | Ok(MatchResult::NotMatched)
3005 | ));
3006 |
3007 | check_test_command_error(
3008 | "lineWidth",
3009 | "Invalid value: ",
3010 | );
3011 | check_test_command_error(
3012 | "lineWidth 8.0 foo",
3013 | "Invalid value: 8.0 foo",
3014 | );
3015 | check_test_command_error(
3016 | "lineWidth foo",
3017 | "Invalid value: foo",
3018 | );
3019 | }
3020 |
3021 | #[test]
3022 | fn test_layout() {
3023 | let script = script_from_string(
3024 | "[test]\n\
3025 | ssbo layout std140 row_major # comment\n\
3026 | ubo layout std430 row_major\n\
3027 | push layout row_major std140\n\
3028 | \n\
3029 | ssbo 1 subdata mat2 0 1 2 3 4\n\
3030 | ubo 0 subdata mat2 0 1 2 3 4\n\
3031 | push mat2 4 1 2 3 4\n\
3032 | \n\
3033 | # Setting only one of the properties resets the other to\n\
3034 | # the default\n\
3035 | ssbo layout row_major\n\
3036 | ubo layout std430 column_major\n\
3037 | push layout std140\n\
3038 | \n\
3039 | ssbo 1 subdata mat2 0 1 2 3 4\n\
3040 | ubo 0 subdata mat2 0 1 2 3 4\n\
3041 | push mat2 4 1 2 3 4".to_string(),
3042 | );
3043 |
3044 | let mut std140_row = Vec::new();
3045 | std140_row.extend_from_slice(&1.0f32.to_ne_bytes());
3046 | std140_row.extend_from_slice(&3.0f32.to_ne_bytes());
3047 | // std140 pads up to 16 bytes
3048 | std140_row.resize(16, 0);
3049 | std140_row.extend_from_slice(&2.0f32.to_ne_bytes());
3050 | std140_row.extend_from_slice(&4.0f32.to_ne_bytes());
3051 |
3052 | let mut std140_column = Vec::new();
3053 | std140_column.extend_from_slice(&1.0f32.to_ne_bytes());
3054 | std140_column.extend_from_slice(&2.0f32.to_ne_bytes());
3055 | // std140 pads up to 16 bytes
3056 | std140_column.resize(16, 0);
3057 | std140_column.extend_from_slice(&3.0f32.to_ne_bytes());
3058 | std140_column.extend_from_slice(&4.0f32.to_ne_bytes());
3059 |
3060 | let mut std430_row = Vec::new();
3061 | std430_row.extend_from_slice(&1.0f32.to_ne_bytes());
3062 | std430_row.extend_from_slice(&3.0f32.to_ne_bytes());
3063 | std430_row.extend_from_slice(&2.0f32.to_ne_bytes());
3064 | std430_row.extend_from_slice(&4.0f32.to_ne_bytes());
3065 |
3066 | let mut std430_column = Vec::new();
3067 | std430_column.extend_from_slice(&1.0f32.to_ne_bytes());
3068 | std430_column.extend_from_slice(&2.0f32.to_ne_bytes());
3069 | std430_column.extend_from_slice(&3.0f32.to_ne_bytes());
3070 | std430_column.extend_from_slice(&4.0f32.to_ne_bytes());
3071 |
3072 | assert_eq!(script.commands().len(), 6);
3073 |
3074 | assert_eq!(
3075 | script.commands()[0].op,
3076 | Operation::SetBufferData {
3077 | desc_set: 0,
3078 | binding: 1,
3079 | offset: 0,
3080 | data: std140_row.clone().into_boxed_slice(),
3081 | },
3082 | );
3083 | assert_eq!(
3084 | script.commands()[1].op,
3085 | Operation::SetBufferData {
3086 | desc_set: 0,
3087 | binding: 0,
3088 | offset: 0,
3089 | data: std430_row.clone().into_boxed_slice(),
3090 | },
3091 | );
3092 | assert_eq!(
3093 | script.commands()[2].op,
3094 | Operation::SetPushCommand {
3095 | offset: 4,
3096 | data: std140_row.clone().into_boxed_slice(),
3097 | },
3098 | );
3099 |
3100 | assert_eq!(
3101 | script.commands()[3].op,
3102 | Operation::SetBufferData {
3103 | desc_set: 0,
3104 | binding: 1,
3105 | offset: 0,
3106 | data: std430_row.clone().into_boxed_slice(),
3107 | },
3108 | );
3109 | assert_eq!(
3110 | script.commands()[4].op,
3111 | Operation::SetBufferData {
3112 | desc_set: 0,
3113 | binding: 0,
3114 | offset: 0,
3115 | data: std430_column.clone().into_boxed_slice(),
3116 | },
3117 | );
3118 | assert_eq!(
3119 | script.commands()[5].op,
3120 | Operation::SetPushCommand {
3121 | offset: 4,
3122 | data: std140_column.clone().into_boxed_slice(),
3123 | },
3124 | );
3125 |
3126 | check_test_command_error(
3127 | "ssbo layout std140 hexagonal",
3128 | "Unknown layout parameter “hexagonal”",
3129 | );
3130 | }
3131 |
3132 | #[test]
3133 | fn test_tolerance() {
3134 | let script = script_from_string(
3135 | "[test]\n\
3136 | tolerance 12% 14% 15% 16% # comment !!\n\
3137 | probe rgb (0, 0) (1, 1, 1)\n\
3138 | tolerance 17 18 19.0 20\n\
3139 | probe rgb (0, 0) (1, 1, 1)\n\
3140 | tolerance 21%
3141 | probe rgb (0, 0) (1, 1, 1)\n\
3142 | tolerance 22\n\
3143 | probe rgb (0, 0) (1, 1, 1)".to_string(),
3144 | );
3145 |
3146 | assert_eq!(script.commands().len(), 4);
3147 | assert_eq!(
3148 | script.commands()[0].op,
3149 | Operation::ProbeRect {
3150 | n_components: 3,
3151 | x: 0,
3152 | y: 0,
3153 | w: 1,
3154 | h: 1,
3155 | color: [1.0, 1.0, 1.0, 0.0],
3156 | tolerance: Tolerance::new([12.0, 14.0, 15.0, 16.0], true),
3157 | },
3158 | );
3159 | assert_eq!(
3160 | script.commands()[1].op,
3161 | Operation::ProbeRect {
3162 | n_components: 3,
3163 | x: 0,
3164 | y: 0,
3165 | w: 1,
3166 | h: 1,
3167 | color: [1.0, 1.0, 1.0, 0.0],
3168 | tolerance: Tolerance::new([17.0, 18.0, 19.0, 20.0], false),
3169 | },
3170 | );
3171 | assert_eq!(
3172 | script.commands()[2].op,
3173 | Operation::ProbeRect {
3174 | n_components: 3,
3175 | x: 0,
3176 | y: 0,
3177 | w: 1,
3178 | h: 1,
3179 | color: [1.0, 1.0, 1.0, 0.0],
3180 | tolerance: Tolerance::new([21.0, 21.0, 21.0, 21.0], true),
3181 | },
3182 | );
3183 | assert_eq!(
3184 | script.commands()[3].op,
3185 | Operation::ProbeRect {
3186 | n_components: 3,
3187 | x: 0,
3188 | y: 0,
3189 | w: 1,
3190 | h: 1,
3191 | color: [1.0, 1.0, 1.0, 0.0],
3192 | tolerance: Tolerance::new([22.0, 22.0, 22.0, 22.0], false),
3193 | },
3194 | );
3195 |
3196 | check_test_command_error(
3197 | "tolerance 1 2 3 4 5",
3198 | "tolerance command has extra arguments",
3199 | );
3200 | check_test_command_error(
3201 | "tolerance 1 2 3 4%",
3202 | "Either all tolerance values must be a percentage or none",
3203 | );
3204 | check_test_command_error(
3205 | "tolerance 1% 2% 3% 4",
3206 | "Either all tolerance values must be a percentage or none",
3207 | );
3208 | check_test_command_error(
3209 | "tolerance foo 2 3 4",
3210 | "cannot parse float from empty string",
3211 | );
3212 | check_test_command_error(
3213 | "tolerance 2 3",
3214 | "There must be either 1 or 4 tolerance values",
3215 | );
3216 | }
3217 |
3218 | #[test]
3219 | fn test_entrypoint() {
3220 | let script = script_from_string(
3221 | "[test]\n\
3222 | vertex entrypoint lister # comment\n\
3223 | tessellation control entrypoint rimmer\n\
3224 | tessellation evaluation entrypoint kryten \n\
3225 | geometry entrypoint kochanski\n\
3226 | fragment entrypoint holly\n\
3227 | compute entrypoint cat\n\
3228 | draw arrays TRIANGLE_LIST 1 1".to_string()
3229 | );
3230 |
3231 | assert_eq!(script.pipeline_keys().len(), 1);
3232 |
3233 | let key = &script.pipeline_keys()[0];
3234 |
3235 | assert_eq!(key.entrypoint(Stage::Vertex), "lister");
3236 | assert_eq!(key.entrypoint(Stage::TessCtrl), "rimmer");
3237 | assert_eq!(key.entrypoint(Stage::TessEval), "kryten");
3238 | assert_eq!(key.entrypoint(Stage::Geometry), "kochanski");
3239 | assert_eq!(key.entrypoint(Stage::Fragment), "holly");
3240 | assert_eq!(key.entrypoint(Stage::Compute), "cat");
3241 |
3242 | check_test_command_error(
3243 | "geometry entrypoint",
3244 | "Missing entrypoint name",
3245 | );
3246 | }
3247 |
3248 | #[test]
3249 | fn test_patch_parameter_vertices() {
3250 | let script = script_from_string(
3251 | "[test]\n\
3252 | patch parameter vertices 64\n\
3253 | draw arrays PATCH_LIST 1 2".to_string()
3254 | );
3255 |
3256 | assert_eq!(script.pipeline_keys().len(), 1);
3257 |
3258 | let create_info = script.pipeline_keys()[0].to_create_info();
3259 | unsafe {
3260 | let create_info =
3261 | &*(create_info.as_ptr()
3262 | as *const vk::VkGraphicsPipelineCreateInfo);
3263 | assert_eq!(
3264 | (*create_info.pTessellationState).patchControlPoints,
3265 | 64
3266 | );
3267 | };
3268 |
3269 | check_test_command_error(
3270 | "patch parameter vertices 1e2",
3271 | "invalid digit found in string",
3272 | );
3273 | check_test_command_error(
3274 | "patch parameter vertices 1 foo",
3275 | "Invalid patch parameter vertices command",
3276 | );
3277 | }
3278 |
3279 | #[test]
3280 | fn test_load_from_invalid_file() {
3281 | let source = Source::from_file(
3282 | "this-file-does-not-exist".to_string().into()
3283 | );
3284 | let e = Script::load_or_error(&source).unwrap_err();
3285 | match e {
3286 | LoadError::Stream(StreamError::IoError(e)) => {
3287 | assert_eq!(e.kind(), io::ErrorKind::NotFound);
3288 | },
3289 | _ => unreachable!("expected StreamError::IoError, got: {}", e),
3290 | };
3291 | }
3292 |
3293 | fn test_glsl_shader(header: &str, stage: Stage) {
3294 | const SHADER_SOURCE: &'static str =
3295 | "# this comment isn’t really a comment and it should stay\n\
3296 | # in the source.\n\
3297 | \n\
3298 | int\n\
3299 | main()\n\
3300 | {\n\
3301 | gl_FragColor = vec4(1.0);\n\
3302 | }";
3303 | let source = format!("{}\n{}", header, SHADER_SOURCE);
3304 | let script = script_from_string(source);
3305 |
3306 | for i in 0..N_STAGES {
3307 | if i == stage as usize {
3308 | assert_eq!(script.stages[i].len(), 1);
3309 | match &script.stages[i][0] {
3310 | Shader::Glsl(s) => assert_eq!(s, SHADER_SOURCE),
3311 | s @ _ => unreachable!("Unexpected shader type: {:?}", s),
3312 | }
3313 | } else {
3314 | assert_eq!(script.stages[i].len(), 0);
3315 | }
3316 | }
3317 | }
3318 |
3319 | #[test]
3320 | fn test_glsl_shaders() {
3321 | test_glsl_shader("[vertex shader]", Stage::Vertex);
3322 | test_glsl_shader("[tessellation control shader]", Stage::TessCtrl);
3323 | test_glsl_shader("[tessellation evaluation shader]", Stage::TessEval);
3324 | test_glsl_shader("[ geometry shader]", Stage::Geometry);
3325 | test_glsl_shader("[ fragment shader ]", Stage::Fragment);
3326 | test_glsl_shader("[ compute shader ]", Stage::Compute);
3327 | }
3328 |
3329 | #[test]
3330 | fn test_spirv_shader() {
3331 | let script = script_from_string(
3332 | "[fragment shader spirv ]\n\
3333 | <insert spirv shader here>".to_string()
3334 | );
3335 | assert_eq!(script.shaders(Stage::Fragment).len(), 1);
3336 |
3337 | match &script.shaders(Stage::Fragment)[0] {
3338 | Shader::Spirv(s) => assert_eq!(s, "<insert spirv shader here>"),
3339 | s @ _ => unreachable!("Unexpected shader type: {:?}", s),
3340 | }
3341 |
3342 | check_error(
3343 | "[fragment shader]\n\
3344 | this is a GLSL shader\n\
3345 | [fragment shader spirv]\n\
3346 | this is a SPIR-V shader",
3347 | "line 3: SPIR-V source can not be linked with other shaders in the \
3348 | same stage",
3349 | );
3350 | check_error(
3351 | "[fragment shader spirv]\n\
3352 | this is a SPIR-V shader\n\
3353 | [fragment shader]\n\
3354 | this is a GLSL shader",
3355 | "line 3: SPIR-V source can not be linked with other shaders in the \
3356 | same stage",
3357 | );
3358 | }
3359 |
3360 | #[test]
3361 | fn test_binary_shader() {
3362 | let script = script_from_string(
3363 | "[vertex shader binary ]\n\
3364 | # comments and blank lines are ok\n\
3365 | \n\
3366 | 1 2 3\n\
3367 | 4 5 feffffff".to_string()
3368 | );
3369 |
3370 | match &script.shaders(Stage::Vertex)[0] {
3371 | Shader::Binary(data) => {
3372 | assert_eq!(data, &[1, 2, 3, 4, 5, 0xfeffffff])
3373 | },
3374 | s @ _ => unreachable!("Expected binary shader, got: {:?}", s),
3375 | }
3376 |
3377 | check_error(
3378 | "[fragment shader binary]\n\
3379 | 1ffffffff",
3380 | "line 2: Invalid hex value: 1ffffffff"
3381 | );
3382 | }
3383 |
3384 | #[test]
3385 | fn test_passthrough_vertex_shader() {
3386 | let script = script_from_string(
3387 | "[vertex shader passthrough]\n\
3388 | # comments and blank lines are allowed in this\n\
3389 | # otherwise empty section\n\
3390 | \n".to_string()
3391 | );
3392 | assert_eq!(script.shaders(Stage::Vertex).len(), 1);
3393 | match &script.shaders(Stage::Vertex)[0] {
3394 | Shader::Binary(s) => assert_eq!(
3395 | s,
3396 | &PASSTHROUGH_VERTEX_SHADER.to_vec(),
3397 | ),
3398 | s @ _ => unreachable!("Unexpected shader type: {:?}", s),
3399 | }
3400 |
3401 | check_error(
3402 | "[vertex shader]\n\
3403 | this is a GLSL shader\n\
3404 | [vertex shader passthrough]",
3405 | "line 3: SPIR-V source can not be linked with other shaders in the \
3406 | same stage",
3407 | );
3408 | check_error(
3409 | "[vertex shader passthrough]\n\
3410 | this line isn’t allowed",
3411 | "line 2: expected empty line",
3412 | );
3413 | }
3414 |
3415 | fn run_test_bad_utf8(filename: String) {
3416 | fs::write(&filename, b"enchant\xe9 in latin1").unwrap();
3417 |
3418 | let source = Source::from_file(filename.into());
3419 | let error = Script::load_or_error(&source).unwrap_err();
3420 |
3421 | match &error {
3422 | LoadError::Stream(StreamError::IoError(e)) => {
3423 | assert_eq!(e.kind(), io::ErrorKind::InvalidData);
3424 | },
3425 | _ => unreachable!("Expected InvalidData error, got: {}", error),
3426 | }
3427 |
3428 | assert_eq!(error.to_string(), "stream did not contain valid UTF-8");
3429 | }
3430 |
3431 | #[test]
3432 | fn test_bad_utf8() {
3433 | let mut filename = std::env::temp_dir();
3434 | filename.push("vkrunner-test-bad-utf8-source");
3435 | let filename_str = filename.to_str().unwrap().to_owned();
3436 |
3437 | // Catch the unwind to try to remove the file that we created
3438 | // if the test fails
3439 | let r = std::panic::catch_unwind(
3440 | move || run_test_bad_utf8(filename_str)
3441 | );
3442 |
3443 | if let Err(e) = fs::remove_file(filename) {
3444 | assert_eq!(e.kind(), io::ErrorKind::NotFound);
3445 | }
3446 |
3447 | if let Err(e) = r {
3448 | std::panic::resume_unwind(e);
3449 | }
3450 | }
3451 |
3452 | #[test]
3453 | fn test_vertex_data() {
3454 | let script = script_from_string(
3455 | "[vertex data]\n\
3456 | 0/R8_UNORM\n\
3457 | 1\n\
3458 | 2\n\
3459 | 3".to_string()
3460 | );
3461 |
3462 | assert_eq!(script.vertex_data().unwrap().raw_data(), &[1, 2, 3]);
3463 |
3464 | check_error(
3465 | "[vertex data]\n\
3466 | 0/R9_UNORM\n\
3467 | 12",
3468 | "line 2: Unknown format: R9_UNORM"
3469 | );
3470 | check_error(
3471 | "[vertex data]\n\
3472 | 0/R8_UNORM\n\
3473 | 12\n\
3474 | [vertex data]\n\
3475 | 0/R8G8_UNORM\n\
3476 | 14",
3477 | "line 4: Duplicate vertex data section"
3478 | );
3479 | check_error(
3480 | "[vertex data]\n\
3481 | [indices]",
3482 | "line 2: Missing header line",
3483 | );
3484 | check_error(
3485 | "[vertex data]",
3486 | "line 2: Missing header line",
3487 | );
3488 | }
3489 |
3490 | #[test]
3491 | fn test_parse_version() {
3492 | let source = Source::from_string(String::new());
3493 | let loader = Loader::new(&source).unwrap();
3494 | assert_eq!(loader.parse_version(" 1.2.3 ").unwrap(), (1, 2, 3));
3495 | assert_eq!(loader.parse_version("1.2").unwrap(), (1, 2, 0));
3496 | assert_eq!(loader.parse_version("1").unwrap(), (1, 0, 0));
3497 |
3498 | assert_eq!(
3499 | loader.parse_version("1.2.3.4").unwrap_err().to_string(),
3500 | "line 0: Invalid Vulkan version",
3501 | );
3502 | assert_eq!(
3503 | loader.parse_version("1.foo.3").unwrap_err().to_string(),
3504 | "line 0: Invalid Vulkan version",
3505 | );
3506 | assert_eq!(
3507 | loader.parse_version("").unwrap_err().to_string(),
3508 | "line 0: Invalid Vulkan version",
3509 | );
3510 | assert_eq!(
3511 | loader.parse_version("1.2 foo").unwrap_err().to_string(),
3512 | "line 0: Invalid Vulkan version",
3513 | );
3514 | }
3515 |
3516 | #[test]
3517 | fn test_comment_section() {
3518 | let script = script_from_string(
3519 | "[comment]\n\
3520 | this is a comment. It will be ignored.\n\
3521 | # this is a comment within a comment. It will be doubly ignored.\n\
3522 | \n\
3523 | \x20 [this isn’t a section header]\n\
3524 | [test]\n\
3525 | draw arrays TRIANGLE_LIST 1 2".to_string()
3526 | );
3527 | assert_eq!(script.commands().len(), 1);
3528 | assert!(matches!(
3529 | script.commands()[0].op, Operation::DrawArrays { .. }
3530 | ));
3531 | }
3532 |
3533 | #[test]
3534 | fn test_requires_section() {
3535 | let script = script_from_string(
3536 | "[comment]\n\
3537 | a comment section can appear before the require section\n\
3538 | [require]\n\
3539 | # comments and blank lines are ok\n\
3540 | \n\
3541 | framebuffer R8_UNORM\n\
3542 | depthstencil R8G8_UNORM\n\
3543 | fbsize 12 32\n\
3544 | vulkan 1.2.3\n\
3545 | shaderBufferInt64Atomics\n\
3546 | VK_KHR_multiview".to_string()
3547 | );
3548 |
3549 | assert_eq!(
3550 | script.window_format().color_format,
3551 | Format::lookup_by_vk_format(vk::VK_FORMAT_R8_UNORM),
3552 | );
3553 | assert_eq!(
3554 | script.window_format().depth_stencil_format,
3555 | Some(Format::lookup_by_vk_format(vk::VK_FORMAT_R8G8_UNORM)),
3556 | );
3557 | assert_eq!(
3558 | (script.window_format().width, script.window_format().height),
3559 | (12, 32),
3560 | );
3561 |
3562 | let reqs = script.requirements();
3563 |
3564 | assert_eq!(reqs.version(), make_version(1, 2, 3));
3565 |
3566 | let mut extensions = std::collections::HashSet::<String>::new();
3567 | extensions.insert("VK_KHR_shader_atomic_int64".to_owned());
3568 | extensions.insert("VK_KHR_multiview".to_owned());
3569 |
3570 | for &ext in reqs.c_extensions().iter() {
3571 | unsafe {
3572 | let ext_c = CStr::from_ptr(ext as *const std::ffi::c_char);
3573 | let ext = ext_c.to_str().unwrap();
3574 | assert!(extensions.remove(ext));
3575 | }
3576 | }
3577 |
3578 | assert_eq!(extensions.len(), 0);
3579 |
3580 | check_error(
3581 | "[require]\n\
3582 | framebuffer R9_UNORM",
3583 | "line 2: Unknown format: R9_UNORM"
3584 | );
3585 | check_error(
3586 | "[require]\n\
3587 | depthstencil R9_UNORM",
3588 | "line 2: Unknown format: R9_UNORM"
3589 | );
3590 | check_error(
3591 | "[require]\n\
3592 | framebuffer ",
3593 | "line 2: Missing format name"
3594 | );
3595 | check_error(
3596 | "[require]\n\
3597 | fbsize",
3598 | "line 2: cannot parse integer from empty string",
3599 | );
3600 | check_error(
3601 | "[require]\n\
3602 | fbsize 1",
3603 | "line 2: cannot parse integer from empty string",
3604 | );
3605 | check_error(
3606 | "[require]\n\
3607 | fbsize 1 2 3",
3608 | "line 2: Invalid fbsize",
3609 | );
3610 | check_error(
3611 | "[require]\n\
3612 | vulkan one point one",
3613 | "line 2: Invalid Vulkan version",
3614 | );
3615 | check_error(
3616 | "[require]\n\
3617 | extension_name_with spaces",
3618 | "line 2: Invalid require line"
3619 | );
3620 | check_error(
3621 | "[indices]\n\
3622 | 1\n\
3623 | [require]\n\
3624 | framebuffer R8_UNORM",
3625 | "line 3: [require] must be the first section"
3626 | );
3627 | }
3628 |
3629 | #[test]
3630 | fn test_indices() {
3631 | let script = script_from_string(
3632 | "[indices]\n\
3633 | # comments and blank lines are ok\n\
3634 | \n\
3635 | 0 1\n\
3636 | 2\n\
3637 | 3 4 5".to_string()
3638 | );
3639 | assert_eq!(script.indices(), [0, 1, 2, 3, 4, 5]);
3640 |
3641 | check_error(
3642 | "[indices]\n\
3643 | \n\
3644 | 65536",
3645 | "line 3: number too large to fit in target type"
3646 | );
3647 | }
3648 |
3649 | #[test]
3650 | fn test_bad_section() {
3651 | check_error(
3652 | "[ reticulated splines ]",
3653 | "line 1: Unknown section “reticulated splines”",
3654 | );
3655 | check_error(
3656 | "[I forgot to close the door",
3657 | "line 1: Missing ‘]’",
3658 | );
3659 | check_error(
3660 | "[vertex shader] <-- this is a great section",
3661 | "line 1: Trailing data after ‘]’",
3662 | );
3663 | }
3664 |
3665 | #[test]
3666 | fn test_replace_shaders_stage_binary() {
3667 | let mut script = script_from_string(
3668 | "[vertex shader]\n\
3669 | shader one\n\
3670 | [vertex shader]\n\
3671 | shader two".to_string()
3672 | );
3673 |
3674 | script.replace_shaders_stage_binary(Stage::Vertex, &[0, 1, 2]);
3675 |
3676 | assert_eq!(script.shaders(Stage::Vertex).len(), 1);
3677 |
3678 | match &script.shaders(Stage::Vertex)[0] {
3679 | Shader::Binary(data) => assert_eq!(data, &[0, 1, 2]),
3680 | s @ _ => unreachable!("Unexpected shader type: {:?}", s),
3681 | }
3682 | }
3683 |
3684 | fn check_buffer_bindings_sorted(script: &Script) {
3685 | for (last_buffer_num, buffer) in script
3686 | .buffers()[1..]
3687 | .iter()
3688 | .enumerate()
3689 | {
3690 | let last_buffer = &script.buffers()[last_buffer_num];
3691 | assert!(buffer.desc_set >= last_buffer.desc_set);
3692 | if buffer.desc_set == last_buffer.desc_set {
3693 | assert!(buffer.binding > last_buffer.binding);
3694 | }
3695 | }
3696 | }
3697 |
3698 | #[test]
3699 | fn sorted_buffer_bindings() {
3700 | let script = script_from_string(
3701 | "[test]\n\
3702 | ssbo 0:0 10\n\
3703 | ssbo 0:1 10\n\
3704 | ssbo 0:2 10\n\
3705 | ssbo 1:0 10\n\
3706 | ssbo 1:1 10\n\
3707 | ssbo 1:2 10\n\
3708 | ssbo 2:0 10\n\
3709 | ssbo 2:1 10\n\
3710 | ssbo 2:2 10\n".to_string()
3711 | );
3712 | assert_eq!(script.buffers().len(), 9);
3713 | check_buffer_bindings_sorted(&script);
3714 |
3715 | let script = script_from_string(
3716 | "[test]\n\
3717 | ssbo 2:2 10\n\
3718 | ssbo 2:1 10\n\
3719 | ssbo 2:0 10\n\
3720 | ssbo 1:2 10\n\
3721 | ssbo 1:1 10\n\
3722 | ssbo 1:0 10\n\
3723 | ssbo 0:2 10\n\
3724 | ssbo 0:1 10\n\
3725 | ssbo 0:0 10\n".to_string()
3726 | );
3727 | assert_eq!(script.buffers().len(), 9);
3728 | check_buffer_bindings_sorted(&script);
3729 | }
3730 | }
3731 |
```