pre vertex buffer
This commit is contained in:
commit
017cfcf82f
14 changed files with 2856 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
/target
|
48
.vscode/launch.json
vendored
Normal file
48
.vscode/launch.json
vendored
Normal file
|
@ -0,0 +1,48 @@
|
|||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"name": "Debug executable 'vulkan-tutorial'",
|
||||
"cargo": {
|
||||
"args": [
|
||||
"build",
|
||||
"--bin=vulkan-tutorial",
|
||||
"--package=vulkan-tutorial"
|
||||
],
|
||||
"filter": {
|
||||
"name": "vulkan-tutorial",
|
||||
"kind": "bin"
|
||||
}
|
||||
},
|
||||
"env": {
|
||||
"RUST_LOG": "info",
|
||||
},
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}"
|
||||
},
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"name": "Debug unit tests in executable 'vulkan-tutorial'",
|
||||
"cargo": {
|
||||
"args": [
|
||||
"test",
|
||||
"--no-run",
|
||||
"--bin=vulkan-tutorial",
|
||||
"--package=vulkan-tutorial"
|
||||
],
|
||||
"filter": {
|
||||
"name": "vulkan-tutorial",
|
||||
"kind": "bin"
|
||||
}
|
||||
},
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}"
|
||||
}
|
||||
]
|
||||
}
|
1785
Cargo.lock
generated
Normal file
1785
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
17
Cargo.toml
Normal file
17
Cargo.toml
Normal file
|
@ -0,0 +1,17 @@
|
|||
[package]
|
||||
name = "vulkan-tutorial"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1"
|
||||
log = "0.4"
|
||||
cgmath = "0.18"
|
||||
png = "0.17"
|
||||
pretty_env_logger = "0.5"
|
||||
thiserror = "1"
|
||||
tobj = { version = "3", features = ["log"] }
|
||||
vulkanalia = { version = "=0.23.0", features = ["libloading", "provisional", "window"] }
|
||||
winit = "0.29"
|
3
shaders/compile.bat
Normal file
3
shaders/compile.bat
Normal file
|
@ -0,0 +1,3 @@
|
|||
C:/VulkanSDK/1.3.280.0/Bin/glslc.exe shaders/shader.vert -o shaders/vert.spv
|
||||
C:/VulkanSDK/1.3.280.0/Bin/glslc.exe shaders/shader.frag -o shaders/frag.spv
|
||||
pause
|
BIN
shaders/frag.spv
Normal file
BIN
shaders/frag.spv
Normal file
Binary file not shown.
9
shaders/shader.frag
Normal file
9
shaders/shader.frag
Normal file
|
@ -0,0 +1,9 @@
|
|||
#version 450
|
||||
|
||||
layout(location = 0) in vec3 fragColor;
|
||||
|
||||
layout(location = 0) out vec4 outColor;
|
||||
|
||||
void main() {
|
||||
outColor = vec4(fragColor, 1.0);
|
||||
}
|
20
shaders/shader.vert
Normal file
20
shaders/shader.vert
Normal file
|
@ -0,0 +1,20 @@
|
|||
#version 450
|
||||
|
||||
vec2 positions[3] = vec2[](
|
||||
vec2(0.0, -0.5),
|
||||
vec2(0.5, 0.5),
|
||||
vec2(-0.5, 0.5)
|
||||
);
|
||||
|
||||
vec3 colors[3] = vec3[](
|
||||
vec3(1.0, 0.0, 0.0),
|
||||
vec3(0.0, 1.0, 0.0),
|
||||
vec3(0.0, 0.0, 1.0)
|
||||
);
|
||||
|
||||
layout(location = 0) out vec3 fragColor;
|
||||
|
||||
void main() {
|
||||
gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0);
|
||||
fragColor = colors[gl_VertexIndex];
|
||||
}
|
BIN
shaders/vert.spv
Normal file
BIN
shaders/vert.spv
Normal file
Binary file not shown.
30
src/app_data.rs
Normal file
30
src/app_data.rs
Normal file
|
@ -0,0 +1,30 @@
|
|||
use vulkanalia::prelude::v1_0::*;
|
||||
// The Vulkan handles and associated properties used by our Vulkan app.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct AppData {
|
||||
pub surface: vk::SurfaceKHR,
|
||||
pub messenger: vk::DebugUtilsMessengerEXT,
|
||||
pub physical_device: vk::PhysicalDevice,
|
||||
pub graphics_queue: vk::Queue,
|
||||
pub present_queue: vk::Queue,
|
||||
|
||||
pub swapchain_format: vk::Format,
|
||||
pub swapchain_extent: vk::Extent2D,
|
||||
pub swapchain: vk::SwapchainKHR,
|
||||
pub swapchain_images: Vec<vk::Image>,
|
||||
|
||||
pub swapchain_image_views: Vec<vk::ImageView>,
|
||||
|
||||
pub pipeline_layout: vk::PipelineLayout,
|
||||
pub render_pass: vk::RenderPass,
|
||||
pub pipeline: vk::Pipeline,
|
||||
pub framebuffers: Vec<vk::Framebuffer>,
|
||||
pub command_pool: vk::CommandPool,
|
||||
pub command_buffers: Vec<vk::CommandBuffer>,
|
||||
|
||||
pub image_available_semaphores: Vec<vk::Semaphore>,
|
||||
pub render_finished_semaphores: Vec<vk::Semaphore>,
|
||||
|
||||
pub in_flight_fences: Vec<vk::Fence>,
|
||||
pub images_in_flight: Vec<vk::Fence>,
|
||||
}
|
5
src/errors.rs
Normal file
5
src/errors.rs
Normal file
|
@ -0,0 +1,5 @@
|
|||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
#[error("Missing {0}.")]
|
||||
pub struct SuitabilityError(pub &'static str);
|
723
src/main.rs
Normal file
723
src/main.rs
Normal file
|
@ -0,0 +1,723 @@
|
|||
#![allow(
|
||||
dead_code,
|
||||
unused_variables,
|
||||
clippy::too_many_arguments,
|
||||
clippy::unnecessary_wraps
|
||||
)]
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use log::*;
|
||||
use winit::dpi::LogicalSize;
|
||||
use winit::event::{Event, WindowEvent};
|
||||
use winit::event_loop::EventLoop;
|
||||
use winit::window::{Window, WindowBuilder};
|
||||
|
||||
use vulkanalia::loader::{LibloadingLoader, LIBRARY};
|
||||
use vulkanalia::window as vk_window;
|
||||
use vulkanalia::prelude::v1_0::*;
|
||||
use vulkanalia::Version;
|
||||
use vulkanalia::bytecode::Bytecode;
|
||||
|
||||
use std::collections::HashSet;
|
||||
use std::ffi::CStr;
|
||||
use std::os::raw::c_void;
|
||||
|
||||
// extension imports
|
||||
use vulkanalia::vk::ExtDebugUtilsExtension;
|
||||
use vulkanalia::vk::KhrSurfaceExtension;
|
||||
use vulkanalia::vk::KhrSwapchainExtension;
|
||||
|
||||
pub mod app_data;
|
||||
pub mod errors;
|
||||
pub mod swapchain;
|
||||
pub mod queue_family_indices;
|
||||
|
||||
const PORTABILITY_MACOS_VERSION: Version = Version::new(1, 3, 216);
|
||||
const VALIDATION_ENABLED: bool =
|
||||
cfg!(debug_assertions);
|
||||
|
||||
const VALIDATION_LAYER: vk::ExtensionName =
|
||||
vk::ExtensionName::from_bytes(b"VK_LAYER_KHRONOS_validation");
|
||||
const DEVICE_EXTENSIONS: &[vk::ExtensionName] = &[vk::KHR_SWAPCHAIN_EXTENSION.name];
|
||||
|
||||
const MAX_FRAMES_IN_FLIGHT: usize = 2;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
pretty_env_logger::init();
|
||||
|
||||
// Window
|
||||
|
||||
let event_loop = EventLoop::new()?;
|
||||
let window = WindowBuilder::new()
|
||||
.with_title("Vulkan Tutorial (Rust)")
|
||||
.with_inner_size(LogicalSize::new(1024, 768))
|
||||
.build(&event_loop)?;
|
||||
|
||||
// App
|
||||
|
||||
let mut app = unsafe { App::create(&window)? };
|
||||
event_loop.run(move |event, elwt| {
|
||||
match event {
|
||||
// Request a redraw when all events were processed.
|
||||
Event::AboutToWait => window.request_redraw(),
|
||||
Event::WindowEvent { event, .. } => match event {
|
||||
// Render a frame if our Vulkan app is not being destroyed.
|
||||
WindowEvent::RedrawRequested if !elwt.exiting() && !app.minimized => unsafe { app.render(&window) }.unwrap(),
|
||||
// Destroy our Vulkan app.
|
||||
WindowEvent::CloseRequested => {
|
||||
elwt.exit();
|
||||
unsafe { app.device.device_wait_idle().unwrap(); }
|
||||
unsafe { app.destroy(); }
|
||||
},
|
||||
WindowEvent::Resized(size) => {
|
||||
if size.width == 0 || size.height == 0 {
|
||||
app.minimized = true;
|
||||
} else {
|
||||
app.minimized = false;
|
||||
app.resized = true;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Our Vulkan app.
|
||||
#[derive(Clone, Debug)]
|
||||
struct App {
|
||||
entry: Entry,
|
||||
instance: Instance,
|
||||
data: app_data::AppData,
|
||||
device: Device,
|
||||
frame: usize,
|
||||
resized: bool,
|
||||
minimized: bool,
|
||||
}
|
||||
|
||||
impl App {
|
||||
/// Creates our Vulkan app.
|
||||
unsafe fn create(window: &Window) -> Result<Self> {
|
||||
let loader = LibloadingLoader::new(LIBRARY)?;
|
||||
let entry = Entry::new(loader).map_err(|b| anyhow!("{}", b))?;
|
||||
let mut data = app_data::AppData::default();
|
||||
let instance = create_instance(window, &entry, &mut data)?;
|
||||
data.surface = vk_window::create_surface(&instance, &window, &window)?;
|
||||
pick_physical_device(&instance, &mut data)?;
|
||||
let device = create_logical_device(&entry, &instance, &mut data)?;
|
||||
swapchain::create_swapchain(window, &instance, &device, &mut data)?;
|
||||
swapchain::create_swapchain_image_views(&device, &mut data)?;
|
||||
create_render_pass(&instance, &device, &mut data)?;
|
||||
|
||||
create_pipeline(&device, &mut data)?;
|
||||
|
||||
create_framebuffers(&device, &mut data)?;
|
||||
|
||||
create_command_pool(&instance, &device, &mut data)?;
|
||||
|
||||
create_command_buffers(&device, &mut data)?;
|
||||
|
||||
create_sync_objects(&device, &mut data)?;
|
||||
|
||||
Ok(Self { entry, instance, data, device, frame: 0 , resized: false, minimized: false})
|
||||
}
|
||||
|
||||
/// Renders a frame for our Vulkan app.
|
||||
unsafe fn render(&mut self, window: &Window) -> Result<()> {
|
||||
let in_flight_fence = self.data.in_flight_fences[self.frame];
|
||||
|
||||
self.device.wait_for_fences(&[in_flight_fence], true, u64::MAX)?;
|
||||
|
||||
let result = self.device.acquire_next_image_khr(
|
||||
self.data.swapchain,
|
||||
u64::MAX,
|
||||
self.data.image_available_semaphores[self.frame],
|
||||
vk::Fence::null(),
|
||||
);
|
||||
|
||||
let image_index = match result {
|
||||
Ok((image_index, _)) => image_index as usize,
|
||||
Err(vk::ErrorCode::OUT_OF_DATE_KHR) => return self.recreate_swapchain(window),
|
||||
Err(e) => return Err(anyhow!(e)),
|
||||
};
|
||||
|
||||
let image_in_flight = self.data.images_in_flight[image_index];
|
||||
if !image_in_flight.is_null() {
|
||||
self.device.wait_for_fences(&[image_in_flight], true, u64::MAX)?;
|
||||
}
|
||||
|
||||
self.data.images_in_flight[image_index] = in_flight_fence;
|
||||
|
||||
let wait_semaphores = &[self.data.image_available_semaphores[self.frame]];
|
||||
let wait_stages = &[vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT];
|
||||
let command_buffers = &[self.data.command_buffers[image_index]];
|
||||
let signal_semaphores = &[self.data.render_finished_semaphores[self.frame]];
|
||||
let submit_info = vk::SubmitInfo::builder()
|
||||
.wait_semaphores(wait_semaphores)
|
||||
.wait_dst_stage_mask(wait_stages)
|
||||
.command_buffers(command_buffers)
|
||||
.signal_semaphores(signal_semaphores);
|
||||
|
||||
self.device.reset_fences(&[in_flight_fence])?;
|
||||
|
||||
self.device
|
||||
.queue_submit(self.data.graphics_queue, &[submit_info], in_flight_fence)?;
|
||||
|
||||
let swapchains = &[self.data.swapchain];
|
||||
let image_indices = &[image_index as u32];
|
||||
let present_info = vk::PresentInfoKHR::builder()
|
||||
.wait_semaphores(signal_semaphores)
|
||||
.swapchains(swapchains)
|
||||
.image_indices(image_indices);
|
||||
|
||||
let result = self.device.queue_present_khr(self.data.present_queue, &present_info);
|
||||
let changed = result == Ok(vk::SuccessCode::SUBOPTIMAL_KHR) || result == Err(vk::ErrorCode::OUT_OF_DATE_KHR);
|
||||
if self.resized || changed {
|
||||
self.resized = false;
|
||||
self.recreate_swapchain(window)?;
|
||||
} else if let Err(e) = result {
|
||||
return Err(anyhow!(e));
|
||||
}
|
||||
|
||||
self.frame = (self.frame + 1) % MAX_FRAMES_IN_FLIGHT;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Destroys our Vulkan app.
|
||||
unsafe fn destroy(&mut self) {
|
||||
self.destroy_swapchain();
|
||||
|
||||
self.data.in_flight_fences
|
||||
.iter()
|
||||
.for_each(|f| self.device.destroy_fence(*f, None));
|
||||
self.data.render_finished_semaphores
|
||||
.iter()
|
||||
.for_each(|s| self.device.destroy_semaphore(*s, None));
|
||||
self.data.image_available_semaphores
|
||||
.iter()
|
||||
.for_each(|s| self.device.destroy_semaphore(*s, None));
|
||||
self.device.destroy_command_pool(self.data.command_pool, None);
|
||||
self.device.destroy_device(None);
|
||||
self.instance.destroy_surface_khr(self.data.surface, None);
|
||||
|
||||
if VALIDATION_ENABLED {
|
||||
self.instance.destroy_debug_utils_messenger_ext(self.data.messenger, None);
|
||||
}
|
||||
|
||||
self.instance.destroy_instance(None);
|
||||
}
|
||||
|
||||
unsafe fn recreate_swapchain(&mut self, window: &Window) -> Result<()> {
|
||||
self.device.device_wait_idle()?;
|
||||
self.destroy_swapchain();
|
||||
swapchain::create_swapchain(window, &self.instance, &self.device, &mut self.data)?;
|
||||
swapchain::create_swapchain_image_views(&self.device, &mut self.data)?;
|
||||
create_render_pass(&self.instance, &self.device, &mut self.data)?;
|
||||
create_pipeline(&self.device, &mut self.data)?;
|
||||
create_framebuffers(&self.device, &mut self.data)?;
|
||||
create_command_buffers(&self.device, &mut self.data)?;
|
||||
self.data
|
||||
.images_in_flight
|
||||
.resize(self.data.swapchain_images.len(), vk::Fence::null());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
unsafe fn destroy_swapchain(&mut self) {
|
||||
self.data.framebuffers
|
||||
.iter()
|
||||
.for_each(|f| self.device.destroy_framebuffer(*f, None));
|
||||
self.device.free_command_buffers(self.data.command_pool, &self.data.command_buffers);
|
||||
self.device.destroy_pipeline(self.data.pipeline, None);
|
||||
self.device.destroy_pipeline_layout(self.data.pipeline_layout, None);
|
||||
self.device.destroy_render_pass(self.data.render_pass, None);
|
||||
self.data.swapchain_image_views
|
||||
.iter()
|
||||
.for_each(|v| self.device.destroy_image_view(*v, None));
|
||||
self.device.destroy_swapchain_khr(self.data.swapchain, None);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
//================================================
|
||||
// Instance
|
||||
//================================================
|
||||
|
||||
unsafe fn create_instance(window: &Window, entry: &Entry, data: &mut app_data::AppData) -> Result<Instance> {
|
||||
let application_info = vk::ApplicationInfo::builder()
|
||||
.application_name(b"Vulkan Tutorial\0")
|
||||
.application_version(vk::make_version(1, 0, 0))
|
||||
.engine_name(b"No Engine\0")
|
||||
.engine_version(vk::make_version(1, 0, 0))
|
||||
.api_version(vk::make_version(1, 0, 0));
|
||||
|
||||
// Validation layers
|
||||
|
||||
let available_layers = entry
|
||||
.enumerate_instance_layer_properties()?
|
||||
.iter()
|
||||
.map(|l| l.layer_name)
|
||||
.collect::<HashSet<_>>();
|
||||
|
||||
if VALIDATION_ENABLED && !available_layers.contains(&VALIDATION_LAYER) {
|
||||
return Err(anyhow!("Validation layer requested but not supported."));
|
||||
}
|
||||
|
||||
let layers = if VALIDATION_ENABLED {
|
||||
vec![VALIDATION_LAYER.as_ptr()]
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
|
||||
// Extension
|
||||
let mut extensions = vk_window::get_required_instance_extensions(window)
|
||||
.iter()
|
||||
.map(|e| e.as_ptr())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if VALIDATION_ENABLED {
|
||||
extensions.push(vk::EXT_DEBUG_UTILS_EXTENSION.name.as_ptr());
|
||||
}
|
||||
|
||||
// Required by Vulkan SDK on macOS since 1.3.216.
|
||||
let flags = if
|
||||
cfg!(target_os = "macos") &&
|
||||
entry.version()? >= PORTABILITY_MACOS_VERSION
|
||||
{
|
||||
info!("Enabling extensions for macOS portability.");
|
||||
extensions.push(vk::KHR_GET_PHYSICAL_DEVICE_PROPERTIES2_EXTENSION.name.as_ptr());
|
||||
extensions.push(vk::KHR_PORTABILITY_ENUMERATION_EXTENSION.name.as_ptr());
|
||||
vk::InstanceCreateFlags::ENUMERATE_PORTABILITY_KHR
|
||||
} else {
|
||||
vk::InstanceCreateFlags::empty()
|
||||
};
|
||||
|
||||
let mut info = vk::InstanceCreateInfo::builder()
|
||||
.application_info(&application_info)
|
||||
.enabled_layer_names(&layers)
|
||||
.enabled_extension_names(&extensions)
|
||||
.flags(flags);
|
||||
|
||||
let mut debug_info = vk::DebugUtilsMessengerCreateInfoEXT::builder()
|
||||
.message_severity(vk::DebugUtilsMessageSeverityFlagsEXT::all())
|
||||
.message_type(vk::DebugUtilsMessageTypeFlagsEXT::all())
|
||||
.user_callback(Some(debug_callback));
|
||||
|
||||
if VALIDATION_ENABLED {
|
||||
info = info.push_next(&mut debug_info);
|
||||
}
|
||||
|
||||
let instance = entry.create_instance(&info, None)?;
|
||||
|
||||
if VALIDATION_ENABLED {
|
||||
data.messenger = instance.create_debug_utils_messenger_ext(&debug_info, None)?;
|
||||
}
|
||||
|
||||
Ok(instance)
|
||||
}
|
||||
|
||||
extern "system" fn debug_callback(
|
||||
severity: vk::DebugUtilsMessageSeverityFlagsEXT,
|
||||
type_: vk::DebugUtilsMessageTypeFlagsEXT,
|
||||
data: *const vk::DebugUtilsMessengerCallbackDataEXT,
|
||||
_: *mut c_void,
|
||||
) -> vk::Bool32 {
|
||||
let data = unsafe { *data };
|
||||
let message = unsafe { CStr::from_ptr(data.message) }.to_string_lossy();
|
||||
|
||||
if severity >= vk::DebugUtilsMessageSeverityFlagsEXT::ERROR {
|
||||
error!("({:?}) {}", type_, message);
|
||||
} else if severity >= vk::DebugUtilsMessageSeverityFlagsEXT::WARNING {
|
||||
warn!("({:?}) {}", type_, message);
|
||||
} else if severity >= vk::DebugUtilsMessageSeverityFlagsEXT::INFO {
|
||||
debug!("({:?}) {}", type_, message);
|
||||
} else {
|
||||
trace!("({:?}) {}", type_, message);
|
||||
}
|
||||
|
||||
vk::FALSE
|
||||
}
|
||||
|
||||
unsafe fn pick_physical_device(instance: &Instance, data: &mut app_data::AppData) -> Result<()> {
|
||||
for physical_device in instance.enumerate_physical_devices()? {
|
||||
let properties = instance.get_physical_device_properties(physical_device);
|
||||
|
||||
if let Err(error) = check_physical_device(instance, data, physical_device) {
|
||||
warn!("Skipping physical device (`{}`): {}", properties.device_name, error);
|
||||
} else {
|
||||
info!("Selected physical device (`{}`).", properties.device_name);
|
||||
data.physical_device = physical_device;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
Err(anyhow!("Failed to find suitable physical device."))
|
||||
}
|
||||
|
||||
unsafe fn check_physical_device(
|
||||
instance: &Instance,
|
||||
data: &app_data::AppData,
|
||||
physical_device: vk::PhysicalDevice,
|
||||
) -> Result<()> {
|
||||
let properties = instance.get_physical_device_properties(physical_device);
|
||||
if properties.device_type != vk::PhysicalDeviceType::DISCRETE_GPU {
|
||||
return Err(anyhow!(errors::SuitabilityError("Only discrete GPUs are supported.")));
|
||||
}
|
||||
|
||||
let features = instance.get_physical_device_features(physical_device);
|
||||
if features.geometry_shader != vk::TRUE {
|
||||
return Err(anyhow!(errors::SuitabilityError("Missing geometry shader support.")));
|
||||
}
|
||||
|
||||
queue_family_indices::QueueFamilyIndices::get(instance, data, physical_device)?;
|
||||
check_physical_device_extensions(instance, physical_device)?;
|
||||
|
||||
let support = swapchain::SwapchainSupport::get(instance, data, physical_device)?;
|
||||
if support.formats.is_empty() || support.present_modes.is_empty() {
|
||||
return Err(anyhow!(errors::SuitabilityError("Insufficient swapchain support.")));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
unsafe fn check_physical_device_extensions(
|
||||
instance: &Instance,
|
||||
physical_device: vk::PhysicalDevice,
|
||||
) -> Result<()> {
|
||||
let extensions = instance
|
||||
.enumerate_device_extension_properties(physical_device, None)?
|
||||
.iter()
|
||||
.map(|e| e.extension_name)
|
||||
.collect::<HashSet<_>>();
|
||||
if DEVICE_EXTENSIONS.iter().all(|e| extensions.contains(e)) {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(anyhow!(errors::SuitabilityError("Missing required device extensions.")))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
unsafe fn create_logical_device(
|
||||
entry: &Entry,
|
||||
instance: &Instance,
|
||||
data: &mut app_data::AppData,
|
||||
) -> Result<Device> {
|
||||
let indices = queue_family_indices::QueueFamilyIndices::get(instance, data, data.physical_device)?;
|
||||
|
||||
let queue_priorities = &[1.0];
|
||||
let queue_info = vk::DeviceQueueCreateInfo::builder()
|
||||
.queue_family_index(indices.graphics)
|
||||
.queue_priorities(queue_priorities);
|
||||
|
||||
let layers = if VALIDATION_ENABLED {
|
||||
vec![VALIDATION_LAYER.as_ptr()]
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
|
||||
let mut extensions = DEVICE_EXTENSIONS
|
||||
.iter()
|
||||
.map(|n| n.as_ptr())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Required by Vulkan SDK on macOS since 1.3.216.
|
||||
if cfg!(target_os = "macos") && entry.version()? >= PORTABILITY_MACOS_VERSION {
|
||||
extensions.push(vk::KHR_PORTABILITY_SUBSET_EXTENSION.name.as_ptr());
|
||||
};
|
||||
|
||||
let features = vk::PhysicalDeviceFeatures::builder();
|
||||
|
||||
let indices = queue_family_indices::QueueFamilyIndices::get(instance, data, data.physical_device)?;
|
||||
|
||||
let mut unique_indices = HashSet::new();
|
||||
unique_indices.insert(indices.graphics);
|
||||
unique_indices.insert(indices.present);
|
||||
|
||||
let queue_priorities = &[1.0];
|
||||
let queue_infos = unique_indices
|
||||
.iter()
|
||||
.map(|i| {
|
||||
vk::DeviceQueueCreateInfo::builder()
|
||||
.queue_family_index(*i)
|
||||
.queue_priorities(queue_priorities)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
|
||||
let info = vk::DeviceCreateInfo::builder()
|
||||
.queue_create_infos(&queue_infos)
|
||||
.enabled_layer_names(&layers)
|
||||
.enabled_extension_names(&extensions)
|
||||
.enabled_features(&features);
|
||||
let device = instance.create_device(data.physical_device, &info, None)?;
|
||||
|
||||
data.graphics_queue = device.get_device_queue(indices.graphics, 0);
|
||||
data.present_queue = device.get_device_queue(indices.present, 0);
|
||||
|
||||
Ok(device)
|
||||
}
|
||||
|
||||
unsafe fn create_pipeline(device: &Device, data: &mut app_data::AppData) -> Result<()> {
|
||||
let vert = include_bytes!("../shaders/vert.spv");
|
||||
let frag = include_bytes!("../shaders/frag.spv");
|
||||
|
||||
let vert_shader_module = create_shader_module(device, &vert[..])?;
|
||||
let frag_shader_module = create_shader_module(device, &frag[..])?;
|
||||
|
||||
let vert_stage = vk::PipelineShaderStageCreateInfo::builder()
|
||||
.stage(vk::ShaderStageFlags::VERTEX)
|
||||
.module(vert_shader_module)
|
||||
.name(b"main\0");
|
||||
|
||||
let frag_stage = vk::PipelineShaderStageCreateInfo::builder()
|
||||
.stage(vk::ShaderStageFlags::FRAGMENT)
|
||||
.module(frag_shader_module)
|
||||
.name(b"main\0");
|
||||
|
||||
let vertex_input_state = vk::PipelineVertexInputStateCreateInfo::builder();
|
||||
|
||||
let input_assembly_state = vk::PipelineInputAssemblyStateCreateInfo::builder()
|
||||
.topology(vk::PrimitiveTopology::TRIANGLE_LIST)
|
||||
.primitive_restart_enable(false);
|
||||
|
||||
let viewport = vk::Viewport::builder()
|
||||
.x(0.0)
|
||||
.y(0.0)
|
||||
.width(data.swapchain_extent.width as f32)
|
||||
.height(data.swapchain_extent.height as f32)
|
||||
.min_depth(0.0)
|
||||
.max_depth(1.0);
|
||||
|
||||
let scissor = vk::Rect2D::builder()
|
||||
.offset(vk::Offset2D { x: 0, y: 0 })
|
||||
.extent(data.swapchain_extent);
|
||||
|
||||
let viewports = &[viewport];
|
||||
let scissors = &[scissor];
|
||||
let viewport_state = vk::PipelineViewportStateCreateInfo::builder()
|
||||
.viewports(viewports)
|
||||
.scissors(scissors);
|
||||
|
||||
let rasterization_state = vk::PipelineRasterizationStateCreateInfo::builder()
|
||||
.depth_clamp_enable(false)
|
||||
.rasterizer_discard_enable(false)
|
||||
.polygon_mode(vk::PolygonMode::FILL)
|
||||
.line_width(1.0)
|
||||
.cull_mode(vk::CullModeFlags::BACK)
|
||||
.front_face(vk::FrontFace::CLOCKWISE)
|
||||
.depth_bias_enable(false);
|
||||
|
||||
let multisample_state = vk::PipelineMultisampleStateCreateInfo::builder()
|
||||
.sample_shading_enable(false)
|
||||
.rasterization_samples(vk::SampleCountFlags::_1);
|
||||
|
||||
let attachment = vk::PipelineColorBlendAttachmentState::builder()
|
||||
.color_write_mask(vk::ColorComponentFlags::all())
|
||||
.blend_enable(false)
|
||||
.src_color_blend_factor(vk::BlendFactor::ONE) // Optional
|
||||
.dst_color_blend_factor(vk::BlendFactor::ZERO) // Optional
|
||||
.color_blend_op(vk::BlendOp::ADD) // Optional
|
||||
.src_alpha_blend_factor(vk::BlendFactor::ONE) // Optional
|
||||
.dst_alpha_blend_factor(vk::BlendFactor::ZERO) // Optional
|
||||
.alpha_blend_op(vk::BlendOp::ADD); // Optional
|
||||
|
||||
let attachments = &[attachment];
|
||||
let color_blend_state = vk::PipelineColorBlendStateCreateInfo::builder()
|
||||
.logic_op_enable(false)
|
||||
.logic_op(vk::LogicOp::COPY)
|
||||
.attachments(attachments)
|
||||
.blend_constants([0.0, 0.0, 0.0, 0.0]);
|
||||
|
||||
let layout_info = vk::PipelineLayoutCreateInfo::builder();
|
||||
|
||||
data.pipeline_layout = device.create_pipeline_layout(&layout_info, None)?;
|
||||
|
||||
let stages = &[vert_stage, frag_stage];
|
||||
let info = vk::GraphicsPipelineCreateInfo::builder()
|
||||
.stages(stages)
|
||||
.vertex_input_state(&vertex_input_state)
|
||||
.input_assembly_state(&input_assembly_state)
|
||||
.viewport_state(&viewport_state)
|
||||
.rasterization_state(&rasterization_state)
|
||||
.multisample_state(&multisample_state)
|
||||
.color_blend_state(&color_blend_state)
|
||||
.layout(data.pipeline_layout)
|
||||
.render_pass(data.render_pass)
|
||||
.subpass(0);
|
||||
|
||||
data.pipeline = device.create_graphics_pipelines(
|
||||
vk::PipelineCache::null(), &[info], None)?.0[0];
|
||||
|
||||
device.destroy_shader_module(vert_shader_module, None);
|
||||
device.destroy_shader_module(frag_shader_module, None);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
unsafe fn create_shader_module(
|
||||
device: &Device,
|
||||
bytecode: &[u8],
|
||||
) -> Result<vk::ShaderModule> {
|
||||
let bytecode = Bytecode::new(bytecode).unwrap();
|
||||
let info = vk::ShaderModuleCreateInfo::builder()
|
||||
.code_size(bytecode.code_size())
|
||||
.code(bytecode.code());
|
||||
|
||||
Ok(device.create_shader_module(&info, None)?)
|
||||
}
|
||||
|
||||
unsafe fn create_render_pass(
|
||||
instance: &Instance,
|
||||
device: &Device,
|
||||
data: &mut app_data::AppData,
|
||||
) -> Result<()> {
|
||||
let color_attachment = vk::AttachmentDescription::builder()
|
||||
.format(data.swapchain_format)
|
||||
.samples(vk::SampleCountFlags::_1)
|
||||
.load_op(vk::AttachmentLoadOp::CLEAR)
|
||||
.store_op(vk::AttachmentStoreOp::STORE)
|
||||
.stencil_load_op(vk::AttachmentLoadOp::DONT_CARE)
|
||||
.stencil_store_op(vk::AttachmentStoreOp::DONT_CARE)
|
||||
.initial_layout(vk::ImageLayout::UNDEFINED)
|
||||
.final_layout(vk::ImageLayout::PRESENT_SRC_KHR);
|
||||
|
||||
let color_attachment_ref = vk::AttachmentReference::builder()
|
||||
.attachment(0)
|
||||
.layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL);
|
||||
|
||||
let color_attachments = &[color_attachment_ref];
|
||||
let subpass = vk::SubpassDescription::builder()
|
||||
.pipeline_bind_point(vk::PipelineBindPoint::GRAPHICS)
|
||||
.color_attachments(color_attachments);
|
||||
|
||||
let dependency = vk::SubpassDependency::builder()
|
||||
.src_subpass(vk::SUBPASS_EXTERNAL)
|
||||
.dst_subpass(0)
|
||||
.src_stage_mask(vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT)
|
||||
.src_access_mask(vk::AccessFlags::empty())
|
||||
.dst_stage_mask(vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT)
|
||||
.dst_access_mask(vk::AccessFlags::COLOR_ATTACHMENT_WRITE);
|
||||
|
||||
let attachments = &[color_attachment];
|
||||
let subpasses = &[subpass];
|
||||
let dependencies = &[dependency];
|
||||
let info = vk::RenderPassCreateInfo::builder()
|
||||
.attachments(attachments)
|
||||
.subpasses(subpasses)
|
||||
.dependencies(dependencies);
|
||||
|
||||
data.render_pass = device.create_render_pass(&info, None)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
unsafe fn create_framebuffers(device: &Device, data: &mut app_data::AppData) -> Result<()> {
|
||||
data.framebuffers = data
|
||||
.swapchain_image_views
|
||||
.iter()
|
||||
.map(|i| {
|
||||
let attachments = &[*i];
|
||||
let create_info = vk::FramebufferCreateInfo::builder()
|
||||
.render_pass(data.render_pass)
|
||||
.attachments(attachments)
|
||||
.width(data.swapchain_extent.width)
|
||||
.height(data.swapchain_extent.height)
|
||||
.layers(1);
|
||||
|
||||
device.create_framebuffer(&create_info, None)
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
unsafe fn create_command_pool(
|
||||
instance: &Instance,
|
||||
device: &Device,
|
||||
data: &mut app_data::AppData,
|
||||
) -> Result<()> {
|
||||
let indices = queue_family_indices::QueueFamilyIndices::get(instance, data, data.physical_device)?;
|
||||
|
||||
let info = vk::CommandPoolCreateInfo::builder()
|
||||
.flags(vk::CommandPoolCreateFlags::empty()) // Optional.
|
||||
.queue_family_index(indices.graphics);
|
||||
|
||||
data.command_pool = device.create_command_pool(&info, None)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
unsafe fn create_command_buffers(device: &Device, data: &mut app_data::AppData) -> Result<()> {
|
||||
let allocate_info = vk::CommandBufferAllocateInfo::builder()
|
||||
.command_pool(data.command_pool)
|
||||
.level(vk::CommandBufferLevel::PRIMARY)
|
||||
.command_buffer_count(data.framebuffers.len() as u32);
|
||||
|
||||
data.command_buffers = device.allocate_command_buffers(&allocate_info)?;
|
||||
|
||||
for (i, command_buffer) in data.command_buffers.iter().enumerate() {
|
||||
let inheritance = vk::CommandBufferInheritanceInfo::builder();
|
||||
|
||||
let info = vk::CommandBufferBeginInfo::builder()
|
||||
.flags(vk::CommandBufferUsageFlags::empty()) // Optional.
|
||||
.inheritance_info(&inheritance); // Optional.
|
||||
|
||||
device.begin_command_buffer(*command_buffer, &info)?;
|
||||
|
||||
let render_area = vk::Rect2D::builder()
|
||||
.offset(vk::Offset2D::default())
|
||||
.extent(data.swapchain_extent);
|
||||
|
||||
let color_clear_value = vk::ClearValue {
|
||||
color: vk::ClearColorValue {
|
||||
float32: [0.0, 0.0, 0.0, 1.0],
|
||||
},
|
||||
};
|
||||
|
||||
let clear_values = &[color_clear_value];
|
||||
let info = vk::RenderPassBeginInfo::builder()
|
||||
.render_pass(data.render_pass)
|
||||
.framebuffer(data.framebuffers[i])
|
||||
.render_area(render_area)
|
||||
.clear_values(clear_values);
|
||||
|
||||
device.cmd_begin_render_pass(
|
||||
*command_buffer, &info, vk::SubpassContents::INLINE);
|
||||
|
||||
device.cmd_bind_pipeline(
|
||||
*command_buffer, vk::PipelineBindPoint::GRAPHICS, data.pipeline);
|
||||
|
||||
device.cmd_draw(*command_buffer, 3, 1, 0, 0);
|
||||
|
||||
device.cmd_end_render_pass(*command_buffer);
|
||||
|
||||
device.end_command_buffer(*command_buffer)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
unsafe fn create_sync_objects(device: &Device, data: &mut app_data::AppData) -> Result<()> {
|
||||
let semaphore_info = vk::SemaphoreCreateInfo::builder();
|
||||
let fence_info = vk::FenceCreateInfo::builder()
|
||||
.flags(vk::FenceCreateFlags::SIGNALED);
|
||||
|
||||
for _ in 0..MAX_FRAMES_IN_FLIGHT {
|
||||
data.image_available_semaphores
|
||||
.push(device.create_semaphore(&semaphore_info, None)?);
|
||||
data.render_finished_semaphores
|
||||
.push(device.create_semaphore(&semaphore_info, None)?);
|
||||
|
||||
data.in_flight_fences.push(device.create_fence(&fence_info, None)?);
|
||||
}
|
||||
|
||||
data.images_in_flight = data.swapchain_images
|
||||
.iter()
|
||||
.map(|_| vk::Fence::null())
|
||||
.collect();
|
||||
|
||||
Ok(())
|
||||
}
|
49
src/queue_family_indices.rs
Normal file
49
src/queue_family_indices.rs
Normal file
|
@ -0,0 +1,49 @@
|
|||
use anyhow::{anyhow, Result};
|
||||
|
||||
use vulkanalia::prelude::v1_0::*;
|
||||
|
||||
// extension imports
|
||||
use vulkanalia::vk::KhrSurfaceExtension;
|
||||
|
||||
use crate::app_data;
|
||||
use crate::errors;
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct QueueFamilyIndices {
|
||||
pub graphics: u32,
|
||||
pub present: u32,
|
||||
}
|
||||
|
||||
impl QueueFamilyIndices {
|
||||
pub unsafe fn get(
|
||||
instance: &Instance,
|
||||
data: &app_data::AppData,
|
||||
physical_device: vk::PhysicalDevice,
|
||||
) -> Result<Self> {
|
||||
let properties = instance
|
||||
.get_physical_device_queue_family_properties(physical_device);
|
||||
|
||||
let graphics = properties
|
||||
.iter()
|
||||
.position(|p| p.queue_flags.contains(vk::QueueFlags::GRAPHICS))
|
||||
.map(|i| i as u32);
|
||||
|
||||
let mut present = None;
|
||||
for (index, properties) in properties.iter().enumerate() {
|
||||
if instance.get_physical_device_surface_support_khr(
|
||||
physical_device,
|
||||
index as u32,
|
||||
data.surface,
|
||||
)? {
|
||||
present = Some(index as u32);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if let (Some(graphics), Some(present)) = (graphics, present) {
|
||||
Ok(Self { graphics, present })
|
||||
} else {
|
||||
Err(anyhow!(errors::SuitabilityError("Missing required queue families.")))
|
||||
}
|
||||
}
|
||||
}
|
166
src/swapchain.rs
Normal file
166
src/swapchain.rs
Normal file
|
@ -0,0 +1,166 @@
|
|||
|
||||
use anyhow::Result;
|
||||
use winit::window::Window;
|
||||
|
||||
use vulkanalia::prelude::v1_0::*;
|
||||
|
||||
// extension imports
|
||||
use vulkanalia::vk::KhrSurfaceExtension;
|
||||
use vulkanalia::vk::KhrSwapchainExtension;
|
||||
|
||||
use crate::app_data;
|
||||
use crate::queue_family_indices;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SwapchainSupport {
|
||||
pub capabilities: vk::SurfaceCapabilitiesKHR,
|
||||
pub formats: Vec<vk::SurfaceFormatKHR>,
|
||||
pub present_modes: Vec<vk::PresentModeKHR>,
|
||||
}
|
||||
|
||||
impl SwapchainSupport {
|
||||
pub unsafe fn get(
|
||||
instance: &Instance,
|
||||
data: &app_data::AppData,
|
||||
physical_device: vk::PhysicalDevice,
|
||||
) -> Result<Self> {
|
||||
Ok(Self {
|
||||
capabilities: instance
|
||||
.get_physical_device_surface_capabilities_khr(
|
||||
physical_device, data.surface)?,
|
||||
formats: instance
|
||||
.get_physical_device_surface_formats_khr(
|
||||
physical_device, data.surface)?,
|
||||
present_modes: instance
|
||||
.get_physical_device_surface_present_modes_khr(
|
||||
physical_device, data.surface)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_swapchain_surface_format(
|
||||
formats: &[vk::SurfaceFormatKHR],
|
||||
) -> vk::SurfaceFormatKHR {
|
||||
formats
|
||||
.iter()
|
||||
.cloned()
|
||||
.find(|f| {
|
||||
f.format == vk::Format::B8G8R8A8_SRGB
|
||||
&& f.color_space == vk::ColorSpaceKHR::SRGB_NONLINEAR
|
||||
})
|
||||
.unwrap_or_else(|| formats[0])
|
||||
}
|
||||
|
||||
pub fn get_swapchain_present_mode(
|
||||
present_modes: &[vk::PresentModeKHR],
|
||||
) -> vk::PresentModeKHR {
|
||||
present_modes
|
||||
.iter()
|
||||
.cloned()
|
||||
.find(|m| *m == vk::PresentModeKHR::MAILBOX)
|
||||
.unwrap_or(vk::PresentModeKHR::FIFO)
|
||||
}
|
||||
|
||||
pub fn get_swapchain_extent(
|
||||
window: &Window,
|
||||
capabilities: vk::SurfaceCapabilitiesKHR,
|
||||
) -> vk::Extent2D {
|
||||
if capabilities.current_extent.width != u32::MAX {
|
||||
capabilities.current_extent
|
||||
} else {
|
||||
vk::Extent2D::builder()
|
||||
.width(window.inner_size().width.clamp(
|
||||
capabilities.min_image_extent.width,
|
||||
capabilities.max_image_extent.width,
|
||||
))
|
||||
.height(window.inner_size().height.clamp(
|
||||
capabilities.min_image_extent.height,
|
||||
capabilities.max_image_extent.height,
|
||||
))
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn create_swapchain(
|
||||
window: &Window,
|
||||
instance: &Instance,
|
||||
device: &Device,
|
||||
data: &mut app_data::AppData,
|
||||
) -> Result<()> {
|
||||
let indices = queue_family_indices::QueueFamilyIndices::get(instance, data, data.physical_device)?;
|
||||
let support = SwapchainSupport::get(instance, data, data.physical_device)?;
|
||||
|
||||
let surface_format = get_swapchain_surface_format(&support.formats);
|
||||
let present_mode = get_swapchain_present_mode(&support.present_modes);
|
||||
let extent = get_swapchain_extent(window, support.capabilities);
|
||||
|
||||
let mut image_count = support.capabilities.min_image_count + 1;
|
||||
if support.capabilities.max_image_count != 0
|
||||
&& image_count > support.capabilities.max_image_count
|
||||
{
|
||||
image_count = support.capabilities.max_image_count;
|
||||
}
|
||||
|
||||
let mut queue_family_indices = vec![];
|
||||
let image_sharing_mode = if indices.graphics != indices.present {
|
||||
queue_family_indices.push(indices.graphics);
|
||||
queue_family_indices.push(indices.present);
|
||||
vk::SharingMode::CONCURRENT
|
||||
} else {
|
||||
vk::SharingMode::EXCLUSIVE
|
||||
};
|
||||
|
||||
let info = vk::SwapchainCreateInfoKHR::builder()
|
||||
.surface(data.surface)
|
||||
.min_image_count(image_count)
|
||||
.image_format(surface_format.format)
|
||||
.image_color_space(surface_format.color_space)
|
||||
.image_extent(extent)
|
||||
.image_array_layers(1)
|
||||
.image_usage(vk::ImageUsageFlags::COLOR_ATTACHMENT)
|
||||
.image_sharing_mode(image_sharing_mode)
|
||||
.queue_family_indices(&queue_family_indices)
|
||||
.pre_transform(support.capabilities.current_transform)
|
||||
.composite_alpha(vk::CompositeAlphaFlagsKHR::OPAQUE)
|
||||
.present_mode(present_mode)
|
||||
.clipped(true)
|
||||
.old_swapchain(vk::SwapchainKHR::null());
|
||||
data.swapchain = device.create_swapchain_khr(&info, None)?;
|
||||
data.swapchain_format = surface_format.format;
|
||||
data.swapchain_extent = extent;
|
||||
data.swapchain_images = device.get_swapchain_images_khr(data.swapchain)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub unsafe fn create_swapchain_image_views(
|
||||
device: &Device,
|
||||
data: &mut app_data::AppData,
|
||||
) -> Result<()> {
|
||||
data.swapchain_image_views = data
|
||||
.swapchain_images
|
||||
.iter()
|
||||
.map(|i| {
|
||||
let components = vk::ComponentMapping::builder()
|
||||
.r(vk::ComponentSwizzle::IDENTITY)
|
||||
.g(vk::ComponentSwizzle::IDENTITY)
|
||||
.b(vk::ComponentSwizzle::IDENTITY)
|
||||
.a(vk::ComponentSwizzle::IDENTITY);
|
||||
let subresource_range = vk::ImageSubresourceRange::builder()
|
||||
.aspect_mask(vk::ImageAspectFlags::COLOR)
|
||||
.base_mip_level(0)
|
||||
.level_count(1)
|
||||
.base_array_layer(0)
|
||||
.layer_count(1);
|
||||
let info = vk::ImageViewCreateInfo::builder()
|
||||
.image(*i)
|
||||
.view_type(vk::ImageViewType::_2D)
|
||||
.format(data.swapchain_format)
|
||||
.components(components)
|
||||
.subresource_range(subresource_range);
|
||||
device.create_image_view(&info, None)
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
Ok(())
|
||||
}
|
Loading…
Reference in a new issue