New JS test for memory leaks.
This test recreates a renderable every few frames and dumps the heap pointer to the console so we can check if it increases over time. Also simplified the personal web server script so that it uses twisted and avoid doing a chdir, seems to be more reliable this way.
This commit is contained in:
@@ -16,6 +16,7 @@ set(CPP_SRC
|
||||
set(COPTS "${COPTS} -DEMSCRIPTEN_HAS_UNBOUND_TYPE_NAMES=0")
|
||||
|
||||
set(LOPTS "${LOPTS} --bind")
|
||||
set(LOPTS "${LOPTS} -s FILESYSTEM=0")
|
||||
set(LOPTS "${LOPTS} -s ALLOW_MEMORY_GROWTH=1")
|
||||
set(LOPTS "${LOPTS} -s MODULARIZE_INSTANCE=1")
|
||||
set(LOPTS "${LOPTS} -s EXPORT_NAME=Filament")
|
||||
|
||||
@@ -1,20 +1,23 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import glob
|
||||
import http.server
|
||||
import os
|
||||
import shutil
|
||||
import socketserver
|
||||
import sys
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from twisted.web.server import Site, GzipEncoderFactory
|
||||
from twisted.web.static import File
|
||||
from twisted.web.resource import Resource, EncodingResourceWrapper
|
||||
from twisted.internet import reactor
|
||||
|
||||
from watchdog.observers import Observer
|
||||
from watchdog.events import FileSystemEventHandler
|
||||
from watchdog.events import LoggingEventHandler
|
||||
|
||||
SCRIPT_DIR = Path(os.path.dirname(os.path.abspath(__file__)))
|
||||
ROOT_DIR = Path(os.path.realpath(SCRIPT_DIR / "../../../"))
|
||||
ROOT_DIR = Path(os.path.realpath(SCRIPT_DIR / "../../.."))
|
||||
BUILD_DIR = ROOT_DIR / "out/cmake-webgl-release"
|
||||
TOOLS_DIR = ROOT_DIR / "out/cmake-release/tools"
|
||||
SERVE_DIR = BUILD_DIR / "libs/filamentjs"
|
||||
@@ -22,26 +25,15 @@ GLMATRIX_DIR = ROOT_DIR / "third_party/gl-matrix"
|
||||
CURR_DIR = Path(os.path.realpath('.'))
|
||||
PORT = 8000
|
||||
|
||||
global server
|
||||
|
||||
# Restart the script if the JavaScript or HTML change.
|
||||
|
||||
class OnDirectoryChanged(FileSystemEventHandler):
|
||||
def on_modified(self, event):
|
||||
global server
|
||||
if event.event_type != 'modified':
|
||||
return
|
||||
server.shutdown()
|
||||
print(f'{event.src_path} was modified\nRestarting...')
|
||||
os.chdir(CURR_DIR)
|
||||
os.execl(sys.executable, *([sys.executable]+sys.argv))
|
||||
|
||||
print("Starting observer...")
|
||||
handler = OnDirectoryChanged()
|
||||
observer = Observer()
|
||||
observer.schedule(handler, path=str(CURR_DIR), recursive=True)
|
||||
observer.start()
|
||||
|
||||
# Copy test assets into the server folder.
|
||||
|
||||
print("Copying assets...")
|
||||
@@ -99,26 +91,20 @@ shutil.copy(parquet_filamesh, SERVE_DIR)
|
||||
for tex in parquet_textures:
|
||||
shutil.copy(tex, SERVE_DIR)
|
||||
|
||||
# Associate wasm files with the correct MIME type.
|
||||
|
||||
Handler = http.server.SimpleHTTPRequestHandler
|
||||
Handler.extensions_map.update({
|
||||
'.wasm': 'application/wasm',
|
||||
})
|
||||
|
||||
# Serve all files in the server folder.
|
||||
|
||||
print(f"Serving {SERVE_DIR}...")
|
||||
print(f" http://localhost:{PORT}/test_redball.html")
|
||||
print(f" http://localhost:{PORT}/test_parquet.html")
|
||||
Handler.directory = SERVE_DIR
|
||||
os.chdir(SERVE_DIR)
|
||||
socketserver.TCPServer.allow_reuse_address = True
|
||||
with socketserver.TCPServer(("", PORT), Handler) as httpd:
|
||||
global server
|
||||
server = httpd
|
||||
httpd.allow_reuse_address = True
|
||||
httpd.serve_forever()
|
||||
print(f" http://localhost:{PORT}/test_leaks.html")
|
||||
|
||||
observer.stop()
|
||||
observer.join()
|
||||
port = 8000
|
||||
webdir = File(SERVE_DIR)
|
||||
webdir.contentTypes['.wasm'] = 'application/wasm'
|
||||
wrapped = EncodingResourceWrapper(webdir, [GzipEncoderFactory()])
|
||||
reactor.listenTCP(port, Site(wrapped))
|
||||
handler = OnDirectoryChanged()
|
||||
observer = Observer()
|
||||
observer.schedule(handler, path=str(CURR_DIR), recursive=True)
|
||||
observer.start()
|
||||
reactor.run()
|
||||
|
||||
18
libs/filamentjs/tests/test_leaks.html
Normal file
18
libs/filamentjs/tests/test_leaks.html
Normal file
@@ -0,0 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>FilamentJS Test</title>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,user-scalable=no,initial-scale=1">
|
||||
<style>
|
||||
body { margin: 0; overflow: hidden; }
|
||||
canvas { touch-action: none; width: 100%; height: 100%; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<canvas></canvas>
|
||||
<script src="filament.js"></script>
|
||||
<script src="gl-matrix-min.js"></script>
|
||||
<script src="test_leaks.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
135
libs/filamentjs/tests/test_leaks.js
Normal file
135
libs/filamentjs/tests/test_leaks.js
Normal file
@@ -0,0 +1,135 @@
|
||||
Filament.init([ 'bakedColor.filamat' ], () => {
|
||||
window.VertexAttribute = Filament.VertexAttribute;
|
||||
window.AttributeType = Filament.VertexBuffer$AttributeType;
|
||||
window.PrimitiveType = Filament.RenderableManager$PrimitiveType;
|
||||
window.IndexType = Filament.IndexBuffer$IndexType;
|
||||
window.Fov = Filament.Camera$Fov;
|
||||
window.LightType = Filament.LightManager$Type;
|
||||
const canvas = document.getElementsByTagName('canvas')[0];
|
||||
window.app = new App(canvas);
|
||||
});
|
||||
|
||||
class App {
|
||||
constructor(canvas) {
|
||||
const BAKED_COLOR_PACKAGE = Filament.Buffer(Filament.assets['bakedColor.filamat']);
|
||||
this.canvas = canvas;
|
||||
this.engine = Filament.Engine.create(this.canvas);
|
||||
this.mi = this.engine.createMaterial(BAKED_COLOR_PACKAGE).getDefaultInstance();
|
||||
const engine = this.engine;
|
||||
this.polygon = Filament.EntityManager.get().create();
|
||||
this.createPolygon(this.polygon, 3, this.mi);
|
||||
this.scene = engine.createScene();
|
||||
this.scene.addEntity(this.polygon);
|
||||
this.swapChain = engine.createSwapChain();
|
||||
this.renderer = engine.createRenderer();
|
||||
this.camera = engine.createCamera();
|
||||
this.view = engine.createView();
|
||||
this.view.setCamera(this.camera);
|
||||
this.view.setScene(this.scene);
|
||||
this.resize();
|
||||
this.render = this.render.bind(this);
|
||||
this.resize = this.resize.bind(this);
|
||||
window.addEventListener("resize", this.resize);
|
||||
window.requestAnimationFrame(this.render);
|
||||
}
|
||||
|
||||
render() {
|
||||
this.animate();
|
||||
this.renderer.render(this.swapChain, this.view);
|
||||
window.requestAnimationFrame(this.render);
|
||||
}
|
||||
|
||||
resize() {
|
||||
const dpr = window.devicePixelRatio;
|
||||
const width = this.canvas.width = window.innerWidth * dpr;
|
||||
const height = this.canvas.height = window.innerHeight * dpr;
|
||||
this.view.setViewport([0, 0, width, height]);
|
||||
const eye = [0, 0, 3], center = [0, 0, -1], up = [0, 1, 0];
|
||||
this.camera.lookAt(eye, center, up);
|
||||
const aspect = width / height;
|
||||
this.camera.setProjectionFov(45, aspect, 1.0, 10.0, aspect < 1 ? Fov.HORIZONTAL : Fov.VERTICAL);
|
||||
}
|
||||
|
||||
createPolygon(polygon, nsides, minstance) {
|
||||
const engine = this.engine;
|
||||
const nverts = nsides + 1;
|
||||
const ntris = nsides;
|
||||
const dtheta = Math.PI * 2 / nsides;
|
||||
// Create typed arrays.
|
||||
const indices = new Uint16Array(3 * ntris);
|
||||
const positions = new Float32Array(2 * nverts);
|
||||
const colors = new Uint32Array(nverts);
|
||||
const palette = new Uint32Array([0xffff0000, 0xff00ff00, 0xff0000ff]);
|
||||
for (let i = 1, j = 0, theta = 0; i < nverts; i++, theta += dtheta) {
|
||||
positions[i * 2] = Math.cos(theta);
|
||||
positions[i * 2 + 1] = Math.sin(theta);
|
||||
colors[i] = palette[i % 3];
|
||||
indices[j++] = 0;
|
||||
indices[j++] = i;
|
||||
indices[j++] = 1 + (i % (nverts - 1));
|
||||
}
|
||||
// Create vertex buffer.
|
||||
const vbuilder = Filament.VertexBuffer.Builder()
|
||||
.vertexCount(nverts)
|
||||
.bufferCount(2)
|
||||
.attribute(VertexAttribute.POSITION, 0, AttributeType.FLOAT2, 0, 8)
|
||||
.attribute(VertexAttribute.COLOR, 1, AttributeType.UBYTE4, 0, 4)
|
||||
.normalized(VertexAttribute.COLOR);
|
||||
const vb = vbuilder.build(engine);
|
||||
vbuilder.delete();
|
||||
const pbuf = Filament.Buffer(positions);
|
||||
const cbuf = Filament.Buffer(colors);
|
||||
vb.setBufferAt(engine, 0, pbuf);
|
||||
vb.setBufferAt(engine, 1, cbuf);
|
||||
pbuf.delete();
|
||||
cbuf.delete();
|
||||
// Create index buffer.
|
||||
const ibuilder = Filament.IndexBuffer.Builder()
|
||||
.indexCount(indices.length)
|
||||
.bufferType(IndexType.USHORT);
|
||||
const ib = ibuilder.build(engine);
|
||||
ibuilder.delete();
|
||||
const ibuf = Filament.Buffer(indices);
|
||||
ib.setBuffer(engine, ibuf);
|
||||
ibuf.delete();
|
||||
// Create renderable component.
|
||||
const rbuilder = Filament.RenderableManager.Builder(1)
|
||||
.boundingBox([ [-1, -1, -1], [1, 1, 1] ])
|
||||
.material(0, minstance)
|
||||
.geometry(0, PrimitiveType.TRIANGLES, vb, ib);
|
||||
rbuilder.build(engine, polygon);
|
||||
rbuilder.delete();
|
||||
vb.delete();
|
||||
ib.delete();
|
||||
}
|
||||
|
||||
// For now, simply print out malloc'd pointer value and check
|
||||
// if it increases over time. Is there a better way of doing this?
|
||||
printMemoryUsage() {
|
||||
const ptr = Filament._malloc(1);
|
||||
Filament._free(ptr);
|
||||
this.maxptr = Math.max(this.maxptr | 0, ptr);
|
||||
console.log(this.maxptr);
|
||||
}
|
||||
|
||||
animate() {
|
||||
// Recreate the polygon every couple frames, changing the number of sides.
|
||||
this.frame = (this.frame | 0) + 1;
|
||||
if (this.frame > 2) {
|
||||
this.frame = 0;
|
||||
this.nsides = (this.nsides | 0) + 1;
|
||||
this.nsides = this.nsides % 10;
|
||||
// Destroy and recreate the renderable component.
|
||||
this.engine.destroyEntity(this.polygon);
|
||||
this.createPolygon(this.polygon, 3 + this.nsides, this.mi);
|
||||
this.printMemoryUsage();
|
||||
}
|
||||
// Rotate the polygon around the Z axis.
|
||||
const radians = Date.now() / 1000;
|
||||
const transform = mat4.fromRotation(mat4.create(), radians, [0, 0, 1]);
|
||||
const tcm = this.engine.getTransformManager();
|
||||
const inst = tcm.getInstance(this.polygon);
|
||||
tcm.setTransform(inst, transform);
|
||||
inst.delete();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user