Skip to main content

SpriteSheet

Struct SpriteSheet 

Source
pub struct SpriteSheet { /* private fields */ }
Expand description

A sprite sheet containing uniformly-sized sprites in a grid layout.

All sprites in a sprite sheet have the same dimensions and are arranged in rows and columns. This makes it efficient for animations where each frame is the same size.

§Example

// Create a sprite sheet from a texture
let sprite_sheet = SpriteSheet::new(
    context,
    texture,
    SpriteSheetDescriptor {
        sprite_width: 32,
        sprite_height: 32,
        columns: 8,
        rows: 4,
        ..Default::default()
    },
);

// Get UV coordinates for sprite at index 5
let uv = sprite_sheet.sprite_uv(5);

// Or by row/column
let uv = sprite_sheet.sprite_uv_at(1, 2);

Implementations§

Source§

impl SpriteSheet

Source

pub fn new( texture: Texture, view: TextureView, texture_width: u32, texture_height: u32, descriptor: SpriteSheetDescriptor, ) -> Self

Create a new sprite sheet from an existing texture.

Source

pub fn from_data( context: &GraphicsContext, data: &[u8], texture_width: u32, texture_height: u32, descriptor: SpriteSheetDescriptor, ) -> Self

Create a sprite sheet from raw pixel data.

§Arguments
  • context - Graphics context
  • data - Raw RGBA pixel data
  • texture_width - Width of the texture in pixels
  • texture_height - Height of the texture in pixels
  • descriptor - Sprite sheet configuration
