drawing a red quad
This commit is contained in:
parent
8cf63a9348
commit
35083ee148
|
@ -0,0 +1,8 @@
|
||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
|
@ -0,0 +1,12 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="JAVA_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||||
|
<exclude-output />
|
||||||
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/target" />
|
||||||
|
</content>
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectRootManager">
|
||||||
|
<output url="file://$PROJECT_DIR$/out" />
|
||||||
|
</component>
|
||||||
|
</project>
|
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/eruption.iml" filepath="$PROJECT_DIR$/.idea/eruption.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
|
@ -181,6 +181,15 @@ version = "0.1.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e"
|
checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cmake"
|
||||||
|
version = "0.1.50"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a31c789563b815f77f4250caee12365734369f942439b7defd71e18a48197130"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "core-foundation"
|
name = "core-foundation"
|
||||||
version = "0.9.3"
|
version = "0.9.3"
|
||||||
|
@ -282,6 +291,7 @@ name = "eruption"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"vulkano",
|
"vulkano",
|
||||||
|
"vulkano-shaders",
|
||||||
"vulkano-win",
|
"vulkano-win",
|
||||||
"winit",
|
"winit",
|
||||||
]
|
]
|
||||||
|
@ -760,6 +770,15 @@ version = "0.6.29"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
|
checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "roxmltree"
|
||||||
|
version = "0.14.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "921904a62e410e37e215c40381b7117f830d9d89ba60ab5236170541dd25646b"
|
||||||
|
dependencies = [
|
||||||
|
"xmlparser",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ryu"
|
name = "ryu"
|
||||||
version = "1.0.13"
|
version = "1.0.13"
|
||||||
|
@ -822,6 +841,27 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "shaderc"
|
||||||
|
version = "0.8.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "31cef52787a0db5108788ea20bed13d6bf4b96287c5c5201e55725f7070f3443"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"shaderc-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "shaderc-sys"
|
||||||
|
version = "0.8.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1e8f8439fffcffd6efcd74197204addf935dbab5752696bd990a6cd36d54cf64"
|
||||||
|
dependencies = [
|
||||||
|
"cmake",
|
||||||
|
"libc",
|
||||||
|
"roxmltree",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "slotmap"
|
name = "slotmap"
|
||||||
version = "1.0.6"
|
version = "1.0.6"
|
||||||
|
@ -1030,6 +1070,21 @@ dependencies = [
|
||||||
"syn 1.0.109",
|
"syn 1.0.109",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "vulkano-shaders"
|
||||||
|
version = "0.33.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5f8cf18e9becbc6d39f1c39e26bcf69546c93989553eb5748cd734a8a697a6e5"
|
||||||
|
dependencies = [
|
||||||
|
"ahash",
|
||||||
|
"heck",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"shaderc",
|
||||||
|
"syn 1.0.109",
|
||||||
|
"vulkano",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "vulkano-win"
|
name = "vulkano-win"
|
||||||
version = "0.33.0"
|
version = "0.33.0"
|
||||||
|
@ -1343,3 +1398,9 @@ name = "xml-rs"
|
||||||
version = "0.8.4"
|
version = "0.8.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3"
|
checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "xmlparser"
|
||||||
|
version = "0.13.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4d25c75bf9ea12c4040a97f829154768bbbce366287e2dc044af160cd79a13fd"
|
||||||
|
|
|
@ -9,4 +9,5 @@ authors = ["Sven Vogel"]
|
||||||
[dependencies]
|
[dependencies]
|
||||||
vulkano = "0.33.0"
|
vulkano = "0.33.0"
|
||||||
vulkano-win = "0.33.0"
|
vulkano-win = "0.33.0"
|
||||||
|
vulkano-shaders = "0.33.0"
|
||||||
winit = "0.28.3"
|
winit = "0.28.3"
|
477
src/lib.rs
477
src/lib.rs
|
@ -1,10 +1,57 @@
|
||||||
use vulkano::device::{DeviceExtensions, Properties, QueueFlags};
|
mod shader;
|
||||||
use vulkano::device::physical::PhysicalDeviceType;
|
|
||||||
|
use std::sync::Arc;
|
||||||
|
use vulkano::device::{Device, DeviceCreateInfo, DeviceExtensions, Properties, Queue, QueueCreateInfo, QueueFlags};
|
||||||
|
use vulkano::device::physical::{PhysicalDevice, PhysicalDeviceType};
|
||||||
|
use vulkano::image::{ImageAccess, ImageUsage, SwapchainImage};
|
||||||
use vulkano::instance::{Instance, InstanceCreateInfo};
|
use vulkano::instance::{Instance, InstanceCreateInfo};
|
||||||
use vulkano::VulkanLibrary;
|
use vulkano::memory::allocator::{AllocationCreateInfo, MemoryUsage, StandardMemoryAllocator};
|
||||||
|
use vulkano::swapchain::{acquire_next_image, AcquireError, Surface, Swapchain, SwapchainCreateInfo, SwapchainCreationError, SwapchainPresentInfo};
|
||||||
|
use vulkano::{sync, VulkanLibrary};
|
||||||
use vulkano_win::VkSurfaceBuild;
|
use vulkano_win::VkSurfaceBuild;
|
||||||
use winit::event_loop::EventLoop;
|
use winit::event_loop::{ControlFlow, EventLoop};
|
||||||
use winit::window::WindowBuilder;
|
use winit::window::{Window, WindowBuilder};
|
||||||
|
use vulkano::buffer::{Buffer, BufferContents, BufferCreateInfo, BufferUsage};
|
||||||
|
use vulkano::command_buffer::allocator::StandardCommandBufferAllocator;
|
||||||
|
use vulkano::command_buffer::{AutoCommandBufferBuilder, CommandBufferUsage, RenderPassBeginInfo, SubpassContents};
|
||||||
|
use vulkano::image::view::ImageView;
|
||||||
|
use vulkano::pipeline::graphics::vertex_input::Vertex;
|
||||||
|
use vulkano::pipeline::graphics::viewport::Viewport;
|
||||||
|
use vulkano::render_pass::{Framebuffer, FramebufferCreateInfo, RenderPass};
|
||||||
|
use vulkano::sync::{FlushError, GpuFuture};
|
||||||
|
use winit::event::{Event, WindowEvent};
|
||||||
|
|
||||||
|
// We now create a buffer that will store the shape of our triangle. We use `#[repr(C)]` here
|
||||||
|
// to force rustc to use a defined layout for our data, as the default representation has *no
|
||||||
|
// guarantees*.
|
||||||
|
#[derive(BufferContents, Vertex)]
|
||||||
|
#[repr(C)]
|
||||||
|
struct Vertex2d {
|
||||||
|
#[format(R32G32_SFLOAT)]
|
||||||
|
position: [f32; 2],
|
||||||
|
}
|
||||||
|
|
||||||
|
const QUAD_VERTICES: [Vertex2d; 6] = [
|
||||||
|
Vertex2d {
|
||||||
|
position: [-1.0, -1.0]
|
||||||
|
},
|
||||||
|
Vertex2d {
|
||||||
|
position: [1.0, -1.0]
|
||||||
|
},
|
||||||
|
Vertex2d {
|
||||||
|
position: [1.0, 1.0]
|
||||||
|
},
|
||||||
|
|
||||||
|
Vertex2d {
|
||||||
|
position: [-1.0, -1.0]
|
||||||
|
},
|
||||||
|
Vertex2d {
|
||||||
|
position: [1.0, 1.0]
|
||||||
|
},
|
||||||
|
Vertex2d {
|
||||||
|
position: [-1.0, 1.0]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
pub fn init() {
|
pub fn init() {
|
||||||
let lib = VulkanLibrary::new().unwrap();
|
let lib = VulkanLibrary::new().unwrap();
|
||||||
|
@ -38,6 +85,284 @@ pub fn init() {
|
||||||
.build_vk_surface(&event_loop, instance.clone())
|
.build_vk_surface(&event_loop, instance.clone())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
let (device, mut queues) = get_device(&instance, &surface);
|
||||||
|
|
||||||
|
// Since we can request multiple queues, the `queues` variable is in fact an iterator. We only
|
||||||
|
// use one queue in this example, so we just retrieve the first and only element of the
|
||||||
|
// iterator.
|
||||||
|
let queue = queues.next().unwrap();
|
||||||
|
|
||||||
|
let (mut swapchain, images) = create_swapchain(&device, &surface);
|
||||||
|
|
||||||
|
let memory_allocator = StandardMemoryAllocator::new_default(device.clone());
|
||||||
|
|
||||||
|
let vertex_buffer = Buffer::from_iter(
|
||||||
|
&memory_allocator,
|
||||||
|
BufferCreateInfo {
|
||||||
|
usage: BufferUsage::VERTEX_BUFFER,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
AllocationCreateInfo {
|
||||||
|
usage: MemoryUsage::Upload,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
QUAD_VERTICES,
|
||||||
|
).unwrap();
|
||||||
|
|
||||||
|
// At this point, OpenGL initialization would be finished. However in Vulkan it is not. OpenGL
|
||||||
|
// implicitly does a lot of computation whenever you draw. In Vulkan, you have to do all this
|
||||||
|
// manually.
|
||||||
|
|
||||||
|
let render_pass = create_render_pass(&device, &swapchain);
|
||||||
|
|
||||||
|
// Dynamic viewports allow us to recreate just the viewport when the window is resized.
|
||||||
|
// Otherwise we would have to recreate the whole pipeline.
|
||||||
|
let mut viewport = Viewport {
|
||||||
|
origin: [0.0, 0.0],
|
||||||
|
dimensions: [0.0, 0.0],
|
||||||
|
depth_range: 0.0..1.0,
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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 mut framebuffers = window_size_dependent_setup(&images, render_pass.clone(), &mut viewport);
|
||||||
|
|
||||||
|
// Before we can start creating and recording command buffers, we need a way of allocating
|
||||||
|
// them. Vulkano provides a command buffer allocator, which manages raw Vulkan command pools
|
||||||
|
// underneath and provides a safe interface for them.
|
||||||
|
let command_buffer_allocator =
|
||||||
|
StandardCommandBufferAllocator::new(device.clone(), Default::default());
|
||||||
|
|
||||||
|
// Initialization is finally finished!
|
||||||
|
|
||||||
|
// In some situations, the swapchain will become invalid by itself. This includes for example
|
||||||
|
// when the window is resized (as the images of the swapchain will no longer match the
|
||||||
|
// window's) or, on Android, when the application went to the background and goes back to the
|
||||||
|
// foreground.
|
||||||
|
//
|
||||||
|
// In this situation, acquiring a swapchain image or presenting it will return an error.
|
||||||
|
// Rendering to an image of that swapchain will not produce any error, but may or may not work.
|
||||||
|
// To continue rendering, we need to recreate the swapchain by creating a new swapchain. Here,
|
||||||
|
// we remember that we need to do this for the next loop iteration.
|
||||||
|
let mut recreate_swapchain = false;
|
||||||
|
|
||||||
|
// In the loop below we are going to submit commands to the GPU. Submitting a command produces
|
||||||
|
// an object that implements the `GpuFuture` trait, which holds the resources for as long as
|
||||||
|
// they are in use by the GPU.
|
||||||
|
//
|
||||||
|
// Destroying the `GpuFuture` blocks until the GPU is finished executing it. In order to avoid
|
||||||
|
// that, we store the submission of the previous frame here.
|
||||||
|
let mut previous_frame_end = Some(sync::now(device.clone()).boxed());
|
||||||
|
|
||||||
|
let pipeline = shader::create_program(&render_pass, &device);
|
||||||
|
|
||||||
|
event_loop.run(move |event, _, control_flow| {
|
||||||
|
match event {
|
||||||
|
Event::WindowEvent {
|
||||||
|
event: WindowEvent::CloseRequested,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
*control_flow = ControlFlow::Exit;
|
||||||
|
}
|
||||||
|
Event::WindowEvent {
|
||||||
|
event: WindowEvent::Resized(_),
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
recreate_swapchain = true;
|
||||||
|
}
|
||||||
|
Event::RedrawEventsCleared => {
|
||||||
|
// Do not draw the frame when the screen dimensions are zero. On Windows, this can
|
||||||
|
// occur when minimizing the application.
|
||||||
|
let window = surface.object().unwrap().downcast_ref::<Window>().unwrap();
|
||||||
|
let dimensions = window.inner_size();
|
||||||
|
if dimensions.width == 0 || dimensions.height == 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// It is important to call this function from time to time, otherwise resources
|
||||||
|
// will keep accumulating and you will eventually reach an out of memory error.
|
||||||
|
// Calling this function polls various fences in order to determine what the GPU
|
||||||
|
// has already processed, and frees the resources that are no longer needed.
|
||||||
|
previous_frame_end.as_mut().unwrap().cleanup_finished();
|
||||||
|
|
||||||
|
// Whenever the window resizes we need to recreate everything dependent on the
|
||||||
|
// window size. In this example that includes the swapchain, the framebuffers and
|
||||||
|
// the dynamic state viewport.
|
||||||
|
if recreate_swapchain {
|
||||||
|
// Use the new dimensions of the window.
|
||||||
|
|
||||||
|
let (new_swapchain, new_images) =
|
||||||
|
match swapchain.recreate(SwapchainCreateInfo {
|
||||||
|
image_extent: dimensions.into(),
|
||||||
|
..swapchain.create_info()
|
||||||
|
}) {
|
||||||
|
Ok(r) => r,
|
||||||
|
// This error tends to happen when the user is manually resizing the
|
||||||
|
// window. Simply restarting the loop is the easiest way to fix this
|
||||||
|
// issue.
|
||||||
|
Err(SwapchainCreationError::ImageExtentNotSupported { .. }) => return,
|
||||||
|
Err(e) => panic!("failed to recreate swapchain: {e}"),
|
||||||
|
};
|
||||||
|
|
||||||
|
swapchain = new_swapchain;
|
||||||
|
|
||||||
|
// Because framebuffers contains a reference to the old swapchain, we need to
|
||||||
|
// recreate framebuffers as well.
|
||||||
|
framebuffers = window_size_dependent_setup(
|
||||||
|
&new_images,
|
||||||
|
render_pass.clone(),
|
||||||
|
&mut viewport,
|
||||||
|
);
|
||||||
|
|
||||||
|
recreate_swapchain = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Before we can draw on the output, we have to *acquire* an image from the
|
||||||
|
// swapchain. If no image is available (which happens if you submit draw commands
|
||||||
|
// too quickly), then the function will block. This operation returns the index of
|
||||||
|
// the image that we are allowed to draw upon.
|
||||||
|
//
|
||||||
|
// This function can block if no image is available. The parameter is an optional
|
||||||
|
// timeout after which the function call will return an error.
|
||||||
|
let (image_index, suboptimal, acquire_future) =
|
||||||
|
match acquire_next_image(swapchain.clone(), None) {
|
||||||
|
Ok(r) => r,
|
||||||
|
Err(AcquireError::OutOfDate) => {
|
||||||
|
recreate_swapchain = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Err(e) => panic!("failed to acquire next image: {e}"),
|
||||||
|
};
|
||||||
|
|
||||||
|
// `acquire_next_image` can be successful, but suboptimal. This means that the
|
||||||
|
// swapchain image will still work, but it may not display correctly. With some
|
||||||
|
// drivers this can be when the window resizes, but it may not cause the swapchain
|
||||||
|
// to become out of date.
|
||||||
|
if suboptimal {
|
||||||
|
recreate_swapchain = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// In order to draw, we have to build a *command buffer*. The command buffer object
|
||||||
|
// holds the list of commands that are going to be executed.
|
||||||
|
//
|
||||||
|
// Building a command buffer is an expensive operation (usually a few hundred
|
||||||
|
// microseconds), but it is known to be a hot path in the driver and is expected to
|
||||||
|
// be optimized.
|
||||||
|
//
|
||||||
|
// Note that we have to pass a queue family when we create the command buffer. The
|
||||||
|
// command buffer will only be executable on that given queue family.
|
||||||
|
let mut builder = AutoCommandBufferBuilder::primary(
|
||||||
|
&command_buffer_allocator,
|
||||||
|
queue.queue_family_index(),
|
||||||
|
CommandBufferUsage::OneTimeSubmit,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
builder
|
||||||
|
// Before we can draw, we have to *enter a render pass*.
|
||||||
|
.begin_render_pass(
|
||||||
|
RenderPassBeginInfo {
|
||||||
|
// A list of values to clear the attachments with. This list contains
|
||||||
|
// one item for each attachment in the render pass. In this case, there
|
||||||
|
// is only one attachment, and we clear it with a blue color.
|
||||||
|
//
|
||||||
|
// Only attachments that have `LoadOp::Clear` are provided with clear
|
||||||
|
// values, any others should use `ClearValue::None` as the clear value.
|
||||||
|
clear_values: vec![Some([0.0, 0.0, 1.0, 1.0].into())],
|
||||||
|
|
||||||
|
..RenderPassBeginInfo::framebuffer(
|
||||||
|
framebuffers[image_index as usize].clone(),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
// The contents of the first (and only) subpass. This can be either
|
||||||
|
// `Inline` or `SecondaryCommandBuffers`. The latter is a bit more advanced
|
||||||
|
// and is not covered here.
|
||||||
|
SubpassContents::Inline,
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
// We are now inside the first subpass of the render pass.
|
||||||
|
.set_viewport(0, [viewport.clone()])
|
||||||
|
.bind_pipeline_graphics(pipeline.clone())
|
||||||
|
.bind_vertex_buffers(0, vertex_buffer.clone())
|
||||||
|
// We add a draw command.
|
||||||
|
.draw(vertex_buffer.len() as u32, 1, 0, 0)
|
||||||
|
.unwrap()
|
||||||
|
// We leave the render pass. Note that if we had multiple subpasses we could
|
||||||
|
// have called `next_subpass` to jump to the next subpass.
|
||||||
|
.end_render_pass()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Finish building the command buffer by calling `build`.
|
||||||
|
let command_buffer = builder.build().unwrap();
|
||||||
|
|
||||||
|
let future = previous_frame_end
|
||||||
|
.take()
|
||||||
|
.unwrap()
|
||||||
|
.join(acquire_future)
|
||||||
|
.then_execute(queue.clone(), command_buffer)
|
||||||
|
.unwrap()
|
||||||
|
// The color output is now expected to contain our triangle. But in order to
|
||||||
|
// show it on the screen, we have to *present* the image by calling
|
||||||
|
// `then_swapchain_present`.
|
||||||
|
//
|
||||||
|
// This function does not actually present the image immediately. Instead it
|
||||||
|
// submits a present command at the end of the queue. This means that it will
|
||||||
|
// only be presented once the GPU has finished executing the command buffer
|
||||||
|
// that draws the triangle.
|
||||||
|
.then_swapchain_present(
|
||||||
|
queue.clone(),
|
||||||
|
SwapchainPresentInfo::swapchain_image_index(swapchain.clone(), image_index),
|
||||||
|
)
|
||||||
|
.then_signal_fence_and_flush();
|
||||||
|
|
||||||
|
match future {
|
||||||
|
Ok(future) => {
|
||||||
|
previous_frame_end = Some(future.boxed());
|
||||||
|
}
|
||||||
|
Err(FlushError::OutOfDate) => {
|
||||||
|
recreate_swapchain = true;
|
||||||
|
previous_frame_end = Some(sync::now(device.clone()).boxed());
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
panic!("failed to flush future: {e}");
|
||||||
|
// previous_frame_end = Some(sync::now(device.clone()).boxed());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This function is called once during initialization, then again whenever the window is resized.
|
||||||
|
fn window_size_dependent_setup(
|
||||||
|
images: &[Arc<SwapchainImage>],
|
||||||
|
render_pass: Arc<RenderPass>,
|
||||||
|
viewport: &mut Viewport,
|
||||||
|
) -> Vec<Arc<Framebuffer>> {
|
||||||
|
let dimensions = images[0].dimensions().width_height();
|
||||||
|
viewport.dimensions = [dimensions[0] as f32, dimensions[1] as f32];
|
||||||
|
|
||||||
|
images
|
||||||
|
.iter()
|
||||||
|
.map(|image| {
|
||||||
|
let view = ImageView::new_default(image.clone()).unwrap();
|
||||||
|
Framebuffer::new(
|
||||||
|
render_pass.clone(),
|
||||||
|
FramebufferCreateInfo {
|
||||||
|
attachments: vec![view],
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_device(instance: &Arc<Instance>, surface: &Arc<Surface>) -> (Arc<Device>, impl ExactSizeIterator<Item=Arc<Queue>> + Sized) {
|
||||||
// Choose device extensions that we're going to use. In order to present images to a surface,
|
// Choose device extensions that we're going to use. In order to present images to a surface,
|
||||||
// we need a `Swapchain`, which is provided by the `khr_swapchain` extension.
|
// we need a `Swapchain`, which is provided by the `khr_swapchain` extension.
|
||||||
let device_extensions = DeviceExtensions {
|
let device_extensions = DeviceExtensions {
|
||||||
|
@ -45,9 +370,75 @@ pub fn init() {
|
||||||
..DeviceExtensions::empty()
|
..DeviceExtensions::empty()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let (physical_device, queue_family_index) = choose_physical_device(&instance, &surface, device_extensions);
|
||||||
|
|
||||||
|
print_physical_device_info(physical_device.properties());
|
||||||
|
|
||||||
|
// Now initializing the device. This is probably the most important object of Vulkan.
|
||||||
|
//
|
||||||
|
// An iterator of created queues is returned by the function alongside the device.
|
||||||
|
Device::new(
|
||||||
|
// Which physical device to connect to.
|
||||||
|
physical_device,
|
||||||
|
DeviceCreateInfo {
|
||||||
|
// A list of optional features and extensions that our program needs to work correctly.
|
||||||
|
// Some parts of the Vulkan specs are optional and must be enabled manually at device
|
||||||
|
// creation. In this example the only thing we are going to need is the `khr_swapchain`
|
||||||
|
// extension that allows us to draw to a window.
|
||||||
|
enabled_extensions: device_extensions,
|
||||||
|
|
||||||
|
// The list of queues that we are going to use. Here we only use one queue, from the
|
||||||
|
// previously chosen queue family.
|
||||||
|
queue_create_infos: vec![QueueCreateInfo {
|
||||||
|
queue_family_index,
|
||||||
|
..Default::default()
|
||||||
|
}],
|
||||||
|
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_render_pass(device: &Arc<Device>, swapchain: &Arc<Swapchain>) -> Arc<RenderPass> {
|
||||||
|
// 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.
|
||||||
|
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: <ty>` 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: swapchain.image_format(),
|
||||||
|
// `samples: 1` means that we ask the GPU to use one sample to determine the value
|
||||||
|
// of each pixel in the color attachment. We could use a larger value
|
||||||
|
// (multisampling) for antialiasing. An example of this can be found in
|
||||||
|
// msaa-renderpass.rs.
|
||||||
|
samples: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn choose_physical_device(instance: &Arc<Instance>, surface: &Arc<Surface>, device_extensions: DeviceExtensions) -> (Arc<PhysicalDevice>, u32) {
|
||||||
// We then choose which physical device to use. First, we enumerate all the available physical
|
// We then choose which physical device to use. First, we enumerate all the available physical
|
||||||
// devices, then apply filters to narrow them down to those that can support our needs.
|
// devices, then apply filters to narrow them down to those that can support our needs.
|
||||||
let (physical_device, queue_family_index) = instance
|
instance
|
||||||
.enumerate_physical_devices()
|
.enumerate_physical_devices()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.filter(|p| {
|
.filter(|p| {
|
||||||
|
@ -102,9 +493,67 @@ pub fn init() {
|
||||||
_ => 5,
|
_ => 5,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.expect("no suitable physical device found");
|
.expect("no suitable physical device found")
|
||||||
|
}
|
||||||
|
|
||||||
print_physical_device_info(physical_device.properties());
|
fn create_swapchain(device: &Arc<Device>, surface: &Arc<Surface>) -> (Arc<Swapchain>, Vec<Arc<SwapchainImage>>) {
|
||||||
|
// Before we can draw on the surface, we have to create what is called a swapchain. Creating a
|
||||||
|
// swapchain allocates the color buffers that will contain the image that will ultimately be
|
||||||
|
// visible on the screen. These images are returned alongside the swapchain.
|
||||||
|
// Querying the capabilities of the surface. When we create the swapchain we can only pass
|
||||||
|
// values that are allowed by the capabilities.
|
||||||
|
let surface_capabilities = device
|
||||||
|
.physical_device()
|
||||||
|
.surface_capabilities(&surface, Default::default())
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Choosing the internal format that the images will have.
|
||||||
|
let image_format = Some(
|
||||||
|
device
|
||||||
|
.physical_device()
|
||||||
|
.surface_formats(&surface, Default::default())
|
||||||
|
.unwrap()[0]
|
||||||
|
.0,
|
||||||
|
);
|
||||||
|
let window = surface.object().unwrap().downcast_ref::<Window>().unwrap();
|
||||||
|
|
||||||
|
// Please take a look at the docs for the meaning of the parameters we didn't mention.
|
||||||
|
Swapchain::new(
|
||||||
|
device.clone(),
|
||||||
|
surface.clone(),
|
||||||
|
SwapchainCreateInfo {
|
||||||
|
min_image_count: surface_capabilities.min_image_count,
|
||||||
|
|
||||||
|
image_format,
|
||||||
|
|
||||||
|
// The dimensions of the window, only used to initially setup the swapchain.
|
||||||
|
//
|
||||||
|
// NOTE:
|
||||||
|
// On some drivers the swapchain dimensions are specified by
|
||||||
|
// `surface_capabilities.current_extent` and the swapchain size must use these
|
||||||
|
// dimensions. These dimensions are always the same as the window dimensions.
|
||||||
|
//
|
||||||
|
// However, other drivers don't specify a value, i.e.
|
||||||
|
// `surface_capabilities.current_extent` is `None`. These drivers will allow
|
||||||
|
// anything, but the only sensible value is the window dimensions.
|
||||||
|
//
|
||||||
|
// Both of these cases need the swapchain to use the window dimensions, so we just
|
||||||
|
// use that.
|
||||||
|
image_extent: window.inner_size().into(),
|
||||||
|
|
||||||
|
image_usage: ImageUsage::COLOR_ATTACHMENT,
|
||||||
|
|
||||||
|
// The alpha mode indicates how the alpha value of the final image will behave. For
|
||||||
|
// example, you can choose whether the window will be opaque or transparent.
|
||||||
|
composite_alpha: surface_capabilities
|
||||||
|
.supported_composite_alpha
|
||||||
|
.into_iter()
|
||||||
|
.next()
|
||||||
|
.unwrap(),
|
||||||
|
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print_physical_device_info(device_properties: &Properties) {
|
fn print_physical_device_info(device_properties: &Properties) {
|
||||||
|
@ -120,15 +569,5 @@ fn print_device_driver_info(device_properties: &Properties) {
|
||||||
let default_info = String::from("<none>");
|
let default_info = String::from("<none>");
|
||||||
let info = device_properties.driver_info.as_ref().unwrap_or(&default_info);
|
let info = device_properties.driver_info.as_ref().unwrap_or(&default_info);
|
||||||
|
|
||||||
format!("driver:\n\tname: {}\n\tversion: {}\n\t info: {}\n", name, device_properties.driver_version, info);
|
println!("driver:\n\tname: {}\n\tversion: {}\n\tinfo: {}\n", name, device_properties.driver_version, info);
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn it_works() {
|
|
||||||
init();
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
use eruption::init;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
init();
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
pub(crate) mod vs {
|
||||||
|
vulkano_shaders::shader! {
|
||||||
|
ty: "vertex",
|
||||||
|
src: r"
|
||||||
|
#version 450
|
||||||
|
layout(location = 0) in vec2 position;
|
||||||
|
void main() {
|
||||||
|
gl_Position = vec4(position, 0.0, 1.0);
|
||||||
|
}
|
||||||
|
",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) mod fs {
|
||||||
|
vulkano_shaders::shader! {
|
||||||
|
ty: "fragment",
|
||||||
|
src: r"
|
||||||
|
#version 450
|
||||||
|
layout(location = 0) out vec4 f_color;
|
||||||
|
void main() {
|
||||||
|
f_color = vec4(1.0, 0.0, 0.0, 1.0);
|
||||||
|
}
|
||||||
|
",
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
mod composit;
|
||||||
|
|
||||||
|
use std::sync::Arc;
|
||||||
|
use vulkano::device::Device;
|
||||||
|
use vulkano::pipeline::graphics::input_assembly::InputAssemblyState;
|
||||||
|
use vulkano::pipeline::graphics::vertex_input::Vertex;
|
||||||
|
use vulkano::pipeline::graphics::viewport::ViewportState;
|
||||||
|
use vulkano::pipeline::GraphicsPipeline;
|
||||||
|
use vulkano::render_pass::{RenderPass, Subpass};
|
||||||
|
use crate::Vertex2d;
|
||||||
|
|
||||||
|
pub fn create_program(render_pass: &Arc<RenderPass>, device: &Arc<Device>) -> Arc<GraphicsPipeline> {
|
||||||
|
|
||||||
|
let vs = composit::vs::load(device.clone()).unwrap();
|
||||||
|
let fs = composit::fs::load(device.clone()).unwrap();
|
||||||
|
|
||||||
|
// Before we draw we have to create what is called a pipeline. This is similar to an OpenGL
|
||||||
|
// program, but much more specific.
|
||||||
|
GraphicsPipeline::start()
|
||||||
|
// 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())
|
||||||
|
// We need to indicate the layout of the vertices.
|
||||||
|
.vertex_input_state(Vertex2d::per_vertex())
|
||||||
|
// The content of the vertex buffer describes a list of triangles.
|
||||||
|
.input_assembly_state(InputAssemblyState::new())
|
||||||
|
// A Vulkan shader can in theory contain multiple entry points, so we have to specify
|
||||||
|
// which one.
|
||||||
|
.vertex_shader(vs.entry_point("main").unwrap(), ())
|
||||||
|
// Use a resizable viewport set to draw over the entire window
|
||||||
|
.viewport_state(ViewportState::viewport_dynamic_scissor_irrelevant())
|
||||||
|
// See `vertex_shader`.
|
||||||
|
.fragment_shader(fs.entry_point("main").unwrap(), ())
|
||||||
|
// Now that our builder is filled, we call `build()` to obtain an actual pipeline.
|
||||||
|
.build(device.clone())
|
||||||
|
.unwrap()
|
||||||
|
}
|
Reference in New Issue