Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

example-runner-wgpu: add --force-spirv-passthru and rely on it for debugPrintf. #1036

Merged
merged 7 commits into from
Jul 20, 2023
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]

### Added ⭐
- [PR#1036](https://github.com/EmbarkStudios/rust-gpu/pull/1036) added a `--force-spirv-passthru` flag to `example-runner-wgpu`, to bypass Naga (`wgpu`'s shader translator),
used it to test `debugPrintf` for `wgpu`, and updated `ShaderPanicStrategy::DebugPrintfThenExit` docs to reflect what "enabling `debugPrintf`" looks like for `wgpu`
<sub><sup>(e.g. `VK_LOADER_LAYERS_ENABLE=VK_LAYER_KHRONOS_validation VK_LAYER_ENABLES=VK_VALIDATION_FEATURE_ENABLE_DEBUG_PRINTF_EXT DEBUG_PRINTF_TO_STDOUT=1`)</sup></sub>
- [PR#1080](https://github.com/EmbarkStudios/rust-gpu/pull/1080) added `debugPrintf`-based
panic reporting, with the desired behavior selected via `spirv_builder::ShaderPanicStrategy`
(see its documentation for more details about each available panic handling strategy)
Expand Down
2 changes: 1 addition & 1 deletion crates/rustc_codegen_spirv/src/builder/builder_methods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2648,7 +2648,7 @@ impl<'a, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'tcx> {
// HACK(eddyb) redirect any possible panic call to an abort, to avoid
// needing to materialize `&core::panic::Location` or `format_args!`.
// FIXME(eddyb) find a way to extract the original message.
self.abort_with_message("panic!(...)".into());
self.abort_with_message("panicked: <unknown message>".into());
self.undef(result_type)
} else if let Some(mode) = buffer_load_intrinsic {
self.codegen_buffer_load_intrinsic(result_type, args, mode)
Expand Down
2 changes: 1 addition & 1 deletion crates/rustc_codegen_spirv/src/builder/intrinsics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,7 @@ impl<'a, 'tcx> IntrinsicCallMethods<'tcx> for Builder<'a, 'tcx> {
}

fn abort(&mut self) {
self.abort_with_message("intrinsics::abort()".into());
self.abort_with_message("aborted: intrinsics::abort() called".into());
}

fn assume(&mut self, _val: Self::Value) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -330,12 +330,16 @@ pub fn convert_custom_aborts_to_unstructured_returns_in_entry_points(
let mut fmt = String::new();

// HACK(eddyb) this improves readability w/ very verbose Vulkan loggers.
fmt += "\n ";
fmt += "\n";

fmt += "[RUST-GPU] ";
fmt += &cx[const_str(message)].replace('%', "%%");

// FIXME(eddyb) deduplicate with "called at" form below
// (not trivial becasue both closures would borrow `fmt`).
if let Some((file, line, col)) = current_debug_src_loc.take() {
fmt += &format!("{file}:{line}:{col}: ").replace('%', "%%");
fmt += &format!("\n at {file}:{line}:{col}").replace('%', "%%");
}
fmt += &cx[const_str(message)].replace('%', "%%");

let mut innermost = true;
let mut append_call = |callsite_debug_src_loc, callee: &str| {
Expand All @@ -360,6 +364,8 @@ pub fn convert_custom_aborts_to_unstructured_returns_in_entry_points(
}
append_call(None, &debug_printf_context_fmt_str);

fmt += "\n";

let abort_inst_def = &mut func_def_body.data_insts[abort_inst];
abort_inst_def.kind = DataInstKind::SpvExtInst {
ext_set: cx.intern("NonSemantic.DebugPrintf"),
Expand Down
43 changes: 34 additions & 9 deletions crates/spirv-builder/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -193,15 +193,40 @@ pub enum ShaderPanicStrategy {
/// If you have multiple entry-points, you *may* need to also enable the
/// `multimodule` node (see <https://github.com/KhronosGroup/SPIRV-Tools/issues/4892>).
///
/// **Note**: actually obtaining the `debugPrintf` output requires enabling:
/// * `VK_KHR_shader_non_semantic_info` Vulkan *Device* extension
/// * Vulkan Validation Layers (which contain the `debugPrintf` implementation)
/// * `VK_VALIDATION_FEATURE_ENABLE_DEBUG_PRINTF_EXT` in Validation Layers,
/// either by using `VkValidationFeaturesEXT` during instance creating,
/// setting the `VK_LAYER_ENABLES` environment variable to that value,
/// or adding it to `khronos_validation.enables` in `vk_layer_settings.txt`
///
/// See also: <https://github.com/KhronosGroup/Vulkan-ValidationLayers/blob/main/docs/debug_printf.md>.
/// **Note**: actually obtaining the `debugPrintf` output requires:
/// * Vulkan Validation Layers (from e.g. the Vulkan SDK)
/// * (they contain the `debugPrintf` implementation, a SPIR-V -> SPIR-V translation)
/// * **set the `VK_LOADER_LAYERS_ENABLE=VK_LAYER_KHRONOS_validation`
/// environment variable** to easily enable them without any code changes
/// * alternatively, `"VK_LAYER_KHRONOS_validation"` can be passed during
/// instance creation, to enable them programmatically
/// * Validation Layers' `debugPrintf` support:
/// * **set the `VK_LAYER_ENABLES=VK_VALIDATION_FEATURE_ENABLE_DEBUG_PRINTF_EXT`
/// environment variable** to easily enable the `debugPrintf` support
/// * alternatively, `VkValidationFeaturesEXT` during instance creation,
/// or the `khronos_validation.enables` field in `vk_layer_settings.txt`,
/// can be used to enable `VK_VALIDATION_FEATURE_ENABLE_DEBUG_PRINTF_EXT`
/// (see also <https://github.com/KhronosGroup/Vulkan-ValidationLayers/blob/main/docs/debug_printf.md>)
/// * for outputting the `debugPrintf` messages sent back from the GPU:
/// * **set the `DEBUG_PRINTF_TO_STDOUT=1` environment variable** if you don't
/// plan on customizing the reporting (see below for alternatives)
/// * for `wgpu`:
/// * **required**: `wgpu::Features::SPIRV_SHADER_PASSTHROUGH` (Naga lacks `debugPrintf`)
/// * *optional*: building in debug mode (and/or with debug-assertions enabled),
/// to enable `wgpu` logging/debug support
/// * (the debug assertions requirement may be lifted in future `wgpu` versions)
/// * this uses `VK_EXT_debug_utils` internally, and is a better-integrated
/// alternative to just setting `DEBUG_PRINTF_TO_STDOUT=1`
/// * `RUST_LOG=wgpu_hal::vulkan=info` (or equivalent) will enable said
/// output (as `debugPrintf` messages have the "info" level)
/// * `RUST_LOG` controls `env_logger`, which isn't itself required,
/// but *some* `log`/`tracing` subscriber is needed to get any output
/// * for Vulkan (e.g. via `ash`):
/// * **required**: enabling the `VK_KHR_shader_non_semantic_info` Vulkan *Device* extension
/// * *optional*: as described above, enabling the Validation Layers and
/// their `debugPrintf` support can be done during instance creation
/// * *optional*: integrating [`VK_EXT_debug_utils`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_EXT_debug_utils.html)
/// allows more reporting flexibility than `DEBUG_PRINTF_TO_STDOUT=1`)
DebugPrintfThenExit {
/// Whether to also print the entry-point inputs (excluding buffers/resources),
/// which should uniquely identify the panicking shader invocation.
Expand Down
10 changes: 4 additions & 6 deletions examples/runners/ash/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -211,8 +211,6 @@ pub fn compile_shaders() -> Vec<SpvFile> {

SpirvBuilder::new("examples/shaders/sky-shader", "spirv-unknown-vulkan1.1")
.print_metadata(MetadataPrintout::None)
// HACK(eddyb) having the `ash` runner do this is the easiest way I found
// to test this `panic!` feature with actual `debugPrintf` support.
.shader_panic_strategy(spirv_builder::ShaderPanicStrategy::DebugPrintfThenExit {
print_inputs: true,
print_backtrace: true,
Expand Down Expand Up @@ -380,10 +378,10 @@ impl RenderBase {
};

let device: ash::Device = {
let mut device_extension_names_raw = vec![khr::Swapchain::name().as_ptr()];
if options.debug_layer {
device_extension_names_raw.push(vk::KhrShaderNonSemanticInfoFn::name().as_ptr());
}
let device_extension_names_raw = [
khr::Swapchain::name().as_ptr(),
vk::KhrShaderNonSemanticInfoFn::name().as_ptr(),
];
let features = vk::PhysicalDeviceFeatures {
shader_clip_distance: 1,
..Default::default()
Expand Down
58 changes: 43 additions & 15 deletions examples/runners/wgpu/src/compute.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,17 @@
use wgpu::util::DeviceExt;
use crate::{maybe_watch, CompiledShaderModules, Options};

use super::Options;
use std::{convert::TryInto, time::Duration};
use wgpu::util::DeviceExt;

pub fn start(options: &Options) {
env_logger::init();

let shader_binary = crate::maybe_watch(options.shader, None);
let compiled_shader_modules = maybe_watch(options, None);

futures::executor::block_on(start_internal(options, shader_binary));
futures::executor::block_on(start_internal(options, compiled_shader_modules));
}

pub async fn start_internal(
_options: &Options,
shader_binary: wgpu::ShaderModuleDescriptor<'static>,
) {
async fn start_internal(options: &Options, compiled_shader_modules: CompiledShaderModules) {
let backends = wgpu::util::backend_bits_from_env().unwrap_or(wgpu::Backends::PRIMARY);
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
backends,
Expand All @@ -24,11 +21,17 @@ pub async fn start_internal(
.await
.expect("Failed to find an appropriate adapter");

let mut features =
wgpu::Features::TIMESTAMP_QUERY | wgpu::Features::TIMESTAMP_QUERY_INSIDE_PASSES;
if options.force_spirv_passthru {
features |= wgpu::Features::SPIRV_SHADER_PASSTHROUGH;
}

let (device, queue) = adapter
.request_device(
&wgpu::DeviceDescriptor {
label: None,
features: wgpu::Features::TIMESTAMP_QUERY,
features,
limits: wgpu::Limits::default(),
},
None,
Expand All @@ -40,8 +43,19 @@ pub async fn start_internal(

let timestamp_period = queue.get_timestamp_period();

// Load the shaders from disk
let module = device.create_shader_module(shader_binary);
let entry_point = "main_cs";

// FIXME(eddyb) automate this decision by default.
let module = compiled_shader_modules.spv_module_for_entry_point(entry_point);
let module = if options.force_spirv_passthru {
unsafe { device.create_shader_module_spirv(&module) }
} else {
let wgpu::ShaderModuleDescriptorSpirV { label, source } = module;
device.create_shader_module(wgpu::ShaderModuleDescriptor {
label,
source: wgpu::ShaderSource::SpirV(source),
})
};

let top = 2u32.pow(20);
let src_range = 1..top;
Expand Down Expand Up @@ -75,7 +89,7 @@ pub async fn start_internal(
label: None,
layout: Some(&pipeline_layout),
module: &module,
entry_point: "main_cs",
entry_point,
});

let readback_buffer = device.create_buffer(&wgpu::BufferDescriptor {
Expand All @@ -97,10 +111,17 @@ pub async fn start_internal(
let timestamp_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Timestamps buffer"),
size: 16,
usage: wgpu::BufferUsages::QUERY_RESOLVE | wgpu::BufferUsages::COPY_SRC,
mapped_at_creation: false,
});

let timestamp_readback_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: None,
size: 16,
usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: true,
});
timestamp_buffer.unmap();
timestamp_readback_buffer.unmap();

let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: None,
Expand Down Expand Up @@ -137,10 +158,17 @@ pub async fn start_internal(
src.len() as wgpu::BufferAddress,
);
encoder.resolve_query_set(&queries, 0..2, &timestamp_buffer, 0);
encoder.copy_buffer_to_buffer(
&timestamp_buffer,
0,
&timestamp_readback_buffer,
0,
timestamp_buffer.size(),
);

queue.submit(Some(encoder.finish()));
let buffer_slice = readback_buffer.slice(..);
let timestamp_slice = timestamp_buffer.slice(..);
let timestamp_slice = timestamp_readback_buffer.slice(..);
timestamp_slice.map_async(wgpu::MapMode::Read, |r| r.unwrap());
buffer_slice.map_async(wgpu::MapMode::Read, |r| r.unwrap());
// NOTE(eddyb) `poll` should return only after the above callbacks fire
Expand All @@ -160,7 +188,7 @@ pub async fn start_internal(
drop(data);
readback_buffer.unmap();
drop(timing_data);
timestamp_buffer.unmap();
timestamp_readback_buffer.unmap();
let mut max = 0;
for (src, out) in src_range.zip(result.iter().copied()) {
if out == u32::MAX {
Expand Down
Loading