Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimize Text rendering / SharedBuffers #972

Merged
merged 2 commits into from
Dec 2, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 4 additions & 12 deletions crates/bevy_render/src/draw.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::{
pipeline::{PipelineCompiler, PipelineDescriptor, PipelineLayout, PipelineSpecialization},
renderer::{
BindGroup, BindGroupId, BufferId, BufferUsage, RenderResource, RenderResourceBinding,
BindGroup, BindGroupId, BufferId, RenderResource, RenderResourceBinding,
RenderResourceBindings, RenderResourceContext, SharedBuffers,
},
shader::Shader,
Expand Down Expand Up @@ -125,7 +125,7 @@ pub struct DrawContext<'a> {
pub shaders: ResMut<'a, Assets<Shader>>,
pub pipeline_compiler: ResMut<'a, PipelineCompiler>,
pub render_resource_context: Res<'a, Box<dyn RenderResourceContext>>,
pub shared_buffers: Res<'a, SharedBuffers>,
pub shared_buffers: ResMut<'a, SharedBuffers>,
#[system_param(ignore)]
pub current_pipeline: Option<Handle<PipelineDescriptor>>,
}
Expand All @@ -135,19 +135,11 @@ pub struct FetchDrawContext;

impl<'a> DrawContext<'a> {
pub fn get_uniform_buffer<T: RenderResource>(
&self,
render_resource: &T,
) -> Result<RenderResourceBinding, DrawError> {
self.get_buffer(render_resource, BufferUsage::UNIFORM)
}

pub fn get_buffer<T: RenderResource>(
&self,
&mut self,
render_resource: &T,
buffer_usage: BufferUsage,
) -> Result<RenderResourceBinding, DrawError> {
self.shared_buffers
.get_buffer(render_resource, buffer_usage)
.get_uniform_buffer(&**self.render_resource_context, render_resource)
.ok_or(DrawError::BufferAllocationFailure)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@ impl Node for SharedBuffersNode {
_input: &ResourceSlots,
_output: &mut ResourceSlots,
) {
let shared_buffers = resources.get::<SharedBuffers>().unwrap();
let mut command_queue = shared_buffers.reset_command_queue();
command_queue.execute(render_context);
let mut shared_buffers = resources.get_mut::<SharedBuffers>().unwrap();
shared_buffers.apply(render_context);
}
}
151 changes: 94 additions & 57 deletions crates/bevy_render/src/renderer/render_resource/shared_buffers.rs
Original file line number Diff line number Diff line change
@@ -1,99 +1,136 @@
use super::{BufferId, BufferInfo, RenderResource, RenderResourceBinding};
use crate::{
render_graph::CommandQueue,
renderer::{BufferUsage, RenderResourceContext},
renderer::{BufferUsage, RenderContext, RenderResourceContext},
};
use bevy_ecs::Res;
use parking_lot::RwLock;
use std::sync::Arc;
use bevy_ecs::{Res, ResMut};

// TODO: Instead of allocating small "exact size" buffers each frame, this should use multiple large shared buffers and probably
// a long-living "cpu mapped" staging buffer. Im punting that for now because I don't know the best way to use wgpu's new async
// buffer mapping yet.
pub struct SharedBuffers {
render_resource_context: Box<dyn RenderResourceContext>,
buffers: Arc<RwLock<Vec<BufferId>>>,
command_queue: Arc<RwLock<CommandQueue>>,
staging_buffer: Option<BufferId>,
uniform_buffer: Option<BufferId>,
buffers_to_free: Vec<BufferId>,
buffer_size: usize,
initial_size: usize,
current_offset: usize,
command_queue: CommandQueue,
}

impl SharedBuffers {
pub fn new(render_resource_context: Box<dyn RenderResourceContext>) -> Self {
pub fn new(initial_size: usize) -> Self {
Self {
render_resource_context,
buffers: Default::default(),
staging_buffer: None,
uniform_buffer: None,
buffer_size: 0,
current_offset: 0,
initial_size,
buffers_to_free: Default::default(),
command_queue: Default::default(),
}
}

pub fn get_buffer<T: RenderResource>(
&self,
pub fn grow(
&mut self,
render_resource_context: &dyn RenderResourceContext,
required_space: usize,
) {
while self.buffer_size < self.current_offset + required_space {
self.buffer_size = if self.buffer_size == 0 {
self.initial_size
} else {
self.buffer_size * 2
};
}

self.current_offset = 0;

if let Some(staging_buffer) = self.staging_buffer.take() {
render_resource_context.unmap_buffer(staging_buffer);
self.buffers_to_free.push(staging_buffer);
}

if let Some(uniform_buffer) = self.uniform_buffer.take() {
self.buffers_to_free.push(uniform_buffer);
}

self.staging_buffer = Some(render_resource_context.create_buffer(BufferInfo {
size: self.buffer_size,
buffer_usage: BufferUsage::MAP_WRITE | BufferUsage::COPY_SRC,
mapped_at_creation: true,
}));
self.uniform_buffer = Some(render_resource_context.create_buffer(BufferInfo {
size: self.buffer_size,
buffer_usage: BufferUsage::COPY_DST | BufferUsage::UNIFORM,
mapped_at_creation: false,
}));
}

pub fn get_uniform_buffer<T: RenderResource>(
&mut self,
render_resource_context: &dyn RenderResourceContext,
render_resource: &T,
buffer_usage: BufferUsage,
) -> Option<RenderResourceBinding> {
if let Some(size) = render_resource.buffer_byte_len() {
let aligned_size = self
.render_resource_context
.get_aligned_uniform_size(size, false);
// PERF: this buffer will be slow
let staging_buffer = self.render_resource_context.create_buffer(BufferInfo {
size: aligned_size,
buffer_usage: BufferUsage::COPY_SRC | BufferUsage::MAP_WRITE,
mapped_at_creation: true,
});
// TODO: overlap alignment if/when possible
let aligned_size = render_resource_context.get_aligned_uniform_size(size, true);
let mut new_offset = self.current_offset + aligned_size;
if new_offset > self.buffer_size {
self.grow(render_resource_context, aligned_size);
new_offset = aligned_size;
}

self.render_resource_context.write_mapped_buffer(
let range = self.current_offset as u64..new_offset as u64;
let staging_buffer = self.staging_buffer.unwrap();
let uniform_buffer = self.uniform_buffer.unwrap();
render_resource_context.write_mapped_buffer(
staging_buffer,
0..size as u64,
range.clone(),
&mut |data, _renderer| {
render_resource.write_buffer_bytes(data);
},
);

self.render_resource_context.unmap_buffer(staging_buffer);

let destination_buffer = self.render_resource_context.create_buffer(BufferInfo {
size: aligned_size,
buffer_usage: BufferUsage::COPY_DST | buffer_usage,
..Default::default()
});

let mut command_queue = self.command_queue.write();
command_queue.copy_buffer_to_buffer(
self.command_queue.copy_buffer_to_buffer(
staging_buffer,
0,
destination_buffer,
0,
size as u64,
self.current_offset as u64,
uniform_buffer,
self.current_offset as u64,
aligned_size as u64,
);

let mut buffers = self.buffers.write();
buffers.push(staging_buffer);
buffers.push(destination_buffer);
self.current_offset = new_offset;
Some(RenderResourceBinding::Buffer {
buffer: destination_buffer,
range: 0..aligned_size as u64,
buffer: uniform_buffer,
range,
dynamic_index: None,
})
} else {
None
}
}

// TODO: remove this when this actually uses shared buffers
pub fn free_buffers(&self) {
let mut buffers = self.buffers.write();
for buffer in buffers.drain(..) {
self.render_resource_context.remove_buffer(buffer)
pub fn update(&mut self, render_resource_context: &dyn RenderResourceContext) {
self.current_offset = 0;
for buffer in self.buffers_to_free.drain(..) {
render_resource_context.remove_buffer(buffer)
}

if let Some(staging_buffer) = self.staging_buffer {
render_resource_context.map_buffer(staging_buffer);
}
}

pub fn reset_command_queue(&self) -> CommandQueue {
let mut command_queue = self.command_queue.write();
std::mem::take(&mut *command_queue)
pub fn apply(&mut self, render_context: &mut dyn RenderContext) {
if let Some(staging_buffer) = self.staging_buffer {
render_context.resources().unmap_buffer(staging_buffer);
}
let mut command_queue = std::mem::take(&mut self.command_queue);
command_queue.execute(render_context);
}
}

// TODO: remove this when this actually uses shared buffers
pub fn free_shared_buffers_system(shared_buffers: Res<SharedBuffers>) {
shared_buffers.free_buffers();
pub fn shared_buffers_update_system(
mut shared_buffers: ResMut<SharedBuffers>,
render_resource_context: Res<Box<dyn RenderResourceContext>>,
) {
shared_buffers.update(&**render_resource_context);
}
15 changes: 3 additions & 12 deletions crates/bevy_text/src/draw.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,7 @@ use bevy_render::{
mesh,
pipeline::{PipelineSpecialization, VertexBufferDescriptor},
prelude::Msaa,
renderer::{
AssetRenderResourceBindings, BindGroup, BufferUsage, RenderResourceBindings,
RenderResourceId,
},
renderer::{AssetRenderResourceBindings, BindGroup, RenderResourceBindings, RenderResourceId},
};
use bevy_sprite::TextureAtlasSprite;
use glyph_brush_layout::{HorizontalAlign, VerticalAlign};
Expand Down Expand Up @@ -108,14 +105,8 @@ impl<'a> Drawable for DrawableText<'a> {

let transform = Mat4::from_translation(self.position + tv.position.extend(0.));

let transform_buffer = context
.shared_buffers
.get_buffer(&transform, BufferUsage::UNIFORM)
.unwrap();
let sprite_buffer = context
.shared_buffers
.get_buffer(&sprite, BufferUsage::UNIFORM)
.unwrap();
let transform_buffer = context.get_uniform_buffer(&transform).unwrap();
let sprite_buffer = context.get_uniform_buffer(&sprite).unwrap();
let sprite_bind_group = BindGroup::build()
.add_binding(0, transform_buffer)
.add_binding(1, sprite_buffer)
Expand Down
11 changes: 7 additions & 4 deletions crates/bevy_wgpu/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ pub use wgpu_resources::*;

use bevy_app::prelude::*;
use bevy_ecs::{Resources, World};
use bevy_render::renderer::{free_shared_buffers_system, RenderResourceContext, SharedBuffers};
use bevy_render::renderer::{shared_buffers_update_system, RenderResourceContext, SharedBuffers};
use renderer::WgpuRenderResourceContext;

#[derive(Default)]
Expand All @@ -22,7 +22,10 @@ impl Plugin for WgpuPlugin {
fn build(&self, app: &mut AppBuilder) {
let render_system = get_wgpu_render_system(app.resources_mut());
app.add_system_to_stage(bevy_render::stage::RENDER, render_system)
.add_system_to_stage(bevy_render::stage::POST_RENDER, free_shared_buffers_system);
.add_system_to_stage(
bevy_render::stage::POST_RENDER,
shared_buffers_update_system,
);
}
}

Expand All @@ -32,8 +35,8 @@ pub fn get_wgpu_render_system(resources: &mut Resources) -> impl FnMut(&mut Worl
.unwrap_or_else(WgpuOptions::default);
let mut wgpu_renderer = future::block_on(WgpuRenderer::new(options));
let resource_context = WgpuRenderResourceContext::new(wgpu_renderer.device.clone());
resources.insert::<Box<dyn RenderResourceContext>>(Box::new(resource_context.clone()));
resources.insert(SharedBuffers::new(Box::new(resource_context)));
resources.insert::<Box<dyn RenderResourceContext>>(Box::new(resource_context));
resources.insert(SharedBuffers::new(4096));
move |world, resources| {
wgpu_renderer.update(world, resources);
}
Expand Down