Skip to content

Add opt-in Dawn and MetalANGLE backends - #8577

Open
danoli3 wants to merge 4 commits into
openframeworks:masterfrom
danoli3:dawn-metalangle
Open

danoli3 wants to merge 4 commits into
openframeworks:masterfrom
danoli3:dawn-metalangle

Conversation

@danoli3

@danoli3 danoli3 commented Oct 10, 2026 •

Copy link
Copy Markdown
Member

Summary

Adds two opt-in Apple backends. The default window stays desktop OpenGL and iOS GLES, and neither Dawn nor MetalANGLE is on the default link line.

  • OF_USE_DAWN=1 selects WebGPU through Dawn on a CAMetalLayer: ofAppDawnWindow on macOS and ofxiOSDawnView on iOS. The renderer batches meshes, polylines, paths, 3D primitives, images, video frames, bitmap strings, and TrueType strings. Custom shaders and GPU instancing are not part of that renderer.
  • OF_USE_ANGLE=1 selects MetalANGLE + MGLKit. iOS runs the same graphicsExample scene on OpenGL ES 1.1, 2.0, and 3.0. macOS opens ofAppMGLWindow. The two flags cannot both be 1.
  • Official Chromium ANGLE (OF_USE_ANGLE_OFFICIAL, libEGL + libGLESv2, no MGLKit) was timed on the iOS simulator. That window is a local build and is not in these commits. It cannot be combined with OF_USE_DAWN or OF_USE_ANGLE.

examples/gl/aquariumExample is the WebGL Aquarium scene (one draw per fish). On Dawn that example uses its own WGSL pipelines and retained buffers, not the generic renderer batch. examples/gl/backendBenchExample is a separate logo stress test (mesh, immediate, and an in-example ofxShapeBatchRenderer). It is not part of core. Aquarium images and meshes are not in git. examples/gl/aquariumExample/scripts/fetch_assets.py downloads them.

How to enable

macOS make:

make Debug OF_USE_DAWN=1
make Debug OF_USE_ANGLE=1

macOS Xcode, in scripts/templates/macos/Project.xcconfig, uncomment one pair:

GCC_PREPROCESSOR_DEFINITIONS = $(inherited) OF_USE_DAWN=1
OTHER_LDFLAGS = $(OF_CORE_LIBS) $(OF_STATIC_LIBS) $(OF_CORE_FRAMEWORKS) $(OF_DAWN_LIBS) $(OF_APP_OF_LIB)
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) OF_USE_ANGLE=1
OTHER_LDFLAGS = $(OF_CORE_LIBS) $(OF_STATIC_LIBS) $(OF_ANGLE_APP_FRAMEWORKS) $(OF_ANGLE_LIBS) $(OF_APP_OF_LIB)

iOS, pass OF_USE_DAWN=1 or OF_USE_ANGLE=1 on the xcodebuild command line so the static lib and the app match, and set the app OTHER_LDFLAGS to $(OF_DAWN_LIBS) or $(OF_ANGLE_APP_FRAMEWORKS) $(OF_ANGLE_LIBS). OF_TEST_GLES_VERSION selects 1, 2, or 3 for ANGLE. Rebuild openFrameworksLib with the same flag. Do not pass OTHER_LDFLAGS on the xcodebuild command line.

Dawn and MetalANGLE binaries come from apothecary. This PR does not move the scripts/apothecary pointer.

Test

Host: arm64 Apple M1 Max, macOS 26.6.2, Xcode 27.0 (27A266a).

iOS device: iPhone 17 Pro simulator, iOS 26.5, id 510B19CE-1ECE-4B16-AEE2-8AB232AC2F07. The physical iPad was offline, so the iOS rows are simulator numbers.

macOS Dawn

cd examples/graphics/graphicsExample
make Debug OF_USE_DAWN=1
open bin/graphicsExample_debug.app
[notice ] ofAppDawnWindow: CAMetalLayer attached scale=1
[notice ] ofWebGPURenderer: Dawn device ready 1024x768 @1

iOS Dawn

apps/templates/ios/Project.xcconfig link line for this run:

OTHER_LDFLAGS = $(OF_CORE_LIBS) $(OF_CORE_FRAMEWORKS) $(OF_DAWN_LIBS) $(LIB_OF_DEBUG)
cd apps/templates/ios
xcodebuild -project emptyExample.xcodeproj -scheme emptyExample -configuration Debug \
  -destination 'platform=iOS Simulator,id=510B19CE-1ECE-4B16-AEE2-8AB232AC2F07' \
  GCC_PREPROCESSOR_DEFINITIONS='GLES_SILENCE_DEPRECATION=1 GLM_FORCE_CTOR_INIT GLM_ENABLE_EXPERIMENTAL OF_USE_DAWN=1' \
  ENABLE_BITCODE=NO IPHONEOS_DEPLOYMENT_TARGET=15.0 ONLY_ACTIVE_ARCH=YES build
xcrun simctl install 510B19CE-1ECE-4B16-AEE2-8AB232AC2F07 \
  <derivedData>/Build/Products/Debug-iphonesimulator/emptyExample.app
xcrun simctl launch --console 510B19CE-1ECE-4B16-AEE2-8AB232AC2F07 emptyExample
[notice ] ofWebGPURenderer: Dawn device ready 1206x2622 @3

1206x2622 @3 is that simulator's framebuffer.

iOS ANGLE, OpenGL ES 1.1, 2.0, and 3.0

Same project and simulator. Link line:

OTHER_LDFLAGS = $(OF_CORE_LIBS) $(OF_ANGLE_APP_FRAMEWORKS) $(OF_ANGLE_LIBS) $(LIB_OF_DEBUG)

OF_TEST_GLES_VERSION was 1, then 2, then 3. The static lib was rebuilt with the same flag each time.

cd apps/templates/ios
xcodebuild -project emptyExample.xcodeproj -scheme emptyExample -configuration Debug \
  -destination 'platform=iOS Simulator,id=510B19CE-1ECE-4B16-AEE2-8AB232AC2F07' \
  GCC_PREPROCESSOR_DEFINITIONS='GLES_SILENCE_DEPRECATION=1 GLM_FORCE_CTOR_INIT GLM_ENABLE_EXPERIMENTAL OF_USE_ANGLE=1 OF_TEST_GLES_VERSION=1' \
  ENABLE_BITCODE=NO IPHONEOS_DEPLOYMENT_TARGET=15.0 ONLY_ACTIVE_ARCH=YES build
xcrun simctl launch --console 510B19CE-1ECE-4B16-AEE2-8AB232AC2F07 emptyExample

ES 1.1:

[notice ] ofxiOSMGLView: ANGLE context ready OpenGL ES 1.1.0 (ANGLE 2.1.0.ec925142edeb) renderer ANGLE (Metal Renderer: Apple iOS simulator GPU) 1206x2622

ES 2.0 (OF_TEST_GLES_VERSION=2):

[notice ] ofxiOSMGLView: ANGLE context ready OpenGL ES 2.0.0 (ANGLE 2.1.0.ec925142edeb) renderer ANGLE (Metal Renderer: Apple iOS simulator GPU) 1206x2622

ES 3.0 (OF_TEST_GLES_VERSION=3):

[notice ] ofxiOSMGLView: ANGLE context ready OpenGL ES 3.0.0 (ANGLE 2.1.0.ec925142edeb) renderer ANGLE (Metal Renderer: Apple iOS simulator GPU) 1206x2622

Each ANGLE launch also printed the duplicate-class warning for EAGLContext (the stub in the app and the system OpenGLES framework). The context that came up is the ANGLE Metal line above. Requesting programmable-renderer minor version 1 on the ES 3 context still reported OpenGL ES 3.0.0.

Aquarium

examples/gl/aquariumExample. Outside view, pretty (normal maps and reflection), vsync off, 600 frames timed from the aquarium scenes= line to SHOT. One drawElements per fish. Dawn logs pipelines=ok and draws=609 at 500 fish, draws=20109 at 20,000 fish.

macOS, 1024×1024:

cd examples/gl/aquariumExample
python3 scripts/fetch_assets.py
make Debug
bin/aquariumExample_debug.app/Contents/MacOS/aquariumExample_debug --view 1 --pretty --novsync --fish 500 --shot 600

Repeat with make Debug OF_USE_ANGLE=1 and make Debug OF_USE_DAWN=1.

Backend 500 fish 20,000 fish
Desktop OpenGL 225.898 fps (2.6561 s) 18.909 fps (31.7308 s)
MetalANGLE GLES 3.0.0 (ANGLE 2.1.0.ec925142edeb), Apple M1 Max 175.973 fps (3.4096 s) 31.684 fps (18.9368 s)
Dawn WGSL retained buffers 208.327 fps (2.8801 s) 10.406 fps (57.6593 s)

ANGLE log:

