rt(d3d11/vk): allow for parallel shader compilation

OpenGL is for obvious reasons incompatible, and for DX12 the graphics pipeline creation has to be on the main thread so there isn't that much gain to be had.
This commit is contained in:
chyyran 2023-02-05 19:48:24 -05:00
parent b81b2b1d25
commit f5fe3e37ef
14 changed files with 265 additions and 151 deletions

100
Cargo.lock generated
View file

@ -301,6 +301,49 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "crossbeam-channel"
version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521"
dependencies = [
"cfg-if",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-deque"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc"
dependencies = [
"cfg-if",
"crossbeam-epoch",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-epoch"
version = "0.9.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01a9af1f4c2ef74bb8aa1f7e19706bc72d03598c8a570bb5de72243c7a9d9d5a"
dependencies = [
"autocfg",
"cfg-if",
"crossbeam-utils",
"memoffset 0.7.1",
"scopeguard",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f"
dependencies = [
"cfg-if",
]
[[package]]
name = "crossfont"
version = "0.5.1"
@ -606,6 +649,15 @@ version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
[[package]]
name = "hermit-abi"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7"
dependencies = [
"libc",
]
[[package]]
name = "ident_case"
version = "1.0.1"
@ -822,6 +874,7 @@ dependencies = [
"librashader-reflect",
"librashader-runtime",
"librashader-spirv-cross",
"rayon",
"rustc-hash",
"thiserror",
"windows",
@ -841,6 +894,7 @@ dependencies = [
"librashader-reflect",
"librashader-runtime",
"librashader-spirv-cross",
"rayon",
"rustc-hash",
"thiserror",
"windows",
@ -879,6 +933,7 @@ dependencies = [
"librashader-spirv-cross",
"num",
"raw-window-handle 0.5.0",
"rayon",
"rustc-hash",
"thiserror",
"winit",
@ -947,6 +1002,15 @@ dependencies = [
"autocfg",
]
[[package]]
name = "memoffset"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4"
dependencies = [
"autocfg",
]
[[package]]
name = "minimal-lexical"
version = "0.2.1"
@ -1059,7 +1123,7 @@ dependencies = [
"bitflags",
"cfg-if",
"libc",
"memoffset",
"memoffset 0.6.5",
]
[[package]]
@ -1072,7 +1136,7 @@ dependencies = [
"bitflags",
"cfg-if",
"libc",
"memoffset",
"memoffset 0.6.5",
]
[[package]]
@ -1181,6 +1245,16 @@ dependencies = [
"autocfg",
]
[[package]]
name = "num_cpus"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b"
dependencies = [
"hermit-abi",
"libc",
]
[[package]]
name = "num_enum"
version = "0.5.9"
@ -1353,6 +1427,28 @@ dependencies = [
"raw-window-handle 0.5.0",
]
[[package]]
name = "rayon"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6db3a213adf02b3bcfd2d3846bb41cb22857d131789e01df434fb7e7bc0759b7"
dependencies = [
"either",
"rayon-core",
]
[[package]]
name = "rayon-core"
version = "1.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "356a0625f1954f730c0201cdab48611198dc6ce21f4acff55089b5a78e6e835b"
dependencies = [
"crossbeam-channel",
"crossbeam-deque",
"crossbeam-utils",
"num_cpus",
]
[[package]]
name = "redox_syscall"
version = "0.2.16"

View file

@ -30,7 +30,7 @@ impl FromCompilation<GlslangCompilation> for GLSL {
compile: GlslangCompilation,
) -> Result<CompilerBackend<Self::Output>, ShaderReflectError> {
Ok(CompilerBackend {
backend: GlslReflect::try_from(compile)?,
backend: GlslReflect::try_from(&compile)?,
})
}
}
@ -52,7 +52,7 @@ impl FromCompilation<GlslangCompilation> for HLSL {
compile: GlslangCompilation,
) -> Result<CompilerBackend<Self::Output>, ShaderReflectError> {
Ok(CompilerBackend {
backend: HlslReflect::try_from(compile)?,
backend: HlslReflect::try_from(&compile)?,
})
}
}

View file

@ -24,9 +24,9 @@ impl FromCompilation<GlslangCompilation> for DXIL {
fn from_compilation(
compile: GlslangCompilation,
) -> Result<CompilerBackend<Self::Output>, ShaderReflectError> {
let vertex = compile.vertex.as_binary().to_vec();
let fragment = compile.fragment.as_binary().to_vec();
let reflect = GlslReflect::try_from(compile)?;
let reflect = GlslReflect::try_from(&compile)?;
let vertex = compile.vertex;
let fragment = compile.fragment;
Ok(CompilerBackend {
// we can just reuse WriteSpirV as the backend.
backend: WriteSpirV {

View file

@ -23,9 +23,9 @@ impl FromCompilation<GlslangCompilation> for SPIRV {
fn from_compilation(
compile: GlslangCompilation,
) -> Result<CompilerBackend<Self::Output>, ShaderReflectError> {
let vertex = compile.vertex.as_binary().to_vec();
let fragment = compile.fragment.as_binary().to_vec();
let reflect = GlslReflect::try_from(compile)?;
let reflect = GlslReflect::try_from(&compile)?;
let vertex = compile.vertex;
let fragment = compile.fragment;
Ok(CompilerBackend {
backend: WriteSpirV {
reflect,

View file

@ -1,11 +1,11 @@
use crate::error::ShaderCompileError;
use librashader_preprocess::ShaderSource;
use shaderc::{CompilationArtifact, CompileOptions, Limit, ShaderKind};
use shaderc::{CompileOptions, Limit, ShaderKind};
/// A reflectable shader compilation via glslang (shaderc).
pub struct GlslangCompilation {
pub(crate) vertex: CompilationArtifact,
pub(crate) fragment: CompilationArtifact,
pub(crate) vertex: Vec<u32>,
pub(crate) fragment: Vec<u32>,
}
impl GlslangCompilation {
@ -137,6 +137,11 @@ pub(crate) fn compile_spirv(
"main",
Some(&options),
)?;
// shaderc has a GIL so Send is unsafe.
let vertex = Vec::from(vertex.as_binary());
let fragment = Vec::from(fragment.as_binary());
Ok(GlslangCompilation { vertex, fragment })
}

View file

@ -7,7 +7,7 @@ use crate::reflect::semantics::{
MAX_BINDINGS_COUNT, MAX_PUSH_BUFFER_SIZE,
};
use crate::reflect::{align_uniform_size, ReflectShader};
use std::ops::Deref;
use std::ops::{Deref, DerefMut};
use spirv_cross::hlsl::ShaderModel;
use spirv_cross::spirv::{Ast, Decoration, Module, Resource, ShaderResources, Type};
@ -18,6 +18,9 @@ use crate::back::targets::{GLSL, HLSL};
use crate::back::{CompileShader, ShaderCompilerOutput};
use crate::reflect::helper::{SemanticErrorBlame, TextureData, UboData};
// This is "probably" OK.
unsafe impl<T: Send + spirv_cross::spirv::Target> Send for CrossReflect<T> {}
pub(crate) struct CrossReflect<T>
where
T: spirv_cross::spirv::Target,
@ -110,7 +113,7 @@ impl ValidateTypeSemantics<Type> for TextureSemantics {
}
}
impl<T> TryFrom<GlslangCompilation> for CrossReflect<T>
impl<T> TryFrom<&GlslangCompilation> for CrossReflect<T>
where
T: spirv_cross::spirv::Target,
Ast<T>: spirv_cross::spirv::Compile<T>,
@ -118,9 +121,9 @@ where
{
type Error = ShaderReflectError;
fn try_from(value: GlslangCompilation) -> Result<Self, Self::Error> {
let vertex_module = Module::from_words(value.vertex.as_binary());
let fragment_module = Module::from_words(value.fragment.as_binary());
fn try_from(value: &GlslangCompilation) -> Result<Self, Self::Error> {
let vertex_module = Module::from_words(&value.vertex);
let fragment_module = Module::from_words(&value.fragment);
let vertex = Ast::parse(&vertex_module)?;
let fragment = Ast::parse(&fragment_module)?;
@ -878,7 +881,7 @@ mod test {
);
}
let spirv = GlslangCompilation::compile(&result).unwrap();
let mut reflect = CrossReflect::<glsl::Target>::try_from(spirv).unwrap();
let mut reflect = CrossReflect::<glsl::Target>::try_from(&spirv).unwrap();
let _shader_reflection = reflect
.reflect(
0,

View file

@ -23,16 +23,16 @@ impl TryFrom<NagaCompilation> for NagaReflect {
}
}
impl TryFrom<GlslangCompilation> for NagaReflect {
impl TryFrom<&GlslangCompilation> for NagaReflect {
type Error = ShaderReflectError;
fn try_from(value: GlslangCompilation) -> Result<Self, Self::Error> {
fn try_from(value: &GlslangCompilation) -> Result<Self, Self::Error> {
let ops = Options::default();
let vertex =
naga::front::spv::Parser::new(value.vertex.as_binary().to_vec().into_iter(), &ops)
naga::front::spv::Parser::new(value.vertex.clone().into_iter(), &ops)
.parse()?;
let fragment =
naga::front::spv::Parser::new(value.fragment.as_binary().to_vec().into_iter(), &ops)
naga::front::spv::Parser::new(value.fragment.clone().into_iter(), &ops)
.parse()?;
Ok(NagaReflect { vertex, fragment })
}

View file

@ -22,6 +22,7 @@ spirv_cross = { package = "librashader-spirv-cross", version = "0.23" }
rustc-hash = "1.1.0"
bytemuck = "1.12.3"
rayon = "1.6.1"
[target.'cfg(windows)'.dependencies.windows]
version = "0.44.0"

View file

@ -32,13 +32,14 @@ use windows::Win32::Graphics::Direct3D11::{
D3D11_TEXTURE2D_DESC, D3D11_USAGE_DEFAULT, D3D11_USAGE_DYNAMIC,
};
use windows::Win32::Graphics::Dxgi::Common::DXGI_FORMAT_R8G8B8A8_UNORM;
use rayon::prelude::*;
pub struct FilterMutable {
pub(crate) passes_enabled: usize,
pub(crate) parameters: FxHashMap<String, f32>,
}
type ShaderPassMeta = ShaderPassArtifact<impl CompileReflectShader<HLSL, GlslangCompilation>>;
type ShaderPassMeta = ShaderPassArtifact<impl CompileReflectShader<HLSL, GlslangCompilation> + Send>;
/// A Direct3D 11 filter chain.
pub struct FilterChainD3D11 {
@ -220,81 +221,85 @@ impl FilterChainD3D11 {
passes: Vec<ShaderPassMeta>,
semantics: &ShaderSemantics,
) -> error::Result<Vec<FilterPass>> {
let mut filters = Vec::new();
// access to ID3D11Device is thread safe.
let filters: Vec<error::Result<FilterPass>> =
passes.into_par_iter()
.enumerate()
.map(|(index, (config, source, mut reflect)) | {
let reflection = reflect.reflect(index, semantics)?;
let hlsl = reflect.compile(None)?;
for (index, (config, source, mut reflect)) in passes.into_iter().enumerate() {
let reflection = reflect.reflect(index, semantics)?;
let hlsl = reflect.compile(None)?;
let vertex_dxbc =
util::d3d_compile_shader(hlsl.vertex.as_bytes(), b"main\0", b"vs_5_0\0")?;
let vs = d3d11_compile_bound_shader(
device,
&vertex_dxbc,
None,
ID3D11Device::CreateVertexShader,
)?;
let vertex_dxbc =
util::d3d_compile_shader(hlsl.vertex.as_bytes(), b"main\0", b"vs_5_0\0")?;
let vs = d3d11_compile_bound_shader(
device,
&vertex_dxbc,
None,
ID3D11Device::CreateVertexShader,
)?;
let ia_desc = DrawQuad::get_spirv_cross_vbo_desc();
let vao = util::d3d11_create_input_layout(device, &ia_desc, &vertex_dxbc)?;
let ia_desc = DrawQuad::get_spirv_cross_vbo_desc();
let vao = util::d3d11_create_input_layout(device, &ia_desc, &vertex_dxbc)?;
let fragment_dxbc =
util::d3d_compile_shader(hlsl.fragment.as_bytes(), b"main\0", b"ps_5_0\0")?;
let ps = d3d11_compile_bound_shader(
device,
&fragment_dxbc,
None,
ID3D11Device::CreatePixelShader,
)?;
let fragment_dxbc =
util::d3d_compile_shader(hlsl.fragment.as_bytes(), b"main\0", b"ps_5_0\0")?;
let ps = d3d11_compile_bound_shader(
device,
&fragment_dxbc,
None,
ID3D11Device::CreatePixelShader,
)?;
let ubo_cbuffer = if let Some(ubo) = &reflection.ubo && ubo.size != 0 {
let buffer = FilterChainD3D11::create_constant_buffer(device, ubo.size)?;
Some(ConstantBufferBinding {
binding: ubo.binding,
size: ubo.size,
stage_mask: ubo.stage_mask,
buffer,
})
} else {
None
};
let ubo_cbuffer = if let Some(ubo) = &reflection.ubo && ubo.size != 0 {
let buffer = FilterChainD3D11::create_constant_buffer(device, ubo.size)?;
Some(ConstantBufferBinding {
binding: ubo.binding,
size: ubo.size,
stage_mask: ubo.stage_mask,
buffer,
})
} else {
None
};
let push_cbuffer = if let Some(push) = &reflection.push_constant && push.size != 0 {
let buffer = FilterChainD3D11::create_constant_buffer(device, push.size)?;
Some(ConstantBufferBinding {
binding: if ubo_cbuffer.is_some() { 1 } else { 0 },
size: push.size,
stage_mask: push.stage_mask,
buffer,
})
} else {
None
};
let push_cbuffer = if let Some(push) = &reflection.push_constant && push.size != 0 {
let buffer = FilterChainD3D11::create_constant_buffer(device, push.size)?;
Some(ConstantBufferBinding {
binding: if ubo_cbuffer.is_some() { 1 } else { 0 },
size: push.size,
stage_mask: push.stage_mask,
buffer,
})
} else {
None
};
let uniform_storage = UniformStorage::new(
reflection.ubo.as_ref().map_or(0, |ubo| ubo.size as usize),
reflection
.push_constant
.as_ref()
.map_or(0, |push| push.size as usize),
);
let uniform_storage = UniformStorage::new(
reflection.ubo.as_ref().map_or(0, |ubo| ubo.size as usize),
reflection
.push_constant
.as_ref()
.map_or(0, |push| push.size as usize),
);
let uniform_bindings = reflection.meta.create_binding_map(|param| param.offset());
let uniform_bindings = reflection.meta.create_binding_map(|param| param.offset());
Ok(FilterPass {
reflection,
vertex_shader: vs,
vertex_layout: vao,
pixel_shader: ps,
uniform_bindings,
uniform_storage,
uniform_buffer: ubo_cbuffer,
push_buffer: push_cbuffer,
source,
config: config.clone(),
})
}).collect();
filters.push(FilterPass {
reflection,
compiled: hlsl,
vertex_shader: vs,
vertex_layout: vao,
pixel_shader: ps,
uniform_bindings,
uniform_storage,
uniform_buffer: ubo_cbuffer,
push_buffer: push_cbuffer,
source,
config: config.clone(),
})
}
let filters: error::Result<Vec<FilterPass>> = filters.into_iter().collect();
let filters = filters?;
Ok(filters)
}

View file

@ -34,7 +34,6 @@ pub struct ConstantBufferBinding {
// slang_process.cpp 141
pub struct FilterPass {
pub reflection: ShaderReflection,
pub compiled: ShaderCompilerOutput<String, CrossHlslContext>,
pub vertex_shader: ID3D11VertexShader,
pub vertex_layout: ID3D11InputLayout,
pub pixel_shader: ID3D11PixelShader,

View file

@ -38,7 +38,7 @@ mod tests {
// "../test/slang-shaders/bezel/Mega_Bezel/Presets/MBZ__0__SMOOTH-ADV.slangp",
// "../test/null.slangp",
const FILTER_PATH: &str = "../test/slang-shaders/bezel/koko-aio/monitor-bloom.slangp";
const FILTER_PATH: &str = "../test/slang-shaders/bezel/Mega_Bezel/Presets/MBZ__0__SMOOTH-ADV.slangp";
const IMAGE_PATH: &str = "../test/finalfightlong.png";
#[test]
fn triangle_d3d11_args() {
@ -84,7 +84,8 @@ mod tests {
force_no_mipmaps: false,
}),
// replace below with 'None' for the triangle
None,
// None,
Some(Image::load(IMAGE_PATH, UVDirection::TopLeft).unwrap())
)
.unwrap();
// let sample = hello_triangle_old::d3d11_hello_triangle::Sample::new(

View file

@ -25,6 +25,8 @@ bytemuck = "1.12.3"
thiserror = "1.0.37"
ash = { version = "0.37.1+1.3.235", features = ["linked", "debug"] }
rayon = "1.6.1"
[dev-dependencies]
num = "0.4.0"
glfw = "0.49.0"

View file

@ -31,6 +31,7 @@ use std::collections::VecDeque;
use std::path::Path;
use std::sync::Arc;
use rayon::prelude::*;
/// A Vulkan device and metadata that is required by the shader runtime.
pub struct VulkanObjects {
pub(crate) device: Arc<ash::Device>,
@ -39,7 +40,7 @@ pub struct VulkanObjects {
pipeline_cache: vk::PipelineCache,
}
type ShaderPassMeta = ShaderPassArtifact<impl CompileReflectShader<SPIRV, GlslangCompilation>>;
type ShaderPassMeta = ShaderPassArtifact<impl CompileReflectShader<SPIRV, GlslangCompilation> + Send>;
/// A collection of handles needed to access the Vulkan instance.
#[derive(Clone)]
@ -308,69 +309,66 @@ impl FilterChainVulkan {
frames_in_flight: u32,
use_render_pass: bool,
) -> error::Result<Box<[FilterPass]>> {
let mut filters = Vec::new();
let frames_in_flight = std::cmp::max(1, frames_in_flight);
// initialize passes
for (index, (config, source, mut reflect)) in passes.into_iter().enumerate() {
let reflection = reflect.reflect(index, semantics)?;
let spirv_words = reflect.compile(None)?;
let filters: Vec<error::Result<FilterPass>> = passes.into_par_iter()
.enumerate()
.map(|(index, (config, source, mut reflect))| {
let reflection = reflect.reflect(index, semantics)?;
let spirv_words = reflect.compile(None)?;
let ubo_size = reflection.ubo.as_ref().map_or(0, |ubo| ubo.size as usize);
let uniform_storage = UniformStorage::new_with_storage(
RawVulkanBuffer::new(
let ubo_size = reflection.ubo.as_ref().map_or(0, |ubo| ubo.size as usize);
let uniform_storage = UniformStorage::new_with_storage(
RawVulkanBuffer::new(
&vulkan.device,
&vulkan.memory_properties,
vk::BufferUsageFlags::UNIFORM_BUFFER,
ubo_size,
)?,
reflection
.push_constant
.as_ref()
.map_or(0, |push| push.size as usize),
);
let uniform_bindings = reflection.meta
.create_binding_map(|param| param.offset());
let render_pass_format = if !use_render_pass {
vk::Format::UNDEFINED
} else if let Some(format) = config.get_format_override() {
format.into()
} else if source.format != ImageFormat::Unknown {
source.format.into()
} else {
ImageFormat::R8G8B8A8Unorm.into()
};
let graphics_pipeline = VulkanGraphicsPipeline::new(
&vulkan.device,
&vulkan.memory_properties,
vk::BufferUsageFlags::UNIFORM_BUFFER,
ubo_size,
)?,
reflection
.push_constant
.as_ref()
.map_or(0, |push| push.size as usize),
);
&vulkan.pipeline_cache,
&spirv_words,
&reflection,
frames_in_flight,
render_pass_format,
)?;
let uniform_bindings = reflection.meta.create_binding_map(|param| param.offset());
let render_pass_format = if !use_render_pass {
vk::Format::UNDEFINED
} else if let Some(format) = config.get_format_override() {
format.into()
} else if source.format != ImageFormat::Unknown {
source.format.into()
} else {
ImageFormat::R8G8B8A8Unorm.into()
};
let graphics_pipeline = VulkanGraphicsPipeline::new(
&vulkan.device,
&vulkan.pipeline_cache,
&spirv_words,
&reflection,
frames_in_flight,
render_pass_format,
)?;
// let ubo_ring = VkUboRing::new(
// &vulkan.device,
// &vulkan.memory_properties,
// frames_in_flight as usize,
// ubo_size,
// )?;
filters.push(FilterPass {
device: vulkan.device.clone(),
reflection,
// compiled: spirv_words,
uniform_storage,
uniform_bindings,
source,
config,
graphics_pipeline,
// ubo_ring,
frames_in_flight,
});
}
Ok(FilterPass {
device: vulkan.device.clone(),
reflection,
// compiled: spirv_words,
uniform_storage,
uniform_bindings,
source,
config,
graphics_pipeline,
// ubo_ring,
frames_in_flight,
})
}).collect();
let filters: error::Result<Vec<FilterPass>> = filters.into_iter().collect();
let filters = filters?;
Ok(filters.into_boxed_slice())
}

View file

@ -134,6 +134,10 @@ impl<'a> Drop for VulkanBufferMapHandle<'a> {
}
}
/// SAFETY: Creating the pointer should be safe in multithreaded contexts.
///
/// Mutation is guarded by DerefMut<Target=[u8]>
unsafe impl Send for RawVulkanBuffer {}
pub struct RawVulkanBuffer {
buffer: ManuallyDrop<VulkanBuffer>,
ptr: NonNull<c_void>,