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