From ac553298df54d6b5a6c1a109296b369ad0354b60 Mon Sep 17 00:00:00 2001 From: nanoqsh Date: Thu, 4 Jan 2024 16:20:40 +0600 Subject: [PATCH] Bindings --- dunge/src/bind.rs | 224 ++++++++++++++++ dunge/src/context.rs | 11 +- dunge/src/group.rs | 239 +++--------------- dunge/src/layer.rs | 2 +- dunge/src/lib.rs | 1 + dunge/src/shader.rs | 2 +- dunge/src/vertex.rs | 21 +- dunge/tests/gradient.png | Bin 0 -> 5568 bytes dunge/tests/triangle_group.png | Bin 0 -> 49389 bytes dunge/tests/triangle_group.rs | 148 +++++++++++ .../{triangle.png => triangle_index.png} | Bin .../tests/{triangle.rs => triangle_index.rs} | 2 +- ...triangle_color.png => triangle_vertex.png} | Bin .../{triangle_color.rs => triangle_vertex.rs} | 8 +- dunge_shader/src/group.rs | 7 +- dunge_shader/src/vector.rs | 2 +- dunge_shader/src/vertex.rs | 25 +- helpers/src/image.rs | 6 +- 18 files changed, 460 insertions(+), 238 deletions(-) create mode 100644 dunge/src/bind.rs create mode 100644 dunge/tests/gradient.png create mode 100644 dunge/tests/triangle_group.png create mode 100644 dunge/tests/triangle_group.rs rename dunge/tests/{triangle.png => triangle_index.png} (100%) rename dunge/tests/{triangle.rs => triangle_index.rs} (96%) rename dunge/tests/{triangle_color.png => triangle_vertex.png} (100%) rename dunge/tests/{triangle_color.rs => triangle_vertex.rs} (90%) diff --git a/dunge/src/bind.rs b/dunge/src/bind.rs new file mode 100644 index 0000000..2600e5a --- /dev/null +++ b/dunge/src/bind.rs @@ -0,0 +1,224 @@ +use { + crate::{ + group::{BoundTexture, Group, Visitor}, + shader::Shader, + state::State, + texture::Sampler, + }, + std::{any::TypeId, fmt, marker::PhantomData, sync::Arc}, + wgpu::{ + BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayout, BindingResource, Device, + }, +}; + +#[derive(Default)] +pub struct VisitGroup<'g>(Vec>); + +impl<'g> VisitGroup<'g> { + fn visit_texture(&mut self, texture: BoundTexture<'g>) { + self.push_resource(BindingResource::TextureView(texture.get().view())); + } + + fn visit_sampler(&mut self, sampler: &'g Sampler) { + self.push_resource(BindingResource::Sampler(sampler.inner())); + } + + fn push_resource(&mut self, resource: BindingResource<'g>) { + let binding = self.0.len() as u32; + self.0.push(BindGroupEntry { binding, resource }); + } +} + +impl<'g> Visitor for VisitGroup<'g> { + type Texture = BoundTexture<'g>; + type Sampler = &'g Sampler; + + fn visit_texture(&mut self, texture: Self::Texture) { + self.visit_texture(texture); + } + + fn visit_sampler(&mut self, sampler: Self::Sampler) { + self.visit_sampler(sampler); + } +} + +fn visit<'g, G>(group: &'g G) -> Vec> +where + G: Group>, +{ + let mut visit = VisitGroup::default(); + group.group(&mut visit); + visit.0 +} + +pub struct GroupHandler { + shader_id: usize, + id: usize, + layout: Arc, + ty: PhantomData, +} + +pub struct ForeignShader; + +impl fmt::Display for ForeignShader { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "the handler doesn't belong to this shader") + } +} + +pub trait Binding { + fn binding(&self) -> Bind; +} + +pub struct Bind<'a> { + pub(crate) shader_id: usize, + pub(crate) groups: &'a [BindGroup], +} + +#[derive(Clone)] +pub struct GroupBinding { + shader_id: usize, + groups: Arc<[BindGroup]>, +} + +impl GroupBinding { + fn new(shader_id: usize, groups: Vec) -> Self { + Self { + shader_id, + groups: Arc::from(groups), + } + } +} + +impl Binding for GroupBinding { + fn binding(&self) -> Bind { + Bind { + shader_id: self.shader_id, + groups: &self.groups, + } + } +} + +pub type Update = Result<(), ForeignShader>; + +pub(crate) fn update<'g, G>( + state: &State, + uni: &mut UniqueGroupBinding, + handler: GroupHandler, + group: &'g G, +) -> Update +where + G: Group>, +{ + if handler.shader_id != uni.0.shader_id { + return Err(ForeignShader); + } + + let entries = visit(group); + let desc = BindGroupDescriptor { + label: None, + layout: &handler.layout, + entries: &entries, + }; + + let new = state.device().create_bind_group(&desc); + let groups = uni.groups(); + groups[handler.id] = new; + Ok(()) +} + +pub struct UniqueGroupBinding(GroupBinding); + +impl UniqueGroupBinding { + pub fn into_inner(self) -> GroupBinding { + self.0 + } + + fn groups(&mut self) -> &mut [BindGroup] { + Arc::get_mut(&mut self.0.groups).expect("uniqueness is guaranteed by the type") + } +} + +impl Binding for UniqueGroupBinding { + fn binding(&self) -> Bind { + self.0.binding() + } +} + +pub(crate) struct TypedGroup { + tyid: TypeId, + bind: Arc, +} + +impl TypedGroup { + pub fn new(tyid: TypeId, bind: BindGroupLayout) -> Self { + Self { + tyid, + bind: Arc::new(bind), + } + } + + pub fn bind(&self) -> &BindGroupLayout { + &self.bind + } +} + +pub struct Binder<'a> { + shader_id: usize, + device: &'a Device, + layout: &'a [TypedGroup], + groups: Vec, +} + +impl<'a> Binder<'a> { + pub(crate) fn new(state: &'a State, shader: &'a Shader) -> Self { + let layout = shader.groups(); + Self { + shader_id: shader.id(), + device: state.device(), + layout, + groups: Vec::with_capacity(layout.len()), + } + } + + pub fn bind<'g, G>(&mut self, group: &'g G) -> GroupHandler + where + G: Group>, + { + let id = self.groups.len(); + let Some(layout) = self.layout.get(id) else { + panic!("too many bindings"); + }; + + if layout.tyid != TypeId::of::() { + panic!("group type doesn't match"); + } + + let layout = Arc::clone(&layout.bind); + let entries = visit(group); + let desc = BindGroupDescriptor { + label: None, + layout: &layout, + entries: &entries, + }; + + let bind = self.device.create_bind_group(&desc); + self.groups.push(bind); + + GroupHandler { + shader_id: self.shader_id, + id, + layout, + ty: PhantomData, + } + } + + pub fn into_binding(self) -> UniqueGroupBinding { + if self.groups.len() != self.layout.len() { + panic!("some group bindings is not set"); + } + + let binding = GroupBinding::new(self.shader_id, self.groups); + UniqueGroupBinding(binding) + } +} diff --git a/dunge/src/context.rs b/dunge/src/context.rs index 4549ba1..d9aa670 100644 --- a/dunge/src/context.rs +++ b/dunge/src/context.rs @@ -1,7 +1,8 @@ use { crate::{ + bind::{self, Binder, GroupHandler, UniqueGroupBinding, Update, VisitGroup}, draw::Draw, - group::{self, Binder, Group, GroupHandler, UniqueGroupBinding, Update}, + group::Group, layer::Layer, mesh::{self, Mesh}, shader::Shader, @@ -87,16 +88,16 @@ impl Context { self.0.draw(render, view, draw) } - pub fn update_group( + pub fn update_group<'g, G>( &self, uni: &mut UniqueGroupBinding, handler: GroupHandler, - group: &G, + group: &'g G, ) -> Update where - G: Group, + G: Group>, { - group::update(&self.0, uni, handler, group) + bind::update(&self.0, uni, handler, group) } } diff --git a/dunge/src/group.rs b/dunge/src/group.rs index 6b5bdb7..bfdde2e 100644 --- a/dunge/src/group.rs +++ b/dunge/src/group.rs @@ -1,237 +1,58 @@ -use { - crate::{ - shader::Shader, - state::State, - texture::{self, Sampler, Texture}, - }, - std::{any::TypeId, fmt, marker::PhantomData, sync::Arc}, - wgpu::{ - BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayout, BindingResource, Device, - }, +use crate::{ + sl::{GlobalOut, ReadGlobal, Ret}, + texture::{BindTexture, Sampler, Texture}, + types::{self, GroupMemberType}, }; pub use dunge_shader::group::*; #[derive(Clone, Copy)] -pub struct BindTexture<'a>(&'a Texture); +pub struct BoundTexture<'a>(&'a Texture); -impl<'a> BindTexture<'a> { +impl<'a> BoundTexture<'a> { pub fn new(texture: &'a T) -> Self where - T: texture::BindTexture, + T: BindTexture, { Self(texture.bind_texture()) } -} - -fn visit(group: &G) -> Vec -where - G: Group, -{ - let mut visit = VisitGroup::default(); - group.group(&mut visit); - visit.0 -} - -#[derive(Default)] -struct VisitGroup<'a>(Vec>); - -impl<'a> VisitGroup<'a> { - fn visit_texture(&mut self, texture: BindTexture<'a>) { - self.push_resource(BindingResource::TextureView(texture.0.view())); - } - - fn visit_sampler(&mut self, sampler: &'a Sampler) { - self.push_resource(BindingResource::Sampler(sampler.inner())); - } - - fn push_resource(&mut self, resource: BindingResource<'a>) { - let binding = self.0.len() as u32; - self.0.push(BindGroupEntry { binding, resource }); - } -} - -impl<'a> Visitor for VisitGroup<'a> { - type Texture = BindTexture<'a>; - type Sampler = &'a Sampler; - - fn visit_texture(&mut self, texture: Self::Texture) { - self.visit_texture(texture); - } - - fn visit_sampler(&mut self, sampler: Self::Sampler) { - self.visit_sampler(sampler); - } -} - -pub struct GroupHandler { - shader_id: usize, - id: usize, - layout: Arc, - ty: PhantomData, -} - -pub struct ForeignShader; - -impl fmt::Display for ForeignShader { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "the handler doesn't belong to this shader") - } -} - -pub trait Binding { - fn binding(&self) -> Bind; -} - -pub struct Bind<'a> { - pub(crate) shader_id: usize, - pub(crate) groups: &'a [BindGroup], -} - -#[derive(Clone)] -pub struct GroupBinding { - shader_id: usize, - groups: Arc<[BindGroup]>, -} -impl GroupBinding { - fn new(shader_id: usize, groups: Vec) -> Self { - Self { - shader_id, - groups: Arc::from(groups), - } - } -} - -impl Binding for GroupBinding { - fn binding(&self) -> Bind { - Bind { - shader_id: self.shader_id, - groups: &self.groups, - } - } -} - -pub type Update = Result<(), ForeignShader>; - -pub(crate) fn update( - state: &State, - uni: &mut UniqueGroupBinding, - handler: GroupHandler, - group: &G, -) -> Update -where - G: Group, -{ - if handler.shader_id != uni.0.shader_id { - return Err(ForeignShader); - } - - let entries = visit(group); - let desc = BindGroupDescriptor { - label: None, - layout: &handler.layout, - entries: &entries, - }; - - let new = state.device().create_bind_group(&desc); - let groups = uni.groups(); - groups[handler.id] = new; - Ok(()) -} - -pub struct UniqueGroupBinding(GroupBinding); - -impl UniqueGroupBinding { - pub fn into_inner(self) -> GroupBinding { + pub(crate) fn get(&self) -> &'a Texture { self.0 } - - fn groups(&mut self) -> &mut [BindGroup] { - Arc::get_mut(&mut self.0.groups).expect("uniqueness is guaranteed by the type") - } } -impl Binding for UniqueGroupBinding { - fn binding(&self) -> Bind { - self.0.binding() - } +/// Describes a group member type projection. +/// +/// The trait is sealed because the derive macro relies on no new types being used. +pub trait MemberProjection: private::Sealed { + const TYPE: GroupMemberType; + type Field; + fn member_projection(id: u32, binding: u32, out: GlobalOut) -> Self::Field; } -pub(crate) struct TypedGroup { - tyid: TypeId, - bind: Arc, -} +impl private::Sealed for BoundTexture<'_> {} -impl TypedGroup { - pub fn new(tyid: TypeId, bind: BindGroupLayout) -> Self { - Self { - tyid, - bind: Arc::new(bind), - } - } +impl MemberProjection for BoundTexture<'_> { + const TYPE: GroupMemberType = GroupMemberType::Tx2df; + type Field = Ret>; - pub fn bind(&self) -> &BindGroupLayout { - &self.bind + fn member_projection(id: u32, binding: u32, out: GlobalOut) -> Self::Field { + ReadGlobal::new(id, binding, out) } } -pub struct Binder<'a> { - shader_id: usize, - device: &'a Device, - layout: &'a [TypedGroup], - groups: Vec, -} - -impl<'a> Binder<'a> { - pub(crate) fn new(state: &'a State, shader: &'a Shader) -> Self { - let layout = shader.groups(); - Self { - shader_id: shader.id(), - device: state.device(), - layout, - groups: Vec::with_capacity(layout.len()), - } - } - - pub fn bind(&mut self, group: &G) -> GroupHandler - where - G: Group, - { - let id = self.groups.len(); - let Some(layout) = self.layout.get(id) else { - panic!("too many bindings"); - }; - - if layout.tyid != TypeId::of::() { - panic!("group type doesn't match"); - } - - let layout = Arc::clone(&layout.bind); - let entries = visit(group); - let desc = BindGroupDescriptor { - label: None, - layout: &layout, - entries: &entries, - }; +impl private::Sealed for &Sampler {} - let bind = self.device.create_bind_group(&desc); - self.groups.push(bind); +impl MemberProjection for &Sampler { + const TYPE: GroupMemberType = GroupMemberType::Sampl; + type Field = Ret; - GroupHandler { - shader_id: self.shader_id, - id, - layout, - ty: PhantomData, - } + fn member_projection(id: u32, binding: u32, out: GlobalOut) -> Self::Field { + ReadGlobal::new(id, binding, out) } +} - pub fn into_binding(self) -> UniqueGroupBinding { - if self.groups.len() != self.layout.len() { - panic!("some group bindings is not set"); - } - - let binding = GroupBinding::new(self.shader_id, self.groups); - UniqueGroupBinding(binding) - } +mod private { + pub trait Sealed {} } diff --git a/dunge/src/layer.rs b/dunge/src/layer.rs index ebb9818..00b5785 100644 --- a/dunge/src/layer.rs +++ b/dunge/src/layer.rs @@ -1,5 +1,5 @@ use { - crate::{group::Binding, mesh::Mesh, shader::Shader, state::State, texture::Format}, + crate::{bind::Binding, mesh::Mesh, shader::Shader, state::State, texture::Format}, std::{iter, marker::PhantomData}, wgpu::{RenderPass, RenderPipeline}, }; diff --git a/dunge/src/lib.rs b/dunge/src/lib.rs index c6decca..89bd3c9 100644 --- a/dunge/src/lib.rs +++ b/dunge/src/lib.rs @@ -1,3 +1,4 @@ +pub mod bind; pub mod color; pub mod context; pub mod draw; diff --git a/dunge/src/shader.rs b/dunge/src/shader.rs index 301e1da..a711190 100644 --- a/dunge/src/shader.rs +++ b/dunge/src/shader.rs @@ -1,6 +1,6 @@ use { crate::{ - group::TypedGroup, + bind::TypedGroup, sl::{InputInfo, IntoModule, Module, Stages}, state::State, types::{GroupMemberType, VectorType}, diff --git a/dunge/src/vertex.rs b/dunge/src/vertex.rs index 6677ae9..e4e25d6 100644 --- a/dunge/src/vertex.rs +++ b/dunge/src/vertex.rs @@ -5,12 +5,17 @@ use crate::{ pub use dunge_shader::vertex::*; -pub trait InputProjection { +/// Describes an input type projection. +/// +/// The trait is sealed because the derive macro relies on no new types being used. +pub trait InputProjection: private::Sealed { const TYPE: VectorType; type Field; fn input_projection(id: u32, index: u32) -> Self::Field; } +impl private::Sealed for [f32; 2] {} + impl InputProjection for [f32; 2] { const TYPE: VectorType = VectorType::Vec2f; type Field = Ret>; @@ -20,6 +25,8 @@ impl InputProjection for [f32; 2] { } } +impl private::Sealed for [f32; 3] {} + impl InputProjection for [f32; 3] { const TYPE: VectorType = VectorType::Vec3f; type Field = Ret>; @@ -29,6 +36,8 @@ impl InputProjection for [f32; 3] { } } +impl private::Sealed for [f32; 4] {} + impl InputProjection for [f32; 4] { const TYPE: VectorType = VectorType::Vec4f; type Field = Ret>; @@ -38,6 +47,8 @@ impl InputProjection for [f32; 4] { } } +impl private::Sealed for glam::Vec2 {} + impl InputProjection for glam::Vec2 { const TYPE: VectorType = VectorType::Vec2f; type Field = Ret>; @@ -47,6 +58,8 @@ impl InputProjection for glam::Vec2 { } } +impl private::Sealed for glam::Vec3 {} + impl InputProjection for glam::Vec3 { const TYPE: VectorType = VectorType::Vec3f; type Field = Ret>; @@ -56,6 +69,8 @@ impl InputProjection for glam::Vec3 { } } +impl private::Sealed for glam::Vec4 {} + impl InputProjection for glam::Vec4 { const TYPE: VectorType = VectorType::Vec4f; type Field = Ret>; @@ -64,3 +79,7 @@ impl InputProjection for glam::Vec4 { ReadInput::new(id, index) } } + +mod private { + pub trait Sealed {} +} diff --git a/dunge/tests/gradient.png b/dunge/tests/gradient.png new file mode 100644 index 0000000000000000000000000000000000000000..0e4d58700edbd8c13cf0fc3702e2d78fbc39e233 GIT binary patch literal 5568 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 literal 0 HcmV?d00001 diff --git a/dunge/tests/triangle_group.png b/dunge/tests/triangle_group.png new file mode 100644 index 0000000000000000000000000000000000000000..2f4a838be44f999dde33b3d6e188af04af5d7868 GIT binary patch literal 49389 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 WX#M3J{CC^8|MRz={^LLU;r|8T<~&XS literal 0 HcmV?d00001 diff --git a/dunge/tests/triangle_group.rs b/dunge/tests/triangle_group.rs new file mode 100644 index 0000000..3134c35 --- /dev/null +++ b/dunge/tests/triangle_group.rs @@ -0,0 +1,148 @@ +use { + dunge::{ + bind::VisitGroup, + color::Rgba, + context::Context, + draw, + group::{BoundTexture, DeclareGroup, Group, Projection, Visitor}, + mesh, + sl::{self, GlobalOut, Groups, Input, Out, ReadGlobal, Ret}, + state::{Options, Render}, + texture::{self, Format, Sampler}, + types::{self, GroupMemberType}, + Vertex, + }, + glam::Vec2, + helpers::Image, + std::{error, fs}, +}; + +type Error = Box; + +#[test] +fn render() -> Result<(), Error> { + const SIZE: (u32, u32) = (300, 300); + + #[repr(C)] + #[derive(Vertex)] + struct Vert { + pos: Vec2, + tex: Vec2, + } + + struct Map<'g> { + tex: BoundTexture<'g>, + sam: &'g Sampler, + } + + impl<'g> Group for Map<'g> { + type Projection = MapProjection; + type Visitor = VisitGroup<'g>; + const DECL: DeclareGroup = + DeclareGroup::new(&[GroupMemberType::Tx2df, GroupMemberType::Sampl]); + + fn group(&self, visit: &mut Self::Visitor) { + visit.visit_texture(self.tex); + visit.visit_sampler(self.sam); + } + } + + struct MapProjection { + tex: Ret>, + sam: Ret, + } + + impl Projection for MapProjection { + fn projection(id: u32, out: GlobalOut) -> Self { + Self { + tex: ReadGlobal::new(id, 0, out.clone()), + sam: ReadGlobal::new(id, 1, out.clone()), + } + } + } + + let triangle = |vert: Input, groups: Groups| Out { + place: sl::concat(vert.pos, Vec2::new(0., 1.)), + color: { + let Groups(map) = groups; + sl::texture_sample(map.tex, map.sam, sl::fragment(vert.tex)) + }, + }; + + let cx = helpers::block_on(Context::new())?; + let shader = cx.make_shader(triangle); + let map = { + let texture = { + use texture::Data; + + let gradient = Image::decode(include_bytes!("gradient.png")); + let data = Data::new(&gradient.data, gradient.size, Format::RgbAlpha)?.with_bind(); + cx.make_texture(data) + }; + + let sampler = cx.make_sampler(); + let map = Map { + tex: BoundTexture::new(&texture), + sam: &sampler, + }; + + let mut binder = cx.make_binder(&shader); + binder.bind(&map); + binder.into_binding() + }; + + let layer = cx.make_layer(Format::RgbAlpha, &shader); + let view = { + use texture::Data; + + let data = Data::empty(SIZE, Format::RgbAlpha)?.with_draw().with_copy(); + cx.make_texture(data) + }; + + let mesh = { + use mesh::Data; + + const VERTS: [Vert; 3] = [ + Vert { + pos: Vec2::new(0., -0.75), + tex: Vec2::new(0., 0.), + }, + Vert { + pos: Vec2::new(0.866, 0.75), + tex: Vec2::new(0., 1.), + }, + Vert { + pos: Vec2::new(-0.866, 0.75), + tex: Vec2::new(1., 1.), + }, + ]; + + let data = Data::from_verts(&VERTS); + cx.make_mesh(&data) + }; + + let buffer = cx.make_copy_buffer(SIZE); + let options = Options::default().with_clear(Rgba::from_standard([0., 0., 0., 1.])); + let draw = draw::from_fn(|mut frame| { + frame.layer(&layer, options).bind(&map).draw(&mesh); + frame.copy_texture(&buffer, &view); + }); + + let mut render = Render::default(); + cx.draw_to_texture(&mut render, &view, draw); + + let mapped = helpers::block_on({ + let (tx, rx) = helpers::oneshot(); + cx.map_view(buffer.view(), tx, rx) + }); + + let data = mapped.data(); + let image = Image::from_fn(SIZE, |x, y| { + let (width, _) = buffer.size(); + let idx = x + y * width; + data[idx as usize] + }); + + fs::write("tests/triangle_group.png", image.encode())?; + Ok(()) +} diff --git a/dunge/tests/triangle.png b/dunge/tests/triangle_index.png similarity index 100% rename from dunge/tests/triangle.png rename to dunge/tests/triangle_index.png diff --git a/dunge/tests/triangle.rs b/dunge/tests/triangle_index.rs similarity index 96% rename from dunge/tests/triangle.rs rename to dunge/tests/triangle_index.rs index d37a496..0923649 100644 --- a/dunge/tests/triangle.rs +++ b/dunge/tests/triangle_index.rs @@ -62,6 +62,6 @@ fn render() -> Result<(), Error> { data[idx as usize] }); - fs::write("tests/triangle.png", image.encode())?; + fs::write("tests/triangle_index.png", image.encode())?; Ok(()) } diff --git a/dunge/tests/triangle_color.png b/dunge/tests/triangle_vertex.png similarity index 100% rename from dunge/tests/triangle_color.png rename to dunge/tests/triangle_vertex.png diff --git a/dunge/tests/triangle_color.rs b/dunge/tests/triangle_vertex.rs similarity index 90% rename from dunge/tests/triangle_color.rs rename to dunge/tests/triangle_vertex.rs index 54de706..1c953e8 100644 --- a/dunge/tests/triangle_color.rs +++ b/dunge/tests/triangle_vertex.rs @@ -26,9 +26,9 @@ fn render() -> Result<(), Error> { col: Vec3, } - let triangle = |input: Input| Out { - place: sl::compose(input.pos, Vec2::new(0., 1.)), - color: sl::vec4_with(sl::fragment(input.col), 1.), + let triangle = |vert: Input| Out { + place: sl::concat(vert.pos, Vec2::new(0., 1.)), + color: sl::vec4_with(sl::fragment(vert.col), 1.), }; let cx = helpers::block_on(Context::new())?; @@ -85,6 +85,6 @@ fn render() -> Result<(), Error> { data[idx as usize] }); - fs::write("tests/triangle_color.png", image.encode())?; + fs::write("tests/triangle_vertex.png", image.encode())?; Ok(()) } diff --git a/dunge_shader/src/group.rs b/dunge_shader/src/group.rs index e6075ea..cc8d6f8 100644 --- a/dunge_shader/src/group.rs +++ b/dunge_shader/src/group.rs @@ -3,14 +3,13 @@ use { std::{iter, slice}, }; -/// Group description. +/// The group type description. pub trait Group { type Projection: Projection + 'static; + type Visitor: Visitor; const DECL: DeclareGroup; - fn group(&self, visit: &mut V) - where - V: Visitor; + fn group(&self, visit: &mut Self::Visitor); } pub trait Projection { diff --git a/dunge_shader/src/vector.rs b/dunge_shader/src/vector.rs index a752f35..4903638 100644 --- a/dunge_shader/src/vector.rs +++ b/dunge_shader/src/vector.rs @@ -103,7 +103,7 @@ where } } -pub const fn compose(a: A, b: B) -> Ret, types::Vec4> +pub const fn concat(a: A, b: B) -> Ret, types::Vec4> where A: Eval>, B: Eval>, diff --git a/dunge_shader/src/vertex.rs b/dunge_shader/src/vertex.rs index c2de0e7..c99a7a1 100644 --- a/dunge_shader/src/vertex.rs +++ b/dunge_shader/src/vertex.rs @@ -3,25 +3,30 @@ use { std::{iter, mem, slice}, }; -/// Vertex type description. +/// The vertex type description. /// /// # Safety /// The fields of [`DeclareInput`] must exactly match the actual struct fields. -/// To do this, the fields must be ordered, so the struct must have the `#[repr(C)]` attribute -/// and the fields must have the same order as specified in [`DeclareInput`]. +/// To do this, the fields must be ordered, so the struct must have the `#[repr(C)]` +/// attribute and the fields must have the same order as specified in [`DeclareInput`]. /// -/// # Example -/// TODO -/// -/// Note that the implementation of the trait requires `unsafe` code, -/// so instead of writing this yourself you can derive it: -/// ```rust,ignore +/// # Deriving +/// Although the library tries to formalize the safety invariant, you still shouldn’t +/// implement the vertex yourself. The most reliable and simple way to do this is to +/// use a derive macro: +/// ```rust +/// # mod dunge { +/// # // fake `Vertex` derive +/// # pub use std::default::Default as Vertex; +/// # } +/// # /// use dunge::Vertex; /// /// #[repr(C)] /// #[derive(Vertex)] /// struct Vert { -/// /* TODO */ +/// pos: [f32; 2], +/// col: [f32; 3], /// } /// ``` /// diff --git a/helpers/src/image.rs b/helpers/src/image.rs index 7f433bd..b1c1377 100644 --- a/helpers/src/image.rs +++ b/helpers/src/image.rs @@ -24,12 +24,16 @@ impl Image { } pub fn decode(bytes: &[u8]) -> Self { - use png::Decoder; + use png::{ColorType, Decoder}; let decoder = Decoder::new(bytes); let mut reader = decoder.read_info().expect("png reader"); let mut data = Box::from(vec![0; reader.output_buffer_size()]); let info = reader.next_frame(&mut data).expect("read image"); + if info.color_type != ColorType::Rgba { + panic!("only rgba format is supported"); + } + Self { data, size: (info.width, info.height),