1 // Copyright (c) 2016 The vulkano developers 2 // Licensed under the Apache License, Version 2.0 3 // <LICENSE-APACHE or 4 // https://www.apache.org/licenses/LICENSE-2.0> or the MIT 5 // license <LICENSE-MIT or https://opensource.org/licenses/MIT>, 6 // at your option. All files in the project carrying such 7 // notice may not be copied, modified, or distributed except 8 // according to those terms. 9 10 //! Image storage (1D, 2D, 3D, arrays, etc.) and image views. 11 //! 12 //! An *image* is a region of memory whose purpose is to store multi-dimensional data. Its 13 //! most common use is to store a 2D array of color pixels (in other words an *image* in 14 //! everyday language), but it can also be used to store arbitrary data. 15 //! 16 //! The advantage of using an image compared to a buffer is that the memory layout is optimized 17 //! for locality. When reading a specific pixel of an image, reading the nearby pixels is really 18 //! fast. Most implementations have hardware dedicated to reading from images if you access them 19 //! through a sampler. 20 //! 21 //! # Properties of an image 22 //! 23 //! # Images and image views 24 //! 25 //! There is a distinction between *images* and *image views*. As its name suggests, an image 26 //! view describes how the GPU must interpret the image. 27 //! 28 //! Transfer and memory operations operate on images themselves, while reading/writing an image 29 //! operates on image views. You can create multiple image views from the same image. 30 //! 31 //! # High-level wrappers 32 //! 33 //! In the vulkano library, an image is any object that implements the [`ImageAccess`] trait. You 34 //! can create a view by wrapping them in an [`ImageView`](crate::image::view::ImageView). 35 //! 36 //! Since the `ImageAccess` trait is low-level, you are encouraged to not implement it yourself but 37 //! instead use one of the provided implementations that are specialized depending on the way you 38 //! are going to use the image: 39 //! 40 //! - An `AttachmentImage` can be used when you want to draw to an image. 41 //! - An `ImmutableImage` stores data which never need be changed after the initial upload, 42 //! like a texture. 43 //! 44 //! # Low-level information 45 //! 46 //! To be written. 47 //! 48 49 pub use self::aspect::ImageAspect; 50 pub use self::aspect::ImageAspects; 51 pub use self::attachment::AttachmentImage; 52 pub use self::immutable::ImmutableImage; 53 pub use self::layout::ImageDescriptorLayouts; 54 pub use self::layout::ImageLayout; 55 pub use self::storage::StorageImage; 56 pub use self::swapchain::SwapchainImage; 57 pub use self::sys::ImageCreationError; 58 pub use self::traits::ImageAccess; 59 pub use self::traits::ImageInner; 60 pub use self::usage::ImageUsage; 61 pub use self::view::ImageViewAbstract; 62 use std::cmp; 63 use std::convert::TryFrom; 64 65 mod aspect; 66 pub mod attachment; // TODO: make private 67 pub mod immutable; // TODO: make private 68 mod layout; 69 mod storage; 70 pub mod swapchain; // TODO: make private 71 pub mod sys; 72 pub mod traits; 73 mod usage; 74 pub mod view; 75 76 #[derive(Clone, Copy, Debug, PartialEq, Eq)] 77 #[repr(u32)] 78 pub enum SampleCount { 79 Sample1 = ash::vk::SampleCountFlags::TYPE_1.as_raw(), 80 Sample2 = ash::vk::SampleCountFlags::TYPE_2.as_raw(), 81 Sample4 = ash::vk::SampleCountFlags::TYPE_4.as_raw(), 82 Sample8 = ash::vk::SampleCountFlags::TYPE_8.as_raw(), 83 Sample16 = ash::vk::SampleCountFlags::TYPE_16.as_raw(), 84 Sample32 = ash::vk::SampleCountFlags::TYPE_32.as_raw(), 85 Sample64 = ash::vk::SampleCountFlags::TYPE_64.as_raw(), 86 } 87 88 impl From<SampleCount> for ash::vk::SampleCountFlags { 89 #[inline] from(val: SampleCount) -> Self90 fn from(val: SampleCount) -> Self { 91 Self::from_raw(val as u32) 92 } 93 } 94 95 impl TryFrom<ash::vk::SampleCountFlags> for SampleCount { 96 type Error = (); 97 98 #[inline] try_from(val: ash::vk::SampleCountFlags) -> Result<Self, Self::Error>99 fn try_from(val: ash::vk::SampleCountFlags) -> Result<Self, Self::Error> { 100 match val { 101 ash::vk::SampleCountFlags::TYPE_1 => Ok(Self::Sample1), 102 ash::vk::SampleCountFlags::TYPE_2 => Ok(Self::Sample2), 103 ash::vk::SampleCountFlags::TYPE_4 => Ok(Self::Sample4), 104 ash::vk::SampleCountFlags::TYPE_8 => Ok(Self::Sample8), 105 ash::vk::SampleCountFlags::TYPE_16 => Ok(Self::Sample16), 106 ash::vk::SampleCountFlags::TYPE_32 => Ok(Self::Sample32), 107 ash::vk::SampleCountFlags::TYPE_64 => Ok(Self::Sample64), 108 _ => Err(()), 109 } 110 } 111 } 112 113 impl TryFrom<u32> for SampleCount { 114 type Error = (); 115 116 #[inline] try_from(val: u32) -> Result<Self, Self::Error>117 fn try_from(val: u32) -> Result<Self, Self::Error> { 118 match val { 119 1 => Ok(Self::Sample1), 120 2 => Ok(Self::Sample2), 121 4 => Ok(Self::Sample4), 122 8 => Ok(Self::Sample8), 123 16 => Ok(Self::Sample16), 124 32 => Ok(Self::Sample32), 125 64 => Ok(Self::Sample64), 126 _ => Err(()), 127 } 128 } 129 } 130 131 /// Specifies how many sample counts supported for an image used for storage operations. 132 #[derive(Debug, Copy, Clone, Default)] 133 pub struct SampleCounts { 134 // specify an image with one sample per pixel 135 pub sample1: bool, 136 // specify an image with 2 samples per pixel 137 pub sample2: bool, 138 // specify an image with 4 samples per pixel 139 pub sample4: bool, 140 // specify an image with 8 samples per pixel 141 pub sample8: bool, 142 // specify an image with 16 samples per pixel 143 pub sample16: bool, 144 // specify an image with 32 samples per pixel 145 pub sample32: bool, 146 // specify an image with 64 samples per pixel 147 pub sample64: bool, 148 } 149 150 impl From<ash::vk::SampleCountFlags> for SampleCounts { from(sample_counts: ash::vk::SampleCountFlags) -> SampleCounts151 fn from(sample_counts: ash::vk::SampleCountFlags) -> SampleCounts { 152 SampleCounts { 153 sample1: !(sample_counts & ash::vk::SampleCountFlags::TYPE_1).is_empty(), 154 sample2: !(sample_counts & ash::vk::SampleCountFlags::TYPE_2).is_empty(), 155 sample4: !(sample_counts & ash::vk::SampleCountFlags::TYPE_4).is_empty(), 156 sample8: !(sample_counts & ash::vk::SampleCountFlags::TYPE_8).is_empty(), 157 sample16: !(sample_counts & ash::vk::SampleCountFlags::TYPE_16).is_empty(), 158 sample32: !(sample_counts & ash::vk::SampleCountFlags::TYPE_32).is_empty(), 159 sample64: !(sample_counts & ash::vk::SampleCountFlags::TYPE_64).is_empty(), 160 } 161 } 162 } 163 164 impl From<SampleCounts> for ash::vk::SampleCountFlags { from(val: SampleCounts) -> ash::vk::SampleCountFlags165 fn from(val: SampleCounts) -> ash::vk::SampleCountFlags { 166 let mut sample_counts = ash::vk::SampleCountFlags::default(); 167 168 if val.sample1 { 169 sample_counts |= ash::vk::SampleCountFlags::TYPE_1; 170 } 171 if val.sample2 { 172 sample_counts |= ash::vk::SampleCountFlags::TYPE_2; 173 } 174 if val.sample4 { 175 sample_counts |= ash::vk::SampleCountFlags::TYPE_4; 176 } 177 if val.sample8 { 178 sample_counts |= ash::vk::SampleCountFlags::TYPE_8; 179 } 180 if val.sample16 { 181 sample_counts |= ash::vk::SampleCountFlags::TYPE_16; 182 } 183 if val.sample32 { 184 sample_counts |= ash::vk::SampleCountFlags::TYPE_32; 185 } 186 if val.sample64 { 187 sample_counts |= ash::vk::SampleCountFlags::TYPE_64; 188 } 189 190 sample_counts 191 } 192 } 193 194 /// Specifies how many mipmaps must be allocated. 195 /// 196 /// Note that at least one mipmap must be allocated, to store the main level of the image. 197 #[derive(Debug, Copy, Clone)] 198 pub enum MipmapsCount { 199 /// Allocates the number of mipmaps required to store all the mipmaps of the image where each 200 /// mipmap is half the dimensions of the previous level. Guaranteed to be always supported. 201 /// 202 /// Note that this is not necessarily the maximum number of mipmaps, as the Vulkan 203 /// implementation may report that it supports a greater value. 204 Log2, 205 206 /// Allocate one mipmap (ie. just the main level). Always supported. 207 One, 208 209 /// Allocate the given number of mipmaps. May result in an error if the value is out of range 210 /// of what the implementation supports. 211 Specific(u32), 212 } 213 214 impl From<u32> for MipmapsCount { 215 #[inline] from(num: u32) -> MipmapsCount216 fn from(num: u32) -> MipmapsCount { 217 MipmapsCount::Specific(num) 218 } 219 } 220 221 /// Helper type for creating extents 222 #[derive(Debug, Copy, Clone)] 223 pub enum Extent { 224 E1D([u32; 1]), 225 E2D([u32; 2]), 226 E3D([u32; 3]), 227 } 228 229 impl From<ash::vk::Extent2D> for Extent { from(extent: ash::vk::Extent2D) -> Self230 fn from(extent: ash::vk::Extent2D) -> Self { 231 Extent::E2D([extent.width, extent.height]) 232 } 233 } 234 235 impl From<ash::vk::Extent3D> for Extent { from(extent: ash::vk::Extent3D) -> Self236 fn from(extent: ash::vk::Extent3D) -> Self { 237 Extent::E3D([extent.width, extent.height, extent.depth]) 238 } 239 } 240 impl TryFrom<Extent> for ash::vk::Extent2D { 241 type Error = (); 242 try_from(extent: Extent) -> Result<Self, Self::Error>243 fn try_from(extent: Extent) -> Result<Self, Self::Error> { 244 match extent { 245 Extent::E2D(a) => Ok(ash::vk::Extent2D { 246 width: a[0], 247 height: a[1], 248 }), 249 _ => Err(()), 250 } 251 } 252 } 253 254 impl TryFrom<Extent> for ash::vk::Extent3D { 255 type Error = (); 256 try_from(extent: Extent) -> Result<Self, Self::Error>257 fn try_from(extent: Extent) -> Result<Self, Self::Error> { 258 match extent { 259 Extent::E3D(a) => Ok(ash::vk::Extent3D { 260 width: a[0], 261 height: a[1], 262 depth: a[2], 263 }), 264 _ => Err(()), 265 } 266 } 267 } 268 269 /// Helper type returned from Device's `fn image_format_properties()` 270 pub struct ImageFormatProperties { 271 pub max_extent: Extent, 272 pub max_mip_levels: MipmapsCount, 273 pub max_array_layers: u32, 274 pub sample_counts: SampleCounts, 275 pub max_resource_size: usize, 276 } 277 278 impl From<ash::vk::ImageFormatProperties> for ImageFormatProperties { from(props: ash::vk::ImageFormatProperties) -> Self279 fn from(props: ash::vk::ImageFormatProperties) -> Self { 280 Self { 281 max_extent: props.max_extent.into(), 282 max_mip_levels: props.max_mip_levels.into(), 283 max_array_layers: props.max_array_layers, 284 sample_counts: props.sample_counts.into(), 285 max_resource_size: props.max_resource_size as usize, 286 } 287 } 288 } 289 290 #[derive(Copy, Clone, Debug, PartialEq, Eq, Default)] 291 pub struct ImageCreateFlags { 292 pub sparse_binding: bool, 293 pub sparse_residency: bool, 294 pub sparse_aliased: bool, 295 pub mutable_format: bool, 296 pub cube_compatible: bool, 297 pub array_2d_compatible: bool, 298 } 299 300 impl ImageCreateFlags { all() -> Self301 pub fn all() -> Self { 302 Self { 303 sparse_binding: true, 304 sparse_residency: true, 305 sparse_aliased: true, 306 mutable_format: true, 307 cube_compatible: true, 308 array_2d_compatible: true, 309 } 310 } 311 none() -> Self312 pub fn none() -> Self { 313 Self::default() 314 } 315 } 316 317 impl From<ImageCreateFlags> for ash::vk::ImageCreateFlags { from(flags: ImageCreateFlags) -> Self318 fn from(flags: ImageCreateFlags) -> Self { 319 let mut vk_flags = Self::default(); 320 if flags.sparse_binding { 321 vk_flags |= ash::vk::ImageCreateFlags::SPARSE_BINDING 322 }; 323 if flags.sparse_residency { 324 vk_flags |= ash::vk::ImageCreateFlags::SPARSE_RESIDENCY 325 }; 326 if flags.sparse_aliased { 327 vk_flags |= ash::vk::ImageCreateFlags::SPARSE_ALIASED 328 }; 329 if flags.mutable_format { 330 vk_flags |= ash::vk::ImageCreateFlags::MUTABLE_FORMAT 331 }; 332 if flags.cube_compatible { 333 vk_flags |= ash::vk::ImageCreateFlags::CUBE_COMPATIBLE 334 }; 335 if flags.array_2d_compatible { 336 vk_flags |= ash::vk::ImageCreateFlags::TYPE_2D_ARRAY_COMPATIBLE_KHR 337 }; 338 vk_flags 339 } 340 } 341 342 #[derive(Copy, Clone, Debug, PartialEq, Eq)] 343 #[repr(i32)] 344 pub enum ImageType { 345 Dim1d = ash::vk::ImageType::TYPE_1D.as_raw(), 346 Dim2d = ash::vk::ImageType::TYPE_2D.as_raw(), 347 Dim3d = ash::vk::ImageType::TYPE_3D.as_raw(), 348 } 349 impl From<ImageType> for ash::vk::ImageType { from(val: ImageType) -> Self350 fn from(val: ImageType) -> Self { 351 ash::vk::ImageType::from_raw(val as i32) 352 } 353 } 354 355 #[derive(Copy, Clone, Debug, PartialEq, Eq)] 356 #[repr(i32)] 357 pub enum ImageTiling { 358 Optimal = ash::vk::ImageTiling::OPTIMAL.as_raw(), 359 Linear = ash::vk::ImageTiling::LINEAR.as_raw(), 360 } 361 362 impl From<ImageTiling> for ash::vk::ImageTiling { from(val: ImageTiling) -> Self363 fn from(val: ImageTiling) -> Self { 364 ash::vk::ImageTiling::from_raw(val as i32) 365 } 366 } 367 368 /// The dimensions of an image. 369 #[derive(Copy, Clone, Debug, PartialEq, Eq)] 370 pub enum ImageDimensions { 371 Dim1d { 372 width: u32, 373 array_layers: u32, 374 }, 375 Dim2d { 376 width: u32, 377 height: u32, 378 array_layers: u32, 379 }, 380 Dim3d { 381 width: u32, 382 height: u32, 383 depth: u32, 384 }, 385 } 386 387 impl ImageDimensions { 388 #[inline] width(&self) -> u32389 pub fn width(&self) -> u32 { 390 match *self { 391 ImageDimensions::Dim1d { width, .. } => width, 392 ImageDimensions::Dim2d { width, .. } => width, 393 ImageDimensions::Dim3d { width, .. } => width, 394 } 395 } 396 397 #[inline] height(&self) -> u32398 pub fn height(&self) -> u32 { 399 match *self { 400 ImageDimensions::Dim1d { .. } => 1, 401 ImageDimensions::Dim2d { height, .. } => height, 402 ImageDimensions::Dim3d { height, .. } => height, 403 } 404 } 405 406 #[inline] width_height(&self) -> [u32; 2]407 pub fn width_height(&self) -> [u32; 2] { 408 [self.width(), self.height()] 409 } 410 411 #[inline] depth(&self) -> u32412 pub fn depth(&self) -> u32 { 413 match *self { 414 ImageDimensions::Dim1d { .. } => 1, 415 ImageDimensions::Dim2d { .. } => 1, 416 ImageDimensions::Dim3d { depth, .. } => depth, 417 } 418 } 419 420 #[inline] width_height_depth(&self) -> [u32; 3]421 pub fn width_height_depth(&self) -> [u32; 3] { 422 [self.width(), self.height(), self.depth()] 423 } 424 425 #[inline] array_layers(&self) -> u32426 pub fn array_layers(&self) -> u32 { 427 match *self { 428 ImageDimensions::Dim1d { array_layers, .. } => array_layers, 429 ImageDimensions::Dim2d { array_layers, .. } => array_layers, 430 ImageDimensions::Dim3d { .. } => 1, 431 } 432 } 433 434 /// Returns the total number of texels for an image of these dimensions. 435 #[inline] num_texels(&self) -> u32436 pub fn num_texels(&self) -> u32 { 437 self.width() * self.height() * self.depth() * self.array_layers() 438 } 439 440 /// Returns the maximum number of mipmaps for these image dimensions. 441 /// 442 /// The returned value is always at least superior or equal to 1. 443 /// 444 /// # Example 445 /// 446 /// ``` 447 /// use vulkano::image::ImageDimensions; 448 /// 449 /// let dims = ImageDimensions::Dim2d { 450 /// width: 32, 451 /// height: 50, 452 /// array_layers: 1, 453 /// }; 454 /// 455 /// assert_eq!(dims.max_mipmaps(), 6); 456 /// ``` 457 /// max_mipmaps(&self) -> u32458 pub fn max_mipmaps(&self) -> u32 { 459 32 - (self.width() | self.height() | self.depth()).leading_zeros() 460 } 461 462 /// Returns the dimensions of the `level`th mipmap level. If `level` is 0, then the dimensions 463 /// are left unchanged. 464 /// 465 /// Returns `None` if `level` is superior or equal to `max_mipmaps()`. 466 /// 467 /// # Example 468 /// 469 /// ``` 470 /// use vulkano::image::ImageDimensions; 471 /// 472 /// let dims = ImageDimensions::Dim2d { 473 /// width: 963, 474 /// height: 256, 475 /// array_layers: 1, 476 /// }; 477 /// 478 /// assert_eq!(dims.mipmap_dimensions(0), Some(dims)); 479 /// assert_eq!(dims.mipmap_dimensions(1), Some(ImageDimensions::Dim2d { 480 /// width: 481, 481 /// height: 128, 482 /// array_layers: 1, 483 /// })); 484 /// assert_eq!(dims.mipmap_dimensions(6), Some(ImageDimensions::Dim2d { 485 /// width: 15, 486 /// height: 4, 487 /// array_layers: 1, 488 /// })); 489 /// assert_eq!(dims.mipmap_dimensions(9), Some(ImageDimensions::Dim2d { 490 /// width: 1, 491 /// height: 1, 492 /// array_layers: 1, 493 /// })); 494 /// assert_eq!(dims.mipmap_dimensions(11), None); 495 /// ``` 496 /// 497 /// # Panic 498 /// 499 /// In debug mode, Panics if `width`, `height` or `depth` is equal to 0. In release, returns 500 /// an unspecified value. 501 /// mipmap_dimensions(&self, level: u32) -> Option<ImageDimensions>502 pub fn mipmap_dimensions(&self, level: u32) -> Option<ImageDimensions> { 503 if level == 0 { 504 return Some(*self); 505 } 506 507 if level >= self.max_mipmaps() { 508 return None; 509 } 510 511 Some(match *self { 512 ImageDimensions::Dim1d { 513 width, 514 array_layers, 515 } => { 516 debug_assert_ne!(width, 0); 517 ImageDimensions::Dim1d { 518 array_layers, 519 width: cmp::max(1, width >> level), 520 } 521 } 522 523 ImageDimensions::Dim2d { 524 width, 525 height, 526 array_layers, 527 } => { 528 debug_assert_ne!(width, 0); 529 debug_assert_ne!(height, 0); 530 ImageDimensions::Dim2d { 531 width: cmp::max(1, width >> level), 532 height: cmp::max(1, height >> level), 533 array_layers, 534 } 535 } 536 537 ImageDimensions::Dim3d { 538 width, 539 height, 540 depth, 541 } => { 542 debug_assert_ne!(width, 0); 543 debug_assert_ne!(height, 0); 544 ImageDimensions::Dim3d { 545 width: cmp::max(1, width >> level), 546 height: cmp::max(1, height >> level), 547 depth: cmp::max(1, depth >> level), 548 } 549 } 550 }) 551 } 552 } 553 554 #[cfg(test)] 555 mod tests { 556 use crate::format::Format; 557 use crate::image::ImageDimensions; 558 use crate::image::ImmutableImage; 559 use crate::image::MipmapsCount; 560 561 #[test] max_mipmaps()562 fn max_mipmaps() { 563 let dims = ImageDimensions::Dim2d { 564 width: 2, 565 height: 1, 566 array_layers: 1, 567 }; 568 assert_eq!(dims.max_mipmaps(), 2); 569 570 let dims = ImageDimensions::Dim2d { 571 width: 2, 572 height: 3, 573 array_layers: 1, 574 }; 575 assert_eq!(dims.max_mipmaps(), 2); 576 577 let dims = ImageDimensions::Dim2d { 578 width: 512, 579 height: 512, 580 array_layers: 1, 581 }; 582 assert_eq!(dims.max_mipmaps(), 10); 583 } 584 585 #[test] mipmap_dimensions()586 fn mipmap_dimensions() { 587 let dims = ImageDimensions::Dim2d { 588 width: 283, 589 height: 175, 590 array_layers: 1, 591 }; 592 assert_eq!(dims.mipmap_dimensions(0), Some(dims)); 593 assert_eq!( 594 dims.mipmap_dimensions(1), 595 Some(ImageDimensions::Dim2d { 596 width: 141, 597 height: 87, 598 array_layers: 1, 599 }) 600 ); 601 assert_eq!( 602 dims.mipmap_dimensions(2), 603 Some(ImageDimensions::Dim2d { 604 width: 70, 605 height: 43, 606 array_layers: 1, 607 }) 608 ); 609 assert_eq!( 610 dims.mipmap_dimensions(3), 611 Some(ImageDimensions::Dim2d { 612 width: 35, 613 height: 21, 614 array_layers: 1, 615 }) 616 ); 617 618 assert_eq!( 619 dims.mipmap_dimensions(4), 620 Some(ImageDimensions::Dim2d { 621 width: 17, 622 height: 10, 623 array_layers: 1, 624 }) 625 ); 626 assert_eq!( 627 dims.mipmap_dimensions(5), 628 Some(ImageDimensions::Dim2d { 629 width: 8, 630 height: 5, 631 array_layers: 1, 632 }) 633 ); 634 assert_eq!( 635 dims.mipmap_dimensions(6), 636 Some(ImageDimensions::Dim2d { 637 width: 4, 638 height: 2, 639 array_layers: 1, 640 }) 641 ); 642 assert_eq!( 643 dims.mipmap_dimensions(7), 644 Some(ImageDimensions::Dim2d { 645 width: 2, 646 height: 1, 647 array_layers: 1, 648 }) 649 ); 650 assert_eq!( 651 dims.mipmap_dimensions(8), 652 Some(ImageDimensions::Dim2d { 653 width: 1, 654 height: 1, 655 array_layers: 1, 656 }) 657 ); 658 assert_eq!(dims.mipmap_dimensions(9), None); 659 } 660 661 #[test] mipmap_working_immutable_image()662 fn mipmap_working_immutable_image() { 663 let (device, queue) = gfx_dev_and_queue!(); 664 665 let dimensions = ImageDimensions::Dim2d { 666 width: 512, 667 height: 512, 668 array_layers: 1, 669 }; 670 { 671 let mut vec = Vec::new(); 672 673 vec.resize(512 * 512, 0u8); 674 675 let (image, _) = ImmutableImage::from_iter( 676 vec.into_iter(), 677 dimensions, 678 MipmapsCount::One, 679 Format::R8Unorm, 680 queue.clone(), 681 ) 682 .unwrap(); 683 assert_eq!(image.mipmap_levels(), 1); 684 } 685 { 686 let mut vec = Vec::new(); 687 688 vec.resize(512 * 512, 0u8); 689 690 let (image, _) = ImmutableImage::from_iter( 691 vec.into_iter(), 692 dimensions, 693 MipmapsCount::Log2, 694 Format::R8Unorm, 695 queue.clone(), 696 ) 697 .unwrap(); 698 assert_eq!(image.mipmap_levels(), 10); 699 } 700 } 701 } 702