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:
Philip Rideout
2018-10-25 16:48:17 -07:00
parent 31b9801f17
commit e8fddba494
4 changed files with 171 additions and 31 deletions

View File

@@ -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")

View File

@@ -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()

View 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>

View 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();
}
}