@aethervtt/shard-render 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +3 -0
  3. package/dist/assets.d.ts +104 -0
  4. package/dist/assets.js +213 -0
  5. package/dist/atmosphere-model.d.ts +96 -0
  6. package/dist/atmosphere-model.js +370 -0
  7. package/dist/atmosphere-nodes.d.ts +55 -0
  8. package/dist/atmosphere-nodes.js +780 -0
  9. package/dist/atmosphere-plugin.d.ts +5 -0
  10. package/dist/atmosphere-plugin.js +42 -0
  11. package/dist/atmosphere-shaders.d.ts +7 -0
  12. package/dist/atmosphere-shaders.js +595 -0
  13. package/dist/atmosphere-state.d.ts +72 -0
  14. package/dist/atmosphere-state.js +18 -0
  15. package/dist/atmosphere.d.ts +191 -0
  16. package/dist/atmosphere.js +950 -0
  17. package/dist/camera.d.ts +83 -0
  18. package/dist/camera.js +163 -0
  19. package/dist/clusters.d.ts +68 -0
  20. package/dist/clusters.js +288 -0
  21. package/dist/culling-shaders.d.ts +2 -0
  22. package/dist/culling-shaders.js +203 -0
  23. package/dist/culling.d.ts +84 -0
  24. package/dist/culling.js +498 -0
  25. package/dist/debug-shaders.d.ts +3 -0
  26. package/dist/debug-shaders.js +207 -0
  27. package/dist/debug-views.d.ts +60 -0
  28. package/dist/debug-views.js +197 -0
  29. package/dist/deferred-plugin.d.ts +5 -0
  30. package/dist/deferred-plugin.js +20 -0
  31. package/dist/deferred-shaders.d.ts +2 -0
  32. package/dist/deferred-shaders.js +151 -0
  33. package/dist/deferred.d.ts +22 -0
  34. package/dist/deferred.js +296 -0
  35. package/dist/deform.d.ts +80 -0
  36. package/dist/deform.js +213 -0
  37. package/dist/describe-plugin.d.ts +5 -0
  38. package/dist/describe-plugin.js +19 -0
  39. package/dist/display-nodes.d.ts +5 -0
  40. package/dist/display-nodes.js +189 -0
  41. package/dist/dynamic-resolution.d.ts +47 -0
  42. package/dist/dynamic-resolution.js +191 -0
  43. package/dist/environment-plugin.d.ts +6 -0
  44. package/dist/environment-plugin.js +44 -0
  45. package/dist/environment-shaders.d.ts +2 -0
  46. package/dist/environment-shaders.js +260 -0
  47. package/dist/environment-state.d.ts +71 -0
  48. package/dist/environment-state.js +144 -0
  49. package/dist/environment.d.ts +70 -0
  50. package/dist/environment.js +529 -0
  51. package/dist/forward.d.ts +123 -0
  52. package/dist/forward.js +1216 -0
  53. package/dist/fxaa.d.ts +2 -0
  54. package/dist/fxaa.js +65 -0
  55. package/dist/gizmo-font.d.ts +21 -0
  56. package/dist/gizmo-font.js +132 -0
  57. package/dist/gizmos-plugin.d.ts +5 -0
  58. package/dist/gizmos-plugin.js +34 -0
  59. package/dist/gizmos.d.ts +156 -0
  60. package/dist/gizmos.js +772 -0
  61. package/dist/gpu-assets.d.ts +114 -0
  62. package/dist/gpu-assets.js +494 -0
  63. package/dist/graph.d.ts +204 -0
  64. package/dist/graph.js +417 -0
  65. package/dist/index.d.ts +53 -0
  66. package/dist/index.js +540 -0
  67. package/dist/instances.d.ts +349 -0
  68. package/dist/instances.js +1335 -0
  69. package/dist/large-world.d.ts +13 -0
  70. package/dist/large-world.js +20 -0
  71. package/dist/lights.d.ts +160 -0
  72. package/dist/lights.js +468 -0
  73. package/dist/material-noise.d.ts +19 -0
  74. package/dist/material-noise.js +7 -0
  75. package/dist/material-pipelines.d.ts +46 -0
  76. package/dist/material-pipelines.js +166 -0
  77. package/dist/materials.d.ts +108 -0
  78. package/dist/materials.js +219 -0
  79. package/dist/noise.d.ts +76 -0
  80. package/dist/noise.js +286 -0
  81. package/dist/overlay-registry.d.ts +18 -0
  82. package/dist/overlay-registry.js +30 -0
  83. package/dist/overlays.d.ts +35 -0
  84. package/dist/overlays.js +344 -0
  85. package/dist/paths.d.ts +20 -0
  86. package/dist/paths.js +46 -0
  87. package/dist/picking-plugin.d.ts +2 -0
  88. package/dist/picking-plugin.js +20 -0
  89. package/dist/picking.d.ts +94 -0
  90. package/dist/picking.js +456 -0
  91. package/dist/pixel-perfect-plugin.d.ts +9 -0
  92. package/dist/pixel-perfect-plugin.js +135 -0
  93. package/dist/pixel-perfect.d.ts +27 -0
  94. package/dist/pixel-perfect.js +70 -0
  95. package/dist/plugin.d.ts +111 -0
  96. package/dist/plugin.js +182 -0
  97. package/dist/pool.d.ts +19 -0
  98. package/dist/pool.js +43 -0
  99. package/dist/post-common.d.ts +51 -0
  100. package/dist/post-common.js +148 -0
  101. package/dist/post-nodes.d.ts +39 -0
  102. package/dist/post-nodes.js +849 -0
  103. package/dist/post-plugin.d.ts +5 -0
  104. package/dist/post-plugin.js +26 -0
  105. package/dist/post-shaders.d.ts +8 -0
  106. package/dist/post-shaders.js +698 -0
  107. package/dist/post.d.ts +175 -0
  108. package/dist/post.js +453 -0
  109. package/dist/readback.d.ts +4 -0
  110. package/dist/readback.js +95 -0
  111. package/dist/render-scale.d.ts +53 -0
  112. package/dist/render-scale.js +63 -0
  113. package/dist/shaders.d.ts +122 -0
  114. package/dist/shaders.js +1168 -0
  115. package/dist/shadows.d.ts +119 -0
  116. package/dist/shadows.js +562 -0
  117. package/dist/skin-asset.d.ts +26 -0
  118. package/dist/skin-asset.js +25 -0
  119. package/dist/skinning-plugin.d.ts +5 -0
  120. package/dist/skinning-plugin.js +16 -0
  121. package/dist/skinning.d.ts +19 -0
  122. package/dist/skinning.js +343 -0
  123. package/dist/standard-fields.d.ts +67 -0
  124. package/dist/standard-fields.js +73 -0
  125. package/dist/standard.d.ts +7 -0
  126. package/dist/standard.js +37 -0
  127. package/dist/stats.d.ts +25 -0
  128. package/dist/stats.js +18 -0
  129. package/dist/target.d.ts +55 -0
  130. package/dist/target.js +70 -0
  131. package/dist/testing.d.ts +34 -0
  132. package/dist/testing.js +126 -0
  133. package/dist/timer.d.ts +28 -0
  134. package/dist/timer.js +136 -0
  135. package/dist/view.d.ts +117 -0
  136. package/dist/view.js +389 -0
  137. package/dist/visibility.d.ts +10 -0
  138. package/dist/visibility.js +84 -0
  139. package/package.json +49 -0
