plum

#treesitter#compiler#wasm

git clone https://git.pyrossh.dev/plum

A statically typed, imperative programming language inspired by rust, python


bcca2f1 — Peter John 2026-11-02T17:42:31+05:30
plum_bridge: render-target textures are premultiplied (unpremultiply on sample, premultiplied clear), bound the bind-group cache; rendertarget fixture covers a translucent target
plum-game/web-test/fixtures/rendertarget.plum CHANGED
@@ -8,6 +8,8 @@
8
8
  # 4. Screen (700,100): rgb(0.5,0.5,0.5) gives (153,153,153)
9
9
  # 5. Normal with alpha 0.5 (850,100): red gives (153,26,26)
10
10
  # 6. a second 16x16 blue target drawn at (400,300)
11
+ # 7. a 16x16 target holding red at alpha 0.5 drawn at (400,400): translucent target
12
+ # contents blend once, giving (153,26,26) like item 5
11
13
  module rendertarget
12
14
  import std/Str
13
15
  import std/Int
@@ -16,10 +18,10 @@ import std/Vec2
16
18
  import std/Color
17
19
  import std/Game
18
20
 
19
- record Targets(big: Int, small: Int, t: Int)
21
+ record Targets(big: Int, small: Int, glass: Int, t: Int)
20
22
 
21
23
  export fun init() -> Targets =
22
- Targets(big: textureCreate(64, 64), small: textureCreate(16, 16), t: 0)
24
+ Targets(big: textureCreate(64, 64), small: textureCreate(16, 16), glass: textureCreate(16, 16), t: 0)
23
25
 
24
26
  export fun update(g: Targets, dt: Float) =
25
27
  g.t = g.t + 1
@@ -32,6 +34,9 @@ export fun draw(g: Targets) =
32
34
  drawRect(32.0, 32.0, 32.0, 32.0, Color.rgb(0.0, 1.0, 0.0))
33
35
  renderTarget(g.small)
34
36
  clear(Color.rgb(0.0, 0.0, 1.0))
37
+ renderTarget(g.glass)
38
+ clear(Color.Transparent)
39
+ drawRect(0.0, 0.0, 16.0, 16.0, Color(r: 1.0, g: 0.0, b: 0.0, a: 0.5))
35
40
  renderTarget(-1)
36
41
  clear(Color.rgb(0.2, 0.2, 0.2))
37
42
  drawTexture(g.big, 60.0, 50.0, Color.White)
@@ -45,5 +50,6 @@ export fun draw(g: Targets) =
45
50
  blendMode(Blend.Normal)
46
51
  drawRect(850.0, 100.0, 100.0, 100.0, Color(r: 1.0, g: 0.0, b: 0.0, a: 0.5))
47
52
  drawTexture(g.small, 400.0, 300.0, Color.White)
53
+ drawTexture(g.glass, 400.0, 400.0, Color.White)
48
54
  drawText("frame {g.t}", 20.0, 500.0, 16.0, Color.White)
49
55
  drawRect(Float(g.t % 600), 600.0, 40.0, 6.0, Color.Yellow)
plum-game/web-test/tests/web_check.rs CHANGED
@@ -97,7 +97,7 @@ fn render_target_pixels() {
97
97
  eprintln!("SKIP: Playwright Chromium not installed (see plum-game/web-test/README.md)");
98
98
  return;
99
99
  }
100
- let pixels = ["130,130,255,0,0", "196,196,0,255,0", "130,196,51,51,51", "450,150,102,153,204", "600,150,26,51,26", "750,150,153,153,153", "900,150,153,26,26", "408,308,0,0,255"];
100
+ let pixels = ["130,130,255,0,0", "196,196,0,255,0", "130,196,51,51,51", "450,150,102,153,204", "600,150,26,51,26", "750,150,153,153,153", "900,150,153,26,26", "408,308,0,0,255", "408,408,153,26,26"];
101
101
  let opts = Options { expect_pixels: pixels.iter().map(|p| plum_web_test::ExpectPixel::parse(p).unwrap()).collect(), ..Options::default() };
