gpui: Add runtime-shaders feature so that Xcode.app is no longer necessary for Nix-based workflows (#7148)

Piotr Osiewicz and Niklas created

Release Notes:

- N/A

Co-authored-by: Niklas <niklas@niklaskorz.de>

Change summary

crates/gpui/Cargo.toml                         |  9 ++++++
crates/gpui/build.rs                           | 25 ++++++++++++++++++-
crates/gpui/src/platform/mac/metal_renderer.rs | 10 +++++++
3 files changed, 40 insertions(+), 4 deletions(-)

Detailed changes

crates/gpui/Cargo.toml 🔗

@@ -8,7 +8,14 @@ publish = false
 license = "Apache-2.0"
 
 [features]
-test-support = ["backtrace", "dhat", "env_logger", "collections/test-support", "util/test-support"]
+test-support = [
+    "backtrace",
+    "dhat",
+    "env_logger",
+    "collections/test-support",
+    "util/test-support",
+]
+runtime_shaders = []
 
 [lib]
 path = "src/gpui.rs"

crates/gpui/build.rs 🔗

@@ -1,7 +1,6 @@
 use std::{
     env,
     path::{Path, PathBuf},
-    process::{self, Command},
 };
 
 use cbindgen::Config;
@@ -9,6 +8,9 @@ use cbindgen::Config;
 fn main() {
     generate_dispatch_bindings();
     let header_path = generate_shader_bindings();
+    #[cfg(feature = "runtime_shaders")]
+    emit_stitched_shaders(&header_path);
+    #[cfg(not(feature = "runtime_shaders"))]
     compile_metal_shaders(&header_path);
 }
 
@@ -95,11 +97,30 @@ fn generate_shader_bindings() -> PathBuf {
     output_path
 }
 
+/// To enable runtime compilation, we need to "stitch" the shaders file with the generated header
+/// so that it is self-contained.
+#[cfg(feature = "runtime_shaders")]
+fn emit_stitched_shaders(header_path: &Path) {
+    use std::str::FromStr;
+    fn stitch_header(header: &Path, shader_path: &Path) -> std::io::Result<PathBuf> {
+        let header_contents = std::fs::read_to_string(header)?;
+        let shader_contents = std::fs::read_to_string(shader_path)?;
+        let stitched_contents = format!("{header_contents}\n{shader_contents}");
+        let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("stitched_shaders.metal");
+        let _ = std::fs::write(&out_path, stitched_contents)?;
+        Ok(out_path)
+    }
+    let shader_source_path = "./src/platform/mac/shaders.metal";
+    let shader_path = PathBuf::from_str(shader_source_path).unwrap();
+    stitch_header(header_path, &shader_path).unwrap();
+    println!("cargo:rerun-if-changed={}", &shader_source_path);
+}
+#[cfg(not(feature = "runtime_shaders"))]
 fn compile_metal_shaders(header_path: &Path) {
+    use std::process::{self, Command};
     let shader_path = "./src/platform/mac/shaders.metal";
     let air_output_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("shaders.air");
     let metallib_output_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("shaders.metallib");
-
     println!("cargo:rerun-if-changed={}", shader_path);
 
     let output = Command::new("xcrun")

crates/gpui/src/platform/mac/metal_renderer.rs 🔗

@@ -17,7 +17,11 @@ use objc::{self, msg_send, sel, sel_impl};
 use smallvec::SmallVec;
 use std::{ffi::c_void, mem, ptr, sync::Arc};
 
+#[cfg(not(feature = "runtime_shaders"))]
 const SHADERS_METALLIB: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/shaders.metallib"));
+#[cfg(feature = "runtime_shaders")]
+const SHADERS_SOURCE_FILE: &'static str =
+    include_str!(concat!(env!("OUT_DIR"), "/stitched_shaders.metal"));
 const INSTANCE_BUFFER_SIZE: usize = 32 * 1024 * 1024; // This is an arbitrary decision. There's probably a more optimal value (maybe even we could adjust dynamically...)
 
 pub(crate) struct MetalRenderer {
@@ -60,7 +64,11 @@ impl MetalRenderer {
                     | AutoresizingMask::HEIGHT_SIZABLE
             ];
         }
-
+        #[cfg(feature = "runtime_shaders")]
+        let library = device
+            .new_library_with_source(&SHADERS_SOURCE_FILE, &metal::CompileOptions::new())
+            .expect("error building metal library");
+        #[cfg(not(feature = "runtime_shaders"))]
         let library = device
             .new_library_with_data(SHADERS_METALLIB)
             .expect("error building metal library");