-
Notifications
You must be signed in to change notification settings - Fork 3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Design a basic npm packaging format #56
Comments
There's a prototype implementation here. Some questions:
|
My immediate reactions to the questions would be
No, vite can just as well use the Javascript object that contains everything.
I think it doesn't matter, since tree-shaking should work either way. |
The linkers will keep unused shader code from WebGPU, I agree. But w/o further changes to spit things up, I think the javascript bundle will include all the shaders from the library. At least in the case of runtime linking w/o a build time support through e.g. a vite plugin.. |
Great initiative! This might have already been resolved, but I'll share my initial thoughts. Let me know in case I missed something.
If we instead parse the source WESL files on the side of the library author, before publishing, we can:
Example of what a type-friendly format could look likeTransform a WGSL file at prepublish time: // The style of imports can of-course change in WESL.
use example-wgsl-utility::{ Gradient };
pub fn red_to_blue_gradient() -> Gradient {
var result: Gradient;
result.from = vec3f(1., 0., 0.);
result.to = vec3f(0., 0., 1.);
return result;
}: Into this: // This lets the types propagate from deep in the
// dependency tree straight up to the library consumer
import { Gradient } from 'example-wgsl-utility';
// Injecting only once, when one or more functions are defined
/**
* @template {unknown[]} TArgs
* @template TReturn
* @typedef {object} WgslFn
* @prop {'function'} kind
* @prop {T} argTypes
*/
const fn = /**@type{<TArgs extends unknown[], TReturn>(argTypes: TArgs, returnType: TReturn, body: string)=>WgslFn<TArgs,TReturn>}*/ (
(argTypes, returnType, body) => ({
kind: 'function',
argTypes,
returnType,
body,
})
);
export const red_to_blue_gradient = fn([], Gradient, '() -> Gradient {
var result: Gradient;
result.from = vec3f(1., 0., 0.);
result.to = vec3f(0., 0., 1.);
return result;
}'}; Example: Using the library to retrieve just WGSLimport * as myLib from 'my-library';
const rawWGSL = linker.link(appWGSL, { libs: [myLib] }); Example: Using the library with TypeGPUimport { red_to_blue_gradient } from 'my-library';
const mainFrag = tgpu.fragmentFn({}, {}).does(() => {
// inferred by TypeScript to return: WgslStruct<{ from: Vec3f, to: Vec3f }>
const grad = red_to_blue_gradient();
return vec4f(grad.from, 1.0);
}); |
One thing that this issue is still missing is "how does a language server get the .wesl code". There are two major approaches
|
If we'd like to support click to go to definition as well as IntelliSense, maybe we should ship the source .wesl/.wgsl files along with the consumer wesl. That way, when going to definition in host-land, it would take the developer to the consumer wesl, and when going to definition in wesl-land, it would take them to the source wesl. |
In the context of my proposal, the "consumer wesl" would be WGSL snippets that are wrapped in easy to interpret JS that can be consumed both by the TS type system as well as JS at runtime. |
On another note, how would transitive dependencies be handled in the current system? For the given example:
Lets say module_a has a dependency on module_c, both being WESL modules. linker.link(appWESL, { libs: [moduleA, moduleB, moduleC] }); If so, that means that if a module gains a direct dependency, then the module consumer has to also become a direct dependant of that dependency. If we instead leverage the host language's imports for "consumer wesl", then the transitive dependency problem solves itself. |
Yes, currently. And also yes, it'd be great to fix things so that users didn't have to specify libraries manually when calling the linker. See vite plugin for WESL for some vague hopes that a plugin could help. |
I think using a pre-parsed version of WGSL at runtime makes a lot of sense. Re-parsing to link at runtime takes time and code space. I expect that the parsing will eventually be more than 150K LOC/sec on a laptop, and that the parser part of the linker will be less than 10kb. I bet a pre-parsed version could save most of that time and code space. If we had a build tool that converts project and library WESL to a pre-parsed form, the pre-parsed form would be more free to evolve and optimize. I think that might be wiser than trying to stabilize a pre-parsed form for long term support.. Or perhaps there ought to be optional fields in the library format. Packages could include WESL source as a baseline, but also include version locked pre-parsed WESL source, or shader-slang source, etc.
I really like the ability to control linking from TypeScript, and also that exposing TypeScript types for WGSL elements so that interop between shaders and TypeScript is safer. I think the interop problem is probably the easier one of the two, e.g. sharing types for uniforms by injected them from TypeScript or reflecting them from WGSL. I know you've been thinking about that too. Maybe we should start with that? For linking control (i.e. controlling how shaders get assembled), I think some will prefer to control things from WESL and some will prefer to control things from TypeScript. Let's talk about how we can support both camps! See #51 too.
I'm not sure if we can use WGSL as the pre-parsed form for shaders. It'd be nice! But wouldn't that preclude the WESL features like conditions or planned features like generics? Those features can trigger different WGSL code specialization at runtime. Maybe we could eventually design some kind of WGSL+annotations as the pre-parsed form.. |
Definitely 🚀. Each host language would most likely have to have its own pre-parsed form that can be properly ingested. For TypeScript, I believe embedding types into packages at build-time, and publishing the result would be the only optimal solution. Otherwise we would be tasking ourselves with traversing the dependency tree recursively, and providing the types by means of code-gen, which is not the best DX. We'll still need codegen for the app WESL, but JS runtimes can differ in where they store libraries (Node: node_modules, Deno: a global cache). |
I'm working on a POC of that since yesterday, I'll share it once I have something to share 🙌 |
From what I can understand, dynamic links (ones determined by the host code) are explicitly defined in WESL source code via |
The |
Let me clarify, the result of linking would be WGSL, the input (as in the app code and lib code) would be the pre-parsed format. |
flowchart LR
%% Nodes
A(".wesl files")
B("TypeScript Types")
C("Preparsed Shaders")
D("wgsl string")
T>Type Tool]
O>Opt Tool]
P>Packager Tool]
R>"App Bundler (opt)"]
N[npm package]
L>Linker]
I[/IDE\]
%% Edge connections between nodes
A --> R --> L --> D
A --> P --> N --> R
A --> T --> B -.-> R
A --> O --> C -.-> R
%% styles
classDef today fill:#7cbded;
linkStyle default stroke-width:2;
linkStyle 0,1,2,3,4,5 stroke:#7cbded,stroke-width:2;
class A,D,P,N,L,R,I today;
Today we go from .wesl files directly to the linker (in blue). I think we're discussing two new data structures.
Many design questions that will be fun to work out together:
|
Exciting! |
Below is a repo containing an example module along with an example app using that module. I used our learnings from TypeGPU to solve the following problems:
https://github.com/iwoplaza/wesl-package-example To see the full IDE experience, it would be best to clone the repo, run the local setup described in the README, and hover over the interesting bits in the example app. Excited to discuss these problems and solutions further! 🙌 |
Let's see if we can come up with a basic npm packaging format for simple WESL libraries so that JavaScript and TypeScript MVP users can get a taste of using stateless WESL libraries.
The packaging format doesn't need to be stable for long term and libaries should be labeled with an unstable version. See #5.
Here's one possible approach:
A library (manually) creates a file that includes WESL files as strings, along with a
package.json
entry:See also packaging and earlier gdoc for related discussion.
The text was updated successfully, but these errors were encountered: