Skip to content

Conversation

@iwoplaza
Copy link

@iwoplaza iwoplaza commented Jul 30, 2025

Hey guys! 👋
Awesome initiative, TUIs are really having a comeback. And when I saw @kommander's involvement, and WebGPU running in the terminal, I had to snoop around the code.

I maintain a TypeScript library that promises better synergy between TypeScript and WebGPU, without getting in the developer's way, called TypeGPU. More TypeScript'y stuff can be sprinkled in where it makes sense, but the rest can be kept vanilla WebGPU. It's still in active development, and I love taking opportunities to see if it can help in projects that already use WebGPU at it's current state. This will help me remove any ['~unstable'] tags from TypeGPU APIs and ensure that what it's providing is actually useful to the community!

If you guys like this direction, we could go further by actually implementing the shader functions in TypeScript, instead of WGSL strings. It's something that my team and I been working on for a couple of months now, where we can compile TypeScript to WGSL at build time*. We can choose the implementation language at the function level, so a shader can be composed of TS and WGSL, no lock in ⛓️‍💥

    • The compilation process is more precisely:
      At build-time: JavaScript -> tinyest (Tiny Embeddable Syntax Tree™) --(crossing over to runtime)--> WGSL
      We do this to support more dynamic shader linking, while still not having to ship a JS parser to the browser/terminal like we would have to otherwise 🤢

@kommander
Copy link
Collaborator

Hey, thanks for the feedback! That looks interesting. I don't fully understand it yet though. Can you speak to what the benefits are compared to TSL?

I am trying to keep opentui low dependency, so I am hesitant to pull in something unstable that would make it harder for me to debug.

@iwoplaza
Copy link
Author

iwoplaza commented Jul 31, 2025

Sure thing!

I guess if I were to compare it TSL & Three.js from a design goals perspective, Three.js is definitely the easiest way to get into 3D on the web. It provides high-level abstractions, and occasionally allows you to drill down. In contrast, TypeGPU helps developers build their bespoke solutions from the ground up, with an ability to opt into higher-level abstractions. By being a thin wrapper around WebGPU, it doesn't have to abstract away the individual VRAM allocations, or what data is stored in each buffer. Pretty much every resource created from a TypeGPU API can be "unwrapped", which returns the backing WebGPU resource that represents it, e.g.:

const fooBuffer = root.createBuffer(arrayOf(vec4f, 16)).$usage('uniform');
//    ^? TgpuBuffer<WgslArray<Vec4f>> & UniformFlag
const fooBackingBuffer = root.unwrap(fooBuffer);
//    ^? GPUBuffer

There are also QoL features like being able to pass an initial value to a buffer, which maps it at creation and serializes the passed in JS value in accordance to the schema (in the case above, arrayOf(vec4f, 16)). Moreover, by defining the schemas once in TypeScript, instead of duplicating it in WGSL and JS serialization logic, we remove the chance of introducing mismatch whenever either side changes, and allow for better guarantees when passing buffers around (much like comparing well typed pointers and void* pointers)

When it comes to TSL, I think it suffers a bit from lack of type information, having to resort to If and For functions in place of control flow, and obstructing what the final compiled shader will actually be.

Following is an example of a TSL function (taken from the current draft of the TSL proposal):

const limitPosition = Fn( ( { position } ) => {

	const limit = 10;

	// Convert to variable using `.toVar()` to be able to use assignments.
	const result = position.toVec3().toVar();

	If( result.y.greaterThan( limit ), () => {

		result.y = limit;

	} ).ElseIf( result.y.lessThan( limit ), () => {

		result.y = limit;

	} );

	return result;

} );

The equivalent function in TGSL (TypeGPU Shading Language, which is just a subset of JS + our standard library):

const limitPosition = tgpu.fn([vec3f], vec3f)((position) => {
  const limit = 10
  // Copying into a new vector
  const result = vec3f(position)
    
  if (result.y > limit) {
    result.y = limit
  } else if (result.y < limit) {
    result.y = limit
  }
  
  return result
})

It becomes clear enough to see that the function doesn't need a conditional at all, and result.y will always equal limit.

If we use the tgpu.resolve API, we can see what the generated WGSL would look like, and it matches the written shader code 1-to-1:

image

