1use crate::{
2 assets::{plugin::AssetPlugin, singleton_asset::LazyResourcePlugin, upload::Asset},
3 ecs::{plugin::Plugin, system::Res},
4 wgpu::{
5 backend::WGPUBackend,
6 mipmap::MipmapGenerator,
7 textures::{bytes_per_pixel, decode_file},
8 },
9};
10
11pub struct CubemapDescriptor {
12 pub size: u32, pub format: wgpu::TextureFormat,
14 pub faces: Option<[Vec<u8>; 6]>,
20 pub face_files: Option<[&'static str; 6]>,
23 pub generate_mips: bool,
24}
25
26impl CubemapDescriptor {
27 pub fn with_format(mut self, format: wgpu::TextureFormat) -> Self {
28 self.format = format;
29 self
30 }
31
32 fn wgpu_descriptor(&self, mip_count: u32, render_target: bool) -> wgpu::TextureDescriptor<'_> {
33 wgpu::TextureDescriptor {
34 label: None,
35 size: wgpu::Extent3d {
36 width: self.size,
37 height: self.size,
38 depth_or_array_layers: 6,
39 },
40 mip_level_count: mip_count,
41 sample_count: 1,
42 dimension: wgpu::TextureDimension::D2,
43 format: self.format,
44 usage: if render_target {
45 wgpu::TextureUsages::TEXTURE_BINDING
46 | wgpu::TextureUsages::COPY_DST
47 | wgpu::TextureUsages::RENDER_ATTACHMENT
48 } else {
49 wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST
50 },
51 view_formats: &[],
52 }
53 }
54}
55
56pub struct GPUCubemap {
57 pub texture: wgpu::Texture,
58 pub view: wgpu::TextureView,
59}
60
61impl Asset<WGPUBackend> for GPUCubemap {
62 type Source = CubemapDescriptor;
63 type Deps<'a> = Res<'a, MipmapGenerator>;
64
65 fn upload<'a>(
66 source: &CubemapDescriptor,
67 backend: &WGPUBackend,
68 mipmap_generator: &Res<'a, MipmapGenerator>,
69 ) -> Option<Self> {
70 let faces: Option<[Vec<u8>; 6]> = if let Some(files) = &source.face_files {
71 let mut out: [Vec<u8>; 6] = Default::default();
72 for (i, path) in files.iter().enumerate() {
73 let (w, h, data) = decode_file(path, source.format);
74 if w != source.size || h != source.size {
75 tracing::error!(
76 "CubemapSpec: face {i} ('{path}') is {w}x{h}, expected {0}x{0}",
77 source.size
78 );
79 return None;
80 }
81 out[i] = data;
82 }
83 Some(out)
84 } else {
85 source.faces.clone()
86 };
87
88 let mip_count = if source.generate_mips {
89 (source.size as f32).log2().floor() as u32 + 1
90 } else {
91 1
92 };
93
94 let texture = backend
95 .device
96 .create_texture(&source.wgpu_descriptor(mip_count, faces.is_none()));
97
98 if let Some(faces) = &faces {
99 for (face, data) in faces.iter().enumerate() {
100 backend.queue.write_texture(
101 wgpu::TexelCopyTextureInfo {
102 texture: &texture,
103 mip_level: 0,
104 origin: wgpu::Origin3d {
105 x: 0,
106 y: 0,
107 z: face as u32,
108 },
109 aspect: wgpu::TextureAspect::All,
110 },
111 data,
112 wgpu::TexelCopyBufferLayout {
113 offset: 0,
114 bytes_per_row: Some(bytes_per_pixel(source.format) * source.size),
115 rows_per_image: Some(source.size),
116 },
117 wgpu::Extent3d {
118 width: source.size,
119 height: source.size,
120 depth_or_array_layers: 1,
121 },
122 );
123 }
124
125 if mip_count > 1 {
126 mipmap_generator.generate_mips(
127 &backend.device,
128 &backend.queue,
129 &texture,
130 source.format,
131 mip_count,
132 6,
133 );
134 }
135 }
136
137 let view = texture.create_view(&wgpu::TextureViewDescriptor {
138 dimension: Some(wgpu::TextureViewDimension::Cube),
139 ..Default::default()
140 });
141 Some(Self { texture, view })
142 }
143}
144
145pub struct CubemapPlugin;
146impl Plugin for CubemapPlugin {
147 fn build(&self, app: &mut crate::prelude::App) {
148 app.add_plugin(LazyResourcePlugin::<WGPUBackend, MipmapGenerator>::new());
149 app.add_plugin(AssetPlugin::<WGPUBackend, GPUCubemap>::new());
150 }
151}