1use crate::prelude::{update_texture, Rect, Vector2, WHITE, Image};
2use anyhow::*;
3use image::GenericImageView;
4use wgpu::*;
5
6#[repr(C)]
17#[derive(Clone, Copy, Debug, Default)]
18pub enum FilterType {
19 Nearest,
21
22 #[default]
24 Linear,
25}
26
27impl FilterType {
28 pub fn to_wgpu_filter(&self) -> FilterMode {
29 match self {
30 FilterType::Nearest => FilterMode::Nearest,
31 FilterType::Linear => FilterMode::Linear,
32 }
33 }
34
35 pub fn to_wgpu_mipmap_filter(&self) -> MipmapFilterMode {
36 match self {
37 FilterType::Nearest => MipmapFilterMode::Nearest,
38 FilterType::Linear => MipmapFilterMode::Linear,
39 }
40 }
41}
42
43#[repr(C)]
44#[derive(Clone, Copy, Debug, Default)]
45pub struct Texture {
46 pub(crate) id: usize,
47 pub(crate) width: u32,
48 pub(crate) height: u32,
49}
50
51impl Texture {
52 pub fn width(&self) -> u32 {
53 self.width
54 }
55
56 pub fn height(&self) -> u32 {
57 self.height
58 }
59
60 pub fn update(&self, image: &Image) {
61 update_texture(*self, image);
62 }
63}
64
65pub(crate) struct TextureEntry {
66 pub gpu: GpuTexture,
67 pub bind_group: BindGroup,
68 pub premultiplied: bool,
69}
70
71#[derive(Clone, Debug)]
72pub struct GpuTexture {
73 #[allow(unused)]
74 pub texture: wgpu::Texture,
75 pub view: TextureView,
76 pub sampler: Sampler,
77}
78
79impl GpuTexture {
80 #[allow(unused)]
81 pub fn empty(
82 device: &Device,
83 width: u32,
84 height: u32,
85 filter_type: FilterType,
86 format: TextureFormat,
87 ) -> Result<Self> {
88 let size = Extent3d { width, height, depth_or_array_layers: 1 };
89
90 let texture = device.create_texture(&TextureDescriptor {
91 label: Some("render target"),
92 size,
93 mip_level_count: 1, sample_count: 1,
95 dimension: TextureDimension::D2,
96 format, usage: TextureUsages::TEXTURE_BINDING
98 | TextureUsages::COPY_DST
99 | TextureUsages::RENDER_ATTACHMENT,
100 view_formats: &[],
101 });
102
103 let filter_mode = filter_type.to_wgpu_filter();
104 let view = texture.create_view(&TextureViewDescriptor::default());
105 let sampler = device.create_sampler(&SamplerDescriptor {
106 address_mode_u: AddressMode::ClampToEdge,
107 address_mode_v: AddressMode::ClampToEdge,
108 address_mode_w: AddressMode::ClampToEdge,
109 mag_filter: filter_mode,
110 min_filter: filter_mode,
111 mipmap_filter: filter_type.to_wgpu_mipmap_filter(),
112 ..Default::default()
113 });
114
115 Ok(Self { texture, view, sampler })
116 }
117
118 #[allow(unused)]
119 pub fn from_bytes(
120 device: &Device,
121 queue: &Queue,
122 bytes: &[u8],
123 filter_type: FilterType,
124 label: &str,
125 ) -> Result<Self> {
126 let img = image::load_from_memory(bytes)?;
127 Self::from_image(device, queue, &img, filter_type, Some(label))
128 }
129
130 #[allow(unused)]
131 pub fn from_image(
132 device: &Device,
133 queue: &Queue,
134 img: &image::DynamicImage,
135 filter_type: FilterType,
136 label: Option<&str>,
137 ) -> Result<Self> {
138 let rgba = img.to_rgba8();
139 let dimensions = img.dimensions();
140
141 let size = Extent3d {
142 width: dimensions.0,
143 height: dimensions.1,
144 depth_or_array_layers: 1,
145 };
146 let mip_level_count = if matches!(filter_type, FilterType::Nearest) {
147 1 } else {
149 dimensions.0.max(dimensions.1).ilog2() + 1
150 };
151 let texture = device.create_texture(&TextureDescriptor {
152 label,
153 size,
154 mip_level_count,
155 sample_count: 1,
156 dimension: TextureDimension::D2,
157 format: TextureFormat::Rgba8UnormSrgb,
158 usage: TextureUsages::TEXTURE_BINDING
159 | TextureUsages::COPY_DST
160 | TextureUsages::RENDER_ATTACHMENT,
161 view_formats: &[],
162 });
163
164 queue.write_texture(
165 TexelCopyTextureInfo {
166 aspect: TextureAspect::All,
167 texture: &texture,
168 mip_level: 0,
169 origin: Origin3d::ZERO,
170 },
171 &rgba,
172 TexelCopyBufferLayout {
173 offset: 0,
174 bytes_per_row: Some(4 * dimensions.0),
175 rows_per_image: Some(dimensions.1),
176 },
177 size,
178 );
179 Self::generate_mipmaps(device, queue, &texture, mip_level_count);
180
181 let filter_mode = filter_type.to_wgpu_filter();
182 let mipmap_filter = filter_type.to_wgpu_mipmap_filter();
183
184 let view = texture.create_view(&TextureViewDescriptor::default());
185 let sampler = device.create_sampler(&SamplerDescriptor {
186 address_mode_u: AddressMode::ClampToEdge,
187 address_mode_v: AddressMode::ClampToEdge,
188 address_mode_w: AddressMode::ClampToEdge,
189 mag_filter: filter_mode,
190 min_filter: filter_mode,
191 mipmap_filter,
192 ..Default::default()
193 });
194
195 Ok(Self {
196 texture,
197 view,
198 sampler,
199 })
200 }
201
202 pub fn generate_mipmaps(
203 device: &Device,
204 queue: &Queue,
205 texture: &wgpu::Texture,
206 mip_count: u32,
207 ) {
208 let shader = device.create_shader_module(include_wgsl!("../shaders/blit.wgsl"));
209
210 let pipeline = device.create_render_pipeline(&RenderPipelineDescriptor {
211 label: Some("mip blit"),
212 layout: None,
213 vertex: VertexState {
214 module: &shader,
215 entry_point: Some("vs"),
216 buffers: &[],
217 compilation_options: Default::default(),
218 },
219 fragment: Some(FragmentState {
220 module: &shader,
221 entry_point: Some("fs"),
222 targets: &[Some(TextureFormat::Rgba8UnormSrgb.into())],
223 compilation_options: Default::default(),
224 }),
225 primitive: PrimitiveState::default(),
226 depth_stencil: None,
227 multisample: MultisampleState::default(),
228 multiview_mask: None,
229 cache: None,
230 });
231
232 let sampler = device.create_sampler(&SamplerDescriptor {
233 mag_filter: FilterMode::Linear,
234 min_filter: FilterMode::Linear,
235 ..Default::default()
236 });
237
238 let layout = pipeline.get_bind_group_layout(0);
239 let mut encoder = device.create_command_encoder(&Default::default());
240
241 for i in 1..mip_count {
242 let src = texture.create_view(&TextureViewDescriptor {
243 base_mip_level: i - 1,
244 mip_level_count: Some(1),
245 ..Default::default()
246 });
247 let dst = texture.create_view(&TextureViewDescriptor {
248 base_mip_level: i,
249 mip_level_count: Some(1),
250 ..Default::default()
251 });
252
253 let bind_group = device.create_bind_group(&BindGroupDescriptor {
254 label: None,
255 layout: &layout,
256 entries: &[
257 BindGroupEntry {
258 binding: 0,
259 resource: BindingResource::TextureView(&src),
260 },
261 BindGroupEntry {
262 binding: 1,
263 resource: BindingResource::Sampler(&sampler),
264 },
265 ],
266 });
267
268 let mut pass = encoder.begin_render_pass(&RenderPassDescriptor {
269 label: None,
270 color_attachments: &[Some(RenderPassColorAttachment {
271 view: &dst,
272 resolve_target: None,
273 depth_slice: None,
274 ops: Operations {
275 load: LoadOp::Clear(Color::TRANSPARENT),
276 store: StoreOp::Store,
277 },
278 })],
279 ..Default::default()
280 });
281 pass.set_pipeline(&pipeline);
282 pass.set_bind_group(0, &bind_group, &[]);
283 pass.draw(0..3, 0..1);
284 }
285
286 queue.submit(std::iter::once(encoder.finish()));
287 }
288
289 pub const DEPTH_FORMAT: TextureFormat = TextureFormat::Depth32Float;
292
293 pub fn create_depth_texture(
294 device: &Device,
295 width: u32,
296 height: u32,
297 label: &str,
298 ) -> Self {
299 let size = Extent3d {
300 width: width.max(1),
301 height: height.max(1),
302 depth_or_array_layers: 1,
303 };
304
305 let desc = TextureDescriptor {
306 label: Some(label),
307 size,
308 mip_level_count: 1,
309 sample_count: 4,
310 dimension: TextureDimension::D2,
311 format: Self::DEPTH_FORMAT,
312 usage: TextureUsages::RENDER_ATTACHMENT
313 | TextureUsages::TEXTURE_BINDING
314 | TextureUsages::COPY_DST,
315 view_formats: &[],
316 };
317
318 let texture = device.create_texture(&desc);
319 let view = texture.create_view(&TextureViewDescriptor::default());
320 let sampler = device.create_sampler(&SamplerDescriptor {
321 address_mode_u: AddressMode::ClampToEdge,
322 address_mode_v: AddressMode::ClampToEdge,
323 address_mode_w: AddressMode::ClampToEdge,
324 mag_filter: FilterMode::Linear,
325 min_filter: FilterMode::Linear,
326 mipmap_filter: MipmapFilterMode::Nearest,
327 compare: Some(CompareFunction::LessEqual),
328 lod_min_clamp: 0.0,
329 lod_max_clamp: 100.0,
330 ..Default::default()
331 });
332
333 Self {
334 texture,
335 view,
336 sampler,
337 }
338 }
339}
340
341pub struct DrawTextureParams {
351 pub dest_size: Option<Vector2>,
352 pub source: Option<Rect>, pub rotation: f32, pub flip_x: bool,
355 pub flip_y: bool,
356 pub pivot: Option<Vector2>, pub color: crate::prelude::Color,
358}
359
360impl Default for DrawTextureParams {
361 fn default() -> Self {
362 Self {
363 dest_size: None,
364 source: None,
365 rotation: 0.0,
366 flip_x: false,
367 flip_y: false,
368 pivot: None,
369 color: WHITE,
370 }
371 }
372}