Towards wiring up images to k4

This patch passes a dynamically sized array of textures to the fine
rasterizer.

A bunch of the low level Vulkan stuff is done, but only enough of the
shaders and encoders to do minimal testing. We'll want to switch from
storage images to sampled images, track the actual array of textures
during encoding, use that to build the descriptor set (which will need
to be more dynamic), and of course run image elements through the
pipeline.

Progress towards #38
This commit is contained in:
Raph Levien 2020-11-18 15:54:11 -08:00
parent 6b06d249ab
commit 047a0830d1
6 changed files with 144 additions and 15 deletions

View file

@ -324,6 +324,12 @@ impl PipelineBuilder {
self self
} }
/// Add a binding with a variable-size array of textures.
pub fn add_textures(mut self, max_textures: u32) -> Self {
self.0.add_textures(max_textures);
self
}
pub unsafe fn create_compute_pipeline( pub unsafe fn create_compute_pipeline(
self, self,
session: &Session, session: &Session,
@ -349,6 +355,12 @@ impl DescriptorSetBuilder {
self self
} }
pub fn add_textures<'a>(mut self, images: impl IntoRefs<'a, Image>) -> Self {
let vk_images = images.into_refs().map(|i| i.vk_image()).collect::<Vec<_>>();
self.0.add_textures(&vk_images);
self
}
pub unsafe fn build( pub unsafe fn build(
self, self,
session: &Session, session: &Session,

View file

@ -212,11 +212,18 @@ pub trait PipelineBuilder<D: Device> {
fn add_buffers(&mut self, n_buffers: u32); fn add_buffers(&mut self, n_buffers: u32);
/// Add storage images to the pipeline. Each has its own binding. /// Add storage images to the pipeline. Each has its own binding.
fn add_images(&mut self, n_images: u32); fn add_images(&mut self, n_images: u32);
/// Add a binding with a variable-size array of textures.
fn add_textures(&mut self, max_textures: u32);
unsafe fn create_compute_pipeline(self, device: &D, code: &[u8]) -> Result<D::Pipeline, Error>; unsafe fn create_compute_pipeline(self, device: &D, code: &[u8]) -> Result<D::Pipeline, Error>;
} }
/// A builder for descriptor sets with more complex layouts.
///
/// Note: the order needs to match the pipeline building, and it also needs to
/// be buffers, then images, then textures.
pub trait DescriptorSetBuilder<D: Device> { pub trait DescriptorSetBuilder<D: Device> {
fn add_buffers(&mut self, buffers: &[&D::Buffer]); fn add_buffers(&mut self, buffers: &[&D::Buffer]);
fn add_images(&mut self, images: &[&D::Image]); fn add_images(&mut self, images: &[&D::Image]);
fn add_textures(&mut self, images: &[&D::Image]);
unsafe fn build(self, device: &D, pipeline: &D::Pipeline) -> Result<D::DescriptorSet, Error>; unsafe fn build(self, device: &D, pipeline: &D::Pipeline) -> Result<D::DescriptorSet, Error>;
} }

View file

@ -71,6 +71,7 @@ pub struct Pipeline {
pipeline: vk::Pipeline, pipeline: vk::Pipeline,
descriptor_set_layout: vk::DescriptorSetLayout, descriptor_set_layout: vk::DescriptorSetLayout,
pipeline_layout: vk::PipelineLayout, pipeline_layout: vk::PipelineLayout,
max_textures: u32,
} }
pub struct DescriptorSet { pub struct DescriptorSet {
@ -92,11 +93,14 @@ pub struct MemFlags(vk::MemoryPropertyFlags);
pub struct PipelineBuilder { pub struct PipelineBuilder {
bindings: Vec<vk::DescriptorSetLayoutBinding>, bindings: Vec<vk::DescriptorSetLayoutBinding>,
binding_flags: Vec<vk::DescriptorBindingFlags>,
max_textures: u32,
} }
pub struct DescriptorSetBuilder { pub struct DescriptorSetBuilder {
buffers: Vec<vk::Buffer>, buffers: Vec<vk::Buffer>,
images: Vec<vk::ImageView>, images: Vec<vk::ImageView>,
textures: Vec<vk::ImageView>,
} }
unsafe extern "system" fn vulkan_debug_callback( unsafe extern "system" fn vulkan_debug_callback(
@ -141,6 +145,8 @@ static EXTS: Lazy<Vec<&'static CStr>> = Lazy::new(|| {
if cfg!(debug_assertions) { if cfg!(debug_assertions) {
exts.push(DebugUtils::name()); exts.push(DebugUtils::name());
} }
// We'll need this to do runtime query of descriptor indexing.
//exts.push(vk::KhrGetPhysicalDeviceProperties2Fn::name());
exts exts
}); });
@ -270,13 +276,22 @@ impl VkInstance {
.queue_family_index(qfi) .queue_family_index(qfi)
.queue_priorities(&queue_priorities) .queue_priorities(&queue_priorities)
.build()]; .build()];
// support for descriptor indexing (maybe should be optional for compatibility)
let descriptor_indexing = vk::PhysicalDeviceDescriptorIndexingFeatures::builder()
.descriptor_binding_variable_descriptor_count(true)
.runtime_descriptor_array(true);
let extensions = match surface { let extensions = match surface {
Some(_) => vec![khr::Swapchain::name().as_ptr()], Some(_) => vec![khr::Swapchain::name().as_ptr()],
None => vec![], None => vec![],
}; };
//extensions.push(vk::KhrMaintenance3Fn::name().as_ptr());
//extensions.push(vk::ExtDescriptorIndexingFn::name().as_ptr());
let create_info = vk::DeviceCreateInfo::builder() let create_info = vk::DeviceCreateInfo::builder()
.queue_create_infos(&queue_create_infos) .queue_create_infos(&queue_create_infos)
.enabled_extension_names(&extensions) .enabled_extension_names(&extensions)
.push_next(&mut descriptor_indexing.build())
.build(); .build();
let device = self.instance.create_device(pdevice, &create_info, None)?; let device = self.instance.create_device(pdevice, &create_info, None)?;
@ -541,6 +556,8 @@ impl crate::Device for VkDevice {
unsafe fn pipeline_builder(&self) -> PipelineBuilder { unsafe fn pipeline_builder(&self) -> PipelineBuilder {
PipelineBuilder { PipelineBuilder {
bindings: Vec::new(), bindings: Vec::new(),
binding_flags: Vec::new(),
max_textures: 0,
} }
} }
@ -548,6 +565,7 @@ impl crate::Device for VkDevice {
DescriptorSetBuilder { DescriptorSetBuilder {
buffers: Vec::new(), buffers: Vec::new(),
images: Vec::new(), images: Vec::new(),
textures: Vec::new(),
} }
} }
@ -922,6 +940,8 @@ impl crate::PipelineBuilder<VkDevice> for PipelineBuilder {
.stage_flags(vk::ShaderStageFlags::COMPUTE) .stage_flags(vk::ShaderStageFlags::COMPUTE)
.build(), .build(),
); );
self.binding_flags
.push(vk::DescriptorBindingFlags::default());
} }
} }
@ -936,9 +956,27 @@ impl crate::PipelineBuilder<VkDevice> for PipelineBuilder {
.stage_flags(vk::ShaderStageFlags::COMPUTE) .stage_flags(vk::ShaderStageFlags::COMPUTE)
.build(), .build(),
); );
self.binding_flags
.push(vk::DescriptorBindingFlags::default());
} }
} }
fn add_textures(&mut self, max_textures: u32) {
let start = self.bindings.len() as u32;
self.bindings.push(
vk::DescriptorSetLayoutBinding::builder()
.binding(start)
// TODO: we do want these to be sampled images
.descriptor_type(vk::DescriptorType::STORAGE_IMAGE)
.descriptor_count(max_textures)
.stage_flags(vk::ShaderStageFlags::COMPUTE)
.build(),
);
self.binding_flags
.push(vk::DescriptorBindingFlags::VARIABLE_DESCRIPTOR_COUNT);
self.max_textures += max_textures;
}
unsafe fn create_compute_pipeline( unsafe fn create_compute_pipeline(
self, self,
device: &VkDevice, device: &VkDevice,
@ -946,7 +984,14 @@ impl crate::PipelineBuilder<VkDevice> for PipelineBuilder {
) -> Result<Pipeline, Error> { ) -> Result<Pipeline, Error> {
let device = &device.device.device; let device = &device.device.device;
let descriptor_set_layout = device.create_descriptor_set_layout( let descriptor_set_layout = device.create_descriptor_set_layout(
&vk::DescriptorSetLayoutCreateInfo::builder().bindings(&self.bindings), &vk::DescriptorSetLayoutCreateInfo::builder()
.bindings(&self.bindings)
// It might be a slight optimization not to push this if max_textures = 0
.push_next(
&mut vk::DescriptorSetLayoutBindingFlagsCreateInfo::builder()
.binding_flags(&self.binding_flags)
.build(),
),
None, None,
)?; )?;
let descriptor_set_layouts = [descriptor_set_layout]; let descriptor_set_layouts = [descriptor_set_layout];
@ -981,6 +1026,7 @@ impl crate::PipelineBuilder<VkDevice> for PipelineBuilder {
pipeline, pipeline,
pipeline_layout, pipeline_layout,
descriptor_set_layout, descriptor_set_layout,
max_textures: self.max_textures,
}) })
} }
} }
@ -994,6 +1040,10 @@ impl crate::DescriptorSetBuilder<VkDevice> for DescriptorSetBuilder {
self.images.extend(images.iter().map(|i| i.image_view)); self.images.extend(images.iter().map(|i| i.image_view));
} }
fn add_textures(&mut self, images: &[&Image]) {
self.textures.extend(images.iter().map(|i| i.image_view));
}
unsafe fn build(self, device: &VkDevice, pipeline: &Pipeline) -> Result<DescriptorSet, Error> { unsafe fn build(self, device: &VkDevice, pipeline: &Pipeline) -> Result<DescriptorSet, Error> {
let device = &device.device.device; let device = &device.device.device;
let mut descriptor_pool_sizes = Vec::new(); let mut descriptor_pool_sizes = Vec::new();
@ -1005,11 +1055,12 @@ impl crate::DescriptorSetBuilder<VkDevice> for DescriptorSetBuilder {
.build(), .build(),
); );
} }
if !self.images.is_empty() { let n_images_total = self.images.len() + pipeline.max_textures as usize;
if n_images_total > 0 {
descriptor_pool_sizes.push( descriptor_pool_sizes.push(
vk::DescriptorPoolSize::builder() vk::DescriptorPoolSize::builder()
.ty(vk::DescriptorType::STORAGE_IMAGE) .ty(vk::DescriptorType::STORAGE_IMAGE)
.descriptor_count(self.images.len() as u32) .descriptor_count(n_images_total as u32)
.build(), .build(),
); );
} }
@ -1030,39 +1081,60 @@ impl crate::DescriptorSetBuilder<VkDevice> for DescriptorSetBuilder {
let mut binding = 0; let mut binding = 0;
// Maybe one call to update_descriptor_sets with an array of descriptor_writes? // Maybe one call to update_descriptor_sets with an array of descriptor_writes?
for buf in &self.buffers { for buf in &self.buffers {
let buf_info = vk::DescriptorBufferInfo::builder()
.buffer(*buf)
.offset(0)
.range(vk::WHOLE_SIZE)
.build();
device.update_descriptor_sets( device.update_descriptor_sets(
&[vk::WriteDescriptorSet::builder() &[vk::WriteDescriptorSet::builder()
.dst_set(descriptor_sets[0]) .dst_set(descriptor_sets[0])
.dst_binding(binding) .dst_binding(binding)
.descriptor_type(vk::DescriptorType::STORAGE_BUFFER) .descriptor_type(vk::DescriptorType::STORAGE_BUFFER)
.buffer_info(&[buf_info]) .buffer_info(&[vk::DescriptorBufferInfo::builder()
.buffer(*buf)
.offset(0)
.range(vk::WHOLE_SIZE)
.build()])
.build()], .build()],
&[], &[],
); );
binding += 1; binding += 1;
} }
for image in &self.images { for image in &self.images {
let image_info = vk::DescriptorImageInfo::builder()
.sampler(vk::Sampler::null())
.image_view(*image)
.image_layout(vk::ImageLayout::GENERAL)
.build();
device.update_descriptor_sets( device.update_descriptor_sets(
&[vk::WriteDescriptorSet::builder() &[vk::WriteDescriptorSet::builder()
.dst_set(descriptor_sets[0]) .dst_set(descriptor_sets[0])
.dst_binding(binding) .dst_binding(binding)
.descriptor_type(vk::DescriptorType::STORAGE_IMAGE) .descriptor_type(vk::DescriptorType::STORAGE_IMAGE)
.image_info(&[image_info]) .image_info(&[vk::DescriptorImageInfo::builder()
.sampler(vk::Sampler::null())
.image_view(*image)
.image_layout(vk::ImageLayout::GENERAL)
.build()])
.build()], .build()],
&[], &[],
); );
binding += 1; binding += 1;
} }
if !self.textures.is_empty() {
let infos = self
.textures
.iter()
.map(|texture| {
vk::DescriptorImageInfo::builder()
.sampler(vk::Sampler::null())
.image_view(*texture)
.image_layout(vk::ImageLayout::GENERAL)
.build()
})
.collect::<Vec<_>>();
device.update_descriptor_sets(
&[vk::WriteDescriptorSet::builder()
.dst_set(descriptor_sets[0])
.dst_binding(binding)
.descriptor_type(vk::DescriptorType::STORAGE_IMAGE)
.image_info(&infos)
.build()],
&[],
);
//binding += 1;
}
Ok(DescriptorSet { Ok(DescriptorSet {
descriptor_set: descriptor_sets[0], descriptor_set: descriptor_sets[0],
}) })

View file

@ -6,6 +6,7 @@
#version 450 #version 450
#extension GL_GOOGLE_include_directive : enable #extension GL_GOOGLE_include_directive : enable
#extension GL_EXT_nonuniform_qualifier : enable
#include "setup.h" #include "setup.h"
@ -28,6 +29,8 @@ layout(set = 0, binding = 2) buffer ClipScratchBuf {
layout(rgba8, set = 0, binding = 3) uniform writeonly image2D image; layout(rgba8, set = 0, binding = 3) uniform writeonly image2D image;
layout(rgba8, set = 0, binding = 4) uniform readonly image2D textures[];
#include "ptcl.h" #include "ptcl.h"
#include "tile.h" #include "tile.h"
@ -103,6 +106,9 @@ void main() {
uint clip_tos = 0; uint clip_tos = 0;
for (uint i = 0; i < CHUNK; i++) { for (uint i = 0; i < CHUNK; i++) {
rgb[i] = vec3(0.5); rgb[i] = vec3(0.5);
if (xy_uint.x < 256 && xy_uint.y < 256) {
rgb[i] = imageLoad(textures[gl_WorkGroupID.x / 16], ivec2(xy_uint.x, xy_uint.y + CHUNK_DY * i)).rgb;
}
mask[i] = 1.0; mask[i] = 1.0;
} }

Binary file not shown.

View file

@ -199,6 +199,8 @@ pub struct Renderer {
n_elements: usize, n_elements: usize,
n_paths: usize, n_paths: usize,
n_pathseg: usize, n_pathseg: usize,
bg_image: hub::Image,
} }
impl Renderer { impl Renderer {
@ -301,16 +303,27 @@ impl Renderer {
], ],
)?; )?;
let bg_image = Self::make_test_bg_image(&session);
let k4_code = include_bytes!("../shader/kernel4.spv"); let k4_code = include_bytes!("../shader/kernel4.spv");
// This is an arbitrary limit on the number of textures that can be referenced by
// the fine rasterizer. To set it for real, we probably want to pay attention both
// to the device limit (maxDescriptorSetSampledImages) but also to the number of
// images encoded (I believe there's an cost when allocating descriptor pools). If
// it can't be satisfied, then for compatibility we'll probably want to fall back
// to an atlasing approach.
let max_textures = 256;
let k4_pipeline = session let k4_pipeline = session
.pipeline_builder() .pipeline_builder()
.add_buffers(3) .add_buffers(3)
.add_images(1) .add_images(1)
.add_textures(max_textures)
.create_compute_pipeline(&session, k4_code)?; .create_compute_pipeline(&session, k4_code)?;
let k4_ds = session let k4_ds = session
.descriptor_set_builder() .descriptor_set_builder()
.add_buffers(&[&ptcl_buf, &tile_buf, &clip_scratch_buf]) .add_buffers(&[&ptcl_buf, &tile_buf, &clip_scratch_buf])
.add_images(&[&image_dev]) .add_images(&[&image_dev])
.add_textures(&[&bg_image])
.build(&session, &k4_pipeline)?; .build(&session, &k4_pipeline)?;
Ok(Renderer { Ok(Renderer {
@ -347,6 +360,7 @@ impl Renderer {
n_elements, n_elements,
n_paths, n_paths,
n_pathseg, n_pathseg,
bg_image,
}) })
} }
@ -469,4 +483,22 @@ impl Renderer {
Ok(image) Ok(image)
} }
} }
/// Make a test image.
fn make_test_bg_image(session: &hub::Session) -> hub::Image {
const WIDTH: usize = 256;
const HEIGHT: usize = 256;
let mut buf = vec![255u8; WIDTH * HEIGHT * 4];
for y in 0..HEIGHT {
for x in 0..WIDTH {
let r = x as u8;
let g = y as u8;
let b = r ^ g;
buf[(y * WIDTH + x) * 4] = r;
buf[(y * WIDTH + x) * 4 + 1] = g;
buf[(y * WIDTH + x) * 4 + 2] = b;
}
}
Self::make_image(session, WIDTH, HEIGHT, &buf, ImageFormat::RgbaPremul).unwrap()
}
} }