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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
|
#![deny(unsafe_code)]
use crate::error::*;
use crate::graphics::Permanent;
use crate::graphics::util::{
stage_in_buffer, allocate_image, init_image_view,
begin_transient_commands, end_transient_commands
};
use std::io::Cursor;
use png::Decoder;
use vulkanalia::{ Device, Instance };
use vulkanalia::vk::{ self, HasBuilder, InstanceV1_0, DeviceV1_0 };
#[derive(Debug)]
pub struct Texture {
image: vk::Image,
image_memory: vk::DeviceMemory,
pub image_view: vk::ImageView,
}
impl Texture {
pub fn new(permanent: &Permanent) -> Result<(Self, u32)> {
let graphics_queue = &permanent.graphics_queue;
let instance = &permanent.instance;
let device = &permanent.device;
let transient_command_pool = &permanent.transient_command_pool;
let (image, image_memory, image_view, mip_count)
= init_texture(instance, device, graphics_queue,
&transient_command_pool)?;
Ok((Texture {
image,
image_memory,
image_view,
}, mip_count))
}
// This relies on its caller to have already waited for the device to be
// idle.
#[allow(unsafe_code)]
pub fn destroy(self, device: &Device) {
unsafe { device.destroy_image(self.image, None) };
unsafe { device.free_memory(self.image_memory, None) };
unsafe { device.destroy_image_view(self.image_view, None) };
}
}
#[allow(unsafe_code)]
fn init_texture(instance: &Instance, device: &Device, queue: &vk::Queue,
command_pool: &vk::CommandPool)
-> Result<(vk::Image, vk::DeviceMemory, vk::ImageView, u32)>
{
let physical_device = device.physical_device();
let png = include_bytes!("../../textures/forest_leaves_04_diff.png");
let decoder = Decoder::new(Cursor::new(png));
let mut reader = decoder.read_info()?;
let (width, height) = reader.info().size();
let format_properties = unsafe {
instance.get_physical_device_format_properties(physical_device,
vk::Format::R8G8B8A8_SRGB)
};
let has_linear_filter = format_properties
.optimal_tiling_features
.contains(vk::FormatFeatureFlags::SAMPLED_IMAGE_FILTER_LINEAR);
let mip_count = if has_linear_filter {
// This will generate mips all the way down to 1x1. It is not clear
// whether there's a benefit to that.
(width.max(height)).ilog2() + 1
} else {
1
};
let mut pixels = vec![0; reader.info().raw_bytes()];
reader.next_frame(&mut pixels)?;
let (staging_buffer, staging_memory, _byte_size)
= stage_in_buffer(instance, device, &pixels)?;
let (image, image_memory)
= allocate_image(instance, device,
width, height, mip_count, vk::SampleCountFlags::_1,
vk::Format::R8G8B8A8_SRGB,
vk::ImageTiling::OPTIMAL,
vk::ImageUsageFlags::SAMPLED
| vk::ImageUsageFlags::TRANSFER_SRC
| vk::ImageUsageFlags::TRANSFER_DST,
vk::MemoryPropertyFlags::DEVICE_LOCAL)?;
change_image_layout(device, queue, command_pool, &image, mip_count,
vk::ImageLayout::UNDEFINED,
vk::ImageLayout::TRANSFER_DST_OPTIMAL)?;
copy_buffer_to_image(device, queue, command_pool, &staging_buffer, &image,
width, height)?;
// This will also change the layout to SHADER_READ_ONLY_OPTIMAL.
fill_mip_levels(device, queue, command_pool, &image,
width, height, mip_count)?;
let view = init_image_view(device, &image, mip_count,
vk::Format::R8G8B8A8_SRGB,
vk::ImageAspectFlags::COLOR)?;
unsafe { device.destroy_buffer(staging_buffer, None) };
unsafe { device.free_memory(staging_memory, None) };
Ok((image, image_memory, view, mip_count))
}
#[allow(unsafe_code)]
fn change_image_layout(device: &Device, queue: &vk::Queue,
command_pool: &vk::CommandPool, image: &vk::Image,
mip_count: u32, old: vk::ImageLayout,
new: vk::ImageLayout)
-> Result<()>
{
let command_buffer = begin_transient_commands(device, command_pool)?;
let subresource_range = vk::ImageSubresourceRange::builder()
.aspect_mask(vk::ImageAspectFlags::COLOR)
.base_mip_level(0)
.level_count(mip_count)
.base_array_layer(0)
.layer_count(1);
// Notionally this is a property that our caller is in a better position
// to know than we are, but in practice the nature of the transition
// strongly implies a particular phase of the image's lifecycle, so we just
// compute it here.
let (source_access, source_stage, destination_access, destination_stage)
= match (old, new)
{
(vk::ImageLayout::UNDEFINED, vk::ImageLayout::TRANSFER_DST_OPTIMAL)
=> (vk::AccessFlags::empty(),
vk::PipelineStageFlags::TOP_OF_PIPE,
vk::AccessFlags::TRANSFER_WRITE,
vk::PipelineStageFlags::TRANSFER),
(vk::ImageLayout::TRANSFER_DST_OPTIMAL,
vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL)
=> (vk::AccessFlags::TRANSFER_WRITE,
vk::PipelineStageFlags::TRANSFER,
vk::AccessFlags::SHADER_READ,
vk::PipelineStageFlags::FRAGMENT_SHADER),
_ => return Err(Error {
message:
format!("Don't know how to change from image layout {:?} to {:?}",
old, new)
})
};
let barrier_info = vk::ImageMemoryBarrier::builder()
.image(*image)
.subresource_range(subresource_range)
.old_layout(old)
.new_layout(new)
.src_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
.dst_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
.src_access_mask(source_access)
.dst_access_mask(destination_access);
unsafe {
device.cmd_pipeline_barrier(command_buffer,
source_stage, destination_stage,
vk::DependencyFlags::empty(),
&[] as &[vk::MemoryBarrier],
&[] as &[vk::BufferMemoryBarrier],
&[barrier_info])
};
end_transient_commands(command_buffer, device, queue, command_pool)?;
Ok(())
}
#[allow(unsafe_code)]
fn copy_buffer_to_image(device: &Device, queue: &vk::Queue,
command_pool: &vk::CommandPool, source: &vk::Buffer,
destination: &vk::Image, width: u32, height: u32)
-> Result<()>
{
let command_buffer = begin_transient_commands(device, command_pool)?;
let subresource_layers = vk::ImageSubresourceLayers::builder()
.aspect_mask(vk::ImageAspectFlags::COLOR)
.mip_level(0)
.base_array_layer(0)
.layer_count(1);
let copy_info = vk::BufferImageCopy::builder()
.buffer_offset(0)
.buffer_row_length(0)
.buffer_image_height(0)
.image_subresource(subresource_layers)
.image_offset(vk::Offset3D { x: 0, y: 0, z: 0 })
.image_extent(vk::Extent3D { width, height, depth: 1 });
unsafe {
device.cmd_copy_buffer_to_image(command_buffer, *source, *destination,
vk::ImageLayout::TRANSFER_DST_OPTIMAL,
&[copy_info])
};
end_transient_commands(command_buffer, device, queue, command_pool)?;
Ok(())
}
// An Image can store multiple mip levels within it, as one of several kinds
// of subresource it has. We deal with this by
#[allow(unsafe_code)]
fn fill_mip_levels(device: &Device, queue: &vk::Queue,
command_pool: &vk::CommandPool, image: &vk::Image,
original_width: u32, original_height: u32,
mip_count: u32)
-> Result<()>
{
let command_buffer = begin_transient_commands(device, command_pool)?;
// We'll be mutating these two builders as we loop through the mip levels,
// because we need to construct a lot of similar things. Remember, the
// builder methods don't mutate in-place, they return a new builder; to
// avoid confusion we always assign that result back to the same variable.
let mut barrier_subresource_range = vk::ImageSubresourceRange::builder()
.aspect_mask(vk::ImageAspectFlags::COLOR)
.level_count(1)
.base_array_layer(0)
.layer_count(1);
let mut blit_barrier_info = vk::ImageMemoryBarrier::builder()
.image(*image)
.src_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
.dst_queue_family_index(vk::QUEUE_FAMILY_IGNORED);
// Now we loop through the mip levels from largest (low numbers) to
// smallest (high numbers). Conceptually, the only thing we're doing is a
// blit that copies each mip level from the one immediately before. Recall
// though that we don't just want to fill in the pixels, we also care about
// pixel format and memory sharing. There are additional operations to deal
// with that. These are best done together, as detailed below.
//
// This loop has a lot of code in it, so we make the "paragraphs" a little
// more dense than usual to make sure the logical grouping is clear.
let mut source_width = original_width;
let mut source_height = original_height;
for destination_mip_level in 1 .. mip_count {
let source_mip_level = destination_mip_level - 1;
let destination_width = (source_width / 2).max(1);
let destination_height = (source_height / 2).max(1);
// So. The name pipeline_barrier is a little misleading; it does indeed
// mean "barrier" in the concurrency sense, but it isn't just initiating
// a wait, it's also performing any needed mutation. We do one of them
// here, acting on this iteration's source level, to set it up for
// reading.
barrier_subresource_range = barrier_subresource_range
.base_mip_level(source_mip_level as u32);
blit_barrier_info = blit_barrier_info
.old_layout(vk::ImageLayout::TRANSFER_DST_OPTIMAL)
.new_layout(vk::ImageLayout::TRANSFER_SRC_OPTIMAL)
.src_access_mask(vk::AccessFlags::TRANSFER_WRITE)
.dst_access_mask(vk::AccessFlags::TRANSFER_READ)
.subresource_range(barrier_subresource_range);
unsafe {
device.cmd_pipeline_barrier(command_buffer,
vk::PipelineStageFlags::TRANSFER,
vk::PipelineStageFlags::TRANSFER,
vk::DependencyFlags::empty(),
&[] as &[vk::MemoryBarrier],
&[] as &[vk::BufferMemoryBarrier],
&[blit_barrier_info])
};
// Now we do the actual blit. Nice and easy, though specifying the
// coordinates is a bit verbose.
let blit_source_layer_info = vk::ImageSubresourceLayers::builder()
.aspect_mask(vk::ImageAspectFlags::COLOR)
.mip_level(source_mip_level as u32)
.base_array_layer(0)
.layer_count(1);
let blit_destination_layer_info = vk::ImageSubresourceLayers::builder()
.aspect_mask(vk::ImageAspectFlags::COLOR)
.mip_level(destination_mip_level as u32)
.base_array_layer(0)
.layer_count(1);
let blit_info = vk::ImageBlit::builder()
.src_offsets([vk::Offset3D { x: 0, y: 0, z: 0 },
vk::Offset3D {
x: source_width as i32,
y: source_height as i32,
z: 1
}])
.src_subresource(blit_source_layer_info)
.dst_offsets([vk::Offset3D { x: 0, y: 0, z: 0 },
vk::Offset3D {
x: destination_width as i32,
y: destination_height as i32,
z: 1
}])
.dst_subresource(blit_destination_layer_info);
unsafe {
device.cmd_blit_image(command_buffer,
*image, vk::ImageLayout::TRANSFER_SRC_OPTIMAL,
*image, vk::ImageLayout::TRANSFER_DST_OPTIMAL,
&[blit_info],
vk::Filter::LINEAR)
};
// Now we do another pipeline_barrier. We're still acting on this
// iteration's source level, not on the destination. We'll never need to
// use it again except from the shader, so we set it appropriately for
// that.
blit_barrier_info = blit_barrier_info
.old_layout(vk::ImageLayout::TRANSFER_SRC_OPTIMAL)
.new_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL)
.src_access_mask(vk::AccessFlags::TRANSFER_READ)
.dst_access_mask(vk::AccessFlags::SHADER_READ);
unsafe {
device.cmd_pipeline_barrier(command_buffer,
vk::PipelineStageFlags::TRANSFER,
vk::PipelineStageFlags::FRAGMENT_SHADER,
vk::DependencyFlags::empty(),
&[] as &[vk::MemoryBarrier],
&[] as &[vk::BufferMemoryBarrier],
&[blit_barrier_info])
};
source_width = destination_width;
source_height = destination_height;
}
let final_mip_level = mip_count - 1;
// We need to do one final pipeline_barrier, because the loop didn't do it
// to the smallest (last) mip level. We change it to have the same settings
// the loop left the rest of them in. The barrier source properties for this
// barrier are different from the others because this level was never useds
// as a blit source, only as a blit destination. The barrier destination
// properties are the same as the rest, so after this all the subresourcess
// will be in their fully-ready state.
barrier_subresource_range = barrier_subresource_range
.base_mip_level(final_mip_level as u32);
blit_barrier_info = blit_barrier_info
.old_layout(vk::ImageLayout::TRANSFER_DST_OPTIMAL)
.new_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL)
.src_access_mask(vk::AccessFlags::TRANSFER_WRITE)
.dst_access_mask(vk::AccessFlags::SHADER_READ)
.subresource_range(barrier_subresource_range);
unsafe {
device.cmd_pipeline_barrier(command_buffer,
vk::PipelineStageFlags::TRANSFER,
vk::PipelineStageFlags::FRAGMENT_SHADER,
vk::DependencyFlags::empty(),
&[] as &[vk::MemoryBarrier],
&[] as &[vk::BufferMemoryBarrier],
&[blit_barrier_info])
};
end_transient_commands(command_buffer, device, queue, command_pool)?;
Ok(())
}
|