Merge remote-tracking branch 'origin/master' into 1.13
# Conflicts: # Source/Engine/GraphicsDevice/Vulkan/GPUSwapChainVulkan.cpp
This commit is contained in:
@@ -80,7 +80,7 @@ Follow the instructions below to compile and run the engine from source.
|
||||
* Open workspace with XCode or Visual Studio Code
|
||||
* Build and run (configuration `Editor.Mac.Development`)
|
||||
|
||||
#### Troubleshooting
|
||||
## Troubleshooting
|
||||
|
||||
* `Could not execute because the specified command or file was not found.`
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using FlaxEditor.GUI.Tree;
|
||||
using FlaxEditor.Options;
|
||||
using FlaxEngine;
|
||||
@@ -54,6 +54,10 @@ public class TreeViewPanel : Panel
|
||||
{
|
||||
Editor.Instance.Windows.ContentWin.Rename(contentNode.Item);
|
||||
}
|
||||
else if (node is ContentFolderTreeNode folderNode)
|
||||
{
|
||||
Editor.Instance.Windows.ContentWin.Rename(folderNode.Folder);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,6 +106,10 @@ public class TreeViewPanel : Panel
|
||||
{
|
||||
items.Add(contentNode.Item);
|
||||
}
|
||||
else if (node is ContentFolderTreeNode folderNode)
|
||||
{
|
||||
items.Add(folderNode.Folder);
|
||||
}
|
||||
}
|
||||
|
||||
Editor.Instance.Windows.ContentWin.Duplicate(items);
|
||||
@@ -121,8 +129,12 @@ public class TreeViewPanel : Panel
|
||||
return;
|
||||
var filePaths = new List<string>();
|
||||
foreach (var node in selection)
|
||||
{
|
||||
if (node is ContentItemTreeNode contentNode)
|
||||
filePaths.Add(contentNode.Item.Path);
|
||||
else if (node is ContentFolderTreeNode folderNode)
|
||||
filePaths.Add(folderNode.Folder.Path);
|
||||
}
|
||||
|
||||
Clipboard.Files = filePaths.ToArray();
|
||||
UpdateContentItemCut(false);
|
||||
@@ -168,6 +180,8 @@ public class TreeViewPanel : Panel
|
||||
{
|
||||
if (node is ContentItemTreeNode contentNode)
|
||||
_cutItems.Add(contentNode.Item);
|
||||
else if (node is ContentFolderTreeNode folderNode)
|
||||
_cutItems.Add(folderNode.Folder);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -330,18 +330,16 @@ bool AndroidPlatformTools::OnPostProcess(CookingData& data)
|
||||
GameCooker::PackageFiles();
|
||||
|
||||
// Validate environment variables
|
||||
Dictionary<String, String> envVars;
|
||||
Platform::GetEnvironmentVariables(envVars);
|
||||
String javaHome;
|
||||
if (!envVars.TryGet(TEXT("JAVA_HOME"), javaHome) || !FileSystem::DirectoryExists(javaHome))
|
||||
if (Platform::GetEnvironmentVariable(TEXT("JAVA_HOME"), javaHome) || !FileSystem::DirectoryExists(javaHome))
|
||||
{
|
||||
LOG(Error, "Missing or invalid JAVA_HOME env variable. {0}", javaHome);
|
||||
return true;
|
||||
}
|
||||
String androidSdk;
|
||||
if (!envVars.TryGet(TEXT("ANDROID_HOME"), androidSdk) || !FileSystem::DirectoryExists(androidSdk))
|
||||
if (Platform::GetEnvironmentVariable(TEXT("ANDROID_HOME"), androidSdk) || !FileSystem::DirectoryExists(androidSdk))
|
||||
{
|
||||
if (!envVars.TryGet(TEXT("ANDROID_SDK"), androidSdk) || !FileSystem::DirectoryExists(androidSdk))
|
||||
if (Platform::GetEnvironmentVariable(TEXT("ANDROID_SDK"), androidSdk) || !FileSystem::DirectoryExists(androidSdk))
|
||||
{
|
||||
LOG(Error, "Missing or invalid ANDROID_HOME env variable. {0}", androidSdk);
|
||||
return true;
|
||||
@@ -355,10 +353,11 @@ bool AndroidPlatformTools::OnPostProcess(CookingData& data)
|
||||
#else
|
||||
const Char* gradlew = TEXT("gradlew");
|
||||
#endif
|
||||
#if PLATFORM_LINUX
|
||||
#if PLATFORM_LINUX || PLATFORM_MAC
|
||||
{
|
||||
CreateProcessSettings procSettings;
|
||||
procSettings.FileName = String::Format(TEXT("chmod +x \"{0}/gradlew\""), data.OriginalOutputPath);
|
||||
procSettings.FileName = TEXT("/bin/chmod");
|
||||
procSettings.Arguments = String::Format(TEXT("+x \"{0}\""), data.OriginalOutputPath / gradlew);
|
||||
procSettings.WorkingDirectory = data.OriginalOutputPath;
|
||||
procSettings.HiddenWindow = true;
|
||||
Platform::CreateProcess(procSettings);
|
||||
@@ -371,7 +370,8 @@ bool AndroidPlatformTools::OnPostProcess(CookingData& data)
|
||||
// .aab
|
||||
{
|
||||
CreateProcessSettings procSettings;
|
||||
procSettings.FileName = String::Format(TEXT("\"{0}\" {1}"), data.OriginalOutputPath / gradlew, distributionPackage ? TEXT(":app:bundle") : TEXT(":app:bundleDebug"));
|
||||
procSettings.FileName = data.OriginalOutputPath / gradlew;
|
||||
procSettings.Arguments = distributionPackage ? TEXT("--console=plain :app:bundle") : TEXT("--console=plain :app:bundleDebug");
|
||||
procSettings.WorkingDirectory = data.OriginalOutputPath;
|
||||
const int32 result = Platform::CreateProcess(procSettings);
|
||||
if (result != 0)
|
||||
@@ -394,7 +394,8 @@ bool AndroidPlatformTools::OnPostProcess(CookingData& data)
|
||||
// .apk
|
||||
{
|
||||
CreateProcessSettings procSettings;
|
||||
procSettings.FileName = String::Format(TEXT("\"{0}\" {1}"), data.OriginalOutputPath / gradlew, distributionPackage ? TEXT("assemble") : TEXT("assembleDebug"));
|
||||
procSettings.FileName = data.OriginalOutputPath / gradlew;
|
||||
procSettings.Arguments = distributionPackage ? TEXT("--console=plain assemble") : TEXT("--console=plain assembleDebug");
|
||||
procSettings.WorkingDirectory = data.OriginalOutputPath;
|
||||
const int32 result = Platform::CreateProcess(procSettings);
|
||||
if (result != 0)
|
||||
|
||||
@@ -29,8 +29,8 @@ namespace FlaxEditor.CustomEditors.Dedicated
|
||||
patchesCount,
|
||||
patchesCount * 16,
|
||||
chunkSize,
|
||||
1.0f / (resolution.X + 1e-9f),
|
||||
1.0f / (resolution.Z + 1e-9f),
|
||||
Mathf.Abs(resolution.X),
|
||||
Mathf.Abs(resolution.Z),
|
||||
totalSize.X / Units.Meters2Units * 0.001f,
|
||||
totalSize.Z / Units.Meters2Units * 0.001f
|
||||
);
|
||||
|
||||
@@ -40,6 +40,7 @@ namespace FlaxEditor.Modules.SourceCodeEditing
|
||||
var codeEditing = Editor.Instance.CodeEditing;
|
||||
var vsCode = codeEditing.GetInBuildEditor(CodeEditorTypes.VSCode);
|
||||
var rider = codeEditing.GetInBuildEditor(CodeEditorTypes.Rider);
|
||||
var clion = codeEditing.GetInBuildEditor(CodeEditorTypes.CLion);
|
||||
|
||||
#if PLATFORM_WINDOWS
|
||||
// Favor the newest Visual Studio
|
||||
@@ -66,6 +67,8 @@ namespace FlaxEditor.Modules.SourceCodeEditing
|
||||
_currentEditor = vsCode;
|
||||
else if (rider != null)
|
||||
_currentEditor = rider;
|
||||
else if (clion != null)
|
||||
_currentEditor = clion;
|
||||
else
|
||||
_currentEditor = codeEditing.GetInBuildEditor(CodeEditorTypes.SystemDefault);
|
||||
}
|
||||
|
||||
@@ -811,17 +811,39 @@ namespace FlaxEditor.Modules
|
||||
private void InitWindowDecorations(RootControl mainWindow)
|
||||
{
|
||||
ScriptsBuilder.GetBinariesConfiguration(out _, out _, out _, out var configuration);
|
||||
string driver = string.Empty;
|
||||
|
||||
var tooltip = new System.Text.StringBuilder();
|
||||
|
||||
// Project info
|
||||
tooltip.AppendLine(Editor.GameProject.Name);
|
||||
var projectPath = Globals.ProjectFolder;
|
||||
#if PLATFORM_WINDOWS
|
||||
projectPath = projectPath.Replace('/', '\\');
|
||||
#endif
|
||||
tooltip.AppendLine(projectPath);
|
||||
|
||||
// Engine info
|
||||
tooltip.Append("Engine Version: ").AppendLine(Globals.EngineVersion);
|
||||
var engineNickname = Editor.EngineProject.EngineNickname;
|
||||
if (!string.IsNullOrEmpty(engineNickname))
|
||||
tooltip.Append($" ({engineNickname})");
|
||||
|
||||
// Build info
|
||||
#if USE_LARGE_WORLDS
|
||||
tooltip.AppendLine("Large Worlds Enabled");
|
||||
#endif
|
||||
tooltip.Append("Configuration: ").AppendLine(configuration);
|
||||
tooltip.Append("Graphics: ").Append(GPUDevice.Instance.RendererType);
|
||||
#if PLATFORM_LINUX
|
||||
driver = LinuxPlatform.DisplayServer;
|
||||
var driver = LinuxPlatform.DisplayServer;
|
||||
if (!string.IsNullOrEmpty(driver))
|
||||
driver = $" ({driver})";
|
||||
tooltip.Append($" ({driver})");
|
||||
#endif
|
||||
|
||||
WindowDecorations = new MainWindowDecorations(mainWindow, !Utilities.Utils.UseCustomWindowDecorations(true))
|
||||
{
|
||||
Parent = mainWindow,
|
||||
IconTooltipText = $"{mainWindow.RootWindow.Title}\nVersion {Globals.EngineVersion}\nConfiguration {configuration}\nGraphics {GPUDevice.Instance.RendererType}{driver}",
|
||||
IconTooltipText = tooltip.ToString(),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -191,13 +191,9 @@ namespace FlaxEditor.Modules
|
||||
var mainWindow = MainWindow;
|
||||
if (mainWindow)
|
||||
{
|
||||
var projectPath = Globals.ProjectFolder;
|
||||
#if PLATFORM_WINDOWS
|
||||
projectPath = projectPath.Replace('/', '\\');
|
||||
#endif
|
||||
var engineVersion = Editor.EngineProject.Version;
|
||||
var engineVersionText = engineVersion.Revision > 0 ? $"{engineVersion.Major}.{engineVersion.Minor}.{engineVersion.Revision}" : $"{engineVersion.Major}.{engineVersion.Minor}";
|
||||
var title = $"Flax Editor {engineVersionText} - \'{projectPath}\'";
|
||||
var title = $"Flax Editor {engineVersionText} - \'{Editor.GameProject.Name}\'";
|
||||
mainWindow.Title = title;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "ScriptsBuilder.h"
|
||||
#include "CodeEditors/VisualStudioCodeEditor.h"
|
||||
#include "CodeEditors/RiderCodeEditor.h"
|
||||
#include "CodeEditors/CLionCodeEditor.h"
|
||||
#if USE_VISUAL_STUDIO_DTE
|
||||
#include "CodeEditors/VisualStudio/VisualStudioEditor.h"
|
||||
#endif
|
||||
@@ -269,6 +270,7 @@ bool CodeEditingManagerService::Init()
|
||||
#endif
|
||||
VisualStudioCodeEditor::FindEditors(&CodeEditors);
|
||||
RiderCodeEditor::FindEditors(&CodeEditors);
|
||||
CLionCodeEditor::FindEditors(&CodeEditors);
|
||||
CodeEditors.Add(New<SystemDefaultCodeEditor>());
|
||||
|
||||
return false;
|
||||
|
||||
@@ -82,6 +82,11 @@ API_ENUM(Namespace="FlaxEditor", Attributes="HideInEditor") enum class CodeEdito
|
||||
/// </summary>
|
||||
Rider,
|
||||
|
||||
/// <summary>
|
||||
/// CLion
|
||||
/// </summary>
|
||||
CLion,
|
||||
|
||||
MAX
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,369 @@
|
||||
// Copyright (c) Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "CLionCodeEditor.h"
|
||||
#include "Engine/Platform/FileSystem.h"
|
||||
#include "Engine/Core/Collections/Sorting.h"
|
||||
#include "Engine/Engine/Globals.h"
|
||||
#include "Engine/Platform/CreateProcessSettings.h"
|
||||
#include "Engine/Platform/File.h"
|
||||
#include "Engine/Serialization/Json.h"
|
||||
#include "Editor/Editor.h"
|
||||
#include "Editor/ProjectInfo.h"
|
||||
#include "Editor/Scripting/ScriptsBuilder.h"
|
||||
|
||||
#if PLATFORM_WINDOWS
|
||||
#include "Engine/Platform/Win32/IncludeWindowsHeaders.h"
|
||||
#elif PLATFORM_MAC
|
||||
#include "Engine/Platform/Apple/AppleUtils.h"
|
||||
#include <AppKit/AppKit.h>
|
||||
#endif
|
||||
|
||||
namespace
|
||||
{
|
||||
struct CLionInstallation
|
||||
{
|
||||
String path;
|
||||
String argumentsPrefix;
|
||||
String version;
|
||||
|
||||
CLionInstallation(const String& path_, const String& argumentsPrefix_, const String& version_)
|
||||
: path(path_)
|
||||
, argumentsPrefix(argumentsPrefix_)
|
||||
, version(version_)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
void AddInstallation(Array<CLionInstallation*>* installations, String path, const String& version, const String& argumentsPrefix = String::Empty)
|
||||
{
|
||||
if (path.IsEmpty())
|
||||
return;
|
||||
|
||||
StringUtils::PathRemoveRelativeParts(path);
|
||||
for (CLionInstallation* installation : *installations)
|
||||
{
|
||||
if (installation->path == path && installation->argumentsPrefix == argumentsPrefix)
|
||||
return;
|
||||
}
|
||||
installations->Add(New<CLionInstallation>(path, argumentsPrefix, version));
|
||||
}
|
||||
|
||||
bool IsCLionProduct(rapidjson_flax::Document& document)
|
||||
{
|
||||
auto productCodeMember = document.FindMember("productCode");
|
||||
if (productCodeMember != document.MemberEnd() && productCodeMember->value == "CL")
|
||||
return true;
|
||||
|
||||
auto nameMember = document.FindMember("name");
|
||||
if (nameMember == document.MemberEnd())
|
||||
return false;
|
||||
|
||||
const String name = nameMember->value.GetText();
|
||||
return name == TEXT("CLion") || name == TEXT("JetBrains CLion");
|
||||
}
|
||||
|
||||
void SearchDirectory(Array<CLionInstallation*>* installations, const String& directory, String launchOverridePath = String::Empty, String launchArgumentsPrefix = String::Empty)
|
||||
{
|
||||
if (!FileSystem::DirectoryExists(directory))
|
||||
return;
|
||||
|
||||
// Load product info
|
||||
Array<byte> productInfoData;
|
||||
const String productInfoPath = directory / TEXT("product-info.json");
|
||||
if (!FileSystem::FileExists(productInfoPath) || File::ReadAllBytes(productInfoPath, productInfoData))
|
||||
return;
|
||||
rapidjson_flax::Document document;
|
||||
document.Parse((char*)productInfoData.Get(), productInfoData.Count());
|
||||
if (document.HasParseError() || !IsCLionProduct(document))
|
||||
return;
|
||||
|
||||
// Find version
|
||||
auto versionMember = document.FindMember("version");
|
||||
if (versionMember == document.MemberEnd())
|
||||
return;
|
||||
|
||||
// Find executable file path
|
||||
auto launchMember = document.FindMember("launch");
|
||||
if (launchMember == document.MemberEnd() || !launchMember->value.IsArray() || launchMember->value.Size() == 0)
|
||||
return;
|
||||
|
||||
auto launcherPathMember = launchMember->value[0].FindMember("launcherPath");
|
||||
if (launcherPathMember == launchMember->value[0].MemberEnd())
|
||||
return;
|
||||
|
||||
auto launcherPath = launcherPathMember->value.GetText();
|
||||
auto exePath = directory / launcherPath;
|
||||
if (!launcherPath.HasChars() || !FileSystem::FileExists(exePath))
|
||||
return;
|
||||
|
||||
AddInstallation(installations, launchOverridePath != String::Empty ? launchOverridePath : exePath, versionMember->value.GetText(), launchArgumentsPrefix);
|
||||
}
|
||||
|
||||
void SearchExecutable(Array<CLionInstallation*>* installations, const String& path)
|
||||
{
|
||||
if (FileSystem::FileExists(path))
|
||||
AddInstallation(installations, path, TEXT("0.0.0"));
|
||||
}
|
||||
|
||||
void ParseVersion(const String& text, int32 version[3])
|
||||
{
|
||||
version[0] = 0;
|
||||
version[1] = 0;
|
||||
version[2] = 0;
|
||||
|
||||
Array<String> values;
|
||||
text.Split('.', values);
|
||||
for (int32 i = 0; i < values.Count() && i < 3; i++)
|
||||
StringUtils::Parse(values[i].Get(), &version[i]);
|
||||
}
|
||||
|
||||
#if PLATFORM_WINDOWS
|
||||
bool FindRegistryKeyItems(HKEY hKey, Array<String>& results)
|
||||
{
|
||||
Char nameBuffer[256];
|
||||
for (int32 i = 0;; i++)
|
||||
{
|
||||
const LONG result = RegEnumKeyW(hKey, i, nameBuffer, ARRAY_COUNT(nameBuffer));
|
||||
if (result == ERROR_NO_MORE_ITEMS)
|
||||
break;
|
||||
if (result != ERROR_SUCCESS)
|
||||
return false;
|
||||
results.Add(nameBuffer);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void SearchRegistry(Array<CLionInstallation*>* installations, HKEY root, const Char* key, const Char* valueName = TEXT(""))
|
||||
{
|
||||
// Open key
|
||||
HKEY keyH;
|
||||
if (RegOpenKeyExW(root, key, 0, KEY_READ, &keyH) != ERROR_SUCCESS)
|
||||
return;
|
||||
|
||||
// Iterate over subkeys
|
||||
Array<String> subKeys;
|
||||
if (FindRegistryKeyItems(keyH, subKeys))
|
||||
{
|
||||
for (auto& subKey : subKeys)
|
||||
{
|
||||
HKEY subKeyH;
|
||||
if (RegOpenKeyExW(keyH, *subKey, 0, KEY_READ, &subKeyH) != ERROR_SUCCESS)
|
||||
continue;
|
||||
|
||||
// Read subkey value
|
||||
DWORD type;
|
||||
DWORD cbData;
|
||||
if (RegQueryValueExW(subKeyH, valueName, nullptr, &type, nullptr, &cbData) != ERROR_SUCCESS || type != REG_SZ)
|
||||
{
|
||||
RegCloseKey(subKeyH);
|
||||
continue;
|
||||
}
|
||||
Array<Char> data;
|
||||
data.Resize((int32)cbData / sizeof(Char));
|
||||
if (RegQueryValueExW(subKeyH, valueName, nullptr, nullptr, reinterpret_cast<LPBYTE>(data.Get()), &cbData) != ERROR_SUCCESS)
|
||||
{
|
||||
RegCloseKey(subKeyH);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if it's a valid installation path
|
||||
String path(data.Get(), data.Count() - 1);
|
||||
SearchDirectory(installations, path);
|
||||
|
||||
RegCloseKey(subKeyH);
|
||||
}
|
||||
}
|
||||
|
||||
RegCloseKey(keyH);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
bool SortInstallations(CLionInstallation* const& i1, CLionInstallation* const& i2)
|
||||
{
|
||||
int32 version1[3], version2[3];
|
||||
ParseVersion(i1->version, version1);
|
||||
ParseVersion(i2->version, version2);
|
||||
|
||||
// Compare by MAJOR.MINOR.BUILD
|
||||
if (version1[0] == version2[0])
|
||||
{
|
||||
if (version1[1] == version2[1])
|
||||
return version1[2] > version2[2];
|
||||
return version1[1] > version2[1];
|
||||
}
|
||||
return version1[0] > version2[0];
|
||||
}
|
||||
|
||||
CLionCodeEditor::CLionCodeEditor(const String& execPath, const String& execArgsPrefix)
|
||||
: _execPath(execPath)
|
||||
, _execArgsPrefix(execArgsPrefix)
|
||||
, _projectPath(Globals::ProjectFolder / TEXT("Cache/Projects/CMake") / Editor::Project->Name)
|
||||
{
|
||||
}
|
||||
|
||||
String CLionCodeEditor::GetProcessArguments(const String& arguments) const
|
||||
{
|
||||
return _execArgsPrefix.HasChars() ? _execArgsPrefix + TEXT(" ") + arguments : arguments;
|
||||
}
|
||||
|
||||
void CLionCodeEditor::FindEditors(Array<CodeEditor*>* output)
|
||||
{
|
||||
Array<CLionInstallation*> installations;
|
||||
Array<String> subDirectories;
|
||||
|
||||
String localAppDataPath;
|
||||
FileSystem::GetSpecialFolderPath(SpecialFolder::LocalAppData, localAppDataPath);
|
||||
|
||||
#if PLATFORM_WINDOWS
|
||||
// Lookup from all known registry locations
|
||||
SearchRegistry(&installations, HKEY_CURRENT_USER, TEXT("SOFTWARE\\JetBrains\\CLion"));
|
||||
SearchRegistry(&installations, HKEY_LOCAL_MACHINE, TEXT("SOFTWARE\\JetBrains\\CLion"));
|
||||
SearchRegistry(&installations, HKEY_CURRENT_USER, TEXT("SOFTWARE\\JetBrains\\CLion"), TEXT("InstallDir"));
|
||||
SearchRegistry(&installations, HKEY_LOCAL_MACHINE, TEXT("SOFTWARE\\JetBrains\\CLion"), TEXT("InstallDir"));
|
||||
SearchRegistry(&installations, HKEY_CURRENT_USER, TEXT("SOFTWARE\\WOW6432Node\\JetBrains\\CLion"));
|
||||
SearchRegistry(&installations, HKEY_LOCAL_MACHINE, TEXT("SOFTWARE\\WOW6432Node\\JetBrains\\CLion"));
|
||||
|
||||
// Versions installed via JetBrains Toolbox
|
||||
FileSystem::GetChildDirectories(subDirectories, localAppDataPath / TEXT("Programs"));
|
||||
FileSystem::GetChildDirectories(subDirectories, localAppDataPath / TEXT("JetBrains\\Toolbox\\apps\\CLion\\ch-0\\"));
|
||||
FileSystem::GetChildDirectories(subDirectories, localAppDataPath / TEXT("JetBrains\\Toolbox\\apps\\CLion\\ch-1\\")); // Beta versions
|
||||
#endif
|
||||
#if PLATFORM_LINUX
|
||||
// TODO: detect Snap installations by reading the desktop file from ~/.local/share/applications and /usr/share/applications.
|
||||
|
||||
SearchDirectory(&installations, TEXT("/usr/share/clion/"));
|
||||
FileSystem::GetChildDirectories(subDirectories, TEXT("/usr/share/clion"));
|
||||
|
||||
// Default suggested location for standalone installations
|
||||
FileSystem::GetChildDirectories(subDirectories, TEXT("/opt/"));
|
||||
|
||||
// Versions installed via JetBrains Toolbox
|
||||
SearchDirectory(&installations, localAppDataPath / TEXT("JetBrains/Toolbox/apps/clion/"));
|
||||
FileSystem::GetChildDirectories(subDirectories, localAppDataPath / TEXT("JetBrains/Toolbox/apps/CLion/ch-0"));
|
||||
FileSystem::GetChildDirectories(subDirectories, localAppDataPath / TEXT("JetBrains/Toolbox/apps/CLion/ch-1")); // Beta versions
|
||||
|
||||
// Detect Flatpak installations
|
||||
SearchDirectory(&installations,
|
||||
TEXT("/var/lib/flatpak/app/com.jetbrains.CLion/current/active/files/extra/clion/"),
|
||||
TEXT("flatpak"),
|
||||
TEXT("run com.jetbrains.CLion"));
|
||||
|
||||
SearchExecutable(&installations, TEXT("/usr/bin/clion"));
|
||||
SearchExecutable(&installations, TEXT("/snap/bin/clion"));
|
||||
#endif
|
||||
|
||||
#if PLATFORM_MAC
|
||||
String applicationSupportFolder;
|
||||
FileSystem::GetSpecialFolderPath(SpecialFolder::ProgramData, applicationSupportFolder);
|
||||
|
||||
NSURL* appURL = [[NSWorkspace sharedWorkspace] URLForApplicationWithBundleIdentifier:@"com.jetbrains.CLion"];
|
||||
if (appURL != nullptr)
|
||||
{
|
||||
const String appPath = AppleUtils::ToString((CFStringRef)[appURL path]);
|
||||
SearchDirectory(&installations, appPath / TEXT("Contents/Resources"), appPath);
|
||||
}
|
||||
|
||||
Array<String> subMacDirectories;
|
||||
FileSystem::GetChildDirectories(subMacDirectories, applicationSupportFolder / TEXT("JetBrains/Toolbox/apps/CLion/ch-0/"));
|
||||
FileSystem::GetChildDirectories(subMacDirectories, applicationSupportFolder / TEXT("JetBrains/Toolbox/apps/CLion/ch-1/"));
|
||||
for (const String& directory : subMacDirectories)
|
||||
{
|
||||
String clionAppPath = directory / TEXT("CLion.app");
|
||||
SearchDirectory(&installations, clionAppPath / TEXT("Contents/Resources"), clionAppPath);
|
||||
}
|
||||
|
||||
// Check the local installer version
|
||||
SearchDirectory(&installations, TEXT("/Applications/CLion.app/Contents/Resources"), TEXT("/Applications/CLion.app"));
|
||||
|
||||
String userFolder;
|
||||
FileSystem::GetSpecialFolderPath(SpecialFolder::Documents, userFolder);
|
||||
String clionAppPath = userFolder / TEXT("../Applications/CLion.app");
|
||||
SearchDirectory(&installations, clionAppPath / TEXT("Contents/Resources"), clionAppPath);
|
||||
#endif
|
||||
|
||||
for (const String& directory : subDirectories)
|
||||
SearchDirectory(&installations, directory);
|
||||
|
||||
// Sort found installations by version number
|
||||
Sorting::QuickSort(installations.Get(), installations.Count(), &SortInstallations);
|
||||
|
||||
for (CLionInstallation* installation : installations)
|
||||
{
|
||||
output->Add(New<CLionCodeEditor>(installation->path, installation->argumentsPrefix));
|
||||
Delete(installation);
|
||||
}
|
||||
}
|
||||
|
||||
CodeEditorTypes CLionCodeEditor::GetType() const
|
||||
{
|
||||
return CodeEditorTypes::CLion;
|
||||
}
|
||||
|
||||
String CLionCodeEditor::GetName() const
|
||||
{
|
||||
return TEXT("CLion");
|
||||
}
|
||||
|
||||
String CLionCodeEditor::GetGenerateProjectCustomArgs() const
|
||||
{
|
||||
return TEXT("-clion");
|
||||
}
|
||||
|
||||
void CLionCodeEditor::OpenFile(const String& path, int32 line)
|
||||
{
|
||||
// Generate project files if missing
|
||||
if (!FileSystem::FileExists(_projectPath / TEXT("CMakeLists.txt")) ||
|
||||
!FileSystem::FileExists(_projectPath / TEXT("CMakePresets.json")))
|
||||
{
|
||||
ScriptsBuilder::GenerateProject(GetGenerateProjectCustomArgs());
|
||||
}
|
||||
|
||||
// Open file
|
||||
line = line > 0 ? line : 1;
|
||||
CreateProcessSettings procSettings;
|
||||
|
||||
#if !PLATFORM_MAC
|
||||
procSettings.FileName = _execPath;
|
||||
procSettings.Arguments = GetProcessArguments(String::Format(TEXT("\"{0}\" --line {2} \"{1}\""), _projectPath, path, line));
|
||||
#else
|
||||
procSettings.FileName = "/usr/bin/open";
|
||||
procSettings.Arguments = String::Format(TEXT("-n -a \"{0}\" --args \"{1}\" --line {3} \"{2}\""), _execPath, _projectPath, path, line);
|
||||
#endif
|
||||
|
||||
procSettings.HiddenWindow = false;
|
||||
procSettings.WaitForEnd = false;
|
||||
procSettings.LogOutput = false;
|
||||
procSettings.ShellExecute = true;
|
||||
Platform::CreateProcess(procSettings);
|
||||
}
|
||||
|
||||
void CLionCodeEditor::OpenSolution()
|
||||
{
|
||||
// Generate project files if missing
|
||||
if (!FileSystem::FileExists(_projectPath / TEXT("CMakeLists.txt")) ||
|
||||
!FileSystem::FileExists(_projectPath / TEXT("CMakePresets.json")))
|
||||
{
|
||||
ScriptsBuilder::GenerateProject(GetGenerateProjectCustomArgs());
|
||||
}
|
||||
|
||||
// Open solution
|
||||
CreateProcessSettings procSettings;
|
||||
#if !PLATFORM_MAC
|
||||
procSettings.FileName = _execPath;
|
||||
procSettings.Arguments = GetProcessArguments(String::Format(TEXT("\"{0}\""), _projectPath));
|
||||
#else
|
||||
procSettings.FileName = "/usr/bin/open";
|
||||
procSettings.Arguments = String::Format(TEXT("-n -a \"{0}\" \"{1}\""), _execPath, _projectPath);
|
||||
#endif
|
||||
procSettings.HiddenWindow = false;
|
||||
procSettings.WaitForEnd = false;
|
||||
procSettings.LogOutput = false;
|
||||
procSettings.ShellExecute = true;
|
||||
Platform::CreateProcess(procSettings);
|
||||
}
|
||||
|
||||
void CLionCodeEditor::OnFileAdded(const String& path)
|
||||
{
|
||||
ScriptsBuilder::GenerateProject(GetGenerateProjectCustomArgs());
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
// Copyright (c) Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Editor/Scripting/CodeEditor.h"
|
||||
|
||||
/// <summary>
|
||||
/// Implementation of code editor utility that is using CLion from JetBrains.
|
||||
/// </summary>
|
||||
class CLionCodeEditor : public CodeEditor
|
||||
{
|
||||
private:
|
||||
|
||||
String _execPath;
|
||||
String _execArgsPrefix;
|
||||
String _projectPath;
|
||||
|
||||
String GetProcessArguments(const String& arguments) const;
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CLionCodeEditor"/> class.
|
||||
/// </summary>
|
||||
/// <param name="execPath">Executable file path</param>
|
||||
/// <param name="execArgsPrefix">Additional arguments to pass before CLion arguments.</param>
|
||||
CLionCodeEditor(const String& execPath, const String& execArgsPrefix = String::Empty);
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Tries to find installed CLion instances. Adds them to the result list.
|
||||
/// </summary>
|
||||
/// <param name="output">The output editors.</param>
|
||||
static void FindEditors(Array<CodeEditor*>* output);
|
||||
|
||||
public:
|
||||
|
||||
// [CodeEditor]
|
||||
CodeEditorTypes GetType() const override;
|
||||
String GetName() const override;
|
||||
String GetGenerateProjectCustomArgs() const override;
|
||||
void OpenFile(const String& path, int32 line) override;
|
||||
void OpenSolution() override;
|
||||
void OnFileAdded(const String& path) override;
|
||||
};
|
||||
@@ -431,7 +431,7 @@ namespace FlaxEditor.Viewport.Previews
|
||||
{
|
||||
base.Draw();
|
||||
|
||||
if (_showCurrentLOD)
|
||||
if (_showCurrentLOD && Model)
|
||||
{
|
||||
var asset = Model;
|
||||
var lodIndex = ComputeLODIndex(asset, out var screenSize);
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace FlaxEditor.Content.Settings
|
||||
internal const string PS5PlatformSettingsTypename = "FlaxEditor.Content.Settings.PS5PlatformSettings";
|
||||
internal const string XboxOnePlatformSettingsTypename = "FlaxEditor.Content.Settings.XboxOnePlatformSettings";
|
||||
internal const string XboxScarlettPlatformSettingsTypename = "FlaxEditor.Content.Settings.XboxScarlettPlatformSettings";
|
||||
internal const string AndroidPlatformSettingsTypename = "FlaxEditor.Content.Settings.AndroidPlatformSettings";
|
||||
internal const string SwitchPlatformSettingsTypename = "FlaxEditor.Content.Settings.SwitchPlatformSettings";
|
||||
#if FLAX_EDITOR
|
||||
internal static string[] OptionalPlatformSettings =
|
||||
@@ -172,9 +173,9 @@ namespace FlaxEditor.Content.Settings
|
||||
|
||||
#if FLAX_EDITOR || PLATFORM_ANDROID
|
||||
/// <summary>
|
||||
/// Reference to <see cref="AndroidPlatformSettings"/> asset. Used to apply configuration on Android platform.
|
||||
/// Reference to Android Platform Settings asset. Used to apply configuration on Android platform.
|
||||
/// </summary>
|
||||
[EditorOrder(2060), EditorDisplay("Platform Settings", "Android"), AssetReference(typeof(AndroidPlatformSettings), true), Tooltip("Reference to Android Platform Settings asset")]
|
||||
[EditorOrder(2060), EditorDisplay("Platform Settings", "Android"), AssetReference(AndroidPlatformSettingsTypename, true), Tooltip("Reference to Android Platform Settings asset")]
|
||||
public JsonAsset AndroidPlatform;
|
||||
#endif
|
||||
|
||||
@@ -334,8 +335,8 @@ namespace FlaxEditor.Content.Settings
|
||||
return Load(gameSettings.XboxScarlettPlatform, XboxScarlettPlatformSettingsTypename) as T;
|
||||
#endif
|
||||
#if FLAX_EDITOR || PLATFORM_ANDROID
|
||||
if (type == typeof(AndroidPlatformSettings))
|
||||
return Load<AndroidPlatformSettings>(gameSettings.AndroidPlatform) as T;
|
||||
if (type.FullName == AndroidPlatformSettingsTypename)
|
||||
return Load(gameSettings.AndroidPlatform, AndroidPlatformSettingsTypename) as T;
|
||||
#endif
|
||||
#if FLAX_EDITOR || PLATFORM_SWITCH
|
||||
if (type.FullName == SwitchPlatformSettingsTypename)
|
||||
@@ -436,7 +437,7 @@ namespace FlaxEditor.Content.Settings
|
||||
return gameSettings.XboxScarlettPlatform;
|
||||
#endif
|
||||
#if FLAX_EDITOR || PLATFORM_ANDROID
|
||||
if (type == typeof(AndroidPlatformSettings))
|
||||
if (type.FullName == AndroidPlatformSettingsTypename)
|
||||
return gameSettings.AndroidPlatform;
|
||||
#endif
|
||||
#if FLAX_EDITOR || PLATFORM_SWITCH
|
||||
|
||||
@@ -27,22 +27,30 @@ void BackBufferVulkan::Setup(GPUSwapChainVulkan* window, VkImage backbuffer, Pix
|
||||
ImageAcquiredSemaphore = New<SemaphoreVulkan>(Device);
|
||||
}
|
||||
|
||||
void BackBufferVulkan::WaitForSubmit()
|
||||
{
|
||||
if (SubmitCmdBuffer)
|
||||
{
|
||||
if (SubmitCmdBufferFenceCounter == SubmitCmdBuffer->GetFenceSignaledCounter())
|
||||
SubmitCmdBuffer->Wait();
|
||||
SubmitCmdBuffer = nullptr;
|
||||
SubmitCmdBufferFenceCounter = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void BackBufferVulkan::Release()
|
||||
{
|
||||
WaitForSubmit();
|
||||
Handle.Release();
|
||||
Delete(RenderingDoneSemaphore);
|
||||
Delete(ImageAcquiredSemaphore);
|
||||
if (SubmitCmdBuffer)
|
||||
{
|
||||
SubmitCmdBuffer->Wait();
|
||||
SubmitCmdBuffer = nullptr;
|
||||
}
|
||||
Device = nullptr;
|
||||
}
|
||||
|
||||
GPUSwapChainVulkan::GPUSwapChainVulkan(GPUDeviceVulkan* device, Window* window)
|
||||
: GPUResourceVulkan(device, StringView::Empty)
|
||||
, _surface(VK_NULL_HANDLE)
|
||||
, _surfaceWindowHandle(nullptr)
|
||||
, _swapChain(VK_NULL_HANDLE)
|
||||
, _currentImageIndex(-1)
|
||||
, _semaphoreIndex(0)
|
||||
@@ -61,16 +69,17 @@ void GPUSwapChainVulkan::ReleaseBackBuffer()
|
||||
_backBuffers.Clear();
|
||||
}
|
||||
|
||||
void GPUSwapChainVulkan::OnReleaseGPU()
|
||||
void GPUSwapChainVulkan::ReleaseSwapChain(bool releaseSurface)
|
||||
{
|
||||
GPUDeviceLock lock(_device);
|
||||
|
||||
_device->WaitForGPU();
|
||||
// The caller must ensure GPU work using the current swapchain is complete before destroying it.
|
||||
if (_memoryUsage != 0)
|
||||
{
|
||||
PROFILE_MEM_DEC(Graphics, _memoryUsage);
|
||||
_memoryUsage = 0;
|
||||
}
|
||||
|
||||
ReleaseBackBuffer();
|
||||
|
||||
// Release data
|
||||
PROFILE_MEM_DEC(Graphics, _memoryUsage);
|
||||
_currentImageIndex = -1;
|
||||
_semaphoreIndex = 0;
|
||||
_acquiredImageIndex = -1;
|
||||
@@ -80,13 +89,23 @@ void GPUSwapChainVulkan::OnReleaseGPU()
|
||||
vkDestroySwapchainKHR(_device->Device, _swapChain, nullptr);
|
||||
_swapChain = VK_NULL_HANDLE;
|
||||
}
|
||||
if (_surface != VK_NULL_HANDLE)
|
||||
// Resize only invalidates the swapchain. Destroy the native surface only if it is no longer valid
|
||||
// or when the whole GPU resource is being released.
|
||||
if (releaseSurface && _surface != VK_NULL_HANDLE)
|
||||
{
|
||||
vkDestroySurfaceKHR(GPUDeviceVulkan::Instance, _surface, nullptr);
|
||||
_surface = VK_NULL_HANDLE;
|
||||
_surfaceWindowHandle = nullptr;
|
||||
}
|
||||
_width = _height = 0;
|
||||
_memoryUsage = 0;
|
||||
}
|
||||
|
||||
void GPUSwapChainVulkan::OnReleaseGPU()
|
||||
{
|
||||
GPUDeviceLock lock(_device);
|
||||
|
||||
_device->WaitForGPU();
|
||||
ReleaseSwapChain(true);
|
||||
}
|
||||
|
||||
bool GPUSwapChainVulkan::IsFullscreen()
|
||||
@@ -116,24 +135,34 @@ GPUTextureView* GPUSwapChainVulkan::GetBackBufferView()
|
||||
if (_acquiredImageIndex == -1)
|
||||
{
|
||||
PROFILE_CPU();
|
||||
auto context = _device->MainContext;
|
||||
auto cmdBufferManager = context->GetCmdBufferManager();
|
||||
// Keep commands recorded before acquire independent from the image-acquired semaphore wait below.
|
||||
if (cmdBufferManager->HasPendingActiveCmdBuffer())
|
||||
context->Flush();
|
||||
|
||||
if (TryPresent(DoAcquireImageIndex) < 0)
|
||||
{
|
||||
LOG(Fatal, "Swapchain acquire image index failed!");
|
||||
}
|
||||
ASSERT(_acquiredImageIndex != -1);
|
||||
|
||||
auto context = _device->MainContext;
|
||||
// Wait for prior GPU work that used this acquired image before recording
|
||||
// commands against it again. Waiting before acquire can target a different image
|
||||
// and unnecessarily serialize frames when the swapchain has multiple images.
|
||||
auto& acquiredBackBuffer = _backBuffers[_acquiredImageIndex];
|
||||
acquiredBackBuffer.WaitForSubmit();
|
||||
|
||||
const auto backBuffer = &_backBuffers[_acquiredImageIndex].Handle;
|
||||
|
||||
auto cmdBufferManager = context->GetCmdBufferManager();
|
||||
auto cmdBuffer = cmdBufferManager->GetCmdBuffer();
|
||||
|
||||
// Transition to render target (typical usage in most cases when calling backbuffer getter)
|
||||
context->AddImageBarrier(backBuffer, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
|
||||
context->FlushBarriers();
|
||||
|
||||
// Submit here so we can add a dependency with the acquired semaphore
|
||||
cmdBuffer->AddWaitSemaphore(VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, _acquiredSemaphore);
|
||||
// Wait until the presentation engine releases the image before the first color-output work can use it.
|
||||
cmdBuffer->AddWaitSemaphore(VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, _acquiredSemaphore);
|
||||
cmdBufferManager->SubmitActiveCmdBuffer();
|
||||
cmdBufferManager->GetNewActiveCommandBuffer();
|
||||
ASSERT(cmdBufferManager->HasPendingActiveCmdBuffer() && cmdBufferManager->GetActiveCmdBuffer()->GetState() == CmdBufferVulkan::State::IsInsideBegin);
|
||||
@@ -145,17 +174,6 @@ void GPUSwapChainVulkan::Begin(RenderTask* task)
|
||||
{
|
||||
GPUSwapChain::Begin(task);
|
||||
|
||||
// Wait for the backbuffer to be available
|
||||
if (_currentImageIndex != -1)
|
||||
{
|
||||
auto& backBuffer = _backBuffers[_currentImageIndex];
|
||||
if (backBuffer.SubmitCmdBuffer)
|
||||
{
|
||||
backBuffer.SubmitCmdBuffer->Wait();
|
||||
backBuffer.SubmitCmdBuffer = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// Rebuild swapchain if need to
|
||||
if (_vsyncPending != _vsyncCurrent)
|
||||
{
|
||||
@@ -171,8 +189,9 @@ bool GPUSwapChainVulkan::Resize(int32 width, int32 height)
|
||||
if (width == _width && height == _height)
|
||||
return false;
|
||||
|
||||
// Wait for GPU to flush commands
|
||||
_device->WaitForGPU();
|
||||
// Flush any pending commands referencing the previous backbuffer before waiting on the device.
|
||||
if (_swapChain != VK_NULL_HANDLE)
|
||||
_device->GetMainContext()->Flush();
|
||||
|
||||
return CreateSwapChain(width, height);
|
||||
}
|
||||
@@ -204,7 +223,7 @@ void GPUSwapChainVulkan::CopyBackbuffer(GPUContext* context, GPUTexture* dst)
|
||||
vkCmdCopyImage(contextVulkan->GetCmdBufferManager()->GetCmdBuffer()->GetHandle(), backBuffer->Image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, dstVulkan->GetHandle(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion);
|
||||
}
|
||||
|
||||
bool GPUSwapChainVulkan::CreateSwapChain(int32 width, int32 height)
|
||||
bool GPUSwapChainVulkan::CreateSwapChain(int32 width, int32 height, bool recreateSurface)
|
||||
{
|
||||
// Skip if window handle is missing (eg. Android window is not yet visible)
|
||||
auto windowHandle = _window->GetNativePtr();
|
||||
@@ -213,14 +232,20 @@ bool GPUSwapChainVulkan::CreateSwapChain(int32 width, int32 height)
|
||||
PROFILE_CPU();
|
||||
GPUDeviceLock lock(_device);
|
||||
const auto device = _device->Device;
|
||||
|
||||
// Check if surface has been created before
|
||||
if (_surface != VK_NULL_HANDLE)
|
||||
const bool hasSwapChain = _swapChain != VK_NULL_HANDLE;
|
||||
if (_surface != VK_NULL_HANDLE && _surfaceWindowHandle != windowHandle)
|
||||
{
|
||||
// Release previous data
|
||||
ReleaseGPU();
|
||||
// Android can replace ANativeWindow during lifecycle changes; iOS can replace the UIView/CAMetalLayer.
|
||||
// A VkSurfaceKHR must not outlive the native object it was created from.
|
||||
recreateSurface = true;
|
||||
}
|
||||
|
||||
// Flush removed resources
|
||||
if (recreateSurface || hasSwapChain)
|
||||
{
|
||||
// Retire old swapchain resources before creating replacement images. On plain resize the VkSurfaceKHR
|
||||
// is intentionally reused; recreate it only when Vulkan or the platform window lifetime requires it.
|
||||
_device->WaitForGPU();
|
||||
ReleaseSwapChain(recreateSurface);
|
||||
_device->DeferredDeletionQueue.ReleaseResources(true);
|
||||
}
|
||||
if (_vsyncInit)
|
||||
@@ -231,15 +256,19 @@ bool GPUSwapChainVulkan::CreateSwapChain(int32 width, int32 height)
|
||||
_vsyncPending &= _window == Engine::MainWindow; // Don't use VSync on new context menus or tooltips in Editor
|
||||
_vsyncInit = false;
|
||||
}
|
||||
ASSERT(_surface == VK_NULL_HANDLE);
|
||||
ASSERT(!recreateSurface || _surface == VK_NULL_HANDLE);
|
||||
ASSERT_LOW_LAYER(_backBuffers.Count() == 0);
|
||||
|
||||
// Create platform-dependent surface
|
||||
VulkanPlatform::CreateSurface(_window, _device, GPUDeviceVulkan::Instance, &_surface);
|
||||
// Create platform-dependent surface if this is the first swapchain or if the previous surface was lost.
|
||||
if (_surface == VK_NULL_HANDLE)
|
||||
{
|
||||
LOG(Warning, "Failed to create Vulkan surface.");
|
||||
return true;
|
||||
VulkanPlatform::CreateSurface(_window, _device, GPUDeviceVulkan::Instance, &_surface);
|
||||
if (_surface == VK_NULL_HANDLE)
|
||||
{
|
||||
LOG(Warning, "Failed to create Vulkan surface.");
|
||||
return true;
|
||||
}
|
||||
_surfaceWindowHandle = windowHandle;
|
||||
}
|
||||
_memoryUsage = 1;
|
||||
|
||||
@@ -542,8 +571,8 @@ int32 GPUSwapChainVulkan::TryPresent(Function<int32(GPUSwapChainVulkan*, void*)>
|
||||
// Recreate swapchain
|
||||
ASSERT(_swapChain != VK_NULL_HANDLE);
|
||||
int32 width = _width, height = _height;
|
||||
ReleaseGPU();
|
||||
CreateSwapChain(width, height);
|
||||
// Preserve the surface for regular out-of-date swaps; recreate it only when Vulkan reports loss.
|
||||
CreateSwapChain(width, height, status == (int32)Status::LostSurface);
|
||||
|
||||
// Flush commands
|
||||
_device->GetMainContext()->Flush();
|
||||
@@ -617,6 +646,7 @@ void GPUSwapChainVulkan::Present(bool vsync)
|
||||
acquiredBackBuffer.SubmitCmdBuffer = context->GetCmdBufferManager()->GetActiveCmdBuffer();
|
||||
|
||||
context->GetCmdBufferManager()->SubmitActiveCmdBuffer(_backBuffers[_acquiredImageIndex].RenderingDoneSemaphore);
|
||||
acquiredBackBuffer.SubmitCmdBufferFenceCounter = acquiredBackBuffer.SubmitCmdBuffer->GetSubmittedFenceCounter();
|
||||
|
||||
// Present the back buffer to the viewport window
|
||||
const auto result = TryPresent(DoPresent, _device->PresentQueue, true);
|
||||
@@ -630,7 +660,6 @@ void GPUSwapChainVulkan::Present(bool vsync)
|
||||
|
||||
// Rebuild swapchain for the next present
|
||||
int32 width = _width, height = _height;
|
||||
ReleaseGPU();
|
||||
CreateSwapChain(width, height);
|
||||
_device->GetMainContext()->Flush();
|
||||
_device->WaitForGPU();
|
||||
|
||||
@@ -34,6 +34,11 @@ public:
|
||||
/// </summary>
|
||||
CmdBufferVulkan* SubmitCmdBuffer = nullptr;
|
||||
|
||||
/// <summary>
|
||||
/// The fence counter value for SubmitCmdBuffer at the time it was submitted.
|
||||
/// </summary>
|
||||
uint64 SubmitCmdBufferFenceCounter = 0;
|
||||
|
||||
/// <summary>
|
||||
/// The render target surface handle.
|
||||
/// </summary>
|
||||
@@ -41,6 +46,7 @@ public:
|
||||
|
||||
public:
|
||||
void Setup(GPUSwapChainVulkan* window, VkImage backbuffer, PixelFormat format, VkExtent3D extent);
|
||||
void WaitForSubmit();
|
||||
void Release();
|
||||
|
||||
public:
|
||||
@@ -61,6 +67,7 @@ class GPUSwapChainVulkan : public GPUResourceVulkan<GPUSwapChain>, public Resour
|
||||
|
||||
private:
|
||||
VkSurfaceKHR _surface;
|
||||
void* _surfaceWindowHandle;
|
||||
VkSwapchainKHR _swapChain;
|
||||
int32 _currentImageIndex;
|
||||
int32 _semaphoreIndex;
|
||||
@@ -106,7 +113,11 @@ public:
|
||||
|
||||
private:
|
||||
void ReleaseBackBuffer();
|
||||
bool CreateSwapChain(int32 width, int32 height);
|
||||
// Releases swapchain-owned images and synchronization state. Keeps the VkSurfaceKHR alive during
|
||||
// normal resize so the native surface is rebuilt only after surface loss or native window replacement.
|
||||
void ReleaseSwapChain(bool releaseSurface);
|
||||
// Recreates the swapchain for the requested size. Set recreateSurface when the VkSurfaceKHR is no longer valid.
|
||||
bool CreateSwapChain(int32 width, int32 height, bool recreateSurface = false);
|
||||
|
||||
public:
|
||||
// [GPUSwapChain]
|
||||
|
||||
@@ -956,7 +956,7 @@ void AndroidPlatform::Tick()
|
||||
// Pool app events
|
||||
int events;
|
||||
android_poll_source* source;
|
||||
while (ALooper_pollAll(0, nullptr, &events, reinterpret_cast<void**>(&source)) >= 0)
|
||||
while (ALooper_pollOnce(0, nullptr, &events, reinterpret_cast<void**>(&source)) >= 0)
|
||||
{
|
||||
// Process event
|
||||
if (source != nullptr)
|
||||
|
||||
@@ -88,6 +88,20 @@ const char* StringUtils::FindIgnoreCase(const char* str, const char* toFind)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void StringUtils::ConvertUTF162ASCII(const Char* from, char* to, int32 len)
|
||||
{
|
||||
if (!from || !to)
|
||||
return;
|
||||
for (int32 i = 0; i < len; i++)
|
||||
{
|
||||
Char c = from[i];
|
||||
if ((c == 0x09 || c == 0x0A || c == 0x0D || (0x20 <= c && c <= 0x7F)))
|
||||
to[i] = (char)c;
|
||||
else
|
||||
to[i] = ' ';
|
||||
}
|
||||
}
|
||||
|
||||
void PrintUTF8Error(const char* from, uint32 fromLength)
|
||||
{
|
||||
LOG(Error, "Not a UTF-8 string. Length: {0}", fromLength);
|
||||
|
||||
@@ -471,6 +471,13 @@ int32 MacPlatform::CreateProcess(CreateProcessSettings& settings)
|
||||
task.arguments = AppleUtils::ParseArguments(AppleUtils::ToNSString(settings.Arguments));
|
||||
if (settings.WorkingDirectory.HasChars())
|
||||
task.currentDirectoryPath = AppleUtils::ToNSString(settings.WorkingDirectory);
|
||||
[task setStandardInput:[NSFileHandle fileHandleWithNullDevice]];
|
||||
if (!captureStdOut)
|
||||
{
|
||||
NSFileHandle* nullDevice = [NSFileHandle fileHandleWithNullDevice];
|
||||
[task setStandardOutput:nullDevice];
|
||||
[task setStandardError:nullDevice];
|
||||
}
|
||||
|
||||
int32 returnCode = 0;
|
||||
if (settings.WaitForEnd)
|
||||
|
||||
@@ -150,6 +150,9 @@ public:
|
||||
// Converts characters from UTF-16 to ANSI
|
||||
static void ConvertUTF162ANSI(const Char* from, char* to, int32 len);
|
||||
|
||||
// Converts characters from UTF-16 to ASCII
|
||||
static void ConvertUTF162ASCII(const Char* from, char* to, int32 len);
|
||||
|
||||
// Convert characters from UTF-8 to UTF-16
|
||||
static void ConvertUTF82UTF16(const char* from, Char* to, int32 fromLength, int32& toLength);
|
||||
|
||||
|
||||
@@ -350,7 +350,13 @@ MessagePipeline MainThreadPipeline;
|
||||
|
||||
// Create UI thread update callback
|
||||
self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(UIThreadMain)];
|
||||
self.displayLink.preferredFramesPerSecond = 60;
|
||||
const int32 targetFrameRate = 60;
|
||||
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 150000
|
||||
if (@available(iOS 15.0, *))
|
||||
self.displayLink.preferredFrameRateRange = CAFrameRateRangeMake(targetFrameRate, targetFrameRate, targetFrameRate);
|
||||
else
|
||||
#endif
|
||||
self.displayLink.preferredFramesPerSecond = targetFrameRate;
|
||||
[self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
|
||||
|
||||
// Run engine on a separate main thread
|
||||
|
||||
@@ -140,7 +140,9 @@ void MaterialGenerator::ProcessGroupMaterial(Box* box, Node* node, Value& value)
|
||||
|
||||
// Write code
|
||||
_writer.Write(TEXT("{\n"));
|
||||
_writer.Write(*code);
|
||||
_writer.Write(code);
|
||||
if (!code.EndsWith(TEXT("\n")) && !code.EndsWith(TEXT(" ")))
|
||||
_writer.Write(TEXT("\n"));
|
||||
_writer.Write(TEXT("}\n"));
|
||||
|
||||
// Link output values to boxes
|
||||
|
||||
@@ -627,8 +627,9 @@ bool MaterialGenerator::Generate(WriteStream& source, MaterialInfo& materialInfo
|
||||
if (in.Length() > 0)
|
||||
{
|
||||
tmp.EnsureCapacity(in.Length() + 1, false);
|
||||
StringUtils::ConvertUTF162ANSI(*in, tmp.Get(), in.Length());
|
||||
StringUtils::ConvertUTF162ASCII(*in, tmp.Get(), in.Length());
|
||||
source.WriteBytes(tmp.Get(), in.Length());
|
||||
tmp.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1309,10 +1309,10 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option
|
||||
}
|
||||
break;
|
||||
case ModelType::SkinnedModel:
|
||||
if (data.LODs.Count() > 1)
|
||||
if (data.LODs.IsEmpty() || data.LODs[0].Meshes.IsEmpty())
|
||||
{
|
||||
LOG(Warning, "Imported skinned model has more than one LOD. Removing the lower LODs. Only single one is supported.");
|
||||
data.LODs.Resize(1);
|
||||
errorMsg = TEXT("Imported skinned model has no valid geometry.");
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case ModelType::Animation:
|
||||
|
||||
@@ -378,7 +378,7 @@ namespace FlaxEngine.GUI
|
||||
{
|
||||
var leftEdge = selection.StartIndex <= textBlock.Range.StartIndex ? textBlock.Bounds.UpperLeft : GetCharPosition(selection.StartIndex, out _);
|
||||
var rightEdge = selection.EndIndex >= textBlock.Range.EndIndex ? textBlock.Bounds.UpperRight : GetCharPosition(selection.EndIndex, out _);
|
||||
float height = font.Height;
|
||||
float height = font.Height / DpiScale;
|
||||
#if PLATFORM_MAC && !PLATFORM_SDL
|
||||
height /= (float)Platform.Dpi / 96.0f; // TODO: refactor DPI support on macOS to skip such hacks
|
||||
#endif
|
||||
@@ -431,7 +431,7 @@ namespace FlaxEngine.GUI
|
||||
if (textBlock.Style.UnderlineBrush != null)
|
||||
{
|
||||
var underLineHeight = 2.0f;
|
||||
var height = font.Height;
|
||||
var height = font.Height / DpiScale;
|
||||
var underlineRect = new Rectangle(textBlock.Bounds.Location.X, textBlock.Bounds.Location.Y + height - underLineHeight * 0.5f, textBlock.Bounds.Width, underLineHeight);
|
||||
textBlock.Style.UnderlineBrush.Draw(underlineRect, textBlock.Style.Color);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace FlaxEngine.GUI;
|
||||
|
||||
@@ -101,6 +102,7 @@ public class Slider : ContainerControl
|
||||
private Float2 _thumbSize = new Float2(16, 16);
|
||||
private bool _isSliding;
|
||||
private bool _mouseOverThumb;
|
||||
private const float _step = 10;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the value (normalized to range 0-100).
|
||||
@@ -340,6 +342,13 @@ public class Slider : ContainerControl
|
||||
}
|
||||
}
|
||||
|
||||
private void StartSliding()
|
||||
{
|
||||
_isSliding = true;
|
||||
StartMouseCapture();
|
||||
SlidingStart?.Invoke();
|
||||
}
|
||||
|
||||
private void EndSliding()
|
||||
{
|
||||
_isSliding = false;
|
||||
@@ -409,38 +418,84 @@ public class Slider : ContainerControl
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnMouseDown(Float2 location, MouseButton button)
|
||||
public override Control OnNavigate(NavDirection direction, Float2 location, Control caller, List<Control> visited)
|
||||
{
|
||||
if (button == MouseButton.Left)
|
||||
{
|
||||
Focus();
|
||||
float mousePosition = Direction is SliderDirection.HorizontalRight or SliderDirection.HorizontalLeft ? location.X : location.Y;
|
||||
// Auto-focus self when navigation comes in
|
||||
if (!IsNavFocused)
|
||||
return this;
|
||||
|
||||
if (_thumbRect.Contains(ref location))
|
||||
{
|
||||
// Start sliding
|
||||
_isSliding = true;
|
||||
StartMouseCapture();
|
||||
SlidingStart?.Invoke();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Click change
|
||||
switch (Direction)
|
||||
{
|
||||
case SliderDirection.HorizontalRight or SliderDirection.VerticalDown:
|
||||
Value += (mousePosition < _thumbCenter ? -1 : 1) * 10;
|
||||
break;
|
||||
case SliderDirection.HorizontalLeft or SliderDirection.VerticalUp:
|
||||
Value -= (mousePosition < _thumbCenter ? -1 : 1) * 10;
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
// Control slider via navigation actions
|
||||
if (IsNavFocused && _isSliding)
|
||||
{
|
||||
var isNavUp = direction == NavDirection.Right || direction == NavDirection.Up;
|
||||
var isDirUp = _direction == SliderDirection.HorizontalRight || _direction == SliderDirection.VerticalUp;
|
||||
Value += (isNavUp == isDirUp ? 1 : -1) * _step;
|
||||
return this;
|
||||
}
|
||||
|
||||
return base.OnMouseDown(location, button);
|
||||
return base.OnNavigate(direction, location, caller, visited);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnKeyDown(KeyboardKeys key)
|
||||
{
|
||||
if (key == KeyboardKeys.Escape)
|
||||
{
|
||||
Defocus();
|
||||
return true;
|
||||
}
|
||||
|
||||
return base.OnKeyDown(key);
|
||||
}
|
||||
|
||||
private void OnClick(Float2 location)
|
||||
{
|
||||
Focus();
|
||||
float mousePosition = Direction is SliderDirection.HorizontalRight or SliderDirection.HorizontalLeft ? location.X : location.Y;
|
||||
|
||||
if (_thumbRect.Contains(ref location))
|
||||
{
|
||||
StartSliding();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Click change
|
||||
switch (Direction)
|
||||
{
|
||||
case SliderDirection.HorizontalRight or SliderDirection.VerticalDown:
|
||||
Value += (mousePosition < _thumbCenter ? -1 : 1) * _step;
|
||||
break;
|
||||
case SliderDirection.HorizontalLeft or SliderDirection.VerticalUp:
|
||||
Value -= (mousePosition < _thumbCenter ? -1 : 1) * _step;
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnMouseDown(Float2 location, MouseButton button)
|
||||
{
|
||||
if (base.OnMouseDown(location, button))
|
||||
return true;
|
||||
|
||||
if (button == MouseButton.Left)
|
||||
{
|
||||
OnClick(location);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnTouchDown(Float2 location, int pointerId)
|
||||
{
|
||||
if (base.OnTouchDown(location, pointerId))
|
||||
return true;
|
||||
|
||||
OnClick(location);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -474,6 +529,18 @@ public class Slider : ContainerControl
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnKeyUp(KeyboardKeys key)
|
||||
{
|
||||
if (key == KeyboardKeys.Escape && _isSliding)
|
||||
{
|
||||
EndSliding();
|
||||
return;
|
||||
}
|
||||
|
||||
base.OnKeyUp(key);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnMouseUp(Float2 location, MouseButton button)
|
||||
{
|
||||
@@ -486,6 +553,45 @@ public class Slider : ContainerControl
|
||||
return base.OnMouseUp(location, button);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnTouchUp(Float2 location, int pointerId)
|
||||
{
|
||||
if (base.OnTouchUp(location, pointerId))
|
||||
return true;
|
||||
|
||||
if (_isSliding)
|
||||
{
|
||||
EndSliding();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnTouchMove(Float2 location, int pointerId)
|
||||
{
|
||||
base.OnTouchMove(location, pointerId);
|
||||
|
||||
if (_isSliding)
|
||||
{
|
||||
OnMouseMove(location);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnSubmit()
|
||||
{
|
||||
base.OnSubmit();
|
||||
|
||||
if (_isSliding)
|
||||
{
|
||||
EndSliding();
|
||||
}
|
||||
else
|
||||
{
|
||||
StartSliding();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnEndMouseCapture()
|
||||
{
|
||||
|
||||
@@ -66,7 +66,7 @@ namespace FlaxEngine.GUI
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The vertical alignment of the text.
|
||||
/// The horizontal alignment of the text.
|
||||
/// </summary>
|
||||
[EditorDisplay("Text Style"), EditorOrder(2024), Tooltip("The horizontal alignment of the text.")]
|
||||
public TextAlignment HorizontalAlignment
|
||||
@@ -268,7 +268,7 @@ namespace FlaxEngine.GUI
|
||||
if (selectedLinesCount == 1)
|
||||
{
|
||||
// Selected is part of single line
|
||||
Rectangle r1 = new Rectangle(leftEdge.X, leftEdge.Y, rightEdge.X - leftEdge.X, fontHeight);
|
||||
Rectangle r1 = new Rectangle(leftEdge.X, leftEdge.Y, rightEdge.X - leftEdge.X, textHeight);
|
||||
Render2D.FillRectangle(r1, selectionColor);
|
||||
}
|
||||
else
|
||||
@@ -276,17 +276,17 @@ namespace FlaxEngine.GUI
|
||||
float leftMargin = _layout.Bounds.Location.X;
|
||||
|
||||
// Selected is more than one line
|
||||
Rectangle r1 = new Rectangle(leftEdge.X, leftEdge.Y, 1000000000, fontHeight);
|
||||
Rectangle r1 = new Rectangle(leftEdge.X, leftEdge.Y, 1000000000, textHeight);
|
||||
Render2D.FillRectangle(r1, selectionColor);
|
||||
//
|
||||
for (int i = 3; i <= selectedLinesCount; i++)
|
||||
{
|
||||
leftEdge.Y += textHeight;
|
||||
Rectangle r = new Rectangle(leftMargin, leftEdge.Y, 1000000000, fontHeight);
|
||||
Rectangle r = new Rectangle(leftMargin, leftEdge.Y, 1000000000, textHeight);
|
||||
Render2D.FillRectangle(r, selectionColor);
|
||||
}
|
||||
//
|
||||
Rectangle r2 = new Rectangle(leftMargin, rightEdge.Y, rightEdge.X - leftMargin, fontHeight);
|
||||
Rectangle r2 = new Rectangle(leftMargin, rightEdge.Y, rightEdge.X - leftMargin, textHeight);
|
||||
Render2D.FillRectangle(r2, selectionColor);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -563,7 +563,7 @@ namespace FlaxEngine.GUI
|
||||
caretPos.X - (caretWidth * 0.5f),
|
||||
caretPos.Y,
|
||||
caretWidth,
|
||||
height * DpiScale);
|
||||
height);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -162,7 +162,10 @@ namespace FlaxEngine.GUI
|
||||
|
||||
if (_splitterClicked)
|
||||
{
|
||||
SplitterValue = _orientation == Orientation.Horizontal ? location.X / Width : location.Y / Height;
|
||||
if (_orientation == Orientation.Horizontal && Width > 0)
|
||||
SplitterValue = location.X / Width;
|
||||
else if (_orientation == Orientation.Vertical && Height > 0)
|
||||
SplitterValue = location.Y / Height;
|
||||
Cursor = _orientation == Orientation.Horizontal ? CursorType.SizeWE : CursorType.SizeNS;
|
||||
_cursorChanged = true;
|
||||
}
|
||||
|
||||
@@ -295,6 +295,7 @@ ${PBXResourcesGroup}
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = FlaxGame/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = "${ProjectName}";
|
||||
INFOPLIST_KEY_CADisableMinimumFrameDurationOnPhone = YES;
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.games";
|
||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
||||
INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
|
||||
@@ -336,6 +337,7 @@ ${PBXResourcesGroup}
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = FlaxGame/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = "${ProjectName}";
|
||||
INFOPLIST_KEY_CADisableMinimumFrameDurationOnPhone = YES;
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.games";
|
||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
||||
INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
|
||||
|
||||
@@ -2,6 +2,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>CADisableMinimumFrameDurationOnPhone</key>
|
||||
<true/>
|
||||
<key>UIApplicationSceneManifest</key>
|
||||
<dict>
|
||||
<key>UIApplicationSupportsMultipleScenes</key>
|
||||
|
||||
@@ -204,6 +204,8 @@ namespace Flax.Build
|
||||
projectFormats.Add(ProjectFormat.VisualStudio2015);
|
||||
if (Configuration.ProjectFormatVSCode)
|
||||
projectFormats.Add(ProjectFormat.VisualStudioCode);
|
||||
if (Configuration.ProjectFormatCLion)
|
||||
projectFormats.Add(ProjectFormat.CMake);
|
||||
if (Configuration.ProjectFormatRider)
|
||||
projectFormats.Add(ProjectFormat.VisualStudio2026);
|
||||
if (!string.IsNullOrEmpty(Configuration.ProjectFormatCustom))
|
||||
|
||||
@@ -213,6 +213,12 @@ namespace Flax.Build
|
||||
[CommandLine("vscode", "Generates Visual Studio Code project format files. Valid only with -genproject option.")]
|
||||
public static bool ProjectFormatVSCode = false;
|
||||
|
||||
/// <summary>
|
||||
/// Generates CMake facade project files for CLion. Valid only with -genproject option.
|
||||
/// </summary>
|
||||
[CommandLine("clion", "Generates CMake facade project files for CLion. Valid only with -genproject option.")]
|
||||
public static bool ProjectFormatCLion = false;
|
||||
|
||||
/// <summary>
|
||||
/// Generates Visual Studio 2022 project format files for Rider. Valid only with -genproject option.
|
||||
/// </summary>
|
||||
|
||||
@@ -22,6 +22,7 @@ namespace Flax.Build.Platforms
|
||||
{
|
||||
TargetPlatform.Windows,
|
||||
TargetPlatform.Linux,
|
||||
TargetPlatform.Mac,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -22,6 +22,7 @@ namespace Flax.Build.Platforms
|
||||
{
|
||||
TargetPlatform.Windows,
|
||||
TargetPlatform.Linux,
|
||||
TargetPlatform.Mac,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
@@ -106,6 +107,9 @@ namespace Flax.Build.Platforms
|
||||
case TargetPlatform.Linux:
|
||||
hostName = "linux-x86_64";
|
||||
break;
|
||||
case TargetPlatform.Mac:
|
||||
hostName = "darwin-x86_64";
|
||||
break;
|
||||
default: throw new InvalidPlatformException(Platform.BuildPlatform.Target);
|
||||
}
|
||||
return hostName;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Flax.Build.Graph;
|
||||
using Flax.Build.NativeCpp;
|
||||
|
||||
@@ -37,10 +38,20 @@ namespace Flax.Build.Platforms
|
||||
: base(platform, architecture, toolchainRoot, null, string.Empty)
|
||||
{
|
||||
var toolchain = ToolsetRoot.Replace('\\', '/');
|
||||
SystemIncludePaths.Add(Path.Combine(toolchain, "sources/usr/include/c++/v1").Replace('\\', '/'));
|
||||
var cxxIncludePath = Path.Combine(toolchain, "sysroot/usr/include/c++/v1");
|
||||
if (!Directory.Exists(cxxIncludePath))
|
||||
cxxIncludePath = Path.Combine(toolchain, "sources/usr/include/c++/v1");
|
||||
SystemIncludePaths.Add(cxxIncludePath.Replace('\\', '/'));
|
||||
SystemIncludePaths.Add(Path.Combine(toolchain, "sysroot/usr/include").Replace('\\', '/'));
|
||||
SystemIncludePaths.Add(Path.Combine(toolchain, "sysroot/usr/local/include").Replace('\\', '/'));
|
||||
SystemIncludePaths.Add(Path.Combine(toolchain, "lib64/clang/9.0.8/include").Replace('\\', '/'));
|
||||
var clangIncludeRoot = Path.Combine(toolchain, "lib/clang");
|
||||
if (!Directory.Exists(clangIncludeRoot))
|
||||
clangIncludeRoot = Path.Combine(toolchain, "lib64/clang");
|
||||
var clangIncludePath = Directory.Exists(clangIncludeRoot)
|
||||
? Directory.GetDirectories(clangIncludeRoot).OrderBy(x => x).LastOrDefault()
|
||||
: null;
|
||||
if (clangIncludePath != null)
|
||||
SystemIncludePaths.Add(Path.Combine(clangIncludePath, "include").Replace('\\', '/'));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -0,0 +1,624 @@
|
||||
// Copyright (c) Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Flax.Build.NativeCpp;
|
||||
|
||||
namespace Flax.Build.Projects.CMake
|
||||
{
|
||||
/// <summary>
|
||||
/// Project generator for CMake-based IDE facade.
|
||||
/// </summary>
|
||||
public class CMakeProjectGenerator : ProjectGenerator
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override string ProjectFileExtension => "cmake";
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string SolutionFileExtension => "cmake";
|
||||
|
||||
/// <inheritdoc />
|
||||
public override TargetType? Type => null;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void GenerateProject(Project project, string solutionPath, bool isMainProject)
|
||||
{
|
||||
// Not used, solution contains all CMake project definitions.
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void GenerateSolution(Solution solution)
|
||||
{
|
||||
var projectFolder = GetProjectFolder(solution);
|
||||
if (!Directory.Exists(projectFolder))
|
||||
Directory.CreateDirectory(projectFolder);
|
||||
|
||||
var generateCLionFiles = Configuration.ProjectFormatCLion;
|
||||
var contents = new StringBuilder();
|
||||
var buildPresets = new List<CMakeBuildPreset>();
|
||||
var defaultBuildTarget = string.Empty;
|
||||
var targetNames = new HashSet<string>();
|
||||
var buildToolPath = Path.ChangeExtension(typeof(Builder).Assembly.Location, null) + Utilities.GetPlatformExecutableExt();
|
||||
|
||||
contents.AppendLine("cmake_minimum_required(VERSION 3.20)");
|
||||
contents.AppendLine();
|
||||
contents.AppendLine("# Generated by Flax.Build. Do not edit manually.");
|
||||
contents.AppendLine("# CMake is used as an IDE facade. Actual builds are delegated to Flax.Build.");
|
||||
contents.AppendLine($"project({EscapeIdentifier(solution.Name)} LANGUAGES C CXX)");
|
||||
contents.AppendLine();
|
||||
contents.AppendLine("set(CMAKE_EXPORT_COMPILE_COMMANDS ON)");
|
||||
contents.AppendLine($"set(FLAX_WORKSPACE {QuotePath(solution.WorkspaceRootPath, solution.WorkspaceRootPath)})");
|
||||
contents.AppendLine($"set(FLAX_BUILD_TOOL {QuotePath(buildToolPath, solution.WorkspaceRootPath)})");
|
||||
if (DotNetSdk.Instance.IsValid)
|
||||
contents.AppendLine($"set(FLAX_DOTNET_ROOT {QuotePath(DotNetSdk.Instance.RootPath, solution.WorkspaceRootPath)})");
|
||||
contents.AppendLine();
|
||||
|
||||
foreach (var project in solution.Projects.Where(x => x.Type == TargetType.NativeCpp))
|
||||
{
|
||||
var visibleFiles = GetProjectFiles(project, generateCLionFiles);
|
||||
var hasCodeModelConfiguration = TryGetCodeModelConfiguration(project, out var codeModelConfiguration);
|
||||
// Use Flax.Build-selected compile units for the code model. The wider project scan is only for visible files.
|
||||
var compileFiles = hasCodeModelConfiguration
|
||||
? GetCodeModelCompileFiles(codeModelConfiguration, generateCLionFiles)
|
||||
: visibleFiles.Where(x => IsCompileSourceFile(x, generateCLionFiles)).ToArray();
|
||||
var files = new HashSet<string>(visibleFiles);
|
||||
files.AddRange(compileFiles);
|
||||
var allFiles = files.OrderBy(x => x).ToArray();
|
||||
var compileFileSet = new HashSet<string>(compileFiles);
|
||||
var nonCompileFiles = allFiles.Where(x => !compileFileSet.Contains(x)).ToArray();
|
||||
var includePaths = hasCodeModelConfiguration ? GetIncludePaths(project, codeModelConfiguration) : Array.Empty<string>();
|
||||
var definitions = hasCodeModelConfiguration ? GetPreprocessorDefinitions(project, codeModelConfiguration) : project.Defines.OrderBy(x => x).ToArray();
|
||||
var cppStandard = hasCodeModelConfiguration ? GetCppStandard(codeModelConfiguration) : 14;
|
||||
var ideTargetName = GetUniqueTargetName(targetNames, "IDE_" + project.Name);
|
||||
|
||||
contents.AppendLine($"# {project.Name}");
|
||||
if (compileFiles.Length != 0)
|
||||
{
|
||||
contents.AppendLine($"add_library({ideTargetName} STATIC EXCLUDE_FROM_ALL)");
|
||||
contents.AppendLine($"set_target_properties({ideTargetName} PROPERTIES EXCLUDE_FROM_ALL TRUE)");
|
||||
AppendTargetSources(contents, ideTargetName, allFiles, solution.WorkspaceRootPath);
|
||||
if (nonCompileFiles.Length != 0)
|
||||
{
|
||||
contents.AppendLine("set_source_files_properties(");
|
||||
foreach (var file in nonCompileFiles)
|
||||
contents.AppendLine($" {QuotePath(file, solution.WorkspaceRootPath)}");
|
||||
contents.AppendLine(" PROPERTIES HEADER_FILE_ONLY TRUE");
|
||||
contents.AppendLine(")");
|
||||
}
|
||||
contents.AppendLine($"set_target_properties({ideTargetName} PROPERTIES CXX_STANDARD {cppStandard} CXX_STANDARD_REQUIRED YES)");
|
||||
AppendTargetPaths(contents, ideTargetName, "target_include_directories", "PRIVATE", includePaths, solution.WorkspaceRootPath);
|
||||
AppendTargetValues(contents, ideTargetName, "target_compile_definitions", "PRIVATE", definitions);
|
||||
}
|
||||
else if (allFiles.Length != 0)
|
||||
{
|
||||
contents.AppendLine($"add_custom_target({ideTargetName} SOURCES");
|
||||
foreach (var file in allFiles)
|
||||
contents.AppendLine($" {QuotePath(file, solution.WorkspaceRootPath)}");
|
||||
contents.AppendLine(")");
|
||||
}
|
||||
|
||||
foreach (var configuration in project.Configurations)
|
||||
{
|
||||
if (generateCLionFiles && !IsCLionDevelopmentPlatform(configuration.Platform))
|
||||
continue;
|
||||
|
||||
var buildTargetName = GetUniqueTargetName(targetNames, "Build_" + project.Name + "_" + configuration.Target.Name + "_" + configuration.PlatformName + "_" + configuration.ConfigurationName + "_" + configuration.ArchitectureName);
|
||||
|
||||
AppendBuildTarget(contents, buildTargetName, "-build", configuration);
|
||||
var presetSuffix = project.Name + "-" + configuration.Target.Name + "-" + configuration.PlatformName + "-" + configuration.ConfigurationName + "-" + configuration.ArchitectureName;
|
||||
if (IsHostConfiguration(configuration))
|
||||
AddBuildPreset(buildPresets, "build-" + presetSuffix, "Build " + presetSuffix, buildTargetName);
|
||||
|
||||
if (defaultBuildTarget.Length == 0 && project == solution.MainProject && configuration.Platform == Platform.BuildPlatform.Target && configuration.Architecture == Platform.BuildTargetArchitecture && configuration.Configuration == TargetConfiguration.Development)
|
||||
defaultBuildTarget = buildTargetName;
|
||||
|
||||
if (generateCLionFiles)
|
||||
{
|
||||
var runProgram = GetRunProgram(project, configuration, out var runArguments);
|
||||
if (IsHostConfiguration(configuration) && !string.IsNullOrEmpty(runProgram))
|
||||
{
|
||||
var runTargetName = GetUniqueTargetName(targetNames, "App_" + project.Name + "_" + configuration.Target.Name + "_" + configuration.PlatformName + "_" + configuration.ConfigurationName + "_" + configuration.ArchitectureName);
|
||||
AppendRunTarget(contents, runTargetName, runProgram, runArguments);
|
||||
contents.AppendLine($"add_dependencies({runTargetName} {buildTargetName})");
|
||||
contents.AppendLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
contents.AppendLine();
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(defaultBuildTarget))
|
||||
{
|
||||
contents.AppendLine($"add_custom_target({EscapeIdentifier(solution.Name)}_Build ALL DEPENDS {defaultBuildTarget})");
|
||||
contents.AppendLine();
|
||||
}
|
||||
|
||||
Utilities.WriteFileIfChanged(Path.Combine(projectFolder, "CMakeLists.txt"), contents.ToString());
|
||||
GeneratePresets(solution, projectFolder, buildPresets);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the directory that contains the generated CMake facade project.
|
||||
/// </summary>
|
||||
public static string GetProjectFolder(Solution solution)
|
||||
{
|
||||
return Path.Combine(solution.WorkspaceRootPath, "Cache", "Projects", "CMake", solution.Name);
|
||||
}
|
||||
|
||||
private struct CMakeBuildPreset
|
||||
{
|
||||
public string Name;
|
||||
public string DisplayName;
|
||||
public string Target;
|
||||
}
|
||||
|
||||
private static void AddBuildPreset(List<CMakeBuildPreset> presets, string name, string displayName, string target)
|
||||
{
|
||||
presets.Add(new CMakeBuildPreset
|
||||
{
|
||||
Name = EscapePresetName(name),
|
||||
DisplayName = displayName,
|
||||
Target = target,
|
||||
});
|
||||
}
|
||||
|
||||
private static void GeneratePresets(Solution solution, string projectFolder, List<CMakeBuildPreset> buildPresets)
|
||||
{
|
||||
var contents = new StringBuilder();
|
||||
contents.AppendLine("{");
|
||||
contents.AppendLine(" \"version\": 3,");
|
||||
contents.AppendLine(" \"cmakeMinimumRequired\": {");
|
||||
contents.AppendLine(" \"major\": 3,");
|
||||
contents.AppendLine(" \"minor\": 20,");
|
||||
contents.AppendLine(" \"patch\": 0");
|
||||
contents.AppendLine(" },");
|
||||
contents.AppendLine(" \"configurePresets\": [");
|
||||
contents.AppendLine(" {");
|
||||
contents.AppendLine(" \"name\": \"flax\",");
|
||||
contents.AppendLine(" \"displayName\": \"Flax\",");
|
||||
contents.AppendLine(" \"description\": \"Configure Flax CMake facade for IDE integration.\",");
|
||||
contents.AppendLine(" \"generator\": \"Ninja\",");
|
||||
contents.AppendLine(" \"binaryDir\": \"${sourceDir}/cmake-build-flax\",");
|
||||
contents.AppendLine(" \"cacheVariables\": {");
|
||||
contents.AppendLine(" \"CMAKE_BUILD_TYPE\": \"Debug\",");
|
||||
contents.AppendLine(" \"CMAKE_EXPORT_COMPILE_COMMANDS\": \"ON\"");
|
||||
contents.AppendLine(" }");
|
||||
contents.AppendLine(" }");
|
||||
contents.AppendLine(" ],");
|
||||
contents.AppendLine(" \"buildPresets\": [");
|
||||
for (var i = 0; i < buildPresets.Count; i++)
|
||||
{
|
||||
var preset = buildPresets[i];
|
||||
contents.AppendLine(" {");
|
||||
contents.AppendLine($" \"name\": {JsonString(preset.Name)},");
|
||||
contents.AppendLine($" \"displayName\": {JsonString(preset.DisplayName)},");
|
||||
contents.AppendLine(" \"configurePreset\": \"flax\",");
|
||||
contents.AppendLine($" \"targets\": [{JsonString(preset.Target)}]");
|
||||
contents.Append(" }");
|
||||
if (i != buildPresets.Count - 1)
|
||||
contents.Append(',');
|
||||
contents.AppendLine();
|
||||
}
|
||||
contents.AppendLine(" ]");
|
||||
contents.AppendLine("}");
|
||||
|
||||
Utilities.WriteFileIfChanged(Path.Combine(projectFolder, "CMakePresets.json"), contents.ToString());
|
||||
}
|
||||
|
||||
private static void AppendBuildTarget(StringBuilder contents, string targetName, string action, Project.ConfigurationData configuration)
|
||||
{
|
||||
contents.AppendLine($"add_custom_target({targetName}");
|
||||
contents.Append(" COMMAND ");
|
||||
if (DotNetSdk.Instance.IsValid)
|
||||
contents.Append("${CMAKE_COMMAND} -E env \"DOTNET_ROOT=${FLAX_DOTNET_ROOT}\" ");
|
||||
contents.AppendLine($"\"${{FLAX_BUILD_TOOL}}\" {Quote(action)} \"-log\" \"-mutex\"");
|
||||
contents.AppendLine(" \"-workspace=${FLAX_WORKSPACE}\"");
|
||||
contents.AppendLine($" \"-arch={configuration.ArchitectureName}\"");
|
||||
contents.AppendLine($" \"-configuration={configuration.ConfigurationName}\"");
|
||||
contents.AppendLine($" \"-platform={configuration.PlatformName}\"");
|
||||
contents.AppendLine($" \"-buildTargets={configuration.Target.Name}\"");
|
||||
if (!string.IsNullOrEmpty(Configuration.Compiler))
|
||||
contents.AppendLine($" \"-compiler={EscapeString(Configuration.Compiler)}\"");
|
||||
if (!string.IsNullOrEmpty(Configuration.Dotnet))
|
||||
contents.AppendLine($" \"-dotnet={EscapeString(Configuration.Dotnet)}\"");
|
||||
if (Configuration.Sanitizers != Sanitizer.None)
|
||||
contents.AppendLine($" \"-sanitizers={Configuration.Sanitizers}\"");
|
||||
contents.AppendLine(" WORKING_DIRECTORY \"${FLAX_WORKSPACE}\"");
|
||||
contents.AppendLine(" USES_TERMINAL");
|
||||
contents.AppendLine(" VERBATIM");
|
||||
contents.AppendLine(")");
|
||||
contents.AppendLine();
|
||||
}
|
||||
|
||||
private static void AppendRunTarget(StringBuilder contents, string targetName, string program, string[] arguments)
|
||||
{
|
||||
var sourcePath = $"${{CMAKE_CURRENT_BINARY_DIR}}/{targetName}.cpp";
|
||||
contents.AppendLine($"file(WRITE \"{sourcePath}\" [=[");
|
||||
contents.AppendLine("#include <cstdio>");
|
||||
contents.AppendLine("#include <vector>");
|
||||
contents.AppendLine("#ifdef _WIN32");
|
||||
contents.AppendLine("#include <process.h>");
|
||||
contents.AppendLine("#else");
|
||||
contents.AppendLine("#include <unistd.h>");
|
||||
contents.AppendLine("#endif");
|
||||
contents.AppendLine("int main(int argc, char** argv) {");
|
||||
contents.AppendLine($" const char* program = \"{CppString(program)}\";");
|
||||
contents.AppendLine(" std::vector<const char*> args;");
|
||||
contents.AppendLine(" args.reserve(argc + 1);");
|
||||
contents.AppendLine(" args.push_back(program);");
|
||||
foreach (var arg in arguments)
|
||||
contents.AppendLine($" args.push_back(\"{CppString(arg)}\");");
|
||||
contents.AppendLine(" for (int i = 1; i < argc; i++)");
|
||||
contents.AppendLine(" args.push_back(argv[i]);");
|
||||
contents.AppendLine(" args.push_back(nullptr);");
|
||||
contents.AppendLine("#ifdef _WIN32");
|
||||
contents.AppendLine(" _execv(args[0], args.data());");
|
||||
contents.AppendLine("#else");
|
||||
contents.AppendLine(" execv(args[0], const_cast<char* const*>(args.data()));");
|
||||
contents.AppendLine("#endif");
|
||||
contents.AppendLine(" perror(args[0]);");
|
||||
contents.AppendLine(" return 1;");
|
||||
contents.AppendLine("}");
|
||||
contents.AppendLine("]=])");
|
||||
contents.AppendLine($"add_executable({targetName} EXCLUDE_FROM_ALL \"{sourcePath}\")");
|
||||
contents.AppendLine($"set_target_properties({targetName} PROPERTIES");
|
||||
contents.AppendLine(" RUNTIME_OUTPUT_DIRECTORY \"${CMAKE_CURRENT_BINARY_DIR}/FlaxLaunchers\"");
|
||||
contents.AppendLine(")");
|
||||
}
|
||||
|
||||
private static void AppendTargetSources(StringBuilder contents, string targetName, string[] files, string workspaceRoot)
|
||||
{
|
||||
contents.AppendLine($"target_sources({targetName} PRIVATE");
|
||||
foreach (var file in files)
|
||||
contents.AppendLine($" {QuotePath(file, workspaceRoot)}");
|
||||
contents.AppendLine(")");
|
||||
}
|
||||
|
||||
private static void AppendTargetPaths(StringBuilder contents, string targetName, string command, string visibility, string[] values, string workspaceRoot)
|
||||
{
|
||||
if (values.Length == 0)
|
||||
return;
|
||||
|
||||
contents.AppendLine($"{command}({targetName} {visibility}");
|
||||
foreach (var value in values)
|
||||
contents.AppendLine($" {QuotePath(value, workspaceRoot)}");
|
||||
contents.AppendLine(")");
|
||||
}
|
||||
|
||||
private static void AppendTargetValues(StringBuilder contents, string targetName, string command, string visibility, string[] values)
|
||||
{
|
||||
if (values.Length == 0)
|
||||
return;
|
||||
|
||||
contents.AppendLine($"{command}({targetName} {visibility}");
|
||||
foreach (var value in values)
|
||||
contents.AppendLine($" {Quote(value)}");
|
||||
contents.AppendLine(")");
|
||||
}
|
||||
|
||||
private static string[] GetProjectFiles(Project project, bool generateCLionFiles)
|
||||
{
|
||||
var files = new HashSet<string>();
|
||||
if (project.SourceFiles != null)
|
||||
files.AddRange(project.SourceFiles);
|
||||
if (project.GeneratedSourceFiles != null)
|
||||
files.AddRange(project.GeneratedSourceFiles);
|
||||
if (project.SourceDirectories != null)
|
||||
{
|
||||
foreach (var folder in project.SourceDirectories)
|
||||
{
|
||||
if (Directory.Exists(folder))
|
||||
files.AddRange(Directory.GetFiles(folder, "*", SearchOption.AllDirectories));
|
||||
}
|
||||
}
|
||||
files.RemoveWhere(x =>
|
||||
!IsProjectFile(x) ||
|
||||
Path.GetFileName(x).Equals(".DS_Store", StringComparison.OrdinalIgnoreCase) ||
|
||||
(generateCLionFiles && !IsCLionDevelopmentSourceFile(x)));
|
||||
return files.OrderBy(x => x).ToArray();
|
||||
}
|
||||
|
||||
private static string[] GetCodeModelCompileFiles(Project.ConfigurationData configuration, bool generateCLionFiles)
|
||||
{
|
||||
var files = new HashSet<string>();
|
||||
var definitions = new HashSet<string>(configuration.TargetBuildOptions.CompileEnv.PreprocessorDefinitions);
|
||||
AddCompileFiles(files, configuration.TargetBuildOptions.SourceFiles, generateCLionFiles, definitions);
|
||||
if (configuration.Modules != null)
|
||||
{
|
||||
foreach (var module in configuration.Modules)
|
||||
{
|
||||
if (module.Key.BuildNativeCode)
|
||||
AddCompileFiles(files, module.Value.SourceFiles, generateCLionFiles, definitions);
|
||||
}
|
||||
}
|
||||
return files.OrderBy(x => x).ToArray();
|
||||
}
|
||||
|
||||
private static void AddCompileFiles(HashSet<string> files, IEnumerable<string> sourceFiles, bool generateCLionFiles, HashSet<string> definitions = null)
|
||||
{
|
||||
if (sourceFiles == null)
|
||||
return;
|
||||
|
||||
foreach (var file in sourceFiles)
|
||||
{
|
||||
if (IsCompileSourceFile(file, generateCLionFiles, definitions))
|
||||
files.Add(file);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TryGetCodeModelConfiguration(Project project, out Project.ConfigurationData configuration)
|
||||
{
|
||||
foreach (var e in project.Configurations)
|
||||
{
|
||||
if (e.TargetBuildOptions != null &&
|
||||
e.Platform == Platform.BuildPlatform.Target &&
|
||||
e.Architecture == Platform.BuildTargetArchitecture &&
|
||||
e.Configuration == TargetConfiguration.Development)
|
||||
{
|
||||
configuration = e;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var e in project.Configurations)
|
||||
{
|
||||
if (e.TargetBuildOptions != null)
|
||||
{
|
||||
configuration = e;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
configuration = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool IsHostConfiguration(Project.ConfigurationData configuration)
|
||||
{
|
||||
return configuration.Platform == Platform.BuildPlatform.Target &&
|
||||
configuration.Architecture == Platform.BuildTargetArchitecture;
|
||||
}
|
||||
|
||||
private static bool IsCLionDevelopmentPlatform(TargetPlatform platform)
|
||||
{
|
||||
switch (platform)
|
||||
{
|
||||
case TargetPlatform.Windows:
|
||||
case TargetPlatform.Linux:
|
||||
case TargetPlatform.Mac:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static string[] GetIncludePaths(Project project, Project.ConfigurationData configuration)
|
||||
{
|
||||
var result = new HashSet<string>();
|
||||
if (project.SearchPaths != null)
|
||||
result.AddRange(project.SearchPaths);
|
||||
result.AddRange(configuration.TargetBuildOptions.CompileEnv.IncludePaths);
|
||||
return result.OrderBy(x => x).ToArray();
|
||||
}
|
||||
|
||||
private static string[] GetPreprocessorDefinitions(Project project, Project.ConfigurationData configuration)
|
||||
{
|
||||
var result = new HashSet<string>();
|
||||
result.AddRange(project.Defines);
|
||||
result.AddRange(configuration.TargetBuildOptions.CompileEnv.PreprocessorDefinitions);
|
||||
return result.OrderBy(x => x).ToArray();
|
||||
}
|
||||
|
||||
private static int GetCppStandard(Project.ConfigurationData configuration)
|
||||
{
|
||||
var result = 14;
|
||||
switch (configuration.TargetBuildOptions.CompileEnv.CppVersion)
|
||||
{
|
||||
case CppVersion.Cpp17:
|
||||
result = Math.Max(result, 17);
|
||||
break;
|
||||
case CppVersion.Cpp20:
|
||||
case CppVersion.Latest:
|
||||
result = Math.Max(result, 20);
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static string GetRunProgram(Project project, Project.ConfigurationData configuration, out string[] arguments)
|
||||
{
|
||||
var target = configuration.Target;
|
||||
var outputType = project.OutputType ?? target.OutputType;
|
||||
if (ShouldRunEditorForProject(configuration, outputType))
|
||||
{
|
||||
var program = Path.Combine(Globals.EngineRoot, Platform.GetEditorBinaryDirectory(), configuration.ConfigurationName, "FlaxEditor" + Utilities.GetPlatformExecutableExt());
|
||||
var args = new List<string>();
|
||||
if (configuration.Platform == TargetPlatform.Linux || configuration.Platform == TargetPlatform.Mac)
|
||||
args.Add("-std");
|
||||
args.Add("-project");
|
||||
args.Add(project.WorkspaceRootPath);
|
||||
args.Add("-skipCompile");
|
||||
arguments = args.ToArray();
|
||||
return program;
|
||||
}
|
||||
|
||||
arguments = configuration.Platform == TargetPlatform.Linux || configuration.Platform == TargetPlatform.Mac ? new[] { "--std" } : Array.Empty<string>();
|
||||
return target.GetOutputFilePath(configuration.TargetBuildOptions, TargetOutputType.Executable);
|
||||
}
|
||||
|
||||
private static bool ShouldRunEditorForProject(Project.ConfigurationData configuration, TargetOutputType outputType)
|
||||
{
|
||||
return outputType != TargetOutputType.Executable && configuration.Target.IsEditor;
|
||||
}
|
||||
|
||||
private static bool IsCppCompileFile(string path)
|
||||
{
|
||||
var extension = Path.GetExtension(path);
|
||||
return extension.Equals(".c", StringComparison.OrdinalIgnoreCase) ||
|
||||
extension.Equals(".cc", StringComparison.OrdinalIgnoreCase) ||
|
||||
extension.Equals(".cpp", StringComparison.OrdinalIgnoreCase) ||
|
||||
extension.Equals(".cxx", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private static bool IsCompileSourceFile(string path, bool generateCLionFiles, HashSet<string> definitions = null)
|
||||
{
|
||||
return IsCppCompileFile(path) &&
|
||||
(!generateCLionFiles || (IsCLionDevelopmentSourceFile(path) && IsCLionSourceGuardActive(path, definitions)));
|
||||
}
|
||||
|
||||
private static bool IsCLionDevelopmentSourceFile(string path)
|
||||
{
|
||||
path = Utilities.NormalizePath(path);
|
||||
return !ContainsPathSegment(path, "Android") &&
|
||||
!ContainsPathSegment(path, "AndroidJNI") &&
|
||||
!ContainsPathSegment(path, "GDK") &&
|
||||
!ContainsPathSegment(path, "iOS") &&
|
||||
!ContainsPathSegment(path, "PS4") &&
|
||||
!ContainsPathSegment(path, "PS5") &&
|
||||
!ContainsPathSegment(path, "Switch") &&
|
||||
!ContainsPathSegment(path, "UWP") &&
|
||||
!ContainsPathSegment(path, "Web") &&
|
||||
!ContainsPathSegment(path, "XboxOne") &&
|
||||
!ContainsPathSegment(path, "XboxScarlett") &&
|
||||
!Path.GetFileName(path).Contains(".Web.", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private static bool ContainsPathSegment(string path, string segment)
|
||||
{
|
||||
return path.IndexOf("/" + segment + "/", StringComparison.OrdinalIgnoreCase) != -1;
|
||||
}
|
||||
|
||||
private static bool IsCLionSourceGuardActive(string path, HashSet<string> definitions)
|
||||
{
|
||||
if (definitions == null)
|
||||
return true;
|
||||
|
||||
path = Utilities.NormalizePath(path);
|
||||
if (ContainsSourcePath(path, "Source/Editor/Cooker/Platform/Windows"))
|
||||
return HasDefinition(definitions, "PLATFORM_TOOLS_WINDOWS");
|
||||
if (ContainsSourcePath(path, "Source/Editor/Cooker/Platform/Linux"))
|
||||
return HasDefinition(definitions, "PLATFORM_TOOLS_LINUX");
|
||||
if (ContainsSourcePath(path, "Source/Editor/Cooker/Platform/Mac"))
|
||||
return HasDefinition(definitions, "PLATFORM_TOOLS_MAC");
|
||||
|
||||
if (ContainsSourcePath(path, "Source/Engine/Engine/Windows"))
|
||||
return HasDefinition(definitions, "PLATFORM_WINDOWS") && !HasDefinition(definitions, "USE_EDITOR");
|
||||
if (ContainsSourcePath(path, "Source/Engine/Engine/Linux"))
|
||||
return HasDefinition(definitions, "PLATFORM_LINUX") && !HasDefinition(definitions, "USE_EDITOR");
|
||||
if (ContainsSourcePath(path, "Source/Engine/Engine/Mac"))
|
||||
return HasDefinition(definitions, "PLATFORM_MAC") && !HasDefinition(definitions, "USE_EDITOR");
|
||||
|
||||
if (ContainsSourcePath(path, "Source/Engine/GraphicsDevice/Vulkan/Win32"))
|
||||
return HasDefinition(definitions, "GRAPHICS_API_VULKAN") && (HasDefinition(definitions, "PLATFORM_WIN32") || HasDefinition(definitions, "PLATFORM_WINDOWS"));
|
||||
if (ContainsSourcePath(path, "Source/Engine/GraphicsDevice/Vulkan/Linux"))
|
||||
return HasDefinition(definitions, "GRAPHICS_API_VULKAN") && HasDefinition(definitions, "PLATFORM_LINUX");
|
||||
if (ContainsSourcePath(path, "Source/Engine/GraphicsDevice/Vulkan/Mac"))
|
||||
return HasDefinition(definitions, "GRAPHICS_API_VULKAN") && HasDefinition(definitions, "PLATFORM_MAC");
|
||||
|
||||
if (ContainsSourcePath(path, "Source/Engine/Platform/Win32"))
|
||||
return HasDefinition(definitions, "PLATFORM_WIN32") || HasDefinition(definitions, "PLATFORM_WINDOWS");
|
||||
if (ContainsSourcePath(path, "Source/Engine/Platform/Windows"))
|
||||
return HasDefinition(definitions, "PLATFORM_WINDOWS");
|
||||
if (ContainsSourcePath(path, "Source/Engine/Platform/Linux"))
|
||||
return HasDefinition(definitions, "PLATFORM_LINUX");
|
||||
if (ContainsSourcePath(path, "Source/Engine/Platform/Mac"))
|
||||
return HasDefinition(definitions, "PLATFORM_MAC");
|
||||
if (ContainsSourcePath(path, "Source/Engine/Platform/Apple"))
|
||||
return HasDefinition(definitions, "PLATFORM_MAC") || HasDefinition(definitions, "PLATFORM_IOS");
|
||||
if (ContainsSourcePath(path, "Source/Engine/Platform/Unix"))
|
||||
return HasDefinition(definitions, "PLATFORM_UNIX");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool ContainsSourcePath(string path, string sourcePath)
|
||||
{
|
||||
return path.IndexOf("/" + sourcePath + "/", StringComparison.OrdinalIgnoreCase) != -1;
|
||||
}
|
||||
|
||||
private static bool HasDefinition(HashSet<string> definitions, string name)
|
||||
{
|
||||
return definitions.Contains(name) ||
|
||||
definitions.Any(x => x.StartsWith(name + "=", StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
private static bool IsProjectFile(string path)
|
||||
{
|
||||
var extension = Path.GetExtension(path);
|
||||
return IsCppCompileFile(path) ||
|
||||
extension.Equals(".h", StringComparison.OrdinalIgnoreCase) ||
|
||||
extension.Equals(".hh", StringComparison.OrdinalIgnoreCase) ||
|
||||
extension.Equals(".hpp", StringComparison.OrdinalIgnoreCase) ||
|
||||
extension.Equals(".hxx", StringComparison.OrdinalIgnoreCase) ||
|
||||
extension.Equals(".inl", StringComparison.OrdinalIgnoreCase) ||
|
||||
extension.Equals(".rc", StringComparison.OrdinalIgnoreCase) ||
|
||||
extension.Equals(".manifest", StringComparison.OrdinalIgnoreCase) ||
|
||||
extension.Equals(".natvis", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private static string GetUniqueTargetName(HashSet<string> names, string name)
|
||||
{
|
||||
var result = EscapeIdentifier(name);
|
||||
var baseName = result;
|
||||
var index = 2;
|
||||
while (!names.Add(result))
|
||||
result = baseName + "_" + index++;
|
||||
return result;
|
||||
}
|
||||
|
||||
private static string EscapeIdentifier(string value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value))
|
||||
return "_";
|
||||
|
||||
var result = new StringBuilder(value.Length);
|
||||
foreach (var c in value)
|
||||
result.Append(char.IsLetterOrDigit(c) || c == '_' ? c : '_');
|
||||
if (char.IsDigit(result[0]))
|
||||
result.Insert(0, '_');
|
||||
return result.ToString();
|
||||
}
|
||||
|
||||
private static string EscapePresetName(string value)
|
||||
{
|
||||
var result = new StringBuilder(value.Length);
|
||||
foreach (var c in value)
|
||||
result.Append(char.IsLetterOrDigit(c) || c == '-' || c == '_' || c == '.' ? char.ToLowerInvariant(c) : '-');
|
||||
return result.ToString();
|
||||
}
|
||||
|
||||
private static string QuotePath(string value, string workspaceRoot, bool includeQuotes = true)
|
||||
{
|
||||
value = Utilities.NormalizePath(value);
|
||||
workspaceRoot = Utilities.NormalizePath(workspaceRoot).TrimEnd('/');
|
||||
if (value.StartsWith(workspaceRoot + "/", StringComparison.Ordinal))
|
||||
value = "${FLAX_WORKSPACE}/" + value.Substring(workspaceRoot.Length + 1);
|
||||
return Quote(value, includeQuotes);
|
||||
}
|
||||
|
||||
private static string Quote(string value, bool includeQuotes = true)
|
||||
{
|
||||
value = EscapeString(value);
|
||||
return includeQuotes ? "\"" + value + "\"" : value;
|
||||
}
|
||||
|
||||
private static string EscapeString(string value)
|
||||
{
|
||||
return value.Replace("\\", "\\\\").Replace(";", "\\;").Replace("\"", "\\\"");
|
||||
}
|
||||
|
||||
private static string JsonString(string value)
|
||||
{
|
||||
return "\"" + value.Replace("\\", "\\\\").Replace("\"", "\\\"") + "\"";
|
||||
}
|
||||
|
||||
private static string CppString(string value)
|
||||
{
|
||||
return value.Replace("\\", "\\\\").Replace("\"", "\\\"").Replace("\n", "\\n").Replace("\r", "\\r");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -47,6 +47,11 @@ namespace Flax.Build.Projects
|
||||
/// </summary>
|
||||
VisualStudioCode,
|
||||
|
||||
/// <summary>
|
||||
/// CMake.
|
||||
/// </summary>
|
||||
CMake,
|
||||
|
||||
/// <summary>
|
||||
/// XCode.
|
||||
/// </summary>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Flax.Build.Projects.CMake;
|
||||
using Flax.Build.Projects.VisualStudio;
|
||||
using Flax.Build.Projects.VisualStudioCode;
|
||||
|
||||
@@ -150,6 +151,7 @@ namespace Flax.Build.Projects
|
||||
case ProjectFormat.VisualStudioCode: return type == TargetType.DotNet
|
||||
? (ProjectGenerator)new CSProjectGenerator(VisualStudioVersion.VisualStudio2015)
|
||||
: (ProjectGenerator)new VisualStudioCodeProjectGenerator();
|
||||
case ProjectFormat.CMake: return new CMakeProjectGenerator();
|
||||
case ProjectFormat.XCode: return new XCodeProjectGenerator();
|
||||
case ProjectFormat.Custom:
|
||||
if (CustomProjectTypes.TryGetValue(Configuration.ProjectFormatCustom, out var factory))
|
||||
|
||||
Reference in New Issue
Block a user