Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimize sequential draws of the same pipeline #2539

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
216 changes: 174 additions & 42 deletions vulkano/src/command_buffer/auto/builder.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use super::{
CommandBuffer, CommandInfo, RenderPassCommand, Resource, ResourceUseRef2, SubmitState,
CommandBuffer, CommandInfo, PipelineEnum, RenderPassCommand, Resource, ResourceUseRef2,
SubmitState, UsedResources,
};
use crate::{
buffer::{Buffer, IndexBuffer, Subbuffer},
Expand All @@ -26,7 +27,8 @@ use crate::{
vertex_input::VertexInputState,
viewport::{Scissor, Viewport},
},
ComputePipeline, DynamicState, GraphicsPipeline, PipelineBindPoint, PipelineLayout,
ComputePipeline, DynamicState, GraphicsPipeline, Pipeline, PipelineBindPoint,
PipelineLayout,
},
query::{QueryControlFlags, QueryPool, QueryType},
range_map::RangeMap,
Expand All @@ -38,12 +40,13 @@ use crate::{
},
DeviceSize, Validated, ValidationError, VulkanError,
};
use ahash::HashMap;
use ahash::{HashMap, HashSet};
use parking_lot::{Mutex, RwLockReadGuard};
use smallvec::SmallVec;
use std::{
collections::hash_map::Entry,
fmt::Debug,
hash::{Hash, Hasher},
mem::take,
ops::{Range, RangeInclusive},
sync::{atomic::AtomicBool, Arc},
Expand Down Expand Up @@ -175,20 +178,20 @@ impl RecordingCommandBuffer {
);

// Add barriers between the commands.
for (command_info, _) in self.commands.iter() {
auto_sync_state.add_command(command_info).map_err(|err| {
auto_sync_state
.add_command_resources(self.commands.iter().map(|(cmd, _)| cmd))
.map_err(|err| {
Box::new(ValidationError {
problem: format!(
"unsolvable resource conflict between:\n\
command resource use: {:?}\n\
previous conflicting command resource use: {:?}",
command resource use: {:?}\n\
previous conflicting command resource use: {:?}",
err.current_use_ref, err.previous_use_ref,
)
.into(),
..Default::default()
})
})?;
}

let (mut barriers, resources_usage, secondary_resources_usage) = auto_sync_state.build();
let final_barrier_index = self.commands.len();
Expand Down Expand Up @@ -266,6 +269,32 @@ impl RecordingCommandBuffer {
name: &'static str,
used_resources: Vec<(ResourceUseRef2, Resource)>,
record_func: impl Fn(&mut RawRecordingCommandBuffer) + Send + Sync + 'static,
) {
self.add_command_resources(
name,
UsedResources {
direct: used_resources,
deferred: None,
},
record_func,
)
}

pub(in crate::command_buffer) fn add_command_deferred(
&mut self,
name: &'static str,
direct: Vec<(ResourceUseRef2, Resource)>,
deferred: Option<(PipelineEnum, DescriptorSetState)>,
record_func: impl Fn(&mut RawRecordingCommandBuffer) + Send + Sync + 'static,
) {
self.add_command_resources(name, UsedResources::deferred(direct, deferred), record_func)
}

fn add_command_resources(
&mut self,
name: &'static str,
used_resources: UsedResources,
record_func: impl Fn(&mut RawRecordingCommandBuffer) + Send + Sync + 'static,
) {
self.commands.push((
CommandInfo {
Expand All @@ -286,7 +315,7 @@ impl RecordingCommandBuffer {
self.commands.push((
CommandInfo {
name,
used_resources,
used_resources: UsedResources::direct(used_resources),
render_pass: RenderPassCommand::Begin,
},
Box::new(record_func),
Expand All @@ -302,7 +331,7 @@ impl RecordingCommandBuffer {
self.commands.push((
CommandInfo {
name,
used_resources,
used_resources: UsedResources::direct(used_resources),
render_pass: RenderPassCommand::End,
},
Box::new(record_func),
Expand Down Expand Up @@ -483,41 +512,130 @@ impl AutoSyncState {
self.secondary_resources_usage,
)
}
}

#[derive(Debug)]
struct MergedCommandInfo<'a> {
name: &'static str,
render_pass: RenderPassCommand,
pipeline: Option<&'a PipelineEnum>,
direct: SmallVec<[&'a Vec<(ResourceUseRef2, Resource)>; 6]>,
deferred: SmallVec<[&'a DescriptorSetState; 6]>,
}

impl<'a> MergedCommandInfo<'a> {
fn from(value: &'a CommandInfo) -> Self {
let (pipeline, deferred) = if let Some((pipeline, state)) = &value.used_resources.deferred {
(Some(pipeline), SmallVec::from_iter([state]))
} else {
(None, SmallVec::new())
};
let direct = if value.used_resources.direct.is_empty() {
SmallVec::new()
} else {
SmallVec::from_iter([&value.used_resources.direct])
};
Self {
name: value.name,
render_pass: value.render_pass,
pipeline,
direct,
deferred,
}
}

fn try_merge(&mut self, b: &'a CommandInfo) -> Result<(), MergedCommandInfo<'a>> {
let mut b = Self::from(b);
match (self.render_pass, b.render_pass) {
(RenderPassCommand::None, RenderPassCommand::None) => (),
_ => return Err(b),
}
if !(self.pipeline == b.pipeline || self.pipeline.is_none() || b.pipeline.is_none()) {
return Err(b);
}

// success
if self.pipeline.is_none() {
self.pipeline = b.pipeline;
}
self.direct.append(&mut b.direct);
// FIXME deferred must be merged together to prevent duplicate resources
self.deferred.append(&mut b.deferred);
Ok(())
}

fn resolve_deferred(&self, pipeline: &Arc<impl Pipeline>) -> Vec<(ResourceUseRef2, Resource)> {
let mut used_resources = Vec::new();
let deferred_deduplicated = HashSet::from_iter(self.deferred.iter().copied());
for state in &deferred_deduplicated {
RecordingCommandBuffer::add_descriptor_sets_resources(
&mut used_resources,
pipeline.as_ref(),
state,
);
}
used_resources
}
}

fn add_command(
impl AutoSyncState {
fn add_command_resources<'a>(
&mut self,
command_info: &CommandInfo,
command_infos: impl Iterator<Item = &'a CommandInfo>,
) -> Result<(), UnsolvableResourceConflict> {
self.check_resource_conflicts(command_info)?;
self.add_resources(command_info);

match command_info.render_pass {
RenderPassCommand::None => (),
RenderPassCommand::Begin => {
debug_assert!(self.latest_render_pass_enter.is_none());
self.latest_render_pass_enter = Some(self.command_index);
}
RenderPassCommand::End => {
debug_assert!(self.latest_render_pass_enter.is_some());
self.latest_render_pass_enter = None;
let merged = command_infos.fold(Vec::new(), |mut vec: Vec<MergedCommandInfo<'_>>, cmd| {
if let Some(last) = vec.last_mut() {
match last.try_merge(cmd) {
Ok(_) => vec,
Err(cmd) => {
vec.push(cmd);
vec
}
}
} else {
vec.push(MergedCommandInfo::from(cmd));
vec
}
}
});

self.command_index += 1;
for cmd in &merged {
let deferred = match &cmd.pipeline {
Some(PipelineEnum::Compute(pipeline)) => cmd.resolve_deferred(pipeline),
Some(PipelineEnum::Graphics(pipeline)) => cmd.resolve_deferred(pipeline),
None => Vec::new(),
};
let used_resources = || {
cmd.direct
.iter()
.flat_map(|vec| vec.iter())
.chain(deferred.iter())
};

self.check_resource_conflicts(cmd.name, used_resources())?;
self.add_resources(cmd.name, used_resources());

match cmd.render_pass {
RenderPassCommand::None => (),
RenderPassCommand::Begin => {
debug_assert!(self.latest_render_pass_enter.is_none());
self.latest_render_pass_enter = Some(self.command_index);
}
RenderPassCommand::End => {
debug_assert!(self.latest_render_pass_enter.is_some());
self.latest_render_pass_enter = None;
}
}

self.command_index += 1;
}
Ok(())
}

fn check_resource_conflicts(
fn check_resource_conflicts<'a>(
&self,
command_info: &CommandInfo,
command_name: &'static str,
used_resources: impl Iterator<Item = &'a (ResourceUseRef2, Resource)>,
) -> Result<(), UnsolvableResourceConflict> {
let &CommandInfo {
name: command_name,
ref used_resources,
..
} = command_info;

for (use_ref, resource) in used_resources {
match *resource {
Resource::Buffer {
Expand Down Expand Up @@ -693,13 +811,11 @@ impl AutoSyncState {
/// - `start_layout` and `end_layout` designate the image layout that the image is expected to
/// be in when the command starts, and the image layout that the image will be transitioned
/// to during the command. When it comes to buffers, you should pass `Undefined` for both.
fn add_resources(&mut self, command_info: &CommandInfo) {
let &CommandInfo {
name: command_name,
ref used_resources,
..
} = command_info;

fn add_resources<'a>(
&mut self,
command_name: &'static str,
used_resources: impl Iterator<Item = &'a (ResourceUseRef2, Resource)>,
) {
for (use_ref, resource) in used_resources {
match *resource {
Resource::Buffer {
Expand Down Expand Up @@ -1593,12 +1709,28 @@ pub(in crate::command_buffer) struct RenderPassStateAttachmentResolveInfo {
pub(in crate::command_buffer) _image_layout: ImageLayout,
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub(in crate::command_buffer) struct DescriptorSetState {
pub(in crate::command_buffer) descriptor_sets: HashMap<u32, SetOrPush>,
pub(in crate::command_buffer) pipeline_layout: Arc<PipelineLayout>,
}

#[derive(Clone)]
impl Hash for DescriptorSetState {
/// HashMaps cannot be hashed, so we do our best job manually
fn hash<H: Hasher>(&self, state: &mut H) {
self.pipeline_layout.hash(state);
for (id, set_or_push) in &self.descriptor_sets {
id.hash(state);
match set_or_push {
SetOrPush::Set(set) => set.hash(state),
// push descriptors are expensive to hash and compare
SetOrPush::Push(_) => (),
}
}
}
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub(in crate::command_buffer) enum SetOrPush {
Set(DescriptorSetWithOffsets),
Push(DescriptorSetResources),
Expand Down
43 changes: 41 additions & 2 deletions vulkano/src/command_buffer/auto/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ use crate::{
buffer::Subbuffer,
device::{Device, DeviceOwned},
image::{Image, ImageLayout, ImageSubresourceRange},
pipeline::{ComputePipeline, GraphicsPipeline},
sync::PipelineStageAccessFlags,
DeviceSize, ValidationError, VulkanObject,
};
Expand Down Expand Up @@ -287,13 +288,51 @@ pub(super) enum Resource {
},
}

pub(in crate::command_buffer) struct UsedResources {
direct: Vec<(ResourceUseRef2, Resource)>,
deferred: Option<(PipelineEnum, DescriptorSetState)>,
}

#[derive(Clone, Eq, PartialEq)]
pub(in crate::command_buffer) enum PipelineEnum {
Compute(Arc<ComputePipeline>),
Graphics(Arc<GraphicsPipeline>),
}

impl Debug for PipelineEnum {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
PipelineEnum::Compute(p) => f.debug_tuple("Compute").field(&p.handle()).finish(),
PipelineEnum::Graphics(p) => f.debug_tuple("Graphics").field(&p.handle()).finish(),
}
}
}

impl UsedResources {
#[inline]
pub fn direct(direct: Vec<(ResourceUseRef2, Resource)>) -> Self {
Self {
direct,
deferred: None,
}
}

#[inline]
pub fn deferred(
direct: Vec<(ResourceUseRef2, Resource)>,
deferred: Option<(PipelineEnum, DescriptorSetState)>,
) -> Self {
Self { direct, deferred }
}
}

struct CommandInfo {
name: &'static str,
used_resources: Vec<(ResourceUseRef2, Resource)>,
used_resources: UsedResources,
render_pass: RenderPassCommand,
}

#[derive(Debug)]
#[derive(Copy, Clone, Debug)]
enum RenderPassCommand {
None,
Begin,
Expand Down
Loading
Loading