diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..13566b8
--- /dev/null
+++ b/.idea/.gitignore
@@ -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
diff --git a/.idea/eruption.iml b/.idea/eruption.iml
new file mode 100644
index 0000000..9b4cf84
--- /dev/null
+++ b/.idea/eruption.iml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..639900d
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..3df7df4
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..35eb1dd
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Cargo.lock b/Cargo.lock
index a6d5f85..10a594b 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -181,6 +181,15 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e"
+[[package]]
+name = "cmake"
+version = "0.1.50"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a31c789563b815f77f4250caee12365734369f942439b7defd71e18a48197130"
+dependencies = [
+ "cc",
+]
+
[[package]]
name = "core-foundation"
version = "0.9.3"
@@ -282,6 +291,7 @@ name = "eruption"
version = "0.1.0"
dependencies = [
"vulkano",
+ "vulkano-shaders",
"vulkano-win",
"winit",
]
@@ -760,6 +770,15 @@ version = "0.6.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
+[[package]]
+name = "roxmltree"
+version = "0.14.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "921904a62e410e37e215c40381b7117f830d9d89ba60ab5236170541dd25646b"
+dependencies = [
+ "xmlparser",
+]
+
[[package]]
name = "ryu"
version = "1.0.13"
@@ -822,6 +841,27 @@ dependencies = [
"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]]
name = "slotmap"
version = "1.0.6"
@@ -1030,6 +1070,21 @@ dependencies = [
"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]]
name = "vulkano-win"
version = "0.33.0"
@@ -1343,3 +1398,9 @@ name = "xml-rs"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3"
+
+[[package]]
+name = "xmlparser"
+version = "0.13.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4d25c75bf9ea12c4040a97f829154768bbbce366287e2dc044af160cd79a13fd"
diff --git a/Cargo.toml b/Cargo.toml
index dae3cb3..a26d022 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -9,4 +9,5 @@ authors = ["Sven Vogel"]
[dependencies]
vulkano = "0.33.0"
vulkano-win = "0.33.0"
+vulkano-shaders = "0.33.0"
winit = "0.28.3"
\ No newline at end of file
diff --git a/src/lib.rs b/src/lib.rs
index 1bfddf8..470cff1 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,10 +1,57 @@
-use vulkano::device::{DeviceExtensions, Properties, QueueFlags};
-use vulkano::device::physical::PhysicalDeviceType;
+mod shader;
+
+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::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 winit::event_loop::EventLoop;
-use winit::window::WindowBuilder;
+use winit::event_loop::{ControlFlow, EventLoop};
+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() {
let lib = VulkanLibrary::new().unwrap();
@@ -38,6 +85,284 @@ pub fn init() {
.build_vk_surface(&event_loop, instance.clone())
.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::().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],
+ render_pass: Arc,
+ viewport: &mut Viewport,
+) -> Vec> {
+ 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::>()
+}
+
+fn get_device(instance: &Arc, surface: &Arc) -> (Arc, impl ExactSizeIterator- > + Sized) {
// 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.
let device_extensions = DeviceExtensions {
@@ -45,9 +370,75 @@ pub fn init() {
..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, swapchain: &Arc) -> Arc {
+ // 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: ` 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, surface: &Arc, device_extensions: DeviceExtensions) -> (Arc, u32) {
// 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.
- let (physical_device, queue_family_index) = instance
+ instance
.enumerate_physical_devices()
.unwrap()
.filter(|p| {
@@ -102,9 +493,67 @@ pub fn init() {
_ => 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, surface: &Arc) -> (Arc, Vec>) {
+ // 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::().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) {
@@ -120,15 +569,5 @@ fn print_device_driver_info(device_properties: &Properties) {
let default_info = String::from("");
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);
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- #[test]
- fn it_works() {
- init();
- }
-}
+ println!("driver:\n\tname: {}\n\tversion: {}\n\tinfo: {}\n", name, device_properties.driver_version, info);
+}
\ No newline at end of file
diff --git a/src/main.rs b/src/main.rs
new file mode 100644
index 0000000..18c3665
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,7 @@
+use eruption::init;
+
+fn main() {
+ init();
+
+
+}
\ No newline at end of file
diff --git a/src/shader/composit.rs b/src/shader/composit.rs
new file mode 100644
index 0000000..6b34326
--- /dev/null
+++ b/src/shader/composit.rs
@@ -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);
+ }
+ ",
+ }
+}
\ No newline at end of file
diff --git a/src/shader/mod.rs b/src/shader/mod.rs
new file mode 100644
index 0000000..b6c03fc
--- /dev/null
+++ b/src/shader/mod.rs
@@ -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, device: &Arc) -> Arc {
+
+ 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()
+}
\ No newline at end of file