use sdl3::{
event::Event,
gpu::{
Buffer, BufferBinding, BufferRegion, BufferUsageFlags, ColorTargetDescription,
ColorTargetInfo, CompareOp, CopyPass, CullMode, DepthStencilState, DepthStencilTargetInfo,
Device, FillMode, GraphicsPipelineTargetInfo, IndexElementSize, LoadOp, PrimitiveType,
RasterizerState, SampleCount, ShaderFormat, ShaderStage, StoreOp, TextureCreateInfo,
TextureFormat, TextureType, TextureUsage, TransferBuffer, TransferBufferLocation,
TransferBufferUsage, VertexAttribute, VertexBufferDescription, VertexElementFormat,
VertexInputRate, VertexInputState,
},
keyboard::Keycode,
pixels::Color,
Error,
};
#[repr(packed)]
#[derive(Copy, Clone)]
pub struct VertexPosition {
pub x: f32,
pub y: f32,
pub z: f32,
}
const CUBE_VERTICES: &[VertexPosition] = &[
VertexPosition {
x: -0.5,
y: -0.5,
z: -0.5,
},
VertexPosition {
x: 0.5,
y: -0.5,
z: -0.5,
},
VertexPosition {
x: 0.5,
y: 0.5,
z: -0.5,
},
VertexPosition {
x: -0.5,
y: 0.5,
z: -0.5,
},
VertexPosition {
x: -0.5,
y: -0.5,
z: 0.5,
},
VertexPosition {
x: 0.5,
y: -0.5,
z: 0.5,
},
VertexPosition {
x: 0.5,
y: 0.5,
z: 0.5,
},
VertexPosition {
x: -0.5,
y: 0.5,
z: 0.5,
},
];
const CUBE_INDICES: &[u16] = &[
0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7, 4, 0, 3, 3, 7, 4, 2, 1, 5, 5, 6, 2, 7, 3, 2, 2, 6, 7, 0, 4, 5, 5, 1, 0, ];
const WINDOW_SIZE: u32 = 800;
pub fn main() -> Result<(), Box<dyn std::error::Error>> {
let sdl_context = sdl3::init()?;
let video_subsystem = sdl_context.video()?;
let window = video_subsystem
.window("rust-sdl3 demo: GPU (cube)", WINDOW_SIZE, WINDOW_SIZE)
.position_centered()
.build()
.map_err(|e| e.to_string())?;
let gpu = Device::new(
ShaderFormat::SPIRV | ShaderFormat::DXIL | ShaderFormat::DXBC | ShaderFormat::METALLIB,
true,
)?
.with_window(&window)?;
let vert_shader = gpu
.create_shader()
.with_code(
ShaderFormat::SPIRV,
include_bytes!("shaders/cube.vert.spv"),
ShaderStage::Vertex,
)
.with_uniform_buffers(1)
.with_entrypoint(c"main")
.build()?;
let frag_shader = gpu
.create_shader()
.with_code(
ShaderFormat::SPIRV,
include_bytes!("shaders/cube.frag.spv"),
ShaderStage::Fragment,
)
.with_entrypoint(c"main")
.build()?;
let swapchain_format = gpu.get_swapchain_texture_format(&window);
let pipeline = gpu
.create_graphics_pipeline()
.with_primitive_type(PrimitiveType::TriangleList)
.with_fragment_shader(&frag_shader)
.with_vertex_shader(&vert_shader)
.with_vertex_input_state(
VertexInputState::new()
.with_vertex_buffer_descriptions(&[VertexBufferDescription::new()
.with_slot(0)
.with_pitch(size_of::<VertexPosition>() as u32)
.with_input_rate(VertexInputRate::Vertex)
.with_instance_step_rate(0)])
.with_vertex_attributes(&[VertexAttribute::new()
.with_format(VertexElementFormat::Float3)
.with_location(0)
.with_buffer_slot(0)
.with_offset(0)]),
)
.with_rasterizer_state(
RasterizerState::new()
.with_fill_mode(FillMode::Fill)
.with_cull_mode(CullMode::None),
)
.with_depth_stencil_state(
DepthStencilState::new()
.with_enable_depth_test(true)
.with_enable_depth_write(true)
.with_compare_op(CompareOp::Less),
)
.with_target_info(
GraphicsPipelineTargetInfo::new()
.with_color_target_descriptions(&[
ColorTargetDescription::new().with_format(swapchain_format)
])
.with_has_depth_stencil_target(true)
.with_depth_stencil_format(TextureFormat::D16Unorm),
)
.build()?;
drop(vert_shader);
drop(frag_shader);
let vertices_len_bytes = std::mem::size_of_val(CUBE_VERTICES);
let indices_len_bytes = std::mem::size_of_val(CUBE_INDICES);
let transfer_buffer = gpu
.create_transfer_buffer()
.with_size(vertices_len_bytes.max(indices_len_bytes) as u32)
.with_usage(TransferBufferUsage::UPLOAD)
.build()?;
let copy_commands = gpu.acquire_command_buffer()?;
let copy_pass = gpu.begin_copy_pass(©_commands)?;
let vertex_buffer = create_buffer_with_data(
&gpu,
&transfer_buffer,
©_pass,
BufferUsageFlags::VERTEX,
CUBE_VERTICES,
)?;
let index_buffer = create_buffer_with_data(
&gpu,
&transfer_buffer,
©_pass,
BufferUsageFlags::INDEX,
CUBE_INDICES,
)?;
drop(transfer_buffer);
gpu.end_copy_pass(copy_pass);
copy_commands.submit()?;
let mut depth_texture = gpu.create_texture(
TextureCreateInfo::new()
.with_type(TextureType::_2D)
.with_width(WINDOW_SIZE)
.with_height(WINDOW_SIZE)
.with_layer_count_or_depth(1)
.with_num_levels(1)
.with_sample_count(SampleCount::NoMultiSampling)
.with_format(TextureFormat::D16Unorm)
.with_usage(TextureUsage::SAMPLER | TextureUsage::DEPTH_STENCIL_TARGET),
)?;
let mut rotation = 45.0f32;
let mut event_pump = sdl_context.event_pump()?;
'running: loop {
for event in event_pump.poll_iter() {
match event {
Event::Quit { .. }
| Event::KeyDown {
keycode: Some(Keycode::Escape),
..
} => break 'running,
_ => {}
}
}
let mut command_buffer = gpu.acquire_command_buffer()?;
if let Ok(swapchain) = command_buffer.wait_and_acquire_swapchain_texture(&window) {
let color_targets = [ColorTargetInfo::default()
.with_texture(&swapchain)
.with_load_op(LoadOp::CLEAR)
.with_store_op(StoreOp::STORE)
.with_clear_color(Color::RGB(128, 128, 128))];
let depth_target = DepthStencilTargetInfo::new()
.with_texture(&mut depth_texture)
.with_cycle(true)
.with_clear_depth(1.0)
.with_clear_stencil(0)
.with_load_op(LoadOp::CLEAR)
.with_store_op(StoreOp::STORE)
.with_stencil_load_op(LoadOp::CLEAR)
.with_stencil_store_op(StoreOp::STORE);
let render_pass =
gpu.begin_render_pass(&command_buffer, &color_targets, Some(&depth_target))?;
render_pass.bind_graphics_pipeline(&pipeline);
render_pass.bind_vertex_buffers(
0,
&[BufferBinding::new()
.with_buffer(&vertex_buffer)
.with_offset(0)],
);
render_pass.bind_index_buffer(
&BufferBinding::new()
.with_buffer(&index_buffer)
.with_offset(0),
IndexElementSize::_16BIT,
);
command_buffer.push_vertex_uniform_data(0, &rotation);
rotation += 0.1f32;
render_pass.draw_indexed_primitives(CUBE_INDICES.len() as u32, 1, 0, 0, 0);
gpu.end_render_pass(render_pass);
command_buffer.submit()?;
} else {
command_buffer.cancel();
}
}
Ok(())
}
fn create_buffer_with_data<T: Copy>(
gpu: &Device,
transfer_buffer: &TransferBuffer,
copy_pass: &CopyPass,
usage: BufferUsageFlags,
data: &[T],
) -> Result<Buffer, Error> {
let len_bytes = std::mem::size_of_val(data);
let buffer = gpu
.create_buffer()
.with_size(len_bytes as u32)
.with_usage(usage)
.build()?;
let mut map = transfer_buffer.map::<T>(gpu, true);
let mem = map.mem_mut();
for (index, &value) in data.iter().enumerate() {
mem[index] = value;
}
map.unmap();
copy_pass.upload_to_gpu_buffer(
TransferBufferLocation::new()
.with_offset(0)
.with_transfer_buffer(transfer_buffer),
BufferRegion::new()
.with_offset(0)
.with_size(len_bytes as u32)
.with_buffer(&buffer),
true,
);
Ok(buffer)
}