1use std::collections::HashMap;
4
5use crate::lexer::{BindingTypes, Expressions, NodeReference, Nodes, Operators};
6
7mod compiler;
8mod error;
9mod execution;
10mod instruction;
11mod value;
12
13pub use error::VmError;
14use instruction::*;
15use value::*;
16
17#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
19pub struct ResourceSlot {
20 slot: u32,
21 kind: ResourceSlotKind,
23}
24
25#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
26enum ResourceSlotKind {
27 Resource,
28 PushConstant,
29 DynamicResource,
30 BuiltinPosition,
31 Input,
32 Output,
33}
34
35impl ResourceSlot {
36 pub const fn new(slot: u32) -> Self {
37 Self {
38 slot,
39 kind: ResourceSlotKind::Resource,
40 }
41 }
42
43 pub const fn slot(&self) -> u32 {
44 self.slot
45 }
46
47 const fn virtual_slot(slot: u32, kind: ResourceSlotKind) -> Self {
48 Self { slot, kind }
49 }
50
51 const fn is_dynamic_resource(&self) -> bool {
52 matches!(self.kind, ResourceSlotKind::DynamicResource)
53 }
54}
55
56const PUSH_CONSTANT_SLOT: ResourceSlot = ResourceSlot::virtual_slot(0, ResourceSlotKind::PushConstant);
57
58pub const fn input_slot(location: u8) -> ResourceSlot {
59 ResourceSlot::virtual_slot(location as u32, ResourceSlotKind::Input)
60}
61
62pub const fn output_slot(location: u8) -> ResourceSlot {
63 ResourceSlot::virtual_slot(location as u32, ResourceSlotKind::Output)
64}
65
66pub const fn builtin_position_slot() -> ResourceSlot {
68 ResourceSlot::virtual_slot(0, ResourceSlotKind::BuiltinPosition)
69}
70
71fn dynamic_resource_slot(register: usize) -> ResourceSlot {
72 ResourceSlot::virtual_slot(
73 u32::try_from(register).expect(
74 "Invalid VM resource register. The most likely cause is that compilation produced more registers than the flat slot representation can address.",
75 ),
76 ResourceSlotKind::DynamicResource,
77 )
78}
79
80#[derive(Clone, Debug, PartialEq, Eq)]
82pub enum ValueType {
83 Bool,
84 U8,
85 U16,
86 U32,
87 I32,
88 F32,
89 Vec2U16,
90 Vec4U16,
91 Vec2I,
92 Vec3U,
93 Vec2U,
94 Vec4U,
95 Vec2F,
96 Vec3F,
97 Vec4F,
98 Mat4F,
99 Mat4x3F,
100 Texture2D,
101 Texture3D,
102 ArrayTexture2D,
103 Struct {
104 name: String,
105 fields: Vec<BufferMemberLayout>,
106 size: usize,
107 },
108}
109
110impl ValueType {
111 pub const fn size(&self) -> usize {
112 match self {
113 ValueType::Bool => 1,
114 ValueType::U8 => 1,
115 ValueType::U16 => 2,
116 ValueType::U32 | ValueType::I32 | ValueType::F32 => 4,
117 ValueType::Vec2U16 => 4,
118 ValueType::Vec4U16 => 8,
119 ValueType::Vec2I => 8,
120 ValueType::Vec2U | ValueType::Vec2F => 8,
121 ValueType::Vec3U => 12,
122 ValueType::Vec4U | ValueType::Vec4F => 16,
123 ValueType::Vec3F => 12,
124 ValueType::Mat4F => 64,
125 ValueType::Mat4x3F => 48,
126 ValueType::Texture2D | ValueType::Texture3D | ValueType::ArrayTexture2D => 0,
127 ValueType::Struct { size, .. } => *size,
128 }
129 }
130
131 fn name(&self) -> &str {
132 match self {
133 ValueType::Bool => "bool",
134 ValueType::U8 => "u8",
135 ValueType::U16 => "u16",
136 ValueType::U32 => "u32",
137 ValueType::I32 => "i32",
138 ValueType::F32 => "f32",
139 ValueType::Vec2U16 => "vec2u16",
140 ValueType::Vec4U16 => "vec4u16",
141 ValueType::Vec2I => "vec2i",
142 ValueType::Vec3U => "vec3u",
143 ValueType::Vec2U => "vec2u",
144 ValueType::Vec4U => "vec4u",
145 ValueType::Vec2F => "vec2f",
146 ValueType::Vec3F => "vec3f",
147 ValueType::Vec4F => "vec4f",
148 ValueType::Mat4F => "mat4f",
149 ValueType::Mat4x3F => "mat4x3f",
150 ValueType::Texture2D => "Texture2D",
151 ValueType::Texture3D => "Texture3D",
152 ValueType::ArrayTexture2D => "ArrayTexture2D",
153 ValueType::Struct { name, .. } => name,
154 }
155 }
156
157 fn field(&self, name: &str) -> Option<&BufferMemberLayout> {
158 match self {
159 ValueType::Struct { fields, .. } => fields.iter().find(|field| field.name() == name),
160 _ => None,
161 }
162 }
163}
164
165#[derive(Clone, Debug, PartialEq, Eq)]
167pub struct BufferMemberLayout {
168 name: String,
169 offset: usize,
170 value_type: ValueType,
171 count: usize,
172}
173
174impl BufferMemberLayout {
175 pub fn name(&self) -> &str {
176 &self.name
177 }
178
179 pub const fn offset(&self) -> usize {
180 self.offset
181 }
182
183 pub fn value_type(&self) -> &ValueType {
184 &self.value_type
185 }
186
187 pub const fn count(&self) -> usize {
188 self.count
189 }
190
191 fn element_offset(&self, index: usize) -> Result<usize, VmError> {
192 if index >= self.count {
193 return Err(VmError::BufferArrayIndexOutOfBounds {
194 index,
195 count: self.count,
196 });
197 }
198 Ok(self.offset + self.value_type.size() * index)
199 }
200}
201
202#[derive(Clone, Debug, PartialEq, Eq)]
204pub struct BufferLayout {
205 members: Vec<BufferMemberLayout>,
206 size: usize,
207}
208
209impl BufferLayout {
210 pub fn members(&self) -> &[BufferMemberLayout] {
211 &self.members
212 }
213
214 pub const fn size(&self) -> usize {
215 self.size
216 }
217
218 fn member(&self, name: &str) -> Option<&BufferMemberLayout> {
219 self.members.iter().find(|member| member.name == name)
220 }
221}
222
223#[derive(Clone, Debug, PartialEq, Eq)]
225pub enum DescriptorLayout {
226 Buffer(BufferLayout),
227 Texture,
228 Image,
229 PushConstant(BufferLayout),
230}
231
232#[derive(Debug)]
234pub struct Buffer {
235 layout: BufferLayout,
236 data: Vec<u8>,
237}
238
239impl Buffer {
240 pub fn new(layout: BufferLayout) -> Self {
241 Self {
242 data: vec![0; layout.size()],
243 layout,
244 }
245 }
246
247 pub fn layout(&self) -> &BufferLayout {
248 &self.layout
249 }
250
251 pub fn bytes(&self) -> &[u8] {
252 &self.data
253 }
254
255 pub fn read(&self, member_name: &str) -> Result<Value, VmError> {
257 let member = self.member_layout(member_name)?;
258 if member.count() != 1 {
259 return Err(VmError::UnsupportedBufferLayout {
260 message: format!("Array member `{}` requires an element index", member_name),
261 });
262 }
263
264 self.read_value(member.offset, &member.value_type)
265 }
266
267 pub fn read_indexed(&self, member_name: &str, index: usize) -> Result<Value, VmError> {
269 let member = self.member_layout(member_name)?;
270 let offset = member.element_offset(index)?;
271 self.read_value(offset, member.value_type())
272 }
273
274 pub fn read_field(&self, member_name: &str, field_name: &str) -> Result<Value, VmError> {
276 let member = self.member_layout(member_name)?;
277 if member.count() != 1 {
278 return Err(VmError::UnsupportedBufferLayout {
279 message: format!("Array member `{}` requires an element index", member_name),
280 });
281 }
282 self.read_indexed_field(member_name, 0, field_name)
283 }
284
285 pub fn read_indexed_field(&self, member_name: &str, index: usize, field_name: &str) -> Result<Value, VmError> {
287 let member = self.member_layout(member_name)?;
288 let field = member
289 .value_type()
290 .field(field_name)
291 .ok_or_else(|| VmError::UnknownBufferMember {
292 member: format!("{}.{}", member_name, field_name),
293 })?;
294 let offset = member.element_offset(index)? + field.offset();
295 self.read_value(offset, field.value_type())
296 }
297
298 pub fn write(&mut self, member_name: &str, value: Value) -> Result<(), VmError> {
300 let (offset, value_type) = {
301 let member = self.member_layout(member_name)?;
302 if member.count() != 1 {
303 return Err(VmError::UnsupportedBufferLayout {
304 message: format!("Array member `{}` requires an element index", member_name),
305 });
306 }
307 (member.offset, member.value_type.clone())
308 };
309
310 self.write_value(offset, &value_type, &value)
311 }
312
313 pub fn write_indexed(&mut self, member_name: &str, index: usize, value: Value) -> Result<(), VmError> {
315 let (offset, value_type) = {
316 let member = self.member_layout(member_name)?;
317 (member.element_offset(index)?, member.value_type().clone())
318 };
319 self.write_value(offset, &value_type, &value)
320 }
321
322 pub fn write_field(&mut self, member_name: &str, field_name: &str, value: Value) -> Result<(), VmError> {
324 let member = self.member_layout(member_name)?;
325 if member.count() != 1 {
326 return Err(VmError::UnsupportedBufferLayout {
327 message: format!("Array member `{}` requires an element index", member_name),
328 });
329 }
330 self.write_indexed_field(member_name, 0, field_name, value)
331 }
332
333 pub fn write_indexed_field(
335 &mut self,
336 member_name: &str,
337 index: usize,
338 field_name: &str,
339 value: Value,
340 ) -> Result<(), VmError> {
341 let (offset, value_type) = {
342 let member = self.member_layout(member_name)?;
343 let field = member
344 .value_type()
345 .field(field_name)
346 .ok_or_else(|| VmError::UnknownBufferMember {
347 member: format!("{}.{}", member_name, field_name),
348 })?;
349 (member.element_offset(index)? + field.offset(), field.value_type().clone())
350 };
351 self.write_value(offset, &value_type, &value)
352 }
353
354 pub fn read_f32(&self, member_name: &str) -> Result<f32, VmError> {
356 match self.read(member_name)? {
357 Value::F32(value) => Ok(value),
358 value => Err(VmError::TypeMismatch {
359 expected: "f32".to_string(),
360 found: value.value_type().name().to_string(),
361 }),
362 }
363 }
364
365 fn read_value(&self, offset: usize, value_type: &ValueType) -> Result<Value, VmError> {
366 let bytes = self.read_bytes(offset, value_type.size())?;
367
368 let value = match value_type {
369 ValueType::Bool => Value::Bool(bytes[0] != 0),
370 ValueType::U8 => Value::U8(bytes[0]),
371 ValueType::U16 => Value::U16(u16::from_ne_bytes(bytes.try_into().expect("Invalid u16 byte count"))),
372 ValueType::U32 => Value::U32(u32::from_ne_bytes(bytes.try_into().expect("Invalid u32 byte count"))),
373 ValueType::I32 => Value::I32(i32::from_ne_bytes(bytes.try_into().expect("Invalid i32 byte count"))),
374 ValueType::F32 => Value::F32(f32::from_ne_bytes(bytes.try_into().expect("Invalid f32 byte count"))),
375 ValueType::Vec2U16 => Value::Vec2U16(read_u16_array::<2>(bytes)?),
376 ValueType::Vec4U16 => Value::Vec4U16(read_u16_array::<4>(bytes)?),
377 ValueType::Vec2I => Value::Vec2I(read_i32_array::<2>(bytes)?),
378 ValueType::Vec2U => Value::Vec2U(read_u32_array::<2>(bytes)?),
379 ValueType::Vec3U => Value::Vec3U(read_u32_array::<3>(bytes)?),
380 ValueType::Vec4U => Value::Vec4U(read_u32_array::<4>(bytes)?),
381 ValueType::Vec2F => Value::Vec2F(read_f32_array::<2>(bytes)?),
382 ValueType::Vec3F => Value::Vec3F(read_f32_array::<3>(bytes)?),
383 ValueType::Vec4F => Value::Vec4F(read_f32_array::<4>(bytes)?),
384 ValueType::Mat4F => Value::Mat4F(read_f32_array::<16>(bytes)?),
385 ValueType::Mat4x3F => Value::Mat4x3F(read_f32_array::<12>(bytes)?),
386 ValueType::Texture2D | ValueType::Texture3D | ValueType::ArrayTexture2D => {
387 return Err(VmError::UnsupportedBufferLayout {
388 message: "Resource handles cannot be stored in CPU buffer memory".to_string(),
389 });
390 }
391 ValueType::Struct { fields, .. } => {
392 let mut values = Vec::with_capacity(fields.len());
393 for field in fields {
394 values.push(self.read_value(offset + field.offset(), field.value_type())?);
395 }
396 Value::Struct {
397 value_type: value_type.clone(),
398 fields: values,
399 }
400 }
401 };
402
403 Ok(value)
404 }
405
406 fn write_value(&mut self, offset: usize, value_type: &ValueType, value: &Value) -> Result<(), VmError> {
407 if !value.matches_type(value_type) {
408 return Err(VmError::TypeMismatch {
409 expected: value_type.name().to_string(),
410 found: value.value_type().name().to_string(),
411 });
412 }
413
414 match value {
415 Value::Bool(value) => self.write_bytes(offset, &[u8::from(*value)]),
416 Value::U8(value) => self.write_bytes(offset, &value.to_ne_bytes()),
417 Value::U16(value) => self.write_bytes(offset, &value.to_ne_bytes()),
418 Value::U32(value) => self.write_bytes(offset, &value.to_ne_bytes()),
419 Value::I32(value) => self.write_bytes(offset, &value.to_ne_bytes()),
420 Value::F32(value) => self.write_bytes(offset, &value.to_ne_bytes()),
421 Value::Vec2U16(value) => write_u16_slice(self, offset, value),
422 Value::Vec4U16(value) => write_u16_slice(self, offset, value),
423 Value::Vec2I(value) => write_i32_slice(self, offset, value),
424 Value::Vec2U(value) => write_u32_slice(self, offset, value),
425 Value::Vec3U(value) => write_u32_slice(self, offset, value),
426 Value::Vec4U(value) => write_u32_slice(self, offset, value),
427 Value::Vec2F(value) => write_f32_slice(self, offset, value),
428 Value::Vec3F(value) => write_f32_slice(self, offset, value),
429 Value::Vec4F(value) => write_f32_slice(self, offset, value),
430 Value::Mat4F(value) => write_f32_slice(self, offset, value),
431 Value::Mat4x3F(value) => write_f32_slice(self, offset, value),
432 Value::Resource { .. } => Err(VmError::UnsupportedBufferLayout {
433 message: "Resource handles cannot be written into CPU buffer memory".to_string(),
434 }),
435 Value::Struct { fields, .. } => {
436 let ValueType::Struct {
437 fields: field_layouts, ..
438 } = value_type
439 else {
440 unreachable!("Struct values are validated before writing")
441 };
442 for (field, field_layout) in fields.iter().zip(field_layouts) {
443 self.write_value(offset + field_layout.offset(), field_layout.value_type(), field)?;
444 }
445 Ok(())
446 }
447 }
448 }
449
450 fn read_bytes(&self, offset: usize, size: usize) -> Result<&[u8], VmError> {
451 self.data.get(offset..offset + size).ok_or(VmError::BufferAccessOutOfBounds {
452 offset,
453 size,
454 buffer_size: self.data.len(),
455 })
456 }
457
458 fn write_bytes(&mut self, offset: usize, bytes: &[u8]) -> Result<(), VmError> {
459 let buffer_size = self.data.len();
460 let slice = self
461 .data
462 .get_mut(offset..offset + bytes.len())
463 .ok_or(VmError::BufferAccessOutOfBounds {
464 offset,
465 size: bytes.len(),
466 buffer_size,
467 })?;
468
469 slice.copy_from_slice(bytes);
470
471 Ok(())
472 }
473
474 fn member_layout(&self, member_name: &str) -> Result<&BufferMemberLayout, VmError> {
475 self.layout.member(member_name).ok_or_else(|| VmError::UnknownBufferMember {
476 member: member_name.to_string(),
477 })
478 }
479}
480
481#[derive(Debug)]
483pub struct Texture {
484 width: u32,
485 height: u32,
486 depth: u32,
487 texels: Vec<Texel>,
488}
489
490#[derive(Clone, Copy, Debug)]
491enum Texel {
492 Zero,
493 Float([f32; 4]),
494 U32(u32),
495}
496
497impl Texel {
498 const fn kind(self) -> &'static str {
499 match self {
500 Self::Zero => "untyped zero",
501 Self::Float(_) => "float RGBA",
502 Self::U32(_) => "u32",
503 }
504 }
505
506 fn float(self) -> Result<[f32; 4], VmError> {
507 match self {
508 Self::Zero => Ok([0.0; 4]),
509 Self::Float(value) => Ok(value),
510 value => Err(VmError::TextureFormatMismatch {
511 expected: "float RGBA",
512 found: value.kind(),
513 }),
514 }
515 }
516
517 fn u32(self) -> Result<u32, VmError> {
518 match self {
519 Self::Zero => Ok(0),
520 Self::U32(value) => Ok(value),
521 value => Err(VmError::TextureFormatMismatch {
522 expected: "u32",
523 found: value.kind(),
524 }),
525 }
526 }
527}
528
529impl Texture {
530 pub fn new(width: u32, height: u32) -> Result<Self, VmError> {
531 Self::new_3d(width, height, 1)
532 }
533
534 pub fn new_3d(width: u32, height: u32, depth: u32) -> Result<Self, VmError> {
536 if width == 0 || height == 0 || depth == 0 {
537 return Err(VmError::InvalidTextureDimensions { width, height, depth });
538 }
539
540 let texel_count = (width as usize)
541 .checked_mul(height as usize)
542 .and_then(|area| area.checked_mul(depth as usize))
543 .ok_or(VmError::TextureTexelCountOverflow { width, height, depth })?;
544 texel_count
545 .checked_mul(std::mem::size_of::<Texel>())
546 .filter(|byte_count| *byte_count <= isize::MAX as usize)
547 .ok_or(VmError::TextureTexelCountOverflow { width, height, depth })?;
548
549 let mut texels = Vec::new();
551 texels
552 .try_reserve_exact(texel_count)
553 .map_err(|_| VmError::TextureTexelCountOverflow { width, height, depth })?;
554 texels.resize(texel_count, Texel::Zero);
555 Ok(Self {
556 width,
557 height,
558 depth,
559 texels,
560 })
561 }
562
563 pub fn write(&mut self, coord: [u32; 2], value: [f32; 4]) -> Result<(), VmError> {
564 let index = self.texel_index([coord[0], coord[1], 0])?;
565 self.texels[index] = Texel::Float(value);
566 Ok(())
567 }
568
569 pub fn write_3d(&mut self, coord: [u32; 3], value: [f32; 4]) -> Result<(), VmError> {
571 let index = self.texel_index(coord)?;
572 self.texels[index] = Texel::Float(value);
573 Ok(())
574 }
575
576 pub fn write_u32(&mut self, coord: [u32; 2], value: u32) -> Result<(), VmError> {
578 let index = self.texel_index([coord[0], coord[1], 0])?;
579 self.texels[index] = Texel::U32(value);
580 Ok(())
581 }
582
583 pub fn fetch(&self, coord: [u32; 2]) -> Result<Value, VmError> {
585 Ok(Value::Vec4F(self.fetch_texel([coord[0], coord[1], 0])?))
586 }
587
588 pub fn fetch_u32(&self, coord: [u32; 2]) -> Result<Value, VmError> {
590 let index = self.texel_index([coord[0], coord[1], 0])?;
591 Ok(Value::U32(self.texels[index].u32()?))
592 }
593
594 pub fn sample(&self, uv: [f32; 2]) -> Result<Value, VmError> {
596 let (x0, x1, tx) = normalized_linear_axis(uv[0], self.width);
597 let (y0, y1, ty) = normalized_linear_axis(uv[1], self.height);
598
599 let top = lerp_rgba(self.fetch_texel([x0, y0, 0])?, self.fetch_texel([x1, y0, 0])?, tx);
600 let bottom = lerp_rgba(self.fetch_texel([x0, y1, 0])?, self.fetch_texel([x1, y1, 0])?, tx);
601
602 Ok(Value::Vec4F(lerp_rgba(top, bottom, ty)))
603 }
604
605 pub fn sample_3d(&self, uvw: [f32; 3]) -> Result<Value, VmError> {
607 let x = normalized_linear_axis(uvw[0], self.width);
608 let y = normalized_linear_axis(uvw[1], self.height);
609 let z = normalized_linear_axis(uvw[2], self.depth);
610 let low = [x.0, y.0, z.0];
611 let high = [x.1, y.1, z.1];
612 let factor = [x.2, y.2, z.2];
613 let low_plane = lerp_rgba(
614 lerp_rgba(
615 self.fetch_texel([low[0], low[1], low[2]])?,
616 self.fetch_texel([high[0], low[1], low[2]])?,
617 factor[0],
618 ),
619 lerp_rgba(
620 self.fetch_texel([low[0], high[1], low[2]])?,
621 self.fetch_texel([high[0], high[1], low[2]])?,
622 factor[0],
623 ),
624 factor[1],
625 );
626 let high_plane = lerp_rgba(
627 lerp_rgba(
628 self.fetch_texel([low[0], low[1], high[2]])?,
629 self.fetch_texel([high[0], low[1], high[2]])?,
630 factor[0],
631 ),
632 lerp_rgba(
633 self.fetch_texel([low[0], high[1], high[2]])?,
634 self.fetch_texel([high[0], high[1], high[2]])?,
635 factor[0],
636 ),
637 factor[1],
638 );
639 Ok(Value::Vec4F(lerp_rgba(low_plane, high_plane, factor[2])))
640 }
641
642 fn fetch_texel(&self, coord: [u32; 3]) -> Result<[f32; 4], VmError> {
643 let index = self.texel_index(coord)?;
644 self.texels[index].float()
645 }
646
647 fn texel_index(&self, coord: [u32; 3]) -> Result<usize, VmError> {
648 let [x, y, z] = coord;
649 if x >= self.width || y >= self.height || z >= self.depth {
650 return Err(VmError::TextureAccessOutOfBounds {
651 x,
652 y,
653 z,
654 width: self.width,
655 height: self.height,
656 depth: self.depth,
657 });
658 }
659
660 Ok(((z as usize) * self.height as usize + y as usize) * self.width as usize + x as usize)
661 }
662
663 fn contains_2d(&self, coord: [u32; 2]) -> bool {
664 coord[0] < self.width && coord[1] < self.height
665 }
666
667 fn atomic_or(&mut self, coord: [u32; 2], value: u32) -> Result<u32, VmError> {
668 let index = self.texel_index([coord[0], coord[1], 0])?;
669 let previous = self.texels[index].u32()?;
670 let updated = previous | value;
671 self.texels[index] = Texel::U32(updated);
672 Ok(previous)
673 }
674}
675
676enum DescriptorBinding<'a> {
677 Buffer(&'a mut Buffer),
678 Texture(&'a mut Texture),
679 Image(&'a mut Texture),
680}
681
682impl DescriptorBinding<'_> {
683 const fn kind(&self) -> &'static str {
684 match self {
685 Self::Buffer(_) => "buffer",
686 Self::Texture(_) => "texture",
687 Self::Image(_) => "image",
688 }
689 }
690
691 fn type_mismatch(&self, slot: ResourceSlot, expected: &'static str) -> VmError {
692 VmError::DescriptorTypeMismatch {
693 slot,
694 expected,
695 found: self.kind(),
696 }
697 }
698}
699
700#[derive(Clone, Debug, Default, PartialEq)]
702pub struct MeshOutputs {
703 vertex_count: u32,
704 primitive_count: u32,
705 vertex_positions: Vec<[f32; 4]>,
706 triangles: Vec<[u32; 3]>,
707}
708
709impl MeshOutputs {
710 pub fn new() -> Self {
712 Self::default()
713 }
714
715 pub const fn vertex_count(&self) -> u32 {
717 self.vertex_count
718 }
719
720 pub const fn primitive_count(&self) -> u32 {
722 self.primitive_count
723 }
724
725 pub fn vertex_position(&self, index: usize) -> Option<[f32; 4]> {
727 self.vertex_positions.get(index).copied()
728 }
729
730 pub fn triangle(&self, index: usize) -> Option<[u32; 3]> {
732 self.triangles.get(index).copied()
733 }
734
735 fn set_counts(
737 &mut self,
738 vertex_count: u32,
739 primitive_count: u32,
740 max_vertex_count: u32,
741 max_primitive_count: u32,
742 clear: bool,
743 ) -> Result<(), VmError> {
744 if vertex_count > max_vertex_count {
745 return Err(VmError::MeshOutputCountLimitExceeded {
746 kind: "vertex",
747 requested: vertex_count,
748 limit: max_vertex_count,
749 });
750 }
751 if primitive_count > max_primitive_count {
752 return Err(VmError::MeshOutputCountLimitExceeded {
753 kind: "primitive",
754 requested: primitive_count,
755 limit: max_primitive_count,
756 });
757 }
758
759 if clear {
760 self.begin_invocation();
761 }
762 self.vertex_count = vertex_count;
763 self.primitive_count = primitive_count;
764 self.vertex_positions.resize(vertex_count as usize, [0.0; 4]);
765 self.triangles.resize(primitive_count as usize, [0; 3]);
766 Ok(())
767 }
768
769 fn begin_invocation(&mut self) {
770 self.vertex_positions.fill([0.0; 4]);
772 self.triangles.fill([0; 3]);
773 }
774}
775
776#[derive(Clone, Debug, Default, PartialEq)]
778pub struct TaskOutputs {
779 mesh_output_count: Option<u32>,
780 payloads: HashMap<String, Vec<Option<Value>>>,
781}
782
783impl TaskOutputs {
784 pub fn new() -> Self {
786 Self::default()
787 }
788
789 pub const fn mesh_output_count(&self) -> Option<u32> {
791 self.mesh_output_count
792 }
793
794 pub fn payload_value(&self, name: &str, index: usize) -> Option<&Value> {
796 self.payloads.get(name)?.get(index)?.as_ref()
797 }
798
799 fn set_mesh_output_count(&mut self, count: u32) {
800 self.mesh_output_count = Some(count);
801 let count = count as usize;
802 for payload in self.payloads.values_mut() {
803 if count < payload.len() {
804 payload[count..].fill(None);
806 }
807 }
808 }
809
810 fn begin_workgroup(&mut self) {
812 self.mesh_output_count = None;
813 for payload in self.payloads.values_mut() {
814 payload.fill(None);
815 }
816 }
817
818 fn write_payload(&mut self, name: &str, index: usize, count: usize, value: Value) -> Result<(), VmError> {
820 if index >= count {
821 return Err(VmError::TaskPayloadOutputIndexOutOfBounds {
822 name: name.to_string(),
823 index,
824 count,
825 });
826 }
827
828 let payload = if let Some(payload) = self.payloads.get_mut(name) {
829 payload
830 } else {
831 self.payloads.insert(name.to_string(), vec![None; count]);
832 self.payloads
833 .get_mut(name)
834 .expect(
835 "Missing inserted task payload. The most likely cause is that the payload map changed between insertion and lookup.",
836 )
837 };
838 if payload.len() != count {
839 payload.clear();
841 payload.resize(count, None);
842 }
843 payload[index] = Some(value);
844 Ok(())
845 }
846}
847
848#[derive(Clone, Debug, Default, PartialEq)]
850pub struct WorkgroupState {
851 values: HashMap<String, Option<Value>>,
852}
853
854impl WorkgroupState {
855 pub fn new() -> Self {
857 Self::default()
858 }
859
860 fn begin_workgroup(&mut self) {
862 for value in self.values.values_mut() {
863 *value = None;
864 }
865 }
866
867 fn load(&self, name: &str, value_type: &ValueType) -> Result<Value, VmError> {
869 let value = self
870 .values
871 .get(name)
872 .and_then(Option::as_ref)
873 .ok_or_else(|| VmError::UninitializedWorkgroupValue { name: name.to_string() })?;
874 if !value.matches_type(value_type) {
875 return Err(VmError::TypeMismatch {
876 expected: value_type.name().to_string(),
877 found: value.value_type().name().to_string(),
878 });
879 }
880 Ok(value.clone())
881 }
882
883 fn store(&mut self, name: &str, value_type: &ValueType, value: Value) -> Result<(), VmError> {
885 if !value.matches_type(value_type) {
886 return Err(VmError::TypeMismatch {
887 expected: value_type.name().to_string(),
888 found: value.value_type().name().to_string(),
889 });
890 }
891 if let Some(stored) = self.values.get_mut(name) {
892 *stored = Some(value);
893 } else {
894 self.values.insert(name.to_string(), Some(value));
895 }
896 Ok(())
897 }
898
899 fn atomic_add_u32(&mut self, name: &str, value: u32) -> Result<u32, VmError> {
901 let stored = self
902 .values
903 .get_mut(name)
904 .and_then(Option::as_mut)
905 .ok_or_else(|| VmError::UninitializedWorkgroupValue { name: name.to_string() })?;
906 let Value::U32(previous) = stored else {
907 return Err(VmError::TypeMismatch {
908 expected: ValueType::U32.name().to_string(),
909 found: stored.value_type().name().to_string(),
910 });
911 };
912 let previous = *previous;
913 *stored = Value::U32(previous.wrapping_add(value));
914 Ok(previous)
915 }
916}
917
918pub struct DescriptorBindings<'a> {
920 bindings: HashMap<ResourceSlot, DescriptorBinding<'a>>,
921 push_constant: Option<&'a mut Buffer>,
922 mesh_outputs: Option<&'a mut MeshOutputs>,
923 task_outputs: Option<&'a mut TaskOutputs>,
924 workgroup_state: Option<&'a mut WorkgroupState>,
925 task_payloads: HashMap<String, Vec<Value>>,
926}
927
928impl<'a> Default for DescriptorBindings<'a> {
929 fn default() -> Self {
930 Self::new()
931 }
932}
933
934impl<'a> DescriptorBindings<'a> {
935 pub fn new() -> Self {
936 Self {
937 bindings: HashMap::new(),
938 push_constant: None,
939 mesh_outputs: None,
940 task_outputs: None,
941 workgroup_state: None,
942 task_payloads: HashMap::new(),
943 }
944 }
945
946 pub fn bind_buffer(&mut self, slot: ResourceSlot, buffer: &'a mut Buffer) {
947 self.bindings.insert(slot, DescriptorBinding::Buffer(buffer));
948 }
949
950 pub fn bind_texture(&mut self, slot: ResourceSlot, texture: &'a mut Texture) {
951 self.bindings.insert(slot, DescriptorBinding::Texture(texture));
952 }
953
954 pub fn bind_image(&mut self, slot: ResourceSlot, image: &'a mut Texture) {
955 self.bindings.insert(slot, DescriptorBinding::Image(image));
956 }
957
958 pub fn bind_push_constant(&mut self, push_constant: &'a mut Buffer) {
959 self.push_constant = Some(push_constant);
960 }
961
962 pub fn bind_mesh_outputs(&mut self, mesh_outputs: &'a mut MeshOutputs) {
964 self.mesh_outputs = Some(mesh_outputs);
965 }
966
967 pub fn bind_task_outputs(&mut self, task_outputs: &'a mut TaskOutputs) {
969 self.task_outputs = Some(task_outputs);
970 }
971
972 pub fn bind_workgroup_state(&mut self, workgroup_state: &'a mut WorkgroupState) {
974 self.workgroup_state = Some(workgroup_state);
975 }
976
977 pub fn bind_task_payload(&mut self, name: impl Into<String>, values: impl IntoIterator<Item = Value>) {
981 self.task_payloads.insert(name.into(), values.into_iter().collect());
982 }
983
984 fn buffer_mut(&mut self, slot: ResourceSlot) -> Result<&mut Buffer, VmError> {
985 let descriptor = self.bindings.get_mut(&slot).ok_or(VmError::UnboundDescriptor { slot })?;
986
987 match descriptor {
988 DescriptorBinding::Buffer(buffer) => Ok(&mut **buffer),
989 descriptor => Err(descriptor.type_mismatch(slot, "buffer")),
990 }
991 }
992
993 fn texture_mut(&mut self, slot: ResourceSlot) -> Result<&mut Texture, VmError> {
994 let descriptor = self.bindings.get_mut(&slot).ok_or(VmError::UnboundDescriptor { slot })?;
995
996 match descriptor {
997 DescriptorBinding::Texture(texture) => Ok(&mut **texture),
998 descriptor => Err(descriptor.type_mismatch(slot, "texture")),
999 }
1000 }
1001
1002 fn image_mut(&mut self, slot: ResourceSlot) -> Result<&mut Texture, VmError> {
1003 let descriptor = self.bindings.get_mut(&slot).ok_or(VmError::UnboundDescriptor { slot })?;
1004
1005 match descriptor {
1006 DescriptorBinding::Image(image) => Ok(&mut **image),
1007 descriptor => Err(descriptor.type_mismatch(slot, "image")),
1008 }
1009 }
1010
1011 fn push_constant_mut(&mut self) -> Result<&mut Buffer, VmError> {
1012 self.push_constant.as_deref_mut().ok_or(VmError::MissingPushConstant)
1013 }
1014
1015 fn mesh_outputs_mut(&mut self) -> Result<&mut MeshOutputs, VmError> {
1016 self.mesh_outputs.as_deref_mut().ok_or(VmError::MissingMeshOutputs)
1017 }
1018
1019 fn task_outputs_mut(&mut self) -> Result<&mut TaskOutputs, VmError> {
1020 self.task_outputs.as_deref_mut().ok_or(VmError::MissingTaskOutputs)
1021 }
1022
1023 fn workgroup_state_mut(&mut self) -> Result<&mut WorkgroupState, VmError> {
1024 self.workgroup_state.as_deref_mut().ok_or(VmError::MissingWorkgroupState)
1025 }
1026
1027 fn begin_task_workgroup(&mut self) {
1029 if let Some(task_outputs) = self.task_outputs.as_deref_mut() {
1030 task_outputs.begin_workgroup();
1031 }
1032 if let Some(workgroup_state) = self.workgroup_state.as_deref_mut() {
1033 workgroup_state.begin_workgroup();
1034 }
1035 }
1036
1037 fn task_payload_value(&self, name: &str, index: usize) -> Result<Value, VmError> {
1038 let values = self
1039 .task_payloads
1040 .get(name)
1041 .ok_or_else(|| VmError::MissingTaskPayload { name: name.to_string() })?;
1042 values
1043 .get(index)
1044 .cloned()
1045 .ok_or_else(|| VmError::TaskPayloadIndexOutOfBounds {
1046 name: name.to_string(),
1047 index,
1048 count: values.len(),
1049 })
1050 }
1051}
1052
1053#[derive(Clone, Debug, Default)]
1055pub struct SpecializationValues {
1056 values: HashMap<String, Value>,
1057}
1058
1059impl SpecializationValues {
1060 pub fn new() -> Self {
1062 Self::default()
1063 }
1064
1065 pub fn set(&mut self, name: impl Into<String>, value: Value) -> Option<Value> {
1067 self.values.insert(name.into(), value)
1068 }
1069
1070 pub fn get(&self, name: &str) -> Option<&Value> {
1072 self.values.get(name)
1073 }
1074}
1075
1076#[derive(Clone, Debug, PartialEq, Eq)]
1078pub struct ExecutionConfig {
1079 instruction_limit: usize,
1080 call_depth_limit: usize,
1081 max_mesh_vertex_count: u32,
1082 max_mesh_primitive_count: u32,
1083 max_task_mesh_output_count: u32,
1084 thread_id: [u32; 2],
1085 thread_idx: u32,
1086 thread_position: u32,
1087 threadgroup_position: u32,
1088}
1089
1090impl Default for ExecutionConfig {
1091 fn default() -> Self {
1092 Self {
1093 instruction_limit: 1_000_000,
1094 call_depth_limit: 64,
1095 max_mesh_vertex_count: 256,
1096 max_mesh_primitive_count: 256,
1097 max_task_mesh_output_count: 256,
1098 thread_id: [0, 0],
1099 thread_idx: 0,
1100 thread_position: 0,
1101 threadgroup_position: 0,
1102 }
1103 }
1104}
1105
1106impl ExecutionConfig {
1107 pub fn new(instruction_limit: usize) -> Self {
1109 Self {
1110 instruction_limit,
1111 ..Self::default()
1112 }
1113 }
1114
1115 pub const fn instruction_limit(&self) -> usize {
1117 self.instruction_limit
1118 }
1119
1120 pub const fn call_depth_limit(&self) -> usize {
1122 self.call_depth_limit
1123 }
1124
1125 pub const fn max_mesh_vertex_count(&self) -> u32 {
1127 self.max_mesh_vertex_count
1128 }
1129
1130 pub const fn max_mesh_primitive_count(&self) -> u32 {
1132 self.max_mesh_primitive_count
1133 }
1134
1135 pub const fn max_task_mesh_output_count(&self) -> u32 {
1137 self.max_task_mesh_output_count
1138 }
1139
1140 pub const fn thread_id(&self) -> [u32; 2] {
1142 self.thread_id
1143 }
1144
1145 pub const fn thread_idx(&self) -> u32 {
1147 self.thread_idx
1148 }
1149
1150 pub const fn thread_position(&self) -> u32 {
1152 self.thread_position
1153 }
1154
1155 pub const fn threadgroup_position(&self) -> u32 {
1157 self.threadgroup_position
1158 }
1159
1160 pub fn with_call_depth_limit(mut self, limit: usize) -> Self {
1162 self.call_depth_limit = limit;
1163 self
1164 }
1165
1166 pub fn with_max_mesh_vertex_count(mut self, limit: u32) -> Self {
1168 self.max_mesh_vertex_count = limit;
1169 self
1170 }
1171
1172 pub fn with_max_mesh_primitive_count(mut self, limit: u32) -> Self {
1174 self.max_mesh_primitive_count = limit;
1175 self
1176 }
1177
1178 pub fn with_max_task_mesh_output_count(mut self, limit: u32) -> Self {
1180 self.max_task_mesh_output_count = limit;
1181 self
1182 }
1183
1184 pub fn with_thread_id(mut self, thread_id: [u32; 2]) -> Self {
1186 self.thread_id = thread_id;
1187 self
1188 }
1189
1190 pub fn with_thread_idx(mut self, thread_idx: u32) -> Self {
1192 self.thread_idx = thread_idx;
1193 self
1194 }
1195
1196 pub fn with_thread_position(mut self, position: u32) -> Self {
1198 self.thread_position = position;
1199 self
1200 }
1201
1202 pub fn with_threadgroup_position(mut self, position: u32) -> Self {
1204 self.threadgroup_position = position;
1205 self
1206 }
1207}
1208
1209struct ExecutionState<'a> {
1211 config: &'a ExecutionConfig,
1212 remaining_instructions: usize,
1213 call_depth: usize,
1214}
1215
1216impl<'a> ExecutionState<'a> {
1217 fn new(config: &'a ExecutionConfig) -> Self {
1218 Self {
1219 config,
1220 remaining_instructions: config.instruction_limit(),
1221 call_depth: 0,
1222 }
1223 }
1224
1225 fn consume_instruction(&mut self) -> Result<(), VmError> {
1226 if self.remaining_instructions == 0 {
1227 return Err(VmError::InstructionLimitExceeded {
1228 limit: self.config.instruction_limit(),
1229 });
1230 }
1231 self.remaining_instructions -= 1;
1232 Ok(())
1233 }
1234
1235 fn enter_call(&mut self) -> Result<(), VmError> {
1236 if self.call_depth >= self.config.call_depth_limit() {
1237 return Err(VmError::CallDepthLimitExceeded {
1238 limit: self.config.call_depth_limit(),
1239 });
1240 }
1241 self.call_depth += 1;
1242 Ok(())
1243 }
1244
1245 fn leave_call(&mut self) {
1246 self.call_depth -= 1;
1247 }
1248}
1249
1250pub struct ExecutableProgram {
1252 descriptor_layouts: HashMap<ResourceSlot, DescriptorLayout>,
1253 functions: Vec<ExecutableFunction>,
1254 main_function: usize,
1255}
1256
1257struct ExecutableFunction {
1259 instructions: Vec<Instruction>,
1260 local_types: Vec<ValueType>,
1261 register_count: usize,
1262 parameter_count: usize,
1263 return_type: Option<ValueType>,
1264}
1265
1266impl ExecutableProgram {
1267 #[allow(clippy::mutable_key_type)]
1269 pub fn compile(program: NodeReference) -> Result<Self, VmError> {
1270 Self::compile_with_specializations(program, &SpecializationValues::new())
1271 }
1272
1273 #[allow(clippy::mutable_key_type)]
1275 pub fn compile_with_specializations(
1276 program: NodeReference,
1277 specializations: &SpecializationValues,
1278 ) -> Result<Self, VmError> {
1279 compiler::compile(program, specializations)
1280 }
1281
1282 pub fn descriptor_layout(&self, slot: ResourceSlot) -> Option<&DescriptorLayout> {
1283 self.descriptor_layouts.get(&slot)
1284 }
1285
1286 pub fn buffer_layout(&self, slot: ResourceSlot) -> Option<&BufferLayout> {
1287 match self.descriptor_layouts.get(&slot) {
1288 Some(DescriptorLayout::Buffer(layout)) => Some(layout),
1289 Some(DescriptorLayout::Texture) => None,
1290 Some(DescriptorLayout::Image) => None,
1291 Some(DescriptorLayout::PushConstant(_)) => None,
1292 None => None,
1293 }
1294 }
1295
1296 pub fn push_constant_layout(&self) -> Option<&BufferLayout> {
1297 self.descriptor_layouts.values().find_map(|layout| match layout {
1298 DescriptorLayout::PushConstant(layout) => Some(layout),
1299 _ => None,
1300 })
1301 }
1302
1303 pub fn input_layout(&self, location: u8) -> Option<&BufferLayout> {
1304 self.buffer_layout(input_slot(location))
1305 }
1306
1307 pub fn output_layout(&self, location: u8) -> Option<&BufferLayout> {
1308 self.buffer_layout(output_slot(location))
1309 }
1310
1311 pub fn builtin_position_layout(&self) -> Option<&BufferLayout> {
1312 self.buffer_layout(builtin_position_slot())
1313 }
1314}
1315
1316#[derive(Clone, Debug, PartialEq)]
1318pub enum Value {
1319 Bool(bool),
1320 U8(u8),
1321 U16(u16),
1322 U32(u32),
1323 I32(i32),
1324 F32(f32),
1325 Vec2U16([u16; 2]),
1326 Vec4U16([u16; 4]),
1327 Vec2I([i32; 2]),
1328 Vec2U([u32; 2]),
1329 Vec3U([u32; 3]),
1330 Vec4U([u32; 4]),
1331 Vec2F([f32; 2]),
1332 Vec3F([f32; 3]),
1333 Vec4F([f32; 4]),
1334 Mat4F([f32; 16]),
1335 Mat4x3F([f32; 12]),
1336 Resource { slot: ResourceSlot, value_type: ValueType },
1337 Struct { value_type: ValueType, fields: Vec<Value> },
1338}
1339
1340impl Value {
1341 fn value_type(&self) -> ValueType {
1342 match self {
1343 Value::Bool(_) => ValueType::Bool,
1344 Value::U8(_) => ValueType::U8,
1345 Value::U16(_) => ValueType::U16,
1346 Value::U32(_) => ValueType::U32,
1347 Value::I32(_) => ValueType::I32,
1348 Value::F32(_) => ValueType::F32,
1349 Value::Vec2U16(_) => ValueType::Vec2U16,
1350 Value::Vec4U16(_) => ValueType::Vec4U16,
1351 Value::Vec2I(_) => ValueType::Vec2I,
1352 Value::Vec2U(_) => ValueType::Vec2U,
1353 Value::Vec3U(_) => ValueType::Vec3U,
1354 Value::Vec4U(_) => ValueType::Vec4U,
1355 Value::Vec2F(_) => ValueType::Vec2F,
1356 Value::Vec3F(_) => ValueType::Vec3F,
1357 Value::Vec4F(_) => ValueType::Vec4F,
1358 Value::Mat4F(_) => ValueType::Mat4F,
1359 Value::Mat4x3F(_) => ValueType::Mat4x3F,
1360 Value::Resource { value_type, .. } => value_type.clone(),
1361 Value::Struct { value_type, .. } => value_type.clone(),
1362 }
1363 }
1364
1365 fn matches_type(&self, expected: &ValueType) -> bool {
1366 match (self, expected) {
1367 (
1368 Value::Struct { value_type, fields },
1369 ValueType::Struct {
1370 fields: expected_fields, ..
1371 },
1372 ) => {
1373 value_type == expected
1374 && fields.len() == expected_fields.len()
1375 && fields
1376 .iter()
1377 .zip(expected_fields)
1378 .all(|(field, expected_field)| field.matches_type(expected_field.value_type()))
1379 }
1380 _ => self.value_type() == *expected,
1381 }
1382 }
1383}
1384
1385#[cfg(test)]
1386mod tests;