Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ Bottom level categories:
- `TextureFormat::is_srgb()` has been renamed to `TextureFormat::has_srgb_suffix()` to clarify its function. By @kpreid in [#9758](https://github.com/gfx-rs/wgpu/pull/9758).
- Remove the never-constructed `CreateBlasError::InvalidAabbStride` variant. `create_blas` takes no stride, so it could never be produced; AABB stride is validated at build time as `BuildAccelerationStructureError::InvalidAabbStride`. By @mstampfli in [#9935](https://github.com/gfx-rs/wgpu/pull/9935).
- Use `Arc`'d resources instead of IDs as the default resource type in `PipelineLayoutDescriptor`. By @sagudev in [#9985](https://github.com/gfx-rs/wgpu/pull/9985)
- `wgpu::Error::Validation::description` from `Device::create_shader_module` no longer include the shader source text and detailed compiler messages, per the WebGPU specification. These details remain accessible via `ShaderModule::get_compilation_info` or `Debug` formatting. By @beicause in [#10033](https://github.com/gfx-rs/wgpu/pull/10033).

#### naga

Expand Down
81 changes: 66 additions & 15 deletions cts_runner/tests/integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,27 +58,43 @@ fn exec_js_file(script_file: impl AsRef<OsStr>) -> Result<(), Error> {
Ok(())
}

fn check_js_stderr(script: &str, expected: &str) -> Result<(), Error> {
fn check_js_stderr(script: &str, expected_stderr: &str) -> Result<(), Error> {
check_js_stdout(script, "", expected_stderr)
}

fn check_js_stdout(
script: &str,
expected_stdout: &str,
expected_stderr: &str,
) -> Result<(), Error> {
let mut tempfile = NamedTempFile::new().unwrap();
tempfile.write_all(script.as_bytes()).unwrap();
tempfile.flush().unwrap();
let output = exec_cts_runner(tempfile.path());
if !output.stdout.is_empty() {

let stdout_str = str::from_utf8(&output.stdout).unwrap();
if expected_stdout.is_empty() && !output.stdout.is_empty() {
return Err(Error(format!(
"unexpected output on stdout: {:?}",
stdout_str,
)));
} else if stdout_str != expected_stdout {
return Err(Error(format!(
"unexpected output on stdout: {}",
str::from_utf8(&output.stdout).unwrap(),
"expected the following output on stdout:\n{:?}\n\nbut observed:\n{:?}",
expected_stdout, stdout_str,
)));
}

let stderr_str = str::from_utf8(&output.stderr).unwrap();
if expected.is_empty() && !stderr_str.is_empty() {
if expected_stderr.is_empty() && !stderr_str.is_empty() {
return Err(Error(format!(
"unexpected output on stderr: {}",
"unexpected output on stderr: {:?}",
stderr_str,
)));
} else if stderr_str != expected {
} else if stderr_str != expected_stderr {
return Err(Error(format!(
"expected the following output on stderr:\n{}\n\nbut observed:\n{}",
expected, stderr_str,
"expected the following output on stderr:\n{:?}\n\nbut observed:\n{:?}",
expected_stderr, stderr_str,
)));
}
if !output.status.success() {
Expand Down Expand Up @@ -143,13 +159,48 @@ fn uncaptured_error() -> Result<(), Error> {
const device = await adapter.requestDevice();
device.createShaderModule({ code })
"#,
"cts_runner caught WebGPU error:\x20
Shader '' parsing error: the type of `val` is expected to be `u32`, but got `{AbstractFloat}`
┌─ wgsl:1:7
1 │ const val: u32 = 1.1;
│ ^^^ definition of `val`\n\n\n",
"cts_runner caught WebGPU error: Shader '' parsing error\n",
)
}

#[test]
fn shader_compilation_message() {
check_js_stdout(
r#"
const code = `const val: u32 = 1.1;`;

const adapter = await navigator.gpu.requestAdapter();
const device = await adapter.requestDevice();
const shaderModule = device.createShaderModule({ code });
console.log(await shaderModule.getCompilationInfo());

// Keep the event loop alive so the `uncapturederror` event is
// dispatched before the script exits.
await new Promise((r) => setTimeout(r, 100));
"#,
concat!(
"GPUCompilationInfo {\n",
" messages: [\n",
" GPUCompilationMessage {\n",
" message: \x1b[32m\"\\n\"\x1b[39m +\n",
" \x1b[32m\"Shader '' parsing error: the type of `val` is expected to be `u32`, but got `{AbstractFloat}`\\n\"\x1b[39m +\n",
" \x1b[32m\" ┌─ wgsl:1:7\\n\"\x1b[39m +\n",
" \x1b[32m\" │\\n\"\x1b[39m +\n",
" \x1b[32m\"1 │ const val: u32 = 1.1;\\n\"\x1b[39m +\n",
" \x1b[32m\" │ ^^^ definition of `val`\\n\"\x1b[39m +\n",
" \x1b[32m\"\\n\"\x1b[39m,\n",
" type: \x1b[32m\"error\"\x1b[39m,\n",
" lineNum: \x1b[33m1\x1b[39m,\n",
" linePos: \x1b[33m7\x1b[39m,\n",
" offset: \x1b[33m6\x1b[39m,\n",
" length: \x1b[33m3\x1b[39m\n",
" }\n",
" ]\n",
"}\n",
),
"cts_runner caught WebGPU error: Shader '' parsing error\n",
)
.unwrap();
}

#[test]
Expand Down
12 changes: 6 additions & 6 deletions deno_webgpu/01_webgpu.js
Original file line number Diff line number Diff line change
Expand Up @@ -598,7 +598,8 @@ ObjectDefineProperty(GPUShaderModulePrototype, privateCustomInspect, {
},
});

ObjectDefineProperty(GPUCompilationInfo, privateCustomInspect, {
const GPUCompilationInfoPrototype = GPUCompilationInfo.prototype;
ObjectDefineProperty(GPUCompilationInfoPrototype, privateCustomInspect, {
__proto__: null,
value(inspect, inspectOptions) {
return inspect(
Expand All @@ -616,9 +617,9 @@ ObjectDefineProperty(GPUCompilationInfo, privateCustomInspect, {
);
},
});
const GPUCompilationInfoPrototype = GPUCompilationInfo.prototype;

ObjectDefineProperty(GPUCompilationMessage, privateCustomInspect, {
const GPUCompilationMessagePrototype = GPUCompilationMessage.prototype;
ObjectDefineProperty(GPUCompilationMessagePrototype, privateCustomInspect, {
__proto__: null,
value(inspect, inspectOptions) {
return inspect(
Expand All @@ -631,8 +632,8 @@ ObjectDefineProperty(GPUCompilationMessage, privateCustomInspect, {
keys: [
"message",
"type",
"line_num",
"line_pos",
"lineNum",
"linePos",
"offset",
"length",
],
Expand All @@ -641,7 +642,6 @@ ObjectDefineProperty(GPUCompilationMessage, privateCustomInspect, {
);
},
});
const GPUCompilationMessagePrototype = GPUCompilationMessage.prototype;

class GPUShaderStage {
constructor() {
Expand Down
11 changes: 11 additions & 0 deletions deno_webgpu/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,17 @@ pub(crate) fn fmt_err(err: &(dyn std::error::Error + 'static)) -> String {

let mut e = err.source();
while let Some(source) = e {
// Don't print detailed compiler messages when formatting error's description.
//
// per the WebGPU specification <https://gpuweb.github.io/gpuweb/#dom-gpudevice-createshadermodule>,
// the message of the validation error raised by `createShaderModule` should not include those details,
// since they are accessible via `getCompilationInfo()`.
match err.downcast_ref::<CreateShaderModuleError>() {
Some(CreateShaderModuleError::Parsing(_)) => break,
Some(CreateShaderModuleError::Validation(_)) => break,
_ => {}
};

output.push_str(&format!(": {source}"));
e = source.source();
}
Expand Down
12 changes: 6 additions & 6 deletions deno_webgpu/shader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,14 +124,14 @@ impl GPUCompilationMessage {

impl GPUCompilationMessage {
fn new(error: &pipeline::CreateShaderModuleError, source: &str) -> Self {
let message = error.to_string();

let loc = match error {
pipeline::CreateShaderModuleError::Parsing(e) => e.inner.location(source),
let (loc, message) = match error {
pipeline::CreateShaderModuleError::Parsing(e) => {
(e.inner.location(source), e.to_string())
}
pipeline::CreateShaderModuleError::Validation(e) => {
e.inner.location(source)
(e.inner.location(source), e.to_string())
}
_ => None,
_ => (None, error.to_string()),
};

match loc {
Expand Down
21 changes: 19 additions & 2 deletions tests/tests/wgpu-gpu/dual_source_blending.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,12 +107,29 @@ async fn dual_source_blending_disabled(ctx: TestingContext) {
fail(
&ctx.device,
|| {
let _ = ctx.device.create_shader_module(ShaderModuleDescriptor {
let module = ctx.device.create_shader_module(ShaderModuleDescriptor {
label: Some("shader"),
source: ShaderSource::Wgsl(FRAGMENT_SHADER_WITH_DUAL_SOURCE_BLENDING.into()),
});
let info = pollster::block_on(module.get_compilation_info());
assert_eq!(
info.messages[0].message_type,
wgpu::CompilationMessageType::Error
);
assert_eq!(
info.messages[0].location,
Some(SourceLocation {
line_number: 2,
line_position: 8,
offset: 8,
length: 20
})
);
assert!(info.messages[0].message.contains(
"the `dual_source_blending` extension is not supported in the current environment"
))
},
Some("the `dual_source_blending` extension is not supported in the current environment"),
Some("Shader 'shader' parsing error"),
);
}

Expand Down
47 changes: 44 additions & 3 deletions tests/tests/wgpu-gpu/shader/compilation_messages/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,37 @@ static SHADER_COMPILE_ERROR: GpuTestConfiguration = GpuTestConfiguration::new()
let sm = ctx
.device
.create_shader_module(include_wgsl!("error_shader.wgsl"));
assert!(pollster::block_on(scope.pop()).is_some());

let Some(wgpu::Error::Validation {
source,
description,
}) = scope.pop().await
else {
panic!("Expected validation error not found")
};
// Description of validation error should not include detailed shader compilation message.
assert_eq!(
description,
"Validation Error\n\nCaused by:\n \
In Device::create_shader_module, label = 'error_shader.wgsl'\n \
Shader 'error_shader.wgsl' parsing error\n"
);
let msg = source
.source()
.unwrap()
.source()
.unwrap()
.downcast_ref::<naga::error::ShaderError<naga::front::wgsl::ParseError>>()
.unwrap()
.to_string();

let compilation_info = sm.get_compilation_info().await;
let error_message = compilation_info
.messages
.iter()
.find(|message| message.message_type == wgpu::CompilationMessageType::Error)
.expect("Expected error message not found");
assert_eq!(error_message.message, msg);
let span = error_message.location.expect("Expected span not found");
assert_eq!(
span.offset, 32,
Expand Down Expand Up @@ -99,14 +122,32 @@ static ENABLE_EXTENSION_UNAVAILABLE: GpuTestConfiguration = GpuTestConfiguration
fail(
&ctx.device,
|| {
ctx.device
let module = ctx
.device
.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("shader declaring enable extension"),
source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(
ENABLE_EXTENSION_SHADER_SOURCE,
)),
});
let info = pollster::block_on(module.get_compilation_info());
assert_eq!(
info.messages[0].message_type,
wgpu::CompilationMessageType::Error
);
assert_eq!(
info.messages[0].location,
Some(wgpu::SourceLocation {
line_number: 2,
line_position: 12,
offset: 12,
length: 3
})
);
assert!(info.messages[0]
.message
.contains("the `f16` extension is not supported in the current environment"))
},
Some("the `f16` extension is not supported in the current environment"),
Some("Shader 'shader declaring enable extension' parsing error"),
);
});
50 changes: 46 additions & 4 deletions wgpu-core/src/pipeline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,19 +182,19 @@ impl ShaderModule {
#[non_exhaustive]
pub enum CreateShaderModuleError {
#[cfg(feature = "wgsl")]
#[error(transparent)]
#[error("Shader '{label}' parsing error", label = _0.label.as_deref().unwrap_or_default())]
Parsing(#[from] ShaderError<naga::front::wgsl::ParseError>),
#[cfg(feature = "glsl")]
#[error(transparent)]
#[error("Shader '{label}' parsing error", label = _0.label.as_deref().unwrap_or_default())]
ParsingGlsl(#[from] ShaderError<naga::front::glsl::ParseErrors>),
#[cfg(feature = "spirv")]
#[error(transparent)]
#[error("Shader '{label}' parsing error", label = _0.label.as_deref().unwrap_or_default())]
ParsingSpirV(#[from] ShaderError<naga::front::spv::Error>),
#[error("Failed to generate the backend-specific code")]
Generation,
#[error(transparent)]
Device(#[from] DeviceError),
#[error(transparent)]
#[error("Shader '{label}' validation error", label = _0.label.as_deref().unwrap_or_default())]
Validation(#[from] ShaderError<naga::WithSpan<naga::valid::ValidationError>>),
#[error(transparent)]
MissingFeatures(#[from] MissingFeatures),
Expand Down Expand Up @@ -1165,3 +1165,45 @@ impl RenderPipeline {
(bgl, error)
}
}

#[cfg(feature = "wgsl")]
#[cfg(test)]
mod tests {
use alloc::{boxed::Box, string::ToString};
use naga::error::ShaderError;

use super::CreateShaderModuleError;

// Test that validation error `Display` doesn't include
// the shader source text and detailed compiler messages,
// per the WebGPU specification <https://gpuweb.github.io/gpuweb/#dom-gpudevice-createshadermodule>,
#[test]
fn create_shader_module_error_message() {
let source: &str = "not valid wgsl";
let error = CreateShaderModuleError::Parsing(ShaderError {
source: source.to_string(),
label: Some("my shader".to_string()),
inner: Box::new(
naga::front::wgsl::Frontend::new()
.parse(source)
.unwrap_err(),
),
});

assert_eq!(error.to_string(), "Shader 'my shader' parsing error");

let source: &str = "fn main() -> f32 { let arr = array(1.0); return arr[-1]; }";
let module = naga::front::wgsl::parse_str(source).unwrap();
let mut validator = naga::valid::Validator::new(
naga::valid::ValidationFlags::all(),
naga::valid::Capabilities::empty(),
);
let error = CreateShaderModuleError::Validation(ShaderError {
source: source.to_string(),
label: Some("my shader".to_string()),
inner: validator.validate(&module).unwrap_err(),
});

assert_eq!(error.to_string(), "Shader 'my shader' validation error");
}
}
Loading