@triforge/render-budget-core 0.1.0
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.
- package/dist/core/BudgetPlanner.d.ts +3 -0
- package/dist/core/BudgetPlanner.d.ts.map +1 -0
- package/dist/core/BudgetPlanner.js +87 -0
- package/dist/core/BudgetPlanner.js.map +1 -0
- package/dist/core/DeviceProfiler.d.ts +7 -0
- package/dist/core/DeviceProfiler.d.ts.map +1 -0
- package/dist/core/DeviceProfiler.js +150 -0
- package/dist/core/DeviceProfiler.js.map +1 -0
- package/dist/core/RenderBudgetSystem.d.ts +24 -0
- package/dist/core/RenderBudgetSystem.d.ts.map +1 -0
- package/dist/core/RenderBudgetSystem.js +105 -0
- package/dist/core/RenderBudgetSystem.js.map +1 -0
- package/dist/core/types.d.ts +61 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +2 -0
- package/dist/core/types.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/package.json +45 -0
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { HardwareProfile, NetworkProfile, RenderBudgetOverride, RenderBudgetPlan } from './types.js';
|
|
2
|
+
export declare function planBudget(hardware: HardwareProfile, network: NetworkProfile, override?: RenderBudgetOverride | null): RenderBudgetPlan;
|
|
3
|
+
//# sourceMappingURL=BudgetPlanner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BudgetPlanner.d.ts","sourceRoot":"","sources":["../../src/core/BudgetPlanner.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EACf,cAAc,EACd,oBAAoB,EACpB,gBAAgB,EAGjB,MAAM,YAAY,CAAA;AAgDnB,wBAAgB,UAAU,CACxB,QAAQ,EAAE,eAAe,EACzB,OAAO,EAAE,cAAc,EACvB,QAAQ,CAAC,EAAE,oBAAoB,GAAG,IAAI,GACrC,gBAAgB,CA0ClB"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
const TIER_ORDER = ['potato', 'low', 'mid', 'high'];
|
|
2
|
+
function tierRank(tier) {
|
|
3
|
+
return TIER_ORDER.indexOf(tier);
|
|
4
|
+
}
|
|
5
|
+
function moreConservative(a, b) {
|
|
6
|
+
return tierRank(a) <= tierRank(b) ? a : b;
|
|
7
|
+
}
|
|
8
|
+
/** High tier is the only variant that requests AVIF — gate it on real decode support, not device class alone. */
|
|
9
|
+
function hardwareTextureTier(hw) {
|
|
10
|
+
if (hw.maxTextureSize >= 8192 && hw.supportsAVIF)
|
|
11
|
+
return 'high';
|
|
12
|
+
if (hw.maxTextureSize >= 4096)
|
|
13
|
+
return 'mid';
|
|
14
|
+
if (hw.maxTextureSize >= 2048)
|
|
15
|
+
return 'low';
|
|
16
|
+
return 'potato';
|
|
17
|
+
}
|
|
18
|
+
function networkTextureTier(net) {
|
|
19
|
+
if (net.downlinkMbps !== null) {
|
|
20
|
+
if (net.downlinkMbps >= 5)
|
|
21
|
+
return 'high';
|
|
22
|
+
if (net.downlinkMbps >= 1.5)
|
|
23
|
+
return 'mid';
|
|
24
|
+
return 'low';
|
|
25
|
+
}
|
|
26
|
+
switch (net.effectiveType) {
|
|
27
|
+
case '4g':
|
|
28
|
+
return 'high';
|
|
29
|
+
case '3g':
|
|
30
|
+
return 'mid';
|
|
31
|
+
case '2g':
|
|
32
|
+
case 'slow-2g':
|
|
33
|
+
return 'low';
|
|
34
|
+
default:
|
|
35
|
+
// Unknown network signal (e.g. effectiveType missing) — don't punish an
|
|
36
|
+
// otherwise-capable device, but don't assume the best case either.
|
|
37
|
+
return 'mid';
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/** Shader complexity depends only on GPU capability proxies — network has no bearing on compile safety. */
|
|
41
|
+
function shaderComplexityTier(hw) {
|
|
42
|
+
if (hw.maxTextureSize >= 4096 && hw.maxContexts >= 8)
|
|
43
|
+
return 'full';
|
|
44
|
+
if (hw.maxTextureSize >= 2048 && hw.maxContexts >= 4)
|
|
45
|
+
return 'reduced';
|
|
46
|
+
return 'baked-only';
|
|
47
|
+
}
|
|
48
|
+
export function planBudget(hardware, network, override) {
|
|
49
|
+
// 1. Explicit manual override wins outright, skips all detection.
|
|
50
|
+
if (override) {
|
|
51
|
+
const hwTier = hardwareTextureTier(hardware);
|
|
52
|
+
return {
|
|
53
|
+
textureTier: override.textureTier ?? hwTier,
|
|
54
|
+
textureFormat: (override.textureTier ?? hwTier) === 'high' && hardware.supportsAVIF ? 'avif' : 'jpg',
|
|
55
|
+
shaderComplexity: override.shaderComplexity ?? shaderComplexityTier(hardware),
|
|
56
|
+
maxConcurrentContexts: override.maxConcurrentContexts ?? hardware.maxContexts,
|
|
57
|
+
source: 'manual-override',
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
const hwTextureTier = hardwareTextureTier(hardware);
|
|
61
|
+
const shaderComplexity = shaderComplexityTier(hardware);
|
|
62
|
+
const maxConcurrentContexts = hardware.maxContexts;
|
|
63
|
+
// 2. saveData is an explicit user/OS data-cost signal — forces the most conservative
|
|
64
|
+
// texture tier regardless of measured hardware/network. Shader complexity and context
|
|
65
|
+
// budget are unaffected: they're about what the device can safely render, not bandwidth.
|
|
66
|
+
if (network.saveData) {
|
|
67
|
+
return {
|
|
68
|
+
textureTier: 'potato',
|
|
69
|
+
textureFormat: 'jpg',
|
|
70
|
+
shaderComplexity,
|
|
71
|
+
maxConcurrentContexts,
|
|
72
|
+
source: 'save-data',
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
// 3. Neither hardware nor network categorically outranks the other for texture
|
|
76
|
+
// selection — whichever is more conservative wins.
|
|
77
|
+
const netTextureTier = networkTextureTier(network);
|
|
78
|
+
const textureTier = moreConservative(hwTextureTier, netTextureTier);
|
|
79
|
+
return {
|
|
80
|
+
textureTier,
|
|
81
|
+
textureFormat: textureTier === 'high' && hardware.supportsAVIF ? 'avif' : 'jpg',
|
|
82
|
+
shaderComplexity,
|
|
83
|
+
maxConcurrentContexts,
|
|
84
|
+
source: 'auto',
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=BudgetPlanner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BudgetPlanner.js","sourceRoot":"","sources":["../../src/core/BudgetPlanner.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { HardwareProbeEnv, HardwareProfile, NetworkProbeEnv, NetworkProfile } from './types.js';
|
|
2
|
+
export declare function profileHardware(env: HardwareProbeEnv): Promise<HardwareProfile>;
|
|
3
|
+
export declare function profileNetwork(env: NetworkProbeEnv): Promise<NetworkProfile>;
|
|
4
|
+
/** Default environment for real browsers — the only place this package touches actual globals. */
|
|
5
|
+
export declare function createBrowserHardwareEnv(): HardwareProbeEnv;
|
|
6
|
+
export declare function createBrowserNetworkEnv(probeAssetUrl: string): NetworkProbeEnv;
|
|
7
|
+
//# sourceMappingURL=DeviceProfiler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DeviceProfiler.d.ts","sourceRoot":"","sources":["../../src/core/DeviceProfiler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AA2DpG,wBAAsB,eAAe,CAAC,GAAG,EAAE,gBAAgB,GAAG,OAAO,CAAC,eAAe,CAAC,CAOrF;AAWD,wBAAsB,cAAc,CAAC,GAAG,EAAE,eAAe,GAAG,OAAO,CAAC,cAAc,CAAC,CAiBlF;AAED,kGAAkG;AAClG,wBAAgB,wBAAwB,IAAI,gBAAgB,CAiC3D;AAED,wBAAgB,uBAAuB,CAAC,aAAa,EAAE,MAAM,GAAG,eAAe,CAkB9E"}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
const FALLBACK_MAX_CONTEXTS = 8;
|
|
2
|
+
const FALLBACK_MAX_TEXTURE_SIZE = 2048;
|
|
3
|
+
/**
|
|
4
|
+
* Hard safety cap on simultaneous probe contexts. Real mobile ceilings are commonly
|
|
5
|
+
* 8-16; this only needs to be high enough to find that ceiling, never high enough to
|
|
6
|
+
* itself become the kind of context-exhaustion bug this package exists to prevent.
|
|
7
|
+
*/
|
|
8
|
+
const MAX_CONTEXT_PROBE_ATTEMPTS = 20;
|
|
9
|
+
/**
|
|
10
|
+
* Probes the real number of simultaneous WebGL contexts this browser tolerates by
|
|
11
|
+
* creating scratch contexts until creation fails or the safety cap is hit, then
|
|
12
|
+
* releasing all of them. Must run before any other WebGL consumer (renderers,
|
|
13
|
+
* carousels, etc.) mounts — probing after the fact would compete with real contexts
|
|
14
|
+
* and could itself trigger the context loss this package exists to prevent.
|
|
15
|
+
*
|
|
16
|
+
* Reads MAX_TEXTURE_SIZE from the first context created in this same pass, rather
|
|
17
|
+
* than creating a separate context afterward: real WEBGL_lose_context releases are
|
|
18
|
+
* asynchronous, so a context "lost" here isn't guaranteed to free its slot in time
|
|
19
|
+
* for a second, later creation — that would make the texture-size read spuriously
|
|
20
|
+
* fail right after a context-count probe that used the whole budget.
|
|
21
|
+
*/
|
|
22
|
+
function probeContextsAndTextureSize(env) {
|
|
23
|
+
const held = [];
|
|
24
|
+
let maxTextureSize = FALLBACK_MAX_TEXTURE_SIZE;
|
|
25
|
+
try {
|
|
26
|
+
for (let i = 0; i < MAX_CONTEXT_PROBE_ATTEMPTS; i++) {
|
|
27
|
+
const gl = env.createWebGLContext();
|
|
28
|
+
if (!gl)
|
|
29
|
+
break;
|
|
30
|
+
held.push(gl);
|
|
31
|
+
if (held.length === 1) {
|
|
32
|
+
try {
|
|
33
|
+
const size = gl.getParameter(gl.MAX_TEXTURE_SIZE);
|
|
34
|
+
if (typeof size === 'number' && Number.isFinite(size) && size > 0)
|
|
35
|
+
maxTextureSize = size;
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
// keep the fallback
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return {
|
|
43
|
+
maxContexts: held.length > 0 ? held.length : FALLBACK_MAX_CONTEXTS,
|
|
44
|
+
maxTextureSize,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
finally {
|
|
48
|
+
if (env.loseContext) {
|
|
49
|
+
for (const gl of held) {
|
|
50
|
+
try {
|
|
51
|
+
env.loseContext(gl);
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
// best-effort cleanup only — a failure to release a scratch context
|
|
55
|
+
// must never surface as a profiling failure
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
export async function profileHardware(env) {
|
|
62
|
+
const { maxContexts, maxTextureSize } = probeContextsAndTextureSize(env);
|
|
63
|
+
const [supportsAVIF, supportsWebP] = await Promise.all([
|
|
64
|
+
env.probeImageDecode('avif').catch(() => false),
|
|
65
|
+
env.probeImageDecode('webp').catch(() => false),
|
|
66
|
+
]);
|
|
67
|
+
return { maxContexts, maxTextureSize, supportsAVIF, supportsWebP };
|
|
68
|
+
}
|
|
69
|
+
const PROBE_DOWNLOAD_THRESHOLD_MBPS = { fast: 5, medium: 1.5 };
|
|
70
|
+
function classifyDownlink(mbps) {
|
|
71
|
+
if (mbps === null)
|
|
72
|
+
return 'unknown';
|
|
73
|
+
if (mbps >= PROBE_DOWNLOAD_THRESHOLD_MBPS.fast)
|
|
74
|
+
return '4g';
|
|
75
|
+
if (mbps >= PROBE_DOWNLOAD_THRESHOLD_MBPS.medium)
|
|
76
|
+
return '3g';
|
|
77
|
+
return '2g';
|
|
78
|
+
}
|
|
79
|
+
export async function profileNetwork(env) {
|
|
80
|
+
const conn = env.connection;
|
|
81
|
+
if (conn && typeof conn.effectiveType === 'string') {
|
|
82
|
+
return {
|
|
83
|
+
effectiveType: conn.effectiveType,
|
|
84
|
+
downlinkMbps: typeof conn.downlink === 'number' ? conn.downlink : null,
|
|
85
|
+
saveData: conn.saveData === true,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
// No Network Information API (e.g. Safari/iOS) — fall back to a timed probe download.
|
|
89
|
+
const downlinkMbps = await env.probeDownloadMbps().catch(() => null);
|
|
90
|
+
return {
|
|
91
|
+
effectiveType: classifyDownlink(downlinkMbps),
|
|
92
|
+
downlinkMbps,
|
|
93
|
+
saveData: false,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
/** Default environment for real browsers — the only place this package touches actual globals. */
|
|
97
|
+
export function createBrowserHardwareEnv() {
|
|
98
|
+
return {
|
|
99
|
+
createWebGLContext() {
|
|
100
|
+
try {
|
|
101
|
+
const canvas = document.createElement('canvas');
|
|
102
|
+
return (canvas.getContext('webgl2') ??
|
|
103
|
+
canvas.getContext('webgl') ??
|
|
104
|
+
null);
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
loseContext(gl) {
|
|
111
|
+
const ext = gl.getExtension('WEBGL_lose_context');
|
|
112
|
+
ext?.loseContext();
|
|
113
|
+
},
|
|
114
|
+
async probeImageDecode(format) {
|
|
115
|
+
// 1x1 sample images, smallest valid encode of each format.
|
|
116
|
+
const samples = {
|
|
117
|
+
avif: 'data:image/avif;base64,AAAAHGZ0eXBhdmlmAAAAAGF2aWZtaWYxbWlhZgAAAOptZXRhAAAAAAAAACFoZGxyAAAAAAAAAABwaWN0AAAAAAAAAAAAAAAAAAAAAA5waXRtAAAAAAABAAAAImlsb2MAAAAARAAAAgABAAEAAAEAAAAeAAABAAABAAAAKGlpbmYAAAAAAAEAAAAaaW5mZQIAAAAAAQAAYXYwMUltYWdlAAAAAGppcHJwAAAAWGlwY28AAAAUaXNwZQAAAAAAAAABAAAAAQAAAAxhdjFDgQAMAAAAABNjb2xybmNseAACAAIABoAAAAAXaXBtYQAAAAAAAAABAAEEAQIDhAAAACBtZGF0EgAKCBgABogQEDQgMgkQAAAAB8dSLfI=',
|
|
118
|
+
webp: 'data:image/webp;base64,UklGRhoAAABXRUJQVlA4TA0AAAAvAAAAEAcQERGIiP4HAA==',
|
|
119
|
+
};
|
|
120
|
+
return new Promise((resolve) => {
|
|
121
|
+
const img = new Image();
|
|
122
|
+
img.onload = () => resolve(img.width > 0 && img.height > 0);
|
|
123
|
+
img.onerror = () => resolve(false);
|
|
124
|
+
img.src = samples[format];
|
|
125
|
+
});
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
export function createBrowserNetworkEnv(probeAssetUrl) {
|
|
130
|
+
const nav = typeof navigator !== 'undefined' ? navigator : undefined;
|
|
131
|
+
return {
|
|
132
|
+
connection: nav?.connection,
|
|
133
|
+
async probeDownloadMbps() {
|
|
134
|
+
try {
|
|
135
|
+
const start = performance.now();
|
|
136
|
+
const res = await fetch(probeAssetUrl, { cache: 'no-store' });
|
|
137
|
+
const blob = await res.blob();
|
|
138
|
+
const seconds = (performance.now() - start) / 1000;
|
|
139
|
+
if (seconds <= 0)
|
|
140
|
+
return null;
|
|
141
|
+
const bits = blob.size * 8;
|
|
142
|
+
return bits / seconds / 1000000;
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=DeviceProfiler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DeviceProfiler.js","sourceRoot":"","sources":["../../src/core/DeviceProfiler.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { BudgetStorage, HardwareProbeEnv, NetworkProbeEnv, RenderBudgetOverride, RenderBudgetPlan } from './types.js';
|
|
2
|
+
export interface RenderBudgetSystemConfig {
|
|
3
|
+
/** Let end users persist an explicit quality choice (e.g. a settings panel). Default true. */
|
|
4
|
+
allowUserOverride?: boolean;
|
|
5
|
+
/**
|
|
6
|
+
* Let a `?forceQuality=` query param force a tier for testing. Default false (opt-in per
|
|
7
|
+
* consumer, e.g. `!isProd`). Can only force a WORSE tier than auto-detected — never better —
|
|
8
|
+
* so a real visitor can't report an unreproducible performance bug by forcing a higher tier
|
|
9
|
+
* than their device actually supports.
|
|
10
|
+
*/
|
|
11
|
+
allowDevOverride?: boolean;
|
|
12
|
+
/** Small fixed-size asset used to time-probe download speed where navigator.connection is unavailable. */
|
|
13
|
+
probeAssetUrl?: string;
|
|
14
|
+
storage?: BudgetStorage;
|
|
15
|
+
hardwareEnv?: HardwareProbeEnv;
|
|
16
|
+
networkEnv?: NetworkProbeEnv;
|
|
17
|
+
/** Override how the dev query param is read — for tests; defaults to location.search. */
|
|
18
|
+
readDevOverrideParam?: () => string | null;
|
|
19
|
+
}
|
|
20
|
+
export declare function getUserOverride(storage: BudgetStorage): RenderBudgetOverride | null;
|
|
21
|
+
export declare function setUserOverride(storage: BudgetStorage, override: RenderBudgetOverride): void;
|
|
22
|
+
export declare function clearUserOverride(storage: BudgetStorage): void;
|
|
23
|
+
export declare function init(config?: RenderBudgetSystemConfig): Promise<RenderBudgetPlan>;
|
|
24
|
+
//# sourceMappingURL=RenderBudgetSystem.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RenderBudgetSystem.d.ts","sourceRoot":"","sources":["../../src/core/RenderBudgetSystem.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,aAAa,EACb,gBAAgB,EAEhB,eAAe,EACf,oBAAoB,EACpB,gBAAgB,EAEjB,MAAM,YAAY,CAAA;AAYnB,MAAM,WAAW,wBAAwB;IACvC,8FAA8F;IAC9F,iBAAiB,CAAC,EAAE,OAAO,CAAA;IAC3B;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAA;IAC1B,0GAA0G;IAC1G,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,OAAO,CAAC,EAAE,aAAa,CAAA;IACvB,WAAW,CAAC,EAAE,gBAAgB,CAAA;IAC9B,UAAU,CAAC,EAAE,eAAe,CAAA;IAC5B,yFAAyF;IACzF,oBAAoB,CAAC,EAAE,MAAM,MAAM,GAAG,IAAI,CAAA;CAC3C;AAgCD,wBAAgB,eAAe,CAAC,OAAO,EAAE,aAAa,GAAG,oBAAoB,GAAG,IAAI,CAQnF;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,oBAAoB,GAAG,IAAI,CAE5F;AAED,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI,CAE9D;AAMD,wBAAsB,IAAI,CAAC,MAAM,GAAE,wBAA6B,GAAG,OAAO,CAAC,gBAAgB,CAAC,CA+C3F"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { createBrowserHardwareEnv, createBrowserNetworkEnv, profileHardware, profileNetwork } from './DeviceProfiler.js';
|
|
2
|
+
import { planBudget } from './BudgetPlanner.js';
|
|
3
|
+
/** Bump when profileHardware()'s probing logic changes in a way that invalidates old cached profiles. */
|
|
4
|
+
const HARDWARE_CACHE_VERSION = 1;
|
|
5
|
+
const HARDWARE_CACHE_KEY = 'triforge-render-budget:hardware';
|
|
6
|
+
const USER_OVERRIDE_KEY = 'triforge-render-budget:user-override';
|
|
7
|
+
const TIER_ORDER = ['potato', 'low', 'mid', 'high'];
|
|
8
|
+
function tierRank(tier) {
|
|
9
|
+
return TIER_ORDER.indexOf(tier);
|
|
10
|
+
}
|
|
11
|
+
function defaultStorage() {
|
|
12
|
+
if (typeof localStorage === 'undefined') {
|
|
13
|
+
// No persistent storage available (e.g. a non-browser test host without one injected) —
|
|
14
|
+
// degrade to a no-op so callers still get a working (just non-persistent) system.
|
|
15
|
+
return { getItem: () => null, setItem: () => { } };
|
|
16
|
+
}
|
|
17
|
+
return localStorage;
|
|
18
|
+
}
|
|
19
|
+
function defaultReadDevOverrideParam() {
|
|
20
|
+
if (typeof location === 'undefined')
|
|
21
|
+
return null;
|
|
22
|
+
return new URLSearchParams(location.search).get('forceQuality');
|
|
23
|
+
}
|
|
24
|
+
function readCachedHardware(storage) {
|
|
25
|
+
const raw = storage.getItem(HARDWARE_CACHE_KEY);
|
|
26
|
+
if (!raw)
|
|
27
|
+
return null;
|
|
28
|
+
try {
|
|
29
|
+
const parsed = JSON.parse(raw);
|
|
30
|
+
if (parsed.version !== HARDWARE_CACHE_VERSION)
|
|
31
|
+
return null;
|
|
32
|
+
return parsed.profile;
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function writeCachedHardware(storage, profile) {
|
|
39
|
+
storage.setItem(HARDWARE_CACHE_KEY, JSON.stringify({ version: HARDWARE_CACHE_VERSION, profile }));
|
|
40
|
+
}
|
|
41
|
+
export function getUserOverride(storage) {
|
|
42
|
+
const raw = storage.getItem(USER_OVERRIDE_KEY);
|
|
43
|
+
if (!raw)
|
|
44
|
+
return null;
|
|
45
|
+
try {
|
|
46
|
+
return JSON.parse(raw);
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
export function setUserOverride(storage, override) {
|
|
53
|
+
storage.setItem(USER_OVERRIDE_KEY, JSON.stringify(override));
|
|
54
|
+
}
|
|
55
|
+
export function clearUserOverride(storage) {
|
|
56
|
+
storage.setItem(USER_OVERRIDE_KEY, '');
|
|
57
|
+
}
|
|
58
|
+
function isValidTextureTier(value) {
|
|
59
|
+
return value === 'high' || value === 'mid' || value === 'low' || value === 'potato';
|
|
60
|
+
}
|
|
61
|
+
export async function init(config = {}) {
|
|
62
|
+
const allowUserOverride = config.allowUserOverride ?? true;
|
|
63
|
+
const allowDevOverride = config.allowDevOverride ?? false;
|
|
64
|
+
const storage = config.storage ?? defaultStorage();
|
|
65
|
+
const hardwareEnv = config.hardwareEnv ?? createBrowserHardwareEnv();
|
|
66
|
+
const networkEnv = config.networkEnv ?? createBrowserNetworkEnv(config.probeAssetUrl ?? '');
|
|
67
|
+
const readDevOverrideParam = config.readDevOverrideParam ?? defaultReadDevOverrideParam;
|
|
68
|
+
// Step 1 (cheap, synchronous): user override wins outright, skips all detection.
|
|
69
|
+
if (allowUserOverride) {
|
|
70
|
+
const userOverride = getUserOverride(storage);
|
|
71
|
+
if (userOverride) {
|
|
72
|
+
// Still need a hardware profile to fill in fields the override doesn't specify
|
|
73
|
+
// (e.g. a user who only picked a texture tier still needs a real shaderComplexity).
|
|
74
|
+
const hardware = readCachedHardware(storage) ?? (await profileHardware(hardwareEnv));
|
|
75
|
+
writeCachedHardware(storage, hardware);
|
|
76
|
+
const network = await profileNetwork(networkEnv);
|
|
77
|
+
return planBudget(hardware, network, userOverride);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
// Step 2 & 3: hardware (cached) and network (never cached — changes visit to visit)
|
|
81
|
+
// probe concurrently; neither depends on the other's result.
|
|
82
|
+
const cachedHardware = readCachedHardware(storage);
|
|
83
|
+
const [hardware, network] = await Promise.all([
|
|
84
|
+
cachedHardware ? Promise.resolve(cachedHardware) : profileHardware(hardwareEnv),
|
|
85
|
+
profileNetwork(networkEnv),
|
|
86
|
+
]);
|
|
87
|
+
if (!cachedHardware)
|
|
88
|
+
writeCachedHardware(storage, hardware);
|
|
89
|
+
// Step 4: saveData / min(hardware,network) precedence, via planBudget.
|
|
90
|
+
const autoPlan = planBudget(hardware, network, null);
|
|
91
|
+
// Dev override: only allowed to make the tier WORSE than what auto-detection found.
|
|
92
|
+
if (allowDevOverride) {
|
|
93
|
+
const forced = readDevOverrideParam();
|
|
94
|
+
if (isValidTextureTier(forced) && tierRank(forced) < tierRank(autoPlan.textureTier)) {
|
|
95
|
+
return {
|
|
96
|
+
...autoPlan,
|
|
97
|
+
textureTier: forced,
|
|
98
|
+
textureFormat: forced === 'high' && hardware.supportsAVIF ? 'avif' : 'jpg',
|
|
99
|
+
source: 'manual-override',
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return autoPlan;
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=RenderBudgetSystem.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RenderBudgetSystem.js","sourceRoot":"","sources":["../../src/core/RenderBudgetSystem.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAE,uBAAuB,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AACxH,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAA;AAW/C,yGAAyG;AACzG,MAAM,sBAAsB,GAAG,CAAC,CAAA;AAChC,MAAM,kBAAkB,GAAG,iCAAiC,CAAA;AAC5D,MAAM,iBAAiB,GAAG,sCAAsC,CAAA;AAEhE,MAAM,UAAU,GAAyB,CAAC,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,CAAA;AACzE,SAAS,QAAQ,CAAC,IAAwB;IACxC,OAAO,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;AACjC,CAAC;AAqBD,SAAS,cAAc;IACrB,IAAI,OAAO,YAAY,KAAK,WAAW,EAAE,CAAC;QACxC,wFAAwF;QACxF,kFAAkF;QAClF,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,GAAE,CAAC,EAAE,CAAA;IACnD,CAAC;IACD,OAAO,YAAY,CAAA;AACrB,CAAC;AAED,SAAS,2BAA2B;IAClC,IAAI,OAAO,QAAQ,KAAK,WAAW;QAAE,OAAO,IAAI,CAAA;IAChD,OAAO,IAAI,eAAe,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,cAAc,CAAC,CAAA;AACjE,CAAC;AAED,SAAS,kBAAkB,CAAC,OAAsB;IAChD,MAAM,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,kBAAkB,CAAC,CAAA;IAC/C,IAAI,CAAC,GAAG;QAAE,OAAO,IAAI,CAAA;IACrB,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAkD,CAAA;QAC/E,IAAI,MAAM,CAAC,OAAO,KAAK,sBAAsB;YAAE,OAAO,IAAI,CAAA;QAC1D,OAAO,MAAM,CAAC,OAAO,CAAA;IACvB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAA;IACb,CAAC;AACH,CAAC;AAED,SAAS,mBAAmB,CAAC,OAAsB,EAAE,OAAwB;IAC3E,OAAO,CAAC,OAAO,CAAC,kBAAkB,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,sBAAsB,EAAE,OAAO,EAAE,CAAC,CAAC,CAAA;AACnG,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,OAAsB;IACpD,MAAM,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAA;IAC9C,IAAI,CAAC,GAAG;QAAE,OAAO,IAAI,CAAA;IACrB,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAyB,CAAA;IAChD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAA;IACb,CAAC;AACH,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,OAAsB,EAAE,QAA8B;IACpF,OAAO,CAAC,OAAO,CAAC,iBAAiB,EAAE,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAA;AAC9D,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,OAAsB;IACtD,OAAO,CAAC,OAAO,CAAC,iBAAiB,EAAE,EAAE,CAAC,CAAA;AACxC,CAAC;AAED,SAAS,kBAAkB,CAAC,KAAoB;IAC9C,OAAO,KAAK,KAAK,MAAM,IAAI,KAAK,KAAK,KAAK,IAAI,KAAK,KAAK,KAAK,IAAI,KAAK,KAAK,QAAQ,CAAA;AACrF,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,IAAI,CAAC,SAAmC,EAAE;IAC9D,MAAM,iBAAiB,GAAG,MAAM,CAAC,iBAAiB,IAAI,IAAI,CAAA;IAC1D,MAAM,gBAAgB,GAAG,MAAM,CAAC,gBAAgB,IAAI,KAAK,CAAA;IACzD,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,IAAI,cAAc,EAAE,CAAA;IAClD,MAAM,WAAW,GAAG,MAAM,CAAC,WAAW,IAAI,wBAAwB,EAAE,CAAA;IACpE,MAAM,UAAU,GAAG,MAAM,CAAC,UAAU,IAAI,uBAAuB,CAAC,MAAM,CAAC,aAAa,IAAI,EAAE,CAAC,CAAA;IAC3F,MAAM,oBAAoB,GAAG,MAAM,CAAC,oBAAoB,IAAI,2BAA2B,CAAA;IAEvF,iFAAiF;IACjF,IAAI,iBAAiB,EAAE,CAAC;QACtB,MAAM,YAAY,GAAG,eAAe,CAAC,OAAO,CAAC,CAAA;QAC7C,IAAI,YAAY,EAAE,CAAC;YACjB,+EAA+E;YAC/E,oFAAoF;YACpF,MAAM,QAAQ,GAAG,kBAAkB,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,eAAe,CAAC,WAAW,CAAC,CAAC,CAAA;YACpF,mBAAmB,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAA;YACtC,MAAM,OAAO,GAAG,MAAM,cAAc,CAAC,UAAU,CAAC,CAAA;YAChD,OAAO,UAAU,CAAC,QAAQ,EAAE,OAAO,EAAE,YAAY,CAAC,CAAA;QACpD,CAAC;IACH,CAAC;IAED,oFAAoF;IACpF,6DAA6D;IAC7D,MAAM,cAAc,GAAG,kBAAkB,CAAC,OAAO,CAAC,CAAA;IAClD,MAAM,CAAC,QAAQ,EAAE,OAAO,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;QAC5C,cAAc,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,eAAe,CAAC,WAAW,CAAC;QAC/E,cAAc,CAAC,UAAU,CAAC;KAC3B,CAAC,CAAA;IACF,IAAI,CAAC,cAAc;QAAE,mBAAmB,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAA;IAE3D,uEAAuE;IACvE,MAAM,QAAQ,GAAG,UAAU,CAAC,QAAQ,EAAE,OAAO,EAAE,IAAI,CAAC,CAAA;IAEpD,oFAAoF;IACpF,IAAI,gBAAgB,EAAE,CAAC;QACrB,MAAM,MAAM,GAAG,oBAAoB,EAAE,CAAA;QACrC,IAAI,kBAAkB,CAAC,MAAM,CAAC,IAAI,QAAQ,CAAC,MAAM,CAAC,GAAG,QAAQ,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;YACpF,OAAO;gBACL,GAAG,QAAQ;gBACX,WAAW,EAAE,MAAM;gBACnB,aAAa,EAAE,MAAM,KAAK,MAAM,IAAI,QAAQ,CAAC,YAAY,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK;gBAC1E,MAAM,EAAE,iBAAiB;aAC1B,CAAA;QACH,CAAC;IACH,CAAC;IAED,OAAO,QAAQ,CAAA;AACjB,CAAC"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
export type TextureVariantTier = 'high' | 'mid' | 'low' | 'potato';
|
|
2
|
+
export type ShaderComplexityTier = 'full' | 'reduced' | 'baked-only';
|
|
3
|
+
export interface HardwareProfile {
|
|
4
|
+
/** Highest number of simultaneous WebGL contexts the browser tolerated before losing one. */
|
|
5
|
+
maxContexts: number;
|
|
6
|
+
/** gl.getParameter(gl.MAX_TEXTURE_SIZE) from a real context, or a conservative fallback. */
|
|
7
|
+
maxTextureSize: number;
|
|
8
|
+
/** Whether the browser can actually decode an AVIF image (probed, not sniffed from UA). */
|
|
9
|
+
supportsAVIF: boolean;
|
|
10
|
+
/** Whether the browser can actually decode a WebP image. */
|
|
11
|
+
supportsWebP: boolean;
|
|
12
|
+
}
|
|
13
|
+
export interface NetworkProfile {
|
|
14
|
+
/** 'slow-2g' | '2g' | '3g' | '4g' | 'unknown' — from navigator.connection.effectiveType, or derived from the probe download. */
|
|
15
|
+
effectiveType: string;
|
|
16
|
+
/** Estimated downlink in Mbps, if known. */
|
|
17
|
+
downlinkMbps: number | null;
|
|
18
|
+
/** OS/browser-level explicit data-saver signal (navigator.connection.saveData). */
|
|
19
|
+
saveData: boolean;
|
|
20
|
+
}
|
|
21
|
+
export interface RenderBudgetPlan {
|
|
22
|
+
/** Which texture asset variant to request. Combines resolution + format (see textureFormat). */
|
|
23
|
+
textureTier: TextureVariantTier;
|
|
24
|
+
/** Format to prefer for the resolved textureTier. */
|
|
25
|
+
textureFormat: 'avif' | 'jpg';
|
|
26
|
+
/** How complex a shader graph is safe to compile — governs procedural-vs-baked material choices. */
|
|
27
|
+
shaderComplexity: ShaderComplexityTier;
|
|
28
|
+
/** Max simultaneous WebGL contexts a consumer (e.g. a context-pool) should allow. */
|
|
29
|
+
maxConcurrentContexts: number;
|
|
30
|
+
/** Source of the final decision, for debugging/telemetry. */
|
|
31
|
+
source: 'manual-override' | 'save-data' | 'auto';
|
|
32
|
+
}
|
|
33
|
+
export interface RenderBudgetOverride {
|
|
34
|
+
textureTier?: TextureVariantTier;
|
|
35
|
+
shaderComplexity?: ShaderComplexityTier;
|
|
36
|
+
maxConcurrentContexts?: number;
|
|
37
|
+
}
|
|
38
|
+
/** Injectable seam: anything that can decide "hardware-tier-worthy" without touching real globals in tests. */
|
|
39
|
+
export interface HardwareProbeEnv {
|
|
40
|
+
/** Create a WebGL context on a scratch canvas; return null if creation fails. */
|
|
41
|
+
createWebGLContext(): WebGLRenderingContext | WebGL2RenderingContext | null;
|
|
42
|
+
/** Release/lose a previously created context, if the environment can simulate that (tests only). */
|
|
43
|
+
loseContext?(gl: WebGLRenderingContext | WebGL2RenderingContext): void;
|
|
44
|
+
/** Attempt to decode a tiny sample image of the given format; resolve true/false, never throw. */
|
|
45
|
+
probeImageDecode(format: 'avif' | 'webp'): Promise<boolean>;
|
|
46
|
+
}
|
|
47
|
+
export interface NetworkProbeEnv {
|
|
48
|
+
/** Mirrors navigator.connection — undefined where the Network Information API isn't available (e.g. Safari). */
|
|
49
|
+
connection?: {
|
|
50
|
+
effectiveType?: string;
|
|
51
|
+
downlink?: number;
|
|
52
|
+
saveData?: boolean;
|
|
53
|
+
};
|
|
54
|
+
/** Fallback timed download of a small fixed-size asset; resolves measured Mbps, or null if it fails. */
|
|
55
|
+
probeDownloadMbps(): Promise<number | null>;
|
|
56
|
+
}
|
|
57
|
+
export interface BudgetStorage {
|
|
58
|
+
getItem(key: string): string | null;
|
|
59
|
+
setItem(key: string, value: string): void;
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,kBAAkB,GAAG,MAAM,GAAG,KAAK,GAAG,KAAK,GAAG,QAAQ,CAAA;AAClE,MAAM,MAAM,oBAAoB,GAAG,MAAM,GAAG,SAAS,GAAG,YAAY,CAAA;AAEpE,MAAM,WAAW,eAAe;IAC9B,6FAA6F;IAC7F,WAAW,EAAE,MAAM,CAAA;IACnB,4FAA4F;IAC5F,cAAc,EAAE,MAAM,CAAA;IACtB,2FAA2F;IAC3F,YAAY,EAAE,OAAO,CAAA;IACrB,4DAA4D;IAC5D,YAAY,EAAE,OAAO,CAAA;CACtB;AAED,MAAM,WAAW,cAAc;IAC7B,gIAAgI;IAChI,aAAa,EAAE,MAAM,CAAA;IACrB,4CAA4C;IAC5C,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,mFAAmF;IACnF,QAAQ,EAAE,OAAO,CAAA;CAClB;AAED,MAAM,WAAW,gBAAgB;IAC/B,gGAAgG;IAChG,WAAW,EAAE,kBAAkB,CAAA;IAC/B,qDAAqD;IACrD,aAAa,EAAE,MAAM,GAAG,KAAK,CAAA;IAC7B,oGAAoG;IACpG,gBAAgB,EAAE,oBAAoB,CAAA;IACtC,qFAAqF;IACrF,qBAAqB,EAAE,MAAM,CAAA;IAC7B,6DAA6D;IAC7D,MAAM,EAAE,iBAAiB,GAAG,WAAW,GAAG,MAAM,CAAA;CACjD;AAED,MAAM,WAAW,oBAAoB;IACnC,WAAW,CAAC,EAAE,kBAAkB,CAAA;IAChC,gBAAgB,CAAC,EAAE,oBAAoB,CAAA;IACvC,qBAAqB,CAAC,EAAE,MAAM,CAAA;CAC/B;AAED,+GAA+G;AAC/G,MAAM,WAAW,gBAAgB;IAC/B,iFAAiF;IACjF,kBAAkB,IAAI,qBAAqB,GAAG,sBAAsB,GAAG,IAAI,CAAA;IAC3E,oGAAoG;IACpG,WAAW,CAAC,CAAC,EAAE,EAAE,qBAAqB,GAAG,sBAAsB,GAAG,IAAI,CAAA;IACtE,kGAAkG;IAClG,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;CAC5D;AAED,MAAM,WAAW,eAAe;IAC9B,gHAAgH;IAChH,UAAU,CAAC,EAAE;QACX,aAAa,CAAC,EAAE,MAAM,CAAA;QACtB,QAAQ,CAAC,EAAE,MAAM,CAAA;QACjB,QAAQ,CAAC,EAAE,OAAO,CAAA;KACnB,CAAA;IACD,wGAAwG;IACxG,iBAAiB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAA;CAC5C;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;IACnC,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;CAC1C"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":""}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export type { HardwareProfile, NetworkProfile, RenderBudgetPlan, RenderBudgetOverride, TextureVariantTier, ShaderComplexityTier, HardwareProbeEnv, NetworkProbeEnv, BudgetStorage, } from './core/types.js';
|
|
2
|
+
export { profileHardware, profileNetwork, createBrowserHardwareEnv, createBrowserNetworkEnv, } from './core/DeviceProfiler.js';
|
|
3
|
+
export { planBudget } from './core/BudgetPlanner.js';
|
|
4
|
+
export { init as initRenderBudget, getUserOverride, setUserOverride, clearUserOverride, } from './core/RenderBudgetSystem.js';
|
|
5
|
+
export type { RenderBudgetSystemConfig } from './core/RenderBudgetSystem.js';
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,eAAe,EACf,cAAc,EACd,gBAAgB,EAChB,oBAAoB,EACpB,kBAAkB,EAClB,oBAAoB,EACpB,gBAAgB,EAChB,eAAe,EACf,aAAa,GACd,MAAM,iBAAiB,CAAA;AAExB,OAAO,EACL,eAAe,EACf,cAAc,EACd,wBAAwB,EACxB,uBAAuB,GACxB,MAAM,0BAA0B,CAAA;AAEjC,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAA;AAEpD,OAAO,EACL,IAAI,IAAI,gBAAgB,EACxB,eAAe,EACf,eAAe,EACf,iBAAiB,GAClB,MAAM,8BAA8B,CAAA;AACrC,YAAY,EAAE,wBAAwB,EAAE,MAAM,8BAA8B,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { profileHardware, profileNetwork, createBrowserHardwareEnv, createBrowserNetworkEnv, } from './core/DeviceProfiler.js';
|
|
2
|
+
export { planBudget } from './core/BudgetPlanner.js';
|
|
3
|
+
export { init as initRenderBudget, getUserOverride, setUserOverride, clearUserOverride, } from './core/RenderBudgetSystem.js';
|
|
4
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAYA,OAAO,EACL,eAAe,EACf,cAAc,EACd,wBAAwB,EACxB,uBAAuB,GACxB,MAAM,0BAA0B,CAAA;AAEjC,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAA;AAEpD,OAAO,EACL,IAAI,IAAI,gBAAgB,EACxB,eAAe,EACf,eAAe,EACf,iBAAiB,GAClB,MAAM,8BAA8B,CAAA"}
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@triforge/render-budget-core",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Engine-agnostic device/network profiling and render-quality budget planning — texture tier, shader complexity, context-count policy from real runtime probes, not heuristics",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist",
|
|
10
|
+
"package.json"
|
|
11
|
+
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "tsc",
|
|
14
|
+
"dev": "tsc --watch",
|
|
15
|
+
"test": "node test/run-tests.js",
|
|
16
|
+
"bench": "node bench/run-bench.js"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"render-budget",
|
|
20
|
+
"device-profiler",
|
|
21
|
+
"webgl",
|
|
22
|
+
"texture-variants",
|
|
23
|
+
"three.js"
|
|
24
|
+
],
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"typescript": "^5.3.0"
|
|
28
|
+
},
|
|
29
|
+
"author": "Aftab Ibrahim Kazi",
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "https://github.com/AftabIbrahimKazi/triforge.git",
|
|
33
|
+
"directory": "render-budget-core"
|
|
34
|
+
},
|
|
35
|
+
"homepage": "https://github.com/AftabIbrahimKazi/triforge#readme",
|
|
36
|
+
"publishConfig": {
|
|
37
|
+
"access": "public"
|
|
38
|
+
},
|
|
39
|
+
"exports": {
|
|
40
|
+
".": {
|
|
41
|
+
"import": "./dist/index.js",
|
|
42
|
+
"types": "./dist/index.d.ts"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|