Initial release of Lumen Wallpaper
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
use lumen_renderer::shader::compile_shader;
|
||||
use std::process::ExitCode;
|
||||
|
||||
fn main() -> ExitCode {
|
||||
let Some(path) = std::env::args().nth(1) else {
|
||||
eprintln!("Usage: lumen-shader-check <shader.frag>");
|
||||
return ExitCode::from(2);
|
||||
};
|
||||
let source = match std::fs::read_to_string(&path) {
|
||||
Ok(source) => source,
|
||||
Err(error) => {
|
||||
eprintln!("Cannot read {path}: {error}");
|
||||
return ExitCode::FAILURE;
|
||||
}
|
||||
};
|
||||
|
||||
match compile_shader(&source) {
|
||||
Ok(shader) => {
|
||||
println!(
|
||||
"{path}: valid (vertex: {} words, fragment: {} words)",
|
||||
shader.vertex.len(),
|
||||
shader.fragment.len()
|
||||
);
|
||||
ExitCode::SUCCESS
|
||||
}
|
||||
Err(error) => {
|
||||
eprintln!("{error}");
|
||||
ExitCode::FAILURE
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
use std::ffi::c_void;
|
||||
|
||||
#[repr(C, align(16))]
|
||||
#[derive(Clone, Copy, Debug, Pod, Zeroable)]
|
||||
pub struct LumenUniforms {
|
||||
pub mvp: [f32; 16],
|
||||
pub resolution: [f32; 3],
|
||||
pub time: f32,
|
||||
pub mouse: [f32; 4],
|
||||
pub item_size: [f32; 2],
|
||||
pub time_delta: f32,
|
||||
pub frame_rate: f32,
|
||||
pub date: [f32; 4],
|
||||
pub frame: i32,
|
||||
pub screen_index: i32,
|
||||
pub screen_count: i32,
|
||||
pub sample_rate: f32,
|
||||
pub screen_offset: [f32; 2],
|
||||
pub desktop_resolution: [f32; 2],
|
||||
}
|
||||
|
||||
const _: [(); 160] = [(); std::mem::size_of::<LumenUniforms>()];
|
||||
|
||||
#[repr(C)]
|
||||
pub struct LumenFrame {
|
||||
pub instance: *mut c_void,
|
||||
pub physical_device: *mut c_void,
|
||||
pub device: *mut c_void,
|
||||
pub command_buffer: *mut c_void,
|
||||
pub render_pass: u64,
|
||||
pub uniforms: LumenUniforms,
|
||||
pub viewport: [f32; 4],
|
||||
pub scissor: [i32; 4],
|
||||
pub sample_count: u32,
|
||||
pub frame_slot: u32,
|
||||
pub frames_in_flight: u32,
|
||||
pub reserved: u32,
|
||||
}
|
||||
|
||||
const _: [(); 256] = [(); std::mem::size_of::<LumenFrame>()];
|
||||
@@ -0,0 +1,217 @@
|
||||
pub mod frame;
|
||||
pub mod shader;
|
||||
mod vulkan;
|
||||
|
||||
use crate::frame::LumenFrame;
|
||||
use crate::shader::{compile_shader, CompiledShader};
|
||||
use crate::vulkan::Gpu;
|
||||
use ash::vk::Handle;
|
||||
use std::ffi::{c_char, c_int, CStr, CString};
|
||||
use std::panic::{catch_unwind, AssertUnwindSafe};
|
||||
|
||||
pub struct Renderer {
|
||||
source: String,
|
||||
compiled: Option<CompiledShader>,
|
||||
source_dirty: bool,
|
||||
source_failed: bool,
|
||||
pipeline_dirty: bool,
|
||||
blocked: bool,
|
||||
gpu: Option<Gpu>,
|
||||
error: CString,
|
||||
}
|
||||
|
||||
impl Renderer {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
source: String::new(),
|
||||
compiled: None,
|
||||
source_dirty: true,
|
||||
source_failed: false,
|
||||
pipeline_dirty: false,
|
||||
blocked: false,
|
||||
gpu: None,
|
||||
error: CString::new("No shader loaded").unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
fn set_shader(&mut self, source: &str) {
|
||||
if self.source == source {
|
||||
return;
|
||||
}
|
||||
self.source = source.to_owned();
|
||||
self.source_dirty = true;
|
||||
self.blocked = false;
|
||||
self.clear_error();
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
self.gpu = None;
|
||||
self.pipeline_dirty = true;
|
||||
self.blocked = false;
|
||||
}
|
||||
|
||||
fn set_error(&mut self, message: impl Into<String>) {
|
||||
let message = message.into().replace('\0', " ");
|
||||
self.error =
|
||||
CString::new(message).unwrap_or_else(|_| CString::new("Renderer error").unwrap());
|
||||
}
|
||||
|
||||
fn clear_error(&mut self) {
|
||||
self.error = CString::default();
|
||||
}
|
||||
|
||||
fn render(&mut self, frame: &LumenFrame) -> Result<bool, String> {
|
||||
if self.source_dirty {
|
||||
self.source_dirty = false;
|
||||
match compile_shader(&self.source) {
|
||||
Ok(compiled) => {
|
||||
self.compiled = Some(compiled);
|
||||
self.source_failed = false;
|
||||
self.pipeline_dirty = true;
|
||||
self.blocked = false;
|
||||
self.clear_error();
|
||||
}
|
||||
Err(error) => {
|
||||
self.source_failed = true;
|
||||
self.set_error(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if self.blocked && self.gpu.is_none() {
|
||||
return Err(self.error.to_string_lossy().into_owned());
|
||||
}
|
||||
|
||||
let Some(compiled) = self.compiled.clone() else {
|
||||
return Err(self.error.to_string_lossy().into_owned());
|
||||
};
|
||||
|
||||
let device_handle = frame.device as usize as u64;
|
||||
let render_pass = ash::vk::RenderPass::from_raw(frame.render_pass);
|
||||
let samples = crate::vulkan::sample_count_flags(frame.sample_count)?;
|
||||
let frames_in_flight = (frame.frames_in_flight.max(1) as usize).min(16);
|
||||
let resources_changed = self.gpu.as_ref().is_some_and(|gpu| {
|
||||
gpu.device_handle.as_raw() != device_handle
|
||||
|| gpu.frames_in_flight != frames_in_flight
|
||||
|| gpu.samples != samples
|
||||
});
|
||||
if resources_changed {
|
||||
self.reset();
|
||||
}
|
||||
|
||||
if self.gpu.is_none() {
|
||||
match unsafe { Gpu::new(frame) } {
|
||||
Ok(gpu) => {
|
||||
self.gpu = Some(gpu);
|
||||
self.pipeline_dirty = true;
|
||||
}
|
||||
Err(error) => {
|
||||
self.blocked = true;
|
||||
self.set_error(format!("Vulkan initialization failed: {error}"));
|
||||
return Err(self.error.to_string_lossy().into_owned());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
let gpu = self.gpu.as_mut().unwrap();
|
||||
if gpu.render_pass != render_pass {
|
||||
gpu.render_pass = render_pass;
|
||||
self.pipeline_dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
if self.pipeline_dirty {
|
||||
let result = unsafe { self.gpu.as_mut().unwrap().create_pipeline(&compiled) };
|
||||
match result {
|
||||
Ok(()) => {
|
||||
self.pipeline_dirty = false;
|
||||
if !self.source_failed {
|
||||
self.clear_error();
|
||||
}
|
||||
}
|
||||
Err(error) => {
|
||||
self.set_error(format!("Vulkan pipeline creation failed: {error}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let gpu = self.gpu.as_ref().unwrap();
|
||||
if gpu.pipeline.is_null()
|
||||
|| gpu.pipeline_render_pass != render_pass
|
||||
|| gpu.pipeline_samples != samples
|
||||
{
|
||||
return Err(self.error.to_string_lossy().into_owned());
|
||||
}
|
||||
|
||||
unsafe { gpu.draw(frame) };
|
||||
Ok(!self.error.to_bytes().is_empty())
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn lumen_renderer_create() -> *mut Renderer {
|
||||
Box::into_raw(Box::new(Renderer::new()))
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn lumen_renderer_destroy(renderer: *mut Renderer) {
|
||||
if !renderer.is_null() {
|
||||
drop(Box::from_raw(renderer));
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn lumen_renderer_set_shader(renderer: *mut Renderer, source: *const c_char) {
|
||||
if renderer.is_null() || source.is_null() {
|
||||
return;
|
||||
}
|
||||
let result = catch_unwind(AssertUnwindSafe(|| {
|
||||
let source = CStr::from_ptr(source).to_string_lossy();
|
||||
(*renderer).set_shader(&source);
|
||||
}));
|
||||
if result.is_err() {
|
||||
(*renderer).set_error("Panic while updating the shader");
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn lumen_renderer_reset(renderer: *mut Renderer) {
|
||||
if let Some(renderer) = renderer.as_mut() {
|
||||
let result = catch_unwind(AssertUnwindSafe(|| renderer.reset()));
|
||||
if result.is_err() {
|
||||
renderer.set_error("Panic while releasing Vulkan resources");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn lumen_renderer_render(
|
||||
renderer: *mut Renderer,
|
||||
frame: *const LumenFrame,
|
||||
) -> c_int {
|
||||
if renderer.is_null() || frame.is_null() {
|
||||
return -1;
|
||||
}
|
||||
let renderer = &mut *renderer;
|
||||
match catch_unwind(AssertUnwindSafe(|| renderer.render(&*frame))) {
|
||||
Ok(Ok(true)) => 1,
|
||||
Ok(Ok(false)) => 0,
|
||||
Ok(Err(error)) => {
|
||||
renderer.set_error(error);
|
||||
-1
|
||||
}
|
||||
Err(_) => {
|
||||
renderer.set_error("Panic in the Vulkan renderer");
|
||||
-1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn lumen_renderer_error(renderer: *const Renderer) -> *const c_char {
|
||||
if renderer.is_null() {
|
||||
return c"Invalid renderer handle".as_ptr();
|
||||
}
|
||||
(*renderer).error.as_ptr()
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
use naga::back::spv;
|
||||
use naga::front::glsl::{Frontend, Options as GlslOptions};
|
||||
use naga::valid::{Capabilities, ValidationFlags, Validator};
|
||||
use naga::ShaderStage;
|
||||
|
||||
const UNIFORMS: &str = r#"
|
||||
layout(std140, set = 0, binding = 0) uniform LumenUniforms {
|
||||
mat4 lwMvp;
|
||||
vec3 iResolution;
|
||||
float iTime;
|
||||
vec4 iMouse;
|
||||
vec2 lwItemSize;
|
||||
float iTimeDelta;
|
||||
float iFrameRate;
|
||||
vec4 iDate;
|
||||
int iFrame;
|
||||
int iScreenIndex;
|
||||
int iScreenCount;
|
||||
float iSampleRate;
|
||||
vec2 iScreenOffset;
|
||||
vec2 iDesktopResolution;
|
||||
};
|
||||
"#;
|
||||
|
||||
const VERTEX_PREFIX: &str = r#"#version 450
|
||||
layout(location = 0) out vec2 lwUv;
|
||||
"#;
|
||||
|
||||
const VERTEX_MAIN: &str = r#"
|
||||
void main() {
|
||||
vec2 position = vec2(
|
||||
float((gl_VertexIndex << 1) & 2),
|
||||
float(gl_VertexIndex & 2)
|
||||
);
|
||||
lwUv = position;
|
||||
gl_Position = lwMvp * vec4(position * lwItemSize, 0.0, 1.0);
|
||||
}
|
||||
"#;
|
||||
|
||||
const FRAGMENT_PREFIX: &str = r#"#version 450
|
||||
layout(location = 0) in vec2 lwUv;
|
||||
layout(location = 0) out vec4 fragColor;
|
||||
"#;
|
||||
|
||||
const FRAGMENT_MAIN: &str = r#"
|
||||
void main() {
|
||||
vec2 fragCoord = vec2(lwUv.x * iResolution.x, (1.0 - lwUv.y) * iResolution.y);
|
||||
vec4 color = vec4(0.0, 0.0, 0.0, 1.0);
|
||||
mainImage(color, fragCoord);
|
||||
fragColor = color;
|
||||
}
|
||||
"#;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct CompiledShader {
|
||||
pub vertex: Vec<u32>,
|
||||
pub fragment: Vec<u32>,
|
||||
}
|
||||
|
||||
pub fn compile_shader(user_source: &str) -> Result<CompiledShader, String> {
|
||||
let user_source = sanitize_user_source(user_source);
|
||||
let vertex_source = format!("{VERTEX_PREFIX}{UNIFORMS}{VERTEX_MAIN}");
|
||||
let fragment_source = format!("{FRAGMENT_PREFIX}{UNIFORMS}\n{user_source}\n{FRAGMENT_MAIN}");
|
||||
|
||||
Ok(CompiledShader {
|
||||
vertex: compile_stage(&vertex_source, ShaderStage::Vertex)?,
|
||||
fragment: compile_stage(&fragment_source, ShaderStage::Fragment)?,
|
||||
})
|
||||
}
|
||||
|
||||
fn sanitize_user_source(source: &str) -> String {
|
||||
source
|
||||
.lines()
|
||||
.map(|line| {
|
||||
let trimmed = line.trim_start();
|
||||
if trimmed.starts_with("#version") || trimmed.starts_with("precision ") {
|
||||
""
|
||||
} else {
|
||||
line
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
}
|
||||
|
||||
fn compile_stage(source: &str, stage: ShaderStage) -> Result<Vec<u32>, String> {
|
||||
let stage_name = match stage {
|
||||
ShaderStage::Vertex => "vertex",
|
||||
ShaderStage::Fragment => "fragment",
|
||||
ShaderStage::Compute => "compute",
|
||||
_ => "unsupported",
|
||||
};
|
||||
|
||||
let module = Frontend::default()
|
||||
.parse(&GlslOptions::from(stage), source)
|
||||
.map_err(|errors| {
|
||||
format!(
|
||||
"GLSL {stage_name} compilation failed:\n{}",
|
||||
errors.emit_to_string(source)
|
||||
)
|
||||
})?;
|
||||
|
||||
let info = Validator::new(ValidationFlags::all(), Capabilities::default())
|
||||
.validate(&module)
|
||||
.map_err(|error| {
|
||||
format!(
|
||||
"GLSL {stage_name} validation failed:\n{}",
|
||||
error.emit_to_string_with_path(source, "glsl")
|
||||
)
|
||||
})?;
|
||||
|
||||
let mut options = spv::Options::default();
|
||||
options.fake_missing_bindings = false;
|
||||
options.binding_map.insert(
|
||||
naga::ResourceBinding {
|
||||
group: 0,
|
||||
binding: 0,
|
||||
},
|
||||
spv::BindingInfo {
|
||||
descriptor_set: 0,
|
||||
binding: 0,
|
||||
binding_array_size: None,
|
||||
},
|
||||
);
|
||||
options
|
||||
.flags
|
||||
.remove(spv::WriterFlags::ADJUST_COORDINATE_SPACE);
|
||||
let pipeline = spv::PipelineOptions {
|
||||
shader_stage: stage,
|
||||
entry_point: "main".to_owned(),
|
||||
};
|
||||
|
||||
spv::write_vec(&module, &info, &options, Some(&pipeline))
|
||||
.map_err(|error| format!("SPIR-V {stage_name} generation failed: {error}"))
|
||||
}
|
||||
@@ -0,0 +1,371 @@
|
||||
use crate::frame::LumenFrame;
|
||||
use crate::shader::CompiledShader;
|
||||
use ash::vk::{self, Handle};
|
||||
use ash::{Device, Entry, Instance};
|
||||
use std::ffi::{c_void, CStr};
|
||||
use std::mem::size_of;
|
||||
|
||||
pub struct Gpu {
|
||||
_entry: Entry,
|
||||
instance: Instance,
|
||||
device: Device,
|
||||
physical_device: vk::PhysicalDevice,
|
||||
pub device_handle: vk::Device,
|
||||
pub render_pass: vk::RenderPass,
|
||||
pub pipeline_render_pass: vk::RenderPass,
|
||||
descriptor_set_layout: vk::DescriptorSetLayout,
|
||||
pipeline_layout: vk::PipelineLayout,
|
||||
descriptor_pool: vk::DescriptorPool,
|
||||
descriptor_set: vk::DescriptorSet,
|
||||
uniform_buffer: vk::Buffer,
|
||||
uniform_memory: vk::DeviceMemory,
|
||||
uniform_mapped: *mut c_void,
|
||||
pub uniform_stride: u64,
|
||||
pub frames_in_flight: usize,
|
||||
pub samples: vk::SampleCountFlags,
|
||||
pub pipeline_samples: vk::SampleCountFlags,
|
||||
pub pipeline: vk::Pipeline,
|
||||
}
|
||||
|
||||
impl Gpu {
|
||||
pub unsafe fn new(frame: &LumenFrame) -> Result<Self, String> {
|
||||
if frame.instance.is_null()
|
||||
|| frame.physical_device.is_null()
|
||||
|| frame.device.is_null()
|
||||
|| frame.command_buffer.is_null()
|
||||
|| frame.render_pass == 0
|
||||
{
|
||||
return Err("Qt did not provide all required Vulkan handles".to_owned());
|
||||
}
|
||||
|
||||
let entry = Entry::load().map_err(|error| error.to_string())?;
|
||||
let instance_handle = vk::Instance::from_raw(frame.instance as usize as u64);
|
||||
let instance = Instance::load(entry.static_fn(), instance_handle);
|
||||
let device_handle = vk::Device::from_raw(frame.device as usize as u64);
|
||||
let device = Device::load(instance.fp_v1_0(), device_handle);
|
||||
let physical_device = vk::PhysicalDevice::from_raw(frame.physical_device as usize as u64);
|
||||
let properties = instance.get_physical_device_properties(physical_device);
|
||||
let alignment = properties.limits.min_uniform_buffer_offset_alignment.max(1);
|
||||
let uniform_size = size_of::<crate::frame::LumenUniforms>() as u64;
|
||||
let uniform_stride = uniform_size.div_ceil(alignment) * alignment;
|
||||
let samples = sample_count_flags(frame.sample_count)?;
|
||||
|
||||
let mut gpu = Self {
|
||||
_entry: entry,
|
||||
instance,
|
||||
device,
|
||||
physical_device,
|
||||
device_handle,
|
||||
render_pass: vk::RenderPass::from_raw(frame.render_pass),
|
||||
pipeline_render_pass: vk::RenderPass::null(),
|
||||
descriptor_set_layout: vk::DescriptorSetLayout::null(),
|
||||
pipeline_layout: vk::PipelineLayout::null(),
|
||||
descriptor_pool: vk::DescriptorPool::null(),
|
||||
descriptor_set: vk::DescriptorSet::null(),
|
||||
uniform_buffer: vk::Buffer::null(),
|
||||
uniform_memory: vk::DeviceMemory::null(),
|
||||
uniform_mapped: std::ptr::null_mut(),
|
||||
uniform_stride,
|
||||
frames_in_flight: (frame.frames_in_flight.max(1) as usize).min(16),
|
||||
samples,
|
||||
pipeline_samples: vk::SampleCountFlags::empty(),
|
||||
pipeline: vk::Pipeline::null(),
|
||||
};
|
||||
gpu.create_uniform_resources()?;
|
||||
Ok(gpu)
|
||||
}
|
||||
|
||||
unsafe fn create_uniform_resources(&mut self) -> Result<(), String> {
|
||||
let binding = vk::DescriptorSetLayoutBinding::default()
|
||||
.binding(0)
|
||||
.descriptor_type(vk::DescriptorType::UNIFORM_BUFFER_DYNAMIC)
|
||||
.descriptor_count(1)
|
||||
.stage_flags(vk::ShaderStageFlags::VERTEX | vk::ShaderStageFlags::FRAGMENT);
|
||||
let layout_info =
|
||||
vk::DescriptorSetLayoutCreateInfo::default().bindings(std::slice::from_ref(&binding));
|
||||
self.descriptor_set_layout = self
|
||||
.device
|
||||
.create_descriptor_set_layout(&layout_info, None)
|
||||
.map_err(vk_error)?;
|
||||
|
||||
let layouts = [self.descriptor_set_layout];
|
||||
let pipeline_layout_info = vk::PipelineLayoutCreateInfo::default().set_layouts(&layouts);
|
||||
self.pipeline_layout = self
|
||||
.device
|
||||
.create_pipeline_layout(&pipeline_layout_info, None)
|
||||
.map_err(vk_error)?;
|
||||
|
||||
let pool_size = vk::DescriptorPoolSize::default()
|
||||
.ty(vk::DescriptorType::UNIFORM_BUFFER_DYNAMIC)
|
||||
.descriptor_count(1);
|
||||
let pool_sizes = [pool_size];
|
||||
let pool_info = vk::DescriptorPoolCreateInfo::default()
|
||||
.max_sets(1)
|
||||
.pool_sizes(&pool_sizes);
|
||||
self.descriptor_pool = self
|
||||
.device
|
||||
.create_descriptor_pool(&pool_info, None)
|
||||
.map_err(vk_error)?;
|
||||
|
||||
let allocate_info = vk::DescriptorSetAllocateInfo::default()
|
||||
.descriptor_pool(self.descriptor_pool)
|
||||
.set_layouts(&layouts);
|
||||
self.descriptor_set = self
|
||||
.device
|
||||
.allocate_descriptor_sets(&allocate_info)
|
||||
.map_err(vk_error)?[0];
|
||||
|
||||
let buffer_info = vk::BufferCreateInfo::default()
|
||||
.size(self.uniform_stride * self.frames_in_flight as u64)
|
||||
.usage(vk::BufferUsageFlags::UNIFORM_BUFFER)
|
||||
.sharing_mode(vk::SharingMode::EXCLUSIVE);
|
||||
self.uniform_buffer = self
|
||||
.device
|
||||
.create_buffer(&buffer_info, None)
|
||||
.map_err(vk_error)?;
|
||||
|
||||
let requirements = self
|
||||
.device
|
||||
.get_buffer_memory_requirements(self.uniform_buffer);
|
||||
let properties = self
|
||||
.instance
|
||||
.get_physical_device_memory_properties(self.physical_device);
|
||||
let memory_type = (0..properties.memory_type_count)
|
||||
.find(|index| {
|
||||
let supported = requirements.memory_type_bits & (1 << index) != 0;
|
||||
let flags = properties.memory_types[*index as usize].property_flags;
|
||||
let required =
|
||||
vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT;
|
||||
supported && flags.contains(required)
|
||||
})
|
||||
.ok_or_else(|| "No host-visible coherent memory for the uniform buffer".to_owned())?;
|
||||
let allocate_info = vk::MemoryAllocateInfo::default()
|
||||
.allocation_size(requirements.size)
|
||||
.memory_type_index(memory_type);
|
||||
self.uniform_memory = self
|
||||
.device
|
||||
.allocate_memory(&allocate_info, None)
|
||||
.map_err(vk_error)?;
|
||||
self.device
|
||||
.bind_buffer_memory(self.uniform_buffer, self.uniform_memory, 0)
|
||||
.map_err(vk_error)?;
|
||||
self.uniform_mapped = self
|
||||
.device
|
||||
.map_memory(
|
||||
self.uniform_memory,
|
||||
0,
|
||||
vk::WHOLE_SIZE,
|
||||
vk::MemoryMapFlags::empty(),
|
||||
)
|
||||
.map_err(vk_error)?;
|
||||
|
||||
let buffer_info = vk::DescriptorBufferInfo::default()
|
||||
.buffer(self.uniform_buffer)
|
||||
.offset(0)
|
||||
.range(size_of::<crate::frame::LumenUniforms>() as u64);
|
||||
let buffer_infos = [buffer_info];
|
||||
let write = vk::WriteDescriptorSet::default()
|
||||
.dst_set(self.descriptor_set)
|
||||
.dst_binding(0)
|
||||
.descriptor_type(vk::DescriptorType::UNIFORM_BUFFER_DYNAMIC)
|
||||
.buffer_info(&buffer_infos);
|
||||
self.device.update_descriptor_sets(&[write], &[]);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub unsafe fn create_pipeline(&mut self, shader: &CompiledShader) -> Result<(), String> {
|
||||
let vertex_module = self.create_shader_module(&shader.vertex)?;
|
||||
let fragment_module = match self.create_shader_module(&shader.fragment) {
|
||||
Ok(module) => module,
|
||||
Err(error) => {
|
||||
self.device.destroy_shader_module(vertex_module, None);
|
||||
return Err(error);
|
||||
}
|
||||
};
|
||||
let result = self.create_pipeline_with_modules(vertex_module, fragment_module);
|
||||
self.device.destroy_shader_module(vertex_module, None);
|
||||
self.device.destroy_shader_module(fragment_module, None);
|
||||
|
||||
let new_pipeline = result?;
|
||||
let old_pipeline = self.pipeline;
|
||||
self.pipeline = new_pipeline;
|
||||
self.pipeline_render_pass = self.render_pass;
|
||||
self.pipeline_samples = self.samples;
|
||||
if !old_pipeline.is_null() {
|
||||
self.device.device_wait_idle().map_err(vk_error)?;
|
||||
self.device.destroy_pipeline(old_pipeline, None);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
unsafe fn create_shader_module(&self, words: &[u32]) -> Result<vk::ShaderModule, String> {
|
||||
let info = vk::ShaderModuleCreateInfo::default().code(words);
|
||||
self.device
|
||||
.create_shader_module(&info, None)
|
||||
.map_err(vk_error)
|
||||
}
|
||||
|
||||
unsafe fn create_pipeline_with_modules(
|
||||
&self,
|
||||
vertex_module: vk::ShaderModule,
|
||||
fragment_module: vk::ShaderModule,
|
||||
) -> Result<vk::Pipeline, String> {
|
||||
let entry_name = CStr::from_bytes_with_nul(b"main\0").unwrap();
|
||||
let stages = [
|
||||
vk::PipelineShaderStageCreateInfo::default()
|
||||
.stage(vk::ShaderStageFlags::VERTEX)
|
||||
.module(vertex_module)
|
||||
.name(entry_name),
|
||||
vk::PipelineShaderStageCreateInfo::default()
|
||||
.stage(vk::ShaderStageFlags::FRAGMENT)
|
||||
.module(fragment_module)
|
||||
.name(entry_name),
|
||||
];
|
||||
let vertex_input = vk::PipelineVertexInputStateCreateInfo::default();
|
||||
let input_assembly = vk::PipelineInputAssemblyStateCreateInfo::default()
|
||||
.topology(vk::PrimitiveTopology::TRIANGLE_LIST);
|
||||
let viewport_state = vk::PipelineViewportStateCreateInfo::default()
|
||||
.viewport_count(1)
|
||||
.scissor_count(1);
|
||||
let rasterization = vk::PipelineRasterizationStateCreateInfo::default()
|
||||
.polygon_mode(vk::PolygonMode::FILL)
|
||||
.cull_mode(vk::CullModeFlags::NONE)
|
||||
.front_face(vk::FrontFace::COUNTER_CLOCKWISE)
|
||||
.line_width(1.0);
|
||||
let multisample =
|
||||
vk::PipelineMultisampleStateCreateInfo::default().rasterization_samples(self.samples);
|
||||
let depth_stencil = vk::PipelineDepthStencilStateCreateInfo::default();
|
||||
let color_attachment = vk::PipelineColorBlendAttachmentState::default()
|
||||
.blend_enable(false)
|
||||
.color_write_mask(
|
||||
vk::ColorComponentFlags::R
|
||||
| vk::ColorComponentFlags::G
|
||||
| vk::ColorComponentFlags::B
|
||||
| vk::ColorComponentFlags::A,
|
||||
);
|
||||
let color_attachments = [color_attachment];
|
||||
let color_blend =
|
||||
vk::PipelineColorBlendStateCreateInfo::default().attachments(&color_attachments);
|
||||
let dynamic_states = [vk::DynamicState::VIEWPORT, vk::DynamicState::SCISSOR];
|
||||
let dynamic = vk::PipelineDynamicStateCreateInfo::default().dynamic_states(&dynamic_states);
|
||||
let pipeline_info = vk::GraphicsPipelineCreateInfo::default()
|
||||
.stages(&stages)
|
||||
.vertex_input_state(&vertex_input)
|
||||
.input_assembly_state(&input_assembly)
|
||||
.viewport_state(&viewport_state)
|
||||
.rasterization_state(&rasterization)
|
||||
.multisample_state(&multisample)
|
||||
.depth_stencil_state(&depth_stencil)
|
||||
.color_blend_state(&color_blend)
|
||||
.dynamic_state(&dynamic)
|
||||
.layout(self.pipeline_layout)
|
||||
.render_pass(self.render_pass)
|
||||
.subpass(0);
|
||||
|
||||
self.device
|
||||
.create_graphics_pipelines(vk::PipelineCache::null(), &[pipeline_info], None)
|
||||
.map(|pipelines| pipelines[0])
|
||||
.map_err(|(pipelines, error)| {
|
||||
for pipeline in pipelines {
|
||||
self.device.destroy_pipeline(pipeline, None);
|
||||
}
|
||||
vk_error(error)
|
||||
})
|
||||
}
|
||||
|
||||
pub unsafe fn draw(&self, frame: &LumenFrame) {
|
||||
let slot = frame.frame_slot as usize % self.frames_in_flight;
|
||||
let dynamic_offset = (slot as u64 * self.uniform_stride) as u32;
|
||||
std::ptr::copy_nonoverlapping(
|
||||
&frame.uniforms as *const _ as *const u8,
|
||||
self.uniform_mapped
|
||||
.cast::<u8>()
|
||||
.add(dynamic_offset as usize),
|
||||
size_of::<crate::frame::LumenUniforms>(),
|
||||
);
|
||||
let command_buffer = vk::CommandBuffer::from_raw(frame.command_buffer as usize as u64);
|
||||
self.device.cmd_bind_pipeline(
|
||||
command_buffer,
|
||||
vk::PipelineBindPoint::GRAPHICS,
|
||||
self.pipeline,
|
||||
);
|
||||
self.device.cmd_bind_descriptor_sets(
|
||||
command_buffer,
|
||||
vk::PipelineBindPoint::GRAPHICS,
|
||||
self.pipeline_layout,
|
||||
0,
|
||||
&[self.descriptor_set],
|
||||
&[dynamic_offset],
|
||||
);
|
||||
let viewport = vk::Viewport {
|
||||
x: frame.viewport[0],
|
||||
y: frame.viewport[1],
|
||||
width: frame.viewport[2].max(1.0),
|
||||
height: frame.viewport[3].max(1.0),
|
||||
min_depth: 0.0,
|
||||
max_depth: 1.0,
|
||||
};
|
||||
let scissor = vk::Rect2D {
|
||||
offset: vk::Offset2D {
|
||||
x: frame.scissor[0],
|
||||
y: frame.scissor[1],
|
||||
},
|
||||
extent: vk::Extent2D {
|
||||
width: frame.scissor[2].max(1) as u32,
|
||||
height: frame.scissor[3].max(1) as u32,
|
||||
},
|
||||
};
|
||||
self.device.cmd_set_viewport(command_buffer, 0, &[viewport]);
|
||||
self.device.cmd_set_scissor(command_buffer, 0, &[scissor]);
|
||||
self.device.cmd_draw(command_buffer, 3, 1, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Gpu {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
let _ = self.device.device_wait_idle();
|
||||
if !self.pipeline.is_null() {
|
||||
self.device.destroy_pipeline(self.pipeline, None);
|
||||
}
|
||||
if !self.uniform_mapped.is_null() {
|
||||
self.device.unmap_memory(self.uniform_memory);
|
||||
}
|
||||
if !self.uniform_buffer.is_null() {
|
||||
self.device.destroy_buffer(self.uniform_buffer, None);
|
||||
}
|
||||
if !self.uniform_memory.is_null() {
|
||||
self.device.free_memory(self.uniform_memory, None);
|
||||
}
|
||||
if !self.descriptor_pool.is_null() {
|
||||
self.device
|
||||
.destroy_descriptor_pool(self.descriptor_pool, None);
|
||||
}
|
||||
if !self.pipeline_layout.is_null() {
|
||||
self.device
|
||||
.destroy_pipeline_layout(self.pipeline_layout, None);
|
||||
}
|
||||
if !self.descriptor_set_layout.is_null() {
|
||||
self.device
|
||||
.destroy_descriptor_set_layout(self.descriptor_set_layout, None);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn sample_count_flags(count: u32) -> Result<vk::SampleCountFlags, String> {
|
||||
match count {
|
||||
1 => Ok(vk::SampleCountFlags::TYPE_1),
|
||||
2 => Ok(vk::SampleCountFlags::TYPE_2),
|
||||
4 => Ok(vk::SampleCountFlags::TYPE_4),
|
||||
8 => Ok(vk::SampleCountFlags::TYPE_8),
|
||||
16 => Ok(vk::SampleCountFlags::TYPE_16),
|
||||
32 => Ok(vk::SampleCountFlags::TYPE_32),
|
||||
64 => Ok(vk::SampleCountFlags::TYPE_64),
|
||||
_ => Err(format!("Unsupported Vulkan sample count: {count}")),
|
||||
}
|
||||
}
|
||||
|
||||
fn vk_error(error: vk::Result) -> String {
|
||||
format!("{error}")
|
||||
}
|
||||
Reference in New Issue
Block a user