diff --git a/libs/filamentjs/CMakeLists.txt b/libs/filamentjs/CMakeLists.txt
index 58d5055355..24d1cbfe90 100644
--- a/libs/filamentjs/CMakeLists.txt
+++ b/libs/filamentjs/CMakeLists.txt
@@ -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")
diff --git a/libs/filamentjs/tests/test.py b/libs/filamentjs/tests/test.py
index f21e0c0051..58dfaf254e 100755
--- a/libs/filamentjs/tests/test.py
+++ b/libs/filamentjs/tests/test.py
@@ -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()
diff --git a/libs/filamentjs/tests/test_leaks.html b/libs/filamentjs/tests/test_leaks.html
new file mode 100644
index 0000000000..95356d48b1
--- /dev/null
+++ b/libs/filamentjs/tests/test_leaks.html
@@ -0,0 +1,18 @@
+
+
+
+ FilamentJS Test
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/libs/filamentjs/tests/test_leaks.js b/libs/filamentjs/tests/test_leaks.js
new file mode 100644
index 0000000000..da8db4ac5b
--- /dev/null
+++ b/libs/filamentjs/tests/test_leaks.js
@@ -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();
+ }
+}