Examples found in repository?
examples/sprite_sheet.rs (lines 180-192)
152fn main() {
153    logging::init();
154
155    run_app(|ctx| {
156        let graphics_ctx =
157            GraphicsContext::new_owned_sync().expect("Failed to create graphics context");
158        let mut windows = HashMap::new();
159
160        let scale = Window::platform_dpi() as f32;
161        let window = ctx
162            .create_window(WindowDescriptor {
163                title: "Sprite Sheet Animation Example".to_string(),
164                size: Some(WinitPhysicalSize::new(400.0 * scale, 400.0 * scale)),
165                ..Default::default()
166            })
167            .expect("Failed to create window");
168
169        let renderable_window = RenderWindowBuilder::new()
170            .color_format(wgpu::TextureFormat::Bgra8UnormSrgb)
171            .with_depth_default()
172            .build(window, graphics_ctx.clone())
173            .expect("Failed to create render window");
174
175        let window_id = renderable_window.id();
176        windows.insert(window_id, renderable_window);
177
178        // Generate sprite sheet
179        let (sprite_data, tex_width, tex_height) = generate_sprite_sheet_data();
180        let sprite_sheet = SpriteSheet::from_data(
181            &graphics_ctx,
182            &sprite_data,
183            tex_width,
184            tex_height,
185            SpriteSheetDescriptor {
186                sprite_width: 64,
187                sprite_height: 64,
188                columns: 4,
189                rows: 1,
190                ..Default::default()
191            },
192        );
193
194        // Create animation (4 frames at 8 fps)
195        let animation = SpriteAnimation::new(4, 8.0);
196
197        // Create shader module
198        let shader = graphics_ctx
199            .device()
200            .create_shader_module(wgpu::ShaderModuleDescriptor {
201                label: Some("Sprite Shader"),
202                source: wgpu::ShaderSource::Wgsl(SHADER.into()),
203            });
204
205        // Create bind group layout
206        let bind_group_layout =
207            graphics_ctx
208                .device()
209                .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
210                    label: Some("Sprite Bind Group Layout"),
211                    entries: &[
212                        wgpu::BindGroupLayoutEntry {
213                            binding: 0,
214                            visibility: wgpu::ShaderStages::VERTEX,
215                            ty: wgpu::BindingType::Buffer {
216                                ty: wgpu::BufferBindingType::Uniform,
217                                has_dynamic_offset: false,
218                                min_binding_size: None,
219                            },
220                            count: None,
221                        },
222                        wgpu::BindGroupLayoutEntry {
223                            binding: 1,
224                            visibility: wgpu::ShaderStages::FRAGMENT,
225                            ty: wgpu::BindingType::Texture {
226                                sample_type: wgpu::TextureSampleType::Float { filterable: true },
227                                view_dimension: wgpu::TextureViewDimension::D2,
228                                multisampled: false,
229                            },
230                            count: None,
231                        },
232                        wgpu::BindGroupLayoutEntry {
233                            binding: 2,
234                            visibility: wgpu::ShaderStages::FRAGMENT,
235                            ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
236                            count: None,
237                        },
238                    ],
239                });
240
241        // Create pipeline layout
242        let pipeline_layout =
243            graphics_ctx
244                .device()
245                .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
246                    label: Some("Sprite Pipeline Layout"),
247                    bind_group_layouts: &[&bind_group_layout],
248                    push_constant_ranges: &[],
249                });
250
251        // Create render pipeline
252        let pipeline =
253            graphics_ctx
254                .device()
255                .create_render_pipeline(&wgpu::RenderPipelineDescriptor {
256                    label: Some("Sprite Pipeline"),
257                    layout: Some(&pipeline_layout),
258                    vertex: wgpu::VertexState {
259                        module: &shader,
260                        entry_point: Some("vs_main"),
261                        buffers: &[wgpu::VertexBufferLayout {
262                            array_stride: std::mem::size_of::<Vertex>() as u64,
263                            step_mode: wgpu::VertexStepMode::Vertex,
264                            attributes: &[
265                                wgpu::VertexAttribute {
266                                    offset: 0,
267                                    shader_location: 0,
268                                    format: wgpu::VertexFormat::Float32x2,
269                                },
270                                wgpu::VertexAttribute {
271                                    offset: 8,
272                                    shader_location: 1,
273                                    format: wgpu::VertexFormat::Float32x2,
274                                },
275                            ],
276                        }],
277                        compilation_options: wgpu::PipelineCompilationOptions::default(),
278                    },
279                    fragment: Some(wgpu::FragmentState {
280                        module: &shader,
281                        entry_point: Some("fs_main"),
282                        targets: &[Some(wgpu::ColorTargetState {
283                            format: wgpu::TextureFormat::Bgra8UnormSrgb,
284                            blend: Some(wgpu::BlendState::ALPHA_BLENDING),
285                            write_mask: wgpu::ColorWrites::ALL,
286                        })],
287                        compilation_options: wgpu::PipelineCompilationOptions::default(),
288                    }),
289                    primitive: wgpu::PrimitiveState {
290                        topology: wgpu::PrimitiveTopology::TriangleList,
291                        ..Default::default()
292                    },
293                    depth_stencil: None,
294                    multisample: wgpu::MultisampleState::default(),
295                    multiview: None,
296                    cache: None,
297                });
298
299        // Create uniform buffer
300        let uniforms = Uniforms {
301            mvp: [
302                [1.0, 0.0, 0.0, 0.0],
303                [0.0, 1.0, 0.0, 0.0],
304                [0.0, 0.0, 1.0, 0.0],
305                [0.0, 0.0, 0.0, 1.0],
306            ],
307        };
308        let uniform_buffer =
309            graphics_ctx
310                .device()
311                .create_buffer_init(&wgpu::util::BufferInitDescriptor {
312                    label: Some("Uniform Buffer"),
313                    contents: bytemuck::cast_slice(&[uniforms]),
314                    usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
315                });
316
317        // Create sampler
318        let sampler = graphics_ctx
319            .device()
320            .create_sampler(&wgpu::SamplerDescriptor {
321                label: Some("Sprite Sampler"),
322                mag_filter: wgpu::FilterMode::Linear,
323                min_filter: wgpu::FilterMode::Linear,
324                ..Default::default()
325            });
326
327        // Create bind group
328        let bind_group = graphics_ctx
329            .device()
330            .create_bind_group(&wgpu::BindGroupDescriptor {
331                label: Some("Sprite Bind Group"),
332                layout: &bind_group_layout,
333                entries: &[
334                    wgpu::BindGroupEntry {
335                        binding: 0,
336                        resource: uniform_buffer.as_entire_binding(),
337                    },
338                    wgpu::BindGroupEntry {
339                        binding: 1,
340                        resource: wgpu::BindingResource::TextureView(sprite_sheet.view()),
341                    },
342                    wgpu::BindGroupEntry {
343                        binding: 2,
344                        resource: wgpu::BindingResource::Sampler(&sampler),
345                    },
346                ],
347            });
348
349        // Initial vertex buffer (will be updated each frame with new UVs)
350        let vertices = create_quad_vertices(0.0, 0.0, 1.0, 1.0);
351        let vertex_buffer =
352            graphics_ctx
353                .device()
354                .create_buffer_init(&wgpu::util::BufferInitDescriptor {
355                    label: Some("Vertex Buffer"),
356                    contents: bytemuck::cast_slice(&vertices),
357                    usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
358                });
359
360        Box::new(App {
361            _context: graphics_ctx,
362            windows,
363            pipeline,
364            bind_group,
365            vertex_buffer,
366            uniform_buffer,
367            sprite_sheet,
368            animation,
369            last_update: Instant::now(),
370        })
371    });
372}
Source

