diff --git a/Content/Shaders/Reflections.flax b/Content/Shaders/Reflections.flax
index a18cf4a83..b260430e0 100644
--- a/Content/Shaders/Reflections.flax
+++ b/Content/Shaders/Reflections.flax
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:83514233fd2a22bbfe1a35520bcb279c9370b8d6715381201d83ea32ddc3b488
-size 3654
+oid sha256:1a6c6343f6963656c2cac6554302f0ffc15706e83d3f196153b92406712a01fc
+size 3680
diff --git a/Source/Editor/Cooker/CookingData.h b/Source/Editor/Cooker/CookingData.h
index 1ecfaf353..88b23d6bc 100644
--- a/Source/Editor/Cooker/CookingData.h
+++ b/Source/Editor/Cooker/CookingData.h
@@ -141,6 +141,11 @@ API_ENUM() enum class BuildPlatform
///
API_ENUM(Attributes="EditorDisplay(null, \"Windows ARM64\")")
WindowsARM64 = 15,
+
+ ///
+ /// Web
+ ///
+ Web = 16,
};
///
diff --git a/Source/Editor/Cooker/GameCooker.cpp b/Source/Editor/Cooker/GameCooker.cpp
index da2d74f87..4dbe680cf 100644
--- a/Source/Editor/Cooker/GameCooker.cpp
+++ b/Source/Editor/Cooker/GameCooker.cpp
@@ -69,6 +69,10 @@
#include "Platform/iOS/iOSPlatformTools.h"
#include "Engine/Platform/iOS/iOSPlatformSettings.h"
#endif
+#if PLATFORM_TOOLS_WEB
+#include "Platform/Web/WebPlatformTools.h"
+#include "Engine/Platform/Web/WebPlatformSettings.h"
+#endif
namespace GameCookerImpl
{
@@ -151,6 +155,8 @@ const Char* ToString(const BuildPlatform platform)
return TEXT("iOS ARM64");
case BuildPlatform::WindowsARM64:
return TEXT("Windows ARM64");
+ case BuildPlatform::Web:
+ return TEXT("Web");
default:
return TEXT("");
}
@@ -307,6 +313,10 @@ void CookingData::GetBuildPlatformName(const Char*& platform, const Char*& archi
platform = TEXT("Windows");
architecture = TEXT("ARM64");
break;
+ case BuildPlatform::Web:
+ platform = TEXT("Web");
+ architecture = TEXT("x86");
+ break;
default:
LOG(Fatal, "Unknown or unsupported build platform.");
}
@@ -461,6 +471,11 @@ PlatformTools* GameCooker::GetTools(BuildPlatform platform)
case BuildPlatform::iOSARM64:
result = New();
break;
+#endif
+#if PLATFORM_TOOLS_WEB
+ case BuildPlatform::Web:
+ result = New();
+ break;
#endif
}
Tools.Add(platform, result);
@@ -604,6 +619,9 @@ void GameCooker::GetCurrentPlatform(PlatformType& platform, BuildPlatform& build
case PlatformType::iOS:
buildPlatform = BuildPlatform::iOSARM64;
break;
+ case PlatformType::Web:
+ buildPlatform = BuildPlatform::Web;
+ break;
default: ;
}
}
diff --git a/Source/Editor/Cooker/GameCooker.cs b/Source/Editor/Cooker/GameCooker.cs
index 70a9f4dac..ef91dd5f9 100644
--- a/Source/Editor/Cooker/GameCooker.cs
+++ b/Source/Editor/Cooker/GameCooker.cs
@@ -106,6 +106,7 @@ namespace FlaxEditor
case BuildPlatform.MacOSARM64:
case BuildPlatform.MacOSx64: return PlatformType.Mac;
case BuildPlatform.iOSARM64: return PlatformType.iOS;
+ case BuildPlatform.Web: return PlatformType.Web;
default: throw new ArgumentOutOfRangeException(nameof(buildPlatform), buildPlatform, null);
}
}
diff --git a/Source/Editor/Cooker/Platform/Web/WebPlatformTools.cpp b/Source/Editor/Cooker/Platform/Web/WebPlatformTools.cpp
new file mode 100644
index 000000000..d1ed7fb9a
--- /dev/null
+++ b/Source/Editor/Cooker/Platform/Web/WebPlatformTools.cpp
@@ -0,0 +1,60 @@
+// Copyright (c) Wojciech Figat. All rights reserved.
+
+#if PLATFORM_TOOLS_WEB
+
+#include "WebPlatformTools.h"
+#include "Engine/Platform/File.h"
+#include "Engine/Platform/FileSystem.h"
+#include "Engine/Platform/Web/WebPlatformSettings.h"
+#include "Engine/Core/Config/GameSettings.h"
+#include "Engine/Core/Config/BuildSettings.h"
+#include "Engine/Content/Content.h"
+#include "Engine/Content/JsonAsset.h"
+#include "Engine/Graphics/PixelFormatExtensions.h"
+
+IMPLEMENT_SETTINGS_GETTER(WebPlatformSettings, WebPlatform);
+
+const Char* WebPlatformTools::GetDisplayName() const
+{
+ return TEXT("Web");
+}
+
+const Char* WebPlatformTools::GetName() const
+{
+ return TEXT("Web");
+}
+
+PlatformType WebPlatformTools::GetPlatform() const
+{
+ return PlatformType::Web;
+}
+
+ArchitectureType WebPlatformTools::GetArchitecture() const
+{
+ return ArchitectureType::x86;
+}
+
+DotNetAOTModes WebPlatformTools::UseAOT() const
+{
+ return DotNetAOTModes::MonoAOTStatic;
+}
+
+PixelFormat WebPlatformTools::GetTextureFormat(CookingData& data, TextureBase* texture, PixelFormat format)
+{
+ // TODO: texture compression for Web (eg. ASTC for mobile and BC for others?)
+ return PixelFormatExtensions::FindUncompressedFormat(format);
+}
+
+bool WebPlatformTools::IsNativeCodeFile(CookingData& data, const String& file)
+{
+ String extension = FileSystem::GetExtension(file);
+ return extension.IsEmpty() || extension == TEXT("html") || extension == TEXT("js") || extension == TEXT("wams");
+}
+
+bool WebPlatformTools::OnPostProcess(CookingData& data)
+{
+ // TODO: customizable HTML templates
+ return false;
+}
+
+#endif
diff --git a/Source/Editor/Cooker/Platform/Web/WebPlatformTools.h b/Source/Editor/Cooker/Platform/Web/WebPlatformTools.h
new file mode 100644
index 000000000..7b14dcd10
--- /dev/null
+++ b/Source/Editor/Cooker/Platform/Web/WebPlatformTools.h
@@ -0,0 +1,26 @@
+// Copyright (c) Wojciech Figat. All rights reserved.
+
+#pragma once
+
+#if PLATFORM_TOOLS_WEB
+
+#include "../../PlatformTools.h"
+
+///
+/// The Web platform support tools.
+///
+class WebPlatformTools : public PlatformTools
+{
+public:
+ // [PlatformTools]
+ const Char* GetDisplayName() const override;
+ const Char* GetName() const override;
+ PlatformType GetPlatform() const override;
+ ArchitectureType GetArchitecture() const override;
+ DotNetAOTModes UseAOT() const override;
+ PixelFormat GetTextureFormat(CookingData& data, TextureBase* texture, PixelFormat format) override;
+ bool IsNativeCodeFile(CookingData& data, const String& file) override;
+ bool OnPostProcess(CookingData& data) override;
+};
+
+#endif
diff --git a/Source/Editor/Cooker/Steps/CookAssetsStep.cpp b/Source/Editor/Cooker/Steps/CookAssetsStep.cpp
index c95306505..fc06f0153 100644
--- a/Source/Editor/Cooker/Steps/CookAssetsStep.cpp
+++ b/Source/Editor/Cooker/Steps/CookAssetsStep.cpp
@@ -572,6 +572,14 @@ bool ProcessShaderBase(CookAssetsStep::AssetCookData& data, ShaderAssetBase* ass
COMPILE_PROFILE(Vulkan_SM5, SHADER_FILE_CHUNK_INTERNAL_VULKAN_SM5_CACHE);
break;
}
+#endif
+#if PLATFORM_TOOLS_WEB
+ case BuildPlatform::Web:
+ {
+ const char* platformDefineName = "PLATFORM_WEB";
+ // TODO: compile shaders for WebGPU
+ break;
+ }
#endif
default:
{
diff --git a/Source/Editor/Editor.Build.cs b/Source/Editor/Editor.Build.cs
index e75954909..5dad98458 100644
--- a/Source/Editor/Editor.Build.cs
+++ b/Source/Editor/Editor.Build.cs
@@ -87,6 +87,7 @@ public class Editor : EditorModule
AddPlatformTools(options, platformToolsRoot, platformToolsRootExternal, "Android", "PLATFORM_TOOLS_ANDROID");
AddPlatformTools(options, platformToolsRoot, platformToolsRootExternal, "iOS", "PLATFORM_TOOLS_IOS");
}
+ AddPlatformTools(options, platformToolsRoot, platformToolsRootExternal, "Web", "PLATFORM_TOOLS_WEB");
// Visual Studio integration
if (options.Platform.Target == TargetPlatform.Windows && Flax.Build.Platform.BuildTargetPlatform == TargetPlatform.Windows)
diff --git a/Source/Editor/GUI/PlatformSelector.cs b/Source/Editor/GUI/PlatformSelector.cs
index ee72a5973..8c1f89e45 100644
--- a/Source/Editor/GUI/PlatformSelector.cs
+++ b/Source/Editor/GUI/PlatformSelector.cs
@@ -93,6 +93,7 @@ namespace FlaxEditor.GUI
new PlatformData(PlatformType.PS5, icons.PS5Icon128, "PlayStation 5"),
new PlatformData(PlatformType.Mac, icons.MacOSIcon128, "macOS"),
new PlatformData(PlatformType.iOS, icons.IOSIcon128, "iOS"),
+ new PlatformData(PlatformType.Web, icons.Flax64, "Web"),
};
const float IconSize = 64.0f;
diff --git a/Source/Editor/Surface/Archetypes/Tools.cs b/Source/Editor/Surface/Archetypes/Tools.cs
index 68a733197..60ecfaba6 100644
--- a/Source/Editor/Surface/Archetypes/Tools.cs
+++ b/Source/Editor/Surface/Archetypes/Tools.cs
@@ -1603,9 +1603,9 @@ namespace FlaxEditor.Surface.Archetypes
Title = "Platform Switch",
Description = "Gets the input value based on the runtime-platform type",
Flags = NodeFlags.AllGraphs,
- Size = new Float2(220, 240),
+ Size = new Float2(220, 260),
ConnectionsHints = ConnectionsHint.Value,
- IndependentBoxes = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 },
+ IndependentBoxes = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13 },
DependentBoxes = new[] { 0 },
Elements = new[]
{
@@ -1622,6 +1622,7 @@ namespace FlaxEditor.Surface.Archetypes
NodeElementArchetype.Factory.Input(9, "PlayStation 5", true, null, 10),
NodeElementArchetype.Factory.Input(10, "Mac", true, null, 11),
NodeElementArchetype.Factory.Input(11, "iOS", true, null, 12),
+ NodeElementArchetype.Factory.Input(12, "Web", true, null, 13),
}
},
new NodeArchetype
diff --git a/Source/Editor/Windows/GameCookerWindow.cs b/Source/Editor/Windows/GameCookerWindow.cs
index 8aab5d759..65107e105 100644
--- a/Source/Editor/Windows/GameCookerWindow.cs
+++ b/Source/Editor/Windows/GameCookerWindow.cs
@@ -52,6 +52,7 @@ namespace FlaxEditor.Windows
{ PlatformType.PS5, new PS5() },
{ PlatformType.Mac, new Mac() },
{ PlatformType.iOS, new iOS() },
+ { PlatformType.Web, new Web() },
};
public BuildTabProxy(GameCookerWindow win, PlatformSelector platformSelector)
@@ -165,6 +166,7 @@ namespace FlaxEditor.Windows
case BuildPlatform.UWPx64:
case BuildPlatform.LinuxX64:
case BuildPlatform.AndroidARM64:
+ case BuildPlatform.Web:
text += "\nUse Flax Launcher and download the required package.";
break;
#endif
@@ -538,6 +540,11 @@ namespace FlaxEditor.Windows
protected override BuildPlatform BuildPlatform => BuildPlatform.iOSARM64;
}
+ class Web : Platform
+ {
+ protected override BuildPlatform BuildPlatform => BuildPlatform.Web;
+ }
+
class Editor : CustomEditor
{
private PlatformType _platform;
@@ -592,6 +599,9 @@ namespace FlaxEditor.Windows
case PlatformType.iOS:
name = "iOS";
break;
+ case PlatformType.Web:
+ name = "Web";
+ break;
default:
name = Utilities.Utils.GetPropertyNameUI(_platform.ToString());
break;
diff --git a/Source/Engine/Audio/Audio.Build.cs b/Source/Engine/Audio/Audio.Build.cs
index e38e9b527..d2e628957 100644
--- a/Source/Engine/Audio/Audio.Build.cs
+++ b/Source/Engine/Audio/Audio.Build.cs
@@ -60,6 +60,9 @@ public class Audio : EngineModule
case TargetPlatform.iOS:
useOpenAL = true;
break;
+ case TargetPlatform.Web:
+ // TODO: audio playback on Web (OpenAL)
+ break;
default: throw new InvalidPlatformException(options.Platform.Target);
}
diff --git a/Source/Engine/Audio/AudioClip.cpp b/Source/Engine/Audio/AudioClip.cpp
index 4e258e81a..4529aaa83 100644
--- a/Source/Engine/Audio/AudioClip.cpp
+++ b/Source/Engine/Audio/AudioClip.cpp
@@ -194,6 +194,7 @@ bool AudioClip::ExtractData(Array& resultData, AudioDataInfo& resultDataIn
bool AudioClip::ExtractDataFloat(Array& resultData, AudioDataInfo& resultDataInfo)
{
+#if COMPILE_WITH_AUDIO_TOOL
// Extract PCM data
Array data;
if (ExtractDataRaw(data, resultDataInfo))
@@ -205,6 +206,9 @@ bool AudioClip::ExtractDataFloat(Array& resultData, AudioDataInfo& result
resultDataInfo.BitDepth = 32;
return false;
+#else
+ return true;
+#endif
}
bool AudioClip::ExtractDataRaw(Array& resultData, AudioDataInfo& resultDataInfo)
@@ -475,6 +479,7 @@ bool AudioClip::WriteBuffer(int32 chunkIndex)
}
info.NumSamples = Math::AlignDown(data.Length() / bytesPerSample, info.NumChannels * bytesPerSample);
+#if COMPILE_WITH_AUDIO_TOOL
// Convert to Mono if used as 3D source and backend doesn't support it
if (Is3D() && info.NumChannels > 1 && EnumHasNoneFlags(AudioBackend::Features(), AudioBackend::FeatureFlags::SpatialMultiChannel))
{
@@ -486,6 +491,7 @@ bool AudioClip::WriteBuffer(int32 chunkIndex)
info.NumSamples = samplesPerChannel;
data = Span(tmp2.Get(), tmp2.Count());
}
+#endif
// Write samples to the audio buffer (create one if missing)
Locker.Lock(); // StreamingTask loads buffers without lock so do it here
diff --git a/Source/Engine/Core/Config/GameSettings.cpp b/Source/Engine/Core/Config/GameSettings.cpp
index c91567d62..c75d2e8cb 100644
--- a/Source/Engine/Core/Config/GameSettings.cpp
+++ b/Source/Engine/Core/Config/GameSettings.cpp
@@ -95,6 +95,8 @@ IMPLEMENT_ENGINE_SETTINGS_GETTER(SwitchPlatformSettings, SwitchPlatform);
IMPLEMENT_ENGINE_SETTINGS_GETTER(MacPlatformSettings, MacPlatform);
#elif PLATFORM_IOS
IMPLEMENT_ENGINE_SETTINGS_GETTER(iOSPlatformSettings, iOSPlatform);
+#elif PLATFORM_WEB
+IMPLEMENT_ENGINE_SETTINGS_GETTER(WebPlatformSettings, WebPlatform);
#else
#error Unknown platform
#endif
@@ -280,6 +282,7 @@ void GameSettings::Deserialize(DeserializeStream& stream, ISerializeModifier* mo
DESERIALIZE(PS5Platform);
DESERIALIZE(MacPlatform);
DESERIALIZE(iOSPlatform);
+ DESERIALIZE(WebPlatform);
}
#if USE_EDITOR
diff --git a/Source/Engine/Core/Config/GameSettings.cs b/Source/Engine/Core/Config/GameSettings.cs
index 2f6ee40c1..4f66261be 100644
--- a/Source/Engine/Core/Config/GameSettings.cs
+++ b/Source/Engine/Core/Config/GameSettings.cs
@@ -210,6 +210,14 @@ namespace FlaxEditor.Content.Settings
public JsonAsset iOSPlatform;
#endif
+#if FLAX_EDITOR || PLATFORM_WEB
+ ///
+ /// Reference to asset. Used to apply configuration on Web platform.
+ ///
+ [EditorOrder(2110), EditorDisplay("Platform Settings", "Web"), AssetReference(typeof(WebPlatformSettings), true), Tooltip("Reference to Web Platform Settings asset")]
+ public JsonAsset WebPlatform;
+#endif
+
///
/// Gets the absolute path to the game settings asset file.
///
@@ -345,6 +353,10 @@ namespace FlaxEditor.Content.Settings
if (type == typeof(iOSPlatformSettings))
return Load(gameSettings.iOSPlatform) as T;
#endif
+#if FLAX_EDITOR || PLATFORM_WEB
+ if (type == typeof(WebPlatformSettings))
+ return Load(gameSettings.WebPlatform) as T;
+#endif
if (gameSettings.CustomSettings != null)
{
@@ -443,6 +455,10 @@ namespace FlaxEditor.Content.Settings
if (type == typeof(iOSPlatformSettings))
return gameSettings.iOSPlatform;
#endif
+#if FLAX_EDITOR || PLATFORM_WEB
+ if (type == typeof(WebPlatformSettings))
+ return gameSettings.WebPlatform;
+#endif
if (gameSettings.CustomSettings != null)
{
@@ -557,6 +573,8 @@ namespace FlaxEditor.Content.Settings
return SaveAsset(gameSettings, ref gameSettings.MacPlatform, obj);
if (type == typeof(iOSPlatformSettings))
return SaveAsset(gameSettings, ref gameSettings.iOSPlatform, obj);
+ if (type == typeof(WebPlatformSettings))
+ return SaveAsset(gameSettings, ref gameSettings.WebPlatform, obj);
return true;
}
diff --git a/Source/Engine/Core/Config/GameSettings.h b/Source/Engine/Core/Config/GameSettings.h
index db1f8ac53..7c3969f2b 100644
--- a/Source/Engine/Core/Config/GameSettings.h
+++ b/Source/Engine/Core/Config/GameSettings.h
@@ -90,6 +90,7 @@ public:
Guid PS5Platform;
Guid MacPlatform;
Guid iOSPlatform;
+ Guid WebPlatform;
public:
///
diff --git a/Source/Engine/Core/Config/PlatformSettings.h b/Source/Engine/Core/Config/PlatformSettings.h
index 6f8de1afa..b485e42c6 100644
--- a/Source/Engine/Core/Config/PlatformSettings.h
+++ b/Source/Engine/Core/Config/PlatformSettings.h
@@ -38,3 +38,6 @@
#if PLATFORM_IOS
#include "Engine/Platform/iOS/iOSPlatformSettings.h"
#endif
+#if PLATFORM_WEB
+#include "Engine/Platform/Web/WebPlatformSettings.h"
+#endif
diff --git a/Source/Engine/Core/Log.cpp b/Source/Engine/Core/Log.cpp
index 013031ced..8241097b1 100644
--- a/Source/Engine/Core/Log.cpp
+++ b/Source/Engine/Core/Log.cpp
@@ -17,7 +17,7 @@
#endif
#include
-#define LOG_ENABLE_FILE (!PLATFORM_SWITCH)
+#define LOG_ENABLE_FILE (!PLATFORM_SWITCH && !PLATFORM_WEB)
#define LOG_ENABLE_WINDOWS_SINGLE_NEW_LINE_CHAR (PLATFORM_WINDOWS && PLATFORM_DESKTOP && (USE_EDITOR || !BUILD_RELEASE))
namespace
diff --git a/Source/Engine/Core/Memory/Allocation.h b/Source/Engine/Core/Memory/Allocation.h
index f274d37a5..63c9dbcc3 100644
--- a/Source/Engine/Core/Memory/Allocation.h
+++ b/Source/Engine/Core/Memory/Allocation.h
@@ -58,7 +58,7 @@ public:
typedef void* Tag;
template
- class alignas(sizeof(void*)) Data
+ class alignas(uint64) Data
{
private:
byte _data[Capacity * sizeof(T)];
@@ -200,13 +200,13 @@ public:
typedef void* Tag;
template
- class alignas(sizeof(void*)) Data
+ class alignas(sizeof(uint64)) Data
{
private:
typedef typename FallbackAllocation::template Data FallbackData;
bool _useFallback = false;
- alignas(sizeof(void*)) byte _data[Capacity * sizeof(T)];
+ alignas(sizeof(uint64)) byte _data[Capacity * sizeof(T)];
FallbackData _fallback;
public:
diff --git a/Source/Engine/Engine/Game.h b/Source/Engine/Engine/Game.h
index 571258ed2..c12b25683 100644
--- a/Source/Engine/Engine/Game.h
+++ b/Source/Engine/Engine/Game.h
@@ -26,6 +26,8 @@
#include "Mac/MacGame.h"
#elif PLATFORM_IOS
#include "iOS/iOSGame.h"
+#elif PLATFORM_WEB
+#include "Web/WebGame.h"
#else
#error Missing Game implementation!
#endif
diff --git a/Source/Engine/Engine/Web/WebGame.h b/Source/Engine/Engine/Web/WebGame.h
new file mode 100644
index 000000000..f65ba4a93
--- /dev/null
+++ b/Source/Engine/Engine/Web/WebGame.h
@@ -0,0 +1,19 @@
+// Copyright (c) Wojciech Figat. All rights reserved.
+
+#pragma once
+
+#if PLATFORM_WEB
+
+#include "../Base/GameBase.h"
+
+///
+/// The game class implementation for Web platform.
+///
+///
+class WebGame : public GameBase
+{
+};
+
+typedef WebGame Game;
+
+#endif
diff --git a/Source/Engine/Graphics/Config.h b/Source/Engine/Graphics/Config.h
index 43fb319bf..2059fb0a9 100644
--- a/Source/Engine/Graphics/Config.h
+++ b/Source/Engine/Graphics/Config.h
@@ -45,7 +45,9 @@
#define GPU_USE_WINDOW_SRV 1
// True if allow graphics profile events and markers
+#ifndef GPU_ALLOW_PROFILE_EVENTS
#define GPU_ALLOW_PROFILE_EVENTS (!BUILD_RELEASE)
+#endif
// True if allow hardware tessellation shaders (Hull and Domain shaders)
#ifndef GPU_ALLOW_TESSELLATION_SHADERS
@@ -58,7 +60,9 @@
#endif
// Enable/disable creating GPU resources on separate threads (otherwise only the main thread can be used)
+#ifndef GPU_ENABLE_ASYNC_RESOURCES_CREATION
#define GPU_ENABLE_ASYNC_RESOURCES_CREATION 1
+#endif
// Enable/disable force shaders recompilation
#define GPU_FORCE_RECOMPILE_SHADERS 0
diff --git a/Source/Engine/Graphics/Graphics.Build.cs b/Source/Engine/Graphics/Graphics.Build.cs
index 51b37144b..caebebe50 100644
--- a/Source/Engine/Graphics/Graphics.Build.cs
+++ b/Source/Engine/Graphics/Graphics.Build.cs
@@ -96,6 +96,10 @@ public class Graphics : EngineModule
else
Log.WarningOnce(string.Format("Building for {0} without Vulkan rendering backend (Vulkan SDK is missing)", options.Platform.Target), ref _logMissingVulkanSDK);
break;
+ case TargetPlatform.Web:
+ options.PrivateDependencies.Add("GraphicsDeviceNull");
+ // TODO: add WebGPU
+ break;
default: throw new InvalidPlatformException(options.Platform.Target);
}
diff --git a/Source/Engine/Graphics/RenderBuffers.cpp b/Source/Engine/Graphics/RenderBuffers.cpp
index 8b67a64b3..65fc01ea2 100644
--- a/Source/Engine/Graphics/RenderBuffers.cpp
+++ b/Source/Engine/Graphics/RenderBuffers.cpp
@@ -127,7 +127,7 @@ GPUTexture* RenderBuffers::RequestHiZ(GPUContext* context, bool fullRes, int32 m
// Allocate or resize buffer (with full mip-chain)
// TODO: migrate to inverse depth and try using r16 again as default (should have no artifacts anymore)
- auto format = PLATFORM_ANDROID || PLATFORM_IOS || PLATFORM_SWITCH ? PixelFormat::R16_UInt : PixelFormat::R32_Float;
+ auto format = PLATFORM_WEB || PLATFORM_ANDROID || PLATFORM_IOS || PLATFORM_SWITCH ? PixelFormat::R16_UInt : PixelFormat::R32_Float;
auto width = fullRes ? _width : Math::Max(_width >> 1, 1);
auto height = fullRes ? _height : Math::Max(_height >> 1, 1);
auto desc = GPUTextureDescription::New2D(width, height, mipLevels, format, GPUTextureFlags::ShaderResource);
diff --git a/Source/Engine/Main/Main.Build.cs b/Source/Engine/Main/Main.Build.cs
index 194f268a1..20a4dad75 100644
--- a/Source/Engine/Main/Main.Build.cs
+++ b/Source/Engine/Main/Main.Build.cs
@@ -74,6 +74,9 @@ public class Main : EngineModule
case TargetPlatform.iOS:
options.SourcePaths.Add(Path.Combine(FolderPath, "Default"));
break;
+ case TargetPlatform.Web:
+ options.SourcePaths.Add(Path.Combine(FolderPath, "Web"));
+ break;
default: throw new InvalidPlatformException(options.Platform.Target);
}
}
diff --git a/Source/Engine/Main/Web/main.cpp b/Source/Engine/Main/Web/main.cpp
new file mode 100644
index 000000000..5ea325542
--- /dev/null
+++ b/Source/Engine/Main/Web/main.cpp
@@ -0,0 +1,12 @@
+// Copyright (c) Wojciech Figat. All rights reserved.
+
+#if PLATFORM_WEB
+
+#include "Engine/Engine/Engine.h"
+
+int main()
+{
+ return Engine::Main(TEXT(""));
+}
+
+#endif
diff --git a/Source/Engine/Physics/Physics.Build.cs b/Source/Engine/Physics/Physics.Build.cs
index f735a31d2..f31afcffa 100644
--- a/Source/Engine/Physics/Physics.Build.cs
+++ b/Source/Engine/Physics/Physics.Build.cs
@@ -39,6 +39,12 @@ public class Physics : EngineModule
{
base.Setup(options);
+ if (options.Platform.Target == TargetPlatform.Web) // TODO: build PhysX for Web
+ {
+ options.PrivateDefinitions.Add("COMPILE_WITH_EMPTY_PHYSICS");
+ return;
+ }
+
SetupPhysicsBackend(this, options);
if (WithCooking)
diff --git a/Source/Engine/Platform/Android/AndroidPlatform.h b/Source/Engine/Platform/Android/AndroidPlatform.h
index a1491fb06..b55deadd9 100644
--- a/Source/Engine/Platform/Android/AndroidPlatform.h
+++ b/Source/Engine/Platform/Android/AndroidPlatform.h
@@ -99,11 +99,6 @@ public:
static void Yield();
static double GetTimeSeconds();
static uint64 GetTimeCycles();
- FORCE_INLINE static uint64 GetClockFrequency()
- {
- // Dummy value
- return 1000000;
- }
static void GetSystemTime(int32& year, int32& month, int32& dayOfWeek, int32& day, int32& hour, int32& minute, int32& second, int32& millisecond);
static void GetUTCTime(int32& year, int32& month, int32& dayOfWeek, int32& day, int32& hour, int32& minute, int32& second, int32& millisecond);
static bool Init();
diff --git a/Source/Engine/Platform/Base/PlatformBase.cpp b/Source/Engine/Platform/Base/PlatformBase.cpp
index 29d13a2b0..290863fce 100644
--- a/Source/Engine/Platform/Base/PlatformBase.cpp
+++ b/Source/Engine/Platform/Base/PlatformBase.cpp
@@ -44,7 +44,7 @@ static_assert(sizeof(double) == 8, "Invalid double type size.");
// Check configuration
static_assert((PLATFORM_THREADS_LIMIT & (PLATFORM_THREADS_LIMIT - 1)) == 0, "Threads limit must be power of two.");
-static_assert(PLATFORM_THREADS_LIMIT % 4 == 0, "Threads limit must be multiple of 4.");
+static_assert(PLATFORM_THREADS_LIMIT % 4 == 0 || PLATFORM_THREADS_LIMIT == 1, "Threads limit must be multiple of 4.");
const Char* PlatformBase::ApplicationClassName = TEXT("FlaxWindow");
float PlatformBase::CustomDpiScale = 1.0f;
@@ -301,6 +301,15 @@ bool PlatformBase::Is64BitApp()
#endif
}
+bool PlatformBase::Is64BitPlatform()
+{
+#if PLATFORM_64BITS
+ return true;
+#else
+ return false;
+#endif
+}
+
int32 PlatformBase::GetCacheLineSize()
{
return (int32)Platform::GetCPUInfo().CacheLineSize;
@@ -820,6 +829,8 @@ const Char* ToString(PlatformType type)
return TEXT("Mac");
case PlatformType::iOS:
return TEXT("iOS");
+ case PlatformType::Web:
+ return TEXT("Web");
default:
return TEXT("");
}
diff --git a/Source/Engine/Platform/Base/PlatformBase.h b/Source/Engine/Platform/Base/PlatformBase.h
index 535c825a1..f8e072f2f 100644
--- a/Source/Engine/Platform/Base/PlatformBase.h
+++ b/Source/Engine/Platform/Base/PlatformBase.h
@@ -378,7 +378,7 @@ public:
/// Returns true if running on 64-bit computer
///
/// True if running on 64-bit computer, otherwise false.
- API_PROPERTY() static bool Is64BitPlatform() = delete;
+ API_PROPERTY() static bool Is64BitPlatform();
///
/// Gets the name of the operating system.
@@ -470,7 +470,11 @@ public:
/// Gets the system clock frequency.
///
/// The clock frequency.
- API_PROPERTY() static uint64 GetClockFrequency() = delete;
+ API_PROPERTY() static uint64 GetClockFrequency()
+ {
+ // Dummy value
+ return 1000000;
+ }
///
/// Gets current system time based on current computer settings.
diff --git a/Source/Engine/Platform/Base/ThreadBase.cpp b/Source/Engine/Platform/Base/ThreadBase.cpp
index 6776bc818..6cbb32f8b 100644
--- a/Source/Engine/Platform/Base/ThreadBase.cpp
+++ b/Source/Engine/Platform/Base/ThreadBase.cpp
@@ -48,7 +48,7 @@ void ThreadBase::Kill(bool waitForJoin)
return;
}
ASSERT(GetID());
- const auto thread = static_cast(this);
+ Thread* thread = (Thread*)this;
// Stop runnable object
if (_callAfterWork && _runnable)
diff --git a/Source/Engine/Platform/ConditionVariable.h b/Source/Engine/Platform/ConditionVariable.h
index 0bb707317..69b19b407 100644
--- a/Source/Engine/Platform/ConditionVariable.h
+++ b/Source/Engine/Platform/ConditionVariable.h
@@ -4,7 +4,7 @@
#if PLATFORM_WINDOWS || PLATFORM_UWP || PLATFORM_XBOX_ONE || PLATFORM_XBOX_SCARLETT
#include "Win32/Win32ConditionVariable.h"
-#elif PLATFORM_LINUX || PLATFORM_ANDROID || PLATFORM_PS4 || PLATFORM_PS5 || PLATFORM_MAC || PLATFORM_IOS
+#elif PLATFORM_LINUX || PLATFORM_ANDROID || PLATFORM_PS4 || PLATFORM_PS5 || PLATFORM_MAC || PLATFORM_IOS || PLATFORM_WEB
#include "Unix/UnixConditionVariable.h"
#elif PLATFORM_SWITCH
#include "Platforms/Switch/Engine/Platform/SwitchConditionVariable.h"
diff --git a/Source/Engine/Platform/CriticalSection.h b/Source/Engine/Platform/CriticalSection.h
index 317aff994..f9637b38d 100644
--- a/Source/Engine/Platform/CriticalSection.h
+++ b/Source/Engine/Platform/CriticalSection.h
@@ -4,7 +4,7 @@
#if PLATFORM_WINDOWS || PLATFORM_UWP || PLATFORM_XBOX_ONE || PLATFORM_XBOX_SCARLETT
#include "Win32/Win32CriticalSection.h"
-#elif PLATFORM_LINUX || PLATFORM_ANDROID || PLATFORM_PS4 || PLATFORM_PS5 || PLATFORM_MAC || PLATFORM_IOS
+#elif PLATFORM_LINUX || PLATFORM_ANDROID || PLATFORM_PS4 || PLATFORM_PS5 || PLATFORM_MAC || PLATFORM_IOS || PLATFORM_WEB
#include "Unix/UnixCriticalSection.h"
#elif PLATFORM_SWITCH
#include "Platforms/Switch/Engine/Platform/SwitchCriticalSection.h"
diff --git a/Source/Engine/Platform/Defines.h b/Source/Engine/Platform/Defines.h
index d6e817bc6..ce4fbcb5c 100644
--- a/Source/Engine/Platform/Defines.h
+++ b/Source/Engine/Platform/Defines.h
@@ -67,6 +67,11 @@ API_ENUM() enum class PlatformType
///
API_ENUM(Attributes="EditorDisplay(null, \"iOS\")")
iOS = 11,
+
+ ///
+ /// Running on Web.
+ ///
+ Web = 12,
};
///
@@ -143,6 +148,9 @@ API_ENUM() enum class ArchitectureType
#if !defined(PLATFORM_SDL)
#define PLATFORM_SDL 0
#endif
+#if !defined(PLATFORM_WEB)
+#define PLATFORM_WEB 0
+#endif
#if PLATFORM_WINDOWS
#include "Windows/WindowsDefines.h"
@@ -166,6 +174,8 @@ API_ENUM() enum class ArchitectureType
#include "Mac/MacDefines.h"
#elif PLATFORM_IOS
#include "iOS/iOSDefines.h"
+#elif PLATFORM_WEB
+#include "Web/WebDefines.h"
#else
#error Missing Defines implementation!
#endif
diff --git a/Source/Engine/Platform/File.h b/Source/Engine/Platform/File.h
index 3177ead03..e03d5902a 100644
--- a/Source/Engine/Platform/File.h
+++ b/Source/Engine/Platform/File.h
@@ -4,7 +4,7 @@
#if PLATFORM_WINDOWS || PLATFORM_UWP || PLATFORM_XBOX_ONE || PLATFORM_XBOX_SCARLETT
#include "Win32/Win32File.h"
-#elif PLATFORM_LINUX || PLATFORM_PS4 || PLATFORM_PS5 || PLATFORM_MAC
+#elif PLATFORM_LINUX || PLATFORM_PS4 || PLATFORM_PS5 || PLATFORM_MAC || PLATFORM_WEB
#include "Unix/UnixFile.h"
#elif PLATFORM_IOS
#include "iOS/iOSFile.h"
diff --git a/Source/Engine/Platform/FileSystem.h b/Source/Engine/Platform/FileSystem.h
index 3d7b94860..a2a0e4e17 100644
--- a/Source/Engine/Platform/FileSystem.h
+++ b/Source/Engine/Platform/FileSystem.h
@@ -24,6 +24,8 @@
#include "Mac/MacFileSystem.h"
#elif PLATFORM_IOS
#include "iOS/iOSFileSystem.h"
+#elif PLATFORM_WEB
+#include "Web/WebFileSystem.h"
#else
#error Missing File System implementation!
#endif
diff --git a/Source/Engine/Platform/Linux/LinuxPlatform.h b/Source/Engine/Platform/Linux/LinuxPlatform.h
index 6c857ccf9..841dcb1e3 100644
--- a/Source/Engine/Platform/Linux/LinuxPlatform.h
+++ b/Source/Engine/Platform/Linux/LinuxPlatform.h
@@ -114,11 +114,6 @@ public:
static void Yield();
static double GetTimeSeconds();
static uint64 GetTimeCycles();
- FORCE_INLINE static uint64 GetClockFrequency()
- {
- // Dummy value
- return 1000000;
- }
static void GetSystemTime(int32& year, int32& month, int32& dayOfWeek, int32& day, int32& hour, int32& minute, int32& second, int32& millisecond);
static void GetUTCTime(int32& year, int32& month, int32& dayOfWeek, int32& day, int32& hour, int32& minute, int32& second, int32& millisecond);
#if !BUILD_RELEASE
diff --git a/Source/Engine/Platform/Network.h b/Source/Engine/Platform/Network.h
index 7beb42622..26a754525 100644
--- a/Source/Engine/Platform/Network.h
+++ b/Source/Engine/Platform/Network.h
@@ -4,7 +4,7 @@
#if PLATFORM_WINDOWS || PLATFORM_UWP || PLATFORM_XBOX_ONE || PLATFORM_XBOX_SCARLETT
#include "Win32/Win32Network.h"
-#elif PLATFORM_LINUX || PLATFORM_ANDROID || PLATFORM_MAC || PLATFORM_IOS
+#elif PLATFORM_LINUX || PLATFORM_ANDROID || PLATFORM_MAC || PLATFORM_IOS || PLATFORM_WEB
#include "Unix/UnixNetwork.h"
#elif PLATFORM_PS4
#include "Platforms/PS4/Engine/Platform/PS4Network.h"
diff --git a/Source/Engine/Platform/Platform.Build.cs b/Source/Engine/Platform/Platform.Build.cs
index 7272270c0..dc976088c 100644
--- a/Source/Engine/Platform/Platform.Build.cs
+++ b/Source/Engine/Platform/Platform.Build.cs
@@ -87,20 +87,17 @@ public class Platform : EngineModule
options.SourcePaths.Add(Path.Combine(FolderPath, "Apple"));
options.SourcePaths.Add(Path.Combine(FolderPath, "iOS"));
break;
+ case TargetPlatform.Web:
+ options.SourcePaths.Add(Path.Combine(FolderPath, "Unix"));
+ options.SourcePaths.Add(Path.Combine(FolderPath, "Web"));
+ break;
default: throw new InvalidPlatformException(options.Platform.Target);
}
if (EngineConfiguration.WithSDL(options))
{
- switch (options.Platform.Target)
- {
- case TargetPlatform.Windows:
- case TargetPlatform.Linux:
- case TargetPlatform.Mac:
- options.PublicDependencies.Add("SDL");
- options.SourcePaths.Add(Path.Combine(FolderPath, "SDL"));
- break;
- }
+ options.PublicDependencies.Add("SDL");
+ options.SourcePaths.Add(Path.Combine(FolderPath, "SDL"));
if (options.Platform.Target == TargetPlatform.Linux)
options.PublicDependencies.Add("Wayland");
}
@@ -115,6 +112,7 @@ public class Platform : EngineModule
options.SourceFiles.Add(Path.Combine(FolderPath, "Apple", "ApplePlatformSettings.h"));
options.SourceFiles.Add(Path.Combine(FolderPath, "Mac", "MacPlatformSettings.h"));
options.SourceFiles.Add(Path.Combine(FolderPath, "iOS", "iOSPlatformSettings.h"));
+ options.SourceFiles.Add(Path.Combine(FolderPath, "Web", "WebPlatformSettings.h"));
AddSourceFileIfExists(options, Path.Combine(Globals.EngineRoot, "Source", "Platforms", "XboxOne", "Engine", "Platform", "XboxOnePlatformSettings.h"));
AddSourceFileIfExists(options, Path.Combine(Globals.EngineRoot, "Source", "Platforms", "XboxScarlett", "Engine", "Platform", "XboxScarlettPlatformSettings.h"));
AddSourceFileIfExists(options, Path.Combine(Globals.EngineRoot, "Source", "Platforms", "PS4", "Engine", "Platform", "PS4PlatformSettings.h"));
diff --git a/Source/Engine/Platform/Platform.h b/Source/Engine/Platform/Platform.h
index 279e4141f..e2c6808c9 100644
--- a/Source/Engine/Platform/Platform.h
+++ b/Source/Engine/Platform/Platform.h
@@ -32,6 +32,8 @@
#include "Mac/MacPlatform.h"
#elif PLATFORM_IOS
#include "iOS/iOSPlatform.h"
+#elif PLATFORM_WEB
+#include "Web/WebPlatform.h"
#else
#error Missing Platform implementation!
#endif
diff --git a/Source/Engine/Platform/ReadWriteLock.h b/Source/Engine/Platform/ReadWriteLock.h
index 5386e6ab3..a037a5b22 100644
--- a/Source/Engine/Platform/ReadWriteLock.h
+++ b/Source/Engine/Platform/ReadWriteLock.h
@@ -4,7 +4,7 @@
#if PLATFORM_WINDOWS || PLATFORM_UWP || PLATFORM_XBOX_ONE || PLATFORM_XBOX_SCARLETT
#include "Win32/Win32ReadWriteLock.h"
-#elif PLATFORM_LINUX || PLATFORM_ANDROID || PLATFORM_PS4 || PLATFORM_PS5 || PLATFORM_MAC || PLATFORM_IOS
+#elif PLATFORM_LINUX || PLATFORM_ANDROID || PLATFORM_PS4 || PLATFORM_PS5 || PLATFORM_MAC || PLATFORM_IOS || PLATFORM_WEB
#include "Unix/UnixReadWriteLock.h"
#elif PLATFORM_SWITCH
#include "Platforms/Switch/Engine/Platform/SwitchReadWriteLock.h"
diff --git a/Source/Engine/Platform/SDL/SDLPlatform.Web.cpp b/Source/Engine/Platform/SDL/SDLPlatform.Web.cpp
new file mode 100644
index 000000000..cf9523e50
--- /dev/null
+++ b/Source/Engine/Platform/SDL/SDLPlatform.Web.cpp
@@ -0,0 +1,207 @@
+// Copyright (c) Wojciech Figat. All rights reserved.
+
+#if PLATFORM_SDL && PLATFORM_WEB
+
+#include "SDLWindow.h"
+#include "Engine/Platform/MessageBox.h"
+#include "Engine/Core/Log.h"
+#include
+#include
+
+bool SDLPlatform::InitInternal()
+{
+ return false;
+}
+
+bool SDLPlatform::UsesWindows()
+{
+ return false;
+}
+
+bool SDLPlatform::UsesWayland()
+{
+ return false;
+}
+
+bool SDLPlatform::UsesX11()
+{
+ return false;
+}
+
+void SDLPlatform::PreHandleEvents()
+{
+}
+
+void SDLPlatform::PostHandleEvents()
+{
+}
+
+bool SDLWindow::HandleEventInternal(SDL_Event& event)
+{
+ return false;
+}
+
+void SDLPlatform::SetHighDpiAwarenessEnabled(bool enable)
+{
+}
+
+DragDropEffect SDLWindow::DoDragDrop(const StringView& data)
+{
+ return DragDropEffect::None;
+}
+
+DragDropEffect SDLWindow::DoDragDrop(const StringView& data, const Float2& offset, Window* dragSourceWindow)
+{
+ Show();
+ return DragDropEffect::None;
+}
+
+DialogResult MessageBox::Show(Window* parent, const StringView& text, const StringView& caption, MessageBoxButtons buttons, MessageBoxIcon icon)
+{
+ StringAnsi textAnsi(text);
+ StringAnsi captionAnsi(caption);
+
+ SDL_MessageBoxData data;
+ SDL_MessageBoxButtonData dataButtons[3];
+ data.window = nullptr;
+ data.title = captionAnsi.GetText();
+ data.message = textAnsi.GetText();
+ data.colorScheme = nullptr;
+
+ switch (icon)
+ {
+ case MessageBoxIcon::Error:
+ case MessageBoxIcon::Hand:
+ case MessageBoxIcon::Stop:
+ data.flags |= SDL_MESSAGEBOX_ERROR;
+ break;
+ case MessageBoxIcon::Asterisk:
+ case MessageBoxIcon::Information:
+ case MessageBoxIcon::Question:
+ data.flags |= SDL_MESSAGEBOX_INFORMATION;
+ break;
+ case MessageBoxIcon::Exclamation:
+ case MessageBoxIcon::Warning:
+ data.flags |= SDL_MESSAGEBOX_WARNING;
+ break;
+ default:
+ break;
+ }
+
+ switch (buttons)
+ {
+ case MessageBoxButtons::AbortRetryIgnore:
+ dataButtons[0] =
+ {
+ SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT,
+ (int)DialogResult::Abort,
+ "Abort"
+ };
+ dataButtons[1] =
+ {
+ SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT,
+ (int)DialogResult::Retry,
+ "Retry"
+ };
+ dataButtons[2] =
+ {
+ 0,
+ (int)DialogResult::Ignore,
+ "Ignore"
+ };
+ data.numbuttons = 3;
+ break;
+ case MessageBoxButtons::OK:
+ dataButtons[0] =
+ {
+ SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT | SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT,
+ (int)DialogResult::OK,
+ "OK"
+ };
+ data.numbuttons = 1;
+ break;
+ case MessageBoxButtons::OKCancel:
+ dataButtons[0] =
+ {
+ SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT,
+ (int)DialogResult::OK,
+ "OK"
+ };
+ dataButtons[1] =
+ {
+ SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT,
+ (int)DialogResult::Cancel,
+ "Cancel"
+ };
+ data.numbuttons = 2;
+ break;
+ case MessageBoxButtons::RetryCancel:
+ dataButtons[0] =
+ {
+ SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT,
+ (int)DialogResult::Retry,
+ "Retry"
+ };
+ dataButtons[1] =
+ {
+ SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT,
+ (int)DialogResult::Cancel,
+ "Cancel"
+ };
+ data.numbuttons = 2;
+ break;
+ case MessageBoxButtons::YesNo:
+ dataButtons[0] =
+ {
+ SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT,
+ (int)DialogResult::Yes,
+ "Yes"
+ };
+ dataButtons[1] =
+ {
+ SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT,
+ (int)DialogResult::No,
+ "No"
+ };
+ data.numbuttons = 2;
+ break;
+ case MessageBoxButtons::YesNoCancel:
+ {
+ dataButtons[0] =
+ {
+ SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT,
+ (int)DialogResult::Yes,
+ "Yes"
+ };
+ dataButtons[1] =
+ {
+ 0,
+ (int)DialogResult::No,
+ "No"
+ };
+ dataButtons[2] =
+ {
+ SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT,
+ (int)DialogResult::Cancel,
+ "Cancel"
+ };
+ data.numbuttons = 3;
+ break;
+ }
+ default:
+ break;
+ }
+ data.buttons = dataButtons;
+
+ int result = -1;
+ if (!SDL_ShowMessageBox(&data, &result))
+ {
+ LOG(Error, "Failed to show SDL message box: {0}", String(SDL_GetError()));
+ return DialogResult::Abort;
+ }
+ if (result < 0)
+ return DialogResult::None;
+ return (DialogResult)result;
+}
+
+#endif
diff --git a/Source/Engine/Platform/SDL/SDLPlatform.cpp b/Source/Engine/Platform/SDL/SDLPlatform.cpp
index 8f9d4e967..3a15e116f 100644
--- a/Source/Engine/Platform/SDL/SDLPlatform.cpp
+++ b/Source/Engine/Platform/SDL/SDLPlatform.cpp
@@ -32,7 +32,7 @@
namespace SDLImpl
{
int32 SystemDpi = 96;
-#if PLATFORM_LINUX
+#if PLATFORM_LINUX || PLATFORM_WEB
String UserLocale("en");
#endif
bool WindowDecorationsSupported = true;
@@ -109,7 +109,7 @@ bool SDLPlatform::Init()
if (!SDL_InitSubSystem(SDL_INIT_VIDEO | SDL_INIT_GAMEPAD))
Platform::Fatal(String::Format(TEXT("Failed to initialize SDL: {0}."), String(SDL_GetError())));
-#if PLATFORM_LINUX
+#if PLATFORM_LINUX || PLATFORM_WEB
int localesCount = 0;
auto locales = SDL_GetPreferredLocales(&localesCount);
for (int i = 0; i < localesCount; i++)
@@ -241,7 +241,7 @@ int32 SDLPlatform::GetDpi()
return SDLImpl::SystemDpi;
}
-#if PLATFORM_LINUX
+#if PLATFORM_LINUX || PLATFORM_WEB
String SDLPlatform::GetUserLocaleName()
{
return SDLImpl::UserLocale;
@@ -435,8 +435,10 @@ int32 SDLPlatform::CreateProcess(CreateProcessSettings& settings)
SDL_SetPointerProperty(props, SDL_PROP_PROCESS_CREATE_ARGS_POINTER, cmd.Get());
SDL_SetPointerProperty(props, SDL_PROP_PROCESS_CREATE_ENVIRONMENT_POINTER, env);
SDL_SetBooleanProperty(props, SDL_PROP_PROCESS_CREATE_BACKGROUND_BOOLEAN, background);
+#if !PLATFORM_WEB
if (workingDirectory.HasChars())
SDL_SetStringProperty(props, SDL_PROP_PROCESS_CREATE_WORKING_DIRECTORY_STRING, workingDirectory.Get());
+#endif
if (captureStdOut)
{
SDL_SetNumberProperty(props, SDL_PROP_PROCESS_CREATE_STDOUT_NUMBER, SDL_PROCESS_STDIO_APP);
diff --git a/Source/Engine/Platform/SDL/SDLPlatform.h b/Source/Engine/Platform/SDL/SDLPlatform.h
index bb8211485..526a58875 100644
--- a/Source/Engine/Platform/SDL/SDLPlatform.h
+++ b/Source/Engine/Platform/SDL/SDLPlatform.h
@@ -7,29 +7,25 @@
#include "Engine/Platform/Base/Enums.h"
#if PLATFORM_WINDOWS
#include "Engine/Platform/Windows/WindowsPlatform.h"
+typedef WindowsPlatform SDLPlatformBase;
typedef struct tagMSG MSG;
#elif PLATFORM_LINUX
#include "Engine/Platform/Linux/LinuxPlatform.h"
+typedef LinuxPlatform SDLPlatformBase;
union _XEvent;
#elif PLATFORM_MAC
#include "Engine/Platform/Mac/MacPlatform.h"
+typedef MacPlatform SDLPlatformBase;
+#elif PLATFORM_WEB
+#include "Engine/Platform/Web/WebPlatform.h"
+typedef WebPlatform SDLPlatformBase;
#else
-static_assert(false, "Unsupported Platform");
+static_assert(false, "Unsupported SDL platform.");
#endif
class SDLWindow;
union SDL_Event;
-#if PLATFORM_WINDOWS
-typedef WindowsPlatform SDLPlatformBase;
-#elif PLATFORM_LINUX
-typedef LinuxPlatform SDLPlatformBase;
-#elif PLATFORM_MAC
-typedef MacPlatform SDLPlatformBase;
-#else
-static_assert(false, "Unsupported SDL platform.");
-#endif
-
///
/// The SDL platform implementation and application management utilities.
///
@@ -84,7 +80,7 @@ public:
static BatteryInfo GetBatteryInfo();
#endif
static int32 GetDpi();
-#if PLATFORM_LINUX
+#if PLATFORM_LINUX || PLATFORM_WEB
static String GetUserLocaleName();
#endif
static bool CanOpenUrl(const StringView& url);
diff --git a/Source/Engine/Platform/SDL/SDLWindow.cpp b/Source/Engine/Platform/SDL/SDLWindow.cpp
index 45e686148..0b83d9c0b 100644
--- a/Source/Engine/Platform/SDL/SDLWindow.cpp
+++ b/Source/Engine/Platform/SDL/SDLWindow.cpp
@@ -38,6 +38,7 @@
#include "Engine/Platform/Linux/IncludeX11.h"
#elif PLATFORM_MAC
#include
+#elif PLATFORM_WEB
#else
static_assert(false, "Unsupported Platform");
#endif
@@ -77,6 +78,8 @@ void* GetNativeWindowPointer(SDL_Window* window)
windowPtr = SDL_GetPointerProperty(props, SDL_PROP_WINDOW_ANDROID_WINDOW_POINTER, nullptr);
#elif PLATFORM_IOS
windowPtr = SDL_GetPointerProperty(props, SDL_PROP_WINDOW_UIKIT_WINDOW_POINTER, nullptr);
+#elif PLATFORM_WEB
+ windowPtr = (void*)1; // Mock value (TODO: consider SDL_PROP_WINDOW_EMSCRIPTEN_CANVAS_ID_STRING)
#else
static_assert(false, "unsupported platform");
#endif
diff --git a/Source/Engine/Platform/Thread.h b/Source/Engine/Platform/Thread.h
index 99a7daf25..b6408aa4c 100644
--- a/Source/Engine/Platform/Thread.h
+++ b/Source/Engine/Platform/Thread.h
@@ -16,6 +16,8 @@
#include "Platforms/Switch/Engine/Platform/SwitchThread.h"
#elif PLATFORM_MAC || PLATFORM_IOS
#include "Apple/AppleThread.h"
+#elif PLATFORM_WEB
+#include "Web/WebThread.h"
#else
#error Missing Thread implementation!
#endif
diff --git a/Source/Engine/Platform/Types.h b/Source/Engine/Platform/Types.h
index 7a008abc8..cd5c5ce23 100644
--- a/Source/Engine/Platform/Types.h
+++ b/Source/Engine/Platform/Types.h
@@ -305,6 +305,29 @@ typedef UnixNetwork Network;
class UserBase;
typedef UserBase User;
+#elif PLATFORM_WEB
+
+class UnixCriticalSection;
+typedef UnixCriticalSection CriticalSection;
+class UnixReadWriteLock;
+typedef UnixReadWriteLock ReadWriteLock;
+class UnixConditionVariable;
+typedef UnixConditionVariable ConditionVariable;
+class WebFileSystem;
+typedef WebFileSystem FileSystem;
+class FileSystemWatcherBase;
+typedef FileSystemWatcherBase FileSystemWatcher;
+class UnixFile;
+typedef UnixFile File;
+class WebThread;
+typedef WebThread Thread;
+class ClipboardBase;
+typedef ClipboardBase Clipboard;
+class UnixNetwork;
+typedef UnixNetwork Network;
+class UserBase;
+typedef UserBase User;
+
#else
#error Missing Types implementation!
diff --git a/Source/Engine/Platform/Web/WebDefines.h b/Source/Engine/Platform/Web/WebDefines.h
new file mode 100644
index 000000000..edfff3c04
--- /dev/null
+++ b/Source/Engine/Platform/Web/WebDefines.h
@@ -0,0 +1,30 @@
+// Copyright (c) Wojciech Figat. All rights reserved.
+
+#pragma once
+
+#if PLATFORM_WEB
+
+#include "../Unix/UnixDefines.h"
+
+// Platform description
+#define PLATFORM_TYPE PlatformType::Web
+#define PLATFORM_64BITS 0
+#define PLATFORM_ARCH ArchitectureType::x86
+#define PLATFORM_CACHE_LINE_SIZE 64
+#define PLATFORM_DEBUG_BREAK
+#define PLATFORM_OUT_OF_MEMORY_BUFFER_SIZE 0
+
+// Configure graphics
+#define GPU_ALLOW_TESSELLATION_SHADERS 0
+#define GPU_ALLOW_GEOMETRY_SHADERS 0
+#define GPU_ALLOW_PROFILE_EVENTS 0
+
+// Threading is optional
+#ifdef __EMSCRIPTEN_PTHREADS__
+#define PLATFORM_THREADS_LIMIT 4
+#else
+#define PLATFORM_THREADS_LIMIT 1
+#define GPU_ENABLE_ASYNC_RESOURCES_CREATION 0
+#endif
+
+#endif
diff --git a/Source/Engine/Platform/Web/WebFileSystem.h b/Source/Engine/Platform/Web/WebFileSystem.h
new file mode 100644
index 000000000..0d196f233
--- /dev/null
+++ b/Source/Engine/Platform/Web/WebFileSystem.h
@@ -0,0 +1,19 @@
+// Copyright (c) Wojciech Figat. All rights reserved.
+
+#pragma once
+
+#if PLATFORM_WEB
+
+#include "Engine/Platform/Unix/UnixFileSystem.h"
+
+///
+/// Web platform implementation of filesystem service.
+///
+class FLAXENGINE_API WebFileSystem : public UnixFileSystem
+{
+public:
+ // [UnixFileSystem]
+ static void GetSpecialFolderPath(const SpecialFolder type, String& result);
+};
+
+#endif
diff --git a/Source/Engine/Platform/Web/WebPlatform.cpp b/Source/Engine/Platform/Web/WebPlatform.cpp
new file mode 100644
index 000000000..8c7aed48f
--- /dev/null
+++ b/Source/Engine/Platform/Web/WebPlatform.cpp
@@ -0,0 +1,254 @@
+// Copyright (c) Wojciech Figat. All rights reserved.
+
+#if PLATFORM_WEB
+
+#include "WebPlatform.h"
+#include "WebFileSystem.h"
+#include "Engine/Core/Log.h"
+#include "Engine/Core/Types/String.h"
+#include "Engine/Core/Types/Version.h"
+#include "Engine/Core/Types/TimeSpan.h"
+#include "Engine/Core/Types/Guid.h"
+#include "Engine/Platform/CPUInfo.h"
+#include "Engine/Platform/MemoryStats.h"
+#if !BUILD_RELEASE
+#include "Engine/Core/Types/StringView.h"
+#include "Engine/Utilities/StringConverter.h"
+#endif
+#include
+#include
+#include
+#include
+#include
+
+namespace
+{
+ CPUInfo Cpu;
+};
+
+void WebFileSystem::GetSpecialFolderPath(const SpecialFolder type, String& result)
+{
+ result = TEXT("/");
+}
+
+String WebPlatform::GetSystemName()
+{
+ return TEXT("Browser");
+}
+
+Version WebPlatform::GetSystemVersion()
+{
+ return Version(1, 0);
+}
+
+CPUInfo WebPlatform::GetCPUInfo()
+{
+ return Cpu;
+}
+
+MemoryStats WebPlatform::GetMemoryStats()
+{
+ // Mock memory stats
+ MemoryStats result;
+ result.TotalPhysicalMemory = emscripten_get_heap_max();
+ result.UsedPhysicalMemory = emscripten_get_heap_size();
+ result.TotalVirtualMemory = result.TotalPhysicalMemory;
+ result.UsedVirtualMemory = result.UsedPhysicalMemory;
+ result.ProgramSizeMemory = 0;
+ return result;
+}
+
+ProcessMemoryStats WebPlatform::GetProcessMemoryStats()
+{
+ // Mock memory stats
+ ProcessMemoryStats result;
+ result.UsedPhysicalMemory = 1 * 1024 * 1024;
+ result.UsedVirtualMemory = result.UsedPhysicalMemory;
+ return result;
+}
+
+void WebPlatform::SetThreadPriority(ThreadPriority priority)
+{
+ // Not supported
+}
+
+void WebPlatform::SetThreadAffinityMask(uint64 affinityMask)
+{
+ // Not supported
+}
+
+void WebPlatform::Sleep(int32 milliseconds)
+{
+ //emscripten_sleep(milliseconds);
+ emscripten_thread_sleep(milliseconds);
+}
+
+void WebPlatform::Yield()
+{
+ Sleep(0);
+}
+
+double WebPlatform::GetTimeSeconds()
+{
+ double time = emscripten_get_now();
+ return time * 0.001;
+}
+
+uint64 WebPlatform::GetTimeCycles()
+{
+ return (uint64)(emscripten_get_now() * 1000.0);
+}
+
+void WebPlatform::GetSystemTime(int32& year, int32& month, int32& dayOfWeek, int32& day, int32& hour, int32& minute, int32& second, int32& millisecond)
+{
+ // Get local time
+ using namespace std::chrono;
+ system_clock::time_point now = system_clock::now();
+ time_t tt = system_clock::to_time_t(now);
+ tm time = *localtime(&tt);
+
+ // Extract time
+ year = time.tm_year + 1900;
+ month = time.tm_mon + 1;
+ dayOfWeek = time.tm_wday;
+ day = time.tm_mday;
+ hour = time.tm_hour;
+ minute = time.tm_min;
+ second = time.tm_sec;
+ millisecond = (int64)emscripten_get_now() % 1000; // Fake it based on other timer
+}
+
+void WebPlatform::GetUTCTime(int32& year, int32& month, int32& dayOfWeek, int32& day, int32& hour, int32& minute, int32& second, int32& millisecond)
+{
+ // Get UTC time
+ using namespace std::chrono;
+ system_clock::time_point now = system_clock::now();
+ time_t tt = system_clock::to_time_t(now);
+ tm time = *gmtime(&tt);
+
+ // Extract time
+ year = time.tm_year + 1900;
+ month = time.tm_mon + 1;
+ dayOfWeek = time.tm_wday;
+ day = time.tm_mday;
+ hour = time.tm_hour;
+ minute = time.tm_min;
+ second = time.tm_sec;
+ millisecond = (int64)emscripten_get_now() % 1000; // Fake it based on other timer
+}
+
+#if !BUILD_RELEASE
+
+void WebPlatform::Log(const StringView& msg)
+{
+ const StringAsANSI<512> msgAnsi(*msg, msg.Length());
+
+ // Fix % characters that should not be formatted
+ auto buffer = (char*)msgAnsi.Get();
+ for (int32 i = 0; buffer[i]; i++)
+ {
+ if (buffer[i] == '%')
+ buffer[i] = 'p';
+ }
+
+ emscripten_log(EM_LOG_CONSOLE, buffer);
+}
+
+bool WebPlatform::IsDebuggerPresent()
+{
+ return false;
+}
+
+#endif
+
+String WebPlatform::GetComputerName()
+{
+ return TEXT("Web");
+}
+
+bool WebPlatform::GetHasFocus()
+{
+ return true;
+}
+
+String WebPlatform::GetMainDirectory()
+{
+ return TEXT("/");
+}
+
+String WebPlatform::GetExecutableFilePath()
+{
+ return TEXT("/index.html");
+}
+
+Guid WebPlatform::GetUniqueDeviceId()
+{
+ return Guid(1, 2, 3, 4);
+}
+
+String WebPlatform::GetWorkingDirectory()
+{
+ return GetMainDirectory();
+}
+
+bool WebPlatform::SetWorkingDirectory(const String& path)
+{
+ return true;
+}
+
+bool WebPlatform::Init()
+{
+ if (PlatformBase::Init())
+ return true;
+
+ // Set info about the CPU
+ Platform::MemoryClear(&Cpu, sizeof(Cpu));
+ Cpu.ProcessorPackageCount = 1;
+ Cpu.ProcessorCoreCount = Math::Min(emscripten_num_logical_cores(), PLATFORM_THREADS_LIMIT);
+ Cpu.LogicalProcessorCount = Cpu.ProcessorCoreCount;
+ Cpu.ClockSpeed = GetClockFrequency();
+
+ return false;
+}
+
+void WebPlatform::LogInfo()
+{
+ PlatformBase::LogInfo();
+
+#ifdef __EMSCRIPTEN_major__
+ LOG(Info, "Emscripten {}.{}.{}", __EMSCRIPTEN_major__, __EMSCRIPTEN_minor__, __EMSCRIPTEN_tiny__);
+#elif defined(__EMSCRIPTEN_MAJOR__)
+ LOG(Info, "Emscripten {}.{}.{}", __EMSCRIPTEN_MAJOR__, __EMSCRIPTEN_MINOR__, __EMSCRIPTEN_TINY__);
+#else
+ LOG(Info, "Emscripten");
+#endif
+#ifdef __EMSCRIPTEN_PTHREADS__
+ LOG(Info, "Threading: pthreads");
+#else
+ LOG(Info, "Threading: disabled");
+#endif
+}
+
+void WebPlatform::Tick()
+{
+}
+
+void WebPlatform::Exit()
+{
+}
+
+void* WebPlatform::LoadLibrary(const Char* filename)
+{
+ return nullptr;
+}
+
+void WebPlatform::FreeLibrary(void* handle)
+{
+}
+
+void* WebPlatform::GetProcAddress(void* handle, const char* symbol)
+{
+ return nullptr;
+}
+
+#endif
diff --git a/Source/Engine/Platform/Web/WebPlatform.h b/Source/Engine/Platform/Web/WebPlatform.h
new file mode 100644
index 000000000..af4f3c146
--- /dev/null
+++ b/Source/Engine/Platform/Web/WebPlatform.h
@@ -0,0 +1,116 @@
+// Copyright (c) Wojciech Figat. All rights reserved.
+
+#pragma once
+
+#if PLATFORM_WEB
+
+#include "../Unix/UnixPlatform.h"
+#ifdef __EMSCRIPTEN_PTHREADS__
+#include
+#endif
+
+///
+/// The Web platform implementation and application management utilities.
+///
+class FLAXENGINE_API WebPlatform : public UnixPlatform
+{
+public:
+ // [UnixPlatform]
+ FORCE_INLINE static void MemoryBarrier()
+ {
+#ifdef __EMSCRIPTEN_PTHREADS__
+ // Fake a fence with an arbitrary atomic operation (from emscripten_atomic_fence to avoid including it for less header bloat)
+ uint8 temp = 0;
+ __c11_atomic_fetch_or((_Atomic uint8*)&temp, 0, __ATOMIC_SEQ_CST);
+#endif
+ }
+ FORCE_INLINE static void MemoryPrefetch(void const* ptr)
+ {
+ __builtin_prefetch(static_cast(ptr));
+ }
+ FORCE_INLINE static int64 InterlockedExchange(int64 volatile* dst, int64 exchange)
+ {
+ return __sync_lock_test_and_set(dst, exchange);
+ }
+ FORCE_INLINE static int32 InterlockedCompareExchange(int32 volatile* dst, int32 exchange, int32 comperand)
+ {
+ return __sync_val_compare_and_swap(dst, comperand, exchange);
+ }
+ FORCE_INLINE static int64 InterlockedCompareExchange(int64 volatile* dst, int64 exchange, int64 comperand)
+ {
+ return __sync_val_compare_and_swap(dst, comperand, exchange);
+ }
+ FORCE_INLINE static int64 InterlockedIncrement(int64 volatile* dst)
+ {
+ return __sync_add_and_fetch(dst, 1);
+ }
+ FORCE_INLINE static int64 InterlockedDecrement(int64 volatile* dst)
+ {
+ return __sync_sub_and_fetch(dst, 1);
+ }
+ FORCE_INLINE static int64 InterlockedAdd(int64 volatile* dst, int64 value)
+ {
+ return __sync_fetch_and_add(dst, value);
+ }
+ FORCE_INLINE static int32 AtomicRead(int32 const volatile* dst)
+ {
+ int32 result;
+ __atomic_load(dst, &result, __ATOMIC_SEQ_CST);
+ return result;
+ }
+ FORCE_INLINE static int64 AtomicRead(int64 const volatile* dst)
+ {
+ int64 result;
+ __atomic_load(dst, &result, __ATOMIC_SEQ_CST);
+ return result;
+ }
+ FORCE_INLINE static void AtomicStore(int32 volatile* dst, int32 value)
+ {
+ __atomic_store(dst, &value, __ATOMIC_SEQ_CST);
+ }
+ FORCE_INLINE static void AtomicStore(int64 volatile* dst, int64 value)
+ {
+ __atomic_store(dst, &value, __ATOMIC_SEQ_CST);
+ }
+ FORCE_INLINE static uint64 GetCurrentThreadID()
+ {
+#ifdef __EMSCRIPTEN_PTHREADS__
+ return (uint64)pthread_self();
+#else
+ return 0;
+#endif
+ }
+ static String GetSystemName();
+ static Version GetSystemVersion();
+ static CPUInfo GetCPUInfo();
+ static MemoryStats GetMemoryStats();
+ static ProcessMemoryStats GetProcessMemoryStats();
+ static void SetThreadPriority(ThreadPriority priority);
+ static void SetThreadAffinityMask(uint64 affinityMask);
+ static void Sleep(int32 milliseconds);
+ static void Yield();
+ static double GetTimeSeconds();
+ static uint64 GetTimeCycles();
+ static void GetSystemTime(int32& year, int32& month, int32& dayOfWeek, int32& day, int32& hour, int32& minute, int32& second, int32& millisecond);
+ static void GetUTCTime(int32& year, int32& month, int32& dayOfWeek, int32& day, int32& hour, int32& minute, int32& second, int32& millisecond);
+#if !BUILD_RELEASE
+ static void Log(const StringView& msg);
+ static bool IsDebuggerPresent();
+#endif
+ static String GetComputerName();
+ static bool GetHasFocus();
+ static String GetMainDirectory();
+ static String GetExecutableFilePath();
+ static Guid GetUniqueDeviceId();
+ static String GetWorkingDirectory();
+ static bool SetWorkingDirectory(const String& path);
+ static bool Init();
+ static void LogInfo();
+ static void Tick();
+ static void Exit();
+ static void* LoadLibrary(const Char* filename);
+ static void FreeLibrary(void* handle);
+ static void* GetProcAddress(void* handle, const char* symbol);
+};
+
+#endif
diff --git a/Source/Engine/Platform/Web/WebPlatformSettings.h b/Source/Engine/Platform/Web/WebPlatformSettings.h
new file mode 100644
index 000000000..8408e3bf7
--- /dev/null
+++ b/Source/Engine/Platform/Web/WebPlatformSettings.h
@@ -0,0 +1,27 @@
+// Copyright (c) Wojciech Figat. All rights reserved.
+
+#pragma once
+
+#if PLATFORM_WEB || USE_EDITOR
+
+#include "Engine/Core/Config/PlatformSettingsBase.h"
+
+///
+/// Web platform settings.
+///
+API_CLASS(Sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API WebPlatformSettings : public SettingsBase
+{
+ DECLARE_SCRIPTING_TYPE_MINIMAL(WebPlatformSettings);
+ API_AUTO_SERIALIZATION();
+
+ ///
+ /// Gets the instance of the settings asset (default value if missing). Object returned by this method is always loaded with valid data to use.
+ ///
+ static WebPlatformSettings* Get();
+};
+
+#if PLATFORM_WEB
+typedef WebPlatformSettings PlatformSettings;
+#endif
+
+#endif
diff --git a/Source/Engine/Platform/Web/WebThread.h b/Source/Engine/Platform/Web/WebThread.h
new file mode 100644
index 000000000..a5086787b
--- /dev/null
+++ b/Source/Engine/Platform/Web/WebThread.h
@@ -0,0 +1,63 @@
+// Copyright (c) Wojciech Figat. All rights reserved.
+
+#pragma once
+
+#if PLATFORM_WEB
+
+#include "../Base/ThreadBase.h"
+
+///
+/// Thread object for Web platform.
+///
+class FLAXENGINE_API WebThread : public ThreadBase
+{
+public:
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The runnable.
+ /// The thread name.
+ /// The thread priority.
+ WebThread(IRunnable* runnable, const String& name, ThreadPriority priority)
+ : ThreadBase(runnable, name, priority)
+ {
+ }
+
+public:
+ ///
+ /// Factory method to create a thread with the specified stack size and thread priority
+ ///
+ /// The runnable object to execute
+ /// Name of the thread
+ /// Tells the thread whether it needs to adjust its priority or not. Defaults to normal priority
+ /// The size of the stack to create. 0 means use the current thread's stack size
+ /// Pointer to the new thread or null if cannot create it
+ static WebThread* Create(IRunnable* runnable, const String& name, ThreadPriority priority = ThreadPriority::Normal, uint32 stackSize = 0)
+ {
+ return New(runnable, name, priority);
+ }
+
+public:
+ // [ThreadBase]
+ void Join() override
+ {
+ // TOOD: impl this
+ }
+
+protected:
+ // [ThreadBase]
+ void ClearHandleInternal() override
+ {
+ // TOOD: impl this
+ }
+ void SetPriorityInternal(ThreadPriority priority) override
+ {
+ // TOOD: impl this
+ }
+ void KillInternal(bool waitForJoin) override
+ {
+ // TOOD: impl this
+ }
+};
+
+#endif
diff --git a/Source/Engine/Profiler/Profiler.Build.cs b/Source/Engine/Profiler/Profiler.Build.cs
index 013336181..b1bdd85b8 100644
--- a/Source/Engine/Profiler/Profiler.Build.cs
+++ b/Source/Engine/Profiler/Profiler.Build.cs
@@ -15,6 +15,8 @@ public class Profiler : EngineModule
/// True if use profiler, otherwise false.
public static bool Use(BuildOptions options)
{
+ if (options.Platform.Target == TargetPlatform.Web)
+ return false;
return options.Configuration != TargetConfiguration.Release || options.Target.IsEditor;
}
diff --git a/Source/Engine/Renderer/ScreenSpaceReflectionsPass.cpp b/Source/Engine/Renderer/ScreenSpaceReflectionsPass.cpp
index 38d4e295c..be8970e7b 100644
--- a/Source/Engine/Renderer/ScreenSpaceReflectionsPass.cpp
+++ b/Source/Engine/Renderer/ScreenSpaceReflectionsPass.cpp
@@ -304,7 +304,7 @@ GPUTexture* ScreenSpaceReflectionsPass::Render(RenderContext& renderContext, GPU
// Also for high surface roughness values it adds more blur to the reflection tail which looks more realistic.
// Downscale with gaussian blur
- auto filterMode = PLATFORM_ANDROID || PLATFORM_IOS || PLATFORM_SWITCH ? MultiScaler::FilterMode::GaussianBlur5 : MultiScaler::FilterMode::GaussianBlur9;
+ auto filterMode = PLATFORM_WEB || PLATFORM_ANDROID || PLATFORM_IOS || PLATFORM_SWITCH ? MultiScaler::FilterMode::GaussianBlur5 : MultiScaler::FilterMode::GaussianBlur9;
for (int32 mipLevel = 1; mipLevel < colorBufferMips; mipLevel++)
{
const int32 mipWidth = Math::Max(colorBufferWidth >> mipLevel, 1);
diff --git a/Source/Engine/Renderer/ShadowsPass.cpp b/Source/Engine/Renderer/ShadowsPass.cpp
index 8825d2e8d..91f2d3832 100644
--- a/Source/Engine/Renderer/ShadowsPass.cpp
+++ b/Source/Engine/Renderer/ShadowsPass.cpp
@@ -784,7 +784,7 @@ void ShadowsPass::SetupLight(ShadowsCustomBuffer& shadows, RenderContext& render
// Disable cascades blending when baking lightmaps
if (IsRunningRadiancePass)
atlasLight.BlendCSM = false;
-#elif PLATFORM_SWITCH || PLATFORM_IOS || PLATFORM_ANDROID
+#elif PLATFORM_WEB || PLATFORM_SWITCH || PLATFORM_IOS || PLATFORM_ANDROID
// Disable cascades blending on low-end platforms
atlasLight.BlendCSM = false;
#endif
diff --git a/Source/Engine/Threading/JobSystem.cpp b/Source/Engine/Threading/JobSystem.cpp
index 8d62aa8e3..3cc9acc62 100644
--- a/Source/Engine/Threading/JobSystem.cpp
+++ b/Source/Engine/Threading/JobSystem.cpp
@@ -6,16 +6,13 @@
#include "Engine/Platform/Thread.h"
#include "Engine/Platform/ConditionVariable.h"
#include "Engine/Core/Types/Span.h"
-#include "Engine/Core/Types/Pair.h"
-#include "Engine/Core/Memory/SimpleHeapAllocation.h"
-#include "Engine/Core/Collections/RingBuffer.h"
#include "Engine/Engine/EngineService.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Profiler/ProfilerMemory.h"
#if USE_CSHARP
#include "Engine/Scripting/ManagedCLR/MCore.h"
-#include "Engine/Scripting/Internal/InternalCalls.h"
#endif
+#include "Engine/Scripting/Internal/InternalCalls.h"
#define JOB_SYSTEM_ENABLED 1
diff --git a/Source/Engine/Tools/TextureTool/TextureTool.Build.cs b/Source/Engine/Tools/TextureTool/TextureTool.Build.cs
index ae15af8c3..bfb8fc0d7 100644
--- a/Source/Engine/Tools/TextureTool/TextureTool.Build.cs
+++ b/Source/Engine/Tools/TextureTool/TextureTool.Build.cs
@@ -38,6 +38,7 @@ public class TextureTool : EngineModule
case TargetPlatform.Switch:
case TargetPlatform.Mac:
case TargetPlatform.iOS:
+ case TargetPlatform.Web:
useStb = true;
break;
default: throw new InvalidPlatformException(options.Platform.Target);
diff --git a/Source/Engine/Video/Video.Build.cs b/Source/Engine/Video/Video.Build.cs
index 3975480ee..ed9ed7c4e 100644
--- a/Source/Engine/Video/Video.Build.cs
+++ b/Source/Engine/Video/Video.Build.cs
@@ -63,6 +63,10 @@ public class Video : EngineModule
options.SourcePaths.Add(Path.Combine(FolderPath, "Android"));
options.CompileEnv.PreprocessorDefinitions.Add("VIDEO_API_ANDROID");
break;
+ case TargetPlatform.Web:
+ // No implemented
+ break;
+ default: throw new InvalidPlatformException(options.Platform.Target);
}
}
diff --git a/Source/Engine/Visject/ShaderGraph.cpp b/Source/Engine/Visject/ShaderGraph.cpp
index faf344263..b297ad91e 100644
--- a/Source/Engine/Visject/ShaderGraph.cpp
+++ b/Source/Engine/Visject/ShaderGraph.cpp
@@ -796,6 +796,7 @@ void ShaderGenerator::ProcessGroupTools(Box* box, Node* node, Value& value)
PLATFORM_CASE(10, "PLATFORM_PS5");
PLATFORM_CASE(11, "PLATFORM_MAC");
PLATFORM_CASE(12, "PLATFORM_IOS");
+ PLATFORM_CASE(13, "PLATFORM_WEB");
#undef PLATFORM_CASE
break;
}
diff --git a/Source/Engine/Visject/VisjectGraph.cpp b/Source/Engine/Visject/VisjectGraph.cpp
index 7d3505d41..ec4f51364 100644
--- a/Source/Engine/Visject/VisjectGraph.cpp
+++ b/Source/Engine/Visject/VisjectGraph.cpp
@@ -1015,6 +1015,9 @@ void VisjectExecutor::ProcessGroupTools(Box* box, Node* node, Value& value)
case PlatformType::iOS:
boxId = 12;
break;
+ case PlatformType::Web:
+ boxId = 13;
+ break;
default: ;
}
value = tryGetValue(node->GetBox(node->GetBox(boxId)->HasConnection() ? boxId : 1), Value::Zero);
diff --git a/Source/Shaders/Reflections.shader b/Source/Shaders/Reflections.shader
index 891411af1..11478c7d1 100644
--- a/Source/Shaders/Reflections.shader
+++ b/Source/Shaders/Reflections.shader
@@ -8,7 +8,7 @@
#include "./Flax/ReflectionsCommon.hlsl"
// Enable/disable blurring SSR during sampling results and mixing with reflections buffer
-#define SSR_MIX_BLUR (!defined(PLATFORM_ANDROID) && !defined(PLATFORM_IOS) && !defined(PLATFORM_SWITCH))
+#define SSR_MIX_BLUR (!defined(PLATFORM_WEB) && !defined(PLATFORM_ANDROID) && !defined(PLATFORM_IOS) && !defined(PLATFORM_SWITCH))
META_CB_BEGIN(0, Data)
EnvProbeData PData;
diff --git a/Source/ThirdParty/NvCloth/NvCloth.Build.cs b/Source/ThirdParty/NvCloth/NvCloth.Build.cs
index af629c084..71e9a7e03 100644
--- a/Source/ThirdParty/NvCloth/NvCloth.Build.cs
+++ b/Source/ThirdParty/NvCloth/NvCloth.Build.cs
@@ -38,6 +38,7 @@ public class NvCloth : DepsModule
case TargetPlatform.Mac:
case TargetPlatform.iOS:
case TargetPlatform.Linux:
+ case TargetPlatform.Web:
libName = "NvCloth";
break;
case TargetPlatform.Switch:
diff --git a/Source/ThirdParty/SDL/SDL.Build.cs b/Source/ThirdParty/SDL/SDL.Build.cs
index 91ce939f7..8b55f56c3 100644
--- a/Source/ThirdParty/SDL/SDL.Build.cs
+++ b/Source/ThirdParty/SDL/SDL.Build.cs
@@ -48,6 +48,9 @@ public class SDL : DepsModule
case TargetPlatform.Mac:
options.OutputFiles.Add(Path.Combine(depsRoot, "libSDL3.a"));
break;
+ case TargetPlatform.Web:
+ options.OutputFiles.Add("--use-port=sdl3");
+ return;
default: throw new InvalidPlatformException(options.Platform.Target);
}
diff --git a/Source/ThirdParty/freetype/freetype.Build.cs b/Source/ThirdParty/freetype/freetype.Build.cs
index f6c3380ec..85c1d8e89 100644
--- a/Source/ThirdParty/freetype/freetype.Build.cs
+++ b/Source/ThirdParty/freetype/freetype.Build.cs
@@ -45,6 +45,9 @@ public class freetype : DepsModule
case TargetPlatform.iOS:
options.OutputFiles.Add(Path.Combine(depsRoot, "libfreetype.a"));
break;
+ case TargetPlatform.Web:
+ options.OutputFiles.Add("--use-port=freetype");
+ break;
default: throw new InvalidPlatformException(options.Platform.Target);
}
diff --git a/Source/ThirdParty/ogg/ogg.Build.cs b/Source/ThirdParty/ogg/ogg.Build.cs
index 1a3a67fc0..f8b14bffa 100644
--- a/Source/ThirdParty/ogg/ogg.Build.cs
+++ b/Source/ThirdParty/ogg/ogg.Build.cs
@@ -45,6 +45,9 @@ public class ogg : DepsModule
case TargetPlatform.iOS:
options.OutputFiles.Add(Path.Combine(depsRoot, "libogg.a"));
break;
+ case TargetPlatform.Web:
+ options.OutputFiles.Add("--use-port=ogg");
+ break;
default: throw new InvalidPlatformException(options.Platform.Target);
}
}
diff --git a/Source/ThirdParty/vorbis/vorbis.Build.cs b/Source/ThirdParty/vorbis/vorbis.Build.cs
index afdc04e60..edd55d886 100644
--- a/Source/ThirdParty/vorbis/vorbis.Build.cs
+++ b/Source/ThirdParty/vorbis/vorbis.Build.cs
@@ -51,6 +51,9 @@ public class vorbis : DepsModule
case TargetPlatform.PS5:
options.OutputFiles.Add(Path.Combine(depsRoot, "libvorbis.a"));
break;
+ case TargetPlatform.Web:
+ options.OutputFiles.Add("--use-port=vorbis");
+ break;
default: throw new InvalidPlatformException(options.Platform.Target);
}
}
diff --git a/Source/Tools/Flax.Build/Build/DepsModule.cs b/Source/Tools/Flax.Build/Build/DepsModule.cs
index bdf9c47ac..8039eb36d 100644
--- a/Source/Tools/Flax.Build/Build/DepsModule.cs
+++ b/Source/Tools/Flax.Build/Build/DepsModule.cs
@@ -35,6 +35,7 @@ namespace Flax.Build
case TargetPlatform.Switch:
case TargetPlatform.Mac:
case TargetPlatform.iOS:
+ case TargetPlatform.Web:
options.OutputFiles.Add(Path.Combine(path, string.Format("lib{0}.a", name)));
break;
default: throw new InvalidPlatformException(options.Platform.Target);
diff --git a/Source/Tools/Flax.Build/Build/Module.cs b/Source/Tools/Flax.Build/Build/Module.cs
index 89a6ffa7f..bb11b2857 100644
--- a/Source/Tools/Flax.Build/Build/Module.cs
+++ b/Source/Tools/Flax.Build/Build/Module.cs
@@ -108,6 +108,7 @@ namespace Flax.Build
case TargetPlatform.Switch: return "PLATFORM_SWITCH";
case TargetPlatform.Mac: return "PLATFORM_MAC";
case TargetPlatform.iOS: return "PLATFORM_IOS";
+ case TargetPlatform.Web: return "PLATFORM_WEB";
default: throw new InvalidPlatformException(platform);
}
}
diff --git a/Source/Tools/Flax.Build/Build/Platform.cs b/Source/Tools/Flax.Build/Build/Platform.cs
index 59572d669..f64dc6ab9 100644
--- a/Source/Tools/Flax.Build/Build/Platform.cs
+++ b/Source/Tools/Flax.Build/Build/Platform.cs
@@ -170,7 +170,7 @@ namespace Flax.Build
///
/// Gets the default project format used by the given platform.
///
- public abstract Projects.ProjectFormat DefaultProjectFormat { get; }
+ public virtual Projects.ProjectFormat DefaultProjectFormat => Projects.ProjectFormat.VisualStudioCode;
///
/// Creates the toolchain for a given architecture.
@@ -343,6 +343,7 @@ namespace Flax.Build
case TargetPlatform.Switch: return targetArchitecture == TargetArchitecture.ARM64;
case TargetPlatform.Mac: return targetArchitecture == TargetArchitecture.ARM64 || targetArchitecture == TargetArchitecture.x64;
case TargetPlatform.iOS: return targetArchitecture == TargetArchitecture.ARM64;
+ case TargetPlatform.Web: return targetArchitecture == TargetArchitecture.x86;
default: return false;
}
}
diff --git a/Source/Tools/Flax.Build/Build/TargetPlatform.cs b/Source/Tools/Flax.Build/Build/TargetPlatform.cs
index 47889c1bf..e51c92674 100644
--- a/Source/Tools/Flax.Build/Build/TargetPlatform.cs
+++ b/Source/Tools/Flax.Build/Build/TargetPlatform.cs
@@ -61,6 +61,11 @@ namespace Flax.Build
/// Running on iPhone.
///
iOS = 11,
+
+ ///
+ /// Running on Web.
+ ///
+ Web = 12,
}
///
diff --git a/Source/Tools/Flax.Build/Configuration.cs b/Source/Tools/Flax.Build/Configuration.cs
index 5cd643b8f..9f2796767 100644
--- a/Source/Tools/Flax.Build/Configuration.cs
+++ b/Source/Tools/Flax.Build/Configuration.cs
@@ -296,6 +296,8 @@ namespace Flax.Build
public static bool WithCSharp(NativeCpp.BuildOptions options)
{
+ if (options.Platform.Target == TargetPlatform.Web)
+ return false; // TODO: implement .NET for WebAssembly
return UseCSharp || options.Target.IsEditor;
}
@@ -318,6 +320,8 @@ namespace Flax.Build
case TargetPlatform.Linux:
case TargetPlatform.Mac:
return UseSDL;
+ case TargetPlatform.Web:
+ return true;
default: return false;
}
}
diff --git a/Source/Tools/Flax.Build/Globals.cs b/Source/Tools/Flax.Build/Globals.cs
index ad5c1f74e..033d2f40e 100644
--- a/Source/Tools/Flax.Build/Globals.cs
+++ b/Source/Tools/Flax.Build/Globals.cs
@@ -37,6 +37,7 @@ namespace Flax.Build
TargetPlatform.Switch,
TargetPlatform.Mac,
TargetPlatform.iOS,
+ TargetPlatform.Web,
};
///
diff --git a/Source/Tools/Flax.Build/Platforms/Linux/LinuxPlatform.cs b/Source/Tools/Flax.Build/Platforms/Linux/LinuxPlatform.cs
index 5c8561106..2a9773f47 100644
--- a/Source/Tools/Flax.Build/Platforms/Linux/LinuxPlatform.cs
+++ b/Source/Tools/Flax.Build/Platforms/Linux/LinuxPlatform.cs
@@ -104,6 +104,7 @@ namespace Flax.Build.Platforms
{
case TargetPlatform.Linux: return HasRequiredSDKsInstalled;
case TargetPlatform.Android: return AndroidSdk.Instance.IsValid && AndroidNdk.Instance.IsValid;
+ case TargetPlatform.Web: return EmscriptenSdk.Instance.IsValid;
default: return false;
}
}
diff --git a/Source/Tools/Flax.Build/Platforms/Mac/MacPlatform.cs b/Source/Tools/Flax.Build/Platforms/Mac/MacPlatform.cs
index 8bd6b30ac..2da5f2494 100644
--- a/Source/Tools/Flax.Build/Platforms/Mac/MacPlatform.cs
+++ b/Source/Tools/Flax.Build/Platforms/Mac/MacPlatform.cs
@@ -42,6 +42,7 @@ namespace Flax.Build.Platforms
{
case TargetPlatform.iOS:
case TargetPlatform.Mac: return HasRequiredSDKsInstalled;
+ case TargetPlatform.Web: return EmscriptenSdk.Instance.IsValid;
default: return false;
}
}
diff --git a/Source/Tools/Flax.Build/Platforms/Unix/UnixToolchain.cs b/Source/Tools/Flax.Build/Platforms/Unix/UnixToolchain.cs
index 990219a89..2c0929c72 100644
--- a/Source/Tools/Flax.Build/Platforms/Unix/UnixToolchain.cs
+++ b/Source/Tools/Flax.Build/Platforms/Unix/UnixToolchain.cs
@@ -414,7 +414,7 @@ namespace Flax.Build.Platforms
// Language for the file
args.Add("-x");
- if (Path.GetExtension(sourceFile).Equals(".c", StringComparison.OrdinalIgnoreCase))
+ if (sourceFile.EndsWith(".c", StringComparison.OrdinalIgnoreCase))
args.Add("c");
else
{
diff --git a/Source/Tools/Flax.Build/Platforms/Web/EmscriptenSdk.cs b/Source/Tools/Flax.Build/Platforms/Web/EmscriptenSdk.cs
new file mode 100644
index 000000000..946fc0c87
--- /dev/null
+++ b/Source/Tools/Flax.Build/Platforms/Web/EmscriptenSdk.cs
@@ -0,0 +1,87 @@
+// Copyright (c) Wojciech Figat. All rights reserved.
+
+using System;
+using System.IO;
+using System.Linq;
+
+namespace Flax.Build.Platforms
+{
+ ///
+ /// The Emscripten SDK (https://emscripten.org/).
+ ///
+ ///
+ public sealed class EmscriptenSdk : Sdk
+ {
+ ///
+ /// The singleton instance.
+ ///
+ public static readonly EmscriptenSdk Instance = new EmscriptenSdk();
+
+ ///
+ public override TargetPlatform[] Platforms => new[]
+ {
+ TargetPlatform.Windows,
+ TargetPlatform.Linux,
+ TargetPlatform.Mac,
+ };
+
+ ///
+ /// Full path to the current SDK folder with binaries, tools and sources (eg. '%EMSDK%\upstream').
+ ///
+ public string EmscriptenPath;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public EmscriptenSdk()
+ {
+ if (!Platforms.Contains(Platform.BuildTargetPlatform))
+ return;
+
+ // Find Emscripten SDK folder path
+ var sdkPath = Environment.GetEnvironmentVariable("EMSDK");
+ if (string.IsNullOrEmpty(sdkPath))
+ {
+ Log.Warning("Missing Emscripten SDK. Cannot build for Web platform.");
+ }
+ else if (!Directory.Exists(sdkPath))
+ {
+ Log.Warning(string.Format("Specified Emscripten SDK folder in EMSDK env variable doesn't exist ({0})", sdkPath));
+ }
+ else
+ {
+ RootPath = sdkPath;
+ EmscriptenPath = Path.Combine(sdkPath, "upstream");
+ var versionPath = Path.Combine(EmscriptenPath, "emscripten", "emscripten-version.txt");
+ if (File.Exists(versionPath))
+ {
+ try
+ {
+ // Read version
+ var versionStr = File.ReadAllLines(versionPath)[0];
+ versionStr = versionStr.Trim();
+ if (versionStr.StartsWith('\"') && versionStr.EndsWith('\"'))
+ versionStr = versionStr.Substring(1, versionStr.Length - 2);
+ Version = new Version(versionStr);
+
+ var minVersion = new Version(4, 0);
+ if (Version < minVersion)
+ {
+ Log.Error(string.Format("Unsupported Emscripten SDK version {0}. Minimum supported is {1}.", Version, minVersion));
+ return;
+ }
+ Log.Info(string.Format("Found Emscripten SDK {0} at {1}", Version, RootPath));
+ IsValid = true;
+ }
+ catch (Exception ex)
+ {
+ Log.Error($"Failed to read Emscripten SDK version from file '{versionPath}'");
+ Log.Exception(ex);
+ }
+ }
+ else
+ Log.Warning($"Missing file {versionPath}");
+ }
+ }
+ }
+}
diff --git a/Source/Tools/Flax.Build/Platforms/Web/WebPlatform.cs b/Source/Tools/Flax.Build/Platforms/Web/WebPlatform.cs
new file mode 100644
index 000000000..dd1540c5e
--- /dev/null
+++ b/Source/Tools/Flax.Build/Platforms/Web/WebPlatform.cs
@@ -0,0 +1,68 @@
+// Copyright (c) Wojciech Figat. All rights reserved.
+
+using Flax.Build.Projects;
+
+namespace Flax.Build.Platforms
+{
+ ///
+ /// The build platform for Web with Emscripten.
+ ///
+ ///
+ public sealed class WebPlatform : Platform, IProjectCustomizer
+ {
+ ///
+ public override TargetPlatform Target => TargetPlatform.Web;
+
+ ///
+ public override bool HasRequiredSDKsInstalled { get; }
+
+ ///
+ public override bool HasSharedLibrarySupport => false;
+
+ ///
+ public override bool HasModularBuildSupport => false;
+
+ ///
+ public override bool HasDynamicCodeExecutionSupport => false;
+
+ ///
+ public override string ExecutableFileExtension => ".html";
+
+ ///
+ public override string SharedLibraryFileExtension => ".so";
+
+ ///
+ public override string StaticLibraryFileExtension => ".a";
+
+ ///
+ public override string ProgramDatabaseFileExtension => string.Empty;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public WebPlatform()
+ {
+ HasRequiredSDKsInstalled = EmscriptenSdk.Instance.IsValid;
+ }
+
+ ///
+ protected override Toolchain CreateToolchain(TargetArchitecture architecture)
+ {
+ if (architecture != TargetArchitecture.x86)
+ throw new InvalidArchitectureException(architecture, "Web is compiled into WebAssembly and doesn't have specific architecture but is mocked as x86.");
+ return new WebToolchain(this, architecture);
+ }
+
+ ///
+ void IProjectCustomizer.GetSolutionArchitectureName(TargetArchitecture architecture, ref string name)
+ {
+ name = "Web";
+ }
+
+ ///
+ void IProjectCustomizer.GetProjectArchitectureName(Project project, Platform platform, TargetArchitecture architecture, ref string name)
+ {
+ name = "Win32";
+ }
+ }
+}
diff --git a/Source/Tools/Flax.Build/Platforms/Web/WebToolchain.cs b/Source/Tools/Flax.Build/Platforms/Web/WebToolchain.cs
new file mode 100644
index 000000000..a0e22c7e5
--- /dev/null
+++ b/Source/Tools/Flax.Build/Platforms/Web/WebToolchain.cs
@@ -0,0 +1,302 @@
+// Copyright (c) Wojciech Figat. All rights reserved.
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using Flax.Build.Graph;
+using Flax.Build.NativeCpp;
+
+namespace Flax.Build.Platforms
+{
+ ///
+ /// The build toolchain for Web with Emscripten.
+ ///
+ ///
+ public sealed class WebToolchain : Toolchain
+ {
+ private string _sysrootPath;
+ private string _compilerPath;
+ private Version _compilerVersion;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The platform.
+ /// The target architecture.
+ public WebToolchain(WebPlatform platform, TargetArchitecture architecture)
+ : base(platform, architecture)
+ {
+ var sdkPath = EmscriptenSdk.Instance.EmscriptenPath;
+
+ // Setup tools
+ _compilerPath = Path.Combine(sdkPath, "emscripten", "emcc");
+ var clangPath = Path.Combine(sdkPath, "bin", "clang++");
+ if (Platform.BuildTargetPlatform == TargetPlatform.Windows)
+ {
+ _compilerPath += ".bat";
+ clangPath += ".exe";
+ }
+
+ // Determinate compiler version
+ _compilerVersion = UnixToolchain.GetClangVersion(platform.Target, clangPath);
+
+ // Setup system paths
+ SystemIncludePaths.Add(Path.Combine(sdkPath, "lib", "clang", _compilerVersion.Major.ToString(), "include"));
+ SystemIncludePaths.Add(Path.Combine(sdkPath, "emscripten", "system", "include"));
+ SystemIncludePaths.Add(Path.Combine(sdkPath, "emscripten", "system", "lib"));
+ _sysrootPath = Path.Combine(sdkPath, "emscripten/cache/sysroot/include");
+ }
+
+ public static string GetLibName(string path)
+ {
+ var libName = Path.GetFileNameWithoutExtension(path);
+ if (libName.StartsWith("lib"))
+ libName = libName.Substring(3);
+ return libName;
+ }
+
+ ///
+ public override string DllExport => "__attribute__((__visibility__(\\\"default\\\")))";
+
+ ///
+ public override string DllImport => "";
+
+ ///
+ public override TargetCompiler Compiler => TargetCompiler.Clang;
+
+ ///
+ public override string NativeCompilerPath => _compilerPath;
+
+ ///
+ public override void LogInfo()
+ {
+ Log.Info("Clang version: " + _compilerVersion);
+ }
+
+ ///
+ public override void SetupEnvironment(BuildOptions options)
+ {
+ base.SetupEnvironment(options);
+
+ options.CompileEnv.PreprocessorDefinitions.Add("PLATFORM_WEB");
+ options.CompileEnv.PreprocessorDefinitions.Add("PLATFORM_UNIX");
+ options.CompileEnv.PreprocessorDefinitions.Add("__EMSCRIPTEN__");
+ options.CompileEnv.EnableExceptions = false;
+ options.CompileEnv.CpuArchitecture = CpuArchitecture.None; // TODO: try SIMD support in Emscripten
+ }
+
+ private void AddSharedArgs(List args, bool debugInformation, bool optimization, FavorSizeOrSpeed favorSizeOrSpeed)
+ {
+ if (debugInformation)
+ args.Add("-g2");
+ else
+ args.Add("-g0");
+
+ if (favorSizeOrSpeed == FavorSizeOrSpeed.SmallCode)
+ args.Add("-Os");
+ if (favorSizeOrSpeed == FavorSizeOrSpeed.FastCode)
+ args.Add("-O3");
+ else if (optimization)
+ args.Add("-O2");
+ else
+ args.Add("-O0");
+ }
+
+ ///
+ public override CompileOutput CompileCppFiles(TaskGraph graph, BuildOptions options, List sourceFiles, string outputPath)
+ {
+ var output = new CompileOutput();
+
+ // Setup arguments shared by all source files
+ var commonArgs = new List();
+ commonArgs.AddRange(options.CompileEnv.CustomArgs);
+ {
+ commonArgs.Add("-c");
+
+ AddSharedArgs(commonArgs, options.CompileEnv.DebugInformation, options.CompileEnv.Optimization, options.CompileEnv.FavorSizeOrSpeed);
+ }
+
+ // Add preprocessor definitions
+ foreach (var definition in options.CompileEnv.PreprocessorDefinitions)
+ {
+ commonArgs.Add(string.Format("-D \"{0}\"", definition));
+ }
+
+ // Add include paths
+ foreach (var includePath in options.CompileEnv.IncludePaths)
+ {
+ if (SystemIncludePaths.Contains(includePath)) // TODO: fix SystemIncludePaths so this chack can be removed
+ continue; // Skip system includes as those break compilation (need to fix sys root linking for emscripten)
+ commonArgs.Add(string.Format("-I\"{0}\"", includePath.Replace('\\', '/')));
+ }
+
+ // Hack for sysroot includes
+ commonArgs.Add(string.Format("-I\"{0}\"", _sysrootPath.Replace('\\', '/')));
+
+ // Compile all C/C++ files
+ var args = new List();
+ foreach (var sourceFile in sourceFiles)
+ {
+ var sourceFilename = Path.GetFileNameWithoutExtension(sourceFile);
+ var task = graph.Add();
+
+ // Use shared arguments
+ args.Clear();
+ args.AddRange(commonArgs);
+
+ // Language for the file
+ args.Add("-x");
+ if (sourceFile.EndsWith(".c", StringComparison.OrdinalIgnoreCase))
+ args.Add("c");
+ else
+ {
+ args.Add("c++");
+
+ // C++ version
+ switch (options.CompileEnv.CppVersion)
+ {
+ case CppVersion.Cpp14:
+ args.Add("-std=c++14");
+ break;
+ case CppVersion.Cpp17:
+ case CppVersion.Latest:
+ args.Add("-std=c++17");
+ break;
+ case CppVersion.Cpp20:
+ args.Add("-std=c++20");
+ break;
+ }
+ }
+
+ // Object File Name
+ var objFile = Path.Combine(outputPath, sourceFilename + ".o");
+ args.Add(string.Format("-o \"{0}\"", objFile.Replace('\\', '/')));
+ output.ObjectFiles.Add(objFile);
+ task.ProducedFiles.Add(objFile);
+
+ // Source File Name
+ args.Add("\"" + sourceFile.Replace('\\', '/') + "\"");
+
+ // Request included files to exist
+ var includes = IncludesCache.FindAllIncludedFiles(sourceFile);
+ task.PrerequisiteFiles.AddRange(includes);
+
+ // Compile
+ task.WorkingDirectory = options.WorkingDirectory;
+ task.CommandPath = _compilerPath;
+ task.CommandArguments = string.Join(" ", args);
+ task.PrerequisiteFiles.Add(sourceFile);
+ task.InfoMessage = Path.GetFileName(sourceFile);
+ task.Cost = task.PrerequisiteFiles.Count; // TODO: include source file size estimation to improve tasks sorting
+ }
+
+ return output;
+ }
+
+ private Task CreateBinary(TaskGraph graph, BuildOptions options, string outputFilePath)
+ {
+ var task = graph.Add();
+
+ // Setup arguments
+ var args = new List();
+ args.AddRange(options.LinkEnv.CustomArgs);
+ {
+ args.Add(string.Format("-o \"{0}\"", outputFilePath.Replace('\\', '/')));
+
+ AddSharedArgs(args, options.LinkEnv.DebugInformation, options.LinkEnv.Optimization, options.CompileEnv.FavorSizeOrSpeed);
+ }
+
+ args.Add("-Wl,--start-group");
+
+ // Input libraries
+ var libraryPaths = new HashSet();
+ var dynamicLibExt = Platform.SharedLibraryFileExtension;
+ foreach (var library in options.LinkEnv.InputLibraries.Concat(options.Libraries))
+ {
+ var dir = Path.GetDirectoryName(library);
+ var ext = Path.GetExtension(library);
+ if (library.StartsWith("--use-port="))
+ {
+ // Ports (https://emscripten.org/docs/compiling/Building-Projects.html#emscripten-ports)
+ args.Add(library);
+ }
+ else if (string.IsNullOrEmpty(dir))
+ {
+ args.Add(string.Format("\"-l{0}\"", library));
+ }
+ else if (string.IsNullOrEmpty(ext))
+ {
+ // Skip executable
+ }
+ else if (ext == dynamicLibExt)
+ {
+ // Link against dynamic library
+ task.PrerequisiteFiles.Add(library);
+ libraryPaths.Add(dir);
+ args.Add(string.Format("\"-l{0}\"", GetLibName(library)));
+ }
+ else
+ {
+ task.PrerequisiteFiles.Add(library);
+ args.Add(string.Format("\"-l{0}\"", GetLibName(library)));
+ }
+ }
+
+ // Input files (link static libraries last)
+ task.PrerequisiteFiles.AddRange(options.LinkEnv.InputFiles);
+ foreach (var file in options.LinkEnv.InputFiles.Where(x => !x.EndsWith(".a")).Concat(options.LinkEnv.InputFiles.Where(x => x.EndsWith(".a"))))
+ {
+ args.Add(string.Format("\"{0}\"", file.Replace('\\', '/')));
+ }
+
+ // Additional lib paths
+ libraryPaths.AddRange(options.LinkEnv.LibraryPaths);
+ foreach (var path in libraryPaths)
+ {
+ args.Add(string.Format("-L\"{0}\"", path.Replace('\\', '/')));
+ }
+
+ args.Add("-Wl,--end-group");
+
+ // Use a response file (it can contain any commands that you would specify on the command line)
+ bool useResponseFile = true;
+ string responseFile = null;
+ if (useResponseFile)
+ {
+ responseFile = Path.Combine(options.IntermediateFolder, Path.GetFileName(outputFilePath) + ".response");
+ task.PrerequisiteFiles.Add(responseFile);
+ Utilities.WriteFileIfChanged(responseFile, string.Join(Environment.NewLine, args));
+ }
+
+ // Link
+ task.WorkingDirectory = options.WorkingDirectory;
+ task.CommandPath = _compilerPath;
+ task.CommandArguments = useResponseFile ? string.Format("@\"{0}\"", responseFile) : string.Join(" ", args);
+ task.InfoMessage = "Linking " + outputFilePath;
+ task.Cost = task.PrerequisiteFiles.Count;
+ task.ProducedFiles.Add(outputFilePath);
+
+ return task;
+ }
+
+ ///
+ public override void LinkFiles(TaskGraph graph, BuildOptions options, string outputFilePath)
+ {
+ outputFilePath = Utilities.NormalizePath(outputFilePath);
+
+ Task linkTask;
+ switch (options.LinkEnv.Output)
+ {
+ case LinkerOutput.Executable:
+ case LinkerOutput.SharedLibrary:
+ linkTask = CreateBinary(graph, options, outputFilePath);
+ break;
+ case LinkerOutput.StaticLibrary:
+ case LinkerOutput.ImportLibrary:
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+ }
+ }
+}
diff --git a/Source/Tools/Flax.Build/Platforms/Windows/WindowsPlatform.cs b/Source/Tools/Flax.Build/Platforms/Windows/WindowsPlatform.cs
index a78f8c24f..fb4b55bf9 100644
--- a/Source/Tools/Flax.Build/Platforms/Windows/WindowsPlatform.cs
+++ b/Source/Tools/Flax.Build/Platforms/Windows/WindowsPlatform.cs
@@ -67,6 +67,7 @@ namespace Flax.Build.Platforms
case TargetPlatform.Switch: return Sdk.HasValid("SwitchSdk");
case TargetPlatform.XboxOne:
case TargetPlatform.XboxScarlett: return GetPlatform(platform, true)?.HasRequiredSDKsInstalled ?? false;
+ case TargetPlatform.Web: return EmscriptenSdk.Instance.IsValid;
default: return false;
}
}