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

Simplified Kernel creation and reuse #4

Merged
merged 2 commits into from
Nov 17, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
13 changes: 5 additions & 8 deletions examples/image-compatibility/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@ use gpgpu::primitives::pixels::Rgba8Uint;
// This example simply mirrors an image using the image crate compatibility feature.
fn main() {
let fw = gpgpu::Framework::default();
let shader_mod =
gpgpu::utils::shader::from_wgsl_file(&fw, "examples/image-compatibility/mirror.wgsl")
.unwrap();
let shader =
gpgpu::Shader::from_wgsl_file(&fw, "examples/image-compatibility/mirror.wgsl").unwrap();

let dynamic_img = image::open("examples/image-compatibility/monke.jpg").unwrap(); // RGB8 image ...
let rgba = dynamic_img.into_rgba8(); // ... converted to RGBA8
Expand All @@ -16,14 +15,12 @@ fn main() {
let input_img = gpgpu::GpuConstImage::from_image_buffer(&fw, &rgba); // Input
let output_img = gpgpu::GpuImage::<Rgba8Uint>::new(&fw, width, height); // Output

let binds = gpgpu::DescriptorSet::default()
let desc = gpgpu::DescriptorSet::default()
.bind_const_image(&input_img)
.bind_image(&output_img);
let program = gpgpu::Program::new(&shader, "main").add_descriptor_set(desc);

fw.create_kernel_builder(&shader_mod, "main")
.add_descriptor_set(binds)
.build()
.enqueue(width / 32, height / 32, 1); // Since the kernel workgroup size is (32,32,1) dims are divided
gpgpu::Kernel::new(&fw, program).enqueue(width / 32, height / 32, 1); // Since the kernel workgroup size is (32,32,1) dims are divided

let output = output_img.read_to_image_buffer().unwrap();
output
Expand Down
12 changes: 4 additions & 8 deletions examples/mirror-image/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ use gpgpu::primitives::pixels::Rgba8Uint;
// This example simply mirrors an image.
fn main() {
let fw = gpgpu::Framework::default();
let shader_mod =
gpgpu::utils::shader::from_wgsl_file(&fw, "examples/mirror-image/mirror.wgsl").unwrap();
let shader = gpgpu::Shader::from_wgsl_file(&fw, "examples/mirror-image/mirror.wgsl").unwrap();

let dynamic_img = image::open("examples/mirror-image/monke.jpg").unwrap(); // RGB8 image ...
let rgba = dynamic_img.into_rgba8(); // ... converted to RGBA8
Expand All @@ -18,17 +17,14 @@ fn main() {
// Write input image into the GPU
input_img.write(&rgba);

let binds = gpgpu::DescriptorSet::default()
let desc = gpgpu::DescriptorSet::default()
.bind_const_image(&input_img)
.bind_image(&output_img);
let program = gpgpu::Program::new(&shader, "main").add_descriptor_set(desc);

fw.create_kernel_builder(&shader_mod, "main")
.add_descriptor_set(binds)
.build()
.enqueue(width / 32, height / 32, 1); // Since the kernel workgroup size is (32,32,1) dims are divided
gpgpu::Kernel::new(&fw, program).enqueue(width / 32, height / 32, 1); // Since the kernel workgroup size is (32, 32, 1) dims are divided

let output_bytes = output_img.read().unwrap();

image::save_buffer(
"examples/mirror-image/mirror-monke.png",
&output_bytes,
Expand Down
11 changes: 4 additions & 7 deletions examples/parallel-compute/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ lazy_static::lazy_static! {

fn main() {
let shader = Arc::new(
gpgpu::utils::shader::from_wgsl_file(&FW, "examples/parallel-compute/shader.wgsl").unwrap(),
gpgpu::Shader::from_wgsl_file(&FW, "examples/parallel-compute/shader.wgsl").unwrap(),
);

let threading = 4; // Threading level
Expand All @@ -29,17 +29,14 @@ fn main() {
let local_input_buffer = gpgpu::GpuBuffer::from_slice(&FW, &local_cpu_data);
let local_output_buffer = gpgpu::GpuBuffer::<u32>::new(&FW, size as usize);

let binds = gpgpu::DescriptorSet::default()
let desc = gpgpu::DescriptorSet::default()
.bind_buffer(&local_shader_input_buffer, gpgpu::GpuBufferUsage::ReadOnly)
.bind_buffer(&local_input_buffer, gpgpu::GpuBufferUsage::ReadOnly)
.bind_buffer(&local_output_buffer, gpgpu::GpuBufferUsage::ReadWrite);
let program = gpgpu::Program::new(&local_shader, "main").add_descriptor_set(desc);

let kernel = &FW
.create_kernel_builder(&local_shader, "main")
.add_descriptor_set(binds)
.build();
gpgpu::Kernel::new(&FW, program).enqueue(size / 32, 1, 1);

kernel.enqueue(size / 32, 1, 1);
local_output_buffer.read().unwrap()
});

Expand Down
15 changes: 7 additions & 8 deletions examples/simple-compute/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@
fn main() {
let fw = gpgpu::Framework::default(); // Framework initialization.

let shader_mod =
gpgpu::utils::shader::from_wgsl_file(&fw, "examples/simple-compute/mult.wgsl").unwrap(); // Shader loading.
let shader = gpgpu::Shader::from_wgsl_file(&fw, "examples/simple-compute/mult.wgsl").unwrap(); // Shader loading.

let size = 10000; // Size of the vectors

Expand All @@ -22,12 +21,12 @@ fn main() {
.bind_buffer(&gpu_vec_b, gpgpu::GpuBufferUsage::ReadOnly) // Binding 1
.bind_buffer(&gpu_vec_c, gpgpu::GpuBufferUsage::ReadWrite); // Binding 2. read_write in shader. No write-only yet.

// Creation of a kernel. If a function on a GPU with an already set of inputs and
// outputs following a layout (the variable `bindings` above).
let kernel = fw
.create_kernel_builder(&shader_mod, "main")
.add_descriptor_set(bindings)
.build();
// Match a shader entry point with its descriptor (the bindings).
// A program represents a function on a GPU with an already set of inputs and outputs following a layout (the variable `bindings` above).
let program = gpgpu::Program::new(&shader, "main").add_descriptor_set(bindings);

// Creation of a kernel. This represents the `program` function and its `enqueuing` parameters,
let kernel = gpgpu::Kernel::new(&fw, program);

// Execution of the kernel. It needs 3 dimmensions, x y and z.
// Since we are using single-dim vectors, only x is required.
Expand Down
10 changes: 4 additions & 6 deletions examples/webcam/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,17 +46,15 @@ fn main() {

let gpu_output = GpuImage::<Rgba8UintNorm>::new(&fw, WIDTH as u32, HEIGHT as u32); // Shader output

let shader = gpgpu::Shader::from_wgsl_file(&fw, "examples/webcam/shader.wgsl").unwrap();

let desc = DescriptorSet::default()
.bind_const_image(&gpu_input)
.bind_image(&gpu_output)
.bind_uniform_buffer(&buf_time);
let program = gpgpu::Program::new(&shader, "main").add_descriptor_set(desc);

let shader = gpgpu::utils::shader::from_wgsl_file(&fw, "examples/webcam/shader.wgsl").unwrap();

let kernel = fw
.create_kernel_builder(&shader, "main")
.add_descriptor_set(desc)
.build();
let kernel = gpgpu::Kernel::new(&fw, program);

let time = std::time::Instant::now();

Expand Down
24 changes: 1 addition & 23 deletions src/framework.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{Framework, KernelBuilder};
use crate::Framework;

impl Default for Framework {
fn default() -> Self {
Expand Down Expand Up @@ -53,28 +53,6 @@ impl Framework {
}
}

/// Creates a [`KernelBuilder`] from a `wgpu` [`wgpu::ShaderModule`] and
/// its `entry_point`.
///
/// A `ShaderModule` can be created using the `utils::shader` methods,
/// [`utils::shader::from_spirv_file`](crate::utils::shader::from_spirv_file) and
/// [`utils::shader::from_spirv_bytes`](crate::utils::shader::from_spirv_bytes); or
/// using `wgpu`.
pub fn create_kernel_builder<'sha>(
&self,
shader: &'sha wgpu::ShaderModule,
entry_point: impl Into<String>,
) -> KernelBuilder<'_, '_, 'sha> {
KernelBuilder {
fw: self,
layouts: Vec::new(),
descriptors: Vec::new(),
sets: Vec::new(),
shader,
entry_point: entry_point.into(),
}
}

pub(crate) fn create_download_staging_buffer(&self, size: usize) -> wgpu::Buffer {
self.device.create_buffer(&wgpu::BufferDescriptor {
label: None,
Expand Down
128 changes: 91 additions & 37 deletions src/kernel.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use std::{borrow::Cow, path::Path};

use crate::{
primitives::PixelInfo, DescriptorSet, GpuBuffer, GpuBufferUsage, GpuConstImage, GpuImage,
GpuUniformBuffer, Kernel, KernelBuilder,
primitives::PixelInfo, DescriptorSet, Framework, GpuBuffer, GpuBufferUsage, GpuConstImage,
GpuImage, GpuResult, GpuUniformBuffer, Kernel, Program, Shader,
};

impl<'res> DescriptorSet<'res> {
Expand Down Expand Up @@ -185,66 +187,118 @@ impl<'res> DescriptorSet<'res> {
}
}

impl<'fw, 'res, 'sha> KernelBuilder<'fw, 'res, 'sha> {
/// Adds a [`DescriptorSet`] into the [`KernelBuilder`] internal layout.
impl Shader {
/// Creates a [`wgpu::ShaderModule`] instance from a SPIR-V file.
Copy link
Owner Author

Choose a reason for hiding this comment

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

Fix doc

pub fn from_spirv_file(fw: &Framework, path: impl AsRef<Path>) -> GpuResult<Self> {
let bytes = std::fs::read(&path)?;
let shader_name = path.as_ref().to_str();

Ok(Self::from_spirv_bytes(fw, &bytes, shader_name))
}

/// Initialises a [`Shader`] from SPIR-V bytes with an optional `name`.
pub fn from_spirv_bytes(fw: &Framework, bytes: &[u8], name: Option<&str>) -> Self {
let source = wgpu::util::make_spirv(bytes);

let shader = fw
.device
.create_shader_module(&wgpu::ShaderModuleDescriptor {
label: name,
source,
});

Self(shader)
}

/// Initialises a [`Shader`] from a `WGSL` file.
pub fn from_wgsl_file(fw: &Framework, path: impl AsRef<Path>) -> GpuResult<Self> {
let source_string = std::fs::read_to_string(&path)?;
let shader_name = path.as_ref().to_str();

Ok(Self(fw.device.create_shader_module(
&wgpu::ShaderModuleDescriptor {
label: shader_name,
source: wgpu::ShaderSource::Wgsl(Cow::Owned(source_string)),
},
)))
}
}

impl<'sha, 'res> Program<'sha, 'res> {
/// Creates a new [`Program`] using a `shader` and an `entry_point`.
pub fn new(shader: &'sha Shader, entry_point: impl Into<String>) -> Self {
Self {
shader,
entry_point: entry_point.into(),
descriptors: Vec::new(),
}
}

/// Adds a [`DescriptorSet`] for this [`Program`].
Copy link
Owner Author

Choose a reason for hiding this comment

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

Typo

pub fn add_descriptor_set(mut self, desc: DescriptorSet<'res>) -> Self {
let set_layout =
self.fw
self.descriptors.push(desc);
self
}
}

impl<'fw> Kernel<'fw> {
/// Creates a [`KernelBuilder`] from a [`Program`].
Copy link
Owner Author

Choose a reason for hiding this comment

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

Change this entire bit of documentation

///
/// A `ShaderModule` can be created using the `utils::shader` methods,
/// [`utils::shader::from_spirv_file`](crate::utils::shader::from_spirv_file) and
/// [`utils::shader::from_spirv_bytes`](crate::utils::shader::from_spirv_bytes); or
/// using `wgpu`.
pub fn new<'sha, 'res>(fw: &'fw Framework, program: Program<'sha, 'res>) -> Self {
let mut layouts = Vec::new();
let mut sets = Vec::new();

// Unwraping of descriptors from program
for desc in &program.descriptors {
let set_layout = fw
.device
.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: None,
entries: &desc.set_layout,
});

let set = self
.fw
.device
.create_bind_group(&wgpu::BindGroupDescriptor {
let set = fw.device.create_bind_group(&wgpu::BindGroupDescriptor {
label: None,
layout: &set_layout,
entries: &desc.binds,
});

self.layouts.push(set_layout);
self.descriptors.push(desc);
self.sets.push(set);

self
}
layouts.push(set_layout);
sets.push(set);
}

/// Builds a [`Kernel`] from the layout stored in [`KernelBuilder`].
pub fn build(self) -> Kernel<'fw> {
let sets = self.layouts.iter().collect::<Vec<_>>();
// Compute pipeline bindings
let group_layouts = layouts.iter().collect::<Vec<_>>();

let pipeline_layout =
self.fw
.device
.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: None,
bind_group_layouts: &sets,
push_constant_ranges: &[],
});
let pipeline_layout = fw
.device
.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: None,
bind_group_layouts: &group_layouts,
push_constant_ranges: &[],
});

let pipeline = self
.fw
let pipeline = fw
.device
.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
label: None,
module: self.shader,
entry_point: &self.entry_point,
module: &program.shader.0,
entry_point: &program.entry_point,
layout: Some(&pipeline_layout),
});

Kernel {
fw: self.fw,
Self {
fw,
pipeline,
sets: self.sets,
entry_point: self.entry_point,
sets,
entry_point: program.entry_point,
}
}
}

impl<'fw> Kernel<'fw> {
/// Enqueues the execution of this [`Kernel`] to the GPU.
///
/// [`Kernel`] will dispatch `x`, `y` and `z` workgroups per dimension.
Expand Down
27 changes: 13 additions & 14 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,6 @@ pub mod features;
pub mod framework;
pub mod kernel;
pub mod primitives;
pub mod utils;

/// Lazy error handling :)
pub type GpuResult<T> = Result<T, Box<dyn std::error::Error>>;
Expand Down Expand Up @@ -141,27 +140,27 @@ pub struct GpuImage<'fw, P>(GenericImage<'fw, P>);
/// under the [`DescriptorSet::bind_const_image`](crate::DescriptorSet::bind_const_image) documentation.
pub struct GpuConstImage<'fw, P>(GenericImage<'fw, P>);

/// Represents a shader.
///
/// It's just a wrapper over [`wgpu::ShaderModule`].
pub struct Shader(wgpu::ShaderModule);

/// Represents an entry point with its bindings on a [`Shader`].
pub struct Program<'sha, 'res> {
shader: &'sha Shader,
entry_point: String,
descriptors: Vec<DescriptorSet<'res>>,
}

/// Contains a binding group of resources.
#[derive(Default)]
#[derive(Default, Clone)]
pub struct DescriptorSet<'res> {
set_layout: Vec<wgpu::BindGroupLayoutEntry>,
binds: Vec<wgpu::BindGroupEntry<'res>>,
}

/// Creates a [`Kernel`] instance with the bindings
/// used during the configuration of this structure.
pub struct KernelBuilder<'fw, 'res, 'sha> {
fw: &'fw Framework,
layouts: Vec<wgpu::BindGroupLayout>,
descriptors: Vec<DescriptorSet<'res>>,
sets: Vec<wgpu::BindGroup>,
shader: &'sha wgpu::ShaderModule,
entry_point: String,
}

/// Used to enqueue the execution of a shader with the bidings provided.
///
/// Can only be created from [`KernelBuilder`].
/// Equivalent to OpenCL's Kernel.
pub struct Kernel<'fw> {
fw: &'fw Framework,
Expand Down
Loading