We can implement any function in either TGSL or just WGSL (as it's done in this PR). In case TypeGPU does not behave as you expect, this makes it really easy to granularly eject out without having to rewrite the whole shader.

To summarize, TypeGPU uses the fact that it can start fresh, and sticks close to WebGPU while providing better synergy with TypeScript via advanced end-to-end (CPU-to-GPU) type inference 🚀

@kommander
Copy link
Collaborator

Ah now it is clearer. That is neat.

@nikelborm
Copy link

nikelborm commented Aug 1, 2025

Hi! I don't understand much about either opentui or typegpu, but I'm just curious about both projects and love typesafety. @iwoplaza, in the message you sent above, you showed the function

const limitPosition = tgpu.fn([vec3f], vec3f)((position) => {
  const limit = 10
  // Copying into a new vector
  const result = vec3f(position)
    
  if (result.y > limit) {
    result.y = limit
  } else if (result.y < limit) {
    result.y = limit
  }
  
  return result
})

Which has proper syntax highlighting, and most of the development IDEs will render it just fine. However, in the actual code of the PR, most of the code I see uses string literals, which, at least on the GitHub side, makes this code less readable.

const blendColors = tgpu.fn([vec4f, vec4f], vec4f)`(color1, color2) {
  let a1 = color1.a;
  let a2 = color2.a;
  
  if (a1 == 0.0 && a2 == 0.0) {
    return vec4<f32>(0.0, 0.0, 0.0, 0.0);
  }
  
  let outAlpha = a1 + a2 - a1 * a2;
  if (outAlpha == 0.0) {
    return vec4<f32>(0.0, 0.0, 0.0, 0.0);
  }
  
  let rgb = (color1.rgb * a1 + color2.rgb * a2 * (1.0 - a1)) / outAlpha;
  
  return vec4<f32>(rgb, outAlpha);
}`

Is it as it should be? Next, I wanted to ask you if there will be problems with linters? I doubt special support for typegpu will be added in all of them, so that they will interpret these strings somehow differently, and it seems like most of them won't validate the code inside multiline-string backticks. How many more steps will be needed to get the typescript compiler's feedback? Will I see syntax errors or type errors properly highlighted just inside my VS Code IDE, pointing at exact words inside those string literals? Would I need to use something like ts-patch? Will syntax highlighting work properly?

@nikelborm
Copy link

nikelborm commented Aug 1, 2025

I remember with ts-patch, which, for example, is used by Effect-TS/language-service, people were able to write custom transformations of AST, so that losing syntax highlighting wouldn't be necessary. And people were able to avoid runtime and do everything inside the ts -> js compilation step. Something similar is done in deepkit, which generates runtime code from purely TS's AST and TS's types/interfaces.

@iwoplaza
Copy link
Author

iwoplaza commented Aug 1, 2025

Hey @nikelborm! Very good questions.

However, in the actual code of the PR, most of the code I see uses string literals, which, at least on the GitHub side, makes this code less readable.

There are IDE extensions (at least there is one for VS Code) that add syntax highlighting to template literals like this, when they’re preceded by a /wgsl/ comment. I chose to omit them based on a personal preference, I like seeing which part is JS, and which part is WGSL.

As for JS implementations of these functions, we have build plugins that enable this functionality for pretty much every bundler out there (thanks to unplugin), and now we have an incentive to build one for the Bun plugin system ✨.

Next, I wanted to ask you if there will be problems with linters? I doubt special support for typegpu will be added in all of them, so that they will interpret these strings somehow differently, and it seems like most of them won't validate the code inside multiline-string backticks.

That’s true, linters, at least for the foreseeable future, won’t be able to interpret the WGSL inside of template literals. It’s definitely a tradeoff for projects that stay in this state of implementing every function with WGSL, but I would push for the code in this PR to move towards JS-implementations, and use WGSL-implementations as convenient escape-hatches. This way, the TypeScript LSP (and other existing TS tooling) can cover both host and shader code.

I remember with ts-patch, which, for example, is used by Effect-TS/language-service, people were able to write custom transformations of AST, so that losing syntax highlighting wouldn't be necessary.

Interesting! I’ll have to take a closer look at these projects. If we’d be able to extend type checking to WGSL-implemented functions’ bodies, then that would be pretty amazing.

@nikelborm
Copy link

nikelborm commented Aug 1, 2025

If I can take another moment of your time, I would like to ask you about syntax error reporting inside. If I mistyped inside string literals, for example, instead of let, I typed lt, or made a mistake when typing some inner variable name. How many steps will I need to take to see squigly lines? Will I see them at all? Or will I just get them at the compilation step or runtime? Is error reporting included with syntax highlighting inside the VS Code plugin? Will I need to run something inside a terminal? Also, what about CI/CD integrations? Is there something interesting there?

@iwoplaza
Copy link
Author

iwoplaza commented Aug 1, 2025

That's actually a cool idea for a linter plugin 🤔. It's definitely doable, I'll create an issue for that idea.

@iwoplaza
Copy link
Author

iwoplaza commented Aug 1, 2025

Also, what about CI/CD integrations? Is there something interesting there?

Since every TypeGPU functions that's implemented in JS is valid JS not only syntactically but semantically, we can unit test shader functions without requiring a GPU 🧪

@iwoplaza
Copy link
Author

iwoplaza commented Aug 1, 2025

Tracking the linter plugin here: software-mansion/TypeGPU#1559

@iwoplaza
Copy link
Author

iwoplaza commented Aug 4, 2025

Hey @nikelborm! I've managed to port our plugin to the Bun Plugin system, and created a separate PR where the functions are implemented in TypeScript instead of WGSL, in case you wanted to take a look: #8

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants