|
1 | | -# moon_wgsl |
2 | | - |
3 | | -`Milky2018/moon_wgsl` is a MoonBit library for composing WGSL shader modules |
4 | | -with `naga_oil`-style preprocessing and imports. |
5 | | - |
6 | | -Use it when your shaders contain directives such as `#define_import_path`, |
7 | | -`#ifdef`, `#define`, and `#import`, and you want to resolve them from MoonBit |
8 | | -without adding a separate shader build step. |
9 | | - |
10 | | -## Install |
11 | | - |
12 | | -Add the package from Mooncakes, then import the subpackages you need: |
13 | | - |
14 | | -```mbt check |
15 | | -///| |
16 | | -test "README: package is available" { |
17 | | - let value_defines = @common.default_wgsl_value_defines() |
18 | | - debug_inspect(value_defines.length() > 0, content="true") |
19 | | -} |
| 1 | +# moon_wgsl workspace |
| 2 | + |
| 3 | +This repository contains four separately owned MoonBit modules: |
| 4 | + |
| 5 | +- `Milky2018/wgsl` — official WGSL lexer, AST, parser, semantic IR, |
| 6 | + validation, and runtime writer |
| 7 | +- `Milky2018/moon_wgsl_naga` — Naga-compatible ordering, naming, writer, |
| 8 | + and trace behavior |
| 9 | +- `Milky2018/moon_wgsl_naga_oil` — naga-oil directives, imports, |
| 10 | + preprocessing, resolution, composition, export, profiles, and diagnostics |
| 11 | +- `Milky2018/moon_wgsl` — the small user-facing workflow facade |
| 12 | + |
| 13 | +Most applications should import `Milky2018/moon_wgsl`. Its opaque `Composer` |
| 14 | +supports exactly source/module registration, `prepare`, `compose`, and |
| 15 | +`export_wgsl`. Lower-level parser, IR, compatibility, graph, rewrite, and |
| 16 | +diagnostic stages are not facade methods. |
| 17 | + |
| 18 | +```mbt |
| 19 | +let composer = @moon_wgsl.Composer::default() |
| 20 | +composer.register_source("main.wgsl", "fn answer() -> u32 { return 42u; }") |
| 21 | +let source = composer.compose( |
| 22 | + "main.wgsl", |
| 23 | + @moon_wgsl.WgslComposeOptions::default(), |
| 24 | +) |
20 | 25 | ``` |
21 | 26 |
|
22 | | -Most users only need these packages: |
23 | | - |
24 | | -- `@common` for shared option and result types |
25 | | -- `@metadata` for inspecting directives and imports |
26 | | -- `@preprocess` for evaluating one shader source |
27 | | -- `@resolver` for source registries and source-tree scanning |
28 | | -- `@compose` for module composition |
29 | | -- `@export` for single-file export |
30 | | - |
31 | | -## Features |
32 | | - |
33 | | -- Conditional preprocessing: `#ifdef`, `#ifndef`, `#if`, `#else if`, `#else`, |
34 | | - and `#endif` |
35 | | -- Shader definition values: bools, signed integers, unsigned integers, and raw |
36 | | - WGSL text values |
37 | | -- Grouped, aliased, and quoted-path imports |
38 | | -- Composer-owned source registries for hermetic composition |
39 | | -- Optional source-tree scanning through `moonbitlang/x/fs` |
40 | | -- Single-file WGSL export with source catalog, source map, provenance, and |
41 | | - diagnostics |
42 | | - |
43 | | -## Quick Start |
44 | | - |
45 | | -### Compose Modules |
46 | | - |
47 | | -Register WGSL source strings on a `Composer`, then compose the root shader. |
48 | | - |
49 | | -```mbt check |
50 | | -///| |
51 | | -test "README: compose registered modules" { |
52 | | - let composer : @compose.Composer = @compose.Composer::default() |
53 | | - composer.clear_sources() |
54 | | -
|
55 | | - composer.register_source( |
56 | | - "maths.wgsl", "#define_import_path demo::maths\nconst TWO: f32 = 2.0;\n", |
57 | | - ) |
58 | | - composer.register_source( |
59 | | - "main.wgsl", "#import demo::maths::TWO\nfn scale(x: f32) -> f32 {\n return x * TWO;\n}\n", |
60 | | - ) |
61 | | -
|
62 | | - let options : @common.WgslComposeOptions = @common.WgslComposeOptions::default() |
63 | | - let composed = composer.compose_wgsl("main.wgsl", options) catch { |
64 | | - err => abort(err.message()) |
65 | | - } |
66 | | -
|
67 | | - debug_inspect(composed.contains("fn scale"), content="true") |
68 | | - debug_inspect(composed.contains("#import"), content="false") |
69 | | -} |
70 | | -``` |
71 | | - |
72 | | -### Preprocess One Shader |
73 | | - |
74 | | -Use `Preprocessor::preprocess` when you only need conditional compilation and |
75 | | -shader-definition substitution for a single source string. |
76 | | - |
77 | | -```mbt check |
78 | | -///| |
79 | | -test "README: preprocess one shader" { |
80 | | - let defs : Map[String, @common.ShaderDefValue] = Map([]) |
81 | | - defs.set("TEXTURE", Bool(true)) |
82 | | -
|
83 | | - let source = "#ifdef TEXTURE\nvar sprite_texture: texture_2d<f32>;\n#else\nvar sprite_texture: texture_2d_array<f32>;\n#endif\n" |
84 | | - let output = @preprocess.Preprocessor::default().preprocess(source, defs) catch { |
85 | | - _ => abort("preprocess failed") |
86 | | - } |
87 | | -
|
88 | | - debug_inspect( |
89 | | - output.preprocessed_source.contains("texture_2d<f32>"), |
90 | | - content="true", |
91 | | - ) |
92 | | -} |
93 | | -``` |
94 | | - |
95 | | -### Read Metadata |
96 | | - |
97 | | -Use metadata extraction when you want to inspect a shader before composing it. |
98 | | - |
99 | | -```mbt check |
100 | | -///| |
101 | | -test "README: inspect metadata" { |
102 | | - let source = "#define_import_path demo::main\n#define HDR\n#import demo::maths::TWO\nfn scale(x: f32) -> f32 {\n return x * TWO;\n}\n" |
103 | | - let metadata = @metadata.get_preprocessor_metadata(source) catch { |
104 | | - _ => abort("metadata extraction failed") |
105 | | - } |
| 27 | +The synchronized ownership change is intentionally breaking. Legacy package |
| 28 | +paths, compatibility records in WGSL Core, direct directive/import parsers, |
| 29 | +and the re-exported lower-level `Composer` are not retained as aliases. See |
| 30 | +[`docs/ownership-migration.md`](docs/ownership-migration.md) for the complete |
| 31 | +old-to-new mapping and [`docs/adr/0020-enforce-conceptual-ownership-and-deep-interfaces.md`](docs/adr/0020-enforce-conceptual-ownership-and-deep-interfaces.md) |
| 32 | +for the final architecture. |
106 | 33 |
|
107 | | - debug_inspect(metadata.name, content="Some(\"demo::main\")") |
108 | | - debug_inspect(metadata.imports.length(), content="1") |
109 | | -} |
110 | | -``` |
111 | | - |
112 | | -### Export One File |
113 | | - |
114 | | -Use `export_wgsl_with_options` to compose and tree-shake a root shader into one |
115 | | -WGSL file. |
116 | | - |
117 | | -```mbt check |
118 | | -///| |
119 | | -test "README: export single file" { |
120 | | - let composer : @compose.Composer = @compose.Composer::default() |
121 | | - composer.clear_sources() |
122 | | - composer.register_source( |
123 | | - "shared.wgsl", "#define_import_path demo::shared\nstruct Value {\n x: f32,\n}\nfn read(value: Value) -> f32 {\n return value.x;\n}\n", |
124 | | - ) |
125 | | - composer.register_source( |
126 | | - "main.wgsl", "#import demo::shared::{Value, read}\nfn shade(value: Value) -> f32 {\n return read(value);\n}\n", |
127 | | - ) |
128 | | -
|
129 | | - let compose_options : @common.WgslComposeOptions = @common.WgslComposeOptions::default() |
130 | | - let export_options : @common.WgslExportOptions = { root_items: ["shade"] } |
131 | | - let output = @export.export_wgsl_with_options( |
132 | | - composer, "main.wgsl", compose_options, export_options, |
133 | | - ) catch { |
134 | | - err => abort(err.message()) |
135 | | - } |
136 | | -
|
137 | | - debug_inspect(output.source.contains("#import"), content="false") |
138 | | - debug_inspect(output.source.contains("fn shade"), content="true") |
139 | | - debug_inspect(output.diagnostics.length(), content="0") |
140 | | -} |
141 | | -``` |
142 | | - |
143 | | -## Import Syntax |
144 | | - |
145 | | -The supported import forms match common `naga_oil` usage: |
146 | | - |
147 | | -```wgsl |
148 | | -#import bevy_render::view::View |
149 | | -#import bevy_render::maths as maths |
150 | | -#import bevy_render::{view::View, globals::Globals} |
151 | | -#import bevy_render::{maths::{PI_2, powsafe}} |
152 | | -#import "shaders/skills/shared.wgsl" Vertex, VertexOutput |
153 | | -#import "../shared/common.wgsl" SharedVertex, build_color |
154 | | -``` |
155 | | - |
156 | | -Relative quoted imports are resolved against the registered path of the |
157 | | -importing shader. |
158 | | - |
159 | | -## Recommended APIs |
160 | | - |
161 | | -- `@compose.Composer::default` |
162 | | -- `Composer::register_source` |
163 | | -- `Composer::register_source_files` |
164 | | -- `Composer::register_source_tree` |
165 | | -- `Composer::compose_wgsl` |
166 | | -- `@preprocess.Preprocessor::default` |
167 | | -- `Preprocessor::preprocess` |
168 | | -- `@metadata.get_preprocessor_metadata` |
169 | | -- `@resolver.scan_wgsl_source_files` |
170 | | -- `@resolver.scan_wgsl_source_files_checked` |
171 | | -- `@export.export_wgsl_with_options` |
172 | | - |
173 | | -Important option/result types live in `@common`, including: |
174 | | - |
175 | | -- `WgslComposeOptions` |
176 | | -- `WgslExportOptions` |
177 | | -- `WgslSourceFile` |
178 | | -- `WgslSourceScanOptions` |
179 | | -- `PreprocessOutput` |
180 | | -- `PreprocessorMetaData` |
181 | | -- `PreparedWgslSource` |
182 | | -- `WgslExportOutput` |
183 | | -- `WgslDiagnostic` |
184 | | -- `ShaderDefValue` |
185 | | - |
186 | | -## Compatibility |
187 | | - |
188 | | -The library aims to preserve the practical `naga_oil` programming model used by |
189 | | -real shader pipelines. Internally, composition uses structured parsing, |
190 | | -symbol-identity-aware binding, and an IR-backed validation pipeline before |
191 | | -returning runtime-oriented WGSL. |
192 | | - |
193 | | -For implementation details and parity status, see: |
194 | | - |
195 | | -- [`docs/naga_oil-parity.md`](docs/naga_oil-parity.md) |
196 | | -- [`docs/moon_wgsl-issue-tracker.md`](docs/moon_wgsl-issue-tracker.md) |
197 | | - |
198 | | -## Development |
199 | | - |
200 | | -Run the test suite from the module root: |
201 | | - |
202 | | -```bash |
203 | | -moon test |
204 | | -``` |
| 34 | +Development and release commands are documented in [`README.md`](README.md). |
205 | 35 |
|
206 | 36 | ## License |
207 | 37 |
|
|
0 commit comments