use crate::device::VulkanContext;
use crate::resource::{backend_err, transition, vk_format, VulkanTexture};
use ash::vk;
use gpu_allocator::vulkan::{Allocation, AllocationCreateDesc, AllocationScheme};
use gpu_allocator::MemoryLocation;
use impeller_hal::{
Batch, BlendMode, ClipRole, Error, Extent2D, PassDescriptor, Result, TextureDescriptor,
TextureUsage,
};
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub(crate) struct RenderPassKey {
pub(crate) format: vk::Format,
pub(crate) clears: bool,
pub(crate) samples: u32,
pub(crate) presents: bool,
pub(crate) stencil: Option<vk::Format>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub(crate) struct PipelineKey {
pub(crate) format: vk::Format,
pub(crate) blend: BlendMode,
pub(crate) samples: u32,
pub(crate) stencil: Option<vk::Format>,
pub(crate) program: Option<u32>,
pub(crate) role: ClipRole,
}
#[derive(Default)]
pub(crate) struct PipelineCache {
pub(crate) layout: Option<vk::PipelineLayout>,
pub(crate) render_passes: HashMap<RenderPassKey, vk::RenderPass>,
pub(crate) pipelines: HashMap<PipelineKey, vk::Pipeline>,
}
impl PipelineCache {
pub(crate) fn render_pass(&self, key: RenderPassKey) -> Option<vk::RenderPass> {
self.render_passes.get(&key).copied()
}
pub(crate) fn pipeline(&self, key: PipelineKey) -> Option<vk::Pipeline> {
self.pipelines.get(&key).copied()
}
pub(crate) fn layout(&self) -> Option<vk::PipelineLayout> {
self.layout
}
pub(crate) fn destroy(&mut self, device: &ash::Device) {
unsafe {
for pipeline in self.pipelines.values() {
device.destroy_pipeline(*pipeline, None);
}
for pass in self.render_passes.values() {
device.destroy_render_pass(*pass, None);
}
if let Some(layout) = self.layout {
device.destroy_pipeline_layout(layout, None);
}
}
self.pipelines.clear();
self.render_passes.clear();
self.layout = None;
}
}
impl VulkanContext {
pub fn draw_indexed(
&mut self,
target: &mut VulkanTexture,
vertices: &[[f32; 2]],
indices: &[u32],
material: impeller_hal::Material,
blend: BlendMode,
clear: Option<[f32; 4]>,
) -> Result<()> {
let mut batch = Batch::new();
batch.push(vertices, indices, material, blend)?;
self.submit_batch(
target,
&batch,
PassDescriptor {
clear,
samples: 1,
viewport: None,
},
)
}
pub fn submit_batch(
&mut self,
target: &mut VulkanTexture,
batch: &Batch,
pass: PassDescriptor,
) -> Result<()> {
self.submit_batch_textured(target, batch, pass, &[])
}
pub fn submit_batch_textured(
&mut self,
target: &mut VulkanTexture,
batch: &Batch,
pass: PassDescriptor,
textures: &[&VulkanTexture],
) -> Result<()> {
let format = vk_format(target.format());
let clear = pass.clear;
self.capabilities().check_blend_modes(batch)?;
if !pass.samples.is_power_of_two() {
return Err(Error::Unsupported("sample count is not a power of two"));
}
if !self.capabilities().sample_counts.supports(pass.samples) {
return Err(Error::Unsupported("sample count not supported by device"));
}
if pass.is_multisampled() && clear.is_none() {
return Err(Error::Unsupported(
"a multisampled pass must clear: give the pass a clear color, \
or draw it at one sample. Preserving would need a \
resolved-to-multisample copy, which this technique has no \
reverse of",
));
}
if batch.is_empty() {
return match clear {
Some(c) => self.clear_texture(target, c),
None => Ok(()),
};
}
let stencil_format = self.stencil_format_for(batch)?;
let pass_key = RenderPassKey {
format,
clears: clear.is_some(),
samples: pass.samples,
presents: false,
stencil: stencil_format,
};
let render_pass = self.ensure_render_pass(pass_key)?;
for draw in batch.draws() {
self.ensure_pipeline(
PipelineKey {
format,
program: draw.material.program(),
blend: draw.blend,
samples: pass.samples,
stencil: stencil_format,
role: draw.stencil.role,
},
render_pass,
)?;
}
let bindings = crate::sampling::build(self, batch, textures)?;
let device = self.raw_device().clone();
let vertex_buffer = self.upload(
cast_bytes(batch.vertices()),
vk::BufferUsageFlags::VERTEX_BUFFER,
)?;
let index_buffer = self.upload(
cast_bytes(batch.indices()),
vk::BufferUsageFlags::INDEX_BUFFER,
)?;
let (material_buffer, materials) = crate::materials::build(self, batch, target.format())?;
let result = self.record_batch(
&device,
target,
format,
render_pass,
batch,
vertex_buffer.buffer,
index_buffer.buffer,
pass,
stencil_format,
&bindings,
&materials,
textures,
);
self.release(vertex_buffer);
self.release(index_buffer);
self.release(material_buffer);
materials.destroy(&device);
bindings.destroy(self);
result
}
#[allow(clippy::too_many_arguments)]
fn record_batch(
&mut self,
device: &ash::Device,
target: &mut VulkanTexture,
format: vk::Format,
render_pass: vk::RenderPass,
batch: &Batch,
vertex_buffer: vk::Buffer,
index_buffer: vk::Buffer,
pass: PassDescriptor,
stencil_format: Option<vk::Format>,
bindings: &crate::sampling::Bindings,
materials: &crate::materials::Materials,
sampled: &[&VulkanTexture],
) -> Result<()> {
let layout = self.pipeline_cache().layout().expect("ensured above");
let extent = target.extent();
let clear = pass.clear;
let view_info = vk::ImageViewCreateInfo::default()
.image(target.raw_image())
.view_type(vk::ImageViewType::TYPE_2D)
.format(format)
.subresource_range(
vk::ImageSubresourceRange::default()
.aspect_mask(vk::ImageAspectFlags::COLOR)
.level_count(1)
.layer_count(1),
);
let view = unsafe { device.create_image_view(&view_info, None) }
.map_err(|e| backend_err("create_image_view", e))?;
let multisample = if pass.is_multisampled() {
match self.create_multisample_buffer(target, pass.samples) {
Ok(ms) => Some(ms),
Err(e) => {
unsafe { device.destroy_image_view(view, None) };
return Err(e);
}
}
} else {
None
};
let stencil = match stencil_format {
Some(format) => match crate::stencil::create(self, extent, pass.samples, format) {
Ok(buffer) => Some(buffer),
Err(e) => {
unsafe { device.destroy_image_view(view, None) };
if let Some((tex, ms_view)) = multisample {
unsafe { device.destroy_image_view(ms_view, None) };
self.destroy_texture(tex);
}
return Err(e);
}
},
None => None,
};
let mut attachments: Vec<vk::ImageView> = match &multisample {
Some((_, ms_view)) => vec![*ms_view, view],
None => vec![view],
};
if let Some(stencil) = &stencil {
attachments.push(stencil.view);
}
let fb_info = vk::FramebufferCreateInfo::default()
.render_pass(render_pass)
.attachments(&attachments)
.width(extent.width)
.height(extent.height)
.layers(1);
let framebuffer = match unsafe { device.create_framebuffer(&fb_info, None) } {
Ok(fb) => fb,
Err(e) => {
unsafe { device.destroy_image_view(view, None) };
if let Some((tex, ms_view)) = multisample {
unsafe { device.destroy_image_view(ms_view, None) };
self.destroy_texture(tex);
}
if let Some(stencil) = stencil {
stencil.destroy(self);
}
return Err(backend_err("create_framebuffer", e));
}
};
let previous_layout = target.layout();
let outcome = (|| -> Result<()> {
let cmd = self.begin_one_shot()?;
crate::sampling::transition_for_sampling(device, cmd, sampled);
if clear.is_none() {
transition(
device,
cmd,
target.raw_image(),
previous_layout,
vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL,
);
}
let mut clear_values = vec![vk::ClearValue {
color: vk::ClearColorValue {
float32: clear.unwrap_or([0.0; 4]),
},
}];
clear_values.resize(attachments.len(), vk::ClearValue::default());
let clear_values = &clear_values[..];
let area = vk::Rect2D {
offset: vk::Offset2D { x: 0, y: 0 },
extent: vk::Extent2D {
width: extent.width,
height: extent.height,
},
};
let begin = vk::RenderPassBeginInfo::default()
.render_pass(render_pass)
.framebuffer(framebuffer)
.render_area(area)
.clear_values(clear_values);
let viewport = pass_viewport(pass, extent);
unsafe {
device.cmd_begin_render_pass(cmd, &begin, vk::SubpassContents::INLINE);
device.cmd_set_viewport(cmd, 0, &[viewport]);
device.cmd_set_scissor(cmd, 0, &[area]);
device.cmd_bind_vertex_buffers(cmd, 0, &[vertex_buffer], &[0]);
device.cmd_bind_index_buffer(cmd, index_buffer, 0, vk::IndexType::UINT32);
let recording = PassRecording {
device,
cmd,
layout,
area,
bindings,
materials,
};
let mut state = RecordedState::at_pass_start(area);
for (index, draw) in batch.draws().iter().enumerate() {
let pipeline = self
.pipeline_cache()
.pipeline(PipelineKey {
format,
program: draw.material.program(),
blend: draw.blend,
samples: pass.samples,
stencil: stencil_format,
role: draw.stencil.role,
})
.expect("ensured above");
record_draw(&recording, draw, index, pipeline, &mut state);
}
device.cmd_end_render_pass(cmd);
}
self.submit_one_shot(cmd)
})();
unsafe {
device.destroy_framebuffer(framebuffer, None);
device.destroy_image_view(view, None);
}
if let Some((tex, ms_view)) = multisample {
unsafe { device.destroy_image_view(ms_view, None) };
self.destroy_texture(tex);
}
if let Some(stencil) = stencil {
stencil.destroy(self);
}
if outcome.is_ok() {
target.set_layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL);
}
outcome
}
fn stencil_format_for(&self, batch: &Batch) -> Result<Option<vk::Format>> {
if !batch.uses_stencil() {
return Ok(None);
}
batch.check_clip_depth()?;
crate::stencil::stencil_format(self).map(Some)
}
fn create_multisample_buffer(
&mut self,
target: &VulkanTexture,
samples: u32,
) -> Result<(VulkanTexture, vk::ImageView)> {
let desc = TextureDescriptor {
extent: target.extent(),
format: target.format(),
usage: TextureUsage {
render_target: true,
..TextureUsage::default()
},
sample_count: samples,
mip_levels: 1,
#[cfg(unix)]
external: None,
};
let texture = self.create_texture(&desc)?;
let device = self.raw_device().clone();
let info = vk::ImageViewCreateInfo::default()
.image(texture.raw_image())
.view_type(vk::ImageViewType::TYPE_2D)
.format(vk_format(target.format()))
.subresource_range(
vk::ImageSubresourceRange::default()
.aspect_mask(vk::ImageAspectFlags::COLOR)
.level_count(1)
.layer_count(1),
);
match unsafe { device.create_image_view(&info, None) } {
Ok(view) => Ok((texture, view)),
Err(e) => {
self.destroy_texture(texture);
Err(backend_err("create_image_view", e))
}
}
}
pub fn submit_batch_deferred(
&mut self,
target: &mut VulkanTexture,
batch: &Batch,
pass: PassDescriptor,
) -> Result<crate::fence::VulkanFence> {
self.submit_batch_deferred_synchronized(target, batch, pass, &[], Default::default())
}
pub fn submit_batch_deferred_synchronized(
&mut self,
target: &mut VulkanTexture,
batch: &Batch,
pass: PassDescriptor,
textures: &[&VulkanTexture],
sync: crate::device::FrameSync<'_>,
) -> Result<crate::fence::VulkanFence> {
if pass.is_multisampled() {
return Err(Error::Unsupported(
"deferred submission of a multisampled pass",
));
}
let format = vk_format(target.format());
if batch.is_empty() {
return Err(Error::Unsupported("deferred submission of an empty batch"));
}
let stencil_format = self.stencil_format_for(batch)?;
let pass_key = RenderPassKey {
format,
clears: pass.clear.is_some(),
samples: 1,
presents: sync.presents,
stencil: stencil_format,
};
let render_pass = self.ensure_render_pass(pass_key)?;
for draw in batch.draws() {
self.ensure_pipeline(
PipelineKey {
format,
program: draw.material.program(),
blend: draw.blend,
samples: 1,
stencil: stencil_format,
role: draw.stencil.role,
},
render_pass,
)?;
}
let device = self.raw_device().clone();
let vertex_buffer = self.upload(
cast_bytes(batch.vertices()),
vk::BufferUsageFlags::VERTEX_BUFFER,
)?;
let index_buffer = self.upload(
cast_bytes(batch.indices()),
vk::BufferUsageFlags::INDEX_BUFFER,
)?;
let result = self.record_deferred(
&device,
target,
format,
render_pass,
batch,
vertex_buffer.buffer,
index_buffer.buffer,
pass,
stencil_format,
textures,
sync,
);
match result {
Ok(mut fence) => {
fence.retained.push(vertex_buffer);
fence.retained.push(index_buffer);
Ok(fence)
}
Err(e) => {
self.release(vertex_buffer);
self.release(index_buffer);
Err(e)
}
}
}
pub fn retire_fence(&mut self, mut fence: crate::fence::VulkanFence) {
fence.retire();
for buffer in std::mem::take(&mut fence.retained) {
self.release(buffer);
}
if let Some(bindings) = fence.bindings.take() {
bindings.destroy(self);
}
if let Some(materials) = fence.materials.take() {
materials.destroy(&self.raw_device().clone());
}
}
#[allow(clippy::too_many_arguments)]
fn record_deferred(
&mut self,
device: &ash::Device,
target: &mut VulkanTexture,
format: vk::Format,
render_pass: vk::RenderPass,
batch: &Batch,
vertex_buffer: vk::Buffer,
index_buffer: vk::Buffer,
pass: PassDescriptor,
stencil_format: Option<vk::Format>,
textures: &[&VulkanTexture],
sync: crate::device::FrameSync<'_>,
) -> Result<crate::fence::VulkanFence> {
let bindings = crate::sampling::build(self, batch, textures)?;
let (material_buffer, materials) = crate::materials::build(self, batch, target.format())?;
let layout = self.pipeline_cache().layout().expect("ensured above");
let extent = target.extent();
let view_info = vk::ImageViewCreateInfo::default()
.image(target.raw_image())
.view_type(vk::ImageViewType::TYPE_2D)
.format(format)
.subresource_range(
vk::ImageSubresourceRange::default()
.aspect_mask(vk::ImageAspectFlags::COLOR)
.level_count(1)
.layer_count(1),
);
let view = unsafe { device.create_image_view(&view_info, None) }
.map_err(|e| backend_err("create_image_view", e))?;
let attachments = [view];
let fb_info = vk::FramebufferCreateInfo::default()
.render_pass(render_pass)
.attachments(&attachments)
.width(extent.width)
.height(extent.height)
.layers(1);
let framebuffer = match unsafe { device.create_framebuffer(&fb_info, None) } {
Ok(fb) => fb,
Err(e) => {
unsafe { device.destroy_image_view(view, None) };
return Err(backend_err("create_framebuffer", e));
}
};
let pipelines: Vec<vk::Pipeline> = batch
.draws()
.iter()
.map(|draw| {
self.pipeline_cache()
.pipeline(PipelineKey {
format,
program: draw.material.program(),
blend: draw.blend,
samples: 1,
stencil: stencil_format,
role: draw.stencil.role,
})
.expect("ensured above")
})
.collect();
let previous_layout = target.layout();
let cmd = self.begin_one_shot()?;
unsafe {
crate::sampling::transition_for_sampling(device, cmd, textures);
if pass.clear.is_none() {
transition(
device,
cmd,
target.raw_image(),
previous_layout,
vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL,
);
}
let clear_values = [vk::ClearValue {
color: vk::ClearColorValue {
float32: pass.clear.unwrap_or([0.0; 4]),
},
}];
let area = vk::Rect2D {
offset: vk::Offset2D { x: 0, y: 0 },
extent: vk::Extent2D {
width: extent.width,
height: extent.height,
},
};
let begin = vk::RenderPassBeginInfo::default()
.render_pass(render_pass)
.framebuffer(framebuffer)
.render_area(area)
.clear_values(&clear_values);
let viewport = pass_viewport(pass, extent);
device.cmd_begin_render_pass(cmd, &begin, vk::SubpassContents::INLINE);
device.cmd_set_viewport(cmd, 0, &[viewport]);
device.cmd_set_scissor(cmd, 0, &[area]);
device.cmd_bind_vertex_buffers(cmd, 0, &[vertex_buffer], &[0]);
device.cmd_bind_index_buffer(cmd, index_buffer, 0, vk::IndexType::UINT32);
let recording = PassRecording {
device,
cmd,
layout,
area,
bindings: &bindings,
materials: &materials,
};
let mut state = RecordedState::at_pass_start(area);
for (index, (draw, pipeline)) in batch.draws().iter().zip(&pipelines).enumerate() {
record_draw(&recording, draw, index, *pipeline, &mut state);
}
device.cmd_end_render_pass(cmd);
}
let mut fence = self.submit_exportable(cmd, framebuffer, view, sync)?;
fence.bindings = Some(bindings);
fence.materials = Some(materials);
fence.retained.push(material_buffer);
target.set_layout(if sync.presents {
vk::ImageLayout::PRESENT_SRC_KHR
} else {
vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL
});
Ok(fence)
}
fn ensure_render_pass(&mut self, key: RenderPassKey) -> Result<vk::RenderPass> {
if let Some(pass) = self.pipeline_cache().render_pass(key) {
return Ok(pass);
}
let device = self.raw_device().clone();
let pass = build_render_pass(&device, key)?;
self.pipeline_cache_mut().render_passes.insert(key, pass);
Ok(pass)
}
fn ensure_pipeline(&mut self, key: PipelineKey, render_pass: vk::RenderPass) -> Result<()> {
if self.pipeline_cache().pipeline(key).is_some() {
return Ok(());
}
let device = self.raw_device().clone();
let layout = match self.pipeline_cache().layout() {
Some(l) => l,
None => {
let descriptors = self.descriptor_layout()?;
let materials = self.material_layout()?;
let l = build_pipeline_layout(&device, descriptors, materials)?;
self.pipeline_cache_mut().layout = Some(l);
l
}
};
let fragment = match key.program {
Some(id) => Some(self.runtime_program(id)?.to_vec()),
None => None,
};
let pipeline = build_pipeline(&device, key, render_pass, layout, fragment.as_deref())?;
self.pipeline_cache_mut().pipelines.insert(key, pipeline);
Ok(())
}
pub(crate) fn upload(
&mut self,
bytes: &[u8],
usage: vk::BufferUsageFlags,
) -> Result<StagedBuffer> {
let device = self.raw_device().clone();
let info = vk::BufferCreateInfo::default()
.size(bytes.len().max(1) as u64)
.usage(usage)
.sharing_mode(vk::SharingMode::EXCLUSIVE);
let buffer = unsafe { device.create_buffer(&info, None) }
.map_err(|e| backend_err("create_buffer", e))?;
let requirements = unsafe { device.get_buffer_memory_requirements(buffer) };
let mut allocation = self
.allocator_mut()
.allocate(&AllocationCreateDesc {
name: "upload",
requirements,
location: MemoryLocation::CpuToGpu,
linear: true,
allocation_scheme: AllocationScheme::GpuAllocatorManaged,
})
.map_err(|e| {
unsafe { device.destroy_buffer(buffer, None) };
Error::Backend {
backend: "vulkan",
detail: format!("upload allocation: {e}"),
}
})?;
unsafe { device.bind_buffer_memory(buffer, allocation.memory(), allocation.offset()) }
.map_err(|e| backend_err("bind_buffer_memory", e))?;
let slice = allocation.mapped_slice_mut().ok_or(Error::Backend {
backend: "vulkan",
detail: "upload allocation was not host-visible".into(),
})?;
slice[..bytes.len()].copy_from_slice(bytes);
Ok(StagedBuffer { buffer, allocation })
}
pub(crate) fn release(&mut self, staged: StagedBuffer) {
let _ = self.allocator_mut().free(staged.allocation);
unsafe { self.raw_device().destroy_buffer(staged.buffer, None) };
}
}
pub(crate) struct StagedBuffer {
pub(crate) buffer: vk::Buffer,
pub(crate) allocation: Allocation,
}
fn pass_viewport(pass: PassDescriptor, extent: Extent2D) -> vk::Viewport {
let (offset, size) = match pass.viewport {
Some(v) => (v.offset, v.extent),
None => ([0.0, 0.0], extent),
};
vk::Viewport {
x: offset[0],
y: offset[1],
width: size.width as f32,
height: size.height as f32,
min_depth: 0.0,
max_depth: 1.0,
}
}
fn build_render_pass(device: &ash::Device, key: RenderPassKey) -> Result<vk::RenderPass> {
let (load_op, initial_layout) = if key.clears {
(vk::AttachmentLoadOp::CLEAR, vk::ImageLayout::UNDEFINED)
} else {
(
vk::AttachmentLoadOp::LOAD,
vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL,
)
};
let color = vk::AttachmentDescription::default()
.format(key.format)
.samples(sample_flags(key.samples))
.load_op(load_op)
.store_op(if key.samples > 1 {
vk::AttachmentStoreOp::DONT_CARE
} else {
vk::AttachmentStoreOp::STORE
})
.stencil_load_op(vk::AttachmentLoadOp::DONT_CARE)
.stencil_store_op(vk::AttachmentStoreOp::DONT_CARE)
.initial_layout(initial_layout)
.final_layout(final_layout(key, false));
let resolve = vk::AttachmentDescription::default()
.format(key.format)
.samples(vk::SampleCountFlags::TYPE_1)
.load_op(vk::AttachmentLoadOp::DONT_CARE)
.store_op(vk::AttachmentStoreOp::STORE)
.stencil_load_op(vk::AttachmentLoadOp::DONT_CARE)
.stencil_store_op(vk::AttachmentStoreOp::DONT_CARE)
.initial_layout(vk::ImageLayout::UNDEFINED)
.final_layout(final_layout(key, true));
let stencil = key.stencil.map(|format| {
vk::AttachmentDescription::default()
.format(format)
.samples(sample_flags(key.samples))
.load_op(vk::AttachmentLoadOp::DONT_CARE)
.store_op(vk::AttachmentStoreOp::DONT_CARE)
.stencil_load_op(vk::AttachmentLoadOp::CLEAR)
.stencil_store_op(vk::AttachmentStoreOp::DONT_CARE)
.initial_layout(vk::ImageLayout::UNDEFINED)
.final_layout(vk::ImageLayout::DEPTH_STENCIL_ATTACHMENT_OPTIMAL)
});
let mut attachments: Vec<vk::AttachmentDescription> = if key.samples > 1 {
vec![color, resolve]
} else {
vec![color]
};
let stencil_index = attachments.len() as u32;
if let Some(stencil) = stencil {
attachments.push(stencil);
}
let color_refs = [vk::AttachmentReference::default()
.attachment(0)
.layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL)];
let resolve_refs = [vk::AttachmentReference::default()
.attachment(1)
.layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL)];
let stencil_ref = vk::AttachmentReference::default()
.attachment(stencil_index)
.layout(vk::ImageLayout::DEPTH_STENCIL_ATTACHMENT_OPTIMAL);
let mut subpass = vk::SubpassDescription::default()
.pipeline_bind_point(vk::PipelineBindPoint::GRAPHICS)
.color_attachments(&color_refs);
if key.samples > 1 {
subpass = subpass.resolve_attachments(&resolve_refs);
}
if key.stencil.is_some() {
subpass = subpass.depth_stencil_attachment(&stencil_ref);
}
let subpasses = [subpass];
let attachment_stages = vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT
| vk::PipelineStageFlags::EARLY_FRAGMENT_TESTS
| vk::PipelineStageFlags::LATE_FRAGMENT_TESTS;
let attachment_writes = vk::AccessFlags::COLOR_ATTACHMENT_WRITE
| vk::AccessFlags::COLOR_ATTACHMENT_READ
| vk::AccessFlags::DEPTH_STENCIL_ATTACHMENT_WRITE
| vk::AccessFlags::DEPTH_STENCIL_ATTACHMENT_READ;
let dependencies = [
vk::SubpassDependency::default()
.src_subpass(vk::SUBPASS_EXTERNAL)
.dst_subpass(0)
.src_stage_mask(attachment_stages)
.dst_stage_mask(attachment_stages)
.src_access_mask(vk::AccessFlags::empty())
.dst_access_mask(attachment_writes),
vk::SubpassDependency::default()
.src_subpass(0)
.dst_subpass(vk::SUBPASS_EXTERNAL)
.src_stage_mask(attachment_stages)
.dst_stage_mask(vk::PipelineStageFlags::FRAGMENT_SHADER)
.src_access_mask(
vk::AccessFlags::COLOR_ATTACHMENT_WRITE
| vk::AccessFlags::DEPTH_STENCIL_ATTACHMENT_WRITE,
)
.dst_access_mask(vk::AccessFlags::SHADER_READ),
];
let info = vk::RenderPassCreateInfo::default()
.attachments(&attachments)
.subpasses(&subpasses)
.dependencies(&dependencies);
unsafe { device.create_render_pass(&info, None) }
.map_err(|e| backend_err("create_render_pass", e))
}
fn final_layout(key: RenderPassKey, is_resolve: bool) -> vk::ImageLayout {
let read_by_the_caller = is_resolve || key.samples == 1;
if key.presents && read_by_the_caller {
vk::ImageLayout::PRESENT_SRC_KHR
} else {
vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL
}
}
pub(crate) fn sample_flags(count: u32) -> vk::SampleCountFlags {
match count {
2 => vk::SampleCountFlags::TYPE_2,
4 => vk::SampleCountFlags::TYPE_4,
8 => vk::SampleCountFlags::TYPE_8,
16 => vk::SampleCountFlags::TYPE_16,
_ => vk::SampleCountFlags::TYPE_1,
}
}
fn build_pipeline_layout(
device: &ash::Device,
descriptor_layout: vk::DescriptorSetLayout,
material_layout: vk::DescriptorSetLayout,
) -> Result<vk::PipelineLayout> {
let set_layouts = [descriptor_layout, material_layout];
let info = vk::PipelineLayoutCreateInfo::default().set_layouts(&set_layouts);
unsafe { device.create_pipeline_layout(&info, None) }
.map_err(|e| backend_err("create_pipeline_layout", e))
}
fn build_pipeline(
device: &ash::Device,
key: PipelineKey,
render_pass: vk::RenderPass,
layout: vk::PipelineLayout,
fragment: Option<&[u32]>,
) -> Result<vk::Pipeline> {
let module_info = vk::ShaderModuleCreateInfo::default().code(impeller_shaders::SOLID_SPV);
let module = unsafe { device.create_shader_module(&module_info, None) }
.map_err(|e| backend_err("create_shader_module", e))?;
let fragment_module = match fragment {
Some(code) => {
let info = vk::ShaderModuleCreateInfo::default().code(code);
Some(
unsafe { device.create_shader_module(&info, None) }
.map_err(|e| backend_err("create_shader_module", e))?,
)
}
None => None,
};
let fs_module = fragment_module.unwrap_or(module);
let vs_name = c"vs_main";
let fs_name = c"fs_main";
let stages = [
vk::PipelineShaderStageCreateInfo::default()
.stage(vk::ShaderStageFlags::VERTEX)
.module(module)
.name(vs_name),
vk::PipelineShaderStageCreateInfo::default()
.stage(vk::ShaderStageFlags::FRAGMENT)
.module(fs_module)
.name(fs_name),
];
let bindings = [vk::VertexInputBindingDescription::default()
.binding(0)
.stride(std::mem::size_of::<impeller_hal::Vertex>() as u32)
.input_rate(vk::VertexInputRate::VERTEX)];
let attributes = [
vk::VertexInputAttributeDescription::default()
.location(0)
.binding(0)
.format(vk::Format::R32G32B32_SFLOAT)
.offset(std::mem::offset_of!(impeller_hal::Vertex, position) as u32),
vk::VertexInputAttributeDescription::default()
.location(1)
.binding(0)
.format(vk::Format::R32G32_SFLOAT)
.offset(std::mem::offset_of!(impeller_hal::Vertex, uv) as u32),
vk::VertexInputAttributeDescription::default()
.location(2)
.binding(0)
.format(vk::Format::R32G32B32A32_SFLOAT)
.offset(std::mem::offset_of!(impeller_hal::Vertex, color) as u32),
];
let vertex_input = vk::PipelineVertexInputStateCreateInfo::default()
.vertex_binding_descriptions(&bindings)
.vertex_attribute_descriptions(&attributes);
let assembly = vk::PipelineInputAssemblyStateCreateInfo::default()
.topology(vk::PrimitiveTopology::TRIANGLE_LIST);
let viewport_state = vk::PipelineViewportStateCreateInfo::default()
.viewport_count(1)
.scissor_count(1);
let raster = vk::PipelineRasterizationStateCreateInfo::default()
.cull_mode(vk::CullModeFlags::NONE)
.front_face(vk::FrontFace::COUNTER_CLOCKWISE)
.polygon_mode(vk::PolygonMode::FILL)
.line_width(1.0);
let multisample = vk::PipelineMultisampleStateCreateInfo::default()
.rasterization_samples(sample_flags(key.samples));
let attachment = vk::PipelineColorBlendAttachmentState::default().color_write_mask(
if key.role.writes_color() {
vk::ColorComponentFlags::RGBA
} else {
vk::ColorComponentFlags::empty()
},
);
let blend_attachments = [match key.blend.factors() {
_ if key.blend.is_plain_write() => {
attachment.blend_enable(false)
}
Some(factors) => {
let src = vk_blend_factor(factors.src);
let dst = vk_blend_factor(factors.dst);
attachment
.blend_enable(true)
.src_color_blend_factor(src)
.dst_color_blend_factor(dst)
.color_blend_op(vk::BlendOp::ADD)
.src_alpha_blend_factor(src)
.dst_alpha_blend_factor(dst)
.alpha_blend_op(vk::BlendOp::ADD)
}
None => attachment
.blend_enable(true)
.src_color_blend_factor(vk::BlendFactor::ONE)
.dst_color_blend_factor(vk::BlendFactor::ZERO)
.color_blend_op(vk_advanced_blend_op(key.blend))
.src_alpha_blend_factor(vk::BlendFactor::ONE)
.dst_alpha_blend_factor(vk::BlendFactor::ZERO)
.alpha_blend_op(vk_advanced_blend_op(key.blend)),
}];
let mut blend =
vk::PipelineColorBlendStateCreateInfo::default().attachments(&blend_attachments);
let mut advanced = vk::PipelineColorBlendAdvancedStateCreateInfoEXT::default()
.src_premultiplied(true)
.dst_premultiplied(true)
.blend_overlap(vk::BlendOverlapEXT::UNCORRELATED);
if key.blend.is_advanced() {
blend = blend.push_next(&mut advanced);
}
let stencil_op = vk::StencilOpState::default()
.compare_op(vk::CompareOp::EQUAL)
.fail_op(vk::StencilOp::KEEP)
.depth_fail_op(vk::StencilOp::KEEP)
.pass_op(match key.role {
ClipRole::Content => vk::StencilOp::KEEP,
ClipRole::Narrow => vk::StencilOp::INCREMENT_AND_CLAMP,
ClipRole::Widen => vk::StencilOp::DECREMENT_AND_CLAMP,
})
.compare_mask(0xff)
.write_mask(if key.role.writes_stencil() { 0xff } else { 0 });
let depth_stencil = vk::PipelineDepthStencilStateCreateInfo::default()
.stencil_test_enable(key.stencil.is_some())
.front(stencil_op)
.back(stencil_op);
let dynamic_states = [
vk::DynamicState::VIEWPORT,
vk::DynamicState::SCISSOR,
vk::DynamicState::STENCIL_REFERENCE,
];
let dynamic = vk::PipelineDynamicStateCreateInfo::default().dynamic_states(&dynamic_states);
let info = vk::GraphicsPipelineCreateInfo::default()
.stages(&stages)
.vertex_input_state(&vertex_input)
.input_assembly_state(&assembly)
.viewport_state(&viewport_state)
.rasterization_state(&raster)
.multisample_state(&multisample)
.depth_stencil_state(&depth_stencil)
.color_blend_state(&blend)
.dynamic_state(&dynamic)
.layout(layout)
.render_pass(render_pass)
.subpass(0);
let created =
unsafe { device.create_graphics_pipelines(vk::PipelineCache::null(), &[info], None) };
unsafe { device.destroy_shader_module(module, None) };
if let Some(fragment) = fragment_module {
unsafe { device.destroy_shader_module(fragment, None) };
}
match created {
Ok(pipelines) => Ok(pipelines[0]),
Err((_, e)) => Err(backend_err("create_graphics_pipelines", e)),
}
}
struct PassRecording<'a> {
device: &'a ash::Device,
cmd: vk::CommandBuffer,
layout: vk::PipelineLayout,
area: vk::Rect2D,
bindings: &'a crate::sampling::Bindings,
materials: &'a crate::materials::Materials,
}
struct RecordedState {
pipeline: Option<vk::Pipeline>,
descriptor_set: Option<vk::DescriptorSet>,
scissor: vk::Rect2D,
stencil_reference: Option<u32>,
}
impl RecordedState {
fn at_pass_start(area: vk::Rect2D) -> Self {
Self {
pipeline: None,
descriptor_set: None,
scissor: area,
stencil_reference: None,
}
}
}
unsafe fn record_draw(
pass: &PassRecording,
draw: &impeller_hal::BatchDraw,
index: usize,
pipeline: vk::Pipeline,
state: &mut RecordedState,
) {
let PassRecording {
device,
cmd,
layout,
area,
bindings,
materials,
} = *pass;
unsafe {
if state.pipeline != Some(pipeline) {
device.cmd_bind_pipeline(cmd, vk::PipelineBindPoint::GRAPHICS, pipeline);
state.pipeline = Some(pipeline);
}
let wanted = match draw.clip {
Some(clip) => {
let clip = clip.clamped_to(impeller_hal::Extent2D::new(
area.extent.width,
area.extent.height,
));
vk::Rect2D {
offset: vk::Offset2D {
x: clip.x as i32,
y: clip.y as i32,
},
extent: vk::Extent2D {
width: clip.width,
height: clip.height,
},
}
}
None => area,
};
if state.scissor != wanted {
device.cmd_set_scissor(cmd, 0, &[wanted]);
state.scissor = wanted;
}
let set = bindings.set_for(draw.material.texture_slots());
if state.descriptor_set != Some(set) {
device.cmd_bind_descriptor_sets(
cmd,
vk::PipelineBindPoint::GRAPHICS,
layout,
0,
&[set],
&[],
);
state.descriptor_set = Some(set);
}
let reference = draw.stencil.reference;
if state.stencil_reference != Some(reference) {
device.cmd_set_stencil_reference(cmd, vk::StencilFaceFlags::FRONT_AND_BACK, reference);
state.stencil_reference = Some(reference);
}
device.cmd_bind_descriptor_sets(
cmd,
vk::PipelineBindPoint::GRAPHICS,
layout,
1,
&[materials.set()],
&[materials.offset(index)],
);
device.cmd_draw_indexed(cmd, draw.index_count, 1, draw.first_index, 0, 0);
}
}
fn vk_advanced_blend_op(mode: impeller_hal::BlendMode) -> vk::BlendOp {
use impeller_hal::BlendMode;
match mode {
BlendMode::Multiply => vk::BlendOp::MULTIPLY_EXT,
BlendMode::Screen => vk::BlendOp::SCREEN_EXT,
BlendMode::Overlay => vk::BlendOp::OVERLAY_EXT,
BlendMode::Darken => vk::BlendOp::DARKEN_EXT,
BlendMode::Lighten => vk::BlendOp::LIGHTEN_EXT,
BlendMode::ColorDodge => vk::BlendOp::COLORDODGE_EXT,
BlendMode::ColorBurn => vk::BlendOp::COLORBURN_EXT,
BlendMode::HardLight => vk::BlendOp::HARDLIGHT_EXT,
BlendMode::SoftLight => vk::BlendOp::SOFTLIGHT_EXT,
BlendMode::Difference => vk::BlendOp::DIFFERENCE_EXT,
BlendMode::Exclusion => vk::BlendOp::EXCLUSION_EXT,
BlendMode::Hue => vk::BlendOp::HSL_HUE_EXT,
BlendMode::Saturation => vk::BlendOp::HSL_SATURATION_EXT,
BlendMode::Color => vk::BlendOp::HSL_COLOR_EXT,
BlendMode::Luminosity => vk::BlendOp::HSL_LUMINOSITY_EXT,
other => unreachable!("{other} is not an advanced blend mode"),
}
}
fn vk_blend_factor(factor: impeller_hal::BlendFactor) -> vk::BlendFactor {
use impeller_hal::BlendFactor;
match factor {
BlendFactor::Zero => vk::BlendFactor::ZERO,
BlendFactor::One => vk::BlendFactor::ONE,
BlendFactor::SrcAlpha => vk::BlendFactor::SRC_ALPHA,
BlendFactor::OneMinusSrcAlpha => vk::BlendFactor::ONE_MINUS_SRC_ALPHA,
BlendFactor::DstAlpha => vk::BlendFactor::DST_ALPHA,
BlendFactor::OneMinusDstAlpha => vk::BlendFactor::ONE_MINUS_DST_ALPHA,
BlendFactor::DstColor => vk::BlendFactor::DST_COLOR,
}
}
pub(crate) fn cast_bytes<T>(slice: &[T]) -> &[u8] {
unsafe { std::slice::from_raw_parts(slice.as_ptr() as *const u8, std::mem::size_of_val(slice)) }
}