@@ -0,0 +1,189 @@
1
+ import { Textures } from "@aethervtt/shard-texture";
2
+ import { GpuAssetsResource } from "./gpu-assets.js";
3
+ import { RenderPhase } from "./graph.js";
4
+ import { Graph } from "./plugin.js";
5
+ import { PostEffect } from "./post.js";
6
+ import { idOf, PostCache, sampler, scratch, tex, uniform } from "./post-common.js";
7
+ import { RenderScale } from "./render-scale.js";
8
+ import { cameraOf } from "./view.js";
9
+ function whiteBalance(temperature, tint, out) {
10
+ const t1 = temperature * 100 / 60;
11
+ const t2 = tint * 100 / 60;
12
+ const x = 0.31271 - t1 * (t1 < 0 ? 0.1 : 0.05);
13
+ const y = 2.87 * x - 3 * x * x - 0.27509507 + t2 * 0.05;
14
+ const X = x / y;
15
+ const Z = (1 - x - y) / y;
16
+ const L = 0.7328 * X + 0.4296 - 0.1624 * Z;
17
+ const M = -0.7036 * X + 1.6975 + 61e-4 * Z;
18
+ const S = 3e-3 * X + 0.0136 + 0.9834 * Z;
19
+ out[0] = 0.949237 / L;
20
+ out[1] = 1.03542 / M;
21
+ out[2] = 1.08728 / S;
22
+ return out;
23
+ }
24
+ function tonemapNode() {
25
+ const cache = new PostCache();
26
+ let layout;
27
+ let layoutGen = -1;
28
+ let white;
29
+ const u = new Uint32Array(scratch.buffer);
30
+ const balance = new Float32Array(3);
31
+ return {
32
+ kind: "render",
33
+ phase: RenderPhase.Tonemap,
34
+ enabled: (view) => cameraOf(view) !== void 0,
35
+ reads: ["post-hdr"],
36
+ writes: ["ldr"],
37
+ color: [{ resource: "ldr", clear: { r: 0, g: 0, b: 0, a: 1 } }],
38
+ run: (ctx) => {
39
+ const gpu = ctx.gpu;
40
+ const cam = cameraOf(ctx.view);
41
+ if (!layout || layoutGen !== gpu.generation) {
42
+ layoutGen = gpu.generation;
43
+ layout = gpu.layouts.bindGroupLayout({
44
+ label: "tonemap",
45
+ entries: [tex(0, "unfilterable-float"), uniform(1), tex(2), sampler(3)]
46
+ });
47
+ white = gpu.device.createTexture({
48
+ label: "tonemap/no-lut",
49
+ size: [1, 1],
50
+ format: "rgba8unorm",
51
+ usage: GPUTextureUsage.TEXTURE_BINDING
52
+ });
53
+ }
54
+ const format = ctx.texture("ldr").format;
55
+ const srgb = format.endsWith("-srgb");
56
+ const pipeline = cache.render(
57
+ ctx,
58
+ `tonemap/${format}`,
59
+ "shard::post::tonemap",
60
+ "fs",
61
+ [layout],
62
+ [{ format }],
63
+ {
64
+ SRGB_TARGET: srgb
65
+ }
66
+ );
67
+ if (!pipeline) return;
68
+ const post = cam.post;
69
+ const g = post.grading;
70
+ let flags = 0;
71
+ if (post.effects & PostEffect.Grading) flags |= 1;
72
+ if (post.effects & PostEffect.Vignette) flags |= 2;
73
+ let lut;
74
+ if (post.effects & PostEffect.Grading && g.lut) {
75
+ const texture = ctx.world.resource(Textures).get(g.lut);
76
+ const gt = texture ? ctx.world.resource(GpuAssetsResource).texture(texture) : void 0;
77
+ if (gt) {
78
+ lut = gt.linear;
79
+ flags |= 4;
80
+ }
81
+ }
82
+ scratch.fill(0, 0, 32);
83
+ u[0] = cam.curve;
84
+ u[1] = cam.dither ? 1 : 0;
85
+ u[2] = flags;
86
+ whiteBalance(g.temperature, g.tint, balance);
87
+ scratch[4] = balance[0];
88
+ scratch[5] = balance[1];
89
+ scratch[6] = balance[2];
90
+ scratch[8] = g.saturation;
91
+ scratch[9] = g.contrast;
92
+ scratch.set(g.lift, 12);
93
+ scratch.set(g.gamma, 16);
94
+ scratch.set(g.gain, 20);
95
+ scratch[24] = post.vignette.intensity;
96
+ scratch[25] = post.vignette.smoothness;
97
+ scratch[26] = cam.width / Math.max(1, cam.height);
98
+ scratch[28] = 1 / cam.width;
99
+ scratch[29] = 1 / cam.height;
100
+ const params = cache.buffer(gpu, `${ctx.view.name}/tonemap`, 128);
101
+ params.write(scratch, 0, 0, 32);
102
+ const hdr = ctx.texture("post-hdr");
103
+ const lutView = lut ?? white.createView();
104
+ const group = cache.group(
105
+ gpu,
106
+ `${ctx.view.name}/tonemap`,
107
+ `${idOf(hdr)}/${params.version}/${lut ? idOf(lut) : 0}`,
108
+ layout,
109
+ () => [
110
+ { binding: 0, resource: hdr.createView() },
111
+ { binding: 1, resource: { buffer: params.buffer } },
112
+ { binding: 2, resource: lutView },
113
+ { binding: 3, resource: cache.sampler(gpu) }
114
+ ]
115
+ );
116
+ const pass = ctx.renderPass;
117
+ pass.setPipeline(pipeline);
118
+ pass.setBindGroup(0, group);
119
+ pass.draw(3);
120
+ }
121
+ };
122
+ }
123
+ const upscaleScratch = new Float32Array(4);
124
+ function upscaleNode() {
125
+ const cache = new PostCache();
126
+ let layout;
127
+ let layoutGen = -1;
128
+ return {
129
+ kind: "render",
130
+ phase: RenderPhase.Display + 10,
131
+ enabled: (view) => view.width !== void 0 && cameraOf(view) !== void 0,
132
+ reads: ["display"],
133
+ writes: ["view-target"],
134
+ color: [{ resource: "view-target", clear: { r: 0, g: 0, b: 0, a: 1 } }],
135
+ run: (ctx) => {
136
+ const gpu = ctx.gpu;
137
+ if (!layout || layoutGen !== gpu.generation) {
138
+ layoutGen = gpu.generation;
139
+ layout = gpu.layouts.bindGroupLayout({
140
+ label: "upscale",
141
+ entries: [tex(0), sampler(1), uniform(2)]
142
+ });
143
+ }
144
+ const input = ctx.texture("display");
145
+ const target = ctx.texture("view-target");
146
+ const format = target.format;
147
+ const pipeline = cache.render(
148
+ ctx,
149
+ `upscale/${format}`,
150
+ "shard::post::upscale",
151
+ "fs",
152
+ [layout],
153
+ [{ format }]
154
+ );
155
+ if (!pipeline) return;
156
+ const settings = ctx.world.tryResource(RenderScale);
157
+ upscaleScratch[0] = 1 / target.width;
158
+ upscaleScratch[1] = 1 / target.height;
159
+ upscaleScratch[2] = Math.min(1, Math.max(0, settings?.sharpen ?? 0));
160
+ const params = cache.buffer(gpu, `${ctx.view.name}/upscale`, 16);
161
+ params.write(upscaleScratch, 0, 0, 4);
162
+ const group = cache.group(
163
+ gpu,
164
+ `${ctx.view.name}/upscale`,
165
+ `${idOf(input)}/${params.version}`,
166
+ layout,
167
+ () => [
168
+ { binding: 0, resource: input.createView() },
169
+ { binding: 1, resource: cache.sampler(gpu) },
170
+ { binding: 2, resource: { buffer: params.buffer } }
171
+ ]
172
+ );
173
+ const pass = ctx.renderPass;
174
+ pass.setPipeline(pipeline);
175
+ pass.setBindGroup(0, group);
176
+ pass.draw(3);
177
+ }
178
+ };
179
+ }
180
+ function addDisplayNodes(world) {
181
+ const graph = world.resource(Graph);
182
+ graph.declare({ name: "display", format: "view" });
183
+ graph.addNode("tonemap", tonemapNode());
184
+ graph.addNode("post/upscale", upscaleNode());
185
+ }
186
+ export {
187
+ addDisplayNodes,
188
+ whiteBalance
189
+ };
@@ -0,0 +1,47 @@
1
+ import { type RenderScaleValue } from './render-scale.js';
2
+ /** What the controller reads from the GPU timer. */
3
+ export interface FrameTimings {
4
+ readonly enabled: boolean;
5
+ readonly frameMs: number;
6
+ readonly frameSamples: number;
7
+ }
8
+ /**
9
+ * Dynamic resolution. With GPU timings it steps straight toward the budget (cost ∝ scale²); on
10
+ * the frame interval alone it drops on overruns, undoes a run of drops that didn't help at all
11
+ * (CPU-bound), and probes upward, backing off after failed probes. Vsync makes the interval move
12
+ * in whole refresh periods, so a single drop often shows nothing: runs of drops are judged together.
13
+ */
14
+ export declare class RenderScaleController {
15
+ private ema;
16
+ private samples;
17
+ private cooldown;
18
+ private under;
19
+ private lastSample;
20
+ /** Frame mode: the measurement and scale before the current run of drops (0: none). */
21
+ private runEma;
22
+ private runFrom;
23
+ /** Frame mode: the interval found CPU-bound; overruns no worse than it are left alone. */
24
+ private cpuEma;
25
+ /** Frame mode: the scale before an upward probe, while it's being verified. */
26
+ private probeFrom;
27
+ private probeAge;
28
+ private probeWait;
29
+ private hold;
30
+ /**
31
+ * One frame. `skip`: pipelines are compiling, so the frame says nothing about steady cost.
32
+ * `displayMs`: one refresh of the display, the budget when `targetMs` is 0.
33
+ */
34
+ update(s: RenderScaleValue, dt: number, timings: FrameTimings, skip: boolean, displayMs?: number): void;
35
+ private gpuStep;
36
+ private frameStep;
37
+ private set;
38
+ private reset;
39
+ }
40
+ export declare const updateRenderScale: import("@aethervtt/shard-core").SystemDef<{
41
+ controller: RenderScaleController;
42
+ }>;
43
+ /**
44
+ * Dynamic resolution (spec 0051): RenderScale in auto mode moves the scale to hold the frame
45
+ * budget. Without it, the scale stays where it's set.
46
+ */
47
+ export declare const dynamicResolutionPlugin: import("@aethervtt/shard-runtime").Plugin;
@@ -0,0 +1,191 @@
1
+ import { defineSystem, Last } from "@aethervtt/shard-core";
2
+ import { DisplayRate, definePlugin, Time } from "@aethervtt/shard-runtime";
3
+ import { Gpu, Graph, RenderSet } from "./plugin.js";
4
+ import {
5
+ HIGHEST,
6
+ LOWEST,
7
+ RenderScale,
8
+ SCALE_STEP,
9
+ STEPS
10
+ } from "./render-scale.js";
11
+ import { extractCameras } from "./view.js";
12
+ const snapDown = (s) => Math.floor(s * STEPS + 1e-6) / STEPS;
13
+ const snap = (s) => Math.round(s * STEPS) / STEPS;
14
+ const clamp = (v, lo, hi) => Math.min(hi, Math.max(lo, v));
15
+ const SETTLE_SAMPLES = 10;
16
+ const COOLDOWN = 0.3;
17
+ class RenderScaleController {
18
+ ema = 0;
19
+ samples = 0;
20
+ cooldown = 0;
21
+ under = 0;
22
+ lastSample = -1;
23
+ /** Frame mode: the measurement and scale before the current run of drops (0: none). */
24
+ runEma = 0;
25
+ runFrom = 0;
26
+ /** Frame mode: the interval found CPU-bound; overruns no worse than it are left alone. */
27
+ cpuEma = 0;
28
+ /** Frame mode: the scale before an upward probe, while it's being verified. */
29
+ probeFrom = 0;
30
+ probeAge = 0;
31
+ probeWait = 3;
32
+ hold = 0;
33
+ /**
34
+ * One frame. `skip`: pipelines are compiling, so the frame says nothing about steady cost.
35
+ * `displayMs`: one refresh of the display, the budget when `targetMs` is 0.
36
+ */
37
+ update(s, dt, timings, skip, displayMs = 1e3 / 60) {
38
+ const lo = clamp(s.min, LOWEST, HIGHEST);
39
+ const hi = clamp(s.max, lo, HIGHEST);
40
+ s.budgetMs = s.targetMs > 0 ? s.targetMs : Math.max(displayMs, 1e3 / Math.max(1, s.maxHz));
41
+ if (s.mode !== "auto" || s.windowViews === 0) {
42
+ s.signal = "none";
43
+ this.reset();
44
+ return;
45
+ }
46
+ const clamped = clamp(snap(s.scale), lo, hi);
47
+ if (clamped !== s.scale) this.set(s, clamped);
48
+ const gpu = timings.enabled && timings.frameSamples > 0;
49
+ s.signal = gpu ? "gpu" : "frame";
50
+ if (this.cooldown > 0) {
51
+ this.cooldown -= dt;
52
+ this.lastSample = timings.frameSamples;
53
+ return;
54
+ }
55
+ if (skip || dt >= 0.2) return;
56
+ let sample;
57
+ if (gpu) {
58
+ if (timings.frameSamples === this.lastSample) return;
59
+ this.lastSample = timings.frameSamples;
60
+ sample = timings.frameMs;
61
+ } else {
62
+ sample = dt * 1e3;
63
+ }
64
+ this.ema = this.samples === 0 ? sample : this.ema * 0.9 + sample * 0.1;
65
+ this.samples++;
66
+ s.measuredMs = this.ema;
67
+ if (this.samples < SETTLE_SAMPLES) return;
68
+ if (gpu) this.gpuStep(s, lo, hi, dt);
69
+ else this.frameStep(s, lo, hi, dt);
70
+ }
71
+ gpuStep(s, lo, hi, dt) {
72
+ const t = s.budgetMs;
73
+ const ema = this.ema;
74
+ if (ema > t * 0.95) {
75
+ this.under = 0;
76
+ const next = Math.max(
77
+ lo,
78
+ Math.min(snapDown(s.scale * Math.sqrt(0.8 * t / ema)), s.scale - SCALE_STEP)
79
+ );
80
+ if (next < s.scale) this.set(s, next);
81
+ } else if (s.scale < hi && ema * ((s.scale + SCALE_STEP) / s.scale) ** 2 < t * 0.8) {
82
+ this.under += dt;
83
+ if (this.under < 1) return;
84
+ const want = snapDown(s.scale * Math.sqrt(0.8 * t / ema));
85
+ const next = Math.min(hi, s.scale + 0.1, Math.max(want, s.scale + SCALE_STEP));
86
+ this.set(s, snap(next));
87
+ } else {
88
+ this.under = 0;
89
+ }
90
+ }
91
+ frameStep(s, lo, hi, dt) {
92
+ const t = s.budgetMs;
93
+ const ema = this.ema;
94
+ if (this.hold > 0) {
95
+ this.hold -= dt;
96
+ return;
97
+ }
98
+ if (this.probeFrom > 0) {
99
+ if (ema > t * 1.15) {
100
+ const back = this.probeFrom;
101
+ this.probeFrom = 0;
102
+ this.probeWait = Math.min(this.probeWait * 2, 30);
103
+ this.set(s, back);
104
+ return;
105
+ }
106
+ this.probeAge += dt;
107
+ if (this.probeAge >= 2) {
108
+ this.probeFrom = 0;
109
+ this.probeWait = 3;
110
+ }
111
+ }
112
+ if (ema > t * 1.15) {
113
+ this.under = 0;
114
+ if (this.cpuEma > 0 && ema < this.cpuEma * 1.1) return;
115
+ if (this.runEma === 0) {
116
+ this.runEma = ema;
117
+ this.runFrom = s.scale;
118
+ } else if (s.scale < this.runFrom && (s.scale <= this.runFrom * 0.71 + 1e-6 || s.scale <= lo) && ema > this.runEma * 0.95) {
119
+ const from = this.runFrom;
120
+ this.cpuEma = this.runEma;
121
+ this.runEma = 0;
122
+ this.set(s, from);
123
+ this.hold = 10;
124
+ return;
125
+ }
126
+ if (s.scale <= lo) return;
127
+ this.probeFrom = 0;
128
+ this.set(s, snap(Math.max(lo, s.scale - 2 * SCALE_STEP)));
129
+ } else if (ema <= t * 1.05) {
130
+ this.runEma = 0;
131
+ this.cpuEma = 0;
132
+ this.under += dt;
133
+ if (this.under >= this.probeWait && s.scale < hi && this.probeFrom === 0) {
134
+ this.probeFrom = s.scale;
135
+ this.probeAge = 0;
136
+ this.set(s, snap(Math.min(hi, s.scale + SCALE_STEP)));
137
+ }
138
+ } else {
139
+ this.under = 0;
140
+ }
141
+ }
142
+ set(s, scale) {
143
+ s.scale = scale;
144
+ this.ema = 0;
145
+ this.samples = 0;
146
+ this.under = 0;
147
+ this.cooldown = COOLDOWN;
148
+ }
149
+ reset() {
150
+ this.ema = 0;
151
+ this.samples = 0;
152
+ this.under = 0;
153
+ this.cooldown = 0;
154
+ this.runEma = 0;
155
+ this.cpuEma = 0;
156
+ this.probeFrom = 0;
157
+ this.probeWait = 3;
158
+ this.hold = 0;
159
+ }
160
+ }
161
+ const updateRenderScale = defineSystem({
162
+ name: "render/update-render-scale",
163
+ description: "Dynamic resolution: moves RenderScale.scale to hold the frame budget (auto mode).",
164
+ setup: () => ({ controller: new RenderScaleController() }),
165
+ run: ({ controller }, world) => {
166
+ const s = world.tryResource(RenderScale);
167
+ const gpu = world.tryResource(Gpu);
168
+ const graph = world.tryResource(Graph);
169
+ if (!s || !gpu || !graph) return;
170
+ const display = world.tryResource(DisplayRate)?.periodMs;
171
+ controller.update(
172
+ s,
173
+ world.resource(Time).delta,
174
+ graph.timer,
175
+ gpu.pipelines.pending > 0,
176
+ display
177
+ );
178
+ }
179
+ });
180
+ const dynamicResolutionPlugin = definePlugin({
181
+ name: "render/dynamic-resolution",
182
+ dependencies: ["render/forward"],
183
+ build(app) {
184
+ app.addSystems(Last, updateRenderScale.inSet(RenderSet.Extract).before(extractCameras));
185
+ }
186
+ });
187
+ export {
188
+ RenderScaleController,
189
+ dynamicResolutionPlugin,
190
+ updateRenderScale
191
+ };
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Image-based lighting and skies (spec 0019): EnvironmentMap and DefaultEnvironment prefiltered on
3
+ * the GPU, Skybox backgrounds, and ProceduralSky (drawn by atmospherePlugin). Without it, cameras
4
+ * are lit by AmbientLight alone.
5
+ */
6
+ export declare const environmentPlugin: import("@aethervtt/shard-runtime").Plugin;
@@ -0,0 +1,44 @@
1
+ import { Last } from "@aethervtt/shard-core";
2
+ import { definePlugin } from "@aethervtt/shard-runtime";
3
+ import {
4
+ DefaultEnvironment,
5
+ describeEnvironment,
6
+ EnvironmentMap,
7
+ ProceduralSky,
8
+ prepareEnvironments,
9
+ runEnvironmentWork,
10
+ Skybox
11
+ } from "./environment.js";
12
+ import { ENVIRONMENT_SHADERS } from "./environment-shaders.js";
13
+ import { ForwardStateResource, isCamera, skyNode } from "./forward.js";
14
+ import { RenderPhase } from "./graph.js";
15
+ import { Graph, RenderDescribers, RenderSet, Shaders } from "./plugin.js";
16
+ import { registerShaders } from "./shaders.js";
17
+ const environmentPlugin = definePlugin({
18
+ name: "render/environment",
19
+ dependencies: ["render/forward"],
20
+ provides: [DefaultEnvironment, EnvironmentMap, ProceduralSky, Skybox],
21
+ build(app) {
22
+ app.world.initResource(DefaultEnvironment);
23
+ app.addSystems(Last, prepareEnvironments.inSet(RenderSet.Prepare));
24
+ },
25
+ ready(app) {
26
+ const world = app.world;
27
+ registerShaders(world.resource(Shaders), ENVIRONMENT_SHADERS);
28
+ world.initResource(RenderDescribers).set("environment", (w) => describeEnvironment(w));
29
+ const graph = world.resource(Graph);
30
+ graph.addNode("environment", {
31
+ kind: "raw",
32
+ phase: RenderPhase.Setup,
33
+ enabled: isCamera,
34
+ // Atmosphere environments bake from the atmosphere LUTs.
35
+ reads: ["atmosphere-luts"],
36
+ writes: ["environment"],
37
+ run: runEnvironmentWork
38
+ });
39
+ graph.addNode("sky", skyNode(world.resource(ForwardStateResource)));
40
+ }
41
+ });
42
+ export {
43
+ environmentPlugin
44
+ };
@@ -0,0 +1,2 @@
1
+ /** WGSL for environments: prefiltering (compute), IBL, and the environment-map background. */
2
+ export declare const ENVIRONMENT_SHADERS: Record<string, string>;