Skip to content
4 changes: 2 additions & 2 deletions dotrix_core/src/assets/mesh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,7 @@ mod tests {
[-width, width, width],
];

width = width / 2.0;
width /= 2.0;

let verticies_test_original_2: Vec<[f32; 3]> = vec![
[-width, -width, -width],
Expand All @@ -367,7 +367,7 @@ mod tests {
[-width, width, width],
];

width = width / 2.0;
width /= 2.0;

let verticies_test_original_3: Vec<[u32; 3]> = vec![
[-width as u32, -width as u32, -width as u32],
Expand Down
1 change: 1 addition & 0 deletions dotrix_core/src/assets/shader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ impl Shader {
/// Loads the shader to GPU
pub fn load(&mut self, renderer: &Renderer) {
if !self.module.loaded() {
self.module.label = self.name.clone();
renderer.load_shader(&mut self.module, &self.code);
}
}
Expand Down
13 changes: 13 additions & 0 deletions dotrix_core/src/assets/texture.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,17 @@ impl Texture {
pub fn unload(&mut self) {
self.buffer.unload();
}

/// Fetch data from the gpu
///
/// This is useful textures that are altered on the gpu
///
/// This operation is slow and should mostly be
/// used for debugging
pub fn fetch_from_gpu(
&mut self,
renderer: &mut Renderer,
) -> impl std::future::Future<Output = Result<Vec<u8>, wgpu::BufferAsyncError>> {
renderer.fetch_texture(&self.buffer, [self.width, self.height, self.depth])
}
}
2 changes: 1 addition & 1 deletion dotrix_core/src/cubemap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ impl Default for CubeMap {
bottom: Id::default(),
back: Id::default(),
front: Id::default(),
buffer: TextureBuffer::new("CubeMap Texture Buffer"),
buffer: TextureBuffer::new_cube("CubeMap Texture Buffer"),
}
}
}
Expand Down
60 changes: 59 additions & 1 deletion dotrix_core/src/renderer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,9 @@ impl Renderer {
buffer.load(self.context(), attributes, indices, count as u32);
}*/

/// Loads the texture buffer to GPU
/// Loads the texture buffer to GPU.
/// This will recreate the texture, as a result it must be rebound on any pipelines for changes
/// to take effect
pub fn load_texture<'a>(
&self,
texture: &mut Texture,
Expand All @@ -95,11 +97,42 @@ impl Renderer {
texture.load(self.context(), width, height, layers);
}

/// Load data from cpu to a texture buffer on GPU
/// This is a noop if texture has not been loaded with `load_texture`
/// Unexpected results/errors occur if the dimensions differs from it dimensions at load time
pub fn update_texture<'a>(
&self,
texture: &mut Texture,
width: u32,
height: u32,
layers: &'a [&'a [u8]],
) {
texture.update(self.context(), width, height, layers);
}

/// This will `[update_texture]` if texture has been loaded or `[load_texture]` if not
/// the same cavets of `[update_texture]` apply in that care must be taken not to change
/// the dimensions between `load` and `update`
pub fn update_or_load_texture<'a>(
&self,
texture: &mut Texture,
width: u32,
height: u32,
layers: &'a [&'a [u8]],
) {
texture.update_or_load(self.context(), width, height, layers);
}

/// Loads the buffer to GPU
pub fn load_buffer<'a>(&self, buffer: &mut Buffer, data: &'a [u8]) {
buffer.load(self.context(), data);
}

/// Create a buffer on GPU without data
pub fn create_buffer(&self, buffer: &mut Buffer, size: u32, mapped: bool) {
buffer.create(self.context(), size, mapped);
}

