1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 use crate::{ 6 device::{ 7 descriptor::{DescriptorSet, DescriptorTotalCount}, 8 DeviceError, MissingFeatures, SHADER_STAGE_COUNT, 9 }, 10 hub::Resource, 11 id::{BindGroupLayoutId, BufferId, DeviceId, SamplerId, TextureViewId, Valid}, 12 memory_init_tracker::MemoryInitTrackerAction, 13 track::{TrackerSet, UsageConflict, DUMMY_SELECTOR}, 14 validation::{MissingBufferUsageError, MissingTextureUsageError}, 15 FastHashMap, Label, LifeGuard, MultiRefCount, Stored, MAX_BIND_GROUPS, 16 }; 17 18 use arrayvec::ArrayVec; 19 20 #[cfg(feature = "replay")] 21 use serde::Deserialize; 22 #[cfg(feature = "trace")] 23 use serde::Serialize; 24 25 use std::{ 26 borrow::{Borrow, Cow}, 27 ops::Range, 28 }; 29 30 use thiserror::Error; 31 32 #[derive(Clone, Debug, Error)] 33 pub enum BindGroupLayoutEntryError { 34 #[error("arrays of bindings unsupported for this type of binding")] 35 ArrayUnsupported, 36 #[error(transparent)] 37 MissingFeatures(#[from] MissingFeatures), 38 } 39 40 #[derive(Clone, Debug, Error)] 41 pub enum CreateBindGroupLayoutError { 42 #[error(transparent)] 43 Device(#[from] DeviceError), 44 #[error("conflicting binding at index {0}")] 45 ConflictBinding(u32), 46 #[error("binding {binding} entry is invalid")] 47 Entry { 48 binding: u32, 49 #[source] 50 error: BindGroupLayoutEntryError, 51 }, 52 #[error(transparent)] 53 TooManyBindings(BindingTypeMaxCountError), 54 } 55 56 //TODO: refactor this to move out `enum BindingError`. 57 58 #[derive(Clone, Debug, Error)] 59 pub enum CreateBindGroupError { 60 #[error(transparent)] 61 Device(#[from] DeviceError), 62 #[error("bind group layout is invalid")] 63 InvalidLayout, 64 #[error("buffer {0:?} is invalid or destroyed")] 65 InvalidBuffer(BufferId), 66 #[error("texture view {0:?} is invalid")] 67 InvalidTextureView(TextureViewId), 68 #[error("sampler {0:?} is invalid")] 69 InvalidSampler(SamplerId), 70 #[error("binding count declared with {expected} items, but {actual} items were provided")] 71 BindingArrayLengthMismatch { actual: usize, expected: usize }, 72 #[error("bound buffer range {range:?} does not fit in buffer of size {size}")] 73 BindingRangeTooLarge { 74 buffer: BufferId, 75 range: Range<wgt::BufferAddress>, 76 size: u64, 77 }, 78 #[error("buffer binding size {actual} is less than minimum {min}")] 79 BindingSizeTooSmall { 80 buffer: BufferId, 81 actual: u64, 82 min: u64, 83 }, 84 #[error("buffer binding size is zero")] 85 BindingZeroSize(BufferId), 86 #[error("number of bindings in bind group descriptor ({actual}) does not match the number of bindings defined in the bind group layout ({expected})")] 87 BindingsNumMismatch { actual: usize, expected: usize }, 88 #[error("binding {0} is used at least twice in the descriptor")] 89 DuplicateBinding(u32), 90 #[error("unable to find a corresponding declaration for the given binding {0}")] 91 MissingBindingDeclaration(u32), 92 #[error(transparent)] 93 MissingBufferUsage(#[from] MissingBufferUsageError), 94 #[error(transparent)] 95 MissingTextureUsage(#[from] MissingTextureUsageError), 96 #[error("binding declared as a single item, but bind group is using it as an array")] 97 SingleBindingExpected, 98 #[error("unable to create a bind group with a swap chain image")] 99 SwapChainImage, 100 #[error("buffer offset {0} does not respect `BIND_BUFFER_ALIGNMENT`")] 101 UnalignedBufferOffset(wgt::BufferAddress), 102 #[error( 103 "buffer binding {binding} range {given} exceeds `max_*_buffer_binding_size` limit {limit}" 104 )] 105 BufferRangeTooLarge { 106 binding: u32, 107 given: u32, 108 limit: u32, 109 }, 110 #[error("binding {binding} has a different type ({actual:?}) than the one in the layout ({expected:?})")] 111 WrongBindingType { 112 // Index of the binding 113 binding: u32, 114 // The type given to the function 115 actual: wgt::BindingType, 116 // Human-readable description of expected types 117 expected: &'static str, 118 }, 119 #[error("texture binding {binding} expects multisampled = {layout_multisampled}, but given a view with samples = {view_samples}")] 120 InvalidTextureMultisample { 121 binding: u32, 122 layout_multisampled: bool, 123 view_samples: u32, 124 }, 125 #[error("texture binding {binding} expects sample type = {layout_sample_type:?}, but given a view with format = {view_format:?}")] 126 InvalidTextureSampleType { 127 binding: u32, 128 layout_sample_type: wgt::TextureSampleType, 129 view_format: wgt::TextureFormat, 130 }, 131 #[error("texture binding {binding} expects dimension = {layout_dimension:?}, but given a view with dimension = {view_dimension:?}")] 132 InvalidTextureDimension { 133 binding: u32, 134 layout_dimension: wgt::TextureViewDimension, 135 view_dimension: wgt::TextureViewDimension, 136 }, 137 #[error("storage texture binding {binding} expects format = {layout_format:?}, but given a view with format = {view_format:?}")] 138 InvalidStorageTextureFormat { 139 binding: u32, 140 layout_format: wgt::TextureFormat, 141 view_format: wgt::TextureFormat, 142 }, 143 #[error("sampler binding {binding} expects comparison = {layout_cmp}, but given a sampler with comparison = {sampler_cmp}")] 144 WrongSamplerComparison { 145 binding: u32, 146 layout_cmp: bool, 147 sampler_cmp: bool, 148 }, 149 #[error("sampler binding {binding} expects filtering = {layout_flt}, but given a sampler with filtering = {sampler_flt}")] 150 WrongSamplerFiltering { 151 binding: u32, 152 layout_flt: bool, 153 sampler_flt: bool, 154 }, 155 #[error("bound texture views can not have both depth and stencil aspects enabled")] 156 DepthStencilAspect, 157 #[error("the adapter does not support simultaneous read + write storage texture access for the format {0:?}")] 158 StorageReadWriteNotSupported(wgt::TextureFormat), 159 #[error(transparent)] 160 ResourceUsageConflict(#[from] UsageConflict), 161 } 162 163 #[derive(Clone, Debug, Error)] 164 pub enum BindingZone { 165 #[error("stage {0:?}")] 166 Stage(wgt::ShaderStage), 167 #[error("whole pipeline")] 168 Pipeline, 169 } 170 171 #[derive(Clone, Debug, Error)] 172 #[error("too many bindings of type {kind:?} in {zone}, limit is {limit}, count was {count}")] 173 pub struct BindingTypeMaxCountError { 174 pub kind: BindingTypeMaxCountErrorKind, 175 pub zone: BindingZone, 176 pub limit: u32, 177 pub count: u32, 178 } 179 180 #[derive(Clone, Debug)] 181 pub enum BindingTypeMaxCountErrorKind { 182 DynamicUniformBuffers, 183 DynamicStorageBuffers, 184 SampledTextures, 185 Samplers, 186 StorageBuffers, 187 StorageTextures, 188 UniformBuffers, 189 } 190 191 #[derive(Debug, Default)] 192 pub(crate) struct PerStageBindingTypeCounter { 193 vertex: u32, 194 fragment: u32, 195 compute: u32, 196 } 197 198 impl PerStageBindingTypeCounter { add(&mut self, stage: wgt::ShaderStage, count: u32)199 pub(crate) fn add(&mut self, stage: wgt::ShaderStage, count: u32) { 200 if stage.contains(wgt::ShaderStage::VERTEX) { 201 self.vertex += count; 202 } 203 if stage.contains(wgt::ShaderStage::FRAGMENT) { 204 self.fragment += count; 205 } 206 if stage.contains(wgt::ShaderStage::COMPUTE) { 207 self.compute += count; 208 } 209 } 210 max(&self) -> (BindingZone, u32)211 pub(crate) fn max(&self) -> (BindingZone, u32) { 212 let max_value = self.vertex.max(self.fragment.max(self.compute)); 213 let mut stage = wgt::ShaderStage::NONE; 214 if max_value == self.vertex { 215 stage |= wgt::ShaderStage::VERTEX 216 } 217 if max_value == self.fragment { 218 stage |= wgt::ShaderStage::FRAGMENT 219 } 220 if max_value == self.compute { 221 stage |= wgt::ShaderStage::COMPUTE 222 } 223 (BindingZone::Stage(stage), max_value) 224 } 225 merge(&mut self, other: &Self)226 pub(crate) fn merge(&mut self, other: &Self) { 227 self.vertex = self.vertex.max(other.vertex); 228 self.fragment = self.fragment.max(other.fragment); 229 self.compute = self.compute.max(other.compute); 230 } 231 validate( &self, limit: u32, kind: BindingTypeMaxCountErrorKind, ) -> Result<(), BindingTypeMaxCountError>232 pub(crate) fn validate( 233 &self, 234 limit: u32, 235 kind: BindingTypeMaxCountErrorKind, 236 ) -> Result<(), BindingTypeMaxCountError> { 237 let (zone, count) = self.max(); 238 if limit < count { 239 Err(BindingTypeMaxCountError { 240 kind, 241 zone, 242 limit, 243 count, 244 }) 245 } else { 246 Ok(()) 247 } 248 } 249 } 250 251 #[derive(Debug, Default)] 252 pub(crate) struct BindingTypeMaxCountValidator { 253 dynamic_uniform_buffers: u32, 254 dynamic_storage_buffers: u32, 255 sampled_textures: PerStageBindingTypeCounter, 256 samplers: PerStageBindingTypeCounter, 257 storage_buffers: PerStageBindingTypeCounter, 258 storage_textures: PerStageBindingTypeCounter, 259 uniform_buffers: PerStageBindingTypeCounter, 260 } 261 262 impl BindingTypeMaxCountValidator { add_binding(&mut self, binding: &wgt::BindGroupLayoutEntry)263 pub(crate) fn add_binding(&mut self, binding: &wgt::BindGroupLayoutEntry) { 264 let count = binding.count.map_or(1, |count| count.get()); 265 match binding.ty { 266 wgt::BindingType::Buffer { 267 ty: wgt::BufferBindingType::Uniform, 268 has_dynamic_offset, 269 .. 270 } => { 271 self.uniform_buffers.add(binding.visibility, count); 272 if has_dynamic_offset { 273 self.dynamic_uniform_buffers += count; 274 } 275 } 276 wgt::BindingType::Buffer { 277 ty: wgt::BufferBindingType::Storage { .. }, 278 has_dynamic_offset, 279 .. 280 } => { 281 self.storage_buffers.add(binding.visibility, count); 282 if has_dynamic_offset { 283 self.dynamic_storage_buffers += count; 284 } 285 } 286 wgt::BindingType::Sampler { .. } => { 287 self.samplers.add(binding.visibility, count); 288 } 289 wgt::BindingType::Texture { .. } => { 290 self.sampled_textures.add(binding.visibility, count); 291 } 292 wgt::BindingType::StorageTexture { .. } => { 293 self.storage_textures.add(binding.visibility, count); 294 } 295 } 296 } 297 merge(&mut self, other: &Self)298 pub(crate) fn merge(&mut self, other: &Self) { 299 self.dynamic_uniform_buffers += other.dynamic_uniform_buffers; 300 self.dynamic_storage_buffers += other.dynamic_storage_buffers; 301 self.sampled_textures.merge(&other.sampled_textures); 302 self.samplers.merge(&other.samplers); 303 self.storage_buffers.merge(&other.storage_buffers); 304 self.storage_textures.merge(&other.storage_textures); 305 self.uniform_buffers.merge(&other.uniform_buffers); 306 } 307 validate(&self, limits: &wgt::Limits) -> Result<(), BindingTypeMaxCountError>308 pub(crate) fn validate(&self, limits: &wgt::Limits) -> Result<(), BindingTypeMaxCountError> { 309 if limits.max_dynamic_uniform_buffers_per_pipeline_layout < self.dynamic_uniform_buffers { 310 return Err(BindingTypeMaxCountError { 311 kind: BindingTypeMaxCountErrorKind::DynamicUniformBuffers, 312 zone: BindingZone::Pipeline, 313 limit: limits.max_dynamic_uniform_buffers_per_pipeline_layout, 314 count: self.dynamic_uniform_buffers, 315 }); 316 } 317 if limits.max_dynamic_storage_buffers_per_pipeline_layout < self.dynamic_storage_buffers { 318 return Err(BindingTypeMaxCountError { 319 kind: BindingTypeMaxCountErrorKind::DynamicStorageBuffers, 320 zone: BindingZone::Pipeline, 321 limit: limits.max_dynamic_storage_buffers_per_pipeline_layout, 322 count: self.dynamic_storage_buffers, 323 }); 324 } 325 self.sampled_textures.validate( 326 limits.max_sampled_textures_per_shader_stage, 327 BindingTypeMaxCountErrorKind::SampledTextures, 328 )?; 329 self.storage_buffers.validate( 330 limits.max_storage_buffers_per_shader_stage, 331 BindingTypeMaxCountErrorKind::StorageBuffers, 332 )?; 333 self.samplers.validate( 334 limits.max_samplers_per_shader_stage, 335 BindingTypeMaxCountErrorKind::Samplers, 336 )?; 337 self.storage_buffers.validate( 338 limits.max_storage_buffers_per_shader_stage, 339 BindingTypeMaxCountErrorKind::StorageBuffers, 340 )?; 341 self.storage_textures.validate( 342 limits.max_storage_textures_per_shader_stage, 343 BindingTypeMaxCountErrorKind::StorageTextures, 344 )?; 345 self.uniform_buffers.validate( 346 limits.max_uniform_buffers_per_shader_stage, 347 BindingTypeMaxCountErrorKind::UniformBuffers, 348 )?; 349 Ok(()) 350 } 351 } 352 353 /// Bindable resource and the slot to bind it to. 354 #[derive(Clone, Debug)] 355 #[cfg_attr(feature = "trace", derive(Serialize))] 356 #[cfg_attr(feature = "replay", derive(Deserialize))] 357 pub struct BindGroupEntry<'a> { 358 /// Slot for which binding provides resource. Corresponds to an entry of the same 359 /// binding index in the [`BindGroupLayoutDescriptor`]. 360 pub binding: u32, 361 /// Resource to attach to the binding 362 pub resource: BindingResource<'a>, 363 } 364 365 /// Describes a group of bindings and the resources to be bound. 366 #[derive(Clone, Debug)] 367 #[cfg_attr(feature = "trace", derive(Serialize))] 368 #[cfg_attr(feature = "replay", derive(Deserialize))] 369 pub struct BindGroupDescriptor<'a> { 370 /// Debug label of the bind group. This will show up in graphics debuggers for easy identification. 371 pub label: Label<'a>, 372 /// The [`BindGroupLayout`] that corresponds to this bind group. 373 pub layout: BindGroupLayoutId, 374 /// The resources to bind to this bind group. 375 pub entries: Cow<'a, [BindGroupEntry<'a>]>, 376 } 377 378 /// Describes a [`BindGroupLayout`]. 379 #[derive(Clone, Debug)] 380 #[cfg_attr(feature = "trace", derive(serde::Serialize))] 381 #[cfg_attr(feature = "replay", derive(serde::Deserialize))] 382 pub struct BindGroupLayoutDescriptor<'a> { 383 /// Debug label of the bind group layout. This will show up in graphics debuggers for easy identification. 384 pub label: Label<'a>, 385 /// Array of entries in this BindGroupLayout 386 pub entries: Cow<'a, [wgt::BindGroupLayoutEntry]>, 387 } 388 389 pub(crate) type BindEntryMap = FastHashMap<u32, wgt::BindGroupLayoutEntry>; 390 391 #[derive(Debug)] 392 pub struct BindGroupLayout<B: hal::Backend> { 393 pub(crate) raw: B::DescriptorSetLayout, 394 pub(crate) device_id: Stored<DeviceId>, 395 pub(crate) multi_ref_count: MultiRefCount, 396 pub(crate) entries: BindEntryMap, 397 pub(crate) desc_count: DescriptorTotalCount, 398 pub(crate) dynamic_count: usize, 399 pub(crate) count_validator: BindingTypeMaxCountValidator, 400 #[cfg(debug_assertions)] 401 pub(crate) label: String, 402 } 403 404 impl<B: hal::Backend> Resource for BindGroupLayout<B> { 405 const TYPE: &'static str = "BindGroupLayout"; 406 life_guard(&self) -> &LifeGuard407 fn life_guard(&self) -> &LifeGuard { 408 unreachable!() 409 } 410 label(&self) -> &str411 fn label(&self) -> &str { 412 #[cfg(debug_assertions)] 413 return &self.label; 414 #[cfg(not(debug_assertions))] 415 return ""; 416 } 417 } 418 419 #[derive(Clone, Debug, Error)] 420 pub enum CreatePipelineLayoutError { 421 #[error(transparent)] 422 Device(#[from] DeviceError), 423 #[error("bind group layout {0:?} is invalid")] 424 InvalidBindGroupLayout(BindGroupLayoutId), 425 #[error( 426 "push constant at index {index} has range bound {bound} not aligned to {}", 427 wgt::PUSH_CONSTANT_ALIGNMENT 428 )] 429 MisalignedPushConstantRange { index: usize, bound: u32 }, 430 #[error(transparent)] 431 MissingFeatures(#[from] MissingFeatures), 432 #[error("push constant range (index {index}) provides for stage(s) {provided:?} but there exists another range that provides stage(s) {intersected:?}. Each stage may only be provided by one range")] 433 MoreThanOnePushConstantRangePerStage { 434 index: usize, 435 provided: wgt::ShaderStage, 436 intersected: wgt::ShaderStage, 437 }, 438 #[error("push constant at index {index} has range {}..{} which exceeds device push constant size limit 0..{max}", range.start, range.end)] 439 PushConstantRangeTooLarge { 440 index: usize, 441 range: Range<u32>, 442 max: u32, 443 }, 444 #[error(transparent)] 445 TooManyBindings(BindingTypeMaxCountError), 446 #[error("bind group layout count {actual} exceeds device bind group limit {max}")] 447 TooManyGroups { actual: usize, max: usize }, 448 } 449 450 #[derive(Clone, Debug, Error)] 451 pub enum PushConstantUploadError { 452 #[error("provided push constant with indices {offset}..{end_offset} overruns matching push constant range at index {idx}, with stage(s) {:?} and indices {:?}", range.stages, range.range)] 453 TooLarge { 454 offset: u32, 455 end_offset: u32, 456 idx: usize, 457 range: wgt::PushConstantRange, 458 }, 459 #[error("provided push constant is for stage(s) {actual:?}, stage with a partial match found at index {idx} with stage(s) {matched:?}, however push constants must be complete matches")] 460 PartialRangeMatch { 461 actual: wgt::ShaderStage, 462 idx: usize, 463 matched: wgt::ShaderStage, 464 }, 465 #[error("provided push constant is for stage(s) {actual:?}, but intersects a push constant range (at index {idx}) with stage(s) {missing:?}. Push constants must provide the stages for all ranges they intersect")] 466 MissingStages { 467 actual: wgt::ShaderStage, 468 idx: usize, 469 missing: wgt::ShaderStage, 470 }, 471 #[error("provided push constant is for stage(s) {actual:?}, however the pipeline layout has no push constant range for the stage(s) {unmatched:?}")] 472 UnmatchedStages { 473 actual: wgt::ShaderStage, 474 unmatched: wgt::ShaderStage, 475 }, 476 #[error("provided push constant offset {0} does not respect `PUSH_CONSTANT_ALIGNMENT`")] 477 Unaligned(u32), 478 } 479 480 /// Describes a pipeline layout. 481 /// 482 /// A `PipelineLayoutDescriptor` can be used to create a pipeline layout. 483 #[derive(Clone, Debug, PartialEq, Eq, Hash)] 484 #[cfg_attr(feature = "trace", derive(Serialize))] 485 #[cfg_attr(feature = "replay", derive(Deserialize))] 486 pub struct PipelineLayoutDescriptor<'a> { 487 /// Debug label of the pipeine layout. This will show up in graphics debuggers for easy identification. 488 pub label: Label<'a>, 489 /// Bind groups that this pipeline uses. The first entry will provide all the bindings for 490 /// "set = 0", second entry will provide all the bindings for "set = 1" etc. 491 pub bind_group_layouts: Cow<'a, [BindGroupLayoutId]>, 492 /// Set of push constant ranges this pipeline uses. Each shader stage that uses push constants 493 /// must define the range in push constant memory that corresponds to its single `layout(push_constant)` 494 /// uniform block. 495 /// 496 /// If this array is non-empty, the [`Features::PUSH_CONSTANTS`](wgt::Features::PUSH_CONSTANTS) must be enabled. 497 pub push_constant_ranges: Cow<'a, [wgt::PushConstantRange]>, 498 } 499 500 #[derive(Debug)] 501 pub struct PipelineLayout<B: hal::Backend> { 502 pub(crate) raw: B::PipelineLayout, 503 pub(crate) device_id: Stored<DeviceId>, 504 pub(crate) life_guard: LifeGuard, 505 pub(crate) bind_group_layout_ids: ArrayVec<[Valid<BindGroupLayoutId>; MAX_BIND_GROUPS]>, 506 pub(crate) push_constant_ranges: ArrayVec<[wgt::PushConstantRange; SHADER_STAGE_COUNT]>, 507 } 508 509 impl<B: hal::Backend> PipelineLayout<B> { 510 /// Validate push constants match up with expected ranges. validate_push_constant_ranges( &self, stages: wgt::ShaderStage, offset: u32, end_offset: u32, ) -> Result<(), PushConstantUploadError>511 pub(crate) fn validate_push_constant_ranges( 512 &self, 513 stages: wgt::ShaderStage, 514 offset: u32, 515 end_offset: u32, 516 ) -> Result<(), PushConstantUploadError> { 517 // Don't need to validate size against the push constant size limit here, 518 // as push constant ranges are already validated to be within bounds, 519 // and we validate that they are within the ranges. 520 521 if offset % wgt::PUSH_CONSTANT_ALIGNMENT != 0 { 522 return Err(PushConstantUploadError::Unaligned(offset)); 523 } 524 525 // Push constant validation looks very complicated on the surface, but 526 // the problem can be range-reduced pretty well. 527 // 528 // Push constants require (summarized from the vulkan spec): 529 // 1. For each byte in the range and for each shader stage in stageFlags, 530 // there must be a push constant range in the layout that includes that 531 // byte and that stage. 532 // 2. For each byte in the range and for each push constant range that overlaps that byte, 533 // `stage` must include all stages in that push constant range’s `stage`. 534 // 535 // However there are some additional constraints that help us: 536 // 3. All push constant ranges are the only range that can access that stage. 537 // i.e. if one range has VERTEX, no other range has VERTEX 538 // 539 // Therefore we can simplify the checks in the following ways: 540 // - Because 3 guarantees that the push constant range has a unique stage, 541 // when we check for 1, we can simply check that our entire updated range 542 // is within a push constant range. i.e. our range for a specific stage cannot 543 // intersect more than one push constant range. 544 let mut used_stages = wgt::ShaderStage::NONE; 545 for (idx, range) in self.push_constant_ranges.iter().enumerate() { 546 // contains not intersects due to 2 547 if stages.contains(range.stages) { 548 if !(range.range.start <= offset && end_offset <= range.range.end) { 549 return Err(PushConstantUploadError::TooLarge { 550 offset, 551 end_offset, 552 idx, 553 range: range.clone(), 554 }); 555 } 556 used_stages |= range.stages; 557 } else if stages.intersects(range.stages) { 558 // Will be caught by used stages check below, but we can do this because of 1 559 // and is more helpful to the user. 560 return Err(PushConstantUploadError::PartialRangeMatch { 561 actual: stages, 562 idx, 563 matched: range.stages, 564 }); 565 } 566 567 // The push constant range intersects range we are uploading 568 if offset < range.range.end && range.range.start < end_offset { 569 // But requires stages we don't provide 570 if !stages.contains(range.stages) { 571 return Err(PushConstantUploadError::MissingStages { 572 actual: stages, 573 idx, 574 missing: stages, 575 }); 576 } 577 } 578 } 579 if used_stages != stages { 580 return Err(PushConstantUploadError::UnmatchedStages { 581 actual: stages, 582 unmatched: stages - used_stages, 583 }); 584 } 585 Ok(()) 586 } 587 } 588 589 impl<B: hal::Backend> Resource for PipelineLayout<B> { 590 const TYPE: &'static str = "PipelineLayout"; 591 life_guard(&self) -> &LifeGuard592 fn life_guard(&self) -> &LifeGuard { 593 &self.life_guard 594 } 595 } 596 597 #[repr(C)] 598 #[derive(Clone, Debug, Hash, PartialEq)] 599 #[cfg_attr(feature = "trace", derive(Serialize))] 600 #[cfg_attr(feature = "replay", derive(Deserialize))] 601 pub struct BufferBinding { 602 pub buffer_id: BufferId, 603 pub offset: wgt::BufferAddress, 604 pub size: Option<wgt::BufferSize>, 605 } 606 607 // Note: Duplicated in `wgpu-rs` as `BindingResource` 608 // They're different enough that it doesn't make sense to share a common type 609 #[derive(Debug, Clone)] 610 #[cfg_attr(feature = "trace", derive(serde::Serialize))] 611 #[cfg_attr(feature = "replay", derive(serde::Deserialize))] 612 pub enum BindingResource<'a> { 613 Buffer(BufferBinding), 614 BufferArray(Cow<'a, [BufferBinding]>), 615 Sampler(SamplerId), 616 TextureView(TextureViewId), 617 TextureViewArray(Cow<'a, [TextureViewId]>), 618 } 619 620 #[derive(Clone, Debug, Error)] 621 pub enum BindError { 622 #[error("number of dynamic offsets ({actual}) doesn't match the number of dynamic bindings in the bind group layout ({expected})")] 623 MismatchedDynamicOffsetCount { actual: usize, expected: usize }, 624 #[error( 625 "dynamic binding at index {idx}: offset {offset} does not respect `BIND_BUFFER_ALIGNMENT`" 626 )] 627 UnalignedDynamicBinding { idx: usize, offset: u32 }, 628 #[error("dynamic binding at index {idx} with offset {offset} would overrun the buffer (limit: {max})")] 629 DynamicBindingOutOfBounds { idx: usize, offset: u32, max: u64 }, 630 } 631 632 #[derive(Debug)] 633 pub struct BindGroupDynamicBindingData { 634 /// The maximum value the dynamic offset can have before running off the end of the buffer. 635 pub(crate) maximum_dynamic_offset: wgt::BufferAddress, 636 } 637 638 #[derive(Debug)] 639 pub struct BindGroup<B: hal::Backend> { 640 pub(crate) raw: DescriptorSet<B>, 641 pub(crate) device_id: Stored<DeviceId>, 642 pub(crate) layout_id: Valid<BindGroupLayoutId>, 643 pub(crate) life_guard: LifeGuard, 644 pub(crate) used: TrackerSet, 645 pub(crate) used_buffer_ranges: Vec<MemoryInitTrackerAction<BufferId>>, 646 pub(crate) dynamic_binding_info: Vec<BindGroupDynamicBindingData>, 647 } 648 649 impl<B: hal::Backend> BindGroup<B> { validate_dynamic_bindings( &self, offsets: &[wgt::DynamicOffset], ) -> Result<(), BindError>650 pub(crate) fn validate_dynamic_bindings( 651 &self, 652 offsets: &[wgt::DynamicOffset], 653 ) -> Result<(), BindError> { 654 if self.dynamic_binding_info.len() != offsets.len() { 655 return Err(BindError::MismatchedDynamicOffsetCount { 656 expected: self.dynamic_binding_info.len(), 657 actual: offsets.len(), 658 }); 659 } 660 661 for (idx, (info, &offset)) in self 662 .dynamic_binding_info 663 .iter() 664 .zip(offsets.iter()) 665 .enumerate() 666 { 667 if offset as wgt::BufferAddress % wgt::BIND_BUFFER_ALIGNMENT != 0 { 668 return Err(BindError::UnalignedDynamicBinding { idx, offset }); 669 } 670 671 if offset as wgt::BufferAddress > info.maximum_dynamic_offset { 672 return Err(BindError::DynamicBindingOutOfBounds { 673 idx, 674 offset, 675 max: info.maximum_dynamic_offset, 676 }); 677 } 678 } 679 680 Ok(()) 681 } 682 } 683 684 impl<B: hal::Backend> Borrow<()> for BindGroup<B> { borrow(&self) -> &()685 fn borrow(&self) -> &() { 686 &DUMMY_SELECTOR 687 } 688 } 689 690 impl<B: hal::Backend> Resource for BindGroup<B> { 691 const TYPE: &'static str = "BindGroup"; 692 life_guard(&self) -> &LifeGuard693 fn life_guard(&self) -> &LifeGuard { 694 &self.life_guard 695 } 696 } 697 698 #[derive(Clone, Debug, Error)] 699 pub enum GetBindGroupLayoutError { 700 #[error("pipeline is invalid")] 701 InvalidPipeline, 702 #[error("invalid group index {0}")] 703 InvalidGroupIndex(u32), 704 } 705