pub fn sprite_uv(&self, index: u32) -> SpriteUV

Get UV coordinates for a sprite by linear index.

Sprites are indexed left-to-right, top-to-bottom, starting from 0.

Examples found in repository?
examples/sprite_sheet.rs (line 417)
404    fn update(
405        &mut self,
406        _ctx: &mut astrelis_winit::app::AppCtx,
407        _time: &astrelis_winit::FrameTime,
408    ) {
409        let now = Instant::now();
410        let dt = now.duration_since(self.last_update).as_secs_f32();
411        self.last_update = now;
412
413        // Update animation
414        if self.animation.update(dt) {
415            // Frame changed - update vertex buffer with new UVs
416            let frame = self.animation.current_frame();
417            let uv = self.sprite_sheet.sprite_uv(frame);
418            let vertices = create_quad_vertices(uv.u_min, uv.v_min, uv.u_max, uv.v_max);
419
420            // Get context from first window
421            if let Some(window) = self.windows.values().next() {
422                window.context().graphics_context().queue().write_buffer(
423                    &self.vertex_buffer,
424                    0,
425                    bytemuck::cast_slice(&vertices),
426                );
427            }
428        }
429    }
Source

pub fn sprite_uv_at(&self, row: u32, col: u32) -> SpriteUV

Get UV coordinates for a sprite by row and column.

Source

pub fn sprite_count(&self) -> u32

Get the total number of sprites in the sheet.

Source

pub fn sprite_size(&self) -> (u32, u32)

Get the sprite dimensions in pixels.

Source

pub fn grid_size(&self) -> (u32, u32)

Get the grid dimensions (columns, rows).

Source

pub fn view(&self) -> &TextureView

Get the texture view for binding.

