#[derive(Debug, PartialEq, Eq, Clone, Error)] #[non_exhaustive] pubenum PipelineCacheValidationError { #[error("The pipeline cache data was truncated")]
Truncated, #[error("The pipeline cache data was longer than recorded")] // TODO: Is it plausible that this would happen
Extended, #[error("The pipeline cache data was corrupted (e.g. the hash didn't match)")]
Corrupted, #[error("The pipeline cacha data was out of date and so cannot be safely used")]
Outdated, #[error("The cache data was created for a different device")]
DeviceMismatch, #[error("Pipeline cacha data was created for a future version of wgpu")]
Unsupported,
}
impl PipelineCacheValidationError { /// Could the error have been avoided? /// That is, is there a mistake in user code interacting with the cache pubfn was_avoidable(&self) -> bool { matchself {
PipelineCacheValidationError::DeviceMismatch => true,
PipelineCacheValidationError::Truncated
| PipelineCacheValidationError::Unsupported
| PipelineCacheValidationError::Extended // It's unusual, but not implausible, to be downgrading wgpu
| PipelineCacheValidationError::Outdated
| PipelineCacheValidationError::Corrupted => false,
}
}
}
/// Validate the data in a pipeline cache pubfn validate_pipeline_cache<'d>(
cache_data: &'d [u8],
adapter: &AdapterInfo,
validation_key: [u8; 16],
) -> Result<&'d [u8], PipelineCacheValidationError> { let adapter_key = adapter_key(adapter)?; let Some((header, remaining_data)) = PipelineCacheHeader::read(cache_data) else { return Err(PipelineCacheValidationError::Truncated);
}; if header.magic != MAGIC { return Err(PipelineCacheValidationError::Corrupted);
} if header.header_version != HEADER_VERSION { return Err(PipelineCacheValidationError::Outdated);
} if header.cache_abi != ABI { return Err(PipelineCacheValidationError::Outdated);
} if header.backend != adapter.backend as u8 { return Err(PipelineCacheValidationError::DeviceMismatch);
} if header.adapter_key != adapter_key { return Err(PipelineCacheValidationError::DeviceMismatch);
} if header.validation_key != validation_key { // If the validation key is wrong, that means that this device has changed // in a way where the cache won't be compatible since the cache was made, // so it is outdated return Err(PipelineCacheValidationError::Outdated);
} let data_size: usize = header
.data_size
.try_into() // If the data was previously more than 4GiB, and we're still on a 32 bit system (ABI check, above) // Then the data must be corrupted
.map_err(|_| PipelineCacheValidationError::Corrupted)?; if remaining_data.len() < data_size { return Err(PipelineCacheValidationError::Truncated);
} if remaining_data.len() > data_size { return Err(PipelineCacheValidationError::Extended);
} if header.hash_space != HASH_SPACE_VALUE { return Err(PipelineCacheValidationError::Corrupted);
}
Ok(remaining_data)
}
pubfn add_cache_header(
in_region: &mut [u8],
data: &[u8],
adapter: &AdapterInfo,
validation_key: [u8; 16],
) {
assert_eq!(in_region.len(), HEADER_LENGTH); let header = PipelineCacheHeader {
adapter_key: adapter_key(adapter)
.expect("Called add_cache_header for an adapter which doesn't support cache data. This is a wgpu internal bug"),
backend: adapter.backend as u8,
cache_abi: ABI,
magic: MAGIC,
header_version: HEADER_VERSION,
validation_key,
hash_space: HASH_SPACE_VALUE,
data_size: data
.len()
.try_into()
.expect("Cache larger than u64::MAX bytes"),
};
header.write(in_region);
}
/// The value used to fill [`PipelineCacheHeader::hash_space`] /// /// If we receive reports of pipeline cache data corruption which is not otherwise caught /// on a real device, it would be worth modifying this /// /// Note that wgpu does not protect against malicious writes to e.g. a file used /// to store a pipeline cache. /// That is the resonsibility of the end application, such as by using a /// private space. const HASH_SPACE_VALUE: u64 = 0xFEDCBA9_876543210;
#[repr(C)] #[derive(PartialEq, Eq)] struct PipelineCacheHeader { /// The magic header to ensure that we have the right file format /// Has a value of MAGIC, as above
magic: [u8; 8], // /// The total size of this header, in bytes // header_size: u32, /// The version of this wgpu header /// Should be equal to HEADER_VERSION above /// /// This must always be the second item, after the value above
header_version: u32, /// The number of bytes in the pointers of this ABI, because some drivers /// have previously not distinguished between their 32 bit and 64 bit drivers /// leading to Vulkan data corruption
cache_abi: u32, /// The id for the backend in use, from [wgt::Backend]
backend: u8, /// The key which identifiers the device/adapter. /// This is used to validate that this pipeline cache (probably) was produced for /// the expected device. /// On Vulkan: it is a combination of vendor ID and device ID
adapter_key: [u8; 15], /// A key used to validate that this device is still compatible with the cache /// /// This should e.g. contain driver version and/or intermediate compiler versions
validation_key: [u8; 16], /// The length of the data which is sent to/recieved from the backend
data_size: u64, /// Space reserved for a hash of the data in future /// /// We assume that your cache storage system will be relatively robust, and so /// do not validate this hash /// /// Therefore, this will always have a value of [`HASH_SPACE_VALUE`]
hash_space: u64,
}
impl PipelineCacheHeader { fn read(data: &[u8]) -> Option<(PipelineCacheHeader, &[u8])> { letmut reader = Reader {
data,
total_read: 0,
}; let magic = reader.read_array()?; let header_version = reader.read_u32()?; let cache_abi = reader.read_u32()?; let backend = reader.read_byte()?; let adapter_key = reader.read_array()?; let validation_key = reader.read_array()?; let data_size = reader.read_u64()?; let data_hash = reader.read_u64()?;
fn adapter_key(adapter: &AdapterInfo) -> Result<[u8; 15], PipelineCacheValidationError> { match adapter.backend {
wgt::Backend::Vulkan => { // If these change size, the header format needs to change // We set the type explicitly so this won't compile in that case let v: [u8; 4] = adapter.vendor.to_be_bytes(); let d: [u8; 4] = adapter.device.to_be_bytes(); let adapter = [ 255, 255, 255, v[0], v[1], v[2], v[3], d[0], d[1], d[2], d[3], 255, 255, 255, 255,
];
Ok(adapter)
}
_ => Err(PipelineCacheValidationError::Unsupported),
}
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.