filly 0.1.0__tar.gz → 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {filly-0.1.0 → filly-0.2.0}/NOTES.md +1 -1
- {filly-0.1.0 → filly-0.2.0}/PKG-INFO +25 -25
- {filly-0.1.0 → filly-0.2.0}/README.md +19 -19
- {filly-0.1.0 → filly-0.2.0}/docs/explanation/assumptions.md +3 -3
- {filly-0.1.0 → filly-0.2.0}/docs/explanation/design.md +128 -53
- filly-0.2.0/docs/explanation/filament.md +201 -0
- {filly-0.1.0 → filly-0.2.0}/docs/explanation/material-precompilation.md +5 -2
- {filly-0.1.0 → filly-0.2.0}/docs/explanation/web.md +10 -7
- {filly-0.1.0 → filly-0.2.0}/docs/how-to/build.md +47 -10
- {filly-0.1.0 → filly-0.2.0}/docs/how-to/gl-hosts.md +86 -71
- {filly-0.1.0 → filly-0.2.0}/docs/how-to/profile.md +16 -14
- {filly-0.1.0 → filly-0.2.0}/docs/how-to/psychopy.md +47 -37
- {filly-0.1.0 → filly-0.2.0}/docs/how-to/stress-test.md +5 -5
- {filly-0.1.0 → filly-0.2.0}/docs/how-to/web.md +129 -20
- {filly-0.1.0 → filly-0.2.0}/docs/reference/api.md +526 -190
- {filly-0.1.0 → filly-0.2.0}/docs/reference/performance.md +244 -6
- {filly-0.1.0 → filly-0.2.0}/docs/reference/sample-assets.md +4 -3
- filly-0.2.0/docs/reference/upstream-issues.md +596 -0
- {filly-0.1.0 → filly-0.2.0}/docs/reference/validation.md +26 -2
- {filly-0.1.0 → filly-0.2.0}/docs/tutorials/offscreen.md +6 -7
- {filly-0.1.0 → filly-0.2.0}/examples/drifting_grating.py +6 -8
- {filly-0.1.0 → filly-0.2.0}/examples/moderngl_shared.py +5 -6
- filly-0.2.0/examples/pick.py +206 -0
- {filly-0.1.0 → filly-0.2.0}/examples/psychopy_shared.py +18 -18
- {filly-0.1.0 → filly-0.2.0}/examples/psychopy_stress.py +22 -10
- {filly-0.1.0 → filly-0.2.0}/examples/pyglet_shared.py +7 -8
- {filly-0.1.0 → filly-0.2.0}/examples/screenshot.py +10 -12
- {filly-0.1.0 → filly-0.2.0}/examples/shapes.py +5 -3
- {filly-0.1.0 → filly-0.2.0}/examples/stereo.py +6 -7
- {filly-0.1.0 → filly-0.2.0}/examples/zengl_shared.py +6 -5
- {filly-0.1.0 → filly-0.2.0}/native/animation.inc +4 -0
- {filly-0.1.0 → filly-0.2.0}/native/archive_materials.cpp +3 -3
- {filly-0.1.0 → filly-0.2.0}/native/bindings.cpp +267 -131
- {filly-0.1.0 → filly-0.2.0}/native/features.inc +285 -164
- {filly-0.1.0 → filly-0.2.0}/native/gl_interop.cpp +189 -1
- {filly-0.1.0 → filly-0.2.0}/native/gl_interop.h +12 -0
- {filly-0.1.0 → filly-0.2.0}/native/gl_interop_web.cpp +66 -1
- {filly-0.1.0 → filly-0.2.0}/native/gltf_prepare.cpp +3 -2
- {filly-0.1.0 → filly-0.2.0}/native/gltf_prepare.h +5 -1
- {filly-0.1.0 → filly-0.2.0}/native/memory.inc +19 -12
- {filly-0.1.0 → filly-0.2.0}/native/renderer.cpp +906 -299
- {filly-0.1.0 → filly-0.2.0}/native/renderer.h +216 -76
- {filly-0.1.0 → filly-0.2.0}/native/resources.inc +28 -19
- {filly-0.1.0 → filly-0.2.0}/native/slots.inc +1 -1
- {filly-0.1.0 → filly-0.2.0}/pyproject.toml +10 -7
- {filly-0.1.0 → filly-0.2.0}/spec.md +86 -33
- {filly-0.1.0 → filly-0.2.0}/src/filly/__init__.py +11 -7
- {filly-0.1.0 → filly-0.2.0}/src/filly/benchmark.py +2 -2
- {filly-0.1.0 → filly-0.2.0}/src/filly/integrations/_host.py +53 -23
- filly-0.2.0/src/filly/integrations/moderngl.py +56 -0
- {filly-0.1.0 → filly-0.2.0}/src/filly/integrations/psychopy.py +68 -34
- filly-0.2.0/src/filly/integrations/pyglet.py +71 -0
- filly-0.2.0/src/filly/integrations/zengl.py +103 -0
- {filly-0.1.0 → filly-0.2.0}/tests/conftest.py +2 -2
- {filly-0.1.0 → filly-0.2.0}/tests/data/README.md +16 -2
- filly-0.2.0/tests/data/box/Box.gltf +142 -0
- filly-0.2.0/tests/data/box/Box0.bin +0 -0
- filly-0.2.0/tests/data/draco-box/Box.bin +0 -0
- filly-0.2.0/tests/data/draco-box/Box.gltf +152 -0
- {filly-0.1.0 → filly-0.2.0}/tests/test_animation_pointer.py +9 -7
- filly-0.2.0/tests/test_asset.py +344 -0
- {filly-0.1.0 → filly-0.2.0}/tests/test_camera_aspect.py +4 -4
- {filly-0.1.0 → filly-0.2.0}/tests/test_controls.py +2 -2
- filly-0.2.0/tests/test_draco.py +46 -0
- {filly-0.1.0 → filly-0.2.0}/tests/test_effects.py +57 -15
- {filly-0.1.0 → filly-0.2.0}/tests/test_encoding.py +24 -6
- {filly-0.1.0 → filly-0.2.0}/tests/test_features.py +15 -11
- {filly-0.1.0 → filly-0.2.0}/tests/test_framing.py +79 -10
- {filly-0.1.0 → filly-0.2.0}/tests/test_gl_hosts.py +236 -46
- {filly-0.1.0 → filly-0.2.0}/tests/test_gltf_surface_camera.py +15 -7
- {filly-0.1.0 → filly-0.2.0}/tests/test_headless.py +1 -1
- filly-0.1.0/tests/test_host_texture.py → filly-0.2.0/tests/test_imported_texture.py +12 -7
- {filly-0.1.0 → filly-0.2.0}/tests/test_interop.py +18 -18
- {filly-0.1.0 → filly-0.2.0}/tests/test_iridescence_dielectric.py +3 -2
- {filly-0.1.0 → filly-0.2.0}/tests/test_large_scene.py +2 -2
- {filly-0.1.0 → filly-0.2.0}/tests/test_lifecycle.py +80 -7
- {filly-0.1.0 → filly-0.2.0}/tests/test_light_pointer.py +2 -0
- filly-0.2.0/tests/test_lighting.py +250 -0
- {filly-0.1.0 → filly-0.2.0}/tests/test_material_limits.py +2 -4
- {filly-0.1.0 → filly-0.2.0}/tests/test_material_paths.py +18 -14
- {filly-0.1.0 → filly-0.2.0}/tests/test_mesh.py +2 -22
- {filly-0.1.0 → filly-0.2.0}/tests/test_node_tree.py +6 -2
- filly-0.2.0/tests/test_pick.py +376 -0
- {filly-0.1.0 → filly-0.2.0}/tests/test_psychopy.py +126 -45
- {filly-0.1.0 → filly-0.2.0}/tests/test_refraction.py +12 -17
- {filly-0.1.0 → filly-0.2.0}/tests/test_render_calls.py +6 -6
- {filly-0.1.0 → filly-0.2.0}/tests/test_renderer.py +42 -14
- {filly-0.1.0 → filly-0.2.0}/tests/test_rendering_options.py +24 -21
- {filly-0.1.0 → filly-0.2.0}/tests/test_samples.py +2 -1
- {filly-0.1.0 → filly-0.2.0}/tests/test_textures.py +26 -10
- {filly-0.1.0 → filly-0.2.0}/tests/test_volume.py +1 -3
- {filly-0.1.0 → filly-0.2.0}/tests/test_web.py +215 -19
- filly-0.2.0/tests/web/api.html +588 -0
- {filly-0.1.0 → filly-0.2.0}/tests/web/handover.html +5 -4
- {filly-0.1.0 → filly-0.2.0}/tools/benchmark_refraction.py +6 -6
- {filly-0.1.0 → filly-0.2.0}/tools/benchmark_uploads.py +4 -3
- {filly-0.1.0 → filly-0.2.0}/tools/build_filament_web.sh +4 -3
- filly-0.2.0/tools/build_web_container.ps1 +50 -0
- {filly-0.1.0 → filly-0.2.0}/tools/check_sample_assets.py +6 -6
- {filly-0.1.0 → filly-0.2.0}/tools/compare_reference.py +6 -6
- {filly-0.1.0 → filly-0.2.0}/tools/profile_frame.py +30 -33
- {filly-0.1.0 → filly-0.2.0}/tools/profile_matrix.py +5 -5
- {filly-0.1.0 → filly-0.2.0}/tools/profile_tracy.py +5 -40
- filly-0.2.0/tools/web/Dockerfile +12 -0
- filly-0.2.0/uv.lock +7895 -0
- {filly-0.1.0 → filly-0.2.0}/web/bindings.cpp +175 -77
- {filly-0.1.0 → filly-0.2.0}/web/filly.js +360 -187
- filly-0.1.0/src/filly/integrations/moderngl.py +0 -72
- filly-0.1.0/src/filly/integrations/pyglet.py +0 -87
- filly-0.1.0/src/filly/integrations/zengl.py +0 -80
- filly-0.1.0/tests/test_clone.py +0 -269
- filly-0.1.0/tests/test_lighting.py +0 -144
- filly-0.1.0/tests/web/api.html +0 -355
- {filly-0.1.0 → filly-0.2.0}/.github/workflows/wheels.yml +0 -0
- {filly-0.1.0 → filly-0.2.0}/.gitignore +0 -0
- {filly-0.1.0 → filly-0.2.0}/CMakeLists.txt +0 -0
- {filly-0.1.0 → filly-0.2.0}/LICENSE +0 -0
- {filly-0.1.0 → filly-0.2.0}/docs/how-to/reference-comparison.md +0 -0
- {filly-0.1.0 → filly-0.2.0}/docs/images/README.md +0 -0
- {filly-0.1.0 → filly-0.2.0}/docs/images/amber-orthographic.png +0 -0
- {filly-0.1.0 → filly-0.2.0}/docs/images/amber-perspective.png +0 -0
- {filly-0.1.0 → filly-0.2.0}/docs/images/horse.gif +0 -0
- {filly-0.1.0 → filly-0.2.0}/docs/images/horse.png +0 -0
- {filly-0.1.0 → filly-0.2.0}/docs/images/iridescence-environment.png +0 -0
- {filly-0.1.0 → filly-0.2.0}/docs/images/iridescence-studio.png +0 -0
- {filly-0.1.0 → filly-0.2.0}/docs/reference/sample-assets.csv +0 -0
- {filly-0.1.0 → filly-0.2.0}/examples/assets/ATTRIBUTION.md +0 -0
- {filly-0.1.0 → filly-0.2.0}/examples/assets/wooden_playground_horse.glb +0 -0
- {filly-0.1.0 → filly-0.2.0}/native/egl_platform.cpp +0 -0
- {filly-0.1.0 → filly-0.2.0}/native/egl_platform.h +0 -0
- {filly-0.1.0 → filly-0.2.0}/native/engine_config.h +0 -0
- {filly-0.1.0 → filly-0.2.0}/native/json.cpp +0 -0
- {filly-0.1.0 → filly-0.2.0}/native/json.h +0 -0
- {filly-0.1.0 → filly-0.2.0}/native/libwebp.cmake +0 -0
- {filly-0.1.0 → filly-0.2.0}/native/materials/check_refraction.cmake +0 -0
- {filly-0.1.0 → filly-0.2.0}/native/materials/diffuse_transmission.mat.in +0 -0
- {filly-0.1.0 → filly-0.2.0}/native/materials/embed.cmake +0 -0
- {filly-0.1.0 → filly-0.2.0}/native/materials/encode.mat.in +0 -0
- {filly-0.1.0 → filly-0.2.0}/native/materials/fxaa.mat +0 -0
- {filly-0.1.0 → filly-0.2.0}/native/materials/materials.cmake +0 -0
- {filly-0.1.0 → filly-0.2.0}/native/materials/output.cmake +0 -0
- {filly-0.1.0 → filly-0.2.0}/native/materials/specular_glossiness.mat.in +0 -0
- {filly-0.1.0 → filly-0.2.0}/native/materials/surface.mat.in +0 -0
- {filly-0.1.0 → filly-0.2.0}/native/materials/unlit.mat.in +0 -0
- {filly-0.1.0 → filly-0.2.0}/native/materials.h +0 -0
- {filly-0.1.0 → filly-0.2.0}/native/output_pass.cpp +0 -0
- {filly-0.1.0 → filly-0.2.0}/native/output_pass.h +0 -0
- {filly-0.1.0 → filly-0.2.0}/native/refraction.h +0 -0
- {filly-0.1.0 → filly-0.2.0}/native/shapes.cpp +0 -0
- {filly-0.1.0 → filly-0.2.0}/native/vendor/cgltf/LICENSE +0 -0
- {filly-0.1.0 → filly-0.2.0}/native/vendor/cgltf/README.md +0 -0
- {filly-0.1.0 → filly-0.2.0}/native/vendor/cgltf/cgltf.h +0 -0
- {filly-0.1.0 → filly-0.2.0}/native/vendor/meshoptimizer/LICENSE.md +0 -0
- {filly-0.1.0 → filly-0.2.0}/native/vendor/meshoptimizer/README.md +0 -0
- {filly-0.1.0 → filly-0.2.0}/native/vendor/meshoptimizer/indexcodec.cpp +0 -0
- {filly-0.1.0 → filly-0.2.0}/native/vendor/meshoptimizer/meshoptimizer.h +0 -0
- {filly-0.1.0 → filly-0.2.0}/native/vendor/meshoptimizer/vertexcodec.cpp +0 -0
- {filly-0.1.0 → filly-0.2.0}/native/vendor/meshoptimizer/vertexfilter.cpp +0 -0
- {filly-0.1.0 → filly-0.2.0}/native/webp_provider.cpp +0 -0
- {filly-0.1.0 → filly-0.2.0}/native/webp_provider.h +0 -0
- {filly-0.1.0 → filly-0.2.0}/src/filly/_memory.py +0 -0
- {filly-0.1.0 → filly-0.2.0}/src/filly/_stress.py +0 -0
- {filly-0.1.0 → filly-0.2.0}/src/filly/integrations/__init__.py +0 -0
- {filly-0.1.0 → filly-0.2.0}/src/filly/samples/ATTRIBUTION.md +0 -0
- {filly-0.1.0 → filly-0.2.0}/src/filly/samples/__init__.py +0 -0
- {filly-0.1.0 → filly-0.2.0}/src/filly/samples/suzanne.glb +0 -0
- {filly-0.1.0 → filly-0.2.0}/tests/data/green.ktx2 +0 -0
- {filly-0.1.0 → filly-0.2.0}/tests/data/green.webp +0 -0
- {filly-0.1.0 → filly-0.2.0}/tests/data/quadrants.webp +0 -0
- {filly-0.1.0 → filly-0.2.0}/tests/test_logging.py +0 -0
- {filly-0.1.0 → filly-0.2.0}/tests/test_native_imports.py +0 -0
- {filly-0.1.0 → filly-0.2.0}/tests/test_native_preparation.py +0 -0
- {filly-0.1.0 → filly-0.2.0}/tests/test_scene_extensions.py +0 -0
- {filly-0.1.0 → filly-0.2.0}/tests/test_stress.py +0 -0
- {filly-0.1.0 → filly-0.2.0}/tests/web/package-lock.json +0 -0
- {filly-0.1.0 → filly-0.2.0}/tests/web/package.json +0 -0
- {filly-0.1.0 → filly-0.2.0}/tests/web/run_page.mjs +0 -0
- {filly-0.1.0 → filly-0.2.0}/tools/build_filament_linux.py +0 -0
- {filly-0.1.0 → filly-0.2.0}/tools/build_filly_web.sh +0 -0
- {filly-0.1.0 → filly-0.2.0}/tools/check_native_imports.py +0 -0
- {filly-0.1.0 → filly-0.2.0}/tools/ci_linux.sh +0 -0
- {filly-0.1.0 → filly-0.2.0}/tools/fetch_filament.py +0 -0
- {filly-0.1.0 → filly-0.2.0}/tools/make_suzanne.py +0 -0
- {filly-0.1.0 → filly-0.2.0}/tools/profile_rdc_replay.py +0 -0
- {filly-0.1.0 → filly-0.2.0}/web/CMakeLists.txt +0 -0
- {filly-0.1.0 → filly-0.2.0}/web/gl_tracking.js +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Misc things
|
|
2
2
|
- ~~psychopy frequently operates in floats, even for window sizes. Accept floats
|
|
3
|
-
for
|
|
3
|
+
for HostTarget and coerce to ints?~~ Done: target sizes accept integral floats;
|
|
4
4
|
fractional or non-finite values raise ValueError.
|
|
5
5
|
- KHR_xmp_json_ld?
|
|
6
6
|
- Feels like wheel was reinvented unnecessarily, e.g. some of the custom shaders?
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: filly
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: A Python 3D renderer for glTF scenes, built on Google Filament
|
|
5
5
|
Author: Alex Forrence
|
|
6
6
|
License-Expression: MIT
|
|
@@ -9,19 +9,19 @@ Project-URL: Homepage, https://github.com/aforren1/filly
|
|
|
9
9
|
Project-URL: Documentation, https://github.com/aforren1/filly#readme
|
|
10
10
|
Project-URL: Issues, https://github.com/aforren1/filly/issues
|
|
11
11
|
Requires-Python: >=3.10
|
|
12
|
-
Requires-Dist: numpy>=1.
|
|
12
|
+
Requires-Dist: numpy>=1.22
|
|
13
13
|
Provides-Extra: examples
|
|
14
|
-
Requires-Dist: Pillow>=
|
|
14
|
+
Requires-Dist: Pillow>=9.1; extra == "examples"
|
|
15
15
|
Provides-Extra: psychopy
|
|
16
|
-
Requires-Dist: psychopy
|
|
16
|
+
Requires-Dist: psychopy>=2024.2; extra == "psychopy"
|
|
17
17
|
Provides-Extra: pyglet
|
|
18
18
|
Requires-Dist: pyglet>=1.4.11; extra == "pyglet"
|
|
19
19
|
Provides-Extra: moderngl
|
|
20
20
|
Requires-Dist: pyglet>=1.4.11; extra == "moderngl"
|
|
21
|
-
Requires-Dist: moderngl>=5.
|
|
21
|
+
Requires-Dist: moderngl>=5.7; extra == "moderngl"
|
|
22
22
|
Provides-Extra: zengl
|
|
23
23
|
Requires-Dist: pyglet>=1.4.11; extra == "zengl"
|
|
24
|
-
Requires-Dist: zengl>=2.
|
|
24
|
+
Requires-Dist: zengl>=2.1; extra == "zengl"
|
|
25
25
|
Description-Content-Type: text/markdown
|
|
26
26
|
|
|
27
27
|
# filly
|
|
@@ -41,10 +41,9 @@ such as PsychoPy experiments or pyglet, moderngl, and zengl programs.
|
|
|
41
41
|
|
|
42
42
|
## Install
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
You can install filly from [PyPI](https://pypi.org/project/filly/) or [build it from source](docs/how-to/build.md).
|
|
45
45
|
|
|
46
|
-
It needs Python 3.10 or later, Windows or Linux on x86_64, and an OpenGL 4.1 driver. On Linux,
|
|
47
|
-
offscreen rendering works without a display. macOS is not supported at this time.
|
|
46
|
+
It needs Python 3.10 or later, Windows or Linux on x86_64, and an OpenGL 4.1 driver. On Linux, offscreen rendering works without a display. macOS is not supported at this time.
|
|
48
47
|
|
|
49
48
|
## Render an image
|
|
50
49
|
|
|
@@ -57,10 +56,8 @@ with filly.Renderer() as renderer:
|
|
|
57
56
|
horse = scene.load("examples/assets/wooden_playground_horse.glb")
|
|
58
57
|
horse.apply_animation(0, 1.25) # a pose from its spring animation
|
|
59
58
|
|
|
60
|
-
camera =
|
|
61
|
-
camera.
|
|
62
|
-
camera.frame(horse, direction=(-1, -0.3, -1), aspect=800 / 600)
|
|
63
|
-
scene.camera = camera
|
|
59
|
+
scene.camera = renderer.create_camera()
|
|
60
|
+
scene.camera.frame(horse, direction=(-1, -0.3, -1))
|
|
64
61
|
scene.add_directional_light(direction=(-1, -1, -2), intensity=100_000)
|
|
65
62
|
|
|
66
63
|
target = renderer.create_render_target(width=800, height=600)
|
|
@@ -74,20 +71,18 @@ filly renders into a texture that PsychoPy draws directly, with no copy through
|
|
|
74
71
|
|
|
75
72
|
```python
|
|
76
73
|
from psychopy import core, visual
|
|
77
|
-
from filly.integrations.psychopy import
|
|
74
|
+
from filly.integrations.psychopy import HostTarget, create_renderer
|
|
78
75
|
|
|
79
76
|
win = visual.Window((1024, 768), units="pix")
|
|
80
77
|
renderer = create_renderer(win) # shares the window's OpenGL context
|
|
81
78
|
scene = renderer.create_scene()
|
|
82
79
|
horse = scene.load("examples/assets/wooden_playground_horse.glb")
|
|
83
|
-
camera =
|
|
84
|
-
camera.
|
|
85
|
-
camera.frame(horse, aspect=1)
|
|
86
|
-
scene.camera = camera
|
|
80
|
+
scene.camera = renderer.create_camera()
|
|
81
|
+
scene.camera.frame(horse)
|
|
87
82
|
scene.add_directional_light(direction=(-1, -1, -2), intensity=100_000)
|
|
88
83
|
|
|
89
|
-
target =
|
|
90
|
-
stim = target.
|
|
84
|
+
target = HostTarget(renderer, win, width=512, height=512)
|
|
85
|
+
stim = target.create_image_stim()
|
|
91
86
|
clock = core.Clock()
|
|
92
87
|
while clock.getTime() < 3:
|
|
93
88
|
horse.apply_animation(0, clock.getTime())
|
|
@@ -95,12 +90,11 @@ while clock.getTime() < 3:
|
|
|
95
90
|
stim.draw()
|
|
96
91
|
win.flip()
|
|
97
92
|
|
|
98
|
-
target.close()
|
|
99
93
|
renderer.close()
|
|
100
94
|
win.close()
|
|
101
95
|
```
|
|
102
96
|
|
|
103
|
-
Close the
|
|
97
|
+
Close the renderer before the window. The same pattern works with a
|
|
104
98
|
[pyglet, moderngl, or zengl window](docs/how-to/gl-hosts.md).
|
|
105
99
|
|
|
106
100
|
## Features
|
|
@@ -116,6 +110,8 @@ Close the target, then the renderer, then the window. The same pattern works wit
|
|
|
116
110
|
- **Predictable output:** sRGB (default) or linear. Effects such as antialiasing,
|
|
117
111
|
fog, and bloom are off until you enable them. With default settings, the same scene gives the same pixels on every frame.
|
|
118
112
|
- **Several views per frame:** each `render()` call can take its own camera and viewport, for example for side-by-side stereo.
|
|
113
|
+
- **Picking:** find the model, node, and world position under a pixel of a rendered frame, for
|
|
114
|
+
example where a participant clicked. See [picking](docs/reference/api.md#picking).
|
|
119
115
|
- **Browsers:** the same core compiles to WebAssembly for WebGL2. It renders into a texture that the page's own renderer draws, such as PIXI in PsychoJS. See
|
|
120
116
|
[the web build](docs/explanation/web.md).
|
|
121
117
|
|
|
@@ -126,11 +122,13 @@ See the `examples/` folder.
|
|
|
126
122
|
| Example | Shows |
|
|
127
123
|
| --- | --- |
|
|
128
124
|
| `screenshot.py` | The animation above (`--gif`), or a still image, rendered offscreen. |
|
|
129
|
-
| `psychopy_shared.py` | A model in a PsychoPy window. Pass your own GLB path. |
|
|
130
|
-
| `
|
|
125
|
+
| `psychopy_shared.py` | A model in a PsychoPy window with all the bells and whistles. Pass your own GLB path. |
|
|
126
|
+
| `psychopy_stress.py` | Repeated PsychoPy trials, with frame timing and memory reports. See [measure timing](docs/how-to/stress-test.md). |
|
|
127
|
+
| `pyglet_shared.py`, `moderngl_shared.py`, `zengl_shared.py` | The same (minus the bells and whistles) in other OpenGL hosts. |
|
|
131
128
|
| `drifting_grating.py` | A texture updated every frame on a tilted plane. |
|
|
132
129
|
| `stereo.py` | Two cameras side by side in one window. |
|
|
133
130
|
| `shapes.py` | Generated and deforming meshes. |
|
|
131
|
+
| `pick.py` | Click a model in a pyglet window: picking, with the hit node, point, and latency. `--auto` clicks by itself. |
|
|
134
132
|
|
|
135
133
|
## Documentation
|
|
136
134
|
|
|
@@ -141,8 +139,10 @@ See the `examples/` folder.
|
|
|
141
139
|
[compare with Filament's viewer](docs/how-to/reference-comparison.md),
|
|
142
140
|
[use filly in a web page](docs/how-to/web.md)
|
|
143
141
|
- Reference: [API](docs/reference/api.md), [performance](docs/reference/performance.md),
|
|
144
|
-
[validation](docs/reference/validation.md), [sample assets](docs/reference/sample-assets.md)
|
|
142
|
+
[validation](docs/reference/validation.md), [sample assets](docs/reference/sample-assets.md),
|
|
143
|
+
[upstream Filament issues](docs/reference/upstream-issues.md)
|
|
145
144
|
- Explanation: [design and limits](docs/explanation/design.md),
|
|
145
|
+
[filly and Filament](docs/explanation/filament.md),
|
|
146
146
|
[tested assumptions](docs/explanation/assumptions.md),
|
|
147
147
|
[material precompilation](docs/explanation/material-precompilation.md),
|
|
148
148
|
[the web build](docs/explanation/web.md)
|
|
@@ -15,10 +15,9 @@ such as PsychoPy experiments or pyglet, moderngl, and zengl programs.
|
|
|
15
15
|
|
|
16
16
|
## Install
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
You can install filly from [PyPI](https://pypi.org/project/filly/) or [build it from source](docs/how-to/build.md).
|
|
19
19
|
|
|
20
|
-
It needs Python 3.10 or later, Windows or Linux on x86_64, and an OpenGL 4.1 driver. On Linux,
|
|
21
|
-
offscreen rendering works without a display. macOS is not supported at this time.
|
|
20
|
+
It needs Python 3.10 or later, Windows or Linux on x86_64, and an OpenGL 4.1 driver. On Linux, offscreen rendering works without a display. macOS is not supported at this time.
|
|
22
21
|
|
|
23
22
|
## Render an image
|
|
24
23
|
|
|
@@ -31,10 +30,8 @@ with filly.Renderer() as renderer:
|
|
|
31
30
|
horse = scene.load("examples/assets/wooden_playground_horse.glb")
|
|
32
31
|
horse.apply_animation(0, 1.25) # a pose from its spring animation
|
|
33
32
|
|
|
34
|
-
camera =
|
|
35
|
-
camera.
|
|
36
|
-
camera.frame(horse, direction=(-1, -0.3, -1), aspect=800 / 600)
|
|
37
|
-
scene.camera = camera
|
|
33
|
+
scene.camera = renderer.create_camera()
|
|
34
|
+
scene.camera.frame(horse, direction=(-1, -0.3, -1))
|
|
38
35
|
scene.add_directional_light(direction=(-1, -1, -2), intensity=100_000)
|
|
39
36
|
|
|
40
37
|
target = renderer.create_render_target(width=800, height=600)
|
|
@@ -48,20 +45,18 @@ filly renders into a texture that PsychoPy draws directly, with no copy through
|
|
|
48
45
|
|
|
49
46
|
```python
|
|
50
47
|
from psychopy import core, visual
|
|
51
|
-
from filly.integrations.psychopy import
|
|
48
|
+
from filly.integrations.psychopy import HostTarget, create_renderer
|
|
52
49
|
|
|
53
50
|
win = visual.Window((1024, 768), units="pix")
|
|
54
51
|
renderer = create_renderer(win) # shares the window's OpenGL context
|
|
55
52
|
scene = renderer.create_scene()
|
|
56
53
|
horse = scene.load("examples/assets/wooden_playground_horse.glb")
|
|
57
|
-
camera =
|
|
58
|
-
camera.
|
|
59
|
-
camera.frame(horse, aspect=1)
|
|
60
|
-
scene.camera = camera
|
|
54
|
+
scene.camera = renderer.create_camera()
|
|
55
|
+
scene.camera.frame(horse)
|
|
61
56
|
scene.add_directional_light(direction=(-1, -1, -2), intensity=100_000)
|
|
62
57
|
|
|
63
|
-
target =
|
|
64
|
-
stim = target.
|
|
58
|
+
target = HostTarget(renderer, win, width=512, height=512)
|
|
59
|
+
stim = target.create_image_stim()
|
|
65
60
|
clock = core.Clock()
|
|
66
61
|
while clock.getTime() < 3:
|
|
67
62
|
horse.apply_animation(0, clock.getTime())
|
|
@@ -69,12 +64,11 @@ while clock.getTime() < 3:
|
|
|
69
64
|
stim.draw()
|
|
70
65
|
win.flip()
|
|
71
66
|
|
|
72
|
-
target.close()
|
|
73
67
|
renderer.close()
|
|
74
68
|
win.close()
|
|
75
69
|
```
|
|
76
70
|
|
|
77
|
-
Close the
|
|
71
|
+
Close the renderer before the window. The same pattern works with a
|
|
78
72
|
[pyglet, moderngl, or zengl window](docs/how-to/gl-hosts.md).
|
|
79
73
|
|
|
80
74
|
## Features
|
|
@@ -90,6 +84,8 @@ Close the target, then the renderer, then the window. The same pattern works wit
|
|
|
90
84
|
- **Predictable output:** sRGB (default) or linear. Effects such as antialiasing,
|
|
91
85
|
fog, and bloom are off until you enable them. With default settings, the same scene gives the same pixels on every frame.
|
|
92
86
|
- **Several views per frame:** each `render()` call can take its own camera and viewport, for example for side-by-side stereo.
|
|
87
|
+
- **Picking:** find the model, node, and world position under a pixel of a rendered frame, for
|
|
88
|
+
example where a participant clicked. See [picking](docs/reference/api.md#picking).
|
|
93
89
|
- **Browsers:** the same core compiles to WebAssembly for WebGL2. It renders into a texture that the page's own renderer draws, such as PIXI in PsychoJS. See
|
|
94
90
|
[the web build](docs/explanation/web.md).
|
|
95
91
|
|
|
@@ -100,11 +96,13 @@ See the `examples/` folder.
|
|
|
100
96
|
| Example | Shows |
|
|
101
97
|
| --- | --- |
|
|
102
98
|
| `screenshot.py` | The animation above (`--gif`), or a still image, rendered offscreen. |
|
|
103
|
-
| `psychopy_shared.py` | A model in a PsychoPy window. Pass your own GLB path. |
|
|
104
|
-
| `
|
|
99
|
+
| `psychopy_shared.py` | A model in a PsychoPy window with all the bells and whistles. Pass your own GLB path. |
|
|
100
|
+
| `psychopy_stress.py` | Repeated PsychoPy trials, with frame timing and memory reports. See [measure timing](docs/how-to/stress-test.md). |
|
|
101
|
+
| `pyglet_shared.py`, `moderngl_shared.py`, `zengl_shared.py` | The same (minus the bells and whistles) in other OpenGL hosts. |
|
|
105
102
|
| `drifting_grating.py` | A texture updated every frame on a tilted plane. |
|
|
106
103
|
| `stereo.py` | Two cameras side by side in one window. |
|
|
107
104
|
| `shapes.py` | Generated and deforming meshes. |
|
|
105
|
+
| `pick.py` | Click a model in a pyglet window: picking, with the hit node, point, and latency. `--auto` clicks by itself. |
|
|
108
106
|
|
|
109
107
|
## Documentation
|
|
110
108
|
|
|
@@ -115,8 +113,10 @@ See the `examples/` folder.
|
|
|
115
113
|
[compare with Filament's viewer](docs/how-to/reference-comparison.md),
|
|
116
114
|
[use filly in a web page](docs/how-to/web.md)
|
|
117
115
|
- Reference: [API](docs/reference/api.md), [performance](docs/reference/performance.md),
|
|
118
|
-
[validation](docs/reference/validation.md), [sample assets](docs/reference/sample-assets.md)
|
|
116
|
+
[validation](docs/reference/validation.md), [sample assets](docs/reference/sample-assets.md),
|
|
117
|
+
[upstream Filament issues](docs/reference/upstream-issues.md)
|
|
119
118
|
- Explanation: [design and limits](docs/explanation/design.md),
|
|
119
|
+
[filly and Filament](docs/explanation/filament.md),
|
|
120
120
|
[tested assumptions](docs/explanation/assumptions.md),
|
|
121
121
|
[material precompilation](docs/explanation/material-precompilation.md),
|
|
122
122
|
[the web build](docs/explanation/web.md)
|
|
@@ -264,11 +264,11 @@ path and sets it in gltfio's parse, which cgltf then skips. No GLB is assembled.
|
|
|
264
264
|
| Sponza | 0.88 s | 51 to 55 ms | 0.49 to 0.63 s | 243 to 269 ms |
|
|
265
265
|
| NodePerformanceTest | 3.6 to 3.8 s | 1.4 to 1.8 s | 2.6 to 2.7 s | 2.3 to 2.6 s |
|
|
266
266
|
|
|
267
|
-
- `Renderer.finish()` waits by request. `
|
|
267
|
+
- `Renderer.finish()` waits by request. `RenderTarget.read()` must wait for the readback.
|
|
268
268
|
- `ImportedTarget.close()` must wait for imported textures, because the host can then delete or
|
|
269
269
|
reuse the texture name. For owned targets the wait is conservative.
|
|
270
|
-
- `
|
|
271
|
-
so the wait is conservative. It keeps setup cost out of later frames.
|
|
270
|
+
- `Renderer.load_environment()` and `create_environment()` wait after the prefilter work.
|
|
271
|
+
Filament orders the destroy commands itself, so the wait is conservative. It keeps setup cost out of later frames.
|
|
272
272
|
|
|
273
273
|
**Verdict.** The load wait is not needed for memory safety. It is useful for experiments.
|
|
274
274
|
|
|
@@ -5,10 +5,14 @@ Nanobind converts Python values at the boundary. Filament types stay in the nati
|
|
|
5
5
|
The Windows WGL and Linux GLX adapters are isolated in `native/gl_interop.cpp`, and the Linux
|
|
6
6
|
headless EGL platform in `native/egl_platform.cpp`.
|
|
7
7
|
|
|
8
|
+
For how filly's concepts map to Filament and gltfio, and which code depends on Filament
|
|
9
|
+
internals, see [filly and Filament](filament.md).
|
|
10
|
+
|
|
8
11
|
## Ownership
|
|
9
12
|
|
|
10
13
|
Every native resource retains the engine state. The engine tracks resources through weak references.
|
|
11
|
-
This prevents ownership cycles. A scene retains its models,
|
|
14
|
+
This prevents ownership cycles. A scene retains its models, its added lights, and its current
|
|
15
|
+
camera. Cameras belong to the renderer.
|
|
12
16
|
Objects can keep the engine alive after the Python `Renderer` reference is deleted.
|
|
13
17
|
|
|
14
18
|
`renderer.close()` waits for outstanding work and destroys resources before the engine.
|
|
@@ -18,31 +22,46 @@ Model and light `close()` release scene ownership and queue native destruction.
|
|
|
18
22
|
handles invalidate their nodes, node-local materials, and imported lights. A removed imported light
|
|
19
23
|
keeps its transform entity so animation can continue. Added lights use a separate owner whose
|
|
20
24
|
closed state prevents old handles from referring to a replacement light. `Scene.close()` releases
|
|
21
|
-
the scene's children
|
|
22
|
-
Filament scene, color grading, and environment.
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
25
|
+
the scene's children (a closing model detaches its imported cameras from every scene), and then
|
|
26
|
+
destroys its view, Filament scene, color grading, and the scene's environment light and skybox. Renderer cameras, assets, and
|
|
27
|
+
environments stay open for other scenes.
|
|
28
|
+
|
|
29
|
+
A loaded glTF asset is a shared native object that the renderer owns, as Filament's engine owns
|
|
30
|
+
its textures and buffers. Each model is a gltfio instance of it:
|
|
31
|
+
`AssetLoader::createInstance()` adds entities, material instances, lights, skins, and an
|
|
32
|
+
animator, and reuses vertex buffers, textures, and materials. gltfio 1.77.1 has no way to destroy
|
|
33
|
+
one instance, so closing a model only removes its entities from the scene; the asset and all
|
|
34
|
+
its instances are destroyed when the asset handle and the last model close.
|
|
35
|
+
`createInstance()` needs the asset's source data, about the file size in CPU memory, and each
|
|
36
|
+
instance needs the preparation tables for the custom materials, property animation, cameras, and
|
|
37
|
+
morph weights. An open `Asset` keeps them; `Asset.close()` calls `releaseSourceData()` and drops
|
|
38
|
+
the tables. `scene.load()` is a load, one `add()`, and that release, so a model that nothing
|
|
39
|
+
adds again costs no source data. For DamagedHelmet (3.6 MiB file) the release saves 3.7 MiB of
|
|
40
|
+
private bytes. The process grows by 113 MiB per loaded DamagedHelmet on the tested Intel GPU,
|
|
41
|
+
most of it texture memory that the integrated GPU allocates in system memory, so the saving is
|
|
42
|
+
small for textured assets. Name lookups use each instance's own node table, because gltfio's
|
|
43
|
+
name and entity lists cover all instances.
|
|
44
|
+
|
|
45
|
+
An asset handle replaced `scene.load(source, clonable=True)` and `model.clone(scene=...)`. With
|
|
46
|
+
those, an asset could only be created through a scene: keeping a prototype needed a hidden model
|
|
47
|
+
in some scene, and closing that scene closed the prototype. A clone also copied nothing that a
|
|
48
|
+
new instance lacks: it started from the asset's authored state, as an added model does. The
|
|
49
|
+
asset belongs to the renderer and each model holds it, so any scene of the renderer can add it,
|
|
50
|
+
and closing a scene closes only its own models. Textures that the material provider decodes for
|
|
51
|
+
filly's custom materials are kept by source in the asset's tables, so models share them as they
|
|
52
|
+
share gltfio's textures. Before, each instance decoded them again and kept the copies until the
|
|
53
|
+
asset closed.
|
|
54
|
+
|
|
55
|
+
Loading creates the first instance before `ResourceLoader::loadResources()`, and the first
|
|
56
|
+
`add()` uses it. gltfio uploads morph target data only in `loadResources()`, which runs once per
|
|
57
|
+
asset, and only for the instances that exist then; an asset loaded without instances gave every
|
|
58
|
+
model empty morph targets. Later instances get their
|
|
59
|
+
morph target positions from the source data and their morph tangent frames from gltfio's own
|
|
60
|
+
`TangentsJob`, under the same conditions as `loadResources()`, so every model of an asset
|
|
61
|
+
morphs the same. A test compares the lit, morphed first and second models pixel by pixel.
|
|
62
|
+
Before, clones had empty morph targets.
|
|
63
|
+
|
|
64
|
+
`Asset.memory` and `Model.memory` estimate what the asset holds. gltfio lists no textures or buffers, so the
|
|
46
65
|
estimate collects them itself: a texture provider wrapper records the textures that gltfio's
|
|
47
66
|
providers create, and each is sized from its internal format, dimensions, and mip levels. Vertex,
|
|
48
67
|
index, morph target, and bone buffers are sized at preparation from the glTF accessors with the
|
|
@@ -88,8 +107,13 @@ the warning, and Filament has no other switch. A 60 Hz loop, or a 1 ms sleep per
|
|
|
88
107
|
log it. In embedded mode, the host owns the window and every
|
|
89
108
|
display-buffer swap. See [tested assumptions](assumptions.md).
|
|
90
109
|
|
|
91
|
-
Shadows,
|
|
92
|
-
scene properties and disabled by default. The PsychoPy demo enables
|
|
110
|
+
Shadows, FXAA, MSAA, SSAO, bloom, dithering, fog, depth of field, and vignette are
|
|
111
|
+
scene properties and disabled by default. The PsychoPy demo enables FXAA. Screen-space
|
|
112
|
+
refraction is not an option. Every scene has it on: transmission and volume materials render as
|
|
113
|
+
flat white without it, and Filament runs the pass only in frames that draw such a material.
|
|
114
|
+
Until October 2026 it was a scene property. That property looked like a render option, but no
|
|
115
|
+
material depended on it; only a load-time warning did, which gave the rule "set it before
|
|
116
|
+
loading glass".
|
|
93
117
|
Temporal antialiasing, TAA upscaling, screen-space reflections, and dynamic resolution are not
|
|
94
118
|
exposed. In Filament 1.77.1 `PostProcessManager`, TAA and screen-space reflections import the
|
|
95
119
|
previous frame's history buffer (`FrameHistory::getPrevious()`), and dynamic resolution scales
|
|
@@ -149,7 +173,7 @@ the UV sets of the glTF textures. Nothing is compiled.
|
|
|
149
173
|
A generated mesh is loaded as a one-triangle glTF with the requested material and attributes,
|
|
150
174
|
then its renderable gets the mesh's own vertex and index buffers (`setGeometryAt()`). The loader
|
|
151
175
|
therefore gives it the same material as a glTF file with those factors, and the model gets
|
|
152
|
-
nodes, material handles,
|
|
176
|
+
nodes, material handles, and closing without a second code path. The placeholder's
|
|
153
177
|
accessor bounds are the mesh bounds. Positions, tangent frames, UVs, and colors are separate
|
|
154
178
|
vertex buffers, so an update uploads only what changed. A mesh without colors gets a white
|
|
155
179
|
`UBYTE4` color buffer, and `UV1` reads the `UV0` buffer: archive entries serve every material,
|
|
@@ -159,12 +183,13 @@ its method that writes into the staging buffer; a test checks that both give the
|
|
|
159
183
|
Missing normals are area-weighted vertex normals, because shared vertices of an indexed mesh
|
|
160
184
|
should be smooth; flat faces need their own vertices.
|
|
161
185
|
|
|
162
|
-
|
|
186
|
+
An imported texture (`ImportedTexture`) uses the same fences as a shared render target, in the
|
|
187
|
+
other direction.
|
|
163
188
|
Leaving `write()` places a host fence, and the next render of a scene whose models sample the
|
|
164
189
|
texture queues a Filament-side wait on it before the frame. After such a render, Filament places
|
|
165
|
-
one fence, which the texture shares with the target and with the other sampled
|
|
190
|
+
one fence, which the texture shares with the target and with the other sampled imported textures,
|
|
166
191
|
and entering `write()` queues a host-side wait on it. Each Filament fence costs a `glFlush()` on
|
|
167
|
-
Filament's thread, so a render places at most one. A scene that does not sample
|
|
192
|
+
Filament's thread, so a render places at most one. A scene that does not sample an imported texture
|
|
168
193
|
neither waits for its writes nor fences it. Neither side waits on the CPU.
|
|
169
194
|
|
|
170
195
|
Two kinds of per-frame work run only when something changed. A camera whose projection follows
|
|
@@ -182,10 +207,33 @@ from the bind frame moves it away from the geometry. Preparation computes the bo
|
|
|
182
207
|
asset: accessor boxes through node transforms for rigid meshes, and every vertex skinned with the
|
|
183
208
|
rest pose for skinned meshes. A 1,000,000-vertex skinned mesh loaded in 120 ms against 65 ms
|
|
184
209
|
for the same mesh without a skin; the difference also includes gltfio's own skin work and the
|
|
185
|
-
joint and weight uploads (Iris Xe laptop, September 30, 2026).
|
|
210
|
+
joint and weight uploads (Iris Xe laptop, September 30, 2026). Models of an asset share the box, and
|
|
186
211
|
frames do no bounds work. The same rest-pose box, in the mesh node's frame, replaces gltfio's
|
|
187
212
|
culling box for skinned renderables, which had the same fault and could cull a visible mesh.
|
|
188
213
|
|
|
214
|
+
gltfio also has `FilamentInstance::recomputeBoundingBoxes()`. It skins every vertex with the
|
|
215
|
+
joints' current transforms and sets the culling boxes of one instance. filly does not use it.
|
|
216
|
+
A prototype that called it once per asset and copied the boxes to later models gave these results
|
|
217
|
+
(October 12, 2026, Iris Xe laptop):
|
|
218
|
+
|
|
219
|
+
- It ignores morph targets on skinned primitives. The box of a skinned mesh does not include its
|
|
220
|
+
morph targets.
|
|
221
|
+
- It reads `JOINTS_0` and `WEIGHTS_0` without checks. A skinned primitive without these
|
|
222
|
+
attributes stopped the process with an access violation; filly loads it. A joint index past
|
|
223
|
+
the end of the skin's joint list reads outside an array.
|
|
224
|
+
- One large primitive is slower: 149 ms against 54 ms for a skinned primitive with 1,000,000
|
|
225
|
+
vertices. gltfio runs one job per primitive, so the same vertices in 8 primitives took 34 ms
|
|
226
|
+
against 58 ms.
|
|
227
|
+
- It reads every position of rigid meshes: 6 ms for 1,000,000 vertices, against less than
|
|
228
|
+
0.1 ms for accessor bounds. In the Khronos sample assets, the accessor bounds gave the same
|
|
229
|
+
boxes as the data.
|
|
230
|
+
- It joins the primitive boxes of a mesh in the node's frame. filly moves each primitive box into
|
|
231
|
+
the model's frame first, which gives a smaller box under a rotated node.
|
|
232
|
+
|
|
233
|
+
On the 149 Khronos sample assets that load (of 150) and the horse demo, `Model.bounds` and
|
|
234
|
+
`Node.bounds` from the two methods agreed within 0.01% of the box size, except in three assets
|
|
235
|
+
with rotated multi-primitive meshes. There, filly's boxes were smaller, by up to 0.14% of the model and 10% of one node.
|
|
236
|
+
|
|
189
237
|
## Output encoding
|
|
190
238
|
|
|
191
239
|
The encoding does not depend on other options. Earlier, disabling postprocessing wrote linear
|
|
@@ -211,7 +259,7 @@ value is encoded:
|
|
|
211
259
|
| --- | --- | --- |
|
|
212
260
|
| None (default) | The scene view, rendering straight into the RGBA16F buffer | No |
|
|
213
261
|
| MSAA | Filament's multisampled RGB16F buffer, resolved and copied into the RGBA16F buffer | No |
|
|
214
|
-
| Refraction, SSAO, shadows, fog | Their passes; the color pass as without them | No |
|
|
262
|
+
| Refraction materials (transmission, volume), SSAO, shadows, fog | Their passes; the color pass as without them | No |
|
|
215
263
|
| Transparent view | Filament's RGBA16F color buffer, blended into the RGBA16F buffer | No |
|
|
216
264
|
| FXAA | Nothing: filly's FXAA pass runs after the encode pass, on the encoded image | No |
|
|
217
265
|
| Dithering | Nothing: the encode pass adds the noise after the transfer function | No |
|
|
@@ -376,7 +424,8 @@ setting has no effect there.
|
|
|
376
424
|
|
|
377
425
|
## Shared OpenGL textures
|
|
378
426
|
|
|
379
|
-
|
|
427
|
+
Filament's desktop platforms (WGL, GLX, and EGL) do not implement external sync creation; only
|
|
428
|
+
Android's EGL platform does. See [upstream issues](../reference/upstream-issues.md). The wrapper supplies
|
|
380
429
|
a small WGL, GLX, or EGL platform subclass through Filament's public platform interface, for
|
|
381
430
|
offscreen engines too. It places GL fences, waits, and the `GL_FRAMEBUFFER_SRGB` setting in the Filament
|
|
382
431
|
command stream through `Engine::createSync()`.
|
|
@@ -394,7 +443,23 @@ waited for it; its release replaces the unconsumed host fence.
|
|
|
394
443
|
This orders both producer and consumer GPU work. Normal frames do not use `glFinish()` or CPU
|
|
395
444
|
framebuffer copies. Explicit shutdown and image readback can block for completion.
|
|
396
445
|
|
|
397
|
-
The
|
|
446
|
+
The adapters own OpenGL objects that the renderer does not know: the host texture, a draw
|
|
447
|
+
program, and PsychoPy stimuli. An imported target therefore takes an optional callback, which
|
|
448
|
+
`Renderer.close()` calls before it releases anything else, while the host context can still be
|
|
449
|
+
made current. Each adapter passes a callback that closes its target. The callback holds the
|
|
450
|
+
target weakly: the renderer holds the callback, and a strong reference would form a cycle
|
|
451
|
+
through native objects that Python's garbage collector cannot see. The callbacks run in
|
|
452
|
+
`Renderer.close()` only, not when the renderer's state is destroyed: each target holds that
|
|
453
|
+
state, so no target exists then. Thus the renderer's close leaves no host objects behind, and
|
|
454
|
+
only the window must close last.
|
|
455
|
+
|
|
456
|
+
`draw()` blends premultiplied source-over by default in every adapter, because that is what
|
|
457
|
+
Filament writes. The pyglet and moderngl adapters draw a quad in native code that saves and
|
|
458
|
+
restores the OpenGL state that it changes. moderngl cannot read its blend state back, and
|
|
459
|
+
libraries that cache OpenGL state need it restored. A blit, which the pyglet adapter used
|
|
460
|
+
before, cannot blend.
|
|
461
|
+
|
|
462
|
+
The PsychoPy adapter draws with `ImageStim` and owns the host texture.
|
|
398
463
|
Its draw method runs inside `acquire()`. PsychoPy remains responsible for
|
|
399
464
|
`win.flip()`. PsychoPy skips a context switch when it believes its window is current, which is
|
|
400
465
|
wrong after other code switched contexts through pyglet, so the adapter also checks pyglet's
|
|
@@ -420,8 +485,12 @@ adapter converts sampled RGB to straight RGB in a shader, before ImageStim appli
|
|
|
420
485
|
The temporary shader selection is restored after each draw so regular PsychoPy stimuli keep their
|
|
421
486
|
normal rendering behavior. The host window uses average blending.
|
|
422
487
|
|
|
423
|
-
The adapter
|
|
424
|
-
|
|
488
|
+
The adapter uses PsychoPy's context and texture internals, so each PsychoPy version needs its
|
|
489
|
+
own test. It does not use texture providers (an `image` with `colorTexture`), which
|
|
490
|
+
`ImageStim` takes in PsychoPy 2026.2 but not in 2024.2. It gives `ImageStim` a placeholder
|
|
491
|
+
image and then replaces the texture name. The legacy GL path records the program and the
|
|
492
|
+
texture in a display list, so the adapter rebuilds the list in its first draw, with the
|
|
493
|
+
premultiplied program in place. Other backends need separate tests.
|
|
425
494
|
|
|
426
495
|
## Dependencies
|
|
427
496
|
|
|
@@ -479,7 +548,7 @@ A load has these steps:
|
|
|
479
548
|
7. `AssetLoader::createInstance()` with index markers (below), then `ResourceLoader`.
|
|
480
549
|
|
|
481
550
|
The loaded buffer memory lives with the asset until gltfio releases its source data, which is
|
|
482
|
-
|
|
551
|
+
until the `Asset` closes; `scene.load()` releases it after its one model.
|
|
483
552
|
|
|
484
553
|
gltfio identifies nothing by glTF index. Its `MaterialProvider` callbacks get a key, a UV map, a
|
|
485
554
|
label (the material name), and the extras text, and its entity lists are not in node order. While
|
|
@@ -536,16 +605,20 @@ the pinned SDK implements this path despite older wording in its LightManager he
|
|
|
536
605
|
map size and bias settings use the same options for authored and imported lights. Shadows remain
|
|
537
606
|
explicit opt-in at both scene and light levels.
|
|
538
607
|
|
|
539
|
-
|
|
540
|
-
|
|
608
|
+
An `Environment` holds the source environment cubemap and the filtered IBL textures, and the
|
|
609
|
+
renderer owns it. Each scene that uses it builds its own IndirectLight and Skybox from those
|
|
610
|
+
textures, because the IndirectLight holds the intensity and rotation, which are scene settings.
|
|
611
|
+
Building them took 0.03 to 0.3 ms per scene, where filtering the panorama again took 54 to 83 ms.
|
|
612
|
+
Visibility attaches or detaches the skybox without changing lighting. IndirectLight's
|
|
541
613
|
rotation controls both the skybox and native IBL; custom diffuse backlighting receives the same
|
|
542
|
-
rotation update.
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
614
|
+
rotation update. Removing or replacing a scene's environment destroys only that scene's light and
|
|
615
|
+
skybox; closing the environment removes it from every scene first, then destroys its
|
|
616
|
+
textures. A visible skybox writes opaque background pixels even in a transparent view.
|
|
617
|
+
|
|
618
|
+
Native [glTF preparation](#gltf-preparation) decodes meshopt buffers and expands
|
|
619
|
+
`EXT_mesh_gpu_instancing` before gltfio loads the document. Instance children share mesh resources and
|
|
620
|
+
material instances, so Filament's automatic instancing, which every renderer turns on, draws up
|
|
621
|
+
to 64 of them per call (8 in WebGL). Node visibility is evaluated in parent-first order
|
|
549
622
|
without per-frame allocation and controls scene membership for meshes and lights.
|
|
550
623
|
|
|
551
624
|
Volume materials use filly's solid refraction entries. Filament 1.77.1 passes only a mesh
|
|
@@ -583,14 +656,15 @@ cubemap is used as is, and the spherical harmonics in its metadata supply diffus
|
|
|
583
656
|
comes from the roughest reflection level. Filament's KTX parser and upload abort on malformed
|
|
584
657
|
input, so the wrapper checks the file structure first. The custom diffuse-transmission material
|
|
585
658
|
needs an irradiance cubemap; it is filtered from a full-mip copy of the IBL's sharpest level.
|
|
586
|
-
The filter runs only when a model with that material is in
|
|
587
|
-
|
|
659
|
+
The filter runs only when a model with that material is in a scene that uses the environment,
|
|
660
|
+
when the scene gets the environment or the model, whichever comes later, and once per
|
|
661
|
+
environment: it cost about 60 ms, plus about 35 ms
|
|
588
662
|
to build the filter once per renderer (256-pixel IBL, desktop). It performs GPU work and a
|
|
589
663
|
completion wait during setup. Each prefilter object builds its material on first use. Shader
|
|
590
664
|
compilation in the driver on first use, image decoding, and environment filtering belong outside
|
|
591
665
|
a trial's frame loop.
|
|
592
666
|
The renderer creates Filament's IBL prefilter objects once and reuses them. When they were
|
|
593
|
-
destroyed after each call, models loaded after
|
|
667
|
+
destroyed after each call, models loaded after an environment could render black on
|
|
594
668
|
Intel and NVIDIA. The cause inside Filament is not isolated.
|
|
595
669
|
|
|
596
670
|
glTF animation restores authored transforms and morph weights before evaluating a clip, which
|
|
@@ -616,7 +690,8 @@ the scan over a model's copies allocates nothing.
|
|
|
616
690
|
Camera projection and UV transform tracks update grouped values, then rebuild each matrix once
|
|
617
691
|
after channel evaluation. This prevents channel order from producing invalid intermediate clip
|
|
618
692
|
planes or overwriting another UV channel. Imported camera handles borrow glTF-owned components.
|
|
619
|
-
|
|
693
|
+
`Asset.close()` releases only the source data. Closing a model closes its imported cameras and
|
|
694
|
+
detaches them from every scene; the components stay until the asset goes.
|
|
620
695
|
|
|
621
696
|
The build follows [nanobind's packaging interface](https://nanobind.readthedocs.io/en/latest/packaging.html)
|
|
622
697
|
with CMake and scikit-build-core. Windows links the pinned release SDK. Linux builds the same
|
|
@@ -658,7 +733,7 @@ UBO fence reclamation while the driver waits for more commands. A wait without a
|
|
|
658
733
|
(`FENCE_WAIT_FOR_EVER`, a `time_point::max()` deadline) uses an untimed wait. The converted
|
|
659
734
|
deadline overflows, so `wait_until()` returns at once; Filament's frame-info thread then spins on
|
|
660
735
|
the fence mutex and starves the driver thread that would signal the fence. The first
|
|
661
|
-
`
|
|
736
|
+
`RenderTarget.read()` after a render hung this way. The corrections preserve asynchronous
|
|
662
737
|
rendering and do not add GPU completion waits.
|
|
663
738
|
|
|
664
739
|
Shared GLX contexts use the host's X display connection. Opening a separate connection can
|