Examples found in repository?
examples/sprite_sheet.rs (line 340)
152fn main() {
153    logging::init();
154
155    run_app(|ctx| {
156        let graphics_ctx =
157            GraphicsContext::new_owned_sync().expect("Failed to create graphics context");
158        let mut windows = HashMap::new();
159
160        let scale = Window::platform_dpi() as f32;
161        let window = ctx
162            .create_window(WindowDescriptor {
163                title: "Sprite Sheet Animation Example".to_string(),
164                size: Some(WinitPhysicalSize::new(400.0 * scale, 400.0 * scale)),
165                ..Default::default()
166            })
167            .expect("Failed to create window");
168
169        let renderable_window = RenderWindowBuilder::new()
170            .color_format(wgpu::TextureFormat::Bgra8UnormSrgb)
171            .with_depth_default()
172            .build(window, graphics_ctx.clone())
173            .expect("Failed to create render window");
174
175        let window_id = renderable_window.id();
176        windows.insert(window_id, renderable_window);
177
178        // Generate sprite sheet
179        let (sprite_data, tex_width, tex_height) = generate_sprite_sheet_data();
180        let sprite_sheet = SpriteSheet::from_data(
181            &graphics_ctx,
182            &sprite_data,
183            tex_width,
184            tex_height,
185            SpriteSheetDescriptor {
186                sprite_width: 64,
187                sprite_height: 64,
188                columns: 4,
189                rows: 1,
190                ..Default::default()
191            },
192        );
193
194        // Create animation (4 frames at 8 fps)
195        let animation = SpriteAnimation::new(4, 8.0);
196
197        // Create shader module
198        let shader = graphics_ctx
199            .device()
200            .create_shader_module(wgpu::ShaderModuleDescriptor {
201                label: Some("Sprite Shader"),
202                source: wgpu::ShaderSource::Wgsl(SHADER.into()),
203            });
204
205        // Create bind group layout
206        let bind_group_layout =
207            graphics_ctx
208                .device()
209                .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
210                    label: Some("Sprite Bind Group Layout"),
211                    entries: &[
212                        wgpu::BindGroupLayoutEntry {
213                            binding: 0,
214                            visibility: wgpu::ShaderStages::VERTEX,
215                            ty: wgpu::BindingType::Buffer {
216                                ty: wgpu::BufferBindingType::Uniform,
217                                has_dynamic_offset: false,
218                                min_binding_size: None,
219                            },
220                            count: None,
221                        },
222                        wgpu::BindGroupLayoutEntry {
223                            binding: 1,
224                            visibility: wgpu::ShaderStages::FRAGMENT,
225                            ty: wgpu::BindingType::Texture {
226                                sample_type: wgpu::TextureSampleType::Float { filterable: true },
227                                view_dimension: wgpu::TextureViewDimension::D2,
228                                multisampled: false,
229                            },
230                            count: None,
231                        },
232                        wgpu::BindGroupLayoutEntry {
233                            binding: 2,
234                            visibility: wgpu::ShaderStages::FRAGMENT,
235                            ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
236                            count: None,
237                        },
238                    ],
239                });
240
241        // Create pipeline layout
242        let pipeline_layout =
243            graphics_ctx
244                .device()
245                .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
246                    label: Some("Sprite Pipeline Layout"),
247                    bind_group_layouts: &[&bind_group_layout],
248                    push_constant_ranges: &[],
249                });
250
251        // Create render pipeline
252        let pipeline =
253            graphics_ctx
254                .device()
255                .create_render_pipeline(&wgpu::RenderPipelineDescriptor {
256                    label: Some("Sprite Pipeline"),
257                    layout: Some(&pipeline_layout),
258                    vertex: wgpu::VertexState {
259                        module: &shader,
260                        entry_point: Some("vs_main"),
261                        buffers: &[wgpu::VertexBufferLayout {
262                            array_stride: std::mem::size_of::<Vertex>() as u64,
263                            step_mode: wgpu::VertexStepMode::Vertex,
264                            attributes: &[
265                                wgpu::VertexAttribute {
266                                    offset: 0,
267                                    shader_location: 0,
268                                    format: wgpu::VertexFormat::Float32x2,
269                                },
270                                wgpu::VertexAttribute {
271                                    offset: 8,
272                                    shader_location: 1,
273                                    format: wgpu::VertexFormat::Float32x2,
274                                },
275                            ],
276                        }],
277                        compilation_options: wgpu::PipelineCompilationOptions::default(),
278                    },
279                    fragment: Some(wgpu::FragmentState {
280                        module: &shader,
281                        entry_point: Some("fs_main"),
282                        targets: &[Some(wgpu::ColorTargetState {
283                            format: wgpu::TextureFormat::Bgra8UnormSrgb,
284                            blend: Some(wgpu::BlendState::ALPHA_BLENDING),
285                            write_mask: wgpu::ColorWrites::ALL,
286                        })],
287                        compilation_options: wgpu::PipelineCompilationOptions::default(),
288                    }),
289                    primitive: wgpu::PrimitiveState {
290                        topology: wgpu::PrimitiveTopology::TriangleList,
291                        ..Default::default()
292                    },
293                    depth_stencil: None,
294                    multisample: wgpu::MultisampleState::default(),
295                    multiview: None,
296                    cache: None,
297                });
298
299        // Create uniform buffer
300        let uniforms = Uniforms {
301            mvp: [
302                [1.0, 0.0, 0.0, 0.0],
303                [0.0, 1.0, 0.0, 0.0],
304                [0.0, 0.0, 1.0, 0.0],
305                [0.0, 0.0, 0.0, 1.0],
306            ],
307        };
308        let uniform_buffer =
309            graphics_ctx
310                .device()
311                .create_buffer_init(&wgpu::util::BufferInitDescriptor {
312                    label: Some("Uniform Buffer"),
313                    contents: bytemuck::cast_slice(&[uniforms]),
314                    usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
315                });
316
317        // Create sampler
318        let sampler = graphics_ctx
319            .device()
320            .create_sampler(&wgpu::SamplerDescriptor {
321                label: Some("Sprite Sampler"),
322                mag_filter: wgpu::FilterMode::Linear,
323                min_filter: wgpu::FilterMode::Linear,
324                ..Default::default()
325            });
326
327        // Create bind group
328        let bind_group = graphics_ctx
329            .device()
330            .create_bind_group(&wgpu::BindGroupDescriptor {
331                label: Some("Sprite Bind Group"),
332                layout: &bind_group_layout,
333                entries: &[
334                    wgpu::BindGroupEntry {
335                        binding: 0,
336                        resource: uniform_buffer.as_entire_binding(),
337                    },
338                    wgpu::BindGroupEntry {
339                        binding: 1,
340                        resource: wgpu::BindingResource::TextureView(sprite_sheet.view()),
341                    },
342                    wgpu::BindGroupEntry {
343                        binding: 2,
344                        resource: wgpu::BindingResource::Sampler(&sampler),
345                    },
346                ],
347            });
348
349        // Initial vertex buffer (will be updated each frame with new UVs)
350        let vertices = create_quad_vertices(0.0, 0.0, 1.0, 1.0);
351        let vertex_buffer =
352            graphics_ctx
353                .device()
354                .create_buffer_init(&wgpu::util::BufferInitDescriptor {
355                    label: Some("Vertex Buffer"),
356                    contents: bytemuck::cast_slice(&vertices),
357                    usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
358                });
359
360        Box::new(App {
361            _context: graphics_ctx,
362            windows,
363            pipeline,
364            bind_group,
365            vertex_buffer,
366            uniform_buffer,
367            sprite_sheet,
368            animation,
369            last_update: Instant::now(),
370        })
371    });
372}
Source

