oxide_renderer/texture/
mod.rs1mod fallback;
4mod sampler;
5
6use std::path::Path;
7
8use wgpu::{Device, Queue};
9
10pub use fallback::FallbackTexture;
11pub use sampler::SamplerDescriptor;
12
13#[derive(thiserror::Error, Debug)]
14pub enum TextureError {
15 #[error("Failed to load image '{path}': {source}")]
16 ImageLoad {
17 path: String,
18 source: image::ImageError,
19 },
20 #[error("Failed to read image file '{path}': {source}")]
21 Io {
22 path: String,
23 source: std::io::Error,
24 },
25 #[error("Invalid texture dimensions: {width}x{height}")]
26 InvalidDimensions { width: u32, height: u32 },
27}
28
29#[derive(Debug)]
31pub struct Texture {
32 pub texture: wgpu::Texture,
33 pub view: wgpu::TextureView,
34 pub sampler: wgpu::Sampler,
35}
36
37impl Texture {
38 pub fn from_bytes(
40 device: &Device,
41 queue: &Queue,
42 bytes: &[u8],
43 dimensions: (u32, u32),
44 label: Option<&str>,
45 ) -> Self {
46 let (width, height) = dimensions;
47
48 let texture = device.create_texture(&wgpu::TextureDescriptor {
49 label,
50 size: wgpu::Extent3d {
51 width,
52 height,
53 depth_or_array_layers: 1,
54 },
55 mip_level_count: 1,
56 sample_count: 1,
57 dimension: wgpu::TextureDimension::D2,
58 format: wgpu::TextureFormat::Rgba8UnormSrgb,
59 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
60 view_formats: &[],
61 });
62
63 queue.write_texture(
64 texture.as_image_copy(),
65 bytes,
66 wgpu::TexelCopyBufferLayout {
67 offset: 0,
68 bytes_per_row: Some(4 * width),
69 rows_per_image: Some(height),
70 },
71 wgpu::Extent3d {
72 width,
73 height,
74 depth_or_array_layers: 1,
75 },
76 );
77
78 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
79
80 let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
81 address_mode_u: wgpu::AddressMode::ClampToEdge,
82 address_mode_v: wgpu::AddressMode::ClampToEdge,
83 address_mode_w: wgpu::AddressMode::ClampToEdge,
84 mag_filter: wgpu::FilterMode::Linear,
85 min_filter: wgpu::FilterMode::Linear,
86 mipmap_filter: wgpu::MipmapFilterMode::Nearest,
87 ..Default::default()
88 });
89
90 Self {
91 texture,
92 view,
93 sampler,
94 }
95 }
96
97 pub fn from_file(
99 device: &Device,
100 queue: &Queue,
101 path: impl AsRef<Path>,
102 ) -> Result<Self, TextureError> {
103 let path = path.as_ref();
104 let path_str = path.display().to_string();
105
106 let img = image::open(path).map_err(|source| TextureError::ImageLoad {
107 path: path_str.clone(),
108 source,
109 })?;
110
111 let rgba = img.to_rgba8();
112 let dimensions = rgba.dimensions();
113
114 Ok(Self::from_bytes(
115 device,
116 queue,
117 &rgba,
118 dimensions,
119 Some(&path_str),
120 ))
121 }
122
123 pub fn with_sampler(mut self, device: &Device, descriptor: &SamplerDescriptor) -> Self {
125 self.sampler = device.create_sampler(&wgpu::SamplerDescriptor {
126 address_mode_u: descriptor.address_mode_u,
127 address_mode_v: descriptor.address_mode_v,
128 address_mode_w: descriptor.address_mode_w,
129 mag_filter: descriptor.mag_filter,
130 min_filter: descriptor.min_filter,
131 mipmap_filter: descriptor.mipmap_filter,
132 ..Default::default()
133 });
134 self
135 }
136}