| Crates.io | vkobject-rs |
| lib.rs | vkobject-rs |
| version | 0.0.4 |
| created_at | 2025-09-29 14:03:12.526723+00 |
| updated_at | 2025-11-06 11:56:20.228373+00 |
| description | The Vulkan object wrappers for Rust |
| homepage | |
| repository | https://github.com/0xAA55/vkobject-rs |
| max_upload_size | |
| id | 1859575 |
| size | 1,980,707 |
简体中文 | Chinglish
cargo add vkobject-rs
VulkanContextcreate_vulkan_context() with your GLFW window.pub fn draw() in the Example code section.There are some kinds of buffers in this crate:
BufferWithType<T>: A wrapper for Buffer, mainly for the data that won't be modified frequently, so its staging buffer could be discarded.BufferVec<T>: A wrapper for Buffer, provides an interface that is like a Vec<T>, call flush() could upload data to GPU with a command buffer.
flush() will only upload the modified part of the data to the GPU. The data updation is incremental, minimizing the bandwidth usage of CPU-GPU data transfer.UniformBuffer<T>: A wrapper for Buffer, whose data on the CPU side could be dereferenced into a structure, and you can modify the structure members freely.
flush() will upload the whole structure to the GPU side.GenericUniformBuffer: A wrapper for UniformBuffer<T> that erases the generic type <T>.StorageBuffer<T>: A wrapper for Buffer and the usage is the same as UniformBuffer<T> except it's for the shader's storage buffer inputs.
GenericStorageBuffer: A wrapper for StorageBuffer<T> that erases the generic type <T>.StagingBuffer: The staging buffer that's totally transparent to your CPU, you can have its data pointer and modify the data for it to upload to the GPU.
Buffer: The buffer that's transparent to the GPU, and has its own staging buffer for uploading data to the GPU.
upload_staging_buffer() to enqueue an upload command into a command buffer.discard_staging_buffer() to save some system memory if you wish.VulkanBuffer: The most low-level buffer wrapping object, the Buffer and StagingBuffer are implemented by using this object.
A mesh has 4 buffers. According to the usage, they are:
The buffers for a mesh have two types:
BufferWithType<T>
BufferVec<T>
Vec<T>, then call flush() to apply changes to the GPU buffer.#[derive(Debug, Clone)]
pub struct Mesh<BV, V, BE, E, BI, I, BC, C>
where
BV: BufferForDraw<V>,
BE: BufferForDraw<E>,
BI: BufferForDraw<I>,
BC: BufferForDraw<C>,
V: BufferVecStructItem,
E: BufferVecItem + 'static,
I: BufferVecStructItem,
C: BufferVecStructItem {
pub primitive_type: VkPrimitiveTopology,
pub vertices: BV,
pub indices: Option<BE>,
pub instances: Option<BI>,
pub commands: Option<BC>,
vertex_type: V,
element_type: E,
instance_type: I,
command_type: C,
}
/// If a buffer you don't need, use this for your buffer item type
#[derive(Default, Debug, Clone, Copy, Iterable)]
pub struct UnusedBufferItem {}
/// If a buffer you don't need, use this for your buffer type
pub type UnusedBufferType = BufferWithType<UnusedBufferItem>;
/// Use this function to create an unused buffer type
pub fn buffer_unused() -> Option<UnusedBufferType> {
None
}
impl<BV, V, BE, E, BI, I, BC, C> Mesh<BV, V, BE, E, BI, I, BC, C>
where
BV: BufferForDraw<V>,
BE: BufferForDraw<E>,
BI: BufferForDraw<I>,
BC: BufferForDraw<C>,
V: BufferVecStructItem,
E: BufferVecItem + 'static,
I: BufferVecStructItem,
C: BufferVecStructItem {
/// Create the mesh from the buffers
pub fn new(primitive_type: VkPrimitiveTopology, vertices: BV, indices: Option<BE>, instances: Option<BI>, commands: Option<BC>) -> Self {
Self {
primitive_type,
vertices,
indices,
instances,
commands,
vertex_type: V::default(),
element_type: E::default(),
instance_type: I::default(),
command_type: C::default(),
}
}
/// Upload staging buffers to GPU
pub fn flush(&mut self, cmdbuf: VkCommandBuffer) -> Result<(), VulkanError> {
filter_no_staging_buffer(self.vertices.flush(cmdbuf))?;
if let Some(ref mut indices) = self.indices {filter_no_staging_buffer(indices.flush(cmdbuf))?;}
if let Some(ref mut instances) = self.instances {filter_no_staging_buffer(instances.flush(cmdbuf))?;}
if let Some(ref mut commands) = self.commands {filter_no_staging_buffer(commands.flush(cmdbuf))?;}
Ok(())
}
/// Discard staging buffers if the data will never be modified.
pub fn discard_staging_buffers(&mut self) {
self.vertices.discard_staging_buffer();
if let Some(ref mut indices) = self.indices {indices.discard_staging_buffer();}
if let Some(ref mut instances) = self.instances {instances.discard_staging_buffer();}
if let Some(ref mut commands) = self.commands {commands.discard_staging_buffer();}
}
}
The shaders in this crate can compile GLSL or HLSL code to SPIR-V intermediate language. Also, they could be loaded from a binary file instead of the source code.
let draw_shaders = Arc::new(DrawShaders::new(
Arc::new(VulkanShader::new_from_source_file_or_cache(device.clone(), ShaderSourcePath::VertexShader(PathBuf::from("shaders/test.vsh")), false, "main", OptimizationLevel::Performance, false)?),
None,
None,
None,
Arc::new(VulkanShader::new_from_source_file_or_cache(device.clone(), ShaderSourcePath::FragmentShader(PathBuf::from("shaders/test.fsh")), false, "main", OptimizationLevel::Performance, false)?),
));
The VulkanTexture is the wrapper for you to use textures.
The descriptor properties are for the shader inputs; they define which descriptor set and binding has a uniform buffer, or texture, samplers, etc.
Vec<T> since this could help to provide data for array-type inputs of the shaders./// The properties for the descriptor set
#[derive(Debug)]
pub enum DescriptorProp {
/// The props for the samplers
Samplers(Vec<Arc<VulkanSampler>>),
/// The props for the image
Images(Vec<TextureForSample>),
/// The props for the storage buffer
StorageBuffers(Vec<Arc<dyn GenericStorageBuffer>>),
/// The props for the uniform buffers
UniformBuffers(Vec<Arc<dyn GenericUniformBuffer>>),
/// The props for the storage texel buffer
StorageTexelBuffers(Vec<VulkanBufferView>),
/// The props for the uniform texel buffers
UniformTexelBuffers(Vec<VulkanBufferView>),
}
/// The descriptor set properties
#[derive(Default, Debug, Clone)]
pub struct DescriptorProps {
/// The descriptor sets
pub sets: HashMap<u32 /* set */, HashMap<u32 /* binding */, Arc<DescriptorProp>>>,
}
The pipeline wires mesh, texture, uniform buffers, storage buffers, shaders, output images, all together, and defines all of the rendering options.
let pipeline = ctx.create_pipeline_builder(mesh, draw_shaders, desc_props.clone())?
.set_cull_mode(VkCullModeFlagBits::VK_CULL_MODE_NONE as VkCullModeFlags)
.set_depth_test(false)
.set_depth_write(false)
.build()?;
On draw:
let scene = ctx.begin_scene(0, None)?;
scene.set_viewport_swapchain(0.0, 1.0)?;
scene.set_scissor_swapchain()?;
scene.begin_renderpass(Vec4::new(0.0, 0.0, 0.2, 1.0), 1.0, 0)?;
pipeline.draw(scene.get_cmdbuf())?;
scene.end_renderpass()?;
scene.finish();
use glfw::*;
use crate::prelude::*;
use std::{
collections::HashMap,
ffi::CStr,
path::PathBuf,
slice::from_raw_parts_mut,
sync::{
Arc,
Mutex,
RwLock,
atomic::{
AtomicBool,
Ordering,
}
},
thread,
time::Duration,
};
const TEST_TIME: f64 = 10.0;
#[derive(Debug)]
pub struct AppInstance {
pub ctx: Arc<RwLock<VulkanContext>>,
pub window: PWindow,
pub events: GlfwReceiver<(f64, WindowEvent)>,
pub glfw: Glfw,
}
impl AppInstance {
pub fn new(width: u32, height: u32, title: &str, window_mode: glfw::WindowMode) -> Result<Self, VulkanError> {
static GLFW_LOCK: Mutex<u32> = Mutex::new(0);
let glfw_lock = GLFW_LOCK.lock().unwrap();
let mut glfw = glfw::init(glfw::fail_on_errors).unwrap();
glfw.window_hint(WindowHint::ClientApi(ClientApiHint::NoApi));
let (mut window, events) = glfw.create_window(width, height, title, window_mode).expect("Failed to create GLFW window.");
drop(glfw_lock);
window.set_key_polling(true);
let device_requirement = DeviceRequirement {
can_graphics: true,
can_compute: false,
name_subtring: "",
};
let ctx = Arc::new(RwLock::new(create_vulkan_context(&window, device_requirement, PresentInterval::VSync, 1, false)?));
let ctx_lock = ctx.read().unwrap();
for gpu in VulkanGpuInfo::get_gpu_info(&ctx_lock.vkcore)?.iter() {
println!("Found GPU: {}", unsafe{CStr::from_ptr(gpu.properties.deviceName.as_ptr())}.to_str().unwrap());
}
println!("Chosen GPU name: {}", unsafe{CStr::from_ptr(ctx_lock.device.get_gpu().properties.deviceName.as_ptr())}.to_str().unwrap());
println!("Chosen GPU type: {:?}", ctx_lock.device.get_gpu().properties.deviceType);
drop(ctx_lock);
Ok(Self {
glfw,
window,
events,
ctx,
})
}
pub fn get_time(&self) -> f64 {
glfw_get_time()
}
pub fn set_time(&self, time: f64) {
glfw_set_time(time)
}
pub fn run(&mut self,
test_time: Option<f64>,
mut on_render: impl FnMut(&mut VulkanContext, f64) -> Result<(), VulkanError> + Send + 'static
) -> Result<(), VulkanError> {
let exit_flag = Arc::new(AtomicBool::new(false));
let exit_flag_cloned = exit_flag.clone();
let start_time = self.glfw.get_time();
let ctx = self.ctx.clone();
let renderer_thread = thread::spawn(move || {
let mut num_frames = 0;
let mut time_in_sec: u64 = 0;
let mut num_frames_prev: u64 = 0;
while !exit_flag_cloned.load(Ordering::Relaxed) {
let cur_frame_time = glfw_get_time();
let run_time = cur_frame_time - start_time;
on_render(&mut ctx.write().unwrap(), run_time).unwrap();
num_frames += 1;
let new_time_in_sec = run_time.floor() as u64;
if new_time_in_sec > time_in_sec {
let fps = num_frames - num_frames_prev;
println!("FPS: {fps}\tat {new_time_in_sec}s");
time_in_sec = new_time_in_sec;
num_frames_prev = num_frames;
}
}
});
while !self.window.should_close() {
let run_time = glfw_get_time() - start_time;
thread::sleep(Duration::from_millis(1));
self.glfw.poll_events();
for (_, event) in glfw::flush_messages(&self.events) {
match event {
glfw::WindowEvent::Key(Key::Escape, _, Action::Press, _) => {
self.window.set_should_close(true);
}
_ => {}
}
}
if let Some(test_time) = test_time {
if run_time >= test_time {
self.window.set_should_close(true);
}
}
}
exit_flag.store(true, Ordering::Relaxed);
renderer_thread.join().unwrap();
println!("End of the test");
Ok(())
}
}
unsafe impl Send for AppInstance {}
unsafe impl Sync for AppInstance {}
fn main() {
derive_vertex_type! {
pub struct VertexType {
pub position: Vec2,
}
}
derive_uniform_buffer_type! {
pub struct UniformInput {
resolution: Vec3,
time: f32,
}
}
struct Resources {
uniform_input: Arc<dyn GenericUniformBuffer>,
pipeline: Pipeline,
}
impl Resources {
pub fn new(ctx: &mut VulkanContext) -> Result<Self, VulkanError> {
let device = ctx.device.clone();
let draw_shaders = Arc::new(DrawShaders::new(
Arc::new(VulkanShader::new_from_source_file_or_cache(device.clone(), ShaderSourcePath::VertexShader(PathBuf::from("shaders/test.vsh")), false, "main", OptimizationLevel::Performance, false)?),
None,
None,
None,
Arc::new(VulkanShader::new_from_source_file_or_cache(device.clone(), ShaderSourcePath::FragmentShader(PathBuf::from("shaders/test.fsh")), false, "main", OptimizationLevel::Performance, false)?),
));
let uniform_input: Arc<dyn GenericUniformBuffer> = Arc::new(UniformBuffer::<UniformInput>::new(device.clone())?);
let desc_props = Arc::new(DescriptorProps::default());
desc_props.new_uniform_buffer(0, 0, uniform_input.clone());
let pool_in_use = ctx.cmdpools[0].use_pool(None)?;
let vertices_data = vec![
VertexType {
position: Vec2::new(-1.0, -1.0),
},
VertexType {
position: Vec2::new( 1.0, -1.0),
},
VertexType {
position: Vec2::new(-1.0, 1.0),
},
VertexType {
position: Vec2::new( 1.0, 1.0),
},
];
let vertices = Arc::new(RwLock::new(BufferWithType::new(device.clone(), &vertices_data, pool_in_use.cmdbuf, VkBufferUsageFlagBits::VK_BUFFER_USAGE_VERTEX_BUFFER_BIT as VkBufferUsageFlags)?));
let mesh = Arc::new(GenericMeshWithMaterial::new(Arc::new(Mesh::new(VkPrimitiveTopology::VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP, vertices, buffer_unused(), buffer_unused(), buffer_unused())), "", None));
mesh.geometry.flush(pool_in_use.cmdbuf)?;
drop(pool_in_use);
ctx.cmdpools[0].wait_for_submit(u64::MAX)?;
mesh.geometry.discard_staging_buffers();
let pipeline = ctx.create_pipeline_builder(mesh, draw_shaders, desc_props)?
.set_cull_mode(VkCullModeFlagBits::VK_CULL_MODE_NONE as VkCullModeFlags)
.set_depth_test(false)
.set_depth_write(false)
.build()?;
Ok(Self {
uniform_input,
pipeline,
})
}
pub fn draw(&self, ctx: &mut VulkanContext, run_time: f64) -> Result<(), VulkanError> {
let scene = ctx.begin_scene(0, None)?;
let cmdbuf = scene.get_cmdbuf();
let extent = scene.get_rendertarget_extent();
let ui_data = unsafe {from_raw_parts_mut(self.uniform_input.get_staging_buffer_address()? as *mut UniformInput, 1)};
ui_data[0] = UniformInput {
resolution: Vec3::new(extent.width as f32, extent.height as f32, 1.0),
time: run_time as f32,
};
self.uniform_input.flush(cmdbuf)?;
scene.set_viewport_swapchain(0.0, 1.0)?;
scene.set_scissor_swapchain()?;
scene.begin_renderpass(Vec4::new(0.0, 0.0, 0.2, 1.0), 1.0, 0)?;
self.pipeline.draw(cmdbuf)?;
scene.end_renderpass()?;
scene.finish();
Ok(())
}
}
let mut inst = Box::new(AppInstance::new(1024, 768, "Vulkan test", glfw::WindowMode::Windowed).unwrap());
let resources = Resources::new(&mut inst.ctx.write().unwrap()).unwrap();
inst.run(Some(TEST_TIME),
move |ctx: &mut VulkanContext, run_time: f64| -> Result<(), VulkanError> {
resources.draw(ctx, run_time)
}).unwrap();
}
shaderc, it fails and shows the following information:warning: shaderc-sys@0.10.1: shaderc: requested to build from source
error: failed to run custom build command for `shaderc-sys v0.10.1`
Caused by:
process didn't exit successfully: `C:\your\path\to\your\crate\target\release\build\shaderc-sys-03dfa106721f22d5\build-script-build` (exit code: 101)
--- stdout
cargo:warning=shaderc: requested to build from source
--- stderr
thread 'main' panicked at C:\Users\your_name\.cargo\registry\src\index.crates.io-1949cf8c6b5b557f\shaderc-sys-0.10.1\build\cmd_finder.rs:55:13:
couldn't find required command: "ninja"
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
warning: build failed, waiting for other jobs to finish...
winget install Ninja-build.Ninjasudo apt install ninja-buildsudo dnf install ninja-buildbrew install ninja
See: https://github.com/ninja-build/ninja/releases`validation_layer feature was enabled, it failed to run. The error information says:called `Result::unwrap()` on an `Err` value: VkError(VkErrorLayerNotPresent("vkCreateInstance"))