[Vulkano-1441] Adding the possibility to generate mipmaps at image (#1451)

creation

To do so, we created a SubImage that implements the ImageAccess trait
This SubImage will decorate an ImageAccess image and will represent
one or more mip level / array layer level
This commit is contained in:
qnope 2020-12-19 04:08:48 +01:00 committed by GitHub
parent d8f1e77502
commit d90106d4df
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 321 additions and 18 deletions

View File

@ -1,5 +1,6 @@
# Unreleased
- **Breaking** The `ImmutableImage::from_iter` and `ImmutableImage::from_buffer` can build Mipmaps
- **Breaking** `CpuAccessibleBuffer` now uses `RwLock` from `parking_lot`.
- **Breaking** The `Kind` and `SubpassContents` types have been moved to the root of the `command_buffer` module.
- **Breaking** On `AutoCommandBufferBuilder`, the methods `begin_render_pass` and `next_subpass` now take `SubpassContents` instead of a boolean value.

View File

@ -11,6 +11,7 @@ use vulkano::device::{Device, DeviceExtensions};
use vulkano::format::Format;
use vulkano::image::Dimensions;
use vulkano::image::ImmutableImage;
use vulkano::image::MipmapsCount;
use vulkano::instance;
use vulkano::instance::debug::{DebugCallback, MessageSeverity, MessageType};
use vulkano::instance::{Instance, InstanceExtensions, PhysicalDevice};
@ -130,6 +131,7 @@ fn main() {
let _ = ImmutableImage::from_iter(
DATA.iter().cloned(),
dimensions,
MipmapsCount::One,
pixel_format,
queue.clone(),
)

View File

@ -13,7 +13,7 @@ use vulkano::descriptor::descriptor_set::PersistentDescriptorSet;
use vulkano::device::{Device, DeviceExtensions};
use vulkano::format::Format;
use vulkano::framebuffer::{Framebuffer, FramebufferAbstract, RenderPassAbstract, Subpass};
use vulkano::image::{Dimensions, ImageUsage, ImmutableImage, SwapchainImage};
use vulkano::image::{Dimensions, ImageUsage, ImmutableImage, MipmapsCount, SwapchainImage};
use vulkano::instance::{Instance, PhysicalDevice};
use vulkano::pipeline::viewport::Viewport;
use vulkano::pipeline::GraphicsPipeline;
@ -163,6 +163,7 @@ fn main() {
ImmutableImage::from_iter(
image_data.iter().cloned(),
dimensions,
MipmapsCount::One,
Format::R8G8B8A8Srgb,
queue.clone(),
)

View File

@ -321,7 +321,13 @@ impl<P> Hash for BuilderKey<P> {
}
KeyTy::Image => {
let c = &commands_lock.commands[self.command_ids.borrow()[0]];
c.image(self.resource_index).conflict_key().hash(state)
c.image(self.resource_index).conflict_key().hash(state);
c.image(self.resource_index)
.current_miplevels_access()
.hash(state);
c.image(self.resource_index)
.current_layer_levels_access()
.hash(state);
}
}
}
@ -557,6 +563,7 @@ impl<P> SyncCommandBufferBuilder<P> {
{
let cmd1 = &commands_lock.commands[*collision_cmd_id];
let cmd2 = &commands_lock.commands[latest_command_id];
return Err(SyncCommandBufferBuilderError::Conflict {
command1_name: cmd1.name(),
command1_param: match entry_key_resource_ty {
@ -617,8 +624,8 @@ impl<P> SyncCommandBufferBuilder<P> {
let b = &mut self.pending_barrier;
b.add_image_memory_barrier(
img,
0..img.mipmap_levels(),
0..img.dimensions().array_layers(),
img.current_miplevels_access(),
img.current_layer_levels_access(),
entry.stages,
entry.access,
stages,
@ -701,8 +708,8 @@ impl<P> SyncCommandBufferBuilder<P> {
let b = &mut self.pending_barrier;
b.add_image_memory_barrier(
img,
0..img.mipmap_levels(),
0..img.dimensions().array_layers(),
img.current_miplevels_access(),
img.current_layer_levels_access(),
PipelineStages {
bottom_of_pipe: true,
..PipelineStages::none()
@ -774,8 +781,8 @@ impl<P> SyncCommandBufferBuilder<P> {
barrier.add_image_memory_barrier(
img,
0..img.mipmap_levels(),
0..img.dimensions().array_layers(),
img.current_miplevels_access(),
img.current_layer_levels_access(),
state.stages,
state.access,
PipelineStages {

View File

@ -583,6 +583,16 @@ where
fn is_layout_initialized(&self) -> bool {
self.initialized.load(Ordering::SeqCst)
}
#[inline]
fn current_miplevels_access(&self) -> std::ops::Range<u32> {
0..self.mipmap_levels()
}
#[inline]
fn current_layer_levels_access(&self) -> std::ops::Range<u32> {
0..1
}
}
unsafe impl<F, A> ImageClearValue<F::ClearValue> for Arc<AttachmentImage<F, A>>

View File

@ -47,6 +47,7 @@ use memory::pool::MemoryPoolAlloc;
use memory::pool::PotentialDedicatedAllocation;
use memory::pool::StdMemoryPoolAlloc;
use memory::DedicatedAlloc;
use sampler::Filter;
use sync::AccessError;
use sync::NowFuture;
use sync::Sharing;
@ -65,10 +66,110 @@ pub struct ImmutableImage<F, A = PotentialDedicatedAllocation<StdMemoryPoolAlloc
layout: ImageLayout,
}
/// Image whose purpose is to access only a part of one image, for any kind of access
/// We define a part of one image here by a level of mipmap, or a layer of an array
/// The image attribute must be an implementation of ImageAccess
/// The mip_levels_access must be a range showing which mipmaps will be accessed
/// The layer_levels_access must be a range showing which layers will be accessed
/// The layout must be the layout of the image at the beginning and at the end of the command buffer
pub struct SubImage {
image: Arc<dyn ImageAccess + Sync + Send>,
mip_levels_access: std::ops::Range<u32>,
layer_levels_access: std::ops::Range<u32>,
layout: ImageLayout,
}
impl SubImage
{
pub fn new(
image: Arc<dyn ImageAccess + Sync + Send>,
mip_level: u32,
mip_level_count: u32,
layer_level: u32,
layer_level_count: u32,
layout: ImageLayout,
) -> Arc<SubImage> {
debug_assert!(mip_level + mip_level_count <= image.mipmap_levels());
debug_assert!(layer_level + layer_level_count <= image.dimensions().array_layers());
let last_level = mip_level + mip_level_count;
let mip_levels_access = mip_level..last_level;
let last_level = layer_level + layer_level_count;
let layer_levels_access = layer_level..last_level;
Arc::new(SubImage {
image,
mip_levels_access,
layer_levels_access,
layout: ImageLayout::ShaderReadOnlyOptimal,
})
}
}
// Must not implement Clone, as that would lead to multiple `used` values.
pub struct ImmutableImageInitialization<F, A = PotentialDedicatedAllocation<StdMemoryPoolAlloc>> {
image: Arc<ImmutableImage<F, A>>,
used: AtomicBool,
mip_levels_access: std::ops::Range<u32>,
layer_levels_access: std::ops::Range<u32>,
}
fn has_mipmaps(mipmaps: MipmapsCount) -> bool {
match mipmaps {
MipmapsCount::One => false,
MipmapsCount::Log2 => true,
MipmapsCount::Specific(x) => x > 1
}
}
fn generate_mipmaps<Img>(
cbb: &mut AutoCommandBufferBuilder,
image: Arc<Img>,
dimensions: Dimensions,
layout: ImageLayout,
) where
Img: ImageAccess + Send + Sync + 'static,
{
let img_dim = dimensions.to_image_dimensions();
for level in 1..image.mipmap_levels() {
let [xs, ys, ds] = img_dim
.mipmap_dimensions(level - 1)
.unwrap()
.width_height_depth();
let [xd, yd, dd] = img_dim
.mipmap_dimensions(level)
.unwrap()
.width_height_depth();
let src = SubImage::new(
image.clone(),
level - 1,
1,
0,
img_dim.array_layers(),
layout,
);
let dst = SubImage::new(image.clone(), level, 1, 0, img_dim.array_layers(), layout);
cbb.blit_image(
src, //source
[0, 0, 0], //source_top_left
[xs as i32, ys as i32, ds as i32], //source_bottom_right
0, //source_base_array_layer
level - 1, //source_mip_level
dst, //destination
[0, 0, 0], //destination_top_left
[xd as i32, yd as i32, dd as i32], //destination_bottom_right
0, //destination_base_array_layer
level, //destination_mip_level
1, //layer_count
Filter::Linear, //filter
)
.expect("failed to blit a mip map to image!");
}
}
impl<F> ImmutableImage<F> {
@ -211,18 +312,19 @@ impl<F> ImmutableImage<F> {
let init = ImmutableImageInitialization {
image: image.clone(),
used: AtomicBool::new(false),
mip_levels_access: 0..image.mipmap_levels(),
layer_levels_access: 0..image.dimensions().array_layers(),
};
Ok((image, init))
}
/// Construct an ImmutableImage from the contents of `iter`.
///
/// TODO: Support mipmaps
#[inline]
pub fn from_iter<P, I>(
iter: I,
dimensions: Dimensions,
mipmaps: MipmapsCount,
format: F,
queue: Arc<Queue>,
) -> Result<
@ -244,15 +346,14 @@ impl<F> ImmutableImage<F> {
false,
iter,
)?;
ImmutableImage::from_buffer(source, dimensions, format, queue)
ImmutableImage::from_buffer(source, dimensions, mipmaps, format, queue)
}
/// Construct an ImmutableImage containing a copy of the data in `source`.
///
/// TODO: Support mipmaps
pub fn from_buffer<B, P>(
source: B,
dimensions: Dimensions,
mipmaps: MipmapsCount,
format: F,
queue: Arc<Queue>,
) -> Result<
@ -268,23 +369,34 @@ impl<F> ImmutableImage<F> {
F: FormatDesc + AcceptsPixels<P> + 'static + Send + Sync,
Format: AcceptsPixels<P>,
{
let need_to_generate_mipmaps = has_mipmaps(mipmaps);
let usage = ImageUsage {
transfer_destination: true,
transfer_source: need_to_generate_mipmaps,
sampled: true,
..ImageUsage::none()
};
let layout = ImageLayout::ShaderReadOnlyOptimal;
let (buffer, init) = ImmutableImage::uninitialized(
let (image, initializer) = ImmutableImage::uninitialized(
source.device().clone(),
dimensions,
format,
MipmapsCount::One,
mipmaps,
usage,
layout,
source.device().active_queue_families(),
)?;
let init = SubImage::new(
Arc::new(initializer),
0,
1,
0,
1,
ImageLayout::ShaderReadOnlyOptimal,
);
let mut cbb = AutoCommandBufferBuilder::new(source.device().clone(), queue.family())?;
cbb.copy_buffer_to_image_dimensions(
source,
@ -296,14 +408,26 @@ impl<F> ImmutableImage<F> {
0,
)
.unwrap();
if need_to_generate_mipmaps {
generate_mipmaps(
&mut cbb,
image.clone(),
image.dimensions,
ImageLayout::ShaderReadOnlyOptimal,
);
}
let cb = cbb.build().unwrap();
let future = match cb.execute(queue) {
Ok(f) => f,
Err(_) => unreachable!(),
Err(e) => unreachable!("{:?}", e)
};
Ok((buffer, future))
image.initialized.store(true, Ordering::Relaxed);
Ok((image, future))
}
}
@ -392,6 +516,16 @@ where
unsafe fn unlock(&self, new_layout: Option<ImageLayout>) {
debug_assert!(new_layout.is_none());
}
#[inline]
fn current_miplevels_access(&self) -> std::ops::Range<u32> {
0..self.mipmap_levels()
}
#[inline]
fn current_layer_levels_access(&self) -> std::ops::Range<u32> {
0..self.dimensions().array_layers()
}
}
unsafe impl<P, F, A> ImageContent<P> for ImmutableImage<F, A>
@ -449,6 +583,75 @@ where
}
}
unsafe impl ImageAccess for SubImage
{
#[inline]
fn inner(&self) -> ImageInner {
self.image.inner()
}
#[inline]
fn initial_layout_requirement(&self) -> ImageLayout {
self.image.initial_layout_requirement()
}
#[inline]
fn final_layout_requirement(&self) -> ImageLayout {
self.image.final_layout_requirement()
}
#[inline]
fn conflicts_buffer(&self, other: &dyn BufferAccess) -> bool {
false
}
#[inline]
fn conflicts_image(&self, other: &dyn ImageAccess) -> bool {
self.conflict_key() == other.conflict_key()
&& self.current_miplevels_access() == other.current_miplevels_access()
&& self.current_layer_levels_access() == other.current_layer_levels_access()
}
fn current_miplevels_access(&self) -> std::ops::Range<u32> {
self.mip_levels_access.clone()
}
fn current_layer_levels_access(&self) -> std::ops::Range<u32> {
self.layer_levels_access.clone()
}
#[inline]
fn conflict_key(&self) -> u64 {
self.image.conflict_key()
}
#[inline]
fn try_gpu_lock(
&self,
exclusive_access: bool,
expected_layout: ImageLayout,
) -> Result<(), AccessError> {
if expected_layout != self.layout && expected_layout != ImageLayout::Undefined {
return Err(AccessError::UnexpectedImageLayout {
requested: expected_layout,
allowed: self.layout,
});
}
Ok(())
}
#[inline]
unsafe fn increase_gpu_lock(&self) {
self.image.increase_gpu_lock()
}
#[inline]
unsafe fn unlock(&self, new_layout: Option<ImageLayout>) {
self.image.unlock(new_layout)
}
}
impl<F, A> PartialEq for ImmutableImage<F, A>
where
F: 'static + Send + Sync,
@ -536,6 +739,16 @@ where
assert_eq!(new_layout, Some(self.image.layout));
self.image.initialized.store(true, Ordering::Relaxed);
}
#[inline]
fn current_miplevels_access(&self) -> std::ops::Range<u32> {
self.mip_levels_access.clone()
}
#[inline]
fn current_layer_levels_access(&self) -> std::ops::Range<u32> {
self.layer_levels_access.clone()
}
}
impl<F, A> PartialEq for ImmutableImageInitialization<F, A>

View File

@ -685,6 +685,10 @@ impl ImageDimensions {
#[cfg(test)]
mod tests {
use image::ImageDimensions;
use image::ImmutableImage;
use image::Dimensions;
use image::MipmapsCount;
use format;
#[test]
fn max_mipmaps() {
@ -797,4 +801,27 @@ mod tests {
);
assert_eq!(dims.mipmap_dimensions(9), None);
}
}
#[test]
fn mipmap_working_immutable_image() {
let (device, queue) = gfx_dev_and_queue!();
let dimensions = Dimensions::Dim2d{width: 512, height: 512};
{
let mut vec = Vec::new();
vec.resize(512 * 512, 0u8);
let (image, _) = ImmutableImage::from_iter(vec.into_iter(), dimensions, MipmapsCount::One, format::R8Unorm, queue.clone()).unwrap();
assert_eq!(image.mipmap_levels(), 1);
}
{
let mut vec = Vec::new();
vec.resize(512 * 512, 0u8);
let (image, _) = ImmutableImage::from_iter(vec.into_iter(), dimensions, MipmapsCount::Log2, format::R8Unorm, queue.clone()).unwrap();
assert_eq!(image.mipmap_levels(), 10);
}
}
}

View File

@ -264,6 +264,16 @@ where
assert!(new_layout.is_none() || new_layout == Some(ImageLayout::General));
self.gpu_lock.fetch_sub(1, Ordering::SeqCst);
}
#[inline]
fn current_miplevels_access(&self) -> std::ops::Range<u32> {
0..self.mipmap_levels()
}
#[inline]
fn current_layer_levels_access(&self) -> std::ops::Range<u32> {
0..self.dimensions().array_layers()
}
}
unsafe impl<F, A> ImageClearValue<F::ClearValue> for StorageImage<F, A>

View File

@ -157,6 +157,16 @@ unsafe impl<W> ImageAccess for SwapchainImage<W> {
unsafe fn unlock(&self, _: Option<ImageLayout>) {
// TODO: store that the image was initialized
}
#[inline]
fn current_miplevels_access(&self) -> std::ops::Range<u32> {
0..self.mipmap_levels()
}
#[inline]
fn current_layer_levels_access(&self) -> std::ops::Range<u32> {
0..1
}
}
unsafe impl<W> ImageClearValue<<Format as FormatDesc>::ClearValue> for SwapchainImage<W> {

View File

@ -187,6 +187,12 @@ pub unsafe trait ImageAccess {
/// verify whether they actually overlap.
fn conflict_key(&self) -> u64;
/// Returns the current mip level that is accessed by the gpu
fn current_miplevels_access(&self) -> std::ops::Range<u32>;
/// Returns the current layer level that is accessed by the gpu
fn current_layer_levels_access(&self) -> std::ops::Range<u32>;
/// Locks the resource for usage on the GPU. Returns an error if the lock can't be acquired.
///
/// After this function returns `Ok`, you are authorized to use the image on the GPU. If the
@ -324,6 +330,14 @@ where
fn is_layout_initialized(&self) -> bool {
(**self).is_layout_initialized()
}
fn current_miplevels_access(&self) -> std::ops::Range<u32> {
(**self).current_miplevels_access()
}
fn current_layer_levels_access(&self) -> std::ops::Range<u32> {
(**self).current_layer_levels_access()
}
}
impl PartialEq for dyn ImageAccess + Send + Sync {
@ -406,6 +420,14 @@ where
unsafe fn unlock(&self, new_layout: Option<ImageLayout>) {
self.image.unlock(new_layout)
}
fn current_miplevels_access(&self) -> std::ops::Range<u32> {
self.image.current_miplevels_access()
}
fn current_layer_levels_access(&self) -> std::ops::Range<u32> {
self.image.current_layer_levels_access()
}
}
impl<I> PartialEq for ImageAccessFromUndefinedLayout<I>