Compare commits
177 Commits
zed_editor
...
master_fix
| Author | SHA1 | Date | |
|---|---|---|---|
| 824ff7050e | |||
| 042843fe42 | |||
| 8631b389c1 | |||
| e3f5af530b | |||
|
|
e257f9e4a0 | ||
|
|
056de752ed | ||
|
|
76700c0b24 | ||
|
|
9fdcff657d | ||
|
|
2b6339c05c | ||
|
|
bb91202439 | ||
|
|
f25e9f262a | ||
|
|
ee51077f49 | ||
|
|
950e958a58 | ||
|
|
5fdbed2b56 | ||
|
|
0e627577fc | ||
|
|
4846d4b024 | ||
|
|
5e5293bf7b | ||
|
|
d88477dcae | ||
|
|
bd58bd91b4 | ||
|
|
7ce0d88bdc | ||
|
|
98bb2d40d6 | ||
|
|
f4bc620bbd | ||
|
|
0313bf32c9 | ||
|
|
0c887cd29e | ||
|
|
5bd9bce634 | ||
|
|
2a53d0a462 | ||
| 82bd915274 | |||
|
|
71391cf1cc | ||
|
|
b5286af526 | ||
|
|
9f07a2a54e | ||
|
|
c39c642b60 | ||
|
|
02cff3973a | ||
|
|
a63b97d31d | ||
|
|
ca52122656 | ||
|
|
20a7fcf6a0 | ||
|
|
43665aa7eb | ||
|
|
3b9b49950c | ||
|
|
0a8752ec0a | ||
|
|
47685dc2be | ||
|
|
517ee5bb25 | ||
|
|
3ab01d3576 | ||
|
|
31b6d4d658 | ||
|
|
08f840d642 | ||
|
|
776b6259cd | ||
|
|
5c81c71116 | ||
|
|
188b635ea0 | ||
|
|
56066a3212 | ||
|
|
ed50ce9c90 | ||
|
|
a7e77f6e21 | ||
|
|
56278b17ee | ||
|
|
bd78db72b9 | ||
|
|
32bd72fecd | ||
|
|
3a798a70fa | ||
|
|
02429266b1 | ||
|
|
77aea0c69c | ||
|
|
6a3ce862cb | ||
|
|
93217da619 | ||
|
|
63def54dad | ||
|
|
00f9a28729 | ||
|
|
56beca0db4 | ||
|
|
64cd898a65 | ||
|
|
90472a4b31 | ||
|
|
a1999183f2 | ||
|
|
1e3ce48024 | ||
|
|
0007185b5f | ||
|
|
403d2cedc0 | ||
|
|
c8839b8587 | ||
|
|
cf048c9804 | ||
|
|
bea75f51bd | ||
|
|
1bf6612002 | ||
|
|
d9a18b1d31 | ||
|
|
465f30661f | ||
|
|
a62ca5452e | ||
|
|
92254eefcc | ||
|
|
2d56411e5f | ||
|
|
f8dc8ab903 | ||
|
|
2a55cda583 | ||
|
|
7c91c03adf | ||
|
|
caa902ea9b | ||
|
|
fb07071e24 | ||
|
|
a1cb7dcbe7 | ||
|
|
032f698c7b | ||
|
|
e2aaef9b88 | ||
|
|
ee0303bcfb | ||
|
|
1523c7b4ce | ||
|
|
a16a8eaded | ||
|
|
9ff9d48ffd | ||
|
|
dfb1fb91a5 | ||
|
|
8f56ab9534 | ||
|
|
c0dda45c7b | ||
|
|
3efb981f00 | ||
|
|
ed408917c6 | ||
|
|
2ca435a414 | ||
|
|
de9e282bad | ||
|
|
c437daf9be | ||
|
|
1e4f96486f | ||
|
|
e03d0f3322 | ||
|
|
5f0e1253cc | ||
|
|
7e6b040258 | ||
|
|
329ebb6482 | ||
|
|
7a9c58003d | ||
|
|
371a16e37b | ||
|
|
95629e792d | ||
|
|
e79af2fd60 | ||
|
|
4aba0153f8 | ||
|
|
f91c33e17c | ||
|
|
6c29877b20 | ||
|
|
3abde6ecfc | ||
|
|
b42168eee5 | ||
|
|
ac3b2c0ef2 | ||
|
|
f640452b7b | ||
|
|
2f670495ac | ||
|
|
2a36edf528 | ||
|
|
5e690abd76 | ||
|
|
4008e19ca9 | ||
|
|
e9070b30a0 | ||
|
|
636a1ff930 | ||
|
|
3888c4ba21 | ||
|
|
69173803bf | ||
|
|
13e31650be | ||
|
|
3563287f17 | ||
|
|
91ee9f5e05 | ||
| 49918a1067 | |||
|
|
c7997e0c2f | ||
|
|
4805dfbdba | ||
|
|
62424215c1 | ||
|
|
97ae13759e | ||
|
|
108678d94f | ||
|
|
66dbba5c16 | ||
|
|
d84cef0c18 | ||
|
|
077cefc60e | ||
|
|
35acc668c9 | ||
|
|
ebbe0f12ed | ||
|
|
6a8b76278a | ||
|
|
8ac99ef28f | ||
|
|
547cf7f600 | ||
|
|
b8b8b118c9 | ||
|
|
76247323eb | ||
|
|
0ff20c7c85 | ||
|
|
dd690e7495 | ||
|
|
ca500548a3 | ||
|
|
c0b73375b1 | ||
|
|
9c6d6a0b07 | ||
|
|
179f6014bf | ||
|
|
b580c6ec8e | ||
|
|
dd60cf0040 | ||
|
|
cc851b29fc | ||
|
|
59643b2fb9 | ||
|
|
3a5bb81d39 | ||
|
|
5ec860015d | ||
|
|
0f701ec08e | ||
|
|
387c3ea2f4 | ||
|
|
85b134b7be | ||
|
|
5d17d2509d | ||
|
|
036d4b2f4b | ||
|
|
93f12b73d8 | ||
|
|
1091bc6e2c | ||
|
|
594c0fb8e7 | ||
| 3fc1895b56 | |||
|
|
a70d7cf1f9 | ||
|
|
1f592ba1a1 | ||
|
|
a43daf025d | ||
|
|
296a2afa95 | ||
|
|
a7e879a3a4 | ||
|
|
27b160b464 | ||
|
|
7c3c4f1a63 | ||
|
|
fbae93b532 | ||
|
|
35d6e5fd21 | ||
|
|
114828adcb | ||
|
|
053e52c91f | ||
|
|
5a587a8582 | ||
|
|
a9fc5f720d | ||
|
|
d323b1c7e2 | ||
|
|
7377bad721 | ||
|
|
df28b0d977 | ||
|
|
59fb83a469 | ||
|
|
7544500be1 |
2
.github/ISSUE_TEMPLATE/1-bug.yaml
vendored
2
.github/ISSUE_TEMPLATE/1-bug.yaml
vendored
@@ -31,7 +31,7 @@ body:
|
|||||||
- '1.10'
|
- '1.10'
|
||||||
- '1.11'
|
- '1.11'
|
||||||
- master branch
|
- master branch
|
||||||
default: 2
|
default: 3
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: textarea
|
- type: textarea
|
||||||
|
|||||||
BIN
Content/Editor/Primitives/Cube.flax
(Stored with Git LFS)
BIN
Content/Editor/Primitives/Cube.flax
(Stored with Git LFS)
Binary file not shown.
BIN
Content/Shaders/GI/GlobalSurfaceAtlas.flax
(Stored with Git LFS)
BIN
Content/Shaders/GI/GlobalSurfaceAtlas.flax
(Stored with Git LFS)
Binary file not shown.
BIN
Content/Shaders/GlobalSignDistanceField.flax
(Stored with Git LFS)
BIN
Content/Shaders/GlobalSignDistanceField.flax
(Stored with Git LFS)
Binary file not shown.
BIN
Content/Shaders/SDF.flax
(Stored with Git LFS)
BIN
Content/Shaders/SDF.flax
(Stored with Git LFS)
Binary file not shown.
BIN
Content/Shaders/VolumetricFog.flax
(Stored with Git LFS)
BIN
Content/Shaders/VolumetricFog.flax
(Stored with Git LFS)
Binary file not shown.
@@ -4,7 +4,7 @@
|
|||||||
"Major": 1,
|
"Major": 1,
|
||||||
"Minor": 11,
|
"Minor": 11,
|
||||||
"Revision": 0,
|
"Revision": 0,
|
||||||
"Build": 6802
|
"Build": 6805
|
||||||
},
|
},
|
||||||
"Company": "Flax",
|
"Company": "Flax",
|
||||||
"Copyright": "Copyright (c) 2012-2025 Wojciech Figat. All rights reserved.",
|
"Copyright": "Copyright (c) 2012-2025 Wojciech Figat. All rights reserved.",
|
||||||
|
|||||||
@@ -281,6 +281,13 @@ namespace FlaxEditor.Content
|
|||||||
|
|
||||||
private void CacheData()
|
private void CacheData()
|
||||||
{
|
{
|
||||||
|
if (!_asset)
|
||||||
|
{
|
||||||
|
_parameters = Utils.GetEmptyArray<ScriptMemberInfo>();
|
||||||
|
_methods = Utils.GetEmptyArray<ScriptMemberInfo>();
|
||||||
|
_attributes = Utils.GetEmptyArray<Attribute>();
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (_parameters != null)
|
if (_parameters != null)
|
||||||
return;
|
return;
|
||||||
if (_asset.WaitForLoaded())
|
if (_asset.WaitForLoaded())
|
||||||
@@ -344,13 +351,13 @@ namespace FlaxEditor.Content
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public string Name => Path.GetFileNameWithoutExtension(_asset.Path);
|
public string Name => _asset ? Path.GetFileNameWithoutExtension(_asset.Path) : null;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public string Namespace => string.Empty;
|
public string Namespace => string.Empty;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public string TypeName => JsonSerializer.GetStringID(_asset.ID);
|
public string TypeName => _asset ? JsonSerializer.GetStringID(_asset.ID) : null;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public bool IsPublic => true;
|
public bool IsPublic => true;
|
||||||
|
|||||||
@@ -15,26 +15,32 @@
|
|||||||
#include "Editor/ProjectInfo.h"
|
#include "Editor/ProjectInfo.h"
|
||||||
#include "Editor/Utilities/EditorUtilities.h"
|
#include "Editor/Utilities/EditorUtilities.h"
|
||||||
|
|
||||||
GDKPlatformTools::GDKPlatformTools()
|
String GetGDK()
|
||||||
{
|
{
|
||||||
// Find GDK
|
String gdk;
|
||||||
Platform::GetEnvironmentVariable(TEXT("GameDKLatest"), _gdkPath);
|
Platform::GetEnvironmentVariable(TEXT("GameDKLatest"), gdk);
|
||||||
if (_gdkPath.IsEmpty() || !FileSystem::DirectoryExists(_gdkPath))
|
if (gdk.IsEmpty() || !FileSystem::DirectoryExists(gdk))
|
||||||
{
|
{
|
||||||
_gdkPath.Clear();
|
gdk.Clear();
|
||||||
Platform::GetEnvironmentVariable(TEXT("GRDKLatest"), _gdkPath);
|
Platform::GetEnvironmentVariable(TEXT("GRDKLatest"), gdk);
|
||||||
if (_gdkPath.IsEmpty() || !FileSystem::DirectoryExists(_gdkPath))
|
if (gdk.IsEmpty() || !FileSystem::DirectoryExists(gdk))
|
||||||
{
|
{
|
||||||
_gdkPath.Clear();
|
gdk.Clear();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (_gdkPath.EndsWith(TEXT("GRDK\\")))
|
if (gdk.EndsWith(TEXT("GRDK\\")))
|
||||||
_gdkPath.Remove(_gdkPath.Length() - 6);
|
gdk.Remove(gdk.Length() - 6);
|
||||||
else if (_gdkPath.EndsWith(TEXT("GRDK")))
|
else if (gdk.EndsWith(TEXT("GRDK")))
|
||||||
_gdkPath.Remove(_gdkPath.Length() - 5);
|
gdk.Remove(gdk.Length() - 5);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return gdk;
|
||||||
|
}
|
||||||
|
|
||||||
|
GDKPlatformTools::GDKPlatformTools()
|
||||||
|
{
|
||||||
|
_gdkPath = GetGDK();
|
||||||
}
|
}
|
||||||
|
|
||||||
DotNetAOTModes GDKPlatformTools::UseAOT() const
|
DotNetAOTModes GDKPlatformTools::UseAOT() const
|
||||||
@@ -121,7 +127,7 @@ bool GDKPlatformTools::OnPostProcess(CookingData& data, GDKPlatformSettings* pla
|
|||||||
validName.Add('\0');
|
validName.Add('\0');
|
||||||
|
|
||||||
sb.Append(TEXT("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"));
|
sb.Append(TEXT("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"));
|
||||||
sb.Append(TEXT("<Game configVersion=\"0\">\n"));
|
sb.Append(TEXT("<Game configVersion=\"1\">\n"));
|
||||||
sb.AppendFormat(TEXT(" <Identity Name=\"{0}\" Publisher=\"{1}\" Version=\"{2}\"/>\n"),
|
sb.AppendFormat(TEXT(" <Identity Name=\"{0}\" Publisher=\"{1}\" Version=\"{2}\"/>\n"),
|
||||||
validName.Get(),
|
validName.Get(),
|
||||||
platformSettings->PublisherName.HasChars() ? platformSettings->PublisherName : TEXT("CN=") + gameSettings->CompanyName,
|
platformSettings->PublisherName.HasChars() ? platformSettings->PublisherName : TEXT("CN=") + gameSettings->CompanyName,
|
||||||
|
|||||||
@@ -10,9 +10,10 @@
|
|||||||
#include "Engine/Serialization/JsonTools.h"
|
#include "Engine/Serialization/JsonTools.h"
|
||||||
#include "Engine/Serialization/JsonWriters.h"
|
#include "Engine/Serialization/JsonWriters.h"
|
||||||
#include "Editor/Cooker/PlatformTools.h"
|
#include "Editor/Cooker/PlatformTools.h"
|
||||||
|
#include "Engine/Engine/Globals.h"
|
||||||
#include "Editor/Editor.h"
|
#include "Editor/Editor.h"
|
||||||
#include "Editor/ProjectInfo.h"
|
#include "Editor/ProjectInfo.h"
|
||||||
#include "Engine/Engine/Globals.h"
|
#include "Editor/Utilities/EditorUtilities.h"
|
||||||
#if PLATFORM_MAC
|
#if PLATFORM_MAC
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#endif
|
#endif
|
||||||
@@ -127,7 +128,7 @@ bool CompileScriptsStep::DeployBinaries(CookingData& data, const String& path, c
|
|||||||
const String dst = dstPath / StringUtils::GetFileName(file);
|
const String dst = dstPath / StringUtils::GetFileName(file);
|
||||||
if (dst == file)
|
if (dst == file)
|
||||||
continue;
|
continue;
|
||||||
if (FileSystem::CopyFile(dst, file))
|
if (EditorUtilities::CopyFileIfNewer(dst, file))
|
||||||
{
|
{
|
||||||
data.Error(String::Format(TEXT("Failed to copy file from {0} to {1}."), file, dst));
|
data.Error(String::Format(TEXT("Failed to copy file from {0} to {1}."), file, dst));
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -526,6 +526,7 @@ bool ProcessShaderBase(CookAssetsStep::AssetCookData& data, ShaderAssetBase* ass
|
|||||||
#if PLATFORM_TOOLS_XBOX_SCARLETT
|
#if PLATFORM_TOOLS_XBOX_SCARLETT
|
||||||
case BuildPlatform::XboxScarlett:
|
case BuildPlatform::XboxScarlett:
|
||||||
{
|
{
|
||||||
|
options.Platform = PlatformType::XboxScarlett;
|
||||||
const char* platformDefineName = "PLATFORM_XBOX_SCARLETT";
|
const char* platformDefineName = "PLATFORM_XBOX_SCARLETT";
|
||||||
COMPILE_PROFILE(DirectX_SM6, SHADER_FILE_CHUNK_INTERNAL_D3D_SM6_CACHE);
|
COMPILE_PROFILE(DirectX_SM6, SHADER_FILE_CHUNK_INTERNAL_D3D_SM6_CACHE);
|
||||||
break;
|
break;
|
||||||
@@ -1367,7 +1368,10 @@ bool CookAssetsStep::Perform(CookingData& data)
|
|||||||
{
|
{
|
||||||
typeName = e.TypeName;
|
typeName = e.TypeName;
|
||||||
}
|
}
|
||||||
LOG(Info, "{0}: {1:>4} assets of total size {2}", typeName, e.Count, Utilities::BytesToText(e.ContentSize));
|
if (e.Count == 1)
|
||||||
|
LOG(Info, "{0}: 1 asset of total size {1}", typeName, Utilities::BytesToText(e.ContentSize));
|
||||||
|
else
|
||||||
|
LOG(Info, "{0}: {1:>4} assets of total size {2}", typeName, e.Count, Utilities::BytesToText(e.ContentSize));
|
||||||
}
|
}
|
||||||
LOG(Info, "");
|
LOG(Info, "");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -265,7 +265,7 @@ bool DeployDataStep::Perform(CookingData& data)
|
|||||||
}
|
}
|
||||||
if (version.IsEmpty())
|
if (version.IsEmpty())
|
||||||
{
|
{
|
||||||
data.Error(String::Format(TEXT("Failed to find supported .NET {} version (min {}) for the current host platform."), maxVer, minVer));
|
data.Error(String::Format(TEXT("Failed to find supported .NET {} version (min {}) for {} platform."), maxVer, minVer, platformName));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ bool PrecompileAssembliesStep::Perform(CookingData& data)
|
|||||||
data.StepProgress(infoMsg, 0);
|
data.StepProgress(infoMsg, 0);
|
||||||
|
|
||||||
// Override Newtonsoft.Json with AOT-version (one that doesn't use System.Reflection.Emit)
|
// Override Newtonsoft.Json with AOT-version (one that doesn't use System.Reflection.Emit)
|
||||||
|
// TODO: remove it since EngineModule does properly reference AOT lib now
|
||||||
EditorUtilities::CopyFileIfNewer(data.ManagedCodeOutputPath / TEXT("Newtonsoft.Json.dll"), Globals::StartupFolder / TEXT("Source/Platforms/DotNet/AOT/Newtonsoft.Json.dll"));
|
EditorUtilities::CopyFileIfNewer(data.ManagedCodeOutputPath / TEXT("Newtonsoft.Json.dll"), Globals::StartupFolder / TEXT("Source/Platforms/DotNet/AOT/Newtonsoft.Json.dll"));
|
||||||
FileSystem::DeleteFile(data.ManagedCodeOutputPath / TEXT("Newtonsoft.Json.xml"));
|
FileSystem::DeleteFile(data.ManagedCodeOutputPath / TEXT("Newtonsoft.Json.xml"));
|
||||||
FileSystem::DeleteFile(data.ManagedCodeOutputPath / TEXT("Newtonsoft.Json.pdb"));
|
FileSystem::DeleteFile(data.ManagedCodeOutputPath / TEXT("Newtonsoft.Json.pdb"));
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) Wojciech Figat. All rights reserved.
|
// Copyright (c) Wojciech Figat. All rights reserved.
|
||||||
|
|
||||||
using FlaxEngine;
|
using FlaxEngine;
|
||||||
|
using FlaxEngine.GUI;
|
||||||
|
|
||||||
namespace FlaxEditor.CustomEditors.Dedicated
|
namespace FlaxEditor.CustomEditors.Dedicated
|
||||||
{
|
{
|
||||||
@@ -11,7 +12,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
|
|||||||
[CustomEditor(typeof(EnvironmentProbe)), DefaultEditor]
|
[CustomEditor(typeof(EnvironmentProbe)), DefaultEditor]
|
||||||
public class EnvironmentProbeEditor : ActorEditor
|
public class EnvironmentProbeEditor : ActorEditor
|
||||||
{
|
{
|
||||||
private FlaxEngine.GUI.Button _bake;
|
private Button _bake;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override void Initialize(LayoutElementsContainer layout)
|
public override void Initialize(LayoutElementsContainer layout)
|
||||||
@@ -20,8 +21,9 @@ namespace FlaxEditor.CustomEditors.Dedicated
|
|||||||
|
|
||||||
if (Values.HasDifferentTypes == false)
|
if (Values.HasDifferentTypes == false)
|
||||||
{
|
{
|
||||||
layout.Space(10);
|
var group = layout.Group("Bake");
|
||||||
_bake = layout.Button("Bake").Button;
|
group.Panel.ItemsMargin = new Margin(Utilities.Constants.UIMargin * 2);
|
||||||
|
_bake = group.Button("Bake").Button;
|
||||||
_bake.Clicked += BakeButtonClicked;
|
_bake.Clicked += BakeButtonClicked;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -914,9 +914,11 @@ namespace FlaxEditor.CustomEditors.Dedicated
|
|||||||
// Remove drop down arrows and containment lines if no objects in the group
|
// Remove drop down arrows and containment lines if no objects in the group
|
||||||
if (group.Children.Count == 0)
|
if (group.Children.Count == 0)
|
||||||
{
|
{
|
||||||
|
group.Panel.Close();
|
||||||
group.Panel.ArrowImageOpened = null;
|
group.Panel.ArrowImageOpened = null;
|
||||||
group.Panel.ArrowImageClosed = null;
|
group.Panel.ArrowImageClosed = null;
|
||||||
group.Panel.EnableContainmentLines = false;
|
group.Panel.EnableContainmentLines = false;
|
||||||
|
group.Panel.CanOpenClose = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scripts arrange bar
|
// Scripts arrange bar
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) Wojciech Figat. All rights reserved.
|
// Copyright (c) Wojciech Figat. All rights reserved.
|
||||||
|
|
||||||
using FlaxEngine;
|
using FlaxEngine;
|
||||||
|
using FlaxEngine.GUI;
|
||||||
|
|
||||||
namespace FlaxEditor.CustomEditors.Dedicated
|
namespace FlaxEditor.CustomEditors.Dedicated
|
||||||
{
|
{
|
||||||
@@ -19,8 +20,9 @@ namespace FlaxEditor.CustomEditors.Dedicated
|
|||||||
if (Values.HasDifferentTypes == false)
|
if (Values.HasDifferentTypes == false)
|
||||||
{
|
{
|
||||||
// Add 'Bake' button
|
// Add 'Bake' button
|
||||||
layout.Space(10);
|
var group = layout.Group("Bake");
|
||||||
var button = layout.Button("Bake");
|
group.Panel.ItemsMargin = new Margin(Utilities.Constants.UIMargin * 2);
|
||||||
|
var button = group.Button("Bake");
|
||||||
button.Button.Clicked += BakeButtonClicked;
|
button.Button.Clicked += BakeButtonClicked;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -123,6 +123,8 @@ namespace FlaxEditor.CustomEditors.Editors
|
|||||||
{
|
{
|
||||||
base.Refresh();
|
base.Refresh();
|
||||||
|
|
||||||
|
if (Picker == null)
|
||||||
|
return;
|
||||||
var differentValues = HasDifferentValues;
|
var differentValues = HasDifferentValues;
|
||||||
Picker.DifferentValues = differentValues;
|
Picker.DifferentValues = differentValues;
|
||||||
if (!differentValues)
|
if (!differentValues)
|
||||||
|
|||||||
@@ -650,7 +650,7 @@ namespace FlaxEditor.CustomEditors.Editors
|
|||||||
panel.Panel.Size = new Float2(0, 18);
|
panel.Panel.Size = new Float2(0, 18);
|
||||||
panel.Panel.Margin = new Margin(0, 0, Utilities.Constants.UIMargin, 0);
|
panel.Panel.Margin = new Margin(0, 0, Utilities.Constants.UIMargin, 0);
|
||||||
|
|
||||||
var removeButton = panel.Button("-", "Remove the last item");
|
var removeButton = panel.Button("-", "Remove the last item.");
|
||||||
removeButton.Button.Size = new Float2(16, 16);
|
removeButton.Button.Size = new Float2(16, 16);
|
||||||
removeButton.Button.Enabled = size > _minCount;
|
removeButton.Button.Enabled = size > _minCount;
|
||||||
removeButton.Button.AnchorPreset = AnchorPresets.TopRight;
|
removeButton.Button.AnchorPreset = AnchorPresets.TopRight;
|
||||||
@@ -661,7 +661,7 @@ namespace FlaxEditor.CustomEditors.Editors
|
|||||||
Resize(Count - 1);
|
Resize(Count - 1);
|
||||||
};
|
};
|
||||||
|
|
||||||
var addButton = panel.Button("+", "Add a new item");
|
var addButton = panel.Button("+", "Add a new item.");
|
||||||
addButton.Button.Size = new Float2(16, 16);
|
addButton.Button.Size = new Float2(16, 16);
|
||||||
addButton.Button.Enabled = (!NotNullItems || size > 0) && size < _maxCount;
|
addButton.Button.Enabled = (!NotNullItems || size > 0) && size < _maxCount;
|
||||||
addButton.Button.AnchorPreset = AnchorPresets.TopRight;
|
addButton.Button.AnchorPreset = AnchorPresets.TopRight;
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ namespace FlaxEditor.CustomEditors.Editors
|
|||||||
public event Action<TypePickerControl> TypePickerValueChanged;
|
public event Action<TypePickerControl> TypePickerValueChanged;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The custom callback for types validation. Cane be used to implement a rule for types to pick.
|
/// The custom callback for types validation. Can be used to implement a rule for types to pick.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Func<ScriptType, bool> CheckValid;
|
public Func<ScriptType, bool> CheckValid;
|
||||||
|
|
||||||
@@ -353,7 +353,13 @@ namespace FlaxEditor.CustomEditors.Editors
|
|||||||
}
|
}
|
||||||
if (!string.IsNullOrEmpty(typeReference.CheckMethod))
|
if (!string.IsNullOrEmpty(typeReference.CheckMethod))
|
||||||
{
|
{
|
||||||
var parentType = ParentEditor.Values[0].GetType();
|
var parentEditor = ParentEditor;
|
||||||
|
// Find actual parent editor if parent editor is collection editor
|
||||||
|
while (parentEditor.GetType().IsAssignableTo(typeof(CollectionEditor)))
|
||||||
|
parentEditor = parentEditor.ParentEditor;
|
||||||
|
|
||||||
|
var parentType = parentEditor.Values[0].GetType();
|
||||||
|
|
||||||
var method = parentType.GetMethod(typeReference.CheckMethod, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
|
var method = parentType.GetMethod(typeReference.CheckMethod, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
|
||||||
if (method != null)
|
if (method != null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1390,6 +1390,7 @@ namespace FlaxEditor
|
|||||||
public void BuildAllMeshesSDF()
|
public void BuildAllMeshesSDF()
|
||||||
{
|
{
|
||||||
var models = new List<Model>();
|
var models = new List<Model>();
|
||||||
|
var forceRebuild = Input.GetKey(KeyboardKeys.F);
|
||||||
Scene.ExecuteOnGraph(node =>
|
Scene.ExecuteOnGraph(node =>
|
||||||
{
|
{
|
||||||
if (node is StaticModelNode staticModelNode && staticModelNode.Actor is StaticModel staticModel)
|
if (node is StaticModelNode staticModelNode && staticModelNode.Actor is StaticModel staticModel)
|
||||||
@@ -1399,7 +1400,7 @@ namespace FlaxEditor
|
|||||||
model != null &&
|
model != null &&
|
||||||
!models.Contains(model) &&
|
!models.Contains(model) &&
|
||||||
!model.IsVirtual &&
|
!model.IsVirtual &&
|
||||||
model.SDF.Texture == null)
|
(forceRebuild || model.SDF.Texture == null))
|
||||||
{
|
{
|
||||||
models.Add(model);
|
models.Add(model);
|
||||||
}
|
}
|
||||||
@@ -1412,7 +1413,17 @@ namespace FlaxEditor
|
|||||||
{
|
{
|
||||||
var model = models[i];
|
var model = models[i];
|
||||||
Log($"[{i}/{models.Count}] Generating SDF for {model}");
|
Log($"[{i}/{models.Count}] Generating SDF for {model}");
|
||||||
if (!model.GenerateSDF())
|
float resolutionScale = 1.0f, backfacesThreshold = 0.6f;
|
||||||
|
int lodIndex = 6;
|
||||||
|
bool useGPU = true;
|
||||||
|
var sdf = model.SDF;
|
||||||
|
if (sdf.Texture != null)
|
||||||
|
{
|
||||||
|
// Preserve options set on this model
|
||||||
|
resolutionScale = sdf.ResolutionScale;
|
||||||
|
lodIndex = sdf.LOD;
|
||||||
|
}
|
||||||
|
if (!model.GenerateSDF(resolutionScale, lodIndex, true, backfacesThreshold, useGPU))
|
||||||
model.Save();
|
model.Save();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -1587,7 +1598,7 @@ namespace FlaxEditor
|
|||||||
if (dockedTo != null && dockedTo.SelectedTab != gameWin && dockedTo.SelectedTab != null)
|
if (dockedTo != null && dockedTo.SelectedTab != gameWin && dockedTo.SelectedTab != null)
|
||||||
result = dockedTo.SelectedTab.Size;
|
result = dockedTo.SelectedTab.Size;
|
||||||
else
|
else
|
||||||
result = gameWin.Viewport.Size;
|
result = gameWin.Viewport.ContentSize;
|
||||||
|
|
||||||
result *= root.DpiScale;
|
result *= root.DpiScale;
|
||||||
result = Float2.Round(result);
|
result = Float2.Round(result);
|
||||||
|
|||||||
@@ -714,6 +714,7 @@ namespace FlaxEditor.Modules
|
|||||||
_menuToolsBuildCSGMesh = cm.AddButton("Build CSG mesh", inputOptions.BuildCSG, Editor.BuildCSG);
|
_menuToolsBuildCSGMesh = cm.AddButton("Build CSG mesh", inputOptions.BuildCSG, Editor.BuildCSG);
|
||||||
_menuToolsBuildNavMesh = cm.AddButton("Build Nav Mesh", inputOptions.BuildNav, Editor.BuildNavMesh);
|
_menuToolsBuildNavMesh = cm.AddButton("Build Nav Mesh", inputOptions.BuildNav, Editor.BuildNavMesh);
|
||||||
_menuToolsBuildAllMeshesSDF = cm.AddButton("Build all meshes SDF", inputOptions.BuildSDF, Editor.BuildAllMeshesSDF);
|
_menuToolsBuildAllMeshesSDF = cm.AddButton("Build all meshes SDF", inputOptions.BuildSDF, Editor.BuildAllMeshesSDF);
|
||||||
|
_menuToolsBuildAllMeshesSDF.LinkTooltip("Generates Sign Distance Field texture for all meshes used in loaded scenes. Use with 'F' key pressed to force rebuild SDF for meshes with existing one.");
|
||||||
cm.AddSeparator();
|
cm.AddSeparator();
|
||||||
cm.AddButton("Game Cooker", Editor.Windows.GameCookerWin.FocusOrShow);
|
cm.AddButton("Game Cooker", Editor.Windows.GameCookerWin.FocusOrShow);
|
||||||
_menuToolsCancelBuilding = cm.AddButton("Cancel building game", () => GameCooker.Cancel());
|
_menuToolsCancelBuilding = cm.AddButton("Cancel building game", () => GameCooker.Cancel());
|
||||||
|
|||||||
@@ -896,9 +896,11 @@ namespace FlaxEditor.Modules
|
|||||||
|
|
||||||
if (type.IsAssignableTo(typeof(AssetEditorWindow)))
|
if (type.IsAssignableTo(typeof(AssetEditorWindow)))
|
||||||
{
|
{
|
||||||
var ctor = type.GetConstructor(new Type[] { typeof(Editor), typeof(AssetItem) });
|
|
||||||
var assetItem = Editor.ContentDatabase.FindAsset(winData.AssetItemID);
|
var assetItem = Editor.ContentDatabase.FindAsset(winData.AssetItemID);
|
||||||
|
var assetType = assetItem.GetType();
|
||||||
|
var ctor = type.GetConstructor(new Type[] { typeof(Editor), assetType });
|
||||||
var win = (AssetEditorWindow)ctor.Invoke(new object[] { Editor.Instance, assetItem });
|
var win = (AssetEditorWindow)ctor.Invoke(new object[] { Editor.Instance, assetItem });
|
||||||
|
|
||||||
win.Show(winData.DockState, winData.DockState != DockState.Float ? winData.DockedTo : null, winData.SelectOnShow, winData.SplitterValue);
|
win.Show(winData.DockState, winData.DockState != DockState.Float ? winData.DockedTo : null, winData.SelectOnShow, winData.SplitterValue);
|
||||||
if (winData.DockState == DockState.Float)
|
if (winData.DockState == DockState.Float)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -406,6 +406,8 @@ namespace FlaxEngine.Utilities
|
|||||||
{
|
{
|
||||||
if (type == ScriptType.Null)
|
if (type == ScriptType.Null)
|
||||||
return null;
|
return null;
|
||||||
|
if (type.BaseType == null)
|
||||||
|
return type.Type;
|
||||||
while (type.Type == null)
|
while (type.Type == null)
|
||||||
type = type.BaseType;
|
type = type.BaseType;
|
||||||
return type.Type;
|
return type.Type;
|
||||||
|
|||||||
@@ -726,7 +726,7 @@ namespace FlaxEditor.Surface.Archetypes
|
|||||||
|
|
||||||
private void OnSurfaceMouseUp(ref Float2 mouse, MouseButton buttons, ref bool handled)
|
private void OnSurfaceMouseUp(ref Float2 mouse, MouseButton buttons, ref bool handled)
|
||||||
{
|
{
|
||||||
if (handled)
|
if (handled || Surface.Context != Context)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Check click over the connection
|
// Check click over the connection
|
||||||
@@ -751,7 +751,7 @@ namespace FlaxEditor.Surface.Archetypes
|
|||||||
|
|
||||||
private void OnSurfaceMouseDoubleClick(ref Float2 mouse, MouseButton buttons, ref bool handled)
|
private void OnSurfaceMouseDoubleClick(ref Float2 mouse, MouseButton buttons, ref bool handled)
|
||||||
{
|
{
|
||||||
if (handled)
|
if (handled || Surface.Context != Context)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Check double click over the connection
|
// Check double click over the connection
|
||||||
|
|||||||
@@ -2,11 +2,8 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Runtime.Loader;
|
|
||||||
using System.Runtime.Serialization.Formatters.Binary;
|
|
||||||
using FlaxEditor.CustomEditors;
|
using FlaxEditor.CustomEditors;
|
||||||
using FlaxEditor.CustomEditors.Editors;
|
using FlaxEditor.CustomEditors.Editors;
|
||||||
using FlaxEditor.GUI.ContextMenu;
|
using FlaxEditor.GUI.ContextMenu;
|
||||||
@@ -18,6 +15,7 @@ namespace FlaxEditor.Surface
|
|||||||
class AttributesEditor : ContextMenuBase
|
class AttributesEditor : ContextMenuBase
|
||||||
{
|
{
|
||||||
private CustomEditorPresenter _presenter;
|
private CustomEditorPresenter _presenter;
|
||||||
|
private Proxy _proxy;
|
||||||
private byte[] _oldData;
|
private byte[] _oldData;
|
||||||
|
|
||||||
private class Proxy
|
private class Proxy
|
||||||
@@ -72,11 +70,11 @@ namespace FlaxEditor.Surface
|
|||||||
/// Initializes a new instance of the <see cref="AttributesEditor"/> class.
|
/// Initializes a new instance of the <see cref="AttributesEditor"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="attributes">The attributes list to edit.</param>
|
/// <param name="attributes">The attributes list to edit.</param>
|
||||||
/// <param name="attributeType">The allowed attribute types to use.</param>
|
/// <param name="attributeTypes">The allowed attribute types to use.</param>
|
||||||
public AttributesEditor(Attribute[] attributes, IList<Type> attributeType)
|
public AttributesEditor(Attribute[] attributes, IList<Type> attributeTypes)
|
||||||
{
|
{
|
||||||
// Context menu dimensions
|
// Context menu dimensions
|
||||||
const float width = 340.0f;
|
const float width = 375.0f;
|
||||||
const float height = 370.0f;
|
const float height = 370.0f;
|
||||||
Size = new Float2(width, height);
|
Size = new Float2(width, height);
|
||||||
|
|
||||||
@@ -88,61 +86,68 @@ namespace FlaxEditor.Surface
|
|||||||
Parent = this
|
Parent = this
|
||||||
};
|
};
|
||||||
|
|
||||||
// Buttons
|
// Ok and Cancel Buttons
|
||||||
float buttonsWidth = (width - 16.0f) * 0.5f;
|
float buttonsWidth = (width - 12.0f) * 0.5f;
|
||||||
float buttonsHeight = 20.0f;
|
float buttonsHeight = 20.0f;
|
||||||
var cancelButton = new Button(4.0f, title.Bottom + 4.0f, buttonsWidth, buttonsHeight)
|
var okButton = new Button(4.0f, Bottom - 4.0f - buttonsHeight, buttonsWidth, buttonsHeight)
|
||||||
|
{
|
||||||
|
Text = "Ok",
|
||||||
|
Parent = this
|
||||||
|
};
|
||||||
|
okButton.Clicked += OnOkButtonClicked;
|
||||||
|
var cancelButton = new Button(okButton.Right + 4.0f, okButton.Y, buttonsWidth, buttonsHeight)
|
||||||
{
|
{
|
||||||
Text = "Cancel",
|
Text = "Cancel",
|
||||||
Parent = this
|
Parent = this
|
||||||
};
|
};
|
||||||
cancelButton.Clicked += Hide;
|
cancelButton.Clicked += Hide;
|
||||||
var okButton = new Button(cancelButton.Right + 4.0f, cancelButton.Y, buttonsWidth, buttonsHeight)
|
|
||||||
{
|
|
||||||
Text = "OK",
|
|
||||||
Parent = this
|
|
||||||
};
|
|
||||||
okButton.Clicked += OnOkButtonClicked;
|
|
||||||
|
|
||||||
// Actual panel
|
// Actual panel used to display attributes
|
||||||
var panel1 = new Panel(ScrollBars.Vertical)
|
var panel1 = new Panel(ScrollBars.Vertical)
|
||||||
{
|
{
|
||||||
Bounds = new Rectangle(0, okButton.Bottom + 4.0f, width, height - okButton.Bottom - 2.0f),
|
Bounds = new Rectangle(0, title.Bottom + 4.0f, width, height - buttonsHeight - title.Height - 14.0f),
|
||||||
Parent = this
|
Parent = this
|
||||||
};
|
};
|
||||||
var editor = new CustomEditorPresenter(null);
|
var editor = new CustomEditorPresenter(null);
|
||||||
editor.Panel.AnchorPreset = AnchorPresets.HorizontalStretchTop;
|
editor.Panel.AnchorPreset = AnchorPresets.HorizontalStretchTop;
|
||||||
editor.Panel.IsScrollable = true;
|
editor.Panel.IsScrollable = true;
|
||||||
editor.Panel.Parent = panel1;
|
editor.Panel.Parent = panel1;
|
||||||
editor.Panel.Tag = attributeType;
|
editor.Panel.Tag = attributeTypes;
|
||||||
_presenter = editor;
|
_presenter = editor;
|
||||||
|
|
||||||
// Cache 'previous' state to check if attributes were edited after operation
|
// Cache 'previous' state to check if attributes were edited after operation
|
||||||
_oldData = SurfaceMeta.GetAttributesData(attributes);
|
_oldData = SurfaceMeta.GetAttributesData(attributes);
|
||||||
|
|
||||||
editor.Select(new Proxy
|
_proxy = new Proxy
|
||||||
{
|
{
|
||||||
Value = attributes,
|
Value = attributes,
|
||||||
});
|
};
|
||||||
|
editor.Select(_proxy);
|
||||||
|
|
||||||
|
_presenter.Modified += OnPresenterModified;
|
||||||
|
OnPresenterModified();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPresenterModified()
|
||||||
|
{
|
||||||
|
if (_proxy.Value.Length == 0)
|
||||||
|
{
|
||||||
|
var label = _presenter.Label("No attributes.\nPress the \"+\" button to add a new one and then select an attribute type using the \"Type\" dropdown.", TextAlignment.Center);
|
||||||
|
label.Label.Wrapping = TextWrapping.WrapWords;
|
||||||
|
label.Control.Height = 35f;
|
||||||
|
label.Label.Margin = new Margin(10f);
|
||||||
|
label.Label.TextColor = label.Label.TextColorHighlighted = Style.Current.ForegroundGrey;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnOkButtonClicked()
|
private void OnOkButtonClicked()
|
||||||
{
|
{
|
||||||
var newValue = ((Proxy)_presenter.Selection[0]).Value;
|
var newValue = ((Proxy)_presenter.Selection[0]).Value;
|
||||||
for (int i = 0; i < newValue.Length; i++)
|
newValue = newValue.Where(v => v != null).ToArray();
|
||||||
{
|
|
||||||
if (newValue[i] == null)
|
|
||||||
{
|
|
||||||
MessageBox.Show("One of the attributes is null. Please set it to the valid object.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var newData = SurfaceMeta.GetAttributesData(newValue);
|
var newData = SurfaceMeta.GetAttributesData(newValue);
|
||||||
if (!_oldData.SequenceEqual(newData))
|
if (!_oldData.SequenceEqual(newData))
|
||||||
{
|
|
||||||
Edited?.Invoke(newValue);
|
Edited?.Invoke(newValue);
|
||||||
}
|
|
||||||
|
|
||||||
Hide();
|
Hide();
|
||||||
}
|
}
|
||||||
@@ -183,7 +188,9 @@ namespace FlaxEditor.Surface
|
|||||||
{
|
{
|
||||||
_presenter = null;
|
_presenter = null;
|
||||||
_oldData = null;
|
_oldData = null;
|
||||||
|
_proxy = null;
|
||||||
Edited = null;
|
Edited = null;
|
||||||
|
_presenter.Modified -= OnPresenterModified;
|
||||||
|
|
||||||
base.OnDestroy();
|
base.OnDestroy();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -214,22 +214,25 @@ namespace FlaxEditor.Surface
|
|||||||
if (!_isRenaming)
|
if (!_isRenaming)
|
||||||
Render2D.DrawText(style.FontLarge, Title, _headerRect, style.Foreground, TextAlignment.Center, TextAlignment.Center);
|
Render2D.DrawText(style.FontLarge, Title, _headerRect, style.Foreground, TextAlignment.Center, TextAlignment.Center);
|
||||||
|
|
||||||
// Close button
|
if (Surface.CanEdit)
|
||||||
Render2D.DrawSprite(style.Cross, _closeButtonRect, _closeButtonRect.Contains(_mousePosition) && Surface.CanEdit ? style.Foreground : style.ForegroundGrey);
|
|
||||||
|
|
||||||
// Color button
|
|
||||||
Render2D.DrawSprite(style.Settings, _colorButtonRect, _colorButtonRect.Contains(_mousePosition) && Surface.CanEdit ? style.Foreground : style.ForegroundGrey);
|
|
||||||
|
|
||||||
// Check if is resizing
|
|
||||||
if (_isResizing)
|
|
||||||
{
|
{
|
||||||
// Draw overlay
|
// Close button
|
||||||
Render2D.FillRectangle(_resizeButtonRect, style.Selection);
|
Render2D.DrawSprite(style.Cross, _closeButtonRect, _closeButtonRect.Contains(_mousePosition) && Surface.CanEdit ? style.Foreground : style.ForegroundGrey);
|
||||||
Render2D.DrawRectangle(_resizeButtonRect, style.SelectionBorder);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resize button
|
// Color button
|
||||||
Render2D.DrawSprite(style.Scale, _resizeButtonRect, _resizeButtonRect.Contains(_mousePosition) && Surface.CanEdit ? style.Foreground : style.ForegroundGrey);
|
Render2D.DrawSprite(style.Settings, _colorButtonRect, _colorButtonRect.Contains(_mousePosition) && Surface.CanEdit ? style.Foreground : style.ForegroundGrey);
|
||||||
|
|
||||||
|
// Check if is resizing
|
||||||
|
if (_isResizing)
|
||||||
|
{
|
||||||
|
// Draw overlay
|
||||||
|
Render2D.FillRectangle(_resizeButtonRect, style.Selection);
|
||||||
|
Render2D.DrawRectangle(_resizeButtonRect, style.SelectionBorder);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resize button
|
||||||
|
Render2D.DrawSprite(style.Scale, _resizeButtonRect, _resizeButtonRect.Contains(_mousePosition) && Surface.CanEdit ? style.Foreground : style.ForegroundGrey);
|
||||||
|
}
|
||||||
|
|
||||||
// Selection outline
|
// Selection outline
|
||||||
if (_isSelected)
|
if (_isSelected)
|
||||||
|
|||||||
@@ -431,27 +431,6 @@ namespace FlaxEditor.Surface
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool HasIndependentBoxes => Archetype.IndependentBoxes != null;
|
public bool HasIndependentBoxes => Archetype.IndependentBoxes != null;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a value indicating whether this node has dependent boxes with assigned valid types. Otherwise any box has no dependent type assigned.
|
|
||||||
/// </summary>
|
|
||||||
public bool HasDependentBoxesSetup
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (Archetype.DependentBoxes == null || Archetype.IndependentBoxes == null)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
for (int i = 0; i < Archetype.DependentBoxes.Length; i++)
|
|
||||||
{
|
|
||||||
var b = GetBox(Archetype.DependentBoxes[i]);
|
|
||||||
if (b != null && b.CurrentType == b.DefaultType)
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static readonly List<SurfaceNode> UpdateStack = new List<SurfaceNode>();
|
private static readonly List<SurfaceNode> UpdateStack = new List<SurfaceNode>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -400,7 +400,7 @@ namespace FlaxEditor.Surface
|
|||||||
return scriptType.GetGenericTypeDefinition() == typeof(Dictionary<,>);
|
return scriptType.GetGenericTypeDefinition() == typeof(Dictionary<,>);
|
||||||
}
|
}
|
||||||
var managedType = TypeUtils.GetType(scriptType);
|
var managedType = TypeUtils.GetType(scriptType);
|
||||||
return !TypeUtils.IsDelegate(managedType);
|
return managedType != null && !TypeUtils.IsDelegate(managedType);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static bool IsValidVisualScriptFunctionType(ScriptType scriptType)
|
internal static bool IsValidVisualScriptFunctionType(ScriptType scriptType)
|
||||||
@@ -408,7 +408,7 @@ namespace FlaxEditor.Surface
|
|||||||
if (scriptType.IsGenericType || scriptType.IsStatic || !scriptType.IsPublic || scriptType.HasAttribute(typeof(HideInEditorAttribute), true))
|
if (scriptType.IsGenericType || scriptType.IsStatic || !scriptType.IsPublic || scriptType.HasAttribute(typeof(HideInEditorAttribute), true))
|
||||||
return false;
|
return false;
|
||||||
var managedType = TypeUtils.GetType(scriptType);
|
var managedType = TypeUtils.GetType(scriptType);
|
||||||
return !TypeUtils.IsDelegate(managedType);
|
return managedType != null && !TypeUtils.IsDelegate(managedType);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static string GetVisualScriptTypeDescription(ScriptType type)
|
internal static string GetVisualScriptTypeDescription(ScriptType type)
|
||||||
|
|||||||
@@ -469,7 +469,8 @@ namespace FlaxEditor.Surface
|
|||||||
bool handled = base.OnMouseDown(location, button);
|
bool handled = base.OnMouseDown(location, button);
|
||||||
if (!handled)
|
if (!handled)
|
||||||
CustomMouseDown?.Invoke(ref location, button, ref handled);
|
CustomMouseDown?.Invoke(ref location, button, ref handled);
|
||||||
if (handled)
|
var root = Root;
|
||||||
|
if (handled || root == null)
|
||||||
{
|
{
|
||||||
// Clear flags
|
// Clear flags
|
||||||
_isMovingSelection = false;
|
_isMovingSelection = false;
|
||||||
@@ -523,11 +524,11 @@ namespace FlaxEditor.Surface
|
|||||||
if (_leftMouseDown && controlUnderMouse.CanSelect(ref cLocation))
|
if (_leftMouseDown && controlUnderMouse.CanSelect(ref cLocation))
|
||||||
{
|
{
|
||||||
// Check if user is pressing control
|
// Check if user is pressing control
|
||||||
if (Root.GetKey(KeyboardKeys.Control))
|
if (root.GetKey(KeyboardKeys.Control))
|
||||||
{
|
{
|
||||||
AddToSelection(controlUnderMouse);
|
AddToSelection(controlUnderMouse);
|
||||||
}
|
}
|
||||||
else if (Root.GetKey(KeyboardKeys.Shift))
|
else if (root.GetKey(KeyboardKeys.Shift))
|
||||||
{
|
{
|
||||||
RemoveFromSelection(controlUnderMouse);
|
RemoveFromSelection(controlUnderMouse);
|
||||||
}
|
}
|
||||||
@@ -539,7 +540,7 @@ namespace FlaxEditor.Surface
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Start moving selected nodes
|
// Start moving selected nodes
|
||||||
if (!Root.GetKey(KeyboardKeys.Shift))
|
if (!root.GetKey(KeyboardKeys.Shift))
|
||||||
{
|
{
|
||||||
StartMouseCapture();
|
StartMouseCapture();
|
||||||
_movingSelectionViewPos = _rootControl.Location;
|
_movingSelectionViewPos = _rootControl.Location;
|
||||||
@@ -559,7 +560,7 @@ namespace FlaxEditor.Surface
|
|||||||
// Start selecting or commenting
|
// Start selecting or commenting
|
||||||
StartMouseCapture();
|
StartMouseCapture();
|
||||||
|
|
||||||
if (!Root.GetKey(KeyboardKeys.Control) && !Root.GetKey(KeyboardKeys.Shift))
|
if (!root.GetKey(KeyboardKeys.Control) && !root.GetKey(KeyboardKeys.Shift))
|
||||||
{
|
{
|
||||||
ClearSelection();
|
ClearSelection();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -178,19 +178,31 @@ namespace FlaxEditor.Surface
|
|||||||
|
|
||||||
// Update boxes types for nodes that dependant box types based on incoming connections
|
// Update boxes types for nodes that dependant box types based on incoming connections
|
||||||
{
|
{
|
||||||
bool keepUpdating = false;
|
bool keepUpdating = true;
|
||||||
int updateLimit = 100;
|
int updatesMin = 2, updatesMax = 100;
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
|
keepUpdating = false;
|
||||||
for (int i = 0; i < RootControl.Children.Count; i++)
|
for (int i = 0; i < RootControl.Children.Count; i++)
|
||||||
{
|
{
|
||||||
if (RootControl.Children[i] is SurfaceNode node && !node.HasDependentBoxesSetup)
|
if (RootControl.Children[i] is SurfaceNode node)
|
||||||
{
|
{
|
||||||
node.UpdateBoxesTypes();
|
node.UpdateBoxesTypes();
|
||||||
keepUpdating = true;
|
var arch = node.Archetype;
|
||||||
|
if (arch.DependentBoxes != null && arch.IndependentBoxes != null)
|
||||||
|
{
|
||||||
|
foreach (var boxId in arch.DependentBoxes)
|
||||||
|
{
|
||||||
|
var b = node.GetBox(boxId);
|
||||||
|
if (b != null && b.CurrentType == b.DefaultType)
|
||||||
|
{
|
||||||
|
keepUpdating = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} while (keepUpdating && updateLimit-- > 0);
|
} while ((keepUpdating && --updatesMax > 0) || --updatesMin > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
Loaded?.Invoke(this);
|
Loaded?.Invoke(this);
|
||||||
|
|||||||
@@ -74,11 +74,6 @@ struct TextureDataResult
|
|||||||
PixelFormat Format;
|
PixelFormat Format;
|
||||||
Int2 Mip0Size;
|
Int2 Mip0Size;
|
||||||
BytesContainer* Mip0DataPtr;
|
BytesContainer* Mip0DataPtr;
|
||||||
|
|
||||||
TextureDataResult()
|
|
||||||
: Lock(FlaxStorage::LockData::Invalid)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
bool GetTextureDataForSampling(Texture* texture, TextureDataResult& data, bool hdr = false)
|
bool GetTextureDataForSampling(Texture* texture, TextureDataResult& data, bool hdr = false)
|
||||||
|
|||||||
@@ -140,7 +140,7 @@ namespace FlaxEditor.Windows
|
|||||||
}
|
}
|
||||||
|
|
||||||
private string _cacheFolder;
|
private string _cacheFolder;
|
||||||
private Guid _assetId;
|
private AssetItem _item;
|
||||||
private Surface _surface;
|
private Surface _surface;
|
||||||
private Label _loadingLabel;
|
private Label _loadingLabel;
|
||||||
private CancellationTokenSource _token;
|
private CancellationTokenSource _token;
|
||||||
@@ -163,13 +163,13 @@ namespace FlaxEditor.Windows
|
|||||||
public AssetReferencesGraphWindow(Editor editor, AssetItem assetItem)
|
public AssetReferencesGraphWindow(Editor editor, AssetItem assetItem)
|
||||||
: base(editor, false, ScrollBars.None)
|
: base(editor, false, ScrollBars.None)
|
||||||
{
|
{
|
||||||
Title = assetItem.ShortName + " References";
|
_item = assetItem;
|
||||||
|
Title = _item.ShortName + " References";
|
||||||
|
|
||||||
_tempFolder = StringUtils.NormalizePath(Path.GetDirectoryName(Globals.TemporaryFolder));
|
_tempFolder = StringUtils.NormalizePath(Path.GetDirectoryName(Globals.TemporaryFolder));
|
||||||
_cacheFolder = Path.Combine(Globals.ProjectCacheFolder, "References");
|
_cacheFolder = Path.Combine(Globals.ProjectCacheFolder, "References");
|
||||||
if (!Directory.Exists(_cacheFolder))
|
if (!Directory.Exists(_cacheFolder))
|
||||||
Directory.CreateDirectory(_cacheFolder);
|
Directory.CreateDirectory(_cacheFolder);
|
||||||
_assetId = assetItem.ID;
|
|
||||||
_surface = new Surface(this)
|
_surface = new Surface(this)
|
||||||
{
|
{
|
||||||
AnchorPreset = AnchorPresets.StretchAll,
|
AnchorPreset = AnchorPresets.StretchAll,
|
||||||
@@ -194,6 +194,7 @@ namespace FlaxEditor.Windows
|
|||||||
_nodesAssets.Add(assetId);
|
_nodesAssets.Add(assetId);
|
||||||
var node = new AssetNode((uint)_nodes.Count + 1, _surface.Context, GraphNodes[0], GraphGroups[0], assetId);
|
var node = new AssetNode((uint)_nodes.Count + 1, _surface.Context, GraphNodes[0], GraphGroups[0], assetId);
|
||||||
_nodes.Add(node);
|
_nodes.Add(node);
|
||||||
|
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -392,8 +393,7 @@ namespace FlaxEditor.Windows
|
|||||||
_nodesAssets = new HashSet<Guid>();
|
_nodesAssets = new HashSet<Guid>();
|
||||||
var searchLevel = 4; // TODO: make it as an option (somewhere in window UI)
|
var searchLevel = 4; // TODO: make it as an option (somewhere in window UI)
|
||||||
// TODO: add option to filter assets by type (eg. show only textures as leaf nodes)
|
// TODO: add option to filter assets by type (eg. show only textures as leaf nodes)
|
||||||
var assetNode = SpawnNode(_assetId);
|
var assetNode = SpawnNode(_item.ID);
|
||||||
// TODO: add some outline or tint color to the main node
|
|
||||||
BuildGraph(assetNode, searchLevel, false);
|
BuildGraph(assetNode, searchLevel, false);
|
||||||
ArrangeGraph(assetNode, false);
|
ArrangeGraph(assetNode, false);
|
||||||
BuildGraph(assetNode, searchLevel, true);
|
BuildGraph(assetNode, searchLevel, true);
|
||||||
@@ -402,6 +402,10 @@ namespace FlaxEditor.Windows
|
|||||||
return;
|
return;
|
||||||
_progress = 100.0f;
|
_progress = 100.0f;
|
||||||
|
|
||||||
|
var commentRect = assetNode.EditorBounds;
|
||||||
|
commentRect.Expand(80f);
|
||||||
|
_surface.Context.CreateComment(ref commentRect, _item.ShortName, Color.Green);
|
||||||
|
|
||||||
// Update UI
|
// Update UI
|
||||||
FlaxEngine.Scripting.InvokeOnUpdate(() =>
|
FlaxEngine.Scripting.InvokeOnUpdate(() =>
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ using FlaxEditor.Surface;
|
|||||||
using FlaxEditor.Viewport.Previews;
|
using FlaxEditor.Viewport.Previews;
|
||||||
using FlaxEngine;
|
using FlaxEngine;
|
||||||
using FlaxEngine.GUI;
|
using FlaxEngine.GUI;
|
||||||
using FlaxEngine.Utilities;
|
|
||||||
|
|
||||||
namespace FlaxEditor.Windows.Assets
|
namespace FlaxEditor.Windows.Assets
|
||||||
{
|
{
|
||||||
@@ -430,7 +429,7 @@ namespace FlaxEditor.Windows.Assets
|
|||||||
for (var i = 0; i < parameters.Length; i++)
|
for (var i = 0; i < parameters.Length; i++)
|
||||||
{
|
{
|
||||||
var p = parameters[i];
|
var p = parameters[i];
|
||||||
if (p.IsOverride)
|
if (p.IsOverride && p.IsPublic)
|
||||||
{
|
{
|
||||||
p.IsOverride = false;
|
p.IsOverride = false;
|
||||||
actions.Add(new EditParamOverrideAction
|
actions.Add(new EditParamOverrideAction
|
||||||
|
|||||||
@@ -90,25 +90,15 @@ namespace FlaxEditor.Windows.Assets
|
|||||||
|
|
||||||
var gpu = group.Checkbox("Bake on GPU", "If checked, SDF generation will be calculated using GPU on Compute Shader, otherwise CPU will use Job System. GPU generation is fast but result in artifacts in various meshes (eg. foliage).");
|
var gpu = group.Checkbox("Bake on GPU", "If checked, SDF generation will be calculated using GPU on Compute Shader, otherwise CPU will use Job System. GPU generation is fast but result in artifacts in various meshes (eg. foliage).");
|
||||||
gpu.CheckBox.Checked = sdfOptions.GPU;
|
gpu.CheckBox.Checked = sdfOptions.GPU;
|
||||||
|
gpu.CheckBox.StateChanged += c => { Window._sdfOptions.GPU = c.Checked; };
|
||||||
|
|
||||||
var backfacesThresholdProp = group.AddPropertyItem("Backfaces Threshold", "Custom threshold (in range 0-1) for adjusting mesh internals detection based on the percentage of test rays hit triangle backfaces. Use lower value for more dense mesh.");
|
var backfacesThresholdProp = group.AddPropertyItem("Backfaces Threshold", "Custom threshold (in range 0-1) for adjusting mesh internals detection based on the percentage of test rays hit triangle backfaces. Use lower value for more dense mesh.");
|
||||||
var backfacesThreshold = backfacesThresholdProp.FloatValue();
|
var backfacesThreshold = backfacesThresholdProp.FloatValue();
|
||||||
var backfacesThresholdLabel = backfacesThresholdProp.Labels.Last();
|
|
||||||
backfacesThreshold.ValueBox.MinValue = 0.001f;
|
backfacesThreshold.ValueBox.MinValue = 0.001f;
|
||||||
backfacesThreshold.ValueBox.MaxValue = 1.0f;
|
backfacesThreshold.ValueBox.MaxValue = 1.0f;
|
||||||
backfacesThreshold.ValueBox.Value = sdfOptions.BackfacesThreshold;
|
backfacesThreshold.ValueBox.Value = sdfOptions.BackfacesThreshold;
|
||||||
backfacesThreshold.ValueBox.BoxValueChanged += b => { Window._sdfOptions.BackfacesThreshold = b.Value; };
|
backfacesThreshold.ValueBox.BoxValueChanged += b => { Window._sdfOptions.BackfacesThreshold = b.Value; };
|
||||||
|
|
||||||
// Toggle Backfaces Threshold visibility (CPU-only option)
|
|
||||||
gpu.CheckBox.StateChanged += c =>
|
|
||||||
{
|
|
||||||
Window._sdfOptions.GPU = c.Checked;
|
|
||||||
backfacesThresholdLabel.Visible = !c.Checked;
|
|
||||||
backfacesThreshold.ValueBox.Visible = !c.Checked;
|
|
||||||
};
|
|
||||||
backfacesThresholdLabel.Visible = !gpu.CheckBox.Checked;
|
|
||||||
backfacesThreshold.ValueBox.Visible = !gpu.CheckBox.Checked;
|
|
||||||
|
|
||||||
var lodIndex = group.IntegerValue("LOD Index", "Index of the model Level of Detail to use for SDF data building. By default uses the lowest quality LOD for fast building.");
|
var lodIndex = group.IntegerValue("LOD Index", "Index of the model Level of Detail to use for SDF data building. By default uses the lowest quality LOD for fast building.");
|
||||||
lodIndex.IntValue.MinValue = 0;
|
lodIndex.IntValue.MinValue = 0;
|
||||||
lodIndex.IntValue.MaxValue = Asset.LODsCount - 1;
|
lodIndex.IntValue.MaxValue = Asset.LODsCount - 1;
|
||||||
|
|||||||
@@ -142,6 +142,7 @@ namespace FlaxEditor.Windows
|
|||||||
{
|
{
|
||||||
Title = "Content";
|
Title = "Content";
|
||||||
Icon = editor.Icons.Folder32;
|
Icon = editor.Icons.Folder32;
|
||||||
|
var style = Style.Current;
|
||||||
|
|
||||||
FlaxEditor.Utilities.Utils.SetupCommonInputActions(this);
|
FlaxEditor.Utilities.Utils.SetupCommonInputActions(this);
|
||||||
|
|
||||||
@@ -164,6 +165,8 @@ namespace FlaxEditor.Windows
|
|||||||
_navigationBar = new NavigationBar
|
_navigationBar = new NavigationBar
|
||||||
{
|
{
|
||||||
Parent = _toolStrip,
|
Parent = _toolStrip,
|
||||||
|
ScrollbarTrackColor = style.Background,
|
||||||
|
ScrollbarThumbColor = style.ForegroundGrey,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Split panel
|
// Split panel
|
||||||
@@ -179,7 +182,7 @@ namespace FlaxEditor.Windows
|
|||||||
var headerPanel = new ContainerControl
|
var headerPanel = new ContainerControl
|
||||||
{
|
{
|
||||||
AnchorPreset = AnchorPresets.HorizontalStretchTop,
|
AnchorPreset = AnchorPresets.HorizontalStretchTop,
|
||||||
BackgroundColor = Style.Current.Background,
|
BackgroundColor = style.Background,
|
||||||
IsScrollable = false,
|
IsScrollable = false,
|
||||||
Offsets = new Margin(0, 0, 0, 18 + 6),
|
Offsets = new Margin(0, 0, 0, 18 + 6),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -10,17 +10,117 @@ using FlaxEditor.Modules;
|
|||||||
using FlaxEditor.Options;
|
using FlaxEditor.Options;
|
||||||
using FlaxEngine;
|
using FlaxEngine;
|
||||||
using FlaxEngine.GUI;
|
using FlaxEngine.GUI;
|
||||||
using FlaxEngine.Json;
|
|
||||||
|
|
||||||
namespace FlaxEditor.Windows
|
namespace FlaxEditor.Windows
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Render output control with content scaling support.
|
||||||
|
/// </summary>
|
||||||
|
public class ScaledRenderOutputControl : RenderOutputControl
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Custom scale.
|
||||||
|
/// </summary>
|
||||||
|
public float ContentScale = 1.0f;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Actual bounds size for content (incl. scale).
|
||||||
|
/// </summary>
|
||||||
|
public Float2 ContentSize => Size / ContentScale;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public ScaledRenderOutputControl(SceneRenderTask task)
|
||||||
|
: base(task)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void Draw()
|
||||||
|
{
|
||||||
|
DrawSelf();
|
||||||
|
|
||||||
|
// Draw children with scale
|
||||||
|
var scaling = new Float3(ContentScale, ContentScale, 1);
|
||||||
|
Matrix3x3.Scaling(ref scaling, out Matrix3x3 scale);
|
||||||
|
Render2D.PushTransform(scale);
|
||||||
|
if (ClipChildren)
|
||||||
|
{
|
||||||
|
GetDesireClientArea(out var clientArea);
|
||||||
|
Render2D.PushClip(ref clientArea);
|
||||||
|
DrawChildren();
|
||||||
|
Render2D.PopClip();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
DrawChildren();
|
||||||
|
}
|
||||||
|
|
||||||
|
Render2D.PopTransform();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void GetDesireClientArea(out Rectangle rect)
|
||||||
|
{
|
||||||
|
// Scale the area for the client controls
|
||||||
|
rect = new Rectangle(Float2.Zero, Size / ContentScale);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override bool IntersectsContent(ref Float2 locationParent, out Float2 location)
|
||||||
|
{
|
||||||
|
// Skip local PointFromParent but use base code
|
||||||
|
location = base.PointFromParent(ref locationParent);
|
||||||
|
return ContainsPoint(ref location);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override bool IntersectsChildContent(Control child, Float2 location, out Float2 childSpaceLocation)
|
||||||
|
{
|
||||||
|
location /= ContentScale;
|
||||||
|
return base.IntersectsChildContent(child, location, out childSpaceLocation);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override bool ContainsPoint(ref Float2 location, bool precise = false)
|
||||||
|
{
|
||||||
|
if (precise) // Ignore as utility-only element
|
||||||
|
return false;
|
||||||
|
return base.ContainsPoint(ref location, precise);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override bool RayCast(ref Float2 location, out Control hit)
|
||||||
|
{
|
||||||
|
var p = location / ContentScale;
|
||||||
|
if (RayCastChildren(ref p, out hit))
|
||||||
|
return true;
|
||||||
|
return base.RayCast(ref location, out hit);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override Float2 PointToParent(ref Float2 location)
|
||||||
|
{
|
||||||
|
var result = base.PointToParent(ref location);
|
||||||
|
result *= ContentScale;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override Float2 PointFromParent(ref Float2 location)
|
||||||
|
{
|
||||||
|
var result = base.PointFromParent(ref location);
|
||||||
|
result /= ContentScale;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Provides in-editor play mode simulation.
|
/// Provides in-editor play mode simulation.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <seealso cref="FlaxEditor.Windows.EditorWindow" />
|
/// <seealso cref="FlaxEditor.Windows.EditorWindow" />
|
||||||
public class GameWindow : EditorWindow
|
public class GameWindow : EditorWindow
|
||||||
{
|
{
|
||||||
private readonly RenderOutputControl _viewport;
|
private readonly ScaledRenderOutputControl _viewport;
|
||||||
private readonly GameRoot _guiRoot;
|
private readonly GameRoot _guiRoot;
|
||||||
private bool _showGUI = true, _editGUI = true;
|
private bool _showGUI = true, _editGUI = true;
|
||||||
private bool _showDebugDraw = false;
|
private bool _showDebugDraw = false;
|
||||||
@@ -77,7 +177,7 @@ namespace FlaxEditor.Windows
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the viewport.
|
/// Gets the viewport.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public RenderOutputControl Viewport => _viewport;
|
public ScaledRenderOutputControl Viewport => _viewport;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether show game GUI in the view or keep it hidden.
|
/// Gets or sets a value indicating whether show game GUI in the view or keep it hidden.
|
||||||
@@ -295,7 +395,7 @@ namespace FlaxEditor.Windows
|
|||||||
var task = MainRenderTask.Instance;
|
var task = MainRenderTask.Instance;
|
||||||
|
|
||||||
// Setup viewport
|
// Setup viewport
|
||||||
_viewport = new RenderOutputControl(task)
|
_viewport = new ScaledRenderOutputControl(task)
|
||||||
{
|
{
|
||||||
AnchorPreset = AnchorPresets.StretchAll,
|
AnchorPreset = AnchorPresets.StretchAll,
|
||||||
Offsets = Margin.Zero,
|
Offsets = Margin.Zero,
|
||||||
@@ -396,11 +496,8 @@ namespace FlaxEditor.Windows
|
|||||||
{
|
{
|
||||||
if (v == null)
|
if (v == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (v.Size.Y <= 0 || v.Size.X <= 0)
|
if (v.Size.Y <= 0 || v.Size.X <= 0)
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
if (string.Equals(v.Label, "Free Aspect", StringComparison.Ordinal) && v.Size == new Int2(1, 1))
|
if (string.Equals(v.Label, "Free Aspect", StringComparison.Ordinal) && v.Size == new Int2(1, 1))
|
||||||
{
|
{
|
||||||
@@ -448,15 +545,7 @@ namespace FlaxEditor.Windows
|
|||||||
|
|
||||||
private void ResizeViewport()
|
private void ResizeViewport()
|
||||||
{
|
{
|
||||||
if (!_freeAspect)
|
_windowAspectRatio = _freeAspect ? 1 : Width / Height;
|
||||||
{
|
|
||||||
_windowAspectRatio = Width / Height;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_windowAspectRatio = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
var scaleWidth = _viewportAspectRatio / _windowAspectRatio;
|
var scaleWidth = _viewportAspectRatio / _windowAspectRatio;
|
||||||
var scaleHeight = _windowAspectRatio / _viewportAspectRatio;
|
var scaleHeight = _windowAspectRatio / _viewportAspectRatio;
|
||||||
|
|
||||||
@@ -468,6 +557,24 @@ namespace FlaxEditor.Windows
|
|||||||
{
|
{
|
||||||
_viewport.Bounds = new Rectangle(Width * (1 - scaleWidth) / 2, 0, Width * scaleWidth, Height);
|
_viewport.Bounds = new Rectangle(Width * (1 - scaleWidth) / 2, 0, Width * scaleWidth, Height);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_viewport.KeepAspectRatio)
|
||||||
|
{
|
||||||
|
var resolution = _viewport.CustomResolution.HasValue ? (Float2)_viewport.CustomResolution.Value : Size;
|
||||||
|
if (scaleHeight < 1)
|
||||||
|
{
|
||||||
|
_viewport.ContentScale = _viewport.Width / resolution.X;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_viewport.ContentScale = _viewport.Height / resolution.Y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_viewport.ContentScale = 1;
|
||||||
|
}
|
||||||
|
|
||||||
_viewport.SyncBackbufferSize();
|
_viewport.SyncBackbufferSize();
|
||||||
PerformLayout();
|
PerformLayout();
|
||||||
}
|
}
|
||||||
@@ -907,6 +1014,7 @@ namespace FlaxEditor.Windows
|
|||||||
return child.OnNavigate(direction, Float2.Zero, this, visited);
|
return child.OnNavigate(direction, Float2.Zero, this, visited);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -957,7 +1065,7 @@ namespace FlaxEditor.Windows
|
|||||||
else
|
else
|
||||||
_defaultScaleActiveIndex = 0;
|
_defaultScaleActiveIndex = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_customScaleActiveIndex != -1)
|
if (_customScaleActiveIndex != -1)
|
||||||
{
|
{
|
||||||
var options = Editor.UI.CustomViewportScaleOptions;
|
var options = Editor.UI.CustomViewportScaleOptions;
|
||||||
|
|||||||
@@ -296,13 +296,15 @@ namespace FlaxEditor.Windows.Profiler
|
|||||||
var resources = _resources.Get(_memoryUsageChart.SelectedSampleIndex);
|
var resources = _resources.Get(_memoryUsageChart.SelectedSampleIndex);
|
||||||
if (resources == null || resources.Length == 0)
|
if (resources == null || resources.Length == 0)
|
||||||
return;
|
return;
|
||||||
var resourcesOrdered = resources.OrderByDescending(x => x.MemoryUsage);
|
var resourcesOrdered = resources.OrderByDescending(x => x?.MemoryUsage ?? 0);
|
||||||
|
|
||||||
// Add rows
|
// Add rows
|
||||||
var rowColor2 = Style.Current.Background * 1.4f;
|
var rowColor2 = Style.Current.Background * 1.4f;
|
||||||
int rowIndex = 0;
|
int rowIndex = 0;
|
||||||
foreach (var e in resourcesOrdered)
|
foreach (var e in resourcesOrdered)
|
||||||
{
|
{
|
||||||
|
if (e == null)
|
||||||
|
continue;
|
||||||
ClickableRow row;
|
ClickableRow row;
|
||||||
if (_tableRowsCache.Count != 0)
|
if (_tableRowsCache.Count != 0)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -137,6 +137,7 @@ const Char* SplashScreenQuotes[] =
|
|||||||
TEXT("Good Luck Have Fun"),
|
TEXT("Good Luck Have Fun"),
|
||||||
TEXT("GG Well Played"),
|
TEXT("GG Well Played"),
|
||||||
TEXT("Now with documentation."),
|
TEXT("Now with documentation."),
|
||||||
|
TEXT("We do this not because it is easy,\nbut because we thought it would be easy"),
|
||||||
};
|
};
|
||||||
|
|
||||||
SplashScreen::~SplashScreen()
|
SplashScreen::~SplashScreen()
|
||||||
|
|||||||
@@ -246,11 +246,19 @@ void AnimGraphExecutor::ProcessAnimEvents(AnimGraphNode* node, bool loop, float
|
|||||||
const float duration = k.Value.Duration > 1 ? k.Value.Duration : 0.0f;
|
const float duration = k.Value.Duration > 1 ? k.Value.Duration : 0.0f;
|
||||||
#define ADD_OUTGOING_EVENT(type) context.Data->OutgoingEvents.Add({ k.Value.Instance, (AnimatedModel*)context.Data->Object, anim, eventTime, eventDeltaTime, AnimGraphInstanceData::OutgoingEvent::type })
|
#define ADD_OUTGOING_EVENT(type) context.Data->OutgoingEvents.Add({ k.Value.Instance, (AnimatedModel*)context.Data->Object, anim, eventTime, eventDeltaTime, AnimGraphInstanceData::OutgoingEvent::type })
|
||||||
if ((k.Time <= eventTimeMax && eventTimeMin <= k.Time + duration
|
if ((k.Time <= eventTimeMax && eventTimeMin <= k.Time + duration
|
||||||
&& (Math::FloorToInt(animPos) != 0 && Math::CeilToInt(animPrevPos) != Math::CeilToInt(anim->GetDuration()) && Math::FloorToInt(animPrevPos) != 0 && Math::CeilToInt(animPos) != Math::CeilToInt(anim->GetDuration())))
|
&& (Math::FloorToInt(animPos) != 0 && Math::CeilToInt(animPrevPos) != Math::CeilToInt(anim->GetDuration())
|
||||||
|
&& Math::FloorToInt(animPrevPos) != 0 && Math::CeilToInt(animPos) != Math::CeilToInt(anim->GetDuration())))
|
||||||
// Handle the edge case of an event on 0 or on max animation duration during looping
|
// Handle the edge case of an event on 0 or on max animation duration during looping
|
||||||
|| (loop && duration == 0.0f && Math::CeilToInt(animPos) == Math::CeilToInt(anim->GetDuration()) && k.Time == anim->GetDuration())
|
|| (!loop && duration == 0.0f && Math::CeilToInt(animPos) == Math::CeilToInt(anim->GetDuration()) && Math::CeilToInt(animPrevPos) == Math::CeilToInt(anim->GetDuration()) - 1 && Math::NearEqual(k.Time, anim->GetDuration()))
|
||||||
|| (loop && Math::FloorToInt(animPos) == 0 && Math::CeilToInt(animPrevPos) == Math::CeilToInt(anim->GetDuration()) && k.Time == 0.0f)
|
|| (loop && Math::FloorToInt(animPos) == 0 && Math::CeilToInt(animPrevPos) == Math::CeilToInt(anim->GetDuration()) && k.Time == 0.0f)
|
||||||
|| (loop && Math::FloorToInt(animPrevPos) == 0 && Math::CeilToInt(animPos) == Math::CeilToInt(anim->GetDuration()) && k.Time == 0.0f)
|
|| (loop && Math::FloorToInt(animPrevPos) == 0 && Math::CeilToInt(animPos) == Math::CeilToInt(anim->GetDuration()) && k.Time == 0.0f)
|
||||||
|
|| (loop && Math::FloorToInt(animPos) == 0 && Math::CeilToInt(animPrevPos) == Math::CeilToInt(anim->GetDuration()) && Math::NearEqual(k.Time, anim->GetDuration()))
|
||||||
|
|| (loop && Math::FloorToInt(animPrevPos) == 0 && Math::CeilToInt(animPos) == Math::CeilToInt(anim->GetDuration()) && Math::NearEqual(k.Time, anim->GetDuration()))
|
||||||
|
|| (Math::FloorToInt(animPos) == 1 && Math::FloorToInt(animPrevPos) == 0 && k.Time == 1.0f)
|
||||||
|
|| (Math::FloorToInt(animPos) == 0 && Math::FloorToInt(animPrevPos) == 1 && k.Time == 1.0f)
|
||||||
|
|| (Math::CeilToInt(animPos) == Math::CeilToInt(anim->GetDuration()) && Math::CeilToInt(animPrevPos) == Math::CeilToInt(anim->GetDuration()) - 1 && Math::NearEqual(k.Time, anim->GetDuration() - 1.0f))
|
||||||
|
|| (Math::CeilToInt(animPos) == Math::CeilToInt(anim->GetDuration()) - 1 && Math::CeilToInt(animPrevPos) == Math::CeilToInt(anim->GetDuration()) && Math::NearEqual(k.Time, anim->GetDuration() - 1.0f))
|
||||||
|
|| (Math::FloorToInt(animPos) == 0 && Math::FloorToInt(animPrevPos) == 0 && k.Time == 0.0f)
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
int32 stateIndex = -1;
|
int32 stateIndex = -1;
|
||||||
@@ -2433,10 +2441,14 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
|
|||||||
{
|
{
|
||||||
if (bucket.LoopsLeft == 0)
|
if (bucket.LoopsLeft == 0)
|
||||||
{
|
{
|
||||||
// End playing animation
|
// End playing animation and reset bucket params
|
||||||
value = tryGetValue(node->GetBox(1), Value::Null);
|
value = tryGetValue(node->GetBox(1), Value::Null);
|
||||||
bucket.Index = -1;
|
bucket.Index = -1;
|
||||||
slot.Animation = nullptr;
|
slot.Animation = nullptr;
|
||||||
|
bucket.TimePosition = 0.0f;
|
||||||
|
bucket.BlendInPosition = 0.0f;
|
||||||
|
bucket.BlendOutPosition = 0.0f;
|
||||||
|
bucket.LoopsDone = 0;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2545,9 +2557,15 @@ void AnimGraphExecutor::ProcessGroupFunction(Box* boxBase, Node* node, Value& va
|
|||||||
// Function Input
|
// Function Input
|
||||||
case 1:
|
case 1:
|
||||||
{
|
{
|
||||||
|
// Skip when graph is too small (eg. preview) and fallback with default value from the function graph
|
||||||
|
if (context.GraphStack.Count() < 2)
|
||||||
|
{
|
||||||
|
value = tryGetValue(node->TryGetBox(1), Value::Zero);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// Find the function call
|
// Find the function call
|
||||||
AnimGraphNode* functionCallNode = nullptr;
|
AnimGraphNode* functionCallNode = nullptr;
|
||||||
ASSERT(context.GraphStack.Count() >= 2);
|
|
||||||
Graph* graph;
|
Graph* graph;
|
||||||
for (int32 i = context.CallStack.Count() - 1; i >= 0; i--)
|
for (int32 i = context.CallStack.Count() - 1; i >= 0; i--)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -666,7 +666,7 @@ void Asset::onLoaded()
|
|||||||
{
|
{
|
||||||
onLoaded_MainThread();
|
onLoaded_MainThread();
|
||||||
}
|
}
|
||||||
else if (OnLoaded.IsBinded())
|
else if (OnLoaded.IsBinded() || _references.HasItems())
|
||||||
{
|
{
|
||||||
Function<void()> action;
|
Function<void()> action;
|
||||||
action.Bind<Asset, &Asset::onLoaded>(this);
|
action.Bind<Asset, &Asset::onLoaded>(this);
|
||||||
|
|||||||
@@ -218,10 +218,14 @@ Asset::LoadResult MaterialInstance::load()
|
|||||||
Guid baseMaterialId;
|
Guid baseMaterialId;
|
||||||
headerStream.Read(baseMaterialId);
|
headerStream.Read(baseMaterialId);
|
||||||
auto baseMaterial = Content::LoadAsync<MaterialBase>(baseMaterialId);
|
auto baseMaterial = Content::LoadAsync<MaterialBase>(baseMaterialId);
|
||||||
|
if (baseMaterial)
|
||||||
|
baseMaterial->AddReference();
|
||||||
|
|
||||||
// Load parameters
|
// Load parameters
|
||||||
if (Params.Load(&headerStream))
|
if (Params.Load(&headerStream))
|
||||||
{
|
{
|
||||||
|
if (baseMaterial)
|
||||||
|
baseMaterial->RemoveReference();
|
||||||
LOG(Warning, "Cannot load material parameters.");
|
LOG(Warning, "Cannot load material parameters.");
|
||||||
return LoadResult::CannotLoadData;
|
return LoadResult::CannotLoadData;
|
||||||
}
|
}
|
||||||
@@ -239,6 +243,8 @@ Asset::LoadResult MaterialInstance::load()
|
|||||||
ParamsChanged();
|
ParamsChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (baseMaterial)
|
||||||
|
baseMaterial->RemoveReference();
|
||||||
return LoadResult::Ok;
|
return LoadResult::Ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -262,6 +262,7 @@ bool Model::GenerateSDF(float resolutionScale, int32 lodIndex, bool cacheData, f
|
|||||||
LOG(Warning, "Cannot generate SDF for virtual models on a main thread.");
|
LOG(Warning, "Cannot generate SDF for virtual models on a main thread.");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
auto chunkLocks = Storage ? Storage->Lock() : FlaxStorage::LockData();
|
||||||
lodIndex = Math::Clamp(lodIndex, HighestResidentLODIndex(), LODs.Count() - 1);
|
lodIndex = Math::Clamp(lodIndex, HighestResidentLODIndex(), LODs.Count() - 1);
|
||||||
|
|
||||||
// Generate SDF
|
// Generate SDF
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ public:
|
|||||||
model->GetLODData(_lodIndex, data);
|
model->GetLODData(_lodIndex, data);
|
||||||
if (data.IsInvalid())
|
if (data.IsInvalid())
|
||||||
{
|
{
|
||||||
LOG(Warning, "Missing data chunk");
|
LOG(Warning, "Missing data chunk with LOD{} for model '{}'", _lodIndex, model->ToString());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
MemoryReadStream stream(data.Get(), data.Length());
|
MemoryReadStream stream(data.Get(), data.Length());
|
||||||
@@ -234,6 +234,7 @@ bool ModelBase::Save(bool withMeshDataFromGpu, const StringView& path)
|
|||||||
LOG(Error, "To save virtual model asset you need to specify 'withMeshDataFromGpu' (it has no other storage container to get data).");
|
LOG(Error, "To save virtual model asset you need to specify 'withMeshDataFromGpu' (it has no other storage container to get data).");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
auto chunkLocks = Storage ? Storage->Lock() : FlaxStorage::LockData();
|
||||||
ScopeLock lock(Locker);
|
ScopeLock lock(Locker);
|
||||||
|
|
||||||
// Use a temporary chunks for data storage for virtual assets
|
// Use a temporary chunks for data storage for virtual assets
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ public:
|
|||||||
/// <param name="id">The asset id.</param>
|
/// <param name="id">The asset id.</param>
|
||||||
/// <returns>Loaded asset of null.</returns>
|
/// <returns>Loaded asset of null.</returns>
|
||||||
template<typename T>
|
template<typename T>
|
||||||
T* LoadAsync(const Guid& id)
|
T* Load(const Guid& id)
|
||||||
{
|
{
|
||||||
for (auto& e : *this)
|
for (auto& e : *this)
|
||||||
{
|
{
|
||||||
@@ -26,8 +26,10 @@ public:
|
|||||||
return (T*)e.Get();
|
return (T*)e.Get();
|
||||||
}
|
}
|
||||||
auto asset = (T*)::LoadAsset(id, T::TypeInitializer);
|
auto asset = (T*)::LoadAsset(id, T::TypeInitializer);
|
||||||
if (asset)
|
if (asset && !asset->WaitForLoaded())
|
||||||
Add(asset);
|
Add(asset);
|
||||||
|
else
|
||||||
|
asset = nullptr;
|
||||||
return asset;
|
return asset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,7 @@
|
|||||||
#include "Engine/Profiler/ProfilerCPU.h"
|
#include "Engine/Profiler/ProfilerCPU.h"
|
||||||
#include "Engine/Profiler/ProfilerMemory.h"
|
#include "Engine/Profiler/ProfilerMemory.h"
|
||||||
#include "Engine/Scripting/ManagedCLR/MClass.h"
|
#include "Engine/Scripting/ManagedCLR/MClass.h"
|
||||||
|
#include "Engine/Scripting/Internal/InternalCalls.h"
|
||||||
#include "Engine/Scripting/Scripting.h"
|
#include "Engine/Scripting/Scripting.h"
|
||||||
#if USE_EDITOR
|
#if USE_EDITOR
|
||||||
#include "Editor/Editor.h"
|
#include "Editor/Editor.h"
|
||||||
@@ -346,17 +347,21 @@ int32 LoadingThread::Run()
|
|||||||
ContentLoadTask* task;
|
ContentLoadTask* task;
|
||||||
ThisLoadThread = this;
|
ThisLoadThread = this;
|
||||||
|
|
||||||
|
MONO_THREAD_INFO_TYPE* monoThreadInfo = nullptr;
|
||||||
while (Platform::AtomicRead(&_exitFlag) == 0)
|
while (Platform::AtomicRead(&_exitFlag) == 0)
|
||||||
{
|
{
|
||||||
if (LoadTasks.try_dequeue(task))
|
if (LoadTasks.try_dequeue(task))
|
||||||
{
|
{
|
||||||
Run(task);
|
Run(task);
|
||||||
|
MONO_THREAD_INFO_GET(monoThreadInfo);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
MONO_ENTER_GC_SAFE_WITH_INFO(monoThreadInfo);
|
||||||
LoadTasksMutex.Lock();
|
LoadTasksMutex.Lock();
|
||||||
LoadTasksSignal.Wait(LoadTasksMutex);
|
LoadTasksSignal.Wait(LoadTasksMutex);
|
||||||
LoadTasksMutex.Unlock();
|
LoadTasksMutex.Unlock();
|
||||||
|
MONO_EXIT_GC_SAFE_WITH_INFO;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -700,6 +705,7 @@ Asset* Content::GetAsset(const StringView& outputPath)
|
|||||||
{
|
{
|
||||||
if (outputPath.IsEmpty())
|
if (outputPath.IsEmpty())
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
PROFILE_CPU();
|
||||||
ScopeLock lock(AssetsLocker);
|
ScopeLock lock(AssetsLocker);
|
||||||
for (auto i = Assets.Begin(); i.IsNotEnd(); ++i)
|
for (auto i = Assets.Begin(); i.IsNotEnd(); ++i)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -87,6 +87,10 @@ public:
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
double LastAccessTime = 0.0;
|
double LastAccessTime = 0.0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Flag set to indicate that chunk is during loading (atomic access to sync multiple reading threads).
|
||||||
|
/// </summary>
|
||||||
|
int64 IsLoading = 0;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The chunk data.
|
/// The chunk data.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -146,7 +150,7 @@ public:
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
FORCE_INLINE bool IsLoaded() const
|
FORCE_INLINE bool IsLoaded() const
|
||||||
{
|
{
|
||||||
return Data.IsValid();
|
return Data.IsValid() && Platform::AtomicRead(&IsLoading) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -154,7 +158,7 @@ public:
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
FORCE_INLINE bool IsMissing() const
|
FORCE_INLINE bool IsMissing() const
|
||||||
{
|
{
|
||||||
return Data.IsInvalid();
|
return !IsLoaded();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#include "FlaxPackage.h"
|
#include "FlaxPackage.h"
|
||||||
#include "ContentStorageManager.h"
|
#include "ContentStorageManager.h"
|
||||||
#include "Engine/Core/Log.h"
|
#include "Engine/Core/Log.h"
|
||||||
|
#include "Engine/Core/ScopeExit.h"
|
||||||
#include "Engine/Core/Types/TimeSpan.h"
|
#include "Engine/Core/Types/TimeSpan.h"
|
||||||
#include "Engine/Platform/File.h"
|
#include "Engine/Platform/File.h"
|
||||||
#include "Engine/Profiler/ProfilerCPU.h"
|
#include "Engine/Profiler/ProfilerCPU.h"
|
||||||
@@ -74,8 +75,6 @@ FlaxChunk* FlaxChunk::Clone() const
|
|||||||
|
|
||||||
const int32 FlaxStorage::MagicCode = 1180124739;
|
const int32 FlaxStorage::MagicCode = 1180124739;
|
||||||
|
|
||||||
FlaxStorage::LockData FlaxStorage::LockData::Invalid(nullptr);
|
|
||||||
|
|
||||||
struct Header
|
struct Header
|
||||||
{
|
{
|
||||||
int32 MagicCode;
|
int32 MagicCode;
|
||||||
@@ -246,6 +245,7 @@ FlaxStorage::~FlaxStorage()
|
|||||||
ASSERT(IsDisposed());
|
ASSERT(IsDisposed());
|
||||||
CHECK(_chunksLock == 0);
|
CHECK(_chunksLock == 0);
|
||||||
CHECK(_refCount == 0);
|
CHECK(_refCount == 0);
|
||||||
|
CHECK(_isUnloadingData == 0);
|
||||||
ASSERT(_chunks.IsEmpty());
|
ASSERT(_chunks.IsEmpty());
|
||||||
|
|
||||||
#if USE_EDITOR
|
#if USE_EDITOR
|
||||||
@@ -261,6 +261,22 @@ FlaxStorage::~FlaxStorage()
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void FlaxStorage::LockChunks()
|
||||||
|
{
|
||||||
|
RETRY:
|
||||||
|
Platform::InterlockedIncrement(&_chunksLock);
|
||||||
|
if (Platform::AtomicRead(&_isUnloadingData) != 0)
|
||||||
|
{
|
||||||
|
// Someone else is closing file handles or freeing chunks so wait for it to finish and retry
|
||||||
|
Platform::InterlockedDecrement(&_chunksLock);
|
||||||
|
do
|
||||||
|
{
|
||||||
|
Platform::Sleep(1);
|
||||||
|
} while (Platform::AtomicRead(&_isUnloadingData) != 0);
|
||||||
|
goto RETRY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
FlaxStorage::LockData FlaxStorage::LockSafe()
|
FlaxStorage::LockData FlaxStorage::LockSafe()
|
||||||
{
|
{
|
||||||
auto lock = LockData(this);
|
auto lock = LockData(this);
|
||||||
@@ -689,7 +705,6 @@ bool FlaxStorage::LoadAssetHeader(const Guid& id, AssetInitData& data)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load header
|
|
||||||
return LoadAssetHeader(e, data);
|
return LoadAssetHeader(e, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -699,7 +714,10 @@ bool FlaxStorage::LoadAssetChunk(FlaxChunk* chunk)
|
|||||||
ASSERT(IsLoaded());
|
ASSERT(IsLoaded());
|
||||||
ASSERT(chunk != nullptr && _chunks.Contains(chunk));
|
ASSERT(chunk != nullptr && _chunks.Contains(chunk));
|
||||||
|
|
||||||
// Check if already loaded
|
// Protect against loading the same chunk from multiple threads at once
|
||||||
|
while (Platform::InterlockedCompareExchange(&chunk->IsLoading, 1, 0) != 0)
|
||||||
|
Platform::Sleep(1);
|
||||||
|
SCOPE_EXIT{ Platform::AtomicStore(&chunk->IsLoading, 0); };
|
||||||
if (chunk->IsLoaded())
|
if (chunk->IsLoaded())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
@@ -776,12 +794,10 @@ bool FlaxStorage::LoadAssetChunk(FlaxChunk* chunk)
|
|||||||
// Raw data
|
// Raw data
|
||||||
chunk->Data.Read(stream, size);
|
chunk->Data.Read(stream, size);
|
||||||
}
|
}
|
||||||
ASSERT(chunk->IsLoaded());
|
|
||||||
chunk->RegisterUsage();
|
chunk->RegisterUsage();
|
||||||
}
|
}
|
||||||
|
|
||||||
UnlockChunks();
|
UnlockChunks();
|
||||||
|
|
||||||
return failed;
|
return failed;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1420,10 +1436,12 @@ FileReadStream* FlaxStorage::OpenFile()
|
|||||||
|
|
||||||
bool FlaxStorage::CloseFileHandles()
|
bool FlaxStorage::CloseFileHandles()
|
||||||
{
|
{
|
||||||
|
// Guard the whole process so if new thread wants to lock the chunks will need to wait for this to end
|
||||||
|
Platform::InterlockedIncrement(&_isUnloadingData);
|
||||||
|
SCOPE_EXIT{ Platform::InterlockedDecrement(&_isUnloadingData); };
|
||||||
|
|
||||||
if (Platform::AtomicRead(&_chunksLock) == 0 && Platform::AtomicRead(&_files) == 0)
|
if (Platform::AtomicRead(&_chunksLock) == 0 && Platform::AtomicRead(&_files) == 0)
|
||||||
{
|
return false; // Early out when no files are opened
|
||||||
return false;
|
|
||||||
}
|
|
||||||
PROFILE_CPU();
|
PROFILE_CPU();
|
||||||
PROFILE_MEM(ContentFiles);
|
PROFILE_MEM(ContentFiles);
|
||||||
|
|
||||||
@@ -1496,9 +1514,21 @@ void FlaxStorage::Tick(double time)
|
|||||||
{
|
{
|
||||||
auto chunk = _chunks.Get()[i];
|
auto chunk = _chunks.Get()[i];
|
||||||
const bool wasUsed = (time - chunk->LastAccessTime) < unusedDataChunksLifetime;
|
const bool wasUsed = (time - chunk->LastAccessTime) < unusedDataChunksLifetime;
|
||||||
if (!wasUsed && chunk->IsLoaded() && EnumHasNoneFlags(chunk->Flags, FlaxChunkFlags::KeepInMemory))
|
if (!wasUsed &&
|
||||||
|
chunk->IsLoaded() &&
|
||||||
|
EnumHasNoneFlags(chunk->Flags, FlaxChunkFlags::KeepInMemory) &&
|
||||||
|
Platform::AtomicRead(&chunk->IsLoading) == 0)
|
||||||
{
|
{
|
||||||
|
// Guard the unloading so if other thread wants to lock the chunks will need to wait for this to end
|
||||||
|
Platform::InterlockedIncrement(&_isUnloadingData);
|
||||||
|
if (Platform::AtomicRead(&_chunksLock) != 0 || Platform::AtomicRead(&chunk->IsLoading) != 0)
|
||||||
|
{
|
||||||
|
// Someone started loading so skip ticking
|
||||||
|
Platform::InterlockedDecrement(&_isUnloadingData);
|
||||||
|
return;
|
||||||
|
}
|
||||||
chunk->Unload();
|
chunk->Unload();
|
||||||
|
Platform::InterlockedDecrement(&_isUnloadingData);
|
||||||
}
|
}
|
||||||
wasAnyUsed |= wasUsed;
|
wasAnyUsed |= wasUsed;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,6 +90,7 @@ protected:
|
|||||||
int64 _refCount = 0;
|
int64 _refCount = 0;
|
||||||
int64 _chunksLock = 0;
|
int64 _chunksLock = 0;
|
||||||
int64 _files = 0;
|
int64 _files = 0;
|
||||||
|
int64 _isUnloadingData = 0;
|
||||||
double _lastRefLostTime;
|
double _lastRefLostTime;
|
||||||
CriticalSection _loadLocker;
|
CriticalSection _loadLocker;
|
||||||
|
|
||||||
@@ -129,10 +130,7 @@ public:
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Locks the storage chunks data to prevent disposing them. Also ensures that file handles won't be closed while chunks are locked.
|
/// Locks the storage chunks data to prevent disposing them. Also ensures that file handles won't be closed while chunks are locked.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
FORCE_INLINE void LockChunks()
|
void LockChunks();
|
||||||
{
|
|
||||||
Platform::InterlockedIncrement(&_chunksLock);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unlocks the storage chunks data.
|
/// Unlocks the storage chunks data.
|
||||||
@@ -148,7 +146,6 @@ public:
|
|||||||
struct LockData
|
struct LockData
|
||||||
{
|
{
|
||||||
friend FlaxStorage;
|
friend FlaxStorage;
|
||||||
static LockData Invalid;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
FlaxStorage* _storage;
|
FlaxStorage* _storage;
|
||||||
@@ -161,6 +158,11 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
LockData()
|
||||||
|
: _storage(nullptr)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
LockData(const LockData& other)
|
LockData(const LockData& other)
|
||||||
: _storage(other._storage)
|
: _storage(other._storage)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -658,7 +658,10 @@ public:
|
|||||||
--_count;
|
--_count;
|
||||||
T* data = _allocation.Get();
|
T* data = _allocation.Get();
|
||||||
if (index < _count)
|
if (index < _count)
|
||||||
Memory::MoveAssignItems(data + index, data + (index + 1), _count - index);
|
{
|
||||||
|
for (int32 i = index; i < _count; i++)
|
||||||
|
data[i] = MoveTemp(data[i + 1]);
|
||||||
|
}
|
||||||
Memory::DestructItems(data + _count, 1);
|
Memory::DestructItems(data + _count, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -409,27 +409,36 @@ protected:
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Rebuild entire table completely
|
// Rebuild entire table completely
|
||||||
|
const int32 elementsCount = _elementsCount;
|
||||||
|
const int32 oldSize = _size;
|
||||||
AllocationData oldAllocation;
|
AllocationData oldAllocation;
|
||||||
AllocationUtils::MoveToEmpty<BucketType, AllocationType>(oldAllocation, _allocation, _size, _size);
|
AllocationUtils::MoveToEmpty<BucketType, AllocationType>(oldAllocation, _allocation, oldSize, oldSize);
|
||||||
_allocation.Allocate(_size);
|
_allocation.Allocate(_size);
|
||||||
BucketType* data = _allocation.Get();
|
BucketType* data = _allocation.Get();
|
||||||
for (int32 i = 0; i < _size; ++i)
|
for (int32 i = 0; i < oldSize; ++i)
|
||||||
data[i]._state = HashSetBucketState::Empty;
|
data[i]._state = HashSetBucketState::Empty;
|
||||||
BucketType* oldData = oldAllocation.Get();
|
BucketType* oldData = oldAllocation.Get();
|
||||||
FindPositionResult pos;
|
FindPositionResult pos;
|
||||||
for (int32 i = 0; i < _size; ++i)
|
for (int32 i = 0; i < oldSize; ++i)
|
||||||
{
|
{
|
||||||
BucketType& oldBucket = oldData[i];
|
BucketType& oldBucket = oldData[i];
|
||||||
if (oldBucket.IsOccupied())
|
if (oldBucket.IsOccupied())
|
||||||
{
|
{
|
||||||
FindPosition(oldBucket.GetKey(), pos);
|
FindPosition(oldBucket.GetKey(), pos);
|
||||||
ASSERT(pos.FreeSlotIndex != -1);
|
if (pos.FreeSlotIndex == -1)
|
||||||
|
{
|
||||||
|
// Grow and retry to handle pathological cases (eg. heavy collisions)
|
||||||
|
EnsureCapacity(_size + 1, true);
|
||||||
|
FindPosition(oldBucket.GetKey(), pos);
|
||||||
|
ASSERT(pos.FreeSlotIndex != -1);
|
||||||
|
}
|
||||||
BucketType& bucket = _allocation.Get()[pos.FreeSlotIndex];
|
BucketType& bucket = _allocation.Get()[pos.FreeSlotIndex];
|
||||||
bucket = MoveTemp(oldBucket);
|
bucket = MoveTemp(oldBucket);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (int32 i = 0; i < _size; ++i)
|
for (int32 i = 0; i < oldSize; ++i)
|
||||||
oldData[i].Free();
|
oldData[i].Free();
|
||||||
|
_elementsCount = elementsCount;
|
||||||
}
|
}
|
||||||
_deletedCount = 0;
|
_deletedCount = 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,9 @@
|
|||||||
|
|
||||||
#if defined(__clang__)
|
#if defined(__clang__)
|
||||||
|
|
||||||
#define DLLEXPORT __attribute__ ((__visibility__ ("default")))
|
#define DLLEXPORT __attribute__((__visibility__("default")))
|
||||||
#define DLLIMPORT
|
#define DLLIMPORT
|
||||||
|
#define USED __attribute__((used))
|
||||||
#define THREADLOCAL __thread
|
#define THREADLOCAL __thread
|
||||||
#define STDCALL __attribute__((stdcall))
|
#define STDCALL __attribute__((stdcall))
|
||||||
#define CDECL __attribute__((cdecl))
|
#define CDECL __attribute__((cdecl))
|
||||||
@@ -19,7 +20,7 @@
|
|||||||
#define PACK_BEGIN()
|
#define PACK_BEGIN()
|
||||||
#define PACK_END() __attribute__((__packed__))
|
#define PACK_END() __attribute__((__packed__))
|
||||||
#define ALIGN_BEGIN(_align)
|
#define ALIGN_BEGIN(_align)
|
||||||
#define ALIGN_END(_align) __attribute__( (aligned(_align) ) )
|
#define ALIGN_END(_align) __attribute__((aligned(_align)))
|
||||||
#define OFFSET_OF(X, Y) __builtin_offsetof(X, Y)
|
#define OFFSET_OF(X, Y) __builtin_offsetof(X, Y)
|
||||||
#define PRAGMA_DISABLE_DEPRECATION_WARNINGS \
|
#define PRAGMA_DISABLE_DEPRECATION_WARNINGS \
|
||||||
_Pragma("clang diagnostic push") \
|
_Pragma("clang diagnostic push") \
|
||||||
@@ -37,8 +38,9 @@
|
|||||||
|
|
||||||
#elif defined(__GNUC__)
|
#elif defined(__GNUC__)
|
||||||
|
|
||||||
#define DLLEXPORT __attribute__ ((__visibility__ ("default")))
|
#define DLLEXPORT __attribute__((__visibility__("default")))
|
||||||
#define DLLIMPORT
|
#define DLLIMPORT
|
||||||
|
#define USED __attribute__((used))
|
||||||
#define THREADLOCAL __thread
|
#define THREADLOCAL __thread
|
||||||
#define STDCALL __attribute__((stdcall))
|
#define STDCALL __attribute__((stdcall))
|
||||||
#define CDECL __attribute__((cdecl))
|
#define CDECL __attribute__((cdecl))
|
||||||
@@ -52,7 +54,7 @@
|
|||||||
#define PACK_BEGIN()
|
#define PACK_BEGIN()
|
||||||
#define PACK_END() __attribute__((__packed__))
|
#define PACK_END() __attribute__((__packed__))
|
||||||
#define ALIGN_BEGIN(_align)
|
#define ALIGN_BEGIN(_align)
|
||||||
#define ALIGN_END(_align) __attribute__( (aligned(_align) ) )
|
#define ALIGN_END(_align) __attribute__((aligned(_align)))
|
||||||
#define OFFSET_OF(X, Y) __builtin_offsetof(X, Y)
|
#define OFFSET_OF(X, Y) __builtin_offsetof(X, Y)
|
||||||
#define PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
#define PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
||||||
#define PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
#define PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
||||||
@@ -67,6 +69,7 @@
|
|||||||
|
|
||||||
#define DLLEXPORT __declspec(dllexport)
|
#define DLLEXPORT __declspec(dllexport)
|
||||||
#define DLLIMPORT __declspec(dllimport)
|
#define DLLIMPORT __declspec(dllimport)
|
||||||
|
#define USED
|
||||||
#define THREADLOCAL __declspec(thread)
|
#define THREADLOCAL __declspec(thread)
|
||||||
#define STDCALL __stdcall
|
#define STDCALL __stdcall
|
||||||
#define CDECL __cdecl
|
#define CDECL __cdecl
|
||||||
|
|||||||
@@ -620,14 +620,9 @@ bool Collision::RayIntersectsTriangle(const Ray& ray, const Vector3& a, const Ve
|
|||||||
Real rayDistance = edge2.X * distanceCrossEdge1.X + edge2.Y * distanceCrossEdge1.Y + edge2.Z * distanceCrossEdge1.Z;
|
Real rayDistance = edge2.X * distanceCrossEdge1.X + edge2.Y * distanceCrossEdge1.Y + edge2.Z * distanceCrossEdge1.Z;
|
||||||
rayDistance *= inverseDeterminant;
|
rayDistance *= inverseDeterminant;
|
||||||
|
|
||||||
// Check if the triangle is behind the ray origin
|
// Check if the triangle is in front the ray origin
|
||||||
if (rayDistance < 0.0f)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
distance = rayDistance;
|
distance = rayDistance;
|
||||||
return true;
|
return rayDistance >= 0.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CollisionsHelper::RayIntersectsTriangle(const Ray& ray, const Vector3& vertex1, const Vector3& vertex2, const Vector3& vertex3, Real& distance, Vector3& normal)
|
bool CollisionsHelper::RayIntersectsTriangle(const Ray& ray, const Vector3& vertex1, const Vector3& vertex2, const Vector3& vertex3, Real& distance, Vector3& normal)
|
||||||
|
|||||||
@@ -12,11 +12,6 @@ String Ray::ToString() const
|
|||||||
return String::Format(TEXT("{}"), *this);
|
return String::Format(TEXT("{}"), *this);
|
||||||
}
|
}
|
||||||
|
|
||||||
Vector3 Ray::GetPoint(Real distance) const
|
|
||||||
{
|
|
||||||
return Position + Direction * distance;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ray Ray::GetPickRay(float x, float y, const Viewport& viewport, const Matrix& vp)
|
Ray Ray::GetPickRay(float x, float y, const Viewport& viewport, const Matrix& vp)
|
||||||
{
|
{
|
||||||
Vector3 nearPoint(x, y, 0.0f);
|
Vector3 nearPoint(x, y, 0.0f);
|
||||||
|
|||||||
@@ -79,7 +79,10 @@ public:
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="distance">The distance from ray origin.</param>
|
/// <param name="distance">The distance from ray origin.</param>
|
||||||
/// <returns>The calculated point.</returns>
|
/// <returns>The calculated point.</returns>
|
||||||
Vector3 GetPoint(Real distance) const;
|
FORCE_INLINE Vector3 GetPoint(Real distance) const
|
||||||
|
{
|
||||||
|
return Position + Direction * distance;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Determines if there is an intersection between ray and a point.
|
/// Determines if there is an intersection between ray and a point.
|
||||||
|
|||||||
@@ -18,9 +18,7 @@ namespace AllocationUtils
|
|||||||
capacity |= capacity >> 8;
|
capacity |= capacity >> 8;
|
||||||
capacity |= capacity >> 16;
|
capacity |= capacity >> 16;
|
||||||
uint64 capacity64 = (uint64)(capacity + 1) * 2;
|
uint64 capacity64 = (uint64)(capacity + 1) * 2;
|
||||||
if (capacity64 > MAX_int32)
|
return capacity64 >= MAX_int32 ? MAX_int32 : (int32)capacity64 / 2;
|
||||||
capacity64 = MAX_int32;
|
|
||||||
return (int32)capacity64;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Aligns the input value to the next power of 2 to be used as bigger memory allocation block.
|
// Aligns the input value to the next power of 2 to be used as bigger memory allocation block.
|
||||||
|
|||||||
@@ -215,6 +215,11 @@ public:
|
|||||||
return String(_data.Get(), _data.Count());
|
return String(_data.Get(), _data.Count());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
StringAnsi ToStringAnsi() const
|
||||||
|
{
|
||||||
|
return StringAnsi(_data.Get(), _data.Count());
|
||||||
|
}
|
||||||
|
|
||||||
StringView ToStringView() const;
|
StringView ToStringView() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -79,10 +79,11 @@ namespace FlaxEngine.Interop
|
|||||||
NativeLibrary.SetDllImportResolver(Assembly.GetExecutingAssembly(), NativeLibraryImportResolver);
|
NativeLibrary.SetDllImportResolver(Assembly.GetExecutingAssembly(), NativeLibraryImportResolver);
|
||||||
|
|
||||||
// Change default culture to match with Mono runtime default culture
|
// Change default culture to match with Mono runtime default culture
|
||||||
CultureInfo.DefaultThreadCurrentCulture = CultureInfo.InvariantCulture;
|
var culture = CultureInfo.InvariantCulture;
|
||||||
CultureInfo.DefaultThreadCurrentUICulture = CultureInfo.InvariantCulture;
|
CultureInfo.DefaultThreadCurrentCulture = culture;
|
||||||
System.Threading.Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;
|
CultureInfo.DefaultThreadCurrentUICulture = culture;
|
||||||
System.Threading.Thread.CurrentThread.CurrentUICulture = CultureInfo.InvariantCulture;
|
System.Threading.Thread.CurrentThread.CurrentCulture = culture;
|
||||||
|
System.Threading.Thread.CurrentThread.CurrentUICulture = culture;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -425,6 +426,8 @@ namespace FlaxEngine.Interop
|
|||||||
var fieldOffsetPtr = (IntPtr*)field.FieldHandle.Value; // Pointer to MonoClassField
|
var fieldOffsetPtr = (IntPtr*)field.FieldHandle.Value; // Pointer to MonoClassField
|
||||||
fieldOffsetPtr += 3; // Skip three pointers (type, name, parent_and_flags)
|
fieldOffsetPtr += 3; // Skip three pointers (type, name, parent_and_flags)
|
||||||
return *(int*)fieldOffsetPtr - IntPtr.Size * 2; // Load the value of a pointer (4 bytes, int32), then subtracting 16 bytes from it (2 pointers for vtable and threadsync)
|
return *(int*)fieldOffsetPtr - IntPtr.Size * 2; // Load the value of a pointer (4 bytes, int32), then subtracting 16 bytes from it (2 pointers for vtable and threadsync)
|
||||||
|
#else
|
||||||
|
throw new NotImplementedException();
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ void GPUContext::FrameBegin()
|
|||||||
|
|
||||||
void GPUContext::FrameEnd()
|
void GPUContext::FrameEnd()
|
||||||
{
|
{
|
||||||
ClearState();
|
ResetState();
|
||||||
FlushState();
|
FlushState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -189,7 +189,7 @@ public:
|
|||||||
/// [Deprecated in v1.10]
|
/// [Deprecated in v1.10]
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns><c>true</c> if depth buffer is binded; otherwise, <c>false</c>.</returns>
|
/// <returns><c>true</c> if depth buffer is binded; otherwise, <c>false</c>.</returns>
|
||||||
DEPRECATED("IsDepthBufferBinded has been deprecated and will be removed in ")
|
DEPRECATED("IsDepthBufferBinded has been deprecated and will be removed in future")
|
||||||
virtual bool IsDepthBufferBinded() = 0;
|
virtual bool IsDepthBufferBinded() = 0;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@@ -617,8 +617,17 @@ public:
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Clears the context state.
|
/// Clears the context state.
|
||||||
|
/// [Deprecated in v1.12]
|
||||||
/// </summary>
|
/// </summary>
|
||||||
API_FUNCTION() virtual void ClearState() = 0;
|
API_FUNCTION() DEPRECATED("Use ResetState instead") void ClearState()
|
||||||
|
{
|
||||||
|
ResetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Resets the context state.
|
||||||
|
/// </summary>
|
||||||
|
API_FUNCTION() virtual void ResetState() = 0;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Flushes the internal cached context state with a command buffer.
|
/// Flushes the internal cached context state with a command buffer.
|
||||||
|
|||||||
@@ -201,6 +201,7 @@ bool DeferredMaterialShader::Load()
|
|||||||
psDesc.DepthWriteEnable = true;
|
psDesc.DepthWriteEnable = true;
|
||||||
psDesc.DepthEnable = true;
|
psDesc.DepthEnable = true;
|
||||||
psDesc.DepthFunc = ComparisonFunc::Less;
|
psDesc.DepthFunc = ComparisonFunc::Less;
|
||||||
|
psDesc.BlendMode.RenderTargetWriteMask = BlendingMode::ColorWrite::None;
|
||||||
psDesc.HS = nullptr;
|
psDesc.HS = nullptr;
|
||||||
psDesc.DS = nullptr;
|
psDesc.DS = nullptr;
|
||||||
GPUShaderProgramVS* instancedDepthPassVS;
|
GPUShaderProgramVS* instancedDepthPassVS;
|
||||||
|
|||||||
@@ -195,5 +195,10 @@ bool ForwardMaterialShader::Load()
|
|||||||
psDesc.VS = _shader->GetVS("VS_Skinned");
|
psDesc.VS = _shader->GetVS("VS_Skinned");
|
||||||
_cache.DepthSkinned.Init(psDesc);
|
_cache.DepthSkinned.Init(psDesc);
|
||||||
|
|
||||||
|
#if PLATFORM_PS5
|
||||||
|
// Fix shader binding issues on forward shading materials on PS5
|
||||||
|
_drawModes = DrawPass::None;
|
||||||
|
#endif
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -264,5 +264,10 @@ bool ParticleMaterialShader::Load()
|
|||||||
// Lazy initialization
|
// Lazy initialization
|
||||||
_cacheVolumetricFog.Desc.PS = nullptr;
|
_cacheVolumetricFog.Desc.PS = nullptr;
|
||||||
|
|
||||||
|
#if PLATFORM_PS5
|
||||||
|
// Fix shader binding issues on forward shading materials on PS5
|
||||||
|
_drawModes = DrawPass::None;
|
||||||
|
#endif
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -113,7 +113,8 @@ GPUTexture* RenderBuffers::RequestHalfResDepth(GPUContext* context)
|
|||||||
|
|
||||||
PixelFormat RenderBuffers::GetOutputFormat() const
|
PixelFormat RenderBuffers::GetOutputFormat() const
|
||||||
{
|
{
|
||||||
return _useAlpha ? PixelFormat::R16G16B16A16_Float : PixelFormat::R11G11B10_Float;
|
// TODO: fix incorrect alpha leaking into reflections on PS5 with R11G11B10_Float
|
||||||
|
return _useAlpha || PLATFORM_PS5 ? PixelFormat::R16G16B16A16_Float : PixelFormat::R11G11B10_Float;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RenderBuffers::GetUseAlpha() const
|
bool RenderBuffers::GetUseAlpha() const
|
||||||
|
|||||||
@@ -450,7 +450,7 @@ public:
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The high-level renderer context. Used to collect the draw calls for the scene rendering. Can be used to perform a custom rendering.
|
/// The high-level renderer context. Used to collect the draw calls for the scene rendering. Can be used to perform a custom rendering.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
API_STRUCT(NoDefault) struct RenderContext
|
API_STRUCT(NoDefault) struct FLAXENGINE_API RenderContext
|
||||||
{
|
{
|
||||||
DECLARE_SCRIPTING_TYPE_MINIMAL(RenderContext);
|
DECLARE_SCRIPTING_TYPE_MINIMAL(RenderContext);
|
||||||
|
|
||||||
@@ -491,7 +491,7 @@ API_STRUCT(NoDefault) struct RenderContext
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The high-level renderer context batch that encapsulates multiple rendering requests within a single task (eg. optimize main view scene rendering and shadow projections at once).
|
/// The high-level renderer context batch that encapsulates multiple rendering requests within a single task (eg. optimize main view scene rendering and shadow projections at once).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
API_STRUCT(NoDefault) struct RenderContextBatch
|
API_STRUCT(NoDefault) struct FLAXENGINE_API RenderContextBatch
|
||||||
{
|
{
|
||||||
DECLARE_SCRIPTING_TYPE_MINIMAL(RenderContextBatch);
|
DECLARE_SCRIPTING_TYPE_MINIMAL(RenderContextBatch);
|
||||||
|
|
||||||
|
|||||||
@@ -216,20 +216,21 @@ GPUVertexLayout* GPUVertexLayout::Get(const Span<GPUVertexLayout*>& layouts)
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
GPUVertexLayout* GPUVertexLayout::Merge(GPUVertexLayout* base, GPUVertexLayout* reference, bool removeUnused, bool addMissing, int32 missingSlotOverride)
|
GPUVertexLayout* GPUVertexLayout::Merge(GPUVertexLayout* base, GPUVertexLayout* reference, bool removeUnused, bool addMissing, int32 missingSlotOverride, bool referenceOrder)
|
||||||
{
|
{
|
||||||
GPUVertexLayout* result = base ? base : reference;
|
GPUVertexLayout* result = base ? base : reference;
|
||||||
if (base && reference && base != reference)
|
if (base && reference && base != reference)
|
||||||
{
|
{
|
||||||
bool elementsModified = false;
|
bool elementsModified = false;
|
||||||
Elements newElements = base->GetElements();
|
Elements newElements = base->GetElements();
|
||||||
|
const Elements& refElements = reference->GetElements();
|
||||||
if (removeUnused)
|
if (removeUnused)
|
||||||
{
|
{
|
||||||
for (int32 i = newElements.Count() - 1; i >= 0; i--)
|
for (int32 i = newElements.Count() - 1; i >= 0; i--)
|
||||||
{
|
{
|
||||||
bool missing = true;
|
bool missing = true;
|
||||||
const VertexElement& e = newElements.Get()[i];
|
const VertexElement& e = newElements.Get()[i];
|
||||||
for (const VertexElement& ee : reference->GetElements())
|
for (const VertexElement& ee : refElements)
|
||||||
{
|
{
|
||||||
if (ee.Type == e.Type)
|
if (ee.Type == e.Type)
|
||||||
{
|
{
|
||||||
@@ -247,7 +248,7 @@ GPUVertexLayout* GPUVertexLayout::Merge(GPUVertexLayout* base, GPUVertexLayout*
|
|||||||
}
|
}
|
||||||
if (addMissing)
|
if (addMissing)
|
||||||
{
|
{
|
||||||
for (const VertexElement& e : reference->GetElements())
|
for (const VertexElement& e : refElements)
|
||||||
{
|
{
|
||||||
bool missing = true;
|
bool missing = true;
|
||||||
for (const VertexElement& ee : base->GetElements())
|
for (const VertexElement& ee : base->GetElements())
|
||||||
@@ -282,6 +283,32 @@ GPUVertexLayout* GPUVertexLayout::Merge(GPUVertexLayout* base, GPUVertexLayout*
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (referenceOrder)
|
||||||
|
{
|
||||||
|
for (int32 i = 0, j = 0; i < newElements.Count() && j < refElements.Count(); j++)
|
||||||
|
{
|
||||||
|
if (newElements[i].Type == refElements[j].Type)
|
||||||
|
{
|
||||||
|
// Elements match so move forward
|
||||||
|
i++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find reference element in a new list
|
||||||
|
for (int32 k = i + 1; k < newElements.Count(); k++)
|
||||||
|
{
|
||||||
|
if (newElements[k].Type == refElements[j].Type)
|
||||||
|
{
|
||||||
|
// Move matching element to the reference position
|
||||||
|
VertexElement e = newElements[k];
|
||||||
|
newElements.RemoveAt(k);
|
||||||
|
newElements.Insert(i, e);
|
||||||
|
i++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if (elementsModified)
|
if (elementsModified)
|
||||||
result = Get(newElements, true);
|
result = Get(newElements, true);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,8 +84,9 @@ public:
|
|||||||
/// <param name="removeUnused">True to remove elements from base layout that don't exist in a reference layout.</param>
|
/// <param name="removeUnused">True to remove elements from base layout that don't exist in a reference layout.</param>
|
||||||
/// <param name="addMissing">True to add missing elements to base layout that exist in a reference layout.</param>
|
/// <param name="addMissing">True to add missing elements to base layout that exist in a reference layout.</param>
|
||||||
/// <param name="missingSlotOverride">Allows to override the input slot for missing elements. Use value -1 to inherit slot from the reference layout.</param>
|
/// <param name="missingSlotOverride">Allows to override the input slot for missing elements. Use value -1 to inherit slot from the reference layout.</param>
|
||||||
|
/// <param name="referenceOrder">True to reorder result elements to match the reference layout. For example, if input vertex buffer layout is different than vertex shader then it can match those.</param>
|
||||||
/// <returns>Vertex layout object. Doesn't need to be cleared as it's cached for an application lifetime.</returns>
|
/// <returns>Vertex layout object. Doesn't need to be cleared as it's cached for an application lifetime.</returns>
|
||||||
static GPUVertexLayout* Merge(GPUVertexLayout* base, GPUVertexLayout* reference, bool removeUnused = false, bool addMissing = true, int32 missingSlotOverride = -1);
|
static GPUVertexLayout* Merge(GPUVertexLayout* base, GPUVertexLayout* reference, bool removeUnused = false, bool addMissing = true, int32 missingSlotOverride = -1, bool referenceOrder = false);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
// [GPUResource]
|
// [GPUResource]
|
||||||
|
|||||||
@@ -338,10 +338,10 @@ public:
|
|||||||
StreamTextureMipTask(StreamingTexture* texture, int32 mipIndex, Task* rootTask)
|
StreamTextureMipTask(StreamingTexture* texture, int32 mipIndex, Task* rootTask)
|
||||||
: GPUUploadTextureMipTask(texture->GetTexture(), mipIndex, Span<byte>(nullptr, 0), 0, 0, false)
|
: GPUUploadTextureMipTask(texture->GetTexture(), mipIndex, Span<byte>(nullptr, 0), 0, 0, false)
|
||||||
, _streamingTexture(texture)
|
, _streamingTexture(texture)
|
||||||
, _rootTask(rootTask ? rootTask : this)
|
, _rootTask(rootTask)
|
||||||
, _dataLock(_streamingTexture->GetOwner()->LockData())
|
, _dataLock(_streamingTexture->GetOwner()->LockData())
|
||||||
{
|
{
|
||||||
_streamingTexture->_streamingTasks.Add(_rootTask);
|
_streamingTexture->_streamingTasks.Add(this);
|
||||||
_texture.Released.Bind<StreamTextureMipTask, &StreamTextureMipTask::OnResourceReleased2>(this);
|
_texture.Released.Bind<StreamTextureMipTask, &StreamTextureMipTask::OnResourceReleased2>(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -357,7 +357,7 @@ private:
|
|||||||
if (_streamingTexture)
|
if (_streamingTexture)
|
||||||
{
|
{
|
||||||
ScopeLock lock(_streamingTexture->GetOwner()->GetOwnerLocker());
|
ScopeLock lock(_streamingTexture->GetOwner()->GetOwnerLocker());
|
||||||
_streamingTexture->_streamingTasks.Remove(_rootTask);
|
_streamingTexture->_streamingTasks.Remove(this);
|
||||||
_streamingTexture = nullptr;
|
_streamingTexture = nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -422,6 +422,15 @@ protected:
|
|||||||
|
|
||||||
GPUUploadTextureMipTask::OnFail();
|
GPUUploadTextureMipTask::OnFail();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void OnCancel() override
|
||||||
|
{
|
||||||
|
GPUUploadTextureMipTask::OnCancel();
|
||||||
|
|
||||||
|
// Cancel the root task too (eg. mip loading from asset)
|
||||||
|
if (_rootTask != nullptr)
|
||||||
|
_rootTask->Cancel();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Task* StreamingTexture::CreateStreamingTask(int32 residency)
|
Task* StreamingTexture::CreateStreamingTask(int32 residency)
|
||||||
|
|||||||
@@ -771,7 +771,7 @@ Task* TextureBase::RequestMipDataAsync(int32 mipIndex)
|
|||||||
|
|
||||||
FlaxStorage::LockData TextureBase::LockData()
|
FlaxStorage::LockData TextureBase::LockData()
|
||||||
{
|
{
|
||||||
return _parent->Storage ? _parent->Storage->Lock() : FlaxStorage::LockData::Invalid;
|
return _parent->Storage ? _parent->Storage->Lock() : FlaxStorage::LockData();
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextureBase::GetMipData(int32 mipIndex, BytesContainer& data) const
|
void TextureBase::GetMipData(int32 mipIndex, BytesContainer& data) const
|
||||||
|
|||||||
@@ -724,7 +724,7 @@ void GPUContextDX11::SetState(GPUPipelineState* state)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GPUContextDX11::ClearState()
|
void GPUContextDX11::ResetState()
|
||||||
{
|
{
|
||||||
if (!_context)
|
if (!_context)
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -158,7 +158,7 @@ public:
|
|||||||
void SetScissor(const Rectangle& scissorRect) override;
|
void SetScissor(const Rectangle& scissorRect) override;
|
||||||
GPUPipelineState* GetState() const override;
|
GPUPipelineState* GetState() const override;
|
||||||
void SetState(GPUPipelineState* state) override;
|
void SetState(GPUPipelineState* state) override;
|
||||||
void ClearState() override;
|
void ResetState() override;
|
||||||
void FlushState() override;
|
void FlushState() override;
|
||||||
void Flush() override;
|
void Flush() override;
|
||||||
void UpdateBuffer(GPUBuffer* buffer, const void* data, uint32 size, uint32 offset) override;
|
void UpdateBuffer(GPUBuffer* buffer, const void* data, uint32 size, uint32 offset) override;
|
||||||
|
|||||||
@@ -92,9 +92,8 @@ float GPUTimerQueryDX11::GetResult()
|
|||||||
{
|
{
|
||||||
if (!_finalized)
|
if (!_finalized)
|
||||||
{
|
{
|
||||||
#if BUILD_DEBUG
|
if (!HasResult())
|
||||||
ASSERT(HasResult());
|
return 0;
|
||||||
#endif
|
|
||||||
|
|
||||||
UINT64 timeStart, timeEnd;
|
UINT64 timeStart, timeEnd;
|
||||||
auto context = _device->GetIM();
|
auto context = _device->GetIM();
|
||||||
|
|||||||
@@ -143,6 +143,8 @@ void CommandQueueDX12::WaitForFence(uint64 fenceValue)
|
|||||||
|
|
||||||
void CommandQueueDX12::WaitForGPU()
|
void CommandQueueDX12::WaitForGPU()
|
||||||
{
|
{
|
||||||
|
PROFILE_CPU();
|
||||||
|
ZoneColor(TracyWaitZoneColor);
|
||||||
const uint64 value = _fence.Signal(this);
|
const uint64 value = _fence.Signal(this);
|
||||||
_fence.WaitCPU(value);
|
_fence.WaitCPU(value);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -137,7 +137,7 @@ bool GPUBufferDX12::OnInit()
|
|||||||
// Create resource
|
// Create resource
|
||||||
ID3D12Resource* resource;
|
ID3D12Resource* resource;
|
||||||
#if PLATFORM_WINDOWS
|
#if PLATFORM_WINDOWS
|
||||||
D3D12_HEAP_FLAGS heapFlags = D3D12_HEAP_FLAG_CREATE_NOT_ZEROED;
|
D3D12_HEAP_FLAGS heapFlags = EnumHasAnyFlags(_desc.Flags, GPUBufferFlags::VertexBuffer | GPUBufferFlags::IndexBuffer) || _desc.InitData ? D3D12_HEAP_FLAG_CREATE_NOT_ZEROED : D3D12_HEAP_FLAG_NONE;
|
||||||
#else
|
#else
|
||||||
D3D12_HEAP_FLAGS heapFlags = D3D12_HEAP_FLAG_NONE;
|
D3D12_HEAP_FLAGS heapFlags = D3D12_HEAP_FLAG_NONE;
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -29,6 +29,7 @@
|
|||||||
#include "GPUVertexLayoutDX12.h"
|
#include "GPUVertexLayoutDX12.h"
|
||||||
#include "CommandQueueDX12.h"
|
#include "CommandQueueDX12.h"
|
||||||
#include "DescriptorHeapDX12.h"
|
#include "DescriptorHeapDX12.h"
|
||||||
|
#include "RootSignatureDX12.h"
|
||||||
#include "Engine/Graphics/RenderTask.h"
|
#include "Engine/Graphics/RenderTask.h"
|
||||||
#include "Engine/GraphicsDevice/DirectX/RenderToolsDX.h"
|
#include "Engine/GraphicsDevice/DirectX/RenderToolsDX.h"
|
||||||
#include "Engine/Debug/Exceptions/NotImplementedException.h"
|
#include "Engine/Debug/Exceptions/NotImplementedException.h"
|
||||||
@@ -307,6 +308,7 @@ void GPUContextDX12::Reset()
|
|||||||
_device->DummyVB = _device->CreateBuffer(TEXT("DummyVertexBuffer"));
|
_device->DummyVB = _device->CreateBuffer(TEXT("DummyVertexBuffer"));
|
||||||
auto* layout = GPUVertexLayout::Get({ { VertexElement::Types::Attribute3, 0, 0, 0, PixelFormat::R32G32B32A32_Float } });
|
auto* layout = GPUVertexLayout::Get({ { VertexElement::Types::Attribute3, 0, 0, 0, PixelFormat::R32G32B32A32_Float } });
|
||||||
_device->DummyVB->Init(GPUBufferDescription::Vertex(layout, sizeof(Color), 1, &Color::Transparent));
|
_device->DummyVB->Init(GPUBufferDescription::Vertex(layout, sizeof(Color), 1, &Color::Transparent));
|
||||||
|
SetResourceState(_device->DummyVB, D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER, 0);
|
||||||
}
|
}
|
||||||
((GPUBufferDX12*)_device->DummyVB)->GetVBView(dummyVBView);
|
((GPUBufferDX12*)_device->DummyVB)->GetVBView(dummyVBView);
|
||||||
_commandList->IASetVertexBuffers(GPU_MAX_VB_BINDED, 1, &dummyVBView);
|
_commandList->IASetVertexBuffers(GPU_MAX_VB_BINDED, 1, &dummyVBView);
|
||||||
@@ -628,7 +630,9 @@ void GPUContextDX12::flushPS()
|
|||||||
LOG(Error, "Missing Vertex Layout (not assigned to GPUBuffer). Vertex Shader won't read valid data resulting incorrect visuals.");
|
LOG(Error, "Missing Vertex Layout (not assigned to GPUBuffer). Vertex Shader won't read valid data resulting incorrect visuals.");
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
_commandList->SetPipelineState(_currentState->GetState(_rtDepth, _rtCount, _rtHandles, _vertexLayout));
|
ID3D12PipelineState* pso = _currentState->GetState(_rtDepth, _rtCount, _rtHandles, _vertexLayout);
|
||||||
|
ASSERT(pso);
|
||||||
|
_commandList->SetPipelineState(pso);
|
||||||
if (_primitiveTopology != _currentState->PrimitiveTopology)
|
if (_primitiveTopology != _currentState->PrimitiveTopology)
|
||||||
{
|
{
|
||||||
_primitiveTopology = _currentState->PrimitiveTopology;
|
_primitiveTopology = _currentState->PrimitiveTopology;
|
||||||
@@ -1300,7 +1304,7 @@ void GPUContextDX12::SetState(GPUPipelineState* state)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GPUContextDX12::ClearState()
|
void GPUContextDX12::ResetState()
|
||||||
{
|
{
|
||||||
if (!_commandList)
|
if (!_commandList)
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -201,7 +201,7 @@ public:
|
|||||||
void SetScissor(const Rectangle& scissorRect) override;
|
void SetScissor(const Rectangle& scissorRect) override;
|
||||||
GPUPipelineState* GetState() const override;
|
GPUPipelineState* GetState() const override;
|
||||||
void SetState(GPUPipelineState* state) override;
|
void SetState(GPUPipelineState* state) override;
|
||||||
void ClearState() override;
|
void ResetState() override;
|
||||||
void FlushState() override;
|
void FlushState() override;
|
||||||
void Flush() override;
|
void Flush() override;
|
||||||
void UpdateBuffer(GPUBuffer* buffer, const void* data, uint32 size, uint32 offset) override;
|
void UpdateBuffer(GPUBuffer* buffer, const void* data, uint32 size, uint32 offset) override;
|
||||||
|
|||||||
@@ -12,6 +12,9 @@
|
|||||||
#include "GPUSamplerDX12.h"
|
#include "GPUSamplerDX12.h"
|
||||||
#include "GPUVertexLayoutDX12.h"
|
#include "GPUVertexLayoutDX12.h"
|
||||||
#include "GPUSwapChainDX12.h"
|
#include "GPUSwapChainDX12.h"
|
||||||
|
#include "RootSignatureDX12.h"
|
||||||
|
#include "UploadBufferDX12.h"
|
||||||
|
#include "CommandQueueDX12.h"
|
||||||
#include "Engine/Engine/Engine.h"
|
#include "Engine/Engine/Engine.h"
|
||||||
#include "Engine/Engine/CommandLine.h"
|
#include "Engine/Engine/CommandLine.h"
|
||||||
#include "Engine/Graphics/RenderTask.h"
|
#include "Engine/Graphics/RenderTask.h"
|
||||||
@@ -21,20 +24,23 @@
|
|||||||
#include "Engine/Profiler/ProfilerMemory.h"
|
#include "Engine/Profiler/ProfilerMemory.h"
|
||||||
#include "Engine/Core/Log.h"
|
#include "Engine/Core/Log.h"
|
||||||
#include "Engine/Core/Config/PlatformSettings.h"
|
#include "Engine/Core/Config/PlatformSettings.h"
|
||||||
#include "UploadBufferDX12.h"
|
#include "Engine/Core/Types/StringBuilder.h"
|
||||||
#include "CommandQueueDX12.h"
|
|
||||||
#include "Engine/Core/Utilities.h"
|
#include "Engine/Core/Utilities.h"
|
||||||
#include "Engine/Threading/Threading.h"
|
#include "Engine/Threading/Threading.h"
|
||||||
#include "CommandSignatureDX12.h"
|
#include "CommandSignatureDX12.h"
|
||||||
|
|
||||||
static bool CheckDX12Support(IDXGIAdapter* adapter)
|
static bool CheckDX12Support(IDXGIAdapter* adapter)
|
||||||
{
|
{
|
||||||
|
#if PLATFORM_XBOX_SCARLETT || PLATFORM_XBOX_ONE
|
||||||
|
return true;
|
||||||
|
#else
|
||||||
// Try to create device
|
// Try to create device
|
||||||
if (SUCCEEDED(D3D12CreateDevice(adapter, D3D_FEATURE_LEVEL_11_0, _uuidof(ID3D12Device), nullptr)))
|
if (SUCCEEDED(D3D12CreateDevice(adapter, D3D_FEATURE_LEVEL_11_0, _uuidof(ID3D12Device), nullptr)))
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
GPUVertexLayoutDX12::GPUVertexLayoutDX12(GPUDeviceDX12* device, const Elements& elements, bool explicitOffsets)
|
GPUVertexLayoutDX12::GPUVertexLayoutDX12(GPUDeviceDX12* device, const Elements& elements, bool explicitOffsets)
|
||||||
@@ -55,6 +61,310 @@ GPUVertexLayoutDX12::GPUVertexLayoutDX12(GPUDeviceDX12* device, const Elements&
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RootSignatureDX12::RootSignatureDX12()
|
||||||
|
{
|
||||||
|
// Clear structures
|
||||||
|
Platform::MemoryClear(this, sizeof(*this));
|
||||||
|
|
||||||
|
// Descriptor tables
|
||||||
|
{
|
||||||
|
// SRVs
|
||||||
|
D3D12_DESCRIPTOR_RANGE& range = _ranges[0];
|
||||||
|
range.RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SRV;
|
||||||
|
range.NumDescriptors = GPU_MAX_SR_BINDED;
|
||||||
|
range.BaseShaderRegister = 0;
|
||||||
|
range.RegisterSpace = 0;
|
||||||
|
range.OffsetInDescriptorsFromTableStart = D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// UAVs
|
||||||
|
D3D12_DESCRIPTOR_RANGE& range = _ranges[1];
|
||||||
|
range.RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_UAV;
|
||||||
|
range.NumDescriptors = GPU_MAX_UA_BINDED;
|
||||||
|
range.BaseShaderRegister = 0;
|
||||||
|
range.RegisterSpace = 0;
|
||||||
|
range.OffsetInDescriptorsFromTableStart = D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// Samplers
|
||||||
|
D3D12_DESCRIPTOR_RANGE& range = _ranges[2];
|
||||||
|
range.RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER;
|
||||||
|
range.NumDescriptors = GPU_MAX_SAMPLER_BINDED - GPU_STATIC_SAMPLERS_COUNT;
|
||||||
|
range.BaseShaderRegister = GPU_STATIC_SAMPLERS_COUNT;
|
||||||
|
range.RegisterSpace = 0;
|
||||||
|
range.OffsetInDescriptorsFromTableStart = D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Root parameters
|
||||||
|
for (int32 i = 0; i < GPU_MAX_CB_BINDED; i++)
|
||||||
|
{
|
||||||
|
// CBs
|
||||||
|
D3D12_ROOT_PARAMETER& param = _parameters[DX12_ROOT_SIGNATURE_CB + i];
|
||||||
|
param.ParameterType = D3D12_ROOT_PARAMETER_TYPE_CBV;
|
||||||
|
param.ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL;
|
||||||
|
param.Descriptor.ShaderRegister = i;
|
||||||
|
param.Descriptor.RegisterSpace = 0;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// SRVs
|
||||||
|
D3D12_ROOT_PARAMETER& param = _parameters[DX12_ROOT_SIGNATURE_SR];
|
||||||
|
param.ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE;
|
||||||
|
param.ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL;
|
||||||
|
param.DescriptorTable.NumDescriptorRanges = 1;
|
||||||
|
param.DescriptorTable.pDescriptorRanges = &_ranges[0];
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// UAVs
|
||||||
|
D3D12_ROOT_PARAMETER& param = _parameters[DX12_ROOT_SIGNATURE_UA];
|
||||||
|
param.ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE;
|
||||||
|
param.ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL;
|
||||||
|
param.DescriptorTable.NumDescriptorRanges = 1;
|
||||||
|
param.DescriptorTable.pDescriptorRanges = &_ranges[1];
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// Samplers
|
||||||
|
D3D12_ROOT_PARAMETER& param = _parameters[DX12_ROOT_SIGNATURE_SAMPLER];
|
||||||
|
param.ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE;
|
||||||
|
param.ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL;
|
||||||
|
param.DescriptorTable.NumDescriptorRanges = 1;
|
||||||
|
param.DescriptorTable.pDescriptorRanges = &_ranges[2];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Static samplers
|
||||||
|
static_assert(GPU_STATIC_SAMPLERS_COUNT == ARRAY_COUNT(_staticSamplers), "Update static samplers setup.");
|
||||||
|
// Linear Clamp
|
||||||
|
InitSampler(0, D3D12_FILTER_MIN_MAG_MIP_LINEAR, D3D12_TEXTURE_ADDRESS_MODE_CLAMP);
|
||||||
|
// Point Clamp
|
||||||
|
InitSampler(1, D3D12_FILTER_MIN_MAG_MIP_POINT, D3D12_TEXTURE_ADDRESS_MODE_CLAMP);
|
||||||
|
// Linear Wrap
|
||||||
|
InitSampler(2, D3D12_FILTER_MIN_MAG_MIP_LINEAR, D3D12_TEXTURE_ADDRESS_MODE_WRAP);
|
||||||
|
// Point Wrap
|
||||||
|
InitSampler(3, D3D12_FILTER_MIN_MAG_MIP_POINT, D3D12_TEXTURE_ADDRESS_MODE_WRAP);
|
||||||
|
// Shadow
|
||||||
|
InitSampler(4, D3D12_FILTER_COMPARISON_MIN_MAG_MIP_POINT, D3D12_TEXTURE_ADDRESS_MODE_CLAMP, D3D12_COMPARISON_FUNC_LESS_EQUAL);
|
||||||
|
// Shadow PCF
|
||||||
|
InitSampler(5, D3D12_FILTER_COMPARISON_MIN_MAG_MIP_LINEAR, D3D12_TEXTURE_ADDRESS_MODE_CLAMP, D3D12_COMPARISON_FUNC_LESS_EQUAL);
|
||||||
|
|
||||||
|
// Init
|
||||||
|
_desc.NumParameters = ARRAY_COUNT(_parameters);
|
||||||
|
_desc.pParameters = _parameters;
|
||||||
|
_desc.NumStaticSamplers = ARRAY_COUNT(_staticSamplers);
|
||||||
|
_desc.pStaticSamplers = _staticSamplers;
|
||||||
|
_desc.Flags = D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RootSignatureDX12::InitSampler(int32 i, D3D12_FILTER filter, D3D12_TEXTURE_ADDRESS_MODE address, D3D12_COMPARISON_FUNC comparisonFunc)
|
||||||
|
{
|
||||||
|
auto& sampler = _staticSamplers[i];
|
||||||
|
sampler.Filter = filter;
|
||||||
|
sampler.AddressU = address;
|
||||||
|
sampler.AddressV = address;
|
||||||
|
sampler.AddressW = address;
|
||||||
|
sampler.MipLODBias = 0.0f;
|
||||||
|
sampler.MaxAnisotropy = 1;
|
||||||
|
sampler.ComparisonFunc = comparisonFunc;
|
||||||
|
sampler.BorderColor = D3D12_STATIC_BORDER_COLOR_OPAQUE_BLACK;
|
||||||
|
sampler.MinLOD = 0;
|
||||||
|
sampler.MaxLOD = D3D12_FLOAT32_MAX;
|
||||||
|
sampler.ShaderRegister = i;
|
||||||
|
sampler.RegisterSpace = 0;
|
||||||
|
sampler.ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL;
|
||||||
|
}
|
||||||
|
|
||||||
|
ComPtr<ID3DBlob> RootSignatureDX12::Serialize() const
|
||||||
|
{
|
||||||
|
ComPtr<ID3DBlob> signature;
|
||||||
|
ComPtr<ID3DBlob> error;
|
||||||
|
VALIDATE_DIRECTX_CALL(D3D12SerializeRootSignature(&_desc, D3D_ROOT_SIGNATURE_VERSION_1_0, &signature, &error));
|
||||||
|
if (error.Get())
|
||||||
|
{
|
||||||
|
LOG(Error, "D3D12SerializeRootSignature failed with error: {}", String((const char*)error->GetBufferPointer()));
|
||||||
|
}
|
||||||
|
return signature;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if USE_EDITOR
|
||||||
|
|
||||||
|
const Char* GetRootSignatureShaderVisibility(D3D12_SHADER_VISIBILITY visibility)
|
||||||
|
{
|
||||||
|
switch (visibility)
|
||||||
|
{
|
||||||
|
case D3D12_SHADER_VISIBILITY_VERTEX:
|
||||||
|
return TEXT(", visibility=SHADER_VISIBILITY_VERTEX");
|
||||||
|
case D3D12_SHADER_VISIBILITY_HULL:
|
||||||
|
return TEXT(", visibility=SHADER_VISIBILITY_HULL");
|
||||||
|
case D3D12_SHADER_VISIBILITY_DOMAIN:
|
||||||
|
return TEXT(", visibility=SHADER_VISIBILITY_DOMAIN");
|
||||||
|
case D3D12_SHADER_VISIBILITY_GEOMETRY:
|
||||||
|
return TEXT(", visibility=SHADER_VISIBILITY_GEOMETRY");
|
||||||
|
case D3D12_SHADER_VISIBILITY_PIXEL:
|
||||||
|
return TEXT(", visibility=SHADER_VISIBILITY_PIXEL");
|
||||||
|
case D3D12_SHADER_VISIBILITY_AMPLIFICATION:
|
||||||
|
return TEXT(", visibility=SHADER_VISIBILITY_AMPLIFICATION");
|
||||||
|
case D3D12_SHADER_VISIBILITY_MESH:
|
||||||
|
return TEXT(", visibility=SHADER_VISIBILITY_MESH");
|
||||||
|
case D3D12_SHADER_VISIBILITY_ALL:
|
||||||
|
default:
|
||||||
|
return TEXT(""); // Default
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const Char* GetRootSignatureSamplerFilter(D3D12_FILTER filter)
|
||||||
|
{
|
||||||
|
switch (filter)
|
||||||
|
{
|
||||||
|
case D3D12_FILTER_MIN_MAG_MIP_POINT:
|
||||||
|
return TEXT("FILTER_MIN_MAG_MIP_POINT");
|
||||||
|
case D3D12_FILTER_MIN_MAG_MIP_LINEAR:
|
||||||
|
return TEXT("FILTER_MIN_MAG_MIP_LINEAR");
|
||||||
|
case D3D12_FILTER_ANISOTROPIC:
|
||||||
|
return TEXT("FILTER_ANISOTROPIC");
|
||||||
|
case D3D12_FILTER_COMPARISON_MIN_MAG_MIP_POINT:
|
||||||
|
return TEXT("FILTER_COMPARISON_MIN_MAG_MIP_POINT");
|
||||||
|
case D3D12_FILTER_COMPARISON_MIN_MAG_MIP_LINEAR:
|
||||||
|
return TEXT("FILTER_COMPARISON_MIN_MAG_MIP_LINEAR");
|
||||||
|
default:
|
||||||
|
CRASH; // Not implemented
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const Char* GetRootSignatureSamplerAddress(D3D12_TEXTURE_ADDRESS_MODE address)
|
||||||
|
{
|
||||||
|
switch (address)
|
||||||
|
{
|
||||||
|
case D3D12_TEXTURE_ADDRESS_MODE_WRAP:
|
||||||
|
return TEXT("TEXTURE_ADDRESS_WRAP");
|
||||||
|
case D3D12_TEXTURE_ADDRESS_MODE_MIRROR:
|
||||||
|
return TEXT("TEXTURE_ADDRESS_MIRROR");
|
||||||
|
case D3D12_TEXTURE_ADDRESS_MODE_CLAMP:
|
||||||
|
return TEXT("TEXTURE_ADDRESS_CLAMP");
|
||||||
|
case D3D12_TEXTURE_ADDRESS_MODE_BORDER:
|
||||||
|
return TEXT("TEXTURE_ADDRESS_BORDER");
|
||||||
|
case D3D12_TEXTURE_ADDRESS_MODE_MIRROR_ONCE:
|
||||||
|
return TEXT("TEXTURE_ADDRESS_MIRROR_ONCE");
|
||||||
|
default:
|
||||||
|
return TEXT("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const Char* GetRootSignatureSamplerComparisonFunc(D3D12_COMPARISON_FUNC func)
|
||||||
|
{
|
||||||
|
switch (func)
|
||||||
|
{
|
||||||
|
case D3D12_COMPARISON_FUNC_NEVER:
|
||||||
|
return TEXT("COMPARISON_NEVER");
|
||||||
|
case D3D12_COMPARISON_FUNC_LESS:
|
||||||
|
return TEXT("COMPARISON_LESS");
|
||||||
|
case D3D12_COMPARISON_FUNC_EQUAL:
|
||||||
|
return TEXT("COMPARISON_EQUAL");
|
||||||
|
case D3D12_COMPARISON_FUNC_LESS_EQUAL:
|
||||||
|
return TEXT("COMPARISON_LESS_EQUAL");
|
||||||
|
case D3D12_COMPARISON_FUNC_GREATER:
|
||||||
|
return TEXT("COMPARISON_GREATER");
|
||||||
|
case D3D12_COMPARISON_FUNC_NOT_EQUAL:
|
||||||
|
return TEXT("COMPARISON_NOT_EQUAL");
|
||||||
|
case D3D12_COMPARISON_FUNC_GREATER_EQUAL:
|
||||||
|
return TEXT("COMPARISON_GREATER_EQUAL");
|
||||||
|
case D3D12_COMPARISON_FUNC_ALWAYS:
|
||||||
|
default:
|
||||||
|
return TEXT("COMPARISON_ALWAYS");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RootSignatureDX12::ToString(StringBuilder& sb, bool singleLine) const
|
||||||
|
{
|
||||||
|
// Flags
|
||||||
|
sb.Append(TEXT("RootFlags(ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT)"));
|
||||||
|
|
||||||
|
// Parameters
|
||||||
|
const Char newLine = singleLine ? ' ' : '\n';
|
||||||
|
for (const D3D12_ROOT_PARAMETER& param : _parameters)
|
||||||
|
{
|
||||||
|
const Char* visibility = GetRootSignatureShaderVisibility(param.ShaderVisibility);
|
||||||
|
switch (param.ParameterType)
|
||||||
|
{
|
||||||
|
case D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE:
|
||||||
|
sb.AppendFormat(TEXT(",{}DescriptorTable("), newLine);
|
||||||
|
for (uint32 rangeIndex = 0; rangeIndex < param.DescriptorTable.NumDescriptorRanges; rangeIndex++)
|
||||||
|
{
|
||||||
|
if (rangeIndex)
|
||||||
|
sb.Append(TEXT(", "));
|
||||||
|
const D3D12_DESCRIPTOR_RANGE& range = param.DescriptorTable.pDescriptorRanges[rangeIndex];
|
||||||
|
switch (range.RangeType)
|
||||||
|
{
|
||||||
|
case D3D12_DESCRIPTOR_RANGE_TYPE_SRV:
|
||||||
|
sb.AppendFormat(TEXT("SRV(t{}"), range.BaseShaderRegister);
|
||||||
|
break;
|
||||||
|
case D3D12_DESCRIPTOR_RANGE_TYPE_UAV:
|
||||||
|
sb.AppendFormat(TEXT("UAV(u{}"), range.BaseShaderRegister);
|
||||||
|
break;
|
||||||
|
case D3D12_DESCRIPTOR_RANGE_TYPE_CBV:
|
||||||
|
sb.AppendFormat(TEXT("CBV(b{}"), range.BaseShaderRegister);
|
||||||
|
break;
|
||||||
|
case D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER:
|
||||||
|
sb.AppendFormat(TEXT("Sampler(s{}"), range.BaseShaderRegister);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (range.NumDescriptors != 1)
|
||||||
|
{
|
||||||
|
if (range.NumDescriptors == UINT_MAX)
|
||||||
|
sb.Append(TEXT(", numDescriptors=unbounded"));
|
||||||
|
else
|
||||||
|
sb.AppendFormat(TEXT(", numDescriptors={}"), range.NumDescriptors);
|
||||||
|
}
|
||||||
|
if (range.OffsetInDescriptorsFromTableStart != D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND)
|
||||||
|
sb.AppendFormat(TEXT(", offset={}"), range.OffsetInDescriptorsFromTableStart);
|
||||||
|
sb.Append(')');
|
||||||
|
}
|
||||||
|
sb.AppendFormat(TEXT("{})"), visibility);
|
||||||
|
break;
|
||||||
|
case D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS:
|
||||||
|
sb.AppendFormat(TEXT(",{}RootConstants(num32BitConstants={}, b{}{})"), newLine, param.Constants.Num32BitValues, param.Constants.ShaderRegister, visibility);
|
||||||
|
break;
|
||||||
|
case D3D12_ROOT_PARAMETER_TYPE_CBV:
|
||||||
|
sb.AppendFormat(TEXT(",{}CBV(b{}{})"), newLine, param.Descriptor.ShaderRegister, visibility);
|
||||||
|
break;
|
||||||
|
case D3D12_ROOT_PARAMETER_TYPE_SRV:
|
||||||
|
sb.AppendFormat(TEXT(",{}SRV(t{}{})"), newLine, param.Descriptor.ShaderRegister, visibility);
|
||||||
|
break;
|
||||||
|
case D3D12_ROOT_PARAMETER_TYPE_UAV:
|
||||||
|
sb.AppendFormat(TEXT(",{}UAV(u{}{})"), newLine, param.Descriptor.ShaderRegister, visibility);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Static Samplers
|
||||||
|
for (const D3D12_STATIC_SAMPLER_DESC& sampler : _staticSamplers)
|
||||||
|
{
|
||||||
|
const Char* visibility = GetRootSignatureShaderVisibility(sampler.ShaderVisibility);
|
||||||
|
sb.AppendFormat(TEXT(",{}StaticSampler(s{}"), newLine, sampler.ShaderRegister);
|
||||||
|
sb.AppendFormat(TEXT(", filter={}"), GetRootSignatureSamplerFilter(sampler.Filter));
|
||||||
|
sb.AppendFormat(TEXT(", addressU={}"), GetRootSignatureSamplerAddress(sampler.AddressU));
|
||||||
|
sb.AppendFormat(TEXT(", addressV={}"), GetRootSignatureSamplerAddress(sampler.AddressV));
|
||||||
|
sb.AppendFormat(TEXT(", addressW={}"), GetRootSignatureSamplerAddress(sampler.AddressW));
|
||||||
|
sb.AppendFormat(TEXT(", comparisonFunc={}"), GetRootSignatureSamplerComparisonFunc(sampler.ComparisonFunc));
|
||||||
|
sb.AppendFormat(TEXT(", maxAnisotropy={}"), sampler.MaxAnisotropy);
|
||||||
|
sb.Append(TEXT(", borderColor=STATIC_BORDER_COLOR_OPAQUE_BLACK"));
|
||||||
|
sb.AppendFormat(TEXT("{})"), visibility);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String RootSignatureDX12::ToString() const
|
||||||
|
{
|
||||||
|
StringBuilder sb;
|
||||||
|
ToString(sb);
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
StringAnsi RootSignatureDX12::ToStringAnsi() const
|
||||||
|
{
|
||||||
|
StringBuilder sb;
|
||||||
|
ToString(sb);
|
||||||
|
return sb.ToStringAnsi();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
GPUDevice* GPUDeviceDX12::Create()
|
GPUDevice* GPUDeviceDX12::Create()
|
||||||
{
|
{
|
||||||
#if PLATFORM_XBOX_SCARLETT || PLATFORM_XBOX_ONE
|
#if PLATFORM_XBOX_SCARLETT || PLATFORM_XBOX_ONE
|
||||||
@@ -318,6 +628,7 @@ bool GPUDeviceDX12::Init()
|
|||||||
VALIDATE_DIRECTX_CALL(dxgiAdapter->EnumOutputs(0, dxgiOutput.GetAddressOf()));
|
VALIDATE_DIRECTX_CALL(dxgiAdapter->EnumOutputs(0, dxgiOutput.GetAddressOf()));
|
||||||
DXGI_FORMAT backbufferFormat = RenderToolsDX::ToDxgiFormat(GPU_BACK_BUFFER_PIXEL_FORMAT);
|
DXGI_FORMAT backbufferFormat = RenderToolsDX::ToDxgiFormat(GPU_BACK_BUFFER_PIXEL_FORMAT);
|
||||||
UINT modesCount = 0;
|
UINT modesCount = 0;
|
||||||
|
#ifdef _GAMING_XBOX_SCARLETT
|
||||||
VALIDATE_DIRECTX_CALL(dxgiOutput->GetDisplayModeList(backbufferFormat, 0, &modesCount, NULL));
|
VALIDATE_DIRECTX_CALL(dxgiOutput->GetDisplayModeList(backbufferFormat, 0, &modesCount, NULL));
|
||||||
Array<DXGIXBOX_MODE_DESC> modes;
|
Array<DXGIXBOX_MODE_DESC> modes;
|
||||||
modes.Resize((int32)modesCount);
|
modes.Resize((int32)modesCount);
|
||||||
@@ -332,6 +643,11 @@ bool GPUDeviceDX12::Init()
|
|||||||
videoOutput.RefreshRate = Math::Max(videoOutput.RefreshRate, mode.RefreshRate.Numerator / (float)mode.RefreshRate.Denominator);
|
videoOutput.RefreshRate = Math::Max(videoOutput.RefreshRate, mode.RefreshRate.Numerator / (float)mode.RefreshRate.Denominator);
|
||||||
}
|
}
|
||||||
modes.Resize(0);
|
modes.Resize(0);
|
||||||
|
#else
|
||||||
|
videoOutput.Width = 1920;
|
||||||
|
videoOutput.Height = 1080;
|
||||||
|
videoOutput.RefreshRate = 60;
|
||||||
|
#endif
|
||||||
|
|
||||||
#if PLATFORM_GDK
|
#if PLATFORM_GDK
|
||||||
GDKPlatform::Suspended.Bind<GPUDeviceDX12, &GPUDeviceDX12::OnSuspended>(this);
|
GDKPlatform::Suspended.Bind<GPUDeviceDX12, &GPUDeviceDX12::OnSuspended>(this);
|
||||||
@@ -561,170 +877,10 @@ bool GPUDeviceDX12::Init()
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create root signature
|
// Create root signature
|
||||||
// TODO: maybe create set of different root signatures? for UAVs, for compute, for simple drawing, for post fx?
|
|
||||||
{
|
{
|
||||||
// Descriptor tables
|
RootSignatureDX12 signature;
|
||||||
D3D12_DESCRIPTOR_RANGE r[3]; // SRV+UAV+Sampler
|
ComPtr<ID3DBlob> signatureBlob = signature.Serialize();
|
||||||
{
|
VALIDATE_DIRECTX_CALL(_device->CreateRootSignature(0, signatureBlob->GetBufferPointer(), signatureBlob->GetBufferSize(), IID_PPV_ARGS(&_rootSignature)));
|
||||||
D3D12_DESCRIPTOR_RANGE& range = r[0];
|
|
||||||
range.RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SRV;
|
|
||||||
range.NumDescriptors = GPU_MAX_SR_BINDED;
|
|
||||||
range.BaseShaderRegister = 0;
|
|
||||||
range.RegisterSpace = 0;
|
|
||||||
range.OffsetInDescriptorsFromTableStart = D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND;
|
|
||||||
}
|
|
||||||
{
|
|
||||||
D3D12_DESCRIPTOR_RANGE& range = r[1];
|
|
||||||
range.RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_UAV;
|
|
||||||
range.NumDescriptors = GPU_MAX_UA_BINDED;
|
|
||||||
range.BaseShaderRegister = 0;
|
|
||||||
range.RegisterSpace = 0;
|
|
||||||
range.OffsetInDescriptorsFromTableStart = D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND;
|
|
||||||
}
|
|
||||||
{
|
|
||||||
D3D12_DESCRIPTOR_RANGE& range = r[2];
|
|
||||||
range.RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER;
|
|
||||||
range.NumDescriptors = GPU_MAX_SAMPLER_BINDED - GPU_STATIC_SAMPLERS_COUNT;
|
|
||||||
range.BaseShaderRegister = GPU_STATIC_SAMPLERS_COUNT;
|
|
||||||
range.RegisterSpace = 0;
|
|
||||||
range.OffsetInDescriptorsFromTableStart = D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Root parameters
|
|
||||||
D3D12_ROOT_PARAMETER rootParameters[GPU_MAX_CB_BINDED + 3];
|
|
||||||
for (int32 i = 0; i < GPU_MAX_CB_BINDED; i++)
|
|
||||||
{
|
|
||||||
// CB
|
|
||||||
D3D12_ROOT_PARAMETER& rootParam = rootParameters[DX12_ROOT_SIGNATURE_CB + i];
|
|
||||||
rootParam.ParameterType = D3D12_ROOT_PARAMETER_TYPE_CBV;
|
|
||||||
rootParam.ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL;
|
|
||||||
rootParam.Descriptor.ShaderRegister = i;
|
|
||||||
rootParam.Descriptor.RegisterSpace = 0;
|
|
||||||
}
|
|
||||||
{
|
|
||||||
// SRVs
|
|
||||||
D3D12_ROOT_PARAMETER& rootParam = rootParameters[DX12_ROOT_SIGNATURE_SR];
|
|
||||||
rootParam.ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE;
|
|
||||||
rootParam.ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL;
|
|
||||||
rootParam.DescriptorTable.NumDescriptorRanges = 1;
|
|
||||||
rootParam.DescriptorTable.pDescriptorRanges = &r[0];
|
|
||||||
}
|
|
||||||
{
|
|
||||||
// UAVs
|
|
||||||
D3D12_ROOT_PARAMETER& rootParam = rootParameters[DX12_ROOT_SIGNATURE_UA];
|
|
||||||
rootParam.ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE;
|
|
||||||
rootParam.ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL;
|
|
||||||
rootParam.DescriptorTable.NumDescriptorRanges = 1;
|
|
||||||
rootParam.DescriptorTable.pDescriptorRanges = &r[1];
|
|
||||||
}
|
|
||||||
{
|
|
||||||
// Samplers
|
|
||||||
D3D12_ROOT_PARAMETER& rootParam = rootParameters[DX12_ROOT_SIGNATURE_SAMPLER];
|
|
||||||
rootParam.ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE;
|
|
||||||
rootParam.ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL;
|
|
||||||
rootParam.DescriptorTable.NumDescriptorRanges = 1;
|
|
||||||
rootParam.DescriptorTable.pDescriptorRanges = &r[2];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Static samplers
|
|
||||||
D3D12_STATIC_SAMPLER_DESC staticSamplers[6];
|
|
||||||
static_assert(GPU_STATIC_SAMPLERS_COUNT == ARRAY_COUNT(staticSamplers), "Update static samplers setup.");
|
|
||||||
// Linear Clamp
|
|
||||||
staticSamplers[0].Filter = D3D12_FILTER_MIN_MAG_MIP_LINEAR;
|
|
||||||
staticSamplers[0].AddressU = D3D12_TEXTURE_ADDRESS_MODE_CLAMP;
|
|
||||||
staticSamplers[0].AddressV = D3D12_TEXTURE_ADDRESS_MODE_CLAMP;
|
|
||||||
staticSamplers[0].AddressW = D3D12_TEXTURE_ADDRESS_MODE_CLAMP;
|
|
||||||
staticSamplers[0].MipLODBias = 0.0f;
|
|
||||||
staticSamplers[0].MaxAnisotropy = 1;
|
|
||||||
staticSamplers[0].BorderColor = D3D12_STATIC_BORDER_COLOR_OPAQUE_BLACK;
|
|
||||||
staticSamplers[0].MinLOD = 0;
|
|
||||||
staticSamplers[0].MaxLOD = D3D12_FLOAT32_MAX;
|
|
||||||
staticSamplers[0].ShaderRegister = 0;
|
|
||||||
staticSamplers[0].RegisterSpace = 0;
|
|
||||||
staticSamplers[0].ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL;
|
|
||||||
// Point Clamp
|
|
||||||
staticSamplers[1].Filter = D3D12_FILTER_MIN_MAG_MIP_POINT;
|
|
||||||
staticSamplers[1].AddressU = D3D12_TEXTURE_ADDRESS_MODE_CLAMP;
|
|
||||||
staticSamplers[1].AddressV = D3D12_TEXTURE_ADDRESS_MODE_CLAMP;
|
|
||||||
staticSamplers[1].AddressW = D3D12_TEXTURE_ADDRESS_MODE_CLAMP;
|
|
||||||
staticSamplers[1].MipLODBias = 0.0f;
|
|
||||||
staticSamplers[1].MaxAnisotropy = 1;
|
|
||||||
staticSamplers[1].BorderColor = D3D12_STATIC_BORDER_COLOR_OPAQUE_BLACK;
|
|
||||||
staticSamplers[1].MinLOD = 0;
|
|
||||||
staticSamplers[1].MaxLOD = D3D12_FLOAT32_MAX;
|
|
||||||
staticSamplers[1].ShaderRegister = 1;
|
|
||||||
staticSamplers[1].RegisterSpace = 0;
|
|
||||||
staticSamplers[1].ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL;
|
|
||||||
// Linear Wrap
|
|
||||||
staticSamplers[2].Filter = D3D12_FILTER_MIN_MAG_MIP_LINEAR;
|
|
||||||
staticSamplers[2].AddressU = D3D12_TEXTURE_ADDRESS_MODE_WRAP;
|
|
||||||
staticSamplers[2].AddressV = D3D12_TEXTURE_ADDRESS_MODE_WRAP;
|
|
||||||
staticSamplers[2].AddressW = D3D12_TEXTURE_ADDRESS_MODE_WRAP;
|
|
||||||
staticSamplers[2].MipLODBias = 0.0f;
|
|
||||||
staticSamplers[2].MaxAnisotropy = 1;
|
|
||||||
staticSamplers[2].BorderColor = D3D12_STATIC_BORDER_COLOR_OPAQUE_BLACK;
|
|
||||||
staticSamplers[2].MinLOD = 0;
|
|
||||||
staticSamplers[2].MaxLOD = D3D12_FLOAT32_MAX;
|
|
||||||
staticSamplers[2].ShaderRegister = 2;
|
|
||||||
staticSamplers[2].RegisterSpace = 0;
|
|
||||||
staticSamplers[2].ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL;
|
|
||||||
// Point Wrap
|
|
||||||
staticSamplers[3].Filter = D3D12_FILTER_MIN_MAG_MIP_POINT;
|
|
||||||
staticSamplers[3].AddressU = D3D12_TEXTURE_ADDRESS_MODE_WRAP;
|
|
||||||
staticSamplers[3].AddressV = D3D12_TEXTURE_ADDRESS_MODE_WRAP;
|
|
||||||
staticSamplers[3].AddressW = D3D12_TEXTURE_ADDRESS_MODE_WRAP;
|
|
||||||
staticSamplers[3].MipLODBias = 0.0f;
|
|
||||||
staticSamplers[3].MaxAnisotropy = 1;
|
|
||||||
staticSamplers[3].BorderColor = D3D12_STATIC_BORDER_COLOR_OPAQUE_BLACK;
|
|
||||||
staticSamplers[3].MinLOD = 0;
|
|
||||||
staticSamplers[3].MaxLOD = D3D12_FLOAT32_MAX;
|
|
||||||
staticSamplers[3].ShaderRegister = 3;
|
|
||||||
staticSamplers[3].RegisterSpace = 0;
|
|
||||||
staticSamplers[3].ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL;
|
|
||||||
// Shadow
|
|
||||||
staticSamplers[4].Filter = D3D12_FILTER_COMPARISON_MIN_MAG_MIP_POINT;
|
|
||||||
staticSamplers[4].AddressU = D3D12_TEXTURE_ADDRESS_MODE_CLAMP;
|
|
||||||
staticSamplers[4].AddressV = D3D12_TEXTURE_ADDRESS_MODE_CLAMP;
|
|
||||||
staticSamplers[4].AddressW = D3D12_TEXTURE_ADDRESS_MODE_CLAMP;
|
|
||||||
staticSamplers[4].MipLODBias = 0.0f;
|
|
||||||
staticSamplers[4].MaxAnisotropy = 1;
|
|
||||||
staticSamplers[4].ComparisonFunc = D3D12_COMPARISON_FUNC_LESS_EQUAL;
|
|
||||||
staticSamplers[4].BorderColor = D3D12_STATIC_BORDER_COLOR_OPAQUE_BLACK;
|
|
||||||
staticSamplers[4].MinLOD = 0;
|
|
||||||
staticSamplers[4].MaxLOD = D3D12_FLOAT32_MAX;
|
|
||||||
staticSamplers[4].ShaderRegister = 4;
|
|
||||||
staticSamplers[4].RegisterSpace = 0;
|
|
||||||
staticSamplers[4].ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL;
|
|
||||||
// Shadow PCF
|
|
||||||
staticSamplers[5].Filter = D3D12_FILTER_COMPARISON_MIN_MAG_MIP_LINEAR;
|
|
||||||
staticSamplers[5].AddressU = D3D12_TEXTURE_ADDRESS_MODE_CLAMP;
|
|
||||||
staticSamplers[5].AddressV = D3D12_TEXTURE_ADDRESS_MODE_CLAMP;
|
|
||||||
staticSamplers[5].AddressW = D3D12_TEXTURE_ADDRESS_MODE_CLAMP;
|
|
||||||
staticSamplers[5].MipLODBias = 0.0f;
|
|
||||||
staticSamplers[5].MaxAnisotropy = 1;
|
|
||||||
staticSamplers[5].ComparisonFunc = D3D12_COMPARISON_FUNC_LESS_EQUAL;
|
|
||||||
staticSamplers[5].BorderColor = D3D12_STATIC_BORDER_COLOR_OPAQUE_BLACK;
|
|
||||||
staticSamplers[5].MinLOD = 0;
|
|
||||||
staticSamplers[5].MaxLOD = D3D12_FLOAT32_MAX;
|
|
||||||
staticSamplers[5].ShaderRegister = 5;
|
|
||||||
staticSamplers[5].RegisterSpace = 0;
|
|
||||||
staticSamplers[5].ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL;
|
|
||||||
|
|
||||||
// Init
|
|
||||||
D3D12_ROOT_SIGNATURE_DESC rootSignatureDesc;
|
|
||||||
rootSignatureDesc.NumParameters = ARRAY_COUNT(rootParameters);
|
|
||||||
rootSignatureDesc.pParameters = rootParameters;
|
|
||||||
rootSignatureDesc.NumStaticSamplers = ARRAY_COUNT(staticSamplers);
|
|
||||||
rootSignatureDesc.pStaticSamplers = staticSamplers;
|
|
||||||
rootSignatureDesc.Flags = D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT;
|
|
||||||
|
|
||||||
// Serialize
|
|
||||||
ComPtr<ID3DBlob> signature;
|
|
||||||
ComPtr<ID3DBlob> error;
|
|
||||||
VALIDATE_DIRECTX_CALL(D3D12SerializeRootSignature(&rootSignatureDesc, D3D_ROOT_SIGNATURE_VERSION_1, &signature, &error));
|
|
||||||
|
|
||||||
// Create
|
|
||||||
VALIDATE_DIRECTX_CALL(_device->CreateRootSignature(0, signature->GetBufferPointer(), signature->GetBufferSize(), IID_PPV_ARGS(&_rootSignature)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (TimestampQueryHeap.Init())
|
if (TimestampQueryHeap.Init())
|
||||||
|
|||||||
@@ -18,11 +18,6 @@
|
|||||||
#define DX12_BACK_BUFFER_COUNT 2
|
#define DX12_BACK_BUFFER_COUNT 2
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#define DX12_ROOT_SIGNATURE_CB 0
|
|
||||||
#define DX12_ROOT_SIGNATURE_SR (GPU_MAX_CB_BINDED+0)
|
|
||||||
#define DX12_ROOT_SIGNATURE_UA (GPU_MAX_CB_BINDED+1)
|
|
||||||
#define DX12_ROOT_SIGNATURE_SAMPLER (GPU_MAX_CB_BINDED+2)
|
|
||||||
|
|
||||||
class Engine;
|
class Engine;
|
||||||
class WindowsWindow;
|
class WindowsWindow;
|
||||||
class GPUContextDX12;
|
class GPUContextDX12;
|
||||||
|
|||||||
@@ -159,7 +159,7 @@ bool GPUTextureDX12::OnInit()
|
|||||||
initialState = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE | D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE;
|
initialState = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE | D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE;
|
||||||
|
|
||||||
// Create texture
|
// Create texture
|
||||||
#if PLATFORM_WINDOWS
|
#if PLATFORM_WINDOWS && 0
|
||||||
D3D12_HEAP_FLAGS heapFlags = useRTV || useDSV ? D3D12_HEAP_FLAG_CREATE_NOT_ZEROED : D3D12_HEAP_FLAG_NONE;
|
D3D12_HEAP_FLAGS heapFlags = useRTV || useDSV ? D3D12_HEAP_FLAG_CREATE_NOT_ZEROED : D3D12_HEAP_FLAG_NONE;
|
||||||
#else
|
#else
|
||||||
D3D12_HEAP_FLAGS heapFlags = D3D12_HEAP_FLAG_NONE;
|
D3D12_HEAP_FLAGS heapFlags = D3D12_HEAP_FLAG_NONE;
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
// Copyright (c) Wojciech Figat. All rights reserved.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Engine/Graphics/Config.h"
|
||||||
|
#include "../IncludeDirectXHeaders.h"
|
||||||
|
|
||||||
|
#define DX12_ROOT_SIGNATURE_CB 0
|
||||||
|
#define DX12_ROOT_SIGNATURE_SR (GPU_MAX_CB_BINDED+0)
|
||||||
|
#define DX12_ROOT_SIGNATURE_UA (GPU_MAX_CB_BINDED+1)
|
||||||
|
#define DX12_ROOT_SIGNATURE_SAMPLER (GPU_MAX_CB_BINDED+2)
|
||||||
|
|
||||||
|
struct RootSignatureDX12
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
D3D12_ROOT_SIGNATURE_DESC _desc;
|
||||||
|
D3D12_DESCRIPTOR_RANGE _ranges[3];
|
||||||
|
D3D12_ROOT_PARAMETER _parameters[GPU_MAX_CB_BINDED + 3];
|
||||||
|
D3D12_STATIC_SAMPLER_DESC _staticSamplers[6];
|
||||||
|
|
||||||
|
public:
|
||||||
|
RootSignatureDX12();
|
||||||
|
|
||||||
|
ComPtr<ID3DBlob> Serialize() const;
|
||||||
|
#if USE_EDITOR
|
||||||
|
void ToString(class StringBuilder& sb, bool singleLine = false) const;
|
||||||
|
String ToString() const;
|
||||||
|
StringAnsi ToStringAnsi() const;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
private:
|
||||||
|
void InitSampler(int32 i, D3D12_FILTER filter, D3D12_TEXTURE_ADDRESS_MODE address, D3D12_COMPARISON_FUNC comparisonFunc = D3D12_COMPARISON_FUNC_LESS_EQUAL);
|
||||||
|
};
|
||||||
@@ -177,7 +177,7 @@ public:
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void ClearState() override
|
void ResetState() override
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1329,7 +1329,7 @@ void GPUContextVulkan::SetState(GPUPipelineState* state)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GPUContextVulkan::ClearState()
|
void GPUContextVulkan::ResetState()
|
||||||
{
|
{
|
||||||
ResetRenderTarget();
|
ResetRenderTarget();
|
||||||
ResetSR();
|
ResetSR();
|
||||||
|
|||||||
@@ -193,7 +193,7 @@ public:
|
|||||||
void SetScissor(const Rectangle& scissorRect) override;
|
void SetScissor(const Rectangle& scissorRect) override;
|
||||||
GPUPipelineState* GetState() const override;
|
GPUPipelineState* GetState() const override;
|
||||||
void SetState(GPUPipelineState* state) override;
|
void SetState(GPUPipelineState* state) override;
|
||||||
void ClearState() override;
|
void ResetState() override;
|
||||||
void FlushState() override;
|
void FlushState() override;
|
||||||
void Flush() override;
|
void Flush() override;
|
||||||
void UpdateBuffer(GPUBuffer* buffer, const void* data, uint32 size, uint32 offset) override;
|
void UpdateBuffer(GPUBuffer* buffer, const void* data, uint32 size, uint32 offset) override;
|
||||||
|
|||||||
@@ -554,10 +554,11 @@ void AnimatedModel::StopSlotAnimation(const StringView& slotName, Animation* ani
|
|||||||
{
|
{
|
||||||
for (auto& slot : GraphInstance.Slots)
|
for (auto& slot : GraphInstance.Slots)
|
||||||
{
|
{
|
||||||
if (slot.Animation == anim && slot.Name == slotName)
|
if ((slot.Animation == anim || anim == nullptr) && slot.Name == slotName)
|
||||||
{
|
{
|
||||||
//slot.Animation = nullptr; // TODO: make an immediate version of this method and set the animation to nullptr.
|
//slot.Animation = nullptr; // TODO: make an immediate version of this method and set the animation to nullptr.
|
||||||
slot.Reset = true;
|
if (slot.Animation != nullptr)
|
||||||
|
slot.Reset = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -573,7 +574,7 @@ void AnimatedModel::PauseSlotAnimation(const StringView& slotName, Animation* an
|
|||||||
{
|
{
|
||||||
for (auto& slot : GraphInstance.Slots)
|
for (auto& slot : GraphInstance.Slots)
|
||||||
{
|
{
|
||||||
if (slot.Animation == anim && slot.Name == slotName)
|
if ((slot.Animation == anim || anim == nullptr) && slot.Name == slotName)
|
||||||
{
|
{
|
||||||
slot.Pause = true;
|
slot.Pause = true;
|
||||||
break;
|
break;
|
||||||
@@ -595,7 +596,7 @@ bool AnimatedModel::IsPlayingSlotAnimation(const StringView& slotName, Animation
|
|||||||
{
|
{
|
||||||
for (auto& slot : GraphInstance.Slots)
|
for (auto& slot : GraphInstance.Slots)
|
||||||
{
|
{
|
||||||
if (slot.Animation == anim && slot.Name == slotName && !slot.Pause)
|
if ((slot.Animation == anim || anim == nullptr) && slot.Name == slotName && !slot.Pause)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -412,8 +412,8 @@ public:
|
|||||||
/// Stops the animation playback on the slot in Anim Graph.
|
/// Stops the animation playback on the slot in Anim Graph.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="slotName">The name of the slot.</param>
|
/// <param name="slotName">The name of the slot.</param>
|
||||||
/// <param name="anim">The animation to stop.</param>
|
/// <param name="anim">The animation to check. Null to use slot name only.</param>
|
||||||
API_FUNCTION() void StopSlotAnimation(const StringView& slotName, Animation* anim);
|
API_FUNCTION() void StopSlotAnimation(const StringView& slotName, Animation* anim = nullptr);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Pauses all the animations playback on the all slots in Anim Graph.
|
/// Pauses all the animations playback on the all slots in Anim Graph.
|
||||||
@@ -424,8 +424,8 @@ public:
|
|||||||
/// Pauses the animation playback on the slot in Anim Graph.
|
/// Pauses the animation playback on the slot in Anim Graph.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="slotName">The name of the slot.</param>
|
/// <param name="slotName">The name of the slot.</param>
|
||||||
/// <param name="anim">The animation to pause.</param>
|
/// <param name="anim">The animation to check. Null to use slot name only.</param>
|
||||||
API_FUNCTION() void PauseSlotAnimation(const StringView& slotName, Animation* anim);
|
API_FUNCTION() void PauseSlotAnimation(const StringView& slotName, Animation* anim = nullptr);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Checks if any animation playback is active on any slot in Anim Graph (not paused).
|
/// Checks if any animation playback is active on any slot in Anim Graph (not paused).
|
||||||
@@ -436,8 +436,8 @@ public:
|
|||||||
/// Checks if the animation playback is active on the slot in Anim Graph (not paused).
|
/// Checks if the animation playback is active on the slot in Anim Graph (not paused).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="slotName">The name of the slot.</param>
|
/// <param name="slotName">The name of the slot.</param>
|
||||||
/// <param name="anim">The animation to check.</param>
|
/// <param name="anim">The animation to check. Null to use slot name only.</param>
|
||||||
API_FUNCTION() bool IsPlayingSlotAnimation(const StringView& slotName, Animation* anim);
|
API_FUNCTION() bool IsPlayingSlotAnimation(const StringView& slotName, Animation* anim = nullptr);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void ApplyRootMotion(const Transform& rootMotionDelta);
|
void ApplyRootMotion(const Transform& rootMotionDelta);
|
||||||
|
|||||||
@@ -42,38 +42,38 @@ public:
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The reflections texture resolution.
|
/// The reflections texture resolution.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
API_FIELD(Attributes="EditorOrder(0), EditorDisplay(\"Probe\")")
|
API_FIELD(Attributes="EditorOrder(0), EditorDisplay(\"Quality\")")
|
||||||
ProbeCubemapResolution CubemapResolution = ProbeCubemapResolution::UseGraphicsSettings;
|
ProbeCubemapResolution CubemapResolution = ProbeCubemapResolution::UseGraphicsSettings;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The probe update mode.
|
||||||
|
/// </summary>
|
||||||
|
API_FIELD(Attributes = "EditorOrder(10), EditorDisplay(\"Quality\")")
|
||||||
|
ProbeUpdateMode UpdateMode = ProbeUpdateMode::Manual;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The reflections brightness.
|
/// The reflections brightness.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
API_FIELD(Attributes="EditorOrder(10), Limit(0, 1000, 0.01f), EditorDisplay(\"Probe\")")
|
API_FIELD(Attributes="EditorOrder(0), Limit(0, 1000, 0.01f), EditorDisplay(\"Probe\")")
|
||||||
float Brightness = 1.0f;
|
float Brightness = 1.0f;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The probe rendering order. The higher values are render later (on top).
|
/// The probe rendering order. The higher values are render later (on top).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
API_FIELD(Attributes = "EditorOrder(25), EditorDisplay(\"Probe\")")
|
API_FIELD(Attributes = "EditorOrder(20), EditorDisplay(\"Probe\")")
|
||||||
int32 SortOrder = 0;
|
int32 SortOrder = 0;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The probe update mode.
|
|
||||||
/// </summary>
|
|
||||||
API_FIELD(Attributes="EditorOrder(30), EditorDisplay(\"Probe\")")
|
|
||||||
ProbeUpdateMode UpdateMode = ProbeUpdateMode::Manual;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The probe capture camera near plane distance.
|
/// The probe capture camera near plane distance.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
API_FIELD(Attributes="EditorOrder(30), Limit(0, float.MaxValue, 0.01f), EditorDisplay(\"Probe\")")
|
API_FIELD(Attributes="EditorOrder(25), Limit(0, float.MaxValue, 0.01f), EditorDisplay(\"Probe\")")
|
||||||
float CaptureNearPlane = 10.0f;
|
float CaptureNearPlane = 10.0f;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the probe radius.
|
/// Gets the probe radius.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
API_PROPERTY(Attributes="EditorOrder(20), DefaultValue(3000.0f), Limit(0), EditorDisplay(\"Probe\")")
|
API_PROPERTY(Attributes="EditorOrder(15), DefaultValue(3000.0f), Limit(0), EditorDisplay(\"Probe\")")
|
||||||
float GetRadius() const;
|
float GetRadius() const;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -518,7 +518,7 @@ namespace
|
|||||||
Vector3 nextPos = transform.LocalToWorld(next->Value.Translation);
|
Vector3 nextPos = transform.LocalToWorld(next->Value.Translation);
|
||||||
DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(nextPos, NodeSizeByDistance(nextPos, scaleByDistance)), color, 0.0f, depthTest);
|
DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(nextPos, NodeSizeByDistance(nextPos, scaleByDistance)), color, 0.0f, depthTest);
|
||||||
const float d = (next->Time - prev->Time) / 3.0f;
|
const float d = (next->Time - prev->Time) / 3.0f;
|
||||||
DEBUG_DRAW_BEZIER(prevPos, prevPos + prev->TangentOut.Translation * d, nextPos + next->TangentIn.Translation * d, nextPos, color, 0.0f, depthTest);
|
DEBUG_DRAW_BEZIER(prevPos, transform.LocalToWorld(prev->Value.Translation + prev->TangentOut.Translation * d), transform.LocalToWorld(next->Value.Translation + next->TangentIn.Translation * d), nextPos, color, 0.0f, depthTest);
|
||||||
prev = next;
|
prev = next;
|
||||||
prevPos = nextPos;
|
prevPos = nextPos;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ const Char* GetCommandLine(int argc, char* argv[])
|
|||||||
const Char* cmdLine;
|
const Char* cmdLine;
|
||||||
if (length != 0)
|
if (length != 0)
|
||||||
{
|
{
|
||||||
Char* str = (Char*)malloc(length * sizeof(Char));
|
Char* str = (Char*)malloc((length + 1) * sizeof(Char));
|
||||||
cmdLine = str;
|
cmdLine = str;
|
||||||
for (int i = 1; i < argc; i++)
|
for (int i = 1; i < argc; i++)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Internal version number of networking implementation. Updated once engine changes serialization or connection rules.
|
// Internal version number of networking implementation. Updated once engine changes serialization or connection rules.
|
||||||
#define NETWORK_PROTOCOL_VERSION 4
|
#define NETWORK_PROTOCOL_VERSION 5
|
||||||
|
|
||||||
// Enables encoding object ids and typenames via uint32 keys rather than full data send.
|
// Enables encoding object ids and typenames via uint32 keys rather than full data send.
|
||||||
#define USE_NETWORK_KEYS 1
|
#define USE_NETWORK_KEYS 1
|
||||||
@@ -29,6 +29,7 @@ enum class NetworkMessageIDs : uint8
|
|||||||
ObjectDespawn,
|
ObjectDespawn,
|
||||||
ObjectRole,
|
ObjectRole,
|
||||||
ObjectRpc,
|
ObjectRpc,
|
||||||
|
ObjectRpcPart,
|
||||||
|
|
||||||
MAX,
|
MAX,
|
||||||
};
|
};
|
||||||
@@ -48,6 +49,7 @@ public:
|
|||||||
static void OnNetworkMessageObjectDespawn(NetworkEvent& event, NetworkClient* client, NetworkPeer* peer);
|
static void OnNetworkMessageObjectDespawn(NetworkEvent& event, NetworkClient* client, NetworkPeer* peer);
|
||||||
static void OnNetworkMessageObjectRole(NetworkEvent& event, NetworkClient* client, NetworkPeer* peer);
|
static void OnNetworkMessageObjectRole(NetworkEvent& event, NetworkClient* client, NetworkPeer* peer);
|
||||||
static void OnNetworkMessageObjectRpc(NetworkEvent& event, NetworkClient* client, NetworkPeer* peer);
|
static void OnNetworkMessageObjectRpc(NetworkEvent& event, NetworkClient* client, NetworkPeer* peer);
|
||||||
|
static void OnNetworkMessageObjectRpcPart(NetworkEvent& event, NetworkClient* client, NetworkPeer* peer);
|
||||||
|
|
||||||
#if COMPILE_WITH_PROFILER
|
#if COMPILE_WITH_PROFILER
|
||||||
|
|
||||||
|
|||||||
@@ -391,6 +391,7 @@ namespace
|
|||||||
NetworkInternal::OnNetworkMessageObjectDespawn,
|
NetworkInternal::OnNetworkMessageObjectDespawn,
|
||||||
NetworkInternal::OnNetworkMessageObjectRole,
|
NetworkInternal::OnNetworkMessageObjectRole,
|
||||||
NetworkInternal::OnNetworkMessageObjectRpc,
|
NetworkInternal::OnNetworkMessageObjectRpc,
|
||||||
|
NetworkInternal::OnNetworkMessageObjectRpcPart,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -100,6 +100,35 @@ API_STRUCT(NoDefault, Namespace="FlaxEngine.Networking") struct FLAXENGINE_API N
|
|||||||
return Word0 + Word1 != 0;
|
return Word0 + Word1 != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NetworkClientsMask operator&(const NetworkClientsMask& other) const
|
||||||
|
{
|
||||||
|
return { Word0 & other.Word0, Word1 & other.Word1 };
|
||||||
|
}
|
||||||
|
|
||||||
|
NetworkClientsMask operator|(const NetworkClientsMask& other) const
|
||||||
|
{
|
||||||
|
return { Word0 | other.Word0, Word1 | other.Word1 };
|
||||||
|
}
|
||||||
|
|
||||||
|
NetworkClientsMask operator~() const
|
||||||
|
{
|
||||||
|
return { ~Word0, ~Word1 };
|
||||||
|
}
|
||||||
|
|
||||||
|
NetworkClientsMask& operator|=(const NetworkClientsMask& other)
|
||||||
|
{
|
||||||
|
Word0 |= other.Word0;
|
||||||
|
Word1 |= other.Word1;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
NetworkClientsMask& operator&=(const NetworkClientsMask& other)
|
||||||
|
{
|
||||||
|
Word0 &= other.Word0;
|
||||||
|
Word1 &= other.Word1;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
bool operator==(const NetworkClientsMask& other) const
|
bool operator==(const NetworkClientsMask& other) const
|
||||||
{
|
{
|
||||||
return Word0 == other.Word0 && Word1 == other.Word1;
|
return Word0 == other.Word0 && Word1 == other.Word1;
|
||||||
|
|||||||
@@ -55,14 +55,14 @@ PACK_STRUCT(struct NetworkMessageObjectReplicate
|
|||||||
uint32 OwnerFrame;
|
uint32 OwnerFrame;
|
||||||
});
|
});
|
||||||
|
|
||||||
PACK_STRUCT(struct NetworkMessageObjectReplicatePayload
|
PACK_STRUCT(struct NetworkMessageObjectPartPayload
|
||||||
{
|
{
|
||||||
uint16 DataSize;
|
uint16 DataSize;
|
||||||
uint16 PartsCount;
|
uint16 PartsCount;
|
||||||
uint16 PartSize;
|
uint16 PartSize;
|
||||||
});
|
});
|
||||||
|
|
||||||
PACK_STRUCT(struct NetworkMessageObjectReplicatePart
|
PACK_STRUCT(struct NetworkMessageObjectPart
|
||||||
{
|
{
|
||||||
NetworkMessageIDs ID = NetworkMessageIDs::ObjectReplicatePart;
|
NetworkMessageIDs ID = NetworkMessageIDs::ObjectReplicatePart;
|
||||||
uint32 OwnerFrame;
|
uint32 OwnerFrame;
|
||||||
@@ -111,22 +111,11 @@ PACK_STRUCT(struct NetworkMessageObjectRole
|
|||||||
PACK_STRUCT(struct NetworkMessageObjectRpc
|
PACK_STRUCT(struct NetworkMessageObjectRpc
|
||||||
{
|
{
|
||||||
NetworkMessageIDs ID = NetworkMessageIDs::ObjectRpc;
|
NetworkMessageIDs ID = NetworkMessageIDs::ObjectRpc;
|
||||||
uint16 ArgsSize;
|
uint32 OwnerFrame;
|
||||||
});
|
});
|
||||||
|
|
||||||
struct NetworkReplicatedObject
|
struct NetworkReplicatedObject
|
||||||
{
|
{
|
||||||
ScriptingObjectReference<ScriptingObject> Object;
|
|
||||||
Guid ObjectId;
|
|
||||||
Guid ParentId;
|
|
||||||
uint32 OwnerClientId;
|
|
||||||
uint32 LastOwnerFrame = 0;
|
|
||||||
NetworkObjectRole Role;
|
|
||||||
uint8 Spawned : 1;
|
|
||||||
uint8 Synced : 1;
|
|
||||||
DataContainer<uint32> TargetClientIds;
|
|
||||||
INetworkObject* AsNetworkObject;
|
|
||||||
|
|
||||||
struct
|
struct
|
||||||
{
|
{
|
||||||
NetworkClientsMask Mask;
|
NetworkClientsMask Mask;
|
||||||
@@ -139,6 +128,17 @@ struct NetworkReplicatedObject
|
|||||||
}
|
}
|
||||||
} RepCache;
|
} RepCache;
|
||||||
|
|
||||||
|
ScriptingObjectReference<ScriptingObject> Object;
|
||||||
|
Guid ObjectId;
|
||||||
|
Guid ParentId;
|
||||||
|
DataContainer<uint32> TargetClientIds;
|
||||||
|
INetworkObject* AsNetworkObject;
|
||||||
|
uint32 OwnerClientId;
|
||||||
|
uint32 LastOwnerFrame = 0;
|
||||||
|
NetworkObjectRole Role;
|
||||||
|
uint8 Spawned : 1;
|
||||||
|
uint8 Synced : 1;
|
||||||
|
|
||||||
NetworkReplicatedObject()
|
NetworkReplicatedObject()
|
||||||
{
|
{
|
||||||
Spawned = 0;
|
Spawned = 0;
|
||||||
@@ -152,12 +152,12 @@ struct NetworkReplicatedObject
|
|||||||
|
|
||||||
bool operator==(const NetworkReplicatedObject& other) const
|
bool operator==(const NetworkReplicatedObject& other) const
|
||||||
{
|
{
|
||||||
return Object == other.Object;
|
return ObjectId == other.ObjectId;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool operator==(const ScriptingObject* other) const
|
bool operator==(const ScriptingObject* other) const
|
||||||
{
|
{
|
||||||
return Object == other;
|
return other && ObjectId == other->GetID();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool operator==(const Guid& other) const
|
bool operator==(const Guid& other) const
|
||||||
@@ -176,19 +176,25 @@ inline uint32 GetHash(const NetworkReplicatedObject& key)
|
|||||||
return GetHash(key.ObjectId);
|
return GetHash(key.ObjectId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline uint32 GetHash(const ScriptingObject* key)
|
||||||
|
{
|
||||||
|
return key ? GetHash(key->GetID()) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
struct Serializer
|
struct Serializer
|
||||||
{
|
{
|
||||||
NetworkReplicator::SerializeFunc Methods[2];
|
NetworkReplicator::SerializeFunc Methods[2];
|
||||||
void* Tags[2];
|
void* Tags[2];
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ReplicateItem
|
struct PartsItem
|
||||||
{
|
{
|
||||||
ScriptingObjectReference<ScriptingObject> Object;
|
ScriptingObjectReference<ScriptingObject> Object;
|
||||||
Guid ObjectId;
|
Guid ObjectId;
|
||||||
uint16 PartsLeft;
|
uint16 PartsLeft;
|
||||||
uint32 OwnerFrame;
|
uint32 OwnerFrame;
|
||||||
uint32 OwnerClientId;
|
uint32 OwnerClientId;
|
||||||
|
const void* Tag;
|
||||||
Array<byte> Data;
|
Array<byte> Data;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -220,7 +226,7 @@ struct DespawnItem
|
|||||||
DataContainer<uint32> Targets;
|
DataContainer<uint32> Targets;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct RpcItem
|
struct RpcSendItem
|
||||||
{
|
{
|
||||||
ScriptingObjectReference<ScriptingObject> Object;
|
ScriptingObjectReference<ScriptingObject> Object;
|
||||||
NetworkRpcName Name;
|
NetworkRpcName Name;
|
||||||
@@ -233,11 +239,12 @@ namespace
|
|||||||
{
|
{
|
||||||
CriticalSection ObjectsLock;
|
CriticalSection ObjectsLock;
|
||||||
HashSet<NetworkReplicatedObject> Objects;
|
HashSet<NetworkReplicatedObject> Objects;
|
||||||
Array<ReplicateItem> ReplicationParts;
|
Array<PartsItem> ReplicationParts;
|
||||||
|
Array<PartsItem> RpcParts;
|
||||||
Array<SpawnItemParts> SpawnParts;
|
Array<SpawnItemParts> SpawnParts;
|
||||||
Array<SpawnItem> SpawnQueue;
|
Array<SpawnItem> SpawnQueue;
|
||||||
Array<DespawnItem> DespawnQueue;
|
Array<DespawnItem> DespawnQueue;
|
||||||
Array<RpcItem> RpcQueue;
|
Array<RpcSendItem> RpcQueue;
|
||||||
Dictionary<Guid, Guid> IdsRemappingTable;
|
Dictionary<Guid, Guid> IdsRemappingTable;
|
||||||
NetworkStream* CachedWriteStream = nullptr;
|
NetworkStream* CachedWriteStream = nullptr;
|
||||||
NetworkStream* CachedReadStream = nullptr;
|
NetworkStream* CachedReadStream = nullptr;
|
||||||
@@ -251,6 +258,7 @@ namespace
|
|||||||
#endif
|
#endif
|
||||||
Array<Guid> DespawnedObjects;
|
Array<Guid> DespawnedObjects;
|
||||||
uint32 SpawnId = 0;
|
uint32 SpawnId = 0;
|
||||||
|
uint32 RpcId = 0;
|
||||||
|
|
||||||
#if USE_EDITOR
|
#if USE_EDITOR
|
||||||
void OnScriptsReloading()
|
void OnScriptsReloading()
|
||||||
@@ -505,6 +513,76 @@ void SetupObjectSpawnMessageItem(SpawnItem* e, NetworkMessage& msg)
|
|||||||
msg.WriteStructure(msgDataItem);
|
msg.WriteStructure(msgDataItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SendInParts(NetworkPeer* peer, NetworkChannelType channel, const byte* data, const uint16 dataSize, NetworkMessage& msg, const NetworkRpcName& name, bool toServer, const Guid& objectId, uint32 ownerFrame, NetworkMessageIDs partId)
|
||||||
|
{
|
||||||
|
NetworkMessageObjectPartPayload msgDataPayload;
|
||||||
|
msgDataPayload.DataSize = dataSize;
|
||||||
|
const uint32 networkKeyIdWorstCaseSize = sizeof(uint32) + sizeof(Guid);
|
||||||
|
const uint32 msgMaxData = peer->Config.MessageSize - msg.Position - sizeof(NetworkMessageObjectPartPayload);
|
||||||
|
const uint32 partMaxData = peer->Config.MessageSize - sizeof(NetworkMessageObjectPart) - networkKeyIdWorstCaseSize;
|
||||||
|
uint32 partsCount = 1;
|
||||||
|
uint32 dataStart = 0;
|
||||||
|
uint32 msgDataSize = dataSize;
|
||||||
|
if (dataSize > msgMaxData)
|
||||||
|
{
|
||||||
|
// Send msgMaxData within first message
|
||||||
|
msgDataSize = msgMaxData;
|
||||||
|
dataStart += msgMaxData;
|
||||||
|
|
||||||
|
// Send rest of the data in separate parts
|
||||||
|
partsCount += Math::DivideAndRoundUp(dataSize - dataStart, partMaxData);
|
||||||
|
|
||||||
|
// TODO: promote channel to Ordered when using parts?
|
||||||
|
}
|
||||||
|
else
|
||||||
|
dataStart += dataSize;
|
||||||
|
ASSERT(partsCount <= MAX_uint8);
|
||||||
|
msgDataPayload.PartsCount = partsCount;
|
||||||
|
msgDataPayload.PartSize = msgDataSize;
|
||||||
|
msg.WriteStructure(msgDataPayload);
|
||||||
|
msg.WriteBytes(data, msgDataSize);
|
||||||
|
uint32 messageSize = msg.Length;
|
||||||
|
if (toServer)
|
||||||
|
peer->EndSendMessage(channel, msg);
|
||||||
|
else
|
||||||
|
peer->EndSendMessage(channel, msg, CachedTargets);
|
||||||
|
|
||||||
|
// Send all other parts
|
||||||
|
for (uint32 partIndex = 1; partIndex < partsCount; partIndex++)
|
||||||
|
{
|
||||||
|
NetworkMessageObjectPart msgDataPart;
|
||||||
|
msgDataPart.ID = partId;
|
||||||
|
msgDataPart.OwnerFrame = ownerFrame;
|
||||||
|
msgDataPart.DataSize = msgDataPayload.DataSize;
|
||||||
|
msgDataPart.PartsCount = msgDataPayload.PartsCount;
|
||||||
|
msgDataPart.PartStart = dataStart;
|
||||||
|
msgDataPart.PartSize = Math::Min(dataSize - dataStart, partMaxData);
|
||||||
|
msg = peer->BeginSendMessage();
|
||||||
|
msg.WriteStructure(msgDataPart);
|
||||||
|
msg.WriteNetworkId(objectId);
|
||||||
|
msg.WriteBytes(data + msgDataPart.PartStart, msgDataPart.PartSize);
|
||||||
|
messageSize += msg.Length;
|
||||||
|
dataStart += msgDataPart.PartSize;
|
||||||
|
if (toServer)
|
||||||
|
peer->EndSendMessage(channel, msg);
|
||||||
|
else
|
||||||
|
peer->EndSendMessage(channel, msg, CachedTargets);
|
||||||
|
}
|
||||||
|
ASSERT_LOW_LAYER(dataStart == dataSize);
|
||||||
|
|
||||||
|
#if COMPILE_WITH_PROFILER
|
||||||
|
// Network stats recording
|
||||||
|
if (NetworkInternal::EnableProfiling)
|
||||||
|
{
|
||||||
|
auto& profileEvent = NetworkInternal::ProfilerEvents[name];
|
||||||
|
profileEvent.Count++;
|
||||||
|
profileEvent.DataSize += dataSize;
|
||||||
|
profileEvent.MessageSize += messageSize;
|
||||||
|
profileEvent.Receivers += toServer ? 1 : CachedTargets.Count();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
void SendObjectSpawnMessage(const SpawnGroup& group, const Array<NetworkClient*>& clients)
|
void SendObjectSpawnMessage(const SpawnGroup& group, const Array<NetworkClient*>& clients)
|
||||||
{
|
{
|
||||||
PROFILE_CPU();
|
PROFILE_CPU();
|
||||||
@@ -589,7 +667,7 @@ void SendObjectRoleMessage(const NetworkReplicatedObject& item, const NetworkCli
|
|||||||
msg.WriteNetworkId(objectId);
|
msg.WriteNetworkId(objectId);
|
||||||
if (NetworkManager::IsClient())
|
if (NetworkManager::IsClient())
|
||||||
{
|
{
|
||||||
NetworkManager::Peer->EndSendMessage(NetworkChannelType::ReliableOrdered, msg);
|
peer->EndSendMessage(NetworkChannelType::ReliableOrdered, msg);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -598,6 +676,169 @@ void SendObjectRoleMessage(const NetworkReplicatedObject& item, const NetworkCli
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SendDespawn(DespawnItem& e)
|
||||||
|
{
|
||||||
|
NETWORK_REPLICATOR_LOG(Info, "[NetworkReplicator] Despawn object ID={}", e.Id.ToString());
|
||||||
|
NetworkMessageObjectDespawn msgData;
|
||||||
|
Guid objectId = e.Id;
|
||||||
|
{
|
||||||
|
// Remap local client object ids into server ids
|
||||||
|
IdsRemappingTable.KeyOf(objectId, &objectId);
|
||||||
|
}
|
||||||
|
auto peer = NetworkManager::Peer;
|
||||||
|
NetworkMessage msg = peer->BeginSendMessage();
|
||||||
|
msg.WriteStructure(msgData);
|
||||||
|
msg.WriteNetworkId(objectId);
|
||||||
|
BuildCachedTargets(NetworkManager::Clients, e.Targets);
|
||||||
|
if (NetworkManager::IsClient())
|
||||||
|
peer->EndSendMessage(NetworkChannelType::ReliableOrdered, msg);
|
||||||
|
else
|
||||||
|
peer->EndSendMessage(NetworkChannelType::ReliableOrdered, msg, CachedTargets);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SendReplication(ScriptingObject* obj, NetworkClientsMask targetClients)
|
||||||
|
{
|
||||||
|
auto it = Objects.Find(obj->GetID());
|
||||||
|
if (it.IsEnd())
|
||||||
|
return;
|
||||||
|
auto& item = it->Item;
|
||||||
|
const bool isClient = NetworkManager::IsClient();
|
||||||
|
const NetworkClientsMask fullTargetClients = targetClients;
|
||||||
|
|
||||||
|
// If server has no recipients, skip early.
|
||||||
|
if (!isClient && !targetClients)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (item.AsNetworkObject)
|
||||||
|
item.AsNetworkObject->OnNetworkSerialize();
|
||||||
|
|
||||||
|
// Serialize object
|
||||||
|
NetworkStream* stream = CachedWriteStream;
|
||||||
|
stream->Initialize();
|
||||||
|
const bool failed = NetworkReplicator::InvokeSerializer(obj->GetTypeHandle(), obj, stream, true);
|
||||||
|
if (failed)
|
||||||
|
{
|
||||||
|
//NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Cannot serialize object {} of type {} (missing serialization logic)", item.ToString(), obj->GetType().ToString());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const uint32 size = stream->GetPosition();
|
||||||
|
if (size > MAX_uint16)
|
||||||
|
{
|
||||||
|
LOG(Error, "Too much data for object {} replication ({} bytes provided while limit is {}).", item.ToString(), size, MAX_uint16);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if USE_NETWORK_REPLICATOR_CACHE
|
||||||
|
// Process replication cache to skip sending object data if it didn't change
|
||||||
|
if (item.RepCache.Data.Length() == size && Platform::MemoryCompare(item.RepCache.Data.Get(), stream->GetBuffer(), size) == 0)
|
||||||
|
{
|
||||||
|
// Check if only newly joined clients are missing this data to avoid resending it to everyone
|
||||||
|
NetworkClientsMask missingClients = targetClients & ~item.RepCache.Mask;
|
||||||
|
|
||||||
|
// If data is the same and only the client set changed, replicate to missing clients only
|
||||||
|
if (!missingClients)
|
||||||
|
return;
|
||||||
|
targetClients = missingClients;
|
||||||
|
}
|
||||||
|
item.RepCache.Mask = fullTargetClients;
|
||||||
|
item.RepCache.Data.Copy(stream->GetBuffer(), size);
|
||||||
|
#endif
|
||||||
|
// TODO: use Unreliable for dynamic objects that are replicated every frame? (eg. player state)
|
||||||
|
constexpr NetworkChannelType repChannel = NetworkChannelType::Reliable;
|
||||||
|
|
||||||
|
// Skip serialization of objects that none will receive
|
||||||
|
if (!isClient)
|
||||||
|
{
|
||||||
|
BuildCachedTargets(item, targetClients);
|
||||||
|
if (CachedTargets.Count() == 0)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send object to clients
|
||||||
|
NetworkMessageObjectReplicate msgData;
|
||||||
|
msgData.OwnerFrame = NetworkManager::Frame;
|
||||||
|
Guid objectId = item.ObjectId, parentId = item.ParentId;
|
||||||
|
{
|
||||||
|
// Remap local client object ids into server ids
|
||||||
|
IdsRemappingTable.KeyOf(objectId, &objectId);
|
||||||
|
IdsRemappingTable.KeyOf(parentId, &parentId);
|
||||||
|
}
|
||||||
|
NetworkPeer* peer = NetworkManager::Peer;
|
||||||
|
NetworkMessage msg = peer->BeginSendMessage();
|
||||||
|
msg.WriteStructure(msgData);
|
||||||
|
msg.WriteNetworkId(objectId);
|
||||||
|
msg.WriteNetworkId(parentId);
|
||||||
|
msg.WriteNetworkName(obj->GetType().Fullname);
|
||||||
|
const NetworkRpcName name(obj->GetTypeHandle(), StringAnsiView::Empty);
|
||||||
|
SendInParts(peer, repChannel, stream->GetBuffer(), size, msg, name, isClient, objectId, msgData.OwnerFrame, NetworkMessageIDs::ObjectReplicatePart);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SendRpc(RpcSendItem& e)
|
||||||
|
{
|
||||||
|
ScriptingObject* obj = e.Object.Get();
|
||||||
|
if (!obj)
|
||||||
|
return;
|
||||||
|
auto it = Objects.Find(obj->GetID());
|
||||||
|
if (it == Objects.End())
|
||||||
|
{
|
||||||
|
#if !BUILD_RELEASE
|
||||||
|
if (!DespawnedObjects.Contains(obj->GetID()))
|
||||||
|
LOG(Error, "Cannot invoke RPC method '{0}.{1}' on object '{2}' that is not registered in networking (use 'NetworkReplicator.AddObject').", e.Name.First.ToString(), e.Name.Second.ToString(), obj->GetID());
|
||||||
|
#endif
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto& item = it->Item;
|
||||||
|
if (e.ArgsData.Length() > MAX_uint16)
|
||||||
|
{
|
||||||
|
LOG(Error, "Too much data for object RPC method '{}.{}' on object '{}' ({} bytes provided while limit is {}).", e.Name.First.ToString(), e.Name.Second.ToString(), obj->GetID(), e.ArgsData.Length(), MAX_uint16);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const NetworkManagerMode mode = NetworkManager::Mode;
|
||||||
|
NetworkPeer* peer = NetworkManager::Peer;
|
||||||
|
|
||||||
|
bool toServer;
|
||||||
|
if (e.Info.Server && mode == NetworkManagerMode::Client)
|
||||||
|
{
|
||||||
|
// Client -> Server
|
||||||
|
#if USE_NETWORK_REPLICATOR_LOG
|
||||||
|
if (e.Targets.Length() != 0)
|
||||||
|
NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Server RPC '{}.{}' called with non-empty list of targets is not supported (only server will receive it)", e.Name.First.ToString(), e.Name.Second.ToString());
|
||||||
|
#endif
|
||||||
|
toServer = true;
|
||||||
|
}
|
||||||
|
else if (e.Info.Client && (mode == NetworkManagerMode::Server || mode == NetworkManagerMode::Host))
|
||||||
|
{
|
||||||
|
// Server -> Client(s)
|
||||||
|
BuildCachedTargets(NetworkManager::Clients, item.TargetClientIds, e.Targets, NetworkManager::LocalClientId);
|
||||||
|
if (CachedTargets.IsEmpty())
|
||||||
|
return;
|
||||||
|
toServer = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Send RPC message
|
||||||
|
//NETWORK_REPLICATOR_LOG(Info, "[NetworkReplicator] Rpc {}.{} object ID={}", e.Name.First.ToString(), e.Name.Second.ToString(), item.ToString());
|
||||||
|
NetworkMessageObjectRpc msgData;
|
||||||
|
msgData.OwnerFrame = ++RpcId;
|
||||||
|
Guid objectId = item.ObjectId;
|
||||||
|
Guid parentId = item.ParentId;
|
||||||
|
{
|
||||||
|
// Remap local client object ids into server ids
|
||||||
|
IdsRemappingTable.KeyOf(objectId, &objectId);
|
||||||
|
IdsRemappingTable.KeyOf(parentId, &parentId);
|
||||||
|
}
|
||||||
|
NetworkMessage msg = peer->BeginSendMessage();
|
||||||
|
msg.WriteStructure(msgData);
|
||||||
|
msg.WriteNetworkId(objectId);
|
||||||
|
msg.WriteNetworkId(parentId);
|
||||||
|
msg.WriteNetworkName(obj->GetType().Fullname);
|
||||||
|
msg.WriteNetworkName(e.Name.First.GetType().Fullname);
|
||||||
|
msg.WriteNetworkName(e.Name.Second);
|
||||||
|
NetworkChannelType channel = (NetworkChannelType)e.Info.Channel;
|
||||||
|
SendInParts(peer, channel, e.ArgsData.Get(), e.ArgsData.Length(), msg, e.Name, toServer, objectId, msgData.OwnerFrame, NetworkMessageIDs::ObjectRpcPart);
|
||||||
|
}
|
||||||
|
|
||||||
void DeleteNetworkObject(ScriptingObject* obj)
|
void DeleteNetworkObject(ScriptingObject* obj)
|
||||||
{
|
{
|
||||||
// Remove from the mapping table
|
// Remove from the mapping table
|
||||||
@@ -708,38 +949,43 @@ FORCE_INLINE void DirtyObjectImpl(NetworkReplicatedObject& item, ScriptingObject
|
|||||||
Hierarchy->DirtyObject(obj);
|
Hierarchy->DirtyObject(obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
ReplicateItem* AddObjectReplicateItem(NetworkEvent& event, uint32 ownerFrame, uint16 partsCount, uint16 dataSize, const Guid& objectId, uint16 partStart, uint16 partSize, uint32 senderClientId)
|
PartsItem* AddPartsItem(Array<PartsItem>& items, NetworkEvent& event, uint32 ownerFrame, uint16 partsCount, uint16 dataSize, const Guid& objectId, uint16 partStart, uint16 partSize, uint32 senderClientId)
|
||||||
{
|
{
|
||||||
// Reuse or add part item
|
// Reuse or add part item
|
||||||
ReplicateItem* replicateItem = nullptr;
|
PartsItem* item = nullptr;
|
||||||
for (auto& e : ReplicationParts)
|
for (auto& e : items)
|
||||||
{
|
{
|
||||||
if (e.OwnerFrame == ownerFrame && e.Data.Count() == dataSize && e.ObjectId == objectId)
|
if (e.OwnerFrame == ownerFrame && e.Data.Count() == dataSize && e.ObjectId == objectId)
|
||||||
{
|
{
|
||||||
// Reuse
|
// Reuse
|
||||||
replicateItem = &e;
|
item = &e;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!replicateItem)
|
if (!item)
|
||||||
{
|
{
|
||||||
// Add
|
// Add
|
||||||
replicateItem = &ReplicationParts.AddOne();
|
item = &items.AddOne();
|
||||||
replicateItem->ObjectId = objectId;
|
item->ObjectId = objectId;
|
||||||
replicateItem->PartsLeft = partsCount;
|
item->PartsLeft = partsCount;
|
||||||
replicateItem->OwnerFrame = ownerFrame;
|
item->OwnerFrame = ownerFrame;
|
||||||
replicateItem->OwnerClientId = senderClientId;
|
item->OwnerClientId = senderClientId;
|
||||||
replicateItem->Data.Resize(dataSize);
|
item->Data.Resize(dataSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy part data
|
// Copy part data
|
||||||
ASSERT(replicateItem->PartsLeft > 0);
|
ASSERT(item->PartsLeft > 0);
|
||||||
replicateItem->PartsLeft--;
|
item->PartsLeft--;
|
||||||
ASSERT(partStart + partSize <= replicateItem->Data.Count());
|
ASSERT(partStart + partSize <= item->Data.Count());
|
||||||
const void* partData = event.Message.SkipBytes(partSize);
|
const void* partData = event.Message.SkipBytes(partSize);
|
||||||
Platform::MemoryCopy(replicateItem->Data.Get() + partStart, partData, partSize);
|
Platform::MemoryCopy(item->Data.Get() + partStart, partData, partSize);
|
||||||
|
|
||||||
return replicateItem;
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
FORCE_INLINE PartsItem* AddObjectReplicateItem(NetworkEvent& event, uint32 ownerFrame, uint16 partsCount, uint16 dataSize, const Guid& objectId, uint16 partStart, uint16 partSize, uint32 senderClientId)
|
||||||
|
{
|
||||||
|
return AddPartsItem(ReplicationParts, event, ownerFrame, partsCount, dataSize, objectId, partStart, partSize, senderClientId);
|
||||||
}
|
}
|
||||||
|
|
||||||
void InvokeObjectReplication(NetworkReplicatedObject& item, uint32 ownerFrame, byte* data, uint32 dataSize, uint32 senderClientId)
|
void InvokeObjectReplication(NetworkReplicatedObject& item, uint32 ownerFrame, byte* data, uint32 dataSize, uint32 senderClientId)
|
||||||
@@ -787,6 +1033,24 @@ void InvokeObjectReplication(NetworkReplicatedObject& item, uint32 ownerFrame, b
|
|||||||
DirtyObjectImpl(item, obj);
|
DirtyObjectImpl(item, obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FORCE_INLINE PartsItem* AddObjectRpcItem(NetworkEvent& event, uint32 ownerFrame, uint16 partsCount, uint16 dataSize, const Guid& objectId, uint16 partStart, uint16 partSize, uint32 senderClientId)
|
||||||
|
{
|
||||||
|
return AddPartsItem(RpcParts, event, ownerFrame, partsCount, dataSize, objectId, partStart, partSize, senderClientId);
|
||||||
|
}
|
||||||
|
|
||||||
|
void InvokeObjectRpc(const NetworkRpcInfo* info, byte* data, uint32 dataSize, uint32 senderClientId, ScriptingObject* obj)
|
||||||
|
{
|
||||||
|
// Setup message reading stream
|
||||||
|
if (CachedReadStream == nullptr)
|
||||||
|
CachedReadStream = New<NetworkStream>();
|
||||||
|
NetworkStream* stream = CachedReadStream;
|
||||||
|
stream->SenderId = senderClientId;
|
||||||
|
stream->Initialize(data, dataSize);
|
||||||
|
|
||||||
|
// Execute RPC
|
||||||
|
info->Execute(obj, stream, info->Tag);
|
||||||
|
}
|
||||||
|
|
||||||
void InvokeObjectSpawn(const NetworkMessageObjectSpawn& msgData, const Guid& prefabId, const NetworkMessageObjectSpawnItem* msgDataItems)
|
void InvokeObjectSpawn(const NetworkMessageObjectSpawn& msgData, const Guid& prefabId, const NetworkMessageObjectSpawnItem* msgDataItems)
|
||||||
{
|
{
|
||||||
ScopeLock lock(ObjectsLock);
|
ScopeLock lock(ObjectsLock);
|
||||||
@@ -1280,7 +1544,21 @@ void NetworkReplicator::DespawnObject(ScriptingObject* obj)
|
|||||||
// Register for despawning (batched during update)
|
// Register for despawning (batched during update)
|
||||||
auto& despawn = DespawnQueue.AddOne();
|
auto& despawn = DespawnQueue.AddOne();
|
||||||
despawn.Id = obj->GetID();
|
despawn.Id = obj->GetID();
|
||||||
despawn.Targets = item.TargetClientIds;
|
if (item.TargetClientIds.IsValid())
|
||||||
|
{
|
||||||
|
despawn.Targets = item.TargetClientIds;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Snapshot current recipients to avoid sending despawn to clients that connect later (and never got the spawn)
|
||||||
|
Array<uint32, InlinedAllocation<8>> clientIds;
|
||||||
|
for (const NetworkClient* client : NetworkManager::Clients)
|
||||||
|
{
|
||||||
|
if (client->State == NetworkConnectionState::Connected && client->ClientId != item.OwnerClientId)
|
||||||
|
clientIds.Add(client->ClientId);
|
||||||
|
}
|
||||||
|
despawn.Targets.Copy(clientIds);
|
||||||
|
}
|
||||||
|
|
||||||
// Prevent spawning
|
// Prevent spawning
|
||||||
for (int32 i = 0; i < SpawnQueue.Count(); i++)
|
for (int32 i = 0; i < SpawnQueue.Count(); i++)
|
||||||
@@ -1573,6 +1851,31 @@ void NetworkInternal::NetworkReplicatorClientConnected(NetworkClient* client)
|
|||||||
{
|
{
|
||||||
ScopeLock lock(ObjectsLock);
|
ScopeLock lock(ObjectsLock);
|
||||||
NewClients.Add(client);
|
NewClients.Add(client);
|
||||||
|
|
||||||
|
// Ensure cached replication acknowledges the new client without resending to others.
|
||||||
|
// Clear the new client's bit in RepCache and schedule a near-term replication.
|
||||||
|
const int32 clientIndex = NetworkManager::Clients.Find(client);
|
||||||
|
if (clientIndex != -1)
|
||||||
|
{
|
||||||
|
const uint64 bitMask = 1ull << (uint64)(clientIndex % 64);
|
||||||
|
const int32 wordIndex = clientIndex / 64;
|
||||||
|
for (auto it = Objects.Begin(); it.IsNotEnd(); ++it)
|
||||||
|
{
|
||||||
|
auto& item = it->Item;
|
||||||
|
ScriptingObject* obj = item.Object.Get();
|
||||||
|
if (!obj || !item.Spawned || item.Role != NetworkObjectRole::OwnedAuthoritative)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Mark this client as missing cached data
|
||||||
|
uint64* word = wordIndex == 0 ? &item.RepCache.Mask.Word0 : &item.RepCache.Mask.Word1;
|
||||||
|
*word &= ~bitMask;
|
||||||
|
|
||||||
|
// Force next replication tick for this object so the new client gets data promptly
|
||||||
|
if (Hierarchy)
|
||||||
|
Hierarchy->DirtyObject(obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ASSERT(sizeof(NetworkClientsMask) * 8 >= (uint32)NetworkManager::Clients.Count()); // Ensure that clients mask can hold all of clients
|
ASSERT(sizeof(NetworkClientsMask) * 8 >= (uint32)NetworkManager::Clients.Count()); // Ensure that clients mask can hold all of clients
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1652,9 +1955,6 @@ void NetworkInternal::NetworkReplicatorUpdate()
|
|||||||
if (Objects.Count() == 0)
|
if (Objects.Count() == 0)
|
||||||
return;
|
return;
|
||||||
const bool isClient = NetworkManager::IsClient();
|
const bool isClient = NetworkManager::IsClient();
|
||||||
const bool isServer = NetworkManager::IsServer();
|
|
||||||
const bool isHost = NetworkManager::IsHost();
|
|
||||||
NetworkPeer* peer = NetworkManager::Peer;
|
|
||||||
|
|
||||||
if (!isClient && NewClients.Count() != 0)
|
if (!isClient && NewClients.Count() != 0)
|
||||||
{
|
{
|
||||||
@@ -1694,22 +1994,7 @@ void NetworkInternal::NetworkReplicatorUpdate()
|
|||||||
PROFILE_CPU_NAMED("DespawnQueue");
|
PROFILE_CPU_NAMED("DespawnQueue");
|
||||||
for (DespawnItem& e : DespawnQueue)
|
for (DespawnItem& e : DespawnQueue)
|
||||||
{
|
{
|
||||||
// Send despawn message
|
SendDespawn(e);
|
||||||
NETWORK_REPLICATOR_LOG(Info, "[NetworkReplicator] Despawn object ID={}", e.Id.ToString());
|
|
||||||
NetworkMessageObjectDespawn msgData;
|
|
||||||
Guid objectId = e.Id;
|
|
||||||
{
|
|
||||||
// Remap local client object ids into server ids
|
|
||||||
IdsRemappingTable.KeyOf(objectId, &objectId);
|
|
||||||
}
|
|
||||||
NetworkMessage msg = peer->BeginSendMessage();
|
|
||||||
msg.WriteStructure(msgData);
|
|
||||||
msg.WriteNetworkId(objectId);
|
|
||||||
BuildCachedTargets(NetworkManager::Clients, e.Targets);
|
|
||||||
if (isClient)
|
|
||||||
peer->EndSendMessage(NetworkChannelType::ReliableOrdered, msg);
|
|
||||||
else
|
|
||||||
peer->EndSendMessage(NetworkChannelType::ReliableOrdered, msg, CachedTargets);
|
|
||||||
}
|
}
|
||||||
DespawnQueue.Clear();
|
DespawnQueue.Clear();
|
||||||
}
|
}
|
||||||
@@ -1830,6 +2115,7 @@ void NetworkInternal::NetworkReplicatorUpdate()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: remove items from RpcParts after some TTL to reduce memory usage
|
||||||
// TODO: remove items from SpawnParts after some TTL to reduce memory usage
|
// TODO: remove items from SpawnParts after some TTL to reduce memory usage
|
||||||
|
|
||||||
// Replicate all owned networked objects with other clients or server
|
// Replicate all owned networked objects with other clients or server
|
||||||
@@ -1871,136 +2157,11 @@ void NetworkInternal::NetworkReplicatorUpdate()
|
|||||||
PROFILE_CPU_NAMED("Replication");
|
PROFILE_CPU_NAMED("Replication");
|
||||||
if (CachedWriteStream == nullptr)
|
if (CachedWriteStream == nullptr)
|
||||||
CachedWriteStream = New<NetworkStream>();
|
CachedWriteStream = New<NetworkStream>();
|
||||||
NetworkStream* stream = CachedWriteStream;
|
CachedWriteStream->SenderId = NetworkManager::LocalClientId;
|
||||||
stream->SenderId = NetworkManager::LocalClientId;
|
|
||||||
// TODO: use Job System when replicated objects count is large
|
// TODO: use Job System when replicated objects count is large
|
||||||
for (auto& e : CachedReplicationResult->_entries)
|
for (auto& e : CachedReplicationResult->_entries)
|
||||||
{
|
{
|
||||||
ScriptingObject* obj = e.Object;
|
SendReplication(e.Object, e.TargetClients);
|
||||||
auto it = Objects.Find(obj->GetID());
|
|
||||||
if (it.IsEnd())
|
|
||||||
continue;
|
|
||||||
auto& item = it->Item;
|
|
||||||
|
|
||||||
// Skip serialization of objects that none will receive
|
|
||||||
if (!isClient)
|
|
||||||
{
|
|
||||||
BuildCachedTargets(item, e.TargetClients);
|
|
||||||
if (CachedTargets.Count() == 0)
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item.AsNetworkObject)
|
|
||||||
item.AsNetworkObject->OnNetworkSerialize();
|
|
||||||
|
|
||||||
// Serialize object
|
|
||||||
stream->Initialize();
|
|
||||||
const bool failed = NetworkReplicator::InvokeSerializer(obj->GetTypeHandle(), obj, stream, true);
|
|
||||||
if (failed)
|
|
||||||
{
|
|
||||||
//NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Cannot serialize object {} of type {} (missing serialization logic)", item.ToString(), obj->GetType().ToString());
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const uint32 size = stream->GetPosition();
|
|
||||||
if (size > MAX_uint16)
|
|
||||||
{
|
|
||||||
LOG(Error, "Too much data for object {} replication ({} bytes provided while limit is {}).", item.ToString(), size, MAX_uint16);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
#if USE_NETWORK_REPLICATOR_CACHE
|
|
||||||
// Process replication cache to skip sending object data if it didn't change
|
|
||||||
if (item.RepCache.Data.Length() == size &&
|
|
||||||
item.RepCache.Mask == e.TargetClients &&
|
|
||||||
Platform::MemoryCompare(item.RepCache.Data.Get(), stream->GetBuffer(), size) == 0)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
item.RepCache.Mask = e.TargetClients;
|
|
||||||
item.RepCache.Data.Copy(stream->GetBuffer(), size);
|
|
||||||
#endif
|
|
||||||
// TODO: use Unreliable for dynamic objects that are replicated every frame? (eg. player state)
|
|
||||||
constexpr NetworkChannelType repChannel = NetworkChannelType::Reliable;
|
|
||||||
|
|
||||||
// Send object to clients
|
|
||||||
NetworkMessageObjectReplicate msgData;
|
|
||||||
msgData.OwnerFrame = NetworkManager::Frame;
|
|
||||||
Guid objectId = item.ObjectId, parentId = item.ParentId;
|
|
||||||
{
|
|
||||||
// Remap local client object ids into server ids
|
|
||||||
IdsRemappingTable.KeyOf(objectId, &objectId);
|
|
||||||
IdsRemappingTable.KeyOf(parentId, &parentId);
|
|
||||||
}
|
|
||||||
NetworkMessage msg = peer->BeginSendMessage();
|
|
||||||
msg.WriteStructure(msgData);
|
|
||||||
msg.WriteNetworkId(objectId);
|
|
||||||
msg.WriteNetworkId(parentId);
|
|
||||||
msg.WriteNetworkName(obj->GetType().Fullname);
|
|
||||||
NetworkMessageObjectReplicatePayload msgDataPayload;
|
|
||||||
msgDataPayload.DataSize = size;
|
|
||||||
const uint32 networkKeyIdWorstCaseSize = sizeof(uint32) + sizeof(Guid);
|
|
||||||
const uint32 msgMaxData = peer->Config.MessageSize - msg.Position - sizeof(NetworkMessageObjectReplicatePayload);
|
|
||||||
const uint32 partMaxData = peer->Config.MessageSize - sizeof(NetworkMessageObjectReplicatePart) - networkKeyIdWorstCaseSize;
|
|
||||||
uint32 partsCount = 1;
|
|
||||||
uint32 dataStart = 0;
|
|
||||||
uint32 msgDataSize = size;
|
|
||||||
if (size > msgMaxData)
|
|
||||||
{
|
|
||||||
// Send msgMaxData within first message
|
|
||||||
msgDataSize = msgMaxData;
|
|
||||||
dataStart += msgMaxData;
|
|
||||||
|
|
||||||
// Send rest of the data in separate parts
|
|
||||||
partsCount += Math::DivideAndRoundUp(size - dataStart, partMaxData);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
dataStart += size;
|
|
||||||
ASSERT(partsCount <= MAX_uint8);
|
|
||||||
msgDataPayload.PartsCount = partsCount;
|
|
||||||
msgDataPayload.PartSize = msgDataSize;
|
|
||||||
msg.WriteStructure(msgDataPayload);
|
|
||||||
msg.WriteBytes(stream->GetBuffer(), msgDataSize);
|
|
||||||
uint32 dataSize = msgDataSize, messageSize = msg.Length;
|
|
||||||
if (isClient)
|
|
||||||
peer->EndSendMessage(repChannel, msg);
|
|
||||||
else
|
|
||||||
peer->EndSendMessage(repChannel, msg, CachedTargets);
|
|
||||||
|
|
||||||
// Send all other parts
|
|
||||||
for (uint32 partIndex = 1; partIndex < partsCount; partIndex++)
|
|
||||||
{
|
|
||||||
NetworkMessageObjectReplicatePart msgDataPart;
|
|
||||||
msgDataPart.OwnerFrame = msgData.OwnerFrame;
|
|
||||||
msgDataPart.DataSize = msgDataPayload.DataSize;
|
|
||||||
msgDataPart.PartsCount = msgDataPayload.PartsCount;
|
|
||||||
msgDataPart.PartStart = dataStart;
|
|
||||||
msgDataPart.PartSize = Math::Min(size - dataStart, partMaxData);
|
|
||||||
msg = peer->BeginSendMessage();
|
|
||||||
msg.WriteStructure(msgDataPart);
|
|
||||||
msg.WriteNetworkId(objectId);
|
|
||||||
msg.WriteBytes(stream->GetBuffer() + msgDataPart.PartStart, msgDataPart.PartSize);
|
|
||||||
messageSize += msg.Length;
|
|
||||||
dataSize += msgDataPart.PartSize;
|
|
||||||
dataStart += msgDataPart.PartSize;
|
|
||||||
if (isClient)
|
|
||||||
peer->EndSendMessage(repChannel, msg);
|
|
||||||
else
|
|
||||||
peer->EndSendMessage(repChannel, msg, CachedTargets);
|
|
||||||
}
|
|
||||||
ASSERT_LOW_LAYER(dataStart == size);
|
|
||||||
|
|
||||||
#if COMPILE_WITH_PROFILER
|
|
||||||
// Network stats recording
|
|
||||||
if (EnableProfiling)
|
|
||||||
{
|
|
||||||
const Pair<ScriptingTypeHandle, StringAnsiView> name(obj->GetTypeHandle(), StringAnsiView::Empty);
|
|
||||||
auto& profileEvent = ProfilerEvents[name];
|
|
||||||
profileEvent.Count++;
|
|
||||||
profileEvent.DataSize += dataSize;
|
|
||||||
profileEvent.MessageSize += messageSize;
|
|
||||||
profileEvent.Receivers += isClient ? 1 : CachedTargets.Count();
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2009,70 +2170,7 @@ void NetworkInternal::NetworkReplicatorUpdate()
|
|||||||
PROFILE_CPU_NAMED("Rpc");
|
PROFILE_CPU_NAMED("Rpc");
|
||||||
for (auto& e : RpcQueue)
|
for (auto& e : RpcQueue)
|
||||||
{
|
{
|
||||||
ScriptingObject* obj = e.Object.Get();
|
SendRpc(e);
|
||||||
if (!obj)
|
|
||||||
continue;
|
|
||||||
auto it = Objects.Find(obj->GetID());
|
|
||||||
if (it == Objects.End())
|
|
||||||
{
|
|
||||||
#if USE_EDITOR || !BUILD_RELEASE
|
|
||||||
if (!DespawnedObjects.Contains(obj->GetID()))
|
|
||||||
LOG(Error, "Cannot invoke RPC method '{0}.{1}' on object '{2}' that is not registered in networking (use 'NetworkReplicator.AddObject').", e.Name.First.ToString(), String(e.Name.Second), obj->GetID());
|
|
||||||
#endif
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
auto& item = it->Item;
|
|
||||||
|
|
||||||
// Send RPC message
|
|
||||||
//NETWORK_REPLICATOR_LOG(Info, "[NetworkReplicator] Rpc {}::{} object ID={}", e.Name.First.ToString(), String(e.Name.Second), item.ToString());
|
|
||||||
NetworkMessageObjectRpc msgData;
|
|
||||||
Guid msgObjectId = item.ObjectId;
|
|
||||||
Guid msgParentId = item.ParentId;
|
|
||||||
{
|
|
||||||
// Remap local client object ids into server ids
|
|
||||||
IdsRemappingTable.KeyOf(msgObjectId, &msgObjectId);
|
|
||||||
IdsRemappingTable.KeyOf(msgParentId, &msgParentId);
|
|
||||||
}
|
|
||||||
msgData.ArgsSize = (uint16)e.ArgsData.Length();
|
|
||||||
NetworkMessage msg = peer->BeginSendMessage();
|
|
||||||
msg.WriteStructure(msgData);
|
|
||||||
msg.WriteNetworkId(msgObjectId);
|
|
||||||
msg.WriteNetworkId(msgParentId);
|
|
||||||
msg.WriteNetworkName(obj->GetType().Fullname);
|
|
||||||
msg.WriteNetworkName(e.Name.First.GetType().Fullname);
|
|
||||||
msg.WriteNetworkName(e.Name.Second);
|
|
||||||
msg.WriteBytes(e.ArgsData.Get(), e.ArgsData.Length());
|
|
||||||
uint32 dataSize = e.ArgsData.Length(), messageSize = msg.Length, receivers = 0;
|
|
||||||
NetworkChannelType channel = (NetworkChannelType)e.Info.Channel;
|
|
||||||
if (e.Info.Server && isClient)
|
|
||||||
{
|
|
||||||
// Client -> Server
|
|
||||||
#if USE_NETWORK_REPLICATOR_LOG
|
|
||||||
if (e.Targets.Length() != 0)
|
|
||||||
NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Server RPC '{}::{}' called with non-empty list of targets is not supported (only server will receive it)", e.Name.First.ToString(), e.Name.Second.ToString());
|
|
||||||
#endif
|
|
||||||
peer->EndSendMessage(channel, msg);
|
|
||||||
receivers = 1;
|
|
||||||
}
|
|
||||||
else if (e.Info.Client && (isServer || isHost))
|
|
||||||
{
|
|
||||||
// Server -> Client(s)
|
|
||||||
BuildCachedTargets(NetworkManager::Clients, item.TargetClientIds, e.Targets, NetworkManager::LocalClientId);
|
|
||||||
peer->EndSendMessage(channel, msg, CachedTargets);
|
|
||||||
receivers = CachedTargets.Count();
|
|
||||||
}
|
|
||||||
|
|
||||||
#if COMPILE_WITH_PROFILER
|
|
||||||
// Network stats recording
|
|
||||||
if (EnableProfiling && receivers)
|
|
||||||
{
|
|
||||||
auto& profileEvent = ProfilerEvents[e.Name];
|
|
||||||
profileEvent.Count++;
|
|
||||||
profileEvent.DataSize += dataSize;
|
|
||||||
profileEvent.MessageSize += messageSize;
|
|
||||||
profileEvent.Receivers += receivers;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
RpcQueue.Clear();
|
RpcQueue.Clear();
|
||||||
}
|
}
|
||||||
@@ -2085,7 +2183,7 @@ void NetworkInternal::OnNetworkMessageObjectReplicate(NetworkEvent& event, Netwo
|
|||||||
{
|
{
|
||||||
PROFILE_CPU();
|
PROFILE_CPU();
|
||||||
NetworkMessageObjectReplicate msgData;
|
NetworkMessageObjectReplicate msgData;
|
||||||
NetworkMessageObjectReplicatePayload msgDataPayload;
|
NetworkMessageObjectPartPayload msgDataPayload;
|
||||||
Guid objectId, parentId;
|
Guid objectId, parentId;
|
||||||
StringAnsiView objectTypeName;
|
StringAnsiView objectTypeName;
|
||||||
event.Message.ReadStructure(msgData);
|
event.Message.ReadStructure(msgData);
|
||||||
@@ -2095,7 +2193,7 @@ void NetworkInternal::OnNetworkMessageObjectReplicate(NetworkEvent& event, Netwo
|
|||||||
event.Message.ReadStructure(msgDataPayload);
|
event.Message.ReadStructure(msgDataPayload);
|
||||||
ScopeLock lock(ObjectsLock);
|
ScopeLock lock(ObjectsLock);
|
||||||
if (DespawnedObjects.Contains(objectId))
|
if (DespawnedObjects.Contains(objectId))
|
||||||
return; // Skip replicating not-existing objects
|
return; // Skip replicating non-existing objects
|
||||||
NetworkReplicatedObject* e = ResolveObject(objectId, parentId, objectTypeName);
|
NetworkReplicatedObject* e = ResolveObject(objectId, parentId, objectTypeName);
|
||||||
if (!e)
|
if (!e)
|
||||||
return;
|
return;
|
||||||
@@ -2114,7 +2212,7 @@ void NetworkInternal::OnNetworkMessageObjectReplicate(NetworkEvent& event, Netwo
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Add to replication from multiple parts
|
// Add to replication from multiple parts
|
||||||
ReplicateItem* replicateItem = AddObjectReplicateItem(event, msgData.OwnerFrame, msgDataPayload.PartsCount, msgDataPayload.DataSize, objectId, 0, msgDataPayload.PartSize, senderClientId);
|
PartsItem* replicateItem = AddObjectReplicateItem(event, msgData.OwnerFrame, msgDataPayload.PartsCount, msgDataPayload.DataSize, objectId, 0, msgDataPayload.PartSize, senderClientId);
|
||||||
replicateItem->Object = e->Object;
|
replicateItem->Object = e->Object;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2122,13 +2220,13 @@ void NetworkInternal::OnNetworkMessageObjectReplicate(NetworkEvent& event, Netwo
|
|||||||
void NetworkInternal::OnNetworkMessageObjectReplicatePart(NetworkEvent& event, NetworkClient* client, NetworkPeer* peer)
|
void NetworkInternal::OnNetworkMessageObjectReplicatePart(NetworkEvent& event, NetworkClient* client, NetworkPeer* peer)
|
||||||
{
|
{
|
||||||
PROFILE_CPU();
|
PROFILE_CPU();
|
||||||
NetworkMessageObjectReplicatePart msgData;
|
NetworkMessageObjectPart msgData;
|
||||||
Guid objectId;
|
Guid objectId;
|
||||||
event.Message.ReadStructure(msgData);
|
event.Message.ReadStructure(msgData);
|
||||||
event.Message.ReadNetworkId(objectId);
|
event.Message.ReadNetworkId(objectId);
|
||||||
ScopeLock lock(ObjectsLock);
|
ScopeLock lock(ObjectsLock);
|
||||||
if (DespawnedObjects.Contains(objectId))
|
if (DespawnedObjects.Contains(objectId))
|
||||||
return; // Skip replicating not-existing objects
|
return; // Skip replicating non-existing objects
|
||||||
|
|
||||||
const uint32 senderClientId = client ? client->ClientId : NetworkManager::ServerClientId;
|
const uint32 senderClientId = client ? client->ClientId : NetworkManager::ServerClientId;
|
||||||
AddObjectReplicateItem(event, msgData.OwnerFrame, msgData.PartsCount, msgData.DataSize, objectId, msgData.PartStart, msgData.PartSize, senderClientId);
|
AddObjectReplicateItem(event, msgData.OwnerFrame, msgData.PartsCount, msgData.DataSize, objectId, msgData.PartStart, msgData.PartSize, senderClientId);
|
||||||
@@ -2230,7 +2328,9 @@ void NetworkInternal::OnNetworkMessageObjectDespawn(NetworkEvent& event, Network
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to despawn object {}", objectId);
|
// If this client never had the object (eg. it was targeted to other clients only), drop the message quietly
|
||||||
|
DespawnedObjects.Add(objectId);
|
||||||
|
NETWORK_REPLICATOR_LOG(Warning, "[NetworkReplicator] Failed to despawn object {}", objectId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2288,14 +2388,16 @@ void NetworkInternal::OnNetworkMessageObjectRpc(NetworkEvent& event, NetworkClie
|
|||||||
{
|
{
|
||||||
PROFILE_CPU();
|
PROFILE_CPU();
|
||||||
NetworkMessageObjectRpc msgData;
|
NetworkMessageObjectRpc msgData;
|
||||||
Guid msgObjectId, msgParentId;
|
NetworkMessageObjectPartPayload msgDataPayload;
|
||||||
|
Guid objectId, parentId;
|
||||||
StringAnsiView objectTypeName, rpcTypeName, rpcName;
|
StringAnsiView objectTypeName, rpcTypeName, rpcName;
|
||||||
event.Message.ReadStructure(msgData);
|
event.Message.ReadStructure(msgData);
|
||||||
event.Message.ReadNetworkId(msgObjectId);
|
event.Message.ReadNetworkId(objectId);
|
||||||
event.Message.ReadNetworkId(msgParentId);
|
event.Message.ReadNetworkId(parentId);
|
||||||
event.Message.ReadNetworkName(objectTypeName);
|
event.Message.ReadNetworkName(objectTypeName);
|
||||||
event.Message.ReadNetworkName(rpcTypeName);
|
event.Message.ReadNetworkName(rpcTypeName);
|
||||||
event.Message.ReadNetworkName(rpcName);
|
event.Message.ReadNetworkName(rpcName);
|
||||||
|
event.Message.ReadStructure(msgDataPayload);
|
||||||
ScopeLock lock(ObjectsLock);
|
ScopeLock lock(ObjectsLock);
|
||||||
|
|
||||||
// Find RPC info
|
// Find RPC info
|
||||||
@@ -2305,11 +2407,11 @@ void NetworkInternal::OnNetworkMessageObjectRpc(NetworkEvent& event, NetworkClie
|
|||||||
const NetworkRpcInfo* info = NetworkRpcInfo::RPCsTable.TryGet(name);
|
const NetworkRpcInfo* info = NetworkRpcInfo::RPCsTable.TryGet(name);
|
||||||
if (!info)
|
if (!info)
|
||||||
{
|
{
|
||||||
NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Unknown RPC {}::{} for object {}", String(rpcTypeName), String(rpcName), msgObjectId);
|
NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Unknown RPC {}::{} for object {}", String(rpcTypeName), String(rpcName), objectId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
NetworkReplicatedObject* e = ResolveObject(msgObjectId, msgParentId, objectTypeName);
|
NetworkReplicatedObject* e = ResolveObject(objectId, parentId, objectTypeName);
|
||||||
if (e)
|
if (e)
|
||||||
{
|
{
|
||||||
auto& item = *e;
|
auto& item = *e;
|
||||||
@@ -2329,18 +2431,50 @@ void NetworkInternal::OnNetworkMessageObjectRpc(NetworkEvent& event, NetworkClie
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup message reading stream
|
const uint32 senderClientId = client ? client->ClientId : NetworkManager::ServerClientId;
|
||||||
if (CachedReadStream == nullptr)
|
if (msgDataPayload.PartsCount == 1)
|
||||||
CachedReadStream = New<NetworkStream>();
|
{
|
||||||
NetworkStream* stream = CachedReadStream;
|
// Call RPC
|
||||||
stream->SenderId = client ? client->ClientId : NetworkManager::ServerClientId;
|
InvokeObjectRpc(info, event.Message.Buffer + event.Message.Position, msgDataPayload.DataSize, senderClientId, obj);
|
||||||
stream->Initialize(event.Message.Buffer + event.Message.Position, msgData.ArgsSize);
|
}
|
||||||
|
else
|
||||||
// Execute RPC
|
{
|
||||||
info->Execute(obj, stream, info->Tag);
|
// Add to RPC from multiple parts
|
||||||
|
PartsItem* rpcItem = AddObjectRpcItem(event, msgData.OwnerFrame, msgDataPayload.PartsCount, msgDataPayload.DataSize, objectId, 0, msgDataPayload.PartSize, senderClientId);
|
||||||
|
rpcItem->Object = e->Object;
|
||||||
|
rpcItem->Tag = info;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (info->Channel != static_cast<uint8>(NetworkChannelType::Unreliable) && info->Channel != static_cast<uint8>(NetworkChannelType::UnreliableOrdered))
|
else if (info->Channel != static_cast<uint8>(NetworkChannelType::Unreliable) && info->Channel != static_cast<uint8>(NetworkChannelType::UnreliableOrdered))
|
||||||
{
|
{
|
||||||
NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Unknown object {} RPC {}::{}", msgObjectId, String(rpcTypeName), String(rpcName));
|
NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Unknown object {} RPC {}::{}", objectId, String(rpcTypeName), String(rpcName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetworkInternal::OnNetworkMessageObjectRpcPart(NetworkEvent& event, NetworkClient* client, NetworkPeer* peer)
|
||||||
|
{
|
||||||
|
PROFILE_CPU();
|
||||||
|
NetworkMessageObjectPart msgData;
|
||||||
|
Guid objectId;
|
||||||
|
event.Message.ReadStructure(msgData);
|
||||||
|
event.Message.ReadNetworkId(objectId);
|
||||||
|
ScopeLock lock(ObjectsLock);
|
||||||
|
if (DespawnedObjects.Contains(objectId))
|
||||||
|
return; // Skip replicating non-existing objects
|
||||||
|
|
||||||
|
const uint32 senderClientId = client ? client->ClientId : NetworkManager::ServerClientId;
|
||||||
|
PartsItem* rpcItem = AddObjectRpcItem(event, msgData.OwnerFrame, msgData.PartsCount, msgData.DataSize, objectId, msgData.PartStart, msgData.PartSize, senderClientId);
|
||||||
|
if (rpcItem && rpcItem->PartsLeft == 0)
|
||||||
|
{
|
||||||
|
// Got all parts so invoke RPC
|
||||||
|
ScriptingObject* obj = rpcItem->Object.Get();
|
||||||
|
if (obj)
|
||||||
|
{
|
||||||
|
InvokeObjectRpc((const NetworkRpcInfo*)rpcItem->Tag, rpcItem->Data.Get(), rpcItem->Data.Count(), rpcItem->OwnerClientId, obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove item
|
||||||
|
int32 partIndex = (int32)((RpcParts.Get() - rpcItem) / sizeof(rpcItem));
|
||||||
|
RpcParts.RemoveAt(partIndex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -482,9 +482,15 @@ void ParticleEmitterGraphCPUExecutor::ProcessGroupFunction(Box* box, Node* node,
|
|||||||
// Function Input
|
// Function Input
|
||||||
case 1:
|
case 1:
|
||||||
{
|
{
|
||||||
|
// Skip when graph is too small (eg. preview) and fallback with default value from the function graph
|
||||||
|
if (context.GraphStack.Count() < 2)
|
||||||
|
{
|
||||||
|
value = tryGetValue(node->TryGetBox(1), Value::Zero);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// Find the function call
|
// Find the function call
|
||||||
Node* functionCallNode = nullptr;
|
Node* functionCallNode = nullptr;
|
||||||
ASSERT(context.GraphStack.Count() >= 2);
|
|
||||||
ParticleEmitterGraphCPU* graph;
|
ParticleEmitterGraphCPU* graph;
|
||||||
for (int32 i = context.CallStackSize - 1; i >= 0; i--)
|
for (int32 i = context.CallStackSize - 1; i >= 0; i--)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -425,8 +425,8 @@ void ParticleEmitterGPUGenerator::ProcessGroupParticles(Box* box, Node* node, Va
|
|||||||
case 300:
|
case 300:
|
||||||
{
|
{
|
||||||
// Load function asset
|
// Load function asset
|
||||||
const auto function = Assets.LoadAsync<ParticleEmitterFunction>((Guid)node->Values[0]);
|
const auto function = Assets.Load<ParticleEmitterFunction>((Guid)node->Values[0]);
|
||||||
if (!function || function->WaitForLoaded())
|
if (!function)
|
||||||
{
|
{
|
||||||
OnError(node, box, TEXT("Missing or invalid function."));
|
OnError(node, box, TEXT("Missing or invalid function."));
|
||||||
value = Value::Zero;
|
value = Value::Zero;
|
||||||
@@ -439,7 +439,7 @@ void ParticleEmitterGPUGenerator::ProcessGroupParticles(Box* box, Node* node, Va
|
|||||||
{
|
{
|
||||||
if (_callStack[i]->Type == GRAPH_NODE_MAKE_TYPE(14, 300))
|
if (_callStack[i]->Type == GRAPH_NODE_MAKE_TYPE(14, 300))
|
||||||
{
|
{
|
||||||
const auto callFunc = Assets.LoadAsync<ParticleEmitterFunction>((Guid)_callStack[i]->Values[0]);
|
const auto callFunc = Assets.Load<ParticleEmitterFunction>((Guid)_callStack[i]->Values[0]);
|
||||||
if (callFunc == function)
|
if (callFunc == function)
|
||||||
{
|
{
|
||||||
OnError(node, box, String::Format(TEXT("Recursive call to function '{0}'!"), function->ToString()));
|
OnError(node, box, String::Format(TEXT("Recursive call to function '{0}'!"), function->ToString()));
|
||||||
@@ -514,7 +514,7 @@ void ParticleEmitterGPUGenerator::ProcessGroupFunction(Box* box, Node* node, Val
|
|||||||
value = Value::Zero;
|
value = Value::Zero;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
const auto function = Assets.LoadAsync<ParticleEmitterFunction>((Guid)functionCallNode->Values[0]);
|
const auto function = Assets.Load<ParticleEmitterFunction>((Guid)functionCallNode->Values[0]);
|
||||||
if (!_functions.TryGet(functionCallNode, graph) || !function)
|
if (!_functions.TryGet(functionCallNode, graph) || !function)
|
||||||
{
|
{
|
||||||
OnError(node, box, TEXT("Missing calling function graph."));
|
OnError(node, box, TEXT("Missing calling function graph."));
|
||||||
|
|||||||
@@ -156,7 +156,7 @@ void ParticleSystemInstance::Sync(ParticleSystem* system)
|
|||||||
if (GPUParticlesCountReadback)
|
if (GPUParticlesCountReadback)
|
||||||
GPUParticlesCountReadback->ReleaseGPU();
|
GPUParticlesCountReadback->ReleaseGPU();
|
||||||
}
|
}
|
||||||
ASSERT(Emitters.Count() == system->Emitters.Count());
|
CHECK(Emitters.Count() == system->Emitters.Count());
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ParticleSystemInstance::ContainsEmitter(ParticleEmitter* emitter) const
|
bool ParticleSystemInstance::ContainsEmitter(ParticleEmitter* emitter) const
|
||||||
|
|||||||
@@ -194,7 +194,6 @@ void Collider::UpdateLayerBits()
|
|||||||
// Own layer mask
|
// Own layer mask
|
||||||
const uint32 mask1 = Physics::LayerMasks[GetLayer()];
|
const uint32 mask1 = Physics::LayerMasks[GetLayer()];
|
||||||
|
|
||||||
ASSERT(_shape);
|
|
||||||
PhysicsBackend::SetShapeFilterMask(_shape, mask0, mask1);
|
PhysicsBackend::SetShapeFilterMask(_shape, mask0, mask1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#if COMPILE_WITH_EMPTY_PHYSICS
|
#if COMPILE_WITH_EMPTY_PHYSICS
|
||||||
|
|
||||||
#include "Engine/Core/Log.h"
|
#include "Engine/Core/Log.h"
|
||||||
|
#include "Engine/Physics/PhysicsBackend.h"
|
||||||
#include "Engine/Physics/CollisionData.h"
|
#include "Engine/Physics/CollisionData.h"
|
||||||
#include "Engine/Physics/PhysicalMaterial.h"
|
#include "Engine/Physics/PhysicalMaterial.h"
|
||||||
#include "Engine/Physics/PhysicsScene.h"
|
#include "Engine/Physics/PhysicsScene.h"
|
||||||
@@ -226,26 +227,6 @@ bool PhysicsBackend::CheckConvex(void* scene, const Vector3& center, const Colli
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PhysicsBackend::OverlapBox(void* scene, const Vector3& center, const Vector3& halfExtents, Array<Collider*>& results, const Quaternion& rotation, uint32 layerMask, bool hitTriggers)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PhysicsBackend::OverlapSphere(void* scene, const Vector3& center, const float radius, Array<Collider*>& results, uint32 layerMask, bool hitTriggers)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PhysicsBackend::OverlapCapsule(void* scene, const Vector3& center, const float radius, const float height, Array<Collider*>& results, const Quaternion& rotation, uint32 layerMask, bool hitTriggers)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PhysicsBackend::OverlapConvex(void* scene, const Vector3& center, const CollisionData* convexMesh, const Vector3& scale, Array<Collider*>& results, const Quaternion& rotation, uint32 layerMask, bool hitTriggers)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PhysicsBackend::OverlapBox(void* scene, const Vector3& center, const Vector3& halfExtents, Array<PhysicsColliderActor*>& results, const Quaternion& rotation, uint32 layerMask, bool hitTriggers)
|
bool PhysicsBackend::OverlapBox(void* scene, const Vector3& center, const Vector3& halfExtents, Array<PhysicsColliderActor*>& results, const Quaternion& rotation, uint32 layerMask, bool hitTriggers)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
@@ -353,7 +334,7 @@ Vector3 PhysicsBackend::GetRigidDynamicActorCenterOfMass(void* actor)
|
|||||||
return Vector3::Zero;
|
return Vector3::Zero;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PhysicsBackend::SetRigidDynamicActorCenterOfMassOffset(void* actor, const Float3& value)
|
void PhysicsBackend::AddRigidDynamicActorCenterOfMassOffset(void* actor, const Float3& value)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -698,6 +679,15 @@ void PhysicsBackend::SetControllerStepOffset(void* controller, float value)
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Vector3 PhysicsBackend::GetControllerBasePosition(void* controller)
|
||||||
|
{
|
||||||
|
return Vector3::Zero;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PhysicsBackend::SetControllerBasePosition(void* controller, const Vector3& value)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
Vector3 PhysicsBackend::GetControllerUpDirection(void* controller)
|
Vector3 PhysicsBackend::GetControllerUpDirection(void* controller)
|
||||||
{
|
{
|
||||||
return Vector3::Up;
|
return Vector3::Up;
|
||||||
|
|||||||
@@ -422,24 +422,8 @@ bool AndroidFileSystem::getFilesFromDirectoryTop(Array<String>& results, const c
|
|||||||
if (S_ISREG(statEntry.st_mode) != 0)
|
if (S_ISREG(statEntry.st_mode) != 0)
|
||||||
{
|
{
|
||||||
// Validate with filter
|
// Validate with filter
|
||||||
const int32 fullPathLength = StringUtils::Length(fullPath);
|
if (FileSystem::PathFilterHelper(fullPath, searchPattern))
|
||||||
const int32 searchPatternLength = StringUtils::Length(searchPattern);
|
results.Add(String(fullPath));
|
||||||
if (searchPatternLength == 0 || StringUtils::Compare(searchPattern, "*") == 0)
|
|
||||||
{
|
|
||||||
// All files
|
|
||||||
}
|
|
||||||
else if (searchPattern[0] == '*' && searchPatternLength < fullPathLength && StringUtils::Compare(fullPath + fullPathLength - searchPatternLength + 1, searchPattern + 1) == 0)
|
|
||||||
{
|
|
||||||
// Path ending
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// TODO: implement all cases in a generic way
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add file
|
|
||||||
results.Add(String(fullPath));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
#include "Engine/Core/Types/StringView.h"
|
#include "Engine/Core/Types/StringView.h"
|
||||||
#include "Engine/Core/Collections/Array.h"
|
#include "Engine/Core/Collections/Array.h"
|
||||||
#include "Engine/Core/Math/Math.h"
|
#include "Engine/Core/Math/Math.h"
|
||||||
|
#include "Engine/Core/Log.h"
|
||||||
#include "Engine/Engine/Globals.h"
|
#include "Engine/Engine/Globals.h"
|
||||||
|
|
||||||
bool FileSystemBase::ShowOpenFileDialog(Window* parentWindow, const StringView& initialDirectory, const StringView& filter, bool multiSelect, const StringView& title, Array<String, HeapAllocation>& filenames)
|
bool FileSystemBase::ShowOpenFileDialog(Window* parentWindow, const StringView& initialDirectory, const StringView& filter, bool multiSelect, const StringView& title, Array<String, HeapAllocation>& filenames)
|
||||||
@@ -313,3 +314,39 @@ bool FileSystemBase::DirectoryCopyHelper(const String& dst, const String& src, b
|
|||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool FileSystemBase::PathFilterHelper(const char* path, const char* searchPattern)
|
||||||
|
{
|
||||||
|
// Validate with filter
|
||||||
|
const int32 pathLength = StringUtils::Length(path);
|
||||||
|
const int32 searchPatternLength = StringUtils::Length(searchPattern);
|
||||||
|
if (searchPatternLength == 0 ||
|
||||||
|
StringUtils::Compare(searchPattern, "*") == 0 ||
|
||||||
|
StringUtils::Compare(searchPattern, "*.*") == 0)
|
||||||
|
{
|
||||||
|
// All files
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (searchPattern[0] == '*' && StringUtils::Find(searchPattern + 1, "*") == nullptr)
|
||||||
|
{
|
||||||
|
// Path ending
|
||||||
|
return searchPatternLength < pathLength && StringUtils::Compare(path + pathLength - searchPatternLength + 1, searchPattern + 1, searchPatternLength - 1) == 0;
|
||||||
|
}
|
||||||
|
else if (searchPattern[0] == '*' && searchPatternLength > 2 && searchPattern[searchPatternLength - 1] == '*')
|
||||||
|
{
|
||||||
|
// Contains pattern
|
||||||
|
bool match = false;
|
||||||
|
for (int32 i = 0; i < pathLength - searchPatternLength - 1; i++)
|
||||||
|
{
|
||||||
|
int32 len = Math::Min(searchPatternLength - 2, pathLength - i);
|
||||||
|
if (StringUtils::Compare(&path[i], &searchPattern[1], len) == 0)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// TODO: implement all cases in a generic way
|
||||||
|
LOG(Warning, "DirectoryGetFiles: Wildcard filter is not implemented ({})", String(searchPattern));
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|||||||
@@ -284,6 +284,6 @@ public:
|
|||||||
/// <returns>Relative path</returns>
|
/// <returns>Relative path</returns>
|
||||||
static String ConvertAbsolutePathToRelative(const String& basePath, const String& path);
|
static String ConvertAbsolutePathToRelative(const String& basePath, const String& path);
|
||||||
|
|
||||||
private:
|
|
||||||
static bool DirectoryCopyHelper(const String& dst, const String& src, bool withSubDirectories);
|
static bool DirectoryCopyHelper(const String& dst, const String& src, bool withSubDirectories);
|
||||||
|
static bool PathFilterHelper(const char* path, const char* searchPattern);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ Array<User*, FixedAllocation<8>> PlatformBase::Users;
|
|||||||
Delegate<User*> PlatformBase::UserAdded;
|
Delegate<User*> PlatformBase::UserAdded;
|
||||||
Delegate<User*> PlatformBase::UserRemoved;
|
Delegate<User*> PlatformBase::UserRemoved;
|
||||||
void* OutOfMemoryBuffer = nullptr;
|
void* OutOfMemoryBuffer = nullptr;
|
||||||
|
volatile int64 FatalReporting = 0;
|
||||||
|
|
||||||
const Char* ToString(NetworkConnectionType value)
|
const Char* ToString(NetworkConnectionType value)
|
||||||
{
|
{
|
||||||
@@ -306,11 +307,20 @@ int32 PlatformBase::GetCacheLineSize()
|
|||||||
|
|
||||||
void PlatformBase::Fatal(const StringView& msg, void* context, FatalErrorType error)
|
void PlatformBase::Fatal(const StringView& msg, void* context, FatalErrorType error)
|
||||||
{
|
{
|
||||||
|
// Let only one thread to report the error (and wait for it to end to have valid log before crash)
|
||||||
|
RETRY:
|
||||||
|
if (Platform::InterlockedCompareExchange(&FatalReporting, 1, 0) != 0)
|
||||||
|
{
|
||||||
|
Platform::Sleep(1);
|
||||||
|
goto RETRY;
|
||||||
|
}
|
||||||
|
|
||||||
// Check if is already during fatal state
|
// Check if is already during fatal state
|
||||||
if (Engine::FatalError != FatalErrorType::None)
|
if (Engine::FatalError != FatalErrorType::None)
|
||||||
{
|
{
|
||||||
// Just send one more error to the log and back
|
// Just send one more error to the log and back
|
||||||
LOG(Error, "Error after fatal error: {0}", msg);
|
LOG(Error, "Error after fatal error: {0}", msg);
|
||||||
|
Platform::AtomicStore(&FatalReporting, 0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -429,6 +439,8 @@ void PlatformBase::Fatal(const StringView& msg, void* context, FatalErrorType er
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
Platform::AtomicStore(&FatalReporting, 0);
|
||||||
|
|
||||||
// Show error message
|
// Show error message
|
||||||
if (Engine::ReportCrash.IsBinded())
|
if (Engine::ReportCrash.IsBinded())
|
||||||
Engine::ReportCrash(msg, context);
|
Engine::ReportCrash(msg, context);
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user