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 0000000..0e4d587 Binary files /dev/null and b/dunge/tests/gradient.png differ diff --git a/dunge/tests/triangle_group.png b/dunge/tests/triangle_group.png new file mode 100644 index 0000000..2f4a838 Binary files /dev/null and b/dunge/tests/triangle_group.png differ 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),