Skip to content

Commit

Permalink
add sources
Browse files Browse the repository at this point in the history
  • Loading branch information
eugenebokhan committed Jun 28, 2024
1 parent 8f43728 commit 8f1df5f
Show file tree
Hide file tree
Showing 9 changed files with 360 additions and 2 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ Cargo.lock

# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb
*.DS_Store
10 changes: 10 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[package]
name = "wgpu-tools"
version = "0.1.0"
edition = "2021"

[dependencies]
wgpu = "0.20.1"
thiserror = "1.0.57"
nalgebra = "0.33.0"
image = "0.25.1"
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2024 computer-graphics-tools
Copyright (c) 2024 Eugene Bokhan

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
100 changes: 99 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,99 @@
# wgpu-tools
# WGPU Tools

<p align="left">
<img src="media/WGPUTools.png", width="160">
</p>

A Rust library providing utility functions and abstractions for working with [WGPU](https://wgpu.rs), similar to [MetalTools](https://github.com/computer-graphics-tools/metal-tools).

## Features

- Context creation and management
- Texture handling utilities
- GPU operation scheduling
- Error handling

## Installation

Add this to your `Cargo.toml`:

```toml
[dependencies]
wgpu-tools = "0.0.1"
```

## Usage

Here are some examples of how to use wgpu-tools:

### Creating a standalone context

```rust
use wgpu_tools::Context;

async fn create_context() -> Result<Context, wgpu_tools::Error> {
Context::default().await
}
```

### Creating a context with a surface

```rust
use wgpu_tools::Context;

async fn create_context_with_surface(window: &impl raw_window_handle::HasRawWindowHandle) -> Result<(Context, wgpu::Surface), wgpu_tools::Error> {
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor::default());
let surface = unsafe { instance.create_surface(window) }?;
let context = Context::default_with_surface(instance, Some(&surface)).await?;
Ok((context, surface))
}
```

### Scheduling GPU operations

```rust
use wgpu_tools::Context;

fn schedule_gpu_operations(context: &Context) {
context.schedule(|encoder| {
// Perform GPU operations using the encoder
// For example:
let _render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("Render Pass"),
color_attachments: &[],
depth_stencil_attachment: None,
});
// Add more GPU commands as needed
});
}
```

### Loading a texture from an image file

```rust
use wgpu_tools::Context;
use image::io::Reader as ImageReader;

fn load_texture_from_file(context: &Context, path: &std::path::Path) -> Result<wgpu_tools::Texture, wgpu_tools::Error> {
let img = ImageReader::open(path)?.decode()?;
context.texture_from_image(&img, &wgpu::TextureFormat::Rgba8UnormSrgb, Some("Loaded Texture"))
}
```

## Main Components

### Context

The `Context` struct provides a convenient wrapper around wgpu's instance, adapter, device, and queue. It offers methods for creating contexts, scheduling GPU operations, and creating textures.

### Texture

The `Texture` struct encapsulates wgpu texture, view, and sampler objects.

### Error Handling

Custom error types are provided to handle various failure scenarios in wgpu operations.

## License

WGPUTools is licensed under [MIT license](LICENSE).
Binary file added media/WGPUTools.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
221 changes: 221 additions & 0 deletions src/context.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
use super::error::Error;
use super::texture::Texture;
use image::{DynamicImage, GenericImageView};
use nalgebra::SVector;
use wgpu;

pub struct ContextDescriptor<'a, 'b> {
request_adapter_options: wgpu::RequestAdapterOptions<'a, 'b>,
device_descriptor: wgpu::DeviceDescriptor<'a>,
trace_path: Option<&'a std::path::Path>,
}

pub struct Context {
pub instance: wgpu::Instance,
pub adapter: wgpu::Adapter,
pub device: wgpu::Device,
pub queue: wgpu::Queue,
}

impl Context {
pub async fn new<'a, 'b>(
instance: wgpu::Instance,
descriptor: &'a ContextDescriptor<'a, 'b>,
) -> Result<Self, Error> {
let adapter = instance
.request_adapter(&descriptor.request_adapter_options)
.await
.ok_or(Error::RequestingAdapterFailed)?;
let (device, queue) = adapter
.request_device(&descriptor.device_descriptor, descriptor.trace_path)
.await?;

Ok(Self {
instance,
adapter,
device,
queue,
})
}

pub async fn default_with_surface<'a>(
instance: wgpu::Instance,
compatible_surface: Option<&wgpu::Surface<'a>>,
) -> Result<Self, Error> {
let context = Self::new(
instance,
&ContextDescriptor {
request_adapter_options: wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::default(),
force_fallback_adapter: false,
compatible_surface,
},
device_descriptor: wgpu::DeviceDescriptor::default(),
trace_path: None,
},
)
.await?;

Ok(context)
}

pub async fn default() -> Result<Self, Error> {
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor::default());
Self::default_with_surface(instance, None).await
}

