diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 12460131825..7cb22041b93 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -22,6 +22,10 @@ path = "colour-uniform/main.rs" name = "quad" path = "quad/main.rs" +[[bin]] +name = "quad-multiview" +path = "quad-multiview/main.rs" + [[bin]] name = "compute" path = "compute/main.rs" diff --git a/examples/colour-uniform/main.rs b/examples/colour-uniform/main.rs index e092f1d8891..a2555b39b92 100644 --- a/examples/colour-uniform/main.rs +++ b/examples/colour-uniform/main.rs @@ -686,12 +686,13 @@ impl RenderPassState { inputs: &[], resolves: &[], preserves: &[], + view_mask: 0, }; device .borrow() .device - .create_render_pass(&[attachment], &[subpass], &[]) + .create_render_pass(&[attachment], &[subpass], &[], None) .ok() }; diff --git a/examples/mesh-shading/main.rs b/examples/mesh-shading/main.rs index 8f5d63f5dbb..3f10176bd7b 100644 --- a/examples/mesh-shading/main.rs +++ b/examples/mesh-shading/main.rs @@ -379,10 +379,11 @@ where inputs: &[], resolves: &[], preserves: &[], + view_mask: 0, }; ManuallyDrop::new( - unsafe { device.create_render_pass(&[attachment], &[subpass], &[]) } + unsafe { device.create_render_pass(&[attachment], &[subpass], &[], None) } .expect("Can't create render pass"), ) }; diff --git a/examples/quad-multiview/README.md b/examples/quad-multiview/README.md new file mode 100644 index 00000000000..621baf7760e --- /dev/null +++ b/examples/quad-multiview/README.md @@ -0,0 +1,5 @@ +# Quad + +The following image is an output of `cargo run --bin quad --features=gl`: + +![screenshot](screenshot.png "Quad") diff --git a/examples/quad-multiview/data/index.html b/examples/quad-multiview/data/index.html new file mode 100644 index 00000000000..e2b6395bae5 --- /dev/null +++ b/examples/quad-multiview/data/index.html @@ -0,0 +1,17 @@ + + + + + + + + + diff --git a/examples/quad-multiview/data/logo.png b/examples/quad-multiview/data/logo.png new file mode 100644 index 00000000000..e0cc9047bc3 Binary files /dev/null and b/examples/quad-multiview/data/logo.png differ diff --git a/examples/quad-multiview/data/quad.frag b/examples/quad-multiview/data/quad.frag new file mode 100644 index 00000000000..2c4c4017bd5 --- /dev/null +++ b/examples/quad-multiview/data/quad.frag @@ -0,0 +1,17 @@ +#version 450 +#extension GL_ARB_separate_shader_objects : enable +#extension GL_EXT_multiview : enable + +layout(location = 0) in vec2 v_uv; +layout(location = 0) out vec4 target0; + +layout(set = 0, binding = 0) uniform texture2D u_texture; +layout(set = 0, binding = 1) uniform sampler u_sampler; + +void main() { + vec4 mask = vec4(0, 0, 0, 1); + + mask[gl_ViewIndex] = 1.0; + + target0 = texture(sampler2D(u_texture, u_sampler), v_uv) * mask; +} diff --git a/examples/quad-multiview/data/quad.vert b/examples/quad-multiview/data/quad.vert new file mode 100644 index 00000000000..d92b3fea80f --- /dev/null +++ b/examples/quad-multiview/data/quad.vert @@ -0,0 +1,18 @@ +#version 450 +#extension GL_ARB_separate_shader_objects : enable +#extension GL_EXT_multiview : enable + +layout(constant_id = 0) const float scale = 1.2f; + +layout(location = 0) in vec2 a_pos; +layout(location = 1) in vec2 a_uv; +layout(location = 0) out vec2 v_uv; + +out gl_PerVertex { + vec4 gl_Position; +}; + +void main() { + v_uv = a_uv; + gl_Position = vec4(scale * a_pos, 0.0, 1.0); +} diff --git a/examples/quad-multiview/main.rs b/examples/quad-multiview/main.rs new file mode 100644 index 00000000000..553f896512d --- /dev/null +++ b/examples/quad-multiview/main.rs @@ -0,0 +1,1078 @@ +#[cfg(feature = "dx11")] +extern crate gfx_backend_dx11 as back; +#[cfg(feature = "dx12")] +extern crate gfx_backend_dx12 as back; +#[cfg(not(any( + feature = "vulkan", + feature = "dx11", + feature = "dx12", + feature = "metal", + feature = "gl", +)))] +extern crate gfx_backend_empty as back; +#[cfg(any(feature = "gl"))] +extern crate gfx_backend_gl as back; +#[cfg(feature = "metal")] +extern crate gfx_backend_metal as back; +#[cfg(feature = "vulkan")] +extern crate gfx_backend_vulkan as back; + +#[cfg(target_arch = "wasm32")] +use wasm_bindgen::prelude::*; + +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen(start)] +pub fn wasm_main() { + std::panic::set_hook(Box::new(console_error_panic_hook::hook)); + main(); +} + +use hal::{ + buffer, + command, + format as f, + format::{AsFormat, ChannelType, Rgba8Srgb as ColorFormat, Swizzle}, + image as i, + memory as m, + pass, + pass::Subpass, + pool, + prelude::*, + pso, + pso::{PipelineStage, ShaderStageFlags, VertexInputRate}, + queue::{QueueGroup, Submission}, + window, +}; + +use std::{ + borrow::Borrow, + io::Cursor, + iter, + mem::{self, ManuallyDrop}, + ptr, +}; + +#[cfg_attr(rustfmt, rustfmt_skip)] +const DIMS: window::Extent2D = window::Extent2D { width: 1024, height: 768 }; + +const ENTRY_NAME: &str = "main"; + +#[derive(Debug, Clone, Copy)] +#[allow(non_snake_case)] +struct Vertex { + a_Pos: [f32; 2], + a_Uv: [f32; 2], +} + +#[cfg_attr(rustfmt, rustfmt_skip)] +const QUAD: [Vertex; 6] = [ + Vertex { a_Pos: [ -0.5, 0.33 ], a_Uv: [0.0, 1.0] }, + Vertex { a_Pos: [ 0.5, 0.33 ], a_Uv: [1.0, 1.0] }, + Vertex { a_Pos: [ 0.5,-0.33 ], a_Uv: [1.0, 0.0] }, + + Vertex { a_Pos: [ -0.5, 0.33 ], a_Uv: [0.0, 1.0] }, + Vertex { a_Pos: [ 0.5,-0.33 ], a_Uv: [1.0, 0.0] }, + Vertex { a_Pos: [ -0.5,-0.33 ], a_Uv: [0.0, 0.0] }, +]; + +const COLOR_RANGE: i::SubresourceRange = i::SubresourceRange { + aspects: f::Aspects::COLOR, + levels: 0 .. 1, + layers: 0 .. 1, +}; + +fn main() { + #[cfg(target_arch = "wasm32")] + console_log::init_with_level(log::Level::Debug).unwrap(); + + #[cfg(not(target_arch = "wasm32"))] + env_logger::init(); + + #[cfg(not(any( + feature = "vulkan", + feature = "dx11", + feature = "dx12", + feature = "metal", + feature = "gl", + )))] + eprintln!( + "You are running the example with the empty backend, no graphical output is to be expected" + ); + + let event_loop = winit::event_loop::EventLoop::new(); + + let wb = winit::window::WindowBuilder::new() + .with_min_inner_size(winit::dpi::Size::Logical(winit::dpi::LogicalSize::new( + 64.0, 64.0, + ))) + .with_inner_size(winit::dpi::Size::Physical(winit::dpi::PhysicalSize::new( + DIMS.width, + DIMS.height, + ))) + .with_title("quad".to_string()); + + // instantiate backend + #[cfg(not(target_arch = "wasm32"))] + let (windows, instance, mut adapters, surfaces) = { + let windows = [ + wb.clone().with_title("quad view 1".to_string()).build(&event_loop).unwrap(), + wb.clone().with_title("quad view 2".to_string()).build(&event_loop).unwrap(), + ]; + let instance = + back::Instance::create("gfx-rs quad", 1).expect("Failed to create an instance!"); + let adapters = instance.enumerate_adapters(); + let surfaces = unsafe { + [ + instance + .create_surface(&windows[0]) + .expect("Failed to create a surface!"), + instance + .create_surface(&windows[1]) + .expect("Failed to create a surface!"), + ] + }; + // Return `window` so it is not dropped: dropping it invalidates `surface`. + (windows, Some(instance), adapters, surfaces) + }; + + #[cfg(target_arch = "wasm32")] + let (_window, instance, mut adapters, surface) = { + let (window, surface) = { + let window = wb.build(&event_loop).unwrap(); + web_sys::window() + .unwrap() + .document() + .unwrap() + .body() + .unwrap() + .append_child(&winit::platform::web::WindowExtWebSys::canvas(&window)) + .unwrap(); + let surface = back::Surface::from_raw_handle(&window); + (window, surface) + }; + + let adapters = surface.enumerate_adapters(); + (window, None, adapters, surface) + }; + + for adapter in &adapters { + println!("{:?}", adapter.info); + } + + let adapter = adapters.remove(0); + + let mut renderer = Renderer::new(instance, windows, surfaces, adapter); + + renderer.render(); + + // It is important that the closure move captures the Renderer, + // otherwise it will not be dropped when the event loop exits. + event_loop.run(move |event, _, control_flow| { + *control_flow = winit::event_loop::ControlFlow::Wait; + + match event { + winit::event::Event::WindowEvent { event, window_id, .. } => match event { + winit::event::WindowEvent::CloseRequested => { + *control_flow = winit::event_loop::ControlFlow::Exit + } + winit::event::WindowEvent::KeyboardInput { + input: + winit::event::KeyboardInput { + virtual_keycode: Some(winit::event::VirtualKeyCode::Escape), + .. + }, + .. + } => *control_flow = winit::event_loop::ControlFlow::Exit, + winit::event::WindowEvent::Resized(dims) => { + let window_index = renderer.get_window_index(window_id); + renderer.dimensions[window_index] = window::Extent2D { + width: dims.width, + height: dims.height, + }; + renderer.recreate_swapchains(); + } + _ => {} + }, + winit::event::Event::RedrawEventsCleared => { + renderer.render(); + } + _ => {} + } + }); +} + +struct Renderer { + instance: Option, + device: B::Device, + queue_group: QueueGroup, + desc_pool: ManuallyDrop, + surfaces: ManuallyDrop<[B::Surface; 2]>, + adapter: hal::adapter::Adapter, + windows: [winit::window::Window; 2], + format: [hal::format::Format; 2], + dimensions: [window::Extent2D; 2], + viewports: [pso::Viewport; 2], + render_pass: ManuallyDrop, + pipeline: ManuallyDrop, + pipeline_layout: ManuallyDrop, + desc_set: B::DescriptorSet, + set_layout: ManuallyDrop, + submission_complete_semaphores: Vec, + submission_complete_fences: Vec, + cmd_pools: Vec, + cmd_buffers: Vec, + vertex_buffer: ManuallyDrop, + image_upload_buffer: ManuallyDrop, + image_logo: ManuallyDrop, + image_srv: ManuallyDrop, + buffer_memory: ManuallyDrop, + image_memory: ManuallyDrop, + image_upload_memory: ManuallyDrop, + sampler: ManuallyDrop, + frames_in_flight: usize, + frame: u64, +} + +impl Renderer +where + B: hal::Backend, +{ + fn new( + instance: Option, + windows: [winit::window::Window; 2], + mut surfaces: [B::Surface; 2], + adapter: hal::adapter::Adapter, + ) -> Renderer { + let memory_types = adapter.physical_device.memory_properties().memory_types; + let limits = adapter.physical_device.limits(); + + // Build a new device and associated command queues + let family = adapter + .queue_families + .iter() + .find(|family| { + surfaces[0].supports_queue_family(family) + && surfaces[1].supports_queue_family(family) + && family.queue_type().supports_graphics() + }) + .unwrap(); + dbg!(adapter.physical_device.features()); + let mut gpu = unsafe { + adapter + .physical_device + .open(&[(family, &[1.0])], hal::Features::empty() | hal::Features::MULTIVIEW) + .unwrap() + }; + + let mut queue_group = gpu.queue_groups.pop().unwrap(); + let device = gpu.device; + + let mut command_pool = unsafe { + device.create_command_pool(queue_group.family, pool::CommandPoolCreateFlags::empty()) + } + .expect("Can't create command pool"); + + // Setup renderpass and pipeline + let set_layout = ManuallyDrop::new( + unsafe { + device.create_descriptor_set_layout( + &[ + pso::DescriptorSetLayoutBinding { + binding: 0, + ty: pso::DescriptorType::Image { + ty: pso::ImageDescriptorType::Sampled { + with_sampler: false, + }, + }, + count: 1, + stage_flags: ShaderStageFlags::FRAGMENT, + immutable_samplers: false, + }, + pso::DescriptorSetLayoutBinding { + binding: 1, + ty: pso::DescriptorType::Sampler, + count: 1, + stage_flags: ShaderStageFlags::FRAGMENT, + immutable_samplers: false, + }, + ], + &[], + ) + } + .expect("Can't create descriptor set layout"), + ); + + // Descriptors + let mut desc_pool = ManuallyDrop::new( + unsafe { + device.create_descriptor_pool( + 1, // sets + &[ + pso::DescriptorRangeDesc { + ty: pso::DescriptorType::Image { + ty: pso::ImageDescriptorType::Sampled { + with_sampler: false, + }, + }, + count: 1, + }, + pso::DescriptorRangeDesc { + ty: pso::DescriptorType::Sampler, + count: 1, + }, + ], + pso::DescriptorPoolCreateFlags::empty(), + ) + } + .expect("Can't create descriptor pool"), + ); + let desc_set = unsafe { desc_pool.allocate_set(&set_layout) }.unwrap(); + + // Buffer allocations + println!("Memory types: {:?}", memory_types); + let non_coherent_alignment = limits.non_coherent_atom_size as u64; + + let buffer_stride = mem::size_of::() as u64; + let buffer_len = QUAD.len() as u64 * buffer_stride; + assert_ne!(buffer_len, 0); + let padded_buffer_len = ((buffer_len + non_coherent_alignment - 1) + / non_coherent_alignment) + * non_coherent_alignment; + + let mut vertex_buffer = ManuallyDrop::new( + unsafe { device.create_buffer(padded_buffer_len, buffer::Usage::VERTEX) }.unwrap(), + ); + + let buffer_req = unsafe { device.get_buffer_requirements(&vertex_buffer) }; + + let upload_type = memory_types + .iter() + .enumerate() + .position(|(id, mem_type)| { + // type_mask is a bit field where each bit represents a memory type. If the bit is set + // to 1 it means we can use that type for our buffer. So this code finds the first + // memory type that has a `1` (or, is allowed), and is visible to the CPU. + buffer_req.type_mask & (1 << id) != 0 + && mem_type.properties.contains(m::Properties::CPU_VISIBLE) + }) + .unwrap() + .into(); + + // TODO: check transitions: read/write mapping and vertex buffer read + let buffer_memory = unsafe { + let memory = device + .allocate_memory(upload_type, buffer_req.size) + .unwrap(); + device + .bind_buffer_memory(&memory, 0, &mut vertex_buffer) + .unwrap(); + let mapping = device.map_memory(&memory, m::Segment::ALL).unwrap(); + ptr::copy_nonoverlapping(QUAD.as_ptr() as *const u8, mapping, buffer_len as usize); + device + .flush_mapped_memory_ranges(iter::once((&memory, m::Segment::ALL))) + .unwrap(); + device.unmap_memory(&memory); + ManuallyDrop::new(memory) + }; + + // Image + let img_data = include_bytes!("data/logo.png"); + + let img = image::load(Cursor::new(&img_data[..]), image::PNG) + .unwrap() + .to_rgba(); + let (width, height) = img.dimensions(); + let kind = i::Kind::D2(width as i::Size, height as i::Size, 1, 1); + let row_alignment_mask = limits.optimal_buffer_copy_pitch_alignment as u32 - 1; + let image_stride = 4usize; + let row_pitch = (width * image_stride as u32 + row_alignment_mask) & !row_alignment_mask; + let upload_size = (height * row_pitch) as u64; + let padded_upload_size = ((upload_size + non_coherent_alignment - 1) + / non_coherent_alignment) + * non_coherent_alignment; + + let mut image_upload_buffer = ManuallyDrop::new( + unsafe { device.create_buffer(padded_upload_size, buffer::Usage::TRANSFER_SRC) } + .unwrap(), + ); + let image_mem_reqs = unsafe { device.get_buffer_requirements(&image_upload_buffer) }; + + // copy image data into staging buffer + let image_upload_memory = unsafe { + let memory = device + .allocate_memory(upload_type, image_mem_reqs.size) + .unwrap(); + device + .bind_buffer_memory(&memory, 0, &mut image_upload_buffer) + .unwrap(); + let mapping = device.map_memory(&memory, m::Segment::ALL).unwrap(); + for y in 0 .. height as usize { + let row = &(*img)[y * (width as usize) * image_stride + .. (y + 1) * (width as usize) * image_stride]; + ptr::copy_nonoverlapping( + row.as_ptr(), + mapping.offset(y as isize * row_pitch as isize), + width as usize * image_stride, + ); + } + device + .flush_mapped_memory_ranges(iter::once((&memory, m::Segment::ALL))) + .unwrap(); + device.unmap_memory(&memory); + ManuallyDrop::new(memory) + }; + + let mut image_logo = ManuallyDrop::new( + unsafe { + device.create_image( + kind, + 1, + ColorFormat::SELF, + i::Tiling::Optimal, + i::Usage::TRANSFER_DST | i::Usage::SAMPLED, + i::ViewCapabilities::empty(), + ) + } + .unwrap(), + ); + let image_req = unsafe { device.get_image_requirements(&image_logo) }; + + let device_type = memory_types + .iter() + .enumerate() + .position(|(id, memory_type)| { + image_req.type_mask & (1 << id) != 0 + && memory_type.properties.contains(m::Properties::DEVICE_LOCAL) + }) + .unwrap() + .into(); + let image_memory = ManuallyDrop::new( + unsafe { device.allocate_memory(device_type, image_req.size) }.unwrap(), + ); + + unsafe { device.bind_image_memory(&image_memory, 0, &mut image_logo) }.unwrap(); + let image_srv = ManuallyDrop::new( + unsafe { + device.create_image_view( + &image_logo, + i::ViewKind::D2, + ColorFormat::SELF, + Swizzle::NO, + COLOR_RANGE.clone(), + ) + } + .unwrap(), + ); + + let sampler = ManuallyDrop::new( + unsafe { + device.create_sampler(&i::SamplerDesc::new(i::Filter::Linear, i::WrapMode::Clamp)) + } + .expect("Can't create sampler"), + ); + + unsafe { + device.write_descriptor_sets(vec![ + pso::DescriptorSetWrite { + set: &desc_set, + binding: 0, + array_offset: 0, + descriptors: Some(pso::Descriptor::Image( + &*image_srv, + i::Layout::ShaderReadOnlyOptimal, + )), + }, + pso::DescriptorSetWrite { + set: &desc_set, + binding: 1, + array_offset: 0, + descriptors: Some(pso::Descriptor::Sampler(&*sampler)), + }, + ]); + } + + // copy buffer to texture + let mut copy_fence = device.create_fence(false).expect("Could not create fence"); + unsafe { + let mut cmd_buffer = command_pool.allocate_one(command::Level::Primary); + cmd_buffer.begin_primary(command::CommandBufferFlags::ONE_TIME_SUBMIT); + + let image_barrier = m::Barrier::Image { + states: (i::Access::empty(), i::Layout::Undefined) + .. (i::Access::TRANSFER_WRITE, i::Layout::TransferDstOptimal), + target: &*image_logo, + families: None, + range: COLOR_RANGE.clone(), + }; + + cmd_buffer.pipeline_barrier( + PipelineStage::TOP_OF_PIPE .. PipelineStage::TRANSFER, + m::Dependencies::empty(), + &[image_barrier], + ); + + cmd_buffer.copy_buffer_to_image( + &image_upload_buffer, + &image_logo, + i::Layout::TransferDstOptimal, + &[command::BufferImageCopy { + buffer_offset: 0, + buffer_width: row_pitch / (image_stride as u32), + buffer_height: height as u32, + image_layers: i::SubresourceLayers { + aspects: f::Aspects::COLOR, + level: 0, + layers: 0 .. 1, + }, + image_offset: i::Offset { x: 0, y: 0, z: 0 }, + image_extent: i::Extent { + width, + height, + depth: 1, + }, + }], + ); + + let image_barrier = m::Barrier::Image { + states: (i::Access::TRANSFER_WRITE, i::Layout::TransferDstOptimal) + .. (i::Access::SHADER_READ, i::Layout::ShaderReadOnlyOptimal), + target: &*image_logo, + families: None, + range: COLOR_RANGE.clone(), + }; + cmd_buffer.pipeline_barrier( + PipelineStage::TRANSFER .. PipelineStage::FRAGMENT_SHADER, + m::Dependencies::empty(), + &[image_barrier], + ); + + cmd_buffer.finish(); + + queue_group.queues[0] + .submit_without_semaphores(Some(&cmd_buffer), Some(&mut copy_fence)); + + device + .wait_for_fence(©_fence, !0) + .expect("Can't wait for fence"); + } + + unsafe { + device.destroy_fence(copy_fence); + } + + let caps = [ + surfaces[0].capabilities(&adapter.physical_device), + surfaces[1].capabilities(&adapter.physical_device), + ]; + let formats = [ + surfaces[0].supported_formats(&adapter.physical_device), + surfaces[1].supported_formats(&adapter.physical_device), + ]; + println!("formats: {:?}", formats); + let mut format = (0..2).map(|i| { + Some(formats[i].clone().map_or(f::Format::Rgba8Srgb, |formats| { + formats + .iter() + .find(|format| format.base_format().1 == ChannelType::Srgb) + .map(|format| *format) + .unwrap_or(formats[0]) + })) + }).collect::>(); + let format = [format[0].take().unwrap(), format[1].take().unwrap()]; + + let swap_configs = [ + window::SwapchainConfig::from_caps(&caps[0], format[0], DIMS), + window::SwapchainConfig::from_caps(&caps[1], format[1], DIMS), + ]; + println!("{:?}", swap_configs); + let extents = [ + swap_configs[0].extent, + swap_configs[1].extent, + ]; + for i in 0..2 { + unsafe { + surfaces[i] + .configure_swapchain(&device, swap_configs[i].clone()) + .expect("Can't configure swapchain"); + } + } + + let render_pass = { + let attachments = [ + pass::Attachment { + format: Some(format[0]), + samples: 1, + ops: pass::AttachmentOps::new( + pass::AttachmentLoadOp::Clear, + pass::AttachmentStoreOp::Store, + ), + stencil_ops: pass::AttachmentOps::DONT_CARE, + layouts: i::Layout::Undefined .. i::Layout::Present, + }, + pass::Attachment { + format: Some(format[1]), + samples: 1, + ops: pass::AttachmentOps::new( + pass::AttachmentLoadOp::Clear, + pass::AttachmentStoreOp::Store, + ), + stencil_ops: pass::AttachmentOps::DONT_CARE, + layouts: i::Layout::Undefined .. i::Layout::Present, + }, + ]; + + let subpass = pass::SubpassDesc { + colors: &[(0, i::Layout::ColorAttachmentOptimal)], + depth_stencil: None, + inputs: &[], + resolves: &[], + preserves: &[], + view_mask: 0b11, + }; + + ManuallyDrop::new( + unsafe { device.create_render_pass(&attachments, &[subpass], &[], Some(&[0b11])) } + .expect("Can't create render pass"), + ) + }; + + // Define maximum number of frames we want to be able to be "in flight" (being computed + // simultaneously) at once + let frames_in_flight = 3; + + // The number of the rest of the resources is based on the frames in flight. + let mut submission_complete_semaphores = Vec::with_capacity(frames_in_flight * 2); + let mut submission_complete_fences = Vec::with_capacity(frames_in_flight * 2); + // Note: We don't really need a different command pool per frame in such a simple demo like this, + // but in a more 'real' application, it's generally seen as optimal to have one command pool per + // thread per frame. There is a flag that lets a command pool reset individual command buffers + // which are created from it, but by default the whole pool (and therefore all buffers in it) + // must be reset at once. Furthermore, it is often the case that resetting a whole pool is actually + // faster and more efficient for the hardware than resetting individual command buffers, so it's + // usually best to just make a command pool for each set of buffers which need to be reset at the + // same time (each frame). In our case, each pool will only have one command buffer created from it, + // though. + let mut cmd_pools = Vec::with_capacity(frames_in_flight * 2); + let mut cmd_buffers = Vec::with_capacity(frames_in_flight * 2); + + cmd_pools.push(command_pool); + for _ in 1 .. frames_in_flight * 2 { + unsafe { + cmd_pools.push( + device + .create_command_pool( + queue_group.family, + pool::CommandPoolCreateFlags::empty(), + ) + .expect("Can't create command pool"), + ); + } + } + + for i in 0 .. frames_in_flight * 2 { + submission_complete_semaphores.push( + device + .create_semaphore() + .expect("Could not create semaphore"), + ); + submission_complete_fences + .push(device.create_fence(true).expect("Could not create fence")); + cmd_buffers.push(unsafe { cmd_pools[i].allocate_one(command::Level::Primary) }); + } + + let pipeline_layout = ManuallyDrop::new( + unsafe { + device.create_pipeline_layout( + iter::once(&*set_layout), + &[(pso::ShaderStageFlags::VERTEX, 0 .. 8)], + ) + } + .expect("Can't create pipeline layout"), + ); + let pipeline = { + let vs_module = { + let glsl = std::fs::read_to_string("quad-multiview/data/quad.vert").unwrap(); + let file = + glsl_to_spirv::compile(&glsl, glsl_to_spirv::ShaderType::Vertex).unwrap(); + let spirv = auxil::read_spirv(file).unwrap(); + unsafe { device.create_shader_module(&spirv) }.unwrap() + }; + let fs_module = { + let glsl = std::fs::read_to_string("quad-multiview/data/quad.frag").unwrap(); + let file = + glsl_to_spirv::compile(&glsl, glsl_to_spirv::ShaderType::Fragment).unwrap(); + let spirv = auxil::read_spirv(file).unwrap(); + unsafe { device.create_shader_module(&spirv) }.unwrap() + }; + + let pipeline = { + let (vs_entry, fs_entry) = ( + pso::EntryPoint { + entry: ENTRY_NAME, + module: &vs_module, + specialization: hal::spec_const_list![0.8f32], + }, + pso::EntryPoint { + entry: ENTRY_NAME, + module: &fs_module, + specialization: pso::Specialization::default(), + }, + ); + + let subpass = Subpass { + index: 0, + main_pass: &*render_pass, + }; + + let vertex_buffers = vec![ + pso::VertexBufferDesc { + binding: 0, + stride: mem::size_of::() as u32, + rate: VertexInputRate::Vertex, + }, + ]; + + let attributes = vec![ + pso::AttributeDesc { + location: 0, + binding: 0, + element: pso::Element { + format: f::Format::Rg32Sfloat, + offset: 0, + }, + }, + pso::AttributeDesc { + location: 1, + binding: 0, + element: pso::Element { + format: f::Format::Rg32Sfloat, + offset: 8, + }, + }, + ]; + + let mut pipeline_desc = pso::GraphicsPipelineDesc::new( + pso::PrimitiveAssembler::Vertex { + buffers: &vertex_buffers, + attributes: &attributes, + input_assembler: pso::InputAssemblerDesc { + primitive: pso::Primitive::TriangleList, + with_adjacency: false, + restart_index: None, + }, + vertex: vs_entry, + geometry: None, + tessellation: None, + }, + pso::Rasterizer::FILL, + Some(fs_entry), + &*pipeline_layout, + subpass, + ); + + pipeline_desc.blender.targets.push(pso::ColorBlendDesc { + mask: pso::ColorMask::ALL, + blend: Some(pso::BlendState::ALPHA), + }); + + unsafe { device.create_graphics_pipeline(&pipeline_desc, None) } + }; + + unsafe { + device.destroy_shader_module(vs_module); + } + unsafe { + device.destroy_shader_module(fs_module); + } + + ManuallyDrop::new(pipeline.unwrap()) + }; + + // Rendering setup + let viewports = [ + pso::Viewport { + rect: pso::Rect { + x: 0, + y: 0, + w: extents[0].width as _, + h: extents[0].height as _, + }, + depth: 0.0 .. 1.0, + }, + pso::Viewport { + rect: pso::Rect { + x: 0, + y: 0, + w: extents[1].width as _, + h: extents[1].height as _, + }, + depth: 0.0 .. 1.0, + }, + ]; + + Renderer { + instance, + device, + queue_group, + desc_pool, + windows, + surfaces: ManuallyDrop::new(surfaces), + adapter, + format, + dimensions: [DIMS, DIMS], + viewports, + render_pass, + pipeline, + pipeline_layout, + desc_set, + set_layout, + submission_complete_semaphores, + submission_complete_fences, + cmd_pools, + cmd_buffers, + vertex_buffer, + image_upload_buffer, + image_logo, + image_srv, + buffer_memory, + image_memory, + image_upload_memory, + sampler, + frames_in_flight, + frame: 0, + } + } + + fn get_window_index(&self, window_id: winit::window::WindowId) -> usize { + if window_id == self.windows[0].id() { 0 } else { 1 } + } + + fn recreate_swapchains(&mut self) { + for i in 0..2 { + let caps = self.surfaces[i].capabilities(&self.adapter.physical_device); + let swap_config = window::SwapchainConfig::from_caps(&caps, self.format[i], self.dimensions[i]); + println!("{:?}", swap_config); + let extent = swap_config.extent.to_extent(); + + unsafe { + self.surfaces[i] + .configure_swapchain(&self.device, swap_config) + .expect("Can't create swapchain"); + } + + self.viewports[i].rect.w = extent.width as _; + self.viewports[i].rect.h = extent.height as _; + } + } + + fn render(&mut self) { + let surface_images = [ + unsafe { + match self.surfaces[0].acquire_image(!0) { + Ok((image, _)) => image, + Err(_) => { + self.recreate_swapchains(); + return; + } + } + }, + unsafe { + match self.surfaces[1].acquire_image(!0) { + Ok((image, _)) => image, + Err(_) => { + self.recreate_swapchains(); + return; + } + } + }, + ]; + + let framebuffer = unsafe { + self.device + .create_framebuffer( + &self.render_pass, + vec![surface_images[0].borrow(), surface_images[1].borrow()], + i::Extent { + width: self.dimensions[0].width, + height: self.dimensions[0].height, + depth: 1, + }, + ) + .unwrap() + }; + + // Compute index into our resource ring buffers based on the frame number + // and number of frames in flight. Pay close attention to where this index is needed + // versus when the swapchain image index we got from acquire_image is needed. + let frame_idx = self.frame as usize % self.frames_in_flight; + + // Wait for the fence of the previous submission of this frame and reset it; ensures we are + // submitting only up to maximum number of frames_in_flight if we are submitting faster than + // the gpu can keep up with. This would also guarantee that any resources which need to be + // updated with a CPU->GPU data copy are not in use by the GPU, so we can perform those updates. + // In this case there are none to be done, however. + unsafe { + for i in 0..2 { + let fence = &self.submission_complete_fences[frame_idx * 2 + i]; + self.device + .wait_for_fence(fence, !0) + .expect("Failed to wait for fence"); + self.device + .reset_fence(fence) + .expect("Failed to reset fence"); + self.cmd_pools[frame_idx].reset(false); + } + } + + // Rendering + let cmd_buffer = &mut self.cmd_buffers[frame_idx]; + unsafe { + cmd_buffer.begin_primary(command::CommandBufferFlags::ONE_TIME_SUBMIT); + + cmd_buffer.set_viewports(0, &[self.viewports[0].clone()]); + cmd_buffer.set_scissors(0, &[self.viewports[0].rect]); + cmd_buffer.bind_graphics_pipeline(&self.pipeline); + cmd_buffer.bind_vertex_buffers( + 0, + iter::once((&*self.vertex_buffer, buffer::SubRange::WHOLE)), + ); + cmd_buffer.bind_graphics_descriptor_sets( + &self.pipeline_layout, + 0, + iter::once(&self.desc_set), + &[], + ); + + cmd_buffer.begin_render_pass( + &self.render_pass, + &framebuffer, + self.viewports[0].rect, + &[ + command::ClearValue { + color: command::ClearColor { + float32: [0.8, 0.8, 0.8, 1.0], + }, + }, + command::ClearValue { + color: command::ClearColor { + float32: [0.8, 0.8, 0.8, 1.0], + }, + }, + ], + command::SubpassContents::Inline, + ); + cmd_buffer.draw(0 .. 6, 0 .. 1); + cmd_buffer.end_render_pass(); + cmd_buffer.finish(); + + let submission = Submission { + command_buffers: iter::once(&*cmd_buffer), + wait_semaphores: None, + signal_semaphores: vec![ + &self.submission_complete_semaphores[frame_idx * 2 + 0], + ], + }; + self.queue_group.queues[0].submit( + submission, + Some(&self.submission_complete_fences[frame_idx * 2 + 0]), + ); + let submission = Submission { + command_buffers: iter::once(&*cmd_buffer), + wait_semaphores: None, + signal_semaphores: vec![ + &self.submission_complete_semaphores[frame_idx * 2 + 1], + ], + }; + self.queue_group.queues[0].submit( + submission, + Some(&self.submission_complete_fences[frame_idx * 2 + 1]), + ); + + // present frames + let [surface_image_0, surface_image_1] = surface_images; + let mut result = self.queue_group.queues[0].present_surface( + &mut self.surfaces[0], + surface_image_0, + Some(&self.submission_complete_semaphores[frame_idx * 2 + 0]), + ).is_ok(); + result &= self.queue_group.queues[0].present_surface( + &mut self.surfaces[1], + surface_image_1, + Some(&self.submission_complete_semaphores[frame_idx * 2 + 1]), + ).is_ok(); + + self.device.destroy_framebuffer(framebuffer); + + if !result { + self.recreate_swapchains(); + } + } + + // Increment our frame + self.frame += 1; + } +} + +impl Drop for Renderer +where + B: hal::Backend, +{ + fn drop(&mut self) { + self.device.wait_idle().unwrap(); + unsafe { + // TODO: When ManuallyDrop::take (soon to be renamed to ManuallyDrop::read) is stabilized we should use that instead. + self.device + .destroy_descriptor_pool(ManuallyDrop::into_inner(ptr::read(&self.desc_pool))); + self.device + .destroy_descriptor_set_layout(ManuallyDrop::into_inner(ptr::read( + &self.set_layout, + ))); + + self.device + .destroy_buffer(ManuallyDrop::into_inner(ptr::read(&self.vertex_buffer))); + self.device + .destroy_buffer(ManuallyDrop::into_inner(ptr::read( + &self.image_upload_buffer, + ))); + self.device + .destroy_image(ManuallyDrop::into_inner(ptr::read(&self.image_logo))); + self.device + .destroy_image_view(ManuallyDrop::into_inner(ptr::read(&self.image_srv))); + self.device + .destroy_sampler(ManuallyDrop::into_inner(ptr::read(&self.sampler))); + for p in self.cmd_pools.drain(..) { + self.device.destroy_command_pool(p); + } + for s in self.submission_complete_semaphores.drain(..) { + self.device.destroy_semaphore(s); + } + for f in self.submission_complete_fences.drain(..) { + self.device.destroy_fence(f); + } + self.device + .destroy_render_pass(ManuallyDrop::into_inner(ptr::read(&self.render_pass))); + for i in 0..2 { + self.surfaces[i].unconfigure_swapchain(&self.device); + } + self.device + .free_memory(ManuallyDrop::into_inner(ptr::read(&self.buffer_memory))); + self.device + .free_memory(ManuallyDrop::into_inner(ptr::read(&self.image_memory))); + self.device.free_memory(ManuallyDrop::into_inner(ptr::read( + &self.image_upload_memory, + ))); + self.device + .destroy_graphics_pipeline(ManuallyDrop::into_inner(ptr::read(&self.pipeline))); + self.device + .destroy_pipeline_layout(ManuallyDrop::into_inner(ptr::read( + &self.pipeline_layout, + ))); + if let Some(instance) = &self.instance { + let [surface_0, surface_1] = ManuallyDrop::into_inner(ptr::read(&self.surfaces)); + instance.destroy_surface(surface_0); + instance.destroy_surface(surface_1); + } + } + println!("DROPPED!"); + } +} diff --git a/examples/quad-multiview/screenshot.png b/examples/quad-multiview/screenshot.png new file mode 100644 index 00000000000..0ff90a13af0 Binary files /dev/null and b/examples/quad-multiview/screenshot.png differ diff --git a/examples/quad/main.rs b/examples/quad/main.rs index 74eca6893bd..79fd4f582e2 100644 --- a/examples/quad/main.rs +++ b/examples/quad/main.rs @@ -584,10 +584,11 @@ where inputs: &[], resolves: &[], preserves: &[], + view_mask: 0, }; ManuallyDrop::new( - unsafe { device.create_render_pass(&[attachment], &[subpass], &[]) } + unsafe { device.create_render_pass(&[attachment], &[subpass], &[], None) } .expect("Can't create render pass"), ) }; diff --git a/src/backend/dx11/src/device.rs b/src/backend/dx11/src/device.rs index 5e90dd4eefb..0e9ab085732 100644 --- a/src/backend/dx11/src/device.rs +++ b/src/backend/dx11/src/device.rs @@ -812,6 +812,7 @@ impl device::Device for Device { attachments: IA, subpasses: IS, _dependencies: ID, + _correlation_masks: Option<&[u32]>, ) -> Result where IA: IntoIterator, diff --git a/src/backend/dx12/src/device.rs b/src/backend/dx12/src/device.rs index d9720d19576..e83e8463fc4 100644 --- a/src/backend/dx12/src/device.rs +++ b/src/backend/dx12/src/device.rs @@ -1249,6 +1249,7 @@ impl d::Device for Device { attachments: IA, subpasses: IS, dependencies: ID, + _correlation_masks: Option<&[u32]>, ) -> Result where IA: IntoIterator, diff --git a/src/backend/empty/src/lib.rs b/src/backend/empty/src/lib.rs index 36e3bfd7de5..b3a910e5a9d 100644 --- a/src/backend/empty/src/lib.rs +++ b/src/backend/empty/src/lib.rs @@ -230,6 +230,7 @@ impl device::Device for Device { _: IA, _: IS, _: ID, + _: Option<&[u32]>, ) -> Result<(), device::OutOfMemory> where IA: IntoIterator, diff --git a/src/backend/gl/src/device.rs b/src/backend/gl/src/device.rs index d0e0244dace..7f48b4716ed 100644 --- a/src/backend/gl/src/device.rs +++ b/src/backend/gl/src/device.rs @@ -605,6 +605,7 @@ impl d::Device for Device { attachments: IA, subpasses: IS, _dependencies: ID, + correlation_masks: Option<&[u32]>, ) -> Result where IA: IntoIterator, diff --git a/src/backend/metal/src/device.rs b/src/backend/metal/src/device.rs index 84d67a09d8a..dd477e75e6d 100644 --- a/src/backend/metal/src/device.rs +++ b/src/backend/metal/src/device.rs @@ -971,6 +971,7 @@ impl hal::device::Device for Device { attachments: IA, subpasses: IS, _dependencies: ID, + _correlation_masks: Option<&[u32]>, ) -> Result where IA: IntoIterator, diff --git a/src/backend/vulkan/src/device.rs b/src/backend/vulkan/src/device.rs index dc514faf68f..413066eff59 100644 --- a/src/backend/vulkan/src/device.rs +++ b/src/backend/vulkan/src/device.rs @@ -555,6 +555,7 @@ impl d::Device for Device { attachments: IA, subpasses: IS, dependencies: ID, + correlation_masks: Option<&[u32]>, ) -> Result where IA: IntoIterator, @@ -587,109 +588,146 @@ impl d::Device for Device { final_layout: conv::map_image_layout(attachment.layouts.end), } }); + let dependencies = dependencies.into_iter(); - let dependencies = dependencies - .into_iter() - .map(|subpass_dep| { - let sdep = subpass_dep.borrow(); - // TODO: checks - vk::SubpassDependency { - src_subpass: sdep - .passes - .start - .map_or(vk::SUBPASS_EXTERNAL, |id| id as u32), - dst_subpass: sdep.passes.end.map_or(vk::SUBPASS_EXTERNAL, |id| id as u32), - src_stage_mask: conv::map_pipeline_stage(sdep.stages.start), - dst_stage_mask: conv::map_pipeline_stage(sdep.stages.end), - src_access_mask: conv::map_image_access(sdep.accesses.start), - dst_access_mask: conv::map_image_access(sdep.accesses.end), - dependency_flags: mem::transmute(sdep.flags), - } - }); + let (clear_attachments_mask, result) = inplace_it::inplace_or_alloc_array(dependencies.len(), |uninit_guard_dependencies| { + let dependencies = uninit_guard_dependencies.init_with_iter(dependencies); - let (clear_attachments_mask, result) = inplace_it::inplace_or_alloc_array(attachments.len(), |uninit_guard| { - let attachments = uninit_guard.init_with_iter(attachments); - - let clear_attachments_mask = attachments + let vk_dependencies = dependencies .iter() - .enumerate() - .filter_map(|(i, at)| { - if at.load_op == vk::AttachmentLoadOp::CLEAR - || at.stencil_load_op == vk::AttachmentLoadOp::CLEAR - { - Some(1 << i as u64) - } else { - None + .map(|subpass_dep| { + let sdep = subpass_dep.borrow(); + // TODO: checks + vk::SubpassDependency { + src_subpass: sdep + .passes + .start + .map_or(vk::SUBPASS_EXTERNAL, |id| id as u32), + dst_subpass: sdep.passes.end.map_or(vk::SUBPASS_EXTERNAL, |id| id as u32), + src_stage_mask: conv::map_pipeline_stage(sdep.stages.start), + dst_stage_mask: conv::map_pipeline_stage(sdep.stages.end), + src_access_mask: conv::map_image_access(sdep.accesses.start), + dst_access_mask: conv::map_image_access(sdep.accesses.end), + dependency_flags: mem::transmute(sdep.flags), } - }) - .sum(); + }); - let attachment_refs = subpasses - .into_iter() - .map(|subpass| { - let subpass = subpass.borrow(); - fn make_ref(&(id, layout): &pass::AttachmentRef) -> vk::AttachmentReference { - vk::AttachmentReference { - attachment: id as _, - layout: conv::map_image_layout(layout), - } - } - let colors = subpass.colors.iter().map(make_ref).collect::>(); - let depth_stencil = subpass.depth_stencil.map(make_ref); - let inputs = subpass.inputs.iter().map(make_ref).collect::>(); - let preserves = subpass - .preserves - .iter() - .map(|&id| id as u32) - .collect::>(); - let resolves = subpass.resolves.iter().map(make_ref).collect::>(); - - (colors, depth_stencil, inputs, preserves, resolves) - }) - .collect::>(); + let (clear_attachments_mask, result) = inplace_it::inplace_or_alloc_array(attachments.len(), |uninit_guard_attachments| { + let attachments = uninit_guard_attachments.init_with_iter(attachments); - let subpasses = attachment_refs - .iter() - .map(|(colors, depth_stencil, inputs, preserves, resolves)| { - vk::SubpassDescription { - flags: vk::SubpassDescriptionFlags::empty(), - pipeline_bind_point: vk::PipelineBindPoint::GRAPHICS, - input_attachment_count: inputs.len() as u32, - p_input_attachments: inputs.as_ptr(), - color_attachment_count: colors.len() as u32, - p_color_attachments: colors.as_ptr(), - p_resolve_attachments: if resolves.is_empty() { - ptr::null() + let clear_attachments_mask = attachments + .iter() + .enumerate() + .filter_map(|(i, at)| { + if at.load_op == vk::AttachmentLoadOp::CLEAR + || at.stencil_load_op == vk::AttachmentLoadOp::CLEAR + { + Some(1 << i as u64) } else { - resolves.as_ptr() - }, - p_depth_stencil_attachment: match depth_stencil { - Some(ref aref) => aref as *const _, - None => ptr::null(), - }, - preserve_attachment_count: preserves.len() as u32, - p_preserve_attachments: preserves.as_ptr(), - } - }) - .collect::>(); + None + } + }) + .sum(); + + let attachment_refs = subpasses + .into_iter() + .map(|subpass| { + let subpass = subpass.borrow(); + fn make_ref(&(id, layout): &pass::AttachmentRef) -> vk::AttachmentReference { + vk::AttachmentReference { + attachment: id as _, + layout: conv::map_image_layout(layout), + } + } + let colors = subpass.colors.iter().map(make_ref).collect::>(); + let depth_stencil = subpass.depth_stencil.map(make_ref); + let inputs = subpass.inputs.iter().map(make_ref).collect::>(); + let preserves = subpass + .preserves + .iter() + .map(|&id| id as u32) + .collect::>(); + let resolves = subpass.resolves.iter().map(make_ref).collect::>(); + + (colors, depth_stencil, inputs, preserves, resolves, subpass.view_mask) + }) + .collect::>(); - let result = inplace_it::inplace_or_alloc_array(dependencies.len(), |uninit_guard| { - let dependencies = - uninit_guard.init_with_iter(dependencies); + let subpasses = attachment_refs + .iter() + .map(|(colors, depth_stencil, inputs, preserves, resolves, _view_mask)| { + vk::SubpassDescription { + flags: vk::SubpassDescriptionFlags::empty(), + pipeline_bind_point: vk::PipelineBindPoint::GRAPHICS, + input_attachment_count: inputs.len() as u32, + p_input_attachments: inputs.as_ptr(), + color_attachment_count: colors.len() as u32, + p_color_attachments: colors.as_ptr(), + p_resolve_attachments: if resolves.is_empty() { + ptr::null() + } else { + resolves.as_ptr() + }, + p_depth_stencil_attachment: match depth_stencil { + Some(ref aref) => aref as *const _, + None => ptr::null(), + }, + preserve_attachment_count: preserves.len() as u32, + p_preserve_attachments: preserves.as_ptr(), + } + }) + .collect::>(); + + let multiview_enabled = correlation_masks.is_some() && self.shared.features.contains(Features::MULTIVIEW); + + let view_masks = if multiview_enabled { + Some( + attachment_refs + .iter() + .map(|(_colors, _depth_stencil, _inputs, _preserves, _resolves, view_mask)| { + *view_mask + }) + .collect::>() + ) + } else { + None + }; - let info = vk::RenderPassCreateInfo { - s_type: vk::StructureType::RENDER_PASS_CREATE_INFO, - p_next: ptr::null(), - flags: vk::RenderPassCreateFlags::empty(), - attachment_count: attachments.len() as u32, - p_attachments: attachments.as_ptr(), - subpass_count: subpasses.len() as u32, - p_subpasses: subpasses.as_ptr(), - dependency_count: dependencies.len() as u32, - p_dependencies: dependencies.as_ptr(), + let view_offsets = if multiview_enabled { + Some( + dependencies + .iter() + .map(|dependency| dependency.borrow().view_offset) + .collect::>(), + ) + } else { + None }; - self.shared.raw.create_render_pass(&info, None) + let result = inplace_it::inplace_or_alloc_array(vk_dependencies.len(), |uninit_guard| { + let vk_dependencies = uninit_guard.init_with_iter(vk_dependencies); + + let mut info_builder = vk::RenderPassCreateInfo::builder() + .flags(vk::RenderPassCreateFlags::empty()) + .attachments(&attachments) + .subpasses(&subpasses) + .dependencies(&vk_dependencies); + let mut multiview; + + if multiview_enabled { + multiview = vk::RenderPassMultiviewCreateInfo::builder() + .view_masks(&view_masks.unwrap()) + .view_offsets(&view_offsets.unwrap()) + .correlation_masks(correlation_masks.unwrap()) + .build(); + + info_builder = info_builder.push_next(&mut multiview); + } + + self.shared.raw.create_render_pass(&info_builder.build(), None) + }); + + (clear_attachments_mask, result) }); (clear_attachments_mask, result) diff --git a/src/backend/vulkan/src/lib.rs b/src/backend/vulkan/src/lib.rs index 125379496b2..891038f91cd 100644 --- a/src/backend/vulkan/src/lib.rs +++ b/src/backend/vulkan/src/lib.rs @@ -92,6 +92,8 @@ lazy_static! { CStr::from_bytes_with_nul(b"VK_KHR_sampler_mirror_clamp_to_edge\0").unwrap(); static ref KHR_GET_PHYSICAL_DEVICE_PROPERTIES2: &'static CStr = CStr::from_bytes_with_nul(b"VK_KHR_get_physical_device_properties2\0").unwrap(); + static ref KHR_MULTIVIEW: &'static CStr = + CStr::from_bytes_with_nul(b"VK_KHR_multiview\0").unwrap(); static ref EXT_DESCRIPTOR_INDEXING: &'static CStr = CStr::from_bytes_with_nul(b"VK_EXT_descriptor_indexing\0").unwrap(); static ref MESH_SHADER: &'static CStr = MeshShader::name(); @@ -500,17 +502,17 @@ impl hal::Instance for Instance { .map(|device| { let extensions = unsafe { self.raw.inner.enumerate_device_extension_properties(device) }.unwrap(); - let properties = unsafe { self.raw.inner.get_physical_device_properties(device) }; + let properties = PhysicalDeviceProperties::load(&self, &device, &extensions); let info = adapter::AdapterInfo { name: unsafe { - CStr::from_ptr(properties.device_name.as_ptr()) + CStr::from_ptr(properties.inner.device_name.as_ptr()) .to_str() .unwrap_or("Unknown") .to_owned() }, - vendor: properties.vendor_id as usize, - device: properties.device_id as usize, - device_type: match properties.device_type { + vendor: properties.inner.vendor_id as usize, + device: properties.inner.device_id as usize, + device_type: match properties.inner.device_type { ash::vk::PhysicalDeviceType::OTHER => adapter::DeviceType::Other, ash::vk::PhysicalDeviceType::INTEGRATED_GPU => { adapter::DeviceType::IntegratedGpu @@ -650,11 +652,65 @@ impl queue::QueueFamily for QueueFamily { } } +pub struct PhysicalDeviceMultiviewProperties { + pub max_multiview_view_count: u32, + pub max_multiview_instance_index: u32, +} + +impl From for PhysicalDeviceMultiviewProperties { + fn from(other: vk::PhysicalDeviceMultiviewProperties) -> Self { + PhysicalDeviceMultiviewProperties { + max_multiview_view_count: other.max_multiview_view_count, + max_multiview_instance_index: other.max_multiview_instance_index, + } + } +} + +pub struct PhysicalDeviceProperties { + inner: vk::PhysicalDeviceProperties, + multiview: Option, +} + +impl PhysicalDeviceProperties { + pub fn load(instance: &Instance, device: &vk::PhysicalDevice, extensions: &[vk::ExtensionProperties]) -> Self { + fn supports_extension(extensions: &[vk::ExtensionProperties], extension: &CStr) -> bool { + extensions.iter() + .any(|ep| unsafe { CStr::from_ptr(ep.extension_name.as_ptr()) } == extension) + } + + if let Some(ref get_device_properties) = instance.raw.get_physical_device_properties { + let mut properties2_builder = vk::PhysicalDeviceProperties2KHR::builder() + .properties(vk::PhysicalDeviceProperties::builder().build()); + let mut multiview = None; + + // Add extension infos to the p_next chain + if supports_extension(extensions, *KHR_MULTIVIEW) { + multiview = Some(vk::PhysicalDeviceMultiviewProperties::builder().build()); + + properties2_builder = properties2_builder.push_next(multiview.as_mut().unwrap()); + } + + let mut properties2 = properties2_builder.build(); + unsafe { get_device_properties.get_physical_device_properties2_khr(*device, &mut properties2 as *mut _); } + + Self { + inner: properties2.properties, + multiview: multiview.map(PhysicalDeviceMultiviewProperties::from), + } + } else { + Self { + inner: unsafe { instance.raw.inner.get_physical_device_properties(*device) }, + multiview: None, + } + } + } +} + pub struct PhysicalDevice { instance: Arc, handle: vk::PhysicalDevice, extensions: Vec, - properties: vk::PhysicalDeviceProperties, + properties: PhysicalDeviceProperties, known_memory_flags: vk::MemoryPropertyFlags, } @@ -805,7 +861,7 @@ impl adapter::PhysicalDevice for PhysicalDevice { mesh_fn, maintenance_level, }), - vendor_id: self.properties.vendor_id, + vendor_id: self.properties.inner.vendor_id, valid_ash_memory_types, }; @@ -948,16 +1004,18 @@ impl adapter::PhysicalDevice for PhysicalDevice { fn features(&self) -> Features { // see https://github.com/gfx-rs/gfx/issues/1930 let is_windows_intel_dual_src_bug = cfg!(windows) - && self.properties.vendor_id == info::intel::VENDOR - && (self.properties.device_id & info::intel::DEVICE_KABY_LAKE_MASK + && self.properties.inner.vendor_id == info::intel::VENDOR + && (self.properties.inner.device_id & info::intel::DEVICE_KABY_LAKE_MASK == info::intel::DEVICE_KABY_LAKE_MASK - || self.properties.device_id & info::intel::DEVICE_SKY_LAKE_MASK + || self.properties.inner.device_id & info::intel::DEVICE_SKY_LAKE_MASK == info::intel::DEVICE_SKY_LAKE_MASK); let mut descriptor_indexing_features = None; + let mut multiview = None; let features = if let Some(ref get_device_properties) = self.instance.get_physical_device_properties { - let features = vk::PhysicalDeviceFeatures::builder().build(); - let mut features2 = vk::PhysicalDeviceFeatures2KHR::builder().features(features).build(); + let mut features2 = vk::PhysicalDeviceFeatures2KHR::builder() + .features(vk::PhysicalDeviceFeatures::builder().build()) + .build(); // Add extension infos to the p_next chain if self.supports_extension(*EXT_DESCRIPTOR_INDEXING) { @@ -967,6 +1025,13 @@ impl adapter::PhysicalDevice for PhysicalDevice { mut_ref.p_next = mem::replace(&mut features2.p_next, mut_ref as *mut _ as *mut _); } + if self.properties.multiview.is_some() { + multiview = Some(vk::PhysicalDeviceMultiviewFeatures::builder().build()); + + let mut_ref = multiview.as_mut().unwrap(); + mut_ref.p_next = mem::replace(&mut features2.p_next, mut_ref as *mut _ as *mut _); + } + unsafe { get_device_properties.get_physical_device_features2_khr(self.handle, &mut features2 as *mut _); } features2.features } else { @@ -1000,6 +1065,18 @@ impl adapter::PhysicalDevice for PhysicalDevice { } } + if let Some(ref multiview) = multiview { + if multiview.multiview != 0 { + bits |= Features::MULTIVIEW; + } + if multiview.multiview_geometry_shader != 0 { + bits |= Features::MULTIVIEW_GEOMETRY_SHADER; + } + if multiview.multiview_tessellation_shader != 0 { + bits |= Features::MULTIVIEW_TESSELLATION_SHADER; + } + } + if features.robust_buffer_access != 0 { bits |= Features::ROBUST_BUFFER_ACCESS; } @@ -1178,7 +1255,7 @@ impl adapter::PhysicalDevice for PhysicalDevice { } fn limits(&self) -> Limits { - let limits = &self.properties.limits; + let limits = &self.properties.inner.limits; let max_group_count = limits.max_compute_work_group_count; let max_group_size = limits.max_compute_work_group_size; @@ -1294,6 +1371,8 @@ impl adapter::PhysicalDevice for PhysicalDevice { max_mesh_multiview_view_count: 0, mesh_output_per_vertex_granularity: 0, mesh_output_per_primitive_granularity: 0, + max_multiview_view_count: self.properties.multiview.as_ref().map(|multiview| multiview.max_multiview_view_count).unwrap_or(0), + max_multiview_instance_index: self.properties.multiview.as_ref().map(|multiview| multiview.max_multiview_instance_index).unwrap_or(0), } } @@ -1323,27 +1402,27 @@ impl adapter::PhysicalDevice for PhysicalDevice { } // vendor id - if vendor_id != self.properties.vendor_id { + if vendor_id != self.properties.inner.vendor_id { warn!( "Vendor ID mismatch. Device: {:?}, cache: {:?}.", - self.properties.vendor_id, vendor_id, + self.properties.inner.vendor_id, vendor_id, ); return false; } // device id - if device_id != self.properties.device_id { + if device_id != self.properties.inner.device_id { warn!( "Device ID mismatch. Device: {:?}, cache: {:?}.", - self.properties.device_id, device_id, + self.properties.inner.device_id, device_id, ); return false; } - if self.properties.pipeline_cache_uuid != cache[16 .. 16 + vk::UUID_SIZE] { + if self.properties.inner.pipeline_cache_uuid != cache[16 .. 16 + vk::UUID_SIZE] { warn!( "Pipeline cache UUID mismatch. Device: {:?}, cache: {:?}.", - self.properties.pipeline_cache_uuid, + self.properties.inner.pipeline_cache_uuid, &cache[16 .. 16 + vk::UUID_SIZE], ); return false; diff --git a/src/backend/webgpu/src/device.rs b/src/backend/webgpu/src/device.rs index 083454c7e0c..1163fe099de 100644 --- a/src/backend/webgpu/src/device.rs +++ b/src/backend/webgpu/src/device.rs @@ -56,6 +56,7 @@ impl hal::device::Device for Device { _attachments: IA, _subpasses: IS, _dependencies: ID, + _correlation_masks: Option<&[u32]>, ) -> Result<::RenderPass, OutOfMemory> where IA: IntoIterator, diff --git a/src/hal/src/device.rs b/src/hal/src/device.rs index 07e942cdcbc..33103541217 100644 --- a/src/hal/src/device.rs +++ b/src/hal/src/device.rs @@ -440,6 +440,7 @@ pub trait Device: fmt::Debug + Any + Send + Sync { attachments: IA, subpasses: IS, dependencies: ID, + correlation_masks: Option<&[u32]>, ) -> Result where IA: IntoIterator, diff --git a/src/hal/src/lib.rs b/src/hal/src/lib.rs index 8bcf5478340..f8e08554f56 100644 --- a/src/hal/src/lib.rs +++ b/src/hal/src/lib.rs @@ -242,21 +242,28 @@ bitflags! { const UNSIZED_DESCRIPTOR_ARRAY = 0x0800_0000_0000_0000; /// Support triangle fan primitive topology. - const TRIANGLE_FAN = 0x0001 << 64; + const TRIANGLE_FAN = 0x0000_0000_0000_0001 << 64; /// Support separate stencil reference values for front and back sides. - const SEPARATE_STENCIL_REF_VALUES = 0x0002 << 64; + const SEPARATE_STENCIL_REF_VALUES = 0x0000_0000_0000_0002 << 64; /// Support manually specified vertex attribute rates (divisors). - const INSTANCE_RATE = 0x0004 << 64; + const INSTANCE_RATE = 0x0000_0000_0000_0004 << 64; /// Support non-zero mipmap bias on samplers. - const SAMPLER_MIP_LOD_BIAS = 0x0008 << 64; + const SAMPLER_MIP_LOD_BIAS = 0x0000_0000_0000_0008 << 64; /// Make the NDC coordinate system pointing Y up, to match D3D and Metal. - const NDC_Y_UP = 0x0001 << 80; + const NDC_Y_UP = 0x0000_0000_0001_0000 << 64; /// Supports task shader stage. - const TASK_SHADER = 0x0001 << 96; + const TASK_SHADER = 0x0000_0001_0000_0000 << 64; /// Supports mesh shader stage. - const MESH_SHADER = 0x0002 << 96; + const MESH_SHADER = 0x0000_0002_0000_0000 << 64; + + /// Supports multiview + const MULTIVIEW = 0x0000_0004_0000_0000 << 64; + /// Supports multiview geometry shader + const MULTIVIEW_GEOMETRY_SHADER = 0x0000_0008_0000_0000 << 64; + /// Supports multiview tessellation shader + const MULTIVIEW_TESSELLATION_SHADER = 0x0000_0010_0000_0000 << 64; } } @@ -458,6 +465,10 @@ pub struct Limits { /// The granularity with which mesh outputs qualified as per-primitive are allocated. The value can be used to /// compute the memory size used by the mesh shader, which must be less than or equal to pub mesh_output_per_primitive_granularity: u32, + /// One greater than the maximum view index that can be used in a subpass. + pub max_multiview_view_count: u32, + /// The maximum valid value of instance index allowed to be generated by a drawing command recorded within a subpass of a multiview render pass instance. + pub max_multiview_instance_index: u32, } /// An enum describing the type of an index value in a slice's index buffer diff --git a/src/hal/src/pass.rs b/src/hal/src/pass.rs index c6f2d9045b6..8da15f8744d 100644 --- a/src/hal/src/pass.rs +++ b/src/hal/src/pass.rs @@ -144,6 +144,8 @@ pub struct SubpassDependency { pub accesses: Range, /// Dependency flags. pub flags: Dependencies, + /// Controls which views in the source subpass the views in the destination subpass depend on. + pub view_offset: i32, } /// Description of a subpass for render pass creation. @@ -167,6 +169,8 @@ pub struct SubpassDesc<'a> { /// Attachments that are not used by the subpass but must be preserved to be /// passed on to subsequent passes. pub preserves: &'a [AttachmentId], + /// A bitfield of view indices describing which views rendering is broadcast to in each subpass, when multiview is enabled. If subpassCount is zero, each view mask is treated as zero. + pub view_mask: u32, } /// A sub-pass borrow of a pass. diff --git a/src/warden/src/gpu.rs b/src/warden/src/gpu.rs index e07b1cf5e69..28af7933fde 100644 --- a/src/warden/src/gpu.rs +++ b/src/warden/src/gpu.rs @@ -655,6 +655,7 @@ impl Scene { inputs: &t.2, preserves: &t.3, resolves: &t.4, + view_mask: 0, }) .collect::>(); let raw_deps = dependencies.iter().map(|dep| hal::pass::SubpassDependency { @@ -662,10 +663,11 @@ impl Scene { stages: dep.stages.clone(), accesses: dep.accesses.clone(), flags: memory::Dependencies::empty(), + view_offset: 0, }); let rp = RenderPass { - handle: unsafe { device.create_render_pass(raw_atts, raw_subs, raw_deps) } + handle: unsafe { device.create_render_pass(raw_atts, raw_subs, raw_deps, None) } .expect("Render pass creation failure"), attachments: attachments .iter()