#
tokens: 41031/50000 1/78 files (page 9/9)
lines: on (toggle) GitHub
raw markdown copy reset
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 | 
```
Page 9/9FirstPrevNextLast