@@ -0,0 +1 @@ | |||
* text=auto eol=lf |
@@ -0,0 +1,8 @@ | |||
**/*.rs.bk | |||
*.bat | |||
*.user | |||
.DS_Store | |||
/out | |||
/target | |||
Cargo.lock | |||
perf.data* |
@@ -0,0 +1,46 @@ | |||
[package] | |||
name = "blonkus" | |||
version = "0.1.0" | |||
authors = ["Alison G. Watson <marrub@greyserv.net>", "Tae Matous"] | |||
description = "Some bullshit." | |||
homepage = "https://greyserv.net/blonkus/" | |||
repository = "http://git.greyserv.net/marrub/BLONKUS" | |||
readme = "README.md" | |||
license = "CC0-1.0" | |||
edition = "2018" | |||
publish = false | |||
build = "source/build.rs" | |||
[features] | |||
default = ["color-log"] | |||
color-log = ["termcolor"] | |||
[dependencies] | |||
ash = "0.29" | |||
sdl2 = "0.32" | |||
serde_yaml = "0.8" | |||
[dependencies.failure] | |||
version = "0.1" | |||
features = ["std"] | |||
[dependencies.termcolor] | |||
version = "1.0" | |||
optional = true | |||
[dependencies.serde] | |||
version = "1.0" | |||
features = ["derive"] | |||
[profile.dev] | |||
opt-level = 1 | |||
[profile.release] | |||
codegen-units = 1 | |||
lto = true | |||
[[bin]] | |||
name = "blonkus" | |||
path = "source/main.rs" | |||
# EOF |
@@ -0,0 +1,126 @@ | |||
To the extent possible under law, I, Alison G. Watson, and all other | |||
contributors to BLONKUS, have waived all copyright and related or neighboring | |||
rights to this Document as described by the Creative Commons Zero license as | |||
follows: | |||
Creative Commons Legal Code | |||
CC0 1.0 Universal | |||
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE | |||
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN | |||
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS | |||
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES | |||
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS | |||
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM | |||
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED | |||
HEREUNDER. | |||
Statement of Purpose | |||
The laws of most jurisdictions throughout the world automatically confer | |||
exclusive Copyright and Related Rights (defined below) upon the creator | |||
and subsequent owner(s) (each and all, an "owner") of an original work of | |||
authorship and/or a database (each, a "Work"). | |||
Certain owners wish to permanently relinquish those rights to a Work for | |||
the purpose of contributing to a commons of creative, cultural and | |||
scientific works ("Commons") that the public can reliably and without fear | |||
of later claims of infringement build upon, modify, incorporate in other | |||
works, reuse and redistribute as freely as possible in any form whatsoever | |||
and for any purposes, including without limitation commercial purposes. | |||
These owners may contribute to the Commons to promote the ideal of a free | |||
culture and the further production of creative, cultural and scientific | |||
works, or to gain reputation or greater distribution for their Work in | |||
part through the use and efforts of others. | |||
For these and/or other purposes and motivations, and without any | |||
expectation of additional consideration or compensation, the person | |||
associating CC0 with a Work (the "Affirmer"), to the extent that he or she | |||
is an owner of Copyright and Related Rights in the Work, voluntarily | |||
elects to apply CC0 to the Work and publicly distribute the Work under its | |||
terms, with knowledge of his or her Copyright and Related Rights in the | |||
Work and the meaning and intended legal effect of CC0 on those rights. | |||
1. Copyright and Related Rights. A Work made available under CC0 may be | |||
protected by copyright and related or neighboring rights ("Copyright and | |||
Related Rights"). Copyright and Related Rights include, but are not | |||
limited to, the following: | |||
i. the right to reproduce, adapt, distribute, perform, display, | |||
communicate, and translate a Work; | |||
ii. moral rights retained by the original author(s) and/or performer(s); | |||
iii. publicity and privacy rights pertaining to a person's image or | |||
likeness depicted in a Work; | |||
iv. rights protecting against unfair competition in regards to a Work, | |||
subject to the limitations in paragraph 4(a), below; | |||
v. rights protecting the extraction, dissemination, use and reuse of data | |||
in a Work; | |||
vi. database rights (such as those arising under Directive 96/9/EC of the | |||
European Parliament and of the Council of 11 March 1996 on the legal | |||
protection of databases, and under any national implementation | |||
thereof, including any amended or successor version of such | |||
directive); and | |||
vii. other similar, equivalent or corresponding rights throughout the | |||
world based on applicable law or treaty, and any national | |||
implementations thereof. | |||
2. Waiver. To the greatest extent permitted by, but not in contravention | |||
of, applicable law, Affirmer hereby overtly, fully, permanently, | |||
irrevocably and unconditionally waives, abandons, and surrenders all of | |||
Affirmer's Copyright and Related Rights and associated claims and causes | |||
of action, whether now known or unknown (including existing as well as | |||
future claims and causes of action), in the Work (i) in all territories | |||
worldwide, (ii) for the maximum duration provided by applicable law or | |||
treaty (including future time extensions), (iii) in any current or future | |||
medium and for any number of copies, and (iv) for any purpose whatsoever, | |||
including without limitation commercial, advertising or promotional | |||
purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each | |||
member of the public at large and to the detriment of Affirmer's heirs and | |||
successors, fully intending that such Waiver shall not be subject to | |||
revocation, rescission, cancellation, termination, or any other legal or | |||
equitable action to disrupt the quiet enjoyment of the Work by the public | |||
as contemplated by Affirmer's express Statement of Purpose. | |||
3. Public License Fallback. Should any part of the Waiver for any reason | |||
be judged legally invalid or ineffective under applicable law, then the | |||
Waiver shall be preserved to the maximum extent permitted taking into | |||
account Affirmer's express Statement of Purpose. In addition, to the | |||
extent the Waiver is so judged Affirmer hereby grants to each affected | |||
person a royalty-free, non transferable, non sublicensable, non exclusive, | |||
irrevocable and unconditional license to exercise Affirmer's Copyright and | |||
Related Rights in the Work (i) in all territories worldwide, (ii) for the | |||
maximum duration provided by applicable law or treaty (including future | |||
time extensions), (iii) in any current or future medium and for any number | |||
of copies, and (iv) for any purpose whatsoever, including without | |||
limitation commercial, advertising or promotional purposes (the | |||
"License"). The License shall be deemed effective as of the date CC0 was | |||
applied by Affirmer to the Work. Should any part of the License for any | |||
reason be judged legally invalid or ineffective under applicable law, such | |||
partial invalidity or ineffectiveness shall not invalidate the remainder | |||
of the License, and in such case Affirmer hereby affirms that he or she | |||
will not (i) exercise any of his or her remaining Copyright and Related | |||
Rights in the Work or (ii) assert any associated claims and causes of | |||
action with respect to the Work, in either case contrary to Affirmer's | |||
express Statement of Purpose. | |||
4. Limitations and Disclaimers. | |||
a. No trademark or patent rights held by Affirmer are waived, abandoned, | |||
surrendered, licensed or otherwise affected by this document. | |||
b. Affirmer offers the Work as-is and makes no representations or | |||
warranties of any kind concerning the Work, express, implied, | |||
statutory or otherwise, including without limitation warranties of | |||
title, merchantability, fitness for a particular purpose, non | |||
infringement, or the absence of latent or other defects, accuracy, or | |||
the present or absence of errors, whether or not discoverable, all to | |||
the greatest extent permissible under applicable law. | |||
c. Affirmer disclaims responsibility for clearing rights of other persons | |||
that may apply to the Work or any use thereof, including without | |||
limitation any person's Copyright and Related Rights in the Work. | |||
Further, Affirmer disclaims responsibility for obtaining any necessary | |||
consents, permissions or other rights required for any use of the | |||
Work. | |||
d. Affirmer understands and acknowledges that Creative Commons is not a | |||
party to this document and has no duty or obligation with respect to | |||
this CC0 or use of the Work. |
@@ -0,0 +1,3 @@ | |||
# BLONKUS | |||
it's a thing |
@@ -0,0 +1,8 @@ | |||
layout(location = 0) in vec3 frag_color; | |||
layout(location = 0) out vec4 out_color; | |||
void main() { | |||
out_color = vec4(frag_color, 1.0); | |||
} | |||
// EOF |
@@ -0,0 +1,20 @@ | |||
layout(location = 0) out vec3 frag_color; | |||
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) | |||
); | |||
void main() { | |||
gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0); | |||
frag_color = colors[gl_VertexIndex]; | |||
} | |||
// EOF |
@@ -0,0 +1,6 @@ | |||
hard_tabs = true | |||
max_width = 80 | |||
newline_style = "Unix" | |||
tab_spaces = 3 | |||
use_field_init_shorthand = true | |||
use_small_heuristics = "Off" |
@@ -0,0 +1,25 @@ | |||
fn main() { | |||
let out_dir = std::env::var("OUT_DIR").unwrap(); | |||
for file in std::fs::read_dir("glsl").unwrap() { | |||
let file = file.unwrap(); | |||
let path = file.path(); | |||
let file_name = path.file_name().unwrap(); | |||
let out = format!("{}/{}.o", out_dir, file_name.to_str().unwrap()); | |||
let path = path.to_str().unwrap(); | |||
println!("rerun-if-changed={}", path); | |||
std::process::Command::new("glslc") | |||
.arg("-O") | |||
.arg("-o") | |||
.arg(&out) | |||
.arg("-std=450core") | |||
.arg("--target-env=vulkan1.0") | |||
.arg("-Werror") | |||
.arg("-xglsl") | |||
.arg(&path) | |||
.status() | |||
.unwrap(); | |||
} | |||
} | |||
// EOF |
@@ -0,0 +1,222 @@ | |||
#[macro_use] | |||
mod util; | |||
mod render; | |||
use ash::{version::DeviceV1_0, vk}; | |||
use crate::{render::*, util::{err, log, meta}}; | |||
use std::convert::TryInto; | |||
serialize! { | |||
conf: | |||
#[derive(Default)] | |||
struct Conf { | |||
log: log::Conf, | |||
render: render::Conf, | |||
} | |||
} | |||
const MAIN_VERT: &'static [u8] = | |||
include_bytes!(concat!(env!("OUT_DIR"), "/main.vert.o")); | |||
const MAIN_FRAG: &'static [u8] = | |||
include_bytes!(concat!(env!("OUT_DIR"), "/main.frag.o")); | |||
fn draw_frame( | |||
device: &Device, | |||
swapchain: &Swapchain, | |||
image_available_s: &Semaphore, | |||
render_finished_s: &Semaphore, | |||
cmd_buffers: &[CommandBuffer], | |||
queue_graphics: &Queue, | |||
queue_surface: &Queue, | |||
) -> err::Result<()> { | |||
let image_index = unsafe { | |||
device.swapchain_ext.acquire_next_image( | |||
**swapchain, | |||
u64::max_value(), | |||
**image_available_s, | |||
vk::Fence::null(), | |||
)?.0 | |||
}; | |||
let wait_semaphores = [**image_available_s]; | |||
let wait_stages = [vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT]; | |||
let signal_semaphores = [**render_finished_s]; | |||
let submit_cmd_buffers = [*cmd_buffers[image_index as usize]]; | |||
let submit_info = [ | |||
vk::SubmitInfo { | |||
wait_semaphore_count: wait_semaphores.len().try_into()?, | |||
p_wait_semaphores: wait_semaphores.as_ptr(), | |||
p_wait_dst_stage_mask: wait_stages.as_ptr(), | |||
command_buffer_count: submit_cmd_buffers.len().try_into()?, | |||
p_command_buffers: submit_cmd_buffers.as_ptr(), | |||
signal_semaphore_count: signal_semaphores.len().try_into()?, | |||
p_signal_semaphores: signal_semaphores.as_ptr(), | |||
..Default::default() | |||
} | |||
]; | |||
unsafe { | |||
device.queue_submit(**queue_graphics, &submit_info, vk::Fence::null())?; | |||
} | |||
let swapchains = [**swapchain]; | |||
let image_indices = [image_index]; | |||
let present_info = vk::PresentInfoKHR { | |||
wait_semaphore_count: signal_semaphores.len().try_into()?, | |||
p_wait_semaphores: signal_semaphores.as_ptr(), | |||
swapchain_count: swapchains.len().try_into()?, | |||
p_swapchains: swapchains.as_ptr(), | |||
p_image_indices: image_indices.as_ptr(), | |||
..Default::default() | |||
}; | |||
unsafe { | |||
device.swapchain_ext.queue_present(**queue_surface, &present_info)?; | |||
} | |||
Ok(()) | |||
} | |||
fn fallback_main(conf: &Conf, lg: &log::Log) -> err::Result<()> { | |||
let sdl_context = err::from_s(sdl2::init())?; | |||
let sdl_video = err::from_s(sdl_context.video())?; | |||
let window = sdl_video.window(meta::name(), 640, 480).vulkan().build()?; | |||
let entry = ash::Entry::new()?; | |||
let instance = Instance::create(&conf.render, &entry, &window)?; | |||
let phys_device = PhysicalDevice::get(&instance, &conf.render)?; | |||
let surface = Surface::create(&instance, &window)?; | |||
let qf_info = QueueFamilyInfo::collect(&instance, &surface, &phys_device)?; | |||
let device = Device::create(&instance, &phys_device, &qf_info)?; | |||
let queue_graphics = Queue::get(&device, qf_info.gfx_index); | |||
let queue_surface = Queue::get(&device, qf_info.srf_index); | |||
let swapchain = Swapchain::create( | |||
&instance, | |||
&device, | |||
&phys_device, | |||
&surface, | |||
&conf.render, | |||
&qf_info, | |||
)?; | |||
let image_views = | |||
swapchain | |||
.get_images()? | |||
.iter() | |||
.map(|&image| ImageView::create(&device, image, swapchain.format)) | |||
.collect::<Result<Vec<_>, _>>()?; | |||
let shader_vert = ShaderModule::create(&device, &Spir::read(MAIN_VERT))?; | |||
let shader_frag = ShaderModule::create(&device, &Spir::read(MAIN_FRAG))?; | |||
let layout = PipelineLayout::create(&device)?; | |||
let render_pass = RenderPass::create(&device, swapchain.format)?; | |||
let pipelines = Pipeline::create( | |||
&device, | |||
&layout, | |||
&render_pass, | |||
&shader_vert, | |||
&shader_frag, | |||
swapchain.extent, | |||
)?; | |||
let framebuffers = | |||
image_views | |||
.iter() | |||
.map(|image_view| { | |||
Framebuffer::create( | |||
&device, | |||
&render_pass, | |||
image_view, | |||
swapchain.extent, | |||
) | |||
}) | |||
.collect::<Result<Vec<_>, _>>()?; | |||
let cmd_pool = CommandPool::create(&device, qf_info.gfx_index)?; | |||
let cmd_buffers = cmd_pool.allocate_buffers(&framebuffers)?; | |||
cmd_buffers | |||
.iter() | |||
.zip(&framebuffers) | |||
.map(|(cmd_buf, framebuffer)| { | |||
cmd_buf.bind( | |||
framebuffer, | |||
&render_pass, | |||
&pipelines[0], | |||
swapchain.extent, | |||
) | |||
}) | |||
.collect::<Result<(), _>>()?; | |||
let image_available_s = Semaphore::create(&device)?; | |||
let render_finished_s = Semaphore::create(&device)?; | |||
let frame = draw_frame( | |||
&device, | |||
&swapchain, | |||
&image_available_s, | |||
&render_finished_s, | |||
&cmd_buffers, | |||
&queue_graphics, | |||
&queue_surface, | |||
); | |||
if let Err(e) = frame { | |||
lg!(lg, Error, "Error rendering frame: {}", e); | |||
} | |||
std::thread::sleep(std::time::Duration::from_secs(3)); | |||
let seize = unsafe { device.device_wait_idle() }; | |||
if let Err(e) = seize { | |||
lg!(lg, Error, "Error seizing renderer state: {}", e); | |||
} | |||
Ok(()) | |||
} | |||
fn main() { | |||
let lg = | |||
log::Log::new() | |||
.insert_mode("Std", log::Std); | |||
#[cfg(feature = "color-log")] | |||
let lg = lg.insert_mode("Color", log::Color).set_mode("Color").unwrap(); | |||
trace!(&lg); | |||
let (conf, lg) = if let Ok(file) = std::fs::File::open("blonkus.yaml") { | |||
let conf: Result<Conf, _> = serde_yaml::from_reader(file); | |||
match conf { | |||
Ok(conf) => { | |||
(conf, lg) | |||
} | |||
Err(e) => { | |||
lg!(&lg, Error, "Error loading configuration: {}", e); | |||
(Conf::default(), lg) | |||
} | |||
} | |||
} else { | |||
lg!(&lg, Info, "No configuration file available. Using defaults."); | |||
(Conf::default(), lg) | |||
}; | |||
if let Err(e) = fallback_main(&conf, &lg) { | |||
lg!(&lg, Critical, "Uncaught error: {}", e); | |||
} | |||
} | |||
// EOF |
@@ -0,0 +1,65 @@ | |||
mod cmd; | |||
mod conf; | |||
mod device; | |||
mod framebuffer; | |||
mod imageview; | |||
mod instance; | |||
mod pipeline; | |||
mod queue; | |||
mod semaphore; | |||
mod shader; | |||
mod spir; | |||
mod surface; | |||
mod swapchain; | |||
pub use self::{ | |||
cmd::{CommandBuffer, CommandPool}, | |||
conf::{Conf, PresentMode}, | |||
device::{Device, PhysicalDevice}, | |||
framebuffer::Framebuffer, | |||
imageview::ImageView, | |||
instance::Instance, | |||
pipeline::{PipelineLayout, Pipeline, RenderPass}, | |||
queue::{Queue, QueueFamilyInfo}, | |||
semaphore::Semaphore, | |||
shader::ShaderModule, | |||
spir::Spir, | |||
surface::Surface, | |||
swapchain::Swapchain, | |||
}; | |||
use std::{ffi::CStr, os::raw::c_char}; | |||
#[derive(Debug)] | |||
pub struct ErrNoProperty { | |||
name: String, | |||
} | |||
impl std::fmt::Display for ErrNoProperty { | |||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | |||
write!(f, "{} not available", self.name) | |||
} | |||
} | |||
impl std::error::Error for ErrNoProperty {} | |||
pub fn ensure_properties<T, F>( | |||
props: &[T], | |||
names: &[*const c_char], | |||
func: F, | |||
) -> Result<(), ErrNoProperty> | |||
where | |||
F: Fn(&T) -> *const c_char, | |||
{ | |||
for name in names { | |||
let name = unsafe { CStr::from_ptr(*name) }; | |||
if !props.iter().any(|x| unsafe { CStr::from_ptr(func(x)) == name }) { | |||
return Err(ErrNoProperty { name: name.to_string_lossy().to_string() }); | |||
} | |||
} | |||
Ok(()) | |||
} | |||
// EOF |
@@ -0,0 +1,127 @@ | |||
use ash::{version::DeviceV1_0, vk}; | |||
use crate::{render::{Device, Framebuffer, Pipeline, RenderPass}, util::err}; | |||
use std::convert::TryInto; | |||
pub struct CommandPool<'a, 'b> { | |||
device: &'a Device<'b>, | |||
handle: vk::CommandPool, | |||
} | |||
pub struct CommandBuffer<'a, 'b, 'c> { | |||
pool: &'a CommandPool<'b, 'c>, | |||
handle: vk::CommandBuffer, | |||
} | |||
impl<'a, 'b> CommandPool<'a, 'b> { | |||
pub fn create(device: &'a Device<'b>, index: u32) -> err::Result<Self> { | |||
let handle = unsafe { | |||
device.create_command_pool( | |||
&vk::CommandPoolCreateInfo { | |||
queue_family_index: index, | |||
..Default::default() | |||
}, | |||
None, | |||
)? | |||
}; | |||
Ok(Self { device, handle }) | |||
} | |||
pub fn allocate_buffers( | |||
&self, | |||
framebuffers: &[Framebuffer], | |||
) -> err::Result<Vec<CommandBuffer>> { | |||
let alloc_info = vk::CommandBufferAllocateInfo { | |||
command_pool: **self, | |||
level: vk::CommandBufferLevel::PRIMARY, | |||
command_buffer_count: framebuffers.len().try_into()?, | |||
..Default::default() | |||
}; | |||
let bufs = unsafe { | |||
self | |||
.device | |||
.allocate_command_buffers(&alloc_info)? | |||
.iter() | |||
.map(|&handle| CommandBuffer { pool: self, handle }) | |||
.collect() | |||
}; | |||
Ok(bufs) | |||
} | |||
} | |||
impl CommandBuffer<'_, '_, '_> { | |||
pub fn bind( | |||
&self, | |||
framebuffer: &Framebuffer, | |||
render_pass: &RenderPass, | |||
pipeline: &Pipeline, | |||
image_extent: vk::Extent2D, | |||
) -> err::Result<()> { | |||
let begin_info = vk::CommandBufferBeginInfo::default(); | |||
unsafe { | |||
self.pool.device.begin_command_buffer(self.handle, &begin_info)?; | |||
} | |||
let clear_values = [ | |||
vk::ClearValue { | |||
color: vk::ClearColorValue { | |||
float32: [0.0, 0.0, 0.0, 1.0], | |||
} | |||
} | |||
]; | |||
let begin_info = vk::RenderPassBeginInfo { | |||
render_pass: **render_pass, | |||
framebuffer: **framebuffer, | |||
render_area: vk::Rect2D { | |||
offset: vk::Offset2D::default(), | |||
extent: image_extent, | |||
}, | |||
clear_value_count: clear_values.len().try_into()?, | |||
p_clear_values: clear_values.as_ptr(), | |||
..Default::default() | |||
}; | |||
unsafe { | |||
self.pool.device.cmd_begin_render_pass( | |||
self.handle, | |||
&begin_info, | |||
vk::SubpassContents::INLINE, | |||
); | |||
self.pool.device.cmd_bind_pipeline( | |||
self.handle, | |||
vk::PipelineBindPoint::GRAPHICS, | |||
**pipeline, | |||
); | |||
self.pool.device.cmd_draw(self.handle, 3, 1, 0, 0); | |||
self.pool.device.cmd_end_render_pass(self.handle); | |||
self.pool.device.end_command_buffer(self.handle)?; | |||
} | |||
Ok(()) | |||
} | |||
} | |||
impl Drop for CommandPool<'_, '_> { | |||
fn drop(&mut self) { | |||
unsafe { self.device.destroy_command_pool(self.handle, None); } | |||
} | |||
} | |||
impl std::ops::Deref for CommandPool<'_, '_> { | |||
type Target = vk::CommandPool; | |||
fn deref(&self) -> &Self::Target { &self.handle } | |||
} | |||
impl std::ops::Deref for CommandBuffer<'_, '_, '_> { | |||
type Target = vk::CommandBuffer; | |||
fn deref(&self) -> &Self::Target { &self.handle } | |||
} | |||
// EOF |
@@ -0,0 +1,44 @@ | |||
use ash::vk; | |||
serialize! { | |||
conf_enum: | |||
#[derive(Clone, Copy)] | |||
pub enum PresentMode { | |||
Immediate, | |||
Mailbox, | |||
Fifo, | |||
FifoRelaxed, | |||
} | |||
} | |||
serialize! { | |||
conf: | |||
pub struct Conf { | |||
pub device: usize, | |||
pub validation_layers: bool, | |||
pub swap_mode: PresentMode, | |||
} | |||
} | |||
impl Into<vk::PresentModeKHR> for PresentMode { | |||
fn into(self) -> vk::PresentModeKHR { | |||
match self { | |||
Self::Immediate => vk::PresentModeKHR::IMMEDIATE, | |||
Self::Mailbox => vk::PresentModeKHR::MAILBOX, | |||
Self::Fifo => vk::PresentModeKHR::FIFO, | |||
Self::FifoRelaxed => vk::PresentModeKHR::FIFO_RELAXED, | |||
} | |||
} | |||
} | |||
impl Default for Conf { | |||
fn default() -> Self { | |||
Self { | |||
device: 0, | |||
validation_layers: cfg!(debug_assertions), | |||
swap_mode: PresentMode::Mailbox, | |||
} | |||
} | |||
} | |||
// EOF |
@@ -0,0 +1,111 @@ | |||
use ash::{extensions::khr, version::{DeviceV1_0, InstanceV1_0}, vk}; | |||
use crate::{ | |||
render::{Conf, Instance, ensure_properties, QueueFamilyInfo}, | |||
util::err | |||
}; | |||
use std::{convert::TryInto, marker::PhantomData, os::raw::c_char}; | |||
pub struct Device<'a> { | |||
instance: PhantomData<&'a Instance>, | |||
handle: ash::Device, | |||
pub swapchain_ext: khr::Swapchain, | |||
} | |||
pub struct PhysicalDevice<'a> { | |||
instance: PhantomData<&'a Instance>, | |||
handle: vk::PhysicalDevice, | |||
} | |||
fn enable_device_extensions( | |||
instance: &ash::Instance, | |||
phys_device: &PhysicalDevice, | |||
extensions: &[*const c_char], | |||
) -> err::Result<vk::DeviceCreateInfo> { | |||
if extensions.len() > 0 { | |||
let props = unsafe { | |||
instance.enumerate_device_extension_properties(**phys_device)? | |||
}; | |||
ensure_properties(&props, extensions, |x| x.extension_name.as_ptr())?; | |||
} | |||
Ok(vk::DeviceCreateInfo { | |||
enabled_extension_count: extensions.len().try_into()?, | |||
pp_enabled_extension_names: extensions.as_ptr(), | |||
..Default::default() | |||
}) | |||
} | |||
impl<'a> Device<'a> { | |||
pub fn create( | |||
instance: &'a Instance, | |||
phys_device: &'a PhysicalDevice, | |||
qf_info: &QueueFamilyInfo, | |||
) -> err::Result<Self> { | |||
const QUEUE_PRIORITY: [f32; 1] = [1.0]; | |||
let queue_create_info = [ | |||
vk::DeviceQueueCreateInfo { | |||
queue_family_index: qf_info.gfx_index, | |||
queue_count: 1, | |||
p_queue_priorities: QUEUE_PRIORITY.as_ptr(), | |||
..Default::default() | |||
}, | |||
vk::DeviceQueueCreateInfo { | |||
queue_family_index: qf_info.srf_index, | |||
queue_count: 1, | |||
p_queue_priorities: QUEUE_PRIORITY.as_ptr(), | |||
..Default::default() | |||
}, | |||
]; | |||
let device_features = vk::PhysicalDeviceFeatures::default(); | |||
let extensions = [c_str!("VK_KHR_swapchain")]; | |||
let create_info = vk::DeviceCreateInfo { | |||
queue_create_info_count: queue_create_info.len().try_into()?, | |||
p_queue_create_infos: queue_create_info.as_ptr(), | |||
p_enabled_features: &device_features, | |||
..enable_device_extensions(instance, phys_device, &extensions)? | |||
}; | |||
let handle = unsafe { | |||
instance.create_device(**phys_device, &create_info, None)? | |||
}; | |||
let swapchain_ext = khr::Swapchain::new(&**instance, &handle); | |||
Ok(Self { instance: PhantomData, handle, swapchain_ext }) | |||
} | |||
} | |||
impl<'a> PhysicalDevice<'a> { | |||
pub fn get(instance: &'a Instance, conf: &Conf) -> err::Result<Self> { | |||
let devs = unsafe { instance.enumerate_physical_devices()? }; | |||
if conf.device < devs.len() { | |||
let handle = devs[conf.device]; | |||
Ok(Self { instance: PhantomData, handle }) | |||
} else { | |||
Err(err::static_msg("no device for Vulkan")) | |||
} | |||
} | |||
} | |||
impl Drop for Device<'_> { | |||
fn drop(&mut self) { unsafe { self.handle.destroy_device(None); } } | |||
} | |||
impl std::ops::Deref for Device<'_> { | |||
type Target = ash::Device; | |||
fn deref(&self) -> &Self::Target { &self.handle } | |||
} | |||
impl std::ops::Deref for PhysicalDevice<'_> { | |||
type Target = vk::PhysicalDevice; | |||
fn deref(&self) -> &Self::Target { &self.handle } | |||
} | |||
// EOF |
@@ -0,0 +1,45 @@ | |||
use ash::{version::DeviceV1_0, vk}; | |||
use crate::{render::{Device, ImageView, RenderPass}, util::err}; | |||
use std::convert::TryInto; | |||
pub struct Framebuffer<'a, 'b> { | |||
device: &'a Device<'b>, | |||
handle: vk::Framebuffer, | |||
} | |||
impl<'a, 'b> Framebuffer<'a, 'b> { | |||
pub fn create( | |||
device: &'a Device<'b>, | |||
render_pass: &RenderPass, | |||
image_view: &ImageView, | |||
image_extent: vk::Extent2D, | |||
) -> err::Result<Self> { | |||
let attachments = [**image_view]; | |||
let create_info = vk::FramebufferCreateInfo { | |||
render_pass: **render_pass, | |||
attachment_count: attachments.len().try_into()?, | |||
p_attachments: attachments.as_ptr(), | |||
width: image_extent.width, | |||
height: image_extent.height, | |||
layers: 1, | |||
..Default::default() | |||
}; | |||
let handle = unsafe { device.create_framebuffer(&create_info, None)? }; | |||
Ok(Self { device, handle }) | |||
} | |||
} | |||
impl Drop for Framebuffer<'_, '_> { | |||
fn drop(&mut self) { | |||
unsafe { self.device.destroy_framebuffer(self.handle, None); } | |||
} | |||
} | |||
impl std::ops::Deref for Framebuffer<'_, '_> { | |||
type Target = vk::Framebuffer; | |||
fn deref(&self) -> &Self::Target { &self.handle } | |||
} | |||
// EOF |
@@ -0,0 +1,50 @@ | |||
use ash::{version::DeviceV1_0, vk}; | |||
use crate::{render::Device, util::err}; | |||
pub struct ImageView<'a, 'b> { | |||
device: &'a Device<'b>, | |||
handle: vk::ImageView, | |||
} | |||
impl<'a, 'b> ImageView<'a, 'b> { | |||
pub fn create( | |||
device: &'a Device<'b>, | |||
image: vk::Image, | |||
format: vk::Format, | |||
) -> err::Result<Self> { | |||
let create_info = vk::ImageViewCreateInfo { | |||
image, | |||
format, | |||
view_type: vk::ImageViewType::TYPE_2D, | |||
components: vk::ComponentMapping { | |||
r: vk::ComponentSwizzle::IDENTITY, | |||
g: vk::ComponentSwizzle::IDENTITY, | |||
b: vk::ComponentSwizzle::IDENTITY, | |||
a: vk::ComponentSwizzle::IDENTITY, | |||
}, | |||
subresource_range: vk::ImageSubresourceRange { | |||
aspect_mask: vk::ImageAspectFlags::COLOR, | |||
base_mip_level: 0, | |||
level_count: 1, | |||
base_array_layer: 0, | |||
layer_count: 1, | |||
}, | |||
..Default::default() | |||
}; | |||
let handle = unsafe { device.create_image_view(&create_info, None)? }; | |||
Ok(Self { device, handle }) | |||
} | |||
} | |||
impl Drop for ImageView<'_, '_> { | |||
fn drop(&mut self) { | |||
unsafe { self.device.destroy_image_view(self.handle, None); } | |||
} | |||
} | |||
impl std::ops::Deref for ImageView<'_, '_> { | |||
type Target = vk::ImageView; | |||
fn deref(&self) -> &Self::Target { &self.handle } | |||
} | |||
// EOF |
@@ -0,0 +1,75 @@ | |||
use ash::{ | |||
extensions::khr, | |||
version::{EntryV1_0, InstanceV1_0}, | |||
vk, | |||
vk_make_version | |||
}; | |||
use crate::{render::{Conf, ensure_properties}, util::{err, ffi, meta}}; | |||
use std::{convert::TryInto, os::raw::c_char}; | |||
pub struct Instance { | |||
handle: ash::Instance, | |||
pub surface_ext: khr::Surface, | |||
} | |||
fn enable_instance_layers( | |||
layers: &[*const c_char], | |||
entry: &ash::Entry, | |||
) -> err::Result<vk::InstanceCreateInfo> { | |||
if layers.len() > 0 { | |||
let props = entry.enumerate_instance_layer_properties()?; | |||
ensure_properties(&props, layers, |x| x.layer_name.as_ptr())?; | |||
} | |||
Ok(vk::InstanceCreateInfo { | |||
enabled_layer_count: layers.len().try_into()?, | |||
pp_enabled_layer_names: layers.as_ptr(), | |||
..Default::default() | |||
}) | |||
} | |||
impl Instance { | |||
pub fn create( | |||
conf: &Conf, | |||
entry: &ash::Entry, | |||
window: &sdl2::video::Window, | |||
) -> err::Result<Self> { | |||
let exts = err::from_s(window.vulkan_instance_extensions())?; | |||
let exts = ffi::CStringVec::new_from_iter(exts.into_iter())?; | |||
let app_info = vk::ApplicationInfo { | |||
p_engine_name: meta::ffi::name(), | |||
api_version: vk_make_version!(1, 0, 0), | |||
..Default::default() | |||
}; | |||
let mut layers = Vec::new(); | |||
if conf.validation_layers { | |||
layers.push(c_str!("VK_LAYER_KHRONOS_validation")); | |||
} | |||
let create_info = vk::InstanceCreateInfo { | |||
p_application_info: &app_info, | |||
enabled_extension_count: exts.len().try_into()?, | |||
pp_enabled_extension_names: exts.as_ptr(), | |||
..enable_instance_layers(&layers, &entry)? | |||
}; | |||
let handle = unsafe { entry.create_instance(&create_info, None)? }; | |||
let surface_ext = khr::Surface::new(entry, &handle); | |||
Ok(Self { handle, surface_ext }) | |||
} | |||
} | |||
impl Drop for Instance { | |||
fn drop(&mut self) { unsafe { self.handle.destroy_instance(None); } } | |||
} | |||
impl std::ops::Deref for Instance { | |||
type Target = ash::Instance; | |||
fn deref(&self) -> &Self::Target { &self.handle } | |||
} | |||
// EOF |
@@ -0,0 +1,251 @@ | |||
use ash::{version::DeviceV1_0, vk}; | |||
use crate::{render::{Device, ShaderModule}, util::err}; | |||
use std::convert::TryInto; | |||
pub struct PipelineLayout<'a, 'b> { | |||
device: &'a Device<'b>, | |||
handle: vk::PipelineLayout, | |||
} | |||
pub struct RenderPass<'a, 'b> { | |||
device: &'a Device<'b>, | |||
handle: vk::RenderPass, | |||
} | |||
pub struct Pipeline<'a, 'b> { | |||
device: &'a Device<'b>, | |||
handle: vk::Pipeline, | |||
} | |||
impl<'a, 'b> PipelineLayout<'a, 'b> { | |||
pub fn create(device: &'a Device<'b>) -> err::Result<Self> { | |||
let create_info = vk::PipelineLayoutCreateInfo::default(); | |||
let handle = unsafe { | |||
device.create_pipeline_layout(&create_info, None)? | |||
}; | |||
Ok(Self { device, handle }) | |||
} | |||
} | |||
impl<'a, 'b> RenderPass<'a, 'b> { | |||
pub fn create( | |||
device: &'a Device<'b>, | |||
format: vk::Format, | |||
) -> err::Result<Self> { | |||
let attachments = [ | |||
vk::AttachmentDescription { | |||
format, | |||
flags: vk::AttachmentDescriptionFlags::empty(), | |||
samples: vk::SampleCountFlags::TYPE_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 attachment_references = [ | |||
vk::AttachmentReference { | |||
attachment: 0, | |||
layout: vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL, | |||
} | |||
]; | |||
let subpasses = [ | |||
vk::SubpassDescription { | |||
pipeline_bind_point: vk::PipelineBindPoint::GRAPHICS, | |||
color_attachment_count: attachment_references.len().try_into()?, | |||
p_color_attachments: attachment_references.as_ptr(), | |||
..Default::default() | |||
} | |||
]; | |||
let dependencies = [ | |||
vk::SubpassDependency { | |||
src_subpass: vk::SUBPASS_EXTERNAL, | |||
dst_subpass: 0, | |||
src_stage_mask: vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT, | |||
dst_stage_mask: vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT, | |||
src_access_mask: vk::AccessFlags::empty(), | |||
dst_access_mask: | |||
vk::AccessFlags::COLOR_ATTACHMENT_READ | | |||
vk::AccessFlags::COLOR_ATTACHMENT_WRITE, | |||
dependency_flags: vk::DependencyFlags::empty(), | |||
} | |||
]; | |||
let create_info = vk::RenderPassCreateInfo { | |||
attachment_count: attachments.len().try_into()?, | |||
p_attachments: attachments.as_ptr(), | |||
subpass_count: subpasses.len().try_into()?, | |||
p_subpasses: subpasses.as_ptr(), | |||
dependency_count: dependencies.len().try_into()?, | |||
p_dependencies: dependencies.as_ptr(), | |||
..Default::default() | |||
}; | |||
let handle = unsafe { device.create_render_pass(&create_info, None)? }; | |||
Ok(Self { device, handle }) | |||
} | |||
} | |||
impl<'a, 'b> Pipeline<'a, 'b> { | |||
pub fn create( | |||
device: &'a Device<'b>, | |||
layout: &PipelineLayout, | |||
render_pass: &RenderPass, | |||
shader_vert: &ShaderModule, | |||
shader_frag: &ShaderModule, | |||
extent: vk::Extent2D, | |||
) -> err::Result<Vec<Self>> { | |||
let shader_stages = [ | |||
vk::PipelineShaderStageCreateInfo { | |||
stage: vk::ShaderStageFlags::VERTEX, | |||
module: **shader_vert, | |||
p_name: c_str!("main"), | |||
..Default::default() | |||
}, | |||
vk::PipelineShaderStageCreateInfo { | |||
stage: vk::ShaderStageFlags::FRAGMENT, | |||
module: **shader_frag, | |||
p_name: c_str!("main"), | |||
..Default::default() | |||
}, | |||
]; | |||
let vertex_input_state = | |||
vk::PipelineVertexInputStateCreateInfo::default(); | |||
let input_assembly_state = vk::PipelineInputAssemblyStateCreateInfo { | |||
topology: vk::PrimitiveTopology::TRIANGLE_LIST, | |||
primitive_restart_enable: vk::FALSE, | |||
..Default::default() | |||
}; | |||
let viewports = [ | |||
vk::Viewport { | |||
x: 0.0, | |||
y: 0.0, | |||
width: extent.width as f32, | |||
height: extent.height as f32, | |||
min_depth: 0.0, | |||
max_depth: 1.0, | |||
} | |||
]; | |||
let scissors = [ | |||
vk::Rect2D { | |||
extent, | |||
offset: vk::Offset2D::default(), | |||
} | |||
]; | |||
let viewport_state = vk::PipelineViewportStateCreateInfo { | |||
viewport_count: viewports.len().try_into()?, | |||
p_viewports: viewports.as_ptr(), | |||
scissor_count: scissors.len().try_into()?, | |||
p_scissors: scissors.as_ptr(), | |||
..Default::default() | |||
}; | |||
let rasterization_state = vk::PipelineRasterizationStateCreateInfo { | |||
polygon_mode: vk::PolygonMode::FILL, | |||
line_width: 1.0, | |||
cull_mode: vk::CullModeFlags::BACK, | |||
front_face: vk::FrontFace::CLOCKWISE, | |||
..Default::default() | |||
}; | |||
let multisample_state = vk::PipelineMultisampleStateCreateInfo { | |||
sample_shading_enable: vk::FALSE, | |||
rasterization_samples: vk::SampleCountFlags::TYPE_1, | |||
min_sample_shading: 1.0, | |||
..Default::default() | |||
}; | |||
let color_blend_attachments = [ | |||
vk::PipelineColorBlendAttachmentState { | |||
color_write_mask: vk::ColorComponentFlags::all(), | |||
..Default::default() | |||
} | |||
]; | |||
let color_blend_state = vk::PipelineColorBlendStateCreateInfo { | |||
attachment_count: color_blend_attachments.len().try_into()?, | |||
p_attachments: color_blend_attachments.as_ptr(), | |||
..Default::default() | |||
}; | |||
let create_info = vk::GraphicsPipelineCreateInfo { | |||
layout: **layout, | |||
render_pass: **render_pass, | |||
stage_count: shader_stages.len().try_into()?, | |||
p_stages: shader_stages.as_ptr(), | |||
p_vertex_input_state: &vertex_input_state, | |||
p_input_assembly_state: &input_assembly_state, | |||
p_viewport_state: &viewport_state, | |||
p_rasterization_state: &rasterization_state, | |||
p_multisample_state: &multisample_state, | |||
p_color_blend_state: &color_blend_state, | |||
..Default::default() | |||
}; | |||
let pipelines = unsafe { | |||
device | |||
.create_graphics_pipelines( | |||
vk::PipelineCache::null(), | |||
&[create_info], | |||
None, | |||
) | |||
.or_else(|(pipelines, r)| { | |||
for pipeline in pipelines { | |||
device.destroy_pipeline(pipeline, None); | |||
} | |||
Err(r) | |||
})? | |||
.iter() | |||
.map(|&handle| Self { device, handle }) | |||
.collect() | |||
}; | |||
Ok(pipelines) | |||
} | |||
} | |||
impl Drop for PipelineLayout<'_, '_> { | |||
fn drop(&mut self) { | |||
unsafe { self.device.destroy_pipeline_layout(self.handle, None); } | |||
} | |||
} | |||
impl Drop for RenderPass<'_, '_> { | |||
fn drop(&mut self) { | |||
unsafe { self.device.destroy_render_pass(self.handle, None); } | |||
} | |||
} | |||
impl Drop for Pipeline<'_, '_> { | |||
fn drop(&mut self) { | |||
unsafe { self.device.destroy_pipeline(self.handle, None); } | |||
} | |||
} | |||
impl std::ops::Deref for PipelineLayout<'_, '_> { | |||
type Target = vk::PipelineLayout; | |||
fn deref(&self) -> &Self::Target { &self.handle } | |||
} | |||
impl std::ops::Deref for RenderPass<'_, '_> { | |||
type Target = vk::RenderPass; | |||
fn deref(&self) -> &Self::Target { &self.handle } | |||
} | |||
impl std::ops::Deref for Pipeline<'_, '_> { | |||
type Target = vk::Pipeline; | |||
fn deref(&self) -> &Self::Target { &self.handle } | |||
} | |||
// EOF |
@@ -0,0 +1,67 @@ | |||
use ash::{version::{DeviceV1_0, InstanceV1_0}, vk}; | |||
use crate::{render::{Device, Instance, PhysicalDevice, Surface}, util::err}; | |||
use std::{convert::TryInto, marker::PhantomData}; | |||
pub struct QueueFamilyInfo { | |||
pub gfx_index: u32, | |||
pub srf_index: u32, | |||
} | |||
pub struct Queue<'a, 'b> { | |||
device: PhantomData<&'a Device<'b>>, | |||
handle: vk::Queue, | |||
} | |||
impl QueueFamilyInfo { | |||
pub fn collect( | |||
instance: &Instance, | |||
surface: &Surface, | |||
phys_device: &PhysicalDevice, | |||
) -> err::Result<Self> { | |||
let mut gfx_index = None; | |||
let mut srf_index = None; | |||
let queues = unsafe { | |||
instance.get_physical_device_queue_family_properties(**phys_device) | |||
}; | |||
for (i, queue) in queues.iter().enumerate() { | |||
let i = i.try_into()?; | |||
let surface_support = unsafe { | |||
instance.surface_ext.get_physical_device_surface_support( | |||
**phys_device, | |||
i, | |||
**surface, | |||
) | |||
}; | |||
if queue.queue_flags.contains(vk::QueueFlags::GRAPHICS) { | |||
gfx_index = Some(i); | |||
} | |||
if surface_support { | |||
srf_index = Some(i); | |||
} | |||
} | |||
Ok(Self { | |||
gfx_index: gfx_index.ok_or(err::static_msg("no graphics queue"))?, | |||
srf_index: srf_index.ok_or(err::static_msg("no surface queue"))?, | |||
}) | |||
} | |||
} | |||
impl<'a, 'b> Queue<'a, 'b> { | |||
pub fn get(device: &'a Device<'b>, index: u32) -> Self { | |||
let handle = unsafe { device.get_device_queue(index, 0) }; | |||
Self { device: PhantomData, handle } | |||
} | |||
} | |||
impl std::ops::Deref for Queue<'_, '_> { | |||
type Target = vk::Queue; | |||
fn deref(&self) -> &Self::Target { &self.handle } | |||
} | |||
// EOF |
@@ -0,0 +1,28 @@ | |||
use ash::{version::DeviceV1_0, vk}; | |||
use crate::util::err; | |||
pub struct Semaphore<'a> { | |||
device: &'a ash::Device, | |||
handle: vk::Semaphore, | |||
} | |||
impl<'a> Semaphore<'a> { | |||
pub fn create(device: &'a ash::Device) -> err::Result<Self> { | |||
let create_info = vk::SemaphoreCreateInfo::default(); | |||
let handle = unsafe { device.create_semaphore(&create_info, None)? }; | |||
Ok(Self { device, handle }) | |||
} | |||
} | |||
impl Drop for Semaphore<'_> { | |||
fn drop(&mut self) { | |||
unsafe { self.device.destroy_semaphore(self.handle, None); } | |||
} | |||
} | |||
impl std::ops::Deref for Semaphore<'_> { | |||
type Target = vk::Semaphore; | |||
fn deref(&self) -> &Self::Target { &self.handle } | |||
} | |||
// EOF |
@@ -0,0 +1,33 @@ | |||
use ash::{version::DeviceV1_0, vk}; | |||
use crate::{render::{Device, Spir}, util::err}; | |||
pub struct ShaderModule<'a, 'b> { | |||
device: &'a Device<'b>, | |||
handle: vk::ShaderModule, | |||
} | |||
impl<'a, 'b> ShaderModule<'a, 'b> { | |||
pub fn create(device: &'a Device<'b>, spir: &Spir) -> err::Result<Self> { | |||
let create_info = vk::ShaderModuleCreateInfo { | |||
code_size: spir.size, | |||
p_code: spir.code.as_ptr(), | |||
..Default::default() | |||
}; | |||
let handle = unsafe { device.create_shader_module(&create_info, None)? }; | |||
Ok(Self { device, handle }) | |||
} | |||
} | |||
impl Drop for ShaderModule<'_, '_> { | |||
fn drop(&mut self) { | |||
unsafe { self.device.destroy_shader_module(self.handle, None); } | |||
} | |||
} | |||
impl std::ops::Deref for ShaderModule<'_, '_> { | |||
type Target = vk::ShaderModule; | |||
fn deref(&self) -> &Self::Target { &self.handle } | |||
} | |||
// EOF |
@@ -0,0 +1,22 @@ | |||
pub struct Spir { | |||
pub code: Vec<u32>, | |||
pub size: usize, | |||
} | |||
impl Spir { | |||
pub fn read(bytecode: &[u8]) -> Spir { | |||
let mut code = Vec::with_capacity(bytecode.len() / 4); | |||
for word in bytecode.chunks_exact(4) { | |||
code.push(u32::from_le_bytes([word[0], word[1], word[2], word[3]])); | |||
} | |||
let size = code.len() * 4; | |||
Spir { | |||
code, | |||
size, | |||
} | |||
} | |||
} | |||
// EOF |
@@ -0,0 +1,35 @@ | |||
use ash::{version::InstanceV1_0, vk::{self, Handle}}; | |||
use crate::{render::Instance, util::err}; | |||
use std::convert::TryInto; | |||
pub struct Surface<'a> { | |||
instance: &'a Instance, | |||
handle: vk::SurfaceKHR, | |||
} | |||
impl<'a> Surface<'a> { | |||
pub fn create( | |||
instance: &'a Instance, | |||
window: &sdl2::video::Window, | |||
) -> err::Result<Self> { | |||
let handle = { | |||
let instance = instance.handle().as_raw().try_into()?; | |||
let handle = err::from_s(window.vulkan_create_surface(instance))?; | |||
vk::SurfaceKHR::from_raw(handle) | |||
}; | |||
Ok(Self { instance, handle }) | |||
} | |||
} | |||
impl Drop for Surface<'_> { | |||
fn drop(&mut self) { | |||
unsafe { self.instance.surface_ext.destroy_surface(self.handle, None); } | |||
} | |||
} | |||
impl std::ops::Deref for Surface<'_> { | |||
type Target = vk::SurfaceKHR; | |||
fn deref(&self) -> &Self::Target { &self.handle } | |||
} | |||
// EOF |
@@ -0,0 +1,140 @@ | |||
use ash::vk; | |||
use crate::{ | |||
render::{Conf, Device, Instance, PhysicalDevice, QueueFamilyInfo, Surface}, | |||
util::err | |||
}; | |||
use std::convert::TryInto; | |||
pub struct Swapchain<'a, 'b> { | |||
device: &'a Device<'b>, | |||
handle: vk::SwapchainKHR, | |||
pub format: vk::Format, | |||
pub extent: vk::Extent2D, | |||
} | |||
impl<'a, 'b> Swapchain<'a, 'b> { | |||
pub fn create( | |||
instance: &Instance, | |||
device: &'a Device<'b>, | |||
phys_device: &PhysicalDevice, | |||
surface: &Surface, | |||
conf: &Conf, | |||
qf_info: &QueueFamilyInfo, | |||
) -> err::Result<Self> { | |||
let capabilities = unsafe { | |||
instance | |||
.surface_ext | |||
.get_physical_device_surface_capabilities(**phys_device, **surface)? | |||
}; | |||
let formats = unsafe { | |||
instance | |||
.surface_ext | |||
.get_physical_device_surface_formats(**phys_device, **surface)? | |||
}; | |||
let modes = unsafe { | |||
instance | |||
.surface_ext | |||
.get_physical_device_surface_present_modes( | |||
**phys_device, | |||
**surface, | |||
)? | |||
}; | |||
let extent = | |||
if capabilities.current_extent.width != u32::max_value() { | |||
capabilities.current_extent | |||
} else { | |||
vk::Extent2D { | |||
width: | |||
capabilities | |||
.min_image_extent | |||
.width | |||
.max(capabilities.max_image_extent.width), | |||
height: | |||
capabilities | |||
.min_image_extent | |||
.height | |||
.max(capabilities.max_image_extent.height), | |||
} | |||
}; | |||
let format = | |||
*formats | |||
.iter() | |||
.find(|format| { | |||
format.format == vk::Format::B8G8R8A8_UNORM && | |||
format.color_space == vk::ColorSpaceKHR::SRGB_NONLINEAR | |||
}) | |||
.unwrap_or_else(|| &formats[0]); | |||
let mode = | |||
*modes | |||
.iter() | |||
.find(|&&mode| mode == conf.swap_mode.into()) | |||
.unwrap_or_else(|| &vk::PresentModeKHR::FIFO); | |||
let min_image_count = capabilities.min_image_count + 1; | |||
let min_image_count = | |||
if capabilities.max_image_count > 0 && | |||
min_image_count > capabilities.max_image_count | |||
{ | |||
capabilities.max_image_count | |||
} else { | |||
min_image_count | |||
}; | |||
let queue_family_indices = [qf_info.gfx_index, qf_info.srf_index]; | |||
let create_info = vk::SwapchainCreateInfoKHR { | |||
min_image_count, | |||
present_mode: mode, | |||
image_extent: extent, | |||
surface: **surface, | |||
image_format: format.format, | |||
image_color_space: format.color_space, | |||
image_array_layers: 1, | |||
image_usage: vk::ImageUsageFlags::COLOR_ATTACHMENT, | |||
image_sharing_mode: if qf_info.gfx_index != qf_info.srf_index { | |||
vk::SharingMode::CONCURRENT | |||
} else { | |||
vk::SharingMode::EXCLUSIVE | |||
}, | |||
queue_family_index_count: queue_family_indices.len().try_into()?, | |||
p_queue_family_indices: queue_family_indices.as_ptr(), | |||
pre_transform: capabilities.current_transform, | |||
composite_alpha: vk::CompositeAlphaFlagsKHR::OPAQUE, | |||
clipped: vk::TRUE, | |||
..Default::default() | |||
}; | |||
let handle = unsafe { | |||
device.swapchain_ext.create_swapchain(&create_info, None)? | |||
}; | |||
Ok(Self { device, handle, format: format.format, extent }) | |||
} | |||
pub fn get_images(&self) -> err::Result<Vec<vk::Image>> { | |||
let images = unsafe { | |||
self.device.swapchain_ext.get_swapchain_images(self.handle)? | |||
}; | |||
Ok(images) | |||
} | |||
} | |||
impl Drop for Swapchain<'_, '_> { | |||
fn drop(&mut self) { | |||
unsafe { self.device.swapchain_ext.destroy_swapchain(self.handle, None); } | |||
} | |||
} | |||
impl std::ops::Deref for Swapchain<'_, '_> { | |||
type Target = vk::SwapchainKHR; | |||
fn deref(&self) -> &Self::Target { &self.handle } | |||
} | |||
// EOF |
@@ -0,0 +1,85 @@ | |||
#![allow(dead_code)] | |||
#[macro_export] | |||
macro_rules! lg { | |||
($to:expr, $level:ident, $($args:tt)+) => { | |||
{ | |||
let level = $crate::util::log::Level::$level; | |||
let to = $to; | |||
if to.enabled(level) { | |||
let record = $crate::util::log::Record { | |||
level, | |||
module: std::module_path!(), | |||
file: std::file!(), | |||
line: std::line!(), | |||
column: std::column!(), | |||
}; | |||
to.log(std::format_args!($($args)+), record); | |||
} | |||
} | |||
}; | |||
} | |||
#[macro_export] | |||
macro_rules! trace { | |||
($to:expr) => { | |||
#[cfg(debug_assertions)] | |||
{ | |||
$crate::lg!($to, Trace, ""); | |||
} | |||
}; | |||
($to:expr, $val:expr) => { | |||
#[cfg(debug_assertions)] | |||
{ | |||
let val = $val; | |||
$crate::lg!($to, Trace, "{} = {:#?}", std::stringify!($val), &val); | |||
val | |||
} | |||
}; | |||
($to:expr, $($val:expr),+ $(,)?) => { | |||
($($crate::trace!($to, $val)),+,) | |||
}; | |||
} | |||
/// Macro-izes `#[doc = x]`. | |||
#[macro_export] | |||
macro_rules! doc_comment { | |||
($x:expr, $($tt:tt)*) => { | |||
#[doc = $x] | |||
$($tt)* | |||
}; | |||
} | |||
/// Creates a C string from a literal. | |||
#[macro_export] | |||
macro_rules! c_str { | |||
($s:expr) => { | |||
std::concat!($s, "\0").as_ptr() as *const std::os::raw::c_char | |||
}; | |||
} | |||
#[macro_export] | |||
macro_rules! serialize { | |||
(conf_enum: $($tt:tt)*) => { | |||
#[derive(serde::Deserialize, serde::Serialize)] | |||
#[serde(deny_unknown_fields)] | |||
$($tt)* | |||
}; | |||
(conf: $($tt:tt)*) => { | |||
#[derive(serde::Deserialize, serde::Serialize)] | |||
#[serde(rename_all = "kebab-case", deny_unknown_fields, default)] | |||
$($tt)* | |||
}; | |||
} | |||
pub mod err; | |||
pub mod ffi; | |||
pub mod meta; | |||
pub mod log; | |||
// EOF |
@@ -0,0 +1,12 @@ | |||
pub use failure::err_msg as static_msg; | |||
use failure::Error; | |||
pub type Result<T> = std::result::Result<T, Error>; | |||
pub fn from_s<T>(v: std::result::Result<T, String>) -> Result<T> { | |||
match v { | |||
Ok(v) => Ok(v), | |||
Err(e) => Err(static_msg(e)), | |||
} | |||
} |
@@ -0,0 +1,65 @@ | |||
use crate::util::err; | |||
use std::{ffi::CString, os::raw::c_char, ptr::null}; | |||
/// An owned null-terminated string vector. | |||
#[derive(Debug)] | |||
pub struct CStringVec { | |||
sv: Vec<CString>, | |||
cv: Vec<*const c_char>, | |||
} | |||
impl Default for CStringVec { | |||
/// Creates a new empty CStringVec. | |||
#[inline] | |||
fn default() -> Self { | |||
Self { | |||
sv: Vec::new(), | |||
cv: vec![null()], | |||
} | |||
} | |||
} | |||
#[allow(dead_code)] | |||
impl CStringVec { | |||
/// Creates a new `CStringVec` from an iterator. | |||
#[inline] | |||
pub fn new_from_iter<'a, I>(it: I) -> err::Result<Self> | |||
where | |||
I: Iterator<Item = &'a str>, | |||
{ | |||
let mut v = Self::default(); | |||
for st in it { | |||
v.push(CString::new(st)?); | |||
} | |||
Ok(v) | |||
} | |||
/// Pushes a new `CString`. | |||
#[inline] | |||
pub fn push(&mut self, st: CString) { | |||
self.cv.insert(self.cv.len() - 1, st.as_ptr()); | |||
self.sv.push(st); | |||
} | |||
/// Returns the FFI pointer. | |||
#[inline] | |||
pub fn as_ptr(&self) -> *const *const c_char { | |||
self.cv.as_ptr() | |||
} | |||
/// Returns the FFI pointer mutably. | |||
#[inline] | |||
pub fn as_mut_ptr(&mut self) -> *mut *const c_char { | |||
self.cv.as_mut_ptr() | |||
} | |||
/// Returns the length of the vector. | |||
#[inline] | |||
pub fn len(&self) -> usize { | |||
self.sv.len() | |||
} | |||
} | |||
// EOF |
@@ -0,0 +1,84 @@ | |||
mod color; | |||
mod conf; | |||
mod level; | |||
mod standard; | |||
pub use self::{color::Color, conf::Conf, level::Level, standard::Std}; | |||
use std::collections::HashMap; | |||
pub struct Record { | |||
pub level: Level, | |||
pub module: &'static str, | |||
pub file: &'static str, | |||
pub line: u32, | |||
pub column: u32, | |||
} | |||
pub struct Log { | |||
level: Level, | |||
begin: std::time::Instant, | |||
modes: HashMap<&'static str, Box<dyn LogMode>>, | |||
mode: &'static str, | |||
} | |||
pub trait LogMode { | |||
fn log(&self, args: std::fmt::Arguments, record: Record, log: &Log); | |||
} | |||
impl Log { | |||
pub fn new() -> Self { | |||
Self { | |||
modes: HashMap::new(), | |||
level: Level::default(), | |||
begin: std::time::Instant::now(), | |||
mode: "Std", | |||
} | |||
} | |||
pub fn enabled(&self, level: Level) -> bool { | |||
level <= self.level | |||
} | |||
pub fn insert_mode<M>(mut self, name: &'static str, mode: M) -> Self | |||
where | |||
M: LogMode + 'static, | |||
{ | |||
self.modes.insert(name, Box::new(mode)); | |||
self | |||
} | |||
pub fn set_mode(mut self, name: &'static str) -> Result<Self, ()> { | |||
if self.modes.contains_key(name) { | |||
self.mode = name; | |||
Ok(self) | |||
} else { | |||
Err(()) | |||
} | |||
} | |||
pub fn log(&self, args: std::fmt::Arguments, record: Record) { | |||
self.modes[self.mode].log(args, record, self); | |||
} | |||
pub fn write_into( | |||
&self, | |||
args: std::fmt::Arguments, | |||
record: Record, | |||
out: &mut impl std::io::Write, | |||
) -> std::io::Result<()> { | |||
writeln!( | |||
out, | |||
"[{}:{}:{}:{}][{}ms] {}", | |||
record.file, | |||
record.line, | |||
record.column, | |||
record.module, | |||
self.begin.elapsed().as_millis(), | |||
args, | |||
)?; | |||
Ok(()) | |||
} | |||
} | |||
// EOF |
@@ -0,0 +1,36 @@ | |||
#![cfg(feature = "color-log")] | |||
use crate::util::log::{Level, LogMode, Log, Record}; | |||
use termcolor::WriteColor; | |||
pub struct Color; | |||
impl Color { | |||
fn write_level( | |||
&self, | |||
level: Level, | |||
out: &mut impl WriteColor, | |||
) -> std::io::Result<()> { | |||
out.write(b"[")?; | |||
out.set_color(&level.color())?; | |||
write!(out, "{:^8}", level.name())?; | |||
out.reset()?; | |||
out.write(b"]")?; | |||
Ok(()) | |||
} | |||
} | |||
impl LogMode for Color { | |||
fn log(&self, args: std::fmt::Arguments, record: Record, log: &Log) { | |||
let color = termcolor::ColorChoice::Auto; | |||
let mut out = if record.level <= Level::Warning { | |||
termcolor::StandardStream::stderr(color) | |||
} else { | |||
termcolor::StandardStream::stdout(color) | |||
}; | |||
let _ = self.write_level(record.level, &mut out); | |||
let _ = log.write_into(args, record, &mut out); | |||
} | |||
} | |||
// EOF |
@@ -0,0 +1,14 @@ | |||
use crate::util::log::Level; | |||
serialize! { | |||
conf: | |||
pub struct Conf { | |||
pub level: Level, | |||
} | |||
} | |||
impl Default for Conf { | |||
fn default() -> Self { Self { level: Level::default() } } | |||
} | |||
// EOF |
@@ -0,0 +1,68 @@ | |||
serialize! { | |||
conf_enum: | |||
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)] | |||
pub enum Level { | |||
/// Errata that will imminently crash the program. | |||
Critical, | |||
/// Errata that will cause issues. | |||
Error, | |||
/// Errata that can be ignored but should be fixed. | |||
Warning, | |||
/// Information the user will want to read. | |||
Notice, | |||
/// Information that is strictly non-essential. | |||
Info, | |||
/// Debugging information available in all builds. | |||
Debug, | |||
/// Debugging information only available in debug builds. | |||
#[cfg(debug_assertions)] | |||
Trace, | |||
} | |||
} | |||
impl Default for Level { | |||
fn default() -> Self { | |||
#[cfg(debug_assertions)] | |||
let level = Self::Trace; | |||
#[cfg(not(debug_assertions))] | |||
let level = Self::Notice; | |||
level | |||
} | |||
} | |||
impl Level { | |||
pub fn name(self) -> &'static str { | |||
match self { | |||
Self::Critical => "Critical", | |||
Self::Error => "Error", | |||
Self::Warning => "Warning", | |||
Self::Notice => "Notice", | |||
Self::Info => "Info", | |||
Self::Debug => "Debug", | |||
#[cfg(debug_assertions)] | |||
Self::Trace => "Trace", | |||
} | |||
} | |||
#[cfg(feature = "color-log")] | |||
pub fn color(self) -> termcolor::ColorSpec { | |||
use termcolor::Color; | |||
let mut color = termcolor::ColorSpec::new(); | |||
match self { | |||
Self::Critical => color.set_fg(Some(Color::Red)).set_intense(true), | |||
Self::Error => color.set_fg(Some(Color::Red)), | |||
Self::Warning => color.set_fg(Some(Color::Yellow)).set_intense(true), | |||
Self::Notice => color.set_fg(Some(Color::Yellow)), | |||
Self::Info => color.set_fg(Some(Color::Cyan)).set_intense(true), | |||
Self::Debug => color.set_fg(Some(Color::Cyan)), | |||
#[cfg(debug_assertions)] | |||
Self::Trace => color.set_fg(Some(Color::Magenta)).set_intense(true), | |||
}; | |||
color | |||
} | |||
} | |||
// EOF |
@@ -0,0 +1,30 @@ | |||
use crate::util::log::{Level, LogMode, Log, Record}; | |||
use std::io::Write; | |||
pub struct Std; | |||
impl Std { | |||
fn write_into( | |||
&self, | |||
args: std::fmt::Arguments, | |||
record: Record, | |||
log: &Log, | |||
out: &mut impl Write, | |||
) -> std::io::Result<()> { | |||
write!(out, "[{:^8}]", record.level.name())?; | |||
log.write_into(args, record, out)?; | |||
Ok(()) | |||
} | |||
} | |||
impl LogMode for Std { | |||
fn log(&self, args: std::fmt::Arguments, record: Record, log: &Log) { | |||
let _ = if record.level <= Level::Warning { | |||
self.write_into(args, record, log, &mut std::io::stderr()) | |||
} else { | |||
self.write_into(args, record, log, &mut std::io::stdout()) | |||
}; | |||
} | |||
} | |||
// EOF |
@@ -0,0 +1,51 @@ | |||
//! Meta-information of this crate. | |||
macro_rules! meta_str { | |||
($($(#[$outer:meta])* $name:ident = $e:expr;)*) => { | |||
$($(#[$outer])* pub const fn $name() -> &'static str { $e })* | |||
pub mod ffi | |||
{ | |||
use std::os::raw::c_char; | |||
$( | |||
$crate::doc_comment! { | |||
concat!("FFI variant of [`", | |||
stringify!($name), | |||
"`]\n\n[`", | |||
stringify!($name), | |||
"`]: ../fn.", | |||
stringify!($name), | |||
".html"), | |||
pub const fn $name() -> *const c_char { c_str!($e) } | |||
} | |||
)* | |||
} | |||
} | |||
} | |||
meta_str! { | |||
/// The authors of this crate, `:` separated. | |||
authors = env!("CARGO_PKG_AUTHORS"); | |||
/// The description of this crate. | |||
description = env!("CARGO_PKG_DESCRIPTION"); | |||
/// The home page of this crate. | |||
homepage = env!("CARGO_PKG_HOMEPAGE"); | |||
/// The full license text of this crate. | |||
license_text = include_str!("../../LICENSE"); | |||
/// The name of this crate. | |||
name = env!("CARGO_PKG_NAME"); | |||
/// The repository of this crate. | |||
repository = env!("CARGO_PKG_REPOSITORY"); | |||
/// The full version of this crate. | |||
version = env!("CARGO_PKG_VERSION"); | |||
/// The major version of this crate. | |||
version_major = env!("CARGO_PKG_VERSION_MAJOR"); | |||
/// The minor version of this crate. | |||
version_minor = env!("CARGO_PKG_VERSION_MINOR"); | |||
/// The patch version of this crate. | |||
version_patch = env!("CARGO_PKG_VERSION_PATCH"); | |||
/// The pre-release version of this crate. | |||
version_pre = env!("CARGO_PKG_VERSION_PRE"); | |||
} | |||
// EOF |