1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
pub struct CubemapSpec {
pub size: u32, // cubemaps are square per face
pub format: wgpu::TextureFormat,
/// `Some` uploads 6 faces of pixel data up front (wgpu's expected
/// order: +X, -X, +Y, -Y, +Z, -Z). `None` allocates an empty cubemap
/// meant to be filled later by rendering into per-face views — e.g. an
/// environment-map capture pass — in which case [`wgpu_descriptor`](Self::wgpu_descriptor)
/// adds `RENDER_ATTACHMENT` usage instead of requiring upload data.
pub faces: Option<[Vec<u8>; 6]>,
}
impl CubemapSpec {
pub fn wgpu_descriptor(&self) -> wgpu::TextureDescriptor<'static> {
let usage = match self.faces {
Some(_) => wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
None => {
wgpu::TextureUsages::TEXTURE_BINDING
| wgpu::TextureUsages::COPY_DST
| wgpu::TextureUsages::RENDER_ATTACHMENT
}
};
wgpu::TextureDescriptor {
label: None,
size: wgpu::Extent3d {
width: self.size,
height: self.size,
depth_or_array_layers: 6,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: self.format,
usage,
view_formats: &[],
}
}
/// The one part that's easy to get wrong by hand: a cubemap's VIEW
/// must be created with `TextureViewDimension::Cube`, not the
/// default. This is the view you sample from; use
/// [`face_view_descriptor`](Self::face_view_descriptor) to get a
/// single-layer `D2` view for rendering into one face.
pub fn view_descriptor(&self) -> wgpu::TextureViewDescriptor<'static> {
wgpu::TextureViewDescriptor {
dimension: Some(wgpu::TextureViewDimension::Cube),
array_layer_count: Some(6),
..Default::default()
}
}
/// A single-face `D2` view over layer `face` (0..6, wgpu's +X, -X, +Y,
/// -Y, +Z, -Z order) — what you attach as a `RenderPassColorAttachment`
/// to draw into that one face of an [`empty`](Self::empty) cubemap.
pub fn face_view_descriptor(&self, face: u32) -> wgpu::TextureViewDescriptor<'static> {
wgpu::TextureViewDescriptor {
dimension: Some(wgpu::TextureViewDimension::D2),
base_array_layer: face,
array_layer_count: Some(1),
..Default::default()
}
}
/// Create the texture and, if [`faces`](Self::faces) is `Some`, upload
/// all 6 faces. Returns the texture and a `Cube`-dimension view over it
/// (suitable for sampling). For an [`empty`](Self::empty) cubemap,
/// create per-face views yourself with [`face_view_descriptor`](Self::face_view_descriptor)
/// to render into it.
pub fn upload(&self, device: &wgpu::Device, queue: &wgpu::Queue) -> (wgpu::Texture, wgpu::TextureView) {
let texture = device.create_texture(&self.wgpu_descriptor());
if let Some(faces) = &self.faces {
for (face, data) in faces.iter().enumerate() {
queue.write_texture(
wgpu::TexelCopyTextureInfo {
texture: &texture,
mip_level: 0,
origin: wgpu::Origin3d {
x: 0,
y: 0,
z: face as u32,
},
aspect: wgpu::TextureAspect::All,
},
data,
wgpu::TexelCopyBufferLayout {
offset: 0,
bytes_per_row: Some(4 * self.size),
rows_per_image: Some(self.size),
},
wgpu::Extent3d {
width: self.size,
height: self.size,
depth_or_array_layers: 1,
},
);
}
}
let view = texture.create_view(&self.view_descriptor());
(texture, view)
}
}
impl CubemapSpec {
pub fn new(size: u32, faces: [Vec<u8>; 6]) -> Self {
Self {
size,
format: wgpu::TextureFormat::Rgba8Unorm,
faces: Some(faces),
}
}
/// An empty cubemap with no data uploaded — allocated with
/// `RENDER_ATTACHMENT` usage so it can be filled later by rendering
/// into each face (via [`face_view_descriptor`](Self::face_view_descriptor)),
/// e.g. an environment-map capture pass.
pub fn empty(size: u32) -> Self {
Self {
size,
format: wgpu::TextureFormat::Rgba8Unorm,
faces: None,
}
}
pub fn with_format(mut self, format: wgpu::TextureFormat) -> Self {
self.format = format;
self
}
}