diff --git a/.gitignore b/.gitignore index 293f7889e..7bb762623 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ target/ android-build/ **/*.pvp **/*.spv +**/local/ ## other artifacts peridot-sdk/ diff --git a/examples/mpui/Cargo.toml b/examples/mpui/Cargo.toml new file mode 100644 index 000000000..ad662800c --- /dev/null +++ b/examples/mpui/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "mpui" +version = "0.1.0" +authors = ["S.Percentage "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +bedrock = { git = "https://github.com/Pctg-x8/bedrock", branch = "peridot" } +peridot = { path = "../.." } +peridot-vg = { path = "../../vg" } diff --git a/examples/mpui/ci-test.sh b/examples/mpui/ci-test.sh new file mode 100755 index 000000000..25e513520 --- /dev/null +++ b/examples/mpui/ci-test.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +cargo check --verbose --features peridot-vg/use-freetype,bedrock/DynamicLoaded diff --git a/examples/mpui/peridot.toml b/examples/mpui/peridot.toml new file mode 100644 index 000000000..579240ca9 --- /dev/null +++ b/examples/mpui/peridot.toml @@ -0,0 +1,2 @@ +app_package_id = "io.ct2.peridot.examples.mpui" +title = "Peridot Examples - Multiplane UI" diff --git a/examples/mpui/src/lib.rs b/examples/mpui/src/lib.rs new file mode 100644 index 000000000..3b2a4ae74 --- /dev/null +++ b/examples/mpui/src/lib.rs @@ -0,0 +1,618 @@ +use bedrock as br; +use br::MemoryBound; +use peridot::mthelper::{DynamicMut, DynamicMutabilityProvider, SharedRef}; + +pub struct Game { + ui: UIContext, + ui_main: UIPlane, + res_storage: peridot::ResourceStorage< + peridot::Buffer< + br::BufferObject, + br::DeviceMemoryObject, + >, + peridot::StdImage, + >, + marker: std::marker::PhantomData<*const NL>, +} +impl Game { + pub const NAME: &'static str = "Multiplane UI Demo"; + pub const VERSION: (u32, u32, u32) = (0, 1, 0); +} +impl peridot::FeatureRequests for Game {} +impl peridot::EngineEvents for Game { + fn init(e: &mut peridot::Engine) -> Self { + let mut res_storage_alloc = peridot::BulkedResourceStorageAllocator::new(); + let mut init_cp_batch = peridot::TransferBatch::new(); + let mut ui = UIContext::new( + e.graphics(), + &mut res_storage_alloc, + 2048, + &mut init_cp_batch, + ); + let uifont = ui + .load_font("sans-serif", &peridot_vg::FontProperties::default()) + .expect("Failed to load UI Font"); + let mut ui_main = UIPlane::new(); + ui_main.set_root(Box::new(StaticLabel::new(uifont, "test", 12.0))); + ui_main.prepare_render(&mut ui, e.graphics()); + ui_main.layout_all(); + + Game { + res_storage: res_storage_alloc + .alloc(e.graphics()) + .expect("Failed to allocate resource storages"), + ui, + ui_main, + marker: std::marker::PhantomData, + } + } +} + +use std::collections::HashMap; + +pub struct DynamicUpdateBufferWriteRange<'a, T> { + mapped_owner: + peridot::AutocloseMappedMemoryRange<'a, br::DeviceMemoryObject>, + _back_data: std::marker::PhantomData<*mut T>, +} +impl std::ops::Deref for DynamicUpdateBufferWriteRange<'_, T> { + type Target = T; + + fn deref(&self) -> &T { + unsafe { self.mapped_owner.get(0) } + } +} +impl std::ops::DerefMut for DynamicUpdateBufferWriteRange<'_, T> { + fn deref_mut(&mut self) -> &mut T { + unsafe { self.mapped_owner.get_mut(0) } + } +} + +pub struct DynamicUpdateBufferWriteSliceRange<'a, T> { + mapped_owner: + peridot::AutocloseMappedMemoryRange<'a, br::DeviceMemoryObject>, + slice_length: usize, + _back_data: std::marker::PhantomData<*mut T>, +} +impl std::ops::Deref for DynamicUpdateBufferWriteSliceRange<'_, T> { + type Target = [T]; + + fn deref(&self) -> &[T] { + unsafe { self.mapped_owner.slice(0, self.slice_length) } + } +} +impl std::ops::DerefMut for DynamicUpdateBufferWriteSliceRange<'_, T> { + fn deref_mut(&mut self) -> &mut [T] { + unsafe { self.mapped_owner.slice_mut(0, self.slice_length) } + } +} + +pub trait StatedBufferRegion { + fn byte_length(&self) -> usize; + fn byte_range_dev(&self) -> std::ops::Range; + fn byte_range(&self) -> std::ops::Range; +} + +pub struct StatedBufferSliceRegion { + pub offset: u64, + pub first_usage_stage: br::PipelineStageFlags, + pub access_mask: br::vk::VkAccessFlags, + pub slice_length: usize, + _element: std::marker::PhantomData, +} +impl StatedBufferSliceRegion { + pub const fn new( + offset: u64, + first_usage_stage: br::PipelineStageFlags, + access_mask: br::vk::VkAccessFlags, + slice_length: usize, + ) -> Self { + Self { + offset, + first_usage_stage, + access_mask, + slice_length, + _element: std::marker::PhantomData, + } + } +} +impl StatedBufferRegion for StatedBufferSliceRegion { + fn byte_length(&self) -> usize { + std::mem::size_of::() * self.slice_length + } + fn byte_range_dev(&self) -> std::ops::Range { + self.offset..(self.offset + self.byte_length() as u64) + } + fn byte_range(&self) -> std::ops::Range { + self.offset as _..(self.offset as usize + self.byte_length()) + } +} + +/// Buffer region with execution time states +pub struct StatedBufferRegionSingleElement { + pub offset: u64, + pub first_usage_stage: br::PipelineStageFlags, + pub access_mask: br::vk::VkAccessFlags, + _buffer_content: std::marker::PhantomData, +} +impl StatedBufferRegionSingleElement { + pub const fn new( + offset: u64, + first_usage_stage: br::PipelineStageFlags, + access_mask: br::vk::VkAccessFlags, + ) -> Self { + Self { + offset, + first_usage_stage, + access_mask, + _buffer_content: std::marker::PhantomData, + } + } +} +impl StatedBufferRegion for StatedBufferRegionSingleElement { + fn byte_length(&self) -> usize { + std::mem::size_of::() + } + fn byte_range_dev(&self) -> std::ops::Range { + self.offset..(self.offset + self.byte_length() as u64) + } + fn byte_range(&self) -> std::ops::Range { + self.offset as _..self.offset as usize + self.byte_length() + } +} + +pub trait BufferUpdateRequestData { + fn data_bytes(&self) -> &[u8]; + fn dest_buffer(&self) -> &dyn br::Buffer; + fn dest_offset(&self) -> br::vk::VkDeviceSize; + fn dest_barrier(&self) -> (br::PipelineStageFlags, br::vk::VkAccessFlags); +} + +pub struct BufferUpdateRequest<'d, T: Clone, Buffer: br::Buffer> { + pub data: std::borrow::Cow<'d, T>, + pub dest: peridot::DeviceBufferView, + pub dest_barriers: (br::PipelineStageFlags, br::vk::VkAccessFlags), +} +impl<'d, T: Clone, Buffer: br::Buffer> BufferUpdateRequestData + for BufferUpdateRequest<'d, T, Buffer> +{ + fn data_bytes(&self) -> &[u8] { + unsafe { + std::slice::from_raw_parts( + self.data.as_ref() as *const T as _, + std::mem::size_of::(), + ) + } + } + + fn dest_buffer(&self) -> &dyn br::Buffer { + &self.dest.buffer + } + + fn dest_offset(&self) -> br::vk::VkDeviceSize { + self.dest.offset + } + + fn dest_barrier(&self) -> (br::PipelineStageFlags, br::vk::VkAccessFlags) { + self.dest_barriers + } +} + +pub struct BufferArrayUpdateRequest<'d, T: Clone, Buffer: br::Buffer> { + pub data: std::borrow::Cow<'d, [T]>, + pub dest: peridot::DeviceBufferView, + pub dest_barriers: (br::PipelineStageFlags, br::vk::VkAccessFlags), +} +impl<'d, T: Clone, Buffer: br::Buffer> BufferUpdateRequestData + for BufferArrayUpdateRequest<'d, T, Buffer> +{ + fn data_bytes(&self) -> &[u8] { + unsafe { + std::slice::from_raw_parts( + self.data.as_ref().as_ptr() as *const _, + self.data.len() * std::mem::size_of::(), + ) + } + } + + fn dest_buffer(&self) -> &dyn br::Buffer { + &self.dest.buffer + } + + fn dest_offset(&self) -> br::vk::VkDeviceSize { + self.dest.offset + } + + fn dest_barrier(&self) -> (br::PipelineStageFlags, br::vk::VkAccessFlags) { + self.dest_barriers + } +} + +pub struct BufferUpdateManager { + stg_data: SharedRef>, + cap: usize, +} +impl BufferUpdateManager { + const DEFAULT_CAPACITY: usize = 256; + + pub fn new( + e: &peridot::Graphics, + init_cp_batch: &mut peridot::TransferBatch, + ) -> br::Result { + let bp = peridot::BufferPrealloc::with_entries( + e, + std::iter::once(peridot::BufferContent::Raw(Self::DEFAULT_CAPACITY as _, 1)), + ); + let mut alloc = + peridot::BulkedResourceStorageAllocator::<_, peridot::StdImageBackend>::new(); + alloc.add_buffer(bp.build_upload()?); + let peridot::ResourceStorage { mut buffers, .. } = alloc.alloc_upload(e)?; + let stg_data = SharedRef::new(DynamicMut::new(buffers.pop().expect("no objects?"))); + + init_cp_batch.add_buffer_graphics_ready( + br::PipelineStageFlags::HOST, + stg_data.clone(), + 0..Self::DEFAULT_CAPACITY as _, + br::AccessFlags::HOST.write, + ); + + Ok(Self { + stg_data, + cap: Self::DEFAULT_CAPACITY, + }) + } + + fn reserve_enough( + &mut self, + e: &peridot::Graphics, + request_size: usize, + tfb: &mut peridot::TransferBatch, + ) -> br::Result<()> { + if self.cap >= request_size { + return Ok(()); + } + + let bp = peridot::BufferPrealloc::with_entries( + e, + std::iter::once(peridot::BufferContent::Raw(request_size as _, 1)), + ); + let mut alloc = + peridot::BulkedResourceStorageAllocator::<_, peridot::StdImageBackend>::new(); + alloc.add_buffer(bp.build_upload()?); + let peridot::ResourceStorage { mut buffers, .. } = alloc.alloc_upload(e)?; + + self.cap = request_size; + self.stg_data = SharedRef::new(DynamicMut::new(buffers.pop().expect("no objects?"))); + + tfb.add_buffer_graphics_ready( + br::PipelineStageFlags::HOST, + self.stg_data.clone(), + 0..Self::DEFAULT_CAPACITY as _, + br::AccessFlags::HOST.write, + ); + + Ok(()) + } + + pub fn run( + &mut self, + e: &mut peridot::Graphics, + requests: &[Box>], + ) -> br::Result<()> { + let mut pre_tfb = peridot::TransferBatch::new(); + let mut total_size = 0; + let mut placement_offsets = Vec::with_capacity(requests.len()); + for r in requests { + placement_offsets.push(total_size); + total_size += r.data_bytes().len(); + } + self.reserve_enough(e, total_size, &mut pre_tfb)?; + pre_tfb.submit(e)?; + + self.stg_data + .borrow_mut() + .guard_map(0..total_size as _, |range| { + for (r, &o) in requests.iter().zip(&placement_offsets) { + unsafe { + range + .slice_mut(o, r.data_bytes().len()) + .copy_from_slice(r.data_bytes()); + } + } + })?; + + let stg_data = self.stg_data.borrow(); + let mut tfb = peridot::TransferBatch::new(); + for (r, &o) in requests.iter().zip(&placement_offsets) { + tfb.add_copying_buffer( + stg_data.with_dev_offset_ref(o as _), + peridot::DeviceBufferView { + buffer: r.dest_buffer(), + offset: r.dest_offset(), + }, + r.data_bytes().len() as _, + ); + tfb.add_buffer_graphics_ready( + r.dest_barrier().0, + r.dest_buffer(), + o as _..(o + r.data_bytes().len()) as _, + r.dest_barrier().1, + ); + } + + tfb.submit(e) + } +} + +#[repr(transparent)] +#[derive(Clone, Copy, Hash, PartialEq, Eq)] +pub struct FontId(usize); +pub struct UIContext { + charatlas: peridot::DynamicTextureAtlas, + font_provider: peridot_vg::FontProvider, + fonts: Vec, + baked_characters: HashMap<(FontId, char), peridot::TextureSlice>, + update_buffer: BufferUpdateManager, +} +impl UIContext { + pub fn new( + g: &peridot::Graphics, + res_storage_alloc: &mut peridot::BulkedResourceStorageAllocator< + br::BufferObject, + peridot::StdImageBackend, + >, + character_atlas_size: u32, + init_cp_batch: &mut peridot::TransferBatch, + ) -> Self { + UIContext { + charatlas: peridot::DynamicTextureAtlas::new( + g, + peridot::math::Vector2(character_atlas_size, character_atlas_size), + br::vk::VK_FORMAT_R8_UNORM, + res_storage_alloc, + ) + .expect("Failed to create Dynamic Texture Atlas for Characters"), + font_provider: peridot_vg::FontProvider::new() + .expect("Failed to initialize font provider"), + fonts: Vec::new(), + baked_characters: HashMap::new(), + update_buffer: BufferUpdateManager::new(g, init_cp_batch) + .expect("Failed to create update buffer object"), + } + } + + pub fn load_font( + &mut self, + families: &str, + properties: &peridot_vg::FontProperties, + ) -> Result { + self.font_provider + .best_match(families, properties, 12.0) + .map(|f| self.register_font(f)) + } + pub fn register_font(&mut self, font: peridot_vg::Font) -> FontId { + self.fonts.push(font); + FontId(self.fonts.len() - 1) + } + pub fn query_baked_character( + &mut self, + font: FontId, + c: char, + ) -> Option<&peridot::TextureSlice> { + match self.baked_characters.entry((font, c)) { + std::collections::hash_map::Entry::Occupied(e) => Some(e.into_mut()), + std::collections::hash_map::Entry::Vacant(v) => { + // rasterize sdf and insert to cache + unimplemented!("Rasterize SDF and insert to cache"); + } + } + } +} + +#[derive(Clone)] +pub struct Transform { + pub position: peridot::math::Vector3, + pub scale: peridot::math::Vector3, + pub rotation: peridot::math::Quaternion, +} +impl Default for Transform { + fn default() -> Self { + Transform { + position: peridot::math::Zero::ZERO, + scale: peridot::math::One::ONE, + rotation: peridot::math::One::ONE, + } + } +} +pub struct PartialTransform { + pub position: Option>, + pub scale: Option>, + pub rotation: Option>, +} +impl Default for PartialTransform { + fn default() -> Self { + PartialTransform { + position: None, + scale: None, + rotation: None, + } + } +} +pub trait UIElement { + #[allow(unused_variables)] + fn layout(&mut self, placed_at: &Transform) {} + fn layout_size(&self) -> peridot::math::Vector2; + fn prepare_render(&mut self, ctx: &mut UIContext, engine: &peridot::Graphics); +} + +pub struct UIPlane { + base: Transform, + root: Option>, +} +impl UIPlane { + pub fn new() -> Self { + UIPlane { + base: Transform::default(), + root: None, + } + } + + pub fn set_root(&mut self, root: Box) { + self.root = Some(root); + } + pub fn layout_all(&mut self) { + if let Some(ref mut r) = self.root { + r.layout(&self.base); + } + } + pub fn prepare_render(&mut self, ctx: &mut UIContext, engine: &peridot::Graphics) { + if let Some(ref mut r) = self.root { + r.prepare_render(ctx, engine); + } + } +} + +/// Layouted uneditable text +pub struct StaticLabelRenderer { + font: FontId, + text: String, + size: f32, + merged_vertices_view: Option< + peridot::DeviceBufferView< + peridot::Buffer< + br::BufferObject, + br::DeviceMemoryObject, + >, + >, + >, +} +pub struct StaticLabel { + transform: Transform, + renderer: StaticLabelRenderer, +} +impl UIElement for StaticLabel { + fn layout_size(&self) -> peridot::math::Vector2 { + unimplemented!("calc staticlabel layout metrics"); + } + fn prepare_render(&mut self, ctx: &mut UIContext, engine: &peridot::Graphics) { + if self.renderer.merged_vertices_view.is_none() { + let slices = self + .renderer + .text + .chars() + .map(|c| ctx.query_baked_character(self.renderer.font, c).cloned()) + .collect::>>() + .unwrap_or_else(Vec::new); + let mut bp = peridot::BufferPrealloc::new(engine); + bp.add(peridot::BufferContent::vertices::( + slices.len() * 6, + )); + let mut alloc = + peridot::BulkedResourceStorageAllocator::<_, peridot::StdImageBackend>::new(); + alloc.add_buffer( + bp.build_transferred() + .expect("Failed to build label vertices buffer"), + ); + let peridot::ResourceStorage { mut buffers, .. } = alloc + .alloc(engine) + .expect("Failed to allocate label vertices memory"); + let buffer = buffers.pop().expect("no object?"); + self.renderer.merged_vertices_view = Some(buffer.with_dev_offset(0)); + unimplemented!("character layouting"); + + /*let wref = ctx + .update_buffer + .push_update_dyn_array::( + engine, + &buffer, + &StatedBufferRegion::new( + 0, + br::PipelineStageFlags::VERTEX_INPUT, + br::AccessFlags::VERTEX_ATTRIBUTE_READ, + ), + slices.len() * 6, + );*/ + } + } +} +impl StaticLabel { + pub fn new(font: FontId, text: &str, size: f32) -> Self { + Self { + transform: Transform::default(), + renderer: StaticLabelRenderer { + font, + text: String::from(text), + size: size / 12.0, + merged_vertices_view: None, + }, + } + } + + pub fn set_text(&mut self, text: &str) { + self.renderer.merged_vertices_view = None; + self.renderer.text = String::from(text); + } +} + +pub struct VerticalLayoutGroup { + children: Vec<(Box, Transform)>, +} + +pub struct ListGroup { + children: Vec<(Box, Transform)>, +} +impl UIElement for ListGroup { + fn layout(&mut self, placed_at: &Transform) { + let mut offset_y = 0.0; + for (e, et) in &mut self.children { + *et = Transform { + position: placed_at.position.clone() + peridot::math::Vector3(0.0, offset_y, 0.0), + ..placed_at.clone() + }; + e.layout(et); + offset_y += e.layout_size().1; + } + } + fn layout_size(&self) -> peridot::math::Vector2 { + self.children + .iter() + .map(|e| e.0.layout_size()) + .fold(peridot::math::Vector2(0.0, 0.0), |s, es| { + peridot::math::Vector2(s.0.max(es.0), s.1 + es.1) + }) + } + fn prepare_render(&mut self, ctx: &mut UIContext, engine: &peridot::Graphics) { + for (e, _) in &mut self.children { + e.prepare_render(ctx, engine); + } + } +} +pub struct InlineGroup { + children: Vec<(Box, Transform)>, +} +impl UIElement for InlineGroup { + fn layout(&mut self, placed_at: &Transform) { + let mut offset_x = 0.0; + for (e, et) in &mut self.children { + *et = Transform { + position: placed_at.position.clone() + peridot::math::Vector3(offset_x, 0.0, 0.0), + ..placed_at.clone() + }; + e.layout(et); + offset_x += e.layout_size().0; + } + } + fn layout_size(&self) -> peridot::math::Vector2 { + self.children + .iter() + .map(|e| e.0.layout_size()) + .fold(peridot::math::Vector2(0.0, 0.0), |s, es| { + peridot::math::Vector2(s.0 + es.0, s.1.max(es.1)) + }) + } + fn prepare_render(&mut self, ctx: &mut UIContext, engine: &peridot::Graphics) { + for (e, _) in &mut self.children { + e.prepare_render(ctx, engine); + } + } +} diff --git a/examples/vg/assets/shaders/curveColorFixed.csh b/examples/vg/assets/shaders/curveColorFixed.csh index 7cd8d5e07..e6df704c6 100644 --- a/examples/vg/assets/shaders/curveColorFixed.csh +++ b/examples/vg/assets/shaders/curveColorFixed.csh @@ -42,7 +42,8 @@ FragmentShader { if(alpha < 0) discard; Target[0] = vec4(FillR, FillG, FillB, 1.0) * FillA * alpha;*/ + float sdScale = length(dFdx(helper_coord) + dFdy(helper_coord)); float sd = pow(helper_coord.x, 2) - helper_coord.y; - if(sd * lb_dir < 0) discard; - Target[0] = vec4(FillR, FillG, FillB, 1.0) * FillA; + // if(sd * lb_dir < 0) discard; + Target[0] = vec4(FillR, FillG, FillB, 1.0) * FillA * clamp(0.0, 1.0, (1.0 / sdScale) * sd * lb_dir + 0.5); } diff --git a/examples/vg/src/lib.rs b/examples/vg/src/lib.rs index 644f6eb10..f06b12520 100644 --- a/examples/vg/src/lib.rs +++ b/examples/vg/src/lib.rs @@ -157,7 +157,7 @@ impl peridot::EngineEvents for Game { let mut bp = BufferPrealloc::new(&e.graphics()); let vg_offs = ctx.prealloc(&mut bp); - let vg_offs2 = ctx.prealloc(&mut bp); + let vg_offs2 = ctx2.prealloc(&mut bp); let buffer = bp.build_transferred().expect("Buffer Allocation"); let stg_buffer = bp.build_upload().expect("StgBuffer Allocation"); diff --git a/src/atlas.rs b/src/atlas.rs new file mode 100644 index 000000000..8537646f9 --- /dev/null +++ b/src/atlas.rs @@ -0,0 +1,103 @@ +//! Dynamic Atlas Management + +use bedrock as br; +use peridot_math::Vector2; + +use crate::{StdImage, StdImageBackend}; + +#[derive(Clone, Debug)] +pub struct TextureSlice { + pub offset: Vector2, + pub size: Vector2, +} + +pub struct DynamicTextureAtlas { + pub resource_index: usize, + pub size: Vector2, + pub binning: B, +} +impl DynamicTextureAtlas { + pub fn new( + g: &crate::Graphics, + size: Vector2, + format: br::vk::VkFormat, + storage_alloc: &mut crate::BulkedResourceStorageAllocator, + ) -> br::Result { + let usage = br::ImageUsage::SAMPLED.transfer_dest(); + let image = br::ImageDesc::new(&size, format, usage, br::ImageLayout::Undefined) + .create(g.device.clone())?; + + Ok(DynamicTextureAtlas { + resource_index: storage_alloc.add_image(image), + binning: B::with_texture_size(size.clone()), + size, + }) + } + pub fn resource_entity<'s, ExtBuffer: br::Buffer>( + &self, + storage: &'s crate::ResourceStorage, + ) -> &'s StdImage { + storage + .get_image(self.resource_index) + .expect("invalid storage") + } + + pub fn request_rect(&mut self, size: &Vector2) -> Option { + self.binning.request_rect(size) + } + pub fn to_uv(&self, p: &Vector2) -> Vector2 { + Vector2( + p.0 as f32 / self.size.0 as f32, + p.1 as f32 / self.size.1 as f32, + ) + } +} + +/// Processes binning of TextureSlices in Dynamic Texture Atlas +pub trait BinningAlgorithm { + fn with_texture_size(tex_size: Vector2) -> Self; + fn request_rect(&mut self, size: &Vector2) -> Option; +} +/// Most simple binning algorithm +pub struct BookshelfBinning { + limit: Vector2, + current_base_y: u32, + current_max_h: u32, + current_used_x: u32, +} +impl BinningAlgorithm for BookshelfBinning { + fn with_texture_size(tex_size: Vector2) -> Self { + BookshelfBinning { + current_base_y: 0, + current_max_h: 0, + current_used_x: 0, + limit: tex_size, + } + } + fn request_rect(&mut self, size: &Vector2) -> Option { + if self.limit.0 < size.0 || self.limit.1 < size.1 { + // too large + return None; + } + + if self.current_used_x + size.0 > self.limit.0 { + // next line + self.current_base_y += self.current_max_h; + self.current_max_h = 0; + self.current_used_x = 0; + } + + if self.current_base_y + size.1 > self.limit.1 { + // no suitable region + return None; + } + + let offs = Vector2(self.current_used_x, self.current_base_y); + self.current_used_x += size.0; + self.current_max_h = self.current_max_h.max(size.1); + Some(TextureSlice { + offset: offs, + size: size.clone(), + }) + } +} diff --git a/src/batch.rs b/src/batch.rs index 392314661..350fe0dc1 100644 --- a/src/batch.rs +++ b/src/batch.rs @@ -68,43 +68,47 @@ impl< } /// Batching Manager for Transferring Operations. -pub struct TransferBatch { +pub struct TransferBatch<'d> { barrier_range_src: BTreeMap< - ResourceKey>>, + ResourceKey + 'd>>, Range, >, barrier_range_dst: BTreeMap< - ResourceKey>>, + ResourceKey + 'd>>, Range, >, - org_layout_src: - BTreeMap>>, br::ImageLayout>, - org_layout_dst: - BTreeMap>>, br::ImageLayout>, + org_layout_src: BTreeMap< + ResourceKey + 'd>>, + br::ImageLayout, + >, + org_layout_dst: BTreeMap< + ResourceKey + 'd>>, + br::ImageLayout, + >, copy_buffers: HashMap< ( - ResourceKey>>, - ResourceKey>>, + ResourceKey + 'd>>, + ResourceKey + 'd>>, ), Vec, >, init_images: BTreeMap< - ResourceKey>>, + ResourceKey + 'd>>, ( br::vk::VkExtent3D, - Box>, + Box + 'd>, br::vk::VkDeviceSize, ), >, ready_barriers: BTreeMap< br::PipelineStageFlags, ReadyResourceBarriers< - Box>, - Box>, + Box + 'd>, + Box + 'd>, >, >, } -impl TransferBatch { +impl<'d> TransferBatch<'d> { pub fn new() -> Self { Self { barrier_range_src: BTreeMap::new(), @@ -117,15 +121,21 @@ impl TransferBatch { } } + pub fn clear(&mut self) { + self.barrier_range_src.clear(); + self.barrier_range_dst.clear(); + self.org_layout_src.clear(); + self.org_layout_dst.clear(); + self.copy_buffers.clear(); + self.init_images.clear(); + self.ready_barriers.clear(); + } + /// Add copying operation between buffers. pub fn add_copying_buffer( &mut self, - src: crate::DeviceBufferView< - impl br::VkHandle + Clone + 'static, - >, - dst: crate::DeviceBufferView< - impl br::VkHandle + Clone + 'static, - >, + src: crate::DeviceBufferView + Clone + 'd>, + dst: crate::DeviceBufferView + Clone + 'd>, bytes: br::vk::VkDeviceSize, ) { trace!( @@ -162,8 +172,8 @@ impl TransferBatch { #[inline] pub fn add_mirroring_buffer( &mut self, - src: impl br::VkHandle + Clone + 'static, - dst: impl br::VkHandle + Clone + 'static, + src: impl br::VkHandle + Clone + 'd, + dst: impl br::VkHandle + Clone + 'd, offset: br::vk::VkDeviceSize, bytes: br::vk::VkDeviceSize, ) { @@ -184,9 +194,7 @@ impl TransferBatch { pub fn init_image_from( &mut self, dest: impl br::Image + Clone + 'static, - src: crate::DeviceBufferView< - impl br::VkHandle + Clone + 'static, - >, + src: crate::DeviceBufferView + Clone + 'd>, ) { let size = (dest.size().width * dest.size().height) as u64 * (PixelFormat::from(dest.format()).bpp() >> 3) as u64; @@ -213,7 +221,7 @@ impl TransferBatch { pub fn add_buffer_graphics_ready( &mut self, dest_stage: br::PipelineStageFlags, - res: impl br::VkHandle + 'static, + res: impl br::VkHandle + 'd, byterange: Range, access_grants: br::vk::VkAccessFlags, ) { @@ -228,7 +236,7 @@ impl TransferBatch { pub fn add_image_graphics_ready( &mut self, dest_stage: br::PipelineStageFlags, - res: impl br::VkHandle + 'static, + res: impl br::VkHandle + 'd, layout: br::ImageLayout, ) { self.ready_barriers @@ -255,10 +263,10 @@ impl TransferBatch { #[inline] fn update_barrier_range_for( map: &mut BTreeMap< - ResourceKey + 'static>>, + ResourceKey + 'd>>, Range, >, - k: ResourceKey + 'static>>, + k: ResourceKey + 'd>>, new_range: Range, ) { let r = map.entry(k).or_insert_with(|| new_range.clone()); @@ -267,7 +275,7 @@ impl TransferBatch { } } /// Sinking Commands into CommandBuffers -impl TransferBatch { +impl<'d> TransferBatch<'d> { pub fn sink_transfer_commands(&self, r: &mut br::CmdRecord) { let src_barriers = self.barrier_range_src.iter().map(|(b, r)| { br::BufferMemoryBarrier::new( @@ -377,6 +385,22 @@ impl TransferBatch { ); } } + + pub fn submit(&self, e: &mut crate::Graphics) -> br::Result<()> { + e.submit_commands(|r| { + self.sink_transfer_commands(r); + self.sink_graphics_ready_commands(r); + }) + } + + #[cfg(feature = "mt")] + pub async fn submit_async(&self, e: &mut crate::Graphics) -> br::Result<()> { + e.submit_commands_async(|r| { + self.sink_transfer_commands(r); + self.sink_graphics_ready_commands(r); + })? + .await + } } /// Batching mechanism for Updating Descriptor Sets diff --git a/src/lib.rs b/src/lib.rs index 24ee511e2..166e0c735 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,6 +27,8 @@ mod debug; use self::debug::*; pub mod utils; pub use self::utils::*; +pub mod atlas; +pub use self::atlas::*; mod asset; pub use self::asset::*; diff --git a/src/resource.rs b/src/resource.rs index ed448ba52..e59ff1e3b 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -8,6 +8,7 @@ mod buffer; pub use self::buffer::*; mod image; pub use self::image::*; +use num::Integer; #[macro_export] macro_rules! align2 { @@ -16,6 +17,173 @@ macro_rules! align2 { }; } +pub struct BulkedResourceStorageAllocator { + buffers: Vec<(Buffer, u64)>, + images: Vec<(Image, u64)>, + buffers_top: u64, + images_top: u64, + images_align_requirement: u64, + memory_type_bitmask: u32, +} +impl + BulkedResourceStorageAllocator +{ + pub const fn new() -> Self { + Self { + buffers: Vec::new(), + images: Vec::new(), + buffers_top: 0, + images_top: 0, + images_align_requirement: 0, + memory_type_bitmask: std::u32::MAX, + } + } + + pub fn add_buffer(&mut self, buffer: Buffer) -> usize { + let req = buffer.requirements(); + let new_offset = align2!(self.buffers_top, req.alignment); + self.buffers.push((buffer, new_offset)); + self.memory_type_bitmask &= req.memoryTypeBits; + self.buffers_top = new_offset + req.size; + + self.buffers.len() - 1 + } + pub fn add_image(&mut self, image: Image) -> usize { + let req = image.requirements(); + let new_offset = align2!(self.images_top, req.alignment); + self.images.push((image, new_offset)); + self.memory_type_bitmask &= req.memoryTypeBits; + self.images_top = new_offset + req.size; + self.images_align_requirement = self.images_align_requirement.lcm(&req.alignment); + + self.images.len() - 1 + } + pub fn add_images(&mut self, images: impl IntoIterator) -> usize { + let iter = images.into_iter(); + let (min, _) = iter.size_hint(); + if self.images.capacity() < self.images.len() + min { + self.images.reserve(min); + } + let first_id = self.images.len(); + for x in iter { + self.add_image(x); + } + + first_id + } + + pub fn alloc( + self, + g: &Graphics, + ) -> br::Result< + ResourceStorage< + crate::Buffer>, + crate::Image>, + >, + > { + let mt = g + .memory_type_manager + .device_local_index(self.memory_type_bitmask) + .expect("No device-local memory") + .index(); + let images_base = align2!(self.buffers_top, self.images_align_requirement); + let total_size = images_base + self.images_top; + info!( + target: "peridot", + "Allocating Device Memory: {total_size} bytes in {mt}(?0x{:x})", + self.memory_type_bitmask + ); + let mem = SharedRef::new(DynamicMut::new( + g.device.clone().allocate_memory(total_size as _, mt)?, + )); + + Ok(ResourceStorage { + buffers: self + .buffers + .into_iter() + .map(|(b, o)| crate::Buffer::bound(b, &mem, o)) + .collect::>()?, + images: self + .images + .into_iter() + .map(|(i, o)| crate::Image::bound(i, &mem, o + images_base)) + .collect::>()?, + }) + } + pub fn alloc_upload( + self, + g: &Graphics, + ) -> br::Result< + ResourceStorage< + crate::Buffer>, + crate::Image>, + >, + > { + let mt = g + .memory_type_manager + .exact_host_visible_index( + self.memory_type_bitmask, + br::MemoryPropertyFlags::HOST_COHERENT, + ) + .expect("No host-visible memory") + .index(); + let images_base = align2!(self.buffers_top, self.images_align_requirement.max(1)); + let total_size = images_base + self.images_top; + info!( + target: "peridot", + "Allocating Upload Memory: {} bytes in 0x{:x}(?0x{:x})", + total_size, mt, self.memory_type_bitmask + ); + let mem = SharedRef::new(DynamicMut::new( + g.device.clone().allocate_memory(total_size as _, mt)?, + )); + + Ok(ResourceStorage { + buffers: self + .buffers + .into_iter() + .map(|(b, o)| crate::Buffer::bound(b, &mem, o)) + .collect::>()?, + images: self + .images + .into_iter() + .map(|(i, o)| crate::Image::bound(i, &mem, o + images_base)) + .collect::>()?, + }) + } +} +pub struct ResourceStorage { + pub buffers: Vec, + pub images: Vec, +} +impl ResourceStorage { + pub fn get_buffer(&self, index: usize) -> Option<&Buffer> { + self.buffers.get(index) + } + + pub fn get_image(&self, index: usize) -> Option<&Image> { + self.images.get(index) + } +} + +pub struct AutocloseMappedMemoryRange<'m, Memory: br::DeviceMemory>( + Option>, +); +impl<'m, Memory: br::DeviceMemory> Deref for AutocloseMappedMemoryRange<'m, Memory> { + type Target = br::MappedMemoryRange<'m, Memory>; + + fn deref(&self) -> &Self::Target { + unsafe { self.0.as_ref().unwrap_unchecked() } + } +} +impl<'m, Memory: br::DeviceMemory> Drop for AutocloseMappedMemoryRange<'m, Memory> { + fn drop(&mut self) { + unsafe { + self.0.take().unwrap_unchecked().end(); + } + } +} + #[derive(Clone, Copy, Debug)] #[repr(i32)] pub enum PixelFormat { @@ -478,11 +646,13 @@ impl DeviceWorkingTextureAllocator<'_> { .chain(images_cube) .chain(images3) .collect::>()?; - let mut mb = MemoryBadget::, _>::new(g); - for img in images { - mb.add(MemoryBadgetEntry::Image(img)); - } - let mut bound_images = mb.alloc()?; + let mut storage_alloc = + BulkedResourceStorageAllocator::, _>::new(); + storage_alloc.add_images(images); + let ResourceStorage { + images: mut bound_images, + .. + } = storage_alloc.alloc(g)?; let mut cs_v3s = bound_images.split_off(self.planes.len()); let v3s = cs_v3s.split_off(self.cube.len()); @@ -494,7 +664,6 @@ impl DeviceWorkingTextureAllocator<'_> { .map(|(d, res)| { use br::Image; - let res = res.unwrap_image(); let view = res.create_view( None, None, @@ -516,7 +685,6 @@ impl DeviceWorkingTextureAllocator<'_> { .map(|(d, res)| { use br::Image; - let res = res.unwrap_image(); let view = res.create_view( None, Some(br::vk::VK_IMAGE_VIEW_TYPE_CUBE), @@ -538,7 +706,6 @@ impl DeviceWorkingTextureAllocator<'_> { .map(|(d, res)| { use br::Image; - let res = res.unwrap_image(); let view = res.create_view( None, None, diff --git a/src/resource/buffer.rs b/src/resource/buffer.rs index 1a4417fd6..75fd0e49f 100644 --- a/src/resource/buffer.rs +++ b/src/resource/buffer.rs @@ -12,6 +12,9 @@ use crate::{ DeviceObject, }; +pub type StdBufferBackend = br::BufferObject; +pub type StdBuffer = Buffer>; + /// A refcounted buffer object bound with a memory object. #[derive(Clone)] pub struct Buffer( @@ -41,10 +44,9 @@ impl Buffer) -> R, ) -> br::Result { let mut mem = self.1.borrow_mut(); - let mapped_range = AutocloseMappedMemoryRange( - mem.map((self.2 + range.start) as _..(self.2 + range.end) as _)? - .into(), - ); + let mapped_range = AutocloseMappedMemoryRange(Some( + mem.map((self.2 + range.start) as _..(self.2 + range.end) as _)?, + )); Ok(f(&mapped_range)) } @@ -164,7 +166,7 @@ pub enum BufferContent { StorageTexel(u64, u64), } impl BufferContent { - fn usage(&self, src: br::BufferUsage) -> br::BufferUsage { + pub fn usage(&self, src: br::BufferUsage) -> br::BufferUsage { use self::BufferContent::*; match *self { @@ -178,7 +180,7 @@ impl BufferContent { } } - fn alignment(&self, pd: &impl br::PhysicalDevice) -> u64 { + pub fn alignment(&self, pd: &impl br::PhysicalDevice) -> u64 { use self::BufferContent::*; match *self { @@ -194,7 +196,7 @@ impl BufferContent { } } - const fn size(&self) -> u64 { + pub const fn size(&self) -> u64 { use self::BufferContent::*; match *self { @@ -305,6 +307,16 @@ impl<'g> BufferPrealloc<'g> { common_align: 1, } } + pub fn with_entries( + g: &'g crate::Graphics, + entries: impl Iterator, + ) -> Self { + let mut this = Self::new(g); + for e in entries { + this.add(e); + } + this + } pub fn build(&self) -> br::Result> { br::BufferDesc::new(self.total as _, self.usage).create(self.g.device.clone()) diff --git a/src/resource/image.rs b/src/resource/image.rs index 1a676b15c..d762fb967 100644 --- a/src/resource/image.rs +++ b/src/resource/image.rs @@ -6,6 +6,9 @@ use bedrock as br; use crate::mthelper::DynamicMutabilityProvider; use crate::mthelper::{DynamicMut, SharedRef}; +pub type StdImageBackend = br::ImageObject; +pub type StdImage = crate::Image>; + /// A refcounted image object bound with a memory object. #[derive(Clone)] pub struct Image( diff --git a/src/resource/memory.rs b/src/resource/memory.rs index cabdd5663..b350071cf 100644 --- a/src/resource/memory.rs +++ b/src/resource/memory.rs @@ -1,4 +1,5 @@ //! DeviceMemory Helper +#![allow(deprecated)] use bedrock as br; use br::{Device, PhysicalDevice}; @@ -8,6 +9,7 @@ use crate::{ DeviceObject, }; +#[deprecated = "use BulkedResourceStorageAllocator and ResourceStorage for more efficient memory allocation"] pub struct MemoryBadget<'g, Buffer: br::Buffer, Image: br::Image> { g: &'g crate::Graphics, entries: Vec<(MemoryBadgetEntry, u64)>,