pub fn schedule<O>(&self, operations: O)
where
O: Fn(&mut wgpu::CommandEncoder) -> (),
{
let mut command_encoder =
self.device
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("Command Encoder"),
});

operations(&mut command_encoder);

let command_buffer = command_encoder.finish();
self.queue.submit(std::iter::once(command_buffer));
}

pub fn depth_texture(&self, width: &u32, height: &u32, label: &str) -> Texture {
let size = wgpu::Extent3d {
width: *width,
height: *height,
depth_or_array_layers: 1,
};
let texture = self.device.create_texture(&wgpu::TextureDescriptor {
label: Some(label),
size,
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: Texture::DEPTH_FORMAT,
view_formats: &[wgpu::TextureFormat::Depth32Float],
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
});
let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
let sampler = self.device.create_sampler(&wgpu::SamplerDescriptor {
address_mode_u: wgpu::AddressMode::ClampToEdge,
address_mode_v: wgpu::AddressMode::ClampToEdge,
address_mode_w: wgpu::AddressMode::ClampToEdge,
mag_filter: wgpu::FilterMode::Linear,
min_filter: wgpu::FilterMode::Linear,
mipmap_filter: wgpu::FilterMode::Nearest,
compare: Some(wgpu::CompareFunction::LessEqual),
lod_min_clamp: 0.0,
lod_max_clamp: 100.0,
..Default::default()
});

Texture {
texture,
view,
sampler,
}
}

pub fn texture_with_data(
&self,
data: &[u8],
width: &u32,
height: &u32,
texture_format: &wgpu::TextureFormat,
label: Option<&str>,
) -> Result<Texture, Error> {
let size = wgpu::Extent3d {
width: *width,
height: *height,
depth_or_array_layers: 1,
};
let bytes_per_row = 4 * size.width;
let bytes_per_image = bytes_per_row * size.width;

if bytes_per_row == 0 || data.len() < bytes_per_image as usize {
return Err(Error::TextureCreationFailed);
}

let texture = self.device.create_texture(&wgpu::TextureDescriptor {
label,
size,
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: *texture_format,
view_formats: &[*texture_format],
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
});

self.queue.write_texture(
wgpu::ImageCopyTexture {
aspect: wgpu::TextureAspect::All,
texture: &texture,
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
},
data,
wgpu::ImageDataLayout {
offset: 0,
bytes_per_row: Some(bytes_per_row),
rows_per_image: Some(size.height),
},
size,
);

let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
let sampler = self.device.create_sampler(&wgpu::SamplerDescriptor {
address_mode_u: wgpu::AddressMode::ClampToEdge,
address_mode_v: wgpu::AddressMode::ClampToEdge,
address_mode_w: wgpu::AddressMode::ClampToEdge,
mag_filter: wgpu::FilterMode::Linear,
min_filter: wgpu::FilterMode::Linear,
mipmap_filter: wgpu::FilterMode::Linear,
..Default::default()
});

Ok(Texture {
texture,
view,
sampler,
})
}

pub fn texture_from_image(
&self,
image: &DynamicImage,
texture_format: &wgpu::TextureFormat,
label: Option<&str>,
) -> Result<Texture, Error> {
let (width, height) = image.dimensions();
let data = image.to_rgba8();
self.texture_with_data(&data, &width, &height, texture_format, label)
}

pub fn texture_from_image_data(
&self,
data: &[u8],
texture_format: &wgpu::TextureFormat,
label: Option<&str>,
) -> Result<Texture, Error> {
let image = image::load_from_memory(data)?;
self.texture_from_image(&image, texture_format, label)
}

pub fn texture_from_color(
&self,
color: &SVector<f32, 4>,
texture_format: &wgpu::TextureFormat,
label: Option<&str>,
) -> Result<Texture, Error> {
let data: [u8; 4] = [
(color.x * 255_f32) as u8,
(color.y * 255_f32) as u8,
(color.z * 255_f32) as u8,
(color.w * 255_f32) as u8,
];
let (width, height) = (1, 1);
self.texture_with_data(&data, &width, &height, texture_format, label)
}
}
11 changes: 11 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("requesting adapter failed")]
RequestingAdapterFailed,
#[error(transparent)]
RequestingDeviceFailed(#[from] wgpu::RequestDeviceError),
#[error(transparent)]
ImageError(#[from] image::ImageError),
#[error("texture creation failed")]
TextureCreationFailed,
}
7 changes: 7 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
mod context;
mod error;
mod texture;

pub use context::Context;
pub use error::Error;
pub use texture::Texture;
10 changes: 10 additions & 0 deletions src/texture.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#[derive(Debug)]
pub struct Texture {
pub texture: wgpu::Texture,
pub view: wgpu::TextureView,
pub sampler: wgpu::Sampler,
}

impl Texture {
pub const DEPTH_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Depth32Float;
}

0 comments on commit 8f1df5f

Please sign in to comment.