1pub mod render_texture;
4pub mod video_texture;
5
6use std::sync::Arc;
7
8use image::DynamicImage;
9
10use crate::application::Application;
11
12#[derive(Clone)]
16pub struct Texture {
17 inner: Arc<TextureInner>,
18}
19
20impl std::fmt::Debug for Texture {
21 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
22 f.debug_struct("Texture")
23 .field("width", &self.inner.width)
24 .field("height", &self.inner.height)
25 .field("id", &self.id())
26 .finish()
27 }
28}
29
30struct TextureInner {
31 texture: wgpu::Texture,
32 view: wgpu::TextureView,
33 sampler: wgpu::Sampler,
34 width: u32,
35 height: u32,
36}
37
38impl Texture {
39 #[track_caller]
47 #[must_use]
48 pub fn from_rgba(app: &Application, width: u32, height: u32, bytes: &[u8]) -> Self {
49 let expected = (width as usize)
50 .checked_mul(height as usize)
51 .and_then(|n| n.checked_mul(4))
52 .expect("Texture::from_rgba: dimensions overflow usize");
53 assert_eq!(
54 bytes.len(),
55 expected,
56 "Texture::from_rgba: byte length mismatch (got {got}, expected {expected} for {width}×{height} RGBA)",
57 got = bytes.len(),
58 );
59
60 let device = app.device();
61 let queue = app.queue();
62 let texture = device.create_texture(&wgpu::TextureDescriptor {
63 label: Some("wisp::Texture"),
64 size: wgpu::Extent3d {
65 width,
66 height,
67 depth_or_array_layers: 1,
68 },
69 mip_level_count: 1,
70 sample_count: 1,
71 dimension: wgpu::TextureDimension::D2,
72 format: wgpu::TextureFormat::Rgba8UnormSrgb,
73 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
74 view_formats: &[],
75 });
76
77 queue.write_texture(
78 wgpu::TexelCopyTextureInfo {
79 texture: &texture,
80 mip_level: 0,
81 origin: wgpu::Origin3d::ZERO,
82 aspect: wgpu::TextureAspect::All,
83 },
84 bytes,
85 wgpu::TexelCopyBufferLayout {
86 offset: 0,
87 bytes_per_row: Some(width * 4),
88 rows_per_image: Some(height),
89 },
90 wgpu::Extent3d {
91 width,
92 height,
93 depth_or_array_layers: 1,
94 },
95 );
96
97 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
98 let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
99 label: Some("wisp::Texture sampler"),
100 mag_filter: wgpu::FilterMode::Linear,
101 min_filter: wgpu::FilterMode::Linear,
102 ..Default::default()
103 });
104
105 Self {
106 inner: Arc::new(TextureInner {
107 texture,
108 view,
109 sampler,
110 width,
111 height,
112 }),
113 }
114 }
115
116 #[must_use]
118 pub fn from_image(app: &Application, img: &DynamicImage) -> Self {
119 let rgba = img.to_rgba8();
120 Self::from_rgba(app, rgba.width(), rgba.height(), rgba.as_raw())
121 }
122
123 pub(crate) fn from_render_texture_parts(
131 texture: wgpu::Texture,
132 view: wgpu::TextureView,
133 sampler: wgpu::Sampler,
134 width: u32,
135 height: u32,
136 ) -> Self {
137 Self {
138 inner: Arc::new(TextureInner {
139 texture,
140 view,
141 sampler,
142 width,
143 height,
144 }),
145 }
146 }
147
148 #[must_use]
154 pub fn empty(app: &Application, width: u32, height: u32, format: wgpu::TextureFormat) -> Self {
155 let device = app.device();
156 let texture = device.create_texture(&wgpu::TextureDescriptor {
157 label: Some("wisp::Texture::empty"),
158 size: wgpu::Extent3d {
159 width,
160 height,
161 depth_or_array_layers: 1,
162 },
163 mip_level_count: 1,
164 sample_count: 1,
165 dimension: wgpu::TextureDimension::D2,
166 format,
167 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
168 view_formats: &[],
169 });
170 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
171 let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
172 label: Some("wisp::Texture::empty sampler"),
173 mag_filter: wgpu::FilterMode::Linear,
174 min_filter: wgpu::FilterMode::Linear,
175 ..Default::default()
176 });
177 Self {
178 inner: Arc::new(TextureInner {
179 texture,
180 view,
181 sampler,
182 width,
183 height,
184 }),
185 }
186 }
187
188 #[must_use]
190 pub fn width(&self) -> u32 {
191 self.inner.width
192 }
193
194 #[must_use]
196 pub fn height(&self) -> u32 {
197 self.inner.height
198 }
199
200 pub(crate) fn view(&self) -> &wgpu::TextureView {
201 &self.inner.view
202 }
203
204 pub(crate) fn sampler(&self) -> &wgpu::Sampler {
205 &self.inner.sampler
206 }
207
208 pub(crate) fn wgpu_texture(&self) -> &wgpu::Texture {
209 &self.inner.texture
210 }
211
212 pub(crate) fn id(&self) -> usize {
218 Arc::as_ptr(&self.inner) as usize
219 }
220}