pub fn texture(&self) -> &Texture

Get the underlying texture.

Source

pub fn texture_size(&self) -> (u32, u32)

Get texture dimensions.

Trait Implementations§

Source§

impl Debug for SpriteSheet

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> Downcast<T> for T

Source§

fn downcast(&self) -> &T

Source§

impl<T> Downcast for T
where T: Any,

Source§

fn into_any(self: Box<T>) -> Box<dyn Any>

Convert Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>. Box<dyn Any> can then be further downcast into Box<ConcreteType> where ConcreteType implements Trait.
Source§

fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>

Convert Rc<Trait> (where Trait: Downcast) to Rc<Any>. Rc<Any> can then be further downcast into Rc<ConcreteType> where ConcreteType implements Trait.
Source§

fn as_any(&self) -> &(dyn Any + 'static)

Convert &Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot generate &Any’s vtable from &Trait’s.
Source§

fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)

Convert &mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot generate &mut Any’s vtable from &mut Trait’s.
Source§

impl<T> DowncastSync for T
where T: Any + Send + Sync,

Source§

fn into_any_arc(self: Arc<T>) -> Arc<dyn Any + Sync + Send>

Convert Arc<Trait> (where Trait: Downcast) to Arc<Any>. Arc<Any> can then be further downcast into Arc<ConcreteType> where ConcreteType implements Trait.
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> Upcast<T> for T

Source§

fn upcast(&self) -> Option<&T>

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

impl<T> WasmNotSend for T
where T: Send,

Source§

impl<T> WasmNotSendSync for T

Source§

impl<T> WasmNotSync for T
where T: Sync,