[notice ] ofAppMGLWindow: ANGLE context ready OpenGL ES 3.0.0 (ANGLE 2.1.0.ec925142edeb) renderer ANGLE (Metal Renderer: Apple M1 Max) 1024x1024 scale=1

Dawn log:

[notice ] ofWebGPURenderer: Dawn device ready 1024x1024 @1
aquarium dawn meshes=38 textures=57 pipelines=ok
aquarium dawn draws=609

The macOS ANGLE and Dawn timing scripts killed the process after SHOT (rc=-9). That is the harness, not a crash. Dawn skipped ofSaveScreen.

iOS used the same scene compiled into apps/templates/ios emptyExample, then the template was restored to GLES. --novsync does not disable the iOS display link, so a result near 59 fps is that cap. GLES ran at point size on the software renderer. MetalANGLE, official ANGLE, and Dawn used the retina drawable. No iOS screenshot was written.

Backend Pixels 500 fish 20,000 fish
GLES, Apple Software Renderer, OpenGL ES 3.0 APPLE-23.1.1 402×874 6.286 fps (95.4459 s) 1.488 fps (403.3014 s)
MetalANGLE 2.1.0.ec925142edeb, Apple iOS simulator GPU 1206×2622 59.164 fps (10.1413 s) 19.997 fps (30.0039 s)
Dawn, pipelines ok, 609 / 20109 draws 1206×2622 59.866 fps (10.0224 s) 7.624 fps (78.7028 s)
Official ANGLE 2.1.1 7e8009eb2c42, Apple iOS simulator GPU 1206×2622 59.149 fps (10.1438 s) 19.973 fps (30.0408 s)

Official ANGLE log, same simulator, GLES 3, outside view, pretty, 600 frames:

[notice ] ofANGLEContext: EGL 1.5 GLES 3.0
[notice ] ofxiOSANGLEView: ANGLE context ready OpenGL ES 3.0 (ANGLE 2.1.1 git hash: 7e8009eb2c42) renderer ANGLE (Apple, ANGLE Metal Renderer: Apple iOS simulator GPU, Version 26.5 (Build 23F77)) 1206x2622
[notice ] aquarium: gl=OpenGL ES 3.0 (ANGLE 2.1.1 git hash: 7e8009eb2c42) renderer=ANGLE (Apple, ANGLE Metal Renderer: Apple iOS simulator GPU, Version 26.5 (Build 23F77))
SHOT /tmp/aquarium-shot.png fish=500 view=Outside 1 frames=600 sec=10.1438 avg_fps=59.149 px=1206x2622
SHOT /tmp/aquarium-shot.png fish=20000 view=Outside 1 frames=600 sec=30.0408 avg_fps=19.973 px=1206x2622

These framebuffers are not the same size as each other or as the macOS 1024×1024 run.

Pictures

Short clips from the macOS runs on this Apple M1 Max. Aquarium is 500 fish, outside view, pretty, 1024×1024. The logo bench is one mesh of 20,000 triangles, 1024×768. Each GIF is 16 frames at 10 fps. The files live on danoli3/openFrameworks branch pr-8577-media, not in this pull request.

Dawn aquarium for this capture logged pipelines=ok and draws=609. MetalANGLE aquarium was OpenGL ES 3.0.0 (ANGLE 2.1.0.ec925142edeb), renderer ANGLE (Metal Renderer: Apple M1 Max).

Aquarium

OpenGL

Aquarium, desktop OpenGL

Aquarium moving, desktop OpenGL

MetalANGLE

Aquarium, MetalANGLE

Aquarium moving, MetalANGLE

Dawn

Aquarium, Dawn

Aquarium moving, Dawn

openFrameworks logo

OpenGL

Logo, desktop OpenGL

Logo moving, desktop OpenGL

MetalANGLE

Logo, MetalANGLE

Logo moving, MetalANGLE

Dawn

Logo, Dawn

Logo moving, Dawn

@danoli3 danoli3 added this to the 0.13.0 milestone Oct 12, 2026
@danoli3

danoli3 commented Oct 12, 2026

Copy link
Copy Markdown
Member Author

@ofTheo do we think this level of feature should be 13.0 ?

@danoli3

danoli3 commented Oct 13, 2026

Copy link
Copy Markdown
Member Author

Aquarium timing is in the PR description, and the example sources are on dawn-metalangle (88440b53e). Assets are not in the commit. examples/gl/aquariumExample/scripts/fetch_assets.py downloads them.

