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 | ```