Bug 1917102: Update wgpu crates to ec2100052 (2024-09-05). r=supply-chain-reviewers,webgpu-reviewers,nical,ErichDonGubler

Demote some tests to backlog (filed as https://github.com/gfx-rs/wgpu/issues/6232):

- webgpu:shader,validation,const_assert,const_assert:constant_expression_assert:*
- webgpu:shader,validation,const_assert,const_assert:constant_expression_logical_and_assert:*
- webgpu:shader,validation,const_assert,const_assert:constant_expression_logical_or_assert:*

Differential Revision: https://phabricator.services.mozilla.com/D221272
This commit is contained in:
Jim Blandy 2024-09-06 20:44:53 +00:00
parent 7abf66380c
commit e6535b1d07
64 changed files with 1830 additions and 1137 deletions

View File

@ -25,9 +25,9 @@ git = "https://github.com/franziskuskiefer/cose-rust"
rev = "43c22248d136c8b38fe42ea709d08da6355cf04b"
replace-with = "vendored-sources"
[source."git+https://github.com/gfx-rs/wgpu?rev=bbdbafdf8a947b563b46f632a778632b906d9eb4"]
[source."git+https://github.com/gfx-rs/wgpu?rev=ec2100052132a047fe0799c084696d6c0ee7629d"]
git = "https://github.com/gfx-rs/wgpu"
rev = "bbdbafdf8a947b563b46f632a778632b906d9eb4"
rev = "ec2100052132a047fe0799c084696d6c0ee7629d"
replace-with = "vendored-sources"
[source."git+https://github.com/hsivonen/any_all_workaround?rev=7fb1b7034c9f172aade21ee1c8554e8d8a48af80"]

8
Cargo.lock generated
View File

@ -4025,7 +4025,7 @@ checksum = "a2983372caf4480544083767bf2d27defafe32af49ab4df3a0b7fc90793a3664"
[[package]]
name = "naga"
version = "22.0.0"
source = "git+https://github.com/gfx-rs/wgpu?rev=bbdbafdf8a947b563b46f632a778632b906d9eb4#bbdbafdf8a947b563b46f632a778632b906d9eb4"
source = "git+https://github.com/gfx-rs/wgpu?rev=ec2100052132a047fe0799c084696d6c0ee7629d#ec2100052132a047fe0799c084696d6c0ee7629d"
dependencies = [
"arrayvec",
"bit-set",
@ -6794,7 +6794,7 @@ dependencies = [
[[package]]
name = "wgpu-core"
version = "22.0.0"
source = "git+https://github.com/gfx-rs/wgpu?rev=bbdbafdf8a947b563b46f632a778632b906d9eb4#bbdbafdf8a947b563b46f632a778632b906d9eb4"
source = "git+https://github.com/gfx-rs/wgpu?rev=ec2100052132a047fe0799c084696d6c0ee7629d#ec2100052132a047fe0799c084696d6c0ee7629d"
dependencies = [
"arrayvec",
"bit-vec",
@ -6819,7 +6819,7 @@ dependencies = [
[[package]]
name = "wgpu-hal"
version = "22.0.0"
source = "git+https://github.com/gfx-rs/wgpu?rev=bbdbafdf8a947b563b46f632a778632b906d9eb4#bbdbafdf8a947b563b46f632a778632b906d9eb4"
source = "git+https://github.com/gfx-rs/wgpu?rev=ec2100052132a047fe0799c084696d6c0ee7629d#ec2100052132a047fe0799c084696d6c0ee7629d"
dependencies = [
"android_system_properties",
"arrayvec",
@ -6858,7 +6858,7 @@ dependencies = [
[[package]]
name = "wgpu-types"
version = "22.0.0"
source = "git+https://github.com/gfx-rs/wgpu?rev=bbdbafdf8a947b563b46f632a778632b906d9eb4#bbdbafdf8a947b563b46f632a778632b906d9eb4"
source = "git+https://github.com/gfx-rs/wgpu?rev=ec2100052132a047fe0799c084696d6c0ee7629d#ec2100052132a047fe0799c084696d6c0ee7629d"
dependencies = [
"bitflags 2.6.0",
"js-sys",

View File

@ -17,7 +17,7 @@ default = []
[dependencies.wgc]
package = "wgpu-core"
git = "https://github.com/gfx-rs/wgpu"
rev = "bbdbafdf8a947b563b46f632a778632b906d9eb4"
rev = "ec2100052132a047fe0799c084696d6c0ee7629d"
# TODO: remove the replay feature on the next update containing https://github.com/gfx-rs/wgpu/pull/5182
features = ["serde", "replay", "trace", "strict_asserts", "wgsl", "api_log_info"]
@ -26,32 +26,32 @@ features = ["serde", "replay", "trace", "strict_asserts", "wgsl", "api_log_info"
[target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies.wgc]
package = "wgpu-core"
git = "https://github.com/gfx-rs/wgpu"
rev = "bbdbafdf8a947b563b46f632a778632b906d9eb4"
rev = "ec2100052132a047fe0799c084696d6c0ee7629d"
features = ["metal"]
# We want the wgpu-core Direct3D backends on Windows.
[target.'cfg(windows)'.dependencies.wgc]
package = "wgpu-core"
git = "https://github.com/gfx-rs/wgpu"
rev = "bbdbafdf8a947b563b46f632a778632b906d9eb4"
rev = "ec2100052132a047fe0799c084696d6c0ee7629d"
features = ["dx12"]
# We want the wgpu-core Vulkan backend on Linux and Windows.
[target.'cfg(any(windows, all(unix, not(any(target_os = "macos", target_os = "ios")))))'.dependencies.wgc]
package = "wgpu-core"
git = "https://github.com/gfx-rs/wgpu"
rev = "bbdbafdf8a947b563b46f632a778632b906d9eb4"
rev = "ec2100052132a047fe0799c084696d6c0ee7629d"
features = ["vulkan"]
[dependencies.wgt]
package = "wgpu-types"
git = "https://github.com/gfx-rs/wgpu"
rev = "bbdbafdf8a947b563b46f632a778632b906d9eb4"
rev = "ec2100052132a047fe0799c084696d6c0ee7629d"
[dependencies.wgh]
package = "wgpu-hal"
git = "https://github.com/gfx-rs/wgpu"
rev = "bbdbafdf8a947b563b46f632a778632b906d9eb4"
rev = "ec2100052132a047fe0799c084696d6c0ee7629d"
features = ["oom_panic", "device_lost_panic", "internal_error_panic"]
[target.'cfg(windows)'.dependencies]

View File

@ -20,11 +20,11 @@ origin:
# Human-readable identifier for this version/release
# Generally "version NNN", "tag SSS", "bookmark SSS"
release: bbdbafdf8a947b563b46f632a778632b906d9eb4 (2024-08-29T02:44:20Z).
release: ec2100052132a047fe0799c084696d6c0ee7629d (Thu Sep 5 11:08:51 2024).
# Revision to pull in
# Must be a long or short commit SHA (long preferred)
revision: bbdbafdf8a947b563b46f632a778632b906d9eb4
revision: ec2100052132a047fe0799c084696d6c0ee7629d
license: ['MIT', 'Apache-2.0']

View File

@ -3112,12 +3112,12 @@ delta = "0.20.0 -> 22.0.0"
[[audits.naga]]
who = [
"Jim Blandy <jimb@red-bean.com>",
"Teodor Tanasoaia <ttanasoaia@mozilla.com>",
"Erich Gubler <erichdongubler@gmail.com>",
"Jim Blandy <jimb@red-bean.com>",
]
criteria = "safe-to-deploy"
delta = "22.0.0 -> 22.0.0@git:bbdbafdf8a947b563b46f632a778632b906d9eb4"
delta = "22.0.0 -> 22.0.0@git:ec2100052132a047fe0799c084696d6c0ee7629d"
importable = false
[[audits.net2]]
@ -5096,12 +5096,12 @@ delta = "0.20.0 -> 22.0.0"
[[audits.wgpu-core]]
who = [
"Jim Blandy <jimb@red-bean.com>",
"Teodor Tanasoaia <ttanasoaia@mozilla.com>",
"Erich Gubler <erichdongubler@gmail.com>",
"Jim Blandy <jimb@red-bean.com>",
]
criteria = "safe-to-deploy"
delta = "22.0.0 -> 22.0.0@git:bbdbafdf8a947b563b46f632a778632b906d9eb4"
delta = "22.0.0 -> 22.0.0@git:ec2100052132a047fe0799c084696d6c0ee7629d"
importable = false
[[audits.wgpu-hal]]
@ -5169,12 +5169,12 @@ delta = "0.20.0 -> 22.0.0"
[[audits.wgpu-hal]]
who = [
"Jim Blandy <jimb@red-bean.com>",
"Teodor Tanasoaia <ttanasoaia@mozilla.com>",
"Erich Gubler <erichdongubler@gmail.com>",
"Jim Blandy <jimb@red-bean.com>",
]
criteria = "safe-to-deploy"
delta = "22.0.0 -> 22.0.0@git:bbdbafdf8a947b563b46f632a778632b906d9eb4"
delta = "22.0.0 -> 22.0.0@git:ec2100052132a047fe0799c084696d6c0ee7629d"
importable = false
[[audits.wgpu-types]]
@ -5242,12 +5242,12 @@ delta = "0.20.0 -> 22.0.0"
[[audits.wgpu-types]]
who = [
"Jim Blandy <jimb@red-bean.com>",
"Teodor Tanasoaia <ttanasoaia@mozilla.com>",
"Erich Gubler <erichdongubler@gmail.com>",
"Jim Blandy <jimb@red-bean.com>",
]
criteria = "safe-to-deploy"
delta = "22.0.0 -> 22.0.0@git:bbdbafdf8a947b563b46f632a778632b906d9eb4"
delta = "22.0.0 -> 22.0.0@git:ec2100052132a047fe0799c084696d6c0ee7629d"
importable = false
[[audits.whatsys]]

View File

@ -1,13 +1,19 @@
[cts.https.html?q=webgpu:shader,validation,const_assert,const_assert:constant_expression_assert:*]
implementation-status: backlog
[:scope="function"]
expected: FAIL
[:scope="module"]
expected: FAIL
[cts.https.html?q=webgpu:shader,validation,const_assert,const_assert:constant_expression_logical_and_assert:*]
implementation-status: backlog
[:scope="function"]
expected: FAIL
[:scope="module"]
expected: FAIL
[cts.https.html?q=webgpu:shader,validation,const_assert,const_assert:constant_expression_logical_and_no_assert:*]
@ -20,9 +26,12 @@
[cts.https.html?q=webgpu:shader,validation,const_assert,const_assert:constant_expression_logical_or_assert:*]
implementation-status: backlog
[:scope="function"]
expected: FAIL
[:scope="module"]
expected: FAIL
[cts.https.html?q=webgpu:shader,validation,const_assert,const_assert:constant_expression_logical_or_no_assert:*]

File diff suppressed because one or more lines are too long

View File

@ -92,7 +92,7 @@ optional = true
version = "1.1.0"
[dependencies.serde]
version = "1.0.208"
version = "1.0.209"
features = ["derive"]
optional = true
@ -120,6 +120,9 @@ version = "0.11"
[dev-dependencies.hlsl-snapshots]
path = "./hlsl-snapshots"
[dev-dependencies.itertools]
version = "0.10.5"
[dev-dependencies.rspirv]
version = "0.11"
git = "https://github.com/gfx-rs/rspirv"

View File

@ -47,7 +47,7 @@ pub use features::Features;
use crate::{
back::{self, Baked},
proc::{self, NameKey},
proc::{self, ExpressionKindTracker, NameKey},
valid, Handle, ShaderStage, TypeInner,
};
use features::FeaturesManager;
@ -498,6 +498,9 @@ pub enum Error {
Custom(String),
#[error("overrides should not be present at this stage")]
Override,
/// [`crate::Sampling::First`] is unsupported.
#[error("`{:?}` sampling is unsupported", crate::Sampling::First)]
FirstSamplingNotSupported,
}
/// Binary operation with a different logic on the GLSL side.
@ -1534,7 +1537,7 @@ impl<'a, W: Write> Writer<'a, W> {
// here, regardless of the version.
if let Some(sampling) = sampling {
if emit_interpolation_and_auxiliary {
if let Some(qualifier) = glsl_sampling(sampling) {
if let Some(qualifier) = glsl_sampling(sampling)? {
write!(self.out, "{qualifier} ")?;
}
}
@ -1584,6 +1587,7 @@ impl<'a, W: Write> Writer<'a, W> {
info,
expressions: &func.expressions,
named_expressions: &func.named_expressions,
expr_kind_tracker: ExpressionKindTracker::from_arena(&func.expressions),
};
self.named_expressions.clear();
@ -4770,14 +4774,15 @@ const fn glsl_interpolation(interpolation: crate::Interpolation) -> &'static str
}
/// Return the GLSL auxiliary qualifier for the given sampling value.
const fn glsl_sampling(sampling: crate::Sampling) -> Option<&'static str> {
const fn glsl_sampling(sampling: crate::Sampling) -> BackendResult<Option<&'static str>> {
use crate::Sampling as S;
match sampling {
S::Center => None,
Ok(match sampling {
S::First => return Err(Error::FirstSamplingNotSupported),
S::Center | S::Either => None,
S::Centroid => Some("centroid"),
S::Sample => Some("sample"),
}
})
}
/// Helper function that returns the glsl dimension string of [`ImageDimension`](crate::ImageDimension)

View File

@ -202,7 +202,7 @@ impl crate::Sampling {
/// Return the HLSL auxiliary qualifier for the given sampling value.
pub(super) const fn to_hlsl_str(self) -> Option<&'static str> {
match self {
Self::Center => None,
Self::Center | Self::First | Self::Either => None,
Self::Centroid => Some("centroid"),
Self::Sample => Some("sample"),
}

View File

@ -8,7 +8,7 @@ use super::{
};
use crate::{
back::{self, Baked},
proc::{self, NameKey},
proc::{self, ExpressionKindTracker, NameKey},
valid, Handle, Module, Scalar, ScalarKind, ShaderStage, TypeInner,
};
use std::{fmt, mem};
@ -346,6 +346,7 @@ impl<'a, W: fmt::Write> super::Writer<'a, W> {
info,
expressions: &function.expressions,
named_expressions: &function.named_expressions,
expr_kind_tracker: ExpressionKindTracker::from_arena(&function.expressions),
};
let name = self.names[&NameKey::Function(handle)].clone();
@ -386,6 +387,7 @@ impl<'a, W: fmt::Write> super::Writer<'a, W> {
info,
expressions: &ep.function.expressions,
named_expressions: &ep.function.named_expressions,
expr_kind_tracker: ExpressionKindTracker::from_arena(&ep.function.expressions),
};
self.write_wrapped_functions(module, &ctx)?;

View File

@ -3,6 +3,8 @@ Backend functions that export shader [`Module`](super::Module)s into binary and
*/
#![allow(dead_code)] // can be dead if none of the enabled backends need it
use crate::proc::ExpressionKindTracker;
#[cfg(dot_out)]
pub mod dot;
#[cfg(glsl_out)]
@ -118,6 +120,8 @@ pub struct FunctionCtx<'a> {
pub expressions: &'a crate::Arena<crate::Expression>,
/// Map of expressions that have associated variable names
pub named_expressions: &'a crate::NamedExpressions,
/// For constness checks
pub expr_kind_tracker: ExpressionKindTracker,
}
impl FunctionCtx<'_> {

View File

@ -627,6 +627,7 @@ impl ResolvedInterpolation {
(I::Linear, S::Centroid) => Self::CentroidNoPerspective,
(I::Linear, S::Sample) => Self::SampleNoPerspective,
(I::Flat, _) => Self::Flat,
_ => unreachable!(),
}
}

View File

@ -289,6 +289,7 @@ fn process_function(
&mut local_expression_kind_tracker,
&mut emitter,
&mut block,
false,
);
for (old_h, mut expr, span) in expressions.drain() {

View File

@ -1511,7 +1511,12 @@ impl Writer {
}
match sampling {
// Center sampling is the default in SPIR-V.
None | Some(crate::Sampling::Center) => (),
None
| Some(
crate::Sampling::Center
| crate::Sampling::First
| crate::Sampling::Either,
) => (),
Some(crate::Sampling::Centroid) => {
self.decorate(id, Decoration::Centroid, &[]);
}

View File

@ -1,7 +1,7 @@
use super::Error;
use crate::{
back::{self, Baked},
proc::{self, NameKey},
proc::{self, ExpressionKindTracker, NameKey},
valid, Handle, Module, ShaderStage, TypeInner,
};
use std::fmt::Write;
@ -166,6 +166,7 @@ impl<W: Write> Writer<W> {
info: fun_info,
expressions: &function.expressions,
named_expressions: &function.named_expressions,
expr_kind_tracker: ExpressionKindTracker::from_arena(&function.expressions),
};
// Write the function
@ -193,6 +194,7 @@ impl<W: Write> Writer<W> {
info: info.get_entry_point(index),
expressions: &ep.function.expressions,
named_expressions: &ep.function.named_expressions,
expr_kind_tracker: ExpressionKindTracker::from_arena(&ep.function.expressions),
};
self.write_function(module, &ep.function, &func_ctx)?;
@ -1115,8 +1117,14 @@ impl<W: Write> Writer<W> {
func_ctx: &back::FunctionCtx,
name: &str,
) -> BackendResult {
// Some functions are marked as const, but are not yet implemented as constant expression
let quantifier = if func_ctx.expr_kind_tracker.is_impl_const(handle) {
"const"
} else {
"let"
};
// Write variable name
write!(self.out, "let {name}")?;
write!(self.out, "{quantifier} {name}")?;
if self.flags.contains(WriterFlags::EXPLICIT_TYPES) {
write!(self.out, ": ")?;
let ty = &func_ctx.info[handle].ty;
@ -2053,6 +2061,8 @@ const fn sampling_str(sampling: crate::Sampling) -> &'static str {
S::Center => "",
S::Centroid => "centroid",
S::Sample => "sample",
S::First => "first",
S::Either => "either",
}
}

View File

@ -263,6 +263,8 @@ pub(crate) enum Error<'a> {
limit: u8,
},
PipelineConstantIDValue(Span),
NotBool(Span),
ConstAssertFailed(Span),
}
#[derive(Clone, Debug)]
@ -815,6 +817,22 @@ impl<'a> Error<'a> {
)],
notes: vec![],
},
Error::NotBool(span) => ParseError {
message: "must be a const-expression that resolves to a bool".to_string(),
labels: vec![(
span,
"must resolve to bool".into(),
)],
notes: vec![],
},
Error::ConstAssertFailed(span) => ParseError {
message: "const_assert failure".to_string(),
labels: vec![(
span,
"evaluates to false".into(),
)],
notes: vec![],
},
}
}
}

View File

@ -20,13 +20,16 @@ impl<'a> Index<'a> {
// While doing so, reject conflicting definitions.
let mut globals = FastHashMap::with_capacity_and_hasher(tu.decls.len(), Default::default());
for (handle, decl) in tu.decls.iter() {
let ident = decl_ident(decl);
let name = ident.name;
if let Some(old) = globals.insert(name, handle) {
return Err(Error::Redefinition {
previous: decl_ident(&tu.decls[old]).span,
current: ident.span,
});
if let Some(ident) = decl_ident(decl) {
let name = ident.name;
if let Some(old) = globals.insert(name, handle) {
return Err(Error::Redefinition {
previous: decl_ident(&tu.decls[old])
.expect("decl should have ident for redefinition")
.span,
current: ident.span,
});
}
}
}
@ -130,7 +133,7 @@ impl<'a> DependencySolver<'a, '_> {
return if dep_id == id {
// A declaration refers to itself directly.
Err(Error::RecursiveDeclaration {
ident: decl_ident(decl).span,
ident: decl_ident(decl).expect("decl should have ident").span,
usage: dep.usage,
})
} else {
@ -146,14 +149,19 @@ impl<'a> DependencySolver<'a, '_> {
.unwrap_or(0);
Err(Error::CyclicDeclaration {
ident: decl_ident(&self.module.decls[dep_id]).span,
ident: decl_ident(&self.module.decls[dep_id])
.expect("decl should have ident")
.span,
path: self.path[start_at..]
.iter()
.map(|curr_dep| {
let curr_id = curr_dep.decl;
let curr_decl = &self.module.decls[curr_id];
(decl_ident(curr_decl).span, curr_dep.usage)
(
decl_ident(curr_decl).expect("decl should have ident").span,
curr_dep.usage,
)
})
.collect(),
})
@ -182,13 +190,14 @@ impl<'a> DependencySolver<'a, '_> {
}
}
const fn decl_ident<'a>(decl: &ast::GlobalDecl<'a>) -> ast::Ident<'a> {
const fn decl_ident<'a>(decl: &ast::GlobalDecl<'a>) -> Option<ast::Ident<'a>> {
match decl.kind {
ast::GlobalDeclKind::Fn(ref f) => f.name,
ast::GlobalDeclKind::Var(ref v) => v.name,
ast::GlobalDeclKind::Const(ref c) => c.name,
ast::GlobalDeclKind::Override(ref o) => o.name,
ast::GlobalDeclKind::Struct(ref s) => s.name,
ast::GlobalDeclKind::Type(ref t) => t.name,
ast::GlobalDeclKind::Fn(ref f) => Some(f.name),
ast::GlobalDeclKind::Var(ref v) => Some(v.name),
ast::GlobalDeclKind::Const(ref c) => Some(c.name),
ast::GlobalDeclKind::Override(ref o) => Some(o.name),
ast::GlobalDeclKind::Struct(ref s) => Some(s.name),
ast::GlobalDeclKind::Type(ref t) => Some(t.name),
ast::GlobalDeclKind::ConstAssert(_) => None,
}
}

View File

@ -578,8 +578,13 @@ impl<'source, 'temp> Lowerer<'source, 'temp> {
Constructor::Type(ty)
}
ast::ConstructorType::PartialVector { size } => Constructor::PartialVector { size },
ast::ConstructorType::Vector { size, scalar } => {
let ty = ctx.ensure_type_exists(scalar.to_inner_vector(size));
ast::ConstructorType::Vector { size, ty, ty_span } => {
let ty = self.resolve_ast_type(ty, &mut ctx.as_global())?;
let scalar = match ctx.module.types[ty].inner {
crate::TypeInner::Scalar(sc) => sc,
_ => return Err(Error::UnknownScalarType(ty_span)),
};
let ty = ctx.ensure_type_exists(crate::TypeInner::Vector { size, scalar });
Constructor::Type(ty)
}
ast::ConstructorType::PartialMatrix { columns, rows } => {
@ -588,13 +593,22 @@ impl<'source, 'temp> Lowerer<'source, 'temp> {
ast::ConstructorType::Matrix {
rows,
columns,
width,
ty,
ty_span,
} => {
let ty = ctx.ensure_type_exists(crate::TypeInner::Matrix {
columns,
rows,
scalar: crate::Scalar::float(width),
});
let ty = self.resolve_ast_type(ty, &mut ctx.as_global())?;
let scalar = match ctx.module.types[ty].inner {
crate::TypeInner::Scalar(sc) => sc,
_ => return Err(Error::UnknownScalarType(ty_span)),
};
let ty = match scalar.kind {
crate::ScalarKind::Float => ctx.ensure_type_exists(crate::TypeInner::Matrix {
columns,
rows,
scalar,
}),
_ => return Err(Error::BadMatrixScalarKind(ty_span, scalar)),
};
Constructor::Type(ty)
}
ast::ConstructorType::PartialArray => Constructor::PartialArray,

View File

@ -98,7 +98,7 @@ impl<'source> GlobalContext<'source, '_, '_> {
types: self.types,
module: self.module,
const_typifier: self.const_typifier,
expr_type: ExpressionContextType::Constant,
expr_type: ExpressionContextType::Constant(None),
global_expression_kind_tracker: self.global_expression_kind_tracker,
}
}
@ -160,7 +160,8 @@ pub struct StatementContext<'source, 'temp, 'out> {
///
/// [`LocalVariable`]: crate::Expression::LocalVariable
/// [`FunctionArgument`]: crate::Expression::FunctionArgument
local_table: &'temp mut FastHashMap<Handle<ast::Local>, Typed<Handle<crate::Expression>>>,
local_table:
&'temp mut FastHashMap<Handle<ast::Local>, Declared<Typed<Handle<crate::Expression>>>>,
const_typifier: &'temp mut Typifier,
typifier: &'temp mut Typifier,
@ -184,6 +185,32 @@ pub struct StatementContext<'source, 'temp, 'out> {
}
impl<'a, 'temp> StatementContext<'a, 'temp, '_> {
fn as_const<'t>(
&'t mut self,
block: &'t mut crate::Block,
emitter: &'t mut Emitter,
) -> ExpressionContext<'a, 't, '_>
where
'temp: 't,
{
ExpressionContext {
globals: self.globals,
types: self.types,
ast_expressions: self.ast_expressions,
const_typifier: self.const_typifier,
global_expression_kind_tracker: self.global_expression_kind_tracker,
module: self.module,
expr_type: ExpressionContextType::Constant(Some(LocalExpressionContext {
local_table: self.local_table,
function: self.function,
block,
emitter,
typifier: self.typifier,
local_expression_kind_tracker: self.local_expression_kind_tracker,
})),
}
}
fn as_expression<'t>(
&'t mut self,
block: &'t mut crate::Block,
@ -199,7 +226,7 @@ impl<'a, 'temp> StatementContext<'a, 'temp, '_> {
const_typifier: self.const_typifier,
global_expression_kind_tracker: self.global_expression_kind_tracker,
module: self.module,
expr_type: ExpressionContextType::Runtime(RuntimeExpressionContext {
expr_type: ExpressionContextType::Runtime(LocalExpressionContext {
local_table: self.local_table,
function: self.function,
block,
@ -235,12 +262,12 @@ impl<'a, 'temp> StatementContext<'a, 'temp, '_> {
}
}
pub struct RuntimeExpressionContext<'temp, 'out> {
pub struct LocalExpressionContext<'temp, 'out> {
/// A map from [`ast::Local`] handles to the Naga expressions we've built for them.
///
/// This is always [`StatementContext::local_table`] for the
/// enclosing statement; see that documentation for details.
local_table: &'temp FastHashMap<Handle<ast::Local>, Typed<Handle<crate::Expression>>>,
local_table: &'temp FastHashMap<Handle<ast::Local>, Declared<Typed<Handle<crate::Expression>>>>,
function: &'out mut crate::Function,
block: &'temp mut crate::Block,
@ -259,18 +286,18 @@ pub enum ExpressionContextType<'temp, 'out> {
/// We are lowering to an arbitrary runtime expression, to be
/// included in a function's body.
///
/// The given [`RuntimeExpressionContext`] holds information about local
/// The given [`LocalExpressionContext`] holds information about local
/// variables, arguments, and other definitions available only to runtime
/// expressions, not constant or override expressions.
Runtime(RuntimeExpressionContext<'temp, 'out>),
Runtime(LocalExpressionContext<'temp, 'out>),
/// We are lowering to a constant expression, to be included in the module's
/// constant expression arena.
///
/// Everything constant expressions are allowed to refer to is
/// available in the [`ExpressionContext`], so this variant
/// carries no further information.
Constant,
/// Everything global constant expressions are allowed to refer to is
/// available in the [`ExpressionContext`], but local constant expressions can
/// also refer to other
Constant(Option<LocalExpressionContext<'temp, 'out>>),
/// We are lowering to an override expression, to be included in the module's
/// constant expression arena.
@ -352,7 +379,7 @@ impl<'source, 'temp, 'out> ExpressionContext<'source, 'temp, 'out> {
ast_expressions: self.ast_expressions,
const_typifier: self.const_typifier,
module: self.module,
expr_type: ExpressionContextType::Constant,
expr_type: ExpressionContextType::Constant(None),
global_expression_kind_tracker: self.global_expression_kind_tracker,
}
}
@ -376,8 +403,19 @@ impl<'source, 'temp, 'out> ExpressionContext<'source, 'temp, 'out> {
rctx.local_expression_kind_tracker,
rctx.emitter,
rctx.block,
false,
),
ExpressionContextType::Constant => ConstantEvaluator::for_wgsl_module(
ExpressionContextType::Constant(Some(ref mut rctx)) => {
ConstantEvaluator::for_wgsl_function(
self.module,
&mut rctx.function.expressions,
rctx.local_expression_kind_tracker,
rctx.emitter,
rctx.block,
true,
)
}
ExpressionContextType::Constant(None) => ConstantEvaluator::for_wgsl_module(
self.module,
self.global_expression_kind_tracker,
false,
@ -412,15 +450,27 @@ impl<'source, 'temp, 'out> ExpressionContext<'source, 'temp, 'out> {
.eval_expr_to_u32_from(handle, &ctx.function.expressions)
.ok()
}
ExpressionContextType::Constant => self.module.to_ctx().eval_expr_to_u32(handle).ok(),
ExpressionContextType::Constant(Some(ref ctx)) => {
assert!(ctx.local_expression_kind_tracker.is_const(handle));
self.module
.to_ctx()
.eval_expr_to_u32_from(handle, &ctx.function.expressions)
.ok()
}
ExpressionContextType::Constant(None) => {
self.module.to_ctx().eval_expr_to_u32(handle).ok()
}
ExpressionContextType::Override => None,
}
}
fn get_expression_span(&self, handle: Handle<crate::Expression>) -> Span {
match self.expr_type {
ExpressionContextType::Runtime(ref ctx) => ctx.function.expressions.get_span(handle),
ExpressionContextType::Constant | ExpressionContextType::Override => {
ExpressionContextType::Runtime(ref ctx)
| ExpressionContextType::Constant(Some(ref ctx)) => {
ctx.function.expressions.get_span(handle)
}
ExpressionContextType::Constant(None) | ExpressionContextType::Override => {
self.module.global_expressions.get_span(handle)
}
}
@ -428,20 +478,35 @@ impl<'source, 'temp, 'out> ExpressionContext<'source, 'temp, 'out> {
fn typifier(&self) -> &Typifier {
match self.expr_type {
ExpressionContextType::Runtime(ref ctx) => ctx.typifier,
ExpressionContextType::Constant | ExpressionContextType::Override => {
ExpressionContextType::Runtime(ref ctx)
| ExpressionContextType::Constant(Some(ref ctx)) => ctx.typifier,
ExpressionContextType::Constant(None) | ExpressionContextType::Override => {
self.const_typifier
}
}
}
fn local(
&mut self,
local: &Handle<ast::Local>,
span: Span,
) -> Result<Typed<Handle<crate::Expression>>, Error<'source>> {
match self.expr_type {
ExpressionContextType::Runtime(ref ctx) => Ok(ctx.local_table[local].runtime()),
ExpressionContextType::Constant(Some(ref ctx)) => ctx.local_table[local]
.const_time()
.ok_or(Error::UnexpectedOperationInConstContext(span)),
_ => Err(Error::UnexpectedOperationInConstContext(span)),
}
}
fn runtime_expression_ctx(
&mut self,
span: Span,
) -> Result<&mut RuntimeExpressionContext<'temp, 'out>, Error<'source>> {
) -> Result<&mut LocalExpressionContext<'temp, 'out>, Error<'source>> {
match self.expr_type {
ExpressionContextType::Runtime(ref mut ctx) => Ok(ctx),
ExpressionContextType::Constant | ExpressionContextType::Override => {
ExpressionContextType::Constant(_) | ExpressionContextType::Override => {
Err(Error::UnexpectedOperationInConstContext(span))
}
}
@ -480,7 +545,7 @@ impl<'source, 'temp, 'out> ExpressionContext<'source, 'temp, 'out> {
}
// This means a `gather` operation appeared in a constant expression.
// This error refers to the `gather` itself, not its "component" argument.
ExpressionContextType::Constant | ExpressionContextType::Override => {
ExpressionContextType::Constant(_) | ExpressionContextType::Override => {
Err(Error::UnexpectedOperationInConstContext(gather_span))
}
}
@ -505,8 +570,9 @@ impl<'source, 'temp, 'out> ExpressionContext<'source, 'temp, 'out> {
// except that this lets the borrow checker see that it's okay
// to also borrow self.module.types mutably below.
let typifier = match self.expr_type {
ExpressionContextType::Runtime(ref ctx) => ctx.typifier,
ExpressionContextType::Constant | ExpressionContextType::Override => {
ExpressionContextType::Runtime(ref ctx)
| ExpressionContextType::Constant(Some(ref ctx)) => ctx.typifier,
ExpressionContextType::Constant(None) | ExpressionContextType::Override => {
&*self.const_typifier
}
};
@ -542,7 +608,8 @@ impl<'source, 'temp, 'out> ExpressionContext<'source, 'temp, 'out> {
let typifier;
let expressions;
match self.expr_type {
ExpressionContextType::Runtime(ref mut ctx) => {
ExpressionContextType::Runtime(ref mut ctx)
| ExpressionContextType::Constant(Some(ref mut ctx)) => {
resolve_ctx = ResolveContext::with_locals(
self.module,
&ctx.function.local_variables,
@ -551,7 +618,7 @@ impl<'source, 'temp, 'out> ExpressionContext<'source, 'temp, 'out> {
typifier = &mut *ctx.typifier;
expressions = &ctx.function.expressions;
}
ExpressionContextType::Constant | ExpressionContextType::Override => {
ExpressionContextType::Constant(None) | ExpressionContextType::Override => {
resolve_ctx = ResolveContext::with_locals(self.module, &empty_arena, &[]);
typifier = self.const_typifier;
expressions = &self.module.global_expressions;
@ -643,18 +710,20 @@ impl<'source, 'temp, 'out> ExpressionContext<'source, 'temp, 'out> {
span: Span,
) -> Result<Handle<crate::Expression>, Error<'source>> {
match self.expr_type {
ExpressionContextType::Runtime(ref mut rctx) => {
ExpressionContextType::Runtime(ref mut rctx)
| ExpressionContextType::Constant(Some(ref mut rctx)) => {
rctx.block
.extend(rctx.emitter.finish(&rctx.function.expressions));
}
ExpressionContextType::Constant | ExpressionContextType::Override => {}
ExpressionContextType::Constant(None) | ExpressionContextType::Override => {}
}
let result = self.append_expression(expression, span);
match self.expr_type {
ExpressionContextType::Runtime(ref mut rctx) => {
ExpressionContextType::Runtime(ref mut rctx)
| ExpressionContextType::Constant(Some(ref mut rctx)) => {
rctx.emitter.start(&rctx.function.expressions);
}
ExpressionContextType::Constant | ExpressionContextType::Override => {}
ExpressionContextType::Constant(None) | ExpressionContextType::Override => {}
}
result
}
@ -718,6 +787,30 @@ impl<'source> ArgumentContext<'_, 'source> {
}
}
#[derive(Debug, Copy, Clone)]
enum Declared<T> {
/// Value declared as const
Const(T),
/// Value declared as non-const
Runtime(T),
}
impl<T> Declared<T> {
fn runtime(self) -> T {
match self {
Declared::Const(t) | Declared::Runtime(t) => t,
}
}
fn const_time(self) -> Option<T> {
match self {
Declared::Const(t) => Some(t),
Declared::Runtime(_) => None,
}
}
}
/// WGSL type annotations on expressions, types, values, etc.
///
/// Naga and WGSL types are very close, but Naga lacks WGSL's `ref` types, which
@ -814,7 +907,13 @@ impl Components {
*comp = Self::letter_component(ch).ok_or(Error::BadAccessor(name_span))?;
}
Ok(Components::Swizzle { size, pattern })
if name.chars().all(|c| matches!(c, 'x' | 'y' | 'z' | 'w'))
|| name.chars().all(|c| matches!(c, 'r' | 'g' | 'b' | 'a'))
{
Ok(Components::Swizzle { size, pattern })
} else {
Err(Error::BadAccessor(name_span))
}
}
}
@ -935,16 +1034,21 @@ impl<'source, 'temp> Lowerer<'source, 'temp> {
ctx.globals.insert(f.name.name, lowered_decl);
}
ast::GlobalDeclKind::Var(ref v) => {
let ty = self.resolve_ast_type(v.ty, &mut ctx)?;
let explicit_ty =
v.ty.map(|ast| self.resolve_ast_type(ast, &mut ctx))
.transpose()?;
let init;
if let Some(init_ast) = v.init {
let mut ectx = ctx.as_override();
let lowered = self.expression_for_abstract(init_ast, &mut ectx)?;
let ty_res = crate::proc::TypeResolution::Handle(ty);
let converted = ectx
.try_automatic_conversions(lowered, &ty_res, v.name.span)
.map_err(|error| match error {
let mut ectx = ctx.as_override();
let ty;
let initializer;
match (v.init, explicit_ty) {
(Some(init), Some(explicit_ty)) => {
let init = self.expression_for_abstract(init, &mut ectx)?;
let ty_res = crate::proc::TypeResolution::Handle(explicit_ty);
let init = ectx
.try_automatic_conversions(init, &ty_res, v.name.span)
.map_err(|error| match error {
Error::AutoConversion(e) => Error::InitializationTypeMismatch {
name: v.name.span,
expected: e.dest_type,
@ -952,9 +1056,19 @@ impl<'source, 'temp> Lowerer<'source, 'temp> {
},
other => other,
})?;
init = Some(converted);
} else {
init = None;
ty = explicit_ty;
initializer = Some(init);
}
(Some(init), None) => {
let concretized = self.expression(init, &mut ectx)?;
ty = ectx.register_type(concretized)?;
initializer = Some(concretized);
}
(None, Some(explicit_ty)) => {
ty = explicit_ty;
initializer = None;
}
(None, None) => return Err(Error::DeclMissingTypeAndInit(v.name.span)),
}
let binding = if let Some(ref binding) = v.binding {
@ -972,7 +1086,7 @@ impl<'source, 'temp> Lowerer<'source, 'temp> {
space: v.space,
binding,
ty,
init,
init: initializer,
},
span,
);
@ -1090,6 +1204,20 @@ impl<'source, 'temp> Lowerer<'source, 'temp> {
ctx.globals
.insert(alias.name.name, LoweredGlobalDecl::Type(ty));
}
ast::GlobalDeclKind::ConstAssert(condition) => {
let condition = self.expression(condition, &mut ctx.as_const())?;
let span = ctx.module.global_expressions.get_span(condition);
match ctx
.module
.to_ctx()
.eval_expr_to_bool_from(condition, &ctx.module.global_expressions)
{
Some(true) => Ok(()),
Some(false) => Err(Error::ConstAssertFailed(span)),
_ => Err(Error::NotBool(span)),
}?;
}
}
}
@ -1120,7 +1248,7 @@ impl<'source, 'temp> Lowerer<'source, 'temp> {
let ty = self.resolve_ast_type(arg.ty, ctx)?;
let expr = expressions
.append(crate::Expression::FunctionArgument(i as u32), arg.name.span);
local_table.insert(arg.handle, Typed::Plain(expr));
local_table.insert(arg.handle, Declared::Runtime(Typed::Plain(expr)));
named_expressions.insert(expr, (arg.name.name.to_string(), arg.name.span));
local_expression_kind_tracker.insert(expr, crate::proc::ExpressionKind::Runtime);
@ -1268,7 +1396,8 @@ impl<'source, 'temp> Lowerer<'source, 'temp> {
}
block.extend(emitter.finish(&ctx.function.expressions));
ctx.local_table.insert(l.handle, Typed::Plain(value));
ctx.local_table
.insert(l.handle, Declared::Runtime(Typed::Plain(value)));
ctx.named_expressions
.insert(value, (l.name.name.to_string(), l.name.span));
@ -1350,7 +1479,8 @@ impl<'source, 'temp> Lowerer<'source, 'temp> {
Span::UNDEFINED,
)?;
block.extend(emitter.finish(&ctx.function.expressions));
ctx.local_table.insert(v.handle, Typed::Reference(handle));
ctx.local_table
.insert(v.handle, Declared::Runtime(Typed::Reference(handle)));
match initializer {
Some(initializer) => crate::Statement::Store {
@ -1360,6 +1490,41 @@ impl<'source, 'temp> Lowerer<'source, 'temp> {
None => return Ok(()),
}
}
ast::LocalDecl::Const(ref c) => {
let mut emitter = Emitter::default();
emitter.start(&ctx.function.expressions);
let ectx = &mut ctx.as_const(block, &mut emitter);
let mut init = self.expression_for_abstract(c.init, ectx)?;
if let Some(explicit_ty) = c.ty {
let explicit_ty =
self.resolve_ast_type(explicit_ty, &mut ectx.as_global())?;
let explicit_ty_res = crate::proc::TypeResolution::Handle(explicit_ty);
init = ectx
.try_automatic_conversions(init, &explicit_ty_res, c.name.span)
.map_err(|error| match error {
Error::AutoConversion(error) => Error::InitializationTypeMismatch {
name: c.name.span,
expected: error.dest_type,
got: error.source_type,
},
other => other,
})?;
} else {
init = ectx.concretize(init)?;
ectx.register_type(init)?;
}
block.extend(emitter.finish(&ctx.function.expressions));
ctx.local_table
.insert(c.handle, Declared::Const(Typed::Plain(init)));
ctx.named_expressions
.insert(init, (c.name.name.to_string(), c.name.span));
return Ok(());
}
},
ast::StatementKind::If {
condition,
@ -1591,6 +1756,28 @@ impl<'source, 'temp> Lowerer<'source, 'temp> {
value,
}
}
ast::StatementKind::ConstAssert(condition) => {
let mut emitter = Emitter::default();
emitter.start(&ctx.function.expressions);
let condition =
self.expression(condition, &mut ctx.as_const(block, &mut emitter))?;
let span = ctx.function.expressions.get_span(condition);
match ctx
.module
.to_ctx()
.eval_expr_to_bool_from(condition, &ctx.function.expressions)
{
Some(true) => Ok(()),
Some(false) => Err(Error::ConstAssertFailed(span)),
_ => Err(Error::NotBool(span)),
}?;
block.extend(emitter.finish(&ctx.function.expressions));
return Ok(());
}
ast::StatementKind::Ignore(expr) => {
let mut emitter = Emitter::default();
emitter.start(&ctx.function.expressions);
@ -1658,8 +1845,7 @@ impl<'source, 'temp> Lowerer<'source, 'temp> {
return Ok(Typed::Plain(handle));
}
ast::Expression::Ident(ast::IdentExpr::Local(local)) => {
let rctx = ctx.runtime_expression_ctx(span)?;
return Ok(rctx.local_table[&local]);
return ctx.local(&local, span);
}
ast::Expression::Ident(ast::IdentExpr::Unresolved(name)) => {
let global = ctx
@ -2830,16 +3016,34 @@ impl<'source, 'temp> Lowerer<'source, 'temp> {
) -> Result<Handle<crate::Type>, Error<'source>> {
let inner = match ctx.types[handle] {
ast::Type::Scalar(scalar) => scalar.to_inner_scalar(),
ast::Type::Vector { size, scalar } => scalar.to_inner_vector(size),
ast::Type::Vector { size, ty, ty_span } => {
let ty = self.resolve_ast_type(ty, ctx)?;
let scalar = match ctx.module.types[ty].inner {
crate::TypeInner::Scalar(sc) => sc,
_ => return Err(Error::UnknownScalarType(ty_span)),
};
crate::TypeInner::Vector { size, scalar }
}
ast::Type::Matrix {
rows,
columns,
width,
} => crate::TypeInner::Matrix {
columns,
rows,
scalar: crate::Scalar::float(width),
},
ty,
ty_span,
} => {
let ty = self.resolve_ast_type(ty, ctx)?;
let scalar = match ctx.module.types[ty].inner {
crate::TypeInner::Scalar(sc) => sc,
_ => return Err(Error::UnknownScalarType(ty_span)),
};
match scalar.kind {
crate::ScalarKind::Float => crate::TypeInner::Matrix {
columns,
rows,
scalar,
},
_ => return Err(Error::BadMatrixScalarKind(ty_span, scalar)),
}
}
ast::Type::Atomic(scalar) => scalar.to_inner_atomic(),
ast::Type::Pointer { base, space } => {
let base = self.resolve_ast_type(base, ctx)?;

View File

@ -85,6 +85,7 @@ pub enum GlobalDeclKind<'a> {
Override(Override<'a>),
Struct(Struct<'a>),
Type(TypeAlias<'a>),
ConstAssert(Handle<Expression<'a>>),
}
#[derive(Debug)]
@ -109,7 +110,7 @@ pub struct EntryPoint<'a> {
}
#[cfg(doc)]
use crate::front::wgsl::lower::{RuntimeExpressionContext, StatementContext};
use crate::front::wgsl::lower::{LocalExpressionContext, StatementContext};
#[derive(Debug)]
pub struct Function<'a> {
@ -142,7 +143,7 @@ pub struct GlobalVariable<'a> {
pub name: Ident<'a>,
pub space: crate::AddressSpace,
pub binding: Option<ResourceBinding<'a>>,
pub ty: Handle<Type<'a>>,
pub ty: Option<Handle<Type<'a>>>,
pub init: Option<Handle<Expression<'a>>>,
}
@ -198,12 +199,14 @@ pub enum Type<'a> {
Scalar(Scalar),
Vector {
size: crate::VectorSize,
scalar: Scalar,
ty: Handle<Type<'a>>,
ty_span: Span,
},
Matrix {
columns: crate::VectorSize,
rows: crate::VectorSize,
width: crate::Bytes,
ty: Handle<Type<'a>>,
ty_span: Span,
},
Atomic(Scalar),
Pointer {
@ -282,6 +285,7 @@ pub enum StatementKind<'a> {
Increment(Handle<Expression<'a>>),
Decrement(Handle<Expression<'a>>),
Ignore(Handle<Expression<'a>>),
ConstAssert(Handle<Expression<'a>>),
}
#[derive(Debug)]
@ -330,7 +334,8 @@ pub enum ConstructorType<'a> {
/// `vec3<f32>(1.0)`.
Vector {
size: crate::VectorSize,
scalar: Scalar,
ty: Handle<Type<'a>>,
ty_span: Span,
},
/// A matrix construction whose component type is inferred from the
@ -345,7 +350,8 @@ pub enum ConstructorType<'a> {
Matrix {
columns: crate::VectorSize,
rows: crate::VectorSize,
width: crate::Bytes,
ty: Handle<Type<'a>>,
ty_span: Span,
},
/// An array whose component type and size are inferred from the arguments:
@ -460,14 +466,23 @@ pub struct Let<'a> {
pub handle: Handle<Local>,
}
#[derive(Debug)]
pub struct LocalConst<'a> {
pub name: Ident<'a>,
pub ty: Option<Handle<Type<'a>>>,
pub init: Handle<Expression<'a>>,
pub handle: Handle<Local>,
}
#[derive(Debug)]
pub enum LocalDecl<'a> {
Var(LocalVariable<'a>),
Let(Let<'a>),
Const(LocalConst<'a>),
}
#[derive(Debug)]
/// A placeholder for a local variable declaration.
///
/// See [`Function::locals`] for more information.
/// See [`super::ExpressionContext::locals`] for more information.
pub struct Local;

View File

@ -58,6 +58,8 @@ pub fn map_sampling(word: &str, span: Span) -> Result<crate::Sampling, Error<'_>
"center" => Ok(crate::Sampling::Center),
"centroid" => Ok(crate::Sampling::Centroid),
"sample" => Ok(crate::Sampling::Sample),
"first" => Ok(crate::Sampling::First),
"either" => Ok(crate::Sampling::Either),
_ => Err(Error::UnknownAttribute(span)),
}
}

View File

@ -31,20 +31,20 @@ struct ExpressionContext<'input, 'temp, 'out> {
/// A map from identifiers in scope to the locals/arguments they represent.
///
/// The handles refer to the [`Function::locals`] area; see that field's
/// The handles refer to the [`locals`] arena; see that field's
/// documentation for details.
///
/// [`Function::locals`]: ast::Function::locals
/// [`locals`]: ExpressionContext::locals
local_table: &'temp mut SymbolTable<&'input str, Handle<ast::Local>>,
/// Local variable and function argument arena for the function we're building.
///
/// Note that the `Local` here is actually a zero-sized type. The AST keeps
/// all the detailed information about locals - names, types, etc. - in
/// [`LocalDecl`] statements. For arguments, that information is kept in
/// [`arguments`]. This `Arena`'s only role is to assign a unique `Handle`
/// to each of them, and track their definitions' spans for use in
/// diagnostics.
/// Note that the [`ast::Local`] here is actually a zero-sized type. This
/// `Arena`'s only role is to assign a unique `Handle` to each local
/// identifier, and track its definition's span for use in diagnostics. All
/// the detailed information about locals - names, types, etc. - is kept in
/// the [`LocalDecl`] statements we parsed from their declarations. For
/// arguments, that information is kept in [`arguments`].
///
/// In the AST, when an [`Ident`] expression refers to a local variable or
/// argument, its [`IdentExpr`] holds the referent's `Handle<Local>` in this
@ -53,14 +53,15 @@ struct ExpressionContext<'input, 'temp, 'out> {
/// During lowering, [`LocalDecl`] statements add entries to a per-function
/// table that maps `Handle<Local>` values to their Naga representations,
/// accessed via [`StatementContext::local_table`] and
/// [`RuntimeExpressionContext::local_table`]. This table is then consulted when
/// [`LocalExpressionContext::local_table`]. This table is then consulted when
/// lowering subsequent [`Ident`] expressions.
///
/// [`LocalDecl`]: StatementKind::LocalDecl
/// [`arguments`]: Function::arguments
/// [`Ident`]: Expression::Ident
/// [`StatementContext::local_table`]: StatementContext::local_table
/// [`RuntimeExpressionContext::local_table`]: RuntimeExpressionContext::local_table
/// [`LocalDecl`]: ast::StatementKind::LocalDecl
/// [`arguments`]: ast::Function::arguments
/// [`Ident`]: ast::Expression::Ident
/// [`IdentExpr`]: ast::IdentExpr
/// [`StatementContext::local_table`]: super::lower::StatementContext::local_table
/// [`LocalExpressionContext::local_table`]: super::lower::LocalExpressionContext::local_table
locals: &'out mut Arena<ast::Local>,
/// Identifiers used by the current global declaration that have no local definition.
@ -111,6 +112,11 @@ impl<'a> ExpressionContext<'a, '_, '_> {
Ok(handle)
}
}
fn new_scalar(&mut self, scalar: Scalar) -> Handle<ast::Type<'a>> {
self.types
.append(ast::Type::Scalar(scalar), Span::UNDEFINED)
}
}
/// Which grammar rule we are in the midst of parsing.
@ -310,25 +316,22 @@ impl Parser {
"vec2i" => {
return Ok(Some(ast::ConstructorType::Vector {
size: crate::VectorSize::Bi,
scalar: Scalar {
kind: crate::ScalarKind::Sint,
width: 4,
},
ty: ctx.new_scalar(Scalar::I32),
ty_span: Span::UNDEFINED,
}))
}
"vec2u" => {
return Ok(Some(ast::ConstructorType::Vector {
size: crate::VectorSize::Bi,
scalar: Scalar {
kind: crate::ScalarKind::Uint,
width: 4,
},
ty: ctx.new_scalar(Scalar::U32),
ty_span: Span::UNDEFINED,
}))
}
"vec2f" => {
return Ok(Some(ast::ConstructorType::Vector {
size: crate::VectorSize::Bi,
scalar: Scalar::F32,
ty: ctx.new_scalar(Scalar::F32),
ty_span: Span::UNDEFINED,
}))
}
"vec3" => ast::ConstructorType::PartialVector {
@ -337,19 +340,22 @@ impl Parser {
"vec3i" => {
return Ok(Some(ast::ConstructorType::Vector {
size: crate::VectorSize::Tri,
scalar: Scalar::I32,
ty: ctx.new_scalar(Scalar::I32),
ty_span: Span::UNDEFINED,
}))
}
"vec3u" => {
return Ok(Some(ast::ConstructorType::Vector {
size: crate::VectorSize::Tri,
scalar: Scalar::U32,
ty: ctx.new_scalar(Scalar::U32),
ty_span: Span::UNDEFINED,
}))
}
"vec3f" => {
return Ok(Some(ast::ConstructorType::Vector {
size: crate::VectorSize::Tri,
scalar: Scalar::F32,
ty: ctx.new_scalar(Scalar::F32),
ty_span: Span::UNDEFINED,
}))
}
"vec4" => ast::ConstructorType::PartialVector {
@ -358,19 +364,22 @@ impl Parser {
"vec4i" => {
return Ok(Some(ast::ConstructorType::Vector {
size: crate::VectorSize::Quad,
scalar: Scalar::I32,
ty: ctx.new_scalar(Scalar::I32),
ty_span: Span::UNDEFINED,
}))
}
"vec4u" => {
return Ok(Some(ast::ConstructorType::Vector {
size: crate::VectorSize::Quad,
scalar: Scalar::U32,
ty: ctx.new_scalar(Scalar::U32),
ty_span: Span::UNDEFINED,
}))
}
"vec4f" => {
return Ok(Some(ast::ConstructorType::Vector {
size: crate::VectorSize::Quad,
scalar: Scalar::F32,
ty: ctx.new_scalar(Scalar::F32),
ty_span: Span::UNDEFINED,
}))
}
"mat2x2" => ast::ConstructorType::PartialMatrix {
@ -381,7 +390,8 @@ impl Parser {
return Ok(Some(ast::ConstructorType::Matrix {
columns: crate::VectorSize::Bi,
rows: crate::VectorSize::Bi,
width: 4,
ty: ctx.new_scalar(Scalar::F32),
ty_span: Span::UNDEFINED,
}))
}
"mat2x3" => ast::ConstructorType::PartialMatrix {
@ -392,7 +402,8 @@ impl Parser {
return Ok(Some(ast::ConstructorType::Matrix {
columns: crate::VectorSize::Bi,
rows: crate::VectorSize::Tri,
width: 4,
ty: ctx.new_scalar(Scalar::F32),
ty_span: Span::UNDEFINED,
}))
}
"mat2x4" => ast::ConstructorType::PartialMatrix {
@ -403,7 +414,8 @@ impl Parser {
return Ok(Some(ast::ConstructorType::Matrix {
columns: crate::VectorSize::Bi,
rows: crate::VectorSize::Quad,
width: 4,
ty: ctx.new_scalar(Scalar::F32),
ty_span: Span::UNDEFINED,
}))
}
"mat3x2" => ast::ConstructorType::PartialMatrix {
@ -414,7 +426,8 @@ impl Parser {
return Ok(Some(ast::ConstructorType::Matrix {
columns: crate::VectorSize::Tri,
rows: crate::VectorSize::Bi,
width: 4,
ty: ctx.new_scalar(Scalar::F32),
ty_span: Span::UNDEFINED,
}))
}
"mat3x3" => ast::ConstructorType::PartialMatrix {
@ -425,7 +438,8 @@ impl Parser {
return Ok(Some(ast::ConstructorType::Matrix {
columns: crate::VectorSize::Tri,
rows: crate::VectorSize::Tri,
width: 4,
ty: ctx.new_scalar(Scalar::F32),
ty_span: Span::UNDEFINED,
}))
}
"mat3x4" => ast::ConstructorType::PartialMatrix {
@ -436,7 +450,8 @@ impl Parser {
return Ok(Some(ast::ConstructorType::Matrix {
columns: crate::VectorSize::Tri,
rows: crate::VectorSize::Quad,
width: 4,
ty: ctx.new_scalar(Scalar::F32),
ty_span: Span::UNDEFINED,
}))
}
"mat4x2" => ast::ConstructorType::PartialMatrix {
@ -447,7 +462,8 @@ impl Parser {
return Ok(Some(ast::ConstructorType::Matrix {
columns: crate::VectorSize::Quad,
rows: crate::VectorSize::Bi,
width: 4,
ty: ctx.new_scalar(Scalar::F32),
ty_span: Span::UNDEFINED,
}))
}
"mat4x3" => ast::ConstructorType::PartialMatrix {
@ -458,7 +474,8 @@ impl Parser {
return Ok(Some(ast::ConstructorType::Matrix {
columns: crate::VectorSize::Quad,
rows: crate::VectorSize::Tri,
width: 4,
ty: ctx.new_scalar(Scalar::F32),
ty_span: Span::UNDEFINED,
}))
}
"mat4x4" => ast::ConstructorType::PartialMatrix {
@ -469,7 +486,8 @@ impl Parser {
return Ok(Some(ast::ConstructorType::Matrix {
columns: crate::VectorSize::Quad,
rows: crate::VectorSize::Quad,
width: 4,
ty: ctx.new_scalar(Scalar::F32),
ty_span: Span::UNDEFINED,
}))
}
"array" => ast::ConstructorType::PartialArray,
@ -502,19 +520,17 @@ impl Parser {
// parse component type if present
match (lexer.peek().0, partial) {
(Token::Paren('<'), ast::ConstructorType::PartialVector { size }) => {
let scalar = lexer.next_scalar_generic()?;
Ok(Some(ast::ConstructorType::Vector { size, scalar }))
let (ty, ty_span) = self.singular_generic(lexer, ctx)?;
Ok(Some(ast::ConstructorType::Vector { size, ty, ty_span }))
}
(Token::Paren('<'), ast::ConstructorType::PartialMatrix { columns, rows }) => {
let (scalar, span) = lexer.next_scalar_generic_with_span()?;
match scalar.kind {
crate::ScalarKind::Float => Ok(Some(ast::ConstructorType::Matrix {
columns,
rows,
width: scalar.width,
})),
_ => Err(Error::BadMatrixScalarKind(span, scalar)),
}
let (ty, ty_span) = self.singular_generic(lexer, ctx)?;
Ok(Some(ast::ConstructorType::Matrix {
columns,
rows,
ty,
ty_span,
}))
}
(Token::Paren('<'), ast::ConstructorType::PartialArray) => {
lexer.expect_generic_paren('<')?;
@ -570,11 +586,7 @@ impl Parser {
let expr = match name {
// bitcast looks like a function call, but it's an operator and must be handled differently.
"bitcast" => {
lexer.expect_generic_paren('<')?;
let start = lexer.start_byte_offset();
let to = self.type_decl(lexer, ctx)?;
let span = lexer.span_from(start);
lexer.expect_generic_paren('>')?;
let (to, span) = self.singular_generic(lexer, ctx)?;
lexer.open_arguments()?;
let expr = self.general_expression(lexer, ctx)?;
@ -980,8 +992,12 @@ impl Parser {
lexer.expect(Token::Paren('>'))?;
}
let name = lexer.next_ident()?;
lexer.expect(Token::Separator(':'))?;
let ty = self.type_decl(lexer, ctx)?;
let ty = if lexer.skip(Token::Separator(':')) {
Some(self.type_decl(lexer, ctx)?)
} else {
None
};
let init = if lexer.skip(Token::Operation('=')) {
let handle = self.general_expression(lexer, ctx)?;
@ -1058,21 +1074,34 @@ impl Parser {
Ok(members)
}
fn matrix_scalar_type<'a>(
/// Parses `<T>`, returning T and span of T
fn singular_generic<'a>(
&mut self,
lexer: &mut Lexer<'a>,
ctx: &mut ExpressionContext<'a, '_, '_>,
) -> Result<(Handle<ast::Type<'a>>, Span), Error<'a>> {
lexer.expect_generic_paren('<')?;
let start = lexer.start_byte_offset();
let ty = self.type_decl(lexer, ctx)?;
let span = lexer.span_from(start);
lexer.expect_generic_paren('>')?;
Ok((ty, span))
}
fn matrix_with_type<'a>(
&mut self,
lexer: &mut Lexer<'a>,
ctx: &mut ExpressionContext<'a, '_, '_>,
columns: crate::VectorSize,
rows: crate::VectorSize,
) -> Result<ast::Type<'a>, Error<'a>> {
let (scalar, span) = lexer.next_scalar_generic_with_span()?;
match scalar.kind {
crate::ScalarKind::Float => Ok(ast::Type::Matrix {
columns,
rows,
width: scalar.width,
}),
_ => Err(Error::BadMatrixScalarKind(span, scalar)),
}
let (ty, ty_span) = self.singular_generic(lexer, ctx)?;
Ok(ast::Type::Matrix {
columns,
rows,
ty,
ty_span,
})
}
fn type_decl_impl<'a>(
@ -1087,151 +1116,154 @@ impl Parser {
Ok(Some(match word {
"vec2" => {
let scalar = lexer.next_scalar_generic()?;
let (ty, ty_span) = self.singular_generic(lexer, ctx)?;
ast::Type::Vector {
size: crate::VectorSize::Bi,
scalar,
ty,
ty_span,
}
}
"vec2i" => ast::Type::Vector {
size: crate::VectorSize::Bi,
scalar: Scalar {
kind: crate::ScalarKind::Sint,
width: 4,
},
ty: ctx.new_scalar(Scalar::I32),
ty_span: Span::UNDEFINED,
},
"vec2u" => ast::Type::Vector {
size: crate::VectorSize::Bi,
scalar: Scalar {
kind: crate::ScalarKind::Uint,
width: 4,
},
ty: ctx.new_scalar(Scalar::U32),
ty_span: Span::UNDEFINED,
},
"vec2f" => ast::Type::Vector {
size: crate::VectorSize::Bi,
scalar: Scalar::F32,
ty: ctx.new_scalar(Scalar::F32),
ty_span: Span::UNDEFINED,
},
"vec3" => {
let scalar = lexer.next_scalar_generic()?;
let (ty, ty_span) = self.singular_generic(lexer, ctx)?;
ast::Type::Vector {
size: crate::VectorSize::Tri,
scalar,
ty,
ty_span,
}
}
"vec3i" => ast::Type::Vector {
size: crate::VectorSize::Tri,
scalar: Scalar {
kind: crate::ScalarKind::Sint,
width: 4,
},
ty: ctx.new_scalar(Scalar::I32),
ty_span: Span::UNDEFINED,
},
"vec3u" => ast::Type::Vector {
size: crate::VectorSize::Tri,
scalar: Scalar {
kind: crate::ScalarKind::Uint,
width: 4,
},
ty: ctx.new_scalar(Scalar::U32),
ty_span: Span::UNDEFINED,
},
"vec3f" => ast::Type::Vector {
size: crate::VectorSize::Tri,
scalar: Scalar::F32,
ty: ctx.new_scalar(Scalar::F32),
ty_span: Span::UNDEFINED,
},
"vec4" => {
let scalar = lexer.next_scalar_generic()?;
let (ty, ty_span) = self.singular_generic(lexer, ctx)?;
ast::Type::Vector {
size: crate::VectorSize::Quad,
scalar,
ty,
ty_span,
}
}
"vec4i" => ast::Type::Vector {
size: crate::VectorSize::Quad,
scalar: Scalar {
kind: crate::ScalarKind::Sint,
width: 4,
},
ty: ctx.new_scalar(Scalar::I32),
ty_span: Span::UNDEFINED,
},
"vec4u" => ast::Type::Vector {
size: crate::VectorSize::Quad,
scalar: Scalar {
kind: crate::ScalarKind::Uint,
width: 4,
},
ty: ctx.new_scalar(Scalar::U32),
ty_span: Span::UNDEFINED,
},
"vec4f" => ast::Type::Vector {
size: crate::VectorSize::Quad,
scalar: Scalar::F32,
ty: ctx.new_scalar(Scalar::F32),
ty_span: Span::UNDEFINED,
},
"mat2x2" => {
self.matrix_scalar_type(lexer, crate::VectorSize::Bi, crate::VectorSize::Bi)?
self.matrix_with_type(lexer, ctx, crate::VectorSize::Bi, crate::VectorSize::Bi)?
}
"mat2x2f" => ast::Type::Matrix {
columns: crate::VectorSize::Bi,
rows: crate::VectorSize::Bi,
width: 4,
ty: ctx.new_scalar(Scalar::F32),
ty_span: Span::UNDEFINED,
},
"mat2x3" => {
self.matrix_scalar_type(lexer, crate::VectorSize::Bi, crate::VectorSize::Tri)?
self.matrix_with_type(lexer, ctx, crate::VectorSize::Bi, crate::VectorSize::Tri)?
}
"mat2x3f" => ast::Type::Matrix {
columns: crate::VectorSize::Bi,
rows: crate::VectorSize::Tri,
width: 4,
ty: ctx.new_scalar(Scalar::F32),
ty_span: Span::UNDEFINED,
},
"mat2x4" => {
self.matrix_scalar_type(lexer, crate::VectorSize::Bi, crate::VectorSize::Quad)?
self.matrix_with_type(lexer, ctx, crate::VectorSize::Bi, crate::VectorSize::Quad)?
}
"mat2x4f" => ast::Type::Matrix {
columns: crate::VectorSize::Bi,
rows: crate::VectorSize::Quad,
width: 4,
ty: ctx.new_scalar(Scalar::F32),
ty_span: Span::UNDEFINED,
},
"mat3x2" => {
self.matrix_scalar_type(lexer, crate::VectorSize::Tri, crate::VectorSize::Bi)?
self.matrix_with_type(lexer, ctx, crate::VectorSize::Tri, crate::VectorSize::Bi)?
}
"mat3x2f" => ast::Type::Matrix {
columns: crate::VectorSize::Tri,
rows: crate::VectorSize::Bi,
width: 4,
ty: ctx.new_scalar(Scalar::F32),
ty_span: Span::UNDEFINED,
},
"mat3x3" => {
self.matrix_scalar_type(lexer, crate::VectorSize::Tri, crate::VectorSize::Tri)?
self.matrix_with_type(lexer, ctx, crate::VectorSize::Tri, crate::VectorSize::Tri)?
}
"mat3x3f" => ast::Type::Matrix {
columns: crate::VectorSize::Tri,
rows: crate::VectorSize::Tri,
width: 4,
ty: ctx.new_scalar(Scalar::F32),
ty_span: Span::UNDEFINED,
},
"mat3x4" => {
self.matrix_scalar_type(lexer, crate::VectorSize::Tri, crate::VectorSize::Quad)?
self.matrix_with_type(lexer, ctx, crate::VectorSize::Tri, crate::VectorSize::Quad)?
}
"mat3x4f" => ast::Type::Matrix {
columns: crate::VectorSize::Tri,
rows: crate::VectorSize::Quad,
width: 4,
ty: ctx.new_scalar(Scalar::F32),
ty_span: Span::UNDEFINED,
},
"mat4x2" => {
self.matrix_scalar_type(lexer, crate::VectorSize::Quad, crate::VectorSize::Bi)?
self.matrix_with_type(lexer, ctx, crate::VectorSize::Quad, crate::VectorSize::Bi)?
}
"mat4x2f" => ast::Type::Matrix {
columns: crate::VectorSize::Quad,
rows: crate::VectorSize::Bi,
width: 4,
ty: ctx.new_scalar(Scalar::F32),
ty_span: Span::UNDEFINED,
},
"mat4x3" => {
self.matrix_scalar_type(lexer, crate::VectorSize::Quad, crate::VectorSize::Tri)?
self.matrix_with_type(lexer, ctx, crate::VectorSize::Quad, crate::VectorSize::Tri)?
}
"mat4x3f" => ast::Type::Matrix {
columns: crate::VectorSize::Quad,
rows: crate::VectorSize::Tri,
width: 4,
ty: ctx.new_scalar(Scalar::F32),
ty_span: Span::UNDEFINED,
},
"mat4x4" => {
self.matrix_scalar_type(lexer, crate::VectorSize::Quad, crate::VectorSize::Quad)?
self.matrix_with_type(lexer, ctx, crate::VectorSize::Quad, crate::VectorSize::Quad)?
}
"mat4x4f" => ast::Type::Matrix {
columns: crate::VectorSize::Quad,
rows: crate::VectorSize::Quad,
width: 4,
ty: ctx.new_scalar(Scalar::F32),
ty_span: Span::UNDEFINED,
},
"atomic" => {
let scalar = lexer.next_scalar_generic()?;
@ -1688,6 +1720,28 @@ impl Parser {
handle,
}))
}
"const" => {
let _ = lexer.next();
let name = lexer.next_ident()?;
let given_ty = if lexer.skip(Token::Separator(':')) {
let ty = self.type_decl(lexer, ctx)?;
Some(ty)
} else {
None
};
lexer.expect(Token::Operation('='))?;
let expr_id = self.general_expression(lexer, ctx)?;
lexer.expect(Token::Separator(';'))?;
let handle = ctx.declare_local(name)?;
ast::StatementKind::LocalDecl(ast::LocalDecl::Const(ast::LocalConst {
name,
ty: given_ty,
init: expr_id,
handle,
}))
}
"var" => {
let _ = lexer.next();
@ -1963,6 +2017,20 @@ impl Parser {
lexer.expect(Token::Separator(';'))?;
ast::StatementKind::Kill
}
// https://www.w3.org/TR/WGSL/#const-assert-statement
"const_assert" => {
let _ = lexer.next();
// parentheses are optional
let paren = lexer.skip(Token::Paren('('));
let condition = self.general_expression(lexer, ctx)?;
if paren {
lexer.expect(Token::Paren(')'))?;
}
lexer.expect(Token::Separator(';'))?;
ast::StatementKind::ConstAssert(condition)
}
// assignment or a function call
_ => {
self.function_call_or_assignment_statement(lexer, ctx, block)?;
@ -2366,6 +2434,18 @@ impl Parser {
..function
}))
}
(Token::Word("const_assert"), _) => {
// parentheses are optional
let paren = lexer.skip(Token::Paren('('));
let condition = self.general_expression(lexer, &mut ctx)?;
if paren {
lexer.expect(Token::Paren(')'))?;
}
lexer.expect(Token::Separator(';'))?;
Some(ast::GlobalDeclKind::ConstAssert(condition))
}
(Token::End, _) => return Ok(()),
other => return Err(Error::Unexpected(other.1, ExpectedToken::GlobalItem)),
};

View File

@ -530,6 +530,13 @@ pub enum Sampling {
/// Interpolate the value at each sample location. In multisampling, invoke
/// the fragment shader once per sample.
Sample,
/// Use the value provided by the first vertex of the current primitive.
First,
/// Use the value provided by the first or last vertex of the current primitive. The exact
/// choice is implementation-dependent.
Either,
}
/// Member of a user-defined structure.

View File

@ -317,7 +317,7 @@ pub struct ConstantEvaluator<'a> {
#[derive(Debug)]
enum WgslRestrictions<'a> {
/// - const-expressions will be evaluated and inserted in the arena
Const,
Const(Option<FunctionLocalData<'a>>),
/// - const-expressions will be evaluated and inserted in the arena
/// - override-expressions will be inserted in the arena
Override,
@ -347,6 +347,8 @@ struct FunctionLocalData<'a> {
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
pub enum ExpressionKind {
/// If const is also implemented as const
ImplConst,
Const,
Override,
Runtime,
@ -372,14 +374,23 @@ impl ExpressionKindTracker {
pub fn insert(&mut self, value: Handle<Expression>, expr_type: ExpressionKind) {
self.inner.insert(value, expr_type);
}
pub fn is_const(&self, h: Handle<Expression>) -> bool {
matches!(self.type_of(h), ExpressionKind::Const)
matches!(
self.type_of(h),
ExpressionKind::Const | ExpressionKind::ImplConst
)
}
/// Returns `true` if naga can also evaluate expression as const
pub fn is_impl_const(&self, h: Handle<Expression>) -> bool {
matches!(self.type_of(h), ExpressionKind::ImplConst)
}
pub fn is_const_or_override(&self, h: Handle<Expression>) -> bool {
matches!(
self.type_of(h),
ExpressionKind::Const | ExpressionKind::Override
ExpressionKind::Const | ExpressionKind::Override | ExpressionKind::ImplConst
)
}
@ -400,13 +411,14 @@ impl ExpressionKindTracker {
}
fn type_of_with_expr(&self, expr: &Expression) -> ExpressionKind {
use crate::MathFunction as Mf;
match *expr {
Expression::Literal(_) | Expression::ZeroValue(_) | Expression::Constant(_) => {
ExpressionKind::Const
ExpressionKind::ImplConst
}
Expression::Override(_) => ExpressionKind::Override,
Expression::Compose { ref components, .. } => {
let mut expr_type = ExpressionKind::Const;
let mut expr_type = ExpressionKind::ImplConst;
for component in components {
expr_type = expr_type.max(self.type_of(*component))
}
@ -417,13 +429,16 @@ impl ExpressionKindTracker {
Expression::Access { base, index } => self.type_of(base).max(self.type_of(index)),
Expression::Swizzle { vector, .. } => self.type_of(vector),
Expression::Unary { expr, .. } => self.type_of(expr),
Expression::Binary { left, right, .. } => self.type_of(left).max(self.type_of(right)),
Expression::Binary { left, right, .. } => self
.type_of(left)
.max(self.type_of(right))
.max(ExpressionKind::Const),
Expression::Math {
fun,
arg,
arg1,
arg2,
arg3,
..
} => self
.type_of(arg)
.max(
@ -437,8 +452,34 @@ impl ExpressionKindTracker {
.max(
arg3.map(|arg| self.type_of(arg))
.unwrap_or(ExpressionKind::Const),
)
.max(
if matches!(
fun,
Mf::Dot
| Mf::Outer
| Mf::Cross
| Mf::Distance
| Mf::Length
| Mf::Normalize
| Mf::FaceForward
| Mf::Reflect
| Mf::Refract
| Mf::Ldexp
| Mf::Modf
| Mf::Mix
| Mf::Frexp
) {
ExpressionKind::Const
} else {
ExpressionKind::ImplConst
},
),
Expression::As { expr, .. } => self.type_of(expr),
Expression::As { convert, expr, .. } => self.type_of(expr).max(if convert.is_some() {
ExpressionKind::ImplConst
} else {
ExpressionKind::Const
}),
Expression::Select {
condition,
accept,
@ -446,7 +487,8 @@ impl ExpressionKindTracker {
} => self
.type_of(condition)
.max(self.type_of(accept))
.max(self.type_of(reject)),
.max(self.type_of(reject))
.max(ExpressionKind::Const),
Expression::Relational { argument, .. } => self.type_of(argument),
Expression::ArrayLength(expr) => self.type_of(expr),
_ => ExpressionKind::Runtime,
@ -556,7 +598,7 @@ impl<'a> ConstantEvaluator<'a> {
Behavior::Wgsl(if in_override_ctx {
WgslRestrictions::Override
} else {
WgslRestrictions::Const
WgslRestrictions::Const(None)
}),
module,
global_expression_kind_tracker,
@ -603,13 +645,19 @@ impl<'a> ConstantEvaluator<'a> {
local_expression_kind_tracker: &'a mut ExpressionKindTracker,
emitter: &'a mut super::Emitter,
block: &'a mut crate::Block,
is_const: bool,
) -> Self {
let local_data = FunctionLocalData {
global_expressions: &module.global_expressions,
emitter,
block,
};
Self {
behavior: Behavior::Wgsl(WgslRestrictions::Runtime(FunctionLocalData {
global_expressions: &module.global_expressions,
emitter,
block,
})),
behavior: Behavior::Wgsl(if is_const {
WgslRestrictions::Const(Some(local_data))
} else {
WgslRestrictions::Runtime(local_data)
}),
types: &mut module.types,
constants: &module.constants,
overrides: &module.overrides,
@ -718,6 +766,7 @@ impl<'a> ConstantEvaluator<'a> {
span: Span,
) -> Result<Handle<Expression>, ConstantEvaluatorError> {
match self.expression_kind_tracker.type_of_with_expr(&expr) {
ExpressionKind::ImplConst => self.try_eval_and_append_impl(&expr, span),
ExpressionKind::Const => {
let eval_result = self.try_eval_and_append_impl(&expr, span);
// We should be able to evaluate `Const` expressions at this
@ -740,7 +789,7 @@ impl<'a> ConstantEvaluator<'a> {
Behavior::Wgsl(WgslRestrictions::Override | WgslRestrictions::Runtime(_)) => {
Ok(self.append_expr(expr, span, ExpressionKind::Override))
}
Behavior::Wgsl(WgslRestrictions::Const) => {
Behavior::Wgsl(WgslRestrictions::Const(_)) => {
Err(ConstantEvaluatorError::OverrideExpr)
}
Behavior::Glsl(_) => {
@ -761,14 +810,17 @@ impl<'a> ConstantEvaluator<'a> {
const fn is_global_arena(&self) -> bool {
matches!(
self.behavior,
Behavior::Wgsl(WgslRestrictions::Const | WgslRestrictions::Override)
Behavior::Wgsl(WgslRestrictions::Const(None) | WgslRestrictions::Override)
| Behavior::Glsl(GlslRestrictions::Const)
)
}
const fn function_local_data(&self) -> Option<&FunctionLocalData<'a>> {
match self.behavior {
Behavior::Wgsl(WgslRestrictions::Runtime(ref function_local_data))
Behavior::Wgsl(
WgslRestrictions::Runtime(ref function_local_data)
| WgslRestrictions::Const(Some(ref function_local_data)),
)
| Behavior::Glsl(GlslRestrictions::Runtime(ref function_local_data)) => {
Some(function_local_data)
}
@ -1779,9 +1831,13 @@ impl<'a> ConstantEvaluator<'a> {
_ => return Err(ConstantEvaluatorError::InvalidBinaryOpArgs),
}),
(Literal::I32(a), Literal::U32(b)) => Literal::I32(match op {
BinaryOperator::ShiftLeft => a
.checked_shl(b)
.ok_or(ConstantEvaluatorError::ShiftedMoreThan32Bits)?,
BinaryOperator::ShiftLeft => {
if (if a.is_negative() { !a } else { a }).leading_zeros() <= b {
return Err(ConstantEvaluatorError::Overflow("<<".to_string()));
}
a.checked_shl(b)
.ok_or(ConstantEvaluatorError::ShiftedMoreThan32Bits)?
}
BinaryOperator::ShiftRight => a
.checked_shr(b)
.ok_or(ConstantEvaluatorError::ShiftedMoreThan32Bits)?,
@ -1807,8 +1863,11 @@ impl<'a> ConstantEvaluator<'a> {
BinaryOperator::ExclusiveOr => a ^ b,
BinaryOperator::InclusiveOr => a | b,
BinaryOperator::ShiftLeft => a
.checked_shl(b)
.ok_or(ConstantEvaluatorError::ShiftedMoreThan32Bits)?,
.checked_mul(
1u32.checked_shl(b)
.ok_or(ConstantEvaluatorError::ShiftedMoreThan32Bits)?,
)
.ok_or(ConstantEvaluatorError::Overflow("<<".to_string()))?,
BinaryOperator::ShiftRight => a
.checked_shr(b)
.ok_or(ConstantEvaluatorError::ShiftedMoreThan32Bits)?,
@ -2057,7 +2116,10 @@ impl<'a> ConstantEvaluator<'a> {
expr_type: ExpressionKind,
) -> Handle<Expression> {
let h = match self.behavior {
Behavior::Wgsl(WgslRestrictions::Runtime(ref mut function_local_data))
Behavior::Wgsl(
WgslRestrictions::Runtime(ref mut function_local_data)
| WgslRestrictions::Const(Some(ref mut function_local_data)),
)
| Behavior::Glsl(GlslRestrictions::Runtime(ref mut function_local_data)) => {
let is_running = function_local_data.emitter.is_running();
let needs_pre_emit = expr.needs_pre_emit();
@ -2480,7 +2542,7 @@ mod tests {
let expression_kind_tracker = &mut ExpressionKindTracker::from_arena(&global_expressions);
let mut solver = ConstantEvaluator {
behavior: Behavior::Wgsl(WgslRestrictions::Const),
behavior: Behavior::Wgsl(WgslRestrictions::Const(None)),
types: &mut types,
constants: &constants,
overrides: &overrides,
@ -2566,7 +2628,7 @@ mod tests {
let expression_kind_tracker = &mut ExpressionKindTracker::from_arena(&global_expressions);
let mut solver = ConstantEvaluator {
behavior: Behavior::Wgsl(WgslRestrictions::Const),
behavior: Behavior::Wgsl(WgslRestrictions::Const(None)),
types: &mut types,
constants: &constants,
overrides: &overrides,
@ -2684,7 +2746,7 @@ mod tests {
let expression_kind_tracker = &mut ExpressionKindTracker::from_arena(&global_expressions);
let mut solver = ConstantEvaluator {
behavior: Behavior::Wgsl(WgslRestrictions::Const),
behavior: Behavior::Wgsl(WgslRestrictions::Const(None)),
types: &mut types,
constants: &constants,
overrides: &overrides,
@ -2777,7 +2839,7 @@ mod tests {
let expression_kind_tracker = &mut ExpressionKindTracker::from_arena(&global_expressions);
let mut solver = ConstantEvaluator {
behavior: Behavior::Wgsl(WgslRestrictions::Const),
behavior: Behavior::Wgsl(WgslRestrictions::Const(None)),
types: &mut types,
constants: &constants,
overrides: &overrides,
@ -2859,7 +2921,7 @@ mod tests {
let expression_kind_tracker = &mut ExpressionKindTracker::from_arena(&global_expressions);
let mut solver = ConstantEvaluator {
behavior: Behavior::Wgsl(WgslRestrictions::Const),
behavior: Behavior::Wgsl(WgslRestrictions::Const(None)),
types: &mut types,
constants: &constants,
overrides: &overrides,

View File

@ -674,6 +674,19 @@ impl GlobalCtx<'_> {
}
}
/// Try to evaluate the expression in the `arena` using its `handle` and return it as a `bool`.
#[allow(dead_code)]
pub(super) fn eval_expr_to_bool_from(
&self,
handle: crate::Handle<crate::Expression>,
arena: &crate::Arena<crate::Expression>,
) -> Option<bool> {
match self.eval_expr_to_literal_from(handle, arena) {
Some(crate::Literal::Bool(value)) => Some(value),
_ => None,
}
}
#[allow(dead_code)]
pub(crate) fn eval_expr_to_literal(
&self,

View File

@ -50,6 +50,11 @@ pub enum VaryingError {
NotIOShareableType(Handle<crate::Type>),
#[error("Interpolation is not valid")]
InvalidInterpolation,
#[error("Cannot combine {interpolation:?} interpolation with the {sampling:?} sample type")]
InvalidInterpolationSamplingCombination {
interpolation: crate::Interpolation,
sampling: crate::Sampling,
},
#[error("Interpolation must be specified on vertex shader outputs and fragment shader inputs")]
MissingInterpolation,
#[error("Built-in {0:?} is not available at this stage")]
@ -339,6 +344,31 @@ impl VaryingContext<'_> {
}
}
if let Some(interpolation) = interpolation {
let invalid_sampling = match (interpolation, sampling) {
(_, None)
| (
crate::Interpolation::Perspective | crate::Interpolation::Linear,
Some(
crate::Sampling::Center
| crate::Sampling::Centroid
| crate::Sampling::Sample,
),
)
| (
crate::Interpolation::Flat,
Some(crate::Sampling::First | crate::Sampling::Either),
) => None,
(_, Some(invalid_sampling)) => Some(invalid_sampling),
};
if let Some(sampling) = invalid_sampling {
return Err(VaryingError::InvalidInterpolationSamplingCombination {
interpolation,
sampling,
});
}
}
let needs_interpolation = match self.stage {
crate::ShaderStage::Vertex => self.output,
crate::ShaderStage::Fragment => !self.output,

File diff suppressed because one or more lines are too long

View File

@ -884,6 +884,16 @@ pub(crate) fn buffer_binding_type_alignment(
}
}
pub(crate) fn buffer_binding_type_bounds_check_alignment(
alignments: &hal::Alignments,
binding_type: wgt::BufferBindingType,
) -> wgt::BufferAddress {
match binding_type {
wgt::BufferBindingType::Uniform => alignments.uniform_bounds_check_alignment.get(),
wgt::BufferBindingType::Storage { .. } => wgt::COPY_BUFFER_ALIGNMENT,
}
}
#[derive(Debug)]
pub struct BindGroup {
pub(crate) raw: Snatchable<Box<dyn hal::DynBindGroup>>,

View File

@ -82,8 +82,8 @@ pub(crate) enum CommandEncoderStatus {
///
/// When a `CommandEncoder` is left in this state, we have also
/// returned an error result from the function that encountered
/// the problem. Future attempts to use the encoder (that is,
/// calls to [`CommandBuffer::get_encoder`]) will also return
/// the problem. Future attempts to use the encoder (for example,
/// calls to [`CommandBuffer::check_recording`]) will also return
/// errors.
///
/// Calling [`Global::command_encoder_finish`] in this state

View File

@ -3162,7 +3162,7 @@ impl Global {
.map_err(|_| RenderPassErrorInner::InvalidBuffer(buffer_id))
.map_pass_err(scope)?;
let count_buffer = buffers
.get_owned(buffer_id)
.get_owned(count_buffer_id)
.map_err(|_| RenderPassErrorInner::InvalidBuffer(count_buffer_id))
.map_pass_err(scope)?;
@ -3203,7 +3203,7 @@ impl Global {
.map_pass_err(scope)?;
let count_buffer = buffers
.get_owned(buffer_id)
.get_owned(count_buffer_id)
.map_err(|_| RenderPassErrorInner::InvalidBuffer(count_buffer_id))
.map_pass_err(scope)?;

View File

@ -795,14 +795,14 @@ impl Global {
Err(..) => break 'error binding_model::CreateBindGroupError::InvalidLayout,
};
fn map_entry<'a>(
fn resolve_entry<'a>(
e: &BindGroupEntry<'a>,
buffer_storage: &Storage<resource::Buffer>,
sampler_storage: &Storage<resource::Sampler>,
texture_view_storage: &Storage<resource::TextureView>,
) -> Result<ResolvedBindGroupEntry<'a>, binding_model::CreateBindGroupError>
{
let map_buffer = |bb: &BufferBinding| {
let resolve_buffer = |bb: &BufferBinding| {
buffer_storage
.get_owned(bb.buffer_id)
.map(|buffer| ResolvedBufferBinding {
@ -814,42 +814,45 @@ impl Global {
binding_model::CreateBindGroupError::InvalidBufferId(bb.buffer_id)
})
};
let map_sampler = |id: &id::SamplerId| {
let resolve_sampler = |id: &id::SamplerId| {
sampler_storage
.get_owned(*id)
.map_err(|_| binding_model::CreateBindGroupError::InvalidSamplerId(*id))
};
let map_view = |id: &id::TextureViewId| {
let resolve_view = |id: &id::TextureViewId| {
texture_view_storage
.get_owned(*id)
.map_err(|_| binding_model::CreateBindGroupError::InvalidTextureViewId(*id))
};
let resource = match e.resource {
BindingResource::Buffer(ref buffer) => {
ResolvedBindingResource::Buffer(map_buffer(buffer)?)
ResolvedBindingResource::Buffer(resolve_buffer(buffer)?)
}
BindingResource::BufferArray(ref buffers) => {
let buffers = buffers
.iter()
.map(map_buffer)
.map(resolve_buffer)
.collect::<Result<Vec<_>, _>>()?;
ResolvedBindingResource::BufferArray(Cow::Owned(buffers))
}
BindingResource::Sampler(ref sampler) => {
ResolvedBindingResource::Sampler(map_sampler(sampler)?)
ResolvedBindingResource::Sampler(resolve_sampler(sampler)?)
}
BindingResource::SamplerArray(ref samplers) => {
let samplers = samplers
.iter()
.map(map_sampler)
.map(resolve_sampler)
.collect::<Result<Vec<_>, _>>()?;
ResolvedBindingResource::SamplerArray(Cow::Owned(samplers))
}
BindingResource::TextureView(ref view) => {
ResolvedBindingResource::TextureView(map_view(view)?)
ResolvedBindingResource::TextureView(resolve_view(view)?)
}
BindingResource::TextureViewArray(ref views) => {
let views = views.iter().map(map_view).collect::<Result<Vec<_>, _>>()?;
let views = views
.iter()
.map(resolve_view)
.collect::<Result<Vec<_>, _>>()?;
ResolvedBindingResource::TextureViewArray(Cow::Owned(views))
}
};
@ -865,7 +868,7 @@ impl Global {
let sampler_guard = hub.samplers.read();
desc.entries
.iter()
.map(|e| map_entry(e, &buffer_guard, &sampler_guard, &texture_view_guard))
.map(|e| resolve_entry(e, &buffer_guard, &sampler_guard, &texture_view_guard))
.collect::<Result<Vec<_>, _>>()
};
let entries = match entries {

View File

@ -336,19 +336,41 @@ fn map_buffer(
let mapped = unsafe { std::slice::from_raw_parts_mut(mapping.ptr.as_ptr(), size as usize) };
for uninitialized in buffer
.initialization_status
.write()
.drain(offset..(size + offset))
// We can't call flush_mapped_ranges in this case, so we can't drain the uninitialized ranges either
if !mapping.is_coherent
&& kind == HostMap::Read
&& !buffer.usage.contains(wgt::BufferUsages::MAP_WRITE)
{
// The mapping's pointer is already offset, however we track the
// uninitialized range relative to the buffer's start.
let fill_range =
(uninitialized.start - offset) as usize..(uninitialized.end - offset) as usize;
mapped[fill_range].fill(0);
for uninitialized in buffer
.initialization_status
.write()
.uninitialized(offset..(size + offset))
{
// The mapping's pointer is already offset, however we track the
// uninitialized range relative to the buffer's start.
let fill_range =
(uninitialized.start - offset) as usize..(uninitialized.end - offset) as usize;
mapped[fill_range].fill(0);
}
} else {
for uninitialized in buffer
.initialization_status
.write()
.drain(offset..(size + offset))
{
// The mapping's pointer is already offset, however we track the
// uninitialized range relative to the buffer's start.
let fill_range =
(uninitialized.start - offset) as usize..(uninitialized.end - offset) as usize;
mapped[fill_range].fill(0);
if !mapping.is_coherent && kind == HostMap::Read {
unsafe { raw.flush_mapped_ranges(raw_buffer, &[uninitialized]) };
// NOTE: This is only possible when MAPPABLE_PRIMARY_BUFFERS is enabled.
if !mapping.is_coherent
&& kind == HostMap::Read
&& buffer.usage.contains(wgt::BufferUsages::MAP_WRITE)
{
unsafe { raw.flush_mapped_ranges(raw_buffer, &[uninitialized]) };
}
}
}

View File

@ -39,7 +39,9 @@ use once_cell::sync::OnceCell;
use smallvec::SmallVec;
use thiserror::Error;
use wgt::{DeviceLostReason, TextureFormat, TextureSampleType, TextureViewDimension};
use wgt::{
math::align_to, DeviceLostReason, TextureFormat, TextureSampleType, TextureViewDimension,
};
use std::{
borrow::Cow,
@ -1629,7 +1631,7 @@ impl Device {
/// Generate information about late-validated buffer bindings for pipelines.
//TODO: should this be combined with `get_introspection_bind_group_layouts` in some way?
pub(crate) fn make_late_sized_buffer_groups(
fn make_late_sized_buffer_groups(
shader_binding_sizes: &FastHashMap<naga::ResourceBinding, wgt::BufferSize>,
layout: &binding_model::PipelineLayout,
) -> ArrayVec<pipeline::LateSizedBufferGroup, { hal::MAX_BIND_GROUPS }> {
@ -1881,8 +1883,8 @@ impl Device {
Ok(bgl)
}
pub(crate) fn create_buffer_binding<'a>(
self: &Arc<Self>,
fn create_buffer_binding<'a>(
&self,
bb: &'a binding_model::ResolvedBufferBinding,
binding: u32,
decl: &wgt::BindGroupLayoutEntry,
@ -1890,7 +1892,6 @@ impl Device {
dynamic_binding_info: &mut Vec<binding_model::BindGroupDynamicBindingData>,
late_buffer_binding_sizes: &mut FastHashMap<u32, wgt::BufferSize>,
used: &mut BindGroupStates,
limits: &wgt::Limits,
snatch_guard: &'a SnatchGuard<'a>,
) -> Result<hal::BufferBinding<'a, dyn hal::DynBuffer>, binding_model::CreateBindGroupError>
{
@ -1915,7 +1916,7 @@ impl Device {
wgt::BufferBindingType::Uniform => (
wgt::BufferUsages::UNIFORM,
hal::BufferUses::UNIFORM,
limits.max_uniform_buffer_binding_size,
self.limits.max_uniform_buffer_binding_size,
),
wgt::BufferBindingType::Storage { read_only } => (
wgt::BufferUsages::STORAGE,
@ -1924,12 +1925,12 @@ impl Device {
} else {
hal::BufferUses::STORAGE_READ_WRITE
},
limits.max_storage_buffer_binding_size,
self.limits.max_storage_buffer_binding_size,
),
};
let (align, align_limit_name) =
binding_model::buffer_binding_type_alignment(limits, binding_ty);
binding_model::buffer_binding_type_alignment(&self.limits, binding_ty);
if bb.offset % align as u64 != 0 {
return Err(Error::UnalignedBufferOffset(
bb.offset,
@ -2005,10 +2006,21 @@ impl Device {
late_buffer_binding_sizes.insert(binding, late_size);
}
// This was checked against the device's alignment requirements above,
// which should always be a multiple of `COPY_BUFFER_ALIGNMENT`.
assert_eq!(bb.offset % wgt::COPY_BUFFER_ALIGNMENT, 0);
// `wgpu_hal` only restricts shader access to bound buffer regions with
// a certain resolution. For the sake of lazy initialization, round up
// the size of the bound range to reflect how much of the buffer is
// actually going to be visible to the shader.
let bounds_check_alignment =
binding_model::buffer_binding_type_bounds_check_alignment(&self.alignments, binding_ty);
let visible_size = align_to(bind_size, bounds_check_alignment);
used_buffer_ranges.extend(buffer.initialization_status.read().create_action(
buffer,
bb.offset..bb.offset + bind_size,
bb.offset..bb.offset + visible_size,
MemoryInitKind::NeedsInitializedMemory,
));
@ -2020,7 +2032,7 @@ impl Device {
}
fn create_sampler_binding<'a>(
self: &Arc<Self>,
&self,
used: &mut BindGroupStates,
binding: u32,
decl: &wgt::BindGroupLayoutEntry,
@ -2068,8 +2080,8 @@ impl Device {
Ok(sampler.raw())
}
pub(crate) fn create_texture_binding<'a>(
self: &Arc<Self>,
fn create_texture_binding<'a>(
&self,
binding: u32,
decl: &wgt::BindGroupLayoutEntry,
view: &'a Arc<TextureView>,
@ -2167,7 +2179,6 @@ impl Device {
&mut dynamic_binding_info,
&mut late_buffer_binding_sizes,
&mut used,
&self.limits,
&snatch_guard,
)?;
@ -2189,7 +2200,6 @@ impl Device {
&mut dynamic_binding_info,
&mut late_buffer_binding_sizes,
&mut used,
&self.limits,
&snatch_guard,
)?;
hal_buffers.push(bb);
@ -2325,7 +2335,7 @@ impl Device {
Ok(bind_group)
}
pub(crate) fn check_array_binding(
fn check_array_binding(
features: wgt::Features,
count: Option<NonZeroU32>,
num_bindings: usize,
@ -2358,8 +2368,8 @@ impl Device {
Ok(())
}
pub(crate) fn texture_use_parameters(
self: &Arc<Self>,
fn texture_use_parameters(
&self,
binding: u32,
decl: &wgt::BindGroupLayoutEntry,
view: &TextureView,
@ -3449,10 +3459,7 @@ impl Device {
Ok(cache)
}
pub(crate) fn get_texture_format_features(
&self,
format: TextureFormat,
) -> wgt::TextureFormatFeatures {
fn get_texture_format_features(&self, format: TextureFormat) -> wgt::TextureFormatFeatures {
// Variant of adapter.get_texture_format_features that takes device features into account
use wgt::TextureFormatFeatureFlags as tfsc;
let mut format_features = self.adapter.get_texture_format_features(format);
@ -3466,7 +3473,7 @@ impl Device {
format_features
}
pub(crate) fn describe_format_features(
fn describe_format_features(
&self,
format: TextureFormat,
) -> Result<wgt::TextureFormatFeatures, MissingFeatures> {

View File

@ -65,6 +65,35 @@ pub(crate) struct InitTracker<Idx: Ord + Copy + Default> {
uninitialized_ranges: UninitializedRangeVec<Idx>,
}
pub(crate) struct UninitializedIter<'a, Idx: fmt::Debug + Ord + Copy> {
uninitialized_ranges: &'a UninitializedRangeVec<Idx>,
drain_range: Range<Idx>,
next_index: usize,
}
impl<'a, Idx> Iterator for UninitializedIter<'a, Idx>
where
Idx: fmt::Debug + Ord + Copy,
{
type Item = Range<Idx>;
fn next(&mut self) -> Option<Self::Item> {
self.uninitialized_ranges
.get(self.next_index)
.and_then(|range| {
if range.start < self.drain_range.end {
self.next_index += 1;
Some(
range.start.max(self.drain_range.start)
..range.end.min(self.drain_range.end),
)
} else {
None
}
})
}
}
pub(crate) struct InitTrackerDrain<'a, Idx: fmt::Debug + Ord + Copy> {
uninitialized_ranges: &'a mut UninitializedRangeVec<Idx>,
drain_range: Range<Idx>,
@ -190,6 +219,18 @@ where
})
}
// Returns an iterator over the uninitialized ranges in a query range.
pub(crate) fn uninitialized(&mut self, drain_range: Range<Idx>) -> UninitializedIter<Idx> {
let index = self
.uninitialized_ranges
.partition_point(|r| r.end <= drain_range.start);
UninitializedIter {
drain_range,
uninitialized_ranges: &self.uninitialized_ranges,
next_index: index,
}
}
// Drains uninitialized ranges in a query range.
pub(crate) fn drain(&mut self, drain_range: Range<Idx>) -> InitTrackerDrain<Idx> {
let index = self

View File

@ -369,16 +369,16 @@ impl ObservationLog {
self.write_location(new_location);
self.write_action(&Action::Acquisition {
older_rank: older_lock.rank.bit.number(),
older_location: older_lock.location as *const _ as usize,
older_location: addr(older_lock.location),
newer_rank: new_rank.bit.number(),
newer_location: new_location as *const _ as usize,
newer_location: addr(new_location),
});
}
fn write_location(&mut self, location: &'static Location<'static>) {
if self.locations_seen.insert(location) {
self.write_action(&Action::Location {
address: location as *const _ as usize,
address: addr(location),
file: location.file(),
line: location.line(),
column: location.column(),
@ -473,3 +473,8 @@ impl LockRankSet {
self.bits().trailing_zeros()
}
}
/// Convenience for `std::ptr::from_ref(t) as usize`.
fn addr<T>(t: &T) -> usize {
std::ptr::from_ref(t) as usize
}

View File

@ -106,8 +106,8 @@ pub(crate) trait ParentDevice: Labeled {
}
}
fn same_device(&self, device: &Arc<Device>) -> Result<(), DeviceError> {
if Arc::ptr_eq(self.device(), device) {
fn same_device(&self, device: &Device) -> Result<(), DeviceError> {
if std::ptr::eq(&**self.device(), device) {
Ok(())
} else {
Err(DeviceError::DeviceMismatch(Box::new(DeviceMismatch {

File diff suppressed because one or more lines are too long

View File

@ -140,6 +140,7 @@ dx12 = [
"gpu-allocator/d3d12",
"naga/hlsl-out-if-target-windows",
"windows/Win32_Graphics_Direct3D_Fxc",
"windows/Win32_Graphics_Direct3D_Dxc",
"windows/Win32_Graphics_Direct3D",
"windows/Win32_Graphics_Direct3D12",
"windows/Win32_Graphics_DirectComposition",
@ -151,7 +152,6 @@ dx12 = [
"windows/Win32_System_Threading",
"windows/Win32_UI_WindowsAndMessaging",
]
dxc_shader_compiler = ["dep:hassle-rs"]
fragile-send-sync-non-atomic-wasm = ["wgt/fragile-send-sync-non-atomic-wasm"]
gles = [
"naga/glsl-out",
@ -213,29 +213,31 @@ version = "0.29.0"
[target.'cfg(any(target_os="macos", target_os="ios"))'.dependencies.objc]
version = "0.2.5"
[target.'cfg(not(any(target_arch = "wasm32", windows, target_os = "ios")))'.dev-dependencies.glutin]
[target.'cfg(not(any(target_arch = "wasm32", target_os = "ios")))'.dev-dependencies.glutin]
version = "0.31"
features = [
"egl",
"wgl",
"wayland",
"x11",
]
default-features = false
[target.'cfg(not(any(target_arch = "wasm32", windows, target_os = "ios")))'.dev-dependencies.glutin-winit]
[target.'cfg(not(any(target_arch = "wasm32", target_os = "ios")))'.dev-dependencies.glutin-winit]
version = "0.4"
features = [
"egl",
"wgl",
"wayland",
"x11",
]
default-features = false
[target.'cfg(not(any(target_arch = "wasm32", windows, target_os = "ios")))'.dev-dependencies.rwh_05]
[target.'cfg(not(any(target_arch = "wasm32", target_os = "ios")))'.dev-dependencies.rwh_05]
version = "0.5"
package = "raw-window-handle"
[target.'cfg(not(any(target_arch = "wasm32", windows, target_os = "ios")))'.dev-dependencies.winit]
[target.'cfg(not(any(target_arch = "wasm32", target_os = "ios")))'.dev-dependencies.winit]
version = "0.29"
features = [
"android-native-activity",
@ -307,10 +309,6 @@ version = "0.27"
optional = true
default-features = false
[target."cfg(windows)".dependencies.hassle-rs]
version = "0.11.0"
optional = true
[target."cfg(windows)".dependencies.range-alloc]
version = "0.1"
optional = true

View File

@ -14,7 +14,9 @@ use winit::{
use std::{
borrow::{Borrow, Cow},
iter, mem, ptr,
iter,
mem::size_of,
ptr,
time::Instant,
};
@ -193,7 +195,7 @@ impl<A: hal::Api> Example<A> {
ty: wgt::BindingType::Buffer {
ty: wgt::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: wgt::BufferSize::new(mem::size_of::<Globals>() as _),
min_binding_size: wgt::BufferSize::new(size_of::<Globals>() as _),
},
count: None,
},
@ -228,7 +230,7 @@ impl<A: hal::Api> Example<A> {
ty: wgt::BindingType::Buffer {
ty: wgt::BufferBindingType::Uniform,
has_dynamic_offset: true,
min_binding_size: wgt::BufferSize::new(mem::size_of::<Locals>() as _),
min_binding_size: wgt::BufferSize::new(size_of::<Locals>() as _),
},
count: None,
}],
@ -394,7 +396,7 @@ impl<A: hal::Api> Example<A> {
let global_buffer_desc = hal::BufferDescriptor {
label: Some("global"),
size: mem::size_of::<Globals>() as wgt::BufferAddress,
size: size_of::<Globals>() as wgt::BufferAddress,
usage: hal::BufferUses::MAP_WRITE | hal::BufferUses::UNIFORM,
memory_flags: hal::MemoryFlags::PREFER_COHERENT,
};
@ -406,7 +408,7 @@ impl<A: hal::Api> Example<A> {
ptr::copy_nonoverlapping(
&globals as *const Globals as *const u8,
mapping.ptr.as_ptr(),
mem::size_of::<Globals>(),
size_of::<Globals>(),
);
device.unmap_buffer(&buffer);
assert!(mapping.is_coherent);
@ -414,7 +416,7 @@ impl<A: hal::Api> Example<A> {
};
let local_alignment = wgt::math::align_to(
mem::size_of::<Locals>() as u32,
size_of::<Locals>() as u32,
capabilities.limits.min_uniform_buffer_offset_alignment,
);
let local_buffer_desc = hal::BufferDescriptor {
@ -476,7 +478,7 @@ impl<A: hal::Api> Example<A> {
let local_buffer_binding = hal::BufferBinding {
buffer: &local_buffer,
offset: 0,
size: wgt::BufferSize::new(mem::size_of::<Locals>() as _),
size: wgt::BufferSize::new(size_of::<Locals>() as _),
};
let local_group_desc = hal::BindGroupDescriptor {
label: Some("local"),

View File

@ -10,7 +10,7 @@
extern crate wgpu_hal as hal;
#[cfg(not(any(windows, target_arch = "wasm32", target_os = "ios")))]
#[cfg(not(any(target_arch = "wasm32", target_os = "ios")))]
fn main() {
use std::{ffi::CString, num::NonZeroU32};
@ -255,7 +255,6 @@ fn main() {
}
#[cfg(any(
windows,
all(target_arch = "wasm32", not(target_os = "emscripten")),
target_os = "ios"
))]
@ -264,7 +263,6 @@ fn main() {
}
#[cfg(not(any(
windows,
all(target_arch = "wasm32", not(target_os = "emscripten")),
target_os = "ios"
)))]

View File

@ -8,7 +8,9 @@ use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
use glam::{Affine3A, Mat4, Vec3};
use std::{
borrow::{Borrow, Cow},
iter, mem, ptr,
iter,
mem::size_of,
ptr,
time::Instant,
};
use winit::window::WindowButtons;
@ -304,7 +306,7 @@ impl<A: hal::Api> Example<A> {
ty: wgt::BindingType::Buffer {
ty: wgt::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: wgt::BufferSize::new(mem::size_of::<Uniforms>() as _),
min_binding_size: wgt::BufferSize::new(size_of::<Uniforms>() as _),
},
count: None,
},
@ -516,7 +518,7 @@ impl<A: hal::Api> Example<A> {
}
};
let uniforms_size = std::mem::size_of::<Uniforms>();
let uniforms_size = size_of::<Uniforms>();
let uniform_buffer = unsafe {
let uniform_buffer = device
@ -657,8 +659,7 @@ impl<A: hal::Api> Example<A> {
),
];
let instances_buffer_size =
instances.len() * std::mem::size_of::<AccelerationStructureInstance>();
let instances_buffer_size = instances.len() * size_of::<AccelerationStructureInstance>();
let instances_buffer = unsafe {
let instances_buffer = device
@ -828,7 +829,7 @@ impl<A: hal::Api> Example<A> {
};
let instances_buffer_size =
self.instances.len() * std::mem::size_of::<AccelerationStructureInstance>();
self.instances.len() * size_of::<AccelerationStructureInstance>();
let tlas_flags = hal::AccelerationStructureBuildFlags::PREFER_FAST_TRACE
| hal::AccelerationStructureBuildFlags::ALLOW_UPDATE;

View File

@ -4,12 +4,7 @@ use windows::{core::Interface as _, Win32::Graphics::Dxgi};
use crate::dx12::DxgiLib;
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum DxgiFactoryType {
Factory2,
Factory4,
Factory6,
}
// We can rely on the presence of DXGI 1.4 since D3D12 requires WDDM 2.0, Windows 10 (1507), and so does DXGI 1.4.
fn should_keep_adapter(adapter: &Dxgi::IDXGIAdapter1) -> bool {
let desc = unsafe { adapter.GetDesc1() }.unwrap();
@ -52,75 +47,27 @@ fn should_keep_adapter(adapter: &Dxgi::IDXGIAdapter1) -> bool {
}
pub enum DxgiAdapter {
Adapter1(Dxgi::IDXGIAdapter1),
Adapter2(Dxgi::IDXGIAdapter2),
/// Provided by DXGI 1.4
Adapter3(Dxgi::IDXGIAdapter3),
/// Provided by DXGI 1.6
Adapter4(Dxgi::IDXGIAdapter4),
}
impl windows::core::Param<Dxgi::IDXGIAdapter> for &DxgiAdapter {
unsafe fn param(self) -> windows::core::ParamValue<Dxgi::IDXGIAdapter> {
unsafe { self.deref().param() }
}
}
impl Deref for DxgiAdapter {
type Target = Dxgi::IDXGIAdapter;
type Target = Dxgi::IDXGIAdapter3;
fn deref(&self) -> &Self::Target {
match self {
DxgiAdapter::Adapter1(a) => a,
DxgiAdapter::Adapter2(a) => a,
DxgiAdapter::Adapter3(a) => a,
DxgiAdapter::Adapter4(a) => a,
}
}
}
impl DxgiAdapter {
pub fn as_adapter2(&self) -> Option<&Dxgi::IDXGIAdapter2> {
match self {
Self::Adapter1(_) => None,
Self::Adapter2(f) => Some(f),
Self::Adapter3(f) => Some(f),
Self::Adapter4(f) => Some(f),
}
}
pub fn unwrap_adapter2(&self) -> &Dxgi::IDXGIAdapter2 {
self.as_adapter2().unwrap()
}
}
pub fn enumerate_adapters(factory: DxgiFactory) -> Vec<DxgiAdapter> {
let mut adapters = Vec::with_capacity(8);
for cur_index in 0.. {
if let DxgiFactory::Factory6(ref factory6) = factory {
profiling::scope!("IDXGIFactory6::EnumAdapterByGpuPreference");
// We're already at dxgi1.6, we can grab IDXGIAdapter4 directly
let adapter4: Dxgi::IDXGIAdapter4 = match unsafe {
factory6.EnumAdapterByGpuPreference(
cur_index,
Dxgi::DXGI_GPU_PREFERENCE_HIGH_PERFORMANCE,
)
} {
Ok(a) => a,
Err(e) if e.code() == Dxgi::DXGI_ERROR_NOT_FOUND => break,
Err(e) => {
log::error!("Failed enumerating adapters: {}", e);
break;
}
};
if !should_keep_adapter(&adapter4) {
continue;
}
adapters.push(DxgiAdapter::Adapter4(adapter4));
continue;
}
profiling::scope!("IDXGIFactory1::EnumAdapters1");
let adapter1: Dxgi::IDXGIAdapter1 = match unsafe { factory.EnumAdapters1(cur_index) } {
Ok(a) => a,
@ -135,31 +82,12 @@ pub fn enumerate_adapters(factory: DxgiFactory) -> Vec<DxgiAdapter> {
continue;
}
// Do the most aggressive casts first, skipping Adapter4 as we definitely don't have dxgi1_6.
// Adapter1 -> Adapter3
match adapter1.cast::<Dxgi::IDXGIAdapter3>() {
Ok(adapter3) => {
adapters.push(DxgiAdapter::Adapter3(adapter3));
continue;
}
Err(err) => {
log::warn!("Failed casting Adapter1 to Adapter3: {}", err);
}
if let Ok(adapter4) = adapter1.cast::<Dxgi::IDXGIAdapter4>() {
adapters.push(DxgiAdapter::Adapter4(adapter4));
} else {
let adapter3 = adapter1.cast::<Dxgi::IDXGIAdapter3>().unwrap();
adapters.push(DxgiAdapter::Adapter3(adapter3));
}
// Adapter1 -> Adapter2
match adapter1.cast::<Dxgi::IDXGIAdapter2>() {
Ok(adapter2) => {
adapters.push(DxgiAdapter::Adapter2(adapter2));
continue;
}
Err(err) => {
log::warn!("Failed casting Adapter1 to Adapter2: {}", err);
}
}
adapters.push(DxgiAdapter::Adapter1(adapter1));
}
adapters
@ -167,52 +95,37 @@ pub fn enumerate_adapters(factory: DxgiFactory) -> Vec<DxgiAdapter> {
#[derive(Clone, Debug)]
pub enum DxgiFactory {
Factory1(Dxgi::IDXGIFactory1),
Factory2(Dxgi::IDXGIFactory2),
/// Provided by DXGI 1.4
Factory4(Dxgi::IDXGIFactory4),
/// Provided by DXGI 1.5
Factory5(Dxgi::IDXGIFactory5),
/// Provided by DXGI 1.6
Factory6(Dxgi::IDXGIFactory6),
}
impl Deref for DxgiFactory {
type Target = Dxgi::IDXGIFactory1;
type Target = Dxgi::IDXGIFactory4;
fn deref(&self) -> &Self::Target {
match self {
DxgiFactory::Factory1(f) => f,
DxgiFactory::Factory2(f) => f,
DxgiFactory::Factory4(f) => f,
DxgiFactory::Factory5(f) => f,
DxgiFactory::Factory6(f) => f,
}
}
}
impl DxgiFactory {
pub fn as_factory2(&self) -> Option<&Dxgi::IDXGIFactory2> {
match self {
Self::Factory1(_) => None,
Self::Factory2(f) => Some(f),
Self::Factory4(f) => Some(f),
Self::Factory6(f) => Some(f),
}
}
pub fn unwrap_factory2(&self) -> &Dxgi::IDXGIFactory2 {
self.as_factory2().unwrap()
}
pub fn as_factory5(&self) -> Option<&Dxgi::IDXGIFactory5> {
match self {
Self::Factory1(_) | Self::Factory2(_) | Self::Factory4(_) => None,
Self::Factory4(_) => None,
Self::Factory5(f) => Some(f),
Self::Factory6(f) => Some(f),
}
}
}
/// Tries to create a [`Dxgi::IDXGIFactory6`], then a [`Dxgi::IDXGIFactory4`], then a [`Dxgi::IDXGIFactory2`], then a [`Dxgi::IDXGIFactory1`],
/// returning the one that succeeds, or if the required_factory_type fails to be
/// created.
pub fn create_factory(
required_factory_type: DxgiFactoryType,
instance_flags: wgt::InstanceFlags,
) -> Result<(DxgiLib, DxgiFactory), crate::InstanceError> {
let lib_dxgi = DxgiLib::new().map_err(|e| {
@ -225,111 +138,31 @@ pub fn create_factory(
// The `DXGI_CREATE_FACTORY_DEBUG` flag is only allowed to be passed to
// `CreateDXGIFactory2` if the debug interface is actually available. So
// we check for whether it exists first.
match lib_dxgi.debug_interface1() {
Ok(pair) => match pair {
Ok(_debug_controller) => {
factory_flags |= Dxgi::DXGI_CREATE_FACTORY_DEBUG;
}
Err(err) => {
log::warn!("Unable to enable DXGI debug interface: {}", err);
}
},
Err(err) => {
log::warn!("Debug interface function for DXGI not found: {:?}", err);
}
if lib_dxgi.debug_interface1().is_ok() {
factory_flags |= Dxgi::DXGI_CREATE_FACTORY_DEBUG;
}
// Intercept `OutputDebugString` calls
super::exception::register_exception_handler();
}
// Try to create IDXGIFactory4
let factory4 = match lib_dxgi.create_factory2(factory_flags) {
Ok(pair) => match pair {
Ok(factory) => Some(factory),
// We hard error here as we _should have_ been able to make a factory4 but couldn't.
Err(err) => {
// err is a Cow<str>, not an Error implementor
return Err(crate::InstanceError::new(format!(
"failed to create IDXGIFactory4: {err:?}"
)));
}
},
// If we require factory4, hard error.
Err(err) if required_factory_type == DxgiFactoryType::Factory4 => {
return Err(crate::InstanceError::with_source(
String::from("IDXGIFactory1 creation function not found"),
err,
));
}
// If we don't print it to warn as all win7 will hit this case.
Err(err) => {
log::warn!("IDXGIFactory1 creation function not found: {err:?}");
None
}
};
if let Some(factory4) = factory4 {
// Try to cast the IDXGIFactory4 into IDXGIFactory6
let factory6 = factory4.cast::<Dxgi::IDXGIFactory6>();
match factory6 {
Ok(factory6) => {
return Ok((lib_dxgi, DxgiFactory::Factory6(factory6)));
}
// If we require factory6, hard error.
Err(err) if required_factory_type == DxgiFactoryType::Factory6 => {
// err is a Cow<str>, not an Error implementor
return Err(crate::InstanceError::new(format!(
"failed to cast IDXGIFactory4 to IDXGIFactory6: {err:?}"
)));
}
// If we don't print it to warn.
Err(err) => {
log::warn!("Failed to cast IDXGIFactory4 to IDXGIFactory6: {:?}", err);
return Ok((lib_dxgi, DxgiFactory::Factory4(factory4)));
}
}
}
// Try to create IDXGIFactory1
let factory1 = match lib_dxgi.create_factory1() {
Ok(pair) => match pair {
Ok(factory) => factory,
Err(err) => {
// err is a Cow<str>, not an Error implementor
return Err(crate::InstanceError::new(format!(
"failed to create IDXGIFactory1: {err:?}"
)));
}
},
// We always require at least factory1, so hard error
let factory4 = match lib_dxgi.create_factory4(factory_flags) {
Ok(factory) => factory,
Err(err) => {
return Err(crate::InstanceError::with_source(
String::from("IDXGIFactory1 creation function not found"),
String::from("IDXGIFactory4 creation failed"),
err,
));
}
};
// Try to cast the IDXGIFactory1 into IDXGIFactory2
let factory2 = factory1.cast::<Dxgi::IDXGIFactory2>();
match factory2 {
Ok(factory2) => {
return Ok((lib_dxgi, DxgiFactory::Factory2(factory2)));
}
// If we require factory2, hard error.
Err(err) if required_factory_type == DxgiFactoryType::Factory2 => {
// err is a Cow<str>, not an Error implementor
return Err(crate::InstanceError::new(format!(
"failed to cast IDXGIFactory1 to IDXGIFactory2: {err:?}"
)));
}
// If we don't print it to warn.
Err(err) => {
log::warn!("Failed to cast IDXGIFactory1 to IDXGIFactory2: {:?}", err);
}
if let Ok(factory6) = factory4.cast::<Dxgi::IDXGIFactory6>() {
return Ok((lib_dxgi, DxgiFactory::Factory6(factory6)));
}
// We tried to create 4 and 2, but only succeeded with 1.
Ok((lib_dxgi, DxgiFactory::Factory1(factory1)))
if let Ok(factory5) = factory4.cast::<Dxgi::IDXGIFactory5>() {
return Ok((lib_dxgi, DxgiFactory::Factory5(factory5)));
}
Ok((lib_dxgi, DxgiFactory::Factory4(factory4)))
}

View File

@ -1,56 +1,32 @@
use std::borrow::Cow;
use windows::Win32::{Foundation, Graphics::Dxgi};
pub(crate) trait HResult<O> {
fn into_result(self) -> Result<O, Cow<'static, str>>;
fn into_device_result(self, description: &str) -> Result<O, crate::DeviceError>;
}
impl<T> HResult<T> for windows::core::Result<T> {
fn into_result(self) -> Result<T, Cow<'static, str>> {
// TODO: use windows-rs built-in error formatting?
let description = match self {
Ok(t) => return Ok(t),
Err(e) if e.code() == Foundation::E_UNEXPECTED => "unexpected",
Err(e) if e.code() == Foundation::E_NOTIMPL => "not implemented",
Err(e) if e.code() == Foundation::E_OUTOFMEMORY => "out of memory",
Err(e) if e.code() == Foundation::E_INVALIDARG => "invalid argument",
Err(e) => return Err(Cow::Owned(format!("{e:?}"))),
};
Err(Cow::Borrowed(description))
}
fn into_device_result(self, description: &str) -> Result<T, crate::DeviceError> {
#![allow(unreachable_code)]
let err_code = if let Err(err) = &self {
Some(err.code())
} else {
None
};
self.into_result().map_err(|err| {
self.map_err(|err| {
log::error!("{} failed: {}", description, err);
let Some(err_code) = err_code else {
unreachable!()
};
match err_code {
match err.code() {
Foundation::E_OUTOFMEMORY => {
#[cfg(feature = "oom_panic")]
panic!("{description} failed: Out of memory");
return crate::DeviceError::OutOfMemory;
crate::DeviceError::OutOfMemory
}
Dxgi::DXGI_ERROR_DEVICE_RESET | Dxgi::DXGI_ERROR_DEVICE_REMOVED => {
#[cfg(feature = "device_lost_panic")]
panic!("{description} failed: Device lost ({err})");
crate::DeviceError::Lost
}
_ => {
#[cfg(feature = "internal_error_panic")]
panic!("{description} failed: {err}");
crate::DeviceError::Unexpected
}
}
crate::DeviceError::Lost
})
}
}

View File

@ -62,7 +62,7 @@ impl PresentationTimer {
let kernelbase =
libloading::os::windows::Library::open_already_loaded("kernelbase.dll").unwrap();
// No concerns about lifetimes here as kernelbase is always there.
let ptr = unsafe { kernelbase.get(b"QueryInterruptTimePrecise").unwrap() };
let ptr = unsafe { kernelbase.get(b"QueryInterruptTimePrecise\0").unwrap() };
Self::IPresentationManager {
fnQueryInterruptTimePrecise: *ptr,
}

View File

@ -1,4 +1,9 @@
use std::{mem, ptr, sync::Arc, thread};
use std::{
mem::{size_of, size_of_val},
ptr,
sync::Arc,
thread,
};
use parking_lot::Mutex;
use windows::{
@ -59,19 +64,9 @@ impl super::Adapter {
// Create the device so that we can get the capabilities.
let device = {
profiling::scope!("ID3D12Device::create_device");
match library.create_device(&adapter, Direct3D::D3D_FEATURE_LEVEL_11_0) {
Ok(pair) => match pair {
Ok(device) => device,
Err(err) => {
log::warn!("Device creation failed: {}", err);
return None;
}
},
Err(err) => {
log::warn!("Device creation function is not found: {:?}", err);
return None;
}
}
library
.create_device(&adapter, Direct3D::D3D_FEATURE_LEVEL_11_0)
.ok()?
};
profiling::scope!("feature queries");
@ -92,7 +87,7 @@ impl super::Adapter {
device.CheckFeatureSupport(
Direct3D12::D3D12_FEATURE_FEATURE_LEVELS,
<*mut _>::cast(&mut device_levels),
mem::size_of_val(&device_levels) as u32,
size_of_val(&device_levels) as u32,
)
}
.unwrap();
@ -100,7 +95,7 @@ impl super::Adapter {
// We have found a possible adapter.
// Acquire the device information.
let desc = unsafe { adapter.unwrap_adapter2().GetDesc2() }.unwrap();
let desc = unsafe { adapter.GetDesc2() }.unwrap();
let device_name = auxil::dxgi::conv::map_adapter_name(desc.Description);
@ -110,7 +105,7 @@ impl super::Adapter {
device.CheckFeatureSupport(
Direct3D12::D3D12_FEATURE_ARCHITECTURE,
<*mut _>::cast(&mut features_architecture),
mem::size_of_val(&features_architecture) as u32,
size_of_val(&features_architecture) as u32,
)
}
.unwrap();
@ -154,7 +149,7 @@ impl super::Adapter {
device.CheckFeatureSupport(
Direct3D12::D3D12_FEATURE_D3D12_OPTIONS,
<*mut _>::cast(&mut options),
mem::size_of_val(&options) as u32,
size_of_val(&options) as u32,
)
}
.unwrap();
@ -165,7 +160,7 @@ impl super::Adapter {
device.CheckFeatureSupport(
Direct3D12::D3D12_FEATURE_D3D12_OPTIONS2,
<*mut _>::cast(&mut features2),
mem::size_of_val(&features2) as u32,
size_of_val(&features2) as u32,
)
}
.is_ok()
@ -178,7 +173,7 @@ impl super::Adapter {
device.CheckFeatureSupport(
Direct3D12::D3D12_FEATURE_D3D12_OPTIONS3,
<*mut _>::cast(&mut features3),
mem::size_of_val(&features3) as u32,
size_of_val(&features3) as u32,
)
}
.is_ok()
@ -194,7 +189,7 @@ impl super::Adapter {
device.CheckFeatureSupport(
Direct3D12::D3D12_FEATURE_D3D12_OPTIONS7,
<*mut _>::cast(&mut features7),
mem::size_of_val(&features7) as u32,
size_of_val(&features7) as u32,
)
}
.is_ok()
@ -224,7 +219,7 @@ impl super::Adapter {
device.CheckFeatureSupport(
Direct3D12::D3D12_FEATURE_SHADER_MODEL,
<*mut _>::cast(&mut sm),
mem::size_of_val(&sm) as u32,
size_of_val(&sm) as u32,
)
}
.is_ok()
@ -354,7 +349,7 @@ impl super::Adapter {
device.CheckFeatureSupport(
Direct3D12::D3D12_FEATURE_FORMAT_SUPPORT,
<*mut _>::cast(&mut bgra8unorm_info),
mem::size_of_val(&bgra8unorm_info) as u32,
size_of_val(&bgra8unorm_info) as u32,
)
};
hr.is_ok()
@ -372,7 +367,7 @@ impl super::Adapter {
device.CheckFeatureSupport(
Direct3D12::D3D12_FEATURE_D3D12_OPTIONS1,
<*mut _>::cast(&mut features1),
mem::size_of_val(&features1) as u32,
size_of_val(&features1) as u32,
)
};
@ -396,7 +391,7 @@ impl super::Adapter {
device.CheckFeatureSupport(
Direct3D12::D3D12_FEATURE_D3D12_OPTIONS9,
<*mut _>::cast(&mut features9),
mem::size_of_val(&features9) as u32,
size_of_val(&features9) as u32,
)
}
.is_ok()
@ -524,6 +519,9 @@ impl super::Adapter {
Direct3D12::D3D12_TEXTURE_DATA_PITCH_ALIGNMENT as u64,
)
.unwrap(),
// Direct3D correctly bounds-checks all array accesses:
// https://microsoft.github.io/DirectX-Specs/d3d/archive/D3D11_3_FunctionalSpec.htm#18.6.8.2%20Device%20Memory%20Reads
uniform_bounds_check_alignment: wgt::BufferSize::new(1).unwrap(),
},
downlevel,
},
@ -604,7 +602,7 @@ impl crate::Adapter for super::Adapter {
self.device.CheckFeatureSupport(
Direct3D12::D3D12_FEATURE_FORMAT_SUPPORT,
<*mut _>::cast(&mut data),
mem::size_of_val(&data) as u32,
size_of_val(&data) as u32,
)
}
.unwrap();
@ -622,7 +620,7 @@ impl crate::Adapter for super::Adapter {
self.device.CheckFeatureSupport(
Direct3D12::D3D12_FEATURE_FORMAT_SUPPORT,
ptr::addr_of_mut!(data_srv_uav).cast(),
mem::size_of::<Direct3D12::D3D12_FEATURE_DATA_FORMAT_SUPPORT>() as u32,
size_of::<Direct3D12::D3D12_FEATURE_DATA_FORMAT_SUPPORT>() as u32,
)
}
.unwrap();
@ -718,7 +716,7 @@ impl crate::Adapter for super::Adapter {
self.device.CheckFeatureSupport(
Direct3D12::D3D12_FEATURE_MULTISAMPLE_QUALITY_LEVELS,
<*mut _>::cast(&mut ms_levels),
mem::size_of_val(&ms_levels) as u32,
size_of_val(&ms_levels) as u32,
)
}
.is_ok()

View File

@ -53,7 +53,6 @@ impl crate::BufferTextureCopy {
impl super::Temp {
fn prepare_marker(&mut self, marker: &str) -> (&[u16], u32) {
// TODO: Store in HSTRING
self.marker.clear();
self.marker.extend(marker.encode_utf16());
self.marker.push(0);
@ -153,7 +152,7 @@ impl super::CommandEncoder {
self.update_root_elements();
}
//Note: we have to call this lazily before draw calls. Otherwise, D3D complains
// Note: we have to call this lazily before draw calls. Otherwise, D3D complains
// about the root parameters being incompatible with root signature.
fn update_root_elements(&mut self) {
use super::{BufferViewKind as Bvk, PassKind as Pk};
@ -265,7 +264,8 @@ impl crate::CommandEncoder for super::CommandEncoder {
unsafe fn begin_encoding(&mut self, label: crate::Label) -> Result<(), crate::DeviceError> {
let list = loop {
if let Some(list) = self.free_lists.pop() {
let reset_result = unsafe { list.Reset(&self.allocator, None) }.into_result();
// TODO: Is an error expected here and should we print it?
let reset_result = unsafe { list.Reset(&self.allocator, None) };
if reset_result.is_ok() {
break Some(list);
}
@ -314,7 +314,9 @@ impl crate::CommandEncoder for super::CommandEncoder {
for cmd_buf in command_buffers {
self.free_lists.push(cmd_buf.raw);
}
let _todo_handle_error = unsafe { self.allocator.Reset() };
if let Err(e) = unsafe { self.allocator.Reset() } {
log::error!("ID3D12CommandAllocator::Reset() failed with {e}");
}
}
unsafe fn transition_buffers<'a, T>(&mut self, barriers: T)
@ -724,8 +726,7 @@ impl crate::CommandEncoder for super::CommandEncoder {
cat.clear_value.b as f32,
cat.clear_value.a as f32,
];
// TODO: Empty slice vs None?
unsafe { list.ClearRenderTargetView(*rtv, &value, Some(&[])) };
unsafe { list.ClearRenderTargetView(*rtv, &value, None) };
}
if let Some(ref target) = cat.resolve_target {
self.pass.resolves.push(super::PassResolve {
@ -754,12 +755,23 @@ impl crate::CommandEncoder for super::CommandEncoder {
if let Some(ds_view) = ds_view {
if flags != Direct3D12::D3D12_CLEAR_FLAGS::default() {
unsafe {
list.ClearDepthStencilView(
// list.ClearDepthStencilView(
// ds_view,
// flags,
// ds.clear_value.0,
// ds.clear_value.1 as u8,
// None,
// )
// TODO: Replace with the above in the next breaking windows-rs release,
// when https://github.com/microsoft/win32metadata/pull/1971 is in.
(windows_core::Interface::vtable(list).ClearDepthStencilView)(
windows_core::Interface::as_raw(list),
ds_view,
flags,
ds.clear_value.0,
ds.clear_value.1 as u8,
&[],
0,
std::ptr::null(),
)
}
}
@ -796,7 +808,7 @@ impl crate::CommandEncoder for super::CommandEncoder {
Type: Direct3D12::D3D12_RESOURCE_BARRIER_TYPE_TRANSITION,
Flags: Direct3D12::D3D12_RESOURCE_BARRIER_FLAG_NONE,
Anonymous: Direct3D12::D3D12_RESOURCE_BARRIER_0 {
//Note: this assumes `D3D12_RESOURCE_STATE_RENDER_TARGET`.
// Note: this assumes `D3D12_RESOURCE_STATE_RENDER_TARGET`.
// If it's not the case, we can include the `TextureUses` in `PassResove`.
Transition: mem::ManuallyDrop::new(
Direct3D12::D3D12_RESOURCE_TRANSITION_BARRIER {
@ -813,7 +825,7 @@ impl crate::CommandEncoder for super::CommandEncoder {
Type: Direct3D12::D3D12_RESOURCE_BARRIER_TYPE_TRANSITION,
Flags: Direct3D12::D3D12_RESOURCE_BARRIER_FLAG_NONE,
Anonymous: Direct3D12::D3D12_RESOURCE_BARRIER_0 {
//Note: this assumes `D3D12_RESOURCE_STATE_RENDER_TARGET`.
// Note: this assumes `D3D12_RESOURCE_STATE_RENDER_TARGET`.
// If it's not the case, we can include the `TextureUses` in `PassResolve`.
Transition: mem::ManuallyDrop::new(
Direct3D12::D3D12_RESOURCE_TRANSITION_BARRIER {

View File

@ -56,16 +56,18 @@ impl GeneralHeap {
.into_device_result("Descriptor heap creation")?
};
let start = DualHandle {
cpu: unsafe { raw.GetCPUDescriptorHandleForHeapStart() },
gpu: unsafe { raw.GetGPUDescriptorHandleForHeapStart() },
count: 0,
};
Ok(Self {
raw: raw.clone(),
raw,
ty,
handle_size: unsafe { device.GetDescriptorHandleIncrementSize(ty) } as u64,
total_handles,
start: DualHandle {
cpu: unsafe { raw.GetCPUDescriptorHandleForHeapStart() },
gpu: unsafe { raw.GetGPUDescriptorHandleForHeapStart() },
count: 0,
},
start,
ranges: Mutex::new(RangeAllocator::new(0..total_handles)),
})
}
@ -268,12 +270,14 @@ impl CpuHeap {
let raw = unsafe { device.CreateDescriptorHeap::<Direct3D12::ID3D12DescriptorHeap>(&desc) }
.into_device_result("CPU descriptor heap creation")?;
let start = unsafe { raw.GetCPUDescriptorHandleForHeapStart() };
Ok(Self {
inner: Mutex::new(CpuHeapInner {
_raw: raw.clone(),
_raw: raw,
stage: Vec::new(),
}),
start: unsafe { raw.GetCPUDescriptorHandleForHeapStart() },
start,
handle_size,
total,
})
@ -297,7 +301,7 @@ impl fmt::Debug for CpuHeap {
}
pub(super) unsafe fn upload(
device: Direct3D12::ID3D12Device,
device: &Direct3D12::ID3D12Device,
src: &CpuHeapInner,
dst: &GeneralHeap,
dummy_copy_counts: &[u32],

View File

@ -1,5 +1,6 @@
use std::{
ffi, mem,
ffi,
mem::{self, size_of},
num::NonZeroU32,
ptr,
sync::Arc,
@ -84,7 +85,7 @@ impl super::Device {
}
.into_device_result("Zero buffer creation")?;
let zero_buffer = zero_buffer.ok_or(crate::DeviceError::ResourceCreationFailed)?;
let zero_buffer = zero_buffer.ok_or(crate::DeviceError::Unexpected)?;
// Note: without `D3D12_HEAP_FLAG_CREATE_NOT_ZEROED`
// this resource is zeroed by default.
@ -113,7 +114,7 @@ impl super::Device {
)
}
.into_device_result("Command signature creation")?;
signature.ok_or(crate::DeviceError::ResourceCreationFailed)
signature.ok_or(crate::DeviceError::Unexpected)
}
let shared = super::DeviceShared {
@ -121,7 +122,7 @@ impl super::Device {
cmd_signatures: super::CommandSignatures {
draw: create_command_signature(
&raw,
mem::size_of::<wgt::DrawIndirectArgs>(),
size_of::<wgt::DrawIndirectArgs>(),
&[Direct3D12::D3D12_INDIRECT_ARGUMENT_DESC {
Type: Direct3D12::D3D12_INDIRECT_ARGUMENT_TYPE_DRAW,
..Default::default()
@ -130,7 +131,7 @@ impl super::Device {
)?,
draw_indexed: create_command_signature(
&raw,
mem::size_of::<wgt::DrawIndexedIndirectArgs>(),
size_of::<wgt::DrawIndexedIndirectArgs>(),
&[Direct3D12::D3D12_INDIRECT_ARGUMENT_DESC {
Type: Direct3D12::D3D12_INDIRECT_ARGUMENT_TYPE_DRAW_INDEXED,
..Default::default()
@ -139,7 +140,7 @@ impl super::Device {
)?,
dispatch: create_command_signature(
&raw,
mem::size_of::<wgt::DispatchIndirectArgs>(),
size_of::<wgt::DispatchIndirectArgs>(),
&[Direct3D12::D3D12_INDIRECT_ARGUMENT_DESC {
Type: Direct3D12::D3D12_INDIRECT_ARGUMENT_TYPE_DISPATCH,
..Default::default()
@ -287,7 +288,7 @@ impl super::Device {
};
let full_stage = format!(
"{}_{}\0",
"{}_{}",
naga_stage.to_hlsl_str(),
naga_options.shader_model.to_str()
);
@ -305,28 +306,33 @@ impl super::Device {
let source_name = stage.module.raw_name.as_deref();
// Compile with DXC if available, otherwise fall back to FXC
let (result, log_level) = if let Some(ref dxc_container) = self.dxc_container {
let result = if let Some(ref dxc_container) = self.dxc_container {
shader_compilation::compile_dxc(
self,
&source,
source_name,
raw_ep,
stage_bit,
full_stage,
&full_stage,
dxc_container,
)
} else {
let full_stage = ffi::CStr::from_bytes_with_nul(full_stage.as_bytes()).unwrap();
shader_compilation::compile_fxc(
self,
&source,
source_name,
&ffi::CString::new(raw_ep.as_str()).unwrap(),
raw_ep,
stage_bit,
full_stage,
&full_stage,
)
};
let log_level = if result.is_ok() {
log::Level::Info
} else {
log::Level::Error
};
log::log!(
log_level,
"Naga generated shader for {:?} at {:?}:\n{}",
@ -387,7 +393,6 @@ impl crate::Device for super::Device {
&self,
desc: &crate::BufferDescriptor,
) -> Result<super::Buffer, crate::DeviceError> {
let mut resource = None;
let mut size = desc.size;
if desc.usage.contains(crate::BufferUses::UNIFORM) {
let align_mask = Direct3D12::D3D12_CONSTANT_BUFFER_DATA_PLACEMENT_ALIGNMENT as u64 - 1;
@ -410,10 +415,8 @@ impl crate::Device for super::Device {
Flags: conv::map_buffer_usage_to_resource_flags(desc.usage),
};
let allocation =
super::suballocation::create_buffer_resource(self, desc, raw_desc, &mut resource)?;
let resource = resource.ok_or(crate::DeviceError::ResourceCreationFailed)?;
let (resource, allocation) =
super::suballocation::create_buffer_resource(self, desc, raw_desc)?;
if let Some(label) = desc.label {
unsafe { resource.SetName(&windows::core::HSTRING::from(label)) }
@ -470,10 +473,6 @@ impl crate::Device for super::Device {
&self,
desc: &crate::TextureDescriptor,
) -> Result<super::Texture, crate::DeviceError> {
use super::suballocation::create_texture_resource;
let mut resource = None;
let raw_desc = Direct3D12::D3D12_RESOURCE_DESC {
Dimension: conv::map_texture_dimension(desc.dimension),
Alignment: 0,
@ -495,9 +494,9 @@ impl crate::Device for super::Device {
Flags: conv::map_texture_usage_to_resource_flags(desc.usage),
};
let allocation = create_texture_resource(self, desc, raw_desc, &mut resource)?;
let (resource, allocation) =
super::suballocation::create_texture_resource(self, desc, raw_desc)?;
let resource = resource.ok_or(crate::DeviceError::ResourceCreationFailed)?;
if let Some(label) = desc.label {
unsafe { resource.SetName(&windows::core::HSTRING::from(label)) }
.into_device_result("SetName")?;
@ -1295,7 +1294,7 @@ impl crate::Device for super::Device {
Some(inner) => {
let dual = unsafe {
descriptor::upload(
self.raw.clone(),
&self.raw,
&inner,
&self.shared.heap_views,
&desc.layout.copy_counts,
@ -1309,7 +1308,7 @@ impl crate::Device for super::Device {
Some(inner) => {
let dual = unsafe {
descriptor::upload(
self.raw.clone(),
&self.raw,
&inner,
&self.shared.heap_samplers,
&desc.layout.copy_counts,
@ -1643,7 +1642,7 @@ impl crate::Device for super::Device {
}
.into_device_result("Query heap creation")?;
let raw = raw.ok_or(crate::DeviceError::ResourceCreationFailed)?;
let raw = raw.ok_or(crate::DeviceError::Unexpected)?;
if let Some(label) = desc.label {
unsafe { raw.SetName(&windows::core::HSTRING::from(label)) }

View File

@ -1,4 +1,4 @@
use std::sync::Arc;
use std::{mem::size_of_val, sync::Arc};
use parking_lot::RwLock;
use windows::{
@ -10,10 +10,7 @@ use windows::{
};
use super::SurfaceTarget;
use crate::{
auxil::{self, dxgi::result::HResult as _},
dx12::D3D12Lib,
};
use crate::{auxil, dx12::D3D12Lib};
impl Drop for super::Instance {
fn drop(&mut self) {
@ -37,55 +34,28 @@ impl crate::Instance for super::Instance {
.intersects(wgt::InstanceFlags::VALIDATION | wgt::InstanceFlags::GPU_BASED_VALIDATION)
{
// Enable debug layer
match lib_main.debug_interface() {
Ok(pair) => match pair {
Ok(debug_controller) => {
if desc.flags.intersects(wgt::InstanceFlags::VALIDATION) {
unsafe { debug_controller.EnableDebugLayer() }
}
if desc
.flags
.intersects(wgt::InstanceFlags::GPU_BASED_VALIDATION)
{
#[allow(clippy::collapsible_if)]
if let Ok(debug1) = debug_controller.cast::<Direct3D12::ID3D12Debug1>()
{
unsafe { debug1.SetEnableGPUBasedValidation(true) }
} else {
log::warn!("Failed to enable GPU-based validation");
}
}
if let Ok(debug_controller) = lib_main.debug_interface() {
if desc.flags.intersects(wgt::InstanceFlags::VALIDATION) {
unsafe { debug_controller.EnableDebugLayer() }
}
if desc
.flags
.intersects(wgt::InstanceFlags::GPU_BASED_VALIDATION)
{
#[allow(clippy::collapsible_if)]
if let Ok(debug1) = debug_controller.cast::<Direct3D12::ID3D12Debug1>() {
unsafe { debug1.SetEnableGPUBasedValidation(true) }
} else {
log::warn!("Failed to enable GPU-based validation");
}
Err(err) => {
log::warn!("Unable to enable D3D12 debug interface: {}", err);
}
},
Err(err) => {
log::warn!("Debug interface function for D3D12 not found: {:?}", err);
}
}
}
// Create DXGIFactory4
let (lib_dxgi, factory) = auxil::dxgi::factory::create_factory(
auxil::dxgi::factory::DxgiFactoryType::Factory4,
desc.flags,
)?;
let (lib_dxgi, factory) = auxil::dxgi::factory::create_factory(desc.flags)?;
// Create IDXGIFactoryMedia
let factory_media = match lib_dxgi.create_factory_media() {
Ok(pair) => match pair {
Ok(factory_media) => Some(factory_media),
Err(err) => {
log::error!("Failed to create IDXGIFactoryMedia: {}", err);
None
}
},
Err(err) => {
log::warn!("IDXGIFactory1 creation function not found: {:?}", err);
None
}
};
let factory_media = lib_dxgi.create_factory_media().ok();
let mut supports_allow_tearing = false;
if let Some(factory5) = factory.as_factory5() {
@ -94,12 +64,12 @@ impl crate::Instance for super::Instance {
factory5.CheckFeatureSupport(
Dxgi::DXGI_FEATURE_PRESENT_ALLOW_TEARING,
<*mut _>::cast(&mut allow_tearing),
std::mem::size_of_val(&allow_tearing) as u32,
size_of_val(&allow_tearing) as u32,
)
};
match hr.into_result() {
Err(err) => log::warn!("Unable to check for tearing support: {}", err),
match hr {
Err(err) => log::warn!("Unable to check for tearing support: {err}"),
Ok(()) => supports_allow_tearing = true,
}
}

View File

@ -49,14 +49,13 @@ use std::{ffi, fmt, mem, num::NonZeroU32, ops::Deref, sync::Arc};
use arrayvec::ArrayVec;
use parking_lot::{Mutex, RwLock};
use windows::{
core::{Interface, Param as _},
core::{Free, Interface},
Win32::{
Foundation,
Graphics::{Direct3D, Direct3D12, DirectComposition, Dxgi},
System::Threading,
},
};
use windows_core::Free;
use crate::auxil::{
self,
@ -66,21 +65,50 @@ use crate::auxil::{
},
};
#[derive(Debug)]
struct DynLib {
inner: libloading::Library,
}
impl DynLib {
unsafe fn new<P>(filename: P) -> Result<Self, libloading::Error>
where
P: AsRef<ffi::OsStr>,
{
unsafe { libloading::Library::new(filename) }.map(|inner| Self { inner })
}
unsafe fn get<T>(
&self,
symbol: &[u8],
) -> Result<libloading::Symbol<'_, T>, crate::DeviceError> {
unsafe { self.inner.get(symbol) }.map_err(|e| match e {
libloading::Error::GetProcAddress { .. } | libloading::Error::GetProcAddressUnknown => {
crate::DeviceError::Unexpected
}
libloading::Error::IncompatibleSize
| libloading::Error::CreateCString { .. }
| libloading::Error::CreateCStringWithTrailing { .. } => crate::hal_internal_error(e),
_ => crate::DeviceError::Unexpected, // could be unreachable!() but we prefer to be more robust
})
}
}
#[derive(Debug)]
struct D3D12Lib {
lib: libloading::Library,
lib: DynLib,
}
impl D3D12Lib {
fn new() -> Result<Self, libloading::Error> {
unsafe { libloading::Library::new("d3d12.dll").map(|lib| D3D12Lib { lib }) }
unsafe { DynLib::new("d3d12.dll").map(|lib| Self { lib }) }
}
fn create_device(
&self,
adapter: &DxgiAdapter,
feature_level: Direct3D::D3D_FEATURE_LEVEL,
) -> Result<windows_core::Result<Direct3D12::ID3D12Device>, libloading::Error> {
) -> Result<Direct3D12::ID3D12Device, crate::DeviceError> {
// Calls windows::Win32::Graphics::Direct3D12::D3D12CreateDevice on d3d12.dll
type Fun = extern "system" fn(
padapter: *mut core::ffi::c_void,
@ -88,17 +116,21 @@ impl D3D12Lib {
riid: *const windows_core::GUID,
ppdevice: *mut *mut core::ffi::c_void,
) -> windows_core::HRESULT;
let func: libloading::Symbol<Fun> = unsafe { self.lib.get(b"D3D12CreateDevice") }?;
let func: libloading::Symbol<Fun> = unsafe { self.lib.get(b"D3D12CreateDevice\0") }?;
let mut result__ = None;
Ok((func)(
unsafe { adapter.param().abi() },
(func)(
adapter.as_raw(),
feature_level,
// TODO: Generic?
&Direct3D12::ID3D12Device::IID,
<*mut _>::cast(&mut result__),
)
.map(|| result__.expect("D3D12CreateDevice succeeded but result is NULL?")))
.ok()
.into_device_result("Device creation")?;
result__.ok_or(crate::DeviceError::Unexpected)
}
fn serialize_root_signature(
@ -115,11 +147,8 @@ impl D3D12Lib {
ppblob: *mut *mut core::ffi::c_void,
pperrorblob: *mut *mut core::ffi::c_void,
) -> windows_core::HRESULT;
let func: libloading::Symbol<Fun> = unsafe { self.lib.get(b"D3D12SerializeRootSignature") }
.map_err(|e| {
log::error!("Unable to find serialization function: {:?}", e);
crate::DeviceError::Lost
})?;
let func: libloading::Symbol<Fun> =
unsafe { self.lib.get(b"D3D12SerializeRootSignature\0") }?;
let desc = Direct3D12::D3D12_ROOT_SIGNATURE_DESC {
NumParameters: parameters.len() as _,
@ -138,8 +167,6 @@ impl D3D12Lib {
<*mut _>::cast(&mut error),
)
.ok()
// TODO: If there's a HRESULT, error may still be non-null and
// contain info.
.into_device_result("Root signature serialization")?;
if let Some(error) = error {
@ -148,104 +175,102 @@ impl D3D12Lib {
"Root signature serialization error: {:?}",
unsafe { error.as_c_str() }.unwrap().to_str().unwrap()
);
return Err(crate::DeviceError::Lost);
return Err(crate::DeviceError::Unexpected); // could be hal_usage_error or hal_internal_error
}
Ok(D3DBlob(blob.expect(
"D3D12SerializeRootSignature succeeded but result is NULL?",
)))
blob.ok_or(crate::DeviceError::Unexpected)
}
fn debug_interface(
&self,
) -> Result<windows::core::Result<Direct3D12::ID3D12Debug>, libloading::Error> {
fn debug_interface(&self) -> Result<Direct3D12::ID3D12Debug, crate::DeviceError> {
// Calls windows::Win32::Graphics::Direct3D12::D3D12GetDebugInterface on d3d12.dll
type Fun = extern "system" fn(
riid: *const windows_core::GUID,
ppvdebug: *mut *mut core::ffi::c_void,
) -> windows_core::HRESULT;
let func: libloading::Symbol<Fun> = unsafe { self.lib.get(b"D3D12GetDebugInterface") }?;
let func: libloading::Symbol<Fun> = unsafe { self.lib.get(b"D3D12GetDebugInterface\0") }?;
let mut result__ = core::ptr::null_mut();
Ok((func)(&Direct3D12::ID3D12Debug::IID, &mut result__)
.and_then(|| unsafe { windows_core::Type::from_abi(result__) }))
let mut result__ = None;
(func)(&Direct3D12::ID3D12Debug::IID, <*mut _>::cast(&mut result__))
.ok()
.into_device_result("GetDebugInterface")?;
result__.ok_or(crate::DeviceError::Unexpected)
}
}
#[derive(Debug)]
pub(super) struct DxgiLib {
lib: libloading::Library,
lib: DynLib,
}
impl DxgiLib {
pub fn new() -> Result<Self, libloading::Error> {
unsafe { libloading::Library::new("dxgi.dll").map(|lib| DxgiLib { lib }) }
unsafe { DynLib::new("dxgi.dll").map(|lib| Self { lib }) }
}
pub fn debug_interface1(
&self,
) -> Result<windows::core::Result<Dxgi::IDXGIInfoQueue>, libloading::Error> {
/// Will error with crate::DeviceError::Unexpected if DXGI 1.3 is not available.
pub fn debug_interface1(&self) -> Result<Dxgi::IDXGIInfoQueue, crate::DeviceError> {
// Calls windows::Win32::Graphics::Dxgi::DXGIGetDebugInterface1 on dxgi.dll
type Fun = extern "system" fn(
flags: u32,
riid: *const windows_core::GUID,
pdebug: *mut *mut core::ffi::c_void,
) -> windows_core::HRESULT;
let func: libloading::Symbol<Fun> = unsafe { self.lib.get(b"DXGIGetDebugInterface1") }?;
let func: libloading::Symbol<Fun> = unsafe { self.lib.get(b"DXGIGetDebugInterface1\0") }?;
let mut result__ = core::ptr::null_mut();
Ok((func)(0, &Dxgi::IDXGIInfoQueue::IID, &mut result__)
.and_then(|| unsafe { windows_core::Type::from_abi(result__) }))
let mut result__ = None;
(func)(0, &Dxgi::IDXGIInfoQueue::IID, <*mut _>::cast(&mut result__))
.ok()
.into_device_result("debug_interface1")?;
result__.ok_or(crate::DeviceError::Unexpected)
}
pub fn create_factory1(
&self,
) -> Result<windows::core::Result<Dxgi::IDXGIFactory1>, libloading::Error> {
// Calls windows::Win32::Graphics::Dxgi::CreateDXGIFactory1 on dxgi.dll
type Fun = extern "system" fn(
riid: *const windows_core::GUID,
ppfactory: *mut *mut core::ffi::c_void,
) -> windows_core::HRESULT;
let func: libloading::Symbol<Fun> = unsafe { self.lib.get(b"CreateDXGIFactory1") }?;
let mut result__ = core::ptr::null_mut();
Ok((func)(&Dxgi::IDXGIFactory1::IID, &mut result__)
.and_then(|| unsafe { windows_core::Type::from_abi(result__) }))
}
pub fn create_factory2(
/// Will error with crate::DeviceError::Unexpected if DXGI 1.4 is not available.
pub fn create_factory4(
&self,
factory_flags: Dxgi::DXGI_CREATE_FACTORY_FLAGS,
) -> Result<windows::core::Result<Dxgi::IDXGIFactory4>, libloading::Error> {
) -> Result<Dxgi::IDXGIFactory4, crate::DeviceError> {
// Calls windows::Win32::Graphics::Dxgi::CreateDXGIFactory2 on dxgi.dll
type Fun = extern "system" fn(
flags: Dxgi::DXGI_CREATE_FACTORY_FLAGS,
riid: *const windows_core::GUID,
ppfactory: *mut *mut core::ffi::c_void,
) -> windows_core::HRESULT;
let func: libloading::Symbol<Fun> = unsafe { self.lib.get(b"CreateDXGIFactory2") }?;
let func: libloading::Symbol<Fun> = unsafe { self.lib.get(b"CreateDXGIFactory2\0") }?;
let mut result__ = core::ptr::null_mut();
Ok(
(func)(factory_flags, &Dxgi::IDXGIFactory4::IID, &mut result__)
.and_then(|| unsafe { windows_core::Type::from_abi(result__) }),
let mut result__ = None;
(func)(
factory_flags,
&Dxgi::IDXGIFactory4::IID,
<*mut _>::cast(&mut result__),
)
.ok()
.into_device_result("create_factory4")?;
result__.ok_or(crate::DeviceError::Unexpected)
}
pub fn create_factory_media(
&self,
) -> Result<windows::core::Result<Dxgi::IDXGIFactoryMedia>, libloading::Error> {
/// Will error with crate::DeviceError::Unexpected if DXGI 1.3 is not available.
pub fn create_factory_media(&self) -> Result<Dxgi::IDXGIFactoryMedia, crate::DeviceError> {
// Calls windows::Win32::Graphics::Dxgi::CreateDXGIFactory1 on dxgi.dll
type Fun = extern "system" fn(
riid: *const windows_core::GUID,
ppfactory: *mut *mut core::ffi::c_void,
) -> windows_core::HRESULT;
let func: libloading::Symbol<Fun> = unsafe { self.lib.get(b"CreateDXGIFactory1") }?;
let func: libloading::Symbol<Fun> = unsafe { self.lib.get(b"CreateDXGIFactory1\0") }?;
let mut result__ = None;
let mut result__ = core::ptr::null_mut();
// https://learn.microsoft.com/en-us/windows/win32/api/dxgi1_3/nn-dxgi1_3-idxgifactorymedia
Ok((func)(&Dxgi::IDXGIFactoryMedia::IID, &mut result__)
.and_then(|| unsafe { windows_core::Type::from_abi(result__) }))
(func)(&Dxgi::IDXGIFactoryMedia::IID, <*mut _>::cast(&mut result__))
.ok()
.into_device_result("create_factory_media")?;
result__.ok_or(crate::DeviceError::Unexpected)
}
}
@ -368,7 +393,7 @@ pub struct Instance {
}
impl Instance {
pub unsafe fn create_surface_from_visual(&self, visual: *mut std::ffi::c_void) -> Surface {
pub unsafe fn create_surface_from_visual(&self, visual: *mut ffi::c_void) -> Surface {
let visual = unsafe { DirectComposition::IDCompositionVisual::from_raw_borrowed(&visual) }
.expect("COM pointer should not be NULL");
Surface {
@ -382,7 +407,7 @@ impl Instance {
pub unsafe fn create_surface_from_surface_handle(
&self,
surface_handle: *mut std::ffi::c_void,
surface_handle: *mut ffi::c_void,
) -> Surface {
// TODO: We're not given ownership, so we shouldn't call HANDLE::free(). This puts an extra burden on the caller to keep it alive.
// https://learn.microsoft.com/en-us/windows/win32/api/handleapi/nf-handleapi-duplicatehandle could help us, even though DirectComposition is not in the list?
@ -399,7 +424,7 @@ impl Instance {
pub unsafe fn create_surface_from_swap_chain_panel(
&self,
swap_chain_panel: *mut std::ffi::c_void,
swap_chain_panel: *mut ffi::c_void,
) -> Surface {
let swap_chain_panel =
unsafe { types::ISwapChainPanelNative::from_raw_borrowed(&swap_chain_panel) }
@ -622,7 +647,7 @@ struct PassState {
#[test]
fn test_dirty_mask() {
assert_eq!(MAX_ROOT_ELEMENTS, mem::size_of::<u64>() * 8);
assert_eq!(MAX_ROOT_ELEMENTS, u64::BITS as usize);
}
impl PassState {
@ -897,8 +922,7 @@ pub struct ShaderModule {
impl crate::DynShaderModule for ShaderModule {}
pub(super) enum CompiledShader {
#[allow(unused)]
Dxc(Vec<u8>),
Dxc(Direct3D::Dxc::IDxcBlob),
Fxc(Direct3D::ID3DBlob),
}
@ -906,8 +930,8 @@ impl CompiledShader {
fn create_native_shader(&self) -> Direct3D12::D3D12_SHADER_BYTECODE {
match self {
CompiledShader::Dxc(shader) => Direct3D12::D3D12_SHADER_BYTECODE {
pShaderBytecode: shader.as_ptr().cast(),
BytecodeLength: shader.len(),
pShaderBytecode: unsafe { shader.GetBufferPointer() },
BytecodeLength: unsafe { shader.GetBufferSize() },
},
CompiledShader::Fxc(shader) => Direct3D12::D3D12_SHADER_BYTECODE {
pShaderBytecode: unsafe { shader.GetBufferPointer() },
@ -1026,8 +1050,8 @@ impl crate::Surface for Surface {
flags,
)
};
if let Err(err) = result.into_result() {
log::error!("ResizeBuffers failed: {}", err);
if let Err(err) = result {
log::error!("ResizeBuffers failed: {err}");
return Err(crate::SurfaceError::Other("window is in use"));
}
raw
@ -1053,11 +1077,13 @@ impl crate::Surface for Surface {
};
let swap_chain1 = match self.target {
SurfaceTarget::Visual(_) | SurfaceTarget::SwapChainPanel(_) => {
profiling::scope!("IDXGIFactory4::CreateSwapChainForComposition");
profiling::scope!("IDXGIFactory2::CreateSwapChainForComposition");
unsafe {
self.factory
.unwrap_factory2()
.CreateSwapChainForComposition(&device.present_queue, &desc, None)
self.factory.CreateSwapChainForComposition(
&device.present_queue,
&desc,
None,
)
}
}
SurfaceTarget::SurfaceHandle(handle) => {
@ -1077,9 +1103,9 @@ impl crate::Surface for Surface {
}
}
SurfaceTarget::WndHandle(hwnd) => {
profiling::scope!("IDXGIFactory4::CreateSwapChainForHwnd");
profiling::scope!("IDXGIFactory2::CreateSwapChainForHwnd");
unsafe {
self.factory.unwrap_factory2().CreateSwapChainForHwnd(
self.factory.CreateSwapChainForHwnd(
&device.present_queue,
hwnd,
&desc,
@ -1092,24 +1118,22 @@ impl crate::Surface for Surface {
let swap_chain1 = swap_chain1.map_err(|err| {
log::error!("SwapChain creation error: {}", err);
crate::SurfaceError::Other("swap chain creation")
crate::SurfaceError::Other("swapchain creation")
})?;
match &self.target {
SurfaceTarget::WndHandle(_) | SurfaceTarget::SurfaceHandle(_) => {}
SurfaceTarget::Visual(visual) => {
if let Err(err) = unsafe { visual.SetContent(&swap_chain1) }.into_result() {
log::error!("Unable to SetContent: {}", err);
if let Err(err) = unsafe { visual.SetContent(&swap_chain1) } {
log::error!("Unable to SetContent: {err}");
return Err(crate::SurfaceError::Other(
"IDCompositionVisual::SetContent",
));
}
}
SurfaceTarget::SwapChainPanel(swap_chain_panel) => {
if let Err(err) =
unsafe { swap_chain_panel.SetSwapChain(&swap_chain1) }.into_result()
{
log::error!("Unable to SetSwapChain: {}", err);
if let Err(err) = unsafe { swap_chain_panel.SetSwapChain(&swap_chain1) } {
log::error!("Unable to SetSwapChain: {err}");
return Err(crate::SurfaceError::Other(
"ISwapChainPanelNative::SetSwapChain",
));
@ -1117,13 +1141,10 @@ impl crate::Surface for Surface {
}
}
match swap_chain1.cast::<Dxgi::IDXGISwapChain3>() {
Ok(swap_chain3) => swap_chain3,
Err(err) => {
log::error!("Unable to cast swap chain: {}", err);
return Err(crate::SurfaceError::Other("swap chain cast to 3"));
}
}
swap_chain1.cast::<Dxgi::IDXGISwapChain3>().map_err(|err| {
log::error!("Unable to cast swapchain: {err}");
crate::SurfaceError::Other("swapchain cast to version 3")
})?
}
};

View File

@ -1,13 +1,11 @@
use std::ffi::CStr;
use std::ptr;
pub(super) use dxc::{compile_dxc, get_dxc_container, DxcContainer};
use windows::Win32::Graphics::Direct3D;
use crate::auxil::dxgi::result::HResult;
use std::ffi::CStr;
use std::path::PathBuf;
use windows::{
core::{Interface, PCSTR, PCWSTR},
Win32::Graphics::Direct3D::{Dxc, Fxc},
};
// This exists so that users who don't want to use dxc can disable the dxc_shader_compiler feature
// and not have to compile hassle_rs.
// Currently this will use Dxc if it is chosen as the dx12 compiler at `Instance` creation time, and will
// fallback to FXC if the Dxc libraries (dxil.dll and dxcompiler.dll) are not found, or if Fxc is chosen at'
// `Instance` creation time.
@ -16,40 +14,41 @@ pub(super) fn compile_fxc(
device: &super::Device,
source: &str,
source_name: Option<&CStr>,
raw_ep: &CStr,
raw_ep: &str,
stage_bit: wgt::ShaderStages,
full_stage: &CStr,
) -> (
Result<super::CompiledShader, crate::PipelineError>,
log::Level,
) {
full_stage: &str,
) -> Result<super::CompiledShader, crate::PipelineError> {
profiling::scope!("compile_fxc");
let mut shader_data = None;
let mut compile_flags = Direct3D::Fxc::D3DCOMPILE_ENABLE_STRICTNESS;
let mut compile_flags = Fxc::D3DCOMPILE_ENABLE_STRICTNESS;
if device
.private_caps
.instance_flags
.contains(wgt::InstanceFlags::DEBUG)
{
compile_flags |=
Direct3D::Fxc::D3DCOMPILE_DEBUG | Direct3D::Fxc::D3DCOMPILE_SKIP_OPTIMIZATION;
compile_flags |= Fxc::D3DCOMPILE_DEBUG | Fxc::D3DCOMPILE_SKIP_OPTIMIZATION;
}
let raw_ep = std::ffi::CString::new(raw_ep).unwrap();
let full_stage = std::ffi::CString::new(full_stage).unwrap();
// If no name has been set, D3DCompile wants the null pointer.
let source_name = source_name.map(|cstr| cstr.as_ptr()).unwrap_or(ptr::null());
let source_name = source_name
.map(|cstr| cstr.as_ptr().cast())
.unwrap_or(core::ptr::null());
let mut error = None;
let hr = unsafe {
profiling::scope!("Direct3D::Fxc::D3DCompile");
Direct3D::Fxc::D3DCompile(
profiling::scope!("Fxc::D3DCompile");
Fxc::D3DCompile(
// TODO: Update low-level bindings to accept a slice here
source.as_ptr().cast(),
source.len(),
windows::core::PCSTR(source_name.cast()),
PCSTR(source_name),
None,
None,
windows::core::PCSTR(raw_ep.as_ptr().cast()),
windows::core::PCSTR(full_stage.as_ptr().cast()),
PCSTR(raw_ep.as_ptr().cast()),
PCSTR(full_stage.as_ptr().cast()),
compile_flags,
0,
&mut shader_data,
@ -57,13 +56,10 @@ pub(super) fn compile_fxc(
)
};
match hr.into_result() {
match hr {
Ok(()) => {
let shader_data = shader_data.unwrap();
(
Ok(super::CompiledShader::Fxc(shader_data)),
log::Level::Info,
)
Ok(super::CompiledShader::Fxc(shader_data))
}
Err(e) => {
let mut full_msg = format!("FXC D3DCompile error ({e})");
@ -77,234 +73,237 @@ pub(super) fn compile_fxc(
};
let _ = write!(full_msg, ": {}", String::from_utf8_lossy(message));
}
(
Err(crate::PipelineError::Linkage(stage_bit, full_msg)),
log::Level::Warn,
)
Err(crate::PipelineError::Linkage(stage_bit, full_msg))
}
}
}
// The Dxc implementation is behind a feature flag so that users who don't want to use dxc can disable the feature.
#[cfg(feature = "dxc_shader_compiler")]
mod dxc {
use std::ffi::CStr;
use std::path::PathBuf;
trait DxcObj: Interface {
const CLSID: windows::core::GUID;
}
impl DxcObj for Dxc::IDxcCompiler3 {
const CLSID: windows::core::GUID = Dxc::CLSID_DxcCompiler;
}
impl DxcObj for Dxc::IDxcUtils {
const CLSID: windows::core::GUID = Dxc::CLSID_DxcUtils;
}
impl DxcObj for Dxc::IDxcValidator {
const CLSID: windows::core::GUID = Dxc::CLSID_DxcValidator;
}
// Destructor order should be fine since _dxil and _dxc don't rely on each other.
pub(crate) struct DxcContainer {
compiler: hassle_rs::DxcCompiler,
library: hassle_rs::DxcLibrary,
validator: hassle_rs::DxcValidator,
// Has to be held onto for the lifetime of the device otherwise shaders will fail to compile.
_dxc: hassle_rs::Dxc,
// Also Has to be held onto for the lifetime of the device otherwise shaders will fail to validate.
_dxil: hassle_rs::Dxil,
}
#[derive(Debug)]
struct DxcLib {
lib: crate::dx12::DynLib,
}
pub(crate) fn get_dxc_container(
dxc_path: Option<PathBuf>,
dxil_path: Option<PathBuf>,
) -> Result<Option<DxcContainer>, crate::DeviceError> {
// Make sure that dxil.dll exists.
let dxil = match hassle_rs::Dxil::new(dxil_path) {
Ok(dxil) => dxil,
Err(e) => {
log::warn!("Failed to load dxil.dll. Defaulting to FXC instead: {}", e);
return Ok(None);
impl DxcLib {
fn new(lib_path: Option<PathBuf>, lib_name: &'static str) -> Result<Self, libloading::Error> {
let lib_path = if let Some(lib_path) = lib_path {
if lib_path.is_file() {
lib_path
} else {
lib_path.join(lib_name)
}
} else {
PathBuf::from(lib_name)
};
// Needed for explicit validation.
let validator = dxil.create_validator()?;
let dxc = match hassle_rs::Dxc::new(dxc_path) {
Ok(dxc) => dxc,
Err(e) => {
log::warn!(
"Failed to load dxcompiler.dll. Defaulting to FXC instead: {}",
e
);
return Ok(None);
}
};
let compiler = dxc.create_compiler()?;
let library = dxc.create_library()?;
Ok(Some(DxcContainer {
_dxc: dxc,
compiler,
library,
_dxil: dxil,
validator,
}))
unsafe { crate::dx12::DynLib::new(lib_path).map(|lib| Self { lib }) }
}
pub(crate) fn compile_dxc(
device: &crate::dx12::Device,
source: &str,
source_name: Option<&CStr>,
raw_ep: &str,
stage_bit: wgt::ShaderStages,
full_stage: String,
dxc_container: &DxcContainer,
) -> (
Result<crate::dx12::CompiledShader, crate::PipelineError>,
log::Level,
) {
profiling::scope!("compile_dxc");
let mut compile_flags = arrayvec::ArrayVec::<&str, 6>::new_const();
compile_flags.push("-Ges"); // Direct3D::Fxc::D3DCOMPILE_ENABLE_STRICTNESS
compile_flags.push("-Vd"); // Disable implicit validation to work around bugs when dxil.dll isn't in the local directory.
compile_flags.push("-HV"); // Use HLSL 2018, Naga doesn't supported 2021 yet.
compile_flags.push("2018");
pub fn create_instance<T: DxcObj>(&self) -> Result<T, crate::DeviceError> {
type Fun = extern "system" fn(
rclsid: *const windows_core::GUID,
riid: *const windows_core::GUID,
ppv: *mut *mut core::ffi::c_void,
) -> windows_core::HRESULT;
let func: libloading::Symbol<Fun> = unsafe { self.lib.get(b"DxcCreateInstance\0") }?;
if device
.private_caps
.instance_flags
.contains(wgt::InstanceFlags::DEBUG)
{
compile_flags.push("-Zi"); // Direct3D::Fxc::D3DCOMPILE_SKIP_OPTIMIZATION
compile_flags.push("-Od"); // Direct3D::Fxc::D3DCOMPILE_DEBUG
}
let blob = match dxc_container
.library
.create_blob_with_encoding_from_str(source)
.map_err(|e| crate::PipelineError::Linkage(stage_bit, format!("DXC blob error: {e}")))
{
Ok(blob) => blob,
Err(e) => return (Err(e), log::Level::Error),
};
let source_name = source_name
.and_then(|cstr| cstr.to_str().ok())
.unwrap_or("");
let compiled = dxc_container.compiler.compile(
&blob,
source_name,
raw_ep,
&full_stage,
&compile_flags,
None,
&[],
);
let (result, log_level) = match compiled {
Ok(dxc_result) => match dxc_result.get_result() {
Ok(dxc_blob) => {
// Validate the shader.
match dxc_container.validator.validate(dxc_blob) {
Ok(validated_blob) => (
Ok(crate::dx12::CompiledShader::Dxc(validated_blob.to_vec())),
log::Level::Info,
),
Err(e) => (
Err(crate::PipelineError::Linkage(
stage_bit,
format!(
"DXC validation error: {:?}\n{:?}",
get_error_string_from_dxc_result(&dxc_container.library, &e.0)
.unwrap_or_default(),
e.1
),
)),
log::Level::Error,
),
}
}
Err(e) => (
Err(crate::PipelineError::Linkage(
stage_bit,
format!("DXC compile error: {e}"),
)),
log::Level::Error,
),
},
Err(e) => (
Err(crate::PipelineError::Linkage(
stage_bit,
format!(
"DXC compile error: {}",
get_error_string_from_dxc_result(&dxc_container.library, &e.0)
.unwrap_or_default()
),
)),
log::Level::Error,
),
};
(result, log_level)
}
impl From<hassle_rs::HassleError> for crate::DeviceError {
fn from(value: hassle_rs::HassleError) -> Self {
match value {
hassle_rs::HassleError::Win32Error(e) => {
// TODO: This returns an HRESULT, should we try and use the associated Windows error message?
log::error!("Win32 error: {e:?}");
crate::DeviceError::Lost
}
hassle_rs::HassleError::LoadLibraryError { filename, inner } => {
log::error!("Failed to load dxc library {filename:?}. Inner error: {inner:?}");
crate::DeviceError::Lost
}
hassle_rs::HassleError::LibLoadingError(e) => {
log::error!("Failed to load dxc library. {e:?}");
crate::DeviceError::Lost
}
hassle_rs::HassleError::WindowsOnly(e) => {
log::error!("Signing with dxil.dll is only supported on Windows. {e:?}");
crate::DeviceError::Lost
}
// `ValidationError` and `CompileError` should never happen in a context involving `DeviceError`
hassle_rs::HassleError::ValidationError(_e) => unimplemented!(),
hassle_rs::HassleError::CompileError(_e) => unimplemented!(),
}
}
}
fn get_error_string_from_dxc_result(
library: &hassle_rs::DxcLibrary,
error: &hassle_rs::DxcOperationResult,
) -> Result<String, hassle_rs::HassleError> {
error
.get_error_buffer()
.and_then(|error| library.get_blob_as_string(&hassle_rs::DxcBlob::from(error)))
let mut result__ = None;
(func)(&T::CLSID, &T::IID, <*mut _>::cast(&mut result__))
.ok()
.into_device_result("DxcCreateInstance")?;
result__.ok_or(crate::DeviceError::Unexpected)
}
}
// These are stubs for when the `dxc_shader_compiler` feature is disabled.
#[cfg(not(feature = "dxc_shader_compiler"))]
mod dxc {
use std::ffi::CStr;
use std::path::PathBuf;
// Destructor order should be fine since _dxil and _dxc don't rely on each other.
pub(super) struct DxcContainer {
compiler: Dxc::IDxcCompiler3,
utils: Dxc::IDxcUtils,
validator: Dxc::IDxcValidator,
// Has to be held onto for the lifetime of the device otherwise shaders will fail to compile.
_dxc: DxcLib,
// Also Has to be held onto for the lifetime of the device otherwise shaders will fail to validate.
_dxil: DxcLib,
}
pub(crate) struct DxcContainer {}
pub(super) fn get_dxc_container(
dxc_path: Option<PathBuf>,
dxil_path: Option<PathBuf>,
) -> Result<Option<DxcContainer>, crate::DeviceError> {
let dxc = match DxcLib::new(dxc_path, "dxcompiler.dll") {
Ok(dxc) => dxc,
Err(e) => {
log::warn!(
"Failed to load dxcompiler.dll. Defaulting to FXC instead: {}",
e
);
return Ok(None);
}
};
pub(crate) fn get_dxc_container(
_dxc_path: Option<PathBuf>,
_dxil_path: Option<PathBuf>,
) -> Result<Option<DxcContainer>, crate::DeviceError> {
// Falls back to Fxc and logs an error.
log::error!("DXC shader compiler was requested on Instance creation, but the DXC feature is disabled. Enable the `dxc_shader_compiler` feature on wgpu_hal to use DXC.");
Ok(None)
let dxil = match DxcLib::new(dxil_path, "dxil.dll") {
Ok(dxil) => dxil,
Err(e) => {
log::warn!("Failed to load dxil.dll. Defaulting to FXC instead: {}", e);
return Ok(None);
}
};
let compiler = dxc.create_instance::<Dxc::IDxcCompiler3>()?;
let utils = dxc.create_instance::<Dxc::IDxcUtils>()?;
let validator = dxil.create_instance::<Dxc::IDxcValidator>()?;
Ok(Some(DxcContainer {
compiler,
utils,
validator,
_dxc: dxc,
_dxil: dxil,
}))
}
/// Owned PCWSTR
#[allow(clippy::upper_case_acronyms)]
struct OPCWSTR {
inner: Vec<u16>,
}
impl OPCWSTR {
fn new(s: &str) -> Self {
let mut inner: Vec<_> = s.encode_utf16().collect();
inner.push(0);
Self { inner }
}
// It shouldn't be possible that this gets called with the `dxc_shader_compiler` feature disabled.
pub(crate) fn compile_dxc(
_device: &crate::dx12::Device,
_source: &str,
_source_name: Option<&CStr>,
_raw_ep: &str,
_stage_bit: wgt::ShaderStages,
_full_stage: String,
_dxc_container: &DxcContainer,
) -> (
Result<crate::dx12::CompiledShader, crate::PipelineError>,
log::Level,
) {
unimplemented!("Something went really wrong, please report this. Attempted to compile shader with DXC, but the DXC feature is disabled. Enable the `dxc_shader_compiler` feature on wgpu_hal to use DXC.");
fn ptr(&self) -> PCWSTR {
PCWSTR(self.inner.as_ptr())
}
}
fn get_output<T: Interface>(
res: &Dxc::IDxcResult,
kind: Dxc::DXC_OUT_KIND,
) -> Result<T, crate::DeviceError> {
let mut result__: Option<T> = None;
unsafe { res.GetOutput::<T>(kind, &mut None, <*mut _>::cast(&mut result__)) }
.into_device_result("GetOutput")?;
result__.ok_or(crate::DeviceError::Unexpected)
}
fn as_err_str(blob: &Dxc::IDxcBlobUtf8) -> Result<&str, crate::DeviceError> {
let ptr = unsafe { blob.GetStringPointer() };
let len = unsafe { blob.GetStringLength() };
core::str::from_utf8(unsafe { core::slice::from_raw_parts(ptr.0, len) })
.map_err(|_| crate::DeviceError::Unexpected)
}
pub(super) fn compile_dxc(
device: &crate::dx12::Device,
source: &str,
source_name: Option<&CStr>,
raw_ep: &str,
stage_bit: wgt::ShaderStages,
full_stage: &str,
dxc_container: &DxcContainer,
) -> Result<crate::dx12::CompiledShader, crate::PipelineError> {
profiling::scope!("compile_dxc");
let source_name = source_name.and_then(|cstr| cstr.to_str().ok());
let source_name = source_name.map(OPCWSTR::new);
let raw_ep = OPCWSTR::new(raw_ep);
let full_stage = OPCWSTR::new(full_stage);
let mut compile_args = arrayvec::ArrayVec::<PCWSTR, 12>::new_const();
if let Some(source_name) = source_name.as_ref() {
compile_args.push(source_name.ptr())
}
compile_args.extend([
windows::core::w!("-E"),
raw_ep.ptr(),
windows::core::w!("-T"),
full_stage.ptr(),
windows::core::w!("-HV"),
windows::core::w!("2018"), // Use HLSL 2018, Naga doesn't supported 2021 yet.
windows::core::w!("-no-warnings"),
Dxc::DXC_ARG_ENABLE_STRICTNESS,
Dxc::DXC_ARG_SKIP_VALIDATION, // Disable implicit validation to work around bugs when dxil.dll isn't in the local directory.
]);
if device
.private_caps
.instance_flags
.contains(wgt::InstanceFlags::DEBUG)
{
compile_args.push(Dxc::DXC_ARG_DEBUG);
compile_args.push(Dxc::DXC_ARG_SKIP_OPTIMIZATIONS);
}
let buffer = Dxc::DxcBuffer {
Ptr: source.as_ptr().cast(),
Size: source.len(),
Encoding: Dxc::DXC_CP_UTF8.0,
};
let compile_res: Dxc::IDxcResult = unsafe {
dxc_container
.compiler
.Compile(&buffer, Some(&compile_args), None)
}
.into_device_result("Compile")?;
drop(compile_args);
drop(source_name);
drop(raw_ep);
drop(full_stage);
let err_blob = get_output::<Dxc::IDxcBlobUtf8>(&compile_res, Dxc::DXC_OUT_ERRORS)?;
let len = unsafe { err_blob.GetStringLength() };
if len != 0 {
let err = as_err_str(&err_blob)?;
return Err(crate::PipelineError::Linkage(
stage_bit,
format!("DXC compile error: {err}"),
));
}
let blob = get_output::<Dxc::IDxcBlob>(&compile_res, Dxc::DXC_OUT_OBJECT)?;
let err_blob = {
let res = unsafe {
dxc_container
.validator
.Validate(&blob, Dxc::DxcValidatorFlags_InPlaceEdit)
}
.into_device_result("Validate")?;
unsafe { res.GetErrorBuffer() }.into_device_result("GetErrorBuffer")?
};
let size = unsafe { err_blob.GetBufferSize() };
if size != 0 {
let err_blob = unsafe { dxc_container.utils.GetBlobAsUtf8(&err_blob) }
.into_device_result("GetBlobAsUtf8")?;
let err = as_err_str(&err_blob)?;
return Err(crate::PipelineError::Linkage(
stage_bit,
format!("DXC validation error: {err}"),
));
}
Ok(crate::dx12::CompiledShader::Dxc(blob))
}

View File

@ -52,14 +52,14 @@ pub(crate) fn create_buffer_resource(
device: &crate::dx12::Device,
desc: &crate::BufferDescriptor,
raw_desc: Direct3D12::D3D12_RESOURCE_DESC,
resource: &mut Option<Direct3D12::ID3D12Resource>,
) -> Result<Option<AllocationWrapper>, crate::DeviceError> {
) -> Result<(Direct3D12::ID3D12Resource, Option<AllocationWrapper>), crate::DeviceError> {
let is_cpu_read = desc.usage.contains(crate::BufferUses::MAP_READ);
let is_cpu_write = desc.usage.contains(crate::BufferUses::MAP_WRITE);
// Workaround for Intel Xe drivers
if !device.private_caps.suballocation_supported {
return create_committed_buffer_resource(device, desc, raw_desc, resource).map(|()| None);
return create_committed_buffer_resource(device, desc, raw_desc)
.map(|resource| (resource, None));
}
let location = match (is_cpu_read, is_cpu_write) {
@ -80,6 +80,7 @@ pub(crate) fn create_buffer_resource(
location,
);
let allocation = allocator.allocator.allocate(&allocation_desc)?;
let mut resource = None;
unsafe {
device.raw.CreatePlacedResource(
@ -88,32 +89,30 @@ pub(crate) fn create_buffer_resource(
&raw_desc,
Direct3D12::D3D12_RESOURCE_STATE_COMMON,
None,
resource,
&mut resource,
)
}
.into_device_result("Placed buffer creation")?;
if resource.is_none() {
return Err(crate::DeviceError::ResourceCreationFailed);
}
let resource = resource.ok_or(crate::DeviceError::Unexpected)?;
device
.counters
.buffer_memory
.add(allocation.size() as isize);
Ok(Some(AllocationWrapper { allocation }))
Ok((resource, Some(AllocationWrapper { allocation })))
}
pub(crate) fn create_texture_resource(
device: &crate::dx12::Device,
desc: &crate::TextureDescriptor,
raw_desc: Direct3D12::D3D12_RESOURCE_DESC,
resource: &mut Option<Direct3D12::ID3D12Resource>,
) -> Result<Option<AllocationWrapper>, crate::DeviceError> {
) -> Result<(Direct3D12::ID3D12Resource, Option<AllocationWrapper>), crate::DeviceError> {
// Workaround for Intel Xe drivers
if !device.private_caps.suballocation_supported {
return create_committed_texture_resource(device, desc, raw_desc, resource).map(|()| None);
return create_committed_texture_resource(device, desc, raw_desc)
.map(|resource| (resource, None));
}
let location = MemoryLocation::GpuOnly;
@ -128,6 +127,7 @@ pub(crate) fn create_texture_resource(
location,
);
let allocation = allocator.allocator.allocate(&allocation_desc)?;
let mut resource = None;
unsafe {
device.raw.CreatePlacedResource(
@ -136,21 +136,19 @@ pub(crate) fn create_texture_resource(
&raw_desc,
Direct3D12::D3D12_RESOURCE_STATE_COMMON,
None, // clear value
resource,
&mut resource,
)
}
.into_device_result("Placed texture creation")?;
if resource.is_none() {
return Err(crate::DeviceError::ResourceCreationFailed);
}
let resource = resource.ok_or(crate::DeviceError::Unexpected)?;
device
.counters
.texture_memory
.add(allocation.size() as isize);
Ok(Some(AllocationWrapper { allocation }))
Ok((resource, Some(AllocationWrapper { allocation })))
}
pub(crate) fn free_buffer_allocation(
@ -226,8 +224,7 @@ pub(crate) fn create_committed_buffer_resource(
device: &crate::dx12::Device,
desc: &crate::BufferDescriptor,
raw_desc: Direct3D12::D3D12_RESOURCE_DESC,
resource: &mut Option<Direct3D12::ID3D12Resource>,
) -> Result<(), crate::DeviceError> {
) -> Result<Direct3D12::ID3D12Resource, crate::DeviceError> {
let is_cpu_read = desc.usage.contains(crate::BufferUses::MAP_READ);
let is_cpu_write = desc.usage.contains(crate::BufferUses::MAP_WRITE);
@ -250,6 +247,8 @@ pub(crate) fn create_committed_buffer_resource(
VisibleNodeMask: 0,
};
let mut resource = None;
unsafe {
device.raw.CreateCommittedResource(
&heap_properties,
@ -261,24 +260,19 @@ pub(crate) fn create_committed_buffer_resource(
&raw_desc,
Direct3D12::D3D12_RESOURCE_STATE_COMMON,
None,
resource,
&mut resource,
)
}
.into_device_result("Committed buffer creation")?;
if resource.is_none() {
return Err(crate::DeviceError::ResourceCreationFailed);
}
Ok(())
resource.ok_or(crate::DeviceError::Unexpected)
}
pub(crate) fn create_committed_texture_resource(
device: &crate::dx12::Device,
_desc: &crate::TextureDescriptor,
raw_desc: Direct3D12::D3D12_RESOURCE_DESC,
resource: &mut Option<Direct3D12::ID3D12Resource>,
) -> Result<(), crate::DeviceError> {
) -> Result<Direct3D12::ID3D12Resource, crate::DeviceError> {
let heap_properties = Direct3D12::D3D12_HEAP_PROPERTIES {
Type: Direct3D12::D3D12_HEAP_TYPE_CUSTOM,
CPUPageProperty: Direct3D12::D3D12_CPU_PAGE_PROPERTY_NOT_AVAILABLE,
@ -290,6 +284,8 @@ pub(crate) fn create_committed_texture_resource(
VisibleNodeMask: 0,
};
let mut resource = None;
unsafe {
device.raw.CreateCommittedResource(
&heap_properties,
@ -301,14 +297,10 @@ pub(crate) fn create_committed_texture_resource(
&raw_desc,
Direct3D12::D3D12_RESOURCE_STATE_COMMON,
None, // clear value
resource,
&mut resource,
)
}
.into_device_result("Committed texture creation")?;
if resource.is_none() {
return Err(crate::DeviceError::ResourceCreationFailed);
}
Ok(())
resource.ok_or(crate::DeviceError::Unexpected)
}

View File

@ -841,6 +841,16 @@ impl super::Adapter {
alignments: crate::Alignments {
buffer_copy_offset: wgt::BufferSize::new(4).unwrap(),
buffer_copy_pitch: wgt::BufferSize::new(4).unwrap(),
// #6151: `wgpu_hal::gles` doesn't ask Naga to inject bounds
// checks in GLSL, and it doesn't request extensions like
// `KHR_robust_buffer_access_behavior` that would provide
// them, so we can't really implement the checks promised by
// [`crate::BufferBinding`].
//
// Since this is a pre-existing condition, for the time
// being, provide 1 as the value here, to cause as little
// trouble as possible.
uniform_bounds_check_alignment: wgt::BufferSize::new(1).unwrap(),
},
},
})

View File

@ -143,7 +143,7 @@ impl Drop for DisplayOwner {
match self.display {
DisplayRef::X11(ptr) => unsafe {
let func: libloading::Symbol<XCloseDisplayFun> =
self.library.get(b"XCloseDisplay").unwrap();
self.library.get(b"XCloseDisplay\0").unwrap();
func(ptr.as_ptr());
},
DisplayRef::Wayland => {}
@ -155,7 +155,7 @@ fn open_x_display() -> Option<DisplayOwner> {
log::debug!("Loading X11 library to get the current display");
unsafe {
let library = find_library(&["libX11.so.6", "libX11.so"])?;
let func: libloading::Symbol<XOpenDisplayFun> = library.get(b"XOpenDisplay").unwrap();
let func: libloading::Symbol<XOpenDisplayFun> = library.get(b"XOpenDisplay\0").unwrap();
let result = func(ptr::null());
ptr::NonNull::new(result).map(|ptr| DisplayOwner {
display: DisplayRef::X11(ptr),
@ -182,9 +182,9 @@ fn test_wayland_display() -> Option<DisplayOwner> {
let library = unsafe {
let client_library = find_library(&["libwayland-client.so.0", "libwayland-client.so"])?;
let wl_display_connect: libloading::Symbol<WlDisplayConnectFun> =
client_library.get(b"wl_display_connect").unwrap();
client_library.get(b"wl_display_connect\0").unwrap();
let wl_display_disconnect: libloading::Symbol<WlDisplayDisconnectFun> =
client_library.get(b"wl_display_disconnect").unwrap();
client_library.get(b"wl_display_disconnect\0").unwrap();
let display = ptr::NonNull::new(wl_display_connect(ptr::null()))?;
wl_display_disconnect(display.as_ptr());
find_library(&["libwayland-egl.so.1", "libwayland-egl.so"])?
@ -379,7 +379,7 @@ struct EglContextLock<'a> {
display: khronos_egl::Display,
}
/// A guard containing a lock to an [`AdapterContext`]
/// A guard containing a lock to an [`AdapterContext`], while the GL context is kept current.
pub struct AdapterContextLock<'a> {
glow: MutexGuard<'a, ManuallyDrop<glow::Context>>,
egl: Option<EglContextLock<'a>>,
@ -1082,7 +1082,9 @@ impl crate::Instance for Instance {
unsafe { gl.debug_message_callback(super::gl_debug_message_callback) };
}
// Avoid accidental drop when the context is not current.
// Wrap in ManuallyDrop to make it easier to "current" the GL context before dropping this
// GLOW context, which could also happen if a panic occurs after we uncurrent the context
// below but before AdapterContext is constructed.
let gl = ManuallyDrop::new(gl);
inner.egl.unmake_current();
@ -1294,7 +1296,7 @@ impl crate::Surface for Surface {
(WindowKind::Wayland, Rwh::Wayland(handle)) => {
let library = &self.wsi.display_owner.as_ref().unwrap().library;
let wl_egl_window_create: libloading::Symbol<WlEglWindowCreateFun> =
unsafe { library.get(b"wl_egl_window_create") }.unwrap();
unsafe { library.get(b"wl_egl_window_create\0") }.unwrap();
let window =
unsafe { wl_egl_window_create(handle.surface.as_ptr(), 640, 480) }
.cast();
@ -1403,7 +1405,7 @@ impl crate::Surface for Surface {
if let Some(window) = wl_window {
let library = &self.wsi.display_owner.as_ref().unwrap().library;
let wl_egl_window_resize: libloading::Symbol<WlEglWindowResizeFun> =
unsafe { library.get(b"wl_egl_window_resize") }.unwrap();
unsafe { library.get(b"wl_egl_window_resize\0") }.unwrap();
unsafe {
wl_egl_window_resize(
window,
@ -1475,7 +1477,7 @@ impl crate::Surface for Surface {
.expect("unsupported window")
.library;
let wl_egl_window_destroy: libloading::Symbol<WlEglWindowDestroyFun> =
unsafe { library.get(b"wl_egl_window_destroy") }.unwrap();
unsafe { library.get(b"wl_egl_window_destroy\0") }.unwrap();
unsafe { wl_egl_window_destroy(window) };
}
}

View File

@ -1,15 +1,7 @@
use glow::HasContext;
use glutin_wgl_sys::wgl_extra::{
Wgl, CONTEXT_CORE_PROFILE_BIT_ARB, CONTEXT_DEBUG_BIT_ARB, CONTEXT_FLAGS_ARB,
CONTEXT_PROFILE_MASK_ARB,
};
use once_cell::sync::Lazy;
use parking_lot::{Mutex, MutexGuard, RwLock};
use raw_window_handle::{RawDisplayHandle, RawWindowHandle};
use std::{
collections::HashSet,
ffi::{c_void, CStr, CString},
mem::{self, ManuallyDrop},
mem::{self, size_of, size_of_val, ManuallyDrop},
os::raw::c_int,
ptr,
sync::{
@ -19,6 +11,15 @@ use std::{
thread,
time::Duration,
};
use glow::HasContext;
use glutin_wgl_sys::wgl_extra::{
Wgl, CONTEXT_CORE_PROFILE_BIT_ARB, CONTEXT_DEBUG_BIT_ARB, CONTEXT_FLAGS_ARB,
CONTEXT_PROFILE_MASK_ARB,
};
use once_cell::sync::Lazy;
use parking_lot::{Mutex, MutexGuard, RwLock};
use raw_window_handle::{RawDisplayHandle, RawWindowHandle};
use wgt::InstanceFlags;
use windows::{
core::{Error, PCSTR},
@ -48,7 +49,10 @@ impl AdapterContext {
}
pub fn raw_context(&self) -> *mut c_void {
self.inner.lock().context.context.0
match self.inner.lock().context {
Some(ref wgl) => wgl.context.0,
None => ptr::null_mut(),
}
}
/// Obtain a lock to the WGL context and get handle to the [`glow::Context`] that can be used to
@ -62,7 +66,9 @@ impl AdapterContext {
.try_lock_for(Duration::from_secs(CONTEXT_LOCK_TIMEOUT_SECS))
.expect("Could not lock adapter context. This is most-likely a deadlock.");
inner.context.make_current(inner.device.dc).unwrap();
if let Some(wgl) = &inner.context {
wgl.make_current(inner.device.dc).unwrap()
};
AdapterContextLock { inner }
}
@ -79,14 +85,15 @@ impl AdapterContext {
.try_lock_for(Duration::from_secs(CONTEXT_LOCK_TIMEOUT_SECS))
.expect("Could not lock adapter context. This is most-likely a deadlock.");
inner
.context
.make_current(device)
.map(|()| AdapterContextLock { inner })
if let Some(wgl) = &inner.context {
wgl.make_current(device)?;
}
Ok(AdapterContextLock { inner })
}
}
/// A guard containing a lock to an [`AdapterContext`]
/// A guard containing a lock to an [`AdapterContext`], while the GL context is kept current.
pub struct AdapterContextLock<'a> {
inner: MutexGuard<'a, Inner>,
}
@ -101,7 +108,9 @@ impl<'a> std::ops::Deref for AdapterContextLock<'a> {
impl<'a> Drop for AdapterContextLock<'a> {
fn drop(&mut self) {
self.inner.context.unmake_current().unwrap();
if let Some(wgl) = &self.inner.context {
wgl.unmake_current().unwrap()
}
}
}
@ -136,7 +145,7 @@ unsafe impl Sync for WglContext {}
struct Inner {
gl: ManuallyDrop<glow::Context>,
device: InstanceDevice,
context: WglContext,
context: Option<WglContext>,
}
impl Drop for Inner {
@ -150,8 +159,14 @@ impl Drop for Inner {
// Context must be current when dropped. See safety docs on
// `glow::HasContext`.
self.context.make_current(self.device.dc).unwrap();
let _guard = CurrentGuard(&self.context);
//
// NOTE: This is only set to `None` by `Adapter::new_external` which
// requires the context to be current when anything that may be holding
// the `Arc<AdapterShared>` is dropped.
let _guard = self.context.as_ref().map(|wgl| {
wgl.make_current(self.device.dc).unwrap();
CurrentGuard(wgl)
});
// SAFETY: Field not used after this.
unsafe { ManuallyDrop::drop(&mut self.gl) };
}
@ -196,7 +211,7 @@ unsafe fn setup_pixel_format(dc: Gdi::HDC) -> Result<(), crate::InstanceError> {
{
let format = OpenGL::PIXELFORMATDESCRIPTOR {
nVersion: 1,
nSize: mem::size_of::<OpenGL::PIXELFORMATDESCRIPTOR>() as u16,
nSize: size_of::<OpenGL::PIXELFORMATDESCRIPTOR>() as u16,
dwFlags: OpenGL::PFD_DRAW_TO_WINDOW
| OpenGL::PFD_SUPPORT_OPENGL
| OpenGL::PFD_DOUBLEBUFFER,
@ -232,12 +247,7 @@ unsafe fn setup_pixel_format(dc: Gdi::HDC) -> Result<(), crate::InstanceError> {
}
let mut format = Default::default();
if unsafe {
OpenGL::DescribePixelFormat(
dc,
index,
mem::size_of_val(&format) as u32,
Some(&mut format),
)
OpenGL::DescribePixelFormat(dc, index, size_of_val(&format) as u32, Some(&mut format))
} == 0
{
return Err(crate::InstanceError::with_source(
@ -280,7 +290,7 @@ fn create_global_window_class() -> Result<CString, crate::InstanceError> {
}
let window_class = WindowsAndMessaging::WNDCLASSEXA {
cbSize: mem::size_of::<WindowsAndMessaging::WNDCLASSEXA>() as u32,
cbSize: size_of::<WindowsAndMessaging::WNDCLASSEXA>() as u32,
style: WindowsAndMessaging::CS_OWNDC,
lpfnWndProc: Some(wnd_proc),
cbClsExtra: 0,
@ -515,7 +525,9 @@ impl crate::Instance for Instance {
unsafe { gl.debug_message_callback(super::gl_debug_message_callback) };
}
// Avoid accidental drop when the context is not current.
// Wrap in ManuallyDrop to make it easier to "current" the GL context before dropping this
// GLOW context, which could also happen if a panic occurs after we uncurrent the context
// below but before Inner is constructed.
let gl = ManuallyDrop::new(gl);
context.unmake_current().map_err(|e| {
crate::InstanceError::with_source(
@ -528,7 +540,7 @@ impl crate::Instance for Instance {
inner: Arc::new(Mutex::new(Inner {
device,
gl,
context,
context: Some(context),
})),
srgb_capable,
})
@ -570,6 +582,43 @@ impl crate::Instance for Instance {
}
}
impl super::Adapter {
/// Creates a new external adapter using the specified loader function.
///
/// # Safety
///
/// - The underlying OpenGL ES context must be current.
/// - The underlying OpenGL ES context must be current when interfacing with any objects returned by
/// wgpu-hal from this adapter.
/// - The underlying OpenGL ES context must be current when dropping this adapter and when
/// dropping any objects returned from this adapter.
pub unsafe fn new_external(
fun: impl FnMut(&str) -> *const c_void,
) -> Option<crate::ExposedAdapter<super::Api>> {
let context = unsafe { glow::Context::from_loader_function(fun) };
unsafe {
Self::expose(AdapterContext {
inner: Arc::new(Mutex::new(Inner {
gl: ManuallyDrop::new(context),
device: create_instance_device().ok()?,
context: None,
})),
})
}
}
pub fn adapter_context(&self) -> &AdapterContext {
&self.shared.context
}
}
impl super::Device {
/// Returns the underlying WGL context.
pub fn context(&self) -> &AdapterContext {
&self.shared.context
}
}
struct DeviceContextHandle {
device: Gdi::HDC,
window: Foundation::HWND,

View File

@ -51,8 +51,8 @@
//! must use [`CommandEncoder::transition_buffers`] between those two
//! operations.
//!
//! - Pipeline layouts are *explicitly specified* when setting bind
//! group. Incompatible layouts disturb groups bound at higher indices.
//! - Pipeline layouts are *explicitly specified* when setting bind groups.
//! Incompatible layouts disturb groups bound at higher indices.
//!
//! - The API *accepts collections as iterators*, to avoid forcing the user to
//! store data in particular containers. The implementation doesn't guarantee
@ -327,7 +327,7 @@ impl Drop for DropGuard {
}
#[cfg(any(gles, vulkan))]
impl std::fmt::Debug for DropGuard {
impl fmt::Debug for DropGuard {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("DropGuard").finish()
}
@ -345,6 +345,18 @@ pub enum DeviceError {
Unexpected,
}
#[allow(dead_code)] // may be unused on some platforms
#[cold]
fn hal_usage_error<T: fmt::Display>(txt: T) -> ! {
panic!("wgpu-hal invariant was violated (usage error): {txt}")
}
#[allow(dead_code)] // may be unused on some platforms
#[cold]
fn hal_internal_error<T: fmt::Display>(txt: T) -> ! {
panic!("wgpu-hal ran into a preventable internal error: {txt}")
}
#[derive(Clone, Debug, Eq, PartialEq, Error)]
pub enum ShaderError {
#[error("Compilation failed: {0:?}")]
@ -1230,8 +1242,40 @@ pub trait CommandEncoder: WasmNotSendSync + fmt::Debug {
// pass common
/// Sets the bind group at `index` to `group`, assuming the layout
/// of all the preceding groups to be taken from `layout`.
/// Sets the bind group at `index` to `group`.
///
/// If this is not the first call to `set_bind_group` within the current
/// render or compute pass:
///
/// - If `layout` contains `n` bind group layouts, then any previously set
/// bind groups at indices `n` or higher are cleared.
///
/// - If the first `m` bind group layouts of `layout` are equal to those of
/// the previously passed layout, but no more, then any previously set
/// bind groups at indices `m` or higher are cleared.
///
/// It follows from the above that passing the same layout as before doesn't
/// clear any bind groups.
///
/// # Safety
///
/// - This [`CommandEncoder`] must be within a render or compute pass.
///
/// - `index` must be the valid index of some bind group layout in `layout`.
/// Call this the "relevant bind group layout".
///
/// - The layout of `group` must be equal to the relevant bind group layout.
///
/// - The length of `dynamic_offsets` must match the number of buffer
/// bindings [with dynamic offsets][hdo] in the relevant bind group
/// layout.
///
/// - If those buffer bindings are ordered by increasing [`binding` number]
/// and paired with elements from `dynamic_offsets`, then each offset must
/// be a valid offset for the binding's corresponding buffer in `group`.
///
/// [hdo]: wgt::BindingType::Buffer::has_dynamic_offset
/// [`binding` number]: wgt::BindGroupLayoutEntry::binding
unsafe fn set_bind_group(
&mut self,
layout: &<Self::A as Api>::PipelineLayout,
@ -1283,11 +1327,43 @@ pub trait CommandEncoder: WasmNotSendSync + fmt::Debug {
// render passes
// Begins a render pass, clears all active bindings.
/// Begin a new render pass, clearing all active bindings.
///
/// This clears any bindings established by the following calls:
///
/// - [`set_bind_group`](CommandEncoder::set_bind_group)
/// - [`set_push_constants`](CommandEncoder::set_push_constants)
/// - [`begin_query`](CommandEncoder::begin_query)
/// - [`set_render_pipeline`](CommandEncoder::set_render_pipeline)
/// - [`set_index_buffer`](CommandEncoder::set_index_buffer)
/// - [`set_vertex_buffer`](CommandEncoder::set_vertex_buffer)
///
/// # Safety
///
/// - All prior calls to [`begin_render_pass`] on this [`CommandEncoder`] must have been followed
/// by a call to [`end_render_pass`].
///
/// - All prior calls to [`begin_compute_pass`] on this [`CommandEncoder`] must have been followed
/// by a call to [`end_compute_pass`].
///
/// [`begin_render_pass`]: CommandEncoder::begin_render_pass
/// [`begin_compute_pass`]: CommandEncoder::begin_compute_pass
/// [`end_render_pass`]: CommandEncoder::end_render_pass
/// [`end_compute_pass`]: CommandEncoder::end_compute_pass
unsafe fn begin_render_pass(
&mut self,
desc: &RenderPassDescriptor<<Self::A as Api>::QuerySet, <Self::A as Api>::TextureView>,
);
/// End the current render pass.
///
/// # Safety
///
/// - There must have been a prior call to [`begin_render_pass`] on this [`CommandEncoder`]
/// that has not been followed by a call to [`end_render_pass`].
///
/// [`begin_render_pass`]: CommandEncoder::begin_render_pass
/// [`end_render_pass`]: CommandEncoder::end_render_pass
unsafe fn end_render_pass(&mut self);
unsafe fn set_render_pipeline(&mut self, pipeline: &<Self::A as Api>::RenderPipeline);
@ -1353,11 +1429,41 @@ pub trait CommandEncoder: WasmNotSendSync + fmt::Debug {
// compute passes
// Begins a compute pass, clears all active bindings.
/// Begin a new compute pass, clearing all active bindings.
///
/// This clears any bindings established by the following calls:
///
/// - [`set_bind_group`](CommandEncoder::set_bind_group)
/// - [`set_push_constants`](CommandEncoder::set_push_constants)
/// - [`begin_query`](CommandEncoder::begin_query)
/// - [`set_compute_pipeline`](CommandEncoder::set_compute_pipeline)
///
/// # Safety
///
/// - All prior calls to [`begin_render_pass`] on this [`CommandEncoder`] must have been followed
/// by a call to [`end_render_pass`].
///
/// - All prior calls to [`begin_compute_pass`] on this [`CommandEncoder`] must have been followed
/// by a call to [`end_compute_pass`].
///
/// [`begin_render_pass`]: CommandEncoder::begin_render_pass
/// [`begin_compute_pass`]: CommandEncoder::begin_compute_pass
/// [`end_render_pass`]: CommandEncoder::end_render_pass
/// [`end_compute_pass`]: CommandEncoder::end_compute_pass
unsafe fn begin_compute_pass(
&mut self,
desc: &ComputePassDescriptor<<Self::A as Api>::QuerySet>,
);
/// End the current compute pass.
///
/// # Safety
///
/// - There must have been a prior call to [`begin_compute_pass`] on this [`CommandEncoder`]
/// that has not been followed by a call to [`end_compute_pass`].
///
/// [`begin_compute_pass`]: CommandEncoder::begin_compute_pass
/// [`end_compute_pass`]: CommandEncoder::end_compute_pass
unsafe fn end_compute_pass(&mut self);
unsafe fn set_compute_pipeline(&mut self, pipeline: &<Self::A as Api>::ComputePipeline);
@ -1635,9 +1741,27 @@ pub struct InstanceDescriptor<'a> {
pub struct Alignments {
/// The alignment of the start of the buffer used as a GPU copy source.
pub buffer_copy_offset: wgt::BufferSize,
/// The alignment of the row pitch of the texture data stored in a buffer that is
/// used in a GPU copy operation.
pub buffer_copy_pitch: wgt::BufferSize,
/// The finest alignment of bound range checking for uniform buffers.
///
/// When `wgpu_hal` restricts shader references to the [accessible
/// region][ar] of a [`Uniform`] buffer, the size of the accessible region
/// is the bind group binding's stated [size], rounded up to the next
/// multiple of this value.
///
/// We don't need an analogous field for storage buffer bindings, because
/// all our backends promise to enforce the size at least to a four-byte
/// alignment, and `wgpu_hal` requires bound range lengths to be a multiple
/// of four anyway.
///
/// [ar]: struct.BufferBinding.html#accessible-region
/// [`Uniform`]: wgt::BufferBindingType::Uniform
/// [size]: BufferBinding::size
pub uniform_bounds_check_alignment: wgt::BufferSize,
}
#[derive(Clone, Debug)]
@ -1807,6 +1931,40 @@ pub struct PipelineLayoutDescriptor<'a, B: DynBindGroupLayout + ?Sized> {
pub push_constant_ranges: &'a [wgt::PushConstantRange],
}
/// A region of a buffer made visible to shaders via a [`BindGroup`].
///
/// [`BindGroup`]: Api::BindGroup
///
/// ## Accessible region
///
/// `wgpu_hal` guarantees that shaders compiled with
/// [`ShaderModuleDescriptor::runtime_checks`] set to `true` cannot read or
/// write data via this binding outside the *accessible region* of [`buffer`]:
///
/// - The accessible region starts at [`offset`].
///
/// - For [`Storage`] bindings, the size of the accessible region is [`size`],
/// which must be a multiple of 4.
///
/// - For [`Uniform`] bindings, the size of the accessible region is [`size`]
/// rounded up to the next multiple of
/// [`Alignments::uniform_bounds_check_alignment`].
///
/// Note that this guarantee is stricter than WGSL's requirements for
/// [out-of-bounds accesses][woob], as WGSL allows them to return values from
/// elsewhere in the buffer. But this guarantee is necessary anyway, to permit
/// `wgpu-core` to avoid clearing uninitialized regions of buffers that will
/// never be read by the application before they are overwritten. This
/// optimization consults bind group buffer binding regions to determine which
/// parts of which buffers shaders might observe. This optimization is only
/// sound if shader access is bounds-checked.
///
/// [`buffer`]: BufferBinding::buffer
/// [`offset`]: BufferBinding::offset
/// [`size`]: BufferBinding::size
/// [`Storage`]: wgt::BufferBindingType::Storage
/// [`Uniform`]: wgt::BufferBindingType::Uniform
/// [woob]: https://gpuweb.github.io/gpuweb/wgsl/#out-of-bounds-access-sec
#[derive(Debug)]
pub struct BufferBinding<'a, B: DynBuffer + ?Sized> {
/// The buffer being bound.
@ -1925,6 +2083,26 @@ pub enum ShaderInput<'a> {
pub struct ShaderModuleDescriptor<'a> {
pub label: Label<'a>,
/// Enforce bounds checks in shaders, even if the underlying driver doesn't
/// support doing so natively.
///
/// When this is `true`, `wgpu_hal` promises that shaders can only read or
/// write the [accessible region][ar] of a bindgroup's buffer bindings. If
/// the underlying graphics platform cannot implement these bounds checks
/// itself, `wgpu_hal` will inject bounds checks before presenting the
/// shader to the platform.
///
/// When this is `false`, `wgpu_hal` only enforces such bounds checks if the
/// underlying platform provides a way to do so itself. `wgpu_hal` does not
/// itself add any bounds checks to generated shader code.
///
/// Note that `wgpu_hal` users may try to initialize only those portions of
/// buffers that they anticipate might be read from. Passing `false` here
/// may allow shaders to see wider regions of the buffers than expected,
/// making such deferred initialization visible to the application.
///
/// [ar]: struct.BufferBinding.html#accessible-region
pub runtime_checks: bool,
}

View File

@ -997,6 +997,10 @@ impl super::PrivateCapabilities {
alignments: crate::Alignments {
buffer_copy_offset: wgt::BufferSize::new(self.buffer_alignment).unwrap(),
buffer_copy_pitch: wgt::BufferSize::new(4).unwrap(),
// This backend has Naga incorporate bounds checks into the
// Metal Shading Language it generates, so from `wgpu_hal`'s
// users' point of view, references are tightly checked.
uniform_bounds_check_alignment: wgt::BufferSize::new(1).unwrap(),
},
downlevel,
}

View File

@ -342,9 +342,6 @@ impl PhysicalDeviceFeatures {
None
},
robustness2: if enabled_extensions.contains(&ext::robustness2::NAME) {
// Note: enabling `robust_buffer_access2` isn't requires, strictly speaking
// since we can enable `robust_buffer_access` all the time. But it improves
// program portability, so we opt into it if they are supported.
Some(
vk::PhysicalDeviceRobustness2FeaturesEXT::default()
.robust_buffer_access2(private_caps.robust_buffer_access2)
@ -842,6 +839,10 @@ pub struct PhysicalDeviceProperties {
/// `VK_EXT_subgroup_size_control` extension, promoted to Vulkan 1.3.
subgroup_size_control: Option<vk::PhysicalDeviceSubgroupSizeControlProperties<'static>>,
/// Additional `vk::PhysicalDevice` properties from the
/// `VK_EXT_robustness2` extension.
robustness2: Option<vk::PhysicalDeviceRobustness2PropertiesEXT<'static>>,
/// The device API version.
///
/// Which is the version of Vulkan supported for device-level functionality.
@ -1097,13 +1098,38 @@ impl PhysicalDeviceProperties {
}
}
fn to_hal_alignments(&self) -> crate::Alignments {
/// Return a `wgpu_hal::Alignments` structure describing this adapter.
///
/// The `using_robustness2` argument says how this adapter will implement
/// `wgpu_hal`'s guarantee that shaders can only read the [accessible
/// region][ar] of bindgroup's buffer bindings:
///
/// - If this adapter will depend on `VK_EXT_robustness2`'s
/// `robustBufferAccess2` feature to apply bounds checks to shader buffer
/// access, `using_robustness2` must be `true`.
///
/// - Otherwise, this adapter must use Naga to inject bounds checks on
/// buffer accesses, and `using_robustness2` must be `false`.
///
/// [ar]: ../../struct.BufferBinding.html#accessible-region
fn to_hal_alignments(&self, using_robustness2: bool) -> crate::Alignments {
let limits = &self.properties.limits;
crate::Alignments {
buffer_copy_offset: wgt::BufferSize::new(limits.optimal_buffer_copy_offset_alignment)
.unwrap(),
buffer_copy_pitch: wgt::BufferSize::new(limits.optimal_buffer_copy_row_pitch_alignment)
.unwrap(),
uniform_bounds_check_alignment: {
let alignment = if using_robustness2 {
self.robustness2
.unwrap() // if we're using it, we should have its properties
.robust_uniform_buffer_access_size_alignment
} else {
// If the `robustness2` properties are unavailable, then `robustness2` is not available either Naga-injected bounds checks are precise.
1
};
wgt::BufferSize::new(alignment).unwrap()
},
}
}
}
@ -1133,6 +1159,7 @@ impl super::InstanceShared {
let supports_subgroup_size_control = capabilities.device_api_version
>= vk::API_VERSION_1_3
|| capabilities.supports_extension(ext::subgroup_size_control::NAME);
let supports_robustness2 = capabilities.supports_extension(ext::robustness2::NAME);
let supports_acceleration_structure =
capabilities.supports_extension(khr::acceleration_structure::NAME);
@ -1180,6 +1207,13 @@ impl super::InstanceShared {
properties2 = properties2.push_next(next);
}
if supports_robustness2 {
let next = capabilities
.robustness2
.insert(vk::PhysicalDeviceRobustness2PropertiesEXT::default());
properties2 = properties2.push_next(next);
}
unsafe {
get_device_properties.get_physical_device_properties2(phd, &mut properties2)
};
@ -1191,6 +1225,7 @@ impl super::InstanceShared {
capabilities
.supported_extensions
.retain(|&x| x.extension_name_as_c_str() != Ok(ext::robustness2::NAME));
capabilities.robustness2 = None;
}
};
capabilities
@ -1507,7 +1542,7 @@ impl super::Instance {
};
let capabilities = crate::Capabilities {
limits: phd_capabilities.to_wgpu_limits(),
alignments: phd_capabilities.to_hal_alignments(),
alignments: phd_capabilities.to_hal_alignments(private_caps.robust_buffer_access2),
downlevel: wgt::DownlevelCapabilities {
flags: downlevel_flags,
limits: wgt::DownlevelLimits {},
@ -1779,7 +1814,7 @@ impl super::Adapter {
capabilities: Some(capabilities.iter().cloned().collect()),
bounds_check_policies: naga::proc::BoundsCheckPolicies {
index: naga::proc::BoundsCheckPolicy::Restrict,
buffer: if self.private_caps.robust_buffer_access {
buffer: if self.private_caps.robust_buffer_access2 {
naga::proc::BoundsCheckPolicy::Unchecked
} else {
naga::proc::BoundsCheckPolicy::Restrict
@ -2010,7 +2045,7 @@ impl crate::Adapter for super::Adapter {
vk::Result::ERROR_TOO_MANY_OBJECTS => crate::DeviceError::OutOfMemory,
vk::Result::ERROR_INITIALIZATION_FAILED => crate::DeviceError::Lost,
vk::Result::ERROR_EXTENSION_NOT_PRESENT | vk::Result::ERROR_FEATURE_NOT_PRESENT => {
super::hal_usage_error(err)
crate::hal_usage_error(err)
}
other => super::map_host_device_oom_and_lost_err(other),
}

View File

@ -970,14 +970,14 @@ impl crate::Device for super::Device {
.contains(gpu_alloc::MemoryPropertyFlags::HOST_COHERENT);
Ok(crate::BufferMapping { ptr, is_coherent })
} else {
super::hal_usage_error("tried to map external buffer")
crate::hal_usage_error("tried to map external buffer")
}
}
unsafe fn unmap_buffer(&self, buffer: &super::Buffer) {
if let Some(ref block) = buffer.block {
unsafe { block.lock().unmap(&*self.shared) };
} else {
super::hal_usage_error("tried to unmap external buffer")
crate::hal_usage_error("tried to unmap external buffer")
}
}
@ -2520,7 +2520,7 @@ impl super::DeviceShared {
}
}
None => {
super::hal_usage_error(format!(
crate::hal_usage_error(format!(
"no signals reached value {}",
wait_value
));
@ -2537,7 +2537,7 @@ impl From<gpu_alloc::AllocationError> for crate::DeviceError {
use gpu_alloc::AllocationError as Ae;
match error {
Ae::OutOfDeviceMemory | Ae::OutOfHostMemory | Ae::TooManyObjects => Self::OutOfMemory,
Ae::NoCompatibleMemoryTypes => super::hal_usage_error(error),
Ae::NoCompatibleMemoryTypes => crate::hal_usage_error(error),
}
}
}
@ -2546,7 +2546,7 @@ impl From<gpu_alloc::MapError> for crate::DeviceError {
use gpu_alloc::MapError as Me;
match error {
Me::OutOfDeviceMemory | Me::OutOfHostMemory | Me::MapFailed => Self::OutOfMemory,
Me::NonHostVisible | Me::AlreadyMapped => super::hal_usage_error(error),
Me::NonHostVisible | Me::AlreadyMapped => crate::hal_usage_error(error),
}
}
}

View File

@ -493,9 +493,36 @@ struct PrivateCapabilities {
/// Ability to present contents to any screen. Only needed to work around broken platform configurations.
can_present: bool,
non_coherent_map_mask: wgt::BufferAddress,
/// True if this adapter advertises the [`robustBufferAccess`][vrba] feature.
///
/// Note that Vulkan's `robustBufferAccess` is not sufficient to implement
/// `wgpu_hal`'s guarantee that shaders will not access buffer contents via
/// a given bindgroup binding outside that binding's [accessible
/// region][ar]. Enabling `robustBufferAccess` does ensure that
/// out-of-bounds reads and writes are not undefined behavior (that's good),
/// but still permits out-of-bounds reads to return data from anywhere
/// within the buffer, not just the accessible region.
///
/// [ar]: ../struct.BufferBinding.html#accessible-region
/// [vrba]: https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#features-robustBufferAccess
robust_buffer_access: bool,
robust_image_access: bool,
/// True if this adapter supports the [`VK_EXT_robustness2`] extension's
/// [`robustBufferAccess2`] feature.
///
/// This is sufficient to implement `wgpu_hal`'s [required bounds-checking][ar] of
/// shader accesses to buffer contents. If this feature is not available,
/// this backend must have Naga inject bounds checks in the generated
/// SPIR-V.
///
/// [`VK_EXT_robustness2`]: https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_EXT_robustness2.html
/// [`robustBufferAccess2`]: https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkPhysicalDeviceRobustness2FeaturesEXT.html#features-robustBufferAccess2
/// [ar]: ../struct.BufferBinding.html#accessible-region
robust_buffer_access2: bool,
robust_image_access2: bool,
zero_initialize_workgroup_memory: bool,
image_format_list: bool,
@ -1360,8 +1387,3 @@ fn get_lost_err() -> crate::DeviceError {
#[allow(unreachable_code)]
crate::DeviceError::Lost
}
#[cold]
fn hal_usage_error<T: fmt::Display>(txt: T) -> ! {
panic!("wgpu-hal invariant was violated (usage error): {txt}")
}

View File

@ -1 +1 @@
{"files":{"Cargo.toml":"e23d8d5f33e2bc96270c9c839e3e8038095ce1ffd5cca66129766c2879a7e350","LICENSE.APACHE":"a6cba85bc92e0cff7a450b1d873c0eaa2e9fc96bf472df0247a26bec77bf3ff9","LICENSE.MIT":"c7fea58d1cfe49634cd92e54fc10a9d871f4b275321a4cd8c09e449122caaeb4","src/assertions.rs":"d22aa7ddb95d3ae129ff9848fd45913bab41bd354b6f1c1f6c38a86f9d1abbc6","src/counters.rs":"599e9c033c4f8fcc95113bf730191d80f51b3276b3ee8fd03bbc1c27d24bf76d","src/lib.rs":"f3434598049500d0bc2c1d1e047de091c6d8728d5d0ecae22651f97889f5cce0","src/math.rs":"4d03039736dd6926feb139bc68734cb59df34ede310427bbf059e5c925e0af3b"},"package":null}
{"files":{"Cargo.toml":"388f76262a9fe7ce13791695fc1cea6352ca8b1d367bdf1810c2a1eb80180d13","LICENSE.APACHE":"a6cba85bc92e0cff7a450b1d873c0eaa2e9fc96bf472df0247a26bec77bf3ff9","LICENSE.MIT":"c7fea58d1cfe49634cd92e54fc10a9d871f4b275321a4cd8c09e449122caaeb4","src/assertions.rs":"d22aa7ddb95d3ae129ff9848fd45913bab41bd354b6f1c1f6c38a86f9d1abbc6","src/counters.rs":"599e9c033c4f8fcc95113bf730191d80f51b3276b3ee8fd03bbc1c27d24bf76d","src/lib.rs":"aaaa6cc499533277abdb8c9b1fc45c333f5db2cbe012a9fcbabbe43a4936af34","src/math.rs":"4d03039736dd6926feb139bc68734cb59df34ede310427bbf059e5c925e0af3b"},"package":null}

View File

@ -62,7 +62,7 @@ version = "1"
features = ["derive"]
[dev-dependencies.serde_json]
version = "1.0.125"
version = "1.0.127"
[features]
counters = []

View File

@ -13,6 +13,7 @@ use serde::Deserialize;
#[cfg(any(feature = "serde", test))]
use serde::Serialize;
use std::hash::{Hash, Hasher};
use std::mem::size_of;
use std::path::PathBuf;
use std::{num::NonZeroU32, ops::Range};
@ -7228,7 +7229,7 @@ impl DrawIndirectArgs {
unsafe {
std::mem::transmute(std::slice::from_raw_parts(
std::ptr::from_ref(self).cast::<u8>(),
std::mem::size_of::<Self>(),
size_of::<Self>(),
))
}
}
@ -7259,7 +7260,7 @@ impl DrawIndexedIndirectArgs {
unsafe {
std::mem::transmute(std::slice::from_raw_parts(
std::ptr::from_ref(self).cast::<u8>(),
std::mem::size_of::<Self>(),
size_of::<Self>(),
))
}
}
@ -7284,7 +7285,7 @@ impl DispatchIndirectArgs {
unsafe {
std::mem::transmute(std::slice::from_raw_parts(
std::ptr::from_ref(self).cast::<u8>(),
std::mem::size_of::<Self>(),
size_of::<Self>(),
))
}
}
@ -7309,8 +7310,15 @@ impl ShaderBoundChecks {
/// Creates a new configuration where the shader isn't bound checked.
///
/// # Safety
/// The caller MUST ensure that all shaders built with this configuration don't perform any
/// out of bounds reads or writes.
///
/// The caller MUST ensure that all shaders built with this configuration
/// don't perform any out of bounds reads or writes.
///
/// Note that `wgpu_core`, in particular, initializes only those portions of
/// buffers that it expects might be read, and it does not expect contents
/// outside the ranges bound in bindgroups to be accessible, so using this
/// configuration with ill-behaved shaders could expose uninitialized GPU
/// memory contents to the application.
#[must_use]
pub unsafe fn unchecked() -> Self {
ShaderBoundChecks {
@ -7333,10 +7341,6 @@ impl Default for ShaderBoundChecks {
/// Selects which DX12 shader compiler to use.
///
/// If the `wgpu-hal/dx12-shader-compiler` feature isn't enabled then this will fall back
/// to the Fxc compiler at runtime and log an error.
/// This feature is always enabled when using `wgpu`.
///
/// If the `Dxc` option is selected, but `dxcompiler.dll` and `dxil.dll` files aren't found,
/// then this will fall back to the Fxc compiler at runtime and log an error.
///
@ -7353,6 +7357,10 @@ pub enum Dx12Compiler {
///
/// However, it requires both `dxcompiler.dll` and `dxil.dll` to be shipped with the application.
/// These files can be downloaded from <https://github.com/microsoft/DirectXShaderCompiler/releases>.
///
/// Minimum supported version: [v1.5.2010](https://github.com/microsoft/DirectXShaderCompiler/releases/tag/v1.5.2010)
///
/// It also requires WDDM 2.1 (Windows 10 version 1607).
Dxc {
/// Path to the `dxil.dll` file, or path to the directory containing `dxil.dll` file. Passing `None` will use standard platform specific dll loading rules.
dxil_path: Option<PathBuf>,