102
102
  let rt = tokio::runtime::Runtime::new().unwrap();
103
103
  let file = repo_root().join("plum-game/web-test/fixtures/rendertarget.plum");
plum-game/web/plum_bridge.js CHANGED
@@ -246,6 +246,7 @@
246
246
  const ATLAS_W = 2048;
247
247
  const ATLAS_H = 1024;
248
248
  const SDF_BIT = 512; // the texture is the glyph atlas: alpha holds a distance field
249
+ const PREM_BIT = 1024; // the texture is a render target: its colour is premultiplied by its alpha
249
250
  const MAX_SEGMENTS = 96;
250
251
 
251
252
  function clamp01(v) {
@@ -352,7 +353,7 @@
352
353
  t.slot = cur.n;
353
354
  cur.slots[cur.n++] = t;
354
355
  }
355
- slotBits = t.slot | (t.smooth ? SMOOTH_BIT : 0) | (t.sdf ? SDF_BIT : 0);
356
+ slotBits = t.slot | (t.smooth ? SMOOTH_BIT : 0) | (t.sdf ? SDF_BIT : 0) | (t.target ? PREM_BIT : 0);
356
357
  }
357
358
 
358
359
  function vert(x, y, u, v, c) {
@@ -595,7 +596,10 @@ ${cases} default: { return vec4f(1.0); }
595
596
  }
596
597
  fn shade(i: VOut) -> vec4f {
597
598
  let fw = max(fwidth(i.uv.x), fwidth(i.uv.y));
598
- let texel = pick(i.slot, i.uv);
599
+ var texel = pick(i.slot, i.uv);
600
+ if ((i.slot & ${PREM_BIT}u) != 0u) {
601
+ texel = vec4f(texel.rgb / max(texel.a, 0.0001), texel.a);
602
+ }
599
603
  if ((i.slot & ${SDF_BIT}u) != 0u) {
600
604
  let w = clamp(fw * ${ATLAS_W}.0 / ${2 * GLYPH_SPREAD}.0 * 0.75, 0.02, 0.5);
601
605
  return vec4f(i.c.rgb, i.c.a * smoothstep(0.5 - w, 0.5 + w, texel.a));
@@ -767,6 +771,7 @@ fn shade(i: VOut) -> vec4f {
767
771
 
768
772
  // Bind groups by the textures of a draw's slots (unused slots hold the white texture).
769
773
  function bindGroup(batch) {
774
+ if (groups.size > 512) groups.clear(); // bounded: every distinct set of 16 textures is a key
770
775
  let key = "";
771
776
  for (let i = 0; i < SLOTS; i++) key += (i < batch.n ? batch.slots[i].uid : white.uid) + ",";
772
777
  let bg = groups.get(key);
@@ -832,11 +837,12 @@ fn shade(i: VOut) -> vec4f {
832
837
  if (pass) pass.end();
833
838
  const screen = !target;
834
839
  const clearing = clearValue || (screen && !screenCleared ? [0, 0, 0, 1] : null);
840
+ const k = clearing && !screen ? clearing[3] : 1; // a target holds premultiplied colour
835
841
  if (screen) screenCleared = true;
836
842
  pass = enc.beginRenderPass({
837
843
  colorAttachments: [{
838
844
  view: screen ? ctx.getCurrentTexture().createView() : target.view,
839
- clearValue: clearing ? { r: clearing[0], g: clearing[1], b: clearing[2], a: screen ? 1 : clearing[3] } : undefined,
845
+ clearValue: clearing ? { r: clearing[0] * k, g: clearing[1] * k, b: clearing[2] * k, a: screen ? 1 : clearing[3] } : undefined,
840
846
  loadOp: clearing ? "clear" : "load",
841
847
  storeOp: "store",
842
848
  }],