Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Display list of non-deleted WebGL objects #195

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from

Conversation

JannikGM
Copy link
Contributor

@JannikGM JannikGM commented May 7, 2021

I'm currently adding WebGL cleanup routines to our code and required tooling to display not only how much memory is leaking, but also where it leaked from.

This is a first PoC / draft to test this.

I'll leave some comments in the code where input would be appreciated.

(I probably won't be able to reserve much time to clean this up, so I want to get this right in as few review passes as possible; if I don't find time to fix this, please feel free to continue where I left off, or take this as inspiration for implementing this from scratch)

"target": "es5"
"target": "es5",
"lib": [
"ESNext",
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was necessary for WeakRef, and it also required an update of typescript

@@ -4,6 +4,7 @@ import { WebGlConstants, WebGlConstantsByValue } from "../types/webglConstants";
import { FunctionCallbacks, IFunctionInformation } from "../types/functionInformation";
import { ICapture } from "../../shared/capture/capture";
import { WebGlObjects } from "../webGlObjects/baseWebGlObject";
import {StackTrace} from "../../shared/utils/stackTrace";
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: missing spaces

@@ -85,6 +86,8 @@ export abstract class BaseRecorder<T extends WebGLObject> implements IRecorder {
protected readonly startTime: number;
protected readonly memoryPerSecond: { [second: number]: number };

private objects: { [id: number]: WeakRef<WebGLObject> };
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be called instances instead? The code below appears to refer to individual objects as instance.

if (source) {
capture.objects[this.objectName][id] = source;
} else {
console.log("Object without source");
Copy link
Contributor Author

@JannikGM JannikGM May 7, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This currently doesn't pass the linter.
I need a way to display this properly; I never saw these messages, and I assume they got compiled away?

console.log("Object without source");
}
} else {
console.log("Object which was already free'd");
Copy link
Contributor Author

@JannikGM JannikGM May 7, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This currently doesn't pass the linter.
I need a way to display this properly; I never saw these messages, and I assume they got compiled away?

@@ -141,7 +160,19 @@ export abstract class BaseRecorder<T extends WebGLObject> implements IRecorder {
protected abstract delete(instance: T): number;

protected create(functionInformation: IFunctionInformation): void {
// Nothing tracked currently on create.
// Removes the spector internal calls to leave only the relevant part.
const stackTrace = StackTrace.getStackTrace(5, 0);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The range for this StackTrace trimming was not checked. Overall, I wasn't sure why the first entry would have to be removed? Is this only relevant for specific browsers?

// Add our object to the list of tracked objects (object should only exist once).
const tag = WebGlObjects.getWebGlObjectTag(createdWebGlObject);
this.objects = this.objects || [];
if (this.objects[tag.id]) { debugger; }
Copy link
Contributor Author

@JannikGM JannikGM May 7, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This currently doesn't pass the linter.
This should never happen, so I'll probably remove this line or early out, just to shut up the compiler.
Ideally I'd like to have some sort of assertion or sanity check here.

(A similar case already existed in updateWithoutSideEffects, which I consider problematic, too)

@@ -5,6 +5,8 @@ export type WebGlObjectTag = {
customData?: any;
};

export type WebGlObjectSource = string[];
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead, this should be an object which also stores when the object was created.
There could also be a flag where even removed objects are listed / kept, but their delete location / time is also recorded.

@@ -263,6 +263,7 @@ export abstract class BaseState {

return {
__SPECTOR_Object_TAG: WebGlObjects.getWebGlObjectTag(object) || this.options.tagWebGlObject(object),
__SPECTOR_Object_Source: WebGlObjects.getWebGlObjectSource(object),
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this is dead code now? If not, this should be __SPECTOR_Object_Source although I plan to rename it to __SPECTOR_Object_ORIGIN or __SPECTOR_Object_LIFETIME or something.

@@ -501,6 +503,10 @@ export class ResultView {
return null;
}

if (json.__SPECTOR_Object_SOURCE) {
return json.__SPECTOR_Object_SOURCE;
}
Copy link
Contributor Author

@JannikGM JannikGM May 7, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this ever did anything. I'd like to display the origin somewhere in the command list detail pane, too. I simply wasn't sure how to do it (I have a lot of trouble understanding the codebase to be honest).

capture.objects[this.objectName] = {};
for (const id in this.objects) {
if (this.objects.hasOwnProperty(id)) {
const object = this.objects[id].deref();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea of a WeakRef was to allow garbage collection on WebGL objects (although I've never seen it in practice yet).

However, objects which no longer exist must also have their WeakRef removed from this.objects, or those WeakRef themselves still leak memory.
We should probably auto-removed dead references from the this.objects from time to time.
There could be a garbage collector every N object creations, which would look if any WeakRef are dead, to remove the WeakRef itself from the list.
FinalizerRegistry might work to get auto notification when the list would have to be cleaned up.

There are also WeakMap and WeakSet but we probably can't use them here, because we can't iterate over them.

Thoughts?

@sebavan
Copy link
Member

sebavan commented May 10, 2021

I like it overall !!! but I am currently moving home with almost no time this week so I ll be able to have a deeper look next week. At least I got internet back :-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants