wgpu/wgpu-hal/examples/halmark/main.rs

842 lines
30 KiB
Rust
Raw Normal View History

//! This example shows basic usage of wgpu-hal by rendering
//! a ton of moving sprites, each with a separate texture and draw call.
2021-06-08 18:58:48 +00:00
extern crate wgpu_hal as hal;
2021-06-14 05:35:05 +00:00
use hal::{
Adapter as _, CommandEncoder as _, Device as _, Instance as _, Queue as _, Surface as _,
2021-06-14 05:35:05 +00:00
};
use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle};
2021-06-08 18:58:48 +00:00
use std::{
borrow::{Borrow, Cow},
iter, mem, ptr,
time::Instant,
};
2021-06-08 18:58:48 +00:00
const MAX_BUNNIES: usize = 1 << 20;
const BUNNY_SIZE: f32 = 0.15 * 256.0;
const GRAVITY: f32 = -9.8 * 100.0;
const MAX_VELOCITY: f32 = 750.0;
const COMMAND_BUFFER_PER_CONTEXT: usize = 100;
const DESIRED_FRAMES: u32 = 3;
2021-06-08 18:58:48 +00:00
#[repr(C)]
#[derive(Clone, Copy)]
struct Globals {
mvp: [[f32; 4]; 4],
size: [f32; 2],
pad: [f32; 2],
}
#[repr(C, align(256))]
#[derive(Clone, Copy)]
struct Locals {
position: [f32; 2],
velocity: [f32; 2],
color: u32,
_pad: u32,
}
struct ExecutionContext<A: hal::Api> {
encoder: A::CommandEncoder,
fence: A::Fence,
fence_value: hal::FenceValue,
used_views: Vec<A::TextureView>,
used_cmd_bufs: Vec<A::CommandBuffer>,
frames_recorded: usize,
}
impl<A: hal::Api> ExecutionContext<A> {
unsafe fn wait_and_clear(&mut self, device: &A::Device) {
device.wait(&self.fence, self.fence_value, !0).unwrap();
self.encoder.reset_all(self.used_cmd_bufs.drain(..));
for view in self.used_views.drain(..) {
device.destroy_texture_view(view);
}
self.frames_recorded = 0;
}
2021-06-14 05:35:05 +00:00
}
2021-06-10 15:29:35 +00:00
#[allow(dead_code)]
2021-06-08 18:58:48 +00:00
struct Example<A: hal::Api> {
instance: A::Instance,
adapter: A::Adapter,
2021-06-08 18:58:48 +00:00
surface: A::Surface,
surface_format: wgt::TextureFormat,
device: A::Device,
queue: A::Queue,
global_group: A::BindGroup,
local_group: A::BindGroup,
global_group_layout: A::BindGroupLayout,
local_group_layout: A::BindGroupLayout,
2021-06-08 18:58:48 +00:00
pipeline_layout: A::PipelineLayout,
shader: A::ShaderModule,
2021-06-08 18:58:48 +00:00
pipeline: A::RenderPipeline,
bunnies: Vec<Locals>,
local_buffer: A::Buffer,
local_alignment: u32,
2021-06-10 15:29:35 +00:00
global_buffer: A::Buffer,
sampler: A::Sampler,
texture: A::Texture,
texture_view: A::TextureView,
contexts: Vec<ExecutionContext<A>>,
context_index: usize,
2021-06-08 18:58:48 +00:00
extent: [u32; 2],
start: Instant,
}
impl<A: hal::Api> Example<A> {
fn init(window: &winit::window::Window) -> Result<Self, hal::InstanceError> {
2021-06-12 05:04:15 +00:00
let instance_desc = hal::InstanceDescriptor {
name: "example",
2021-06-17 21:13:40 +00:00
flags: if cfg!(debug_assertions) {
2021-06-30 18:43:36 +00:00
hal::InstanceFlags::all()
2021-06-17 21:13:40 +00:00
} else {
2021-06-30 18:43:36 +00:00
hal::InstanceFlags::empty()
2021-06-17 21:13:40 +00:00
},
// Can't rely on having DXC available, so use FXC instead
dx12_shader_compiler: wgt::Dx12Compiler::Fxc,
2021-06-12 05:04:15 +00:00
};
let instance = unsafe { A::Instance::init(&instance_desc)? };
let mut surface = unsafe {
instance
.create_surface(window.raw_display_handle(), window.raw_window_handle())
.unwrap()
};
2021-06-12 05:04:15 +00:00
let (adapter, capabilities) = unsafe {
let mut adapters = instance.enumerate_adapters();
if adapters.is_empty() {
return Err(hal::InstanceError);
}
let exposed = adapters.swap_remove(0);
(exposed.adapter, exposed.capabilities)
2021-06-08 18:58:48 +00:00
};
let surface_caps =
unsafe { adapter.surface_capabilities(&surface) }.ok_or(hal::InstanceError)?;
log::info!("Surface caps: {:#?}", surface_caps);
let hal::OpenDevice { device, mut queue } = unsafe {
adapter
.open(wgt::Features::empty(), &wgt::Limits::default())
.unwrap()
};
2021-06-08 18:58:48 +00:00
let window_size: (u32, u32) = window.inner_size().into();
let surface_config = hal::SurfaceConfiguration {
swap_chain_size: DESIRED_FRAMES.clamp(
*surface_caps.swap_chain_sizes.start(),
*surface_caps.swap_chain_sizes.end(),
),
2021-06-08 18:58:48 +00:00
present_mode: wgt::PresentMode::Fifo,
composite_alpha_mode: wgt::CompositeAlphaMode::Opaque,
2021-06-10 15:29:35 +00:00
format: wgt::TextureFormat::Bgra8UnormSrgb,
2021-06-08 18:58:48 +00:00
extent: wgt::Extent3d {
width: window_size.0,
height: window_size.1,
depth_or_array_layers: 1,
},
usage: hal::TextureUses::COLOR_TARGET,
view_formats: vec![],
2021-06-08 18:58:48 +00:00
};
unsafe {
surface.configure(&device, &surface_config).unwrap();
};
let naga_shader = {
let shader_file = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("examples")
2021-06-10 15:29:35 +00:00
.join("halmark")
2021-06-08 18:58:48 +00:00
.join("shader.wgsl");
let source = std::fs::read_to_string(shader_file).unwrap();
let module = naga::front::wgsl::Frontend::new().parse(&source).unwrap();
2021-06-08 18:58:48 +00:00
let info = naga::valid::Validator::new(
naga::valid::ValidationFlags::all(),
naga::valid::Capabilities::empty(),
)
.validate(&module)
.unwrap();
hal::NagaShader {
module: Cow::Owned(module),
info,
}
2021-06-08 18:58:48 +00:00
};
let shader_desc = hal::ShaderModuleDescriptor {
label: None,
runtime_checks: false,
};
2021-06-08 18:58:48 +00:00
let shader = unsafe {
2021-06-10 15:29:35 +00:00
device
.create_shader_module(&shader_desc, hal::ShaderInput::Naga(naga_shader))
2021-06-10 15:29:35 +00:00
.unwrap()
2021-06-08 18:58:48 +00:00
};
let global_bgl_desc = hal::BindGroupLayoutDescriptor {
label: None,
flags: hal::BindGroupLayoutFlags::empty(),
2021-06-08 19:18:06 +00:00
entries: &[
2021-06-08 18:58:48 +00:00
wgt::BindGroupLayoutEntry {
binding: 0,
2021-06-30 18:43:36 +00:00
visibility: wgt::ShaderStages::VERTEX,
2021-06-08 18:58:48 +00:00
ty: wgt::BindingType::Buffer {
ty: wgt::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: wgt::BufferSize::new(mem::size_of::<Globals>() as _),
},
count: None,
},
wgt::BindGroupLayoutEntry {
binding: 1,
2021-06-30 18:43:36 +00:00
visibility: wgt::ShaderStages::FRAGMENT,
2021-06-08 18:58:48 +00:00
ty: wgt::BindingType::Texture {
sample_type: wgt::TextureSampleType::Float { filterable: true },
view_dimension: wgt::TextureViewDimension::D2,
multisampled: false,
},
count: None,
},
wgt::BindGroupLayoutEntry {
binding: 2,
2021-06-30 18:43:36 +00:00
visibility: wgt::ShaderStages::FRAGMENT,
ty: wgt::BindingType::Sampler(wgt::SamplerBindingType::Filtering),
2021-06-08 18:58:48 +00:00
count: None,
},
2021-06-08 19:18:06 +00:00
],
2021-06-08 18:58:48 +00:00
};
let global_group_layout =
2021-06-08 18:58:48 +00:00
unsafe { device.create_bind_group_layout(&global_bgl_desc).unwrap() };
let local_bgl_desc = hal::BindGroupLayoutDescriptor {
label: None,
flags: hal::BindGroupLayoutFlags::empty(),
2021-06-08 19:18:06 +00:00
entries: &[wgt::BindGroupLayoutEntry {
2021-06-08 18:58:48 +00:00
binding: 0,
2021-06-30 18:43:36 +00:00
visibility: wgt::ShaderStages::VERTEX,
2021-06-08 18:58:48 +00:00
ty: wgt::BindingType::Buffer {
ty: wgt::BufferBindingType::Uniform,
has_dynamic_offset: true,
min_binding_size: wgt::BufferSize::new(mem::size_of::<Locals>() as _),
},
count: None,
2021-06-08 19:18:06 +00:00
}],
2021-06-08 18:58:48 +00:00
};
let local_group_layout =
2021-06-08 18:58:48 +00:00
unsafe { device.create_bind_group_layout(&local_bgl_desc).unwrap() };
let pipeline_layout_desc = hal::PipelineLayoutDescriptor {
label: None,
2021-07-27 05:56:45 +00:00
flags: hal::PipelineLayoutFlags::empty(),
bind_group_layouts: &[&global_group_layout, &local_group_layout],
2021-06-08 19:18:06 +00:00
push_constant_ranges: &[],
2021-06-08 18:58:48 +00:00
};
let pipeline_layout = unsafe {
device
.create_pipeline_layout(&pipeline_layout_desc)
.unwrap()
};
let pipeline_desc = hal::RenderPipelineDescriptor {
label: None,
layout: &pipeline_layout,
vertex_stage: hal::ProgrammableStage {
module: &shader,
2021-06-08 19:18:06 +00:00
entry_point: "vs_main",
2021-06-08 18:58:48 +00:00
},
2021-06-08 19:18:06 +00:00
vertex_buffers: &[],
2021-06-08 18:58:48 +00:00
fragment_stage: Some(hal::ProgrammableStage {
module: &shader,
2021-06-08 19:18:06 +00:00
entry_point: "fs_main",
2021-06-08 18:58:48 +00:00
}),
primitive: wgt::PrimitiveState {
topology: wgt::PrimitiveTopology::TriangleStrip,
..wgt::PrimitiveState::default()
},
depth_stencil: None,
multisample: wgt::MultisampleState::default(),
color_targets: &[Some(wgt::ColorTargetState {
2021-06-08 18:58:48 +00:00
format: surface_config.format,
blend: Some(wgt::BlendState::ALPHA_BLENDING),
write_mask: wgt::ColorWrites::default(),
})],
multiview: None,
2021-06-08 18:58:48 +00:00
};
let pipeline = unsafe { device.create_render_pipeline(&pipeline_desc).unwrap() };
2021-06-10 15:29:35 +00:00
let texture_data = vec![0xFFu8; 4];
2021-06-08 18:58:48 +00:00
let staging_buffer_desc = hal::BufferDescriptor {
label: Some("stage"),
size: texture_data.len() as wgt::BufferAddress,
usage: hal::BufferUses::MAP_WRITE | hal::BufferUses::COPY_SRC,
2021-06-30 18:43:36 +00:00
memory_flags: hal::MemoryFlags::TRANSIENT | hal::MemoryFlags::PREFER_COHERENT,
2021-06-08 18:58:48 +00:00
};
let staging_buffer = unsafe { device.create_buffer(&staging_buffer_desc).unwrap() };
unsafe {
2021-06-10 20:05:44 +00:00
let mapping = device
2021-06-08 18:58:48 +00:00
.map_buffer(&staging_buffer, 0..staging_buffer_desc.size)
.unwrap();
2021-06-10 20:05:44 +00:00
ptr::copy_nonoverlapping(
texture_data.as_ptr(),
mapping.ptr.as_ptr(),
texture_data.len(),
);
2021-06-08 18:58:48 +00:00
device.unmap_buffer(&staging_buffer).unwrap();
2021-06-10 20:05:44 +00:00
assert!(mapping.is_coherent);
2021-06-08 18:58:48 +00:00
}
let texture_desc = hal::TextureDescriptor {
label: None,
size: wgt::Extent3d {
width: 1,
height: 1,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgt::TextureDimension::D2,
format: wgt::TextureFormat::Rgba8UnormSrgb,
2021-07-28 04:09:57 +00:00
usage: hal::TextureUses::COPY_DST | hal::TextureUses::RESOURCE,
2021-06-30 18:43:36 +00:00
memory_flags: hal::MemoryFlags::empty(),
view_formats: vec![],
2021-06-08 18:58:48 +00:00
};
let texture = unsafe { device.create_texture(&texture_desc).unwrap() };
let cmd_encoder_desc = hal::CommandEncoderDescriptor {
2021-06-14 05:35:05 +00:00
label: None,
queue: &queue,
};
let mut cmd_encoder = unsafe { device.create_command_encoder(&cmd_encoder_desc).unwrap() };
unsafe { cmd_encoder.begin_encoding(Some("init")).unwrap() };
2021-06-08 18:58:48 +00:00
{
let buffer_barrier = hal::BufferBarrier {
buffer: &staging_buffer,
usage: hal::BufferUses::empty()..hal::BufferUses::COPY_SRC,
2021-06-08 18:58:48 +00:00
};
let texture_barrier1 = hal::TextureBarrier {
texture: &texture,
range: wgt::ImageSubresourceRange::default(),
usage: hal::TextureUses::UNINITIALIZED..hal::TextureUses::COPY_DST,
2021-06-08 18:58:48 +00:00
};
let texture_barrier2 = hal::TextureBarrier {
texture: &texture,
range: wgt::ImageSubresourceRange::default(),
2021-07-28 04:09:57 +00:00
usage: hal::TextureUses::COPY_DST..hal::TextureUses::RESOURCE,
2021-06-08 18:58:48 +00:00
};
let copy = hal::BufferTextureCopy {
buffer_layout: wgt::ImageDataLayout {
offset: 0,
bytes_per_row: Some(4),
2021-06-08 18:58:48 +00:00
rows_per_image: None,
},
texture_base: hal::TextureCopyBase {
origin: wgt::Origin3d::ZERO,
mip_level: 0,
array_layer: 0,
2021-06-30 18:43:36 +00:00
aspect: hal::FormatAspects::COLOR,
2021-06-08 18:58:48 +00:00
},
size: hal::CopyExtent {
width: 1,
height: 1,
depth: 1,
},
2021-06-08 18:58:48 +00:00
};
unsafe {
cmd_encoder.transition_buffers(iter::once(buffer_barrier));
cmd_encoder.transition_textures(iter::once(texture_barrier1));
cmd_encoder.copy_buffer_to_texture(&staging_buffer, &texture, iter::once(copy));
cmd_encoder.transition_textures(iter::once(texture_barrier2));
2021-06-08 18:58:48 +00:00
}
}
let sampler_desc = hal::SamplerDescriptor {
label: None,
address_modes: [wgt::AddressMode::ClampToEdge; 3],
mag_filter: wgt::FilterMode::Linear,
min_filter: wgt::FilterMode::Nearest,
mipmap_filter: wgt::FilterMode::Nearest,
2023-03-21 16:37:30 +00:00
lod_clamp: 0.0..32.0,
2021-06-08 18:58:48 +00:00
compare: None,
2023-03-21 16:37:30 +00:00
anisotropy_clamp: 1,
2021-06-08 18:58:48 +00:00
border_color: None,
};
let sampler = unsafe { device.create_sampler(&sampler_desc).unwrap() };
let globals = Globals {
// cgmath::ortho() projection
mvp: [
[2.0 / window_size.0 as f32, 0.0, 0.0, 0.0],
[0.0, 2.0 / window_size.1 as f32, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0],
[-1.0, -1.0, 0.0, 1.0],
],
size: [BUNNY_SIZE; 2],
pad: [0.0; 2],
};
let global_buffer_desc = hal::BufferDescriptor {
label: Some("global"),
size: mem::size_of::<Globals>() as wgt::BufferAddress,
usage: hal::BufferUses::MAP_WRITE | hal::BufferUses::UNIFORM,
2021-06-30 18:43:36 +00:00
memory_flags: hal::MemoryFlags::PREFER_COHERENT,
2021-06-08 18:58:48 +00:00
};
let global_buffer = unsafe {
let buffer = device.create_buffer(&global_buffer_desc).unwrap();
2021-06-10 20:05:44 +00:00
let mapping = device
2021-06-08 18:58:48 +00:00
.map_buffer(&buffer, 0..global_buffer_desc.size)
.unwrap();
ptr::copy_nonoverlapping(
&globals as *const Globals as *const u8,
2021-06-10 20:05:44 +00:00
mapping.ptr.as_ptr(),
2021-06-08 18:58:48 +00:00
mem::size_of::<Globals>(),
);
device.unmap_buffer(&buffer).unwrap();
2021-06-10 20:05:44 +00:00
assert!(mapping.is_coherent);
2021-06-08 18:58:48 +00:00
buffer
};
let local_alignment = wgt::math::align_to(
mem::size_of::<Locals>() as u32,
capabilities.limits.min_uniform_buffer_offset_alignment,
);
2021-06-08 18:58:48 +00:00
let local_buffer_desc = hal::BufferDescriptor {
label: Some("local"),
size: (MAX_BUNNIES as wgt::BufferAddress) * (local_alignment as wgt::BufferAddress),
usage: hal::BufferUses::MAP_WRITE | hal::BufferUses::UNIFORM,
2021-06-30 18:43:36 +00:00
memory_flags: hal::MemoryFlags::PREFER_COHERENT,
2021-06-08 18:58:48 +00:00
};
let local_buffer = unsafe { device.create_buffer(&local_buffer_desc).unwrap() };
let view_desc = hal::TextureViewDescriptor {
label: None,
format: texture_desc.format,
dimension: wgt::TextureViewDimension::D2,
2021-07-28 04:09:57 +00:00
usage: hal::TextureUses::RESOURCE,
2021-06-08 18:58:48 +00:00
range: wgt::ImageSubresourceRange::default(),
};
let texture_view = unsafe { device.create_texture_view(&texture, &view_desc).unwrap() };
2021-06-08 18:58:48 +00:00
let global_group = {
let global_buffer_binding = hal::BufferBinding {
buffer: &global_buffer,
offset: 0,
size: None,
};
let texture_binding = hal::TextureBinding {
view: &texture_view,
2021-07-28 04:09:57 +00:00
usage: hal::TextureUses::RESOURCE,
};
2021-06-08 18:58:48 +00:00
let global_group_desc = hal::BindGroupDescriptor {
label: Some("global"),
layout: &global_group_layout,
buffers: &[global_buffer_binding],
samplers: &[&sampler],
textures: &[texture_binding],
2021-06-08 19:18:06 +00:00
entries: &[
2021-06-08 18:58:48 +00:00
hal::BindGroupEntry {
binding: 0,
resource_index: 0,
count: 1,
2021-06-08 18:58:48 +00:00
},
hal::BindGroupEntry {
binding: 1,
resource_index: 0,
count: 1,
2021-06-08 18:58:48 +00:00
},
hal::BindGroupEntry {
binding: 2,
resource_index: 0,
count: 1,
2021-06-08 18:58:48 +00:00
},
2021-06-08 19:18:06 +00:00
],
2021-06-08 18:58:48 +00:00
};
unsafe { device.create_bind_group(&global_group_desc).unwrap() }
};
let local_group = {
let local_buffer_binding = hal::BufferBinding {
buffer: &local_buffer,
offset: 0,
size: wgt::BufferSize::new(mem::size_of::<Locals>() as _),
};
let local_group_desc = hal::BindGroupDescriptor {
label: Some("local"),
layout: &local_group_layout,
buffers: &[local_buffer_binding],
samplers: &[],
textures: &[],
2021-06-08 19:18:06 +00:00
entries: &[hal::BindGroupEntry {
2021-06-08 18:58:48 +00:00
binding: 0,
resource_index: 0,
count: 1,
2021-06-08 19:18:06 +00:00
}],
2021-06-08 18:58:48 +00:00
};
unsafe { device.create_bind_group(&local_group_desc).unwrap() }
};
let init_fence_value = 1;
let fence = unsafe {
2021-06-10 14:54:06 +00:00
let mut fence = device.create_fence().unwrap();
let init_cmd = cmd_encoder.end_encoding().unwrap();
queue
.submit(&[&init_cmd], Some((&mut fence, init_fence_value)))
.unwrap();
device.wait(&fence, init_fence_value, !0).unwrap();
2021-06-08 18:58:48 +00:00
device.destroy_buffer(staging_buffer);
cmd_encoder.reset_all(iter::once(init_cmd));
fence
};
2021-06-08 18:58:48 +00:00
Ok(Example {
instance,
surface,
surface_format: surface_config.format,
adapter,
2021-06-08 18:58:48 +00:00
device,
queue,
pipeline_layout,
shader,
2021-06-08 18:58:48 +00:00
pipeline,
global_group,
local_group,
global_group_layout,
local_group_layout,
2021-06-08 18:58:48 +00:00
bunnies: Vec::new(),
local_buffer,
local_alignment,
2021-06-10 15:29:35 +00:00
global_buffer,
sampler,
texture,
texture_view,
contexts: vec![ExecutionContext {
encoder: cmd_encoder,
fence,
fence_value: init_fence_value + 1,
used_views: Vec::new(),
used_cmd_bufs: Vec::new(),
frames_recorded: 0,
}],
context_index: 0,
2021-06-08 18:58:48 +00:00
extent: [window_size.0, window_size.1],
start: Instant::now(),
})
}
fn is_empty(&self) -> bool {
self.bunnies.is_empty()
}
fn exit(mut self) {
unsafe {
{
let ctx = &mut self.contexts[self.context_index];
self.queue
.submit(&[], Some((&mut ctx.fence, ctx.fence_value)))
.unwrap();
}
for mut ctx in self.contexts {
ctx.wait_and_clear(&self.device);
self.device.destroy_command_encoder(ctx.encoder);
self.device.destroy_fence(ctx.fence);
}
self.device.destroy_bind_group(self.local_group);
self.device.destroy_bind_group(self.global_group);
self.device.destroy_buffer(self.local_buffer);
self.device.destroy_buffer(self.global_buffer);
self.device.destroy_texture_view(self.texture_view);
self.device.destroy_texture(self.texture);
self.device.destroy_sampler(self.sampler);
self.device.destroy_shader_module(self.shader);
self.device.destroy_render_pipeline(self.pipeline);
self.device
.destroy_bind_group_layout(self.local_group_layout);
self.device
.destroy_bind_group_layout(self.global_group_layout);
self.device.destroy_pipeline_layout(self.pipeline_layout);
self.surface.unconfigure(&self.device);
2021-07-16 05:27:30 +00:00
self.device.exit(self.queue);
self.instance.destroy_surface(self.surface);
drop(self.adapter);
}
}
2021-06-08 18:58:48 +00:00
fn update(&mut self, event: winit::event::WindowEvent) {
if let winit::event::WindowEvent::KeyboardInput {
input:
winit::event::KeyboardInput {
virtual_keycode: Some(winit::event::VirtualKeyCode::Space),
state: winit::event::ElementState::Pressed,
..
},
..
} = event
{
let spawn_count = 64 + self.bunnies.len() / 2;
let elapsed = self.start.elapsed();
let color = elapsed.as_nanos() as u32;
println!(
"Spawning {} bunnies, total at {}",
spawn_count,
self.bunnies.len() + spawn_count
);
2021-06-10 15:29:35 +00:00
for i in 0..spawn_count {
let random = ((elapsed.as_nanos() * (i + 1) as u128) & 0xFF) as f32 / 255.0;
2021-06-08 18:58:48 +00:00
let speed = random * MAX_VELOCITY - (MAX_VELOCITY * 0.5);
self.bunnies.push(Locals {
position: [0.0, 0.5 * (self.extent[1] as f32)],
velocity: [speed, 0.0],
color,
_pad: 0,
});
}
}
}
fn render(&mut self) {
let delta = 0.01;
for bunny in self.bunnies.iter_mut() {
bunny.position[0] += bunny.velocity[0] * delta;
bunny.position[1] += bunny.velocity[1] * delta;
bunny.velocity[1] += GRAVITY * delta;
if (bunny.velocity[0] > 0.0
&& bunny.position[0] + 0.5 * BUNNY_SIZE > self.extent[0] as f32)
|| (bunny.velocity[0] < 0.0 && bunny.position[0] - 0.5 * BUNNY_SIZE < 0.0)
{
bunny.velocity[0] *= -1.0;
}
if bunny.velocity[1] < 0.0 && bunny.position[1] < 0.5 * BUNNY_SIZE {
bunny.velocity[1] *= -1.0;
}
}
if !self.bunnies.is_empty() {
let size = self.bunnies.len() * self.local_alignment as usize;
unsafe {
let mapping = self
.device
.map_buffer(&self.local_buffer, 0..size as wgt::BufferAddress)
.unwrap();
ptr::copy_nonoverlapping(
self.bunnies.as_ptr() as *const u8,
mapping.ptr.as_ptr(),
size,
);
assert!(mapping.is_coherent);
self.device.unmap_buffer(&self.local_buffer).unwrap();
}
2021-06-08 18:58:48 +00:00
}
let ctx = &mut self.contexts[self.context_index];
Acquire texture: `Option<std::time::Duration>` timeouts (#2724) * surface.acquire_texture: pass Option<Duration> for timeout A std::time::Duration allows for timeouts to be specified more clearly in Rust using whatever units are convenient for the caller, and an Option also makes it clearer in case no timeout is wanted, as opposed to passing a bitwise !0 as special timeout value. Notably there was an impedance mismatch with the Vulkan backend that takes a 64bit timeout in nanoseconds and uses u64::MAX to indicate no timeout and the backend was not mapping a given u32::MAX into a u64::MAX * surface.acquire_texture: ignore timeout for Android < 11 Prior to Android 11 then Android's vkAcquireNextImageKHR implementation was non-conformant and didn't support timeouts and additionally would log a verbose warning if a timeout was requested. For reference this version of AcquireNextImageKHR doesn't support timeouts: https://android.googlesource.com/platform/frameworks/native/+/refs/tags/android-mainline-10.0.0_r13/vulkan/libvulkan/swapchain.cpp#1426 and this version does: https://android.googlesource.com/platform/frameworks/native/+/refs/tags/android-mainline-11.0.0_r45/vulkan/libvulkan/swapchain.cpp#1438 (i.e. timeout support was added in Android 11) This patch adds a dependency on the `android-properties` crate that provides a simple wrapper for the `__system_property_set` syscall so that the platform version can be read via the `ro.build.version.sdk` property and then for versions < 30 (corresponds to Android 11) any timeout given to `acquire_texture` will be ignored (and `u64::MAX` will be passed to Vulkan)
2022-06-04 16:05:40 +00:00
let surface_tex = unsafe { self.surface.acquire_texture(None).unwrap().unwrap().texture };
let target_barrier0 = hal::TextureBarrier {
texture: surface_tex.borrow(),
range: wgt::ImageSubresourceRange::default(),
usage: hal::TextureUses::UNINITIALIZED..hal::TextureUses::COLOR_TARGET,
};
unsafe {
ctx.encoder.begin_encoding(Some("frame")).unwrap();
ctx.encoder.transition_textures(iter::once(target_barrier0));
}
2021-06-08 18:58:48 +00:00
let surface_view_desc = hal::TextureViewDescriptor {
label: None,
format: self.surface_format,
dimension: wgt::TextureViewDimension::D2,
usage: hal::TextureUses::COLOR_TARGET,
2021-06-08 18:58:48 +00:00
range: wgt::ImageSubresourceRange::default(),
};
let surface_tex_view = unsafe {
self.device
.create_texture_view(surface_tex.borrow(), &surface_view_desc)
.unwrap()
};
let pass_desc = hal::RenderPassDescriptor {
label: None,
2021-06-24 05:22:38 +00:00
extent: wgt::Extent3d {
width: self.extent[0],
height: self.extent[1],
depth_or_array_layers: 1,
},
sample_count: 1,
color_attachments: &[Some(hal::ColorAttachment {
2021-06-08 18:58:48 +00:00
target: hal::Attachment {
view: &surface_tex_view,
usage: hal::TextureUses::COLOR_TARGET,
2021-06-08 18:58:48 +00:00
},
resolve_target: None,
2021-06-30 18:43:36 +00:00
ops: hal::AttachmentOps::STORE,
2021-06-08 18:58:48 +00:00
clear_value: wgt::Color {
r: 0.1,
g: 0.2,
b: 0.3,
a: 1.0,
},
})],
2021-06-08 18:58:48 +00:00
depth_stencil_attachment: None,
multiview: None,
timestamp_writes: None,
2023-08-02 23:10:21 +00:00
occlusion_query_set: None,
2021-06-08 18:58:48 +00:00
};
unsafe {
ctx.encoder.begin_render_pass(&pass_desc);
ctx.encoder.set_render_pipeline(&self.pipeline);
ctx.encoder
.set_bind_group(&self.pipeline_layout, 0, &self.global_group, &[]);
2021-06-08 18:58:48 +00:00
}
for i in 0..self.bunnies.len() {
let offset = (i as wgt::DynamicOffset) * (self.local_alignment as wgt::DynamicOffset);
2021-06-08 18:58:48 +00:00
unsafe {
ctx.encoder
.set_bind_group(&self.pipeline_layout, 1, &self.local_group, &[offset]);
ctx.encoder.draw(0, 4, 0, 1);
2021-06-08 18:58:48 +00:00
}
}
ctx.frames_recorded += 1;
let do_fence = ctx.frames_recorded > COMMAND_BUFFER_PER_CONTEXT;
let target_barrier1 = hal::TextureBarrier {
texture: surface_tex.borrow(),
range: wgt::ImageSubresourceRange::default(),
usage: hal::TextureUses::COLOR_TARGET..hal::TextureUses::PRESENT,
};
unsafe {
ctx.encoder.end_render_pass();
ctx.encoder.transition_textures(iter::once(target_barrier1));
}
unsafe {
let cmd_buf = ctx.encoder.end_encoding().unwrap();
let fence_param = if do_fence {
Some((&mut ctx.fence, ctx.fence_value))
} else {
None
};
self.queue.submit(&[&cmd_buf], fence_param).unwrap();
2021-06-08 18:58:48 +00:00
self.queue.present(&mut self.surface, surface_tex).unwrap();
ctx.used_cmd_bufs.push(cmd_buf);
ctx.used_views.push(surface_tex_view);
};
2021-06-14 05:35:05 +00:00
if do_fence {
log::info!("Context switch from {}", self.context_index);
let old_fence_value = ctx.fence_value;
if self.contexts.len() == 1 {
let hal_desc = hal::CommandEncoderDescriptor {
label: None,
queue: &self.queue,
};
self.contexts.push(unsafe {
ExecutionContext {
encoder: self.device.create_command_encoder(&hal_desc).unwrap(),
fence: self.device.create_fence().unwrap(),
fence_value: 0,
used_views: Vec::new(),
used_cmd_bufs: Vec::new(),
frames_recorded: 0,
}
});
}
self.context_index = (self.context_index + 1) % self.contexts.len();
let next = &mut self.contexts[self.context_index];
unsafe {
next.wait_and_clear(&self.device);
}
next.fence_value = old_fence_value + 1;
}
2021-06-08 18:58:48 +00:00
}
}
cfg_if::cfg_if! {
// Apple + Metal
if #[cfg(all(any(target_os = "macos", target_os = "ios"), feature = "metal"))] {
type Api = hal::api::Metal;
}
// Wasm + Vulkan
else if #[cfg(all(not(target_arch = "wasm32"), feature = "vulkan"))] {
type Api = hal::api::Vulkan;
}
// Windows + DX12
else if #[cfg(all(windows, feature = "dx12"))] {
type Api = hal::api::Dx12;
}
// Anything + GLES
else if #[cfg(feature = "gles")] {
type Api = hal::api::Gles;
}
// Fallback
else {
type Api = hal::api::Empty;
}
}
2021-06-10 14:54:06 +00:00
2021-06-08 18:58:48 +00:00
fn main() {
env_logger::init();
2021-06-08 18:58:48 +00:00
let event_loop = winit::event_loop::EventLoop::new();
let window = winit::window::WindowBuilder::new()
.with_title("hal-bunnymark")
.build(&event_loop)
.unwrap();
2021-06-10 14:54:06 +00:00
let example_result = Example::<Api>::init(&window);
let mut example = Some(example_result.expect("Selected backend is not supported"));
let mut last_frame_inst = Instant::now();
let (mut frame_count, mut accum_time) = (0, 0.0);
event_loop.run(move |event, _, control_flow| {
let _ = &window; // force ownership by the closure
*control_flow = winit::event_loop::ControlFlow::Poll;
match event {
2021-06-10 15:29:35 +00:00
winit::event::Event::RedrawEventsCleared => {
window.request_redraw();
}
winit::event::Event::WindowEvent { event, .. } => match event {
winit::event::WindowEvent::KeyboardInput {
input:
winit::event::KeyboardInput {
virtual_keycode: Some(winit::event::VirtualKeyCode::Escape),
state: winit::event::ElementState::Pressed,
..
},
..
}
| winit::event::WindowEvent::CloseRequested => {
*control_flow = winit::event_loop::ControlFlow::Exit;
}
_ => {
example.as_mut().unwrap().update(event);
}
},
winit::event::Event::RedrawRequested(_) => {
let ex = example.as_mut().unwrap();
{
accum_time += last_frame_inst.elapsed().as_secs_f32();
last_frame_inst = Instant::now();
frame_count += 1;
if frame_count == 100 && !ex.is_empty() {
println!(
"Avg frame time {}ms",
accum_time * 1000.0 / frame_count as f32
);
accum_time = 0.0;
frame_count = 0;
}
}
ex.render();
}
winit::event::Event::LoopDestroyed => {
example.take().unwrap().exit();
}
_ => {}
}
});
2021-06-08 18:58:48 +00:00
}