Apple M1 Max, macOS 26.6.2, Xcode 27.0. Outside view, pretty, vsync off, 600 frames after the scene line. Dawn is the WGSL retained-buffer path (pipelines=ok, 609 draws at 500 fish, 20109 at 20,000).

macOS 1024×1024:

Backend 500 fish 20,000 fish
Desktop OpenGL 225.898 fps (2.6561 s) 18.909 fps (31.7308 s)
MetalANGLE GLES 3.0.0 (ANGLE 2.1.0.ec925142edeb), Apple M1 Max 175.973 fps (3.4096 s) 31.684 fps (18.9368 s)
Dawn 208.327 fps (2.8801 s) 10.406 fps (57.6593 s)

iPhone 17 Pro simulator, iOS 26.5, 510B19CE-1ECE-4B16-AEE2-8AB232AC2F07. --novsync does not disable the display link. GLES is the software renderer at 402×874. ANGLE and Dawn are 1206×2622. No iOS screenshot.

Backend 500 fish 20,000 fish
GLES, OpenGL ES 3.0 APPLE-23.1.1 6.286 fps (95.4459 s) 1.488 fps (403.3014 s)
MetalANGLE, Apple iOS simulator GPU 59.164 fps (10.1413 s) 19.997 fps (30.0039 s)
Dawn 59.866 fps (10.0224 s) 7.624 fps (78.7028 s)

examples/gl/backendBenchExample is the separate logo bench and is not part of this table.

@danoli3

danoli3 commented Oct 13, 2026

Copy link
Copy Markdown
Member Author

Pictures

Short clips from the macOS runs on this Apple M1 Max. Aquarium is 500 fish, outside view, pretty, 1024×1024. The logo bench is one mesh of 20,000 triangles, 1024×768. Each GIF is 16 frames at 10 fps. The files live on danoli3/openFrameworks branch pr-8577-media, not in this pull request.

Dawn aquarium for this capture logged pipelines=ok and draws=609. MetalANGLE aquarium was OpenGL ES 3.0.0 (ANGLE 2.1.0.ec925142edeb), renderer ANGLE (Metal Renderer: Apple M1 Max).

Aquarium

OpenGL

Aquarium, desktop OpenGL

Aquarium moving, desktop OpenGL

MetalANGLE

Aquarium, MetalANGLE

Aquarium moving, MetalANGLE

Dawn

Aquarium, Dawn

Aquarium moving, Dawn

openFrameworks logo

OpenGL

Logo, desktop OpenGL

Logo moving, desktop OpenGL

MetalANGLE

Logo, MetalANGLE

Logo moving, MetalANGLE

Dawn

Logo, Dawn

Logo moving, Dawn

Default windows stay desktop OpenGL and iOS GLES. OF_USE_DAWN=1 selects a WebGPU renderer on a CAMetalLayer (ofAppDawnWindow on macOS, ofxiOSDawnView on iOS) and draws the graphicsExample shapes. OF_USE_ANGLE=1 selects iOS MetalANGLE + MGLKit for OpenGL ES 1.1, 2.0, and 3.0. The two flags cannot both be set, and neither library is on the default link line.
OF_USE_ANGLE=1 on the desktop opens an MGLKit window. Dawn draws meshes, images, text, and video frames. The aquarium example keeps a separate WGSL path with retained buffers and one draw per fish. Aquarium image and mesh assets stay out of git; scripts/fetch_assets.py downloads them.
CI builds Release and produces openFrameworkstvOS.a. The template was still linking openFrameworkstvOSDebug.a and Dawn.
Dawn and MetalANGLE are no longer in the core libs packages; apothecary
publishes them per library and target on its latest-modular release
(oF_<lib>_<target>_<arch>[_gcc<N>].tar.bz2, .zip for Visual Studio).

- scripts/dev/download_modules.sh: downloads modules for a platform,
  verifies SHA-256 against the release digests and unpacks them into
  libs/; Apple installs every Apple target that is published, and modules
  not built for a platform yet are skipped (--require to fail instead)
- download_libs.sh --modules dawn,metalangle (or OF_LIB_MODULES) runs it
  after the core libs, so the platform scripts take it too
- paths: modular packages install per target (lib/macos, lib/ios,
  lib/tvos) and MetalANGLE as MetalANGLE.xcframework; the core xcconfigs,
  makefiles and the macOS template's core build script now point there
- CI: build-macos-renderers downloads the modules and builds
  backendBenchExample with OF_USE_DAWN=1 and OF_USE_ANGLE=1
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.

1 participant