Merge remote-tracking branch 'origin/master' into 1.12

This commit is contained in:
2025-11-20 10:15:07 +01:00
192 changed files with 3380 additions and 1965 deletions
+1 -1
View File
@@ -31,7 +31,7 @@ body:
- '1.10'
- '1.11'
- master branch
default: 2
default: 3
validations:
required: true
- type: textarea
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1 -1
View File
@@ -4,7 +4,7 @@
"Major": 1,
"Minor": 11,
"Revision": 0,
"Build": 6801
"Build": 6804
},
"Company": "Flax",
"Copyright": "Copyright (c) 2012-2025 Wojciech Figat. All rights reserved.",
+2 -2
View File
@@ -49,7 +49,7 @@ Follow the instructions below to compile and run the engine from source.
* Fedora: `sudo dnf install dotnet-sdk-8.0`
* Arch: `sudo pacman -S dotnet-sdk-8.0 dotnet-runtime-8.0 dotnet-targeting-pack-8.0 dotnet-host`
* Install Vulkan SDK ([https://vulkan.lunarg.com/](https://vulkan.lunarg.com/))
* Ubuntu: `sudo apt install vulkan-sdk`
* Ubuntu: `sudo apt install vulkan-sdk` (deprecated, follow official docs)
* Fedora: `sudo dnf install vulkan-headers vulkan-tools vulkan-validation-layers`
* Arch: `sudo pacman -S vulkan-headers vulkan-tools vulkan-validation-layers`
* Install Git with LFS
@@ -60,7 +60,7 @@ Follow the instructions below to compile and run the engine from source.
* Ubuntu: `sudo apt-get install libx11-dev libxcursor-dev libxinerama-dev zlib1g-dev`
* Fedora: `sudo dnf install libX11-devel libXcursor-devel libXinerama-devel ghc-zlib-devel`
* Arch: `sudo pacman -S base-devel libx11 libxcursor libxinerama zlib`
* Install Clang compiler (version 6 or later):
* Install Clang compiler (version 14 or later):
* Ubuntu: `sudo apt-get install clang lldb lld`
* Fedora: `sudo dnf install clang llvm lldb lld`
* Arch: `sudo pacman -S clang lldb lld`
@@ -130,6 +130,11 @@ namespace FlaxEditor.Content
eyeAdaptation.Mode = EyeAdaptationMode.None;
eyeAdaptation.OverrideFlags |= EyeAdaptationSettingsOverride.Mode;
preview.PostFxVolume.EyeAdaptation = eyeAdaptation;
var antiAliasing = preview.PostFxVolume.AntiAliasing;
antiAliasing.Mode = AntialiasingMode.FastApproximateAntialiasing;
antiAliasing.OverrideFlags |= AntiAliasingSettingsOverride.Mode;
preview.PostFxVolume.AntiAliasing = antiAliasing;
}
}
}
@@ -15,26 +15,32 @@
#include "Editor/ProjectInfo.h"
#include "Editor/Utilities/EditorUtilities.h"
GDKPlatformTools::GDKPlatformTools()
String GetGDK()
{
// Find GDK
Platform::GetEnvironmentVariable(TEXT("GameDKLatest"), _gdkPath);
if (_gdkPath.IsEmpty() || !FileSystem::DirectoryExists(_gdkPath))
String gdk;
Platform::GetEnvironmentVariable(TEXT("GameDKLatest"), gdk);
if (gdk.IsEmpty() || !FileSystem::DirectoryExists(gdk))
{
_gdkPath.Clear();
Platform::GetEnvironmentVariable(TEXT("GRDKLatest"), _gdkPath);
if (_gdkPath.IsEmpty() || !FileSystem::DirectoryExists(_gdkPath))
gdk.Clear();
Platform::GetEnvironmentVariable(TEXT("GRDKLatest"), gdk);
if (gdk.IsEmpty() || !FileSystem::DirectoryExists(gdk))
{
_gdkPath.Clear();
gdk.Clear();
}
else
{
if (_gdkPath.EndsWith(TEXT("GRDK\\")))
_gdkPath.Remove(_gdkPath.Length() - 6);
else if (_gdkPath.EndsWith(TEXT("GRDK")))
_gdkPath.Remove(_gdkPath.Length() - 5);
if (gdk.EndsWith(TEXT("GRDK\\")))
gdk.Remove(gdk.Length() - 6);
else if (gdk.EndsWith(TEXT("GRDK")))
gdk.Remove(gdk.Length() - 5);
}
}
return gdk;
}
GDKPlatformTools::GDKPlatformTools()
{
_gdkPath = GetGDK();
}
DotNetAOTModes GDKPlatformTools::UseAOT() const
@@ -121,7 +127,7 @@ bool GDKPlatformTools::OnPostProcess(CookingData& data, GDKPlatformSettings* pla
validName.Add('\0');
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"),
validName.Get(),
platformSettings->PublisherName.HasChars() ? platformSettings->PublisherName : TEXT("CN=") + gameSettings->CompanyName,
@@ -526,6 +526,7 @@ bool ProcessShaderBase(CookAssetsStep::AssetCookData& data, ShaderAssetBase* ass
#if PLATFORM_TOOLS_XBOX_SCARLETT
case BuildPlatform::XboxScarlett:
{
options.Platform = PlatformType::XboxScarlett;
const char* platformDefineName = "PLATFORM_XBOX_SCARLETT";
COMPILE_PROFILE(DirectX_SM6, SHADER_FILE_CHUNK_INTERNAL_D3D_SM6_CACHE);
break;
@@ -265,7 +265,7 @@ bool DeployDataStep::Perform(CookingData& data)
}
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;
}
}
@@ -1,6 +1,7 @@
// Copyright (c) Wojciech Figat. All rights reserved.
using FlaxEngine;
using FlaxEngine.GUI;
namespace FlaxEditor.CustomEditors.Dedicated
{
@@ -11,7 +12,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
[CustomEditor(typeof(EnvironmentProbe)), DefaultEditor]
public class EnvironmentProbeEditor : ActorEditor
{
private FlaxEngine.GUI.Button _bake;
private Button _bake;
/// <inheritdoc />
public override void Initialize(LayoutElementsContainer layout)
@@ -20,8 +21,9 @@ namespace FlaxEditor.CustomEditors.Dedicated
if (Values.HasDifferentTypes == false)
{
layout.Space(10);
_bake = layout.Button("Bake").Button;
var group = layout.Group("Bake");
group.Panel.ItemsMargin = new Margin(Utilities.Constants.UIMargin * 2);
_bake = group.Button("Bake").Button;
_bake.Clicked += BakeButtonClicked;
}
}
@@ -914,9 +914,11 @@ namespace FlaxEditor.CustomEditors.Dedicated
// Remove drop down arrows and containment lines if no objects in the group
if (group.Children.Count == 0)
{
group.Panel.Close();
group.Panel.ArrowImageOpened = null;
group.Panel.ArrowImageClosed = null;
group.Panel.EnableContainmentLines = false;
group.Panel.CanOpenClose = false;
}
// Scripts arrange bar
@@ -1,6 +1,7 @@
// Copyright (c) Wojciech Figat. All rights reserved.
using FlaxEngine;
using FlaxEngine.GUI;
namespace FlaxEditor.CustomEditors.Dedicated
{
@@ -19,8 +20,9 @@ namespace FlaxEditor.CustomEditors.Dedicated
if (Values.HasDifferentTypes == false)
{
// Add 'Bake' button
layout.Space(10);
var button = layout.Button("Bake");
var group = layout.Group("Bake");
group.Panel.ItemsMargin = new Margin(Utilities.Constants.UIMargin * 2);
var button = group.Button("Bake");
button.Button.Clicked += BakeButtonClicked;
}
}
@@ -123,6 +123,8 @@ namespace FlaxEditor.CustomEditors.Editors
{
base.Refresh();
if (Picker == null)
return;
var differentValues = HasDifferentValues;
Picker.DifferentValues = differentValues;
if (!differentValues)
@@ -71,7 +71,7 @@ namespace FlaxEditor.CustomEditors.Editors
menu.AddButton("Copy", linkedEditor.Copy);
var b = menu.AddButton("Duplicate", () => Editor.Duplicate(Index));
b.Enabled = linkedEditor.CanPaste && !Editor._readOnly && Editor._canResize;
b.Enabled = !Editor._readOnly && Editor._canResize;
b = menu.AddButton("Paste", linkedEditor.Paste);
b.Enabled = linkedEditor.CanPaste && !Editor._readOnly;
@@ -407,7 +407,7 @@ namespace FlaxEditor.CustomEditors.Editors
menu.AddButton("Copy", linkedEditor.Copy);
var b = menu.AddButton("Duplicate", () => Editor.Duplicate(Index));
b.Enabled = linkedEditor.CanPaste && !Editor._readOnly && Editor._canResize;
b.Enabled = !Editor._readOnly && Editor._canResize;
var paste = menu.AddButton("Paste", linkedEditor.Paste);
paste.Enabled = linkedEditor.CanPaste && !Editor._readOnly;
@@ -650,7 +650,7 @@ namespace FlaxEditor.CustomEditors.Editors
panel.Panel.Size = new Float2(0, 18);
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.Enabled = size > _minCount;
removeButton.Button.AnchorPreset = AnchorPresets.TopRight;
@@ -661,7 +661,7 @@ namespace FlaxEditor.CustomEditors.Editors
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.Enabled = (!NotNullItems || size > 0) && size < _maxCount;
addButton.Button.AnchorPreset = AnchorPresets.TopRight;
@@ -104,7 +104,7 @@ namespace FlaxEditor.CustomEditors.Editors
public event Action<TypePickerControl> TypePickerValueChanged;
/// <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>
public Func<ScriptType, bool> CheckValid;
@@ -353,7 +353,13 @@ namespace FlaxEditor.CustomEditors.Editors
}
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);
if (method != null)
{
+4 -4
View File
@@ -268,8 +268,8 @@ bool Editor::CheckProjectUpgrade()
// Check if last version was older
else if (lastVersion.Major < FLAXENGINE_VERSION_MAJOR || (lastVersion.Major == FLAXENGINE_VERSION_MAJOR && lastVersion.Minor < FLAXENGINE_VERSION_MINOR))
{
LOG(Warning, "The project was opened with the older editor version last time");
const auto result = MessageBox::Show(TEXT("The project was opened with the older editor version last time. Loading it may modify existing data so older editor version won't open it. Do you want to perform a backup before or cancel operation?"), TEXT("Project upgrade"), MessageBoxButtons::YesNoCancel, MessageBoxIcon::Question);
LOG(Warning, "The project was last opened with an older editor version");
const auto result = MessageBox::Show(TEXT("The project was last opened with an older editor version.\nLoading it may modify existing data, which can result in older editor versions being unable to open it.\n\nDo you want to perform a backup before or cancel the operation?"), TEXT("Project upgrade"), MessageBoxButtons::YesNoCancel, MessageBoxIcon::Question);
if (result == DialogResult::Yes)
{
if (BackupProject())
@@ -291,8 +291,8 @@ bool Editor::CheckProjectUpgrade()
// Check if last version was newer
else if (lastVersion.Major > FLAXENGINE_VERSION_MAJOR || (lastVersion.Major == FLAXENGINE_VERSION_MAJOR && lastVersion.Minor > FLAXENGINE_VERSION_MINOR))
{
LOG(Warning, "The project was opened with the newer editor version last time");
const auto result = MessageBox::Show(TEXT("The project was opened with the newer editor version last time. Loading it may fail and corrupt existing data. Do you want to perform a backup before or cancel operation?"), TEXT("Project upgrade"), MessageBoxButtons::YesNoCancel, MessageBoxIcon::Warning);
LOG(Warning, "The project was last opened with a newer editor version");
const auto result = MessageBox::Show(TEXT("The project was last opened with a newer editor version.\nLoading it may fail and corrupt existing data.\n\nDo you want to perform a backup before loading or cancel the operation?"), TEXT("Project upgrade"), MessageBoxButtons::YesNoCancel, MessageBoxIcon::Warning);
if (result == DialogResult::Yes)
{
if (BackupProject())
+14 -3
View File
@@ -1390,6 +1390,7 @@ namespace FlaxEditor
public void BuildAllMeshesSDF()
{
var models = new List<Model>();
var forceRebuild = Input.GetKey(KeyboardKeys.F);
Scene.ExecuteOnGraph(node =>
{
if (node is StaticModelNode staticModelNode && staticModelNode.Actor is StaticModel staticModel)
@@ -1399,7 +1400,7 @@ namespace FlaxEditor
model != null &&
!models.Contains(model) &&
!model.IsVirtual &&
model.SDF.Texture == null)
(forceRebuild || model.SDF.Texture == null))
{
models.Add(model);
}
@@ -1412,7 +1413,17 @@ namespace FlaxEditor
{
var model = models[i];
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();
}
});
@@ -1587,7 +1598,7 @@ namespace FlaxEditor
if (dockedTo != null && dockedTo.SelectedTab != gameWin && dockedTo.SelectedTab != null)
result = dockedTo.SelectedTab.Size;
else
result = gameWin.Viewport.Size;
result = gameWin.Viewport.ContentSize;
result *= root.DpiScale;
result = Float2.Round(result);
+32 -4
View File
@@ -129,11 +129,39 @@ namespace FlaxEditor.GUI.Input
{
base.Draw();
var style = Style.Current;
var r = new Rectangle(0, 0, Width, Height);
bool isTransparent = _value.A < 1;
Render2D.FillRectangle(r, _value);
Render2D.DrawRectangle(r, IsMouseOver || IsNavFocused ? style.BackgroundSelected : Color.Black);
var style = Style.Current;
var fullRect = new Rectangle(0, 0, Width, Height);
var colorRect = new Rectangle(0, 0, isTransparent ? Width * 0.7f : Width, Height);
if (isTransparent)
{
var alphaRect = new Rectangle(colorRect.Right, 0, Width - colorRect.Right, Height);
// Draw checkerboard pattern to part of the color value box
Render2D.FillRectangle(alphaRect, Color.White);
var smallRectSize = 7.9f;
var numHor = Mathf.CeilToInt(alphaRect.Width / smallRectSize);
var numVer = Mathf.CeilToInt(alphaRect.Height / smallRectSize);
for (int i = 0; i < numHor; i++)
{
for (int j = 0; j < numVer; j++)
{
if ((i + j) % 2 == 0)
{
var rect = new Rectangle(alphaRect.X + smallRectSize * i, alphaRect.Y + smallRectSize * j, new Float2(smallRectSize));
Render2D.PushClip(alphaRect);
Render2D.FillRectangle(rect, Color.Gray);
Render2D.PopClip();
}
}
}
Render2D.FillRectangle(alphaRect, _value);
}
Render2D.FillRectangle(colorRect, _value with { A = 1 });
Render2D.DrawRectangle(fullRect, IsMouseOver || IsNavFocused ? style.BackgroundSelected : Color.Black);
}
/// <inheritdoc />
+2 -2
View File
@@ -447,8 +447,8 @@ namespace FlaxEditor.GUI.Tree
// Select previous parent child
var select = nodeParent.GetChild(myIndex - 1) as TreeNode;
// Select last child if is valid and expanded and has any children
if (select != null && select.IsExpanded && select.HasAnyVisibleChild)
// Get bottom most child node
while (select != null && select.IsExpanded && select.HasAnyVisibleChild)
{
select = select.GetChild(select.ChildrenCount - 1) as TreeNode;
}
+2 -1
View File
@@ -669,7 +669,7 @@ namespace FlaxEditor.Modules
if (item != null)
Editor.ContentEditing.Open(item);
});
cm.AddButton("Editor Options", () => Editor.Windows.EditorOptionsWin.Show());
cm.AddButton("Editor Options", inputOptions.EditorOptionsWindow, () => Editor.Windows.EditorOptionsWin.Show());
// Scene
MenuScene = MainMenu.AddButton("Scene");
@@ -714,6 +714,7 @@ namespace FlaxEditor.Modules
_menuToolsBuildCSGMesh = cm.AddButton("Build CSG mesh", inputOptions.BuildCSG, Editor.BuildCSG);
_menuToolsBuildNavMesh = cm.AddButton("Build Nav Mesh", inputOptions.BuildNav, Editor.BuildNavMesh);
_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.AddButton("Game Cooker", Editor.Windows.GameCookerWin.FocusOrShow);
_menuToolsCancelBuilding = cm.AddButton("Cancel building game", () => GameCooker.Cancel());
+4
View File
@@ -650,6 +650,10 @@ namespace FlaxEditor.Options
[EditorDisplay("Windows"), EditorOrder(4020)]
public InputBinding VisualScriptDebuggerWindow = new InputBinding(KeyboardKeys.None);
[DefaultValue(typeof(InputBinding), "Control+Comma")]
[EditorDisplay("Windows"), EditorOrder(4030)]
public InputBinding EditorOptionsWindow = new InputBinding(KeyboardKeys.Comma, KeyboardKeys.Control);
#endregion
#region Node Editors
@@ -726,7 +726,7 @@ namespace FlaxEditor.Surface.Archetypes
private void OnSurfaceMouseUp(ref Float2 mouse, MouseButton buttons, ref bool handled)
{
if (handled)
if (handled || Surface.Context != Context)
return;
// Check click over the connection
@@ -751,7 +751,7 @@ namespace FlaxEditor.Surface.Archetypes
private void OnSurfaceMouseDoubleClick(ref Float2 mouse, MouseButton buttons, ref bool handled)
{
if (handled)
if (handled || Surface.Context != Context)
return;
// Check double click over the connection
+37 -30
View File
@@ -2,11 +2,8 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
using System.Runtime.Serialization.Formatters.Binary;
using FlaxEditor.CustomEditors;
using FlaxEditor.CustomEditors.Editors;
using FlaxEditor.GUI.ContextMenu;
@@ -18,6 +15,7 @@ namespace FlaxEditor.Surface
class AttributesEditor : ContextMenuBase
{
private CustomEditorPresenter _presenter;
private Proxy _proxy;
private byte[] _oldData;
private class Proxy
@@ -72,11 +70,11 @@ namespace FlaxEditor.Surface
/// Initializes a new instance of the <see cref="AttributesEditor"/> class.
/// </summary>
/// <param name="attributes">The attributes list to edit.</param>
/// <param name="attributeType">The allowed attribute types to use.</param>
public AttributesEditor(Attribute[] attributes, IList<Type> attributeType)
/// <param name="attributeTypes">The allowed attribute types to use.</param>
public AttributesEditor(Attribute[] attributes, IList<Type> attributeTypes)
{
// Context menu dimensions
const float width = 340.0f;
const float width = 375.0f;
const float height = 370.0f;
Size = new Float2(width, height);
@@ -88,61 +86,68 @@ namespace FlaxEditor.Surface
Parent = this
};
// Buttons
float buttonsWidth = (width - 16.0f) * 0.5f;
// Ok and Cancel Buttons
float buttonsWidth = (width - 12.0f) * 0.5f;
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",
Parent = this
};
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)
{
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
};
var editor = new CustomEditorPresenter(null);
editor.Panel.AnchorPreset = AnchorPresets.HorizontalStretchTop;
editor.Panel.IsScrollable = true;
editor.Panel.Parent = panel1;
editor.Panel.Tag = attributeType;
editor.Panel.Tag = attributeTypes;
_presenter = editor;
// Cache 'previous' state to check if attributes were edited after operation
_oldData = SurfaceMeta.GetAttributesData(attributes);
editor.Select(new Proxy
_proxy = new Proxy
{
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()
{
var newValue = ((Proxy)_presenter.Selection[0]).Value;
for (int i = 0; i < newValue.Length; i++)
{
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;
}
}
newValue = newValue.Where(v => v != null).ToArray();
var newData = SurfaceMeta.GetAttributesData(newValue);
if (!_oldData.SequenceEqual(newData))
{
Edited?.Invoke(newValue);
}
Hide();
}
@@ -183,7 +188,9 @@ namespace FlaxEditor.Surface
{
_presenter = null;
_oldData = null;
_proxy = null;
Edited = null;
_presenter.Modified -= OnPresenterModified;
base.OnDestroy();
}
+17 -14
View File
@@ -214,22 +214,25 @@ namespace FlaxEditor.Surface
if (!_isRenaming)
Render2D.DrawText(style.FontLarge, Title, _headerRect, style.Foreground, TextAlignment.Center, TextAlignment.Center);
// Close button
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)
if (Surface.CanEdit)
{
// Draw overlay
Render2D.FillRectangle(_resizeButtonRect, style.Selection);
Render2D.DrawRectangle(_resizeButtonRect, style.SelectionBorder);
}
// Close button
Render2D.DrawSprite(style.Cross, _closeButtonRect, _closeButtonRect.Contains(_mousePosition) && Surface.CanEdit ? style.Foreground : style.ForegroundGrey);
// Resize button
Render2D.DrawSprite(style.Scale, _resizeButtonRect, _resizeButtonRect.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
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
if (_isSelected)
-21
View File
@@ -431,27 +431,6 @@ namespace FlaxEditor.Surface
/// </summary>
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>();
/// <summary>
@@ -469,7 +469,8 @@ namespace FlaxEditor.Surface
bool handled = base.OnMouseDown(location, button);
if (!handled)
CustomMouseDown?.Invoke(ref location, button, ref handled);
if (handled)
var root = Root;
if (handled || root == null)
{
// Clear flags
_isMovingSelection = false;
@@ -523,11 +524,11 @@ namespace FlaxEditor.Surface
if (_leftMouseDown && controlUnderMouse.CanSelect(ref cLocation))
{
// Check if user is pressing control
if (Root.GetKey(KeyboardKeys.Control))
if (root.GetKey(KeyboardKeys.Control))
{
AddToSelection(controlUnderMouse);
}
else if (Root.GetKey(KeyboardKeys.Shift))
else if (root.GetKey(KeyboardKeys.Shift))
{
RemoveFromSelection(controlUnderMouse);
}
@@ -539,7 +540,7 @@ namespace FlaxEditor.Surface
}
// Start moving selected nodes
if (!Root.GetKey(KeyboardKeys.Shift))
if (!root.GetKey(KeyboardKeys.Shift))
{
StartMouseCapture();
_movingSelectionViewPos = _rootControl.Location;
@@ -559,7 +560,7 @@ namespace FlaxEditor.Surface
// Start selecting or commenting
StartMouseCapture();
if (!Root.GetKey(KeyboardKeys.Control) && !Root.GetKey(KeyboardKeys.Shift))
if (!root.GetKey(KeyboardKeys.Control) && !root.GetKey(KeyboardKeys.Shift))
{
ClearSelection();
}
@@ -178,19 +178,31 @@ namespace FlaxEditor.Surface
// Update boxes types for nodes that dependant box types based on incoming connections
{
bool keepUpdating = false;
int updateLimit = 100;
bool keepUpdating = true;
int updatesMin = 2, updatesMax = 100;
do
{
keepUpdating = false;
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();
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);
@@ -74,11 +74,6 @@ struct TextureDataResult
PixelFormat Format;
Int2 Mip0Size;
BytesContainer* Mip0DataPtr;
TextureDataResult()
: Lock(FlaxStorage::LockData::Invalid)
{
}
};
bool GetTextureDataForSampling(Texture* texture, TextureDataResult& data, bool hdr = false)
@@ -74,7 +74,7 @@ namespace FlaxEditor.Viewport
private PrefabUIEditorRoot _uiRoot;
private bool _showUI = false;
private int _defaultScaleActiveIndex = 0;
private int _defaultScaleActiveIndex = -1;
private int _customScaleActiveIndex = -1;
private ContextMenuButton _uiModeButton;
private ContextMenuChildMenu _uiViewOptions;
@@ -140,7 +140,7 @@ namespace FlaxEditor.Windows
}
private string _cacheFolder;
private Guid _assetId;
private AssetItem _item;
private Surface _surface;
private Label _loadingLabel;
private CancellationTokenSource _token;
@@ -163,13 +163,13 @@ namespace FlaxEditor.Windows
public AssetReferencesGraphWindow(Editor editor, AssetItem assetItem)
: base(editor, false, ScrollBars.None)
{
Title = assetItem.ShortName + " References";
_item = assetItem;
Title = _item.ShortName + " References";
_tempFolder = StringUtils.NormalizePath(Path.GetDirectoryName(Globals.TemporaryFolder));
_cacheFolder = Path.Combine(Globals.ProjectCacheFolder, "References");
if (!Directory.Exists(_cacheFolder))
Directory.CreateDirectory(_cacheFolder);
_assetId = assetItem.ID;
_surface = new Surface(this)
{
AnchorPreset = AnchorPresets.StretchAll,
@@ -194,6 +194,7 @@ namespace FlaxEditor.Windows
_nodesAssets.Add(assetId);
var node = new AssetNode((uint)_nodes.Count + 1, _surface.Context, GraphNodes[0], GraphGroups[0], assetId);
_nodes.Add(node);
return node;
}
@@ -392,8 +393,7 @@ namespace FlaxEditor.Windows
_nodesAssets = new HashSet<Guid>();
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)
var assetNode = SpawnNode(_assetId);
// TODO: add some outline or tint color to the main node
var assetNode = SpawnNode(_item.ID);
BuildGraph(assetNode, searchLevel, false);
ArrangeGraph(assetNode, false);
BuildGraph(assetNode, searchLevel, true);
@@ -402,6 +402,10 @@ namespace FlaxEditor.Windows
return;
_progress = 100.0f;
var commentRect = assetNode.EditorBounds;
commentRect.Expand(80f);
_surface.Context.CreateComment(ref commentRect, _item.ShortName, Color.Green);
// Update UI
FlaxEngine.Scripting.InvokeOnUpdate(() =>
{
@@ -14,7 +14,6 @@ using FlaxEditor.Surface;
using FlaxEditor.Viewport.Previews;
using FlaxEngine;
using FlaxEngine.GUI;
using FlaxEngine.Utilities;
namespace FlaxEditor.Windows.Assets
{
@@ -430,7 +429,7 @@ namespace FlaxEditor.Windows.Assets
for (var i = 0; i < parameters.Length; i++)
{
var p = parameters[i];
if (p.IsOverride)
if (p.IsOverride && p.IsPublic)
{
p.IsOverride = false;
actions.Add(new EditParamOverrideAction
+1 -11
View File
@@ -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).");
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 backfacesThreshold = backfacesThresholdProp.FloatValue();
var backfacesThresholdLabel = backfacesThresholdProp.Labels.Last();
backfacesThreshold.ValueBox.MinValue = 0.001f;
backfacesThreshold.ValueBox.MaxValue = 1.0f;
backfacesThreshold.ValueBox.Value = sdfOptions.BackfacesThreshold;
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.");
lodIndex.IntValue.MinValue = 0;
lodIndex.IntValue.MaxValue = Asset.LODsCount - 1;
+4 -1
View File
@@ -142,6 +142,7 @@ namespace FlaxEditor.Windows
{
Title = "Content";
Icon = editor.Icons.Folder32;
var style = Style.Current;
FlaxEditor.Utilities.Utils.SetupCommonInputActions(this);
@@ -164,6 +165,8 @@ namespace FlaxEditor.Windows
_navigationBar = new NavigationBar
{
Parent = _toolStrip,
ScrollbarTrackColor = style.Background,
ScrollbarThumbColor = style.ForegroundGrey,
};
// Split panel
@@ -179,7 +182,7 @@ namespace FlaxEditor.Windows
var headerPanel = new ContainerControl
{
AnchorPreset = AnchorPresets.HorizontalStretchTop,
BackgroundColor = Style.Current.Background,
BackgroundColor = style.Background,
IsScrollable = false,
Offsets = new Margin(0, 0, 0, 18 + 6),
};
+5
View File
@@ -116,6 +116,11 @@ namespace FlaxEditor.Windows
if (InputOptions.WindowShortcutsAvaliable)
Editor.Windows.VisualScriptDebuggerWin.FocusOrShow();
});
InputActions.Add(options => options.EditorOptionsWindow, () =>
{
if (InputOptions.WindowShortcutsAvaliable)
Editor.Windows.EditorOptionsWin.FocusOrShow();
});
// Register
Editor.Windows.OnWindowAdd(this);
+125 -17
View File
@@ -10,17 +10,117 @@ using FlaxEditor.Modules;
using FlaxEditor.Options;
using FlaxEngine;
using FlaxEngine.GUI;
using FlaxEngine.Json;
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>
/// Provides in-editor play mode simulation.
/// </summary>
/// <seealso cref="FlaxEditor.Windows.EditorWindow" />
public class GameWindow : EditorWindow
{
private readonly RenderOutputControl _viewport;
private readonly ScaledRenderOutputControl _viewport;
private readonly GameRoot _guiRoot;
private bool _showGUI = true, _editGUI = true;
private bool _showDebugDraw = false;
@@ -77,7 +177,7 @@ namespace FlaxEditor.Windows
/// <summary>
/// Gets the viewport.
/// </summary>
public RenderOutputControl Viewport => _viewport;
public ScaledRenderOutputControl Viewport => _viewport;
/// <summary>
/// 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;
// Setup viewport
_viewport = new RenderOutputControl(task)
_viewport = new ScaledRenderOutputControl(task)
{
AnchorPreset = AnchorPresets.StretchAll,
Offsets = Margin.Zero,
@@ -396,11 +496,8 @@ namespace FlaxEditor.Windows
{
if (v == null)
return;
if (v.Size.Y <= 0 || v.Size.X <= 0)
{
return;
}
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()
{
if (!_freeAspect)
{
_windowAspectRatio = Width / Height;
}
else
{
_windowAspectRatio = 1;
}
_windowAspectRatio = _freeAspect ? 1 : Width / Height;
var scaleWidth = _viewportAspectRatio / _windowAspectRatio;
var scaleHeight = _windowAspectRatio / _viewportAspectRatio;
@@ -468,6 +557,24 @@ namespace FlaxEditor.Windows
{
_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();
PerformLayout();
}
@@ -907,6 +1014,7 @@ namespace FlaxEditor.Windows
return child.OnNavigate(direction, Float2.Zero, this, visited);
}
}
return null;
}
@@ -957,7 +1065,7 @@ namespace FlaxEditor.Windows
else
_defaultScaleActiveIndex = 0;
}
if (_customScaleActiveIndex != -1)
{
var options = Editor.UI.CustomViewportScaleOptions;
+3 -1
View File
@@ -296,13 +296,15 @@ namespace FlaxEditor.Windows.Profiler
var resources = _resources.Get(_memoryUsageChart.SelectedSampleIndex);
if (resources == null || resources.Length == 0)
return;
var resourcesOrdered = resources.OrderByDescending(x => x.MemoryUsage);
var resourcesOrdered = resources.OrderByDescending(x => x?.MemoryUsage ?? 0);
// Add rows
var rowColor2 = Style.Current.Background * 1.4f;
int rowIndex = 0;
foreach (var e in resourcesOrdered)
{
if (e == null)
continue;
ClickableRow row;
if (_tableRowsCache.Count != 0)
{
+9 -8
View File
@@ -319,22 +319,22 @@ namespace FlaxEditor.Windows
{
if (assetItem.IsOfType<SceneAsset>())
return true;
return assetItem.OnEditorDrag(this);
return assetItem.OnEditorDrag(this) && Level.IsAnySceneLoaded;
}
private static bool ValidateDragActorType(ScriptType actorType)
{
return Editor.Instance.CodeEditing.Actors.Get().Contains(actorType);
return Editor.Instance.CodeEditing.Actors.Get().Contains(actorType) && Level.IsAnySceneLoaded;
}
private static bool ValidateDragControlType(ScriptType controlType)
{
return Editor.Instance.CodeEditing.Controls.Get().Contains(controlType);
return Editor.Instance.CodeEditing.Controls.Get().Contains(controlType) && Level.IsAnySceneLoaded;
}
private static bool ValidateDragScriptItem(ScriptItem script)
{
return Editor.Instance.CodeEditing.Actors.Get(script) != ScriptType.Null;
return Editor.Instance.CodeEditing.Actors.Get(script) != ScriptType.Null && Level.IsAnySceneLoaded;
}
/// <inheritdoc />
@@ -490,6 +490,7 @@ namespace FlaxEditor.Windows
if (result == DragDropEffect.None)
{
_isDropping = true;
// Drag assets
if (_dragAssets != null && _dragAssets.HasValidDrag)
{
@@ -504,7 +505,7 @@ namespace FlaxEditor.Windows
}
var actor = item.OnEditorDrop(this);
actor.Name = item.ShortName;
Level.SpawnActor(actor);
Editor.SceneEditing.Spawn(actor);
var graphNode = Editor.Scene.GetActorNode(actor.ID);
if (graphNode != null)
graphNodes.Add(graphNode);
@@ -527,7 +528,7 @@ namespace FlaxEditor.Windows
continue;
}
actor.Name = item.Name;
Level.SpawnActor(actor);
Editor.SceneEditing.Spawn(actor);
Editor.Scene.MarkSceneEdited(actor.Scene);
}
result = DragDropEffect.Move;
@@ -549,7 +550,7 @@ namespace FlaxEditor.Windows
Control = control,
Name = item.Name,
};
Level.SpawnActor(uiControl);
Editor.SceneEditing.Spawn(uiControl);
Editor.Scene.MarkSceneEdited(uiControl.Scene);
}
result = DragDropEffect.Move;
@@ -571,7 +572,7 @@ namespace FlaxEditor.Windows
continue;
}
actor.Name = actorType.Name;
Level.SpawnActor(actor);
Editor.SceneEditing.Spawn(actor);
var graphNode = Editor.Scene.GetActorNode(actor.ID);
if (graphNode != null)
graphNodes.Add(graphNode);
+1
View File
@@ -137,6 +137,7 @@ const Char* SplashScreenQuotes[] =
TEXT("Good Luck Have Fun"),
TEXT("GG Well Played"),
TEXT("Now with documentation."),
TEXT("We do this not because it is easy,\nbut because we thought it would be easy"),
};
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;
#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
&& (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
|| (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(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;
+2 -2
View File
@@ -23,11 +23,11 @@ void AudioListener::Update()
{
// Update the velocity
const Vector3 pos = GetPosition();
const float dt = Time::Update.UnscaledDeltaTime.GetTotalSeconds();
const float dt = Math::Max(Time::Update.UnscaledDeltaTime.GetTotalSeconds(), 0.00001f);
const auto prevVelocity = _velocity;
_velocity = (pos - _prevPos) / dt;
_prevPos = pos;
if (_velocity != prevVelocity)
if (_velocity != prevVelocity && !_velocity.IsNanOrInfinity())
{
AudioBackend::Listener::VelocityChanged(_velocity);
}
+1 -1
View File
@@ -666,7 +666,7 @@ void Asset::onLoaded()
{
onLoaded_MainThread();
}
else if (OnLoaded.IsBinded())
else if (OnLoaded.IsBinded() || _references.HasItems())
{
Function<void()> action;
action.Bind<Asset, &Asset::onLoaded>(this);
@@ -218,10 +218,14 @@ Asset::LoadResult MaterialInstance::load()
Guid baseMaterialId;
headerStream.Read(baseMaterialId);
auto baseMaterial = Content::LoadAsync<MaterialBase>(baseMaterialId);
if (baseMaterial)
baseMaterial->AddReference();
// Load parameters
if (Params.Load(&headerStream))
{
if (baseMaterial)
baseMaterial->RemoveReference();
LOG(Warning, "Cannot load material parameters.");
return LoadResult::CannotLoadData;
}
@@ -239,6 +243,8 @@ Asset::LoadResult MaterialInstance::load()
ParamsChanged();
}
if (baseMaterial)
baseMaterial->RemoveReference();
return LoadResult::Ok;
}
+1
View File
@@ -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.");
return true;
}
auto chunkLocks = Storage ? Storage->Lock() : FlaxStorage::LockData();
lodIndex = Math::Clamp(lodIndex, HighestResidentLODIndex(), LODs.Count() - 1);
// Generate SDF
+2 -1
View File
@@ -61,7 +61,7 @@ public:
model->GetLODData(_lodIndex, data);
if (data.IsInvalid())
{
LOG(Warning, "Missing data chunk");
LOG(Warning, "Missing data chunk with LOD{} for model '{}'", _lodIndex, model->ToString());
return true;
}
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).");
return true;
}
auto chunkLocks = Storage ? Storage->Lock() : FlaxStorage::LockData();
ScopeLock lock(Locker);
// Use a temporary chunks for data storage for virtual assets
+4 -2
View File
@@ -18,7 +18,7 @@ public:
/// <param name="id">The asset id.</param>
/// <returns>Loaded asset of null.</returns>
template<typename T>
T* LoadAsync(const Guid& id)
T* Load(const Guid& id)
{
for (auto& e : *this)
{
@@ -26,8 +26,10 @@ public:
return (T*)e.Get();
}
auto asset = (T*)::LoadAsset(id, T::TypeInitializer);
if (asset)
if (asset && !asset->WaitForLoaded())
Add(asset);
else
asset = nullptr;
return asset;
}
+1
View File
@@ -700,6 +700,7 @@ Asset* Content::GetAsset(const StringView& outputPath)
{
if (outputPath.IsEmpty())
return nullptr;
PROFILE_CPU();
ScopeLock lock(AssetsLocker);
for (auto i = Assets.Begin(); i.IsNotEnd(); ++i)
{
+6 -2
View File
@@ -87,6 +87,10 @@ public:
/// </summary>
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>
/// The chunk data.
/// </summary>
@@ -146,7 +150,7 @@ public:
/// </summary>
FORCE_INLINE bool IsLoaded() const
{
return Data.IsValid();
return Data.IsValid() && Platform::AtomicRead(&IsLoading) == 0;
}
/// <summary>
@@ -154,7 +158,7 @@ public:
/// </summary>
FORCE_INLINE bool IsMissing() const
{
return Data.IsInvalid();
return !IsLoaded();
}
/// <summary>
+40 -10
View File
@@ -5,6 +5,7 @@
#include "FlaxPackage.h"
#include "ContentStorageManager.h"
#include "Engine/Core/Log.h"
#include "Engine/Core/ScopeExit.h"
#include "Engine/Core/Types/TimeSpan.h"
#include "Engine/Platform/File.h"
#include "Engine/Profiler/ProfilerCPU.h"
@@ -74,8 +75,6 @@ FlaxChunk* FlaxChunk::Clone() const
const int32 FlaxStorage::MagicCode = 1180124739;
FlaxStorage::LockData FlaxStorage::LockData::Invalid(nullptr);
struct Header
{
int32 MagicCode;
@@ -246,6 +245,7 @@ FlaxStorage::~FlaxStorage()
ASSERT(IsDisposed());
CHECK(_chunksLock == 0);
CHECK(_refCount == 0);
CHECK(_isUnloadingData == 0);
ASSERT(_chunks.IsEmpty());
#if USE_EDITOR
@@ -261,6 +261,22 @@ FlaxStorage::~FlaxStorage()
#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()
{
auto lock = LockData(this);
@@ -689,7 +705,6 @@ bool FlaxStorage::LoadAssetHeader(const Guid& id, AssetInitData& data)
return true;
}
// Load header
return LoadAssetHeader(e, data);
}
@@ -699,7 +714,10 @@ bool FlaxStorage::LoadAssetChunk(FlaxChunk* chunk)
ASSERT(IsLoaded());
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())
return false;
@@ -776,12 +794,10 @@ bool FlaxStorage::LoadAssetChunk(FlaxChunk* chunk)
// Raw data
chunk->Data.Read(stream, size);
}
ASSERT(chunk->IsLoaded());
chunk->RegisterUsage();
}
UnlockChunks();
return failed;
}
@@ -1420,10 +1436,12 @@ FileReadStream* FlaxStorage::OpenFile()
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)
{
return false;
}
return false; // Early out when no files are opened
PROFILE_CPU();
PROFILE_MEM(ContentFiles);
@@ -1496,9 +1514,21 @@ void FlaxStorage::Tick(double time)
{
auto chunk = _chunks.Get()[i];
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();
Platform::InterlockedDecrement(&_isUnloadingData);
}
wasAnyUsed |= wasUsed;
}
+7 -5
View File
@@ -90,6 +90,7 @@ protected:
int64 _refCount = 0;
int64 _chunksLock = 0;
int64 _files = 0;
int64 _isUnloadingData = 0;
double _lastRefLostTime;
CriticalSection _loadLocker;
@@ -129,10 +130,7 @@ public:
/// <summary>
/// Locks the storage chunks data to prevent disposing them. Also ensures that file handles won't be closed while chunks are locked.
/// </summary>
FORCE_INLINE void LockChunks()
{
Platform::InterlockedIncrement(&_chunksLock);
}
void LockChunks();
/// <summary>
/// Unlocks the storage chunks data.
@@ -148,7 +146,6 @@ public:
struct LockData
{
friend FlaxStorage;
static LockData Invalid;
private:
FlaxStorage* _storage;
@@ -161,6 +158,11 @@ public:
}
public:
LockData()
: _storage(nullptr)
{
}
LockData(const LockData& other)
: _storage(other._storage)
{
+2 -7
View File
@@ -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;
rayDistance *= inverseDeterminant;
// Check if the triangle is behind the ray origin
if (rayDistance < 0.0f)
{
return false;
}
// Check if the triangle is in front the ray origin
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)
-5
View File
@@ -12,11 +12,6 @@ String Ray::ToString() const
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)
{
Vector3 nearPoint(x, y, 0.0f);
+4 -1
View File
@@ -79,7 +79,10 @@ public:
/// </summary>
/// <param name="distance">The distance from ray origin.</param>
/// <returns>The calculated point.</returns>
Vector3 GetPoint(Real distance) const;
FORCE_INLINE Vector3 GetPoint(Real distance) const
{
return Position + Direction * distance;
}
/// <summary>
/// Determines if there is an intersection between ray and a point.
+1 -1
View File
@@ -208,7 +208,7 @@ public:
typedef typename FallbackAllocation::template Data<T> FallbackData;
bool _useFallback = false;
byte _data[Capacity * sizeof(T)];
alignas(sizeof(void*)) byte _data[Capacity * sizeof(T)];
FallbackData _fallback;
public:
+5
View File
@@ -215,6 +215,11 @@ public:
return String(_data.Get(), _data.Count());
}
StringAnsi ToStringAnsi() const
{
return StringAnsi(_data.Get(), _data.Count());
}
StringView ToStringView() const;
};
+2
View File
@@ -425,6 +425,8 @@ namespace FlaxEngine.Interop
var fieldOffsetPtr = (IntPtr*)field.FieldHandle.Value; // Pointer to MonoClassField
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)
#else
throw new NotImplementedException();
#endif
}
@@ -338,10 +338,10 @@ public:
StreamTextureMipTask(StreamingTexture* texture, int32 mipIndex, Task* rootTask)
: GPUUploadTextureMipTask(texture->GetTexture(), mipIndex, Span<byte>(nullptr, 0), 0, 0, false)
, _streamingTexture(texture)
, _rootTask(rootTask ? rootTask : this)
, _rootTask(rootTask)
, _dataLock(_streamingTexture->GetOwner()->LockData())
{
_streamingTexture->_streamingTasks.Add(_rootTask);
_streamingTexture->_streamingTasks.Add(this);
_texture.Released.Bind<StreamTextureMipTask, &StreamTextureMipTask::OnResourceReleased2>(this);
}
@@ -357,7 +357,7 @@ private:
if (_streamingTexture)
{
ScopeLock lock(_streamingTexture->GetOwner()->GetOwnerLocker());
_streamingTexture->_streamingTasks.Remove(_rootTask);
_streamingTexture->_streamingTasks.Remove(this);
_streamingTexture = nullptr;
}
}
@@ -422,6 +422,15 @@ protected:
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)
@@ -771,7 +771,7 @@ Task* TextureBase::RequestMipDataAsync(int32 mipIndex)
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
@@ -92,9 +92,8 @@ float GPUTimerQueryDX11::GetResult()
{
if (!_finalized)
{
#if BUILD_DEBUG
ASSERT(HasResult());
#endif
if (!HasResult())
return 0;
UINT64 timeStart, timeEnd;
auto context = _device->GetIM();
@@ -29,6 +29,7 @@
#include "GPUVertexLayoutDX12.h"
#include "CommandQueueDX12.h"
#include "DescriptorHeapDX12.h"
#include "RootSignatureDX12.h"
#include "Engine/Graphics/RenderTask.h"
#include "Engine/GraphicsDevice/DirectX/RenderToolsDX.h"
#include "Engine/Debug/Exceptions/NotImplementedException.h"
@@ -307,6 +308,7 @@ void GPUContextDX12::Reset()
_device->DummyVB = _device->CreateBuffer(TEXT("DummyVertexBuffer"));
auto* layout = GPUVertexLayout::Get({ { VertexElement::Types::Attribute3, 0, 0, 0, PixelFormat::R32G32B32A32_Float } });
_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);
_commandList->IASetVertexBuffers(GPU_MAX_VB_BINDED, 1, &dummyVBView);
@@ -491,7 +493,7 @@ void GPUContextDX12::flushUAVs()
ASSERT(uaCount <= GPU_MAX_UA_BINDED);
// Fill table with source descriptors
DxShaderHeader& header = _currentCompute ? ((GPUShaderProgramCSDX12*)_currentCompute)->Header : _currentState->Header;
DxShaderHeader& header = _isCompute ? ((GPUShaderProgramCSDX12*)_currentCompute)->Header : _currentState->Header;
D3D12_CPU_DESCRIPTOR_HANDLE srcDescriptorRangeStarts[GPU_MAX_UA_BINDED];
for (uint32 i = 0; i < uaCount; i++)
{
@@ -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.");
}
#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)
{
_primitiveTopology = _currentState->PrimitiveTopology;
@@ -12,6 +12,9 @@
#include "GPUSamplerDX12.h"
#include "GPUVertexLayoutDX12.h"
#include "GPUSwapChainDX12.h"
#include "RootSignatureDX12.h"
#include "UploadBufferDX12.h"
#include "CommandQueueDX12.h"
#include "Engine/Engine/Engine.h"
#include "Engine/Engine/CommandLine.h"
#include "Engine/Graphics/RenderTask.h"
@@ -21,20 +24,23 @@
#include "Engine/Profiler/ProfilerMemory.h"
#include "Engine/Core/Log.h"
#include "Engine/Core/Config/PlatformSettings.h"
#include "UploadBufferDX12.h"
#include "CommandQueueDX12.h"
#include "Engine/Core/Types/StringBuilder.h"
#include "Engine/Core/Utilities.h"
#include "Engine/Threading/Threading.h"
#include "CommandSignatureDX12.h"
static bool CheckDX12Support(IDXGIAdapter* adapter)
{
#if PLATFORM_XBOX_SCARLETT || PLATFORM_XBOX_ONE
return true;
#else
// Try to create device
if (SUCCEEDED(D3D12CreateDevice(adapter, D3D_FEATURE_LEVEL_11_0, _uuidof(ID3D12Device), nullptr)))
{
return true;
}
return false;
#endif
}
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()
{
#if PLATFORM_XBOX_SCARLETT || PLATFORM_XBOX_ONE
@@ -561,170 +871,10 @@ bool GPUDeviceDX12::Init()
}
// Create root signature
// TODO: maybe create set of different root signatures? for UAVs, for compute, for simple drawing, for post fx?
{
// Descriptor tables
D3D12_DESCRIPTOR_RANGE r[3]; // SRV+UAV+Sampler
{
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)));
RootSignatureDX12 signature;
ComPtr<ID3DBlob> signatureBlob = signature.Serialize();
VALIDATE_DIRECTX_CALL(_device->CreateRootSignature(0, signatureBlob->GetBufferPointer(), signatureBlob->GetBufferSize(), IID_PPV_ARGS(&_rootSignature)));
}
if (TimestampQueryHeap.Init())
@@ -18,11 +18,6 @@
#define DX12_BACK_BUFFER_COUNT 2
#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 WindowsWindow;
class GPUContextDX12;
@@ -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);
};
@@ -114,7 +114,7 @@ GPUContextVulkan::GPUContextVulkan(GPUDeviceVulkan* device, QueueVulkan* queue)
#if GPU_ENABLE_TRACY
#if VK_EXT_calibrated_timestamps && VK_EXT_host_query_reset && !PLATFORM_SWITCH
// Use calibrated timestamps extension
if (vkResetQueryPoolEXT && vkGetCalibratedTimestampsEXT)
if (vkResetQueryPoolEXT && vkGetCalibratedTimestampsEXT && _device->PhysicalDeviceFeatures12.hostQueryReset)
{
_tracyContext = tracy::CreateVkContext(_device->Adapter->Gpu, _device->Device, vkResetQueryPoolEXT, vkGetPhysicalDeviceCalibrateableTimeDomainsEXT, vkGetCalibratedTimestampsEXT);
}
@@ -1568,7 +1568,15 @@ bool GPUDeviceVulkan::Init()
vkGetPhysicalDeviceQueueFamilyProperties(gpu, &queueCount, QueueFamilyProps.Get());
// Query device features
RenderToolsVulkan::ZeroStruct(PhysicalDeviceFeatures12, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES);
vkGetPhysicalDeviceFeatures(gpu, &PhysicalDeviceFeatures);
if (vkGetPhysicalDeviceFeatures2)
{
VkPhysicalDeviceFeatures2 features2;
RenderToolsVulkan::ZeroStruct(features2, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2);
features2.pNext = &PhysicalDeviceFeatures12;
vkGetPhysicalDeviceFeatures2(gpu, &features2);
}
// Get extensions and layers
Array<const char*> deviceExtensions;
@@ -1671,6 +1679,16 @@ bool GPUDeviceVulkan::Init()
VulkanPlatform::RestrictEnabledPhysicalDeviceFeatures(PhysicalDeviceFeatures, enabledFeatures);
deviceInfo.pEnabledFeatures = &enabledFeatures;
#if GPU_ENABLE_TRACY && VK_EXT_calibrated_timestamps && VK_EXT_host_query_reset
VkPhysicalDeviceHostQueryResetFeatures resetFeatures;
if (PhysicalDeviceFeatures12.hostQueryReset)
{
RenderToolsVulkan::ZeroStruct(resetFeatures, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_HOST_QUERY_RESET_FEATURES);
resetFeatures.hostQueryReset = VK_TRUE;
deviceInfo.pNext = &resetFeatures;
}
#endif
// Create the device
VALIDATE_VULKAN_RESULT(vkCreateDevice(gpu, &deviceInfo, nullptr, &Device));
@@ -496,6 +496,7 @@ public:
/// The physical device enabled features.
/// </summary>
VkPhysicalDeviceFeatures PhysicalDeviceFeatures;
VkPhysicalDeviceVulkan12Features PhysicalDeviceFeatures12;
Array<BufferedQueryPoolVulkan*> TimestampQueryPools;
Array<BufferedQueryPoolVulkan*> OcclusionQueryPools;
+11 -11
View File
@@ -42,38 +42,38 @@ public:
/// <summary>
/// The reflections texture resolution.
/// </summary>
API_FIELD(Attributes="EditorOrder(0), EditorDisplay(\"Probe\")")
API_FIELD(Attributes="EditorOrder(0), EditorDisplay(\"Quality\")")
ProbeCubemapResolution CubemapResolution = ProbeCubemapResolution::UseGraphicsSettings;
/// <summary>
/// The probe update mode.
/// </summary>
API_FIELD(Attributes = "EditorOrder(10), EditorDisplay(\"Quality\")")
ProbeUpdateMode UpdateMode = ProbeUpdateMode::Manual;
/// <summary>
/// The reflections brightness.
/// </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;
/// <summary>
/// The probe rendering order. The higher values are render later (on top).
/// </summary>
API_FIELD(Attributes = "EditorOrder(25), EditorDisplay(\"Probe\")")
API_FIELD(Attributes = "EditorOrder(20), EditorDisplay(\"Probe\")")
int32 SortOrder = 0;
/// <summary>
/// The probe update mode.
/// </summary>
API_FIELD(Attributes="EditorOrder(30), EditorDisplay(\"Probe\")")
ProbeUpdateMode UpdateMode = ProbeUpdateMode::Manual;
/// <summary>
/// The probe capture camera near plane distance.
/// </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;
public:
/// <summary>
/// Gets the probe radius.
/// </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;
/// <summary>
+1 -1
View File
@@ -518,7 +518,7 @@ namespace
Vector3 nextPos = transform.LocalToWorld(next->Value.Translation);
DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(nextPos, NodeSizeByDistance(nextPos, scaleByDistance)), color, 0.0f, depthTest);
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;
prevPos = nextPos;
}
+3 -1
View File
@@ -8,7 +8,7 @@
#endif
// 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.
#define USE_NETWORK_KEYS 1
@@ -29,6 +29,7 @@ enum class NetworkMessageIDs : uint8
ObjectDespawn,
ObjectRole,
ObjectRpc,
ObjectRpcPart,
MAX,
};
@@ -48,6 +49,7 @@ public:
static void OnNetworkMessageObjectDespawn(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 OnNetworkMessageObjectRpcPart(NetworkEvent& event, NetworkClient* client, NetworkPeer* peer);
#if COMPILE_WITH_PROFILER
@@ -391,6 +391,7 @@ namespace
NetworkInternal::OnNetworkMessageObjectDespawn,
NetworkInternal::OnNetworkMessageObjectRole,
NetworkInternal::OnNetworkMessageObjectRpc,
NetworkInternal::OnNetworkMessageObjectRpcPart,
};
}
+333 -254
View File
@@ -55,14 +55,14 @@ PACK_STRUCT(struct NetworkMessageObjectReplicate
uint32 OwnerFrame;
});
PACK_STRUCT(struct NetworkMessageObjectReplicatePayload
PACK_STRUCT(struct NetworkMessageObjectPartPayload
{
uint16 DataSize;
uint16 PartsCount;
uint16 PartSize;
});
PACK_STRUCT(struct NetworkMessageObjectReplicatePart
PACK_STRUCT(struct NetworkMessageObjectPart
{
NetworkMessageIDs ID = NetworkMessageIDs::ObjectReplicatePart;
uint32 OwnerFrame;
@@ -111,7 +111,7 @@ PACK_STRUCT(struct NetworkMessageObjectRole
PACK_STRUCT(struct NetworkMessageObjectRpc
{
NetworkMessageIDs ID = NetworkMessageIDs::ObjectRpc;
uint16 ArgsSize;
uint32 OwnerFrame;
});
struct NetworkReplicatedObject
@@ -182,13 +182,14 @@ struct Serializer
void* Tags[2];
};
struct ReplicateItem
struct PartsItem
{
ScriptingObjectReference<ScriptingObject> Object;
Guid ObjectId;
uint16 PartsLeft;
uint32 OwnerFrame;
uint32 OwnerClientId;
const void* Tag;
Array<byte> Data;
};
@@ -220,7 +221,7 @@ struct DespawnItem
DataContainer<uint32> Targets;
};
struct RpcItem
struct RpcSendItem
{
ScriptingObjectReference<ScriptingObject> Object;
NetworkRpcName Name;
@@ -233,11 +234,12 @@ namespace
{
CriticalSection ObjectsLock;
HashSet<NetworkReplicatedObject> Objects;
Array<ReplicateItem> ReplicationParts;
Array<PartsItem> ReplicationParts;
Array<PartsItem> RpcParts;
Array<SpawnItemParts> SpawnParts;
Array<SpawnItem> SpawnQueue;
Array<DespawnItem> DespawnQueue;
Array<RpcItem> RpcQueue;
Array<RpcSendItem> RpcQueue;
Dictionary<Guid, Guid> IdsRemappingTable;
NetworkStream* CachedWriteStream = nullptr;
NetworkStream* CachedReadStream = nullptr;
@@ -251,6 +253,7 @@ namespace
#endif
Array<Guid> DespawnedObjects;
uint32 SpawnId = 0;
uint32 RpcId = 0;
#if USE_EDITOR
void OnScriptsReloading()
@@ -505,6 +508,76 @@ void SetupObjectSpawnMessageItem(SpawnItem* e, NetworkMessage& msg)
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)
{
PROFILE_CPU();
@@ -589,7 +662,7 @@ void SendObjectRoleMessage(const NetworkReplicatedObject& item, const NetworkCli
msg.WriteNetworkId(objectId);
if (NetworkManager::IsClient())
{
NetworkManager::Peer->EndSendMessage(NetworkChannelType::ReliableOrdered, msg);
peer->EndSendMessage(NetworkChannelType::ReliableOrdered, msg);
}
else
{
@@ -598,6 +671,160 @@ 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();
// Skip serialization of objects that none will receive
if (!isClient)
{
BuildCachedTargets(item, targetClients);
if (CachedTargets.Count() == 0)
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 &&
item.RepCache.Mask == targetClients &&
Platform::MemoryCompare(item.RepCache.Data.Get(), stream->GetBuffer(), size) == 0)
{
return;
}
item.RepCache.Mask = 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);
}
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)
{
// Remove from the mapping table
@@ -708,38 +935,43 @@ FORCE_INLINE void DirtyObjectImpl(NetworkReplicatedObject& item, ScriptingObject
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
ReplicateItem* replicateItem = nullptr;
for (auto& e : ReplicationParts)
PartsItem* item = nullptr;
for (auto& e : items)
{
if (e.OwnerFrame == ownerFrame && e.Data.Count() == dataSize && e.ObjectId == objectId)
{
// Reuse
replicateItem = &e;
item = &e;
break;
}
}
if (!replicateItem)
if (!item)
{
// Add
replicateItem = &ReplicationParts.AddOne();
replicateItem->ObjectId = objectId;
replicateItem->PartsLeft = partsCount;
replicateItem->OwnerFrame = ownerFrame;
replicateItem->OwnerClientId = senderClientId;
replicateItem->Data.Resize(dataSize);
item = &items.AddOne();
item->ObjectId = objectId;
item->PartsLeft = partsCount;
item->OwnerFrame = ownerFrame;
item->OwnerClientId = senderClientId;
item->Data.Resize(dataSize);
}
// Copy part data
ASSERT(replicateItem->PartsLeft > 0);
replicateItem->PartsLeft--;
ASSERT(partStart + partSize <= replicateItem->Data.Count());
ASSERT(item->PartsLeft > 0);
item->PartsLeft--;
ASSERT(partStart + partSize <= item->Data.Count());
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)
@@ -787,6 +1019,24 @@ void InvokeObjectReplication(NetworkReplicatedObject& item, uint32 ownerFrame, b
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)
{
ScopeLock lock(ObjectsLock);
@@ -1652,9 +1902,6 @@ void NetworkInternal::NetworkReplicatorUpdate()
if (Objects.Count() == 0)
return;
const bool isClient = NetworkManager::IsClient();
const bool isServer = NetworkManager::IsServer();
const bool isHost = NetworkManager::IsHost();
NetworkPeer* peer = NetworkManager::Peer;
if (!isClient && NewClients.Count() != 0)
{
@@ -1694,22 +1941,7 @@ void NetworkInternal::NetworkReplicatorUpdate()
PROFILE_CPU_NAMED("DespawnQueue");
for (DespawnItem& e : DespawnQueue)
{
// Send despawn message
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);
SendDespawn(e);
}
DespawnQueue.Clear();
}
@@ -1830,6 +2062,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
// Replicate all owned networked objects with other clients or server
@@ -1871,136 +2104,11 @@ void NetworkInternal::NetworkReplicatorUpdate()
PROFILE_CPU_NAMED("Replication");
if (CachedWriteStream == nullptr)
CachedWriteStream = New<NetworkStream>();
NetworkStream* stream = CachedWriteStream;
stream->SenderId = NetworkManager::LocalClientId;
CachedWriteStream->SenderId = NetworkManager::LocalClientId;
// TODO: use Job System when replicated objects count is large
for (auto& e : CachedReplicationResult->_entries)
{
ScriptingObject* obj = e.Object;
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
SendReplication(e.Object, e.TargetClients);
}
}
@@ -2009,70 +2117,7 @@ void NetworkInternal::NetworkReplicatorUpdate()
PROFILE_CPU_NAMED("Rpc");
for (auto& e : RpcQueue)
{
ScriptingObject* obj = e.Object.Get();
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
SendRpc(e);
}
RpcQueue.Clear();
}
@@ -2085,7 +2130,7 @@ void NetworkInternal::OnNetworkMessageObjectReplicate(NetworkEvent& event, Netwo
{
PROFILE_CPU();
NetworkMessageObjectReplicate msgData;
NetworkMessageObjectReplicatePayload msgDataPayload;
NetworkMessageObjectPartPayload msgDataPayload;
Guid objectId, parentId;
StringAnsiView objectTypeName;
event.Message.ReadStructure(msgData);
@@ -2095,7 +2140,7 @@ void NetworkInternal::OnNetworkMessageObjectReplicate(NetworkEvent& event, Netwo
event.Message.ReadStructure(msgDataPayload);
ScopeLock lock(ObjectsLock);
if (DespawnedObjects.Contains(objectId))
return; // Skip replicating not-existing objects
return; // Skip replicating non-existing objects
NetworkReplicatedObject* e = ResolveObject(objectId, parentId, objectTypeName);
if (!e)
return;
@@ -2114,7 +2159,7 @@ void NetworkInternal::OnNetworkMessageObjectReplicate(NetworkEvent& event, Netwo
else
{
// 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;
}
}
@@ -2122,13 +2167,13 @@ void NetworkInternal::OnNetworkMessageObjectReplicate(NetworkEvent& event, Netwo
void NetworkInternal::OnNetworkMessageObjectReplicatePart(NetworkEvent& event, NetworkClient* client, NetworkPeer* peer)
{
PROFILE_CPU();
NetworkMessageObjectReplicatePart msgData;
NetworkMessageObjectPart msgData;
Guid objectId;
event.Message.ReadStructure(msgData);
event.Message.ReadNetworkId(objectId);
ScopeLock lock(ObjectsLock);
if (DespawnedObjects.Contains(objectId))
return; // Skip replicating not-existing objects
return; // Skip replicating non-existing objects
const uint32 senderClientId = client ? client->ClientId : NetworkManager::ServerClientId;
AddObjectReplicateItem(event, msgData.OwnerFrame, msgData.PartsCount, msgData.DataSize, objectId, msgData.PartStart, msgData.PartSize, senderClientId);
@@ -2288,14 +2333,16 @@ void NetworkInternal::OnNetworkMessageObjectRpc(NetworkEvent& event, NetworkClie
{
PROFILE_CPU();
NetworkMessageObjectRpc msgData;
Guid msgObjectId, msgParentId;
NetworkMessageObjectPartPayload msgDataPayload;
Guid objectId, parentId;
StringAnsiView objectTypeName, rpcTypeName, rpcName;
event.Message.ReadStructure(msgData);
event.Message.ReadNetworkId(msgObjectId);
event.Message.ReadNetworkId(msgParentId);
event.Message.ReadNetworkId(objectId);
event.Message.ReadNetworkId(parentId);
event.Message.ReadNetworkName(objectTypeName);
event.Message.ReadNetworkName(rpcTypeName);
event.Message.ReadNetworkName(rpcName);
event.Message.ReadStructure(msgDataPayload);
ScopeLock lock(ObjectsLock);
// Find RPC info
@@ -2305,11 +2352,11 @@ void NetworkInternal::OnNetworkMessageObjectRpc(NetworkEvent& event, NetworkClie
const NetworkRpcInfo* info = NetworkRpcInfo::RPCsTable.TryGet(name);
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;
}
NetworkReplicatedObject* e = ResolveObject(msgObjectId, msgParentId, objectTypeName);
NetworkReplicatedObject* e = ResolveObject(objectId, parentId, objectTypeName);
if (e)
{
auto& item = *e;
@@ -2329,18 +2376,50 @@ void NetworkInternal::OnNetworkMessageObjectRpc(NetworkEvent& event, NetworkClie
return;
}
// Setup message reading stream
if (CachedReadStream == nullptr)
CachedReadStream = New<NetworkStream>();
NetworkStream* stream = CachedReadStream;
stream->SenderId = client ? client->ClientId : NetworkManager::ServerClientId;
stream->Initialize(event.Message.Buffer + event.Message.Position, msgData.ArgsSize);
// Execute RPC
info->Execute(obj, stream, info->Tag);
const uint32 senderClientId = client ? client->ClientId : NetworkManager::ServerClientId;
if (msgDataPayload.PartsCount == 1)
{
// Call RPC
InvokeObjectRpc(info, event.Message.Buffer + event.Message.Position, msgDataPayload.DataSize, senderClientId, obj);
}
else
{
// 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))
{
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);
}
}
@@ -425,8 +425,8 @@ void ParticleEmitterGPUGenerator::ProcessGroupParticles(Box* box, Node* node, Va
case 300:
{
// Load function asset
const auto function = Assets.LoadAsync<ParticleEmitterFunction>((Guid)node->Values[0]);
if (!function || function->WaitForLoaded())
const auto function = Assets.Load<ParticleEmitterFunction>((Guid)node->Values[0]);
if (!function)
{
OnError(node, box, TEXT("Missing or invalid function."));
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))
{
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)
{
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;
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)
{
OnError(node, box, TEXT("Missing calling function graph."));
@@ -156,7 +156,7 @@ void ParticleSystemInstance::Sync(ParticleSystem* system)
if (GPUParticlesCountReadback)
GPUParticlesCountReadback->ReleaseGPU();
}
ASSERT(Emitters.Count() == system->Emitters.Count());
CHECK(Emitters.Count() == system->Emitters.Count());
}
bool ParticleSystemInstance::ContainsEmitter(ParticleEmitter* emitter) const
+5 -2
View File
@@ -194,7 +194,6 @@ void Collider::UpdateLayerBits()
// Own layer mask
const uint32 mask1 = Physics::LayerMasks[GetLayer()];
ASSERT(_shape);
PhysicsBackend::SetShapeFilterMask(_shape, mask0, mask1);
}
@@ -244,10 +243,14 @@ void Collider::UpdateGeometry()
if (actor)
{
const auto rigidBody = dynamic_cast<RigidBody*>(GetParent());
if (_staticActor != nullptr || (rigidBody && CanAttach(rigidBody)))
if (rigidBody && CanAttach(rigidBody))
{
Attach(rigidBody);
}
else if (_staticActor != nullptr)
{
PhysicsBackend::AttachShape(_shape, actor);
}
else
{
// Be static triangle mesh
@@ -1969,6 +1969,7 @@ void PhysicsBackend::EndSimulateScene(void* scene)
scenePhysX->EventsCallback.SendTriggerEvents();
scenePhysX->EventsCallback.SendCollisionEvents();
scenePhysX->EventsCallback.SendJointEvents();
scenePhysX->EventsCallback.Clear();
}
// Clear delta after simulation ended
@@ -4466,14 +4467,14 @@ void PhysicsBackend::FlushRequests(void* scene)
}
if (scenePhysX->RemoveColliders.HasItems())
{
for (int32 i = 0; i < scenePhysX->RemoveColliders.Count(); i++)
scenePhysX->EventsCallback.OnColliderRemoved(scenePhysX->RemoveColliders[i]);
for (PhysicsColliderActor* e : scenePhysX->RemoveColliders)
scenePhysX->EventsCallback.OnColliderRemoved(e);
scenePhysX->RemoveColliders.Clear();
}
if (scenePhysX->RemoveJoints.HasItems())
{
for (int32 i = 0; i < scenePhysX->RemoveJoints.Count(); i++)
scenePhysX->EventsCallback.OnJointRemoved(scenePhysX->RemoveJoints[i]);
for (Joint* e : scenePhysX->RemoveJoints)
scenePhysX->EventsCallback.OnJointRemoved(e);
scenePhysX->RemoveJoints.Clear();
}
+11 -21
View File
@@ -3,6 +3,7 @@
#if COMPILE_WITH_EMPTY_PHYSICS
#include "Engine/Core/Log.h"
#include "Engine/Physics/PhysicsBackend.h"
#include "Engine/Physics/CollisionData.h"
#include "Engine/Physics/PhysicalMaterial.h"
#include "Engine/Physics/PhysicsScene.h"
@@ -226,26 +227,6 @@ bool PhysicsBackend::CheckConvex(void* scene, const Vector3& center, const Colli
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)
{
return false;
@@ -353,7 +334,7 @@ Vector3 PhysicsBackend::GetRigidDynamicActorCenterOfMass(void* actor)
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)
{
return Vector3::Up;
@@ -543,11 +543,9 @@ void WindowsPlatform::ReleaseMutex()
}
}
void WindowsPlatform::PreInit(void* hInstance)
PRAGMA_DISABLE_OPTIMIZATION;
void CheckInstructionSet()
{
ASSERT(hInstance);
Instance = hInstance;
#if PLATFORM_ARCH_X86 || PLATFORM_ARCH_X64
// Check the minimum vector instruction set support
int32 cpuInfo[4] = { -1 };
@@ -597,10 +595,19 @@ void WindowsPlatform::PreInit(void* hInstance)
{
// Not supported CPU
CPUBrand cpu;
Error(String::Format(TEXT("Cannot start program due to lack of CPU feature {}.\n\n{}"), missingFeature, String(cpu.Buffer)));
Platform::Error(String::Format(TEXT("Cannot start program due to lack of CPU feature {}.\n\n{}"), missingFeature, String(cpu.Buffer)));
exit(-1);
}
#endif
}
PRAGMA_ENABLE_OPTIMIZATION;
void WindowsPlatform::PreInit(void* hInstance)
{
ASSERT(hInstance);
Instance = hInstance;
CheckInstructionSet();
// Disable the process from being showing "ghosted" while not responding messages during slow tasks
DisableProcessWindowsGhosting();
@@ -47,6 +47,9 @@ public:
Data CachedData;
ToneMappingMode Mode = ToneMappingMode::None;
Texture* LutTexture = nullptr;
#if COMPILE_WITH_DEV_ENV
uint64 FrameRendered = 0;
#endif
~ColorGradingCustomBuffer()
{
@@ -54,6 +57,17 @@ public:
}
};
#if COMPILE_WITH_DEV_ENV
void ColorGradingPass::OnShaderReloading(Asset* obj)
{
_psLut.Release();
invalidateResources();
_reloadedFrame = Engine::FrameCount;
}
#endif
String ColorGradingPass::ToString() const
{
return TEXT("ColorGradingPass");
@@ -194,6 +208,9 @@ GPUTexture* ColorGradingPass::RenderLUT(RenderContext& renderContext)
// Check if LUT parameter hasn't been changed since the last time
if (Platform::MemoryCompare(&colorGradingBuffer.CachedData , &data, sizeof(Data)) == 0 &&
colorGradingBuffer.Mode == toneMapping.Mode &&
#if COMPILE_WITH_DEV_ENV
colorGradingBuffer.FrameRendered > _reloadedFrame &&
#endif
Engine::FrameCount > 30 && // Skip caching when engine is starting TODO: find why this hack is needed
colorGradingBuffer.LutTexture == lutTexture)
{
@@ -203,6 +220,9 @@ GPUTexture* ColorGradingPass::RenderLUT(RenderContext& renderContext)
colorGradingBuffer.CachedData = data;
colorGradingBuffer.Mode = toneMapping.Mode;
colorGradingBuffer.LutTexture = lutTexture;
#if COMPILE_WITH_DEV_ENV
colorGradingBuffer.FrameRendered = Engine::FrameCount;
#endif
// Render LUT
PROFILE_GPU("Color Grading LUT");
+2 -5
View File
@@ -25,11 +25,8 @@ public:
private:
#if COMPILE_WITH_DEV_ENV
void OnShaderReloading(Asset* obj)
{
_psLut.Release();
invalidateResources();
}
uint64 _reloadedFrame = 0;
void OnShaderReloading(Asset* obj);
#endif
public:
@@ -98,6 +98,7 @@ void EyeAdaptationPass::Render(RenderContext& renderContext, GPUTexture* colorBu
if (mode == EyeAdaptationMode::Manual)
{
// Apply fixed manual exposure
context->ResetSR();
context->SetRenderTarget(*colorBuffer);
context->SetViewportAndScissors((float)colorBuffer->Width(), (float)colorBuffer->Height());
context->SetState(_psManual);
@@ -30,6 +30,9 @@ GPU_CB_STRUCT(Data {
Float2 Input0SizeInv;
Float2 Input2SizeInv;
Float3 PrevWorldOriginOffset;
float Dummy1;
});
MotionBlurPass::MotionBlurPass()
@@ -194,6 +197,7 @@ void MotionBlurPass::RenderMotionVectors(RenderContext& renderContext)
Matrix::Transpose(renderContext.View.ViewProjection(), data.CurrentVP);
Matrix::Transpose(renderContext.View.PrevViewProjection, data.PreviousVP);
data.TemporalAAJitter = renderContext.View.TemporalAAJitter;
data.PrevWorldOriginOffset = renderContext.View.Origin - renderContext.View.PrevOrigin;
auto cb = _shader->GetShader()->GetCB(0);
context->UpdateCB(cb, &data);
context->BindCB(0, cb);
@@ -23,6 +23,10 @@ private:
StringAnsiView _fullname;
uint32 _types = 0;
mutable uint32 _size = 0;
#else
StringAnsiView _name;
StringAnsiView _namespace;
StringAnsiView _fullname;
#endif
MAssembly* _assembly;
@@ -21,6 +21,7 @@
MDomain* MRootDomain = nullptr;
MDomain* MActiveDomain = nullptr;
Array<MDomain*, FixedAllocation<4>> MDomains;
bool MCore::Ready = false;
MClass* MCore::TypeCache::Void = nullptr;
MClass* MCore::TypeCache::Object = nullptr;
@@ -301,6 +302,11 @@ bool MProperty::IsStatic() const
return false;
}
void MCore::OnManagedEventAfterShutdown(const char* eventName)
{
LOG(Error, "Found a binding leak on '{}' event used by C# scripting after shutdown. Ensure to unregister scripting events from objects during disposing.", ::String(eventName));
}
MDomain* MCore::GetRootDomain()
{
return MRootDomain;
@@ -57,6 +57,10 @@ public:
static void UnloadScriptingAssemblyLoadContext();
#endif
// Utility for guarding against using C# scripting runtime after shutdown (eg. when asset delegate is not properly disposed).
static bool Ready;
static void OnManagedEventAfterShutdown(const char* eventName);
public:
/// <summary>
/// Utilities for C# object management.
@@ -19,6 +19,8 @@ protected:
#elif USE_NETCORE
void* _handle;
StringAnsiView _name;
#else
StringAnsiView _name;
#endif
mutable MMethod* _addMethod;
@@ -24,6 +24,8 @@ protected:
void* _type;
int32 _fieldOffset;
StringAnsiView _name;
#else
StringAnsiView _name;
#endif
MClass* _parentClass;
@@ -30,6 +30,8 @@ protected:
mutable void* _returnType;
mutable Array<void*, InlinedAllocation<8>> _parameterTypes;
void CacheSignature() const;
#else
StringAnsiView _name;
#endif
MClass* _parentClass;
MVisibility _visibility;
@@ -22,6 +22,8 @@ protected:
#elif USE_NETCORE
void* _handle;
StringAnsiView _name;
#else
StringAnsiView _name;
#endif
mutable MMethod* _getMethod;
+34 -26
View File
@@ -316,7 +316,8 @@ bool MCore::LoadEngine()
char* buildInfo = CallStaticMethod<char*>(GetStaticMethodPointer(TEXT("GetRuntimeInformation")));
LOG(Info, ".NET runtime version: {0}", ::String(buildInfo));
MCore::GC::FreeMemory(buildInfo);
GC::FreeMemory(buildInfo);
Ready = true;
return false;
}
@@ -327,6 +328,7 @@ void MCore::UnloadEngine()
return;
PROFILE_CPU();
CallStaticMethod<void>(GetStaticMethodPointer(TEXT("Exit")));
Ready = false;
MDomains.ClearDelete();
MRootDomain = nullptr;
ShutdownHostfxr();
@@ -1798,6 +1800,33 @@ bool InitHostfxr()
get_hostfxr_params.dotnet_root = dotnetRoot.Get();
}
#endif
String platformStr;
switch (PLATFORM_TYPE)
{
case PlatformType::Windows:
case PlatformType::UWP:
if (PLATFORM_ARCH == ArchitectureType::x64)
platformStr = "Windows x64";
else if (PLATFORM_ARCH == ArchitectureType::ARM64)
platformStr = "Windows ARM64";
else
platformStr = "Windows x86";
break;
case PlatformType::Linux:
platformStr = PLATFORM_ARCH_ARM64 ? "Linux ARM64" : PLATFORM_ARCH_ARM ? "Linux Arm32" : PLATFORM_64BITS ? "Linux x64" : "Linux x86";
break;
case PlatformType::Mac:
platformStr = PLATFORM_ARCH_ARM || PLATFORM_ARCH_ARM64 ? "macOS ARM64" : PLATFORM_64BITS ? "macOS x64" : "macOS x86";
break;
default:
if (PLATFORM_ARCH == ArchitectureType::x64)
platformStr = "x64";
else if (PLATFORM_ARCH == ArchitectureType::ARM64)
platformStr = "ARM64";
else
platformStr = "x86";
}
char_t hostfxrPath[1024];
size_t hostfxrPathSize = sizeof(hostfxrPath) / sizeof(char_t);
int rc = get_hostfxr_path(hostfxrPath, &hostfxrPathSize, &get_hostfxr_params);
@@ -1810,9 +1839,9 @@ bool InitHostfxr()
Platform::OpenUrl(TEXT("https://dotnet.microsoft.com/en-us/download/dotnet"));
#endif
#if USE_EDITOR
LOG(Fatal, "Missing .NET 8 or later SDK installation required to run Flax Editor.");
LOG(Fatal, "Missing .NET 8 or later SDK installation for {0} is required to run Flax Editor.", platformStr);
#else
LOG(Fatal, "Missing .NET 8 or later Runtime installation required to run this application.");
LOG(Fatal, "Missing .NET 8 or later Runtime installation for {0} is required to run this application.", platformStr);
#endif
return true;
}
@@ -1870,27 +1899,6 @@ bool InitHostfxr()
hostfxr_close(handle);
if (rc == 0x80008096) // FrameworkMissingFailure
{
String platformStr;
switch (PLATFORM_TYPE)
{
case PlatformType::Windows:
case PlatformType::UWP:
if (PLATFORM_ARCH == ArchitectureType::x64)
platformStr = "Windows x64";
else if (PLATFORM_ARCH == ArchitectureType::ARM64)
platformStr = "Windows ARM64";
else
platformStr = "Windows x86";
break;
case PlatformType::Linux:
platformStr = PLATFORM_ARCH_ARM64 ? "Linux ARM64" : PLATFORM_ARCH_ARM ? "Linux Arm32" : PLATFORM_64BITS ? "Linux x64" : "Linux x86";
break;
case PlatformType::Mac:
platformStr = PLATFORM_ARCH_ARM || PLATFORM_ARCH_ARM64 ? "macOS ARM64" : PLATFORM_64BITS ? "macOS x64" : "macOS x86";
break;
default:;
platformStr = "";
}
LOG(Fatal, "Failed to resolve compatible .NET runtime version in '{0}'. Make sure the correct platform version for runtime is installed ({1})", platformStr, String(init_params.dotnet_root));
}
else
@@ -2022,13 +2030,13 @@ static MonoAssembly* OnMonoAssemblyLoad(const char* aname)
String fileName = name;
if (!name.EndsWith(TEXT(".dll")) && !name.EndsWith(TEXT(".exe")))
fileName += TEXT(".dll");
String path = fileName;
String path = Globals::ProjectFolder / String(TEXT("/Dotnet/")) / fileName;
if (!FileSystem::FileExists(path))
{
path = Globals::ProjectFolder / String(TEXT("/Dotnet/shared/Microsoft.NETCore.App/")) / fileName;
if (!FileSystem::FileExists(path))
{
path = Globals::ProjectFolder / String(TEXT("/Dotnet/")) / fileName;
path = fileName;
}
}
+1 -1
View File
@@ -32,7 +32,7 @@ public class Scripting : EngineModule
options.ScriptingAPI.Defines.Add("NET");
AddFrameworkDefines("NET{0}_{1}", dotnetSdk.Version.Major, 0); // "NET7_0"
for (int i = 5; i <= dotnetSdk.Version.Major; i++)
AddFrameworkDefines("NET{0}_{1}_OR_GREATER", dotnetSdk.Version.Major, 0); // "NET7_0_OR_GREATER"
AddFrameworkDefines("NET{0}_{1}_OR_GREATER", i, 0); // "NET7_0_OR_GREATER"
options.ScriptingAPI.Defines.Add("NETCOREAPP");
AddFrameworkDefines("NETCOREAPP{0}_{1}_OR_GREATER", 3, 1); // "NETCOREAPP3_1_OR_GREATER"
AddFrameworkDefines("NETCOREAPP{0}_{1}_OR_GREATER", 2, 2);
@@ -93,6 +93,8 @@ ScriptingObject::ScriptingObject(const SpawnParams& params)
: _gcHandle((MGCHandle)params.Managed)
#elif !COMPILE_WITHOUT_CSHARP
: _gcHandle(params.Managed ? MCore::GCHandle::New(params.Managed) : 0)
#else
: _gcHandle(0)
#endif
, _type(params.Type)
, _id(params.ID)
+4 -1
View File
@@ -5,6 +5,9 @@
#include "Types.h"
#include "Engine/Core/Types/StringView.h"
#include "Engine/Core/Types/Guid.h"
#if PLATFORM_ARCH_ARM64
#include "Engine/Core/Core.h"
#endif
class MMethod;
class BinaryModule;
@@ -563,8 +566,8 @@ FORCE_INLINE int32 GetVTableIndex(void** vtable, int32 entriesCount, void* func)
offset = ((*op & 0x3FFC00) >> 10) * ((*op & 0x40000000) != 0 ? 8 : 4);
return offset / sizeof(void*);
}
CRASH;
}
CRASH;
#elif defined(__clang__)
// On Clang member function pointer represents the offset from the vtable begin.
return (int32)(intptr)func / sizeof(void*);
+5 -3
View File
@@ -15,7 +15,6 @@ class MemoryWriteStream;
struct FLAXENGINE_API ShaderCompilationOptions
{
public:
/// <summary>
/// Name of the target object (name of the shader or material for better logging readability)
/// </summary>
@@ -37,12 +36,16 @@ public:
uint32 SourceLength = 0;
public:
/// <summary>
/// Target shader profile
/// </summary>
ShaderProfile Profile = ShaderProfile::Unknown;
/// <summary>
/// Target platform, set to invalid value of 0 if not used (platform-independent compilation).
/// </summary>
PlatformType Platform = (PlatformType)0;
/// <summary>
/// Disables shaders compiler optimizations. Can be used to debug shaders on a target platform or to speed up the shaders compilation time.
/// </summary>
@@ -64,7 +67,6 @@ public:
Array<ShaderMacro> Macros;
public:
/// <summary>
/// Output stream to write compiled shader cache to
/// </summary>
@@ -196,7 +196,7 @@ bool ShaderCompilerD3D::CompileShader(ShaderFunctionMeta& meta, WritePermutation
default:
return true;
}
if (_profile == ShaderProfile::DirectX_SM5)
if (GetProfile() == ShaderProfile::DirectX_SM5)
{
profileName += "_5_0";
}

Some files were not shown because too many files have changed in this diff Show More