example-runner-wgpu: enable debugPrintf panic support via --force-spirv-passthru.

This commit is contained in:
Eduard-Mihai Burtescu 2023-07-20 07:43:29 +03:00 committed by Eduard-Mihai Burtescu
parent 9808cd0309
commit 24f42f2b42
4 changed files with 125 additions and 53 deletions

View File

@ -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,
@ -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()

View File

@ -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::ShaderModuleDescriptorSpirV<'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,
@ -46,11 +43,14 @@ pub async fn start_internal(
let timestamp_period = queue.get_timestamp_period();
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(&shader_binary) }
unsafe { device.create_shader_module_spirv(&module) }
} else {
let wgpu::ShaderModuleDescriptorSpirV { label, source } = shader_binary;
let wgpu::ShaderModuleDescriptorSpirV { label, source } = module;
device.create_shader_module(wgpu::ShaderModuleDescriptor {
label,
source: wgpu::ShaderSource::SpirV(source),
@ -89,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 {

View File

@ -1,6 +1,5 @@
use crate::maybe_watch;
use crate::{maybe_watch, CompiledShaderModules, Options};
use super::Options;
use shared::ShaderConstants;
use winit::{
event::{ElementState, Event, KeyboardInput, MouseButton, VirtualKeyCode, WindowEvent},
@ -34,9 +33,9 @@ fn mouse_button_index(button: MouseButton) -> usize {
async fn run(
options: Options,
event_loop: EventLoop<wgpu::ShaderModuleDescriptorSpirV<'static>>,
event_loop: EventLoop<CompiledShaderModules>,
window: Window,
shader_binary: wgpu::ShaderModuleDescriptorSpirV<'static>,
compiled_shader_modules: CompiledShaderModules,
) {
let backends = wgpu::util::backend_bits_from_env()
.unwrap_or(wgpu::Backends::VULKAN | wgpu::Backends::METAL);
@ -135,7 +134,7 @@ async fn run(
|pending| pending.preferred_format,
|(_, surface_config)| surface_config.format,
),
shader_binary,
compiled_shader_modules,
);
let start = std::time::Instant::now();
@ -336,24 +335,45 @@ fn create_pipeline(
device: &wgpu::Device,
pipeline_layout: &wgpu::PipelineLayout,
surface_format: wgpu::TextureFormat,
shader_binary: wgpu::ShaderModuleDescriptorSpirV<'_>,
compiled_shader_modules: CompiledShaderModules,
) -> wgpu::RenderPipeline {
// FIXME(eddyb) automate this decision by default.
let module = if options.force_spirv_passthru {
unsafe { device.create_shader_module_spirv(&shader_binary) }
} else {
let wgpu::ShaderModuleDescriptorSpirV { label, source } = shader_binary;
device.create_shader_module(wgpu::ShaderModuleDescriptor {
label,
source: wgpu::ShaderSource::SpirV(source),
})
let create_module = |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 vs_entry_point = shaders::main_vs;
let fs_entry_point = shaders::main_fs;
let vs_module_descr = compiled_shader_modules.spv_module_for_entry_point(vs_entry_point);
let fs_module_descr = compiled_shader_modules.spv_module_for_entry_point(fs_entry_point);
// HACK(eddyb) avoid calling `device.create_shader_module` twice unnecessarily.
let vs_fs_same_module = std::ptr::eq(&vs_module_descr.source[..], &fs_module_descr.source[..]);
let vs_module = &create_module(vs_module_descr);
let fs_module;
let fs_module = if vs_fs_same_module {
vs_module
} else {
fs_module = create_module(fs_module_descr);
&fs_module
};
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: None,
layout: Some(pipeline_layout),
vertex: wgpu::VertexState {
module: &module,
entry_point: shaders::main_vs,
module: vs_module,
entry_point: vs_entry_point,
buffers: &[],
},
primitive: wgpu::PrimitiveState {
@ -372,8 +392,8 @@ fn create_pipeline(
alpha_to_coverage_enabled: false,
},
fragment: Some(wgpu::FragmentState {
module: &module,
entry_point: shaders::main_fs,
module: fs_module,
entry_point: fs_entry_point,
targets: &[Some(wgpu::ColorTargetState {
format: surface_format,
blend: None,
@ -410,7 +430,7 @@ pub fn start(
// Build the shader before we pop open a window, since it might take a while.
let initial_shader = maybe_watch(
options.shader,
options,
#[cfg(not(any(target_os = "android", target_arch = "wasm32")))]
{
let proxy = event_loop.create_proxy();

View File

@ -70,6 +70,7 @@
// crate-specific exceptions:
// #![allow()]
use std::borrow::Cow;
use structopt::StructOpt;
use strum::{Display, EnumString};
@ -87,16 +88,45 @@ pub enum RustGPUShader {
Mouse,
}
struct CompiledShaderModules {
named_spv_modules: Vec<(Option<String>, wgpu::ShaderModuleDescriptorSpirV<'static>)>,
}
impl CompiledShaderModules {
fn spv_module_for_entry_point<'a>(
&'a self,
wanted_entry: &str,
) -> wgpu::ShaderModuleDescriptorSpirV<'a> {
for (name, spv_module) in &self.named_spv_modules {
match name {
Some(name) if name != wanted_entry => continue,
_ => {
return wgpu::ShaderModuleDescriptorSpirV {
label: name.as_deref(),
source: Cow::Borrowed(&spv_module.source),
};
}
}
}
unreachable!(
"{wanted_entry:?} not found in modules {:?}",
self.named_spv_modules
.iter()
.map(|(name, _)| name)
.collect::<Vec<_>>()
);
}
}
fn maybe_watch(
shader: RustGPUShader,
options: &Options,
#[cfg(not(any(target_os = "android", target_arch = "wasm32")))] on_watch: Option<
Box<dyn FnMut(wgpu::ShaderModuleDescriptorSpirV<'static>) + Send + 'static>,
Box<dyn FnMut(CompiledShaderModules) + Send + 'static>,
>,
) -> wgpu::ShaderModuleDescriptorSpirV<'static> {
) -> CompiledShaderModules {
#[cfg(not(any(target_os = "android", target_arch = "wasm32")))]
{
use spirv_builder::{CompileResult, MetadataPrintout, SpirvBuilder};
use std::borrow::Cow;
use std::path::PathBuf;
// Hack: spirv_builder builds into a custom directory if running under cargo, to not
// deadlock, and the default target directory if not. However, packages like `proc-macro2`
@ -106,7 +136,7 @@ fn maybe_watch(
// under cargo by setting these environment variables.
std::env::set_var("OUT_DIR", env!("OUT_DIR"));
std::env::set_var("PROFILE", env!("PROFILE"));
let crate_name = match shader {
let crate_name = match options.shader {
RustGPUShader::Simplest => "simplest-shader",
RustGPUShader::Sky => "sky-shader",
RustGPUShader::Compute => "compute-shader",
@ -117,8 +147,22 @@ fn maybe_watch(
.iter()
.copied()
.collect::<PathBuf>();
let has_debug_printf = options.force_spirv_passthru;
let builder = SpirvBuilder::new(crate_path, "spirv-unknown-vulkan1.1")
.print_metadata(MetadataPrintout::None);
.print_metadata(MetadataPrintout::None)
.shader_panic_strategy(if has_debug_printf {
spirv_builder::ShaderPanicStrategy::DebugPrintfThenExit {
print_inputs: true,
print_backtrace: true,
}
} else {
spirv_builder::ShaderPanicStrategy::SilentExit
})
// HACK(eddyb) needed because of `debugPrintf` instrumentation limitations
// (see https://github.com/KhronosGroup/SPIRV-Tools/issues/4892).
.multimodule(has_debug_printf);
let initial_result = if let Some(mut f) = on_watch {
builder
.watch(move |compile_result| f(handle_compile_result(compile_result)))
@ -126,17 +170,27 @@ fn maybe_watch(
} else {
builder.build().unwrap()
};
fn handle_compile_result(
compile_result: CompileResult,
) -> wgpu::ShaderModuleDescriptorSpirV<'static> {
let module_path = compile_result.module.unwrap_single();
let data = std::fs::read(module_path).unwrap();
// FIXME(eddyb) this reallocates all the data pointlessly, there is
// not a good reason to use `ShaderModuleDescriptorSpirV` specifically.
let spirv = Cow::Owned(wgpu::util::make_spirv_raw(&data).into_owned());
wgpu::ShaderModuleDescriptorSpirV {
label: None,
source: spirv,
fn handle_compile_result(compile_result: CompileResult) -> CompiledShaderModules {
let load_spv_module = |path| {
let data = std::fs::read(path).unwrap();
// FIXME(eddyb) this reallocates all the data pointlessly, there is
// not a good reason to use `ShaderModuleDescriptorSpirV` specifically.
let spirv = Cow::Owned(wgpu::util::make_spirv_raw(&data).into_owned());
wgpu::ShaderModuleDescriptorSpirV {
label: None,
source: spirv,
}
};
CompiledShaderModules {
named_spv_modules: match compile_result.module {
spirv_builder::ModuleResult::SingleModule(path) => {
vec![(None, load_spv_module(path))]
}
spirv_builder::ModuleResult::MultiModule(modules) => modules
.into_iter()
.map(|(name, path)| (Some(name), load_spv_module(path)))
.collect(),
},
}
}
handle_compile_result(initial_result)