Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion dist/spector.bundle.js

Large diffs are not rendered by default.

21 changes: 21 additions & 0 deletions dist/spector.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -589,6 +589,27 @@ export declare class Spector {
spyCanvases(): void;
spyCanvas(canvas: HTMLCanvasElement | OffscreenCanvas): void;
getAvailableContexts(): IAvailableContext[];
/**
* Simulate slow asynchronous shader compilation.
*
* While a non-zero delay is set, any program queried via
* `getProgramParameter(program, COMPLETION_STATUS_KHR)` reports `false`
* (still compiling) for `delayMs` milliseconds after it is linked, then
* reports its real completion status. This emulates a slow GPU driver so
* loading screens, shader fallbacks, and hitch handling can be tested —
* similar to the CPU/network throttling in browser developer tools.
*
* The throttle is installed globally on the WebGL prototypes, so it applies
* to every context on the page without needing canvas spying to be active.
* Only programs linked after the throttle is installed are affected.
*
* @param delayMs - Delay in milliseconds. Use `0` to disable.
*/
setShaderCompileDelay(delayMs: number): void;
/** Disable the simulated shader-compile delay. */
clearShaderCompileDelay(): void;
/** The current simulated shader-compile delay in milliseconds (0 = disabled). */
getShaderCompileDelay(): number;
captureCanvas(canvas: HTMLCanvasElement | OffscreenCanvas, commandCount?: number, quickCapture?: boolean, fullCapture?: boolean): void;
captureContext(context: WebGLRenderingContexts, commandCount?: number, quickCapture?: boolean, fullCapture?: boolean): void;
captureXRContext(commandCount?: number, quickCapture?: boolean, fullCapture?: boolean): void;
Expand Down
2 changes: 1 addition & 1 deletion dist/spector.worker.bundle.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions documentation/changeLogs.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
Please, find below the per release summary of the contribution added to the project per version. Each of the listed versions is having its corresponding tag in the repo.

## v0.9.33
* Add Parallel Shader Compilation Throttling
* Add [Texture Storei Information](https://github.com/BabylonJS/Spector.js/issues/80)
* Add [MCP Support](https://github.com/BabylonJS/Spector.js/pull/345)
* Add [Offscreen canvas support](https://github.com/BabylonJS/Spector.js/pull/343)
Expand Down
112 changes: 109 additions & 3 deletions extensions/contentScript.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ var spectorCommunicationQuickCaptureElementId = "SPECTOR_COMMUNICATION_QUICKCAPT
var spectorCommunicationFullCaptureElementId = "SPECTOR_COMMUNICATION_FULLCAPTURE";
var spectorCommunicationCommandCountElementId = "SPECTOR_COMMUNICATION_COMMANDCOUNT";
var spectorCommunicationRebuildProgramElementId = "SPECTOR_COMMUNICATION_REBUILDPROGRAM";
var spectorCommunicationShaderDelayElementId = "SPECTOR_COMMUNICATION_SHADERDELAY";
var spectorShaderCompileDelayKey = "SPECTOR_SHADERCOMPILEDELAY";

var spectorContextTypeKey = "__spector_context_type";

Expand Down Expand Up @@ -187,14 +189,92 @@ window.__SPECTOR_Canvases = [];
}

try {
// Resolve to absolute so we can fetch and use as base URL
// for runtime URL rewriting inside the blob worker.
var absoluteScriptUrl;
try {
absoluteScriptUrl = new URL(urlStr, location.href).href;
} catch (e) {
absoluteScriptUrl = urlStr;
}

var xhr = new XMLHttpRequest();
xhr.open('GET', urlStr, false);
xhr.open('GET', absoluteScriptUrl, false);
xhr.send();

if (xhr.status === 200) {
if (bundleUrl) {
var importLine = 'importScripts("' + bundleUrl + '");\n';
var modifiedScript = importLine + xhr.responseText;
// Runtime shim: when a worker is loaded from a blob:
// URL, `self.location.href` is the blob URL — which
// means every relative URL passed to importScripts,
// fetch, XMLHttpRequest, or new Worker resolves against
// it and breaks (root-relative URLs throw "URL is
// invalid"; relative URLs become blob:scheme paths).
// We wrap each of those APIs to resolve URLs against
// the worker's ORIGINAL location before delegating to
// the real implementation.
var runtimeShim =
'(function(){\n' +
' var __spectorBase = ' + JSON.stringify(absoluteScriptUrl) + ';\n' +
' self.__SPECTOR_workerBaseUrl = __spectorBase;\n' +
' function __spectorResolve(u) {\n' +
' if (u == null) return u;\n' +
' if (typeof u !== "string") {\n' +
' if (u instanceof URL) return u;\n' +
' try { u = String(u); } catch (e) { return u; }\n' +
' }\n' +
' if (/^(?:[a-z][a-z0-9+.-]*:|\\/\\/)/i.test(u)) return u;\n' +
' try { return new URL(u, __spectorBase).href; }\n' +
' catch (e) { return u; }\n' +
' }\n' +
' self.__SPECTOR_resolveWorkerUrl = __spectorResolve;\n' +
// importScripts (variadic)
' var __origImportScripts = self.importScripts;\n' +
' if (typeof __origImportScripts === "function") {\n' +
' self.importScripts = function() {\n' +
' var args = new Array(arguments.length);\n' +
' for (var i = 0; i < arguments.length; i++) args[i] = __spectorResolve(arguments[i]);\n' +
' return __origImportScripts.apply(self, args);\n' +
' };\n' +
' }\n' +
// fetch
' var __origFetch = self.fetch;\n' +
' if (typeof __origFetch === "function") {\n' +
' self.fetch = function(input, init) {\n' +
' try {\n' +
' if (typeof input === "string") {\n' +
' input = __spectorResolve(input);\n' +
' } else if (input && typeof input === "object" && "url" in input && typeof Request === "function") {\n' +
' var r = input;\n' +
' var resolved = __spectorResolve(r.url);\n' +
' if (resolved !== r.url) input = new Request(resolved, r);\n' +
' }\n' +
' } catch (e) { /* fall through with original input */ }\n' +
' return __origFetch.call(self, input, init);\n' +
' };\n' +
' }\n' +
// XMLHttpRequest.open
' if (typeof XMLHttpRequest === "function" && XMLHttpRequest.prototype && XMLHttpRequest.prototype.open) {\n' +
' var __origXhrOpen = XMLHttpRequest.prototype.open;\n' +
' XMLHttpRequest.prototype.open = function(method, url) {\n' +
' var args = Array.prototype.slice.call(arguments);\n' +
' args[1] = __spectorResolve(url);\n' +
' return __origXhrOpen.apply(this, args);\n' +
' };\n' +
' }\n' +
// Nested Worker construction (workers can spawn workers)
' if (typeof Worker === "function") {\n' +
' var __OrigWorker = Worker;\n' +
' self.Worker = function(scriptURL, options) {\n' +
' var resolved = __spectorResolve(scriptURL);\n' +
' return new __OrigWorker(resolved, options);\n' +
' };\n' +
' self.Worker.prototype = __OrigWorker.prototype;\n' +
' }\n' +
'})();\n';

var importLine = 'importScripts(' + JSON.stringify(bundleUrl) + ');\n';
var modifiedScript = runtimeShim + importLine + xhr.responseText;
var blob = new Blob([modifiedScript], { type: 'application/javascript' });
var blobUrl = URL.createObjectURL(blob);
var w = new __SPECTOR_Origin_Worker(blobUrl, options);
Expand Down Expand Up @@ -275,6 +355,28 @@ if (sessionStorage.getItem(spectorLoadedKey)) {
setTimeout(function () {
spector = new SPECTOR.Spector();
spector.spyCanvases();

// Re-apply a persisted shader-compile delay after a page reload.
try {
var persistedDelay = parseInt(sessionStorage.getItem(spectorShaderCompileDelayKey), 10);
if (!isNaN(persistedDelay) && persistedDelay > 0) {
spector.setShaderCompileDelay(persistedDelay);
}
} catch (e) {
// sessionStorage unavailable — ignore.
}

document.addEventListener("SpectorRequestSetShaderCompileDelayEvent", function() {
var delayElement = document.getElementById(spectorCommunicationShaderDelayElementId);
var delayMs = delayElement ? parseInt(delayElement.value, 10) : 0;
if (isNaN(delayMs) || delayMs < 0) {
delayMs = 0;
}
if (spector && spector.setShaderCompileDelay) {
spector.setShaderCompileDelay(delayMs);
}
});

document.addEventListener("SpectorRequestPauseEvent", function() {
spector.pause();
});
Expand Down Expand Up @@ -477,5 +579,9 @@ if (sessionStorage.getItem(spectorLoadedKey)) {
input5.type = 'Hidden';
input5.id = spectorCommunicationCommandCountElementId;
document.body.appendChild(input5);
var input6 = document.createElement('input');
input6.type = 'Hidden';
input6.id = spectorCommunicationShaderDelayElementId;
document.body.appendChild(input6);
});
}
20 changes: 20 additions & 0 deletions extensions/contentScriptProxy.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ var spectorCommunicationQuickCaptureElementId = "SPECTOR_COMMUNICATION_QUICKCAPT
var spectorCommunicationFullCaptureElementId = "SPECTOR_COMMUNICATION_FULLCAPTURE";
var spectorCommunicationCommandCountElementId = "SPECTOR_COMMUNICATION_COMMANDCOUNT";
var spectorCommunicationRebuildProgramElementId = "SPECTOR_COMMUNICATION_REBUILDPROGRAM";
var spectorCommunicationShaderDelayElementId = "SPECTOR_COMMUNICATION_SHADERDELAY";
var spectorShaderCompileDelayKey = "SPECTOR_SHADERCOMPILEDELAY";

var spectorContextTypeKey = "__spector_context_type";

Expand Down Expand Up @@ -221,6 +223,24 @@ listenForMessage(function (message) {
return;
}

// Simulate slow parallel shader compilation. Applies live (no reload) and
// is persisted for the tab session so it re-applies after a page reload.
if (action === "setShaderCompileDelay") {
var delayMs = parseInt(message.delayMs, 10);
if (isNaN(delayMs) || delayMs < 0) {
delayMs = 0;
}
sessionStorage.setItem(spectorShaderCompileDelayKey, String(delayMs));

var delayElement = document.getElementById(spectorCommunicationShaderDelayElementId);
if (delayElement) {
delayElement.value = String(delayMs);
}
var myEvent = new CustomEvent("SpectorRequestSetShaderCompileDelayEvent");
document.dispatchEvent(myEvent);
return;
}

// Let s refresh the canvases list.
if (action === "requestCanvases") {
setTimeout(function () { refreshCanvases(); }, 0);
Expand Down
21 changes: 21 additions & 0 deletions extensions/popup.html
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,15 @@
#captureOnLoadCount {
width:25px;
}

#shaderCompileDelay {
background-color: #3a3a3a;
color: #f9f9f9;
border: 1px solid #222;
font-family: Consolas, monaco, monospace;
font-size: 13px;
cursor: pointer;
}
</style>
</head>

Expand All @@ -122,6 +131,18 @@
first commands (<label><input type="checkbox" id="captureOnLoadTransient" /> transient</label>):
<span id="captureNow">Capture</span>/<span id="captureOnLoad">Reload</span>.
</p>
<p>
<label>Simulate slow shader compile:
<select id="shaderCompileDelay">
<option value="0">Off</option>
<option value="250">250 ms</option>
<option value="500">500 ms</option>
<option value="1000">1 s</option>
<option value="2000">2 s</option>
<option value="5000">5 s</option>
</select>
</label>
</p>
</div>

<img src="spectorjs.svg" alt="SpectorJS" class="filler" />
Expand Down
37 changes: 37 additions & 0 deletions extensions/popup.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,36 @@ window.addEventListener("DOMContentLoaded", function() {
this.changeOffScreenStatus(offScreenInput.checked);
};

var shaderCompileDelayInput = document.getElementById("shaderCompileDelay");
if (shaderCompileDelayInput) {
// Restore the last selection so the dropdown reflects the active value.
try {
window.browser.storage.local.get("shaderCompileDelay", function (items) {
var stored = items && items.shaderCompileDelay;
if (typeof stored !== "undefined" && stored !== null) {
shaderCompileDelayInput.value = String(stored);
}
});
}
catch (e) {
// Storage unavailable — ignore.
}

shaderCompileDelayInput.onchange = () => {
var delayMs = parseInt(shaderCompileDelayInput.value, 10);
if (isNaN(delayMs) || delayMs < 0) {
delayMs = 0;
}
try {
window.browser.storage.local.set({ shaderCompileDelay: delayMs });
}
catch (e) {
// Storage unavailable — ignore.
}
this.changeShaderCompileDelay(delayMs);
};
}

initUI();
refreshCanvases();
playAll();
Expand Down Expand Up @@ -114,6 +144,13 @@ var changeOffScreenStatus = function(offScreen) {
});
}

var changeShaderCompileDelay = function(delayMs) {
sendMessage({
action: "setShaderCompileDelay",
delayMs: delayMs,
});
}

var drag = function(e) {
e.stopPropagation();
e.preventDefault();
Expand Down
2 changes: 1 addition & 1 deletion extensions/spector.bundle.func.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion extensions/spector.bundle.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion extensions/spector.worker.bundle.js

Large diffs are not rendered by default.

Loading
Loading