Initial release of Lumen Wallpaper

This commit is contained in:
emil28092005
2026-10-25 17:29:16 +03:00
commit 2a77fc6546
31 changed files with 3393 additions and 0 deletions
+31
View File
@@ -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
}
}
}
+41
View File
@@ -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>()];
+217
View File
@@ -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()
}
+135
View File
@@ -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}"))
}
+371
View File
@@ -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}")
}