diff --git a/examples/runners/ash/src/main.rs b/examples/runners/ash/src/main.rs index 8e65bd6a26..9ff4430b60 100644 --- a/examples/runners/ash/src/main.rs +++ b/examples/runners/ash/src/main.rs @@ -211,8 +211,6 @@ pub fn compile_shaders() -> Vec { 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() diff --git a/examples/runners/wgpu/src/compute.rs b/examples/runners/wgpu/src/compute.rs index addb988ed5..8e706e0bc5 100644 --- a/examples/runners/wgpu/src/compute.rs +++ b/examples/runners/wgpu/src/compute.rs @@ -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 { diff --git a/examples/runners/wgpu/src/graphics.rs b/examples/runners/wgpu/src/graphics.rs index 3d5a5812d3..5d1cb202e5 100644 --- a/examples/runners/wgpu/src/graphics.rs +++ b/examples/runners/wgpu/src/graphics.rs @@ -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>, + event_loop: EventLoop, 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(); diff --git a/examples/runners/wgpu/src/lib.rs b/examples/runners/wgpu/src/lib.rs index c69cee2e19..85720b7561 100644 --- a/examples/runners/wgpu/src/lib.rs +++ b/examples/runners/wgpu/src/lib.rs @@ -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, 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::>() + ); + } +} + fn maybe_watch( - shader: RustGPUShader, + options: &Options, #[cfg(not(any(target_os = "android", target_arch = "wasm32")))] on_watch: Option< - Box) + Send + 'static>, + Box, >, -) -> 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::(); + + 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)