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

Trait-based allocator API #60

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
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
8 changes: 5 additions & 3 deletions examples/demo_app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
// notice may not be copied, modified, or distributed except
// according to those terms.

use egui_winit_vulkano::{egui, Gui, GuiConfig};
use egui_winit_vulkano::{allocator::DefaultAllocators, egui, Gui, GuiConfig};
use vulkano::sync::{self, GpuFuture};
use vulkano_util::{
context::{VulkanoConfig, VulkanoContext},
Expand Down Expand Up @@ -52,6 +52,8 @@ pub fn main() {
ci.min_image_count = ci.min_image_count.max(2);
},
);
// Allocators can be shared between renderers to increase re-use
let allocator = DefaultAllocators::new_default(context.device().clone());
// Create gui as main render pass (no overlay means it clears the image each frame)
let mut gui1 = {
let renderer = windows.get_renderer_mut(window1).unwrap();
Expand All @@ -60,7 +62,7 @@ pub fn main() {
renderer.surface(),
renderer.graphics_queue(),
renderer.swapchain_format(),
GuiConfig { allow_srgb_render_target: true, ..GuiConfig::default() },
GuiConfig { allow_srgb_render_target: true, ..GuiConfig::new_alloc(allocator.share()) },
)
};
let mut gui2 = {
Expand All @@ -70,7 +72,7 @@ pub fn main() {
renderer.surface(),
renderer.graphics_queue(),
renderer.swapchain_format(),
GuiConfig::default(),
GuiConfig::new_alloc(allocator),
)
};
// Display the demo application that ships with egui.
Expand Down
2 changes: 1 addition & 1 deletion examples/minimal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ pub fn main() {
renderer.surface(),
renderer.graphics_queue(),
renderer.swapchain_format(),
GuiConfig::default(),
GuiConfig::new_default(context.device().clone()),
)
};
// Create gui state (pass anything your state requires)
Expand Down
8 changes: 4 additions & 4 deletions examples/multisample.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use std::{
};

