# vkobject-rs **Repository Path**: a5k3rn3l/vkobject-rs ## Basic Information - **Project Name**: vkobject-rs - **Description**: Rust 写的针对 Vulkan 封装的更加抽象的高级接口,用于更好地管理资源、管理渲染引擎,并可以和 `globject-rs` 实现相互替换后端的渲染实现。 - **Primary Language**: Unknown - **License**: LGPL-2.1 - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2025-08-13 - **Last Updated**: 2025-11-06 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Vulkan Object Wrapper:另一个 Rust 渲染引擎实现 ## 语言|language Readme-CN.md | [Chinglish](Readme.md) ## 用法 ### 添加到项目 ```cmd cargo add vkobject-rs ``` ## 渲染策略 1. 创建 `VulkanContext` - 如果你想在 GLFW 中使用 **vkobject-rs**,最简单的方法是在你的 GLFW 窗口中使用 `create_vulkan_context()`。 2. 构建用于绘制的管线。 - 管线是一个对象,它将所有缓冲区中的所有数据收集到着色器中,运行着色器,然后将输出连接到渲染目标附件。 - 要构建管线,你必须创建以下内容: 1. 网格。网格描述你要渲染的多边形。 2. 着色器。着色器定义如何处理多边形以生成像素,并将像素写入渲染目标中。 3. 着色器的输入数据。uniform buffer、storage buffer、push constants、textures, or texel buffers 都可以实现这一点。 3. 使用管道进行绘制。 - 请参阅**示例代码**部分中 `pub fn draw()` 的实现。 4. 当对象超出作用域时,资源清理会自动完成,靠的是 Rust 语言的 RAII 规则。 ## 主要使用的对象 ### 缓冲区:用于保存多边形、绘制实例、绘制命令和着色器存储空间 此 crate 中包含以下几种类型的缓冲区: - `BufferWithType`:`Buffer` 的包装器,主要用于存储不经常修改的数据,因此其暂存缓冲区可以丢弃。 - `BufferVec`:`Buffer` 的包装器,提供类似于 `Vec` 的接口,调用 `flush()` 可以将数据通过命令缓冲区上传到 GPU。 - `flush()` 只会将修改后的数据上传到 GPU。数据更新是增量式的,从而最大限度地减少了 CPU-GPU 数据传输的带宽占用。 - 此缓冲区的暂存缓冲区不可丢弃。 - `UniformBuffer`:`Buffer` 的包装器,其 CPU 端的数据可以解引用为结构体,你可以自由修改结构体成员。 - `flush()` 会将整个结构上传到 GPU 端。 - 此缓冲区通常用于着色器输入。 - `GenericUniformBuffer`:`UniformBuffer` 的包装器,用于清除泛型类型 ``。 - `StorageBuffer`:Buffer 的包装器,用法与 `UniformBuffer` 相同,只是它用于着色器的 storage buffer 输入。 - 着色器可以自由修改 storage buffer,但不能修改 uniform buffer。 - `GenericStorageBuffer`:`StorageBuffer` 的包装器,用于清除泛型类型 ``。 - `StagingBuffer`:对 CPU 完全透明的暂存缓冲区,你可以获取其数据指针并修改数据,以便将其上传到 GPU。 - 安全性:你应该知道如何正确操作原始指针。 - `Buffer`:对 GPU 透明的缓冲区,它拥有自己的暂存缓冲区,用于将数据上传到 GPU。 - 将数据传输到暂存缓冲区,然后调用 `upload_staging_buffer()` 将上传命令加入命令缓冲区。 - 上传命令执行后,数据将传输到 GPU,然后调用 `discard_staging_buffer()` 以节省一些系统内存(如果需要)。 - 这玩意儿是底层的东西,不是给你直接用的。 - `VulkanBuffer`:最底层的缓冲区包装对象,`Buffer` 和 `StagingBuffer` 都是使用此对象实现的。 - 这玩意儿是最底层的东西,不是给你直接用的。 ### 网格:用于保存多边形、绘制实例和绘制命令 网格有 4 个缓冲区。根据用途,它们分别是: - 顶点缓冲区 - 索引缓冲区(可选) - 实例缓冲区(可选) - 间接绘制命令缓冲区(可选) 网格的缓冲区有两种类型: - 对于静态绘制,可以使用 `BufferWithType`。 - 缓冲区中的数据一旦初始化,便不会更改。 - 对于动态更新,可以使用 `BufferVec`。 - 你可以像 `Vec` 一样频繁修改其数据,然后调用 `flush()` 将更改应用于 GPU 缓冲区。 ```rust #[derive(Debug, Clone)] pub struct Mesh where BV: BufferForDraw, BE: BufferForDraw, BI: BufferForDraw, BC: BufferForDraw, V: BufferVecStructItem, E: BufferVecItem + 'static, I: BufferVecStructItem, C: BufferVecStructItem { pub primitive_type: VkPrimitiveTopology, pub vertices: BV, pub indices: Option, pub instances: Option, pub commands: Option, 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; /// Use this function to create an unused buffer type pub fn buffer_unused() -> Option { None } impl Mesh where BV: BufferForDraw, BE: BufferForDraw, BI: BufferForDraw, BC: BufferForDraw, 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, instances: Option, commands: Option) -> 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();} } } ``` ### 纹理 `VulkanTexture` 是用于使用纹理的包装器。 ### DescriptorProps 描述符属性用于着色器输入;它们定义哪些描述符集和绑定具有统一的缓冲区、纹理、采样器等。 - 着色器输入被设置为 `Vec`,因为这有助于为着色器的数组类型输入提供数据。 - 对于单变量输入,只需提供数组的一个元素即可。 ```rust /// The properties for the descriptor set #[derive(Debug)] pub enum DescriptorProp { /// The props for the samplers Samplers(Vec>), /// The props for the image Images(Vec), /// The props for the storage buffer StorageBuffers(Vec>), /// The props for the uniform buffers UniformBuffers(Vec>), /// The props for the storage texel buffer StorageTexelBuffers(Vec), /// The props for the uniform texel buffers UniformTexelBuffers(Vec), } /// The descriptor set properties #[derive(Default, Debug, Clone)] pub struct DescriptorProps { /// The descriptor sets pub sets: HashMap>>, } ``` ### 着色器 着色器对象带有编译功能,可以将 GLSL 或 HLSL 代码编译为 SPIR-V 中间语言。此外,它们也可以从二进制文件(而非源代码)加载。 ```rust 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)?), )); ``` ### 管线 管线将网格、纹理、统一缓冲区、存储缓冲区、着色器、输出图像等连接在一起,并定义所有渲染选项。 ```rust 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()?; ``` 绘制时: ```rust 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(); ``` ## 示例代码 ```rust 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>, 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 { static GLFW_LOCK: Mutex = 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, 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, pipeline: Pipeline, } impl Resources { pub fn new(ctx: &mut VulkanContext) -> Result { 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 = Arc::new(UniformBuffer::::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` 时失败,并提供以下报错: ``` 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... ``` * 答案:你需要安装 ninja 并重新编译。 * Windows 系统:运行 `winget install Ninja-build.Ninja` * Debian/Ubuntu 系统:运行 `sudo apt install ninja-build` * Fedora/RHEL 系统:运行 `sudo dnf install ninja-build` * MacOS 系统:运行 `brew install ninja` 参见:https://github.com/ninja-build/ninja/releases` ### 问题:启用 `validation_layer` 特性后,运行失败。错误信息包含以下内容: ``` called `Result::unwrap()` on an `Err` value: VkError(VkErrorLayerNotPresent("vkCreateInstance")) ``` * 答案:这是因为你的 GPU 驱动程序不支持 Vulkan 验证层。你只能在不使用 Vulkan 验证层的情况下调试你的 Vulkan 代码。 * 买一张 NVIDIA 显卡可以解决此问题,因为其驱动程序支持 Vulkan 验证层。