Expand description
Provides a high performance Vulkan 1.2 driver using smart pointers. Supports windowed and headless rendering with a fully-featured render graph and resource pooling types.
This crate allows graphics programmers to focus on acceleration structure, buffer, and image
resources and the shader pipelines they are accessed from. There are no restrictions or opinions
placed on the types of graphics algorithms you might create. Some implementations of common graphics
patterns are provided in the contrib
directory.
§Getting Sarted
Typical usage involves creating an operating system window event loop, and rendering frames
using the provided FrameContext
closure. The EventLoop
builder handles creating an instance
of the Device
driver, however you may construct one manually for headless rendering.
use screen_13::prelude::*;
fn main() -> Result<(), DisplayError> {
let event_loop = EventLoop::new().build()?;
// Use the device to create resources and pipelines before running
let device = &event_loop.device;
event_loop.run(|frame| {
// You may also create resources and pipelines while running
let device = &frame.device;
})
}
§Resources and Pipelines
All resources and pipelines, as well as the driver itself, use shared reference tracking to keep
pointers alive. Screen 13 uses std::sync::Arc
to track references.
§Information
All driver
types have associated information structures which describe their properties.
Each object provides a create
function which uses the information to return an instance.
Resource | Create Using |
---|---|
AccelerationStructureInfo | AccelerationStructure::create |
BufferInfo | Buffer::create or Buffer::create_from_slice |
ImageInfo | Image::create |
For example, a typical host-mappable buffer:
let info = BufferInfo::host_mem(1024, vk::BufferUsageFlags::STORAGE_BUFFER);
let my_buf = Buffer::create(&device, info)?;
Pipeline | Create Using |
---|---|
ComputePipelineInfo | ComputePipeline::create |
GraphicPipelineInfo | GraphicPipeline::create |
RayTracePipelineInfo | RayTracePipeline::create |
For example, a graphics pipeline:
// shader code is raw SPIR-V code as bytes
let vert = Shader::new_vertex(my_vert_code.as_slice());
let frag = Shader::new_fragment(my_frag_code.as_slice());
let info = GraphicPipelineInfo::default();
let my_pipeline = GraphicPipeline::create(&device, info, [vert, frag])?;
§Pooling
Multiple pool
types are available to reduce the impact of frequently creating and dropping
resources. Leased resources behave identically to owned resources and can be used in a render graph.
Resource aliasing is also availble as an optional way to reduce the number of concurrent resources that may be required.
For example, leasing an image:
let mut pool = LazyPool::new(&device);
let info = ImageInfo::image_2d(8, 8, vk::Format::R8G8B8A8_UNORM, vk::ImageUsageFlags::STORAGE);
let my_image = pool.lease(info)?;
§Render Graph Operations
All rendering in Screen 13 is performed using a RenderGraph
composed of user-specified passes,
which may include pipelines and read/write access to resources. Recorded passes are automatically
optimized before submission to the graphics hardware.
Some notes about the awesome render pass optimization which was totally stolen from Granite:
- Scheduling: passes are submitted to the Vulkan API using batches designed for low-latency
- Re-ordering: passes are shuffled using a heuristic which gives the GPU more time to complete work
- Merging: compatible passes are merged into dynamic subpasses when it is more efficient (on-tile rendering)
- Aliasing: resources and pipelines are optimized to emit minimal barriers per unit of work (max one, typically zero)
§Nodes
Resources may be directly bound to a render graph. During the time a resource is bound we refer to
it as a node. Bound nodes may only be used with the graphs they were bound to. Nodes implement
Copy
to make using them easier.
println!("{:?}", buffer); // Buffer
println!("{:?}", image); // Image
// Bind our resources into opaque "usize" nodes
let buffer = graph.bind_node(buffer);
let image = graph.bind_node(image);
// The results have unique types!
println!("{:?}", buffer); // BufferNode
println!("{:?}", image); // ImageNode
// Unbind nodes back into resources (Optional!)
let buffer = graph.unbind_node(buffer);
let image = graph.unbind_node(image);
// Magically, they return to the correct types! (the graph wrapped them in Arc for us)
println!("{:?}", buffer); // Arc<Buffer>
println!("{:?}", image); // Arc<Image>
Note: See this code for all the things that can be bound or unbound from a graph.
Note: Once unbound, the node is invalid and should be dropped.
§Access and synchronization
Render graphs and their passes contain a set of functions used to handle Vulkan synchronization with
prefixes of access
, read
, or write
. For each resource used in a computing, graphics subpass,
ray tracing, or general command buffer you must call an access function. Generally choose a read
or write
function unless you want to be most efficient.
Example:
let mut graph = RenderGraph::new();
let buffer_node = graph.bind_node(buffer);
let image_node = graph.bind_node(image);
graph
.begin_pass("Do some raw Vulkan or interop with another Vulkan library")
.record_cmd_buf(move |device, cmd_buf, bindings| unsafe {
// I always run first!
})
.read_node(buffer_node) // <-- These two functions, read_node/write_node, completely
.write_node(image_node) // handle vulkan synchronization.
.record_cmd_buf(move |device, cmd_buf, bindings| unsafe {
// device is &ash::Device
// cmd_buf is vk::CommandBuffer
// bindings is a magical object you can retrieve the Vulkan resource from
let vk_buffer: vk::Buffer = *bindings[buffer_node];
let vk_image: vk::Image = *bindings[image_node];
// You are free to READ vk_buffer and WRITE vk_image!
});
§Shader pipelines
Pipeline instances may be bound to a PassRef
in order to execute the associated shader code:
graph
.begin_pass("My compute pass")
.bind_pipeline(&my_compute_pipeline)
.record_compute(|compute, _| {
compute.push_constants(&42u32.to_ne_bytes())
.dispatch(128, 1, 1);
});
§Image samplers
By default, Screen 13 will use “linear repeat-mode” samplers unless a special suffix appears as
part of the name within GLSL or HLSL shader code. The _sampler_123
suffix should be used where
1
, 2
, and 3
are replaced with:
l
forLINEAR
texel filtering (default) orn
forNEAREST
l
(default) orn
, as above, but for mipmap filtering- Addressing mode where:
b
isCLAMP_TO_BORDER
e
isCLAMP_TO_EDGE
m
isMIRRORED_REPEAT
r
isREPEAT
For example, the following sampler named pages_sampler_nnr
specifies nearest texel/mipmap modes and repeat addressing:
layout(set = 0, binding = 0) uniform sampler2D pages_sampler_nnr[NUM_PAGES];
For more complex image sampling, use ShaderBuilder::image_sampler
to specify the exact image
sampling mode.
§Vertex input
Optional name suffixes are used in the same way with vertex input as with image samplers. The additional attribution of your shader code is optional but may help in a few scenarios:
- Per-instance vertex rate data
- Multiple vertex buffer binding indexes
The data for vertex input is assumed to be per-vertex and bound to vertex buffer binding index zero.
Add _ibindX
for per-instance data, or the matching _vbindX
for per-vertex data where X
is
replaced with the vertex buffer binding index in each case.
For more complex vertex layouts, use the ShaderBuilder::vertex_input
to specify the exact
layout.
Modules§
- Vulkan 1.2 interface based on smart pointers.
- Rendering operations and command submission.
- Resource leasing and pooling types.
- Things which are used in almost every single Screen 13 program.
Structs§
- A physical display interface.
- Pumps an operating system event loop in order to handle input and other events while drawing to the screen, continuously.
- Builder for
EventLoop
. - A request to render a single frame to the provided render graph.
Enums§
- Describes error conditions relating to physical displays.
- Describes a screen mode for display.