3 个版本 (重大更新)
0.3.0 | 2023年4月29日 |
---|---|
0.2.1 | 2023年4月13日 |
0.1.19 | 2023年3月3日 |
0.1.17 |
|
0.1.0 |
|
#470 在 游戏开发
每月 29 次下载
695KB
14K SLoC
Hotline
Hotline 是一个图形引擎和实时编码工具,允许您在不重新启动应用程序的情况下编辑代码、着色器和渲染配置。它提供了一个 client
应用程序,该应用程序在整个会话期间保持运行。可以重新加载动态 plugins
内部的代码,并且可以通过 pmfx
文件编辑和热重载渲染配置。
有一个演示 视频 展示了其早期阶段的功能和一些工作流程示例,演示了如何创建几何原语。一些开发工作已在 Twitch 上进行实时直播,并已在 YouTube 上存档。
功能
- 易于使用的跨平台图形/计算/OS API,用于快速开发。
- 热重载,实时编码环境(着色器、渲染配置、代码)。
- gfx - 简洁的低级图形 API...
- pmfx - 高级数据驱动图形 API,易于使用且速度快。
- 专注于现代渲染示例(GPU 驱动、多线程、免绑)。
- 硬件加速视频解码。
先决条件
目前仅支持 Windows Direct3D12 平台,计划在未来支持 macOS、Metal、Linux、Vulkan 等。
使用客户端/示例
如果您想使用示例 plugins
或独立的 examples
,建议您使用 GitHub 上的仓库。如果您只想使用库,则 crates.io
是合适的。发布数据和插件存在一些困难,我希望在将来解决这些问题。
构建/获取数据
需要 hotline-data 仓库来构建和提供示例和示例插件的数据,它作为此仓库的子模块包含在内,您可以使用以下命令克隆带子模块
git clone https://github.com/polymonster/hotline.git --recursive
您可以在克隆后添加子模块,或者按照以下步骤更新子模块以保持与主仓库同步:
git submodule update --init --recursive
运行客户端
您可以使用二进制文件 client
,它允许通过 plugins
重新加载代码。仓库中已提供一些 插件。
// build the hotline library and the client, fetch the hotline-data repository
cargo build
// build the data
.\hotline-data\pmbuild.cmd win32-data
// then build plugins
cargo build --manifest-path plugins/Cargo.toml
// run the client
cargo run client
对插件库进行的任何代码更改都将在客户端仍在运行时触发重建和重新加载。您还可以编辑 着色器,其中 hlsl
文件组成着色器代码,pmfx
文件允许您在配置文件中指定管道状态对象。检测到对 pmfx
着色器的任何更改都将进行重建,并重新构建所有修改过的管道或视图。
构建单行代码
为了在开发期间使事情更加方便,并保持 plugins
、client
和 lib
保持同步,并轻松切换配置,您可以使用位于 hotline-data
仓库中的捆绑式 pmbuild 并使用以下命令,这些命令将构建步骤捆绑在一起:
// show aavailable build profiles
.\hotline-data\pmbuild -help
// build release
.\hotline-data\pmbuild win32-release
// build debug
.\hotline-data\pmbuild win32-debug
// run the client
.\hotline-data\pmbuild win32-debug -run
// build and run the client
.\hotline-data\pmbuild win32-release -all -run
在 Visual Studio Code 中构建
包括客户端和示例配置在内的任务和启动文件已包含在 vscode 中。从 vscode 中以调试或发布模式启动 client
将构建核心热线 lib
、client
、data
和 plugins
。
添加插件
插件通过传递包含 Cargo.toml
和是动态库的目录到 add_plugin_lib 来加载。它们可以使用主菜单栏的 文件 > 打开
与客户端交互式打开,通过选择 Cargo.toml
。
基本的 Cargo.toml
设置如下所示:
[package]
name = "ecs_examples"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["rlib", "dylib"]
[dependencies]
hotline-rs = { path = "../.." }
您可以使用 Plugin 特性提供自己的插件实现。一个基本的插件可以通过实现几个函数来挂钩自己
impl Plugin<gfx_platform::Device, os_platform::App> for EmptyPlugin {
fn create() -> Self {
EmptyPlugin {
}
}
fn setup(&mut self, client: Client<gfx_platform::Device, os_platform::App>)
-> Client<gfx_platform::Device, os_platform::App> {
println!("plugin setup");
client
}
fn update(&mut self, client: client::Client<gfx_platform::Device, os_platform::App>)
-> Client<gfx_platform::Device, os_platform::App> {
println!("plugin update");
client
}
fn unload(&mut self, client: Client<gfx_platform::Device, os_platform::App>)
-> Client<gfx_platform::Device, os_platform::App> {
println!("plugin unload");
client
}
fn ui(&mut self, client: Client<gfx_platform::Device, os_platform::App>)
-> Client<gfx_platform::Device, os_platform::App> {
println!("plugin ui");
client
}
}
// the macro instantiates the plugin with a c-abi so it can be loaded dynamically.
hotline_plugin![EmptyPlugin];
Ecs 插件
有一个核心 ecs
插件,它是基于 bevy_ecs 构建的。它允许您动态地提供自己的系统和构建计划。可以在不同的动态库中加载和查找新的 ecs
系统。您可以将 demos
注册并实例化,它们是 setup
、update
和 render
系统的集合。
初始化函数
您可以通过提供一个名为 demo 的初始化函数来设置新的 ecs demo,该函数返回一个 ScheduleInfo
,用于运行哪些系统
/// Init function for primitives demo
#[no_mangle]
pub fn primitives(client: &mut Client<gfx_platform::Device, os_platform::App>) -> ScheduleInfo {
// load resources we may need
client.pmfx.load(&hotline_rs::get_data_path("shaders/debug").as_str()).unwrap();
// fill out info
ScheduleInfo {
setup: systems![
"setup_primitives"
],
update: systems![
"update_cameras",
"update_main_camera_config"
],
render_graph: "mesh_debug"
}
}
设置系统
您可以提供 setup
系统将实体添加到场景中,当动态代码重新加载发生时,世界将被清除,设置系统将被重新执行。这允许设置系统的更改在实时 client
中显示。您可以添加多个 setup
系统,并将并发执行。
#[no_mangle]
pub fn setup_cube(
mut device: bevy_ecs::change_detection::ResMut<DeviceRes>,
mut commands: bevy_ecs::system::Commands) {
let pos = Mat4f::from_translation(Vec3f::unit_y() * 10.0);
let scale = Mat4f::from_scale(splat3f(10.0));
let cube_mesh = hotline_rs::primitives::create_cube_mesh(&mut device.0);
commands.spawn((
Position(Vec3f::zero()),
Velocity(Vec3f::one()),
MeshComponent(cube_mesh.clone()),
WorldMatrix(pos * scale)
));
}
渲染系统
您可以在 pmfx
中指定设置 views
的渲染图,这些视图被分发到 render
函数中。所有渲染系统都在 CPU 上并发运行,它们生成的命令缓冲区将按照 pmfx
渲染图及其依赖项确定的顺序执行。
#[no_mangle]
pub fn render_meshes(
pmfx: &Res<PmfxRes>,
view: &pmfx::View<gfx_platform::Device>,
mesh_draw_query: Query<(&WorldMatrix, &MeshComponent)>) -> Result<(), hotline_rs::Error> {
let fmt = view.pass.get_format_hash();
let mesh_debug = pmfx.get_render_pipeline_for_format(&view.view_pipeline, fmt)?;
let camera = pmfx.get_camera_constants(&view.camera)?;
// setup pass
view.cmd_buf.begin_render_pass(&view.pass);
view.cmd_buf.set_viewport(&view.viewport);
view.cmd_buf.set_scissor_rect(&view.scissor_rect);
view.cmd_buf.set_render_pipeline(&mesh_debug);
view.cmd_buf.push_render_constants(0, 16 * 3, 0, gfx::as_u8_slice(camera));
// make draw calls
for (world_matrix, mesh) in &mesh_draw_query {
view.cmd_buf.push_render_constants(1, 16, 0, &world_matrix.0);
view.cmd_buf.set_index_buffer(&mesh.0.ib);
view.cmd_buf.set_vertex_buffer(&mesh.0.vb, 0);
view.cmd_buf.draw_indexed_instanced(mesh.0.num_indices, 1, 0, 0, 0);
}
// end / transition / execute
view.cmd_buf.end_render_pass();
Ok(())
}
更新系统
您也可以提供自己的 update
系统来动画化和移动实体,这些也会并发执行。
fn update_cameras(
app: Res<AppRes>,
main_window: Res<MainWindowRes>,
mut query: Query<(&mut Position, &mut Rotation, &mut ViewProjectionMatrix), With<Camera>>) {
let app = &app.0;
for (mut position, mut rotation, mut view_proj) in &mut query {
// ..
}
}
注册系统
系统可以从不同的插件动态导入,为此它们需要连接到一个可以通过 ecs
插件动态定位的函数。我希望在将来能够移除这个负担,并能够使用 #[derive()]
来定义它们。
您可以实现一个名为 get_demos_<lib_name>
的函数,该函数返回一个名为 <lib_name>
的 plugin
中可用的演示列表,并且 get_system_<lib_name>
返回 SystemDescriptor
,这样就可以通过名称查找系统。ecs 插件将在所有其他已加载的插件中按名称搜索系统,因此您可以构建和共享功能。
/// Register demo names
#[no_mangle]
pub fn get_demos_ecs_examples() -> Vec<String> {
demos![
"primitives",
"draw_indexed",
"draw_indexed_push_constants",
// ..
]
}
/// Register plugin system functions
#[no_mangle]
pub fn get_system_ecs_examples(name: String, view_name: String) -> Option<SystemDescriptor> {
match name.as_str() {
// setup functions
"setup_draw_indexed" => system_func![setup_draw_indexed],
"setup_primitives" => system_func![setup_primitives],
"setup_draw_indexed_push_constants" => system_func![setup_draw_indexed_push_constants],
// render functions
"render_meshes" => render_func![render_meshes, view_name],
"render_wireframe" => render_func![render_wireframe, view_name],
_ => std::hint::black_box(None)
}
}
系统执行顺序
默认情况下,特定组中的所有系统都将异步执行,并且组将按顺序执行。
SystemSets::Update
- 使用此功能来动画化和移动实体,执行逻辑等。SystemSets::Batch
- 使用此功能批量处理数据,例如烘焙世界矩阵、剔除或更新缓冲区以供渲染。SystemSets::Render
- 用于渲染实体和执行绘制调用。
任何渲染函数都将自动添加到 Render
系统集中,但您可以选择创建自己的集或将其添加到预定义的 SystemSets
中。有些核心操作将会发生,但您可以定义自己的并按以下顺序执行
// updates
"rotate_meshes" => system_func![
rotate_meshes
.in_base_set(CustomSystemSet::Animate)
.after(SystemSets::Update)
],
// batches
"batch_world_matrix_instances" => system_func![
draw::batch_world_matrix_instances
.after(SystemSets::Batch)
],
序列化插件数据
您可以提供自己的可序列化插件数据,这些数据将与 user_config
一起序列化,可以与您的插件分组并在会话之间重新加载。
/// Seriablisable user info for maintaining state between reloads and sessions
#[derive(Serialize, Deserialize, Default, Resource, Clone)]
pub struct SessionInfo {
pub active_demo: String,
pub main_camera: Option<CameraInfo>
}
// the client provides functions which can serialise and deserialise this data for you
fn update_user_config(&mut self) {
// find plugin data for the "ecs" plugin
self.session_info = client.deserialise_plugin_data("ecs");
//.. make updates to your data here
// write back session info which will be serialised to disk and reloaded between sessions
client.serialise_plugin_data("ecs", &self.session_info);
}
作为库使用
您可以在插件系统中或单独使用 hotline 作为库,以使用底层抽象和模块来创建具有图形 API 后端的窗口应用程序。以下是一个简单的示例
基本应用程序
// include prelude for convenience
use hotline_rs::prelude::*;
pub fn main() -> Result<(), hotline_rs::Error> {
// Create an Application
let mut app = os_platform::App::create(os::AppInfo {
name: String::from("triangle"),
window: false,
num_buffers: 0,
dpi_aware: true,
});
// Double buffered
let num_buffers = 2;
// Create an a GPU Device
let mut device = gfx_platform::Device::create(&gfx::DeviceInfo {
render_target_heap_size: num_buffers,
..Default::default()
});
// Create main window
let mut window = app.create_window(os::WindowInfo {
title: String::from("triangle!"),
..Default::default()
});
/// Create swap chain
let swap_chain_info = gfx::SwapChainInfo {
num_buffers: num_buffers as u32,
format: gfx::Format::RGBA8n,
..Default::default()
};
let mut swap_chain = device.create_swap_chain::<os_platform::App>(&swap_chain_info, &window)?;
/// Create a command buffer
let mut cmd = device.create_cmd_buf(num_buffers);
// Run main loop
while app.run() {
// update window and swap chain
window.update(&mut app);
swap_chain.update::<os_platform::App>(&mut device, &window, &mut cmd);
// build command buffer and make draw calls
cmd.reset(&swap_chain);
// Render command can go here
// ..
cmd.close()?;
// execute command buffer
device.execute(&cmd);
// swap for the next frame
swap_chain.swap(&device);
}
// must wait for the final frame to be completed so it is safe to drop GPU resources.
swap_chain.wait_for_last_frame();
Ok(());
}
gfx
该 gfx 模块提供了一个现代图形 API,大致遵循 Direct3D12,同时考虑了 Vulkan 和 Metal 的兼容性。如果您熟悉这些 API,应该很简单,但这里有一个如何执行一些渲染命令的快速示例
// create a buffer
let info = gfx::BufferInfo {
usage: gfx::BufferUsage::Vertex,
cpu_access: gfx::CpuAccessFlags::NONE,
format: gfx::Format::Unknown,
stride: std::mem::size_of::<Vertex>(),
num_elements: 3,
};
let vertex_buffer = device.create_buffer(&info, Some(gfx::as_u8_slice(&vertices)))?;
// create shaders and a pipeline
let vsc_filepath = hotline_rs::get_data_path("shaders/triangle/vs_main.vsc");
let psc_filepath = hotline_rs::get_data_path("shaders/triangle/ps_main.psc");
let vsc_data = fs::read(vsc_filepath)?;
let psc_data = fs::read(psc_filepath)?;
let vsc_info = gfx::ShaderInfo {
shader_type: gfx::ShaderType::Vertex,
compile_info: None
};
let vs = device.create_shader(&vsc_info, &vsc_data)?;
let psc_info = gfx::ShaderInfo {
shader_type: gfx::ShaderType::Vertex,
compile_info: None
};
let fs = device.create_shader(&psc_info, &psc_data)?;
// create the pipeline itself with the vs and fs
let pso = device.create_render_pipeline(&gfx::RenderPipelineInfo {
vs: Some(&vs),
fs: Some(&fs),
input_layout: vec![
gfx::InputElementInfo {
semantic: String::from("POSITION"),
index: 0,
format: gfx::Format::RGB32f,
input_slot: 0,
aligned_byte_offset: 0,
input_slot_class: gfx::InputSlotClass::PerVertex,
step_rate: 0,
},
gfx::InputElementInfo {
semantic: String::from("COLOR"),
index: 0,
format: gfx::Format::RGBA32f,
input_slot: 0,
aligned_byte_offset: 12,
input_slot_class: gfx::InputSlotClass::PerVertex,
step_rate: 0,
},
],
pipeline_layout: gfx::PipelineLayout::default(),
raster_info: gfx::RasterInfo::default(),
depth_stencil_info: gfx::DepthStencilInfo::default(),
blend_info: gfx::BlendInfo {
alpha_to_coverage_enabled: false,
independent_blend_enabled: false,
render_target: vec![gfx::RenderTargetBlendInfo::default()],
},
topology: gfx::Topology::TriangleList,
patch_index: 0,
pass: swap_chain.get_backbuffer_pass(),
})?;
// build command buffer and make draw calls
cmd.reset(&swap_chain);
// manual transition handling
cmd.transition_barrier(&gfx::TransitionBarrier {
texture: Some(swap_chain.get_backbuffer_texture()),
buffer: None,
state_before: gfx::ResourceState::Present,
state_after: gfx::ResourceState::RenderTarget,
});
// render pass approach is used, swap chain automatically creates some for us
cmd.begin_render_pass(swap_chain.get_backbuffer_pass_mut());
cmd.set_viewport(&viewport);
cmd.set_scissor_rect(&scissor);
// set state for the draw
cmd.set_render_pipeline(&pso);
cmd.set_vertex_buffer(&vertex_buffer, 0);
cmd.draw_instanced(3, 1, 0, 0);
cmd.end_render_pass();
// manually transition
cmd.transition_barrier(&gfx::TransitionBarrier {
texture: Some(swap_chain.get_backbuffer_texture()),
buffer: None,
state_before: gfx::ResourceState::RenderTarget,
state_after: gfx::ResourceState::Present,
});
// execute command buffer
cmd.close()?;
device.execute(&cmd);
// swap for the next frame
swap_chain.swap(&device);
pmfx
Pmfx 在 gfx
模块之上构建,以使渲染配置更加直观、数据驱动且开发更快。您可以使用 pmfx 模块和 pmfx
数据以数据驱动的方式配置渲染管线。有关详细信息,请参阅 pmfx-shader 存储库,它目前正在经历更改和改进,但现在支持一系列功能。
您可以为指定渲染管线、纹理(渲染目标)、视图(带摄像机的渲染传递)和渲染图提供 jsn 配置文件。所有字段都提供了有用的默认值,并结合 jsn 继承功能,可以帮助您以最小重复的方式创建许多不同的渲染策略。
textures: {
main_colour: {
ratio: {
window: "main_window",
scale: 1.0
}
format: "RGBA8n"
usage: ["ShaderResource", "RenderTarget"]
samples: 8
}
main_depth(main_colour): {
format: "D24nS8u"
usage: ["ShaderResource", "DepthStencil"]
samples: 8
}
}
views: {
main_view: {
render_target: [
"main_colour"
]
clear_colour: [0.45, 0.55, 0.60, 1.0]
depth_stencil: [
"main_depth"
]
clear_depth: 1.0
viewport: [0.0, 0.0, 1.0, 1.0, 0.0, 1.0]
camera: "main_camera"
}
main_view_no_clear(main_view): {
clear_colour: null
clear_depth: null
}
}
pipelines: {
mesh_debug: {
vs: vs_mesh
ps: ps_checkerboard
push_constants: [
"view_push_constants"
"draw_push_constants"
]
depth_stencil_state: depth_test_less
raster_state: cull_back
topology: "TriangleList"
}
}
render_graphs: {
mesh_debug: {
grid: {
view: "main_view"
pipelines: ["imdraw_3d"]
function: "render_grid"
}
meshes: {
view: "main_view_no_clear"
pipelines: ["mesh_debug"]
function: "render_meshes"
depends_on: ["grid"]
}
wireframe: {
view: "main_view_no_clear"
pipelines: ["wireframe_overlay"]
function: "render_meshes"
depends_on: ["meshes", "grid"]
}
}
}
当构建 pmfx 时,会生成着色器源代码以及一个包含运行时所需有用反射信息的 信息文件。根据着色器输入和使用情况,会自动生成描述符布局和顶点布局。
示例
有几个如何使用热线(gfx, app, av
)的低级别组件的独立示例。您可以按以下方式构建和运行这些示例
// build examples
cargo build --examples
// make sure to build data
.\hotline-data\pmbuild.cmd win32-data
// run a single sample
cargo run --example triangle
三角形
这是一个独立示例,位于 ecs
系统之外。它直接使用 gfx
API 设置和渲染三角形。这提供了对低级别图形设置的全面了解,可以作为任何其他平台移植工作的起点。
无绑定
第二个独立示例用于测试渲染目标、计算着色器、图像加载和无绑定纹理采样。配置了几个渲染传递,并在所有内容在屏幕上合成到四个象限之前,将计算着色器写入可读写纹理。
ImGui 演示
实现和验证 imgui 后端的功能测试 - 这展示了 imgui 的全部功能集,包括停靠、视口和鼠标光标。
播放视频
这是一个独立播放视频的示例,它允许您从磁盘加载视频文件以测试不同视频格式的兼容性。当前实现使用 Windows 媒体基金会和 Direct3D12 设备进行视频解码。av
API 提供了对解码视频帧的访问,作为一个本地的 gfx::Texture
,并在 GPU 上执行所有解码。
Ecs 示例
使用 ecs
和 plugin
系统实现了更多高级示例。可以在 plugins/ecs_examples/examples.rs 中找到它们
绘制
第一个也是最基础的 ecs
驱动的示例使用 cmd_buf.draw_instanced
调用绘制三角形网格实体。这是一个非索引绘制调用,只是为了作为 draw_instanced
函数的测试。使用推送常数来推送相机矩阵,但网格本身是从原始顶点数据绘制的。
绘制索引
与 draw_instanced
调用类似,但这次我们使用索引缓冲区使用 draw_indexed_instanced
绘制立方体网格。作为 [primitives] API 部分创建的所有网格都附带索引缓冲区,一旦我着手实现模型加载器,它们也将包含索引缓冲区,因此 draw_indexed_instanced
很可能比 draw_instanced
使用得更多,但需要支持并测试这两个。
绘制索引推送常数
在绘制索引示例的基础上,这个示例添加了额外的每个实体的绘制信息,即世界矩阵,以定位它们。
绘制间接
这是一个非常简单且不太有用的 execute_indirect
示例;它创建了2个间接的 CommandSignatures
(一个用于 Draw
,另一个用于 DrawIndexed
)然后在CPU上填充了 IndirectArguments
。通过调用 cmd_buf.execute_indirect
来绘制实体。后来这个功能变得更加强大,因为命令缓冲区可以在GPU上填充,但这里只是作为一个非常基本的单元测试,以确保一切连接正常并且可以执行间接绘制。
几何原语
一个基本示例,展示了所有可用的程序生成几何原语。
索引绘制顶点缓冲区实例化
此示例使用2个顶点流,其中一个包含顶点数据,另一个包含每个实体的世界矩阵。每个帧在CPU上将实体世界矩阵批量更新到单个顶点缓冲区中。实例步进由顶点布局驱动。
索引绘制Cbuffer实例化
此示例通过在CPU上更新实体世界矩阵并将它们批量放入更大的常量缓冲区中来提供实例绘制。常量缓冲区绑定到管道槽,并使用顶点着色器语义 SV_InstanceID
来索引常量缓冲区以获取每个实例的世界矩阵。
绘制推送常数纹理
简单的无绑定纹理示例 - 使用推送常数推送每个绘制调用的实体纹理ID。纹理ID(着色器资源视图索引)用于在片段着色器中的无界描述符数组中查找纹理。
切线空间法线贴图
一个测试平台,用于验证着色器、几何体和法线贴图纹理在切线空间法线贴图转换中的正确性。
绘制材质
无绑定材质设置示例。为绘制调用创建实例批,实例缓冲区由一个 uint4
组成,其中打包了每个实体的绘制ID和材质ID。每个实体的世界矩阵在无界描述符数组中查找,同样材质也在其中查找。材质数据由传递给片段着色器的纹理ID组成。在片段着色器中,我们使用纹理ID再次在无界描述符数组中查找存储的显色、法线和粗糙度纹理。
GPU视锥体剔除
此示例利用 execute_indirect
和一个计算着色器在GPU上执行AABB与视锥体剔除。一个结构化缓冲区填充了场景中所有绘制调用的信息,并创建了一个带有计数器和无序访问的结构化缓冲区,用作GPU上的 AppendStructuredBuffer
。在计算着色器中查找实体范围,并通过测试实体AABB范围与相机视锥体平面来执行剔除。在视锥体内或与视锥体相交的实体将它们的绘制调用数据从完整结构化缓冲区复制到绘制间接结构化缓冲区中。绘制间接结构化缓冲区用于驱动 execute_indirect
调用。该示例绘制了64k个实体,每个实体都有唯一的顶点和索引缓冲区,在CPU上运行时间为16ms,而通过 draw_indexed
进行等效数量的绘制调用则需要超过80ms。
点光源
使用点光源、球体和平面创建的快速演示和可视化。着色器应用Cook-Torrance镜面反射和Lambertian漫反射。它还演示了如何添加和操作光实体,以及如何以光数据的形式将数据传递到GPU上,其中对光数组的查找由无绑定ID查找驱动。
聚光灯
与点光源演示类似,此示例展示了聚光灯,它们在单独的循环中处理,并且它们的数据存储在单独的结构化缓冲区中。
方向光源
另一个光线类型示例,方向光被处理并存储在与点光源和聚光源分开的结构化缓冲区中。
测试光栅化状态
这是一个基本测试,用于验证在 .pmfx
配置文件中提供的光栅化状态数据的正确性。使用前表面、后表面和无裁剪渲染了一些原语,并使用线框填充模式进行了另一个绘制调用。
测试混合状态
这个基本测试展示了各种不同的混合模式。它涵盖了常见情况:无混合、alpha混合和加法混合,以及一些更神秘的混合操作,如反向减法和最小/最大混合操作。
测试立方体贴图
这是一个测试,用于验证纹理管道和立方体贴图加载的正确性。使用 texturec
将存储在文件夹中的6个输入面图像打包成一个具有卷积米波级别的 .dds
图像。不同的球面绘制调用单独查找米波级别,以验证米波和面是否已正确加载,并作为使用基于图像的光线卷积的起点。
测试纹理2D数组
这是一个简单的测试,用于验证加载2D纹理数组的管道数据。对纹理数组应用了一个简单的动画,以滚动通过各种数组切片,并且根据硬件米波级别选择自动选择米波级别。
测试纹理3D
该示例加载并渲染了一个包含有符号距离场数据的3D纹理。该图像是在我的C++引擎 [pmtech] 中生成的,该示例作为测试,以确保3D纹理加载正确无误。它还提供了一个简单的演示,说明如何可视化/射线追踪有符号距离场体积。
测试计算
这是一个简单的示例,展示了如何通过 pmfx
和 ecs
系统配置计算过程。我们设置了一个基本的计算过程,将一些噪声写入一个3D读写纹理,然后在光栅化过程中使用基本的3D射线追踪来追踪体积。
测试多个渲染目标
这个示例演示并测试了多个渲染目标输出。它渲染到多个纹理,就像为g缓冲区延迟设置一样。目标启用了MSAA,然后在计算着色器中直接从MSAA资源中采样,输出一个MSAA片段,并将屏幕分成4个象限,以显示MRT设置的输出。
测试
有独立的测试和客户端/插件测试,用于测试图形API功能。这需要一个具有GPU和非无头模式的测试运行器,因此我正在使用我的家用电脑作为自托管的动作运行器。您可以自己运行测试,但由于需要GPU设备和插件加载,测试需要单线程运行。
cargo test -- --test-threads=1
这被包装在 pmbuild
中,因此您也可以运行
pmbuild test
未来计划
- Linux
- Vulkan
- macOS
- Metal
- AV Foundation
- WASM
- WebGPU
贡献
欢迎所有类型的贡献,您可以创建一个分支并发送PR,如果您想提交小的修复或改进。任何对开发感兴趣的人,我很高兴接受不同经验水平的人来帮助这个项目,特别是有更多Rust经验的人。如果您感兴趣,可以通过 Twitter 或 Discord 联系我。
依赖关系
~22–63MB
~1M SLoC