From f2fe615d7278eac8e57a8c033a41178bb9cc215e Mon Sep 17 00:00:00 2001 From: MikhailGorobets Date: Wed, 4 Feb 2026 13:39:37 +0600 Subject: [PATCH] MacOS App: Integrated WebGPUView --- .../Apple/Data/OSX/Base.lproj/Main.storyboard | 33 +++- .../Data/OSX/Images.xcassets/webgpu-logo.png | Bin 0 -> 35010 bytes .../OSX/ModeSelectionViewController.mm | 50 +++++- .../Apple/Source/Classes/OSX/ViewBase.mm | 2 + .../Apple/Source/Classes/OSX/WebGPUView.h | 30 ++++ .../Apple/Source/Classes/OSX/WebGPUView.mm | 153 ++++++++++++++++++ NativeApp/CMakeLists.txt | 3 + NativeApp/include/MacOS/MacOSAppBase.hpp | 3 +- 8 files changed, 268 insertions(+), 6 deletions(-) create mode 100644 NativeApp/Apple/Data/OSX/Images.xcassets/webgpu-logo.png create mode 100644 NativeApp/Apple/Source/Classes/OSX/WebGPUView.h create mode 100644 NativeApp/Apple/Source/Classes/OSX/WebGPUView.mm diff --git a/NativeApp/Apple/Data/OSX/Base.lproj/Main.storyboard b/NativeApp/Apple/Data/OSX/Base.lproj/Main.storyboard index bc15944c..26161580 100644 --- a/NativeApp/Apple/Data/OSX/Base.lproj/Main.storyboard +++ b/NativeApp/Apple/Data/OSX/Base.lproj/Main.storyboard @@ -174,11 +174,11 @@ - + + @@ -230,10 +241,24 @@ + + + + + + + + + + + + + + diff --git a/NativeApp/Apple/Data/OSX/Images.xcassets/webgpu-logo.png b/NativeApp/Apple/Data/OSX/Images.xcassets/webgpu-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..7e82676951a023047f89ce32e55c8a51af7b073c GIT binary patch literal 35010 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%0Aaz$>~{}(EoyHiv)Z-=rUH4k8~_`Q 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 literal 0 HcmV?d00001 diff --git a/NativeApp/Apple/Source/Classes/OSX/ModeSelectionViewController.mm b/NativeApp/Apple/Source/Classes/OSX/ModeSelectionViewController.mm index b43d4386..7121647d 100644 --- a/NativeApp/Apple/Source/Classes/OSX/ModeSelectionViewController.mm +++ b/NativeApp/Apple/Source/Classes/OSX/ModeSelectionViewController.mm @@ -1,4 +1,5 @@ -/* Copyright 2015-2019 Egor Yusov +/* Copyright 2026 Diligent Graphics LLC + * Copyright 2015-2019 Egor Yusov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,6 +25,7 @@ #import "ModeSelectionViewController.h" #import "GLView.h" #import "MetalView.h" +#import "WebGPUView.h" #import "ViewController.h" @@ -50,6 +52,36 @@ - (void)viewDidLoad #if !METAL_SUPPORTED ((NSButton*)self.view.subviews[2]).enabled = false; #endif + +#if !WEBGPU_SUPPORTED + ((NSButton*)self.view.subviews[3]).hidden = true; +#endif +} + +- (void)viewDidAppear +{ + [super viewDidAppear]; + +#if !WEBGPU_SUPPORTED + NSWindow* window = self.view.window; + if (window) + { + CGFloat reduction = self.view.subviews[1].frame.origin.y - + self.view.subviews[3].frame.origin.y; + + // Switch buttons from Auto Layout to autoresizing masks + // so flexibleMinY keeps them pinned to the top edge + for (NSView* subview in self.view.subviews) + subview.translatesAutoresizingMaskIntoConstraints = YES; + self.view.autoresizesSubviews = YES; + + // Shrink window from the bottom (keep top edge fixed) + NSRect frame = window.frame; + frame.origin.y += reduction; + frame.size.height -= reduction; + [window setFrame:frame display:YES]; + } +#endif } - (void) terminateApp:(NSString*) error @@ -111,4 +143,20 @@ - (IBAction)goMetal:(id)sender [self setWindowTitle:name]; } +- (IBAction)goWebGPU:(id)sender +{ + ViewController* webgpuViewController = [self.storyboard instantiateControllerWithIdentifier:@"WebGPUViewControllerID"]; + WebGPUView* webgpuView = (WebGPUView*)[webgpuViewController view]; + self.view.window.contentViewController = webgpuViewController; + + NSString* error = [webgpuView getError]; + if(error != nil) + { + [self terminateApp:error]; + } + + NSString* name = [webgpuView getAppName]; + [self setWindowTitle:name]; +} + @end diff --git a/NativeApp/Apple/Source/Classes/OSX/ViewBase.mm b/NativeApp/Apple/Source/Classes/OSX/ViewBase.mm index 65677a36..3641f8be 100644 --- a/NativeApp/Apple/Source/Classes/OSX/ViewBase.mm +++ b/NativeApp/Apple/Source/Classes/OSX/ViewBase.mm @@ -43,6 +43,8 @@ - (void) awakeFromNib { [super awakeFromNib]; + appLock = [[NSRecursiveLock alloc] init]; + std::vector Args; std::vector ArgStr; @autoreleasepool diff --git a/NativeApp/Apple/Source/Classes/OSX/WebGPUView.h b/NativeApp/Apple/Source/Classes/OSX/WebGPUView.h new file mode 100644 index 00000000..db2db40d --- /dev/null +++ b/NativeApp/Apple/Source/Classes/OSX/WebGPUView.h @@ -0,0 +1,30 @@ +/* Copyright 2026 Diligent Graphics LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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 OF ANY PROPRIETARY RIGHTS. + * + * In no event and under no legal theory, whether in tort (including negligence), + * contract, or otherwise, unless required by applicable law (such as deliberate + * and grossly negligent acts) or agreed to in writing, shall any Contributor be + * liable for any damages, including any direct, indirect, special, incidental, + * or consequential damages of any character arising as a result of this License or + * out of the use or inability to use the software (including but not limited to damages + * for loss of goodwill, work stoppage, computer failure or malfunction, or any and + * all other commercial damages or losses), even if such Contributor has been advised + * of the possibility of such damages. + */ + +#import + +#include "ViewBase.h" + +@interface WebGPUView : ViewBase + +@end diff --git a/NativeApp/Apple/Source/Classes/OSX/WebGPUView.mm b/NativeApp/Apple/Source/Classes/OSX/WebGPUView.mm new file mode 100644 index 00000000..bfe9af5c --- /dev/null +++ b/NativeApp/Apple/Source/Classes/OSX/WebGPUView.mm @@ -0,0 +1,153 @@ +/* Copyright 2026 Diligent Graphics LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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 OF ANY PROPRIETARY RIGHTS. + * + * In no event and under no legal theory, whether in tort (including negligence), + * contract, or otherwise, unless required by applicable law (such as deliberate + * and grossly negligent acts) or agreed to in writing, shall any Contributor be + * liable for any damages, including any direct, indirect, special, incidental, + * or consequential damages of any character arising as a result of this License or + * out of the use or inability to use the software (including but not limited to damages + * for loss of goodwill, work stoppage, computer failure or malfunction, or any and + * all other commercial damages or losses), even if such Contributor has been advised + * of the possibility of such damages. + */ + +#import +#import "WebGPUView.h" + +@implementation WebGPUView +{ +} + +- (id)initWithFrame:(CGRect)frame +{ + self = [super initWithFrame:frame]; + if (self) + { + self.renderMode = Diligent::MacOSAppBase::RenderMode::WebGPU; + } + return self; +} + +- (id)initWithCoder:(NSCoder*)coder +{ + self = [super initWithCoder:coder]; + if (self) + { + self.renderMode = Diligent::MacOSAppBase::RenderMode::WebGPU; + } + return self; +} + + +- (void) awakeFromNib +{ + [super awakeFromNib]; + + // Back the view with a layer created by the makeBackingLayer method. + self.wantsLayer = YES; + + [self initApp:self]; + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + + CVDisplayLinkRef displayLink; + CVDisplayLinkCreateWithActiveCGDisplays(&displayLink); + [self setDisplayLink:displayLink]; + CVDisplayLinkSetOutputCallback(displayLink, &DisplayLinkCallback, (__bridge void*)self); + CVDisplayLinkStart(displayLink); + +#pragma clang diagnostic pop + + [self setPostsBoundsChangedNotifications:YES]; + [self setPostsFrameChangedNotifications:YES]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(boundsDidChange:) name:NSViewBoundsDidChangeNotification object:self]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(boundsDidChange:) name:NSViewFrameDidChangeNotification object:self]; +} + +// Indicates that the view wants to draw using the backing +// layer instead of using drawRect:. +-(BOOL) wantsUpdateLayer +{ + return YES; +} + +// Returns a Metal-compatible layer. ++(Class) layerClass +{ + return [CAMetalLayer class]; +} + +// If the wantsLayer property is set to YES, this method will +// be invoked to return a layer instance. +-(CALayer*) makeBackingLayer +{ + CALayer* layer = [self.class.layerClass layer]; + CGSize viewScale = [self convertSizeToBacking: CGSizeMake(1.0, 1.0)]; + layer.contentsScale = MIN(viewScale.width, viewScale.height); + return layer; +} + +-(void)render +{ + auto* theApp = [self lockApp]; + if (theApp) + { + theApp->Update(); + theApp->Render(); + theApp->Present(); + } + [self unlockApp]; +} + + +- (CVReturn) getFrameForTime:(const CVTimeStamp*)outputTime +{ + // There is no autorelease pool when this method is called + // because it will be called from a background thread. + // It's important to create one or app can leak objects. + @autoreleasepool { + [self render]; + } + return kCVReturnSuccess; +} + +// Rendering loop callback function for use with a CVDisplayLink. +static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, + const CVTimeStamp* now, + const CVTimeStamp* outputTime, + CVOptionFlags flagsIn, + CVOptionFlags* flagsOut, + void* target) +{ + WebGPUView* view = (__bridge WebGPUView*)target; + CVReturn result = [view getFrameForTime:outputTime]; + return result; +} + +-(void)boundsDidChange:(NSNotification *)notification +{ + NSRect viewRectPoints = [self bounds]; + NSRect viewRectPixels = [self convertRectToBacking:viewRectPoints]; + auto* theApp = [self lockApp]; + if (theApp) + { + theApp->WindowResize(viewRectPixels.size.width, viewRectPixels.size.height); + theApp->Update(); + theApp->Render(); + theApp->Present(); + } + [self unlockApp]; +} + +@end diff --git a/NativeApp/CMakeLists.txt b/NativeApp/CMakeLists.txt index 778a57cf..e8963176 100644 --- a/NativeApp/CMakeLists.txt +++ b/NativeApp/CMakeLists.txt @@ -161,6 +161,7 @@ elseif(PLATFORM_MACOS) ${NATIVE_APP_SOURCE_DIR}/Apple/Source/Classes/OSX/GLView.mm ${NATIVE_APP_SOURCE_DIR}/Apple/Source/Classes/OSX/MetalView.mm ${NATIVE_APP_SOURCE_DIR}/Apple/Source/Classes/OSX/MVKView.mm + ${NATIVE_APP_SOURCE_DIR}/Apple/Source/Classes/OSX/WebGPUView.mm ${NATIVE_APP_SOURCE_DIR}/Apple/Source/Classes/OSX/ViewBase.mm ${NATIVE_APP_SOURCE_DIR}/Apple/Source/Classes/OSX/ViewController.mm ${NATIVE_APP_SOURCE_DIR}/Apple/Source/Classes/OSX/ModeSelectionViewController.mm @@ -173,6 +174,7 @@ elseif(PLATFORM_MACOS) ${NATIVE_APP_SOURCE_DIR}/Apple/Source/Classes/OSX/GLView.h ${NATIVE_APP_SOURCE_DIR}/Apple/Source/Classes/OSX/MetalView.h ${NATIVE_APP_SOURCE_DIR}/Apple/Source/Classes/OSX/MVKView.h + ${NATIVE_APP_SOURCE_DIR}/Apple/Source/Classes/OSX/WebGPUView.h ${NATIVE_APP_SOURCE_DIR}/Apple/Source/Classes/OSX/ViewBase.h ${NATIVE_APP_SOURCE_DIR}/Apple/Source/Classes/OSX/ViewController.h ${NATIVE_APP_SOURCE_DIR}/Apple/Source/Classes/OSX/ModeSelectionViewController.h @@ -184,6 +186,7 @@ elseif(PLATFORM_MACOS) ${NATIVE_APP_SOURCE_DIR}/Apple/Data/OSX/Images.xcassets/opengl-logo.png ${NATIVE_APP_SOURCE_DIR}/Apple/Data/OSX/Images.xcassets/vulkan-logo.png ${NATIVE_APP_SOURCE_DIR}/Apple/Data/OSX/Images.xcassets/metal-logo.png + ${NATIVE_APP_SOURCE_DIR}/Apple/Data/OSX/Images.xcassets/webgpu-logo.png ) set(APPLE_INFO_PLIST diff --git a/NativeApp/include/MacOS/MacOSAppBase.hpp b/NativeApp/include/MacOS/MacOSAppBase.hpp index 85400534..291c654b 100644 --- a/NativeApp/include/MacOS/MacOSAppBase.hpp +++ b/NativeApp/include/MacOS/MacOSAppBase.hpp @@ -39,7 +39,8 @@ class MacOSAppBase : public AppBase { OpenGL, MoltenVK, - Metal + Metal, + WebGPU }; using AppBase::Update; void Update();