1 /*!
2 # D3D12 backend internals.
3
4 ## Resource transitions
5
6 Vulkan semantics for resource states doesn't exactly match D3D12.
7
8 For regular images, whenever there is a specific layout used,
9 we map it to a corresponding D3D12 resource state.
10
11 For the swapchain images, we consider them to be in COMMON state
12 everywhere except for render passes, where it's forcefully
13 transitioned into the render state. When transfers to/from are
14 requested, we transition them into and from the COPY_ states.
15
16 For buffers and images in General layout, we the best effort of guessing
17 the single mutable state based on the access flags. We can't reliably
18 handle a case where multiple mutable access flags are used.
19 */
20
21 #[macro_use]
22 extern crate bitflags;
23 #[macro_use]
24 extern crate log;
25
26 mod command;
27 mod conv;
28 mod descriptors_cpu;
29 mod device;
30 mod internal;
31 mod pool;
32 mod resource;
33 mod root_constants;
34 mod window;
35
36 use auxil::FastHashMap;
37 use hal::{
38 adapter, format as f, image, memory, pso::PipelineStage, queue as q, Features, Limits,
39 PhysicalDeviceProperties,
40 };
41 use range_alloc::RangeAllocator;
42
43 use parking_lot::{Mutex, RwLock};
44 use smallvec::SmallVec;
45 use winapi::{
46 shared::{dxgi, dxgi1_2, dxgi1_4, dxgi1_6, minwindef::TRUE, winerror},
47 um::{d3d12, d3d12sdklayers, handleapi, synchapi, winbase},
48 Interface,
49 };
50
51 use std::{
52 borrow::{Borrow, BorrowMut},
53 ffi::OsString,
54 fmt,
55 mem,
56 os::windows::ffi::OsStringExt,
57 //TODO: use parking_lot
58 sync::Arc,
59 };
60
61 use self::descriptors_cpu::DescriptorCpuPool;
62 use crate::resource::Image;
63
64 #[derive(Debug)]
65 pub(crate) struct HeapProperties {
66 pub page_property: d3d12::D3D12_CPU_PAGE_PROPERTY,
67 pub memory_pool: d3d12::D3D12_MEMORY_POOL,
68 }
69
70 // https://msdn.microsoft.com/de-de/library/windows/desktop/dn770377(v=vs.85).aspx
71 // Only 16 input slots allowed.
72 const MAX_VERTEX_BUFFERS: usize = 16;
73 const MAX_DESCRIPTOR_SETS: usize = 8;
74
75 const NUM_HEAP_PROPERTIES: usize = 3;
76
77 pub type DescriptorIndex = u64;
78
79 // Memory types are grouped according to the supported resources.
80 // Grouping is done to circumvent the limitations of heap tier 1 devices.
81 // Devices with Tier 1 will expose `BuffersOnly`, `ImageOnly` and `TargetOnly`.
82 // Devices with Tier 2 or higher will only expose `Universal`.
83 enum MemoryGroup {
84 Universal = 0,
85 BufferOnly,
86 ImageOnly,
87 TargetOnly,
88
89 NumGroups,
90 }
91
92 // https://msdn.microsoft.com/de-de/library/windows/desktop/dn788678(v=vs.85).aspx
93 static HEAPS_NUMA: [HeapProperties; NUM_HEAP_PROPERTIES] = [
94 // DEFAULT
95 HeapProperties {
96 page_property: d3d12::D3D12_CPU_PAGE_PROPERTY_NOT_AVAILABLE,
97 memory_pool: d3d12::D3D12_MEMORY_POOL_L1,
98 },
99 // UPLOAD
100 HeapProperties {
101 page_property: d3d12::D3D12_CPU_PAGE_PROPERTY_WRITE_COMBINE,
102 memory_pool: d3d12::D3D12_MEMORY_POOL_L0,
103 },
104 // READBACK
105 HeapProperties {
106 page_property: d3d12::D3D12_CPU_PAGE_PROPERTY_WRITE_BACK,
107 memory_pool: d3d12::D3D12_MEMORY_POOL_L0,
108 },
109 ];
110
111 static HEAPS_UMA: [HeapProperties; NUM_HEAP_PROPERTIES] = [
112 // DEFAULT
113 HeapProperties {
114 page_property: d3d12::D3D12_CPU_PAGE_PROPERTY_NOT_AVAILABLE,
115 memory_pool: d3d12::D3D12_MEMORY_POOL_L0,
116 },
117 // UPLOAD
118 HeapProperties {
119 page_property: d3d12::D3D12_CPU_PAGE_PROPERTY_WRITE_COMBINE,
120 memory_pool: d3d12::D3D12_MEMORY_POOL_L0,
121 },
122 // READBACK
123 HeapProperties {
124 page_property: d3d12::D3D12_CPU_PAGE_PROPERTY_WRITE_BACK,
125 memory_pool: d3d12::D3D12_MEMORY_POOL_L0,
126 },
127 ];
128
129 static HEAPS_CCUMA: [HeapProperties; NUM_HEAP_PROPERTIES] = [
130 // DEFAULT
131 HeapProperties {
132 page_property: d3d12::D3D12_CPU_PAGE_PROPERTY_NOT_AVAILABLE,
133 memory_pool: d3d12::D3D12_MEMORY_POOL_L0,
134 },
135 // UPLOAD
136 HeapProperties {
137 page_property: d3d12::D3D12_CPU_PAGE_PROPERTY_WRITE_BACK,
138 memory_pool: d3d12::D3D12_MEMORY_POOL_L0,
139 },
140 //READBACK
141 HeapProperties {
142 page_property: d3d12::D3D12_CPU_PAGE_PROPERTY_WRITE_BACK,
143 memory_pool: d3d12::D3D12_MEMORY_POOL_L0,
144 },
145 ];
146
147 #[derive(Debug, Copy, Clone)]
148 pub enum QueueFamily {
149 // Specially marked present queue.
150 // It's basically a normal 3D queue but D3D12 swapchain creation requires an
151 // associated queue, which we don't know on `create_swapchain`.
152 Present,
153 Normal(q::QueueType),
154 }
155
156 const MAX_QUEUES: usize = 16; // infinite, to be fair
157
158 impl q::QueueFamily for QueueFamily {
queue_type(&self) -> q::QueueType159 fn queue_type(&self) -> q::QueueType {
160 match *self {
161 QueueFamily::Present => q::QueueType::General,
162 QueueFamily::Normal(ty) => ty,
163 }
164 }
max_queues(&self) -> usize165 fn max_queues(&self) -> usize {
166 match *self {
167 QueueFamily::Present => 1,
168 QueueFamily::Normal(_) => MAX_QUEUES,
169 }
170 }
id(&self) -> q::QueueFamilyId171 fn id(&self) -> q::QueueFamilyId {
172 // This must match the order exposed by `QUEUE_FAMILIES`
173 q::QueueFamilyId(match *self {
174 QueueFamily::Present => 0,
175 QueueFamily::Normal(q::QueueType::General) => 1,
176 QueueFamily::Normal(q::QueueType::Compute) => 2,
177 QueueFamily::Normal(q::QueueType::Transfer) => 3,
178 _ => unreachable!(),
179 })
180 }
supports_sparse_binding(&self) -> bool181 fn supports_sparse_binding(&self) -> bool {
182 true
183 }
184 }
185
186 impl QueueFamily {
native_type(&self) -> native::CmdListType187 fn native_type(&self) -> native::CmdListType {
188 use hal::queue::QueueFamily as _;
189 use native::CmdListType as Clt;
190
191 let queue_type = self.queue_type();
192 match queue_type {
193 q::QueueType::General | q::QueueType::Graphics => Clt::Direct,
194 q::QueueType::Compute => Clt::Compute,
195 q::QueueType::Transfer => Clt::Copy,
196 }
197 }
198 }
199
200 static QUEUE_FAMILIES: [QueueFamily; 4] = [
201 QueueFamily::Present,
202 QueueFamily::Normal(q::QueueType::General),
203 QueueFamily::Normal(q::QueueType::Compute),
204 QueueFamily::Normal(q::QueueType::Transfer),
205 ];
206
207 #[derive(Default)]
208 struct Workarounds {
209 // On WARP, temporary CPU descriptors are still used by the runtime
210 // after we call `CopyDescriptors`.
211 avoid_cpu_descriptor_overwrites: bool,
212 }
213
214 //Note: fields are dropped in the order of declaration, so we put the
215 // most owning fields last.
216 pub struct PhysicalDevice {
217 features: Features,
218 properties: PhysicalDeviceProperties,
219 format_properties: Arc<FormatProperties>,
220 private_caps: PrivateCapabilities,
221 workarounds: Workarounds,
222 heap_properties: &'static [HeapProperties; NUM_HEAP_PROPERTIES],
223 memory_properties: adapter::MemoryProperties,
224 // Indicates that there is currently an active logical device.
225 // Opening the same adapter multiple times will return the same D3D12Device again.
226 is_open: Arc<Mutex<bool>>,
227 adapter: native::WeakPtr<dxgi1_2::IDXGIAdapter2>,
228 library: Arc<native::D3D12Lib>,
229 }
230
231 impl fmt::Debug for PhysicalDevice {
fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result232 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
233 fmt.write_str("PhysicalDevice")
234 }
235 }
236
237 unsafe impl Send for PhysicalDevice {}
238 unsafe impl Sync for PhysicalDevice {}
239
240 impl adapter::PhysicalDevice<Backend> for PhysicalDevice {
open( &self, families: &[(&QueueFamily, &[q::QueuePriority])], requested_features: Features, ) -> Result<adapter::Gpu<Backend>, hal::device::CreationError>241 unsafe fn open(
242 &self,
243 families: &[(&QueueFamily, &[q::QueuePriority])],
244 requested_features: Features,
245 ) -> Result<adapter::Gpu<Backend>, hal::device::CreationError> {
246 let mut open_guard = match self.is_open.try_lock() {
247 Some(inner) => inner,
248 None => return Err(hal::device::CreationError::TooManyObjects),
249 };
250
251 if !self.features().contains(requested_features) {
252 return Err(hal::device::CreationError::MissingFeature);
253 }
254
255 let device_raw = match self
256 .library
257 .create_device(self.adapter, native::FeatureLevel::L11_0)
258 {
259 Ok((device, hr)) if winerror::SUCCEEDED(hr) => device,
260 Ok((_, hr)) => {
261 error!("error on device creation: {:x}", hr);
262 return Err(hal::device::CreationError::InitializationFailed);
263 }
264 Err(e) => panic!("device creation failed with {:?}", e),
265 };
266
267 // Always create the presentation queue in case we want to build a swapchain.
268 let (present_queue, hr_queue) = device_raw.create_command_queue(
269 QueueFamily::Present.native_type(),
270 native::Priority::Normal,
271 native::CommandQueueFlags::empty(),
272 0,
273 );
274 if !winerror::SUCCEEDED(hr_queue) {
275 error!("error on queue creation: {:x}", hr_queue);
276 }
277
278 let mut device = Device::new(device_raw, &self, present_queue);
279 device.features = requested_features;
280
281 let queue_groups = families
282 .iter()
283 .map(|&(&family, priorities)| {
284 use hal::queue::QueueFamily as _;
285 let mut group = q::QueueGroup::new(family.id());
286
287 let create_idle_event = || native::Event::create(true, false);
288
289 match family {
290 QueueFamily::Present => {
291 // Exactly **one** present queue!
292 // Number of queues need to be larger than 0 else it
293 // violates the specification.
294 let queue = Queue {
295 raw: device.present_queue.clone(),
296 idle_fence: device.create_raw_fence(false),
297 idle_event: create_idle_event(),
298 };
299 device.append_queue(queue.clone());
300 group.add_queue(queue);
301 }
302 QueueFamily::Normal(_) => {
303 let list_type = family.native_type();
304 for _ in 0..priorities.len() {
305 let (queue, hr_queue) = device_raw.create_command_queue(
306 list_type,
307 native::Priority::Normal,
308 native::CommandQueueFlags::empty(),
309 0,
310 );
311
312 if winerror::SUCCEEDED(hr_queue) {
313 let queue = Queue {
314 raw: queue,
315 idle_fence: device.create_raw_fence(false),
316 idle_event: create_idle_event(),
317 };
318 device.append_queue(queue.clone());
319 group.add_queue(queue);
320 } else {
321 error!("error on queue creation: {:x}", hr_queue);
322 }
323 }
324 }
325 }
326
327 group
328 })
329 .collect();
330
331 *open_guard = true;
332
333 Ok(adapter::Gpu {
334 device,
335 queue_groups,
336 })
337 }
338
format_properties(&self, fmt: Option<f::Format>) -> f::Properties339 fn format_properties(&self, fmt: Option<f::Format>) -> f::Properties {
340 let idx = fmt.map(|fmt| fmt as usize).unwrap_or(0);
341 self.format_properties.resolve(idx).properties
342 }
343
image_format_properties( &self, format: f::Format, dimensions: u8, tiling: image::Tiling, usage: image::Usage, view_caps: image::ViewCapabilities, ) -> Option<image::FormatProperties>344 fn image_format_properties(
345 &self,
346 format: f::Format,
347 dimensions: u8,
348 tiling: image::Tiling,
349 usage: image::Usage,
350 view_caps: image::ViewCapabilities,
351 ) -> Option<image::FormatProperties> {
352 conv::map_format(format)?; //filter out unknown formats
353 let format_info = self.format_properties.resolve(format as usize);
354
355 let supported_usage = {
356 use hal::image::Usage as U;
357 let props = match tiling {
358 image::Tiling::Optimal => format_info.properties.optimal_tiling,
359 image::Tiling::Linear => format_info.properties.linear_tiling,
360 };
361 let mut flags = U::empty();
362 if props.contains(f::ImageFeature::TRANSFER_SRC) {
363 flags |= U::TRANSFER_SRC;
364 }
365 if props.contains(f::ImageFeature::TRANSFER_DST) {
366 flags |= U::TRANSFER_DST;
367 }
368 if props.contains(f::ImageFeature::SAMPLED) {
369 flags |= U::SAMPLED;
370 }
371 if props.contains(f::ImageFeature::STORAGE) {
372 flags |= U::STORAGE;
373 }
374 if props.contains(f::ImageFeature::COLOR_ATTACHMENT) {
375 flags |= U::COLOR_ATTACHMENT;
376 }
377 if props.contains(f::ImageFeature::DEPTH_STENCIL_ATTACHMENT) {
378 flags |= U::DEPTH_STENCIL_ATTACHMENT;
379 }
380 flags
381 };
382 if !supported_usage.contains(usage) {
383 return None;
384 }
385
386 let max_resource_size =
387 (d3d12::D3D12_REQ_RESOURCE_SIZE_IN_MEGABYTES_EXPRESSION_A_TERM as usize) << 20;
388 Some(match tiling {
389 image::Tiling::Optimal => image::FormatProperties {
390 max_extent: match dimensions {
391 1 => image::Extent {
392 width: d3d12::D3D12_REQ_TEXTURE1D_U_DIMENSION,
393 height: 1,
394 depth: 1,
395 },
396 2 => image::Extent {
397 width: d3d12::D3D12_REQ_TEXTURE2D_U_OR_V_DIMENSION,
398 height: d3d12::D3D12_REQ_TEXTURE2D_U_OR_V_DIMENSION,
399 depth: 1,
400 },
401 3 => image::Extent {
402 width: d3d12::D3D12_REQ_TEXTURE3D_U_V_OR_W_DIMENSION,
403 height: d3d12::D3D12_REQ_TEXTURE3D_U_V_OR_W_DIMENSION,
404 depth: d3d12::D3D12_REQ_TEXTURE3D_U_V_OR_W_DIMENSION,
405 },
406 _ => return None,
407 },
408 max_levels: d3d12::D3D12_REQ_MIP_LEVELS as _,
409 max_layers: match dimensions {
410 1 => d3d12::D3D12_REQ_TEXTURE1D_ARRAY_AXIS_DIMENSION as _,
411 2 => d3d12::D3D12_REQ_TEXTURE2D_ARRAY_AXIS_DIMENSION as _,
412 _ => return None,
413 },
414 sample_count_mask: if dimensions == 2
415 && !view_caps.contains(image::ViewCapabilities::KIND_CUBE)
416 && !usage.contains(image::Usage::STORAGE)
417 {
418 format_info.sample_count_mask
419 } else {
420 0x1
421 },
422 max_resource_size,
423 },
424 image::Tiling::Linear => image::FormatProperties {
425 max_extent: match dimensions {
426 2 => image::Extent {
427 width: d3d12::D3D12_REQ_TEXTURE2D_U_OR_V_DIMENSION,
428 height: d3d12::D3D12_REQ_TEXTURE2D_U_OR_V_DIMENSION,
429 depth: 1,
430 },
431 _ => return None,
432 },
433 max_levels: 1,
434 max_layers: 1,
435 sample_count_mask: 0x1,
436 max_resource_size,
437 },
438 })
439 }
440
memory_properties(&self) -> adapter::MemoryProperties441 fn memory_properties(&self) -> adapter::MemoryProperties {
442 self.memory_properties.clone()
443 }
444
features(&self) -> Features445 fn features(&self) -> Features {
446 self.features
447 }
448
properties(&self) -> PhysicalDeviceProperties449 fn properties(&self) -> PhysicalDeviceProperties {
450 self.properties
451 }
452 }
453
454 #[derive(Clone)]
455 pub struct Queue {
456 pub(crate) raw: native::CommandQueue,
457 idle_fence: native::Fence,
458 idle_event: native::Event,
459 }
460
461 impl fmt::Debug for Queue {
fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result462 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
463 fmt.write_str("Queue")
464 }
465 }
466
467 impl Queue {
destroy(&self)468 unsafe fn destroy(&self) {
469 handleapi::CloseHandle(self.idle_event.0);
470 self.idle_fence.destroy();
471 self.raw.destroy();
472 }
473
wait_idle_impl(&self) -> Result<(), hal::device::OutOfMemory>474 fn wait_idle_impl(&self) -> Result<(), hal::device::OutOfMemory> {
475 self.raw.signal(self.idle_fence, 1);
476 assert_eq!(
477 winerror::S_OK,
478 self.idle_fence.set_event_on_completion(self.idle_event, 1)
479 );
480
481 unsafe {
482 synchapi::WaitForSingleObject(self.idle_event.0, winbase::INFINITE);
483 }
484
485 Ok(())
486 }
487 }
488
489 unsafe impl Send for Queue {}
490 unsafe impl Sync for Queue {}
491
492 impl q::Queue<Backend> for Queue {
submit<'a, Ic, Iw, Is>( &mut self, command_buffers: Ic, _wait_semaphores: Iw, _signal_semaphores: Is, fence: Option<&mut resource::Fence>, ) where Ic: Iterator<Item = &'a command::CommandBuffer>, Iw: Iterator<Item = (&'a resource::Semaphore, PipelineStage)>, Is: Iterator<Item = &'a resource::Semaphore>,493 unsafe fn submit<'a, Ic, Iw, Is>(
494 &mut self,
495 command_buffers: Ic,
496 _wait_semaphores: Iw,
497 _signal_semaphores: Is,
498 fence: Option<&mut resource::Fence>,
499 ) where
500 Ic: Iterator<Item = &'a command::CommandBuffer>,
501 Iw: Iterator<Item = (&'a resource::Semaphore, PipelineStage)>,
502 Is: Iterator<Item = &'a resource::Semaphore>,
503 {
504 // Reset idle fence and event
505 // That's safe here due to exclusive access to the queue
506 self.idle_fence.signal(0);
507 synchapi::ResetEvent(self.idle_event.0);
508
509 // TODO: semaphores
510 let lists = command_buffers
511 .map(|cmd_buf| cmd_buf.as_raw_list())
512 .collect::<SmallVec<[_; 4]>>();
513 self.raw
514 .ExecuteCommandLists(lists.len() as _, lists.as_ptr());
515
516 if let Some(fence) = fence {
517 assert_eq!(winerror::S_OK, self.raw.Signal(fence.raw.as_mut_ptr(), 1));
518 }
519 }
520
bind_sparse<'a, Iw, Is, Ibi, Ib, Iii, Io, Ii>( &mut self, _wait_semaphores: Iw, _signal_semaphores: Is, _buffer_memory_binds: Ib, _image_opaque_memory_binds: Io, image_memory_binds: Ii, device: &Device, fence: Option<&resource::Fence>, ) where Ibi: Iterator<Item = &'a memory::SparseBind<&'a resource::Memory>>, Ib: Iterator<Item = (&'a mut resource::Buffer, Ibi)>, Iii: Iterator<Item = &'a memory::SparseImageBind<&'a resource::Memory>>, Io: Iterator<Item = (&'a mut resource::Image, Ibi)>, Ii: Iterator<Item = (&'a mut resource::Image, Iii)>, Iw: Iterator<Item = &'a resource::Semaphore>, Is: Iterator<Item = &'a resource::Semaphore>,521 unsafe fn bind_sparse<'a, Iw, Is, Ibi, Ib, Iii, Io, Ii>(
522 &mut self,
523 _wait_semaphores: Iw,
524 _signal_semaphores: Is,
525 _buffer_memory_binds: Ib,
526 _image_opaque_memory_binds: Io,
527 image_memory_binds: Ii,
528 device: &Device,
529 fence: Option<&resource::Fence>,
530 ) where
531 Ibi: Iterator<Item = &'a memory::SparseBind<&'a resource::Memory>>,
532 Ib: Iterator<Item = (&'a mut resource::Buffer, Ibi)>,
533 Iii: Iterator<Item = &'a memory::SparseImageBind<&'a resource::Memory>>,
534 Io: Iterator<Item = (&'a mut resource::Image, Ibi)>,
535 Ii: Iterator<Item = (&'a mut resource::Image, Iii)>,
536 Iw: Iterator<Item = &'a resource::Semaphore>,
537 Is: Iterator<Item = &'a resource::Semaphore>,
538 {
539 // Reset idle fence and event
540 // That's safe here due to exclusive access to the queue
541 self.idle_fence.signal(0);
542 synchapi::ResetEvent(self.idle_event.0);
543
544 // TODO: semaphores
545
546 for (image, binds) in image_memory_binds {
547 let image = image.borrow_mut();
548
549 let (bits, image_kind) = match image {
550 Image::Unbound(unbound) => (unbound.format.surface_desc().bits, unbound.kind),
551 Image::Bound(bound) => (bound.surface_type.desc().bits, bound.kind),
552 };
553 let block_size = match image_kind {
554 image::Kind::D1(_, _) => unimplemented!(),
555 image::Kind::D2(_, _, _, samples) => {
556 image::get_tile_size(image::TileKind::Flat(samples), bits)
557 }
558 image::Kind::D3(_, _, _) => image::get_tile_size(image::TileKind::Volume, bits),
559 };
560
561 // TODO avoid allocations
562 let mut resource_coords = Vec::new();
563 let mut region_sizes = Vec::new();
564 let mut range_flags = Vec::new();
565 let mut heap_range_start_offsets = Vec::new();
566 let mut range_tile_counts = Vec::new();
567
568 let mut heap: *mut d3d12::ID3D12Heap = std::ptr::null_mut();
569 for bind in binds {
570 resource_coords.push(d3d12::D3D12_TILED_RESOURCE_COORDINATE {
571 X: bind.offset.x as u32,
572 Y: bind.offset.y as u32,
573 Z: bind.offset.z as u32,
574 Subresource: image.calc_subresource(
575 bind.subresource.level as _,
576 bind.subresource.layer as _,
577 0,
578 ),
579 });
580
581 // Increment one tile if the extent is not a multiple of the block size
582 // Accessing these IS unsafe, but that is also true of Vulkan as the documentation
583 // requires an extent multiple of the block size.
584 let tile_extents = (
585 (bind.extent.width / block_size.0 as u32)
586 + ((bind.extent.width % block_size.0 as u32) != 0) as u32,
587 (bind.extent.height / block_size.1 as u32)
588 + ((bind.extent.height % block_size.1 as u32) != 0) as u32,
589 (bind.extent.depth / block_size.2 as u32)
590 + ((bind.extent.depth % block_size.2 as u32) != 0) as u32,
591 );
592 let number_tiles = tile_extents.0 * tile_extents.1 * tile_extents.2;
593 region_sizes.push(d3d12::D3D12_TILE_REGION_SIZE {
594 NumTiles: number_tiles,
595 UseBox: 1,
596 Width: tile_extents.0,
597 Height: tile_extents.1 as u16,
598 Depth: tile_extents.2 as u16,
599 });
600
601 if let Some((memory, memory_offset)) = bind.memory {
602 // TODO multiple heap support
603 // would involve multiple update tile mapping calls
604 if heap.is_null() {
605 heap = memory.borrow().heap.as_mut_ptr();
606 } else if cfg!(debug_assertions) {
607 debug_assert_eq!(heap, memory.borrow().heap.as_mut_ptr());
608 }
609 range_flags.push(d3d12::D3D12_TILE_RANGE_FLAG_NONE);
610 heap_range_start_offsets.push(memory_offset as u32);
611 } else {
612 range_flags.push(d3d12::D3D12_TILE_RANGE_FLAG_NULL);
613 heap_range_start_offsets.push(0);
614 }
615 range_tile_counts.push(number_tiles);
616 }
617
618 match image {
619 Image::Bound(bound) => {
620 self.raw.UpdateTileMappings(
621 bound.resource.as_mut_ptr(),
622 resource_coords.len() as u32,
623 resource_coords.as_ptr(),
624 region_sizes.as_ptr(),
625 heap,
626 range_flags.len() as u32,
627 range_flags.as_ptr(),
628 heap_range_start_offsets.as_ptr(),
629 range_tile_counts.as_ptr(),
630 d3d12::D3D12_TILE_MAPPING_FLAG_NONE,
631 );
632 }
633 Image::Unbound(image_unbound) => {
634 let mut resource = native::Resource::null();
635 assert_eq!(
636 winerror::S_OK,
637 device.raw.clone().CreateReservedResource(
638 &image_unbound.desc,
639 d3d12::D3D12_RESOURCE_STATE_COMMON,
640 std::ptr::null(),
641 &d3d12::ID3D12Resource::uuidof(),
642 resource.mut_void(),
643 )
644 );
645
646 self.raw.UpdateTileMappings(
647 resource.as_mut_ptr(),
648 resource_coords.len() as u32,
649 resource_coords.as_ptr(),
650 region_sizes.as_ptr(),
651 heap,
652 range_flags.len() as u32,
653 range_flags.as_ptr(),
654 heap_range_start_offsets.as_ptr(),
655 range_tile_counts.as_ptr(),
656 d3d12::D3D12_TILE_MAPPING_FLAG_NONE,
657 );
658
659 device.bind_image_resource(resource, image, resource::Place::Swapchain {});
660 }
661 }
662 }
663 // TODO sparse buffers and opaque images iterated here
664
665 if let Some(fence) = fence {
666 assert_eq!(winerror::S_OK, self.raw.Signal(fence.raw.as_mut_ptr(), 1));
667 }
668 }
669
present( &mut self, surface: &mut window::Surface, image: window::SwapchainImage, _wait_semaphore: Option<&mut resource::Semaphore>, ) -> Result<Option<hal::window::Suboptimal>, hal::window::PresentError>670 unsafe fn present(
671 &mut self,
672 surface: &mut window::Surface,
673 image: window::SwapchainImage,
674 _wait_semaphore: Option<&mut resource::Semaphore>,
675 ) -> Result<Option<hal::window::Suboptimal>, hal::window::PresentError> {
676 surface.present(image).map(|()| None)
677 }
678
wait_idle(&mut self) -> Result<(), hal::device::OutOfMemory>679 fn wait_idle(&mut self) -> Result<(), hal::device::OutOfMemory> {
680 self.wait_idle_impl()
681 }
682
timestamp_period(&self) -> f32683 fn timestamp_period(&self) -> f32 {
684 let mut frequency = 0u64;
685 unsafe {
686 self.raw.GetTimestampFrequency(&mut frequency);
687 }
688 (1_000_000_000.0 / frequency as f64) as f32
689 }
690 }
691
692 #[derive(Debug, Clone, Copy)]
693 enum MemoryArchitecture {
694 NUMA,
695 UMA,
696 CacheCoherentUMA,
697 }
698
699 #[derive(Debug, Clone, Copy)]
700 pub struct PrivateCapabilities {
701 heterogeneous_resource_heaps: bool,
702 memory_architecture: MemoryArchitecture,
703 }
704
705 #[derive(Clone, Debug)]
706 struct CmdSignatures {
707 draw: native::CommandSignature,
708 draw_indexed: native::CommandSignature,
709 dispatch: native::CommandSignature,
710 }
711
712 impl CmdSignatures {
destroy(&self)713 unsafe fn destroy(&self) {
714 self.draw.destroy();
715 self.draw_indexed.destroy();
716 self.dispatch.destroy();
717 }
718 }
719
720 // Shared objects between command buffers, owned by the device.
721 #[derive(Debug)]
722 struct Shared {
723 pub signatures: CmdSignatures,
724 pub service_pipes: internal::ServicePipes,
725 }
726
727 impl Shared {
destroy(&self)728 unsafe fn destroy(&self) {
729 self.signatures.destroy();
730 self.service_pipes.destroy();
731 }
732 }
733
734 pub struct SamplerStorage {
735 map: Mutex<FastHashMap<image::SamplerDesc, descriptors_cpu::Handle>>,
736 //TODO: respect the D3D12_REQ_SAMPLER_OBJECT_COUNT_PER_DEVICE limit
737 pool: Mutex<DescriptorCpuPool>,
738 heap: resource::DescriptorHeap,
739 origins: RwLock<resource::DescriptorOrigins>,
740 }
741
742 impl SamplerStorage {
destroy(&mut self)743 unsafe fn destroy(&mut self) {
744 self.pool.lock().destroy();
745 self.heap.destroy();
746 }
747 }
748
749 pub struct Device {
750 raw: native::Device,
751 private_caps: PrivateCapabilities,
752 features: Features,
753 format_properties: Arc<FormatProperties>,
754 heap_properties: &'static [HeapProperties],
755 // CPU only pools
756 rtv_pool: Mutex<DescriptorCpuPool>,
757 dsv_pool: Mutex<DescriptorCpuPool>,
758 srv_uav_pool: Mutex<DescriptorCpuPool>,
759 descriptor_updater: Mutex<descriptors_cpu::DescriptorUpdater>,
760 // CPU/GPU descriptor heaps
761 heap_srv_cbv_uav: (
762 resource::DescriptorHeap,
763 Mutex<RangeAllocator<DescriptorIndex>>,
764 ),
765 samplers: SamplerStorage,
766 events: Mutex<Vec<native::Event>>,
767 shared: Arc<Shared>,
768 // Present queue exposed by the `Present` queue family.
769 // Required for swapchain creation. Only a single queue supports presentation.
770 present_queue: native::CommandQueue,
771 // List of all queues created from this device, including present queue.
772 // Needed for `wait_idle`.
773 queues: Vec<Queue>,
774 // Indicates that there is currently an active device.
775 open: Arc<Mutex<bool>>,
776 library: Arc<native::D3D12Lib>,
777 }
778
779 impl fmt::Debug for Device {
fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result780 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
781 fmt.write_str("Device")
782 }
783 }
784
785 unsafe impl Send for Device {} //blocked by ComPtr
786 unsafe impl Sync for Device {} //blocked by ComPtr
787
788 impl Device {
new( device: native::Device, physical_device: &PhysicalDevice, present_queue: native::CommandQueue, ) -> Self789 fn new(
790 device: native::Device,
791 physical_device: &PhysicalDevice,
792 present_queue: native::CommandQueue,
793 ) -> Self {
794 // Allocate descriptor heaps
795 let rtv_pool = DescriptorCpuPool::new(device, native::DescriptorHeapType::Rtv);
796 let dsv_pool = DescriptorCpuPool::new(device, native::DescriptorHeapType::Dsv);
797 let srv_uav_pool = DescriptorCpuPool::new(device, native::DescriptorHeapType::CbvSrvUav);
798
799 // maximum number of CBV/SRV/UAV descriptors in heap for Tier 1
800 let view_capacity = 1_000_000;
801 let heap_srv_cbv_uav = Self::create_descriptor_heap_impl(
802 device,
803 native::DescriptorHeapType::CbvSrvUav,
804 true,
805 view_capacity,
806 );
807 let view_range_allocator = RangeAllocator::new(0..(view_capacity as u64));
808
809 let sampler_pool = DescriptorCpuPool::new(device, native::DescriptorHeapType::Sampler);
810 let heap_sampler = Self::create_descriptor_heap_impl(
811 device,
812 native::DescriptorHeapType::Sampler,
813 true,
814 2_048,
815 );
816
817 let descriptor_updater = descriptors_cpu::DescriptorUpdater::new(
818 device,
819 physical_device.workarounds.avoid_cpu_descriptor_overwrites,
820 );
821
822 let draw_signature = Self::create_command_signature(device, device::CommandSignature::Draw);
823 let draw_indexed_signature =
824 Self::create_command_signature(device, device::CommandSignature::DrawIndexed);
825 let dispatch_signature =
826 Self::create_command_signature(device, device::CommandSignature::Dispatch);
827
828 let signatures = CmdSignatures {
829 draw: draw_signature,
830 draw_indexed: draw_indexed_signature,
831 dispatch: dispatch_signature,
832 };
833 let service_pipes =
834 internal::ServicePipes::new(device, Arc::clone(&physical_device.library));
835 let shared = Shared {
836 signatures,
837 service_pipes,
838 };
839
840 Device {
841 raw: device,
842 library: Arc::clone(&physical_device.library),
843 private_caps: physical_device.private_caps,
844 features: Features::empty(),
845 format_properties: physical_device.format_properties.clone(),
846 heap_properties: physical_device.heap_properties,
847 rtv_pool: Mutex::new(rtv_pool),
848 dsv_pool: Mutex::new(dsv_pool),
849 srv_uav_pool: Mutex::new(srv_uav_pool),
850 descriptor_updater: Mutex::new(descriptor_updater),
851 heap_srv_cbv_uav: (heap_srv_cbv_uav, Mutex::new(view_range_allocator)),
852 samplers: SamplerStorage {
853 map: Mutex::default(),
854 pool: Mutex::new(sampler_pool),
855 heap: heap_sampler,
856 origins: RwLock::default(),
857 },
858 events: Mutex::new(Vec::new()),
859 shared: Arc::new(shared),
860 present_queue,
861 queues: Vec::new(),
862 open: Arc::clone(&physical_device.is_open),
863 }
864 }
865
append_queue(&mut self, queue: Queue)866 fn append_queue(&mut self, queue: Queue) {
867 self.queues.push(queue);
868 }
869
870 /// Get the native d3d12 device.
871 ///
872 /// Required for FFI with libraries like RenderDoc.
as_raw(&self) -> *mut d3d12::ID3D12Device873 pub unsafe fn as_raw(&self) -> *mut d3d12::ID3D12Device {
874 self.raw.as_mut_ptr()
875 }
876 }
877
878 impl Drop for Device {
drop(&mut self)879 fn drop(&mut self) {
880 *self.open.lock() = false;
881
882 unsafe {
883 for queue in &mut self.queues {
884 let _ = q::Queue::wait_idle(queue);
885 queue.destroy();
886 }
887
888 self.shared.destroy();
889 self.heap_srv_cbv_uav.0.destroy();
890 self.samplers.destroy();
891 self.rtv_pool.lock().destroy();
892 self.dsv_pool.lock().destroy();
893 self.srv_uav_pool.lock().destroy();
894
895 self.descriptor_updater.lock().destroy();
896
897 // Debug tracking alive objects
898 let (debug_device, hr_debug) = self.raw.cast::<d3d12sdklayers::ID3D12DebugDevice>();
899 if winerror::SUCCEEDED(hr_debug) {
900 debug_device.ReportLiveDeviceObjects(d3d12sdklayers::D3D12_RLDO_DETAIL);
901 debug_device.destroy();
902 }
903
904 self.raw.destroy();
905 }
906 }
907 }
908
909 #[derive(Debug)]
910 pub struct Instance {
911 pub(crate) factory: native::Factory4,
912 library: Arc<native::D3D12Lib>,
913 lib_dxgi: native::DxgiLib,
914 }
915
916 impl Drop for Instance {
drop(&mut self)917 fn drop(&mut self) {
918 unsafe {
919 self.factory.destroy();
920 }
921 }
922 }
923
924 unsafe impl Send for Instance {}
925 unsafe impl Sync for Instance {}
926
927 impl hal::Instance<Backend> for Instance {
create(_: &str, _: u32) -> Result<Self, hal::UnsupportedBackend>928 fn create(_: &str, _: u32) -> Result<Self, hal::UnsupportedBackend> {
929 let lib_main = match native::D3D12Lib::new() {
930 Ok(lib) => lib,
931 Err(_) => return Err(hal::UnsupportedBackend),
932 };
933
934 #[cfg(debug_assertions)]
935 {
936 // Enable debug layer
937 match lib_main.get_debug_interface() {
938 Ok((debug_controller, hr)) if winerror::SUCCEEDED(hr) => {
939 debug_controller.enable_layer();
940 unsafe {
941 debug_controller.Release();
942 }
943 }
944 _ => {
945 warn!("Unable to get D3D12 debug interface");
946 }
947 }
948 }
949
950 let lib_dxgi = native::DxgiLib::new().unwrap();
951
952 // The `DXGI_CREATE_FACTORY_DEBUG` flag is only allowed to be passed to
953 // `CreateDXGIFactory2` if the debug interface is actually available. So
954 // we check for whether it exists first.
955 let factory_flags = match lib_dxgi.get_debug_interface1() {
956 Ok((queue, hr)) if winerror::SUCCEEDED(hr) => {
957 unsafe { queue.destroy() };
958 native::FactoryCreationFlags::DEBUG
959 }
960 _ => native::FactoryCreationFlags::empty(),
961 };
962
963 // Create DXGI factory
964 let factory = match lib_dxgi.create_factory2(factory_flags) {
965 Ok((factory, hr)) if winerror::SUCCEEDED(hr) => factory,
966 Ok((_, hr)) => {
967 info!("Failed on dxgi factory creation: {:?}", hr);
968 return Err(hal::UnsupportedBackend);
969 }
970 Err(_) => return Err(hal::UnsupportedBackend),
971 };
972
973 Ok(Instance {
974 factory,
975 library: Arc::new(lib_main),
976 lib_dxgi,
977 })
978 }
979
enumerate_adapters(&self) -> Vec<adapter::Adapter<Backend>>980 fn enumerate_adapters(&self) -> Vec<adapter::Adapter<Backend>> {
981 use self::memory::Properties;
982
983 // Try to use high performance order by default (returns None on Windows < 1803)
984 let (use_f6, factory6) = unsafe {
985 let (f6, hr) = self.factory.cast::<dxgi1_6::IDXGIFactory6>();
986 if winerror::SUCCEEDED(hr) {
987 // It's okay to decrement the refcount here because we
988 // have another reference to the factory already owned by `self`.
989 f6.destroy();
990 (true, f6)
991 } else {
992 (false, native::WeakPtr::null())
993 }
994 };
995
996 // Enumerate adapters
997 let mut cur_index = 0;
998 let mut adapters = Vec::new();
999 loop {
1000 let adapter = if use_f6 {
1001 let mut adapter2 = native::WeakPtr::<dxgi1_2::IDXGIAdapter2>::null();
1002 let hr = unsafe {
1003 factory6.EnumAdapterByGpuPreference(
1004 cur_index,
1005 2, // HIGH_PERFORMANCE
1006 &dxgi1_2::IDXGIAdapter2::uuidof(),
1007 adapter2.mut_void() as *mut *mut _,
1008 )
1009 };
1010
1011 if hr == winerror::DXGI_ERROR_NOT_FOUND {
1012 break;
1013 }
1014 if !winerror::SUCCEEDED(hr) {
1015 error!("Failed enumerating adapters: 0x{:x}", hr);
1016 break;
1017 }
1018
1019 adapter2
1020 } else {
1021 let mut adapter1 = native::WeakPtr::<dxgi::IDXGIAdapter1>::null();
1022 let hr1 = unsafe {
1023 self.factory
1024 .EnumAdapters1(cur_index, adapter1.mut_void() as *mut *mut _)
1025 };
1026
1027 if hr1 == winerror::DXGI_ERROR_NOT_FOUND {
1028 break;
1029 }
1030
1031 let (adapter2, hr2) = unsafe { adapter1.cast::<dxgi1_2::IDXGIAdapter2>() };
1032 if !winerror::SUCCEEDED(hr2) {
1033 error!("Failed casting to Adapter2: 0x{:x}", hr2);
1034 break;
1035 }
1036
1037 unsafe {
1038 adapter1.destroy();
1039 }
1040 adapter2
1041 };
1042
1043 cur_index += 1;
1044
1045 // Check for D3D12 support
1046 // Create temporary device to get physical device information
1047 let device = match self
1048 .library
1049 .create_device(adapter, native::FeatureLevel::L11_0)
1050 {
1051 Ok((device, hr)) if winerror::SUCCEEDED(hr) => device,
1052 _ => continue,
1053 };
1054
1055 // We have found a possible adapter
1056 // acquire the device information
1057 let mut desc: dxgi1_2::DXGI_ADAPTER_DESC2 = unsafe { mem::zeroed() };
1058 unsafe {
1059 adapter.GetDesc2(&mut desc);
1060 }
1061
1062 let device_name = {
1063 let len = desc.Description.iter().take_while(|&&c| c != 0).count();
1064 let name = <OsString as OsStringExt>::from_wide(&desc.Description[..len]);
1065 name.to_string_lossy().into_owned()
1066 };
1067
1068 let mut features_architecture: d3d12::D3D12_FEATURE_DATA_ARCHITECTURE =
1069 unsafe { mem::zeroed() };
1070 assert_eq!(winerror::S_OK, unsafe {
1071 device.CheckFeatureSupport(
1072 d3d12::D3D12_FEATURE_ARCHITECTURE,
1073 &mut features_architecture as *mut _ as *mut _,
1074 mem::size_of::<d3d12::D3D12_FEATURE_DATA_ARCHITECTURE>() as _,
1075 )
1076 });
1077
1078 let mut workarounds = Workarounds::default();
1079
1080 let info = adapter::AdapterInfo {
1081 name: device_name,
1082 vendor: desc.VendorId as usize,
1083 device: desc.DeviceId as usize,
1084 device_type: if (desc.Flags & dxgi::DXGI_ADAPTER_FLAG_SOFTWARE) != 0 {
1085 workarounds.avoid_cpu_descriptor_overwrites = true;
1086 adapter::DeviceType::VirtualGpu
1087 } else if features_architecture.CacheCoherentUMA == TRUE {
1088 adapter::DeviceType::IntegratedGpu
1089 } else {
1090 adapter::DeviceType::DiscreteGpu
1091 },
1092 };
1093
1094 let mut features: d3d12::D3D12_FEATURE_DATA_D3D12_OPTIONS = unsafe { mem::zeroed() };
1095 assert_eq!(winerror::S_OK, unsafe {
1096 device.CheckFeatureSupport(
1097 d3d12::D3D12_FEATURE_D3D12_OPTIONS,
1098 &mut features as *mut _ as *mut _,
1099 mem::size_of::<d3d12::D3D12_FEATURE_DATA_D3D12_OPTIONS>() as _,
1100 )
1101 });
1102
1103 let depth_bounds_test_supported = {
1104 let mut features2: d3d12::D3D12_FEATURE_DATA_D3D12_OPTIONS2 =
1105 unsafe { mem::zeroed() };
1106 let hr = unsafe {
1107 device.CheckFeatureSupport(
1108 d3d12::D3D12_FEATURE_D3D12_OPTIONS2,
1109 &mut features2 as *mut _ as *mut _,
1110 mem::size_of::<d3d12::D3D12_FEATURE_DATA_D3D12_OPTIONS2>() as _,
1111 )
1112 };
1113 if hr == winerror::S_OK {
1114 features2.DepthBoundsTestSupported != 0
1115 } else {
1116 false
1117 }
1118 };
1119
1120 let heterogeneous_resource_heaps =
1121 features.ResourceHeapTier != d3d12::D3D12_RESOURCE_HEAP_TIER_1;
1122
1123 let uma = features_architecture.UMA == TRUE;
1124 let cc_uma = features_architecture.CacheCoherentUMA == TRUE;
1125
1126 let (memory_architecture, heap_properties) = match (uma, cc_uma) {
1127 (true, true) => (MemoryArchitecture::CacheCoherentUMA, &HEAPS_CCUMA),
1128 (true, false) => (MemoryArchitecture::UMA, &HEAPS_UMA),
1129 (false, _) => (MemoryArchitecture::NUMA, &HEAPS_NUMA),
1130 };
1131
1132 // https://msdn.microsoft.com/en-us/library/windows/desktop/dn788678(v=vs.85).aspx
1133 let base_memory_types: [adapter::MemoryType; NUM_HEAP_PROPERTIES] =
1134 match memory_architecture {
1135 MemoryArchitecture::NUMA => [
1136 // DEFAULT
1137 adapter::MemoryType {
1138 properties: Properties::DEVICE_LOCAL,
1139 heap_index: 0,
1140 },
1141 // UPLOAD
1142 adapter::MemoryType {
1143 properties: Properties::CPU_VISIBLE | Properties::COHERENT,
1144 heap_index: 1,
1145 },
1146 // READBACK
1147 adapter::MemoryType {
1148 properties: Properties::CPU_VISIBLE
1149 | Properties::COHERENT
1150 | Properties::CPU_CACHED,
1151 heap_index: 1,
1152 },
1153 ],
1154 MemoryArchitecture::UMA => [
1155 // DEFAULT
1156 adapter::MemoryType {
1157 properties: Properties::DEVICE_LOCAL,
1158 heap_index: 0,
1159 },
1160 // UPLOAD
1161 adapter::MemoryType {
1162 properties: Properties::DEVICE_LOCAL
1163 | Properties::CPU_VISIBLE
1164 | Properties::COHERENT,
1165 heap_index: 0,
1166 },
1167 // READBACK
1168 adapter::MemoryType {
1169 properties: Properties::DEVICE_LOCAL
1170 | Properties::CPU_VISIBLE
1171 | Properties::COHERENT
1172 | Properties::CPU_CACHED,
1173 heap_index: 0,
1174 },
1175 ],
1176 MemoryArchitecture::CacheCoherentUMA => [
1177 // DEFAULT
1178 adapter::MemoryType {
1179 properties: Properties::DEVICE_LOCAL,
1180 heap_index: 0,
1181 },
1182 // UPLOAD
1183 adapter::MemoryType {
1184 properties: Properties::DEVICE_LOCAL
1185 | Properties::CPU_VISIBLE
1186 | Properties::COHERENT
1187 | Properties::CPU_CACHED,
1188 heap_index: 0,
1189 },
1190 // READBACK
1191 adapter::MemoryType {
1192 properties: Properties::DEVICE_LOCAL
1193 | Properties::CPU_VISIBLE
1194 | Properties::COHERENT
1195 | Properties::CPU_CACHED,
1196 heap_index: 0,
1197 },
1198 ],
1199 };
1200
1201 let memory_types = if heterogeneous_resource_heaps {
1202 base_memory_types.to_vec()
1203 } else {
1204 // We multiplicate the base memory types depending on the resource usage:
1205 // 0.. 3: Reserved for futures use
1206 // 4.. 6: Buffers
1207 // 7.. 9: Images
1208 // 10..12: Targets
1209 //
1210 // The supported memory types for a resource can be requested by asking for
1211 // the memory requirements. Memory type indices are encoded as bitflags.
1212 // `device::MEM_TYPE_MASK` (0b111) defines the bitmask for one base memory type group.
1213 // The corresponding shift masks (`device::MEM_TYPE_BUFFER_SHIFT`,
1214 // `device::MEM_TYPE_IMAGE_SHIFT`, `device::MEM_TYPE_TARGET_SHIFT`)
1215 // denote the usage group.
1216 let mut types = Vec::new();
1217 for i in 0..MemoryGroup::NumGroups as _ {
1218 types.extend(base_memory_types.iter().map(|mem_type| {
1219 let mut ty = mem_type.clone();
1220
1221 // Images and Targets are not host visible as we can't create
1222 // a corresponding buffer for mapping.
1223 if i == MemoryGroup::ImageOnly as _ || i == MemoryGroup::TargetOnly as _ {
1224 ty.properties.remove(Properties::CPU_VISIBLE);
1225 // Coherent and cached can only be on memory types that are cpu visible
1226 ty.properties.remove(Properties::COHERENT);
1227 ty.properties.remove(Properties::CPU_CACHED);
1228 }
1229 ty
1230 }));
1231 }
1232 types
1233 };
1234
1235 let memory_heaps = {
1236 // Get the IDXGIAdapter3 from the created device to query video memory information.
1237 let adapter_id = unsafe { device.GetAdapterLuid() };
1238 let adapter = {
1239 let mut adapter = native::WeakPtr::<dxgi1_4::IDXGIAdapter3>::null();
1240 unsafe {
1241 assert_eq!(
1242 winerror::S_OK,
1243 self.factory.EnumAdapterByLuid(
1244 adapter_id,
1245 &dxgi1_4::IDXGIAdapter3::uuidof(),
1246 adapter.mut_void(),
1247 )
1248 );
1249 }
1250 adapter
1251 };
1252
1253 let query_memory = |segment: dxgi1_4::DXGI_MEMORY_SEGMENT_GROUP| unsafe {
1254 let mut mem_info: dxgi1_4::DXGI_QUERY_VIDEO_MEMORY_INFO = mem::zeroed();
1255 assert_eq!(
1256 winerror::S_OK,
1257 adapter.QueryVideoMemoryInfo(0, segment, &mut mem_info)
1258 );
1259 mem_info.Budget
1260 };
1261
1262 let mut heaps = vec![adapter::MemoryHeap {
1263 size: query_memory(dxgi1_4::DXGI_MEMORY_SEGMENT_GROUP_LOCAL),
1264 flags: memory::HeapFlags::DEVICE_LOCAL,
1265 }];
1266 if let MemoryArchitecture::NUMA = memory_architecture {
1267 heaps.push(adapter::MemoryHeap {
1268 size: query_memory(dxgi1_4::DXGI_MEMORY_SEGMENT_GROUP_NON_LOCAL),
1269 flags: memory::HeapFlags::empty(),
1270 });
1271 }
1272 heaps
1273 };
1274 //TODO: find a way to get a tighter bound?
1275 let sample_count_mask = 0x3F;
1276
1277 // Theoretically vram limited, but in practice 2^20 is the limit
1278 let tier3_practical_descriptor_limit = 1 << 20;
1279
1280 let full_heap_count = match features.ResourceBindingTier {
1281 d3d12::D3D12_RESOURCE_BINDING_TIER_1 => {
1282 d3d12::D3D12_MAX_SHADER_VISIBLE_DESCRIPTOR_HEAP_SIZE_TIER_1
1283 }
1284 d3d12::D3D12_RESOURCE_BINDING_TIER_2 => {
1285 d3d12::D3D12_MAX_SHADER_VISIBLE_DESCRIPTOR_HEAP_SIZE_TIER_2
1286 }
1287 d3d12::D3D12_RESOURCE_BINDING_TIER_3 => tier3_practical_descriptor_limit,
1288 _ => unreachable!(),
1289 } as _;
1290
1291 let uav_limit = match features.ResourceBindingTier {
1292 d3d12::D3D12_RESOURCE_BINDING_TIER_1 => 8, // conservative, is 64 on feature level 11.1
1293 d3d12::D3D12_RESOURCE_BINDING_TIER_2 => 64,
1294 d3d12::D3D12_RESOURCE_BINDING_TIER_3 => tier3_practical_descriptor_limit,
1295 _ => unreachable!(),
1296 } as _;
1297
1298 let mut tiled_resource_features = Features::empty();
1299 if features.TiledResourcesTier >= d3d12::D3D12_TILED_RESOURCES_TIER_1 {
1300 tiled_resource_features |= Features::SPARSE_BINDING;
1301 tiled_resource_features |= Features::SPARSE_RESIDENCY_IMAGE_2D;
1302 tiled_resource_features |= Features::SPARSE_RESIDENCY_BUFFER;
1303 tiled_resource_features |= Features::SPARSE_RESIDENCY_ALIASED;
1304 tiled_resource_features |= Features::SPARSE_RESIDENCY_2_SAMPLES;
1305 tiled_resource_features |= Features::SPARSE_RESIDENCY_4_SAMPLES;
1306 tiled_resource_features |= Features::SPARSE_RESIDENCY_8_SAMPLES;
1307 tiled_resource_features |= Features::SPARSE_RESIDENCY_16_SAMPLES;
1308 }
1309 if features.TiledResourcesTier >= d3d12::D3D12_TILED_RESOURCES_TIER_3 {
1310 tiled_resource_features |= Features::SPARSE_RESIDENCY_IMAGE_3D;
1311 }
1312
1313 let conservative_faster_features = if features.ConservativeRasterizationTier
1314 == d3d12::D3D12_CONSERVATIVE_RASTERIZATION_TIER_NOT_SUPPORTED
1315 {
1316 Features::empty()
1317 } else {
1318 Features::CONSERVATIVE_RASTERIZATION
1319 };
1320
1321 let physical_device = PhysicalDevice {
1322 library: Arc::clone(&self.library),
1323 adapter,
1324 features:
1325 // TODO: add more features, based on
1326 // https://msdn.microsoft.com/de-de/library/windows/desktop/mt186615(v=vs.85).aspx
1327 Features::ROBUST_BUFFER_ACCESS |
1328 Features::IMAGE_CUBE_ARRAY |
1329 Features::GEOMETRY_SHADER |
1330 Features::TESSELLATION_SHADER |
1331 Features::NON_FILL_POLYGON_MODE |
1332 if depth_bounds_test_supported { Features::DEPTH_BOUNDS } else { Features::empty() } |
1333 //logic_op: false, // Optional on feature level 11_0
1334 Features::MULTI_DRAW_INDIRECT |
1335 Features::FORMAT_BC |
1336 Features::INSTANCE_RATE |
1337 Features::DEPTH_CLAMP |
1338 Features::SAMPLER_MIP_LOD_BIAS |
1339 Features::SAMPLER_BORDER_COLOR |
1340 Features::MUTABLE_COMPARISON_SAMPLER |
1341 Features::SAMPLER_ANISOTROPY |
1342 Features::TEXTURE_DESCRIPTOR_ARRAY |
1343 Features::BUFFER_DESCRIPTOR_ARRAY |
1344 Features::SAMPLER_MIRROR_CLAMP_EDGE |
1345 Features::NDC_Y_UP |
1346 Features::SHADER_SAMPLED_IMAGE_ARRAY_DYNAMIC_INDEXING |
1347 Features::SHADER_STORAGE_IMAGE_ARRAY_DYNAMIC_INDEXING |
1348 Features::SHADER_STORAGE_BUFFER_ARRAY_DYNAMIC_INDEXING |
1349 Features::SHADER_UNIFORM_BUFFER_ARRAY_DYNAMIC_INDEXING |
1350 Features::SAMPLED_TEXTURE_DESCRIPTOR_INDEXING |
1351 Features::STORAGE_TEXTURE_DESCRIPTOR_INDEXING |
1352 Features::STORAGE_BUFFER_DESCRIPTOR_INDEXING |
1353 Features::UNIFORM_BUFFER_DESCRIPTOR_INDEXING |
1354 Features::UNSIZED_DESCRIPTOR_ARRAY |
1355 Features::DRAW_INDIRECT_COUNT |
1356 tiled_resource_features |
1357 conservative_faster_features,
1358 properties: PhysicalDeviceProperties {
1359 limits: Limits {
1360 //TODO: verify all of these not linked to constants
1361 max_memory_allocation_count: !0,
1362 max_bound_descriptor_sets: MAX_DESCRIPTOR_SETS as u16,
1363 descriptor_limits: hal::DescriptorLimits {
1364 max_per_stage_descriptor_samplers: match features.ResourceBindingTier {
1365 d3d12::D3D12_RESOURCE_BINDING_TIER_1 => 16,
1366 d3d12::D3D12_RESOURCE_BINDING_TIER_2 | d3d12::D3D12_RESOURCE_BINDING_TIER_3 | _ => d3d12::D3D12_MAX_SHADER_VISIBLE_SAMPLER_HEAP_SIZE,
1367 } as _,
1368 max_per_stage_descriptor_uniform_buffers: match features.ResourceBindingTier
1369 {
1370 d3d12::D3D12_RESOURCE_BINDING_TIER_1 | d3d12::D3D12_RESOURCE_BINDING_TIER_2 => {
1371 d3d12::D3D12_COMMONSHADER_CONSTANT_BUFFER_API_SLOT_COUNT
1372 }
1373 d3d12::D3D12_RESOURCE_BINDING_TIER_3 | _ => full_heap_count as _,
1374 } as _,
1375 max_per_stage_descriptor_storage_buffers: uav_limit,
1376 max_per_stage_descriptor_sampled_images: match features.ResourceBindingTier
1377 {
1378 d3d12::D3D12_RESOURCE_BINDING_TIER_1 => 128,
1379 d3d12::D3D12_RESOURCE_BINDING_TIER_2
1380 | d3d12::D3D12_RESOURCE_BINDING_TIER_3
1381 | _ => full_heap_count,
1382 } as _,
1383 max_per_stage_descriptor_storage_images: uav_limit,
1384 max_per_stage_resources: !0,
1385 max_descriptor_set_samplers: d3d12::D3D12_MAX_SHADER_VISIBLE_SAMPLER_HEAP_SIZE as _,
1386 max_descriptor_set_uniform_buffers: full_heap_count,
1387 max_descriptor_set_uniform_buffers_dynamic: 8,
1388 max_descriptor_set_storage_buffers: uav_limit,
1389 max_descriptor_set_storage_buffers_dynamic: 4,
1390 max_descriptor_set_sampled_images: full_heap_count,
1391 max_descriptor_set_storage_images: uav_limit,
1392 ..hal::DescriptorLimits::default() // TODO
1393 },
1394 max_uniform_buffer_range: (d3d12::D3D12_REQ_CONSTANT_BUFFER_ELEMENT_COUNT * 16)
1395 as _,
1396 max_storage_buffer_range: !0,
1397 // Is actually 256, but need space for the descriptors in there, so leave at 128 to discourage explosions
1398 max_push_constants_size: 128,
1399 max_image_1d_size: d3d12::D3D12_REQ_TEXTURE1D_U_DIMENSION as _,
1400 max_image_2d_size: d3d12::D3D12_REQ_TEXTURE2D_U_OR_V_DIMENSION as _,
1401 max_image_3d_size: d3d12::D3D12_REQ_TEXTURE3D_U_V_OR_W_DIMENSION as _,
1402 max_image_cube_size: d3d12::D3D12_REQ_TEXTURECUBE_DIMENSION as _,
1403 max_image_array_layers: d3d12::D3D12_REQ_TEXTURE2D_ARRAY_AXIS_DIMENSION as _,
1404 max_texel_elements: 0,
1405 max_patch_size: 0,
1406 max_viewports: d3d12::D3D12_VIEWPORT_AND_SCISSORRECT_OBJECT_COUNT_PER_PIPELINE as _,
1407 max_viewport_dimensions: [d3d12::D3D12_VIEWPORT_BOUNDS_MAX as _; 2],
1408 max_framebuffer_extent: hal::image::Extent {
1409 width: d3d12::D3D12_REQ_TEXTURE2D_U_OR_V_DIMENSION,
1410 height: d3d12::D3D12_REQ_TEXTURE2D_U_OR_V_DIMENSION,
1411 depth: 1,
1412 },
1413 max_framebuffer_layers: 1,
1414 max_compute_work_group_count: [
1415 d3d12::D3D12_CS_DISPATCH_MAX_THREAD_GROUPS_PER_DIMENSION,
1416 d3d12::D3D12_CS_DISPATCH_MAX_THREAD_GROUPS_PER_DIMENSION,
1417 d3d12::D3D12_CS_DISPATCH_MAX_THREAD_GROUPS_PER_DIMENSION,
1418 ],
1419 max_compute_work_group_invocations: d3d12::D3D12_CS_THREAD_GROUP_MAX_THREADS_PER_GROUP as _,
1420 max_compute_work_group_size: [
1421 d3d12::D3D12_CS_THREAD_GROUP_MAX_X,
1422 d3d12::D3D12_CS_THREAD_GROUP_MAX_Y,
1423 d3d12::D3D12_CS_THREAD_GROUP_MAX_Z,
1424 ],
1425 max_vertex_input_attributes: d3d12::D3D12_IA_VERTEX_INPUT_RESOURCE_SLOT_COUNT as _,
1426 max_vertex_input_bindings: d3d12::D3D12_VS_INPUT_REGISTER_COUNT as _,
1427 max_vertex_input_attribute_offset: 255, // TODO
1428 max_vertex_input_binding_stride: d3d12::D3D12_REQ_MULTI_ELEMENT_STRUCTURE_SIZE_IN_BYTES as _,
1429 max_vertex_output_components: d3d12::D3D12_VS_OUTPUT_REGISTER_COUNT as _,
1430 max_fragment_input_components: d3d12::D3D12_PS_INPUT_REGISTER_COUNT as _,
1431 max_fragment_output_attachments: d3d12::D3D12_PS_OUTPUT_REGISTER_COUNT as _,
1432 max_fragment_dual_source_attachments: 1,
1433 max_fragment_combined_output_resources: (d3d12::D3D12_PS_OUTPUT_REGISTER_COUNT + d3d12::D3D12_PS_CS_UAV_REGISTER_COUNT) as _,
1434 min_texel_buffer_offset_alignment: 1, // TODO
1435 min_uniform_buffer_offset_alignment: d3d12::D3D12_CONSTANT_BUFFER_DATA_PLACEMENT_ALIGNMENT as _,
1436 min_storage_buffer_offset_alignment: 4, // TODO
1437 framebuffer_color_sample_counts: sample_count_mask,
1438 framebuffer_depth_sample_counts: sample_count_mask,
1439 framebuffer_stencil_sample_counts: sample_count_mask,
1440 max_color_attachments: d3d12::D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT as _,
1441 buffer_image_granularity: 1,
1442 non_coherent_atom_size: 1, //TODO: confirm
1443 max_sampler_anisotropy: 16.,
1444 optimal_buffer_copy_offset_alignment: d3d12::D3D12_TEXTURE_DATA_PLACEMENT_ALIGNMENT as _,
1445 optimal_buffer_copy_pitch_alignment: d3d12::D3D12_TEXTURE_DATA_PITCH_ALIGNMENT as _,
1446 min_vertex_input_binding_stride_alignment: 1,
1447 ..Limits::default() //TODO
1448 },
1449 dynamic_pipeline_states: hal::DynamicStates::VIEWPORT
1450 | hal::DynamicStates::SCISSOR
1451 | hal::DynamicStates::BLEND_CONSTANTS
1452 | hal::DynamicStates::STENCIL_REFERENCE,
1453 downlevel: hal::DownlevelProperties::all_enabled(),
1454 ..PhysicalDeviceProperties::default()
1455 },
1456 format_properties: Arc::new(FormatProperties::new(device)),
1457 private_caps: PrivateCapabilities {
1458 heterogeneous_resource_heaps,
1459 memory_architecture,
1460 },
1461 workarounds,
1462 heap_properties,
1463 memory_properties: adapter::MemoryProperties {
1464 memory_types,
1465 memory_heaps,
1466 },
1467 is_open: Arc::new(Mutex::new(false)),
1468 };
1469
1470 let queue_families = QUEUE_FAMILIES.to_vec();
1471
1472 adapters.push(adapter::Adapter {
1473 info,
1474 physical_device,
1475 queue_families,
1476 });
1477 }
1478 adapters
1479 }
1480
create_surface( &self, has_handle: &impl raw_window_handle::HasRawWindowHandle, ) -> Result<window::Surface, hal::window::InitError>1481 unsafe fn create_surface(
1482 &self,
1483 has_handle: &impl raw_window_handle::HasRawWindowHandle,
1484 ) -> Result<window::Surface, hal::window::InitError> {
1485 match has_handle.raw_window_handle() {
1486 raw_window_handle::RawWindowHandle::Windows(handle) => {
1487 Ok(self.create_surface_from_hwnd(handle.hwnd))
1488 }
1489 _ => Err(hal::window::InitError::UnsupportedWindowHandle),
1490 }
1491 }
1492
destroy_surface(&self, _surface: window::Surface)1493 unsafe fn destroy_surface(&self, _surface: window::Surface) {
1494 // TODO: Implement Surface cleanup
1495 }
1496 }
1497
1498 #[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
1499 pub enum Backend {}
1500 impl hal::Backend for Backend {
1501 type Instance = Instance;
1502 type PhysicalDevice = PhysicalDevice;
1503 type Device = Device;
1504 type Surface = window::Surface;
1505
1506 type QueueFamily = QueueFamily;
1507 type Queue = Queue;
1508 type CommandBuffer = command::CommandBuffer;
1509
1510 type Memory = resource::Memory;
1511 type CommandPool = pool::CommandPool;
1512
1513 type ShaderModule = resource::ShaderModule;
1514 type RenderPass = resource::RenderPass;
1515 type Framebuffer = resource::Framebuffer;
1516
1517 type Buffer = resource::Buffer;
1518 type BufferView = resource::BufferView;
1519 type Image = resource::Image;
1520 type ImageView = resource::ImageView;
1521 type Sampler = resource::Sampler;
1522
1523 type ComputePipeline = resource::ComputePipeline;
1524 type GraphicsPipeline = resource::GraphicsPipeline;
1525 type PipelineLayout = resource::PipelineLayout;
1526 type PipelineCache = ();
1527 type DescriptorSetLayout = resource::DescriptorSetLayout;
1528 type DescriptorPool = resource::DescriptorPool;
1529 type DescriptorSet = resource::DescriptorSet;
1530
1531 type Fence = resource::Fence;
1532 type Semaphore = resource::Semaphore;
1533 type Event = ();
1534 type QueryPool = resource::QueryPool;
1535 }
1536
validate_line_width(width: f32)1537 fn validate_line_width(width: f32) {
1538 // Note from the Vulkan spec:
1539 // > If the wide lines feature is not enabled, lineWidth must be 1.0
1540 // Simply assert and no-op because DX12 never exposes `Features::LINE_WIDTH`
1541 assert_eq!(width, 1.0);
1542 }
1543
1544 #[derive(Clone, Copy, Debug, Default)]
1545 struct FormatInfo {
1546 properties: f::Properties,
1547 sample_count_mask: u8,
1548 }
1549
1550 #[derive(Debug)]
1551 pub struct FormatProperties {
1552 info: Box<[Mutex<Option<FormatInfo>>]>,
1553 device: native::Device,
1554 }
1555
1556 impl Drop for FormatProperties {
drop(&mut self)1557 fn drop(&mut self) {
1558 unsafe {
1559 self.device.destroy();
1560 }
1561 }
1562 }
1563
1564 impl FormatProperties {
new(device: native::Device) -> Self1565 fn new(device: native::Device) -> Self {
1566 let mut buf = Vec::with_capacity(f::NUM_FORMATS);
1567 buf.push(Mutex::new(Some(FormatInfo::default())));
1568 for _ in 1..f::NUM_FORMATS {
1569 buf.push(Mutex::new(None))
1570 }
1571 FormatProperties {
1572 info: buf.into_boxed_slice(),
1573 device,
1574 }
1575 }
1576
resolve(&self, idx: usize) -> FormatInfo1577 fn resolve(&self, idx: usize) -> FormatInfo {
1578 let mut guard = self.info[idx].lock();
1579 if let Some(info) = *guard {
1580 return info;
1581 }
1582 let format: f::Format = unsafe { mem::transmute(idx as u32) };
1583 let is_compressed = format.surface_desc().is_compressed();
1584 let dxgi_format = match conv::map_format(format) {
1585 Some(format) => format,
1586 None => {
1587 let info = FormatInfo::default();
1588 *guard = Some(info);
1589 return info;
1590 }
1591 };
1592
1593 let properties = {
1594 let mut props = f::Properties::default();
1595 let mut data = d3d12::D3D12_FEATURE_DATA_FORMAT_SUPPORT {
1596 Format: dxgi_format,
1597 Support1: unsafe { mem::zeroed() },
1598 Support2: unsafe { mem::zeroed() },
1599 };
1600 assert_eq!(winerror::S_OK, unsafe {
1601 self.device.CheckFeatureSupport(
1602 d3d12::D3D12_FEATURE_FORMAT_SUPPORT,
1603 &mut data as *mut _ as *mut _,
1604 mem::size_of::<d3d12::D3D12_FEATURE_DATA_FORMAT_SUPPORT>() as _,
1605 )
1606 });
1607 let can_buffer = 0 != data.Support1 & d3d12::D3D12_FORMAT_SUPPORT1_BUFFER;
1608 let can_image = 0
1609 != data.Support1
1610 & (d3d12::D3D12_FORMAT_SUPPORT1_TEXTURE1D
1611 | d3d12::D3D12_FORMAT_SUPPORT1_TEXTURE2D
1612 | d3d12::D3D12_FORMAT_SUPPORT1_TEXTURE3D
1613 | d3d12::D3D12_FORMAT_SUPPORT1_TEXTURECUBE);
1614 let can_linear = can_image && !is_compressed;
1615 if can_image {
1616 props.optimal_tiling |= f::ImageFeature::TRANSFER_SRC
1617 | f::ImageFeature::TRANSFER_DST
1618 | f::ImageFeature::SAMPLED;
1619 }
1620 if can_linear {
1621 props.linear_tiling |= f::ImageFeature::TRANSFER_SRC
1622 | f::ImageFeature::TRANSFER_DST
1623 | f::ImageFeature::SAMPLED
1624 | f::ImageFeature::BLIT_SRC;
1625 }
1626 if data.Support1 & d3d12::D3D12_FORMAT_SUPPORT1_IA_VERTEX_BUFFER != 0 {
1627 props.buffer_features |= f::BufferFeature::VERTEX;
1628 }
1629 if data.Support1 & d3d12::D3D12_FORMAT_SUPPORT1_SHADER_SAMPLE != 0 {
1630 props.optimal_tiling |= f::ImageFeature::SAMPLED_LINEAR;
1631 }
1632 if data.Support1 & d3d12::D3D12_FORMAT_SUPPORT1_RENDER_TARGET != 0 {
1633 props.optimal_tiling |=
1634 f::ImageFeature::COLOR_ATTACHMENT | f::ImageFeature::BLIT_DST;
1635 }
1636 if data.Support1 & d3d12::D3D12_FORMAT_SUPPORT1_BLENDABLE != 0 {
1637 props.optimal_tiling |= f::ImageFeature::COLOR_ATTACHMENT_BLEND;
1638 }
1639 if data.Support1 & d3d12::D3D12_FORMAT_SUPPORT1_DEPTH_STENCIL != 0 {
1640 props.optimal_tiling |= f::ImageFeature::DEPTH_STENCIL_ATTACHMENT;
1641 }
1642 if data.Support1 & d3d12::D3D12_FORMAT_SUPPORT1_SHADER_LOAD != 0 {
1643 //TODO: check d3d12::D3D12_FORMAT_SUPPORT2_UAV_TYPED_LOAD ?
1644 if can_buffer {
1645 props.buffer_features |= f::BufferFeature::UNIFORM_TEXEL;
1646 }
1647 }
1648 if data.Support2 & d3d12::D3D12_FORMAT_SUPPORT2_UAV_ATOMIC_ADD != 0 {
1649 //TODO: other atomic flags?
1650 if can_buffer {
1651 props.buffer_features |= f::BufferFeature::STORAGE_TEXEL_ATOMIC;
1652 }
1653 if can_image {
1654 props.optimal_tiling |= f::ImageFeature::STORAGE_ATOMIC;
1655 }
1656 }
1657 if data.Support2 & d3d12::D3D12_FORMAT_SUPPORT2_UAV_TYPED_STORE != 0 {
1658 if can_buffer {
1659 props.buffer_features |= f::BufferFeature::STORAGE_TEXEL;
1660 }
1661 if can_image {
1662 // Since read-only storage is exposed as SRV, we can guarantee read-only storage
1663 // without checking D3D12_FORMAT_SUPPORT2_UAV_TYPED_LOAD first.
1664 props.optimal_tiling |= f::ImageFeature::STORAGE;
1665
1666 if data.Support2 & d3d12::D3D12_FORMAT_SUPPORT2_UAV_TYPED_LOAD != 0 {
1667 props.optimal_tiling |= f::ImageFeature::STORAGE_READ_WRITE;
1668 }
1669 }
1670 }
1671 //TODO: blits, linear tiling
1672 props
1673 };
1674
1675 let sample_count_mask = if is_compressed {
1676 // just an optimization to avoid the queries
1677 1
1678 } else {
1679 let mut mask = 0;
1680 for i in 0..6 {
1681 let mut data = d3d12::D3D12_FEATURE_DATA_MULTISAMPLE_QUALITY_LEVELS {
1682 Format: dxgi_format,
1683 SampleCount: 1 << i,
1684 Flags: 0,
1685 NumQualityLevels: 0,
1686 };
1687 assert_eq!(winerror::S_OK, unsafe {
1688 self.device.CheckFeatureSupport(
1689 d3d12::D3D12_FEATURE_MULTISAMPLE_QUALITY_LEVELS,
1690 &mut data as *mut _ as *mut _,
1691 mem::size_of::<d3d12::D3D12_FEATURE_DATA_MULTISAMPLE_QUALITY_LEVELS>() as _,
1692 )
1693 });
1694 if data.NumQualityLevels != 0 {
1695 mask |= 1 << i;
1696 }
1697 }
1698 mask
1699 };
1700
1701 let info = FormatInfo {
1702 properties,
1703 sample_count_mask,
1704 };
1705 *guard = Some(info);
1706 info
1707 }
1708 }
1709