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

Texture atlas #154

Merged
merged 23 commits into from
Feb 28, 2020
Merged
Changes from 1 commit
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
1bcfc9a
Implemented a texture atlas for images and svgs.
Maldela Jan 10, 2020
743637e
Merged image and svg texture atlases into one owned by the image pipe…
Maldela Jan 10, 2020
82e0675
Some small debug changes
Maldela Jan 11, 2020
8562a4c
Fixed texture bleeding
Maldela Jan 12, 2020
2f77a6b
Use array of atlases instead of one growing indefinitely.
Maldela Jan 13, 2020
3f38835
Implement allocating large images across multiple texture array layers.
Maldela Jan 16, 2020
c099692
Batch image draw calls into one with multiple instances
Maldela Jan 17, 2020
2f695ef
Updated shaders and removed debug_stub_derive dependency
Maldela Jan 17, 2020
8f9f44b
When deallocating the last allocation in an allocator mark its layer …
Maldela Jan 18, 2020
4617da2
Implemented automatic deallocation of texture space for dropped alloc…
Maldela Jan 19, 2020
82f0a49
Recompile `image` shaders
hecrj Feb 25, 2020
59d45a5
Refactor texture atlas
hecrj Feb 26, 2020
c58d94f
Avoid creating a vertex buffer every frame
hecrj Feb 26, 2020
48d7028
Fix multiple issues from the refactoring
hecrj Feb 26, 2020
d06d06e
Deallocate atlas entries and remove padding
hecrj Feb 26, 2020
883a9f2
Add `env_logger` to `svg` example
hecrj Feb 26, 2020
6cb7fb6
Remove unused code warnings in `iced_wgpu::image`
hecrj Feb 26, 2020
deedf6e
Make new `texture` module private for now
hecrj Feb 26, 2020
271725f
Derive `Debug` for `raster::Memory`
hecrj Feb 26, 2020
bb397cc
Move `Debug` implementation for `vector::Svg`
hecrj Feb 26, 2020
fc55e3a
Move `Atlas::deallocate` after `allocate`
hecrj Feb 26, 2020
4e7159c
Stop creating image pipeline when unnecessary
hecrj Feb 28, 2020
88d4cd0
Remove unnecessary `pub(crate) use`
hecrj Feb 28, 2020
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
10 changes: 6 additions & 4 deletions wgpu/src/image.rs
Original file line number Diff line number Diff line change
@@ -319,6 +319,8 @@ impl Pipeline {
let texture_version = self.texture_atlas.layer_count();

if self.texture_version != texture_version {
log::info!("Atlas has grown. Recreating bind group...");

self.texture =
device.create_bind_group(&wgpu::BindGroupDescriptor {
layout: &self.texture_layout,
@@ -525,12 +527,12 @@ fn add_instance(
_position: position,
_size: size,
_position_in_atlas: [
x as f32 / atlas::SIZE as f32,
y as f32 / atlas::SIZE as f32,
(x as f32 + 0.5) / atlas::SIZE as f32,
(y as f32 + 0.5) / atlas::SIZE as f32,
],
_size_in_atlas: [
width as f32 / atlas::SIZE as f32,
height as f32 / atlas::SIZE as f32,
(width as f32 - 0.5) / atlas::SIZE as f32,
(height as f32 - 0.5) / atlas::SIZE as f32,
],
_layer: layer as u32,
};
5 changes: 2 additions & 3 deletions wgpu/src/image/raster.rs
Original file line number Diff line number Diff line change
@@ -83,10 +83,9 @@ impl Cache {
if let Memory::Host(image) = memory {
let (width, height) = image.dimensions();

let allocation =
atlas.upload(width, height, &image, device, encoder)?;
let entry = atlas.upload(width, height, &image, device, encoder)?;

*memory = Memory::Device(allocation);
*memory = Memory::Device(entry);
}

if let Memory::Device(allocation) = memory {
52 changes: 33 additions & 19 deletions wgpu/src/texture/atlas.rs
Original file line number Diff line number Diff line change
@@ -10,7 +10,7 @@ pub use layer::Layer;

use allocator::Allocator;

pub const SIZE: u32 = 4096;
pub const SIZE: u32 = 2048;

#[derive(Debug)]
pub struct Atlas {
@@ -78,31 +78,42 @@ impl Atlas {
entry
};

log::info!("Allocated atlas entry: {:?}", entry);

let buffer = device
.create_buffer_mapped(data.len(), wgpu::BufferUsage::COPY_SRC)
.fill_from_slice(data);

match &entry {
Entry::Contiguous(allocation) => {
self.upload_texture(&buffer, 0, &allocation, encoder);
self.upload_allocation(
&buffer,
width,
height,
0,
&allocation,
encoder,
);
}
Entry::Fragmented { fragments, .. } => {
for fragment in fragments {
let (x, y) = fragment.allocation.position();

let offset =
(y * height + x) as usize * std::mem::size_of::<C>();
let (x, y) = fragment.position;
let offset = (y * width + x) as usize * 4;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have simplified this a bunch. It turns out you can use copy_buffer_to_texture to upload a sub-region of an image quite easily.


self.upload_texture(
self.upload_allocation(
&buffer,
offset as u64,
width,
height,
offset,
&fragment.allocation,
encoder,
);
}
}
}

log::info!("Current atlas: {:?}", &self);

Some(entry)
}

@@ -204,10 +215,12 @@ impl Atlas {
None
}

fn upload_texture(
fn upload_allocation(
&mut self,
buffer: &wgpu::Buffer,
offset: u64,
image_width: u32,
image_height: u32,
offset: usize,
allocation: &Allocation,
encoder: &mut wgpu::CommandEncoder,
) {
@@ -224,9 +237,9 @@ impl Atlas {
encoder.copy_buffer_to_texture(
wgpu::BufferCopyView {
buffer,
offset,
row_pitch: 4 * width,
image_height: height,
offset: offset as u64,
row_pitch: 4 * image_width,
image_height,
},
wgpu::TextureCopyView {
texture: &self.texture,
@@ -258,7 +271,7 @@ impl Atlas {
height: SIZE,
depth: 1,
},
array_layer_count: (self.layers.len() + amount) as u32,
array_layer_count: self.layers.len() as u32,
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
@@ -268,7 +281,11 @@ impl Atlas {
| wgpu::TextureUsage::SAMPLED,
});

for (i, layer) in self.layers.iter_mut().enumerate() {
let amount_to_copy = self.layers.len() - amount;

for (i, layer) in
self.layers.iter_mut().take(amount_to_copy).enumerate()
{
if layer.is_empty() {
continue;
}
@@ -302,10 +319,7 @@ impl Atlas {
);
}

for _ in 0..amount {
self.layers.push(Layer::Empty);
}

self.texture = new_texture;
self.texture_view = self.texture.create_default_view();
}
}
Copy link
Member

@hecrj hecrj Feb 26, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall, I really like the simplicity of this module.

61 changes: 45 additions & 16 deletions wgpu/src/texture/atlas/allocator.rs
Original file line number Diff line number Diff line change
@@ -2,41 +2,70 @@ use guillotiere::{AtlasAllocator, Size};

pub struct Allocator {
raw: AtlasAllocator,
size: u32,
}

impl Allocator {
const PADDING: u32 = 1;

pub fn new(size: u32) -> Allocator {
let raw = AtlasAllocator::new(Size::new(size as i32, size as i32));

Allocator { raw }
Allocator { raw, size }
}

pub fn allocate(&mut self, width: u32, height: u32) -> Option<Region> {
let allocation = self
.raw
.allocate(Size::new(width as i32 + 2, height as i32 + 2))?;
let padding = (
if width + Self::PADDING * 2 < self.size {
Self::PADDING
} else {
0
},
if height + Self::PADDING * 2 < self.size {
Self::PADDING
} else {
0
},
);

let allocation = self.raw.allocate(Size::new(
(width + padding.0 * 2) as i32,
(height + padding.1 * 2) as i32,
))?;

Some(Region(allocation))
Some(Region {
allocation,
padding,
})
}

pub fn deallocate(&mut self, region: Region) {
self.raw.deallocate(region.0.id);
self.raw.deallocate(region.allocation.id);
}
}

pub struct Region(guillotiere::Allocation);
pub struct Region {
allocation: guillotiere::Allocation,
padding: (u32, u32),
}

impl Region {
pub fn position(&self) -> (u32, u32) {
let rectangle = &self.0.rectangle;
let rectangle = &self.allocation.rectangle;

(rectangle.min.x as u32 + 1, rectangle.min.y as u32 + 1)
(
rectangle.min.x as u32 + self.padding.0,
rectangle.min.y as u32 + self.padding.1,
)
}

pub fn size(&self) -> (u32, u32) {
let size = self.0.rectangle.size();
let size = self.allocation.rectangle.size();

(size.width as u32 - 2, size.height as u32 - 2)
(
size.width as u32 - self.padding.0 * 2,
size.height as u32 - self.padding.1 * 2,
)
}
}

@@ -48,10 +77,10 @@ impl std::fmt::Debug for Allocator {

impl std::fmt::Debug for Region {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Region {{ id: {:?}, rectangle: {:?} }}",
self.0.id, self.0.rectangle
)
f.debug_struct("Region")
.field("id", &self.allocation.id)
.field("rectangle", &self.allocation.rectangle)
.field("padding", &self.padding)
.finish()
}
}