[shaders] Don't apply force_rw_storage to fine

The force_rw_storage feature is a hack that enables a renderer to bind
suballocations from the same buffer object to both readonly and
read-write bindings inside a pipeline layout.

The AA variants of the fine stage don't compile with this feature
enabled, since the read-write PTCL buffer results in a uniformity
analysis failure.

Address this by excluding the `fine` stage from the force_rw_storage
feature to preserve the shader's correctness. Since all storage bindings
in `fine` are readonly, there is no restriction against binding
suballocations to any of the fine pipeline variants in the first place.
diff --git a/crates/shaders/Cargo.toml b/crates/shaders/Cargo.toml
index e6f8c73..5cf538e 100644
--- a/crates/shaders/Cargo.toml
+++ b/crates/shaders/Cargo.toml
@@ -13,7 +13,11 @@
 # to have the `read_write` access mode. For WGSL shaders, this affects the bind group
 # layout of all pipelines and changes the usage scope of storage buffers. For MSL shaders,
 # this removes the `const` qualifier from entry-point parameters in the `device` address
-# space.
+# space. This allows bindings with mixed access modes to be backed by suballocations from
+# the same the buffer object.
+#
+# This feature doesn't apply to the fine stage where all storage bindings are readonly and
+# the same access mode restrictions do not apply.
 #
 # Enabling this feature may have a performance impact and is not recommended.
 force_rw_storage = []
diff --git a/crates/shaders/src/compile/mod.rs b/crates/shaders/src/compile/mod.rs
index fca4a50..a756884 100644
--- a/crates/shaders/src/compile/mod.rs
+++ b/crates/shaders/src/compile/mod.rs
@@ -162,18 +162,25 @@
             if let Some(name) = file_name.to_str() {
                 let suffix = ".wgsl";
                 if let Some(shader_name) = name.strip_suffix(suffix) {
+                    let mut options = preprocess::Options::default();
+                    if cfg!(feature = "force_rw_storage") {
+                        // All bindings in the `fine` stage are readonly which means that a usage
+                        // scope warning can never occur. Plus, the bindings must be declared as
+                        // readonly to prevent a failed compilation due to uniformity analysis.
+                        options.force_rw_storage = !name.starts_with("fine");
+                    }
                     let contents = fs::read_to_string(shader_dir.join(&file_name))
                         .expect("Could read shader {shader_name} contents");
                     if let Some(permutations) = permutation_map.get(shader_name) {
                         for permutation in permutations {
                             let mut defines = defines.clone();
                             defines.extend(permutation.defines.iter().cloned());
-                            let source = preprocess::preprocess(&contents, &defines, &imports);
+                            let source = preprocess::preprocess(&contents, &options, &defines, &imports);
                             let shader_info = Self::new(source.clone(), "main").unwrap();
                             info.insert(permutation.name.clone(), shader_info);
                         }
                     } else {
-                        let source = preprocess::preprocess(&contents, &defines, &imports);
+                        let source = preprocess::preprocess(&contents, &options, &defines, &imports);
                         let shader_info = Self::new(source.clone(), "main").unwrap();
                         info.insert(shader_name.to_string(), shader_info);
                     }
diff --git a/crates/shaders/src/compile/preprocess.rs b/crates/shaders/src/compile/preprocess.rs
index b3c6003..4ff6664 100644
--- a/crates/shaders/src/compile/preprocess.rs
+++ b/crates/shaders/src/compile/preprocess.rs
@@ -37,8 +37,14 @@
     else_passed: bool,
 }
 
+#[derive(Default)]
+pub struct Options {
+    pub force_rw_storage: bool,
+}
+
 pub fn preprocess(
     input: &str,
+    options: &Options,
     defines: &HashSet<String>,
     imports: &HashMap<String, String>,
 ) -> String {
@@ -147,7 +153,7 @@
                         // However, in practise there will only ever be at most 2 stack items, so
                         // it's reasonable to just recompute it every time
                         if stack.iter().all(|item| item.active) {
-                            output.push_str(&preprocess(import, defines, imports));
+                            output.push_str(&preprocess(import, options, defines, imports));
                         }
                     } else {
                         eprintln!("Unknown import `{import_name}` (line {line_number})");
@@ -167,7 +173,7 @@
                 output.push_str("const");
                 output.push_str(&line[3..]);
             } else if let Some(idx) = line.find("var<storage>") {
-                if cfg!(feature = "force_rw_storage") {
+                if options.force_rw_storage {
                     let mut line = line.to_string();
                     line.replace_range(idx..(idx + 12), "var<storage, read_write>");
                     output.push_str(&line);