diff --git a/.github/workflows/of.yml b/.github/workflows/of.yml index 58b7ba5ef3f..27befcf7c97 100644 --- a/.github/workflows/of.yml +++ b/.github/workflows/of.yml @@ -438,6 +438,33 @@ jobs: DEVELOPER_DIR: "/Applications/Xcode.app/Contents/Developer" SDKROOT: "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk" + # Dawn / MetalANGLE are optional modules (apothecary latest-modular), not in the + # core libs package: download them and build with each renderer + build-macos-renderers: + runs-on: macos-15 + strategy: + fail-fast: false + matrix: + renderer: [DAWN, ANGLE] + steps: + - uses: actions/checkout@v6 + - name: ccache + uses: hendrikmuhs/ccache-action@v1.2.23 + with: + key: osx-renderer-${{ matrix.renderer }} + - name: Download libs and modules + run: ./scripts/osx/download_libs.sh -t $RELEASE --modules dawn,metalangle + - name: Build backendBenchExample with OF_USE_${{ matrix.renderer }}=1 + run: | + set -e + cd examples/gl/backendBenchExample + cp ../../../scripts/templates/osx/Makefile ../../../scripts/templates/osx/config.make . + make -j3 Release OF_USE_${{ matrix.renderer }}=1 + ls -d bin/backendBenchExample.app + env: + DEVELOPER_DIR: "/Applications/Xcode.app/Contents/Developer" + SDKROOT: "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk" + # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - build-android: runs-on: ubuntu-24.04 diff --git a/addons/ofxiOS/src/app/ofAppiOSWindow.mm b/addons/ofxiOS/src/app/ofAppiOSWindow.mm index 53a3856e6d4..81b68fa82fd 100644 --- a/addons/ofxiOS/src/app/ofAppiOSWindow.mm +++ b/addons/ofxiOS/src/app/ofAppiOSWindow.mm @@ -45,6 +45,12 @@ #endif #include "ofxiOSGLKView.h" #include "ofxiOSEAGLView.h" +#if OF_USE_DAWN +#include "ofWebGPURenderer.h" +#include "ofxiOSDawnView.h" +#elif OF_USE_ANGLE +#include "ofxiOSMGLView.h" +#endif //----------------------------------------------------------------------------------- instance. static ofAppiOSWindow * _instance = NULL; @@ -106,11 +112,23 @@ settings.setupOrientation = OF_ORIENTATION_DEFAULT; } setOrientation(settings.setupOrientation); +#if OF_USE_DAWN + currentRenderer = std::shared_ptr(new ofWebGPURenderer(this)); + settings.windowControllerType = ofxiOSWindowControllerType::DAWN_KIT; +#elif OF_USE_ANGLE + if(settings.glesVersion >= ESRendererVersion_20) { + currentRenderer = std::shared_ptr(new ofGLProgrammableRenderer(this)); + } else { + currentRenderer = std::shared_ptr(new ofGLRenderer(this)); + } + settings.windowControllerType = ofxiOSWindowControllerType::ANGLE_KIT; +#else if(settings.glesVersion >= ESRendererVersion_20) { currentRenderer = std::shared_ptr(new ofGLProgrammableRenderer(this)); } else { currentRenderer = std::shared_ptr(new ofGLRenderer(this)); } +#endif hasExited = false; } @@ -170,6 +188,13 @@ glm::vec2 ofAppiOSWindow::getWindowPosition() { if(settings.windowControllerType == METAL_KIT || settings.windowControllerType == GL_KIT) return *[[ofxiOSGLKView getInstance] getWindowPosition]; +#if OF_USE_DAWN + else if(settings.windowControllerType == DAWN_KIT && [ofxiOSDawnView getInstance]) + return *[[ofxiOSDawnView getInstance] getWindowPosition]; +#elif OF_USE_ANGLE + else if(settings.windowControllerType == ANGLE_KIT && [ofxiOSMGLViewController getInstance]) + return *[[ofxiOSMGLViewController getInstance] getWindowPosition]; +#endif else return *[[ofxiOSEAGLView getInstance] getWindowPosition]; } @@ -177,6 +202,13 @@ glm::vec2 ofAppiOSWindow::getWindowSize() { if(settings.windowControllerType == METAL_KIT || settings.windowControllerType == GL_KIT) return *[[ofxiOSGLKView getInstance] getWindowSize]; +#if OF_USE_DAWN + else if(settings.windowControllerType == DAWN_KIT && [ofxiOSDawnView getInstance]) + return *[[ofxiOSDawnView getInstance] getWindowSize]; +#elif OF_USE_ANGLE + else if(settings.windowControllerType == ANGLE_KIT && [ofxiOSMGLViewController getInstance]) + return *[[ofxiOSMGLViewController getInstance] getWindowSize]; +#endif else return *[[ofxiOSEAGLView getInstance] getWindowSize]; } @@ -184,6 +216,13 @@ glm::vec2 ofAppiOSWindow::getScreenSize() { if(settings.windowControllerType == METAL_KIT || settings.windowControllerType == GL_KIT) return *[[ofxiOSGLKView getInstance] getScreenSize]; +#if OF_USE_DAWN + else if(settings.windowControllerType == DAWN_KIT && [ofxiOSDawnView getInstance]) + return *[[ofxiOSDawnView getInstance] getScreenSize]; +#elif OF_USE_ANGLE + else if(settings.windowControllerType == ANGLE_KIT && [ofxiOSMGLViewController getInstance]) + return *[[ofxiOSMGLViewController getInstance] getScreenSize]; +#endif else return *[[ofxiOSEAGLView getInstance] getScreenSize]; } diff --git a/addons/ofxiOS/src/core/ofxiOSAppDelegate.mm b/addons/ofxiOS/src/core/ofxiOSAppDelegate.mm index c98916523f7..4747f806dd1 100644 --- a/addons/ofxiOS/src/core/ofxiOSAppDelegate.mm +++ b/addons/ofxiOS/src/core/ofxiOSAppDelegate.mm @@ -38,6 +38,11 @@ #include "ofxiOSExtras.h" #include "ofxiOSAlerts.h" #include "ofxiOSEAGLView.h" +#if OF_USE_DAWN +#include "ofxiOSDawnView.h" +#elif OF_USE_ANGLE +#include "ofxiOSMGLView.h" +#endif #include "ofAppiOSWindow.h" #include "ofAppRunner.h" #include "ofUtils.h" @@ -137,6 +142,15 @@ - (void)applicationDidFinishLaunching:(UIApplication *)application { if ([appDelegateClassName isEqualToString:@"ofxiOSAppDelegate"]) { // app delegate is not being extended. switch(ofxiOSGetOFWindow()->getWindowControllerType()) { +#if OF_USE_DAWN + case DAWN_KIT: + self.uiViewController = [[ofxiOSDawnViewController alloc] initWithFrame:frame app:(ofxiOSApp *)ofGetAppPtr()]; + break; +#elif OF_USE_ANGLE + case ANGLE_KIT: + self.uiViewController = [[ofxiOSMGLViewController alloc] initWithFrame:frame app:(ofxiOSApp *)ofGetAppPtr()]; + break; +#endif case METAL_KIT: NSLog(@"No MetalKit yet supported for openFrameworks: Falling back to GLKit"); case GL_KIT: @@ -194,11 +208,15 @@ - (void)applicationWillResignActive:(UIApplication *)application { if(ofxiOSGetOFWindow()->getWindowControllerType() == CORE_ANIMATION) [ofxiOSGetGLView() stopAnimation]; ofxiOSAlerts.lostFocus(); +#if !OF_USE_DAWN glFinish(); +#endif } - (void)applicationDidEnterBackground:(UIApplication *)application { +#if !OF_USE_DAWN glFinish(); +#endif } - (void)applicationDidBecomeActive:(UIApplication *)application { diff --git a/addons/ofxiOS/src/core/ofxiOSDawnView.h b/addons/ofxiOS/src/core/ofxiOSDawnView.h new file mode 100644 index 00000000000..43ce62fbdab --- /dev/null +++ b/addons/ofxiOS/src/core/ofxiOSDawnView.h @@ -0,0 +1,28 @@ +#pragma once +#include "ofxiOSConstants.h" +#if defined(TARGET_OF_IOS) && OF_USE_DAWN + +#import +#include "ofConstants.h" +#include + +class ofxiOSApp; +class ofAppiOSWindow; + +@interface ofxiOSDawnView : UIView ++ (ofxiOSDawnView *)getInstance; +- (instancetype)initWithFrame:(CGRect)frame andApp:(ofxiOSApp *)app; +- (void *)metalLayer; +- (glm::vec2 *)getWindowPosition; +- (glm::vec2 *)getWindowSize; +- (glm::vec2 *)getScreenSize; +- (void)startRender; +- (void)finishRender; +@end + +@interface ofxiOSDawnViewController : UIViewController +- (instancetype)initWithFrame:(CGRect)frame app:(ofxiOSApp *)app; +@property (nonatomic, strong) ofxiOSDawnView * dawnView; +@end + +#endif diff --git a/addons/ofxiOS/src/core/ofxiOSDawnView.mm b/addons/ofxiOS/src/core/ofxiOSDawnView.mm new file mode 100644 index 00000000000..ed26a433907 --- /dev/null +++ b/addons/ofxiOS/src/core/ofxiOSDawnView.mm @@ -0,0 +1,259 @@ +#include "ofxiOSDawnView.h" +#if defined(TARGET_OF_IOS) && OF_USE_DAWN + +#include "ofAppiOSWindow.h" +#include "ofWebGPURenderer.h" +#include "ofxiOSApp.h" +#include "ofAppRunner.h" +#include "ofGraphics.h" + +#import + +static ofxiOSDawnView * _instanceRef = nil; + +@interface ofxiOSDawnView () { + ofAppiOSWindow * window; + ofxiOSApp * app; + glm::vec2 windowPos; + glm::vec2 windowSize; + glm::vec2 screenSize; + CADisplayLink * displayLink; + NSMutableDictionary * activeTouches; + BOOL didNotifySetup; +} +- (int)indexForTouch:(UITouch *)touch create:(BOOL)create; +- (CGPoint)pixelPointForTouch:(UITouch *)touch; +@end + +@implementation ofxiOSDawnView + ++ (Class)layerClass { + return [CAMetalLayer class]; +} + ++ (ofxiOSDawnView *)getInstance { + return _instanceRef; +} + +- (instancetype)initWithFrame:(CGRect)frame andApp:(ofxiOSApp *)appPtr { + self = [super initWithFrame:frame]; + if(!self) { + return nil; + } + _instanceRef = self; + app = appPtr; + window = ofAppiOSWindow::getInstance(); + self.multipleTouchEnabled = window ? window->isMultiTouch() : NO; + self.contentScaleFactor = [UIScreen mainScreen].scale; + + CAMetalLayer * layer = (CAMetalLayer *)self.layer; + layer.opaque = YES; + layer.contentsScale = self.contentScaleFactor; + layer.pixelFormat = MTLPixelFormatBGRA8Unorm; + layer.framebufferOnly = YES; + + CGFloat scale = self.contentScaleFactor; + screenSize = { (float)([UIScreen mainScreen].bounds.size.width * scale), (float)([UIScreen mainScreen].bounds.size.height * scale) }; + windowPos = { (float)(frame.origin.x * scale), (float)(frame.origin.y * scale) }; + windowSize = { (float)(frame.size.width * scale), (float)(frame.size.height * scale) }; + activeTouches = [NSMutableDictionary dictionary]; + didNotifySetup = NO; + + if(window) { + auto renderer = std::dynamic_pointer_cast(window->renderer()); + if(!renderer) { + renderer = std::make_shared(window); + window->renderer() = renderer; + ofSetCurrentRenderer(renderer, true); + } + CGSize native = layer.drawableSize; + if(native.width < 1 || native.height < 1) { + native = CGSizeMake(frame.size.width * layer.contentsScale, frame.size.height * layer.contentsScale); + layer.drawableSize = native; + } + if(renderer->setup((__bridge void *)layer, (int)native.width, (int)native.height, layer.contentsScale)) { + window->events().notifySetup(); + didNotifySetup = YES; + } + } + + displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(drawFrame)]; + [displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes]; + return self; +} + +- (void)dealloc { + [displayLink invalidate]; + displayLink = nil; + if(_instanceRef == self) { + _instanceRef = nil; + } +} + +- (void *)metalLayer { + return (__bridge void *)self.layer; +} + +- (glm::vec2 *)getWindowPosition { + return &windowPos; +} + +- (glm::vec2 *)getWindowSize { + return &windowSize; +} + +- (glm::vec2 *)getScreenSize { + return &screenSize; +} + +- (void)layoutSubviews { + [super layoutSubviews]; + CAMetalLayer * layer = (CAMetalLayer *)self.layer; + CGSize size = self.bounds.size; + layer.drawableSize = CGSizeMake(size.width * layer.contentsScale, size.height * layer.contentsScale); + windowSize = { (float)layer.drawableSize.width, (float)layer.drawableSize.height }; + if(window) { + if(auto renderer = std::dynamic_pointer_cast(window->renderer())) { + renderer->configureSurface((int)layer.drawableSize.width, (int)layer.drawableSize.height); + } + window->events().notifyWindowResized(windowSize.x, windowSize.y); + } +} + +- (void)startRender { + if(window && window->renderer()) { + window->renderer()->startRender(); + } +} + +- (void)finishRender { + if(window && window->renderer()) { + window->renderer()->finishRender(); + } +} + +- (void)drawFrame { + if(!window) { + return; + } + if(!didNotifySetup) { + return; + } + window->events().notifyUpdate(); + [self startRender]; + if(window->isSetupScreenEnabled()) { + window->renderer()->setupScreen(); + } + window->events().notifyDraw(); + [self finishRender]; +} + +- (int)indexForTouch:(UITouch *)touch create:(BOOL)create { + NSValue * key = [NSValue valueWithPointer:(__bridge void *)touch]; + NSNumber * existing = activeTouches[key]; + if(existing) { + return existing.intValue; + } + if(!create) { + return 0; + } + int touchIndex = 0; + while([activeTouches.allValues containsObject:@(touchIndex)]) { + touchIndex++; + } + activeTouches[key] = @(touchIndex); + return touchIndex; +} + +- (CGPoint)pixelPointForTouch:(UITouch *)touch { + CGPoint p = [touch locationInView:self]; + p.x *= self.contentScaleFactor; + p.y *= self.contentScaleFactor; + return p; +} + +- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { + (void)event; + if(!window) { + return; + } + for(UITouch * touch in touches) { + int touchIndex = [self indexForTouch:touch create:YES]; + CGPoint p = [self pixelPointForTouch:touch]; + if(touchIndex == 0) { + window->events().notifyMousePressed(p.x, p.y, 0); + } + window->events().notifyTouchDown(p.x, p.y, touchIndex); + } +} + +- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { + (void)event; + if(!window) { + return; + } + for(UITouch * touch in touches) { + int touchIndex = [self indexForTouch:touch create:NO]; + CGPoint p = [self pixelPointForTouch:touch]; + if(touchIndex == 0) { + window->events().notifyMouseDragged(p.x, p.y, 0); + } + window->events().notifyTouchMoved(p.x, p.y, touchIndex); + } +} + +- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { + (void)event; + if(!window) { + return; + } + for(UITouch * touch in touches) { + int touchIndex = [self indexForTouch:touch create:NO]; + [activeTouches removeObjectForKey:[NSValue valueWithPointer:(__bridge void *)touch]]; + CGPoint p = [self pixelPointForTouch:touch]; + if(touchIndex == 0) { + window->events().notifyMouseReleased(p.x, p.y, 0); + } + window->events().notifyTouchUp(p.x, p.y, touchIndex); + } +} + +- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { + (void)event; + if(!window) { + return; + } + for(UITouch * touch in touches) { + int touchIndex = [self indexForTouch:touch create:NO]; + [activeTouches removeObjectForKey:[NSValue valueWithPointer:(__bridge void *)touch]]; + CGPoint p = [self pixelPointForTouch:touch]; + window->events().notifyTouchCancelled(p.x, p.y, touchIndex); + } +} + +@end + +@implementation ofxiOSDawnViewController + +- (instancetype)initWithFrame:(CGRect)frame app:(ofxiOSApp *)app { + self = [super init]; + if(!self) { + return nil; + } + self.dawnView = [[ofxiOSDawnView alloc] initWithFrame:frame andApp:app]; + return self; +} + +- (void)loadView { + self.view = self.dawnView; +} + +#if !TARGET_OS_TV +- (BOOL)prefersStatusBarHidden { + return YES; +} +#endif + +@end + +#endif diff --git a/addons/ofxiOS/src/core/ofxiOSMGLView.h b/addons/ofxiOS/src/core/ofxiOSMGLView.h new file mode 100644 index 00000000000..416757901d6 --- /dev/null +++ b/addons/ofxiOS/src/core/ofxiOSMGLView.h @@ -0,0 +1,21 @@ +#pragma once +#include "ofxiOSConstants.h" +#if defined(TARGET_OF_IOS) && OF_USE_ANGLE + +#import +#include "ofConstants.h" +#include + +class ofxiOSApp; + +/// UIViewController that hosts an MGLKView (MetalANGLE). MGLKView is not subclassed +/// for drawing; this controller is the MGLKViewDelegate. +@interface ofxiOSMGLViewController : UIViewController ++ (ofxiOSMGLViewController *)getInstance; +- (instancetype)initWithFrame:(CGRect)frame app:(ofxiOSApp *)app; +- (glm::vec2 *)getWindowPosition; +- (glm::vec2 *)getWindowSize; +- (glm::vec2 *)getScreenSize; +@end + +#endif diff --git a/addons/ofxiOS/src/core/ofxiOSMGLView.mm b/addons/ofxiOS/src/core/ofxiOSMGLView.mm new file mode 100644 index 00000000000..244f05227b8 --- /dev/null +++ b/addons/ofxiOS/src/core/ofxiOSMGLView.mm @@ -0,0 +1,341 @@ +#include "ofxiOSMGLView.h" +#if defined(TARGET_OF_IOS) && OF_USE_ANGLE + +#include "ofAppiOSWindow.h" +#include "ofGLProgrammableRenderer.h" +#include "ofGLRenderer.h" +#include "ofxiOSApp.h" +#include "ofLog.h" + +#import + +static ofxiOSMGLViewController * _instanceRef = nil; + +@class ofxiOSMGLViewController; + +/// Touch and layout forwarding only. Drawing stays on the MGLKViewDelegate. +@interface ofxiOSMGLKitView : MGLKView +@property (nonatomic, weak) ofxiOSMGLViewController * owner; +@end + +@interface ofxiOSMGLViewController () { + ofAppiOSWindow * window; + ofxiOSApp * app; + glm::vec2 windowPos; + glm::vec2 windowSize; + glm::vec2 screenSize; + MGLContext * context; + ofxiOSMGLKitView * glView; + CADisplayLink * displayLink; + NSMutableDictionary * activeTouches; + BOOL didNotifySetup; + CGFloat pixelScale; +} +- (void)updateDimensions; +- (int)indexForTouch:(UITouch *)touch create:(BOOL)create; +- (CGPoint)pixelPointForTouch:(UITouch *)touch inView:(UIView *)view; +@end + +@implementation ofxiOSMGLKitView + +- (void)layoutSubviews { + [super layoutSubviews]; + [self.owner updateDimensions]; +} + +- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { + [self.owner touchesBegan:touches withEvent:event]; +} + +- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { + [self.owner touchesMoved:touches withEvent:event]; +} + +- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { + [self.owner touchesEnded:touches withEvent:event]; +} + +- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { + [self.owner touchesCancelled:touches withEvent:event]; +} + +@end + +@implementation ofxiOSMGLViewController + ++ (ofxiOSMGLViewController *)getInstance { + return _instanceRef; +} + +- (instancetype)initWithFrame:(CGRect)frame app:(ofxiOSApp *)appPtr { + self = [super init]; + if(!self) { + return nil; + } + _instanceRef = self; + app = appPtr; + window = ofAppiOSWindow::getInstance(); + activeTouches = [NSMutableDictionary dictionary]; + didNotifySetup = NO; + pixelScale = 1.f; + if(window && window->isRetinaEnabled()) { + pixelScale = [UIScreen mainScreen].scale; + if(pixelScale < 1.f) { + pixelScale = 1.f; + } + } + + MGLRenderingAPI api = kMGLRenderingAPIOpenGLES2; + if(window) { + const int gles = window->getSettings().glesVersion; + if(gles <= 1) { + api = kMGLRenderingAPIOpenGLES1; + } else if(gles >= 3) { + api = kMGLRenderingAPIOpenGLES3; + } + } + context = [[MGLContext alloc] initWithAPI:api]; + if(!context) { + ofLogError("ofxiOSMGLView") << "MGLContext initWithAPI failed"; + return self; + } + + glView = [[ofxiOSMGLKitView alloc] initWithFrame:frame context:context]; + glView.owner = self; + glView.delegate = self; + glView.enableSetNeedsDisplay = NO; + glView.contentScaleFactor = pixelScale; + glView.multipleTouchEnabled = window ? window->isMultiTouch() : NO; + glView.drawableColorFormat = MGLDrawableColorFormatRGBA8888; + if(window) { + if(window->getSettings().enableDepth) { + if(window->getRendererDepthType() == DEPTH_24) { + glView.drawableDepthFormat = MGLDrawableDepthFormat24; + } else if(window->getRendererDepthType() == DEPTH_16) { + glView.drawableDepthFormat = MGLDrawableDepthFormat16; + } + } + if(window->getRendererStencilType() == STENCIL_8) { + glView.drawableStencilFormat = MGLDrawableStencilFormat8; + } + if(window->isAntiAliasingEnabled()) { + glView.drawableMultisample = MGLDrawableMultisample4X; + } + } + [self updateDimensions]; + return self; +} + +- (void)dealloc { + [displayLink invalidate]; + displayLink = nil; + if(_instanceRef == self) { + _instanceRef = nil; + } +} + +- (void)loadView { + self.view = glView ? glView : [[UIView alloc] initWithFrame:CGRectZero]; +} + +- (void)viewDidAppear:(BOOL)animated { + [super viewDidAppear:animated]; + if(!displayLink && glView && context) { + displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(tick:)]; + [displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes]; + } +} + +- (void)viewWillDisappear:(BOOL)animated { + [super viewWillDisappear:animated]; + [displayLink invalidate]; + displayLink = nil; +} + +#if !TARGET_OS_TV +- (BOOL)prefersStatusBarHidden { + return YES; +} +#endif + +- (glm::vec2 *)getWindowPosition { + return &windowPos; +} + +- (glm::vec2 *)getWindowSize { + return &windowSize; +} + +- (glm::vec2 *)getScreenSize { + return &screenSize; +} + +- (void)updateDimensions { + if(!glView) { + return; + } + CGSize drawable = glView.drawableSize; + if(drawable.width < 1.f || drawable.height < 1.f) { + drawable = CGSizeMake(glView.bounds.size.width * pixelScale, glView.bounds.size.height * pixelScale); + } + glm::vec2 nextSize = { (float)drawable.width, (float)drawable.height }; + bool changed = nextSize.x != windowSize.x || nextSize.y != windowSize.y; + windowPos = { (float)(glView.frame.origin.x * pixelScale), (float)(glView.frame.origin.y * pixelScale) }; + windowSize = nextSize; + UIScreen * screen = glView.window.screen ?: [UIScreen mainScreen]; + screenSize = { (float)(screen.bounds.size.width * pixelScale), (float)(screen.bounds.size.height * pixelScale) }; + if(window && didNotifySetup && changed) { + window->events().notifyWindowResized(windowSize.x, windowSize.y); + } +} + +- (void)tick:(CADisplayLink *)link { + (void)link; + [glView display]; +} + +- (void)mglkView:(MGLKView *)view drawInRect:(CGRect)rect { + (void)rect; + if(!window || !window->renderer()) { + return; + } + [self updateDimensions]; + if(!didNotifySetup) { + [view bindDrawable]; + const int gles = window->getSettings().glesVersion; + if(window->isProgrammableRenderer()) { + int major = gles >= 3 ? 3 : 2; + int minor = 0; +#if defined(OF_TEST_GLES_MINOR) + minor = OF_TEST_GLES_MINOR; +#endif + static_cast(window->renderer().get())->setup(major, minor); + } else { + static_cast(window->renderer().get())->setup(); + } + const char * version = reinterpret_cast(glGetString(GL_VERSION)); + const char * rendererName = reinterpret_cast(glGetString(GL_RENDERER)); + ofLogNotice("ofxiOSMGLView") << "ANGLE context ready " << (version ? version : "?") + << " renderer " << (rendererName ? rendererName : "?") + << " " << (int)windowSize.x << "x" << (int)windowSize.y; + window->events().notifySetup(); + didNotifySetup = YES; + } + window->events().notifyUpdate(); + window->renderer()->startRender(); + if(window->isSetupScreenEnabled()) { + window->renderer()->setupScreen(); + } + window->events().notifyDraw(); + window->renderer()->finishRender(); +} + +- (int)indexForTouch:(UITouch *)touch create:(BOOL)create { + NSValue * key = [NSValue valueWithPointer:(__bridge void *)touch]; + NSNumber * existing = activeTouches[key]; + if(existing) { + return existing.intValue; + } + if(!create) { + return 0; + } + int touchIndex = 0; + while([activeTouches.allValues containsObject:@(touchIndex)]) { + touchIndex++; + } + activeTouches[key] = @(touchIndex); + return touchIndex; +} + +- (CGPoint)pixelPointForTouch:(UITouch *)touch inView:(UIView *)view { + CGPoint p = [touch locationInView:view]; + p.x *= pixelScale; + p.y *= pixelScale; + return p; +} + +- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { + (void)event; + if(!window || !didNotifySetup) { + return; + } + for(UITouch * touch in touches) { + int touchIndex = [self indexForTouch:touch create:YES]; + CGPoint p = [self pixelPointForTouch:touch inView:glView]; + if(touchIndex == 0) { + window->events().notifyMousePressed(p.x, p.y, 0); + } + window->events().notifyTouchDown(p.x, p.y, touchIndex); + } +} + +- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { + (void)event; + if(!window || !didNotifySetup) { + return; + } + for(UITouch * touch in touches) { + int touchIndex = [self indexForTouch:touch create:NO]; + CGPoint p = [self pixelPointForTouch:touch inView:glView]; + if(touchIndex == 0) { + window->events().notifyMouseDragged(p.x, p.y, 0); + } + window->events().notifyTouchMoved(p.x, p.y, touchIndex); + } +} + +- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { + (void)event; + if(!window || !didNotifySetup) { + return; + } + for(UITouch * touch in touches) { + int touchIndex = [self indexForTouch:touch create:NO]; + [activeTouches removeObjectForKey:[NSValue valueWithPointer:(__bridge void *)touch]]; + CGPoint p = [self pixelPointForTouch:touch inView:glView]; + if(touchIndex == 0) { + window->events().notifyMouseReleased(p.x, p.y, 0); + } + window->events().notifyTouchUp(p.x, p.y, touchIndex); + } +} + +- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { + (void)event; + if(!window || !didNotifySetup) { + return; + } + for(UITouch * touch in touches) { + int touchIndex = [self indexForTouch:touch create:NO]; + [activeTouches removeObjectForKey:[NSValue valueWithPointer:(__bridge void *)touch]]; + CGPoint p = [self pixelPointForTouch:touch inView:glView]; + window->events().notifyTouchCancelled(p.x, p.y, touchIndex); + } +} + +@end + +// The static lib still compiles the EAGL / GLKit views. ANGLE never creates +// them, but the linker still needs these Apple-only symbols, and OpenGLES +// cannot be linked beside MetalANGLE. +@implementation EAGLContext +@end + +@interface GLKView : UIView +@end +@implementation GLKView +@end + +@interface GLKViewController : UIViewController +@end +@implementation GLKViewController +@end + +extern "C" void glRenderbufferStorageMultisampleAPPLE(unsigned int, int, unsigned int, int, int) {} +extern "C" void glResolveMultisampleFramebufferAPPLE(void) {} + +NSString * const kEAGLDrawablePropertyColorFormat = @"EAGLDrawablePropertyColorFormat"; +NSString * const kEAGLColorFormatRGBA8 = @"EAGLColorFormatRGBA8"; +NSString * const kEAGLDrawablePropertyRetainedBacking = @"EAGLDrawablePropertyRetainedBacking"; + +#endif diff --git a/addons/ofxiOS/src/ofxiOS.h b/addons/ofxiOS/src/ofxiOS.h index 41f766faaf0..e09fc4af44a 100644 --- a/addons/ofxiOS/src/ofxiOS.h +++ b/addons/ofxiOS/src/ofxiOS.h @@ -48,6 +48,12 @@ #include "ofxiOSGLKViewController.h" #include "ofxiOSGLKView.h" #include "ofxiOSEAGLView.h" +#if OF_USE_DAWN +#include "ofxiOSDawnView.h" +#include "ofWebGPURenderer.h" +#elif OF_USE_ANGLE +#include "ofxiOSMGLView.h" +#endif #include "ofxiOSApp.h" #include "ofxiOSExtras.h" diff --git a/addons/ofxiOS/src/ofxiOSConstants.h b/addons/ofxiOS/src/ofxiOSConstants.h index 7f81073f79b..99daf121438 100644 --- a/addons/ofxiOS/src/ofxiOSConstants.h +++ b/addons/ofxiOS/src/ofxiOSConstants.h @@ -76,7 +76,9 @@ enum ofxiOSRendererType { enum ofxiOSWindowControllerType{ CORE_ANIMATION = 1, GL_KIT = 2, - METAL_KIT = 3 + METAL_KIT = 3, + DAWN_KIT = 4, + ANGLE_KIT = 5 }; enum ofxiOSRendererColorFormat{ diff --git a/addons/ofxiOS/src/tvOS/ofxtvOSAppDelegate.mm b/addons/ofxiOS/src/tvOS/ofxtvOSAppDelegate.mm index ef5663d1f32..ff67ac451bf 100644 --- a/addons/ofxiOS/src/tvOS/ofxtvOSAppDelegate.mm +++ b/addons/ofxiOS/src/tvOS/ofxtvOSAppDelegate.mm @@ -17,6 +17,11 @@ #include "ofAppRunner.h" #include "ofUtils.h" #include "ofFileUtils.h" +#if OF_USE_DAWN +#include "ofxiOSDawnView.h" +#elif OF_USE_ANGLE +#include "ofxiOSMGLView.h" +#endif @implementation ofxtvOSAppDelegate @@ -43,6 +48,15 @@ - (void)applicationDidFinishLaunching:(UIApplication *)application { if ([appDelegateClassName isEqualToString:@"ofxtvOSAppDelegate"]) { // app delegate is not being extended. switch(ofxiOSGetOFWindow()->getWindowControllerType()) { +#if OF_USE_DAWN + case DAWN_KIT: + self.uiViewController = [[ofxiOSDawnViewController alloc] initWithFrame:frame app:(ofxiOSApp *)ofGetAppPtr()]; + break; +#elif OF_USE_ANGLE + case ANGLE_KIT: + self.uiViewController = [[ofxiOSMGLViewController alloc] initWithFrame:frame app:(ofxiOSApp *)ofGetAppPtr()]; + break; +#endif case METAL_KIT: NSLog(@"No MetalKit yet supported for openFrameworks: Falling back to GLKit"); case GL_KIT: @@ -67,7 +81,9 @@ - (void)applicationWillResignActive:(UIApplication *)application { } - (void)applicationDidEnterBackground:(UIApplication *)application { +#if !OF_USE_DAWN glFinish(); +#endif } - (void)applicationDidBecomeActive:(UIApplication *)application { diff --git a/examples/gl/aquariumExample/README.md b/examples/gl/aquariumExample/README.md new file mode 100644 index 00000000000..0209dd97be9 --- /dev/null +++ b/examples/gl/aquariumExample/README.md @@ -0,0 +1,26 @@ +# Aquarium + +The [WebGL Aquarium](https://webglsamples.org/aquarium/aquarium.html) (Gregg Tavares and Human Engines, Copyright 2009 Google Inc., BSD-3-Clause). This is the scene in the [MetalANGLE](https://github.com/kakashidinho/metalangle) readme: static fish meshes, one draw per fish, tail bend in the vertex shader, plus the tank, props, seaweed, light rays, and bubbles. + +Assets are not in git. From this directory: + +``` +python3 scripts/fetch_assets.py +make Debug +``` + +The fetch script downloads `aquarium/assets` from WebGLSamples and packs the tdl meshes. It leaves out the Maya / TGA `source_assets` tree. See `bin/data/LICENSE-aquarium.txt`. + +Defaults match the web page: 500 fish, inside view, fog on. Once the frame rate is above 40 the page turns normal maps and reflection on. This example does the same. `--pretty` starts in that mode. + +Keys: + +- `1`–`9`, `0` — fish counts 1, 100, 500, 1000, 5000, 10000, 15000, 20000, 25000, 30000. `8` is the 20 000 fish shot from the MetalANGLE readme. +- space — cycle the six camera views (the second is outside the globe) +- `n` normal maps, `r` reflection, `f` fog, `t` tank, `m` museum, `b` bubbles, `y` light rays, `l` shark lasers + +``` +bin/aquariumExample_debug.app/Contents/MacOS/aquariumExample_debug --fish 20000 --view 1 --pretty +``` + +`--novsync` turns vsync off. `--shot 120` saves `/tmp/aquarium-shot.png` and quits. diff --git a/examples/gl/aquariumExample/bin/data/LICENSE-aquarium.txt b/examples/gl/aquariumExample/bin/data/LICENSE-aquarium.txt new file mode 100644 index 00000000000..c8b0e3b68ec --- /dev/null +++ b/examples/gl/aquariumExample/bin/data/LICENSE-aquarium.txt @@ -0,0 +1,32 @@ +The aquarium scene, meshes, textures, and shader algorithms are from the +WebGL Aquarium by Gregg Tavares and Human Engines, hosted at +https://github.com/WebGLSamples/WebGLSamples.github.io (aquarium/). + +Copyright 2009, Google Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/examples/gl/aquariumExample/bin/data/shaders_gl3/bubble.frag b/examples/gl/aquariumExample/bin/data/shaders_gl3/bubble.frag new file mode 100644 index 00000000000..698c632d702 --- /dev/null +++ b/examples/gl/aquariumExample/bin/data/shaders_gl3/bubble.frag @@ -0,0 +1,11 @@ +#version 150 + +uniform sampler2D colorMap; + +in vec2 v_texCoord; +in vec4 v_color; +out vec4 fragColor; + +void main() { + fragColor = texture(colorMap, v_texCoord) * v_color; +} diff --git a/examples/gl/aquariumExample/bin/data/shaders_gl3/bubble.vert b/examples/gl/aquariumExample/bin/data/shaders_gl3/bubble.vert new file mode 100644 index 00000000000..d0ba568661f --- /dev/null +++ b/examples/gl/aquariumExample/bin/data/shaders_gl3/bubble.vert @@ -0,0 +1,16 @@ +#version 150 + +uniform mat4 viewProjection; + +in vec4 position; +in vec2 texcoord; +in vec4 color; + +out vec2 v_texCoord; +out vec4 v_color; + +void main() { + v_texCoord = texcoord; + v_color = color; + gl_Position = viewProjection * position; +} diff --git a/examples/gl/aquariumExample/bin/data/shaders_gl3/fish.vert b/examples/gl/aquariumExample/bin/data/shaders_gl3/fish.vert new file mode 100644 index 00000000000..270b0e61678 --- /dev/null +++ b/examples/gl/aquariumExample/bin/data/shaders_gl3/fish.vert @@ -0,0 +1,58 @@ +#version 150 +// Ported from the WebGL Aquarium fishVertexShader. +// Copyright 2009, Google Inc. BSD-3-Clause. + +uniform vec3 lightWorldPos; +uniform mat4 viewInverse; +uniform mat4 viewProjection; +uniform vec3 worldPosition; +uniform vec3 nextPosition; +uniform float scale; +uniform float time; +uniform float fishLength; +uniform float fishWaveLength; +uniform float fishBendAmount; + +in vec4 position; +in vec3 normal; +in vec2 texcoord; +in vec3 tangent; +in vec3 binormal; + +out vec4 v_position; +out vec2 v_texCoord; +out vec3 v_tangent; +out vec3 v_binormal; +out vec3 v_normal; +out vec3 v_surfaceToLight; +out vec3 v_surfaceToView; + +void main() { + vec3 vz = normalize(worldPosition - nextPosition); + vec3 vx = normalize(cross(vec3(0.0, 1.0, 0.0), vz)); + vec3 vy = cross(vz, vx); + mat4 orientMat = mat4( + vec4(vx, 0.0), + vec4(vy, 0.0), + vec4(vz, 0.0), + vec4(worldPosition, 1.0)); + mat4 scaleMat = mat4( + vec4(scale, 0.0, 0.0, 0.0), + vec4(0.0, scale, 0.0, 0.0), + vec4(0.0, 0.0, scale, 0.0), + vec4(0.0, 0.0, 0.0, 1.0)); + mat4 world = orientMat * scaleMat; + + v_texCoord = texcoord; + float mult = position.z > 0.0 ? (position.z / fishLength) : (-position.z / fishLength * 2.0); + float s = sin(time + mult * fishWaveLength); + float offset = pow(mult, 2.0) * s * fishBendAmount; + vec4 bent = position + vec4(offset, 0.0, 0.0, 0.0); + v_position = viewProjection * world * bent; + v_normal = (world * vec4(normal, 0.0)).xyz; + v_surfaceToLight = lightWorldPos - (world * position).xyz; + v_surfaceToView = (viewInverse[3] - (world * position)).xyz; + v_binormal = (world * vec4(binormal, 0.0)).xyz; + v_tangent = (world * vec4(tangent, 0.0)).xyz; + gl_Position = v_position; +} diff --git a/examples/gl/aquariumExample/bin/data/shaders_gl3/inner.frag b/examples/gl/aquariumExample/bin/data/shaders_gl3/inner.frag new file mode 100644 index 00000000000..e98f6644d29 --- /dev/null +++ b/examples/gl/aquariumExample/bin/data/shaders_gl3/inner.frag @@ -0,0 +1,48 @@ +#version 150 +// Ported from the WebGL Aquarium innerRefractionMapFragmentShader. +// Copyright 2009, Google Inc. BSD-3-Clause. + +uniform float uNormal; +uniform float uFog; +uniform float refractionFudge; +uniform float eta; +uniform float tankColorFudge; +uniform float fogPower; +uniform float fogMult; +uniform float fogOffset; +uniform vec4 fogColor; +uniform sampler2D diffuse; +uniform sampler2D normalMap; +uniform sampler2D reflectionMap; +uniform samplerCube skybox; + +in vec4 v_position; +in vec2 v_texCoord; +in vec3 v_tangent; +in vec3 v_binormal; +in vec3 v_normal; +in vec3 v_surfaceToView; + +out vec4 fragColor; + +void main() { + vec4 diffuseColor = texture(diffuse, v_texCoord) + vec4(tankColorFudge, tankColorFudge, tankColorFudge, 1.0); + vec4 refraction = texture(reflectionMap, v_texCoord); + vec3 normal = normalize(v_normal); + if (uNormal > 0.5) { + vec4 normalSpec = texture(normalMap, v_texCoord); + vec3 tangentNormal = normalSpec.xyz - vec3(0.5); + tangentNormal = normalize(tangentNormal + vec3(0.0, 0.0, refractionFudge)); + mat3 tangentToWorld = mat3(v_tangent, v_binormal, v_normal); + normal = normalize(tangentToWorld * tangentNormal); + } + vec3 surfaceToView = normalize(v_surfaceToView); + vec3 refractionVec = refract(surfaceToView, normal, eta); + vec4 skyColor = texture(skybox, refractionVec); + vec4 outColor = vec4(mix(skyColor * diffuseColor, diffuseColor, refraction.r).rgb, diffuseColor.a); + if (uFog > 0.5) { + float fog = clamp(pow(v_position.z / v_position.w, fogPower) * fogMult - fogOffset, 0.0, 1.0); + outColor = mix(outColor, vec4(fogColor.rgb, diffuseColor.a), fog); + } + fragColor = outColor; +} diff --git a/examples/gl/aquariumExample/bin/data/shaders_gl3/lit.frag b/examples/gl/aquariumExample/bin/data/shaders_gl3/lit.frag new file mode 100644 index 00000000000..eaf43a770a8 --- /dev/null +++ b/examples/gl/aquariumExample/bin/data/shaders_gl3/lit.frag @@ -0,0 +1,67 @@ +#version 150 +// Ported from the WebGL Aquarium diffuse / normal / reflection fragments. +// Copyright 2009, Google Inc. BSD-3-Clause. + +uniform vec4 lightColor; +uniform vec4 ambient; +uniform vec4 specular; +uniform float shininess; +uniform float specularFactor; +uniform float uNormal; +uniform float uReflect; +uniform float uNormalBias; +uniform float uFog; +uniform float fogPower; +uniform float fogMult; +uniform float fogOffset; +uniform vec4 fogColor; +uniform sampler2D diffuse; +uniform sampler2D normalMap; +uniform sampler2D reflectionMap; +uniform samplerCube skybox; + +in vec4 v_position; +in vec2 v_texCoord; +in vec3 v_tangent; +in vec3 v_binormal; +in vec3 v_normal; +in vec3 v_surfaceToLight; +in vec3 v_surfaceToView; + +out vec4 fragColor; + +vec4 lit(float l, float h, float m) { + return vec4(1.0, max(l, 0.0), (l > 0.0) ? pow(max(0.0, h), m) : 0.0, 1.0); +} + +void main() { + vec4 diffuseColor = texture(diffuse, v_texCoord); + vec4 normalSpec = vec4(0.0); + vec3 normal = normalize(v_normal); + if (uNormal > 0.5) { + normalSpec = texture(normalMap, v_texCoord); + vec3 tangentNormal = normalSpec.xyz - vec3(0.5); + if (uNormalBias > 0.5) { + tangentNormal = normalize(tangentNormal + vec3(0.0, 0.0, 2.0)); + } + mat3 tangentToWorld = mat3(v_tangent, v_binormal, v_normal); + normal = normalize(tangentToWorld * tangentNormal); + } + vec3 surfaceToLight = normalize(v_surfaceToLight); + vec3 surfaceToView = normalize(v_surfaceToView); + vec3 halfVector = normalize(surfaceToLight + surfaceToView); + vec4 litR = lit(dot(normal, surfaceToLight), dot(normal, halfVector), shininess); + vec4 litColor = vec4((lightColor * (diffuseColor * litR.y + diffuseColor * ambient + + specular * litR.z * specularFactor * normalSpec.a)).rgb, diffuseColor.a); + vec4 outColor = litColor; + if (uReflect > 0.5) { + vec4 reflection = texture(reflectionMap, v_texCoord); + vec4 skyColor = texture(skybox, -reflect(surfaceToView, normal)); + outColor = vec4(mix(skyColor, litColor, 1.0 - reflection.r).rgb, diffuseColor.a); + } + if (uFog > 0.5) { + float fog = clamp(pow(v_position.z / v_position.w, fogPower) * fogMult - fogOffset, 0.0, 1.0); + outColor = mix(outColor, vec4(fogColor.rgb, diffuseColor.a), fog); + } + fragColor = outColor; +} diff --git a/examples/gl/aquariumExample/bin/data/shaders_gl3/outer.frag b/examples/gl/aquariumExample/bin/data/shaders_gl3/outer.frag new file mode 100644 index 00000000000..d94476c6878 --- /dev/null +++ b/examples/gl/aquariumExample/bin/data/shaders_gl3/outer.frag @@ -0,0 +1,37 @@ +#version 150 +// Ported from the WebGL Aquarium outerRefractionMapFragmentShader. +// Copyright 2009, Google Inc. BSD-3-Clause. + +uniform float uNormal; +uniform sampler2D diffuse; +uniform sampler2D normalMap; +uniform sampler2D reflectionMap; +uniform samplerCube skybox; + +in vec2 v_texCoord; +in vec3 v_tangent; +in vec3 v_binormal; +in vec3 v_normal; +in vec3 v_surfaceToView; + +out vec4 fragColor; + +void main() { + vec4 diffuseColor = texture(diffuse, v_texCoord); + vec4 reflection = texture(reflectionMap, v_texCoord); + vec3 normal = normalize(v_normal); + if (uNormal > 0.5) { + vec4 normalSpec = texture(normalMap, v_texCoord); + vec3 tangentNormal = normalSpec.xyz - vec3(0.5); + mat3 tangentToWorld = mat3(v_tangent, v_binormal, v_normal); + normal = normalize(tangentToWorld * tangentNormal); + } + vec3 surfaceToView = normalize(v_surfaceToView); + vec4 skyColor = texture(skybox, -reflect(surfaceToView, normal)); + float fudgeAmount = 1.1; + vec3 fudge = skyColor.rgb * vec3(fudgeAmount); + float bright = min(1.0, fudge.r * fudge.g * fudge.b); + vec4 reflectColor = mix(vec4(skyColor.rgb, bright), diffuseColor, 1.0 - reflection.r); + float r = abs(dot(surfaceToView, normal)); + fragColor = vec4(mix(skyColor, reflectColor, (r + 0.3) * reflection.r).rgb, 1.0 - r); +} diff --git a/examples/gl/aquariumExample/bin/data/shaders_gl3/seaweed.frag b/examples/gl/aquariumExample/bin/data/shaders_gl3/seaweed.frag new file mode 100644 index 00000000000..5bb601c3ba2 --- /dev/null +++ b/examples/gl/aquariumExample/bin/data/shaders_gl3/seaweed.frag @@ -0,0 +1,46 @@ +#version 150 +// Ported from the WebGL Aquarium seaweedFragmentShader. +// Copyright 2009, Google Inc. BSD-3-Clause. + +uniform vec4 lightColor; +uniform vec4 ambient; +uniform vec4 specular; +uniform float shininess; +uniform float specularFactor; +uniform float uFog; +uniform float fogPower; +uniform float fogMult; +uniform float fogOffset; +uniform vec4 fogColor; +uniform sampler2D diffuse; + +in vec4 v_position; +in vec2 v_texCoord; +in vec3 v_normal; +in vec3 v_surfaceToLight; +in vec3 v_surfaceToView; + +out vec4 fragColor; + +vec4 lit(float l, float h, float m) { + return vec4(1.0, max(l, 0.0), (l > 0.0) ? pow(max(0.0, h), m) : 0.0, 1.0); +} + +void main() { + vec4 diffuseColor = texture(diffuse, v_texCoord); + if (diffuseColor.a < 0.3) { + discard; + } + vec3 normal = normalize(v_normal); + vec3 surfaceToLight = normalize(v_surfaceToLight); + vec3 surfaceToView = normalize(v_surfaceToView); + vec3 halfVector = normalize(surfaceToLight + surfaceToView); + vec4 litR = lit(dot(normal, surfaceToLight), dot(normal, halfVector), shininess); + vec4 outColor = vec4((lightColor * (diffuseColor * litR.y + diffuseColor * ambient + + specular * litR.z * specularFactor)).rgb, diffuseColor.a); + if (uFog > 0.5) { + float fog = clamp(pow(v_position.z / v_position.w, fogPower) * fogMult - fogOffset, 0.0, 1.0); + outColor = mix(outColor, vec4(fogColor.rgb, diffuseColor.a), fog); + } + fragColor = outColor; +} diff --git a/examples/gl/aquariumExample/bin/data/shaders_gl3/seaweed.vert b/examples/gl/aquariumExample/bin/data/shaders_gl3/seaweed.vert new file mode 100644 index 00000000000..4d79c47166a --- /dev/null +++ b/examples/gl/aquariumExample/bin/data/shaders_gl3/seaweed.vert @@ -0,0 +1,41 @@ +#version 150 +// Ported from the WebGL Aquarium seaweedVertexShader. +// Copyright 2009, Google Inc. BSD-3-Clause. + +uniform mat4 world; +uniform mat4 viewProjection; +uniform vec3 lightWorldPos; +uniform mat4 viewInverse; +uniform float time; + +in vec4 position; +in vec3 normal; +in vec2 texcoord; + +out vec4 v_position; +out vec2 v_texCoord; +out vec3 v_normal; +out vec3 v_surfaceToLight; +out vec3 v_surfaceToView; + +void main() { + vec3 toCamera = normalize(viewInverse[3].xyz - world[3].xyz); + vec3 yAxis = vec3(0.0, 1.0, 0.0); + vec3 xAxis = cross(yAxis, toCamera); + mat4 newWorld = mat4( + vec4(xAxis, 0.0), + vec4(yAxis, 0.0), + vec4(xAxis, 0.0), + world[3]); + vec4 swayed = position + vec4( + sin(time * 0.5) * pow(position.y * 0.07, 2.0), + -4.0, + 0.0, + 0.0); + v_texCoord = texcoord; + v_position = (viewProjection * newWorld) * swayed; + v_normal = (newWorld * vec4(normal, 0.0)).xyz; + v_surfaceToLight = lightWorldPos - (world * position).xyz; + v_surfaceToView = (viewInverse[3] - (world * position)).xyz; + gl_Position = v_position; +} diff --git a/examples/gl/aquariumExample/bin/data/shaders_gl3/static.vert b/examples/gl/aquariumExample/bin/data/shaders_gl3/static.vert new file mode 100644 index 00000000000..db7b4b906a0 --- /dev/null +++ b/examples/gl/aquariumExample/bin/data/shaders_gl3/static.vert @@ -0,0 +1,33 @@ +#version 150 +// Ported from the WebGL Aquarium. Copyright 2009, Google Inc. BSD-3-Clause. + +uniform mat4 viewProjection; +uniform vec3 lightWorldPos; +uniform mat4 world; +uniform mat4 viewInverse; +uniform mat4 worldInverseTranspose; + +in vec4 position; +in vec3 normal; +in vec2 texcoord; +in vec3 tangent; +in vec3 binormal; + +out vec4 v_position; +out vec2 v_texCoord; +out vec3 v_tangent; +out vec3 v_binormal; +out vec3 v_normal; +out vec3 v_surfaceToLight; +out vec3 v_surfaceToView; + +void main() { + v_texCoord = texcoord; + v_position = viewProjection * world * position; + v_normal = (worldInverseTranspose * vec4(normal, 0.0)).xyz; + v_surfaceToLight = lightWorldPos - (world * position).xyz; + v_surfaceToView = (viewInverse[3] - (world * position)).xyz; + v_binormal = (worldInverseTranspose * vec4(binormal, 0.0)).xyz; + v_tangent = (worldInverseTranspose * vec4(tangent, 0.0)).xyz; + gl_Position = v_position; +} diff --git a/examples/gl/aquariumExample/bin/data/shaders_gl3/tex.frag b/examples/gl/aquariumExample/bin/data/shaders_gl3/tex.frag new file mode 100644 index 00000000000..201a905b76d --- /dev/null +++ b/examples/gl/aquariumExample/bin/data/shaders_gl3/tex.frag @@ -0,0 +1,11 @@ +#version 150 + +uniform vec4 colorMult; +uniform sampler2D colorMap; + +in vec2 v_texCoord; +out vec4 fragColor; + +void main() { + fragColor = texture(colorMap, v_texCoord) * colorMult; +} diff --git a/examples/gl/aquariumExample/bin/data/shaders_gl3/tex.vert b/examples/gl/aquariumExample/bin/data/shaders_gl3/tex.vert new file mode 100644 index 00000000000..57c95492ed3 --- /dev/null +++ b/examples/gl/aquariumExample/bin/data/shaders_gl3/tex.vert @@ -0,0 +1,14 @@ +#version 150 + +uniform mat4 world; +uniform mat4 viewProjection; + +in vec4 position; +in vec2 texcoord; + +out vec2 v_texCoord; + +void main() { + v_texCoord = texcoord; + gl_Position = viewProjection * world * position; +} diff --git a/examples/gl/aquariumExample/scripts/fetch_assets.py b/examples/gl/aquariumExample/scripts/fetch_assets.py new file mode 100644 index 00000000000..7a752957b6c --- /dev/null +++ b/examples/gl/aquariumExample/scripts/fetch_assets.py @@ -0,0 +1,195 @@ +#!/usr/bin/env python3 +"""Download the WebGL Aquarium runtime assets and pack the tdl meshes. + +Source: https://github.com/WebGLSamples/WebGLSamples.github.io +License: BSD-3-Clause, Copyright 2009, Google Inc. +Skips aquarium/source_assets (Maya / TGA masters). The running demo uses +aquarium/assets plus two static textures. +""" + +import array +import json +import os +import sys +import urllib.request + +ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) +DATA = os.path.join(ROOT, "bin", "data") +ASSET_DIR = os.path.join(DATA, "assets") +MODEL_DIR = os.path.join(DATA, "models") +RAW = "https://raw.githubusercontent.com/WebGLSamples/WebGLSamples.github.io/master/aquarium/" +API = "https://api.github.com/repos/WebGLSamples/WebGLSamples.github.io/contents/aquarium/" + +# Matches g_sceneInfo in aquarium.js. Unlisted models stay in the base group. +SCENE = { + "SmallFishA": {"group": "fish", "program": "fish", "fog": True, "blend": True}, + "MediumFishA": {"group": "fish", "program": "fish", "fog": True, "blend": True}, + "MediumFishB": {"group": "fish", "program": "fish", "fog": True, "blend": True}, + "BigFishA": {"group": "fish", "program": "fish", "fog": True, "blend": True}, + "BigFishB": {"group": "fish", "program": "fish", "fog": True, "blend": True}, + "EnvironmentBox": {"group": "outside", "program": "diffuse", "fog": False, "blend": False}, + "GlobeBase": {"group": "base", "program": "diffuse", "fog": False, "blend": False}, + "GlobeInner": {"group": "inner", "program": "inner", "fog": True, "blend": False}, + "GlobeOuter": {"group": "outer", "program": "outer", "fog": True, "blend": True}, + "Skybox": {"group": "outside", "program": "diffuse", "fog": False, "blend": False}, + "SupportBeams": {"group": "outside", "program": "auto", "fog": False, "blend": False}, + "SeaweedA": {"group": "seaweed", "program": "seaweed", "fog": True, "blend": True}, + "SeaweedB": {"group": "seaweed", "program": "seaweed", "fog": True, "blend": True}, +} + +HAS_NORMAL = 1 +HAS_UV = 2 +HAS_TAN = 4 +HAS_BIN = 8 + + +def urlopen(url): + req = urllib.request.Request(url, headers={"User-Agent": "openFrameworks-aquarium-example"}) + return urllib.request.urlopen(req, timeout=120) + + +def list_dir(path): + with urlopen(API + path) as resp: + return json.load(resp) + + +def download(url, dest, expect=None): + if expect and os.path.isfile(dest) and os.path.getsize(dest) == expect: + return + os.makedirs(os.path.dirname(dest), exist_ok=True) + print("get", os.path.basename(dest), flush=True) + with urlopen(url) as resp: + data = resp.read() + with open(dest, "wb") as f: + f.write(data) + + +def field(model, *names): + fields = model.get("fields", {}) + for name in names: + if name in fields: + return fields[name] + return None + + +def floats(field_info): + if not field_info: + return [] + return [float(v) for v in field_info["data"]] + + +def write_f32(f, values): + buf = array.array("f", values) + if sys.byteorder != "little": + buf.byteswap() + buf.tofile(f) + + +def write_u32(f, values): + buf = array.array("I", values) + if sys.byteorder != "little": + buf.byteswap() + buf.tofile(f) + + +def pack_model(js_path, aqm_path): + with open(js_path, "r", encoding="utf-8") as f: + data = json.load(f) + models = data["models"] + with open(aqm_path, "wb") as out: + out.write(b"AQM1") + write_u32(out, [len(models)]) + for model in models: + textures = model.get("textures", {}) + keys = [(k, textures[k]) for k in ("diffuse", "normalMap", "reflectionMap") if k in textures] + write_u32(out, [len(keys)]) + for key, filename in keys: + kb = key.encode("utf-8") + fb = filename.encode("utf-8") + write_u32(out, [len(kb)]) + out.write(kb) + write_u32(out, [len(fb)]) + out.write(fb) + pos_f = field(model, "position", "positions", "vertexPositions") + if not pos_f: + raise SystemExit("no positions in " + js_path + " fields=" + ",".join(model.get("fields", {}))) + pos = floats(pos_f) + ncomp = int(pos_f.get("numComponents", 3)) + nverts = len(pos) // ncomp + if ncomp != 3: + raise SystemExit("expected 3-component positions in " + js_path) + nor = floats(field(model, "normal", "normals", "vertexNormals")) + uv = floats(field(model, "texCoord", "texcoord", "uv", "vertexTextureCoords")) + tan = floats(field(model, "tangent", "tangents")) + binor = floats(field(model, "binormal", "binormals")) + idx_f = field(model, "indices", "index") + if idx_f: + indices = [int(v) for v in idx_f["data"]] + else: + indices = list(range(nverts)) + flags = 0 + if nor: + flags |= HAS_NORMAL + if uv: + flags |= HAS_UV + if tan: + flags |= HAS_TAN + if binor: + flags |= HAS_BIN + write_u32(out, [nverts, len(indices), flags]) + write_f32(out, pos) + if nor: + write_f32(out, nor) + if uv: + write_f32(out, uv) + if tan: + write_f32(out, tan) + if binor: + write_f32(out, binor) + write_u32(out, indices) + print(" ", os.path.basename(js_path), "verts", nverts, "idx", len(indices), "flags", flags, flush=True) + + +def main(): + os.makedirs(ASSET_DIR, exist_ok=True) + os.makedirs(MODEL_DIR, exist_ok=True) + entries = list_dir("assets") + kept = [] + for entry in entries: + if entry.get("type") != "file": + continue + name = entry["name"] + dest = os.path.join(ASSET_DIR, name) + download(RAW + "assets/" + name, dest, entry.get("size")) + kept.append(name) + for name in ("beam.png", "bubble.png"): + download(RAW + "static_assets/" + name, os.path.join(ASSET_DIR, name)) + kept.append(name) + + scenes = [] + for name in sorted(kept): + if not name.endswith(".js") or name == "PropPlacement.js": + continue + stem = name[:-3] + info = SCENE.get(stem, {"group": "base", "program": "auto", "fog": True, "blend": False}) + aqm = os.path.join(MODEL_DIR, stem + ".aqm") + pack_model(os.path.join(ASSET_DIR, name), aqm) + scenes.append({ + "name": stem, + "file": "models/" + stem + ".aqm", + "group": info["group"], + "program": info["program"], + "fog": info["fog"], + "blend": info["blend"], + }) + + with open(os.path.join(ASSET_DIR, "PropPlacement.js"), "r", encoding="utf-8") as f: + placement = json.load(f) + scene_doc = {"scenes": scenes, "objects": placement["objects"]} + with open(os.path.join(DATA, "scene.json"), "w", encoding="utf-8") as f: + json.dump(scene_doc, f, separators=(",", ":")) + print("scenes", len(scenes), "objects", len(placement["objects"]), flush=True) + + +if __name__ == "__main__": + main() diff --git a/examples/gl/aquariumExample/src/aquariumDawn.cpp b/examples/gl/aquariumExample/src/aquariumDawn.cpp new file mode 100644 index 00000000000..9a5381fc278 --- /dev/null +++ b/examples/gl/aquariumExample/src/aquariumDawn.cpp @@ -0,0 +1,1130 @@ +#include "ofApp.h" + +#if !OF_USE_DAWN + +bool aquariumDawnPrepare(ofApp &) { return false; } +void aquariumDawnDraw(ofApp &) {} + +#else + +#include "ofWebGPURenderer.h" + +#include +#include + +namespace { + +constexpr uint32_t kStride = 512; +constexpr uint32_t kSlots = 40000; + +enum Pipe { + PipeLit = 0, + PipeFish, + PipeSeaweed, + PipeInner, + PipeOuter, + PipeRay, + PipeBubble, + PipeCount +}; + +struct GpuMesh { + wgpu::Buffer pos, nor, uv, tan, bin, color, index; + uint64_t posBytes = 0, norBytes = 0, uvBytes = 0, tanBytes = 0, binBytes = 0, colorBytes = 0, indexBytes = 0; + uint32_t indexCount = 0; + uint32_t vertexCount = 0; + bool indexed = false; + wgpu::BindGroup group[3]; +}; + +float * at(float * u, int offset) { + return u + offset; +} + +void writeMat(float * u, int offset, const aq::Mat4 & m) { + std::memcpy(at(u, offset), m.m, sizeof(m.m)); +} + +void write4(float * u, int offset, float x, float y, float z, float w) { + float * p = at(u, offset); + p[0] = x; + p[1] = y; + p[2] = z; + p[3] = w; +} + +void write1(float * u, int offset, float v) { + u[offset] = v; +} + +struct State { + bool ready = false; + wgpu::Device device; + wgpu::Queue queue; + wgpu::BindGroupLayout layout; + wgpu::RenderPipeline pipeline[PipeCount]; + wgpu::Buffer uniform[3]; + wgpu::Sampler repeatSampler; + wgpu::Sampler clampSampler; + wgpu::Texture whiteTex; + wgpu::TextureView whiteView; + wgpu::Texture cubeTex; + wgpu::TextureView cubeView; + std::vector textures; + std::unordered_map views; + std::unordered_map meshes; + GpuMesh ray; + GpuMesh bubble; + int ring = 0; + bool loggedDraw = false; +}; + +State g; + +const char * kWGSL = R"WGSL( +struct U { + viewProjection: mat4x4f, + world: mat4x4f, + worldInverseTranspose: mat4x4f, + viewInverse: mat4x4f, + lightWorldPos: vec4f, + lightColor: vec4f, + ambient: vec4f, + specular: vec4f, + fogColor: vec4f, + shininess: f32, + specularFactor: f32, + uNormal: f32, + uReflect: f32, + uNormalBias: f32, + uFog: f32, + fogPower: f32, + fogMult: f32, + fogOffset: f32, + refractionFudge: f32, + eta: f32, + tankColorFudge: f32, + time: f32, + scale: f32, + fishLength: f32, + fishWaveLength: f32, + fishBendAmount: f32, + pad0: f32, + pad1: f32, + pad2: f32, + colorMult: vec4f, + worldPosition: vec4f, + nextPosition: vec4f, +} +@group(0) @binding(0) var u: U; +@group(0) @binding(1) var diffuseTex: texture_2d; +@group(0) @binding(2) var normalTex: texture_2d; +@group(0) @binding(3) var reflectTex: texture_2d; +@group(0) @binding(4) var skyTex: texture_cube; +@group(0) @binding(5) var sampRepeat: sampler; +@group(0) @binding(6) var sampClamp: sampler; + +struct LitIn { + @location(0) pos: vec3f, + @location(1) normal: vec3f, + @location(2) uv: vec2f, + @location(3) tangent: vec3f, + @location(4) binormal: vec3f, +} +struct SeaIn { + @location(0) pos: vec3f, + @location(1) normal: vec3f, + @location(2) uv: vec2f, +} +struct TexIn { + @location(0) pos: vec3f, + @location(1) uv: vec2f, +} +struct BubIn { + @location(0) pos: vec3f, + @location(1) uv: vec2f, + @location(2) color: vec4f, +} +struct LitOut { + @builtin(position) clip: vec4f, + @location(0) v_position: vec4f, + @location(1) uv: vec2f, + @location(2) tangent: vec3f, + @location(3) binormal: vec3f, + @location(4) normal: vec3f, + @location(5) toLight: vec3f, + @location(6) toView: vec3f, +} +struct TexOut { + @builtin(position) clip: vec4f, + @location(0) uv: vec2f, +} +struct BubOut { + @builtin(position) clip: vec4f, + @location(0) uv: vec2f, + @location(1) color: vec4f, +} + +fn lit(l: f32, h: f32, m: f32) -> vec4f { + let spec = select(0.0, pow(max(h, 0.0), m), l > 0.0); + return vec4f(1.0, max(l, 0.0), spec, 1.0); +} + +@vertex fn vsStatic(in: LitIn) -> LitOut { + var o: LitOut; + let wp = u.world * vec4f(in.pos, 1.0); + o.uv = in.uv; + o.v_position = u.viewProjection * wp; + o.normal = (u.worldInverseTranspose * vec4f(in.normal, 0.0)).xyz; + o.tangent = (u.worldInverseTranspose * vec4f(in.tangent, 0.0)).xyz; + o.binormal = (u.worldInverseTranspose * vec4f(in.binormal, 0.0)).xyz; + o.toLight = u.lightWorldPos.xyz - wp.xyz; + o.toView = (u.viewInverse[3] - wp).xyz; + o.clip = o.v_position; + return o; +} + +@vertex fn vsFish(in: LitIn) -> LitOut { + var o: LitOut; + let pos = in.pos; + let vz = normalize(u.worldPosition.xyz - u.nextPosition.xyz); + let vx = normalize(cross(vec3f(0.0, 1.0, 0.0), vz)); + let vy = cross(vz, vx); + let orient = mat4x4f(vec4f(vx, 0.0), vec4f(vy, 0.0), vec4f(vz, 0.0), vec4f(u.worldPosition.xyz, 1.0)); + let sm = mat4x4f( + vec4f(u.scale, 0.0, 0.0, 0.0), + vec4f(0.0, u.scale, 0.0, 0.0), + vec4f(0.0, 0.0, u.scale, 0.0), + vec4f(0.0, 0.0, 0.0, 1.0)); + let world = orient * sm; + let len = max(u.fishLength, 0.001); + let mult = select(-pos.z / len * 2.0, pos.z / len, pos.z > 0.0); + let s = sin(u.time + mult * u.fishWaveLength); + let offset = mult * mult * s * u.fishBendAmount; + let bent = vec4f(pos.x + offset, pos.y, pos.z, 1.0); + let wp = world * vec4f(pos, 1.0); + o.uv = in.uv; + o.v_position = u.viewProjection * (world * bent); + o.normal = (world * vec4f(in.normal, 0.0)).xyz; + o.tangent = (world * vec4f(in.tangent, 0.0)).xyz; + o.binormal = (world * vec4f(in.binormal, 0.0)).xyz; + o.toLight = u.lightWorldPos.xyz - wp.xyz; + o.toView = (u.viewInverse[3] - wp).xyz; + o.clip = o.v_position; + return o; +} + +@vertex fn vsSeaweed(in: SeaIn) -> LitOut { + var o: LitOut; + let toCamera = normalize(u.viewInverse[3].xyz - u.world[3].xyz); + let yAxis = vec3f(0.0, 1.0, 0.0); + let xAxis = cross(yAxis, toCamera); + let newWorld = mat4x4f(vec4f(xAxis, 0.0), vec4f(yAxis, 0.0), vec4f(xAxis, 0.0), u.world[3]); + let swayT = in.pos.y * 0.07; + let sway = swayT * swayT; + let swayed = vec4f(in.pos, 1.0) + vec4f(sin(u.time * 0.5) * sway, -4.0, 0.0, 0.0); + let wp = u.world * vec4f(in.pos, 1.0); + o.uv = in.uv; + o.v_position = (u.viewProjection * newWorld) * swayed; + o.normal = (newWorld * vec4f(in.normal, 0.0)).xyz; + o.tangent = vec3f(1.0, 0.0, 0.0); + o.binormal = vec3f(0.0, 0.0, 1.0); + o.toLight = u.lightWorldPos.xyz - wp.xyz; + o.toView = (u.viewInverse[3] - wp).xyz; + o.clip = o.v_position; + return o; +} + +@vertex fn vsTex(in: TexIn) -> TexOut { + var o: TexOut; + o.clip = u.viewProjection * u.world * vec4f(in.pos, 1.0); + o.uv = in.uv; + return o; +} + +@vertex fn vsBubble(in: BubIn) -> BubOut { + var o: BubOut; + o.clip = u.viewProjection * vec4f(in.pos, 1.0); + o.uv = in.uv; + o.color = in.color; + return o; +} + +fn fogMix(color: vec4f, clip: vec4f) -> vec4f { + if (u.uFog < 0.5) { + return color; + } + let ndc = max(clip.z / clip.w, 0.0); + let fog = clamp(pow(ndc, u.fogPower) * u.fogMult - u.fogOffset, 0.0, 1.0); + return mix(color, vec4f(u.fogColor.rgb, color.a), fog); +} + +@fragment fn fsLit(in: LitOut) -> @location(0) vec4f { + let diffuseColor = textureSample(diffuseTex, sampRepeat, in.uv); + var normalSpec = vec4f(0.0); + var normal = normalize(in.normal); + if (u.uNormal > 0.5) { + normalSpec = textureSample(normalTex, sampRepeat, in.uv); + var tangentNormal = normalSpec.xyz - vec3f(0.5); + if (u.uNormalBias > 0.5) { + tangentNormal = normalize(tangentNormal + vec3f(0.0, 0.0, 2.0)); + } + normal = normalize(mat3x3f(in.tangent, in.binormal, in.normal) * tangentNormal); + } + let surfaceToLight = normalize(in.toLight); + let surfaceToView = normalize(in.toView); + let halfVector = normalize(surfaceToLight + surfaceToView); + let litR = lit(dot(normal, surfaceToLight), dot(normal, halfVector), u.shininess); + let rgb = (u.lightColor * (diffuseColor * litR.y + diffuseColor * u.ambient + u.specular * litR.z * u.specularFactor * normalSpec.a)).rgb; + var outColor = vec4f(rgb, diffuseColor.a); + if (u.uReflect > 0.5) { + let reflection = textureSample(reflectTex, sampRepeat, in.uv); + let skyColor = textureSample(skyTex, sampClamp, -reflect(surfaceToView, normal)); + outColor = vec4f(mix(skyColor, outColor, 1.0 - reflection.r).rgb, diffuseColor.a); + } + return fogMix(outColor, in.v_position); +} + +@fragment fn fsSeaweed(in: LitOut) -> @location(0) vec4f { + let diffuseColor = textureSample(diffuseTex, sampRepeat, in.uv); + if (diffuseColor.a < 0.3) { + discard; + } + let normal = normalize(in.normal); + let surfaceToLight = normalize(in.toLight); + let surfaceToView = normalize(in.toView); + let halfVector = normalize(surfaceToLight + surfaceToView); + let litR = lit(dot(normal, surfaceToLight), dot(normal, halfVector), u.shininess); + let rgb = (u.lightColor * (diffuseColor * litR.y + diffuseColor * u.ambient + u.specular * litR.z * u.specularFactor)).rgb; + return fogMix(vec4f(rgb, diffuseColor.a), in.v_position); +} + +@fragment fn fsInner(in: LitOut) -> @location(0) vec4f { + let diffuseColor = textureSample(diffuseTex, sampRepeat, in.uv) + vec4f(u.tankColorFudge, u.tankColorFudge, u.tankColorFudge, 1.0); + let refraction = textureSample(reflectTex, sampRepeat, in.uv); + var normal = normalize(in.normal); + if (u.uNormal > 0.5) { + let normalSpec = textureSample(normalTex, sampRepeat, in.uv); + var tangentNormal = normalSpec.xyz - vec3f(0.5); + tangentNormal = normalize(tangentNormal + vec3f(0.0, 0.0, u.refractionFudge)); + normal = normalize(mat3x3f(in.tangent, in.binormal, in.normal) * tangentNormal); + } + let surfaceToView = normalize(in.toView); + let refractionVec = refract(surfaceToView, normal, u.eta); + let skyColor = textureSample(skyTex, sampClamp, refractionVec); + let outColor = vec4f(mix(skyColor * diffuseColor, diffuseColor, refraction.r).rgb, diffuseColor.a); + return fogMix(outColor, in.v_position); +} + +@fragment fn fsOuter(in: LitOut) -> @location(0) vec4f { + let diffuseColor = textureSample(diffuseTex, sampRepeat, in.uv); + let reflection = textureSample(reflectTex, sampRepeat, in.uv); + var normal = normalize(in.normal); + if (u.uNormal > 0.5) { + let normalSpec = textureSample(normalTex, sampRepeat, in.uv); + let tangentNormal = normalSpec.xyz - vec3f(0.5); + normal = normalize(mat3x3f(in.tangent, in.binormal, in.normal) * tangentNormal); + } + let surfaceToView = normalize(in.toView); + let skyColor = textureSample(skyTex, sampClamp, -reflect(surfaceToView, normal)); + let fudge = skyColor.rgb * vec3f(1.1); + let bright = min(1.0, fudge.r * fudge.g * fudge.b); + let reflectColor = mix(vec4f(skyColor.rgb, bright), diffuseColor, 1.0 - reflection.r); + let r = abs(dot(surfaceToView, normal)); + return vec4f(mix(skyColor, reflectColor, (r + 0.3) * reflection.r).rgb, 1.0 - r); +} + +@fragment fn fsTex(in: TexOut) -> @location(0) vec4f { + return textureSample(diffuseTex, sampRepeat, in.uv) * u.colorMult; +} + +@fragment fn fsBubble(in: BubOut) -> @location(0) vec4f { + return textureSample(diffuseTex, sampRepeat, in.uv) * in.color; +} +)WGSL"; + +wgpu::Buffer makeBuffer(uint64_t size, wgpu::BufferUsage usage) { + wgpu::BufferDescriptor desc = {}; + desc.size = std::max(size, 4); + desc.usage = usage; + return g.device.CreateBuffer(&desc); +} + +void writeBuffer(wgpu::Buffer buffer, const void * data, uint64_t size) { + if (!buffer || !data || size == 0) { + return; + } + g.queue.WriteBuffer(buffer, 0, data, size); +} + +int mipCountFor(int w, int h) { + int n = 1; + while (w > 1 || h > 1) { + w = std::max(1, w / 2); + h = std::max(1, h / 2); + ++n; + } + return n; +} + +std::vector downsample(const std::vector & src, int w, int h, int & nw, int & nh) { + nw = std::max(1, w / 2); + nh = std::max(1, h / 2); + std::vector dst(static_cast(nw) * static_cast(nh) * 4); + for (int y = 0; y < nh; ++y) { + for (int x = 0; x < nw; ++x) { + const int x0 = std::min(x * 2, w - 1); + const int y0 = std::min(y * 2, h - 1); + const int x1 = std::min(x0 + 1, w - 1); + const int y1 = std::min(y0 + 1, h - 1); + unsigned char * d = dst.data() + (static_cast(y) * nw + x) * 4; + for (int c = 0; c < 4; ++c) { + const int s00 = src[(static_cast(y0) * w + x0) * 4 + c]; + const int s10 = src[(static_cast(y0) * w + x1) * 4 + c]; + const int s01 = src[(static_cast(y1) * w + x0) * 4 + c]; + const int s11 = src[(static_cast(y1) * w + x1) * 4 + c]; + d[c] = static_cast((s00 + s10 + s01 + s11) / 4); + } + } + } + return dst; +} + +std::vector toRGBA(const ofPixels & pix) { + const int w = pix.getWidth(); + const int h = pix.getHeight(); + const int channels = pix.getNumChannels(); + std::vector rgba(static_cast(w) * static_cast(h) * 4, 255); + if (w <= 0 || h <= 0 || !pix.getData() || channels < 1) { + return {}; + } + const unsigned char * src = pix.getData(); + for (int i = 0; i < w * h; ++i) { + const unsigned char * p = src + static_cast(i) * channels; + unsigned char * d = rgba.data() + static_cast(i) * 4; + if (channels == 1) { + d[0] = d[1] = d[2] = p[0]; + } else if (channels == 2) { + d[0] = d[1] = d[2] = p[0]; + d[3] = p[1]; + } else if (channels == 3) { + d[0] = p[0]; + d[1] = p[1]; + d[2] = p[2]; + } else { + d[0] = p[0]; + d[1] = p[1]; + d[2] = p[2]; + d[3] = p[3]; + } + } + return rgba; +} + +wgpu::TextureView upload2D(const std::vector & rgba, int w, int h) { + if (rgba.empty() || w <= 0 || h <= 0) { + return g.whiteView; + } + const int mips = mipCountFor(w, h); + wgpu::TextureDescriptor desc = {}; + desc.usage = wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::CopyDst; + desc.dimension = wgpu::TextureDimension::e2D; + desc.size = { static_cast(w), static_cast(h), 1 }; + desc.format = wgpu::TextureFormat::RGBA8Unorm; + desc.mipLevelCount = static_cast(mips); + wgpu::Texture tex = g.device.CreateTexture(&desc); + std::vector level = rgba; + int lw = w; + int lh = h; + for (int mip = 0; mip < mips; ++mip) { + const uint32_t bpr = (static_cast(lw) * 4u + 255u) & ~255u; + std::vector padded(static_cast(bpr) * static_cast(lh)); + for (int y = 0; y < lh; ++y) { + std::memcpy(padded.data() + static_cast(y) * bpr, level.data() + static_cast(y) * lw * 4, static_cast(lw) * 4); + } + wgpu::TexelCopyTextureInfo dst = {}; + dst.texture = tex; + dst.mipLevel = static_cast(mip); + dst.aspect = wgpu::TextureAspect::All; + wgpu::TexelCopyBufferLayout layout = {}; + layout.bytesPerRow = bpr; + layout.rowsPerImage = static_cast(lh); + wgpu::Extent3D size = { static_cast(lw), static_cast(lh), 1 }; + g.queue.WriteTexture(&dst, padded.data(), padded.size(), &layout, &size); + if (mip + 1 < mips) { + int nw = 0; + int nh = 0; + level = downsample(level, lw, lh, nw, nh); + lw = nw; + lh = nh; + } + } + g.textures.push_back(tex); + return tex.CreateView(); +} + +wgpu::TextureView viewNamed(const std::string & name) { + if (name.empty()) { + return g.whiteView; + } + auto found = g.views.find(name); + if (found == g.views.end()) { + return g.whiteView; + } + return found->second; +} + +wgpu::BindGroup makeGroup(int slot, wgpu::TextureView diffuse, wgpu::TextureView normal, wgpu::TextureView reflect) { + wgpu::BindGroupEntry entries[7] = {}; + entries[0].binding = 0; + entries[0].buffer = g.uniform[slot]; + entries[0].offset = 0; + entries[0].size = kStride; + entries[1].binding = 1; + entries[1].textureView = diffuse ? diffuse : g.whiteView; + entries[2].binding = 2; + entries[2].textureView = normal ? normal : g.whiteView; + entries[3].binding = 3; + entries[3].textureView = reflect ? reflect : g.whiteView; + entries[4].binding = 4; + entries[4].textureView = g.cubeView; + entries[5].binding = 5; + entries[5].sampler = g.repeatSampler; + entries[6].binding = 6; + entries[6].sampler = g.clampSampler; + wgpu::BindGroupDescriptor desc = {}; + desc.layout = g.layout; + desc.entryCount = 7; + desc.entries = entries; + return g.device.CreateBindGroup(&desc); +} + +void finishGroups(GpuMesh & mesh, wgpu::TextureView diffuse, wgpu::TextureView normal, wgpu::TextureView reflect) { + for (int slot = 0; slot < 3; ++slot) { + mesh.group[slot] = makeGroup(slot, diffuse, normal, reflect); + } +} + +wgpu::Buffer uploadArray(const std::vector & data, uint64_t & bytes) { + bytes = data.size() * sizeof(float); + wgpu::Buffer buffer = makeBuffer(std::max(bytes, 4), wgpu::BufferUsage::Vertex | wgpu::BufferUsage::CopyDst); + if (!data.empty()) { + writeBuffer(buffer, data.data(), bytes); + } + return buffer; +} + +bool uploadModel(const ofApp::Mesh & mesh, const ofApp::Model & model) { + if (mesh.indices.empty() || mesh.pos.empty()) { + return false; + } + GpuMesh gpu; + gpu.indexed = true; + gpu.indexCount = static_cast(mesh.indices.size()); + gpu.vertexCount = static_cast(mesh.pos.size() / 3); + gpu.pos = uploadArray(mesh.pos, gpu.posBytes); + gpu.nor = uploadArray(mesh.nor, gpu.norBytes); + gpu.uv = uploadArray(mesh.uv, gpu.uvBytes); + gpu.tan = uploadArray(mesh.tan, gpu.tanBytes); + gpu.bin = uploadArray(mesh.bin, gpu.binBytes); + gpu.indexBytes = mesh.indices.size() * sizeof(uint32_t); + gpu.index = makeBuffer(gpu.indexBytes, wgpu::BufferUsage::Index | wgpu::BufferUsage::CopyDst); + writeBuffer(gpu.index, mesh.indices.data(), gpu.indexBytes); + finishGroups(gpu, viewNamed(model.diffuse), viewNamed(model.normalMap), viewNamed(model.reflectionMap)); + g.meshes[&mesh] = std::move(gpu); + return true; +} + +wgpu::RenderPipeline makePipeline(const char * vs, const char * fs, int kind, bool blend, bool additive, bool depthWrite, wgpu::CullMode cull) { + wgpu::VertexAttribute attrs[5] = {}; + wgpu::VertexBufferLayout layouts[5] = {}; + int n = 0; + auto add = [&](int location, wgpu::VertexFormat format, uint64_t stride) { + attrs[n].format = format; + attrs[n].offset = 0; + attrs[n].shaderLocation = static_cast(location); + layouts[n].arrayStride = stride; + layouts[n].stepMode = wgpu::VertexStepMode::Vertex; + layouts[n].attributeCount = 1; + layouts[n].attributes = &attrs[n]; + ++n; + }; + if (kind == PipeBubble) { + add(0, wgpu::VertexFormat::Float32x3, 12); + add(1, wgpu::VertexFormat::Float32x2, 8); + add(2, wgpu::VertexFormat::Float32x4, 16); + } else if (kind == PipeRay) { + add(0, wgpu::VertexFormat::Float32x3, 12); + add(1, wgpu::VertexFormat::Float32x2, 8); + } else if (kind == PipeSeaweed) { + add(0, wgpu::VertexFormat::Float32x3, 12); + add(1, wgpu::VertexFormat::Float32x3, 12); + add(2, wgpu::VertexFormat::Float32x2, 8); + } else { + add(0, wgpu::VertexFormat::Float32x3, 12); + add(1, wgpu::VertexFormat::Float32x3, 12); + add(2, wgpu::VertexFormat::Float32x2, 8); + add(3, wgpu::VertexFormat::Float32x3, 12); + add(4, wgpu::VertexFormat::Float32x3, 12); + } + + wgpu::BlendState blendState = {}; + if (additive) { + blendState.color.srcFactor = wgpu::BlendFactor::SrcAlpha; + blendState.color.dstFactor = wgpu::BlendFactor::One; + blendState.alpha.srcFactor = wgpu::BlendFactor::SrcAlpha; + blendState.alpha.dstFactor = wgpu::BlendFactor::One; + } else if (blend) { + blendState.color.srcFactor = wgpu::BlendFactor::SrcAlpha; + blendState.color.dstFactor = wgpu::BlendFactor::OneMinusSrcAlpha; + blendState.alpha.srcFactor = wgpu::BlendFactor::SrcAlpha; + blendState.alpha.dstFactor = wgpu::BlendFactor::OneMinusSrcAlpha; + } else { + blendState.color.srcFactor = wgpu::BlendFactor::One; + blendState.color.dstFactor = wgpu::BlendFactor::Zero; + blendState.alpha.srcFactor = wgpu::BlendFactor::One; + blendState.alpha.dstFactor = wgpu::BlendFactor::Zero; + } + blendState.color.operation = wgpu::BlendOperation::Add; + blendState.alpha.operation = wgpu::BlendOperation::Add; + + wgpu::ColorTargetState target = {}; + target.format = wgpu::TextureFormat::BGRA8Unorm; + target.blend = &blendState; + target.writeMask = wgpu::ColorWriteMask::All; + + wgpu::FragmentState fragment = {}; + fragment.module = nullptr; + wgpu::ShaderSourceWGSL wgsl; + wgsl.code = kWGSL; + wgpu::ShaderModuleDescriptor shaderDesc; + shaderDesc.nextInChain = &wgsl; + wgpu::ShaderModule module = g.device.CreateShaderModule(&shaderDesc); + fragment.module = module; + fragment.entryPoint = fs; + fragment.targetCount = 1; + fragment.targets = ⌖ + + wgpu::DepthStencilState depth = {}; + depth.format = wgpu::TextureFormat::Depth24Plus; + depth.depthWriteEnabled = depthWrite ? wgpu::OptionalBool::True : wgpu::OptionalBool::False; + depth.depthCompare = wgpu::CompareFunction::LessEqual; + + wgpu::RenderPipelineDescriptor pipe = {}; + pipe.layout = nullptr; + wgpu::PipelineLayoutDescriptor layoutDesc; + layoutDesc.bindGroupLayoutCount = 1; + layoutDesc.bindGroupLayouts = &g.layout; + wgpu::PipelineLayout layout = g.device.CreatePipelineLayout(&layoutDesc); + pipe.layout = layout; + pipe.vertex.module = module; + pipe.vertex.entryPoint = vs; + pipe.vertex.bufferCount = static_cast(n); + pipe.vertex.buffers = layouts; + pipe.fragment = &fragment; + pipe.primitive.topology = wgpu::PrimitiveTopology::TriangleList; + pipe.primitive.cullMode = cull; + pipe.primitive.frontFace = wgpu::FrontFace::CCW; + pipe.depthStencil = &depth; + pipe.multisample.count = 1; + return g.device.CreateRenderPipeline(&pipe); +} + +void fillShared(float * u, ofApp & app, const aq::Mat4 & viewProjection) { + std::memset(u, 0, kStride); + writeMat(u, 0, viewProjection); + writeMat(u, 48, app.viewInverse); + write4(u, 64, app.light[0], app.light[1], app.light[2], 0.f); + write4(u, 68, 1.f, 1.f, 1.f, 1.f); + write4(u, 72, app.ambient[0], app.ambient[1], app.ambient[2], 1.f); + write4(u, 76, 1.f, 1.f, 1.f, 1.f); + write4(u, 80, app.fogRgb[0], app.fogRgb[1], app.fogRgb[2], 1.f); + write1(u, 90, app.fogPower); + write1(u, 91, app.fogMult); + write1(u, 92, app.fogOffset); + write1(u, 93, app.refractionFudge); + write1(u, 94, app.eta); + write1(u, 95, app.tankColorFudge); +} + +struct Item { + int pipe = PipeLit; + const GpuMesh * mesh = nullptr; + float u[kStride / sizeof(float)]; +}; + +void pushModel(std::vector & items, ofApp & app, const ofApp::Model & model, const aq::Mat4 & world, int pipe, float specular, float useNormal, float useReflect, float bias, float fog, float time) { + auto found = g.meshes.find(&model.mesh); + if (found == g.meshes.end() || found->second.indexCount == 0) { + return; + } + Item item; + item.pipe = pipe; + item.mesh = &found->second; + fillShared(item.u, app, app.viewProjection); + writeMat(item.u, 16, world); + aq::Mat4 inv; + aq::Mat4 invT; + aq::inverse(inv, world); + aq::transpose(invT, inv); + writeMat(item.u, 32, invT); + write1(item.u, 84, pipe == PipeFish ? 5.f : 50.f); + write1(item.u, 85, pipe == PipeFish ? 0.3f : specular); + write1(item.u, 86, useNormal); + write1(item.u, 87, useReflect); + write1(item.u, 88, bias); + write1(item.u, 89, fog); + write1(item.u, 96, time); + items.push_back(item); +} + +void bindMesh(wgpu::RenderPassEncoder pass, const GpuMesh & mesh, int pipe) { + if (pipe == PipeRay) { + pass.SetVertexBuffer(0, mesh.pos, 0, mesh.posBytes); + pass.SetVertexBuffer(1, mesh.uv, 0, mesh.uvBytes); + return; + } + if (pipe == PipeBubble) { + pass.SetVertexBuffer(0, mesh.pos, 0, mesh.posBytes); + pass.SetVertexBuffer(1, mesh.uv, 0, mesh.uvBytes); + pass.SetVertexBuffer(2, mesh.color, 0, mesh.colorBytes); + return; + } + if (pipe == PipeSeaweed) { + pass.SetVertexBuffer(0, mesh.pos, 0, mesh.posBytes); + pass.SetVertexBuffer(1, mesh.nor, 0, mesh.norBytes); + pass.SetVertexBuffer(2, mesh.uv, 0, mesh.uvBytes); + return; + } + pass.SetVertexBuffer(0, mesh.pos, 0, mesh.posBytes); + pass.SetVertexBuffer(1, mesh.nor, 0, mesh.norBytes); + pass.SetVertexBuffer(2, mesh.uv, 0, mesh.uvBytes); + pass.SetVertexBuffer(3, mesh.tan, 0, mesh.tanBytes); + pass.SetVertexBuffer(4, mesh.bin, 0, mesh.binBytes); + if (mesh.indexed) { + pass.SetIndexBuffer(mesh.index, wgpu::IndexFormat::Uint32, 0, mesh.indexBytes); + } +} + +} // namespace + +bool aquariumDawnPrepare(ofApp & app) { + auto renderer = std::dynamic_pointer_cast(ofGetCurrentRenderer()); + if (!renderer || !renderer->getDevice()) { + ofLogError("aquarium") << "dawn device missing"; + return false; + } + g.device = renderer->getDevice(); + g.queue = renderer->getQueue(); + + const unsigned char whitePx[4] = {255, 255, 255, 255}; + g.whiteView = upload2D(std::vector(whitePx, whitePx + 4), 1, 1); + for (const auto & image : app.images) { + const ofPixels & pix = image.second.getPixels(); + std::vector rgba = toRGBA(pix); + if (!rgba.empty()) { + g.views[image.first] = upload2D(rgba, pix.getWidth(), pix.getHeight()); + } + } + + const char * faces[6] = { + "assets/GlobeOuter_EM_positive_x.jpg", + "assets/GlobeOuter_EM_negative_x.jpg", + "assets/GlobeOuter_EM_positive_y.jpg", + "assets/GlobeOuter_EM_negative_y.jpg", + "assets/GlobeOuter_EM_positive_z.jpg", + "assets/GlobeOuter_EM_negative_z.jpg" + }; + std::vector> faceRGBA(6); + int cw = 0; + int ch = 0; + bool cubeOk = true; + for (int i = 0; i < 6; ++i) { + ofImage face; + face.setUseTexture(false); + if (!face.load(faces[i])) { + cubeOk = false; + break; + } + if (i == 0) { + cw = face.getWidth(); + ch = face.getHeight(); + } else if (face.getWidth() != cw || face.getHeight() != ch) { + cubeOk = false; + break; + } + faceRGBA[i] = toRGBA(face.getPixels()); + } + if (!cubeOk || cw <= 0 || ch <= 0) { + cw = ch = 1; + for (int i = 0; i < 6; ++i) { + faceRGBA[i] = {255, 255, 255, 255}; + } + } + const int cubeMips = mipCountFor(cw, ch); + wgpu::TextureDescriptor cubeDesc = {}; + cubeDesc.usage = wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::CopyDst; + cubeDesc.dimension = wgpu::TextureDimension::e2D; + cubeDesc.size = { static_cast(cw), static_cast(ch), 6 }; + cubeDesc.format = wgpu::TextureFormat::RGBA8Unorm; + cubeDesc.mipLevelCount = static_cast(cubeMips); + g.cubeTex = g.device.CreateTexture(&cubeDesc); + for (int face = 0; face < 6; ++face) { + std::vector level = faceRGBA[face]; + int lw = cw; + int lh = ch; + for (int mip = 0; mip < cubeMips; ++mip) { + const uint32_t bpr = (static_cast(lw) * 4u + 255u) & ~255u; + std::vector padded(static_cast(bpr) * static_cast(lh)); + for (int y = 0; y < lh; ++y) { + std::memcpy(padded.data() + static_cast(y) * bpr, level.data() + static_cast(y) * lw * 4, static_cast(lw) * 4); + } + wgpu::TexelCopyTextureInfo dst = {}; + dst.texture = g.cubeTex; + dst.mipLevel = static_cast(mip); + dst.origin = { 0, 0, static_cast(face) }; + dst.aspect = wgpu::TextureAspect::All; + wgpu::TexelCopyBufferLayout layout = {}; + layout.bytesPerRow = bpr; + layout.rowsPerImage = static_cast(lh); + wgpu::Extent3D size = { static_cast(lw), static_cast(lh), 1 }; + g.queue.WriteTexture(&dst, padded.data(), padded.size(), &layout, &size); + if (mip + 1 < cubeMips) { + int nw = 0; + int nh = 0; + level = downsample(level, lw, lh, nw, nh); + lw = nw; + lh = nh; + } + } + } + wgpu::TextureViewDescriptor cubeViewDesc = {}; + cubeViewDesc.dimension = wgpu::TextureViewDimension::Cube; + cubeViewDesc.format = wgpu::TextureFormat::RGBA8Unorm; + cubeViewDesc.mipLevelCount = static_cast(cubeMips); + cubeViewDesc.arrayLayerCount = 6; + g.cubeView = g.cubeTex.CreateView(&cubeViewDesc); + + wgpu::SamplerDescriptor repeatDesc = {}; + repeatDesc.addressModeU = wgpu::AddressMode::Repeat; + repeatDesc.addressModeV = wgpu::AddressMode::Repeat; + repeatDesc.addressModeW = wgpu::AddressMode::Repeat; + repeatDesc.magFilter = wgpu::FilterMode::Linear; + repeatDesc.minFilter = wgpu::FilterMode::Linear; + repeatDesc.mipmapFilter = wgpu::MipmapFilterMode::Linear; + g.repeatSampler = g.device.CreateSampler(&repeatDesc); + wgpu::SamplerDescriptor clampDesc = repeatDesc; + clampDesc.addressModeU = wgpu::AddressMode::ClampToEdge; + clampDesc.addressModeV = wgpu::AddressMode::ClampToEdge; + clampDesc.addressModeW = wgpu::AddressMode::ClampToEdge; + g.clampSampler = g.device.CreateSampler(&clampDesc); + + for (int slot = 0; slot < 3; ++slot) { + g.uniform[slot] = makeBuffer(static_cast(kSlots) * kStride, wgpu::BufferUsage::Uniform | wgpu::BufferUsage::CopyDst); + } + + wgpu::BindGroupLayoutEntry layoutEntries[7] = {}; + layoutEntries[0].binding = 0; + layoutEntries[0].visibility = wgpu::ShaderStage::Vertex | wgpu::ShaderStage::Fragment; + layoutEntries[0].buffer.type = wgpu::BufferBindingType::Uniform; + layoutEntries[0].buffer.hasDynamicOffset = true; + layoutEntries[0].buffer.minBindingSize = kStride; + for (int binding = 1; binding <= 3; ++binding) { + layoutEntries[binding].binding = static_cast(binding); + layoutEntries[binding].visibility = wgpu::ShaderStage::Fragment; + layoutEntries[binding].texture.sampleType = wgpu::TextureSampleType::Float; + layoutEntries[binding].texture.viewDimension = wgpu::TextureViewDimension::e2D; + } + layoutEntries[4].binding = 4; + layoutEntries[4].visibility = wgpu::ShaderStage::Fragment; + layoutEntries[4].texture.sampleType = wgpu::TextureSampleType::Float; + layoutEntries[4].texture.viewDimension = wgpu::TextureViewDimension::Cube; + layoutEntries[5].binding = 5; + layoutEntries[5].visibility = wgpu::ShaderStage::Fragment; + layoutEntries[5].sampler.type = wgpu::SamplerBindingType::Filtering; + layoutEntries[6].binding = 6; + layoutEntries[6].visibility = wgpu::ShaderStage::Fragment; + layoutEntries[6].sampler.type = wgpu::SamplerBindingType::Filtering; + wgpu::BindGroupLayoutDescriptor layoutDesc = {}; + layoutDesc.entryCount = 7; + layoutDesc.entries = layoutEntries; + g.layout = g.device.CreateBindGroupLayout(&layoutDesc); + + g.pipeline[PipeLit] = makePipeline("vsStatic", "fsLit", PipeLit, false, false, true, wgpu::CullMode::Back); + g.pipeline[PipeFish] = makePipeline("vsFish", "fsLit", PipeFish, true, false, true, wgpu::CullMode::Back); + g.pipeline[PipeSeaweed] = makePipeline("vsSeaweed", "fsSeaweed", PipeSeaweed, true, false, true, wgpu::CullMode::Back); + g.pipeline[PipeInner] = makePipeline("vsStatic", "fsInner", PipeInner, false, false, true, wgpu::CullMode::Back); + g.pipeline[PipeOuter] = makePipeline("vsStatic", "fsOuter", PipeOuter, true, false, true, wgpu::CullMode::Back); + g.pipeline[PipeRay] = makePipeline("vsTex", "fsTex", PipeRay, true, true, false, wgpu::CullMode::None); + g.pipeline[PipeBubble] = makePipeline("vsBubble", "fsBubble", PipeBubble, true, false, false, wgpu::CullMode::None); + + int uploaded = 0; + for (const ofApp::Scene & scene : app.scenes) { + for (const ofApp::Model & model : scene.models) { + if (uploadModel(model.mesh, model)) { + ++uploaded; + } + } + } + + std::vector rp, ru; + const float corner[4][3] = {{-0.5f, 0.f, 0.f}, {0.5f, 0.f, 0.f}, {0.5f, 1.f, 0.f}, {-0.5f, 1.f, 0.f}}; + const float coord[4][2] = {{0.f, 0.f}, {1.f, 0.f}, {1.f, 1.f}, {0.f, 1.f}}; + const int order[6] = {0, 1, 2, 0, 2, 3}; + for (int index : order) { + rp.push_back(corner[index][0]); + rp.push_back(corner[index][1]); + rp.push_back(corner[index][2]); + ru.push_back(coord[index][0]); + ru.push_back(coord[index][1]); + } + g.ray.vertexCount = 6; + g.ray.pos = uploadArray(rp, g.ray.posBytes); + g.ray.uv = uploadArray(ru, g.ray.uvBytes); + finishGroups(g.ray, viewNamed("LightRay.png"), g.whiteView, g.whiteView); + finishGroups(g.bubble, viewNamed("bubble.png"), g.whiteView, g.whiteView); + + g.ready = g.pipeline[PipeLit] && g.pipeline[PipeFish] && uploaded > 0; + std::cout << "aquarium dawn meshes=" << uploaded + << " textures=" << g.views.size() + << " pipelines=" << (g.ready ? "ok" : "failed") << std::endl; + ofLogNotice("aquarium") << "dawn meshes=" << uploaded + << " textures=" << g.views.size() + << " pipelines=" << (g.ready ? "ok" : "failed"); + return g.ready; +} + +void aquariumDawnDraw(ofApp & app) { + if (!g.ready) { + return; + } + auto renderer = std::dynamic_pointer_cast(ofGetCurrentRenderer()); + if (!renderer) { + return; + } + wgpu::RenderPassEncoder pass = renderer->getRenderPass(); + if (!pass) { + return; + } + const float width = static_cast(std::max(ofGetWidth(), 1)); + const float height = static_cast(std::max(ofGetHeight(), 1)); + pass.SetViewport(0.f, 0.f, width, height, 0.f, 1.f); + + std::vector items; + items.reserve(static_cast(app.fishTotal) + 128); + auto drawGroup = [&](const std::string & group, float specular) { + int index = 0; + for (const ofApp::Object & obj : app.objects) { + const ofApp::Scene & scene = app.scenes[obj.scene]; + if (scene.group != group) { + continue; + } + int pipe = PipeLit; + if (scene.program == "inner") { + pipe = PipeInner; + } else if (scene.program == "outer") { + pipe = PipeOuter; + } else if (scene.program == "seaweed") { + pipe = PipeSeaweed; + } + const float fog = (app.optFog && scene.fog) ? 1.f : 0.f; + for (const ofApp::Model & model : scene.models) { + float useNormal = 0.f; + float useReflect = 0.f; + if (scene.program != "diffuse" && scene.program != "seaweed" && app.optNormal && !model.normalMap.empty()) { + useNormal = 1.f; + } + if (scene.program == "auto" && app.optReflect && !model.reflectionMap.empty()) { + useReflect = 1.f; + } + pushModel(items, app, model, obj.world, pipe, specular, useNormal, useReflect, 0.f, fog, app.clock + static_cast(index)); + } + ++index; + } + }; + drawGroup("base", 1.f); + + app.resetPseudo(); + for (auto & kind : app.fish) { + if (kind.num <= 0 || kind.scene < 0 || app.scenes[kind.scene].models.empty()) { + continue; + } + const ofApp::Model & model = app.scenes[kind.scene].models[0]; + const float useNormal = (app.optNormal && !model.normalMap.empty()) ? 1.f : 0.f; + const float useReflect = (app.optReflect && !model.reflectionMap.empty()) ? 1.f : 0.f; + const float fog = app.optFog ? 1.f : 0.f; + const float baseClock = app.clock * app.fishSpeed; + const float tail = kind.tailSpeed * app.fishTailSpeed; + const float height = app.fishHeight + kind.heightOffset; + const float heightRange = app.fishHeightRange * kind.heightRange; + for (int i = 0; i < kind.num; ++i) { + const float fishClock = baseClock + static_cast(i) * app.fishOffset; + const float speed = kind.speed + app.pseudo() * kind.speedRange; + const float scale = 1.f + app.pseudo(); + const float xRadius = kind.radius + app.pseudo() * kind.radiusRange; + const float yRadius = 2.f + app.pseudo() * heightRange; + const float zRadius = kind.radius + app.pseudo() * kind.radiusRange; + const float speedClock = fishClock * speed; + const float px = std::sin(speedClock * app.fishXClock) * xRadius; + const float py = std::sin(speedClock * app.fishYClock) * yRadius + height; + const float pz = std::cos(speedClock * app.fishZClock) * zRadius; + const float nx = std::sin(speedClock * app.fishXClock - 0.04f) * xRadius; + const float ny = std::sin(speedClock * app.fishYClock - 0.01f) * yRadius + height; + const float nz = std::cos(speedClock * app.fishZClock - 0.04f) * zRadius; + const float time = std::fmod((app.clock + static_cast(i)) * tail * speed, static_cast(PI) * 2.f); + auto found = g.meshes.find(&model.mesh); + if (found == g.meshes.end()) { + continue; + } + Item item; + item.pipe = PipeFish; + item.mesh = &found->second; + fillShared(item.u, app, app.viewProjection); + write1(item.u, 84, 5.f); + write1(item.u, 85, 0.3f); + write1(item.u, 86, useNormal); + write1(item.u, 87, useReflect); + write1(item.u, 88, 1.f); + write1(item.u, 89, fog); + write1(item.u, 96, time); + write1(item.u, 97, scale); + write1(item.u, 98, kind.length); + write1(item.u, 99, kind.wave); + write1(item.u, 100, kind.bend); + write4(item.u, 108, px, py, pz, 1.f); + write4(item.u, 112, nx, ny, nz, 1.f); + items.push_back(item); + } + } + if (app.optTank) { + drawGroup("inner", 1.f); + } + drawGroup("seaweed", 1.f); + if (app.optMuseum) { + drawGroup("outside", 0.f); + } + if (app.optBubbles && !app.bubbles.empty() && g.bubble.group[0]) { + float right[3]; + float upv[3]; + aq::axis(right, app.viewInverse, 0); + aq::axis(upv, app.viewInverse, 1); + const float quad[6][2] = {{-1, -1}, {1, -1}, {1, 1}, {-1, -1}, {1, 1}, {-1, 1}}; + const float quv[6][2] = {{0, 0}, {1, 0}, {1, 1}, {0, 0}, {1, 1}, {0, 1}}; + std::vector pos, uv, col; + pos.reserve(app.bubbles.size() * 18); + uv.reserve(app.bubbles.size() * 12); + col.reserve(app.bubbles.size() * 24); + for (const ofApp::Bubble & bubble : app.bubbles) { + const float t = bubble.age; + const float cx = bubble.x + bubble.vx * t; + const float cy = bubble.y + bubble.vy * t + 0.5f * bubble.ay * t * t; + const float cz = bubble.z + bubble.vz * t; + const float u = std::min(1.f, t / bubble.life); + const float size = bubble.size0 + (bubble.size1 - bubble.size0) * u; + const float alpha = u < 0.8f ? 1.f : (1.f - u) / 0.2f; + const float half = size * 0.5f; + for (int v = 0; v < 6; ++v) { + pos.push_back(cx + right[0] * quad[v][0] * half + upv[0] * quad[v][1] * half); + pos.push_back(cy + right[1] * quad[v][0] * half + upv[1] * quad[v][1] * half); + pos.push_back(cz + right[2] * quad[v][0] * half + upv[2] * quad[v][1] * half); + uv.push_back(quv[v][0]); + uv.push_back(quv[v][1]); + col.push_back(0.7f); + col.push_back(0.8f); + col.push_back(1.f); + col.push_back(alpha); + } + } + const uint64_t posBytes = pos.size() * sizeof(float); + const uint64_t uvBytes = uv.size() * sizeof(float); + const uint64_t colBytes = col.size() * sizeof(float); + if (g.bubble.posBytes < posBytes) { + g.bubble.pos = makeBuffer(posBytes, wgpu::BufferUsage::Vertex | wgpu::BufferUsage::CopyDst); + g.bubble.posBytes = g.bubble.pos.GetSize(); + } + if (g.bubble.uvBytes < uvBytes) { + g.bubble.uv = makeBuffer(uvBytes, wgpu::BufferUsage::Vertex | wgpu::BufferUsage::CopyDst); + g.bubble.uvBytes = g.bubble.uv.GetSize(); + } + if (g.bubble.colorBytes < colBytes) { + g.bubble.color = makeBuffer(colBytes, wgpu::BufferUsage::Vertex | wgpu::BufferUsage::CopyDst); + g.bubble.colorBytes = g.bubble.color.GetSize(); + } + writeBuffer(g.bubble.pos, pos.data(), posBytes); + writeBuffer(g.bubble.uv, uv.data(), uvBytes); + writeBuffer(g.bubble.color, col.data(), colBytes); + g.bubble.vertexCount = static_cast(pos.size() / 3); + Item item; + item.pipe = PipeBubble; + item.mesh = &g.bubble; + fillShared(item.u, app, app.viewProjection); + items.push_back(item); + } + if (app.optRays && g.ray.vertexCount > 0) { + for (const ofApp::Ray & ray : app.rays) { + const float lerp = ray.duration > 0.f ? ray.timer / ray.duration : 0.f; + const float y = std::max(70.f, std::min(120.f, 50.f + app.eyeHeight)); + aq::Mat4 spin, shift, placed, scale, world; + aq::rotationZ(spin, ray.rot + lerp * 0.2f); + aq::translation(shift, ray.x, y, 0.f); + aq::mul(placed, spin, shift); + aq::scaling(scale, 10.f, -100.f, 10.f); + aq::mul(world, scale, placed); + Item item; + item.pipe = PipeRay; + item.mesh = &g.ray; + fillShared(item.u, app, app.viewProjectionNoRot); + writeMat(item.u, 16, world); + const float alpha = std::sin(lerp * static_cast(PI)); + write4(item.u, 104, 1.f, 1.f, 1.f, alpha); + items.push_back(item); + } + } + if (app.optTank) { + drawGroup("outer", 1.f); + } + + if (items.size() > kSlots) { + items.resize(kSlots); + } + const int slot = g.ring; + g.ring = (g.ring + 1) % 3; + std::vector bytes(items.size() * kStride); + for (size_t i = 0; i < items.size(); ++i) { + std::memcpy(bytes.data() + i * kStride, items[i].u, kStride); + } + if (!bytes.empty()) { + g.queue.WriteBuffer(g.uniform[slot], 0, bytes.data(), bytes.size()); + } + int currentPipe = -1; + const GpuMesh * currentMesh = nullptr; + for (size_t i = 0; i < items.size(); ++i) { + const Item & item = items[i]; + if (!item.mesh || !g.pipeline[item.pipe] || !item.mesh->group[slot]) { + continue; + } + if (currentPipe != item.pipe) { + pass.SetPipeline(g.pipeline[item.pipe]); + currentPipe = item.pipe; + currentMesh = nullptr; + } + if (currentMesh != item.mesh) { + bindMesh(pass, *item.mesh, item.pipe); + currentMesh = item.mesh; + } + uint32_t offset = static_cast(i * kStride); + pass.SetBindGroup(0, item.mesh->group[slot], 1, &offset); + if (item.mesh->indexed) { + pass.DrawIndexed(item.mesh->indexCount); + } else { + pass.Draw(item.mesh->vertexCount); + } + } + if (!g.loggedDraw) { + g.loggedDraw = true; + std::cout << "aquarium dawn draws=" << items.size() << std::endl; + ofLogNotice("aquarium") << "dawn draws=" << items.size(); + } +} + +#endif diff --git a/examples/gl/aquariumExample/src/aquariumMath.h b/examples/gl/aquariumExample/src/aquariumMath.h new file mode 100644 index 00000000000..4a4c9f6eebe --- /dev/null +++ b/examples/gl/aquariumExample/src/aquariumMath.h @@ -0,0 +1,192 @@ +#pragma once +// Matrix helpers copied from tdl.fast (BSD-3-Clause, Copyright 2009, Google Inc.). +// tdl installs the row-major multiply. The float[16] layout is what WebGL uploads. + +#include +#include + +namespace aq { + +struct Mat4 { + float m[16]; +}; + +inline void identity(Mat4 & d) { + std::memset(d.m, 0, sizeof(d.m)); + d.m[0] = d.m[5] = d.m[10] = d.m[15] = 1.f; +} + +inline void copy(Mat4 & d, const Mat4 & a) { + std::memcpy(d.m, a.m, sizeof(d.m)); +} + +// tdl.fast.rowMajor.mulMatrixMatrix4. Locals first so dst may alias a or b. +inline void mul(Mat4 & dst, const Mat4 & aIn, const Mat4 & bIn) { + const float * a = aIn.m; + const float * b = bIn.m; + const float a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3]; + const float a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7]; + const float a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11]; + const float a30 = a[12], a31 = a[13], a32 = a[14], a33 = a[15]; + const float b00 = b[0], b01 = b[1], b02 = b[2], b03 = b[3]; + const float b10 = b[4], b11 = b[5], b12 = b[6], b13 = b[7]; + const float b20 = b[8], b21 = b[9], b22 = b[10], b23 = b[11]; + const float b30 = b[12], b31 = b[13], b32 = b[14], b33 = b[15]; + dst.m[0] = a00 * b00 + a01 * b10 + a02 * b20 + a03 * b30; + dst.m[1] = a00 * b01 + a01 * b11 + a02 * b21 + a03 * b31; + dst.m[2] = a00 * b02 + a01 * b12 + a02 * b22 + a03 * b32; + dst.m[3] = a00 * b03 + a01 * b13 + a02 * b23 + a03 * b33; + dst.m[4] = a10 * b00 + a11 * b10 + a12 * b20 + a13 * b30; + dst.m[5] = a10 * b01 + a11 * b11 + a12 * b21 + a13 * b31; + dst.m[6] = a10 * b02 + a11 * b12 + a12 * b22 + a13 * b32; + dst.m[7] = a10 * b03 + a11 * b13 + a12 * b23 + a13 * b33; + dst.m[8] = a20 * b00 + a21 * b10 + a22 * b20 + a23 * b30; + dst.m[9] = a20 * b01 + a21 * b11 + a22 * b21 + a23 * b31; + dst.m[10] = a20 * b02 + a21 * b12 + a22 * b22 + a23 * b32; + dst.m[11] = a20 * b03 + a21 * b13 + a22 * b23 + a23 * b33; + dst.m[12] = a30 * b00 + a31 * b10 + a32 * b20 + a33 * b30; + dst.m[13] = a30 * b01 + a31 * b11 + a32 * b21 + a33 * b31; + dst.m[14] = a30 * b02 + a31 * b12 + a32 * b22 + a33 * b32; + dst.m[15] = a30 * b03 + a31 * b13 + a32 * b23 + a33 * b33; +} + +inline void transpose(Mat4 & dst, const Mat4 & src) { + Mat4 t; + const float * m = src.m; + t.m[0] = m[0]; t.m[1] = m[4]; t.m[2] = m[8]; t.m[3] = m[12]; + t.m[4] = m[1]; t.m[5] = m[5]; t.m[6] = m[9]; t.m[7] = m[13]; + t.m[8] = m[2]; t.m[9] = m[6]; t.m[10] = m[10]; t.m[11] = m[14]; + t.m[12] = m[3]; t.m[13] = m[7]; t.m[14] = m[11]; t.m[15] = m[15]; + dst = t; +} + +// tdl.fast.inverse4 +inline void inverse(Mat4 & dst, const Mat4 & src) { + const float * m = src.m; + const float m00 = m[0], m01 = m[1], m02 = m[2], m03 = m[3]; + const float m10 = m[4], m11 = m[5], m12 = m[6], m13 = m[7]; + const float m20 = m[8], m21 = m[9], m22 = m[10], m23 = m[11]; + const float m30 = m[12], m31 = m[13], m32 = m[14], m33 = m[15]; + const float tmp0 = m22 * m33, tmp1 = m32 * m23, tmp2 = m12 * m33, tmp3 = m32 * m13; + const float tmp4 = m12 * m23, tmp5 = m22 * m13, tmp6 = m02 * m33, tmp7 = m32 * m03; + const float tmp8 = m02 * m23, tmp9 = m22 * m03, tmp10 = m02 * m13, tmp11 = m12 * m03; + const float tmp12 = m20 * m31, tmp13 = m30 * m21, tmp14 = m10 * m31, tmp15 = m30 * m11; + const float tmp16 = m10 * m21, tmp17 = m20 * m11, tmp18 = m00 * m31, tmp19 = m30 * m01; + const float tmp20 = m00 * m21, tmp21 = m20 * m01, tmp22 = m00 * m11, tmp23 = m10 * m01; + const float t0 = (tmp0 * m11 + tmp3 * m21 + tmp4 * m31) - (tmp1 * m11 + tmp2 * m21 + tmp5 * m31); + const float t1 = (tmp1 * m01 + tmp6 * m21 + tmp9 * m31) - (tmp0 * m01 + tmp7 * m21 + tmp8 * m31); + const float t2 = (tmp2 * m01 + tmp7 * m11 + tmp10 * m31) - (tmp3 * m01 + tmp6 * m11 + tmp11 * m31); + const float t3 = (tmp5 * m01 + tmp8 * m11 + tmp11 * m21) - (tmp4 * m01 + tmp9 * m11 + tmp10 * m21); + const float d = 1.f / (m00 * t0 + m10 * t1 + m20 * t2 + m30 * t3); + dst.m[0] = d * t0; + dst.m[1] = d * t1; + dst.m[2] = d * t2; + dst.m[3] = d * t3; + dst.m[4] = d * ((tmp1 * m10 + tmp2 * m20 + tmp5 * m30) - (tmp0 * m10 + tmp3 * m20 + tmp4 * m30)); + dst.m[5] = d * ((tmp0 * m00 + tmp7 * m20 + tmp8 * m30) - (tmp1 * m00 + tmp6 * m20 + tmp9 * m30)); + dst.m[6] = d * ((tmp3 * m00 + tmp6 * m10 + tmp11 * m30) - (tmp2 * m00 + tmp7 * m10 + tmp10 * m30)); + dst.m[7] = d * ((tmp4 * m00 + tmp9 * m10 + tmp10 * m20) - (tmp5 * m00 + tmp8 * m10 + tmp11 * m20)); + dst.m[8] = d * ((tmp12 * m13 + tmp15 * m23 + tmp16 * m33) - (tmp13 * m13 + tmp14 * m23 + tmp17 * m33)); + dst.m[9] = d * ((tmp13 * m03 + tmp18 * m23 + tmp21 * m33) - (tmp12 * m03 + tmp19 * m23 + tmp20 * m33)); + dst.m[10] = d * ((tmp14 * m03 + tmp19 * m13 + tmp22 * m33) - (tmp15 * m03 + tmp18 * m13 + tmp23 * m33)); + dst.m[11] = d * ((tmp17 * m03 + tmp20 * m13 + tmp23 * m23) - (tmp16 * m03 + tmp21 * m13 + tmp22 * m23)); + dst.m[12] = d * ((tmp14 * m22 + tmp17 * m32 + tmp13 * m12) - (tmp16 * m32 + tmp12 * m12 + tmp15 * m22)); + dst.m[13] = d * ((tmp20 * m32 + tmp12 * m02 + tmp19 * m22) - (tmp18 * m22 + tmp21 * m32 + tmp13 * m02)); + dst.m[14] = d * ((tmp18 * m12 + tmp23 * m32 + tmp15 * m02) - (tmp22 * m32 + tmp14 * m02 + tmp19 * m12)); + dst.m[15] = d * ((tmp22 * m22 + tmp16 * m02 + tmp21 * m12) - (tmp20 * m12 + tmp23 * m22 + tmp17 * m02)); +} + +inline float dot3(const float * a, const float * b) { + return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; +} + +inline void cross3(float * d, const float * a, const float * b) { + d[0] = a[1] * b[2] - a[2] * b[1]; + d[1] = a[2] * b[0] - a[0] * b[2]; + d[2] = a[0] * b[1] - a[1] * b[0]; +} + +inline void normalize3(float * d, const float * a) { + float n = std::sqrt(dot3(a, a)); + if (n > 0.00001f) { + d[0] = a[0] / n; + d[1] = a[1] / n; + d[2] = a[2] / n; + } else { + d[0] = d[1] = d[2] = 0.f; + } +} + +inline void cameraLookAt(Mat4 & dst, const float * eye, const float * target, const float * up) { + float t0[3] = {eye[0] - target[0], eye[1] - target[1], eye[2] - target[2]}; + float vz[3], vx[3], vy[3], t1[3]; + normalize3(vz, t0); + cross3(t1, up, vz); + normalize3(vx, t1); + cross3(vy, vz, vx); + dst.m[0] = vx[0]; dst.m[1] = vx[1]; dst.m[2] = vx[2]; dst.m[3] = 0.f; + dst.m[4] = vy[0]; dst.m[5] = vy[1]; dst.m[6] = vy[2]; dst.m[7] = 0.f; + dst.m[8] = vz[0]; dst.m[9] = vz[1]; dst.m[10] = vz[2]; dst.m[11] = 0.f; + dst.m[12] = eye[0]; dst.m[13] = eye[1]; dst.m[14] = eye[2]; dst.m[15] = 1.f; +} + +inline void frustum(Mat4 & dst, float left, float right, float bottom, float top, float near, float far) { + const float dx = right - left; + const float dy = top - bottom; + const float dz = near - far; + std::memset(dst.m, 0, sizeof(dst.m)); + dst.m[0] = 2.f * near / dx; + dst.m[5] = 2.f * near / dy; + dst.m[8] = (left + right) / dx; + dst.m[9] = (top + bottom) / dy; + dst.m[10] = far / dz; + dst.m[11] = -1.f; + dst.m[14] = near * far / dz; +} + +inline void translation(Mat4 & dst, float x, float y, float z) { + identity(dst); + dst.m[12] = x; + dst.m[13] = y; + dst.m[14] = z; +} + +inline void scaling(Mat4 & dst, float x, float y, float z) { + std::memset(dst.m, 0, sizeof(dst.m)); + dst.m[0] = x; + dst.m[5] = y; + dst.m[10] = z; + dst.m[15] = 1.f; +} + +inline void rotationY(Mat4 & dst, float angle) { + const float c = std::cos(angle); + const float s = std::sin(angle); + std::memset(dst.m, 0, sizeof(dst.m)); + dst.m[0] = c; + dst.m[2] = -s; + dst.m[5] = 1.f; + dst.m[8] = s; + dst.m[10] = c; + dst.m[15] = 1.f; +} + +inline void rotationZ(Mat4 & dst, float angle) { + const float c = std::cos(angle); + const float s = std::sin(angle); + std::memset(dst.m, 0, sizeof(dst.m)); + dst.m[0] = c; + dst.m[1] = s; + dst.m[4] = -s; + dst.m[5] = c; + dst.m[10] = 1.f; + dst.m[15] = 1.f; +} + +inline void axis(float * d, const Mat4 & m, int a) { + d[0] = m.m[a * 4 + 0]; + d[1] = m.m[a * 4 + 1]; + d[2] = m.m[a * 4 + 2]; +} + +} // namespace aq diff --git a/examples/gl/aquariumExample/src/main.cpp b/examples/gl/aquariumExample/src/main.cpp new file mode 100644 index 00000000000..d4a9591bbce --- /dev/null +++ b/examples/gl/aquariumExample/src/main.cpp @@ -0,0 +1,14 @@ +#include "ofMain.h" +#include "ofApp.h" + +int main(int argc, char ** argv) { + aquariumConfigure(argc, argv); + + ofGLWindowSettings settings; + settings.setGLVersion(3, 2); + settings.setSize(1024, 1024); + settings.windowMode = OF_WINDOW; + ofCreateWindow(settings); + ofRunApp(std::make_shared()); + ofRunMainLoop(); +} diff --git a/examples/gl/aquariumExample/src/ofApp.cpp b/examples/gl/aquariumExample/src/ofApp.cpp new file mode 100644 index 00000000000..bb0daf06e88 --- /dev/null +++ b/examples/gl/aquariumExample/src/ofApp.cpp @@ -0,0 +1,1160 @@ +#include "ofApp.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +int gFishPreset = 2; +int gView = 0; +bool gPretty = false; +bool gNoVsync = false; +int gShotFrame = 0; +int gFishOverride = -1; +std::chrono::steady_clock::time_point gTimedStart; +bool gTimed = false; + +void reportShot(ofApp & app) { + const double sec = gTimed + ? std::chrono::duration(std::chrono::steady_clock::now() - gTimedStart).count() + : 0.0; + const double fps = sec > 0.0 ? static_cast(app.frameCount) / sec : 0.0; + std::ostringstream line; + line << std::fixed + << "SHOT /tmp/aquarium-shot.png fish=" << app.fishTotal + << " view=" << ofApp::kViews[app.viewIndex].name + << " frames=" << app.frameCount + << " sec=" << std::setprecision(4) << sec + << " avg_fps=" << std::setprecision(3) << fps + << " px=" << ofGetWidth() << "x" << ofGetHeight(); + std::cout << line.str() << std::endl; + ofLogNotice("aquarium") << line.str(); +#if defined(TARGET_OF_IOS) + if (const char * home = std::getenv("HOME")) { + std::ofstream out(std::string(home) + "/Documents/aquarium-result.txt", std::ios::trunc); + out << line.str() << std::endl; + } + std::cout.flush(); + std::_Exit(0); +#else + ofExit(0); +#endif +} + +float unitRand() { + return static_cast(std::rand()) / static_cast(RAND_MAX); +} + +bool raySphere(const float * p1, const float * p2, const float * center, float radius, float * hit) { + const float eps = 0.0001f; + float dp[3] = {p2[0] - p1[0], p2[1] - p1[1], p2[2] - p1[2]}; + const float a = aq::dot3(dp, dp); + const float b = 2.f * (dp[0] * (p1[0] - center[0]) + dp[1] * (p1[1] - center[1]) + dp[2] * (p1[2] - center[2])); + const float c = aq::dot3(center, center) + aq::dot3(p1, p1) + - 2.f * (center[0] * p1[0] + center[1] * p1[1] + center[2] * p1[2]) + - radius * radius; + const float disc = b * b - 4.f * a * c; + if (std::fabs(a) < eps || disc < 0.f) { + return false; + } + const float sq = std::sqrt(disc); + const float mu1 = (-b + sq) / (2.f * a); + const float mu2 = (-b - sq) / (2.f * a); + const float m = std::max(mu1, mu2); + hit[0] = p1[0] + m * dp[0]; + hit[1] = p1[1] + m * dp[1]; + hit[2] = p1[2] + m * dp[2]; + return true; +} + +bool refractVec(const float * i, const float * n, float eta, float * out) { + const float dotNI = aq::dot3(n, i); + const float k = 1.f - eta * eta * (1.f - dotNI * dotNI); + if (k < 0.f) { + return false; + } + const float scale = eta * dotNI + std::sqrt(k); + out[0] = eta * i[0] - scale * n[0]; + out[1] = eta * i[1] - scale * n[1]; + out[2] = eta * i[2] - scale * n[2]; + return true; +} + +} // namespace + +const int ofApp::kFishPresets[10] = {1, 100, 500, 1000, 5000, 10000, 15000, 20000, 25000, 30000}; + +const ofApp::View ofApp::kViews[6] = { + {"Inside 1", 63.3f, 91.6f, 7.5f, 13.2f, 0.0258f, 82.699f, {0.218f, 0.502f, 0.706f}, 16.5f, 1.5f, 0.738f, {0.338f, 0.81f, 1.f}}, + {"Outside 1", 17.1f, 69.2f, 59.1f, 124.4f, 0.0258f, 56.923f, {0.218f, 0.246f, 0.394f}, 27.1f, 1.46f, 0.53f, {0.382f, 0.602f, 1.f}}, + {"Inside original", 0.f, 88.f, 38.f, 69.f, 0.0258f, 64.f, {0.218f, 0.246f, 0.394f}, 16.5f, 1.5f, 0.738f, {0.338f, 0.81f, 1.f}}, + {"Outside original", 72.f, 73.f, 3.9f, 120.f, 0.0258f, 74.f, {0.218f, 0.246f, 0.394f}, 27.1f, 1.46f, 0.53f, {0.382f, 0.602f, 1.f}}, + {"Center", 24.f, 73.f, 24.f, 0.f, 0.06f, 60.f, {0.22f, 0.25f, 0.39f}, 14.5f, 1.3f, 0.53f, {0.54f, 0.86f, 1.f}}, + {"Outside LG", 20.f, 127.f, 39.9f, 124.f, 0.06f, 24.f, {0.22f, 0.25f, 0.39f}, 27.1f, 1.2f, 0.53f, {0.382f, 0.602f, 1.f}}, +}; + +void aquariumConfigure(int argc, char ** argv) { + for (int i = 1; i < argc; ++i) { + const std::string arg = argv[i]; + if (arg == "--pretty") { + gPretty = true; + } else if (arg == "--novsync") { + gNoVsync = true; + } else if (arg == "--view" && i + 1 < argc) { + gView = std::atoi(argv[++i]); + } else if (arg == "--fish" && i + 1 < argc) { + gFishOverride = std::atoi(argv[++i]); + } else if (arg == "--shot" && i + 1 < argc) { + gShotFrame = std::atoi(argv[++i]); + } + } +} + +bool ofApp::loadShader(ofShader & shader, const std::string & vert, const std::string & frag) { + auto sourceFor = [](const std::string & path, bool fragment) { + std::string source = ofBufferFromFile(path).getText(); +#if defined(TARGET_OPENGLES) + const std::string from = "#version 150"; + const auto pos = source.find(from); + if (pos != std::string::npos) { + source.replace(pos, from.size(), "#version 300 es"); + } + if (fragment) { + const auto line = source.find('\n'); + if (line != std::string::npos) { + source.insert(line + 1, + "precision highp float;\nprecision highp int;\n" + "precision highp sampler2D;\nprecision highp samplerCube;\n"); + } + } +#endif + return source; + }; + const std::string vertSource = sourceFor(vert, false); + const std::string fragSource = sourceFor(frag, true); + if (vertSource.empty() || !shader.setupShaderFromSource(GL_VERTEX_SHADER, vertSource)) { + ofLogError("aquarium") << "vert " << vert; + return false; + } + if (fragSource.empty() || !shader.setupShaderFromSource(GL_FRAGMENT_SHADER, fragSource)) { + ofLogError("aquarium") << "frag " << frag; + return false; + } + if (ofIsGLProgrammableRenderer()) { + shader.bindDefaults(); + shader.bindAttribute(4, "tangent"); + shader.bindAttribute(5, "binormal"); + } + return shader.linkProgram(); +} + +const ofTexture & ofApp::textureNamed(const std::string & name) { + auto it = images.find(name); + if (it == images.end() || !it->second.isAllocated()) { + return white; + } + return it->second.getTexture(); +} + +bool ofApp::loadAqm(const std::string & path, std::vector & models) { + std::ifstream in(path, std::ios::binary); + if (!in) { + ofLogError("aquarium") << "missing " << path; + return false; + } + std::vector bytes((std::istreambuf_iterator(in)), std::istreambuf_iterator()); + if (bytes.size() < 8 || std::memcmp(bytes.data(), "AQM1", 4) != 0) { + ofLogError("aquarium") << "bad mesh " << path; + return false; + } + const unsigned char * p = bytes.data() + 4; + const unsigned char * end = bytes.data() + bytes.size(); + auto need = [&](size_t n) { + if (p + n > end) { + p = end; + return false; + } + return true; + }; + auto u32 = [&]() { + std::uint32_t v = 0; + if (need(4)) { + std::memcpy(&v, p, 4); + p += 4; + } + return v; + }; + auto str = [&]() { + const std::uint32_t n = u32(); + std::string s; + if (need(n)) { + s.assign(reinterpret_cast(p), n); + p += n; + } + return s; + }; + const std::uint32_t nmodels = u32(); + const int hasNormal = 1, hasUv = 2, hasTan = 4, hasBin = 8; + for (std::uint32_t m = 0; m < nmodels; ++m) { + Model model; + const std::uint32_t ntex = u32(); + for (std::uint32_t t = 0; t < ntex; ++t) { + const std::string key = str(); + const std::string file = str(); + if (key == "diffuse") { + model.diffuse = file; + } else if (key == "normalMap") { + model.normalMap = file; + } else if (key == "reflectionMap") { + model.reflectionMap = file; + } + } + const std::uint32_t nverts = u32(); + const std::uint32_t nidx = u32(); + const std::uint32_t flags = u32(); + if (!need(sizeof(float) * nverts * 3)) { + return false; + } + std::vector pos(nverts * 3); + std::memcpy(pos.data(), p, pos.size() * sizeof(float)); + p += pos.size() * sizeof(float); + float maxZ = -1e30f; + for (std::uint32_t v = 0; v < nverts; ++v) { + maxZ = std::max(maxZ, pos[v * 3 + 2]); + } + std::vector nor, uv, tan, bin; + auto takeF = [&](std::vector & dst, int comps) { + dst.resize(nverts * comps); + if (!need(dst.size() * sizeof(float))) { + return; + } + std::memcpy(dst.data(), p, dst.size() * sizeof(float)); + p += dst.size() * sizeof(float); + }; + if (flags & hasNormal) { + takeF(nor, 3); + } + if (flags & hasUv) { + takeF(uv, 2); + } + if (flags & hasTan) { + takeF(tan, 3); + } + if (flags & hasBin) { + takeF(bin, 3); + } + if (nor.empty()) { + nor.assign(nverts * 3, 0.f); + for (std::uint32_t v = 0; v < nverts; ++v) { + nor[v * 3 + 1] = 1.f; + } + } + if (uv.empty()) { + uv.assign(nverts * 2, 0.f); + } + if (tan.empty() || bin.empty()) { + tan.resize(nverts * 3); + bin.resize(nverts * 3); + for (std::uint32_t v = 0; v < nverts; ++v) { + float nrm[3] = {nor[v * 3], nor[v * 3 + 1], nor[v * 3 + 2]}; + float helper[3] = {0.f, 1.f, 0.f}; + if (std::fabs(nrm[1]) > 0.9f) { + helper[0] = 1.f; + helper[1] = 0.f; + } + float t[3], b[3]; + aq::cross3(t, helper, nrm); + aq::normalize3(t, t); + aq::cross3(b, nrm, t); + aq::normalize3(b, b); + tan[v * 3] = t[0]; + tan[v * 3 + 1] = t[1]; + tan[v * 3 + 2] = t[2]; + bin[v * 3] = b[0]; + bin[v * 3 + 1] = b[1]; + bin[v * 3 + 2] = b[2]; + } + } + std::vector idx(nidx); + if (!need(sizeof(std::uint32_t) * nidx)) { + return false; + } + for (std::uint32_t i = 0; i < nidx; ++i) { + std::uint32_t v = 0; + std::memcpy(&v, p, 4); + p += 4; + idx[i] = static_cast(v); + } +#if OF_USE_DAWN + model.mesh.pos = std::move(pos); + model.mesh.nor = std::move(nor); + model.mesh.uv = std::move(uv); + model.mesh.tan = std::move(tan); + model.mesh.bin = std::move(bin); + model.mesh.indices.resize(nidx); + for (std::uint32_t i = 0; i < nidx; ++i) { + model.mesh.indices[i] = static_cast(idx[i]); + } +#else + model.mesh.vbo.setVertexData(pos.data(), 3, static_cast(nverts), GL_STATIC_DRAW); + model.mesh.vbo.setNormalData(nor.data(), static_cast(nverts), GL_STATIC_DRAW); + model.mesh.vbo.setTexCoordData(uv.data(), static_cast(nverts), GL_STATIC_DRAW); + model.mesh.vbo.setAttributeData(4, tan.data(), 3, static_cast(nverts), GL_STATIC_DRAW); + model.mesh.vbo.setAttributeData(5, bin.data(), 3, static_cast(nverts), GL_STATIC_DRAW); + model.mesh.vbo.setIndexData(idx.data(), static_cast(nidx), GL_STATIC_DRAW); +#endif + model.mesh.indexCount = static_cast(nidx); + model.mesh.maxZ = maxZ; + models.push_back(std::move(model)); + } + return p <= end; +} + +void ofApp::applyView(int index) { + viewIndex = (index % 6 + 6) % 6; + const View & v = kViews[viewIndex]; + targetHeight = v.targetHeight; + targetRadius = v.targetRadius; + eyeHeight = v.eyeHeight; + eyeRadius = v.eyeRadius; + eyeSpeed = v.eyeSpeed; + fov = v.fov; + std::memcpy(ambient, v.ambient, sizeof(ambient)); + fogPower = v.fogPower; + fogMult = v.fogMult; + fogOffset = v.fogOffset; + std::memcpy(fogRgb, v.fogRgb, sizeof(fogRgb)); +} + +void ofApp::distributeFish(int totalFish) { + fishTotal = std::max(0, totalFish); + int numLeft = fishTotal; + const char * types[] = {"Big", "Medium", "Small"}; + for (auto & kind : fish) { + kind.num = 0; + } + for (const char * type : types) { + for (auto & kind : fish) { + if (std::strncmp(kind.name, type, std::strlen(type)) != 0) { + continue; + } + int numType = numLeft; + if (std::strcmp(type, "Big") == 0) { + numType = std::min(numLeft, fishTotal < 100 ? 1 : 2); + } else if (std::strcmp(type, "Medium") == 0) { + const int cap = fishTotal < 1000 ? (fishTotal / 10) : (fishTotal < 10000 ? 80 : 160); + numType = std::min(numLeft, cap); + } + numLeft -= numType; + kind.num = numType; + kind.inst.assign(numType, FishKind::Inst{}); + } + } +} + +void ofApp::resetPseudo() { + randomSeed = 0; +} + +float ofApp::pseudo() { + randomSeed = 134775813u * randomSeed + 1u; + return static_cast(static_cast(randomSeed) / 4294967296.0); +} + +void ofApp::initRay(Ray & ray) { + const float r = unitRand(); + ray.duration = 1.f + unitRand() * 1.f; + ray.timer = ray.duration; + ray.rot = r * 1.f; + ray.x = (r - 0.5f) * 20.f; +} + +void ofApp::spawnBubbles() { + const float radius = unitRand() * 50.f; + const float angle = unitRand() * static_cast(PI) * 2.f; + const float ox = std::sin(angle) * radius; + const float oz = std::cos(angle) * radius; + for (int i = 0; i < 100; ++i) { + Bubble b; + b.x = ox + (unitRand() * 2.f - 1.f) * 0.1f; + b.y = -2.f + (unitRand() * 2.f - 1.f) * 2.f; + b.z = oz + (unitRand() * 2.f - 1.f) * 0.1f; + b.vx = (unitRand() * 2.f - 1.f) * 0.05f; + b.vy = 0.f; + b.vz = (unitRand() * 2.f - 1.f) * 0.05f; + b.ay = 0.05f + unitRand() * 0.02f; + b.size0 = 0.01f + unitRand() * 0.01f; + b.size1 = 0.4f + unitRand() * 0.2f; + b.age = 0; + b.life = 40.f; + bubbles.push_back(b); + } +} + +void ofApp::setup() { +#if defined(TARGET_OF_IOS) + // ofxiOSAppDelegate points the data root at the bundle root. This example + // keeps scene.json under Documents/data, which the simulator can read. + if (const char * home = std::getenv("HOME")) { + ofSetDataPathRoot(std::string(home) + "/Documents/data/"); + } +#endif + ofDisableArbTex(); + ofSetWindowTitle("Aquarium"); + ofSetVerticalSync(!gNoVsync); + ofSetFrameRate(0); + ofBackground(0, 204, 255); + +#if !OF_USE_DAWN + ofPixels px; + px.allocate(1, 1, OF_PIXELS_RGBA); + px.setColor(ofColor(255, 255, 255, 255)); + white.allocate(px); + white.loadData(px); +#endif + + if (!ofFile::doesFileExist("scene.json")) { + ofLogError("aquarium") << "missing bin/data/scene.json — run scripts/fetch_assets.py"; + return; + } + const ofJson doc = ofLoadJson("scene.json"); + for (const auto & item : doc["scenes"]) { + Scene scene; + scene.name = item.value("name", ""); + scene.group = item.value("group", "base"); + scene.program = item.value("program", "auto"); + scene.fog = item.value("fog", true); + scene.blend = item.value("blend", false); + if (!loadAqm(ofToDataPath(item.value("file", "")), scene.models)) { + ofLogError("aquarium") << "failed " << scene.name; + continue; + } + sceneByName[scene.name] = static_cast(scenes.size()); + scenes.push_back(std::move(scene)); + } + for (const auto & item : doc["objects"]) { + const std::string name = item.value("name", ""); + auto found = sceneByName.find(name); + if (found == sceneByName.end()) { + continue; + } + Object obj; + obj.scene = found->second; + const auto & w = item["worldMatrix"]; + for (int i = 0; i < 16 && i < static_cast(w.size()); ++i) { + obj.world.m[i] = w[i].get(); + } + objects.push_back(obj); + } + + ofDirectory assets("assets"); + assets.listDir(); + for (const ofFile & image : assets.getFiles()) { + const std::string name = image.getFileName(); + const std::string ext = ofToLower(image.getExtension()); + if (ext != "png" && ext != "jpg" && ext != "jpeg") { + continue; + } + ofImage loaded; +#if OF_USE_DAWN + loaded.setUseTexture(false); +#endif + if (!loaded.load(image.path())) { + ofLogWarning("aquarium") << "texture " << name; + continue; + } +#if !OF_USE_DAWN + ofTexture & tex = loaded.getTexture(); + tex.generateMipmap(); + tex.setTextureWrap(GL_REPEAT, GL_REPEAT); + tex.setTextureMinMagFilter(GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR); +#endif + images.emplace(name, std::move(loaded)); + } + +#if !OF_USE_DAWN + const char * faces[6] = { + "GlobeOuter_EM_positive_x.jpg", + "GlobeOuter_EM_negative_x.jpg", + "GlobeOuter_EM_positive_y.jpg", + "GlobeOuter_EM_negative_y.jpg", + "GlobeOuter_EM_positive_z.jpg", + "GlobeOuter_EM_negative_z.jpg" + }; + glGenTextures(1, &cube); + glBindTexture(GL_TEXTURE_CUBE_MAP, cube); + for (int i = 0; i < 6; ++i) { + ofImage face; + face.setUseTexture(false); + if (!face.load(std::string("assets/") + faces[i])) { + ofLogError("aquarium") << "sky face " << faces[i]; + continue; + } + const ofPixels & pix = face.getPixels(); + const GLenum format = pix.getNumChannels() == 4 ? GL_RGBA : GL_RGB; + glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, format, pix.getWidth(), pix.getHeight(), 0, format, GL_UNSIGNED_BYTE, pix.getData()); + } + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); + glBindTexture(GL_TEXTURE_CUBE_MAP, 0); +#endif + +#if !OF_USE_DAWN + auto quad = [](std::vector & pos, std::vector & uv, float angle, bool ray) { + const float c = std::cos(angle); + const float s = std::sin(angle); + float corner[4][3]; + float coord[4][2] = {{0, 0}, {1, 0}, {1, 1}, {0, 1}}; + if (ray) { + corner[0][0] = -0.5f; corner[0][1] = 0; corner[0][2] = 0; + corner[1][0] = 0.5f; corner[1][1] = 0; corner[1][2] = 0; + corner[2][0] = 0.5f; corner[2][1] = 1; corner[2][2] = 0; + corner[3][0] = -0.5f; corner[3][1] = 1; corner[3][2] = 0; + } else { + corner[0][0] = -0.5f; corner[0][1] = 0; corner[0][2] = 0; + corner[1][0] = 0.5f; corner[1][1] = 0; corner[1][2] = 0; + corner[2][0] = 0.5f; corner[2][1] = 0; corner[2][2] = 1; + corner[3][0] = -0.5f; corner[3][1] = 0; corner[3][2] = 1; + } + const int order[6] = {0, 1, 2, 0, 2, 3}; + for (int index : order) { + const float x = corner[index][0]; + const float y = corner[index][1]; + const float z = corner[index][2]; + pos.push_back(c * x - s * y); + pos.push_back(s * x + c * y); + pos.push_back(z); + uv.push_back(coord[index][0]); + uv.push_back(coord[index][1]); + } + }; + std::vector lp, lu, rp, ru; + const float turn = 120.f * static_cast(PI) / 180.f; + quad(lp, lu, 0, false); + quad(lp, lu, turn, false); + quad(lp, lu, -turn, false); + quad(rp, ru, 0, true); + laserVbo.setVertexData(lp.data(), 3, static_cast(lp.size() / 3), GL_STATIC_DRAW); + laserVbo.setTexCoordData(lu.data(), static_cast(lu.size() / 2), GL_STATIC_DRAW); + laserCount = static_cast(lp.size() / 3); + rayVbo.setVertexData(rp.data(), 3, static_cast(rp.size() / 3), GL_STATIC_DRAW); + rayVbo.setTexCoordData(ru.data(), static_cast(ru.size() / 2), GL_STATIC_DRAW); + rayCount = static_cast(rp.size() / 3); + + const bool shadersOk = + loadShader(litShader, "shaders_gl3/static.vert", "shaders_gl3/lit.frag") && + loadShader(innerShader, "shaders_gl3/static.vert", "shaders_gl3/inner.frag") && + loadShader(outerShader, "shaders_gl3/static.vert", "shaders_gl3/outer.frag") && + loadShader(seaweedShader, "shaders_gl3/seaweed.vert", "shaders_gl3/seaweed.frag") && + loadShader(fishShader, "shaders_gl3/fish.vert", "shaders_gl3/lit.frag") && + loadShader(texShader, "shaders_gl3/tex.vert", "shaders_gl3/tex.frag") && + loadShader(bubbleShader, "shaders_gl3/bubble.vert", "shaders_gl3/bubble.frag"); + if (!shadersOk) { + ofLogError("aquarium") << "shader link failed"; + return; + } +#endif + + auto add = [&](const char * name, float speed, float speedRange, float radius, float radiusRange, float tail, float heightOffset, float heightRange, float length, float wave, float bend) { + FishKind kind; + kind.name = name; + kind.speed = speed; + kind.speedRange = speedRange; + kind.radius = radius; + kind.radiusRange = radiusRange; + kind.tailSpeed = tail; + kind.heightOffset = heightOffset; + kind.heightRange = heightRange; + kind.length = length; + kind.wave = wave; + kind.bend = bend; + fish.push_back(kind); + }; + add("SmallFishA", 1.f, 1.5f, 30.f, 25.f, 10.f, 0.f, 16.f, 10.f, 1.f, 2.f); + add("MediumFishA", 1.f, 2.f, 10.f, 20.f, 1.f, 0.f, 16.f, 10.f, -2.f, 2.f); + add("MediumFishB", 0.5f, 4.f, 10.f, 20.f, 3.f, -8.f, 5.f, 10.f, -2.f, 2.f); + add("BigFishA", 0.5f, 0.5f, 50.f, 3.f, 1.5f, 0.f, 16.f, 10.f, -1.f, 0.5f); + add("BigFishB", 0.5f, 0.5f, 45.f, 3.f, 1.f, 0.f, 16.f, 10.f, -0.7f, 0.3f); + fish[3].lasers = true; + fish[3].laserRot = 0.04f; + fish[3].laserOff[1] = 0.1f; + fish[3].laserOff[2] = 9.f; + fish[3].laserScale[0] = fish[3].laserScale[1] = 0.3f; + fish[3].laserScale[2] = 1000.f; + fish[4].lasers = true; + fish[4].laserRot = 0.04f; + fish[4].laserOff[1] = -0.3f; + fish[4].laserOff[2] = 9.f; + fish[4].laserScale[0] = fish[4].laserScale[1] = 0.3f; + fish[4].laserScale[2] = 1000.f; + for (auto & kind : fish) { + auto found = sceneByName.find(kind.name); + if (found == sceneByName.end() || scenes[found->second].models.empty()) { + ofLogError("aquarium") << "missing fish " << kind.name; + continue; + } + kind.scene = found->second; + kind.maxZ = scenes[kind.scene].models[0].mesh.maxZ; + } + + rays.resize(5); + for (auto & ray : rays) { + initRay(ray); + } + applyView(gView); + if (gPretty) { + optNormal = true; + optReflect = true; + prettyDone = true; + } + if (gFishOverride >= 0) { + distributeFish(gFishOverride); + } else { + fishSetting = std::max(0, std::min(9, gFishPreset)); + distributeFish(kFishPresets[fishSetting]); + } +#if OF_USE_DAWN + if (!aquariumDawnPrepare(*this)) { + ofLogError("aquarium") << "dawn setup failed"; + return; + } +#endif + ready = true; +#if OF_USE_DAWN + std::cout << "aquarium dawn=WebGPU retained-buffers one-draw-per-fish" << std::endl; +#else + const char * glVersion = reinterpret_cast(glGetString(GL_VERSION)); + const char * glRenderer = reinterpret_cast(glGetString(GL_RENDERER)); + std::cout << "aquarium gl=" << (glVersion ? glVersion : "?") + << " renderer=" << (glRenderer ? glRenderer : "?") << std::endl; + ofLogNotice("aquarium") << "gl=" << (glVersion ? glVersion : "?") + << " renderer=" << (glRenderer ? glRenderer : "?"); +#endif + gTimedStart = std::chrono::steady_clock::now(); + gTimed = true; + std::cout << "aquarium scenes=" << scenes.size() + << " objects=" << objects.size() + << " fish=" << fishTotal + << " view=" << kViews[viewIndex].name + << " px=" << ofGetWidth() << "x" << ofGetHeight() + << std::endl; + ofLogNotice("aquarium") << "scenes=" << scenes.size() + << " objects=" << objects.size() + << " fish=" << fishTotal + << " view=" << kViews[viewIndex].name + << " px=" << ofGetWidth() << "x" << ofGetHeight(); +} + +void ofApp::update() { + if (!ready) { + return; + } + const float now = ofGetElapsedTimef(); + const float dt = then == 0.f ? 0.f : now - then; + then = now; + clock += dt * globalsSpeed; + eyeClock += dt * eyeSpeed; + if (optRays) { + for (auto & ray : rays) { + ray.timer -= dt * globalsSpeed; + if (ray.timer < 0.f) { + initRay(ray); + } + } + } + if (optBubbles) { + bubbleTimer -= dt * globalsSpeed; + if (bubbleTimer < 0.f) { + bubbleTimer = 2.f + unitRand() * 8.f; + spawnBubbles(); + } + for (auto & bubble : bubbles) { + bubble.age += dt * globalsSpeed; + } + bubbles.erase(std::remove_if(bubbles.begin(), bubbles.end(), [](const Bubble & b) { + return b.age > b.life; + }), bubbles.end()); + } + if (!prettyDone && ofGetFrameRate() > 40.f) { + prettyDone = true; + optNormal = true; + optReflect = true; + std::cout << "aquarium pretty on (normal maps + reflection)" << std::endl; + } +} + +void ofApp::setMat(const ofShader & shader, const char * name, const aq::Mat4 & m) { + glm::mat4 matrix; + std::memcpy(glm::value_ptr(matrix), m.m, sizeof(m.m)); + shader.setUniformMatrix4f(name, matrix); +} + +void ofApp::updateCamera() { + const float aspect = ofGetHeight() > 0 ? static_cast(ofGetWidth()) / static_cast(ofGetHeight()) : 1.f; + const float near = 1.f; + const float far = 25000.f; + const float top = std::tan(fov * static_cast(PI) / 180.f * 0.5f) * near; + const float bottom = -top; + const float left = aspect * bottom; + const float right = aspect * top; + aq::frustum(projection, left, right, bottom, top, near, far); + + eye[0] = std::sin(eyeClock) * eyeRadius; + eye[1] = eyeHeight; + eye[2] = std::cos(eyeClock) * eyeRadius; + float target[3] = { + std::sin(eyeClock + static_cast(PI)) * targetRadius, + targetHeight, + std::cos(eyeClock + static_cast(PI)) * targetRadius + }; + const float up[3] = {0.f, 1.f, 0.f}; + aq::cameraLookAt(viewInverse, eye, target, up); + aq::inverse(view, viewInverse); + aq::mul(viewProjection, view, projection); + + float xAxis[3], yAxis[3]; + aq::axis(xAxis, viewInverse, 0); + aq::axis(yAxis, viewInverse, 1); + light[0] = eye[0] + xAxis[0] * 20.f + yAxis[0] * 30.f; + light[1] = eye[1] + xAxis[1] * 20.f + yAxis[1] * 30.f; + light[2] = eye[2] + xAxis[2] * 20.f + yAxis[2] * 30.f; + + aq::Mat4 translationOnly; + aq::translation(translationOnly, view.m[12], view.m[13], view.m[14]); + aq::mul(viewProjectionNoRot, translationOnly, projection); +} + +void ofApp::setShared(const ofShader & shader) { + setMat(shader, "viewProjection", viewProjection); + setMat(shader, "viewInverse", viewInverse); + shader.setUniform3f("lightWorldPos", light[0], light[1], light[2]); + shader.setUniform4f("lightColor", 1.f, 1.f, 1.f, 1.f); + shader.setUniform4f("ambient", ambient[0], ambient[1], ambient[2], 1.f); + shader.setUniform4f("fogColor", fogRgb[0], fogRgb[1], fogRgb[2], 1.f); + shader.setUniform1f("fogPower", fogPower); + shader.setUniform1f("fogMult", fogMult); + shader.setUniform1f("fogOffset", fogOffset); +} + +void ofApp::bindModelTextures(const ofShader & shader, const Model & model) { + shader.setUniformTexture("diffuse", textureNamed(model.diffuse), 0); + shader.setUniformTexture("normalMap", textureNamed(model.normalMap), 1); + shader.setUniformTexture("reflectionMap", textureNamed(model.reflectionMap), 2); + shader.setUniformTexture("skybox", GL_TEXTURE_CUBE_MAP, static_cast(cube), 3); +} + +void ofApp::drawGroup(const std::string & group, float specularFactor) { + int index = 0; + for (const Object & obj : objects) { + const Scene & scene = scenes[obj.scene]; + if (scene.group != group) { + continue; + } + ofShader * shader = &litShader; + if (scene.program == "inner") { + shader = &innerShader; + } else if (scene.program == "outer") { + shader = &outerShader; + } else if (scene.program == "seaweed") { + shader = &seaweedShader; + } + if (scene.blend) { + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + } else { + glDisable(GL_BLEND); + } + shader->begin(); + setShared(*shader); + shader->setUniform1f("uFog", (optFog && scene.fog) ? 1.f : 0.f); + shader->setUniform1f("shininess", 50.f); + shader->setUniform1f("specularFactor", specularFactor); + shader->setUniform4f("specular", 1.f, 1.f, 1.f, 1.f); + shader->setUniform1f("time", clock + static_cast(index)); + if (scene.program == "inner") { + shader->setUniform1f("refractionFudge", refractionFudge); + shader->setUniform1f("eta", eta); + shader->setUniform1f("tankColorFudge", tankColorFudge); + } + aq::Mat4 inv; + aq::Mat4 invT; + aq::inverse(inv, obj.world); + aq::transpose(invT, inv); + setMat(*shader, "world", obj.world); + setMat(*shader, "worldInverseTranspose", invT); + for (const Model & model : scene.models) { + float useNormal = 0.f; + float useReflect = 0.f; + if (scene.program != "diffuse" && scene.program != "seaweed" && optNormal && !model.normalMap.empty()) { + useNormal = 1.f; + } + if ((scene.program == "auto") && optReflect && !model.reflectionMap.empty()) { + useReflect = 1.f; + } + shader->setUniform1f("uNormal", useNormal); + shader->setUniform1f("uReflect", useReflect); + shader->setUniform1f("uNormalBias", 0.f); + bindModelTextures(*shader, model); + if (model.mesh.indexCount > 0) { + model.mesh.vbo.drawElements(GL_TRIANGLES, model.mesh.indexCount); + } + } + shader->end(); + ++index; + } +} + +void ofApp::drawFish() { + resetPseudo(); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glEnable(GL_CULL_FACE); + fishShader.begin(); + setShared(fishShader); + fishShader.setUniform1f("uFog", optFog ? 1.f : 0.f); + fishShader.setUniform1f("shininess", 5.f); + fishShader.setUniform1f("specularFactor", 0.3f); + fishShader.setUniform4f("specular", 1.f, 1.f, 1.f, 1.f); + for (auto & kind : fish) { + if (kind.num <= 0 || kind.scene < 0) { + continue; + } + const Model & model = scenes[kind.scene].models[0]; + const float useNormal = (optNormal && !model.normalMap.empty()) ? 1.f : 0.f; + const float useReflect = (optReflect && !model.reflectionMap.empty()) ? 1.f : 0.f; + fishShader.setUniform1f("uNormal", useNormal); + fishShader.setUniform1f("uReflect", useReflect); + fishShader.setUniform1f("uNormalBias", 1.f); + fishShader.setUniform1f("fishLength", kind.length); + fishShader.setUniform1f("fishWaveLength", kind.wave); + fishShader.setUniform1f("fishBendAmount", kind.bend); + bindModelTextures(fishShader, model); + const float baseClock = clock * fishSpeed; + const float tail = kind.tailSpeed * fishTailSpeed; + const float height = fishHeight + kind.heightOffset; + const float heightRange = fishHeightRange * kind.heightRange; + for (int i = 0; i < kind.num; ++i) { + const float fishClock = baseClock + static_cast(i) * fishOffset; + const float speed = kind.speed + pseudo() * kind.speedRange; + const float scale = (1.f + pseudo()) ; + const float xRadius = kind.radius + pseudo() * kind.radiusRange; + const float yRadius = 2.f + pseudo() * heightRange; + const float zRadius = kind.radius + pseudo() * kind.radiusRange; + const float speedClock = fishClock * speed; + const float xClock = speedClock * fishXClock; + const float yClock = speedClock * fishYClock; + const float zClock = speedClock * fishZClock; + const float px = std::sin(xClock) * xRadius; + const float py = std::sin(yClock) * yRadius + height; + const float pz = std::cos(zClock) * zRadius; + const float nx = std::sin(xClock - 0.04f) * xRadius; + const float ny = std::sin(yClock - 0.01f) * yRadius + height; + const float nz = std::cos(zClock - 0.04f) * zRadius; + const float time = std::fmod((clock + static_cast(i)) * tail * speed, static_cast(PI) * 2.f); + fishShader.setUniform3f("worldPosition", px, py, pz); + fishShader.setUniform3f("nextPosition", nx, ny, nz); + fishShader.setUniform1f("scale", scale); + fishShader.setUniform1f("time", time); + model.mesh.vbo.drawElements(GL_TRIANGLES, model.mesh.indexCount); + if (optLasers && kind.lasers) { + FishKind::Inst & inst = kind.inst[static_cast(i)]; + inst.position[0] = px; + inst.position[1] = py; + inst.position[2] = pz; + inst.target[0] = nx; + inst.target[1] = ny; + inst.target[2] = nz; + inst.scale = scale; + inst.time = time; + inst.outside = false; + } + } + } + fishShader.end(); +} + +void ofApp::drawLasers(bool outside) { + glEnable(GL_BLEND); + glBlendFunc(GL_ONE, GL_ONE); + glDisable(GL_CULL_FACE); + glDepthMask(GL_FALSE); + texShader.begin(); + setMat(texShader, "viewProjection", viewProjection); + texShader.setUniformTexture("colorMap", textureNamed("beam.png"), 0); + const float c = 0.5f + static_cast(frameCount % 2) + 0.5f; + texShader.setUniform4f("colorMult", c, c * 0.1f, c * 0.1f, c); + const float up[3] = {0.f, 1.f, 0.f}; + const float center[3] = {0.f, 36.f, 0.f}; + for (auto & kind : fish) { + if (!kind.lasers) { + continue; + } + for (int i = 0; i < kind.num; ++i) { + FishKind::Inst & inst = kind.inst[static_cast(i)]; + aq::Mat4 world; + if (!outside) { + const float mult = kind.maxZ / kind.length; + const float s = std::sin(inst.time + mult * kind.wave); + const float offset = mult * mult * s * kind.bend; + float off[3] = {offset, kind.laserOff[1], kind.laserOff[2]}; + aq::Mat4 scale; + aq::Mat4 look; + aq::Mat4 spin; + aq::Mat4 shift; + aq::Mat4 spun; + aq::scaling(scale, 1.f, 1.f, 1.f); + aq::cameraLookAt(look, inst.position, inst.target, up); + aq::mul(world, scale, look); + aq::rotationY(spin, s * kind.laserRot); + aq::translation(shift, off[0], off[1], off[2]); + aq::mul(spun, spin, shift); + aq::Mat4 placed; + aq::mul(placed, spun, world); + world = placed; + float dir[3] = {world.m[8], world.m[9], world.m[10]}; + aq::normalize3(dir, dir); + float p1[3] = {world.m[12], world.m[13], world.m[14]}; + float p2[3] = {p1[0] + dir[0] * 1000.f, p1[1] + dir[1] * 1000.f, p1[2] + dir[2] * 1000.f}; + float hit[3]; + if (!raySphere(p1, p2, center, 74.f, hit)) { + continue; + } + const float dx = hit[0] - p1[0]; + const float dy = hit[1] - p1[1]; + const float dz = hit[2] - p1[2]; + const float len = std::sqrt(dx * dx + dy * dy + dz * dz); + aq::Mat4 beamScale; + aq::Mat4 beam; + aq::scaling(beamScale, kind.laserScale[0], kind.laserScale[1], len); + aq::mul(beam, beamScale, world); + setMat(texShader, "world", beam); + laserVbo.draw(GL_TRIANGLES, 0, laserCount); + float normal[3]; + aq::normalize3(normal, hit); + float neg[3] = {-dir[0], -dir[1], -dir[2]}; + float bent[3]; + if (refractVec(neg, normal, 1.2f, bent)) { + inst.outside = true; + inst.outPos[0] = hit[0]; + inst.outPos[1] = hit[1]; + inst.outPos[2] = hit[2]; + inst.outTarget[0] = hit[0] + bent[0]; + inst.outTarget[1] = hit[1] + bent[1]; + inst.outTarget[2] = hit[2] + bent[2]; + } + } else if (inst.outside) { + aq::Mat4 scale; + aq::Mat4 look; + aq::scaling(scale, 0.5f, 0.5f, 200.f); + aq::cameraLookAt(look, inst.outPos, inst.outTarget, up); + aq::mul(world, scale, look); + setMat(texShader, "world", world); + laserVbo.draw(GL_TRIANGLES, 0, laserCount); + } + } + } + texShader.end(); + glDisable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glEnable(GL_CULL_FACE); + glDepthMask(GL_TRUE); +} + +void ofApp::drawBubbles() { + if (bubbles.empty()) { + return; + } + float right[3]; + float upv[3]; + aq::axis(right, viewInverse, 0); + aq::axis(upv, viewInverse, 1); + std::vector pos; + std::vector uv; + std::vector col; + pos.reserve(bubbles.size() * 18); + uv.reserve(bubbles.size() * 12); + col.reserve(bubbles.size() * 24); + const float quad[6][2] = {{-1, -1}, {1, -1}, {1, 1}, {-1, -1}, {1, 1}, {-1, 1}}; + const float quv[6][2] = {{0, 0}, {1, 0}, {1, 1}, {0, 0}, {1, 1}, {0, 1}}; + for (const Bubble & bubble : bubbles) { + const float t = bubble.age; + const float cx = bubble.x + bubble.vx * t; + const float cy = bubble.y + bubble.vy * t + 0.5f * bubble.ay * t * t; + const float cz = bubble.z + bubble.vz * t; + const float u = std::min(1.f, t / bubble.life); + const float size = bubble.size0 + (bubble.size1 - bubble.size0) * u; + const float alpha = u < 0.8f ? 1.f : (1.f - u) / 0.2f; + const float half = size * 0.5f; + for (int v = 0; v < 6; ++v) { + pos.push_back(cx + right[0] * quad[v][0] * half + upv[0] * quad[v][1] * half); + pos.push_back(cy + right[1] * quad[v][0] * half + upv[1] * quad[v][1] * half); + pos.push_back(cz + right[2] * quad[v][0] * half + upv[2] * quad[v][1] * half); + uv.push_back(quv[v][0]); + uv.push_back(quv[v][1]); + col.push_back(0.7f); + col.push_back(0.8f); + col.push_back(1.f); + col.push_back(alpha); + } + } + const int count = static_cast(pos.size() / 3); + bubbleVbo.setVertexData(pos.data(), 3, count, GL_STREAM_DRAW); + bubbleVbo.setTexCoordData(uv.data(), count, GL_STREAM_DRAW); + bubbleVbo.setColorData(col.data(), count, GL_STREAM_DRAW); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glDepthMask(GL_FALSE); + glDisable(GL_CULL_FACE); + bubbleShader.begin(); + setMat(bubbleShader, "viewProjection", viewProjection); + bubbleShader.setUniformTexture("colorMap", textureNamed("bubble.png"), 0); + bubbleVbo.draw(GL_TRIANGLES, 0, count); + bubbleShader.end(); + glDepthMask(GL_TRUE); + glEnable(GL_CULL_FACE); +} + +void ofApp::drawRays() { + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE); + glDepthMask(GL_FALSE); + glDisable(GL_CULL_FACE); + texShader.begin(); + setMat(texShader, "viewProjection", viewProjectionNoRot); + texShader.setUniformTexture("colorMap", textureNamed("LightRay.png"), 0); + for (const Ray & ray : rays) { + const float lerp = ray.duration > 0.f ? ray.timer / ray.duration : 0.f; + const float y = std::max(70.f, std::min(120.f, 50.f + eyeHeight)); + aq::Mat4 spin; + aq::Mat4 shift; + aq::Mat4 placed; + aq::Mat4 scale; + aq::Mat4 world; + aq::rotationZ(spin, ray.rot + lerp * 0.2f); + aq::translation(shift, ray.x, y, 0.f); + aq::mul(placed, spin, shift); + aq::scaling(scale, 10.f, -100.f, 10.f); + aq::mul(world, scale, placed); + const float alpha = std::sin(lerp * static_cast(PI)); + texShader.setUniform4f("colorMult", 1.f, 1.f, 1.f, alpha); + setMat(texShader, "world", world); + rayVbo.draw(GL_TRIANGLES, 0, rayCount); + } + texShader.end(); + glDepthMask(GL_TRUE); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glEnable(GL_CULL_FACE); +} + +void ofApp::draw() { + if (!ready) { + return; + } +#if OF_USE_DAWN + updateCamera(); + aquariumDawnDraw(*this); + ++frameCount; + if (gShotFrame > 0 && frameCount >= gShotFrame) { + reportShot(*this); + } + return; +#endif + glEnable(GL_DEPTH_TEST); + glDepthFunc(GL_LEQUAL); + glDepthMask(GL_TRUE); + glEnable(GL_CULL_FACE); + glCullFace(GL_BACK); + glFrontFace(GL_CCW); + glDisable(GL_BLEND); + + updateCamera(); + drawGroup("base", 1.f); + drawFish(); + if (optTank) { + drawGroup("inner", 1.f); + } + drawGroup("seaweed", 1.f); + if (optLasers) { + drawLasers(false); + } + if (optMuseum) { + drawGroup("outside", 0.f); + } + if (optBubbles) { + drawBubbles(); + } + if (optRays) { + drawRays(); + } + if (optTank) { + drawGroup("outer", 1.f); + } + if (optLasers) { + drawLasers(true); + } + + glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_TRUE); + glClearColor(0.f, 0.f, 0.f, 1.f); + glClear(GL_COLOR_BUFFER_BIT); + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + + ++frameCount; + if (gShotFrame > 0 && frameCount >= gShotFrame) { +#if !defined(TARGET_OF_IOS) + ofSaveScreen("/tmp/aquarium-shot.png"); +#endif + reportShot(*this); + return; + } + + ofSetColor(255); + std::string hud = "fish " + ofToString(fishTotal) + " view " + kViews[viewIndex].name; + hud += std::string(" ") + (optNormal ? "normal " : "") + (optReflect ? "reflect " : "") + (optFog ? "fog" : ""); + hud += " fps " + ofToString(ofGetFrameRate(), 1); + hud += "\n1-9,0 fish space view n r f t m b y l"; + ofDrawBitmapStringHighlight(hud, 16, 24); +} + +void ofApp::keyPressed(int key) { + if (!ready) { + return; + } + if (key == ' ') { + applyView(viewIndex + 1); + } else if (key >= '1' && key <= '9') { + fishSetting = key - '1'; + distributeFish(kFishPresets[fishSetting]); + } else if (key == '0') { + fishSetting = 9; + distributeFish(kFishPresets[9]); + } else if (key == 'n' || key == 'N') { + optNormal = !optNormal; + prettyDone = true; + } else if (key == 'r' || key == 'R') { + optReflect = !optReflect; + prettyDone = true; + } else if (key == 'f' || key == 'F') { + optFog = !optFog; + } else if (key == 't' || key == 'T') { + optTank = !optTank; + } else if (key == 'm' || key == 'M') { + optMuseum = !optMuseum; + } else if (key == 'b' || key == 'B') { + optBubbles = !optBubbles; + } else if (key == 'y' || key == 'Y') { + optRays = !optRays; + } else if (key == 'l' || key == 'L') { + optLasers = !optLasers; + } +} diff --git a/examples/gl/aquariumExample/src/ofApp.h b/examples/gl/aquariumExample/src/ofApp.h new file mode 100644 index 00000000000..7b98f8da906 --- /dev/null +++ b/examples/gl/aquariumExample/src/ofApp.h @@ -0,0 +1,202 @@ +#pragma once + +#include "ofMain.h" +#include "aquariumMath.h" + +#include +#include +#include +#include + +#if defined(TARGET_OF_IOS) +#include "ofxiOSApp.h" +#endif + +void aquariumConfigure(int argc, char ** argv); + +class ofApp; +bool aquariumDawnPrepare(ofApp & app); +void aquariumDawnDraw(ofApp & app); + +// WebGL Aquarium (webglsamples.org) in openFrameworks. +// Copyright 2009, Google Inc. Portions of the scene logic are BSD-3-Clause. +#if defined(TARGET_OF_IOS) +class ofApp : public ofxiOSApp { +#else +class ofApp : public ofBaseApp { +#endif +public: + void setup() override; + void update() override; + void draw() override; + void keyPressed(int key) override; + + struct Mesh { + ofVbo vbo; + int indexCount = 0; + float maxZ = 0; +#if OF_USE_DAWN + std::vector pos; + std::vector nor; + std::vector uv; + std::vector tan; + std::vector bin; + std::vector indices; +#endif + }; + struct Model { + Mesh mesh; + std::string diffuse; + std::string normalMap; + std::string reflectionMap; + }; + struct Scene { + std::string name; + std::string group; + std::string program; + bool fog = true; + bool blend = false; + std::vector models; + }; + struct Object { + int scene = -1; + aq::Mat4 world; + }; + struct FishKind { + const char * name = ""; + float speed = 0; + float speedRange = 0; + float radius = 0; + float radiusRange = 0; + float tailSpeed = 0; + float heightOffset = 0; + float heightRange = 0; + float length = 10; + float wave = 1; + float bend = 1; + bool lasers = false; + float laserRot = 0; + float laserOff[3] = {0, 0, 0}; + float laserScale[3] = {1, 1, 1}; + int num = 0; + int scene = -1; + float maxZ = 1; + struct Inst { + float position[3] = {}; + float target[3] = {}; + float scale = 1; + float time = 0; + bool outside = false; + float outPos[3] = {}; + float outTarget[3] = {}; + }; + std::vector inst; + }; + struct Ray { + float duration = 1; + float timer = 1; + float rot = 0; + float x = 0; + }; + struct Bubble { + float x = 0, y = 0, z = 0; + float vx = 0, vy = 0, vz = 0; + float ay = 0; + float size0 = 0.01f; + float size1 = 0.4f; + float age = 0; + float life = 40; + }; + struct View { + const char * name; + float targetHeight, targetRadius, eyeHeight, eyeRadius, eyeSpeed, fov; + float ambient[3]; + float fogPower, fogMult, fogOffset; + float fogRgb[3]; + }; + + bool loadShader(ofShader & shader, const std::string & vert, const std::string & frag); + bool loadAqm(const std::string & path, std::vector & models); + const ofTexture & textureNamed(const std::string & name); + void applyView(int index); + void distributeFish(int totalFish); + void updateCamera(); + void setMat(const ofShader & shader, const char * name, const aq::Mat4 & m); + void setShared(const ofShader & shader); + void bindModelTextures(const ofShader & shader, const Model & model); + void drawGroup(const std::string & group, float specularFactor); + void drawFish(); + void drawLasers(bool outside); + void drawRays(); + void drawBubbles(); + void initRay(Ray & ray); + void spawnBubbles(); + float pseudo(); + void resetPseudo(); + + bool ready = false; + std::vector scenes; + std::vector objects; + std::unordered_map sceneByName; + std::unordered_map images; + ofTexture white; + GLuint cube = 0; + + ofShader litShader; + ofShader innerShader; + ofShader outerShader; + ofShader seaweedShader; + ofShader fishShader; + ofShader texShader; + ofShader bubbleShader; + + ofVbo laserVbo; + int laserCount = 0; + ofVbo rayVbo; + int rayCount = 0; + ofVbo bubbleVbo; + + std::vector fish; + std::vector rays; + std::vector bubbles; + float bubbleTimer = 0; + + aq::Mat4 projection; + aq::Mat4 view; + aq::Mat4 viewInverse; + aq::Mat4 viewProjection; + aq::Mat4 viewProjectionNoRot; + float eye[3] = {}; + float light[3] = {}; + + int viewIndex = 0; + int fishSetting = 2; + int fishTotal = 500; + float targetHeight = 0, targetRadius = 88, eyeHeight = 38, eyeRadius = 69, eyeSpeed = 0.0258f, fov = 64; + float ambient[3] = {0.218f, 0.246f, 0.394f}; + float fogPower = 16.5f, fogMult = 1.5f, fogOffset = 0.738f; + float fogRgb[3] = {0.338f, 0.81f, 1.f}; + float fishHeightRange = 1, fishHeight = 25, fishSpeed = 0.124f, fishOffset = 0.52f; + float fishXClock = 1, fishYClock = 0.556f, fishZClock = 1, fishTailSpeed = 1; + float globalsSpeed = 1; + float refractionFudge = 3, eta = 1, tankColorFudge = 0.8f; + + bool optNormal = false; + bool optReflect = false; + bool optFog = true; + bool optTank = true; + bool optMuseum = true; + bool optBubbles = true; + bool optRays = true; + bool optLasers = false; + bool prettyDone = false; + + float clock = 0; + float eyeClock = 0; + float then = 0; + int frameCount = 0; + std::uint32_t randomSeed = 0; + + static const int kFishPresets[10]; + static const View kViews[6]; +}; diff --git a/examples/gl/backendBenchExample/src/main.cpp b/examples/gl/backendBenchExample/src/main.cpp new file mode 100644 index 00000000000..c3d9905233b --- /dev/null +++ b/examples/gl/backendBenchExample/src/main.cpp @@ -0,0 +1,14 @@ +#include "ofMain.h" +#include "ofApp.h" + +int main(int argc, char ** argv) { + benchConfigure(argc, argv); + + ofGLWindowSettings settings; + settings.setSize(1024, 768); + settings.windowMode = OF_WINDOW; + + auto window = ofCreateWindow(settings); + ofRunApp(window, std::make_shared()); + ofRunMainLoop(); +} diff --git a/examples/gl/backendBenchExample/src/ofApp.cpp b/examples/gl/backendBenchExample/src/ofApp.cpp new file mode 100644 index 00000000000..ca6e164f7c8 --- /dev/null +++ b/examples/gl/backendBenchExample/src/ofApp.cpp @@ -0,0 +1,355 @@ +#include "ofApp.h" + +#include "../vendor/ofxShapeBatchRenderer/ofxShapeBatchRenderer.cpp" + +#include +#include +#include +#include +#include + +#if defined(TARGET_OF_IOS) && !defined(TARGET_OF_TVOS) && !OF_USE_DAWN && !OF_USE_ANGLE +#include "ofxiOS.h" +#endif + +namespace { + +int gCount = 0; +std::string gMode = "both"; +float gSeconds = 10.f; +std::unique_ptr gBatch; + +bool wantMode(ofApp::BenchMode mode) { + if(gMode == "mesh") { + return mode == ofApp::BenchMode::Mesh; + } + if(gMode == "immediate") { + return mode == ofApp::BenchMode::Immediate; + } + if(gMode == "batch") { + return mode == ofApp::BenchMode::Batch; + } + return true; +} + +const char * modeName(ofApp::BenchMode mode) { + switch(mode) { + case ofApp::BenchMode::Mesh: return "mesh"; + case ofApp::BenchMode::Immediate: return "immediate"; + case ofApp::BenchMode::Batch: return "batch"; + } + return "batch"; +} + +// 256px openFrameworks mark (examples/3d/ofBoxExample/bin/data/of.png). +// Stripes are lines of constant x+y, period 15, white on residues 4..9. +// The circle and the F sit on top of the stripes as solid white. +bool logoMark(int x, int y) { + const float dx = (x + 0.5f) - 75.246f; + const float dy = (y + 0.5f) - 138.411f; + if(dx * dx + dy * dy <= 60.708f * 60.708f) { + return true; + } + if(x >= 147 && x <= 199 && y >= 72 && y <= 196) { + return true; + } + if(x >= 207 && y >= 72 && y <= 111 && (x + y) <= 324) { + return true; + } + if(x >= 207 && y >= 128 && y <= 156 && (x + y) <= 369) { + return true; + } + return false; +} + +bool stripeWhite(int x, int y) { + int s = (x + y) % 15; + if(s < 0) { + s += 15; + } + return s >= 4 && s <= 9; +} + +void hsv(float h, float & r, float & g, float & b) { + h = std::fmod(h, 1.f); + if(h < 0.f) { + h += 1.f; + } + const float x = h * 6.f; + const int sector = static_cast(x); + const float f = x - static_cast(sector); + switch(sector % 6) { + case 0: r = 1.f; g = f; b = 0.f; break; + case 1: r = 1.f - f; g = 1.f; b = 0.f; break; + case 2: r = 0.f; g = 1.f; b = f; break; + case 3: r = 0.f; g = 1.f - f; b = 1.f; break; + case 4: r = f; g = 0.f; b = 1.f; break; + default: r = 1.f; g = 0.f; b = 1.f - f; break; + } +} + +} + +void benchConfigure(int argc, char ** argv) { + for(int i = 1; i < argc; i++) { + const std::string arg = argv[i]; + if(arg == "--count" && i + 1 < argc) { + gCount = std::atoi(argv[++i]); + } else if(arg == "--mode" && i + 1 < argc) { + gMode = argv[++i]; + } else if(arg == "--seconds" && i + 1 < argc) { + gSeconds = static_cast(std::atof(argv[++i])); + } + } +} + +std::string ofApp::backendLabel() const { + auto renderer = ofGetCurrentRenderer(); + if(!renderer) { + return "none"; + } +#if OF_USE_DAWN + if(renderer->getType() == "WebGPU") { + return "dawn"; + } +#else + const GLubyte * name = glGetString(GL_RENDERER); + if(name) { + return reinterpret_cast(name); + } +#endif + return renderer->getType(); +} + +void stopBench() { +#if defined(TARGET_OF_IOS) + std::exit(0); +#else + ofExit(0); +#endif +} + +void ofApp::setup() { + ofSetVerticalSync(false); + ofSetFrameRate(0); +#if defined(TARGET_OF_IOS) && !defined(TARGET_OF_TVOS) && !OF_USE_DAWN && !OF_USE_ANGLE + if(ofxiOSGLKViewController * controller = ofxiOSGetGLKViewController()) { + [controller setPreferredFPS:120]; + } +#endif + ofSetBackgroundColor(0, 0, 0); + ofSetBackgroundAuto(true); + seconds_ = gSeconds > 0.f ? gSeconds : 10.f; + + const ofApp::BenchMode modes[] = { + ofApp::BenchMode::Mesh, + ofApp::BenchMode::Immediate, + ofApp::BenchMode::Batch + }; + if(gCount > 0) { + for(ofApp::BenchMode mode : modes) { + if(wantMode(mode)) { + trials_.push_back({ gCount, mode }); + } + } + } else { + const int counts[] = { 1000, 5000, 20000, 50000 }; + for(int count : counts) { + for(ofApp::BenchMode mode : modes) { + if(wantMode(mode)) { + trials_.push_back({ count, mode }); + } + } + } + } + mesh_.setMode(OF_PRIMITIVE_TRIANGLES); + mesh_.enableColors(); + buildLogo(); + startTrial(); +} + +void ofApp::buildLogo() { + logo_.clear(); + // Finer than the source so 50k items do not stack on the same point. + const int cells = 400; + const float step = 256.f / static_cast(cells); + logo_.reserve(static_cast(cells * cells) / 2); + for(int iy = 0; iy < cells; iy++) { + for(int ix = 0; ix < cells; ix++) { + const float fx = (static_cast(ix) + 0.5f) * step; + const float fy = (static_cast(iy) + 0.5f) * step; + const int x = static_cast(fx); + const int y = static_cast(fy); + if(x < 1 || y < 1 || x > 254 || y > 254) { + continue; + } + const bool mark = logoMark(x, y); + const bool stripe = stripeWhite(x, y); + if(!mark && !stripe) { + continue; + } + LogoPoint point; + point.x = fx; + point.y = fy; + point.mark = mark; + int band = (x + y) / 15; + if(band < 0) { + band = 0; + } + point.band = static_cast(band); + logo_.push_back(point); + } + } +} + +void ofApp::startTrial() { + measuring_ = false; + frames_ = 0; + minDt_ = 1.e9f; + if(trial_ < static_cast(trials_.size())) { + const Trial & trial = trials_[trial_]; + mesh_.getVertices().resize(static_cast(trial.count) * 3); + mesh_.getColors().resize(static_cast(trial.count) * 3); + if(trial.mode == BenchMode::Batch) { + gBatch.reset(new ofxShapeBatchRenderer(SBR_TRIANGLE, trial.count, 1)); + } + } +} + +void ofApp::drawLogo() { + const Trial & trial = trials_[trial_]; + const int n = trial.count; + if(logo_.empty() || n <= 0) { + return; + } + const float t = static_cast(ofGetElapsedTimef()); + const float side = std::min(static_cast(ofGetWidth()), static_cast(ofGetHeight())) * 0.92f; + const float left = (static_cast(ofGetWidth()) - side) * 0.5f; + const float top = (static_cast(ofGetHeight()) - side) * 0.5f; + const float scale = side / 256.f; + // Long axis of each triangle follows the stripe direction (1, -1). + const float cs = 0.70710678f; + const float sn = -0.70710678f; + const float lx[3] = { 14.f, -8.f, -8.f }; + const float ly[3] = { 0.f, 5.f, -5.f }; + const std::size_t count = logo_.size(); + + auto place = [&](int i, float & x, float & y, float & r, float & g, float & b) { + const LogoPoint & point = logo_[(static_cast(static_cast(i) * 2654435761u)) % count]; + const float slide = std::sin(t * 0.6f + static_cast(point.band) * 0.35f) * 0.8f; + x = left + (point.x + slide * cs) * scale; + y = top + (point.y + slide * sn) * scale; + if(point.mark) { + r = g = b = 1.f; + } else { + hsv(static_cast(point.band) * 0.045f + t * 0.02f, r, g, b); + } + }; + + if(trial.mode == BenchMode::Mesh) { + auto & verts = mesh_.getVertices(); + auto & colors = mesh_.getColors(); + for(int i = 0; i < n; i++) { + float x, y, r, g, b; + place(i, x, y, r, g, b); + const ofFloatColor color(r, g, b, 1.f); + for(int k = 0; k < 3; k++) { + verts[static_cast(i) * 3 + k] = { + x + lx[k] * cs - ly[k] * sn, + y + lx[k] * sn + ly[k] * cs, + 0.f + }; + colors[static_cast(i) * 3 + k] = color; + } + } + mesh_.draw(OF_MESH_FILL); + } else if(trial.mode == BenchMode::Batch && gBatch) { + gBatch->clear(); + for(int i = 0; i < n; i++) { + float x, y, r, g, b; + place(i, x, y, r, g, b); + const int ir = static_cast(r * 255.f + 0.5f); + const int ig = static_cast(g * 255.f + 0.5f); + const int ib = static_cast(b * 255.f + 0.5f); + gBatch->setColor(ir, ig, ib, 255); + gBatch->addTriangle( + ofPoint(x + lx[0] * cs - ly[0] * sn, y + lx[0] * sn + ly[0] * cs, 0.f), + ofPoint(x + lx[1] * cs - ly[1] * sn, y + lx[1] * sn + ly[1] * cs, 0.f), + ofPoint(x + lx[2] * cs - ly[2] * sn, y + lx[2] * sn + ly[2] * cs, 0.f)); + } + gBatch->draw(); + } else { + for(int i = 0; i < n; i++) { + float x, y, r, g, b; + place(i, x, y, r, g, b); + ofSetColor(ofFloatColor(r, g, b, 1.f)); + ofDrawTriangle( + x + lx[0] * cs - ly[0] * sn, y + lx[0] * sn + ly[0] * cs, + x + lx[1] * cs - ly[1] * sn, y + lx[1] * sn + ly[1] * cs, + x + lx[2] * cs - ly[2] * sn, y + lx[2] * sn + ly[2] * cs); + } + } +} + +void ofApp::update() { +} + +void ofApp::draw() { + if(trials_.empty() || trial_ >= static_cast(trials_.size())) { + stopBench(); + return; + } + if(!started_) { + started_ = true; + phaseStart_ = ofGetElapsedTimef(); + std::cout << "RENDERER backend=" << backendLabel() + << " px=" << ofGetWidth() << "x" << ofGetHeight() + << " seconds=" << seconds_ << std::endl; + } + + drawLogo(); + + const double now = ofGetElapsedTimef(); + if(!measuring_) { + if(now - phaseStart_ >= 1.0) { + measuring_ = true; + phaseStart_ = now; + frames_ = 0; + minDt_ = 1.e9f; + } + return; + } + + frames_++; + const float dt = ofGetLastFrameTime(); + if(dt > 0.f && dt < minDt_) { + minDt_ = dt; + } + if(now - phaseStart_ < static_cast(seconds_)) { + return; + } + + const double sec = now - phaseStart_; + const double avg = sec > 0.0 ? static_cast(frames_) / sec : 0.0; + const double minFps = minDt_ < 1.e8f && minDt_ > 0.f ? 1.0 / static_cast(minDt_) : 0.0; + const Trial & trial = trials_[trial_]; + std::ostringstream line; + line << "BENCH backend=" << backendLabel() + << " mode=" << modeName(trial.mode) + << " n=" << trial.count + << " sec=" << sec + << " frames=" << frames_ + << " avg_fps=" << avg + << " min_fps=" << minFps + << " px=" << ofGetWidth() << "x" << ofGetHeight(); + std::cout << line.str() << std::endl; + ofLogNotice("backendBench") << line.str(); + + trial_++; + if(trial_ >= static_cast(trials_.size())) { + stopBench(); + return; + } + phaseStart_ = ofGetElapsedTimef(); + startTrial(); +} diff --git a/examples/gl/backendBenchExample/src/ofApp.h b/examples/gl/backendBenchExample/src/ofApp.h new file mode 100644 index 00000000000..150e062cf8b --- /dev/null +++ b/examples/gl/backendBenchExample/src/ofApp.h @@ -0,0 +1,57 @@ +#pragma once + +#include "ofMain.h" +#include +#include + +#if defined(TARGET_OF_IOS) +#include "ofxiOSApp.h" +#endif + +void benchConfigure(int argc, char ** argv); + +#if defined(TARGET_OF_IOS) +class ofApp : public ofxiOSApp { +#else +class ofApp : public ofBaseApp { +#endif +public: + enum class BenchMode { + Mesh, + Immediate, + Batch + }; + + void setup() override; + void update() override; + void draw() override; + +private: + struct Trial { + int count = 0; + BenchMode mode = BenchMode::Mesh; + }; + + struct LogoPoint { + float x = 0.f; + float y = 0.f; + bool mark = false; + unsigned char band = 0; + }; + + void startTrial(); + void buildLogo(); + void drawLogo(); + std::string backendLabel() const; + + std::vector trials_; + std::vector logo_; + int trial_ = 0; + float seconds_ = 10.f; + bool measuring_ = false; + bool started_ = false; + double phaseStart_ = 0.0; + int frames_ = 0; + float minDt_ = 1.e9f; + ofMesh mesh_; +}; diff --git a/examples/gl/backendBenchExample/vendor/ofxShapeBatchRenderer/ofxShapeBatchRenderer.cpp b/examples/gl/backendBenchExample/vendor/ofxShapeBatchRenderer/ofxShapeBatchRenderer.cpp new file mode 100644 index 00000000000..725bf6d4e72 --- /dev/null +++ b/examples/gl/backendBenchExample/vendor/ofxShapeBatchRenderer/ofxShapeBatchRenderer.cpp @@ -0,0 +1,662 @@ +/*********************************************************************** + + Copyright (C) 2011 by Zach Gage + Copyright (C) 2015-2022 Dan Rosser - Updated for Various Apps and Games - Used in Super Hexagon + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + + ************************************************************************/ + +#include "ofxShapeBatchRenderer.h" + +ofxShapeBatchRenderer::ofxShapeBatchRenderer(SHAPEMODE_t mode, int _numObjectsPerLayer, int _numLayers) +{ + numObjectsPerLayer = _numObjectsPerLayer; + numLayers = _numLayers; + + verts = new double[numLayers*numObjectsPerLayer*(mode*3)]; + colors = new unsigned char[numLayers*numObjectsPerLayer*(mode*4)]; //4 colors for each vertex. + numObjects = new int[numLayers*numObjectsPerLayer]; + numObjectsLast = new int[numLayers*numObjectsPerLayer]; + layerLineWidth = new int[numLayers]; + +#if defined(USING_VBO) + meshes.reserve(numLayers); +#endif + + for(int i=0;isetMode(OF_PRIMITIVE_TRIANGLES); + mesh->setUsage(GL_STREAM_DRAW); + mesh->disableNormals(); + mesh->disableTextures(); + mesh->disableIndices(); + mesh->enableColors(); + const std::size_t count = static_cast(numObjectsPerLayer) * static_cast(shapeMode); + mesh->getVertices().resize(count); + mesh->getColors().resize(count); + meshes.push_back(mesh); + #endif + } +} + +ofxShapeBatchRenderer::~ofxShapeBatchRenderer() +{ + delete verts; + delete colors; + delete numObjects; + delete layerLineWidth; + #if defined(USING_VBO) + if(meshes.size() > 0) { + for (int i = meshes.size() - 1; i >= 0; i--) { + if (meshes[i] != nullptr) { + delete meshes[i]; + meshes[i] = nullptr; + } + } + } + meshes.clear(); + #endif +} + +//shapes -- + +bool ofxShapeBatchRenderer::addTriangle(ofPoint a, ofPoint b, ofPoint c, int layer) +{return addTriangle(a,red,green,blue,alpha, b,red,green,blue,alpha, c,red,green,blue,alpha, layer);} + +bool ofxShapeBatchRenderer::addTriangle(ofPoint a, int aR, int aG, int aB, int aA, ofPoint b, int bR, int bG, int bB, int bA, ofPoint c, int cR, int cG, int cB, int cA, int layer) +{ + if(shapeMode!=SBR_TRIANGLE) + { + ofLogError("ofxShapeBatchRenderer") << "RENDER ERROR: Could not add triangle to ShapeBatchRender set up for lines!"; + return false; + } + + if(numObjects[layer] >= numObjectsPerLayer) + { + ofLogError("ofxShapeBatchRenderer") << "RENDER ERROR: Layer " << layer << " over allocated! Max " << numObjectsPerLayer << " objects per layer!"; + return false; + } + + if(layer > numLayers) + { + ofLogError("ofxShapeBatchRenderer") << "RENDER ERROR: Bogus layer '" << layer << "'! Only " << numLayers << " layers compiled!"; + return false; + } + + int layerOffset = layer * numObjectsPerLayer; + int vertexOffset = (layerOffset + numObjects[layer]) * (shapeMode*3); + int colorOffset = (layerOffset + numObjects[layer]) * (shapeMode*4); + + #if defined(USING_VBO) + { + const int base = numObjects[layer] * shapeMode; + auto & verts = meshes[layer]->getVertices(); + auto & cols = meshes[layer]->getColors(); + verts[base] = ofVec3f(a.x, a.y, a.z); + verts[base + 1] = ofVec3f(b.x, b.y, b.z); + verts[base + 2] = ofVec3f(c.x, c.y, c.z); + cols[base] = ofColor(aR, aG, aB, aA); + cols[base + 1] = ofColor(bR, bG, bB, bA); + cols[base + 2] = ofColor(cR, cG, cB, cA); + } + #else + + //verticies ------------------------------------ + verts[vertexOffset ] = a.x; + verts[vertexOffset + 1 ] = a.y; + verts[vertexOffset + 2 ] = a.z; + + verts[vertexOffset + 3 ] = b.x; + verts[vertexOffset + 4 ] = b.y; + verts[vertexOffset + 5 ] = b.z; + + verts[vertexOffset + 6 ] = c.x; + verts[vertexOffset + 7 ] = c.y; + verts[vertexOffset + 8 ] = c.z; + + //colors --------------------------------------- + colors[colorOffset ] = aR; + colors[colorOffset + 1 ] = aG; + colors[colorOffset + 2 ] = aB; + colors[colorOffset + 3 ] = aA; + + colors[colorOffset + 4 ] = bR; + colors[colorOffset + 5 ] = bG; + colors[colorOffset + 6 ] = bB; + colors[colorOffset + 7 ] = bA; + + colors[colorOffset + 8 ] = cR; + colors[colorOffset + 9 ] = cG; + colors[colorOffset + 10] = cB; + colors[colorOffset + 11] = cA; + #endif + + numObjects[layer]++; + + return true; +} + +bool ofxShapeBatchRenderer::addLine(ofPoint a, ofPoint b, int layer) +{return addLine(a,red,green,blue,alpha, b,red,green,blue,alpha, layer);} + +bool ofxShapeBatchRenderer::addLine(ofPoint a, int aR, int aG, int aB, int aA, ofPoint b, int bR, int bG, int bB, int bA, int layer) +{ + if(shapeMode!=SBR_LINE) + { + ofLogError("ofxShapeBatchRenderer") << "RENDER ERROR: Could not add line to ShapeBatchRender set up for triangles!"; + return false; + } + + if(numObjects[layer] >= numObjectsPerLayer) + { + ofLogError("ofxShapeBatchRenderer") << "RENDER ERROR: Layer " << layer << " over allocated! Max " << numObjectsPerLayer << " objects per layer!"; + return false; + } + + if(layer > numLayers) + { + ofLogError("ofxShapeBatchRenderer") << "RENDER ERROR: Bogus layer '" << layer << "'! Only " << numLayers << " layers compiled!"; + return false; + } + + int layerOffset = layer * numObjectsPerLayer; + int vertexOffset = (layerOffset + numObjects[layer]) * (shapeMode*3); + int colorOffset = (layerOffset + numObjects[layer]) * (shapeMode*4); + + #if defined(USING_VBO) + { + const int base = numObjects[layer] * shapeMode; + auto & verts = meshes[layer]->getVertices(); + auto & cols = meshes[layer]->getColors(); + verts[base] = ofVec3f(a.x, a.y, a.z); + verts[base + 1] = ofVec3f(b.x, b.y, b.z); + cols[base] = ofColor(aR, aG, aB, aA); + cols[base + 1] = ofColor(bR, bG, bB, bA); + } + + //meshes[layer]->enableIndices(); + + #else + + //verticies ------------------------------------ + verts[vertexOffset ] = a.x; + verts[vertexOffset + 1 ] = a.y; + verts[vertexOffset + 2 ] = a.z; + + verts[vertexOffset + 3 ] = b.x; + verts[vertexOffset + 4 ] = b.y; + verts[vertexOffset + 5 ] = b.z; + + //colors --------------------------------------- + colors[colorOffset ] = aR; + colors[colorOffset + 1 ] = aG; + colors[colorOffset + 2 ] = aB; + colors[colorOffset + 3 ] = aA; + + colors[colorOffset + 4 ] = bR; + colors[colorOffset + 5 ] = bG; + colors[colorOffset + 6 ] = bB; + colors[colorOffset + 7 ] = bA; + #endif + + numObjects[layer]++; + + return true; +} + + +//handled -- +bool ofxShapeBatchRenderer::addCircle(float x, float y, float z, float radius, int layer) +{return addElipse(x,y,z,radius,radius,layer);} + +bool ofxShapeBatchRenderer::addElipse(float x, float y, float z, float w, float h, int layer) +{ + if(layer > numLayers) + { + ofLogError("ofxShapeBatchRenderer") << "RENDER ERROR: Bogus layer '" << layer << "'! Only " << numLayers << " layers compiled!"; + return false; + } + + int layerOffset = layer * numObjectsPerLayer; + int vertexOffset; + int colorOffset; + + if(shapeMode == SBR_LINE) + { + int k = 0; + for(int i=0;i= numObjectsPerLayer) + { + ofLogError("ofxShapeBatchRenderer") << "RENDER ERROR: Layer " << layer << " over allocated! Max " << numObjectsPerLayer << " objects per layer! Ran out during circle addition"; + return false; + } + + if(i>0) + { + //verticies ------------------------------------ + verts[vertexOffset ] = x + circlePts[k] * w * 0.5; + verts[vertexOffset + 1 ] = y + circlePts[k+1] * h * 0.5; + verts[vertexOffset + 2 ] = z; + + verts[vertexOffset + 3 ] = x + circlePts[k-2] * w * 0.5; + verts[vertexOffset + 4 ] = y + circlePts[k-1] * h * 0.5; + verts[vertexOffset + 5 ] = z; + } + else if(i==0) + { + //verticies ------------------------------------ + verts[vertexOffset ] = x + circlePts[k] * w * 0.5; + verts[vertexOffset + 1 ] = y + circlePts[k+1] * h * 0.5; + verts[vertexOffset + 2 ] = z; + + verts[vertexOffset + 3 ] = x + circlePts[numCirclePts*2-2] * w * 0.5; + verts[vertexOffset + 4 ] = y + circlePts[numCirclePts*2-1] * h * 0.5; + verts[vertexOffset + 5 ] = z; + } + + //colors --------------------------------------- + colors[colorOffset ] = red; + colors[colorOffset + 1 ] = green; + colors[colorOffset + 2 ] = blue; + colors[colorOffset + 3 ] = alpha; + + colors[colorOffset + 4 ] = red; + colors[colorOffset + 5 ] = green; + colors[colorOffset + 6 ] = blue; + colors[colorOffset + 7 ] = alpha; + + numObjects[layer]++; + k+=2; + } + } + else if(shapeMode == SBR_TRIANGLE) + { + int k = 0; + + for(int i=0;i= numObjectsPerLayer) + { + ofLogError("ofxShapeBatchRenderer") << "RENDER ERROR: Layer " << layer << " over allocated! Max " << numObjectsPerLayer << " objects per layer! Ran out during circle addition"; + return false; + } + + if(i>0) + { + //verticies ------------------------------------ + verts[vertexOffset ] = x + circlePts[k] * w * 0.5; + verts[vertexOffset + 1 ] = y + circlePts[k+1] * h * 0.5; + verts[vertexOffset + 2 ] = z; + + verts[vertexOffset + 3 ] = x + circlePts[k-2] * w * 0.5; + verts[vertexOffset + 4 ] = y + circlePts[k-1] * h * 0.5; + verts[vertexOffset + 5 ] = z; + + verts[vertexOffset + 6 ] = x; + verts[vertexOffset + 7 ] = y; + verts[vertexOffset + 8 ] = z; + } + else if(i==0) + { + //verticies ------------------------------------ + verts[vertexOffset ] = x + circlePts[k] * w * 0.5; + verts[vertexOffset + 1 ] = y + circlePts[k+1] * h * 0.5; + verts[vertexOffset + 2 ] = z; + + verts[vertexOffset + 3 ] = x + circlePts[numCirclePts*2-2] * w * 0.5; + verts[vertexOffset + 4 ] = y + circlePts[numCirclePts*2-1] * h * 0.5; + verts[vertexOffset + 5 ] = z; + + verts[vertexOffset + 6 ] = x; + verts[vertexOffset + 7 ] = y; + verts[vertexOffset + 8 ] = z; + } + + //colors --------------------------------------- + colors[colorOffset ] = red; + colors[colorOffset + 1 ] = green; + colors[colorOffset + 2 ] = blue; + colors[colorOffset + 3 ] = alpha; + + colors[colorOffset + 4 ] = red; + colors[colorOffset + 5 ] = green; + colors[colorOffset + 6 ] = blue; + colors[colorOffset + 7 ] = alpha; + + colors[colorOffset + 8 ] = red; + colors[colorOffset + 9 ] = green; + colors[colorOffset + 10] = blue; + colors[colorOffset + 11] = alpha; + + numObjects[layer]++; + k+=2; + } + } + + return true; +} + +bool ofxShapeBatchRenderer::addRect(float x, float y, float z, float w, float h, int layer) +{ + if(layer > numLayers) + { + ofLogError("ofxShapeBatchRenderer") << "RENDER ERROR: Bogus layer '" << layer << "'! Only " << numLayers << " layers compiled!"; + return false; + } + + int layerOffset = layer * numObjectsPerLayer; + int vertexOffset = (layerOffset + numObjects[layer]) * (shapeMode*3); + int colorOffset = (layerOffset + numObjects[layer]) * (shapeMode*4); + + if(shapeMode == SBR_LINE) + { + if(numObjects[layer]+4 >= numObjectsPerLayer) + { + ofLogError("ofxShapeBatchRenderer") << "RENDER ERROR: Layer " << layer << " over allocated! Max " << numObjectsPerLayer << " objects per layer! Ran out during rect addition"; + return false; + } + + //verticies ------------------------------------ + verts[vertexOffset ] = x; + verts[vertexOffset + 1 ] = y; + verts[vertexOffset + 2 ] = z; + + verts[vertexOffset + 3 ] = x + w; + verts[vertexOffset + 4 ] = y; + verts[vertexOffset + 5 ] = z; + + + verts[vertexOffset + 6 ] = x; + verts[vertexOffset + 7 ] = y; + verts[vertexOffset + 8 ] = z; + + verts[vertexOffset + 9 ] = x; + verts[vertexOffset + 10] = y + h; + verts[vertexOffset + 11] = z; + + + verts[vertexOffset + 12] = x + w; + verts[vertexOffset + 13] = y; + verts[vertexOffset + 14] = z; + + verts[vertexOffset + 15] = x + w; + verts[vertexOffset + 16] = y + h; + verts[vertexOffset + 17] = z; + + + verts[vertexOffset + 18] = x; + verts[vertexOffset + 19] = y + h; + verts[vertexOffset + 20] = z; + + verts[vertexOffset + 21] = x + w; + verts[vertexOffset + 22] = y + h; + verts[vertexOffset + 23] = z; + + //colors --------------------------------------- + colors[colorOffset ] = red; + colors[colorOffset + 1 ] = green; + colors[colorOffset + 2 ] = blue; + colors[colorOffset + 3 ] = alpha; + + colors[colorOffset + 4 ] = red; + colors[colorOffset + 5 ] = green; + colors[colorOffset + 6 ] = blue; + colors[colorOffset + 7 ] = alpha; + + + colors[colorOffset + 8 ] = red; + colors[colorOffset + 9 ] = green; + colors[colorOffset + 10] = blue; + colors[colorOffset + 11] = alpha; + + colors[colorOffset + 12] = red; + colors[colorOffset + 13] = green; + colors[colorOffset + 14] = blue; + colors[colorOffset + 15] = alpha; + + + colors[colorOffset + 16] = red; + colors[colorOffset + 17] = green; + colors[colorOffset + 18] = blue; + colors[colorOffset + 19] = alpha; + + colors[colorOffset + 20] = red; + colors[colorOffset + 21] = green; + colors[colorOffset + 22] = blue; + colors[colorOffset + 23] = alpha; + + + colors[colorOffset + 24] = red; + colors[colorOffset + 25] = green; + colors[colorOffset + 26] = blue; + colors[colorOffset + 27] = alpha; + + colors[colorOffset + 28] = red; + colors[colorOffset + 29] = green; + colors[colorOffset + 30] = blue; + colors[colorOffset + 31] = alpha; + + numObjects[layer]+=4; + } + else if(shapeMode == SBR_TRIANGLE) + { + if(numObjects[layer]+2 >= numObjectsPerLayer) + { + ofLogError("ofxShapeBatchRenderer") << "RENDER ERROR: Layer " << layer << " over allocated! Max " << numObjectsPerLayer << " objects per layer! Ran out during rect addition"; + return false; + } + + //verticies ------------------------------------ + verts[vertexOffset ] = x; + verts[vertexOffset + 1 ] = y; + verts[vertexOffset + 2 ] = z; + + verts[vertexOffset + 3 ] = x + w; + verts[vertexOffset + 4 ] = y; + verts[vertexOffset + 5 ] = z; + + verts[vertexOffset + 6 ] = x; + verts[vertexOffset + 7 ] = y + h; + verts[vertexOffset + 8 ] = z; + + + verts[vertexOffset + 9 ] = x + w; + verts[vertexOffset + 10] = y; + verts[vertexOffset + 11] = z; + + verts[vertexOffset + 12] = x; + verts[vertexOffset + 13] = y + h; + verts[vertexOffset + 14] = z; + + verts[vertexOffset + 15] = x + w; + verts[vertexOffset + 16] = y + h; + verts[vertexOffset + 17] = z; + + //colors --------------------------------------- + colors[colorOffset ] = red; + colors[colorOffset + 1 ] = green; + colors[colorOffset + 2 ] = blue; + colors[colorOffset + 3 ] = alpha; + + colors[colorOffset + 4 ] = red; + colors[colorOffset + 5 ] = green; + colors[colorOffset + 6 ] = blue; + colors[colorOffset + 7 ] = alpha; + + colors[colorOffset + 8 ] = red; + colors[colorOffset + 9 ] = green; + colors[colorOffset + 10] = blue; + colors[colorOffset + 11] = alpha; + + + colors[colorOffset + 12] = red; + colors[colorOffset + 13] = green; + colors[colorOffset + 14] = blue; + colors[colorOffset + 15] = alpha; + + colors[colorOffset + 16] = red; + colors[colorOffset + 17] = green; + colors[colorOffset + 18] = blue; + colors[colorOffset + 19] = alpha; + + colors[colorOffset + 20] = red; + colors[colorOffset + 21] = green; + colors[colorOffset + 22] = blue; + colors[colorOffset + 23] = alpha; + + numObjects[layer]+=2; + } + + return true; +} + +bool ofxShapeBatchRenderer::addCenteredRect(float x, float y, float z, float w, float h, int layer) +{ + return addRect(x-w/2,y-h/2,z,w,h,layer); +} + +//functions -- + + +void ofxShapeBatchRenderer::clear() +{ + for(int i = 0; i < numLayers; i++) { + numObjects[i] = 0; + } + +} + +void ofxShapeBatchRenderer::draw() +{ + #if defined(USING_VBO) + for(int l = 0; l < numLayers; l++) + { + if(numObjects[l] > 0) + { + #if defined(USE_DRAW_INSTANCED) + meshes[l]->drawInstanced(OF_MESH_FILL, numObjects[l]*shapeMode); + #else + // ofVboMesh::draw keeps a GL_STATIC_DRAW buffer and later frames + // only glBufferSubData it, which stayed on the first upload here. + // ofMesh::draw submits the rebuilt vertices every frame. + meshes[l]->ofMesh::draw(OF_MESH_FILL); + #endif + } + numObjectsLast[l]=numObjects[l]; + } + #else // not using vbo + + if(safeMode) + { + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_COLOR_ARRAY); + } + + glVertexPointer(3, GL_FLOAT, 0, &verts[0]); + glColorPointer(4, GL_UNSIGNED_BYTE, 0, &colors[0]); + + for(int l = 0; l < numLayers; l++) + { + if(numObjects[l] > 0) + { + + glLineWidth(layerLineWidth[l]); + + if(shapeMode == SBR_LINE) + glDrawArrays(GL_LINES, l*numObjectsPerLayer*shapeMode, numObjects[l]*shapeMode); + else if(shapeMode == SBR_TRIANGLE) + glDrawArrays(GL_TRIANGLES, l*numObjectsPerLayer*shapeMode, numObjects[l]*shapeMode); + } + // //cout<<"layer: "< 1 && res != numCirclePts){ + numCirclePts = res; + + float angle = 0.0f; + float angleAdder = M_TWO_PI / (float)res; + int k = 0; + for (int i = 0; i < numCirclePts; i++){ + circlePts[k] = cos(angle); + circlePts[k+1] = sin(angle); + angle += angleAdder; + k+=2; + } + bSetupCircle = true; + } +} + +void ofxShapeBatchRenderer::setLayerLineWidth(int layer, int lineWidth) +{ + layerLineWidth[layer]=lineWidth; +} + +void ofxShapeBatchRenderer::setSafeMode(bool safe) +{ + safeMode=safe; +} diff --git a/examples/gl/backendBenchExample/vendor/ofxShapeBatchRenderer/ofxShapeBatchRenderer.h b/examples/gl/backendBenchExample/vendor/ofxShapeBatchRenderer/ofxShapeBatchRenderer.h new file mode 100644 index 00000000000..95b6c7ee432 --- /dev/null +++ b/examples/gl/backendBenchExample/vendor/ofxShapeBatchRenderer/ofxShapeBatchRenderer.h @@ -0,0 +1,110 @@ +/*********************************************************************** + + Copyright (C) 2011 by Zach Gage + Copyright (C) 2015-2022 Dan Rosser - Updated for Various Apps and Games - Used in Super Hexagon + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + + ************************************************************************/ + +/* + ofxShapeBatchRenderer was written primarily for use on embedded devices as + a deferred renderer for of shape calls. + */ + +#ifndef OFX_SHAPE_BATCH_RENDERER_H +#define OFX_SHAPE_BATCH_RENDERER_H + +#include "ofMain.h" + +#define DEFAULT_LAYER 0 +#define USING_VBO +// USE_DRAW_INSTANCED calls ofVboMesh::drawInstanced with numObjects*shapeMode. +// That repeats the already-batched mesh. The bench measures one draw(). +// #define USE_DRAW_INSTANCED + +enum SHAPEMODE_t {SBR_LINE=2, SBR_TRIANGLE=3}; //line or triangle based drawing.... essentially ofNoFill(), or ofFill(); + +//class ------------------------------------------- +class ofxShapeBatchRenderer +{ +public: + ofxShapeBatchRenderer(SHAPEMODE_t mode, int _numObjectsPerLayer, int _numLayers); + ~ofxShapeBatchRenderer(); + + //raw -- + //this can only be used if you're in triangle mode + bool addTriangle(ofPoint a, ofPoint b, ofPoint c, int layer=DEFAULT_LAYER); + bool addTriangle(ofPoint a, int aR, int aG, int aB, int aA, ofPoint b, int bR, int bG, int bB, int bA, ofPoint c, int cR, int cG, int cB, int cA, int layer=DEFAULT_LAYER); + + //this can only be used if you're in line mode + bool addLine(ofPoint a, ofPoint b, int layer=DEFAULT_LAYER); + bool addLine(ofPoint a, int aR, int aG, int aB, int aA, ofPoint b, int bR, int bG, int bB, int bA, int layer=DEFAULT_LAYER); + + + //handled -- + bool addCircle(float x, float y, float z, float radius, int layer=DEFAULT_LAYER); + bool addElipse(float x, float y, float z, float w, float h, int layer=DEFAULT_LAYER); + + bool addRect(float x, float y, float z, float w, float h, int layer=DEFAULT_LAYER); + bool addCenteredRect(float x, float y, float z, float w, float h, int layer=DEFAULT_LAYER); + + //functional -- + void draw(); + void clear(); + + //features -- + void setColor(int r, int g, int b, int a=255); + void setCircleResolution(int resolution); + void setLayerLineWidth(int layer, int lineWidth); //only relavent in SBR_LINE mode + void setSafeMode(bool safe); + + void setupLayer(int layer); + +protected: + double * verts; + unsigned char * colors; + + int * numObjects; + int * numObjectsLast; + int * layerLineWidth; + + int numLayers; + int numObjectsPerLayer; + + bool bSetupCircle; + + bool safeMode; + + SHAPEMODE_t shapeMode; + + int red; + int green; + int blue; + int alpha; + + int numCirclePts; + float circlePts[OF_MAX_CIRCLE_PTS*2]; + + #if defined(USING_VBO) + vector meshes; + #endif +}; + +#endif diff --git a/libs/openFrameworks/app/ofAppDawnWindow.h b/libs/openFrameworks/app/ofAppDawnWindow.h new file mode 100644 index 00000000000..3af33d95994 --- /dev/null +++ b/libs/openFrameworks/app/ofAppDawnWindow.h @@ -0,0 +1,88 @@ +#pragma once + +#include "ofConstants.h" +#if OF_USE_DAWN && !defined(TARGET_OF_IOS) + +#include "ofAppBaseWindow.h" +#include "ofWindowSettings.h" +#include "ofEvents.h" +#include + +struct GLFWwindow; +class ofWebGPURenderer; + +class ofAppDawnWindow : public ofAppBaseWindow { +public: + ofAppDawnWindow(); + ~ofAppDawnWindow() override; + + ofAppDawnWindow(const ofAppDawnWindow &) = delete; + ofAppDawnWindow & operator=(const ofAppDawnWindow &) = delete; + + static void loop() {} + static bool doesLoop() { return false; } + static bool allowsMultiWindow() { return true; } + static bool needsPolling() { return true; } + static void pollEvents(); + + using ofAppBaseWindow::setup; + void setup(const ofWindowSettings & settings) override; + void setup(const ofDawnWindowSettings & settings); + void update() override; + void draw() override; + void close() override; + + bool getWindowShouldClose() override; + void setWindowShouldClose() override; + + ofCoreEvents & events() override; + std::shared_ptr & renderer() override; + + glm::vec2 getWindowSize() override; + glm::vec2 getScreenSize() override; + glm::vec2 getWindowPosition() override; + int getWidth() override; + int getHeight() override; + + void setWindowTitle(std::string title) override; + void setWindowPosition(int x, int y) override; + void setWindowShape(int w, int h) override; + void setVerticalSync(bool enabled) override; + + void * getWindowContext() override; +#if defined(TARGET_OSX) + void * getCocoaWindow() override; +#endif + void makeCurrent() override; + + GLFWwindow * getGLFWWindow() { return windowP; } + ofDawnWindowSettings getSettings() const { return settings; } + +private: + void attachMetalLayer(); + void resizeSurface(int w, int h); + + static ofAppDawnWindow * setCurrent(GLFWwindow * windowP); + static void mouse_cb(GLFWwindow * windowP_, int button, int state, int mods); + static void motion_cb(GLFWwindow * windowP_, double x, double y); + static void entry_cb(GLFWwindow * windowP_, int entered); + static void keyboard_cb(GLFWwindow * windowP_, int key, int scancode, int action, int mods); + static void resize_cb(GLFWwindow * windowP_, int w, int h); + static void framebuffer_cb(GLFWwindow * windowP_, int w, int h); + static void exit_cb(GLFWwindow * windowP_); + static void scroll_cb(GLFWwindow * windowP_, double x, double y); + static void drop_cb(GLFWwindow * windowP_, int numFiles, const char ** dropString); + static void error_cb(int errorCode, const char * errorDescription); + + ofDawnWindowSettings settings; + std::unique_ptr coreEvents; + std::shared_ptr currentRenderer; + GLFWwindow * windowP = nullptr; + void * metalLayer = nullptr; + int windowW = 0; + int windowH = 0; + float pixelScale = 1.f; + bool bWindowNeedsShowing = true; +}; + +#endif diff --git a/libs/openFrameworks/app/ofAppDawnWindow.mm b/libs/openFrameworks/app/ofAppDawnWindow.mm new file mode 100644 index 00000000000..c151ef64374 --- /dev/null +++ b/libs/openFrameworks/app/ofAppDawnWindow.mm @@ -0,0 +1,403 @@ +#include "ofAppDawnWindow.h" +#if OF_USE_DAWN && !defined(TARGET_OF_IOS) + +#include "ofWebGPURenderer.h" +#include "ofBaseApp.h" +#include "ofLog.h" +#include "ofAppRunner.h" +#include "ofGraphics.h" +#include + +#define GLFW_INCLUDE_NONE +#define GLFW_EXPOSE_NATIVE_COCOA +#include +#include + +#if defined(TARGET_OSX) || defined(TARGET_OF_MAC) +#import +#import +#endif + +#if defined(TARGET_OF_IOS) +#import +#import +#endif + +namespace { + ofAppDawnWindow * currentDawnWindow = nullptr; +} + +void ofAppDawnWindow::error_cb(int errorCode, const char * errorDescription) { + ofLogError("ofAppDawnWindow") << "GLFW " << errorCode << ": " << errorDescription; +} + +ofAppDawnWindow::ofAppDawnWindow() + : coreEvents(new ofCoreEvents) { + glfwSetErrorCallback(error_cb); +} + +ofAppDawnWindow::~ofAppDawnWindow() { + close(); +} + +void ofAppDawnWindow::pollEvents() { + glfwPollEvents(); +} + +void ofAppDawnWindow::setup(const ofWindowSettings & settings) { + const ofDawnWindowSettings * dawn = dynamic_cast(&settings); + if(dawn) { + setup(*dawn); + } else { + setup(ofDawnWindowSettings(settings)); + } +} + +void ofAppDawnWindow::setup(const ofDawnWindowSettings & _settings) { + if(windowP) { + ofLogError("ofAppDawnWindow") << "window already setup"; + return; + } + settings = _settings; + + if(!glfwInit()) { + ofLogError("ofAppDawnWindow") << "glfwInit failed"; + return; + } + + glfwDefaultWindowHints(); + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE); + glfwWindowHint(GLFW_RESIZABLE, settings.resizable ? GLFW_TRUE : GLFW_FALSE); + glfwWindowHint(GLFW_DECORATED, settings.decorated ? GLFW_TRUE : GLFW_FALSE); + + windowW = settings.isSizeSet() ? settings.getWidth() : 1024; + windowH = settings.isSizeSet() ? settings.getHeight() : 768; + const char * title = settings.title.empty() ? "openFrameworks (Dawn)" : settings.title.c_str(); + windowP = glfwCreateWindow(windowW, windowH, title, nullptr, nullptr); + if(!windowP) { + ofLogError("ofAppDawnWindow") << "glfwCreateWindow failed"; + return; + } + + if(settings.isPositionSet()) { + glfwSetWindowPos(windowP, settings.getPosition().x, settings.getPosition().y); + } + + glfwSetWindowUserPointer(windowP, this); + glfwSetMouseButtonCallback(windowP, mouse_cb); + glfwSetCursorPosCallback(windowP, motion_cb); + glfwSetCursorEnterCallback(windowP, entry_cb); + glfwSetKeyCallback(windowP, keyboard_cb); + glfwSetWindowSizeCallback(windowP, resize_cb); + glfwSetFramebufferSizeCallback(windowP, framebuffer_cb); + glfwSetWindowCloseCallback(windowP, exit_cb); + glfwSetScrollCallback(windowP, scroll_cb); + glfwSetDropCallback(windowP, drop_cb); + + attachMetalLayer(); + + auto renderer = std::make_shared(this); + int fbw = 0; + int fbh = 0; + glfwGetFramebufferSize(windowP, &fbw, &fbh); + if(!renderer->setup(metalLayer, fbw, fbh, pixelScale)) { + ofLogError("ofAppDawnWindow") << "Dawn renderer setup failed"; + return; + } + currentRenderer = renderer; + ofSetCurrentRenderer(currentRenderer, true); + + bWindowNeedsShowing = true; + currentDawnWindow = this; +} + +void ofAppDawnWindow::attachMetalLayer() { +#if defined(TARGET_OSX) || defined(TARGET_OF_MAC) + NSWindow * nsWindow = glfwGetCocoaWindow(windowP); + NSView * view = nsWindow.contentView; + view.wantsLayer = YES; + CAMetalLayer * layer = [CAMetalLayer layer]; + layer.contentsScale = nsWindow.backingScaleFactor; + layer.opaque = YES; + layer.framebufferOnly = YES; + view.layer = layer; + int fbw = 0; + int fbh = 0; + glfwGetFramebufferSize(windowP, &fbw, &fbh); + if(fbw > 0 && fbh > 0) { + layer.drawableSize = CGSizeMake(fbw, fbh); + } + metalLayer = (__bridge void *)layer; + pixelScale = nsWindow.backingScaleFactor; + ofLogNotice("ofAppDawnWindow") << "CAMetalLayer attached scale=" << pixelScale; +#else + ofLogError("ofAppDawnWindow") << "desktop Dawn window is macOS-only; use ofxiOSDawnView on iOS"; +#endif +} + +void ofAppDawnWindow::resizeSurface(int w, int h) { + windowW = w; + windowH = h; + int fbw = 0; + int fbh = 0; + if(windowP) { + glfwGetFramebufferSize(windowP, &fbw, &fbh); + } + if(metalLayer && fbw > 0 && fbh > 0) { + ((__bridge CAMetalLayer *)metalLayer).drawableSize = CGSizeMake(fbw, fbh); + } + auto * dawn = dynamic_cast(currentRenderer.get()); + if(dawn) { + dawn->configureSurface(fbw > 0 ? fbw : w, fbh > 0 ? fbh : h); + } +} + +void ofAppDawnWindow::update() { + events().notifyUpdate(); +} + +void ofAppDawnWindow::draw() { + if(bWindowNeedsShowing && windowP) { + glfwShowWindow(windowP); + bWindowNeedsShowing = false; + } + currentRenderer->startRender(); + if(currentRenderer->getBackgroundAuto()) { + currentRenderer->background(currentRenderer->getBackgroundColor()); + } + currentRenderer->setupScreen(); + events().notifyDraw(); + currentRenderer->finishRender(); +} + +void ofAppDawnWindow::close() { + if(windowP) { + glfwSetWindowUserPointer(windowP, nullptr); + glfwDestroyWindow(windowP); + windowP = nullptr; + } + currentRenderer.reset(); + metalLayer = nullptr; + if(currentDawnWindow == this) { + currentDawnWindow = nullptr; + } +} + +bool ofAppDawnWindow::getWindowShouldClose() { + return windowP ? glfwWindowShouldClose(windowP) : true; +} + +void ofAppDawnWindow::setWindowShouldClose() { + if(windowP) { + glfwSetWindowShouldClose(windowP, GL_TRUE); + } +} + +ofCoreEvents & ofAppDawnWindow::events() { + return *coreEvents; +} + +std::shared_ptr & ofAppDawnWindow::renderer() { + return currentRenderer; +} + +glm::vec2 ofAppDawnWindow::getWindowSize() { + float scale = pixelScale > 0.f ? pixelScale : 1.f; + return { (float)windowW * scale, (float)windowH * scale }; +} + +glm::vec2 ofAppDawnWindow::getScreenSize() { + if(!windowP) { + return getWindowSize(); + } + GLFWmonitor * monitor = glfwGetWindowMonitor(windowP); + if(!monitor) { + monitor = glfwGetPrimaryMonitor(); + } + const GLFWvidmode * mode = monitor ? glfwGetVideoMode(monitor) : nullptr; + if(!mode) { + return getWindowSize(); + } + return { (float)mode->width, (float)mode->height }; +} + +glm::vec2 ofAppDawnWindow::getWindowPosition() { + int x = 0; + int y = 0; + if(windowP) { + glfwGetWindowPos(windowP, &x, &y); + } + return { (float)x, (float)y }; +} + +int ofAppDawnWindow::getWidth() { + float scale = pixelScale > 0.f ? pixelScale : 1.f; + return std::max(1, (int)std::lround((float)windowW * scale)); +} + +int ofAppDawnWindow::getHeight() { + float scale = pixelScale > 0.f ? pixelScale : 1.f; + return std::max(1, (int)std::lround((float)windowH * scale)); +} + +void ofAppDawnWindow::setWindowTitle(std::string title) { + settings.title = title; + if(windowP) { + glfwSetWindowTitle(windowP, title.c_str()); + } +} + +void ofAppDawnWindow::setWindowPosition(int x, int y) { + if(windowP) { + glfwSetWindowPos(windowP, x, y); + } +} + +void ofAppDawnWindow::setWindowShape(int w, int h) { + if(windowP) { + glfwSetWindowSize(windowP, w, h); + } + resizeSurface(w, h); +} + +void ofAppDawnWindow::setVerticalSync(bool enabled) { + if(auto dawn = std::dynamic_pointer_cast(currentRenderer)) { + dawn->setPresentVSync(enabled); + } +#if defined(TARGET_OSX) || defined(TARGET_OF_MAC) + if(metalLayer) { + CAMetalLayer * layer = (__bridge CAMetalLayer *)metalLayer; + layer.displaySyncEnabled = enabled ? YES : NO; + } +#endif +} + +void * ofAppDawnWindow::getWindowContext() { + return windowP; +} + +void * ofAppDawnWindow::getCocoaWindow() { +#if defined(TARGET_OSX) || defined(TARGET_OF_MAC) + return windowP ? (__bridge void *)glfwGetCocoaWindow(windowP) : nullptr; +#else + return nullptr; +#endif +} + +void ofAppDawnWindow::makeCurrent() { + currentDawnWindow = this; + if(currentRenderer) { + ofSetCurrentRenderer(currentRenderer, false); + } +} + +ofAppDawnWindow * ofAppDawnWindow::setCurrent(GLFWwindow * windowP_) { + ofAppDawnWindow * window = static_cast(glfwGetWindowUserPointer(windowP_)); + if(window) { + window->makeCurrent(); + } + return window; +} + +void ofAppDawnWindow::mouse_cb(GLFWwindow * windowP_, int button, int state, int mods) { + auto * window = setCurrent(windowP_); + if(!window) { + return; + } + float x = window->events().getMouseX(); + float y = window->events().getMouseY(); + if(state == GLFW_PRESS) { + window->events().notifyMousePressed(x, y, button); + } else { + window->events().notifyMouseReleased(x, y, button); + } + (void)mods; +} + +void ofAppDawnWindow::motion_cb(GLFWwindow * windowP_, double x, double y) { + auto * window = setCurrent(windowP_); + if(!window) { + return; + } + if(glfwGetMouseButton(windowP_, GLFW_MOUSE_BUTTON_LEFT) == GLFW_PRESS) { + window->events().notifyMouseDragged(x, y, 0); + } else { + window->events().notifyMouseMoved(x, y); + } +} + +void ofAppDawnWindow::entry_cb(GLFWwindow * windowP_, int entered) { + auto * window = setCurrent(windowP_); + if(!window) { + return; + } + if(entered) { + window->events().notifyMouseEntered(window->events().getMouseX(), window->events().getMouseY()); + } else { + window->events().notifyMouseExited(window->events().getMouseX(), window->events().getMouseY()); + } +} + +void ofAppDawnWindow::keyboard_cb(GLFWwindow * windowP_, int key, int scancode, int action, int mods) { + auto * window = setCurrent(windowP_); + if(!window) { + return; + } + (void)scancode; + (void)mods; + if(action == GLFW_PRESS || action == GLFW_REPEAT) { + window->events().notifyKeyPressed(key); + } else if(action == GLFW_RELEASE) { + window->events().notifyKeyReleased(key); + } +} + +void ofAppDawnWindow::resize_cb(GLFWwindow * windowP_, int w, int h) { + auto * window = setCurrent(windowP_); + if(!window) { + return; + } + window->resizeSurface(w, h); + window->events().notifyWindowResized(w, h); +} + +void ofAppDawnWindow::framebuffer_cb(GLFWwindow * windowP_, int w, int h) { + auto * window = setCurrent(windowP_); + if(!window) { + return; + } + auto * dawn = dynamic_cast(window->currentRenderer.get()); + if(dawn) { + dawn->configureSurface(w, h); + } +} + +void ofAppDawnWindow::exit_cb(GLFWwindow * windowP_) { + auto * window = setCurrent(windowP_); + if(window) { + window->events().notifyExit(); + } +} + +void ofAppDawnWindow::scroll_cb(GLFWwindow * windowP_, double x, double y) { + auto * window = setCurrent(windowP_); + if(window) { + window->events().notifyMouseScrolled(window->events().getMouseX(), window->events().getMouseY(), x, y); + } +} + +void ofAppDawnWindow::drop_cb(GLFWwindow * windowP_, int numFiles, const char ** dropString) { + auto * window = setCurrent(windowP_); + if(!window) { + return; + } + ofDragInfo info; + for(int i = 0; i < numFiles; ++i) { + info.files.emplace_back(dropString[i]); + } + info.position = { window->events().getMouseX(), window->events().getMouseY() }; + window->events().notifyDragEvent(info); +} + +#endif diff --git a/libs/openFrameworks/app/ofAppMGLWindow.h b/libs/openFrameworks/app/ofAppMGLWindow.h new file mode 100644 index 00000000000..bd077db3018 --- /dev/null +++ b/libs/openFrameworks/app/ofAppMGLWindow.h @@ -0,0 +1,83 @@ +#pragma once + +#include "ofConstants.h" +#if OF_USE_ANGLE && defined(TARGET_OSX) && !defined(TARGET_OF_IOS) + +#include "ofAppBaseWindow.h" +#include "ofWindowSettings.h" +#include "ofEvents.h" +#include + +class ofBaseRenderer; + +/// Desktop window that presents OpenGL ES through MetalANGLE + MGLKit. +class ofAppMGLWindow : public ofAppBaseWindow { +public: + ofAppMGLWindow(); + ~ofAppMGLWindow() override; + + ofAppMGLWindow(const ofAppMGLWindow &) = delete; + ofAppMGLWindow & operator=(const ofAppMGLWindow &) = delete; + + static void loop() {} + static bool doesLoop() { return false; } + static bool allowsMultiWindow() { return true; } + static bool needsPolling() { return true; } + static void pollEvents(); + + void setup(const ofWindowSettings & settings) override; + void update() override; + void draw() override; + void close() override; + + /// Called by the MGLKView delegate. The OF loop calls draw(), which presents the view. + void renderFrame(); + + bool getWindowShouldClose() override; + void setWindowShouldClose() override; + + ofCoreEvents & events() override; + std::shared_ptr & renderer() override; + + glm::vec2 getWindowSize() override; + glm::vec2 getScreenSize() override; + glm::vec2 getWindowPosition() override; + int getWidth() override; + int getHeight() override; + + void setWindowTitle(std::string title) override; + void setWindowPosition(int x, int y) override; + void setWindowShape(int w, int h) override; + void setFullscreen(bool fullscreen) override; + void toggleFullscreen() override; + ofWindowMode getWindowMode() override; + + void enableSetupScreen() override; + void disableSetupScreen() override; + void setVerticalSync(bool enabled) override; + + void * getWindowContext() override; + void * getCocoaWindow() override; + void makeCurrent() override; + + void updateDrawableSize(); + +private: + void showWindow(); + + ofWindowSettings settings; + std::unique_ptr coreEvents; + std::shared_ptr currentRenderer; + void * owner = nullptr; + int windowW = 0; + int windowH = 0; + int glesMajor = 2; + int glesMinor = 0; + float pixelScale = 1.f; + bool bEnableSetupScreen = true; + bool bWindowNeedsShowing = true; + bool bShouldClose = false; + bool bSetup = false; +}; + +#endif diff --git a/libs/openFrameworks/app/ofAppMGLWindow.mm b/libs/openFrameworks/app/ofAppMGLWindow.mm new file mode 100644 index 00000000000..8954f48854f --- /dev/null +++ b/libs/openFrameworks/app/ofAppMGLWindow.mm @@ -0,0 +1,563 @@ +#include "ofAppMGLWindow.h" +#if OF_USE_ANGLE && defined(TARGET_OSX) && !defined(TARGET_OF_IOS) + +#include "ofGLProgrammableRenderer.h" +#include "ofGLRenderer.h" +#include "ofAppRunner.h" +#include "ofGraphics.h" +#include "ofLog.h" +#include + +#import +#import +#import + +@class ofMGLWindowOwner; + +@interface ofMGLKitView : MGLKView +@property (nonatomic, weak) ofMGLWindowOwner * owner; +@end + +@interface ofMGLWindowOwner : NSObject +@property (nonatomic, strong) NSWindow * window; +@property (nonatomic, strong) ofMGLKitView * glView; +@property (nonatomic, strong) MGLContext * context; +@property (nonatomic, assign) ofAppMGLWindow * cppWindow; +- (void)noteResize; +- (glm::vec2)pointForEvent:(NSEvent *)event; +- (void)mouseDown:(NSEvent *)event; +- (void)mouseUp:(NSEvent *)event; +- (void)mouseDragged:(NSEvent *)event; +- (void)mouseMoved:(NSEvent *)event; +- (void)rightMouseDown:(NSEvent *)event; +- (void)rightMouseUp:(NSEvent *)event; +- (void)rightMouseDragged:(NSEvent *)event; +- (void)otherMouseDown:(NSEvent *)event; +- (void)otherMouseUp:(NSEvent *)event; +- (void)otherMouseDragged:(NSEvent *)event; +- (void)scrollWheel:(NSEvent *)event; +- (void)keyEvent:(NSEvent *)event pressed:(BOOL)pressed; +@end + +namespace { + +int ofKeyFromEvent(NSEvent * event) { + switch (event.keyCode) { + case 53: return OF_KEY_ESC; + case 36: return OF_KEY_RETURN; + case 48: return OF_KEY_TAB; + case 51: return OF_KEY_BACKSPACE; + case 117: return OF_KEY_DEL; + case 123: return OF_KEY_LEFT; + case 124: return OF_KEY_RIGHT; + case 125: return OF_KEY_DOWN; + case 126: return OF_KEY_UP; + case 115: return OF_KEY_HOME; + case 119: return OF_KEY_END; + case 116: return OF_KEY_PAGE_UP; + case 121: return OF_KEY_PAGE_DOWN; + default: break; + } + NSString * chars = event.charactersIgnoringModifiers; + if (chars.length > 0) { + unichar c = [chars characterAtIndex:0]; + if (c == 27) { + return OF_KEY_ESC; + } + if (c == 127 || c == 8) { + return OF_KEY_BACKSPACE; + } + if (c >= 32 && c < 127) { + return (int)c; + } + } + static const int fKeys[] = { 122, 120, 99, 118, 96, 97, 98, 100, 101, 109, 103, 111 }; + static const int fOf[] = { + OF_KEY_F1, OF_KEY_F2, OF_KEY_F3, OF_KEY_F4, OF_KEY_F5, OF_KEY_F6, + OF_KEY_F7, OF_KEY_F8, OF_KEY_F9, OF_KEY_F10, OF_KEY_F11, OF_KEY_F12 + }; + for (int i = 0; i < 12; ++i) { + if (event.keyCode == fKeys[i]) { + return fOf[i]; + } + } + return 0; +} + +int buttonForType(NSEventType type) { + switch (type) { + case NSEventTypeRightMouseDown: + case NSEventTypeRightMouseUp: + case NSEventTypeRightMouseDragged: + return OF_MOUSE_BUTTON_RIGHT; + case NSEventTypeOtherMouseDown: + case NSEventTypeOtherMouseUp: + case NSEventTypeOtherMouseDragged: + return OF_MOUSE_BUTTON_MIDDLE; + default: + return OF_MOUSE_BUTTON_LEFT; + } +} + +void glesVersionFromSettings(const ofWindowSettings & settings, int & major, int & minor) { + major = 2; + minor = 0; + if (const auto * gles = dynamic_cast(&settings)) { + major = gles->glesVersion; + minor = gles->glesVersionMinor; + } else if (const auto * gl = dynamic_cast(&settings)) { + major = gl->glVersionMajor; + if (major >= 3) { + major = 3; + } else if (major <= 1) { + major = 1; + } else { + major = 2; + } + minor = 0; + } +#if defined(OF_TEST_GLES_VERSION) + major = OF_TEST_GLES_VERSION; +#endif +#if defined(OF_TEST_GLES_MINOR) + minor = OF_TEST_GLES_MINOR; +#endif +} + +} // namespace + +@implementation ofMGLKitView + +- (BOOL)acceptsFirstResponder { + return YES; +} + +- (void)layout { + [super layout]; + [self.owner noteResize]; +} + +- (void)mouseDown:(NSEvent *)event { [self.owner mouseDown:event]; } +- (void)mouseUp:(NSEvent *)event { [self.owner mouseUp:event]; } +- (void)mouseDragged:(NSEvent *)event { [self.owner mouseDragged:event]; } +- (void)mouseMoved:(NSEvent *)event { [self.owner mouseMoved:event]; } +- (void)rightMouseDown:(NSEvent *)event { [self.owner rightMouseDown:event]; } +- (void)rightMouseUp:(NSEvent *)event { [self.owner rightMouseUp:event]; } +- (void)rightMouseDragged:(NSEvent *)event { [self.owner rightMouseDragged:event]; } +- (void)otherMouseDown:(NSEvent *)event { [self.owner otherMouseDown:event]; } +- (void)otherMouseUp:(NSEvent *)event { [self.owner otherMouseUp:event]; } +- (void)otherMouseDragged:(NSEvent *)event { [self.owner otherMouseDragged:event]; } +- (void)scrollWheel:(NSEvent *)event { [self.owner scrollWheel:event]; } +- (void)keyDown:(NSEvent *)event { [self.owner keyEvent:event pressed:YES]; } +- (void)keyUp:(NSEvent *)event { [self.owner keyEvent:event pressed:NO]; } + +@end + +@implementation ofMGLWindowOwner + +- (glm::vec2)pointForEvent:(NSEvent *)event { + if (!self.glView || !self.cppWindow) { + return { 0.f, 0.f }; + } + NSPoint p = [self.glView convertPoint:event.locationInWindow fromView:nil]; + const float scale = self.glView.layer.contentsScale > 0.f ? self.glView.layer.contentsScale : 1.f; + const float height = self.glView.bounds.size.height; + return { (float)p.x * scale, (height - (float)p.y) * scale }; +} + +- (void)noteResize { + if (self.cppWindow) { + const int previousW = self.cppWindow->getWidth(); + const int previousH = self.cppWindow->getHeight(); + self.cppWindow->updateDrawableSize(); + if (previousW != self.cppWindow->getWidth() || previousH != self.cppWindow->getHeight()) { + self.cppWindow->events().notifyWindowResized(self.cppWindow->getWidth(), self.cppWindow->getHeight()); + } + } +} + +- (void)mouseDown:(NSEvent *)event { + if (!self.cppWindow) return; + glm::vec2 p = [self pointForEvent:event]; + self.cppWindow->events().notifyMousePressed(p.x, p.y, buttonForType(event.type)); +} +- (void)mouseUp:(NSEvent *)event { + if (!self.cppWindow) return; + glm::vec2 p = [self pointForEvent:event]; + self.cppWindow->events().notifyMouseReleased(p.x, p.y, buttonForType(event.type)); +} +- (void)mouseDragged:(NSEvent *)event { + if (!self.cppWindow) return; + glm::vec2 p = [self pointForEvent:event]; + self.cppWindow->events().notifyMouseDragged(p.x, p.y, buttonForType(event.type)); +} +- (void)mouseMoved:(NSEvent *)event { + if (!self.cppWindow) return; + glm::vec2 p = [self pointForEvent:event]; + self.cppWindow->events().notifyMouseMoved(p.x, p.y); +} +- (void)rightMouseDown:(NSEvent *)event { [self mouseDown:event]; } +- (void)rightMouseUp:(NSEvent *)event { [self mouseUp:event]; } +- (void)rightMouseDragged:(NSEvent *)event { [self mouseDragged:event]; } +- (void)otherMouseDown:(NSEvent *)event { [self mouseDown:event]; } +- (void)otherMouseUp:(NSEvent *)event { [self mouseUp:event]; } +- (void)otherMouseDragged:(NSEvent *)event { [self mouseDragged:event]; } + +- (void)scrollWheel:(NSEvent *)event { + if (!self.cppWindow) return; + glm::vec2 p = [self pointForEvent:event]; + self.cppWindow->events().notifyMouseScrolled(p.x, p.y, event.scrollingDeltaX, event.scrollingDeltaY); +} + +- (void)keyEvent:(NSEvent *)event pressed:(BOOL)pressed { + if (!self.cppWindow) return; + const int key = ofKeyFromEvent(event); + if (key == 0) return; + if (pressed) { + self.cppWindow->events().notifyKeyPressed(key); + } else { + self.cppWindow->events().notifyKeyReleased(key); + } +} + +- (void)mglkView:(MGLKView *)view drawInRect:(CGRect)rect { + (void)view; + (void)rect; + if (self.cppWindow) { + self.cppWindow->renderFrame(); + } +} + +- (BOOL)windowShouldClose:(NSWindow *)sender { + (void)sender; + if (self.cppWindow) { + self.cppWindow->setWindowShouldClose(); + } + return NO; +} + +@end + +ofAppMGLWindow::ofAppMGLWindow() + : coreEvents(new ofCoreEvents) { +} + +ofAppMGLWindow::~ofAppMGLWindow() { + close(); +} + +void ofAppMGLWindow::pollEvents() { + @autoreleasepool { + while (NSEvent * event = [NSApp nextEventMatchingMask:NSEventMaskAny + untilDate:[NSDate distantPast] + inMode:NSDefaultRunLoopMode + dequeue:YES]) { + [NSApp sendEvent:event]; + } + } +} + +void ofAppMGLWindow::setup(const ofWindowSettings & _settings) { + if (bSetup) { + ofLogError("ofAppMGLWindow") << "window already setup"; + return; + } + settings = _settings; + // settings is ofWindowSettings, so the copy drops ofGLWindowSettings. + // Read the version from the caller's object. + glesVersionFromSettings(_settings, glesMajor, glesMinor); + + [NSApplication sharedApplication]; + [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; + if (![[NSRunningApplication currentApplication] isFinishedLaunching]) { + [NSApp finishLaunching]; + } + + const int width = settings.isSizeSet() ? settings.getWidth() : 1024; + const int height = settings.isSizeSet() ? settings.getHeight() : 768; + NSRect frame = NSMakeRect(0, 0, width, height); + NSWindowStyleMask style = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable; + + ofMGLWindowOwner * windowOwner = [[ofMGLWindowOwner alloc] init]; + windowOwner.cppWindow = this; + windowOwner.window = [[NSWindow alloc] initWithContentRect:frame + styleMask:style + backing:NSBackingStoreBuffered + defer:NO]; + NSString * title = settings.title.empty() ? @"openFrameworks (ANGLE)" : [NSString stringWithUTF8String:settings.title.c_str()]; + windowOwner.window.title = title; + windowOwner.window.delegate = windowOwner; + if (settings.isPositionSet()) { + [windowOwner.window setFrameOrigin:NSMakePoint(settings.getPosition().x, settings.getPosition().y)]; + } else { + [windowOwner.window center]; + } + + MGLRenderingAPI api = kMGLRenderingAPIOpenGLES2; + if (glesMajor <= 1) { + api = kMGLRenderingAPIOpenGLES1; + } else if (glesMajor >= 3) { + api = kMGLRenderingAPIOpenGLES3; + } + windowOwner.context = [[MGLContext alloc] initWithAPI:api]; + if (!windowOwner.context) { + ofLogError("ofAppMGLWindow") << "MGLContext initWithAPI failed"; + return; + } + + windowOwner.glView = [[ofMGLKitView alloc] initWithFrame:frame context:windowOwner.context]; + windowOwner.glView.owner = windowOwner; + windowOwner.glView.delegate = windowOwner; + windowOwner.glView.drawableColorFormat = MGLDrawableColorFormatRGBA8888; + windowOwner.glView.drawableDepthFormat = MGLDrawableDepthFormat24; + pixelScale = settings.isHiDPI() ? windowOwner.window.backingScaleFactor : 1.f; + if (pixelScale < 1.f) { + pixelScale = 1.f; + } + windowOwner.glView.wantsLayer = YES; + windowOwner.glView.layer.contentsScale = pixelScale; + windowOwner.window.contentView = windowOwner.glView; + + NSTrackingArea * tracking = [[NSTrackingArea alloc] initWithRect:windowOwner.glView.bounds + options:NSTrackingMouseMoved | NSTrackingActiveInKeyWindow | NSTrackingInVisibleRect + owner:windowOwner.glView + userInfo:nil]; + [windowOwner.glView addTrackingArea:tracking]; + + owner = (__bridge_retained void *)windowOwner; + updateDrawableSize(); + + if (glesMajor >= 2) { + currentRenderer = std::make_shared(this); + } else { + currentRenderer = std::make_shared(this); + } + makeCurrent(); + [windowOwner.glView bindDrawable]; + if (glesMajor >= 2) { + static_cast(currentRenderer.get())->setup(glesMajor, glesMinor); + } else { + static_cast(currentRenderer.get())->setup(); + } + ofSetCurrentRenderer(currentRenderer, true); + + const char * version = reinterpret_cast(glGetString(GL_VERSION)); + const char * rendererName = reinterpret_cast(glGetString(GL_RENDERER)); + ofLogNotice("ofAppMGLWindow") << "ANGLE context ready " << (version ? version : "?") + << " renderer " << (rendererName ? rendererName : "?") + << " " << windowW << "x" << windowH << " scale=" << pixelScale; + + bSetup = true; + bWindowNeedsShowing = true; +} + +void ofAppMGLWindow::updateDrawableSize() { + ofMGLWindowOwner * windowOwner = (__bridge ofMGLWindowOwner *)owner; + if (!windowOwner.glView) { + return; + } + CGSize drawable = windowOwner.glView.drawableSize; + if (drawable.width < 1.0 || drawable.height < 1.0) { + drawable = CGSizeMake(windowOwner.glView.bounds.size.width * pixelScale, + windowOwner.glView.bounds.size.height * pixelScale); + } + windowW = std::max(1, (int)std::lround(drawable.width)); + windowH = std::max(1, (int)std::lround(drawable.height)); +} + +void ofAppMGLWindow::showWindow() { + ofMGLWindowOwner * windowOwner = (__bridge ofMGLWindowOwner *)owner; + if (!windowOwner.window) { + return; + } + [windowOwner.window makeKeyAndOrderFront:nil]; + [windowOwner.window makeFirstResponder:windowOwner.glView]; + [NSApp activate]; + if (settings.windowMode == OF_FULLSCREEN) { + [windowOwner.window toggleFullScreen:nil]; + } + bWindowNeedsShowing = false; +} + +void ofAppMGLWindow::update() { + events().notifyUpdate(); +} + +void ofAppMGLWindow::draw() { + if (bWindowNeedsShowing) { + showWindow(); + } + ofMGLWindowOwner * windowOwner = (__bridge ofMGLWindowOwner *)owner; + if (windowOwner.glView) { + [windowOwner.glView display]; + } +} + +void ofAppMGLWindow::renderFrame() { + if (!currentRenderer) { + return; + } + updateDrawableSize(); + makeCurrent(); + currentRenderer->startRender(); + if (currentRenderer->getBackgroundAuto()) { + currentRenderer->background(currentRenderer->getBackgroundColor()); + } + if (bEnableSetupScreen) { + currentRenderer->setupScreen(); + } + events().notifyDraw(); + currentRenderer->finishRender(); +} + +void ofAppMGLWindow::close() { + if (!owner) { + currentRenderer.reset(); + return; + } + ofMGLWindowOwner * windowOwner = (__bridge_transfer ofMGLWindowOwner *)owner; + owner = nullptr; + windowOwner.cppWindow = nil; + [windowOwner.window orderOut:nil]; + windowOwner.window.delegate = nil; + windowOwner.window = nil; + windowOwner.glView = nil; + windowOwner.context = nil; + currentRenderer.reset(); +} + +bool ofAppMGLWindow::getWindowShouldClose() { + return bShouldClose || owner == nullptr; +} + +void ofAppMGLWindow::setWindowShouldClose() { + bShouldClose = true; +} + +ofCoreEvents & ofAppMGLWindow::events() { + return *coreEvents; +} + +std::shared_ptr & ofAppMGLWindow::renderer() { + return currentRenderer; +} + +glm::vec2 ofAppMGLWindow::getWindowSize() { + return { (float)windowW, (float)windowH }; +} + +glm::vec2 ofAppMGLWindow::getScreenSize() { + NSScreen * screen = [NSScreen mainScreen]; + ofMGLWindowOwner * windowOwner = (__bridge ofMGLWindowOwner *)owner; + if (windowOwner.window.screen) { + screen = windowOwner.window.screen; + } + if (!screen) { + return getWindowSize(); + } + return { (float)screen.frame.size.width * pixelScale, (float)screen.frame.size.height * pixelScale }; +} + +glm::vec2 ofAppMGLWindow::getWindowPosition() { + ofMGLWindowOwner * windowOwner = (__bridge ofMGLWindowOwner *)owner; + if (!windowOwner.window) { + return { 0.f, 0.f }; + } + NSRect frame = windowOwner.window.frame; + return { (float)frame.origin.x, (float)frame.origin.y }; +} + +int ofAppMGLWindow::getWidth() { + return std::max(1, windowW); +} + +int ofAppMGLWindow::getHeight() { + return std::max(1, windowH); +} + +void ofAppMGLWindow::setWindowTitle(std::string title) { + settings.title = title; + ofMGLWindowOwner * windowOwner = (__bridge ofMGLWindowOwner *)owner; + if (windowOwner.window) { + windowOwner.window.title = [NSString stringWithUTF8String:title.c_str()]; + } +} + +void ofAppMGLWindow::setWindowPosition(int x, int y) { + ofMGLWindowOwner * windowOwner = (__bridge ofMGLWindowOwner *)owner; + if (windowOwner.window) { + [windowOwner.window setFrameOrigin:NSMakePoint(x, y)]; + } +} + +void ofAppMGLWindow::setWindowShape(int w, int h) { + ofMGLWindowOwner * windowOwner = (__bridge ofMGLWindowOwner *)owner; + if (!windowOwner.window) { + return; + } + NSRect frame = windowOwner.window.frame; + frame.size = NSMakeSize(w, h); + [windowOwner.window setFrame:frame display:YES]; + updateDrawableSize(); +} + +void ofAppMGLWindow::setFullscreen(bool fullscreen) { + ofMGLWindowOwner * windowOwner = (__bridge ofMGLWindowOwner *)owner; + if (!windowOwner.window) { + settings.windowMode = fullscreen ? OF_FULLSCREEN : OF_WINDOW; + return; + } + const bool isFullscreen = (windowOwner.window.styleMask & NSWindowStyleMaskFullScreen) != 0; + if (isFullscreen != fullscreen) { + [windowOwner.window toggleFullScreen:nil]; + } + settings.windowMode = fullscreen ? OF_FULLSCREEN : OF_WINDOW; +} + +void ofAppMGLWindow::toggleFullscreen() { + setFullscreen(getWindowMode() != OF_FULLSCREEN); +} + +ofWindowMode ofAppMGLWindow::getWindowMode() { + return settings.windowMode; +} + +void ofAppMGLWindow::enableSetupScreen() { + bEnableSetupScreen = true; +} + +void ofAppMGLWindow::disableSetupScreen() { + bEnableSetupScreen = false; +} + +void ofAppMGLWindow::setVerticalSync(bool enabled) { + ofMGLWindowOwner * windowOwner = (__bridge ofMGLWindowOwner *)owner; + CALayer * layer = windowOwner.glView.glLayer; + for(CALayer * sublayer in layer.sublayers) { + if([sublayer isKindOfClass:[CAMetalLayer class]]) { + ((CAMetalLayer *)sublayer).displaySyncEnabled = enabled ? YES : NO; + } + } +} + +void * ofAppMGLWindow::getWindowContext() { + ofMGLWindowOwner * windowOwner = (__bridge ofMGLWindowOwner *)owner; + return (__bridge void *)windowOwner.context; +} + +void * ofAppMGLWindow::getCocoaWindow() { + ofMGLWindowOwner * windowOwner = (__bridge ofMGLWindowOwner *)owner; + return (__bridge void *)windowOwner.window; +} + +void ofAppMGLWindow::makeCurrent() { + ofMGLWindowOwner * windowOwner = (__bridge ofMGLWindowOwner *)owner; + if (windowOwner.context) { + [MGLContext setCurrentContext:windowOwner.context forLayer:windowOwner.glView.glLayer]; + } + if (currentRenderer) { + ofSetCurrentRenderer(currentRenderer, false); + } +} + +#endif diff --git a/libs/openFrameworks/app/ofAppRunner.cpp b/libs/openFrameworks/app/ofAppRunner.cpp index ed2f10a396a..ac427ead985 100644 --- a/libs/openFrameworks/app/ofAppRunner.cpp +++ b/libs/openFrameworks/app/ofAppRunner.cpp @@ -14,10 +14,17 @@ using std::shared_ptr; #if !defined(TARGET_NODISPLAY) #if !defined( TARGET_OF_IOS ) & !defined(TARGET_ANDROID) & !defined(TARGET_EMSCRIPTEN) & !defined(TARGET_RASPBERRY_PI_LEGACY) + #if OF_USE_DAWN + #include "ofAppDawnWindow.h" + #elif OF_USE_ANGLE + #include "ofAppMGLWindow.h" + #else #include "ofAppGLFWWindow.h" + #endif //special case so we preserve supplied settngs //TODO: remove me when we remove the ofAppGLFWWindow setters. //-------------------------------------- +#if !OF_USE_DAWN && !OF_USE_ANGLE void ofSetupOpenGL(const shared_ptr & windowPtr, int w, int h, ofWindowMode screenMode){ ofInit(); auto settings = windowPtr->getSettings(); @@ -27,6 +34,7 @@ using std::shared_ptr; windowPtr->setup(settings); } #endif + #endif #endif #ifdef TARGET_LINUX diff --git a/libs/openFrameworks/app/ofAppRunner.h b/libs/openFrameworks/app/ofAppRunner.h index 86f4f01e1e8..6ec46a0216e 100644 --- a/libs/openFrameworks/app/ofAppRunner.h +++ b/libs/openFrameworks/app/ofAppRunner.h @@ -1,11 +1,14 @@ #pragma once +#include "ofConstants.h" #include "ofMainLoop.h" #include "ofWindowSettings.h" class ofRectangle; class ofAppBaseWindow; +#if !OF_USE_DAWN && !OF_USE_ANGLE class ofAppGLFWWindow; +#endif class ofBaseApp; class ofBaseRenderer; class ofCoreEvents; @@ -26,9 +29,11 @@ void ofSetupOpenGL(const std::shared_ptr & windowPtr, int w, int h, ofWi windowPtr->setup(settings); } +#if !OF_USE_DAWN && !OF_USE_ANGLE //special case so we preserve supplied settngs //TODO: remove me when we remove the ofSetupOpenGL legacy approach. void ofSetupOpenGL(const std::shared_ptr & windowPtr, int w, int h, ofWindowMode screenMode); +#endif template static void noopDeleter(Window *) { } diff --git a/libs/openFrameworks/app/ofMainLoop.cpp b/libs/openFrameworks/app/ofMainLoop.cpp index 8e36fbae9d0..f2d63d976fa 100644 --- a/libs/openFrameworks/app/ofMainLoop.cpp +++ b/libs/openFrameworks/app/ofMainLoop.cpp @@ -7,7 +7,7 @@ #include "ofMainLoop.h" #include "ofBaseApp.h" -// #include "ofConstants.h" +#include "ofConstants.h" //======================================================================== // default windowing @@ -26,7 +26,13 @@ #elif defined(TARGET_EMSCRIPTEN) #include "ofxAppEmscriptenWindow.h" #else + #if OF_USE_DAWN + #include "ofAppDawnWindow.h" + #elif OF_USE_ANGLE + #include "ofAppMGLWindow.h" + #else #include "ofAppGLFWWindow.h" + #endif #endif ofMainLoop::ofMainLoop() @@ -55,6 +61,10 @@ std::shared_ptr ofMainLoop::createWindow(const ofWindowSettings std::shared_ptr window = std::make_shared(); #elif defined(TARGET_EMSCRIPTEN) std::shared_ptr window = std::make_shared(); + #elif OF_USE_DAWN + std::shared_ptr window = std::make_shared(); + #elif OF_USE_ANGLE + std::shared_ptr window = std::make_shared(); #elif defined(TARGET_OPENGLES) std::shared_ptr window = std::make_shared(); #else diff --git a/libs/openFrameworks/app/ofWindowSettings.h b/libs/openFrameworks/app/ofWindowSettings.h index dd14368e026..9c2df525574 100644 --- a/libs/openFrameworks/app/ofWindowSettings.h +++ b/libs/openFrameworks/app/ofWindowSettings.h @@ -166,3 +166,22 @@ class ofGLESWindowSettings: public ofWindowSettings{ int glesVersion; int glesVersionMinor; }; + +class ofDawnWindowSettings: public ofWindowSettings{ +public: + ofDawnWindowSettings() = default; + + ofDawnWindowSettings(const ofWindowSettings & settings) + :ofWindowSettings(settings){ + const ofDawnWindowSettings * dawnSettings = dynamic_cast(&settings); + if(dawnSettings){ + vsync = dawnSettings->vsync; + resizable = dawnSettings->resizable; + decorated = dawnSettings->decorated; + } + } + + bool vsync = true; + bool resizable = true; + bool decorated = true; +}; diff --git a/libs/openFrameworks/gl/ofTexture.cpp b/libs/openFrameworks/gl/ofTexture.cpp index 15df2c9d1ac..b8b56cf0065 100644 --- a/libs/openFrameworks/gl/ofTexture.cpp +++ b/libs/openFrameworks/gl/ofTexture.cpp @@ -839,6 +839,14 @@ void ofTexture::setAnchorPoint(float x, float y){ } //---------------------------------------------------------- +glm::vec2 ofTexture::getAnchor() const{ + return {anchor.x, anchor.y}; +} + +bool ofTexture::isAnchorPercent() const{ + return bAnchorIsPct; +} + void ofTexture::resetAnchor(){ anchor = {0.f, 0.f, 0.f}; bAnchorIsPct = false; diff --git a/libs/openFrameworks/gl/ofTexture.h b/libs/openFrameworks/gl/ofTexture.h index d9f10cd2978..5fd806ee79b 100644 --- a/libs/openFrameworks/gl/ofTexture.h +++ b/libs/openFrameworks/gl/ofTexture.h @@ -776,6 +776,8 @@ class ofTexture : public ofBaseDraws { /// \param x Horizontal texture position in pixels. /// \param y Vertical texture position in pixels. void setAnchorPoint(float x, float y); + glm::vec2 getAnchor() const; + bool isAnchorPercent() const; /// \brief Reset the anchor point to (0, 0). void resetAnchor(); diff --git a/libs/openFrameworks/gl/ofVboMesh.cpp b/libs/openFrameworks/gl/ofVboMesh.cpp index 96d15abf5ae..ad06817d347 100644 --- a/libs/openFrameworks/gl/ofVboMesh.cpp +++ b/libs/openFrameworks/gl/ofVboMesh.cpp @@ -1,5 +1,8 @@ #include "ofVboMesh.h" #include "ofGLBaseTypes.h" +#include "ofGLUtils.h" +#include "ofAppRunner.h" +#include "ofLog.h" #ifdef TARGET_ANDROID #include "ofxAndroidUtils.h" @@ -115,11 +118,24 @@ const ofVbo & ofVboMesh::getVbo() const{ void ofVboMesh::drawInstanced(ofPolyRenderMode drawMode, int primCount) const{ if(getNumVertices()==0) return; + if(!ofGetCurrentRenderer() || !ofGetGLRenderer()){ + static bool logged = false; + if(!logged){ + ofLogVerbose("ofVboMesh") << "drawInstanced has no GL renderer; drawing the mesh once"; + logged = true; + } + ofMesh::draw(drawMode); + return; + } ofGetGLRenderer()->drawInstanced(*this,drawMode,primCount); } void ofVboMesh::draw(ofPolyRenderMode drawMode) const{ if(getNumVertices()==0) return; + if(!ofGetCurrentRenderer() || !ofGetGLRenderer()){ + ofMesh::draw(drawMode); + return; + } drawInstanced(drawMode, 1); } diff --git a/libs/openFrameworks/graphics/ofBitmapFont.cpp b/libs/openFrameworks/graphics/ofBitmapFont.cpp index e4f31082c98..9e7bc2db138 100644 --- a/libs/openFrameworks/graphics/ofBitmapFont.cpp +++ b/libs/openFrameworks/graphics/ofBitmapFont.cpp @@ -468,6 +468,11 @@ void ofBitmapFont::unloadTexture(){ texture.clear(); } +const ofPixels & ofBitmapFont::getAtlasPixels() const{ + init(); + return pixels; +} + const ofTexture & ofBitmapFont::getTexture() const{ if(!texture.isAllocated()){ ofBitmapFont::init(); diff --git a/libs/openFrameworks/graphics/ofBitmapFont.h b/libs/openFrameworks/graphics/ofBitmapFont.h index 55f4432dabf..ece3ea718ca 100644 --- a/libs/openFrameworks/graphics/ofBitmapFont.h +++ b/libs/openFrameworks/graphics/ofBitmapFont.h @@ -22,6 +22,7 @@ class ofBitmapFont{ ~ofBitmapFont(); ofMesh getMesh(const std::string & text, int x, int y, ofDrawBitmapMode mode=OF_BITMAPMODE_MODEL_BILLBOARD, bool vFlipped=true) const; const ofTexture & getTexture() const; + const ofPixels & getAtlasPixels() const; ofRectangle getBoundingBox(const std::string & text, int x, int y, ofDrawBitmapMode mode = ofGetStyle().drawBitmapMode, bool vFlipped = ofIsVFlipped()) const; private: static void init(); diff --git a/libs/openFrameworks/graphics/ofImage.cpp b/libs/openFrameworks/graphics/ofImage.cpp index bff7371eec7..b7b52d47d54 100644 --- a/libs/openFrameworks/graphics/ofImage.cpp +++ b/libs/openFrameworks/graphics/ofImage.cpp @@ -980,7 +980,11 @@ void ofImage_::allocate(int w, int h, ofImageType newType){ pixels.allocate(w, h, newType); // take care of texture allocation -- +#if OF_USE_DAWN + if (pixels.isAllocated() && bUseTexture && ofGetCurrentRenderer() && ofGetGLRenderer()){ +#else if (pixels.isAllocated() && bUseTexture){ +#endif tex.allocate(pixels); } @@ -1147,7 +1151,11 @@ void ofImage_::update(){ height = pixels.getHeight(); bpp = pixels.getBitsPerPixel(); type = pixels.getImageType(); +#if OF_USE_DAWN + if (pixels.isAllocated() && bUseTexture && ofGetCurrentRenderer() && ofGetGLRenderer()){ +#else if (pixels.isAllocated() && bUseTexture){ +#endif int glInternalFormat = ofGetGLInternalFormat(pixels); if(!tex.isAllocated() || tex.getWidth() != width || tex.getHeight() != height || tex.getTextureData().glInternalFormat != glInternalFormat){ tex.allocate(pixels); diff --git a/libs/openFrameworks/graphics/ofTrueTypeFont.cpp b/libs/openFrameworks/graphics/ofTrueTypeFont.cpp index d4309b7675d..af52edb25be 100644 --- a/libs/openFrameworks/graphics/ofTrueTypeFont.cpp +++ b/libs/openFrameworks/graphics/ofTrueTypeFont.cpp @@ -500,6 +500,7 @@ ofTrueTypeFont::ofTrueTypeFont(const ofTrueTypeFont& mom) settings = mom.settings; glyphIndexMap = mom.glyphIndexMap; texAtlas = mom.texAtlas; + atlasPixels = mom.atlasPixels; face = mom.face; } @@ -532,6 +533,7 @@ ofTrueTypeFont & ofTrueTypeFont::operator=(const ofTrueTypeFont& mom){ settings = mom.settings; glyphIndexMap = mom.glyphIndexMap; texAtlas = mom.texAtlas; + atlasPixels = mom.atlasPixels; face = mom.face; return *this; @@ -565,6 +567,7 @@ ofTrueTypeFont::ofTrueTypeFont(ofTrueTypeFont&& mom) settings = mom.settings; glyphIndexMap = std::move(mom.glyphIndexMap); texAtlas = mom.texAtlas; + atlasPixels = std::move(mom.atlasPixels); face = mom.face; } @@ -596,6 +599,7 @@ ofTrueTypeFont & ofTrueTypeFont::operator=(ofTrueTypeFont&& mom){ settings = mom.settings; glyphIndexMap = std::move(mom.glyphIndexMap); texAtlas = mom.texAtlas; + atlasPixels = std::move(mom.atlasPixels); face = mom.face; return *this; } @@ -604,6 +608,7 @@ ofTrueTypeFont & ofTrueTypeFont::operator=(ofTrueTypeFont&& mom){ void ofTrueTypeFont::unloadTextures(){ if(!bLoadedOk) return; texAtlas.clear(); + atlasPixels.clear(); } //----------------------------------------------------------- @@ -867,6 +872,11 @@ bool ofTrueTypeFont::load(const ofTrueTypeFontSettings & _settings){ x+= glyph.tW + border*2.0; } +#if OF_USE_DAWN + atlasPixels = atlasPixelsLuminanceAlpha; + bLoadedOk = true; + return true; +#else int maxSize; glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxSize); if(w > maxSize || h > maxSize){ @@ -885,6 +895,7 @@ bool ofTrueTypeFont::load(const ofTrueTypeFontSettings & _settings){ bLoadedOk = true; return true; } +#endif } //----------------------------------------------------------- @@ -1253,6 +1264,10 @@ const ofTexture & ofTrueTypeFont::getFontTexture() const{ return texAtlas; } +const ofPixels & ofTrueTypeFont::getFontAtlasPixels() const{ + return atlasPixels; +} + //----------------------------------------------------------- glm::vec2 ofTrueTypeFont::getFirstGlyphPosForTexture(const string & str, bool vflip) const{ if(!str.empty()){ diff --git a/libs/openFrameworks/graphics/ofTrueTypeFont.h b/libs/openFrameworks/graphics/ofTrueTypeFont.h index 74c941a39e7..fee8267fe9a 100644 --- a/libs/openFrameworks/graphics/ofTrueTypeFont.h +++ b/libs/openFrameworks/graphics/ofTrueTypeFont.h @@ -363,6 +363,7 @@ class ofTrueTypeFont { std::vector getStringAsPoints(const std::string & str, bool vflip = true, bool filled = true) const; const ofMesh & getStringMesh(const std::string & s, float x, float y, bool vflip = true) const; const ofTexture & getFontTexture() const; + const ofPixels & getFontAtlasPixels() const; // FIXME: Maybe return a const & so texture is not copied? ofTexture getStringTexture(const std::string & s, bool vflip = true) const; glm::vec2 getFirstGlyphPosForTexture(const std::string & str, bool vflip) const; @@ -436,6 +437,7 @@ class ofTrueTypeFont { size_t indexForGlyph(uint32_t glyph) const; ofTexture texAtlas; + ofPixels atlasPixels; mutable ofMesh stringQuads; /// \endcond diff --git a/libs/openFrameworks/graphics/ofWebGPURenderer.cpp b/libs/openFrameworks/graphics/ofWebGPURenderer.cpp new file mode 100644 index 00000000000..396b28ffc9f --- /dev/null +++ b/libs/openFrameworks/graphics/ofWebGPURenderer.cpp @@ -0,0 +1,1658 @@ +#include "ofWebGPURenderer.h" +#if OF_USE_DAWN + +#include "ofAppBaseWindow.h" +#include "ofCamera.h" +#include "ofLog.h" +#include "ofNode.h" +#include "ofPath.h" +#include "ofTrueTypeFont.h" +#include "ofImage.h" +#include "of3dPrimitives.h" +#include "ofMesh.h" +#include "ofVideoBaseTypes.h" + +#include +#include "ofPolyline.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +const std::string ofWebGPURenderer::TYPE = "WebGPU"; + +ofWebGPURenderer::ofWebGPURenderer(ofAppBaseWindow * window) + : window_(window) + , matrixStack(window) + , graphics3d(this) { + currentStyle = ofStyle(); +} + +ofWebGPURenderer::~ofWebGPURenderer() { + shutdown(); +} + +void ofWebGPURenderer::shutdown() { + if(inFrame_ && pass_) { + pass_.End(); + pass_ = nullptr; + inFrame_ = false; + } + encoder_ = nullptr; + queue_ = nullptr; + device_ = nullptr; + adapter_ = nullptr; + surface_ = nullptr; + instance_ = nullptr; + surfaceConfigured_ = false; + pipelinesReady_ = false; + for(auto & pipeline : pipelines_) { + pipeline = nullptr; + } + bindGroupLayout_ = nullptr; + whiteTex_ = nullptr; + whiteView_ = nullptr; + activeView_ = nullptr; + sampler_ = nullptr; + depthTex_ = nullptr; + depthView_ = nullptr; + texCache_.clear(); + gpuVerts_.clear(); + batches_.clear(); + for(auto & slot : slots_) { + slot.vertices = nullptr; + slot.uniforms = nullptr; + slot.vertexBytes = 0; + slot.uniformBytes = 0; + } +} + +bool ofWebGPURenderer::setup(void * metalLayer, int width, int height, float contentsScale) { + shutdown(); + metalLayer_ = metalLayer; + width_ = std::max(1, width); + height_ = std::max(1, height); + contentsScale_ = contentsScale > 0.f ? contentsScale : 1.f; + + if(!metalLayer_) { + ofLogError("ofWebGPURenderer") << "setup: CAMetalLayer is null"; + return false; + } + + wgpu::InstanceDescriptor instanceDesc = {}; + wgpu::InstanceFeatureName features[] = { wgpu::InstanceFeatureName::TimedWaitAny }; + instanceDesc.requiredFeatureCount = 1; + instanceDesc.requiredFeatures = features; + instance_ = wgpu::CreateInstance(&instanceDesc); + if(!instance_) { + ofLogError("ofWebGPURenderer") << "wgpuCreateInstance failed"; + return false; + } + + wgpu::SurfaceSourceMetalLayer metalSrc; + metalSrc.layer = metalLayer_; + wgpu::SurfaceDescriptor surfDesc; + surfDesc.nextInChain = &metalSrc; + surface_ = instance_.CreateSurface(&surfDesc); + if(!surface_) { + ofLogError("ofWebGPURenderer") << "CreateSurface failed"; + return false; + } + + wgpu::RequestAdapterOptions adapterOpts = {}; + adapterOpts.compatibleSurface = surface_; + adapterOpts.powerPreference = wgpu::PowerPreference::HighPerformance; + + struct AdapterResult { + wgpu::Adapter adapter; + bool done = false; + std::string message; + } adapterResult; + + auto adapterFuture = instance_.RequestAdapter( + &adapterOpts, + wgpu::CallbackMode::WaitAnyOnly, + [&adapterResult](wgpu::RequestAdapterStatus status, wgpu::Adapter adapter, wgpu::StringView message) { + if(status == wgpu::RequestAdapterStatus::Success) { + adapterResult.adapter = adapter; + } else if(message.data) { + adapterResult.message = (message.length == WGPU_STRLEN) ? std::string(message.data) : std::string(message.data, message.length); + } + adapterResult.done = true; + }); + instance_.WaitAny(adapterFuture, UINT64_MAX); + if(!adapterResult.adapter) { + ofLogError("ofWebGPURenderer") << "RequestAdapter failed: " << adapterResult.message; + return false; + } + adapter_ = adapterResult.adapter; + + struct DeviceResult { + wgpu::Device device; + bool done = false; + std::string message; + } deviceResult; + + wgpu::DeviceDescriptor deviceDesc = {}; + deviceDesc.SetUncapturedErrorCallback([](const wgpu::Device &, wgpu::ErrorType type, wgpu::StringView message) { + std::string msg = message.data ? ((message.length == WGPU_STRLEN) ? std::string(message.data) : std::string(message.data, message.length)) : ""; + ofLogError("Dawn") << "error " << (uint32_t)type << ": " << msg; + }); + auto deviceFuture = adapter_.RequestDevice( + &deviceDesc, + wgpu::CallbackMode::WaitAnyOnly, + [&deviceResult](wgpu::RequestDeviceStatus status, wgpu::Device device, wgpu::StringView message) { + if(status == wgpu::RequestDeviceStatus::Success) { + deviceResult.device = device; + } else if(message.data) { + deviceResult.message = (message.length == WGPU_STRLEN) ? std::string(message.data) : std::string(message.data, message.length); + } + deviceResult.done = true; + }); + instance_.WaitAny(deviceFuture, UINT64_MAX); + if(!deviceResult.device) { + ofLogError("ofWebGPURenderer") << "RequestDevice failed: " << deviceResult.message; + return false; + } + device_ = deviceResult.device; + queue_ = device_.GetQueue(); + + configureSurface(width_, height_); + setupGraphicDefaults(); + ofLogNotice("ofWebGPURenderer") << "Dawn device ready " << width_ << "x" << height_ << " @" << contentsScale_; + return true; +} + +void ofWebGPURenderer::configureSurface(int width, int height) { + if(!device_ || !surface_) { + return; + } + width_ = std::max(1, width); + height_ = std::max(1, height); + + wgpu::SurfaceCapabilities caps = {}; + surface_.GetCapabilities(adapter_, &caps); + if(caps.formatCount > 0) { + surfaceFormat_ = caps.formats[0]; + } + + wgpu::SurfaceConfiguration config = {}; + config.device = device_; + config.format = surfaceFormat_; + config.usage = wgpu::TextureUsage::RenderAttachment; + config.alphaMode = wgpu::CompositeAlphaMode::Opaque; + config.width = static_cast(width_); + config.height = static_cast(height_); + wgpu::PresentMode mode = wgpu::PresentMode::Fifo; + if(!vsync_) { + bool immediate = false; + bool mailbox = false; + for(size_t i = 0; i < caps.presentModeCount; i++) { + if(caps.presentModes[i] == wgpu::PresentMode::Immediate) { + immediate = true; + } else if(caps.presentModes[i] == wgpu::PresentMode::Mailbox) { + mailbox = true; + } + } + if(immediate) { + mode = wgpu::PresentMode::Immediate; + } else if(mailbox) { + mode = wgpu::PresentMode::Mailbox; + } + } + config.presentMode = mode; + surface_.Configure(&config); + surfaceConfigured_ = true; + matrixStack.viewport(0, 0, width_, height_, true); + depthTex_ = nullptr; + depthView_ = nullptr; +} + +void ofWebGPURenderer::setPresentVSync(bool enabled) { + if(vsync_ == enabled) { + return; + } + vsync_ = enabled; + if(surfaceConfigured_) { + configureSurface(width_, height_); + } +} + +void ofWebGPURenderer::startRender() { + if(!isSetup() || inFrame_) { + return; + } + if(!surfaceConfigured_) { + configureSurface(width_, height_); + } + + wgpu::SurfaceTexture surfaceTexture; + surface_.GetCurrentTexture(&surfaceTexture); + if(surfaceTexture.status != wgpu::SurfaceGetCurrentTextureStatus::SuccessOptimal && + surfaceTexture.status != wgpu::SurfaceGetCurrentTextureStatus::SuccessSuboptimal) { + ofLogVerbose("ofWebGPURenderer") << "GetCurrentTexture status " << (uint32_t)surfaceTexture.status; + configureSurface(width_, height_); + surface_.GetCurrentTexture(&surfaceTexture); + if(surfaceTexture.status != wgpu::SurfaceGetCurrentTextureStatus::SuccessOptimal && + surfaceTexture.status != wgpu::SurfaceGetCurrentTextureStatus::SuccessSuboptimal) { + return; + } + } + + encoder_ = device_.CreateCommandEncoder(); + const ofFloatColor bg = currentStyle.bgColor; + beginColorPass(surfaceTexture.texture.CreateView(), true, bg.r, bg.g, bg.b, bg.a); + inFrame_ = true; +} + +void ofWebGPURenderer::finishRender() { + if(!inFrame_) { + return; + } + flushBatches(); + if(pass_) { + pass_.End(); + pass_ = nullptr; + } + if(encoder_ && queue_) { + wgpu::CommandBuffer cmd = encoder_.Finish(); + queue_.Submit(1, &cmd); + } + encoder_ = nullptr; + if(surface_) { + surface_.Present(); + } + inFrame_ = false; +} + +void ofWebGPURenderer::applyClear(float r, float g, float b, float a) { + currentStyle.bgColor.set(r, g, b, a); + if(!inFrame_) { + return; + } + flushBatches(); + // Mid-frame clear: end the current pass and start a new one with this color. + if(pass_) { + pass_.End(); + pass_ = nullptr; + } + wgpu::SurfaceTexture surfaceTexture; + surface_.GetCurrentTexture(&surfaceTexture); + if(!surfaceTexture.texture) { + return; + } + beginColorPass(surfaceTexture.texture.CreateView(), true, r, g, b, a); +} + + +namespace { + +uint64_t hashBytes(const void * data, size_t bytes) { + const auto * p = static_cast(data); + uint64_t h = 14695981039346656037ull; + for(size_t i = 0; i < bytes; i++) { + h ^= p[i]; + h *= 1099511628211ull; + } + return h; +} + +template +unsigned char channelTo8(T v) { + if constexpr(std::is_same::value) { + return v; + } else if constexpr(std::is_floating_point::value) { + float c = static_cast(v); + if(c < 0.f) { + c = 0.f; + } else if(c > 1.f) { + c = 1.f; + } + return static_cast(c * 255.f + 0.5f); + } else { + float c = static_cast(v) / 65535.f; + if(c < 0.f) { + c = 0.f; + } else if(c > 1.f) { + c = 1.f; + } + return static_cast(c * 255.f + 0.5f); + } +} + +template +bool pixelsToRGBA(const ofPixels_ & pix, std::vector & rgba, int & width, int & height) { + if(!pix.isAllocated() || pix.getWidth() == 0 || pix.getHeight() == 0) { + return false; + } + width = static_cast(pix.getWidth()); + height = static_cast(pix.getHeight()); + const int channels = static_cast(pix.getNumChannels()); + if(channels < 1 || channels > 4) { + return false; + } + const bool bgr = pix.getPixelFormat() == OF_PIXELS_BGR || pix.getPixelFormat() == OF_PIXELS_BGRA; + rgba.resize(static_cast(width) * static_cast(height) * 4); + const T * src = pix.getData(); + for(int i = 0; i < width * height; i++) { + const T * p = src + static_cast(i) * channels; + unsigned char r = 255, g = 255, b = 255, a = 255; + if(channels == 1) { + r = g = b = channelTo8(p[0]); + } else if(channels == 2) { + r = g = b = channelTo8(p[0]); + a = channelTo8(p[1]); + } else if(channels == 3) { + r = channelTo8(p[0]); + g = channelTo8(p[1]); + b = channelTo8(p[2]); + } else { + r = channelTo8(p[0]); + g = channelTo8(p[1]); + b = channelTo8(p[2]); + a = channelTo8(p[3]); + } + if(bgr) { + std::swap(r, b); + } + rgba[static_cast(i) * 4 + 0] = r; + rgba[static_cast(i) * 4 + 1] = g; + rgba[static_cast(i) * 4 + 2] = b; + rgba[static_cast(i) * 4 + 3] = a; + } + return true; +} + +} + +void ofWebGPURenderer::ensureDepth() { + if(!device_ || width_ <= 0 || height_ <= 0 || depthView_) { + return; + } + wgpu::TextureDescriptor desc = {}; + desc.usage = wgpu::TextureUsage::RenderAttachment; + desc.dimension = wgpu::TextureDimension::e2D; + desc.size = { static_cast(width_), static_cast(height_), 1 }; + desc.format = wgpu::TextureFormat::Depth24Plus; + desc.mipLevelCount = 1; + desc.sampleCount = 1; + depthTex_ = device_.CreateTexture(&desc); + if(depthTex_) { + depthView_ = depthTex_.CreateView(); + } +} + +void ofWebGPURenderer::beginColorPass(wgpu::TextureView colorView, bool clear, float r, float g, float b, float a) { + ensureDepth(); + wgpu::RenderPassColorAttachment color = {}; + color.view = colorView; + color.loadOp = clear ? wgpu::LoadOp::Clear : wgpu::LoadOp::Load; + color.storeOp = wgpu::StoreOp::Store; + color.clearValue = { r, g, b, a }; + + wgpu::RenderPassDepthStencilAttachment depth = {}; + depth.view = depthView_; + depth.depthLoadOp = clear ? wgpu::LoadOp::Clear : wgpu::LoadOp::Load; + depth.depthStoreOp = wgpu::StoreOp::Store; + depth.depthClearValue = 1.f; + + wgpu::RenderPassDescriptor passDesc = {}; + passDesc.colorAttachmentCount = 1; + passDesc.colorAttachments = &color; + if(depthView_) { + passDesc.depthStencilAttachment = &depth; + } + pass_ = encoder_.BeginRenderPass(&passDesc); +} + +void ofWebGPURenderer::logDrawOnce(const char * what) const { + if(!loggedDraw_) { + ofLogVerbose("ofWebGPURenderer") << "draw(" << what << ") is not implemented"; + loggedDraw_ = true; + } +} + +void ofWebGPURenderer::pushView() { matrixStack.pushView(); } +void ofWebGPURenderer::popView() { matrixStack.popView(); } +void ofWebGPURenderer::viewport(ofRectangle vp) { matrixStack.viewport(vp.x, vp.y, vp.width, vp.height, isVFlipped()); } +void ofWebGPURenderer::viewport(float x, float y, float width, float height, bool vflip) { + if(width < 0) { + width = width_; + } + if(height < 0) { + height = height_; + } + matrixStack.viewport(x, y, width, height, vflip); +} +void ofWebGPURenderer::setupScreenPerspective(float width, float height, float fov, float nearDist, float farDist) { + float viewW = width < 0 ? getViewportWidth() : width; + float viewH = height < 0 ? getViewportHeight() : height; + if(viewH < 1.f) { + viewH = 1.f; + } + float eyeX = viewW / 2.f; + float eyeY = viewH / 2.f; + float halfFov = glm::pi() * fov / 360.0f; + float dist = eyeY / tanf(halfFov); + float aspect = viewW / viewH; + if(nearDist == 0) { + nearDist = dist / 10.0f; + } + if(farDist == 0) { + farDist = dist * 10.0f; + } + matrixMode(OF_MATRIX_PROJECTION); + loadMatrix(glm::perspective(glm::radians(fov), aspect, nearDist, farDist)); + matrixMode(OF_MATRIX_MODELVIEW); + loadViewMatrix(glm::lookAt(glm::vec3(eyeX, eyeY, dist), glm::vec3(eyeX, eyeY, 0), glm::vec3(0, 1, 0))); +} +void ofWebGPURenderer::setupScreenOrtho(float width, float height, float nearDist, float farDist) { + float viewW = width < 0 ? getViewportWidth() : width; + float viewH = height < 0 ? getViewportHeight() : height; + matrixMode(OF_MATRIX_PROJECTION); + loadMatrix(glm::ortho(0.f, viewW, 0.f, viewH, nearDist, farDist)); + matrixMode(OF_MATRIX_MODELVIEW); + loadViewMatrix(glm::mat4(1.0)); +} +void ofWebGPURenderer::setOrientation(ofOrientation orientation, bool vFlip) { matrixStack.setOrientation(orientation, vFlip); } +ofRectangle ofWebGPURenderer::getCurrentViewport() const { return matrixStack.getCurrentViewport(); } +ofRectangle ofWebGPURenderer::getNativeViewport() const { return matrixStack.getNativeViewport(); } +int ofWebGPURenderer::getViewportWidth() const { return getCurrentViewport().width; } +int ofWebGPURenderer::getViewportHeight() const { return getCurrentViewport().height; } +bool ofWebGPURenderer::isVFlipped() const { return matrixStack.isVFlipped(); } +void ofWebGPURenderer::setCoordHandedness(ofHandednessType) {} +ofHandednessType ofWebGPURenderer::getCoordHandedness() const { return matrixStack.getHandedness(); } + +void ofWebGPURenderer::pushMatrix() { matrixStack.pushMatrix(); } +void ofWebGPURenderer::popMatrix() { matrixStack.popMatrix(); } +glm::mat4 ofWebGPURenderer::getCurrentMatrix(ofMatrixMode) const { return matrixStack.getCurrentMatrix(); } +glm::mat4 ofWebGPURenderer::getCurrentOrientationMatrix() const { return matrixStack.getOrientationMatrix(); } +void ofWebGPURenderer::translate(float x, float y, float z) { matrixStack.translate(x, y, z); } +void ofWebGPURenderer::translate(const glm::vec3 & p) { matrixStack.translate(p.x, p.y, p.z); } +void ofWebGPURenderer::scale(float xAmnt, float yAmnt, float zAmnt) { matrixStack.scale(xAmnt, yAmnt, zAmnt); } +void ofWebGPURenderer::rotateRad(float radians, float vecX, float vecY, float vecZ) { matrixStack.rotateRad(radians, vecX, vecY, vecZ); } +void ofWebGPURenderer::rotateXRad(float radians) { matrixStack.rotateRad(radians, 1, 0, 0); } +void ofWebGPURenderer::rotateYRad(float radians) { matrixStack.rotateRad(radians, 0, 1, 0); } +void ofWebGPURenderer::rotateZRad(float radians) { matrixStack.rotateRad(radians, 0, 0, 1); } +void ofWebGPURenderer::rotateRad(float radians) { matrixStack.rotateRad(radians, 0, 0, 1); } +void ofWebGPURenderer::matrixMode(ofMatrixMode mode) { matrixStack.matrixMode(mode); } +void ofWebGPURenderer::loadIdentityMatrix() { matrixStack.loadIdentityMatrix(); } +void ofWebGPURenderer::loadMatrix(const glm::mat4 & m) { matrixStack.loadMatrix(m); } +void ofWebGPURenderer::loadMatrix(const float * m) { matrixStack.loadMatrix(glm::make_mat4(m)); } +void ofWebGPURenderer::multMatrix(const glm::mat4 & m) { matrixStack.multMatrix(m); } +void ofWebGPURenderer::multMatrix(const float * m) { matrixStack.multMatrix(glm::make_mat4(m)); } +void ofWebGPURenderer::loadViewMatrix(const glm::mat4 & m) { matrixStack.loadViewMatrix(m); } +void ofWebGPURenderer::multViewMatrix(const glm::mat4 & m) { matrixStack.multViewMatrix(m); } +glm::mat4 ofWebGPURenderer::getCurrentViewMatrix() const { return matrixStack.getViewMatrix(); } +glm::mat4 ofWebGPURenderer::getCurrentNormalMatrix() const { return glm::inverseTranspose(glm::mat3(matrixStack.getModelViewMatrix())); } + +void ofWebGPURenderer::bind(const ofCamera &, const ofRectangle &) {} +void ofWebGPURenderer::unbind(const ofCamera &) {} + +void ofWebGPURenderer::setupGraphicDefaults() { + matrixStack.setRenderSurface(*window_); + matrixStack.loadIdentityMatrix(); + currentStyle = ofStyle(); +} + +void ofWebGPURenderer::setupScreen() { + setupScreenPerspective(); +} + +void ofWebGPURenderer::setRectMode(ofRectMode mode) { currentStyle.rectMode = mode; } +ofRectMode ofWebGPURenderer::getRectMode() { return currentStyle.rectMode; } +void ofWebGPURenderer::setFillMode(ofFillFlag fill) { currentStyle.bFill = (fill == OF_FILLED); } +ofFillFlag ofWebGPURenderer::getFillMode() { return currentStyle.bFill ? OF_FILLED : OF_OUTLINE; } +void ofWebGPURenderer::setLineWidth(float lineWidth) { currentStyle.lineWidth = lineWidth; } +void ofWebGPURenderer::setPointSize(float pointSize) { currentStyle.pointSize = pointSize; } +void ofWebGPURenderer::setDepthTest(bool depthTest) { bDepthTest_ = depthTest; } +void ofWebGPURenderer::setBlendMode(ofBlendMode blendMode) { currentStyle.blendingMode = blendMode; } +void ofWebGPURenderer::setLineSmoothing(bool smooth) { currentStyle.smoothing = smooth; } +void ofWebGPURenderer::setCircleResolution(int res) { + currentStyle.circleResolution = res; + rebuildCircle(res); +} +void ofWebGPURenderer::enableAntiAliasing() {} +void ofWebGPURenderer::disableAntiAliasing() {} + +void ofWebGPURenderer::setColor(float r, float g, float b) { setColor(r, g, b, 1.f); } +void ofWebGPURenderer::setColor(float r, float g, float b, float a) { currentStyle.color.set(r, g, b, a); } +void ofWebGPURenderer::setColor(const ofFloatColor & color) { currentStyle.color = color; } +void ofWebGPURenderer::setColor(const ofFloatColor & color, float _a) { + currentStyle.color = color; + currentStyle.color.a = _a; +} +void ofWebGPURenderer::setColor(float gray) { setColor(gray, gray, gray); } +void ofWebGPURenderer::setHexColor(int hexColor) { currentStyle.color.setHex(hexColor); } +void ofWebGPURenderer::setBitmapTextMode(ofDrawBitmapMode mode) { currentStyle.drawBitmapMode = mode; } + +ofFloatColor ofWebGPURenderer::getBackgroundColor() { return currentStyle.bgColor; } +void ofWebGPURenderer::setBackgroundColor(const ofFloatColor & c) { currentStyle.bgColor = c; } +void ofWebGPURenderer::background(const ofFloatColor & c) { + setBackgroundColor(c); + applyClear(c.r, c.g, c.b, c.a); +} +void ofWebGPURenderer::background(float brightness) { background(ofFloatColor(brightness, brightness, brightness)); } +void ofWebGPURenderer::background(int hexColor, int _a) { + ofFloatColor c; + c.setHex(hexColor, _a / 255.f); + background(c); +} +void ofWebGPURenderer::background(float r, float g, float b, float a) { background(ofFloatColor(r, g, b, a)); } +void ofWebGPURenderer::setBackgroundAuto(bool bManual) { bBackgroundAuto = bManual; } +bool ofWebGPURenderer::getBackgroundAuto() { return bBackgroundAuto; } +void ofWebGPURenderer::clear() { applyClear(0, 0, 0, 0); } +void ofWebGPURenderer::clear(float r, float g, float b, float a) { applyClear(r, g, b, a); } +void ofWebGPURenderer::clear(float brightness, float a) { applyClear(brightness, brightness, brightness, a); } +void ofWebGPURenderer::clearAlpha() {} + +void ofWebGPURenderer::draw(const ofPolyline & poly) const { + const auto & verts = poly.getVertices(); + if(verts.size() < 2) { + return; + } + beginBatch(2); + for(size_t i = 0; i + 1 < verts.size(); i++) { + pushVert(verts[i].x, verts[i].y, verts[i].z); + pushVert(verts[i + 1].x, verts[i + 1].y, verts[i + 1].z); + } + if(poly.isClosed() && verts.front() != verts.back()) { + pushVert(verts.back().x, verts.back().y, verts.back().z); + pushVert(verts.front().x, verts.front().y, verts.front().z); + } +} + +void ofWebGPURenderer::draw(const ofPath & shape) const { + ofFloatColor prev = currentStyle.color; + auto * mut = const_cast(this); + if(shape.isFilled()) { + if(shape.getUseShapeColor()) { + mut->setColor(shape.getFillColor()); + } + draw(shape.getTessellation(), OF_MESH_FILL, false, false, false); + } + if(shape.hasOutline()) { + const float lineWidth = currentStyle.lineWidth; + if(shape.getUseShapeColor()) { + mut->setColor(shape.getStrokeColor()); + } + mut->setLineWidth(shape.getStrokeWidth()); + const std::vector & outlines = shape.getOutline(); + for(const ofPolyline & line : outlines) { + draw(line); + } + mut->setLineWidth(lineWidth); + } + if(shape.getUseShapeColor()) { + mut->setColor(prev); + } +} + +void ofWebGPURenderer::draw(const ofMesh & vertexData, ofPolyRenderMode renderType, bool useColors, bool useTextures, bool) const { + drawMesh(vertexData, renderType, useColors, useTextures); +} + +void ofWebGPURenderer::draw(const of3dPrimitive & model, ofPolyRenderMode renderType) const { + auto * mut = const_cast(this); + mut->pushMatrix(); + mut->multMatrix(model.getGlobalTransformMatrix()); + const ofMesh & mesh = model.getMesh(); + drawMesh(mesh, renderType, mesh.usingColors(), mesh.usingTextures()); + mut->popMatrix(); +} + +void ofWebGPURenderer::draw(const ofNode & node) const { + auto * mut = const_cast(this); + mut->pushMatrix(); + mut->multMatrix(node.getGlobalTransformMatrix()); + const_cast(node).customDraw(this); + mut->popMatrix(); +} + +void ofWebGPURenderer::drawTexturedQuad(float x, float y, float z, float w, float h, float u0, float v0, float u1, float v1, const ofTexture & tex) const { + if(w == 0.f || h == 0.f) { + return; + } + float x0 = x; + float y0 = y; + float x1 = x + w; + float y1 = y + h; + if(currentStyle.rectMode == OF_RECTMODE_CENTER) { + x0 -= w * 0.5f; + y0 -= h * 0.5f; + x1 -= w * 0.5f; + y1 -= h * 0.5f; + } + const glm::vec2 anchor = tex.getAnchor(); + const float ax = tex.isAnchorPercent() ? anchor.x * w : anchor.x; + const float ay = tex.isAnchorPercent() ? anchor.y * h : anchor.y; + x0 -= ax; + y0 -= ay; + x1 -= ax; + y1 -= ay; + if(tex.texData.bFlipTexture == isVFlipped()) { + std::swap(y0, y1); + } + const ofFloatColor color = currentStyle.color; + beginBatch(0); + pushVert(x0, y0, z, color, u0, v0); + pushVert(x1, y0, z, color, u1, v0); + pushVert(x1, y1, z, color, u1, v1); + pushVert(x0, y0, z, color, u0, v0); + pushVert(x1, y1, z, color, u1, v1); + pushVert(x0, y1, z, color, u0, v1); +} + +template +void ofWebGPURenderer::drawImagePixels(const ofImage_ & image, float x, float y, float z, float w, float h, float sx, float sy, float sw, float sh) const { + if(!image.isAllocated()) { + return; + } + const auto & pix = image.getPixels(); + std::vector rgba; + int pw = 0; + int ph = 0; + if(!pixelsToRGBA(pix, rgba, pw, ph) || pw <= 0 || ph <= 0) { + return; + } + if(sw == 0.f) { + sw = static_cast(pw); + } + if(sh == 0.f) { + sh = static_cast(ph); + } + const float u0 = sx / static_cast(pw); + const float v0 = sy / static_cast(ph); + const float u1 = (sx + sw) / static_cast(pw); + const float v1 = (sy + sh) / static_cast(ph); + const uint64_t stamp = hashBytes(pix.getData(), pix.size() * sizeof(PixelType)) ^ (static_cast(pw) << 32) ^ static_cast(ph); + activeView_ = uploadRGBA(&image, rgba.data(), pw, ph, stamp); + drawTexturedQuad(x, y, z, w, h, u0, v0, u1, v1, image.getTexture()); + activeView_ = whiteView_; +} + +void ofWebGPURenderer::draw(const ofImage & image, float x, float y, float z, float w, float h, float sx, float sy, float sw, float sh) const { + drawImagePixels(image, x, y, z, w, h, sx, sy, sw, sh); +} + +void ofWebGPURenderer::draw(const ofFloatImage & image, float x, float y, float z, float w, float h, float sx, float sy, float sw, float sh) const { + drawImagePixels(image, x, y, z, w, h, sx, sy, sw, sh); +} + +void ofWebGPURenderer::draw(const ofShortImage & image, float x, float y, float z, float w, float h, float sx, float sy, float sw, float sh) const { + drawImagePixels(image, x, y, z, w, h, sx, sy, sw, sh); +} + +void ofWebGPURenderer::draw(const ofBaseVideoDraws & video, float x, float y, float w, float h) const { + if(!video.isInitialized()) { + return; + } + const ofPixels & pix = video.getPixels(); + std::vector rgba; + int pw = 0; + int ph = 0; + if(!pixelsToRGBA(pix, rgba, pw, ph)) { + return; + } + const uint64_t stamp = hashBytes(pix.getData(), pix.size()) ^ (static_cast(pw) << 32) ^ static_cast(ph); + activeView_ = uploadRGBA(&video, rgba.data(), pw, ph, stamp); + ofTexture placeholder; + drawTexturedQuad(x, y, 0.f, w, h, 0.f, 0.f, 1.f, 1.f, placeholder); + activeView_ = whiteView_; +} + +void ofWebGPURenderer::drawMesh(const ofMesh & mesh, ofPolyRenderMode renderType, bool useColors, bool useTextures) const { + const auto & verts = mesh.getVertices(); + const size_t nVerts = verts.size(); + if(nVerts == 0) { + return; + } + const bool indexed = mesh.getNumIndices() > 0 && renderType != OF_MESH_POINTS; + const ofIndexType * indices = indexed ? mesh.getIndexPointer() : nullptr; + const size_t n = indexed ? mesh.getNumIndices() : nVerts; + auto nth = [&](size_t i) -> size_t { + return indexed ? static_cast(indices[i]) : i; + }; + + const ofPrimitiveMode mode = mesh.getMode(); + const bool points = renderType == OF_MESH_POINTS || mode == OF_PRIMITIVE_POINTS; + const bool triangleLike = mode == OF_PRIMITIVE_TRIANGLES || mode == OF_PRIMITIVE_TRIANGLE_STRIP || mode == OF_PRIMITIVE_TRIANGLE_FAN; + const bool lineLike = mode == OF_PRIMITIVE_LINES || mode == OF_PRIMITIVE_LINE_STRIP || mode == OF_PRIMITIVE_LINE_LOOP; + + const bool colored = useColors && mesh.usingColors() && mesh.getNumColors() > 0; + const ofFloatColor * cols = colored ? mesh.getColorsPointer() : nullptr; + const size_t nCols = colored ? mesh.getNumColors() : 0; + const bool textured = useTextures && mesh.usingTextures() && mesh.getNumTexCoords() > 0 && activeView_ && whiteView_ && activeView_.Get() != whiteView_.Get(); + const glm::vec2 * tcs = textured ? mesh.getTexCoordsPointer() : nullptr; + const size_t nT = textured ? mesh.getNumTexCoords() : 0; + + auto emit = [&](size_t vi) { + const auto & p = verts[vi]; + ofFloatColor color = currentStyle.color; + if(cols && vi < nCols) { + color = cols[vi]; + } + float u = 0.f; + float v = 0.f; + if(tcs && vi < nT) { + u = tcs[vi].x; + v = tcs[vi].y; + } + pushVert(p.x, p.y, p.z, color, u, v); + }; + auto emit2 = [&](size_t a, size_t b) { + const size_t ia = nth(a); + const size_t ib = nth(b); + if(ia >= nVerts || ib >= nVerts) { + return; + } + emit(ia); + emit(ib); + }; + auto emit3 = [&](size_t a, size_t b, size_t c) { + const size_t ia = nth(a); + const size_t ib = nth(b); + const size_t ic = nth(c); + if(ia >= nVerts || ib >= nVerts || ic >= nVerts) { + return; + } + emit(ia); + emit(ib); + emit(ic); + }; + + if(points) { + const float ps = std::max(currentStyle.pointSize, 1.f); + const bool expand = pointSprites_ || ps > 1.01f; + if(!expand) { + beginBatch(4); + for(size_t i = 0; i < nVerts; i++) { + emit(i); + } + return; + } + // WebGPU points are 1px. Expand to a screen-space quad so point size and + // point sprites match the GL path. Identity MVP consumes NDC positions. + const glm::mat4 mvp = gpuMvp(); + const ofRectangle vp = getCurrentViewport(); + const float vw = vp.width > 1.f ? vp.width : static_cast(std::max(width_, 1)); + const float vh = vp.height > 1.f ? vp.height : static_cast(std::max(height_, 1)); + const float dxNdc = ps / vw; + const float dyNdc = ps / vh; + const bool sprite = pointSprites_ && textured; + beginBatch(0, glm::mat4(1.f)); + for(size_t i = 0; i < nVerts; i++) { + const glm::vec3 & p = verts[i]; + const glm::vec4 clip = mvp * glm::vec4(p, 1.f); + if(clip.w <= 0.f) { + continue; + } + const float invW = 1.f / clip.w; + const float nx = clip.x * invW; + const float ny = clip.y * invW; + const float nz = clip.z * invW; + ofFloatColor color = currentStyle.color; + if(cols && i < nCols) { + color = cols[i]; + } + float u0 = 0.f; + float v0 = 0.f; + float u1 = 0.f; + float v1 = 0.f; + if(sprite) { + u1 = 1.f; + v1 = 1.f; + } else if(tcs && i < nT) { + u0 = u1 = tcs[i].x; + v0 = v1 = tcs[i].y; + } + const float x0 = nx - dxNdc; + const float x1 = nx + dxNdc; + const float y0 = ny - dyNdc; + const float y1 = ny + dyNdc; + pushVert(x0, y0, nz, color, u0, v0); + pushVert(x1, y0, nz, color, u1, v0); + pushVert(x1, y1, nz, color, u1, v1); + pushVert(x0, y0, nz, color, u0, v0); + pushVert(x1, y1, nz, color, u1, v1); + pushVert(x0, y1, nz, color, u0, v1); + } + return; + } + + if(renderType == OF_MESH_WIREFRAME && triangleLike) { + beginBatch(2); + if(mode == OF_PRIMITIVE_TRIANGLES) { + for(size_t i = 0; i + 2 < n; i += 3) { + emit2(i, i + 1); + emit2(i + 1, i + 2); + emit2(i + 2, i); + } + } else if(mode == OF_PRIMITIVE_TRIANGLE_STRIP) { + for(size_t i = 0; i + 2 < n; i++) { + emit2(i, i + 1); + emit2(i + 1, i + 2); + emit2(i + 2, i); + } + } else { + for(size_t i = 1; i + 1 < n; i++) { + emit2(0, i); + emit2(i, i + 1); + emit2(i + 1, 0); + } + } + return; + } + + if(lineLike) { + beginBatch(2); + if(mode == OF_PRIMITIVE_LINES) { + for(size_t i = 0; i + 1 < n; i += 2) { + emit2(i, i + 1); + } + } else if(mode == OF_PRIMITIVE_LINE_LOOP) { + for(size_t i = 0; i + 1 < n; i++) { + emit2(i, i + 1); + } + if(n >= 2) { + emit2(n - 1, 0); + } + } else { + for(size_t i = 0; i + 1 < n; i++) { + emit2(i, i + 1); + } + } + return; + } + + if(triangleLike) { + beginBatch(0); + if(mode == OF_PRIMITIVE_TRIANGLE_STRIP) { + for(size_t i = 0; i + 2 < n; i++) { + emit3(i, i + 1, i + 2); + } + } else if(mode == OF_PRIMITIVE_TRIANGLE_FAN) { + for(size_t i = 1; i + 1 < n; i++) { + emit3(0, i, i + 1); + } + } else { + for(size_t i = 0; i + 2 < n; i += 3) { + emit3(i, i + 1, i + 2); + } + } + return; + } + + const bool wire = renderType == OF_MESH_WIREFRAME; + auto emitTri = [&](size_t a, size_t b, size_t c) { + if(wire) { + emit2(a, b); + emit2(b, c); + emit2(c, a); + } else { + emit3(a, b, c); + } + }; +#ifndef TARGET_OPENGLES + if(mode == OF_PRIMITIVE_LINES_ADJACENCY) { + beginBatch(2); + for(size_t i = 0; i + 3 < n; i += 4) { + emit2(i + 1, i + 2); + } + return; + } + if(mode == OF_PRIMITIVE_LINE_STRIP_ADJACENCY) { + beginBatch(2); + if(n >= 4) { + for(size_t i = 1; i + 2 < n; i++) { + emit2(i, i + 1); + } + } + return; + } + if(mode == OF_PRIMITIVE_TRIANGLES_ADJACENCY) { + beginBatch(wire ? 2 : 0); + for(size_t i = 0; i + 5 < n; i += 6) { + emitTri(i, i + 2, i + 4); + } + return; + } + if(mode == OF_PRIMITIVE_TRIANGLE_STRIP_ADJACENCY) { + // OpenGL triangle-strip adjacency: even i uses (2i, 2i+2, 2i+4), + // odd i uses (2i+2, 2i, 2i+4). Adjacency vertices stay unused. + beginBatch(wire ? 2 : 0); + for(size_t tri = 0;; tri++) { + size_t i0 = 0; + size_t i1 = 0; + size_t i2 = 0; + if((tri & 1u) == 0u) { + i0 = 2 * tri; + i1 = i0 + 2; + i2 = i0 + 4; + } else { + i0 = 2 * tri + 2; + i1 = 2 * tri; + i2 = 2 * tri + 4; + } + if(i2 >= n) { + break; + } + emitTri(i0, i1, i2); + } + return; + } + if(mode == OF_PRIMITIVE_PATCHES) { + // No tessellation stage. Draw the control polygon so the mesh is visible. + if(n >= 3 && !wire) { + beginBatch(0); + for(size_t i = 1; i + 1 < n; i++) { + emit3(0, i, i + 1); + } + } else if(n >= 2) { + beginBatch(2); + for(size_t i = 0; i + 1 < n; i++) { + emit2(i, i + 1); + } + if(n >= 3) { + emit2(n - 1, 0); + } + } + return; + } +#endif + + logDrawOnce("mesh primitive"); +} + +wgpu::TextureView ofWebGPURenderer::uploadRGBA(const void * owner, const unsigned char * rgba, int width, int height, uint64_t stamp) const { + if(!device_ || !queue_ || !rgba || width <= 0 || height <= 0) { + return whiteView_; + } + for(size_t i = 0; i < texCache_.size(); i++) { + TexCache & cached = texCache_[i]; + if(cached.owner == owner && cached.stamp == stamp && cached.width == width && cached.height == height && cached.view) { + if(i + 1 != texCache_.size()) { + std::rotate(texCache_.begin() + static_cast(i), texCache_.begin() + static_cast(i + 1), texCache_.end()); + } + return texCache_.back().view; + } + } + + wgpu::TextureDescriptor desc = {}; + desc.usage = wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::CopyDst; + desc.dimension = wgpu::TextureDimension::e2D; + desc.size = { static_cast(width), static_cast(height), 1 }; + desc.format = wgpu::TextureFormat::RGBA8Unorm; + desc.mipLevelCount = 1; + desc.sampleCount = 1; + wgpu::Texture texture = device_.CreateTexture(&desc); + if(!texture) { + return whiteView_; + } + const uint32_t rowBytes = (static_cast(width) * 4u + 255u) & ~255u; + std::vector padded(static_cast(rowBytes) * static_cast(height)); + for(int y = 0; y < height; y++) { + std::memcpy(padded.data() + static_cast(y) * rowBytes, rgba + static_cast(y) * static_cast(width) * 4u, static_cast(width) * 4u); + } + wgpu::TexelCopyTextureInfo dst = {}; + dst.texture = texture; + dst.aspect = wgpu::TextureAspect::All; + wgpu::TexelCopyBufferLayout layout = {}; + layout.bytesPerRow = rowBytes; + layout.rowsPerImage = static_cast(height); + wgpu::Extent3D size = { static_cast(width), static_cast(height), 1 }; + queue_.WriteTexture(&dst, padded.data(), padded.size(), &layout, &size); + wgpu::TextureView view = texture.CreateView(); + + if(texCache_.size() >= 24) { + texCache_.erase(texCache_.begin()); + } + TexCache entry; + entry.owner = owner; + entry.stamp = stamp; + entry.width = width; + entry.height = height; + entry.texture = texture; + entry.view = view; + texCache_.push_back(entry); + return view; +} + +void ofWebGPURenderer::drawString(std::string text, float x, float y, float z) const { + auto * mut = const_cast(this); + float sx = 0.f; + float sy = 0.f; + bool hasModelView = false; + bool hasProjection = false; + bool hasViewport = false; + ofRectangle viewportRect; + + switch(currentStyle.drawBitmapMode) { + case OF_BITMAPMODE_SIMPLE: + sx = x; + sy = y; + break; + case OF_BITMAPMODE_SCREEN: { + hasViewport = true; + mut->pushView(); + viewportRect = matrixStack.getFullSurfaceViewport(); + mut->viewport(viewportRect); + mut->matrixMode(OF_MATRIX_PROJECTION); + mut->loadIdentityMatrix(); + mut->matrixMode(OF_MATRIX_MODELVIEW); + glm::mat4 modelView(1.f); + modelView = glm::translate(modelView, glm::vec3(-1.f, -1.f, 0.f)); + modelView = glm::scale(modelView, glm::vec3(2.f / viewportRect.width, 2.f / viewportRect.height, 1.f)); + modelView = glm::translate(modelView, glm::vec3(x, y, 0.f)); + mut->loadMatrix(modelView); + break; + } + case OF_BITMAPMODE_VIEWPORT: + viewportRect = getCurrentViewport(); + hasProjection = true; + mut->matrixMode(OF_MATRIX_PROJECTION); + mut->pushMatrix(); + mut->loadIdentityMatrix(); + hasModelView = true; + mut->matrixMode(OF_MATRIX_MODELVIEW); + mut->pushMatrix(); + { + glm::mat4 modelView(1.f); + modelView = glm::translate(modelView, glm::vec3(-1.f, -1.f, 0.f)); + modelView = glm::scale(modelView, glm::vec3(2.f / viewportRect.width, 2.f / viewportRect.height, 1.f)); + modelView = glm::translate(modelView, glm::vec3(x, y, 0.f)); + mut->loadMatrix(modelView); + } + break; + case OF_BITMAPMODE_MODEL: + hasModelView = true; + mut->matrixMode(OF_MATRIX_MODELVIEW); + mut->pushMatrix(); + mut->translate(x, y, z); + break; + case OF_BITMAPMODE_MODEL_BILLBOARD: { + viewportRect = getCurrentViewport(); + glm::mat4 mat = matrixStack.getProjectionMatrixNoOrientation() * matrixStack.getModelViewMatrix(); + glm::vec4 projected = mat * glm::vec4(x, y, z, 1.f); + if(projected.w == 0.f) { + return; + } + glm::vec3 screen = glm::vec3(projected) / projected.w; + screen += glm::vec3(1.f); + screen *= 0.5f; + screen.x += viewportRect.x; + screen.x *= viewportRect.width; + screen.y += viewportRect.y; + screen.y *= viewportRect.height; + if(screen.z >= 1.f) { + return; + } + hasProjection = true; + mut->matrixMode(OF_MATRIX_PROJECTION); + mut->pushMatrix(); + mut->loadIdentityMatrix(); + hasModelView = true; + mut->matrixMode(OF_MATRIX_MODELVIEW); + mut->pushMatrix(); + glm::mat4 modelView(1.f); + modelView = glm::translate(modelView, glm::vec3(-1.f, -1.f, 0.f)); + modelView = glm::scale(modelView, glm::vec3(2.f / viewportRect.width, 2.f / viewportRect.height, 1.f)); + modelView = glm::translate(modelView, glm::vec3(screen.x, screen.y, 0.f)); + mut->loadMatrix(modelView); + break; + } + default: + break; + } + + const ofPixels & atlas = bitmapFont_.getAtlasPixels(); + std::vector rgba; + int pw = 0; + int ph = 0; + if(pixelsToRGBA(atlas, rgba, pw, ph)) { + const uint64_t stamp = hashBytes(atlas.getData(), atlas.size()); + const ofBlendMode blend = currentStyle.blendingMode; + mut->setBlendMode(OF_BLENDMODE_ALPHA); + activeView_ = uploadRGBA(&bitmapFont_, rgba.data(), pw, ph, stamp); + ofMesh glyphs = bitmapFont_.getMesh(text, static_cast(sx), static_cast(sy), currentStyle.drawBitmapMode, isVFlipped()); + drawMesh(glyphs, OF_MESH_FILL, false, true); + activeView_ = whiteView_; + mut->setBlendMode(blend); + } + + if(hasViewport) { + mut->popView(); + } else { + if(hasModelView) { + mut->popMatrix(); + } + if(hasProjection) { + mut->matrixMode(OF_MATRIX_PROJECTION); + mut->popMatrix(); + mut->matrixMode(OF_MATRIX_MODELVIEW); + } + } +} + +void ofWebGPURenderer::drawString(const ofTrueTypeFont & font, std::string text, float x, float y) const { + if(!font.isLoaded()) { + return; + } + const ofPixels & atlas = font.getFontAtlasPixels(); + std::vector rgba; + int pw = 0; + int ph = 0; + if(!pixelsToRGBA(atlas, rgba, pw, ph)) { + logDrawOnce("ttf"); + return; + } + auto * mut = const_cast(this); + const ofBlendMode blend = currentStyle.blendingMode; + mut->setBlendMode(OF_BLENDMODE_ALPHA); + const uint64_t stamp = hashBytes(atlas.getData(), atlas.size()); + activeView_ = uploadRGBA(&font, rgba.data(), pw, ph, stamp); + drawMesh(font.getStringMesh(text, x, y, isVFlipped()), OF_MESH_FILL, false, true); + activeView_ = whiteView_; + mut->setBlendMode(blend); +} + +glm::mat4 ofWebGPURenderer::gpuMvp() const { + glm::mat4 zFix(1.f); + zFix[2][2] = 0.5f; + zFix[3][2] = 0.5f; + return zFix * matrixStack.getModelViewProjectionMatrix(); +} + +void ofWebGPURenderer::rebuildCircle(int res) const { + if(res < 3) { + res = 3; + } + if((int)circleUnit_.size() == res + 1) { + return; + } + ofPolyline poly; + poly.arc(0, 0, 0, 1, 1, 0, 360, res); + circleUnit_.assign(poly.getVertices().begin(), poly.getVertices().end()); +} + +void ofWebGPURenderer::pushVert(float x, float y, float z) const { + pushVert(x, y, z, currentStyle.color, 0.f, 0.f); +} + +void ofWebGPURenderer::pushVert(float x, float y, float z, const ofFloatColor & color, float u, float v) const { + gpuVerts_.push_back({ x, y, z, color.r, color.g, color.b, color.a, u, v }); + if(!batches_.empty()) { + batches_.back().count++; + } +} + +void ofWebGPURenderer::beginBatch(int primBase) const { + beginBatch(primBase, gpuMvp()); +} + +void ofWebGPURenderer::beginBatch(int primBase, const glm::mat4 & mvp) const { + const bool alpha = currentStyle.blendingMode != OF_BLENDMODE_DISABLED; + const int pipeline = primBase + (alpha ? 1 : 0) + (bDepthTest_ ? 6 : 0); + const WGPUTextureView view = activeView_ ? activeView_.Get() : nullptr; + if(!batches_.empty()) { + DrawBatch & last = batches_.back(); + const WGPUTextureView lastView = last.view ? last.view.Get() : nullptr; + if(last.pipeline == pipeline && last.mvp == mvp && lastView == view) { + return; + } + } + DrawBatch batch; + batch.first = static_cast(gpuVerts_.size()); + batch.pipeline = pipeline; + batch.mvp = mvp; + batch.view = activeView_; + batches_.push_back(batch); +} + +void ofWebGPURenderer::enablePointSprites() { + pointSprites_ = true; +} + +void ofWebGPURenderer::disablePointSprites() { + pointSprites_ = false; +} + +void ofWebGPURenderer::drawBackgroundGradient(const ofFloatColor & start, const ofFloatColor & end, ofGradientMode mode) const { + const float w = static_cast(getViewportWidth()); + const float h = static_cast(getViewportHeight()); + ofMesh mesh; + mesh.setMode(OF_PRIMITIVE_TRIANGLE_FAN); + if(mode == OF_GRADIENT_CIRCULAR) { + const glm::vec2 center(w / 2.f, h / 2.f); + mesh.addVertex(glm::vec3(center, 0.f)); + mesh.addColor(start); + const float n = 32.f; + const float angleBisector = glm::two_pi() / (n * 2.f); + const float smallRadius = glm::length(glm::vec2(w / 2.f, h / 2.f)); + const float bigRadius = smallRadius / std::cos(angleBisector); + for(int i = 0; i <= 32; i++) { + const float theta = static_cast(i) * glm::two_pi() / n; + mesh.addVertex(glm::vec3(center + glm::vec2(std::sin(theta), std::cos(theta)) * bigRadius, 0.f)); + mesh.addColor(end); + } + } else if(mode == OF_GRADIENT_LINEAR) { + mesh.addVertex(glm::vec3(0.f, 0.f, 0.f)); + mesh.addVertex(glm::vec3(w, 0.f, 0.f)); + mesh.addVertex(glm::vec3(w, h, 0.f)); + mesh.addVertex(glm::vec3(0.f, h, 0.f)); + mesh.addColor(start); + mesh.addColor(start); + mesh.addColor(end); + mesh.addColor(end); + } else if(mode == OF_GRADIENT_BAR) { + mesh.addVertex(glm::vec3(w / 2.f, h / 2.f, 0.f)); + mesh.addVertex(glm::vec3(0.f, h / 2.f, 0.f)); + mesh.addVertex(glm::vec3(0.f, 0.f, 0.f)); + mesh.addVertex(glm::vec3(w, 0.f, 0.f)); + mesh.addVertex(glm::vec3(w, h / 2.f, 0.f)); + mesh.addVertex(glm::vec3(w, h, 0.f)); + mesh.addVertex(glm::vec3(0.f, h, 0.f)); + mesh.addVertex(glm::vec3(0.f, h / 2.f, 0.f)); + mesh.addColor(start); + mesh.addColor(start); + mesh.addColor(end); + mesh.addColor(end); + mesh.addColor(start); + mesh.addColor(end); + mesh.addColor(end); + mesh.addColor(start); + } + auto * mut = const_cast(this); + const bool depth = bDepthTest_; + mut->bDepthTest_ = false; + drawMesh(mesh, OF_MESH_FILL, true, false); + mut->bDepthTest_ = depth; +} + +void ofWebGPURenderer::drawLine(float x1, float y1, float z1, float x2, float y2, float z2) const { + beginBatch(2); + pushVert(x1, y1, z1); + pushVert(x2, y2, z2); +} + +void ofWebGPURenderer::drawRectangle(float x, float y, float z, float w, float h) const { + float x0 = x; + float y0 = y; + float x1 = x + w; + float y1 = y + h; + if(currentStyle.rectMode != OF_RECTMODE_CORNER) { + x0 = x - w * 0.5f; + y0 = y - h * 0.5f; + x1 = x + w * 0.5f; + y1 = y + h * 0.5f; + } + if(currentStyle.bFill) { + beginBatch(0); + pushVert(x0, y0, z); + pushVert(x1, y0, z); + pushVert(x1, y1, z); + pushVert(x0, y0, z); + pushVert(x1, y1, z); + pushVert(x0, y1, z); + } else { + beginBatch(2); + pushVert(x0, y0, z); + pushVert(x1, y0, z); + pushVert(x1, y0, z); + pushVert(x1, y1, z); + pushVert(x1, y1, z); + pushVert(x0, y1, z); + pushVert(x0, y1, z); + pushVert(x0, y0, z); + } +} + +void ofWebGPURenderer::drawTriangle(float x1, float y1, float z1, float x2, float y2, float z2, float x3, float y3, float z3) const { + if(currentStyle.bFill) { + beginBatch(0); + pushVert(x1, y1, z1); + pushVert(x2, y2, z2); + pushVert(x3, y3, z3); + } else { + beginBatch(2); + pushVert(x1, y1, z1); + pushVert(x2, y2, z2); + pushVert(x2, y2, z2); + pushVert(x3, y3, z3); + pushVert(x3, y3, z3); + pushVert(x1, y1, z1); + } +} + +void ofWebGPURenderer::drawCircle(float x, float y, float z, float radius) const { + rebuildCircle(currentStyle.circleResolution); + if(circleUnit_.size() < 3) { + return; + } + if(currentStyle.bFill) { + beginBatch(0); + const glm::vec3 & c0 = circleUnit_[0]; + for(size_t i = 1; i + 1 < circleUnit_.size(); i++) { + pushVert(x + radius * c0.x, y + radius * c0.y, z); + pushVert(x + radius * circleUnit_[i].x, y + radius * circleUnit_[i].y, z); + pushVert(x + radius * circleUnit_[i + 1].x, y + radius * circleUnit_[i + 1].y, z); + } + } else { + beginBatch(2); + for(size_t i = 0; i + 1 < circleUnit_.size(); i++) { + pushVert(x + radius * circleUnit_[i].x, y + radius * circleUnit_[i].y, z); + pushVert(x + radius * circleUnit_[i + 1].x, y + radius * circleUnit_[i + 1].y, z); + } + } +} + +void ofWebGPURenderer::drawEllipse(float x, float y, float z, float width, float height) const { + rebuildCircle(currentStyle.circleResolution); + if(circleUnit_.size() < 3) { + return; + } + const float rx = width * 0.5f; + const float ry = height * 0.5f; + if(currentStyle.bFill) { + beginBatch(0); + const glm::vec3 & c0 = circleUnit_[0]; + for(size_t i = 1; i + 1 < circleUnit_.size(); i++) { + pushVert(x + rx * c0.x, y + ry * c0.y, z); + pushVert(x + rx * circleUnit_[i].x, y + ry * circleUnit_[i].y, z); + pushVert(x + rx * circleUnit_[i + 1].x, y + ry * circleUnit_[i + 1].y, z); + } + } else { + beginBatch(2); + for(size_t i = 0; i + 1 < circleUnit_.size(); i++) { + pushVert(x + rx * circleUnit_[i].x, y + ry * circleUnit_[i].y, z); + pushVert(x + rx * circleUnit_[i + 1].x, y + ry * circleUnit_[i + 1].y, z); + } + } +} + +void ofWebGPURenderer::ensurePipelines() { + if(pipelinesReady_ || !device_) { + return; + } + static const char * kWGSL = R"( +struct Uniforms { mvp: mat4x4f } +@group(0) @binding(0) var u: Uniforms; +@group(0) @binding(1) var tex: texture_2d; +@group(0) @binding(2) var samp: sampler; +struct VSIn { + @location(0) pos: vec3f, + @location(1) color: vec4f, + @location(2) uv: vec2f, +} +struct VSOut { + @builtin(position) clip: vec4f, + @location(0) color: vec4f, + @location(1) uv: vec2f, +} +@vertex fn vs(in: VSIn) -> VSOut { + var o: VSOut; + o.clip = u.mvp * vec4f(in.pos, 1.0); + o.color = in.color; + o.uv = in.uv; + return o; +} +@fragment fn fs(in: VSOut) -> @location(0) vec4f { + return in.color * textureSample(tex, samp, in.uv); +} +)"; + wgpu::ShaderSourceWGSL wgsl; + wgsl.code = kWGSL; + wgpu::ShaderModuleDescriptor shaderDesc; + shaderDesc.nextInChain = &wgsl; + wgpu::ShaderModule module = device_.CreateShaderModule(&shaderDesc); + + const unsigned char whitePx[4] = { 255, 255, 255, 255 }; + wgpu::TextureDescriptor whiteDesc = {}; + whiteDesc.usage = wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::CopyDst; + whiteDesc.dimension = wgpu::TextureDimension::e2D; + whiteDesc.size = { 1, 1, 1 }; + whiteDesc.format = wgpu::TextureFormat::RGBA8Unorm; + whiteDesc.mipLevelCount = 1; + whiteDesc.sampleCount = 1; + whiteTex_ = device_.CreateTexture(&whiteDesc); + if(whiteTex_) { + unsigned char padded[256] = {}; + padded[0] = whitePx[0]; + padded[1] = whitePx[1]; + padded[2] = whitePx[2]; + padded[3] = whitePx[3]; + wgpu::TexelCopyTextureInfo dst = {}; + dst.texture = whiteTex_; + dst.aspect = wgpu::TextureAspect::All; + wgpu::TexelCopyBufferLayout layout = {}; + layout.bytesPerRow = 256; + layout.rowsPerImage = 1; + wgpu::Extent3D size = { 1, 1, 1 }; + queue_.WriteTexture(&dst, padded, sizeof(padded), &layout, &size); + whiteView_ = whiteTex_.CreateView(); + } + if(!activeView_) { + activeView_ = whiteView_; + } + + wgpu::SamplerDescriptor samplerDesc = {}; + samplerDesc.addressModeU = wgpu::AddressMode::ClampToEdge; + samplerDesc.addressModeV = wgpu::AddressMode::ClampToEdge; + samplerDesc.addressModeW = wgpu::AddressMode::ClampToEdge; + samplerDesc.magFilter = wgpu::FilterMode::Linear; + samplerDesc.minFilter = wgpu::FilterMode::Linear; + samplerDesc.mipmapFilter = wgpu::MipmapFilterMode::Nearest; + sampler_ = device_.CreateSampler(&samplerDesc); + + wgpu::BindGroupLayoutEntry layoutEntries[3] = {}; + layoutEntries[0].binding = 0; + layoutEntries[0].visibility = wgpu::ShaderStage::Vertex; + layoutEntries[0].buffer.type = wgpu::BufferBindingType::Uniform; + layoutEntries[0].buffer.hasDynamicOffset = true; + layoutEntries[0].buffer.minBindingSize = 64; + layoutEntries[1].binding = 1; + layoutEntries[1].visibility = wgpu::ShaderStage::Fragment; + layoutEntries[1].texture.sampleType = wgpu::TextureSampleType::Float; + layoutEntries[1].texture.viewDimension = wgpu::TextureViewDimension::e2D; + layoutEntries[2].binding = 2; + layoutEntries[2].visibility = wgpu::ShaderStage::Fragment; + layoutEntries[2].sampler.type = wgpu::SamplerBindingType::Filtering; + wgpu::BindGroupLayoutDescriptor layoutDesc; + layoutDesc.entryCount = 3; + layoutDesc.entries = layoutEntries; + bindGroupLayout_ = device_.CreateBindGroupLayout(&layoutDesc); + + wgpu::PipelineLayoutDescriptor pipelineLayoutDesc; + pipelineLayoutDesc.bindGroupLayoutCount = 1; + pipelineLayoutDesc.bindGroupLayouts = &bindGroupLayout_; + wgpu::PipelineLayout pipelineLayout = device_.CreatePipelineLayout(&pipelineLayoutDesc); + + wgpu::VertexAttribute attributes[3] = {}; + attributes[0].format = wgpu::VertexFormat::Float32x3; + attributes[0].offset = 0; + attributes[0].shaderLocation = 0; + attributes[1].format = wgpu::VertexFormat::Float32x4; + attributes[1].offset = 12; + attributes[1].shaderLocation = 1; + attributes[2].format = wgpu::VertexFormat::Float32x2; + attributes[2].offset = 28; + attributes[2].shaderLocation = 2; + wgpu::VertexBufferLayout bufferLayout = {}; + bufferLayout.arrayStride = sizeof(GpuVertex); + bufferLayout.stepMode = wgpu::VertexStepMode::Vertex; + bufferLayout.attributeCount = 3; + bufferLayout.attributes = attributes; + + for(int i = 0; i < 12; i++) { + const int prim = i % 6; + const bool depth = i >= 6; + const bool lines = prim >= 2 && prim < 4; + const bool points = prim >= 4; + const bool alpha = (prim % 2) == 1; + wgpu::BlendState blend = {}; + if(alpha) { + blend.color.srcFactor = wgpu::BlendFactor::SrcAlpha; + blend.color.dstFactor = wgpu::BlendFactor::OneMinusSrcAlpha; + blend.alpha.srcFactor = wgpu::BlendFactor::SrcAlpha; + blend.alpha.dstFactor = wgpu::BlendFactor::OneMinusSrcAlpha; + } else { + blend.color.srcFactor = wgpu::BlendFactor::One; + blend.color.dstFactor = wgpu::BlendFactor::Zero; + blend.alpha.srcFactor = wgpu::BlendFactor::One; + blend.alpha.dstFactor = wgpu::BlendFactor::Zero; + } + blend.color.operation = wgpu::BlendOperation::Add; + blend.alpha.operation = wgpu::BlendOperation::Add; + + wgpu::ColorTargetState target = {}; + target.format = surfaceFormat_; + target.blend = &blend; + target.writeMask = wgpu::ColorWriteMask::All; + + wgpu::FragmentState fragment = {}; + fragment.module = module; + fragment.entryPoint = "fs"; + fragment.targetCount = 1; + fragment.targets = ⌖ + + wgpu::DepthStencilState depthState = {}; + depthState.format = wgpu::TextureFormat::Depth24Plus; + depthState.depthWriteEnabled = depth ? wgpu::OptionalBool::True : wgpu::OptionalBool::False; + depthState.depthCompare = depth ? wgpu::CompareFunction::Less : wgpu::CompareFunction::Always; + + wgpu::PrimitiveTopology topology = wgpu::PrimitiveTopology::TriangleList; + if(lines) { + topology = wgpu::PrimitiveTopology::LineList; + } else if(points) { + topology = wgpu::PrimitiveTopology::PointList; + } + + wgpu::RenderPipelineDescriptor pipe = {}; + pipe.layout = pipelineLayout; + pipe.vertex.module = module; + pipe.vertex.entryPoint = "vs"; + pipe.vertex.bufferCount = 1; + pipe.vertex.buffers = &bufferLayout; + pipe.fragment = &fragment; + pipe.primitive.topology = topology; + pipe.primitive.cullMode = wgpu::CullMode::None; + pipe.depthStencil = &depthState; + pipe.multisample.count = 1; + pipelines_[i] = device_.CreateRenderPipeline(&pipe); + } + pipelinesReady_ = true; +} + +void ofWebGPURenderer::flushBatches() { + if(!inFrame_ || !pass_ || gpuVerts_.empty() || !device_ || !queue_) { + gpuVerts_.clear(); + batches_.clear(); + return; + } + ensurePipelines(); + if(!pipelinesReady_ || !whiteView_ || !sampler_) { + gpuVerts_.clear(); + batches_.clear(); + return; + } + + const uint64_t vertexBytes = static_cast(gpuVerts_.size()) * sizeof(GpuVertex); + const uint64_t uniformStride = 256; + const uint64_t uniformBytes = std::max(uniformStride, static_cast(batches_.size()) * uniformStride); + FrameSlot & slot = slots_[slotIndex_]; + slotIndex_ = (slotIndex_ + 1) % 3; + if(!slot.vertices || slot.vertexBytes < vertexBytes) { + wgpu::BufferDescriptor bd = {}; + bd.size = std::max(vertexBytes, 256); + bd.usage = wgpu::BufferUsage::Vertex | wgpu::BufferUsage::CopyDst; + slot.vertices = device_.CreateBuffer(&bd); + slot.vertexBytes = bd.size; + } + if(!slot.uniforms || slot.uniformBytes < uniformBytes) { + wgpu::BufferDescriptor bd = {}; + bd.size = std::max(uniformBytes, 256); + bd.usage = wgpu::BufferUsage::Uniform | wgpu::BufferUsage::CopyDst; + slot.uniforms = device_.CreateBuffer(&bd); + slot.uniformBytes = bd.size; + } + queue_.WriteBuffer(slot.vertices, 0, gpuVerts_.data(), vertexBytes); + + std::vector uniformData(static_cast(uniformBytes), 0); + for(size_t i = 0; i < batches_.size(); i++) { + std::memcpy(uniformData.data() + i * uniformStride, &batches_[i].mvp, sizeof(glm::mat4)); + } + queue_.WriteBuffer(slot.uniforms, 0, uniformData.data(), uniformBytes); + + struct CachedGroup { + WGPUTextureView key = nullptr; + wgpu::BindGroup group; + }; + std::vector groups; + auto bindFor = [&](wgpu::TextureView view) -> wgpu::BindGroup { + if(!view) { + view = whiteView_; + } + const WGPUTextureView key = view.Get(); + for(const CachedGroup & group : groups) { + if(group.key == key) { + return group.group; + } + } + wgpu::BindGroupEntry entries[3] = {}; + entries[0].binding = 0; + entries[0].buffer = slot.uniforms; + entries[0].offset = 0; + entries[0].size = uniformStride; + entries[1].binding = 1; + entries[1].textureView = view; + entries[2].binding = 2; + entries[2].sampler = sampler_; + wgpu::BindGroupDescriptor bgDesc = {}; + bgDesc.layout = bindGroupLayout_; + bgDesc.entryCount = 3; + bgDesc.entries = entries; + CachedGroup created; + created.key = key; + created.group = device_.CreateBindGroup(&bgDesc); + groups.push_back(created); + return created.group; + }; + + pass_.SetViewport(0, 0, static_cast(width_), static_cast(height_), 0.f, 1.f); + pass_.SetVertexBuffer(0, slot.vertices, 0, vertexBytes); + for(size_t i = 0; i < batches_.size(); i++) { + const DrawBatch & batch = batches_[i]; + if(batch.count == 0 || batch.pipeline < 0 || batch.pipeline >= 12 || !pipelines_[batch.pipeline]) { + continue; + } + uint32_t offset = static_cast(i * uniformStride); + wgpu::BindGroup group = bindFor(batch.view); + pass_.SetPipeline(pipelines_[batch.pipeline]); + pass_.SetBindGroup(0, group, 1, &offset); + pass_.Draw(batch.count, 1, batch.first, 0); + } + gpuVerts_.clear(); + batches_.clear(); +} +ofPath & ofWebGPURenderer::getPath() { return path; } +ofStyle ofWebGPURenderer::getStyle() const { return currentStyle; } +void ofWebGPURenderer::setStyle(const ofStyle & style) { currentStyle = style; } +void ofWebGPURenderer::pushStyle() { styleHistory.push_back(currentStyle); } +void ofWebGPURenderer::popStyle() { + if(!styleHistory.empty()) { + currentStyle = styleHistory.back(); + styleHistory.pop_back(); + } +} +void ofWebGPURenderer::setCurveResolution(int resolution) { currentStyle.curveResolution = resolution; } +void ofWebGPURenderer::setPolyMode(ofPolyWindingMode mode) { currentStyle.polyMode = mode; } +const of3dGraphics & ofWebGPURenderer::get3dGraphics() const { return graphics3d; } +of3dGraphics & ofWebGPURenderer::get3dGraphics() { return graphics3d; } + +#endif diff --git a/libs/openFrameworks/graphics/ofWebGPURenderer.h b/libs/openFrameworks/graphics/ofWebGPURenderer.h new file mode 100644 index 00000000000..a6580fd38ca --- /dev/null +++ b/libs/openFrameworks/graphics/ofWebGPURenderer.h @@ -0,0 +1,252 @@ +#pragma once + +#include "ofConstants.h" +#if OF_USE_DAWN + +#include "ofGraphicsBaseTypes.h" +#include "ofMatrixStack.h" +#include "of3dGraphics.h" +#include "ofPath.h" +#include "ofBitmapFont.h" + +#include +#include +#include + +class ofAppBaseWindow; +template +class ofImage_; + +/// WebGPU renderer backed by Dawn (WGPU -> Metal on Apple). +/// Presents a CAMetalLayer, clears the background, and batches meshes, +/// polylines, paths, 3d primitives, images, video frames, bitmap strings, +/// and TrueType strings into colored (and optionally textured) draws. +/// Shader objects and GPU instancing are not implemented. +class ofWebGPURenderer : public ofBaseRenderer { +public: + static const std::string TYPE; + + ofWebGPURenderer(ofAppBaseWindow * window); + ~ofWebGPURenderer() override; + + const std::string & getType() override { return TYPE; } + + /// Attach a CAMetalLayer (void* to stay C++-safe) and create the device. + bool setup(void * metalLayer, int width, int height, float contentsScale); + void configureSurface(int width, int height); + void setPresentVSync(bool enabled); + void shutdown(); + + bool isSetup() const { return device_ != nullptr && surface_ != nullptr; } + wgpu::Device getDevice() const { return device_; } + wgpu::Queue getQueue() const { return queue_; } + wgpu::Surface getSurface() const { return surface_; } + wgpu::RenderPassEncoder getRenderPass() const { return pass_; } + void * getMetalLayer() const { return metalLayer_; } + + void startRender() override; + void finishRender() override; + + using ofBaseRenderer::draw; + void draw(const ofPolyline & poly) const override; + void draw(const ofPath & shape) const override; + void draw(const ofMesh & vertexData, ofPolyRenderMode renderType, bool useColors, bool useTextures, bool useNormals) const override; + void draw(const of3dPrimitive & model, ofPolyRenderMode renderType) const override; + void draw(const ofNode & model) const override; + void draw(const ofImage & image, float x, float y, float z, float w, float h, float sx, float sy, float sw, float sh) const override; + void draw(const ofFloatImage & image, float x, float y, float z, float w, float h, float sx, float sy, float sw, float sh) const override; + void draw(const ofShortImage & image, float x, float y, float z, float w, float h, float sx, float sy, float sw, float sh) const override; + void draw(const ofBaseVideoDraws & video, float x, float y, float w, float h) const override; + + void pushView() override; + void popView() override; + void viewport(ofRectangle viewport) override; + void viewport(float x = 0, float y = 0, float width = -1, float height = -1, bool vflip = true) override; + void setupScreenPerspective(float width = -1, float height = -1, float fov = 60, float nearDist = 0, float farDist = 0) override; + void setupScreenOrtho(float width = -1, float height = -1, float nearDist = -1, float farDist = 1) override; + void setOrientation(ofOrientation orientation, bool vFlip) override; + ofRectangle getCurrentViewport() const override; + ofRectangle getNativeViewport() const override; + int getViewportWidth() const override; + int getViewportHeight() const override; + bool isVFlipped() const override; + void setCoordHandedness(ofHandednessType handedness) override; + ofHandednessType getCoordHandedness() const override; + + void pushMatrix() override; + void popMatrix() override; + glm::mat4 getCurrentMatrix(ofMatrixMode matrixMode_) const override; + glm::mat4 getCurrentOrientationMatrix() const override; + void translate(float x, float y, float z = 0) override; + void translate(const glm::vec3 & p) override; + void scale(float xAmnt, float yAmnt, float zAmnt = 1) override; + void rotateRad(float radians, float vecX, float vecY, float vecZ) override; + void rotateXRad(float radians) override; + void rotateYRad(float radians) override; + void rotateZRad(float radians) override; + void rotateRad(float radians) override; + void matrixMode(ofMatrixMode mode) override; + void loadIdentityMatrix() override; + void loadMatrix(const glm::mat4 & m) override; + void loadMatrix(const float * m) override; + void multMatrix(const glm::mat4 & m) override; + void multMatrix(const float * m) override; + void loadViewMatrix(const glm::mat4 & m) override; + void multViewMatrix(const glm::mat4 & m) override; + glm::mat4 getCurrentViewMatrix() const override; + glm::mat4 getCurrentNormalMatrix() const override; + + void bind(const ofCamera & camera, const ofRectangle & viewport) override; + void unbind(const ofCamera & camera) override; + void setupGraphicDefaults() override; + void setupScreen() override; + + void setRectMode(ofRectMode mode) override; + ofRectMode getRectMode() override; + void setFillMode(ofFillFlag fill) override; + ofFillFlag getFillMode() override; + void setLineWidth(float lineWidth) override; + void setPointSize(float pointSize) override; + void enablePointSprites() override; + void disablePointSprites() override; + void setDepthTest(bool depthTest) override; + void setBlendMode(ofBlendMode blendMode) override; + void setLineSmoothing(bool smooth) override; + void setCircleResolution(int res) override; + void enableAntiAliasing() override; + void disableAntiAliasing() override; + + void setColor(float r, float g, float b) override; + void setColor(float r, float g, float b, float a) override; + void setColor(const ofFloatColor & color) override; + void setColor(const ofFloatColor & color, float _a) override; + void setColor(float gray) override; + void setHexColor(int hexColor) override; + void setBitmapTextMode(ofDrawBitmapMode mode) override; + + ofFloatColor getBackgroundColor() override; + void setBackgroundColor(const ofFloatColor & c) override; + void background(const ofFloatColor & c) override; + void background(float brightness) override; + void background(int hexColor, int _a = 255) override; + void background(float r, float g, float b, float a = 1.f) override; + void setBackgroundAuto(bool bManual) override; + bool getBackgroundAuto() override; + void clear() override; + void clear(float r, float g, float b, float a = 0) override; + void clear(float brightness, float a = 0) override; + void clearAlpha() override; + + void drawLine(float x1, float y1, float z1, float x2, float y2, float z2) const override; + void drawRectangle(float x, float y, float z, float w, float h) const override; + void drawTriangle(float x1, float y1, float z1, float x2, float y2, float z2, float x3, float y3, float z3) const override; + void drawCircle(float x, float y, float z, float radius) const override; + void drawEllipse(float x, float y, float z, float width, float height) const override; + void drawString(std::string text, float x, float y, float z) const override; + void drawString(const ofTrueTypeFont & font, std::string text, float x, float y) const override; + void drawBackgroundGradient(const ofFloatColor & start, const ofFloatColor & end, ofGradientMode mode) const override; + + ofPath & getPath() override; + ofStyle getStyle() const override; + void setStyle(const ofStyle & style) override; + void pushStyle() override; + void popStyle() override; + void setCurveResolution(int resolution) override; + void setPolyMode(ofPolyWindingMode mode) override; + const of3dGraphics & get3dGraphics() const override; + of3dGraphics & get3dGraphics() override; + +private: + void applyClear(float r, float g, float b, float a); + void beginColorPass(wgpu::TextureView colorView, bool clear, float r, float g, float b, float a); + void ensureDepth(); + void logDrawOnce(const char * what) const; + void ensurePipelines(); + void flushBatches(); + void beginBatch(int primBase) const; + void beginBatch(int primBase, const glm::mat4 & mvp) const; + void pushVert(float x, float y, float z) const; + void pushVert(float x, float y, float z, const ofFloatColor & color, float u, float v) const; + void rebuildCircle(int res) const; + glm::mat4 gpuMvp() const; + void drawMesh(const ofMesh & mesh, ofPolyRenderMode renderType, bool useColors, bool useTextures) const; + wgpu::TextureView uploadRGBA(const void * owner, const unsigned char * rgba, int width, int height, uint64_t stamp) const; + void drawTexturedQuad(float x, float y, float z, float w, float h, float u0, float v0, float u1, float v1, const ofTexture & tex) const; + template + void drawImagePixels(const ofImage_ & image, float x, float y, float z, float w, float h, float sx, float sy, float sw, float sh) const; + + struct GpuVertex { + float x, y, z; + float r, g, b, a; + float u, v; + }; + struct DrawBatch { + uint32_t first = 0; + uint32_t count = 0; + int pipeline = 0; + wgpu::TextureView view; + glm::mat4 mvp { 1.f }; + }; + struct FrameSlot { + wgpu::Buffer vertices; + wgpu::Buffer uniforms; + uint64_t vertexBytes = 0; + uint64_t uniformBytes = 0; + }; + struct TexCache { + const void * owner = nullptr; + uint64_t stamp = 0; + int width = 0; + int height = 0; + wgpu::Texture texture; + wgpu::TextureView view; + }; + + ofAppBaseWindow * window_ = nullptr; + void * metalLayer_ = nullptr; + int width_ = 0; + int height_ = 0; + float contentsScale_ = 1.f; + + wgpu::Instance instance_; + wgpu::Adapter adapter_; + wgpu::Device device_; + wgpu::Queue queue_; + wgpu::Surface surface_; + wgpu::TextureFormat surfaceFormat_ = wgpu::TextureFormat::BGRA8Unorm; + + wgpu::CommandEncoder encoder_; + wgpu::RenderPassEncoder pass_; + bool inFrame_ = false; + bool surfaceConfigured_ = false; + + ofMatrixStack matrixStack; + of3dGraphics graphics3d; + ofPath path; + ofStyle currentStyle; + std::deque styleHistory; + bool bBackgroundAuto = true; + bool bDepthTest_ = false; + bool pointSprites_ = false; + mutable bool loggedDraw_ = false; + bool vsync_ = true; + + mutable std::vector gpuVerts_; + mutable std::vector batches_; + mutable std::vector circleUnit_; + mutable std::vector texCache_; + mutable wgpu::TextureView activeView_; + wgpu::BindGroupLayout bindGroupLayout_; + wgpu::RenderPipeline pipelines_[12]; + wgpu::Texture whiteTex_; + wgpu::TextureView whiteView_; + wgpu::Sampler sampler_; + wgpu::Texture depthTex_; + wgpu::TextureView depthView_; + bool pipelinesReady_ = false; + FrameSlot slots_[3]; + int slotIndex_ = 0; + ofBitmapFont bitmapFont_; +}; + +#endif // OF_USE_DAWN diff --git a/libs/openFrameworks/ofMain.h b/libs/openFrameworks/ofMain.h index c370881f208..b299fd11a31 100644 --- a/libs/openFrameworks/ofMain.h +++ b/libs/openFrameworks/ofMain.h @@ -79,7 +79,14 @@ #include "ofMainLoop.h" #include "ofWindowSettings.h" #if !defined(TARGET_OF_IOS) & !defined(TARGET_ANDROID) & !defined(TARGET_EMSCRIPTEN) & !defined(TARGET_RASPBERRY_PI_LEGACY) - #include "ofAppGLFWWindow.h" + #if OF_USE_DAWN + #include "ofAppDawnWindow.h" + #include "ofWebGPURenderer.h" + #elif OF_USE_ANGLE + #include "ofAppMGLWindow.h" + #else + #include "ofAppGLFWWindow.h" + #endif // #if !defined(TARGET_LINUX_ARM) // #include "ofAppGlutWindow.h" // #endif diff --git a/libs/openFrameworks/utils/ofConstants.h b/libs/openFrameworks/utils/ofConstants.h index 227cbe78104..5609fa9d7db 100644 --- a/libs/openFrameworks/utils/ofConstants.h +++ b/libs/openFrameworks/utils/ofConstants.h @@ -13,6 +13,25 @@ #define OF_USE_LEGACY_VECTOR_MATH 0 #endif +// Renderer backends. Default remains desktop GL / iOS GLES. +// OF_USE_DAWN=1 selects WebGPU (Dawn -> Metal). OF_USE_ANGLE=1 selects +// MetalANGLE + MGLKit (GLES3 -> Metal). The two flags are mutually exclusive. +// OF_USE_ANGLE_OFFICIAL=1 is reserved for chromium ANGLE without MGLKit. +// Pass the flag on the xcodebuild / make command line so the static lib and +// the app are compiled with the same backend. +#ifndef OF_USE_DAWN + #define OF_USE_DAWN 0 +#endif +#ifndef OF_USE_ANGLE + #define OF_USE_ANGLE 0 +#endif +#ifndef OF_USE_ANGLE_OFFICIAL + #define OF_USE_ANGLE_OFFICIAL 0 +#endif +#if OF_USE_DAWN && OF_USE_ANGLE + #error OF_USE_DAWN and OF_USE_ANGLE cannot both be 1 +#endif + // This enables glm's old behavior of initializing with non garbage values #if !defined(GLM_FORCE_CTOR_INIT) #define GLM_FORCE_CTOR_INIT @@ -203,7 +222,6 @@ enum ofTargetPlatform{ #endif #if defined(TARGET_OS_OSX) && !defined(TARGET_OF_IOS) - #define TARGET_GLFW_WINDOW #define OF_CAIRO #define OF_RTAUDIO #ifndef __MACOSX_CORE__ @@ -212,8 +230,21 @@ enum ofTargetPlatform{ #ifndef OF_NO_FMOD #define OF_NO_FMOD #endif - #include "GL/glew.h" - #include "OpenGL/OpenGL.h" + #if OF_USE_ANGLE + // MetalANGLE implements GLES. Desktop glew and OpenGL.framework define the + // same gl* symbols, so this path includes ANGLE's GLES headers instead. + #define TARGET_OPENGLES + // GLES/gl.h emits ES1 entry points only when this is 1, and it does not + // set the default itself. ES2/ES3 headers set it after glTexEnvf is skipped. + #ifndef GL_GLES_PROTOTYPES + #define GL_GLES_PROTOTYPES 1 + #endif + #include + #else + #define TARGET_GLFW_WINDOW + #include "GL/glew.h" + #include "OpenGL/OpenGL.h" + #endif #if defined(__LITTLE_ENDIAN__) #define TARGET_LITTLE_ENDIAN // intel cpu diff --git a/libs/openFrameworks/utils/ofSystemUtils.cpp b/libs/openFrameworks/utils/ofSystemUtils.cpp index 3c4177c662f..786d4b60374 100644 --- a/libs/openFrameworks/utils/ofSystemUtils.cpp +++ b/libs/openFrameworks/utils/ofSystemUtils.cpp @@ -343,7 +343,9 @@ ofFileDialogResult ofSystemLoadDialog(std::string windowTitle, bool bFolderSelec //---------------------------------------------------------------------------------------- #if defined(TARGET_OS_MAC) && !TARGET_OS_IPHONE && !TARGET_OS_WATCH && !TARGET_OS_TV && defined(__OBJC__) @autoreleasepool { +#if !OF_USE_ANGLE NSOpenGLContext *context = [NSOpenGLContext currentContext]; +#endif NSOpenPanel * loadDialog = [NSOpenPanel openPanel]; [loadDialog setAllowsMultipleSelection:NO]; @@ -365,7 +367,9 @@ ofFileDialogResult ofSystemLoadDialog(std::string windowTitle, bool bFolderSelec } NSInteger buttonClicked = [loadDialog runModal]; +#if !OF_USE_ANGLE [context makeCurrentContext]; +#endif restoreAppWindowFocus(); if(buttonClicked == NSModalResponseOK) { @@ -511,13 +515,17 @@ ofFileDialogResult ofSystemSaveDialog(std::string defaultName, std::string messa #if defined(TARGET_OS_MAC) && !TARGET_OS_IPHONE && !TARGET_OS_WATCH && !TARGET_OS_TV && defined(__OBJC__) @autoreleasepool { NSSavePanel * saveDialog = [NSSavePanel savePanel]; +#if !OF_USE_ANGLE NSOpenGLContext *context = [NSOpenGLContext currentContext]; +#endif [saveDialog setMessage:[NSString stringWithUTF8String:messageName.c_str()]]; [saveDialog setNameFieldStringValue:[NSString stringWithUTF8String:defaultName.c_str()]]; NSInteger buttonClicked = [saveDialog runModal]; restoreAppWindowFocus(); +#if !OF_USE_ANGLE [context makeCurrentContext]; +#endif if(buttonClicked == NSModalResponseOK){ results.filePath = std::string([[[saveDialog URL] path] UTF8String]); diff --git a/libs/openFrameworks/video/ofAVFoundationPlayer.mm b/libs/openFrameworks/video/ofAVFoundationPlayer.mm index dddcf6d1cd7..5486fdd4851 100644 --- a/libs/openFrameworks/video/ofAVFoundationPlayer.mm +++ b/libs/openFrameworks/video/ofAVFoundationPlayer.mm @@ -23,7 +23,12 @@ bResetPixels = false; bUpdatePixels = false; bUpdateTexture = false; +#if OF_USE_ANGLE + // CVOpenGLTextureCache targets the desktop GL context, not MetalANGLE. + bUseTextureCache = false; +#else bUseTextureCache = true; +#endif } //-------------------------------------------------------------- @@ -45,7 +50,12 @@ bResetPixels = false; bUpdatePixels = false; bUpdateTexture = false; +#if OF_USE_ANGLE + // CVOpenGLTextureCache targets the desktop GL context, not MetalANGLE. + bUseTextureCache = false; +#else bUseTextureCache = true; +#endif std::swap(videoPlayer, other.videoPlayer); return *this; @@ -125,13 +135,15 @@ &_videoTextureCache); #endif -#ifdef TARGET_OSX +#if defined(TARGET_OSX) && !OF_USE_ANGLE err = CVOpenGLTextureCacheCreate(kCFAllocatorDefault, nullptr, CGLGetCurrentContext(), CGLGetPixelFormat(CGLGetCurrentContext()), nullptr, &_videoTextureCache); +#elif defined(TARGET_OSX) + err = kCVReturnSuccess; #endif if(err) { @@ -175,7 +187,12 @@ bResetPixels = false; bUpdatePixels = false; bUpdateTexture = false; +#if OF_USE_ANGLE + // CVOpenGLTextureCache targets the desktop GL context, not MetalANGLE. + bUseTextureCache = false; +#else bUseTextureCache = true; +#endif } //-------------------------------------------------------------- @@ -195,7 +212,12 @@ bResetPixels = false; bUpdatePixels = false; bUpdateTexture = false; +#if OF_USE_ANGLE + // CVOpenGLTextureCache targets the desktop GL context, not MetalANGLE. + bUseTextureCache = false; +#else bUseTextureCache = true; +#endif } //-------------------------------------------------------------- @@ -446,8 +468,8 @@ texData.tex_t = 1.0f; // these values need to be reset to 1.0 to work properly. texData.tex_u = 1.0f; // assuming this is something to do with the way ios - CVReturn err; - unsigned int textureCacheID; + CVReturn err = kCVReturnSuccess; + unsigned int textureCacheID = 0; #ifdef TARGET_OF_IOS @@ -473,7 +495,7 @@ #endif -#ifdef TARGET_OSX +#if defined(TARGET_OSX) && !OF_USE_ANGLE err = CVOpenGLTextureCacheCreateTextureFromImage(nullptr, _videoTextureCache, @@ -502,7 +524,7 @@ CVOpenGLESTextureCacheFlush(_videoTextureCache, 0); #endif -#ifdef TARGET_OSX +#if defined(TARGET_OSX) && !OF_USE_ANGLE CVOpenGLTextureCacheFlush(_videoTextureCache, 0); #endif killTexture(); @@ -514,7 +536,7 @@ CFRelease(_videoTextureRef); _videoTextureRef = nullptr; } -#elif defined TARGET_OSX +#elif defined(TARGET_OSX) && !OF_USE_ANGLE if (_videoTextureRef != nullptr) { CVOpenGLTextureRelease(_videoTextureRef); _videoTextureRef = nullptr; @@ -533,7 +555,7 @@ } #endif -#ifdef TARGET_OSX +#if defined(TARGET_OSX) && !OF_USE_ANGLE if(_videoTextureCache != nullptr) { CVOpenGLTextureCacheRelease(_videoTextureCache); _videoTextureCache = nullptr; diff --git a/libs/openFrameworksCompiled/project/ios/CoreOF.xcconfig b/libs/openFrameworksCompiled/project/ios/CoreOF.xcconfig index 2a3ae791162..3a91d61d9b3 100644 --- a/libs/openFrameworksCompiled/project/ios/CoreOF.xcconfig +++ b/libs/openFrameworksCompiled/project/ios/CoreOF.xcconfig @@ -23,6 +23,9 @@ HEADER_CURL = "$(OF_PATH)/libs/curl/include" HEADER_URIPARSER = "$(OF_PATH)/libs/uriparser/include" HEADER_PUGIXML = "$(OF_PATH)/libs/pugixml/include" HEADER_OPENSSL = "$(OF_PATH)/libs/openssl/include" +HEADER_DAWN = "$(OF_PATH)/libs/dawn/include" +HEADER_METAL = "$(OF_PATH)/libs/metal/include" +HEADER_METALANGLE = "$(OF_PATH)/libs/metalangle/include" //------- Libraries @@ -41,9 +44,17 @@ LINK_OBJC_RUNTIME = YES OF_CORE_LIBS = $(MISC_FLAGS) $(LIB_OF) -OF_CORE_HEADERS = $(HEADER_OF) $(HEADER_OFXIOS) $(HEADER_UTF8) $(HEADER_FREETYPE) $(HEADER_FREETYPE2) $(HEADER_GLEW) $(HEADER_FREEIMAGE) $(HEADER_TESS2) $(HEADER_RTAUDIO) $(HEADER_JSON) $(HEADER_GLM) $(HEADER_FMT) $(HEADER_CURL) $(HEADER_URIPARSER) $(HEADER_PUGIXML) $(HEADER_OPENSSL) +OF_CORE_HEADERS = $(HEADER_OF) $(HEADER_OFXIOS) $(HEADER_UTF8) $(HEADER_FREETYPE) $(HEADER_FREETYPE2) $(HEADER_GLEW) $(HEADER_FREEIMAGE) $(HEADER_TESS2) $(HEADER_RTAUDIO) $(HEADER_JSON) $(HEADER_GLM) $(HEADER_FMT) $(HEADER_CURL) $(HEADER_URIPARSER) $(HEADER_PUGIXML) $(HEADER_OPENSSL) $(HEADER_DAWN) $(HEADER_METAL) $(HEADER_METALANGLE) -OF_CORE_FRAMEWORKS = -framework AudioToolbox -framework Accelerate -framework AVFoundation -framework CoreAudio -framework CoreGraphics -framework CoreLocation -framework CoreMotion -framework CoreMedia -framework CoreVideo -framework Foundation -framework GameController -framework GLKit -framework MapKit -framework OpenAL -framework OpenGLES -framework UIKit -framework Security -framework CFNetwork -framework QuartzCore -framework CoreHaptics -framework Metal +OF_CORE_FRAMEWORKS = -framework AudioToolbox -framework Accelerate -framework AVFoundation -framework CoreAudio -framework CoreGraphics -framework CoreLocation -framework CoreMotion -framework CoreMedia -framework CoreVideo -framework Foundation -framework GameController -framework GLKit -framework MapKit -framework OpenAL -framework OpenGLES -framework UIKit -framework Security -framework CFNetwork -framework QuartzCore -framework CoreHaptics -framework Metal -framework MetalKit + +OF_DAWN_LIBS[sdk=iphoneos*] = "$(OF_PATH)/libs/dawn/lib/ios/dawn.xcframework/ios-arm64/libwebgpu_dawn.a" -framework Metal -framework MetalKit -framework IOSurface +OF_DAWN_LIBS[sdk=iphonesimulator*] = "$(OF_PATH)/libs/dawn/lib/ios/dawn.xcframework/ios-arm64_x86_64-simulator/libwebgpu_dawn.a" -framework Metal -framework MetalKit -framework IOSurface + +// App link line for OF_USE_ANGLE=1. OpenGLES and GLKit stay off so gl* resolves to MetalANGLE. +OF_ANGLE_APP_FRAMEWORKS = -framework AudioToolbox -framework Accelerate -framework AVFoundation -framework CoreAudio -framework CoreGraphics -framework CoreLocation -framework CoreMotion -framework CoreMedia -framework CoreVideo -framework Foundation -framework GameController -framework MapKit -framework OpenAL -framework UIKit -framework Security -framework CFNetwork -framework QuartzCore -framework CoreHaptics -framework Metal -framework MetalKit -framework IOSurface +OF_ANGLE_LIBS[sdk=iphoneos*] = "$(OF_PATH)/libs/metalangle/lib/ios/MetalANGLE.xcframework/ios-arm64/MetalANGLE.a" +OF_ANGLE_LIBS[sdk=iphonesimulator*] = "$(OF_PATH)/libs/metalangle/lib/ios/MetalANGLE.xcframework/ios-arm64_x86_64-simulator/MetalANGLE.a" GCC_PREPROCESSOR_DEFINITIONS = $(inherited) GLES_SILENCE_DEPRECATION=1 GLM_FORCE_CTOR_INIT GLM_ENABLE_EXPERIMENTAL diff --git a/libs/openFrameworksCompiled/project/ios/iOS+OFLib.xcodeproj/project.pbxproj b/libs/openFrameworksCompiled/project/ios/iOS+OFLib.xcodeproj/project.pbxproj index c338f9b83dd..83239bf9c1d 100644 --- a/libs/openFrameworksCompiled/project/ios/iOS+OFLib.xcodeproj/project.pbxproj +++ b/libs/openFrameworksCompiled/project/ios/iOS+OFLib.xcodeproj/project.pbxproj @@ -183,6 +183,12 @@ E4F76E51176CB27200798745 /* ofBitmapFont.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E4F76DAC176CB27200798745 /* ofBitmapFont.cpp */; }; E4F76E52176CB27200798745 /* ofBitmapFont.h in Headers */ = {isa = PBXBuildFile; fileRef = E4F76DAD176CB27200798745 /* ofBitmapFont.h */; }; E4F76E55176CB27200798745 /* ofGraphics.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E4F76DB0176CB27200798745 /* ofGraphics.cpp */; }; + E4DA02017A00000100000013 /* ofWebGPURenderer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E4DA02017A00000100000003 /* ofWebGPURenderer.cpp */; }; + E4DA02017A00000100000014 /* ofWebGPURenderer.h in Headers */ = {isa = PBXBuildFile; fileRef = E4DA02017A00000100000004 /* ofWebGPURenderer.h */; }; + E4DA02017A00000100000015 /* ofxiOSDawnView.mm in Sources */ = {isa = PBXBuildFile; fileRef = E4DA02017A00000100000005 /* ofxiOSDawnView.mm */; }; + E4DA02017A00000100000016 /* ofxiOSDawnView.h in Headers */ = {isa = PBXBuildFile; fileRef = E4DA02017A00000100000006 /* ofxiOSDawnView.h */; }; + E4DA02017A00000100000017 /* ofxiOSMGLView.mm in Sources */ = {isa = PBXBuildFile; fileRef = E4DA02017A00000100000007 /* ofxiOSMGLView.mm */; }; + E4DA02017A00000100000018 /* ofxiOSMGLView.h in Headers */ = {isa = PBXBuildFile; fileRef = E4DA02017A00000100000008 /* ofxiOSMGLView.h */; }; E4F76E56176CB27200798745 /* ofGraphics.h in Headers */ = {isa = PBXBuildFile; fileRef = E4F76DB1176CB27200798745 /* ofGraphics.h */; }; E4F76E57176CB27200798745 /* ofImage.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E4F76DB2176CB27200798745 /* ofImage.cpp */; }; E4F76E58176CB27200798745 /* ofImage.h in Headers */ = {isa = PBXBuildFile; fileRef = E4F76DB3176CB27200798745 /* ofImage.h */; }; @@ -437,6 +443,12 @@ E4F76DAC176CB27200798745 /* ofBitmapFont.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ofBitmapFont.cpp; sourceTree = ""; }; E4F76DAD176CB27200798745 /* ofBitmapFont.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ofBitmapFont.h; sourceTree = ""; }; E4F76DB0176CB27200798745 /* ofGraphics.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ofGraphics.cpp; sourceTree = ""; }; + E4DA02017A00000100000003 /* ofWebGPURenderer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ofWebGPURenderer.cpp; sourceTree = ""; }; + E4DA02017A00000100000004 /* ofWebGPURenderer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ofWebGPURenderer.h; sourceTree = ""; }; + E4DA02017A00000100000005 /* ofxiOSDawnView.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ofxiOSDawnView.mm; sourceTree = ""; }; + E4DA02017A00000100000006 /* ofxiOSDawnView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ofxiOSDawnView.h; sourceTree = ""; }; + E4DA02017A00000100000007 /* ofxiOSMGLView.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ofxiOSMGLView.mm; sourceTree = ""; }; + E4DA02017A00000100000008 /* ofxiOSMGLView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ofxiOSMGLView.h; sourceTree = ""; }; E4F76DB1176CB27200798745 /* ofGraphics.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ofGraphics.h; sourceTree = ""; }; E4F76DB2176CB27200798745 /* ofImage.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ofImage.cpp; sourceTree = ""; }; E4F76DB3176CB27200798745 /* ofImage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ofImage.h; sourceTree = ""; }; @@ -661,6 +673,10 @@ 90080018204EDB5500DC786A /* ofxiOSGLKView.mm */, 90080015204EDA4600DC786A /* ofxiOSGLKViewController.h */, 90080013204EDA1300DC786A /* ofxiOSGLKViewController.mm */, + E4DA02017A00000100000006 /* ofxiOSDawnView.h */, + E4DA02017A00000100000005 /* ofxiOSDawnView.mm */, + E4DA02017A00000100000008 /* ofxiOSMGLView.h */, + E4DA02017A00000100000007 /* ofxiOSMGLView.mm */, 15594F9015C56A8A00727FF2 /* ofxiOSViewController.h */, 15594F8D15C56A8A00727FF2 /* ofxiOSViewController.mm */, ); @@ -887,6 +903,8 @@ E4F76DAD176CB27200798745 /* ofBitmapFont.h */, E4F76DB0176CB27200798745 /* ofGraphics.cpp */, E4F76DB1176CB27200798745 /* ofGraphics.h */, + E4DA02017A00000100000003 /* ofWebGPURenderer.cpp */, + E4DA02017A00000100000004 /* ofWebGPURenderer.h */, E4F76DB2176CB27200798745 /* ofImage.cpp */, E4F76DB3176CB27200798745 /* ofImage.h */, E4F76DB4176CB27200798745 /* ofPath.cpp */, @@ -1061,6 +1079,9 @@ E4F76E50176CB27200798745 /* of3dGraphics.h in Headers */, E4F76E52176CB27200798745 /* ofBitmapFont.h in Headers */, E4F76E56176CB27200798745 /* ofGraphics.h in Headers */, + E4DA02017A00000100000014 /* ofWebGPURenderer.h in Headers */, + E4DA02017A00000100000016 /* ofxiOSDawnView.h in Headers */, + E4DA02017A00000100000018 /* ofxiOSMGLView.h in Headers */, E4F76E58176CB27200798745 /* ofImage.h in Headers */, E4F76E5A176CB27200798745 /* ofPath.h in Headers */, E4F76E5C176CB27200798745 /* ofPixels.h in Headers */, @@ -1250,6 +1271,9 @@ E4F76E4F176CB27200798745 /* of3dGraphics.cpp in Sources */, E4F76E51176CB27200798745 /* ofBitmapFont.cpp in Sources */, E4F76E55176CB27200798745 /* ofGraphics.cpp in Sources */, + E4DA02017A00000100000013 /* ofWebGPURenderer.cpp in Sources */, + E4DA02017A00000100000015 /* ofxiOSDawnView.mm in Sources */, + E4DA02017A00000100000017 /* ofxiOSMGLView.mm in Sources */, E4F76E57176CB27200798745 /* ofImage.cpp in Sources */, E4F76E59176CB27200798745 /* ofPath.cpp in Sources */, E4F76E5B176CB27200798745 /* ofPixels.cpp in Sources */, diff --git a/libs/openFrameworksCompiled/project/macos/CoreOF.xcconfig b/libs/openFrameworksCompiled/project/macos/CoreOF.xcconfig index 75cf8ae6a0a..17d93774e64 100644 --- a/libs/openFrameworksCompiled/project/macos/CoreOF.xcconfig +++ b/libs/openFrameworksCompiled/project/macos/CoreOF.xcconfig @@ -26,6 +26,9 @@ HEADER_OPENSSL = "$(OF_PATH)/libs/openssl/include" HEADER_BROTLI = "$(OF_PATH)/libs/brotli/include" HEADER_URIPARSER = "$(OF_PATH)/libs/uriparser/include" HEADER_PUGIXML = "$(OF_PATH)/libs/pugixml/include" +HEADER_DAWN = "$(OF_PATH)/libs/dawn/include" +HEADER_METAL = "$(OF_PATH)/libs/metal/include" +HEADER_METALANGLE = "$(OF_PATH)/libs/metalangle/include" //------- Libraries LIB_OF = "$(OF_PATH)/libs/openFrameworksCompiled/lib/macos/openFrameworks.a" @@ -49,7 +52,29 @@ LIB_OF_DEBUG = "$(OF_PATH)/libs/openFrameworksCompiled/lib/macos/openFrameworksD OF_CORE_LIBS = $(MISC_FLAGS) //$(LIB_FMOD) -OF_CORE_HEADERS = $(HEADER_OF) $(HEADER_FREEIMAGE) $(HEADER_UTF8) $(HEADER_JSON) $(HEADER_GLM) $(HEADER_TESS2) $(HEADER_CAIRO) $(HEADER_RTAUDIO) $(HEADER_GLFW) $(HEADER_FREETYPE) $(HEADER_FREETYPE2) $(HEADER_CURL) $(HEADER_URIPARSER) $(HEADER_PUGIXML) $(HEADER_GLEW) $(HEADER_CURL) $(HEADER_OPENSSL) $(HEADER_BROTLI) +// Archives an app must link when it links openFrameworks.a directly. +// openFrameworks.framework absorbs the same xcframeworks in its Frameworks phase. +// Keep these out of OF_CORE_LIBS. The lib project sets OTHER_LDFLAGS = $(OF_CORE_LIBS), +// and these slices are macOS-only. +OF_STATIC_LIBS = "$(OF_PATH)/libs/zlib/lib/macos/zlib.xcframework/macos-arm64_x86_64/zlib.a" "$(OF_PATH)/libs/brotli/lib/macos/brotli.xcframework/macos-arm64_x86_64/brotli.a" "$(OF_PATH)/libs/libpng/lib/macos/libpng.xcframework/macos-arm64_x86_64/libpng.a" "$(OF_PATH)/libs/freetype/lib/macos/freetype.xcframework/macos-arm64_x86_64/libfreetype.a" "$(OF_PATH)/libs/pixman/lib/macos/pixman.xcframework/macos-arm64_x86_64/libpixman-1.a" "$(OF_PATH)/libs/cairo/lib/macos/cairo.xcframework/macos-arm64_x86_64/libcairo.a" "$(OF_PATH)/libs/fmt/lib/macos/fmt.xcframework/macos-arm64_x86_64/libfmt.a" "$(OF_PATH)/libs/pugixml/lib/macos/pugixml.xcframework/macos-arm64_x86_64/libpugixml.a" "$(OF_PATH)/libs/tess2/lib/macos/tess2.xcframework/macos-arm64_x86_64/libtess2.a" "$(OF_PATH)/libs/uriparser/lib/macos/uriparser.xcframework/macos-arm64_x86_64/uriparser.a" "$(OF_PATH)/libs/openssl/lib/macos/libcrypto.xcframework/macos-arm64_x86_64/libcrypto.a" "$(OF_PATH)/libs/openssl/lib/macos/libssl.xcframework/macos-arm64_x86_64/libssl.a" "$(OF_PATH)/libs/curl/lib/macos/curl.xcframework/macos-arm64_x86_64/curl.a" "$(OF_PATH)/libs/FreeImage/lib/macos/FreeImage.xcframework/macos-arm64_x86_64/FreeImage.a" "$(OF_PATH)/libs/glew/lib/macos/glew.xcframework/macos-arm64_x86_64/libGLEW.a" "$(OF_PATH)/libs/glfw/lib/macos/glfw.xcframework/macos-arm64_x86_64/libglfw3.a" "$(OF_PATH)/libs/rtAudio/lib/macos/rtAudio.xcframework/macos-arm64_x86_64/librtaudio.a" + +// Desktop GL or mobile GLES for the openFrameworks framework target. +// Not part of the default app link line: the app xcconfig uses OF_CORE_FRAMEWORKS, +// and an ANGLE build must be able to leave this off. +OF_PLATFORM_GL[sdk=macosx*] = -framework OpenGL +OF_PLATFORM_GL[sdk=iphoneos*] = -framework OpenGLES -framework GLKit +OF_PLATFORM_GL[sdk=iphonesimulator*] = -framework OpenGLES -framework GLKit +OF_PLATFORM_GL[sdk=appletvos*] = -framework OpenGLES -framework GLKit +OF_PLATFORM_GL[sdk=appletvsimulator*] = -framework OpenGLES -framework GLKit + +OF_CORE_HEADERS = $(HEADER_OF) $(HEADER_FREEIMAGE) $(HEADER_UTF8) $(HEADER_JSON) $(HEADER_GLM) $(HEADER_TESS2) $(HEADER_CAIRO) $(HEADER_RTAUDIO) $(HEADER_GLFW) $(HEADER_FREETYPE) $(HEADER_FREETYPE2) $(HEADER_CURL) $(HEADER_URIPARSER) $(HEADER_PUGIXML) $(HEADER_GLEW) $(HEADER_CURL) $(HEADER_OPENSSL) $(HEADER_BROTLI) $(HEADER_DAWN) $(HEADER_METAL) $(HEADER_METALANGLE) + +// Link these from Project.xcconfig when OF_USE_DAWN=1 +OF_DAWN_LIBS = "$(OF_PATH)/libs/dawn/lib/macos/dawn.xcframework/macos-arm64_x86_64/libwebgpu_dawn.a" -framework Metal -framework MetalKit -framework IOSurface + +// Link these from Project.xcconfig when OF_USE_ANGLE=1. Drop OpenGL.framework so its gl* symbols do not collide with MetalANGLE. +OF_ANGLE_LIBS = "$(OF_PATH)/libs/metalangle/lib/macos/MetalANGLE.xcframework/macos-arm64_x86_64/MetalANGLE.a" +OF_ANGLE_APP_FRAMEWORKS = -framework Accelerate -framework AppKit -framework ApplicationServices -framework AudioToolbox -framework AVFoundation -framework Cocoa -framework CoreAudio -framework CoreFoundation -framework CoreMedia -framework CoreServices -framework CoreVideo -framework Foundation -framework IOKit -framework QuartzCore -framework Security -framework CFNetwork -framework SystemConfiguration -framework Metal -framework MetalKit -framework IOSurface OF_CORE_FRAMEWORKS = -framework Accelerate -framework AppKit -framework ApplicationServices -framework AudioToolbox -framework AVFoundation -framework Cocoa -framework CoreAudio -framework CoreFoundation -framework CoreMedia -framework CoreServices -framework CoreVideo -framework Foundation -framework IOKit -framework OpenGL -framework QuartzCore -framework Security -framework CFNetwork -framework SystemConfiguration -framework Metal //-framework AudioToolbox -framework Accelerate -framework AVFoundation -framework CoreAudio -framework CoreGraphics -framework CoreLocation -framework CoreMotion -framework CoreMedia -framework CoreVideo -framework Foundation -framework GameController -framework GLKit -framework MapKit -framework OpenAL -framework OpenGLES -framework UIKit -framework Security -framework QuartzCore -framework CoreHaptics diff --git a/libs/openFrameworksCompiled/project/macos/config.macos.default.mk b/libs/openFrameworksCompiled/project/macos/config.macos.default.mk index e3177e4d516..336233d5dd4 100644 --- a/libs/openFrameworksCompiled/project/macos/config.macos.default.mk +++ b/libs/openFrameworksCompiled/project/macos/config.macos.default.mk @@ -44,6 +44,12 @@ PLATFORM_RUN_COMMAND = cd bin/$(BIN_NAME).app/Contents/MacOS/;./$(BIN_NAME) ########################################################################################## PLATFORM_DEFINES = __MACOSX_CORE__ +ifeq ($(OF_USE_DAWN),1) + PLATFORM_DEFINES += OF_USE_DAWN=1 +endif +ifeq ($(OF_USE_ANGLE),1) + PLATFORM_DEFINES += OF_USE_ANGLE=1 +endif ########################################################################################## # PLATFORM REQUIRED ADDON @@ -226,6 +232,16 @@ PLATFORM_LDFLAGS = -stdlib=$(MAC_OS_STD_LIB) #PLATFORM_LDFLAGS += -arch i386 # PLATFORM_LDFLAGS += -lcurl PLATFORM_LDFLAGS += -mmacosx-version-min=$(MAC_OS_MIN_VERSION) -v +ifeq ($(OF_USE_DAWN),1) + PLATFORM_CFLAGS += -I$(OF_ROOT)/libs/dawn/include + PLATFORM_LDFLAGS += $(OF_ROOT)/libs/dawn/lib/macos/dawn.xcframework/macos-arm64_x86_64/libwebgpu_dawn.a +endif +ifeq ($(OF_USE_ANGLE),1) + PLATFORM_CFLAGS += -I$(OF_ROOT)/libs/metalangle/include + PLATFORM_CFLAGS += -I$(OF_ROOT)/libs/metal/include + PLATFORM_LDFLAGS += -ObjC + PLATFORM_LDFLAGS += $(OF_ROOT)/libs/metalangle/lib/macos/MetalANGLE.xcframework/macos-arm64_x86_64/MetalANGLE.a +endif ########################################################################################## # PLATFORM OPTIMIZATION CFLAGS @@ -287,6 +303,11 @@ PLATFORM_CORE_EXCLUSIONS += $(OF_LIBS_PATH)/openFrameworks/app/ofAppEGLWindow.cp PLATFORM_CORE_EXCLUSIONS += $(OF_LIBS_PATH)/fmod/% PLATFORM_CORE_EXCLUSIONS += $(OF_LIBS_PATH)/openFrameworks/sound/ofFmodSoundPlayer.cpp PLATFORM_CFLAGS += -DUSE_FMOD=0 + +# Dawn and MetalANGLE are opt-in. Do not auto-link every xcframework slice +# into the default OpenGL build. +PLATFORM_CORE_EXCLUSIONS += $(OF_LIBS_PATH)/dawn/% +PLATFORM_CORE_EXCLUSIONS += $(OF_LIBS_PATH)/metalangle/% # endif ########################################################################################## @@ -370,6 +391,15 @@ PLATFORM_FRAMEWORKS += QuartzCore PLATFORM_FRAMEWORKS += Security PLATFORM_FRAMEWORKS += CFNetwork PLATFORM_FRAMEWORKS += SystemConfiguration +ifeq ($(OF_USE_DAWN),1) + PLATFORM_FRAMEWORKS += MetalKit + PLATFORM_FRAMEWORKS += IOSurface +endif +ifeq ($(OF_USE_ANGLE),1) + PLATFORM_FRAMEWORKS := $(filter-out OpenGL,$(PLATFORM_FRAMEWORKS)) + PLATFORM_FRAMEWORKS += MetalKit + PLATFORM_FRAMEWORKS += IOSurface +endif ifeq ($(USE_GST),1) diff --git a/libs/openFrameworksCompiled/project/macos/openFrameworksLib.xcodeproj/project.pbxproj b/libs/openFrameworksCompiled/project/macos/openFrameworksLib.xcodeproj/project.pbxproj index 4974f0316e7..1a76df2a8ab 100644 --- a/libs/openFrameworksCompiled/project/macos/openFrameworksLib.xcodeproj/project.pbxproj +++ b/libs/openFrameworksCompiled/project/macos/openFrameworksLib.xcodeproj/project.pbxproj @@ -17,6 +17,11 @@ 2292E73F19E3049700DE9411 /* ofBufferObject.h in Headers */ = {isa = PBXBuildFile; fileRef = 2292E73D19E3049700DE9411 /* ofBufferObject.h */; }; 229EB9A61B3181C800FF7B5F /* ofEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 229EB9A51B3181C800FF7B5F /* ofEvent.h */; }; 22A1C453170AFCB60079E473 /* ofRendererCollection.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 22A1C452170AFCB60079E473 /* ofRendererCollection.cpp */; }; + E4DA01017A00000100000011 /* ofAppDawnWindow.mm in Sources */ = {isa = PBXBuildFile; fileRef = E4DA01017A00000100000001 /* ofAppDawnWindow.mm */; }; + E4DA01017A00000100000041 /* ofAppMGLWindow.mm in Sources */ = {isa = PBXBuildFile; fileRef = E4DA01017A00000100000031 /* ofAppMGLWindow.mm */; }; + E4DA01017A00000100000012 /* ofAppDawnWindow.h in Headers */ = {isa = PBXBuildFile; fileRef = E4DA01017A00000100000002 /* ofAppDawnWindow.h */; }; + E4DA01017A00000100000013 /* ofWebGPURenderer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E4DA01017A00000100000003 /* ofWebGPURenderer.cpp */; }; + E4DA01017A00000100000014 /* ofWebGPURenderer.h in Headers */ = {isa = PBXBuildFile; fileRef = E4DA01017A00000100000004 /* ofWebGPURenderer.h */; }; 22FAD01E17049373002A7EB3 /* ofAppGLFWWindow.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 22FAD01C17049373002A7EB3 /* ofAppGLFWWindow.cpp */; }; 22FAD01F17049373002A7EB3 /* ofAppGLFWWindow.h in Headers */ = {isa = PBXBuildFile; fileRef = 22FAD01D17049373002A7EB3 /* ofAppGLFWWindow.h */; }; 27DEA3111796F578000A9E90 /* ofXml.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 27DEA30F1796F578000A9E90 /* ofXml.cpp */; }; @@ -139,6 +144,11 @@ BF6277052BADCF7C008864C1 /* ofMainLoop.h in Sources */ = {isa = PBXBuildFile; fileRef = 9979E8211A1CCC44007E55D1 /* ofMainLoop.h */; }; BF6277062BADCF7C008864C1 /* ofAppNoWindow.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E495DF7B178896A900994238 /* ofAppNoWindow.cpp */; }; BF6277072BADCF7C008864C1 /* ofAppNoWindow.h in Sources */ = {isa = PBXBuildFile; fileRef = E495DF7C178896A900994238 /* ofAppNoWindow.h */; }; + E4DA01017A00000100000021 /* ofAppDawnWindow.mm in Sources */ = {isa = PBXBuildFile; fileRef = E4DA01017A00000100000001 /* ofAppDawnWindow.mm */; }; + E4DA01017A00000100000051 /* ofAppMGLWindow.mm in Sources */ = {isa = PBXBuildFile; fileRef = E4DA01017A00000100000031 /* ofAppMGLWindow.mm */; }; + E4DA01017A00000100000022 /* ofAppDawnWindow.h in Sources */ = {isa = PBXBuildFile; fileRef = E4DA01017A00000100000002 /* ofAppDawnWindow.h */; }; + E4DA01017A00000100000023 /* ofWebGPURenderer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E4DA01017A00000100000003 /* ofWebGPURenderer.cpp */; }; + E4DA01017A00000100000024 /* ofWebGPURenderer.h in Sources */ = {isa = PBXBuildFile; fileRef = E4DA01017A00000100000004 /* ofWebGPURenderer.h */; }; BF6277082BADCF7C008864C1 /* ofAppGLFWWindow.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 22FAD01C17049373002A7EB3 /* ofAppGLFWWindow.cpp */; }; BF6277092BADCF7C008864C1 /* ofxOpenALSoundPlayer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = BF23BF682BAC872D000E2E0E /* ofxOpenALSoundPlayer.cpp */; }; BF62770A2BADCF7C008864C1 /* ofAppGLFWWindow.h in Sources */ = {isa = PBXBuildFile; fileRef = 22FAD01D17049373002A7EB3 /* ofAppGLFWWindow.h */; }; @@ -147,6 +157,8 @@ BF62770D2BADCF7C008864C1 /* ofxtvOSGLKViewController.mm in Sources */ = {isa = PBXBuildFile; fileRef = BF23BF762BAC872D000E2E0E /* ofxtvOSGLKViewController.mm */; }; BF62770E2BADCF7C008864C1 /* ofAppBaseWindow.h in Sources */ = {isa = PBXBuildFile; fileRef = E4B27AAC10CBE92A00536013 /* ofAppBaseWindow.h */; }; BF62770F2BADCF7C008864C1 /* ofxiOSEAGLView.mm in Sources */ = {isa = PBXBuildFile; fileRef = BF23BF4A2BAC872D000E2E0E /* ofxiOSEAGLView.mm */; }; + E4DA04017A00000100000011 /* ofxiOSDawnView.mm in Sources */ = {isa = PBXBuildFile; fileRef = E4DA04017A00000100000001 /* ofxiOSDawnView.mm */; }; + E4DA04017A00000100000013 /* ofxiOSMGLView.mm in Sources */ = {isa = PBXBuildFile; fileRef = E4DA04017A00000100000003 /* ofxiOSMGLView.mm */; }; BF6277102BADCF7C008864C1 /* ofAppRunner.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E4B27AAF10CBE92A00536013 /* ofAppRunner.cpp */; }; BF6277112BADCF7C008864C1 /* ofAppRunner.h in Sources */ = {isa = PBXBuildFile; fileRef = E4B27AB010CBE92A00536013 /* ofAppRunner.h */; }; BF6277122BADCF7C008864C1 /* ofBaseApp.h in Sources */ = {isa = PBXBuildFile; fileRef = E4B27AB110CBE92A00536013 /* ofBaseApp.h */; }; @@ -318,24 +330,24 @@ BF6277B92BADCF7C008864C1 /* OpenGL.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF6276B22BADC6C9008864C1 /* OpenGL.framework */; platformFilters = (maccatalyst, macos, ); settings = {ATTRIBUTES = (Required, ); }; }; BF6277BA2BADCF7C008864C1 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF6276B32BADC6C9008864C1 /* Cocoa.framework */; platformFilters = (macos, ); settings = {ATTRIBUTES = (Required, ); }; }; BF6277BB2BADCF7C008864C1 /* pixman.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF6609F02BA1DC9F005FA2B1 /* pixman.xcframework */; platformFilters = (macos, ); settings = {ATTRIBUTES = (Weak, ); }; }; - BF6277BC2BADCF7C008864C1 /* zlib.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF660A462BA1DD1D005FA2B1 /* zlib.xcframework */; }; - BF6277BD2BADCF7C008864C1 /* curl.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF660A4A2BA1DD3B005FA2B1 /* curl.xcframework */; platformFilters = (ios, macos, xros, ); settings = {ATTRIBUTES = (Weak, ); }; }; + BF6277BC2BADCF7C008864C1 /* zlib.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF660A462BA1DD1D005FA2B1 /* zlib.xcframework */; platformFilters = (ios, macos, tvos, ); }; + BF6277BD2BADCF7C008864C1 /* curl.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF660A4A2BA1DD3B005FA2B1 /* curl.xcframework */; platformFilters = (ios, macos, tvos, ); settings = {ATTRIBUTES = (Weak, ); }; }; BF6277BE2BADCF7C008864C1 /* cairo.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF6609E62BA1DC13005FA2B1 /* cairo.xcframework */; platformFilters = (macos, ); settings = {ATTRIBUTES = (Weak, ); }; }; - BF6277BF2BADCF7C008864C1 /* FreeImage.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF6609E42BA1DB6F005FA2B1 /* FreeImage.xcframework */; }; - BF6277C02BADCF7C008864C1 /* tess2.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF660A422BA1DCF9005FA2B1 /* tess2.xcframework */; }; - BF6277C12BADCF7C008864C1 /* pugixml.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF6609F22BA1DCAD005FA2B1 /* pugixml.xcframework */; }; + BF6277BF2BADCF7C008864C1 /* FreeImage.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF6609E42BA1DB6F005FA2B1 /* FreeImage.xcframework */; platformFilters = (ios, macos, tvos, ); }; + BF6277C02BADCF7C008864C1 /* tess2.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF660A422BA1DCF9005FA2B1 /* tess2.xcframework */; platformFilters = (ios, macos, tvos, ); }; + BF6277C12BADCF7C008864C1 /* pugixml.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF6609F22BA1DCAD005FA2B1 /* pugixml.xcframework */; platformFilters = (ios, macos, tvos, ); }; BF6277C22BADCF7C008864C1 /* glew.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF6609EA2BA1DC57005FA2B1 /* glew.xcframework */; platformFilters = (macos, ); settings = {ATTRIBUTES = (Weak, ); }; }; - BF6277C32BADCF7C008864C1 /* glfw.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF6609EC2BA1DC6B005FA2B1 /* glfw.xcframework */; platformFilters = (maccatalyst, macos, ); settings = {ATTRIBUTES = (Weak, ); }; }; + BF6277C32BADCF7C008864C1 /* glfw.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF6609EC2BA1DC6B005FA2B1 /* glfw.xcframework */; platformFilters = (macos, ); settings = {ATTRIBUTES = (Weak, ); }; }; BF6277C42BADCF7C008864C1 /* rtAudio.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF6609F42BA1DCBB005FA2B1 /* rtAudio.xcframework */; platformFilters = (macos, ); settings = {ATTRIBUTES = (Weak, ); }; }; - BF6277C52BADCF7C008864C1 /* fmt.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF660A482BA1DD2E005FA2B1 /* fmt.xcframework */; }; - BF6277C62BADCF7C008864C1 /* uriparser.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF660A442BA1DD04005FA2B1 /* uriparser.xcframework */; }; - BF6277C72BADCF7C008864C1 /* libpng.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF6609EE2BA1DC91005FA2B1 /* libpng.xcframework */; }; - BF6277C82BADCF7C008864C1 /* freetype.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF6609E82BA1DC4B005FA2B1 /* freetype.xcframework */; }; + BF6277C52BADCF7C008864C1 /* fmt.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF660A482BA1DD2E005FA2B1 /* fmt.xcframework */; platformFilters = (ios, macos, tvos, ); }; + BF6277C62BADCF7C008864C1 /* uriparser.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF660A442BA1DD04005FA2B1 /* uriparser.xcframework */; platformFilters = (ios, macos, tvos, ); }; + BF6277C72BADCF7C008864C1 /* libpng.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF6609EE2BA1DC91005FA2B1 /* libpng.xcframework */; platformFilters = (ios, macos, tvos, ); }; + BF6277C82BADCF7C008864C1 /* freetype.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF6609E82BA1DC4B005FA2B1 /* freetype.xcframework */; platformFilters = (ios, macos, tvos, ); }; BF6277C92BADCF7C008864C1 /* openssl.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF6276A92BADB16E008864C1 /* openssl.xcframework */; platformFilters = (ios, macos, xros, ); settings = {ATTRIBUTES = (Weak, ); }; }; - BF8551622BADB16E008864C1 /* libssl.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF8551602BADB16E008864C1 /* libssl.xcframework */; platformFilters = (ios, macos, xros, ); settings = {ATTRIBUTES = (Weak, ); }; }; - BF8551652BADB16E008864C1 /* libcrypto.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF8551632BADB16E008864C1 /* libcrypto.xcframework */; platformFilters = (ios, macos, xros, ); settings = {ATTRIBUTES = (Weak, ); }; }; + BF8551622BADB16E008864C1 /* libssl.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF8551602BADB16E008864C1 /* libssl.xcframework */; platformFilters = (ios, macos, tvos, ); settings = {ATTRIBUTES = (Weak, ); }; }; + BF8551652BADB16E008864C1 /* libcrypto.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF8551632BADB16E008864C1 /* libcrypto.xcframework */; platformFilters = (ios, macos, tvos, ); settings = {ATTRIBUTES = (Weak, ); }; }; BF6277CA2BADCF7C008864C1 /* TVUIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF23C0292BAD0954000E2E0E /* TVUIKit.framework */; platformFilters = (tvos, ); }; - BF6277CB2BADCF7C008864C1 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF23C02A2BAD0955000E2E0E /* UIKit.framework */; platformFilters = (ios, maccatalyst, tvos, watchos, xros, ); }; + BF6277CB2BADCF7C008864C1 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF23C02A2BAD0955000E2E0E /* UIKit.framework */; platformFilters = (ios, maccatalyst, tvos, xros, ); }; BF6277CC2BADCF7C008864C1 /* IOKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF23C0272BAD02DE000E2E0E /* IOKit.framework */; platformFilters = (ios, maccatalyst, macos, xros, ); settings = {ATTRIBUTES = (Weak, ); }; }; BF6277CE2BADCF7C008864C1 /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF23BDAB2BAC7A6F000E2E0E /* AudioToolbox.framework */; platformFilters = (ios, maccatalyst, macos, tvos, xros, ); }; BF6277CF2BADCF7C008864C1 /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF23BDAC2BAC7A6F000E2E0E /* AVFoundation.framework */; }; @@ -344,15 +356,15 @@ BF6277D22BADCF7C008864C1 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF23C00B2BAC8A1C000E2E0E /* QuartzCore.framework */; }; BF6277D32BADCF7C008864C1 /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF23BDAD2BAC7A6F000E2E0E /* CoreFoundation.framework */; }; BF6277D42BADCF7C008864C1 /* Accelerate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF23BDAE2BAC7A6F000E2E0E /* Accelerate.framework */; }; - BF6277D52BADCF7C008864C1 /* CoreAudioTypes.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF23BDAF2BAC7A6F000E2E0E /* CoreAudioTypes.framework */; platformFilters = (maccatalyst, macos, watchos, xros, ); }; - BF6277D62BADCF7C008864C1 /* MapKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF23C0052BAC8A1C000E2E0E /* MapKit.framework */; }; + BF6277D52BADCF7C008864C1 /* CoreAudioTypes.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF23BDAF2BAC7A6F000E2E0E /* CoreAudioTypes.framework */; platformFilters = (maccatalyst, macos, xros, ); }; + BF6277D62BADCF7C008864C1 /* MapKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF23C0052BAC8A1C000E2E0E /* MapKit.framework */; platformFilters = (ios, maccatalyst, macos, tvos, xros, ); }; BF6277D72BADCF7C008864C1 /* CoreData.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF23BDB02BAC7A6F000E2E0E /* CoreData.framework */; }; BF6277D82BADCF7C008864C1 /* CoreHaptics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF23BDBA2BAC7DD0000E2E0E /* CoreHaptics.framework */; platformFilters = (ios, maccatalyst, macos, tvos, xros, ); }; BF6277D92BADCF7C008864C1 /* GLKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF23BDBC2BAC7DD0000E2E0E /* GLKit.framework */; platformFilters = (ios, tvos, ); }; BF6277DA2BADCF7C008864C1 /* CoreMedia.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF23C01D2BAC8A1C000E2E0E /* CoreMedia.framework */; }; BF6277DB2BADCF7C008864C1 /* CoreAudio.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF23BDB12BAC7A6F000E2E0E /* CoreAudio.framework */; }; BF6277DC2BADCF7C008864C1 /* CoreMedia.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF23BDC72BAC7DD0000E2E0E /* CoreMedia.framework */; }; - BF6277DD2BADCF7C008864C1 /* CoreMotion.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF23C0212BAC8A1C000E2E0E /* CoreMotion.framework */; platformFilters = (ios, maccatalyst, macos, watchos, xros, ); settings = {ATTRIBUTES = (Weak, ); }; }; + BF6277DD2BADCF7C008864C1 /* CoreMotion.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF23C0212BAC8A1C000E2E0E /* CoreMotion.framework */; platformFilters = (ios, maccatalyst, macos, xros, ); settings = {ATTRIBUTES = (Weak, ); }; }; BF6277DE2BADCF7C008864C1 /* CoreVideo.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF23BDC82BAC7DD0000E2E0E /* CoreVideo.framework */; }; BF6277DF2BADCF7C008864C1 /* CoreAudio.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF23C01B2BAC8A1C000E2E0E /* CoreAudio.framework */; }; BF6277E02BADCF7C008864C1 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF23BDBE2BAC7DD0000E2E0E /* QuartzCore.framework */; platformFilters = (ios, maccatalyst, macos, tvos, xros, ); }; @@ -366,7 +378,7 @@ BF6277E82BADCF7C008864C1 /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF23C0192BAC8A1C000E2E0E /* AVFoundation.framework */; }; BF6277E92BADCF7C008864C1 /* OpenAL.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF23BDC32BAC7DD0000E2E0E /* OpenAL.framework */; platformFilters = (ios, maccatalyst, macos, tvos, ); }; BF6277F22BADD5A9008864C1 /* AVFAudio.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF6277F12BADD5A9008864C1 /* AVFAudio.framework */; }; - BF6279EE2BB075A6008864C1 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF6279ED2BB075A6008864C1 /* Security.framework */; platformFilters = (maccatalyst, macos, watchos, xros, ); }; + BF6279EE2BB075A6008864C1 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF6279ED2BB075A6008864C1 /* Security.framework */; platformFilters = (ios, maccatalyst, macos, tvos, xros, ); }; BF6279F02BB075DB008864C1 /* ApplicationServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF6279EF2BB075DB008864C1 /* ApplicationServices.framework */; platformFilters = (maccatalyst, macos, ); }; BF6609E52BA1DB6F005FA2B1 /* FreeImage.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF6609E42BA1DB6F005FA2B1 /* FreeImage.xcframework */; }; BF6609E72BA1DC13005FA2B1 /* cairo.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF6609E62BA1DC13005FA2B1 /* cairo.xcframework */; }; @@ -383,7 +395,7 @@ BF660A492BA1DD2E005FA2B1 /* fmt.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF660A482BA1DD2E005FA2B1 /* fmt.xcframework */; }; BF660A4B2BA1DD3B005FA2B1 /* curl.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF660A4A2BA1DD3B005FA2B1 /* curl.xcframework */; }; BF8D1B102D6AF3EE007984A0 /* brotli.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF8D1B0F2D6AF3EE007984A0 /* brotli.xcframework */; }; - BF8D1B112D6AF3EE007984A0 /* brotli.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF8D1B0F2D6AF3EE007984A0 /* brotli.xcframework */; }; + BF8D1B112D6AF3EE007984A0 /* brotli.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF8D1B0F2D6AF3EE007984A0 /* brotli.xcframework */; platformFilters = (ios, macos, tvos, ); }; BF8D1B132D6AF423007984A0 /* ofMaterialBaseTypes.h in Headers */ = {isa = PBXBuildFile; fileRef = BF8D1B122D6AF423007984A0 /* ofMaterialBaseTypes.h */; }; BF8D1B142D6AF423007984A0 /* ofMaterialBaseTypes.h in Headers */ = {isa = PBXBuildFile; fileRef = BF8D1B122D6AF423007984A0 /* ofMaterialBaseTypes.h */; }; BF8D1B1C2D6AF435007984A0 /* ofSingleton.h in Headers */ = {isa = PBXBuildFile; fileRef = BF8D1B182D6AF435007984A0 /* ofSingleton.h */; }; @@ -516,6 +528,12 @@ 2292E73D19E3049700DE9411 /* ofBufferObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ofBufferObject.h; path = gl/ofBufferObject.h; sourceTree = ""; }; 229EB9A51B3181C800FF7B5F /* ofEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ofEvent.h; sourceTree = ""; }; 22A1C452170AFCB60079E473 /* ofRendererCollection.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ofRendererCollection.cpp; sourceTree = ""; }; + E4DA01017A00000100000001 /* ofAppDawnWindow.mm */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.objcpp; fileEncoding = 4; path = ofAppDawnWindow.mm; sourceTree = ""; }; + E4DA01017A00000100000002 /* ofAppDawnWindow.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ofAppDawnWindow.h; sourceTree = ""; }; + E4DA01017A00000100000031 /* ofAppMGLWindow.mm */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.objcpp; fileEncoding = 4; path = ofAppMGLWindow.mm; sourceTree = ""; }; + E4DA01017A00000100000032 /* ofAppMGLWindow.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ofAppMGLWindow.h; sourceTree = ""; }; + E4DA01017A00000100000003 /* ofWebGPURenderer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ofWebGPURenderer.cpp; sourceTree = ""; }; + E4DA01017A00000100000004 /* ofWebGPURenderer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ofWebGPURenderer.h; sourceTree = ""; }; 22FAD01C17049373002A7EB3 /* ofAppGLFWWindow.cpp */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.objcpp; fileEncoding = 4; path = ofAppGLFWWindow.cpp; sourceTree = ""; }; 22FAD01D17049373002A7EB3 /* ofAppGLFWWindow.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ofAppGLFWWindow.h; sourceTree = ""; }; 27DEA30F1796F578000A9E90 /* ofXml.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ofXml.cpp; sourceTree = ""; }; @@ -589,6 +607,10 @@ BF23BF482BAC872D000E2E0E /* ofxiOSAppDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ofxiOSAppDelegate.mm; sourceTree = ""; }; BF23BF492BAC872D000E2E0E /* ofxiOSEAGLView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ofxiOSEAGLView.h; sourceTree = ""; }; BF23BF4A2BAC872D000E2E0E /* ofxiOSEAGLView.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ofxiOSEAGLView.mm; sourceTree = ""; }; + E4DA04017A00000100000001 /* ofxiOSDawnView.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ofxiOSDawnView.mm; sourceTree = ""; }; + E4DA04017A00000100000002 /* ofxiOSDawnView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ofxiOSDawnView.h; sourceTree = ""; }; + E4DA04017A00000100000003 /* ofxiOSMGLView.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ofxiOSMGLView.mm; sourceTree = ""; }; + E4DA04017A00000100000004 /* ofxiOSMGLView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ofxiOSMGLView.h; sourceTree = ""; }; BF23BF4B2BAC872D000E2E0E /* ofxiOSGLKView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ofxiOSGLKView.h; sourceTree = ""; }; BF23BF4C2BAC872D000E2E0E /* ofxiOSGLKView.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ofxiOSGLKView.mm; sourceTree = ""; }; BF23BF4D2BAC872D000E2E0E /* ofxiOSGLKViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ofxiOSGLKViewController.h; sourceTree = ""; }; @@ -712,7 +734,17 @@ BF660A462BA1DD1D005FA2B1 /* zlib.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = zlib.xcframework; path = ../../../zlib/lib/macos/zlib.xcframework; sourceTree = ""; }; BF660A482BA1DD2E005FA2B1 /* fmt.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = fmt.xcframework; path = ../../../fmt/lib/macos/fmt.xcframework; sourceTree = ""; }; BF660A4A2BA1DD3B005FA2B1 /* curl.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = curl.xcframework; path = ../../../curl/lib/macos/curl.xcframework; sourceTree = ""; }; - BF8D1B0F2D6AF3EE007984A0 /* brotli.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = brotli.xcframework; path = /Users/one/SOURCE/openFrameworksiOSTIPS/libs/brotli/lib/macos/brotli.xcframework; sourceTree = ""; }; + BF8D1B0F2D6AF3EE007984A0 /* brotli.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = brotli.xcframework; path = ../../../brotli/lib/macos/brotli.xcframework; sourceTree = ""; }; + E4DA05017A00000100000001 /* CFNetwork.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CFNetwork.framework; path = System/Library/Frameworks/CFNetwork.framework; sourceTree = SDKROOT; }; + E4DA05017A00000100000003 /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; }; + E4DA05017A00000100000005 /* MetalKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MetalKit.framework; path = System/Library/Frameworks/MetalKit.framework; sourceTree = SDKROOT; }; + E4DA05017A00000100000007 /* IOSurface.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = IOSurface.framework; path = System/Library/Frameworks/IOSurface.framework; sourceTree = SDKROOT; }; + E4DA05017A00000100000009 /* CoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreServices.framework; path = System/Library/Frameworks/CoreServices.framework; sourceTree = SDKROOT; }; + E4DA05017A00000100000002 /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E4DA05017A00000100000001 /* CFNetwork.framework */; platformFilters = (ios, maccatalyst, macos, tvos, xros, ); }; + E4DA05017A00000100000004 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E4DA05017A00000100000003 /* SystemConfiguration.framework */; platformFilters = (ios, maccatalyst, macos, tvos, ); }; + E4DA05017A00000100000006 /* MetalKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E4DA05017A00000100000005 /* MetalKit.framework */; platformFilters = (ios, maccatalyst, macos, tvos, xros, ); }; + E4DA05017A00000100000008 /* IOSurface.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E4DA05017A00000100000007 /* IOSurface.framework */; platformFilters = (ios, maccatalyst, macos, tvos, xros, ); }; + E4DA05017A0000010000000A /* CoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E4DA05017A00000100000009 /* CoreServices.framework */; platformFilters = (maccatalyst, macos, ); }; BF8D1B122D6AF423007984A0 /* ofMaterialBaseTypes.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ofMaterialBaseTypes.h; path = gl/ofMaterialBaseTypes.h; sourceTree = ""; }; BF8D1B152D6AF435007984A0 /* ofJson.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ofJson.h; sourceTree = ""; }; BF8D1B162D6AF435007984A0 /* ofRandomDistributions.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ofRandomDistributions.h; sourceTree = ""; }; @@ -846,7 +878,6 @@ BF6279F02BB075DB008864C1 /* ApplicationServices.framework in Frameworks */, BF6279EE2BB075A6008864C1 /* Security.framework in Frameworks */, BF6277F22BADD5A9008864C1 /* AVFAudio.framework in Frameworks */, - BF6277B92BADCF7C008864C1 /* OpenGL.framework in Frameworks */, BF6277BA2BADCF7C008864C1 /* Cocoa.framework in Frameworks */, BF6277BB2BADCF7C008864C1 /* pixman.xcframework in Frameworks */, BF6277BC2BADCF7C008864C1 /* zlib.xcframework in Frameworks */, @@ -879,7 +910,11 @@ BF6277D72BADCF7C008864C1 /* CoreData.framework in Frameworks */, BF8D1B112D6AF3EE007984A0 /* brotli.xcframework in Frameworks */, BF6277D82BADCF7C008864C1 /* CoreHaptics.framework in Frameworks */, - BF6277D92BADCF7C008864C1 /* GLKit.framework in Frameworks */, + E4DA05017A00000100000002 /* CFNetwork.framework in Frameworks */, + E4DA05017A00000100000004 /* SystemConfiguration.framework in Frameworks */, + E4DA05017A00000100000006 /* MetalKit.framework in Frameworks */, + E4DA05017A00000100000008 /* IOSurface.framework in Frameworks */, + E4DA05017A0000010000000A /* CoreServices.framework in Frameworks */, BF6277DA2BADCF7C008864C1 /* CoreMedia.framework in Frameworks */, BF6277DB2BADCF7C008864C1 /* CoreAudio.framework in Frameworks */, BF6277DC2BADCF7C008864C1 /* CoreMedia.framework in Frameworks */, @@ -970,6 +1005,11 @@ BF23BDAD2BAC7A6F000E2E0E /* CoreFoundation.framework */, BF6276B32BADC6C9008864C1 /* Cocoa.framework */, BF6276B22BADC6C9008864C1 /* OpenGL.framework */, + E4DA05017A00000100000001 /* CFNetwork.framework */, + E4DA05017A00000100000003 /* SystemConfiguration.framework */, + E4DA05017A00000100000005 /* MetalKit.framework */, + E4DA05017A00000100000007 /* IOSurface.framework */, + E4DA05017A00000100000009 /* CoreServices.framework */, ); name = frameworks; sourceTree = ""; @@ -1000,6 +1040,10 @@ BF23BF482BAC872D000E2E0E /* ofxiOSAppDelegate.mm */, BF23BF492BAC872D000E2E0E /* ofxiOSEAGLView.h */, BF23BF4A2BAC872D000E2E0E /* ofxiOSEAGLView.mm */, + E4DA04017A00000100000002 /* ofxiOSDawnView.h */, + E4DA04017A00000100000001 /* ofxiOSDawnView.mm */, + E4DA04017A00000100000004 /* ofxiOSMGLView.h */, + E4DA04017A00000100000003 /* ofxiOSMGLView.mm */, BF23BF4B2BAC872D000E2E0E /* ofxiOSGLKView.h */, BF23BF4C2BAC872D000E2E0E /* ofxiOSGLKView.mm */, BF23BF4D2BAC872D000E2E0E /* ofxiOSGLKViewController.h */, @@ -1270,6 +1314,10 @@ E495DF7C178896A900994238 /* ofAppNoWindow.h */, 22FAD01C17049373002A7EB3 /* ofAppGLFWWindow.cpp */, 22FAD01D17049373002A7EB3 /* ofAppGLFWWindow.h */, + E4DA01017A00000100000001 /* ofAppDawnWindow.mm */, + E4DA01017A00000100000002 /* ofAppDawnWindow.h */, + E4DA01017A00000100000031 /* ofAppMGLWindow.mm */, + E4DA01017A00000100000032 /* ofAppMGLWindow.h */, E4B27AAC10CBE92A00536013 /* ofAppBaseWindow.h */, E4B27AAF10CBE92A00536013 /* ofAppRunner.cpp */, E4B27AB010CBE92A00536013 /* ofAppRunner.h */, @@ -1481,6 +1529,8 @@ 22A1C452170AFCB60079E473 /* ofRendererCollection.cpp */, DA97FD3612F5A61A005C9991 /* ofCairoRenderer.cpp */, DA97FD3712F5A61A005C9991 /* ofCairoRenderer.h */, + E4DA01017A00000100000003 /* ofWebGPURenderer.cpp */, + E4DA01017A00000100000004 /* ofWebGPURenderer.h */, E4F3BB0012F4C751002D19BB /* ofBitmapFont.cpp */, E4F3BB0112F4C751002D19BB /* ofBitmapFont.h */, E4F3BB0412F4C752002D19BB /* ofGraphics.cpp */, @@ -1800,6 +1850,9 @@ BF6277062BADCF7C008864C1 /* ofAppNoWindow.cpp in Sources */, BF6277072BADCF7C008864C1 /* ofAppNoWindow.h in Sources */, BF6277082BADCF7C008864C1 /* ofAppGLFWWindow.cpp in Sources */, + E4DA01017A00000100000021 /* ofAppDawnWindow.mm in Sources */, + E4DA01017A00000100000051 /* ofAppMGLWindow.mm in Sources */, + E4DA01017A00000100000023 /* ofWebGPURenderer.cpp in Sources */, BF6277092BADCF7C008864C1 /* ofxOpenALSoundPlayer.cpp in Sources */, BF62770A2BADCF7C008864C1 /* ofAppGLFWWindow.h in Sources */, BF62770B2BADCF7C008864C1 /* SoundOutputStream.m in Sources */, @@ -1807,6 +1860,8 @@ BF62770D2BADCF7C008864C1 /* ofxtvOSGLKViewController.mm in Sources */, BF62770E2BADCF7C008864C1 /* ofAppBaseWindow.h in Sources */, BF62770F2BADCF7C008864C1 /* ofxiOSEAGLView.mm in Sources */, + E4DA04017A00000100000011 /* ofxiOSDawnView.mm in Sources */, + E4DA04017A00000100000013 /* ofxiOSMGLView.mm in Sources */, BF6277102BADCF7C008864C1 /* ofAppRunner.cpp in Sources */, BF6277112BADCF7C008864C1 /* ofAppRunner.h in Sources */, BF6277122BADCF7C008864C1 /* ofBaseApp.h in Sources */, @@ -2049,6 +2104,9 @@ DAC22D4116E7A4AF0020226D /* ofParameterGroup.cpp in Sources */, 67D96B971651AF6D00D5242D /* ofGLUtils.cpp in Sources */, 22FAD01E17049373002A7EB3 /* ofAppGLFWWindow.cpp in Sources */, + E4DA01017A00000100000011 /* ofAppDawnWindow.mm in Sources */, + E4DA01017A00000100000041 /* ofAppMGLWindow.mm in Sources */, + E4DA01017A00000100000013 /* ofWebGPURenderer.cpp in Sources */, 22A1C453170AFCB60079E473 /* ofRendererCollection.cpp in Sources */, 22769591170D9DD200604FC3 /* ofMatrixStack.cpp in Sources */, 22246D93176C9987008A8AF4 /* ofGLProgrammableRenderer.cpp in Sources */, @@ -2160,11 +2218,12 @@ MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; + OTHER_LDFLAGS = "$(inherited) $(OF_PLATFORM_GL)"; PRODUCT_BUNDLE_IDENTIFIER = com.openFrameworks.macOS; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = auto; SKIP_INSTALL = YES; - SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator macosx watchos watchsimulator xros xrsimulator"; + SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator macosx xros xrsimulator"; SUPPORTS_MACCATALYST = YES; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = YES; @@ -2173,11 +2232,10 @@ SWIFT_INSTALL_OBJC_HEADER = NO; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2,3,4,7"; + TARGETED_DEVICE_FAMILY = "1,2,3,7"; TVOS_DEPLOYMENT_TARGET = 18.0; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; - WATCHOS_DEPLOYMENT_TARGET = 9.0; }; name = Debug; }; @@ -2270,11 +2328,12 @@ MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20"; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; + OTHER_LDFLAGS = "$(inherited) $(OF_PLATFORM_GL)"; PRODUCT_BUNDLE_IDENTIFIER = com.openFrameworks.macOS; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = auto; SKIP_INSTALL = YES; - SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator macosx watchos watchsimulator xros xrsimulator"; + SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator macosx xros xrsimulator"; SUPPORTS_MACCATALYST = YES; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = YES; @@ -2282,11 +2341,10 @@ SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_INSTALL_OBJC_HEADER = NO; SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2,3,4,7"; + TARGETED_DEVICE_FAMILY = "1,2,3,7"; TVOS_DEPLOYMENT_TARGET = 18.0; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; - WATCHOS_DEPLOYMENT_TARGET = 9.0; }; name = Release; }; @@ -2319,7 +2377,7 @@ isa = XCBuildConfiguration; buildSettings = { BUILT_PRODUCTS_DIR = "$(SRCROOT)/../../lib/macos/"; - CONFIGURATION_BUILD_DIR = "$(SRCROOT)/../../lib/macos/build/output"; + CONFIGURATION_BUILD_DIR = "$(SRCROOT)/../../lib/macos/"; CONFIGURATION_TEMP_DIR = "$(SRCROOT)/../../lib/macos/build/temp/release/"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; EXECUTABLE_PREFIX = ""; diff --git a/libs/openFrameworksCompiled/project/osx/CoreOF.xcconfig b/libs/openFrameworksCompiled/project/osx/CoreOF.xcconfig index 7d1f8f5fda7..51ab860bad5 100644 --- a/libs/openFrameworksCompiled/project/osx/CoreOF.xcconfig +++ b/libs/openFrameworksCompiled/project/osx/CoreOF.xcconfig @@ -24,6 +24,9 @@ HEADER_CURL = "$(OF_PATH)/libs/curl/include" HEADER_SSL = "$(OF_PATH)/libs/openssl/include" HEADER_URIPARSER = "$(OF_PATH)/libs/uriparser/include" HEADER_PUGIXML = "$(OF_PATH)/libs/pugixml/include" +HEADER_DAWN = "$(OF_PATH)/libs/dawn/include" +HEADER_METAL = "$(OF_PATH)/libs/metal/include" +HEADER_METALANGLE = "$(OF_PATH)/libs/metalangle/include" //------- Libraries LIB_OF_RELEASE = "$(OF_PATH)/libs/openFrameworksCompiled/lib/osx/openFrameworks.a" @@ -45,7 +48,13 @@ LIB_OF_DEBUG = "$(OF_PATH)/libs/openFrameworksCompiled/lib/osx/openFrameworksDeb //LIB_PUGIXML = "$(OF_PATH)/libs/pugixml/lib/osx/pugixml.a" -OF_CORE_HEADERS = $(HEADER_OF) $(HEADER_FREETYPE) $(HEADER_FREETYPE2) $(HEADER_GLEW) $(HEADER_FREEIMAGE) $(HEADER_TESS2) $(HEADER_CAIRO) $(HEADER_RTAUDIO) $(HEADER_GLFW) $(HEADER_UTF8) $(HEADER_JSON) $(HEADER_GLM) $(HEADER_CURL) ${HEADER_SSL} $(HEADER_URIPARSER) $(HEADER_PUGIXML) ${HEADER_BROTLI} +OF_CORE_HEADERS = $(HEADER_OF) $(HEADER_FREETYPE) $(HEADER_FREETYPE2) $(HEADER_GLEW) $(HEADER_FREEIMAGE) $(HEADER_TESS2) $(HEADER_CAIRO) $(HEADER_RTAUDIO) $(HEADER_GLFW) $(HEADER_UTF8) $(HEADER_JSON) $(HEADER_GLM) $(HEADER_CURL) ${HEADER_SSL} $(HEADER_URIPARSER) $(HEADER_PUGIXML) ${HEADER_BROTLI} $(HEADER_DAWN) $(HEADER_METAL) $(HEADER_METALANGLE) + +OF_DAWN_LIBS = "$(OF_PATH)/libs/dawn/lib/macos/dawn.xcframework/macos-arm64_x86_64/libwebgpu_dawn.a" -framework Metal -framework MetalKit -framework IOSurface + +// OF_USE_ANGLE=1. OpenGL.framework is omitted so its gl* symbols do not collide with MetalANGLE. +OF_ANGLE_LIBS = "$(OF_PATH)/libs/metalangle/lib/macos/MetalANGLE.xcframework/macos-arm64_x86_64/MetalANGLE.a" +OF_ANGLE_APP_FRAMEWORKS = -framework Accelerate -framework AppKit -framework ApplicationServices -framework AudioToolbox -framework AVFoundation -framework Cocoa -framework CoreAudio -framework CoreFoundation -framework CoreMedia -framework CoreServices -framework CoreVideo -framework Foundation -framework IOKit -framework QuartzCore -framework Security -framework CFNetwork -framework SystemConfiguration -framework Metal -framework MetalKit -framework IOSurface OF_CORE_FRAMEWORKS = -framework Accelerate -framework AppKit -framework ApplicationServices -framework AudioToolbox -framework AVFoundation -framework Cocoa -framework CoreAudio -framework CoreFoundation -framework CoreMedia -framework CoreServices -framework CoreVideo -framework Foundation -framework IOKit -framework OpenGL -framework QuartzCore -framework Security -framework CFNetwork -framework SystemConfiguration -framework Metal diff --git a/libs/openFrameworksCompiled/project/osx/config.osx.default.mk b/libs/openFrameworksCompiled/project/osx/config.osx.default.mk index bff4844e2ee..6740b6e2926 100644 --- a/libs/openFrameworksCompiled/project/osx/config.osx.default.mk +++ b/libs/openFrameworksCompiled/project/osx/config.osx.default.mk @@ -44,6 +44,12 @@ PLATFORM_RUN_COMMAND = cd bin/$(BIN_NAME).app/Contents/MacOS/;./$(BIN_NAME) ########################################################################################## PLATFORM_DEFINES = __MACOSX_CORE__ +ifeq ($(OF_USE_DAWN),1) + PLATFORM_DEFINES += OF_USE_DAWN=1 +endif +ifeq ($(OF_USE_ANGLE),1) + PLATFORM_DEFINES += OF_USE_ANGLE=1 +endif ########################################################################################## # PLATFORM REQUIRED ADDON @@ -228,6 +234,16 @@ PLATFORM_LDFLAGS = -stdlib=$(MAC_OS_STD_LIB) #PLATFORM_LDFLAGS += -arch i386 # PLATFORM_LDFLAGS += -lcurl PLATFORM_LDFLAGS += -mmacosx-version-min=$(MAC_OS_MIN_VERSION) -v +ifeq ($(OF_USE_DAWN),1) + PLATFORM_CFLAGS += -I$(OF_ROOT)/libs/dawn/include + PLATFORM_LDFLAGS += $(OF_ROOT)/libs/dawn/lib/macos/dawn.xcframework/macos-arm64_x86_64/libwebgpu_dawn.a +endif +ifeq ($(OF_USE_ANGLE),1) + PLATFORM_CFLAGS += -I$(OF_ROOT)/libs/metalangle/include + PLATFORM_CFLAGS += -I$(OF_ROOT)/libs/metal/include + PLATFORM_LDFLAGS += -ObjC + PLATFORM_LDFLAGS += $(OF_ROOT)/libs/metalangle/lib/macos/MetalANGLE.xcframework/macos-arm64_x86_64/MetalANGLE.a +endif ########################################################################################## # PLATFORM OPTIMIZATION CFLAGS @@ -289,6 +305,11 @@ PLATFORM_CORE_EXCLUSIONS += $(OF_LIBS_PATH)/openFrameworks/app/ofAppEGLWindow.cp PLATFORM_CORE_EXCLUSIONS += $(OF_LIBS_PATH)/fmod/% PLATFORM_CORE_EXCLUSIONS += $(OF_LIBS_PATH)/openFrameworks/sound/ofFmodSoundPlayer.cpp PLATFORM_CFLAGS += -DUSE_FMOD=0 + +# Dawn and MetalANGLE are opt-in. Do not auto-link every xcframework slice +# into the default OpenGL build. +PLATFORM_CORE_EXCLUSIONS += $(OF_LIBS_PATH)/dawn/% +PLATFORM_CORE_EXCLUSIONS += $(OF_LIBS_PATH)/metalangle/% # endif ########################################################################################## @@ -372,6 +393,15 @@ PLATFORM_FRAMEWORKS += QuartzCore PLATFORM_FRAMEWORKS += Security PLATFORM_FRAMEWORKS += CFNetwork PLATFORM_FRAMEWORKS += SystemConfiguration +ifeq ($(OF_USE_DAWN),1) + PLATFORM_FRAMEWORKS += MetalKit + PLATFORM_FRAMEWORKS += IOSurface +endif +ifeq ($(OF_USE_ANGLE),1) + PLATFORM_FRAMEWORKS := $(filter-out OpenGL,$(PLATFORM_FRAMEWORKS)) + PLATFORM_FRAMEWORKS += MetalKit + PLATFORM_FRAMEWORKS += IOSurface +endif ifeq ($(USE_GST),1) diff --git a/libs/openFrameworksCompiled/project/tvOS/CoreOF.xcconfig b/libs/openFrameworksCompiled/project/tvOS/CoreOF.xcconfig index 6d864b200f2..dde2b78437e 100644 --- a/libs/openFrameworksCompiled/project/tvOS/CoreOF.xcconfig +++ b/libs/openFrameworksCompiled/project/tvOS/CoreOF.xcconfig @@ -25,6 +25,9 @@ HEADER_FMT = "$(OF_PATH)/libs/fmt/include" HEADER_CURL = "$(OF_PATH)/libs/curl/include" HEADER_URIPARSER = "$(OF_PATH)/libs/uriparser/include" HEADER_PUGIXML = "$(OF_PATH)/libs/pugixml/include" +HEADER_DAWN = "$(OF_PATH)/libs/dawn/include" +HEADER_METAL = "$(OF_PATH)/libs/metal/include" +HEADER_METALANGLE = "$(OF_PATH)/libs/metalangle/include" //------- Libraries LIB_OF = "$(OF_PATH)/libs/openFrameworksCompiled/lib/tvos/openFrameworkstvOS.a" @@ -36,9 +39,17 @@ MISC_FLAGS = "-ObjC" OF_CORE_LIBS = $(MISC_FLAGS) $(LIB_OF) -OF_CORE_HEADERS = $(HEADER_OF) $(HEADER_OFXIOS) $(HEADER_UTF8) $(HEADER_FREETYPE) $(HEADER_FREETYPE2) $(HEADER_GLEW) $(HEADER_FREEIMAGE) $(HEADER_TESS2) $(HEADER_FMT) $(HEADER_RTAUDIO) $(HEADER_JSON) $(HEADER_GLM) $(HEADER_FMT) $(HEADER_CURL) $(HEADER_URIPARSER) $(HEADER_PUGIXML) +OF_CORE_HEADERS = $(HEADER_OF) $(HEADER_OFXIOS) $(HEADER_UTF8) $(HEADER_FREETYPE) $(HEADER_FREETYPE2) $(HEADER_GLEW) $(HEADER_FREEIMAGE) $(HEADER_TESS2) $(HEADER_FMT) $(HEADER_RTAUDIO) $(HEADER_JSON) $(HEADER_GLM) $(HEADER_FMT) $(HEADER_CURL) $(HEADER_URIPARSER) $(HEADER_PUGIXML) $(HEADER_DAWN) $(HEADER_METAL) $(HEADER_METALANGLE) -OF_CORE_FRAMEWORKS = -framework AudioToolbox -framework Accelerate -framework AVFoundation -framework CoreAudio -framework CoreGraphics -framework CoreLocation -framework CoreMedia -framework CoreVideo -framework Foundation -framework GameController -framework GLKit -framework MapKit -framework OpenAL -framework OpenGLES -framework UIKit -framework Security -framework CFNetwork -framework QuartzCore -framework Metal -framework MetalKit +OF_CORE_FRAMEWORKS = -framework AudioToolbox -framework Accelerate -framework AVFoundation -framework CoreAudio -framework CoreGraphics -framework CoreLocation -framework CoreMedia -framework CoreVideo -framework Foundation -framework GameController -framework GLKit -framework MapKit -framework OpenAL -framework OpenGLES -framework UIKit -framework Security -framework CFNetwork -framework QuartzCore -framework Metal -framework MetalKit + +// Opt-in. Pass OF_USE_DAWN=1 or OF_USE_ANGLE=1 to both the static lib and the app. +OF_DAWN_LIBS[sdk=appletvos*] = "$(OF_PATH)/libs/dawn/lib/tvos/dawn.xcframework/tvos-arm64/libwebgpu_dawn.a" -framework Metal -framework MetalKit -framework IOSurface +OF_DAWN_LIBS[sdk=appletvsimulator*] = "$(OF_PATH)/libs/dawn/lib/tvos/dawn.xcframework/tvos-arm64_x86_64-simulator/libwebgpu_dawn.a" -framework Metal -framework MetalKit -framework IOSurface +OF_ANGLE_LIBS[sdk=appletvos*] = "$(OF_PATH)/libs/metalangle/lib/tvos/MetalANGLE.xcframework/tvos-arm64/MetalANGLE.a" +OF_ANGLE_LIBS[sdk=appletvsimulator*] = "$(OF_PATH)/libs/metalangle/lib/tvos/MetalANGLE.xcframework/tvos-arm64_x86_64-simulator/MetalANGLE.a" +// OpenGLES and GLKit collide with MetalANGLE's gl* symbols. +OF_ANGLE_APP_FRAMEWORKS = -framework AudioToolbox -framework Accelerate -framework AVFoundation -framework CoreAudio -framework CoreGraphics -framework CoreLocation -framework CoreMedia -framework CoreVideo -framework Foundation -framework GameController -framework MapKit -framework OpenAL -framework UIKit -framework Security -framework CFNetwork -framework QuartzCore -framework Metal -framework MetalKit -framework IOSurface DEAD_CODE_STRIPPING = YES diff --git a/libs/openFrameworksCompiled/project/tvOS/tvOS+OFLib.xcodeproj/project.pbxproj b/libs/openFrameworksCompiled/project/tvOS/tvOS+OFLib.xcodeproj/project.pbxproj index 401f332c57f..38ae35726d2 100644 --- a/libs/openFrameworksCompiled/project/tvOS/tvOS+OFLib.xcodeproj/project.pbxproj +++ b/libs/openFrameworksCompiled/project/tvOS/tvOS+OFLib.xcodeproj/project.pbxproj @@ -15,6 +15,9 @@ 844639C41BC3443E00F24926 /* ofxiOSAppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 8446397F1BC3443E00F24926 /* ofxiOSAppDelegate.mm */; }; 844639C51BC3443E00F24926 /* ofxiOSViewController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 844639811BC3443E00F24926 /* ofxiOSViewController.mm */; }; 844639C61BC3443E00F24926 /* ofxiOSEAGLView.mm in Sources */ = {isa = PBXBuildFile; fileRef = 844639831BC3443E00F24926 /* ofxiOSEAGLView.mm */; }; + E4DA03017A00000100000011 /* ofWebGPURenderer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E4DA03017A00000100000001 /* ofWebGPURenderer.cpp */; }; + E4DA03017A00000100000013 /* ofxiOSDawnView.mm in Sources */ = {isa = PBXBuildFile; fileRef = E4DA03017A00000100000003 /* ofxiOSDawnView.mm */; }; + E4DA03017A00000100000015 /* ofxiOSMGLView.mm in Sources */ = {isa = PBXBuildFile; fileRef = E4DA03017A00000100000005 /* ofxiOSMGLView.mm */; }; 844639C71BC3443E00F24926 /* EAGLView.m in Sources */ = {isa = PBXBuildFile; fileRef = 844639861BC3443E00F24926 /* EAGLView.m */; }; 844639C81BC3443E00F24926 /* ES1Renderer.m in Sources */ = {isa = PBXBuildFile; fileRef = 844639891BC3443E00F24926 /* ES1Renderer.m */; }; 844639C91BC3443E00F24926 /* ES2Renderer.m in Sources */ = {isa = PBXBuildFile; fileRef = 8446398B1BC3443E00F24926 /* ES2Renderer.m */; }; @@ -172,6 +175,12 @@ 844639811BC3443E00F24926 /* ofxiOSViewController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ofxiOSViewController.mm; sourceTree = ""; }; 844639821BC3443E00F24926 /* ofxiOSEAGLView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ofxiOSEAGLView.h; sourceTree = ""; }; 844639831BC3443E00F24926 /* ofxiOSEAGLView.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ofxiOSEAGLView.mm; sourceTree = ""; }; + E4DA03017A00000100000001 /* ofWebGPURenderer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ofWebGPURenderer.cpp; sourceTree = ""; }; + E4DA03017A00000100000002 /* ofWebGPURenderer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ofWebGPURenderer.h; sourceTree = ""; }; + E4DA03017A00000100000003 /* ofxiOSDawnView.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ofxiOSDawnView.mm; sourceTree = ""; }; + E4DA03017A00000100000004 /* ofxiOSDawnView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ofxiOSDawnView.h; sourceTree = ""; }; + E4DA03017A00000100000005 /* ofxiOSMGLView.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ofxiOSMGLView.mm; sourceTree = ""; }; + E4DA03017A00000100000006 /* ofxiOSMGLView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ofxiOSMGLView.h; sourceTree = ""; }; 844639851BC3443E00F24926 /* EAGLView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EAGLView.h; sourceTree = ""; }; 844639861BC3443E00F24926 /* EAGLView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = EAGLView.m; sourceTree = ""; }; 844639871BC3443E00F24926 /* ESRenderer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ESRenderer.h; sourceTree = ""; }; @@ -496,6 +505,10 @@ 844639811BC3443E00F24926 /* ofxiOSViewController.mm */, 844639821BC3443E00F24926 /* ofxiOSEAGLView.h */, 844639831BC3443E00F24926 /* ofxiOSEAGLView.mm */, + E4DA03017A00000100000004 /* ofxiOSDawnView.h */, + E4DA03017A00000100000003 /* ofxiOSDawnView.mm */, + E4DA03017A00000100000006 /* ofxiOSMGLView.h */, + E4DA03017A00000100000005 /* ofxiOSMGLView.mm */, ); path = core; sourceTree = ""; @@ -774,6 +787,8 @@ 9957D8A71BDDDC9B0002D53C /* ofBitmapFont.h */, 9957D8A81BDDDC9B0002D53C /* ofGraphics.cpp */, 9957D8A91BDDDC9B0002D53C /* ofGraphics.h */, + E4DA03017A00000100000001 /* ofWebGPURenderer.cpp */, + E4DA03017A00000100000002 /* ofWebGPURenderer.h */, 9957D8AA1BDDDC9B0002D53C /* ofImage.cpp */, 9957D8AB1BDDDC9B0002D53C /* ofImage.h */, 9957D8AC1BDDDC9B0002D53C /* ofPath.cpp */, @@ -1072,6 +1087,9 @@ 9957D90A1BDDDC9B0002D53C /* ofGLRenderer.cpp in Sources */, 9018089F20535AA8004A7774 /* ofxiOSGLKView.mm in Sources */, 9957D9141BDDDC9B0002D53C /* ofGraphics.cpp in Sources */, + E4DA03017A00000100000011 /* ofWebGPURenderer.cpp in Sources */, + E4DA03017A00000100000013 /* ofxiOSDawnView.mm in Sources */, + E4DA03017A00000100000015 /* ofxiOSMGLView.mm in Sources */, 9957D9361BDDDC9B0002D53C /* ofVideoPlayer.cpp in Sources */, 9957D92D1BDDDC9B0002D53C /* ofLog.cpp in Sources */, 844639C41BC3443E00F24926 /* ofxiOSAppDelegate.mm in Sources */, diff --git a/scripts/dev/download_libs.sh b/scripts/dev/download_libs.sh index e1c70fb9e4f..6541b665eba 100755 --- a/scripts/dev/download_libs.sh +++ b/scripts/dev/download_libs.sh @@ -43,6 +43,9 @@ cat << EOF --full-clean Also remove shared include/ (and bin/ for vs|msys2) for libs being installed. Use for a hard reset of one platform install; not needed for multi-platform side-by-side installs. + --modules LIST Also install optional library modules from apothecary's latest-modular + release, e.g. --modules dawn,metalangle (default: \$OF_LIB_MODULES). + Modules not built for this platform yet are skipped. --skip-sha Do not verify package SHA-256 against GitHub release digests --require-sha Fail if a package has no digest or local hash tools are missing -s, --silent Silent download progress @@ -183,6 +186,10 @@ while [[ $# -gt 0 ]]; do -vs|--vs2026) VS_2026=1 ;; + --modules) + MODULES="$2" + shift # past argument + ;; -h|--help) printHelp exit 0 @@ -678,3 +685,15 @@ elif [[ "$SHA_STATUS" == "skipped" || "$SHA_STATUS" == "no-digest" || "$SHA_STAT else echo " openFrameworks download_libs and install complete!" fi + +# optional modules (dawn, metalangle, ...) are separate packages on latest-modular +MODULES="${MODULES:-${OF_LIB_MODULES:-}}" +if [ -n "$MODULES" ]; then + MODULE_ARGS=(-p "$PLATFORM") + case "$PLATFORM" in + vs|emscripten|msys2) [ -n "$ARCH" ] && MODULE_ARGS+=(-a "$ARCH") ;; + esac + [ "$SILENT_ARGS" == "1" ] && MODULE_ARGS+=(-s) + [ "$NO_SSL" == "1" ] && MODULE_ARGS+=(-k) + "$SCRIPT_DIR/download_modules.sh" "${MODULE_ARGS[@]}" "$MODULES" || exit 1 +fi diff --git a/scripts/dev/download_modules.sh b/scripts/dev/download_modules.sh new file mode 100755 index 00000000000..2352ec4b135 --- /dev/null +++ b/scripts/dev/download_modules.sh @@ -0,0 +1,161 @@ +#!/usr/bin/env bash +# Downloads optional library modules (e.g. dawn, metalangle) that are not part +# of the core openFrameworksLibs packages. apothecary publishes them per library +# on its latest-modular release as oF___[_gcc].tar.bz2 +# (.zip for Visual Studio / MSYS2); each one unpacks to libs//. +# +# Modules that aren't built for a target yet are skipped with a note. +# +# Usage: download_modules.sh -p [-a ] [-t ] [--require] [...] +# download_modules.sh -p macos dawn metalangle +# OF_LIB_MODULES=dawn,metalangle scripts/macos/download_libs.sh (via download_libs.sh --modules) + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +OF_ROOT="$( cd "$SCRIPT_DIR/../.." && pwd )" +REPO="openframeworks/apothecary" +TAG="latest-modular" +PLATFORM="" +ARCH="" +REQUIRE=0 +SILENT_ARGS="" +NO_SSL="" +MODULES=() + +. "$SCRIPT_DIR/downloader.sh" +. "$SCRIPT_DIR/sha_verify.sh" + +printHelp(){ + sed -n '2,13p' "$0" | sed 's/^# \{0,1\}//' + cat </dev/null | cut -d. -f1) + local first=gcc14 second=gcc10 + [[ -n "$gcc" && "$gcc" -lt 14 ]] && { first=gcc10; second=gcc14; } + case "$PLATFORM" in + linux64) echo "linux_64_${first}.tar.bz2 linux_64_${second}.tar.bz2" ;; + linuxaarch64) echo "linux_arm64_${first}.tar.bz2 linux_arm64_${second}.tar.bz2 linux_aarch64_gcc10.tar.bz2" ;; + linuxarmv6l) echo "linux_armv6l_gcc10.tar.bz2" ;; + linuxarmv7l) echo "linux_armv7l_gcc10.tar.bz2" ;; + esac ;; + *) echo "" ;; + esac +} + +MAP=$(mktemp 2>/dev/null || mktemp -t ofmodmap) +HAVE_MAP=0 +fetchGithubReleaseDigests "$REPO" "$TAG" "$MAP" && HAVE_MAP=1 + +isPublished(){ + local asset="$1" + if [[ $HAVE_MAP -eq 1 ]]; then + [[ -n "$(lookupDigest "$MAP" "$asset")" ]] + else + curl -fsIL -o /dev/null "https://github.com/$REPO/releases/download/$TAG/$asset" + fi +} + +mkdir -p "$OF_ROOT/libs/download" +cd "$OF_ROOT/libs/download" || exit 1 + +failed=0 +installed=() +for module in "${MODULES[@]}"; do + [[ -z "$module" ]] && continue + groups=$(candidateSuffixes) + if [[ -z "$groups" ]]; then + echo " ! [$module] modules aren't published for platform $PLATFORM" + [[ $REQUIRE -eq 1 ]] && failed=1 + continue + fi + found_any=0 + # "|" separates targets that are all installed (Apple); spaces separate + # alternatives for one target (first published wins) + IFS='|' read -r -a targets <<< "$groups" + for alternatives in "${targets[@]}"; do + asset="" + for suffix in $alternatives; do + if isPublished "oF_${module}_${suffix}"; then asset="oF_${module}_${suffix}"; break; fi + done + [[ -z "$asset" ]] && continue + found_any=1 + echo " [$module] downloading $asset" + rm -f "$asset" + downloader "https://github.com/$REPO/releases/download/$TAG/$asset" $SILENT_ARGS $NO_SSL + if [[ ! -s "$asset" ]]; then + echo " ! [$module] download failed: $asset"; failed=1; continue + fi + if [[ $HAVE_MAP -eq 1 ]] && ! verifyPackagesAgainstMap "$asset" "$MAP" "GitHub apothecary $TAG digests"; then + echo " ! [$module] SHA-256 mismatch: $asset"; rm -f "$asset"; failed=1; continue + fi + # replace the lib/ folders this package contains, keep the module's other targets + case "$asset" in + *.zip) entries=$(unzip -Z1 "$asset") ;; + *) entries=$(tar -tjf "$asset") ;; + esac + for libdir in $(echo "$entries" | awk -F/ 'NF>=4 && $2=="lib" && $3!="" {print $1"/lib/"$3}' | sort -u); do + rm -rf "$OF_ROOT/libs/$libdir" + done + case "$asset" in + *.zip) unzip -qo "$asset" -d "$OF_ROOT/libs/" ;; + *) tar -xjf "$asset" -C "$OF_ROOT/libs/" ;; + esac || { echo " ! [$module] could not unpack $asset"; failed=1; continue; } + installed+=("$asset") + done + if [[ $found_any -eq 0 ]]; then + echo " ~ [$module] not built for $PLATFORM yet on $TAG — skipped" + [[ $REQUIRE -eq 1 ]] && failed=1 + fi +done +rm -f "$MAP" + +if [[ ${#installed[@]} -gt 0 ]]; then + echo " openFrameworks modules installed: ${installed[*]}" +fi +exit $failed diff --git a/scripts/templates/macos/Project.xcconfig b/scripts/templates/macos/Project.xcconfig index 2898bb39e1c..4e6fccbfc35 100644 --- a/scripts/templates/macos/Project.xcconfig +++ b/scripts/templates/macos/Project.xcconfig @@ -60,7 +60,7 @@ ICON_FILE = $(OF_PATH)/libs/openFrameworksCompiled/project/macos/$(ICON_NAME) //FOR AV ENGINE SOUND PLAYER UNCOMMENT TWO LINES BELOW OF_NO_FMOD=1 USER_PREPROCESSOR_DEFINITIONS="OF_NO_FMOD=1" -GCC_PREPROCESSOR_DEFINITIONS=$(inherited)$(USER_PREPROCESSOR_DEFINITIONS) +GCC_PREPROCESSOR_DEFINITIONS=$(inherited) $(USER_PREPROCESSOR_DEFINITIONS) //APPSTORE, uncomment next lines to bundle data folder and code sign //OF_CODESIGN = 1 @@ -71,5 +71,20 @@ GCC_PREPROCESSOR_DEFINITIONS=$(inherited)$(USER_PREPROCESSOR_DEFINITIONS) #include? "App.xcconfig" OTHER_CFLAGS = $(OF_CORE_CFLAGS) -OTHER_LDFLAGS = $(OF_CORE_LIBS) $(OF_CORE_FRAMEWORKS) HEADER_SEARCH_PATHS = $(OF_CORE_HEADERS) + +// emptyExamplemacOS links openFrameworks.a plus these archives. +// emptyExample links openFrameworks.framework and sets its own OTHER_LDFLAGS, +// so this line is not added again on top of the framework. +OF_APP_OF_LIB = $(LIB_OF) +OF_APP_OF_LIB[config=Debug] = $(LIB_OF_DEBUG) +OTHER_LDFLAGS = $(OF_CORE_LIBS) $(OF_STATIC_LIBS) $(OF_CORE_FRAMEWORKS) $(OF_APP_OF_LIB) + +// Dawn (WebGPU / Metal). Uncomment both lines. Do not enable Dawn and ANGLE in the same build. +// 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) +// +// MetalANGLE (GLES via Metal + MGLKit). Uncomment both lines. Leaves OpenGL.framework off. +// 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) +// OF_USE_ANGLE_OFFICIAL=1 is reserved for chromium ANGLE without MGLKit. diff --git a/scripts/templates/macos/emptyExample.xcodeproj/project.pbxproj b/scripts/templates/macos/emptyExample.xcodeproj/project.pbxproj index e992e95fb43..f62b91bf5f3 100644 --- a/scripts/templates/macos/emptyExample.xcodeproj/project.pbxproj +++ b/scripts/templates/macos/emptyExample.xcodeproj/project.pbxproj @@ -373,7 +373,7 @@ "CLANG_WARN_CONSTANT_CONVERSION": "YES", "GCC_NO_COMMON_BLOCKS": "YES", "MARKETING_VERSION": "1.0", - "CODE_SIGN_ENTITLEMENTS": "emptymacOSExample/emptymacOSExample.entitlements", + "CODE_SIGN_ENTITLEMENTS": "of.entitlements", "CLANG_WARN_OBJC_ROOT_CLASS": "YES_ERROR", "PRODUCT_NAME": "$(TARGET_NAME)", "SUPPORTED_PLATFORMS": "appletvos appletvsimulator iphoneos iphonesimulator macosx xros xrsimulator", @@ -392,7 +392,7 @@ "CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER": "YES", "CLANG_ENABLE_OBJC_WEAK": "YES", "CLANG_WARN_STRICT_PROTOTYPES": "YES", - "ENABLE_USER_SCRIPT_SANDBOXING": "YES", + "ENABLE_USER_SCRIPT_SANDBOXING": "NO", "GCC_WARN_ABOUT_RETURN_TYPE": "YES_ERROR", "GCC_WARN_UNUSED_FUNCTION": "YES", "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]": "YES", @@ -407,7 +407,8 @@ "$(inherited)" ], "CLANG_WARN_OBJC_LITERAL_CONVERSION": "YES", - "PRODUCT_BUNDLE_IDENTIFIER": "com.openFrameworks.emptyExample" + "PRODUCT_BUNDLE_IDENTIFIER": "com.openFrameworks.emptyExample", + "OTHER_LDFLAGS": "-ObjC" }, "name": "Debug" }, @@ -446,11 +447,6 @@ "FRAMEWORK_SEARCH_PATHS": "$(inherited)", "COPY_PHASE_STRIP": "NO", "ARCHS": "$(ARCHS_STANDARD)", - "OTHER_LDFLAGS": [ - "$(OF_CORE_LIBS)", - "$(OF_CORE_FRAMEWORKS)", - "$(LIB_OF_DEBUG)" - ], "INFOPLIST_KEY_LSApplicationCategoryType": "public.app-category.games", "LIBRARY_SEARCH_PATHS": "$(inherited)", "INFOPLIST_KEY_NSMicrophoneUsageDescription": "This app needs to access the microphone" @@ -573,7 +569,8 @@ "BF6278092BADE950008864C1", "BF6277F32BADE870008864C1", "BF6277F42BADE870008864C1", - "BF6277F52BADE870008864C1" + "BF6277F52BADE870008864C1", + "BF6277F82BADE870008864C1" ], "dependencies": [], "name": "emptyExample", @@ -940,7 +937,7 @@ "CLANG_WARN_CONSTANT_CONVERSION": "YES", "GCC_NO_COMMON_BLOCKS": "YES", "MARKETING_VERSION": "1.0", - "CODE_SIGN_ENTITLEMENTS": "emptymacOSExample/emptymacOSExample.entitlements", + "CODE_SIGN_ENTITLEMENTS": "of.entitlements", "CLANG_WARN_OBJC_ROOT_CLASS": "YES_ERROR", "PRODUCT_NAME": "$(TARGET_NAME)", "SUPPORTED_PLATFORMS": "appletvos appletvsimulator iphoneos iphonesimulator macosx xros xrsimulator", @@ -958,7 +955,7 @@ "CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER": "YES", "CLANG_ENABLE_OBJC_WEAK": "YES", "CLANG_WARN_STRICT_PROTOTYPES": "YES", - "ENABLE_USER_SCRIPT_SANDBOXING": "YES", + "ENABLE_USER_SCRIPT_SANDBOXING": "NO", "GCC_WARN_ABOUT_RETURN_TYPE": "YES_ERROR", "GCC_WARN_UNUSED_FUNCTION": "YES", "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]": "YES", @@ -970,7 +967,8 @@ "SWIFT_EMIT_LOC_STRINGS": "YES", "CLANG_WARN_NON_LITERAL_NULL_CONVERSION": "YES", "CLANG_WARN_OBJC_LITERAL_CONVERSION": "YES", - "PRODUCT_BUNDLE_IDENTIFIER": "com.openFrameworks.emptyExample" + "PRODUCT_BUNDLE_IDENTIFIER": "com.openFrameworks.emptyExample", + "OTHER_LDFLAGS": "-ObjC" }, "name": "Release" }, @@ -1010,14 +1008,8 @@ "FRAMEWORK_SEARCH_PATHS": "$(inherited)", "COPY_PHASE_STRIP": "YES", "ARCHS": "$(ARCHS_STANDARD)", - "OTHER_LDFLAGS": [ - "$(OF_CORE_LIBS)", - "$(OF_CORE_FRAMEWORKS)", - "$(LIB_OF)" - ], "INFOPLIST_KEY_LSApplicationCategoryType": "public.app-category.games", "LIBRARY_SEARCH_PATHS": "$(inherited)", - "baseConfigurationReference": "E4EB6923138AFD0F00A09F29", "INFOPLIST_KEY_NSMicrophoneUsageDescription": "This app needs to access the microphone" }, "name": "Release" @@ -1383,8 +1375,8 @@ "outputPaths": [], "isa": "PBXShellScriptBuildPhase", "runOnlyForDeploymentPostprocessing": "0", - "shellScript": "echo \"💾 Compiling Openframeworks\"\nxcodebuild -project \"$OF_PATH/libs/openFrameworksCompiled/project/macos/openFrameworksLib.xcodeproj\" -target openFrameworks -configuration \"${CONFIGURATION}\" CLANG_CXX_LANGUAGE_STANDARD=$CLANG_CXX_LANGUAGE_STANDARD MACOSX_DEPLOYMENT_TARGET=$MACOSX_DEPLOYMENT_TARGET GCC_PREPROCESSOR_DEFINITIONS=$USER_PREPROCESSOR_DEFINITIONS\n", - "name": "Run Script — Compile OF", + "shellScript": "echo \"\ud83d\udcbe Compiling openFrameworks\"\n# Static archive consumed by emptyExamplemacOS. Keep the lib project's own defines.\nxcodebuild -project \"$OF_PATH/libs/openFrameworksCompiled/project/macos/openFrameworksLib.xcodeproj\" -target openFrameworksLib -configuration \"${CONFIGURATION}\" CLANG_CXX_LANGUAGE_STANDARD=\"$CLANG_CXX_LANGUAGE_STANDARD\" MACOSX_DEPLOYMENT_TARGET=\"$MACOSX_DEPLOYMENT_TARGET\" GCC_PREPROCESSOR_DEFINITIONS=\"\\$(inherited) ${GCC_PREPROCESSOR_DEFINITIONS}\"\n", + "name": "Run Script \u2014 Compile OF", "files": [] }, "BF6278AC2BADEBFE008864C1": { @@ -1660,7 +1652,7 @@ "$OF_PATH/libs/openFrameworksCompiled/lib/macos/openFrameworks.framework" ], "runOnlyForDeploymentPostprocessing": "0", - "shellScript": "echo \"💾 Compiling OpenFrameworks\"\nxcodebuild -workspace \"$OF_PATH/libs/openFrameworksCompiled/project/macos/openFrameworksLib.xcworkspace\" -scheme openFrameworks -configuration \"${CONFIGURATION}\" CLANG_CXX_LANGUAGE_STANDARD=$CLANG_CXX_LANGUAGE_STANDARD MACOSX_DEPLOYMENT_TARGET=$MACOSX_DEPLOYMENT_TARGET GCC_PREPROCESSOR_DEFINITIONS=$USER_PREPROCESSOR_DEFINITIONS\n", + "shellScript": "echo \"\ud83d\udcbe Compiling OpenFrameworks\"\nDEFS=\"${GCC_PREPROCESSOR_DEFINITIONS}\"\nGL_FLAGS=\"\"\nANGLE_LIB=\"\"\nDAWN_LIB=\"\"\ncase \"${PLATFORM_NAME}\" in\n macosx)\n GL_FLAGS=\"-framework OpenGL\"\n ANGLE_LIB=\"${OF_PATH}/libs/metalangle/lib/macos/MetalANGLE.xcframework/macos-arm64_x86_64/MetalANGLE.a\"\n DAWN_LIB=\"${OF_PATH}/libs/dawn/lib/macos/dawn.xcframework/macos-arm64_x86_64/libwebgpu_dawn.a\" ;;\n iphoneos)\n GL_FLAGS=\"-framework OpenGLES -framework GLKit\"\n ANGLE_LIB=\"${OF_PATH}/libs/metalangle/lib/ios/MetalANGLE.xcframework/ios-arm64/MetalANGLE.a\"\n DAWN_LIB=\"${OF_PATH}/libs/dawn/lib/ios/dawn.xcframework/ios-arm64/libwebgpu_dawn.a\" ;;\n iphonesimulator)\n GL_FLAGS=\"-framework OpenGLES -framework GLKit\"\n ANGLE_LIB=\"${OF_PATH}/libs/metalangle/lib/ios/MetalANGLE.xcframework/ios-arm64_x86_64-simulator/MetalANGLE.a\"\n DAWN_LIB=\"${OF_PATH}/libs/dawn/lib/ios/dawn.xcframework/ios-arm64_x86_64-simulator/libwebgpu_dawn.a\" ;;\n appletvos)\n GL_FLAGS=\"-framework OpenGLES -framework GLKit\"\n ANGLE_LIB=\"${OF_PATH}/libs/metalangle/lib/tvos/MetalANGLE.xcframework/tvos-arm64/MetalANGLE.a\"\n DAWN_LIB=\"${OF_PATH}/libs/dawn/lib/tvos/dawn.xcframework/tvos-arm64/libwebgpu_dawn.a\" ;;\n appletvsimulator)\n GL_FLAGS=\"-framework OpenGLES -framework GLKit\"\n ANGLE_LIB=\"${OF_PATH}/libs/metalangle/lib/tvos/MetalANGLE.xcframework/tvos-arm64_x86_64-simulator/MetalANGLE.a\"\n DAWN_LIB=\"${OF_PATH}/libs/dawn/lib/tvos/dawn.xcframework/tvos-arm64_x86_64-simulator/libwebgpu_dawn.a\" ;;\nesac\nLD_ARG=\"\"\ncase \" ${DEFS} \" in\n *\" OF_USE_ANGLE=1 \"*)\n if [ -z \"${ANGLE_LIB}\" ]; then echo \"MetalANGLE has no slice for ${PLATFORM_NAME}\"; exit 1; fi\n LD_ARG=\"OTHER_LDFLAGS=-ObjC -framework Metal -framework MetalKit -framework IOSurface ${ANGLE_LIB}\" ;;\n *\" OF_USE_DAWN=1 \"*)\n if [ -z \"${DAWN_LIB}\" ]; then echo \"Dawn has no slice for ${PLATFORM_NAME}\"; exit 1; fi\n LD_ARG=\"OTHER_LDFLAGS=-ObjC ${GL_FLAGS} -framework Metal -framework MetalKit -framework IOSurface ${DAWN_LIB}\" ;;\nesac\nset -- -workspace \"$OF_PATH/libs/openFrameworksCompiled/project/macos/openFrameworksLib.xcworkspace\" -scheme openFrameworks -configuration \"${CONFIGURATION}\"\nif [ -n \"${SDK_NAME}\" ]; then set -- \"$@\" -sdk \"${SDK_NAME}\"; fi\nset -- \"$@\" \"CLANG_CXX_LANGUAGE_STANDARD=${CLANG_CXX_LANGUAGE_STANDARD}\" \"MACOSX_DEPLOYMENT_TARGET=${MACOSX_DEPLOYMENT_TARGET}\" \"GCC_PREPROCESSOR_DEFINITIONS=\\$(inherited) ${DEFS}\"\nif [ -n \"${LD_ARG}\" ]; then set -- \"$@\" \"${LD_ARG}\"; fi\nxcodebuild \"$@\"\n", "outputFileListPaths": [], "inputFileListPaths": [ "" @@ -1781,6 +1773,27 @@ "lastKnownFileType": "sourcecode.cpp.objcpp", "sourceTree": "", "fileEncoding": "4" + }, + "BF6278C32BADEC41008864C1": { + "isa": "PBXBuildFile", + "fileRef": "BF6278C12BADEC41008864C1", + "settings": { + "ATTRIBUTES": [ + "CodeSignOnCopy", + "RemoveHeadersOnCopy" + ] + } + }, + "BF6277F82BADE870008864C1": { + "isa": "PBXCopyFilesBuildPhase", + "buildActionMask": "2147483647", + "dstPath": "", + "dstSubfolderSpec": "10", + "files": [ + "BF6278C32BADEC41008864C1" + ], + "name": "Embed Frameworks", + "runOnlyForDeploymentPostprocessing": "0" } }, "rootObject": "E4B69B4C0A3A1720003C02F2" diff --git a/scripts/templates/tvOS/Project.xcconfig b/scripts/templates/tvOS/Project.xcconfig index 8cb20f3a3f0..b92167a3a10 100644 --- a/scripts/templates/tvOS/Project.xcconfig +++ b/scripts/templates/tvOS/Project.xcconfig @@ -11,6 +11,14 @@ USER_PREPROCESSOR_DEFINITIONS="OF_NO_FMOD=1" OTHER_LDFLAGS = $(OF_CORE_LIBS) $(OF_CORE_FRAMEWORKS) HEADER_SEARCH_PATHS = $(OF_CORE_HEADERS) +// Dawn (WebGPU / Metal). Rebuild tvOS+OFLib with the same flag. Do not combine with ANGLE. +// GCC_PREPROCESSOR_DEFINITIONS = $(inherited) OF_USE_DAWN=1 +// OTHER_LDFLAGS = $(OF_CORE_LIBS) $(OF_CORE_FRAMEWORKS) $(OF_DAWN_LIBS) +// +// MetalANGLE. Drop OpenGLES and GLKit. Rebuild tvOS+OFLib with the same flag. +// GCC_PREPROCESSOR_DEFINITIONS = $(inherited) OF_USE_ANGLE=1 +// OTHER_LDFLAGS = $(OF_CORE_LIBS) $(OF_ANGLE_APP_FRAMEWORKS) $(OF_ANGLE_LIBS) + ENABLE_BITCODE = NO COMPRESS_PNG_FILES = NO GCC_THUMB_SUPPORT = NO diff --git a/scripts/templates/tvOS/emptyExample.xcodeproj/project.pbxproj b/scripts/templates/tvOS/emptyExample.xcodeproj/project.pbxproj index b81b0429c0c..4f093762672 100644 --- a/scripts/templates/tvOS/emptyExample.xcodeproj/project.pbxproj +++ b/scripts/templates/tvOS/emptyExample.xcodeproj/project.pbxproj @@ -313,7 +313,6 @@ "files": [ "BFDAFEA42C2C8AE0006B0AE0", "84C8DB871BC3732E001606C1", - "84C8DB891BC3732E001606C1", "99752D361BF220CC0026316A", "84C8DB801BC3732E001606C1", "84C8DB811BC3732E001606C1",