use egui::{epaint::Shadow, style::Margin, vec2, Align, Align2, Color32, Frame, Rounding, Window};
use egui_winit_vulkano::{egui, Gui, GuiConfig};
use egui_winit_vulkano::{allocator::Allocators, egui, Gui, GuiConfig};
use vulkano::{
buffer::{Buffer, BufferContents, BufferCreateInfo, BufferUsage, Subbuffer},
command_buffer::{
Expand Down Expand Up @@ -81,7 +81,7 @@ pub fn main() {
GuiConfig {
// Must match your pipeline's sample count
samples: SampleCount::Sample4,
..Default::default()
..GuiConfig::new_default(context.device().clone())
},
);

Expand Down Expand Up @@ -316,11 +316,11 @@ impl MSAAPipeline {
)
}

pub fn render(
pub fn render<Alloc: Allocators>(
&mut self,
before_future: Box<dyn GpuFuture>,
image: Arc<ImageView>,
gui: &mut Gui,
gui: &mut Gui<Alloc>,
) -> Box<dyn GpuFuture> {
let mut builder = AutoCommandBufferBuilder::primary(
&self.command_buffer_allocator,
Expand Down
2 changes: 1 addition & 1 deletion examples/paint_callback.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ pub fn main() {
renderer.surface(),
renderer.graphics_queue(),
renderer.swapchain_format(),
GuiConfig::default(),
GuiConfig::new_default(context.device().clone()),
);

let scene = Arc::new(Mutex::new(Scene::new(gui.render_resources())));
Expand Down
8 changes: 4 additions & 4 deletions examples/subpass.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use std::{
};

use egui::{epaint::Shadow, style::Margin, vec2, Align, Align2, Color32, Frame, Rounding, Window};
use egui_winit_vulkano::{egui, Gui, GuiConfig};
use egui_winit_vulkano::{allocator::Allocators, egui, Gui, GuiConfig};
use vulkano::{
buffer::{Buffer, BufferContents, BufferCreateInfo, BufferUsage, Subbuffer},
command_buffer::{
Expand Down Expand Up @@ -77,7 +77,7 @@ pub fn main() {
windows.get_primary_renderer_mut().unwrap().graphics_queue(),
gui_pipeline.gui_pass(),
windows.get_primary_renderer_mut().unwrap().swapchain_format(),
GuiConfig::default(),
GuiConfig::new_default(context.device().clone()),
);

// Create gui state (pass anything your state requires)
Expand Down Expand Up @@ -267,11 +267,11 @@ impl SimpleGuiPipeline {
)
}

pub fn render(
pub fn render<Alloc: Allocators>(
&mut self,
before_future: Box<dyn GpuFuture>,
image: Arc<ImageView>,
gui: &mut Gui,
gui: &mut Gui<Alloc>,
) -> Box<dyn GpuFuture> {
let mut builder = AutoCommandBufferBuilder::primary(
&self.command_buffer_allocator,
Expand Down
10 changes: 7 additions & 3 deletions examples/wholesome/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
use std::sync::Arc;

use egui::{load::SizedTexture, Context, ImageSource, Visuals};
use egui_winit_vulkano::{egui, Gui, GuiConfig};
use egui_winit_vulkano::{allocator::Allocators, egui, Gui, GuiConfig};
use vulkano::{
command_buffer::allocator::{
StandardCommandBufferAllocator, StandardCommandBufferAllocatorCreateInfo,
Expand Down Expand Up @@ -51,7 +51,11 @@ pub struct GuiState {
}

impl GuiState {
pub fn new(gui: &mut Gui, scene_image: Arc<ImageView>, scene_view_size: [u32; 2]) -> GuiState {
pub fn new<Alloc: Allocators>(
gui: &mut Gui<Alloc>,
scene_image: Arc<ImageView>,
scene_view_size: [u32; 2],
) -> GuiState {
// tree.png asset is from https://github.com/sotrh/learn-wgpu/tree/master/docs/beginner/tutorial5-textures
let image_texture_id1 = gui.register_user_image(
include_bytes!("./assets/tree.png"),
Expand Down Expand Up @@ -153,7 +157,7 @@ pub fn main() {
renderer.surface(),
renderer.graphics_queue(),
renderer.swapchain_format(),
GuiConfig::default(),
GuiConfig::new_default(context.device().clone()),
)
};
// Create a simple image to which we'll draw the triangle scene
Expand Down
204 changes: 204 additions & 0 deletions src/allocator.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
use std::{fmt::Debug, sync::Arc};

use vulkano::{
buffer::{
allocator::{SubbufferAllocator, SubbufferAllocatorCreateInfo},
BufferUsage, Subbuffer,
},
device::Device,
image::{Image, ImageCreateInfo},
memory::{
allocator::{
AllocationCreateInfo, BumpAllocator, DeviceLayout, GenericMemoryAllocator,
GenericMemoryAllocatorCreateInfo, MemoryTypeFilter, StandardMemoryAllocator,
},
DeviceAlignment,
},
NonZeroDeviceSize,
};

/// A collection of allocators needed for the integration.
pub trait Allocators {
type Error: Debug;
/// Make a short-lived subbuffer for uploading and drawing vertices and indicies.
///
/// * Must be `HOST_VISIBLE` (sequential write), and compatible with `VERTEX_BUFER ` and `INDEX_BUFFER` usages.
fn make_vertex_index_buffer(
&mut self,
device_layout: DeviceLayout,
) -> Result<Subbuffer<[u8]>, Self::Error>;
/// Make a short-lived subbuffer for uploading images.
///
/// * Must be `HOST_VISIBLE` (sequential write), and compatible with `TRANSFER_SRC` usage.
fn make_image_stage_buffer(
&mut self,
bytes_len: NonZeroDeviceSize,
) -> Result<Subbuffer<[u8]>, Self::Error>;
/// Make a long-lived image. Corresponds one-to-one with an egui image.
///
/// * Must be usable with `R8G8_UNORM` and `R8G8B8A8_SRGB` color images.
fn make_image(&mut self, info: ImageCreateInfo) -> Result<Arc<Image>, Self::Error>;
}
impl<Base: Allocators> Allocators for &mut Base {
type Error = Base::Error;
fn make_vertex_index_buffer(
&mut self,
device_layout: DeviceLayout,
) -> Result<Subbuffer<[u8]>, Self::Error> {
(*self).make_vertex_index_buffer(device_layout)
}
fn make_image_stage_buffer(
&mut self,
bytes_len: NonZeroDeviceSize,
) -> Result<Subbuffer<[u8]>, Self::Error> {
(*self).make_image_stage_buffer(bytes_len)
}
fn make_image(&mut self, info: ImageCreateInfo) -> Result<Arc<Image>, Self::Error> {
(*self).make_image(info)
}
}

/// May be shared with several instances through use of [`DefaultAllocators::share`].
pub struct DefaultAllocators {
/// The internal allocator of image_stage + vertex_index, for `share`ing.
/// Bump is used as we expect these allocations to last only a fraction of a second, and to come in bursts.
pub bump: Arc<GenericMemoryAllocator<BumpAllocator>>,
pub image_stage: SubbufferAllocator<GenericMemoryAllocator<BumpAllocator>>,
pub vertex_index: SubbufferAllocator<GenericMemoryAllocator<BumpAllocator>>,
pub images: Arc<StandardMemoryAllocator>,
}

impl DefaultAllocators {
/// 5MiB is a practical upper limit to vertex/index size - Openning all windows in `demo_app` doesn't even reach it.
/// That many windows open is hardly usable let alone a common usecase!
const SMALL_VERTEX_BLOCK: u64 = 8 * 1024 * 1024;
/// 8MiB is enough stage for a ~1500 square fullcolor image, or 2048 square font image to be uploaded,
/// Plus a generous wiggle room.
const SMALL_IMAGE_STAGE_BLOCK: u64 = 16 * 1024 * 1024;
/// This is a hard thing to "default" - on it's own egui uses only a tiny amount of image memory,
/// however many usecases involve loading user images which greatly increases that.
const IMAGE_BLOCK: u64 = 32 * 1024 * 1024;

/// Allocators providing a good default for most apps, optimizing for many meshes and a few images.
/// Aside from this, it will work for any usecase at some loss of efficiency.
///
/// If you require many images or are updating images frequently,
/// consider populating the fields with a custom allocator with large image memory pools.
pub fn new_default(device: Arc<Device>) -> Self {
let bump = Arc::new(GenericMemoryAllocator::new(
device.clone(),
GenericMemoryAllocatorCreateInfo {
// Use the same size for all.
// Many of these types wont be touched, and thus won't actually be allocated.
block_sizes: &device
.physical_device()
.memory_properties()
.memory_types
.iter()
.map(|_| Self::SMALL_IMAGE_STAGE_BLOCK + Self::SMALL_VERTEX_BLOCK)
.collect::<Vec<_>>(),
// These are transient resources, and should *always* return their mem in a
// sub-divide-able manner to the pool after they're destroyed.
dedicated_allocation: false,
..Default::default()
},
));
// Smarter allocator for long-lived images
let images = Arc::new(StandardMemoryAllocator::new(
device.clone(),
GenericMemoryAllocatorCreateInfo {
// Use the same size for all.
// Many of these types wont be touched, and thus won't actually be allocated.
block_sizes: &device
.physical_device()
.memory_properties()
.memory_types
.iter()
.map(|_| Self::IMAGE_BLOCK)
.collect::<Vec<_>>(),
dedicated_allocation: true,
..Default::default()
},
));
Self::default_from_allocs(bump, images)
}
fn default_from_allocs(
bump: Arc<GenericMemoryAllocator<BumpAllocator>>,
images: Arc<StandardMemoryAllocator>,
) -> Self {
// Neither of these are hard limits and will grow transparently if needed but are good reasonable guesses
// Low-overhead bump memory for short-lived staging buffers
Self {
image_stage: SubbufferAllocator::new(bump.clone(), SubbufferAllocatorCreateInfo {
arena_size: Self::SMALL_IMAGE_STAGE_BLOCK,
buffer_usage: BufferUsage::TRANSFER_SRC,
memory_type_filter: MemoryTypeFilter::HOST_SEQUENTIAL_WRITE,
..Default::default()
}),
vertex_index: SubbufferAllocator::new(bump.clone(), SubbufferAllocatorCreateInfo {
arena_size: Self::SMALL_VERTEX_BLOCK,
buffer_usage: BufferUsage::VERTEX_BUFFER | BufferUsage::INDEX_BUFFER,
memory_type_filter: MemoryTypeFilter::HOST_SEQUENTIAL_WRITE,
..Default::default()
}),
bump,
images,
}
}
pub fn share(&self) -> Self {
Self::default_from_allocs(self.bump.clone(), self.images.clone())
}
}

// Every member is sync, so we don't need exclusive access to allocate.
impl Allocators for &DefaultAllocators {
type Error = ();

fn make_vertex_index_buffer(
&mut self,
device_layout: DeviceLayout,
) -> Result<Subbuffer<[u8]>, Self::Error> {
self.vertex_index.allocate(device_layout).map_err(|_| ())
}

fn make_image_stage_buffer(
&mut self,
bytes_len: NonZeroDeviceSize,
) -> Result<Subbuffer<[u8]>, Self::Error> {
// Infallible, align of one can never overflow `DeviceSize`
let layout = DeviceLayout::new(bytes_len, DeviceAlignment::MIN).unwrap();
self.image_stage.allocate(layout).map_err(|_| ())
}

fn make_image(&mut self, info: ImageCreateInfo) -> Result<Arc<Image>, Self::Error> {
Image::new(self.images.clone(), info, AllocationCreateInfo {
memory_type_filter: MemoryTypeFilter::PREFER_DEVICE,
..Default::default()
})
.map_err(|_| ())
}
}

// Delegate to &DefaultAllocators impl.
impl Allocators for DefaultAllocators {
// Forward Err to &DefaultAllocators
type Error = <&'static DefaultAllocators as Allocators>::Error;

fn make_vertex_index_buffer(
&mut self,
device_layout: DeviceLayout,
) -> Result<Subbuffer<[u8]>, Self::Error> {
(&*self).make_vertex_index_buffer(device_layout)
}

fn make_image_stage_buffer(
&mut self,
bytes_len: NonZeroDeviceSize,
) -> Result<Subbuffer<[u8]>, Self::Error> {
(&*self).make_image_stage_buffer(bytes_len)
}

fn make_image(&mut self, info: ImageCreateInfo) -> Result<Arc<Image>, Self::Error> {
(&*self).make_image(info)
}
}
Loading
Loading