plum
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
|
-
|
|
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
|
}],
|