408: Staging belt r=kyren a=kvark

Implementing the belt itself isn't too hard, but integrating it with the example framework and winit's event loop is somewhat challenging. Perhaps, there needs to be a `LocalSpawner` parameter to `render()` like I did [in vange-rs](e7bc944ef5/bin/boilerplate.rs (L26))?

Edit: code is all ready and shiny... but it does NOT work. Buffers don't come back from mapping for some reason.

Co-authored-by: Dzmitry Malyshau <kvarkus@gmail.com>
Co-authored-by: kyren <kerriganw@gmail.com>
This commit is contained in:
bors[bot]
2020-06-25 17:44:04 +00:00
committed by GitHub
12 changed files with 373 additions and 150 deletions

View File

@@ -1,8 +1,6 @@
// Flocking boids example with gpu compute update pass
// adapted from https://github.com/austinEng/webgpu-samples/blob/master/src/examples/computeBoids.ts
extern crate rand;
#[path = "../framework.rs"]
mod framework;
@@ -31,7 +29,7 @@ impl framework::Example for Example {
sc_desc: &wgpu::SwapChainDescriptor,
device: &wgpu::Device,
_queue: &wgpu::Queue,
) -> (Self, Option<wgpu::CommandBuffer>) {
) -> Self {
// load (and compile) shaders and create shader modules
let boids_module = device.create_shader_module(wgpu::include_spirv!("boids.comp.spv"));
@@ -221,18 +219,15 @@ impl framework::Example for Example {
// returns Example struct and No encoder commands
(
Example {
particle_bind_groups,
particle_buffers,
vertices_buffer,
compute_pipeline,
render_pipeline,
work_group_count,
frame_num: 0,
},
None,
)
Example {
particle_bind_groups,
particle_buffers,
vertices_buffer,
compute_pipeline,
render_pipeline,
work_group_count,
frame_num: 0,
}
}
/// update is called for any WindowEvent not handled by the framework
@@ -256,8 +251,9 @@ impl framework::Example for Example {
&mut self,
frame: &wgpu::SwapChainTexture,
device: &wgpu::Device,
_queue: &wgpu::Queue,
) -> wgpu::CommandBuffer {
queue: &wgpu::Queue,
_spawner: &impl futures::task::LocalSpawn,
) {
// create render pass descriptor
let render_pass_descriptor = wgpu::RenderPassDescriptor {
color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor {
@@ -298,7 +294,7 @@ impl framework::Example for Example {
self.frame_num += 1;
// done
command_encoder.finish()
queue.submit(Some(command_encoder.finish()));
}
}

View File

@@ -116,7 +116,7 @@ impl framework::Example for Example {
sc_desc: &wgpu::SwapChainDescriptor,
device: &wgpu::Device,
queue: &wgpu::Queue,
) -> (Self, Option<wgpu::CommandBuffer>) {
) -> Self {
use std::mem;
// Create the vertex and index buffers
@@ -287,15 +287,14 @@ impl framework::Example for Example {
});
// Done
let this = Example {
Example {
vertex_buf,
index_buf,
index_count: index_data.len(),
bind_group,
uniform_buf,
pipeline,
};
(this, None)
}
}
fn update(&mut self, _event: winit::event::WindowEvent) {
@@ -317,8 +316,9 @@ impl framework::Example for Example {
&mut self,
frame: &wgpu::SwapChainTexture,
device: &wgpu::Device,
_queue: &wgpu::Queue,
) -> wgpu::CommandBuffer {
queue: &wgpu::Queue,
_spawner: &impl futures::task::LocalSpawn,
) {
let mut encoder =
device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
{
@@ -348,7 +348,7 @@ impl framework::Example for Example {
rpass.draw_indexed(0..self.index_count as u32, 0, 0..1);
}
encoder.finish()
queue.submit(Some(encoder.finish()));
}
}

View File

@@ -1,8 +1,9 @@
use std::time;
use futures::task::LocalSpawn;
#[cfg(not(target_arch = "wasm32"))]
use std::time::{Duration, Instant};
use winit::{
event::{self, WindowEvent},
event_loop::{ControlFlow, EventLoop},
window::Window,
};
#[cfg_attr(rustfmt, rustfmt_skip)]
@@ -36,7 +37,7 @@ pub trait Example: 'static + Sized {
sc_desc: &wgpu::SwapChainDescriptor,
device: &wgpu::Device,
queue: &wgpu::Queue,
) -> (Self, Option<wgpu::CommandBuffer>);
) -> Self;
fn resize(
&mut self,
sc_desc: &wgpu::SwapChainDescriptor,
@@ -49,10 +50,32 @@ pub trait Example: 'static + Sized {
frame: &wgpu::SwapChainTexture,
device: &wgpu::Device,
queue: &wgpu::Queue,
) -> wgpu::CommandBuffer;
spawner: &impl LocalSpawn,
);
}
async fn run_async<E: Example>(event_loop: EventLoop<()>, window: Window) {
struct Setup {
window: winit::window::Window,
event_loop: EventLoop<()>,
instance: wgpu::Instance,
size: winit::dpi::PhysicalSize<u32>,
surface: wgpu::Surface,
adapter: wgpu::Adapter,
device: wgpu::Device,
queue: wgpu::Queue,
}
async fn setup<E: Example>(title: &str) -> Setup {
let event_loop = EventLoop::new();
let mut builder = winit::window::WindowBuilder::new();
builder = builder.with_title(title);
#[cfg(windows_OFF)] // TODO
{
use winit::platform::windows::WindowBuilderExtWindows;
builder = builder.with_no_redirection_bitmap(true);
}
let window = builder.build(&event_loop).unwrap();
log::info!("Initializing the surface...");
let instance = wgpu::Instance::new(wgpu::BackendBit::PRIMARY);
@@ -90,6 +113,75 @@ async fn run_async<E: Example>(event_loop: EventLoop<()>, window: Window) {
.await
.unwrap();
Setup {
window,
event_loop,
instance,
size,
surface,
adapter,
device,
queue,
}
}
fn start<E: Example>(
Setup {
window,
event_loop,
instance,
size,
surface,
adapter,
device,
queue,
}: Setup,
) {
#[cfg(not(target_arch = "wasm32"))]
let (mut pool, spawner) = {
env_logger::init();
#[cfg(feature = "subscriber")]
{
let chrome_tracing_dir = std::env::var("WGPU_CHROME_TRACING");
wgpu::util::initialize_default_subscriber(chrome_tracing_dir.ok());
};
let local_pool = futures::executor::LocalPool::new();
let spawner = local_pool.spawner();
(local_pool, spawner)
};
#[cfg(target_arch = "wasm32")]
let spawner = {
use futures::{future::LocalFutureObj, task::SpawnError};
use winit::platform::web::WindowExtWebSys;
struct WebSpawner {}
impl LocalSpawn for WebSpawner {
fn spawn_local_obj(
&self,
future: LocalFutureObj<'static, ()>,
) -> Result<(), SpawnError> {
Ok(wasm_bindgen_futures::spawn_local(future))
}
}
std::panic::set_hook(Box::new(console_error_panic_hook::hook));
console_log::init().expect("could not initialize logger");
// On wasm, append the canvas to the document body
web_sys::window()
.and_then(|win| win.document())
.and_then(|doc| doc.body())
.and_then(|body| {
body.append_child(&web_sys::Element::from(window.canvas()))
.ok()
})
.expect("couldn't append canvas to document body");
WebSpawner {}
};
let mut sc_desc = wgpu::SwapChainDescriptor {
usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT,
// TODO: Allow srgb unconditionally
@@ -105,13 +197,10 @@ async fn run_async<E: Example>(event_loop: EventLoop<()>, window: Window) {
let mut swap_chain = device.create_swap_chain(&surface, &sc_desc);
log::info!("Initializing the example...");
let (mut example, init_command_buf) = E::init(&sc_desc, &device, &queue);
if init_command_buf.is_some() {
queue.submit(init_command_buf);
}
let mut example = E::init(&sc_desc, &device, &queue);
#[cfg(not(target_arch = "wasm32"))]
let mut last_update_inst = time::Instant::now();
let mut last_update_inst = Instant::now();
log::info!("Entering render loop...");
event_loop.run(move |event, _, control_flow| {
@@ -121,7 +210,7 @@ async fn run_async<E: Example>(event_loop: EventLoop<()>, window: Window) {
} else {
#[cfg(not(target_arch = "wasm32"))]
{
ControlFlow::WaitUntil(time::Instant::now() + time::Duration::from_millis(10))
ControlFlow::WaitUntil(Instant::now() + Duration::from_millis(10))
}
#[cfg(target_arch = "wasm32")]
{
@@ -132,10 +221,12 @@ async fn run_async<E: Example>(event_loop: EventLoop<()>, window: Window) {
event::Event::MainEventsCleared => {
#[cfg(not(target_arch = "wasm32"))]
{
if last_update_inst.elapsed() > time::Duration::from_millis(20) {
if last_update_inst.elapsed() > Duration::from_millis(20) {
window.request_redraw();
last_update_inst = time::Instant::now();
last_update_inst = Instant::now();
}
pool.run_until_stalled();
}
#[cfg(target_arch = "wasm32")]
@@ -178,53 +269,26 @@ async fn run_async<E: Example>(event_loop: EventLoop<()>, window: Window) {
}
};
let command_buf = example.render(&frame.output, &device, &queue);
queue.submit(Some(command_buf));
example.render(&frame.output, &device, &queue, &spawner);
}
_ => {}
}
});
}
#[cfg(not(target_arch = "wasm32"))]
pub fn run<E: Example>(title: &str) {
let event_loop = EventLoop::new();
let mut builder = winit::window::WindowBuilder::new();
builder = builder.with_title(title);
#[cfg(windows_OFF)] // TODO
{
use winit::platform::windows::WindowBuilderExtWindows;
builder = builder.with_no_redirection_bitmap(true);
}
let window = builder.build(&event_loop).unwrap();
let setup = futures::executor::block_on(setup::<E>(title));
start::<E>(setup);
}
#[cfg(not(target_arch = "wasm32"))]
{
env_logger::init();
#[cfg(feature = "subscriber")]
{
let chrome_tracing_dir = std::env::var("WGPU_CHROME_TRACING");
wgpu::util::initialize_default_subscriber(chrome_tracing_dir.ok());
};
futures::executor::block_on(run_async::<E>(event_loop, window));
}
#[cfg(target_arch = "wasm32")]
{
std::panic::set_hook(Box::new(console_error_panic_hook::hook));
console_log::init().expect("could not initialize logger");
use winit::platform::web::WindowExtWebSys;
// On wasm, append the canvas to the document body
web_sys::window()
.and_then(|win| win.document())
.and_then(|doc| doc.body())
.and_then(|body| {
body.append_child(&web_sys::Element::from(window.canvas()))
.ok()
})
.expect("couldn't append canvas to document body");
wasm_bindgen_futures::spawn_local(run_async::<E>(event_loop, window));
}
#[cfg(target_arch = "wasm32")]
pub fn run<E: Example>(title: &str) {
let title = title.to_owned();
wasm_bindgen_futures::spawn_local(async move {
let setup = setup::<E>(&title).await;
start::<E>(setup);
});
}
// This allows treating the framework as a standalone example,

View File

@@ -204,8 +204,8 @@ impl framework::Example for Example {
fn init(
sc_desc: &wgpu::SwapChainDescriptor,
device: &wgpu::Device,
_queue: &wgpu::Queue,
) -> (Self, Option<wgpu::CommandBuffer>) {
queue: &wgpu::Queue,
) -> Self {
use std::mem;
let mut init_encoder =
@@ -375,14 +375,14 @@ impl framework::Example for Example {
// Done
Self::generate_mipmaps(&mut init_encoder, &device, &texture, mip_level_count);
queue.submit(Some(init_encoder.finish()));
let this = Example {
Example {
vertex_buf,
bind_group,
uniform_buf,
draw_pipeline,
};
(this, Some(init_encoder.finish()))
}
}
fn update(&mut self, _event: winit::event::WindowEvent) {
@@ -404,8 +404,9 @@ impl framework::Example for Example {
&mut self,
frame: &wgpu::SwapChainTexture,
device: &wgpu::Device,
_queue: &wgpu::Queue,
) -> wgpu::CommandBuffer {
queue: &wgpu::Queue,
_spawner: &impl futures::task::LocalSpawn,
) {
let mut encoder =
device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
{
@@ -432,7 +433,7 @@ impl framework::Example for Example {
rpass.draw(0..4, 0..1);
}
encoder.finish()
queue.submit(Some(encoder.finish()));
}
}

View File

@@ -132,7 +132,7 @@ impl framework::Example for Example {
sc_desc: &wgpu::SwapChainDescriptor,
device: &wgpu::Device,
_queue: &wgpu::Queue,
) -> (Self, Option<wgpu::CommandBuffer>) {
) -> Self {
log::info!("Press left/right arrow keys to change sample_count.");
let sample_count = 4;
@@ -179,7 +179,7 @@ impl framework::Example for Example {
vertex_count,
);
let this = Example {
Example {
bundle,
vs_module,
fs_module,
@@ -190,8 +190,7 @@ impl framework::Example for Example {
sample_count,
rebuild_bundle: false,
sc_desc: sc_desc.clone(),
};
(this, None)
}
}
fn update(&mut self, event: winit::event::WindowEvent) {
@@ -234,8 +233,9 @@ impl framework::Example for Example {
&mut self,
frame: &wgpu::SwapChainTexture,
device: &wgpu::Device,
_queue: &wgpu::Queue,
) -> wgpu::CommandBuffer {
queue: &wgpu::Queue,
_spawner: &impl futures::task::LocalSpawn,
) {
if self.rebuild_bundle {
self.bundle = Example::create_bundle(
device,
@@ -281,7 +281,7 @@ impl framework::Example for Example {
.execute_bundles(iter::once(&self.bundle));
}
encoder.finish()
queue.submit(iter::once(encoder.finish()));
}
}

View File

@@ -1,4 +1,4 @@
use std::{mem, ops::Range, rc::Rc};
use std::{iter, mem, ops::Range, rc::Rc};
#[path = "../framework.rs"]
mod framework;
@@ -207,7 +207,7 @@ impl framework::Example for Example {
sc_desc: &wgpu::SwapChainDescriptor,
device: &wgpu::Device,
_queue: &wgpu::Queue,
) -> (Self, Option<wgpu::CommandBuffer>) {
) -> Self {
// Create the vertex and index buffers
let vertex_size = mem::size_of::<Vertex>();
let (cube_vertex_data, cube_index_data) = create_cube();
@@ -656,7 +656,7 @@ impl framework::Example for Example {
label: None,
});
let this = Example {
Example {
entities,
lights,
lights_are_dirty: true,
@@ -664,8 +664,7 @@ impl framework::Example for Example {
forward_pass,
forward_depth: depth_texture.create_default_view(),
light_uniform_buf,
};
(this, None)
}
}
fn update(&mut self, _event: winit::event::WindowEvent) {
@@ -708,7 +707,8 @@ impl framework::Example for Example {
frame: &wgpu::SwapChainTexture,
device: &wgpu::Device,
queue: &wgpu::Queue,
) -> wgpu::CommandBuffer {
_spawner: &impl futures::task::LocalSpawn,
) {
// update uniforms
for entity in self.entities.iter_mut() {
if entity.rotation_speed != 0.0 {
@@ -810,7 +810,7 @@ impl framework::Example for Example {
}
}
encoder.finish()
queue.submit(iter::once(encoder.finish()));
}
}

View File

@@ -1,6 +1,8 @@
#[path = "../framework.rs"]
mod framework;
use futures::task::{LocalSpawn, LocalSpawnExt};
const SKYBOX_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba8Unorm;
type Uniform = cgmath::Matrix4<f32>;
@@ -19,6 +21,7 @@ pub struct Skybox {
bind_group: wgpu::BindGroup,
uniform_buf: wgpu::Buffer,
uniforms: Uniforms,
staging_belt: wgpu::util::StagingBelt,
}
impl Skybox {
@@ -39,7 +42,7 @@ impl framework::Example for Skybox {
sc_desc: &wgpu::SwapChainDescriptor,
device: &wgpu::Device,
queue: &wgpu::Queue,
) -> (Self, Option<wgpu::CommandBuffer>) {
) -> Self {
let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
bindings: &[
wgpu::BindGroupLayoutEntry::new(
@@ -227,16 +230,15 @@ impl framework::Example for Skybox {
],
label: None,
});
(
Self {
pipeline,
bind_group,
uniform_buf,
aspect,
uniforms,
},
None,
)
Skybox {
pipeline,
bind_group,
uniform_buf,
aspect,
uniforms,
staging_belt: wgpu::util::StagingBelt::new(0x100, device),
}
}
fn update(&mut self, _event: winit::event::WindowEvent) {
@@ -261,15 +263,21 @@ impl framework::Example for Skybox {
frame: &wgpu::SwapChainTexture,
device: &wgpu::Device,
queue: &wgpu::Queue,
) -> wgpu::CommandBuffer {
spawner: &impl LocalSpawn,
) {
// update rotation
let rotation = cgmath::Matrix4::<f32>::from_angle_x(cgmath::Deg(0.25));
self.uniforms[1] = self.uniforms[1] * rotation;
queue.write_buffer(
&self.uniform_buf,
0,
bytemuck::cast_slice(&raw_uniforms(&self.uniforms)),
);
let raw_uniforms = raw_uniforms(&self.uniforms);
self.staging_belt
.write_buffer(
&self.uniform_buf,
0,
wgpu::BufferSize::new((raw_uniforms.len() * 4) as wgpu::BufferAddress).unwrap(),
device,
)
.copy_from_slice(bytemuck::cast_slice(&raw_uniforms));
let transfer_comb = self.staging_belt.flush(device);
let mut encoder =
device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
@@ -295,7 +303,11 @@ impl framework::Example for Skybox {
rpass.set_bind_group(0, &self.bind_group, &[]);
rpass.draw(0..3 as u32, 0..1);
}
encoder.finish()
queue.submit(vec![transfer_comb, encoder.finish()]);
let belt_future = self.staging_belt.recall();
spawner.spawn_local(belt_future).unwrap();
}
}

View File

@@ -95,7 +95,7 @@ impl framework::Example for Example {
sc_desc: &wgpu::SwapChainDescriptor,
device: &wgpu::Device,
queue: &wgpu::Queue,
) -> (Self, Option<wgpu::CommandBuffer>) {
) -> Self {
let mut uniform_workaround = false;
let vs_module = device.create_shader_module(wgpu::include_spirv!("shader.vert.spv"));
let fs_bytes: Vec<u8> = match device.capabilities() {
@@ -335,16 +335,13 @@ impl framework::Example for Example {
alpha_to_coverage_enabled: false,
});
(
Self {
vertex_buffer,
index_buffer,
bind_group,
pipeline,
uniform_workaround_data,
},
None,
)
Self {
vertex_buffer,
index_buffer,
bind_group,
pipeline,
uniform_workaround_data,
}
}
fn resize(
&mut self,
@@ -361,8 +358,9 @@ impl framework::Example for Example {
&mut self,
frame: &wgpu::SwapChainTexture,
device: &wgpu::Device,
_queue: &wgpu::Queue,
) -> wgpu::CommandBuffer {
queue: &wgpu::Queue,
_spawner: &impl futures::task::LocalSpawn,
) {
let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("primary"),
});
@@ -393,7 +391,7 @@ impl framework::Example for Example {
drop(rpass);
encoder.finish()
queue.submit(Some(encoder.finish()));
}
}

View File

@@ -4,7 +4,7 @@ mod framework;
mod point_gen;
use cgmath::Point3;
use std::mem;
use std::{iter, mem};
///
/// Radius of the terrain.
@@ -269,7 +269,7 @@ impl framework::Example for Example {
sc_desc: &wgpu::SwapChainDescriptor,
device: &wgpu::Device,
queue: &wgpu::Queue,
) -> (Self, Option<wgpu::CommandBuffer>) {
) -> Self {
// Size of one water vertex
let water_vertex_size = mem::size_of::<point_gen::WaterVertexAttributes>();
@@ -609,7 +609,7 @@ impl framework::Example for Example {
});
// Done
let this = Example {
Example {
water_vertex_buf,
water_vertex_count: water_vertices.len(),
water_bind_group_layout,
@@ -632,8 +632,7 @@ impl framework::Example for Example {
current_frame: 0,
active: Some(0),
};
(this, None)
}
}
fn update(&mut self, _event: winit::event::WindowEvent) {
@@ -680,7 +679,8 @@ impl framework::Example for Example {
frame: &wgpu::SwapChainTexture,
device: &wgpu::Device,
queue: &wgpu::Queue,
) -> wgpu::CommandBuffer {
_spawner: &impl futures::task::LocalSpawn,
) {
// Increment frame count regardless of if we draw.
self.current_frame += 1;
let back_color = wgpu::Color {
@@ -698,21 +698,21 @@ impl framework::Example for Example {
bytemuck::cast_slice(&[water_sin, water_cos]),
);
// Only render valid frames. See resize method.
if let Some(active) = self.active {
if active >= self.current_frame {
return;
}
} else {
return;
}
// The encoder provides a way to turn our instructions here, into
// a command buffer the GPU can understand.
let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("Main Command Encoder"),
});
// Only render valid frames. See resize method.
if let Some(active) = self.active {
if active >= self.current_frame {
return encoder.finish();
}
} else {
return encoder.finish();
}
// First pass: render the reflection.
{
let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
@@ -791,7 +791,7 @@ impl framework::Example for Example {
rpass.draw(0..self.water_vertex_count as u32, 0..1);
}
encoder.finish()
queue.submit(iter::once(encoder.finish()));
}
}

View File

@@ -1433,7 +1433,7 @@ impl Buffer {
}
}
impl BufferSlice<'_> {
impl<'a> BufferSlice<'a> {
//TODO: fn slice(&self) -> Self
/// Map the buffer. Buffer is ready to map once the future is resolved.
@@ -1473,7 +1473,7 @@ impl BufferSlice<'_> {
/// Synchronously and immediately map a buffer for reading. If the buffer is not immediately mappable
/// through [`BufferDescriptor::mapped_at_creation`] or [`BufferSlice::map_async`], will panic.
pub fn get_mapped_range(&self) -> BufferView {
pub fn get_mapped_range(&self) -> BufferView<'a> {
let end = self.buffer.map_context.lock().add(self.offset, self.size);
let data = Context::buffer_get_mapped_range(
&*self.buffer.context,
@@ -1485,7 +1485,7 @@ impl BufferSlice<'_> {
/// Synchronously and immediately map a buffer for writing. If the buffer is not immediately mappable
/// through [`BufferDescriptor::mapped_at_creation`] or [`BufferSlice::map_async`], will panic.
pub fn get_mapped_range_mut(&self) -> BufferViewMut {
pub fn get_mapped_range_mut(&self) -> BufferViewMut<'a> {
let end = self.buffer.map_context.lock().add(self.offset, self.size);
let data = Context::buffer_get_mapped_range_mut(
&*self.buffer.context,

148
wgpu/src/util/belt.rs Normal file
View File

@@ -0,0 +1,148 @@
use crate::{
Buffer, BufferAddress, BufferDescriptor, BufferSize, BufferUsage, BufferViewMut, CommandBuffer,
CommandEncoder, CommandEncoderDescriptor, Device, MapMode,
};
use futures::{future::join_all, FutureExt};
use std::{future::Future, mem, sync::mpsc};
struct Chunk {
buffer: Buffer,
size: BufferAddress,
offset: BufferAddress,
}
/// Staging belt is a machine that uploads data.
///
/// Internally it uses a ring-buffer of staging buffers that are sub-allocated.
/// It has an advantage over `Queue.write_buffer` in a way that it returns a mutable slice,
/// which you can fill to avoid an extra data copy.
pub struct StagingBelt {
chunk_size: BufferAddress,
encoder: CommandEncoder,
/// Chunks that we are actively using for pending transfers at this moment.
active_chunks: Vec<Chunk>,
/// Chunks that have scheduled transfers already.
closed_chunks: Vec<Chunk>,
/// Chunks that are back from the GPU and ready to be used.
free_chunks: Vec<Chunk>,
sender: mpsc::Sender<Chunk>,
receiver: mpsc::Receiver<Chunk>,
}
impl StagingBelt {
/// Create a new staging belt.
///
/// The `chunk_size` is the unit of internal buffer allocation.
/// It's better when it's big, but ideally still 1-4 times less than
/// the total amount of data uploaded per submission.
pub fn new(chunk_size: BufferAddress, device: &Device) -> Self {
let (sender, receiver) = mpsc::channel();
StagingBelt {
chunk_size,
encoder: device.create_command_encoder(&CommandEncoderDescriptor::default()),
active_chunks: Vec::new(),
closed_chunks: Vec::new(),
free_chunks: Vec::new(),
sender,
receiver,
}
}
/// Allocate the staging belt slice of `size` to be uploaded into the `target` buffer
/// at the specified offset.
///
/// The upload will only really be scheduled at the next `StagingBelt::flush` call.
pub fn write_buffer(
&mut self,
target: &Buffer,
offset: BufferAddress,
size: BufferSize,
device: &Device,
) -> BufferViewMut {
let mut chunk = if let Some(index) = self
.active_chunks
.iter()
.position(|chunk| chunk.offset + size.get() <= chunk.size)
{
self.active_chunks.swap_remove(index)
} else if let Some(index) = self
.free_chunks
.iter()
.position(|chunk| size.get() <= chunk.size)
{
self.free_chunks.swap_remove(index)
} else {
let size = self.chunk_size.max(size.get());
#[cfg(not(target_arch = "wasm32"))]
wgc::span!(_guard, INFO, "Creating chunk of size {}", size);
Chunk {
buffer: device.create_buffer(&BufferDescriptor {
label: Some("staging"),
size,
usage: BufferUsage::MAP_WRITE | BufferUsage::COPY_SRC,
mapped_at_creation: true,
}),
size,
offset: 0,
}
};
self.encoder
.copy_buffer_to_buffer(&chunk.buffer, chunk.offset, target, offset, size.get());
let old_offset = chunk.offset;
chunk.offset += size.get();
let remainder = chunk.offset % crate::COPY_BUFFER_ALIGNMENT;
if remainder != 0 {
chunk.offset += crate::COPY_BUFFER_ALIGNMENT - remainder;
}
self.active_chunks.push(chunk);
self.active_chunks
.last()
.unwrap()
.buffer
.slice(old_offset..old_offset + size.get())
.get_mapped_range_mut()
}
/// Produce a command buffer with all the accumulated transfers.
///
/// At this point, all the partially used staging buffers are closed until
/// the GPU is done copying the data from them.
pub fn flush(&mut self, device: &Device) -> CommandBuffer {
#[cfg(not(target_arch = "wasm32"))]
wgc::span!(_guard, DEBUG, "Flushing chunks");
for chunk in self.active_chunks.drain(..) {
chunk.buffer.unmap();
self.closed_chunks.push(chunk);
}
mem::replace(
&mut self.encoder,
device.create_command_encoder(&CommandEncoderDescriptor::default()),
)
.finish()
}
/// Recall all of the closed buffers back for re-usal.
///
/// This has to be called after the command buffer produced by `flush` is submitted!
pub fn recall(&mut self) -> impl Future<Output = ()> + Send {
while let Ok(mut chunk) = self.receiver.try_recv() {
chunk.offset = 0;
self.free_chunks.push(chunk);
}
let sender_template = &self.sender;
join_all(self.closed_chunks.drain(..).map(|chunk| {
let sender = sender_template.clone();
chunk
.buffer
.slice(..)
.map_async(MapMode::Write)
.inspect(move |_| sender.send(chunk).unwrap())
}))
.map(|_| ())
}
}

View File

@@ -1,6 +1,10 @@
mod belt;
#[cfg(all(not(target_arch = "wasm32"), feature = "subscriber"))]
pub use wgc::logging::subscriber::{initialize_default_subscriber, ChromeTracingLayer};
pub use belt::StagingBelt;
/// Wrapper aligning contents to at least 4.
#[repr(align(4))]
pub struct WordAligned<Bytes: ?Sized>(pub Bytes);