Initial commit.

This commit is contained in:
Signal 2026-02-22 17:45:44 -05:00
commit f215bc3742
43 changed files with 2983 additions and 0 deletions

View file

@ -0,0 +1,813 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 77;
objects = {
/* Begin PBXBuildFile section */
9B20EE962F4BC69100117DD8 /* libwgpu_native.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 9B20EE952F4BC69100117DD8 /* libwgpu_native.a */; };
9B20EE982F4BC6C600117DD8 /* libngtcp2.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 9B20EE972F4BC6C600117DD8 /* libngtcp2.a */; };
9B20EE9C2F4BCF7D00117DD8 /* libglfw3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 9B20EE9B2F4BCF7D00117DD8 /* libglfw3.a */; };
9B20EEAA2F4BD04000117DD8 /* libClient.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 9B20EE672F4BC09C00117DD8 /* libClient.a */; };
9B20EEAB2F4BD04400117DD8 /* libServer.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 9B20EE752F4BC0FD00117DD8 /* libServer.a */; };
9B20EEAC2F4BD04400117DD8 /* libShared.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 9B20EE7F2F4BC11100117DD8 /* libShared.a */; };
9B20EEAD2F4BD05300117DD8 /* libShared.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 9B20EE7F2F4BC11100117DD8 /* libShared.a */; };
9B20EEAE2F4BD05700117DD8 /* libShared.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 9B20EE7F2F4BC11100117DD8 /* libShared.a */; };
9B20EEC22F4D028400117DD8 /* libgnutls.30.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 9B20EEC12F4D028400117DD8 /* libgnutls.30.dylib */; };
9B20EEC42F4D141600117DD8 /* libev.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 9B20EEC32F4D141600117DD8 /* libev.a */; };
9B20EEDA2F60D87E00117DD8 /* IOKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9B20EED92F60D87E00117DD8 /* IOKit.framework */; };
9B20EEDC2F60D88D00117DD8 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9B20EEDB2F60D88D00117DD8 /* QuartzCore.framework */; };
9B20EEE02F60D92300117DD8 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9B20EEDF2F60D92300117DD8 /* Cocoa.framework */; };
9B20EF182F60F1DF00117DD8 /* CoreVideo.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9B20EF172F60F1DF00117DD8 /* CoreVideo.framework */; };
9B20EF192F60F1E500117DD8 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9B20EEDB2F60D88D00117DD8 /* QuartzCore.framework */; };
9B20EF1A2F60F1F000117DD8 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9B20EEDF2F60D92300117DD8 /* Cocoa.framework */; };
9B20EF1C2F60F22500117DD8 /* IOKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9B20EED92F60D87E00117DD8 /* IOKit.framework */; };
9B20EF1D2F60F23C00117DD8 /* CoreVideo.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9B20EF172F60F1DF00117DD8 /* CoreVideo.framework */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
9B20EE8B2F4BC55500117DD8 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 9B20EE292F4BBE9600117DD8 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 9B20EE7E2F4BC11100117DD8;
remoteInfo = Shared;
};
9B20EE8D2F4BC55F00117DD8 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 9B20EE292F4BBE9600117DD8 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 9B20EE7E2F4BC11100117DD8;
remoteInfo = Shared;
};
9B20EE8F2F4BC56500117DD8 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 9B20EE292F4BBE9600117DD8 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 9B20EE662F4BC09C00117DD8;
remoteInfo = Client;
};
9B20EE912F4BC56500117DD8 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 9B20EE292F4BBE9600117DD8 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 9B20EE742F4BC0FD00117DD8;
remoteInfo = Server;
};
9B20EE932F4BC56500117DD8 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 9B20EE292F4BBE9600117DD8 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 9B20EE7E2F4BC11100117DD8;
remoteInfo = Shared;
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
9B20EE582F4BBF4800117DD8 /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = /usr/share/man/man1/;
dstSubfolderSpec = 0;
files = (
);
runOnlyForDeploymentPostprocessing = 1;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
9B20EE3E2F4BBEEC00117DD8 /* CMakeLists.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = CMakeLists.txt; sourceTree = "<group>"; };
9B20EE5A2F4BBF4800117DD8 /* TestGame */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = TestGame; sourceTree = BUILT_PRODUCTS_DIR; };
9B20EE672F4BC09C00117DD8 /* libClient.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libClient.a; sourceTree = BUILT_PRODUCTS_DIR; };
9B20EE752F4BC0FD00117DD8 /* libServer.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libServer.a; sourceTree = BUILT_PRODUCTS_DIR; };
9B20EE7F2F4BC11100117DD8 /* libShared.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libShared.a; sourceTree = BUILT_PRODUCTS_DIR; };
9B20EE952F4BC69100117DD8 /* libwgpu_native.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libwgpu_native.a; path = "../../../eclipse-workspace/ArtifactEngine/deps/webgpu/lib/libwgpu_native.a"; sourceTree = "<group>"; };
9B20EE972F4BC6C600117DD8 /* libngtcp2.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libngtcp2.a; path = "../../../eclipse-workspace/ArtifactEngine/deps/ngtcp2/build/lib/libngtcp2.a"; sourceTree = "<group>"; };
9B20EE9B2F4BCF7D00117DD8 /* libglfw3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libglfw3.a; path = "../../../eclipse-workspace/ArtifactEngine/deps/glfw/install/lib/libglfw3.a"; sourceTree = "<group>"; };
9B20EEC12F4D028400117DD8 /* libgnutls.30.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libgnutls.30.dylib; path = ../../../homebrew/Cellar/gnutls/3.8.11/lib/libgnutls.30.dylib; sourceTree = "<group>"; };
9B20EEC32F4D141600117DD8 /* libev.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libev.a; path = ../../../homebrew/Cellar/libev/4.33/lib/libev.a; sourceTree = "<group>"; };
9B20EED42F60D84B00117DD8 /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = System/Library/Frameworks/CoreFoundation.framework; sourceTree = SDKROOT; };
9B20EED72F60D86C00117DD8 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; };
9B20EED92F60D87E00117DD8 /* IOKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = IOKit.framework; path = System/Library/Frameworks/IOKit.framework; sourceTree = SDKROOT; };
9B20EEDB2F60D88D00117DD8 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; };
9B20EEDD2F60D89F00117DD8 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
9B20EEDF2F60D92300117DD8 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; };
9B20EEE12F60D93200117DD8 /* Metal.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Metal.framework; path = System/Library/Frameworks/Metal.framework; sourceTree = SDKROOT; };
9B20EF172F60F1DF00117DD8 /* CoreVideo.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreVideo.framework; path = System/Library/Frameworks/CoreVideo.framework; sourceTree = SDKROOT; };
/* End PBXFileReference section */
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
9B20EE6C2F4BC0C500117DD8 /* Exceptions for "Client" folder in "Client" target */ = {
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
additionalCompilerFlagsByRelativePath = {
glfw3webgpu/glfw3webgpu.c = "-x objective-c";
};
membershipExceptions = (
Client.cpp,
Client.h,
glfw3webgpu/glfw3webgpu.c,
glfw3webgpu/glfw3webgpu.h,
Graphics/ChunkRenderer.cpp,
Graphics/EntityRenderer.cpp,
Graphics/EntityRenderer.h,
Graphics/Graphics.cpp,
Graphics/Graphics.h,
Graphics/UIRenderer.cpp,
Graphics/UIRenderer.h,
Graphics/WorldRenderer.cpp,
Graphics/WorldRenderer.h,
Platform/Keybinds.cpp,
Platform/Keybinds.h,
Platform/Window.cpp,
Platform/Window.h,
World/Player.cpp,
);
target = 9B20EE662F4BC09C00117DD8 /* Client */;
};
9B20EE7A2F4BC10500117DD8 /* Exceptions for "Server" folder in "Server" target */ = {
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = (
Server.cpp,
Server.h,
);
target = 9B20EE742F4BC0FD00117DD8 /* Server */;
};
9B20EE842F4BC11700117DD8 /* Exceptions for "Shared" folder in "Shared" target */ = {
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = (
Events.h,
Network/Connection.cpp,
Network/Network.cpp,
Network/Network.h,
Shared.cpp,
Shared.h,
);
target = 9B20EE7E2F4BC11100117DD8 /* Shared */;
};
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
/* Begin PBXFileSystemSynchronizedRootGroup section */
9B20EE4B2F4BBF0200117DD8 /* Client */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
9B20EE6C2F4BC0C500117DD8 /* Exceptions for "Client" folder in "Client" target */,
);
path = Client;
sourceTree = "<group>";
};
9B20EE522F4BBF0A00117DD8 /* Server */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
9B20EE7A2F4BC10500117DD8 /* Exceptions for "Server" folder in "Server" target */,
);
path = Server;
sourceTree = "<group>";
};
9B20EE552F4BBF0D00117DD8 /* Shared */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
9B20EE842F4BC11700117DD8 /* Exceptions for "Shared" folder in "Shared" target */,
);
path = Shared;
sourceTree = "<group>";
};
9B20EE5B2F4BBF4800117DD8 /* TestGame */ = {
isa = PBXFileSystemSynchronizedRootGroup;
path = TestGame;
sourceTree = "<group>";
};
/* End PBXFileSystemSynchronizedRootGroup section */
/* Begin PBXFrameworksBuildPhase section */
9B20EE572F4BBF4800117DD8 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
9B20EF1D2F60F23C00117DD8 /* CoreVideo.framework in Frameworks */,
9B20EEE02F60D92300117DD8 /* Cocoa.framework in Frameworks */,
9B20EEDC2F60D88D00117DD8 /* QuartzCore.framework in Frameworks */,
9B20EEDA2F60D87E00117DD8 /* IOKit.framework in Frameworks */,
9B20EEAB2F4BD04400117DD8 /* libServer.a in Frameworks */,
9B20EEAC2F4BD04400117DD8 /* libShared.a in Frameworks */,
9B20EEAA2F4BD04000117DD8 /* libClient.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
9B20EE652F4BC09C00117DD8 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
9B20EF1C2F60F22500117DD8 /* IOKit.framework in Frameworks */,
9B20EF1A2F60F1F000117DD8 /* Cocoa.framework in Frameworks */,
9B20EF192F60F1E500117DD8 /* QuartzCore.framework in Frameworks */,
9B20EF182F60F1DF00117DD8 /* CoreVideo.framework in Frameworks */,
9B20EEAD2F4BD05300117DD8 /* libShared.a in Frameworks */,
9B20EE9C2F4BCF7D00117DD8 /* libglfw3.a in Frameworks */,
9B20EE962F4BC69100117DD8 /* libwgpu_native.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
9B20EE732F4BC0FD00117DD8 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
9B20EEAE2F4BD05700117DD8 /* libShared.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
9B20EE7D2F4BC11100117DD8 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
9B20EEC42F4D141600117DD8 /* libev.a in Frameworks */,
9B20EEC22F4D028400117DD8 /* libgnutls.30.dylib in Frameworks */,
9B20EE982F4BC6C600117DD8 /* libngtcp2.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
9B20EE282F4BBE9600117DD8 = {
isa = PBXGroup;
children = (
9B20EE4B2F4BBF0200117DD8 /* Client */,
9B20EE3E2F4BBEEC00117DD8 /* CMakeLists.txt */,
9B20EE522F4BBF0A00117DD8 /* Server */,
9B20EE552F4BBF0D00117DD8 /* Shared */,
9B20EE5B2F4BBF4800117DD8 /* TestGame */,
9B20EE852F4BC12400117DD8 /* Frameworks */,
9B20EE322F4BBE9600117DD8 /* Products */,
);
sourceTree = "<group>";
};
9B20EE322F4BBE9600117DD8 /* Products */ = {
isa = PBXGroup;
children = (
9B20EE5A2F4BBF4800117DD8 /* TestGame */,
9B20EE672F4BC09C00117DD8 /* libClient.a */,
9B20EE752F4BC0FD00117DD8 /* libServer.a */,
9B20EE7F2F4BC11100117DD8 /* libShared.a */,
);
name = Products;
sourceTree = "<group>";
};
9B20EE852F4BC12400117DD8 /* Frameworks */ = {
isa = PBXGroup;
children = (
9B20EF172F60F1DF00117DD8 /* CoreVideo.framework */,
9B20EEE12F60D93200117DD8 /* Metal.framework */,
9B20EEDF2F60D92300117DD8 /* Cocoa.framework */,
9B20EEDD2F60D89F00117DD8 /* Foundation.framework */,
9B20EEDB2F60D88D00117DD8 /* QuartzCore.framework */,
9B20EED92F60D87E00117DD8 /* IOKit.framework */,
9B20EED72F60D86C00117DD8 /* CoreGraphics.framework */,
9B20EED42F60D84B00117DD8 /* CoreFoundation.framework */,
9B20EEC32F4D141600117DD8 /* libev.a */,
9B20EEC12F4D028400117DD8 /* libgnutls.30.dylib */,
9B20EE9B2F4BCF7D00117DD8 /* libglfw3.a */,
9B20EE972F4BC6C600117DD8 /* libngtcp2.a */,
9B20EE952F4BC69100117DD8 /* libwgpu_native.a */,
);
name = Frameworks;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXHeadersBuildPhase section */
9B20EE632F4BC09C00117DD8 /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
9B20EE712F4BC0FD00117DD8 /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
9B20EE7B2F4BC11100117DD8 /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXHeadersBuildPhase section */
/* Begin PBXNativeTarget section */
9B20EE592F4BBF4800117DD8 /* TestGame */ = {
isa = PBXNativeTarget;
buildConfigurationList = 9B20EE5E2F4BBF4800117DD8 /* Build configuration list for PBXNativeTarget "TestGame" */;
buildPhases = (
9B20EE562F4BBF4800117DD8 /* Sources */,
9B20EE572F4BBF4800117DD8 /* Frameworks */,
9B20EE582F4BBF4800117DD8 /* CopyFiles */,
);
buildRules = (
);
dependencies = (
9B20EE902F4BC56500117DD8 /* PBXTargetDependency */,
9B20EE922F4BC56500117DD8 /* PBXTargetDependency */,
9B20EE942F4BC56500117DD8 /* PBXTargetDependency */,
);
fileSystemSynchronizedGroups = (
9B20EE5B2F4BBF4800117DD8 /* TestGame */,
);
name = TestGame;
packageProductDependencies = (
);
productName = TestGame;
productReference = 9B20EE5A2F4BBF4800117DD8 /* TestGame */;
productType = "com.apple.product-type.tool";
};
9B20EE662F4BC09C00117DD8 /* Client */ = {
isa = PBXNativeTarget;
buildConfigurationList = 9B20EE682F4BC09C00117DD8 /* Build configuration list for PBXNativeTarget "Client" */;
buildPhases = (
9B20EE632F4BC09C00117DD8 /* Headers */,
9B20EE642F4BC09C00117DD8 /* Sources */,
9B20EE652F4BC09C00117DD8 /* Frameworks */,
);
buildRules = (
);
dependencies = (
9B20EE8E2F4BC55F00117DD8 /* PBXTargetDependency */,
);
name = Client;
packageProductDependencies = (
);
productName = Client;
productReference = 9B20EE672F4BC09C00117DD8 /* libClient.a */;
productType = "com.apple.product-type.library.static";
};
9B20EE742F4BC0FD00117DD8 /* Server */ = {
isa = PBXNativeTarget;
buildConfigurationList = 9B20EE762F4BC0FD00117DD8 /* Build configuration list for PBXNativeTarget "Server" */;
buildPhases = (
9B20EE712F4BC0FD00117DD8 /* Headers */,
9B20EE722F4BC0FD00117DD8 /* Sources */,
9B20EE732F4BC0FD00117DD8 /* Frameworks */,
);
buildRules = (
);
dependencies = (
9B20EE8C2F4BC55500117DD8 /* PBXTargetDependency */,
);
name = Server;
packageProductDependencies = (
);
productName = Server;
productReference = 9B20EE752F4BC0FD00117DD8 /* libServer.a */;
productType = "com.apple.product-type.library.static";
};
9B20EE7E2F4BC11100117DD8 /* Shared */ = {
isa = PBXNativeTarget;
buildConfigurationList = 9B20EE802F4BC11100117DD8 /* Build configuration list for PBXNativeTarget "Shared" */;
buildPhases = (
9B20EE7B2F4BC11100117DD8 /* Headers */,
9B20EE7C2F4BC11100117DD8 /* Sources */,
9B20EE7D2F4BC11100117DD8 /* Frameworks */,
);
buildRules = (
);
dependencies = (
);
name = Shared;
packageProductDependencies = (
);
productName = Shared;
productReference = 9B20EE7F2F4BC11100117DD8 /* libShared.a */;
productType = "com.apple.product-type.library.static";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
9B20EE292F4BBE9600117DD8 /* Project object */ = {
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = 1;
LastUpgradeCheck = 1640;
TargetAttributes = {
9B20EE592F4BBF4800117DD8 = {
CreatedOnToolsVersion = 16.4;
};
9B20EE662F4BC09C00117DD8 = {
CreatedOnToolsVersion = 16.4;
};
9B20EE742F4BC0FD00117DD8 = {
CreatedOnToolsVersion = 16.4;
};
9B20EE7E2F4BC11100117DD8 = {
CreatedOnToolsVersion = 16.4;
};
};
};
buildConfigurationList = 9B20EE2C2F4BBE9600117DD8 /* Build configuration list for PBXProject "ArtifactEngine" */;
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 9B20EE282F4BBE9600117DD8;
minimizedProjectReferenceProxies = 1;
preferredProjectObjectVersion = 77;
productRefGroup = 9B20EE322F4BBE9600117DD8 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
9B20EE592F4BBF4800117DD8 /* TestGame */,
9B20EE662F4BC09C00117DD8 /* Client */,
9B20EE742F4BC0FD00117DD8 /* Server */,
9B20EE7E2F4BC11100117DD8 /* Shared */,
);
};
/* End PBXProject section */
/* Begin PBXSourcesBuildPhase section */
9B20EE562F4BBF4800117DD8 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
9B20EE642F4BC09C00117DD8 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
9B20EE722F4BC0FD00117DD8 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
9B20EE7C2F4BC11100117DD8 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
9B20EE8C2F4BC55500117DD8 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 9B20EE7E2F4BC11100117DD8 /* Shared */;
targetProxy = 9B20EE8B2F4BC55500117DD8 /* PBXContainerItemProxy */;
};
9B20EE8E2F4BC55F00117DD8 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 9B20EE7E2F4BC11100117DD8 /* Shared */;
targetProxy = 9B20EE8D2F4BC55F00117DD8 /* PBXContainerItemProxy */;
};
9B20EE902F4BC56500117DD8 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 9B20EE662F4BC09C00117DD8 /* Client */;
targetProxy = 9B20EE8F2F4BC56500117DD8 /* PBXContainerItemProxy */;
};
9B20EE922F4BC56500117DD8 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 9B20EE742F4BC0FD00117DD8 /* Server */;
targetProxy = 9B20EE912F4BC56500117DD8 /* PBXContainerItemProxy */;
};
9B20EE942F4BC56500117DD8 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 9B20EE7E2F4BC11100117DD8 /* Shared */;
targetProxy = 9B20EE932F4BC56500117DD8 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin XCBuildConfiguration section */
9B20EE362F4BBE9600117DD8 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MACOSX_DEPLOYMENT_TARGET = 15.5;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = macosx;
};
name = Debug;
};
9B20EE372F4BBE9600117DD8 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MACOSX_DEPLOYMENT_TARGET = 15.5;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = macosx;
};
name = Release;
};
9B20EE5F2F4BBF4800117DD8 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
HEADER_SEARCH_PATHS = (
"/Users/iboettcher/eclipse-workspace/ArtifactEngine/deps/glfw/install/include",
"/Users/iboettcher/eclipse-workspace/ArtifactEngine/deps/glm",
"/Users/iboettcher/eclipse-workspace/ArtifactEngine/deps/webgpu/include",
/Users/iboettcher/Desktop/misc/ArtifactEngine/Shared,
/Users/iboettcher/Desktop/misc/ArtifactEngine/Client,
/Users/iboettcher/Desktop/misc/ArtifactEngine/Server,
"/Users/iboettcher/eclipse-workspace/ArtifactEngine/deps/ngtcp2/install/include",
/Users/iboettcher/homebrew/include,
);
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Debug;
};
9B20EE602F4BBF4800117DD8 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
HEADER_SEARCH_PATHS = (
"/Users/iboettcher/eclipse-workspace/ArtifactEngine/deps/glfw/install/include",
"/Users/iboettcher/eclipse-workspace/ArtifactEngine/deps/glm",
"/Users/iboettcher/eclipse-workspace/ArtifactEngine/deps/webgpu/include",
/Users/iboettcher/Desktop/misc/ArtifactEngine/Shared,
/Users/iboettcher/Desktop/misc/ArtifactEngine/Client,
/Users/iboettcher/Desktop/misc/ArtifactEngine/Server,
"/Users/iboettcher/eclipse-workspace/ArtifactEngine/deps/ngtcp2/install/include",
/Users/iboettcher/homebrew/include,
);
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Release;
};
9B20EE692F4BC09C00117DD8 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
EXECUTABLE_PREFIX = lib;
HEADER_SEARCH_PATHS = (
"/Users/iboettcher/eclipse-workspace/ArtifactEngine/deps/webgpu/include",
"/Users/iboettcher/eclipse-workspace/ArtifactEngine/deps",
"/Users/iboettcher/eclipse-workspace/ArtifactEngine/deps/glfw/install/include",
/Users/iboettcher/Desktop/misc/ArtifactEngine/Shared,
"/Users/iboettcher/eclipse-workspace/ArtifactEngine/deps/glm",
/Users/iboettcher/homebrew/include,
"/Users/iboettcher/eclipse-workspace/ArtifactEngine/deps/ngtcp2/install/include",
);
LIBRARY_SEARCH_PATHS = (
"/Users/iboettcher/eclipse-workspace/ArtifactEngine/deps/glfw/install/lib",
"/Users/iboettcher/eclipse-workspace/ArtifactEngine/deps/webgpu/lib",
);
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
};
name = Debug;
};
9B20EE6A2F4BC09C00117DD8 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
EXECUTABLE_PREFIX = lib;
HEADER_SEARCH_PATHS = (
"/Users/iboettcher/eclipse-workspace/ArtifactEngine/deps/webgpu/include",
"/Users/iboettcher/eclipse-workspace/ArtifactEngine/deps",
"/Users/iboettcher/eclipse-workspace/ArtifactEngine/deps/glfw/install/include",
/Users/iboettcher/Desktop/misc/ArtifactEngine/Shared,
"/Users/iboettcher/eclipse-workspace/ArtifactEngine/deps/glm",
/Users/iboettcher/homebrew/include,
"/Users/iboettcher/eclipse-workspace/ArtifactEngine/deps/ngtcp2/install/include",
);
LIBRARY_SEARCH_PATHS = (
"/Users/iboettcher/eclipse-workspace/ArtifactEngine/deps/glfw/install/lib",
"/Users/iboettcher/eclipse-workspace/ArtifactEngine/deps/webgpu/lib",
);
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
};
name = Release;
};
9B20EE772F4BC0FD00117DD8 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
EXECUTABLE_PREFIX = lib;
HEADER_SEARCH_PATHS = (
/Users/iboettcher/Desktop/misc/ArtifactEngine/Shared,
"/Users/iboettcher/eclipse-workspace/ArtifactEngine/deps/glm",
"/Users/iboettcher/eclipse-workspace/ArtifactEngine/deps/ngtcp2/install/include",
/Users/iboettcher/homebrew/include,
);
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
};
name = Debug;
};
9B20EE782F4BC0FD00117DD8 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
EXECUTABLE_PREFIX = lib;
HEADER_SEARCH_PATHS = (
/Users/iboettcher/Desktop/misc/ArtifactEngine/Shared,
"/Users/iboettcher/eclipse-workspace/ArtifactEngine/deps/glm",
"/Users/iboettcher/eclipse-workspace/ArtifactEngine/deps/ngtcp2/install/include",
/Users/iboettcher/homebrew/include,
);
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
};
name = Release;
};
9B20EE812F4BC11100117DD8 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
EXECUTABLE_PREFIX = lib;
HEADER_SEARCH_PATHS = (
"/Users/iboettcher/eclipse-workspace/ArtifactEngine/deps/ngtcp2/install/include",
"/Users/iboettcher/eclipse-workspace/ArtifactEngine/deps",
"/Users/iboettcher/eclipse-workspace/ArtifactEngine/deps/glm",
/Users/iboettcher/homebrew/include,
);
LIBRARY_SEARCH_PATHS = (
"/Users/iboettcher/eclipse-workspace/ArtifactEngine/deps/ngtcp2/install/lib",
/Users/iboettcher/homebrew/lib,
);
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
};
name = Debug;
};
9B20EE822F4BC11100117DD8 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
EXECUTABLE_PREFIX = lib;
HEADER_SEARCH_PATHS = (
"/Users/iboettcher/eclipse-workspace/ArtifactEngine/deps/ngtcp2/install/include",
"/Users/iboettcher/eclipse-workspace/ArtifactEngine/deps",
"/Users/iboettcher/eclipse-workspace/ArtifactEngine/deps/glm",
/Users/iboettcher/homebrew/include,
);
LIBRARY_SEARCH_PATHS = (
"/Users/iboettcher/eclipse-workspace/ArtifactEngine/deps/ngtcp2/install/lib",
/Users/iboettcher/homebrew/lib,
);
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
9B20EE2C2F4BBE9600117DD8 /* Build configuration list for PBXProject "ArtifactEngine" */ = {
isa = XCConfigurationList;
buildConfigurations = (
9B20EE362F4BBE9600117DD8 /* Debug */,
9B20EE372F4BBE9600117DD8 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
9B20EE5E2F4BBF4800117DD8 /* Build configuration list for PBXNativeTarget "TestGame" */ = {
isa = XCConfigurationList;
buildConfigurations = (
9B20EE5F2F4BBF4800117DD8 /* Debug */,
9B20EE602F4BBF4800117DD8 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
9B20EE682F4BC09C00117DD8 /* Build configuration list for PBXNativeTarget "Client" */ = {
isa = XCConfigurationList;
buildConfigurations = (
9B20EE692F4BC09C00117DD8 /* Debug */,
9B20EE6A2F4BC09C00117DD8 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
9B20EE762F4BC0FD00117DD8 /* Build configuration list for PBXNativeTarget "Server" */ = {
isa = XCConfigurationList;
buildConfigurations = (
9B20EE772F4BC0FD00117DD8 /* Debug */,
9B20EE782F4BC0FD00117DD8 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
9B20EE802F4BC11100117DD8 /* Build configuration list for PBXNativeTarget "Shared" */ = {
isa = XCConfigurationList;
buildConfigurations = (
9B20EE812F4BC11100117DD8 /* Debug */,
9B20EE822F4BC11100117DD8 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 9B20EE292F4BBE9600117DD8 /* Project object */;
}

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Bucket
uuid = "85AB823F-9355-4481-B75D-B26D363783AE"
type = "1"
version = "2.0">
</Bucket>

View file

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>SchemeUserState</key>
<dict>
<key>ArtifactEngine.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>0</integer>
</dict>
<key>Client.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>2</integer>
</dict>
<key>Server.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>3</integer>
</dict>
<key>Shared.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>4</integer>
</dict>
<key>TestGame.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>1</integer>
</dict>
<key>glfw3webgpu.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>5</integer>
</dict>
</dict>
</dict>
</plist>

20
CMakeLists.txt Normal file
View file

@ -0,0 +1,20 @@
cmake_minimum_required(VERSION 3.20)
project(ArtifactEngine LANGUAGES CXX)
# MARK: - Options
option(BUILD_SHARED_LIBS "Build libraries as SHARED instead of STATIC" OFF)
option(BUILD_EXAMPLES "Build example/test applications" ON )
option(BUILD_TESTS "Build unit tests" OFF)
# MARK: - Libraries
add_subdirectory(Shared)
add_subdirectory(Client)
add_subdirectory(Server)
# MARK: - Test Game
if(BUILD_EXAMPLES)
add_subdirectory(Examples/TestGame)
endif()

6
Client/CMakeLists.txt Normal file
View file

@ -0,0 +1,6 @@
file(GLOB_RECURSE MY_SOURCES CONFIGURE_DEPENDS
"*.cpp"
"*.h"
)
add_library(Client ${MY_SOURCES})

55
Client/Client.cpp Normal file
View file

@ -0,0 +1,55 @@
#include <chrono>
#include "Client.h"
#include "Graphics/UIRenderer.h"
#include "Graphics/Graphics.h"
#include <Network/Network.h>
namespace Artifact {
void Client::init() {
window = subsystem<Window>();
for (auto & system : subsystems) {
system->engine = this;
system->init();
system->reload();
}
}
void Client::render() {
for (auto & system : subsystems) {
system->render();
}
}
void Client::run() {
init();
auto time = std::chrono::steady_clock::now();
while (!window->shouldClose()) {
auto now = std::chrono::steady_clock::now();
if (time - now > std::chrono::milliseconds(50)) {
tick();
}
render();
}
}
void Client::tick() {
for (auto & system : subsystems) {
system->tick();
}
}
void Client::addDefaultSubsystems() {
addSubsystem<Window>(1080, 640, "Artifact Engine");
addSubsystem<NetworkClient>();
auto graphics = addSubsystem<Graphics>();
{
graphics->addSubsystem<UIRenderer>();
}
}
}

27
Client/Client.h Normal file
View file

@ -0,0 +1,27 @@
#pragma once
#include <memory>
#include "Shared.h"
#include "Platform/Window.h"
namespace Artifact {
/// The client class.
class Client: public Engine<BaseSubsystem> {
public:
Window * window = nullptr;
void addDefaultSubsystems();
void initDefault();
/// Initializes the client. This includes opening a window, creating a WebGPU device, etc.
void init();
/// Runs a single iteration of the render loop.
void render();
/// Runs a single iteration of the tick loop.
void tick();
/// Runs the client.
void run();
};
}

View file

@ -0,0 +1 @@
#include "ChunkRenderer.h"

View file

@ -0,0 +1,22 @@
#pragma once
#include <unordered_map>
#include <glm/glm/glm.hpp>
#include <World/Chunk.h>
#include <Shared.h>
#include "Graphics.h"
namespace Artifact {
class ChunkRenderer: public GraphicsSubsystem {
std::unordered_map<glm::ivec3, Chunk> chunks;
public:
void render() {
}
};
}

View file

@ -0,0 +1,7 @@
#include "EntityRenderer.h"
namespace Artifact {
}

View file

@ -0,0 +1,12 @@
#pragma once
#include <Shared.h>
#include "Graphics.h"
namespace Artifact {
class EntityRenderer: public GraphicsSubsystem {
};
}

View file

@ -0,0 +1,168 @@
#include "Graphics.h"
#include "../Platform/Window.h"
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
namespace Artifact {
void Graphics::init() {
instance = wgpuCreateInstance(nullptr);
window = engine->subsystem<Window>();
surface = window->createWGPUSurface(instance);
WGPURequestAdapterOptions opts {
.compatibleSurface = surface
};
wgpuInstanceRequestAdapter(instance, &opts, {
.mode = WGPUCallbackMode_AllowProcessEvents,
.callback = [](auto status, auto adapter, auto msg, auto userdata, auto) {
if (status == WGPURequestAdapterStatus_Success) {
static_cast<Graphics *>(userdata)->adapter = adapter;
} else {
throw std::runtime_error("Failed to get wgpu adapter.");
}
},
.userdata1 = this
});
// To avoid dealing with concurrency, we just block until the adapter is available (or the program errors).
while (!adapter) wgpuInstanceProcessEvents(instance);
WGPUDeviceDescriptor desc {
.uncapturedErrorCallbackInfo = {
.callback = [](auto device, auto type, auto msg, auto, auto) {
const char * message = (msg.data ? reinterpret_cast<const char *>(msg.data) : "No message");
fprintf(stderr, "Uncaptured WebGPU Error (type %d): %s\n", type, message);
}}
};
wgpuAdapterRequestDevice(adapter, &desc, {
.mode = WGPUCallbackMode_AllowProcessEvents,
.callback = [](auto status, auto device, auto msg, auto userdata, auto) {
if (status == WGPURequestDeviceStatus_Success) {
static_cast<Graphics *>(userdata)->device = device;
} else {
throw std::runtime_error("Failed to get wgpu device.");
}
},
.userdata1 = this
});
while (!device) wgpuInstanceProcessEvents(instance);
WGPUTextureDescriptor depthDesc = {};
depthDesc.dimension = WGPUTextureDimension_2D;
depthDesc.size.width = 1080;
depthDesc.size.height = 640;
depthDesc.size.depthOrArrayLayers = 1;
depthDesc.format = WGPUTextureFormat_Depth32Float;
depthDesc.usage = WGPUTextureUsage_RenderAttachment;
depthDesc.mipLevelCount = 1;
depthDesc.sampleCount = 1;
depthTexture = wgpuDeviceCreateTexture(device, &depthDesc);
depthTextureView = wgpuTextureCreateView(depthTexture, nullptr);
queue = wgpuDeviceGetQueue(device);
WGPUSurfaceConfiguration surfaceConfig = {};
surfaceConfig.device = device;
surfaceConfig.format = WGPUTextureFormat_BGRA8Unorm; // FIXME: Auto-detect
surfaceConfig.usage = WGPUTextureUsage_RenderAttachment;
surfaceConfig.width = 1080;
surfaceConfig.height = 640;
surfaceConfig.presentMode = WGPUPresentMode_Fifo;
wgpuSurfaceConfigure(surface, &surfaceConfig);
for (auto & system : subsystems) {
system->graphics = this;
printf("Graphics: %p", system->graphics);
system->init();
system->reload();
}
}
void Graphics::deinit() {
for (auto & system : subsystems) {
system->deinit();
}
wgpuTextureDestroy(depthTexture);
wgpuQueueRelease(queue);
wgpuDeviceDestroy(device);
wgpuAdapterRelease(adapter);
wgpuSurfaceRelease(surface);
wgpuInstanceRelease(instance);
}
void Graphics::render() {
WGPUSurfaceTexture surfaceTexture;
wgpuSurfaceGetCurrentTexture(surface, &surfaceTexture);
if (surfaceTexture.status == WGPUSurfaceGetCurrentTextureStatus_Error || !surfaceTexture.texture) {
throw std::runtime_error("Failed to get surface texture");
}
WGPUTextureView nextTexture = wgpuTextureCreateView(surfaceTexture.texture, nullptr);
if (!nextTexture) {
throw std::runtime_error("Failed to create texture view");
}
WGPUCommandEncoder encoder = wgpuDeviceCreateCommandEncoder(device, nullptr);
for (auto & system : subsystems) {
system->render(nextTexture, encoder);
}
WGPUCommandBufferDescriptor cmdBufferDesc = {};
cmdBufferDesc.label = WGPUStringView{"Command buffer", WGPU_STRLEN};
WGPUCommandBuffer cmdBuffer = wgpuCommandEncoderFinish(encoder, nullptr);
wgpuQueueSubmit(queue, 1, &cmdBuffer);
#ifndef WASM_BUILD
wgpuSurfacePresent(surface);
#endif
wgpuTextureViewRelease(nextTexture);
wgpuTextureRelease(surfaceTexture.texture);
}
WGPUTexture Graphics::createTextureFromData(const void* data, uint32_t width, uint32_t height) {
WGPUTextureDescriptor textureDesc = {};
textureDesc.dimension = WGPUTextureDimension_2D;
textureDesc.size.width = width;
textureDesc.size.height = height;
textureDesc.size.depthOrArrayLayers = 1;
textureDesc.format = WGPUTextureFormat_RGBA8Unorm;
textureDesc.usage = WGPUTextureUsage_TextureBinding | WGPUTextureUsage_CopyDst;
textureDesc.mipLevelCount = 1;
textureDesc.sampleCount = 1;
auto texture = wgpuDeviceCreateTexture(device, &textureDesc);
// WGPUTextureViewDescriptor viewDesc = {};
// viewDesc.format = WGPUTextureFormat_RGBA8Unorm;
// viewDesc.dimension = WGPUTextureViewDimension_2D;
// viewDesc.mipLevelCount = 1;
// viewDesc.arrayLayerCount = 1;
// tex->view = wgpuTextureCreateView(tex->texture, &viewDesc);
WGPUTexelCopyTextureInfo destination = {};
destination.texture = texture;
destination.mipLevel = 0;
destination.origin = {0, 0, 0};
destination.aspect = WGPUTextureAspect_All;
WGPUTexelCopyBufferLayout layout = {};
layout.offset = 0;
layout.bytesPerRow = width * 4;
layout.rowsPerImage = height;
WGPUExtent3D writeSize = {width, height, 1};
wgpuQueueWriteTexture(queue, &destination, data, width * height * 4, &layout, &writeSize);
return texture;
}
WGPUTexture Graphics::createTextureFromFile(const char* file) {
int width, height, channels;
auto data = stbi_load(file, &width, &height, &channels, 4);
auto out = createTextureFromData(data, width, height);
stbi_image_free(data);
return out;
}
}

View file

@ -0,0 +1,45 @@
#pragma once
#include <webgpu/webgpu.h>
#include <Shared.h>
#include "../Platform/Window.h"
namespace Artifact {
class Graphics;
class GraphicsSubsystem {
public:
Graphics * graphics;
virtual void init() {}
virtual void reload() {}
virtual void deinit() {}
virtual void render(WGPUTextureView nextTexture, WGPUCommandEncoder encoder) {}
};
class Graphics: public BaseSubsystem, public Engine<GraphicsSubsystem> {
public:
WGPUInstance instance = nullptr;
WGPUSurface surface = nullptr;
WGPUAdapter adapter = nullptr;
WGPUDevice device = nullptr;
WGPUTexture depthTexture = nullptr;
WGPUTextureView depthTextureView = nullptr;
WGPUQueue queue = nullptr;
Window * window;
void init() override;
void deinit() override;
void render() override;
void onSubsystemAdd(GraphicsSubsystem * system) {
system->graphics = this;
}
WGPUTexture createTextureFromData(const void* data, uint32_t width, uint32_t height);
WGPUTexture createTextureFromFile(const char* file);
};
}

View file

@ -0,0 +1,431 @@
#include "UIRenderer.h"
#include <iostream>
#define NK_IMPLEMENTATION
#include <nuklear.h>
#include <glm/gtc/matrix_transform.hpp>
namespace Artifact {
namespace Events {
struct DrawUI {};
}
static std::unordered_map<Key, nk_keys> ArtifactToNuklear {};
namespace {
static struct _nkInit {
_nkInit() {
// Populate the Artifact-to-Nuklear key map.
#define X(nuklear, artifact) ArtifactToNuklear[Key::artifact] = nuklear
X(NK_KEY_CTRL, ControlLeft);
X(NK_KEY_CTRL, ControlRight);
X(NK_KEY_SHIFT, ShiftLeft);
X(NK_KEY_SHIFT, ShiftRight);
X(NK_KEY_ENTER, Enter);
X(NK_KEY_BACKSPACE, DeleteBackward);
X(NK_KEY_DEL, DeleteForward);
X(NK_KEY_LEFT, ArrowLeft);
X(NK_KEY_RIGHT, ArrowRight);
X(NK_KEY_UP, ArrowUp);
X(NK_KEY_DOWN, ArrowDown);
X(NK_KEY_TAB, Tab);
#undef X
}
} __nkInit;
}
void UIRenderer::init() {
printf("UI: %p", graphics);
window = graphics->window;
window->listen<Events::InputBegin>([this](auto ev) {
nk_input_begin(&ctx);
});
window->listen<Events::InputEnd>([this](auto ev) {
nk_input_end(&ctx);
});
window->listen<Events::CursorPosEvent>([this](auto ev) {
nk_input_motion(&ctx, ev.x, ev.y);
});
window->listen<Events::ScrollEvent>([this](auto ev) {
nk_input_scroll(&ctx, nk_vec2(ev.dx, ev.dy));
});
window->listen<Events::MouseEvent>([this](auto ev) {
nk_input_button(&ctx, ev.button == Events::MOUSE_BUTTON_RIGHT ? NK_BUTTON_RIGHT : NK_BUTTON_LEFT, (int)ev.x, (int)ev.y, ev.state);
});
window->listen<Events::KeyDownEvent>([this](auto ev) {
nk_input_key(&ctx, ArtifactToNuklear[ev.key], true);
});
window->listen<Events::KeyUpEvent>([this](auto ev) {
nk_input_key(&ctx, ArtifactToNuklear[ev.key], false);
});
window->listen<Events::CharInputEvent>([this](auto ev) {
nk_input_char(&ctx, ev.codepoint);
});
nk_init_default(&ctx, nullptr);
nk_font_atlas_init_default(&atlas);
nk_font_atlas_begin(&atlas);
nk_font* font;
void* fontData = nullptr;
// FIXME: Automatic path resolution
std::string assetPath = std::string(getenv("HOME")) + "/eclipse-workspace/ArtifactEngine/assets";
FILE* fontFile = fopen((assetPath + "/fonts/Arial.ttf").c_str(), "rb");
if (fontFile) {
fseek(fontFile, 0, SEEK_END);
long fontSize = ftell(fontFile);
fseek(fontFile, 0, SEEK_SET);
fontData = malloc(fontSize);
fread(fontData, 1, fontSize, fontFile);
fclose(fontFile);
font = nk_font_atlas_add_from_memory(&atlas, fontData, fontSize, 13.0f, nullptr);
} else {
fprintf(stderr, (std::string("Failed to load font: ") + assetPath + "/fonts/Arial.ttf\n").c_str());
font = nk_font_atlas_add_default(&atlas, 13.0f, nullptr);
}
int fontWidth, fontHeight;
const void* fontImage = nk_font_atlas_bake(&atlas, &fontWidth, &fontHeight, NK_FONT_ATLAS_RGBA32);
fontTexture = graphics->createTextureFromData(fontImage, fontWidth, fontHeight);
fontTextureView = wgpuTextureCreateView(fontTexture, nullptr);
nk_font_atlas_end(&atlas, nk_handle_id(0), nullptr);
nk_style_set_font(&ctx, &font->handle);
if (fontData) free(fontData);
unsigned char whitePixel[4] = {255, 255, 255, 255};
dummyTexture = graphics->createTextureFromData(whitePixel, 1, 1);
dummyTextureView = wgpuTextureCreateView(dummyTexture, nullptr);
nk_buffer_init_default(&vertexBufferNK);
nk_buffer_init_default(&indexBufferNK);
nk_buffer_init_default(&commandBufferNK);
}
struct NKVertex {
float pos[2];
float uv[2];
uint8_t color[4];
};
void UIRenderer::reload() {
WGPUBufferDescriptor uiVertexBufferDesc = {};
uiVertexBufferDesc.size = 1024 * 1024 * sizeof(NKVertex);
uiVertexBufferDesc.usage = WGPUBufferUsage_Vertex | WGPUBufferUsage_CopyDst;
uiVertexBufferDesc.label = WGPUStringView{"UI Vertex Buffer", WGPU_STRLEN};
vertexBuffer = wgpuDeviceCreateBuffer(graphics->device, &uiVertexBufferDesc);
WGPUBufferDescriptor uiIndexBufferDesc = {};
uiIndexBufferDesc.size = 1024 * 1024 * sizeof(uint16_t);
uiIndexBufferDesc.usage = WGPUBufferUsage_Index | WGPUBufferUsage_CopyDst;
uiIndexBufferDesc.label = WGPUStringView{"UI Index Buffer", WGPU_STRLEN};
indexBuffer = wgpuDeviceCreateBuffer(graphics->device, &uiIndexBufferDesc);
WGPUBufferDescriptor uiUniformBufferDesc = {};
uiUniformBufferDesc.size = sizeof(glm::mat4);
uiUniformBufferDesc.usage = WGPUBufferUsage_Uniform | WGPUBufferUsage_CopyDst;
uiUniformBufferDesc.label = WGPUStringView{"UI Uniform Buffer", WGPU_STRLEN};
uniformBuffer = wgpuDeviceCreateBuffer(graphics->device, &uiUniformBufferDesc);
std::string uiVSCode = R"(
struct Uniforms {
ortho: mat4x4<f32>,
}
@group(0) @binding(0) var<uniform> uniforms: Uniforms;
struct VertexOutput {
@builtin(position) pos: vec4<f32>,
@location(0) uv: vec2<f32>,
@location(1) color: vec4<f32>,
}
@vertex
fn vs_main(@location(0) pos: vec2<f32>, @location(1) uv: vec2<f32>, @location(2) color: vec4<f32>) -> VertexOutput {
var out: VertexOutput;
out.pos = uniforms.ortho * vec4<f32>(pos, 0.0, 1.0);
out.uv = uv;
out.color = color;
return out;
}
)";
std::string uiFSCode = R"(
@group(0) @binding(1) var texture: texture_2d<f32>;
@group(0) @binding(2) var sampler_: sampler;
@fragment
fn fs_main(@location(0) uv: vec2<f32>, @location(1) color: vec4<f32>) -> @location(0) vec4<f32> {
return textureSample(texture, sampler_, uv) * color;
}
)";
WGPUShaderModuleDescriptor uiVSDesc = {};
WGPUShaderSourceWGSL src = {.code = WGPUStringView{uiVSCode.c_str(), uiVSCode.size()}};
src.chain.sType = WGPUSType_ShaderSourceWGSL;
uiVSDesc.nextInChain = &src.chain;
WGPUShaderModule uiVSModule = wgpuDeviceCreateShaderModule(graphics->device, &uiVSDesc);
WGPUShaderModuleDescriptor uiFSDesc = {};
src = {.code = WGPUStringView{uiFSCode.c_str(), uiFSCode.size()}};
src.chain.sType = WGPUSType_ShaderSourceWGSL;
uiFSDesc.nextInChain = &src.chain;
WGPUShaderModule uiFSModule = wgpuDeviceCreateShaderModule(graphics->device, &uiFSDesc);
WGPUSamplerDescriptor uiSamplerDesc = {};
uiSamplerDesc.addressModeU = WGPUAddressMode_ClampToEdge;
uiSamplerDesc.addressModeV = WGPUAddressMode_ClampToEdge;
uiSamplerDesc.addressModeW = WGPUAddressMode_ClampToEdge;
uiSamplerDesc.magFilter = WGPUFilterMode_Linear;
uiSamplerDesc.minFilter = WGPUFilterMode_Linear;
uiSamplerDesc.mipmapFilter = WGPUMipmapFilterMode_Linear;
uiSamplerDesc.maxAnisotropy = 1;
sampler = wgpuDeviceCreateSampler(graphics->device, &uiSamplerDesc);
if (!sampler) {
throw std::runtime_error("Failed to create UI sampler");
}
WGPUBindGroupLayoutEntry uiBglEntries[3] = {};
uiBglEntries[0].binding = 0;
uiBglEntries[0].visibility = WGPUShaderStage_Vertex;
uiBglEntries[0].buffer.type = WGPUBufferBindingType_Uniform;
uiBglEntries[1].binding = 1;
uiBglEntries[1].visibility = WGPUShaderStage_Fragment;
uiBglEntries[1].texture.sampleType = WGPUTextureSampleType_Float;
uiBglEntries[1].texture.viewDimension = WGPUTextureViewDimension_2D;
uiBglEntries[2].binding = 2;
uiBglEntries[2].visibility = WGPUShaderStage_Fragment;
uiBglEntries[2].sampler.type = WGPUSamplerBindingType_Filtering;
WGPUBindGroupLayoutDescriptor uiBglDesc = {};
uiBglDesc.entryCount = 3;
uiBglDesc.entries = uiBglEntries;
bgl = wgpuDeviceCreateBindGroupLayout(graphics->device, &uiBglDesc);
if (!bgl) {
throw std::runtime_error("Failed to create bind group layout");
}
WGPUVertexAttribute uiAttributes[3] = {};
uiAttributes[0].format = WGPUVertexFormat_Float32x2;
uiAttributes[0].offset = offsetof(NKVertex, pos);
uiAttributes[0].shaderLocation = 0;
uiAttributes[1].format = WGPUVertexFormat_Float32x2;
uiAttributes[1].offset = offsetof(NKVertex, uv);
uiAttributes[1].shaderLocation = 1;
uiAttributes[2].format = WGPUVertexFormat_Unorm8x4;
uiAttributes[2].offset = offsetof(NKVertex, color);
uiAttributes[2].shaderLocation = 2;
WGPUVertexBufferLayout uiVBLayout = {};
uiVBLayout.arrayStride = sizeof(NKVertex);
uiVBLayout.stepMode = WGPUVertexStepMode_Vertex;
uiVBLayout.attributeCount = 3;
uiVBLayout.attributes = uiAttributes;
WGPUBlendState uiBlendState = {};
uiBlendState.color.srcFactor = WGPUBlendFactor_SrcAlpha;
uiBlendState.color.dstFactor = WGPUBlendFactor_OneMinusSrcAlpha;
uiBlendState.color.operation = WGPUBlendOperation_Add;
uiBlendState.alpha.srcFactor = WGPUBlendFactor_One;
uiBlendState.alpha.dstFactor = WGPUBlendFactor_OneMinusSrcAlpha;
uiBlendState.alpha.operation = WGPUBlendOperation_Add;
WGPUColorTargetState uiColorTarget = {};
uiColorTarget.format = WGPUTextureFormat_BGRA8Unorm;
uiColorTarget.blend = &uiBlendState;
uiColorTarget.writeMask = WGPUColorWriteMask_All;
WGPUFragmentState uiFragmentState = {};
uiFragmentState.module = uiFSModule;
uiFragmentState.entryPoint = WGPUStringView{"fs_main", WGPU_STRLEN};
uiFragmentState.targetCount = 1;
uiFragmentState.targets = &uiColorTarget;
WGPUPipelineLayoutDescriptor uiPlDesc = {};
uiPlDesc.bindGroupLayoutCount = 1;
uiPlDesc.bindGroupLayouts = &bgl;
WGPUPipelineLayout uiPipelineLayout = wgpuDeviceCreatePipelineLayout(graphics->device, &uiPlDesc);
WGPURenderPipelineDescriptor uiPipelineDesc = {};
uiPipelineDesc.vertex.module = uiVSModule;
uiPipelineDesc.vertex.entryPoint = WGPUStringView{"vs_main", WGPU_STRLEN};
uiPipelineDesc.vertex.bufferCount = 1;
uiPipelineDesc.vertex.buffers = &uiVBLayout;
uiPipelineDesc.fragment = &uiFragmentState;
uiPipelineDesc.primitive.topology = WGPUPrimitiveTopology_TriangleList;
uiPipelineDesc.primitive.frontFace = WGPUFrontFace_CCW;
uiPipelineDesc.primitive.cullMode = WGPUCullMode_None;
uiPipelineDesc.layout = uiPipelineLayout;
uiPipelineDesc.multisample.count = 1;
uiPipelineDesc.multisample.mask = ~0u;
pipeline = wgpuDeviceCreateRenderPipeline(graphics->device, &uiPipelineDesc);
}
std::string txt = "Test";
int _txt = 0;
void UIRenderer::render(WGPUTextureView nextTexture, WGPUCommandEncoder encoder) {
if (nk_begin(&ctx, "Test", nk_rect(10, 10, 100, 100), NK_WINDOW_TITLE | NK_WINDOW_BORDER | NK_WINDOW_MOVABLE | NK_WINDOW_SCALABLE)) {
nk_layout_row_dynamic(&ctx, 50, 2);
nk_label(&ctx, "Test", 0);
nk_button_label(&ctx, "Test");
nk_layout_row_dynamic(&ctx, 50, 2);
nk_label(&ctx, "Test", 0);
nk_button_label(&ctx, "Test");
nk_layout_row_dynamic(&ctx, 50, 1);
nk_edit_string(&ctx, NK_EDIT_BOX, txt.data(), &_txt, 100, nk_filter_ascii);
if (nk_contextual_begin(&ctx, NK_PANEL_CONTEXTUAL, nk_vec2(100, 100), nk_rect(10, 10, 100, 100))) {
nk_layout_row_dynamic(&ctx, 25, 1);
nk_contextual_item_label(&ctx, "Button", NK_TEXT_CENTERED);
nk_contextual_end(&ctx);
}
}
nk_end(&ctx);
dispatch(Events::DrawUI {});
nk_buffer_clear(&vertexBufferNK);
nk_buffer_clear(&indexBufferNK);
nk_buffer_clear(&commandBufferNK);
struct nk_convert_config config = {};
static const struct nk_draw_vertex_layout_element vertex_layout[] = {
{NK_VERTEX_POSITION, NK_FORMAT_FLOAT, NK_OFFSETOF(NKVertex, pos)},
{NK_VERTEX_TEXCOORD, NK_FORMAT_FLOAT, NK_OFFSETOF(NKVertex, uv)},
{NK_VERTEX_COLOR, NK_FORMAT_R8G8B8A8, NK_OFFSETOF(NKVertex, color)},
{NK_VERTEX_LAYOUT_END}
};
config.vertex_layout = vertex_layout;
config.vertex_size = sizeof(NKVertex);
config.vertex_alignment = alignof(NKVertex);
config.tex_null.texture = nk_handle_id(0);
config.tex_null.uv = {0.0f, 0.0f};
config.circle_segment_count = 22;
config.curve_segment_count = 22;
config.arc_segment_count = 22;
config.global_alpha = 1.0f;
config.shape_AA = NK_ANTI_ALIASING_ON;
config.line_AA = NK_ANTI_ALIASING_ON;
nk_convert(&ctx, &commandBufferNK, &vertexBufferNK, &indexBufferNK, &config);
// FIXME: Variable size
int viewportWidth = 1080;
int viewportHeight = 640;
auto ortho = glm::ortho(0.0f, (float)viewportWidth, (float)viewportHeight, 0.0f, -1.0f, 1.0f);
wgpuQueueWriteBuffer(graphics->queue, uniformBuffer, 0, &ortho, sizeof(ortho));
WGPURenderPassColorAttachment colorAttachment = {};
colorAttachment.view = nextTexture;
colorAttachment.loadOp = WGPULoadOp_Load;
colorAttachment.storeOp = WGPUStoreOp_Store;
colorAttachment.clearValue = {0, 0.2, 0.4, 1};
colorAttachment.depthSlice = -1;
WGPURenderPassDepthStencilAttachment depthAttachment = {};
depthAttachment.view = graphics->depthTextureView;
depthAttachment.depthLoadOp = WGPULoadOp_Clear;
depthAttachment.depthStoreOp = WGPUStoreOp_Store;
depthAttachment.depthClearValue = 1.0f;
depthAttachment.depthReadOnly = false;
depthAttachment.stencilLoadOp = WGPULoadOp_Undefined;
depthAttachment.stencilStoreOp = WGPUStoreOp_Undefined;
WGPURenderPassDescriptor renderPassDesc = {};
renderPassDesc.colorAttachmentCount = 1;
renderPassDesc.colorAttachments = &colorAttachment;
renderPassDesc.depthStencilAttachment = &depthAttachment;
colorAttachment.loadOp = WGPULoadOp_Load;
WGPURenderPassDescriptor uiRenderPassDesc = {};
uiRenderPassDesc.colorAttachmentCount = 1;
uiRenderPassDesc.colorAttachments = &colorAttachment;
WGPURenderPassEncoder uiRenderPass = wgpuCommandEncoderBeginRenderPass(encoder, &uiRenderPassDesc);
wgpuRenderPassEncoderSetPipeline(uiRenderPass, pipeline);
wgpuRenderPassEncoderSetViewport(uiRenderPass, 0, 0, (float)viewportWidth, (float)viewportHeight, 0.0f, 1.0f);
size_t vertexSize = nk_buffer_total(&vertexBufferNK);
size_t indexSize = nk_buffer_total(&indexBufferNK);
if (vertexSize > 0 && indexSize > 0) {
wgpuQueueWriteBuffer(graphics->queue, vertexBuffer, 0, vertexBufferNK.memory.ptr, vertexSize);
wgpuQueueWriteBuffer(graphics->queue, indexBuffer, 0, indexBufferNK.memory.ptr, indexSize);
wgpuRenderPassEncoderSetVertexBuffer(uiRenderPass, 0, vertexBuffer, 0, vertexSize);
wgpuRenderPassEncoderSetIndexBuffer(uiRenderPass, indexBuffer, WGPUIndexFormat_Uint16, 0, indexSize);
std::vector<WGPUBindGroup> bindGroups;
WGPUBindGroupEntry uiBgEntries[3] = {};
uiBgEntries[0].binding = 0;
uiBgEntries[0].buffer = uniformBuffer;
uiBgEntries[0].offset = 0;
uiBgEntries[0].size = sizeof(glm::mat4);
uiBgEntries[2].binding = 2;
uiBgEntries[2].sampler = sampler;
std::unordered_map<int, WGPUTextureView> textureMap = {{0, fontTextureView}};
const struct nk_draw_command* cmd;
uint32_t offset = 0;
nk_draw_foreach(cmd, &ctx, &commandBufferNK) {
if (!cmd->elem_count) continue;
if (offset + cmd->elem_count > indexSize / sizeof(uint16_t)) break;
uint32_t scissorX = static_cast<uint32_t>(cmd->clip_rect.x < 0 ? 0 : cmd->clip_rect.x);
uint32_t scissorY = static_cast<uint32_t>(cmd->clip_rect.y < 0 ? 0 : cmd->clip_rect.y);
uint32_t scissorW = static_cast<uint32_t>(cmd->clip_rect.w);
uint32_t scissorH = static_cast<uint32_t>(cmd->clip_rect.h);
scissorX = std::min(scissorX, (uint32_t)viewportWidth - 1);
scissorY = std::min(scissorY, (uint32_t)viewportHeight - 1);
if (scissorW > viewportWidth || scissorH > viewportHeight || scissorX + scissorW > viewportWidth || scissorY + scissorH > viewportHeight) {
scissorW = std::min(scissorW, viewportWidth - scissorX);
scissorH = std::min(scissorH, viewportHeight - scissorY);
}
wgpuRenderPassEncoderSetScissorRect(uiRenderPass, scissorX, scissorY, scissorW, scissorH);
uiBgEntries[1].binding = 1;
uiBgEntries[1].textureView = cmd->texture.id >= 0 && textureMap.count(cmd->texture.id) ? textureMap[cmd->texture.id] : dummyTextureView;
WGPUBindGroupDescriptor uiBgDesc = {};
uiBgDesc.layout = bgl;
uiBgDesc.entryCount = 3;
uiBgDesc.entries = uiBgEntries;
WGPUBindGroup uiDynamicBindGroup = wgpuDeviceCreateBindGroup(graphics->device, &uiBgDesc);
bindGroups.push_back(uiDynamicBindGroup);
wgpuRenderPassEncoderSetBindGroup(uiRenderPass, 0, uiDynamicBindGroup, 0, nullptr);
wgpuRenderPassEncoderDrawIndexed(uiRenderPass, cmd->elem_count, 1, offset, 0, 0);
offset += cmd->elem_count;
}
wgpuRenderPassEncoderEnd(uiRenderPass);
for (WGPUBindGroup bg : bindGroups) wgpuBindGroupRelease(bg);
} else {
wgpuRenderPassEncoderEnd(uiRenderPass);
}
nk_clear(&ctx);
}
}

View file

@ -0,0 +1,47 @@
#pragma once
#define NK_INCLUDE_FIXED_TYPES
#define NK_INCLUDE_STANDARD_IO
#define NK_INCLUDE_STANDARD_VARARGS
#define NK_INCLUDE_DEFAULT_ALLOCATOR
#define NK_INCLUDE_VERTEX_BUFFER_OUTPUT
#define NK_INCLUDE_FONT_BAKING
#define NK_INCLUDE_DEFAULT_FONT
#include <nuklear.h>
#include <webgpu/webgpu.h>
#include <Shared.h>
#include "Graphics.h"
#include "../Platform/Window.h"
namespace Artifact {
class UIRenderer: public GraphicsSubsystem, public EventTarget {
nk_context ctx {};
nk_font_atlas atlas {};
WGPURenderPipeline pipeline = nullptr;
Window * window = nullptr;
WGPUBuffer vertexBuffer = nullptr;
WGPUBuffer indexBuffer = nullptr;
WGPUBuffer uniformBuffer = nullptr;
WGPUSampler sampler = nullptr;
WGPUBindGroupLayout bgl = nullptr;
WGPUTexture fontTexture = nullptr;
WGPUTextureView fontTextureView = nullptr;
WGPUTexture dummyTexture = nullptr;
WGPUTextureView dummyTextureView = nullptr;
nk_buffer vertexBufferNK;
nk_buffer indexBufferNK;
nk_buffer commandBufferNK;
void init() override;
void reload() override;
void render(WGPUTextureView nextTexture, WGPUCommandEncoder encoder) override;
WGPURenderPipeline makePipeline();
};
}

View file

@ -0,0 +1 @@
#include "WorldRenderer.h"

View file

@ -0,0 +1,3 @@
#pragma once

View file

@ -0,0 +1,12 @@
#include "Keybinds.h"
namespace Artifact {
namespace Keybinds {
}
}

View file

@ -0,0 +1,13 @@
#pragma once
#include "Window.h"
namespace Artifact {
namespace Keybinds {
}
}

163
Client/Platform/Window.cpp Normal file
View file

@ -0,0 +1,163 @@
#include <stdexcept>
#include "Window.h"
#include "glfw3webgpu/glfw3webgpu.h"
namespace Artifact {
static std::array<Key, GLFW_KEY_LAST + 1> GLFWToArtifact {};
namespace {
// Ensure that GLFW is transparently initialized prior to window creation.
static struct _glfwInit {
_glfwInit() {
if(!glfwInit()) throw std::runtime_error("Failed to initialize GLFW.");
// Populate the global GLFW-to-Artifact keycode mapping.
GLFWToArtifact.fill(Key::Unknown);
#define X(glfw, artifact) GLFWToArtifact[glfw] = Key::artifact
X(GLFW_KEY_A, A);
X(GLFW_KEY_B, B);
X(GLFW_KEY_C, C);
X(GLFW_KEY_D, D);
X(GLFW_KEY_E, E);
X(GLFW_KEY_F, F);
X(GLFW_KEY_G, G);
X(GLFW_KEY_H, H);
X(GLFW_KEY_I, I);
X(GLFW_KEY_J, J);
X(GLFW_KEY_K, K);
X(GLFW_KEY_L, L);
X(GLFW_KEY_M, M);
X(GLFW_KEY_N, N);
X(GLFW_KEY_O, O);
X(GLFW_KEY_P, P);
X(GLFW_KEY_Q, Q);
X(GLFW_KEY_R, R);
X(GLFW_KEY_S, S);
X(GLFW_KEY_T, T);
X(GLFW_KEY_U, U);
X(GLFW_KEY_V, V);
X(GLFW_KEY_W, W);
X(GLFW_KEY_X, X);
X(GLFW_KEY_Y, Y);
X(GLFW_KEY_Z, Z);
X(GLFW_KEY_0, Zero);
X(GLFW_KEY_1, One);
X(GLFW_KEY_2, Two);
X(GLFW_KEY_3, Three);
X(GLFW_KEY_4, Four);
X(GLFW_KEY_5, Five);
X(GLFW_KEY_6, Six);
X(GLFW_KEY_7, Seven);
X(GLFW_KEY_8, Eight);
X(GLFW_KEY_9, Nine);
X(GLFW_KEY_ENTER, Enter);
X(GLFW_KEY_LEFT_SHIFT, ShiftLeft);
X(GLFW_KEY_RIGHT_SHIFT, ShiftRight);
X(GLFW_KEY_LEFT_CONTROL, ControlLeft);
X(GLFW_KEY_RIGHT_CONTROL, ControlRight);
X(GLFW_KEY_LEFT_ALT, OptionLeft);
X(GLFW_KEY_RIGHT_ALT, OptionRight);
X(GLFW_KEY_LEFT_SUPER, CommandLeft);
X(GLFW_KEY_RIGHT_SUPER, CommandRight);
X(GLFW_KEY_BACKSPACE, DeleteBackward);
X(GLFW_KEY_DELETE, DeleteForward);
X(GLFW_KEY_LEFT, ArrowLeft);
X(GLFW_KEY_RIGHT, ArrowRight);
X(GLFW_KEY_UP, ArrowUp);
X(GLFW_KEY_DOWN, ArrowDown);
X(GLFW_KEY_TAB, Tab);
#undef X
}
~_glfwInit() {
glfwTerminate();
}
} __glfwInit;
}
Window::Window(uint32_t width, uint32_t height, std::string title) {
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
window = glfwCreateWindow(width, height, "", nullptr, nullptr);
glfwSetWindowUserPointer(window, this);
glfwSetCursorPosCallback(window, [](auto window, auto x, auto y) {
static_cast<Window *>(glfwGetWindowUserPointer(window))->dispatch(Events::CursorPosEvent {
.x = x,
.y = y
});
});
glfwSetMouseButtonCallback(window, [](auto window, auto button, auto action, auto mods) {
Events::MouseButton myButton = Events::MOUSE_BUTTON_LEFT;
if (button == GLFW_MOUSE_BUTTON_2) {
myButton = Events::MOUSE_BUTTON_RIGHT;
} else if (button == GLFW_MOUSE_BUTTON_3) {
myButton = Events::MOUSE_BUTTON_MIDDLE;
}
double x, y;
glfwGetCursorPos(window, &x, &y);
static_cast<Window *>(glfwGetWindowUserPointer(window))->dispatch(Events::MouseEvent {
.button = myButton,
.state = action == GLFW_PRESS,
.x = x,
.y = y
});
});
glfwSetScrollCallback(window, [](auto window, auto dx, auto dy) {
static_cast<Window *>(glfwGetWindowUserPointer(window))->dispatch(Events::ScrollEvent {
.dx = dx,
.dy = dy
});
});
glfwSetKeyCallback(window, [](auto window, auto key, auto scancode, auto action, auto mods) {
if (action == GLFW_PRESS) {
static_cast<Window *>(glfwGetWindowUserPointer(window))->dispatch(Events::KeyDownEvent {
.key = GLFWToArtifact[key]
});
} else if (action == GLFW_RELEASE) {
static_cast<Window *>(glfwGetWindowUserPointer(window))->dispatch(Events::KeyUpEvent {
.key = GLFWToArtifact[key]
});
}
});
glfwSetCharCallback(window, [](auto window, auto codepoint) {
static_cast<Window *>(glfwGetWindowUserPointer(window))->dispatch(Events::CharInputEvent {
.codepoint = codepoint
});
});
}
void Window::render() {
dispatch(Events::InputBegin {});
glfwPollEvents();
dispatch(Events::InputEnd {});
}
WGPUSurface Window::createWGPUSurface(WGPUInstance instance) {
return glfwCreateWindowWGPUSurface(instance, window);
}
void Window::setTitle(std::string title) {
glfwSetWindowTitle(window, title.c_str());
}
bool Window::shouldClose() {
return glfwWindowShouldClose(window);
}
}

88
Client/Platform/Window.h Normal file
View file

@ -0,0 +1,88 @@
#pragma once
#include <string>
#include <GLFW/glfw3.h>
#include <webgpu/webgpu.h>
#include "Shared.h"
#include "Events.h"
namespace Artifact {
enum class Key {
Unknown = 0,
A, B, C, D, E, F, G, H, I, J, K, L, M,
N, O, P, Q, R, S, T, U, V, W, X, Y, Z,
Zero, One, Two, Three, Four, Five,
Six, Seven, Eight, Nine,
ShiftLeft, ShiftRight,
ControlLeft, ControlRight,
OptionLeft, OptionRight,
CommandLeft, CommandRight,
Enter,
DeleteForward, DeleteBackward,
ArrowLeft, ArrowRight, ArrowUp, ArrowDown,
Tab,
};
namespace Events {
struct InputBegin {};
struct InputEnd {};
enum Action {
ACTION_PRESS,
ACTION_RELEASE
};
struct KeyDownEvent {
Key key;
};
struct KeyUpEvent {
Key key;
};
struct CharInputEvent {
uint32_t codepoint;
};
struct CursorPosEvent {
double x;
double y;
};
struct ScrollEvent {
double dx;
double dy;
};
enum MouseButton {
MOUSE_BUTTON_LEFT,
MOUSE_BUTTON_RIGHT,
MOUSE_BUTTON_MIDDLE
};
struct MouseEvent {
MouseButton button;
bool state;
double x;
double y;
};
}
class Window: public EventTarget, public BaseSubsystem {
private:
GLFWwindow * window = nullptr;
public:
Window(uint32_t width, uint32_t height, std::string title);
void render() override;
WGPUSurface createWGPUSurface(WGPUInstance instance);
void setTitle(std::string title);
bool shouldClose();
};
}

3
Client/World/Player.cpp Normal file
View file

@ -0,0 +1,3 @@
#include "Player.h"

11
Client/World/Player.h Normal file
View file

@ -0,0 +1,11 @@
#pragma once
#include <Shared.h>
namespace Artifact {
class Player: public BaseSubsystem {
};
}

1
Client/glfw3webgpu/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
releases/

View file

@ -0,0 +1,15 @@
# This is only meant to be included as a subdirectory in another project.
# It assumes that targets 'glfw' and 'webgpu' exist.
# Look at examples/CMakeLists.txt to see how to use it in a project.
# The glfw3webgpu target
add_library(glfw3webgpu STATIC glfw3webgpu.c)
target_include_directories(glfw3webgpu PUBLIC .)
target_link_libraries(glfw3webgpu PUBLIC glfw webgpu)
# Copy compile definitions that are PRIVATE in glfw
if (GLFW_BUILD_COCOA)
target_compile_definitions(glfw3webgpu PRIVATE _GLFW_COCOA)
target_compile_options(glfw3webgpu PRIVATE -x objective-c)
target_link_libraries(glfw3webgpu PRIVATE "-framework Cocoa" "-framework CoreVideo" "-framework IOKit" "-framework QuartzCore")
endif()

View file

@ -0,0 +1,20 @@
MIT License
Copyright (c) 2022-2024 Élie Michel and the wgpu-native authors
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.

View file

@ -0,0 +1,94 @@
<img src="https://github.com/eliemichel/glfw3webgpu/actions/workflows/cmake.yml/badge.svg" alt="CMake Badge" />
<div align="center">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/eliemichel/LearnWebGPU/main/images/webgpu-dark.svg">
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/eliemichel/LearnWebGPU/main/images/webgpu-light.svg">
<img alt="Learn WebGPU Logo" src="images/webgpu-dark.svg" width="200">
</picture>
<a href="https://github.com/eliemichel/LearnWebGPU">LearnWebGPU</a> &nbsp;|&nbsp; <a href="https://github.com/eliemichel/WebGPU-Cpp">WebGPU-C++</a> &nbsp;|&nbsp; <a href="https://github.com/eliemichel/WebGPU-distribution">WebGPU-distribution</a><br/>
<a href="https://github.com/eliemichel/glfw3webgpu">glfw3webgpu</a> &nbsp;|&nbsp; <a href="https://github.com/eliemichel/sdl2webgpu">sdl2webgpu</a> &nbsp;|&nbsp; <a href="https://github.com/eliemichel/sdl3webgpu">sdl3webgpu</a>
<a href="https://discord.gg/2Tar4Kt564"><img src="https://img.shields.io/static/v1?label=Discord&message=Join%20us!&color=blue&logo=discord&logoColor=white" alt="Discord | Join us!"/></a>
</div>
GLFW WebGPU Extension
=====================
This is an extension for the great [GLFW](https://www.glfw.org/) library for using it with **WebGPU native**. It was written as part of the [Learn WebGPU for native C++](https://eliemichel.github.io/LearnWebGPU) tutorial series.
Table of Contents
-----------------
- [Overview](#overview)
- [Usage](#usage)
- [Example](#example)
- [License](#license)
Overview
--------
This extension simply provides the following function:
```C
WGPUSurface glfwCreateWindowWGPUSurface(WGPUInstance instance, GLFWwindow* window);
```
Given a GLFW window, `glfwCreateWindowWGPUSurface` returns a WebGPU *surface* that corresponds to the window's back-end. This is a process that is highly platform-specific, which is why I believe it belongs to GLFW.
Usage
-----
**NB** The current version of this extension is written for GLFW 3.4. Up to version 1.2.0, it was written for GLFW 3.3.8.
Your project must link to an implementation of WebGPU (providing `webgpu.h`) and of course to GLFW. Then:
**Option A** If you use CMake, you can simply include this project as a subdirectory with `add_subdirectory(glfw3webgpu)` (see the content of [`CMakeLists.txt`](CMakeLists.txt)).
**Option B** Just copy [`glfw3webgpu.h`](glfw3webgpu.h) and [`glfw3webgpu.c`](glfw3webgpu.c) to your project's source tree. On MacOS, you must add the compile option `-x objective-c` and the link libraries `-framework Cocoa`, `-framework CoreVideo`, `-framework IOKit`, and `-framework QuartzCore`.
Example
-------
Thanks to this extension it is possible to simply write a fully cross-platform WebGPU hello world:
```C
#include "glfw3webgpu.h"
#define GLFW_INCLUDE_NONE
#include <GLFW/glfw3.h>
#include <webgpu/webgpu.h>
#include <stdio.h>
int main(int argc, char* argv[]) {
// Init WebGPU
WGPUInstanceDescriptor desc;
desc.nextInChain = NULL;
WGPUInstance instance = wgpuCreateInstance(&desc);
// Init GLFW
glfwInit();
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
GLFWwindow* window = glfwCreateWindow(640, 480, "Learn WebGPU", NULL, NULL);
// Here we create our WebGPU surface from the window!
WGPUSurface surface = glfwCreateWindowWGPUSurface(instance, window);
printf("surface = %p", surface);
// Terminate GLFW
while (!glfwWindowShouldClose(window)) glfwPollEvents();
glfwDestroyWindow(window);
glfwTerminate();
return 0;
}
```
**NB** The linking process depends on the implementation of WebGPU that you are using. You can find detailed instructions for the `wgpu-native` implementation in [this Hello WebGPU chapter](https://eliemichel.github.io/LearnWebGPU/getting-started/hello-webgpu.html). You may also check out [`examples/CMakeLists.txt`](examples/CMakeLists.txt).
License
-------
See [LICENSE.txt](LICENSE.txt).

View file

@ -0,0 +1,191 @@
/**
* This is an extension of GLFW for WebGPU, abstracting away the details of
* OS-specific operations.
*
* This file is part of the "Learn WebGPU for C++" book.
* https://eliemichel.github.io/LearnWebGPU
*
* Most of this code comes from the wgpu-native triangle example:
* https://github.com/gfx-rs/wgpu-native/blob/master/examples/triangle/main.c
*
* MIT License
* Copyright (c) 2022-2025 Elie Michel and the wgpu-native authors
*
* 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 "glfw3webgpu.h"
#include <webgpu/webgpu.h>
#include <GLFW/glfw3.h>
// Begin hack
#if defined(__APPLE__)
#define _GLFW_COCOA
#elif defined(_WIN32)
#define _GLFW_WIN32
#elif defined(__linux__)
#define _GLFW_X11 // or _GLFW_WAYLAND if you built GLFW with Wayland
#endif
// End hack
#ifdef __EMSCRIPTEN__
# define GLFW_EXPOSE_NATIVE_EMSCRIPTEN
# ifndef GLFW_PLATFORM_EMSCRIPTEN // not defined in older versions of emscripten
# define GLFW_PLATFORM_EMSCRIPTEN 0
# endif
#else // __EMSCRIPTEN__
# ifdef _GLFW_X11
# define GLFW_EXPOSE_NATIVE_X11
# endif
# ifdef _GLFW_WAYLAND
# define GLFW_EXPOSE_NATIVE_WAYLAND
# endif
# ifdef _GLFW_COCOA
# define GLFW_EXPOSE_NATIVE_COCOA
# endif
# ifdef _GLFW_WIN32
# define GLFW_EXPOSE_NATIVE_WIN32
# endif
#endif // __EMSCRIPTEN__
#ifdef GLFW_EXPOSE_NATIVE_COCOA
# include <Foundation/Foundation.h>
# include <QuartzCore/CAMetalLayer.h>
#endif
#ifndef __EMSCRIPTEN__
# include <GLFW/glfw3native.h>
#endif
WGPUSurface glfwCreateWindowWGPUSurface(WGPUInstance instance, GLFWwindow* window) {
#ifndef __EMSCRIPTEN__
switch (glfwGetPlatform()) {
#else
// glfwGetPlatform is not available in older versions of emscripten
switch (GLFW_PLATFORM_EMSCRIPTEN) {
#endif
#ifdef GLFW_EXPOSE_NATIVE_X11
case GLFW_PLATFORM_X11: {
Display* x11_display = glfwGetX11Display();
Window x11_window = glfwGetX11Window(window);
WGPUSurfaceSourceXlibWindow fromXlibWindow;
fromXlibWindow.chain.sType = WGPUSType_SurfaceSourceXlibWindow;
fromXlibWindow.chain.next = NULL;
fromXlibWindow.display = x11_display;
fromXlibWindow.window = x11_window;
WGPUSurfaceDescriptor surfaceDescriptor;
surfaceDescriptor.nextInChain = &fromXlibWindow.chain;
surfaceDescriptor.label = (WGPUStringView){ NULL, WGPU_STRLEN };
return wgpuInstanceCreateSurface(instance, &surfaceDescriptor);
}
#endif // GLFW_EXPOSE_NATIVE_X11
#ifdef GLFW_EXPOSE_NATIVE_WAYLAND
case GLFW_PLATFORM_WAYLAND: {
struct wl_display* wayland_display = glfwGetWaylandDisplay();
struct wl_surface* wayland_surface = glfwGetWaylandWindow(window);
WGPUSurfaceSourceWaylandSurface fromWaylandSurface;
fromWaylandSurface.chain.sType = WGPUSType_SurfaceSourceWaylandSurface;
fromWaylandSurface.chain.next = NULL;
fromWaylandSurface.display = wayland_display;
fromWaylandSurface.surface = wayland_surface;
WGPUSurfaceDescriptor surfaceDescriptor;
surfaceDescriptor.nextInChain = &fromWaylandSurface.chain;
surfaceDescriptor.label = (WGPUStringView){ NULL, WGPU_STRLEN };
return wgpuInstanceCreateSurface(instance, &surfaceDescriptor);
}
#endif // GLFW_EXPOSE_NATIVE_WAYLAND
#ifdef GLFW_EXPOSE_NATIVE_COCOA
case GLFW_PLATFORM_COCOA: {
id metal_layer = [CAMetalLayer layer];
NSWindow* ns_window = glfwGetCocoaWindow(window);
[ns_window.contentView setWantsLayer : YES] ;
[ns_window.contentView setLayer : metal_layer] ;
WGPUSurfaceSourceMetalLayer fromMetalLayer;
fromMetalLayer.chain.sType = WGPUSType_SurfaceSourceMetalLayer;
fromMetalLayer.chain.next = NULL;
fromMetalLayer.layer = (__bridge void *)(metal_layer);
WGPUSurfaceDescriptor surfaceDescriptor;
surfaceDescriptor.nextInChain = &fromMetalLayer.chain;
surfaceDescriptor.label = (WGPUStringView){ NULL, WGPU_STRLEN };
return wgpuInstanceCreateSurface(instance, &surfaceDescriptor);
}
#endif // GLFW_EXPOSE_NATIVE_COCOA
#ifdef GLFW_EXPOSE_NATIVE_WIN32
case GLFW_PLATFORM_WIN32: {
HWND hwnd = glfwGetWin32Window(window);
HINSTANCE hinstance = GetModuleHandle(NULL);
WGPUSurfaceSourceWindowsHWND fromWindowsHWND;
fromWindowsHWND.chain.sType = WGPUSType_SurfaceSourceWindowsHWND;
fromWindowsHWND.chain.next = NULL;
fromWindowsHWND.hinstance = hinstance;
fromWindowsHWND.hwnd = hwnd;
WGPUSurfaceDescriptor surfaceDescriptor;
surfaceDescriptor.nextInChain = &fromWindowsHWND.chain;
surfaceDescriptor.label = (WGPUStringView){ NULL, WGPU_STRLEN };
return wgpuInstanceCreateSurface(instance, &surfaceDescriptor);
}
#endif // GLFW_EXPOSE_NATIVE_WIN32
#ifdef GLFW_EXPOSE_NATIVE_EMSCRIPTEN
case GLFW_PLATFORM_EMSCRIPTEN: {
# ifdef WEBGPU_BACKEND_EMDAWNWEBGPU
WGPUEmscriptenSurfaceSourceCanvasHTMLSelector fromCanvasHTMLSelector;
fromCanvasHTMLSelector.chain.sType = WGPUSType_EmscriptenSurfaceSourceCanvasHTMLSelector;
fromCanvasHTMLSelector.selector = (WGPUStringView){ "canvas", WGPU_STRLEN };
# else
WGPUSurfaceDescriptorFromCanvasHTMLSelector fromCanvasHTMLSelector;
fromCanvasHTMLSelector.chain.sType = WGPUSType_SurfaceDescriptorFromCanvasHTMLSelector;
fromCanvasHTMLSelector.selector = "canvas";
# endif
fromCanvasHTMLSelector.chain.next = NULL;
WGPUSurfaceDescriptor surfaceDescriptor;
surfaceDescriptor.nextInChain = &fromCanvasHTMLSelector.chain;
# ifdef WEBGPU_BACKEND_EMDAWNWEBGPU
surfaceDescriptor.label = (WGPUStringView){ NULL, WGPU_STRLEN };
# else
surfaceDescriptor.label = NULL;
# endif
return wgpuInstanceCreateSurface(instance, &surfaceDescriptor);
}
#endif // GLFW_EXPOSE_NATIVE_EMSCRIPTEN
default:
// Unsupported platform
return NULL;
}
}

View file

@ -0,0 +1,62 @@
/**
* This is an extension of GLFW for WebGPU, abstracting away the details of
* OS-specific operations.
*
* This file is part of the "Learn WebGPU for C++" book.
* https://eliemichel.github.io/LearnWebGPU
*
* MIT License
* Copyright (c) 2022-2024 Elie Michel and the wgpu-native authors
*
* 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.
*/
#ifndef _glfw3_webgpu_h_
#define _glfw3_webgpu_h_
#include <webgpu/webgpu.h>
#include <GLFW/glfw3.h>
#ifdef __cplusplus
extern "C" {
#endif
/*! @brief Creates a WebGPU surface for the specified window.
*
* This function creates a WGPUSurface object for the specified window.
*
* If the surface cannot be created, this function returns `NULL`.
*
* It is the responsibility of the caller to destroy the window surface. The
* window surface must be destroyed using `wgpuSurfaceRelease`.
*
* @param[in] instance The WebGPU instance to create the surface in.
* @param[in] window The window to create the surface for.
* @return The handle of the surface. This is set to `NULL` if an error
* occurred.
*
* @ingroup webgpu
*/
WGPUSurface glfwCreateWindowWGPUSurface(WGPUInstance instance, GLFWwindow* window);
#ifdef __cplusplus
}
#endif
#endif // _glfw3_webgpu_h_

6
Server/CMakeLists.txt Normal file
View file

@ -0,0 +1,6 @@
file(GLOB_RECURSE MY_SOURCES CONFIGURE_DEPENDS
"*.cpp"
"*.h"
)
add_library(Server ${MY_SOURCES})

14
Server/Server.cpp Normal file
View file

@ -0,0 +1,14 @@
#include "Server.h"
#include "Network/Network.h"
namespace Artifact {
void Server::run() {
}
void Server::addDefaultSubsystems() {
addSubsystem<NetworkServer>();
}
}

13
Server/Server.h Normal file
View file

@ -0,0 +1,13 @@
#pragma once
#include "Shared.h"
namespace Artifact {
class Server: public Engine<BaseSubsystem> {
public:
void run();
void addDefaultSubsystems();
};
}

6
Shared/CMakeLists.txt Normal file
View file

@ -0,0 +1,6 @@
file(GLOB_RECURSE MY_SOURCES CONFIGURE_DEPENDS
"*.cpp"
"*.h"
)
add_library(Shared ${MY_SOURCES})

91
Shared/Events.h Normal file
View file

@ -0,0 +1,91 @@
#pragma once
#include <unordered_map>
#include <vector>
#include <functional>
#include <typeindex>
#include <any>
#include <algorithm>
class EventTarget {
public:
template<typename T>
using Callback = std::function<void(const T &)>;
// Subscribe to an event type.
template<typename T>
uint64_t listen(Callback<T> callback) {
auto type = std::type_index(typeid(T));
auto & vec = callbacks[type];
uint64_t id = nextEventId++;
vec.emplace_back([cb = std::move(callback)](const std::any & e) {
cb(std::any_cast<const T &>(e));
});
return id;
}
// Unsubscribe a specific listener.
template<typename T>
void unlisten(uint64_t id) {
_unlisten(std::type_index(typeid(T)), id);
}
// Synchronously dispatch an event.
template<typename T>
void dispatch(const T & event) {
auto it = callbacks.find(std::type_index(typeid(T)));
if (it == callbacks.end()) return;
// Copy the list in case a callback unsubscribes during dispatch.
auto copy = it->second;
for (const auto & cb : copy) {
cb(event);
}
}
// Should be called on a main loop to dispatch queued events.
void processQueue() {
for (auto & [anyEvent, copy] : queued) {
auto it = callbacks.find(anyEvent.type());
if (it != callbacks.end()) {
for (const auto & cb : copy) {
cb(anyEvent);
}
}
}
queued.clear();
}
// Queue an event to be dispatched on the next call to `processQueue`.
template<typename T>
void queue(const T & event) {
auto type = std::type_index(typeid(T));
auto it = callbacks.find(type);
if (it == callbacks.end()) return;
queued.emplace_back(std::any(event), it->second);
}
private:
using AnyCallback = std::function<void(const std::any &)>;
using CallbackWithId = std::pair<AnyCallback, uint64_t>;
std::unordered_map<std::type_index, std::vector<AnyCallback>> callbacks {};
std::vector<std::pair<std::any, std::vector<AnyCallback>>> queued {};
uint64_t nextEventId = 0;
void _unlisten(std::type_index type, uint64_t id) {
// auto it = callbacks.find(type);
// if (it == callbacks.end()) return;
//
// auto & vec = it->second;
// auto erase_it = std::find_if(vec.begin(), vec.end(),
// [id](const auto& p) { return p.second == id; });
//
// if (erase_it != vec.end()) {
// *erase_it = std::move(vec.back());
// vec.pop_back();
// }
}
};

View file

@ -0,0 +1,148 @@
#include <string>
#include <thread>
#include <ngtcp2/ngtcp2_crypto.h>
#include <ngtcp2/ngtcp2_crypto_gnutls.h>
#include <gnutls/crypto.h>
#include <cista.h>
#include "Connection.h"
namespace Artifact {
Connection::Connection() {
ngtcp2_settings_default(&settings);
ngtcp2_transport_params_default(&transportParams);
transportParams.initial_max_streams_bidi = 1;
transportParams.initial_max_data = 65535;
transportParams.initial_max_stream_data_bidi_local = 65535;
callbacks.encrypt = ngtcp2_crypto_encrypt_cb;
callbacks.decrypt = ngtcp2_crypto_decrypt_cb;
callbacks.hp_mask = ngtcp2_crypto_hp_mask_cb;
callbacks.update_key = ngtcp2_crypto_update_key_cb;
callbacks.delete_crypto_aead_ctx = ngtcp2_crypto_delete_crypto_aead_ctx_cb;
callbacks.delete_crypto_cipher_ctx = ngtcp2_crypto_delete_crypto_cipher_ctx_cb;
callbacks.get_path_challenge_data = ngtcp2_crypto_get_path_challenge_data_cb;
callbacks.rand = [](auto dest, auto len, auto ctx) {
gnutls_rnd(GNUTLS_RND_RANDOM, dest, len);
};
callbacks.get_new_connection_id = [](auto conn, auto cid, auto token, auto cidlen, auto userdata) {
gnutls_rnd(GNUTLS_RND_RANDOM, cid, cidlen);
gnutls_rnd(GNUTLS_RND_RANDOM, token, NGTCP2_STATELESS_RESET_TOKENLEN);
return 0;
};
callbacks.version_negotiation = ngtcp2_crypto_version_negotiation_cb;
gnutls_rnd(GNUTLS_RND_RANDOM, dcid.data, dcid.datalen);
gnutls_rnd(GNUTLS_RND_RANDOM, scid.data, scid.datalen);
}
void readPacket(ngtcp2_pkt_info * info, void * packet) {
}
ClientConnection::ClientConnection(std::string host, std::string port) {
struct addrinfo hints;
struct addrinfo * local = nullptr;
struct addrinfo * remote = nullptr;
getaddrinfo("::", "", &hints, &local);
getaddrinfo(host.c_str(), port.c_str(), &hints, &remote);
ngtcp2_path path = {
.local = {
.addr = local->ai_addr,
.addrlen = local->ai_addrlen
},
.remote = {
.addr = remote->ai_addr,
.addrlen = remote->ai_addrlen
}
};
if (gnutls_init(&session, GNUTLS_CLIENT) != GNUTLS_E_SUCCESS) {
throw std::runtime_error("Failed to initialize gnutls.");
}
gnutls_set_default_priority(session);
ngtcp2_conn_client_new(&conn, &dcid, &scid, &path, NGTCP2_PROTO_VER_V1, &callbacks, &settings, &transportParams, nullptr, this);
ngtcp2_conn_set_tls_native_handle(conn, session);
}
ServerConnection::ServerConnection(ngtcp2_path path) : path(path) {
if (gnutls_init(&session, GNUTLS_CLIENT) != GNUTLS_E_SUCCESS) {
throw std::runtime_error("Failed to initialize gnutls.");
}
gnutls_set_default_priority(session);
ngtcp2_conn_server_new(&conn, &dcid, &scid, &path, NGTCP2_PROTO_VER_V1, &callbacks, &settings, &transportParams, nullptr, this);
ngtcp2_conn_set_tls_native_handle(conn, session);
}
ServerListener::ServerListener(std::string port) {
struct addrinfo hints;
struct addrinfo * local = nullptr;
getaddrinfo("::", "", &hints, &local);
socketDescriptor = socket(local->ai_family, local->ai_socktype, local->ai_protocol);
int on = 1;
setsockopt(socketDescriptor, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<char *>(&on), sizeof(int));
if (local->ai_family == AF_INET6) {
int off = 0;
setsockopt(socketDescriptor, IPPROTO_IPV6, IPV6_V6ONLY, reinterpret_cast<char *>(&off), sizeof(int));
}
if (bind(socketDescriptor, local->ai_addr, local->ai_addrlen)) {
// Save our actual local address for later use.
getsockname(socketDescriptor, reinterpret_cast<sockaddr *>(&localAddr.addr), &localAddr.addrlen);
}
}
void ServerListener::onReadable(struct ev_loop *, ev_io * watcher, int) {
auto self = static_cast<ServerListener *>(watcher->data);
uint8_t buf[2048];
ngtcp2_addr remote;
auto len = recvfrom(self->socketDescriptor, buf, sizeof(buf), 0, remote.addr, &remote.addrlen);
ngtcp2_pkt_info pi{};
ngtcp2_pkt_hd hd{};
ngtcp2_pkt_decode_hd_long(&hd, buf, len);
auto it = self->connections.find(hd.dcid);
if (it != self->connections.end()) {
ServerConnection * conn = it->second.get();
ngtcp2_conn_read_pkt(conn->conn, &conn->path, &pi, buf, len, std::chrono::steady_clock::now().time_since_epoch().count());
conn->readPacket(&pi, buf);
return;
}
if (hd.flags & NGTCP2_PKT_FLAG_LONG_FORM && hd.type == NGTCP2_PKT_INITIAL) {
ngtcp2_pkt_hd accept_hd{};
if (ngtcp2_accept(&accept_hd, buf, static_cast<size_t>(len)) != 0) {
return;
}
auto new_conn = std::make_unique<ServerConnection>(ngtcp2_path {
.local = self->localAddr,
.remote = remote
});
self->connections[accept_hd.dcid] = std::move(new_conn);
}
}
void ServerListener::run() {
std::thread([this]() {
loop = ev_loop_new(EVFLAG_AUTO);
ev_io_init(&watcher, onReadable, socketDescriptor, EV_READ);
watcher.data = this;
ev_io_start(loop, &watcher);
ev_run(loop, 0);
}).detach();
}
}

View file

@ -0,0 +1,95 @@
#pragma once
#include <string>
#include <unordered_map>
#include <ngtcp2/ngtcp2.h>
#include <gnutls/gnutls.h>
#include <ev.h>
// Platform-specific socket includes and helpers
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#pragma comment(lib, "ws2_32.lib")
using socket_t = SOCKET;
#define CLOSE_SOCKET closesocket
#define SOCKLEN_T int
#define IS_INVALID_SOCKET(s) ((s) == INVALID_SOCKET)
#define GET_LAST_ERROR() WSAGetLastError()
#else
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <errno.h>
#include <netdb.h>
using socket_t = int;
#define CLOSE_SOCKET close
#define SOCKLEN_T socklen_t
#define IS_INVALID_SOCKET(s) ((s) < 0)
#define GET_LAST_ERROR() errno
#endif
namespace Artifact {
namespace {
struct Ngtcp2CidEqual {
bool operator()(const ngtcp2_cid& a, const ngtcp2_cid& b) const noexcept {
return a.datalen == b.datalen &&
std::memcmp(a.data, b.data, a.datalen) == 0;
}
};
struct Ngtcp2CidHash {
size_t operator()(const ngtcp2_cid& cid) const noexcept {
size_t h = cid.datalen;
for (uint8_t i = 0; i < cid.datalen; ++i) {
h = (h * 31) ^ cid.data[i]; // or better hash as above
}
return h;
}
};
}
class Connection {
public:
ngtcp2_settings settings;
ngtcp2_transport_params transportParams;
ngtcp2_callbacks callbacks;
ngtcp2_cid dcid, scid;
gnutls_session_t session;
ngtcp2_conn * conn;
Connection();
void readPacket(ngtcp2_pkt_info * info, void * packet);
};
class ClientConnection: public Connection {
public:
ClientConnection(std::string host, std::string port);
};
class ServerConnection: public Connection {
public:
ngtcp2_path path;
ServerConnection(ngtcp2_path path);
};
class ServerListener {
socket_t socketDescriptor;
std::unordered_map<ngtcp2_cid, std::unique_ptr<ServerConnection>, Ngtcp2CidHash, Ngtcp2CidEqual> connections;
ngtcp2_addr localAddr;
struct ev_loop * loop;
ev_io watcher;
public:
ServerListener(std::string port);
static void onReadable(struct ev_loop *, ev_io * watcher, int);
void run();
};
}

View file

@ -0,0 +1,31 @@
#include "Network.h"
namespace Artifact {
void NetworkServer::host(NetworkClient * local) {
if (active) {
unhost();
}
active = true;
localClient = local;
listener = local->listen<Events::NetworkMessage>([](auto ev) {
});
}
void NetworkServer::unhost() {
active = false;
if (localClient) {
localClient->unlisten<Events::NetworkMessage>(listener);
localClient = nullptr;
} else if (server) {
server = nullptr;
}
}
void NetworkClient::connect(NetworkServer * local) {
localServer = local;
}
}

45
Shared/Network/Network.h Normal file
View file

@ -0,0 +1,45 @@
#pragma once
#include "Shared.h"
#include "Events.h"
#include "Connection.h"
namespace Artifact {
namespace Events {
struct NetworkMessage {
void * data;
};
}
class NetworkClient;
class NetworkServer: public BaseSubsystem, public EventTarget {
NetworkClient * localClient = nullptr;
std::unique_ptr<ServerListener> server = nullptr;
bool active = false;
uint64_t listener;
public:
void host(NetworkClient * client);
void host(std::string port);
void unhost();
};
class NetworkClient: public BaseSubsystem, public EventTarget {
NetworkServer * localServer = nullptr;
std::unique_ptr<Connection> server = nullptr;
bool active = false;
uint64_t listener;
public:
void connect(NetworkServer * local);
void connect(std::string host, std::string port);
void disconnect();
};
}

7
Shared/Shared.cpp Normal file
View file

@ -0,0 +1,7 @@
#include "Shared.h"
namespace Artifact {
}

86
Shared/Shared.h Normal file
View file

@ -0,0 +1,86 @@
#pragma once
#include <vector>
#include <glm/glm.hpp>
namespace std {
template<>
struct hash<glm::ivec3> {
std::size_t operator()(const glm::ivec3& v) const noexcept {
// Very common and reasonably good quality combination for floats
// (tries to mix bits while being fast)
std::size_t seed = 0;
// You can use any of these styles — pick one:
// ────────────────────────────────────────────────────────────────
// Style 1: Boost hash_combine pattern (very popular)
auto hash_float = [](float f) -> std::size_t {
return std::hash<float>{}(f);
};
seed ^= hash_float(v.x) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
seed ^= hash_float(v.y) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
seed ^= hash_float(v.z) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
// ────────────────────────────────────────────────────────────────
// Style 2: Simpler — fold with multiplication (often enough)
// seed = std::hash<float>{}(v.x);
// seed = seed * 31 + std::hash<float>{}(v.y);
// seed = seed * 31 + std::hash<float>{}(v.z);
// ────────────────────────────────────────────────────────────────
// Style 3: Treat as 12-byte blob → good quality, but slower on some platforms
// std::size_t h = 0;
// std::memcpy(&h, &v, sizeof(float)); // x
// h ^= std::hash<std::uint32_t>{}(h);
// std::memcpy(&h, reinterpret_cast<const char*>(&v) + 4, sizeof(float));
// h ^= std::hash<std::uint32_t>{}(h) * 0x85ebca6b;
// std::memcpy(&h, reinterpret_cast<const char*>(&v) + 8, sizeof(float));
// return std::hash<std::uint32_t>{}(h);
return seed;
}
};
} // namespace std
namespace Artifact {
template<typename Subsystem>
class Engine {
public:
std::vector<std::unique_ptr<Subsystem>> subsystems;
template <typename T, typename... Args>
T * addSubsystem(Args &&... args) {
auto system = std::make_unique<T>(std::forward<Args>(args)...);
auto ptr = system.get();
subsystems.push_back(std::move(system));
return ptr;
}
template <typename T>
T * subsystem() const {
for (const auto & system : subsystems) {
if (T * ptr = dynamic_cast<T *>(system.get())) {
return ptr;
}
}
return nullptr;
}
};
class BaseSubsystem {
public:
Engine<BaseSubsystem> * engine = nullptr;
virtual void init() {}
virtual void reload() {}
virtual void deinit() {}
virtual void render() {}
virtual void tick() {}
};
}

9
Shared/World/Chunk.h Normal file
View file

@ -0,0 +1,9 @@
#include <vector>
#include <glm/glm/glm.hpp>
struct Chunk {
glm::ivec3 pos;
std::vector<uint16_t> data;
bool dirty = false;
};

49
TestGame/main.cpp Normal file
View file

@ -0,0 +1,49 @@
#include <iostream>
#include <thread>
#include <Client.h>
#include <Server.h>
#include <Network/Network.h>
using namespace Artifact;
struct Args {
bool server = false;
std::string host = "";
std::string port = "";
};
int main(int argc, const char * argv[]) {
Args args {};
for (int i = 0; i < argc; ++i) {
if (strcmp(argv[i], "--server")) {
args.server = true;
} else if (strcmp(argv[i], "--host")) {
args.host = argv[++i];
} else if (strcmp(argv[i], "--port")) {
args.port = argv[++i];
}
}
Client client;
client.addDefaultSubsystems();
std::thread([&client]() {
Server server;
server.addDefaultSubsystems();
auto serverListener = server.subsystem<NetworkServer>();
auto clientConnection = client.subsystem<NetworkClient>();
serverListener->host(clientConnection);
clientConnection->connect(serverListener);
server.run();
}).detach();
client.run();
return 0;
}