/// Loads the sampler to GPU
pub fn load_sampler(&self, sampler: &mut Sampler) {
sampler.load(self.context());
Expand All @@ -110,6 +143,27 @@ impl Renderer {
shader_module.load(self.context(), code);
}

/// Copy a texture to a buffer
pub fn copy_texture_to_buffer(
&mut self,
texture: &Texture,
buffer: &Buffer,
extent: [u32; 3],
bytes_per_pixel: u32,
) {
self.context_mut()
.run_copy_texture_to_buffer(texture, buffer, extent, bytes_per_pixel);
}

/// Fetch texture from GPU
pub fn fetch_texture(
&mut self,
texture: &Texture,
dimensions: [u32; 3],
) -> impl std::future::Future<Output = Result<Vec<u8>, wgpu::BufferAsyncError>> {
texture.fetch_from_gpu(dimensions, self.context_mut())
}

/// Forces engine to reload shaders
pub fn reload(&mut self) {
self.dirty = true;
Expand Down Expand Up @@ -263,6 +317,10 @@ pub fn release(mut renderer: Mut<Renderer>) {
if renderer.cycle == 0 {
renderer.cycle = 1;
}
// Check for resource cleanups and mapping callbacks
if let Some(context) = renderer.context.as_ref() {
context.device.poll(wgpu::Maintain::Poll);
}
}

/// Resize handling system
Expand Down
83 changes: 75 additions & 8 deletions dotrix_core/src/renderer/bindings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,20 @@ pub enum Binding<'a> {
Uniform(&'a str, Stage, &'a Buffer),
/// Texture binding
Texture(&'a str, Stage, &'a Texture),
/// Cube Texture binding
TextureCube(&'a str, Stage, &'a Texture),
/// 2D Texture Array binding
TextureArray(&'a str, Stage, &'a Texture),
/// 3D Texture binding
Texture3D(&'a str, Stage, &'a Texture),
/// Storage texture binding
StorageTexture(&'a str, Stage, &'a Texture, Access),
/// Storage texture cube binding
StorageTextureCube(&'a str, Stage, &'a Texture, Access),
/// Storage 2D texture array binding
StorageTextureArray(&'a str, Stage, &'a Texture, Access),
/// Storage texture binding 3D
StorageTexture3D(&'a str, Stage, &'a Texture, Access),
/// Texture sampler binding
Sampler(&'a str, Stage, &'a Sampler),
/// Storage binding
Expand Down Expand Up @@ -76,25 +86,41 @@ impl<'a> BindGroup<'a> {
visibility: stage.into(),
ty: wgpu::BindingType::Texture {
multisampled: false,
sample_type: wgpu::TextureSampleType::Float {
filterable: texture.is_filterable(),
},
sample_type: texture.sample_type(),
view_dimension: wgpu::TextureViewDimension::D2,
},
count: None,
},
Binding::Texture3D(_, stage, texture) => wgpu::BindGroupLayoutEntry {
Binding::TextureCube(_, stage, texture) => wgpu::BindGroupLayoutEntry {
binding: index as u32,
visibility: stage.into(),
ty: wgpu::BindingType::Texture {
multisampled: false,
sample_type: wgpu::TextureSampleType::Float {
filterable: texture.is_filterable(),
},
sample_type: texture.sample_type(),
view_dimension: wgpu::TextureViewDimension::Cube,
},
count: None,
},
Binding::TextureArray(_, stage, texture) => wgpu::BindGroupLayoutEntry {
binding: index as u32,
visibility: stage.into(),
ty: wgpu::BindingType::Texture {
multisampled: false,
sample_type: texture.sample_type(),
view_dimension: wgpu::TextureViewDimension::D2Array,
},
count: None,
},
Binding::Texture3D(_, stage, texture) => wgpu::BindGroupLayoutEntry {
binding: index as u32,
visibility: stage.into(),
ty: wgpu::BindingType::Texture {
multisampled: false,
sample_type: texture.sample_type(),
view_dimension: wgpu::TextureViewDimension::D3,
},
count: None,
},
Binding::StorageTexture(_, stage, texture, access) => wgpu::BindGroupLayoutEntry {
binding: index as u32,
visibility: stage.into(),
Expand All @@ -105,6 +131,42 @@ impl<'a> BindGroup<'a> {
},
count: None,
},
Binding::StorageTextureCube(_, stage, texture, access) => {
wgpu::BindGroupLayoutEntry {
binding: index as u32,
visibility: stage.into(),
ty: wgpu::BindingType::StorageTexture {
access: access.into(),
format: texture.format,
view_dimension: wgpu::TextureViewDimension::Cube,
},
count: None,
}
}
Binding::StorageTextureArray(_, stage, texture, access) => {
wgpu::BindGroupLayoutEntry {
binding: index as u32,
visibility: stage.into(),
ty: wgpu::BindingType::StorageTexture {
access: access.into(),
format: texture.format,
view_dimension: wgpu::TextureViewDimension::D2Array,
},
count: None,
}
}
Binding::StorageTexture3D(_, stage, texture, access) => {
wgpu::BindGroupLayoutEntry {
binding: index as u32,
visibility: stage.into(),
ty: wgpu::BindingType::StorageTexture {
access: access.into(),
format: texture.format,
view_dimension: wgpu::TextureViewDimension::D3,
},
count: None,
}
}
Binding::Sampler(_, stage, _) => wgpu::BindGroupLayoutEntry {
binding: index as u32,
visibility: stage.into(),
Expand Down Expand Up @@ -169,8 +231,13 @@ impl Bindings {
uniform.get().as_entire_binding()
}
Binding::Texture(_, _, texture)
| Binding::TextureCube(_, _, texture)
| Binding::TextureArray(_, _, texture)
| Binding::Texture3D(_, _, texture)
| Binding::StorageTexture(_, _, texture, _) => {
| Binding::StorageTexture(_, _, texture, _)
| Binding::StorageTextureCube(_, _, texture, _)
| Binding::StorageTextureArray(_, _, texture, _)
| Binding::StorageTexture3D(_, _, texture, _) => {
wgpu::BindingResource::TextureView(texture.get())
}
Binding::Sampler(_, _, sampler) => {
Expand Down
36 changes: 36 additions & 0 deletions dotrix_core/src/renderer/buffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,16 @@ impl Buffer {
Self::new(label).use_as_indirect()
}

/// Construct new Map Read buffer
pub fn map_read(label: &str) -> Self {
Self::new(label).use_as_map_read()
}

/// Construct new Map Write buffer
pub fn map_write(label: &str) -> Self {
Self::new(label).use_as_map_write()
}

/// Allow to use as Vertex Buffer
#[must_use]
pub fn use_as_vertex(mut self) -> Self {
Expand Down Expand Up @@ -82,6 +92,20 @@ impl Buffer {
self
}

/// Allow to use as Map Read Buffer
#[must_use]
pub fn use_as_map_read(mut self) -> Self {
self.usage |= wgpu::BufferUsages::MAP_READ;
self
}

/// Allow to use as Map Write Buffer
#[must_use]
pub fn use_as_map_write(mut self) -> Self {
self.usage |= wgpu::BufferUsages::MAP_WRITE;
self
}

/// Allow reading from buffer
#[must_use]
pub fn allow_read(mut self) -> Self {
Expand Down Expand Up @@ -116,6 +140,18 @@ impl Buffer {
}
}

/// Create buffer of size without data
///
/// Typically used for staging buffers
pub fn create(&mut self, ctx: &Context, size: u32, mapped: bool) {
self.wgpu_buffer = Some(ctx.device.create_buffer(&wgpu::BufferDescriptor {
label: Some(self.label.as_str()),
size: size as wgpu::BufferAddress,
usage: self.usage,
mapped_at_creation: mapped,
}));
}

/// Check if buffer isloaded
pub fn loaded(&self) -> bool {
self.wgpu_buffer.is_some()
Expand Down
42 changes: 42 additions & 0 deletions dotrix_core/src/renderer/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,48 @@ impl Context {
cpass.dispatch(args.work_groups.x, args.work_groups.y, args.work_groups.z);
}
}

pub(crate) fn run_copy_texture_to_buffer(
&mut self,
texture: &super::Texture,
buffer: &super::Buffer,
extent: [u32; 3],
bytes_per_pixel: u32,
) {
let encoder = self.encoder.as_mut().expect("WGPU encoder must be set");
let unpadded_bytes_per_row: u32 =
std::num::NonZeroU32::new(bytes_per_pixel as u32 * extent[0])
.unwrap()
.into();
let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT as u32;
let padded_bytes_per_row_padding = (align - unpadded_bytes_per_row % align) % align;
let padded_bytes_per_row = unpadded_bytes_per_row + padded_bytes_per_row_padding;

encoder.copy_texture_to_buffer(
wgpu::ImageCopyTexture {
texture: texture
.wgpu_texture
.as_ref()
.expect("Texture must be loaded"),
mip_level: 0,
origin: wgpu::Origin3d { x: 0, y: 0, z: 0 },
aspect: wgpu::TextureAspect::All,
},
wgpu::ImageCopyBuffer {
buffer: buffer.wgpu_buffer.as_ref().expect("Buffer must be ready"),
layout: wgpu::ImageDataLayout {
offset: 0,
bytes_per_row: Some(std::num::NonZeroU32::new(padded_bytes_per_row).unwrap()),
rows_per_image: Some(std::num::NonZeroU32::new(extent[1]).unwrap()),
},
},
wgpu::Extent3d {
width: extent[0],
height: extent[1],
depth_or_array_layers: extent[2],
},
);
}
}

pub(crate) async fn init(window: &winit::window::Window, sample_count: u32) -> Context {
Expand Down
Loading