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 00000000..7e826769
Binary files /dev/null and b/NativeApp/Apple/Data/OSX/Images.xcassets/webgpu-logo.png differ
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();