diff --git a/Cargo.toml b/Cargo.toml index e10a3d510..5cc95be56 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -109,6 +109,9 @@ path = "examples/basics/7_modules/7_modules.rs" name = "vk_triangle" path = "examples/vulkan/vk_triangle.rs" [[example]] +name = "vk_triangle_raw_frame" +path = "examples/vulkan/vk_triangle_raw_frame.rs" +[[example]] name = "vk_teapot" path = "examples/vulkan/vk_teapot.rs" [[example]] diff --git a/examples/vulkan/vk_image.rs b/examples/vulkan/vk_image.rs index 4133316fc..c62727805 100644 --- a/examples/vulkan/vk_image.rs +++ b/examples/vulkan/vk_image.rs @@ -15,7 +15,6 @@ use nannou::vulkano::image::{Dimensions, ImmutableImage}; use nannou::vulkano::pipeline::viewport::Viewport; use nannou::vulkano::pipeline::{GraphicsPipeline, GraphicsPipelineAbstract}; use nannou::vulkano::sampler::{Filter, MipmapMode, Sampler, SamplerAddressMode}; -use nannou::window::SwapchainFramebuffers; fn main() { nannou::app(model).run(); @@ -25,7 +24,7 @@ struct Model { render_pass: Arc, pipeline: Arc, vertex_buffer: Arc>, - framebuffers: RefCell, + framebuffer: RefCell, desciptor_set: Arc, } @@ -77,7 +76,7 @@ fn model(app: &App) -> Model { load: Clear, store: Store, format: app.main_window().swapchain().format(), - samples: 1, + samples: app.main_window().msaa_samples(), initial_layout: ImageLayout::PresentSrc, final_layout: ImageLayout::PresentSrc, } @@ -142,13 +141,13 @@ fn model(app: &App) -> Model { .unwrap(), ); - let framebuffers = RefCell::new(SwapchainFramebuffers::default()); + let framebuffer = RefCell::new(ViewFramebuffer::default()); Model { render_pass, pipeline, vertex_buffer, - framebuffers, + framebuffer, desciptor_set, } } @@ -166,8 +165,8 @@ fn view(app: &App, model: &Model, frame: Frame) -> Frame { scissors: None, }; - // Update framebuffers so that count matches swapchain image count and dimensions match. - model.framebuffers.borrow_mut() + // Update framebuffer in case of resize. + model.framebuffer.borrow_mut() .update(&frame, model.render_pass.clone(), |builder, image| builder.add(image)) .unwrap(); @@ -180,7 +179,7 @@ fn view(app: &App, model: &Model, frame: Frame) -> Frame { frame .add_commands() .begin_render_pass( - model.framebuffers.borrow()[frame.swapchain_image_index()].clone(), + model.framebuffer.borrow().as_ref().unwrap().clone(), false, clear_values, ) diff --git a/examples/vulkan/vk_image_sequence.rs b/examples/vulkan/vk_image_sequence.rs index c620d1fc1..871c4bb48 100644 --- a/examples/vulkan/vk_image_sequence.rs +++ b/examples/vulkan/vk_image_sequence.rs @@ -15,7 +15,6 @@ use nannou::vulkano::image::{Dimensions, ImmutableImage}; use nannou::vulkano::pipeline::viewport::Viewport; use nannou::vulkano::pipeline::{GraphicsPipeline, GraphicsPipelineAbstract}; use nannou::vulkano::sampler::{Filter, MipmapMode, Sampler, SamplerAddressMode}; -use nannou::window::SwapchainFramebuffers; fn main() { nannou::app(model).run(); @@ -25,7 +24,7 @@ struct Model { render_pass: Arc, pipeline: Arc, vertex_buffer: Arc>, - framebuffers: RefCell, + framebuffer: RefCell, desciptor_set: Arc, } @@ -78,7 +77,7 @@ fn model(app: &App) -> Model { load: Clear, store: Store, format: app.main_window().swapchain().format(), - samples: 1, + samples: app.main_window().msaa_samples(), initial_layout: ImageLayout::PresentSrc, final_layout: ImageLayout::PresentSrc, } @@ -167,13 +166,13 @@ fn model(app: &App) -> Model { .unwrap(), ); - let framebuffers = RefCell::new(SwapchainFramebuffers::default()); + let framebuffer = RefCell::new(ViewFramebuffer::default()); Model { render_pass, pipeline, vertex_buffer, - framebuffers, + framebuffer, desciptor_set, } } @@ -191,8 +190,8 @@ fn view(app: &App, model: &Model, frame: Frame) -> Frame { scissors: None, }; - // Update framebuffers so that count matches swapchain image count and dimensions match. - model.framebuffers.borrow_mut() + // Update framebuffer in case of window resize. + model.framebuffer.borrow_mut() .update(&frame, model.render_pass.clone(), |builder, image| builder.add(image)) .unwrap(); @@ -206,7 +205,7 @@ fn view(app: &App, model: &Model, frame: Frame) -> Frame { frame .add_commands() .begin_render_pass( - model.framebuffers.borrow()[frame.swapchain_image_index()].clone(), + model.framebuffer.borrow().as_ref().unwrap().clone(), false, clear_values, ) diff --git a/examples/vulkan/vk_images.rs b/examples/vulkan/vk_images.rs index 7a3773751..f79e53586 100644 --- a/examples/vulkan/vk_images.rs +++ b/examples/vulkan/vk_images.rs @@ -15,7 +15,6 @@ use nannou::vulkano::image::{Dimensions, ImmutableImage}; use nannou::vulkano::pipeline::viewport::Viewport; use nannou::vulkano::pipeline::{GraphicsPipeline, GraphicsPipelineAbstract}; use nannou::vulkano::sampler::{Filter, MipmapMode, Sampler, SamplerAddressMode}; -use nannou::window::SwapchainFramebuffers; fn main() { nannou::app(model).run(); @@ -25,7 +24,7 @@ struct Model { render_pass: Arc, pipeline: Arc, vertex_buffer: Arc>, - framebuffers: RefCell, + framebuffer: RefCell, desciptor_set: Arc, } @@ -78,7 +77,7 @@ fn model(app: &App) -> Model { load: Clear, store: Store, format: app.main_window().swapchain().format(), - samples: 1, + samples: app.main_window().msaa_samples(), initial_layout: ImageLayout::PresentSrc, final_layout: ImageLayout::PresentSrc, } @@ -159,13 +158,13 @@ fn model(app: &App) -> Model { .unwrap(), ); - let framebuffers = RefCell::new(SwapchainFramebuffers::default()); + let framebuffer = RefCell::new(ViewFramebuffer::default()); Model { render_pass, pipeline, vertex_buffer, - framebuffers, + framebuffer, desciptor_set, } } @@ -183,8 +182,8 @@ fn view(_app: &App, model: &Model, frame: Frame) -> Frame { scissors: None, }; - // Update framebuffers so that count matches swapchain image count and dimensions match. - model.framebuffers.borrow_mut() + // Update framebuffer in case of window resize. + model.framebuffer.borrow_mut() .update(&frame, model.render_pass.clone(), |builder, image| builder.add(image)) .unwrap(); @@ -193,7 +192,7 @@ fn view(_app: &App, model: &Model, frame: Frame) -> Frame { frame .add_commands() .begin_render_pass( - model.framebuffers.borrow()[frame.swapchain_image_index()].clone(), + model.framebuffer.borrow().as_ref().unwrap().clone(), false, clear_values, ) diff --git a/examples/vulkan/vk_quad_warp/warp.rs b/examples/vulkan/vk_quad_warp/warp.rs index e121d3c9a..973acd318 100644 --- a/examples/vulkan/vk_quad_warp/warp.rs +++ b/examples/vulkan/vk_quad_warp/warp.rs @@ -11,7 +11,6 @@ use nannou::vulkano::framebuffer::{RenderPassAbstract, Subpass}; use nannou::vulkano::pipeline::viewport::Viewport; use nannou::vulkano::pipeline::{GraphicsPipeline, GraphicsPipelineAbstract}; use nannou::vulkano::sampler::{Filter, MipmapMode, Sampler, SamplerAddressMode}; -use nannou::window::SwapchainFramebuffers; use nannou::vulkano::image::attachment::AttachmentImage; use nannou::vulkano::sync::GpuFuture; use nannou::math::Matrix4; @@ -22,7 +21,7 @@ use crate::Model; pub struct Warp { render_pass: Arc, pipeline: Arc, - framebuffers: RefCell, + framebuffer: RefCell, uniform_buffer: CpuBufferPool, sampler: Arc, } @@ -51,7 +50,7 @@ pub(crate) fn warp(app: &App) -> Warp { load: Clear, store: Store, format: app.main_window().swapchain().format(), - samples: 1, + samples: app.main_window().msaa_samples(), initial_layout: ImageLayout::PresentSrc, final_layout: ImageLayout::PresentSrc, } @@ -92,12 +91,12 @@ pub(crate) fn warp(app: &App) -> Warp { .unwrap(), ); - let framebuffers = RefCell::new(SwapchainFramebuffers::default()); + let framebuffer = RefCell::new(ViewFramebuffer::default()); Warp { render_pass, pipeline, - framebuffers, + framebuffer, sampler, uniform_buffer, } @@ -191,8 +190,8 @@ pub(crate) fn view(app: &App, model: &Model, inter_image: Arc, scissors: None, }; - // Update framebuffers so that count matches swapchain image count and dimensions match. - warp.framebuffers.borrow_mut() + // Update framebuffer in case of window resize. + warp.framebuffer.borrow_mut() .update(&frame, warp.render_pass.clone(), |builder, image| builder.add(image)) .expect("framebuffer failed to create"); @@ -213,7 +212,7 @@ pub(crate) fn view(app: &App, model: &Model, inter_image: Arc, frame .add_commands() .begin_render_pass( - warp.framebuffers.borrow()[frame.swapchain_image_index()].clone(), + warp.framebuffer.borrow().as_ref().unwrap().clone(), false, clear_values, ) diff --git a/examples/vulkan/vk_shader_include/mod.rs b/examples/vulkan/vk_shader_include/mod.rs index 21a321c68..33c6abb31 100644 --- a/examples/vulkan/vk_shader_include/mod.rs +++ b/examples/vulkan/vk_shader_include/mod.rs @@ -8,9 +8,7 @@ use std::sync::Arc; use nannou::vulkano::buffer::{BufferUsage, CpuAccessibleBuffer}; use nannou::vulkano::command_buffer::DynamicState; use nannou::vulkano::device::DeviceOwned; -use nannou::vulkano::framebuffer::{ - Framebuffer, FramebufferAbstract, FramebufferCreationError, RenderPassAbstract, Subpass, -}; +use nannou::vulkano::framebuffer::{RenderPassAbstract, Subpass}; use nannou::vulkano::pipeline::viewport::Viewport; use nannou::vulkano::pipeline::{GraphicsPipeline, GraphicsPipelineAbstract}; @@ -22,7 +20,7 @@ struct Model { render_pass: Arc, pipeline: Arc, vertex_buffer: Arc>, - framebuffers: RefCell>>, + framebuffer: RefCell, } #[derive(Debug, Clone)] @@ -75,7 +73,7 @@ fn model(app: &App) -> Model { load: Clear, store: Store, format: app.main_window().swapchain().format(), - samples: 1, + samples: app.main_window().msaa_samples(), initial_layout: ImageLayout::PresentSrc, final_layout: ImageLayout::PresentSrc, } @@ -101,13 +99,13 @@ fn model(app: &App) -> Model { .unwrap(), ); - let framebuffers = RefCell::new(Vec::new()); + let framebuffer = RefCell::new(ViewFramebuffer::default()); Model { render_pass, pipeline, vertex_buffer, - framebuffers, + framebuffer, } } @@ -124,20 +122,10 @@ fn view(app: &App, model: &Model, frame: Frame) -> Frame { scissors: None, }; - // Update the framebuffers if necessary. - while frame.swapchain_image_index() >= model.framebuffers.borrow().len() { - let fb = - create_framebuffer(model.render_pass.clone(), frame.swapchain_image().clone()).unwrap(); - model.framebuffers.borrow_mut().push(Arc::new(fb)); - } - - // If the dimensions for the current framebuffer do not match, recreate it. - if frame.swapchain_image_is_new() { - let fb = &mut model.framebuffers.borrow_mut()[frame.swapchain_image_index()]; - let new_fb = - create_framebuffer(model.render_pass.clone(), frame.swapchain_image().clone()).unwrap(); - *fb = Arc::new(new_fb); - } + // Update the framebuffer in case of window resize. + model.framebuffer.borrow_mut() + .update(&frame, model.render_pass.clone(), |builder, image| builder.add(image)) + .unwrap(); let clear_values = vec![[0.0, 1.0, 0.0, 1.0].into()]; @@ -150,7 +138,7 @@ fn view(app: &App, model: &Model, frame: Frame) -> Frame { frame .add_commands() .begin_render_pass( - model.framebuffers.borrow()[frame.swapchain_image_index()].clone(), + model.framebuffer.borrow().as_ref().unwrap().clone(), false, clear_values, ) @@ -236,13 +224,3 @@ void main() { }" } } - -fn create_framebuffer( - render_pass: Arc, - swapchain_image: Arc, -) -> Result, FramebufferCreationError> { - let fb = Framebuffer::start(render_pass) - .add(swapchain_image)? - .build()?; - Ok(Arc::new(fb) as _) -} diff --git a/examples/vulkan/vk_teapot.rs b/examples/vulkan/vk_teapot.rs index 2ab0d380c..628b16090 100644 --- a/examples/vulkan/vk_teapot.rs +++ b/examples/vulkan/vk_teapot.rs @@ -16,7 +16,6 @@ use nannou::vulkano::pipeline::{GraphicsPipeline, GraphicsPipelineAbstract, GraphicsPipelineCreationError}; use nannou::vulkano::pipeline::vertex::TwoBuffersDefinition; use nannou::vulkano::pipeline::viewport::Viewport; -use nannou::window::SwapchainFramebuffers; use std::cell::RefCell; use std::sync::Arc; @@ -38,7 +37,7 @@ struct Graphics { render_pass: Arc, graphics_pipeline: Arc, depth_image: Arc, - framebuffers: SwapchainFramebuffers, + framebuffer: ViewFramebuffer, } #[derive(Copy, Clone)] @@ -93,7 +92,7 @@ fn model(app: &App) -> Model { load: Clear, store: Store, format: app.main_window().swapchain().format(), - samples: 1, + samples: app.main_window().msaa_samples(), initial_layout: ImageLayout::PresentSrc, final_layout: ImageLayout::PresentSrc, }, @@ -101,7 +100,7 @@ fn model(app: &App) -> Model { load: Clear, store: DontCare, format: Format::D16Unorm, - samples: 1, + samples: app.main_window().msaa_samples(), } }, pass: { @@ -121,10 +120,14 @@ fn model(app: &App) -> Model { [w as f32, h as f32], ).unwrap(); - let depth_image = AttachmentImage::transient(device.clone(), [w, h], Format::D16Unorm) - .unwrap(); + let depth_image = AttachmentImage::transient_multisampled( + device.clone(), + [w, h], + app.main_window().msaa_samples(), + Format::D16Unorm, + ).unwrap(); - let framebuffers = SwapchainFramebuffers::default(); + let framebuffer = ViewFramebuffer::default(); let graphics = RefCell::new(Graphics { vertex_buffer, @@ -136,7 +139,7 @@ fn model(app: &App) -> Model { render_pass, graphics_pipeline, depth_image, - framebuffers, + framebuffer, }); Model { graphics } @@ -159,17 +162,18 @@ fn view(app: &App, model: &Model, frame: Frame) -> Frame { [w as f32, h as f32], ).unwrap(); - graphics.depth_image = AttachmentImage::transient( + graphics.depth_image = AttachmentImage::transient_multisampled( device.clone(), [w, h], + frame.image_msaa_samples(), Format::D16Unorm, ).unwrap(); } - // Update framebuffers so that count matches swapchain image count and dimensions match. + // Update framebuffer so that count matches swapchain image count and dimensions match. let render_pass = graphics.render_pass.clone(); let depth_image = graphics.depth_image.clone(); - graphics.framebuffers + graphics.framebuffer .update(&frame, render_pass, |builder, image| builder.add(image)?.add(depth_image.clone())) .unwrap(); @@ -218,7 +222,7 @@ fn view(app: &App, model: &Model, frame: Frame) -> Frame { frame .add_commands() .begin_render_pass( - graphics.framebuffers[frame.swapchain_image_index()].clone(), + graphics.framebuffer.as_ref().unwrap().clone(), false, clear_values, ) diff --git a/examples/vulkan/vk_teapot_camera.rs b/examples/vulkan/vk_teapot_camera.rs index 63e486681..c88686d1e 100644 --- a/examples/vulkan/vk_teapot_camera.rs +++ b/examples/vulkan/vk_teapot_camera.rs @@ -16,7 +16,6 @@ use nannou::vulkano::pipeline::{GraphicsPipeline, GraphicsPipelineAbstract, GraphicsPipelineCreationError}; use nannou::vulkano::pipeline::vertex::TwoBuffersDefinition; use nannou::vulkano::pipeline::viewport::Viewport; -use nannou::window::SwapchainFramebuffers; use std::cell::RefCell; use std::sync::Arc; @@ -36,7 +35,7 @@ struct Graphics { render_pass: Arc, graphics_pipeline: Arc, depth_image: Arc, - framebuffers: SwapchainFramebuffers, + framebuffer: ViewFramebuffer, } // A simple first person camera. @@ -132,7 +131,7 @@ fn model(app: &App) -> Model { load: Clear, store: Store, format: app.main_window().swapchain().format(), - samples: 1, + samples: app.main_window().msaa_samples(), initial_layout: ImageLayout::PresentSrc, final_layout: ImageLayout::PresentSrc, }, @@ -140,7 +139,7 @@ fn model(app: &App) -> Model { load: Clear, store: DontCare, format: Format::D16Unorm, - samples: 1, + samples: app.main_window().msaa_samples(), } }, pass: { @@ -160,10 +159,14 @@ fn model(app: &App) -> Model { [w as f32, h as f32], ).unwrap(); - let depth_image = AttachmentImage::transient(device.clone(), [w, h], Format::D16Unorm) - .unwrap(); + let depth_image = AttachmentImage::transient_multisampled( + device.clone(), + [w, h], + app.main_window().msaa_samples(), + Format::D16Unorm, + ).unwrap(); - let framebuffers = SwapchainFramebuffers::default(); + let framebuffer = ViewFramebuffer::default(); let graphics = RefCell::new(Graphics { vertex_buffer, @@ -175,7 +178,7 @@ fn model(app: &App) -> Model { render_pass, graphics_pipeline, depth_image, - framebuffers, + framebuffer, }); let eye = Point3::new(0.0, 0.0, 1.0); @@ -293,17 +296,18 @@ fn view(_app: &App, model: &Model, frame: Frame) -> Frame { [w as f32, h as f32], ).unwrap(); - graphics.depth_image = AttachmentImage::transient( + graphics.depth_image = AttachmentImage::transient_multisampled( device.clone(), [w, h], + frame.image_msaa_samples(), Format::D16Unorm, ).unwrap(); } - // Update framebuffers so that count matches swapchain image count and dimensions match. + // Update framebuffer in case of window resize. let render_pass = graphics.render_pass.clone(); let depth_image = graphics.depth_image.clone(); - graphics.framebuffers + graphics.framebuffer .update(&frame, render_pass, |builder, image| builder.add(image)?.add(depth_image.clone())) .unwrap(); @@ -348,7 +352,7 @@ fn view(_app: &App, model: &Model, frame: Frame) -> Frame { frame .add_commands() .begin_render_pass( - graphics.framebuffers[frame.swapchain_image_index()].clone(), + graphics.framebuffer.as_ref().unwrap().clone(), false, clear_values, ) diff --git a/examples/vulkan/vk_triangle.rs b/examples/vulkan/vk_triangle.rs index 7aa31d6ff..7874d3cb9 100644 --- a/examples/vulkan/vk_triangle.rs +++ b/examples/vulkan/vk_triangle.rs @@ -11,7 +11,6 @@ use nannou::vulkano::device::DeviceOwned; use nannou::vulkano::framebuffer::{RenderPassAbstract, Subpass}; use nannou::vulkano::pipeline::viewport::Viewport; use nannou::vulkano::pipeline::{GraphicsPipeline, GraphicsPipelineAbstract}; -use nannou::window::SwapchainFramebuffers; fn main() { nannou::app(model).run(); @@ -21,7 +20,7 @@ struct Model { render_pass: Arc, pipeline: Arc, vertex_buffer: Arc>, - framebuffers: RefCell, + framebuffer: RefCell, } #[derive(Debug, Clone)] @@ -87,9 +86,7 @@ fn model(app: &App) -> Model { // same format as the swapchain. format: app.main_window().swapchain().format(), // TODO: - samples: 1, - initial_layout: ImageLayout::PresentSrc, - final_layout: ImageLayout::PresentSrc, + samples: app.main_window().msaa_samples(), } }, pass: { @@ -128,18 +125,15 @@ fn model(app: &App) -> Model { .unwrap(), ); - // The render pass we created above only describes the layout of our framebuffers. Before we - // can draw we also need to create the actual framebuffers. - // - // Since we need to draw to multiple images, we are going to create a different framebuffer for - // each image. - let framebuffers = RefCell::new(SwapchainFramebuffers::default()); + // The render pass we created above only describes the layout of our framebuffer. Before we + // can draw we also need to create the actual framebuffer. + let framebuffer = RefCell::new(ViewFramebuffer::default()); Model { render_pass, pipeline, vertex_buffer, - framebuffers, + framebuffer, } } @@ -159,19 +153,19 @@ fn view(_app: &App, model: &Model, frame: Frame) -> Frame { scissors: None, }; - // Update framebuffers so that count matches swapchain image count and dimensions match. - model.framebuffers.borrow_mut() + // Update the framebuffer. + model.framebuffer.borrow_mut() .update(&frame, model.render_pass.clone(), |builder, image| builder.add(image)) .unwrap(); - // Specify the color to clear the framebuffer with i.e. blue + // Specify the color to clear the framebuffer with i.e. blue. let clear_values = vec![[0.0, 0.0, 1.0, 1.0].into()]; // Submit the draw commands. frame .add_commands() .begin_render_pass( - model.framebuffers.borrow()[frame.swapchain_image_index()].clone(), + model.framebuffer.borrow().as_ref().unwrap().clone(), false, clear_values, ) diff --git a/examples/vulkan/vk_triangle_raw_frame.rs b/examples/vulkan/vk_triangle_raw_frame.rs new file mode 100644 index 000000000..a7c57ac30 --- /dev/null +++ b/examples/vulkan/vk_triangle_raw_frame.rs @@ -0,0 +1,220 @@ +extern crate nannou; + +use nannou::prelude::*; +use nannou::vulkano; +use std::cell::RefCell; +use std::sync::Arc; + +use nannou::vulkano::buffer::{BufferUsage, CpuAccessibleBuffer}; +use nannou::vulkano::command_buffer::DynamicState; +use nannou::vulkano::device::DeviceOwned; +use nannou::vulkano::framebuffer::{RenderPassAbstract, Subpass}; +use nannou::vulkano::pipeline::viewport::Viewport; +use nannou::vulkano::pipeline::{GraphicsPipeline, GraphicsPipelineAbstract}; +use nannou::window::SwapchainFramebuffers; + +fn main() { + nannou::app(model).run(); +} + +struct Model { + render_pass: Arc, + pipeline: Arc, + vertex_buffer: Arc>, + framebuffers: RefCell, +} + +#[derive(Debug, Clone)] +struct Vertex { + position: [f32; 2], +} + +nannou::vulkano::impl_vertex!(Vertex, position); + +fn model(app: &App) -> Model { + app.new_window() + .with_dimensions(512, 512) + .raw_view(view) + .build() + .unwrap(); + + // The gpu device associated with the window's swapchain + let device = app.main_window().swapchain().device().clone(); + + // We now create a buffer that will store the shape of our triangle. + let vertex_buffer = { + CpuAccessibleBuffer::from_iter( + device.clone(), + BufferUsage::all(), + [ + Vertex { + position: [-0.5, -0.25], + }, + Vertex { + position: [0.0, 0.5], + }, + Vertex { + position: [0.25, -0.1], + }, + ] + .iter() + .cloned(), + ) + .unwrap() + }; + + let vertex_shader = vs::Shader::load(device.clone()).unwrap(); + let fragment_shader = fs::Shader::load(device.clone()).unwrap(); + + // The next step is to create a *render pass*, which is an object that describes where the + // output of the graphics pipeline will go. It describes the layout of the images + // where the colors, depth and/or stencil information will be written. + let render_pass = Arc::new( + nannou::vulkano::single_pass_renderpass!( + device.clone(), + attachments: { + // `color` is a custom name we give to the first and only attachment. + color: { + // `load: Clear` means that we ask the GPU to clear the content of this + // attachment at the start of the drawing. + load: Clear, + // `store: Store` means that we ask the GPU to store the output of the draw + // in the actual image. We could also ask it to discard the result. + store: Store, + // `format: ` indicates the type of the format of the image. This has to + // be one of the types of the `vulkano::format` module (or alternatively one + // of your structs that implements the `FormatDesc` trait). Here we use the + // same format as the swapchain. + format: app.main_window().swapchain().format(), + // TODO: + samples: 1, + initial_layout: ImageLayout::PresentSrc, + final_layout: ImageLayout::PresentSrc, + } + }, + pass: { + // We use the attachment named `color` as the one and only color attachment. + color: [color], + // No depth-stencil attachment is indicated with empty brackets. + depth_stencil: {} + } + ) + .unwrap(), + ); + + // Before we draw we have to create what is called a pipeline. This is similar to an OpenGL + // program, but much more specific. + let pipeline = Arc::new( + GraphicsPipeline::start() + // We need to indicate the layout of the vertices. + // The type `SingleBufferDefinition` actually contains a template parameter + // corresponding to the type of each vertex. + .vertex_input_single_buffer::() + // A Vulkan shader can in theory contain multiple entry points, so we have to specify + // which one. The `main` word of `main_entry_point` actually corresponds to the name of + // the entry point. + .vertex_shader(vertex_shader.main_entry_point(), ()) + // The content of the vertex buffer describes a list of triangles. + .triangle_list() + // Use a resizable viewport set to draw over the entire window + .viewports_dynamic_scissors_irrelevant(1) + // See `vertex_shader`. + .fragment_shader(fragment_shader.main_entry_point(), ()) + // We have to indicate which subpass of which render pass this pipeline is going to be + // used in. The pipeline will only be usable from this particular subpass. + .render_pass(Subpass::from(render_pass.clone(), 0).unwrap()) + // Now that our builder is filled, we call `build()` to obtain an actual pipeline. + .build(device.clone()) + .unwrap(), + ); + + // The render pass we created above only describes the layout of our framebuffers. Before we + // can draw we also need to create the actual framebuffers. + // + // Since we need to draw to multiple images, we are going to create a different framebuffer for + // each image. + let framebuffers = RefCell::new(SwapchainFramebuffers::default()); + + Model { + render_pass, + pipeline, + vertex_buffer, + framebuffers, + } +} + +// Draw the state of your `Model` into the given `RawFrame` here. +fn view(_app: &App, model: &Model, frame: RawFrame) -> RawFrame { + // Dynamic viewports allow us to recreate just the viewport when the window is resized + // Otherwise we would have to recreate the whole pipeline. + let [w, h] = frame.swapchain_image().dimensions(); + let viewport = Viewport { + origin: [0.0, 0.0], + dimensions: [w as _, h as _], + depth_range: 0.0..1.0, + }; + let dynamic_state = DynamicState { + line_width: None, + viewports: Some(vec![viewport]), + scissors: None, + }; + + // Update framebuffers so that count matches swapchain image count and dimensions match. + model.framebuffers.borrow_mut() + .update(&frame, model.render_pass.clone(), |builder, image| builder.add(image)) + .unwrap(); + + // Specify the color to clear the framebuffer with i.e. blue + let clear_values = vec![[0.0, 0.0, 1.0, 1.0].into()]; + + // Submit the draw commands. + frame + .add_commands() + .begin_render_pass( + model.framebuffers.borrow()[frame.swapchain_image_index()].clone(), + false, + clear_values, + ) + .unwrap() + .draw( + model.pipeline.clone(), + &dynamic_state, + vec![model.vertex_buffer.clone()], + (), + (), + ) + .unwrap() + .end_render_pass() + .expect("failed to add `end_render_pass` command"); + + frame +} + +mod vs { + nannou::vulkano_shaders::shader! { + ty: "vertex", + src: " +#version 450 + +layout(location = 0) in vec2 position; + +void main() { + gl_Position = vec4(position, 0.0, 1.0); +}" + } +} + +mod fs { + nannou::vulkano_shaders::shader! { + ty: "fragment", + src: " +#version 450 + +layout(location = 0) out vec4 f_color; + +void main() { + f_color = vec4(1.0, 0.0, 0.0, 1.0); +} +" + } +} diff --git a/src/app.rs b/src/app.rs index e91a0622b..3ab4a98f7 100644 --- a/src/app.rs +++ b/src/app.rs @@ -17,7 +17,7 @@ use audio::cpal; use draw; use event::{self, Event, LoopEvent, Key, Update}; use find_folder; -use frame::Frame; +use frame::{Frame, RawFrame, RenderData}; use geom; use gpu; use state; @@ -936,8 +936,13 @@ impl App { let draw = self.draw_state.draw.borrow_mut(); draw.reset(); if self.draw_state.renderer.borrow().is_none() { - let renderer = draw::backend::vulkano::Renderer::new(device, color_format, DEPTH_FORMAT) - .expect("failed to create `Draw` renderer for vulkano backend"); + let msaa_samples = window.msaa_samples(); + let renderer = draw::backend::vulkano::Renderer::new( + device, + color_format, + DEPTH_FORMAT, + msaa_samples, + ).expect("failed to create `Draw` renderer for vulkano backend"); *self.draw_state.renderer.borrow_mut() = Some(RefCell::new(renderer)); } let renderer = self.draw_state.renderer.borrow_mut(); @@ -2011,7 +2016,7 @@ where }; // Construct and emit a frame via `view` for receiving the user's graphics commands. - let frame = Frame::new_empty( + let raw_frame = RawFrame::new_empty( queue.clone(), window_id, nth_frame, @@ -2027,21 +2032,84 @@ where .get(&window_id) .and_then(|w| w.user_functions.view.clone()) }; - let frame = match window_view { - Some(window::View::Sketch(view)) => view(app, frame), + + // A function to simplify taking a window's render data. + fn take_window_frame_render_data(app: &App, window_id: window::Id) -> Option { + let mut windows = app.windows.borrow_mut(); + windows + .get_mut(&window_id) + .and_then(|w| w.frame_render_data.take()) + } + + // A function to simplify giving the frame render data back to the window. + fn set_window_frame_render_data(app: &App, window_id: window::Id, render_data: RenderData) { + let mut windows = app.windows.borrow_mut(); + if let Some(window) = windows.get_mut(&window_id) { + window.frame_render_data = Some(render_data); + } + } + + let command_buffer = match window_view { + Some(window::View::Sketch(view)) => { + let render_data = take_window_frame_render_data(app, window_id) + .expect("failed to take window's `frame_render_data`"); + let frame = Frame::new_empty(raw_frame, render_data) + .expect("failed to create `Frame`"); + let frame = view(app, frame); + let (render_data, raw_frame) = frame.finish() + .expect("failed to resolve frame's intermediary_image to the swapchain_image"); + set_window_frame_render_data(app, window_id, render_data); + raw_frame.finish().build().expect("failed to build command buffer") + } Some(window::View::WithModel(view)) => { + let render_data = take_window_frame_render_data(app, window_id) + .expect("failed to take window's `frame_render_data`"); + let frame = Frame::new_empty(raw_frame, render_data) + .expect("failed to create `Frame`"); let view = view .to_fn_ptr::() .expect("unexpected model argument given to window view function"); - (*view)(app, model, frame) - }, + let frame = (*view)(app, model, frame); + let (render_data, raw_frame) = frame.finish() + .expect("failed to resolve frame's intermediary_image to the swapchain_image"); + set_window_frame_render_data(app, window_id, render_data); + raw_frame.finish().build().expect("failed to build command buffer") + } + Some(window::View::WithModelRaw(raw_view)) => { + let raw_view = raw_view + .to_fn_ptr::() + .expect("unexpected model argument given to window raw_view function"); + let raw_frame = (*raw_view)(app, model, raw_frame); + raw_frame.finish().build().expect("failed to build command buffer") + } None => match default_view { - Some(View::Sketch(view)) => view(app, frame), - Some(View::WithModel(view)) => view(app, &model, frame), - None => frame, - }, + Some(View::Sketch(view)) => { + let render_data = take_window_frame_render_data(app, window_id) + .expect("failed to take window's `frame_render_data`"); + let frame = Frame::new_empty(raw_frame, render_data) + .expect("failed to create `Frame`"); + let frame = view(app, frame); + let (render_data, raw_frame) = frame.finish() + .expect("failed to resolve frame's intermediary_image to the swapchain_image"); + set_window_frame_render_data(app, window_id, render_data); + raw_frame.finish().build().expect("failed to build command buffer") + } + Some(View::WithModel(view)) => { + let render_data = take_window_frame_render_data(app, window_id) + .expect("failed to take window's `frame_render_data`"); + let frame = Frame::new_empty(raw_frame, render_data) + .expect("failed to create `Frame`"); + let frame = view(app, &model, frame); + let (render_data, raw_frame) = frame.finish() + .expect("failed to resolve frame's intermediary_image to the swapchain_image"); + set_window_frame_render_data(app, window_id, render_data); + raw_frame.finish().build().expect("failed to build command buffer") + } + None => { + raw_frame.finish().build().expect("failed to build command buffer") + } + } }; - let command_buffer = frame.finish().build().expect("failed to build command buffer"); let mut windows = app.windows.borrow_mut(); let window = windows.get_mut(&window_id).expect("no window for id"); diff --git a/src/draw/backend/vulkano.rs b/src/draw/backend/vulkano.rs index 070b98213..db5058f04 100644 --- a/src/draw/backend/vulkano.rs +++ b/src/draw/backend/vulkano.rs @@ -1,7 +1,7 @@ //! The `vulkano` backend for rendering the contents of a **Draw**'s mesh. use draw; -use frame::Frame; +use frame::{Frame, ViewFramebuffer}; use math::{BaseFloat, NumCast}; use std::error::Error as StdError; use std::fmt; @@ -15,12 +15,10 @@ use vulkano::framebuffer::{FramebufferCreationError, LoadOp, RenderPassAbstract, RenderPassCreationError, RenderPassDesc, Subpass}; use vulkano::image::attachment::AttachmentImage; use vulkano::image::ImageCreationError; -use vulkano::instance::PhysicalDevice; use vulkano::memory::DeviceMemoryAllocError; use vulkano::pipeline::{GraphicsPipeline, GraphicsPipelineAbstract}; use vulkano::pipeline::viewport::Viewport; use vulkano::sync::GpuFuture; -use window::SwapchainFramebuffers; /// A type used for rendering a **nannou::draw::Mesh** with a vulkan graphics pipeline. pub struct Renderer { @@ -28,7 +26,7 @@ pub struct Renderer { graphics_pipeline: Arc, vertices: Vec, render_pass_images: Option, - swapchain_framebuffers: SwapchainFramebuffers, + view_framebuffer: ViewFramebuffer, } /// The `Vertex` type passed to the vertex shader. @@ -67,8 +65,6 @@ pub struct Vertex { // The images used within the `Draw` render pass. struct RenderPassImages { - multisampled_color: Arc, - multisampled_depth: Arc, depth: Arc, } @@ -201,26 +197,16 @@ impl RenderPassImages { fn new( device: Arc, dimensions: [u32; 2], - color_format: Format, depth_format: Format, msaa_samples: u32, ) -> Result { - let multisampled_color = AttachmentImage::transient_multisampled( - device.clone(), - dimensions, - msaa_samples, - color_format, - )?; - let multisampled_depth = AttachmentImage::transient_multisampled( - device.clone(), + let depth = AttachmentImage::transient_multisampled( + device, dimensions, msaa_samples, depth_format, )?; - let depth = AttachmentImage::transient(device, dimensions, depth_format)?; Ok(RenderPassImages { - multisampled_color, - multisampled_depth, depth, }) } @@ -234,9 +220,9 @@ impl Renderer { device: Arc, color_format: Format, depth_format: Format, + msaa_samples: u32, ) -> Result { - let msaa_samples = msaa_samples(&device.physical_device()); - let load_op = LoadOp::DontCare; + let load_op = LoadOp::Load; let render_pass = Arc::new( create_render_pass(device, color_format, depth_format, load_op, msaa_samples)? ) as Arc; @@ -244,13 +230,13 @@ impl Renderer { as Arc; let vertices = vec![]; let render_pass_images = None; - let swapchain_framebuffers = SwapchainFramebuffers::default(); + let view_framebuffer = ViewFramebuffer::default(); Ok(Renderer { render_pass, graphics_pipeline, vertices, render_pass_images, - swapchain_framebuffers, + view_framebuffer, }) } @@ -272,13 +258,13 @@ impl Renderer { ref mut graphics_pipeline, ref mut vertices, ref mut render_pass_images, - ref mut swapchain_framebuffers, + ref mut view_framebuffer, } = *self; // Retrieve the color/depth image load op and clear values based on the bg color. let bg_color = draw.state.borrow().background_color; - let (load_op, clear_ms_color, clear_ms_depth) = match bg_color { - None => (LoadOp::DontCare, ClearValue::None, ClearValue::None), + let (load_op, clear_color, clear_depth) = match bg_color { + None => (LoadOp::Load, ClearValue::None, ClearValue::None), Some(color) => { let clear_color = [color.red, color.green, color.blue, color.alpha].into(); let clear_depth = 1f32.into(); @@ -293,9 +279,9 @@ impl Renderer { .map(|desc| desc.load != load_op) .unwrap_or(true); - let device = frame.swapchain_image().swapchain().device().clone(); - let color_format = frame.swapchain_image().swapchain().format(); - let msaa_samples = msaa_samples(&device.physical_device()); + let device = frame.queue().device().clone(); + let color_format = frame.image_format(); + let msaa_samples = frame.image_msaa_samples(); // If necessary, recreate the render pass and in turn the graphics pipeline. if recreate_render_pass { @@ -310,16 +296,12 @@ impl Renderer { } // Prepare clear values. - let clear_color = ClearValue::None; - let clear_depth = ClearValue::None; let clear_values = vec![ - clear_ms_color, - clear_ms_depth, clear_color, clear_depth, ]; - let image_dims = frame.swapchain_image().dimensions(); + let image_dims = frame.image().dimensions(); let [img_w, img_h] = image_dims; let queue = frame.queue().clone(); @@ -340,13 +322,12 @@ impl Renderer { // Create (or recreate) the render pass images if necessary. let recreate_images = render_pass_images .as_ref() - .map(|imgs| image_dims != imgs.multisampled_color.dimensions()) + .map(|imgs| image_dims != imgs.depth.dimensions()) .unwrap_or(true); if recreate_images { *render_pass_images = Some(RenderPassImages::new( device.clone(), image_dims, - color_format, depth_format, msaa_samples, )?); @@ -356,10 +337,8 @@ impl Renderer { let render_pass_images = render_pass_images.as_mut().expect("render_pass_images is `None`"); // Ensure framebuffers are up to date with the frame's swapchain image and render pass. - swapchain_framebuffers.update(&frame, render_pass.clone(), |builder, image| { + view_framebuffer.update(&frame, render_pass.clone(), |builder, image| { builder - .add(render_pass_images.multisampled_color.clone())? - .add(render_pass_images.multisampled_depth.clone())? .add(image)? .add(render_pass_images.depth.clone()) }).unwrap(); @@ -378,7 +357,7 @@ impl Renderer { frame .add_commands() .begin_render_pass( - swapchain_framebuffers[frame.swapchain_image_index()].clone(), + view_framebuffer.as_ref().unwrap().clone(), false, clear_values, )? @@ -410,10 +389,10 @@ pub fn create_render_pass( LoadOp::Clear => { create_render_pass_clear(device, color_format, depth_format, msaa_samples) } - LoadOp::DontCare => { - create_render_pass_dont_care(device, color_format, depth_format, msaa_samples) + LoadOp::Load => { + create_render_pass_load(device, color_format, depth_format, msaa_samples) } - LoadOp::Load => unreachable!(), + LoadOp::DontCare => unreachable!(), } } @@ -428,37 +407,24 @@ pub fn create_render_pass_clear( let rp = single_pass_renderpass!( device, attachments: { - multisampled_color: { - load: Clear, - store: DontCare, - format: color_format, - samples: msaa_samples, - }, - multisampled_depth: { - load: Clear, - store: DontCare, - format: depth_format, - samples: msaa_samples, - }, color: { - load: DontCare, + load: Clear, store: Store, format: color_format, - samples: 1, + samples: msaa_samples, }, depth: { - load: DontCare, + load: Clear, store: Store, format: depth_format, - samples: 1, + samples: msaa_samples, initial_layout: ImageLayout::Undefined, final_layout: ImageLayout::DepthStencilAttachmentOptimal, } }, pass: { - color: [multisampled_color], - depth_stencil: {multisampled_depth}, - resolve: [color], + color: [color], + depth_stencil: {depth} } )?; Ok(Arc::new(rp)) @@ -466,7 +432,7 @@ pub fn create_render_pass_clear( /// Create a render pass that uses `LoadOp::Clear` for the multisampled color and depth /// attachments. -pub fn create_render_pass_dont_care( +pub fn create_render_pass_load( device: Arc, color_format: Format, depth_format: Format, @@ -475,37 +441,24 @@ pub fn create_render_pass_dont_care( let rp = single_pass_renderpass!( device, attachments: { - multisampled_color: { - load: DontCare, - store: DontCare, - format: color_format, - samples: msaa_samples, - }, - multisampled_depth: { - load: DontCare, - store: DontCare, - format: depth_format, - samples: msaa_samples, - }, color: { - load: DontCare, + load: Load, store: Store, format: color_format, - samples: 1, + samples: msaa_samples, }, depth: { - load: DontCare, + load: Load, store: Store, format: depth_format, - samples: 1, + samples: msaa_samples, initial_layout: ImageLayout::Undefined, final_layout: ImageLayout::DepthStencilAttachmentOptimal, } }, pass: { - color: [multisampled_color], - depth_stencil: {multisampled_depth}, - resolve: [color], + color: [color], + depth_stencil: {depth} } )?; Ok(Arc::new(rp)) @@ -548,17 +501,6 @@ where Ok(Arc::new(pipeline)) } -/// Determine the number of samples to use for MSAA. -/// -/// The target is 4, but we fall back to the limit if its lower. -pub fn msaa_samples(physical_device: &PhysicalDevice) -> u32 { - const TARGET_SAMPLES: u32 = 4; - let color = physical_device.limits().framebuffer_color_sample_counts(); - let depth = physical_device.limits().framebuffer_depth_sample_counts(); - let limit = std::cmp::min(color, depth); - std::cmp::min(limit, TARGET_SAMPLES) -} - // Error Implementations impl From for RendererCreationError { @@ -676,8 +618,8 @@ impl fmt::Debug for Renderer { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!( f, - "Renderer ( render_pass, graphics_pipeline, framebuffers: {} )", - self.swapchain_framebuffers.len(), + "Renderer ( render_pass, graphics_pipeline, framebuffer: {} )", + if self.view_framebuffer.is_some() { "Some" } else { "None" }, ) } } diff --git a/src/frame/mod.rs b/src/frame/mod.rs new file mode 100644 index 000000000..55b8ba7a3 --- /dev/null +++ b/src/frame/mod.rs @@ -0,0 +1,456 @@ +//! Items related to the **Frame** type, describing a single frame of graphics for a single window. + +use draw::properties::color::IntoRgba; +use std::error::Error as StdError; +use std::{fmt, ops}; +use std::sync::Arc; +use vulkano; +use vulkano::command_buffer::BeginRenderPassError; +use vulkano::device::{Device, DeviceOwned}; +use vulkano::format::{ClearValue, Format}; +use vulkano::framebuffer::{AttachmentsList, Framebuffer, FramebufferAbstract, FramebufferBuilder, + FramebufferCreationError, RenderPassAbstract, RenderPassCreationError}; +use vulkano::image::{AttachmentImage, ImageCreationError, ImageUsage}; +use vulkano::VulkanObject; +use window::SwapchainFramebuffers; + +pub mod raw; + +pub use self::raw::{AddCommands, RawFrame}; + +/// A **Frame** to which the user can draw graphics before it is presented to the display. +/// +/// **Frame**s are delivered to the user for drawing via the user's **view** function. +/// +/// See the **RawFrame** docs for more details on how the implementation works under the hood. The +/// **Frame** type differs in that rather than drawing directly to the swapchain image the user may +/// draw to an intermediary image. There are several advantages of drawing to an intermediary +/// image. +pub struct Frame { + raw_frame: RawFrame, + data: RenderData, +} + +/// A helper type for managing a framebuffer associated with a window's `view` function. +/// +/// Creating and maintaining the framebuffer that targets the `Frame`s image can be a tedious task +/// that requires a lot of boilerplate code. This type simplifies the process with a single +/// `update` method that creates or recreates the framebuffer if any of the following conditions +/// are met: +/// - The `update` method is called for the first time. +/// - The `frame.image_is_new()` method indicates that the swapchain or its images have recently +/// been recreated and the framebuffer should be recreated accordingly. +/// - The given render pass is different to that which was used to create the existing framebuffer. +#[derive(Default)] +pub struct ViewFramebuffer { + framebuffer: Option>, +} + +/// Data necessary for rendering the **Frame**'s `image` to the the `swapchain_image` of the inner +/// raw frame. +pub(crate) struct RenderData { + render_pass: Arc, + // The intermediary image to which the user will draw. + // + // The number of multisampling samples may be specified by the user when constructing the + // window with which the `Frame` is associated. + pub(crate) intermediary_image: Arc, + intermediary_image_is_new: bool, + swapchain_framebuffers: SwapchainFramebuffers, +} + +pub type ViewFramebufferBuilder = FramebufferBuilder, A>; +pub type FramebufferBuildResult = Result, FramebufferCreationError>; + +/// Errors that might occur during creation of the `RenderData` for a frame. +#[derive(Debug)] +pub enum RenderDataCreationError { + RenderPassCreation(RenderPassCreationError), + ImageCreation(ImageCreationError), +} + +/// Errors that might occur during creation of the `Frame`. +#[derive(Debug)] +pub enum FrameCreationError { + ImageCreation(ImageCreationError), + FramebufferCreation(FramebufferCreationError), +} + +/// Errors that might occur during `Frame::finish`. +#[derive(Debug)] +pub enum FrameFinishError { + BeginRenderPass(BeginRenderPassError), +} + +impl Frame { + /// The default number of multisample anti-aliasing samples used if the window with which the + /// `Frame` is associated supports it. + pub const DEFAULT_MSAA_SAMPLES: u32 = 8; + + /// Initialise a new empty frame ready for "drawing". + pub(crate) fn new_empty( + raw_frame: RawFrame, + mut data: RenderData, + ) -> Result { + // If the image dimensions differ to that of the swapchain image, recreate it. + let image_dims = raw_frame.swapchain_image().dimensions(); + + if AttachmentImage::dimensions(&data.intermediary_image) != image_dims { + let msaa_samples = vulkano::image::ImageAccess::samples(&data.intermediary_image); + data.intermediary_image = create_intermediary_image( + raw_frame.swapchain_image().swapchain().device().clone(), + image_dims, + msaa_samples, + raw_frame.swapchain_image().swapchain().format(), + )?; + data.intermediary_image_is_new = true; + } + + { + let RenderData { + ref mut swapchain_framebuffers, + ref intermediary_image, + ref render_pass, + .. + } = data; + + // Ensure framebuffers are up to date with the frame's swapchain image and render pass. + swapchain_framebuffers.update(&raw_frame, render_pass.clone(), |builder, image| { + builder.add(intermediary_image.clone())?.add(image) + })?; + } + + Ok(Frame { raw_frame, data }) + } + + /// Called after the user's `view` function returns, this consumes the `Frame`, adds commands + /// for drawing the `intermediary_image` to the `swapchain_image` and returns the inner + /// `RenderData` and `RawFrame` so that the `RenderData` may be stored back within the `Window` + /// and the `RawFrame` may be `finish`ed. + pub(crate) fn finish(self) -> Result<(RenderData, RawFrame), FrameFinishError> { + let Frame { mut data, raw_frame } = self; + + // The framebuffer for the current swapchain image. + let framebuffer = data.swapchain_framebuffers[raw_frame.swapchain_image_index()].clone(); + + // Neither the intermediary image nor swapchain image require clearing upon load. + let clear_values = vec![ClearValue::None, ClearValue::None]; + let is_secondary = false; + + raw_frame + .add_commands() + .begin_render_pass(framebuffer, is_secondary, clear_values)? + .end_render_pass() + .expect("failed to add `end_render_pass` command"); + + // The intermediary image is no longer "new". + data.intermediary_image_is_new = false; + + Ok((data, raw_frame)) + } + + /// The image to which all graphics should be drawn this frame. + /// + /// After the **view** function returns, this image will be resolved to the swapchain image for + /// this frame and then that swapchain image will be presented to the screen. + pub fn image(&self) -> &Arc { + &self.data.intermediary_image + } + + /// If the **Frame**'s image is new because it is the first frame or because it was recently + /// recreated to match the dimensions of the window's swapchain, this will return `true`. + /// + /// This is useful for determining whether or not a user's framebuffer might need + /// reconstruction in the case that it targets the frame's image. + pub fn image_is_new(&self) -> bool { + self.raw_frame.nth() == 0 || self.data.intermediary_image_is_new + } + + /// The color format of the `Frame`'s intermediary image. + pub fn image_format(&self) -> Format { + vulkano::image::ImageAccess::format(self.image()) + } + + /// The number of MSAA samples of the `Frame`'s intermediary image. + pub fn image_msaa_samples(&self) -> u32 { + vulkano::image::ImageAccess::samples(self.image()) + } + + /// Clear the image with the given color. + pub fn clear(&self, color: C) + where + C: IntoRgba, + { + let rgba = color.into_rgba(); + let value = ClearValue::Float([rgba.red, rgba.green, rgba.blue, rgba.alpha]); + let image = self.data.intermediary_image.clone(); + self.add_commands() + .clear_color_image(image, value) + .expect("failed to submit `clear_color_image` command"); + } +} + +impl ViewFramebuffer { + /// Ensure the framebuffer is up to date with the render pass and `frame`'s image. + pub fn update( + &mut self, + frame: &Frame, + render_pass: Arc, + builder: F, + ) -> Result<(), FramebufferCreationError> + where + F: Fn(ViewFramebufferBuilder<()>, Arc) -> FramebufferBuildResult, + A: 'static + AttachmentsList + Send + Sync, + { + let needs_creation = frame.image_is_new() + || self.framebuffer.is_none() + || RenderPassAbstract::inner(self.framebuffer.as_ref().unwrap()).internal_object() + != render_pass.inner().internal_object(); + if needs_creation { + let builder = builder( + Framebuffer::start(render_pass.clone()), + frame.data.intermediary_image.clone(), + )?; + let fb = builder.build()?; + self.framebuffer = Some(Arc::new(fb)); + } + Ok(()) + } +} + +impl RenderData { + /// Initialise the render data. + /// + /// Creates an `AttachmentImage` with the given parameters. + /// + /// If `msaa_samples` is greater than 1 a `multisampled` image will be created. Otherwise the + /// a regular non-multisampled image will be created. + pub(crate) fn new( + device: Arc, + dimensions: [u32; 2], + msaa_samples: u32, + format: Format, + ) -> Result { + let render_pass = create_render_pass(device.clone(), format, msaa_samples)?; + let intermediary_image = create_intermediary_image(device, dimensions, msaa_samples, format)?; + let swapchain_framebuffers = Default::default(); + let intermediary_image_is_new = true; + Ok(RenderData { + render_pass, + intermediary_image, + swapchain_framebuffers, + intermediary_image_is_new, + }) + } +} + +impl ops::Deref for Frame { + type Target = RawFrame; + fn deref(&self) -> &Self::Target { + &self.raw_frame + } +} + +impl ops::Deref for ViewFramebuffer { + type Target = Option>; + fn deref(&self) -> &Self::Target { + &self.framebuffer + } +} + +impl From for RenderDataCreationError { + fn from(err: RenderPassCreationError) -> Self { + RenderDataCreationError::RenderPassCreation(err) + } +} + +impl From for RenderDataCreationError { + fn from(err: ImageCreationError) -> Self { + RenderDataCreationError::ImageCreation(err) + } +} + +impl From for FrameCreationError { + fn from(err: ImageCreationError) -> Self { + FrameCreationError::ImageCreation(err) + } +} + +impl From for FrameCreationError { + fn from(err: FramebufferCreationError) -> Self { + FrameCreationError::FramebufferCreation(err) + } +} + +impl From for FrameFinishError { + fn from(err: BeginRenderPassError) -> Self { + FrameFinishError::BeginRenderPass(err) + } +} + +impl StdError for RenderDataCreationError { + fn description(&self) -> &str { + match *self { + RenderDataCreationError::RenderPassCreation(ref err) => err.description(), + RenderDataCreationError::ImageCreation(ref err) => err.description(), + } + } + + fn cause(&self) -> Option<&StdError> { + match *self { + RenderDataCreationError::RenderPassCreation(ref err) => Some(err), + RenderDataCreationError::ImageCreation(ref err) => Some(err), + } + } +} + +impl StdError for FrameCreationError { + fn description(&self) -> &str { + match *self { + FrameCreationError::ImageCreation(ref err) => err.description(), + FrameCreationError::FramebufferCreation(ref err) => err.description(), + } + } + + fn cause(&self) -> Option<&StdError> { + match *self { + FrameCreationError::ImageCreation(ref err) => Some(err), + FrameCreationError::FramebufferCreation(ref err) => Some(err), + } + } +} + +impl StdError for FrameFinishError { + fn description(&self) -> &str { + match *self { + FrameFinishError::BeginRenderPass(ref err) => err.description(), + } + } + + fn cause(&self) -> Option<&StdError> { + match *self { + FrameFinishError::BeginRenderPass(ref err) => Some(err), + } + } +} + +impl fmt::Display for RenderDataCreationError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.description()) + } +} + +impl fmt::Display for FrameCreationError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.description()) + } +} + +impl fmt::Display for FrameFinishError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.description()) + } +} + +impl fmt::Debug for RenderData { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "RenderData") + } +} + +// A function to simplify creating the `AttachmentImage` used as the intermediary image render +// target. +// +// If `msaa_samples` is 0 or 1, a non-multisampled image will be created. +fn create_intermediary_image( + device: Arc, + dimensions: [u32; 2], + msaa_samples: u32, + format: Format, +) -> Result, ImageCreationError> { + let usage = ImageUsage { + transfer_source: true, + transfer_destination: true, + color_attachment: true, + //sampled: true, + ..ImageUsage::none() + }; + match msaa_samples { + 0 | 1 => AttachmentImage::with_usage(device, dimensions, format, usage), + _ => { + AttachmentImage::multisampled_with_usage( + device, + dimensions, + msaa_samples, + format, + usage, + ) + }, + } +} + +// Create the render pass for drawing the intermediary image to the swapchain image. +fn create_render_pass( + device: Arc, + color_format: Format, + msaa_samples: u32, +) -> Result, RenderPassCreationError> { + match msaa_samples { + // Render pass without multisampling. + 0 | 1 => { + let rp = single_pass_renderpass!( + device, + attachments: { + intermediary_color: { + load: Load, + store: Store, + format: color_format, + samples: 1, + }, + swapchain_color: { + load: DontCare, + store: Store, + format: color_format, + samples: 1, + initial_layout: ImageLayout::PresentSrc, + final_layout: ImageLayout::PresentSrc, + } + }, + pass: { + color: [swapchain_color], + depth_stencil: {} + } + )?; + Ok(Arc::new(rp) as Arc) + } + + // Renderpass with multisampling. + _ => { + let rp = single_pass_renderpass!( + device, + attachments: { + intermediary_color: { + load: Load, + store: Store, + format: color_format, + samples: msaa_samples, + }, + swapchain_color: { + load: DontCare, + store: Store, + format: color_format, + samples: 1, + initial_layout: ImageLayout::PresentSrc, + final_layout: ImageLayout::PresentSrc, + } + }, + pass: { + color: [intermediary_color], + depth_stencil: {} + resolve: [swapchain_color], + } + )?; + Ok(Arc::new(rp) as Arc) + } + } +} diff --git a/src/frame.rs b/src/frame/raw.rs similarity index 96% rename from src/frame.rs rename to src/frame/raw.rs index ae7162d69..3fe01120b 100644 --- a/src/frame.rs +++ b/src/frame/raw.rs @@ -1,4 +1,5 @@ -use draw::properties::color::IntoRgba; +//! The lower-level "raw" frame type allowing to draw directly to the window's swapchain image. + use std::sync::{Arc, Mutex}; use vulkano; use vulkano::buffer::{BufferAccess, TypedBufferAccess}; @@ -19,15 +20,15 @@ use vulkano::sampler::Filter; use window; use window::SwapchainImage; -/// Allows the user to draw a single **Frame** to the surface of a window. +/// Allows the user to draw a single **RawFrame** to the surface of a window. /// /// The application's **view** function is called each time the application is ready to retrieve a -/// new image that will be displayed to a window. The **Frame** type can be thought of as the +/// new image that will be displayed to a window. The **RawFrame** type can be thought of as the /// canvas to which you draw this image. /// /// ## Under the hood - Vulkan /// -/// There are a couple of main goals for the **Frame** type: +/// There are a couple of main goals for the **RawFrame** type: /// /// - Allow for maximum flexibility and customisation over the: /// - Render Pass @@ -113,7 +114,7 @@ use window::SwapchainImage; /// /// **Note:** If you find you are unable to do something or that this API is too restrictive, /// please open an issue about it so that might be able to work out a solution! -pub struct Frame { +pub struct RawFrame { // The `AutoCommandBufferBuilder` type used for building the frame's command buffer. // // An `Option` is used here to allow for taking the builder by `self` as type's builder methods @@ -136,16 +137,16 @@ pub struct Frame { } /// A builder type that allows chaining together commands for the command buffer that will be used -/// to draw to the swapchain image framebuffer associated with this **Frame**. +/// to draw to the swapchain image framebuffer associated with this **RawFrame**. pub struct AddCommands<'a> { - frame: &'a Frame, + frame: &'a RawFrame, } // The `AutoCommandBufferBuilder` type used for building the frame's command buffer. type AutoCommandBufferBuilder = vulkano::command_buffer::AutoCommandBufferBuilder; -impl Frame { +impl RawFrame { // Initialise a new empty frame ready for "drawing". pub(crate) fn new_empty( queue: Arc, @@ -158,7 +159,7 @@ impl Frame { let device = queue.device().clone(); let cb_builder = AutoCommandBufferBuilder::primary_one_time_submit(device, queue.family())?; let command_buffer_builder = Mutex::new(Some(cb_builder)); - let frame = Frame { + let frame = RawFrame { command_buffer_builder, window_id, nth, @@ -170,7 +171,7 @@ impl Frame { Ok(frame) } - // Called after the user's `view` function, this consumes the `Frame` and returns the inner + // Called after the user's `view` function, this consumes the `RawFrame` and returns the inner // command buffer builder so that it can be completed. pub(crate) fn finish(self) -> AutoCommandBufferBuilder { self.command_buffer_builder @@ -193,25 +194,12 @@ impl Frame { (self.nth - self.swapchain_image_index as u64) == self.swapchain_frame_created } - /// Add commands to be executed by the GPU once the **Frame** is returned. + /// Add commands to be executed by the GPU once the **RawFrame** is returned. pub fn add_commands(&self) -> AddCommands { let frame = self; AddCommands { frame } } - /// Clear the image with the given color. - pub fn clear(&self, color: C) - where - C: IntoRgba, - { - let rgba = color.into_rgba(); - let value = ClearValue::Float([rgba.red, rgba.green, rgba.blue, rgba.alpha]); - let image = self.swapchain_image.clone(); - self.add_commands() - .clear_color_image(image, value) - .expect("failed to submit `clear_color_image` command"); - } - /// The `Id` of the window whose vulkan surface is associated with this frame. pub fn window_id(&self) -> window::Id { self.window_id @@ -254,10 +242,10 @@ impl<'a> AddCommands<'a> { { let mut guard = self.frame.command_buffer_builder .lock() - .expect("failed to lock `Frame`'s inner command buffer builder"); + .expect("failed to lock `RawFrame`'s inner command buffer builder"); let mut builder = guard .take() - .expect("the `Frame`'s inner command buffer should always be `Some`"); + .expect("the `RawFrame`'s inner command buffer should always be `Some`"); builder = map(builder)?; *guard = Some(builder); } diff --git a/src/gpu.rs b/src/gpu.rs index 9b1fab456..d6cf8d13f 100644 --- a/src/gpu.rs +++ b/src/gpu.rs @@ -4,7 +4,8 @@ use std::borrow::Cow; use std::panic::RefUnwindSafe; use std::sync::Arc; use vulkano::format::Format; -use vulkano::instance::{ApplicationInfo, Instance, InstanceCreationError, InstanceExtensions}; +use vulkano::instance::{ApplicationInfo, Instance, InstanceCreationError, InstanceExtensions, + PhysicalDevice}; use vulkano::instance::debug::{DebugCallback, DebugCallbackCreationError, Message, MessageTypes}; use vulkano::instance::loader::{FunctionPointers, Loader}; use vulkano_win; @@ -235,3 +236,17 @@ pub fn format_is_srgb(format: Format) -> bool { _ => false, } } + +/// Given some target MSAA samples, limit it by the capabilities of the given `physical_device`. +/// +/// This is useful for attempting a specific multisampling sample count but falling back to a +/// supported count in the case that the desired count is unsupported. +/// +/// Specifically, this function limits the given `target_msaa_samples` to the minimum of the color +/// and depth sample count limits. +pub fn msaa_samples_limited(physical_device: &PhysicalDevice, target_msaa_samples: u32) -> u32 { + let color_limit = physical_device.limits().framebuffer_color_sample_counts(); + let depth_limit = physical_device.limits().framebuffer_depth_sample_counts(); + let msaa_limit = std::cmp::min(color_limit, depth_limit); + std::cmp::min(msaa_limit, target_msaa_samples) +} diff --git a/src/lib.rs b/src/lib.rs index 5df08146b..1a1f35b04 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -34,7 +34,7 @@ pub mod color; pub mod draw; pub mod ease; pub mod event; -mod frame; +pub mod frame; pub mod geom; pub mod gpu; pub mod image; diff --git a/src/prelude.rs b/src/prelude.rs index 84691afbc..a272f5cf4 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -7,7 +7,7 @@ pub use color::{Hsl, Hsla, Hsv, Hsva, Rgb, Rgba}; pub use event::WindowEvent::*; pub use event::{AxisMotion, Event, Key, MouseButton, MouseScrollDelta, TouchEvent, TouchPhase, TouchpadPressure, Update, WindowEvent}; -pub use frame::Frame; +pub use frame::{Frame, RawFrame, ViewFramebuffer}; pub use geom::{ self, pt2, pt3, vec2, vec3, vec4, Cuboid, Point2, Point3, Rect, Vector2, Vector3, Vector4, }; diff --git a/src/ui.rs b/src/ui.rs index 39c461dac..4f564d125 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -23,7 +23,7 @@ pub mod prelude { pub use super::{color, image, position, text, widget}; } -use frame::Frame; +use frame::{Frame, ViewFramebuffer}; use self::conrod_core::text::rt::gpu_cache::CacheWriteErr; use self::conrod_vulkano::RendererCreationError; use std::cell::RefCell; @@ -37,8 +37,7 @@ use vulkano::command_buffer::CopyBufferImageError; use vulkano::format::{ClearValue, D16Unorm, Format}; use vulkano::framebuffer::{FramebufferCreationError, RenderPassAbstract, RenderPassCreationError}; use vulkano::image::{AttachmentImage, ImageCreationError}; -use vulkano::instance::PhysicalDevice; -use window::{self, SwapchainFramebuffers, Window}; +use window::{self, Window}; use winit; use App; @@ -81,14 +80,11 @@ enum RenderMode { struct RenderTarget { render_pass: Arc, images: RenderPassImages, - swapchain_framebuffers: SwapchainFramebuffers, - msaa_samples: u32, + view_framebuffer: ViewFramebuffer, } // The buffers associated with a render target. struct RenderPassImages { - multisampled_color: Arc, - multisampled_depth: Arc, depth: Arc>, } @@ -103,7 +99,6 @@ pub struct Builder<'a> { default_font_path: Option, glyph_cache_dimensions: Option<(u32, u32)>, render_pass_subpass: Option, - msaa_samples: Option, } /// Failed to build the `Ui`. @@ -153,9 +148,6 @@ pub const DEPTH_FORMAT_TY: DepthFormat = D16Unorm; /// The depth format used by the depth buffer in the default render target. pub const DEPTH_FORMAT: Format = Format::D16Unorm; -/// The default samples used for multisampling the `Ui` when using this module's render target. -pub const DEFAULT_MSAA_SAMPLES: u32 = 4; - impl conrod_winit::WinitWindow for Window { fn get_inner_size(&self) -> Option<(u32, u32)> { let (w, h) = self.inner_size_points(); @@ -187,7 +179,6 @@ impl<'a> Builder<'a> { default_font_path: None, glyph_cache_dimensions: None, render_pass_subpass: None, - msaa_samples: None, } } @@ -277,17 +268,6 @@ impl<'a> Builder<'a> { self } - /// The number of samples to use for multisample anti aliasing. - /// - /// If unspecified, uses `DEFAULT_MSAA_SAMPLES`. - /// - /// This parameter is only valid if the `Ui` has it's own render target and render pass. In - /// other words, if `subpass` has been specified, this parameter will have no effect. - pub fn msaa_samples(mut self, msaa_samples: u32) -> Self { - self.msaa_samples = Some(msaa_samples); - self - } - /// Build a `Ui` with the specified parameters. /// /// Returns `None` if the window at the given `Id` is closed or if the inner `Renderer` returns @@ -303,7 +283,6 @@ impl<'a> Builder<'a> { default_font_path, glyph_cache_dimensions, render_pass_subpass, - msaa_samples, } = self; // If the user didn't specify a window, use the "main" one. @@ -348,12 +327,7 @@ impl<'a> Builder<'a> { let (subpass, render_mode) = match render_pass_subpass { Some(subpass) => (subpass, Mutex::new(RenderMode::Subpass)), None => { - let device = window.swapchain_device(); - let msaa_samples = match msaa_samples { - None => valid_msaa_samples(&device.physical_device()), - Some(samples) => samples, - }; - let render_target = RenderTarget::new(&window, msaa_samples)?; + let render_target = RenderTarget::new(&window)?; let subpass = Subpass::from(render_target.render_pass.clone(), 0) .expect("unable to retrieve subpass for index `0`"); let render_mode = Mutex::new(RenderMode::OwnedRenderTarget(render_target)); @@ -410,16 +384,6 @@ impl<'a> Builder<'a> { } } -/// Determine the number of samples to use for MSAA. -/// -/// The target is `DEFAULT_MSAA_SAMPLES`, but we fall back to the limit if its lower. -pub fn valid_msaa_samples(physical_device: &PhysicalDevice) -> u32 { - let color = physical_device.limits().framebuffer_color_sample_counts(); - let depth = physical_device.limits().framebuffer_depth_sample_counts(); - let limit = std::cmp::min(color, depth); - std::cmp::min(limit, DEFAULT_MSAA_SAMPLES) -} - /// Create a minimal, single-pass render pass with which the `Ui` may be rendered. /// /// This is used internally within the `Ui` build process so in most cases you should not need to @@ -433,39 +397,24 @@ pub fn create_render_pass( let render_pass = single_pass_renderpass!( device, attachments: { - multisampled_color: { - load: DontCare, - store: DontCare, - format: color_format, - samples: msaa_samples, - }, color: { - load: DontCare, + load: Load, store: Store, format: color_format, - samples: 1, - initial_layout: ImageLayout::PresentSrc, - final_layout: ImageLayout::PresentSrc, - }, - multisampled_depth: { - load: DontCare, - store: DontCare, - format: depth_format, samples: msaa_samples, }, depth: { - load: DontCare, + load: Load, store: Store, format: depth_format, - samples: 1, + samples: msaa_samples, initial_layout: ImageLayout::Undefined, final_layout: ImageLayout::DepthStencilAttachmentOptimal, } }, pass: { - color: [multisampled_color], - depth_stencil: {multisampled_depth}, - resolve: [color], + color: [color], + depth_stencil: {depth} } )?; Ok(Arc::new(render_pass)) @@ -473,30 +422,18 @@ pub fn create_render_pass( impl RenderPassImages { // Create the buffers for a default render target. - fn new( - window: &Window, - msaa_samples: u32, - ) -> Result { + fn new(window: &Window) -> Result { let device = window.swapchain_device().clone(); // TODO: Change this to use `window.inner_size_pixels/points` (which is correct?). let image_dims = window.swapchain_images()[0].dimensions(); - let color_format = window.swapchain().format(); - let multisampled_color = AttachmentImage::transient_multisampled( - device.clone(), - image_dims, - msaa_samples, - color_format, - )?; - let multisampled_depth = AttachmentImage::transient_multisampled( + let msaa_samples = window.msaa_samples(); + let depth = AttachmentImage::transient_multisampled( device.clone(), image_dims, msaa_samples, - DEPTH_FORMAT, + DEPTH_FORMAT_TY, )?; - let depth = AttachmentImage::transient(device, image_dims, DEPTH_FORMAT_TY)?; Ok(RenderPassImages { - multisampled_color, - multisampled_depth, depth, }) } @@ -504,13 +441,14 @@ impl RenderPassImages { impl RenderTarget { // Initialise a new render target. - fn new(window: &Window, msaa_samples: u32) -> Result { + fn new(window: &Window) -> Result { let device = window.swapchain_device().clone(); let color_format = window.swapchain().format(); + let msaa_samples = window.msaa_samples(); let render_pass = create_render_pass(device, color_format, DEPTH_FORMAT, msaa_samples)?; - let images = RenderPassImages::new(window, msaa_samples)?; - let swapchain_framebuffers = SwapchainFramebuffers::default(); - Ok(RenderTarget { render_pass, images, msaa_samples, swapchain_framebuffers }) + let images = RenderPassImages::new(window)?; + let view_framebuffer = ViewFramebuffer::default(); + Ok(RenderTarget { render_pass, images, view_framebuffer }) } } @@ -685,22 +623,19 @@ pub fn draw_primitives( let RenderTarget { ref render_pass, ref mut images, - ref mut swapchain_framebuffers, - msaa_samples, + ref mut view_framebuffer, } = *render_target; // Recreate buffers if the swapchain was recreated. let image_dims = frame.swapchain_image().dimensions(); - if image_dims != images.multisampled_color.dimensions() { - *images = RenderPassImages::new(&window, msaa_samples)?; + if image_dims != images.depth.dimensions() { + *images = RenderPassImages::new(&window)?; } - // Ensure swapchain framebuffers are up to date. - swapchain_framebuffers.update(&frame, render_pass.clone(), |builder, image| { + // Ensure image framebuffer are up to date. + view_framebuffer.update(&frame, render_pass.clone(), |builder, image| { builder - .add(images.multisampled_color.clone())? .add(image.clone())? - .add(images.multisampled_depth.clone())? .add(images.depth.clone()) })?; @@ -736,15 +671,13 @@ pub fn draw_primitives( let queue = window.swapchain_queue().clone(); let cmds = renderer.draw(queue, image_map, viewport)?; if !cmds.is_empty() { - let multisampled_color = ClearValue::None; let color = ClearValue::None; - let multisampled_depth = ClearValue::None; let depth = ClearValue::None; - let clear_values = vec![multisampled_color, color, multisampled_depth, depth]; + let clear_values = vec![color, depth]; frame .add_commands() .begin_render_pass( - swapchain_framebuffers[frame.swapchain_image_index()].clone(), + view_framebuffer.as_ref().unwrap().clone(), false, clear_values.clone(), ) diff --git a/src/window.rs b/src/window.rs index 2a3f01919..788197205 100644 --- a/src/window.rs +++ b/src/window.rs @@ -3,7 +3,7 @@ use app::LoopMode; use event::{Key, MouseButton, MouseScrollDelta, TouchEvent, TouchPhase, TouchpadPressure, WindowEvent}; -use frame::Frame; +use frame::{self, Frame, RawFrame}; use geom; use geom::{Point2, Vector2}; use gpu; @@ -44,6 +44,7 @@ pub struct Builder<'app> { title_was_set: bool, swapchain_builder: SwapchainBuilder, user_functions: UserFunctions, + msaa_samples: Option, } /// For storing all user functions within the window. @@ -75,6 +76,12 @@ pub(crate) struct UserFunctions { /// The user function type for drawing their model to the surface of a single window. pub type ViewFn = fn(&App, &Model, Frame) -> Frame; +/// The user function type for drawing their model to the surface of a single window. +/// +/// Unlike the `ViewFn`, the `RawViewFn` is designed for drawing directly to a window's swapchain +/// images rather than to a convenient intermediary image. +pub type RawViewFn = fn(&App, &Model, RawFrame) -> RawFrame; + /// The same as `ViewFn`, but provides no user model to draw from. /// /// Useful for simple, stateless sketching. @@ -84,6 +91,7 @@ pub type SketchFn = fn(&App, Frame) -> Frame; #[derive(Clone)] pub(crate) enum View { WithModel(ViewFnAny), + WithModelRaw(RawViewFnAny), Sketch(SketchFn), } @@ -185,6 +193,7 @@ macro_rules! fn_any { } fn_any!(ViewFn, ViewFnAny); +fn_any!(RawViewFn, RawViewFnAny); fn_any!(EventFn, EventFnAny); fn_any!(RawEventFn, RawEventFnAny); fn_any!(KeyPressedFn, KeyPressedFnAny); @@ -214,7 +223,10 @@ fn_any!(ClosedFn, ClosedFnAny); pub struct Window { pub(crate) queue: Arc, pub(crate) surface: Arc, + msaa_samples: u32, pub(crate) swapchain: Arc, + // Data for rendering a `Frame`'s intermediary image to a swapchain image. + pub(crate) frame_render_data: Option, pub(crate) frame_count: u64, pub(crate) user_functions: UserFunctions, // If the user specified one of the following parameters, use these when recreating the @@ -290,14 +302,14 @@ pub struct SwapchainFramebuffers { framebuffers: Vec>, } -type SwapchainFramebufferBuilder = FramebufferBuilder, A>; -type FramebufferBuildResult = Result, FramebufferCreationError>; +pub type SwapchainFramebufferBuilder = FramebufferBuilder, A>; +pub type FramebufferBuildResult = Result, FramebufferCreationError>; impl SwapchainFramebuffers { /// Ensure the framebuffers are up to date with the render pass and frame's swapchain image. pub fn update( &mut self, - frame: &Frame, + frame: &RawFrame, render_pass: Arc, builder: F, ) -> Result<(), FramebufferCreationError> @@ -347,6 +359,7 @@ pub enum BuildError { DeviceCreation(vulkano::device::DeviceCreationError), SwapchainCreation(SwapchainCreationError), SwapchainCapabilities(vulkano::swapchain::CapabilitiesError), + RenderDataCreation(frame::RenderDataCreationError), SurfaceDoesNotSupportCompositeAlphaOpaque, } @@ -605,6 +618,7 @@ impl<'app> Builder<'app> { title_was_set: false, swapchain_builder: Default::default(), user_functions: Default::default(), + msaa_samples: None, } } @@ -626,6 +640,33 @@ impl<'app> Builder<'app> { self } + /// Specify the number of samples per pixel for the multisample anti-aliasing render pass. + /// + /// If `msaa_samples` is unspecified, the first default value that nannou will attempt to use + /// can be found via the `Frame::DEFAULT_MSAA_SAMPLES` constant. If however this value is not + /// supported by the window's swapchain, nannou will fallback to the next smaller power of 2 + /// that is supported. If MSAA is not supported at all, then the default will be 1. + /// + /// **Note:** This parameter has no meaning if the window uses a **raw_view** function for + /// rendering graphics to the window rather than a **view** function. This is because the + /// **raw_view** function provides a **RawFrame** with direct access to the swapchain image + /// itself and thus must manage their own MSAA pass. + /// + /// On the other hand, the `view` function provides the `Frame` type which allows the user to + /// render to a multisampled intermediary image allowing Nannou to take care of resolving the + /// multisampled image to the swapchain image. In order to avoid confusion, The `Window::build` + /// method will `panic!` if the user tries to specify `msaa_samples` as well as a `raw_view` + /// method. + /// + /// *TODO: Perhaps it would be worth adding two separate methods for specifying msaa samples. + /// One for forcing a certain number of samples and returning an error otherwise, and another + /// for attempting to use the given number of samples but falling back to a supported value in + /// the case that the specified number is not supported.* + pub fn msaa_samples(mut self, msaa_samples: u32) -> Self { + self.msaa_samples = Some(msaa_samples); + self + } + /// Provide a simple function for drawing to the window. /// /// This is similar to `view` but does not provide access to user data via a Model type. This @@ -635,7 +676,7 @@ impl<'app> Builder<'app> { self } - /// The `view` function that the app will call to allow you to present your Model to the + /// The **view** function that the app will call to allow you to present your Model to the /// surface of the window on your display. pub fn view(mut self, view_fn: ViewFn) -> Self where @@ -645,6 +686,20 @@ impl<'app> Builder<'app> { self } + /// The **view** function that the app will call to allow you to present your Model to the + /// surface of the window on your display. + /// + /// Unlike the **ViewFn**, the **RawViewFn** provides a **RawFrame** that is designed for + /// drawing directly to a window's swapchain images, rather than to a convenient intermediary + /// image. + pub fn raw_view(mut self, raw_view_fn: RawViewFn) -> Self + where + M: 'static, + { + self.user_functions.view = Some(View::WithModelRaw(RawViewFnAny::from_fn_ptr(raw_view_fn))); + self + } + /// A function for updating your model on `WindowEvent`s associated with this window. /// /// These include events such as key presses, mouse movement, clicks, resizing, etc. @@ -859,6 +914,7 @@ impl<'app> Builder<'app> { title_was_set, swapchain_builder, user_functions, + msaa_samples, } = self; // If the title was not set, default to the "nannou - ". @@ -967,6 +1023,23 @@ impl<'app> Builder<'app> { )? }; + // If we're using an intermediary image for rendering frames to swapchain images, create + // the necessary render data. + let (frame_render_data, msaa_samples) = match user_functions.view { + Some(View::WithModel(_)) | Some(View::Sketch(_)) | None => { + let target_msaa_samples = msaa_samples.unwrap_or(Frame::DEFAULT_MSAA_SAMPLES); + let msaa_samples = gpu::msaa_samples_limited(&physical_device, target_msaa_samples); + let render_data = frame::RenderData::new( + device.clone(), + swapchain.dimensions(), + msaa_samples, + swapchain.format(), + )?; + (Some(render_data), msaa_samples) + } + Some(View::WithModelRaw(_)) => (None, 1), + }; + let window_id = surface.window().id(); let needs_recreation = AtomicBool::new(false); let previous_frame_end = Mutex::new(None); @@ -981,7 +1054,9 @@ impl<'app> Builder<'app> { let window = Window { queue, surface, + msaa_samples, swapchain, + frame_render_data, frame_count, user_functions, user_specified_present_mode, @@ -1008,6 +1083,7 @@ impl<'app> Builder<'app> { title_was_set, swapchain_builder, user_functions, + msaa_samples, } = self; let window = map(window); Builder { @@ -1017,6 +1093,7 @@ impl<'app> Builder<'app> { title_was_set, swapchain_builder, user_functions, + msaa_samples, } } @@ -1322,28 +1399,18 @@ impl Window { &self.swapchain.images } - // Custom methods. - - /// Attempts to determine whether or not the window is currently fullscreen. + /// The number of samples used in the MSAA for the image associated with the `view` function's + /// `Frame` type. /// - /// TODO: This currently relies on comparing `outer_size_pixels` to the dimensions of the - /// `current_monitor`, which may not be exactly accurate on some platforms or even conceptually - /// correct in the case that a title bar is included or something. This should probably be a - /// method upstream within the `winit` crate itself. Alternatively we could attempt to manually - /// track whether or not the window is fullscreen ourselves, however this could get quite - /// complicated quite quickly. - pub fn is_fullscreen(&self) -> bool { - let (w, h) = self.outer_size_pixels(); - let (mw, mh): (u32, u32) = self.current_monitor().get_dimensions().into(); - w == mw && h == mh + /// **Note:** If the user specified a `raw_view` function rather than a `view` function, this + /// value will always return `1`. + pub fn msaa_samples(&self) -> u32 { + self.msaa_samples } - /// The number of times `view` has been called with a `Frame` for this window. - pub fn elapsed_frames(&self) -> u64 { - self.frame_count - } + // Custom methods. - /// A utility function to simplify the recreation of a swapchain. + // A utility function to simplify the recreation of a swapchain. pub(crate) fn replace_swapchain( &mut self, new_swapchain: Arc, @@ -1362,6 +1429,26 @@ impl Window { images: new_images, previous_frame_end: Mutex::new(previous_frame_end), }); + // TODO: Update frame_render_data? + } + + /// Attempts to determine whether or not the window is currently fullscreen. + /// + /// TODO: This currently relies on comparing `outer_size_pixels` to the dimensions of the + /// `current_monitor`, which may not be exactly accurate on some platforms or even conceptually + /// correct in the case that a title bar is included or something. This should probably be a + /// method upstream within the `winit` crate itself. Alternatively we could attempt to manually + /// track whether or not the window is fullscreen ourselves, however this could get quite + /// complicated quite quickly. + pub fn is_fullscreen(&self) -> bool { + let (w, h) = self.outer_size_pixels(); + let (mw, mh): (u32, u32) = self.current_monitor().get_dimensions().into(); + w == mw && h == mh + } + + /// The number of times `view` has been called with a `Frame` for this window. + pub fn elapsed_frames(&self) -> u64 { + self.frame_count } } @@ -1371,6 +1458,7 @@ impl fmt::Debug for View { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let variant = match *self { View::WithModel(ref v) => format!("WithModel({:?})", v), + View::WithModelRaw(ref v) => format!("WithModelRaw({:?})", v), View::Sketch(_) => "Sketch".to_string(), }; write!(f, "View::{}", variant) @@ -1406,6 +1494,7 @@ impl StdError for BuildError { BuildError::DeviceCreation(ref err) => err.description(), BuildError::SwapchainCreation(ref err) => err.description(), BuildError::SwapchainCapabilities(ref err) => err.description(), + BuildError::RenderDataCreation(ref err) => err.description(), BuildError::SurfaceDoesNotSupportCompositeAlphaOpaque => "`CompositeAlpha::Opaque` not supported by window surface", } @@ -1441,3 +1530,9 @@ impl From for BuildError { BuildError::SwapchainCapabilities(e) } } + +impl From for BuildError { + fn from(e: frame::RenderDataCreationError) -> Self { + BuildError::RenderDataCreation(e) + } +}