Compare commits
163 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3bf3264f48 | |||
| b043490413 | |||
| 3542f20787 | |||
| 43511a96b0 | |||
| 48f302b4fd | |||
| d9c787a661 | |||
| 97bcdacd9a | |||
| 253442abd1 | |||
| c8912ad100 | |||
| 40413edbab | |||
| c10cfc8e45 | |||
| 5739c0bef4 | |||
| 4f97225c46 | |||
| 2c8da4ea04 | |||
| d697bd7402 | |||
| 0bcc01c3c7 | |||
| de76d3623e | |||
| 33caae6935 | |||
| 75d9e36bd2 | |||
| 72f2c8f5cd | |||
| 9cfb3dd220 | |||
| 62d6658444 | |||
| 66818802b1 | |||
| cc4fdf5cc2 | |||
| fde40949b2 | |||
| 65c1a8258e | |||
| 6e89f152ef | |||
| e71f43ea79 | |||
| d33ebc3105 | |||
| 0de61ba218 | |||
| 283a3e6bf9 | |||
| ec9f05fe11 | |||
| b039e3779d | |||
| fc8a9b69d2 | |||
| b38f6c5721 | |||
| 8281e743cd | |||
| 0c1af2f243 | |||
| ba48b2e4f3 | |||
| 64708a14d9 | |||
| 7f2ba7a81e | |||
| fb21ffd3be | |||
| 7127ccda37 | |||
| af3836d611 | |||
| ead71e6836 | |||
| a421effd1b | |||
| 241441d5b9 | |||
| 14b0fb355a | |||
| 4489f43777 | |||
| c17a9f653e | |||
| e7fd901807 | |||
| bdabcd5e43 | |||
| 6cc8f693f3 | |||
| 1079791bed | |||
| 627f3a2dec | |||
| 2f05a0987e | |||
| 6335bcdc93 | |||
| 650fa781d3 | |||
| 000d786d49 | |||
| 3eb85000aa | |||
| 9c5daf419b | |||
| 9a85ae7142 | |||
| fad0f7a345 | |||
| aac399c6a1 | |||
| 4c5035e433 | |||
| b14c2cfc74 | |||
| cbcfa4013b | |||
| 429f8e5336 | |||
| 6cc0edf0eb | |||
| f7d8f36add | |||
| 2e98300693 | |||
| 83de99877c | |||
| 94e529e801 | |||
| 7a569d4f14 | |||
| 24675ace93 | |||
| 3a5d831e71 | |||
| 0aecb35ab1 | |||
| c4bb39aeac | |||
| 61431a6400 | |||
| d4da1d80d0 | |||
| 3bede1d6bc | |||
| ce4ad8aa71 | |||
| 75bd206416 | |||
| 578af12f2b | |||
| acbbd34ecf | |||
| c8b06ba7ec | |||
| ad46b74f6a | |||
| 47a9d76938 | |||
| 442cb8f2dd | |||
| 45e121bf77 | |||
| 8e76d0d9d8 | |||
| f4905cfccc | |||
| bf9a015959 | |||
| 4fd7f51fdf | |||
| f5e483069d | |||
| 0bd28ecb65 | |||
| 9eec54171c | |||
| 486781661e | |||
| b201897ae6 | |||
| c33ada2715 | |||
| dcb9b5150f | |||
| f905b4013b | |||
| b0033a35b7 | |||
| f47a2909fe | |||
| b1c76ec7f9 | |||
| db2130f340 | |||
| edb3badcb3 | |||
| c33b2cc11e | |||
| 135110387d | |||
| 6b3502675a | |||
| 615e847e01 | |||
| 468e05c6e8 | |||
| a46402df6b | |||
| 1fa9aadebd | |||
| c0f52235c6 | |||
| c782f07b76 | |||
| dc9a8a2f84 | |||
| 49943e13de | |||
| 1988fae929 | |||
| 804315bb3e | |||
| 0cacc58b53 | |||
| 7437b69d52 | |||
| f5f4fb29f2 | |||
| 33617a702a | |||
| 84b53bb9c8 | |||
| c3ea883b21 | |||
| f41d01f4a9 | |||
| 141a8de0da | |||
| 63b6fafa1b | |||
| 2ce1103530 | |||
| 6121a6fadf | |||
| 145134f145 | |||
| a7c9eff959 | |||
| c2e8e492d7 | |||
| c916fb1844 | |||
| 004339b81e | |||
| 3f78e47918 | |||
| 7b4a9f1a63 | |||
| 561d40fd71 | |||
| 133340b2ea | |||
| 344665e34f | |||
| 5d188c8c2d | |||
| 9447f3d569 | |||
| df8dc9173a | |||
| b186d19faa | |||
| ea749f12a3 | |||
| 74750fd604 | |||
| b52e8bad4c | |||
| 44f8e86245 | |||
| b036692154 | |||
| 5d050ca020 | |||
| 1b6a31b6e5 | |||
| ab4743fdb1 | |||
| 49629222c1 | |||
| 9d6778ff1e | |||
| ce73394531 | |||
| 463c8dba0d | |||
| 9bb6104cf7 | |||
| 151f4a4923 | |||
| 36f588a792 | |||
| 07c7a250cc | |||
| eab4a8c404 | |||
| cde3e3d710 | |||
| a8dc67a1b2 |
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.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
+3
-2
@@ -2,9 +2,9 @@
|
||||
"Name": "Flax",
|
||||
"Version": {
|
||||
"Major": 1,
|
||||
"Minor": 12,
|
||||
"Minor": 13,
|
||||
"Revision": 0,
|
||||
"Build": 6912
|
||||
"Build": 7003
|
||||
},
|
||||
"Company": "Flax",
|
||||
"Copyright": "Copyright (c) 2012-2026 Wojciech Figat. All rights reserved.",
|
||||
@@ -13,6 +13,7 @@
|
||||
"Configuration": {
|
||||
"UseCSharp": true,
|
||||
"UseLargeWorlds": false,
|
||||
"UseReverseZ": true,
|
||||
"UseDotNet": true,
|
||||
"Windows": {
|
||||
"UseSDL": false,
|
||||
|
||||
@@ -257,6 +257,7 @@
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateThisQualifierSettings/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/GrammarAndSpelling/GrammarChecking/Exceptions/=Try_0020to_0020scripting/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/GrammarAndSpelling/GrammarChecking/Exceptions/=will_0020fallback/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/GrammarAndSpelling/GrammarChecking/RulesStates/=LanguageTool_002EEN_002EE_005FG/@EntryIndexedValue">DisabledByUser</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/Todo/TodoPatterns/=EEA05B0ED8200E4BA9D2D3F1052EBFFD/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/Todo/TodoPatterns/=EEA05B0ED8200E4BA9D2D3F1052EBFFD/Color/@EntryValue">Blue</s:String>
|
||||
|
||||
@@ -360,6 +360,11 @@ public:
|
||||
/// </summary>
|
||||
Array<BinaryModuleInfo, InlinedAllocation<64>> BinaryModules;
|
||||
|
||||
/// <summary>
|
||||
/// Cached version of the built binaries from project Version Control.
|
||||
/// </summary>
|
||||
String VersionControlInfo;
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -42,6 +42,13 @@ bool CompileScriptsStep::DeployBinaries(CookingData& data, const String& path, c
|
||||
return true;
|
||||
}
|
||||
|
||||
// Metadata
|
||||
auto versionControlInfoMember = document.FindMember("VersionControlInfo");
|
||||
if (versionControlInfoMember != document.MemberEnd() && data.VersionControlInfo.IsEmpty())
|
||||
{
|
||||
data.VersionControlInfo = versionControlInfoMember->value.GetText();
|
||||
}
|
||||
|
||||
// Deploy all references
|
||||
auto referencesMember = document.FindMember("References");
|
||||
if (referencesMember != document.MemberEnd())
|
||||
@@ -245,8 +252,15 @@ bool CompileScriptsStep::Perform(CookingData& data)
|
||||
writer.String(target);
|
||||
writer.JKEY("Platform");
|
||||
writer.String(platform);
|
||||
writer.JKEY("Architecture");
|
||||
writer.String(architecture);
|
||||
writer.JKEY("Configuration");
|
||||
writer.String(configuration);
|
||||
if (data.VersionControlInfo.HasChars())
|
||||
{
|
||||
writer.JKEY("VersionControlInfo");
|
||||
writer.String(data.VersionControlInfo);
|
||||
}
|
||||
|
||||
writer.JKEY("BinaryModules");
|
||||
writer.StartArray();
|
||||
|
||||
@@ -50,6 +50,10 @@
|
||||
#endif
|
||||
#include "FlaxEngine.Gen.h"
|
||||
|
||||
#ifndef REVERSE_Z
|
||||
#define REVERSE_Z 0
|
||||
#endif
|
||||
|
||||
Dictionary<String, CookAssetsStep::ProcessAssetFunc> CookAssetsStep::AssetProcessors;
|
||||
|
||||
void IBuildCache::InvalidateCacheShaders()
|
||||
@@ -233,6 +237,11 @@ void CookAssetsStep::CacheData::Load(CookingData& data)
|
||||
LOG(Info, "{0} option has been modified.", TEXT("ShadersGenerateDebugData"));
|
||||
invalidateShaders = true;
|
||||
}
|
||||
if (REVERSE_Z != Settings.Global.ShadersReverseZ)
|
||||
{
|
||||
LOG(Info, "{0} option has been modified.", TEXT("ShadersReverseZ"));
|
||||
invalidateShaders = true;
|
||||
}
|
||||
#if PLATFORM_TOOLS_WINDOWS
|
||||
if (data.Platform == BuildPlatform::Windows32 || data.Platform == BuildPlatform::Windows64)
|
||||
{
|
||||
@@ -1076,6 +1085,7 @@ bool CookAssetsStep::Perform(CookingData& data)
|
||||
{
|
||||
cache.Settings.Global.ShadersNoOptimize = buildSettings->ShadersNoOptimize;
|
||||
cache.Settings.Global.ShadersGenerateDebugData = buildSettings->ShadersGenerateDebugData;
|
||||
cache.Settings.Global.ShadersReverseZ = REVERSE_Z;
|
||||
cache.Settings.Global.StreamingSettingsAssetId = gameSettings->Streaming;
|
||||
cache.Settings.Global.ShadersVersion = GPU_SHADER_CACHE_VERSION;
|
||||
cache.Settings.Global.MaterialGraphVersion = MATERIAL_GRAPH_VERSION;
|
||||
|
||||
@@ -97,6 +97,7 @@ public:
|
||||
{
|
||||
bool ShadersNoOptimize;
|
||||
bool ShadersGenerateDebugData;
|
||||
bool ShadersReverseZ;
|
||||
Guid StreamingSettingsAssetId;
|
||||
int32 ShadersVersion;
|
||||
int32 MaterialGraphVersion;
|
||||
|
||||
@@ -343,6 +343,7 @@ namespace FlaxEditor.CustomEditors
|
||||
}
|
||||
|
||||
private bool _buildOnUpdate;
|
||||
private bool _initialized;
|
||||
private bool _readOnly;
|
||||
|
||||
/// <summary>
|
||||
@@ -430,6 +431,7 @@ namespace FlaxEditor.CustomEditors
|
||||
|
||||
ClearLayout();
|
||||
_buildOnUpdate = false;
|
||||
_initialized = true;
|
||||
Editor.Setup(this);
|
||||
|
||||
Panel.IsLayoutLocked = false;
|
||||
@@ -506,6 +508,10 @@ namespace FlaxEditor.CustomEditors
|
||||
/// </summary>
|
||||
protected virtual void OnSelectionChanged()
|
||||
{
|
||||
// Defer building the layout after we have initialized to improve initial loading times
|
||||
if (!_initialized)
|
||||
_buildOnUpdate = true;
|
||||
else
|
||||
BuildLayout();
|
||||
SelectionChanged?.Invoke();
|
||||
}
|
||||
|
||||
@@ -261,6 +261,7 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
public static List<ItemInfo> GetItemsForType(ScriptType type, bool useProperties, bool useFields, bool usePropertiesWithoutSetter = false)
|
||||
{
|
||||
var items = new List<ItemInfo>();
|
||||
var isPlayMode = Editor.IsPlayMode;
|
||||
|
||||
if (useProperties)
|
||||
{
|
||||
@@ -278,7 +279,7 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
continue;
|
||||
|
||||
// Skip hidden fields, handle special attributes
|
||||
if ((!p.IsPublic && !showInEditor) || attributes.Any(x => x is HideInEditorAttribute))
|
||||
if ((!p.IsPublic && !showInEditor) || attributes.Any(x => x is HideInEditorAttribute hide && (!isPlayMode || !hide.ShowInPlayMode)))
|
||||
continue;
|
||||
|
||||
items.Add(new ItemInfo(p, attributes));
|
||||
@@ -293,11 +294,10 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
for (int i = 0; i < fields.Length; i++)
|
||||
{
|
||||
var f = fields[i];
|
||||
|
||||
var attributes = f.GetAttributes(true);
|
||||
|
||||
// Skip hidden fields, handle special attributes
|
||||
if ((!f.IsPublic && !attributes.Any(x => x is ShowInEditorAttribute)) || attributes.Any(x => x is HideInEditorAttribute))
|
||||
if ((!f.IsPublic && !attributes.Any(x => x is ShowInEditorAttribute)) || attributes.Any(x => x is HideInEditorAttribute hide && (!isPlayMode || !hide.ShowInPlayMode)))
|
||||
continue;
|
||||
|
||||
items.Add(new ItemInfo(f, attributes));
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
// Copyright (c) Wojciech Figat. All rights reserved.
|
||||
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
using System;
|
||||
|
||||
namespace FlaxEditor.CustomEditors.Editors
|
||||
{
|
||||
/// <summary>
|
||||
/// Default implementation of the inspector used to edit TimeSpan value type properties.
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(TimeSpan)), DefaultEditor]
|
||||
class TimeSpanEditor : CustomEditor
|
||||
{
|
||||
private TextBox _textBox;
|
||||
private bool _isRefreshing;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override DisplayStyle Style => DisplayStyle.Inline;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize(LayoutElementsContainer layout)
|
||||
{
|
||||
if (HasDifferentTypes)
|
||||
return;
|
||||
|
||||
_textBox = layout.Custom<TextBox>().CustomControl;
|
||||
_textBox.EditEnd += OnEditEnd;
|
||||
}
|
||||
|
||||
private void OnEditEnd()
|
||||
{
|
||||
if (_isRefreshing)
|
||||
return;
|
||||
if (TimeSpan.TryParse(_textBox.Text, out var timeSpan))
|
||||
SetValue(timeSpan);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Refresh()
|
||||
{
|
||||
base.Refresh();
|
||||
|
||||
_isRefreshing = true;
|
||||
_textBox.Text = HasDifferentValues ? "Multiple Values" : ((TimeSpan)Values[0]).ToString("G");
|
||||
_isRefreshing = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Default implementation of the inspector used to edit DateTime value type properties.
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(DateTime)), DefaultEditor]
|
||||
class DateTimeEditor : CustomEditor
|
||||
{
|
||||
private TextBox _textBox;
|
||||
private bool _isRefreshing;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override DisplayStyle Style => DisplayStyle.Inline;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize(LayoutElementsContainer layout)
|
||||
{
|
||||
if (HasDifferentTypes)
|
||||
return;
|
||||
|
||||
_textBox = layout.Custom<TextBox>().CustomControl;
|
||||
_textBox.EditEnd += OnEditEnd;
|
||||
}
|
||||
|
||||
private void OnEditEnd()
|
||||
{
|
||||
if (_isRefreshing)
|
||||
return;
|
||||
if (DateTime.TryParse(_textBox.Text, out var timeSpan))
|
||||
SetValue(timeSpan);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Refresh()
|
||||
{
|
||||
base.Refresh();
|
||||
|
||||
_isRefreshing = true;
|
||||
_textBox.Text = HasDifferentValues ? "Multiple Values" : ((DateTime)Values[0]).ToString("g");
|
||||
_isRefreshing = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -355,7 +355,7 @@ namespace FlaxEditor.GUI.ContextMenu
|
||||
if (_previouslyFocused != null)
|
||||
{
|
||||
_previouslyFocused.RootWindow?.Focus();
|
||||
_previouslyFocused.Focus();
|
||||
_previouslyFocused?.Focus();
|
||||
_previouslyFocused = null;
|
||||
}
|
||||
|
||||
|
||||
@@ -222,7 +222,10 @@ internal class DirectionGizmo : ContainerControl
|
||||
else
|
||||
{
|
||||
// This could be some actual math expression, not that hack
|
||||
var fov = _owner.Viewport.FieldOfView / 60.0f;
|
||||
float fov = _owner.Viewport.FieldOfView;
|
||||
if (_owner.Viewport.ViewportCamera is Viewport.Cameras.FPSCamera fpsCam)
|
||||
fov += fpsCam.AdditionalZoomFOV;
|
||||
fov /= 60.0f;
|
||||
float scaleAt30 = 0.1f, scaleAt60 = 1.0f, scaleAt120 = 1.5f, scaleAt180 = 3.0f;
|
||||
heightNormalization /= Mathf.Lerp(scaleAt30, scaleAt60, fov);
|
||||
heightNormalization /= Mathf.Lerp(scaleAt60, scaleAt120, Mathf.Saturate(fov - 1));
|
||||
|
||||
@@ -139,15 +139,11 @@ namespace FlaxEditor.Gizmo
|
||||
DrawSelectionDepth(context, renderContext.Task, customDepth);
|
||||
_actors.Clear();
|
||||
|
||||
var near = renderContext.View.Near;
|
||||
var far = renderContext.View.Far;
|
||||
var projection = renderContext.View.Projection;
|
||||
|
||||
// Render outline
|
||||
_material.SetParameterValue("OutlineColor0", _color0);
|
||||
_material.SetParameterValue("OutlineColor1", _color1);
|
||||
_material.SetParameterValue("CustomDepth", customDepth);
|
||||
_material.SetParameterValue("ViewInfo", new Float4(1.0f / projection.M11, 1.0f / projection.M22, far / (far - near), (-far * near) / (far - near) / far));
|
||||
_material.SetParameterValue("ViewInfo", renderContext.View.ViewInfo);
|
||||
Renderer.DrawPostFxMaterial(context, ref renderContext, _material, output, input.View());
|
||||
|
||||
// Cleanup
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace FlaxEditor.Modules
|
||||
private bool _rebuildInitFlag;
|
||||
private int _itemsCreated;
|
||||
private int _itemsDeleted;
|
||||
private readonly HashSet<MainContentFolderTreeNode> _dirtyNodes = new HashSet<MainContentFolderTreeNode>();
|
||||
private readonly HashSet<ContentFolderTreeNode> _dirtyNodes = new HashSet<ContentFolderTreeNode>();
|
||||
|
||||
/// <summary>
|
||||
/// The project directory.
|
||||
@@ -1309,27 +1309,32 @@ namespace FlaxEditor.Modules
|
||||
|
||||
internal void OnDirectoryEvent(MainContentFolderTreeNode node, FileSystemEventArgs e)
|
||||
{
|
||||
// Ensure to be ready for external events
|
||||
// Ignore events during fast setup
|
||||
if (_isDuringFastSetup)
|
||||
return;
|
||||
ContentFolderTreeNode dirtyNode = node;
|
||||
|
||||
// TODO: maybe we could make it faster! since we have a path so it would be easy to just create or delete given file. but remember about subdirectories
|
||||
// Filter the node based on modified path
|
||||
// (eg. if we have event for 'Content/Folder1/Folder2' and node is 'Content/Folder1' then we should process but skip other 'Content' subfolders)
|
||||
var path = StringUtils.NormalizePath(Path.GetDirectoryName(e.FullPath));
|
||||
var pathItem = node.Folder.Find(path) as ContentFolder;
|
||||
if (pathItem != null)
|
||||
{
|
||||
dirtyNode = pathItem.Node;
|
||||
}
|
||||
|
||||
// Switch type
|
||||
switch (e.ChangeType)
|
||||
{
|
||||
case WatcherChangeTypes.Created:
|
||||
case WatcherChangeTypes.Deleted:
|
||||
case WatcherChangeTypes.Renamed:
|
||||
{
|
||||
lock (_dirtyNodes)
|
||||
{
|
||||
_dirtyNodes.Add(node);
|
||||
_dirtyNodes.Add(dirtyNode);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnScriptsReload()
|
||||
{
|
||||
@@ -1383,14 +1388,15 @@ namespace FlaxEditor.Modules
|
||||
// Update all dirty content tree nodes
|
||||
lock (_dirtyNodes)
|
||||
{
|
||||
Profiler.BeginEvent("ContentDatabase.Refresh");
|
||||
foreach (var node in _dirtyNodes)
|
||||
{
|
||||
LoadFolder(node, true);
|
||||
|
||||
if (_enableEvents)
|
||||
WorkspaceModified?.Invoke();
|
||||
}
|
||||
if (_enableEvents && _dirtyNodes.Count != 0)
|
||||
WorkspaceModified?.Invoke();
|
||||
_dirtyNodes.Clear();
|
||||
Profiler.EndEvent();
|
||||
}
|
||||
|
||||
// Lazy-rebuilds
|
||||
|
||||
@@ -2,12 +2,8 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Xml;
|
||||
using FlaxEditor.Scripting;
|
||||
using FlaxEngine;
|
||||
|
||||
@@ -21,7 +17,6 @@ namespace FlaxEditor.Modules.SourceCodeEditing
|
||||
{
|
||||
private Dictionary<ScriptType, string> _typeCache = new Dictionary<ScriptType, string>();
|
||||
private Dictionary<ScriptMemberInfo, string> _memberCache = new Dictionary<ScriptMemberInfo, string>();
|
||||
private Dictionary<Assembly, Dictionary<string, string>> _xmlCache = new Dictionary<Assembly, Dictionary<string, string>>();
|
||||
|
||||
internal CodeDocsModule(Editor editor)
|
||||
: base(editor)
|
||||
@@ -61,13 +56,9 @@ namespace FlaxEditor.Modules.SourceCodeEditing
|
||||
else if (type.Type != null)
|
||||
{
|
||||
// Try to use xml docs for managed type
|
||||
var xml = GetXmlDocs(type.Type.Assembly);
|
||||
if (xml != null)
|
||||
{
|
||||
var key = "T:" + GetXmlKey(type.Type.FullName);
|
||||
if (xml.TryGetValue(key, out var xmlDoc))
|
||||
text += '\n' + FilterWhitespaces(xmlDoc);
|
||||
}
|
||||
var xmlDoc = DebugCommands.GetXml(type.Type);
|
||||
if (xmlDoc != null)
|
||||
text += '\n' + xmlDoc;
|
||||
}
|
||||
|
||||
_typeCache.Add(type, text);
|
||||
@@ -108,242 +99,20 @@ namespace FlaxEditor.Modules.SourceCodeEditing
|
||||
else if (member.Type != null)
|
||||
{
|
||||
// Try to use xml docs for managed member
|
||||
var memberInfo = member.Type;
|
||||
var xml = GetXmlDocs(memberInfo.DeclaringType.Assembly);
|
||||
if (xml != null)
|
||||
{
|
||||
// [Reference: MSDN Magazine, October 2019, Volume 34 Number 10, "Accessing XML Documentation via Reflection"]
|
||||
// https://docs.microsoft.com/en-us/archive/msdn-magazine/2019/october/csharp-accessing-xml-documentation-via-reflection
|
||||
var memberType = memberInfo.MemberType;
|
||||
string key = null;
|
||||
if (memberType.HasFlag(MemberTypes.Field))
|
||||
{
|
||||
var fieldInfo = (FieldInfo)memberInfo;
|
||||
key = "F:" + GetXmlKey(fieldInfo.DeclaringType.FullName) + "." + fieldInfo.Name;
|
||||
}
|
||||
else if (memberType.HasFlag(MemberTypes.Property))
|
||||
{
|
||||
var propertyInfo = (PropertyInfo)memberInfo;
|
||||
key = "P:" + GetXmlKey(propertyInfo.DeclaringType.FullName) + "." + propertyInfo.Name;
|
||||
}
|
||||
else if (memberType.HasFlag(MemberTypes.Event))
|
||||
{
|
||||
var eventInfo = (EventInfo)memberInfo;
|
||||
key = "E:" + GetXmlKey(eventInfo.DeclaringType.FullName) + "." + eventInfo.Name;
|
||||
}
|
||||
else if (memberType.HasFlag(MemberTypes.Constructor))
|
||||
{
|
||||
var constructorInfo = (ConstructorInfo)memberInfo;
|
||||
key = GetXmlKey(constructorInfo);
|
||||
}
|
||||
else if (memberType.HasFlag(MemberTypes.Method))
|
||||
{
|
||||
var methodInfo = (MethodInfo)memberInfo;
|
||||
key = GetXmlKey(methodInfo);
|
||||
}
|
||||
else if (memberType.HasFlag(MemberTypes.TypeInfo) || memberType.HasFlag(MemberTypes.NestedType))
|
||||
{
|
||||
var typeInfo = (TypeInfo)memberInfo;
|
||||
key = "T:" + GetXmlKey(typeInfo.FullName);
|
||||
}
|
||||
if (key != null)
|
||||
xml.TryGetValue(key, out text);
|
||||
|
||||
// Customize tooltips for properties to be more human-readable in UI
|
||||
if (text != null && memberType.HasFlag(MemberTypes.Property) && text.StartsWith("Gets or sets ", StringComparison.Ordinal))
|
||||
{
|
||||
text = text.Substring(13);
|
||||
unsafe
|
||||
{
|
||||
fixed (char* e = text)
|
||||
e[0] = char.ToUpper(e[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
var xmlDoc = DebugCommands.GetXml(member.Type);
|
||||
if (xmlDoc != null)
|
||||
text = xmlDoc;
|
||||
}
|
||||
|
||||
_memberCache.Add(member, text);
|
||||
return text;
|
||||
}
|
||||
|
||||
// [Reference: MSDN Magazine, October 2019, Volume 34 Number 10, "Accessing XML Documentation via Reflection"]
|
||||
// https://docs.microsoft.com/en-us/archive/msdn-magazine/2019/october/csharp-accessing-xml-documentation-via-reflection
|
||||
|
||||
private string GetXmlKey(MethodInfo methodInfo)
|
||||
{
|
||||
var typeGenericMap = new Dictionary<string, int>();
|
||||
var methodGenericMap = new Dictionary<string, int>();
|
||||
ParameterInfo[] parameterInfos = methodInfo.GetParameters();
|
||||
|
||||
if (methodInfo.DeclaringType.IsGenericType)
|
||||
{
|
||||
var methods = methodInfo.DeclaringType.GetGenericTypeDefinition().GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
methodInfo = methods.First(x => x.MetadataToken == methodInfo.MetadataToken);
|
||||
}
|
||||
|
||||
Type[] typeGenericArguments = methodInfo.DeclaringType.GetGenericArguments();
|
||||
for (int i = 0; i < typeGenericArguments.Length; i++)
|
||||
{
|
||||
Type typeGeneric = typeGenericArguments[i];
|
||||
typeGenericMap[typeGeneric.Name] = i;
|
||||
}
|
||||
|
||||
Type[] methodGenericArguments = methodInfo.GetGenericArguments();
|
||||
for (int i = 0; i < methodGenericArguments.Length; i++)
|
||||
{
|
||||
Type methodGeneric = methodGenericArguments[i];
|
||||
methodGenericMap[methodGeneric.Name] = i;
|
||||
}
|
||||
|
||||
string declarationTypeString = GetXmlKey(methodInfo.DeclaringType, false, typeGenericMap, methodGenericMap);
|
||||
string memberNameString = methodInfo.Name;
|
||||
string methodGenericArgumentsString = methodGenericMap.Count > 0 ? "``" + methodGenericMap.Count : string.Empty;
|
||||
string parametersString = parameterInfos.Length > 0 ? "(" + string.Join(",", methodInfo.GetParameters().Select(x => GetXmlKey(x.ParameterType, true, typeGenericMap, methodGenericMap))) + ")" : string.Empty;
|
||||
|
||||
string key = "M:" + declarationTypeString + "." + memberNameString + methodGenericArgumentsString + parametersString;
|
||||
if (methodInfo.Name is "op_Implicit" || methodInfo.Name is "op_Explicit")
|
||||
{
|
||||
key += "~" + GetXmlKey(methodInfo.ReturnType, true, typeGenericMap, methodGenericMap);
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
private string GetXmlKey(ConstructorInfo constructorInfo)
|
||||
{
|
||||
var typeGenericMap = new Dictionary<string, int>();
|
||||
var methodGenericMap = new Dictionary<string, int>();
|
||||
ParameterInfo[] parameterInfos = constructorInfo.GetParameters();
|
||||
|
||||
Type[] typeGenericArguments = constructorInfo.DeclaringType.GetGenericArguments();
|
||||
for (int i = 0; i < typeGenericArguments.Length; i++)
|
||||
{
|
||||
Type typeGeneric = typeGenericArguments[i];
|
||||
typeGenericMap[typeGeneric.Name] = i;
|
||||
}
|
||||
|
||||
string declarationTypeString = GetXmlKey(constructorInfo.DeclaringType, false, typeGenericMap, methodGenericMap);
|
||||
string methodGenericArgumentsString = methodGenericMap.Count > 0 ? "``" + methodGenericMap.Count : string.Empty;
|
||||
string parametersString = parameterInfos.Length > 0 ? "(" + string.Join(",", constructorInfo.GetParameters().Select(x => GetXmlKey(x.ParameterType, true, typeGenericMap, methodGenericMap))) + ")" : string.Empty;
|
||||
|
||||
return "M:" + declarationTypeString + "." + "#ctor" + methodGenericArgumentsString + parametersString;
|
||||
}
|
||||
|
||||
internal static string GetXmlKey(Type type, bool isMethodParameter, Dictionary<string, int> typeGenericMap, Dictionary<string, int> methodGenericMap)
|
||||
{
|
||||
if (type.IsGenericParameter)
|
||||
{
|
||||
if (methodGenericMap.TryGetValue(type.Name, out var methodIndex))
|
||||
return "``" + methodIndex;
|
||||
if (typeGenericMap.TryGetValue(type.Name, out var typeKey))
|
||||
return "`" + typeKey;
|
||||
return "`";
|
||||
}
|
||||
if (type.HasElementType)
|
||||
{
|
||||
string elementTypeString = GetXmlKey(type.GetElementType(), isMethodParameter, typeGenericMap, methodGenericMap);
|
||||
if (type.IsPointer)
|
||||
return elementTypeString + "*";
|
||||
if (type.IsByRef)
|
||||
return elementTypeString + "@";
|
||||
if (type.IsArray)
|
||||
{
|
||||
int rank = type.GetArrayRank();
|
||||
string arrayDimensionsString = rank > 1 ? "[" + string.Join(",", Enumerable.Repeat("0:", rank)) + "]" : "[]";
|
||||
return elementTypeString + arrayDimensionsString;
|
||||
}
|
||||
throw new Exception();
|
||||
}
|
||||
|
||||
string prefaceString = type.IsNested ? GetXmlKey(type.DeclaringType, isMethodParameter, typeGenericMap, methodGenericMap) + "." : type.Namespace + ".";
|
||||
string typeNameString = isMethodParameter ? Regex.Replace(type.Name, @"`\d+", string.Empty) : type.Name;
|
||||
string genericArgumentsString = type.IsGenericType && isMethodParameter ? "{" + string.Join(",", type.GetGenericArguments().Select(argument => GetXmlKey(argument, true, typeGenericMap, methodGenericMap))) + "}" : string.Empty;
|
||||
return prefaceString + typeNameString + genericArgumentsString;
|
||||
}
|
||||
|
||||
private static string GetXmlKey(string typeFullNameString)
|
||||
{
|
||||
return Regex.Replace(typeFullNameString, @"\[.*\]", string.Empty).Replace('+', '.');
|
||||
}
|
||||
|
||||
private static string FilterWhitespaces(string str)
|
||||
{
|
||||
if (str.Contains(" ", StringComparison.Ordinal))
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
var prev = str[0];
|
||||
sb.Append(prev);
|
||||
for (int i = 1; i < str.Length; i++)
|
||||
{
|
||||
var c = str[i];
|
||||
if (prev != ' ' || c != ' ')
|
||||
{
|
||||
sb.Append(c);
|
||||
}
|
||||
prev = c;
|
||||
}
|
||||
str = sb.ToString();
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
private Dictionary<string, string> GetXmlDocs(Assembly assembly)
|
||||
{
|
||||
if (!_xmlCache.TryGetValue(assembly, out var result))
|
||||
{
|
||||
Profiler.BeginEvent("GetXmlDocs");
|
||||
|
||||
var assemblyPath = Utils.GetAssemblyLocation(assembly);
|
||||
var assemblyName = assembly.GetName().Name;
|
||||
var xmlFilePath = Path.ChangeExtension(assemblyPath, ".xml");
|
||||
if (!File.Exists(assemblyPath) && !string.IsNullOrEmpty(assemblyPath))
|
||||
{
|
||||
var uri = new UriBuilder(assemblyPath);
|
||||
var path = Uri.UnescapeDataString(uri.Path);
|
||||
xmlFilePath = Path.Combine(Path.GetDirectoryName(path), assemblyName + ".xml");
|
||||
}
|
||||
if (File.Exists(xmlFilePath))
|
||||
{
|
||||
Profiler.BeginEvent(assemblyName);
|
||||
try
|
||||
{
|
||||
// Parse xml documentation
|
||||
using (var xmlReader = XmlReader.Create(new StreamReader(xmlFilePath)))
|
||||
{
|
||||
result = new Dictionary<string, string>();
|
||||
while (xmlReader.Read())
|
||||
{
|
||||
if (xmlReader.NodeType == XmlNodeType.Element && string.Equals(xmlReader.Name, "member", StringComparison.Ordinal))
|
||||
{
|
||||
string rawName = xmlReader["name"];
|
||||
var memberReader = xmlReader.ReadSubtree();
|
||||
if (memberReader.ReadToDescendant("summary"))
|
||||
{
|
||||
// Remove <see cref=""/> and replace them with the captured group (the content of the cref). Additionally, getting rid of prefixes
|
||||
const string crefPattern = @"<see\s+cref=""(?:[A-Z]:FlaxEngine\.)?([^""]+)""\s*\/>";
|
||||
result[rawName] = Regex.Replace(memberReader.ReadInnerXml(), crefPattern, "$1").Replace('\n', ' ').Trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore errors
|
||||
}
|
||||
Profiler.EndEvent();
|
||||
}
|
||||
|
||||
_xmlCache[assembly] = result;
|
||||
Profiler.EndEvent();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private void OnTypesCleared()
|
||||
{
|
||||
_typeCache.Clear();
|
||||
_memberCache.Clear();
|
||||
_xmlCache.Clear();
|
||||
DebugCommands.ClearXml();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -465,6 +465,8 @@ namespace FlaxEditor.Modules
|
||||
|
||||
internal void ProgressFailed(string message)
|
||||
{
|
||||
if (StatusBar == null)
|
||||
return;
|
||||
_progressFailed = true;
|
||||
StatusBar.StatusColor = Style.Current.Statusbar.Failed;
|
||||
StatusBar.Text = message;
|
||||
|
||||
@@ -347,6 +347,14 @@ namespace FlaxEditor.Options
|
||||
[EditorDisplay("Viewport"), EditorOrder(1550)]
|
||||
public InputBinding Down = new InputBinding(KeyboardKeys.Q);
|
||||
|
||||
[DefaultValue(typeof(InputBinding), "C")]
|
||||
[EditorDisplay("Viewport"), EditorOrder(1551)]
|
||||
public InputBinding ZoomIn = new InputBinding(KeyboardKeys.C);
|
||||
|
||||
[DefaultValue(typeof(InputBinding), "Z")]
|
||||
[EditorDisplay("Viewport"), EditorOrder(1552)]
|
||||
public InputBinding ZoomOut = new InputBinding(KeyboardKeys.Z);
|
||||
|
||||
[DefaultValue(typeof(InputBinding), "None")]
|
||||
[EditorDisplay("Viewport", "Toggle Camera Rotation"), EditorOrder(1560)]
|
||||
public InputBinding CameraToggleRotation = new InputBinding(KeyboardKeys.None);
|
||||
|
||||
@@ -807,8 +807,8 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
},
|
||||
Elements = new[]
|
||||
{
|
||||
NodeElementArchetype.Factory.Input(0, "A", true, null, 0),
|
||||
NodeElementArchetype.Factory.Input(1, "B", true, null, 1),
|
||||
NodeElementArchetype.Factory.Input(0, "UV", true, null, 0),
|
||||
NodeElementArchetype.Factory.Input(1, "Center", true, null, 1),
|
||||
NodeElementArchetype.Factory.Input(2, "Radius", true, typeof(float), 2, 0),
|
||||
NodeElementArchetype.Factory.Input(3, "Hardness", true, typeof(float), 3, 1),
|
||||
NodeElementArchetype.Factory.Input(4, "Invert", true, typeof(bool), 4, 2),
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace FlaxEditor.Tools.Foliage
|
||||
private int _selectedInstanceIndex = -1;
|
||||
|
||||
/// <summary>
|
||||
/// The foliage painting gizmo.
|
||||
/// The foliage editing gizmo.
|
||||
/// </summary>
|
||||
public EditFoliageGizmo Gizmo;
|
||||
|
||||
@@ -66,8 +66,6 @@ namespace FlaxEditor.Tools.Foliage
|
||||
base.Init(owner);
|
||||
|
||||
Gizmo = new EditFoliageGizmo(owner, this);
|
||||
SelectionOutline = FlaxEngine.Object.New<EditFoliageSelectionOutline>();
|
||||
SelectionOutline.GizmoMode = this;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -84,6 +82,11 @@ namespace FlaxEditor.Tools.Foliage
|
||||
base.OnActivated();
|
||||
|
||||
Owner.Gizmos.Active = Gizmo;
|
||||
if (SelectionOutline == null)
|
||||
{
|
||||
SelectionOutline = FlaxEngine.Object.New<EditFoliageSelectionOutline>();
|
||||
SelectionOutline.GizmoMode = this;
|
||||
}
|
||||
((MainEditorGizmoViewport)Owner).OverrideSelectionOutline(SelectionOutline);
|
||||
SelectedInstanceIndex = -1;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ using FlaxEngine;
|
||||
namespace FlaxEditor.Tools.Foliage
|
||||
{
|
||||
/// <summary>
|
||||
/// The custom outline for drawing the selected foliage instances outlines.
|
||||
/// The custom outline for drawing the selected foliage instance.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEditor.Gizmo.SelectionOutline" />
|
||||
[HideInEditor]
|
||||
|
||||
@@ -5,7 +5,6 @@ using System.Collections.Generic;
|
||||
using FlaxEditor.GUI.Tabs;
|
||||
using FlaxEditor.Modules;
|
||||
using FlaxEditor.SceneGraph.Actors;
|
||||
using FlaxEditor.Viewport.Modes;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
|
||||
@@ -214,7 +213,7 @@ namespace FlaxEditor.Tools.Foliage
|
||||
|
||||
private void InitSculptMode()
|
||||
{
|
||||
var tab = _modes.AddTab(FoliageTypes = new FoliageTypesTab(this));
|
||||
var tab = _modes.AddTab(FoliageTypes = new FoliageTypesTab(this, Editor.Windows.EditWin.Viewport.EditFoliageTypesGizmo));
|
||||
tab.Selected += OnTabSelected;
|
||||
}
|
||||
|
||||
@@ -251,7 +250,7 @@ namespace FlaxEditor.Tools.Foliage
|
||||
switch (_modes.SelectedTabIndex)
|
||||
{
|
||||
case 0:
|
||||
Editor.Windows.EditWin.Viewport.Gizmos.SetActiveMode<NoGizmoMode>();
|
||||
Editor.Windows.EditWin.Viewport.Gizmos.SetActiveMode<FoliageTypesGizmoMode>();
|
||||
break;
|
||||
case 1:
|
||||
Editor.Windows.EditWin.Viewport.Gizmos.SetActiveMode<PaintFoliageGizmoMode>();
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
// Copyright (c) Wojciech Figat. All rights reserved.
|
||||
|
||||
using FlaxEditor.Gizmo;
|
||||
|
||||
namespace FlaxEditor.Tools.Foliage
|
||||
{
|
||||
/// <summary>
|
||||
/// Gizmo for editing foliage types.
|
||||
/// </summary>
|
||||
public sealed class FoliageTypesGizmo : GizmoBase
|
||||
{
|
||||
/// <summary>
|
||||
/// The parent mode.
|
||||
/// </summary>
|
||||
public readonly FoliageTypesGizmoMode GizmoMode;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="EditFoliageGizmo"/> class.
|
||||
/// </summary>
|
||||
/// <param name="owner">The owner.</param>
|
||||
/// <param name="mode">The mode.</param>
|
||||
public FoliageTypesGizmo(IGizmoOwner owner, FoliageTypesGizmoMode mode)
|
||||
: base(owner)
|
||||
{
|
||||
GizmoMode = mode;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Pick()
|
||||
{
|
||||
// Get mouse ray and try to hit foliage instance
|
||||
var foliage = GizmoMode.SelectedFoliage;
|
||||
if (!foliage)
|
||||
return;
|
||||
var ray = Owner.MouseRay;
|
||||
if (foliage.Intersects(ref ray, out _, out _, out var instanceIndex))
|
||||
{
|
||||
// Select hit instance type
|
||||
var instance = foliage.GetInstance(instanceIndex);
|
||||
GizmoMode.SelectedTypeIndex = instance.Type;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
// Copyright (c) Wojciech Figat. All rights reserved.
|
||||
|
||||
using FlaxEditor.Gizmo;
|
||||
using FlaxEditor.SceneGraph.Actors;
|
||||
using FlaxEditor.Viewport;
|
||||
using FlaxEditor.Viewport.Modes;
|
||||
|
||||
namespace FlaxEditor.Tools.Foliage
|
||||
{
|
||||
/// <summary>
|
||||
/// Foliage types editing mode.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEditor.Viewport.Modes.EditorGizmoMode" />
|
||||
public class FoliageTypesGizmoMode : EditorGizmoMode
|
||||
{
|
||||
/// <summary>
|
||||
/// The foliage types gizmo.
|
||||
/// </summary>
|
||||
public FoliageTypesGizmo Gizmo;
|
||||
|
||||
/// <summary>
|
||||
/// The foliage type editing selection outline.
|
||||
/// </summary>
|
||||
public FoliageTypesOutline SelectionOutline;
|
||||
|
||||
/// <summary>
|
||||
/// The selected foliage type index.
|
||||
/// </summary>
|
||||
public int SelectedTypeIndex
|
||||
{
|
||||
get => Editor.Instance.Windows.ToolboxWin.Foliage.SelectedFoliageTypeIndex;
|
||||
set => Editor.Instance.Windows.ToolboxWin.Foliage.SelectedFoliageTypeIndex = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the selected foliage actor (see <see cref="Modules.SceneEditingModule"/>).
|
||||
/// </summary>
|
||||
public FlaxEngine.Foliage SelectedFoliage
|
||||
{
|
||||
get
|
||||
{
|
||||
var sceneEditing = Editor.Instance.SceneEditing;
|
||||
var foliageNode = sceneEditing.SelectionCount == 1 ? sceneEditing.Selection[0] as FoliageNode : null;
|
||||
return (FlaxEngine.Foliage)foliageNode?.Actor;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Init(IGizmoOwner owner)
|
||||
{
|
||||
base.Init(owner);
|
||||
|
||||
Gizmo = new FoliageTypesGizmo(owner, this);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Dispose()
|
||||
{
|
||||
FlaxEngine.Object.Destroy(ref SelectionOutline);
|
||||
|
||||
base.Dispose();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnActivated()
|
||||
{
|
||||
base.OnActivated();
|
||||
|
||||
Owner.Gizmos.Active = Gizmo;
|
||||
if (SelectionOutline == null)
|
||||
{
|
||||
SelectionOutline = FlaxEngine.Object.New<FoliageTypesOutline>();
|
||||
SelectionOutline.GizmoMode = this;
|
||||
}
|
||||
((MainEditorGizmoViewport)Owner).OverrideSelectionOutline(SelectionOutline);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnDeactivated()
|
||||
{
|
||||
((MainEditorGizmoViewport)Owner).OverrideSelectionOutline(null);
|
||||
|
||||
base.OnDeactivated();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
// Copyright (c) Wojciech Figat. All rights reserved.
|
||||
|
||||
using FlaxEditor.Gizmo;
|
||||
using FlaxEngine;
|
||||
|
||||
namespace FlaxEditor.Tools.Foliage
|
||||
{
|
||||
/// <summary>
|
||||
/// The custom outline for drawing the selected foliage type.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEditor.Gizmo.SelectionOutline" />
|
||||
[HideInEditor]
|
||||
public class FoliageTypesOutline : SelectionOutline
|
||||
{
|
||||
/// <summary>
|
||||
/// The parent mode.
|
||||
/// </summary>
|
||||
public FoliageTypesGizmoMode GizmoMode;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanRender()
|
||||
{
|
||||
if (!HasDataReady)
|
||||
return false;
|
||||
|
||||
var foliage = GizmoMode.SelectedFoliage;
|
||||
if (!foliage)
|
||||
return false;
|
||||
var typeIndex = GizmoMode.SelectedTypeIndex;
|
||||
if (typeIndex < 0 || typeIndex >= foliage.FoliageTypesCount)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Render(GPUContext context, ref RenderContext renderContext, GPUTexture input, GPUTexture output)
|
||||
{
|
||||
base.Render(context, ref renderContext, input, output);
|
||||
|
||||
// Restore debug option
|
||||
var foliage = GizmoMode.SelectedFoliage;
|
||||
if (foliage)
|
||||
foliage._drawFoliageType = -1;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void DrawSelectionDepth(GPUContext context, SceneRenderTask task, GPUTexture customDepth)
|
||||
{
|
||||
var foliage = GizmoMode.SelectedFoliage;
|
||||
if (!foliage)
|
||||
return;
|
||||
var typeIndex = GizmoMode.SelectedTypeIndex;
|
||||
if (typeIndex < 0 || typeIndex >= foliage.FoliageTypesCount)
|
||||
return;
|
||||
|
||||
// Draw instances of the given type
|
||||
foliage._drawFoliageType = typeIndex;
|
||||
_actors.Add(foliage);
|
||||
Renderer.DrawSceneDepth(context, task, customDepth, _actors);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -334,7 +334,8 @@ namespace FlaxEditor.Tools.Foliage
|
||||
/// Initializes a new instance of the <see cref="FoliageTypesTab"/> class.
|
||||
/// </summary>
|
||||
/// <param name="tab">The parent tab.</param>
|
||||
public FoliageTypesTab(FoliageTab tab)
|
||||
/// <param name="mode">The gizmo mode.</param>
|
||||
public FoliageTypesTab(FoliageTab tab, FoliageTypesGizmoMode mode)
|
||||
: base("Foliage Types")
|
||||
{
|
||||
Tab = tab;
|
||||
|
||||
@@ -21,6 +21,7 @@ namespace FlaxEditor.Viewport.Cameras
|
||||
private Transform _startMove;
|
||||
private Transform _endMove;
|
||||
private float _moveStartTime = -1;
|
||||
private float _additionalFOV;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this viewport is animating movement.
|
||||
@@ -32,6 +33,15 @@ namespace FlaxEditor.Viewport.Cameras
|
||||
/// </summary>
|
||||
public Vector3 TargetPoint = new Vector3(-200);
|
||||
|
||||
/// <summary>
|
||||
/// Additional field of view used for zooming the camera in and out.
|
||||
/// </summary>
|
||||
public float AdditionalZoomFOV
|
||||
{
|
||||
get => _additionalFOV;
|
||||
private set => _additionalFOV = Mathf.Clamp(value, 5 - Viewport.FieldOfView, 160f - Viewport.FieldOfView);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets view.
|
||||
/// </summary>
|
||||
@@ -216,7 +226,7 @@ namespace FlaxEditor.Viewport.Cameras
|
||||
pitch += mouseDelta.Y;
|
||||
}
|
||||
|
||||
// Zoom in/out
|
||||
// Zoom in/out with mouse wheel
|
||||
if (input.IsZooming && !input.IsRotating)
|
||||
{
|
||||
position += forward * (Viewport.MouseWheelZoomSpeedFactor * input.MouseWheelDelta * 25.0f);
|
||||
@@ -226,6 +236,17 @@ namespace FlaxEditor.Viewport.Cameras
|
||||
}
|
||||
}
|
||||
|
||||
// Zoom in and out by changing FOV
|
||||
if (input.IsRotating && (input.ZoomInDown || input.ZoomOutDown))
|
||||
{
|
||||
float delta = (input.ZoomInDown ? -0.8f : 0.8f);
|
||||
AdditionalZoomFOV += delta;
|
||||
}
|
||||
else if (!input.IsRotating)
|
||||
{
|
||||
AdditionalZoomFOV = 0f;
|
||||
}
|
||||
|
||||
// Move camera with the gizmo
|
||||
if (input.IsOrbiting && isUsingGizmo)
|
||||
{
|
||||
|
||||
@@ -51,6 +51,16 @@ namespace FlaxEditor.Viewport
|
||||
/// </summary>
|
||||
public bool IsOrbiting;
|
||||
|
||||
/// <summary>
|
||||
/// The zoom in state.
|
||||
/// </summary>
|
||||
public bool ZoomInDown;
|
||||
|
||||
/// <summary>
|
||||
/// The zoom out state.
|
||||
/// </summary>
|
||||
public bool ZoomOutDown;
|
||||
|
||||
/// <summary>
|
||||
/// The is control down flag.
|
||||
/// </summary>
|
||||
@@ -109,6 +119,10 @@ namespace FlaxEditor.Viewport
|
||||
IsAltDown = window.GetKey(KeyboardKeys.Alt);
|
||||
WasAltDownBefore = prevInput.WasAltDownBefore || prevInput.IsAltDown;
|
||||
|
||||
InputOptions inputOptions = Editor.Instance.Options.Options.Input;
|
||||
ZoomInDown = window.GetKey(inputOptions.ZoomIn.Key);
|
||||
ZoomOutDown = window.GetKey(inputOptions.ZoomOut.Key);
|
||||
|
||||
IsMouseRightDown = useMouse && window.GetMouseButton(MouseButton.Right);
|
||||
IsMouseMiddleDown = useMouse && window.GetMouseButton(MouseButton.Middle);
|
||||
IsMouseLeftDown = useMouse && window.GetMouseButton(MouseButton.Left);
|
||||
@@ -1433,7 +1447,10 @@ namespace FlaxEditor.Viewport
|
||||
else
|
||||
{
|
||||
float aspect = Width / Height;
|
||||
Matrix.PerspectiveFov(_fieldOfView * Mathf.DegreesToRadians, aspect, _nearPlane, _farPlane, out result);
|
||||
float fov = _fieldOfView;
|
||||
if (_camera is FPSCamera fpsCam)
|
||||
fov += fpsCam.AdditionalZoomFOV;
|
||||
Matrix.PerspectiveFov(fov * Mathf.DegreesToRadians, aspect, _nearPlane, _farPlane, out result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1497,9 +1514,14 @@ namespace FlaxEditor.Viewport
|
||||
Matrix.Multiply(ref v, ref p, out var ivp);
|
||||
ivp.Invert();
|
||||
|
||||
// Create near and far points
|
||||
var nearPoint = new Vector3(mousePosition, _nearPlane);
|
||||
var farPoint = new Vector3(mousePosition, _farPlane);
|
||||
// Create near and far points, with device depth of 1 and 0 respectively
|
||||
#if REVERSE_Z
|
||||
var nearPoint = new Vector3(mousePosition, 1.0f);
|
||||
var farPoint = new Vector3(mousePosition, 0.0f);
|
||||
#else
|
||||
var nearPoint = new Vector3(mousePosition, 0.0f);
|
||||
var farPoint = new Vector3(mousePosition, 1.0f);
|
||||
#endif
|
||||
viewport.Unproject(ref nearPoint, ref ivp, out nearPoint);
|
||||
viewport.Unproject(ref farPoint, ref ivp, out farPoint);
|
||||
|
||||
|
||||
@@ -179,12 +179,17 @@ namespace FlaxEditor.Viewport
|
||||
public Tools.Terrain.EditTerrainGizmoMode EditTerrainGizmo;
|
||||
|
||||
/// <summary>
|
||||
/// The paint foliage gizmo.
|
||||
/// The edit foliage types gizmo.
|
||||
/// </summary>
|
||||
public Tools.Foliage.FoliageTypesGizmoMode EditFoliageTypesGizmo;
|
||||
|
||||
/// <summary>
|
||||
/// The paint foliage instances gizmo.
|
||||
/// </summary>
|
||||
public Tools.Foliage.PaintFoliageGizmoMode PaintFoliageGizmo;
|
||||
|
||||
/// <summary>
|
||||
/// The edit foliage gizmo.
|
||||
/// The edit foliage instances gizmo.
|
||||
/// </summary>
|
||||
public Tools.Foliage.EditFoliageGizmoMode EditFoliageGizmo;
|
||||
|
||||
@@ -276,6 +281,7 @@ namespace FlaxEditor.Viewport
|
||||
Gizmos.AddMode(SculptTerrainGizmo = new Tools.Terrain.SculptTerrainGizmoMode());
|
||||
Gizmos.AddMode(PaintTerrainGizmo = new Tools.Terrain.PaintTerrainGizmoMode());
|
||||
Gizmos.AddMode(EditTerrainGizmo = new Tools.Terrain.EditTerrainGizmoMode());
|
||||
Gizmos.AddMode(EditFoliageTypesGizmo = new Tools.Foliage.FoliageTypesGizmoMode());
|
||||
Gizmos.AddMode(PaintFoliageGizmo = new Tools.Foliage.PaintFoliageGizmoMode());
|
||||
Gizmos.AddMode(EditFoliageGizmo = new Tools.Foliage.EditFoliageGizmoMode());
|
||||
|
||||
@@ -343,10 +349,12 @@ namespace FlaxEditor.Viewport
|
||||
/// <param name="customSelectionOutline">The custom selection outline or null if use default one.</param>
|
||||
public void OverrideSelectionOutline(SelectionOutline customSelectionOutline)
|
||||
{
|
||||
if (Task == null)
|
||||
return;
|
||||
|
||||
if (_customSelectionOutline != null)
|
||||
{
|
||||
Task.RemoveCustomPostFx(_customSelectionOutline);
|
||||
Object.Destroy(ref _customSelectionOutline);
|
||||
Task.AddCustomPostFx(customSelectionOutline ? customSelectionOutline : SelectionOutline);
|
||||
}
|
||||
else if (customSelectionOutline != null)
|
||||
@@ -530,7 +538,7 @@ namespace FlaxEditor.Viewport
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Toggles game view view mode on or off.
|
||||
/// Toggles view mode on/off.
|
||||
/// </summary>
|
||||
public void ToggleGameView()
|
||||
{
|
||||
@@ -547,9 +555,9 @@ namespace FlaxEditor.Viewport
|
||||
// Set flags & values
|
||||
Task.ViewFlags = _gameViewActive ? _preGameViewFlags : ViewFlags.DefaultGame;
|
||||
Task.ViewMode = _gameViewActive ? _preGameViewViewMode : ViewMode.Default;
|
||||
ShowFpsCounter = _gameViewActive ? _gameViewWasFpsCounterShown : false;
|
||||
ShowNavigation = _gameViewActive ? _gameViewWasNavigationShown : false;
|
||||
Grid.Enabled = _gameViewActive ? _gameViewWasGridShown : false;
|
||||
ShowFpsCounter = _gameViewActive && _gameViewWasFpsCounterShown;
|
||||
ShowNavigation = _gameViewActive && _gameViewWasNavigationShown;
|
||||
Grid.Enabled = _gameViewActive && _gameViewWasGridShown;
|
||||
|
||||
_gameViewActive = !_gameViewActive;
|
||||
|
||||
@@ -730,6 +738,20 @@ namespace FlaxEditor.Viewport
|
||||
base.OnLeftMouseButtonUp();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnKeyDown(KeyboardKeys key)
|
||||
{
|
||||
if (base.OnKeyDown(key))
|
||||
return true;
|
||||
|
||||
if (key == KeyboardKeys.Escape)
|
||||
{
|
||||
_editor.SceneEditing.Deselect();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnMouseUp(Float2 location, MouseButton button)
|
||||
{
|
||||
@@ -843,8 +865,8 @@ namespace FlaxEditor.Viewport
|
||||
if (_task != null)
|
||||
{
|
||||
// Release if task is not used to save screenshot for project icon
|
||||
ReleaseTaskResources();
|
||||
Object.Destroy(ref _task);
|
||||
ReleaseResources();
|
||||
}
|
||||
|
||||
base.OnDestroy();
|
||||
@@ -860,6 +882,7 @@ namespace FlaxEditor.Viewport
|
||||
_savedTask = _task;
|
||||
_savedBackBuffer = _backBuffer;
|
||||
|
||||
ReleaseTaskResources();
|
||||
_task = null;
|
||||
_backBuffer = null;
|
||||
}
|
||||
@@ -870,20 +893,20 @@ namespace FlaxEditor.Viewport
|
||||
{
|
||||
_savedTask.Enabled = false;
|
||||
Object.Destroy(_savedTask);
|
||||
ReleaseResources();
|
||||
ReleaseTaskResources();
|
||||
_savedTask = null;
|
||||
}
|
||||
Object.Destroy(ref _savedBackBuffer);
|
||||
}
|
||||
|
||||
private void ReleaseResources()
|
||||
private void ReleaseTaskResources()
|
||||
{
|
||||
if (Task)
|
||||
if (_task)
|
||||
{
|
||||
Task.RemoveCustomPostFx(SelectionOutline);
|
||||
Task.RemoveCustomPostFx(EditorPrimitives);
|
||||
Task.RemoveCustomPostFx(_editorSpritesRenderer);
|
||||
Task.RemoveCustomPostFx(_customSelectionOutline);
|
||||
_task.RemoveCustomPostFx(SelectionOutline);
|
||||
_task.RemoveCustomPostFx(EditorPrimitives);
|
||||
_task.RemoveCustomPostFx(_editorSpritesRenderer);
|
||||
_task.RemoveCustomPostFx(_customSelectionOutline);
|
||||
}
|
||||
Object.Destroy(ref SelectionOutline);
|
||||
Object.Destroy(ref EditorPrimitives);
|
||||
|
||||
@@ -233,7 +233,7 @@ namespace FlaxEditor.Windows.Assets
|
||||
var materialInstance = proxy.Window?.Asset;
|
||||
if (materialInstance == null)
|
||||
{
|
||||
layout.Label("No parameters");
|
||||
layout.Label("No parameters", TextAlignment.Center);
|
||||
return;
|
||||
}
|
||||
if (!materialInstance.IsLoaded || (materialInstance.BaseMaterial && !materialInstance.BaseMaterial.IsLoaded))
|
||||
@@ -246,7 +246,10 @@ namespace FlaxEditor.Windows.Assets
|
||||
base.Initialize(layout);
|
||||
|
||||
if (parameters.Length == 0)
|
||||
{
|
||||
layout.Label("No parameters", TextAlignment.Center);
|
||||
return;
|
||||
}
|
||||
|
||||
var parametersGroup = SurfaceUtils.InitGraphParametersGroup(layout);
|
||||
var settingButton = parametersGroup.AddSettingsButton();
|
||||
|
||||
@@ -176,7 +176,8 @@ namespace FlaxEditor.Windows
|
||||
if (Owner != null && (!Owner._searchPopup?.Visible ?? true))
|
||||
{
|
||||
// Focus back the input field as user want to modify command from history
|
||||
Owner._searchPopup?.Hide();
|
||||
Owner.HideHistory();
|
||||
Owner.HideSearch();
|
||||
Owner.RootWindow.Focus();
|
||||
Owner.Focus();
|
||||
Owner.OnKeyDown(key);
|
||||
@@ -209,6 +210,7 @@ namespace FlaxEditor.Windows
|
||||
|
||||
private OutputLogWindow _window;
|
||||
private ItemsListContextMenu _searchPopup;
|
||||
private ItemsListContextMenu _historyPopup;
|
||||
private bool _isSettingText;
|
||||
|
||||
public CommandLineBox(float x, float y, float width, OutputLogWindow window)
|
||||
@@ -226,6 +228,24 @@ namespace FlaxEditor.Windows
|
||||
_isSettingText = false;
|
||||
}
|
||||
|
||||
private void HideSearch()
|
||||
{
|
||||
if (_searchPopup != null)
|
||||
{
|
||||
_searchPopup.Hide();
|
||||
_searchPopup = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void HideHistory()
|
||||
{
|
||||
if (_historyPopup != null)
|
||||
{
|
||||
_historyPopup.Dispose();
|
||||
_historyPopup = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void ShowPopup(ref ItemsListContextMenu cm, IEnumerable<string> commands, string searchText = null)
|
||||
{
|
||||
if (cm == null)
|
||||
@@ -295,7 +315,7 @@ namespace FlaxEditor.Windows
|
||||
private void OnRootWindowLostFocus()
|
||||
{
|
||||
// Prevent popup from staying active when editor window looses focus
|
||||
_searchPopup?.Hide();
|
||||
HideSearch();
|
||||
if (RootWindow?.Window != null)
|
||||
RootWindow.Window.LostFocus -= OnRootWindowLostFocus;
|
||||
}
|
||||
@@ -330,6 +350,7 @@ namespace FlaxEditor.Windows
|
||||
if (isWhitespaceOnly)
|
||||
DebugCommands.GetAllCommands(out commands);
|
||||
|
||||
HideHistory();
|
||||
ShowPopup(ref _searchPopup, isWhitespaceOnly ? commands : matches, text);
|
||||
|
||||
if (isWhitespaceOnly)
|
||||
@@ -342,7 +363,7 @@ namespace FlaxEditor.Windows
|
||||
return;
|
||||
}
|
||||
}
|
||||
_searchPopup?.Hide();
|
||||
HideSearch();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -353,7 +374,8 @@ namespace FlaxEditor.Windows
|
||||
case KeyboardKeys.Return:
|
||||
{
|
||||
// Run command
|
||||
_searchPopup?.Hide();
|
||||
HideSearch();
|
||||
HideHistory();
|
||||
var command = Text.Trim();
|
||||
if (command.Length == 0)
|
||||
return true;
|
||||
@@ -430,9 +452,8 @@ namespace FlaxEditor.Windows
|
||||
if (_window._commandHistory != null && _window._commandHistory.Count != 0)
|
||||
{
|
||||
// Show command history popup
|
||||
_searchPopup?.Hide();
|
||||
ItemsListContextMenu cm = null;
|
||||
ShowPopup(ref cm, _window._commandHistory);
|
||||
HideSearch();
|
||||
ShowPopup(ref _historyPopup, _window._commandHistory);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -49,7 +49,7 @@ namespace FlaxEditor.Windows.Profiler
|
||||
{
|
||||
Title = "Draw (GPU)",
|
||||
AnchorPreset = AnchorPresets.HorizontalStretchTop,
|
||||
Offsets = new Margin(0, 0, _drawTimeCPU.Height + 2, 0),
|
||||
Offsets = new Margin(0, 0, _drawTimeCPU.Height + 2, SingleChart.DefaultHeight),
|
||||
FormatSample = v => (Mathf.RoundToInt(v * 10.0f) / 10.0f) + " ms",
|
||||
Parent = mainPanel,
|
||||
};
|
||||
|
||||
@@ -58,6 +58,15 @@ public:
|
||||
return &_knowledge;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the blackboard of a given type.
|
||||
/// </summary>
|
||||
template<typename T>
|
||||
FORCE_INLINE T* GetBlackboard()
|
||||
{
|
||||
return _knowledge.GetBlackboard<T>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the last behavior tree execution result.
|
||||
/// </summary>
|
||||
|
||||
@@ -124,6 +124,18 @@ public:
|
||||
RemoveGoal(T::TypeInitializer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the blackboard of a given type.
|
||||
/// </summary>
|
||||
template<typename T>
|
||||
FORCE_INLINE T* GetBlackboard()
|
||||
{
|
||||
auto* structure = Blackboard.AsStructure<T>();
|
||||
if (structure)
|
||||
return structure;
|
||||
return Cast<T>((ScriptingObject*)Blackboard);
|
||||
}
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Compares two values and returns the comparision result.
|
||||
|
||||
@@ -11,6 +11,18 @@ using FlaxEngine.GUI;
|
||||
|
||||
namespace FlaxEngine
|
||||
{
|
||||
partial class Behavior
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the blackboard of the given type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"> The blackboard type.</typeparam>
|
||||
public T GetBlackboard<T>()
|
||||
{
|
||||
return Knowledge.GetBlackboard<T>();
|
||||
}
|
||||
}
|
||||
|
||||
partial class BehaviorKnowledge
|
||||
{
|
||||
/// <summary>
|
||||
@@ -33,6 +45,16 @@ namespace FlaxEngine
|
||||
{
|
||||
RemoveGoal(typeof(T));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the blackboard of the given type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"> The blackboard type.</typeparam>
|
||||
[Unmanaged]
|
||||
public T GetBlackboard<T>()
|
||||
{
|
||||
return (T)Blackboard;
|
||||
}
|
||||
}
|
||||
|
||||
partial class BehaviorTreeRootNode
|
||||
|
||||
@@ -429,6 +429,9 @@ void Asset::Reload()
|
||||
|
||||
ScopeLock lock(Locker);
|
||||
|
||||
// Cancel any still-running loading task (e.g. if WaitForLoaded timed out)
|
||||
Platform::AtomicStore(&_loadingTask, 0);
|
||||
|
||||
if (IsLoaded())
|
||||
{
|
||||
// Unload current data
|
||||
@@ -611,6 +614,13 @@ bool Asset::onLoad(LoadAssetTask* task)
|
||||
|
||||
Locker.Lock();
|
||||
|
||||
// Re-check after acquiring lock (loading task may have been cleared by Reload, or replaced by a new task)
|
||||
if (Platform::AtomicRead(&_loadingTask) == 0)
|
||||
{
|
||||
Locker.Unlock();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Load asset
|
||||
LoadResult result;
|
||||
#if USE_EDITOR
|
||||
|
||||
@@ -101,9 +101,9 @@ bool Material::CanUseLightmap() const
|
||||
return _materialShader && _materialShader->CanUseLightmap();
|
||||
}
|
||||
|
||||
bool Material::CanUseInstancing(InstancingHandler& handler) const
|
||||
bool Material::CanUseInstancing(const RenderContext& renderContext, InstancingHandler& handler) const
|
||||
{
|
||||
return _materialShader && _materialShader->CanUseInstancing(handler);
|
||||
return _materialShader && _materialShader->CanUseInstancing(renderContext, handler);
|
||||
}
|
||||
|
||||
void Material::Bind(BindParameters& params)
|
||||
|
||||
@@ -48,7 +48,7 @@ public:
|
||||
bool IsReady() const override;
|
||||
DrawPass GetDrawModes() const override;
|
||||
bool CanUseLightmap() const override;
|
||||
bool CanUseInstancing(InstancingHandler& handler) const override;
|
||||
bool CanUseInstancing(const RenderContext& renderContext, InstancingHandler& handler) const override;
|
||||
void Bind(BindParameters& params) override;
|
||||
|
||||
// [ShaderAssetBase]
|
||||
|
||||
@@ -168,9 +168,9 @@ bool MaterialInstance::CanUseLightmap() const
|
||||
return _baseMaterial && _baseMaterial->CanUseLightmap();
|
||||
}
|
||||
|
||||
bool MaterialInstance::CanUseInstancing(InstancingHandler& handler) const
|
||||
bool MaterialInstance::CanUseInstancing(const RenderContext& renderContext, InstancingHandler& handler) const
|
||||
{
|
||||
return _baseMaterial && _baseMaterial->CanUseInstancing(handler);
|
||||
return _baseMaterial && _baseMaterial->CanUseInstancing(renderContext, handler);
|
||||
}
|
||||
|
||||
void MaterialInstance::Bind(BindParameters& params)
|
||||
|
||||
@@ -53,7 +53,7 @@ public:
|
||||
bool IsReady() const override;
|
||||
DrawPass GetDrawModes() const override;
|
||||
bool CanUseLightmap() const override;
|
||||
bool CanUseInstancing(InstancingHandler& handler) const override;
|
||||
bool CanUseInstancing(const RenderContext& renderContext, InstancingHandler& handler) const override;
|
||||
void Bind(BindParameters& params) override;
|
||||
|
||||
protected:
|
||||
|
||||
@@ -27,6 +27,17 @@ API_STRUCT(NoDefault) struct FLAXENGINE_API SceneReference
|
||||
{
|
||||
return ID != other.ID;
|
||||
}
|
||||
|
||||
FORCE_INLINE SceneReference& operator=(const Guid& id)
|
||||
{
|
||||
ID = id;
|
||||
return *this;
|
||||
}
|
||||
|
||||
FORCE_INLINE operator Guid() const
|
||||
{
|
||||
return ID;
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "Engine/Level/Types.h"
|
||||
#include "Engine/Debug/Exceptions/JsonParseException.h"
|
||||
#include "Engine/Profiler/ProfilerCPU.h"
|
||||
#include "Engine/Profiler/ProfilerMemory.h"
|
||||
#if USE_EDITOR
|
||||
#include "Engine/Core/Collections/HashSet.h"
|
||||
#include "Engine/Core/Collections/Dictionary.h"
|
||||
@@ -20,17 +21,66 @@ bool JsonStorageProxy::IsValidExtension(const StringView& extension)
|
||||
return extension == DEFAULT_SCENE_EXTENSION || extension == DEFAULT_PREFAB_EXTENSION || extension == DEFAULT_JSON_EXTENSION;
|
||||
}
|
||||
|
||||
StringAnsiView ParseJsonString(const StringAnsiView& json, int32 start)
|
||||
{
|
||||
while (start < json.Length() && json[start] != '\"')
|
||||
start++;
|
||||
int32 end = start + 1;
|
||||
while (end < json.Length() && json[end] != '\"')
|
||||
end++;
|
||||
return json.Substring(start + 1, end - start - 1);
|
||||
}
|
||||
|
||||
bool JsonStorageProxy::GetAssetInfo(const StringView& path, Guid& resultId, String& resultDataTypeName)
|
||||
{
|
||||
PROFILE_CPU();
|
||||
// TODO: we could just open file and start reading until we find 'ID:..' without parsing whole file - could be much more faster
|
||||
PROFILE_MEM(Content);
|
||||
ZoneText(*path, path.Length());
|
||||
|
||||
// Read the first part of the file to get asset metadata (ID and TypeName)
|
||||
auto file = File::Open(path, FileMode::OpenExisting, FileAccess::Read, FileShare::All);
|
||||
if (!file)
|
||||
return false;
|
||||
Array<byte> fileData;
|
||||
fileData.Resize(256);
|
||||
uint32 read = 0;
|
||||
file->Read(fileData.Get(), fileData.Count(), &read);
|
||||
Delete(file);
|
||||
file = nullptr;
|
||||
if (read != 0)
|
||||
{
|
||||
// Naive Json parsing to get ID and TypeName without full parsing
|
||||
StringAnsiView json((const char*)fileData.Get(), read);
|
||||
StringAnsiView idStart("\"ID\": ");
|
||||
StringAnsiView typenameStart("\"TypeName\": ");
|
||||
bool hasOneOfThem = false;
|
||||
for (int32 i = 0; i < json.Length() - 7; i++)
|
||||
{
|
||||
if (json.Substring(i).StartsWith(idStart))
|
||||
{
|
||||
StringAnsiView value = ParseJsonString(json, i + idStart.Length());
|
||||
if (Guid::Parse(value, resultId))
|
||||
continue;
|
||||
if (hasOneOfThem)
|
||||
return true;
|
||||
hasOneOfThem = true;
|
||||
i += value.Length() + idStart.Length() + 2;
|
||||
}
|
||||
if (json.Substring(i).StartsWith(typenameStart))
|
||||
{
|
||||
StringAnsiView value = ParseJsonString(json, i + typenameStart.Length());
|
||||
resultDataTypeName = String(value);
|
||||
if (hasOneOfThem)
|
||||
return true;
|
||||
hasOneOfThem = true;
|
||||
i += value.Length() + typenameStart.Length() + 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Load file
|
||||
Array<byte> fileData;
|
||||
if (File::ReadAllBytes(path, fileData))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Parse data
|
||||
rapidjson_flax::Document document;
|
||||
@@ -89,11 +139,8 @@ void FindObjectIds(const rapidjson_flax::Value& obj, const rapidjson_flax::Docum
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
bool JsonStorageProxy::ChangeId(const StringView& path, const Guid& newId)
|
||||
{
|
||||
#if USE_EDITOR
|
||||
PROFILE_CPU();
|
||||
|
||||
// Load file
|
||||
@@ -140,8 +187,6 @@ bool JsonStorageProxy::ChangeId(const StringView& path, const Guid& newId)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
#else
|
||||
LOG(Warning, "Editing cooked content is invalid.");
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -30,6 +30,7 @@ public:
|
||||
/// <returns>True if found any asset, otherwise false.</returns>
|
||||
static bool GetAssetInfo(const StringView& path, Guid& resultId, String& resultDataTypeName);
|
||||
|
||||
#if USE_EDITOR
|
||||
/// <summary>
|
||||
/// Changes asset ID.
|
||||
/// </summary>
|
||||
@@ -37,4 +38,5 @@ public:
|
||||
/// <param name="newId">Asset ID to set</param>
|
||||
/// <returns>True if found any asset, otherwise false.</returns>
|
||||
static bool ChangeId(const StringView& path, const Guid& newId);
|
||||
#endif
|
||||
};
|
||||
|
||||
@@ -113,6 +113,8 @@ CreateAssetContext::CreateAssetContext(const StringView& inputPath, const String
|
||||
|
||||
CreateAssetResult CreateAssetContext::Run(const CreateAssetFunction& callback)
|
||||
{
|
||||
PROFILE_CPU();
|
||||
PROFILE_MEM(Content);
|
||||
ASSERT(callback.IsBinded());
|
||||
|
||||
// Call action
|
||||
@@ -207,6 +209,9 @@ void CreateAssetContext::AddMeta(JsonWriter& writer) const
|
||||
|
||||
void CreateAssetContext::ApplyChanges()
|
||||
{
|
||||
PROFILE_CPU();
|
||||
PROFILE_MEM(Content);
|
||||
|
||||
// Get access
|
||||
auto storage = ContentStorageManager::TryGetStorage(TargetAssetPath);
|
||||
if (storage && storage->IsLoaded())
|
||||
@@ -274,6 +279,8 @@ bool AssetsImportingManager::Create(const String& tag, const StringView& outputP
|
||||
|
||||
bool AssetsImportingManager::Import(const StringView& inputPath, const StringView& outputPath, Guid& assetId, void* arg)
|
||||
{
|
||||
PROFILE_CPU();
|
||||
PROFILE_MEM(Content);
|
||||
LOG(Info, "Importing file '{0}' to '{1}'...", inputPath, outputPath);
|
||||
|
||||
// Check if input file exists
|
||||
@@ -347,6 +354,7 @@ String AssetsImportingManager::GetImportPath(const String& path)
|
||||
bool AssetsImportingManager::Create(const Function<CreateAssetResult(CreateAssetContext&)>& callback, const StringView& inputPath, const StringView& outputPath, Guid& assetId, void* arg)
|
||||
{
|
||||
PROFILE_CPU();
|
||||
PROFILE_MEM(Content);
|
||||
ZoneText(*outputPath, outputPath.Length());
|
||||
const auto startTime = Platform::GetTimeSeconds();
|
||||
|
||||
|
||||
@@ -44,12 +44,32 @@ public:
|
||||
IMPLEMENT_ENGINE_SETTINGS_GETTER(BuildSettings, GameCooking);
|
||||
|
||||
#include "Engine/Content/Deprecated.h"
|
||||
|
||||
PRAGMA_DISABLE_DEPRECATION_WARNINGS;
|
||||
|
||||
bool GraphicsSettings::GetUeeHDRProbes() const
|
||||
{
|
||||
return UseHDRProbes;
|
||||
}
|
||||
|
||||
void GraphicsSettings::SetUeeHDRProbes(bool value)
|
||||
{
|
||||
MARK_CONTENT_DEPRECATED();
|
||||
UseHDRProbes = value;
|
||||
DefaultProbeCubemapFormat = value ? ProbeCubemapFormats::R11G11B10 : ProbeCubemapFormats::R8G8B8A8;
|
||||
}
|
||||
|
||||
bool GraphicsSettings::GetUseHDRProbes() const
|
||||
{
|
||||
return UseHDRProbes;
|
||||
}
|
||||
|
||||
void GraphicsSettings::SetUseHDRProbes(bool value)
|
||||
{
|
||||
DefaultProbeCubemapFormat = value ? ProbeCubemapFormats::R11G11B10 : ProbeCubemapFormats::R8G8B8A8;
|
||||
}
|
||||
|
||||
PRAGMA_ENABLE_DEPRECATION_WARNINGS;
|
||||
|
||||
void GraphicsSettings::OnDeserializing(const CallbackContext& context)
|
||||
{
|
||||
#if 0 // TODO: move to Linear color space as default once it's ready for production
|
||||
|
||||
@@ -31,6 +31,22 @@ public:
|
||||
R16G16B16A16,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// The environment probes cubemap texture storage formats.
|
||||
/// </summary>
|
||||
API_ENUM(Attributes = "EnumDisplay(EnumDisplayAttribute.FormatMode.None)")
|
||||
enum class ProbeCubemapFormats
|
||||
{
|
||||
// LDR uncompressed format (32-bit per pixel).
|
||||
R8G8B8A8,
|
||||
// HDR uncompressed format (32-bit per pixel, no alpha).
|
||||
R11G11B10,
|
||||
// HDR compressed format (8-bit per pixel, no alpha). Converted into ASTC/Basis for mobile/web. Realtime probes will fallback to R11G11B10.
|
||||
BC6,
|
||||
// HDR compressed format (8-bit per pixel). Converted into ASTC/Basis for mobile/web. Realtime probes will fallback to R11G11B10.
|
||||
BC7,
|
||||
};
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Enables rendering synchronization with the refresh rate of the display device to avoid "tearing" artifacts.
|
||||
@@ -87,9 +103,16 @@ public:
|
||||
ProbeCubemapResolution DefaultProbeResolution = ProbeCubemapResolution::_128;
|
||||
|
||||
/// <summary>
|
||||
/// If checked, Environment Probes will use HDR texture format. Improves quality in very bright scenes at cost of higher memory usage.
|
||||
/// Environment Probes texture storage format. Controls the quality fo reflections and memory usage of probes data.
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="EditorOrder(1502), EditorDisplay(\"Quality\")")
|
||||
ProbeCubemapFormats DefaultProbeCubemapFormat = ProbeCubemapFormats::BC6;
|
||||
|
||||
/// <summary>
|
||||
/// If checked, Environment Probes will use HDR texture format. Improves quality in very bright scenes at cost of higher memory usage.
|
||||
/// [Deprecated in v1.13]
|
||||
/// </summary>
|
||||
DEPRECATED("Use DefaultProbeCubemapFormat instead.")
|
||||
bool UseHDRProbes = false;
|
||||
|
||||
/// <summary>
|
||||
@@ -127,7 +150,7 @@ public:
|
||||
/// <summary>
|
||||
/// The Global Illumination probes spacing distance (in world units). Defines the quality of the GI resolution. Adjust to 200-500 to improve performance and lower frequency GI data.
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="EditorOrder(2120), Limit(50, 1000), EditorDisplay(\"Global Illumination\")")
|
||||
API_FIELD(Attributes="EditorOrder(2120), Limit(50, 1000), EditorDisplay(\"Global Illumination\"), ValueCategory(Utils.ValueCategory.Distance)")
|
||||
float GIProbesSpacing = 100;
|
||||
|
||||
/// <summary>
|
||||
@@ -175,8 +198,10 @@ private:
|
||||
/// Renamed UeeHDRProbes into UseHDRProbes
|
||||
/// [Deprecated on 12.10.2022, expires on 12.10.2024]
|
||||
/// </summary>
|
||||
API_PROPERTY(Attributes="Serialize, Obsolete, NoUndo") DEPRECATED("Use UseHDRProbes instead.") bool GetUeeHDRProbes() const { return UseHDRProbes; }
|
||||
API_PROPERTY(Attributes="Serialize, Obsolete, NoUndo") DEPRECATED("Use UseHDRProbes instead.") void SetUeeHDRProbes(bool value);
|
||||
API_PROPERTY(Attributes="Serialize, Obsolete, NoUndo") DEPRECATED("Use DefaultProbeCubemapFormat instead.") bool GetUeeHDRProbes() const;
|
||||
API_PROPERTY(Attributes="Serialize, Obsolete, NoUndo") DEPRECATED("Use DefaultProbeCubemapFormat instead.") void SetUeeHDRProbes(bool value);
|
||||
API_PROPERTY(Attributes="Serialize, Obsolete, NoUndo") DEPRECATED("Use DefaultProbeCubemapFormat instead.") bool GetUseHDRProbes() const;
|
||||
API_PROPERTY(Attributes="Serialize, Obsolete, NoUndo") DEPRECATED("Use DefaultProbeCubemapFormat instead.") void SetUseHDRProbes(bool value);
|
||||
API_FUNCTION(Attributes="OnDeserializing", Hidden) void OnDeserializing(const CallbackContext& context);
|
||||
|
||||
public:
|
||||
|
||||
@@ -59,6 +59,11 @@ void BoundingFrustum::SetMatrix(const Matrix& matrix)
|
||||
_pFar.Normal.Z = matrix.M34 - matrix.M33;
|
||||
_pFar.D = matrix.M44 - matrix.M43;
|
||||
_pFar.Normalize();
|
||||
|
||||
#if REVERSE_Z
|
||||
// Swap far and near planes if reverse z
|
||||
Swap(_pFar, _pNear);
|
||||
#endif
|
||||
}
|
||||
|
||||
Plane BoundingFrustum::GetPlane(int32 index) const
|
||||
|
||||
@@ -243,6 +243,11 @@ namespace FlaxEngine
|
||||
far.Normal.Z = matrix.M34 - matrix.M33;
|
||||
far.D = matrix.M44 - matrix.M43;
|
||||
far.Normalize();
|
||||
|
||||
#if REVERSE_Z
|
||||
// Swap far and near planes if reverse z
|
||||
(near, far) = (far, near);
|
||||
#endif
|
||||
}
|
||||
|
||||
private static Vector3 Get3PlanesInterPoint(ref Plane p1, ref Plane p2, ref Plane p3)
|
||||
|
||||
@@ -66,6 +66,12 @@ public:
|
||||
double Values[4][4];
|
||||
double Raw[16];
|
||||
};
|
||||
public:
|
||||
/// <summary>A matrix with all of its components set to zero.</summary>
|
||||
static const Double4x4 Zero;
|
||||
|
||||
/// <summary>The identity matrix.</summary>
|
||||
static const Double4x4 Identity;
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
@@ -73,6 +79,32 @@ public:
|
||||
/// </summary>
|
||||
Double4x4() = default;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Matrix"/> struct.
|
||||
/// </summary>
|
||||
Double4x4(double m11, double m12, double m13, double m14,
|
||||
double m21, double m22, double m23, double m24,
|
||||
double m31, double m32, double m33, double m34,
|
||||
double m41, double m42, double m43, double m44)
|
||||
: M11(m11)
|
||||
, M12(m12)
|
||||
, M13(m13)
|
||||
, M14(m14)
|
||||
, M21(m21)
|
||||
, M22(m22)
|
||||
, M23(m23)
|
||||
, M24(m24)
|
||||
, M31(m31)
|
||||
, M32(m32)
|
||||
, M33(m33)
|
||||
, M34(m34)
|
||||
, M41(m41)
|
||||
, M42(m42)
|
||||
, M43(m43)
|
||||
, M44(m44)
|
||||
{
|
||||
}
|
||||
|
||||
Double4x4(const Matrix& matrix);
|
||||
|
||||
public:
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "../Types/String.h"
|
||||
|
||||
static_assert(sizeof(Matrix) == 4 * 4 * 4, "Invalid Matrix type size.");
|
||||
static_assert(sizeof(Double4x4) == 8 * 4 * 4, "Invalid Double4x4 type size.");
|
||||
|
||||
const Matrix Matrix::Zero(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f);
|
||||
const Matrix Matrix::Identity(
|
||||
@@ -18,6 +19,13 @@ const Matrix Matrix::Identity(
|
||||
0.0f, 0.0f, 1.0f, 0.0f,
|
||||
0.0f, 0.0f, 0.0f, 1.0f);
|
||||
|
||||
const Double4x4 Double4x4::Zero(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
|
||||
const Double4x4 Double4x4::Identity(
|
||||
1.0, 0.0, 0.0, 0.0,
|
||||
0.0, 1.0, 0.0, 0.0,
|
||||
0.0, 0.0, 1.0, 0.0,
|
||||
0.0, 0.0, 0.0, 1.0);
|
||||
|
||||
Matrix::Matrix(const Matrix3x3& matrix)
|
||||
{
|
||||
Platform::MemoryCopy(&M11, &matrix.M11, sizeof(Float3));
|
||||
@@ -514,10 +522,15 @@ void Matrix::OrthoOffCenter(float left, float right, float bottom, float top, fl
|
||||
result = Identity;
|
||||
result.M11 = 2.0f / (right - left);
|
||||
result.M22 = 2.0f / (top - bottom);
|
||||
result.M33 = zRange;
|
||||
result.M41 = (left + right) / (left - right);
|
||||
result.M42 = (top + bottom) / (bottom - top);
|
||||
#if REVERSE_Z
|
||||
result.M33 = -zRange;
|
||||
result.M43 = zFar * zRange;
|
||||
#else
|
||||
result.M33 = zRange;
|
||||
result.M43 = -zNear * zRange;
|
||||
#endif
|
||||
}
|
||||
|
||||
void Matrix::PerspectiveFov(float fov, float aspect, float zNear, float zFar, Matrix& result)
|
||||
@@ -533,16 +546,21 @@ void Matrix::PerspectiveFov(float fov, float aspect, float zNear, float zFar, Ma
|
||||
|
||||
void Matrix::PerspectiveOffCenter(float left, float right, float bottom, float top, float zNear, float zFar, Matrix& result)
|
||||
{
|
||||
const float zRange = zFar / (zFar - zNear);
|
||||
const float zRange = 1.0f / (zFar - zNear);
|
||||
|
||||
result = Zero;
|
||||
result.M11 = 2.0f * zNear / (right - left);
|
||||
result.M22 = 2.0f * zNear / (top - bottom);
|
||||
result.M31 = (left + right) / (left - right);
|
||||
result.M32 = (top + bottom) / (bottom - top);
|
||||
result.M33 = zRange;
|
||||
result.M34 = 1.0f;
|
||||
result.M43 = -zNear * zRange;
|
||||
#if REVERSE_Z
|
||||
result.M33 = -zNear * zRange;
|
||||
result.M43 = zFar * zNear * zRange;
|
||||
#else
|
||||
result.M33 = zFar * zRange;
|
||||
result.M43 = -zFar * zNear * zRange;
|
||||
#endif
|
||||
}
|
||||
|
||||
void Matrix::RotationX(float angle, Matrix& result)
|
||||
|
||||
@@ -2176,10 +2176,15 @@ namespace FlaxEngine
|
||||
result = Identity;
|
||||
result.M11 = 2.0f / (right - left);
|
||||
result.M22 = 2.0f / (top - bottom);
|
||||
result.M33 = zRange;
|
||||
result.M41 = (left + right) / (left - right);
|
||||
result.M42 = (top + bottom) / (bottom - top);
|
||||
#if REVERSE_Z
|
||||
result.M33 = -zRange;
|
||||
result.M43 = zfar * zRange;
|
||||
#else
|
||||
result.M33 = zRange;
|
||||
result.M43 = -znear * zRange;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -2238,14 +2243,19 @@ namespace FlaxEngine
|
||||
public static void PerspectiveFov(float fov, float aspect, float znear, float zfar, out Matrix result)
|
||||
{
|
||||
var yScale = (float)(1.0f / Math.Tan(fov * 0.5f));
|
||||
var q = zfar / (zfar - znear);
|
||||
var zRange = 1.0f / (zfar - znear);
|
||||
result = new Matrix
|
||||
{
|
||||
M11 = yScale / aspect,
|
||||
M22 = yScale,
|
||||
M33 = q,
|
||||
M34 = 1.0f,
|
||||
M43 = -q * znear,
|
||||
#if REVERSE_Z
|
||||
M33 = -znear * zRange,
|
||||
M43 = znear * zfar * zRange,
|
||||
#else
|
||||
M33 = zfar * zRange,
|
||||
M43 = -znear * zfar * zRange,
|
||||
#endif
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2275,16 +2285,21 @@ namespace FlaxEngine
|
||||
/// <param name="result">When the method completes, contains the created projection matrix.</param>
|
||||
public static void PerspectiveOffCenter(float left, float right, float bottom, float top, float znear, float zfar, out Matrix result)
|
||||
{
|
||||
float zRange = zfar / (zfar - znear);
|
||||
float zRange = 1.0f / (zfar - znear);
|
||||
result = new Matrix
|
||||
{
|
||||
M11 = 2.0f * znear / (right - left),
|
||||
M22 = 2.0f * znear / (top - bottom),
|
||||
M31 = (left + right) / (left - right),
|
||||
M32 = (top + bottom) / (bottom - top),
|
||||
M33 = zRange,
|
||||
M34 = 1.0f,
|
||||
M43 = -znear * zRange,
|
||||
#if REVERSE_Z
|
||||
M33 = -znear * zRange,
|
||||
M43 = znear * zfar * zRange,
|
||||
#else
|
||||
M33 = zfar * zRange,
|
||||
M43 = -znear * zfar * zRange,
|
||||
#endif
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -57,6 +57,15 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Rectangle"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="packed">The location and size packed.</param>
|
||||
Rectangle(const Float4& packed)
|
||||
{
|
||||
Platform::MemoryCopy(this, &packed, sizeof(*this));
|
||||
}
|
||||
|
||||
public:
|
||||
String ToString() const;
|
||||
|
||||
|
||||
@@ -150,11 +150,18 @@ void Transform::LocalToWorldVector(const Vector3& vector, Vector3& result) const
|
||||
Vector3::Transform(tmp, Orientation, result);
|
||||
}
|
||||
|
||||
void Transform::LocalToWorld(const Vector3& point, Vector3& result) const
|
||||
void Transform::LocalToWorld(const Float3& point, Float3& result) const
|
||||
{
|
||||
Vector3 tmp = point * Scale;
|
||||
Vector3::Transform(tmp, Orientation, tmp);
|
||||
Vector3::Add(tmp, Translation, result);
|
||||
Float3 tmp = point * Scale;
|
||||
Float3::Transform(tmp, Orientation, tmp);
|
||||
Float3::Add(tmp, Translation, result);
|
||||
}
|
||||
|
||||
void Transform::LocalToWorld(const Double3& point, Double3& result) const
|
||||
{
|
||||
Double3 tmp = point * Scale;
|
||||
Double3::Transform(tmp, Orientation, tmp);
|
||||
Double3::Add(tmp, Translation, result);
|
||||
}
|
||||
|
||||
void Transform::WorldToLocal(const Transform& other, Transform& result) const
|
||||
|
||||
@@ -219,7 +219,14 @@ public:
|
||||
/// </summary>
|
||||
/// <param name="point">The local space point.</param>
|
||||
/// <param name="result">The world space point.</param>
|
||||
void LocalToWorld(const Vector3& point, Vector3& result) const;
|
||||
void LocalToWorld(const Float3& point, Float3& result) const;
|
||||
|
||||
/// <summary>
|
||||
/// Performs transformation of the given point in local space to the world space of this transform.
|
||||
/// </summary>
|
||||
/// <param name="point">The local space point.</param>
|
||||
/// <param name="result">The world space point.</param>
|
||||
void LocalToWorld(const Double3& point, Double3& result) const;
|
||||
|
||||
/// <summary>
|
||||
/// Performs transformation of the given transform in local space to the world space of this transform.
|
||||
|
||||
@@ -296,15 +296,13 @@ namespace FlaxEngine
|
||||
/// <summary>
|
||||
/// Converts a screen space point into a corresponding point in world space.
|
||||
/// </summary>
|
||||
/// <param name="source">The vector to project.</param>
|
||||
/// <param name="source">The vector to project, screen uv and device depth.</param>
|
||||
/// <param name="projection">The projection matrix.</param>
|
||||
/// <param name="view">The view matrix.</param>
|
||||
/// <param name="world">The world matrix.</param>
|
||||
/// <returns>The unprojected Vector.</returns>
|
||||
public Vector3 Unproject(Vector3 source, Matrix projection, Matrix view, Matrix world)
|
||||
public Vector3 Unproject(Vector3 source, Matrix projection, Matrix view)
|
||||
{
|
||||
Matrix.Multiply(ref world, ref view, out Matrix matrix);
|
||||
Matrix.Multiply(ref matrix, ref projection, out matrix);
|
||||
Matrix.Multiply(ref view, ref projection, out Matrix matrix);
|
||||
Matrix.Invert(ref matrix, out matrix);
|
||||
|
||||
Unproject(ref source, ref matrix, out Vector3 vector);
|
||||
@@ -314,8 +312,8 @@ namespace FlaxEngine
|
||||
/// <summary>
|
||||
/// Converts a screen space point into a corresponding point in world space.
|
||||
/// </summary>
|
||||
/// <param name="source">The vector to project.</param>
|
||||
/// <param name="matrix">An inverted combined WorldViewProjection matrix.</param>
|
||||
/// <param name="source">The vector to project, screen uv and device depth.</param>
|
||||
/// <param name="matrix">An inverted combined ViewProjection matrix.</param>
|
||||
/// <param name="vector">The unprojected vector.</param>
|
||||
public void Unproject(ref Vector3 source, ref Matrix matrix, out Vector3 vector)
|
||||
{
|
||||
|
||||
@@ -19,6 +19,14 @@ public class Debug : EngineModule
|
||||
{
|
||||
options.PublicDefinitions.Add("COMPILE_WITH_DEBUG_DRAW");
|
||||
}
|
||||
|
||||
if (options.Target.IsEditor || options.Configuration != TargetConfiguration.Release)
|
||||
{
|
||||
// Used by DebugCommands to parse Xml documentation
|
||||
options.ScriptingAPI.SystemReferences.Add("System.Xml");
|
||||
options.ScriptingAPI.SystemReferences.Add("System.Xml.ReaderWriter");
|
||||
options.ScriptingAPI.SystemReferences.Add("System.Text.RegularExpressions");
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -16,8 +16,11 @@
|
||||
#include "Engine/Scripting/ManagedCLR/MMethod.h"
|
||||
#include "Engine/Scripting/ManagedCLR/MField.h"
|
||||
#include "Engine/Scripting/ManagedCLR/MProperty.h"
|
||||
#include "Engine/Scripting/ManagedCLR/MUtils.h"
|
||||
#include "FlaxEngine.Gen.h"
|
||||
|
||||
#define WITH_HELP (USE_EDITOR || !BUILD_RELEASE) && USE_CSHARP
|
||||
|
||||
struct CommandData
|
||||
{
|
||||
String Name;
|
||||
@@ -26,6 +29,38 @@ struct CommandData
|
||||
void* MethodGet = nullptr;
|
||||
void* MethodSet = nullptr;
|
||||
void* Field = nullptr;
|
||||
#if WITH_HELP
|
||||
mutable String Help;
|
||||
|
||||
StringView GetHelp() const
|
||||
{
|
||||
if (Help.IsEmpty())
|
||||
{
|
||||
if (dynamic_cast<ManagedBinaryModule*>(Module))
|
||||
{
|
||||
// Get C# type and member name
|
||||
const MClass* mclass = nullptr;
|
||||
StringAnsiView name;
|
||||
if (auto field = (MField*)Field)
|
||||
{
|
||||
mclass = field->GetParentClass();
|
||||
name = field->GetName();
|
||||
}
|
||||
else if (auto method = (MMethod*)(Method ? Method : (MethodGet ? MethodGet : MethodSet)))
|
||||
{
|
||||
mclass = method->GetParentClass();
|
||||
name = method->GetName();
|
||||
}
|
||||
|
||||
// Use Xml docs reader used by Editor to get tooltips
|
||||
auto getXmlInternal = DebugCommands::TypeInitializer.GetClass()->GetMethod("GetXmlInternal", 2);
|
||||
void* params[2] = { INTERNAL_TYPE_GET_OBJECT(mclass->GetType()), MUtils::ToString(name) };
|
||||
Help = MUtils::ToString((MString*)getXmlInternal->Invoke(nullptr, params, nullptr));
|
||||
}
|
||||
}
|
||||
return Help;
|
||||
}
|
||||
#endif
|
||||
|
||||
static void PrettyPrint(StringBuilder& sb, const Variant& value)
|
||||
{
|
||||
@@ -122,7 +157,11 @@ struct CommandData
|
||||
// Parse arguments
|
||||
if (args == StringView(TEXT("?"), 1))
|
||||
{
|
||||
LOG(Warning, "TODO: debug commands help/docs printing"); // TODO: debug commands help/docs printing (use CodeDocsModule that parses XML docs)
|
||||
#if WITH_HELP
|
||||
// Print command description
|
||||
LOG(Info, "> {} ?", Name);
|
||||
LOG_STR(Info, GetHelp());
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
Array<Variant> params;
|
||||
@@ -356,6 +395,22 @@ namespace
|
||||
InitCommands();
|
||||
Locker.Unlock();
|
||||
}
|
||||
|
||||
const CommandData* GetCommand(StringView command)
|
||||
{
|
||||
if (command.FindLast(' ') != -1)
|
||||
command = command.Left(command.Find(' '));
|
||||
// TODO: fix missing string handle on 1st command execution (command gets invalid after InitCommands due to dotnet GC or dotnet interop handles flush)
|
||||
String commandCopy = command;
|
||||
command = commandCopy;
|
||||
EnsureInited();
|
||||
for (auto& e : Commands)
|
||||
{
|
||||
if (e.Name == command)
|
||||
return &e;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
class DebugCommandsService : public EngineService
|
||||
@@ -475,27 +530,29 @@ void DebugCommands::GetAllCommands(Array<StringView>& commands)
|
||||
DebugCommands::CommandFlags DebugCommands::GetCommandFlags(StringView command)
|
||||
{
|
||||
CommandFlags result = CommandFlags::None;
|
||||
if (command.FindLast(' ') != -1)
|
||||
command = command.Left(command.Find(' '));
|
||||
// TODO: fix missing string handle on 1st command execution (command gets invalid after InitCommands due to dotnet GC or dotnet interop handles flush)
|
||||
String commandCopy = command;
|
||||
command = commandCopy;
|
||||
EnsureInited();
|
||||
for (auto& e : Commands)
|
||||
if (auto cmd = GetCommand(command))
|
||||
{
|
||||
if (e.Name == command)
|
||||
{
|
||||
if (e.Method)
|
||||
if (cmd->Method)
|
||||
result |= CommandFlags::Exec;
|
||||
else if (e.Field)
|
||||
else if (cmd->Field)
|
||||
result |= CommandFlags::ReadWrite;
|
||||
if (e.MethodGet)
|
||||
if (cmd->MethodGet)
|
||||
result |= CommandFlags::Read;
|
||||
if (e.MethodSet)
|
||||
if (cmd->MethodSet)
|
||||
result |= CommandFlags::Write;
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
StringView DebugCommands::GetCommandHelp(StringView command)
|
||||
{
|
||||
StringView result;
|
||||
#if WITH_HELP
|
||||
if (auto cmd = GetCommand(command))
|
||||
{
|
||||
result = cmd->GetHelp();
|
||||
}
|
||||
#endif
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,17 @@
|
||||
// Copyright (c) Wojciech Figat. All rights reserved.
|
||||
|
||||
#if FLAX_EDITOR || !BUILD_RELEASE
|
||||
#define WITH_HELP
|
||||
#endif
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Xml;
|
||||
|
||||
namespace FlaxEngine
|
||||
{
|
||||
@@ -12,4 +23,331 @@ namespace FlaxEngine
|
||||
public sealed class DebugCommand : Attribute
|
||||
{
|
||||
}
|
||||
|
||||
partial class DebugCommands
|
||||
{
|
||||
#if WITH_HELP
|
||||
private static Dictionary<Assembly, Dictionary<string, string>> _xmlCache;
|
||||
private static StringBuilder _sb;
|
||||
|
||||
internal static void ClearXml()
|
||||
{
|
||||
if (_xmlCache == null)
|
||||
return;
|
||||
foreach (var asm in _xmlCache.Keys.ToArray())
|
||||
{
|
||||
if (asm.IsCollectible)
|
||||
_xmlCache.Remove(asm);
|
||||
}
|
||||
}
|
||||
|
||||
internal static string GetXmlInternal(Type type, string memberName)
|
||||
{
|
||||
// Redirect into type when no member specified
|
||||
if (string.IsNullOrEmpty(memberName))
|
||||
return GetXml(type);
|
||||
|
||||
// Redirect property function getter/setter into owning property docs
|
||||
if (memberName.StartsWith("get_") || memberName.StartsWith("set_"))
|
||||
memberName = memberName.Substring(4);
|
||||
|
||||
// Find member of that name
|
||||
var members = type.GetMember(memberName, BindingFlags.Static | BindingFlags.Public);
|
||||
if (members.Length == 0)
|
||||
return null;
|
||||
return GetXml(members[0]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the XML docs text for the type.
|
||||
/// </summary>
|
||||
/// <param name="type">The type.</param>
|
||||
/// <returns>The documentation help.</returns>
|
||||
public static string GetXml(Type type)
|
||||
{
|
||||
var xml = GetXmlDocs(type.Assembly);
|
||||
if (xml != null)
|
||||
{
|
||||
var key = "T:" + GetXmlKey(type.FullName);
|
||||
if (xml.TryGetValue(key, out var xmlDoc))
|
||||
return FilterWhitespaces(xmlDoc);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the XML docs text for the type member.
|
||||
/// </summary>
|
||||
/// <param name="member">The type member.</param>
|
||||
/// <returns>The documentation help.</returns>
|
||||
public static string GetXml(MemberInfo member)
|
||||
{
|
||||
string text = null;
|
||||
var xml = GetXmlDocs(member.DeclaringType.Assembly);
|
||||
if (xml != null)
|
||||
{
|
||||
// [Reference: MSDN Magazine, October 2019, Volume 34 Number 10, "Accessing XML Documentation via Reflection"]
|
||||
// https://docs.microsoft.com/en-us/archive/msdn-magazine/2019/october/csharp-accessing-xml-documentation-via-reflection
|
||||
var memberType = member.MemberType;
|
||||
string key = null;
|
||||
if (memberType.HasFlag(MemberTypes.Field))
|
||||
{
|
||||
var fieldInfo = (FieldInfo)member;
|
||||
key = "F:" + GetXmlKey(fieldInfo.DeclaringType.FullName) + "." + fieldInfo.Name;
|
||||
}
|
||||
else if (memberType.HasFlag(MemberTypes.Property))
|
||||
{
|
||||
var propertyInfo = (PropertyInfo)member;
|
||||
key = "P:" + GetXmlKey(propertyInfo.DeclaringType.FullName) + "." + propertyInfo.Name;
|
||||
}
|
||||
else if (memberType.HasFlag(MemberTypes.Event))
|
||||
{
|
||||
var eventInfo = (EventInfo)member;
|
||||
key = "E:" + GetXmlKey(eventInfo.DeclaringType.FullName) + "." + eventInfo.Name;
|
||||
}
|
||||
else if (memberType.HasFlag(MemberTypes.Constructor))
|
||||
{
|
||||
var constructorInfo = (ConstructorInfo)member;
|
||||
key = GetXmlKey(constructorInfo);
|
||||
}
|
||||
else if (memberType.HasFlag(MemberTypes.Method))
|
||||
{
|
||||
var methodInfo = (MethodInfo)member;
|
||||
key = GetXmlKey(methodInfo);
|
||||
}
|
||||
else if (memberType.HasFlag(MemberTypes.TypeInfo) || memberType.HasFlag(MemberTypes.NestedType))
|
||||
{
|
||||
var typeInfo = (TypeInfo)member;
|
||||
key = "T:" + GetXmlKey(typeInfo.FullName);
|
||||
}
|
||||
if (key != null)
|
||||
{
|
||||
xml.TryGetValue(key, out text);
|
||||
text = FilterWhitespaces(text);
|
||||
}
|
||||
|
||||
// Customize tooltips for properties to be more human-readable in UI
|
||||
if (text != null && memberType.HasFlag(MemberTypes.Property) && text.StartsWith("Gets or sets ", StringComparison.Ordinal))
|
||||
{
|
||||
text = text.Substring(13);
|
||||
unsafe
|
||||
{
|
||||
fixed (char* e = text)
|
||||
e[0] = char.ToUpper(e[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
private static string FilterWhitespaces(string str)
|
||||
{
|
||||
if (str != null && str.Contains(" ", StringComparison.Ordinal))
|
||||
{
|
||||
if (_sb == null)
|
||||
_sb = new StringBuilder();
|
||||
else
|
||||
_sb.Clear();
|
||||
var sb = _sb;
|
||||
var prev = str[0];
|
||||
sb.Append(prev);
|
||||
for (int i = 1; i < str.Length; i++)
|
||||
{
|
||||
var c = str[i];
|
||||
if (prev != ' ' || c != ' ')
|
||||
sb.Append(c);
|
||||
prev = c;
|
||||
}
|
||||
str = sb.ToString();
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
// [Reference: MSDN Magazine, October 2019, Volume 34 Number 10, "Accessing XML Documentation via Reflection"]
|
||||
// https://docs.microsoft.com/en-us/archive/msdn-magazine/2019/october/csharp-accessing-xml-documentation-via-reflection
|
||||
|
||||
private static string GetXmlKey(MethodInfo methodInfo)
|
||||
{
|
||||
var typeGenericMap = new Dictionary<string, int>();
|
||||
var methodGenericMap = new Dictionary<string, int>();
|
||||
ParameterInfo[] parameterInfos = methodInfo.GetParameters();
|
||||
|
||||
if (methodInfo.DeclaringType.IsGenericType)
|
||||
{
|
||||
var methods = methodInfo.DeclaringType.GetGenericTypeDefinition().GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
methodInfo = methods.First(x => x.MetadataToken == methodInfo.MetadataToken);
|
||||
}
|
||||
|
||||
Type[] typeGenericArguments = methodInfo.DeclaringType.GetGenericArguments();
|
||||
for (int i = 0; i < typeGenericArguments.Length; i++)
|
||||
{
|
||||
Type typeGeneric = typeGenericArguments[i];
|
||||
typeGenericMap[typeGeneric.Name] = i;
|
||||
}
|
||||
|
||||
Type[] methodGenericArguments = methodInfo.GetGenericArguments();
|
||||
for (int i = 0; i < methodGenericArguments.Length; i++)
|
||||
{
|
||||
Type methodGeneric = methodGenericArguments[i];
|
||||
methodGenericMap[methodGeneric.Name] = i;
|
||||
}
|
||||
|
||||
string declarationTypeString = GetXmlKey(methodInfo.DeclaringType, false, typeGenericMap, methodGenericMap);
|
||||
string memberNameString = methodInfo.Name;
|
||||
string methodGenericArgumentsString = methodGenericMap.Count > 0 ? "``" + methodGenericMap.Count : string.Empty;
|
||||
string parametersString = parameterInfos.Length > 0 ? "(" + string.Join(",", methodInfo.GetParameters().Select(x => GetXmlKey(x.ParameterType, true, typeGenericMap, methodGenericMap))) + ")" : string.Empty;
|
||||
|
||||
string key = "M:" + declarationTypeString + "." + memberNameString + methodGenericArgumentsString + parametersString;
|
||||
if (methodInfo.Name is "op_Implicit" || methodInfo.Name is "op_Explicit")
|
||||
{
|
||||
key += "~" + GetXmlKey(methodInfo.ReturnType, true, typeGenericMap, methodGenericMap);
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
private static string GetXmlKey(ConstructorInfo constructorInfo)
|
||||
{
|
||||
var typeGenericMap = new Dictionary<string, int>();
|
||||
var methodGenericMap = new Dictionary<string, int>();
|
||||
ParameterInfo[] parameterInfos = constructorInfo.GetParameters();
|
||||
|
||||
Type[] typeGenericArguments = constructorInfo.DeclaringType.GetGenericArguments();
|
||||
for (int i = 0; i < typeGenericArguments.Length; i++)
|
||||
{
|
||||
Type typeGeneric = typeGenericArguments[i];
|
||||
typeGenericMap[typeGeneric.Name] = i;
|
||||
}
|
||||
|
||||
string declarationTypeString = GetXmlKey(constructorInfo.DeclaringType, false, typeGenericMap, methodGenericMap);
|
||||
string methodGenericArgumentsString = methodGenericMap.Count > 0 ? "``" + methodGenericMap.Count : string.Empty;
|
||||
string parametersString = parameterInfos.Length > 0 ? "(" + string.Join(",", constructorInfo.GetParameters().Select(x => GetXmlKey(x.ParameterType, true, typeGenericMap, methodGenericMap))) + ")" : string.Empty;
|
||||
|
||||
return "M:" + declarationTypeString + "." + "#ctor" + methodGenericArgumentsString + parametersString;
|
||||
}
|
||||
|
||||
internal static string GetXmlKey(Type type, bool isMethodParameter, Dictionary<string, int> typeGenericMap, Dictionary<string, int> methodGenericMap)
|
||||
{
|
||||
if (type.IsGenericParameter)
|
||||
{
|
||||
if (methodGenericMap.TryGetValue(type.Name, out var methodIndex))
|
||||
return "``" + methodIndex;
|
||||
if (typeGenericMap.TryGetValue(type.Name, out var typeKey))
|
||||
return "`" + typeKey;
|
||||
return "`";
|
||||
}
|
||||
if (type.HasElementType)
|
||||
{
|
||||
string elementTypeString = GetXmlKey(type.GetElementType(), isMethodParameter, typeGenericMap, methodGenericMap);
|
||||
if (type.IsPointer)
|
||||
return elementTypeString + "*";
|
||||
if (type.IsByRef)
|
||||
return elementTypeString + "@";
|
||||
if (type.IsArray)
|
||||
{
|
||||
int rank = type.GetArrayRank();
|
||||
string arrayDimensionsString = rank > 1 ? "[" + string.Join(",", Enumerable.Repeat("0:", rank)) + "]" : "[]";
|
||||
return elementTypeString + arrayDimensionsString;
|
||||
}
|
||||
throw new Exception();
|
||||
}
|
||||
|
||||
string prefaceString = type.IsNested ? GetXmlKey(type.DeclaringType, isMethodParameter, typeGenericMap, methodGenericMap) + "." : type.Namespace + ".";
|
||||
string typeNameString = isMethodParameter ? Regex.Replace(type.Name, @"`\d+", string.Empty) : type.Name;
|
||||
string genericArgumentsString = type.IsGenericType && isMethodParameter ? "{" + string.Join(",", type.GetGenericArguments().Select(argument => GetXmlKey(argument, true, typeGenericMap, methodGenericMap))) + "}" : string.Empty;
|
||||
return prefaceString + typeNameString + genericArgumentsString;
|
||||
}
|
||||
|
||||
private static string GetXmlKey(string typeFullNameString)
|
||||
{
|
||||
return Regex.Replace(typeFullNameString, @"\[.*\]", string.Empty).Replace('+', '.');
|
||||
}
|
||||
|
||||
private static Dictionary<string, string> GetXmlDocs(Assembly assembly)
|
||||
{
|
||||
if (_xmlCache == null)
|
||||
_xmlCache = new Dictionary<Assembly, Dictionary<string, string>>();
|
||||
if (!_xmlCache.TryGetValue(assembly, out var result))
|
||||
{
|
||||
Profiler.BeginEvent("GetXmlDocs");
|
||||
|
||||
// Find XML file path (based on assembly location)
|
||||
var assemblyPath = Utils.GetAssemblyLocation(assembly);
|
||||
var assemblyName = assembly.GetName().Name;
|
||||
var xmlFilePath = Path.ChangeExtension(assemblyPath, ".xml");
|
||||
if (!File.Exists(assemblyPath) && !string.IsNullOrEmpty(assemblyPath))
|
||||
{
|
||||
var uri = new UriBuilder(assemblyPath);
|
||||
var path = Uri.UnescapeDataString(uri.Path);
|
||||
xmlFilePath = Path.Combine(Path.GetDirectoryName(path), assemblyName + ".xml");
|
||||
}
|
||||
if (File.Exists(xmlFilePath))
|
||||
{
|
||||
Profiler.BeginEvent(assemblyName);
|
||||
try
|
||||
{
|
||||
// Parse xml documentation
|
||||
using (var xmlReader = XmlReader.Create(new StreamReader(xmlFilePath)))
|
||||
{
|
||||
result = new Dictionary<string, string>();
|
||||
StringBuilder content = new StringBuilder(2048);
|
||||
while (xmlReader.Read())
|
||||
{
|
||||
if (xmlReader.NodeType == XmlNodeType.Element && string.Equals(xmlReader.Name, "member", StringComparison.Ordinal))
|
||||
{
|
||||
string rawName = xmlReader["name"];
|
||||
var memberReader = xmlReader.ReadSubtree();
|
||||
if (memberReader.ReadToDescendant("summary"))
|
||||
{
|
||||
content.Clear();
|
||||
do
|
||||
{
|
||||
if (memberReader.NodeType == XmlNodeType.Element && memberReader.Read())
|
||||
{
|
||||
while (memberReader.NodeType == XmlNodeType.Text)
|
||||
{
|
||||
content.Append(memberReader.Value);
|
||||
if (memberReader.Read() && memberReader.NodeType == XmlNodeType.Element)
|
||||
{
|
||||
var nodeRef = TrimRef(memberReader.GetAttribute("cref")); // <see cref=""/>
|
||||
if (nodeRef == null)
|
||||
nodeRef = memberReader.GetAttribute("name"); // <paramref name=""/>
|
||||
content.Append(nodeRef);
|
||||
memberReader.Read();
|
||||
|
||||
string TrimRef(string str)
|
||||
{
|
||||
if (str == null)
|
||||
return null;
|
||||
if (str.IndexOf(":FlaxEngine.") == 1)
|
||||
return str.Substring("T:FlaxEngine.".Length);
|
||||
return str.Substring("T:".Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (memberReader.NodeType == XmlNodeType.EndElement && memberReader.Name == "summary")
|
||||
break;
|
||||
} while (memberReader.Read());
|
||||
|
||||
result[rawName] = content.ToString().Trim(' ', '\r', '\n');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore errors
|
||||
}
|
||||
Profiler.EndEvent();
|
||||
}
|
||||
|
||||
_xmlCache[assembly] = result;
|
||||
Profiler.EndEvent();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,8 +56,17 @@ public:
|
||||
/// Returns flags of the command.
|
||||
/// </summary>
|
||||
/// <param name="command">The full name of the command.</param>
|
||||
/// <returns>Command flags.</returns>
|
||||
API_FUNCTION() static CommandFlags GetCommandFlags(StringView command);
|
||||
|
||||
/// <summary>
|
||||
/// Returns help text of the command (from documentation comment).
|
||||
/// </summary>
|
||||
/// <remarks>Only available in non-Release builds and Editor.</remarks>
|
||||
/// <param name="command">The full name of the command.</param>
|
||||
/// <returns>Command help text or empty if failed to get it.</returns>
|
||||
API_FUNCTION() static StringView GetCommandHelp(StringView command);
|
||||
|
||||
public:
|
||||
static bool Iterate(const StringView& searchText, int32& index);
|
||||
static StringView GetCommandName(int32 index);
|
||||
|
||||
@@ -127,6 +127,7 @@ bool GameBase::Init()
|
||||
if (!gameSettings)
|
||||
return true;
|
||||
GameBaseImpl::FirstScene = gameSettings->FirstScene;
|
||||
Level::PreloadSceneAsync(GameBaseImpl::FirstScene.GetID());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
namespace FlaxEngine;
|
||||
|
||||
partial class GameplayGlobals
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a value of a given type from the global variables. (it must be added first).
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the variable to retrieve.</param>
|
||||
/// <typeparam name="T">The type of the variable to retrieve.</typeparam>
|
||||
/// <returns>The value of the variable, or default if not found or type mismatch.</returns>
|
||||
public T GetValue<T>(string name)
|
||||
{
|
||||
var obj = GetValue(name);
|
||||
if (obj is T t)
|
||||
return t;
|
||||
return default;
|
||||
}
|
||||
}
|
||||
@@ -13,8 +13,6 @@ API_CLASS(Static, Attributes="DebugCommand") class FLAXENGINE_API Globals
|
||||
DECLARE_SCRIPTING_TYPE_NO_SPAWN(Globals);
|
||||
|
||||
public:
|
||||
// Paths
|
||||
|
||||
// Main engine directory path.
|
||||
API_FIELD(ReadOnly) static String StartupFolder;
|
||||
|
||||
@@ -54,8 +52,6 @@ public:
|
||||
#endif
|
||||
|
||||
public:
|
||||
// State
|
||||
|
||||
// True if fatal error occurred (engine is exiting).
|
||||
// [Deprecated in v1.10]
|
||||
DEPRECATED("Use Engine::FatalError instead.") static bool FatalErrorOccurred;
|
||||
@@ -91,14 +87,10 @@ public:
|
||||
DEPRECATED("Use Engine::ExitCode instead.") static int32 ExitCode;
|
||||
|
||||
public:
|
||||
// Threading
|
||||
|
||||
// Main Engine thread id.
|
||||
API_FIELD(ReadOnly) static uint64 MainThreadID;
|
||||
|
||||
public:
|
||||
// Config
|
||||
|
||||
/// <summary>
|
||||
/// The full engine version.
|
||||
/// </summary>
|
||||
|
||||
@@ -20,6 +20,7 @@ namespace
|
||||
Nullable<bool> Fullscreen;
|
||||
Nullable<Float2> Size;
|
||||
bool CursorVisible = true;
|
||||
CursorType CachedCursorType = CursorType::Default;
|
||||
CursorLockMode CursorLock = CursorLockMode::None;
|
||||
CursorLockMode PendingCursorLock = CursorLockMode::None;
|
||||
bool LastGameViewportFocus = false;
|
||||
@@ -107,10 +108,17 @@ void Screen::SetCursorVisible(const bool value)
|
||||
#else
|
||||
const auto win = Engine::MainWindow;
|
||||
#endif
|
||||
if (!value && win)
|
||||
{
|
||||
// Cache cursor used before hiding it to restore it later (eg. if game uses image cursor and hides it for a while)
|
||||
CachedCursorType = win->GetCursor();
|
||||
if (CachedCursorType == CursorType::Hidden)
|
||||
CachedCursorType = CursorType::Default;
|
||||
}
|
||||
if (win && Engine::HasGameViewportFocus())
|
||||
win->SetCursor(value ? CursorType::Default : CursorType::Hidden);
|
||||
win->SetCursor(value ? CachedCursorType : CursorType::Hidden);
|
||||
else if (win)
|
||||
win->SetCursor(CursorType::Default);
|
||||
win->SetCursor(CachedCursorType);
|
||||
CursorVisible = value;
|
||||
}
|
||||
|
||||
|
||||
+396
-192
@@ -5,6 +5,8 @@
|
||||
#include "FoliageCluster.h"
|
||||
#include "Engine/Core/Log.h"
|
||||
#include "Engine/Core/Random.h"
|
||||
#include "Engine/Core/Math/Rectangle.h"
|
||||
#include "Engine/Core/Collections/BitArray.h"
|
||||
#include "Engine/Engine/Engine.h"
|
||||
#include "Engine/Graphics/Graphics.h"
|
||||
#include "Engine/Graphics/RenderTask.h"
|
||||
@@ -16,14 +18,18 @@
|
||||
#endif
|
||||
#include "Engine/Level/SceneQuery.h"
|
||||
#include "Engine/Profiler/ProfilerCPU.h"
|
||||
#include "Engine/Profiler/ProfilerMemory.h"
|
||||
#include "Engine/Renderer/RenderList.h"
|
||||
#include "Engine/Renderer/GlobalSignDistanceFieldPass.h"
|
||||
#include "Engine/Renderer/GI/GlobalSurfaceAtlasPass.h"
|
||||
#include "Engine/Serialization/Serialization.h"
|
||||
#include "Engine/Serialization/MemoryReadStream.h"
|
||||
#include "Engine/Serialization/MemoryWriteStream.h"
|
||||
#include "Engine/Utilities/Encryption.h"
|
||||
#include <ThirdParty/LZ4/lz4.h>
|
||||
|
||||
#define FOLIAGE_GET_DRAW_MODES(renderContext, type) (type._drawModes & renderContext.View.Pass & renderContext.View.GetShadowsDrawPassMask(type.ShadowsMode))
|
||||
#define FOLIAGE_CAN_DRAW(renderContext, type) (type.IsReady() && FOLIAGE_GET_DRAW_MODES(renderContext, type) != DrawPass::None && type.Model->CanBeRendered())
|
||||
#define FOLIAGE_CAN_DRAW(renderContext, type) (type.IsReady() && type._canDraw && FOLIAGE_GET_DRAW_MODES(renderContext, type) != DrawPass::None && type.Model->CanBeRendered())
|
||||
|
||||
Foliage::Foliage(const SpawnParams& params)
|
||||
: Actor(params)
|
||||
@@ -124,44 +130,47 @@ void Foliage::AddToCluster(ChunkedArray<FoliageCluster, FOLIAGE_CLUSTER_CHUNKS_S
|
||||
|
||||
#if !FOLIAGE_USE_SINGLE_QUAD_TREE && FOLIAGE_USE_DRAW_CALLS_BATCHING
|
||||
|
||||
void Foliage::DrawInstance(DrawContext& context, FoliageInstance& instance, Model* model, int32 lod, float lodDitherFactor, DrawCallsList* drawCallsLists, BatchedDrawCalls& result) const
|
||||
void Foliage::DrawInstance(DrawContext& context, FoliageInstance& instance, int32 lod, float lodDitherFactor, DrawCallsList* drawCallsLists, BatchedDrawCalls& result) const
|
||||
{
|
||||
const auto& meshes = model->LODs.Get()[lod].Meshes;
|
||||
for (int32 meshIndex = 0; meshIndex < meshes.Count(); meshIndex++)
|
||||
const auto& drawCalls = drawCallsLists[lod];
|
||||
const auto* drawCallsPtr = drawCalls.Get();
|
||||
for (int32 i = 0; i < drawCalls.Count(); i++)
|
||||
{
|
||||
auto& drawCall = drawCallsLists[lod][meshIndex];
|
||||
if (!drawCall.Material)
|
||||
continue;
|
||||
auto& drawCall = drawCallsPtr[i];
|
||||
Platform::MemoryPrefetch(&drawCallsPtr[i + 1]);
|
||||
|
||||
DrawKey key;
|
||||
key.Mat = drawCall.Material;
|
||||
key.Geo = &meshes.Get()[meshIndex];
|
||||
key.Lightmap = instance.Lightmap.TextureIndex;
|
||||
key.Geo = (Mesh*)(void*)drawCall.Geometry.IndexBuffer; // Hack to pass this over Foliage::DrawType
|
||||
key.Lightmap = instance.LightmapTextureIndex;
|
||||
auto* e = result.TryGet(key);
|
||||
if (!e)
|
||||
{
|
||||
e = &result.Add(key, BatchedDrawCall(context.RenderContext.List))->Value;
|
||||
ASSERT_LOW_LAYER(key.Mat);
|
||||
e->DrawCall.Material = key.Mat;
|
||||
e->DrawCall.Surface.Lightmap = EnumHasAnyFlags(_staticFlags, StaticFlags::Lightmap) && _scene ? _scene->LightmapsData.GetReadyLightmap(key.Lightmap) : nullptr;
|
||||
e->DrawCall.Surface.GeometrySize = key.Geo->GetBox().GetSize();
|
||||
e->DrawCall.Surface.Lightmap = key.Lightmap != -1 && EnumHasAnyFlags(_staticFlags, StaticFlags::Lightmap) && _scene ? _scene->LightmapsData.GetReadyLightmap(key.Lightmap) : nullptr;
|
||||
e->DrawCall.Surface.GeometrySize = drawCall.Surface.GeometrySize;
|
||||
}
|
||||
|
||||
// Add instance to the draw batch
|
||||
auto& instanceData = e->Instances.AddOne();
|
||||
Matrix world;
|
||||
const Transform transform = _transform.LocalToWorld(instance.Transform);
|
||||
const Float3 translation = transform.Translation - context.ViewOrigin;
|
||||
Matrix::Transformation(transform.Scale, transform.Orientation, translation, world);
|
||||
constexpr float worldDeterminantSign = 1.0f;
|
||||
instanceData.Store(world, world, instance.Lightmap.UVsArea, drawCall.Surface.GeometrySize, instance.Random, worldDeterminantSign, lodDitherFactor);
|
||||
if (!instance.CachedDrawWorldValid)
|
||||
{
|
||||
Transform transform;
|
||||
_transform.LocalToWorld(instance.Transform, transform);
|
||||
const Float3 translation = transform.Translation - context.ViewOrigin;
|
||||
Matrix::Transformation(transform.Scale, transform.Orientation, translation, instance.CachedDrawWorld);
|
||||
instance.CachedDrawWorldValid = true;
|
||||
}
|
||||
instanceData.Store(instance.CachedDrawWorld, instance.CachedDrawWorld, instance.LightmapUVsArea, drawCall.Surface.GeometrySize, instance.Random, worldDeterminantSign, lodDitherFactor);
|
||||
}
|
||||
}
|
||||
|
||||
void Foliage::DrawCluster(DrawContext& context, FoliageCluster* cluster, DrawCallsList* drawCallsLists, BatchedDrawCalls& result) const
|
||||
{
|
||||
// Skip clusters that around too far from view
|
||||
if (Float3::Distance(context.LodView.Position, cluster->TotalBoundsSphere.Center - context.LodView.Origin) - (float)cluster->TotalBoundsSphere.Radius > cluster->MaxCullDistance)
|
||||
if (Float3::Distance(context.LodViewPosition, cluster->TotalBoundsSphere.Center - context.LodViewOrigin) - (float)cluster->TotalBoundsSphere.Radius > cluster->MaxCullDistance)
|
||||
return;
|
||||
//DebugDraw::DrawBox(cluster->Bounds, Color::Red);
|
||||
|
||||
@@ -173,7 +182,7 @@ void Foliage::DrawCluster(DrawContext& context, FoliageCluster* cluster, DrawCal
|
||||
box = cluster->Children[idx]->TotalBounds; \
|
||||
box.Minimum -= context.ViewOrigin; \
|
||||
box.Maximum -= context.ViewOrigin; \
|
||||
if (context.RenderContext.View.CullingFrustum.Intersects(box)) \
|
||||
if (context.CullingFrustum.Intersects(box)) \
|
||||
DrawCluster(context, cluster->Children[idx], drawCallsLists, result)
|
||||
DRAW_CLUSTER(0);
|
||||
DRAW_CLUSTER(1);
|
||||
@@ -185,61 +194,64 @@ void Foliage::DrawCluster(DrawContext& context, FoliageCluster* cluster, DrawCal
|
||||
{
|
||||
// Draw visible instances
|
||||
const auto frame = Engine::FrameCount;
|
||||
const auto model = context.FoliageType.Model.Get();
|
||||
const auto model = context.FoliageTypeModel;
|
||||
const auto transitionLOD = context.RenderContext.View.Pass != DrawPass::Depth; // Let the main view pass update LOD transitions
|
||||
const auto instances = cluster->Instances.Get();
|
||||
const auto instancesCount = cluster->Instances.Count();
|
||||
// TODO: move DrawState to be stored per-view (so shadows can fade objects on their own)
|
||||
for (int32 i = 0; i < cluster->Instances.Count(); i++)
|
||||
for (int32 i = 0; i < instancesCount; i++)
|
||||
{
|
||||
auto& instance = *cluster->Instances.Get()[i];
|
||||
auto& instance = *instances[i];
|
||||
Platform::MemoryPrefetch(instances[i + 1]);
|
||||
BoundingSphere sphere = instance.Bounds;
|
||||
sphere.Center -= context.ViewOrigin;
|
||||
if (Float3::Distance(context.LodView.Position, sphere.Center) - (float)sphere.Radius < instance.CullDistance &&
|
||||
context.RenderContext.View.CullingFrustum.Intersects(sphere) &&
|
||||
if (Float3::Distance(context.LodViewPosition, sphere.Center) - (float)sphere.Radius < instance.CullDistance &&
|
||||
context.CullingFrustum.Intersects(sphere) &&
|
||||
RenderTools::ComputeBoundsScreenRadiusSquared(sphere.Center, (float)sphere.Radius, context.RenderContext.View) * context.ViewScreenSizeSq >= context.MinObjectPixelSizeSq)
|
||||
{
|
||||
const auto modelFrame = instance.DrawState.PrevFrame + 1;
|
||||
const auto modelFrame = instance.DrawStatePrevFrame + 1;
|
||||
|
||||
// Select a proper LOD index (model may be culled)
|
||||
int32 lodIndex = RenderTools::ComputeModelLOD(model, sphere.Center, (float)sphere.Radius, context.RenderContext);
|
||||
if (lodIndex == -1)
|
||||
{
|
||||
// Handling model fade-out transition
|
||||
if (modelFrame == frame && instance.DrawState.PrevLOD != -1)
|
||||
if (modelFrame == frame && instance.DrawStatePrevLOD != -1)
|
||||
{
|
||||
if (transitionLOD)
|
||||
{
|
||||
// Check if start transition
|
||||
if (instance.DrawState.LODTransition == 255)
|
||||
if (instance.DrawStateLODTransition == 255)
|
||||
{
|
||||
instance.DrawState.LODTransition = 0;
|
||||
instance.DrawStateLODTransition = 0;
|
||||
}
|
||||
|
||||
RenderTools::UpdateModelLODTransition(instance.DrawState.LODTransition);
|
||||
RenderTools::UpdateModelLODTransition(instance.DrawStateLODTransition);
|
||||
|
||||
// Check if end transition
|
||||
if (instance.DrawState.LODTransition == 255)
|
||||
if (instance.DrawStateLODTransition == 255)
|
||||
{
|
||||
instance.DrawState.PrevLOD = lodIndex;
|
||||
instance.DrawStatePrevLOD = lodIndex;
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto prevLOD = model->ClampLODIndex(instance.DrawState.PrevLOD);
|
||||
const float normalizedProgress = static_cast<float>(instance.DrawState.LODTransition) * (1.0f / 255.0f);
|
||||
DrawInstance(context, instance, model, prevLOD, normalizedProgress, drawCallsLists, result);
|
||||
const auto prevLOD = context.ClampLODIndex(instance.DrawStatePrevLOD);
|
||||
const float normalizedProgress = static_cast<float>(instance.DrawStateLODTransition) * (1.0f / 255.0f);
|
||||
DrawInstance(context, instance, prevLOD, normalizedProgress, drawCallsLists, result);
|
||||
}
|
||||
}
|
||||
else if (instance.DrawState.LODTransition < 255)
|
||||
else if (instance.DrawStateLODTransition < 255)
|
||||
{
|
||||
const auto prevLOD = model->ClampLODIndex(instance.DrawState.PrevLOD);
|
||||
const float normalizedProgress = static_cast<float>(instance.DrawState.LODTransition) * (1.0f / 255.0f);
|
||||
DrawInstance(context, instance, model, prevLOD, normalizedProgress, drawCallsLists, result);
|
||||
const auto prevLOD = context.ClampLODIndex(instance.DrawStatePrevLOD);
|
||||
const float normalizedProgress = static_cast<float>(instance.DrawStateLODTransition) * (1.0f / 255.0f);
|
||||
DrawInstance(context, instance, prevLOD, normalizedProgress, drawCallsLists, result);
|
||||
}
|
||||
}
|
||||
instance.DrawState.PrevFrame = frame;
|
||||
instance.DrawStatePrevFrame = frame;
|
||||
continue;
|
||||
}
|
||||
lodIndex += context.RenderContext.View.ModelLODBias;
|
||||
lodIndex = model->ClampLODIndex(lodIndex);
|
||||
lodIndex = context.ClampLODIndex(lodIndex);
|
||||
|
||||
if (transitionLOD)
|
||||
{
|
||||
@@ -247,50 +259,50 @@ void Foliage::DrawCluster(DrawContext& context, FoliageCluster* cluster, DrawCal
|
||||
if (modelFrame == frame)
|
||||
{
|
||||
// Check if start transition
|
||||
if (instance.DrawState.PrevLOD != lodIndex && instance.DrawState.LODTransition == 255)
|
||||
if (instance.DrawStatePrevLOD != lodIndex && instance.DrawStateLODTransition == 255)
|
||||
{
|
||||
instance.DrawState.LODTransition = 0;
|
||||
instance.DrawStateLODTransition = 0;
|
||||
}
|
||||
|
||||
RenderTools::UpdateModelLODTransition(instance.DrawState.LODTransition);
|
||||
RenderTools::UpdateModelLODTransition(instance.DrawStateLODTransition);
|
||||
|
||||
// Check if end transition
|
||||
if (instance.DrawState.LODTransition == 255)
|
||||
if (instance.DrawStateLODTransition == 255)
|
||||
{
|
||||
instance.DrawState.PrevLOD = lodIndex;
|
||||
instance.DrawStatePrevLOD = lodIndex;
|
||||
}
|
||||
}
|
||||
// Check if there was a gap between frames in drawing this model instance
|
||||
else if (modelFrame < frame || instance.DrawState.PrevLOD == -1)
|
||||
else if (modelFrame < frame || instance.DrawStatePrevLOD == -1)
|
||||
{
|
||||
// Reset state
|
||||
instance.DrawState.PrevLOD = lodIndex;
|
||||
instance.DrawState.LODTransition = 0;
|
||||
instance.DrawStatePrevLOD = lodIndex;
|
||||
instance.DrawStateLODTransition = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Draw
|
||||
if (instance.DrawState.PrevLOD == lodIndex)
|
||||
if (instance.DrawStatePrevLOD == lodIndex)
|
||||
{
|
||||
DrawInstance(context, instance, model, lodIndex, 0.0f, drawCallsLists, result);
|
||||
DrawInstance(context, instance, lodIndex, 0.0f, drawCallsLists, result);
|
||||
}
|
||||
else if (instance.DrawState.PrevLOD == -1)
|
||||
else if (instance.DrawStatePrevLOD == -1)
|
||||
{
|
||||
const float normalizedProgress = static_cast<float>(instance.DrawState.LODTransition) * (1.0f / 255.0f);
|
||||
DrawInstance(context, instance, model, lodIndex, 1.0f - normalizedProgress, drawCallsLists, result);
|
||||
const float normalizedProgress = static_cast<float>(instance.DrawStateLODTransition) * (1.0f / 255.0f);
|
||||
DrawInstance(context, instance, lodIndex, 1.0f - normalizedProgress, drawCallsLists, result);
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto prevLOD = model->ClampLODIndex(instance.DrawState.PrevLOD);
|
||||
const float normalizedProgress = static_cast<float>(instance.DrawState.LODTransition) * (1.0f / 255.0f);
|
||||
DrawInstance(context, instance, model, prevLOD, normalizedProgress, drawCallsLists, result);
|
||||
DrawInstance(context, instance, model, lodIndex, normalizedProgress - 1.0f, drawCallsLists, result);
|
||||
const auto prevLOD = context.ClampLODIndex(instance.DrawStatePrevLOD);
|
||||
const float normalizedProgress = static_cast<float>(instance.DrawStateLODTransition) * (1.0f / 255.0f);
|
||||
DrawInstance(context, instance, prevLOD, normalizedProgress, drawCallsLists, result);
|
||||
DrawInstance(context, instance, lodIndex, normalizedProgress - 1.0f, drawCallsLists, result);
|
||||
}
|
||||
|
||||
//DebugDraw::DrawSphere(instance.Bounds, Color::YellowGreen);
|
||||
|
||||
if (transitionLOD)
|
||||
instance.DrawState.PrevFrame = frame;
|
||||
instance.DrawStatePrevFrame = frame;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -301,7 +313,7 @@ void Foliage::DrawCluster(DrawContext& context, FoliageCluster* cluster, DrawCal
|
||||
void Foliage::DrawCluster(DrawContext& context, FoliageCluster* cluster, Mesh::DrawInfo& draw)
|
||||
{
|
||||
// Skip clusters that around too far from view
|
||||
if (Float3::Distance(context.LodView.Position, cluster->TotalBoundsSphere.Center - context.LodView.Origin) - (float)cluster->TotalBoundsSphere.Radius > cluster->MaxCullDistance)
|
||||
if (Float3::Distance(context.LodViewPosition, cluster->TotalBoundsSphere.Center - context.LodViewOrigin) - (float)cluster->TotalBoundsSphere.Radius > cluster->MaxCullDistance)
|
||||
return;
|
||||
//DebugDraw::DrawBox(cluster->Bounds, Color::Red);
|
||||
|
||||
@@ -313,7 +325,7 @@ void Foliage::DrawCluster(DrawContext& context, FoliageCluster* cluster, Mesh::D
|
||||
box = cluster->Children[idx]->TotalBounds; \
|
||||
box.Minimum -= context.ViewOrigin; \
|
||||
box.Maximum -= context.ViewOrigin; \
|
||||
if (context.RenderContext.View.CullingFrustum.Intersects(box)) \
|
||||
if (context.CullingFrustum.Intersects(box)) \
|
||||
DrawCluster(context, cluster->Children[idx], draw)
|
||||
DRAW_CLUSTER(0);
|
||||
DRAW_CLUSTER(1);
|
||||
@@ -334,8 +346,8 @@ void Foliage::DrawCluster(DrawContext& context, FoliageCluster* cluster, Mesh::D
|
||||
|
||||
// Check if can draw this instance
|
||||
if (type._canDraw &&
|
||||
Float3::Distance(context.LodView.Position, sphere.Center) - (float)sphere.Radius < instance.CullDistance &&
|
||||
context.RenderContext.View.CullingFrustum.Intersects(sphere) &&
|
||||
Float3::Distance(context.LodViewPosition, sphere.Center) - (float)sphere.Radius < instance.CullDistance &&
|
||||
context.CullingFrustum.Intersects(sphere) &&
|
||||
RenderTools::ComputeBoundsScreenRadiusSquared(sphere.Center, sphere.Radius, context.RenderContext.View) * context.ViewScreenSizeSq >= context.MinObjectPixelSizeSq)
|
||||
{
|
||||
Matrix world;
|
||||
@@ -344,14 +356,16 @@ void Foliage::DrawCluster(DrawContext& context, FoliageCluster* cluster, Mesh::D
|
||||
Matrix::Transformation(transform.Scale, transform.Orientation, translation, world);
|
||||
|
||||
// Disable motion blur
|
||||
GeometryDrawStateData drawState;
|
||||
drawState.PrevWorld = world;
|
||||
instance.DrawState.PrevWorld = world;
|
||||
|
||||
// Draw model
|
||||
draw.Lightmap = _scene->LightmapsData.GetReadyLightmap(instance.Lightmap.TextureIndex);
|
||||
draw.Lightmap = _scene->LightmapsData.GetReadyLightmap(instance.LightmapTextureIndex);
|
||||
draw.LightmapUVs = &instance.Lightmap.UVsArea;
|
||||
draw.Buffer = &type.Entries;
|
||||
draw.World = &world;
|
||||
draw.DrawState = &instance.DrawState;
|
||||
draw.DrawState = &drawState;
|
||||
draw.Bounds = sphere;
|
||||
draw.PerInstanceRandom = instance.Random;
|
||||
draw.DrawModes = type._drawModes;
|
||||
@@ -360,7 +374,7 @@ void Foliage::DrawCluster(DrawContext& context, FoliageCluster* cluster, Mesh::D
|
||||
|
||||
//DebugDraw::DrawSphere(instance.Bounds, Color::YellowGreen);
|
||||
|
||||
instance.DrawState.PrevFrame = frame;
|
||||
instance.DrawStatePrevFrame = frame;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -434,12 +448,13 @@ void Foliage::DrawFoliageJob(int32 i)
|
||||
{
|
||||
PROFILE_CPU();
|
||||
PROFILE_MEM(Graphics);
|
||||
const FoliageType& type = FoliageTypes[i];
|
||||
if (type._canDraw)
|
||||
{
|
||||
const int32 foliageIndex = i / _renderContextBatch->Contexts.Count();
|
||||
const int32 contextIndex = i % _renderContextBatch->Contexts.Count();
|
||||
const FoliageType& type = FoliageTypes[foliageIndex];
|
||||
if (!type._canDraw)
|
||||
return;
|
||||
DrawCallsList drawCallsLists[MODEL_MAX_LODS];
|
||||
for (RenderContext& renderContext : _renderContextBatch->Contexts)
|
||||
{
|
||||
RenderContext& renderContext = _renderContextBatch->Contexts[contextIndex];
|
||||
#if !FOLIAGE_USE_SINGLE_QUAD_TREE && FOLIAGE_USE_DRAW_CALLS_BATCHING
|
||||
DrawType(renderContext, type, drawCallsLists);
|
||||
#else
|
||||
@@ -452,8 +467,6 @@ void Foliage::DrawFoliageJob(int32 i)
|
||||
draw.Deformation = nullptr;
|
||||
DrawType(renderContext, type, draw);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -468,15 +481,21 @@ void Foliage::DrawType(RenderContext& renderContext, const FoliageType& type, Me
|
||||
return;
|
||||
const DrawPass typeDrawModes = FOLIAGE_GET_DRAW_MODES(renderContext, type);
|
||||
PROFILE_CPU_ASSET(type.Model);
|
||||
auto& lodView = renderContext.LodProxyView ? *renderContext.LodProxyView : renderContext.View;
|
||||
DrawContext context
|
||||
{
|
||||
renderContext,
|
||||
renderContext.LodProxyView ? *renderContext.LodProxyView : renderContext.View,
|
||||
type,
|
||||
type.Model,
|
||||
renderContext.View.Origin,
|
||||
Math::Square(Graphics::Shadows::MinObjectPixelSize),
|
||||
renderContext.View.ScreenSize.X * renderContext.View.ScreenSize.Y,
|
||||
lodView.Origin,
|
||||
Math::Square(Graphics::Shadows::MinObjectPixelSize),
|
||||
lodView.Position,
|
||||
type.Model->HighestResidentLODIndex(),
|
||||
type.Model->GetLODsCount() - 1,
|
||||
renderContext.View.CullingFrustum,
|
||||
};
|
||||
_cachedDrawWorldOrigin = renderContext.View.Origin;
|
||||
if (context.RenderContext.View.Pass != DrawPass::Depth)
|
||||
context.MinObjectPixelSizeSq = 0.0f; // Don't use it in main view
|
||||
#if FOLIAGE_USE_DRAW_CALLS_BATCHING
|
||||
@@ -486,12 +505,10 @@ void Foliage::DrawType(RenderContext& renderContext, const FoliageType& type, Me
|
||||
auto& modelLod = type.Model->LODs[lod];
|
||||
DrawCallsList& drawCallsList = drawCallsLists[lod];
|
||||
const auto& meshes = modelLod.Meshes;
|
||||
drawCallsList.Resize(meshes.Count());
|
||||
drawCallsList.EnsureCapacity(meshes.Count());
|
||||
for (int32 meshIndex = 0; meshIndex < meshes.Count(); meshIndex++)
|
||||
{
|
||||
const auto& mesh = meshes.Get()[meshIndex];
|
||||
auto& drawCall = drawCallsList.Get()[meshIndex];
|
||||
drawCall.Material = nullptr; // DrawInstance skips draw calls from meshes with unset material
|
||||
|
||||
// Check entry visibility
|
||||
const auto& entry = type.Entries[mesh.GetMaterialSlotIndex()];
|
||||
@@ -516,7 +533,10 @@ void Foliage::DrawType(RenderContext& renderContext, const FoliageType& type, Me
|
||||
if (drawModes == DrawPass::None)
|
||||
continue;
|
||||
|
||||
// Add mesh to drawing
|
||||
auto& drawCall = drawCallsList.AddOne();
|
||||
drawCall.Material = material;
|
||||
drawCall.Geometry.IndexBuffer = (GPUBuffer*)(void*)&mesh; // Wrap pointer to mesh for DrawKey::Geo
|
||||
drawCall.Surface.GeometrySize = mesh.GetBox().GetSize();
|
||||
}
|
||||
}
|
||||
@@ -589,10 +609,29 @@ void Foliage::DrawType(RenderContext& renderContext, const FoliageType& type, Me
|
||||
#endif
|
||||
}
|
||||
|
||||
void Foliage::InitType(const RenderView& view, FoliageType& type)
|
||||
void Foliage::PreDraw(const RenderView& view)
|
||||
{
|
||||
// When origin changes, then update all instance matrices
|
||||
if (_cachedDrawWorldOrigin != view.Origin)
|
||||
{
|
||||
_cachedDrawWorldOrigin = view.Origin;
|
||||
for (auto i = Instances.Begin(); i.IsNotEnd(); ++i)
|
||||
i->CachedDrawWorldValid = false;
|
||||
}
|
||||
|
||||
// Cache data per foliage instance type
|
||||
for (int32 i = 0; i < FoliageTypes.Count(); i++)
|
||||
InitType(view, i);
|
||||
}
|
||||
|
||||
void Foliage::InitType(const RenderView& view, int32 typeIndex)
|
||||
{
|
||||
FoliageType& type = FoliageTypes[typeIndex];
|
||||
const DrawPass drawModes = type._drawModes & view.Pass & view.GetShadowsDrawPassMask(type.ShadowsMode);
|
||||
type._canDraw = type.IsReady() && drawModes != DrawPass::None && type.Model && type.Model->CanBeRendered();
|
||||
#if USE_EDITOR
|
||||
type._canDraw &= _drawFoliageType == -1 || _drawFoliageType == typeIndex;
|
||||
#endif
|
||||
bool drawModesDirty = false;
|
||||
for (int32 j = 0; j < type.Entries.Count(); j++)
|
||||
{
|
||||
@@ -609,6 +648,44 @@ void Foliage::InitType(const RenderView& view, FoliageType& type)
|
||||
GetSceneRendering()->UpdateActor(this, _sceneRenderingKey, ISceneRenderingListener::DrawModes);
|
||||
}
|
||||
|
||||
void Foliage::UpdateBounds()
|
||||
{
|
||||
PROFILE_CPU();
|
||||
PROFILE_MEM(LevelFoliage);
|
||||
|
||||
// Cache bounds for each foliage type
|
||||
BitArray<> typeReady;
|
||||
Array<BoundingSphere> typeBounds;
|
||||
typeReady.Resize(FoliageTypes.Count());
|
||||
typeBounds.Resize(FoliageTypes.Count());
|
||||
for (int32 i = 0; i < typeBounds.Count(); i++)
|
||||
{
|
||||
auto& type = FoliageTypes[i];
|
||||
bool ready = type.IsReady();
|
||||
typeReady.Set(i, ready);
|
||||
if (ready)
|
||||
BoundingSphere::FromBox(type.Model->GetBox(), typeBounds[i]);
|
||||
}
|
||||
|
||||
// Update bounds for all instances
|
||||
Matrix foliageWorld, instanceLocal, instanceWorld;
|
||||
GetLocalToWorldMatrix(foliageWorld);
|
||||
for (auto i = Instances.Begin(); i.IsNotEnd(); ++i)
|
||||
{
|
||||
auto& instance = *i;
|
||||
if (typeReady.Get(instance.Type))
|
||||
{
|
||||
instance.Transform.GetWorld(instanceLocal);
|
||||
Matrix::Multiply(foliageWorld, instanceLocal, instanceWorld);
|
||||
BoundingSphere::Transform(typeBounds[instance.Type], instanceWorld, instance.Bounds);
|
||||
}
|
||||
else
|
||||
{
|
||||
instance.Bounds = BoundingSphere::Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int32 Foliage::GetInstancesCount() const
|
||||
{
|
||||
return Instances.Count();
|
||||
@@ -721,6 +798,7 @@ void Foliage::AddInstance(const FoliageInstance& instance)
|
||||
data->Bounds = BoundingSphere::Empty;
|
||||
data->Random = Random::Rand();
|
||||
data->CullDistance = type->CullDistance + type->CullDistanceRandomRange * data->Random;
|
||||
data->CachedDrawWorldValid = false;
|
||||
|
||||
// Validate foliage type model
|
||||
if (!type->IsReady())
|
||||
@@ -755,33 +833,27 @@ void Foliage::RemoveInstance(ChunkedArray<FoliageInstance, FOLIAGE_INSTANCE_CHUN
|
||||
void Foliage::SetInstanceTransform(int32 index, const Transform& value)
|
||||
{
|
||||
auto& instance = Instances[index];
|
||||
auto type = &FoliageTypes[instance.Type];
|
||||
const auto& type = FoliageTypes[instance.Type];
|
||||
|
||||
// Change transform
|
||||
instance.Transform = value;
|
||||
instance.CachedDrawWorldValid = false;
|
||||
|
||||
// Update bounds
|
||||
if (type.IsReady())
|
||||
{
|
||||
BoundingSphere typeBounds;
|
||||
BoundingSphere::FromBox(type.Model->GetBox(), typeBounds);
|
||||
Matrix foliageWorld, instanceLocal, instanceWorld;
|
||||
GetLocalToWorldMatrix(foliageWorld);
|
||||
instance.Transform.GetWorld(instanceLocal);
|
||||
Matrix::Multiply(foliageWorld, instanceLocal, instanceWorld);
|
||||
BoundingSphere::Transform(typeBounds, instanceWorld, instance.Bounds);
|
||||
}
|
||||
else
|
||||
{
|
||||
instance.Bounds = BoundingSphere::Empty;
|
||||
if (!type->IsReady())
|
||||
return;
|
||||
Vector3 corners[8];
|
||||
auto& meshes = type->Model->LODs[0].Meshes;
|
||||
const Transform transform = _transform.LocalToWorld(instance.Transform);
|
||||
for (int32 j = 0; j < meshes.Count(); j++)
|
||||
{
|
||||
meshes[j].GetBox().GetCorners(corners);
|
||||
|
||||
for (int32 k = 0; k < 8; k++)
|
||||
{
|
||||
Vector3::Transform(corners[k], transform, corners[k]);
|
||||
}
|
||||
BoundingSphere meshBounds;
|
||||
BoundingSphere::FromPoints(corners, 8, meshBounds);
|
||||
ASSERT(meshBounds.Radius > ZeroTolerance);
|
||||
|
||||
BoundingSphere::Merge(instance.Bounds, meshBounds, instance.Bounds);
|
||||
}
|
||||
instance.Bounds.Radius += ZeroTolerance;
|
||||
}
|
||||
|
||||
void Foliage::OnFoliageTypeModelLoaded(int32 index)
|
||||
@@ -800,34 +872,23 @@ void Foliage::OnFoliageTypeModelLoaded(int32 index)
|
||||
#endif
|
||||
{
|
||||
PROFILE_CPU_NAMED("Update Bounds");
|
||||
Vector3 corners[8];
|
||||
auto& meshes = type.Model->LODs[0].Meshes;
|
||||
|
||||
BoundingSphere typeBounds;
|
||||
BoundingSphere::FromBox(type.Model->GetBox(), typeBounds);
|
||||
Matrix foliageWorld, instanceLocal, instanceWorld;
|
||||
GetLocalToWorldMatrix(foliageWorld);
|
||||
|
||||
for (auto i = Instances.Begin(); i.IsNotEnd(); ++i)
|
||||
{
|
||||
auto& instance = *i;
|
||||
if (instance.Type != index)
|
||||
continue;
|
||||
instance.Bounds = BoundingSphere::Empty;
|
||||
const Transform transform = _transform.LocalToWorld(instance.Transform);
|
||||
|
||||
// Include all meshes
|
||||
for (int32 j = 0; j < meshes.Count(); j++)
|
||||
{
|
||||
// TODO: cache bounds for all model meshes and reuse later
|
||||
meshes[j].GetBox().GetCorners(corners);
|
||||
|
||||
// TODO: use SIMD
|
||||
for (int32 k = 0; k < 8; k++)
|
||||
{
|
||||
Vector3::Transform(corners[k], transform, corners[k]);
|
||||
}
|
||||
BoundingSphere meshBounds;
|
||||
BoundingSphere::FromPoints(corners, 8, meshBounds);
|
||||
BoundingSphere::Merge(instance.Bounds, meshBounds, instance.Bounds);
|
||||
}
|
||||
instance.Transform.GetWorld(instanceLocal);
|
||||
Matrix::Multiply(foliageWorld, instanceLocal, instanceWorld);
|
||||
BoundingSphere::Transform(typeBounds, instanceWorld, instance.Bounds);
|
||||
|
||||
#if !FOLIAGE_USE_SINGLE_QUAD_TREE
|
||||
// TODO: use SIMD
|
||||
BoundingBox::FromSphere(instance.Bounds, box);
|
||||
if (hasAnyInstance)
|
||||
BoundingBox::Merge(totalBoundsType, box, totalBoundsType);
|
||||
@@ -1164,12 +1225,7 @@ void Foliage::Draw(RenderContext& renderContext)
|
||||
return;
|
||||
PROFILE_CPU();
|
||||
const RenderView& view = renderContext.View;
|
||||
|
||||
// Cache data per foliage instance type
|
||||
for (auto& type : FoliageTypes)
|
||||
{
|
||||
InitType(renderContext.View, type);
|
||||
}
|
||||
PreDraw(view);
|
||||
|
||||
if (renderContext.View.Pass == DrawPass::GlobalSDF)
|
||||
{
|
||||
@@ -1234,21 +1290,23 @@ void Foliage::Draw(RenderContext& renderContext)
|
||||
// Draw single foliage instance projection into Global Surface Atlas
|
||||
auto& instance = *(FoliageInstance*)GlobalSurfaceAtlasPass::Instance()->GetCurrentActorObject();
|
||||
auto& type = FoliageTypes[instance.Type];
|
||||
InitType(renderContext.View, type);
|
||||
InitType(renderContext.View, instance.Type);
|
||||
Matrix world;
|
||||
const Transform transform = _transform.LocalToWorld(instance.Transform);
|
||||
renderContext.View.GetWorldMatrix(transform, world);
|
||||
GeometryDrawStateData drawState;
|
||||
drawState.PrevWorld = world;
|
||||
Mesh::DrawInfo draw;
|
||||
draw.Flags = GetStaticFlags();
|
||||
draw.LODBias = 0;
|
||||
draw.ForcedLOD = -1;
|
||||
draw.SortOrder = 0;
|
||||
draw.VertexColors = nullptr;
|
||||
draw.Lightmap = _scene ? _scene->LightmapsData.GetReadyLightmap(instance.Lightmap.TextureIndex) : nullptr;
|
||||
draw.LightmapUVs = &instance.Lightmap.UVsArea;
|
||||
draw.Lightmap = _scene ? _scene->LightmapsData.GetReadyLightmap(instance.LightmapTextureIndex) : nullptr;
|
||||
draw.LightmapUVs = &instance.LightmapUVsArea;
|
||||
draw.Buffer = &type.Entries;
|
||||
draw.World = &world;
|
||||
draw.DrawState = &instance.DrawState;
|
||||
draw.DrawState = &drawState;
|
||||
draw.Deformation = nullptr;
|
||||
draw.Bounds = instance.Bounds;
|
||||
draw.PerInstanceRandom = instance.Random;
|
||||
@@ -1267,8 +1325,6 @@ void Foliage::Draw(RenderContext& renderContext)
|
||||
draw.ForcedLOD = -1;
|
||||
draw.VertexColors = nullptr;
|
||||
draw.Deformation = nullptr;
|
||||
#else
|
||||
DrawCallsList draw[MODEL_MAX_LODS];
|
||||
#endif
|
||||
#if FOLIAGE_USE_SINGLE_QUAD_TREE
|
||||
if (Root)
|
||||
@@ -1276,6 +1332,9 @@ void Foliage::Draw(RenderContext& renderContext)
|
||||
#else
|
||||
for (auto& type : FoliageTypes)
|
||||
{
|
||||
#if !FOLIAGE_USE_SINGLE_QUAD_TREE && FOLIAGE_USE_DRAW_CALLS_BATCHING
|
||||
DrawCallsList draw[MODEL_MAX_LODS];
|
||||
#endif
|
||||
DrawType(renderContext, type, draw);
|
||||
}
|
||||
#endif
|
||||
@@ -1291,15 +1350,13 @@ void Foliage::Draw(RenderContextBatch& renderContextBatch)
|
||||
const RenderView& view = renderContextBatch.GetMainContext().View;
|
||||
if (EnumHasAnyFlags(view.Pass, DrawPass::GBuffer) && !(view.Pass & (DrawPass::GlobalSDF | DrawPass::GlobalSurfaceAtlas)) && renderContextBatch.EnableAsync)
|
||||
{
|
||||
// Cache data per foliage instance type
|
||||
for (FoliageType& type : FoliageTypes)
|
||||
InitType(view, type);
|
||||
PreDraw(view);
|
||||
|
||||
// Run async job for each foliage type
|
||||
_renderContextBatch = &renderContextBatch;
|
||||
Function<void(int32)> func;
|
||||
func.Bind<Foliage, &Foliage::DrawFoliageJob>(this);
|
||||
const int64 waitLabel = JobSystem::Dispatch(func, FoliageTypes.Count());
|
||||
const int64 waitLabel = JobSystem::Dispatch(func, FoliageTypes.Count() * renderContextBatch.Contexts.Count());
|
||||
renderContextBatch.WaitLabels.Add(waitLabel);
|
||||
return;
|
||||
}
|
||||
@@ -1336,6 +1393,7 @@ struct InstanceEncoded1
|
||||
static constexpr int32 Base64Size = GetInstanceBase64Size(Size);
|
||||
};
|
||||
|
||||
// [Deprecated in v1.13]
|
||||
struct InstanceEncoded2
|
||||
{
|
||||
int32 Type;
|
||||
@@ -1343,27 +1401,180 @@ struct InstanceEncoded2
|
||||
Float3 Translation;
|
||||
Quaternion Orientation;
|
||||
Float3 Scale;
|
||||
LightmapEntry Lightmap;
|
||||
int32 LightmapTextureIndex;
|
||||
Rectangle LightmapUVsArea;
|
||||
|
||||
static const int32 Size = 68;
|
||||
static const int32 Base64Size = GetInstanceBase64Size(Size);
|
||||
};
|
||||
|
||||
// [Deprecated in v1.13]
|
||||
typedef InstanceEncoded2 InstanceEncoded;
|
||||
static_assert(InstanceEncoded::Size == sizeof(InstanceEncoded), "Please update base64 buffer size to match the encoded instance buffer.");
|
||||
static_assert(InstanceEncoded::Base64Size == GetInstanceBase64Size(sizeof(InstanceEncoded)), "Please update base64 buffer size to match the encoded instance buffer.");
|
||||
|
||||
struct FoliageChunkMeta
|
||||
{
|
||||
uint16 InstanceCounter;
|
||||
};
|
||||
|
||||
struct FoliageInstanceData
|
||||
{
|
||||
union
|
||||
{
|
||||
struct
|
||||
{
|
||||
uint16 Type : 14; // Max 16,384 foliage types, which is more than enough for any use case
|
||||
uint16 OrientationNegativeW : 1;
|
||||
uint16 HasLightmap : 1;
|
||||
};
|
||||
uint16 Packed;
|
||||
};
|
||||
int16 OrientationX, OrientationY, OrientationZ;
|
||||
float Random;
|
||||
Float3 Position, Scale;
|
||||
};
|
||||
|
||||
struct FoliageWriter
|
||||
{
|
||||
static constexpr int32 InstanceSizeApprox = sizeof(FoliageInstanceData);
|
||||
static constexpr int32 InstancesPerChunk = 64;
|
||||
typedef uint16 CompressedSize;
|
||||
int32 InstanceCounter = 0;
|
||||
Foliage::SerializeStream& Stream;
|
||||
MemoryWriteStream Memory;
|
||||
Array<byte> Compressed;
|
||||
Array<char> Base64;
|
||||
|
||||
FoliageWriter(Foliage::SerializeStream& stream)
|
||||
: Stream(stream)
|
||||
, Memory(Math::RoundUpToPowerOf2(InstancesPerChunk* InstanceSizeApprox))
|
||||
{
|
||||
Memory.Move<FoliageChunkMeta>();
|
||||
}
|
||||
|
||||
void Write(const FoliageInstance& instance)
|
||||
{
|
||||
// Fixed-size data
|
||||
FoliageInstanceData data;
|
||||
data.Type = (uint16)instance.Type;
|
||||
data.HasLightmap = instance.HasLightmap();
|
||||
data.Random = instance.Random;
|
||||
data.Position = (Float3)instance.Transform.Translation;
|
||||
data.Scale = instance.Transform.Scale;
|
||||
data.OrientationX = (int16)(instance.Transform.Orientation.X * MAX_int16);
|
||||
data.OrientationY = (int16)(instance.Transform.Orientation.Y * MAX_int16);
|
||||
data.OrientationZ = (int16)(instance.Transform.Orientation.Z * MAX_int16);
|
||||
data.OrientationNegativeW = instance.Transform.Orientation.W < 0;
|
||||
Memory.Write(data);
|
||||
|
||||
// Lightmap data (if used)
|
||||
if (data.HasLightmap)
|
||||
{
|
||||
Memory.Write(instance.LightmapTextureIndex);
|
||||
Memory.Write(instance.LightmapUVsArea);
|
||||
}
|
||||
|
||||
// Move to the next instance
|
||||
InstanceCounter++;
|
||||
if (InstanceCounter >= InstancesPerChunk)
|
||||
Flush();
|
||||
}
|
||||
|
||||
void Flush()
|
||||
{
|
||||
if (InstanceCounter == 0)
|
||||
return;
|
||||
|
||||
// Store chunk size metadata in the beginning
|
||||
static_assert(InstancesPerChunk * InstanceSizeApprox * 2 < MAX_uint16, "Too much data for potential chunk storage.");
|
||||
auto meta = (FoliageChunkMeta*)Memory.GetHandle();
|
||||
meta->InstanceCounter = (uint16)InstanceCounter;
|
||||
InstanceCounter = 0;
|
||||
|
||||
// Compress with LZ4
|
||||
const int32 srcSize = (int32)Memory.GetPosition();
|
||||
const int32 maxSize = LZ4_compressBound(srcSize);
|
||||
Compressed.Resize(maxSize + sizeof(CompressedSize)); // Place decompressed size in the beginning
|
||||
const int32 dstSize = LZ4_compress_default((const char*)Memory.GetHandle(), (char*)Compressed.Get() + sizeof(CompressedSize), srcSize, maxSize);
|
||||
Compressed.Resize(dstSize + sizeof(CompressedSize));
|
||||
*(CompressedSize*)Compressed.Get() = (CompressedSize)srcSize;
|
||||
|
||||
// Convert raw bytes into Base64 string and write to Json stream
|
||||
Encryption::Base64Encode(Compressed.Get(), Compressed.Count(), Base64);
|
||||
Stream.String(Base64.Get(), Base64.Count());
|
||||
|
||||
// Reset memory writer
|
||||
Memory.Reset();
|
||||
Memory.Move<FoliageChunkMeta>();
|
||||
}
|
||||
};
|
||||
|
||||
struct FoliageReader
|
||||
{
|
||||
typedef FoliageWriter::CompressedSize CompressedSize;
|
||||
Array<byte> Base64;
|
||||
Array<byte> Decompressed;
|
||||
|
||||
FoliageReader()
|
||||
{
|
||||
}
|
||||
|
||||
void Read(Foliage& foliage, int32& instanceIndex, StringAnsiView base64)
|
||||
{
|
||||
// Convert Base64 string into raw bytes
|
||||
Encryption::Base64Decode(base64.Get(), base64.Length(), Base64);
|
||||
|
||||
// Decompress with LZ4
|
||||
auto originalSize = *(CompressedSize*)Base64.Get();
|
||||
Decompressed.Resize(originalSize);
|
||||
const int32 res = LZ4_decompress_safe((const char*)Base64.Get() + sizeof(CompressedSize), (char*)Decompressed.Get(), Base64.Count() - sizeof(CompressedSize), originalSize);
|
||||
ASSERT(res >= 0);
|
||||
Decompressed.Resize(res);
|
||||
|
||||
// Read instances from memory
|
||||
MemoryReadStream memory(Decompressed);
|
||||
FoliageChunkMeta meta;
|
||||
memory.Read(meta);
|
||||
FoliageInstanceData data;
|
||||
ASSERT(meta.InstanceCounter <= FoliageWriter::InstancesPerChunk);
|
||||
for (int32 i = 0; i < meta.InstanceCounter; i++)
|
||||
{
|
||||
memory.Read(data);
|
||||
|
||||
auto& instance = foliage.Instances[instanceIndex++];
|
||||
instance.Type = data.Type;
|
||||
instance.Random = data.Random;
|
||||
instance.Transform.Translation = data.Position;
|
||||
instance.Transform.Scale = data.Scale;
|
||||
Quaternion q;
|
||||
q.X = (float)data.OrientationX * (1.0f / (float)MAX_int16);
|
||||
q.Y = (float)data.OrientationY * (1.0f / (float)MAX_int16);
|
||||
q.Z = (float)data.OrientationZ * (1.0f / (float)MAX_int16);
|
||||
q.W = Math::Sqrt(Math::Max(1.0f - q.X * q.X - q.Y * q.Y - q.Z * q.Z, 0.0f));
|
||||
if (data.OrientationNegativeW)
|
||||
q.W *= -1;
|
||||
q.Normalize();
|
||||
instance.Transform.Orientation = q;
|
||||
if (data.HasLightmap)
|
||||
{
|
||||
memory.Read(instance.LightmapTextureIndex);
|
||||
memory.Read(instance.LightmapUVsArea);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
void Foliage::Serialize(SerializeStream& stream, const void* otherObj)
|
||||
{
|
||||
// Base
|
||||
Actor::Serialize(stream, otherObj);
|
||||
|
||||
SERIALIZE_GET_OTHER_OBJ(Foliage);
|
||||
|
||||
if (FoliageTypes.IsEmpty())
|
||||
return;
|
||||
|
||||
PROFILE_CPU();
|
||||
PROFILE_MEM(LevelFoliage);
|
||||
|
||||
stream.JKEY("Foliage");
|
||||
stream.StartArray();
|
||||
@@ -1377,25 +1588,19 @@ void Foliage::Serialize(SerializeStream& stream, const void* otherObj)
|
||||
|
||||
stream.JKEY("Instances");
|
||||
stream.StartArray();
|
||||
InstanceEncoded enc;
|
||||
char base64[InstanceEncoded::Base64Size + 2];
|
||||
base64[0] = '\"';
|
||||
base64[InstanceEncoded::Base64Size + 1] = '\"';
|
||||
|
||||
// Put some metadata
|
||||
stream.Int(Instances.Count());
|
||||
|
||||
// Write instances in chunks
|
||||
FoliageWriter writer(stream);
|
||||
// TODO: run this in parallel for better performance on large foliage data (keep the order of instances in the stream, run job for each 64 instances, let one job keep writing results back to the stream to avoid too much memory usage)
|
||||
for (auto i = Instances.Begin(); i.IsNotEnd(); ++i)
|
||||
{
|
||||
auto& instance = *i;
|
||||
|
||||
enc.Type = instance.Type;
|
||||
enc.Random = instance.Random;
|
||||
enc.Translation = instance.Transform.Translation;
|
||||
enc.Orientation = instance.Transform.Orientation;
|
||||
enc.Scale = instance.Transform.Scale;
|
||||
enc.Lightmap = instance.Lightmap;
|
||||
|
||||
Encryption::Base64Encode((const byte*)&enc, sizeof(enc), base64 + 1);
|
||||
|
||||
stream.RawValue(base64, InstanceEncoded::Base64Size + 2);
|
||||
writer.Write(*i);
|
||||
}
|
||||
writer.Flush();
|
||||
|
||||
stream.EndArray();
|
||||
}
|
||||
|
||||
@@ -1419,18 +1624,18 @@ void Foliage::Deserialize(DeserializeStream& stream, ISerializeModifier* modifie
|
||||
int32 foliageTypesCount = 0;
|
||||
const auto& foliageTypesMember = stream.FindMember("Foliage");
|
||||
if (foliageTypesMember != stream.MemberEnd() && foliageTypesMember->value.IsArray())
|
||||
{
|
||||
foliageTypesCount = foliageTypesMember->value.Size();
|
||||
}
|
||||
if (foliageTypesCount)
|
||||
{
|
||||
PROFILE_CPU_NAMED("Types");
|
||||
const DeserializeStream& items = foliageTypesMember->value;;
|
||||
FoliageTypes.Resize(foliageTypesCount, false);
|
||||
for (int32 i = 0; i < foliageTypesCount; i++)
|
||||
{
|
||||
FoliageTypes[i].Foliage = this;
|
||||
FoliageTypes[i].Index = i;
|
||||
FoliageTypes[i].Deserialize((DeserializeStream&)items[i], modifier);
|
||||
auto& type = FoliageTypes[i];
|
||||
type.Foliage = this;
|
||||
type.Index = i;
|
||||
type.Deserialize((DeserializeStream&)items[i], modifier);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1439,12 +1644,31 @@ void Foliage::Deserialize(DeserializeStream& stream, ISerializeModifier* modifie
|
||||
return;
|
||||
|
||||
// Deserialize foliage instances
|
||||
int32 foliageInstancesCount = 0;
|
||||
const auto& foliageInstancesMember = stream.FindMember("Instances");
|
||||
if (foliageInstancesMember != stream.MemberEnd() && foliageInstancesMember->value.IsArray())
|
||||
if (foliageInstancesMember == stream.MemberEnd() || !foliageInstancesMember->value.IsArray())
|
||||
return;
|
||||
if (modifier->EngineBuild >= 7001)
|
||||
{
|
||||
foliageInstancesCount = foliageInstancesMember->value.Size();
|
||||
const DeserializeStream& items = foliageInstancesMember->value;
|
||||
int32 chunksCount = (int32)items.Size() - 1;
|
||||
if (chunksCount <= 0)
|
||||
return;
|
||||
int32 foliageInstancesCount = items[0].GetInt();
|
||||
Instances.Resize(foliageInstancesCount);
|
||||
PROFILE_CPU_NAMED("Instances");
|
||||
|
||||
FoliageReader reader;
|
||||
int32 instanceIndex = 0;
|
||||
for (int32 i = 1; i <= chunksCount; i++)
|
||||
{
|
||||
reader.Read(*this, instanceIndex, items[i].GetStringAnsiView());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// [Deprecated in v1.13]
|
||||
MARK_CONTENT_DEPRECATED();
|
||||
int32 foliageInstancesCount = foliageInstancesMember->value.Size();
|
||||
if (foliageInstancesCount)
|
||||
{
|
||||
const DeserializeStream& items = foliageInstancesMember->value;
|
||||
@@ -1473,7 +1697,7 @@ void Foliage::Deserialize(DeserializeStream& stream, ISerializeModifier* modifie
|
||||
instance.Transform.Translation = enc.Translation;
|
||||
instance.Transform.Orientation = enc.Orientation;
|
||||
instance.Transform.Scale = enc.Scale;
|
||||
instance.Lightmap = LightmapEntry();
|
||||
instance.LightmapTextureIndex = -1;
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -1497,7 +1721,10 @@ void Foliage::Deserialize(DeserializeStream& stream, ISerializeModifier* modifie
|
||||
instance.Transform.Translation = enc.Translation;
|
||||
instance.Transform.Orientation = enc.Orientation;
|
||||
instance.Transform.Scale = enc.Scale;
|
||||
instance.Lightmap = enc.Lightmap;
|
||||
instance.LightmapTextureIndex = enc.LightmapTextureIndex;
|
||||
instance.LightmapUVsArea = Half4(enc.LightmapUVsArea);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1521,7 +1748,6 @@ void Foliage::Deserialize(DeserializeStream& stream, ISerializeModifier* modifie
|
||||
auto& type = FoliageTypes[instance.Type];
|
||||
instance.CullDistance = type.CullDistance + type.CullDistanceRandomRange * instance.Random;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Foliage::OnLayerChanged()
|
||||
@@ -1553,35 +1779,13 @@ void Foliage::OnTransformChanged()
|
||||
|
||||
PROFILE_CPU();
|
||||
|
||||
// Update instances matrices and cached world bounds
|
||||
Vector3 corners[8];
|
||||
Matrix world;
|
||||
GetLocalToWorldMatrix(world);
|
||||
if (IsDuringPlay())
|
||||
{
|
||||
// Invalidate cached world matrix
|
||||
for (auto i = Instances.Begin(); i.IsNotEnd(); ++i)
|
||||
{
|
||||
auto& instance = *i;
|
||||
auto type = &FoliageTypes[instance.Type];
|
||||
|
||||
// Update bounds
|
||||
instance.Bounds = BoundingSphere::Empty;
|
||||
if (!type->IsReady())
|
||||
continue;
|
||||
auto& meshes = type->Model->LODs[0].Meshes;
|
||||
const Transform transform = _transform.LocalToWorld(instance.Transform);
|
||||
for (int32 j = 0; j < meshes.Count(); j++)
|
||||
{
|
||||
meshes[j].GetBox().GetCorners(corners);
|
||||
|
||||
for (int32 k = 0; k < 8; k++)
|
||||
{
|
||||
Vector3::Transform(corners[k], transform, corners[k]);
|
||||
}
|
||||
BoundingSphere meshBounds;
|
||||
BoundingSphere::FromPoints(corners, 8, meshBounds);
|
||||
|
||||
BoundingSphere::Merge(instance.Bounds, meshBounds, instance.Bounds);
|
||||
}
|
||||
i->CachedDrawWorldValid = false;
|
||||
}
|
||||
|
||||
UpdateBounds();
|
||||
RebuildClusters();
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "FoliageInstance.h"
|
||||
#include "FoliageCluster.h"
|
||||
#include "FoliageType.h"
|
||||
#include "Engine/Core/Math/BoundingFrustum.h"
|
||||
#include "Engine/Core/Memory/ArenaAllocation.h"
|
||||
#include "Engine/Level/Actor.h"
|
||||
|
||||
@@ -19,6 +20,7 @@ API_CLASS() class FLAXENGINE_API Foliage final : public Actor
|
||||
private:
|
||||
bool _disableFoliageTypeEvents;
|
||||
int32 _sceneRenderingKey = -1;
|
||||
Vector3 _cachedDrawWorldOrigin = Vector3::Zero;
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
@@ -158,15 +160,25 @@ public:
|
||||
|
||||
private:
|
||||
void AddToCluster(ChunkedArray<FoliageCluster, FOLIAGE_CLUSTER_CHUNKS_SIZE>& clusters, FoliageCluster* cluster, FoliageInstance& instance);
|
||||
|
||||
struct DrawContext
|
||||
{
|
||||
RenderContext& RenderContext;
|
||||
const RenderView& LodView;
|
||||
const FoliageType& FoliageType;
|
||||
Model* FoliageTypeModel;
|
||||
Vector3 ViewOrigin;
|
||||
float MinObjectPixelSizeSq;
|
||||
float ViewScreenSizeSq;
|
||||
Float3 LodViewOrigin;
|
||||
float MinObjectPixelSizeSq;
|
||||
Float3 LodViewPosition;
|
||||
int32 MinLOD, MaxLOD;
|
||||
BoundingFrustum CullingFrustum;
|
||||
|
||||
FORCE_INLINE int32 ClampLODIndex(int32 index) const
|
||||
{
|
||||
return index < MinLOD ? MinLOD : index < MaxLOD ? index : MaxLOD;
|
||||
}
|
||||
};
|
||||
|
||||
#if !FOLIAGE_USE_SINGLE_QUAD_TREE && FOLIAGE_USE_DRAW_CALLS_BATCHING
|
||||
struct DrawKey
|
||||
{
|
||||
@@ -190,7 +202,7 @@ private:
|
||||
|
||||
typedef Array<struct DrawCall, InlinedAllocation<8>> DrawCallsList;
|
||||
typedef Dictionary<DrawKey, struct BatchedDrawCall, ConcurrentArenaAllocation> BatchedDrawCalls;
|
||||
void DrawInstance(DrawContext& context, FoliageInstance& instance, Model* model, int32 lod, float lodDitherFactor, DrawCallsList* drawCallsLists, BatchedDrawCalls& result) const;
|
||||
void DrawInstance(DrawContext& context, FoliageInstance& instance, int32 lod, float lodDitherFactor, DrawCallsList* drawCallsLists, BatchedDrawCalls& result) const;
|
||||
void DrawCluster(DrawContext& context, FoliageCluster* cluster, DrawCallsList* drawCallsLists, BatchedDrawCalls& result) const;
|
||||
void DrawType(RenderContext& renderContext, const FoliageType& type, DrawCallsList* drawCallsLists);
|
||||
#else
|
||||
@@ -204,7 +216,9 @@ private:
|
||||
RenderContextBatch* _renderContextBatch;
|
||||
#endif
|
||||
|
||||
void InitType(const RenderView& view, FoliageType& type);
|
||||
void PreDraw(const RenderView& view);
|
||||
void InitType(const RenderView& view, int32 typeIndex);
|
||||
void UpdateBounds();
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
@@ -217,6 +231,12 @@ public:
|
||||
/// <returns>True whether the two objects intersected, otherwise false.</returns>
|
||||
API_FUNCTION() bool Intersects(API_PARAM(Ref) const Ray& ray, API_PARAM(Out) Real& distance, API_PARAM(Out) Vector3& normal, API_PARAM(Out) int32& instanceIndex);
|
||||
|
||||
private:
|
||||
#if USE_EDITOR
|
||||
// Debug filter to draw only specific foliage type (in editor).
|
||||
API_FIELD(Internal) int32 _drawFoliageType = -1;
|
||||
#endif
|
||||
|
||||
public:
|
||||
// [Actor]
|
||||
void Draw(RenderContext& renderContext) override;
|
||||
|
||||
@@ -4,15 +4,15 @@
|
||||
|
||||
#include "Engine/Core/Math/Transform.h"
|
||||
#include "Engine/Core/Math/BoundingSphere.h"
|
||||
#include "Engine/Renderer/DrawCall.h"
|
||||
#include "Engine/Level/Scene/Lightmap.h"
|
||||
#include "Engine/Core/Math/Half.h"
|
||||
#include "Engine/Core/Math/Matrix.h"
|
||||
|
||||
/// <summary>
|
||||
/// Foliage instanced mesh instance. Packed data with very little of logic. Managed by the foliage chunks and foliage actor itself.
|
||||
/// </summary>
|
||||
API_STRUCT(NoPod) struct FLAXENGINE_API FoliageInstance
|
||||
API_STRUCT(NoPod, NoDefault) struct FLAXENGINE_API FoliageInstance
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_NO_SPAWN(FoliageInstance);
|
||||
DECLARE_SCRIPTING_TYPE_MINIMAL(FoliageInstance);
|
||||
|
||||
/// <summary>
|
||||
/// The local-space transformation of the mesh relative to the foliage actor.
|
||||
@@ -20,14 +20,39 @@ API_STRUCT(NoPod) struct FLAXENGINE_API FoliageInstance
|
||||
API_FIELD() Transform Transform;
|
||||
|
||||
/// <summary>
|
||||
/// The model drawing state.
|
||||
/// The cached instance bounds (in world space).
|
||||
/// </summary>
|
||||
GeometryDrawStateData DrawState;
|
||||
API_FIELD() BoundingSphere Bounds;
|
||||
|
||||
/// <summary>
|
||||
/// The previous frame index. In sync with Engine::FrameCount used to detect new frames and rendering gaps to reset state.
|
||||
/// </summary>
|
||||
uint64 DrawStatePrevFrame = 0;
|
||||
|
||||
/// <summary>
|
||||
/// The foliage type index. Foliage types are hold in foliage actor and shared by instances using the same model.
|
||||
/// </summary>
|
||||
API_FIELD() int32 Type;
|
||||
API_FIELD() uint16 Type;
|
||||
|
||||
/// <summary>
|
||||
/// The lightmap index for the foliage instance. -1 if unused.
|
||||
/// </summary>
|
||||
int8 LightmapTextureIndex = -1;
|
||||
|
||||
/// <summary>
|
||||
/// The previous frame model LOD index used. It's locked during LOD transition to cache the transition start LOD.
|
||||
/// </summary>
|
||||
char DrawStatePrevLOD = -1;
|
||||
|
||||
/// <summary>
|
||||
/// The LOD transition timer.
|
||||
/// </summary>
|
||||
byte DrawStateLODTransition = 255;
|
||||
|
||||
/// <summary>
|
||||
/// Flag used to indicate whether CachedDrawWorld contains valid data.
|
||||
/// </summary>
|
||||
byte CachedDrawWorldValid = 0;
|
||||
|
||||
/// <summary>
|
||||
/// The per-instance random value from range [0;1].
|
||||
@@ -40,14 +65,14 @@ API_STRUCT(NoPod) struct FLAXENGINE_API FoliageInstance
|
||||
float CullDistance;
|
||||
|
||||
/// <summary>
|
||||
/// The cached instance bounds (in world space).
|
||||
/// Lightmap UVs area that entry occupies (packed Rectangle into Half4).
|
||||
/// </summary>
|
||||
API_FIELD() BoundingSphere Bounds;
|
||||
Half4 LightmapUVsArea;
|
||||
|
||||
/// <summary>
|
||||
/// The lightmap entry for the foliage instance.
|
||||
/// Cached local-to-world transformation matrix for the instance used during rendering. Relative to the last rendering context origin. Valid only if CachedDrawWorldValid flag is set.
|
||||
/// </summary>
|
||||
LightmapEntry Lightmap;
|
||||
Matrix CachedDrawWorld;
|
||||
|
||||
public:
|
||||
bool operator==(const FoliageInstance& v) const
|
||||
@@ -60,7 +85,7 @@ public:
|
||||
/// </summary>
|
||||
FORCE_INLINE bool HasLightmap() const
|
||||
{
|
||||
return Lightmap.TextureIndex != INVALID_INDEX;
|
||||
return LightmapTextureIndex != INVALID_INDEX;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -68,6 +93,6 @@ public:
|
||||
/// </summary>
|
||||
FORCE_INLINE void RemoveLightmap()
|
||||
{
|
||||
Lightmap.TextureIndex = INVALID_INDEX;
|
||||
LightmapTextureIndex = INVALID_INDEX;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "Engine/Graphics/GPUDevice.h"
|
||||
#include "Engine/Graphics/GPUPass.h"
|
||||
#include "Engine/Profiler/ProfilerCPU.h"
|
||||
#include "Engine/Profiler/ProfilerGPU.h"
|
||||
|
||||
DefaultGPUTasksExecutor::DefaultGPUTasksExecutor()
|
||||
: _context(nullptr)
|
||||
@@ -33,6 +34,7 @@ void DefaultGPUTasksExecutor::FrameBegin()
|
||||
const int32 count = GPUDevice::Instance->GetTasksManager()->RequestWork(buffer, 32);
|
||||
if (count == 0)
|
||||
return;
|
||||
PROFILE_GPU("GPUTasks");
|
||||
GPUMemoryPass pass(_context->GPU);
|
||||
for (int32 i = 0; i < count; i++)
|
||||
{
|
||||
|
||||
@@ -629,6 +629,17 @@ public:
|
||||
|
||||
uint32 GetHash(const BlendingMode& key);
|
||||
|
||||
// Temp defines to put them into ComparisonFunc enum conditionally
|
||||
#if REVERSE_Z
|
||||
#define REVERSE_Z_COMP_DEFAULT 5
|
||||
#define REVERSE_Z_COMP_DEFAULT_INV 2
|
||||
#define REVERSE_Z_COMP_DEFAULT_EQ 7
|
||||
#else
|
||||
#define REVERSE_Z_COMP_DEFAULT 2
|
||||
#define REVERSE_Z_COMP_DEFAULT_INV 5
|
||||
#define REVERSE_Z_COMP_DEFAULT_EQ 4
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Comparison function modes
|
||||
/// </summary>
|
||||
@@ -651,9 +662,21 @@ API_ENUM() enum class ComparisonFunc : byte
|
||||
// Always pass the comparison.
|
||||
Always = 8,
|
||||
|
||||
API_ENUM(Attributes="HideInEditor") MAX
|
||||
API_ENUM(Attributes="HideInEditor") MAX,
|
||||
|
||||
// Default comparision when rendering scene objects (Less or Greater).
|
||||
API_ENUM(Attributes="HideInEditor") Default = REVERSE_Z_COMP_DEFAULT,
|
||||
// Default comparision when rendering scene objects inverted (Greater or Less).
|
||||
API_ENUM(Attributes="HideInEditor") DefaultInv = REVERSE_Z_COMP_DEFAULT_INV,
|
||||
// Default comparision when rendering scene objects with equal (LessEqual or GreaterEqual).
|
||||
API_ENUM(Attributes="HideInEditor") DefaultEqual = REVERSE_Z_COMP_DEFAULT_EQ,
|
||||
};
|
||||
|
||||
// Remove temp defines to plug conditional value into the enum
|
||||
#undef REVERSE_Z_COMP_DEFAULT
|
||||
#undef REVERSE_Z_COMP_DEFAULT_INV
|
||||
#undef REVERSE_Z_COMP_DEFAULT_EQ
|
||||
|
||||
/// <summary>
|
||||
/// Rendering quality levels.
|
||||
/// </summary>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "GPUContext.h"
|
||||
#include "GPUDevice.h"
|
||||
#include "GPUPass.h"
|
||||
#include "RenderTask.h"
|
||||
#include "Textures/GPUTexture.h"
|
||||
|
||||
@@ -94,8 +95,10 @@ void GPUContext::Draw(GPUTexture* dst, GPUTexture* src)
|
||||
ResetRenderTarget();
|
||||
const float width = (float)dst->Width();
|
||||
const float height = (float)dst->Height();
|
||||
auto rt = dst->View();
|
||||
auto rtAction = GPUDrawPassAction::Store;
|
||||
GPUDrawPass drawPass(this, ToSpan(&rt, 1), ToSpan(&rtAction, 1));
|
||||
SetViewport(width, height);
|
||||
SetRenderTarget(dst->View());
|
||||
BindSR(0, src->View());
|
||||
SetState(_device->GetCopyLinearPS());
|
||||
DrawFullscreenTriangle();
|
||||
@@ -107,8 +110,10 @@ void GPUContext::Draw(GPUTexture* dst, GPUTextureView* src)
|
||||
ResetRenderTarget();
|
||||
const float width = (float)dst->Width();
|
||||
const float height = (float)dst->Height();
|
||||
auto rt = dst->View();
|
||||
auto rtAction = GPUDrawPassAction::Store;
|
||||
GPUDrawPass drawPass(this, ToSpan(&rt, 1), ToSpan(&rtAction, 1));
|
||||
SetViewport(width, height);
|
||||
SetRenderTarget(dst->View());
|
||||
BindSR(0, src);
|
||||
SetState(_device->GetCopyLinearPS());
|
||||
DrawFullscreenTriangle();
|
||||
@@ -137,3 +142,8 @@ void GPUContext::SetResourceState(GPUResource* resource, uint64 state, int32 sub
|
||||
void GPUContext::ForceRebindDescriptors()
|
||||
{
|
||||
}
|
||||
|
||||
void GPUContext::BeginDrawPass(GPUDrawPass& pass)
|
||||
{
|
||||
SetRenderTarget(pass.DepthBuffer, ToSpan(pass.RenderTargets, pass.RenderTargetsCount));
|
||||
}
|
||||
|
||||
@@ -14,6 +14,16 @@
|
||||
#undef MemoryBarrier
|
||||
#endif
|
||||
|
||||
#if REVERSE_Z
|
||||
#define GPU_DEPTH_RANGE_MIN 1.0f
|
||||
#define GPU_DEPTH_RANGE_MAX 0.0f
|
||||
#define GPU_DEPTH_RANGE_BOUNDS(min, max) max, min
|
||||
#else
|
||||
#define GPU_DEPTH_RANGE_MIN 0.0f
|
||||
#define GPU_DEPTH_RANGE_MAX 1.0f
|
||||
#define GPU_DEPTH_RANGE_BOUNDS(min, max) min, max
|
||||
#endif
|
||||
|
||||
class GPUConstantBuffer;
|
||||
class GPUShaderProgramCS;
|
||||
class GPUBuffer;
|
||||
@@ -27,6 +37,7 @@ class GPUTextureView;
|
||||
class GPUBufferView;
|
||||
class GPUVertexLayout;
|
||||
struct GPUPass;
|
||||
struct GPUDrawPass;
|
||||
enum class GPUResourceAccess;
|
||||
enum class GPUQueryType;
|
||||
|
||||
@@ -207,7 +218,7 @@ public:
|
||||
/// <param name="depthBuffer">The depth buffer to clear.</param>
|
||||
/// <param name="depthValue">The clear depth value.</param>
|
||||
/// <param name="stencilValue">The clear stencil value.</param>
|
||||
API_FUNCTION() virtual void ClearDepth(GPUTextureView* depthBuffer, float depthValue = 1.0f, uint8 stencilValue = 0) = 0;
|
||||
API_FUNCTION() virtual void ClearDepth(GPUTextureView* depthBuffer, float depthValue = GPU_DEPTH_RANGE_MAX, uint8 stencilValue = 0) = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Clears an unordered access buffer with a float value.
|
||||
@@ -627,9 +638,10 @@ public:
|
||||
/// <summary>
|
||||
/// Sets the minimum and maximum depth values for depth bounds test.
|
||||
/// </summary>
|
||||
/// <remarks>Both values must be between 0.0 and 1.0, MinDepth must be less than or equal to MaxDepth.</remarks>
|
||||
/// <param name="minDepth">The minimum value for depth bound test.</param>
|
||||
/// <param name="maxDepth">The maximum value for depth bound test.</param>
|
||||
API_FUNCTION() virtual void SetDepthBounds(float minDepth, float maxDepth) = 0;
|
||||
API_FUNCTION() virtual void SetDepthBounds(float minDepth = 0.0f, float maxDepth = 1.0f) = 0;
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
@@ -697,4 +709,12 @@ public:
|
||||
virtual void OverlapUA(bool end)
|
||||
{
|
||||
}
|
||||
|
||||
// Begins draw pass rendering. See GPUDrawPass.
|
||||
virtual void BeginDrawPass(GPUDrawPass& pass);
|
||||
|
||||
// Ends draw pass rendering. See GPUDrawPass.
|
||||
virtual void EndDrawPass()
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
@@ -160,7 +160,7 @@ GPUPipelineState::Description GPUPipelineState::Description::Default =
|
||||
true, // DepthWriteEnable
|
||||
true, // DepthClipEnable
|
||||
false, // DepthBoundsEnable
|
||||
ComparisonFunc::Less, // DepthFunc
|
||||
ComparisonFunc::Default, // DepthFunc
|
||||
false, // StencilEnable
|
||||
0xff, // StencilReadMask
|
||||
0xff, // StencilWriteMask
|
||||
@@ -185,7 +185,7 @@ GPUPipelineState::Description GPUPipelineState::Description::DefaultNoDepth =
|
||||
false, // DepthWriteEnable
|
||||
false, // DepthClipEnable
|
||||
false, // DepthBoundsEnable
|
||||
ComparisonFunc::Less, // DepthFunc
|
||||
ComparisonFunc::Default, // DepthFunc
|
||||
false, // StencilEnable
|
||||
0xff, // StencilReadMask
|
||||
0xff, // StencilWriteMask
|
||||
@@ -210,7 +210,7 @@ GPUPipelineState::Description GPUPipelineState::Description::DefaultFullscreenTr
|
||||
false, // DepthWriteEnable
|
||||
false, // DepthClipEnable
|
||||
false, // DepthBoundsEnable
|
||||
ComparisonFunc::Less, // DepthFunc
|
||||
ComparisonFunc::Default, // DepthFunc
|
||||
false, // StencilEnable
|
||||
0xff, // StencilReadMask
|
||||
0xff, // StencilWriteMask
|
||||
@@ -361,8 +361,7 @@ void GPUDevice::OnRequestingExit()
|
||||
Engine::FatalError != FatalErrorType::GPUHang &&
|
||||
Engine::FatalError != FatalErrorType::GPUOutOfMemory)
|
||||
return;
|
||||
// TODO: get and log actual GPU memory used by the engine (API-specific)
|
||||
DumpResourcesToLog();
|
||||
OnCrash();
|
||||
}
|
||||
|
||||
GPUDevice::GPUDevice(RendererType type, ShaderProfile profile)
|
||||
@@ -623,6 +622,16 @@ void GPUDevice::DumpResources()
|
||||
|
||||
extern void ClearVertexLayoutCache();
|
||||
|
||||
bool UseVSync()
|
||||
{
|
||||
bool vsync = Graphics::UseVSync;
|
||||
if (CommandLine::Options.NoVSync.HasValue())
|
||||
vsync = !CommandLine::Options.NoVSync.GetValue();
|
||||
else if (CommandLine::Options.VSync.HasValue())
|
||||
vsync = CommandLine::Options.VSync.GetValue();
|
||||
return vsync;
|
||||
}
|
||||
|
||||
void GPUDevice::preDispose()
|
||||
{
|
||||
Locker.Lock();
|
||||
@@ -666,26 +675,26 @@ void GPUDevice::DrawEnd()
|
||||
{
|
||||
PROFILE_CPU_NAMED("Present");
|
||||
|
||||
// Check if use VSync
|
||||
bool useVSync = Graphics::UseVSync;
|
||||
if (CommandLine::Options.NoVSync.HasValue())
|
||||
useVSync = !CommandLine::Options.NoVSync.GetValue();
|
||||
else if (CommandLine::Options.VSync.HasValue())
|
||||
useVSync = CommandLine::Options.VSync.GetValue();
|
||||
|
||||
// Find index of the last rendered window task (use vsync only on the last window)
|
||||
int32 lastWindowIndex = -1;
|
||||
// Check if use VSync (prioritize the last window, in case of multi-window in Editor)
|
||||
bool useVSync = UseVSync();
|
||||
int32 vsyncTask = -1;
|
||||
for (int32 i = RenderTask::Tasks.Count() - 1; i >= 0; i--)
|
||||
{
|
||||
const auto task = RenderTask::Tasks[i];
|
||||
if (task && task->LastUsedFrame == Engine::FrameCount && task->SwapChain && task->SwapChain->IsReady())
|
||||
{
|
||||
lastWindowIndex = i;
|
||||
vsyncTask = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (_rendererType == RendererType::Vulkan && RenderTask::Tasks.Contains(_lastVSyncTask))
|
||||
{
|
||||
// On Vulkan, maintain the last window that was VSynced to avoid recreating swapchain too often (eg. when using context menus or tooltips)
|
||||
vsyncTask = RenderTask::Tasks.Find(_lastVSyncTask);
|
||||
}
|
||||
|
||||
// Call present on all used tasks
|
||||
_lastVSyncTask = nullptr;
|
||||
int32 presentCount = 0;
|
||||
bool anyVSync = false;
|
||||
#if COMPILE_WITH_PROFILER
|
||||
@@ -697,7 +706,7 @@ void GPUDevice::DrawEnd()
|
||||
if (task && task->LastUsedFrame == Engine::FrameCount && task->SwapChain && task->SwapChain->IsReady())
|
||||
{
|
||||
bool vsync = useVSync;
|
||||
if (lastWindowIndex != i)
|
||||
if (vsyncTask != i)
|
||||
{
|
||||
// Perform VSync only on the last window
|
||||
vsync = false;
|
||||
@@ -712,6 +721,8 @@ void GPUDevice::DrawEnd()
|
||||
|
||||
anyVSync |= vsync;
|
||||
task->OnPresent(vsync);
|
||||
if (vsync)
|
||||
_lastVSyncTask = task;
|
||||
presentCount++;
|
||||
}
|
||||
}
|
||||
@@ -751,6 +762,11 @@ void GPUDevice::RenderEnd()
|
||||
#endif
|
||||
}
|
||||
|
||||
void GPUDevice::OnCrash()
|
||||
{
|
||||
DumpResourcesToLog();
|
||||
}
|
||||
|
||||
GPUTasksContext* GPUDevice::CreateTasksContext()
|
||||
{
|
||||
return New<GPUTasksContext>(this);
|
||||
@@ -822,6 +838,16 @@ uint64 GPUDevice::GetMemoryUsage() const
|
||||
return result;
|
||||
}
|
||||
|
||||
GPUMemoryStats GPUDevice::GetMemoryStats()
|
||||
{
|
||||
GPUMemoryStats stats;
|
||||
stats.UsedDedicatedMemory = GetMemoryUsage();
|
||||
stats.TotalDedicatedMemory = TotalGraphicsMemory;
|
||||
stats.UsedSystemMemory = 0;
|
||||
stats.TotalSystemMemory = 0;
|
||||
return stats;
|
||||
}
|
||||
|
||||
Array<GPUResource*> GPUDevice::GetResources() const
|
||||
{
|
||||
_resourcesLock.Lock();
|
||||
|
||||
@@ -33,6 +33,34 @@ class Model;
|
||||
class Material;
|
||||
class MaterialBase;
|
||||
|
||||
/// <summary>
|
||||
/// Contains information about current GPU memory usage and budget.
|
||||
/// </summary>
|
||||
API_STRUCT(NoDefault) struct GPUMemoryStats
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_MINIMAL(GPUMemoryStats);
|
||||
|
||||
/// <summary>
|
||||
/// Amount of used dedicated video memory in bytes. Memory local to the device, and represents the fastest available memory to the GPU.
|
||||
/// </summary>
|
||||
API_FIELD() uint64 UsedDedicatedMemory = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Total amount of dedicated memory budget in bytes. Memory local to the device, and represents the fastest available memory to the GPU.
|
||||
/// </summary>
|
||||
API_FIELD() uint64 TotalDedicatedMemory = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Amount of used system video memory in bytes. Memory non-local to the device, and may have slower performance than the dedicated/local.
|
||||
/// </summary>
|
||||
API_FIELD() uint64 UsedSystemMemory = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Total amount of system memory budget in bytes. Memory non-local to the device, and may have slower performance than the dedicated/local.
|
||||
/// </summary>
|
||||
API_FIELD() uint64 TotalSystemMemory = 0;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Graphics device object for rendering on GPU.
|
||||
/// </summary>
|
||||
@@ -134,6 +162,7 @@ protected:
|
||||
PrivateData* _res;
|
||||
Array<GPUResource*> _resources;
|
||||
CriticalSection _resourcesLock;
|
||||
RenderTask* _lastVSyncTask = nullptr;
|
||||
|
||||
void OnRequestingExit();
|
||||
|
||||
@@ -272,10 +301,15 @@ public:
|
||||
API_PROPERTY() virtual void* GetNativePtr() const = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the amount of memory usage by all the GPU resources (in bytes).
|
||||
/// Gets the amount of memory usage by all the GPU resources (in bytes). Returned value is estimated based on resources created by the engine and might not be accurate. Use GPUMemoryStats for more detailed memory budget usage.
|
||||
/// </summary>
|
||||
API_PROPERTY() uint64 GetMemoryUsage() const;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current GPU memory stats.
|
||||
/// </summary>
|
||||
API_PROPERTY() virtual GPUMemoryStats GetMemoryStats();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list with all active GPU resources.
|
||||
/// </summary>
|
||||
@@ -417,6 +451,11 @@ protected:
|
||||
/// </summary>
|
||||
virtual void RenderEnd();
|
||||
|
||||
/// <summary>
|
||||
/// Called when program crashed due to GPU error (out of memory, hang, error - see Engine::FatalError). By default, it logs all GPU resources to the log. Can be used to update platform-specific stats or extract crash-info.
|
||||
/// </summary>
|
||||
virtual void OnCrash();
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Creates the texture.
|
||||
|
||||
@@ -71,4 +71,99 @@ struct FLAXENGINE_API GPUComputePass : GPUPass
|
||||
}
|
||||
};
|
||||
|
||||
// TODO: add GPUDrawPass for render targets and depth/stencil setup with optimized clear for faster drawing on tiled-GPUs (mobile)
|
||||
/// <summary>
|
||||
/// GPU pass operations on attached render targets and depth buffer. Defines the load/store actions for each attachment to optimize GPU rendering by reducing memory bandwidth usage.
|
||||
/// </summary>
|
||||
enum class GPUDrawPassAction
|
||||
{
|
||||
// No action, the content of the render target or depth buffer is undefined. Discards the resulting value of the render pass for this attachment.
|
||||
None = 0,
|
||||
|
||||
// Loads the existing value for this attachment into the draw pass.
|
||||
Load = 1,
|
||||
|
||||
// Loads the clear value for this attachment into the draw pass. Clear value is provided by the GPUContext::Clear performed on the texture before pass begins.
|
||||
Clear = 2,
|
||||
|
||||
// Stores the resulting value of the render pass for this attachment.
|
||||
Store = 4,
|
||||
|
||||
// Resolves the resulting MSAA value and stores final value into the attachment.
|
||||
ResolveMultisample = 8,
|
||||
|
||||
// Mask of flags allowed by load operation (reading data from attachment).
|
||||
LoadMask = None | Load | Clear,
|
||||
|
||||
// Mask of flags allowed by store operation (writing data to attachment).
|
||||
StoreMask = None | Store | ResolveMultisample,
|
||||
|
||||
// Loads the existing value for this attachment into the draw pass and stores the resulting value of the render pass for this attachment.
|
||||
LoadStore = Load | Store,
|
||||
|
||||
// Loads the clear value for this attachment into the draw pass and stores the resulting value of the render pass for this attachment.
|
||||
ClearStore = Clear | Store,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// GPU pass that explicitly defines render targets and depth buffer within a rendering pass. Can be used to optimize GPU rendering on tiled GPUs with manual control over attachment operations (load, store, clear, discard, etc.) that reduce memory bandwidth usage.
|
||||
/// </summary>
|
||||
/// <remarks>Draw Pass discards any render targets or depth buffer bound to the context (reduces state-tracking).</remarks>
|
||||
struct FLAXENGINE_API GPUDrawPass : GPUPass
|
||||
{
|
||||
GPUTextureView* DepthBuffer;
|
||||
GPUTextureView** RenderTargets;
|
||||
GPUDrawPassAction* RenderTargetsActions;
|
||||
int32 RenderTargetsCount;
|
||||
GPUDrawPassAction DepthAction;
|
||||
|
||||
GPUDrawPass(GPUContext* context, Span<GPUTextureView*> renderTargets)
|
||||
: GPUPass(context)
|
||||
, DepthBuffer(nullptr)
|
||||
, RenderTargets(renderTargets.Get())
|
||||
, RenderTargetsActions(nullptr)
|
||||
, RenderTargetsCount(renderTargets.Length())
|
||||
, DepthAction(GPUDrawPassAction::None)
|
||||
{
|
||||
Context->BeginDrawPass(*this);
|
||||
}
|
||||
|
||||
GPUDrawPass(GPUContext* context, GPUTextureView* depthBuffer, Span<GPUTextureView*> renderTargets)
|
||||
: GPUPass(context)
|
||||
, DepthBuffer(depthBuffer)
|
||||
, RenderTargets(renderTargets.Get())
|
||||
, RenderTargetsActions(nullptr)
|
||||
, RenderTargetsCount(renderTargets.Length())
|
||||
, DepthAction(GPUDrawPassAction::LoadStore)
|
||||
{
|
||||
Context->BeginDrawPass(*this);
|
||||
}
|
||||
|
||||
GPUDrawPass(GPUContext* context, GPUTextureView* depthBuffer, GPUDrawPassAction depthAction, Span<GPUTextureView*> renderTargets, Span<GPUDrawPassAction> renderTargetsActions)
|
||||
: GPUPass(context)
|
||||
, DepthBuffer(depthBuffer)
|
||||
, RenderTargets(renderTargets.Get())
|
||||
, RenderTargetsActions(renderTargetsActions.Get())
|
||||
, RenderTargetsCount(renderTargets.Length())
|
||||
, DepthAction(depthAction)
|
||||
{
|
||||
ASSERT_LOW_LAYER(renderTargets.Length() == renderTargetsActions.Length());
|
||||
Context->BeginDrawPass(*this);
|
||||
}
|
||||
|
||||
GPUDrawPass(GPUContext* context, Span<GPUTextureView*> renderTargets, Span<GPUDrawPassAction> renderTargetsActions)
|
||||
: GPUPass(context)
|
||||
, DepthBuffer(nullptr)
|
||||
, RenderTargets(renderTargets.Get())
|
||||
, RenderTargetsActions(renderTargetsActions.Get())
|
||||
, RenderTargetsCount(renderTargets.Length())
|
||||
, DepthAction(GPUDrawPassAction::None)
|
||||
{
|
||||
ASSERT_LOW_LAYER(renderTargets.Length() == renderTargetsActions.Length());
|
||||
Context->BeginDrawPass(*this);
|
||||
}
|
||||
|
||||
~GPUDrawPass()
|
||||
{
|
||||
Context->EndDrawPass();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -10,6 +10,7 @@ template<int Size>
|
||||
class GPUPipelineStatePermutations
|
||||
{
|
||||
public:
|
||||
enum { StaticSize = Size };
|
||||
GPUPipelineState* States[Size];
|
||||
|
||||
public:
|
||||
@@ -97,13 +98,13 @@ public:
|
||||
}
|
||||
|
||||
public:
|
||||
bool Create(GPUPipelineState::Description& desc, GPUShader* shader, const StringAnsiView& psName)
|
||||
bool Create(GPUPipelineState::Description& desc, GPUShader* shader, const StringAnsiView& psName, int32 permutationOffset = 0)
|
||||
{
|
||||
for (int i = 0; i < Size; i++)
|
||||
{
|
||||
ASSERT(Base::States[i]);
|
||||
|
||||
desc.PS = shader->GetPS(psName, i);
|
||||
desc.PS = shader->GetPS(psName, i + permutationOffset);
|
||||
if (Base::States[i]->Init(desc))
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ bool Graphics::SpreadWorkload = true;
|
||||
#if !BUILD_RELEASE || USE_EDITOR
|
||||
float Graphics::TestValue = 0.0f;
|
||||
#endif
|
||||
float Graphics::MotionVectors::MinObjectScreenSize = 0.02f;
|
||||
float Graphics::Shadows::MinObjectPixelSize = 2.0f;
|
||||
bool Graphics::PostProcessing::ColorGradingVolumeLUT = true;
|
||||
|
||||
|
||||
@@ -19,32 +19,32 @@ public:
|
||||
API_FIELD() static bool UseVSync;
|
||||
|
||||
/// <summary>
|
||||
/// Anti Aliasing quality setting.
|
||||
/// Anti Aliasing quality setting. Available values are: Low, Medium, High, Ultra (or 0, 1, 2, 3).
|
||||
/// </summary>
|
||||
API_FIELD() static Quality AAQuality;
|
||||
|
||||
/// <summary>
|
||||
/// Screen Space Reflections quality setting.
|
||||
/// Screen Space Reflections quality setting. Available values are: Low, Medium, High, Ultra (or 0, 1, 2, 3).
|
||||
/// </summary>
|
||||
API_FIELD() static Quality SSRQuality;
|
||||
|
||||
/// <summary>
|
||||
/// Screen Space Ambient Occlusion quality setting.
|
||||
/// Screen Space Ambient Occlusion quality setting. Available values are: Low, Medium, High, Ultra (or 0, 1, 2, 3).
|
||||
/// </summary>
|
||||
API_FIELD() static Quality SSAOQuality;
|
||||
|
||||
/// <summary>
|
||||
/// Volumetric Fog quality setting.
|
||||
/// Volumetric Fog quality setting. Available values are: Low, Medium, High, Ultra (or 0, 1, 2, 3).
|
||||
/// </summary>
|
||||
API_FIELD() static Quality VolumetricFogQuality;
|
||||
|
||||
/// <summary>
|
||||
/// The shadows quality.
|
||||
/// The shadows filtering quality (sampling). Available values are: Low, Medium, High, Ultra (or 0, 1, 2, 3).
|
||||
/// </summary>
|
||||
API_FIELD() static Quality ShadowsQuality;
|
||||
|
||||
/// <summary>
|
||||
/// The shadow maps quality (textures resolution).
|
||||
/// The shadow maps quality (textures resolution). Available values are: Low, Medium, High, Ultra (or 0, 1, 2, 3).
|
||||
/// </summary>
|
||||
API_FIELD() static Quality ShadowMapsQuality;
|
||||
|
||||
@@ -59,12 +59,12 @@ public:
|
||||
API_FIELD() static bool AllowCSMBlending;
|
||||
|
||||
/// <summary>
|
||||
/// The Global SDF quality. Controls the volume texture resolution and amount of cascades to use.
|
||||
/// The Global SDF quality. Controls the volume texture resolution and amount of cascades to use. Available values are: Low, Medium, High, Ultra (or 0, 1, 2, 3).
|
||||
/// </summary>
|
||||
API_FIELD() static Quality GlobalSDFQuality;
|
||||
|
||||
/// <summary>
|
||||
/// The Global Illumination quality. Controls the quality of the GI effect.
|
||||
/// The Global Illumination quality. Controls the quality of the GI effect. Available values are: Low, Medium, High, Ultra (or 0, 1, 2, 3).
|
||||
/// </summary>
|
||||
API_FIELD() static Quality GIQuality;
|
||||
|
||||
@@ -110,6 +110,15 @@ public:
|
||||
API_FIELD() static float MinObjectPixelSize;
|
||||
};
|
||||
|
||||
// Motion Vectors rendering configuration.
|
||||
API_CLASS(Static, Attributes="DebugCommand") class FLAXENGINE_API MotionVectors
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_MINIMAL(MotionVectors);
|
||||
|
||||
// The minimum screen size of objects to draw motion vectors. Improves performance by skipping too small objects (eg. sub-pixel) from rendering motion vectors.
|
||||
API_FIELD() static float MinObjectScreenSize;
|
||||
};
|
||||
|
||||
// Post Processing effects rendering configuration.
|
||||
API_CLASS(Static, Attributes="DebugCommand") class FLAXENGINE_API PostProcessing
|
||||
{
|
||||
|
||||
@@ -26,10 +26,10 @@ bool DeferredMaterialShader::CanUseLightmap() const
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DeferredMaterialShader::CanUseInstancing(InstancingHandler& handler) const
|
||||
bool DeferredMaterialShader::CanUseInstancing(const RenderContext& renderContext, InstancingHandler& handler) const
|
||||
{
|
||||
handler = { SurfaceDrawCallHandler::GetHash, SurfaceDrawCallHandler::CanBatch, };
|
||||
return _instanced;
|
||||
return _instanced || renderContext.View.Pass == DrawPass::Depth;
|
||||
}
|
||||
|
||||
void DeferredMaterialShader::Bind(BindParameters& params)
|
||||
@@ -182,7 +182,7 @@ bool DeferredMaterialShader::Load()
|
||||
// Motion Vectors pass
|
||||
psDesc.DepthWriteEnable = false;
|
||||
psDesc.DepthEnable = true;
|
||||
psDesc.DepthFunc = ComparisonFunc::LessEqual;
|
||||
psDesc.DepthFunc = ComparisonFunc::DefaultEqual;
|
||||
psDesc.VS = _shader->GetVS("VS");
|
||||
psDesc.PS = _shader->GetPS("PS_MotionVectors");
|
||||
_cache.MotionVectors.Init(psDesc);
|
||||
@@ -200,7 +200,7 @@ bool DeferredMaterialShader::Load()
|
||||
psDesc.DepthClipEnable = false;
|
||||
psDesc.DepthWriteEnable = true;
|
||||
psDesc.DepthEnable = true;
|
||||
psDesc.DepthFunc = ComparisonFunc::Less;
|
||||
psDesc.DepthFunc = ComparisonFunc::Default;
|
||||
psDesc.BlendMode.RenderTargetWriteMask = BlendingMode::ColorWrite::None;
|
||||
psDesc.HS = nullptr;
|
||||
psDesc.DS = nullptr;
|
||||
|
||||
@@ -77,7 +77,7 @@ public:
|
||||
// [MaterialShader]
|
||||
DrawPass GetDrawModes() const override;
|
||||
bool CanUseLightmap() const override;
|
||||
bool CanUseInstancing(InstancingHandler& handler) const override;
|
||||
bool CanUseInstancing(const RenderContext& renderContext, InstancingHandler& handler) const override;
|
||||
void Bind(BindParameters& params) override;
|
||||
void Unload() override;
|
||||
|
||||
|
||||
@@ -178,7 +178,11 @@ bool DeformableMaterialShader::Load()
|
||||
psDesc.DepthClipEnable = false;
|
||||
psDesc.DepthWriteEnable = true;
|
||||
psDesc.DepthEnable = true;
|
||||
#if REVERSE_Z
|
||||
psDesc.DepthFunc = ComparisonFunc::Greater;
|
||||
#else
|
||||
psDesc.DepthFunc = ComparisonFunc::Less;
|
||||
#endif
|
||||
psDesc.HS = nullptr;
|
||||
psDesc.DS = nullptr;
|
||||
_cache.Depth.Init(psDesc);
|
||||
|
||||
@@ -22,10 +22,11 @@ DrawPass ForwardMaterialShader::GetDrawModes() const
|
||||
return _drawModes;
|
||||
}
|
||||
|
||||
bool ForwardMaterialShader::CanUseInstancing(InstancingHandler& handler) const
|
||||
bool ForwardMaterialShader::CanUseInstancing(const RenderContext& renderContext, InstancingHandler& handler) const
|
||||
{
|
||||
handler = { SurfaceDrawCallHandler::GetHash, SurfaceDrawCallHandler::CanBatch, };
|
||||
return false; // TODO: support instancing when using ForwardShadingFeature
|
||||
// TODO: support instancing when using ForwardShadingFeature
|
||||
return renderContext.View.Pass == DrawPass::Depth;
|
||||
}
|
||||
|
||||
void ForwardMaterialShader::Bind(BindParameters& params)
|
||||
@@ -184,7 +185,7 @@ bool ForwardMaterialShader::Load()
|
||||
psDesc.DepthClipEnable = false;
|
||||
psDesc.DepthWriteEnable = true;
|
||||
psDesc.DepthEnable = true;
|
||||
psDesc.DepthFunc = ComparisonFunc::Less;
|
||||
psDesc.DepthFunc = ComparisonFunc::Default;
|
||||
psDesc.BlendMode.RenderTargetWriteMask = BlendingMode::ColorWrite::None;
|
||||
psDesc.HS = nullptr;
|
||||
psDesc.DS = nullptr;
|
||||
|
||||
@@ -71,7 +71,7 @@ public:
|
||||
public:
|
||||
// [MaterialShader]
|
||||
DrawPass GetDrawModes() const override;
|
||||
bool CanUseInstancing(InstancingHandler& handler) const override;
|
||||
bool CanUseInstancing(const RenderContext& renderContext, InstancingHandler& handler) const override;
|
||||
void Bind(BindParameters& params) override;
|
||||
void Unload() override;
|
||||
|
||||
|
||||
@@ -126,9 +126,10 @@ public:
|
||||
/// <summary>
|
||||
/// Returns true if material can use draw calls instancing.
|
||||
/// </summary>
|
||||
/// <param name="renderContext">The rendering context.</param>
|
||||
/// <param name="handler">The output data for the instancing handling used to hash, batch and write draw calls. Valid only when function returns true.</param>
|
||||
/// <returns>True if can use instancing, otherwise false.</returns>
|
||||
virtual bool CanUseInstancing(InstancingHandler& handler) const
|
||||
virtual bool CanUseInstancing(const RenderContext& renderContext, InstancingHandler& handler) const
|
||||
{
|
||||
#if BUILD_DEBUG
|
||||
handler = { nullptr, nullptr };
|
||||
|
||||
@@ -78,7 +78,7 @@ void ForwardShadingFeature::Bind(MaterialShader::BindParameters& params, Span<by
|
||||
for (int32 i = 0; i < cache->EnvironmentProbes.Count(); i++)
|
||||
{
|
||||
const RenderEnvironmentProbeData& probe = cache->EnvironmentProbes.Get()[i];
|
||||
const float sphereCullDistance = objectBounds.Radius + probe.Radius;
|
||||
const float sphereCullDistance = (float)objectBounds.Radius + probe.Radius;
|
||||
const float distanceSq = Float3::DistanceSquared(probe.Position, objectBounds.Center);
|
||||
if (distanceSq <= sphereCullDistance * sphereCullDistance && distanceSq < minDistanceSq)
|
||||
{
|
||||
|
||||
@@ -189,7 +189,7 @@ bool TerrainMaterialShader::Load()
|
||||
psDesc.DepthClipEnable = false;
|
||||
psDesc.DepthWriteEnable = true;
|
||||
psDesc.DepthEnable = true;
|
||||
psDesc.DepthFunc = ComparisonFunc::Less;
|
||||
psDesc.DepthFunc = ComparisonFunc::Default;
|
||||
psDesc.BlendMode.RenderTargetWriteMask = BlendingMode::ColorWrite::None;
|
||||
psDesc.HS = nullptr;
|
||||
psDesc.DS = nullptr;
|
||||
|
||||
@@ -305,7 +305,7 @@ void Mesh::Draw(const RenderContext& renderContext, const DrawInfo& info, float
|
||||
drawCall.Surface.GeometrySize = _box.GetSize();
|
||||
drawCall.Surface.PrevWorld = info.DrawState->PrevWorld;
|
||||
drawCall.Surface.Lightmap = (info.Flags & StaticFlags::Lightmap) != StaticFlags::None ? info.Lightmap : nullptr;
|
||||
drawCall.Surface.LightmapUVsArea = info.LightmapUVs ? *info.LightmapUVs : Rectangle::Empty;
|
||||
drawCall.Surface.LightmapUVsArea = info.LightmapUVs ? *info.LightmapUVs : Half4::Zero;
|
||||
drawCall.Surface.LODDitherFactor = lodDitherFactor;
|
||||
drawCall.PerInstanceRandom = info.PerInstanceRandom;
|
||||
drawCall.StencilValue = info.StencilValue;
|
||||
@@ -368,7 +368,7 @@ void Mesh::Draw(const RenderContextBatch& renderContextBatch, const DrawInfo& in
|
||||
drawCall.Surface.GeometrySize = _box.GetSize();
|
||||
drawCall.Surface.PrevWorld = info.DrawState->PrevWorld;
|
||||
drawCall.Surface.Lightmap = (info.Flags & StaticFlags::Lightmap) != StaticFlags::None ? info.Lightmap : nullptr;
|
||||
drawCall.Surface.LightmapUVsArea = info.LightmapUVs ? *info.LightmapUVs : Rectangle::Empty;
|
||||
drawCall.Surface.LightmapUVsArea = info.LightmapUVs ? *info.LightmapUVs : Half4::Zero;
|
||||
drawCall.Surface.LODDitherFactor = lodDitherFactor;
|
||||
drawCall.PerInstanceRandom = info.PerInstanceRandom;
|
||||
drawCall.StencilValue = info.StencilValue;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user